activerecord 1.12.2 → 1.13.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of activerecord might be problematic. Click here for more details.

Files changed (66) hide show
  1. data/CHANGELOG +92 -0
  2. data/README +9 -9
  3. data/lib/active_record/acts/list.rb +1 -1
  4. data/lib/active_record/acts/nested_set.rb +13 -13
  5. data/lib/active_record/acts/tree.rb +7 -6
  6. data/lib/active_record/aggregations.rb +4 -4
  7. data/lib/active_record/associations.rb +82 -21
  8. data/lib/active_record/associations/association_collection.rb +0 -8
  9. data/lib/active_record/associations/association_proxy.rb +5 -2
  10. data/lib/active_record/associations/belongs_to_association.rb +6 -2
  11. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +11 -1
  12. data/lib/active_record/associations/has_many_association.rb +34 -5
  13. data/lib/active_record/associations/has_one_association.rb +1 -1
  14. data/lib/active_record/base.rb +144 -59
  15. data/lib/active_record/callbacks.rb +6 -6
  16. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +3 -2
  17. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +4 -4
  18. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +10 -8
  19. data/lib/active_record/connection_adapters/abstract_adapter.rb +1 -1
  20. data/lib/active_record/connection_adapters/db2_adapter.rb +17 -1
  21. data/lib/active_record/connection_adapters/oci_adapter.rb +322 -185
  22. data/lib/active_record/connection_adapters/sqlite_adapter.rb +9 -8
  23. data/lib/active_record/connection_adapters/sqlserver_adapter.rb +107 -14
  24. data/lib/active_record/fixtures.rb +10 -8
  25. data/lib/active_record/migration.rb +20 -6
  26. data/lib/active_record/observer.rb +4 -3
  27. data/lib/active_record/reflection.rb +5 -5
  28. data/lib/active_record/transactions.rb +2 -2
  29. data/lib/active_record/validations.rb +70 -40
  30. data/lib/active_record/vendor/mysql411.rb +9 -13
  31. data/lib/active_record/version.rb +2 -2
  32. data/rakefile +1 -1
  33. data/test/abstract_unit.rb +5 -0
  34. data/test/ar_schema_test.rb +1 -1
  35. data/test/associations_extensions_test.rb +37 -0
  36. data/test/associations_go_eager_test.rb +25 -0
  37. data/test/associations_test.rb +14 -6
  38. data/test/base_test.rb +63 -45
  39. data/test/connections/native_sqlite/connection.rb +2 -2
  40. data/test/connections/native_sqlite3/connection.rb +2 -2
  41. data/test/connections/native_sqlite3/in_memory_connection.rb +1 -1
  42. data/test/debug.log +2857 -0
  43. data/test/deprecated_finder_test.rb +3 -9
  44. data/test/finder_test.rb +27 -13
  45. data/test/fixtures/author.rb +4 -0
  46. data/test/fixtures/comment.rb +4 -8
  47. data/test/fixtures/db_definitions/create_oracle_db.bat +0 -5
  48. data/test/fixtures/db_definitions/create_oracle_db.sh +0 -5
  49. data/test/fixtures/db_definitions/db2.drop.sql +0 -1
  50. data/test/fixtures/db_definitions/oci.sql +2 -0
  51. data/test/fixtures/db_definitions/sqlserver.drop.sql +1 -1
  52. data/test/fixtures/developer.rb +18 -1
  53. data/test/fixtures/fixture_database.sqlite +0 -0
  54. data/test/fixtures/fixture_database_2.sqlite +0 -0
  55. data/test/fixtures/migrations_with_duplicate/1_people_have_last_names.rb +9 -0
  56. data/test/fixtures/migrations_with_duplicate/2_we_need_reminders.rb +12 -0
  57. data/test/fixtures/migrations_with_duplicate/3_foo.rb +7 -0
  58. data/test/fixtures/migrations_with_duplicate/3_innocent_jointable.rb +12 -0
  59. data/test/fixtures/post.rb +18 -4
  60. data/test/fixtures/reply.rb +3 -1
  61. data/test/inheritance_test.rb +3 -2
  62. data/test/{conditions_scoping_test.rb → method_scoping_test.rb} +55 -10
  63. data/test/migration_test.rb +68 -31
  64. data/test/readonly_test.rb +65 -5
  65. data/test/validations_test.rb +10 -2
  66. metadata +13 -4
data/CHANGELOG CHANGED
@@ -1,3 +1,95 @@
1
+ *1.13.0* (November 7th, 2005)
2
+
3
+ * Fixed faulty regex in get_table_name method (SQLServerAdapter) #2639 [Ryan Tomayko]
4
+
5
+ * Added :include as an option for association declarations [DHH]. Example:
6
+
7
+ has_many :posts, :include => [ :author, :comments ]
8
+
9
+ * Rename Base.constrain to Base.with_scope so it doesn't conflict with existing concept of database constraints. Make scoping more robust: uniform method => parameters, validated method names and supported finder parameters, raise exception on nested scopes. [Jeremy Kemper] Example:
10
+
11
+ Comment.with_scope(:find => { :conditions => 'active=true' }, :create => { :post_id => 5 }) do
12
+ # Find where name = ? and active=true
13
+ Comment.find :all, :conditions => ['name = ?', name]
14
+ # Create comment associated with :post_id
15
+ Comment.create :body => "Hello world"
16
+ end
17
+
18
+ * Fixed that SQL Server should ignore :size declarations on anything but integer and string in the agnostic schema representation #2756 [Ryan Tomayko]
19
+
20
+ * Added constrain scoping for creates using a hash of attributes bound to the :creation key [DHH]. Example:
21
+
22
+ Comment.constrain(:creation => { :post_id => 5 }) do
23
+ # Associated with :post_id
24
+ Comment.create :body => "Hello world"
25
+ end
26
+
27
+ This is rarely used directly, but allows for find_or_create on associations. So you can do:
28
+
29
+ # If the tag doesn't exist, a new one is created that's associated with the person
30
+ person.tags.find_or_create_by_name("Summer")
31
+
32
+ * Added find_or_create_by_X as a second type of dynamic finder that'll create the record if it doesn't already exist [DHH]. Example:
33
+
34
+ # No 'Summer' tag exists
35
+ Tag.find_or_create_by_name("Summer") # equal to Tag.create(:name => "Summer")
36
+
37
+ # Now the 'Summer' tag does exist
38
+ Tag.find_or_create_by_name("Summer") # equal to Tag.find_by_name("Summer")
39
+
40
+ * Added extension capabilities to has_many and has_and_belongs_to_many proxies [DHH]. Example:
41
+
42
+ class Account < ActiveRecord::Base
43
+ has_many :people do
44
+ def find_or_create_by_name(name)
45
+ first_name, *last_name = name.split
46
+ last_name = last_name.join " "
47
+
48
+ find_or_create_by_first_name_and_last_name(first_name, last_name)
49
+ end
50
+ end
51
+ end
52
+
53
+ person = Account.find(:first).people.find_or_create_by_name("David Heinemeier Hansson")
54
+ person.first_name # => "David"
55
+ person.last_name # => "Heinemeier Hansson"
56
+
57
+ Note that the anoymous module must be declared using brackets, not do/end (due to order of evaluation).
58
+
59
+ * Omit internal dtproperties table from SQLServer table list. #2729 [rtomayko@gmail.com]
60
+
61
+ * Quote column names in generated SQL. #2728 [rtomayko@gmail.com]
62
+
63
+ * Correct the pure-Ruby MySQL 4.1.1 shim's version test. #2718 [Jeremy Kemper]
64
+
65
+ * Add Model.create! to match existing model.save! method. When save! raises RecordInvalid, you can catch the exception, retrieve the invalid record (invalid_exception.record), and see its errors (invalid_exception.record.errors). [Jeremy Kemper]
66
+
67
+ * Correct fixture behavior when table name pluralization is off. #2719 [Rick Bradley <rick@rickbradley.com>]
68
+
69
+ * Changed :dbfile to :database for SQLite adapter for consistency (old key still works as an alias) #2644 [Dan Peterson]
70
+
71
+ * Added migration support for Oracle #2647 [Michael Schoen]
72
+
73
+ * Worked around that connection can't be reset if allow_concurrency is off. #2648 [Michael Schoen <schoenm@earthlink.net>]
74
+
75
+ * Fixed SQL Server adapter to pass even more tests and do even better #2634 [rtomayko@gmail.com]
76
+
77
+ * Fixed SQL Server adapter so it honors options[:conditions] when applying :limits #1978 [Tom Ward]
78
+
79
+ * Added migration support to SQL Server adapter (please someone do the same for Oracle and DB2) #2625 [Tom Ward]
80
+
81
+ * Use AR::Base.silence rather than AR::Base.logger.silence in fixtures to preserve Log4r compatibility. #2618 [dansketcher@gmail.com]
82
+
83
+ * Constraints are cloned so they can't be inadvertently modified while they're
84
+ in effect. Added :readonly finder constraint. Calling an association collection's class method (Part.foobar via item.parts.foobar) constrains :readonly => false since the collection's :joins constraint would otherwise force it to true. [Jeremy Kemper <rails@bitsweat.net>]
85
+
86
+ * Added :offset and :limit to the kinds of options that Base.constrain can use #2466 [duane.johnson@gmail.com]
87
+
88
+ * Fixed handling of nil number columns on Oracle and cleaned up tests for Oracle in general #2555 [schoenm@earthlink.net]
89
+
90
+ * Added quoted_true and quoted_false methods and tables to db2_adapter and cleaned up tests for DB2 #2493, #2624 [maik schmidt]
91
+
92
+
1
93
  *1.12.2* (October 26th, 2005)
2
94
 
3
95
  * Allow symbols to rename columns when using SQLite adapter. #2531 [kevin.clark@gmail.com]
data/README CHANGED
@@ -1,14 +1,14 @@
1
1
  = Active Record -- Object-relation mapping put on rails
2
2
 
3
3
  Active Record connects business objects and database tables to create a persistable
4
- domain model where logic and data is presented in one wrapping. It's an implementation
4
+ domain model where logic and data are presented in one wrapping. It's an implementation
5
5
  of the object-relational mapping (ORM) pattern[http://www.martinfowler.com/eaaCatalog/activeRecord.html]
6
6
  by the same name as described by Martin Fowler:
7
7
 
8
8
  "An object that wraps a row in a database table or view, encapsulates
9
9
  the database access, and adds domain logic on that data."
10
10
 
11
- Active Records main contribution to the pattern is to relieve the original of two stunting problems:
11
+ Active Record's main contribution to the pattern is to relieve the original of two stunting problems:
12
12
  lack of associations and inheritance. By adding a simple domain language-like set of macros to describe
13
13
  the former and integrating the Single Table Inheritance pattern for the latter, Active Record narrows the
14
14
  gap of functionality between the data mapper and active record approach.
@@ -157,7 +157,7 @@ A short rundown of the major features:
157
157
 
158
158
  pkId = 1234
159
159
  cat = Cat.find(pkId)
160
- # something even more interesting involving a the same cat...
160
+ # something even more interesting involving the same cat...
161
161
  cat.save
162
162
 
163
163
  {Learn more}[link:classes/ActiveRecord/Base.html]
@@ -165,7 +165,7 @@ A short rundown of the major features:
165
165
 
166
166
  * Database abstraction through simple adapters (~100 lines) with a shared connector
167
167
 
168
- ActiveRecord::Base.establish_connection(:adapter => "sqlite", :dbfile => "dbfile")
168
+ ActiveRecord::Base.establish_connection(:adapter => "sqlite", :database => "dbfile")
169
169
 
170
170
  ActiveRecord::Base.establish_connection(
171
171
  :adapter => "mysql",
@@ -189,7 +189,7 @@ A short rundown of the major features:
189
189
 
190
190
  Data definitions are specified only in the database. Active Record queries the database for
191
191
  the column names (that then serves to determine which attributes are valid) on regular
192
- objects instantiation through the new constructor and relies on the column names in the rows
192
+ object instantiation through the new constructor and relies on the column names in the rows
193
193
  with the finders.
194
194
 
195
195
  # CREATE TABLE companies (
@@ -235,7 +235,7 @@ Active Record will also automatically link the "Person" object to the "people" t
235
235
 
236
236
  == Simple example (2/2): Using the domain
237
237
 
238
- Picking a database connection for all the active records
238
+ Picking a database connection for all the Active Records
239
239
 
240
240
  ActiveRecord::Base.establish_connection(
241
241
  :adapter => "mysql",
@@ -300,9 +300,9 @@ It's also highly recommended to have a look at the unit tests. Read more in link
300
300
 
301
301
  == Philosophy
302
302
 
303
- Active Record attempts to provide a coherent wrapping for the inconvenience that is
303
+ Active Record attempts to provide a coherent wrapper as a solution for the inconvenience that is
304
304
  object-relational mapping. The prime directive for this mapping has been to minimize
305
- the amount of code needed to built a real-world domain model. This is made possible
305
+ the amount of code needed to build a real-world domain model. This is made possible
306
306
  by relying on a number of conventions that make it easy for Active Record to infer
307
307
  complex relations and structures from a minimal amount of explicit direction.
308
308
 
@@ -357,4 +357,4 @@ RubyForge page at http://rubyforge.org/projects/activerecord. And as Jim from Ra
357
357
  new feature to be submitted in the form of new unit tests.
358
358
 
359
359
  For other information, feel free to ask on the ruby-talk mailing list
360
- (which is mirrored to comp.lang.ruby) or contact mailto:david@loudthinking.com.
360
+ (which is mirrored to comp.lang.ruby) or contact mailto:david@loudthinking.com.
@@ -6,7 +6,7 @@ module ActiveRecord
6
6
  base.extend(ClassMethods)
7
7
  end
8
8
 
9
- # This act provides the capabilities for sorting and reordering a number of objects in list.
9
+ # This act provides the capabilities for sorting and reordering a number of objects in a list.
10
10
  # The class that has this specified needs to have a "position" column defined as an integer on
11
11
  # the mapped database table.
12
12
  #
@@ -7,17 +7,17 @@ module ActiveRecord
7
7
  end
8
8
 
9
9
  # This acts provides Nested Set functionality. Nested Set is similiar to Tree, but with
10
- # the added feature that you can select the children and all of it's descendants with
10
+ # the added feature that you can select the children and all of their descendents with
11
11
  # a single query. A good use case for this is a threaded post system, where you want
12
12
  # to display every reply to a comment without multiple selects.
13
13
  #
14
14
  # A google search for "Nested Set" should point you in the direction to explain the
15
- # data base theory. I figured a bunch of this from
15
+ # database theory. I figured out a bunch of this from
16
16
  # http://threebit.net/tutorials/nestedset/tutorial1.html
17
17
  #
18
- # Instead of picturing a leaf node structure with child pointing back to their parent,
18
+ # Instead of picturing a leaf node structure with children pointing back to their parent,
19
19
  # the best way to imagine how this works is to think of the parent entity surrounding all
20
- # of it's children, and it's parent surrounding it, etc. Assuming that they are lined up
20
+ # of its children, and its parent surrounding it, etc. Assuming that they are lined up
21
21
  # horizontally, we store the left and right boundries in the database.
22
22
  #
23
23
  # Imagine:
@@ -42,7 +42,7 @@ module ActiveRecord
42
42
  # | |___________________________| |___________________________| |
43
43
  # |___________________________________________________________________|
44
44
  #
45
- # The numbers represent the left and right boundries. The table them might
45
+ # The numbers represent the left and right boundries. The table then might
46
46
  # look like this:
47
47
  # ID | PARENT | LEFT | RIGHT | DATA
48
48
  # 1 | 0 | 1 | 14 | root
@@ -63,10 +63,10 @@ module ActiveRecord
63
63
  # There are instance methods for all of these.
64
64
  #
65
65
  # The structure is good if you need to group things together; the downside is that
66
- # keeping data integrity is a pain, and both adding and removing and entry
66
+ # keeping data integrity is a pain, and both adding and removing an entry
67
67
  # require a full table write.
68
68
  #
69
- # This sets up a before_destroy trigger to prune the tree correctly if one of it's
69
+ # This sets up a before_destroy trigger to prune the tree correctly if one of its
70
70
  # elements gets deleted.
71
71
  #
72
72
  module ClassMethods
@@ -134,10 +134,10 @@ module ActiveRecord
134
134
  end
135
135
 
136
136
 
137
- # Added a child to this object in the tree. If this object hasn't been initialized,
137
+ # Adds a child to this object in the tree. If this object hasn't been initialized,
138
138
  # it gets set up as a root node. Otherwise, this method will update all of the
139
- # other elements in the tree and shift them to the right. Keeping everything
140
- # balanaced.
139
+ # other elements in the tree and shift them to the right, keeping everything
140
+ # balanced.
141
141
  def add_child( child )
142
142
  self.reload
143
143
  child.reload
@@ -179,17 +179,17 @@ module ActiveRecord
179
179
  return (self[right_col_name] - self[left_col_name] - 1)/2
180
180
  end
181
181
 
182
- # Returns a set of itself and all of it's nested children
182
+ # Returns a set of itself and all of its nested children
183
183
  def full_set
184
184
  self.class.find(:all, :conditions => "#{scope_condition} AND (#{left_col_name} BETWEEN #{self[left_col_name]} and #{self[right_col_name]})" )
185
185
  end
186
186
 
187
- # Returns a set of all of it's children and nested children
187
+ # Returns a set of all of its children and nested children
188
188
  def all_children
189
189
  self.class.find(:all, :conditions => "#{scope_condition} AND (#{left_col_name} > #{self[left_col_name]}) and (#{right_col_name} < #{self[right_col_name]})" )
190
190
  end
191
191
 
192
- # Returns a set of only this entries immediate children
192
+ # Returns a set of only this entry's immediate children
193
193
  def direct_children
194
194
  self.class.find(:all, :conditions => "#{scope_condition} and #{parent_column} = #{self.id}")
195
195
  end
@@ -6,8 +6,8 @@ module ActiveRecord
6
6
  base.extend(ClassMethods)
7
7
  end
8
8
 
9
- # Specify this act if you want to model a tree structure by providing a parent association and an children
10
- # association. This act assumes that requires that you have a foreign key column, which by default is called parent_id.
9
+ # Specify this act if you want to model a tree structure by providing a parent association and a children
10
+ # association. This act requires that you have a foreign key column, which by default is called parent_id.
11
11
  #
12
12
  # class Category < ActiveRecord::Base
13
13
  # acts_as_tree :order => "name"
@@ -30,13 +30,14 @@ module ActiveRecord
30
30
  #
31
31
  # In addition to the parent and children associations, the following instance methods are added to the class
32
32
  # after specifying the act:
33
- # * siblings: Return all the children of the parent excluding the current node ([ subchild2 ] when called from subchild1)
34
- # * ancestors: Returns all the ancestors of the current node ([child1, root] when called from subchild2)
35
- # * root: Returns the root of the current node (root when called from subchild2)
33
+ # * siblings : Returns all the children of the parent, excluding the current node ([ subchild2 ] when called from subchild1)
34
+ # * self_and_siblings : Returns all the children of the parent, including the current node ([ subchild1, subchild2 ] when called from subchild1)
35
+ # * ancestors : Returns all the ancestors of the current node ([child1, root] when called from subchild2)
36
+ # * root : Returns the root of the current node (root when called from subchild2)
36
37
  module ClassMethods
37
38
  # Configuration options are:
38
39
  #
39
- # * <tt>foreign_key</tt> - specifies the column name to use for track of the tree (default: parent_id)
40
+ # * <tt>foreign_key</tt> - specifies the column name to use for tracking of the tree (default: parent_id)
40
41
  # * <tt>order</tt> - makes it possible to sort the children according to this SQL snippet.
41
42
  # * <tt>counter_cache</tt> - keeps a count in a children_count column if set to true (default: false).
42
43
  def acts_as_tree(options = {})
@@ -7,8 +7,8 @@ module ActiveRecord
7
7
 
8
8
  # Active Record implements aggregation through a macro-like class method called +composed_of+ for representing attributes
9
9
  # as value objects. It expresses relationships like "Account [is] composed of Money [among other things]" or "Person [is]
10
- # composed of [an] address". Each call to the macro adds a description on how the value objects are created from the
11
- # attributes of the entity object (when the entity is initialized either as a new object or from finding an existing)
10
+ # composed of [an] address". Each call to the macro adds a description of how the value objects are created from the
11
+ # attributes of the entity object (when the entity is initialized either as a new object or from finding an existing object)
12
12
  # and how it can be turned back into attributes (when the entity is saved to the database). Example:
13
13
  #
14
14
  # class Customer < ActiveRecord::Base
@@ -88,8 +88,8 @@ module ActiveRecord
88
88
  # == Writing value objects
89
89
  #
90
90
  # Value objects are immutable and interchangeable objects that represent a given value, such as a Money object representing
91
- # $5. Two Money objects both representing $5 should be equal (through methods such == and <=> from Comparable if ranking makes
92
- # sense). This is unlike a entity objects where equality is determined by identity. An entity class such as Customer can
91
+ # $5. Two Money objects both representing $5 should be equal (through methods such as == and <=> from Comparable if ranking
92
+ # makes sense). This is unlike entity objects where equality is determined by identity. An entity class such as Customer can
93
93
  # easily have two different objects that both have an address on Hyancintvej. Entity identity is determined by object or
94
94
  # relational unique identifiers (such as primary keys). Normal ActiveRecord::Base classes are entity objects.
95
95
  #
@@ -22,7 +22,7 @@ module ActiveRecord
22
22
 
23
23
  # Associations are a set of macro-like class methods for tying objects together through foreign keys. They express relationships like
24
24
  # "Project has one Project Manager" or "Project belongs to a Portfolio". Each macro adds a number of methods to the class which are
25
- # specialized according to the collection or association symbol and the options hash. It works much the same was as Ruby's own attr*
25
+ # specialized according to the collection or association symbol and the options hash. It works much the same way as Ruby's own attr*
26
26
  # methods. Example:
27
27
  #
28
28
  # class Project < ActiveRecord::Base
@@ -80,7 +80,7 @@ module ActiveRecord
80
80
  #
81
81
  # === One-to-one associations
82
82
  #
83
- # * Assigning an object to a has_one association automatically saves that object, and the object being replaced (if there is one), in
83
+ # * Assigning an object to a has_one association automatically saves that object and the object being replaced (if there is one), in
84
84
  # order to update their primary keys - except if the parent object is unsaved (new_record? == true).
85
85
  # * If either of these saves fail (due to one of the objects being invalid) the assignment statement returns false and the assignment
86
86
  # is cancelled.
@@ -120,6 +120,46 @@ module ActiveRecord
120
120
  # Should any of the before_add callbacks throw an exception, the object does not get added to the collection. Same with
121
121
  # the before_remove callbacks, if an exception is thrown the object doesn't get removed.
122
122
  #
123
+ # === Association extensions
124
+ #
125
+ # The proxy objects that controls the access to associations can be extended through anonymous modules. This is especially
126
+ # beneficial for adding new finders, creators, and other factory-type methods that are only used as part of this associatio.
127
+ # Example:
128
+ #
129
+ # class Account < ActiveRecord::Base
130
+ # has_many :people do
131
+ # def find_or_create_by_name(name)
132
+ # first_name, *last_name = name.split
133
+ # last_name = last_name.join " "
134
+ #
135
+ # find_or_create_by_first_name_and_last_name(first_name, last_name)
136
+ # end
137
+ # end
138
+ # end
139
+ #
140
+ # person = Account.find(:first).people.find_or_create_by_name("David Heinemeier Hansson")
141
+ # person.first_name # => "David"
142
+ # person.last_name # => "Heinemeier Hansson"
143
+ #
144
+ # If you need to share the same extensions between many associations, you can use a named extension module. Example:
145
+ #
146
+ # module FindOrCreateByNameExtension
147
+ # def find_or_create_by_name(name)
148
+ # first_name, *last_name = name.split
149
+ # last_name = last_name.join " "
150
+ #
151
+ # find_or_create_by_first_name_and_last_name(first_name, last_name)
152
+ # end
153
+ # end
154
+ #
155
+ # class Account < ActiveRecord::Base
156
+ # has_many :people, :extend => FindOrCreateByNameExtension
157
+ # end
158
+ #
159
+ # class Company < ActiveRecord::Base
160
+ # has_many :people, :extend => FindOrCreateByNameExtension
161
+ # end
162
+ #
123
163
  # == Caching
124
164
  #
125
165
  # All of the methods are built on a simple caching principle that will keep the result of the last query around unless specifically
@@ -164,8 +204,8 @@ module ActiveRecord
164
204
  #
165
205
  # That'll add another join along the lines of: LEFT OUTER JOIN comments ON comments.post_id = posts.id. And we'll be down to 1 query.
166
206
  # But that shouldn't fool you to think that you can pull out huge amounts of data with no performance penalty just because you've reduced
167
- # the number of queries. The database still needs to send all the data to Active Record and it still needs to be processed. So its no
168
- # catch-all for performance problems, but its a great way to cut down on the number of queries in a situation as the one described above.
207
+ # the number of queries. The database still needs to send all the data to Active Record and it still needs to be processed. So it's no
208
+ # catch-all for performance problems, but it's a great way to cut down on the number of queries in a situation as the one described above.
169
209
  #
170
210
  # Please note that limited eager loading with has_many and has_and_belongs_to_many associations is not compatible with describing conditions
171
211
  # on these eager tables. This will work:
@@ -240,10 +280,10 @@ module ActiveRecord
240
280
  # * <tt>collection.find</tt> - finds an associated object according to the same rules as Base.find.
241
281
  # * <tt>collection.build(attributes = {})</tt> - returns a new object of the collection type that has been instantiated
242
282
  # with +attributes+ and linked to this object through a foreign key but has not yet been saved. *Note:* This only works if an
243
- # associated object already exists, not if its nil!
283
+ # associated object already exists, not if it's nil!
244
284
  # * <tt>collection.create(attributes = {})</tt> - returns a new object of the collection type that has been instantiated
245
285
  # with +attributes+ and linked to this object through a foreign key and that has already been saved (if it passed the validation).
246
- # *Note:* This only works if an associated object already exists, not if its nil!
286
+ # *Note:* This only works if an associated object already exists, not if it's nil!
247
287
  #
248
288
  # Example: A Firm class declares <tt>has_many :clients</tt>, which will add:
249
289
  # * <tt>Firm#clients</tt> (similar to <tt>Clients.find :all, :conditions => "firm_id = #{id}"</tt>)
@@ -271,7 +311,7 @@ module ActiveRecord
271
311
  # of this class in lower-case and "_id" suffixed. So a +Person+ class that makes a has_many association will use "person_id"
272
312
  # as the default foreign_key.
273
313
  # * <tt>:dependent</tt> - if set to :destroy (or true) all the associated objects are destroyed
274
- # alongside this object. Also accepts :nullify which will set the associated objects foriegn key
314
+ # alongside this object. Also accepts :nullify which will set the associated object's foreign key
275
315
  # field to NULL.
276
316
  # May not be set if :exclusively_dependent is also set.
277
317
  # * <tt>:exclusively_dependent</tt> - if set to true all the associated object are deleted in one SQL statement without having their
@@ -279,12 +319,15 @@ module ActiveRecord
279
319
  # clean-up in before_destroy. The upside is that it's much faster, especially if there's a counter_cache involved.
280
320
  # May not be set if :dependent is also set.
281
321
  # * <tt>:finder_sql</tt> - specify a complete SQL statement to fetch the association. This is a good way to go for complex
282
- # associations that depends on multiple tables. Note: When this option is used, +find_in_collection+ is _not_ added.
322
+ # associations that depend on multiple tables. Note: When this option is used, +find_in_collection+ is _not_ added.
283
323
  # * <tt>:counter_sql</tt> - specify a complete SQL statement to fetch the size of the association. If +:finder_sql+ is
284
324
  # specified but +:counter_sql+, +:counter_sql+ will be generated by replacing SELECT ... FROM with SELECT COUNT(*) FROM.
325
+ # * <tt>:extend</tt> - specify a named module for extending the proxy, see "Association extensions".
326
+ # * <tt>:include</tt> - specify second-order associations that should be eager loaded when the collection is loaded.
285
327
  #
286
328
  # Option examples:
287
329
  # has_many :comments, :order => "posted_on"
330
+ # has_many :comments, :include => :author
288
331
  # has_many :people, :class_name => "Person", :conditions => "deleted = 0", :order => "name"
289
332
  # has_many :tracks, :order => "position", :dependent => true
290
333
  # has_many :subscribers, :class_name => "Person", :finder_sql =>
@@ -292,13 +335,15 @@ module ActiveRecord
292
335
  # 'FROM people p, post_subscriptions ps ' +
293
336
  # 'WHERE ps.post_id = #{id} AND ps.person_id = p.id ' +
294
337
  # 'ORDER BY p.first_name'
295
- def has_many(association_id, options = {})
338
+ def has_many(association_id, options = {}, &extension)
296
339
  options.assert_valid_keys(
297
340
  :foreign_key, :class_name, :exclusively_dependent, :dependent,
298
- :conditions, :order, :finder_sql, :counter_sql,
299
- :before_add, :after_add, :before_remove, :after_remove
341
+ :conditions, :order, :include, :finder_sql, :counter_sql,
342
+ :before_add, :after_add, :before_remove, :after_remove, :extend
300
343
  )
301
344
 
345
+ options[:extend] = create_extension_module(association_id, extension) if block_given?
346
+
302
347
  association_name, association_class_name, association_class_primary_key_name =
303
348
  associate_identification(association_id, options[:class_name], options[:foreign_key])
304
349
 
@@ -369,18 +414,19 @@ module ActiveRecord
369
414
  # sql fragment, such as "rank = 5".
370
415
  # * <tt>:order</tt> - specify the order from which the associated object will be picked at the top. Specified as
371
416
  # an "ORDER BY" sql fragment, such as "last_name, first_name DESC"
372
- # * <tt>:dependent</tt> - if set to :destroy (or true) all the associated object is destroyed when this object is. Also
417
+ # * <tt>:dependent</tt> - if set to :destroy (or true) all the associated objects are destroyed when this object is. Also,
373
418
  # association is assigned.
374
419
  # * <tt>:foreign_key</tt> - specify the foreign key used for the association. By default this is guessed to be the name
375
420
  # of this class in lower-case and "_id" suffixed. So a +Person+ class that makes a has_one association will use "person_id"
376
421
  # as the default foreign_key.
422
+ # * <tt>:include</tt> - specify second-order associations that should be eager loaded when this object is loaded.
377
423
  #
378
424
  # Option examples:
379
425
  # has_one :credit_card, :dependent => true
380
426
  # has_one :last_comment, :class_name => "Comment", :order => "posted_on"
381
427
  # has_one :project_manager, :class_name => "Person", :conditions => "role = 'project_manager'"
382
428
  def has_one(association_id, options = {})
383
- options.assert_valid_keys(:class_name, :foreign_key, :remote, :conditions, :order, :dependent, :counter_cache)
429
+ options.assert_valid_keys(:class_name, :foreign_key, :remote, :conditions, :order, :include, :dependent, :counter_cache, :extend)
384
430
 
385
431
  association_name, association_class_name, association_class_primary_key_name =
386
432
  associate_identification(association_id, options[:class_name], options[:foreign_key], false)
@@ -453,6 +499,7 @@ module ActiveRecord
453
499
  # and decrement_counter. The counter cache is incremented when an object of this class is created and decremented when it's
454
500
  # destroyed. This requires that a column named "#{table_name}_count" (such as comments_count for a belonging Comment class)
455
501
  # is used on the associate class (such as a Post class).
502
+ # * <tt>:include</tt> - specify second-order associations that should be eager loaded when this object is loaded.
456
503
  #
457
504
  # Option examples:
458
505
  # belongs_to :firm, :foreign_key => "client_of"
@@ -460,7 +507,7 @@ module ActiveRecord
460
507
  # belongs_to :valid_coupon, :class_name => "Coupon", :foreign_key => "coupon_id",
461
508
  # :conditions => 'discounts > #{payments_count}'
462
509
  def belongs_to(association_id, options = {})
463
- options.assert_valid_keys(:class_name, :foreign_key, :remote, :conditions, :order, :dependent, :counter_cache)
510
+ options.assert_valid_keys(:class_name, :foreign_key, :remote, :conditions, :order, :include, :dependent, :counter_cache, :extend)
464
511
 
465
512
  association_name, association_class_name, class_primary_key_name =
466
513
  associate_identification(association_id, options[:class_name], options[:foreign_key], false)
@@ -558,8 +605,8 @@ module ActiveRecord
558
605
  # of this class in lower-case and "_id" suffixed. So a +Person+ class that makes a has_and_belongs_to_many association
559
606
  # will use "person_id" as the default foreign_key.
560
607
  # * <tt>:association_foreign_key</tt> - specify the association foreign key used for the association. By default this is
561
- # guessed to be the name of the associated class in lower-case and "_id" suffixed. So the associated class is +Project+
562
- # that makes a has_and_belongs_to_many association will use "project_id" as the default association foreign_key.
608
+ # guessed to be the name of the associated class in lower-case and "_id" suffixed. So if the associated class is +Project+,
609
+ # the has_and_belongs_to_many association will use "project_id" as the default association foreign_key.
563
610
  # * <tt>:conditions</tt> - specify the conditions that the associated object must meet in order to be included as a "WHERE"
564
611
  # sql fragment, such as "authorized = 1".
565
612
  # * <tt>:order</tt> - specify the order in which the associated objects are returned as a "ORDER BY" sql fragment, such as "last_name, first_name DESC"
@@ -569,20 +616,25 @@ module ActiveRecord
569
616
  # classes with a manual one
570
617
  # * <tt>:insert_sql</tt> - overwrite the default generated SQL used to add links between the associated classes
571
618
  # with a manual one
619
+ # * <tt>:extend</tt> - anonymous module for extending the proxy, see "Association extensions".
620
+ # * <tt>:include</tt> - specify second-order associations that should be eager loaded when the collection is loaded.
572
621
  #
573
622
  # Option examples:
574
623
  # has_and_belongs_to_many :projects
624
+ # has_and_belongs_to_many :projects, :include => [ :milestones, :manager ]
575
625
  # has_and_belongs_to_many :nations, :class_name => "Country"
576
626
  # has_and_belongs_to_many :categories, :join_table => "prods_cats"
577
627
  # has_and_belongs_to_many :active_projects, :join_table => 'developers_projects', :delete_sql =>
578
628
  # 'DELETE FROM developers_projects WHERE active=1 AND developer_id = #{id} AND project_id = #{record.id}'
579
- def has_and_belongs_to_many(association_id, options = {})
629
+ def has_and_belongs_to_many(association_id, options = {}, &extension)
580
630
  options.assert_valid_keys(
581
- :class_name, :table_name, :foreign_key, :association_foreign_key, :conditions,
631
+ :class_name, :table_name, :foreign_key, :association_foreign_key, :conditions, :include,
582
632
  :join_table, :finder_sql, :delete_sql, :insert_sql, :order, :uniq, :before_add, :after_add,
583
- :before_remove, :after_remove
633
+ :before_remove, :after_remove, :extend
584
634
  )
585
635
 
636
+ options[:extend] = create_extension_module(association_id, extension) if block_given?
637
+
586
638
  association_name, association_class_name, association_class_primary_key_name =
587
639
  associate_identification(association_id, options[:class_name], options[:foreign_key])
588
640
 
@@ -893,7 +945,7 @@ module ActiveRecord
893
945
  connection.select_values(
894
946
  construct_finder_sql_for_association_limiting(options),
895
947
  "#{name} Load IDs For Limited Eager Loading"
896
- ).collect { |id| "'#{id}'" }.join(", ")
948
+ ).collect { |id| connection.quote(id) }.join(", ")
897
949
  end
898
950
 
899
951
  def construct_finder_sql_for_association_limiting(options)
@@ -978,7 +1030,16 @@ module ActiveRecord
978
1030
  def condition_word(sql)
979
1031
  sql =~ /where/i ? " AND " : "WHERE "
980
1032
  end
981
- end
982
1033
 
1034
+ def create_extension_module(association_id, extension)
1035
+ extension_module_name = "#{self.to_s}#{association_id.to_s.camelize}AssociationExtension"
1036
+
1037
+ silence_warnings do
1038
+ Object.const_set(extension_module_name, Module.new(&extension))
1039
+ end
1040
+
1041
+ extension_module_name.constantize
1042
+ end
1043
+ end
983
1044
  end
984
1045
  end