sequel_model 0.2 → 0.3

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 CHANGED
@@ -1,3 +1,15 @@
1
+ === 0.3 (2008-01-18)
2
+
3
+ * Implemented Validatable::Errors class.
4
+
5
+ * Added Model#reload as alias to Model#refresh.
6
+
7
+ * Changed Model.create to accept a block (#126).
8
+
9
+ * Rewrote validations.
10
+
11
+ * Fixed Model#initialize to accept nil values (#115).
12
+
1
13
  === 0.2 (2008-01-02)
2
14
 
3
15
  * Removed deprecated Model.recreate_table method.
data/Rakefile CHANGED
@@ -9,7 +9,7 @@ include FileUtils
9
9
  # Configuration
10
10
  ##############################################################################
11
11
  NAME = "sequel_model"
12
- VERS = "0.2"
12
+ VERS = "0.3"
13
13
  CLEAN.include ["**/.*.sw?", "pkg/*", ".config", "doc/*", "coverage/*"]
14
14
  RDOC_OPTS = [
15
15
  "--quiet",
@@ -66,7 +66,6 @@ spec = Gem::Specification.new do |s|
66
66
  end
67
67
 
68
68
  s.add_dependency("sequel_core", '>= 0.5')
69
- s.add_dependency("validatable")
70
69
 
71
70
  s.files = %w(COPYING README Rakefile) + Dir.glob("{doc,spec,lib}/**/*")
72
71
 
@@ -128,6 +127,11 @@ Spec::Rake::SpecTask.new("spec_no_cov") do |t|
128
127
  t.spec_opts = File.read("spec/spec.opts").split("\n")
129
128
  end
130
129
 
130
+ desc "check documentation coverage"
131
+ task :dcov do
132
+ sh "find lib -name '*.rb' | xargs dcov"
133
+ end
134
+
131
135
  ##############################################################################
132
136
  # Statistics
133
137
  ##############################################################################
@@ -127,9 +127,9 @@ module Sequel
127
127
 
128
128
  # Creates new instance with values set to passed-in Hash ensuring that
129
129
  # new? returns true.
130
- def self.create(values = {})
130
+ def self.create(values = {}, &block)
131
131
  db.transaction do
132
- obj = new(values, true)
132
+ obj = new(values, true, &block)
133
133
  obj.save
134
134
  obj
135
135
  end
@@ -173,7 +173,7 @@ module Sequel
173
173
  # This method guesses whether the record exists when
174
174
  # <tt>new_record</tt> is set to false.
175
175
  def initialize(values = {}, new_record = false, &block)
176
- @values = values
176
+ @values = values || {}
177
177
  @changed_columns = []
178
178
 
179
179
  @new = new_record
@@ -251,6 +251,7 @@ module Sequel
251
251
  @values = this.first || raise(Error, "Record not found")
252
252
  self
253
253
  end
254
+ alias_method :reload, :refresh
254
255
 
255
256
  # Like delete but runs hooks before and after delete.
256
257
  def destroy
@@ -85,5 +85,70 @@ module Sequel
85
85
  class_def(name) {from.filter(key => pk)}
86
86
  end
87
87
  end
88
+
89
+ # TODO: Add/Replace current relations with the following specifications:
90
+ # ======================================================================
91
+
92
+ # Database modelling is generally done with an ER (Entity Relationship) diagram.
93
+ # Shouldn't ORM's facilitate simlilar specification?
94
+
95
+ # class Post < Sequel::Model(:users)
96
+ # relationships do
97
+ # # Specify the relationships that exist with the User model (users table)
98
+ # # These relationships are precisely the ER diagram connecting arrows.
99
+ # end
100
+ # end
101
+
102
+ #
103
+ # = Relationships
104
+ #
105
+ # are specifications of the ends of the ER diagrams connectors that are touching
106
+ # the current model.
107
+ #
108
+ # one_to_one, has_one
109
+ # many_to_one, belongs_to
110
+ # many_to_many, has_many
111
+ # ?parameters may be :zero, :one, :many which specifies the cardinality of the connection
112
+
113
+ # Example:
114
+ # class Post < Sequel::Model(:users)
115
+ # relationships do
116
+ # has :one, :blog, :required => true # blog_id field, cannot be null
117
+ # has :one, :account # account_id field
118
+ # has :many, :comments # comments_posts join table
119
+ # has :many, :authors, :required => true # authors_posts join table, requires at least one author
120
+ # end
121
+ # end
122
+
123
+ #
124
+ # Relationship API Details
125
+ #
126
+
127
+ #
128
+ # == belongs_to
129
+ #
130
+
131
+ # Defines an blog and blog= method
132
+ # belongs_to :blog
133
+
134
+ # Same, but uses "b_id" as the blog's id field.
135
+ # belongs_to :blog, :key => :b_id
136
+
137
+ # has_many :comments
138
+ # * Defines comments method which will query the join table appropriately.
139
+ # * Checks to see if a "comments_posts" join table exists (alphabetical order)
140
+ # ** If it does not exist, will create the join table.
141
+ # ** If options are passed in these will be used to further define the join table.
142
+
143
+
144
+ # Benefits:
145
+ # * Normalized DB
146
+ # * Easy to define join objects
147
+ # * Efficient queries, database gets to use indexed fields (pkeys) instead of a string field and an id.
148
+ #
149
+ # For example, polymorphic associations now become:
150
+ # [user] 1-* [addresses_users] *-1 [addresses]
151
+ # [companies] 1-* [addresses_companies] *-1 [addresses]
152
+ # [clients] 1-* [addresses_clients] *-1 [addresses]
88
153
  end
89
154
  end
@@ -1,113 +1,253 @@
1
- require 'validatable'
1
+ gem 'assistance', '>= 0.1.1' # because we need Array#extract_options!
2
2
 
3
3
  module Sequel
4
- class Model
5
- # =Basic Sequel Validations
6
- #
7
- # Sequel validations are based on the Validatable gem http://validatable.rubyforge.org/
8
- #
9
- # To assign default validations to a sequel model:
10
- #
11
- # class MyModel < SequelModel(:items)
12
- # validates do
13
- # format_of...
14
- # presence_of...
15
- # acceptance_of...
16
- # confirmation_of...
17
- # length_of...
18
- # true_for...
19
- # numericality_of...
20
- # format_of...
21
- # validates_base...
22
- # validates_each...
23
- # end
24
- # end
25
- #
26
- # You may also perform the usual 'longhand' way to assign default model validates
27
- # directly within the model class itself:
28
- #
29
- # class MyModel < SequelModel(:items)
30
- # validates_format_of...
31
- # validates_presence_of...
32
- # validates_acceptance_of...
33
- # validates_confirmation_of...
34
- # validates_length_of...
35
- # validates_true_for...
36
- # validates_numericality_of...
37
- # validates_format_of...
38
- # validates_base...
39
- # validates_each...
40
- # end
41
- #
42
- # Each validation allows for arguments:
43
- # TODO: fill the argument options in here
44
- #
45
- # =Advanced Sequel Validations
46
- #
47
- # TODO: verify that advanced validates work as stated (aka write specs)
48
- # NOTE: experimental
49
- #
50
- # To store validates for conditional usage simply specify a name with which to store them
51
- # class User < Sequel::Model
52
- #
53
- # # This set of validates becomes stored as :default and gets injected into the model.
54
- # validates do
55
- # # standard validates calls
56
- # end
57
- #
58
- # validates(:registration) do
59
- # # user registration specific validates
60
- # end
61
- #
62
- # validates(:promotion) do
63
- # # user promotion validates
64
- # end
65
- #
66
- # end
67
- #
68
- # To use the above validates:
69
- #
70
- # @user.valid? # Runs the default validations only.
71
- # @user.valid?(:registration) # Runs both default and registration validations
72
- # @user.valid?(:promotion) # Runs both default and promotion validations
73
- #
74
- # You may determine whether the model has validates via:
75
- #
76
- # has_validations? # will return true / false based on existence of validations on the model.
77
- #
78
- # You may also retrieve the validations block if needed:
79
- #
80
- # validates(:registration) # returns the registration validation block.
81
- #
82
- # validates() method parameters:
83
- # a validations block - runs the validations block on the model & stores as :default
84
- # a name and a validations block - stores the block under the name
85
- # a name - returns a stored block of that name or nil
86
- # nothing - returns true / false based on if validations exist for the model.
87
- #
88
- module Validations
89
- class Generator
90
- def initialize(model_class ,&block)
91
- @model_class = model_class
92
- instance_eval(&block)
93
- end
4
+ # The Validatable module provides validation capabilities as a mixin. When
5
+ # included into a class, it enhances the class with class and instance
6
+ # methods for defining validations and validating class instances.
7
+ #
8
+ # The Validatable emulates the validation capabilities of ActiveRecord, and
9
+ # provides methods for validating acceptance, confirmation, presence, format,
10
+ # length and numericality of attributes.
11
+ #
12
+ # To use validations, you need to include the Validatable module in your
13
+ # class:
14
+ #
15
+ # class MyClass
16
+ # include Sequel::Validatable
17
+ # validates_length_of :password, :minimum => 6
18
+ # end
19
+ module Validatable
20
+ # Includes the Validatable class methods into the including class.
21
+ def self.included(c)
22
+ c.extend ClassMethods
23
+ end
24
+
25
+ # Returns the validation errors associated with the object.
26
+ def errors
27
+ @errors ||= Errors.new
28
+ end
29
+
30
+ # Validates the object.
31
+ def validate
32
+ errors.clear
33
+ self.class.validate(self)
34
+ end
94
35
 
95
- def method_missing(method, *args)
96
- method = :"validates_#{method}"
97
- @model_class.send(method, *args)
36
+ # Validates the object and returns true if no errors are reported.
37
+ def valid?
38
+ validate
39
+ errors.empty?
40
+ end
41
+
42
+ # Validatable::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
+ # Returns true if no errors are stored.
50
+ def empty?
51
+ @errors.empty?
52
+ end
53
+
54
+ # Clears all errors.
55
+ def clear
56
+ @errors.clear
57
+ end
58
+
59
+ # Returns the errors for the given attribute.
60
+ def on(att)
61
+ @errors[att]
62
+ end
63
+ alias_method :[], :on
64
+
65
+ # Adds an error for the given attribute.
66
+ def add(att, msg)
67
+ @errors[att] << msg
68
+ end
69
+
70
+ # Returns an array of fully-formatted error messages.
71
+ def full_messages
72
+ @errors.inject([]) do |m, kv| att, errors = *kv
73
+ errors.each {|e| m << "#{att} #{e}"}
74
+ m
98
75
  end
99
76
  end
100
77
  end
78
+
79
+ # The Generator class is used to generate validation definitions using
80
+ # the validates {} idiom.
81
+ class Generator
82
+ # Initializes a new generator.
83
+ def initialize(receiver ,&block)
84
+ @receiver = receiver
85
+ instance_eval(&block)
86
+ end
101
87
 
102
- include ::Validatable
88
+ # Delegates method calls to the receiver by calling receiver.validates_xxx.
89
+ def method_missing(m, *args)
90
+ @receiver.send(:"validates_#{m}", *args)
91
+ end
92
+ end
93
+
94
+ # Validatable class methods.
95
+ module ClassMethods
96
+ # Defines validations by converting a longhand block into a series of
97
+ # shorthand definitions. For example:
98
+ #
99
+ # class MyClass
100
+ # include Sequel::Validatable
101
+ # validates do
102
+ # length_of :name, :minimum => 6
103
+ # length_of :password, :minimum => 8
104
+ # end
105
+ # end
106
+ #
107
+ # is equivalent to:
108
+ # class MyClass
109
+ # include Sequel::Validatable
110
+ # validates_length_of :name, :minimum => 6
111
+ # validates_length_of :password, :minimum => 8
112
+ # end
113
+ def validates(&block)
114
+ Generator.new(self, &block)
115
+ end
103
116
 
104
- def self.validates(&block)
105
- Validations::Generator.new(self, &block)
117
+ # Returns the validations hash for the class.
118
+ def validations
119
+ @validations ||= Hash.new {|h, k| h[k] = []}
120
+ end
121
+
122
+ # Returns true if validations are defined.
123
+ def has_validations?
124
+ !validations.empty?
125
+ end
126
+
127
+ # Validates the given instance.
128
+ def validate(o)
129
+ validations.each do |att, procs|
130
+ v = o.send(att)
131
+ procs.each {|p| p[o, att, v]}
132
+ end
133
+ end
134
+
135
+ # Adds a validation for each of the given attributes using the supplied
136
+ # block. The block must accept three arguments: instance, attribute and
137
+ # value, e.g.:
138
+ #
139
+ # validates_each :name, :password do |object, attribute, value|
140
+ # object.errors[attribute] << 'is not nice' unless value.nice?
141
+ # end
142
+ def validates_each(*atts, &block)
143
+ atts.each {|a| validations[a] << block}
144
+ end
145
+
146
+ # Validates acceptance of an attribute.
147
+ def validates_acceptance_of(*atts)
148
+ opts = {
149
+ :message => 'is not accepted',
150
+ :allow_nil => true,
151
+ :accept => '1'
152
+ }.merge!(atts.extract_options!)
153
+
154
+ validates_each(*atts) do |o, a, v|
155
+ next if (v.nil? && opts[:allow_nil]) || (v.blank? && opts[:allow_blank])
156
+ o.errors[a] << opts[:message] unless v == opts[:accept]
157
+ end
158
+ end
159
+
160
+ # Validates confirmation of an attribute.
161
+ def validates_confirmation_of(*atts)
162
+ opts = {
163
+ :message => 'is not confirmed',
164
+ }.merge!(atts.extract_options!)
165
+
166
+ validates_each(*atts) do |o, a, v|
167
+ next if (v.nil? && opts[:allow_nil]) || (v.blank? && opts[:allow_blank])
168
+ c = o.send(:"#{a}_confirmation")
169
+ o.errors[a] << opts[:message] unless v == c
170
+ end
171
+ end
172
+
173
+ # Validates the format of an attribute.
174
+ def validates_format_of(*atts)
175
+ opts = {
176
+ :message => 'is invalid',
177
+ }.merge!(atts.extract_options!)
178
+
179
+ unless opts[:with].is_a?(Regexp)
180
+ raise Sequel::Error, "A regular expression must be supplied as the :with option of the options hash"
181
+ end
182
+
183
+ validates_each(*atts) do |o, a, v|
184
+ next if (v.nil? && opts[:allow_nil]) || (v.blank? && opts[:allow_blank])
185
+ o.errors[a] << opts[:message] unless v.to_s =~ opts[:with]
186
+ end
187
+ end
188
+
189
+ # Validates the length of an attribute.
190
+ def validates_length_of(*atts)
191
+ opts = {
192
+ :too_long => 'is too long',
193
+ :too_short => 'is too short',
194
+ :wrong_length => 'is the wrong length'
195
+ }.merge!(atts.extract_options!)
196
+
197
+ validates_each(*atts) do |o, a, v|
198
+ next if (v.nil? && opts[:allow_nil]) || (v.blank? && opts[:allow_blank])
199
+ if m = opts[:maximum]
200
+ o.errors[a] << (opts[:message] || opts[:too_long]) unless v && v.size <= m
201
+ end
202
+ if m = opts[:minimum]
203
+ o.errors[a] << (opts[:message] || opts[:too_short]) unless v && v.size >= m
204
+ end
205
+ if i = opts[:is]
206
+ o.errors[a] << (opts[:message] || opts[:wrong_length]) unless v && v.size == i
207
+ end
208
+ if w = opts[:within]
209
+ o.errors[a] << (opts[:message] || opts[:wrong_length]) unless v && w.include?(v.size)
210
+ end
211
+ end
212
+ end
213
+
214
+ NUMBER_RE = /^\d*\.{0,1}\d+$/
215
+ INTEGER_RE = /\A[+-]?\d+\Z/
216
+
217
+ # Validates whether an attribute is a number.
218
+ def validates_numericality_of(*atts)
219
+ opts = {
220
+ :message => 'is not a number',
221
+ }.merge!(atts.extract_options!)
222
+
223
+ re = opts[:only_integer] ? INTEGER_RE : NUMBER_RE
224
+
225
+ validates_each(*atts) do |o, a, v|
226
+ next if (v.nil? && opts[:allow_nil]) || (v.blank? && opts[:allow_blank])
227
+ o.errors[a] << opts[:message] unless v.to_s =~ re
228
+ end
229
+ end
230
+
231
+ # Validates the presence of an attribute.
232
+ def validates_presence_of(*atts)
233
+ opts = {
234
+ :message => 'is not present',
235
+ }.merge!(atts.extract_options!)
236
+
237
+ validates_each(*atts) do |o, a, v|
238
+ o.errors[a] << opts[:message] unless v && !v.blank?
239
+ end
240
+ end
106
241
  end
242
+ end
107
243
 
108
- # return true if there are validations stored, false otherwise
109
- def self.has_validations?
110
- validations.length > 0 ? true : false
244
+ class Model
245
+ include Validatable
246
+
247
+ alias_method :save!, :save
248
+ def save(*args)
249
+ return false unless valid?
250
+ save!(*args)
111
251
  end
112
252
  end
113
253
  end
data/spec/model_spec.rb CHANGED
@@ -401,27 +401,6 @@ describe Sequel::Model, "(:tablename)" do
401
401
 
402
402
  end
403
403
 
404
- describe Sequel::Model, ".create" do
405
-
406
- before(:each) do
407
- MODEL_DB.reset
408
- @c = Class.new(Sequel::Model(:items))
409
- end
410
-
411
- it "should be able to create rows in the associated table" do
412
- o = @c.create(:x => 1)
413
- o.class.should == @c
414
- MODEL_DB.sqls.should == ['INSERT INTO items (x) VALUES (1)', "SELECT * FROM items WHERE (id IN ('INSERT INTO items (x) VALUES (1)')) LIMIT 1"]
415
- end
416
-
417
- it "should be able to create rows without any values specified" do
418
- o = @c.create
419
- o.class.should == @c
420
- MODEL_DB.sqls.should == ["INSERT INTO items DEFAULT VALUES", "SELECT * FROM items WHERE (id IN ('INSERT INTO items DEFAULT VALUES')) LIMIT 1"]
421
- end
422
-
423
- end
424
-
425
404
  describe Sequel::Model, "A model class without a primary key" do
426
405
 
427
406
  before(:each) do
data/spec/record_spec.rb CHANGED
@@ -409,3 +409,91 @@ describe Sequel::Model, "#===" do
409
409
  end
410
410
  end
411
411
 
412
+ describe Sequel::Model, "#initialize" do
413
+ setup do
414
+ @c = Class.new(Sequel::Model) do
415
+ end
416
+ end
417
+
418
+ specify "should accept values" do
419
+ m = @c.new(:id => 1, :x => 2)
420
+ m.values.should == {:id => 1, :x => 2}
421
+ end
422
+
423
+ specify "should accept no values" do
424
+ m = @c.new
425
+ m.values.should == {}
426
+ end
427
+
428
+ specify "should accept nil values" do
429
+ m = @c.new(nil)
430
+ m.values.should == {}
431
+ end
432
+
433
+ specify "should accept a block to execute" do
434
+ m = @c.new {|o| o[:id] = 1234}
435
+ m.id.should == 1234
436
+ end
437
+ end
438
+
439
+ describe Sequel::Model, ".create" do
440
+
441
+ before(:each) do
442
+ MODEL_DB.reset
443
+ @c = Class.new(Sequel::Model(:items)) do
444
+ def columns; [:x]; end
445
+ end
446
+ end
447
+
448
+ it "should be able to create rows in the associated table" do
449
+ o = @c.create(:x => 1)
450
+ o.class.should == @c
451
+ MODEL_DB.sqls.should == ['INSERT INTO items (x) VALUES (1)', "SELECT * FROM items WHERE (id IN ('INSERT INTO items (x) VALUES (1)')) LIMIT 1"]
452
+ end
453
+
454
+ it "should be able to create rows without any values specified" do
455
+ o = @c.create
456
+ o.class.should == @c
457
+ MODEL_DB.sqls.should == ["INSERT INTO items DEFAULT VALUES", "SELECT * FROM items WHERE (id IN ('INSERT INTO items DEFAULT VALUES')) LIMIT 1"]
458
+ end
459
+
460
+ it "should accept a block and run it" do
461
+ o1, o2, o3 = nil, nil, nil
462
+ o = @c.create {|o3| o1 = o3; o2 = :blah; o3.x = 333}
463
+ o.class.should == @c
464
+ o1.should === o
465
+ o3.should === o
466
+ o2.should == :blah
467
+ MODEL_DB.sqls.should == ["INSERT INTO items (x) VALUES (333)", "SELECT * FROM items WHERE (id IN ('INSERT INTO items (x) VALUES (333)')) LIMIT 1"]
468
+ end
469
+ end
470
+
471
+ describe Sequel::Model, "#refresh" do
472
+ setup do
473
+ MODEL_DB.reset
474
+ @c = Class.new(Sequel::Model(:items)) do
475
+ def columns; [:x]; end
476
+ end
477
+ end
478
+
479
+ specify "should reload the instance values from the database" do
480
+ @m = @c.new(:id => 555)
481
+ @m[:x] = 'blah'
482
+ @m.this.should_receive(:first).and_return({:x => 'kaboom', :id => 555})
483
+ @m.refresh
484
+ @m[:x].should == 'kaboom'
485
+ end
486
+
487
+ specify "should raise if the instance is not found" do
488
+ @m = @c.new(:id => 555)
489
+ @m.this.should_receive(:first).and_return(nil)
490
+ proc {@m.refresh}.should raise_error(Sequel::Error)
491
+ end
492
+
493
+ specify "should be aliased by #reload" do
494
+ @m = @c.new(:id => 555)
495
+ @m.this.should_receive(:first).and_return({:x => 'kaboom', :id => 555})
496
+ @m.reload
497
+ @m[:x].should == 'kaboom'
498
+ end
499
+ end
data/spec/spec_helper.rb CHANGED
@@ -1,4 +1,7 @@
1
1
  require 'rubygems'
2
+ unless Object.const_defined?('Sequel')
3
+ require 'sequel_core'
4
+ end
2
5
  require File.join(File.dirname(__FILE__), "../lib/sequel_model")
3
6
 
4
7
  class MockDataset < Sequel::Dataset
@@ -1,5 +1,295 @@
1
1
  require File.join(File.dirname(__FILE__), "spec_helper")
2
2
 
3
+ describe "Sequel::Validatable::Errors" do
4
+ setup do
5
+ @errors = Sequel::Validatable::Errors.new
6
+ class Sequel::Validatable::Errors
7
+ attr_accessor :errors
8
+ end
9
+ end
10
+
11
+ specify "should be clearable using #clear" do
12
+ @errors.errors = {1 => 2, 3 => 4}
13
+ @errors.clear
14
+ @errors.errors.should == {}
15
+ end
16
+
17
+ specify "should be empty if no errors are added" do
18
+ @errors.should be_empty
19
+ @errors[:blah] << "blah"
20
+ @errors.should_not be_empty
21
+ end
22
+
23
+ specify "should return errors for a specific attribute using #on or #[]" do
24
+ @errors[:blah].should == []
25
+ @errors.on(:blah).should == []
26
+
27
+ @errors[:blah] << 'blah'
28
+ @errors[:blah].should == ['blah']
29
+ @errors.on(:blah).should == ['blah']
30
+
31
+ @errors[:bleu].should == []
32
+ @errors.on(:bleu).should == []
33
+ end
34
+
35
+ specify "should accept errors using #[] << or #add" do
36
+ @errors[:blah] << 'blah'
37
+ @errors[:blah].should == ['blah']
38
+
39
+ @errors.add :blah, 'zzzz'
40
+ @errors[:blah].should == ['blah', 'zzzz']
41
+ end
42
+
43
+ specify "should return full messages using #full_messages" do
44
+ @errors.full_messages.should == []
45
+
46
+ @errors[:blow] << 'blieuh'
47
+ @errors[:blow] << 'blich'
48
+ @errors[:blay] << 'bliu'
49
+ msgs = @errors.full_messages
50
+ msgs.size.should == 3
51
+ msgs.should include('blow blieuh', 'blow blich', 'blay bliu')
52
+ end
53
+ end
54
+
55
+ describe Sequel::Validatable do
56
+ setup do
57
+ @c = Class.new do
58
+ include Sequel::Validatable
59
+
60
+ def self.validates_coolness_of(attr)
61
+ validates_each(attr) {|o, a, v| o.errors[a] << 'is not cool' if v != :cool}
62
+ end
63
+ end
64
+
65
+ @d = Class.new do
66
+ attr_accessor :errors
67
+ def initialize; @errors = Sequel::Validatable::Errors.new; end
68
+ end
69
+ end
70
+
71
+ specify "should respond to validates, validations, has_validations?" do
72
+ @c.should respond_to(:validations)
73
+ @c.should respond_to(:has_validations?)
74
+ end
75
+
76
+ specify "should acccept validation definitions using validates_each" do
77
+ @c.validates_each(:xx, :yy) {|o, a, v| o.errors[a] << 'too low' if v < 50}
78
+
79
+ @c.validations[:xx].size.should == 1
80
+ @c.validations[:yy].size.should == 1
81
+
82
+ o = @d.new
83
+ @c.validations[:xx].first.call(o, :aa, 40)
84
+ @c.validations[:yy].first.call(o, :bb, 60)
85
+
86
+ o.errors.full_messages.should == ['aa too low']
87
+ end
88
+
89
+ specify "should return true/false for has_validations?" do
90
+ @c.has_validations?.should == false
91
+ @c.validates_each(:xx) {1}
92
+ @c.has_validations?.should == true
93
+ end
94
+
95
+ specify "should provide a validates method that takes block with validation definitions" do
96
+ @c.validates do
97
+ coolness_of :blah
98
+ end
99
+ @c.validations[:blah].should_not be_empty
100
+
101
+ o = @d.new
102
+ @c.validations[:blah].first.call(o, :ttt, 40)
103
+ o.errors.full_messages.should == ['ttt is not cool']
104
+ o.errors.clear
105
+ @c.validations[:blah].first.call(o, :ttt, :cool)
106
+ o.errors.should be_empty
107
+ end
108
+ end
109
+
110
+ describe "A Validatable instance" do
111
+ setup do
112
+ @c = Class.new do
113
+ attr_accessor :score
114
+
115
+ include Sequel::Validatable
116
+
117
+ validates_each :score do |o, a, v|
118
+ o.errors[a] << 'too low' if v < 87
119
+ end
120
+ end
121
+
122
+ @o = @c.new
123
+ end
124
+
125
+ specify "should supply a #valid? method that returns true if validations pass" do
126
+ @o.score = 50
127
+ @o.should_not be_valid
128
+ @o.score = 100
129
+ @o.should be_valid
130
+ end
131
+
132
+ specify "should provide an errors object" do
133
+ @o.score = 100
134
+ @o.should be_valid
135
+ @o.errors.should be_empty
136
+
137
+ @o.score = 86
138
+ @o.should_not be_valid
139
+ @o.errors[:score].should == ['too low']
140
+ @o.errors[:blah].should be_empty
141
+ end
142
+ end
143
+
144
+ describe Sequel::Validatable::Generator do
145
+ setup do
146
+ $testit = nil
147
+
148
+ @c = Class.new do
149
+ include Sequel::Validatable
150
+
151
+ def self.validates_blah
152
+ $testit = 1324
153
+ end
154
+ end
155
+ end
156
+
157
+ specify "should instance_eval the block, sending everything to its receiver" do
158
+ Sequel::Validatable::Generator.new(@c) do
159
+ blah
160
+ end
161
+ $testit.should == 1324
162
+ end
163
+ end
164
+
165
+ describe "Sequel validations" do
166
+ setup do
167
+ @c = Class.new do
168
+ attr_accessor :value
169
+ include Sequel::Validatable
170
+ end
171
+ @m = @c.new
172
+ end
173
+
174
+ specify "should validate acceptance_of" do
175
+ @c.validates_acceptance_of :value
176
+ @m.should be_valid
177
+ @m.value = '1'
178
+ @m.should be_valid
179
+ end
180
+
181
+ specify "should validate acceptance_of with accept" do
182
+ @c.validates_acceptance_of :value, :accept => 'true'
183
+ @m.value = '1'
184
+ @m.should_not be_valid
185
+ @m.value = 'true'
186
+ @m.should be_valid
187
+ end
188
+
189
+ specify "should validate acceptance_of with allow_nil => false" do
190
+ @c.validates_acceptance_of :value, :allow_nil => false
191
+ @m.should_not be_valid
192
+ end
193
+
194
+ specify "should validate confirmation_of" do
195
+ @c.send(:attr_accessor, :value_confirmation)
196
+ @c.validates_confirmation_of :value
197
+
198
+ @m.value = 'blah'
199
+ @m.should_not be_valid
200
+
201
+ @m.value_confirmation = 'blah'
202
+ @m.should be_valid
203
+ end
204
+
205
+ specify "should validate format_of" do
206
+ @c.validates_format_of :value, :with => /.+_.+/
207
+ @m.value = 'abc_'
208
+ @m.should_not be_valid
209
+ @m.value = 'abc_def'
210
+ @m.should be_valid
211
+ end
212
+
213
+ specify "should raise for validate_format_of without regexp" do
214
+ proc {@c.validates_format_of :value}.should raise_error(Sequel::Error)
215
+ proc {@c.validates_format_of :value, :with => :blah}.should raise_error(Sequel::Error)
216
+ end
217
+
218
+ specify "should validate length_of with maximum" do
219
+ @c.validates_length_of :value, :maximum => 5
220
+ @m.should_not be_valid
221
+ @m.value = '12345'
222
+ @m.should be_valid
223
+ @m.value = '123456'
224
+ @m.should_not be_valid
225
+ end
226
+
227
+ specify "should validate length_of with minimum" do
228
+ @c.validates_length_of :value, :minimum => 5
229
+ @m.should_not be_valid
230
+ @m.value = '12345'
231
+ @m.should be_valid
232
+ @m.value = '1234'
233
+ @m.should_not be_valid
234
+ end
235
+
236
+ specify "should validate length_of with within" do
237
+ @c.validates_length_of :value, :within => 2..5
238
+ @m.should_not be_valid
239
+ @m.value = '12345'
240
+ @m.should be_valid
241
+ @m.value = '1'
242
+ @m.should_not be_valid
243
+ @m.value = '123456'
244
+ @m.should_not be_valid
245
+ end
246
+
247
+ specify "should validate length_of with is" do
248
+ @c.validates_length_of :value, :is => 3
249
+ @m.should_not be_valid
250
+ @m.value = '123'
251
+ @m.should be_valid
252
+ @m.value = '12'
253
+ @m.should_not be_valid
254
+ @m.value = '1234'
255
+ @m.should_not be_valid
256
+ end
257
+
258
+ specify "should validate length_of with allow_nil" do
259
+ @c.validates_length_of :value, :is => 3, :allow_nil => true
260
+ @m.should be_valid
261
+ end
262
+
263
+ specify "should validate numericality_of" do
264
+ @c.validates_numericality_of :value
265
+ @m.value = 'blah'
266
+ @m.should_not be_valid
267
+ @m.value = '123'
268
+ @m.should be_valid
269
+ @m.value = '123.1231'
270
+ @m.should be_valid
271
+ end
272
+
273
+ specify "should validate numericality_of with only_integer" do
274
+ @c.validates_numericality_of :value, :only_integer => true
275
+ @m.value = 'blah'
276
+ @m.should_not be_valid
277
+ @m.value = '123'
278
+ @m.should be_valid
279
+ @m.value = '123.1231'
280
+ @m.should_not be_valid
281
+ end
282
+
283
+ specify "should validate presence_of" do
284
+ @c.validates_presence_of :value
285
+ @m.should_not be_valid
286
+ @m.value = ''
287
+ @m.should_not be_valid
288
+ @m.value = 1234
289
+ @m.should be_valid
290
+ end
291
+ end
292
+
3
293
  describe Sequel::Model, "Validations" do
4
294
 
5
295
  before(:all) do
@@ -31,35 +321,18 @@ describe Sequel::Model, "Validations" do
31
321
  end
32
322
  end
33
323
 
34
- it "should have a hook before validating" do
35
- class Person < Sequel::Model(:people)
36
- before_validation do
37
- self.name = "default name"
38
- end
39
- validations.clear
40
- validates_presence_of :name
41
- end
42
-
43
- @person = Person.new
44
- @person.valid?.should be_true
45
- end
46
-
47
- it "should include errors from other models" do
48
- pending("Waiting for Wayne's amazing associations!")
49
- end
50
-
51
324
  it "should validate the acceptance of a column" do
52
- class Cow < Sequel::Model(:cows)
325
+ class Cow < Sequel::Model(:cows)
53
326
  validations.clear
54
- validates_acceptance_of :got_milk
327
+ validates_acceptance_of :got_milk, :accept => 'blah', :allow_nil => false
55
328
  end
56
329
 
57
330
  @cow = Cow.new
58
- @cow.valid?.should be_false
59
- @cow.errors.on(:got_milk).should == "must be accepted"
331
+ @cow.should_not be_valid
332
+ @cow.errors.full_messages.should == ["got_milk is not accepted"]
60
333
 
61
- @cow.got_milk = "true"
62
- @cow.valid?.should be_true
334
+ @cow.got_milk = "blah"
335
+ @cow.should be_valid
63
336
  end
64
337
 
65
338
  it "should validate the confirmation of a column" do
@@ -73,29 +346,11 @@ describe Sequel::Model, "Validations" do
73
346
  end
74
347
 
75
348
  @user = User.new
76
- @user.valid?.should be_false
77
- @user.errors.on(:password).should == "doesn't match confirmation"
349
+ @user.should_not be_valid
350
+ @user.errors.full_messages.should == ["password is not confirmed"]
78
351
 
79
352
  @user.password = "test"
80
- @user.valid?.should be_true
81
- end
82
-
83
- it "should validate each with logic" do
84
- class ZipCodeService; end
85
-
86
- class Address < Sequel::Model(:addresses)
87
- validations.clear
88
- validates_each :zip_code, :logic => lambda { errors.add(:zip_code, "is not valid") unless ZipCodeService.allows(zip_code) }
89
- end
90
-
91
- @address = Address.new :zip_code => "48108"
92
- ZipCodeService.should_receive(:allows).with("48108").and_return(false)
93
- @address.valid?.should be_false
94
- @address.errors.on(:zip_code).should == "is not valid"
95
-
96
- @address2 = Address.new :zip_code => "48104"
97
- ZipCodeService.should_receive(:allows).with("48104").and_return(true)
98
- @address2.valid?.should be_true
353
+ @user.should be_valid
99
354
  end
100
355
 
101
356
  it "should validate format of column" do
@@ -109,9 +364,9 @@ describe Sequel::Model, "Validations" do
109
364
  @person.valid?.should be_true
110
365
  end
111
366
 
112
- it "should allow for :with_exactly => /[a-zA-Z]/, which wraps the supplied regex with ^<regex>$" do
113
- pending("TODO: Add this option to Validatable#validates_format_of")
114
- end
367
+ # it "should allow for :with_exactly => /[a-zA-Z]/, which wraps the supplied regex with ^<regex>$" do
368
+ # pending("TODO: Add this option to Validatable#validates_format_of")
369
+ # end
115
370
 
116
371
  it "should validate length of column" do
117
372
  class Person < Sequel::Model(:people)
@@ -129,17 +384,20 @@ describe Sequel::Model, "Validations" do
129
384
  :middle_name => "danger"
130
385
  )
131
386
 
132
- @person.valid?.should be_false
133
- @person.errors.on(:first_name).should == "is invalid"
134
- @person.errors.on(:last_name).should == "is invalid"
135
- @person.errors.on(:initials).should == "is invalid"
136
- @person.errors.on(:middle_name).should == "is invalid"
387
+ @person.should_not be_valid
388
+ @person.errors.full_messages.size.should == 4
389
+ @person.errors.full_messages.should include(
390
+ 'first_name is too long',
391
+ 'last_name is too short',
392
+ 'middle_name is the wrong length',
393
+ 'initials is the wrong length'
394
+ )
137
395
 
138
396
  @person.first_name = "Lancelot"
139
397
  @person.last_name = "1234567890123456789012345678901"
140
398
  @person.initials = "LC"
141
399
  @person.middle_name = "Will"
142
- @person.valid?.should be_true
400
+ @person.should be_valid
143
401
  end
144
402
 
145
403
  it "should validate numericality of column" do
@@ -149,42 +407,27 @@ describe Sequel::Model, "Validations" do
149
407
  end
150
408
 
151
409
  @person = Person.new :age => "Twenty"
152
- @person.valid?.should be_false
153
- @person.errors.on(:age).should == "must be a number"
410
+ @person.should_not be_valid
411
+ @person.errors.full_messages.should == ['age is not a number']
154
412
 
155
413
  @person.age = 20
156
- @person.valid?.should be_true
414
+ @person.should be_valid
157
415
  end
158
416
 
159
417
  it "should validate the presence of a column" do
160
- class Cow < Sequel::Model(:cows)
418
+ class Cow < Sequel::Model(:cows)
161
419
  validations.clear
162
420
  validates_presence_of :name
163
421
  end
164
422
 
165
423
  @cow = Cow.new
166
- @cow.valid?.should be_false
167
- @cow.errors.on(:name).should == "can't be empty"
168
- @cow.errors.full_messages.first.should == "Name can't be empty"
424
+ @cow.should_not be_valid
425
+ @cow.errors.full_messages.should == ['name is not present']
169
426
 
170
427
  @cow.name = "Betsy"
171
- @cow.valid?.should be_true
428
+ @cow.should be_valid
172
429
  end
173
430
 
174
- it "should validate true for a column" do
175
- class Person < Sequel::Model(:people)
176
- validations.clear
177
- validates_true_for :first_name, :logic => lambda { first_name == "Alison" }
178
- end
179
-
180
- @person = Person.new :first_name => "Nina"
181
- @person.valid?.should be_false
182
- @person.errors.on(:first_name).should == "is invalid"
183
-
184
- @person.first_name = "Alison"
185
- @person.valid?.should be_true
186
- end
187
-
188
431
  it "should have a validates block that calls multple validations" do
189
432
  class Person < Sequel::Model(:people)
190
433
  validations.clear
@@ -194,7 +437,7 @@ describe Sequel::Model, "Validations" do
194
437
  end
195
438
  end
196
439
 
197
- Person.validations.length.should eql(2)
440
+ Person.validations[:first_name].size.should == 2
198
441
 
199
442
  @person = Person.new :first_name => "Lancelot99"
200
443
  @person.valid?.should be_false
@@ -203,12 +446,6 @@ describe Sequel::Model, "Validations" do
203
446
  @person2.valid?.should be_true
204
447
  end
205
448
 
206
- it "should require and include the validatable gem" do
207
- Gem.loaded_specs["validatable"].should_not be_nil
208
- Sequel::Model.should respond_to(:validates_format_of) # validatable gem
209
- Sequel::Model.should respond_to(:validations) # Validations module
210
- end
211
-
212
449
  it "should allow 'longhand' validations direcly within the model." do
213
450
  lambda {
214
451
  class Person < Sequel::Model(:people)
@@ -219,26 +456,6 @@ describe Sequel::Model, "Validations" do
219
456
  Person.validations.length.should eql(1)
220
457
  end
221
458
 
222
- it "should validates do should allow shorthand method for every longhand validates_* method" do
223
- class Person
224
- validations.clear
225
- validates do
226
- format_of :first_name, :with => /^[a-zA-Z]+$/
227
- length_of :first_name, :maximum => 30
228
- presence_of :first_name
229
- numericality_of :age
230
- acceptance_of :terms
231
- confirmation_of :password
232
- true_for :first_name, :logic => lambda { first_name == "Alison" }
233
- #validates_each :last_name, :logic => lambda { errors.add(:zip_code, "is not valid") unless ZipCodeService.allows(zip_code) }
234
- #base
235
- end
236
-
237
- # Now check to make sure that each validation exists in the model's validations.
238
- end
239
- pending("finish this spec for each case")
240
- end
241
-
242
459
  it "should define a has_validations? method which returns true if the model has validations, false otherwise" do
243
460
  class Person < Sequel::Model(:people)
244
461
  validations.clear
@@ -255,22 +472,53 @@ describe Sequel::Model, "Validations" do
255
472
  Person.should have_validations
256
473
  Smurf.should_not have_validations
257
474
  end
258
-
259
475
  end
260
476
 
261
- describe Sequel::Model, "validates" do
477
+ describe "Model#save!" do
478
+ setup do
479
+ @c = Class.new(Sequel::Model(:people)) do
480
+ def columns; [:id]; end
481
+
482
+ validates_each :id do |o, a, v|
483
+ o.errors[a] << 'blah' unless v == 5
484
+ end
485
+ end
486
+ @m = @c.new(:id => 4)
487
+ MODEL_DB.reset
488
+ end
262
489
 
490
+ specify "should save regardless of validations" do
491
+ @m.should_not be_valid
492
+ @m.save!
493
+ MODEL_DB.sqls.should == ['UPDATE people SET id = 4 WHERE (id = 4)']
494
+ end
495
+ end
263
496
 
264
- before(:all) do
265
- class Person < Sequel::Model(:people)
266
- def columns
267
- [:id,:name,:first_name,:last_name,:middle_name,:initials,:age]
497
+ describe "Model#save!" do
498
+ setup do
499
+ @c = Class.new(Sequel::Model(:people)) do
500
+ def columns; [:id]; end
501
+
502
+ validates_each :id do |o, a, v|
503
+ o.errors[a] << 'blah' unless v == 5
268
504
  end
269
505
  end
506
+ @m = @c.new(:id => 4)
507
+ MODEL_DB.reset
508
+ end
509
+
510
+ specify "should save only if validations pass" do
511
+ @m.should_not be_valid
512
+ @m.save
513
+ MODEL_DB.sqls.should be_empty
514
+
515
+ @m.id = 5
516
+ @m.should be_valid
517
+ @m.save
518
+ MODEL_DB.sqls.should == ['UPDATE people SET id = 5 WHERE (id = 5)']
270
519
  end
271
520
 
272
- it "should runs the validations block on the model & store as :default when only a validations block is passed in"
273
- it "should store the block under the name passed in when both a name and a validations block are passed in"
274
- it "should return the stored validations block corresponding to the name given, if only a name is given (no block)"
275
- it "should return true or false based on if validations exist on the model if no arguments are given"
276
- end
521
+ specify "should return false if validations fail" do
522
+ @m.save.should == false
523
+ end
524
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sequel_model
3
3
  version: !ruby/object:Gem::Version
4
- version: "0.2"
4
+ version: "0.3"
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sharon Rosner
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2008-01-02 00:00:00 +02:00
12
+ date: 2008-01-18 00:00:00 +02:00
13
13
  default_executable:
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
@@ -21,15 +21,6 @@ dependencies:
21
21
  - !ruby/object:Gem::Version
22
22
  version: "0.5"
23
23
  version:
24
- - !ruby/object:Gem::Dependency
25
- name: validatable
26
- version_requirement:
27
- version_requirements: !ruby/object:Gem::Requirement
28
- requirements:
29
- - - ">="
30
- - !ruby/object:Gem::Version
31
- version: "0"
32
- version:
33
24
  description: Lightweight ORM for Ruby
34
25
  email: ciconia@gmail.com
35
26
  executables: []