Merge branch 'master' of git://github.com/lifo/docrails

This commit is contained in:
Xavier Noria 2011-07-09 12:20:24 +02:00
commit b65bd01d55
14 changed files with 86 additions and 63 deletions

@ -12,13 +12,13 @@ module Caching
#
# <% cache do %>
# All the topics in the system:
# <%= render :partial => "topic", :collection => Topic.find(:all) %>
# <%= render :partial => "topic", :collection => Topic.all %>
# <% end %>
#
# This cache will bind the name of the action that called it, so if
# this code was part of the view for the topics/list action, you
# would be able to invalidate it using:
#
#
# expire_fragment(:controller => "topics", :action => "list")
#
# This default behavior is limited if you need to cache multiple

@ -29,7 +29,7 @@ module ActionController
# class EventsController < ActionController::Base
# helper FormattedTimeHelper
# def index
# @events = Event.find(:all)
# @events = Event.all
# end
# end
#

@ -9,7 +9,7 @@ module ActionController #:nodoc:
# respond_to :html, :xml, :json
#
# def index
# @people = Person.find(:all)
# @people = Person.all
# respond_with(@people)
# end
# end

@ -20,7 +20,7 @@ module AtomFeedHelper
# # GET /posts.html
# # GET /posts.atom
# def index
# @posts = Post.find(:all)
# @posts = Post.all
#
# respond_to do |format|
# format.html

@ -191,7 +191,7 @@ def association_instance_set(name, association)
# * <tt>Project#portfolio, Project#portfolio=(portfolio), Project#portfolio.nil?</tt>
# * <tt>Project#project_manager, Project#project_manager=(project_manager), Project#project_manager.nil?,</tt>
# * <tt>Project#milestones.empty?, Project#milestones.size, Project#milestones, Project#milestones<<(milestone),</tt>
# <tt>Project#milestones.delete(milestone), Project#milestones.find(milestone_id), Project#milestones.find(:all, options),</tt>
# <tt>Project#milestones.delete(milestone), Project#milestones.find(milestone_id), Project#milestones.all(options),</tt>
# <tt>Project#milestones.build, Project#milestones.create</tt>
# * <tt>Project#categories.empty?, Project#categories.size, Project#categories, Project#categories<<(category1),</tt>
# <tt>Project#categories.delete(category1)</tt>
@ -420,7 +420,7 @@ def association_instance_set(name, association)
# end
# end
#
# person = Account.find(:first).people.find_or_create_by_name("David Heinemeier Hansson")
# person = Account.first.people.find_or_create_by_name("David Heinemeier Hansson")
# person.first_name # => "David"
# person.last_name # => "Heinemeier Hansson"
#
@ -477,7 +477,7 @@ def association_instance_set(name, association)
# belongs_to :book
# end
#
# @author = Author.find :first
# @author = Author.first
# @author.authorships.collect { |a| a.book } # selects all books that the author's authorships belong to
# @author.books # selects all books by using the Authorship join model
#
@ -497,7 +497,7 @@ def association_instance_set(name, association)
# belongs_to :client
# end
#
# @firm = Firm.find :first
# @firm = Firm.first
# @firm.clients.collect { |c| c.invoices }.flatten # select all invoices for all clients of the firm
# @firm.invoices # selects all invoices by going through the Client join model
#
@ -669,7 +669,7 @@ def association_instance_set(name, association)
# To iterate over these one hundred posts, we'll generate 201 database queries. Let's
# first just optimize it for retrieving the author:
#
# Post.find(:all, :include => :author).each do |post|
# Post.includes(:author).each do |post|
#
# This references the name of the +belongs_to+ association that also used the <tt>:author</tt>
# symbol. After loading the posts, find will collect the +author_id+ from each one and load
@ -678,7 +678,7 @@ def association_instance_set(name, association)
#
# We can improve upon the situation further by referencing both associations in the finder with:
#
# Post.find(:all, :include => [ :author, :comments ]).each do |post|
# Post.includes(:author, :comments).each do |post|
#
# This will load all comments with a single query. This reduces the total number of queries
# to 3. More generally the number of queries will be 1 plus the number of associations
@ -686,7 +686,7 @@ def association_instance_set(name, association)
#
# To include a deep hierarchy of associations, use a hash:
#
# Post.find(:all, :include => [ :author, { :comments => { :author => :gravatar } } ]).each do |post|
# Post.includes(:author, {:comments => {:author => :gravatar}}).each do |post|
#
# That'll grab not only all the comments but all their authors and gravatar pictures.
# You can mix and match symbols, arrays and hashes in any combination to describe the
@ -714,13 +714,13 @@ def association_instance_set(name, association)
# <tt>:order => "author.name DESC"</tt> will work but <tt>:order => "name DESC"</tt> will not.
#
# If you do want eager load only some members of an association it is usually more natural
# to <tt>:include</tt> an association which has conditions defined on it:
# to include an association which has conditions defined on it:
#
# class Post < ActiveRecord::Base
# has_many :approved_comments, :class_name => 'Comment', :conditions => ['approved = ?', true]
# end
#
# Post.find(:all, :include => :approved_comments)
# Post.includes(:approved_comments)
#
# This will load posts and eager load the +approved_comments+ association, which contains
# only those comments that have been approved.
@ -732,7 +732,7 @@ def association_instance_set(name, association)
# has_many :most_recent_comments, :class_name => 'Comment', :order => 'id DESC', :limit => 10
# end
#
# Picture.find(:first, :include => :most_recent_comments).most_recent_comments # => returns all associated comments.
# Picture.includes(:most_recent_comments).first.most_recent_comments # => returns all associated comments.
#
# When eager loaded, conditions are interpolated in the context of the model class, not
# the model instance. Conditions are lazily interpolated before the actual model exists.
@ -745,7 +745,7 @@ def association_instance_set(name, association)
#
# A call that tries to eager load the addressable model
#
# Address.find(:all, :include => :addressable)
# Address.includes(:addressable)
#
# This will execute one query to load the addresses and load the addressables with one
# query per addressable type.
@ -763,43 +763,43 @@ def association_instance_set(name, association)
# second time, the table is aliased as <tt>#{reflection_name}_#{parent_table_name}</tt>.
# Indexes are appended for any more successive uses of the table name.
#
# Post.find :all, :joins => :comments
# Post.joins(:comments)
# # => SELECT ... FROM posts INNER JOIN comments ON ...
# Post.find :all, :joins => :special_comments # STI
# Post.joins(:special_comments) # STI
# # => SELECT ... FROM posts INNER JOIN comments ON ... AND comments.type = 'SpecialComment'
# Post.find :all, :joins => [:comments, :special_comments] # special_comments is the reflection name, posts is the parent table name
# Post.joins(:comments, :special_comments) # special_comments is the reflection name, posts is the parent table name
# # => SELECT ... FROM posts INNER JOIN comments ON ... INNER JOIN comments special_comments_posts
#
# Acts as tree example:
#
# TreeMixin.find :all, :joins => :children
# TreeMixin.joins(:children)
# # => SELECT ... FROM mixins INNER JOIN mixins childrens_mixins ...
# TreeMixin.find :all, :joins => {:children => :parent}
# TreeMixin.joins(:children => :parent)
# # => SELECT ... FROM mixins INNER JOIN mixins childrens_mixins ...
# INNER JOIN parents_mixins ...
# TreeMixin.find :all, :joins => {:children => {:parent => :children}}
# TreeMixin.joins(:children => {:parent => :children})
# # => SELECT ... FROM mixins INNER JOIN mixins childrens_mixins ...
# INNER JOIN parents_mixins ...
# INNER JOIN mixins childrens_mixins_2
#
# Has and Belongs to Many join tables use the same idea, but add a <tt>_join</tt> suffix:
#
# Post.find :all, :joins => :categories
# Post.joins(:categories)
# # => SELECT ... FROM posts INNER JOIN categories_posts ... INNER JOIN categories ...
# Post.find :all, :joins => {:categories => :posts}
# Post.joins(:categories => :posts)
# # => SELECT ... FROM posts INNER JOIN categories_posts ... INNER JOIN categories ...
# INNER JOIN categories_posts posts_categories_join INNER JOIN posts posts_categories
# Post.find :all, :joins => {:categories => {:posts => :categories}}
# Post.joins(:categories => {:posts => :categories})
# # => SELECT ... FROM posts INNER JOIN categories_posts ... INNER JOIN categories ...
# INNER JOIN categories_posts posts_categories_join INNER JOIN posts posts_categories
# INNER JOIN categories_posts categories_posts_join INNER JOIN categories categories_posts_2
#
# If you wish to specify your own custom joins using a <tt>:joins</tt> option, those table
# If you wish to specify your own custom joins using <tt>joins</tt> method, those table
# names will take precedence over the eager associations:
#
# Post.find :all, :joins => :comments, :joins => "inner join comments ..."
# Post.joins(:comments).joins("inner join comments ...")
# # => SELECT ... FROM posts INNER JOIN comments_posts ON ... INNER JOIN comments ...
# Post.find :all, :joins => [:comments, :special_comments], :joins => "inner join comments ..."
# Post.joins(:comments, :special_comments).joins("inner join comments ...")
# # => SELECT ... FROM posts INNER JOIN comments comments_posts ON ...
# INNER JOIN comments special_comments_posts ...
# INNER JOIN comments ...
@ -1031,7 +1031,7 @@ module ClassMethods
# === Example
#
# Example: A Firm class declares <tt>has_many :clients</tt>, which will add:
# * <tt>Firm#clients</tt> (similar to <tt>Clients.find :all, :conditions => ["firm_id = ?", id]</tt>)
# * <tt>Firm#clients</tt> (similar to <tt>Clients.all :conditions => ["firm_id = ?", id]</tt>)
# * <tt>Firm#clients<<</tt>
# * <tt>Firm#clients.delete</tt>
# * <tt>Firm#clients=</tt>
@ -1195,7 +1195,7 @@ def has_many(name, options = {}, &extension)
# === Example
#
# An Account class declares <tt>has_one :beneficiary</tt>, which will add:
# * <tt>Account#beneficiary</tt> (similar to <tt>Beneficiary.find(:first, :conditions => "account_id = #{id}")</tt>)
# * <tt>Account#beneficiary</tt> (similar to <tt>Beneficiary.first(:conditions => "account_id = #{id}")</tt>)
# * <tt>Account#beneficiary=(beneficiary)</tt> (similar to <tt>beneficiary.account_id = account.id; beneficiary.save</tt>)
# * <tt>Account#build_beneficiary</tt> (similar to <tt>Beneficiary.new("account_id" => id)</tt>)
# * <tt>Account#create_beneficiary</tt> (similar to <tt>b = Beneficiary.new("account_id" => id); b.save; b</tt>)

@ -12,7 +12,7 @@ module Associations
# has_many :posts
# end
#
# blog = Blog.find(:first)
# blog = Blog.first
#
# the association proxy in <tt>blog.posts</tt> has the object in +blog+ as
# <tt>@owner</tt>, the collection of its posts as <tt>@target</tt>, and

@ -87,7 +87,7 @@ class InvalidForeignKey < WrappedDatabaseException
#
# For example, in
#
# Location.find :all, :conditions => ["lat = ? AND lng = ?", 53.7362]
# Location.where("lat = ? AND lng = ?", 53.7362)
#
# two placeholders are given but only one variable to fill them.
class PreparedStatementInvalid < ActiveRecordError

@ -149,13 +149,13 @@ class FixturesFileNotFound < StandardError; end
# self.use_transactional_fixtures = true
#
# test "godzilla" do
# assert !Foo.find(:all).empty?
# assert !Foo.all.empty?
# Foo.destroy_all
# assert Foo.find(:all).empty?
# assert Foo.all.empty?
# end
#
# test "godzilla aftermath" do
# assert !Foo.find(:all).empty?
# assert !Foo.all.empty?
# end
# end
#

@ -6,6 +6,8 @@
module ActiveSupport
class HashWithIndifferentAccess < Hash
# Always returns true, so that <tt>Array#extract_options!</tt> finds members of this class.
def extractable_options?
true
end

@ -256,7 +256,7 @@ The above will yield the supplied block with +1000+ invoices every time.
h3. Conditions
The +find+ method allows you to specify conditions to limit the records returned, representing the +WHERE+-part of the SQL statement. Conditions can either be specified as a string, array, or hash.
The +where+ method allows you to specify conditions to limit the records returned, representing the +WHERE+-part of the SQL statement. Conditions can either be specified as a string, array, or hash.
h4. Pure String Conditions

@ -32,7 +32,7 @@ h4. Main Features
The first is to concatenate of assets. This is important in a production environment to reduce the number of requests that a client browser has to make to render a web page. While Rails already has a feature to concatenate these types of asset--by placing +:cache => true+ at the end of tags such as +javascript_include_tag+ and +stylesheet_link_tag+--, many people do not use it.
The default behavior in 3.1 and onward is to concatenate all files into one master file each for JS and CSS, however you can separate files or groups of files if required (see below). In production an MD5 fingerprint is inserted into each filename.
The default behavior in Rails 3.1 and onward is to concatenate all files into one master file each for JS and CSS, however you can separate files or groups of files if required (see below). In production an MD5 fingerprint is inserted into each filename.
The second feature of the pipeline is to minify or compress. For CSS this usually involves removing whitespace and comments. For Javascript more complex processes can be applied.
@ -42,7 +42,7 @@ The third feature is the ability to code these assets using another language, or
h4. What is fingerprinting and why should I care?
Fingerprinting is a technique where the filenames of content that is static or infrequently updated is altered to be unique to the content contained in the file.
Fingerprinting is a technique where the filenames of content that is static or infrequently updated is altered to be unique to the content contained in the file.
When a filename is unique and based on its content, http headers can be set to encourage caches everywhere (at ISPs, in browsers) to keep there own copy of the content. When the content is updated, the fingerprint will change and the remote clients will request the new file. This is generally known as _cachebusting_.
@ -57,8 +57,8 @@ This is the strategy adopted by the Rails asset pipeline.
Rails old strategy was to append a query string to every asset linked with a built-in helper. In the source the generated code looked like this:
<plain>
/stylesheets/global.css?1309495796
</plain>
/stylesheets/global.css?1309495796
</plain>
This has several disadvantages:
@ -88,8 +88,7 @@ This is not to say that assets can (or should) no longer be placed in +public+.
When a scaffold or controller is generated for the application, Rails will also generate a JavaScript file (or CoffeeScript if the +coffee-script+ gem is in the +Gemfile+) and a Cascading Style Sheet file (or SCSS if +sass-rails+ is in the +Gemfile+) file for that controller.
For example, if a +ProjectsController+ is generated, there will be a new file at +app/assets/javascripts/projects.js.coffee+ and another at +app/assets/stylesheets/projects.css.scss+. You can put any JavaScript or CSS unique to a controller inside their respective asset files.
For example, if a +ProjectsController+ is generated, there will be a new file at +app/assets/javascripts/projects.js.coffee+ and another at +app/assets/stylesheets/projects.css.scss+. You should put any JavaScript or CSS unique to a controller inside their respective asset files, as these files can then be loaded just for these controllers with lines such as +<%= javascript_include_tag params[:controller] %>+ or +<%= stylesheet_link_tag params[:controller] %>+.
h4. Asset Organization
@ -184,6 +183,31 @@ In the development environment assets are compiled and cached on the first reque
If any of the files in the manifest have changed between requests, the server will respond with a new compiled file.
h4. Debugging Assets
You can put +?debug_assets=true+ or +?debug_assets=1+ at the end of a URL and Sprockets will expand the lines which load the assets. For example, if we had an +app/assets/javascripts/application.js+ file containing these lines:
<plain>
//= require "projects"
//= require "tickets"
</plain>
By default, this would only render this line when used with +<%= javascript_include_tag "application" %>+ in a view or layout:
<html>
<script src='/assets/application.js'></script>
</html>
When the +debug_assets+ parameter is set, this line will be expanded out into three separate lines, separating out the combined file into their parts.
<html>
<script src='/assets/application.js'></script>
<script src='/assets/projects.js'></script>
<script src='/assets/tickets.js'></script>
</html>
This allows the individual parts of an asset to be rendered and debugged separately.
h3. In Production
In the production environment, assets are served slightly differently.
@ -308,3 +332,4 @@ h3. Making Your Library or Gem a Pre-Processor
"You should be able to register [your gems] on Tilt and Sprockets will find them." - Josh
Tilt: https://github.com/rtomayko/tilt

@ -127,7 +127,7 @@ When something is logged it's printed into the corresponding log if the log leve
The available log levels are: +:debug+, +:info+, +:warn+, +:error+, and +:fatal+, corresponding to the log level numbers from 0 up to 4 respectively. To change the default log level, use
<ruby>
config.log_level = Logger::WARN # In any environment initializer, or
config.log_level = :warn # In any environment initializer, or
Rails.logger.level = 0 # at any time
</ruby>
@ -480,11 +480,7 @@ class Author < ActiveRecord::Base
def find_recent_comments(limit = 10)
debugger
@recent_comments ||= comments.find(
:all,
:conditions => ["created_at > ?", 1.week.ago],
:limit => limit
)
@recent_comments ||= comments.where("created_at > ?", 1.week.ago).limit(limit)
end
end
</ruby>
@ -493,7 +489,7 @@ TIP: You can use ruby-debug while using +rails console+. Just remember to +requi
<shell>
$ rails console
Loading development environment (Rails 2.1.0)
Loading development environment (Rails 3.1.0)
>> require "ruby-debug"
=> []
>> author = Author.first
@ -507,15 +503,15 @@ With the code stopped, take a look around:
<shell>
(rdb:1) list
[6, 15] in /PathTo/project/app/models/author.rb
[2, 9] in /PathTo/project/app/models/author.rb
2 has_one :editorial
3 has_many :comments
4
5 def find_recent_comments(limit = 10)
6 debugger
7 @recent_comments ||= comments.find(
8 :all,
9 :conditions => ["created_at > ?", 1.week.ago],
10 :limit => limit
=> 11 )
12 end
13 end
=> 7 @recent_comments ||= comments.where("created_at > ?", 1.week.ago).limit(limit)
8 end
9 end
</shell>
You are at the end of the line, but... was this line executed? You can inspect the instance variables.

@ -589,7 +589,7 @@ def upload
end
</ruby>
Once a file has been uploaded, there are a multitude of potential tasks, ranging from where to store the files (on disk, Amazon S3, etc) and associating them with models to resizing image files and generating thumbnails. The intricacies of this are beyond the scope of this guide, but there are several plugins designed to assist with these. Two of the better known ones are "Attachment-Fu":https://github.com/technoweenie/attachment_fu and "Paperclip":http://www.thoughtbot.com/projects/paperclip.
Once a file has been uploaded, there are a multitude of potential tasks, ranging from where to store the files (on disk, Amazon S3, etc) and associating them with models to resizing image files and generating thumbnails. The intricacies of this are beyond the scope of this guide, but there are several libraries designed to assist with these. Two of the better known ones are "CarrierWave":https://github.com/jnicklas/carrierwave and "Paperclip":http://www.thoughtbot.com/projects/paperclip.
NOTE: If the user has not selected a file the corresponding parameter will be an empty string.

@ -649,7 +649,7 @@ h5(#sql-injection-introduction). Introduction
SQL injection attacks aim at influencing database queries by manipulating web application parameters. A popular goal of SQL injection attacks is to bypass authorization. Another goal is to carry out data manipulation or reading arbitrary data. Here is an example of how not to use user input data in a query:
<ruby>
Project.all(:conditions => "name = '#{params[:name]}'")
Project.where("name = '#{params[:name]}'")
</ruby>
This could be in a search action and the user may enter a project's name that he wants to find. If a malicious user enters ' OR 1 --, the resulting SQL query will be:
@ -681,7 +681,7 @@ h5. Unauthorized Reading
The UNION statement connects two SQL queries and returns the data in one set. An attacker can use it to read arbitrary data from the database. Let's take the example from above:
<ruby>
Project.all(:conditions => "name = '#{params[:name]}'")
Project.where("name = '#{params[:name]}'")
</ruby>
And now let's inject another query using the UNION statement:
@ -703,18 +703,18 @@ Also, the second query renames some columns with the AS statement so that the we
h5(#sql-injection-countermeasures). Countermeasures
Ruby on Rails has a built-in filter for special SQL characters, which will escape ' , " , NULL character and line breaks. <em class="highlight">Using +Model.find(id)+ or +Model.find_by_some thing(something)+ automatically applies this countermeasure</em>. But in SQL fragments, especially <em class="highlight">in conditions fragments (+:conditions => "..."+), the +connection.execute()+ or +Model.find_by_sql()+ methods, it has to be applied manually</em>.
Ruby on Rails has a built-in filter for special SQL characters, which will escape ' , " , NULL character and line breaks. <em class="highlight">Using +Model.find(id)+ or +Model.find_by_some thing(something)+ automatically applies this countermeasure</em>. But in SQL fragments, especially <em class="highlight">in conditions fragments (+where("...")+), the +connection.execute()+ or +Model.find_by_sql()+ methods, it has to be applied manually</em>.
Instead of passing a string to the conditions option, you can pass an array to sanitize tainted strings like this:
<ruby>
Model.first(:conditions => ["login = ? AND password = ?", entered_user_name, entered_password])
Model.where("login = ? AND password = ?", entered_user_name, entered_password).first
</ruby>
As you can see, the first part of the array is an SQL fragment with question marks. The sanitized versions of the variables in the second part of the array replace the question marks. Or you can pass a hash for the same result:
<ruby>
Model.first(:conditions => {:login => entered_user_name, :password => entered_password})
Model.where(:login => entered_user_name, :password => entered_password).first
</ruby>
The array or hash form is only available in model instances. You can try +sanitize_sql()+ elsewhere. _(highlight)Make it a habit to think about the security consequences when using an external string in SQL_.