activerecord 1.7.0 → 1.8.0
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of activerecord might be problematic. Click here for more details.
- data/CHANGELOG +55 -0
- data/lib/active_record.rb +5 -7
- data/lib/active_record/aggregations.rb +2 -1
- data/lib/active_record/associations.rb +2 -1
- data/lib/active_record/associations/association_proxy.rb +4 -0
- data/lib/active_record/associations/has_many_association.rb +6 -4
- data/lib/active_record/associations/has_one_association.rb +7 -2
- data/lib/active_record/base.rb +36 -5
- data/lib/active_record/connection_adapters/abstract_adapter.rb +58 -10
- data/lib/active_record/connection_adapters/mysql_adapter.rb +30 -9
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +3 -2
- data/lib/active_record/connection_adapters/sqlite_adapter.rb +17 -0
- data/lib/active_record/fixtures.rb +108 -31
- data/lib/active_record/migration.rb +94 -0
- data/lib/active_record/reflection.rb +10 -4
- data/lib/active_record/validations.rb +76 -42
- data/rakefile +2 -2
- data/test/aaa_create_tables_test.rb +4 -4
- data/test/aggregations_test.rb +18 -4
- data/test/associations_test.rb +2 -1
- data/test/base_test.rb +10 -0
- data/test/fixtures/company.rb +1 -1
- data/test/fixtures/customer.rb +17 -0
- data/test/fixtures/customers.yml +1 -0
- data/test/fixtures/db_definitions/db2.sql +1 -0
- data/test/fixtures/db_definitions/mysql.sql +1 -0
- data/test/fixtures/db_definitions/oci.sql +1 -0
- data/test/fixtures/db_definitions/postgresql.sql +1 -0
- data/test/fixtures/db_definitions/sqlite.sql +2 -1
- data/test/fixtures/db_definitions/sqlserver.sql +1 -0
- data/test/fixtures/fixture_database.sqlite +0 -0
- data/test/fixtures/fixture_database_2.sqlite +0 -0
- data/test/fixtures/migrations/1_people_have_last_names.rb +9 -0
- data/test/fixtures/migrations/2_we_need_reminders.rb +12 -0
- data/test/fixtures_test.rb +30 -1
- data/test/migration_mysql.rb +104 -0
- data/test/reflection_test.rb +8 -4
- data/test/validations_test.rb +38 -0
- metadata +9 -4
@@ -1,8 +1,6 @@
|
|
1
1
|
require 'erb'
|
2
2
|
require 'yaml'
|
3
3
|
require 'csv'
|
4
|
-
require 'active_support/class_inheritable_attributes'
|
5
|
-
require 'active_support/inflector'
|
6
4
|
|
7
5
|
# Fixtures are a way of organizing data that you want to test against; in short, sample data. They come in 3 flavours:
|
8
6
|
#
|
@@ -140,16 +138,46 @@ require 'active_support/inflector'
|
|
140
138
|
# This is however a feature to be used with some caution. The point of fixtures are that they're stable units of predictable
|
141
139
|
# sample data. If you feel that you need to inject dynamic values, then perhaps you should reexamine whether your application
|
142
140
|
# is properly testable. Hence, dynamic values in fixtures are to be considered a code smell.
|
141
|
+
#
|
142
|
+
# = Transactional fixtures
|
143
|
+
#
|
144
|
+
# TestCases can use begin+rollback to isolate their changes to the database instead of having to delete+insert for every test case.
|
145
|
+
# They can also turn off auto-instantiation of fixture data since the feature is costly and often unused.
|
146
|
+
#
|
147
|
+
# class FooTest < Test::Unit::TestCase
|
148
|
+
# self.use_transactional_fixtures = true
|
149
|
+
# self.use_instantiated_fixtures = false
|
150
|
+
#
|
151
|
+
# fixtures :foos
|
152
|
+
#
|
153
|
+
# def test_godzilla
|
154
|
+
# assert !Foo.find_all.emtpy?
|
155
|
+
# Foo.destroy_all
|
156
|
+
# assert Foo.find_all.emtpy?
|
157
|
+
# end
|
158
|
+
#
|
159
|
+
# def test_godzilla_aftermath
|
160
|
+
# assert !Foo.find_all.emtpy?
|
161
|
+
# end
|
162
|
+
# end
|
163
|
+
#
|
164
|
+
# If you preload your test database with all fixture data (probably in the Rakefile task) and use transactional fixtures,
|
165
|
+
# then you may omit all fixtures declarations in your test cases since all the data's already there and every case rolls back its changes.
|
166
|
+
#
|
167
|
+
# When *not* to use transactional fixtures:
|
168
|
+
# 1. You're testing whether a transaction works correctly. Nested transactions don't commit until all parent transactions commit,
|
169
|
+
# particularly, the fixtures transaction which is begun in setup and rolled back in teardown. Thus, you won't be able to verify
|
170
|
+
# the results of your transaction until Active Record supports nested transactions or savepoints (in progress.)
|
171
|
+
# 2. Your database does not support transactions. Every Active Record database supports transactions except MySQL MyISAM.
|
172
|
+
# Use InnoDB, MaxDB, or NDB instead.
|
143
173
|
class Fixtures < Hash
|
144
174
|
DEFAULT_FILTER_RE = /\.ya?ml$/
|
145
175
|
|
146
|
-
def self.instantiate_fixtures(object,
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
object.instance_variable_set "@#{name}", model
|
152
|
-
end
|
176
|
+
def self.instantiate_fixtures(object, table_name, fixtures)
|
177
|
+
object.instance_variable_set "@#{table_name}", fixtures
|
178
|
+
fixtures.each do |name, fixture|
|
179
|
+
if model = fixture.find
|
180
|
+
object.instance_variable_set "@#{name}", model
|
153
181
|
end
|
154
182
|
end
|
155
183
|
end
|
@@ -219,7 +247,7 @@ class Fixtures < Hash
|
|
219
247
|
yaml = YAML::load(erb_render(IO.read(yaml_file_path)))
|
220
248
|
yaml.each { |name, data| self[name] = Fixture.new(data, @class_name) } if yaml
|
221
249
|
rescue Exception=>boom
|
222
|
-
raise Fixture::FormatError, "a YAML error occured parsing #{yaml_file_path}"
|
250
|
+
raise Fixture::FormatError, "a YAML error occured parsing #{yaml_file_path}. Please note that YAML must be consistently indented using spaces. Tabs are not allowed. Please have a look at http://www.yaml.org/faq.html"
|
223
251
|
end
|
224
252
|
elsif File.file?(csv_file_path)
|
225
253
|
# CSV fixtures
|
@@ -324,51 +352,100 @@ class Fixture #:nodoc:
|
|
324
352
|
end
|
325
353
|
end
|
326
354
|
|
327
|
-
module Test#:nodoc:
|
328
|
-
module Unit#:nodoc:
|
355
|
+
module Test #:nodoc:
|
356
|
+
module Unit #:nodoc:
|
329
357
|
class TestCase #:nodoc:
|
330
358
|
include ClassInheritableAttributes
|
331
359
|
|
332
360
|
cattr_accessor :fixture_path
|
333
|
-
|
361
|
+
class_inheritable_accessor :fixture_table_names
|
362
|
+
class_inheritable_accessor :use_transactional_fixtures
|
363
|
+
class_inheritable_accessor :use_instantiated_fixtures
|
364
|
+
|
365
|
+
self.fixture_table_names = []
|
366
|
+
self.use_transactional_fixtures = false
|
367
|
+
self.use_instantiated_fixtures = true
|
334
368
|
|
335
369
|
def self.fixtures(*table_names)
|
336
|
-
|
337
|
-
|
370
|
+
self.fixture_table_names = table_names.flatten
|
371
|
+
require_fixture_classes
|
338
372
|
end
|
339
373
|
|
340
|
-
def self.require_fixture_classes
|
341
|
-
|
374
|
+
def self.require_fixture_classes
|
375
|
+
fixture_table_names.each do |table_name|
|
342
376
|
begin
|
343
|
-
require
|
377
|
+
require Inflector.singularize(table_name.to_s)
|
344
378
|
rescue LoadError
|
345
|
-
# Let's hope the developer
|
379
|
+
# Let's hope the developer has included it himself
|
346
380
|
end
|
347
381
|
end
|
348
382
|
end
|
349
383
|
|
350
|
-
def
|
351
|
-
|
384
|
+
def setup_with_fixtures
|
385
|
+
# Load fixtures once and begin transaction.
|
386
|
+
if use_transactional_fixtures
|
387
|
+
load_fixtures unless @already_loaded_fixtures
|
388
|
+
@already_loaded_fixtures = true
|
389
|
+
ActiveRecord::Base.lock_mutex
|
390
|
+
ActiveRecord::Base.connection.begin_db_transaction
|
391
|
+
|
392
|
+
# Load fixtures for every test.
|
393
|
+
else
|
394
|
+
load_fixtures
|
395
|
+
end
|
396
|
+
|
397
|
+
# Instantiate fixtures for every test if requested.
|
398
|
+
instantiate_fixtures if use_instantiated_fixtures
|
399
|
+
end
|
400
|
+
|
401
|
+
alias_method :setup, :setup_with_fixtures
|
402
|
+
|
403
|
+
def teardown_with_fixtures
|
404
|
+
# Rollback changes.
|
405
|
+
if use_transactional_fixtures
|
406
|
+
ActiveRecord::Base.connection.rollback_db_transaction
|
407
|
+
ActiveRecord::Base.unlock_mutex
|
408
|
+
end
|
352
409
|
end
|
353
410
|
|
354
|
-
|
355
|
-
|
356
|
-
|
357
|
-
|
358
|
-
|
359
|
-
|
411
|
+
alias_method :teardown, :teardown_with_fixtures
|
412
|
+
|
413
|
+
def self.method_added(method)
|
414
|
+
case method.to_s
|
415
|
+
when 'setup'
|
416
|
+
unless method_defined?(:setup_without_fixtures)
|
417
|
+
alias_method :setup_without_fixtures, :setup
|
418
|
+
define_method(:setup) do
|
419
|
+
setup_with_fixtures
|
420
|
+
setup_without_fixtures
|
421
|
+
end
|
422
|
+
end
|
423
|
+
when 'teardown'
|
424
|
+
unless method_defined?(:teardown_without_fixtures)
|
425
|
+
alias_method :teardown_without_fixtures, :teardown
|
426
|
+
define_method(:teardown) do
|
427
|
+
teardown_without_fixtures
|
428
|
+
teardown_with_fixtures
|
429
|
+
end
|
360
430
|
end
|
361
431
|
end
|
362
432
|
end
|
363
433
|
|
364
434
|
private
|
365
|
-
def
|
366
|
-
|
435
|
+
def load_fixtures
|
436
|
+
@loaded_fixtures = {}
|
437
|
+
fixture_table_names.each do |table_name|
|
438
|
+
@loaded_fixtures[table_name] = Fixtures.create_fixtures(fixture_path, table_name)
|
439
|
+
end
|
367
440
|
end
|
368
441
|
|
369
|
-
def
|
370
|
-
|
442
|
+
def instantiate_fixtures
|
443
|
+
raise RuntimeError, 'Load fixtures before instantiating them.' if @loaded_fixtures.nil?
|
444
|
+
@loaded_fixtures.each do |table_name, fixtures|
|
445
|
+
Fixtures.instantiate_fixtures(self, table_name, fixtures)
|
446
|
+
end
|
371
447
|
end
|
372
448
|
end
|
449
|
+
|
373
450
|
end
|
374
451
|
end
|
@@ -0,0 +1,94 @@
|
|
1
|
+
module ActiveRecord
|
2
|
+
class IrreversibleMigration < ActiveRecordError
|
3
|
+
end
|
4
|
+
|
5
|
+
class Migration
|
6
|
+
class << self
|
7
|
+
def up() end
|
8
|
+
def down() end
|
9
|
+
|
10
|
+
private
|
11
|
+
def method_missing(method, *arguments, &block)
|
12
|
+
ActiveRecord::Base.connection.send(method, *arguments, &block)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
class Migrator
|
18
|
+
class << self
|
19
|
+
def up(migrations_path, target_version = nil)
|
20
|
+
new(:up, migrations_path, target_version).migrate
|
21
|
+
end
|
22
|
+
|
23
|
+
def down(migrations_path, target_version = nil)
|
24
|
+
new(:down, migrations_path, target_version).migrate
|
25
|
+
end
|
26
|
+
|
27
|
+
def current_version
|
28
|
+
Base.connection.select_one("SELECT version FROM schema_info")["version"].to_i
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def initialize(direction, migrations_path, target_version = nil)
|
33
|
+
@direction, @migrations_path, @target_version = direction, migrations_path, target_version
|
34
|
+
Base.connection.initialize_schema_information
|
35
|
+
end
|
36
|
+
|
37
|
+
def current_version
|
38
|
+
self.class.current_version
|
39
|
+
end
|
40
|
+
|
41
|
+
def migrate
|
42
|
+
migration_classes do |version, migration_class|
|
43
|
+
Base.logger.info("Reached target version: #{@target_version}") and break if reached_target_version?(version)
|
44
|
+
next if irrelevant_migration?(version)
|
45
|
+
|
46
|
+
Base.logger.info "Migrating to #{migration_class} (#{version})"
|
47
|
+
migration_class.send(@direction)
|
48
|
+
set_schema_version(version)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
private
|
53
|
+
def migration_classes
|
54
|
+
for migration_file in migration_files
|
55
|
+
load(migration_file)
|
56
|
+
version, name = migration_version_and_name(migration_file)
|
57
|
+
yield version, migration_class(name)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def migration_files
|
62
|
+
files = Dir["#{@migrations_path}/[0-9]*_*.rb"]
|
63
|
+
down? ? files.reverse : files
|
64
|
+
end
|
65
|
+
|
66
|
+
def migration_class(migration_name)
|
67
|
+
migration_name.camelize.constantize
|
68
|
+
end
|
69
|
+
|
70
|
+
def migration_version_and_name(migration_file)
|
71
|
+
return *migration_file.scan(/([0-9]+)_([_a-z0-9]*).rb/).first
|
72
|
+
end
|
73
|
+
|
74
|
+
def set_schema_version(version)
|
75
|
+
Base.connection.update("UPDATE schema_info SET version = #{down? ? version.to_i - 1 : version.to_i}")
|
76
|
+
end
|
77
|
+
|
78
|
+
def up?
|
79
|
+
@direction == :up
|
80
|
+
end
|
81
|
+
|
82
|
+
def down?
|
83
|
+
@direction == :down
|
84
|
+
end
|
85
|
+
|
86
|
+
def reached_target_version?(version)
|
87
|
+
(up? && version.to_i - 1 == @target_version) || (down? && version.to_i == @target_version)
|
88
|
+
end
|
89
|
+
|
90
|
+
def irrelevant_migration?(version)
|
91
|
+
(up? && version.to_i <= current_version) || (down? && version.to_i > current_version)
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
@@ -10,7 +10,7 @@ module ActiveRecord
|
|
10
10
|
|
11
11
|
def composed_of_with_reflection(part_id, options = {})
|
12
12
|
composed_of_without_reflection(part_id, options)
|
13
|
-
write_inheritable_array "aggregations", [ AggregateReflection.new(part_id, options, self) ]
|
13
|
+
write_inheritable_array "aggregations", [ AggregateReflection.new(:composed_of, part_id, options, self) ]
|
14
14
|
end
|
15
15
|
|
16
16
|
alias_method :composed_of, :composed_of_with_reflection
|
@@ -24,7 +24,7 @@ module ActiveRecord
|
|
24
24
|
|
25
25
|
def #{association_type}_with_reflection(association_id, options = {})
|
26
26
|
#{association_type}_without_reflection(association_id, options)
|
27
|
-
write_inheritable_array "associations", [ AssociationReflection.new(association_id, options, self) ]
|
27
|
+
write_inheritable_array "associations", [ AssociationReflection.new(:#{association_type}, association_id, options, self) ]
|
28
28
|
end
|
29
29
|
|
30
30
|
alias_method :#{association_type}, :#{association_type}_with_reflection
|
@@ -67,8 +67,8 @@ module ActiveRecord
|
|
67
67
|
# those classes. Objects of AggregateReflection and AssociationReflection are returned by the Reflection::ClassMethods.
|
68
68
|
class MacroReflection
|
69
69
|
attr_reader :active_record
|
70
|
-
def initialize(name, options, active_record)
|
71
|
-
@name, @options, @active_record = name, options, active_record
|
70
|
+
def initialize(macro, name, options, active_record)
|
71
|
+
@macro, @name, @options, @active_record = macro, name, options, active_record
|
72
72
|
end
|
73
73
|
|
74
74
|
# Returns the name of the macro, so it would return :balance for "composed_of :balance, :class_name => 'Money'" or
|
@@ -77,6 +77,12 @@ module ActiveRecord
|
|
77
77
|
@name
|
78
78
|
end
|
79
79
|
|
80
|
+
# Returns the name of the macro, so it would return :composed_of for
|
81
|
+
# "composed_of :balance, :class_name => 'Money'" or :has_many for "has_many :clients".
|
82
|
+
def macro
|
83
|
+
@macro
|
84
|
+
end
|
85
|
+
|
80
86
|
# Returns the hash of options used for the macro, so it would return { :class_name => "Money" } for
|
81
87
|
# "composed_of :balance, :class_name => 'Money'" or {} for "has_many :clients".
|
82
88
|
def options
|
@@ -16,6 +16,7 @@ module ActiveRecord
|
|
16
16
|
:too_short => "is too short (min is %d characters)",
|
17
17
|
:wrong_length => "is the wrong length (should be %d characters)",
|
18
18
|
:taken => "has already been taken",
|
19
|
+
:not_a_number => "is not a number",
|
19
20
|
}
|
20
21
|
|
21
22
|
# Holds a hash with all the default error messages, such that they can be replaced by your own copy or localizations.
|
@@ -193,6 +194,20 @@ module ActiveRecord
|
|
193
194
|
# They offer a more declarative way of specifying when the model is valid and when it is not. It is recommended to use
|
194
195
|
# these over the low-level calls to validate and validate_on_create when possible.
|
195
196
|
module ClassMethods
|
197
|
+
DEFAULT_VALIDATION_OPTIONS = {
|
198
|
+
:on => :save,
|
199
|
+
:allow_nil => false,
|
200
|
+
:message => nil
|
201
|
+
}.freeze
|
202
|
+
|
203
|
+
DEFAULT_SIZE_VALIDATION_OPTIONS = DEFAULT_VALIDATION_OPTIONS.merge(
|
204
|
+
:too_long => ActiveRecord::Errors.default_error_messages[:too_long],
|
205
|
+
:too_short => ActiveRecord::Errors.default_error_messages[:too_short],
|
206
|
+
:wrong_length => ActiveRecord::Errors.default_error_messages[:wrong_length]
|
207
|
+
).freeze
|
208
|
+
|
209
|
+
ALL_RANGE_OPTIONS = [ :is, :within, :in, :minimum, :maximum ].freeze
|
210
|
+
|
196
211
|
def validate(*methods, &block)
|
197
212
|
methods << block if block_given?
|
198
213
|
write_inheritable_set(:validate, methods)
|
@@ -208,6 +223,31 @@ module ActiveRecord
|
|
208
223
|
write_inheritable_set(:validate_on_update, methods)
|
209
224
|
end
|
210
225
|
|
226
|
+
# Validates each attribute against a block.
|
227
|
+
#
|
228
|
+
# class Person < ActiveRecord::Base
|
229
|
+
# validates_each :first_name, :last_name do |record, attr|
|
230
|
+
# record.errors.add attr, 'starts with z.' if attr[0] == ?z
|
231
|
+
# end
|
232
|
+
# end
|
233
|
+
#
|
234
|
+
# Options:
|
235
|
+
# * <tt>on</tt> - Specifies when this validation is active (default is :save, other options :create, :update)
|
236
|
+
# * <tt>allow_nil</tt> - Skip validation if attribute is nil.
|
237
|
+
def validates_each(*attrs)
|
238
|
+
options = attrs.last.is_a?(Hash) ? attrs.pop.symbolize_keys : {}
|
239
|
+
attrs = attrs.flatten
|
240
|
+
|
241
|
+
# Declare the validation.
|
242
|
+
send(validation_method(options[:on] || :save)) do |record|
|
243
|
+
attrs.each do |attr|
|
244
|
+
value = record.send(attr)
|
245
|
+
next if value.nil? && options[:allow_nil]
|
246
|
+
yield record, attr, value
|
247
|
+
end
|
248
|
+
end
|
249
|
+
end
|
250
|
+
|
211
251
|
# Encapsulates the pattern of wanting to validate a password or email address field with a confirmation. Example:
|
212
252
|
#
|
213
253
|
# Model:
|
@@ -279,48 +319,6 @@ module ActiveRecord
|
|
279
319
|
end
|
280
320
|
end
|
281
321
|
|
282
|
-
|
283
|
-
DEFAULT_VALIDATION_OPTIONS = {
|
284
|
-
:on => :save,
|
285
|
-
:allow_nil => false,
|
286
|
-
:message => nil
|
287
|
-
}.freeze
|
288
|
-
|
289
|
-
DEFAULT_SIZE_VALIDATION_OPTIONS = DEFAULT_VALIDATION_OPTIONS.merge(
|
290
|
-
:too_long => ActiveRecord::Errors.default_error_messages[:too_long],
|
291
|
-
:too_short => ActiveRecord::Errors.default_error_messages[:too_short],
|
292
|
-
:wrong_length => ActiveRecord::Errors.default_error_messages[:wrong_length]
|
293
|
-
).freeze
|
294
|
-
|
295
|
-
ALL_RANGE_OPTIONS = [ :is, :within, :in, :minimum, :maximum ].freeze
|
296
|
-
|
297
|
-
|
298
|
-
# Validates each attribute against a block.
|
299
|
-
#
|
300
|
-
# class Person < ActiveRecord::Base
|
301
|
-
# validates_each :first_name, :last_name do |record, attr|
|
302
|
-
# record.errors.add attr, 'starts with z.' if attr[0] == ?z
|
303
|
-
# end
|
304
|
-
# end
|
305
|
-
#
|
306
|
-
# Options:
|
307
|
-
# * <tt>on</tt> - Specifies when this validation is active (default is :save, other options :create, :update)
|
308
|
-
# * <tt>allow_nil</tt> - Skip validation if attribute is nil.
|
309
|
-
def validates_each(*attrs)
|
310
|
-
options = attrs.last.is_a?(Hash) ? attrs.pop.symbolize_keys : {}
|
311
|
-
attrs = attrs.flatten
|
312
|
-
|
313
|
-
# Declare the validation.
|
314
|
-
send(validation_method(options[:on] || :save)) do |record|
|
315
|
-
attrs.each do |attr|
|
316
|
-
value = record.send(attr)
|
317
|
-
next if value.nil? && options[:allow_nil]
|
318
|
-
yield record, attr, value
|
319
|
-
end
|
320
|
-
end
|
321
|
-
end
|
322
|
-
|
323
|
-
|
324
322
|
# Validates that the specified attribute matches the length restrictions supplied. Only one option can be used at a time:
|
325
323
|
#
|
326
324
|
# class Person < ActiveRecord::Base
|
@@ -516,6 +514,42 @@ module ActiveRecord
|
|
516
514
|
end
|
517
515
|
end
|
518
516
|
|
517
|
+
# Validates whether the value of the specified attribute is numeric by trying to convert it to
|
518
|
+
# a float with Kernel.Float (if <tt>integer</tt> is false) or applying it to the regular expression
|
519
|
+
# <tt>/^[\+\-]?\d+$/</tt> (if <tt>integer</tt> is set to true).
|
520
|
+
#
|
521
|
+
# class Person < ActiveRecord::Base
|
522
|
+
# validates_numericality_of :value, :on => :create
|
523
|
+
# end
|
524
|
+
#
|
525
|
+
# Configuration options:
|
526
|
+
# * <tt>message</tt> - A custom error message (default is: "is not a number")
|
527
|
+
# * <tt>on</tt> Specifies when this validation is active (default is :save, other options :create, :update)
|
528
|
+
# * <tt>only_integer</tt> Specifies whether the value has to be an integer, e.g. an integral value (default is false)
|
529
|
+
def validates_numericality_of(*attr_names)
|
530
|
+
configuration = { :message => ActiveRecord::Errors.default_error_messages[:not_a_number], :on => :save,
|
531
|
+
:integer => false }
|
532
|
+
configuration.update(attr_names.pop) if attr_names.last.is_a?(Hash)
|
533
|
+
|
534
|
+
for attr_name in attr_names
|
535
|
+
if configuration[:only_integer]
|
536
|
+
# we have to use a regexp here, because Kernel.Integer accepts nil and "0xdeadbeef", but does not
|
537
|
+
# accept "099" and String#to_i accepts everything. The string containing the regexp is evaluated twice
|
538
|
+
# so we have to escape everything properly
|
539
|
+
class_eval(%(#{validation_method(configuration[:on])} %{
|
540
|
+
errors.add("#{attr_name}", "#{configuration[:message]}") unless #{attr_name}_before_type_cast.to_s =~ /^[\\\\+\\\\-]?\\\\d+$/
|
541
|
+
}))
|
542
|
+
else
|
543
|
+
class_eval(%(#{validation_method(configuration[:on])} %{
|
544
|
+
begin
|
545
|
+
Kernel.Float(#{attr_name}_before_type_cast)
|
546
|
+
rescue ArgumentError, TypeError
|
547
|
+
errors.add("#{attr_name}", "#{configuration[:message]}")
|
548
|
+
end
|
549
|
+
}))
|
550
|
+
end
|
551
|
+
end
|
552
|
+
end
|
519
553
|
|
520
554
|
private
|
521
555
|
def write_inheritable_set(key, methods)
|