sequel 1.4.0 → 1.5.0

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