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.
- data/CHANGELOG +34 -0
- data/README +0 -0
- data/Rakefile +6 -5
- data/lib/active_record.rb +8 -10
- data/lib/active_record/association_preload.rb +17 -12
- data/lib/active_record/associations.rb +45 -27
- data/lib/active_record/associations/association_collection.rb +8 -5
- data/lib/active_record/associations/association_proxy.rb +2 -6
- data/lib/active_record/associations/belongs_to_association.rb +0 -0
- data/lib/active_record/associations/belongs_to_polymorphic_association.rb +0 -0
- data/lib/active_record/associations/has_and_belongs_to_many_association.rb +2 -3
- data/lib/active_record/associations/has_many_association.rb +16 -4
- data/lib/active_record/associations/has_many_through_association.rb +1 -1
- data/lib/active_record/associations/has_one_association.rb +2 -2
- data/lib/active_record/associations/has_one_through_association.rb +4 -0
- data/lib/active_record/base.rb +33 -15
- data/lib/active_record/calculations.rb +20 -7
- data/lib/active_record/callbacks.rb +0 -0
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +9 -6
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +17 -10
- data/lib/active_record/connection_adapters/abstract_adapter.rb +0 -0
- data/lib/active_record/connection_adapters/mysql_adapter.rb +53 -24
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +66 -20
- data/lib/active_record/connection_adapters/sqlite_adapter.rb +12 -0
- data/lib/active_record/dirty.rb +10 -3
- data/lib/active_record/fixtures.rb +0 -0
- data/lib/active_record/locking/optimistic.rb +1 -0
- data/lib/active_record/migration.rb +35 -8
- data/lib/active_record/named_scope.rb +6 -1
- data/lib/active_record/observer.rb +7 -5
- data/lib/active_record/test_case.rb +13 -2
- data/lib/active_record/validations.rb +19 -9
- data/lib/active_record/version.rb +1 -1
- data/test/cases/active_schema_test_postgresql.rb +2 -2
- data/test/cases/adapter_test.rb +1 -1
- data/test/cases/associations/belongs_to_associations_test.rb +19 -0
- data/test/cases/associations/cascaded_eager_loading_test.rb +13 -1
- data/test/cases/associations/eager_load_includes_full_sti_class_test.rb +36 -0
- data/test/cases/associations/eager_test.rb +25 -1
- data/test/cases/associations/has_and_belongs_to_many_associations_test.rb +27 -5
- data/test/cases/associations/has_many_associations_test.rb +106 -4
- data/test/cases/associations/has_many_through_associations_test.rb +10 -0
- data/test/cases/associations/has_one_associations_test.rb +22 -0
- data/test/cases/associations/has_one_through_associations_test.rb +44 -5
- data/test/cases/associations/join_model_test.rb +7 -0
- data/test/cases/associations_test.rb +2 -2
- data/test/cases/attribute_methods_test.rb +10 -10
- data/test/cases/base_test.rb +39 -16
- data/test/cases/calculations_test.rb +53 -1
- data/test/cases/column_definition_test.rb +36 -0
- data/test/cases/database_statements_test.rb +12 -0
- data/test/cases/defaults_test.rb +1 -1
- data/test/cases/deprecated_finder_test.rb +0 -0
- data/test/cases/dirty_test.rb +94 -0
- data/test/cases/finder_test.rb +7 -0
- data/test/cases/fixtures_test.rb +0 -0
- data/test/cases/helper.rb +5 -5
- data/test/cases/inheritance_test.rb +9 -2
- data/test/cases/lifecycle_test.rb +54 -1
- data/test/cases/locking_test.rb +20 -0
- data/test/cases/method_scoping_test.rb +11 -1
- data/test/cases/migration_test.rb +147 -22
- data/test/cases/multiple_db_test.rb +1 -1
- data/test/cases/named_scope_test.rb +50 -1
- data/test/cases/query_cache_test.rb +4 -3
- data/test/cases/readonly_test.rb +0 -0
- data/test/cases/reflection_test.rb +3 -3
- data/test/cases/schema_dumper_test.rb +46 -0
- data/test/cases/unconnected_test.rb +0 -0
- data/test/cases/validations_test.rb +30 -5
- data/test/debug.log +358 -0
- data/test/fixtures/fixture_database.sqlite3 +0 -0
- data/test/fixtures/fixture_database_2.sqlite3 +0 -0
- data/test/models/author.rb +4 -0
- data/test/models/category.rb +1 -0
- data/test/models/company.rb +10 -1
- data/test/models/developer.rb +4 -1
- data/test/models/person.rb +1 -1
- data/test/models/post.rb +6 -1
- data/test/models/project.rb +1 -1
- data/test/models/reply.rb +0 -0
- data/test/models/topic.rb +1 -0
- data/test/schema/mysql_specific_schema.rb +2 -2
- data/test/schema/schema.rb +8 -0
- metadata +11 -5
- 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
|
-
|
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
|
-
|
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
|
-
|
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 =
|
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
|
-
|
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
|
-
|
747
|
-
'
|
748
|
-
|
749
|
-
'
|
750
|
-
|
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
|
|
data/lib/active_record/dirty.rb
CHANGED
@@ -123,7 +123,10 @@ module ActiveRecord
|
|
123
123
|
attr = attr.to_s
|
124
124
|
|
125
125
|
# The attribute already has an unsaved change.
|
126
|
-
|
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
|
-
|
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
|
-
|
374
|
-
|
375
|
-
|
376
|
-
|
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
|
-
|
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.
|
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
|
-
|
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
|
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
|
-
|
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
|
-
$
|
37
|
+
$queries_executed = []
|
27
38
|
yield
|
28
39
|
ensure
|
29
|
-
assert_equal num, $
|
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 =
|
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 =
|
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
|
-
|
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
|
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
|