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
@@ -1,115 +1,59 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
#
|
4
|
-
#
|
5
|
-
|
6
|
-
#
|
7
|
-
#
|
8
|
-
|
9
|
-
#
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
# include Validation
|
14
|
-
# validates_length_of :password, :minimum => 6
|
15
|
-
# end
|
16
|
-
module Validation
|
17
|
-
# Includes the Validation class methods into the including class.
|
18
|
-
def self.included(c)
|
19
|
-
c.extend ClassMethods
|
20
|
-
end
|
21
|
-
|
22
|
-
# Returns the validation errors associated with the object.
|
23
|
-
def errors
|
24
|
-
@errors ||= Errors.new
|
25
|
-
end
|
26
|
-
|
27
|
-
# Validates the object.
|
28
|
-
def validate
|
29
|
-
errors.clear
|
30
|
-
return false if before_validation == false
|
31
|
-
self.class.validate(self)
|
32
|
-
after_validation
|
33
|
-
nil
|
34
|
-
end
|
35
|
-
|
36
|
-
# Validates the object and returns true if no errors are reported.
|
37
|
-
def valid?
|
38
|
-
return false if validate == false
|
39
|
-
errors.empty?
|
40
|
-
end
|
1
|
+
module Sequel
|
2
|
+
class Model
|
3
|
+
# The Validation module houses a couple of subclasses used by Sequel's
|
4
|
+
# validation code.
|
5
|
+
module Validation
|
6
|
+
# Validation::Errors represents validation errors, a simple hash subclass
|
7
|
+
# with a few convenience methods.
|
8
|
+
class Errors < ::Hash
|
9
|
+
# Assign an array of messages for each attribute on access
|
10
|
+
def initialize
|
11
|
+
super{|h,k| h[k] = []}
|
12
|
+
end
|
41
13
|
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
def each(&block)
|
61
|
-
@errors.each(&block)
|
62
|
-
end
|
63
|
-
|
64
|
-
# Returns true if no errors are stored.
|
65
|
-
def empty?
|
66
|
-
@errors.empty?
|
67
|
-
end
|
68
|
-
|
69
|
-
# Returns an array of fully-formatted error messages.
|
70
|
-
def full_messages
|
71
|
-
@errors.inject([]) do |m, kv| att, errors = *kv
|
72
|
-
errors.each {|e| m << "#{att} #{e}"}
|
73
|
-
m
|
14
|
+
# Adds an error for the given attribute.
|
15
|
+
def add(att, msg)
|
16
|
+
self[att] << msg
|
17
|
+
end
|
18
|
+
|
19
|
+
# Returns an array of fully-formatted error messages.
|
20
|
+
def full_messages
|
21
|
+
inject([]) do |m, kv|
|
22
|
+
att, errors = *kv
|
23
|
+
errors.each {|e| m << "#{att} #{e}"}
|
24
|
+
m
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
# Returns the errors for the given attribute.
|
29
|
+
def on(att)
|
30
|
+
self[att]
|
31
|
+
end
|
74
32
|
end
|
75
|
-
end
|
76
|
-
|
77
|
-
# Returns the errors for the given attribute.
|
78
|
-
def on(att)
|
79
|
-
@errors[att]
|
80
|
-
end
|
81
|
-
alias_method :[], :on
|
82
|
-
|
83
|
-
# Returns size of errors array
|
84
|
-
def size
|
85
|
-
@errors.size
|
86
|
-
end
|
87
|
-
end
|
88
33
|
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
34
|
+
# The Generator class is used to generate validation definitions using
|
35
|
+
# the validates {} idiom.
|
36
|
+
class Generator
|
37
|
+
# Initializes a new generator.
|
38
|
+
def initialize(receiver ,&block)
|
39
|
+
@receiver = receiver
|
40
|
+
instance_eval(&block)
|
41
|
+
end
|
42
|
+
|
43
|
+
# Delegates method calls to the receiver by calling receiver.validates_xxx.
|
44
|
+
def method_missing(m, *args, &block)
|
45
|
+
@receiver.send(:"validates_#{m}", *args, &block)
|
46
|
+
end
|
47
|
+
end
|
96
48
|
end
|
97
49
|
|
98
|
-
# Delegates method calls to the receiver by calling receiver.validates_xxx.
|
99
|
-
def method_missing(m, *args, &block)
|
100
|
-
@receiver.send(:"validates_#{m}", *args, &block)
|
101
|
-
end
|
102
|
-
end
|
103
|
-
|
104
|
-
# Validation class methods.
|
105
|
-
module ClassMethods
|
106
50
|
# Returns true if validations are defined.
|
107
|
-
def has_validations?
|
51
|
+
def self.has_validations?
|
108
52
|
!validations.empty?
|
109
53
|
end
|
110
54
|
|
111
55
|
# Instructs the model to skip validations defined in superclasses
|
112
|
-
def skip_superclass_validations
|
56
|
+
def self.skip_superclass_validations
|
113
57
|
@skip_superclass_validations = true
|
114
58
|
end
|
115
59
|
|
@@ -130,12 +74,12 @@ module Validation
|
|
130
74
|
# validates_length_of :name, :minimum => 6
|
131
75
|
# validates_length_of :password, :minimum => 8
|
132
76
|
# end
|
133
|
-
def validates(&block)
|
134
|
-
Generator.new(self, &block)
|
77
|
+
def self.validates(&block)
|
78
|
+
Validation::Generator.new(self, &block)
|
135
79
|
end
|
136
80
|
|
137
81
|
# Validates the given instance.
|
138
|
-
def validate(o)
|
82
|
+
def self.validate(o)
|
139
83
|
if superclass.respond_to?(:validate) && !@skip_superclass_validations
|
140
84
|
superclass.validate(o)
|
141
85
|
end
|
@@ -146,7 +90,7 @@ module Validation
|
|
146
90
|
end
|
147
91
|
|
148
92
|
# Validates acceptance of an attribute.
|
149
|
-
def validates_acceptance_of(*atts)
|
93
|
+
def self.validates_acceptance_of(*atts)
|
150
94
|
opts = {
|
151
95
|
:message => 'is not accepted',
|
152
96
|
:allow_nil => true,
|
@@ -154,18 +98,20 @@ module Validation
|
|
154
98
|
}.merge!(atts.extract_options!)
|
155
99
|
|
156
100
|
validates_each(*atts) do |o, a, v|
|
101
|
+
next unless o.instance_eval(&if_proc(opts))
|
157
102
|
next if (v.nil? && opts[:allow_nil]) || (v.blank? && opts[:allow_blank])
|
158
103
|
o.errors[a] << opts[:message] unless v == opts[:accept]
|
159
104
|
end
|
160
105
|
end
|
161
106
|
|
162
107
|
# Validates confirmation of an attribute.
|
163
|
-
def validates_confirmation_of(*atts)
|
108
|
+
def self.validates_confirmation_of(*atts)
|
164
109
|
opts = {
|
165
110
|
:message => 'is not confirmed',
|
166
111
|
}.merge!(atts.extract_options!)
|
167
112
|
|
168
113
|
validates_each(*atts) do |o, a, v|
|
114
|
+
next unless o.instance_eval(&if_proc(opts))
|
169
115
|
next if (v.nil? && opts[:allow_nil]) || (v.blank? && opts[:allow_blank])
|
170
116
|
c = o.send(:"#{a}_confirmation")
|
171
117
|
o.errors[a] << opts[:message] unless v == c
|
@@ -179,12 +125,12 @@ module Validation
|
|
179
125
|
# validates_each :name, :password do |object, attribute, value|
|
180
126
|
# object.errors[attribute] << 'is not nice' unless value.nice?
|
181
127
|
# end
|
182
|
-
def validates_each(*atts, &block)
|
183
|
-
atts.each
|
128
|
+
def self.validates_each(*atts, &block)
|
129
|
+
atts.each{|a| validations[a] << block}
|
184
130
|
end
|
185
131
|
|
186
132
|
# Validates the format of an attribute.
|
187
|
-
def validates_format_of(*atts)
|
133
|
+
def self.validates_format_of(*atts)
|
188
134
|
opts = {
|
189
135
|
:message => 'is invalid',
|
190
136
|
}.merge!(atts.extract_options!)
|
@@ -194,13 +140,14 @@ module Validation
|
|
194
140
|
end
|
195
141
|
|
196
142
|
validates_each(*atts) do |o, a, v|
|
143
|
+
next unless o.instance_eval(&if_proc(opts))
|
197
144
|
next if (v.nil? && opts[:allow_nil]) || (v.blank? && opts[:allow_blank])
|
198
145
|
o.errors[a] << opts[:message] unless v.to_s =~ opts[:with]
|
199
146
|
end
|
200
147
|
end
|
201
148
|
|
202
149
|
# Validates the length of an attribute.
|
203
|
-
def validates_length_of(*atts)
|
150
|
+
def self.validates_length_of(*atts)
|
204
151
|
opts = {
|
205
152
|
:too_long => 'is too long',
|
206
153
|
:too_short => 'is too short',
|
@@ -208,6 +155,7 @@ module Validation
|
|
208
155
|
}.merge!(atts.extract_options!)
|
209
156
|
|
210
157
|
validates_each(*atts) do |o, a, v|
|
158
|
+
next unless o.instance_eval(&if_proc(opts))
|
211
159
|
next if (v.nil? && opts[:allow_nil]) || (v.blank? && opts[:allow_blank])
|
212
160
|
if m = opts[:maximum]
|
213
161
|
o.errors[a] << (opts[:message] || opts[:too_long]) unless v && v.size <= m
|
@@ -225,12 +173,13 @@ module Validation
|
|
225
173
|
end
|
226
174
|
|
227
175
|
# Validates whether an attribute is a number.
|
228
|
-
def validates_numericality_of(*atts)
|
176
|
+
def self.validates_numericality_of(*atts)
|
229
177
|
opts = {
|
230
178
|
:message => 'is not a number',
|
231
179
|
}.merge!(atts.extract_options!)
|
232
180
|
|
233
181
|
validates_each(*atts) do |o, a, v|
|
182
|
+
next unless o.instance_eval(&if_proc(opts))
|
234
183
|
next if (v.nil? && opts[:allow_nil]) || (v.blank? && opts[:allow_blank])
|
235
184
|
begin
|
236
185
|
if opts[:only_integer]
|
@@ -245,12 +194,13 @@ module Validation
|
|
245
194
|
end
|
246
195
|
|
247
196
|
# Validates the presence of an attribute.
|
248
|
-
def validates_presence_of(*atts)
|
197
|
+
def self.validates_presence_of(*atts)
|
249
198
|
opts = {
|
250
199
|
:message => 'is not present',
|
251
200
|
}.merge!(atts.extract_options!)
|
252
201
|
|
253
202
|
validates_each(*atts) do |o, a, v|
|
203
|
+
next unless o.instance_eval(&if_proc(opts))
|
254
204
|
o.errors[a] << opts[:message] unless v && !v.blank?
|
255
205
|
end
|
256
206
|
end
|
@@ -258,12 +208,13 @@ module Validation
|
|
258
208
|
# Validates only if the fields in the model (specified by atts) are
|
259
209
|
# unique in the database. You should also add a unique index in the
|
260
210
|
# database, as this suffers from a fairly obvious race condition.
|
261
|
-
def validates_uniqueness_of(*atts)
|
211
|
+
def self.validates_uniqueness_of(*atts)
|
262
212
|
opts = {
|
263
213
|
:message => 'is already taken',
|
264
214
|
}.merge!(atts.extract_options!)
|
265
215
|
|
266
216
|
validates_each(*atts) do |o, a, v|
|
217
|
+
next unless o.instance_eval(&if_proc(opts))
|
267
218
|
next if v.blank?
|
268
219
|
num_dups = o.class.filter(a => v).count
|
269
220
|
allow = if num_dups == 0
|
@@ -287,8 +238,43 @@ module Validation
|
|
287
238
|
end
|
288
239
|
|
289
240
|
# Returns the validations hash for the class.
|
290
|
-
def validations
|
241
|
+
def self.validations
|
291
242
|
@validations ||= Hash.new {|h, k| h[k] = []}
|
292
243
|
end
|
244
|
+
|
245
|
+
### Private Class Methods ###
|
246
|
+
|
247
|
+
def self.if_proc(opts)
|
248
|
+
case opts[:if]
|
249
|
+
when Symbol then proc{send opts[:if]}
|
250
|
+
when Proc then opts[:if]
|
251
|
+
when nil then proc{true}
|
252
|
+
else raise(::Sequel::Error, "invalid value for :if validation option")
|
253
|
+
end
|
254
|
+
end
|
255
|
+
|
256
|
+
private_class_method :if_proc
|
257
|
+
|
258
|
+
### Instance Methods ###
|
259
|
+
|
260
|
+
# Returns the validation errors associated with the object.
|
261
|
+
def errors
|
262
|
+
@errors ||= Validation::Errors.new
|
263
|
+
end
|
264
|
+
|
265
|
+
# Validates the object.
|
266
|
+
def validate
|
267
|
+
errors.clear
|
268
|
+
return false if before_validation == false
|
269
|
+
self.class.validate(self)
|
270
|
+
after_validation
|
271
|
+
nil
|
272
|
+
end
|
273
|
+
|
274
|
+
# Validates the object and returns true if no errors are reported.
|
275
|
+
def valid?
|
276
|
+
return false if validate == false
|
277
|
+
errors.empty?
|
278
|
+
end
|
293
279
|
end
|
294
280
|
end
|
@@ -40,9 +40,9 @@ describe Sequel::Model::Associations::AssociationReflection, "#reciprocal" do
|
|
40
40
|
it "should use the :reciprocal value if present" do
|
41
41
|
@c = Class.new(Sequel::Model)
|
42
42
|
@d = Class.new(Sequel::Model)
|
43
|
-
@c.many_to_one :c, :class=>@d, :reciprocal
|
43
|
+
@c.many_to_one :c, :class=>@d, :reciprocal=>:xx
|
44
44
|
@c.association_reflection(:c).should include(:reciprocal)
|
45
|
-
@c.association_reflection(:c).reciprocal.should ==
|
45
|
+
@c.association_reflection(:c).reciprocal.should == :xx
|
46
46
|
end
|
47
47
|
|
48
48
|
it "should figure out the reciprocal if the :reciprocal value is not present" do
|
@@ -55,13 +55,13 @@ describe Sequel::Model::Associations::AssociationReflection, "#reciprocal" do
|
|
55
55
|
ParParentThree.many_to_many :par_parents
|
56
56
|
|
57
57
|
ParParent.association_reflection(:par_parent_two).should_not include(:reciprocal)
|
58
|
-
ParParent.association_reflection(:par_parent_two).reciprocal.should ==
|
58
|
+
ParParent.association_reflection(:par_parent_two).reciprocal.should == :par_parents
|
59
59
|
ParParentTwo.association_reflection(:par_parents).should_not include(:reciprocal)
|
60
|
-
ParParentTwo.association_reflection(:par_parents).reciprocal.should ==
|
60
|
+
ParParentTwo.association_reflection(:par_parents).reciprocal.should == :par_parent_two
|
61
61
|
ParParent.association_reflection(:par_parent_threes).should_not include(:reciprocal)
|
62
|
-
ParParent.association_reflection(:par_parent_threes).reciprocal.should ==
|
62
|
+
ParParent.association_reflection(:par_parent_threes).reciprocal.should == :par_parents
|
63
63
|
ParParentThree.association_reflection(:par_parents).should_not include(:reciprocal)
|
64
|
-
ParParentThree.association_reflection(:par_parents).reciprocal.should ==
|
64
|
+
ParParentThree.association_reflection(:par_parents).reciprocal.should == :par_parent_threes
|
65
65
|
end
|
66
66
|
end
|
67
67
|
|
data/spec/associations_spec.rb
CHANGED
@@ -144,12 +144,12 @@ describe Sequel::Model, "many_to_one" do
|
|
144
144
|
d = @c2.create_new(:id => 1)
|
145
145
|
MODEL_DB.reset
|
146
146
|
d.parent_id = 234
|
147
|
-
d.
|
147
|
+
d.associations[:parent].should == nil
|
148
148
|
ds = @c2.dataset
|
149
149
|
def ds.fetch_rows(sql, &block); MODEL_DB.sqls << sql; yield({:id=>234}) end
|
150
150
|
e = d.parent
|
151
151
|
MODEL_DB.sqls.should == ["SELECT nodes.* FROM nodes WHERE (id = 234) LIMIT 1"]
|
152
|
-
d.
|
152
|
+
d.associations[:parent].should == e
|
153
153
|
end
|
154
154
|
|
155
155
|
it "should set cached instance variable when assigned" do
|
@@ -157,10 +157,10 @@ describe Sequel::Model, "many_to_one" do
|
|
157
157
|
|
158
158
|
d = @c2.create(:id => 1)
|
159
159
|
MODEL_DB.reset
|
160
|
-
d.
|
160
|
+
d.associations[:parent].should == nil
|
161
161
|
d.parent = @c2.new(:id => 234)
|
162
162
|
e = d.parent
|
163
|
-
d.
|
163
|
+
d.associations[:parent].should == e
|
164
164
|
MODEL_DB.sqls.should == []
|
165
165
|
end
|
166
166
|
|
@@ -169,7 +169,7 @@ describe Sequel::Model, "many_to_one" do
|
|
169
169
|
|
170
170
|
d = @c2.create(:id => 1, :parent_id => 234)
|
171
171
|
MODEL_DB.reset
|
172
|
-
d.
|
172
|
+
d.associations[:parent] = 42
|
173
173
|
d.parent.should == 42
|
174
174
|
MODEL_DB.sqls.should == []
|
175
175
|
end
|
@@ -180,7 +180,7 @@ describe Sequel::Model, "many_to_one" do
|
|
180
180
|
d = @c2.create(:id => 1)
|
181
181
|
MODEL_DB.reset
|
182
182
|
d.parent_id = 234
|
183
|
-
d.
|
183
|
+
d.associations[:parent] = 42
|
184
184
|
d.parent(true).should_not == 42
|
185
185
|
MODEL_DB.sqls.should == ["SELECT nodes.* FROM nodes WHERE (id = 234) LIMIT 1"]
|
186
186
|
end
|
@@ -234,18 +234,31 @@ describe Sequel::Model, "many_to_one" do
|
|
234
234
|
MODEL_DB.sqls.should == []
|
235
235
|
end
|
236
236
|
|
237
|
+
it "should not create the setter method if :read_only option is used" do
|
238
|
+
@c2.many_to_one :parent, :class => @c2, :read_only=>true
|
239
|
+
@c2.instance_methods.should(include('parent'))
|
240
|
+
@c2.instance_methods.should_not(include('parent='))
|
241
|
+
end
|
242
|
+
|
243
|
+
it "should raise an error if trying to set a model object that doesn't have a valid primary key" do
|
244
|
+
@c2.many_to_one :parent, :class => @c2
|
245
|
+
p = @c2.new
|
246
|
+
c = @c2.load(:id=>123)
|
247
|
+
proc{c.parent = p}.should raise_error(Sequel::Error)
|
248
|
+
end
|
249
|
+
|
237
250
|
it "should have belongs_to alias" do
|
238
251
|
@c2.belongs_to :parent, :class => @c2
|
239
252
|
|
240
253
|
d = @c2.create_new(:id => 1)
|
241
254
|
MODEL_DB.reset
|
242
255
|
d.parent_id = 234
|
243
|
-
d.
|
256
|
+
d.associations[:parent].should == nil
|
244
257
|
ds = @c2.dataset
|
245
258
|
def ds.fetch_rows(sql, &block); MODEL_DB.sqls << sql; yield({:id=>234}) end
|
246
259
|
e = d.parent
|
247
260
|
MODEL_DB.sqls.should == ["SELECT nodes.* FROM nodes WHERE (id = 234) LIMIT 1"]
|
248
|
-
d.
|
261
|
+
d.associations[:parent].should == e
|
249
262
|
end
|
250
263
|
end
|
251
264
|
|
@@ -350,6 +363,26 @@ describe Sequel::Model, "one_to_many" do
|
|
350
363
|
MODEL_DB.sqls.should == ['UPDATE attributes SET node_id = NULL WHERE (id = 2345)']
|
351
364
|
end
|
352
365
|
|
366
|
+
it "should raise an error in add_ and remove_ if the passed object returns false to save (is not valid)" do
|
367
|
+
@c2.one_to_many :attributes, :class => @c1
|
368
|
+
n = @c2.new(:id => 1234)
|
369
|
+
a = @c1.new(:id => 2345)
|
370
|
+
def a.valid?; false; end
|
371
|
+
proc{n.add_attribute(a)}.should raise_error(Sequel::Error)
|
372
|
+
proc{n.remove_attribute(a)}.should raise_error(Sequel::Error)
|
373
|
+
end
|
374
|
+
|
375
|
+
it "should raise an error if the model object doesn't have a valid primary key" do
|
376
|
+
@c2.one_to_many :attributes, :class => @c1
|
377
|
+
a = @c2.new
|
378
|
+
n = @c1.load(:id=>123)
|
379
|
+
proc{a.attributes_dataset}.should raise_error(Sequel::Error)
|
380
|
+
proc{a.attributes}.should raise_error(Sequel::Error)
|
381
|
+
proc{a.add_attribute(n)}.should raise_error(Sequel::Error)
|
382
|
+
proc{a.remove_attribute(n)}.should raise_error(Sequel::Error)
|
383
|
+
proc{a.remove_all_attributes}.should raise_error(Sequel::Error)
|
384
|
+
end
|
385
|
+
|
353
386
|
it "should support a select option" do
|
354
387
|
@c2.one_to_many :attributes, :class => @c1, :select => [:id, :name]
|
355
388
|
|
@@ -414,14 +447,33 @@ describe Sequel::Model, "one_to_many" do
|
|
414
447
|
MODEL_DB.sqls.should == ['SELECT attributes.* FROM attributes WHERE ((node_id = 1234) AND (xxx IS NULL)) ORDER BY kind']
|
415
448
|
end
|
416
449
|
|
450
|
+
it "should have the block argument affect the _dataset method" do
|
451
|
+
@c2.one_to_many :attributes, :class => @c1 do |ds|
|
452
|
+
ds.filter(:xxx => 456)
|
453
|
+
end
|
454
|
+
@c2.new(:id => 1234).attributes_dataset.sql.should == 'SELECT attributes.* FROM attributes WHERE ((node_id = 1234) AND (xxx = 456))'
|
455
|
+
end
|
456
|
+
|
457
|
+
it "should support a :limit option" do
|
458
|
+
@c2.one_to_many :attributes, :class => @c1 , :limit=>10
|
459
|
+
@c2.new(:id => 1234).attributes_dataset.sql.should == 'SELECT attributes.* FROM attributes WHERE (node_id = 1234) LIMIT 10'
|
460
|
+
@c2.one_to_many :attributes, :class => @c1 , :limit=>[10,10]
|
461
|
+
@c2.new(:id => 1234).attributes_dataset.sql.should == 'SELECT attributes.* FROM attributes WHERE (node_id = 1234) LIMIT 10 OFFSET 10'
|
462
|
+
end
|
463
|
+
|
464
|
+
it "should have the :eager option affect the _dataset method" do
|
465
|
+
@c2.one_to_many :attributes, :class => @c2 , :eager=>:attributes
|
466
|
+
@c2.new(:id => 1234).attributes_dataset.opts[:eager].should == {:attributes=>nil}
|
467
|
+
end
|
468
|
+
|
417
469
|
it "should set cached instance variable when accessed" do
|
418
470
|
@c2.one_to_many :attributes, :class => @c1
|
419
471
|
|
420
472
|
n = @c2.new(:id => 1234)
|
421
473
|
MODEL_DB.reset
|
422
|
-
n.
|
474
|
+
n.associations.include?(:attributes).should == false
|
423
475
|
atts = n.attributes
|
424
|
-
atts.should == n.
|
476
|
+
atts.should == n.associations[:attributes]
|
425
477
|
MODEL_DB.sqls.should == ['SELECT attributes.* FROM attributes WHERE (node_id = 1234)']
|
426
478
|
end
|
427
479
|
|
@@ -430,7 +482,7 @@ describe Sequel::Model, "one_to_many" do
|
|
430
482
|
|
431
483
|
n = @c2.new(:id => 1234)
|
432
484
|
MODEL_DB.reset
|
433
|
-
n.
|
485
|
+
n.associations[:attributes] = 42
|
434
486
|
n.attributes.should == 42
|
435
487
|
MODEL_DB.sqls.should == []
|
436
488
|
end
|
@@ -440,7 +492,7 @@ describe Sequel::Model, "one_to_many" do
|
|
440
492
|
|
441
493
|
n = @c2.new(:id => 1234)
|
442
494
|
MODEL_DB.reset
|
443
|
-
n.
|
495
|
+
n.associations[:attributes] = 42
|
444
496
|
n.attributes(true).should_not == 42
|
445
497
|
MODEL_DB.sqls.should == ['SELECT attributes.* FROM attributes WHERE (node_id = 1234)']
|
446
498
|
end
|
@@ -452,7 +504,7 @@ describe Sequel::Model, "one_to_many" do
|
|
452
504
|
att = @c1.new(:id => 345)
|
453
505
|
MODEL_DB.reset
|
454
506
|
a = []
|
455
|
-
n.
|
507
|
+
n.associations[:attributes] = a
|
456
508
|
n.add_attribute(att)
|
457
509
|
a.should == [att]
|
458
510
|
end
|
@@ -470,11 +522,11 @@ describe Sequel::Model, "one_to_many" do
|
|
470
522
|
it "should remove item from cached instance variable if it exists when calling remove_" do
|
471
523
|
@c2.one_to_many :attributes, :class => @c1
|
472
524
|
|
473
|
-
n = @c2.
|
474
|
-
att = @c1.
|
525
|
+
n = @c2.load(:id => 1234)
|
526
|
+
att = @c1.load(:id => 345)
|
475
527
|
MODEL_DB.reset
|
476
528
|
a = [att]
|
477
|
-
n.
|
529
|
+
n.associations[:attributes] = a
|
478
530
|
n.remove_attribute(att)
|
479
531
|
a.should == []
|
480
532
|
end
|
@@ -485,12 +537,22 @@ describe Sequel::Model, "one_to_many" do
|
|
485
537
|
|
486
538
|
n = @c2.new(:id => 1234)
|
487
539
|
att = @c1.new(:id => 345)
|
488
|
-
att.
|
540
|
+
att.associations[:node] = n
|
489
541
|
att.node.should == n
|
490
542
|
n.remove_attribute(att)
|
491
543
|
att.node.should == nil
|
492
544
|
end
|
493
545
|
|
546
|
+
it "should not create the add_, remove_, or remove_all_ methods if :read_only option is used" do
|
547
|
+
@c2.one_to_many :attributes, :class => @c1, :read_only=>true
|
548
|
+
im = @c2.instance_methods
|
549
|
+
im.should(include('attributes'))
|
550
|
+
im.should(include('attributes_dataset'))
|
551
|
+
im.should_not(include('add_attribute'))
|
552
|
+
im.should_not(include('remove_attribute'))
|
553
|
+
im.should_not(include('remove_all_attributes'))
|
554
|
+
end
|
555
|
+
|
494
556
|
it "should have has_many alias" do
|
495
557
|
@c2.has_many :attributes, :class => @c1
|
496
558
|
|
@@ -521,7 +583,7 @@ describe Sequel::Model, "one_to_many" do
|
|
521
583
|
end
|
522
584
|
|
523
585
|
it "should use an explicit reciprocal instance variable if given" do
|
524
|
-
@c2.one_to_many :attributes, :class => @c1, :key => :node_id, :reciprocal
|
586
|
+
@c2.one_to_many :attributes, :class => @c1, :key => :node_id, :reciprocal=>:wxyz
|
525
587
|
|
526
588
|
n = @c2.new(:id => 1234)
|
527
589
|
atts = n.attributes
|
@@ -530,7 +592,7 @@ describe Sequel::Model, "one_to_many" do
|
|
530
592
|
atts.size.should == 1
|
531
593
|
atts.first.should be_a_kind_of(@c1)
|
532
594
|
atts.first.values.should == {}
|
533
|
-
atts.first.
|
595
|
+
atts.first.associations[:wxyz].should == n
|
534
596
|
|
535
597
|
MODEL_DB.sqls.length.should == 1
|
536
598
|
end
|
@@ -545,7 +607,7 @@ describe Sequel::Model, "one_to_many" do
|
|
545
607
|
@c2.one_to_many :attributes, :class => @c1
|
546
608
|
node = @c2.new(:id => 1234)
|
547
609
|
node.remove_all_attributes
|
548
|
-
node.
|
610
|
+
node.associations[:attributes].should == []
|
549
611
|
end
|
550
612
|
|
551
613
|
it "remove_all should return the array of previously associated items if the cached instance variable exists" do
|
@@ -555,9 +617,9 @@ describe Sequel::Model, "one_to_many" do
|
|
555
617
|
d = @c1.dataset
|
556
618
|
def d.fetch_rows(s); end
|
557
619
|
node.attributes.should == []
|
558
|
-
def attrib.save!; end
|
620
|
+
def attrib.save!; self end
|
559
621
|
node.add_attribute(attrib)
|
560
|
-
node.
|
622
|
+
node.associations[:attributes].should == [attrib]
|
561
623
|
node.remove_all_attributes.should == [attrib]
|
562
624
|
end
|
563
625
|
|
@@ -577,11 +639,69 @@ describe Sequel::Model, "one_to_many" do
|
|
577
639
|
node = @c2.new(:id => 1234)
|
578
640
|
node.attributes.should == []
|
579
641
|
attrib.node.should == nil
|
580
|
-
def attrib.save!; end
|
642
|
+
def attrib.save!; self end
|
581
643
|
node.add_attribute(attrib)
|
582
|
-
attrib.
|
644
|
+
attrib.associations[:node].should == node
|
583
645
|
node.remove_all_attributes
|
584
|
-
attrib.
|
646
|
+
attrib.associations.fetch(:node, 2).should == nil
|
647
|
+
end
|
648
|
+
|
649
|
+
it "should add a getter method if the :one_to_one option is true" do
|
650
|
+
@c2.one_to_many :attributes, :class => @c1, :one_to_one=>true
|
651
|
+
att = @c2.new(:id => 1234).attribute
|
652
|
+
MODEL_DB.sqls.should == ['SELECT attributes.* FROM attributes WHERE (node_id = 1234)']
|
653
|
+
att.should be_a_kind_of(@c1)
|
654
|
+
att.values.should == {}
|
655
|
+
end
|
656
|
+
|
657
|
+
it "should not add a getter method if the :one_to_one option is true and :read_only option is true" do
|
658
|
+
@c2.one_to_many :attributes, :class => @c1, :one_to_one=>true, :read_only=>true
|
659
|
+
im = @c2.instance_methods
|
660
|
+
im.should(include('attribute'))
|
661
|
+
im.should_not(include('attribute='))
|
662
|
+
end
|
663
|
+
|
664
|
+
it "should have the getter method raise an error if more than one record is found" do
|
665
|
+
@c2.one_to_many :attributes, :class => @c1, :one_to_one=>true
|
666
|
+
d = @c1.dataset
|
667
|
+
def d.fetch_rows(s); 2.times{yield Hash.new} end
|
668
|
+
proc{@c2.new(:id => 1234).attribute}.should raise_error(Sequel::Error)
|
669
|
+
end
|
670
|
+
|
671
|
+
it "should add a setter method if the :one_to_one option is true" do
|
672
|
+
@c2.one_to_many :attributes, :class => @c1, :one_to_one=>true
|
673
|
+
attrib = @c1.new(:id=>3)
|
674
|
+
d = @c1.dataset
|
675
|
+
def d.fetch_rows(s); yield({:id=>3}) end
|
676
|
+
@c2.new(:id => 1234).attribute = attrib
|
677
|
+
['INSERT INTO attributes (node_id, id) VALUES (1234, 3)',
|
678
|
+
'INSERT INTO attributes (id, node_id) VALUES (3, 1234)'].should(include(MODEL_DB.sqls.first))
|
679
|
+
MODEL_DB.sqls.last.should == 'UPDATE attributes SET node_id = NULL WHERE ((node_id = 1234) AND (id != 3))'
|
680
|
+
MODEL_DB.sqls.length.should == 2
|
681
|
+
@c2.new(:id => 1234).attribute.should == attrib
|
682
|
+
MODEL_DB.sqls.clear
|
683
|
+
attrib = @c1.load(:id=>3)
|
684
|
+
@c2.new(:id => 1234).attribute = attrib
|
685
|
+
MODEL_DB.sqls.should == ['UPDATE attributes SET node_id = 1234, id = 3 WHERE (id = 3)',
|
686
|
+
'UPDATE attributes SET node_id = NULL WHERE ((node_id = 1234) AND (id != 3))']
|
687
|
+
end
|
688
|
+
|
689
|
+
it "should raise an error if the one_to_one getter would be the same as the association name" do
|
690
|
+
proc{@c2.one_to_many :song, :class => @c1, :one_to_one=>true}.should raise_error(Sequel::Error)
|
691
|
+
end
|
692
|
+
|
693
|
+
it "should not create remove_ and remove_all methods if :one_to_one option is used" do
|
694
|
+
@c2.one_to_many :attributes, :class => @c1, :one_to_one=>true
|
695
|
+
@c2.new.should_not(respond_to(:remove_attribute))
|
696
|
+
@c2.new.should_not(respond_to(:remove_all_attributes))
|
697
|
+
end
|
698
|
+
|
699
|
+
it "should make non getter and setter methods private if :one_to_one option is used" do
|
700
|
+
@c2.one_to_many :attributes, :class => @c1, :one_to_one=>true do |ds| end
|
701
|
+
meths = @c2.private_instance_methods(false)
|
702
|
+
meths.should(include("attributes"))
|
703
|
+
meths.should(include("add_attribute"))
|
704
|
+
meths.should(include("attributes_dataset"))
|
585
705
|
end
|
586
706
|
end
|
587
707
|
|
@@ -723,6 +843,25 @@ describe Sequel::Model, "many_to_many" do
|
|
723
843
|
MODEL_DB.sqls.first.should == 'SELECT attributes.* FROM attributes INNER JOIN attributes_nodes ON ((attributes_nodes.attribute_id = attributes.id) AND (attributes_nodes.node_id = 1234)) WHERE (xxx = 555) ORDER BY blah1, blah2'
|
724
844
|
end
|
725
845
|
|
846
|
+
it "should have the block argument affect the _dataset method" do
|
847
|
+
@c2.many_to_many :attributes, :class => @c1 do |ds|
|
848
|
+
ds.filter(:xxx => 456)
|
849
|
+
end
|
850
|
+
@c2.new(:id => 1234).attributes_dataset.sql.should == 'SELECT attributes.* FROM attributes INNER JOIN attributes_nodes ON ((attributes_nodes.attribute_id = attributes.id) AND (attributes_nodes.node_id = 1234)) WHERE (xxx = 456)'
|
851
|
+
end
|
852
|
+
|
853
|
+
it "should support a :limit option" do
|
854
|
+
@c2.many_to_many :attributes, :class => @c1 , :limit=>10
|
855
|
+
@c2.new(:id => 1234).attributes_dataset.sql.should == 'SELECT attributes.* FROM attributes INNER JOIN attributes_nodes ON ((attributes_nodes.attribute_id = attributes.id) AND (attributes_nodes.node_id = 1234)) LIMIT 10'
|
856
|
+
@c2.many_to_many :attributes, :class => @c1 , :limit=>[10, 10]
|
857
|
+
@c2.new(:id => 1234).attributes_dataset.sql.should == 'SELECT attributes.* FROM attributes INNER JOIN attributes_nodes ON ((attributes_nodes.attribute_id = attributes.id) AND (attributes_nodes.node_id = 1234)) LIMIT 10 OFFSET 10'
|
858
|
+
end
|
859
|
+
|
860
|
+
it "should have the :eager option affect the _dataset method" do
|
861
|
+
@c2.many_to_many :attributes, :class => @c2 , :eager=>:attributes
|
862
|
+
@c2.new(:id => 1234).attributes_dataset.opts[:eager].should == {:attributes=>nil}
|
863
|
+
end
|
864
|
+
|
726
865
|
it "should define an add_ method" do
|
727
866
|
@c2.many_to_many :attributes, :class => @c1
|
728
867
|
|
@@ -743,6 +882,25 @@ describe Sequel::Model, "many_to_many" do
|
|
743
882
|
MODEL_DB.sqls.first.should == 'DELETE FROM attributes_nodes WHERE ((node_id = 1234) AND (attribute_id = 2345))'
|
744
883
|
end
|
745
884
|
|
885
|
+
it "should raise an error if the model object doesn't have a valid primary key" do
|
886
|
+
@c2.many_to_many :attributes, :class => @c1
|
887
|
+
a = @c2.new
|
888
|
+
n = @c1.load(:id=>123)
|
889
|
+
proc{a.attributes_dataset}.should raise_error(Sequel::Error)
|
890
|
+
proc{a.attributes}.should raise_error(Sequel::Error)
|
891
|
+
proc{a.add_attribute(n)}.should raise_error(Sequel::Error)
|
892
|
+
proc{a.remove_attribute(n)}.should raise_error(Sequel::Error)
|
893
|
+
proc{a.remove_all_attributes}.should raise_error(Sequel::Error)
|
894
|
+
end
|
895
|
+
|
896
|
+
it "should raise an error if trying to add/remove a model object that doesn't have a valid primary key" do
|
897
|
+
@c2.many_to_many :attributes, :class => @c1
|
898
|
+
n = @c1.new
|
899
|
+
a = @c2.load(:id=>123)
|
900
|
+
proc{a.add_attribute(n)}.should raise_error(Sequel::Error)
|
901
|
+
proc{a.remove_attribute(n)}.should raise_error(Sequel::Error)
|
902
|
+
end
|
903
|
+
|
746
904
|
it "should provide an array with all members of the association" do
|
747
905
|
@c2.many_to_many :attributes, :class => @c1
|
748
906
|
|
@@ -760,9 +918,9 @@ describe Sequel::Model, "many_to_many" do
|
|
760
918
|
|
761
919
|
n = @c2.new(:id => 1234)
|
762
920
|
MODEL_DB.reset
|
763
|
-
n.
|
921
|
+
n.associations.include?(:attributes).should == false
|
764
922
|
atts = n.attributes
|
765
|
-
atts.should == n.
|
923
|
+
atts.should == n.associations[:attributes]
|
766
924
|
MODEL_DB.sqls.length.should == 1
|
767
925
|
end
|
768
926
|
|
@@ -771,7 +929,7 @@ describe Sequel::Model, "many_to_many" do
|
|
771
929
|
|
772
930
|
n = @c2.new(:id => 1234)
|
773
931
|
MODEL_DB.reset
|
774
|
-
n.
|
932
|
+
n.associations[:attributes] = 42
|
775
933
|
n.attributes.should == 42
|
776
934
|
MODEL_DB.sqls.should == []
|
777
935
|
end
|
@@ -781,7 +939,7 @@ describe Sequel::Model, "many_to_many" do
|
|
781
939
|
|
782
940
|
n = @c2.new(:id => 1234)
|
783
941
|
MODEL_DB.reset
|
784
|
-
n.
|
942
|
+
n.associations[:attributes] = 42
|
785
943
|
n.attributes(true).should_not == 42
|
786
944
|
MODEL_DB.sqls.length.should == 1
|
787
945
|
end
|
@@ -793,7 +951,7 @@ describe Sequel::Model, "many_to_many" do
|
|
793
951
|
att = @c1.new(:id => 345)
|
794
952
|
MODEL_DB.reset
|
795
953
|
a = []
|
796
|
-
n.
|
954
|
+
n.associations[:attributes] = a
|
797
955
|
n.add_attribute(att)
|
798
956
|
a.should == [att]
|
799
957
|
end
|
@@ -804,7 +962,7 @@ describe Sequel::Model, "many_to_many" do
|
|
804
962
|
|
805
963
|
n = @c2.new(:id => 1234)
|
806
964
|
att = @c1.new(:id => 345)
|
807
|
-
att.
|
965
|
+
att.associations[:nodes] = []
|
808
966
|
n.add_attribute(att)
|
809
967
|
att.nodes.should == [n]
|
810
968
|
end
|
@@ -816,7 +974,7 @@ describe Sequel::Model, "many_to_many" do
|
|
816
974
|
att = @c1.new(:id => 345)
|
817
975
|
MODEL_DB.reset
|
818
976
|
a = [att]
|
819
|
-
n.
|
977
|
+
n.associations[:attributes] = a
|
820
978
|
n.remove_attribute(att)
|
821
979
|
a.should == []
|
822
980
|
end
|
@@ -827,11 +985,21 @@ describe Sequel::Model, "many_to_many" do
|
|
827
985
|
|
828
986
|
n = @c2.new(:id => 1234)
|
829
987
|
att = @c1.new(:id => 345)
|
830
|
-
att.
|
988
|
+
att.associations[:nodes] = [n]
|
831
989
|
n.remove_attribute(att)
|
832
990
|
att.nodes.should == []
|
833
991
|
end
|
834
992
|
|
993
|
+
it "should not create the add_, remove_, or remove_all_ methods if :read_only option is used" do
|
994
|
+
@c2.many_to_many :attributes, :class => @c1, :read_only=>true
|
995
|
+
im = @c2.instance_methods
|
996
|
+
im.should(include('attributes'))
|
997
|
+
im.should(include('attributes_dataset'))
|
998
|
+
im.should_not(include('add_attribute'))
|
999
|
+
im.should_not(include('remove_attribute'))
|
1000
|
+
im.should_not(include('remove_all_attributes'))
|
1001
|
+
end
|
1002
|
+
|
835
1003
|
it "should have has_and_belongs_to_many alias" do
|
836
1004
|
@c2.has_and_belongs_to_many :attributes, :class => @c1
|
837
1005
|
|
@@ -851,7 +1019,7 @@ describe Sequel::Model, "many_to_many" do
|
|
851
1019
|
@c2.many_to_many :attributes, :class => @c1
|
852
1020
|
node = @c2.new(:id => 1234)
|
853
1021
|
node.remove_all_attributes
|
854
|
-
node.
|
1022
|
+
node.associations[:attributes].should == []
|
855
1023
|
end
|
856
1024
|
|
857
1025
|
it "remove_all should return the array of previously associated items if the cached instance variable exists" do
|
@@ -862,7 +1030,7 @@ describe Sequel::Model, "many_to_many" do
|
|
862
1030
|
def d.fetch_rows(s); end
|
863
1031
|
node.attributes.should == []
|
864
1032
|
node.add_attribute(attrib)
|
865
|
-
node.
|
1033
|
+
node.associations[:attributes].should == [attrib]
|
866
1034
|
node.remove_all_attributes.should == [attrib]
|
867
1035
|
end
|
868
1036
|
|
@@ -883,9 +1051,9 @@ describe Sequel::Model, "many_to_many" do
|
|
883
1051
|
node.attributes.should == []
|
884
1052
|
attrib.nodes.should == []
|
885
1053
|
node.add_attribute(attrib)
|
886
|
-
attrib.
|
1054
|
+
attrib.associations[:nodes].should == [node]
|
887
1055
|
node.remove_all_attributes
|
888
|
-
attrib.
|
1056
|
+
attrib.associations[:nodes].should == []
|
889
1057
|
end
|
890
1058
|
end
|
891
1059
|
|