sequel 2.0.1 → 2.1.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/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
|
|