activerecord 1.9.1 → 1.10.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.

Files changed (68) hide show
  1. data/CHANGELOG +78 -0
  2. data/README +1 -1
  3. data/install.rb +7 -42
  4. data/lib/active_record.rb +2 -0
  5. data/lib/active_record/acts/list.rb +28 -4
  6. data/lib/active_record/acts/nested_set.rb +212 -0
  7. data/lib/active_record/associations.rb +203 -21
  8. data/lib/active_record/associations/association_proxy.rb +10 -2
  9. data/lib/active_record/associations/belongs_to_association.rb +0 -1
  10. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +15 -9
  11. data/lib/active_record/associations/has_many_association.rb +25 -25
  12. data/lib/active_record/associations/has_one_association.rb +2 -2
  13. data/lib/active_record/base.rb +134 -110
  14. data/lib/active_record/connection_adapters/abstract_adapter.rb +9 -9
  15. data/lib/active_record/connection_adapters/mysql_adapter.rb +4 -0
  16. data/lib/active_record/connection_adapters/oci_adapter.rb +2 -2
  17. data/lib/active_record/connection_adapters/sqlserver_adapter.rb +1 -2
  18. data/lib/active_record/deprecated_associations.rb +1 -19
  19. data/lib/active_record/deprecated_finders.rb +41 -0
  20. data/lib/active_record/fixtures.rb +24 -11
  21. data/lib/active_record/observer.rb +17 -11
  22. data/lib/active_record/reflection.rb +5 -1
  23. data/lib/active_record/transactions.rb +7 -0
  24. data/lib/active_record/validations.rb +32 -33
  25. data/rakefile +30 -6
  26. data/test/associations_go_eager_test.rb +55 -0
  27. data/test/associations_test.rb +72 -15
  28. data/test/base_test.rb +15 -21
  29. data/test/deprecated_associations_test.rb +0 -24
  30. data/test/deprecated_finder_test.rb +147 -0
  31. data/test/finder_test.rb +37 -37
  32. data/test/fixtures/author.rb +3 -0
  33. data/test/fixtures/authors.yml +7 -0
  34. data/test/fixtures/categories.yml +7 -0
  35. data/test/fixtures/categories_posts.yml +11 -0
  36. data/test/fixtures/category.rb +3 -0
  37. data/test/fixtures/comment.rb +5 -0
  38. data/test/fixtures/comments.yml +17 -0
  39. data/test/fixtures/company.rb +3 -0
  40. data/test/fixtures/courses.yml +4 -4
  41. data/test/fixtures/db_definitions/db2.drop.sql +6 -0
  42. data/test/fixtures/db_definitions/db2.sql +46 -0
  43. data/test/fixtures/db_definitions/mysql.drop.sql +6 -1
  44. data/test/fixtures/db_definitions/mysql.sql +60 -12
  45. data/test/fixtures/db_definitions/mysql2.sql +1 -1
  46. data/test/fixtures/db_definitions/oci.drop.sql +5 -0
  47. data/test/fixtures/db_definitions/oci.sql +45 -0
  48. data/test/fixtures/db_definitions/postgresql.drop.sql +6 -0
  49. data/test/fixtures/db_definitions/postgresql.sql +45 -0
  50. data/test/fixtures/db_definitions/sqlite.drop.sql +6 -1
  51. data/test/fixtures/db_definitions/sqlite.sql +46 -0
  52. data/test/fixtures/db_definitions/sqlserver.drop.sql +7 -1
  53. data/test/fixtures/db_definitions/sqlserver.sql +46 -0
  54. data/test/fixtures/fk_test_has_fk.yml +3 -0
  55. data/test/fixtures/fk_test_has_pk.yml +2 -0
  56. data/test/fixtures/mixin.rb +18 -0
  57. data/test/fixtures/mixins.yml +30 -0
  58. data/test/fixtures/post.rb +8 -0
  59. data/test/fixtures/posts.yml +20 -0
  60. data/test/fixtures/task.rb +3 -0
  61. data/test/fixtures/tasks.yml +7 -0
  62. data/test/fixtures_test.rb +34 -2
  63. data/test/mixin_nested_set_test.rb +184 -0
  64. data/test/mixin_test.rb +28 -3
  65. data/test/validations_test.rb +16 -0
  66. metadata +21 -5
  67. data/test/fixtures/db_definitions/drop_oracle_tables.sql +0 -35
  68. data/test/fixtures/db_definitions/drop_oracle_tables2.sql +0 -3
@@ -186,7 +186,7 @@ module ActiveRecord
186
186
  when :time then string_to_dummy_time(value)
187
187
  when :date then string_to_date(value)
188
188
  when :binary then binary_to_string(value)
189
- when :boolean then (value == "t" or value == true ? true : false)
189
+ when :boolean then value == true or (value =~ /^t(rue)?$/i) == 0 or value.to_s == '1'
190
190
  else value
191
191
  end
192
192
  end
@@ -205,22 +205,22 @@ module ActiveRecord
205
205
 
206
206
  private
207
207
  def string_to_date(string)
208
- return string if string.is_a?(Date)
209
- date_array = ParseDate.parsedate(string)
208
+ return string unless string.is_a?(String)
209
+ date_array = ParseDate.parsedate(string.to_s)
210
210
  # treat 0000-00-00 as nil
211
211
  Date.new(date_array[0], date_array[1], date_array[2]) rescue nil
212
212
  end
213
213
 
214
214
  def string_to_time(string)
215
- return string if string.is_a?(Time)
216
- time_array = ParseDate.parsedate(string).compact
215
+ return string unless string.is_a?(String)
216
+ time_array = ParseDate.parsedate(string.to_s).compact
217
217
  # treat 0000-00-00 00:00:00 as nil
218
218
  Time.send(Base.default_timezone, *time_array) rescue nil
219
219
  end
220
220
 
221
221
  def string_to_dummy_time(string)
222
- return string if string.is_a?(Time)
223
- time_array = ParseDate.parsedate(string)
222
+ return string unless string.is_a?(String)
223
+ time_array = ParseDate.parsedate(string.to_s)
224
224
  # pad the resulting array with dummy date information
225
225
  time_array[0] = 2000; time_array[1] = 1; time_array[2] = 1;
226
226
  Time.send(Base.default_timezone, *time_array) rescue nil
@@ -378,8 +378,8 @@ module ActiveRecord
378
378
  end
379
379
  end
380
380
 
381
- def create_table(name)
382
- execute "CREATE TABLE #{name} (id #{native_database_types[:primary_key]})"
381
+ def create_table(name, options = "")
382
+ execute "CREATE TABLE #{name} (id #{native_database_types[:primary_key]}) #{options}"
383
383
  table_definition = yield TableDefinition.new
384
384
  table_definition.columns.each { |column_name, type, options| add_column(name, column_name, type, options) }
385
385
  end
@@ -190,6 +190,10 @@ module ActiveRecord
190
190
  execute "CREATE DATABASE #{name}"
191
191
  end
192
192
 
193
+
194
+ def create_table(name)
195
+ super(name, "ENGINE=InnoDB")
196
+ end
193
197
 
194
198
  private
195
199
  def select(sql, name = nil)
@@ -45,7 +45,7 @@ begin
45
45
  return nil if value.nil? || value =~ /^\s*null\s*$/i
46
46
  case type
47
47
  when :string then value
48
- when :integer then value.to_i
48
+ when :integer then defined?(value.to_i) ? value.to_i : (value ? 1 : 0)
49
49
  when :float then value.to_f
50
50
  when :datetime then cast_to_date_or_time(value)
51
51
  when :time then cast_to_time(value)
@@ -261,4 +261,4 @@ begin
261
261
  end
262
262
  rescue LoadError
263
263
  # OCI8 driver is unavailable.
264
- end
264
+ end
@@ -203,7 +203,6 @@ module ActiveRecord
203
203
  end
204
204
  end
205
205
  end
206
-
207
206
  log(sql, name, @connection) do |conn|
208
207
  conn.execute(sql)
209
208
  select_one("SELECT @@IDENTITY AS Ident")["Ident"]
@@ -362,7 +361,7 @@ module ActiveRecord
362
361
  end
363
362
 
364
363
  def query_contains_identity_column(sql, col)
365
- return sql =~ /[\(\.\,]\s*#{col}/
364
+ return sql =~ /[\[.,]\s*#{col}/
366
365
  end
367
366
 
368
367
  def get_order_by(sql)
@@ -84,25 +84,7 @@ module ActiveRecord
84
84
  !#{association_name}(force_reload).nil?
85
85
  end
86
86
  end_eval
87
- end
88
-
89
- def deprecated_build_method(method_prefix, collection_name, collection_class_name, class_primary_key_name)# :nodoc:
90
- module_eval <<-"end_eval", __FILE__, __LINE__
91
- def #{method_prefix + collection_name}(attributes = {})
92
- association = #{collection_class_name}.new
93
- association.attributes = attributes.merge({ "#{class_primary_key_name}" => id})
94
- association
95
- end
96
- end_eval
97
- end
98
-
99
- def deprecated_create_method(method_prefix, collection_name, collection_class_name, class_primary_key_name)# :nodoc:
100
- module_eval <<-"end_eval", __FILE__, __LINE__
101
- def #{method_prefix + collection_name}(attributes = nil)
102
- #{collection_class_name}.create((attributes || {}).merge({ "#{class_primary_key_name}" => id}))
103
- end
104
- end_eval
105
- end
87
+ end
106
88
  end
107
89
  end
108
90
  end
@@ -0,0 +1,41 @@
1
+ module ActiveRecord
2
+ class Base
3
+ class << self
4
+ # This method is deprecated in favor of find with the :conditions option.
5
+ #
6
+ # Works like find, but the record matching +id+ must also meet the +conditions+.
7
+ # +RecordNotFound+ is raised if no record can be found matching the +id+ or meeting the condition.
8
+ # Example:
9
+ # Person.find_on_conditions 5, "first_name LIKE '%dav%' AND last_name = 'heinemeier'"
10
+ def find_on_conditions(ids, conditions) # :nodoc:
11
+ find(ids, :conditions => conditions)
12
+ end
13
+
14
+ # This method is deprecated in favor of find(:first, options).
15
+ #
16
+ # Returns the object for the first record responding to the conditions in +conditions+,
17
+ # such as "group = 'master'". If more than one record is returned from the query, it's the first that'll
18
+ # be used to create the object. In such cases, it might be beneficial to also specify
19
+ # +orderings+, like "income DESC, name", to control exactly which record is to be used. Example:
20
+ # Employee.find_first "income > 50000", "income DESC, name"
21
+ def find_first(conditions = nil, orderings = nil, joins = nil) # :nodoc:
22
+ find(:first, :conditions => conditions, :order => orderings, :joins => joins)
23
+ end
24
+
25
+ # This method is deprecated in favor of find(:all, options).
26
+ #
27
+ # Returns an array of all the objects that could be instantiated from the associated
28
+ # table in the database. The +conditions+ can be used to narrow the selection of objects (WHERE-part),
29
+ # such as by "color = 'red'", and arrangement of the selection can be done through +orderings+ (ORDER BY-part),
30
+ # such as by "last_name, first_name DESC". A maximum of returned objects and their offset can be specified in
31
+ # +limit+ with either just a single integer as the limit or as an array with the first element as the limit,
32
+ # the second as the offset. Examples:
33
+ # Project.find_all "category = 'accounts'", "last_accessed DESC", 15
34
+ # Project.find_all ["category = ?", category_name], "created ASC", [15, 20]
35
+ def find_all(conditions = nil, orderings = nil, limit = nil, joins = nil) # :nodoc:
36
+ limit, offset = limit.is_a?(Array) ? limit : [ limit, nil ]
37
+ find(:all, :conditions => conditions, :order => orderings, :joins => joins, :limit => limit, :offset => offset)
38
+ end
39
+ end
40
+ end
41
+ end
@@ -185,7 +185,10 @@ class Fixtures < Hash
185
185
  DEFAULT_FILTER_RE = /\.ya?ml$/
186
186
 
187
187
  def self.instantiate_fixtures(object, table_name, fixtures, load_instances=true)
188
- object.instance_variable_set "@#{table_name}", fixtures
188
+ old_logger_level = ActiveRecord::Base.logger.level
189
+ ActiveRecord::Base.logger.level = Logger::ERROR
190
+
191
+ object.instance_variable_set "@#{table_name.to_s.gsub('.','_')}", fixtures
189
192
  if load_instances
190
193
  fixtures.each do |name, fixture|
191
194
  if model = fixture.find
@@ -193,12 +196,14 @@ class Fixtures < Hash
193
196
  end
194
197
  end
195
198
  end
199
+
200
+ ActiveRecord::Base.logger.level = old_logger_level
196
201
  end
197
202
 
198
203
  def self.instantiate_all_loaded_fixtures(object, load_instances=true)
199
204
  all_loaded_fixtures.each do |table_name, fixtures|
200
205
  Fixtures.instantiate_fixtures(object, table_name, fixtures, load_instances)
201
- end
206
+ end
202
207
  end
203
208
 
204
209
  cattr_accessor :all_loaded_fixtures
@@ -238,7 +243,7 @@ class Fixtures < Hash
238
243
  pk = eval("#{table_class}::primary_key")
239
244
  if pk == 'id'
240
245
  connection.execute(
241
- "SELECT setval('public.#{table.to_s}_id_seq', (SELECT MAX(id) FROM #{table.to_s}), true)",
246
+ "SELECT setval('#{table.to_s}_id_seq', (SELECT MAX(id) FROM #{table.to_s}), true)",
242
247
  'Setting Sequence'
243
248
  )
244
249
  end
@@ -246,6 +251,8 @@ class Fixtures < Hash
246
251
  end
247
252
  end
248
253
 
254
+ attr_reader :table_name
255
+
249
256
  def initialize(connection, table_name, fixture_path, file_filter = DEFAULT_FILTER_RE)
250
257
  @connection, @table_name, @fixture_path, @file_filter = connection, table_name, fixture_path, file_filter
251
258
  @class_name = Inflector.classify(@table_name)
@@ -342,7 +349,8 @@ class Fixture #:nodoc:
342
349
  end
343
350
 
344
351
  def key_list
345
- @fixture.keys.join(", ")
352
+ columns = @fixture.keys.collect{ |column_name| ActiveRecord::Base.connection.quote_column_name(column_name) }
353
+ columns.join(", ")
346
354
  end
347
355
 
348
356
  def value_list
@@ -464,30 +472,35 @@ module Test #:nodoc:
464
472
  private
465
473
  def load_fixtures
466
474
  @loaded_fixtures = {}
467
- fixture_table_names.each do |table_name|
468
- @loaded_fixtures[table_name] = Fixtures.create_fixtures(fixture_path, table_name)
475
+ fixtures = Fixtures.create_fixtures(fixture_path, fixture_table_names)
476
+ unless fixtures.nil?
477
+ if fixtures.instance_of?(Fixtures)
478
+ @loaded_fixtures[fixtures.table_name] = fixtures
479
+ else
480
+ fixtures.each { |f| @loaded_fixtures[f.table_name] = f }
481
+ end
469
482
  end
470
483
  end
471
-
484
+
472
485
  # for pre_loaded_fixtures, only require the classes once. huge speed improvement
473
486
  @@required_fixture_classes = false
474
-
487
+
475
488
  def instantiate_fixtures
476
489
  if pre_loaded_fixtures
477
490
  raise RuntimeError, 'Load fixtures before instantiating them.' if Fixtures.all_loaded_fixtures.empty?
478
491
  unless @@required_fixture_classes
479
- self.class.require_fixture_classes Fixtures.all_loaded_fixtures.keys
492
+ self.class.require_fixture_classes Fixtures.all_loaded_fixtures.keys
480
493
  @@required_fixture_classes = true
481
494
  end
482
495
  Fixtures.instantiate_all_loaded_fixtures(self, load_instances?)
483
- else
496
+ else
484
497
  raise RuntimeError, 'Load fixtures before instantiating them.' if @loaded_fixtures.nil?
485
498
  @loaded_fixtures.each do |table_name, fixtures|
486
499
  Fixtures.instantiate_fixtures(self, table_name, fixtures, load_instances?)
487
500
  end
488
501
  end
489
502
  end
490
-
503
+
491
504
  def load_instances?
492
505
  use_instantiated_fixtures != :no_instances
493
506
  end
@@ -1,10 +1,11 @@
1
1
  require 'singleton'
2
2
 
3
3
  module ActiveRecord
4
- # Observers can be programmed to react to lifecycle callbacks in another class to implement
5
- # trigger-like behavior outside the original class. This is a great way to reduce the clutter that
6
- # normally comes when the model class is burdened with excess responsibility that doesn't pertain to
7
- # the core and nature of the class. Example:
4
+ # Observer classes respond to lifecycle callbacks to implement trigger-like
5
+ # behavior outside the original class. This is a great way to reduce the
6
+ # clutter that normally comes when the model class is burdened with
7
+ # functionality that doesn't pertain to the core responsibility of the
8
+ # class. Example:
8
9
  #
9
10
  # class CommentObserver < ActiveRecord::Observer
10
11
  # def after_save(comment)
@@ -12,13 +13,13 @@ module ActiveRecord
12
13
  # end
13
14
  # end
14
15
  #
15
- # This Observer is triggered when a Comment#save is finished and sends a notification about it to the administrator.
16
+ # This Observer sends an email when a Comment#save is finished.
16
17
  #
17
- # == Observing a class that can't be infered
18
+ # == Observing a class that can't be inferred
18
19
  #
19
20
  # Observers will by default be mapped to the class with which they share a name. So CommentObserver will
20
21
  # be tied to observing Comment, ProductManagerObserver to ProductManager, and so on. If you want to name your observer
21
- # something else than the class you're interested in observing, you can implement the observed_class class method. Like this:
22
+ # differently than the class you're interested in observing, you can use the Observer.observe class method:
22
23
  #
23
24
  # class AuditObserver < ActiveRecord::Observer
24
25
  # observe Account
@@ -28,9 +29,7 @@ module ActiveRecord
28
29
  # end
29
30
  # end
30
31
  #
31
- # == Observing multiple classes at once
32
- #
33
- # If the audit observer needs to watch more than one kind of object, this can be specified in an array, like this:
32
+ # If the audit observer needs to watch more than one kind of object, this can be specified with multiple arguments:
34
33
  #
35
34
  # class AuditObserver < ActiveRecord::Observer
36
35
  # observe Account, Balance
@@ -42,7 +41,14 @@ module ActiveRecord
42
41
  #
43
42
  # The AuditObserver will now act on both updates to Account and Balance by treating them both as records.
44
43
  #
44
+ # == Available callback methods
45
+ #
45
46
  # The observer can implement callback methods for each of the methods described in the Callbacks module.
47
+ #
48
+ # == Triggering Observers
49
+ #
50
+ # In order to activate an observer, you need to call Observer.instance. In Rails, this can be done in controllers
51
+ # using the short-hand of for example observer :comment_observer.
46
52
  class Observer
47
53
  include Singleton
48
54
 
@@ -75,4 +81,4 @@ module ActiveRecord
75
81
  self.class.name.scan(/(.*)Observer/)[0][0]
76
82
  end
77
83
  end
78
- end
84
+ end
@@ -114,7 +114,11 @@ module ActiveRecord
114
114
  # Holds all the meta-data about an association as it was specified in the Active Record class.
115
115
  class AssociationReflection < MacroReflection #:nodoc:
116
116
  def klass
117
- active_record.send(:compute_type, (name_to_class_name(name.id2name)))
117
+ @klass ||= active_record.send(:compute_type, (name_to_class_name(name.id2name)))
118
+ end
119
+
120
+ def table_name
121
+ @table_name ||= klass.table_name
118
122
  end
119
123
 
120
124
  private
@@ -6,6 +6,9 @@ module ActiveRecord
6
6
  module Transactions # :nodoc:
7
7
  TRANSACTION_MUTEX = Mutex.new
8
8
 
9
+ class TransactionError < ActiveRecordError # :nodoc:
10
+ end
11
+
9
12
  def self.append_features(base)
10
13
  super
11
14
  base.extend(ClassMethods)
@@ -78,6 +81,9 @@ module ActiveRecord
78
81
  # Tribute: Object-level transactions are implemented by Transaction::Simple by Austin Ziegler.
79
82
  module ClassMethods
80
83
  def transaction(*objects, &block)
84
+ previous_handler = trap('TERM') do
85
+ raise TransactionError, "Transaction aborted"
86
+ end
81
87
  lock_mutex
82
88
 
83
89
  begin
@@ -93,6 +99,7 @@ module ActiveRecord
93
99
  raise
94
100
  ensure
95
101
  unlock_mutex
102
+ trap('TERM', previous_handler)
96
103
  end
97
104
  end
98
105
 
@@ -1,4 +1,7 @@
1
1
  module ActiveRecord
2
+ class RecordInvalid < ActiveRecordError #:nodoc:
3
+ end
4
+
2
5
  # Active Record validation is reported to and from this object, which is used by Base#save to
3
6
  # determine whether the object in a valid state to be saved. See usage example in Validations.
4
7
  class Errors
@@ -291,16 +294,18 @@ module ActiveRecord
291
294
  # Configuration options:
292
295
  # * <tt>message</tt> - A custom error message (default is: "can't be empty")
293
296
  # * <tt>on</tt> - Specifies when this validation is active (default is :save, other options :create, :update)
297
+ # * <tt>accept</tt> - Specifies value that is considered accepted. The default value is a string "1", which
298
+ # makes it easy to relate to an HTML checkbox.
294
299
  #
295
- # NOTE: The agreement is considered valid if it's set to the string "1". This makes it easy to relate it to an HTML checkbox.
300
+
296
301
  def validates_acceptance_of(*attr_names)
297
- configuration = { :message => ActiveRecord::Errors.default_error_messages[:accepted], :on => :save, :allow_nil => true }
302
+ configuration = { :message => ActiveRecord::Errors.default_error_messages[:accepted], :on => :save, :allow_nil => true, :accept => "1" }
298
303
  configuration.update(attr_names.pop) if attr_names.last.is_a?(Hash)
299
304
 
300
305
  attr_accessor *attr_names
301
306
 
302
307
  validates_each(attr_names,configuration) do |record, attr_name, value|
303
- record.errors.add(attr_name, configuration[:message]) unless value == "1"
308
+ record.errors.add(attr_name, configuration[:message]) unless value == configuration[:accept]
304
309
  end
305
310
  end
306
311
 
@@ -354,12 +359,12 @@ module ActiveRecord
354
359
  # Ensure that one and only one range option is specified.
355
360
  range_options = ALL_RANGE_OPTIONS & options.keys
356
361
  case range_options.size
357
- when 0
358
- raise ArgumentError, 'Range unspecified. Specify the :within, :maximum, :minimum, or :is option.'
359
- when 1
360
- # Valid number of options; do nothing.
361
- else
362
- raise ArgumentError, 'Too many range options specified. Choose only one.'
362
+ when 0
363
+ raise ArgumentError, 'Range unspecified. Specify the :within, :maximum, :minimum, or :is option.'
364
+ when 1
365
+ # Valid number of options; do nothing.
366
+ else
367
+ raise ArgumentError, 'Too many range options specified. Choose only one.'
363
368
  end
364
369
 
365
370
  # Get range option and value.
@@ -367,33 +372,21 @@ module ActiveRecord
367
372
  option_value = options[range_options.first]
368
373
 
369
374
  # Declare different validations per option.
370
- case range_options.first
375
+
376
+ validity_checks = { :is => "==", :minimum => ">=", :maximum => "<=" }
377
+ message_options = { :is => :wrong_length, :minimum => :too_short, :maximum => :too_long }
378
+
379
+ case option
371
380
  when :within, :in
372
- raise ArgumentError, ':within must be a Range' unless option_value.is_a?(Range)
373
- validates_each(attrs, options) do |record, attr|
374
- next if record.send(attr).nil? and options[:allow_nil]
375
- record.errors.add_on_boundary_breaking(attr, option_value, options[:too_long], options[:too_short])
376
- end
377
- when :is
378
- raise ArgumentError, ':is must be a nonnegative Integer' unless option_value.is_a?(Integer) and option_value >= 0
379
- message = options[:message] || options[:wrong_length]
380
- message = (message % option_value) rescue message
381
- validates_each(attrs, options) do |record, attr, value|
382
- record.errors.add(attr, message) if value.nil? or value.size != option_value
383
- end
384
- when :minimum
385
- raise ArgumentError, ':minimum must be a nonnegative Integer' unless option_value.is_a?(Integer) and option_value >= 0
386
- message = options[:message] || options[:too_short]
381
+ raise ArgumentError, ':within must be a Range' unless option_value.is_a?(Range) # '
382
+ validates_length_of attrs, :minimum => option_value.begin, :allow_nil => options[:allow_nil]
383
+ validates_length_of attrs, :maximum => option_value.end, :allow_nil => options[:allow_nil]
384
+ when :is, :minimum, :maximum
385
+ raise ArgumentError, ":#{option} must be a nonnegative Integer" unless option_value.is_a?(Integer) and option_value >= 0 # '
386
+ message = options[:message] || options[message_options[option]]
387
387
  message = (message % option_value) rescue message
388
388
  validates_each(attrs, options) do |record, attr, value|
389
- record.errors.add(attr, message) if value.nil? or value.size < option_value
390
- end
391
- when :maximum
392
- raise ArgumentError, ':maximum must be a nonnegative Integer' unless option_value.is_a?(Integer) and option_value >= 0
393
- message = options[:message] || options[:too_long]
394
- message = (message % option_value) rescue message
395
- validates_each(attrs, options) do |record, attr, value|
396
- record.errors.add(attr, message) if value.nil? or value.size > option_value
389
+ record.errors.add(attr, message) if value.nil? or !value.size.method(validity_checks[option])[option_value]
397
390
  end
398
391
  end
399
392
  end
@@ -562,6 +555,12 @@ module ActiveRecord
562
555
  if perform_validation && valid? || !perform_validation then save_without_validation else false end
563
556
  end
564
557
 
558
+ # Attempts to save the record just like Base.save but will raise a RecordInvalid exception instead of returning false
559
+ # if the record is not valid.
560
+ def save!
561
+ valid? ? save_without_validation : raise(RecordInvalid)
562
+ end
563
+
565
564
  # Updates a single attribute and saves the record without going through the normal validation procedure.
566
565
  # This is especially useful for boolean flags on existing records. The regular +update_attribute+ method
567
566
  # in Base is replaced with this when the validations module is mixed in, which it is by default.