Agile Web Development with Rails phần 7 pdf

55 707 1
Agile Web Development with Rails phần 7 pdf

Đang tải... (xem toàn văn)

Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống

Thông tin tài liệu

CACHING,PART ONE 321 Expiring Pages Creating cached pages is only one half of the equation. If the content ini- tially used to create these pages changes, the cached versions will become out-of-date, and we’ll need a way of expiring them. The trick is to code the application to notice when the data used to create a dynamic page has changed and then to remove the cached version. The next time a request comes through for that URL, the cached page will be regenerated based on the new content. Expiring Pages Explicitly The low-level way to remove cached pages is with the expire_page() and expire_action( ) methods. These take the same parameters as url_for() and expire the cached page that matches the generated URL. For example, our content controller might have an action that allows us to create an article and another action that updates an existing article. When we create an article, the list of articles on the public page will become obsolete, so we call expire_page( ), passing in the action name that displays the public page. When we update an existing article, the public index page remains unchanged (at least, it does in our application), but any cached version of this particular article should be deleted. Because this cache was created using caches_action, we need to expire the page using expire_action( ), passing in the action name and the article id. File 17 def create_article article = Article.new(params[:article]) if article.save expire_page :action => "public_content" else # end end def update_article article = Article.new(params[:article]) if article.save expire_action :action => "premium_content", :id => article else # end end The method that deletes an article does a bit more work—it has to both invalidate the public index page and remove the specific article page. File 17 def delete_article Article.destroy(params[:id]) expire_page :action => "public_content" expire_action :action => "premium_content", :id => params[:id] end Report erratum CACHING,PART ONE 322 Expiring Pages Implicitly The expire_xxx methods work well, but they also couple the caching func- tion to the code in your controllers. Every time you change something in the database, you also have to work out which cached pages this might affect. While this is easy for smaller applications, this gets more difficult as the application grows. A change made in one controller might affect pages cached in another. Business logic in helper methods, which really shouldn’t have to know about HTML pages, now needs to worry about expiring cached pages. Fortunately, Rails can simplify some of this coupling using sweepers.A sweepers sweeper is a special kind of observer on your model objects. When some- thing significant happens in the model, the sweeper expires the cached pages that depend on that model’s data. Your application can have as many sweepers as it needs. You’ll typically create a separate sweeper to manage the caching for each controller. Put your sweeper code in app/models. File 20 class ArticleSweeper < ActionController::Caching::Sweeper observe Article # If we create a new article, the public list # of articles must be regenerated def after_create(article) expire_public_page end # If we update an existing article, the cached version # of that particular article becomes stale def after_update(article) expire_article_page(article.id) end # Deleting a page means we update the public list # and blow away the cached article def after_destroy(article) expire_public_page expire_article_page(article.id) end private def expire_public_page expire_page(:controller => "content", :action => 'public_content') end def expire_article_page(article_id) expire_action(:controller => "content", :action => "premium_content", :id => article_id) end end The flow through the sweeper is somewhat convoluted. Report erratum CACHING,PART ONE 323 • The sweeper is defined as an observer on one or more Active Record classes. In this case it observes the Article model. (We first talked about observers back on page 270.) The sweeper uses hook methods (such as after_update( )) to expire cached pages if appropriate. • The sweeper is also declared to be active in a controller using the cache_sweeper directive. class ContentController < ApplicationController before_filter :verify_premium_user, :except => :public_content caches_page :public_content caches_action :premium_content cache_sweeper :article_sweeper, :only => [ :create_article, :update_article, :delete_article ] # • If a request comes in that invokes one of the actions that the sweeper is filtering, the sweeper is activated. If any of the Active Record observer methods fires, the page and action expiry methods will be called. If the Active Record observer gets invoked but the current action is not selected as a cache sweeper, the expire calls in the sweeper are ignored. Otherwise, the expiry takes place. Time-Based Expiry of Cached Pages Consider a site that shows fairly volatile information such as stock quotes or news headlines. If we did the style of caching where we expired a page whenever the underlying information changed, we’d be expiring pages con- stantly. The cache would rarely get used, and we’d lose the benefit of having it. In these circumstances, you might want to consider switching to time- based caching, where you build the cached pages exactly as we did previ- ously but don’t expire them when their content becomes obsolete. You run a separate background process that periodically goes into the cache directory and deletes the cache files. You choose how this deletion occurs—you could simply remove all files, the files created more than so many minutes ago, or the files whose names match some pattern. That part is application-specific. The next time a request comes in for one of these pages, it won’t be satis- fied from the cache and the application will handle it. In the process, it’ll automatically repopulate that particular page in the cache, lightening the load for subsequent fetches of this page. Report erratum THE PROBLEM WITH GET REQUESTS 324 Where do you find the cache files to delete? Not surprisingly, this is con- figurable. Page cache files are by default stored in the public directory of your application. They’ll be named after the URL they are caching, with an . html extension. For example, the page cache file for content/show/1 will be in app/public/content/show/1.html This naming scheme is no coincidence; it allows the web server to find the cache files automatically. You can, however, override the defaults using ActionController::Base.page_cache_directory = "dir/name" ActionController::Base.page_cache_extension = ".html" Action cache files are not by default stored in the regular file system direc- tory structure and cannot be expired using this technique. 16.9 The Problem with GET Requests At the time this book was written, there’s a debate raging about the way web applications use links to trigger actions. Here’s the issue. Almost since HTTP was invented, it was recognized that there is a fundamental difference between HTTP GET and HTTP POST requests. Tim Berners-Lee wrote about it back in 1996. 13 Use GET requests to retrieve information from the server, and use POST requests to request a change of state on the server. The problem is that this rule has been widely ignored by web developers. Every time you see an application with an Add To Cart link, you’re seeing a violation, because clicking on the link generates a GET request that changes the state of the application (it adds something to the cart in this example). Up until now, we’ve gotten away with it. This changed in the spring of 2005 when Google released their Google Web Accelerator (GWA), a piece of client-side code that sped up end users’ browsing. It did this in part by precaching pages. While the user reads the current page, the accelerator software scans it for links and arranges for the corresponding pages to be read and cached in the background. Now imagine that you’re looking at an online store containing Add To Cart links. While you’re deciding between the maroon hot pants and the purple tank top, the accelerator is busy following links. Each link followed adds a new item to your cart. 13 http://www.w3.org/DesignIssues/Axioms Report erratum THE PROBLEM WITH GET REQUESTS 325 The problem has always been there. Search engines and other spiders constantly follow links on public web pages. Normally, though, these links that invoke state-changing actions in applications (such as our Add To Cart link) are not exposed until the user has started some kind of trans- action, so the spider won’t see or follow them. The fact that the GWA runs on the client side of the equation suddenly exposed all these links. In an ideal world, every request that has a side effect would be a POST, 14 not a GET. Rather than using links, web pages would use forms and but- tons whenever they want the server to do something active. The world, though, isn’t ideal, and there are thousands (millions?) of pages out there that break the rules when it comes to GET requests. The default link_to( ) method in Rails generates a regular link, which when clicked creates a GET request. But this certainly isn’t a Rails-specific problem. Many large and successful sites do the same. Is this really a problem? As always, the answer is “It depends.” If you code applications with dangerous links (such as Delete Or der, Fire Employee,or Fire Missile), there’s the risk that these links will be followed unintention- ally and your application will dutifully perform the requested action. Fixing the GET Problem Following a simple rule can effectively eliminate the risk associated with dangerous links. The underlying axiom is straightforward: never allow astraight <a href=" " link that does something dangerous to be followed without some kind of human intervention. Here are some techniques for making this work in practice. • Use forms and buttons, rather than hyperlinks, to do things that change state on the server. Forms can be submitted using POST requests, which means that they will not be submitted by spiders following links, and browsers will warn you if you reload a page. Within Rails, this means using the button_to( ) helper to point to dan- gerous actions. However, you’ll need to design your web pages with care. HTML does not allow forms to be nested, so you can’t use but- ton_to ( ) within another form. • Use confirmation pages. For cases where you can’t use a form, create a link that references a page that asks for confirmation. This confir- 14 Or a rarer PUT or DELETE request. Report erratum THE PROBLEM WITH GET REQUESTS 326 mation should be triggered by the submit button of a form; hence, the destructive action won’t be triggered automatically. Some folks also use the following techniques, hoping they’ll prevent the problem. They don’t work. • Don’t think your actions are protected just because you’ve installed a JavaScript confirmation box on the link. For example, Rails lets you write link_to(:action => :delete, :confirm => "Are you sure?") This will stop users from accidentally doing damage by clicking the link, but only if they have JavaScript enabled in their browsers. It also does nothing to prevent spiders and automated tools from blindly following the link anyway. • Don’t think your actions are protected if they appear only in a portion of your web site that requires users to log in. While this does prevent global spiders (such as those employed by the search engines) from getting to them, it does not stop client-side technologies (such as Google Web Accelerator). • Don’t think your actions are protected if you use a robots.txt file to control which pages are spidered. This will not protect you from client-side technologies. All this might sound fairly bleak. The real situation isn’t that bad. Just follow one simple rule when you design your site, and you’ll avoid all these issues. Put All Destructive Actions Behind a POST Request Web Health Warning Report erratum Chapter 17 Action View We’ve seen how the routing component determines which controller to use and how the controller chooses an action. We’ve also seen how the con- troller and action between them decide what to render back to the user. Normally that rendering takes place at the end of the action, and typically it involves a template. That’s what this chapter is all about. The Action- View module encapsulates all the functionality needed to render templates, most commonly generating HTML or XML back to the user. As its name suggests, ActionView is the view part of our MVC trilogy. 17.1 Templates When you write a view, you’re writing a template: something that will get expanded to generate the final result. To understand how these templates work, we need to look at three things • Where the templates go • The environment they run in, and • What goes inside them. Where Templates Go The render( ) method expects to find templates under the directory defined by the global template_root configuration option. By default, this is set to the directory app/views of the current application. Within this direc- tory, the convention is to have a separate subdirectory for the views of each controller. Our Depot application, for instance, includes admin and store controllers. As a result, we have templates in app/views/admin and app/views/store. Each directory typically contains templates named after the actions in the corresponding controller. TEMPLATES 328 You can also have templates that aren’t named after actions. These can be rendered from the controller using calls such as render(:action => 'fake_action_name') render(:template => 'controller/name) render(:file => 'dir/template') The last of these allows you to store templates anywhere on your file sys- tem. This is useful if you want to share templates across applications. The Template Environment Templates contain a mixture of fixed text and code. The code is used to add dynamic content to the template. That code runs in an environment that gives it access to the information set up by the controller. • All instance variables of the controller are also available in the tem- plate. This is how actions communicate data to the templates. • The controller objects headers, params, request, response,andsession are available as accessor methods in the view. In general, the view code probably shouldn’t be using these directly, as responsibility for handling them should rest with the controller. However, we do find this useful when debugging. For example, the following rhtml template uses the debug( ) method to display the contents of the request, the details of the parameters, and the current response. <h4>Request</h4> <%= debug(request) %> <h4>Params</h4> <%= debug(params) %> <h4>Response</h4> <%= debug(response) %> • The current controller object is accessible using the attribute named controller. This allows the template to call any public method in the controller (including the methods in ActionController). • The path to the base directory of the templates is available in the attribute base_path. What Goes in a Template Out of the box, Rails support two types of template. • rxml templates use the Builder library to construct XML responses. • rhtml templates are a mixture of HTML and embedded Ruby, and are typically used to generate HTML pages. We’ll talk briefly about Builder next, then look at rhtml. The rest of the chapter applies equally to both. Report erratum BUILDER TEMPLATES 329 17.2 Builder templates Builder is a freestanding library that lets you express structured text (such as XML) in code. 1 A Builder template (in a file with an .rxml extension) contains Ruby code that uses the Builder library to generate XML. Here’s a simple Builder template that outputs a list of product names and prices in XML. xml.div(:class => "productlist")do xml.timestamp(Time.now) @products.each do |product| xml.product do xml.productname(product.title) xml.price(product.price, :currency => "USD") end end end With an appropriate collection of products (passed in from the controller), the template might produce something such as <div class="productlist"> <timestamp>Tue Apr 19 15:54:26 CDT 2005</timestamp> <product> <productname>Pragmatic Programmer</productname> <price currency="USD">39.96</price> </product> <product> <productname>Programming Ruby</productname> <price currency="USD">44.95</price> </product> </div> Notice how Builder has taken the names of methods and converted them to XML tags; when we said xml.price, it created a tag called < price > whose contents were the first parameter and whose attributes were set from the subsequent hash. If the name of the tag you want to use conflicts with an existing method name, you’ll need to use the tag!( ) method to generate the tag. xml.tag!("id", product.id) Builder can generate just about any XML you need: it supports name- spaces, entities, processing instructions, and even XML comments. Have a look at the Builder documentation for details. 1 Builder is available on RubyForge (http://builder.rubyforge.org/) and via RubyGems. Rails comes packaged with its own copy of Builder, so you won’t have to download anything to get started. Report erratum RHTML TEMPLATES 330 17.3 RHTML Templates At its simplest, an rhtml template is just a regular HTML file. If a template contains no dynamic content, it is simply sent as-is to the user’s browser. The following is a perfectly valid rhtml template. <h1>Hello, Dave!</h1> <p> How are you, today? </p> However, applications that just render static templates tend to be a bit boring to use. We can spice them up using dynamic content. <h1>Hello, Dave!</h1> <p> It 's <%= Time.now %> </p> If you’re a JSP programmer, you’ll recognize this as an inline expression: any code between <%= and %> is evaluated, the result is converted to a string using to_s( ), and that string is substituted into the resulting page. The expression inside the tags can be arbitrary code. <h1>Hello, Dave!</h1> <p> It 's <%= require 'date' DAY_NAMES = %w{ Sunday Monday Tuesday Wednesday Thursday Friday Saturday } today = Date.today DAY_NAMES[today.wday] %> </p> Putting lots of business logic into a template is generally considered to be a Very Bad Thing, and you’ll risk incurring the wrath of the coding police should you get caught. We’ll look at a better way of handling this when we discuss helpers on page 332. Sometimes you need code in a template that doesn’t directly generate any output. If you leave the equals sign off the opening tag, the contents are executed, but nothing is inserted into the template. We could have written the previous example as <% require 'date' DAY_NAMES = %w{ Sunday Monday Tuesday Wednesday Thursday Friday Saturday } today = Date.today %> <h1>Hello, Dave!</h1> <p> It 's <%= DAY_NAMES[today.wday] %>. Tomorrow is <%= DAY_NAMES[(today + 1).wday] %>. </p> Report erratum [...]... 66.6 67% 1) %> 66 .7% 212-555-1212 Report erratum 335 F ORMATTING H ELPERS true, :delimiter => " ") %> (212) 555 1212 12,345, 678 12_345_ 678 16.6 67. .. lend themselves well to dealing with presentational logic in a succinct and effective way To cope, these environments come up with an alternative language to be used instead of the primary when dealing with the view PHP has Smarty, Java has Velocity, Python has Cheetah Rails doesn’t have anything because Ruby is already an incredibly wellsuited language for dealing with presentational logic Do you... logic See the documentation for helper_method for details 17. 5 Formatting Helpers Rails comes with a bunch of built-in helper methods, available to all views In this section we’ll touch on the highlights, but you’ll probably want to look at the Action View RDoc for the specifics—there’s a lot of functionality in there One set of helper methods deals with the formatting of dates, numbers, and text 16 .7 The debug( ) method dumps out its parameter using YAML and escapes the result so it can be displayed in an HTML page This can help when trying to look at the values in model objects or request parameters - !ruby/hash:HashWithIndifferentAccess name: Dave language: Ruby action: objects controller: test Yet another set of helpers deal with text... Finally, the JavaScriptHelper module defines a number of helpers for working with JavaScript These create JavaScript snippets that run in the browser to generate special effects and to have the page dynamically interact with our application That’s the subject of a separate chapter, Chapter 18, The Web, V2.0, on page 373 By default, image and stylesheet assets are assumed to live in the /images and... populating the current page array ourselves These different uses are all covered in the RDoc 17. 8 Form Helpers Rails features a fully integrated web stack This is most apparent in the way that the model, controller, and view components interoperate to support creating and editing information in database tables Figure 17. 2, on the next page, shows how the various attributes in the model pass through the controller... user.update_attributes(params[:user]) Rails integration goes deeper than this Looking at the rthml file in Figure 17. 2 you can see that the template uses a set of helper methods to create the form’s HTML, methods such as form_tag( ) and text_field( ) Let’s look at these helper methods next Form Helpers HTML forms in templates should start with a form_tag( ) and end with end_form_tag( ) The first parameter... the values from the form for that object In the controller, this could be used to update all product rows with something like File 158 Product.update(params[:product].keys, params[:product].values) ports tags with type="file", but we’ll discuss these in Section 17. 8, Uploading Files to Rails Applications, on page 350.) All helper methods take at least two parameters The first is the name of an... objects interact with models in another important way; they are aware of the errors structure held within each model and will use it to flag attributes that have failed validation When constructing the HTML for each field in a model, the helper methods invoke that model’s errors.on(field) method If any errors are returned, the generated HTML will be wrapped in tag with class="fieldWithErrors" If you... @params[:arg2], :size => 3) %> Without error checking, the controller code would be trivial def calculate if request.post? @result = Float(params[:arg1]).send(params[:op], params[:arg2]) end end However, running a web page without error checking is a luxury we can’t afford, so we’ll have to go with the longer version File 163 def calculate if request.post? @errors . number _with_ delimiter(12345 678 ) %> 12,345, 678 <%= number _with_ delimiter(12345 678 , delimiter = "_") %> 12_345_ 678 <%= number _with_ precision(50.0/3) %> 16.6 67 <%= number _with_ precision(50.0/3,. warn you if you reload a page. Within Rails, this means using the button_to( ) helper to point to dan- gerous actions. However, you’ll need to design your web pages with care. HTML does not allow. to dealing with presentational logic in a succinct and effective way. To cope, these environments come up with an alternative language to be used instead of the primary when dealing with the view.

Ngày đăng: 07/08/2014, 00:22

Từ khóa liên quan

Tài liệu cùng người dùng

Tài liệu liên quan