sequel 1.4.0 → 1.5.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.
@@ -28,20 +28,21 @@ module Sequel
28
28
  end
29
29
  alias_method :is_a, :is
30
30
 
31
- # Returns the module for the specified plugin. If the module is not
32
- # defined, the corresponding plugin gem is automatically loaded.
33
- def plugin_module(plugin)
34
- module_name = plugin.to_s.gsub(/(^|_)(.)/) {$2.upcase}
35
- if not Sequel::Plugins.const_defined?(module_name)
36
- require plugin_gem(plugin)
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
@@ -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
- # Compares model instances by pkey.
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 ensuring that
135
- # new? returns true.
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
- db.transaction do
138
- obj = new(values, &block)
139
- obj.save
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
- def update_with_params(values)
147
- c = columns
148
- values.each do |k, v| m = :"#{k}="
149
- send(m, v) if c.include?(k) || respond_to?(m)
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
- class << self
156
- def create_with_params(params)
157
- record = new
158
- record.update_with_params(params)
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
- unless from_db
199
- @values = {}
200
- if values
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 = 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
- block[self] if block
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
- # Updates and saves values to database from the passed-in Hash.
278
- def set(values)
279
- v = values.inject({}) {|m, kv| m[kv[0].to_sym] = kv[1]; m}
280
- this.update(v)
281
- v.each {|k, v| @values[k] = v}
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
- ATTR_RE = /^([a-zA-Z_]\w*)(=)?$/.freeze
308
- EQUAL_SIGN = '='.freeze
309
-
310
- def method_missing(m, *args) #:nodoc:
311
- if m.to_s =~ ATTR_RE
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
@@ -32,6 +32,8 @@ module Sequel
32
32
  # Creates table.
33
33
  def self.create_table
34
34
  db.create_table_sql_list(table_name, *schema.create_info).each {|s| db << s}
35
+ @columns = nil
36
+ columns
35
37
  end
36
38
 
37
39
  # Drops table.
@@ -1,6 +1,304 @@
1
- gem "assistance", ">= 0.1.2" # because we need Validations
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
- require "assistance"
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