|
823554ea
»
|
dhh |
2005-01-15 |
Added support for associati... |
1 |
require 'active_record/associations/association_proxy' |
|
db045dbb
»
|
dhh |
2004-11-23 |
Initial |
2 |
require 'active_record/associations/association_collection' |
|
823554ea
»
|
dhh |
2005-01-15 |
Added support for associati... |
3 |
require 'active_record/associations/belongs_to_association' |
|
57b7532b
»
|
dhh |
2005-12-01 |
Work-in progress for provid... |
4 |
require 'active_record/associations/belongs_to_polymorphic_association' |
|
823554ea
»
|
dhh |
2005-01-15 |
Added support for associati... |
5 |
require 'active_record/associations/has_one_association' |
|
db045dbb
»
|
dhh |
2004-11-23 |
Initial |
6 |
require 'active_record/associations/has_many_association' |
|
6abda696
»
|
dhh |
2005-12-02 |
Added preliminary support f... |
7 |
require 'active_record/associations/has_many_through_association' |
|
db045dbb
»
|
dhh |
2004-11-23 |
Initial |
8 |
require 'active_record/associations/has_and_belongs_to_many_association' |
| |
9 |
require 'active_record/deprecated_associations' |
| |
10 |
|
| |
11 |
module ActiveRecord |
|
57af961a
»
|
technoweenie |
2006-03-18 |
Raise error when trying to ... |
12 |
class HasManyThroughAssociationNotFoundError < ActiveRecordError |
| |
13 |
def initialize(reflection) |
| |
14 |
@reflection = reflection |
| |
15 |
end |
| |
16 |
|
| |
17 |
def message |
| |
18 |
"Could not find the association '#{@reflection.options[:through]}' in model #{@reflection.klass}" |
| |
19 |
end |
| |
20 |
end |
| |
21 |
|
| |
22 |
class HasManyThroughAssociationPolymorphicError < ActiveRecordError |
| |
23 |
def initialize(owner_class_name, reflection, source_reflection) |
| |
24 |
@owner_class_name = owner_class_name |
| |
25 |
@reflection = reflection |
| |
26 |
@source_reflection = source_reflection |
| |
27 |
end |
| |
28 |
|
| |
29 |
def message |
| |
30 |
"Cannot have a has_many :through association '#{@owner_class_name}##{@reflection.name}' on the polymorphic object '#{@source_reflection.class_name}##{@source_reflection.name}'." |
| |
31 |
end |
| |
32 |
end |
| |
33 |
|
| |
34 |
class HasManyThroughSourceAssociationNotFoundError < ActiveRecordError |
| |
35 |
def initialize(through_reflection, source_reflection_name) |
| |
36 |
@through_reflection = through_reflection |
| |
37 |
@source_reflection_name = source_reflection_name |
| |
38 |
end |
| |
39 |
|
| |
40 |
def message |
| |
41 |
"Could not find the source association '#{@source_reflection_name}' in model #{@through_reflection.klass}" |
| |
42 |
end |
| |
43 |
end |
| |
44 |
|
| |
45 |
class EagerLoadPolymorphicError < ActiveRecordError |
| |
46 |
def initialize(reflection) |
| |
47 |
@reflection = reflection |
| |
48 |
end |
| |
49 |
|
| |
50 |
def message |
| |
51 |
"Can not eagerly load the polymorphic association '#{@reflection.name}'" |
| |
52 |
end |
| |
53 |
end |
| |
54 |
|
|
db045dbb
»
|
dhh |
2004-11-23 |
Initial |
55 |
module Associations # :nodoc: |
|
7c8d2f28
»
|
dhh |
2005-04-02 |
Removed broken attempt to D... |
56 |
def self.append_features(base) |
| |
57 |
super |
| |
58 |
base.extend(ClassMethods) |
| |
59 |
end |
| |
60 |
|
|
4b229d10
»
|
dhh |
2004-12-22 |
Added Base#clear_associatio... |
61 |
# Clears out the association cache |
| |
62 |
def clear_association_cache #:nodoc: |
| |
63 |
self.class.reflect_on_all_associations.to_a.each do |assoc| |
| |
64 |
instance_variable_set "@#{assoc.name}", nil |
|
deda0ee4
»
|
dhh |
2005-06-25 |
Fixed that clear_associatio... |
65 |
end unless self.new_record? |
|
4b229d10
»
|
dhh |
2004-12-22 |
Added Base#clear_associatio... |
66 |
end |
|
abc895b8
»
|
dhh |
2005-04-03 |
Added new Base.find API and... |
67 |
|
|
db045dbb
»
|
dhh |
2004-11-23 |
Initial |
68 |
# Associations are a set of macro-like class methods for tying objects together through foreign keys. They express relationships like |
| |
69 |
# "Project has one Project Manager" or "Project belongs to a Portfolio". Each macro adds a number of methods to the class which are |
|
a8eea0b0
»
|
dhh |
2005-10-26 |
Fix docs (closes #2491) |
70 |
# specialized according to the collection or association symbol and the options hash. It works much the same way as Ruby's own attr* |
|
db045dbb
»
|
dhh |
2004-11-23 |
Initial |
71 |
# methods. Example: |
| |
72 |
# |
| |
73 |
# class Project < ActiveRecord::Base |
| |
74 |
# belongs_to :portfolio |
| |
75 |
# has_one :project_manager |
| |
76 |
# has_many :milestones |
| |
77 |
# has_and_belongs_to_many :categories |
| |
78 |
# end |
| |
79 |
# |
| |
80 |
# The project class now has the following methods (and more) to ease the traversal and manipulation of its relationships: |
|
823554ea
»
|
dhh |
2005-01-15 |
Added support for associati... |
81 |
# * <tt>Project#portfolio, Project#portfolio=(portfolio), Project#portfolio.nil?</tt> |
|
db045dbb
»
|
dhh |
2004-11-23 |
Initial |
82 |
# * <tt>Project#project_manager, Project#project_manager=(project_manager), Project#project_manager.nil?,</tt> |
| |
83 |
# * <tt>Project#milestones.empty?, Project#milestones.size, Project#milestones, Project#milestones<<(milestone),</tt> |
| |
84 |
# <tt>Project#milestones.delete(milestone), Project#milestones.find(milestone_id), Project#milestones.find_all(conditions),</tt> |
| |
85 |
# <tt>Project#milestones.build, Project#milestones.create</tt> |
| |
86 |
# * <tt>Project#categories.empty?, Project#categories.size, Project#categories, Project#categories<<(category1),</tt> |
| |
87 |
# <tt>Project#categories.delete(category1)</tt> |
| |
88 |
# |
| |
89 |
# == Example |
| |
90 |
# |
|
b921b5da
»
|
dhh |
2005-04-30 |
Fixed association picture r... |
91 |
# link:files/examples/associations.png |
|
db045dbb
»
|
dhh |
2004-11-23 |
Initial |
92 |
# |
| |
93 |
# == Is it belongs_to or has_one? |
| |
94 |
# |
| |
95 |
# Both express a 1-1 relationship, the difference is mostly where to place the foreign key, which goes on the table for the class |
| |
96 |
# saying belongs_to. Example: |
| |
97 |
# |
| |
98 |
# class Post < ActiveRecord::Base |
| |
99 |
# has_one :author |
| |
100 |
# end |
| |
101 |
# |
| |
102 |
# class Author < ActiveRecord::Base |
| |
103 |
# belongs_to :post |
| |
104 |
# end |
| |
105 |
# |
| |
106 |
# The tables for these classes could look something like: |
| |
107 |
# |
| |
108 |
# CREATE TABLE posts ( |
| |
109 |
# id int(11) NOT NULL auto_increment, |
| |
110 |
# title varchar default NULL, |
| |
111 |
# PRIMARY KEY (id) |
| |
112 |
# ) |
| |
113 |
# |
| |
114 |
# CREATE TABLE authors ( |
| |
115 |
# id int(11) NOT NULL auto_increment, |
| |
116 |
# post_id int(11) default NULL, |
| |
117 |
# name varchar default NULL, |
| |
118 |
# PRIMARY KEY (id) |
| |
119 |
# ) |
| |
120 |
# |
|
823554ea
»
|
dhh |
2005-01-15 |
Added support for associati... |
121 |
# == Unsaved objects and associations |
| |
122 |
# |
| |
123 |
# You can manipulate objects and associations before they are saved to the database, but there is some special behaviour you should be |
| |
124 |
# aware of, mostly involving the saving of associated objects. |
| |
125 |
# |
| |
126 |
# === One-to-one associations |
| |
127 |
# |
|
a8eea0b0
»
|
dhh |
2005-10-26 |
Fix docs (closes #2491) |
128 |
# * Assigning an object to a has_one association automatically saves that object and the object being replaced (if there is one), in |
|
823554ea
»
|
dhh |
2005-01-15 |
Added support for associati... |
129 |
# order to update their primary keys - except if the parent object is unsaved (new_record? == true). |
| |
130 |
# * If either of these saves fail (due to one of the objects being invalid) the assignment statement returns false and the assignment |
| |
131 |
# is cancelled. |
| |
132 |
# * If you wish to assign an object to a has_one association without saving it, use the #association.build method (documented below). |
| |
133 |
# * Assigning an object to a belongs_to association does not save the object, since the foreign key field belongs on the parent. It does |
| |
134 |
# not save the parent either. |
| |
135 |
# |
| |
136 |
# === Collections |
| |
137 |
# |
| |
138 |
# * Adding an object to a collection (has_many or has_and_belongs_to_many) automatically saves that object, except if the parent object |
| |
139 |
# (the owner of the collection) is not yet stored in the database. |
| |
140 |
# * If saving any of the objects being added to a collection (via #push or similar) fails, then #push returns false. |
| |
141 |
# * You can add an object to a collection without automatically saving it by using the #collection.build method (documented below). |
| |
142 |
# * All unsaved (new_record? == true) members of the collection are automatically saved when the parent is saved. |
| |
143 |
# |
|
17f7f8a0
»
|
dhh |
2005-07-06 |
Made documentation ready fo... |
144 |
# === Association callbacks |
|
4180e57b
»
|
dhh |
2005-07-04 |
Added callback hooks to ass... |
145 |
# |
| |
146 |
# Similiar to the normal callbacks that hook into the lifecycle of an Active Record object, you can also define callbacks that get |
| |
147 |
# trigged when you add an object to or removing an object from a association collection. Example: |
| |
148 |
# |
| |
149 |
# class Project |
| |
150 |
# has_and_belongs_to_many :developers, :after_add => :evaluate_velocity |
| |
151 |
# |
| |
152 |
# def evaluate_velocity(developer) |
| |
153 |
# ... |
| |
154 |
# end |
| |
155 |
# end |
| |
156 |
# |
| |
157 |
# It's possible to stack callbacks by passing them as an array. Example: |
| |
158 |
# |
| |
159 |
# class Project |
|
17f7f8a0
»
|
dhh |
2005-07-06 |
Made documentation ready fo... |
160 |
# has_and_belongs_to_many :developers, :after_add => [:evaluate_velocity, Proc.new { |p, d| p.shipping_date = Time.now}] |
|
4180e57b
»
|
dhh |
2005-07-04 |
Added callback hooks to ass... |
161 |
# end |
| |
162 |
# |
| |
163 |
# Possible callbacks are: before_add, after_add, before_remove and after_remove. |
| |
164 |
# |
| |
165 |
# Should any of the before_add callbacks throw an exception, the object does not get added to the collection. Same with |
| |
166 |
# the before_remove callbacks, if an exception is thrown the object doesn't get removed. |
| |
167 |
# |
|
8c512a1c
»
|
dhh |
2005-11-03 |
Added extension capabilitie... |
168 |
# === Association extensions |
| |
169 |
# |
| |
170 |
# The proxy objects that controls the access to associations can be extended through anonymous modules. This is especially |
|
3c01e01d
»
|
Marcel Molina |
2005-12-19 |
Fix typo in association doc... |
171 |
# beneficial for adding new finders, creators, and other factory-type methods that are only used as part of this association. |
|
8c512a1c
»
|
dhh |
2005-11-03 |
Added extension capabilitie... |
172 |
# Example: |
| |
173 |
# |
| |
174 |
# class Account < ActiveRecord::Base |
|
c8dd66fd
»
|
dhh |
2005-11-06 |
Made association extensions... |
175 |
# has_many :people do |
|
8c512a1c
»
|
dhh |
2005-11-03 |
Added extension capabilitie... |
176 |
# def find_or_create_by_name(name) |
|
e5d9ad3e
»
|
dhh |
2005-12-12 |
Added option inheritance fo... |
177 |
# first_name, last_name = name.split(" ", 2) |
|
da7752e5
»
|
dhh |
2005-11-06 |
Sharper example |
178 |
# find_or_create_by_first_name_and_last_name(first_name, last_name) |
|
8c512a1c
»
|
dhh |
2005-11-03 |
Added extension capabilitie... |
179 |
# end |
|
c8dd66fd
»
|
dhh |
2005-11-06 |
Made association extensions... |
180 |
# end |
|
8c512a1c
»
|
dhh |
2005-11-03 |
Added extension capabilitie... |
181 |
# end |
| |
182 |
# |
| |
183 |
# person = Account.find(:first).people.find_or_create_by_name("David Heinemeier Hansson") |
| |
184 |
# person.first_name # => "David" |
| |
185 |
# person.last_name # => "Heinemeier Hansson" |
| |
186 |
# |
|
c8dd66fd
»
|
dhh |
2005-11-06 |
Made association extensions... |
187 |
# If you need to share the same extensions between many associations, you can use a named extension module. Example: |
| |
188 |
# |
| |
189 |
# module FindOrCreateByNameExtension |
| |
190 |
# def find_or_create_by_name(name) |
|
e5d9ad3e
»
|
dhh |
2005-12-12 |
Added option inheritance fo... |
191 |
# first_name, last_name = name.split(" ", 2) |
|
da7752e5
»
|
dhh |
2005-11-06 |
Sharper example |
192 |
# find_or_create_by_first_name_and_last_name(first_name, last_name) |
|
c8dd66fd
»
|
dhh |
2005-11-06 |
Made association extensions... |
193 |
# end |
| |
194 |
# end |
| |
195 |
# |
| |
196 |
# class Account < ActiveRecord::Base |
| |
197 |
# has_many :people, :extend => FindOrCreateByNameExtension |
| |
198 |
# end |
| |
199 |
# |
| |
200 |
# class Company < ActiveRecord::Base |
| |
201 |
# has_many :people, :extend => FindOrCreateByNameExtension |
| |
202 |
# end |
|
8c512a1c
»
|
dhh |
2005-11-03 |
Added extension capabilitie... |
203 |
# |
|
db045dbb
»
|
dhh |
2004-11-23 |
Initial |
204 |
# == Caching |
| |
205 |
# |
| |
206 |
# All of the methods are built on a simple caching principle that will keep the result of the last query around unless specifically |
| |
207 |
# instructed not to. The cache is even shared across methods to make it even cheaper to use the macro-added methods without |
| |
208 |
# worrying too much about performance at the first go. Example: |
| |
209 |
# |
| |
210 |
# project.milestones # fetches milestones from the database |
| |
211 |
# project.milestones.size # uses the milestone cache |
| |
212 |
# project.milestones.empty? # uses the milestone cache |
| |
213 |
# project.milestones(true).size # fetches milestones from the database |
| |
214 |
# project.milestones # uses the milestone cache |
| |
215 |
# |
|
515886a5
»
|
dhh |
2005-04-18 |
Added documentation for new... |
216 |
# == Eager loading of associations |
| |
217 |
# |
| |
218 |
# Eager loading is a way to find objects of a certain class and a number of named associations along with it in a single SQL call. This is |
| |
219 |
# one of the easiest ways of to prevent the dreaded 1+N problem in which fetching 100 posts that each needs to display their author |
| |
220 |
# triggers 101 database queries. Through the use of eager loading, the 101 queries can be reduced to 1. Example: |
| |
221 |
# |
| |
222 |
# class Post < ActiveRecord::Base |
| |
223 |
# belongs_to :author |
| |
224 |
# has_many :comments |
| |
225 |
# end |
| |
226 |
# |
| |
227 |
# Consider the following loop using the class above: |
| |
228 |
# |
|
4e2f7bec
»
|
dhh |
2005-04-19 |
Added documentation about :... |
229 |
# for post in Post.find(:all) |
|
515886a5
»
|
dhh |
2005-04-18 |
Added documentation for new... |
230 |
# puts "Post: " + post.title |
| |
231 |
# puts "Written by: " + post.author.name |
| |
232 |
# puts "Last comment on: " + post.comments.first.created_on |
| |
233 |
# end |
| |
234 |
# |
| |
235 |
# To iterate over these one hundred posts, we'll generate 201 database queries. Let's first just optimize it for retrieving the author: |
| |
236 |
# |
|
4e2f7bec
»
|
dhh |
2005-04-19 |
Added documentation about :... |
237 |
# for post in Post.find(:all, :include => :author) |
|
515886a5
»
|
dhh |
2005-04-18 |
Added documentation for new... |
238 |
# |
| |
239 |
# This references the name of the belongs_to association that also used the :author symbol, so the find will now weave in a join something |
| |
240 |
# like this: LEFT OUTER JOIN authors ON authors.id = posts.author_id. Doing so will cut down the number of queries from 201 to 101. |
| |
241 |
# |
| |
242 |
# We can improve upon the situation further by referencing both associations in the finder with: |
| |
243 |
# |
|
4e2f7bec
»
|
dhh |
2005-04-19 |
Added documentation about :... |
244 |
# for post in Post.find(:all, :include => [ :author, :comments ]) |
|
515886a5
»
|
dhh |
2005-04-18 |
Added documentation for new... |
245 |
# |
| |
246 |
# 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. |
| |
247 |
# 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 |
|
a8eea0b0
»
|
dhh |
2005-10-26 |
Fix docs (closes #2491) |
248 |
# 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 |
| |
249 |
# 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. |
|
515886a5
»
|
dhh |
2005-04-18 |
Added documentation for new... |
250 |
# |
|
851dd080
»
|
dhh |
2005-10-18 |
Added support for using lim... |
251 |
# Please note that limited eager loading with has_many and has_and_belongs_to_many associations is not compatible with describing conditions |
| |
252 |
# on these eager tables. This will work: |
| |
253 |
# |
| |
254 |
# Post.find(:all, :include => :comments, :conditions => "posts.title = 'magic forest'", :limit => 2) |
| |
255 |
# |
| |
256 |
# ...but this will not (and an ArgumentError will be raised): |
| |
257 |
# |
| |
258 |
# Post.find(:all, :include => :comments, :conditions => "comments.body like 'Normal%'", :limit => 2) |
|
4e2f7bec
»
|
dhh |
2005-04-19 |
Added documentation about :... |
259 |
# |
|
5462358c
»
|
dhh |
2005-04-19 |
Fixed that :get, :post, and... |
260 |
# Also have in mind that since the eager loading is pulling from multiple tables, you'll have to disambiguate any column references |
|
6f344000
»
|
dhh |
2005-04-19 |
Fixed order of loading in e... |
261 |
# in both conditions and orders. So :order => "posts.id DESC" will work while :order => "id DESC" will not. This may require that |
| |
262 |
# you alter the :order and :conditions on the association definitions themselves. |
| |
263 |
# |
|
3022ee84
»
|
dhh |
2005-05-21 |
Added note of limitation fo... |
264 |
# It's currently not possible to use eager loading on multiple associations from the same table. Eager loading will also not pull |
| |
265 |
# additional attributes on join tables, so "rich associations" with has_and_belongs_to_many is not a good fit for eager loading. |
|
5462358c
»
|
dhh |
2005-04-19 |
Fixed that :get, :post, and... |
266 |
# |
|
db045dbb
»
|
dhh |
2004-11-23 |
Initial |
267 |
# == Modules |
| |
268 |
# |
| |
269 |
# By default, associations will look for objects within the current module scope. Consider: |
| |
270 |
# |
| |
271 |
# module MyApplication |
| |
272 |
# module Business |
| |
273 |
# class Firm < ActiveRecord::Base |
| |
274 |
# has_many :clients |
| |
275 |
# end |
| |
276 |
# |
| |
277 |
# class Company < ActiveRecord::Base; end |
| |
278 |
# end |
| |
279 |
# end |
| |
280 |
# |
| |
281 |
# When Firm#clients is called, it'll in turn call <tt>MyApplication::Business::Company.find(firm.id)</tt>. If you want to associate |
| |
282 |
# with a class in another module scope this can be done by specifying the complete class name, such as: |
| |
283 |
# |
| |
284 |
# module MyApplication |
| |
285 |
# module Business |
| |
286 |
# class Firm < ActiveRecord::Base; end |
| |
287 |
# end |
| |
288 |
# |
| |
289 |
# module Billing |
| |
290 |
# class Account < ActiveRecord::Base |
| |
291 |
# belongs_to :firm, :class_name => "MyApplication::Business::Firm" |
| |
292 |
# end |
| |
293 |
# end |
| |
294 |
# end |
| |
295 |
# |
| |
296 |
# == Type safety with ActiveRecord::AssociationTypeMismatch |
| |
297 |
# |
| |
298 |
# If you attempt to assign an object to an association that doesn't match the inferred or specified <tt>:class_name</tt>, you'll |
| |
299 |
# get a ActiveRecord::AssociationTypeMismatch. |
| |
300 |
# |
| |
301 |
# == Options |
| |
302 |
# |
| |
303 |
# All of the association macros can be specialized through options which makes more complex cases than the simple and guessable ones |
| |
304 |
# possible. |
| |
305 |
module ClassMethods |
|
cab24945
»
|
dhh |
2005-01-01 |
Updated documentation for a... |
306 |
# Adds the following methods for retrieval and query of collections of associated objects. |
|
db045dbb
»
|
dhh |
2004-11-23 |
Initial |
307 |
# +collection+ is replaced with the symbol passed as the first argument, so |
|
823554ea
»
|
dhh |
2005-01-15 |
Added support for associati... |
308 |
# <tt>has_many :clients</tt> would add among others <tt>clients.empty?</tt>. |
|
db045dbb
»
|
dhh |
2004-11-23 |
Initial |
309 |
# * <tt>collection(force_reload = false)</tt> - returns an array of all the associated objects. |
| |
310 |
# An empty array is returned if none are found. |
| |
311 |
# * <tt>collection<<(object, ...)</tt> - adds one or more objects to the collection by setting their foreign keys to the collection's primary key. |
|
1ba17797
»
|
dhh |
2005-04-02 |
Added that model.items.dele... |
312 |
# * <tt>collection.delete(object, ...)</tt> - removes one or more objects from the collection by setting their foreign keys to NULL. |
| |
313 |
# This will also destroy the objects if they're declared as belongs_to and dependent on this model. |
|
bfe6a759
»
|
dhh |
2005-06-15 |
Added actual database-chang... |
314 |
# * <tt>collection=objects</tt> - replaces the collections content by deleting and adding objects as appropriate. |
| |
315 |
# * <tt>collection_singular_ids=ids</tt> - replace the collection by the objects identified by the primary keys in +ids+ |
|
efaf2af0
»
|
jeremy |
2005-09-27 |
r3653@asus: jeremy | 2005... |
316 |
# * <tt>collection.clear</tt> - removes every object from the collection. This destroys the associated objects if they |
|
e18fad6c
»
|
dhh |
2006-03-18 |
Update lingering uses of de... |
317 |
# are <tt>:dependent</tt>, deletes them directly from the database if they are <tt>:dependent => :delete_all</tt>, |
|
efaf2af0
»
|
jeremy |
2005-09-27 |
r3653@asus: jeremy | 2005... |
318 |
# and sets their foreign keys to NULL otherwise. |
|
db045dbb
»
|
dhh |
2004-11-23 |
Initial |
319 |
# * <tt>collection.empty?</tt> - returns true if there are no associated objects. |
| |
320 |
# * <tt>collection.size</tt> - returns the number of associated objects. |
|
c1611a70
»
|
dhh |
2005-04-18 |
Updated documentation here ... |
321 |
# * <tt>collection.find</tt> - finds an associated object according to the same rules as Base.find. |
|
db045dbb
»
|
dhh |
2004-11-23 |
Initial |
322 |
# * <tt>collection.build(attributes = {})</tt> - returns a new object of the collection type that has been instantiated |
|
3b9e90a4
»
|
dhh |
2005-04-11 |
Moved build_association and... |
323 |
# with +attributes+ and linked to this object through a foreign key but has not yet been saved. *Note:* This only works if an |
|
a8eea0b0
»
|
dhh |
2005-10-26 |
Fix docs (closes #2491) |
324 |
# associated object already exists, not if it's nil! |
|
db045dbb
»
|
dhh |
2004-11-23 |
Initial |
325 |
# * <tt>collection.create(attributes = {})</tt> - returns a new object of the collection type that has been instantiated |
| |
326 |
# with +attributes+ and linked to this object through a foreign key and that has already been saved (if it passed the validation). |
|
a8eea0b0
»
|
dhh |
2005-10-26 |
Fix docs (closes #2491) |
327 |
# *Note:* This only works if an associated object already exists, not if it's nil! |
|
db045dbb
»
|
dhh |
2004-11-23 |
Initial |
328 |
# |
| |
329 |
# Example: A Firm class declares <tt>has_many :clients</tt>, which will add: |
|
c1611a70
»
|
dhh |
2005-04-18 |
Updated documentation here ... |
330 |
# * <tt>Firm#clients</tt> (similar to <tt>Clients.find :all, :conditions => "firm_id = #{id}"</tt>) |
|
db045dbb
»
|
dhh |
2004-11-23 |
Initial |
331 |
# * <tt>Firm#clients<<</tt> |
| |
332 |
# * <tt>Firm#clients.delete</tt> |
|
bfe6a759
»
|
dhh |
2005-06-15 |
Added actual database-chang... |
333 |
# * <tt>Firm#clients=</tt> |
| |
334 |
# * <tt>Firm#client_ids=</tt> |
|
db045dbb
»
|
dhh |
2004-11-23 |
Initial |
335 |
# * <tt>Firm#clients.clear</tt> |
| |
336 |
# * <tt>Firm#clients.empty?</tt> (similar to <tt>firm.clients.size == 0</tt>) |
| |
337 |
# * <tt>Firm#clients.size</tt> (similar to <tt>Client.count "firm_id = #{id}"</tt>) |
|
c1611a70
»
|
dhh |
2005-04-18 |
Updated documentation here ... |
338 |
# * <tt>Firm#clients.find</tt> (similar to <tt>Client.find(id, :conditions => "firm_id = #{id}")</tt>) |
|
db045dbb
»
|
dhh |
2004-11-23 |
Initial |
339 |
# * <tt>Firm#clients.build</tt> (similar to <tt>Client.new("firm_id" => id)</tt>) |
|
1d4d7217
»
|
dhh |
2005-06-21 |
Fixed docs #856 |
340 |
# * <tt>Firm#clients.create</tt> (similar to <tt>c = Client.new("firm_id" => id); c.save; c</tt>) |
|
db045dbb
»
|
dhh |
2004-11-23 |
Initial |
341 |
# The declaration can also include an options hash to specialize the behavior of the association. |
| |
342 |
# |
| |
343 |
# Options are: |
|
098fa943
»
|
dhh |
2005-02-07 |
Fixed documentation snafus ... |
344 |
# * <tt>:class_name</tt> - specify the class name of the association. Use it only if that name can't be inferred |
|
db045dbb
»
|
dhh |
2004-11-23 |
Initial |
345 |
# from the association name. So <tt>has_many :products</tt> will by default be linked to the +Product+ class, but |
| |
346 |
# if the real class name is +SpecialProduct+, you'll have to specify it with this option. |
| |
347 |
# * <tt>:conditions</tt> - specify the conditions that the associated objects must meet in order to be included as a "WHERE" |
| |
348 |
# sql fragment, such as "price > 5 AND name LIKE 'B%'". |
| |
349 |
# * <tt>:order</tt> - specify the order in which the associated objects are returned as a "ORDER BY" sql fragment, |
| |
350 |
# such as "last_name, first_name DESC" |
|
33092681
»
|
jeremy |
2005-11-10 |
Add :group option, correspo... |
351 |
# * <tt>:group</tt> - specify the attribute by which the associated objects are returned as a "GROUP BY" sql fragment, |
| |
352 |
# such as "category" |
|
db045dbb
»
|
dhh |
2004-11-23 |
Initial |
353 |
# * <tt>:foreign_key</tt> - specify the foreign key used for the association. By default this is guessed to be the name |
| |
354 |
# of this class in lower-case and "_id" suffixed. So a +Person+ class that makes a has_many association will use "person_id" |
| |
355 |
# as the default foreign_key. |
|
57565b35
»
|
NZKoz |
2006-03-09 |
format fix for locking [Mic... |
356 |
# * <tt>:dependent</tt> - if set to :destroy all the associated objects are destroyed |
|
c54b51fa
»
|
jeremy |
2005-11-08 |
Deprecate the old, confusin... |
357 |
# alongside this object by calling their destroy method. If set to :delete_all all associated |
| |
358 |
# objects are deleted *without* calling their destroy method. If set to :nullify all associated |
| |
359 |
# objects' foreign keys are set to NULL *without* calling their save callbacks. |
|
57565b35
»
|
NZKoz |
2006-03-09 |
format fix for locking [Mic... |
360 |
# NOTE: :dependent => true is deprecated and has been replaced with :dependent => :destroy. |
|
db045dbb
»
|
dhh |
2004-11-23 |
Initial |
361 |
# May not be set if :exclusively_dependent is also set. |
|
c54b51fa
»
|
jeremy |
2005-11-08 |
Deprecate the old, confusin... |
362 |
# * <tt>:exclusively_dependent</tt> - Deprecated; equivalent to :dependent => :delete_all. If set to true all |
| |
363 |
# the associated object are deleted in one SQL statement without having their |
|
db045dbb
»
|
dhh |
2004-11-23 |
Initial |
364 |
# before_destroy callback run. This should only be used on associations that depend solely on this class and don't need to do any |
| |
365 |
# clean-up in before_destroy. The upside is that it's much faster, especially if there's a counter_cache involved. |
| |
366 |
# May not be set if :dependent is also set. |
| |
367 |
# * <tt>:finder_sql</tt> - specify a complete SQL statement to fetch the association. This is a good way to go for complex |
|
a8eea0b0
»
|
dhh |
2005-10-26 |
Fix docs (closes #2491) |
368 |
# associations that depend on multiple tables. Note: When this option is used, +find_in_collection+ is _not_ added. |
|
ea759cb7
»
|
dhh |
2004-12-07 |
Added counter_sql option fo... |
369 |
# * <tt>:counter_sql</tt> - specify a complete SQL statement to fetch the size of the association. If +:finder_sql+ is |
| |
370 |
# specified but +:counter_sql+, +:counter_sql+ will be generated by replacing SELECT ... FROM with SELECT COUNT(*) FROM. |
|
c8dd66fd
»
|
dhh |
2005-11-06 |
Made association extensions... |
371 |
# * <tt>:extend</tt> - specify a named module for extending the proxy, see "Association extensions". |
|
49c801b7
»
|
dhh |
2005-11-06 |
Added :include as an option... |
372 |
# * <tt>:include</tt> - specify second-order associations that should be eager loaded when the collection is loaded. |
|
e5d9ad3e
»
|
dhh |
2005-12-12 |
Added option inheritance fo... |
373 |
# * <tt>:group</tt>: An attribute name by which the result should be grouped. Uses the GROUP BY SQL-clause. |
| |
374 |
# * <tt>:limit</tt>: An integer determining the limit on the number of rows that should be returned. |
| |
375 |
# * <tt>:offset</tt>: An integer determining the offset from where the rows should be fetched. So at 5, it would skip the first 4 rows. |
| |
376 |
# * <tt>:select</tt>: By default, this is * as in SELECT * FROM, but can be changed if you for example want to do a join, but not |
| |
377 |
# include the joined columns. |
|
db045dbb
»
|
dhh |
2004-11-23 |
Initial |
378 |
# |
| |
379 |
# Option examples: |
| |
380 |
# has_many :comments, :order => "posted_on" |
|
49c801b7
»
|
dhh |
2005-11-06 |
Added :include as an option... |
381 |
# has_many :comments, :include => :author |
|
db045dbb
»
|
dhh |
2004-11-23 |
Initial |
382 |
# has_many :people, :class_name => "Person", :conditions => "deleted = 0", :order => "name" |
|
57565b35
»
|
NZKoz |
2006-03-09 |
format fix for locking [Mic... |
383 |
# has_many :tracks, :order => "position", :dependent => :destroy |
| |
384 |
# has_many :comments, :dependent => :nullify |
|
db045dbb
»
|
dhh |
2004-11-23 |
Initial |
385 |
# has_many :subscribers, :class_name => "Person", :finder_sql => |
| |
386 |
# 'SELECT DISTINCT people.* ' + |
| |
387 |
# 'FROM people p, post_subscriptions ps ' + |
| |
388 |
# 'WHERE ps.post_id = #{id} AND ps.person_id = p.id ' + |
| |
389 |
# 'ORDER BY p.first_name' |
|
c8dd66fd
»
|
dhh |
2005-11-06 |
Made association extensions... |
390 |
def has_many(association_id, options = {}, &extension) |
|
6abda696
»
|
dhh |
2005-12-02 |
Added preliminary support f... |
391 |
reflection = create_has_many_reflection(association_id, options, &extension) |
|
04397693
»
|
dhh |
2005-09-09 |
Refactored away all the leg... |
392 |
|
|
6abda696
»
|
dhh |
2005-12-02 |
Added preliminary support f... |
393 |
configure_dependency_for_has_many(reflection) |
|
c8dd66fd
»
|
dhh |
2005-11-06 |
Made association extensions... |
394 |
|
|
6abda696
»
|
dhh |
2005-12-02 |
Added preliminary support f... |
395 |
if options[:through] |
| |
396 |
collection_reader_method(reflection, HasManyThroughAssociation) |
| |
397 |
else |
| |
398 |
add_multiple_associated_save_callbacks(reflection.name) |
| |
399 |
add_association_callbacks(reflection.name, reflection.options) |
| |
400 |
collection_accessor_methods(reflection, HasManyAssociation) |
|
db045dbb
»
|
dhh |
2004-11-23 |
Initial |
401 |
end |
| |
402 |
|
|
6abda696
»
|
dhh |
2005-12-02 |
Added preliminary support f... |
403 |
add_deprecated_api_for_has_many(reflection.name) |
|
db045dbb
»
|
dhh |
2004-11-23 |
Initial |
404 |
end |
| |
405 |
|
|
cab24945
»
|
dhh |
2005-01-01 |
Updated documentation for a... |
406 |
# Adds the following methods for retrieval and query of a single associated object. |
|
db045dbb
»
|
dhh |
2004-11-23 |
Initial |
407 |
# +association+ is replaced with the symbol passed as the first argument, so |
|
823554ea
»
|
dhh |
2005-01-15 |
Added support for associati... |
408 |
# <tt>has_one :manager</tt> would add among others <tt>manager.nil?</tt>. |
|
db045dbb
»
|
dhh |
2004-11-23 |
Initial |
409 |
# * <tt>association(force_reload = false)</tt> - returns the associated object. Nil is returned if none is found. |
| |
410 |
# * <tt>association=(associate)</tt> - assigns the associate object, extracts the primary key, sets it as the foreign key, |
| |
411 |
# and saves the associate object. |
| |
412 |
# * <tt>association.nil?</tt> - returns true if there is no associated object. |
|
3b9e90a4
»
|
dhh |
2005-04-11 |
Moved build_association and... |
413 |
# * <tt>build_association(attributes = {})</tt> - returns a new object of the associated type that has been instantiated |
|
fd67e3be
»
|
dhh |
2005-01-25 |
The create and build method... |
414 |
# with +attributes+ and linked to this object through a foreign key but has not yet been saved. Note: This ONLY works if |
| |
415 |
# an association already exists. It will NOT work if the association is nil. |
|
3b9e90a4
»
|
dhh |
2005-04-11 |
Moved build_association and... |
416 |
# * <tt>create_association(attributes = {})</tt> - returns a new object of the associated type that has been instantiated |
|
db045dbb
»
|
dhh |
2004-11-23 |
Initial |
417 |
# with +attributes+ and linked to this object through a foreign key and that has already been saved (if it passed the validation). |
| |
418 |
# |
| |
419 |
# Example: An Account class declares <tt>has_one :beneficiary</tt>, which will add: |
|
3dfa56cc
»
|
dhh |
2005-06-26 |
Updated all references to t... |
420 |
# * <tt>Account#beneficiary</tt> (similar to <tt>Beneficiary.find(:first, :conditions => "account_id = #{id}")</tt>) |
|
db045dbb
»
|
dhh |
2004-11-23 |
Initial |
421 |
# * <tt>Account#beneficiary=(beneficiary)</tt> (similar to <tt>beneficiary.account_id = account.id; beneficiary.save</tt>) |
| |
422 |
# * <tt>Account#beneficiary.nil?</tt> |
|
3b9e90a4
»
|
dhh |
2005-04-11 |
Moved build_association and... |
423 |
# * <tt>Account#build_beneficiary</tt> (similar to <tt>Beneficiary.new("account_id" => id)</tt>) |
| |
424 |
# * <tt>Account#create_beneficiary</tt> (similar to <tt>b = Beneficiary.new("account_id" => id); b.save; b</tt>) |
| |
425 |
# |
|
db045dbb
»
|
dhh |
2004-11-23 |
Initial |
426 |
# The declaration can also include an options hash to specialize the behavior of the association. |
| |
427 |
# |
| |
428 |
# Options are: |
|
098fa943
»
|
dhh |
2005-02-07 |
Fixed documentation snafus ... |
429 |
# * <tt>:class_name</tt> - specify the class name of the association. Use it only if that name can't be inferred |
|
db045dbb
»
|
dhh |
2004-11-23 |
Initial |
430 |
# from the association name. So <tt>has_one :manager</tt> will by default be linked to the +Manager+ class, but |
| |
431 |
# if the real class name is +Person+, you'll have to specify it with this option. |
| |
432 |
# * <tt>:conditions</tt> - specify the conditions that the associated object must meet in order to be included as a "WHERE" |
| |
433 |
# sql fragment, such as "rank = 5". |
| |
434 |
# * <tt>:order</tt> - specify the order from which the associated object will be picked at the top. Specified as |
| |
435 |
# an "ORDER BY" sql fragment, such as "last_name, first_name DESC" |
|
a8eea0b0
»
|
dhh |
2005-10-26 |
Fix docs (closes #2491) |
436 |
# * <tt>:dependent</tt> - if set to :destroy (or true) all the associated objects are destroyed when this object is. Also, |
|
7267db58
»
|
dhh |
2005-03-06 |
Added destruction of depend... |
437 |
# association is assigned. |
|
db045dbb
»
|
dhh |
2004-11-23 |
Initial |
438 |
# * <tt>:foreign_key</tt> - specify the foreign key used for the association. By default this is guessed to be the name |
| |
439 |
# of this class in lower-case and "_id" suffixed. So a +Person+ class that makes a has_one association will use "person_id" |
| |
440 |
# as the default foreign_key. |
|
49c801b7
»
|
dhh |
2005-11-06 |
Added :include as an option... |
441 |
# * <tt>:include</tt> - specify second-order associations that should be eager loaded when this object is loaded. |
|
db045dbb
»
|
dhh |
2004-11-23 |
Initial |
442 |
# |
| |
443 |
# Option examples: |
|
57565b35
»
|
NZKoz |
2006-03-09 |
format fix for locking [Mic... |
444 |
# has_one :credit_card, :dependent => :destroy # destroys the associated credit card |
| |
445 |
# has_one :credit_card, :dependent => :nullify # updates the associated records foriegn key value to null rather than destroying it |
|
db045dbb
»
|
dhh |
2004-11-23 |
Initial |
446 |
# has_one :last_comment, :class_name => "Comment", :order => "posted_on" |
| |
447 |
# has_one :project_manager, :class_name => "Person", :conditions => "role = 'project_manager'" |
| |
448 |
def has_one(association_id, options = {}) |
|
6abda696
»
|
dhh |
2005-12-02 |
Added preliminary support f... |
449 |
reflection = create_has_one_reflection(association_id, options) |
|
db045dbb
»
|
dhh |
2004-11-23 |
Initial |
450 |
|
|
823554ea
»
|
dhh |
2005-01-15 |
Added support for associati... |
451 |
module_eval do |
| |
452 |
after_save <<-EOF |
|
6abda696
»
|
dhh |
2005-12-02 |
Added preliminary support f... |
453 |
association = instance_variable_get("@#{reflection.name}") |
|
3ebde40c
»
|
dhh |
2005-01-18 |
Cleanup the proxy rollback ... |
454 |
unless association.nil? |
|
6abda696
»
|
dhh |
2005-12-02 |
Added preliminary support f... |
455 |
association["#{reflection.primary_key_name}"] = id |
|
823554ea
»
|
dhh |
2005-01-15 |
Added support for associati... |
456 |
association.save(true) |
| |
457 |
end |
| |
458 |
EOF |
| |
459 |
end |
| |
460 |
|
|
6abda696
»
|
dhh |
2005-12-02 |
Added preliminary support f... |
461 |
association_accessor_methods(reflection, HasOneAssociation) |
| |
462 |
association_constructor_method(:build, reflection, HasOneAssociation) |
| |
463 |
association_constructor_method(:create, reflection, HasOneAssociation) |
|
db045dbb
»
|
dhh |
2004-11-23 |
Initial |
464 |
|
|
6abda696
»
|
dhh |
2005-12-02 |
Added preliminary support f... |
465 |
configure_dependency_for_has_one(reflection) |
|
823554ea
»
|
dhh |
2005-01-15 |
Added support for associati... |
466 |
|
| |
467 |
# deprecated api |
|
6abda696
»
|
dhh |
2005-12-02 |
Added preliminary support f... |
468 |
deprecated_has_association_method(reflection.name) |
| |
469 |
deprecated_association_comparison_method(reflection.name, reflection.class_name) |
|
db045dbb
»
|
dhh |
2004-11-23 |
Initial |
470 |
end |
| |
471 |
|
|
cab24945
»
|
dhh |
2005-01-01 |
Updated documentation for a... |
472 |
# Adds the following methods for retrieval and query for a single associated object that this object holds an id to. |
|
db045dbb
»
|
dhh |
2004-11-23 |
Initial |
473 |
# +association+ is replaced with the symbol passed as the first argument, so |
|
823554ea
»
|
dhh |
2005-01-15 |
Added support for associati... |
474 |
# <tt>belongs_to :author</tt> would add among others <tt>author.nil?</tt>. |
|
db045dbb
»
|
dhh |
2004-11-23 |
Initial |
475 |
# * <tt>association(force_reload = false)</tt> - returns the associated object. Nil is returned if none is found. |
| |
476 |
# * <tt>association=(associate)</tt> - assigns the associate object, extracts the primary key, and sets it as the foreign key. |
| |
477 |
# * <tt>association.nil?</tt> - returns true if there is no associated object. |
|
3b9e90a4
»
|
dhh |
2005-04-11 |
Moved build_association and... |
478 |
# * <tt>build_association(attributes = {})</tt> - returns a new object of the associated type that has been instantiated |
|
823554ea
»
|
dhh |
2005-01-15 |
Added support for associati... |
479 |
# with +attributes+ and linked to this object through a foreign key but has not yet been saved. |
|
3b9e90a4
»
|
dhh |
2005-04-11 |
Moved build_association and... |
480 |
# * <tt>create_association(attributes = {})</tt> - returns a new object of the associated type that has been instantiated |
|
823554ea
»
|
dhh |
2005-01-15 |
Added support for associati... |
481 |
# with +attributes+ and linked to this object through a foreign key and that has already been saved (if it passed the validation). |
|
db045dbb
»
|
dhh |
2004-11-23 |
Initial |
482 |
# |
|
e58f2675
»
|
dhh |
2005-02-23 |
Documentation fixes #694 |
483 |
# Example: A Post class declares <tt>belongs_to :author</tt>, which will add: |
|
db045dbb
»
|
dhh |
2004-11-23 |
Initial |
484 |
# * <tt>Post#author</tt> (similar to <tt>Author.find(author_id)</tt>) |
| |
485 |
# * <tt>Post#author=(author)</tt> (similar to <tt>post.author_id = author.id</tt>) |
| |
486 |
# * <tt>Post#author?</tt> (similar to <tt>post.author == some_author</tt>) |
| |
487 |
# * <tt>Post#author.nil?</tt> |
|
1d4d7217
»
|
dhh |
2005-06-21 |
Fixed docs #856 |
488 |
# * <tt>Post#build_author</tt> (similar to <tt>post.author = Author.new</tt>) |
| |
489 |
# * <tt>Post#create_author</tt> (similar to <tt>post.author = Author.new; post.author.save; post.author</tt>) |
|
db045dbb
»
|
dhh |
2004-11-23 |
Initial |
490 |
# The declaration can also include an options hash to specialize the behavior of the association. |
| |
491 |
# |
| |
492 |
# Options are: |
|
098fa943
»
|
dhh |
2005-02-07 |
Fixed documentation snafus ... |
493 |
# * <tt>:class_name</tt> - specify the class name of the association. Use it only if that name can't be inferred |
|
db045dbb
»
|
dhh |
2004-11-23 |
Initial |
494 |
# from the association name. So <tt>has_one :author</tt> will by default be linked to the +Author+ class, but |
| |
495 |
# if the real class name is +Person+, you'll have to specify it with this option. |
| |
496 |
# * <tt>:conditions</tt> - specify the conditions that the associated object must meet in order to be included as a "WHERE" |
| |
497 |
# sql fragment, such as "authorized = 1". |
| |
498 |
# * <tt>:order</tt> - specify the order from which the associated object will be picked at the top. Specified as |
| |
499 |
# an "ORDER BY" sql fragment, such as "last_name, first_name DESC" |
| |
500 |
# * <tt>:foreign_key</tt> - specify the foreign key used for the association. By default this is guessed to be the name |
| |
501 |
# of the associated class in lower-case and "_id" suffixed. So a +Person+ class that makes a belongs_to association to a |
| |
502 |
# +Boss+ class will use "boss_id" as the default foreign_key. |
| |
503 |
# * <tt>:counter_cache</tt> - caches the number of belonging objects on the associate class through use of increment_counter |
| |
504 |
# and decrement_counter. The counter cache is incremented when an object of this class is created and decremented when it's |
| |
505 |
# destroyed. This requires that a column named "#{table_name}_count" (such as comments_count for a belonging Comment class) |
|
87898bad
»
|
jamis |
2006-03-09 |
Allow counter_cache to acce... |
506 |
# is used on the associate class (such as a Post class). You can also specify a custom counter cache column by given that |
| |
507 |
# name instead of a true/false value to this option (e.g., <tt>:counter_cache => :my_custom_counter</tt>.) |
|
49c801b7
»
|
dhh |
2005-11-06 |
Added :include as an option... |
508 |
# * <tt>:include</tt> - specify second-order associations that should be eager loaded when this object is loaded. |
|
db045dbb
»
|
dhh |
2004-11-23 |
Initial |
509 |
# |
| |
510 |
# Option examples: |
| |
511 |
# belongs_to :firm, :foreign_key => "client_of" |
| |
512 |
# belongs_to :author, :class_name => "Person", :foreign_key => "author_id" |
| |
513 |
# belongs_to :valid_coupon, :class_name => "Coupon", :foreign_key => "coupon_id", |
| |
514 |
# :conditions => 'discounts > #{payments_count}' |
|
823554ea
»
|
dhh |
2005-01-15 |
Added support for associati... |
515 |
def belongs_to(association_id, options = {}) |
|
6abda696
»
|
dhh |
2005-12-02 |
Added preliminary support f... |
516 |
reflection = create_belongs_to_reflection(association_id, options) |
| |
517 |
|
| |
518 |
if reflection.options[:polymorphic] |
| |
519 |
association_accessor_methods(reflection, BelongsToPolymorphicAssociation) |
|
57b7532b
»
|
dhh |
2005-12-01 |
Work-in progress for provid... |
520 |
|
| |
521 |
module_eval do |
| |
522 |
before_save <<-EOF |
|
6abda696
»
|
dhh |
2005-12-02 |
Added preliminary support f... |
523 |
association = instance_variable_get("@#{reflection.name}") |
|
57b7532b
»
|
dhh |
2005-12-01 |
Work-in progress for provid... |
524 |
if !association.nil? |
| |
525 |
if association.new_record? |
| |
526 |
association.save(true) |
| |
527 |
end |
| |
528 |
|
| |
529 |
if association.updated? |
|
6abda696
»
|
dhh |
2005-12-02 |
Added preliminary support f... |
530 |
self["#{reflection.primary_key_name}"] = association.id |
|
0859779d
»
|
technoweenie |
2006-03-15 |
Allow :dependent options to... |
531 |
self["#{reflection.options[:foreign_type]}"] = association.class.base_class.name.to_s |
|
57b7532b
»
|
dhh |
2005-12-01 |
Work-in progress for provid... |
532 |
end |
|
5213a1f7
»
|
jamis |
2005-09-20 |
Fixed saving a record with ... |
533 |
end |
|
57b7532b
»
|
dhh |
2005-12-01 |
Work-in progress for provid... |
534 |
EOF |
| |
535 |
end |
| |
536 |
else |
|
6abda696
»
|
dhh |
2005-12-02 |
Added preliminary support f... |
537 |
association_accessor_methods(reflection, BelongsToAssociation) |
| |
538 |
association_constructor_method(:build, reflection, BelongsToAssociation) |
| |
539 |
association_constructor_method(:create, reflection, BelongsToAssociation) |
|
57b7532b
»
|
dhh |
2005-12-01 |
Work-in progress for provid... |
540 |
|
| |
541 |
module_eval do |
| |
542 |
before_save <<-EOF |
|
6abda696
»
|
dhh |
2005-12-02 |
Added preliminary support f... |
543 |
association = instance_variable_get("@#{reflection.name}") |
|
57b7532b
»
|
dhh |
2005-12-01 |
Work-in progress for provid... |
544 |
if !association.nil? |
| |
545 |
if association.new_record? |
| |
546 |
association.save(true) |
| |
547 |
end |
| |
548 |
|
| |
549 |
if association.updated? |
|
6abda696
»
|
dhh |
2005-12-02 |
Added preliminary support f... |
550 |
self["#{reflection.primary_key_name}"] = association.id |
|
57b7532b
»
|
dhh |
2005-12-01 |
Work-in progress for provid... |
551 |
end |
| |
552 |
end |
| |
553 |
EOF |
| |
554 |
end |
|
823554ea
»
|
dhh |
2005-01-15 |
Added support for associati... |
555 |
|
|
57b7532b
»
|
dhh |
2005-12-01 |
Work-in progress for provid... |
556 |
# deprecated api |
|
6abda696
»
|
dhh |
2005-12-02 |
Added preliminary support f... |
557 |
deprecated_has_association_method(reflection.name) |
| |
558 |
deprecated_association_comparison_method(reflection.name, reflection.class_name) |
|
57b7532b
»
|
dhh |
2005-12-01 |
Work-in progress for provid... |
559 |
end |
|
964b67dd
»
|
jamis |
2006-03-04 |
Make counter_cache work wit... |
560 |
|
| |
561 |
if options[:counter_cache] |
|
87898bad
»
|
jamis |
2006-03-09 |
Allow counter_cache to acce... |
562 |
cache_column = options[:counter_cache] == true ? |
| |
563 |
"#{self.to_s.underscore.pluralize}_count" : |
| |
564 |
options[:counter_cache] |
| |
565 |
|
|
964b67dd
»
|
jamis |
2006-03-04 |
Make counter_cache work wit... |
566 |
module_eval( |
|
87898bad
»
|
jamis |
2006-03-09 |
Allow counter_cache to acce... |
567 |
"after_create '#{reflection.name}.class.increment_counter(\"#{cache_column}\", #{reflection.primary_key_name})" + |
|
964b67dd
»
|
jamis |
2006-03-04 |
Make counter_cache work wit... |
568 |
" unless #{reflection.name}.nil?'" |
| |
569 |
) |
| |
570 |
|
| |
571 |
module_eval( |
|
87898bad
»
|
jamis |
2006-03-09 |
Allow counter_cache to acce... |
572 |
"before_destroy '#{reflection.name}.class.decrement_counter(\"#{cache_column}\", #{reflection.primary_key_name})" + |
|
964b67dd
»
|
jamis |
2006-03-04 |
Make counter_cache work wit... |
573 |
" unless #{reflection.name}.nil?'" |
| |
574 |
) |
| |
575 |
end |
|
823554ea
»
|
dhh |
2005-01-15 |
Added support for associati... |
576 |
end |
| |
577 |
|
|
db045dbb
»
|
dhh |
2004-11-23 |
Initial |
578 |
# Associates two classes via an intermediate join table. Unless the join table is explicitly specified as |
| |
579 |
# an option, it is guessed using the lexical order of the class names. So a join between Developer and Project |
| |
580 |
# will give the default join table name of "developers_projects" because "D" outranks "P". |
| |
581 |
# |
| |
582 |
# Any additional fields added to the join table will be placed as attributes when pulling records out through |
| |
583 |
# has_and_belongs_to_many associations. This is helpful when have information about the association itself |
|
cab24945
»
|
dhh |
2005-01-01 |
Updated documentation for a... |
584 |
# that you want available on retrieval. Note that any fields in the join table will override matching field names |
| |
585 |
# in the two joined tables. As a consequence, having an "id" field in the join table usually has the undesirable |
| |
586 |
# result of clobbering the "id" fields in either of the other two tables. |
|
db045dbb
»
|
dhh |
2004-11-23 |
Initial |
587 |
# |
|
cab24945
»
|
dhh |
2005-01-01 |
Updated documentation for a... |
588 |
# Adds the following methods for retrieval and query. |
|
db045dbb
»
|
dhh |
2004-11-23 |
Initial |
589 |
# +collection+ is replaced with the symbol passed as the first argument, so |
|
4eab3758
»
|
dhh |
2005-02-23 |
Finished polishing API docs |
590 |
# <tt>has_and_belongs_to_many :categories</tt> would add among others <tt>categories.empty?</tt>. |
|
db045dbb
»
|
dhh |
2004-11-23 |
Initial |
591 |
# * <tt>collection(force_reload = false)</tt> - returns an array of all the associated objects. |
| |
592 |
# An empty array is returned if none is found. |
| |
593 |
# * <tt>collection<<(object, ...)</tt> - adds one or more objects to the collection by creating associations in the join table |
| |
594 |
# (collection.push and collection.concat are aliases to this method). |
| |
595 |
# * <tt>collection.push_with_attributes(object, join_attributes)</tt> - adds one to the collection by creating an association in the join table that |
| |
596 |
# also holds the attributes from <tt>join_attributes</tt> (should be a hash with the column names as keys). This can be used to have additional |
| |
597 |
# attributes on the join, which will be injected into the associated objects when they are retrieved through the collection. |
| |
598 |
# (collection.concat_with_attributes is an alias to this method). |
| |
599 |
# * <tt>collection.delete(object, ...)</tt> - removes one or more objects from the collection by removing their associations from the join table. |
| |
600 |
# This does not destroy the objects. |
|
bfe6a759
»
|
dhh |
2005-06-15 |
Added actual database-chang... |
601 |
# * <tt>collection=objects</tt> - replaces the collections content by deleting and adding objects as appropriate. |
| |
602 |
# * <tt>collection_singular_ids=ids</tt> - replace the collection by the objects identified by the primary keys in +ids+ |
|
db045dbb
»
|
dhh |
2004-11-23 |
Initial |
603 |
# * <tt>collection.clear</tt> - removes every object from the collection. This does not destroy the objects. |
| |
604 |
# * <tt>collection.empty?</tt> - returns true if there are no associated objects. |
| |
605 |
# * <tt>collection.size</tt> - returns the number of associated objects. |
|
823554ea
»
|
dhh |
2005-01-15 |
Added support for associati... |
606 |
# * <tt>collection.find(id)</tt> - finds an associated object responding to the +id+ and that |
| |
607 |
# meets the condition that it has to be associated with this object. |
|
db045dbb
»
|
dhh |
2004-11-23 |
Initial |
608 |
# |
| |
609 |
# Example: An Developer class declares <tt>has_and_belongs_to_many :projects</tt>, which will add: |
| |
610 |
# * <tt>Developer#projects</tt> |
| |
611 |
# * <tt>Developer#projects<<</tt> |
|
823554ea
»
|
dhh |
2005-01-15 |
Added support for associati... |
612 |
# * <tt>Developer#projects.push_with_attributes</tt> |
|
db045dbb
»
|
dhh |
2004-11-23 |
Initial |
613 |
# * <tt>Developer#projects.delete</tt> |
|
bfe6a759
»
|
dhh |
2005-06-15 |
Added actual database-chang... |
614 |
# * <tt>Developer#projects=</tt> |
| |
615 |
# * <tt>Developer#project_ids=</tt> |
|
db045dbb
»
|
dhh |
2004-11-23 |
Initial |
616 |
# * <tt>Developer#projects.clear</tt> |
| |
617 |
# * <tt>Developer#projects.empty?</tt> |
| |
618 |
# * <tt>Developer#projects.size</tt> |
| |
619 |
# * <tt>Developer#projects.find(id)</tt> |
| |
620 |
# The declaration may include an options hash to specialize the behavior of the association. |
| |
621 |
# |
| |
622 |
# Options are: |
|
098fa943
»
|
dhh |
2005-02-07 |
Fixed documentation snafus ... |
623 |
# * <tt>:class_name</tt> - specify the class name of the association. Use it only if that name can't be inferred |
|
db045dbb
»
|
dhh |
2004-11-23 |
Initial |
624 |
# from the association name. So <tt>has_and_belongs_to_many :projects</tt> will by default be linked to the |
| |
625 |
# +Project+ class, but if the real class name is +SuperProject+, you'll have to specify it with this option. |
| |
626 |
# * <tt>:join_table</tt> - specify the name of the join table if the default based on lexical order isn't what you want. |
| |
627 |
# WARNING: If you're overwriting the table name of either class, the table_name method MUST be declared underneath any |
| |
628 |
# has_and_belongs_to_many declaration in order to work. |
| |
629 |
# * <tt>:foreign_key</tt> - specify the foreign key used for the association. By default this is guessed to be the name |
| |
630 |
# of this class in lower-case and "_id" suffixed. So a +Person+ class that makes a has_and_belongs_to_many association |
| |
631 |
# will use "person_id" as the default foreign_key. |
| |
632 |
# * <tt>:association_foreign_key</tt> - specify the association foreign key used for the association. By default this is |
|
a8eea0b0
»
|
dhh |
2005-10-26 |
Fix docs (closes #2491) |
633 |
# guessed to be the name of the associated class in lower-case and "_id" suffixed. So if the associated class is +Project+, |
| |
634 |
# the has_and_belongs_to_many association will use "project_id" as the default association foreign_key. |
|
db045dbb
»
|
dhh |
2004-11-23 |
Initial |
635 |
# * <tt>:conditions</tt> - specify the conditions that the associated object must meet in order to be included as a "WHERE" |
| |
636 |
# sql fragment, such as "authorized = 1". |
| |
637 |
# * <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" |
| |
638 |
# * <tt>:uniq</tt> - if set to true, duplicate associated objects will be ignored by accessors and query methods |
| |
639 |
# * <tt>:finder_sql</tt> - overwrite the default generated SQL used to fetch the association with a manual one |
| |
640 |
# * <tt>:delete_sql</tt> - overwrite the default generated SQL used to remove links between the associated |
| |
641 |
# classes with a manual one |
| |
642 |
# * <tt>:insert_sql</tt> - overwrite the default generated SQL used to add links between the associated classes |
| |
643 |
# with a manual one |
|
8c512a1c
»
|
dhh |
2005-11-03 |
Added extension capabilitie... |
644 |
# * <tt>:extend</tt> - anonymous module for extending the proxy, see "Association extensions". |
|
49c801b7
»
|
dhh |
2005-11-06 |
Added :include as an option... |
645 |
# * <tt>:include</tt> - specify second-order associations that should be eager loaded when the collection is loaded. |
|
e5d9ad3e
»
|
dhh |
2005-12-12 |
Added option inheritance fo... |
646 |
# * <tt>:group</tt>: An attribute name by which the result should be grouped. Uses the GROUP BY SQL-clause. |
| |
647 |
# * <tt>:limit</tt>: An integer determining the limit on the number of rows that should be returned. |
| |
648 |
# * <tt>:offset</tt>: An integer determining the offset from where the rows should be fetched. So at 5, it would skip the first 4 rows. |
| |
649 |
# * <tt>:select</tt>: By default, this is * as in SELECT * FROM, but can be changed if you for example want to do a join, but not |
| |
650 |
# include the joined columns. |
|
db045dbb
»
|
dhh |
2004-11-23 |
Initial |
651 |
# |
| |
652 |
# Option examples: |
| |
653 |
# has_and_belongs_to_many :projects |
|
49c801b7
»
|
dhh |
2005-11-06 |
Added :include as an option... |
654 |
# has_and_belongs_to_many :projects, :include => [ :milestones, :manager ] |
|
db045dbb
»
|
dhh |
2004-11-23 |
Initial |
655 |
# has_and_belongs_to_many :nations, :class_name => "Country" |
| |
656 |
# has_and_belongs_to_many :categories, :join_table => "prods_cats" |
|
190e0464
»
|
dhh |
2005-05-19 |
Fixed that :delete_sql in h... |
657 |
# has_and_belongs_to_many :active_projects, :join_table => 'developers_projects', :delete_sql => |
| |
658 |
# 'DELETE FROM developers_projects WHERE active=1 AND developer_id = #{id} AND project_id = #{record.id}' |
|
c8dd66fd
»
|
dhh |
2005-11-06 |
Made association extensions... |
659 |
def has_and_belongs_to_many(association_id, options = {}, &extension) |
|
6abda696
»
|
dhh |
2005-12-02 |
Added preliminary support f... |
660 |
reflection = create_has_and_belongs_to_many_reflection(association_id, options, &extension) |
| |
661 |
|
| |
662 |
add_multiple_associated_save_callbacks(reflection.name) |
| |
663 |
collection_accessor_methods(reflection, HasAndBelongsToManyAssociation) |
|
db045dbb
»
|
dhh |
2004-11-23 |
Initial |
664 |
|
|
35b4bdcf
»
|
jeremy |
2005-11-08 |
Destroy associated has_and_... |
665 |
# Don't use a before_destroy callback since users' before_destroy |
| |
666 |
# callbacks will be executed after the association is wiped out. |
|
6abda696
»
|
dhh |
2005-12-02 |
Added preliminary support f... |
667 |
old_method = "destroy_without_habtm_shim_for_#{reflection.name}" |
|
35b4bdcf
»
|
jeremy |
2005-11-08 |
Destroy associated has_and_... |
668 |
class_eval <<-end_eval |
| |
669 |
alias_method :#{old_method}, :destroy_without_callbacks |
| |
670 |
def destroy_without_callbacks |
|
6abda696
»
|
dhh |
2005-12-02 |
Added preliminary support f... |
671 |
#{reflection.name}.clear |
|
35b4bdcf
»
|
jeremy |
2005-11-08 |
Destroy associated has_and_... |
672 |
#{old_method} |
| |
673 |
end |
| |
674 |
end_eval |
| |
675 |
|
|
6abda696
»
|
dhh |
2005-12-02 |
Added preliminary support f... |
676 |
add_association_callbacks(reflection.name, options) |
|
db045dbb
»
|
dhh |
2004-11-23 |
Initial |
677 |
|
| |
678 |
# deprecated api |
|
6abda696
»
|
dhh |
2005-12-02 |
Added preliminary support f... |
679 |
deprecated_collection_count_method(reflection.name) |
| |
680 |
deprecated_add_association_relation(reflection.name) |
| |
681 |
deprecated_remove_association_relation(reflection.name) |
| |
682 |
deprecated_has_collection_method(reflection.name) |
|
db045dbb
»
|
dhh |
2004-11-23 |
Initial |
683 |
end |
| |
684 |
|
| |
685 |
private |
| |
686 |
def join_table_name(first_table_name, second_table_name) |
| |
687 |
if first_table_name < second_table_name |
| |
688 |
join_table = "#{first_table_name}_#{second_table_name}" |
| |
689 |
else |
| |
690 |
join_table = "#{second_table_name}_#{first_table_name}" |
| |
691 |
end |
| |
692 |
|
| |
693 |
table_name_prefix + join_table + table_name_suffix |
| |
694 |
end |
| |
695 |
|
|
6abda696
»
|
dhh |
2005-12-02 |
Added preliminary support f... |
696 |
def association_accessor_methods(reflection, association_proxy_class) |
| |
697 |
define_method(reflection.name) do |*params| |
|
823554ea
»
|
dhh |
2005-01-15 |
Added support for associati... |
698 |
force_reload = params.first unless params.empty? |
|
6abda696
»
|
dhh |
2005-12-02 |
Added preliminary support f... |
699 |
association = instance_variable_get("@#{reflection.name}") |
| |
700 |
|
| |
701 |
if association.nil? || force_reload |
| |
702 |
association = association_proxy_class.new(self, reflection) |
|
bce0e149
»
|
dhh |
2005-01-18 |
Fixed that the belongs_to a... |
703 |
retval = association.reload |
| |
704 |
unless retval.nil? |
|
6abda696
»
|
dhh |
2005-12-02 |
Added preliminary support f... |
705 |
instance_variable_set("@#{reflection.name}", association) |
|
bce0e149
»
|
dhh |
2005-01-18 |
Fixed that the belongs_to a... |
706 |
else |
|
6abda696
»
|
dhh |
2005-12-02 |
Added preliminary support f... |
707 |
instance_variable_set("@#{reflection.name}", nil) |
|
bce0e149
»
|
dhh |
2005-01-18 |
Fixed that the belongs_to a... |
708 |
return nil |
| |
709 |
end |
| |
710 |
end |
| |
711 |
association |
| |
712 |
end |
| |
713 |
|
|
6abda696
»
|
dhh |
2005-12-02 |
Added preliminary support f... |
714 |
define_method("#{reflection.name}=") do |new_value| |
| |
715 |
association = instance_variable_get("@#{reflection.name}") |
|
3ebde40c
»
|
dhh |
2005-01-18 |
Cleanup the proxy rollback ... |
716 |
if association.nil? |
|
6abda696
»
|
dhh |
2005-12-02 |
Added preliminary support f... |
717 |
association = association_proxy_class.new(self, reflection) |
|
bce0e149
»
|
dhh |
2005-01-18 |
Fixed that the belongs_to a... |
718 |
end |
|
6abda696
»
|
dhh |
2005-12-02 |
Added preliminary support f... |
719 |
|
|
bce0e149
»
|
dhh |
2005-01-18 |
Fixed that the belongs_to a... |
720 |
association.replace(new_value) |
|
6abda696
»
|
dhh |
2005-12-02 |
Added preliminary support f... |
721 |
|
|
bce0e149
»
|
dhh |
2005-01-18 |
Fixed that the belongs_to a... |
722 |
unless new_value.nil? |
|
6abda696
»
|
dhh |
2005-12-02 |
Added preliminary support f... |
723 |
instance_variable_set("@#{reflection.name}", association) |
|
bce0e149
»
|
dhh |
2005-01-18 |
Fixed that the belongs_to a... |
724 |
else |
|
6abda696
»
|
dhh |
2005-12-02 |
Added preliminary support f... |
725 |
instance_variable_set("@#{reflection.name}", nil) |
|
3ebde40c
»
|
dhh |
2005-01-18 |
Cleanup the proxy rollback ... |
726 |
return nil |
|
bce0e149
»
|
dhh |
2005-01-18 |
Fixed that the belongs_to a... |
727 |
end |
|
6abda696
»
|
dhh |
2005-12-02 |
Added preliminary support f... |
728 |
|
|
bce0e149
»
|
dhh |
2005-01-18 |
Fixed that the belongs_to a... |
729 |
association |
| |
730 |
end |
|
f8783abf
»
|
dhh |
2005-04-03 |
Made eager loading work eve... |
731 |
|
|
6abda696
»
|
dhh |
2005-12-02 |
Added preliminary support f... |
732 |
define_method("set_#{reflection.name}_target") do |target| |
|
75b8ac80
»
|
dhh |
2005-04-14 |
Dont initialize the associa... |
733 |
return if target.nil? |
|
6abda696
»
|
dhh |
2005-12-02 |
Added preliminary support f... |
734 |
association = association_proxy_class.new(self, reflection) |
|
f8783abf
»
|
dhh |
2005-04-03 |
Made eager loading work eve... |
735 |
association.target = target |
|
6abda696
»
|
dhh |
2005-12-02 |
Added preliminary support f... |
736 |
instance_variable_set("@#{reflection.name}", association) |
|
f8783abf
»
|
dhh |
2005-04-03 |
Made eager loading work eve... |
737 |
end |
|
bce0e149
»
|
dhh |
2005-01-18 |
Fixed that the belongs_to a... |
738 |
end |
| |
739 |
|
|
6abda696
»
|
dhh |
2005-12-02 |
Added preliminary support f... |
740 |
def collection_reader_method(reflection, association_proxy_class) |
| |
741 |
define_method(reflection.name) do |*params| |
|
bce0e149
»
|
dhh |
2005-01-18 |
Fixed that the belongs_to a... |
742 |
force_reload = params.first unless params.empty? |
|
6abda696
»
|
dhh |
2005-12-02 |
Added preliminary support f... |
743 |
association = instance_variable_get("@#{reflection.name}") |
| |
744 |
|
|
823554ea
»
|
dhh |
2005-01-15 |
Added support for associati... |
745 |
unless association.respond_to?(:loaded?) |
|
6abda696
»
|
dhh |
2005-12-02 |
Added preliminary support f... |
746 |
association = association_proxy_class.new(self, reflection) |
| |
747 |
instance_variable_set("@#{reflection.name}", association) |
|
db045dbb
»
|
dhh |
2004-11-23 |
Initial |
748 |
end |
|
6abda696
»
|
dhh |
2005-12-02 |
Added preliminary support f... |
749 |
|
|
823554ea
»
|
dhh |
2005-01-15 |
Added support for associati... |
750 |
association.reload if force_reload |
|
6abda696
»
|
dhh |
2005-12-02 |
Added preliminary support f... |
751 |
|
|
823554ea
»
|
dhh |
2005-01-15 |
Added support for associati... |
752 |
association |
| |
753 |
end |
|
6abda696
»
|
dhh |
2005-12-02 |
Added preliminary support f... |
754 |
end |
|
db045dbb
»
|
dhh |
2004-11-23 |
Initial |
755 |
|
|
6abda696
»
|
dhh |
2005-12-02 |
Added preliminary support f... |
756 |
def collection_accessor_methods(reflection, association_proxy_class) |
| |
757 |
collection_reader_method(reflection, association_proxy_class) |
| |
758 |
|
| |
759 |
define_method("#{reflection.name}=") do |new_value| |
| |
760 |
association = instance_variable_get("@#{reflection.name}") |
|
823554ea
»
|
dhh |
2005-01-15 |
Added support for associati... |
761 |
unless association.respond_to?(:loaded?) |
|
6abda696
»
|
dhh |
2005-12-02 |
Added preliminary support f... |
762 |
association = association_proxy_class.new(self, reflection) |
| |
763 |
instance_variable_set("@#{reflection.name}", association) |
|
db045dbb
»
|
dhh |
2004-11-23 |
Initial |
764 |
end |
|
823554ea
»
|
dhh |
2005-01-15 |
Added support for associati... |
765 |
association.replace(new_value) |
| |
766 |
association |
| |
767 |
end |
|
bfe6a759
»
|
dhh |
2005-06-15 |
Added actual database-chang... |
768 |
|
|
6abda696
»
|
dhh |
2005-12-02 |
Added preliminary support f... |
769 |
define_method("#{reflection.name.to_s.singularize}_ids=") do |new_value| |
| |
770 |
send("#{reflection.name}=", reflection.class_name.constantize.find(new_value)) |
|
bfe6a759
»
|
dhh |
2005-06-15 |
Added actual database-chang... |
771 |
end |
|
db045dbb
»
|
dhh |
2004-11-23 |
Initial |
772 |
end |
| |
773 |
|
|
823554ea
»
|
dhh |
2005-01-15 |
Added support for associati... |
774 |
def require_association_class(class_name) |
| |
775 |
require_association(Inflector.underscore(class_name)) if class_name |
|
db045dbb
»
|
dhh |
2004-11-23 |
Initial |
776 |
end |
| |
777 |
|
|
823554ea
»
|
dhh |
2005-01-15 |
Added support for associati... |
778 |
def add_multiple_associated_save_callbacks(association_name) |
|
e19bd169
»
|
jeremy |
2005-10-01 |
Association validation does... |
779 |
method_name = "validate_associated_records_for_#{association_name}".to_sym |
| |
780 |
define_method(method_name) do |
| |
781 |
association = instance_variable_get("@#{association_name}") |
| |
782 |
if association.respond_to?(:loaded?) |
| |
783 |
if new_record? |
| |
784 |
association |
| |
785 |
else |
| |
786 |
association.select { |record| record.new_record? } |
| |
787 |
end.each do |record| |
| |
788 |
errors.add "#{association_name}" unless record.valid? |
|
db045dbb
»
|
dhh |
2004-11-23 |
Initial |
789 |
end |
|
e19bd169
»
|
jeremy |
2005-10-01 |
Association validation does... |
790 |
end |
|
823554ea
»
|
dhh |
2005-01-15 |
Added support for associati... |
791 |
end |
|
db045dbb
»
|
dhh |
2004-11-23 |
Initial |
792 |
|
|
e19bd169
»
|
jeremy |
2005-10-01 |
Association validation does... |
793 |
validate method_name |
|
c6e01f5b
»
|
csshsh |
2005-12-21 |
Fixed that saving a model w... |
794 |
before_save("@new_record_before_save = new_record?; true") |
|
e19bd169
»
|
jeremy |
2005-10-01 |
Association validation does... |
795 |
|
| |
796 |
after_callback = <<-end_eval |
| |
797 |
association = instance_variable_get("@#{association_name}") |
|
c66ac210
»
|
csshsh |
2005-11-23 |
Fixed bug where using updat... |
798 |
|
|
e19bd169
»
|
jeremy |
2005-10-01 |
Association validation does... |
799 |
if association.respond_to?(:loaded?) |
| |
800 |
if @new_record_before_save |
| |
801 |
records_to_save = association |
| |
802 |
else |
| |
803 |
records_to_save = association.select { |record| record.new_record? } |
|
823554ea
»
|
dhh |
2005-01-15 |
Added support for associati... |
804 |
end |
|
e19bd169
»
|
jeremy |
2005-10-01 |
Association validation does... |
805 |
records_to_save.each { |record| association.send(:insert_record, record) } |
| |
806 |
association.send(:construct_sql) # reconstruct the SQL queries now that we know the owner's id |
| |
807 |
end |
| |
808 |
end_eval |
|
c66ac210
»
|
csshsh |
2005-11-23 |
Fixed bug where using updat... |
809 |
|
|
e19bd169
»
|
jeremy |
2005-10-01 |
Association validation does... |
810 |
# Doesn't use after_save as that would save associations added in after_create/after_update twice |
| |
811 |
after_create(after_callback) |
| |
812 |
after_update(after_callback) |
|
db045dbb
»
|
dhh |
2004-11-23 |
Initial |
813 |
end |
|
abc895b8
»
|
dhh |
2005-04-03 |
Added new Base.find API and... |
814 |
|
|
6abda696
»
|
dhh |
2005-12-02 |
Added preliminary support f... |
815 |
def association_constructor_method(constructor, reflection, association_proxy_class) |
| |
816 |
define_method("#{constructor}_#{reflection.name}") do |*params| |
|
2bdaff4a
»
|
dhh |
2005-06-06 |
Added a second parameter to... |
817 |
attributees = params.first unless params.empty? |
| |
818 |
replace_existing = params[1].nil? ? true : params[1] |
|
6abda696
»
|
dhh |
2005-12-02 |
Added preliminary support f... |
819 |
association = instance_variable_get("@#{reflection.name}") |
|
3b9e90a4
»
|
dhh |
2005-04-11 |
Moved build_association and... |
820 |
|
| |
821 |
if association.nil? |
|
6abda696
»
|
dhh |
2005-12-02 |
Added preliminary support f... |
822 |
association = association_proxy_class.new(self, reflection) |
| |
823 |
instance_variable_set("@#{reflection.name}", association) |
|
3b9e90a4
»
|
dhh |
2005-04-11 |
Moved build_association and... |
824 |
end |
| |
825 |
|
|
bfe6a759
»
|
dhh |
2005-06-15 |
Added actual database-chang... |
826 |
if association_proxy_class == HasOneAssociation |
| |
827 |
association.send(constructor, attributees, replace_existing) |
| |
828 |
else |
| |
829 |
association.send(constructor, attributees) |
| |
830 |
end |
|
3b9e90a4
»
|
dhh |
2005-04-11 |
Moved build_association and... |
831 |
end |
| |
832 |
end |
|
d49a5fcb
»
|
NZKoz |
2006-02-09 |
* Fix pagination problems w... |
833 |
|
| |
834 |
def count_with_associations(options = {}) |
|
55854c41
»
|
dhh |
2006-03-04 |
Added cascading eager loadi... |
835 |
join_dependency = JoinDependency.new(self, options[:include]) |
| |
836 |
return count_by_sql(construct_counter_sql_with_included_associations(options, join_dependency)) |
|
d49a5fcb
»
|
NZKoz |
2006-02-09 |
* Fix pagination problems w... |
837 |
end |
|
abc895b8
»
|
dhh |
2005-04-03 |
Added new Base.find API and... |
838 |
|
| |
839 |
def find_with_associations(options = {}) |
|
55854c41
»
|
dhh |
2006-03-04 |
Added cascading eager loadi... |
840 |
join_dependency = JoinDependency.new(self, options[:include]) |
| |
841 |
rows = select_all_rows(options, join_dependency) |
| |
842 |
return join_dependency.instantiate(rows) |
|
3a7be80f
»
|
dhh |
2006-03-15 |
Change LEFT OUTER JOIN auth... |
843 |
end |
|
6f344000
»
|
dhh |
2005-04-19 |
Fixed order of loading in e... |
844 |
|
|
6abda696
»
|
dhh |
2005-12-02 |
Added preliminary support f... |
845 |
def configure_dependency_for_has_many(reflection) |
| |
846 |
if reflection.options[:dependent] && reflection.options[:exclusively_dependent] |
| |
847 |
raise ArgumentError, ':dependent and :exclusively_dependent are mutually exclusive options. You may specify one or the other.' |
| |
848 |
end |
| |
849 |
|
| |
850 |
if reflection.options[:exclusively_dependent] |
| |
851 |
reflection.options[:dependent] = :delete_all |
| |
852 |
#warn "The :exclusively_dependent option is deprecated. Please use :dependent => :delete_all instead.") |
| |
853 |
end |
| |
854 |
|
| |
855 |
# See HasManyAssociation#delete_records. Dependent associations |
| |
856 |
# delete children, otherwise foreign key is set to NULL. |
|
0859779d
»
|
technoweenie |
2006-03-15 |
Allow :dependent options to... |
857 |
|
| |
858 |
# Add polymorphic type if the :as option is present |
| |
859 |
dependent_conditions = %(#{reflection.primary_key_name} = \#{record.quoted_id}) |
| |
860 |
if reflection.options[:as] |
| |
861 |
dependent_conditions += " AND #{reflection.options[:as]}_type = '#{base_class.name}'" |
| |
862 |
end |
| |
863 |
|
|
6abda696
»
|
dhh |
2005-12-02 |
Added preliminary support f... |
864 |
case reflection.options[:dependent] |
| |
865 |
when :destroy, true |
| |
866 |
module_eval "before_destroy '#{reflection.name}.each { |o| o.destroy }'" |
| |
867 |
when :delete_all |
|
0859779d
»
|
technoweenie |
2006-03-15 |
Allow :dependent options to... |
868 |
module_eval "before_destroy { |record| #{reflection.class_name}.delete_all(%(#{dependent_conditions})) }" |
|
6abda696
»
|
dhh |
2005-12-02 |
Added preliminary support f... |
869 |
when :nullify |
|
0859779d
»
|
technoweenie |
2006-03-15 |
Allow :dependent options to... |
870 |
module_eval "before_destroy { |record| #{reflection.class_name}.update_all(%(#{reflection.primary_key_name} = NULL), %(#{dependent_conditions})) }" |
|
6abda696
»
|
dhh |
2005-12-02 |
Added preliminary support f... |
871 |
when nil, false |
| |
872 |
# pass |
| |
873 |
else |
|
57565b35
»
|
NZKoz |
2006-03-09 |
format fix for locking [Mic... |
874 |
raise ArgumentError, 'The :dependent option expects either :destroy, :delete_all, or :nullify' |
|
6abda696
»
|
dhh |
2005-12-02 |
Added preliminary support f... |
875 |
end |
| |
876 |
end |
| |
877 |
|
| |
878 |
def configure_dependency_for_has_one(reflection) |
| |
879 |
case reflection.options[:dependent] |
| |
880 |
when :destroy, true |
| |
881 |
module_eval "before_destroy '#{reflection.name}.destroy unless #{reflection.name}.nil?'" |
| |
882 |
when :nullify |
| |
883 |
module_eval "before_destroy '#{reflection.name}.update_attribute(\"#{reflection.primary_key_name}\", nil)'" |
| |
884 |
when nil, false |
| |
885 |
# pass |
| |
886 |
else |
| |
887 |
raise ArgumentError, "The :dependent option expects either :destroy or :nullify." |
| |
888 |
end |
| |
889 |
end |
| |
890 |
|
| |
891 |
|
| |
892 |
def add_deprecated_api_for_has_many(association_name) |
| |
893 |
deprecated_collection_count_method(association_name) |
| |
894 |
deprecated_add_association_relation(association_name) |
| |
895 |
deprecated_remove_association_relation(association_name) |
| |
896 |
deprecated_has_collection_method(association_name) |
| |
897 |
deprecated_find_in_collection_method(association_name) |
| |
898 |
deprecated_find_all_in_collection_method(association_name) |
| |
899 |
deprecated_collection_create_method(association_name) |
| |
900 |
deprecated_collection_build_method(association_name) |
| |
901 |
end |
| |
902 |
|
| |
903 |
def create_has_many_reflection(association_id, options, &extension) |
| |
904 |
options.assert_valid_keys( |
|
e5d9ad3e
»
|
dhh |
2005-12-12 |
Added option inheritance fo... |
905 |
:class_name, :table_name, :foreign_key, |
| |
906 |
:exclusively_dependent, :dependent, |
| |
907 |
:select, :conditions, :include, :order, :group, :limit, :offset, |
|
581f12b7
»
|
Tobias Lütke |
2005-12-20 |
removed :piggyback in favor... |
908 |
:as, :through, |
|
e5d9ad3e
»
|
dhh |
2005-12-12 |
Added option inheritance fo... |
909 |
:finder_sql, :counter_sql, |
| |
910 |
:before_add, :after_add, :before_remove, :after_remove, |
| |
911 |
:extend |
|
6abda696
»
|
dhh |
2005-12-02 |
Added preliminary support f... |
912 |
) |
| |
913 |
|
| |
914 |
options[:extend] = create_extension_module(association_id, extension) if block_given? |
| |
915 |
|
|
8203a2af
»
|
dhh |
2006-02-26 |
Dont require association cl... |
916 |
create_reflection(:has_many, association_id, options, self) |
|
6abda696
»
|
dhh |
2005-12-02 |
Added preliminary support f... |
917 |
end |
| |
918 |
|
| |
919 |
def create_has_one_reflection(association_id, options) |
| |
920 |
options.assert_valid_keys( |
|
b3065a51
»
|
jeremy |
2006-02-09 |
Polymorphic join support fo... |
921 |
:class_name, :foreign_key, :remote, :conditions, :order, :include, :dependent, :counter_cache, :extend, :as |
|
6abda696
»
|
dhh |
2005-12-02 |
Added preliminary support f... |
922 |
) |
| |
923 |
|
|
8203a2af
»
|
dhh |
2006-02-26 |
Dont require association cl... |
924 |
create_reflection(:has_one, association_id, options, self) |
|
6abda696
»
|
dhh |
2005-12-02 |
Added preliminary support f... |
925 |
end |
| |
926 |
|
| |
927 |
def create_belongs_to_reflection(association_id, options) |
| |
928 |
options.assert_valid_keys( |
| |
929 |
:class_name, :foreign_key, :foreign_type, :remote, :conditions, :order, :include, :dependent, |
| |
930 |
:counter_cache, :extend, :polymorphic |
| |
931 |
) |
| |
932 |
|
| |
933 |
reflection = create_reflection(:belongs_to, association_id, options, self) |
| |
934 |
|
| |
935 |
if options[:polymorphic] |
| |
936 |
reflection.options[:foreign_type] ||= reflection.class_name.underscore + "_type" |
| |
937 |
end |
| |
938 |
|
| |
939 |
reflection |
| |
940 |
end |
| |
941 |
|
| |
942 |
def create_has_and_belongs_to_many_reflection(association_id, options, &extension) |
| |
943 |
options.assert_valid_keys( |
|
e5d9ad3e
»
|
dhh |
2005-12-12 |
Added option inheritance fo... |
944 |
:class_name, :table_name, :join_table, :foreign_key, :association_foreign_key, |
| |
945 |
:select, :conditions, :include, :order, :group, :limit, :offset, |
| |
946 |
:finder_sql, :delete_sql, :insert_sql, :uniq, |
| |
947 |
:before_add, :after_add, :before_remove, :after_remove, |
| |
948 |
:extend |
|
6abda696
»
|
dhh |
2005-12-02 |
Added preliminary support f... |
949 |
) |
| |
950 |
|
| |
951 |
options[:extend] = create_extension_module(association_id, extension) if block_given? |
| |
952 |
|
| |
953 |
reflection = create_reflection(:has_and_belongs_to_many, association_id, options, self) |
| |
954 |
|
| |
955 |
reflection.options[:join_table] ||= join_table_name(undecorated_table_name(self.to_s), undecorated_table_name(reflection.class_name)) |
| |
956 |
|
| |
957 |
reflection |
| |
958 |
end |
| |
959 |
|
|
49d0f0cb
»
|
dhh |
2005-04-18 |
Speeded up eager loading a ... |
960 |
def reflect_on_included_associations(associations) |
|
251a5d45
»
|
seckar |
2005-09-18 |
Fix eager loading error mes... |
961 |
[ associations ].flatten.collect { |association| reflect_on_association(association.to_s.intern) } |
|
49d0f0cb
»
|
dhh |
2005-04-18 |
Speeded up eager loading a ... |
962 |
end |
| |
963 |
|
|
5b9b904f
»
|
dhh |
2005-07-10 |
Added support for limit and... |
964 |
def guard_against_unlimitable_reflections(reflections, options) |
| |
965 |
if (options[:offset] || options[:limit]) && !using_limitable_reflections?(reflections) |
| |
966 |
raise( |
| |
967 |
ConfigurationError, |
| |
968 |
"You can not use offset and limit together with has_many or has_and_belongs_to_many associations" |
| |
969 |
) |
| |
970 |
end |
| |
971 |
end |
| |
972 |
|
|
55854c41
»
|
dhh |
2006-03-04 |
Added cascading eager loadi... |
973 |
def select_all_rows(options, join_dependency) |
|
49d0f0cb
»
|
dhh |
2005-04-18 |
Speeded up eager loading a ... |
974 |
connection.select_all( |
|
55854c41
»
|
dhh |
2006-03-04 |
Added cascading eager loadi... |
975 |
construct_finder_sql_with_included_associations(options, join_dependency), |
|
49d0f0cb
»
|
dhh |
2005-04-18 |
Speeded up eager loading a ... |
976 |
"#{name} Load Including Associations" |
| |
977 |
) |
| |
978 |
end |
|
d49a5fcb
»
|
NZKoz |
2006-02-09 |
* Fix pagination problems w... |
979 |
|
|
55854c41
»
|
dhh |
2006-03-04 |
Added cascading eager loadi... |
980 |
def construct_counter_sql_with_included_associations(options, join_dependency) |
|
d49a5fcb
»
|
NZKoz |
2006-02-09 |
* Fix pagination problems w... |
981 |
sql = "SELECT COUNT(DISTINCT #{table_name}.#{primary_key})" |
| |
982 |
|
| |
983 |
# A (slower) workaround if we're using a backend, like sqlite, that doesn't support COUNT DISTINCT. |
| |
984 |
if !Base.connection.supports_count_distinct? |
| |
985 |
sql = "SELECT COUNT(*) FROM (SELECT DISTINCT #{table_name}.#{primary_key}" |
| |
986 |
end |
| |
987 |
|
| |
988 |
sql << " FROM #{table_name} " |
|
55854c41
»
|
dhh |
2006-03-04 |
Added cascading eager loadi... |
989 |
sql << join_dependency.join_associations.collect{|join| join.association_join }.join |
|
d49a5fcb
»
|
NZKoz |
2006-02-09 |
* Fix pagination problems w... |
990 |
sql << "#{options[:joins]} " if options[:joins] |
| |
991 |
|
| |
992 |
add_conditions!(sql, options[:conditions]) |
|
55854c41
»
|
dhh |
2006-03-04 |
Added cascading eager loadi... |
993 |
add_limited_ids_condition!(sql, options, join_dependency) if !using_limitable_reflections?(join_dependency.reflections) && options[:limit] |
|
d49a5fcb
»
|
NZKoz |
2006-02-09 |
* Fix pagination problems w... |
994 |
|
|
55854c41
»
|
dhh |
2006-03-04 |
Added cascading eager loadi... |
995 |
add_limit!(sql, options) if using_limitable_reflections?(join_dependency.reflections) |
|
d49a5fcb
»
|
NZKoz |
2006-02-09 |
* Fix pagination problems w... |
996 |
|
| |
997 |
if !Base.connection.supports_count_distinct? |
| |
998 |
sql << ")" |
| |
999 |
end |
| |
1000 |
|
| |
1001 |
return sanitize_sql(sql) |
| |
1002 |
end |
|
057cf491
»
|
dhh |
2005-04-10 |
Added support for has_and_b... |
1003 |
|
|
55854c41
»
|
dhh |
2006-03-04 |
Added cascading eager loadi... |
1004 |
def construct_finder_sql_with_included_associations(options, join_dependency) |
| |
1005 |
sql = "SELECT #{column_aliases(join_dependency)} FROM #{options[:from] || table_name} " |
| |
1006 |
sql << join_dependency.join_associations.collect{|join| join.association_join }.join |
|
efb55d1c
»
|
dhh |
2005-04-03 |
Allow order, conditions, an... |
1007 |
sql << "#{options[:joins]} " if options[:joins] |
|
d49a5fcb
»
|
NZKoz |
2006-02-09 |
* Fix pagination problems w... |
1008 |
|
|
efb55d1c
»
|
dhh |
2005-04-03 |
Allow order, conditions, an... |
1009 |
add_conditions!(sql, options[:conditions]) |
|
55854c41
»
|
dhh |
2006-03-04 |
Added cascading eager loadi... |
1010 |
add_limited_ids_condition!(sql, options, join_dependency) if !using_limitable_reflections?(join_dependency.reflections) && options[:limit] |
|
851dd080
»
|
dhh |
2005-10-18 |
Added support for using lim... |
1011 |
|
|
efb55d1c
»
|
dhh |
2005-04-03 |
Allow order, conditions, an... |
1012 |
sql << "ORDER BY #{options[:order]} " if options[:order] |
|
d49a5fcb
»
|
NZKoz |
2006-02-09 |
* Fix pagination problems w... |
1013 |
|
|
55854c41
»
|
dhh |
2006-03-04 |
Added cascading eager loadi... |
1014 |
add_limit!(sql, options) if using_limitable_reflections?(join_dependency.reflections) |
|
d49a5fcb
»
|
NZKoz |
2006-02-09 |
* Fix pagination problems w... |
1015 |
|
|
abc895b8
»
|
dhh |
2005-04-03 |
Added new Base.find API and... |
1016 |
return sanitize_sql(sql) |
| |
1017 |
end |
|
d49a5fcb
»
|
NZKoz |
2006-02-09 |
* Fix pagination problems w... |
1018 |
|
|
55854c41
»
|
dhh |
2006-03-04 |
Added cascading eager loadi... |
1019 |
def add_limited_ids_condition!(sql, options, join_dependency) |
| |
1020 |
unless (id_list = select_limited_ids_list(options, join_dependency)).empty? |
|
851dd080
»
|
dhh |
2005-10-18 |
Added support for using lim... |
1021 |
sql << "#{condition_word(sql)} #{table_name}.#{primary_key} IN (#{id_list}) " |
| |
1022 |
end |
| |
1023 |
end |
|
d49a5fcb
»
|
NZKoz |
2006-02-09 |
* Fix pagination problems w... |
1024 |
|
|
55854c41
»
|
dhh |
2006-03-04 |
Added cascading eager loadi... |
1025 |
def select_limited_ids_list(options, join_dependency) |
|
851dd080
»
|
dhh |
2005-10-18 |
Added support for using lim... |
1026 |
connection.select_values( |
|
55854c41
»
|
dhh |
2006-03-04 |
Added cascading eager loadi... |
1027 |
construct_finder_sql_for_association_limiting(options, join_dependency), |
|
851dd080
»
|
dhh |
2005-10-18 |
Added support for using lim... |
1028 |
"#{name} Load IDs For Limited Eager Loading" |
|
beff664f
»
|
dhh |
2005-10-27 |
Refactor DB exceptions and ... |
1029 |
).collect { |id| connection.quote(id) }.join(", ") |
|
851dd080
»
|
dhh |
2005-10-18 |
Added support for using lim... |
1030 |
end |
|
d49a5fcb
»
|
NZKoz |
2006-02-09 |
* Fix pagination problems w... |
1031 |
|
|
55854c41
»
|
dhh |
2006-03-04 |
Added cascading eager loadi... |
1032 |
def construct_finder_sql_for_association_limiting(options, join_dependency) |
|
d49a5fcb
»
|
NZKoz |
2006-02-09 |
* Fix pagination problems w... |
1033 |
#sql = "SELECT DISTINCT #{table_name}.#{primary_key} FROM #{table_name} " |
| |
1034 |
sql = "SELECT " |
| |
1035 |
sql << "DISTINCT #{table_name}." if include_eager_conditions?(options) || include_eager_order?(options) |
| |
1036 |
sql << "#{primary_key} FROM #{table_name} " |
| |
1037 |
|
| |
1038 |
if include_eager_conditions?(options) || include_eager_order?(options) |
|
55854c41
»
|
dhh |
2006-03-04 |
Added cascading eager loadi... |
1039 |
sql << join_dependency.join_associations.collect{|join| join.association_join }.join |
|
d49a5fcb
»
|
NZKoz |
2006-02-09 |
* Fix pagination problems w... |
1040 |
sql << "#{options[:joins]} " if options[:joins] |
| |
1041 |
end |
|
851dd080
»
|
dhh |
2005-10-18 |
Added support for using lim... |
1042 |
|
| |
1043 |
add_conditions!(sql, options[:conditions]) |
| |
1044 |
sql << "ORDER BY #{options[:order]} " if options[:order] |
| |
1045 |
add_limit!(sql, options) |
| |
1046 |
return sanitize_sql(sql) |
| |
1047 |
end |
| |
1048 |
|
| |
1049 |
def include_eager_conditions?(options) |
|
e466fc45
»
|
dhh |
2005-12-07 |
Fixed that using :include t... |
1050 |
conditions = options[:conditions] |
| |
1051 |
return false unless conditions |
| |
1052 |
conditions = conditions.first if conditions.is_a?(Array) |
| |
1053 |
conditions.scan(/(\w+)\.\w+/).flatten.any? do |condition_table_name| |
|
851dd080
»
|
dhh |
2005-10-18 |
Added support for using lim... |
1054 |
condition_table_name != table_name |
| |
1055 |
end |
| |
1056 |
end |
|
d49a5fcb
»
|
NZKoz |
2006-02-09 |
* Fix pagination problems w... |
1057 |
|
| |
1058 |
def include_eager_order?(options) |
| |
1059 |
order = options[:order] |
| |
1060 |
return false unless order |
| |
1061 |
order.scan(/(\w+)\.\w+/).flatten.any? do |order_table_name| |
| |
1062 |
order_table_name != table_name |
| |
1063 |
end |
| |
1064 |
end |
|
851dd080
»
|
dhh |
2005-10-18 |
Added support for using lim... |
1065 |
|
|
5b9b904f
»
|
dhh |
2005-07-10 |
Added support for limit and... |
1066 |
def using_limitable_reflections?(reflections) |
| |
1067 |
reflections.reject { |r| [ :belongs_to, :has_one ].include?(r.macro) }.length.zero? |
| |
1068 |
end |
| |
1069 |
|
|
55854c41
»
|
dhh |
2006-03-04 |
Added cascading eager loadi... |
1070 |
def column_aliases(join_dependency) |
| |
1071 |
join_dependency.joins.collect{|join| join.column_names_with_alias.collect{|column_name, aliased_name| |
| |
1072 |
"#{join.aliased_table_name}.#{connection.quote_column_name column_name} AS #{aliased_name}"}}.flatten.join(", ") |
|
057cf491
»
|
dhh |
2005-04-10 |
Added support for has_and_b... |
1073 |
end |
| |
1074 |
|
|
4180e57b
»
|
dhh |
2005-07-04 |
Added callback hooks to ass... |
1075 |
def add_association_callbacks(association_name, options) |
|
e19bd169
»
|
jeremy |
2005-10-01 |
Association validation does... |
1076 |
callbacks = %w(before_add after_add before_remove after_remove) |
| |
1077 |
callbacks.each do |callback_name| |
| |
1078 |
full_callback_name = "#{callback_name.to_s}_for_#{association_name.to_s}" |
| |
1079 |
defined_callbacks = options[callback_name.to_sym] |
| |
1080 |
if options.has_key?(callback_name.to_sym) |
| |
1081 |
class_inheritable_reader full_callback_name.to_sym |
| |
1082 |
write_inheritable_array(full_callback_name.to_sym, [defined_callbacks].flatten) |
| |
1083 |
end |
| |
1084 |
end |
|
4180e57b
»
|
dhh |
2005-07-04 |
Added callback hooks to ass... |
1085 |
end |
|
057cf491
»
|
dhh |
2005-04-10 |
Added support for has_and_b... |
1086 |
|
|
851dd080
»
|
dhh |
2005-10-18 |
Added support for using lim... |
1087 |
def condition_word(sql) |
| |
1088 |
sql =~ /where/i ? " AND " : "WHERE " |
| |
1089 |
end |
|
e19bd169
»
|
jeremy |
2005-10-01 |
Association validation does... |
1090 |
|
|
c8dd66fd
»
|
dhh |
2005-11-06 |
Made association extensions... |
1091 |
def create_extension_module(association_id, extension) |
| |
1092 |
extension_module_name = "#{self.to_s}#{association_id.to_s.camelize}AssociationExtension" |
| |
1093 |
|
| |
1094 |
silence_warnings do |
| |
1095 |
Object.const_set(extension_module_name, Module.new(&extension)) |
| |
1096 |
end |
| |
1097 |
|
| |
1098 |
extension_module_name.constantize |
| |
1099 |
end |
|
55854c41
»
|
dhh |
2006-03-04 |
Added cascading eager loadi... |
1100 |
|
| |
1101 |
class JoinDependency |
|
4f00c705
»
|
dhh |
2006-03-05 |
Fixed eager loading problem... |
1102 |
attr_reader :joins, :reflections, :table_aliases |
|
55854c41
»
|
dhh |
2006-03-04 |
Added cascading eager loadi... |
1103 |
|
| |
1104 |
def initialize(base, associations) |
| |
1105 |
@joins = [JoinBase.new(base)] |
| |
1106 |
@associations = associations |
| |
1107 |
@reflections = [] |
| |
1108 |
@base_records_hash = {} |
| |
1109 |
@base_records_in_order = [] |
|
4f00c705
»
|
dhh |
2006-03-05 |
Fixed eager loading problem... |
1110 |
@table_aliases = Hash.new { |aliases, table| aliases[table] = 0 } |
| |
1111 |
@table_aliases[base.table_name] = 1 |
|
55854c41
»
|
dhh |
2006-03-04 |
Added cascading eager loadi... |
1112 |
build(associations) |
| |
1113 |
end |
| |
1114 |
|
| |
1115 |
def join_associations |
| |
1116 |
@joins[1..-1].to_a |
| |
1117 |
end |
| |
1118 |
|
| |
1119 |
def join_base |
| |
1120 |
@joins[0] |
| |
1121 |
end |
| |
1122 |
|
| |
1123 |
def instantiate(rows) |
| |
1124 |
rows.each_with_index do |row, i| |
| |
1125 |
primary_id = join_base.record_id(row) |
| |
1126 |
unless @base_records_hash[primary_id] |
| |
1127 |
@base_records_in_order << (@base_records_hash[primary_id] = join_base.instantiate(row)) |
| |
1128 |
end |
| |
1129 |
construct(@base_records_hash[primary_id], @associations, join_associations.dup, row) |
| |
1130 |
end |
| |
1131 |
return @base_records_in_order |
| |
1132 |
end |
| |
1133 |
|
| |
1134 |
def aliased_table_names_for(table_name) |
| |
1135 |
joins.select{|join| join.table_name == table_name }.collect{|join| join.aliased_table_name} |
| |
1136 |
end |
| |
1137 |
|
| |
1138 |
protected |
| |
1139 |
def build(associations, parent = nil) |
| |
1140 |
parent ||= @joins.last |
| |
1141 |
case associations |
| |
1142 |
when Symbol, String |
| |
1143 |
reflection = parent.reflections[associations.to_s.intern] or |
| |
1144 |
raise ConfigurationError, "Association named '#{ associations }' was not found; perhaps you misspelled it?" |
| |
1145 |
@reflections << reflection |
| |
1146 |
@joins << JoinAssociation.new(reflection, self, parent) |
| |
1147 |
when Array |
| |
1148 |
associations.each do |association| |
| |
1149 |
build(association, parent) |
| |
1150 |
end |
| |
1151 |
when Hash |
| |
1152 |
associations.keys.sort{|a,b|a.to_s<=>b.to_s}.each do |name| |
| |
1153 |
build(name, parent) |
| |
1154 |
build(associations[name]) |
| |
1155 |
end |
| |
1156 |
else |
| |
1157 |
raise ConfigurationError, associations.inspect |
| |
1158 |
end |
| |
1159 |
end |
| |
1160 |
|
| |
1161 |
def construct(parent, associations, joins, row) |
| |
1162 |
case associations |
| |
1163 |
when Symbol, String |
| |
1164 |
while (join = joins.shift).reflection.name.to_s != associations.to_s |
| |
1165 |
raise ConfigurationError, "Not Enough Associations" if joins.empty? |
| |
1166 |
end |
| |
1167 |
construct_association(parent, join, row) |
| |
1168 |
when Array |
| |
1169 |
associations.each do |association| |
| |
1170 |
construct(parent, association, joins, row) |
| |
1171 |
end |
| |
1172 |
when Hash |
| |
1173 |
associations.keys.sort{|a,b|a.to_s<=>b.to_s}.each do |name| |
| |
1174 |
association = construct_association(parent, joins.shift, row) |
| |
1175 |
construct(association, associations[name], joins, row) if association |
| |
1176 |
end |
| |
1177 |
else |
| |
1178 |
raise ConfigurationError, associations.inspect |
| |
1179 |
end |
| |
1180 |
end |
| |
1181 |
|
| |
1182 |
def construct_association(record, join, row) |
| |
1183 |
case join.reflection.macro |
| |
1184 |
when :has_many, :has_and_belongs_to_many |
| |
1185 |
collection = record.send(join.reflection.name) |
| |
1186 |
collection.loaded |
| |
1187 |
|
| |
1188 |
return nil if record.id.to_s != join.parent.record_id(row).to_s or row[join.aliased_primary_key].nil? |
| |
1189 |
association = join.instantiate(row) |
| |
1190 |
collection.target.push(association) unless collection.target.include?(association) |
| |
1191 |
when :has_one, :belongs_to |
| |
1192 |
return if record.id.to_s != join.parent.record_id(row).to_s or row[join.aliased_primary_key].nil? |
| |
1193 |
association = join.instantiate(row) |
| |
1194 |
record.send("set_#{join.reflection.name}_target", association) |
| |
1195 |
else |
| |
1196 |
raise ConfigurationError, "unknown macro: #{join.reflection.macro}" |
| |
1197 |
end |
| |
1198 |
return association |
| |
1199 |
end |
| |
1200 |
|
| |
1201 |
class JoinBase |
| |
1202 |
attr_reader :active_record |
|
9c9069a6
»
|
technoweenie |
2006-03-18 |
Fixed has_many :through to ... |
1203 |
delegate :table_name, :column_names, :primary_key, :reflections, :sanitize_sql, :to => :active_record |
|
55854c41
»
|
dhh |
2006-03-04 |
Added cascading eager loadi... |
1204 |
|
| |
1205 |
def initialize(active_record) |
| |
1206 |
@active_record = active_record |
| |
1207 |
@cached_record = {} |
| |
1208 |
end |
| |
1209 |
|
| |
1210 |
def aliased_prefix |
| |
1211 |
"t0" |
| |
1212 |
end |
| |
1213 |
|
| |
1214 |
def aliased_primary_key |
| |
1215 |
"#{ aliased_prefix }_r0" |
| |
1216 |
end |
| |
1217 |
|
| |
1218 |
def aliased_table_name |
| |
1219 |
active_record.table_name |
| |
1220 |
end |
| |
1221 |
|
| |
1222 |
def column_names_with_alias |
| |
1223 |
unless @column_names_with_alias |
| |
1224 |
@column_names_with_alias = [] |
| |
1225 |
([primary_key] + (column_names - [primary_key])).each_with_index do |column_name, i| |
| |
1226 |
@column_names_with_alias << [column_name, "#{ aliased_prefix }_r#{ i }"] |
| |
1227 |
end |
| |
1228 |
end |
| |
1229 |
return @column_names_with_alias |
| |
1230 |
end |
| |
1231 |
|
| |
1232 |
def extract_record(row) |
| |
1233 |
column_names_with_alias.inject({}){|record, (cn, an)| record[cn] = row[an]; record} |
| |
1234 |
end |
| |
1235 |
|
| |
1236 |
def record_id(row) |
| |
1237 |
row[aliased_primary_key] |
| |
1238 |
end |
| |
1239 |
|
| |
1240 |
def instantiate(row) |
| |
1241 |
@cached_record[record_id(row)] ||= active_record.instantiate(extract_record(row)) |
| |
1242 |
end |
| |
1243 |
end |
| |
1244 |
|
| |
1245 |
class JoinAssociation < JoinBase |
|
229c0f43
»
|
technoweenie |
2006-03-17 |
Rework table aliasing to ac... |
1246 |
attr_reader :reflection, :parent, :aliased_table_name, :aliased_prefix, :aliased_join_table_name, :parent_table_name |
| |
1247 |
delegate :options, :klass, :to => :reflection |
|
55854c41
»
|
dhh |
2006-03-04 |
Added cascading eager loadi... |
1248 |
|
| |
1249 |
def initialize(reflection, join_dependency, parent = nil) |
|
57af961a
»
|
technoweenie |
2006-03-18 |
Raise error when trying to ... |
1250 |
reflection.check_validity! |
| |
1251 |
if reflection.options[:polymorphic] |
| |
1252 |
raise EagerLoadPolymorphicError.new(reflection) |
| |
1253 |
end |
| |
1254 |
|
|
55854c41
»
|
dhh |
2006-03-04 |
Added cascading eager loadi... |
1255 |
super(reflection.klass) |
| |
1256 |
@parent = parent |
| |
1257 |
@reflection = reflection |
| |
1258 |
@aliased_prefix = "t#{ join_dependency.joins.size }" |
|
229c0f43
»
|
technoweenie |
2006-03-17 |
Rework table aliasing to ac... |
1259 |
@aliased_table_name = sti? ? pluralize(reflection.name) : table_name # start with the table name |
| |
1260 |
@parent_table_name = sti? ? pluralize(parent.active_record.name.underscore) : parent.active_record.table_name |
|
4f00c705
»
|
dhh |
2006-03-05 |
Fixed eager loading problem... |
1261 |
unless join_dependency.table_aliases[aliased_table_name].zero? |
| |
1262 |
# if the table name has been used, then use an alias |
|
229c0f43
»
|
technoweenie |
2006-03-17 |
Rework table aliasing to ac... |
1263 |
@aliased_table_name = active_record.connection.table_alias_for "#{pluralize(reflection.name)}_#{parent_table_name}" |
| |
1264 |
table_index = join_dependency.table_aliases[aliased_table_name] |
| |
1265 |
@aliased_table_name = @aliased_table_name[0..active_record.connection.table_alias_length-3] + "_#{table_index+1}" if table_index > 0 |
|
4f00c705
»
|
dhh |
2006-03-05 |
Fixed eager loading problem... |
1266 |
end |
|
02d34440
»
|
technoweenie |
2006-03-15 |
Alias the has_and_belongs_t... |
1267 |
if reflection.macro == :has_and_belongs_to_many || (reflection.macro == :has_many && reflection.options[:through]) |
| |
1268 |
@aliased_join_table_name = reflection.macro == :has_and_belongs_to_many ? reflection.options[:join_table] : parent.active_record.reflect_on_association(reflection.options[:through]).klass.table_name |
| |
1269 |
unless join_dependency.table_aliases[aliased_join_table_name].zero? |
|
229c0f43
»
|
technoweenie |
2006-03-17 |
Rework table aliasing to ac... |
1270 |
@aliased_join_table_name = active_record.connection.table_alias_for "#{pluralize(reflection.name)}_#{parent_table_name}_join" |
| |
1271 |
table_index = join_dependency.table_aliases[aliased_join_table_name] |
| |
1272 |
@aliased_join_table_name = @aliased_join_table_name[0..active_record.connection.table_alias_length-3] + "_#{table_index+1}" if table_index > 0 |
|
02d34440
»
|
technoweenie |
2006-03-15 |
Alias the has_and_belongs_t... |
1273 |
end |
| |
1274 |
join_dependency.table_aliases[aliased_join_table_name] += 1 |
| |
1275 |
end |
|
4f00c705
»
|
dhh |
2006-03-05 |
Fixed eager loading problem... |
1276 |
join_dependency.table_aliases[aliased_table_name] += 1 |
|
55854c41
»
|
dhh |
2006-03-04 |
Added cascading eager loadi... |
1277 |
end |
| |
1278 |
|
| |
1279 |
def association_join |
|
4f00c705
»
|
dhh |
2006-03-05 |
Fixed eager loading problem... |
1280 |
join = case reflection.macro |
|
55854c41
»
|
dhh |
2006-03-04 |
Added cascading eager loadi... |
1281 |
when :has_and_belongs_to_many |
|
229c0f43
»
|
technoweenie |
2006-03-17 |
Rework table aliasing to ac... |
1282 |
" LEFT OUTER JOIN %s ON %s.%s = %s.%s " % [ |
| |
1283 |
table_alias_for(options[:join_table], aliased_join_table_name), |
| |
1284 |
aliased_join_table_name, |
|
55854c41
»
|
dhh |
2006-03-04 |
Added cascading eager loadi... |
1285 |
options[:foreign_key] || reflection.active_record.to_s.classify.foreign_key, |
| |
1286 |
reflection.active_record.table_name, reflection.active_record.primary_key] + |
|
229c0f43
»
|
technoweenie |
2006-03-17 |
Rework table aliasing to ac... |
1287 |
" LEFT OUTER JOIN %s ON %s.%s = %s.%s " % [ |
| |
1288 |
table_name_and_alias, aliased_table_name, klass.primary_key, |
|
a3502c41
»
|
technoweenie |
2006-03-15 |
Use association's :conditio... |
1289 |
aliased_join_table_name, options[:association_foreign_key] || klass.table_name.classify.foreign_key |
|
55854c41
»
|
dhh |
2006-03-04 |
Added cascading eager loadi... |
1290 |
] |
| |
1291 |
when :has_many, :has_one |
|
4f00c705
»
|
dhh |
2006-03-05 |
Fixed eager loading problem... |
1292 |
case |
| |
1293 |
when reflection.macro == :has_many && reflection.options[:through] |
|
a3502c41
»
|
technoweenie |
2006-03-15 |
Use association's :conditio... |
1294 |
through_reflection = parent.active_record.reflect_on_association(reflection.options[:through]) |
|
9c9069a6
»
|
technoweenie |
2006-03-18 |
Fixed has_many :through to ... |
1295 |
through_conditions = through_reflection.options[:conditions] ? "AND #{interpolate_sql(sanitize_sql(through_reflection.options[:conditions]))}" : '' |
|
4f00c705
»
|
dhh |
2006-03-05 |
Fixed eager loading problem... |
1296 |
if through_reflection.options[:as] # has_many :through against a polymorphic join |
| |
1297 |
polymorphic_foreign_key = through_reflection.options[:as].to_s + '_id' |
| |
1298 |
polymorphic_foreign_type = through_reflection.options[:as].to_s + '_type' |
| |
1299 |
|
|
229c0f43
»
|
technoweenie |
2006-03-17 |
Rework table aliasing to ac... |
1300 |
" LEFT OUTER JOIN %s ON (%s.%s = %s.%s AND %s.%s = %s) " % [ |
| |
1301 |
table_alias_for(through_reflection.klass.table_name, aliased_join_table_name), |
|
02d34440
»
|
technoweenie |
2006-03-15 |
Alias the has_and_belongs_t... |
1302 |
aliased_join_table_name, polymorphic_foreign_key, |
|
4f00c705
»
|
dhh |
2006-03-05 |
Fixed eager loading problem... |
1303 |
parent.aliased_table_name, parent.primary_key, |
|
02d34440
»
|
technoweenie |
2006-03-15 |
Alias the has_and_belongs_t... |
1304 |
aliased_join_table_name, polymorphic_foreign_type, klass.quote(parent.active_record.base_class.name)] + |
|
229c0f43
»
|
technoweenie |
2006-03-17 |
Rework table aliasing to ac... |
1305 |
" LEFT OUTER JOIN %s ON %s.%s = %s.%s " % [table_name_and_alias, |
|
02d34440
»
|
technoweenie |
2006-03-15 |
Alias the has_and_belongs_t... |
1306 |
aliased_table_name, primary_key, aliased_join_table_name, options[:foreign_key] || reflection.klass.to_s.classify.foreign_key |
|
4f00c705
»
|
dhh |
2006-03-05 |
Fixed eager loading problem... |
1307 |
] |
| |
1308 |
else # has_many :through against a normal join |
|
229c0f43
»
|
technoweenie |
2006-03-17 |
Rework table aliasing to ac... |
1309 |
" LEFT OUTER JOIN %s ON %s.%s = %s.%s " % [ |
| |
1310 |
table_alias_for(through_reflection.klass.table_name, aliased_join_table_name), aliased_join_table_name, |
|
02d34440
»
|
technoweenie |
2006-03-15 |
Alias the has_and_belongs_t... |
1311 |
through_reflection.options[:foreign_key] || through_reflection.active_record.to_s.classify.foreign_key, |
|
4f00c705
»
|
dhh |
2006-03-05 |
Fixed eager loading problem... |
1312 |
parent.aliased_table_name, parent.primary_key] + |
|
229c0f43
»
|
technoweenie |
2006-03-17 |
Rework table aliasing to ac... |
1313 |
" LEFT OUTER JOIN %s ON %s.%s = %s.%s " % [ |
| |
1314 |
table_name_and_alias, |
|
02d34440
»
|
technoweenie |
2006-03-15 |
Alias the has_and_belongs_t... |
1315 |
aliased_table_name, primary_key, |
| |
1316 |
aliased_join_table_name, options[:foreign_key] || klass.to_s.classify.foreign_key |
|
4f00c705
»
|
dhh |
2006-03-05 |
Fixed eager loading problem... |
1317 |
] |
| |
1318 |
end |
| |
1319 |
|
| |
1320 |
when reflection.macro == :has_many && reflection.options[:as] |
|
229c0f43
»
|
technoweenie |
2006-03-17 |
Rework table aliasing to ac... |
1321 |
" LEFT OUTER JOIN %s ON %s.%s = %s.%s AND %s.%s = %s" % [ |
| |
1322 |
table_name_and_alias, |
|
4f00c705
»
|
dhh |
2006-03-05 |
Fixed eager loading problem... |
1323 |
aliased_table_name, "#{reflection.options[:as]}_id", |
| |
1324 |
parent.aliased_table_name, parent.primary_key, |
| |
1325 |
aliased_table_name, "#{reflection.options[:as]}_type", |
|
7c4b7aff
»
|
technoweenie |
2006-03-06 |
Fix quoting of inheritance ... |
1326 |
klass.quote(parent.active_record.base_class.name) |
|
4f00c705
»
|
dhh |
2006-03-05 |
Fixed eager loading problem... |
1327 |
] |
| |
1328 |
else |
|
229c0f43
»
|
technoweenie |
2006-03-17 |
Rework table aliasing to ac... |
1329 |
" LEFT OUTER JOIN %s ON %s.%s = %s.%s " % [ |
| |
1330 |
table_name_and_alias, |
|
4f00c705
»
|
dhh |
2006-03-05 |
Fixed eager loading problem... |
1331 |
aliased_table_name, options[:foreign_key] || reflection.active_record.to_s.classify.foreign_key, |
| |
1332 |
parent.aliased_table_name, parent.primary_key |
| |
1333 |
] |
| |
1334 |
end |
|
55854c41
»
|
dhh |
2006-03-04 |
Added cascading eager loadi... |
1335 |
when :belongs_to |
|
229c0f43
»
|
technoweenie |
2006-03-17 |
Rework table aliasing to ac... |
1336 |
" LEFT OUTER JOIN %s ON %s.%s = %s.%s " % [ |
| |
1337 |
table_name_and_alias, aliased_table_name, reflection.klass.primary_key, |
|
6c67905c
»
|
dhh |
2006-03-18 |
Fixed that eager loading fr... |
1338 |
parent.aliased_table_name, options[:foreign_key] || klass.to_s.foreign_key |
|
55854c41
»
|
dhh |
2006-03-04 |
Added cascading eager loadi... |
1339 |
] |
| |
1340 |
else |
| |
1341 |
"" |
|
a3502c41
»
|
technoweenie |
2006-03-15 |
Use association's :conditio... |
1342 |
end || '' |
|
7c4b7aff
»
|
technoweenie |
2006-03-06 |
Fix quoting of inheritance ... |
1343 |
join << %(AND %s.%s = %s ) % [ |
|
4f00c705
»
|
dhh |
2006-03-05 |
Fixed eager loading problem... |
1344 |
aliased_table_name, |
|
7c4b7aff
»
|
technoweenie |
2006-03-06 |
Fix quoting of inheritance ... |
1345 |
reflection.active_record.connection.quote_column_name(reflection.active_record.inheritance_column), |
|
229c0f43
»
|
technoweenie |
2006-03-17 |
Rework table aliasing to ac... |
1346 |
klass.quote(klass.name)] if sti? |
|
9c9069a6
»
|
technoweenie |
2006-03-18 |
Fixed has_many :through to ... |
1347 |
join << "AND #{interpolate_sql(sanitize_sql(reflection.options[:conditions]))} " if reflection.options[:conditions] |
|
4f00c705
»
|
dhh |
2006-03-05 |
Fixed eager loading problem... |
1348 |
join |
|
55854c41
»
|
dhh |
2006-03-04 |
Added cascading eager loadi... |
1349 |
end |
|
229c0f43
»
|
technoweenie |
2006-03-17 |
Rework table aliasing to ac... |
1350 |
|
| |
1351 |
protected |
| |
1352 |
def sti? |
| |
1353 |
!klass.descends_from_active_record? |
| |
1354 |
end |
| |
1355 |
|
| |
1356 |
def pluralize(table_name) |
| |
1357 |
ActiveRecord::Base.pluralize_table_names ? table_name.to_s.pluralize : table_name |
| |
1358 |
end |
| |
1359 |
|
| |
1360 |
def table_alias_for(table_name, table_alias) |
| |
1361 |
"#{table_name} #{table_alias if table_name != table_alias}".strip |
| |
1362 |
end |
| |
1363 |
|
| |
1364 |
def table_name_and_alias |
| |
1365 |
table_alias_for table_name, @aliased_table_name |
| |
1366 |
end |
|
9c9069a6
»
|
technoweenie |
2006-03-18 |
Fixed has_many :through to ... |
1367 |
|
| |
1368 |
def interpolate_sql(sql) |
| |
1369 |
instance_eval("%@#{sql.gsub('@', '\@')}@") |
| |
1370 |
end |
|
55854c41
»
|
dhh |
2006-03-04 |
Added cascading eager loadi... |
1371 |
end |
| |
1372 |
end |
|
c8dd66fd
»
|
dhh |
2005-11-06 |
Made association extensions... |
1373 |
end |
|
db045dbb
»
|
dhh |
2004-11-23 |
Initial |
1374 |
end |
|
823554ea
»
|
dhh |
2005-01-15 |
Added support for associati... |
1375 |
end |