From 559178b80d51af003c3cb12629099f939682b994 Mon Sep 17 00:00:00 2001 From: Pratik Naik Date: Sat, 25 Oct 2008 03:43:38 +0530 Subject: Update guides and release notes --- railties/doc/guides/html/2_2_release_notes.html | 19 ++- .../doc/guides/html/actioncontroller_basics.html | 2 +- .../guides/html/getting_started_with_rails.html | 166 +++++++++++++++++++-- .../doc/guides/html/layouts_and_rendering.html | 140 ++++++++++++++++- railties/doc/guides/html/security.html | 56 ++++++- railties/doc/guides/source/2_2_release_notes.txt | 11 +- .../source/actioncontroller_basics/methods.txt | 2 +- .../doc/guides/source/active_record_basics.txt | 157 +++++++++++++------ .../guides/source/getting_started_with_rails.txt | 131 +++++++++++++++- .../doc/guides/source/layouts_and_rendering.txt | 124 ++++++++++++++- railties/doc/guides/source/security.txt | 63 +++++++- 11 files changed, 785 insertions(+), 86 deletions(-) (limited to 'railties') diff --git a/railties/doc/guides/html/2_2_release_notes.html b/railties/doc/guides/html/2_2_release_notes.html index c657be20b4..d21905f715 100644 --- a/railties/doc/guides/html/2_2_release_notes.html +++ b/railties/doc/guides/html/2_2_release_notes.html @@ -586,16 +586,15 @@ by Lorenzo Bettini http://www.lorenzobettini.it http://www.gnu.org/software/src-highlite -->
class Photo < ActiveRecord::Base
-  belongs_to :Product
+  belongs_to :product
 end
 
 class Product < ActiveRecord::Base
-  has_many :products
+  has_many :photos
 end
 
 # Get all products with copyright-free photos:
-Product.find(:all, :joins => :photo,
-  :conditions => { :photos => { :copyright => false }})
+Product.all(:joins => :photos, :conditions => { :photos => { :copyright => false }})
 

5.6. Other ActiveRecord Changes

-

The Layouts & rendering guide explains this in more detail.

+

The Layouts & rendering guide explains this in more detail.

3. Parameters

diff --git a/railties/doc/guides/html/getting_started_with_rails.html b/railties/doc/guides/html/getting_started_with_rails.html index e7be98d34a..d9a1779d56 100644 --- a/railties/doc/guides/html/getting_started_with_rails.html +++ b/railties/doc/guides/html/getting_started_with_rails.html @@ -269,6 +269,16 @@ ul#navMain {
  • + DRYing up the Code + +
  • +
  • Adding a Second Model
      @@ -480,6 +490,24 @@ Transferring representations of the state of that resource between system compon

      For example, to a Rails application a request such as this:

      DELETE /photos/17

      would be understood to refer to a photo resource with the ID of 17, and to indicate a desired action - deleting that resource. REST is a natural style for the architecture of web applications, and Rails makes it even more natural by using conventions to shield you from some of the RESTful complexities.

      +

      If you’d like more details on REST as an architectural style, these resources are more approachable than Fielding’s thesis:

      +
  • 3. Creating a New Rails Project

    @@ -1402,7 +1430,7 @@ http://www.gnu.org/software/src-highlite --> Note -Sharp-eyed readers will have noticed that the form_for declaration is identical for the create and edit views. Rails generates different code for the two forms because it's smart enough to notice that in the one case it's being passed a new record that has never been saved, and in the other case an existing record that has already been saved to the database. In a production Rails application, you would ordinarily eliminate this duplication by moving identical code to a partial template, which you could then include in both parent templates. But the scaffold generator tries not to make too many assumptions, and generates code that’s easy to modify if you want different forms for create and edit. +Sharp-eyed readers will have noticed that the form_for declaration is identical for the new and edit views. Rails generates different code for the two forms because it's smart enough to notice that in the one case it's being passed a new record that has never been saved, and in the other case an existing record that has already been saved to the database. In a production Rails application, you would ordinarily eliminate this duplication by moving identical code to a partial template, which you could then include in both parent templates. But the scaffold generator tries not to make too many assumptions, and generates code that’s easy to modify if you want different forms for create and edit.

    6.12. Destroying a Post

    @@ -1424,10 +1452,124 @@ http://www.gnu.org/software/src-highlite -->

    The destroy method of an Active Record model instance removes the corresponding record from the database. After that's done, there isn't any record to display, so Rails redirects the user's browser to the index view for the model.

    -

    7. Adding a Second Model

    +

    7. DRYing up the Code

    +
    +

    At this point, it’s worth looking at some of the tools that Rails provides to eliminate duplication in your code. In particular, you can use partials to clean up duplication in views and filters to help with duplication in controllers.

    +

    7.1. Using Partials to Eliminate View Duplication

    +

    As you saw earlier, the scaffold-generated views for the new and edit actions are largely identical. You can pull the shared code out into a partial template. This requires editing the new and edit views, and adding a new template:

    +

    new.html.erb: +[source, ruby]

    +
    +
    +
    <h1>New post</h1>
    +
    +<%= render :partial => "form" %>
    +
    +<%= link_to 'Back', posts_path %>
    +
    +

    edit.html.erb: +[source, ruby]

    +
    +
    +
    <h1>Editing post</h1>
    +
    +<%= render :partial => "form" %>
    +
    +<%= link_to 'Show', @post %> |
    +<%= link_to 'Back', posts_path %>
    +
    +

    _form.html.erb: +[source, ruby]

    +
    +
    +
    <% form_for(@post) do |f| %>
    +  <%= f.error_messages %>
    +
    +  <p>
    +    <%= f.label :name %><br />
    +    <%= f.text_field :name %>
    +  </p>
    +  <p>
    +    <%= f.label :title, "title" %><br />
    +    <%= f.text_field :title %>
    +  </p>
    +  <p>
    +    <%= f.label :content %><br />
    +    <%= f.text_area :content %>
    +  </p>
    +  <p>
    +    <%= f.submit "Save" %>
    +  </p>
    +<% end %>
    +
    +

    Now, when Rails renders the new or edit view, it will insert the _form partial at the indicated point. Note the naming convention for partials: if you refer to a partial named form inside of a view, the corresponding file is _form.html.erb, with a leading underscore.

    +

    For more information on partials, refer to the Layouts and Rending in Rails guide.

    +

    7.2. Using Filters to Eliminate Controller Duplication

    +

    At this point, if you look at the controller for posts, you’ll see some duplication:

    +
    +
    +
    class PostsController < ApplicationController
    +  # ...
    +  def show
    +    @post = Post.find(params[:id])
    +        # ...
    +  end
    +
    +  def edit
    +    @post = Post.find(params[:id])
    +  end
    +
    +  def update
    +    @post = Post.find(params[:id])
    +    # ...
    +  end
    +
    +  def destroy
    +    @post = Post.find(params[:id])
    +    # ...
    +  end
    +end
    +
    +

    Four instances of the exact same line of code doesn’t seem very DRY. Rails provides filters as a way to address this sort of repeated code. In this case, you can DRY things up by using a before_filter:

    +
    +
    +
    class PostsController < ApplicationController
    +  before_filter :find_post, :only => [:show, :edit, :update, :destroy]
    +  # ...
    +  def show
    +        # ...
    +  end
    +
    +  def edit
    +  end
    +
    +  def update
    +    # ...
    +  end
    +
    +  def destroy
    +    # ...
    +  end
    +
    +  private
    +    def find_post
    +      @post = Post.find(params[:id])
    +    end
    +end
    +
    +

    Rails runs before filters before any action in the controller. You can use the :only clause to limit a before filter to only certain actions, or an :except clause to specifically skip a before filter for certain actions. Rails also allows you to define after filters that run after processing an action, as well as around filters that surround the processing of actions. Filters can also be defined in external classes to make it easy to share them between controllers.

    +

    For more information on filters, see the Action Controller Basics guide.

    +
    +

    8. Adding a Second Model

    Now that you've seen what's in a model built with scaffolding, it's time to add a second model to the application. The second model will handle comments on blog posts.

    -

    7.1. Generating a Model

    +

    8.1. Generating a Model

    Models in Rails use a singular name, and their corresponding database tables use a plural name. For the model to hold comments, the convention is to use the name Comment. Even if you don't want to use the entire apparatus set up by scaffolding, most Rails developers still use generators to make things like models and controllers. To create the new model, run this command in your terminal:

    $ rake db:migrate
     

    Rails is smart enough to only execute the migrations that have not already been run against this particular database.

    -

    7.2. Associating Models

    -

    Active Record associations let you declaratively quantify the relationship between two models. In the case of comments and posts, you could write out the relationships this way:

    +

    8.2. Associating Models

    +

    Active Record associations let you easily declare the relationship between two models. In the case of comments and posts, you could write out the relationships this way:

    • @@ -1538,10 +1680,10 @@ http://www.gnu.org/software/src-highlite --> Tip -For more information on Active Record associations, see the Active Record Associations guide. +For more information on Active Record associations, see the Active Record Associations guide.

    -

    7.3. Adding a Route

    +

    8.3. Adding a Route

    Routes are entries in the config/routes.rb file that tell Rails how to match incoming HTTP requests to controller actions. Open up that file and find the existing line referring to posts. Then edit it as follows:

    For more information on routing, see the Rails Routing from the Outside In guide.
    -

    7.4. Generating a Controller

    +

    8.4. Generating a Controller

    With the model in hand, you can turn your attention to creating a matching controller. Again, there's a generator for this:

    @comment = @post.comments.build
     

    This creates a new Comment object and sets up the post_id field to have the id from the specified Post object in a single operation.

    -

    7.5. Building Views

    +

    8.5. Building Views

    Because you skipped scaffolding, you'll need to build views for comments "by hand." Invoking script/generate controller will give you skeleton views, but they'll be devoid of actual content. Here's a first pass at fleshing out the comment views.

    The index.html.erb view:

    @@ -1795,7 +1937,7 @@ http://www.gnu.org/software/src-highlite --> <%= link_to 'Back', post_comments_path(@post) %>

    Again, the added complexity here (compared to the views you saw for managing comments) comes from the necessity of juggling a post and its comments at the same time.

    -

    7.6. Hooking Comments to Posts

    +

    8.6. Hooking Comments to Posts

    As a final step, I'll modify the show.html.erb view for a post to show the comments on that post, and to allow managing those comments:

    Note that each post has its own individual comments collection, accessible as @post.comments. That's a consequence of the declarative associations in the models. Path helpers such as post_comments_path come from the nested route declaration in config/routes.rb.

    -

    8. What's Next?

    +

    9. What's Next?

    Now that you've seen your first Rails application, you should feel free to update it and experiment on your own. But you don't have to do everything without help. As you need assistance getting up and running with Rails, feel free to consult these support resources:

    -

    9. Changelog

    +

    10. Changelog

      diff --git a/railties/doc/guides/html/layouts_and_rendering.html b/railties/doc/guides/html/layouts_and_rendering.html index 916cdd2053..aeda0c5c5e 100644 --- a/railties/doc/guides/html/layouts_and_rendering.html +++ b/railties/doc/guides/html/layouts_and_rendering.html @@ -523,7 +523,7 @@ http://www.gnu.org/software/src-highlite -->
      render :file => filename, :content_type => 'application/rss'
       
    The :layout Option
    -

    With most of the options to render, the rendered content is displayed as part of the current layout. You'll learn more about layouts and how to use them later in this guide. To find the current layout, Rails first looks for a file in app/views/layouts with the same base name as the controller. For example, rendering actions from the PhotosController class will use /app/views/layouts/photos.html.erb. If there is no such controller-specific layout, Rails will use /app/views/layouts/application.html.erb.

    +

    With most of the options to render, the rendered content is displayed as part of the current layout. You'll learn more about layouts and how to use them later in this guide.

    You can use the :layout option to tell Rails to use a specific file as the layout for the current action:

    render :xml => photo, :location => photo_url(photo)
     
    -

    2.2.11. Avoiding Double Render Errors

    +

    2.2.11. Finding Layouts

    +

    To find the current layout, Rails first looks for a file in app/views/layouts with the same base name as the controller. For example, rendering actions from the PhotosController class will use /app/views/layouts/photos.html.erb. If there is no such controller-specific layout, Rails will use /app/views/layouts/application.html.erb. If there is no .erb layout, Rails will use a .builder layout if one exists. Rails also provides several ways to more precisely assign specific layouts to individual controllers and actions.

    +
    Specifying Layouts on a per-Controller Basis
    +

    You can override the automatic layout conventions in your controllers by using the layout declaration in the controller. For example:

    +
    +
    +
    class ProductsController < ApplicationController
    +  layout "inventory"
    +  #...
    +end
    +
    +

    With this declaration, all methods within ProductsController will use app/views/layouts/inventory.html.erb for their layout.

    +

    To assign a specific layout for the entire application, use a declaration in your ApplicationController class:

    +
    +
    +
    class ApplicationController < ActionController::Base
    +  layout "main"
    +  #...
    +end
    +
    +

    With this declaration, all views in the entire application will use app/views/layouts/main.html.erb for their layout.

    +
    Choosing Layouts at Runtime
    +

    You can use a symbol to defer the choice of layout until a request is processed:

    +
    +
    +
    class ProductsController < ApplicationController
    +  layout :products_layout
    +
    +  def show
    +    @product = Product.find(params[:id])
    +  end
    +
    +  private
    +    def products_layout
    +      @current_user.special? ? "special" : "products"
    +    end
    +
    +end
    +
    +

    Now, if the current user is a special user, they'll get a special layout when viewing a product. You can even use an inline method to determine the layout:

    +
    +
    +
    class ProductsController < ApplicationController
    +  layout proc{ |controller| controller.
    +  # ...
    +end
    +
    +
    Conditional Layouts
    +

    Layouts specified at the controller level support :only and :except options that take either a method name or an array of method names:

    +
    +
    +
    class ProductsController < ApplicationController
    +  layout "inventory", :only => :index
    +  layout "product", :except => [:index, :rss]
    +  #...
    +end
    +
    +

    With those declarations, the inventory layout would be used only for the index method, the product layout would be used for everything else except the rss method, and the rss method will have its layout determined by the automatic layout rules.

    +
    Layout Inheritance
    +

    Layouts are shared downwards in the hierarchy, and more specific layouts always override more general ones. For example:

    +
    +
    +
    class ApplicationController < ActionController::Base
    +  layout "main"
    +  #...
    +end
    +
    +class PostsController < ApplicationController
    +  # ...
    +end
    +
    +class SpecialPostsController < PostsController
    +  layout "special"
    +  # ...
    +end
    +
    +class OldPostsController < SpecialPostsController
    +  layout nil
    +
    +  def show
    +    @post = Post.find(params[:id])
    +  end
    +
    +  def index
    +    @old_posts = Post.older
    +    render :layout => "old"
    +  end
    +  # ...
    +end
    +
    +

    In this application:

    +
    +

    2.2.12. Avoiding Double Render Errors

    Sooner or later, most Rails developers will see the error message "Can only render or redirect once per action". While this is annoying, it's relatively easy to fix. Usually it happens because of a fundamental misunderstanding of the way that render works.

    For example, here's some code that will trigger this error:

    @@ -666,8 +799,7 @@ http://www.gnu.org/software/src-highlite -->

    3. Structuring Layouts

    -

    When Rails renders a view as a response, it does so by combining the view with the current layout. To find the current layout, Rails first looks for a file in app/views/layouts with the same base name as the controller. For example, rendering actions from the PhotosController class will use /app/views/layouts/photos.html.erb. If there is no such controller-specific layout, Rails will use /app/views/layouts/application.html.erb. You can also specify a particular layout by using the :layout option to render.

    -

    Within a layout, you have access to three tools for combining different bits of output to form the overall response:

    +

    When Rails renders a view as a response, it does so by combining the view with the current layout (using the rules for finding the current layout that were covered earlier in this guide). Within a layout, you have access to three tools for combining different bits of output to form the overall response:

    • diff --git a/railties/doc/guides/html/security.html b/railties/doc/guides/html/security.html index 4ece0814d5..a135d9b486 100644 --- a/railties/doc/guides/html/security.html +++ b/railties/doc/guides/html/security.html @@ -303,6 +303,8 @@ ul#navMain {

    • Command Line Injection
    • +
    • Header Injection
    • +
  • @@ -316,7 +318,7 @@ ul#navMain {

    This manual describes common security problems in web applications and how to avoid them with Rails. If you have any questions or suggestions, please -mail me at 42 {et} rorsecurity.info. After reading it, you should be familiar with:

    +mail me, Heiko Webers, at 42 {et} rorsecurity.info. After reading it, you should be familiar with:

    • @@ -1206,7 +1208,10 @@ s = sanitize(user_input, :tags => tags, :attributes => %w(href title))

      This example, again, showed that a blacklist filter is never complete. However, as custom CSS in web applications is a quite rare feature, I am not aware of a whitelist CSS filter. If you want to allow custom colours or images, you can allow the user to choose them and build the CSS in the web application. Use Rails' sanitize() method as a model for a whitelist CSS filter, if you really need one.

    8.5. Textile Injection

    If you want to provide text formatting other than HTML (due to security), use a mark-up language which is converted to HTML on the server-side. RedCloth is such a language for Ruby, but without precautions, it is also vulnerable to XSS.

    -

    For example, RedCloth translates test to <em>test<em>, which makes the text italic. However, up to the current version 3.0.4, it is still vulnerable to XSS:

    +
    +
    +
    For example, RedCloth translates _test_ to <em>test<em>, which makes the text italic. However, up to the current version 3.0.4, it is still vulnerable to XSS. Get the http://www.redcloth.org[all-new version 4] that removed serious bugs. However, even that version has http://www.rorsecurity.info/journal/2008/10/13/new-redcloth-security.html[some security bugs], so the countermeasures still apply. Here is an example for version 3.0.4:
    +
    >> RedCloth.new('<script>alert(1)</script>').to_html
    @@ -1241,6 +1246,53 @@ s = sanitize(user_input, :tags => tags, :attributes => %w(href title))system("/bin/echo","hello; rm *")
     # prints "hello; rm *" and does not delete files
    +

    8.9. Header Injection

    +

    HTTP headers are dynamically generated and under certain circumstances user input may be injected. This can lead to false redirection, XSS or HTTP response splitting.

    +

    HTTP request headers have a Referer, User-Agent (client software) and Cookie field, among others. Response headers for example have a status code, Cookie and Location (redirection target URL) field. All of them are user-supplied and may be manipulated with more or less effort. Remember to escape these header fields, too. For example when you display the user agent in an administration area.

    +

    Besides that, it is important to know what you are doing when building response headers partly based on user input. For example you want to redirect the user back to a specific page. To do that you introduced a “referer“ field in a form to redirect to the given address:

    +
    +
    +
    redirect_to params[:referer]
    +
    +

    What happens is that Rails puts the string into the Location header field and sends a 302 (redirect) status to the browser. The first thing a malicious user would do, is this:

    +
    +
    +
    http://www.yourapplication.com/controller/action?referer=http://www.malicious.tld
    +
    +

    And due to a bug in (Ruby and) Rails up to version 2.1.2 (excluding it), a hacker may inject arbitrary header fields; for example like this:

    +
    +
    +
    http://www.yourapplication.com/controller/action?referer=http://www.malicious.tld%0d%0aX-Header:+Hi!
    +http://www.yourapplication.com/controller/action?referer=path/at/your/app%0d%0aLocation:+http://www.malicious.tld
    +
    +

    Note that "%0d%0a" is URL-encoded for "\r\n" which is a carriage-return and line-feed (CRLF) in Ruby. So the resulting HTTP header for the second example will be the following because the second Location header field overwrites the first.

    +
    +
    +
    HTTP/1.1 302 Moved Temporarily
    +(...)
    +Location: http://www.malicious.tld
    +
    +

    So attack vectors for Header Injection are based on the injection of CRLF characters in a header field. And what could an attacker do with a false redirection? He could redirect to a phishing site that looks the same as yours, but asks to login again (and sends the login credentials to the attacker). Or he could install malicious software through browser security holes on that site. Rails 2.1.2 escapes these characters for the Location field in the redirect_to method. Make sure you do it yourself when you build other header fields with user input.

    +

    8.9.1. Response Splitting

    +

    If Header Injection was possible, Response Splitting might be, too. In HTTP, the header block is followed by two CRLFs and the actual data (usually HTML). The idea of Response Splitting is to inject two CRLFs into a header field, followed by another response with malicious HTML. The response will be:

    +
    +
    +
    HTTP/1.1 302 Found [First standard 302 response]
    +Date: Tue, 12 Apr 2005 22:09:07 GMT
    +Location:
Content-Type: text/html
    +
    +
    +HTTP/1.1 200 OK [Second New response created by attacker begins]
    +Content-Type: text/html
    +
    +
    +<html><font color=red>hey</font></html> [Arbitary malicious input is
    +Keep-Alive: timeout=15, max=100         shown as the redirected page]
    +Connection: Keep-Alive
    +Transfer-Encoding: chunked
    +Content-Type: text/html
    +
    +

    Under certain circumstances this would present the malicious HTML to the victim. However, this seems to work with Keep-Alive connections, only (and many browsers are using one-time connections). But you can't rely on this. In any case this is a serious bug, and you should update your Rails to version 2.0.5 or 2.1.2 to eliminate Header Injection (and thus response splitting) risks.

    9. Additional resources

    diff --git a/railties/doc/guides/source/2_2_release_notes.txt b/railties/doc/guides/source/2_2_release_notes.txt index 2623f21371..4543b5309c 100644 --- a/railties/doc/guides/source/2_2_release_notes.txt +++ b/railties/doc/guides/source/2_2_release_notes.txt @@ -149,16 +149,15 @@ You can now specify conditions on join tables using a hash. This is a big help i [source, ruby] ------------------------------------------------------- class Photo < ActiveRecord::Base - belongs_to :Product + belongs_to :product end class Product < ActiveRecord::Base - has_many :products + has_many :photos end # Get all products with copyright-free photos: -Product.find(:all, :joins => :photo, - :conditions => { :photos => { :copyright => false }}) +Product.all(:joins => :photos, :conditions => { :photos => { :copyright => false }}) ------------------------------------------------------- * More information: @@ -197,6 +196,8 @@ User.find_by_name!('Moby') Active Record association proxies now respect the scope of methods on the proxied object. Previously (given User has_one :account) +@user.account.private_method+ would call the private method on the associated Account object. That fails in Rails 2.2; if you need this functionality, you should use +@user.account.send(:private_method)+ (or make the method public instead of private or protected). Please note that if you're overriding +method_missing+, you should also override +respond_to+ to match the behavior in order for associations to function normally. * Lead Contributor: Adam Milligan +* More information: + - link:http://afreshcup.com/2008/10/24/rails-22-change-private-methods-on-association-proxies-are-private/[Rails 2.2 Change: Private Methods on Association Proxies are Private] === Other ActiveRecord Changes @@ -412,4 +413,4 @@ Previously the above code made available a local variable called +customer+ insi == Credits -Release notes compiled by link:http://afreshcup.com[Mike Gunderloy] \ No newline at end of file +Release notes compiled by link:http://afreshcup.com[Mike Gunderloy] diff --git a/railties/doc/guides/source/actioncontroller_basics/methods.txt b/railties/doc/guides/source/actioncontroller_basics/methods.txt index a1ef204adb..370b492e41 100644 --- a/railties/doc/guides/source/actioncontroller_basics/methods.txt +++ b/railties/doc/guides/source/actioncontroller_basics/methods.txt @@ -34,4 +34,4 @@ def new end ---------------------------------------------- -The Layouts & rendering guide explains this in more detail. +The link:../layouts_and_rendering.html[Layouts & rendering guide] explains this in more detail. diff --git a/railties/doc/guides/source/active_record_basics.txt b/railties/doc/guides/source/active_record_basics.txt index 60ee1ef7b7..e0542ff62e 100644 --- a/railties/doc/guides/source/active_record_basics.txt +++ b/railties/doc/guides/source/active_record_basics.txt @@ -1,19 +1,82 @@ -ActiveRecord Basics -================================= -This guide will explain in detail how the ActiveRecord design pattern is used inside Ruby on Rails to make communication with the database clear and easy to understand. -The intent of this guide is to explain the ActiveRecord implementation used by Rails though easy to understand examples, metaphors and detailed explanations of the actual Rails source code. -After reading this guide readers should have a strong grasp of the ActiveRecord concept and how it can be used with or without Rails. Hopefully, some of the philosophical and theoretical intentions discussed here will also make them a stronger and better developer. -== ORM The Blueprint of ActiveRecord -If ActiveRecord is the engine of Rails then ORM is the blueprint of that engine. ORM is short for “Object Relational Mapping” and is a programming concept used to make structures within a system relational. ORM seeks to give semantic meaning to the associations between elements of the system for example tables within a database. -As a thought experiment imagine the components that make up a typical car. There are doors, seats, windows, engines etc. Viewed independently they are simple parts, yet when bolted together through the aid of a blueprint, the parts become a more complex device. ORM is the blueprint that describes how the individual parts relate to one another and in some cases infers the part’s purpose through the way the associations are described. -== ActiveRecord The Engine of Rails -ActiveRecord is a metaphor used to access data within a database. The name “Active Record” was coined by Martin Fowler in his book “Patterns of Enterprise Application Architecture”. ActiveRecord is a conceptual model of the database record and the relationships to other records. -As a side note, from now when I refer to ActiveRecord I’ll be referring to the specific Rails implementation and not the design pattern in general. I make this distinction because, as Rails has evolved so too has the Rails specific implementation of their version of ActiveRecord. -Specifically, the Rails ActiveRecord pattern adds inheritance and associations. The associations are created by using a DSL (domain specific language) of macros, and a STI (Single Table Inheritance) to facilitate the inheritance. -Rails uses ActiveRecord to abstract much of the drudgery or C.R.U.D (explained later) of working with data in databases. Using ActiveRecord Rails automates the mapping between: +Active Record Basics +==================== + +Active Record is a design pattern that mitigates the mind-numbing mental gymnastics often needed to get your application to communicate with a database. This guide explains in detail how This guide uses a mix of real-world examples, metaphors and detailed explanations of the actual Rails source code to help you make the most of AcitveRecord. + +After reading this guide readers should have a strong grasp of the Active Record pattern and how it can be used with or without Rails. Hopefully, some of the philosophical and theoretical intentions discussed here will also make them a stronger and better developer. + +== ORM The Blueprint of Active Record + +If Active Record is the engine of Rails then ORM is the blueprint of that engine. ORM is short for “Object Relational Mapping” and is a programming concept used to make structures within a system relational. As a thought experiment imagine the components that make up a typical car. There are doors, seats, windows, engines etc. Viewed independently they are simple parts, yet when bolted together through the aid of a blueprint, the parts become a more complex device. ORM is the blueprint that describes how the individual parts relate to one another and in some cases infers the part’s purpose through the way the associations are described. + +== Active Record The Engine of Rails + +Active Record is a design pattern used to access data within a database. The name “Active Record” was coined by Martin Fowler in his book “Patterns of Enterprise Application Architecture”. Essentially, when a record is returned from the database instead of being just the data it is wrapped in a class, which gives you methods to control that data with. The rails framework is built around the MVC (Model View Controller) design patten and the Active Record is used as the default Model. + +The Rails community added several useful concepts to their version of Active Record, including inheritance and associations, which are extremely useful for web applications. The associations are created by using a DSL (domain specific language) of macros, and inheritance is achieved through the use of STI (Single Table Inheritance) at the database level. + +By following a few simple conventions the Rails Active Record will automatically map between: + * Classes & Database Tables * Class attributes & Database Table Columns + +=== Rails Active Record Conventions +Here are the key conventions to consider when using Active Record. + +==== Naming Conventions +Database Table - Plural with underscores separating words i.e. (book_clubs) +Model Class - Singular with the first letter of each word capitalized i.e. (BookClub) +Here are some additional Examples: + +[grid="all"] +`-------------`--------------- +Model / Class Table / Schema +---------------------------- +Post posts +LineItem line_items +Deer deer +Mouse mice +Person people +---------------------------- + +==== Schema Conventions + +To take advantage of some of the magic of Rails database tables must be modeled +to reflect the ORM decisions that Rails makes. + +[grid="all"] +`-------------`--------------------------------------------------------------------------------- +Convention +------------------------------------------------------------------------------------------------- +Foreign keys These fields are named table_id i.e. (item_id, order_id) +Primary Key Rails automatically creates a primary key column named "id" unless told otherwise. +------------------------------------------------------------------------------------------------- + +==== Magic Field Names + +When these optional fields are used in your database table definition they give the Active Record +instance additional features. + +NOTE: While these column names are optional they are in fact reserved by ActiveRecord. Steer clear of reserved keywords unless you want the extra functionality. For example, "type" is a reserved keyword +used to designate a table using Single Table Inheritance. If you are not using STI, try an analogous +keyword like "context", that may still accurately describe the data you are modeling. + +[grid="all"] +`------------------------`------------------------------------------------------------------------------ +Attribute Purpose +------------------------------------------------------------------------------------------------------ +created_at / created_on Rails stores the current date & time to this field when creating the record. +updated_at / updated_on Rails stores the current date & time to this field when updating the record. +lock_version Adds optimistic locking to a model link:http://api.rubyonrails.com/classes/ActiveRecord/Locking.html[more about optimistic locking]. +type Specifies that the model uses Single Table Inheritance link:http://api.rubyonrails.com/classes/ActiveRecord/Base.html[more about STI]. +id All models require an id. the default is name is "id" but can be changed using the "set_primary_key" or "primary_key" methods. +_table_name_\_count Can be used to caches the number of belonging objects on the associated class. +------------------------------------------------------------------------------------------------------ + +By default rails assumes all tables will use “id” as their primary key to identify each record. Though fortunately you won’t have explicitly declare this, Rails will automatically create that field unless you tell it not to. + For example suppose you created a database table called cars: + [source, sql] ------------------------------------------------------- mysql> CREATE TABLE cars ( @@ -24,13 +87,17 @@ mysql> CREATE TABLE cars ( model VARCHAR(100) ); ------------------------------------------------------- + Now you created a class named Car, which is to represent an instance of a record from your table. + [source, ruby] ------------------------------------------------------- class Car end ------------------------------------------------------- + As you might expect without defining the explicit mappings between your class and the table it is impossible for Rails or any other program to correctly map those relationships. + [source, ruby] ------------------------------------------------------- >> c = Car.new @@ -39,15 +106,19 @@ As you might expect without defining the explicit mappings between your class an NoMethodError: undefined method `doors' for # from (irb):2 ------------------------------------------------------- + Now you could define a door methods to write and read data to and from the database. In a nutshell this is what ActiveRecord does. According to the Rails API: “Active Record objects don‘t specify their attributes directly, but rather infer them from the table definition with which they‘re linked. Adding, removing, and changing attributes and their type is done directly in the database. Any change is instantly reflected in the Active Record objects. The mapping that binds a given Active Record class to a certain database table will happen automatically in most common cases, but can be overwritten for the uncommon ones.” Lets try our Car class again, this time inheriting from ActiveRecord. + [source, ruby] ------------------------------------------------------- class Car < ActiveRecord::Base end ------------------------------------------------------- + Now if we try to access an attribute of the table ActiveRecord automatically handles the mappings for us, as you can see in the following example. + [source, ruby] ------------------------------------------------------- >> c = Car.new @@ -56,7 +127,30 @@ Now if we try to access an attribute of the table ActiveRecord automatically han => nil ------------------------------------------------------- -This wrapper implements attribute accessors, callbacks and validations, which can make the data more powerful. +Rails further extends this model by giving each ActiveRecord a way of describing the variety of ways records are associated with one another. We will touch on some of these associations later in the guide but I encourage readers who are interested to read the guide to ActiveRecord associations for an in-depth explanation of the variety of ways rails can model associations. +- Associations between objects controlled by meta-programming macros. + +== Philosophical Approaches & Common Conventions +Rails has a reputation of being a zero-config framework which means that it aims to get you off the ground with as little pre-flight checking as possible. This speed benefit is achieved by following “Convention over Configuration”, which is to say that if you agree to live with the defaults then you benefit from a the inherent speed-boost. As Courtneay Gasking put it to me once “You don’t want to off-road on Rails”. ActiveRecord is no different, while it’s possible to override or subvert any of the conventions of AR, unless you have a good reason for doing so you will probably be happy with the defaults. The following is a list of the common conventions of ActiveRecord + +== ActiveRecord Magic + - timestamps + - updates + +== How ActiveRecord Maps your Database. +- sensible defaults +- overriding conventions + +== Growing Your Database Relationships Naturally + +== Attributes + - attribute accessor method. How to override them? + - attribute? + - dirty records + - +== ActiveRecord handling the CRUD of your Rails application - Understanding the life-cycle of an ActiveRecord + +== Validations & Callbacks - Validations * create! * validates_acceptance_of @@ -84,37 +178,4 @@ This wrapper implements attribute accessors, callbacks and validations, which ca * (6) before_create * (-) create * (7) after_create - * (8) after_save - -Rails further extends this model by giving each ActiveRecord a way of describing the variety of ways records are associated with one another. We will touch on some of these associations later in the guide but I encourage readers who are interested to read the guide to ActiveRecord associations for an in-depth explanation of the variety of ways rails can model associations. -- Associations between objects controlled by meta-programming macros. - -== Philosophical Approaches & Common Conventions -Rails has a reputation of being a zero-config framework which means that it aims to get you off the ground with as little pre-flight checking as possible. This speed benefit is achieved by following “Convention over Configuration”, which is to say that if you agree to live with the defaults then you benefit from a the inherent speed-boost. As Courtneay Gasking put it to me once “You don’t want to off-road on Rails”. ActiveRecord is no different, while it’s possible to override or subvert any of the conventions of AR, unless you have a good reason for doing so you will probably be happy with the defaults. The following is a list of the common conventions of ActiveRecord - - -ActiveRecord is the default model component of the Model-view-controller web-application framework Ruby on Rails, and is also a stand-alone ORM package for other Ruby applications. In both forms, it was conceived of by David Heinemeier Hansson, and has been improved upon by a number of contributors. --wikipedia - - - Naming Conventions - - Class Names are Singular - - Tables names are the plural name of the class name - - Tables contain an identity column named id - - ids -== ActiveRecord Magic - - timestamps - - updates - -== How ActiveRecord Maps your Database. -- sensible defaults -- overriding conventions - -== Growing Your Database Relationships Naturally - -== Attributes - - attribute accessor method. How to override them? - - attribute? - - dirty records - - -== ActiveRecord handling the CRUD of your Rails application - Understanding the life-cycle of an ActiveRecord - -== Validations & Callbacks \ No newline at end of file + * (8) after_save \ No newline at end of file diff --git a/railties/doc/guides/source/getting_started_with_rails.txt b/railties/doc/guides/source/getting_started_with_rails.txt index 8f0ebe674e..45e6485886 100644 --- a/railties/doc/guides/source/getting_started_with_rails.txt +++ b/railties/doc/guides/source/getting_started_with_rails.txt @@ -107,6 +107,12 @@ For example, to a Rails application a request such as this: would be understood to refer to a photo resource with the ID of 17, and to indicate a desired action - deleting that resource. REST is a natural style for the architecture of web applications, and Rails makes it even more natural by using conventions to shield you from some of the RESTful complexities. +If you’d like more details on REST as an architectural style, these resources are more approachable than Fielding’s thesis: + +* link:http://www.infoq.com/articles/rest-introduction[A Brief Introduction to REST] by Stefan Tilkov +* link:http://bitworking.org/news/373/An-Introduction-to-REST[An Introduction to REST] (video tutorial) by Joe Gregorio +* link:http://en.wikipedia.org/wiki/Representational_State_Transfer[Representational State Transfer] article in Wikipedia + == Creating a New Rails Project If you follow this guide, you'll create a Rails project called +blog+, a (very) simple weblog. Before you can start building the application, you need to make sure that you have Rails itself installed. @@ -715,7 +721,7 @@ end In the +update+ action, Rails first uses the +:id+ parameter passed back from the edit view to locate the database record that's being edited. The +update_attributes+ call then takes the rest of the parameters from the request and applies them to this record. If all goes well, the user is redirected to the post's +show+ view. If there are any problems, it's back to +edit+ to correct them. -NOTE: Sharp-eyed readers will have noticed that the +form_for+ declaration is identical for the +create+ and +edit+ views. Rails generates different code for the two forms because it's smart enough to notice that in the one case it's being passed a new record that has never been saved, and in the other case an existing record that has already been saved to the database. In a production Rails application, you would ordinarily eliminate this duplication by moving identical code to a _partial template_, which you could then include in both parent templates. But the scaffold generator tries not to make too many assumptions, and generates code that’s easy to modify if you want different forms for +create+ and +edit+. +NOTE: Sharp-eyed readers will have noticed that the +form_for+ declaration is identical for the +new+ and +edit+ views. Rails generates different code for the two forms because it's smart enough to notice that in the one case it's being passed a new record that has never been saved, and in the other case an existing record that has already been saved to the database. In a production Rails application, you would ordinarily eliminate this duplication by moving identical code to a _partial template_, which you could then include in both parent templates. But the scaffold generator tries not to make too many assumptions, and generates code that’s easy to modify if you want different forms for +create+ and +edit+. === Destroying a Post @@ -736,6 +742,125 @@ end The +destroy+ method of an Active Record model instance removes the corresponding record from the database. After that's done, there isn't any record to display, so Rails redirects the user's browser to the index view for the model. +== DRYing up the Code + +At this point, it’s worth looking at some of the tools that Rails provides to eliminate duplication in your code. In particular, you can use _partials_ to clean up duplication in views and _filters_ to help with duplication in controllers. + +=== Using Partials to Eliminate View Duplication + +As you saw earlier, the scaffold-generated views for the +new+ and +edit+ actions are largely identical. You can pull the shared code out into a +partial+ template. This requires editing the new and edit views, and adding a new template: + ++new.html.erb+: +[source, ruby] +------------------------------------------------------- +

    New post

    + +<%= render :partial => "form" %> + +<%= link_to 'Back', posts_path %> +------------------------------------------------------- + ++edit.html.erb+: +[source, ruby] +------------------------------------------------------- +

    Editing post

    + +<%= render :partial => "form" %> + +<%= link_to 'Show', @post %> | +<%= link_to 'Back', posts_path %> +------------------------------------------------------- + ++_form.html.erb+: +[source, ruby] +------------------------------------------------------- +<% form_for(@post) do |f| %> + <%= f.error_messages %> + +

    + <%= f.label :name %>
    + <%= f.text_field :name %> +

    +

    + <%= f.label :title, "title" %>
    + <%= f.text_field :title %> +

    +

    + <%= f.label :content %>
    + <%= f.text_area :content %> +

    +

    + <%= f.submit "Save" %> +

    +<% end %> +------------------------------------------------------- + +Now, when Rails renders the +new+ or +edit+ view, it will insert the +_form+ partial at the indicated point. Note the naming convention for partials: if you refer to a partial named +form+ inside of a view, the corresponding file is +_form.html.erb+, with a leading underscore. + +For more information on partials, refer to the link:../layouts_and_rendering.html[Layouts and Rending in Rails] guide. + +=== Using Filters to Eliminate Controller Duplication + +At this point, if you look at the controller for posts, you’ll see some duplication: + +[source, ruby] +------------------------------------------------------- +class PostsController < ApplicationController + # ... + def show + @post = Post.find(params[:id]) + # ... + end + + def edit + @post = Post.find(params[:id]) + end + + def update + @post = Post.find(params[:id]) + # ... + end + + def destroy + @post = Post.find(params[:id]) + # ... + end +end +------------------------------------------------------- + +Four instances of the exact same line of code doesn’t seem very DRY. Rails provides _filters_ as a way to address this sort of repeated code. In this case, you can DRY things up by using a +before_filter+: + +[source, ruby] +------------------------------------------------------- +class PostsController < ApplicationController + before_filter :find_post, :only => [:show, :edit, :update, :destroy] + # ... + def show + # ... + end + + def edit + end + + def update + # ... + end + + def destroy + # ... + end + + private + def find_post + @post = Post.find(params[:id]) + end +end +------------------------------------------------------- + +Rails runs _before filters_ before any action in the controller. You can use the +:only+ clause to limit a before filter to only certain actions, or an +:except+ clause to specifically skip a before filter for certain actions. Rails also allows you to define _after filters_ that run after processing an action, as well as _around filters_ that surround the processing of actions. Filters can also be defined in external classes to make it easy to share them between controllers. + +For more information on filters, see the link:actioncontroller_basics.html[Action Controller Basics] guide. + == Adding a Second Model Now that you've seen what's in a model built with scaffolding, it's time to add a second model to the application. The second model will handle comments on blog posts. @@ -798,7 +923,7 @@ Rails is smart enough to only execute the migrations that have not already been === Associating Models -Active Record associations let you declaratively quantify the relationship between two models. In the case of comments and posts, you could write out the relationships this way: +Active Record associations let you easily declare the relationship between two models. In the case of comments and posts, you could write out the relationships this way: * Each comment belongs to one post * One post can have many comments @@ -825,7 +950,7 @@ end These two declarations enable a good bit of automatic behavior. For example, if you have an instance variable +@post+ containing a post, you can retrieve all the comments belonging to that post as the array +@post.comments+. -TIP: For more information on Active Record associations, see the link:../association_basics.html+[Active Record Associations] guide. +TIP: For more information on Active Record associations, see the link:../association_basics.html[Active Record Associations] guide. === Adding a Route diff --git a/railties/doc/guides/source/layouts_and_rendering.txt b/railties/doc/guides/source/layouts_and_rendering.txt index ed56b82ffd..c4bb5b0591 100644 --- a/railties/doc/guides/source/layouts_and_rendering.txt +++ b/railties/doc/guides/source/layouts_and_rendering.txt @@ -186,7 +186,7 @@ render :file => filename, :content_type => 'application/rss' ===== The +:layout+ Option -With most of the options to +render+, the rendered content is displayed as part of the current layout. You'll learn more about layouts and how to use them later in this guide. To find the current layout, Rails first looks for a file in +app/views/layouts+ with the same base name as the controller. For example, rendering actions from the +PhotosController+ class will use +/app/views/layouts/photos.html.erb+. If there is no such controller-specific layout, Rails will use +/app/views/layouts/application.html.erb+. +With most of the options to +render+, the rendered content is displayed as part of the current layout. You'll learn more about layouts and how to use them later in this guide. You can use the +:layout+ option to tell Rails to use a specific file as the layout for the current action: @@ -223,6 +223,124 @@ You can use the +:location+ option to set the HTTP +Location+ header: render :xml => photo, :location => photo_url(photo) ------------------------------------------------------- +==== Finding Layouts + +To find the current layout, Rails first looks for a file in +app/views/layouts+ with the same base name as the controller. For example, rendering actions from the +PhotosController+ class will use +/app/views/layouts/photos.html.erb+. If there is no such controller-specific layout, Rails will use +/app/views/layouts/application.html.erb+. If there is no +.erb+ layout, Rails will use a +.builder+ layout if one exists. Rails also provides several ways to more precisely assign specific layouts to individual controllers and actions. + +===== Specifying Layouts on a per-Controller Basis + +You can override the automatic layout conventions in your controllers by using the +layout+ declaration in the controller. For example: + +[source, ruby] +------------------------------------------------------- +class ProductsController < ApplicationController + layout "inventory" + #... +end +------------------------------------------------------- + +With this declaration, all methods within +ProductsController+ will use +app/views/layouts/inventory.html.erb+ for their layout. + +To assign a specific layout for the entire application, use a declaration in your +ApplicationController+ class: + +[source, ruby] +------------------------------------------------------- +class ApplicationController < ActionController::Base + layout "main" + #... +end +------------------------------------------------------- + +With this declaration, all views in the entire application will use +app/views/layouts/main.html.erb+ for their layout. + +===== Choosing Layouts at Runtime + +You can use a symbol to defer the choice of layout until a request is processed: + +[source, ruby] +------------------------------------------------------- +class ProductsController < ApplicationController + layout :products_layout + + def show + @product = Product.find(params[:id]) + end + + private + def products_layout + @current_user.special? ? "special" : "products" + end + +end +------------------------------------------------------- + +Now, if the current user is a special user, they'll get a special layout when viewing a product. You can even use an inline method to determine the layout: + +[source, ruby] +------------------------------------------------------- +class ProductsController < ApplicationController + layout proc{ |controller| controller. + # ... +end +------------------------------------------------------- + +===== Conditional Layouts + +Layouts specified at the controller level support +:only+ and +:except+ options that take either a method name or an array of method names: + +------------------------------------------------------- +class ProductsController < ApplicationController + layout "inventory", :only => :index + layout "product", :except => [:index, :rss] + #... +end +------------------------------------------------------- + +With those declarations, the +inventory+ layout would be used only for the +index+ method, the +product+ layout would be used for everything else except the +rss+ method, and the +rss+ method will have its layout determined by the automatic layout rules. + +===== Layout Inheritance + +Layouts are shared downwards in the hierarchy, and more specific layouts always override more general ones. For example: + +[source, ruby] +------------------------------------------------------- +class ApplicationController < ActionController::Base + layout "main" + #... +end + +class PostsController < ApplicationController + # ... +end + +class SpecialPostsController < PostsController + layout "special" + # ... +end + +class OldPostsController < SpecialPostsController + layout nil + + def show + @post = Post.find(params[:id]) + end + + def index + @old_posts = Post.older + render :layout => "old" + end + # ... +end +------------------------------------------------------- + +In this application: + +* In general, views will be rendered in the +main+ layout +* +PostsController#index+ will use the +main+ layout +* +SpecialPostsController#index+ will use the +special+ layout +* +OldPostsController#show+ will use no layout at all +* +OldPostsController#index+ will use the +old+ layout + ==== Avoiding Double Render Errors Sooner or later, most Rails developers will see the error message "Can only render or redirect once per action". While this is annoying, it's relatively easy to fix. Usually it happens because of a fundamental misunderstanding of the way that +render+ works. @@ -332,9 +450,7 @@ head :created, :location => photo_path(@photo) == Structuring Layouts -When Rails renders a view as a response, it does so by combining the view with the current layout. To find the current layout, Rails first looks for a file in +app/views/layouts+ with the same base name as the controller. For example, rendering actions from the +PhotosController+ class will use +/app/views/layouts/photos.html.erb+. If there is no such controller-specific layout, Rails will use +/app/views/layouts/application.html.erb+. You can also specify a particular layout by using the +:layout+ option to +render+. - -Within a layout, you have access to three tools for combining different bits of output to form the overall response: +When Rails renders a view as a response, it does so by combining the view with the current layout (using the rules for finding the current layout that were covered earlier in this guide). Within a layout, you have access to three tools for combining different bits of output to form the overall response: * Asset tags * +yield+ and +content_for+ diff --git a/railties/doc/guides/source/security.txt b/railties/doc/guides/source/security.txt index d068a22491..53819babb7 100644 --- a/railties/doc/guides/source/security.txt +++ b/railties/doc/guides/source/security.txt @@ -2,7 +2,7 @@ Ruby On Rails Security Guide ============================ This manual describes common security problems in web applications and how to avoid them with Rails. If you have any questions or suggestions, please -mail me at 42 {_et_} rorsecurity.info. After reading it, you should be familiar with: +mail me, Heiko Webers, at 42 {_et_} rorsecurity.info. After reading it, you should be familiar with: - All countermeasures [,#fffcdb]#that are highlighted# - The concept of sessions in Rails, what to put in there and popular attack methods @@ -858,7 +858,8 @@ This example, again, showed that a blacklist filter is never complete. However, -- _If you want to provide text formatting other than HTML (due to security), use a mark-up language which is converted to HTML on the server-side. http://whytheluckystiff.net/ruby/redcloth/[RedCloth] is such a language for Ruby, but without precautions, it is also vulnerable to XSS._ -For example, RedCloth translates _test_ to test, which makes the text italic. However, up to the current version 3.0.4, it is still vulnerable to XSS: + For example, RedCloth translates _test_ to test, which makes the text italic. However, up to the current version 3.0.4, it is still vulnerable to XSS. Get the http://www.redcloth.org[all-new version 4] that removed serious bugs. However, even that version has http://www.rorsecurity.info/journal/2008/10/13/new-redcloth-security.html[some security bugs], so the countermeasures still apply. Here is an example for version 3.0.4: + ........... >> RedCloth.new('').to_html @@ -908,6 +909,64 @@ system("/bin/echo","hello; rm *") # prints "hello; rm *" and does not delete files .......... + +=== Header Injection +-- _HTTP headers are dynamically generated and under certain circumstances user input may be injected. This can lead to false redirection, XSS or HTTP response splitting._ + +HTTP request headers have a Referer, User-Agent (client software) and Cookie field, among others. Response headers for example have a status code, Cookie and Location (redirection target URL) field. All of them are user-supplied and may be manipulated with more or less effort. [,#fffcdb]#Remember to escape these header fields, too.# For example when you display the user agent in an administration area. + +Besides that, it is [,#fffcdb]#important to know what you are doing when building response headers partly based on user input.# For example you want to redirect the user back to a specific page. To do that you introduced a “referer“ field in a form to redirect to the given address: + +.......... +redirect_to params[:referer] +.......... + +What happens is that Rails puts the string into the Location header field and sends a 302 (redirect) status to the browser. The first thing a malicious user would do, is this: + +.......... +http://www.yourapplication.com/controller/action?referer=http://www.malicious.tld +.......... + +And due to a bug in (Ruby and) Rails up to version 2.1.2 (excluding it), a hacker may inject arbitrary header fields; for example like this: + +.......... +http://www.yourapplication.com/controller/action?referer=http://www.malicious.tld%0d%0aX-Header:+Hi! +http://www.yourapplication.com/controller/action?referer=path/at/your/app%0d%0aLocation:+http://www.malicious.tld +.......... + +Note that "%0d%0a" is URL-encoded for "\r\n" which is a carriage-return and line-feed (CRLF) in Ruby. So the resulting HTTP header for the second example will be the following because the second Location header field overwrites the first. + +.......... +HTTP/1.1 302 Moved Temporarily +(...) +Location: http://www.malicious.tld +.......... + +So [,#fffcdb]#attack vectors for Header Injection are based on the injection of CRLF characters in a header field.# And what could an attacker do with a false redirection? He could redirect to a phishing site that looks the same as yours, but asks to login again (and sends the login credentials to the attacker). Or he could install malicious software through browser security holes on that site. [,#fffcdb]#Rails 2.1.2 escapes these characters for the Location field in the redirect_to method. Make sure you do it yourself when you build other header fields with user input.# + +==== Response Splitting +If Header Injection was possible, Response Splitting might be, too. In HTTP, the header block is followed by two CRLFs and the actual data (usually HTML). The idea of Response Splitting is to inject two CRLFs into a header field, followed by another response with malicious HTML. The response will be: + +.......... +HTTP/1.1 302 Found [First standard 302 response] +Date: Tue, 12 Apr 2005 22:09:07 GMT +Location:
Content-Type: text/html + + +HTTP/1.1 200 OK [Second New response created by attacker begins] +Content-Type: text/html + + +hey [Arbitary malicious input is +Keep-Alive: timeout=15, max=100 shown as the redirected page] +Connection: Keep-Alive +Transfer-Encoding: chunked +Content-Type: text/html +.......... + +Under certain circumstances this would present the malicious HTML to the victim. However, this seems to work with Keep-Alive connections, only (and many browsers are using one-time connections). But you can't rely on this. [,#fffcdb]#In any case this is a serious bug, and you should update your Rails to version 2.0.5 or 2.1.2 to eliminate Header Injection (and thus response splitting) risks.# + + == Additional resources The security landscape shifts and it is important to keep up to date, because missing a new vulnerability can be catastrophic. You can find additional resources about (Rails) security here: -- cgit v1.2.3 From 5cf932344a4482a0cfbda57e9f69ebe64e70a261 Mon Sep 17 00:00:00 2001 From: Pratik Naik Date: Sat, 25 Oct 2008 04:34:09 +0530 Subject: Fix typos in release notes --- railties/doc/guides/html/2_2_release_notes.html | 2 +- railties/doc/guides/html/layouts_and_rendering.html | 8 ++++---- railties/doc/guides/source/2_2_release_notes.txt | 2 +- railties/doc/guides/source/layouts_and_rendering.txt | 8 ++++---- 4 files changed, 10 insertions(+), 10 deletions(-) (limited to 'railties') diff --git a/railties/doc/guides/html/2_2_release_notes.html b/railties/doc/guides/html/2_2_release_notes.html index d21905f715..7e975d35a6 100644 --- a/railties/doc/guides/html/2_2_release_notes.html +++ b/railties/doc/guides/html/2_2_release_notes.html @@ -935,7 +935,7 @@ The addition of ActiveSupport::Rescuable allows any class to mix in the
  • -Enumerable#several? to encapsulate collection.size > 1 +Enumerable#many? to encapsulate collection.size > 1

  • diff --git a/railties/doc/guides/html/layouts_and_rendering.html b/railties/doc/guides/html/layouts_and_rendering.html index aeda0c5c5e..a394da71df 100644 --- a/railties/doc/guides/html/layouts_and_rendering.html +++ b/railties/doc/guides/html/layouts_and_rendering.html @@ -1190,7 +1190,7 @@ http://www.gnu.org/software/src-highlite --> by Lorenzo Bettini http://www.lorenzobettini.it http://www.gnu.org/software/src-highlite --> -
    new.rhtml.erb:
    +
    new.html.erb:
     
     <h1>New zone</h1>
     <%= error_messages_for :zone %>
    @@ -1240,7 +1240,7 @@ http://www.gnu.org/software/src-highlite -->
     by Lorenzo Bettini
     http://www.lorenzobettini.it
     http://www.gnu.org/software/src-highlite -->
    -
    index.rhtml.erb:
    +
    index.html.erb:
     
     <h1>Products</h1>
     <%= render :partial => "product", :collection => @products %>
    @@ -1273,7 +1273,7 @@ http://www.gnu.org/software/src-highlite -->
     by Lorenzo Bettini
     http://www.lorenzobettini.it
     http://www.gnu.org/software/src-highlite -->
    -
    index.rhtml.erb:
    +
    index.html.erb:
     
     <h1>Products</h1>
     <%= render :partial => @products %>
    @@ -1288,7 +1288,7 @@ _product.html.erb:
     by Lorenzo Bettini
     http://www.lorenzobettini.it
     http://www.gnu.org/software/src-highlite -->
    -
    index.rhtml.erb:
    +
    index.html.erb:
     
     <h1>Contacts</h1>
     <%= render :partial => [customer1, employee1, customer2, employee2] %>
    diff --git a/railties/doc/guides/source/2_2_release_notes.txt b/railties/doc/guides/source/2_2_release_notes.txt
    index 4543b5309c..8d627c9e63 100644
    --- a/railties/doc/guides/source/2_2_release_notes.txt
    +++ b/railties/doc/guides/source/2_2_release_notes.txt
    @@ -349,7 +349,7 @@ Lead Contributor: link:http://workingwithrails.com/person/5830-daniel-schierbeck
     * The addition of +ActiveSupport::Rescuable+ allows any class to mix in the +rescue_from+ syntax.
     * +past?+, +today?+ and +future?+ for +Date+ and +Time+ classes to facilitate date/time comparisons.
     * +Array#second+ through +Array#tenth+ as aliases for +Array#[1]+ through +Array#[9]+
    -* +Enumerable#several?+ to encapsulate +collection.size > 1+
    +* +Enumerable#many?+ to encapsulate +collection.size > 1+
     * +Inflector#parameterize+ produces a URL-ready version of its input, for use in +to_param+.
     * +Time#advance+ recognizes fractional days and weeks, so you can do +1.7.weeks.ago+, +1.5.hours.since+, and so on.
     * The included TzInfo library has been upgraded to version 0.3.11.
    diff --git a/railties/doc/guides/source/layouts_and_rendering.txt b/railties/doc/guides/source/layouts_and_rendering.txt
    index c4bb5b0591..3d970b60ce 100644
    --- a/railties/doc/guides/source/layouts_and_rendering.txt
    +++ b/railties/doc/guides/source/layouts_and_rendering.txt
    @@ -768,7 +768,7 @@ You can also pass local variables into partials, making them even more powerful
     
     [source, html]
     -------------------------------------------------------
    -new.rhtml.erb:
    +new.html.erb:
     
     

    New zone

    <%= error_messages_for :zone %> @@ -819,7 +819,7 @@ Partials are very useful in rendering collections. When you pass a collection to [source, html] ------------------------------------------------------- -index.rhtml.erb: +index.html.erb:

    Products

    <%= render :partial => "product", :collection => @products %> @@ -851,7 +851,7 @@ There's also a shorthand syntax available for rendering collections. For example [source, html] ------------------------------------------------------- -index.rhtml.erb: +index.html.erb:

    Products

    <%= render :partial => @products %> @@ -865,7 +865,7 @@ Rails determines the name of the partial to use by looking at the model name in [source, html] ------------------------------------------------------- -index.rhtml.erb: +index.html.erb:

    Contacts

    <%= render :partial => [customer1, employee1, customer2, employee2] %> -- cgit v1.2.3 From 47007c75afbe2b482e2a87e11678556ccf0f9a43 Mon Sep 17 00:00:00 2001 From: Luca Guidi Date: Mon, 29 Sep 2008 09:44:49 +0200 Subject: Update prototype to 1.6.0.3 Signed-off-by: David Heinemeier Hansson --- railties/CHANGELOG | 5 + railties/html/javascripts/prototype.js | 337 +++++++++++++++++++++------------ 2 files changed, 223 insertions(+), 119 deletions(-) (limited to 'railties') diff --git a/railties/CHANGELOG b/railties/CHANGELOG index 390da3b890..81cf6a0108 100644 --- a/railties/CHANGELOG +++ b/railties/CHANGELOG @@ -1,3 +1,8 @@ +*2.2.1 [RC2 or 2.2 final]* + +* Update Prototype to 1.6.0.3 [sam] + + *2.2.0 [RC1] (October 24th, 2008)* * Fixed that sqlite would report "db/development.sqlite3 already exists" whether true or not on db:create #614 [Antonio Cangiano] diff --git a/railties/html/javascripts/prototype.js b/railties/html/javascripts/prototype.js index 2c70b8a7e8..dfe8ab4e13 100644 --- a/railties/html/javascripts/prototype.js +++ b/railties/html/javascripts/prototype.js @@ -1,4 +1,4 @@ -/* Prototype JavaScript framework, version 1.6.0.2 +/* Prototype JavaScript framework, version 1.6.0.3 * (c) 2005-2008 Sam Stephenson * * Prototype is freely distributable under the terms of an MIT-style license. @@ -7,23 +7,26 @@ *--------------------------------------------------------------------------*/ var Prototype = { - Version: '1.6.0.2', + Version: '1.6.0.3', Browser: { - IE: !!(window.attachEvent && !window.opera), - Opera: !!window.opera, + IE: !!(window.attachEvent && + navigator.userAgent.indexOf('Opera') === -1), + Opera: navigator.userAgent.indexOf('Opera') > -1, WebKit: navigator.userAgent.indexOf('AppleWebKit/') > -1, - Gecko: navigator.userAgent.indexOf('Gecko') > -1 && navigator.userAgent.indexOf('KHTML') == -1, + Gecko: navigator.userAgent.indexOf('Gecko') > -1 && + navigator.userAgent.indexOf('KHTML') === -1, MobileSafari: !!navigator.userAgent.match(/Apple.*Mobile.*Safari/) }, BrowserFeatures: { XPath: !!document.evaluate, + SelectorsAPI: !!document.querySelector, ElementExtensions: !!window.HTMLElement, SpecificElementExtensions: - document.createElement('div').__proto__ && - document.createElement('div').__proto__ !== - document.createElement('form').__proto__ + document.createElement('div')['__proto__'] && + document.createElement('div')['__proto__'] !== + document.createElement('form')['__proto__'] }, ScriptFragment: ']*>([\\S\\s]*?)<\/script>', @@ -83,12 +86,13 @@ Class.Methods = { var property = properties[i], value = source[property]; if (ancestor && Object.isFunction(value) && value.argumentNames().first() == "$super") { - var method = value, value = Object.extend((function(m) { + var method = value; + value = (function(m) { return function() { return ancestor[m].apply(this, arguments) }; - })(property).wrap(method), { - valueOf: function() { return method }, - toString: function() { return method.toString() } - }); + })(property).wrap(method); + + value.valueOf = method.valueOf.bind(method); + value.toString = method.toString.bind(method); } this.prototype[property] = value; } @@ -167,7 +171,7 @@ Object.extend(Object, { }, isElement: function(object) { - return object && object.nodeType == 1; + return !!(object && object.nodeType == 1); }, isArray: function(object) { @@ -198,7 +202,8 @@ Object.extend(Object, { Object.extend(Function.prototype, { argumentNames: function() { - var names = this.toString().match(/^[\s\(]*function[^(]*\((.*?)\)/)[1].split(",").invoke("strip"); + var names = this.toString().match(/^[\s\(]*function[^(]*\(([^\)]*)\)/)[1] + .replace(/\s+/g, '').split(','); return names.length == 1 && !names[0] ? [] : names; }, @@ -232,6 +237,11 @@ Object.extend(Function.prototype, { }, timeout); }, + defer: function() { + var args = [0.01].concat($A(arguments)); + return this.delay.apply(this, args); + }, + wrap: function(wrapper) { var __method = this; return function() { @@ -248,8 +258,6 @@ Object.extend(Function.prototype, { } }); -Function.prototype.defer = Function.prototype.delay.curry(0.01); - Date.prototype.toJSON = function() { return '"' + this.getUTCFullYear() + '-' + (this.getUTCMonth() + 1).toPaddedString(2) + '-' + @@ -530,7 +538,7 @@ if (Prototype.Browser.WebKit || Prototype.Browser.IE) Object.extend(String.proto return this.replace(/&/g,'&').replace(//g,'>'); }, unescapeHTML: function() { - return this.replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>'); + return this.stripTags().replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>'); } }); @@ -547,7 +555,7 @@ Object.extend(String.prototype.escapeHTML, { text: document.createTextNode('') }); -with (String.prototype.escapeHTML) div.appendChild(text); +String.prototype.escapeHTML.div.appendChild(String.prototype.escapeHTML.text); var Template = Class.create({ initialize: function(template, pattern) { @@ -589,10 +597,9 @@ var $break = { }; var Enumerable = { each: function(iterator, context) { var index = 0; - iterator = iterator.bind(context); try { this._each(function(value) { - iterator(value, index++); + iterator.call(context, value, index++); }); } catch (e) { if (e != $break) throw e; @@ -601,47 +608,46 @@ var Enumerable = { }, eachSlice: function(number, iterator, context) { - iterator = iterator ? iterator.bind(context) : Prototype.K; var index = -number, slices = [], array = this.toArray(); + if (number < 1) return array; while ((index += number) < array.length) slices.push(array.slice(index, index+number)); return slices.collect(iterator, context); }, all: function(iterator, context) { - iterator = iterator ? iterator.bind(context) : Prototype.K; + iterator = iterator || Prototype.K; var result = true; this.each(function(value, index) { - result = result && !!iterator(value, index); + result = result && !!iterator.call(context, value, index); if (!result) throw $break; }); return result; }, any: function(iterator, context) { - iterator = iterator ? iterator.bind(context) : Prototype.K; + iterator = iterator || Prototype.K; var result = false; this.each(function(value, index) { - if (result = !!iterator(value, index)) + if (result = !!iterator.call(context, value, index)) throw $break; }); return result; }, collect: function(iterator, context) { - iterator = iterator ? iterator.bind(context) : Prototype.K; + iterator = iterator || Prototype.K; var results = []; this.each(function(value, index) { - results.push(iterator(value, index)); + results.push(iterator.call(context, value, index)); }); return results; }, detect: function(iterator, context) { - iterator = iterator.bind(context); var result; this.each(function(value, index) { - if (iterator(value, index)) { + if (iterator.call(context, value, index)) { result = value; throw $break; } @@ -650,17 +656,16 @@ var Enumerable = { }, findAll: function(iterator, context) { - iterator = iterator.bind(context); var results = []; this.each(function(value, index) { - if (iterator(value, index)) + if (iterator.call(context, value, index)) results.push(value); }); return results; }, grep: function(filter, iterator, context) { - iterator = iterator ? iterator.bind(context) : Prototype.K; + iterator = iterator || Prototype.K; var results = []; if (Object.isString(filter)) @@ -668,7 +673,7 @@ var Enumerable = { this.each(function(value, index) { if (filter.match(value)) - results.push(iterator(value, index)); + results.push(iterator.call(context, value, index)); }); return results; }, @@ -696,9 +701,8 @@ var Enumerable = { }, inject: function(memo, iterator, context) { - iterator = iterator.bind(context); this.each(function(value, index) { - memo = iterator(memo, value, index); + memo = iterator.call(context, memo, value, index); }); return memo; }, @@ -711,10 +715,10 @@ var Enumerable = { }, max: function(iterator, context) { - iterator = iterator ? iterator.bind(context) : Prototype.K; + iterator = iterator || Prototype.K; var result; this.each(function(value, index) { - value = iterator(value, index); + value = iterator.call(context, value, index); if (result == null || value >= result) result = value; }); @@ -722,10 +726,10 @@ var Enumerable = { }, min: function(iterator, context) { - iterator = iterator ? iterator.bind(context) : Prototype.K; + iterator = iterator || Prototype.K; var result; this.each(function(value, index) { - value = iterator(value, index); + value = iterator.call(context, value, index); if (result == null || value < result) result = value; }); @@ -733,10 +737,10 @@ var Enumerable = { }, partition: function(iterator, context) { - iterator = iterator ? iterator.bind(context) : Prototype.K; + iterator = iterator || Prototype.K; var trues = [], falses = []; this.each(function(value, index) { - (iterator(value, index) ? + (iterator.call(context, value, index) ? trues : falses).push(value); }); return [trues, falses]; @@ -751,19 +755,20 @@ var Enumerable = { }, reject: function(iterator, context) { - iterator = iterator.bind(context); var results = []; this.each(function(value, index) { - if (!iterator(value, index)) + if (!iterator.call(context, value, index)) results.push(value); }); return results; }, sortBy: function(iterator, context) { - iterator = iterator.bind(context); return this.map(function(value, index) { - return {value: value, criteria: iterator(value, index)}; + return { + value: value, + criteria: iterator.call(context, value, index) + }; }).sort(function(left, right) { var a = left.criteria, b = right.criteria; return a < b ? -1 : a > b ? 1 : 0; @@ -815,8 +820,12 @@ function $A(iterable) { if (Prototype.Browser.WebKit) { $A = function(iterable) { if (!iterable) return []; - if (!(Object.isFunction(iterable) && iterable == '[object NodeList]') && - iterable.toArray) return iterable.toArray(); + // In Safari, only use the `toArray` method if it's not a NodeList. + // A NodeList is a function, has an function `item` property, and a numeric + // `length` property. Adapted from Google Doctype. + if (!(typeof iterable === 'function' && typeof iterable.length === + 'number' && typeof iterable.item === 'function') && iterable.toArray) + return iterable.toArray(); var length = iterable.length || 0, results = new Array(length); while (length--) results[length] = iterable[length]; return results; @@ -963,8 +972,8 @@ Object.extend(Number.prototype, { return this + 1; }, - times: function(iterator) { - $R(0, this, true).each(iterator); + times: function(iterator, context) { + $R(0, this, true).each(iterator, context); return this; }, @@ -1011,7 +1020,9 @@ var Hash = Class.create(Enumerable, (function() { }, get: function(key) { - return this._object[key]; + // simulating poorly supported hasOwnProperty + if (this._object[key] !== Object.prototype[key]) + return this._object[key]; }, unset: function(key) { @@ -1051,14 +1062,14 @@ var Hash = Class.create(Enumerable, (function() { }, toQueryString: function() { - return this.map(function(pair) { + return this.inject([], function(results, pair) { var key = encodeURIComponent(pair.key), values = pair.value; if (values && typeof values == 'object') { if (Object.isArray(values)) - return values.map(toQueryPair.curry(key)).join('&'); - } - return toQueryPair(key, values); + return results.concat(values.map(toQueryPair.curry(key))); + } else results.push(toQueryPair(key, values)); + return results; }).join('&'); }, @@ -1558,6 +1569,7 @@ if (!Node.ELEMENT_NODE) { return Element.writeAttribute(cache[tagName].cloneNode(false), attributes); }; Object.extend(this.Element, element || { }); + if (element) this.Element.prototype = element.prototype; }).call(window); Element.cache = { }; @@ -1574,12 +1586,14 @@ Element.Methods = { }, hide: function(element) { - $(element).style.display = 'none'; + element = $(element); + element.style.display = 'none'; return element; }, show: function(element) { - $(element).style.display = ''; + element = $(element); + element.style.display = ''; return element; }, @@ -1733,7 +1747,7 @@ Element.Methods = { element = $(element); if (arguments.length == 1) return element.firstDescendant(); return Object.isNumber(expression) ? element.descendants()[expression] : - element.select(expression)[index || 0]; + Element.select(element, expression)[index || 0]; }, previous: function(element, expression, index) { @@ -1863,24 +1877,16 @@ Element.Methods = { descendantOf: function(element, ancestor) { element = $(element), ancestor = $(ancestor); - var originalAncestor = ancestor; if (element.compareDocumentPosition) return (element.compareDocumentPosition(ancestor) & 8) === 8; - if (element.sourceIndex && !Prototype.Browser.Opera) { - var e = element.sourceIndex, a = ancestor.sourceIndex, - nextAncestor = ancestor.nextSibling; - if (!nextAncestor) { - do { ancestor = ancestor.parentNode; } - while (!(nextAncestor = ancestor.nextSibling) && ancestor.parentNode); - } - if (nextAncestor && nextAncestor.sourceIndex) - return (e > a && e < nextAncestor.sourceIndex); - } + if (ancestor.contains) + return ancestor.contains(element) && ancestor !== element; while (element = element.parentNode) - if (element == originalAncestor) return true; + if (element == ancestor) return true; + return false; }, @@ -1895,7 +1901,7 @@ Element.Methods = { element = $(element); style = style == 'float' ? 'cssFloat' : style.camelize(); var value = element.style[style]; - if (!value) { + if (!value || value == 'auto') { var css = document.defaultView.getComputedStyle(element, null); value = css ? css[style] : null; } @@ -1934,7 +1940,7 @@ Element.Methods = { getDimensions: function(element) { element = $(element); - var display = $(element).getStyle('display'); + var display = element.getStyle('display'); if (display != 'none' && display != null) // Safari bug return {width: element.offsetWidth, height: element.offsetHeight}; @@ -1963,7 +1969,7 @@ Element.Methods = { element.style.position = 'relative'; // Opera returns the offset relative to the positioning context, when an // element is position relative but top and left have not been defined - if (window.opera) { + if (Prototype.Browser.Opera) { element.style.top = 0; element.style.left = 0; } @@ -2018,7 +2024,7 @@ Element.Methods = { valueL += element.offsetLeft || 0; element = element.offsetParent; if (element) { - if (element.tagName == 'BODY') break; + if (element.tagName.toUpperCase() == 'BODY') break; var p = Element.getStyle(element, 'position'); if (p !== 'static') break; } @@ -2028,7 +2034,7 @@ Element.Methods = { absolutize: function(element) { element = $(element); - if (element.getStyle('position') == 'absolute') return; + if (element.getStyle('position') == 'absolute') return element; // Position.prepare(); // To be done manually by Scripty when it needs it. var offsets = element.positionedOffset(); @@ -2052,7 +2058,7 @@ Element.Methods = { relativize: function(element) { element = $(element); - if (element.getStyle('position') == 'relative') return; + if (element.getStyle('position') == 'relative') return element; // Position.prepare(); // To be done manually by Scripty when it needs it. element.style.position = 'relative'; @@ -2103,7 +2109,7 @@ Element.Methods = { element = forElement; do { - if (!Prototype.Browser.Opera || element.tagName == 'BODY') { + if (!Prototype.Browser.Opera || (element.tagName && (element.tagName.toUpperCase() == 'BODY'))) { valueT -= element.scrollTop || 0; valueL -= element.scrollLeft || 0; } @@ -2218,6 +2224,9 @@ else if (Prototype.Browser.IE) { Element.Methods.getOffsetParent = Element.Methods.getOffsetParent.wrap( function(proceed, element) { element = $(element); + // IE throws an error if element is not in document + try { element.offsetParent } + catch(e) { return $(document.body) } var position = element.getStyle('position'); if (position !== 'static') return proceed(element); element.setStyle({ position: 'relative' }); @@ -2231,6 +2240,8 @@ else if (Prototype.Browser.IE) { Element.Methods[method] = Element.Methods[method].wrap( function(proceed, element) { element = $(element); + try { element.offsetParent } + catch(e) { return Element._returnOffset(0,0) } var position = element.getStyle('position'); if (position !== 'static') return proceed(element); // Trigger hasLayout on the offset parent so that IE6 reports @@ -2246,6 +2257,14 @@ else if (Prototype.Browser.IE) { ); }); + Element.Methods.cumulativeOffset = Element.Methods.cumulativeOffset.wrap( + function(proceed, element) { + try { element.offsetParent } + catch(e) { return Element._returnOffset(0,0) } + return proceed(element); + } + ); + Element.Methods.getStyle = function(element, style) { element = $(element); style = (style == 'float' || style == 'cssFloat') ? 'styleFloat' : style.camelize(); @@ -2337,7 +2356,7 @@ else if (Prototype.Browser.IE) { Element._attributeTranslations.has = {}; $w('colSpan rowSpan vAlign dateTime accessKey tabIndex ' + - 'encType maxLength readOnly longDesc').each(function(attr) { + 'encType maxLength readOnly longDesc frameBorder').each(function(attr) { Element._attributeTranslations.write.names[attr.toLowerCase()] = attr; Element._attributeTranslations.has[attr.toLowerCase()] = attr; }); @@ -2390,7 +2409,7 @@ else if (Prototype.Browser.WebKit) { (value < 0.00001) ? 0 : value; if (value == 1) - if(element.tagName == 'IMG' && element.width) { + if(element.tagName.toUpperCase() == 'IMG' && element.width) { element.width++; element.width--; } else try { var n = document.createTextNode(' '); @@ -2521,7 +2540,7 @@ Element.Methods.Simulated = { hasAttribute: function(element, attribute) { attribute = Element._attributeTranslations.has[attribute] || attribute; var node = $(element).getAttributeNode(attribute); - return node && node.specified; + return !!(node && node.specified); } }; @@ -2530,9 +2549,9 @@ Element.Methods.ByTag = { }; Object.extend(Element, Element.Methods); if (!Prototype.BrowserFeatures.ElementExtensions && - document.createElement('div').__proto__) { + document.createElement('div')['__proto__']) { window.HTMLElement = { }; - window.HTMLElement.prototype = document.createElement('div').__proto__; + window.HTMLElement.prototype = document.createElement('div')['__proto__']; Prototype.BrowserFeatures.ElementExtensions = true; } @@ -2547,7 +2566,7 @@ Element.extend = (function() { element.nodeType != 1 || element == window) return element; var methods = Object.clone(Methods), - tagName = element.tagName, property, value; + tagName = element.tagName.toUpperCase(), property, value; // extend methods for specific tags if (ByTag[tagName]) Object.extend(methods, ByTag[tagName]); @@ -2643,7 +2662,7 @@ Element.addMethods = function(methods) { if (window[klass]) return window[klass]; window[klass] = { }; - window[klass].prototype = document.createElement(tagName).__proto__; + window[klass].prototype = document.createElement(tagName)['__proto__']; return window[klass]; } @@ -2669,12 +2688,18 @@ Element.addMethods = function(methods) { document.viewport = { getDimensions: function() { - var dimensions = { }; - var B = Prototype.Browser; + var dimensions = { }, B = Prototype.Browser; $w('width height').each(function(d) { var D = d.capitalize(); - dimensions[d] = (B.WebKit && !document.evaluate) ? self['inner' + D] : - (B.Opera) ? document.body['client' + D] : document.documentElement['client' + D]; + if (B.WebKit && !document.evaluate) { + // Safari <3.0 needs self.innerWidth/Height + dimensions[d] = self['inner' + D]; + } else if (B.Opera && parseFloat(window.opera.version()) < 9.5) { + // Opera <9.5 needs document.body.clientWidth/Height + dimensions[d] = document.body['client' + D] + } else { + dimensions[d] = document.documentElement['client' + D]; + } }); return dimensions; }, @@ -2693,14 +2718,24 @@ document.viewport = { window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop); } }; -/* Portions of the Selector class are derived from Jack Slocum’s DomQuery, +/* Portions of the Selector class are derived from Jack Slocum's DomQuery, * part of YUI-Ext version 0.40, distributed under the terms of an MIT-style * license. Please see http://www.yui-ext.com/ for more information. */ var Selector = Class.create({ initialize: function(expression) { this.expression = expression.strip(); - this.compileMatcher(); + + if (this.shouldUseSelectorsAPI()) { + this.mode = 'selectorsAPI'; + } else if (this.shouldUseXPath()) { + this.mode = 'xpath'; + this.compileXPathMatcher(); + } else { + this.mode = "normal"; + this.compileMatcher(); + } + }, shouldUseXPath: function() { @@ -2715,16 +2750,29 @@ var Selector = Class.create({ // XPath can't do namespaced attributes, nor can it read // the "checked" property from DOM nodes - if ((/(\[[\w-]*?:|:checked)/).test(this.expression)) + if ((/(\[[\w-]*?:|:checked)/).test(e)) return false; return true; }, - compileMatcher: function() { - if (this.shouldUseXPath()) - return this.compileXPathMatcher(); + shouldUseSelectorsAPI: function() { + if (!Prototype.BrowserFeatures.SelectorsAPI) return false; + + if (!Selector._div) Selector._div = new Element('div'); + + // Make sure the browser treats the selector as valid. Test on an + // isolated element to minimize cost of this check. + try { + Selector._div.querySelector(this.expression); + } catch(e) { + return false; + } + + return true; + }, + compileMatcher: function() { var e = this.expression, ps = Selector.patterns, h = Selector.handlers, c = Selector.criteria, le, p, m; @@ -2742,7 +2790,7 @@ var Selector = Class.create({ p = ps[i]; if (m = e.match(p)) { this.matcher.push(Object.isFunction(c[i]) ? c[i](m) : - new Template(c[i]).evaluate(m)); + new Template(c[i]).evaluate(m)); e = e.replace(m[0], ''); break; } @@ -2781,8 +2829,27 @@ var Selector = Class.create({ findElements: function(root) { root = root || document; - if (this.xpath) return document._getElementsByXPath(this.xpath, root); - return this.matcher(root); + var e = this.expression, results; + + switch (this.mode) { + case 'selectorsAPI': + // querySelectorAll queries document-wide, then filters to descendants + // of the context element. That's not what we want. + // Add an explicit context to the selector if necessary. + if (root !== document) { + var oldId = root.id, id = $(root).identify(); + e = "#" + id + " " + e; + } + + results = $A(root.querySelectorAll(e)).map(Element.extend); + root.id = oldId; + + return results; + case 'xpath': + return document._getElementsByXPath(this.xpath, root); + default: + return this.matcher(root); + } }, match: function(element) { @@ -2873,10 +2940,10 @@ Object.extend(Selector, { 'first-child': '[not(preceding-sibling::*)]', 'last-child': '[not(following-sibling::*)]', 'only-child': '[not(preceding-sibling::* or following-sibling::*)]', - 'empty': "[count(*) = 0 and (count(text()) = 0 or translate(text(), ' \t\r\n', '') = '')]", + 'empty': "[count(*) = 0 and (count(text()) = 0)]", 'checked': "[@checked]", - 'disabled': "[@disabled]", - 'enabled': "[not(@disabled)]", + 'disabled': "[(@disabled) and (@type!='hidden')]", + 'enabled': "[not(@disabled) and (@type!='hidden')]", 'not': function(m) { var e = m[6], p = Selector.patterns, x = Selector.xpath, le, v; @@ -2968,7 +3035,7 @@ Object.extend(Selector, { className: /^\.([\w\-\*]+)(\b|$)/, pseudo: /^:((first|last|nth|nth-last|only)(-child|-of-type)|empty|checked|(en|dis)abled|not)(\((.*?)\))?(\b|$|(?=\s|[:+~>]))/, - attrPresence: /^\[([\w]+)\]/, + attrPresence: /^\[((?:[\w]+:)?[\w]+)\]/, attr: /\[((?:[\w-]*:)?[\w-]+)\s*(?:([!^$*~|]?=)\s*((['"])([^\4]*?)\4|([^'"][^\]]*?)))?\]/ }, @@ -3081,7 +3148,7 @@ Object.extend(Selector, { nextElementSibling: function(node) { while (node = node.nextSibling) - if (node.nodeType == 1) return node; + if (node.nodeType == 1) return node; return null; }, @@ -3270,7 +3337,7 @@ Object.extend(Selector, { 'empty': function(nodes, value, root) { for (var i = 0, results = [], node; node = nodes[i]; i++) { // IE treats comments as element nodes - if (node.tagName == '!' || (node.firstChild && !node.innerHTML.match(/^\s*$/))) continue; + if (node.tagName == '!' || node.firstChild) continue; results.push(node); } return results; @@ -3288,7 +3355,8 @@ Object.extend(Selector, { 'enabled': function(nodes, value, root) { for (var i = 0, results = [], node; node = nodes[i]; i++) - if (!node.disabled) results.push(node); + if (!node.disabled && (!node.type || node.type !== 'hidden')) + results.push(node); return results; }, @@ -3308,11 +3376,14 @@ Object.extend(Selector, { operators: { '=': function(nv, v) { return nv == v; }, '!=': function(nv, v) { return nv != v; }, - '^=': function(nv, v) { return nv.startsWith(v); }, + '^=': function(nv, v) { return nv == v || nv && nv.startsWith(v); }, + '$=': function(nv, v) { return nv == v || nv && nv.endsWith(v); }, + '*=': function(nv, v) { return nv == v || nv && nv.include(v); }, '$=': function(nv, v) { return nv.endsWith(v); }, '*=': function(nv, v) { return nv.include(v); }, '~=': function(nv, v) { return (' ' + nv + ' ').include(' ' + v + ' '); }, - '|=': function(nv, v) { return ('-' + nv.toUpperCase() + '-').include('-' + v.toUpperCase() + '-'); } + '|=': function(nv, v) { return ('-' + (nv || "").toUpperCase() + + '-').include('-' + (v || "").toUpperCase() + '-'); } }, split: function(expression) { @@ -3386,7 +3457,7 @@ var Form = { var data = elements.inject({ }, function(result, element) { if (!element.disabled && element.name) { key = element.name; value = $(element).getValue(); - if (value != null && (element.type != 'submit' || (!submitted && + if (value != null && element.type != 'file' && (element.type != 'submit' || (!submitted && submit !== false && (!submit || key == submit) && (submitted = true)))) { if (key in result) { // a key is already present; construct an array of values @@ -3547,7 +3618,6 @@ Form.Element.Methods = { disable: function(element) { element = $(element); - element.blur(); element.disabled = true; return element; }, @@ -3587,22 +3657,22 @@ Form.Element.Serializers = { else element.value = value; }, - select: function(element, index) { - if (Object.isUndefined(index)) + select: function(element, value) { + if (Object.isUndefined(value)) return this[element.type == 'select-one' ? 'selectOne' : 'selectMany'](element); else { - var opt, value, single = !Object.isArray(index); + var opt, currentValue, single = !Object.isArray(value); for (var i = 0, length = element.length; i < length; i++) { opt = element.options[i]; - value = this.optionValue(opt); + currentValue = this.optionValue(opt); if (single) { - if (value == index) { + if (currentValue == value) { opt.selected = true; return; } } - else opt.selected = index.include(value); + else opt.selected = value.include(currentValue); } } }, @@ -3773,8 +3843,23 @@ Event.Methods = (function() { isRightClick: function(event) { return isButton(event, 2) }, element: function(event) { - var node = Event.extend(event).target; - return Element.extend(node.nodeType == Node.TEXT_NODE ? node.parentNode : node); + event = Event.extend(event); + + var node = event.target, + type = event.type, + currentTarget = event.currentTarget; + + if (currentTarget && currentTarget.tagName) { + // Firefox screws up the "click" event when moving between radio buttons + // via arrow keys. It also screws up the "load" and "error" events on images, + // reporting the document as the target instead of the original image. + if (type === 'load' || type === 'error' || + (type === 'click' && currentTarget.tagName.toLowerCase() === 'input' + && currentTarget.type === 'radio')) + node = currentTarget; + } + if (node.nodeType == Node.TEXT_NODE) node = node.parentNode; + return Element.extend(node); }, findElement: function(event, expression) { @@ -3785,11 +3870,15 @@ Event.Methods = (function() { }, pointer: function(event) { + var docElement = document.documentElement, + body = document.body || { scrollLeft: 0, scrollTop: 0 }; return { x: event.pageX || (event.clientX + - (document.documentElement.scrollLeft || document.body.scrollLeft)), + (docElement.scrollLeft || body.scrollLeft) - + (docElement.clientLeft || 0)), y: event.pageY || (event.clientY + - (document.documentElement.scrollTop || document.body.scrollTop)) + (docElement.scrollTop || body.scrollTop) - + (docElement.clientTop || 0)) }; }, @@ -3834,7 +3923,7 @@ Event.extend = (function() { }; } else { - Event.prototype = Event.prototype || document.createEvent("HTMLEvents").__proto__; + Event.prototype = Event.prototype || document.createEvent("HTMLEvents")['__proto__']; Object.extend(Event.prototype, methods); return Prototype.K; } @@ -3899,10 +3988,20 @@ Object.extend(Event, (function() { cache[id][eventName] = null; } + + // Internet Explorer needs to remove event handlers on page unload + // in order to avoid memory leaks. if (window.attachEvent) { window.attachEvent("onunload", destroyCache); } + // Safari has a dummy event handler on page unload so that it won't + // use its bfcache. Safari <= 3.1 has an issue with restoring the "document" + // object when page is returned to via the back button using its bfcache. + if (Prototype.Browser.WebKit) { + window.addEventListener('unload', Prototype.emptyFunction, false); + } + return { observe: function(element, eventName, handler) { element = $(element); -- cgit v1.2.3 From f8f22dac8e08656f1798d6bf2aa8accc534b077d Mon Sep 17 00:00:00 2001 From: Christopher Currie Date: Sat, 25 Oct 2008 21:39:49 -0700 Subject: Update non-gems distributions to use sqlite3 as the default, matching the "rails" command default [#1270 status:committed] Signed-off-by: David Heinemeier Hansson --- railties/Rakefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'railties') diff --git a/railties/Rakefile b/railties/Rakefile index adb6db0b64..872ea83ec2 100644 --- a/railties/Rakefile +++ b/railties/Rakefile @@ -192,7 +192,7 @@ task :copy_configs do app_name = "rails" socket = nil require 'erb' - File.open("#{PKG_DESTINATION}/config/database.yml", 'w') {|f| f.write ERB.new(IO.read("configs/databases/mysql.yml"), nil, '-').result(binding)} + File.open("#{PKG_DESTINATION}/config/database.yml", 'w') {|f| f.write ERB.new(IO.read("configs/databases/sqlite3.yml"), nil, '-').result(binding)} cp "configs/routes.rb", "#{PKG_DESTINATION}/config/routes.rb" -- cgit v1.2.3 From 7418d367f0ac4a4ac0ab4604c1b10db78efc6865 Mon Sep 17 00:00:00 2001 From: Mathias Meyer Date: Tue, 30 Sep 2008 23:30:30 +0200 Subject: Fixed plugin generator so that generated unit tests would subclass ActiveSupport::TestCase, also introduced a helper script to reduce the needed require statements. [#1137 state:committed] Signed-off-by: David Heinemeier Hansson --- railties/CHANGELOG | 2 ++ .../generators/components/plugin/plugin_generator.rb | 20 ++++++++++---------- .../generators/components/plugin/templates/Rakefile | 1 + .../components/plugin/templates/test_helper.rb | 3 +++ .../components/plugin/templates/unit_test.rb | 4 ++-- 5 files changed, 18 insertions(+), 12 deletions(-) create mode 100644 railties/lib/rails_generator/generators/components/plugin/templates/test_helper.rb (limited to 'railties') diff --git a/railties/CHANGELOG b/railties/CHANGELOG index 81cf6a0108..058afddbde 100644 --- a/railties/CHANGELOG +++ b/railties/CHANGELOG @@ -1,5 +1,7 @@ *2.2.1 [RC2 or 2.2 final]* +* Fixed plugin generator so that generated unit tests would subclass ActiveSupport::TestCase, also introduced a helper script to reduce the needed require statements #1137 [Mathias Meyer] + * Update Prototype to 1.6.0.3 [sam] diff --git a/railties/lib/rails_generator/generators/components/plugin/plugin_generator.rb b/railties/lib/rails_generator/generators/components/plugin/plugin_generator.rb index 615c575e6e..6826998252 100644 --- a/railties/lib/rails_generator/generators/components/plugin/plugin_generator.rb +++ b/railties/lib/rails_generator/generators/components/plugin/plugin_generator.rb @@ -16,16 +16,16 @@ class PluginGenerator < Rails::Generator::NamedBase m.directory "#{plugin_path}/tasks" m.directory "#{plugin_path}/test" - m.template 'README', "#{plugin_path}/README" - m.template 'MIT-LICENSE', "#{plugin_path}/MIT-LICENSE" - m.template 'Rakefile', "#{plugin_path}/Rakefile" - m.template 'init.rb', "#{plugin_path}/init.rb" - m.template 'install.rb', "#{plugin_path}/install.rb" - m.template 'uninstall.rb', "#{plugin_path}/uninstall.rb" - m.template 'plugin.rb', "#{plugin_path}/lib/#{file_name}.rb" - m.template 'tasks.rake', "#{plugin_path}/tasks/#{file_name}_tasks.rake" - m.template 'unit_test.rb', "#{plugin_path}/test/#{file_name}_test.rb" - + m.template 'README', "#{plugin_path}/README" + m.template 'MIT-LICENSE', "#{plugin_path}/MIT-LICENSE" + m.template 'Rakefile', "#{plugin_path}/Rakefile" + m.template 'init.rb', "#{plugin_path}/init.rb" + m.template 'install.rb', "#{plugin_path}/install.rb" + m.template 'uninstall.rb', "#{plugin_path}/uninstall.rb" + m.template 'plugin.rb', "#{plugin_path}/lib/#{file_name}.rb" + m.template 'tasks.rake', "#{plugin_path}/tasks/#{file_name}_tasks.rake" + m.template 'unit_test.rb', "#{plugin_path}/test/#{file_name}_test.rb" + m.template 'test_helper.rb', "#{plugin_path}/test/test_helper.rb" if @with_generator m.directory "#{plugin_path}/generators" m.directory "#{plugin_path}/generators/#{file_name}" diff --git a/railties/lib/rails_generator/generators/components/plugin/templates/Rakefile b/railties/lib/rails_generator/generators/components/plugin/templates/Rakefile index 1824fb10fe..85e8ff1834 100644 --- a/railties/lib/rails_generator/generators/components/plugin/templates/Rakefile +++ b/railties/lib/rails_generator/generators/components/plugin/templates/Rakefile @@ -8,6 +8,7 @@ task :default => :test desc 'Test the <%= file_name %> plugin.' Rake::TestTask.new(:test) do |t| t.libs << 'lib' + t.libs << 'test' t.pattern = 'test/**/*_test.rb' t.verbose = true end diff --git a/railties/lib/rails_generator/generators/components/plugin/templates/test_helper.rb b/railties/lib/rails_generator/generators/components/plugin/templates/test_helper.rb new file mode 100644 index 0000000000..cf148b8b47 --- /dev/null +++ b/railties/lib/rails_generator/generators/components/plugin/templates/test_helper.rb @@ -0,0 +1,3 @@ +require 'rubygems' +require 'active_support' +require 'active_support/test_case' \ No newline at end of file diff --git a/railties/lib/rails_generator/generators/components/plugin/templates/unit_test.rb b/railties/lib/rails_generator/generators/components/plugin/templates/unit_test.rb index 6ede6ef1d2..3e0bc29d3a 100644 --- a/railties/lib/rails_generator/generators/components/plugin/templates/unit_test.rb +++ b/railties/lib/rails_generator/generators/components/plugin/templates/unit_test.rb @@ -1,6 +1,6 @@ -require 'test/unit' +require 'test_helper' -class <%= class_name %>Test < Test::Unit::TestCase +class <%= class_name %>Test < ActiveSupport::TestCase # Replace this with your real tests. test "the truth" do assert true -- cgit v1.2.3 From 408c7227575e0c395a45605783cade1ba3b51e02 Mon Sep 17 00:00:00 2001 From: Jeffrey Hardy Date: Fri, 31 Oct 2008 02:12:48 -0400 Subject: Really silence spec warnings when running GemDependency tests [#1308 state:resolved] Signed-off-by: David Heinemeier Hansson --- railties/test/gem_dependency_test.rb | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) (limited to 'railties') diff --git a/railties/test/gem_dependency_test.rb b/railties/test/gem_dependency_test.rb index 5f026b2616..4f9e824c9f 100644 --- a/railties/test/gem_dependency_test.rb +++ b/railties/test/gem_dependency_test.rb @@ -1,12 +1,11 @@ -require 'lib/rails/vendor_gem_source_index' -Rails::VendorGemSourceIndex.silence_spec_warnings = true - require 'plugin_test_helper' class Rails::GemDependency public :install_command, :unpack_command end +Rails::VendorGemSourceIndex.silence_spec_warnings = true + uses_mocha "Plugin Tests" do class GemDependencyTest < Test::Unit::TestCase def setup -- cgit v1.2.3 From 01433af6a5dd5bcd71a70574e7d0b356ba7b0246 Mon Sep 17 00:00:00 2001 From: Matt Jones Date: Sun, 26 Oct 2008 02:25:52 -0400 Subject: Make refresh_specs more resilient. Always add vendor/gems to gem search path. Use Gem.clear_paths to ensure we get a current searcher. Signed-off-by: Michael Koziarski --- railties/lib/initializer.rb | 1 + railties/lib/rails/gem_dependency.rb | 37 +++++++++++++++++++++++------------- railties/lib/tasks/gems.rake | 3 ++- 3 files changed, 27 insertions(+), 14 deletions(-) (limited to 'railties') diff --git a/railties/lib/initializer.rb b/railties/lib/initializer.rb index 6500b2d309..e3a0e3bad1 100644 --- a/railties/lib/initializer.rb +++ b/railties/lib/initializer.rb @@ -267,6 +267,7 @@ module Rails end def add_gem_load_paths + Rails::GemDependency.add_frozen_gem_path unless @configuration.gems.empty? require "rubygems" @configuration.gems.each { |gem| gem.add_load_paths } diff --git a/railties/lib/rails/gem_dependency.rb b/railties/lib/rails/gem_dependency.rb index 46d5fd3a47..cd280ac023 100644 --- a/railties/lib/rails/gem_dependency.rb +++ b/railties/lib/rails/gem_dependency.rb @@ -18,11 +18,14 @@ module Rails def self.add_frozen_gem_path @@paths_loaded ||= begin - Gem.source_index = Rails::VendorGemSourceIndex.new(Gem.source_index) + source_index = Rails::VendorGemSourceIndex.new(Gem.source_index) + Gem.clear_paths + Gem.source_index = source_index # loaded before us - we can't change them, so mark them Gem.loaded_specs.each do |name, spec| @@framework_gems[name] = spec end + true end end @@ -170,19 +173,27 @@ module Rails exact_dep = Gem::Dependency.new(name, "= #{specification.version}") matches = real_gems.search(exact_dep) installed_spec = matches.first - if installed_spec - # we have a real copy - # get a fresh spec - matches should only have one element - # note that there is no reliable method to check that the loaded - # spec is the same as the copy from real_gems - Gem.activate changes - # some of the fields - real_spec = Gem::Specification.load(matches.first.loaded_from) - write_spec(directory, real_spec) - puts "Reloaded specification for #{name} from installed gems." + if File.exist?(File.dirname(spec_filename(directory))) + if installed_spec + # we have a real copy + # get a fresh spec - matches should only have one element + # note that there is no reliable method to check that the loaded + # spec is the same as the copy from real_gems - Gem.activate changes + # some of the fields + real_spec = Gem::Specification.load(matches.first.loaded_from) + write_spec(directory, real_spec) + puts "Reloaded specification for #{name} from installed gems." + else + # the gem isn't installed locally - write out our current specs + write_spec(directory, specification) + puts "Gem #{name} not loaded locally - writing out current spec." + end else - # the gem isn't installed locally - write out our current specs - write_spec(directory, specification) - puts "Gem #{name} not loaded locally - writing out current spec." + if framework_gem? + puts "Gem directory for #{name} not found - check if it's loading before rails." + else + puts "Something bad is going on - gem directory not found for #{name}." + end end end diff --git a/railties/lib/tasks/gems.rake b/railties/lib/tasks/gems.rake index e2cb4b9577..754e3ba5c9 100644 --- a/railties/lib/tasks/gems.rake +++ b/railties/lib/tasks/gems.rake @@ -6,10 +6,11 @@ task :gems => 'gems:base' do puts puts "I = Installed" puts "F = Frozen" + puts "R = Framework (loaded before rails starts)" end def print_gem_status(gem, indent=1) - code = gem.loaded? ? (gem.frozen? ? "F" : "I") : " " + code = gem.loaded? ? (gem.frozen? ? (gem.framework_gem? ? "R" : "F") : "I") : " " puts " "*(indent-1)+" - [#{code}] #{gem.name} #{gem.requirement.to_s}" gem.dependencies.each { |g| print_gem_status(g, indent+1)} if gem.loaded? end -- cgit v1.2.3