activerecord 2.0.5 → 2.1.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 +168 -6
- data/README +27 -22
- data/RUNNING_UNIT_TESTS +7 -4
- data/Rakefile +22 -25
- data/lib/active_record.rb +8 -2
- data/lib/active_record/aggregations.rb +21 -12
- data/lib/active_record/association_preload.rb +277 -0
- data/lib/active_record/associations.rb +481 -295
- data/lib/active_record/associations/association_collection.rb +162 -37
- data/lib/active_record/associations/association_proxy.rb +71 -7
- data/lib/active_record/associations/belongs_to_association.rb +5 -3
- data/lib/active_record/associations/belongs_to_polymorphic_association.rb +5 -6
- data/lib/active_record/associations/has_and_belongs_to_many_association.rb +12 -64
- data/lib/active_record/associations/has_many_association.rb +8 -73
- data/lib/active_record/associations/has_many_through_association.rb +68 -117
- data/lib/active_record/associations/has_one_association.rb +7 -5
- data/lib/active_record/associations/has_one_through_association.rb +28 -0
- data/lib/active_record/attribute_methods.rb +69 -19
- data/lib/active_record/base.rb +496 -275
- data/lib/active_record/calculations.rb +28 -21
- data/lib/active_record/callbacks.rb +9 -38
- data/lib/active_record/connection_adapters/abstract/connection_specification.rb +3 -2
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +2 -2
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +6 -0
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +232 -45
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +141 -27
- data/lib/active_record/connection_adapters/abstract_adapter.rb +9 -13
- data/lib/active_record/connection_adapters/mysql_adapter.rb +57 -24
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +143 -42
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +1 -1
- data/lib/active_record/connection_adapters/sqlite_adapter.rb +18 -10
- data/lib/active_record/dirty.rb +158 -0
- data/lib/active_record/fixtures.rb +121 -156
- data/lib/active_record/locking/optimistic.rb +14 -11
- data/lib/active_record/locking/pessimistic.rb +2 -2
- data/lib/active_record/migration.rb +157 -77
- data/lib/active_record/named_scope.rb +163 -0
- data/lib/active_record/observer.rb +19 -5
- data/lib/active_record/reflection.rb +34 -14
- data/lib/active_record/schema.rb +7 -14
- data/lib/active_record/schema_dumper.rb +4 -4
- data/lib/active_record/serialization.rb +5 -5
- data/lib/active_record/serializers/json_serializer.rb +37 -28
- data/lib/active_record/serializers/xml_serializer.rb +52 -29
- data/lib/active_record/test_case.rb +36 -0
- data/lib/active_record/timestamp.rb +4 -4
- data/lib/active_record/transactions.rb +3 -3
- data/lib/active_record/validations.rb +182 -248
- data/lib/active_record/version.rb +2 -2
- data/test/{fixtures → assets}/example.log +0 -0
- data/test/{fixtures → assets}/flowers.jpg +0 -0
- data/test/cases/aaa_create_tables_test.rb +24 -0
- data/test/cases/active_schema_test_mysql.rb +95 -0
- data/test/cases/active_schema_test_postgresql.rb +24 -0
- data/test/{adapter_test.rb → cases/adapter_test.rb} +15 -14
- data/test/{adapter_test_sqlserver.rb → cases/adapter_test_sqlserver.rb} +95 -95
- data/test/{aggregations_test.rb → cases/aggregations_test.rb} +20 -20
- data/test/{ar_schema_test.rb → cases/ar_schema_test.rb} +6 -6
- data/test/cases/associations/belongs_to_associations_test.rb +412 -0
- data/test/{associations → cases/associations}/callbacks_test.rb +24 -10
- data/test/{associations → cases/associations}/cascaded_eager_loading_test.rb +18 -17
- data/test/cases/associations/eager_load_nested_include_test.rb +83 -0
- data/test/{associations → cases/associations}/eager_singularization_test.rb +5 -5
- data/test/{associations → cases/associations}/eager_test.rb +216 -51
- data/test/{associations → cases/associations}/extension_test.rb +8 -8
- data/test/cases/associations/has_and_belongs_to_many_associations_test.rb +684 -0
- data/test/cases/associations/has_many_associations_test.rb +932 -0
- data/test/cases/associations/has_many_through_associations_test.rb +190 -0
- data/test/cases/associations/has_one_associations_test.rb +323 -0
- data/test/cases/associations/has_one_through_associations_test.rb +74 -0
- data/test/{associations → cases/associations}/inner_join_association_test.rb +20 -20
- data/test/{associations → cases/associations}/join_model_test.rb +175 -35
- data/test/cases/associations_test.rb +262 -0
- data/test/{attribute_methods_test.rb → cases/attribute_methods_test.rb} +103 -11
- data/test/{base_test.rb → cases/base_test.rb} +338 -191
- data/test/{binary_test.rb → cases/binary_test.rb} +6 -4
- data/test/{calculations_test.rb → cases/calculations_test.rb} +35 -23
- data/test/{callbacks_test.rb → cases/callbacks_test.rb} +7 -7
- data/test/{class_inheritable_attributes_test.rb → cases/class_inheritable_attributes_test.rb} +3 -3
- data/test/{column_alias_test.rb → cases/column_alias_test.rb} +3 -3
- data/test/{connection_test_firebird.rb → cases/connection_test_firebird.rb} +2 -2
- data/test/{connection_test_mysql.rb → cases/connection_test_mysql.rb} +2 -2
- data/test/{copy_table_test_sqlite.rb → cases/copy_table_test_sqlite.rb} +13 -13
- data/test/{datatype_test_postgresql.rb → cases/datatype_test_postgresql.rb} +8 -8
- data/test/{date_time_test.rb → cases/date_time_test.rb} +5 -5
- data/test/{default_test_firebird.rb → cases/default_test_firebird.rb} +3 -3
- data/test/{defaults_test.rb → cases/defaults_test.rb} +8 -6
- data/test/{deprecated_finder_test.rb → cases/deprecated_finder_test.rb} +3 -3
- data/test/cases/dirty_test.rb +163 -0
- data/test/cases/finder_respond_to_test.rb +76 -0
- data/test/{finder_test.rb → cases/finder_test.rb} +266 -33
- data/test/{fixtures_test.rb → cases/fixtures_test.rb} +88 -72
- data/test/cases/helper.rb +47 -0
- data/test/{inheritance_test.rb → cases/inheritance_test.rb} +61 -17
- data/test/cases/invalid_date_test.rb +24 -0
- data/test/{json_serialization_test.rb → cases/json_serialization_test.rb} +36 -11
- data/test/{lifecycle_test.rb → cases/lifecycle_test.rb} +16 -13
- data/test/{locking_test.rb → cases/locking_test.rb} +17 -10
- data/test/{method_scoping_test.rb → cases/method_scoping_test.rb} +75 -39
- data/test/{migration_test.rb → cases/migration_test.rb} +420 -80
- data/test/{migration_test_firebird.rb → cases/migration_test_firebird.rb} +3 -3
- data/test/{mixin_test.rb → cases/mixin_test.rb} +7 -6
- data/test/{modules_test.rb → cases/modules_test.rb} +11 -6
- data/test/{multiple_db_test.rb → cases/multiple_db_test.rb} +5 -5
- data/test/cases/named_scope_test.rb +157 -0
- data/test/{pk_test.rb → cases/pk_test.rb} +10 -10
- data/test/{query_cache_test.rb → cases/query_cache_test.rb} +12 -10
- data/test/{readonly_test.rb → cases/readonly_test.rb} +11 -11
- data/test/{reflection_test.rb → cases/reflection_test.rb} +15 -14
- data/test/{reserved_word_test_mysql.rb → cases/reserved_word_test_mysql.rb} +4 -5
- data/test/{schema_authorization_test_postgresql.rb → cases/schema_authorization_test_postgresql.rb} +5 -5
- data/test/cases/schema_dumper_test.rb +138 -0
- data/test/cases/schema_test_postgresql.rb +102 -0
- data/test/{serialization_test.rb → cases/serialization_test.rb} +7 -7
- data/test/{synonym_test_oracle.rb → cases/synonym_test_oracle.rb} +5 -5
- data/test/{table_name_test_sqlserver.rb → cases/table_name_test_sqlserver.rb} +3 -3
- data/test/{threaded_connections_test.rb → cases/threaded_connections_test.rb} +7 -7
- data/test/{transactions_test.rb → cases/transactions_test.rb} +31 -5
- data/test/{unconnected_test.rb → cases/unconnected_test.rb} +2 -2
- data/test/{validations_test.rb → cases/validations_test.rb} +141 -39
- data/test/{xml_serialization_test.rb → cases/xml_serialization_test.rb} +12 -12
- data/test/config.rb +5 -0
- data/test/connections/native_db2/connection.rb +1 -1
- data/test/connections/native_firebird/connection.rb +1 -1
- data/test/connections/native_frontbase/connection.rb +1 -1
- data/test/connections/native_mysql/connection.rb +1 -1
- data/test/connections/native_openbase/connection.rb +1 -1
- data/test/connections/native_oracle/connection.rb +1 -1
- data/test/connections/native_postgresql/connection.rb +1 -3
- data/test/connections/native_sqlite/connection.rb +2 -2
- data/test/connections/native_sqlite3/connection.rb +2 -2
- data/test/connections/native_sqlite3/in_memory_connection.rb +3 -3
- data/test/connections/native_sybase/connection.rb +1 -1
- data/test/fixtures/author_addresses.yml +5 -0
- data/test/fixtures/authors.yml +2 -0
- data/test/fixtures/clubs.yml +6 -0
- data/test/fixtures/jobs.yml +7 -0
- data/test/fixtures/members.yml +4 -0
- data/test/fixtures/memberships.yml +20 -0
- data/test/fixtures/owners.yml +7 -0
- data/test/fixtures/people.yml +4 -1
- data/test/fixtures/pets.yml +14 -0
- data/test/fixtures/posts.yml +1 -0
- data/test/fixtures/price_estimates.yml +7 -0
- data/test/fixtures/readers.yml +5 -0
- data/test/fixtures/references.yml +17 -0
- data/test/fixtures/sponsors.yml +9 -0
- data/test/fixtures/subscribers.yml +7 -0
- data/test/fixtures/subscriptions.yml +12 -0
- data/test/fixtures/taggings.yml +4 -1
- data/test/fixtures/topics.yml +22 -2
- data/test/fixtures/warehouse-things.yml +3 -0
- data/test/{fixtures/migrations_with_decimal → migrations/decimal}/1_give_me_big_numbers.rb +0 -0
- data/test/{fixtures/migrations_with_duplicate → migrations/duplicate}/1_people_have_last_names.rb +1 -1
- data/test/{fixtures/migrations_with_duplicate → migrations/duplicate}/2_we_need_reminders.rb +1 -1
- data/test/{fixtures/migrations_with_duplicate → migrations/duplicate}/3_foo.rb +0 -0
- data/test/{fixtures/migrations → migrations/duplicate}/3_innocent_jointable.rb +0 -0
- data/test/migrations/duplicate_names/20080507052938_chunky.rb +7 -0
- data/test/migrations/duplicate_names/20080507053028_chunky.rb +7 -0
- data/test/{fixtures/migrations_with_duplicate → migrations/interleaved/pass_1}/3_innocent_jointable.rb +0 -0
- data/test/{fixtures/migrations → migrations/interleaved/pass_2}/1_people_have_last_names.rb +1 -1
- data/test/{fixtures/migrations_with_missing_versions/4_innocent_jointable.rb → migrations/interleaved/pass_2/3_innocent_jointable.rb} +0 -0
- data/test/{fixtures/migrations_with_missing_versions → migrations/interleaved/pass_3}/1_people_have_last_names.rb +1 -1
- data/test/migrations/interleaved/pass_3/2_i_raise_on_down.rb +8 -0
- data/test/migrations/interleaved/pass_3/3_innocent_jointable.rb +12 -0
- data/test/{fixtures/migrations_with_missing_versions → migrations/missing}/1000_people_have_middle_names.rb +1 -1
- data/test/migrations/missing/1_people_have_last_names.rb +9 -0
- data/test/{fixtures/migrations_with_missing_versions → migrations/missing}/3_we_need_reminders.rb +1 -1
- data/test/migrations/missing/4_innocent_jointable.rb +12 -0
- data/test/migrations/valid/1_people_have_last_names.rb +9 -0
- data/test/{fixtures/migrations → migrations/valid}/2_we_need_reminders.rb +1 -1
- data/test/migrations/valid/3_innocent_jointable.rb +12 -0
- data/test/{fixtures → models}/author.rb +28 -4
- data/test/{fixtures → models}/auto_id.rb +0 -0
- data/test/{fixtures → models}/binary.rb +0 -0
- data/test/{fixtures → models}/book.rb +0 -0
- data/test/{fixtures → models}/categorization.rb +0 -0
- data/test/{fixtures → models}/category.rb +8 -5
- data/test/{fixtures → models}/citation.rb +0 -0
- data/test/models/club.rb +7 -0
- data/test/{fixtures → models}/column_name.rb +0 -0
- data/test/{fixtures → models}/comment.rb +5 -3
- data/test/{fixtures → models}/company.rb +15 -6
- data/test/{fixtures → models}/company_in_module.rb +5 -3
- data/test/{fixtures → models}/computer.rb +0 -1
- data/test/{fixtures → models}/contact.rb +1 -1
- data/test/{fixtures → models}/course.rb +0 -0
- data/test/{fixtures → models}/customer.rb +8 -8
- data/test/{fixtures → models}/default.rb +0 -0
- data/test/{fixtures → models}/developer.rb +14 -10
- data/test/{fixtures → models}/edge.rb +0 -0
- data/test/{fixtures → models}/entrant.rb +0 -0
- data/test/models/guid.rb +2 -0
- data/test/{fixtures → models}/item.rb +0 -0
- data/test/models/job.rb +5 -0
- data/test/{fixtures → models}/joke.rb +0 -0
- data/test/{fixtures → models}/keyboard.rb +0 -0
- data/test/{fixtures → models}/legacy_thing.rb +0 -0
- data/test/{fixtures → models}/matey.rb +0 -0
- data/test/models/member.rb +9 -0
- data/test/models/membership.rb +9 -0
- data/test/{fixtures → models}/minimalistic.rb +0 -0
- data/test/{fixtures → models}/mixed_case_monkey.rb +0 -0
- data/test/{fixtures → models}/movie.rb +0 -0
- data/test/{fixtures → models}/order.rb +2 -2
- data/test/models/owner.rb +4 -0
- data/test/{fixtures → models}/parrot.rb +0 -0
- data/test/models/person.rb +10 -0
- data/test/models/pet.rb +4 -0
- data/test/models/pirate.rb +9 -0
- data/test/{fixtures → models}/post.rb +23 -2
- data/test/models/price_estimate.rb +3 -0
- data/test/{fixtures → models}/project.rb +1 -0
- data/test/{fixtures → models}/reader.rb +0 -0
- data/test/models/reference.rb +4 -0
- data/test/{fixtures → models}/reply.rb +7 -5
- data/test/{fixtures → models}/ship.rb +0 -0
- data/test/models/sponsor.rb +4 -0
- data/test/{fixtures → models}/subject.rb +0 -0
- data/test/{fixtures → models}/subscriber.rb +2 -0
- data/test/models/subscription.rb +4 -0
- data/test/{fixtures → models}/tag.rb +0 -0
- data/test/{fixtures → models}/tagging.rb +0 -0
- data/test/{fixtures → models}/task.rb +0 -0
- data/test/{fixtures → models}/topic.rb +32 -4
- data/test/{fixtures → models}/treasure.rb +2 -0
- data/test/{fixtures → models}/vertex.rb +0 -0
- data/test/models/warehouse_thing.rb +5 -0
- data/test/schema/mysql_specific_schema.rb +12 -0
- data/test/schema/postgresql_specific_schema.rb +103 -0
- data/test/schema/schema.rb +421 -0
- data/test/schema/schema2.rb +6 -0
- data/test/schema/sqlite_specific_schema.rb +25 -0
- data/test/schema/sqlserver_specific_schema.rb +5 -0
- metadata +192 -176
- data/test/aaa_create_tables_test.rb +0 -72
- data/test/abstract_unit.rb +0 -84
- data/test/active_schema_test_mysql.rb +0 -46
- data/test/all.sh +0 -8
- data/test/association_inheritance_reload.rb +0 -14
- data/test/associations_test.rb +0 -2177
- data/test/fixtures/bad_fixtures/attr_with_numeric_first_char +0 -1
- data/test/fixtures/bad_fixtures/attr_with_spaces +0 -1
- data/test/fixtures/bad_fixtures/blank_line +0 -3
- data/test/fixtures/bad_fixtures/duplicate_attributes +0 -3
- data/test/fixtures/bad_fixtures/missing_value +0 -1
- data/test/fixtures/db_definitions/db2.drop.sql +0 -33
- data/test/fixtures/db_definitions/db2.sql +0 -235
- data/test/fixtures/db_definitions/db22.drop.sql +0 -2
- data/test/fixtures/db_definitions/db22.sql +0 -5
- data/test/fixtures/db_definitions/firebird.drop.sql +0 -65
- data/test/fixtures/db_definitions/firebird.sql +0 -310
- data/test/fixtures/db_definitions/firebird2.drop.sql +0 -2
- data/test/fixtures/db_definitions/firebird2.sql +0 -6
- data/test/fixtures/db_definitions/frontbase.drop.sql +0 -33
- data/test/fixtures/db_definitions/frontbase.sql +0 -273
- data/test/fixtures/db_definitions/frontbase2.drop.sql +0 -1
- data/test/fixtures/db_definitions/frontbase2.sql +0 -4
- data/test/fixtures/db_definitions/openbase.drop.sql +0 -2
- data/test/fixtures/db_definitions/openbase.sql +0 -318
- data/test/fixtures/db_definitions/openbase2.drop.sql +0 -2
- data/test/fixtures/db_definitions/openbase2.sql +0 -7
- data/test/fixtures/db_definitions/oracle.drop.sql +0 -67
- data/test/fixtures/db_definitions/oracle.sql +0 -330
- data/test/fixtures/db_definitions/oracle2.drop.sql +0 -2
- data/test/fixtures/db_definitions/oracle2.sql +0 -6
- data/test/fixtures/db_definitions/postgresql.drop.sql +0 -44
- data/test/fixtures/db_definitions/postgresql.sql +0 -292
- data/test/fixtures/db_definitions/postgresql2.drop.sql +0 -2
- data/test/fixtures/db_definitions/postgresql2.sql +0 -4
- data/test/fixtures/db_definitions/schema.rb +0 -354
- data/test/fixtures/db_definitions/schema2.rb +0 -11
- data/test/fixtures/db_definitions/sqlite.drop.sql +0 -33
- data/test/fixtures/db_definitions/sqlite.sql +0 -219
- data/test/fixtures/db_definitions/sqlite2.drop.sql +0 -2
- data/test/fixtures/db_definitions/sqlite2.sql +0 -5
- data/test/fixtures/db_definitions/sybase.drop.sql +0 -35
- data/test/fixtures/db_definitions/sybase.sql +0 -222
- data/test/fixtures/db_definitions/sybase2.drop.sql +0 -4
- data/test/fixtures/db_definitions/sybase2.sql +0 -5
- data/test/fixtures/developers_projects/david_action_controller +0 -3
- data/test/fixtures/developers_projects/david_active_record +0 -3
- data/test/fixtures/developers_projects/jamis_active_record +0 -2
- data/test/fixtures/person.rb +0 -4
- data/test/fixtures/pirate.rb +0 -5
- data/test/fixtures/subscribers/first +0 -2
- data/test/fixtures/subscribers/second +0 -2
- data/test/schema_dumper_test.rb +0 -131
- data/test/schema_test_postgresql.rb +0 -64
@@ -1,11 +1,25 @@
|
|
1
1
|
require 'active_record/connection_adapters/abstract_adapter'
|
2
2
|
|
3
|
+
begin
|
4
|
+
require_library_or_gem 'pg'
|
5
|
+
rescue LoadError => e
|
6
|
+
begin
|
7
|
+
require_library_or_gem 'postgres'
|
8
|
+
class PGresult
|
9
|
+
alias_method :nfields, :num_fields unless self.method_defined?(:nfields)
|
10
|
+
alias_method :ntuples, :num_tuples unless self.method_defined?(:ntuples)
|
11
|
+
alias_method :ftype, :type unless self.method_defined?(:ftype)
|
12
|
+
alias_method :cmd_tuples, :cmdtuples unless self.method_defined?(:cmd_tuples)
|
13
|
+
end
|
14
|
+
rescue LoadError
|
15
|
+
raise e
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
3
19
|
module ActiveRecord
|
4
20
|
class Base
|
5
21
|
# Establishes a connection to the database that's used by all Active Record objects
|
6
22
|
def self.postgresql_connection(config) # :nodoc:
|
7
|
-
require_library_or_gem 'postgres' unless self.class.const_defined?(:PGconn)
|
8
|
-
|
9
23
|
config = config.symbolize_keys
|
10
24
|
host = config[:host]
|
11
25
|
port = config[:port] || 5432
|
@@ -214,15 +228,15 @@ module ActiveRecord
|
|
214
228
|
#
|
215
229
|
# Options:
|
216
230
|
#
|
217
|
-
# * <tt>:host</tt>
|
218
|
-
# * <tt>:port</tt>
|
219
|
-
# * <tt>:username</tt>
|
220
|
-
# * <tt>:password</tt>
|
221
|
-
# * <tt>:database</tt>
|
222
|
-
# * <tt>:schema_search_path</tt>
|
223
|
-
# * <tt>:encoding</tt>
|
224
|
-
# * <tt>:min_messages</tt>
|
225
|
-
# * <tt>:allow_concurrency</tt>
|
231
|
+
# * <tt>:host</tt> - Defaults to "localhost".
|
232
|
+
# * <tt>:port</tt> - Defaults to 5432.
|
233
|
+
# * <tt>:username</tt> - Defaults to nothing.
|
234
|
+
# * <tt>:password</tt> - Defaults to nothing.
|
235
|
+
# * <tt>:database</tt> - The name of the database. No default, must be provided.
|
236
|
+
# * <tt>:schema_search_path</tt> - An optional schema search path for the connection given as a string of comma-separated schema names. This is backward-compatible with the <tt>:schema_order</tt> option.
|
237
|
+
# * <tt>:encoding</tt> - An optional client encoding that is used in a <tt>SET client_encoding TO <encoding></tt> call on the connection.
|
238
|
+
# * <tt>:min_messages</tt> - An optional client min messages that is used in a <tt>SET client_min_messages TO <min_messages></tt> call on the connection.
|
239
|
+
# * <tt>:allow_concurrency</tt> - If true, use async query methods so Ruby threads don't deadlock; otherwise, use blocking query methods.
|
226
240
|
class PostgreSQLAdapter < AbstractAdapter
|
227
241
|
# Returns 'PostgreSQL' as adapter name for identification purposes.
|
228
242
|
def adapter_name
|
@@ -300,7 +314,7 @@ module ActiveRecord
|
|
300
314
|
# postgres-pr does not raise an exception when client_min_messages is set higher
|
301
315
|
# than error and "SHOW standard_conforming_strings" fails, but returns an empty
|
302
316
|
# PGresult instead.
|
303
|
-
has_support =
|
317
|
+
has_support = query('SHOW standard_conforming_strings')[0][0] rescue false
|
304
318
|
self.client_min_messages = client_min_messages_old
|
305
319
|
has_support
|
306
320
|
end
|
@@ -346,7 +360,7 @@ module ActiveRecord
|
|
346
360
|
# There are some incorrectly compiled postgres drivers out there
|
347
361
|
# that don't define PGconn.escape.
|
348
362
|
self.class.instance_eval do
|
349
|
-
|
363
|
+
undef_method(:quote_string)
|
350
364
|
end
|
351
365
|
end
|
352
366
|
quote_string(s)
|
@@ -369,11 +383,22 @@ module ActiveRecord
|
|
369
383
|
|
370
384
|
# REFERENTIAL INTEGRITY ====================================
|
371
385
|
|
386
|
+
def supports_disable_referential_integrity?() #:nodoc:
|
387
|
+
version = query("SHOW server_version")[0][0].split('.')
|
388
|
+
(version[0].to_i >= 8 && version[1].to_i >= 1) ? true : false
|
389
|
+
rescue
|
390
|
+
return false
|
391
|
+
end
|
392
|
+
|
372
393
|
def disable_referential_integrity(&block) #:nodoc:
|
373
|
-
|
394
|
+
if supports_disable_referential_integrity?() then
|
395
|
+
execute(tables.collect { |name| "ALTER TABLE #{quote_table_name(name)} DISABLE TRIGGER ALL" }.join(";"))
|
396
|
+
end
|
374
397
|
yield
|
375
398
|
ensure
|
376
|
-
|
399
|
+
if supports_disable_referential_integrity?() then
|
400
|
+
execute(tables.collect { |name| "ALTER TABLE #{quote_table_name(name)} ENABLE TRIGGER ALL" }.join(";"))
|
401
|
+
end
|
377
402
|
end
|
378
403
|
|
379
404
|
# DATABASE STATEMENTS ======================================
|
@@ -390,14 +415,28 @@ module ActiveRecord
|
|
390
415
|
super || pk && last_insert_id(table, sequence_name || default_sequence_name(table, pk))
|
391
416
|
end
|
392
417
|
|
393
|
-
#
|
418
|
+
# create a 2D array representing the result set
|
419
|
+
def result_as_array(res) #:nodoc:
|
420
|
+
ary = []
|
421
|
+
for i in 0...res.ntuples do
|
422
|
+
ary << []
|
423
|
+
for j in 0...res.nfields do
|
424
|
+
ary[i] << res.getvalue(i,j)
|
425
|
+
end
|
426
|
+
end
|
427
|
+
return ary
|
428
|
+
end
|
429
|
+
|
430
|
+
|
431
|
+
# Queries the database and returns the results in an Array-like object
|
394
432
|
def query(sql, name = nil) #:nodoc:
|
395
433
|
log(sql, name) do
|
396
434
|
if @async
|
397
|
-
@connection.
|
435
|
+
res = @connection.async_exec(sql)
|
398
436
|
else
|
399
|
-
@connection.
|
437
|
+
res = @connection.exec(sql)
|
400
438
|
end
|
439
|
+
return result_as_array(res)
|
401
440
|
end
|
402
441
|
end
|
403
442
|
|
@@ -415,7 +454,7 @@ module ActiveRecord
|
|
415
454
|
|
416
455
|
# Executes an UPDATE query and returns the number of affected tuples.
|
417
456
|
def update_sql(sql, name = nil)
|
418
|
-
super.
|
457
|
+
super.cmd_tuples
|
419
458
|
end
|
420
459
|
|
421
460
|
# Begins a transaction.
|
@@ -435,6 +474,50 @@ module ActiveRecord
|
|
435
474
|
|
436
475
|
# SCHEMA STATEMENTS ========================================
|
437
476
|
|
477
|
+
def recreate_database(name) #:nodoc:
|
478
|
+
drop_database(name)
|
479
|
+
create_database(name)
|
480
|
+
end
|
481
|
+
|
482
|
+
# Create a new PostgreSQL database. Options include <tt>:owner</tt>, <tt>:template</tt>,
|
483
|
+
# <tt>:encoding</tt>, <tt>:tablespace</tt>, and <tt>:connection_limit</tt> (note that MySQL uses
|
484
|
+
# <tt>:charset</tt> while PostgreSQL uses <tt>:encoding</tt>).
|
485
|
+
#
|
486
|
+
# Example:
|
487
|
+
# create_database config[:database], config
|
488
|
+
# create_database 'foo_development', :encoding => 'unicode'
|
489
|
+
def create_database(name, options = {})
|
490
|
+
options = options.reverse_merge(:encoding => "utf8")
|
491
|
+
|
492
|
+
option_string = options.symbolize_keys.sum do |key, value|
|
493
|
+
case key
|
494
|
+
when :owner
|
495
|
+
" OWNER = '#{value}'"
|
496
|
+
when :template
|
497
|
+
" TEMPLATE = #{value}"
|
498
|
+
when :encoding
|
499
|
+
" ENCODING = '#{value}'"
|
500
|
+
when :tablespace
|
501
|
+
" TABLESPACE = #{value}"
|
502
|
+
when :connection_limit
|
503
|
+
" CONNECTION LIMIT = #{value}"
|
504
|
+
else
|
505
|
+
""
|
506
|
+
end
|
507
|
+
end
|
508
|
+
|
509
|
+
execute "CREATE DATABASE #{name}#{option_string}"
|
510
|
+
end
|
511
|
+
|
512
|
+
# Drops a PostgreSQL database
|
513
|
+
#
|
514
|
+
# Example:
|
515
|
+
# drop_database 'matt_development'
|
516
|
+
def drop_database(name) #:nodoc:
|
517
|
+
execute "DROP DATABASE IF EXISTS #{name}"
|
518
|
+
end
|
519
|
+
|
520
|
+
|
438
521
|
# Returns the list of all tables in the schema search path or a specified schema.
|
439
522
|
def tables(name = nil)
|
440
523
|
schemas = schema_search_path.split(/,/).map { |p| quote(p) }.join(',')
|
@@ -447,14 +530,16 @@ module ActiveRecord
|
|
447
530
|
|
448
531
|
# Returns the list of all indexes for a table.
|
449
532
|
def indexes(table_name, name = nil)
|
450
|
-
|
451
|
-
|
452
|
-
|
533
|
+
schemas = schema_search_path.split(/,/).map { |p| quote(p) }.join(',')
|
534
|
+
result = query(<<-SQL, name)
|
535
|
+
SELECT distinct i.relname, d.indisunique, a.attname
|
536
|
+
FROM pg_class t, pg_class i, pg_index d, pg_attribute a
|
453
537
|
WHERE i.relkind = 'i'
|
454
538
|
AND d.indexrelid = i.oid
|
455
539
|
AND d.indisprimary = 'f'
|
456
540
|
AND t.oid = d.indrelid
|
457
541
|
AND t.relname = '#{table_name}'
|
542
|
+
AND i.relnamespace IN (SELECT oid FROM pg_namespace WHERE nspname IN (#{schemas}) )
|
458
543
|
AND a.attrelid = t.oid
|
459
544
|
AND ( d.indkey[0]=a.attnum OR d.indkey[1]=a.attnum
|
460
545
|
OR d.indkey[2]=a.attnum OR d.indkey[3]=a.attnum
|
@@ -529,8 +614,10 @@ module ActiveRecord
|
|
529
614
|
end
|
530
615
|
if pk
|
531
616
|
if sequence
|
617
|
+
quoted_sequence = quote_column_name(sequence)
|
618
|
+
|
532
619
|
select_value <<-end_sql, 'Reset sequence'
|
533
|
-
SELECT setval('#{
|
620
|
+
SELECT setval('#{quoted_sequence}', (SELECT COALESCE(MAX(#{quote_column_name pk})+(SELECT increment_by FROM #{quoted_sequence}), (SELECT min_value FROM #{quoted_sequence})) FROM #{quote_table_name(table)}), false)
|
534
621
|
end_sql
|
535
622
|
else
|
536
623
|
@logger.warn "#{table} has primary key #{pk} with no default sequence" if @logger
|
@@ -564,7 +651,13 @@ module ActiveRecord
|
|
564
651
|
# Support the 7.x and 8.0 nextval('foo'::text) as well as
|
565
652
|
# the 8.1+ nextval('foo'::regclass).
|
566
653
|
result = query(<<-end_sql, 'PK and custom sequence')[0]
|
567
|
-
SELECT attr.attname,
|
654
|
+
SELECT attr.attname,
|
655
|
+
CASE
|
656
|
+
WHEN split_part(def.adsrc, '''', 2) ~ '.' THEN
|
657
|
+
substr(split_part(def.adsrc, '''', 2),
|
658
|
+
strpos(split_part(def.adsrc, '''', 2), '.')+1)
|
659
|
+
ELSE split_part(def.adsrc, '''', 2)
|
660
|
+
END
|
568
661
|
FROM pg_class t
|
569
662
|
JOIN pg_attribute attr ON (t.oid = attrelid)
|
570
663
|
JOIN pg_attrdef def ON (adrelid = attrelid AND adnum = attnum)
|
@@ -574,6 +667,7 @@ module ActiveRecord
|
|
574
667
|
AND def.adsrc ~* 'nextval'
|
575
668
|
end_sql
|
576
669
|
end
|
670
|
+
|
577
671
|
# [primary_key, sequence]
|
578
672
|
[result.first, result.last]
|
579
673
|
rescue
|
@@ -585,13 +679,14 @@ module ActiveRecord
|
|
585
679
|
execute "ALTER TABLE #{name} RENAME TO #{new_name}"
|
586
680
|
end
|
587
681
|
|
588
|
-
# Adds a column to
|
682
|
+
# Adds a new column to the named table.
|
683
|
+
# See TableDefinition#column for details of the options you can use.
|
589
684
|
def add_column(table_name, column_name, type, options = {})
|
590
685
|
default = options[:default]
|
591
686
|
notnull = options[:null] == false
|
592
687
|
|
593
688
|
# Add the column.
|
594
|
-
execute("ALTER TABLE #{table_name} ADD COLUMN #{quote_column_name(column_name)} #{type_to_sql(type, options[:limit])}")
|
689
|
+
execute("ALTER TABLE #{quote_table_name(table_name)} ADD COLUMN #{quote_column_name(column_name)} #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}")
|
595
690
|
|
596
691
|
change_column_default(table_name, column_name, default) if options_include_default?(options)
|
597
692
|
change_column_null(table_name, column_name, false, default) if notnull
|
@@ -599,17 +694,23 @@ module ActiveRecord
|
|
599
694
|
|
600
695
|
# Changes the column of a table.
|
601
696
|
def change_column(table_name, column_name, type, options = {})
|
697
|
+
quoted_table_name = quote_table_name(table_name)
|
698
|
+
|
602
699
|
begin
|
603
|
-
execute "ALTER TABLE #{
|
700
|
+
execute "ALTER TABLE #{quoted_table_name} ALTER COLUMN #{quote_column_name(column_name)} TYPE #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}"
|
604
701
|
rescue ActiveRecord::StatementInvalid
|
605
702
|
# This is PostgreSQL 7.x, so we have to use a more arcane way of doing it.
|
606
|
-
|
607
|
-
|
608
|
-
|
609
|
-
|
610
|
-
|
611
|
-
|
612
|
-
|
703
|
+
begin
|
704
|
+
begin_db_transaction
|
705
|
+
tmp_column_name = "#{column_name}_ar_tmp"
|
706
|
+
add_column(table_name, tmp_column_name, type, options)
|
707
|
+
execute "UPDATE #{quoted_table_name} SET #{quote_column_name(tmp_column_name)} = CAST(#{quote_column_name(column_name)} AS #{type_to_sql(type, options[:limit], options[:precision], options[:scale])})"
|
708
|
+
remove_column(table_name, column_name)
|
709
|
+
rename_column(table_name, tmp_column_name, column_name)
|
710
|
+
commit_db_transaction
|
711
|
+
rescue
|
712
|
+
rollback_db_transaction
|
713
|
+
end
|
613
714
|
end
|
614
715
|
|
615
716
|
change_column_default(table_name, column_name, options[:default]) if options_include_default?(options)
|
@@ -618,19 +719,19 @@ module ActiveRecord
|
|
618
719
|
|
619
720
|
# Changes the default value of a table column.
|
620
721
|
def change_column_default(table_name, column_name, default)
|
621
|
-
execute "ALTER TABLE #{table_name} ALTER COLUMN #{quote_column_name(column_name)} SET DEFAULT #{quote(default)}"
|
722
|
+
execute "ALTER TABLE #{quote_table_name(table_name)} ALTER COLUMN #{quote_column_name(column_name)} SET DEFAULT #{quote(default)}"
|
622
723
|
end
|
623
724
|
|
624
725
|
def change_column_null(table_name, column_name, null, default = nil)
|
625
726
|
unless null || default.nil?
|
626
|
-
execute("UPDATE #{table_name} SET #{quote_column_name(column_name)}=#{quote(default)} WHERE #{quote_column_name(column_name)} IS NULL")
|
727
|
+
execute("UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote(default)} WHERE #{quote_column_name(column_name)} IS NULL")
|
627
728
|
end
|
628
|
-
execute("ALTER TABLE #{table_name} ALTER #{quote_column_name(column_name)} #{null ? 'DROP' : 'SET'} NOT NULL")
|
729
|
+
execute("ALTER TABLE #{quote_table_name(table_name)} ALTER #{quote_column_name(column_name)} #{null ? 'DROP' : 'SET'} NOT NULL")
|
629
730
|
end
|
630
731
|
|
631
732
|
# Renames a column in a table.
|
632
733
|
def rename_column(table_name, column_name, new_column_name)
|
633
|
-
execute "ALTER TABLE #{table_name} RENAME COLUMN #{quote_column_name(column_name)} TO #{quote_column_name(new_column_name)}"
|
734
|
+
execute "ALTER TABLE #{quote_table_name(table_name)} RENAME COLUMN #{quote_column_name(column_name)} TO #{quote_column_name(new_column_name)}"
|
634
735
|
end
|
635
736
|
|
636
737
|
# Drops an index from a table.
|
@@ -675,7 +776,7 @@ module ActiveRecord
|
|
675
776
|
# Returns an ORDER BY clause for the passed order option.
|
676
777
|
#
|
677
778
|
# PostgreSQL does not allow arbitrary ordering when using DISTINCT ON, so we work around this
|
678
|
-
# by wrapping the sql as a sub-select and ordering in that query.
|
779
|
+
# by wrapping the +sql+ string as a sub-select and ordering in that query.
|
679
780
|
def add_order_by_for_association_limiting!(sql, options) #:nodoc:
|
680
781
|
return sql if options[:order].blank?
|
681
782
|
|
@@ -780,10 +881,10 @@ module ActiveRecord
|
|
780
881
|
|
781
882
|
def select_raw(sql, name = nil)
|
782
883
|
res = execute(sql, name)
|
783
|
-
results = res
|
884
|
+
results = result_as_array(res)
|
784
885
|
fields = []
|
785
886
|
rows = []
|
786
|
-
if
|
887
|
+
if res.ntuples > 0
|
787
888
|
fields = res.fields
|
788
889
|
results.each do |row|
|
789
890
|
hashed_row = {}
|
@@ -792,7 +893,7 @@ module ActiveRecord
|
|
792
893
|
# then strip them off. Indeed it would be prettier to do this in
|
793
894
|
# PostgreSQLColumn.string_to_decimal but would break form input
|
794
895
|
# fields that call value_before_type_cast.
|
795
|
-
if res.
|
896
|
+
if res.ftype(cell_index) == MONEY_COLUMN_TYPE_OID
|
796
897
|
# Because money output is formatted according to the locale, there are two
|
797
898
|
# cases to consider (note the decimal separators):
|
798
899
|
# (1) $12,345,678.12
|
@@ -25,7 +25,7 @@ module ActiveRecord
|
|
25
25
|
module ConnectionAdapters #:nodoc:
|
26
26
|
class SQLite3Adapter < SQLiteAdapter # :nodoc:
|
27
27
|
def table_structure(table_name)
|
28
|
-
returning structure = @connection.table_info(table_name) do
|
28
|
+
returning structure = @connection.table_info(quote_table_name(table_name)) do
|
29
29
|
raise(ActiveRecord::StatementInvalid, "Could not find table '#{table_name}'") if structure.empty?
|
30
30
|
end
|
31
31
|
end
|
@@ -70,7 +70,7 @@ module ActiveRecord
|
|
70
70
|
#
|
71
71
|
# Options:
|
72
72
|
#
|
73
|
-
# * <tt>:database</tt>
|
73
|
+
# * <tt>:database</tt> - Path to the database file.
|
74
74
|
class SQLiteAdapter < AbstractAdapter
|
75
75
|
def adapter_name #:nodoc:
|
76
76
|
'SQLite'
|
@@ -107,7 +107,7 @@ module ActiveRecord
|
|
107
107
|
:decimal => { :name => "decimal" },
|
108
108
|
:datetime => { :name => "datetime" },
|
109
109
|
:timestamp => { :name => "datetime" },
|
110
|
-
:time => { :name => "
|
110
|
+
:time => { :name => "time" },
|
111
111
|
:date => { :name => "date" },
|
112
112
|
:binary => { :name => "blob" },
|
113
113
|
:boolean => { :name => "boolean" }
|
@@ -192,7 +192,7 @@ module ActiveRecord
|
|
192
192
|
end
|
193
193
|
|
194
194
|
def indexes(table_name, name = nil) #:nodoc:
|
195
|
-
execute("PRAGMA index_list(#{table_name})", name).map do |row|
|
195
|
+
execute("PRAGMA index_list(#{quote_table_name(table_name)})", name).map do |row|
|
196
196
|
index = IndexDefinition.new(table_name, row['name'])
|
197
197
|
index.unique = row['unique'] != '0'
|
198
198
|
index.columns = execute("PRAGMA index_info('#{index.name}')").map { |col| col['name'] }
|
@@ -214,16 +214,23 @@ module ActiveRecord
|
|
214
214
|
end
|
215
215
|
|
216
216
|
def add_column(table_name, column_name, type, options = {}) #:nodoc:
|
217
|
+
if @connection.respond_to?(:transaction_active?) && @connection.transaction_active?
|
218
|
+
raise StatementInvalid, 'Cannot add columns to a SQLite database while inside a transaction'
|
219
|
+
end
|
220
|
+
|
217
221
|
super(table_name, column_name, type, options)
|
218
222
|
# See last paragraph on http://www.sqlite.org/lang_altertable.html
|
219
223
|
execute "VACUUM"
|
220
224
|
end
|
221
225
|
|
222
|
-
def remove_column(table_name,
|
223
|
-
|
224
|
-
|
226
|
+
def remove_column(table_name, *column_names) #:nodoc:
|
227
|
+
column_names.flatten.each do |column_name|
|
228
|
+
alter_table(table_name) do |definition|
|
229
|
+
definition.columns.delete(definition[column_name])
|
230
|
+
end
|
225
231
|
end
|
226
232
|
end
|
233
|
+
alias :remove_columns :remove_column
|
227
234
|
|
228
235
|
def change_column_default(table_name, column_name, default) #:nodoc:
|
229
236
|
alter_table(table_name) do |definition|
|
@@ -257,7 +264,7 @@ module ActiveRecord
|
|
257
264
|
record = {}
|
258
265
|
row.each_key do |key|
|
259
266
|
if key.is_a?(String)
|
260
|
-
record[key.sub(
|
267
|
+
record[key.sub(/^"?\w+"?\./, '')] = row[key]
|
261
268
|
end
|
262
269
|
end
|
263
270
|
record
|
@@ -265,7 +272,7 @@ module ActiveRecord
|
|
265
272
|
end
|
266
273
|
|
267
274
|
def table_structure(table_name)
|
268
|
-
returning structure = execute("PRAGMA table_info(#{table_name})") do
|
275
|
+
returning structure = execute("PRAGMA table_info(#{quote_table_name(table_name)})") do
|
269
276
|
raise(ActiveRecord::StatementInvalid, "Could not find table '#{table_name}'") if structure.empty?
|
270
277
|
end
|
271
278
|
end
|
@@ -340,8 +347,9 @@ module ActiveRecord
|
|
340
347
|
columns = columns.find_all{|col| from_columns.include?(column_mappings[col])}
|
341
348
|
quoted_columns = columns.map { |col| quote_column_name(col) } * ','
|
342
349
|
|
343
|
-
|
344
|
-
|
350
|
+
quoted_to = quote_table_name(to)
|
351
|
+
@connection.execute "SELECT * FROM #{quote_table_name(from)}" do |row|
|
352
|
+
sql = "INSERT INTO #{quoted_to} (#{quoted_columns}) VALUES ("
|
345
353
|
sql << columns.map {|col| quote row[column_mappings[col]]} * ', '
|
346
354
|
sql << ')'
|
347
355
|
@connection.execute sql
|
@@ -0,0 +1,158 @@
|
|
1
|
+
module ActiveRecord
|
2
|
+
# Track unsaved attribute changes.
|
3
|
+
#
|
4
|
+
# A newly instantiated object is unchanged:
|
5
|
+
# person = Person.find_by_name('uncle bob')
|
6
|
+
# person.changed? # => false
|
7
|
+
#
|
8
|
+
# Change the name:
|
9
|
+
# person.name = 'Bob'
|
10
|
+
# person.changed? # => true
|
11
|
+
# person.name_changed? # => true
|
12
|
+
# person.name_was # => 'uncle bob'
|
13
|
+
# person.name_change # => ['uncle bob', 'Bob']
|
14
|
+
# person.name = 'Bill'
|
15
|
+
# person.name_change # => ['uncle bob', 'Bill']
|
16
|
+
#
|
17
|
+
# Save the changes:
|
18
|
+
# person.save
|
19
|
+
# person.changed? # => false
|
20
|
+
# person.name_changed? # => false
|
21
|
+
#
|
22
|
+
# Assigning the same value leaves the attribute unchanged:
|
23
|
+
# person.name = 'Bill'
|
24
|
+
# person.name_changed? # => false
|
25
|
+
# person.name_change # => nil
|
26
|
+
#
|
27
|
+
# Which attributes have changed?
|
28
|
+
# person.name = 'bob'
|
29
|
+
# person.changed # => ['name']
|
30
|
+
# person.changes # => { 'name' => ['Bill', 'bob'] }
|
31
|
+
#
|
32
|
+
# Before modifying an attribute in-place:
|
33
|
+
# person.name_will_change!
|
34
|
+
# person.name << 'by'
|
35
|
+
# person.name_change # => ['uncle bob', 'uncle bobby']
|
36
|
+
module Dirty
|
37
|
+
def self.included(base)
|
38
|
+
base.attribute_method_suffix '_changed?', '_change', '_will_change!', '_was'
|
39
|
+
base.alias_method_chain :write_attribute, :dirty
|
40
|
+
base.alias_method_chain :save, :dirty
|
41
|
+
base.alias_method_chain :save!, :dirty
|
42
|
+
base.alias_method_chain :update, :dirty
|
43
|
+
base.alias_method_chain :reload, :dirty
|
44
|
+
|
45
|
+
base.superclass_delegating_accessor :partial_updates
|
46
|
+
base.partial_updates = true
|
47
|
+
end
|
48
|
+
|
49
|
+
# Do any attributes have unsaved changes?
|
50
|
+
# person.changed? # => false
|
51
|
+
# person.name = 'bob'
|
52
|
+
# person.changed? # => true
|
53
|
+
def changed?
|
54
|
+
!changed_attributes.empty?
|
55
|
+
end
|
56
|
+
|
57
|
+
# List of attributes with unsaved changes.
|
58
|
+
# person.changed # => []
|
59
|
+
# person.name = 'bob'
|
60
|
+
# person.changed # => ['name']
|
61
|
+
def changed
|
62
|
+
changed_attributes.keys
|
63
|
+
end
|
64
|
+
|
65
|
+
# Map of changed attrs => [original value, new value]
|
66
|
+
# person.changes # => {}
|
67
|
+
# person.name = 'bob'
|
68
|
+
# person.changes # => { 'name' => ['bill', 'bob'] }
|
69
|
+
def changes
|
70
|
+
changed.inject({}) { |h, attr| h[attr] = attribute_change(attr); h }
|
71
|
+
end
|
72
|
+
|
73
|
+
# Attempts to +save+ the record and clears changed attributes if successful.
|
74
|
+
def save_with_dirty(*args) #:nodoc:
|
75
|
+
if status = save_without_dirty(*args)
|
76
|
+
changed_attributes.clear
|
77
|
+
end
|
78
|
+
status
|
79
|
+
end
|
80
|
+
|
81
|
+
# Attempts to <tt>save!</tt> the record and clears changed attributes if successful.
|
82
|
+
def save_with_dirty!(*args) #:nodoc:
|
83
|
+
status = save_without_dirty!(*args)
|
84
|
+
changed_attributes.clear
|
85
|
+
status
|
86
|
+
end
|
87
|
+
|
88
|
+
# <tt>reload</tt> the record and clears changed attributes.
|
89
|
+
def reload_with_dirty(*args) #:nodoc:
|
90
|
+
record = reload_without_dirty(*args)
|
91
|
+
changed_attributes.clear
|
92
|
+
record
|
93
|
+
end
|
94
|
+
|
95
|
+
private
|
96
|
+
# Map of change attr => original value.
|
97
|
+
def changed_attributes
|
98
|
+
@changed_attributes ||= {}
|
99
|
+
end
|
100
|
+
|
101
|
+
# Handle *_changed? for method_missing.
|
102
|
+
def attribute_changed?(attr)
|
103
|
+
changed_attributes.include?(attr)
|
104
|
+
end
|
105
|
+
|
106
|
+
# Handle *_change for method_missing.
|
107
|
+
def attribute_change(attr)
|
108
|
+
[changed_attributes[attr], __send__(attr)] if attribute_changed?(attr)
|
109
|
+
end
|
110
|
+
|
111
|
+
# Handle *_was for method_missing.
|
112
|
+
def attribute_was(attr)
|
113
|
+
attribute_changed?(attr) ? changed_attributes[attr] : __send__(attr)
|
114
|
+
end
|
115
|
+
|
116
|
+
# Handle *_will_change! for method_missing.
|
117
|
+
def attribute_will_change!(attr)
|
118
|
+
changed_attributes[attr] = clone_attribute_value(:read_attribute, attr)
|
119
|
+
end
|
120
|
+
|
121
|
+
# Wrap write_attribute to remember original attribute value.
|
122
|
+
def write_attribute_with_dirty(attr, value)
|
123
|
+
attr = attr.to_s
|
124
|
+
|
125
|
+
# The attribute already has an unsaved change.
|
126
|
+
unless changed_attributes.include?(attr)
|
127
|
+
old = clone_attribute_value(:read_attribute, attr)
|
128
|
+
changed_attributes[attr] = old if field_changed?(attr, old, value)
|
129
|
+
end
|
130
|
+
|
131
|
+
# Carry on.
|
132
|
+
write_attribute_without_dirty(attr, value)
|
133
|
+
end
|
134
|
+
|
135
|
+
def update_with_dirty
|
136
|
+
if partial_updates?
|
137
|
+
update_without_dirty(changed)
|
138
|
+
else
|
139
|
+
update_without_dirty
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
def field_changed?(attr, old, value)
|
144
|
+
if column = column_for_attribute(attr)
|
145
|
+
if column.type == :integer && column.null && old.nil?
|
146
|
+
# For nullable integer columns, NULL gets stored in database for blank (i.e. '') values.
|
147
|
+
# Hence we don't record it as a change if the value changes from nil to ''.
|
148
|
+
value = nil if value.blank?
|
149
|
+
else
|
150
|
+
value = column.type_cast(value)
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
old != value
|
155
|
+
end
|
156
|
+
|
157
|
+
end
|
158
|
+
end
|