diff options
Diffstat (limited to 'railties/guides/source/active_record_querying.textile')
-rw-r--r-- | railties/guides/source/active_record_querying.textile | 319 |
1 files changed, 197 insertions, 122 deletions
diff --git a/railties/guides/source/active_record_querying.textile b/railties/guides/source/active_record_querying.textile index 53095a2bd3..009d541106 100644 --- a/railties/guides/source/active_record_querying.textile +++ b/railties/guides/source/active_record_querying.textile @@ -19,8 +19,6 @@ Code examples throughout this guide will refer to one or more of the following m TIP: All of the following models use +id+ as the primary key, unless specified otherwise. -<br /> - <ruby> class Client < ActiveRecord::Base has_one :address @@ -65,6 +63,7 @@ The methods are: * +lock+ * +readonly+ * +from+ +* +having+ All of the above methods return an instance of <tt>ActiveRecord::Relation</tt>. @@ -103,7 +102,7 @@ h5. +first+ <ruby> client = Client.first -=> #<Client id: 1, first_name: => "Lifo"> +=> #<Client id: 1, first_name: "Lifo"> </ruby> SQL equivalent of the above is: @@ -120,7 +119,7 @@ h5. +last+ <ruby> client = Client.last -=> #<Client id: 221, first_name: => "Russel"> +=> #<Client id: 221, first_name: "Russel"> </ruby> SQL equivalent of the above is: @@ -149,24 +148,24 @@ SQL equivalent of the above is: SELECT * FROM clients WHERE (clients.id IN (1,10)) </sql> -<tt>Model.find(array_of_primary_key)</tt> will raise an +ActiveRecord::RecordNotFound+ exception unless a matching record is found for <strong>all</strong> of the supplied primary keys. +WARNING: <tt>Model.find(array_of_primary_key)</tt> will raise an +ActiveRecord::RecordNotFound+ exception unless a matching record is found for <strong>all</strong> of the supplied primary keys. h4. Retrieving Multiple Objects in Batches -Sometimes you need to iterate over a large set of records. For example to send a newsletter to all users, to export some data, etc. +Sometimes you need to iterate over a large set of records. For example to send a newsletter to all users, to export some data, etc. The following may seem very straight forward at first: <ruby> # Very inefficient when users table has thousands of rows. -User.each do |user| +User.all.each do |user| NewsLetter.weekly_deliver(user) end </ruby> But if the total number of rows in the table is very large, the above approach may vary from being under performant to just plain impossible. -This is because +User.each+ makes Active Record fetch _the entire table_, build a model object per row, and keep the entire array in the memory. Sometimes that is just too many objects and demands too much memory. +This is because +User.all.each+ makes Active Record fetch _the entire table_, build a model object per row, and keep the entire array in the memory. Sometimes that is just too many objects and demands too much memory. h5. +find_each+ @@ -231,10 +230,10 @@ WARNING: Building your own conditions as pure strings can leave you vulnerable t h4. Array Conditions -Now what if that number could vary, say as an argument from somewhere, or perhaps from the user's level status somewhere? The find then becomes something like: +Now what if that number could vary, say as an argument from somewhere? The find then becomes something like: <ruby> -Client.where(["orders_count = ?", params[:orders]]) +Client.where("orders_count = ?", params[:orders]) </ruby> Active Record will go through the first element in the conditions value and any additional elements will replace the question marks +(?)+ in the first element. @@ -242,7 +241,7 @@ Active Record will go through the first element in the conditions value and any Or if you want to specify two conditions, you can do it like: <ruby> -Client.where(["orders_count = ? AND locked = ?", params[:orders], false]) +Client.where("orders_count = ? AND locked = ?", params[:orders], false) </ruby> In this example, the first question mark will be replaced with the value in +params[:orders]+ and the second will be replaced with the SQL representation of +false+, which depends on the adapter. @@ -250,7 +249,7 @@ In this example, the first question mark will be replaced with the value in +par The reason for doing code like: <ruby> -Client.where(["orders_count = ?", params[:orders]]) +Client.where("orders_count = ?", params[:orders]) </ruby> instead of: @@ -268,8 +267,8 @@ h5. Placeholder Conditions Similar to the +(?)+ replacement style of params, you can also specify keys/values hash in your array conditions: <ruby> -Client.where( - ["created_at >= :start_date AND created_at <= :end_date", { :start_date => params[:start_date], :end_date => params[:end_date] }]) +Client.where("created_at >= :start_date AND created_at <= :end_date", + {:start_date => params[:start_date], :end_date => params[:end_date]}) </ruby> This makes for clearer readability if you have a large number of variable conditions. @@ -279,62 +278,15 @@ h5(#array-range_conditions). Range Conditions If you're looking for a range inside of a table (for example, users created in a certain timeframe) you can use the conditions option coupled with the +IN+ SQL statement for this. If you had two dates coming in from a controller you could do something like this to look for a range: <ruby> -Client.where(["created_at IN (?)", - (params[:start_date].to_date)..(params[:end_date].to_date)]) +Client.where(:created_at => (params[:start_date].to_date)..(params[:end_date].to_date)) </ruby> -This would generate the proper query which is great for small ranges but not so good for larger ranges. For example if you pass in a range of date objects spanning a year that's 365 (or possibly 366, depending on the year) strings it will attempt to match your field against. +This query will generate something similar to the following SQL: <sql> -SELECT * FROM users WHERE (created_at IN - ('2007-12-31','2008-01-01','2008-01-02','2008-01-03','2008-01-04','2008-01-05', - '2008-01-06','2008-01-07','2008-01-08','2008-01-09','2008-01-10','2008-01-11', - '2008-01-12','2008-01-13','2008-01-14','2008-01-15','2008-01-16','2008-01-17', - '2008-01-18','2008-01-19','2008-01-20','2008-01-21','2008-01-22','2008-01-23',... - ‘2008-12-15','2008-12-16','2008-12-17','2008-12-18','2008-12-19','2008-12-20', - '2008-12-21','2008-12-22','2008-12-23','2008-12-24','2008-12-25','2008-12-26', - '2008-12-27','2008-12-28','2008-12-29','2008-12-30','2008-12-31')) + SELECT "clients".* FROM "clients" WHERE ("clients"."created_at" BETWEEN '2010-09-29' AND '2010-11-30') </sql> -h5. Time and Date Conditions - -Things can get *really* messy if you pass in Time objects as it will attempt to compare your field to *every second* in that range: - -<ruby> -Client.where(["created_at IN (?)", - (params[:start_date].to_date.to_time)..(params[:end_date].to_date.to_time)]) -</ruby> - -<sql> -SELECT * FROM users WHERE (created_at IN - ('2007-12-01 00:00:00', '2007-12-01 00:00:01' ... - '2007-12-01 23:59:59', '2007-12-02 00:00:00')) -</sql> - -This could possibly cause your database server to raise an unexpected error, for example MySQL will throw back this error: - -<shell> -Got a packet bigger than 'max_allowed_packet' bytes: _query_ -</shell> - -Where _query_ is the actual query used to get that error. - -In this example it would be better to use greater-than and less-than operators in SQL, like so: - -<ruby> -Client.where( - ["created_at > ? AND created_at < ?", params[:start_date], params[:end_date]]) -</ruby> - -You can also use the greater-than-or-equal-to and less-than-or-equal-to like this: - -<ruby> -Client.where( - ["created_at >= ? AND created_at <= ?", params[:start_date], params[:end_date]]) -</ruby> - -Just like in Ruby. If you want a shorter syntax be sure to check out the "Hash Conditions":#hash-conditions section later on in the guide. - h4. Hash Conditions Active Record also allows you to pass in hash conditions which can increase the readability of your conditions syntax. With hash conditions, you pass in a hash with keys of the fields you want conditionalised and the values of how you want to conditionalise them: @@ -344,13 +296,13 @@ NOTE: Only equality, range and subset checking are possible with Hash conditions h5. Equality Conditions <ruby> -Client.where({ :locked => true }) +Client.where(:locked => true) </ruby> The field name can also be a string: <ruby> -Client.where({ 'locked' => true }) +Client.where('locked' => true) </ruby> h5(#hash-range_conditions). Range Conditions @@ -358,7 +310,7 @@ h5(#hash-range_conditions). Range Conditions The good thing about this is that we can pass in a range for our fields without it generating a large query as shown in the preamble of this section. <ruby> -Client.where({ :created_at => (Time.now.midnight - 1.day)..Time.now.midnight}) +Client.where(:created_at => (Time.now.midnight - 1.day)..Time.now.midnight) </ruby> This will find all clients created yesterday by using a +BETWEEN+ SQL statement: @@ -374,7 +326,7 @@ h5. Subset Conditions If you want to find records using the +IN+ expression you can pass an array to the conditions hash: <ruby> -Client.where({ :orders_count => [1,3,5] }) +Client.where(:orders_count => [1,3,5]) </ruby> This code will generate SQL like this: @@ -383,9 +335,9 @@ This code will generate SQL like this: SELECT * FROM clients WHERE (clients.orders_count IN (1,3,5)) </sql> -h4. Ordering +h3. Ordering -To retrieve records from the database in a specific order, you can specify the +:order+ option to the +find+ call. +To retrieve records from the database in a specific order, you can use the +order+ method. For example, if you're getting a set of records and want to order them in ascending order by the +created_at+ field in your table: @@ -407,7 +359,7 @@ Or ordering by multiple fields: Client.order("orders_count ASC, created_at DESC") </ruby> -h4. Selecting Specific Fields +h3. Selecting Specific Fields By default, <tt>Model.find</tt> selects all the fields from the result set using +select *+. @@ -443,7 +395,7 @@ You can also call SQL functions within the select option. For example, if you wo Client.select("DISTINCT(name)") </ruby> -h4. Limit and Offset +h3. Limit and Offset To apply +LIMIT+ to the SQL fired by the +Model.find+, you can specify the +LIMIT+ using +limit+ and +offset+ methods on the relation. @@ -468,10 +420,10 @@ Client.limit(5).offset(30) will return instead a maximum of 5 clients beginning with the 31st. The SQL looks like: <sql> -SELECT * FROM clients LIMIT 5, 30 +SELECT * FROM clients LIMIT 5 OFFSET 30 </sql> -h4. Group +h3. Group To apply a +GROUP BY+ clause to the SQL fired by the finder, you can specify the +group+ method on the find. @@ -486,17 +438,17 @@ And this will give you a single +Order+ object for each date where there are ord The SQL that would be executed would be something like this: <sql> -SELECT * FROM orders GROUP BY date(created_at) +SELECT * FROM orders GROUP BY date(created_at) ORDER BY created_at </sql> -h4. Having +h3. Having SQL uses the +HAVING+ clause to specify conditions on the +GROUP BY+ fields. You can add the +HAVING+ clause to the SQL fired by the +Model.find+ by adding the +:having+ option to the find. For example: <ruby> -Order.group("date(created_at)".having(["created_at > ?", 1.month.ago]) +Order.group("date(created_at)").having("created_at > ?", 1.month.ago) </ruby> The SQL that would be executed would be something like this: @@ -507,25 +459,49 @@ SELECT * FROM orders GROUP BY date(created_at) HAVING created_at > '2009-01-15' This will return single order objects for each day, but only for the last month. -h4. Readonly Objects +h3. Overriding Conditions -To explicitly disallow modification/destruction of the matching records returned in a Relation object, you could chain the +readonly+ method as +true+ to the find call. +You can specify certain conditions to be excepted by using the +except+ method. -Any attempt to alter or destroy the readonly records will not succeed, raising an +ActiveRecord::ReadOnlyRecord+ exception. To set this option, specify it like this: +For example: <ruby> -Client.first.readonly(true) +Post.where('id > 10').limit(20).order('id asc').except(:order) </ruby> -For example, calling the following code will raise an +ActiveRecord::ReadOnlyRecord+ exception: +The SQL that would be executed: + +<sql> +SELECT * FROM posts WHERE id > 10 LIMIT 20 +</sql> + +You can also override conditions using the +only+ method. + +For example: <ruby> -client = Client.first.readonly(true) -client.locked = false +Post.where('id > 10').limit(20).order('id desc').only(:order, :where) +</ruby> + +The SQL that would be executed: + +<sql> +SELECT * FROM posts WHERE id > 10 ORDER BY id DESC +</sql> + +h3. Readonly Objects + +Active Record provides +readonly+ method on a relation to explicitly disallow modification or deletion of any of the returned object. Any attempt to alter or destroy a readonly record will not succeed, raising an +ActiveRecord::ReadOnlyRecord+ exception. + +<ruby> +client = Client.readonly.first +client.visits += 1 client.save </ruby> -h4. Locking Records for Update +As +client+ is explicitly set to be a readonly object, the above code will raise an +ActiveRecord::ReadOnlyRecord+ exception when calling +client.save+ with an updated value of _visits_. + +h3. Locking Records for Update Locking is helpful for preventing race conditions when updating records in the database and ensuring atomic updates. @@ -534,7 +510,7 @@ Active Record provides two locking mechanisms: * Optimistic Locking * Pessimistic Locking -h5. Optimistic Locking +h4. Optimistic Locking Optimistic locking allows multiple users to access the same record for edits, and assumes a minimum of conflicts with the data. It does this by checking whether another process has made changes to a record since it was opened. An +ActiveRecord::StaleObjectError+ exception is thrown if that has occurred and the update is ignored. @@ -569,15 +545,15 @@ class Client < ActiveRecord::Base end </ruby> -h5. Pessimistic Locking +h4. Pessimistic Locking -Pessimistic locking uses a locking mechanism provided by the underlying database. Passing +:lock => true+ to +Model.find+ obtains an exclusive lock on the selected rows. +Model.find+ using +:lock+ are usually wrapped inside a transaction for preventing deadlock conditions. +Pessimistic locking uses a locking mechanism provided by the underlying database. Using +lock+ when building a relation obtains an exclusive lock on the selected rows. Relations using +lock+ are usually wrapped inside a transaction for preventing deadlock conditions. For example: <ruby> Item.transaction do - i = Item.first(:lock => true) + i = Item.lock.first i.name = 'Jones' i.save end @@ -592,25 +568,25 @@ Item Update (0.4ms) UPDATE `items` SET `updated_at` = '2009-02-07 18:05:56', ` SQL (0.8ms) COMMIT </sql> -You can also pass raw SQL to the +:lock+ option to allow different types of locks. For example, MySQL has an expression called +LOCK IN SHARE MODE+ where you can lock a record but still allow other queries to read it. To specify this expression just pass it in as the lock option: +You can also pass raw SQL to the +lock+ method for allowing different types of locks. For example, MySQL has an expression called +LOCK IN SHARE MODE+ where you can lock a record but still allow other queries to read it. To specify this expression just pass it in as the lock option: <ruby> Item.transaction do - i = Item.find(1, :lock => "LOCK IN SHARE MODE") + i = Item.lock("LOCK IN SHARE MODE").find(1) i.increment!(:views) end </ruby> h3. Joining Tables -<tt>Model.find</tt> provides a +:joins+ option for specifying +JOIN+ clauses on the resulting SQL. There are multiple ways to specify the +:joins+ option: +Active Record provides a finder method called +joins+ for specifying +JOIN+ clauses on the resulting SQL. There are multiple ways to use the +joins+ method. h4. Using a String SQL Fragment -You can just supply the raw SQL specifying the +JOIN+ clause to the +:joins+ option. For example: +You can just supply the raw SQL specifying the +JOIN+ clause to +joins+: <ruby> -Client.all(:joins => 'LEFT OUTER JOIN addresses ON addresses.client_id = clients.id') +Client.joins('LEFT OUTER JOIN addresses ON addresses.client_id = clients.id') </ruby> This will result in the following SQL: @@ -621,11 +597,9 @@ SELECT clients.* FROM clients LEFT OUTER JOIN addresses ON addresses.client_id = h4. Using Array/Hash of Named Associations -WARNING: This method only works with +INNER JOIN+, +WARNING: This method only works with +INNER JOIN+. -<br /> - -Active Record lets you use the names of the "associations":association_basics.html defined on the model as a shortcut for specifying the +:joins+ option. +Active Record lets you use the names of the "associations":association_basics.html defined on the model as a shortcut for specifying +JOIN+ clause for those associations when using the +joins+ method. For example, consider the following +Category+, +Post+, +Comments+ and +Guest+ models: @@ -674,7 +648,7 @@ Post.joins(:category, :comments) This produces: <sql> -SELECT posts.* FROM posts +SELECT posts.* FROM posts INNER JOIN categories ON posts.category_id = categories.id INNER JOIN comments ON comments.post_id = posts.id </sql> @@ -718,7 +692,7 @@ Eager loading is the mechanism for loading the associated records of the objects Consider the following code, which finds 10 clients and prints their postcodes: <ruby> -clients = Client.all(:limit => 10) +clients = Client.limit(10) clients.each do |client| puts client.address.postcode @@ -753,7 +727,7 @@ h4. Eager Loading Multiple Associations Active Record lets you eager load any number of associations with a single +Model.find+ call by using an array, hash, or a nested hash of array/hash with the +includes+ method. -h5. Array of Multiple Associations +h5. Array of Multiple Associations <ruby> Post.includes(:category, :comments) @@ -771,17 +745,119 @@ This will find the category with id 1 and eager load all of the associated posts h4. Specifying Conditions on Eager Loaded Associations -Even though Active Record lets you specify conditions on the eager loaded associations just like +joins+, the recommended way is to use "joins":#joining-tables instead. +Even though Active Record lets you specify conditions on the eager loaded associations just like +joins+, the recommended way is to use "joins":#joining-tables instead. + +However if you must do this, you may use +where+ as you would normally. + +<ruby> +Post.includes(:comments).where("comments.visible", true) +</ruby> + +This would generate a query which contains a +LEFT OUTER JOIN+ whereas the +joins+ method would generate one using the +INNER JOIN+ function instead. + +<ruby> + SELECT "posts"."id" AS t0_r0, ... "comments"."updated_at" AS t1_r5 FROM "posts" LEFT OUTER JOIN "comments" ON "comments"."post_id" = "posts"."id" WHERE (comments.visible) +</ruby> + +If there was no +where+ condition, this would generate the normal set of two queries. + +If, in the case of this +includes+ query, there were no comments for any posts, all the posts would still be loaded. By using +joins+ (an INNER JOIN), the join conditions *must* match, otherwise no records will be returned. + +h3. Scopes + +Scoping allows you to specify commonly-used ARel queries which can be referenced as method calls on the association objects or models. With these scopes, you can use every method previously covered such as +where+, +joins+ and +includes+. All scope methods will return an +ActiveRecord::Relation+ object which will allow for further methods (such as other scopes) to be called on it. + +To define a simple scope, we use the +scope+ method inside the class, passing the ARel query that we'd like run when this scope is called: + +<ruby> +class Post < ActiveRecord::Base + scope :published, where(:published => true) +end +</ruby> + +Just like before, these methods are also chainable: + +<ruby> +class Post < ActiveRecord::Base + scope :published, where(:published => true).joins(:category) +end +</ruby> + +Scopes are also chainable within scopes: + +<ruby> +class Post < ActiveRecord::Base + scope :published, where(:published => true) + scope :published_and_commented, published.and(self.arel_table[:comments_count].gt(0)) +end +</ruby> + +To call this +published+ scope we can call it on either the class: + +<ruby> +Post.published => [published posts] +</ruby> + +Or on an association consisting of +Post+ objects: + +<ruby> +category = Category.first +category.posts.published => [published posts belonging to this category] +</ruby> + +h4. Working with times + +If you're working with dates or times within scopes, due to how they are evaluated, you will need to use a lambda so that the scope is evaluated every time. + +<ruby> +class Post < ActiveRecord::Base + scope :last_week, lambda { where("created_at < ?", Time.zone.now ) } +end +</ruby> + +Without the +lambda+, this +Time.zone.now+ will only be called once. + +h4. Passing in arguments + +When a +lambda+ is used for a +scope+, it can take arguments: + +<ruby> +class Post < ActiveRecord::Base + scope :1_week_before, lambda { |time| where("created_at < ?", time) +end +</ruby> + +This may then be called using this: + +<ruby> +Post.1_week_before(Time.zone.now) +</ruby> + +However, this is just duplicating the functionality that would be provided to you by a class method. + +<ruby> +class Post < ActiveRecord::Base + def self.1_week_before(time) + where("created_at < ?", time) + end +end +</ruby> + +Using a class method is the preferred way to accept arguments for scopes. These methods will still be accessible on the association objects: + +<ruby> +category.posts.1_week_before(time) +</ruby> h3. Dynamic Finders -For every field (also known as an attribute) you define in your table, Active Record provides a finder method. If you have a field called +first_name+ on your +Client+ model for example, you get +find_by_first_name+ and +find_all_by_first_name+ for free from Active Record. If you have also have a +locked+ field on the +Client+ model, you also get +find_by_locked+ and +find_all_by_locked+. +For every field (also known as an attribute) you define in your table, Active Record provides a finder method. If you have a field called +first_name+ on your +Client+ model for example, you get +find_by_first_name+ and +find_all_by_first_name+ for free from Active Record. If you have a +locked+ field on the +Client+ model, you also get +find_by_locked+ and +find_all_by_locked+ methods. -You can do +find_last_by_*+ methods too which will find the last record matching your argument. +You can also use +find_last_by_*+ methods which will find the last record matching your argument. You can specify an exclamation point (<tt>!</tt>) on the end of the dynamic finders to get them to raise an +ActiveRecord::RecordNotFound+ error if they do not return any records, like +Client.find_by_name!("Ryan")+ -If you want to find both by name and locked, you can chain these finders together by simply typing +and+ between the fields for example +Client.find_by_first_name_and_locked("Ryan", true)+. +If you want to find both by name and locked, you can chain these finders together by simply typing +and+ between the fields. For example, +Client.find_by_first_name_and_locked("Ryan", true)+. There's another set of dynamic finders that let you find or create/initialize objects if they aren't found. These work in a similar fashion to the other finders and can be used like +find_or_create_by_first_name(params[:first_name])+. Using this will first perform a find and then create if the find returns +nil+. The SQL looks like this for +Client.find_or_create_by_first_name("Ryan")+: @@ -807,8 +883,8 @@ h3. Finding by SQL If you'd like to use your own SQL to find records in a table you can use +find_by_sql+. The +find_by_sql+ method will return an array of objects even if the underlying query returns just a single record. For example you could run this query: <ruby> -Client.find_by_sql("SELECT * FROM clients - INNER JOIN orders ON clients.id = orders.client_id +Client.find_by_sql("SELECT * FROM clients + INNER JOIN orders ON clients.id = orders.client_id ORDER clients.created_at desc") </ruby> @@ -838,13 +914,13 @@ Client.exists?(1,2,3) Client.exists?([1,2,3]) </ruby> -The +exists+ method may also take a +conditions+ option much like find: +It's even possible to use +exists?+ without any arguments on a model or a relation. <ruby> -Client.exists?(:conditions => "first_name = 'Ryan'") +Client.where(:first_name => 'Ryan').exists? </ruby> -It's even possible to use +exists?+ without any arguments: +The above returns +true+ if there is at least one client with the +first_name+ 'Ryan' and +false+ otherwise. <ruby> Client.exists? @@ -856,22 +932,24 @@ h3. Calculations This section uses count as an example method in this preamble, but the options described apply to all sub-sections. -<tt>count</tt> takes conditions much in the same way +exists?+ does: +All calculation methods work directly on a model: <ruby> -Client.count(:conditions => "first_name = 'Ryan'") +Client.count +# SELECT count(*) AS count_all FROM clients </ruby> -Which will execute: +Or on a relation : -<sql> -SELECT count(*) AS count_all FROM clients WHERE (first_name = 'Ryan') -</sql> +<ruby> +Client.where(:first_name => 'Ryan').count +# SELECT count(*) AS count_all FROM clients WHERE (first_name = 'Ryan') +</ruby> -You can also use the +includes+ or +joins+ methods for this to do something a little more complex: +You can also use various finder methods on a relation for performing complex calculations: <ruby> -Client.where("clients.first_name = 'Ryan' AND orders.status = 'received'").includes("orders").count +Client.includes("orders").where(:first_name => 'Ryan', :orders => {:status => 'received'}).count </ruby> Which will execute: @@ -882,8 +960,6 @@ SELECT count(DISTINCT clients.id) AS count_all FROM clients (clients.first_name = 'Ryan' AND orders.status = 'received') </sql> -This code specifies +clients.first_name+ just in case one of the join tables has a field also called +first_name+ and it uses +orders.status+ because that's the name of our join table. - h4. Count If you want to see how many records are in your model's table you could call +Client.count+ and that will return the number. If you want to be more specific and find all the clients with their age present in the database you can use +Client.count(:age)+. @@ -934,8 +1010,7 @@ For options, please see the parent section, "Calculations":#calculations. h3. Changelog -"Lighthouse ticket":http://rails.lighthouseapp.com/projects/16213-rails-guides/tickets/16 - +* December 23 2010: Add documentation for the +scope+ method. "Ryan Bigg":http://ryanbigg.com * April 7, 2010: Fixed document to validate XHTML 1.0 Strict. "Jaime Iniesta":http://jaimeiniesta.com * February 3, 2010: Update to Rails 3 by "James Miller":credits.html#bensie * February 7, 2009: Second version by "Pratik":credits.html#lifo |