sequel 2.1.0 → 2.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/lib/sequel_model.rb CHANGED
@@ -53,9 +53,14 @@ module Sequel
53
53
  # cache_store, cache_ttl, dataset_methods, primary_key, restricted_columns,
54
54
  # sti_dataset, and sti_key. You should not usually need to
55
55
  # access these directly.
56
- # * The following class level attr_accessors are created: strict_param_setting
57
- # and typecast_on_assignment:
56
+ # * The following class level attr_accessors are created: raise_on_save_failure,
57
+ # strict_param_setting, and typecast_on_assignment:
58
58
  #
59
+ # # Don't raise an error if a validation attempt fails in
60
+ # # save/create/save_changes/etc.
61
+ # Model.raise_on_save_failure = false
62
+ # Model.before_save{false}
63
+ # Model.new.save # => nil
59
64
  # # Don't raise errors in new/set/update/etc. if an attempt to
60
65
  # # access a missing/restricted method occurs (just silently
61
66
  # # skip it)
@@ -2,11 +2,47 @@ module Sequel
2
2
  class Model
3
3
  module Associations
4
4
  # AssociationReflection is a Hash subclass that keeps information on Sequel::Model associations. It
5
- # provides a few methods to reduce the amount of internal code duplication. It should not
5
+ # provides methods to reduce internal code duplication. It should not
6
6
  # be instantiated by the user.
7
7
  class AssociationReflection < Hash
8
+ ASSOCIATION_TYPES = [:many_to_one, :one_to_many, :many_to_many]
8
9
  RECIPROCAL_ASSOCIATIONS = {:many_to_one=>:one_to_many, :one_to_many=>:many_to_one, :many_to_many=>:many_to_many}
9
10
 
11
+ # Name symbol for _add_ internal association method
12
+ def _add_method
13
+ :"_add_#{self[:name].to_s.singularize}"
14
+ end
15
+
16
+ # Name symbol for _dataset association method
17
+ def _dataset_method
18
+ :"_#{self[:name]}_dataset"
19
+ end
20
+
21
+ # Name symbol for _remove_all internal association method
22
+ def _remove_all_method
23
+ :"_remove_all_#{self[:name]}"
24
+ end
25
+
26
+ # Name symbol for _remove_ internal association method
27
+ def _remove_method
28
+ :"_remove_#{self[:name].to_s.singularize}"
29
+ end
30
+
31
+ # Name symbol for setter association method
32
+ def _setter_method
33
+ :"_#{self[:name]}="
34
+ end
35
+
36
+ # Name symbol for add_ association method
37
+ def add_method
38
+ :"add_#{self[:name].to_s.singularize}"
39
+ end
40
+
41
+ # Name symbol for association method, the same as the name of the association.
42
+ def association_method
43
+ self[:name]
44
+ end
45
+
10
46
  # The class associated to the current model class via this association
11
47
  def associated_class
12
48
  self[:class] ||= self[:class_name].constantize
@@ -17,6 +53,54 @@ module Sequel
17
53
  self[:associated_primary_key] ||= associated_class.primary_key
18
54
  end
19
55
 
56
+ # Name symbol for dataset association method
57
+ def dataset_method
58
+ :"#{self[:name]}_dataset"
59
+ end
60
+
61
+ # Name symbol for _helper internal association method
62
+ def dataset_helper_method
63
+ :"_#{self[:name]}_dataset_helper"
64
+ end
65
+
66
+ # Whether the dataset needs a primary key to function
67
+ def dataset_need_primary_key?
68
+ self[:type] != :many_to_one
69
+ end
70
+
71
+ # Name symbol for default join table
72
+ def default_join_table
73
+ ([self[:class_name].demodulize, self[:model].name.demodulize]. \
74
+ map{|i| i.pluralize.underscore}.sort.join('_')).to_sym
75
+ end
76
+
77
+ # Default foreign key name symbol for key in associated table that points to
78
+ # current table's primary key.
79
+ def default_left_key
80
+ :"#{self[:model].name.demodulize.underscore}_id"
81
+ end
82
+
83
+ # Default foreign key name symbol for foreign key in current model's table that points to
84
+ # the given association's table's primary key.
85
+ def default_right_key
86
+ :"#{self[:type] == :many_to_one ? self[:name] : self[:name].to_s.singularize}_id"
87
+ end
88
+
89
+ # Name symbol for _dataset association method
90
+ def eager_dataset_method
91
+ :"#{self[:name]}_eager_dataset"
92
+ end
93
+
94
+ # Whether to eagerly graph a lazy dataset
95
+ def eager_graph_lazy_dataset?
96
+ self[:type] != :many_to_one or opts[:key]
97
+ end
98
+
99
+ # Whether the associated object needs a primary key to be added/removed
100
+ def need_associated_primary_key?
101
+ self[:type] == :many_to_many
102
+ end
103
+
20
104
  # Returns/sets the reciprocal association variable, if one exists
21
105
  def reciprocal
22
106
  return self[:reciprocal] if include?(:reciprocal)
@@ -42,9 +126,35 @@ module Sequel
42
126
  self[:reciprocal] = nil
43
127
  end
44
128
 
129
+ # Name symbol for remove_all_ association method
130
+ def remove_all_method
131
+ :"remove_all_#{self[:name]}"
132
+ end
133
+
134
+ # Name symbol for remove_ association method
135
+ def remove_method
136
+ :"remove_#{self[:name].to_s.singularize}"
137
+ end
138
+
45
139
  # The columns to select when loading the association
46
140
  def select
47
- self[:select] ||= associated_class.table_name.*
141
+ return self[:select] if include?(:select)
142
+ self[:select] = self[:type] == :many_to_many ? associated_class.table_name.* : nil
143
+ end
144
+
145
+ # Whether to set the reciprocal to the current object when loading
146
+ def set_reciprocal_to_self?
147
+ self[:type] == :one_to_many
148
+ end
149
+
150
+ # Name symbol for setter association method
151
+ def setter_method
152
+ :"#{self[:name]}="
153
+ end
154
+
155
+ # Whether the association should return a single object or multiple objects.
156
+ def single_associated_object?
157
+ self[:type] == :many_to_one
48
158
  end
49
159
  end
50
160
  end
@@ -11,15 +11,17 @@
11
11
  # end
12
12
  #
13
13
  # The project class now has the following instance methods:
14
- # * portfolio - Returns the associated portfolio
14
+ # * portfolio - Returns the associated portfolio.
15
15
  # * portfolio=(obj) - Sets the associated portfolio to the object,
16
16
  # but the change is not persisted until you save the record.
17
+ # * portfolio_dataset - Returns a dataset that would return the associated
18
+ # portfolio, only useful in fairly specific circumstances.
17
19
  # * milestones - Returns an array of associated milestones
20
+ # * add_milestone(obj) - Associates the passed milestone with this object.
21
+ # * remove_milestone(obj) - Removes the association with the passed milestone.
22
+ # * remove_all_milestones - Removes associations with all associated milestones.
18
23
  # * milestones_dataset - Returns a dataset that would return the associated
19
24
  # milestones, allowing for further filtering/limiting/etc.
20
- # * add_milestone(obj) - Associates the passed milestone with this object
21
- # * remove_milestone(obj) - Removes the association with the passed milestone
22
- # * remove_all_milestones - Removes associations with all associated milestones
23
25
  #
24
26
  # If you want to override the behavior of the add_/remove_/remove_all_ methods,
25
27
  # there are private instance methods created that a prepended with an
@@ -61,18 +63,18 @@ module Sequel::Model::Associations
61
63
  # associated model's primary key. Each associated model object can
62
64
  # be associated with more than one current model objects. Each current
63
65
  # model object can be associated with only one associated model object.
64
- # Similar to ActiveRecord/DataMapper's belongs_to.
66
+ # Similar to ActiveRecord's belongs_to.
65
67
  # * :one_to_many - Foreign key in associated model's table points to this
66
68
  # model's primary key. Each current model object can be associated with
67
69
  # more than one associated model objects. Each associated model object
68
70
  # can be associated with only one current model object.
69
- # Similar to ActiveRecord/DataMapper's has_many.
71
+ # Similar to ActiveRecord's has_many.
70
72
  # * :many_to_many - A join table is used that has a foreign key that points
71
73
  # to this model's primary key and a foreign key that points to the
72
74
  # associated model's primary key. Each current model object can be
73
75
  # associated with many associated model objects, and each associated
74
76
  # model object can be associated with many current model objects.
75
- # Similar to ActiveRecord/DataMapper's has_and_belongs_to_many.
77
+ # Similar to ActiveRecord's has_and_belongs_to_many.
76
78
  #
77
79
  # A one to one relationship can be set up with a many_to_one association
78
80
  # on the table with the foreign key, and a one_to_many association with the
@@ -82,25 +84,55 @@ module Sequel::Model::Associations
82
84
  #
83
85
  # The following options can be supplied:
84
86
  # * *ALL types*:
87
+ # - :after_add - Symbol, Proc, or array of both/either specifying a callback to call
88
+ # after a new item is added to the association.
89
+ # - :after_load - Symbol, Proc, or array of both/either specifying a callback to call
90
+ # after the associated record(s) have been retrieved from the database. Not called
91
+ # when eager loading (see the :eager_loader option to accomplish it when eager loading).
92
+ # - :after_remove - Symbol, Proc, or array of both/either specifying a callback to call
93
+ # after an item is removed from the association.
85
94
  # - :allow_eager - If set to false, you cannot load the association eagerly
86
95
  # via eager or eager_graph
96
+ # - :before_add - Symbol, Proc, or array of both/either specifying a callback to call
97
+ # before a new item is added to the association.
98
+ # - :before_remove - Symbol, Proc, or array of both/either specifying a callback to call
99
+ # before an item is removed from the association.
87
100
  # - :class - The associated class or its name. If not
88
101
  # given, uses the association's name, which is camelized (and
89
102
  # singularized unless the type is :many_to_one)
90
- # - :eager - The associations to eagerly load when loading the associated object.
103
+ # - :dataset - A proc that is instance_evaled to get the base dataset
104
+ # to use for the _dataset method (before the other options are applied).
105
+ # - :eager - The associations to eagerly load via EagerLoading#eager when loading the associated object(s).
91
106
  # For many_to_one associations, this is ignored unless this association is
92
107
  # being eagerly loaded, as it doesn't save queries unless multiple objects
93
108
  # can be loaded at once.
94
109
  # - :eager_block - If given, use the block instead of the default block when
95
110
  # eagerly loading. To not use a block when eager loading (when one is used normally),
96
111
  # set to nil.
97
- # - :graph_conditions - The conditions to use on the SQL join when eagerly loading
98
- # the association via eager_graph
112
+ # - :eager_graph - The associations to eagerly load via EagerLoading#eager_graph when loading the associated object(s).
113
+ # For many_to_one associations, this is ignored unless this association is
114
+ # being eagerly loaded, as it doesn't save queries unless multiple objects
115
+ # can be loaded at once.
116
+ # - :eager_loader - A proc to use to implement eager loading, overriding the default. Takes three arguments,
117
+ # a key hash (used solely to enhance performance), an array of records,
118
+ # and a hash of dependent associations. The associated records should
119
+ # be queried from the database and the associations cache for each
120
+ # record should be populated for this to work correctly.
121
+ # - :extend - A module or array of modules to extend the dataset with.
122
+ # - :graph_block - The block to pass to join_table when eagerly loading
123
+ # the association via eager_graph.
124
+ # - :graph_conditions - The additional conditions to use on the SQL join when eagerly loading
125
+ # the association via eager_graph. Should be a hash or an array of all two pairs.
99
126
  # - :graph_join_type - The type of SQL join to use when eagerly loading the association via
100
- # eager_graph
127
+ # eager_graph. Defaults to :left_outer.
128
+ # - :graph_only_conditions - The conditions to use on the SQL join when eagerly loading
129
+ # the association via eager_graph, instead of the default conditions specified by the
130
+ # foreign/primary keys. This option causes the :graph_conditions option to be ignored.
101
131
  # - :graph_select - A column or array of columns to select from the associated table
102
132
  # when eagerly loading the association via eager_graph. Defaults to all
103
133
  # columns in the associated table.
134
+ # - :limit - Limit the number of records to the provided value. Use
135
+ # an array with two arguments for the value to specify a limit and offset.
104
136
  # - :order - the column(s) by which to order the association dataset. Can be a
105
137
  # singular column or an array.
106
138
  # - :read_only - Do not add a setter method (for many_to_one or one_to_many with :one_to_one),
@@ -115,9 +147,6 @@ module Sequel::Model::Associations
115
147
  # use this option, but beware that the join table attributes can clash with
116
148
  # attributes from the model table, so you should alias any attributes that have
117
149
  # the same name in both the join table and the associated table.
118
- # * :one_to_many, :many_to_many:
119
- # - :limit - Limit the number of records to the provided value. Use
120
- # an array with two arguments for the value to specify a limit and offset.
121
150
  # * :many_to_one:
122
151
  # - :key - foreign_key in current model's table that references
123
152
  # associated model's primary key, as a symbol. Defaults to :"#{name}_id".
@@ -142,11 +171,21 @@ module Sequel::Model::Associations
142
171
  # primary key, as a symbol. Defaults to :"#{self.name.underscore}_id".
143
172
  # - :right_key - foreign key in join table that points to associated
144
173
  # model's primary key, as a symbol. Defaults to Defaults to :"#{name.to_s.singularize}_id".
145
- # - :graph_join_table_conditions - The conditions to use on the SQL join for the join table when eagerly loading
146
- # the association via eager_graph
174
+ # - :graph_join_table_block - The block to pass to join_table for
175
+ # the join table when eagerly loading the association via eager_graph.
176
+ # - :graph_join_table_conditions - The additional conditions to use on the SQL join for
177
+ # the join table when eagerly loading the association via eager_graph. Should be a hash
178
+ # or an array of all two pairs.
179
+ # - :graph_join_type - The type of SQL join to use for the join table when eagerly
180
+ # loading the association via eager_graph. Defaults to the :graph_join_type option or
181
+ # :left_outer.
182
+ # - :graph_join_table_only_conditions - The conditions to use on the SQL join for the join
183
+ # table when eagerly loading the association via eager_graph, instead of the default
184
+ # conditions specified by the foreign/primary keys. This option causes the
185
+ # :graph_join_table_conditions option to be ignored.
147
186
  def associate(type, name, opts = {}, &block)
148
- # check arguments
149
- raise ArgumentError unless [:many_to_one, :one_to_many, :many_to_many].include?(type) && Symbol === name
187
+ raise(Error, 'invalid association type') unless AssociationReflection::ASSOCIATION_TYPES.include?(type)
188
+ raise(Error, 'Model.associate name argument must be a symbol') unless Symbol === name
150
189
 
151
190
  # merge early so we don't modify opts
152
191
  opts = opts.merge(:type => type, :name => name, :block => block, :cache => true, :model => self)
@@ -155,6 +194,9 @@ module Sequel::Model::Associations
155
194
  opts[:graph_join_type] ||= :left_outer
156
195
  opts[:graph_conditions] = opts[:graph_conditions] ? opts[:graph_conditions].to_a : []
157
196
  opts[:graph_select] = Array(opts[:graph_select]) if opts[:graph_select]
197
+ [:before_add, :before_remove, :after_add, :after_remove, :after_load, :extend].each do |cb_type|
198
+ opts[cb_type] = Array(opts[cb_type])
199
+ end
158
200
 
159
201
  # find class
160
202
  case opts[:class]
@@ -165,7 +207,7 @@ module Sequel::Model::Associations
165
207
  opts[:class_name] ||= opts[:class].name
166
208
  end
167
209
 
168
- send(:"def_#{type}", name, opts)
210
+ send(:"def_#{type}", opts)
169
211
 
170
212
  # don't add to association_reflections until we are sure there are no errors
171
213
  association_reflections[name] = opts
@@ -201,250 +243,170 @@ module Sequel::Model::Associations
201
243
 
202
244
  private
203
245
 
204
- # Name symbol for add association method
205
- def association_add_method_name(name)
206
- :"add_#{name.to_s.singularize}"
207
- end
208
-
209
- # Name symbol for remove_all association method
210
- def association_remove_all_method_name(name)
211
- :"remove_all_#{name}"
212
- end
213
-
214
- # Name symbol for remove association method
215
- def association_remove_method_name(name)
216
- :"remove_#{name.to_s.singularize}"
217
- end
218
-
219
246
  # Hash storing the association reflections. Keys are association name
220
247
  # symbols, values are association reflection hashes.
221
248
  def association_reflections
222
249
  @association_reflections ||= {}
223
250
  end
224
251
 
225
- # Adds association methods to the model for *_to_many associations.
226
- def def_association_dataset_methods(name, opts, &block)
227
- dataset_method = :"#{name}_dataset"
228
- helper_method = :"#{name}_helper"
229
- dataset_block = opts[:block]
230
- order = opts[:order]
231
- eager = opts[:eager]
232
- limit = opts[:limit]
252
+ # Add the add_ instance method
253
+ def def_add_method(opts)
254
+ class_def(opts.add_method){|o| add_associated_object(opts, o)}
255
+ end
233
256
 
257
+ # Adds association methods to the model for *_to_many associations.
258
+ def def_association_dataset_methods(opts)
234
259
  # If a block is given, define a helper method for it, because it takes
235
260
  # an argument. This is unnecessary in Ruby 1.9, as that has instance_exec.
236
- if dataset_block
237
- class_def(helper_method, &dataset_block)
238
- private helper_method
239
- end
240
-
241
- # define a method returning the association dataset (with optional order)
242
- class_def(dataset_method) do
243
- raise(Sequel::Error, 'model object does not have a primary key') unless pk
244
- ds = instance_eval(&block).select(*opts.select)
245
- ds = ds.order(*order) if order
246
- ds = ds.limit(*limit) if limit
247
- ds = ds.eager(eager) if eager
248
- ds = send(helper_method, ds) if dataset_block
249
- ds
250
- end
251
-
252
- class_def(name) do |*reload|
253
- if (assoc = @associations).include?(name) and !reload[0]
254
- assoc[name]
255
- else
256
- objs = send(dataset_method).all
257
- # Only one_to_many associations should set the reciprocal object
258
- if (opts[:type] == :one_to_many) && (reciprocal = opts.reciprocal)
259
- objs.each{|o| o.associations[reciprocal] = self}
260
- end
261
- assoc[name] = objs
262
- end
261
+ if opts[:block]
262
+ class_def(opts.dataset_helper_method, &opts[:block])
263
+ private opts.dataset_helper_method
263
264
  end
265
+ class_def(opts._dataset_method, &opts[:dataset])
266
+ private opts._dataset_method
267
+ class_def(opts.dataset_method){_dataset(opts)}
268
+ class_def(opts.association_method){|*reload| load_associated_objects(opts, reload[0])}
264
269
  end
265
270
 
266
271
  # Adds many_to_many association instance methods
267
- def def_many_to_many(name, opts)
268
- left = (opts[:left_key] ||= default_remote_key)
269
- right = (opts[:right_key] ||= default_foreign_key(opts))
272
+ def def_many_to_many(opts)
273
+ name = opts[:name]
274
+ model = self
275
+ left = (opts[:left_key] ||= opts.default_left_key)
276
+ right = (opts[:right_key] ||= opts.default_right_key)
270
277
  opts[:class_name] ||= name.to_s.singularize.camelize
271
- join_table = (opts[:join_table] ||= default_join_table_name(opts))
272
- opts[:left_key_alias] ||= :"x_foreign_key_x"
273
- opts[:left_key_select] ||= :"#{join_table}__#{left}___#{opts[:left_key_alias]}"
278
+ join_table = (opts[:join_table] ||= opts.default_join_table)
279
+ left_key_alias = opts[:left_key_alias] ||= :x_foreign_key_x
280
+ left_key_select = opts[:left_key_select] ||= left.qualify(join_table).as(opts[:left_key_alias])
274
281
  opts[:graph_join_table_conditions] = opts[:graph_join_table_conditions] ? opts[:graph_join_table_conditions].to_a : []
282
+ opts[:graph_join_table_join_type] ||= opts[:graph_join_type]
283
+ opts[:dataset] ||= proc{opts.associated_class.inner_join(join_table, [[right, opts.associated_primary_key], [left, pk]])}
275
284
  database = db
276
285
 
277
- def_association_dataset_methods(name, opts) do
278
- opts.associated_class.inner_join(join_table, [[right, opts.associated_primary_key], [left, pk]])
286
+ opts[:eager_loader] ||= proc do |key_hash, records, associations|
287
+ h = key_hash[model.primary_key]
288
+ records.each{|object| object.associations[name] = []}
289
+ model.eager_loading_dataset(opts, opts.associated_class.inner_join(join_table, [[right, opts.associated_primary_key], [left, h.keys]]), Array(opts.select) + Array(left_key_select), associations).all do |assoc_record|
290
+ next unless objects = h[assoc_record.values.delete(left_key_alias)]
291
+ objects.each{|object| object.associations[name].push(assoc_record)}
292
+ end
279
293
  end
294
+
295
+ def_association_dataset_methods(opts)
280
296
 
281
297
  return if opts[:read_only]
282
298
 
283
- add_meth = association_add_method_name(name)
284
- internal_add_meth = :"_#{add_meth}"
285
- remove_meth = association_remove_method_name(name)
286
- internal_remove_meth = :"_#{remove_meth}"
287
- remove_all_meth = association_remove_all_method_name(name)
288
- internal_remove_all_meth = :"_#{remove_all_meth}"
289
-
290
- class_def(internal_add_meth) do |o|
299
+ class_def(opts._add_method) do |o|
291
300
  database[join_table].insert(left=>pk, right=>o.pk)
292
301
  end
293
- class_def(internal_remove_meth) do |o|
302
+ class_def(opts._remove_method) do |o|
294
303
  database[join_table].filter([[left, pk], [right, o.pk]]).delete
295
304
  end
296
- class_def(internal_remove_all_meth) do
305
+ class_def(opts._remove_all_method) do
297
306
  database[join_table].filter(left=>pk).delete
298
307
  end
299
- private internal_add_meth, internal_remove_meth, internal_remove_all_meth
308
+ private opts._add_method, opts._remove_method, opts._remove_all_method
300
309
 
301
- class_def(add_meth) do |o|
302
- raise(Sequel::Error, 'model object does not have a primary key') unless pk && o.pk
303
- send(internal_add_meth, o)
304
- if (assoc = @associations).include?(name)
305
- assoc[name].push(o)
306
- end
307
- if reciprocal = opts.reciprocal and array = o.associations[reciprocal] and !array.include?(self)
308
- array.push(self)
309
- end
310
- o
311
- end
312
- class_def(remove_meth) do |o|
313
- raise(Sequel::Error, 'model object does not have a primary key') unless pk && o.pk
314
- send(internal_remove_meth, o)
315
- if (assoc = @associations).include?(name)
316
- assoc[name].delete_if{|x| o === x}
317
- end
318
- if reciprocal = opts.reciprocal and array = o.associations[reciprocal]
319
- array.delete_if{|x| self === x}
320
- end
321
- o
322
- end
323
- class_def(remove_all_meth) do
324
- raise(Sequel::Error, 'model object does not have a primary key') unless pk
325
- send(internal_remove_all_meth)
326
- if (assoc = @associations).include?(name)
327
- reciprocal = opts.reciprocal
328
- arr = assoc[name]
329
- ret = arr.dup
330
- arr.each do |o|
331
- if reciprocal and array = o.associations[reciprocal]
332
- array.delete_if{|x| self === x}
333
- end
334
- end
335
- end
336
- assoc[name] = []
337
- ret
338
- end
310
+ def_add_method(opts)
311
+ def_remove_methods(opts)
339
312
  end
340
313
 
341
314
  # Adds many_to_one association instance methods
342
- def def_many_to_one(name, opts)
343
- key = (opts[:key] ||= default_foreign_key(opts))
315
+ def def_many_to_one(opts)
316
+ name = opts[:name]
317
+ model = self
318
+ opts[:key] = opts.default_right_key unless opts.include?(:key)
319
+ key = opts[:key]
344
320
  opts[:class_name] ||= name.to_s.camelize
345
-
346
- class_def(name) do |*reload|
347
- if (assoc = @associations).include?(name) and !reload[0]
348
- assoc[name]
349
- else
350
- obj = if fk = send(key)
351
- opts.associated_class.select(*opts.select).filter(opts.associated_primary_key=>fk).first
321
+ opts[:dataset] ||= proc do
322
+ klass = opts.associated_class
323
+ klass.filter(opts.associated_primary_key.qualify(klass.table_name)=>send(key))
324
+ end
325
+ opts[:eager_loader] ||= proc do |key_hash, records, associations|
326
+ h = key_hash[key]
327
+ keys = h.keys
328
+ # Skip eager loading if no objects have a foreign key for this association
329
+ unless keys.empty?
330
+ # Default the cached association to nil, so any object that doesn't have it
331
+ # populated will have cached the negative lookup.
332
+ records.each{|object| object.associations[name] = nil}
333
+ model.eager_loading_dataset(opts, opts.associated_class.filter(opts.associated_primary_key.qualify(opts.associated_class.table_name)=>keys), opts.select, associations).all do |assoc_record|
334
+ next unless objects = h[assoc_record.pk]
335
+ objects.each{|object| object.associations[name] = assoc_record}
352
336
  end
353
- assoc[name] = obj
354
337
  end
355
338
  end
339
+
340
+ def_association_dataset_methods(opts)
341
+
356
342
  return if opts[:read_only]
357
343
 
358
- class_def(:"#{name}=") do |o|
344
+ class_def(opts._setter_method){|o| send(:"#{key}=", (o.pk if o))}
345
+ private opts._setter_method
346
+
347
+ class_def(opts.setter_method) do |o|
359
348
  raise(Sequel::Error, 'model object does not have a primary key') if o && !o.pk
360
- reciprocal = opts.reciprocal
361
- if (assoc = @associations).include?(name) and reciprocal
362
- old_val = assoc[name]
363
- end
364
- assoc[name] = o
365
- send(:"#{key}=", (o.pk if o))
366
- if reciprocal and old_val != o
367
- if old_val and array = old_val.associations[reciprocal]
368
- array.delete_if{|x| self === x}
369
- end
370
- if o and array = o.associations[reciprocal] and !array.include?(self)
371
- array.push(self)
372
- end
373
- end
349
+ old_val = send(opts.association_method)
350
+ return o if old_val == o
351
+ return if old_val and run_association_callbacks(opts, :before_remove, old_val) == false
352
+ return if o and run_association_callbacks(opts, :before_add, o) == false
353
+ send(opts._setter_method, o)
354
+ @associations[name] = o
355
+ remove_reciprocal_object(opts, old_val) if old_val
356
+ add_reciprocal_object(opts, o) if o
357
+ run_association_callbacks(opts, :after_add, o) if o
358
+ run_association_callbacks(opts, :after_remove, old_val) if old_val
374
359
  o
375
360
  end
376
361
  end
377
362
 
378
363
  # Adds one_to_many association instance methods
379
- def def_one_to_many(name, opts)
380
- key = (opts[:key] ||= default_remote_key)
364
+ def def_one_to_many(opts)
365
+ name = opts[:name]
366
+ model = self
367
+ key = (opts[:key] ||= opts.default_left_key)
381
368
  opts[:class_name] ||= name.to_s.singularize.camelize
369
+ opts[:dataset] ||= proc do
370
+ klass = opts.associated_class
371
+ klass.filter(key.qualify(klass.table_name) => pk)
372
+ end
373
+ opts[:eager_loader] ||= proc do |key_hash, records, associations|
374
+ h = key_hash[model.primary_key]
375
+ records.each{|object| object.associations[name] = []}
376
+ reciprocal = opts.reciprocal
377
+ model.eager_loading_dataset(opts, opts.associated_class.filter(key.qualify(opts.associated_class.table_name)=>h.keys), opts.select, associations).all do |assoc_record|
378
+ next unless objects = h[assoc_record[key]]
379
+ objects.each do |object|
380
+ object.associations[name].push(assoc_record)
381
+ assoc_record.associations[reciprocal] = object if reciprocal
382
+ end
383
+ end
384
+ end
382
385
 
383
- def_association_dataset_methods(name, opts) {opts.associated_class.filter(key => pk)}
386
+ def_association_dataset_methods(opts)
384
387
 
385
388
  unless opts[:read_only]
386
- add_meth = association_add_method_name(name)
387
- internal_add_meth = :"_#{add_meth}"
388
- class_def(internal_add_meth) do |o|
389
+ class_def(opts._add_method) do |o|
389
390
  o.send(:"#{key}=", pk)
390
391
  o.save || raise(Sequel::Error, "invalid associated object, cannot save")
391
392
  end
392
- private internal_add_meth
393
+ private opts._add_method
394
+ def_add_method(opts)
393
395
 
394
- class_def(add_meth) do |o|
395
- raise(Sequel::Error, 'model object does not have a primary key') unless pk
396
- send(internal_add_meth, o)
397
- if (assoc = @associations).include?(name)
398
- assoc[name].push(o)
399
- end
400
- if reciprocal = opts.reciprocal
401
- o.associations[reciprocal] = self
402
- end
403
- o
404
- end
405
396
  unless opts[:one_to_one]
406
- remove_meth = association_remove_method_name(name)
407
- internal_remove_meth = :"_#{remove_meth}"
408
- remove_all_meth = association_remove_all_method_name(name)
409
- internal_remove_all_meth = :"_#{remove_all_meth}"
410
-
411
- class_def(internal_remove_meth) do |o|
397
+ class_def(opts._remove_method) do |o|
412
398
  o.send(:"#{key}=", nil)
413
399
  o.save || raise(Sequel::Error, "invalid associated object, cannot save")
414
400
  end
415
- class_def(internal_remove_all_meth) do
401
+ class_def(opts._remove_all_method) do
416
402
  opts.associated_class.filter(key=>pk).update(key=>nil)
417
403
  end
418
- private internal_remove_meth, internal_remove_all_meth
419
-
420
- class_def(remove_meth) do |o|
421
- raise(Sequel::Error, 'model object does not have a primary key') unless pk
422
- send(internal_remove_meth, o)
423
- if (assoc = @associations).include?(name)
424
- assoc[name].delete_if{|x| o === x}
425
- end
426
- if reciprocal = opts.reciprocal
427
- o.associations[reciprocal] = nil
428
- end
429
- o
430
- end
431
- class_def(remove_all_meth) do
432
- raise(Sequel::Error, 'model object does not have a primary key') unless pk
433
- send(internal_remove_all_meth)
434
- if (assoc = @associations).include?(name)
435
- arr = assoc[name]
436
- ret = arr.dup
437
- if reciprocal = opts.reciprocal
438
- arr.each{|o| o.associations[reciprocal] = nil}
439
- end
440
- end
441
- assoc[name] = []
442
- ret
443
- end
404
+ private opts._remove_method, opts._remove_all_method
405
+ def_remove_methods(opts)
444
406
  end
445
407
  end
446
408
  if opts[:one_to_one]
447
- private name, :"#{name}_dataset"
409
+ private opts.association_method, opts.dataset_method
448
410
  n = name.to_s.singularize.to_sym
449
411
  raise(Sequel::Error, "one_to_many association names should still be plural even when using the :one_to_one option") if n == name
450
412
  class_def(n) do |*o|
@@ -453,11 +415,11 @@ module Sequel::Model::Associations
453
415
  objs.first
454
416
  end
455
417
  unless opts[:read_only]
456
- private add_meth
418
+ private opts.add_method
457
419
  class_def(:"#{n}=") do |o|
458
420
  klass = opts.associated_class
459
421
  model.db.transaction do
460
- send(add_meth, o)
422
+ send(opts.add_method, o)
461
423
  klass.filter(Sequel::SQL::BooleanExpression.new(:AND, {key=>pk}, ~{klass.primary_key=>o.pk}.sql_expr)).update(key=>nil)
462
424
  end
463
425
  end
@@ -465,22 +427,9 @@ module Sequel::Model::Associations
465
427
  end
466
428
  end
467
429
 
468
- # Default foreign key name symbol for foreign key in current model's table that points to
469
- # the given association's table's primary key.
470
- def default_foreign_key(reflection)
471
- name = reflection[:name]
472
- :"#{reflection[:type] == :many_to_one ? name : name.to_s.singularize}_id"
473
- end
474
-
475
- # Name symbol for default join table
476
- def default_join_table_name(opts)
477
- ([opts[:class_name].demodulize, name.demodulize]. \
478
- map{|i| i.pluralize.underscore}.sort.join('_')).to_sym
479
- end
480
-
481
- # Default foreign key name symbol for key in associated table that points to
482
- # current table's primary key.
483
- def default_remote_key
484
- :"#{name.demodulize.underscore}_id"
430
+ # Add the remove_ and remove_all instance methods
431
+ def def_remove_methods(opts)
432
+ class_def(opts.remove_method){|o| remove_associated_object(opts, o)}
433
+ class_def(opts.remove_all_method){remove_all_associated_objects(opts)}
485
434
  end
486
435
  end