sequel 2.0.1 → 2.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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