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
@@ -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
|
|