sequel 1.4.0 → 1.5.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +56 -0
- data/README +257 -154
- data/Rakefile +8 -16
- data/lib/sequel_model.rb +15 -257
- data/lib/sequel_model/associations.rb +70 -33
- data/lib/sequel_model/base.rb +80 -35
- data/lib/sequel_model/caching.rb +3 -3
- data/lib/sequel_model/deprecated.rb +81 -0
- data/lib/sequel_model/eager_loading.rb +303 -43
- data/lib/sequel_model/hooks.rb +29 -25
- data/lib/sequel_model/inflections.rb +112 -0
- data/lib/sequel_model/inflector.rb +279 -0
- data/lib/sequel_model/plugins.rb +15 -14
- data/lib/sequel_model/record.rb +87 -75
- data/lib/sequel_model/schema.rb +2 -0
- data/lib/sequel_model/validations.rb +300 -2
- data/spec/associations_spec.rb +175 -9
- data/spec/base_spec.rb +37 -18
- data/spec/caching_spec.rb +7 -4
- data/spec/deprecated_relations_spec.rb +3 -43
- data/spec/eager_loading_spec.rb +295 -7
- data/spec/hooks_spec.rb +7 -4
- data/spec/inflector_spec.rb +34 -0
- data/spec/model_spec.rb +30 -53
- data/spec/record_spec.rb +191 -33
- data/spec/spec_helper.rb +17 -2
- data/spec/validations_spec.rb +414 -15
- metadata +7 -22
- data/lib/sequel_model/pretty_table.rb +0 -73
data/lib/sequel_model/plugins.rb
CHANGED
@@ -28,20 +28,21 @@ module Sequel
|
|
28
28
|
end
|
29
29
|
alias_method :is_a, :is
|
30
30
|
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
31
|
+
private
|
32
|
+
# Returns the module for the specified plugin. If the module is not
|
33
|
+
# defined, the corresponding plugin gem is automatically loaded.
|
34
|
+
def plugin_module(plugin)
|
35
|
+
module_name = plugin.to_s.gsub(/(^|_)(.)/) {$2.upcase}
|
36
|
+
if not Sequel::Plugins.const_defined?(module_name)
|
37
|
+
require plugin_gem(plugin)
|
38
|
+
end
|
39
|
+
Sequel::Plugins.const_get(module_name)
|
40
|
+
end
|
41
|
+
|
42
|
+
# Returns the gem name for the given plugin.
|
43
|
+
def plugin_gem(plugin)
|
44
|
+
"sequel_#{plugin}"
|
37
45
|
end
|
38
|
-
Sequel::Plugins.const_get(module_name)
|
39
|
-
end
|
40
|
-
|
41
|
-
# Returns the gem name for the given plugin.
|
42
|
-
def plugin_gem(plugin)
|
43
|
-
"sequel_#{plugin}"
|
44
|
-
end
|
45
46
|
end
|
46
47
|
end
|
47
|
-
end
|
48
|
+
end
|
data/lib/sequel_model/record.rb
CHANGED
@@ -40,10 +40,18 @@ module Sequel
|
|
40
40
|
def ==(obj)
|
41
41
|
(obj.class == model) && (obj.values == @values)
|
42
42
|
end
|
43
|
+
alias_method :eql?, :"=="
|
43
44
|
|
44
|
-
#
|
45
|
+
# If pk is not nil, true only if the objects have the same class and pk.
|
46
|
+
# If pk is nil, false.
|
45
47
|
def ===(obj)
|
46
|
-
(obj.class == model) && (obj.pk == pk)
|
48
|
+
pk.nil? ? false : (obj.class == model) && (obj.pk == pk)
|
49
|
+
end
|
50
|
+
|
51
|
+
# Unique for objects with the same class and pk (if pk is not nil), or
|
52
|
+
# the same class and values (if pk is nil).
|
53
|
+
def hash
|
54
|
+
[model, pk.nil? ? @values.sort_by{|k,v| k.to_s} : pk].hash
|
47
55
|
end
|
48
56
|
|
49
57
|
# Returns key for primary key.
|
@@ -131,36 +139,39 @@ module Sequel
|
|
131
139
|
class_def(:cache_key) {raise Error, "No primary key is associated with this model"}
|
132
140
|
end
|
133
141
|
|
134
|
-
# Creates new instance with values set to passed-in Hash
|
135
|
-
#
|
142
|
+
# Creates new instance with values set to passed-in Hash, saves it
|
143
|
+
# (running any callbacks), and returns the instance.
|
136
144
|
def self.create(values = {}, &block)
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
obj
|
141
|
-
end
|
145
|
+
obj = new(values, &block)
|
146
|
+
obj.save
|
147
|
+
obj
|
142
148
|
end
|
143
149
|
|
144
150
|
# Updates the instance with the supplied values with support for virtual
|
145
151
|
# attributes, ignoring any values for which no setter method is available.
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
152
|
+
# Does not save the record.
|
153
|
+
#
|
154
|
+
# If no columns have been set for this model (very unlikely), assume symbol
|
155
|
+
# keys are valid column names, and assign the column value based on that.
|
156
|
+
def set_with_params(hash)
|
157
|
+
columns_not_set = !model.instance_variable_get(:@columns)
|
158
|
+
meths = setter_methods
|
159
|
+
hash.each do |k,v|
|
160
|
+
m = "#{k}="
|
161
|
+
if meths.include?(m)
|
162
|
+
send(m, v)
|
163
|
+
elsif columns_not_set && (Symbol === k)
|
164
|
+
self[k] = v
|
165
|
+
end
|
150
166
|
end
|
151
|
-
save_changes
|
152
167
|
end
|
153
|
-
alias_method :update_with, :update_with_params
|
154
168
|
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
record
|
160
|
-
end
|
161
|
-
alias_method :create_with, :create_with_params
|
169
|
+
# Runs set_with_params and saves the changes (which runs any callback methods).
|
170
|
+
def update_with_params(values)
|
171
|
+
set_with_params(values)
|
172
|
+
save_changes
|
162
173
|
end
|
163
|
-
|
174
|
+
|
164
175
|
# Returns (naked) dataset bound to current instance.
|
165
176
|
def this
|
166
177
|
@this ||= self.class.dataset.filter(:id => @values[:id]).limit(1).naked
|
@@ -194,27 +205,19 @@ module Sequel
|
|
194
205
|
# This method guesses whether the record exists when
|
195
206
|
# <tt>new_record</tt> is set to false.
|
196
207
|
def initialize(values = nil, from_db = false, &block)
|
208
|
+
values ||= {}
|
197
209
|
@changed_columns = []
|
198
|
-
|
199
|
-
@
|
200
|
-
|
201
|
-
values.each do |k, v| m = :"#{k}="
|
202
|
-
if respond_to?(m)
|
203
|
-
send(m, v)
|
204
|
-
values.delete(k)
|
205
|
-
end
|
206
|
-
end
|
207
|
-
values.inject(@values) {|m, kv| m[kv[0].to_sym] = kv[1]; m}
|
208
|
-
# @values.merge!(values)
|
209
|
-
end
|
210
|
+
if from_db
|
211
|
+
@new = false
|
212
|
+
@values = values
|
210
213
|
else
|
211
|
-
@values =
|
214
|
+
@values = {}
|
215
|
+
@new = true
|
216
|
+
set_with_params(values)
|
212
217
|
end
|
213
|
-
|
214
|
-
k = primary_key
|
215
|
-
@new = !from_db
|
218
|
+
@changed_columns.clear
|
216
219
|
|
217
|
-
|
220
|
+
yield self if block
|
218
221
|
after_initialize
|
219
222
|
end
|
220
223
|
|
@@ -228,7 +231,6 @@ module Sequel
|
|
228
231
|
def new?
|
229
232
|
@new
|
230
233
|
end
|
231
|
-
alias :new_record? :new?
|
232
234
|
|
233
235
|
# Returns true when current instance exists, false otherwise.
|
234
236
|
def exists?
|
@@ -274,17 +276,47 @@ module Sequel
|
|
274
276
|
save(*@changed_columns) unless @changed_columns.empty?
|
275
277
|
end
|
276
278
|
|
277
|
-
#
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
279
|
+
# Sets the value attributes without saving the record. Returns
|
280
|
+
# the values changed. Raises an error if the keys are not symbols
|
281
|
+
# or strings or a string key was passed that was not a valid column.
|
282
|
+
# This is a low level method that does not respect virtual attributes. It
|
283
|
+
# should probably be avoided. Look into using set_with_params instead.
|
284
|
+
def set_values(values)
|
285
|
+
s = str_columns
|
286
|
+
vals = values.inject({}) do |m, kv|
|
287
|
+
k, v = kv
|
288
|
+
k = case k
|
289
|
+
when Symbol
|
290
|
+
k
|
291
|
+
when String
|
292
|
+
# Prevent denial of service via memory exhaustion by only
|
293
|
+
# calling to_sym if the symbol already exists.
|
294
|
+
raise(::Sequel::Error, "all string keys must be a valid columns") unless s.include?(k)
|
295
|
+
k.to_sym
|
296
|
+
else
|
297
|
+
raise(::Sequel::Error, "Only symbols and strings allows as keys")
|
298
|
+
end
|
299
|
+
m[k] = v
|
300
|
+
m
|
301
|
+
end
|
302
|
+
vals.each {|k, v| @values[k] = v}
|
303
|
+
vals
|
304
|
+
end
|
305
|
+
|
306
|
+
# Sets the values attributes with set_values and then updates
|
307
|
+
# the record in the database using those values. This is a
|
308
|
+
# low level method that does not run the usual save callbacks.
|
309
|
+
# It should probably be avoided. Look into using update_with_params instead.
|
310
|
+
def update_values(values)
|
311
|
+
this.update(set_values(values))
|
282
312
|
end
|
283
|
-
alias_method :update, :set
|
284
313
|
|
285
314
|
# Reloads values from database and returns self.
|
286
315
|
def refresh
|
287
316
|
@values = this.first || raise(Error, "Record not found")
|
317
|
+
model.all_association_reflections.each do |r|
|
318
|
+
instance_variable_set("@#{r[:name]}", nil)
|
319
|
+
end
|
288
320
|
self
|
289
321
|
end
|
290
322
|
alias_method :reload, :refresh
|
@@ -296,41 +328,21 @@ module Sequel
|
|
296
328
|
delete
|
297
329
|
after_destroy
|
298
330
|
end
|
331
|
+
self
|
299
332
|
end
|
300
333
|
|
301
|
-
# Deletes and returns self.
|
334
|
+
# Deletes and returns self. Does not run callbacks.
|
335
|
+
# Look into using destroy instead.
|
302
336
|
def delete
|
303
337
|
this.delete
|
304
338
|
self
|
305
339
|
end
|
306
340
|
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
att = $1.to_sym
|
313
|
-
write = $2 == EQUAL_SIGN
|
314
|
-
|
315
|
-
# check whether the column is legal
|
316
|
-
unless @values.has_key?(att) || columns.include?(att)
|
317
|
-
raise Error, "Invalid column (#{att.inspect}) for #{self}"
|
318
|
-
end
|
319
|
-
|
320
|
-
# define the column accessor
|
321
|
-
Thread.exclusive do
|
322
|
-
if write
|
323
|
-
model.class_def(m) {|v| self[att] = v}
|
324
|
-
else
|
325
|
-
model.class_def(m) {self[att]}
|
326
|
-
end
|
327
|
-
end
|
328
|
-
|
329
|
-
# call the accessor
|
330
|
-
respond_to?(m) ? send(m, *args) : super(m, *args)
|
331
|
-
else
|
332
|
-
super(m, *args)
|
341
|
+
private
|
342
|
+
# Returns all methods that can be used for attribute
|
343
|
+
# assignment (those that end with =)
|
344
|
+
def setter_methods
|
345
|
+
methods.grep(/=\z/)
|
333
346
|
end
|
334
|
-
end
|
335
347
|
end
|
336
348
|
end
|
data/lib/sequel_model/schema.rb
CHANGED
@@ -1,6 +1,304 @@
|
|
1
|
-
|
1
|
+
class Array
|
2
|
+
# Removes and returns the last member of the array if it is a hash. Otherwise,
|
3
|
+
# an empty hash is returned This method is useful when writing methods that
|
4
|
+
# take an options hash as the last parameter. For example:
|
5
|
+
#
|
6
|
+
# def validate_each(*args, &block)
|
7
|
+
# opts = args.extract_options!
|
8
|
+
# ...
|
9
|
+
# end
|
10
|
+
def extract_options!
|
11
|
+
last.is_a?(Hash) ? pop : {}
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
# The Validations module provides validation capabilities as a mixin. When
|
16
|
+
# included into a class, it enhances the class with class and instance
|
17
|
+
# methods for defining validations and validating class instances.
|
18
|
+
#
|
19
|
+
# The Validation emulates the validation capabilities of ActiveRecord, and
|
20
|
+
# provides methods for validating acceptance, confirmation, presence, format,
|
21
|
+
# length and numericality of attributes.
|
22
|
+
#
|
23
|
+
# To use validations, you need to include the Validation module in your
|
24
|
+
# class:
|
25
|
+
#
|
26
|
+
# class MyClass
|
27
|
+
# include Validation
|
28
|
+
# validates_length_of :password, :minimum => 6
|
29
|
+
# end
|
30
|
+
module Validation
|
31
|
+
# Includes the Validation class methods into the including class.
|
32
|
+
def self.included(c)
|
33
|
+
c.extend ClassMethods
|
34
|
+
end
|
35
|
+
|
36
|
+
# Returns the validation errors associated with the object.
|
37
|
+
def errors
|
38
|
+
@errors ||= Errors.new
|
39
|
+
end
|
40
|
+
|
41
|
+
# Validates the object.
|
42
|
+
def validate
|
43
|
+
errors.clear
|
44
|
+
self.class.validate(self)
|
45
|
+
end
|
46
|
+
|
47
|
+
# Validates the object and returns true if no errors are reported.
|
48
|
+
def valid?
|
49
|
+
validate
|
50
|
+
errors.empty?
|
51
|
+
end
|
52
|
+
|
53
|
+
# Validation::Errors represents validation errors.
|
54
|
+
class Errors
|
55
|
+
# Initializes a new instance of validation errors.
|
56
|
+
def initialize
|
57
|
+
@errors = Hash.new {|h, k| h[k] = []}
|
58
|
+
end
|
59
|
+
|
60
|
+
# Returns true if no errors are stored.
|
61
|
+
def empty?
|
62
|
+
@errors.empty?
|
63
|
+
end
|
64
|
+
|
65
|
+
# Clears all errors.
|
66
|
+
def clear
|
67
|
+
@errors.clear
|
68
|
+
end
|
69
|
+
|
70
|
+
# Returns size of errors array
|
71
|
+
def size
|
72
|
+
@errors.size
|
73
|
+
end
|
74
|
+
|
75
|
+
# Iterates over errors
|
76
|
+
def each(&block)
|
77
|
+
@errors.each(&block)
|
78
|
+
end
|
79
|
+
|
80
|
+
# Returns the errors for the given attribute.
|
81
|
+
def on(att)
|
82
|
+
@errors[att]
|
83
|
+
end
|
84
|
+
alias_method :[], :on
|
85
|
+
|
86
|
+
# Adds an error for the given attribute.
|
87
|
+
def add(att, msg)
|
88
|
+
@errors[att] << msg
|
89
|
+
end
|
90
|
+
|
91
|
+
# Returns an array of fully-formatted error messages.
|
92
|
+
def full_messages
|
93
|
+
@errors.inject([]) do |m, kv| att, errors = *kv
|
94
|
+
errors.each {|e| m << "#{att} #{e}"}
|
95
|
+
m
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
# The Generator class is used to generate validation definitions using
|
101
|
+
# the validates {} idiom.
|
102
|
+
class Generator
|
103
|
+
# Initializes a new generator.
|
104
|
+
def initialize(receiver ,&block)
|
105
|
+
@receiver = receiver
|
106
|
+
instance_eval(&block)
|
107
|
+
end
|
108
|
+
|
109
|
+
# Delegates method calls to the receiver by calling receiver.validates_xxx.
|
110
|
+
def method_missing(m, *args, &block)
|
111
|
+
@receiver.send(:"validates_#{m}", *args, &block)
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
# Validation class methods.
|
116
|
+
module ClassMethods
|
117
|
+
# Defines validations by converting a longhand block into a series of
|
118
|
+
# shorthand definitions. For example:
|
119
|
+
#
|
120
|
+
# class MyClass
|
121
|
+
# include Validation
|
122
|
+
# validates do
|
123
|
+
# length_of :name, :minimum => 6
|
124
|
+
# length_of :password, :minimum => 8
|
125
|
+
# end
|
126
|
+
# end
|
127
|
+
#
|
128
|
+
# is equivalent to:
|
129
|
+
# class MyClass
|
130
|
+
# include Validation
|
131
|
+
# validates_length_of :name, :minimum => 6
|
132
|
+
# validates_length_of :password, :minimum => 8
|
133
|
+
# end
|
134
|
+
def validates(&block)
|
135
|
+
Generator.new(self, &block)
|
136
|
+
end
|
137
|
+
|
138
|
+
# Returns the validations hash for the class.
|
139
|
+
def validations
|
140
|
+
@validations ||= Hash.new {|h, k| h[k] = []}
|
141
|
+
end
|
2
142
|
|
3
|
-
|
143
|
+
# Returns true if validations are defined.
|
144
|
+
def has_validations?
|
145
|
+
!validations.empty?
|
146
|
+
end
|
147
|
+
|
148
|
+
# Validates the given instance.
|
149
|
+
def validate(o)
|
150
|
+
if superclass.respond_to?(:validate) && !@skip_superclass_validations
|
151
|
+
superclass.validate(o)
|
152
|
+
end
|
153
|
+
validations.each do |att, procs|
|
154
|
+
v = o.send(att)
|
155
|
+
procs.each {|p| p[o, att, v]}
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
def skip_superclass_validations
|
160
|
+
@skip_superclass_validations = true
|
161
|
+
end
|
162
|
+
|
163
|
+
# Adds a validation for each of the given attributes using the supplied
|
164
|
+
# block. The block must accept three arguments: instance, attribute and
|
165
|
+
# value, e.g.:
|
166
|
+
#
|
167
|
+
# validates_each :name, :password do |object, attribute, value|
|
168
|
+
# object.errors[attribute] << 'is not nice' unless value.nice?
|
169
|
+
# end
|
170
|
+
def validates_each(*atts, &block)
|
171
|
+
atts.each {|a| validations[a] << block}
|
172
|
+
end
|
173
|
+
|
174
|
+
# Validates acceptance of an attribute.
|
175
|
+
def validates_acceptance_of(*atts)
|
176
|
+
opts = {
|
177
|
+
:message => 'is not accepted',
|
178
|
+
:allow_nil => true,
|
179
|
+
:accept => '1'
|
180
|
+
}.merge!(atts.extract_options!)
|
181
|
+
|
182
|
+
validates_each(*atts) do |o, a, v|
|
183
|
+
next if (v.nil? && opts[:allow_nil]) || (v.blank? && opts[:allow_blank])
|
184
|
+
o.errors[a] << opts[:message] unless v == opts[:accept]
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
# Validates confirmation of an attribute.
|
189
|
+
def validates_confirmation_of(*atts)
|
190
|
+
opts = {
|
191
|
+
:message => 'is not confirmed',
|
192
|
+
}.merge!(atts.extract_options!)
|
193
|
+
|
194
|
+
validates_each(*atts) do |o, a, v|
|
195
|
+
next if (v.nil? && opts[:allow_nil]) || (v.blank? && opts[:allow_blank])
|
196
|
+
c = o.send(:"#{a}_confirmation")
|
197
|
+
o.errors[a] << opts[:message] unless v == c
|
198
|
+
end
|
199
|
+
end
|
200
|
+
|
201
|
+
# Validates the format of an attribute.
|
202
|
+
def validates_format_of(*atts)
|
203
|
+
opts = {
|
204
|
+
:message => 'is invalid',
|
205
|
+
}.merge!(atts.extract_options!)
|
206
|
+
|
207
|
+
unless opts[:with].is_a?(Regexp)
|
208
|
+
raise ArgumentError, "A regular expression must be supplied as the :with option of the options hash"
|
209
|
+
end
|
210
|
+
|
211
|
+
validates_each(*atts) do |o, a, v|
|
212
|
+
next if (v.nil? && opts[:allow_nil]) || (v.blank? && opts[:allow_blank])
|
213
|
+
o.errors[a] << opts[:message] unless v.to_s =~ opts[:with]
|
214
|
+
end
|
215
|
+
end
|
216
|
+
|
217
|
+
# Validates the length of an attribute.
|
218
|
+
def validates_length_of(*atts)
|
219
|
+
opts = {
|
220
|
+
:too_long => 'is too long',
|
221
|
+
:too_short => 'is too short',
|
222
|
+
:wrong_length => 'is the wrong length'
|
223
|
+
}.merge!(atts.extract_options!)
|
224
|
+
|
225
|
+
validates_each(*atts) do |o, a, v|
|
226
|
+
next if (v.nil? && opts[:allow_nil]) || (v.blank? && opts[:allow_blank])
|
227
|
+
if m = opts[:maximum]
|
228
|
+
o.errors[a] << (opts[:message] || opts[:too_long]) unless v && v.size <= m
|
229
|
+
end
|
230
|
+
if m = opts[:minimum]
|
231
|
+
o.errors[a] << (opts[:message] || opts[:too_short]) unless v && v.size >= m
|
232
|
+
end
|
233
|
+
if i = opts[:is]
|
234
|
+
o.errors[a] << (opts[:message] || opts[:wrong_length]) unless v && v.size == i
|
235
|
+
end
|
236
|
+
if w = opts[:within]
|
237
|
+
o.errors[a] << (opts[:message] || opts[:wrong_length]) unless v && w.include?(v.size)
|
238
|
+
end
|
239
|
+
end
|
240
|
+
end
|
241
|
+
|
242
|
+
NUMBER_RE = /^\d*\.{0,1}\d+$/
|
243
|
+
INTEGER_RE = /\A[+-]?\d+\Z/
|
244
|
+
|
245
|
+
# Validates whether an attribute is a number.
|
246
|
+
def validates_numericality_of(*atts)
|
247
|
+
opts = {
|
248
|
+
:message => 'is not a number',
|
249
|
+
}.merge!(atts.extract_options!)
|
250
|
+
|
251
|
+
re = opts[:only_integer] ? INTEGER_RE : NUMBER_RE
|
252
|
+
|
253
|
+
validates_each(*atts) do |o, a, v|
|
254
|
+
next if (v.nil? && opts[:allow_nil]) || (v.blank? && opts[:allow_blank])
|
255
|
+
o.errors[a] << opts[:message] unless v.to_s =~ re
|
256
|
+
end
|
257
|
+
end
|
258
|
+
|
259
|
+
# Validates the presence of an attribute.
|
260
|
+
def validates_presence_of(*atts)
|
261
|
+
opts = {
|
262
|
+
:message => 'is not present',
|
263
|
+
}.merge!(atts.extract_options!)
|
264
|
+
|
265
|
+
validates_each(*atts) do |o, a, v|
|
266
|
+
o.errors[a] << opts[:message] unless v && !v.blank?
|
267
|
+
end
|
268
|
+
end
|
269
|
+
|
270
|
+
# Validates only if the fields in the model (specified by atts) are
|
271
|
+
# unique in the database. You should also add a unique index in the
|
272
|
+
# database, as this suffers from a fairly obvious race condition.
|
273
|
+
def validates_uniqueness_of(*atts)
|
274
|
+
opts = {
|
275
|
+
:message => 'is already taken',
|
276
|
+
}.merge!(atts.extract_options!)
|
277
|
+
|
278
|
+
validates_each(*atts) do |o, a, v|
|
279
|
+
next if v.blank?
|
280
|
+
num_dups = o.class.filter(a => v).count
|
281
|
+
allow = if num_dups == 0
|
282
|
+
# No unique value in the database
|
283
|
+
true
|
284
|
+
elsif num_dups > 1
|
285
|
+
# Multiple "unique" values in the database!!
|
286
|
+
# Someone didn't add a unique index
|
287
|
+
false
|
288
|
+
elsif o.new?
|
289
|
+
# New record, but unique value already exists in the database
|
290
|
+
false
|
291
|
+
elsif o.class[a => v].pk == o.pk
|
292
|
+
# Unique value exists in database, but for the same record, so the update won't cause a duplicate record
|
293
|
+
true
|
294
|
+
else
|
295
|
+
false
|
296
|
+
end
|
297
|
+
o.errors[a] << opts[:message] unless allow
|
298
|
+
end
|
299
|
+
end
|
300
|
+
end
|
301
|
+
end
|
4
302
|
|
5
303
|
module Sequel
|
6
304
|
class Model
|