activerecord 2.1.0 → 2.1.1

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 (86) hide show
  1. data/CHANGELOG +34 -0
  2. data/README +0 -0
  3. data/Rakefile +6 -5
  4. data/lib/active_record.rb +8 -10
  5. data/lib/active_record/association_preload.rb +17 -12
  6. data/lib/active_record/associations.rb +45 -27
  7. data/lib/active_record/associations/association_collection.rb +8 -5
  8. data/lib/active_record/associations/association_proxy.rb +2 -6
  9. data/lib/active_record/associations/belongs_to_association.rb +0 -0
  10. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +0 -0
  11. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +2 -3
  12. data/lib/active_record/associations/has_many_association.rb +16 -4
  13. data/lib/active_record/associations/has_many_through_association.rb +1 -1
  14. data/lib/active_record/associations/has_one_association.rb +2 -2
  15. data/lib/active_record/associations/has_one_through_association.rb +4 -0
  16. data/lib/active_record/base.rb +33 -15
  17. data/lib/active_record/calculations.rb +20 -7
  18. data/lib/active_record/callbacks.rb +0 -0
  19. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +9 -6
  20. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +17 -10
  21. data/lib/active_record/connection_adapters/abstract_adapter.rb +0 -0
  22. data/lib/active_record/connection_adapters/mysql_adapter.rb +53 -24
  23. data/lib/active_record/connection_adapters/postgresql_adapter.rb +66 -20
  24. data/lib/active_record/connection_adapters/sqlite_adapter.rb +12 -0
  25. data/lib/active_record/dirty.rb +10 -3
  26. data/lib/active_record/fixtures.rb +0 -0
  27. data/lib/active_record/locking/optimistic.rb +1 -0
  28. data/lib/active_record/migration.rb +35 -8
  29. data/lib/active_record/named_scope.rb +6 -1
  30. data/lib/active_record/observer.rb +7 -5
  31. data/lib/active_record/test_case.rb +13 -2
  32. data/lib/active_record/validations.rb +19 -9
  33. data/lib/active_record/version.rb +1 -1
  34. data/test/cases/active_schema_test_postgresql.rb +2 -2
  35. data/test/cases/adapter_test.rb +1 -1
  36. data/test/cases/associations/belongs_to_associations_test.rb +19 -0
  37. data/test/cases/associations/cascaded_eager_loading_test.rb +13 -1
  38. data/test/cases/associations/eager_load_includes_full_sti_class_test.rb +36 -0
  39. data/test/cases/associations/eager_test.rb +25 -1
  40. data/test/cases/associations/has_and_belongs_to_many_associations_test.rb +27 -5
  41. data/test/cases/associations/has_many_associations_test.rb +106 -4
  42. data/test/cases/associations/has_many_through_associations_test.rb +10 -0
  43. data/test/cases/associations/has_one_associations_test.rb +22 -0
  44. data/test/cases/associations/has_one_through_associations_test.rb +44 -5
  45. data/test/cases/associations/join_model_test.rb +7 -0
  46. data/test/cases/associations_test.rb +2 -2
  47. data/test/cases/attribute_methods_test.rb +10 -10
  48. data/test/cases/base_test.rb +39 -16
  49. data/test/cases/calculations_test.rb +53 -1
  50. data/test/cases/column_definition_test.rb +36 -0
  51. data/test/cases/database_statements_test.rb +12 -0
  52. data/test/cases/defaults_test.rb +1 -1
  53. data/test/cases/deprecated_finder_test.rb +0 -0
  54. data/test/cases/dirty_test.rb +94 -0
  55. data/test/cases/finder_test.rb +7 -0
  56. data/test/cases/fixtures_test.rb +0 -0
  57. data/test/cases/helper.rb +5 -5
  58. data/test/cases/inheritance_test.rb +9 -2
  59. data/test/cases/lifecycle_test.rb +54 -1
  60. data/test/cases/locking_test.rb +20 -0
  61. data/test/cases/method_scoping_test.rb +11 -1
  62. data/test/cases/migration_test.rb +147 -22
  63. data/test/cases/multiple_db_test.rb +1 -1
  64. data/test/cases/named_scope_test.rb +50 -1
  65. data/test/cases/query_cache_test.rb +4 -3
  66. data/test/cases/readonly_test.rb +0 -0
  67. data/test/cases/reflection_test.rb +3 -3
  68. data/test/cases/schema_dumper_test.rb +46 -0
  69. data/test/cases/unconnected_test.rb +0 -0
  70. data/test/cases/validations_test.rb +30 -5
  71. data/test/debug.log +358 -0
  72. data/test/fixtures/fixture_database.sqlite3 +0 -0
  73. data/test/fixtures/fixture_database_2.sqlite3 +0 -0
  74. data/test/models/author.rb +4 -0
  75. data/test/models/category.rb +1 -0
  76. data/test/models/company.rb +10 -1
  77. data/test/models/developer.rb +4 -1
  78. data/test/models/person.rb +1 -1
  79. data/test/models/post.rb +6 -1
  80. data/test/models/project.rb +1 -1
  81. data/test/models/reply.rb +0 -0
  82. data/test/models/topic.rb +1 -0
  83. data/test/schema/mysql_specific_schema.rb +2 -2
  84. data/test/schema/schema.rb +8 -0
  85. metadata +11 -5
  86. data/lib/active_record/vendor/db2.rb +0 -362
@@ -23,8 +23,8 @@ module ActiveRecord
23
23
  config = config.symbolize_keys
24
24
  host = config[:host]
25
25
  port = config[:port] || 5432
26
- username = config[:username].to_s
27
- password = config[:password].to_s
26
+ username = config[:username].to_s if config[:username]
27
+ password = config[:password].to_s if config[:password]
28
28
 
29
29
  if config.has_key?(:database)
30
30
  database = config[:database]
@@ -47,6 +47,14 @@ module ActiveRecord
47
47
  end
48
48
 
49
49
  private
50
+ def extract_limit(sql_type)
51
+ case sql_type
52
+ when /^bigint/i; 8
53
+ when /^smallint/i; 2
54
+ else super
55
+ end
56
+ end
57
+
50
58
  # Extracts the scale from PostgreSQL-specific data types.
51
59
  def extract_scale(sql_type)
52
60
  # Money type has a fixed scale of 2.
@@ -174,8 +182,8 @@ module ActiveRecord
174
182
  def self.extract_value_from_default(default)
175
183
  case default
176
184
  # Numeric types
177
- when /\A-?\d+(\.\d*)?\z/
178
- default
185
+ when /\A\(?(-?\d+(\.\d*)?\)?)\z/
186
+ $1
179
187
  # Character types
180
188
  when /\A'(.*)'::(?:character varying|bpchar|text)\z/m
181
189
  $1
@@ -319,6 +327,10 @@ module ActiveRecord
319
327
  has_support
320
328
  end
321
329
 
330
+ def supports_insert_with_returning?
331
+ postgresql_version >= 80200
332
+ end
333
+
322
334
  # Returns the configured supported identifier length supported by PostgreSQL,
323
335
  # or report the default of 63 on PostgreSQL 7.x.
324
336
  def table_alias_length
@@ -360,7 +372,7 @@ module ActiveRecord
360
372
  # There are some incorrectly compiled postgres drivers out there
361
373
  # that don't define PGconn.escape.
362
374
  self.class.instance_eval do
363
- undef_method(:quote_string)
375
+ remove_method(:quote_string)
364
376
  end
365
377
  end
366
378
  quote_string(s)
@@ -411,8 +423,34 @@ module ActiveRecord
411
423
 
412
424
  # Executes an INSERT query and returns the new record's ID
413
425
  def insert(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil)
426
+ # Extract the table from the insert sql. Yuck.
414
427
  table = sql.split(" ", 4)[2].gsub('"', '')
415
- super || pk && last_insert_id(table, sequence_name || default_sequence_name(table, pk))
428
+
429
+ # Try an insert with 'returning id' if available (PG >= 8.2)
430
+ if supports_insert_with_returning?
431
+ pk, sequence_name = *pk_and_sequence_for(table) unless pk
432
+ if pk
433
+ id = select_value("#{sql} RETURNING #{quote_column_name(pk)}")
434
+ clear_query_cache
435
+ return id
436
+ end
437
+ end
438
+
439
+ # Otherwise, insert then grab last_insert_id.
440
+ if insert_id = super
441
+ insert_id
442
+ else
443
+ # If neither pk nor sequence name is given, look them up.
444
+ unless pk || sequence_name
445
+ pk, sequence_name = *pk_and_sequence_for(table)
446
+ end
447
+
448
+ # If a pk is given, fallback to default sequence name.
449
+ # Don't fetch last insert id for a table without a pk.
450
+ if pk && sequence_name ||= default_sequence_name(table, pk)
451
+ last_insert_id(table, sequence_name)
452
+ end
453
+ end
416
454
  end
417
455
 
418
456
  # create a 2D array representing the result set
@@ -492,13 +530,13 @@ module ActiveRecord
492
530
  option_string = options.symbolize_keys.sum do |key, value|
493
531
  case key
494
532
  when :owner
495
- " OWNER = '#{value}'"
533
+ " OWNER = \"#{value}\""
496
534
  when :template
497
- " TEMPLATE = #{value}"
535
+ " TEMPLATE = \"#{value}\""
498
536
  when :encoding
499
537
  " ENCODING = '#{value}'"
500
538
  when :tablespace
501
- " TABLESPACE = #{value}"
539
+ " TABLESPACE = \"#{value}\""
502
540
  when :connection_limit
503
541
  " CONNECTION LIMIT = #{value}"
504
542
  else
@@ -506,7 +544,7 @@ module ActiveRecord
506
544
  end
507
545
  end
508
546
 
509
- execute "CREATE DATABASE #{name}#{option_string}"
547
+ execute "CREATE DATABASE #{quote_table_name(name)}#{option_string}"
510
548
  end
511
549
 
512
550
  # Drops a PostgreSQL database
@@ -514,7 +552,15 @@ module ActiveRecord
514
552
  # Example:
515
553
  # drop_database 'matt_development'
516
554
  def drop_database(name) #:nodoc:
517
- execute "DROP DATABASE IF EXISTS #{name}"
555
+ if postgresql_version >= 80200
556
+ execute "DROP DATABASE IF EXISTS #{quote_table_name(name)}"
557
+ else
558
+ begin
559
+ execute "DROP DATABASE #{quote_table_name(name)}"
560
+ rescue ActiveRecord::StatementInvalid
561
+ @logger.warn "#{name} database doesn't exist." if @logger
562
+ end
563
+ end
518
564
  end
519
565
 
520
566
 
@@ -676,7 +722,7 @@ module ActiveRecord
676
722
 
677
723
  # Renames a table.
678
724
  def rename_table(name, new_name)
679
- execute "ALTER TABLE #{name} RENAME TO #{new_name}"
725
+ execute "ALTER TABLE #{quote_table_name(name)} RENAME TO #{quote_table_name(new_name)}"
680
726
  end
681
727
 
682
728
  # Adds a new column to the named table.
@@ -698,7 +744,8 @@ module ActiveRecord
698
744
 
699
745
  begin
700
746
  execute "ALTER TABLE #{quoted_table_name} ALTER COLUMN #{quote_column_name(column_name)} TYPE #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}"
701
- rescue ActiveRecord::StatementInvalid
747
+ rescue ActiveRecord::StatementInvalid => e
748
+ raise e if postgresql_version > 80000
702
749
  # This is PostgreSQL 7.x, so we have to use a more arcane way of doing it.
703
750
  begin
704
751
  begin_db_transaction
@@ -743,15 +790,14 @@ module ActiveRecord
743
790
  def type_to_sql(type, limit = nil, precision = nil, scale = nil)
744
791
  return super unless type.to_s == 'integer'
745
792
 
746
- if limit.nil? || limit == 4
747
- 'integer'
748
- elsif limit < 4
749
- 'smallint'
750
- else
751
- 'bigint'
793
+ case limit
794
+ when 1..2; 'smallint'
795
+ when 3..4, nil; 'integer'
796
+ when 5..8; 'bigint'
797
+ else raise(ActiveRecordError, "No integer type has byte size #{limit}. Use a numeric with precision 0 instead.")
752
798
  end
753
799
  end
754
-
800
+
755
801
  # Returns a SELECT DISTINCT clause for a given set of columns and a given ORDER BY clause.
756
802
  #
757
803
  # PostgreSQL requires the ORDER BY columns in the select list for distinct queries, and
@@ -238,6 +238,15 @@ module ActiveRecord
238
238
  end
239
239
  end
240
240
 
241
+ def change_column_null(table_name, column_name, null, default = nil)
242
+ unless null || default.nil?
243
+ execute("UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote(default)} WHERE #{quote_column_name(column_name)} IS NULL")
244
+ end
245
+ alter_table(table_name) do |definition|
246
+ definition[column_name].null = null
247
+ end
248
+ end
249
+
241
250
  def change_column(table_name, column_name, type, options = {}) #:nodoc:
242
251
  alter_table(table_name) do |definition|
243
252
  include_default = options_include_default?(options)
@@ -251,6 +260,9 @@ module ActiveRecord
251
260
  end
252
261
 
253
262
  def rename_column(table_name, column_name, new_column_name) #:nodoc:
263
+ unless columns(table_name).detect{|c| c.name == column_name.to_s }
264
+ raise ActiveRecord::ActiveRecordError, "Missing column #{table_name}.#{column_name}"
265
+ end
254
266
  alter_table(table_name, :rename => {column_name.to_s => new_column_name.to_s})
255
267
  end
256
268
 
@@ -123,7 +123,10 @@ module ActiveRecord
123
123
  attr = attr.to_s
124
124
 
125
125
  # The attribute already has an unsaved change.
126
- unless changed_attributes.include?(attr)
126
+ if changed_attributes.include?(attr)
127
+ old = changed_attributes[attr]
128
+ changed_attributes.delete(attr) unless field_changed?(attr, old, value)
129
+ else
127
130
  old = clone_attribute_value(:read_attribute, attr)
128
131
  changed_attributes[attr] = old if field_changed?(attr, old, value)
129
132
  end
@@ -134,7 +137,9 @@ module ActiveRecord
134
137
 
135
138
  def update_with_dirty
136
139
  if partial_updates?
137
- update_without_dirty(changed)
140
+ # Serialized attributes should always be written in case they've been
141
+ # changed in place.
142
+ update_without_dirty(changed | self.class.serialized_attributes.keys)
138
143
  else
139
144
  update_without_dirty
140
145
  end
@@ -142,9 +147,11 @@ module ActiveRecord
142
147
 
143
148
  def field_changed?(attr, old, value)
144
149
  if column = column_for_attribute(attr)
145
- if column.type == :integer && column.null && old.nil?
150
+ if column.type == :integer && column.null && (old.nil? || old == 0)
146
151
  # For nullable integer columns, NULL gets stored in database for blank (i.e. '') values.
147
152
  # Hence we don't record it as a change if the value changes from nil to ''.
153
+ # If an old value of 0 is set to '' we want this to get changed to nil as otherwise it'll
154
+ # be typecast back to 0 (''.to_i => 0)
148
155
  value = nil if value.blank?
149
156
  else
150
157
  value = column.type_cast(value)
File without changes
@@ -68,6 +68,7 @@ module ActiveRecord
68
68
 
69
69
  def update_with_lock(attribute_names = @attributes.keys) #:nodoc:
70
70
  return update_without_lock(attribute_names) unless locking_enabled?
71
+ return 0 if attribute_names.empty?
71
72
 
72
73
  lock_col = self.class.locking_column
73
74
  previous_value = send(lock_col).to_i
@@ -238,6 +238,22 @@ module ActiveRecord
238
238
  # lower than the current schema version: when migrating up, those
239
239
  # never-applied "interleaved" migrations will be automatically applied, and
240
240
  # when migrating down, never-applied "interleaved" migrations will be skipped.
241
+ #
242
+ # == Timestamped Migrations
243
+ #
244
+ # By default, Rails generates migrations that look like:
245
+ #
246
+ # 20080717013526_your_migration_name.rb
247
+ #
248
+ # The prefix is a generation timestamp (in UTC).
249
+ #
250
+ # If you'd prefer to use numeric prefixes, you can turn timestamped migrations
251
+ # off by setting:
252
+ #
253
+ # config.active_record.timestamped_migrations = false
254
+ #
255
+ # In environment.rb.
256
+ #
241
257
  class Migration
242
258
  @@verbose = true
243
259
  cattr_accessor :verbose
@@ -369,11 +385,17 @@ module ActiveRecord
369
385
  Base.table_name_prefix + 'schema_migrations' + Base.table_name_suffix
370
386
  end
371
387
 
388
+ def get_all_versions
389
+ Base.connection.select_values("SELECT version FROM #{schema_migrations_table_name}").map(&:to_i).sort
390
+ end
391
+
372
392
  def current_version
373
- version = Base.connection.select_values(
374
- "SELECT version FROM #{schema_migrations_table_name}"
375
- ).map(&:to_i).max rescue nil
376
- version || 0
393
+ sm_table = schema_migrations_table_name
394
+ if Base.connection.table_exists?(sm_table)
395
+ get_all_versions.max || 0
396
+ else
397
+ 0
398
+ end
377
399
  end
378
400
 
379
401
  def proper_table_name(name)
@@ -389,7 +411,7 @@ module ActiveRecord
389
411
  end
390
412
 
391
413
  def current_version
392
- self.class.current_version
414
+ migrated.last || 0
393
415
  end
394
416
 
395
417
  def current_migration
@@ -399,7 +421,10 @@ module ActiveRecord
399
421
  def run
400
422
  target = migrations.detect { |m| m.version == @target_version }
401
423
  raise UnknownMigrationVersionError.new(@target_version) if target.nil?
402
- target.migrate(@direction)
424
+ unless (up? && migrated.include?(target.version.to_i)) || (down? && !migrated.include?(target.version.to_i))
425
+ target.migrate(@direction)
426
+ record_version_state_after_migrating(target.version)
427
+ end
403
428
  end
404
429
 
405
430
  def migrate
@@ -470,17 +495,19 @@ module ActiveRecord
470
495
  end
471
496
 
472
497
  def migrated
473
- sm_table = self.class.schema_migrations_table_name
474
- Base.connection.select_values("SELECT version FROM #{sm_table}").map(&:to_i).sort
498
+ @migrated_versions ||= self.class.get_all_versions
475
499
  end
476
500
 
477
501
  private
478
502
  def record_version_state_after_migrating(version)
479
503
  sm_table = self.class.schema_migrations_table_name
480
504
 
505
+ @migrated_versions ||= []
481
506
  if down?
507
+ @migrated_versions.delete(version.to_i)
482
508
  Base.connection.update("DELETE FROM #{sm_table} WHERE version = '#{version}'")
483
509
  else
510
+ @migrated_versions.push(version.to_i).sort!
484
511
  Base.connection.insert("INSERT INTO #{sm_table} (version) VALUES ('#{version}')")
485
512
  end
486
513
  end
@@ -82,6 +82,7 @@ module ActiveRecord
82
82
  # expected_options = { :conditions => { :colored => 'red' } }
83
83
  # assert_equal expected_options, Shirt.colored('red').proxy_options
84
84
  def named_scope(name, options = {}, &block)
85
+ name = name.to_sym
85
86
  scopes[name] = lambda do |parent_scope, *args|
86
87
  Scope.new(parent_scope, case options
87
88
  when Hash
@@ -102,7 +103,7 @@ module ActiveRecord
102
103
  attr_reader :proxy_scope, :proxy_options
103
104
 
104
105
  [].methods.each do |m|
105
- unless m =~ /(^__|^nil\?|^send|^object_id$|class|extend|find|count|sum|average|maximum|minimum|paginate|first|last|empty?)/
106
+ unless m =~ /(^__|^nil\?|^send|^object_id$|class|extend|^find$|count|sum|average|maximum|minimum|paginate|first|last|empty\?|respond_to\?)/
106
107
  delegate m, :to => :proxy_found
107
108
  end
108
109
  end
@@ -139,6 +140,10 @@ module ActiveRecord
139
140
  @found ? @found.empty? : count.zero?
140
141
  end
141
142
 
143
+ def respond_to?(method, include_private = false)
144
+ super || @proxy_scope.respond_to?(method, include_private)
145
+ end
146
+
142
147
  protected
143
148
  def proxy_found
144
149
  @found || load_found
@@ -20,7 +20,7 @@ module ActiveRecord
20
20
  # ActiveRecord::Base.observers = Cacher, GarbageCollector
21
21
  #
22
22
  # Note: Setting this does not instantiate the observers yet. +instantiate_observers+ is
23
- # called during startup, and before each development request.
23
+ # called during startup, and before each development request.
24
24
  def observers=(*observers)
25
25
  @observers = observers.flatten
26
26
  end
@@ -130,11 +130,11 @@ module ActiveRecord
130
130
  # Observers register themselves in the model class they observe, since it is the class that
131
131
  # notifies them of events when they occur. As a side-effect, when an observer is loaded its
132
132
  # corresponding model class is loaded.
133
- #
133
+ #
134
134
  # Up to (and including) Rails 2.0.2 observers were instantiated between plugins and
135
- # application initializers. Now observers are loaded after application initializers,
135
+ # application initializers. Now observers are loaded after application initializers,
136
136
  # so observed models can make use of extensions.
137
- #
137
+ #
138
138
  # If by any chance you are using observed models in the initialization you can still
139
139
  # load their observers by calling <tt>ModelObserver.instance</tt> before. Observers are
140
140
  # singletons and that call instantiates and registers them.
@@ -189,7 +189,9 @@ module ActiveRecord
189
189
 
190
190
  def add_observer!(klass)
191
191
  klass.add_observer(self)
192
- klass.class_eval 'def after_find() end' unless klass.respond_to?(:after_find)
192
+ if respond_to?(:after_find) && !klass.method_defined?(:after_find)
193
+ klass.class_eval 'def after_find() end'
194
+ end
193
195
  end
194
196
  end
195
197
  end
@@ -22,11 +22,22 @@ module ActiveRecord
22
22
  end
23
23
  end
24
24
 
25
+ def assert_sql(*patterns_to_match)
26
+ $queries_executed = []
27
+ yield
28
+ ensure
29
+ failed_patterns = []
30
+ patterns_to_match.each do |pattern|
31
+ failed_patterns << pattern unless $queries_executed.any?{ |sql| pattern === sql }
32
+ end
33
+ assert failed_patterns.empty?, "Query pattern(s) #{failed_patterns.map(&:inspect).join(', ')} not found."
34
+ end
35
+
25
36
  def assert_queries(num = 1)
26
- $query_count = 0
37
+ $queries_executed = []
27
38
  yield
28
39
  ensure
29
- assert_equal num, $query_count, "#{$query_count} instead of #{num} queries were executed."
40
+ assert_equal num, $queries_executed.size, "#{$queries_executed.size} instead of #{num} queries were executed."
30
41
  end
31
42
 
32
43
  def assert_no_queries(&block)
@@ -480,8 +480,9 @@ module ActiveRecord
480
480
  # validates_length_of :fax, :in => 7..32, :allow_nil => true
481
481
  # validates_length_of :phone, :in => 7..32, :allow_blank => true
482
482
  # validates_length_of :user_name, :within => 6..20, :too_long => "pick a shorter name", :too_short => "pick a longer name"
483
- # validates_length_of :fav_bra_size, :minimum=>1, :too_short=>"please enter at least %d character"
484
- # validates_length_of :smurf_leader, :is=>4, :message=>"papa is spelled with %d characters... don't play me."
483
+ # validates_length_of :fav_bra_size, :minimum => 1, :too_short => "please enter at least %d character"
484
+ # validates_length_of :smurf_leader, :is => 4, :message => "papa is spelled with %d characters... don't play me."
485
+ # validates_length_of :essay, :minimum => 100, :too_short => "Your essay must be at least %d words."), :tokenizer => lambda {|str| str.scan(/\w+/) }
485
486
  # end
486
487
  #
487
488
  # Configuration options:
@@ -492,7 +493,6 @@ module ActiveRecord
492
493
  # * <tt>:in</tt> - A synonym(or alias) for <tt>:within</tt>.
493
494
  # * <tt>:allow_nil</tt> - Attribute may be +nil+; skip validation.
494
495
  # * <tt>:allow_blank</tt> - Attribute may be blank; skip validation.
495
- #
496
496
  # * <tt>:too_long</tt> - The error message if the attribute goes over the maximum (default is: "is too long (maximum is %d characters)").
497
497
  # * <tt>:too_short</tt> - The error message if the attribute goes under the minimum (default is: "is too short (min is %d characters)").
498
498
  # * <tt>:wrong_length</tt> - The error message if using the <tt>:is</tt> method and the attribute is the wrong size (default is: "is the wrong length (should be %d characters)").
@@ -504,12 +504,16 @@ module ActiveRecord
504
504
  # * <tt>:unless</tt> - Specifies a method, proc or string to call to determine if the validation should
505
505
  # not occur (e.g. <tt>:unless => :skip_validation</tt>, or <tt>:unless => Proc.new { |user| user.signup_step <= 2 }</tt>). The
506
506
  # method, proc or string should return or evaluate to a true or false value.
507
+ # * <tt>:tokenizer</tt> - Specifies how to split up the attribute string. (e.g. <tt>:tokenizer => lambda {|str| str.scan(/\w+/)}</tt> to
508
+ # count words as in above example.)
509
+ # Defaults to <tt>lambda{ |value| value.split(//) }</tt> which counts individual characters.
507
510
  def validates_length_of(*attrs)
508
511
  # Merge given options with defaults.
509
512
  options = {
510
513
  :too_long => ActiveRecord::Errors.default_error_messages[:too_long],
511
514
  :too_short => ActiveRecord::Errors.default_error_messages[:too_short],
512
- :wrong_length => ActiveRecord::Errors.default_error_messages[:wrong_length]
515
+ :wrong_length => ActiveRecord::Errors.default_error_messages[:wrong_length],
516
+ :tokenizer => lambda {|value| value.split(//)}
513
517
  }.merge(DEFAULT_VALIDATION_OPTIONS)
514
518
  options.update(attrs.extract_options!.symbolize_keys)
515
519
 
@@ -536,7 +540,7 @@ module ActiveRecord
536
540
  too_long = options[:too_long] % option_value.end
537
541
 
538
542
  validates_each(attrs, options) do |record, attr, value|
539
- value = value.split(//) if value.kind_of?(String)
543
+ value = options[:tokenizer].call(value) if value.kind_of?(String)
540
544
  if value.nil? or value.size < option_value.begin
541
545
  record.errors.add(attr, too_short)
542
546
  elsif value.size > option_value.end
@@ -553,7 +557,7 @@ module ActiveRecord
553
557
  message = (options[:message] || options[message_options[option]]) % option_value
554
558
 
555
559
  validates_each(attrs, options) do |record, attr, value|
556
- value = value.split(//) if value.kind_of?(String)
560
+ value = options[:tokenizer].call(value) if value.kind_of?(String)
557
561
  record.errors.add(attr, message) unless !value.nil? and value.size.method(validity_checks[option])[option_value]
558
562
  end
559
563
  end
@@ -614,14 +618,20 @@ module ActiveRecord
614
618
  # class (which has a database table to query from).
615
619
  finder_class = class_hierarchy.detect { |klass| !klass.abstract_class? }
616
620
 
617
- if value.nil? || (configuration[:case_sensitive] || !finder_class.columns_hash[attr_name.to_s].text?)
621
+ is_text_column = finder_class.columns_hash[attr_name.to_s].text?
622
+
623
+ if !value.nil? && is_text_column
624
+ value = value.to_s
625
+ end
626
+
627
+ if value.nil? || (configuration[:case_sensitive] || !is_text_column)
618
628
  condition_sql = "#{record.class.quoted_table_name}.#{attr_name} #{attribute_condition(value)}"
619
629
  condition_params = [value]
620
630
  else
621
631
  # sqlite has case sensitive SELECT query, while MySQL/Postgresql don't.
622
632
  # Hence, this is needed only for sqlite.
623
633
  condition_sql = "LOWER(#{record.class.quoted_table_name}.#{attr_name}) #{attribute_condition(value)}"
624
- condition_params = [value.downcase]
634
+ condition_params = [value.chars.downcase]
625
635
  end
626
636
 
627
637
  if scope = configuration[:scope]
@@ -851,7 +861,7 @@ module ActiveRecord
851
861
  raw_value = raw_value.to_i
852
862
  else
853
863
  begin
854
- raw_value = Kernel.Float(raw_value.to_s)
864
+ raw_value = Kernel.Float(raw_value)
855
865
  rescue ArgumentError, TypeError
856
866
  record.errors.add(attr_name, configuration[:message] || ActiveRecord::Errors.default_error_messages[:not_a_number])
857
867
  next