sequel_model 0.2 → 0.3

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