sequel 2.0.1 → 2.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +38 -0
- data/README +3 -4
- data/Rakefile +4 -4
- data/lib/sequel_model.rb +22 -2
- data/lib/sequel_model/association_reflection.rb +2 -9
- data/lib/sequel_model/associations.rb +184 -91
- data/lib/sequel_model/base.rb +117 -22
- data/lib/sequel_model/caching.rb +1 -1
- data/lib/sequel_model/dataset_methods.rb +26 -0
- data/lib/sequel_model/eager_loading.rb +16 -20
- data/lib/sequel_model/hooks.rb +1 -1
- data/lib/sequel_model/plugins.rb +1 -1
- data/lib/sequel_model/record.rb +125 -39
- data/lib/sequel_model/validations.rb +101 -115
- data/spec/association_reflection_spec.rb +6 -6
- data/spec/associations_spec.rb +205 -37
- data/spec/base_spec.rb +161 -1
- data/spec/dataset_methods_spec.rb +66 -0
- data/spec/eager_loading_spec.rb +36 -25
- data/spec/model_spec.rb +51 -6
- data/spec/record_spec.rb +172 -62
- data/spec/schema_spec.rb +7 -0
- data/spec/validations_spec.rb +152 -51
- metadata +5 -3
data/CHANGELOG
CHANGED
@@ -1,3 +1,41 @@
|
|
1
|
+
=== HEAD
|
2
|
+
|
3
|
+
* Break association add_/remove_/remove_all_ methods into two parts, for easier overriding (jeremyevans)
|
4
|
+
|
5
|
+
* Add Model.strict_param_setting, on by default, which raises errors if a missing/restricted method is called via new/set/update/etc. (jeremyevans)
|
6
|
+
|
7
|
+
* Raise errors when using association methods on objects without valid primary keys (jeremyevans)
|
8
|
+
|
9
|
+
* The model's primary key is a restricted column by default, Add model.unrestrict_primary_key to get the old behavior (jeremyevans)
|
10
|
+
|
11
|
+
* Add Model.set_(allowed|restricted)_columns, which affect which columns create/new/set/update/etc. modify (jeremyevans)
|
12
|
+
|
13
|
+
* Calls to Model.def_dataset_method with a block are cached and reapplied to the new dataset if set_dataset is called, even in a subclass (jeremyevans)
|
14
|
+
|
15
|
+
* The :reciprocal option to associations should now be the symbol name of the reciprocal association, not an instance variable symbol (jeremyevans)
|
16
|
+
|
17
|
+
* Add Model#associations, which is a hash holding a cache of associated objects, with each association being a separate key (jeremyevans)
|
18
|
+
|
19
|
+
* Make all associations support a :graph_select option, specifying a column or array of columns to select when using eager_graph (jeremyevans)
|
20
|
+
|
21
|
+
* Bring back Model#set and Model#update, now the same as Model#set_with_params and Model#update_with_params (jeremyevans)
|
22
|
+
|
23
|
+
* Allow model datasets to call to_hash without any arguments, which allows easy creation of identity maps (jeremyevans)
|
24
|
+
|
25
|
+
* Add Model.set_sti_key, for easily setting up single table inheritance (jeremyevans)
|
26
|
+
|
27
|
+
* Make all associations support a :read_only option, which doesn't add methods that modify the database (jeremyevans)
|
28
|
+
|
29
|
+
* Make *_to_many associations support a :limit option, for specifying a limit to the resulting records (and possibly an offset) (jeremyevans)
|
30
|
+
|
31
|
+
* Make association block argument and :eager option affect the _dataset method (jeremyevans)
|
32
|
+
|
33
|
+
* Add a :one_to_one option to one_to_many associations, which creates a getter and setter similar to many_to_one (a.k.a. has_one) (jeremyevans)
|
34
|
+
|
35
|
+
* add_ and remove_ one_to_many association methods now raise an error if the passed object cannot be saved, instead of saving without validation (jeremyevans)
|
36
|
+
|
37
|
+
* Add support for :if option on validations, using a symbol (specifying an instance method) or a proc (dtsato)
|
38
|
+
|
1
39
|
=== 2.0.1 (2008-06-04)
|
2
40
|
|
3
41
|
* Make the choice of Time or DateTime optional for typecasting :datetime types, default to Time (jeremyevans)
|
data/README
CHANGED
@@ -26,8 +26,7 @@ You can, however, explicitly set the table name or even the dataset used:
|
|
26
26
|
* {Source code}[http://github.com/jeremyevans/sequel]
|
27
27
|
* {Bug tracking}[http://code.google.com/p/ruby-sequel/issues/list]
|
28
28
|
* {Google group}[http://groups.google.com/group/sequel-talk]
|
29
|
-
* {
|
30
|
-
* {API RDoc}[http://sequel.rubyforge.org]
|
29
|
+
* {RDoc}[http://sequel.rubyforge.org]
|
31
30
|
|
32
31
|
To check out the source code:
|
33
32
|
|
@@ -127,11 +126,11 @@ Hooks are defined by supplying a block:
|
|
127
126
|
|
128
127
|
class Post < Sequel::Model
|
129
128
|
after_create do
|
130
|
-
|
129
|
+
author.increase_post_count
|
131
130
|
end
|
132
131
|
|
133
132
|
after_destroy do
|
134
|
-
author.
|
133
|
+
author.decrease_post_count
|
135
134
|
end
|
136
135
|
end
|
137
136
|
|
data/Rakefile
CHANGED
@@ -9,8 +9,8 @@ include FileUtils
|
|
9
9
|
# Configuration
|
10
10
|
##############################################################################
|
11
11
|
NAME = "sequel"
|
12
|
-
VERS = "2.0
|
13
|
-
SEQUEL_CORE_VERS= "2.0
|
12
|
+
VERS = "2.1.0"
|
13
|
+
SEQUEL_CORE_VERS= "2.1.0"
|
14
14
|
CLEAN.include ["**/.*.sw?", "pkg", ".config", "rdoc", "coverage"]
|
15
15
|
RDOC_OPTS = ["--quiet", "--line-numbers", "--inline-source", '--title', \
|
16
16
|
'Sequel: The Database Toolkit for Ruby: Model Classes', '--main', 'README']
|
@@ -60,13 +60,13 @@ end
|
|
60
60
|
desc "Install sequel gem"
|
61
61
|
task :install do
|
62
62
|
sh %{rake package}
|
63
|
-
sh %{sudo gem install pkg/#{NAME}-#{VERS}}
|
63
|
+
sh %{sudo gem install pkg/#{NAME}-#{VERS} --local}
|
64
64
|
end
|
65
65
|
|
66
66
|
desc "Install sequel gem without docs"
|
67
67
|
task :install_no_docs do
|
68
68
|
sh %{rake package}
|
69
|
-
sh %{sudo gem install pkg/#{NAME}-#{VERS} --no-rdoc --no-ri}
|
69
|
+
sh %{sudo gem install pkg/#{NAME}-#{VERS} --no-rdoc --no-ri --local}
|
70
70
|
end
|
71
71
|
|
72
72
|
desc "Uninstall sequel gem"
|
data/lib/sequel_model.rb
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
require 'sequel_core'
|
2
|
-
%w"inflector base hooks record schema association_reflection
|
2
|
+
%w"inflector base hooks record schema association_reflection dataset_methods
|
3
3
|
associations caching plugins validations eager_loading".each do |f|
|
4
4
|
require "sequel_model/#{f}"
|
5
5
|
end
|
@@ -49,9 +49,29 @@ module Sequel
|
|
49
49
|
# should not override these instance methods.
|
50
50
|
# * The following instance_methods all call the class method of the same
|
51
51
|
# name: columns, dataset, db, primary_key, str_columns.
|
52
|
+
# * The following class level attr_readers are created: allowed_columns,
|
53
|
+
# cache_store, cache_ttl, dataset_methods, primary_key, restricted_columns,
|
54
|
+
# sti_dataset, and sti_key. You should not usually need to
|
55
|
+
# access these directly.
|
56
|
+
# * The following class level attr_accessors are created: strict_param_setting
|
57
|
+
# and typecast_on_assignment:
|
58
|
+
#
|
59
|
+
# # Don't raise errors in new/set/update/etc. if an attempt to
|
60
|
+
# # access a missing/restricted method occurs (just silently
|
61
|
+
# # skip it)
|
62
|
+
# Model.strict_param_setting = false
|
63
|
+
# Model.new(:id=>1) # No Error
|
64
|
+
# # Don't typecast attribute values on assignment
|
65
|
+
# Model.typecast_on_assignment = false
|
66
|
+
# m = Model.new
|
67
|
+
# m.number = '10'
|
68
|
+
# m.number # => '10' instead of 10
|
69
|
+
#
|
70
|
+
# * The following class level method aliases are defined:
|
71
|
+
# * Model.dataset= => set_dataset
|
72
|
+
# * Model.is_a => is
|
52
73
|
class Model
|
53
74
|
extend Enumerable
|
54
75
|
extend Associations
|
55
|
-
include Validation
|
56
76
|
end
|
57
77
|
end
|
@@ -28,14 +28,14 @@ module Sequel
|
|
28
28
|
associated_class.all_association_reflections.each do |assoc_reflect|
|
29
29
|
if assoc_reflect[:type] == :many_to_many && assoc_reflect[:left_key] == right_key \
|
30
30
|
&& assoc_reflect[:right_key] == left_key && assoc_reflect[:join_table] == join_table
|
31
|
-
return self[:reciprocal] =
|
31
|
+
return self[:reciprocal] = assoc_reflect[:name]
|
32
32
|
end
|
33
33
|
end
|
34
34
|
else
|
35
35
|
key = self[:key]
|
36
36
|
associated_class.all_association_reflections.each do |assoc_reflect|
|
37
37
|
if assoc_reflect[:type] == reciprocal_type && assoc_reflect[:key] == key
|
38
|
-
return self[:reciprocal] =
|
38
|
+
return self[:reciprocal] = assoc_reflect[:name]
|
39
39
|
end
|
40
40
|
end
|
41
41
|
end
|
@@ -46,13 +46,6 @@ module Sequel
|
|
46
46
|
def select
|
47
47
|
self[:select] ||= associated_class.table_name.*
|
48
48
|
end
|
49
|
-
|
50
|
-
private
|
51
|
-
|
52
|
-
# Name symbol of association instance variable
|
53
|
-
def association_ivar(name)
|
54
|
-
:"@#{name}"
|
55
|
-
end
|
56
49
|
end
|
57
50
|
end
|
58
51
|
end
|
@@ -21,6 +21,12 @@
|
|
21
21
|
# * remove_milestone(obj) - Removes the association with the passed milestone
|
22
22
|
# * remove_all_milestones - Removes associations with all associated milestones
|
23
23
|
#
|
24
|
+
# If you want to override the behavior of the add_/remove_/remove_all_ methods,
|
25
|
+
# there are private instance methods created that a prepended with an
|
26
|
+
# underscore (e.g. _add_milestone). The private instance methods can be
|
27
|
+
# easily overridden, but you shouldn't override the public instance methods,
|
28
|
+
# as they deal with how associations are cached.
|
29
|
+
#
|
24
30
|
# By default the classes for the associations are inferred from the association
|
25
31
|
# name, so for example the Project#portfolio will return an instance of
|
26
32
|
# Portfolio, and Project#milestones will return an array of Milestone
|
@@ -68,6 +74,12 @@ module Sequel::Model::Associations
|
|
68
74
|
# model object can be associated with many current model objects.
|
69
75
|
# Similar to ActiveRecord/DataMapper's has_and_belongs_to_many.
|
70
76
|
#
|
77
|
+
# A one to one relationship can be set up with a many_to_one association
|
78
|
+
# on the table with the foreign key, and a one_to_many association with the
|
79
|
+
# :one_to_one option specified on the table without the foreign key. The
|
80
|
+
# two associations will operate similarly, except that the many_to_one
|
81
|
+
# association setter doesn't update the database until you call save manually.
|
82
|
+
#
|
71
83
|
# The following options can be supplied:
|
72
84
|
# * *ALL types*:
|
73
85
|
# - :allow_eager - If set to false, you cannot load the association eagerly
|
@@ -86,9 +98,14 @@ module Sequel::Model::Associations
|
|
86
98
|
# the association via eager_graph
|
87
99
|
# - :graph_join_type - The type of SQL join to use when eagerly loading the association via
|
88
100
|
# eager_graph
|
101
|
+
# - :graph_select - A column or array of columns to select from the associated table
|
102
|
+
# when eagerly loading the association via eager_graph. Defaults to all
|
103
|
+
# columns in the associated table.
|
89
104
|
# - :order - the column(s) by which to order the association dataset. Can be a
|
90
105
|
# singular column or an array.
|
91
|
-
# - :
|
106
|
+
# - :read_only - Do not add a setter method (for many_to_one or one_to_many with :one_to_one),
|
107
|
+
# or add_/remove_/remove_all_ methods (for one_to_many, many_to_many)
|
108
|
+
# - :reciprocal - the symbol name of the reciprocal association,
|
92
109
|
# if it exists. By default, sequel will try to determine it by looking at the
|
93
110
|
# associated model's assocations for a association that matches
|
94
111
|
# the current association's key(s). Set to nil to not use a reciprocal.
|
@@ -98,6 +115,9 @@ module Sequel::Model::Associations
|
|
98
115
|
# use this option, but beware that the join table attributes can clash with
|
99
116
|
# attributes from the model table, so you should alias any attributes that have
|
100
117
|
# 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.
|
101
121
|
# * :many_to_one:
|
102
122
|
# - :key - foreign_key in current model's table that references
|
103
123
|
# associated model's primary key, as a symbol. Defaults to :"#{name}_id".
|
@@ -105,6 +125,14 @@ module Sequel::Model::Associations
|
|
105
125
|
# - :key - foreign key in associated model's table that references
|
106
126
|
# current model's primary key, as a symbol. Defaults to
|
107
127
|
# :"#{self.name.underscore}_id".
|
128
|
+
# - :one_to_one: Create a getter and setter similar to those of many_to_one
|
129
|
+
# associations. The getter returns a singular matching record, or raises an
|
130
|
+
# error if multiple records match. The setter updates the record given and removes
|
131
|
+
# associations with all other records. When this option is used, the other
|
132
|
+
# association methods usually added are either removed or made private,
|
133
|
+
# so using this is similar to using many_to_one, in terms of the methods
|
134
|
+
# it adds, the main difference is that the foreign key is in the associated
|
135
|
+
# table instead of the current table.
|
108
136
|
# * :many_to_many:
|
109
137
|
# - :join_table - name of table that includes the foreign keys to both
|
110
138
|
# the current model and the associated model, as a symbol. Defaults to the name
|
@@ -126,6 +154,7 @@ module Sequel::Model::Associations
|
|
126
154
|
opts[:eager_block] = block unless opts.include?(:eager_block)
|
127
155
|
opts[:graph_join_type] ||= :left_outer
|
128
156
|
opts[:graph_conditions] = opts[:graph_conditions] ? opts[:graph_conditions].to_a : []
|
157
|
+
opts[:graph_select] = Array(opts[:graph_select]) if opts[:graph_select]
|
129
158
|
|
130
159
|
# find class
|
131
160
|
case opts[:class]
|
@@ -177,11 +206,6 @@ module Sequel::Model::Associations
|
|
177
206
|
:"add_#{name.to_s.singularize}"
|
178
207
|
end
|
179
208
|
|
180
|
-
# Name symbol of association instance variable
|
181
|
-
def association_ivar(name)
|
182
|
-
:"@#{name}"
|
183
|
-
end
|
184
|
-
|
185
209
|
# Name symbol for remove_all association method
|
186
210
|
def association_remove_all_method_name(name)
|
187
211
|
:"remove_all_#{name}"
|
@@ -203,47 +227,44 @@ module Sequel::Model::Associations
|
|
203
227
|
dataset_method = :"#{name}_dataset"
|
204
228
|
helper_method = :"#{name}_helper"
|
205
229
|
dataset_block = opts[:block]
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
class_def(dataset_method) {instance_eval(&block).order(*order)}
|
211
|
-
else
|
212
|
-
class_def(dataset_method, &block)
|
213
|
-
end
|
214
|
-
|
230
|
+
order = opts[:order]
|
231
|
+
eager = opts[:eager]
|
232
|
+
limit = opts[:limit]
|
233
|
+
|
215
234
|
# If a block is given, define a helper method for it, because it takes
|
216
235
|
# an argument. This is unnecessary in Ruby 1.9, as that has instance_exec.
|
217
236
|
if dataset_block
|
218
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
|
219
250
|
end
|
220
251
|
|
221
252
|
class_def(name) do |*reload|
|
222
|
-
if !reload[0]
|
223
|
-
|
253
|
+
if (assoc = @associations).include?(name) and !reload[0]
|
254
|
+
assoc[name]
|
224
255
|
else
|
225
|
-
|
226
|
-
# if the a dataset block was specified, we need to call it and use
|
227
|
-
# the result as the dataset to fetch records from.
|
228
|
-
if dataset_block
|
229
|
-
ds = send(helper_method, ds)
|
230
|
-
end
|
231
|
-
if eager = opts[:eager]
|
232
|
-
ds = ds.eager(eager)
|
233
|
-
end
|
234
|
-
objs = ds.all
|
256
|
+
objs = send(dataset_method).all
|
235
257
|
# Only one_to_many associations should set the reciprocal object
|
236
258
|
if (opts[:type] == :one_to_many) && (reciprocal = opts.reciprocal)
|
237
|
-
objs.each{|o| o.
|
259
|
+
objs.each{|o| o.associations[reciprocal] = self}
|
238
260
|
end
|
239
|
-
|
261
|
+
assoc[name] = objs
|
240
262
|
end
|
241
263
|
end
|
242
264
|
end
|
243
265
|
|
244
266
|
# Adds many_to_many association instance methods
|
245
267
|
def def_many_to_many(name, opts)
|
246
|
-
ivar = association_ivar(name)
|
247
268
|
left = (opts[:left_key] ||= default_remote_key)
|
248
269
|
right = (opts[:right_key] ||= default_foreign_key(opts))
|
249
270
|
opts[:class_name] ||= name.to_s.singularize.camelize
|
@@ -254,75 +275,100 @@ module Sequel::Model::Associations
|
|
254
275
|
database = db
|
255
276
|
|
256
277
|
def_association_dataset_methods(name, opts) do
|
257
|
-
opts.associated_class.
|
278
|
+
opts.associated_class.inner_join(join_table, [[right, opts.associated_primary_key], [left, pk]])
|
258
279
|
end
|
259
280
|
|
260
|
-
|
281
|
+
return if opts[:read_only]
|
282
|
+
|
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|
|
261
291
|
database[join_table].insert(left=>pk, right=>o.pk)
|
262
|
-
|
263
|
-
|
292
|
+
end
|
293
|
+
class_def(internal_remove_meth) do |o|
|
294
|
+
database[join_table].filter([[left, pk], [right, o.pk]]).delete
|
295
|
+
end
|
296
|
+
class_def(internal_remove_all_meth) do
|
297
|
+
database[join_table].filter(left=>pk).delete
|
298
|
+
end
|
299
|
+
private internal_add_meth, internal_remove_meth, internal_remove_all_meth
|
300
|
+
|
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)
|
264
306
|
end
|
265
|
-
if
|
266
|
-
|
267
|
-
list.push(self)
|
307
|
+
if reciprocal = opts.reciprocal and array = o.associations[reciprocal] and !array.include?(self)
|
308
|
+
array.push(self)
|
268
309
|
end
|
269
310
|
o
|
270
311
|
end
|
271
|
-
class_def(
|
272
|
-
|
273
|
-
|
274
|
-
|
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}
|
275
317
|
end
|
276
|
-
if
|
277
|
-
|
318
|
+
if reciprocal = opts.reciprocal and array = o.associations[reciprocal]
|
319
|
+
array.delete_if{|x| self === x}
|
278
320
|
end
|
279
321
|
o
|
280
322
|
end
|
281
|
-
class_def(
|
282
|
-
|
283
|
-
|
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)
|
284
327
|
reciprocal = opts.reciprocal
|
328
|
+
arr = assoc[name]
|
285
329
|
ret = arr.dup
|
286
330
|
arr.each do |o|
|
287
|
-
if reciprocal
|
288
|
-
|
331
|
+
if reciprocal and array = o.associations[reciprocal]
|
332
|
+
array.delete_if{|x| self === x}
|
289
333
|
end
|
290
334
|
end
|
291
335
|
end
|
292
|
-
|
336
|
+
assoc[name] = []
|
293
337
|
ret
|
294
338
|
end
|
295
339
|
end
|
296
340
|
|
297
341
|
# Adds many_to_one association instance methods
|
298
342
|
def def_many_to_one(name, opts)
|
299
|
-
ivar = association_ivar(name)
|
300
|
-
|
301
343
|
key = (opts[:key] ||= default_foreign_key(opts))
|
302
344
|
opts[:class_name] ||= name.to_s.camelize
|
303
345
|
|
304
346
|
class_def(name) do |*reload|
|
305
|
-
if !reload[0]
|
306
|
-
|
347
|
+
if (assoc = @associations).include?(name) and !reload[0]
|
348
|
+
assoc[name]
|
307
349
|
else
|
308
350
|
obj = if fk = send(key)
|
309
351
|
opts.associated_class.select(*opts.select).filter(opts.associated_primary_key=>fk).first
|
310
352
|
end
|
311
|
-
|
312
|
-
obj
|
353
|
+
assoc[name] = obj
|
313
354
|
end
|
314
355
|
end
|
356
|
+
return if opts[:read_only]
|
315
357
|
|
316
|
-
class_def(:"#{name}=") do |o|
|
317
|
-
|
318
|
-
|
358
|
+
class_def(:"#{name}=") do |o|
|
359
|
+
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
|
319
365
|
send(:"#{key}=", (o.pk if o))
|
320
|
-
if reciprocal
|
321
|
-
if old_val
|
322
|
-
|
366
|
+
if reciprocal and old_val != o
|
367
|
+
if old_val and array = old_val.associations[reciprocal]
|
368
|
+
array.delete_if{|x| self === x}
|
323
369
|
end
|
324
|
-
if o
|
325
|
-
|
370
|
+
if o and array = o.associations[reciprocal] and !array.include?(self)
|
371
|
+
array.push(self)
|
326
372
|
end
|
327
373
|
end
|
328
374
|
o
|
@@ -331,44 +377,91 @@ module Sequel::Model::Associations
|
|
331
377
|
|
332
378
|
# Adds one_to_many association instance methods
|
333
379
|
def def_one_to_many(name, opts)
|
334
|
-
ivar = association_ivar(name)
|
335
380
|
key = (opts[:key] ||= default_remote_key)
|
336
381
|
opts[:class_name] ||= name.to_s.singularize.camelize
|
337
382
|
|
338
|
-
def_association_dataset_methods(name, opts) {opts.associated_class.
|
383
|
+
def_association_dataset_methods(name, opts) {opts.associated_class.filter(key => pk)}
|
339
384
|
|
340
|
-
|
341
|
-
|
342
|
-
|
343
|
-
|
344
|
-
|
345
|
-
|
346
|
-
if reciprocal = opts.reciprocal
|
347
|
-
o.instance_variable_set(reciprocal, self)
|
385
|
+
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
|
+
o.send(:"#{key}=", pk)
|
390
|
+
o.save || raise(Sequel::Error, "invalid associated object, cannot save")
|
348
391
|
end
|
349
|
-
|
350
|
-
|
351
|
-
|
352
|
-
|
353
|
-
|
354
|
-
|
355
|
-
|
392
|
+
private internal_add_meth
|
393
|
+
|
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
|
356
404
|
end
|
357
|
-
|
358
|
-
|
405
|
+
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|
|
412
|
+
o.send(:"#{key}=", nil)
|
413
|
+
o.save || raise(Sequel::Error, "invalid associated object, cannot save")
|
414
|
+
end
|
415
|
+
class_def(internal_remove_all_meth) do
|
416
|
+
opts.associated_class.filter(key=>pk).update(key=>nil)
|
417
|
+
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
|
359
444
|
end
|
360
|
-
o
|
361
445
|
end
|
362
|
-
|
363
|
-
|
364
|
-
|
365
|
-
|
366
|
-
|
367
|
-
|
446
|
+
if opts[:one_to_one]
|
447
|
+
private name, :"#{name}_dataset"
|
448
|
+
n = name.to_s.singularize.to_sym
|
449
|
+
raise(Sequel::Error, "one_to_many association names should still be plural even when using the :one_to_one option") if n == name
|
450
|
+
class_def(n) do |*o|
|
451
|
+
objs = send(name, *o)
|
452
|
+
raise(Sequel::Error, "multiple values found for a one-to-one relationship") if objs.length > 1
|
453
|
+
objs.first
|
454
|
+
end
|
455
|
+
unless opts[:read_only]
|
456
|
+
private add_meth
|
457
|
+
class_def(:"#{n}=") do |o|
|
458
|
+
klass = opts.associated_class
|
459
|
+
model.db.transaction do
|
460
|
+
send(add_meth, o)
|
461
|
+
klass.filter(Sequel::SQL::BooleanExpression.new(:AND, {key=>pk}, ~{klass.primary_key=>o.pk}.sql_expr)).update(key=>nil)
|
462
|
+
end
|
368
463
|
end
|
369
464
|
end
|
370
|
-
instance_variable_set(ivar, [])
|
371
|
-
ret
|
372
465
|
end
|
373
466
|
end
|
374
467
|
|