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.
@@ -1,115 +1,59 @@
1
- # The Validations module provides validation capabilities as a mixin. When
2
- # included into a class, it enhances the class with class and instance
3
- # methods for defining validations and validating class instances.
4
- #
5
- # The Validation emulates the validation capabilities of ActiveRecord, and
6
- # provides methods for validating acceptance, confirmation, presence, format,
7
- # length and numericality of attributes.
8
- #
9
- # To use validations, you need to include the Validation module in your
10
- # class:
11
- #
12
- # class MyClass
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
- # Validation::Errors represents validation errors.
43
- class Errors
44
- # Initializes a new instance of validation errors.
45
- def initialize
46
- @errors = Hash.new {|h, k| h[k] = []}
47
- end
48
-
49
- # Adds an error for the given attribute.
50
- def add(att, msg)
51
- @errors[att] << msg
52
- end
53
-
54
- # Clears all errors.
55
- def clear
56
- @errors.clear
57
- end
58
-
59
- # Iterates over errors
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
- # The Generator class is used to generate validation definitions using
90
- # the validates {} idiom.
91
- class Generator
92
- # Initializes a new generator.
93
- def initialize(receiver ,&block)
94
- @receiver = receiver
95
- instance_eval(&block)
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 {|a| validations[a] << block}
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=>:@xx
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 == :@xx
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 == :@par_parents
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 == :@par_parent_two
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 == :@par_parents
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 == :@par_parent_threes
64
+ ParParentThree.association_reflection(:par_parents).reciprocal.should == :par_parent_threes
65
65
  end
66
66
  end
67
67
 
@@ -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.instance_variable_get("@parent").should == nil
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.instance_variable_get("@parent").should == e
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.instance_variable_get("@parent").should == nil
160
+ d.associations[:parent].should == nil
161
161
  d.parent = @c2.new(:id => 234)
162
162
  e = d.parent
163
- d.instance_variable_get("@parent").should == e
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.instance_variable_set(:@parent, 42)
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.instance_variable_set(:@parent, 42)
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.instance_variable_get("@parent").should == nil
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.instance_variable_get("@parent").should == e
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.instance_variables.include?("@attributes").should == false
474
+ n.associations.include?(:attributes).should == false
423
475
  atts = n.attributes
424
- atts.should == n.instance_variable_get("@attributes")
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.instance_variable_set(:@attributes, 42)
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.instance_variable_set(:@attributes, 42)
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.instance_variable_set(:@attributes, a)
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.new(:id => 1234)
474
- att = @c1.new(:id => 345)
525
+ n = @c2.load(:id => 1234)
526
+ att = @c1.load(:id => 345)
475
527
  MODEL_DB.reset
476
528
  a = [att]
477
- n.instance_variable_set(:@attributes, a)
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.instance_variable_set(:@node, n)
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=>'@wxyz'
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.instance_variable_get('@wxyz').should == n
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.instance_variable_get(:@attributes).should == []
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.instance_variable_get(:@attributes).should == [attrib]
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.instance_variable_get(:@node).should == node
644
+ attrib.associations[:node].should == node
583
645
  node.remove_all_attributes
584
- attrib.instance_variable_get(:@node).should == :null
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.instance_variables.include?("@attributes").should == false
921
+ n.associations.include?(:attributes).should == false
764
922
  atts = n.attributes
765
- atts.should == n.instance_variable_get("@attributes")
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.instance_variable_set(:@attributes, 42)
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.instance_variable_set(:@attributes, 42)
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.instance_variable_set(:@attributes, a)
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.instance_variable_set(:@nodes, [])
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.instance_variable_set(:@attributes, a)
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.instance_variable_set(:@nodes, [n])
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.instance_variable_get(:@attributes).should == []
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.instance_variable_get(:@attributes).should == [attrib]
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.instance_variable_get(:@nodes).should == [node]
1054
+ attrib.associations[:nodes].should == [node]
887
1055
  node.remove_all_attributes
888
- attrib.instance_variable_get(:@nodes).should == []
1056
+ attrib.associations[:nodes].should == []
889
1057
  end
890
1058
  end
891
1059