epugh-sequel 0.0.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.
Files changed (134) hide show
  1. data/README.rdoc +652 -0
  2. data/VERSION.yml +4 -0
  3. data/bin/sequel +104 -0
  4. data/lib/sequel.rb +1 -0
  5. data/lib/sequel/adapters/ado.rb +85 -0
  6. data/lib/sequel/adapters/db2.rb +132 -0
  7. data/lib/sequel/adapters/dbi.rb +101 -0
  8. data/lib/sequel/adapters/do.rb +197 -0
  9. data/lib/sequel/adapters/do/mysql.rb +38 -0
  10. data/lib/sequel/adapters/do/postgres.rb +92 -0
  11. data/lib/sequel/adapters/do/sqlite.rb +31 -0
  12. data/lib/sequel/adapters/firebird.rb +307 -0
  13. data/lib/sequel/adapters/informix.rb +75 -0
  14. data/lib/sequel/adapters/jdbc.rb +485 -0
  15. data/lib/sequel/adapters/jdbc/h2.rb +62 -0
  16. data/lib/sequel/adapters/jdbc/mysql.rb +56 -0
  17. data/lib/sequel/adapters/jdbc/oracle.rb +23 -0
  18. data/lib/sequel/adapters/jdbc/postgresql.rb +101 -0
  19. data/lib/sequel/adapters/jdbc/sqlite.rb +43 -0
  20. data/lib/sequel/adapters/mysql.rb +370 -0
  21. data/lib/sequel/adapters/odbc.rb +184 -0
  22. data/lib/sequel/adapters/openbase.rb +57 -0
  23. data/lib/sequel/adapters/oracle.rb +140 -0
  24. data/lib/sequel/adapters/postgres.rb +453 -0
  25. data/lib/sequel/adapters/shared/mssql.rb +93 -0
  26. data/lib/sequel/adapters/shared/mysql.rb +341 -0
  27. data/lib/sequel/adapters/shared/oracle.rb +62 -0
  28. data/lib/sequel/adapters/shared/postgres.rb +743 -0
  29. data/lib/sequel/adapters/shared/progress.rb +34 -0
  30. data/lib/sequel/adapters/shared/sqlite.rb +263 -0
  31. data/lib/sequel/adapters/sqlite.rb +243 -0
  32. data/lib/sequel/adapters/utils/date_format.rb +21 -0
  33. data/lib/sequel/adapters/utils/stored_procedures.rb +75 -0
  34. data/lib/sequel/adapters/utils/unsupported.rb +62 -0
  35. data/lib/sequel/connection_pool.rb +258 -0
  36. data/lib/sequel/core.rb +204 -0
  37. data/lib/sequel/core_sql.rb +185 -0
  38. data/lib/sequel/database.rb +687 -0
  39. data/lib/sequel/database/schema_generator.rb +324 -0
  40. data/lib/sequel/database/schema_methods.rb +164 -0
  41. data/lib/sequel/database/schema_sql.rb +324 -0
  42. data/lib/sequel/dataset.rb +422 -0
  43. data/lib/sequel/dataset/convenience.rb +237 -0
  44. data/lib/sequel/dataset/prepared_statements.rb +220 -0
  45. data/lib/sequel/dataset/sql.rb +1105 -0
  46. data/lib/sequel/deprecated.rb +529 -0
  47. data/lib/sequel/exceptions.rb +44 -0
  48. data/lib/sequel/extensions/blank.rb +42 -0
  49. data/lib/sequel/extensions/inflector.rb +288 -0
  50. data/lib/sequel/extensions/pagination.rb +96 -0
  51. data/lib/sequel/extensions/pretty_table.rb +78 -0
  52. data/lib/sequel/extensions/query.rb +48 -0
  53. data/lib/sequel/extensions/string_date_time.rb +47 -0
  54. data/lib/sequel/metaprogramming.rb +44 -0
  55. data/lib/sequel/migration.rb +212 -0
  56. data/lib/sequel/model.rb +142 -0
  57. data/lib/sequel/model/association_reflection.rb +263 -0
  58. data/lib/sequel/model/associations.rb +1024 -0
  59. data/lib/sequel/model/base.rb +911 -0
  60. data/lib/sequel/model/deprecated.rb +188 -0
  61. data/lib/sequel/model/deprecated_hooks.rb +103 -0
  62. data/lib/sequel/model/deprecated_inflector.rb +335 -0
  63. data/lib/sequel/model/deprecated_validations.rb +384 -0
  64. data/lib/sequel/model/errors.rb +37 -0
  65. data/lib/sequel/model/exceptions.rb +7 -0
  66. data/lib/sequel/model/inflections.rb +230 -0
  67. data/lib/sequel/model/plugins.rb +74 -0
  68. data/lib/sequel/object_graph.rb +230 -0
  69. data/lib/sequel/plugins/caching.rb +122 -0
  70. data/lib/sequel/plugins/hook_class_methods.rb +122 -0
  71. data/lib/sequel/plugins/schema.rb +53 -0
  72. data/lib/sequel/plugins/single_table_inheritance.rb +63 -0
  73. data/lib/sequel/plugins/validation_class_methods.rb +373 -0
  74. data/lib/sequel/sql.rb +854 -0
  75. data/lib/sequel/version.rb +11 -0
  76. data/lib/sequel_core.rb +1 -0
  77. data/lib/sequel_model.rb +1 -0
  78. data/spec/adapters/ado_spec.rb +46 -0
  79. data/spec/adapters/firebird_spec.rb +376 -0
  80. data/spec/adapters/informix_spec.rb +96 -0
  81. data/spec/adapters/mysql_spec.rb +875 -0
  82. data/spec/adapters/oracle_spec.rb +272 -0
  83. data/spec/adapters/postgres_spec.rb +692 -0
  84. data/spec/adapters/spec_helper.rb +10 -0
  85. data/spec/adapters/sqlite_spec.rb +550 -0
  86. data/spec/core/connection_pool_spec.rb +526 -0
  87. data/spec/core/core_ext_spec.rb +156 -0
  88. data/spec/core/core_sql_spec.rb +528 -0
  89. data/spec/core/database_spec.rb +1214 -0
  90. data/spec/core/dataset_spec.rb +3513 -0
  91. data/spec/core/expression_filters_spec.rb +363 -0
  92. data/spec/core/migration_spec.rb +261 -0
  93. data/spec/core/object_graph_spec.rb +280 -0
  94. data/spec/core/pretty_table_spec.rb +58 -0
  95. data/spec/core/schema_generator_spec.rb +167 -0
  96. data/spec/core/schema_spec.rb +778 -0
  97. data/spec/core/spec_helper.rb +82 -0
  98. data/spec/core/version_spec.rb +7 -0
  99. data/spec/extensions/blank_spec.rb +67 -0
  100. data/spec/extensions/caching_spec.rb +201 -0
  101. data/spec/extensions/hook_class_methods_spec.rb +470 -0
  102. data/spec/extensions/inflector_spec.rb +122 -0
  103. data/spec/extensions/pagination_spec.rb +99 -0
  104. data/spec/extensions/pretty_table_spec.rb +91 -0
  105. data/spec/extensions/query_spec.rb +85 -0
  106. data/spec/extensions/schema_spec.rb +111 -0
  107. data/spec/extensions/single_table_inheritance_spec.rb +53 -0
  108. data/spec/extensions/spec_helper.rb +90 -0
  109. data/spec/extensions/string_date_time_spec.rb +93 -0
  110. data/spec/extensions/validation_class_methods_spec.rb +1054 -0
  111. data/spec/integration/dataset_test.rb +160 -0
  112. data/spec/integration/eager_loader_test.rb +683 -0
  113. data/spec/integration/prepared_statement_test.rb +130 -0
  114. data/spec/integration/schema_test.rb +183 -0
  115. data/spec/integration/spec_helper.rb +75 -0
  116. data/spec/integration/type_test.rb +96 -0
  117. data/spec/model/association_reflection_spec.rb +93 -0
  118. data/spec/model/associations_spec.rb +1780 -0
  119. data/spec/model/base_spec.rb +494 -0
  120. data/spec/model/caching_spec.rb +217 -0
  121. data/spec/model/dataset_methods_spec.rb +78 -0
  122. data/spec/model/eager_loading_spec.rb +1165 -0
  123. data/spec/model/hooks_spec.rb +472 -0
  124. data/spec/model/inflector_spec.rb +126 -0
  125. data/spec/model/model_spec.rb +588 -0
  126. data/spec/model/plugins_spec.rb +142 -0
  127. data/spec/model/record_spec.rb +1243 -0
  128. data/spec/model/schema_spec.rb +92 -0
  129. data/spec/model/spec_helper.rb +124 -0
  130. data/spec/model/validations_spec.rb +1080 -0
  131. data/spec/rcov.opts +6 -0
  132. data/spec/spec.opts +0 -0
  133. data/spec/spec_config.rb.example +10 -0
  134. metadata +202 -0
@@ -0,0 +1,53 @@
1
+ module Sequel
2
+ module Plugins
3
+ module Schema
4
+ module ClassMethods
5
+ # Creates table, using the column information from set_schema.
6
+ def create_table
7
+ db.create_table(table_name, :generator=>@schema)
8
+ @db_schema = get_db_schema(true)
9
+ columns
10
+ end
11
+
12
+ # Drops the table if it exists and then runs create_table. Should probably
13
+ # not be used except in testing.
14
+ def create_table!
15
+ drop_table rescue nil
16
+ create_table
17
+ end
18
+
19
+ # Drops table.
20
+ def drop_table
21
+ db.drop_table(table_name)
22
+ end
23
+
24
+ # Returns table schema created with set_schema for direct descendant of Model.
25
+ # Does not retreive schema information from the database, see db_schema if you
26
+ # want that.
27
+ def schema
28
+ @schema || (superclass.schema unless superclass == Model)
29
+ end
30
+
31
+ # Defines a table schema (see Schema::Generator for more information).
32
+ #
33
+ # This is only needed if you want to use the create_table/create_table! methods.
34
+ # Will also set the dataset if you provide a name, as well as setting
35
+ # the primary key if you defined one in the passed block.
36
+ #
37
+ # In general, it is a better idea to use migrations for production code, as
38
+ # migrations allow changes to existing schema. set_schema is mostly useful for
39
+ # test code or simple examples.
40
+ def set_schema(name = nil, &block)
41
+ set_dataset(db[name]) if name
42
+ @schema = Sequel::Schema::Generator.new(db, &block)
43
+ set_primary_key(@schema.primary_key_name) if @schema.primary_key_name
44
+ end
45
+
46
+ # Returns true if table exists, false otherwise.
47
+ def table_exists?
48
+ db.table_exists?(table_name)
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,63 @@
1
+ module Sequel
2
+ module Plugins
3
+ # Sequel's built in Single Table Inheritance plugin makes subclasses
4
+ # of this model only load rows where the given key field matches the
5
+ # subclass's name. If the key given has a NULL value or there are
6
+ # any problems looking up the class, uses the current class.
7
+ #
8
+ # You should only use this in the parent class, not in the subclasses.
9
+ #
10
+ # You shouldn't call set_dataset in the model after applying this
11
+ # plugin, otherwise subclasses might use the wrong dataset.
12
+ #
13
+ # The filters and row_proc that sti_key sets up in subclasses may not work correctly if
14
+ # those subclasses have further subclasses. For those middle subclasses,
15
+ # you may need to call set_dataset manually with the correct filter and
16
+ # row_proc.
17
+ module SingleTableInheritance
18
+ # Set the sti_key and sti_dataset for the model, and change the
19
+ # dataset's row_proc so that the dataset yields objects of varying classes,
20
+ # where the class used has the same name as the key field.
21
+ def self.apply(model, key)
22
+ m = model.method(:constantize)
23
+ model.instance_eval do
24
+ @sti_key = key
25
+ @sti_dataset = dataset
26
+ dataset.row_proc = lambda{|r| (m.call(r[key]) rescue model).load(r)}
27
+ end
28
+ end
29
+
30
+ module ClassMethods
31
+ # The base dataset for STI, to which filters are added to get
32
+ # only the models for the specific STI subclass.
33
+ attr_reader :sti_dataset
34
+
35
+ # The column name holding the STI key for this model
36
+ attr_reader :sti_key
37
+
38
+ # Copy the sti_key and sti_dataset to the subclasses, and filter the
39
+ # subclass's dataset so it is restricted to rows where the key column
40
+ # matches the subclass's name.
41
+ def inherited(subclass)
42
+ super
43
+ sk = sti_key
44
+ sd = sti_dataset
45
+ subclass.set_dataset(sd.filter(sk=>subclass.name.to_s), :inherited=>true)
46
+ subclass.instance_eval do
47
+ @sti_key = sk
48
+ @sti_dataset = sd
49
+ @simple_table = nil
50
+ end
51
+ end
52
+ end
53
+
54
+ module InstanceMethods
55
+ # Set the sti_key column to the name of the model.
56
+ def before_create
57
+ return false if super == false
58
+ send("#{model.sti_key}=", model.name.to_s)
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,373 @@
1
+ module Sequel
2
+ require 'extensions/blank'
3
+
4
+ module Plugins
5
+ module ValidationClassMethods
6
+ module ClassMethods
7
+ # The Generator class is used to generate validation definitions using
8
+ # the validates {} idiom.
9
+ class Generator
10
+ # Initializes a new generator.
11
+ def initialize(receiver ,&block)
12
+ @receiver = receiver
13
+ instance_eval(&block)
14
+ end
15
+
16
+ # Delegates method calls to the receiver by calling receiver.validates_xxx.
17
+ def method_missing(m, *args, &block)
18
+ @receiver.send(:"validates_#{m}", *args, &block)
19
+ end
20
+ end
21
+
22
+ # Returns true if validations are defined.
23
+ def has_validations?
24
+ !validations.empty?
25
+ end
26
+
27
+ # Instructs the model to skip validations defined in superclasses
28
+ def skip_superclass_validations
29
+ @skip_superclass_validations = true
30
+ end
31
+
32
+ # Defines validations by converting a longhand block into a series of
33
+ # shorthand definitions. For example:
34
+ #
35
+ # class MyClass < Sequel::Model
36
+ # validates do
37
+ # length_of :name, :minimum => 6
38
+ # length_of :password, :minimum => 8
39
+ # end
40
+ # end
41
+ #
42
+ # is equivalent to:
43
+ # class MyClass < Sequel::Model
44
+ # validates_length_of :name, :minimum => 6
45
+ # validates_length_of :password, :minimum => 8
46
+ # end
47
+ def validates(&block)
48
+ Generator.new(self, &block)
49
+ end
50
+
51
+ # Validates the given instance.
52
+ def validate(o)
53
+ if superclass.respond_to?(:validate) && !@skip_superclass_validations
54
+ superclass.validate(o)
55
+ end
56
+ validations.each do |att, procs|
57
+ v = case att
58
+ when Array
59
+ att.collect{|a| o.send(a)}
60
+ else
61
+ o.send(att)
62
+ end
63
+ procs.each {|tag, p| p.call(o, att, v)}
64
+ end
65
+ end
66
+
67
+ # Validates acceptance of an attribute. Just checks that the value
68
+ # is equal to the :accept option. This method is unique in that
69
+ # :allow_nil is assumed to be true instead of false.
70
+ #
71
+ # Possible Options:
72
+ # * :accept - The value required for the object to be valid (default: '1')
73
+ # * :message - The message to use (default: 'is not accepted')
74
+ def validates_acceptance_of(*atts)
75
+ opts = {
76
+ :message => 'is not accepted',
77
+ :allow_nil => true,
78
+ :accept => '1',
79
+ :tag => :acceptance,
80
+ }.merge!(extract_options!(atts))
81
+ atts << opts
82
+ validates_each(*atts) do |o, a, v|
83
+ o.errors[a] << opts[:message] unless v == opts[:accept]
84
+ end
85
+ end
86
+
87
+ # Validates confirmation of an attribute. Checks that the object has
88
+ # a _confirmation value matching the current value. For example:
89
+ #
90
+ # validates_confirmation_of :blah
91
+ #
92
+ # Just makes sure that object.blah = object.blah_confirmation. Often used for passwords
93
+ # or email addresses on web forms.
94
+ #
95
+ # Possible Options:
96
+ # * :message - The message to use (default: 'is not confirmed')
97
+ def validates_confirmation_of(*atts)
98
+ opts = {
99
+ :message => 'is not confirmed',
100
+ :tag => :confirmation,
101
+ }.merge!(extract_options!(atts))
102
+ atts << opts
103
+ validates_each(*atts) do |o, a, v|
104
+ o.errors[a] << opts[:message] unless v == o.send(:"#{a}_confirmation")
105
+ end
106
+ end
107
+
108
+ # Adds a validation for each of the given attributes using the supplied
109
+ # block. The block must accept three arguments: instance, attribute and
110
+ # value, e.g.:
111
+ #
112
+ # validates_each :name, :password do |object, attribute, value|
113
+ # object.errors[attribute] << 'is not nice' unless value.nice?
114
+ # end
115
+ #
116
+ # Possible Options:
117
+ # * :allow_blank - Whether to skip the validation if the value is blank.
118
+ # * :allow_missing - Whether to skip the validation if the attribute isn't a key in the
119
+ # values hash. This is different from allow_nil, because Sequel only sends the attributes
120
+ # in the values when doing an insert or update. If the attribute is not present, Sequel
121
+ # doesn't specify it, so the database will use the table's default value. This is different
122
+ # from having an attribute in values with a value of nil, which Sequel will send as NULL.
123
+ # If your database table has a non NULL default, this may be a good option to use. You
124
+ # don't want to use allow_nil, because if the attribute is in values but has a value nil,
125
+ # Sequel will attempt to insert a NULL value into the database, instead of using the
126
+ # database's default.
127
+ # * :allow_nil - Whether to skip the validation if the value is nil.
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
+ # * :tag - The tag to use for this validation.
131
+ def validates_each(*atts, &block)
132
+ opts = extract_options!(atts)
133
+ blk = if (i = opts[:if]) || (am = opts[:allow_missing]) || (an = opts[:allow_nil]) || (ab = opts[:allow_blank])
134
+ proc do |o,a,v|
135
+ next if i && !validation_if_proc(o, i)
136
+ next if an && Array(v).all?{|x| x.nil?}
137
+ next if ab && Array(v).all?{|x| x.blank?}
138
+ next if am && Array(a).all?{|x| !o.values.has_key?(x)}
139
+ block.call(o,a,v)
140
+ end
141
+ else
142
+ block
143
+ end
144
+ tag = opts[:tag]
145
+ atts.each do |a|
146
+ a_vals = validations[a]
147
+ if tag && (old = a_vals.find{|x| x[0] == tag})
148
+ old[1] = blk
149
+ else
150
+ a_vals << [tag, blk]
151
+ end
152
+ end
153
+ end
154
+
155
+ # Validates the format of an attribute, checking the string representation of the
156
+ # value against the regular expression provided by the :with option.
157
+ #
158
+ # Possible Options:
159
+ # * :message - The message to use (default: 'is invalid')
160
+ # * :with - The regular expression to validate the value with (required).
161
+ def validates_format_of(*atts)
162
+ opts = {
163
+ :message => 'is invalid',
164
+ :tag => :format,
165
+ }.merge!(extract_options!(atts))
166
+
167
+ unless opts[:with].is_a?(Regexp)
168
+ raise ArgumentError, "A regular expression must be supplied as the :with option of the options hash"
169
+ end
170
+
171
+ atts << opts
172
+ validates_each(*atts) do |o, a, v|
173
+ o.errors[a] << opts[:message] unless v.to_s =~ opts[:with]
174
+ end
175
+ end
176
+
177
+ # Validates the length of an attribute.
178
+ #
179
+ # Possible Options:
180
+ # * :is - The exact size required for the value to be valid (no default)
181
+ # * :maximum - The maximum size allowed for the value (no default)
182
+ # * :message - The message to use (no default, overrides :too_long, :too_short, and :wrong_length
183
+ # options if present)
184
+ # * :minimum - The minimum size allowed for the value (no default)
185
+ # * :too_long - The message to use use if it the value is too long (default: 'is too long')
186
+ # * :too_short - The message to use use if it the value is too short (default: 'is too short')
187
+ # * :within - The array/range that must include the size of the value for it to be valid (no default)
188
+ # * :wrong_length - The message to use use if it the value is not valid (default: 'is the wrong length')
189
+ def validates_length_of(*atts)
190
+ opts = {
191
+ :too_long => 'is too long',
192
+ :too_short => 'is too short',
193
+ :wrong_length => 'is the wrong length'
194
+ }.merge!(extract_options!(atts))
195
+
196
+ opts[:tag] ||= ([:length] + [:maximum, :minimum, :is, :within].reject{|x| !opts.include?(x)}).join('-').to_sym
197
+ atts << opts
198
+ validates_each(*atts) do |o, a, v|
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
+ # Validates whether an attribute is not a string. This is generally useful
215
+ # in conjunction with raise_on_typecast_failure = false, where you are
216
+ # passing in string values for non-string attributes (such as numbers and dates).
217
+ # If typecasting fails (invalid number or date), the value of the attribute will
218
+ # be a string in an invalid format, and if typecasting succeeds, the value will
219
+ # not be a string.
220
+ #
221
+ # Possible Options:
222
+ # * :message - The message to use (default: 'is a string' or 'is not a valid (integer|datetime|etc.)' if the type is known)
223
+ def validates_not_string(*atts)
224
+ opts = {
225
+ :tag => :not_string,
226
+ }.merge!(extract_options!(atts))
227
+ atts << opts
228
+ validates_each(*atts) do |o, a, v|
229
+ if v.is_a?(String)
230
+ unless message = opts[:message]
231
+ message = if sch = o.db_schema[a] and typ = sch[:type]
232
+ "is not a valid #{typ}"
233
+ else
234
+ "is a string"
235
+ end
236
+ end
237
+ o.errors[a] << message
238
+ end
239
+ end
240
+ end
241
+
242
+ # Validates whether an attribute is a number.
243
+ #
244
+ # Possible Options:
245
+ # * :message - The message to use (default: 'is not a number')
246
+ # * :only_integer - Whether only integers are valid values (default: false)
247
+ def validates_numericality_of(*atts)
248
+ opts = {
249
+ :message => 'is not a number',
250
+ :tag => :numericality,
251
+ }.merge!(extract_options!(atts))
252
+ atts << opts
253
+ validates_each(*atts) do |o, a, v|
254
+ begin
255
+ if opts[:only_integer]
256
+ Kernel.Integer(v.to_s)
257
+ else
258
+ Kernel.Float(v.to_s)
259
+ end
260
+ rescue
261
+ o.errors[a] << opts[:message]
262
+ end
263
+ end
264
+ end
265
+
266
+ # Validates the presence of an attribute. Requires the value not be blank,
267
+ # with false considered present instead of absent.
268
+ #
269
+ # Possible Options:
270
+ # * :message - The message to use (default: 'is not present')
271
+ def validates_presence_of(*atts)
272
+ opts = {
273
+ :message => 'is not present',
274
+ :tag => :presence,
275
+ }.merge!(extract_options!(atts))
276
+ atts << opts
277
+ validates_each(*atts) do |o, a, v|
278
+ o.errors[a] << opts[:message] if v.blank? && v != false
279
+ end
280
+ end
281
+
282
+ # Validates that an attribute is within a specified range or set of values.
283
+ #
284
+ # Possible Options:
285
+ # * :in - An array or range of values to check for validity (required)
286
+ # * :message - The message to use (default: 'is not in range or set: <specified range>')
287
+ def validates_inclusion_of(*atts)
288
+ opts = extract_options!(atts)
289
+ unless opts[:in] && opts[:in].respond_to?(:include?)
290
+ raise ArgumentError, "The :in parameter is required, and respond to include?"
291
+ end
292
+ opts[:message] ||= "is not in range or set: #{opts[:in].inspect}"
293
+ atts << opts
294
+ validates_each(*atts) do |o, a, v|
295
+ o.errors[a] << opts[:message] unless opts[:in].include?(v)
296
+ end
297
+ end
298
+
299
+ # Validates only if the fields in the model (specified by atts) are
300
+ # unique in the database. Pass an array of fields instead of multiple
301
+ # fields to specify that the combination of fields must be unique,
302
+ # instead of that each field should have a unique value.
303
+ #
304
+ # This means that the code:
305
+ # validates_uniqueness_of([:column1, :column2])
306
+ # validates the grouping of column1 and column2 while
307
+ # validates_uniqueness_of(:column1, :column2)
308
+ # validates them separately.
309
+ #
310
+ # You should also add a unique index in the
311
+ # database, as this suffers from a fairly obvious race condition.
312
+ #
313
+ # Possible Options:
314
+ # * :message - The message to use (default: 'is already taken')
315
+ def validates_uniqueness_of(*atts)
316
+ opts = {
317
+ :message => 'is already taken',
318
+ :tag => :uniqueness,
319
+ }.merge!(extract_options!(atts))
320
+
321
+ atts << opts
322
+ validates_each(*atts) do |o, a, v|
323
+ error_field = a
324
+ a = Array(a)
325
+ v = Array(v)
326
+ ds = o.class.filter(a.zip(v))
327
+ num_dups = ds.count
328
+ allow = if num_dups == 0
329
+ # No unique value in the database
330
+ true
331
+ elsif num_dups > 1
332
+ # Multiple "unique" values in the database!!
333
+ # Someone didn't add a unique index
334
+ false
335
+ elsif o.new?
336
+ # New record, but unique value already exists in the database
337
+ false
338
+ elsif ds.first === o
339
+ # Unique value exists in database, but for the same record, so the update won't cause a duplicate record
340
+ true
341
+ else
342
+ false
343
+ end
344
+ o.errors[error_field] << opts[:message] unless allow
345
+ end
346
+ end
347
+
348
+ # Returns the validations hash for the class.
349
+ def validations
350
+ @validations ||= Hash.new {|h, k| h[k] = []}
351
+ end
352
+
353
+ private
354
+
355
+ def validation_if_proc(o, i)
356
+ case i
357
+ when Symbol then o.send(i)
358
+ when Proc then o.instance_eval(&i)
359
+ when nil then true
360
+ else raise(::Sequel::Error, "invalid value for :if validation option")
361
+ end
362
+ end
363
+ end
364
+
365
+ module InstanceMethods
366
+ # Validates the object.
367
+ def validate
368
+ model.validate(self)
369
+ end
370
+ end
371
+ end
372
+ end
373
+ end