sequel 2.1.0 → 2.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/CHANGELOG +37 -1
- data/Rakefile +2 -2
- data/doc/advanced_associations.rdoc +624 -0
- data/lib/sequel_model.rb +7 -2
- data/lib/sequel_model/association_reflection.rb +112 -2
- data/lib/sequel_model/associations.rb +171 -222
- data/lib/sequel_model/base.rb +28 -7
- data/lib/sequel_model/eager_loading.rb +12 -71
- data/lib/sequel_model/record.rb +143 -14
- data/lib/sequel_model/validations.rb +108 -17
- data/spec/association_reflection_spec.rb +10 -2
- data/spec/associations_spec.rb +449 -94
- data/spec/caching_spec.rb +72 -11
- data/spec/eager_loading_spec.rb +168 -21
- data/spec/hooks_spec.rb +53 -15
- data/spec/model_spec.rb +12 -11
- data/spec/spec_helper.rb +0 -7
- data/spec/validations_spec.rb +39 -16
- metadata +6 -4
data/lib/sequel_model/base.rb
CHANGED
@@ -8,6 +8,7 @@ module Sequel
|
|
8
8
|
@allowed_columns = nil
|
9
9
|
@dataset_methods = {}
|
10
10
|
@primary_key = :id
|
11
|
+
@raise_on_save_failure = true
|
11
12
|
@restrict_primary_key = true
|
12
13
|
@restricted_columns = nil
|
13
14
|
@sti_dataset = nil
|
@@ -26,6 +27,10 @@ module Sequel
|
|
26
27
|
# The default primary key for classes (default: :id)
|
27
28
|
metaattr_reader :primary_key
|
28
29
|
|
30
|
+
# Whether to raise an error instead of returning nil on a failure
|
31
|
+
# to save/create/save_changes/etc.
|
32
|
+
metaattr_accessor :raise_on_save_failure
|
33
|
+
|
29
34
|
# Which columns should not be update in a call to set
|
30
35
|
# (default: no columns).
|
31
36
|
metaattr_reader :restricted_columns
|
@@ -52,12 +57,13 @@ module Sequel
|
|
52
57
|
insert_multiple intersect interval invert_order join join_table last
|
53
58
|
left_outer_join limit map multi_insert naked order order_by order_more
|
54
59
|
paginate print query range reverse_order right_outer_join select
|
55
|
-
select_all select_more set set_graph_aliases single_value size to_csv
|
56
|
-
transform union uniq unordered update where'.map{|x| x.to_sym}
|
60
|
+
select_all select_more set set_graph_aliases single_value size to_csv to_hash
|
61
|
+
transform union uniq unfiltered unordered update where'.map{|x| x.to_sym}
|
57
62
|
|
58
63
|
# Instance variables that are inherited in subclasses
|
59
|
-
INHERITED_INSTANCE_VARIABLES = {:@allowed_columns=>:dup, :@
|
60
|
-
:@
|
64
|
+
INHERITED_INSTANCE_VARIABLES = {:@allowed_columns=>:dup, :@cache_store=>nil,
|
65
|
+
:@cache_ttl=>nil, :@dataset_methods=>:dup, :@primary_key=>nil,
|
66
|
+
:@raise_on_save_failure=>nil, :@restricted_columns=>:dup, :@restrict_primary_key=>nil,
|
61
67
|
:@sti_dataset=>nil, :@sti_key=>nil, :@strict_param_setting=>nil,
|
62
68
|
:@typecast_on_assignment=>nil}
|
63
69
|
|
@@ -83,7 +89,7 @@ module Sequel
|
|
83
89
|
# dataset) it will use Dataset#columns to find the columns, which
|
84
90
|
# may be empty if the Dataset has no records.
|
85
91
|
def self.columns
|
86
|
-
@columns || set_columns(dataset.naked.columns
|
92
|
+
@columns || set_columns(dataset.naked.columns)
|
87
93
|
end
|
88
94
|
|
89
95
|
# Creates new instance with values set to passed-in Hash, saves it
|
@@ -92,7 +98,7 @@ module Sequel
|
|
92
98
|
# returns false.
|
93
99
|
def self.create(values = {}, &block)
|
94
100
|
obj = new(values, &block)
|
95
|
-
return
|
101
|
+
return unless obj.save
|
96
102
|
obj
|
97
103
|
end
|
98
104
|
|
@@ -155,6 +161,17 @@ module Sequel
|
|
155
161
|
db.fetch(*args).set_model(self)
|
156
162
|
end
|
157
163
|
|
164
|
+
# Modify and return eager loading dataset based on association options
|
165
|
+
def self.eager_loading_dataset(opts, ds, select, associations)
|
166
|
+
ds = ds.select(*select) if select
|
167
|
+
ds = ds.order(*opts[:order]) if opts[:order]
|
168
|
+
ds = ds.eager(opts[:eager]) if opts[:eager]
|
169
|
+
ds = ds.eager_graph(opts[:eager_graph]) if opts[:eager_graph]
|
170
|
+
ds = ds.eager(associations) unless associations.blank?
|
171
|
+
ds = opts[:eager_block].call(ds) if opts[:eager_block]
|
172
|
+
ds
|
173
|
+
end
|
174
|
+
|
158
175
|
# Finds a single record according to the supplied filter, e.g.:
|
159
176
|
#
|
160
177
|
# Ticket.find :author => 'Sharon' # => record
|
@@ -441,7 +458,11 @@ module Sequel
|
|
441
458
|
# Dataset is for a single table with all columns,
|
442
459
|
# so set the columns based on the order they were
|
443
460
|
# returned by the schema.
|
444
|
-
|
461
|
+
cols = schema_array.collect{|k,v| k}
|
462
|
+
set_columns(cols)
|
463
|
+
# Also set the columns for the dataset, so the dataset
|
464
|
+
# doesn't have to do a query to get them.
|
465
|
+
dataset.instance_variable_set(:@columns, cols)
|
445
466
|
end
|
446
467
|
else
|
447
468
|
# If the dataset uses multiple tables or custom sql or getting
|
@@ -56,8 +56,8 @@ module Sequel::Model::Associations::EagerLoading
|
|
56
56
|
# or join the tables you need to filter on manually.
|
57
57
|
#
|
58
58
|
# Each association's order, if definied, is respected. Eager also works
|
59
|
-
# on a limited dataset
|
60
|
-
# argument, it is used.
|
59
|
+
# on a limited dataset, but does not use any :limit options for associations.
|
60
|
+
# If the association uses a block or has an :eager_block argument, it is used.
|
61
61
|
def eager(*associations)
|
62
62
|
model = check_model
|
63
63
|
opt = @opts[:eager]
|
@@ -90,7 +90,7 @@ module Sequel::Model::Associations::EagerLoading
|
|
90
90
|
# This does not respect each association's order, as all associations are loaded in
|
91
91
|
# a single query. If you want to order the results, you must manually call .order.
|
92
92
|
#
|
93
|
-
# #eager_graph probably won't work
|
93
|
+
# #eager_graph probably won't work correctly on a limited dataset, unless you are
|
94
94
|
# only graphing many_to_one associations.
|
95
95
|
#
|
96
96
|
# Does not use the block defined for the association, since it does a single query for
|
@@ -132,18 +132,22 @@ module Sequel::Model::Associations::EagerLoading
|
|
132
132
|
assoc_table_alias = ds.eager_unique_table_alias(ds, assoc_name)
|
133
133
|
join_type = r[:graph_join_type]
|
134
134
|
conditions = r[:graph_conditions]
|
135
|
+
use_only_conditions = r.include?(:graph_only_conditions)
|
136
|
+
only_conditions = r[:graph_only_conditions]
|
135
137
|
select = r[:graph_select]
|
138
|
+
graph_block = r[:graph_block]
|
136
139
|
ds = case assoc_type = r[:type]
|
137
140
|
when :many_to_one
|
138
|
-
ds.graph(klass, [[klass.primary_key,
|
141
|
+
ds.graph(klass, use_only_conditions ? only_conditions : [[klass.primary_key, r[:key].qualify(ta)]] + conditions, :select=>select, :table_alias=>assoc_table_alias, :join_type=>join_type, &graph_block)
|
139
142
|
when :one_to_many
|
140
|
-
ds = ds.graph(klass, [[r[:key],
|
143
|
+
ds = ds.graph(klass, use_only_conditions ? only_conditions : [[r[:key], model.primary_key.qualify(ta)]] + conditions, :select=>select, :table_alias=>assoc_table_alias, :join_type=>join_type, &graph_block)
|
141
144
|
# We only load reciprocals for one_to_many associations, as other reciprocals don't make sense
|
142
145
|
ds.opts[:eager_graph][:reciprocals][assoc_table_alias] = r.reciprocal
|
143
146
|
ds
|
144
147
|
when :many_to_many
|
145
|
-
|
146
|
-
ds.graph(
|
148
|
+
use_jt_only_conditions = r.include?(:graph_join_table_only_conditions)
|
149
|
+
ds = ds.graph(r[:join_table], use_jt_only_conditions ? r[:graph_join_table_only_conditions] : [[r[:left_key], model.primary_key.qualify(ta)]] + r[:graph_join_table_conditions], :select=>false, :table_alias=>ds.eager_unique_table_alias(ds, r[:join_table]), :join_type=>r[:graph_join_table_join_type], &r[:graph_join_table_block])
|
150
|
+
ds.graph(klass, use_only_conditions ? only_conditions : [[klass.primary_key, r[:right_key]]] + conditions, :select=>select, :table_alias=>assoc_table_alias, :join_type=>join_type, &graph_block)
|
147
151
|
end
|
148
152
|
eager_graph = ds.opts[:eager_graph]
|
149
153
|
eager_graph[:requirements][assoc_table_alias] = requirements.dup
|
@@ -358,70 +362,7 @@ module Sequel::Model::Associations::EagerLoading
|
|
358
362
|
end
|
359
363
|
end
|
360
364
|
|
361
|
-
|
362
|
-
# for the association for all model objects
|
363
|
-
reflections.each do |reflection|
|
364
|
-
assoc_class = reflection.associated_class
|
365
|
-
assoc_name = reflection[:name]
|
366
|
-
# Proc for setting cascaded eager loading
|
367
|
-
assoc_block = Proc.new do |d|
|
368
|
-
if order = reflection[:order]
|
369
|
-
d = d.order(*order)
|
370
|
-
end
|
371
|
-
if c = eager_assoc[assoc_name]
|
372
|
-
d = d.eager(c)
|
373
|
-
end
|
374
|
-
if c = reflection[:eager]
|
375
|
-
d = d.eager(c)
|
376
|
-
end
|
377
|
-
if b = reflection[:eager_block]
|
378
|
-
d = b.call(d)
|
379
|
-
end
|
380
|
-
d
|
381
|
-
end
|
382
|
-
case rtype = reflection[:type]
|
383
|
-
when :many_to_one
|
384
|
-
key = reflection[:key]
|
385
|
-
h = key_hash[key]
|
386
|
-
keys = h.keys
|
387
|
-
# No records have the foreign key set for this association, so skip it
|
388
|
-
next unless keys.length > 0
|
389
|
-
# Set the instance variable to null by default, so records that
|
390
|
-
# don't have a associated records will cache the negative lookup.
|
391
|
-
a.each do |object|
|
392
|
-
object.associations[assoc_name] = nil
|
393
|
-
end
|
394
|
-
assoc_block.call(assoc_class.select(*reflection.select).filter(assoc_class.primary_key=>keys)).all do |assoc_object|
|
395
|
-
next unless objects = h[assoc_object.pk]
|
396
|
-
objects.each{|object| object.associations[assoc_name] = assoc_object}
|
397
|
-
end
|
398
|
-
when :one_to_many, :many_to_many
|
399
|
-
h = key_hash[model.primary_key]
|
400
|
-
ds = if rtype == :one_to_many
|
401
|
-
fkey = reflection[:key]
|
402
|
-
reciprocal = reflection.reciprocal
|
403
|
-
assoc_class.select(*reflection.select).filter(fkey=>h.keys)
|
404
|
-
else
|
405
|
-
fkey = reflection[:left_key_alias]
|
406
|
-
assoc_class.select(*(Array(reflection.select)+Array(reflection[:left_key_select]))).inner_join(reflection[:join_table], [[reflection[:right_key], reflection.associated_primary_key], [reflection[:left_key], h.keys]])
|
407
|
-
end
|
408
|
-
h.values.each do |object_array|
|
409
|
-
object_array.each{|object| object.associations[assoc_name] = []}
|
410
|
-
end
|
411
|
-
assoc_block.call(ds).all do |assoc_object|
|
412
|
-
fk = if rtype == :many_to_many
|
413
|
-
assoc_object.values.delete(fkey)
|
414
|
-
else
|
415
|
-
assoc_object[fkey]
|
416
|
-
end
|
417
|
-
next unless objects = h[fk]
|
418
|
-
objects.each do |object|
|
419
|
-
object.associations[assoc_name].push(assoc_object)
|
420
|
-
assoc_object.associations[reciprocal] = object if reciprocal
|
421
|
-
end
|
422
|
-
end
|
423
|
-
end
|
424
|
-
end
|
365
|
+
reflections.each{|r| r[:eager_loader].call(key_hash, a, eager_assoc[r[:name]])}
|
425
366
|
end
|
426
367
|
|
427
368
|
# Build associations from the graph if #eager_graph was used,
|
data/lib/sequel_model/record.rb
CHANGED
@@ -2,7 +2,7 @@ module Sequel
|
|
2
2
|
class Model
|
3
3
|
# The setter methods (methods ending with =) that are never allowed
|
4
4
|
# to be called automatically via set.
|
5
|
-
RESTRICTED_SETTER_METHODS = %w"== === []= taguri= typecast_on_assignment="
|
5
|
+
RESTRICTED_SETTER_METHODS = %w"== === []= taguri= typecast_on_assignment= strict_param_setting= raise_on_save_failure="
|
6
6
|
|
7
7
|
# The current cached associations. A hash with the keys being the
|
8
8
|
# association name symbols and the values being the associated object
|
@@ -13,6 +13,10 @@ module Sequel
|
|
13
13
|
# see Model#[]=.
|
14
14
|
attr_reader :changed_columns
|
15
15
|
|
16
|
+
# Whether this model instance should raise an exception instead of
|
17
|
+
# returning nil on a failure to save/save_changes/etc.
|
18
|
+
attr_writer :raise_on_save_failure
|
19
|
+
|
16
20
|
# Whether this model instance should raise an error if attempting
|
17
21
|
# to call a method through set/update and their variants that either
|
18
22
|
# doesn't exist or access to it is denied.
|
@@ -42,6 +46,7 @@ module Sequel
|
|
42
46
|
@associations = {}
|
43
47
|
@db_schema = model.db_schema
|
44
48
|
@changed_columns = []
|
49
|
+
@raise_on_save_failure = model.raise_on_save_failure
|
45
50
|
@strict_param_setting = model.strict_param_setting
|
46
51
|
@typecast_on_assignment = model.typecast_on_assignment
|
47
52
|
if from_db
|
@@ -108,7 +113,7 @@ module Sequel
|
|
108
113
|
# the item from the database and returns self.
|
109
114
|
def destroy
|
110
115
|
db.transaction do
|
111
|
-
return
|
116
|
+
return save_failure(:destroy) if before_destroy == false
|
112
117
|
delete
|
113
118
|
after_destroy
|
114
119
|
end
|
@@ -187,12 +192,13 @@ module Sequel
|
|
187
192
|
alias_method :reload, :refresh
|
188
193
|
|
189
194
|
# Creates or updates the record, after making sure the record
|
190
|
-
# is valid. If the record is not valid,
|
191
|
-
#
|
192
|
-
#
|
193
|
-
# returns self.
|
195
|
+
# is valid. If the record is not valid, or before_save,
|
196
|
+
# before_create (if new?), or before_update (if !new?) return
|
197
|
+
# false, returns nil unless raise_on_save_failure is true.
|
198
|
+
# Otherwise, returns self. You can provide an optional list of
|
199
|
+
# columns to update, in which case it only updates those columns.
|
194
200
|
def save(*columns)
|
195
|
-
return
|
201
|
+
return save_failure(:save) unless valid?
|
196
202
|
save!(*columns)
|
197
203
|
end
|
198
204
|
|
@@ -200,12 +206,12 @@ module Sequel
|
|
200
206
|
# it first. You can provide an optional list of columns to update,
|
201
207
|
# in which case it only updates those columns.
|
202
208
|
# If before_save, before_create (if new?), or before_update
|
203
|
-
# (if !new?) return false, returns
|
204
|
-
# returns self.
|
209
|
+
# (if !new?) return false, returns nil unless raise_on_save_failure
|
210
|
+
# is true. Otherwise, returns self.
|
205
211
|
def save!(*columns)
|
206
|
-
return
|
212
|
+
return save_failure(:save) if before_save == false
|
207
213
|
if @new
|
208
|
-
return
|
214
|
+
return save_failure(:create) if before_create == false
|
209
215
|
iid = model.dataset.insert(@values)
|
210
216
|
# if we have a regular primary key and it's not set in @values,
|
211
217
|
# we assume it's the last inserted id
|
@@ -219,7 +225,7 @@ module Sequel
|
|
219
225
|
@new = false
|
220
226
|
after_create
|
221
227
|
else
|
222
|
-
return
|
228
|
+
return save_failure(:update) if before_update == false
|
223
229
|
if columns.empty?
|
224
230
|
this.update(@values)
|
225
231
|
@changed_columns = []
|
@@ -234,9 +240,10 @@ module Sequel
|
|
234
240
|
end
|
235
241
|
|
236
242
|
# Saves only changed columns or does nothing if no columns are marked as
|
237
|
-
# chanaged.
|
243
|
+
# chanaged. If no columns have been changed, returns nil. If unable to
|
244
|
+
# save, returns false unless raise_on_save_failure is true.
|
238
245
|
def save_changes
|
239
|
-
save(*@changed_columns) unless @changed_columns.empty?
|
246
|
+
save(*@changed_columns) || false unless @changed_columns.empty?
|
240
247
|
end
|
241
248
|
|
242
249
|
# Updates the instance with the supplied values with support for virtual
|
@@ -335,6 +342,128 @@ module Sequel
|
|
335
342
|
|
336
343
|
private
|
337
344
|
|
345
|
+
# Backbone behind association_dataset
|
346
|
+
def _dataset(opts)
|
347
|
+
raise(Sequel::Error, 'model object does not have a primary key') if opts.dataset_need_primary_key? && !pk
|
348
|
+
ds = send(opts._dataset_method)
|
349
|
+
opts[:extend].each{|m| ds.extend(m)}
|
350
|
+
ds = ds.select(*opts.select) if opts.select
|
351
|
+
ds = ds.order(*opts[:order]) if opts[:order]
|
352
|
+
ds = ds.limit(*opts[:limit]) if opts[:limit]
|
353
|
+
ds = ds.eager(*opts[:eager]) if opts[:eager]
|
354
|
+
ds = ds.eager_graph(opts[:eager_graph]) if opts[:eager_graph] && opts.eager_graph_lazy_dataset?
|
355
|
+
ds = send(opts.dataset_helper_method, ds) if opts[:block]
|
356
|
+
ds
|
357
|
+
end
|
358
|
+
|
359
|
+
# Add the given associated object to the given association
|
360
|
+
def add_associated_object(opts, o)
|
361
|
+
raise(Sequel::Error, 'model object does not have a primary key') unless pk
|
362
|
+
raise(Sequel::Error, 'associated object does not have a primary key') if opts.need_associated_primary_key? && !o.pk
|
363
|
+
return if run_association_callbacks(opts, :before_add, o) == false
|
364
|
+
send(opts._add_method, o)
|
365
|
+
@associations[opts[:name]].push(o) if @associations.include?(opts[:name])
|
366
|
+
add_reciprocal_object(opts, o)
|
367
|
+
run_association_callbacks(opts, :after_add, o)
|
368
|
+
o
|
369
|
+
end
|
370
|
+
|
371
|
+
# Add/Set the current object to/as the given object's reciprocal association.
|
372
|
+
def add_reciprocal_object(opts, o)
|
373
|
+
return unless reciprocal = opts.reciprocal
|
374
|
+
case opts[:type]
|
375
|
+
when :many_to_many, :many_to_one
|
376
|
+
if array = o.associations[reciprocal] and !array.include?(self)
|
377
|
+
array.push(self)
|
378
|
+
end
|
379
|
+
when :one_to_many
|
380
|
+
o.associations[reciprocal] = self
|
381
|
+
end
|
382
|
+
end
|
383
|
+
|
384
|
+
# Load the associated objects using the dataset
|
385
|
+
def load_associated_objects(opts, reload=false)
|
386
|
+
name = opts[:name]
|
387
|
+
if @associations.include?(name) and !reload
|
388
|
+
@associations[name]
|
389
|
+
else
|
390
|
+
objs = if opts.single_associated_object?
|
391
|
+
if !opts[:key]
|
392
|
+
send(opts.dataset_method).all.first
|
393
|
+
elsif send(opts[:key])
|
394
|
+
send(opts.dataset_method).first
|
395
|
+
end
|
396
|
+
else
|
397
|
+
objs = send(opts.dataset_method).all
|
398
|
+
end
|
399
|
+
run_association_callbacks(opts, :after_load, objs)
|
400
|
+
# Only one_to_many associations should set the reciprocal object
|
401
|
+
objs.each{|o| add_reciprocal_object(opts, o)} if opts.set_reciprocal_to_self?
|
402
|
+
@associations[name] = objs
|
403
|
+
end
|
404
|
+
end
|
405
|
+
|
406
|
+
# Remove all associated objects from the given association
|
407
|
+
def remove_all_associated_objects(opts)
|
408
|
+
raise(Sequel::Error, 'model object does not have a primary key') unless pk
|
409
|
+
send(opts._remove_all_method)
|
410
|
+
ret = @associations[opts[:name]].each{|o| remove_reciprocal_object(opts, o)} if @associations.include?(opts[:name])
|
411
|
+
@associations[opts[:name]] = []
|
412
|
+
ret
|
413
|
+
end
|
414
|
+
|
415
|
+
# Remove the given associated object from the given association
|
416
|
+
def remove_associated_object(opts, o)
|
417
|
+
raise(Sequel::Error, 'model object does not have a primary key') unless pk
|
418
|
+
raise(Sequel::Error, 'associated object does not have a primary key') if opts.need_associated_primary_key? && !o.pk
|
419
|
+
return if run_association_callbacks(opts, :before_remove, o) == false
|
420
|
+
send(opts._remove_method, o)
|
421
|
+
@associations[opts[:name]].delete_if{|x| o === x} if @associations.include?(opts[:name])
|
422
|
+
remove_reciprocal_object(opts, o)
|
423
|
+
run_association_callbacks(opts, :after_remove, o)
|
424
|
+
o
|
425
|
+
end
|
426
|
+
|
427
|
+
# Remove/unset the current object from/as the given object's reciprocal association.
|
428
|
+
def remove_reciprocal_object(opts, o)
|
429
|
+
return unless reciprocal = opts.reciprocal
|
430
|
+
case opts[:type]
|
431
|
+
when :many_to_many, :many_to_one
|
432
|
+
if array = o.associations[reciprocal]
|
433
|
+
array.delete_if{|x| self === x}
|
434
|
+
end
|
435
|
+
when :one_to_many
|
436
|
+
o.associations[reciprocal] = nil
|
437
|
+
end
|
438
|
+
end
|
439
|
+
|
440
|
+
# Run the callback for the association with the object.
|
441
|
+
def run_association_callbacks(reflection, callback_type, object)
|
442
|
+
raise_error = @raise_on_save_failure
|
443
|
+
raise_error = true if reflection[:type] == :many_to_one
|
444
|
+
stop_on_false = true if [:before_add, :before_remove].include?(callback_type)
|
445
|
+
reflection[callback_type].each do |cb|
|
446
|
+
res = case cb
|
447
|
+
when Symbol
|
448
|
+
send(cb, object)
|
449
|
+
when Proc
|
450
|
+
cb.call(self, object)
|
451
|
+
else
|
452
|
+
raise Error, "callbacks should either be Procs or Symbols"
|
453
|
+
end
|
454
|
+
if res == false and stop_on_false
|
455
|
+
save_failure("modify association for", raise_error)
|
456
|
+
return false
|
457
|
+
end
|
458
|
+
end
|
459
|
+
end
|
460
|
+
|
461
|
+
# Raise an error if raise_on_save_failure is true
|
462
|
+
def save_failure(action, raise_error = nil)
|
463
|
+
raise_error = @raise_on_save_failure if raise_error.nil?
|
464
|
+
raise(Error, "unable to #{action} record") if raise_error
|
465
|
+
end
|
466
|
+
|
338
467
|
# Set the columns, filtered by the only and except arrays.
|
339
468
|
def set_restricted(hash, only, except)
|
340
469
|
columns_not_set = model.instance_variable_get(:@columns).blank?
|
@@ -1,6 +1,6 @@
|
|
1
1
|
module Sequel
|
2
2
|
class Model
|
3
|
-
# The Validation module houses a couple of
|
3
|
+
# The Validation module houses a couple of classes used by Sequel's
|
4
4
|
# validation code.
|
5
5
|
module Validation
|
6
6
|
# Validation::Errors represents validation errors, a simple hash subclass
|
@@ -85,11 +85,21 @@ module Sequel
|
|
85
85
|
end
|
86
86
|
validations.each do |att, procs|
|
87
87
|
v = o.send(att)
|
88
|
-
procs.each {|p| p
|
88
|
+
procs.each {|tag, p| p.call(o, att, v)}
|
89
89
|
end
|
90
90
|
end
|
91
91
|
|
92
|
-
# Validates acceptance of an attribute.
|
92
|
+
# Validates acceptance of an attribute. Just checks that the value
|
93
|
+
# is equal to the :accept option.
|
94
|
+
#
|
95
|
+
# Possible Options:
|
96
|
+
# * :accept - The value required for the object to be valid (default: '1')
|
97
|
+
# * :allow_blank - Whether to skip the validation if the value is blank (default: false)
|
98
|
+
# * :allow_nil - Whether to skip the validation if the value is nil (default: true)
|
99
|
+
# * :if - A symbol (indicating an instance_method) or proc (which is instance_evaled)
|
100
|
+
# skipping this validation if it returns nil or false.
|
101
|
+
# * :message - The message to use (default: 'is not accepted')
|
102
|
+
# * :tag - The tag to use for this validation (default: :acceptance)
|
93
103
|
def self.validates_acceptance_of(*atts)
|
94
104
|
opts = {
|
95
105
|
:message => 'is not accepted',
|
@@ -97,21 +107,35 @@ module Sequel
|
|
97
107
|
:accept => '1'
|
98
108
|
}.merge!(atts.extract_options!)
|
99
109
|
|
110
|
+
atts << {:if=>opts[:if], :tag=>opts[:tag]||:acceptance}
|
100
111
|
validates_each(*atts) do |o, a, v|
|
101
|
-
next unless o.instance_eval(&if_proc(opts))
|
102
112
|
next if (v.nil? && opts[:allow_nil]) || (v.blank? && opts[:allow_blank])
|
103
113
|
o.errors[a] << opts[:message] unless v == opts[:accept]
|
104
114
|
end
|
105
115
|
end
|
106
116
|
|
107
|
-
# Validates confirmation of an attribute.
|
117
|
+
# Validates confirmation of an attribute. Checks that the object has
|
118
|
+
# a _confirmation value matching the current value. For example:
|
119
|
+
#
|
120
|
+
# validates_confirmation_of :blah
|
121
|
+
#
|
122
|
+
# Just makes sure that object.blah = object.blah_confirmation. Often used for passwords
|
123
|
+
# or email addresses on web forms.
|
124
|
+
#
|
125
|
+
# Possible Options:
|
126
|
+
# * :allow_false - Whether to skip the validation if the value is blank (default: false)
|
127
|
+
# * :allow_nil - Whether to skip the validation if the value is nil (default: false)
|
128
|
+
# * :if - A symbol (indicating an instance_method) or proc (which is instance_evaled)
|
129
|
+
# skipping this validation if it returns nil or false.
|
130
|
+
# * :message - The message to use (default: 'is not confirmed')
|
131
|
+
# * :tag - The tag to use for this validation (default: :confirmation)
|
108
132
|
def self.validates_confirmation_of(*atts)
|
109
133
|
opts = {
|
110
134
|
:message => 'is not confirmed',
|
111
135
|
}.merge!(atts.extract_options!)
|
112
|
-
|
136
|
+
|
137
|
+
atts << {:if=>opts[:if], :tag=>opts[:tag]||:confirmation}
|
113
138
|
validates_each(*atts) do |o, a, v|
|
114
|
-
next unless o.instance_eval(&if_proc(opts))
|
115
139
|
next if (v.nil? && opts[:allow_nil]) || (v.blank? && opts[:allow_blank])
|
116
140
|
c = o.send(:"#{a}_confirmation")
|
117
141
|
o.errors[a] << opts[:message] unless v == c
|
@@ -125,11 +149,40 @@ module Sequel
|
|
125
149
|
# validates_each :name, :password do |object, attribute, value|
|
126
150
|
# object.errors[attribute] << 'is not nice' unless value.nice?
|
127
151
|
# end
|
152
|
+
#
|
153
|
+
# Possible Options:
|
154
|
+
# * :if - A symbol (indicating an instance_method) or proc (which is instance_evaled)
|
155
|
+
# skipping this validation if it returns nil or false.
|
156
|
+
# * :tag - The tag to use for this validation (default: nil)
|
128
157
|
def self.validates_each(*atts, &block)
|
129
|
-
atts.
|
158
|
+
opts = atts.extract_options!
|
159
|
+
blk = if opts[:if]
|
160
|
+
proc{|o,a,v| block.call(o,a,v) if o.instance_eval(&if_proc(opts))}
|
161
|
+
else
|
162
|
+
block
|
163
|
+
end
|
164
|
+
tag = opts[:tag]
|
165
|
+
atts.each do |a|
|
166
|
+
a_vals = validations[a]
|
167
|
+
if tag && (old = a_vals.find{|x| x[0] == tag})
|
168
|
+
old[1] = blk
|
169
|
+
else
|
170
|
+
a_vals << [tag, blk]
|
171
|
+
end
|
172
|
+
end
|
130
173
|
end
|
131
174
|
|
132
|
-
# Validates the format of an attribute
|
175
|
+
# Validates the format of an attribute, checking the string representation of the
|
176
|
+
# value against the regular expression provided by the :with option.
|
177
|
+
#
|
178
|
+
# Possible Options:
|
179
|
+
# * :allow_blank - Whether to skip the validation if the value is blank (default: false)
|
180
|
+
# * :allow_nil - Whether to skip the validation if the value is nil (default: false)
|
181
|
+
# * :if - A symbol (indicating an instance_method) or proc (which is instance_evaled)
|
182
|
+
# skipping this validation if it returns nil or false.
|
183
|
+
# * :message - The message to use (default: 'is invalid')
|
184
|
+
# * :tag - The tag to use for this validation (default: :format)
|
185
|
+
# * :with - The regular expression to validate the value with (required).
|
133
186
|
def self.validates_format_of(*atts)
|
134
187
|
opts = {
|
135
188
|
:message => 'is invalid',
|
@@ -139,14 +192,30 @@ module Sequel
|
|
139
192
|
raise ArgumentError, "A regular expression must be supplied as the :with option of the options hash"
|
140
193
|
end
|
141
194
|
|
195
|
+
atts << {:if=>opts[:if], :tag=>opts[:tag]||:format}
|
142
196
|
validates_each(*atts) do |o, a, v|
|
143
|
-
next unless o.instance_eval(&if_proc(opts))
|
144
197
|
next if (v.nil? && opts[:allow_nil]) || (v.blank? && opts[:allow_blank])
|
145
198
|
o.errors[a] << opts[:message] unless v.to_s =~ opts[:with]
|
146
199
|
end
|
147
200
|
end
|
148
201
|
|
149
202
|
# Validates the length of an attribute.
|
203
|
+
#
|
204
|
+
# Possible Options:
|
205
|
+
# * :allow_blank - Whether to skip the validation if the value is blank (default: false)
|
206
|
+
# * :allow_nil - Whether to skip the validation if the value is nil (default: false)
|
207
|
+
# * :if - A symbol (indicating an instance_method) or proc (which is instance_evaled)
|
208
|
+
# skipping this validation if it returns nil or false.
|
209
|
+
# * :is - The exact size required for the value to be valid (no default)
|
210
|
+
# * :maximum - The maximum size allowed for the value (no default)
|
211
|
+
# * :message - The message to use (no default, overrides :too_long, :too_short, and :wrong_length
|
212
|
+
# options if present)
|
213
|
+
# * :minimum - The minimum size allowed for the value (no default)
|
214
|
+
# * :tag - The tag to use for this validation (default: :length)
|
215
|
+
# * :too_long - The message to use use if it the value is too long (default: 'is too long')
|
216
|
+
# * :too_short - The message to use use if it the value is too short (default: 'is too short')
|
217
|
+
# * :with - The array/range that must include the size of the value for it to be valid (no default)
|
218
|
+
# * :wrong_length - The message to use use if it the value is not valid (default: 'is the wrong length')
|
150
219
|
def self.validates_length_of(*atts)
|
151
220
|
opts = {
|
152
221
|
:too_long => 'is too long',
|
@@ -154,8 +223,8 @@ module Sequel
|
|
154
223
|
:wrong_length => 'is the wrong length'
|
155
224
|
}.merge!(atts.extract_options!)
|
156
225
|
|
226
|
+
atts << {:if=>opts[:if], :tag=>opts[:tag]||:length}
|
157
227
|
validates_each(*atts) do |o, a, v|
|
158
|
-
next unless o.instance_eval(&if_proc(opts))
|
159
228
|
next if (v.nil? && opts[:allow_nil]) || (v.blank? && opts[:allow_blank])
|
160
229
|
if m = opts[:maximum]
|
161
230
|
o.errors[a] << (opts[:message] || opts[:too_long]) unless v && v.size <= m
|
@@ -173,13 +242,22 @@ module Sequel
|
|
173
242
|
end
|
174
243
|
|
175
244
|
# Validates whether an attribute is a number.
|
245
|
+
#
|
246
|
+
# Possible Options:
|
247
|
+
# * :allow_blank - Whether to skip the validation if the value is blank (default: false)
|
248
|
+
# * :allow_nil - Whether to skip the validation if the value is nil (default: false)
|
249
|
+
# * :if - A symbol (indicating an instance_method) or proc (which is instance_evaled)
|
250
|
+
# skipping this validation if it returns nil or false.
|
251
|
+
# * :message - The message to use (default: 'is not a number')
|
252
|
+
# * :tag - The tag to use for this validation (default: :numericality)
|
253
|
+
# * :only_integer - Whether only integers are valid values (default: false)
|
176
254
|
def self.validates_numericality_of(*atts)
|
177
255
|
opts = {
|
178
256
|
:message => 'is not a number',
|
179
257
|
}.merge!(atts.extract_options!)
|
180
258
|
|
259
|
+
atts << {:if=>opts[:if], :tag=>opts[:tag]||:numericality}
|
181
260
|
validates_each(*atts) do |o, a, v|
|
182
|
-
next unless o.instance_eval(&if_proc(opts))
|
183
261
|
next if (v.nil? && opts[:allow_nil]) || (v.blank? && opts[:allow_blank])
|
184
262
|
begin
|
185
263
|
if opts[:only_integer]
|
@@ -193,28 +271,41 @@ module Sequel
|
|
193
271
|
end
|
194
272
|
end
|
195
273
|
|
196
|
-
# Validates the presence of an attribute.
|
274
|
+
# Validates the presence of an attribute. Requires the value not be blank,
|
275
|
+
# with false considered present instead of absent.
|
276
|
+
#
|
277
|
+
# Possible Options:
|
278
|
+
# * :if - A symbol (indicating an instance_method) or proc (which is instance_evaled)
|
279
|
+
# skipping this validation if it returns nil or false.
|
280
|
+
# * :message - The message to use (default: 'is not present')
|
281
|
+
# * :tag - The tag to use for this validation (default: :presence)
|
197
282
|
def self.validates_presence_of(*atts)
|
198
283
|
opts = {
|
199
284
|
:message => 'is not present',
|
200
285
|
}.merge!(atts.extract_options!)
|
201
286
|
|
287
|
+
atts << {:if=>opts[:if], :tag=>opts[:tag]||:presence}
|
202
288
|
validates_each(*atts) do |o, a, v|
|
203
|
-
|
204
|
-
o.errors[a] << opts[:message] unless v && !v.blank?
|
289
|
+
o.errors[a] << opts[:message] if v.blank? && v != false
|
205
290
|
end
|
206
291
|
end
|
207
292
|
|
208
293
|
# Validates only if the fields in the model (specified by atts) are
|
209
294
|
# unique in the database. You should also add a unique index in the
|
210
295
|
# database, as this suffers from a fairly obvious race condition.
|
296
|
+
#
|
297
|
+
# Possible Options:
|
298
|
+
# * :if - A symbol (indicating an instance_method) or proc (which is instance_evaled)
|
299
|
+
# skipping this validation if it returns nil or false.
|
300
|
+
# * :message - The message to use (default: 'is already taken')
|
301
|
+
# * :tag - The tag to use for this validation (default: :uniqueness)
|
211
302
|
def self.validates_uniqueness_of(*atts)
|
212
303
|
opts = {
|
213
304
|
:message => 'is already taken',
|
214
305
|
}.merge!(atts.extract_options!)
|
215
306
|
|
307
|
+
atts << {:if=>opts[:if], :tag=>opts[:tag]||:uniqueness}
|
216
308
|
validates_each(*atts) do |o, a, v|
|
217
|
-
next unless o.instance_eval(&if_proc(opts))
|
218
309
|
next if v.blank?
|
219
310
|
num_dups = o.class.filter(a => v).count
|
220
311
|
allow = if num_dups == 0
|
@@ -244,7 +335,7 @@ module Sequel
|
|
244
335
|
|
245
336
|
### Private Class Methods ###
|
246
337
|
|
247
|
-
def self.if_proc(opts)
|
338
|
+
def self.if_proc(opts) # :nodoc:
|
248
339
|
case opts[:if]
|
249
340
|
when Symbol then proc{send opts[:if]}
|
250
341
|
when Proc then opts[:if]
|