activerecord 1.4.0 → 1.5.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 +98 -0
- data/install.rb +1 -0
- data/lib/active_record.rb +1 -0
- data/lib/active_record/acts/list.rb +19 -16
- data/lib/active_record/associations.rb +164 -164
- data/lib/active_record/associations/association_collection.rb +44 -71
- data/lib/active_record/associations/association_proxy.rb +76 -0
- data/lib/active_record/associations/belongs_to_association.rb +74 -0
- data/lib/active_record/associations/has_and_belongs_to_many_association.rb +34 -21
- data/lib/active_record/associations/has_many_association.rb +34 -30
- data/lib/active_record/associations/has_one_association.rb +48 -0
- data/lib/active_record/base.rb +62 -18
- data/lib/active_record/callbacks.rb +17 -8
- data/lib/active_record/connection_adapters/abstract_adapter.rb +11 -10
- data/lib/active_record/connection_adapters/mysql_adapter.rb +1 -0
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +29 -1
- data/lib/active_record/connection_adapters/sqlite_adapter.rb +94 -73
- data/lib/active_record/deprecated_associations.rb +46 -8
- data/lib/active_record/fixtures.rb +1 -1
- data/lib/active_record/observer.rb +5 -1
- data/lib/active_record/support/binding_of_caller.rb +72 -68
- data/lib/active_record/support/breakpoint.rb +526 -524
- data/lib/active_record/support/class_inheritable_attributes.rb +105 -29
- data/lib/active_record/support/core_ext.rb +1 -0
- data/lib/active_record/support/core_ext/hash.rb +5 -0
- data/lib/active_record/support/core_ext/hash/keys.rb +35 -0
- data/lib/active_record/support/core_ext/numeric.rb +7 -0
- data/lib/active_record/support/core_ext/numeric/bytes.rb +33 -0
- data/lib/active_record/support/core_ext/numeric/time.rb +59 -0
- data/lib/active_record/support/core_ext/string.rb +5 -0
- data/lib/active_record/support/core_ext/string/inflections.rb +41 -0
- data/lib/active_record/support/dependencies.rb +1 -14
- data/lib/active_record/support/inflector.rb +6 -6
- data/lib/active_record/support/misc.rb +0 -24
- data/lib/active_record/validations.rb +34 -1
- data/lib/active_record/vendor/mysql411.rb +305 -0
- data/rakefile +11 -2
- data/test/abstract_unit.rb +1 -2
- data/test/associations_test.rb +234 -23
- data/test/base_test.rb +50 -1
- data/test/callbacks_test.rb +16 -0
- data/test/connections/native_mysql/connection.rb +2 -2
- data/test/connections/native_sqlite3/connection.rb +34 -0
- data/test/deprecated_associations_test.rb +36 -2
- data/test/fixtures/company.rb +2 -0
- data/test/fixtures/computer.rb +3 -0
- data/test/fixtures/computers.yml +3 -0
- data/test/fixtures/db_definitions/db2.sql +5 -0
- data/test/fixtures/db_definitions/mysql.sql +5 -0
- data/test/fixtures/db_definitions/postgresql.sql +5 -0
- data/test/fixtures/db_definitions/sqlite.sql +5 -0
- data/test/fixtures/db_definitions/sqlserver.sql +5 -1
- data/test/fixtures/fixture_database.sqlite +0 -0
- data/test/validations_test.rb +21 -0
- metadata +22 -2
data/CHANGELOG
CHANGED
@@ -1,3 +1,101 @@
|
|
1
|
+
*1.5.0* (January 17th, 2005)
|
2
|
+
|
3
|
+
* Fixed that unit tests for MySQL are now run as the "rails" user instead of root #455 [Eric Hodel]
|
4
|
+
|
5
|
+
* Added validates_associated that enables validation of objects in an unsaved association #398 [Tim Bates]. Example:
|
6
|
+
|
7
|
+
class Book < ActiveRecord::Base
|
8
|
+
has_many :pages
|
9
|
+
belongs_to :library
|
10
|
+
|
11
|
+
validates_associated :pages, :library
|
12
|
+
end
|
13
|
+
|
14
|
+
* Added support for associating unsaved objects #402 [Tim Bates]. Rules that govern this addition:
|
15
|
+
|
16
|
+
== Unsaved objects and associations
|
17
|
+
|
18
|
+
You can manipulate objects and associations before they are saved to the database, but there is some special behaviour you should be
|
19
|
+
aware of, mostly involving the saving of associated objects.
|
20
|
+
|
21
|
+
=== One-to-one associations
|
22
|
+
|
23
|
+
* Assigning an object to a has_one association automatically saves that object, and the object being replaced (if there is one), in
|
24
|
+
order to update their primary keys - except if the parent object is unsaved (new_record? == true).
|
25
|
+
* If either of these saves fail (due to one of the objects being invalid) the assignment statement returns false and the assignment
|
26
|
+
is cancelled.
|
27
|
+
* If you wish to assign an object to a has_one association without saving it, use the #association.build method (documented below).
|
28
|
+
* Assigning an object to a belongs_to association does not save the object, since the foreign key field belongs on the parent. It does
|
29
|
+
not save the parent either.
|
30
|
+
|
31
|
+
=== Collections
|
32
|
+
|
33
|
+
* Adding an object to a collection (has_many or has_and_belongs_to_many) automatically saves that object, except if the parent object
|
34
|
+
(the owner of the collection) is not yet stored in the database.
|
35
|
+
* If saving any of the objects being added to a collection (via #push or similar) fails, then #push returns false.
|
36
|
+
* You can add an object to a collection without automatically saving it by using the #collection.build method (documented below).
|
37
|
+
* All unsaved (new_record? == true) members of the collection are automatically saved when the parent is saved.
|
38
|
+
|
39
|
+
* Added replace to associations, so you can do project.manager.replace(new_manager) or project.milestones.replace(new_milestones) #402 [Tim Bates]
|
40
|
+
|
41
|
+
* Added build and create methods to has_one and belongs_to associations, so you can now do project.manager.build(attributes) #402 [Tim Bates]
|
42
|
+
|
43
|
+
* Added that if a before_* callback returns false, all the later callbacks and the associated action are cancelled. If an after_* callback returns false, all the later callbacks are cancelled. Callbacks are generally run in the order they are defined, with the exception of callbacks defined as methods on the model, which are called last. #402 [Tim Bates]
|
44
|
+
|
45
|
+
* Fixed that Base#== wouldn't work for multiple references to the same unsaved object #402 [Tim Bates]
|
46
|
+
|
47
|
+
* Fixed binary support for PostgreSQL #444 [alex@byzantine.no]
|
48
|
+
|
49
|
+
* Added a differenciation between AssociationCollection#size and -length. Now AssociationCollection#size returns the size of the
|
50
|
+
collection by executing a SELECT COUNT(*) query if the collection hasn't been loaded and calling collection.size if it has. If
|
51
|
+
it's more likely than not that the collection does have a size larger than zero and you need to fetch that collection afterwards,
|
52
|
+
it'll take one less SELECT query if you use length.
|
53
|
+
|
54
|
+
* Added Base#attributes that returns a hash of all the attributes with their names as keys and clones of their objects as values #433 [atyp.de]
|
55
|
+
|
56
|
+
* Fixed that foreign keys named the same as the association would cause stack overflow #437 [Eric Anderson]
|
57
|
+
|
58
|
+
* Fixed default scope of acts_as_list from "1" to "1 = 1", so it'll work in PostgreSQL (among other places) #427 [Alexey]
|
59
|
+
|
60
|
+
* Added Base#reload that reloads the attributes of an object from the database #422 [Andreas Schwarz]
|
61
|
+
|
62
|
+
* Added SQLite3 compatibility through the sqlite3-ruby adapter by Jamis Buck #381 [bitsweat]
|
63
|
+
|
64
|
+
* Added support for the new protocol spoken by MySQL 4.1.1+ servers for the Ruby/MySQL adapter that ships with Rails #440 [Matt Mower]
|
65
|
+
|
66
|
+
* Added that Observers can use the observes class method instead of overwriting self.observed_class().
|
67
|
+
|
68
|
+
Before:
|
69
|
+
class ListSweeper < ActiveRecord::Base
|
70
|
+
def self.observed_class() [ List, Item ]
|
71
|
+
end
|
72
|
+
|
73
|
+
After:
|
74
|
+
class ListSweeper < ActiveRecord::Base
|
75
|
+
observes List, Item
|
76
|
+
end
|
77
|
+
|
78
|
+
* Fixed that conditions in has_many and has_and_belongs_to_many should be interpolated just like the finder_sql is
|
79
|
+
|
80
|
+
* Fixed Base#update_attribute to be indifferent to whether a string or symbol is used to describe the name
|
81
|
+
|
82
|
+
* Added Base#toggle(attribute) and Base#toggle!(attribute) that makes it easier to flip a switch or flag.
|
83
|
+
|
84
|
+
Before: topic.update_attribute(:approved, !approved?)
|
85
|
+
After : topic.toggle!(:approved)
|
86
|
+
|
87
|
+
* Added Base#increment!(attribute) and Base#decrement!(attribute) that also saves the records. Example:
|
88
|
+
|
89
|
+
page.views # => 1
|
90
|
+
page.increment!(:views) # executes an UPDATE statement
|
91
|
+
page.views # => 2
|
92
|
+
|
93
|
+
page.increment(:views).increment!(:views)
|
94
|
+
page.views # => 4
|
95
|
+
|
96
|
+
* Added Base#increment(attribute) and Base#decrement(attribute) that encapsulates the += 1 and -= 1 patterns.
|
97
|
+
|
98
|
+
|
1
99
|
*1.4.0* (January 4th, 2005)
|
2
100
|
|
3
101
|
* Added automated optimistic locking if the field <tt>lock_version</tt> is present. Each update to the
|
data/install.rb
CHANGED
data/lib/active_record.rb
CHANGED
@@ -27,11 +27,12 @@ module ActiveRecord
|
|
27
27
|
# Configuration options are:
|
28
28
|
#
|
29
29
|
# * +column+ - specifies the column name to use for keeping the position integer (default: position)
|
30
|
-
# * +scope+ - restricts what is to be considered a list. Given a symbol, it'll attach "_id"
|
31
|
-
# as the foreign key restriction. It's also possible
|
32
|
-
#
|
30
|
+
# * +scope+ - restricts what is to be considered a list. Given a symbol, it'll attach "_id"
|
31
|
+
# (if that hasn't been already) and use that as the foreign key restriction. It's also possible
|
32
|
+
# to give it an entire string that is interpolated if you need a tighter scope than just a foreign key.
|
33
|
+
# Example: <tt>acts_as_list :scope => 'todo_list_id = #{todo_list_id} AND completed = 0'</tt>
|
33
34
|
def acts_as_list(options = {})
|
34
|
-
configuration = { :column => "position", :scope => "1" }
|
35
|
+
configuration = { :column => "position", :scope => "1 = 1" }
|
35
36
|
configuration.update(options) if options.is_a?(Hash)
|
36
37
|
|
37
38
|
configuration[:scope] = "#{configuration[:scope]}_id".intern if configuration[:scope].is_a?(Symbol) && configuration[:scope].to_s !~ /_id$/
|
@@ -65,7 +66,10 @@ module ActiveRecord
|
|
65
66
|
end
|
66
67
|
end
|
67
68
|
|
68
|
-
# All the methods available to a record that has had <tt>acts_as_list</tt> specified.
|
69
|
+
# All the methods available to a record that has had <tt>acts_as_list</tt> specified. Each method works
|
70
|
+
# by assuming the object to be the item in the list, so <tt>chapter.move_lower</tt> would move that chapter
|
71
|
+
# lower in the list of all chapters. Likewise, <tt>chapter.first?</tt> would return true if that chapter is
|
72
|
+
# the first in the list of all chapters.
|
69
73
|
module InstanceMethods
|
70
74
|
def move_lower
|
71
75
|
return unless lower_item
|
@@ -75,7 +79,7 @@ module ActiveRecord
|
|
75
79
|
increment_position
|
76
80
|
end
|
77
81
|
end
|
78
|
-
|
82
|
+
|
79
83
|
def move_higher
|
80
84
|
return unless higher_item
|
81
85
|
|
@@ -84,14 +88,14 @@ module ActiveRecord
|
|
84
88
|
decrement_position
|
85
89
|
end
|
86
90
|
end
|
87
|
-
|
91
|
+
|
88
92
|
def move_to_bottom
|
89
93
|
self.class.transaction do
|
90
94
|
decrement_positions_on_lower_items
|
91
95
|
assume_bottom_position
|
92
96
|
end
|
93
97
|
end
|
94
|
-
|
98
|
+
|
95
99
|
def move_to_top
|
96
100
|
self.class.transaction do
|
97
101
|
increment_positions_on_higher_items
|
@@ -99,7 +103,6 @@ module ActiveRecord
|
|
99
103
|
end
|
100
104
|
end
|
101
105
|
|
102
|
-
|
103
106
|
def remove_from_list
|
104
107
|
decrement_positions_on_lower_items
|
105
108
|
end
|
@@ -113,11 +116,10 @@ module ActiveRecord
|
|
113
116
|
update_attribute position_column, self.send(position_column).to_i - 1
|
114
117
|
end
|
115
118
|
|
116
|
-
|
117
119
|
def first?
|
118
120
|
self.send(position_column) == 1
|
119
121
|
end
|
120
|
-
|
122
|
+
|
121
123
|
def last?
|
122
124
|
self.send(position_column) == bottom_position_in_list
|
123
125
|
end
|
@@ -140,9 +142,9 @@ module ActiveRecord
|
|
140
142
|
end
|
141
143
|
|
142
144
|
def add_to_list_bottom
|
143
|
-
|
145
|
+
self[position_column] = bottom_position_in_list.to_i + 1
|
144
146
|
end
|
145
|
-
|
147
|
+
|
146
148
|
# Overwrite this method to define the scope of the list changes
|
147
149
|
def scope_condition() "1" end
|
148
150
|
|
@@ -159,13 +161,14 @@ module ActiveRecord
|
|
159
161
|
end
|
160
162
|
|
161
163
|
def assume_bottom_position
|
162
|
-
update_attribute
|
164
|
+
update_attribute(position_column, bottom_position_in_list.to_i + 1)
|
163
165
|
end
|
164
166
|
|
165
167
|
def assume_top_position
|
166
|
-
update_attribute
|
168
|
+
update_attribute(position_column, 1)
|
167
169
|
end
|
168
|
-
|
170
|
+
|
171
|
+
# This has the effect of moving all the lower items up one.
|
169
172
|
def decrement_positions_on_lower_items
|
170
173
|
self.class.update_all(
|
171
174
|
"#{position_column} = (#{position_column} - 1)", "#{scope_condition} AND #{position_column} > #{send(position_column).to_i}"
|
@@ -1,4 +1,7 @@
|
|
1
|
+
require 'active_record/associations/association_proxy'
|
1
2
|
require 'active_record/associations/association_collection'
|
3
|
+
require 'active_record/associations/belongs_to_association'
|
4
|
+
require 'active_record/associations/has_one_association'
|
2
5
|
require 'active_record/associations/has_many_association'
|
3
6
|
require 'active_record/associations/has_and_belongs_to_many_association'
|
4
7
|
require 'active_record/deprecated_associations'
|
@@ -30,9 +33,9 @@ module ActiveRecord
|
|
30
33
|
# end
|
31
34
|
#
|
32
35
|
# The project class now has the following methods (and more) to ease the traversal and manipulation of its relationships:
|
33
|
-
# * <tt>Project#portfolio, Project#portfolio=(portfolio), Project#portfolio.nil
|
36
|
+
# * <tt>Project#portfolio, Project#portfolio=(portfolio), Project#portfolio.nil?</tt>
|
34
37
|
# * <tt>Project#project_manager, Project#project_manager=(project_manager), Project#project_manager.nil?,</tt>
|
35
|
-
# <tt>Project#project_manager
|
38
|
+
# <tt>Project#project_manager.build, Project#project_manager.create</tt>
|
36
39
|
# * <tt>Project#milestones.empty?, Project#milestones.size, Project#milestones, Project#milestones<<(milestone),</tt>
|
37
40
|
# <tt>Project#milestones.delete(milestone), Project#milestones.find(milestone_id), Project#milestones.find_all(conditions),</tt>
|
38
41
|
# <tt>Project#milestones.build, Project#milestones.create</tt>
|
@@ -71,6 +74,29 @@ module ActiveRecord
|
|
71
74
|
# PRIMARY KEY (id)
|
72
75
|
# )
|
73
76
|
#
|
77
|
+
# == Unsaved objects and associations
|
78
|
+
#
|
79
|
+
# You can manipulate objects and associations before they are saved to the database, but there is some special behaviour you should be
|
80
|
+
# aware of, mostly involving the saving of associated objects.
|
81
|
+
#
|
82
|
+
# === One-to-one associations
|
83
|
+
#
|
84
|
+
# * Assigning an object to a has_one association automatically saves that object, and the object being replaced (if there is one), in
|
85
|
+
# order to update their primary keys - except if the parent object is unsaved (new_record? == true).
|
86
|
+
# * If either of these saves fail (due to one of the objects being invalid) the assignment statement returns false and the assignment
|
87
|
+
# is cancelled.
|
88
|
+
# * If you wish to assign an object to a has_one association without saving it, use the #association.build method (documented below).
|
89
|
+
# * Assigning an object to a belongs_to association does not save the object, since the foreign key field belongs on the parent. It does
|
90
|
+
# not save the parent either.
|
91
|
+
#
|
92
|
+
# === Collections
|
93
|
+
#
|
94
|
+
# * Adding an object to a collection (has_many or has_and_belongs_to_many) automatically saves that object, except if the parent object
|
95
|
+
# (the owner of the collection) is not yet stored in the database.
|
96
|
+
# * If saving any of the objects being added to a collection (via #push or similar) fails, then #push returns false.
|
97
|
+
# * You can add an object to a collection without automatically saving it by using the #collection.build method (documented below).
|
98
|
+
# * All unsaved (new_record? == true) members of the collection are automatically saved when the parent is saved.
|
99
|
+
#
|
74
100
|
# == Caching
|
75
101
|
#
|
76
102
|
# All of the methods are built on a simple caching principle that will keep the result of the last query around unless specifically
|
@@ -124,7 +150,7 @@ module ActiveRecord
|
|
124
150
|
module ClassMethods
|
125
151
|
# Adds the following methods for retrieval and query of collections of associated objects.
|
126
152
|
# +collection+ is replaced with the symbol passed as the first argument, so
|
127
|
-
# <tt>has_many :clients</tt> would add among others <tt>
|
153
|
+
# <tt>has_many :clients</tt> would add among others <tt>clients.empty?</tt>.
|
128
154
|
# * <tt>collection(force_reload = false)</tt> - returns an array of all the associated objects.
|
129
155
|
# An empty array is returned if none are found.
|
130
156
|
# * <tt>collection<<(object, ...)</tt> - adds one or more objects to the collection by setting their foreign keys to the collection's primary key.
|
@@ -200,18 +226,9 @@ module ActiveRecord
|
|
200
226
|
module_eval "before_destroy { |record| #{association_class_name}.delete_all(%(#{association_class_primary_key_name} = \#{record.quoted_id})) }"
|
201
227
|
end
|
202
228
|
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
if association.nil?
|
207
|
-
association = HasManyAssociation.new(self,
|
208
|
-
association_name, association_class_name,
|
209
|
-
association_class_primary_key_name, options)
|
210
|
-
instance_variable_set("@#{association_name}", association)
|
211
|
-
end
|
212
|
-
association.reload if force_reload
|
213
|
-
association
|
214
|
-
end
|
229
|
+
add_multiple_associated_save_callbacks(association_name)
|
230
|
+
|
231
|
+
association_accessor_methods(association_name, association_class_name, association_class_primary_key_name, options, HasManyAssociation)
|
215
232
|
|
216
233
|
# deprecated api
|
217
234
|
deprecated_collection_count_method(association_name)
|
@@ -220,31 +237,28 @@ module ActiveRecord
|
|
220
237
|
deprecated_has_collection_method(association_name)
|
221
238
|
deprecated_find_in_collection_method(association_name)
|
222
239
|
deprecated_find_all_in_collection_method(association_name)
|
223
|
-
|
224
|
-
|
240
|
+
deprecated_collection_create_method(association_name)
|
241
|
+
deprecated_collection_build_method(association_name)
|
225
242
|
end
|
226
243
|
|
227
244
|
# Adds the following methods for retrieval and query of a single associated object.
|
228
245
|
# +association+ is replaced with the symbol passed as the first argument, so
|
229
|
-
# <tt>has_one :manager</tt> would add among others <tt>
|
246
|
+
# <tt>has_one :manager</tt> would add among others <tt>manager.nil?</tt>.
|
230
247
|
# * <tt>association(force_reload = false)</tt> - returns the associated object. Nil is returned if none is found.
|
231
248
|
# * <tt>association=(associate)</tt> - assigns the associate object, extracts the primary key, sets it as the foreign key,
|
232
249
|
# and saves the associate object.
|
233
|
-
# * <tt>association?(object, force_reload = false)</tt> - returns true if the +object+ is of the same type and has the
|
234
|
-
# same id as the associated object.
|
235
250
|
# * <tt>association.nil?</tt> - returns true if there is no associated object.
|
236
|
-
# * <tt>
|
251
|
+
# * <tt>association.build(attributes = {})</tt> - returns a new object of the associated type that has been instantiated
|
237
252
|
# with +attributes+ and linked to this object through a foreign key but has not yet been saved.
|
238
|
-
# * <tt>
|
253
|
+
# * <tt>association.create(attributes = {})</tt> - returns a new object of the associated type that has been instantiated
|
239
254
|
# with +attributes+ and linked to this object through a foreign key and that has already been saved (if it passed the validation).
|
240
255
|
#
|
241
256
|
# Example: An Account class declares <tt>has_one :beneficiary</tt>, which will add:
|
242
257
|
# * <tt>Account#beneficiary</tt> (similar to <tt>Beneficiary.find_first "account_id = #{id}"</tt>)
|
243
258
|
# * <tt>Account#beneficiary=(beneficiary)</tt> (similar to <tt>beneficiary.account_id = account.id; beneficiary.save</tt>)
|
244
|
-
# * <tt>Account#beneficiary?</tt> (similar to <tt>account.beneficiary == some_beneficiary</tt>)
|
245
259
|
# * <tt>Account#beneficiary.nil?</tt>
|
246
|
-
# * <tt>Account#
|
247
|
-
# * <tt>Account#
|
260
|
+
# * <tt>Account#beneficiary.build</tt> (similar to <tt>Beneficiary.new("account_id" => id)</tt>)
|
261
|
+
# * <tt>Account#beneficiary.create</tt> (similar to <tt>b = Beneficiary.new("account_id" => id); b.save; b</tt>)
|
248
262
|
# The declaration can also include an options hash to specialize the behavior of the association.
|
249
263
|
#
|
250
264
|
# Options are:
|
@@ -265,35 +279,53 @@ module ActiveRecord
|
|
265
279
|
# has_one :last_comment, :class_name => "Comment", :order => "posted_on"
|
266
280
|
# has_one :project_manager, :class_name => "Person", :conditions => "role = 'project_manager'"
|
267
281
|
def has_one(association_id, options = {})
|
268
|
-
|
269
|
-
belongs_to(association_id, options)
|
282
|
+
validate_options([ :class_name, :foreign_key, :remote, :conditions, :order, :dependent, :counter_cache ], options.keys)
|
270
283
|
|
271
|
-
association_name, association_class_name,
|
284
|
+
association_name, association_class_name, association_class_primary_key_name =
|
272
285
|
associate_identification(association_id, options[:class_name], options[:foreign_key], false)
|
273
286
|
|
274
287
|
require_association_class(association_class_name)
|
275
288
|
|
276
|
-
|
277
|
-
|
278
|
-
|
289
|
+
module_eval do
|
290
|
+
after_save <<-EOF
|
291
|
+
association = instance_variable_get("@#{association_name}")
|
292
|
+
if (true or @new_record_before_save) and association.respond_to?(:loaded?) and not association.nil?
|
293
|
+
association["#{association_class_primary_key_name}"] = id
|
294
|
+
association.save(true)
|
295
|
+
association.send(:construct_sql)
|
296
|
+
end
|
297
|
+
EOF
|
298
|
+
end
|
299
|
+
|
300
|
+
association_accessor_methods(association_name, association_class_name, association_class_primary_key_name, options, HasOneAssociation)
|
279
301
|
|
280
|
-
module_eval "before_destroy '#{association_name}.destroy
|
302
|
+
module_eval "before_destroy '#{association_name}.destroy unless #{association_name}.nil?'" if options[:dependent]
|
303
|
+
|
304
|
+
# deprecated api
|
305
|
+
deprecated_has_association_method(association_name)
|
306
|
+
deprecated_build_method("build_", association_name, association_class_name, association_class_primary_key_name)
|
307
|
+
deprecated_create_method("create_", association_name, association_class_name, association_class_primary_key_name)
|
308
|
+
deprecated_association_comparison_method(association_name, association_class_name)
|
281
309
|
end
|
282
310
|
|
283
311
|
# Adds the following methods for retrieval and query for a single associated object that this object holds an id to.
|
284
312
|
# +association+ is replaced with the symbol passed as the first argument, so
|
285
|
-
# <tt>belongs_to :author</tt> would add among others <tt>
|
313
|
+
# <tt>belongs_to :author</tt> would add among others <tt>author.nil?</tt>.
|
286
314
|
# * <tt>association(force_reload = false)</tt> - returns the associated object. Nil is returned if none is found.
|
287
315
|
# * <tt>association=(associate)</tt> - assigns the associate object, extracts the primary key, and sets it as the foreign key.
|
288
|
-
# * <tt>association?(object, force_reload = false)</tt> - returns true if the +object+ is of the same type and has the
|
289
|
-
# same id as the associated object.
|
290
316
|
# * <tt>association.nil?</tt> - returns true if there is no associated object.
|
317
|
+
# * <tt>association.build(attributes = {})</tt> - returns a new object of the associated type that has been instantiated
|
318
|
+
# with +attributes+ and linked to this object through a foreign key but has not yet been saved.
|
319
|
+
# * <tt>association.create(attributes = {})</tt> - returns a new object of the associated type that has been instantiated
|
320
|
+
# with +attributes+ and linked to this object through a foreign key and that has already been saved (if it passed the validation).
|
291
321
|
#
|
292
322
|
# Example: A Post class declares <tt>has_one :author</tt>, which will add:
|
293
323
|
# * <tt>Post#author</tt> (similar to <tt>Author.find(author_id)</tt>)
|
294
324
|
# * <tt>Post#author=(author)</tt> (similar to <tt>post.author_id = author.id</tt>)
|
295
325
|
# * <tt>Post#author?</tt> (similar to <tt>post.author == some_author</tt>)
|
296
326
|
# * <tt>Post#author.nil?</tt>
|
327
|
+
# * <tt>Post#author.build</tt> (similar to <tt>Author.new("post_id" => id)</tt>)
|
328
|
+
# * <tt>Post#author.create</tt> (similar to <tt>b = Author.new("post_id" => id); b.save; b</tt>)
|
297
329
|
# The declaration can also include an options hash to specialize the behavior of the association.
|
298
330
|
#
|
299
331
|
# Options are:
|
@@ -317,47 +349,46 @@ module ActiveRecord
|
|
317
349
|
# belongs_to :author, :class_name => "Person", :foreign_key => "author_id"
|
318
350
|
# belongs_to :valid_coupon, :class_name => "Coupon", :foreign_key => "coupon_id",
|
319
351
|
# :conditions => 'discounts > #{payments_count}'
|
320
|
-
|
321
|
-
|
352
|
+
def belongs_to(association_id, options = {})
|
353
|
+
validate_options([ :class_name, :foreign_key, :remote, :conditions, :order, :dependent, :counter_cache ], options.keys)
|
322
354
|
|
323
|
-
|
324
|
-
|
355
|
+
association_name, association_class_name, class_primary_key_name =
|
356
|
+
associate_identification(association_id, options[:class_name], options[:foreign_key], false)
|
325
357
|
|
326
|
-
|
358
|
+
require_association_class(association_class_name)
|
327
359
|
|
328
|
-
|
360
|
+
association_class_primary_key_name = options[:foreign_key] || Inflector.underscore(Inflector.demodulize(association_class_name)) + "_id"
|
329
361
|
|
330
|
-
|
331
|
-
association_finder = <<-"end_eval"
|
332
|
-
#{association_class_name}.find_first(
|
333
|
-
"#{class_primary_key_name} = \#{quoted_id}#{options[:conditions] ? " AND " + options[:conditions] : ""}",
|
334
|
-
#{options[:order] ? "\"" + options[:order] + "\"" : "nil" }
|
335
|
-
)
|
336
|
-
end_eval
|
337
|
-
else
|
338
|
-
association_finder = options[:conditions] ?
|
339
|
-
"#{association_class_name}.find_on_conditions(#{association_class_primary_key_name}, \"#{options[:conditions]}\")" :
|
340
|
-
"#{association_class_name}.find(#{association_class_primary_key_name})"
|
341
|
-
end
|
342
|
-
|
343
|
-
has_association_method(association_name)
|
344
|
-
association_reader_method(association_name, association_finder)
|
345
|
-
belongs_to_writer_method(association_name, association_class_name, association_class_primary_key_name)
|
346
|
-
association_comparison_method(association_name, association_class_name)
|
362
|
+
association_accessor_methods(association_name, association_class_name, association_class_primary_key_name, options, BelongsToAssociation)
|
347
363
|
|
348
|
-
|
349
|
-
|
350
|
-
|
351
|
-
|
352
|
-
|
364
|
+
module_eval do
|
365
|
+
before_save <<-EOF
|
366
|
+
association = instance_variable_get("@#{association_name}")
|
367
|
+
if association.respond_to?(:loaded?) and not association.nil? and association.new_record?
|
368
|
+
association.save(true)
|
369
|
+
self["#{association_class_primary_key_name}"] = association.id
|
370
|
+
association.send(:construct_sql)
|
371
|
+
end
|
372
|
+
EOF
|
373
|
+
end
|
374
|
+
|
375
|
+
if options[:counter_cache]
|
376
|
+
module_eval(
|
377
|
+
"after_create '#{association_class_name}.increment_counter(\"#{Inflector.pluralize(self.to_s.downcase). + "_count"}\", #{association_class_primary_key_name})" +
|
378
|
+
" unless #{association_name}.nil?'"
|
379
|
+
)
|
353
380
|
|
354
|
-
|
355
|
-
|
356
|
-
|
357
|
-
|
358
|
-
end
|
381
|
+
module_eval(
|
382
|
+
"before_destroy '#{association_class_name}.decrement_counter(\"#{Inflector.pluralize(self.to_s.downcase) + "_count"}\", #{association_class_primary_key_name})" +
|
383
|
+
" unless #{association_name}.nil?'"
|
384
|
+
)
|
359
385
|
end
|
360
386
|
|
387
|
+
# deprecated api
|
388
|
+
deprecated_has_association_method(association_name)
|
389
|
+
deprecated_association_comparison_method(association_name, association_class_name)
|
390
|
+
end
|
391
|
+
|
361
392
|
# Associates two classes via an intermediate join table. Unless the join table is explicitly specified as
|
362
393
|
# an option, it is guessed using the lexical order of the class names. So a join between Developer and Project
|
363
394
|
# will give the default join table name of "developers_projects" because "D" outranks "P".
|
@@ -371,7 +402,7 @@ module ActiveRecord
|
|
371
402
|
#
|
372
403
|
# Adds the following methods for retrieval and query.
|
373
404
|
# +collection+ is replaced with the symbol passed as the first argument, so
|
374
|
-
# <tt>has_and_belongs_to_many :categories</tt> would add among others +
|
405
|
+
# <tt>has_and_belongs_to_many :categories</tt> would add among others +categories.empty?+.
|
375
406
|
# * <tt>collection(force_reload = false)</tt> - returns an array of all the associated objects.
|
376
407
|
# An empty array is returned if none is found.
|
377
408
|
# * <tt>collection<<(object, ...)</tt> - adds one or more objects to the collection by creating associations in the join table
|
@@ -385,10 +416,13 @@ module ActiveRecord
|
|
385
416
|
# * <tt>collection.clear</tt> - removes every object from the collection. This does not destroy the objects.
|
386
417
|
# * <tt>collection.empty?</tt> - returns true if there are no associated objects.
|
387
418
|
# * <tt>collection.size</tt> - returns the number of associated objects.
|
419
|
+
# * <tt>collection.find(id)</tt> - finds an associated object responding to the +id+ and that
|
420
|
+
# meets the condition that it has to be associated with this object.
|
388
421
|
#
|
389
422
|
# Example: An Developer class declares <tt>has_and_belongs_to_many :projects</tt>, which will add:
|
390
423
|
# * <tt>Developer#projects</tt>
|
391
424
|
# * <tt>Developer#projects<<</tt>
|
425
|
+
# * <tt>Developer#projects.push_with_attributes</tt>
|
392
426
|
# * <tt>Developer#projects.delete</tt>
|
393
427
|
# * <tt>Developer#projects.clear</tt>
|
394
428
|
# * <tt>Developer#projects.empty?</tt>
|
@@ -431,23 +465,13 @@ module ActiveRecord
|
|
431
465
|
|
432
466
|
require_association_class(association_class_name)
|
433
467
|
|
434
|
-
|
435
|
-
|
436
|
-
|
437
|
-
|
438
|
-
|
439
|
-
association = instance_variable_get("@#{association_name}")
|
440
|
-
if association.nil?
|
441
|
-
association = HasAndBelongsToManyAssociation.new(self,
|
442
|
-
association_name, association_class_name,
|
443
|
-
association_class_primary_key_name, join_table, options)
|
444
|
-
instance_variable_set("@#{association_name}", association)
|
445
|
-
end
|
446
|
-
association.reload if force_reload
|
447
|
-
association
|
448
|
-
end
|
468
|
+
options[:join_table] ||= join_table_name(undecorated_table_name(self.to_s), undecorated_table_name(association_class_name))
|
469
|
+
|
470
|
+
add_multiple_associated_save_callbacks(association_name)
|
471
|
+
|
472
|
+
association_accessor_methods(association_name, association_class_name, association_class_primary_key_name, options, HasAndBelongsToManyAssociation)
|
449
473
|
|
450
|
-
before_destroy_sql = "DELETE FROM #{join_table} WHERE #{association_class_primary_key_name} = \\\#{self.quoted_id}"
|
474
|
+
before_destroy_sql = "DELETE FROM #{options[:join_table]} WHERE #{association_class_primary_key_name} = \\\#{self.quoted_id}"
|
451
475
|
module_eval(%{before_destroy "self.connection.delete(%{#{before_destroy_sql}})"}) # "
|
452
476
|
|
453
477
|
# deprecated api
|
@@ -487,94 +511,70 @@ module ActiveRecord
|
|
487
511
|
return association_id.id2name, association_class_name, primary_key_name
|
488
512
|
end
|
489
513
|
|
490
|
-
def
|
491
|
-
|
492
|
-
|
493
|
-
|
494
|
-
|
495
|
-
|
496
|
-
|
497
|
-
|
514
|
+
def association_accessor_methods(association_name, association_class_name, association_class_primary_key_name, options, association_proxy_class)
|
515
|
+
define_method(association_name) do |*params|
|
516
|
+
force_reload = params.first unless params.empty?
|
517
|
+
association = instance_variable_get("@#{association_name}")
|
518
|
+
unless association.respond_to?(:loaded?)
|
519
|
+
association = association_proxy_class.new(self,
|
520
|
+
association_name, association_class_name,
|
521
|
+
association_class_primary_key_name, options)
|
522
|
+
instance_variable_set("@#{association_name}", association)
|
498
523
|
end
|
499
|
-
|
500
|
-
|
524
|
+
association.reload if force_reload
|
525
|
+
association
|
526
|
+
end
|
501
527
|
|
502
|
-
|
503
|
-
|
504
|
-
|
505
|
-
|
506
|
-
|
507
|
-
|
508
|
-
|
509
|
-
nil
|
510
|
-
end
|
511
|
-
end
|
512
|
-
|
513
|
-
return @#{association_name}
|
528
|
+
define_method("#{association_name}=") do |new_value|
|
529
|
+
association = instance_variable_get("@#{association_name}")
|
530
|
+
unless association.respond_to?(:loaded?)
|
531
|
+
association = association_proxy_class.new(self,
|
532
|
+
association_name, association_class_name,
|
533
|
+
association_class_primary_key_name, options)
|
534
|
+
instance_variable_set("@#{association_name}", association)
|
514
535
|
end
|
515
|
-
|
536
|
+
association.replace(new_value)
|
537
|
+
association
|
538
|
+
end
|
516
539
|
end
|
517
540
|
|
518
|
-
def
|
519
|
-
|
520
|
-
def #{association_name}=(association)
|
521
|
-
if association.nil?
|
522
|
-
@#{association_name}.#{class_primary_key_name} = nil
|
523
|
-
@#{association_name}.save(false)
|
524
|
-
@#{association_name} = nil
|
525
|
-
else
|
526
|
-
raise ActiveRecord::AssociationTypeMismatch unless #{association_class_name} === association
|
527
|
-
association.#{class_primary_key_name} = id
|
528
|
-
association.save(false)
|
529
|
-
@#{association_name} = association
|
530
|
-
end
|
531
|
-
end
|
532
|
-
end_eval
|
541
|
+
def require_association_class(class_name)
|
542
|
+
require_association(Inflector.underscore(class_name)) if class_name
|
533
543
|
end
|
534
544
|
|
535
|
-
def
|
536
|
-
module_eval
|
537
|
-
|
538
|
-
|
539
|
-
|
540
|
-
|
541
|
-
|
542
|
-
|
543
|
-
|
545
|
+
def add_multiple_associated_save_callbacks(association_name)
|
546
|
+
module_eval do
|
547
|
+
before_save <<-end_eval
|
548
|
+
@new_record_before_save = new_record?
|
549
|
+
association = instance_variable_get("@#{association_name}")
|
550
|
+
if association.respond_to?(:loaded?)
|
551
|
+
if new_record?
|
552
|
+
records_to_save = association
|
553
|
+
else
|
554
|
+
records_to_save = association.select{ |record| record.new_record? }
|
555
|
+
end
|
556
|
+
records_to_save.inject(true) do |result,record|
|
557
|
+
result &&= record.valid?
|
558
|
+
end
|
544
559
|
end
|
545
|
-
|
546
|
-
|
547
|
-
end
|
548
|
-
|
549
|
-
def has_association_method(association_name)
|
550
|
-
module_eval <<-"end_eval", __FILE__, __LINE__
|
551
|
-
def has_#{association_name}?(force_reload = false)
|
552
|
-
!#{association_name}(force_reload).nil?
|
553
|
-
end
|
554
|
-
end_eval
|
555
|
-
end
|
556
|
-
|
557
|
-
def build_method(method_prefix, collection_name, collection_class_name, class_primary_key_name)
|
558
|
-
module_eval <<-"end_eval", __FILE__, __LINE__
|
559
|
-
def #{method_prefix + collection_name}(attributes = {})
|
560
|
-
association = #{collection_class_name}.new
|
561
|
-
association.attributes = attributes.merge({ "#{class_primary_key_name}" => id})
|
562
|
-
association
|
563
|
-
end
|
564
|
-
end_eval
|
565
|
-
end
|
566
|
-
|
567
|
-
def create_method(method_prefix, collection_name, collection_class_name, class_primary_key_name)
|
568
|
-
module_eval <<-"end_eval", __FILE__, __LINE__
|
569
|
-
def #{method_prefix + collection_name}(attributes = nil)
|
570
|
-
#{collection_class_name}.create((attributes || {}).merge({ "#{class_primary_key_name}" => id}))
|
571
|
-
end
|
572
|
-
end_eval
|
573
|
-
end
|
560
|
+
end_eval
|
561
|
+
end
|
574
562
|
|
575
|
-
|
576
|
-
|
563
|
+
module_eval do
|
564
|
+
after_save <<-end_eval
|
565
|
+
association = instance_variable_get("@#{association_name}")
|
566
|
+
if association.respond_to?(:loaded?)
|
567
|
+
if @new_record_before_save
|
568
|
+
records_to_save = association
|
569
|
+
else
|
570
|
+
records_to_save = association.select{ |record| record.new_record? }
|
571
|
+
end
|
572
|
+
records_to_save.each{ |record| association.send(:insert_record, record) }
|
573
|
+
association.send(:construct_sql) # reconstruct the SQL queries now that we know the owner's id
|
574
|
+
end
|
575
|
+
end_eval
|
576
|
+
end
|
577
577
|
end
|
578
578
|
end
|
579
579
|
end
|
580
|
-
end
|
580
|
+
end
|