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.
- data/CHANGELOG +92 -0
- data/README +9 -9
- data/lib/active_record/acts/list.rb +1 -1
- data/lib/active_record/acts/nested_set.rb +13 -13
- data/lib/active_record/acts/tree.rb +7 -6
- data/lib/active_record/aggregations.rb +4 -4
- data/lib/active_record/associations.rb +82 -21
- data/lib/active_record/associations/association_collection.rb +0 -8
- data/lib/active_record/associations/association_proxy.rb +5 -2
- data/lib/active_record/associations/belongs_to_association.rb +6 -2
- data/lib/active_record/associations/has_and_belongs_to_many_association.rb +11 -1
- data/lib/active_record/associations/has_many_association.rb +34 -5
- data/lib/active_record/associations/has_one_association.rb +1 -1
- data/lib/active_record/base.rb +144 -59
- data/lib/active_record/callbacks.rb +6 -6
- data/lib/active_record/connection_adapters/abstract/connection_specification.rb +3 -2
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +4 -4
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +10 -8
- data/lib/active_record/connection_adapters/abstract_adapter.rb +1 -1
- data/lib/active_record/connection_adapters/db2_adapter.rb +17 -1
- data/lib/active_record/connection_adapters/oci_adapter.rb +322 -185
- data/lib/active_record/connection_adapters/sqlite_adapter.rb +9 -8
- data/lib/active_record/connection_adapters/sqlserver_adapter.rb +107 -14
- data/lib/active_record/fixtures.rb +10 -8
- data/lib/active_record/migration.rb +20 -6
- data/lib/active_record/observer.rb +4 -3
- data/lib/active_record/reflection.rb +5 -5
- data/lib/active_record/transactions.rb +2 -2
- data/lib/active_record/validations.rb +70 -40
- data/lib/active_record/vendor/mysql411.rb +9 -13
- data/lib/active_record/version.rb +2 -2
- data/rakefile +1 -1
- data/test/abstract_unit.rb +5 -0
- data/test/ar_schema_test.rb +1 -1
- data/test/associations_extensions_test.rb +37 -0
- data/test/associations_go_eager_test.rb +25 -0
- data/test/associations_test.rb +14 -6
- data/test/base_test.rb +63 -45
- data/test/connections/native_sqlite/connection.rb +2 -2
- data/test/connections/native_sqlite3/connection.rb +2 -2
- data/test/connections/native_sqlite3/in_memory_connection.rb +1 -1
- data/test/debug.log +2857 -0
- data/test/deprecated_finder_test.rb +3 -9
- data/test/finder_test.rb +27 -13
- data/test/fixtures/author.rb +4 -0
- data/test/fixtures/comment.rb +4 -8
- data/test/fixtures/db_definitions/create_oracle_db.bat +0 -5
- data/test/fixtures/db_definitions/create_oracle_db.sh +0 -5
- data/test/fixtures/db_definitions/db2.drop.sql +0 -1
- data/test/fixtures/db_definitions/oci.sql +2 -0
- data/test/fixtures/db_definitions/sqlserver.drop.sql +1 -1
- data/test/fixtures/developer.rb +18 -1
- data/test/fixtures/fixture_database.sqlite +0 -0
- data/test/fixtures/fixture_database_2.sqlite +0 -0
- data/test/fixtures/migrations_with_duplicate/1_people_have_last_names.rb +9 -0
- data/test/fixtures/migrations_with_duplicate/2_we_need_reminders.rb +12 -0
- data/test/fixtures/migrations_with_duplicate/3_foo.rb +7 -0
- data/test/fixtures/migrations_with_duplicate/3_innocent_jointable.rb +12 -0
- data/test/fixtures/post.rb +18 -4
- data/test/fixtures/reply.rb +3 -1
- data/test/inheritance_test.rb +3 -2
- data/test/{conditions_scoping_test.rb → method_scoping_test.rb} +55 -10
- data/test/migration_test.rb +68 -31
- data/test/readonly_test.rb +65 -5
- data/test/validations_test.rb +10 -2
- 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
|
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
|
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
|
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", :
|
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
|
-
|
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
|
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
|
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
|
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
|
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
|
-
#
|
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
|
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
|
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
|
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
|
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
|
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
|
-
#
|
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
|
140
|
-
#
|
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
|
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
|
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
|
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
|
10
|
-
# association. This act
|
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:
|
34
|
-
# *
|
35
|
-
# *
|
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
|
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
|
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
|
92
|
-
# sense). This is unlike
|
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
|
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
|
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
|
168
|
-
# catch-all for performance problems, but
|
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
|
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
|
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
|
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
|
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
|
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
|
-
#
|
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|
|
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
|