activerecord 2.3.18 → 3.0.0.beta
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 +105 -34
- data/examples/performance.rb +3 -39
- data/examples/simple.rb +14 -0
- data/lib/active_record.rb +81 -47
- data/lib/active_record/aggregations.rb +1 -3
- data/lib/active_record/association_preload.rb +39 -54
- data/lib/active_record/associations.rb +262 -419
- data/lib/active_record/associations/association_collection.rb +85 -100
- data/lib/active_record/associations/association_proxy.rb +20 -18
- data/lib/active_record/associations/belongs_to_association.rb +8 -8
- data/lib/active_record/associations/has_and_belongs_to_many_association.rb +13 -35
- data/lib/active_record/associations/has_many_association.rb +11 -19
- data/lib/active_record/associations/has_many_through_association.rb +30 -183
- data/lib/active_record/associations/has_one_association.rb +10 -10
- data/lib/active_record/associations/has_one_through_association.rb +13 -11
- data/lib/active_record/associations/through_association_scope.rb +153 -0
- data/lib/active_record/attribute_methods.rb +17 -370
- data/lib/active_record/attribute_methods/before_type_cast.rb +33 -0
- data/lib/active_record/attribute_methods/dirty.rb +87 -0
- data/lib/active_record/attribute_methods/primary_key.rb +44 -0
- data/lib/active_record/attribute_methods/query.rb +37 -0
- data/lib/active_record/attribute_methods/read.rb +116 -0
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +60 -0
- data/lib/active_record/attribute_methods/write.rb +37 -0
- data/lib/active_record/autosave_association.rb +20 -41
- data/lib/active_record/base.rb +357 -1180
- data/lib/active_record/batches.rb +10 -16
- data/lib/active_record/callbacks.rb +66 -126
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +17 -13
- data/lib/active_record/connection_adapters/abstract/connection_specification.rb +5 -25
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +4 -43
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +3 -2
- data/lib/active_record/connection_adapters/abstract/quoting.rb +15 -4
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +1 -1
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +18 -72
- data/lib/active_record/connection_adapters/abstract_adapter.rb +16 -49
- data/lib/active_record/connection_adapters/mysql_adapter.rb +15 -27
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +84 -46
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +9 -3
- data/lib/active_record/connection_adapters/sqlite_adapter.rb +34 -65
- data/lib/active_record/fixtures.rb +21 -25
- data/lib/active_record/locale/en.yml +9 -27
- data/lib/active_record/locking/optimistic.rb +16 -48
- data/lib/active_record/migration.rb +59 -46
- data/lib/active_record/named_scope.rb +85 -92
- data/lib/active_record/nested_attributes.rb +18 -24
- data/lib/active_record/observer.rb +18 -94
- data/lib/active_record/railtie.rb +83 -0
- data/lib/active_record/railties/controller_runtime.rb +38 -0
- data/lib/active_record/railties/databases.rake +477 -0
- data/lib/active_record/railties/subscriber.rb +27 -0
- data/lib/active_record/reflection.rb +2 -15
- data/lib/active_record/relation.rb +339 -0
- data/lib/active_record/relation/calculations.rb +259 -0
- data/lib/active_record/relation/finder_methods.rb +315 -0
- data/lib/active_record/relation/predicate_builder.rb +46 -0
- data/lib/active_record/relation/query_methods.rb +218 -0
- data/lib/active_record/relation/spawn_methods.rb +102 -0
- data/lib/active_record/schema_dumper.rb +10 -6
- data/lib/active_record/serialization.rb +31 -74
- data/lib/active_record/serializers/xml_serializer.rb +33 -121
- data/lib/active_record/session_store.rb +1 -9
- data/lib/active_record/test_case.rb +1 -3
- data/lib/active_record/timestamp.rb +7 -5
- data/lib/active_record/transactions.rb +9 -9
- data/lib/active_record/validations.rb +51 -1100
- data/lib/active_record/validations/associated.rb +47 -0
- data/lib/active_record/validations/uniqueness.rb +181 -0
- data/lib/active_record/version.rb +3 -3
- data/lib/generators/active_record.rb +30 -0
- data/lib/generators/active_record/migration/migration_generator.rb +25 -0
- data/lib/generators/active_record/migration/templates/migration.rb +11 -0
- data/lib/generators/active_record/model/model_generator.rb +33 -0
- data/lib/generators/active_record/model/templates/migration.rb +16 -0
- data/lib/generators/active_record/model/templates/model.rb +5 -0
- data/lib/generators/active_record/observer/observer_generator.rb +15 -0
- data/lib/generators/active_record/observer/templates/observer.rb +2 -0
- data/lib/generators/active_record/session_migration/session_migration_generator.rb +24 -0
- data/lib/generators/active_record/session_migration/templates/migration.rb +16 -0
- metadata +67 -325
- data/RUNNING_UNIT_TESTS +0 -36
- data/Rakefile +0 -268
- data/install.rb +0 -30
- data/lib/active_record/calculations.rb +0 -321
- data/lib/active_record/connection_adapters/abstract/database_limits.rb +0 -57
- data/lib/active_record/dirty.rb +0 -183
- data/lib/active_record/serializers/json_serializer.rb +0 -91
- data/lib/activerecord.rb +0 -2
- data/test/assets/example.log +0 -1
- data/test/assets/flowers.jpg +0 -0
- data/test/cases/aaa_create_tables_test.rb +0 -24
- data/test/cases/active_schema_test_mysql.rb +0 -122
- data/test/cases/active_schema_test_postgresql.rb +0 -24
- data/test/cases/adapter_test.rb +0 -144
- data/test/cases/aggregations_test.rb +0 -167
- data/test/cases/ar_schema_test.rb +0 -32
- data/test/cases/associations/belongs_to_associations_test.rb +0 -438
- data/test/cases/associations/callbacks_test.rb +0 -161
- data/test/cases/associations/cascaded_eager_loading_test.rb +0 -131
- data/test/cases/associations/eager_load_includes_full_sti_class_test.rb +0 -36
- data/test/cases/associations/eager_load_nested_include_test.rb +0 -131
- data/test/cases/associations/eager_load_nested_polymorphic_include.rb +0 -19
- data/test/cases/associations/eager_singularization_test.rb +0 -145
- data/test/cases/associations/eager_test.rb +0 -852
- data/test/cases/associations/extension_test.rb +0 -62
- data/test/cases/associations/habtm_join_table_test.rb +0 -56
- data/test/cases/associations/has_and_belongs_to_many_associations_test.rb +0 -827
- data/test/cases/associations/has_many_associations_test.rb +0 -1273
- data/test/cases/associations/has_many_through_associations_test.rb +0 -360
- data/test/cases/associations/has_one_associations_test.rb +0 -330
- data/test/cases/associations/has_one_through_associations_test.rb +0 -209
- data/test/cases/associations/inner_join_association_test.rb +0 -93
- data/test/cases/associations/inverse_associations_test.rb +0 -566
- data/test/cases/associations/join_model_test.rb +0 -712
- data/test/cases/associations_test.rb +0 -282
- data/test/cases/attribute_methods_test.rb +0 -305
- data/test/cases/autosave_association_test.rb +0 -1218
- data/test/cases/base_test.rb +0 -2166
- data/test/cases/batches_test.rb +0 -81
- data/test/cases/binary_test.rb +0 -30
- data/test/cases/calculations_test.rb +0 -360
- data/test/cases/callbacks_observers_test.rb +0 -38
- data/test/cases/callbacks_test.rb +0 -438
- data/test/cases/class_inheritable_attributes_test.rb +0 -32
- data/test/cases/column_alias_test.rb +0 -17
- data/test/cases/column_definition_test.rb +0 -70
- data/test/cases/connection_pool_test.rb +0 -25
- data/test/cases/connection_test_firebird.rb +0 -8
- data/test/cases/connection_test_mysql.rb +0 -65
- data/test/cases/copy_table_test_sqlite.rb +0 -80
- data/test/cases/counter_cache_test.rb +0 -84
- data/test/cases/database_statements_test.rb +0 -12
- data/test/cases/datatype_test_postgresql.rb +0 -204
- data/test/cases/date_time_test.rb +0 -37
- data/test/cases/default_test_firebird.rb +0 -16
- data/test/cases/defaults_test.rb +0 -111
- data/test/cases/deprecated_finder_test.rb +0 -30
- data/test/cases/dirty_test.rb +0 -316
- data/test/cases/finder_respond_to_test.rb +0 -76
- data/test/cases/finder_test.rb +0 -1098
- data/test/cases/fixtures_test.rb +0 -661
- data/test/cases/helper.rb +0 -68
- data/test/cases/i18n_test.rb +0 -46
- data/test/cases/inheritance_test.rb +0 -262
- data/test/cases/invalid_date_test.rb +0 -24
- data/test/cases/json_serialization_test.rb +0 -219
- data/test/cases/lifecycle_test.rb +0 -193
- data/test/cases/locking_test.rb +0 -350
- data/test/cases/method_scoping_test.rb +0 -704
- data/test/cases/migration_test.rb +0 -1649
- data/test/cases/migration_test_firebird.rb +0 -124
- data/test/cases/mixin_test.rb +0 -96
- data/test/cases/modules_test.rb +0 -109
- data/test/cases/multiple_db_test.rb +0 -85
- data/test/cases/named_scope_test.rb +0 -372
- data/test/cases/nested_attributes_test.rb +0 -840
- data/test/cases/pk_test.rb +0 -119
- data/test/cases/pooled_connections_test.rb +0 -103
- data/test/cases/query_cache_test.rb +0 -129
- data/test/cases/readonly_test.rb +0 -107
- data/test/cases/reflection_test.rb +0 -234
- data/test/cases/reload_models_test.rb +0 -22
- data/test/cases/repair_helper.rb +0 -50
- data/test/cases/reserved_word_test_mysql.rb +0 -176
- data/test/cases/sanitize_test.rb +0 -25
- data/test/cases/schema_authorization_test_postgresql.rb +0 -75
- data/test/cases/schema_dumper_test.rb +0 -211
- data/test/cases/schema_test_postgresql.rb +0 -178
- data/test/cases/serialization_test.rb +0 -47
- data/test/cases/sp_test_mysql.rb +0 -16
- data/test/cases/synonym_test_oracle.rb +0 -17
- data/test/cases/timestamp_test.rb +0 -75
- data/test/cases/transactions_test.rb +0 -543
- data/test/cases/unconnected_test.rb +0 -32
- data/test/cases/validations_i18n_test.rb +0 -925
- data/test/cases/validations_test.rb +0 -1684
- data/test/cases/xml_serialization_test.rb +0 -240
- data/test/cases/yaml_serialization_test.rb +0 -11
- data/test/config.rb +0 -5
- data/test/connections/jdbc_jdbcderby/connection.rb +0 -18
- data/test/connections/jdbc_jdbch2/connection.rb +0 -18
- data/test/connections/jdbc_jdbchsqldb/connection.rb +0 -18
- data/test/connections/jdbc_jdbcmysql/connection.rb +0 -26
- data/test/connections/jdbc_jdbcpostgresql/connection.rb +0 -26
- data/test/connections/jdbc_jdbcsqlite3/connection.rb +0 -25
- data/test/connections/native_db2/connection.rb +0 -25
- data/test/connections/native_firebird/connection.rb +0 -26
- data/test/connections/native_frontbase/connection.rb +0 -27
- data/test/connections/native_mysql/connection.rb +0 -25
- data/test/connections/native_openbase/connection.rb +0 -21
- data/test/connections/native_oracle/connection.rb +0 -27
- data/test/connections/native_postgresql/connection.rb +0 -21
- data/test/connections/native_sqlite/connection.rb +0 -25
- data/test/connections/native_sqlite3/connection.rb +0 -25
- data/test/connections/native_sqlite3/in_memory_connection.rb +0 -18
- data/test/connections/native_sybase/connection.rb +0 -23
- data/test/fixtures/accounts.yml +0 -29
- data/test/fixtures/all/developers.yml +0 -0
- data/test/fixtures/all/people.csv +0 -0
- data/test/fixtures/all/tasks.yml +0 -0
- data/test/fixtures/author_addresses.yml +0 -5
- data/test/fixtures/author_favorites.yml +0 -4
- data/test/fixtures/authors.yml +0 -9
- data/test/fixtures/binaries.yml +0 -132
- data/test/fixtures/books.yml +0 -7
- data/test/fixtures/categories.yml +0 -14
- data/test/fixtures/categories/special_categories.yml +0 -9
- data/test/fixtures/categories/subsubdir/arbitrary_filename.yml +0 -4
- data/test/fixtures/categories_ordered.yml +0 -7
- data/test/fixtures/categories_posts.yml +0 -23
- data/test/fixtures/categorizations.yml +0 -17
- data/test/fixtures/clubs.yml +0 -6
- data/test/fixtures/comments.yml +0 -59
- data/test/fixtures/companies.yml +0 -56
- data/test/fixtures/computers.yml +0 -4
- data/test/fixtures/courses.yml +0 -7
- data/test/fixtures/customers.yml +0 -26
- data/test/fixtures/developers.yml +0 -21
- data/test/fixtures/developers_projects.yml +0 -17
- data/test/fixtures/edges.yml +0 -6
- data/test/fixtures/entrants.yml +0 -14
- data/test/fixtures/faces.yml +0 -11
- data/test/fixtures/fk_test_has_fk.yml +0 -3
- data/test/fixtures/fk_test_has_pk.yml +0 -2
- data/test/fixtures/funny_jokes.yml +0 -10
- data/test/fixtures/interests.yml +0 -33
- data/test/fixtures/items.yml +0 -4
- data/test/fixtures/jobs.yml +0 -7
- data/test/fixtures/legacy_things.yml +0 -3
- data/test/fixtures/mateys.yml +0 -4
- data/test/fixtures/member_types.yml +0 -6
- data/test/fixtures/members.yml +0 -6
- data/test/fixtures/memberships.yml +0 -20
- data/test/fixtures/men.yml +0 -5
- data/test/fixtures/minimalistics.yml +0 -2
- data/test/fixtures/mixed_case_monkeys.yml +0 -6
- data/test/fixtures/mixins.yml +0 -29
- data/test/fixtures/movies.yml +0 -7
- data/test/fixtures/naked/csv/accounts.csv +0 -1
- data/test/fixtures/naked/yml/accounts.yml +0 -1
- data/test/fixtures/naked/yml/companies.yml +0 -1
- data/test/fixtures/naked/yml/courses.yml +0 -1
- data/test/fixtures/organizations.yml +0 -5
- data/test/fixtures/owners.yml +0 -7
- data/test/fixtures/parrots.yml +0 -27
- data/test/fixtures/parrots_pirates.yml +0 -7
- data/test/fixtures/people.yml +0 -15
- data/test/fixtures/pets.yml +0 -14
- data/test/fixtures/pirates.yml +0 -9
- data/test/fixtures/polymorphic_designs.yml +0 -19
- data/test/fixtures/polymorphic_prices.yml +0 -19
- data/test/fixtures/posts.yml +0 -52
- data/test/fixtures/price_estimates.yml +0 -7
- data/test/fixtures/projects.yml +0 -7
- data/test/fixtures/readers.yml +0 -9
- data/test/fixtures/references.yml +0 -17
- data/test/fixtures/reserved_words/distinct.yml +0 -5
- data/test/fixtures/reserved_words/distincts_selects.yml +0 -11
- data/test/fixtures/reserved_words/group.yml +0 -14
- data/test/fixtures/reserved_words/select.yml +0 -8
- data/test/fixtures/reserved_words/values.yml +0 -7
- data/test/fixtures/ships.yml +0 -5
- data/test/fixtures/sponsors.yml +0 -9
- data/test/fixtures/subscribers.yml +0 -7
- data/test/fixtures/subscriptions.yml +0 -12
- data/test/fixtures/taggings.yml +0 -28
- data/test/fixtures/tags.yml +0 -7
- data/test/fixtures/tasks.yml +0 -7
- data/test/fixtures/tees.yml +0 -4
- data/test/fixtures/ties.yml +0 -4
- data/test/fixtures/topics.yml +0 -42
- data/test/fixtures/toys.yml +0 -4
- data/test/fixtures/treasures.yml +0 -10
- data/test/fixtures/vertices.yml +0 -4
- data/test/fixtures/warehouse-things.yml +0 -3
- data/test/fixtures/zines.yml +0 -5
- data/test/migrations/broken/100_migration_that_raises_exception.rb +0 -10
- data/test/migrations/decimal/1_give_me_big_numbers.rb +0 -15
- data/test/migrations/duplicate/1_people_have_last_names.rb +0 -9
- data/test/migrations/duplicate/2_we_need_reminders.rb +0 -12
- data/test/migrations/duplicate/3_foo.rb +0 -7
- data/test/migrations/duplicate/3_innocent_jointable.rb +0 -12
- data/test/migrations/duplicate_names/20080507052938_chunky.rb +0 -7
- data/test/migrations/duplicate_names/20080507053028_chunky.rb +0 -7
- data/test/migrations/interleaved/pass_1/3_innocent_jointable.rb +0 -12
- data/test/migrations/interleaved/pass_2/1_people_have_last_names.rb +0 -9
- data/test/migrations/interleaved/pass_2/3_innocent_jointable.rb +0 -12
- data/test/migrations/interleaved/pass_3/1_people_have_last_names.rb +0 -9
- data/test/migrations/interleaved/pass_3/2_i_raise_on_down.rb +0 -8
- data/test/migrations/interleaved/pass_3/3_innocent_jointable.rb +0 -12
- data/test/migrations/missing/1000_people_have_middle_names.rb +0 -9
- data/test/migrations/missing/1_people_have_last_names.rb +0 -9
- data/test/migrations/missing/3_we_need_reminders.rb +0 -12
- data/test/migrations/missing/4_innocent_jointable.rb +0 -12
- data/test/migrations/valid/1_people_have_last_names.rb +0 -9
- data/test/migrations/valid/2_we_need_reminders.rb +0 -12
- data/test/migrations/valid/3_innocent_jointable.rb +0 -12
- data/test/models/author.rb +0 -151
- data/test/models/auto_id.rb +0 -4
- data/test/models/binary.rb +0 -2
- data/test/models/bird.rb +0 -9
- data/test/models/book.rb +0 -4
- data/test/models/categorization.rb +0 -5
- data/test/models/category.rb +0 -34
- data/test/models/citation.rb +0 -6
- data/test/models/club.rb +0 -13
- data/test/models/column_name.rb +0 -3
- data/test/models/comment.rb +0 -29
- data/test/models/company.rb +0 -173
- data/test/models/company_in_module.rb +0 -78
- data/test/models/computer.rb +0 -3
- data/test/models/contact.rb +0 -16
- data/test/models/contract.rb +0 -5
- data/test/models/course.rb +0 -3
- data/test/models/customer.rb +0 -73
- data/test/models/default.rb +0 -2
- data/test/models/developer.rb +0 -101
- data/test/models/edge.rb +0 -5
- data/test/models/entrant.rb +0 -3
- data/test/models/essay.rb +0 -3
- data/test/models/event.rb +0 -3
- data/test/models/event_author.rb +0 -8
- data/test/models/face.rb +0 -7
- data/test/models/guid.rb +0 -2
- data/test/models/interest.rb +0 -5
- data/test/models/invoice.rb +0 -4
- data/test/models/item.rb +0 -7
- data/test/models/job.rb +0 -5
- data/test/models/joke.rb +0 -3
- data/test/models/keyboard.rb +0 -3
- data/test/models/legacy_thing.rb +0 -3
- data/test/models/line_item.rb +0 -3
- data/test/models/man.rb +0 -9
- data/test/models/matey.rb +0 -4
- data/test/models/member.rb +0 -12
- data/test/models/member_detail.rb +0 -5
- data/test/models/member_type.rb +0 -3
- data/test/models/membership.rb +0 -9
- data/test/models/minimalistic.rb +0 -2
- data/test/models/mixed_case_monkey.rb +0 -3
- data/test/models/movie.rb +0 -5
- data/test/models/order.rb +0 -4
- data/test/models/organization.rb +0 -6
- data/test/models/owner.rb +0 -5
- data/test/models/parrot.rb +0 -22
- data/test/models/person.rb +0 -16
- data/test/models/pet.rb +0 -5
- data/test/models/pirate.rb +0 -80
- data/test/models/polymorphic_design.rb +0 -3
- data/test/models/polymorphic_price.rb +0 -3
- data/test/models/post.rb +0 -102
- data/test/models/price_estimate.rb +0 -3
- data/test/models/project.rb +0 -30
- data/test/models/reader.rb +0 -4
- data/test/models/reference.rb +0 -4
- data/test/models/reply.rb +0 -46
- data/test/models/ship.rb +0 -19
- data/test/models/ship_part.rb +0 -7
- data/test/models/sponsor.rb +0 -4
- data/test/models/subject.rb +0 -4
- data/test/models/subscriber.rb +0 -8
- data/test/models/subscription.rb +0 -4
- data/test/models/tag.rb +0 -7
- data/test/models/tagging.rb +0 -10
- data/test/models/task.rb +0 -3
- data/test/models/tee.rb +0 -4
- data/test/models/tie.rb +0 -4
- data/test/models/topic.rb +0 -80
- data/test/models/toy.rb +0 -6
- data/test/models/treasure.rb +0 -8
- data/test/models/vertex.rb +0 -9
- data/test/models/warehouse_thing.rb +0 -5
- data/test/models/zine.rb +0 -3
- data/test/schema/mysql_specific_schema.rb +0 -31
- data/test/schema/postgresql_specific_schema.rb +0 -114
- data/test/schema/schema.rb +0 -550
- data/test/schema/schema2.rb +0 -6
- data/test/schema/sqlite_specific_schema.rb +0 -25
data/lib/active_record/base.rb
CHANGED
@@ -1,6 +1,18 @@
|
|
1
1
|
require 'yaml'
|
2
2
|
require 'set'
|
3
|
-
require 'active_support/
|
3
|
+
require 'active_support/benchmarkable'
|
4
|
+
require 'active_support/dependencies'
|
5
|
+
require 'active_support/time'
|
6
|
+
require 'active_support/core_ext/class/attribute_accessors'
|
7
|
+
require 'active_support/core_ext/class/delegating_attributes'
|
8
|
+
require 'active_support/core_ext/class/inheritable_attributes'
|
9
|
+
require 'active_support/core_ext/array/extract_options'
|
10
|
+
require 'active_support/core_ext/hash/deep_merge'
|
11
|
+
require 'active_support/core_ext/hash/indifferent_access'
|
12
|
+
require 'active_support/core_ext/hash/slice'
|
13
|
+
require 'active_support/core_ext/string/behavior'
|
14
|
+
require 'active_support/core_ext/object/metaclass'
|
15
|
+
require 'active_support/core_ext/module/delegation'
|
4
16
|
|
5
17
|
module ActiveRecord #:nodoc:
|
6
18
|
# Generic Active Record exception class.
|
@@ -56,6 +68,29 @@ module ActiveRecord #:nodoc:
|
|
56
68
|
class StatementInvalid < ActiveRecordError
|
57
69
|
end
|
58
70
|
|
71
|
+
# Raised when SQL statement is invalid and the application gets a blank result.
|
72
|
+
class ThrowResult < ActiveRecordError
|
73
|
+
end
|
74
|
+
|
75
|
+
# Parent class for all specific exceptions which wrap database driver exceptions
|
76
|
+
# provides access to the original exception also.
|
77
|
+
class WrappedDatabaseException < StatementInvalid
|
78
|
+
attr_reader :original_exception
|
79
|
+
|
80
|
+
def initialize(message, original_exception)
|
81
|
+
super(message)
|
82
|
+
@original_exception = original_exception
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
# Raised when a record cannot be inserted because it would violate a uniqueness constraint.
|
87
|
+
class RecordNotUnique < WrappedDatabaseException
|
88
|
+
end
|
89
|
+
|
90
|
+
# Raised when a record cannot be inserted or updated because it references a non-existent record.
|
91
|
+
class InvalidForeignKey < WrappedDatabaseException
|
92
|
+
end
|
93
|
+
|
59
94
|
# Raised when number of bind variables in statement given to <tt>:condition</tt> key (for example, when using +find+ method)
|
60
95
|
# does not match number of expected variables.
|
61
96
|
#
|
@@ -118,11 +153,6 @@ module ActiveRecord #:nodoc:
|
|
118
153
|
class DangerousAttributeError < ActiveRecordError
|
119
154
|
end
|
120
155
|
|
121
|
-
# Raised when you've tried to access a column which wasn't loaded by your finder.
|
122
|
-
# Typically this is because <tt>:select</tt> has been specified.
|
123
|
-
class MissingAttributeError < NoMethodError
|
124
|
-
end
|
125
|
-
|
126
156
|
# Raised when unknown attributes are supplied via mass assignment.
|
127
157
|
class UnknownAttributeError < NoMethodError
|
128
158
|
end
|
@@ -226,6 +256,12 @@ module ActiveRecord #:nodoc:
|
|
226
256
|
#
|
227
257
|
# Student.find(:all, :conditions => { :grade => [9,11,12] })
|
228
258
|
#
|
259
|
+
# When joining tables, nested hashes or keys written in the form 'table_name.column_name' can be used to qualify the table name of a
|
260
|
+
# particular condition. For instance:
|
261
|
+
#
|
262
|
+
# Student.find(:all, :conditions => { :schools => { :type => 'public' }}, :joins => :schools)
|
263
|
+
# Student.find(:all, :conditions => { 'schools.type' => 'public' }, :joins => :schools)
|
264
|
+
#
|
229
265
|
# == Overwriting default accessors
|
230
266
|
#
|
231
267
|
# All column values are automatically available through basic accessors on the Active Record object, but sometimes you
|
@@ -390,7 +426,7 @@ module ActiveRecord #:nodoc:
|
|
390
426
|
# So it's possible to assign a logger to the class through <tt>Base.logger=</tt> which will then be used by all
|
391
427
|
# instances in the current object space.
|
392
428
|
class Base
|
393
|
-
##
|
429
|
+
##
|
394
430
|
# :singleton-method:
|
395
431
|
# Accepts a logger conforming to the interface of Log4r or the default Ruby 1.8+ Logger class, which is then passed
|
396
432
|
# on to any new database connections made and which can be retrieved on both a class and instance level by calling +logger+.
|
@@ -424,11 +460,11 @@ module ActiveRecord #:nodoc:
|
|
424
460
|
# as a Hash.
|
425
461
|
#
|
426
462
|
# For example, the following database.yml...
|
427
|
-
#
|
463
|
+
#
|
428
464
|
# development:
|
429
465
|
# adapter: sqlite3
|
430
466
|
# database: db/development.sqlite3
|
431
|
-
#
|
467
|
+
#
|
432
468
|
# production:
|
433
469
|
# adapter: sqlite3
|
434
470
|
# database: db/production.sqlite3
|
@@ -462,9 +498,6 @@ module ActiveRecord #:nodoc:
|
|
462
498
|
# Accessor for the name of the prefix string to prepend to every table name. So if set to "basecamp_", all
|
463
499
|
# table names will be named like "basecamp_projects", "basecamp_people", etc. This is a convenient way of creating a namespace
|
464
500
|
# for tables in a shared database. By default, the prefix is the empty string.
|
465
|
-
#
|
466
|
-
# If you are organising your models within modules you can add a prefix to the models within a namespace by defining
|
467
|
-
# a singleton method in the parent module called table_name_prefix which returns your chosen prefix.
|
468
501
|
cattr_accessor :table_name_prefix, :instance_writer => false
|
469
502
|
@@table_name_prefix = ""
|
470
503
|
|
@@ -483,14 +516,6 @@ module ActiveRecord #:nodoc:
|
|
483
516
|
cattr_accessor :pluralize_table_names, :instance_writer => false
|
484
517
|
@@pluralize_table_names = true
|
485
518
|
|
486
|
-
##
|
487
|
-
# :singleton-method:
|
488
|
-
# Determines whether to use ANSI codes to colorize the logging statements committed by the connection adapter. These colors
|
489
|
-
# make it much easier to overview things during debugging (when used through a reader like +tail+ and on a black background), but
|
490
|
-
# may complicate matters if you use software like syslog. This is true, by default.
|
491
|
-
cattr_accessor :colorize_logging, :instance_writer => false
|
492
|
-
@@colorize_logging = true
|
493
|
-
|
494
519
|
##
|
495
520
|
# :singleton-method:
|
496
521
|
# Determines whether to use Time.local (using :local) or Time.utc (using :utc) when pulling dates and times from the database.
|
@@ -516,128 +541,24 @@ module ActiveRecord #:nodoc:
|
|
516
541
|
@@timestamped_migrations = true
|
517
542
|
|
518
543
|
# Determine whether to store the full constant name including namespace when using STI
|
519
|
-
|
520
|
-
self.store_full_sti_class =
|
544
|
+
superclass_delegating_accessor :store_full_sti_class
|
545
|
+
self.store_full_sti_class = true
|
521
546
|
|
522
547
|
# Stores the default scope for the class
|
523
548
|
class_inheritable_accessor :default_scoping, :instance_writer => false
|
524
549
|
self.default_scoping = []
|
525
550
|
|
526
551
|
class << self # Class methods
|
527
|
-
|
528
|
-
|
529
|
-
|
530
|
-
|
531
|
-
# * Find first - This will return the first record matched by the options used. These options can either be specific
|
532
|
-
# conditions or merely an order. If no record can be matched, +nil+ is returned. Use
|
533
|
-
# <tt>Model.find(:first, *args)</tt> or its shortcut <tt>Model.first(*args)</tt>.
|
534
|
-
# * Find last - This will return the last record matched by the options used. These options can either be specific
|
535
|
-
# conditions or merely an order. If no record can be matched, +nil+ is returned. Use
|
536
|
-
# <tt>Model.find(:last, *args)</tt> or its shortcut <tt>Model.last(*args)</tt>.
|
537
|
-
# * Find all - This will return all the records matched by the options used.
|
538
|
-
# If no records are found, an empty array is returned. Use
|
539
|
-
# <tt>Model.find(:all, *args)</tt> or its shortcut <tt>Model.all(*args)</tt>.
|
540
|
-
#
|
541
|
-
# All approaches accept an options hash as their last parameter.
|
542
|
-
#
|
543
|
-
# ==== Parameters
|
544
|
-
#
|
545
|
-
# * <tt>:conditions</tt> - An SQL fragment like "administrator = 1", <tt>[ "user_name = ?", username ]</tt>, or <tt>["user_name = :user_name", { :user_name => user_name }]</tt>. See conditions in the intro.
|
546
|
-
# * <tt>:order</tt> - An SQL fragment like "created_at DESC, name".
|
547
|
-
# * <tt>:group</tt> - An attribute name by which the result should be grouped. Uses the <tt>GROUP BY</tt> SQL-clause.
|
548
|
-
# * <tt>:having</tt> - Combined with +:group+ this can be used to filter the records that a <tt>GROUP BY</tt> returns. Uses the <tt>HAVING</tt> SQL-clause.
|
549
|
-
# * <tt>:limit</tt> - An integer determining the limit on the number of rows that should be returned.
|
550
|
-
# * <tt>:offset</tt> - An integer determining the offset from where the rows should be fetched. So at 5, it would skip rows 0 through 4.
|
551
|
-
# * <tt>:joins</tt> - Either an SQL fragment for additional joins like "LEFT JOIN comments ON comments.post_id = id" (rarely needed),
|
552
|
-
# named associations in the same form used for the <tt>:include</tt> option, which will perform an <tt>INNER JOIN</tt> on the associated table(s),
|
553
|
-
# or an array containing a mixture of both strings and named associations.
|
554
|
-
# If the value is a string, then the records will be returned read-only since they will have attributes that do not correspond to the table's columns.
|
555
|
-
# Pass <tt>:readonly => false</tt> to override.
|
556
|
-
# * <tt>:include</tt> - Names associations that should be loaded alongside. The symbols named refer
|
557
|
-
# to already defined associations. See eager loading under Associations.
|
558
|
-
# * <tt>:select</tt> - By default, this is "*" as in "SELECT * FROM", but can be changed if you, for example, want to do a join but not
|
559
|
-
# include the joined columns. Takes a string with the SELECT SQL fragment (e.g. "id, name").
|
560
|
-
# * <tt>:from</tt> - By default, this is the table name of the class, but can be changed to an alternate table name (or even the name
|
561
|
-
# of a database view).
|
562
|
-
# * <tt>:readonly</tt> - Mark the returned records read-only so they cannot be saved or updated.
|
563
|
-
# * <tt>:lock</tt> - An SQL fragment like "FOR UPDATE" or "LOCK IN SHARE MODE".
|
564
|
-
# <tt>:lock => true</tt> gives connection's default exclusive lock, usually "FOR UPDATE".
|
565
|
-
#
|
566
|
-
# ==== Examples
|
567
|
-
#
|
568
|
-
# # find by id
|
569
|
-
# Person.find(1) # returns the object for ID = 1
|
570
|
-
# Person.find(1, 2, 6) # returns an array for objects with IDs in (1, 2, 6)
|
571
|
-
# Person.find([7, 17]) # returns an array for objects with IDs in (7, 17)
|
572
|
-
# Person.find([1]) # returns an array for the object with ID = 1
|
573
|
-
# Person.find(1, :conditions => "administrator = 1", :order => "created_on DESC")
|
574
|
-
#
|
575
|
-
# Note that returned records may not be in the same order as the ids you
|
576
|
-
# provide since database rows are unordered. Give an explicit <tt>:order</tt>
|
577
|
-
# to ensure the results are sorted.
|
578
|
-
#
|
579
|
-
# ==== Examples
|
580
|
-
#
|
581
|
-
# # find first
|
582
|
-
# Person.find(:first) # returns the first object fetched by SELECT * FROM people
|
583
|
-
# Person.find(:first, :conditions => [ "user_name = ?", user_name])
|
584
|
-
# Person.find(:first, :conditions => [ "user_name = :u", { :u => user_name }])
|
585
|
-
# Person.find(:first, :order => "created_on DESC", :offset => 5)
|
586
|
-
#
|
587
|
-
# # find last
|
588
|
-
# Person.find(:last) # returns the last object fetched by SELECT * FROM people
|
589
|
-
# Person.find(:last, :conditions => [ "user_name = ?", user_name])
|
590
|
-
# Person.find(:last, :order => "created_on DESC", :offset => 5)
|
591
|
-
#
|
592
|
-
# # find all
|
593
|
-
# Person.find(:all) # returns an array of objects for all the rows fetched by SELECT * FROM people
|
594
|
-
# Person.find(:all, :conditions => [ "category IN (?)", categories], :limit => 50)
|
595
|
-
# Person.find(:all, :conditions => { :friends => ["Bob", "Steve", "Fred"] }
|
596
|
-
# Person.find(:all, :offset => 10, :limit => 10)
|
597
|
-
# Person.find(:all, :include => [ :account, :friends ])
|
598
|
-
# Person.find(:all, :group => "category")
|
599
|
-
#
|
600
|
-
# Example for find with a lock: Imagine two concurrent transactions:
|
601
|
-
# each will read <tt>person.visits == 2</tt>, add 1 to it, and save, resulting
|
602
|
-
# in two saves of <tt>person.visits = 3</tt>. By locking the row, the second
|
603
|
-
# transaction has to wait until the first is finished; we get the
|
604
|
-
# expected <tt>person.visits == 4</tt>.
|
605
|
-
#
|
606
|
-
# Person.transaction do
|
607
|
-
# person = Person.find(1, :lock => true)
|
608
|
-
# person.visits += 1
|
609
|
-
# person.save!
|
610
|
-
# end
|
611
|
-
def find(*args)
|
612
|
-
options = args.extract_options!
|
613
|
-
validate_find_options(options)
|
614
|
-
set_readonly_option!(options)
|
615
|
-
|
616
|
-
case args.first
|
617
|
-
when :first then find_initial(options)
|
618
|
-
when :last then find_last(options)
|
619
|
-
when :all then find_every(options)
|
620
|
-
else find_from_ids(args, options)
|
621
|
-
end
|
622
|
-
end
|
623
|
-
|
624
|
-
# A convenience wrapper for <tt>find(:first, *args)</tt>. You can pass in all the
|
625
|
-
# same arguments to this method as you can to <tt>find(:first)</tt>.
|
626
|
-
def first(*args)
|
627
|
-
find(:first, *args)
|
552
|
+
def colorize_logging(*args)
|
553
|
+
ActiveSupport::Deprecation.warn "ActiveRecord::Base.colorize_logging and " <<
|
554
|
+
"config.active_record.colorize_logging are deprecated. Please use " <<
|
555
|
+
"Rails::Subscriber.colorize_logging or config.colorize_logging instead", caller
|
628
556
|
end
|
557
|
+
alias :colorize_logging= :colorize_logging
|
629
558
|
|
630
|
-
|
631
|
-
|
632
|
-
|
633
|
-
find(:last, *args)
|
634
|
-
end
|
635
|
-
|
636
|
-
# This is an alias for find(:all). You can pass in all the same arguments to this method as you can
|
637
|
-
# to find(:all)
|
638
|
-
def all(*args)
|
639
|
-
find(:all, *args)
|
640
|
-
end
|
559
|
+
delegate :find, :first, :last, :all, :destroy, :destroy_all, :exists?, :delete, :delete_all, :update, :update_all, :to => :scoped
|
560
|
+
delegate :select, :group, :order, :limit, :joins, :where, :preload, :eager_load, :includes, :from, :lock, :readonly, :having, :to => :scoped
|
561
|
+
delegate :count, :average, :minimum, :maximum, :sum, :calculate, :to => :scoped
|
641
562
|
|
642
563
|
# Executes a custom SQL query against your database and returns all the results. The results will
|
643
564
|
# be returned as an array with columns requested encapsulated as attributes of the model you call
|
@@ -665,37 +586,6 @@ module ActiveRecord #:nodoc:
|
|
665
586
|
connection.select_all(sanitize_sql(sql), "#{name} Load").collect! { |record| instantiate(record) }
|
666
587
|
end
|
667
588
|
|
668
|
-
# Returns true if a record exists in the table that matches the +id+ or
|
669
|
-
# conditions given, or false otherwise. The argument can take five forms:
|
670
|
-
#
|
671
|
-
# * Integer - Finds the record with this primary key.
|
672
|
-
# * String - Finds the record with a primary key corresponding to this
|
673
|
-
# string (such as <tt>'5'</tt>).
|
674
|
-
# * Array - Finds the record that matches these +find+-style conditions
|
675
|
-
# (such as <tt>['color = ?', 'red']</tt>).
|
676
|
-
# * Hash - Finds the record that matches these +find+-style conditions
|
677
|
-
# (such as <tt>{:color => 'red'}</tt>).
|
678
|
-
# * No args - Returns false if the table is empty, true otherwise.
|
679
|
-
#
|
680
|
-
# For more information about specifying conditions as a Hash or Array,
|
681
|
-
# see the Conditions section in the introduction to ActiveRecord::Base.
|
682
|
-
#
|
683
|
-
# Note: You can't pass in a condition as a string (like <tt>name =
|
684
|
-
# 'Jamie'</tt>), since it would be sanitized and then queried against
|
685
|
-
# the primary key column, like <tt>id = 'name = \'Jamie\''</tt>.
|
686
|
-
#
|
687
|
-
# ==== Examples
|
688
|
-
# Person.exists?(5)
|
689
|
-
# Person.exists?('5')
|
690
|
-
# Person.exists?(:name => "David")
|
691
|
-
# Person.exists?(['name LIKE ?', "%#{query}%"])
|
692
|
-
# Person.exists?
|
693
|
-
def exists?(id_or_conditions = {})
|
694
|
-
find_initial(
|
695
|
-
:select => "#{quoted_table_name}.#{primary_key}",
|
696
|
-
:conditions => expand_id_conditions(id_or_conditions)) ? true : false
|
697
|
-
end
|
698
|
-
|
699
589
|
# Creates an object (or multiple objects) and saves it to the database, if validations pass.
|
700
590
|
# The resulting object is returned whether the object was saved successfully to the database or not.
|
701
591
|
#
|
@@ -729,181 +619,6 @@ module ActiveRecord #:nodoc:
|
|
729
619
|
end
|
730
620
|
end
|
731
621
|
|
732
|
-
# Updates an object (or multiple objects) and saves it to the database, if validations pass.
|
733
|
-
# The resulting object is returned whether the object was saved successfully to the database or not.
|
734
|
-
#
|
735
|
-
# ==== Parameters
|
736
|
-
#
|
737
|
-
# * +id+ - This should be the id or an array of ids to be updated.
|
738
|
-
# * +attributes+ - This should be a hash of attributes to be set on the object, or an array of hashes.
|
739
|
-
#
|
740
|
-
# ==== Examples
|
741
|
-
#
|
742
|
-
# # Updating one record:
|
743
|
-
# Person.update(15, :user_name => 'Samuel', :group => 'expert')
|
744
|
-
#
|
745
|
-
# # Updating multiple records:
|
746
|
-
# people = { 1 => { "first_name" => "David" }, 2 => { "first_name" => "Jeremy" } }
|
747
|
-
# Person.update(people.keys, people.values)
|
748
|
-
def update(id, attributes)
|
749
|
-
if id.is_a?(Array)
|
750
|
-
idx = -1
|
751
|
-
id.collect { |one_id| idx += 1; update(one_id, attributes[idx]) }
|
752
|
-
else
|
753
|
-
object = find(id)
|
754
|
-
object.update_attributes(attributes)
|
755
|
-
object
|
756
|
-
end
|
757
|
-
end
|
758
|
-
|
759
|
-
# Deletes the row with a primary key matching the +id+ argument, using a
|
760
|
-
# SQL +DELETE+ statement, and returns the number of rows deleted. Active
|
761
|
-
# Record objects are not instantiated, so the object's callbacks are not
|
762
|
-
# executed, including any <tt>:dependent</tt> association options or
|
763
|
-
# Observer methods.
|
764
|
-
#
|
765
|
-
# You can delete multiple rows at once by passing an Array of <tt>id</tt>s.
|
766
|
-
#
|
767
|
-
# Note: Although it is often much faster than the alternative,
|
768
|
-
# <tt>#destroy</tt>, skipping callbacks might bypass business logic in
|
769
|
-
# your application that ensures referential integrity or performs other
|
770
|
-
# essential jobs.
|
771
|
-
#
|
772
|
-
# ==== Examples
|
773
|
-
#
|
774
|
-
# # Delete a single row
|
775
|
-
# Todo.delete(1)
|
776
|
-
#
|
777
|
-
# # Delete multiple rows
|
778
|
-
# Todo.delete([2,3,4])
|
779
|
-
def delete(id)
|
780
|
-
delete_all([ "#{connection.quote_column_name(primary_key)} IN (?)", id ])
|
781
|
-
end
|
782
|
-
|
783
|
-
# Destroy an object (or multiple objects) that has the given id, the object is instantiated first,
|
784
|
-
# therefore all callbacks and filters are fired off before the object is deleted. This method is
|
785
|
-
# less efficient than ActiveRecord#delete but allows cleanup methods and other actions to be run.
|
786
|
-
#
|
787
|
-
# This essentially finds the object (or multiple objects) with the given id, creates a new object
|
788
|
-
# from the attributes, and then calls destroy on it.
|
789
|
-
#
|
790
|
-
# ==== Parameters
|
791
|
-
#
|
792
|
-
# * +id+ - Can be either an Integer or an Array of Integers.
|
793
|
-
#
|
794
|
-
# ==== Examples
|
795
|
-
#
|
796
|
-
# # Destroy a single object
|
797
|
-
# Todo.destroy(1)
|
798
|
-
#
|
799
|
-
# # Destroy multiple objects
|
800
|
-
# todos = [1,2,3]
|
801
|
-
# Todo.destroy(todos)
|
802
|
-
def destroy(id)
|
803
|
-
if id.is_a?(Array)
|
804
|
-
id.map { |one_id| destroy(one_id) }
|
805
|
-
else
|
806
|
-
find(id).destroy
|
807
|
-
end
|
808
|
-
end
|
809
|
-
|
810
|
-
# Updates all records with details given if they match a set of conditions supplied, limits and order can
|
811
|
-
# also be supplied. This method constructs a single SQL UPDATE statement and sends it straight to the
|
812
|
-
# database. It does not instantiate the involved models and it does not trigger Active Record callbacks.
|
813
|
-
#
|
814
|
-
# ==== Parameters
|
815
|
-
#
|
816
|
-
# * +updates+ - A string of column and value pairs that will be set on any records that match conditions. This creates the SET clause of the generated SQL.
|
817
|
-
# * +conditions+ - An SQL fragment like "administrator = 1" or [ "user_name = ?", username ]. See conditions in the intro for more info.
|
818
|
-
# * +options+ - Additional options are <tt>:limit</tt> and <tt>:order</tt>, see the examples for usage.
|
819
|
-
#
|
820
|
-
# ==== Examples
|
821
|
-
#
|
822
|
-
# # Update all billing objects with the 3 different attributes given
|
823
|
-
# Billing.update_all( "category = 'authorized', approved = 1, author = 'David'" )
|
824
|
-
#
|
825
|
-
# # Update records that match our conditions
|
826
|
-
# Billing.update_all( "author = 'David'", "title LIKE '%Rails%'" )
|
827
|
-
#
|
828
|
-
# # Update records that match our conditions but limit it to 5 ordered by date
|
829
|
-
# Billing.update_all( "author = 'David'", "title LIKE '%Rails%'",
|
830
|
-
# :order => 'created_at', :limit => 5 )
|
831
|
-
def update_all(updates, conditions = nil, options = {})
|
832
|
-
sql = "UPDATE #{quoted_table_name} SET #{sanitize_sql_for_assignment(updates)} "
|
833
|
-
|
834
|
-
scope = scope(:find)
|
835
|
-
|
836
|
-
select_sql = ""
|
837
|
-
add_conditions!(select_sql, conditions, scope)
|
838
|
-
|
839
|
-
if options.has_key?(:limit) || (scope && scope[:limit])
|
840
|
-
# Only take order from scope if limit is also provided by scope, this
|
841
|
-
# is useful for updating a has_many association with a limit.
|
842
|
-
add_order!(select_sql, options[:order], scope)
|
843
|
-
|
844
|
-
add_limit!(select_sql, options, scope)
|
845
|
-
sql.concat(connection.limited_update_conditions(select_sql, quoted_table_name, connection.quote_column_name(primary_key)))
|
846
|
-
else
|
847
|
-
add_order!(select_sql, options[:order], nil)
|
848
|
-
sql.concat(select_sql)
|
849
|
-
end
|
850
|
-
|
851
|
-
connection.update(sql, "#{name} Update")
|
852
|
-
end
|
853
|
-
|
854
|
-
# Destroys the records matching +conditions+ by instantiating each
|
855
|
-
# record and calling its +destroy+ method. Each object's callbacks are
|
856
|
-
# executed (including <tt>:dependent</tt> association options and
|
857
|
-
# +before_destroy+/+after_destroy+ Observer methods). Returns the
|
858
|
-
# collection of objects that were destroyed; each will be frozen, to
|
859
|
-
# reflect that no changes should be made (since they can't be
|
860
|
-
# persisted).
|
861
|
-
#
|
862
|
-
# Note: Instantiation, callback execution, and deletion of each
|
863
|
-
# record can be time consuming when you're removing many records at
|
864
|
-
# once. It generates at least one SQL +DELETE+ query per record (or
|
865
|
-
# possibly more, to enforce your callbacks). If you want to delete many
|
866
|
-
# rows quickly, without concern for their associations or callbacks, use
|
867
|
-
# +delete_all+ instead.
|
868
|
-
#
|
869
|
-
# ==== Parameters
|
870
|
-
#
|
871
|
-
# * +conditions+ - A string, array, or hash that specifies which records
|
872
|
-
# to destroy. If omitted, all records are destroyed. See the
|
873
|
-
# Conditions section in the introduction to ActiveRecord::Base for
|
874
|
-
# more information.
|
875
|
-
#
|
876
|
-
# ==== Examples
|
877
|
-
#
|
878
|
-
# Person.destroy_all("last_login < '2004-04-04'")
|
879
|
-
# Person.destroy_all(:status => "inactive")
|
880
|
-
def destroy_all(conditions = nil)
|
881
|
-
find(:all, :conditions => conditions).each { |object| object.destroy }
|
882
|
-
end
|
883
|
-
|
884
|
-
# Deletes the records matching +conditions+ without instantiating the records first, and hence not
|
885
|
-
# calling the +destroy+ method nor invoking callbacks. This is a single SQL DELETE statement that
|
886
|
-
# goes straight to the database, much more efficient than +destroy_all+. Be careful with relations
|
887
|
-
# though, in particular <tt>:dependent</tt> rules defined on associations are not honored. Returns
|
888
|
-
# the number of rows affected.
|
889
|
-
#
|
890
|
-
# ==== Parameters
|
891
|
-
#
|
892
|
-
# * +conditions+ - Conditions are specified the same way as with +find+ method.
|
893
|
-
#
|
894
|
-
# ==== Example
|
895
|
-
#
|
896
|
-
# Post.delete_all("person_id = 5 AND (category = 'Something' OR category = 'Else')")
|
897
|
-
# Post.delete_all(["person_id = ? AND (category = ? OR category = ?)", 5, 'Something', 'Else'])
|
898
|
-
#
|
899
|
-
# Both calls delete the affected posts all at once with a single DELETE statement. If you need to destroy dependent
|
900
|
-
# associations or call your <tt>before_*</tt> or +after_destroy+ callbacks, use the +destroy_all+ method instead.
|
901
|
-
def delete_all(conditions = nil)
|
902
|
-
sql = "DELETE FROM #{quoted_table_name} "
|
903
|
-
add_conditions!(sql, conditions, scope(:find))
|
904
|
-
connection.delete(sql, "#{name} Delete all")
|
905
|
-
end
|
906
|
-
|
907
622
|
# Returns the result of an SQL statement that should only include a COUNT(*) in the SELECT part.
|
908
623
|
# The use of this method should be restricted to complicated SQL queries that can't be executed
|
909
624
|
# using the ActiveRecord::Calculations class methods. Look into those before using this.
|
@@ -936,18 +651,11 @@ module ActiveRecord #:nodoc:
|
|
936
651
|
def reset_counters(id, *counters)
|
937
652
|
object = find(id)
|
938
653
|
counters.each do |association|
|
939
|
-
child_class = reflect_on_association(association
|
940
|
-
|
941
|
-
counter_name = child_class.reflect_on_association(belongs_name).counter_cache_column
|
942
|
-
value = object.send(association).count
|
654
|
+
child_class = reflect_on_association(association).klass
|
655
|
+
counter_name = child_class.reflect_on_association(self.name.downcase.to_sym).counter_cache_column
|
943
656
|
|
944
|
-
connection.update(
|
945
|
-
UPDATE #{quoted_table_name}
|
946
|
-
SET #{connection.quote_column_name(counter_name)} = #{value}
|
947
|
-
WHERE #{connection.quote_column_name(primary_key)} = #{quote_value(object.id)}
|
948
|
-
CMD
|
657
|
+
connection.update("UPDATE #{quoted_table_name} SET #{connection.quote_column_name(counter_name)} = #{object.send(association).count} WHERE #{connection.quote_column_name(primary_key)} = #{quote_value(object.id)}", "#{name} UPDATE")
|
949
658
|
end
|
950
|
-
return true
|
951
659
|
end
|
952
660
|
|
953
661
|
# A generic "counter updater" implementation, intended primarily to be
|
@@ -980,13 +688,19 @@ module ActiveRecord #:nodoc:
|
|
980
688
|
# # SET comment_count = comment_count + 1,
|
981
689
|
# # WHERE id IN (10, 15)
|
982
690
|
def update_counters(id, counters)
|
983
|
-
updates = counters.
|
984
|
-
|
985
|
-
|
986
|
-
|
691
|
+
updates = counters.inject([]) { |list, (counter_name, increment)|
|
692
|
+
sign = increment < 0 ? "-" : "+"
|
693
|
+
list << "#{connection.quote_column_name(counter_name)} = COALESCE(#{connection.quote_column_name(counter_name)}, 0) #{sign} #{increment.abs}"
|
694
|
+
}.join(", ")
|
695
|
+
|
696
|
+
if id.is_a?(Array)
|
697
|
+
ids_list = id.map {|i| quote_value(i)}.join(', ')
|
698
|
+
condition = "IN (#{ids_list})"
|
699
|
+
else
|
700
|
+
condition = "= #{quote_value(id)}"
|
987
701
|
end
|
988
702
|
|
989
|
-
update_all(updates.
|
703
|
+
update_all(updates, "#{connection.quote_column_name(primary_key)} #{condition}")
|
990
704
|
end
|
991
705
|
|
992
706
|
# Increment a number field by one, usually representing a count.
|
@@ -1049,8 +763,23 @@ module ActiveRecord #:nodoc:
|
|
1049
763
|
#
|
1050
764
|
# To start from an all-closed default and enable attributes as needed,
|
1051
765
|
# have a look at +attr_accessible+.
|
766
|
+
#
|
767
|
+
# If the access logic of your application is richer you can use <tt>Hash#except</tt>
|
768
|
+
# or <tt>Hash#slice</tt> to sanitize the hash of parameters before they are
|
769
|
+
# passed to Active Record.
|
770
|
+
#
|
771
|
+
# For example, it could be the case that the list of protected attributes
|
772
|
+
# for a given model depends on the role of the user:
|
773
|
+
#
|
774
|
+
# # Assumes plan_id is not protected because it depends on the role.
|
775
|
+
# params[:account] = params[:account].except(:plan_id) unless admin?
|
776
|
+
# @account.update_attributes(params[:account])
|
777
|
+
#
|
778
|
+
# Note that +attr_protected+ is still applied to the received hash. Thus,
|
779
|
+
# with this technique you can at most _extend_ the list of protected
|
780
|
+
# attributes for a particular mass-assignment call.
|
1052
781
|
def attr_protected(*attributes)
|
1053
|
-
write_inheritable_attribute(:attr_protected, Set.new(attributes.map
|
782
|
+
write_inheritable_attribute(:attr_protected, Set.new(attributes.map {|a| a.to_s}) + (protected_attributes || []))
|
1054
783
|
end
|
1055
784
|
|
1056
785
|
# Returns an array of all the attributes that have been protected from mass-assignment.
|
@@ -1082,6 +811,21 @@ module ActiveRecord #:nodoc:
|
|
1082
811
|
#
|
1083
812
|
# customer.credit_rating = "Average"
|
1084
813
|
# customer.credit_rating # => "Average"
|
814
|
+
#
|
815
|
+
# If the access logic of your application is richer you can use <tt>Hash#except</tt>
|
816
|
+
# or <tt>Hash#slice</tt> to sanitize the hash of parameters before they are
|
817
|
+
# passed to Active Record.
|
818
|
+
#
|
819
|
+
# For example, it could be the case that the list of accessible attributes
|
820
|
+
# for a given model depends on the role of the user:
|
821
|
+
#
|
822
|
+
# # Assumes plan_id is accessible because it depends on the role.
|
823
|
+
# params[:account] = params[:account].except(:plan_id) unless admin?
|
824
|
+
# @account.update_attributes(params[:account])
|
825
|
+
#
|
826
|
+
# Note that +attr_accessible+ is still applied to the received hash. Thus,
|
827
|
+
# with this technique you can at most _narrow_ the list of accessible
|
828
|
+
# attributes for a particular mass-assignment call.
|
1085
829
|
def attr_accessible(*attributes)
|
1086
830
|
write_inheritable_attribute(:attr_accessible, Set.new(attributes.map(&:to_s)) + (accessible_attributes || []))
|
1087
831
|
end
|
@@ -1098,7 +842,7 @@ module ActiveRecord #:nodoc:
|
|
1098
842
|
|
1099
843
|
# Returns an array of all the attributes that have been specified as readonly.
|
1100
844
|
def readonly_attributes
|
1101
|
-
read_inheritable_attribute(:attr_readonly)
|
845
|
+
read_inheritable_attribute(:attr_readonly) || []
|
1102
846
|
end
|
1103
847
|
|
1104
848
|
# If you have an attribute that needs to be saved to the database as an object, and retrieved as the same object,
|
@@ -1162,6 +906,10 @@ module ActiveRecord #:nodoc:
|
|
1162
906
|
reset_table_name
|
1163
907
|
end
|
1164
908
|
|
909
|
+
def quoted_table_name
|
910
|
+
@quoted_table_name ||= connection.quote_table_name(table_name)
|
911
|
+
end
|
912
|
+
|
1165
913
|
def reset_table_name #:nodoc:
|
1166
914
|
base = base_class
|
1167
915
|
|
@@ -1176,40 +924,14 @@ module ActiveRecord #:nodoc:
|
|
1176
924
|
contained = contained.singularize if parent.pluralize_table_names
|
1177
925
|
contained << '_'
|
1178
926
|
end
|
1179
|
-
name = "#{
|
927
|
+
name = "#{table_name_prefix}#{contained}#{undecorated_table_name(base.name)}#{table_name_suffix}"
|
1180
928
|
end
|
1181
929
|
|
930
|
+
@quoted_table_name = nil
|
1182
931
|
set_table_name(name)
|
1183
932
|
name
|
1184
933
|
end
|
1185
934
|
|
1186
|
-
# Defines the primary key field -- can be overridden in subclasses. Overwriting will negate any effect of the
|
1187
|
-
# primary_key_prefix_type setting, though.
|
1188
|
-
def primary_key
|
1189
|
-
reset_primary_key
|
1190
|
-
end
|
1191
|
-
|
1192
|
-
def reset_primary_key #:nodoc:
|
1193
|
-
key = get_primary_key(base_class.name)
|
1194
|
-
set_primary_key(key)
|
1195
|
-
key
|
1196
|
-
end
|
1197
|
-
|
1198
|
-
def get_primary_key(base_name) #:nodoc:
|
1199
|
-
key = 'id'
|
1200
|
-
case primary_key_prefix_type
|
1201
|
-
when :table_name
|
1202
|
-
key = base_name.to_s.foreign_key(false)
|
1203
|
-
when :table_name_with_underscore
|
1204
|
-
key = base_name.to_s.foreign_key
|
1205
|
-
end
|
1206
|
-
key
|
1207
|
-
end
|
1208
|
-
|
1209
|
-
def full_table_name_prefix #:nodoc:
|
1210
|
-
(parents.detect{ |p| p.respond_to?(:table_name_prefix) } || self).table_name_prefix
|
1211
|
-
end
|
1212
|
-
|
1213
935
|
# Defines the column name for use with single table inheritance
|
1214
936
|
# -- can be set in subclasses like so: self.inheritance_column = "type_id"
|
1215
937
|
def inheritance_column
|
@@ -1239,18 +961,6 @@ module ActiveRecord #:nodoc:
|
|
1239
961
|
end
|
1240
962
|
alias :table_name= :set_table_name
|
1241
963
|
|
1242
|
-
# Sets the name of the primary key column to use to the given value,
|
1243
|
-
# or (if the value is nil or false) to the value returned by the given
|
1244
|
-
# block.
|
1245
|
-
#
|
1246
|
-
# class Project < ActiveRecord::Base
|
1247
|
-
# set_primary_key "sysid"
|
1248
|
-
# end
|
1249
|
-
def set_primary_key(value = nil, &block)
|
1250
|
-
define_attr_method :primary_key, value, &block
|
1251
|
-
end
|
1252
|
-
alias :primary_key= :set_primary_key
|
1253
|
-
|
1254
964
|
# Sets the name of the inheritance column to use to the given value,
|
1255
965
|
# or (if the value # is nil or false) to the value returned by the
|
1256
966
|
# given block.
|
@@ -1286,8 +996,6 @@ module ActiveRecord #:nodoc:
|
|
1286
996
|
|
1287
997
|
# Turns the +table_name+ back into a class name following the reverse rules of +table_name+.
|
1288
998
|
def class_name(table_name = table_name) # :nodoc:
|
1289
|
-
ActiveSupport::Deprecation.warn("ActiveRecord::Base#class_name is deprecated and will be removed in Rails 3.", caller)
|
1290
|
-
|
1291
999
|
# remove any prefix and/or suffix from the table name
|
1292
1000
|
class_name = table_name[table_name_prefix.length..-(table_name_suffix.length + 1)].camelize
|
1293
1001
|
class_name = class_name.singularize if pluralize_table_names
|
@@ -1365,54 +1073,33 @@ module ActiveRecord #:nodoc:
|
|
1365
1073
|
# end
|
1366
1074
|
# end
|
1367
1075
|
def reset_column_information
|
1368
|
-
|
1369
|
-
@column_names = @columns = @columns_hash = @content_columns = @dynamic_methods_hash = @
|
1076
|
+
undefine_attribute_methods
|
1077
|
+
@column_names = @columns = @columns_hash = @content_columns = @dynamic_methods_hash = @inheritance_column = nil
|
1078
|
+
@arel_engine = @unscoped = @arel_table = nil
|
1370
1079
|
end
|
1371
1080
|
|
1372
1081
|
def reset_column_information_and_inheritable_attributes_for_all_subclasses#:nodoc:
|
1373
1082
|
subclasses.each { |klass| klass.reset_inheritable_attributes; klass.reset_column_information }
|
1374
1083
|
end
|
1375
1084
|
|
1376
|
-
|
1085
|
+
# Set the lookup ancestors for ActiveModel.
|
1086
|
+
def lookup_ancestors #:nodoc:
|
1377
1087
|
klass = self
|
1378
1088
|
classes = [klass]
|
1379
|
-
while klass != klass.base_class
|
1089
|
+
while klass != klass.base_class
|
1380
1090
|
classes << klass = klass.superclass
|
1381
1091
|
end
|
1382
1092
|
classes
|
1383
1093
|
rescue
|
1384
1094
|
# OPTIMIZE this rescue is to fix this test: ./test/cases/reflection_test.rb:56:in `test_human_name_for_column'
|
1385
|
-
#
|
1095
|
+
# Apparently the method base_class causes some trouble.
|
1386
1096
|
# It now works for sure.
|
1387
1097
|
[self]
|
1388
1098
|
end
|
1389
1099
|
|
1390
|
-
#
|
1391
|
-
|
1392
|
-
|
1393
|
-
# module now.
|
1394
|
-
# Specify +options+ with additional translating options.
|
1395
|
-
def human_attribute_name(attribute_key_name, options = {})
|
1396
|
-
defaults = self_and_descendants_from_active_record.map do |klass|
|
1397
|
-
:"#{klass.name.underscore}.#{attribute_key_name}"
|
1398
|
-
end
|
1399
|
-
defaults << options[:default] if options[:default]
|
1400
|
-
defaults.flatten!
|
1401
|
-
defaults << attribute_key_name.to_s.humanize
|
1402
|
-
options[:count] ||= 1
|
1403
|
-
I18n.translate(defaults.shift, options.merge(:default => defaults, :scope => [:activerecord, :attributes]))
|
1404
|
-
end
|
1405
|
-
|
1406
|
-
# Transform the modelname into a more humane format, using I18n.
|
1407
|
-
# Defaults to the basic humanize method.
|
1408
|
-
# Default scope of the translation is activerecord.models
|
1409
|
-
# Specify +options+ with additional translating options.
|
1410
|
-
def human_name(options = {})
|
1411
|
-
defaults = self_and_descendants_from_active_record.map do |klass|
|
1412
|
-
:"#{klass.name.underscore}"
|
1413
|
-
end
|
1414
|
-
defaults << self.name.humanize
|
1415
|
-
I18n.translate(defaults.shift, {:scope => [:activerecord, :models], :count => 1, :default => defaults}.merge(options))
|
1100
|
+
# Set the i18n scope to overwrite ActiveModel.
|
1101
|
+
def i18n_scope #:nodoc:
|
1102
|
+
:activerecord
|
1416
1103
|
end
|
1417
1104
|
|
1418
1105
|
# True if this isn't a concrete subclass needing a STI type condition.
|
@@ -1452,38 +1139,6 @@ module ActiveRecord #:nodoc:
|
|
1452
1139
|
connection.quote(object)
|
1453
1140
|
end
|
1454
1141
|
|
1455
|
-
# Log and benchmark multiple statements in a single block. Example:
|
1456
|
-
#
|
1457
|
-
# Project.benchmark("Creating project") do
|
1458
|
-
# project = Project.create("name" => "stuff")
|
1459
|
-
# project.create_manager("name" => "David")
|
1460
|
-
# project.milestones << Milestone.find(:all)
|
1461
|
-
# end
|
1462
|
-
#
|
1463
|
-
# The benchmark is only recorded if the current level of the logger is less than or equal to the <tt>log_level</tt>,
|
1464
|
-
# which makes it easy to include benchmarking statements in production software that will remain inexpensive because
|
1465
|
-
# the benchmark will only be conducted if the log level is low enough.
|
1466
|
-
#
|
1467
|
-
# The logging of the multiple statements is turned off unless <tt>use_silence</tt> is set to false.
|
1468
|
-
def benchmark(title, log_level = Logger::DEBUG, use_silence = true)
|
1469
|
-
if logger && logger.level <= log_level
|
1470
|
-
result = nil
|
1471
|
-
ms = Benchmark.ms { result = use_silence ? silence { yield } : yield }
|
1472
|
-
logger.add(log_level, '%s (%.1fms)' % [title, ms])
|
1473
|
-
result
|
1474
|
-
else
|
1475
|
-
yield
|
1476
|
-
end
|
1477
|
-
end
|
1478
|
-
|
1479
|
-
# Silences the logger for the duration of the block.
|
1480
|
-
def silence
|
1481
|
-
old_logger_level, logger.level = logger.level, Logger::ERROR if logger
|
1482
|
-
yield
|
1483
|
-
ensure
|
1484
|
-
logger.level = old_logger_level if logger
|
1485
|
-
end
|
1486
|
-
|
1487
1142
|
# Overwrite the default class equality method to provide support for association proxies.
|
1488
1143
|
def ===(object)
|
1489
1144
|
object.is_a?(self)
|
@@ -1519,179 +1174,55 @@ module ActiveRecord #:nodoc:
|
|
1519
1174
|
store_full_sti_class ? name : name.demodulize
|
1520
1175
|
end
|
1521
1176
|
|
1522
|
-
|
1523
|
-
|
1524
|
-
|
1525
|
-
|
1526
|
-
conditions.each do |condition|
|
1527
|
-
unless condition.blank?
|
1528
|
-
sql = sanitize_sql(condition)
|
1529
|
-
segments << sql unless sql.blank?
|
1530
|
-
end
|
1531
|
-
end
|
1532
|
-
|
1533
|
-
"(#{segments.join(') AND (')})" unless segments.empty?
|
1177
|
+
def unscoped
|
1178
|
+
@unscoped ||= Relation.new(self, arel_table)
|
1179
|
+
finder_needs_type_condition? ? @unscoped.where(type_condition) : @unscoped
|
1534
1180
|
end
|
1535
1181
|
|
1536
|
-
|
1537
|
-
|
1538
|
-
|
1539
|
-
find_every(options).first
|
1540
|
-
end
|
1541
|
-
|
1542
|
-
def find_last(options)
|
1543
|
-
order = options[:order]
|
1544
|
-
|
1545
|
-
if order
|
1546
|
-
order = reverse_sql_order(order)
|
1547
|
-
elsif !scoped?(:find, :order)
|
1548
|
-
order = "#{table_name}.#{primary_key} DESC"
|
1549
|
-
end
|
1550
|
-
|
1551
|
-
if scoped?(:find, :order)
|
1552
|
-
scope = scope(:find)
|
1553
|
-
original_scoped_order = scope[:order]
|
1554
|
-
scope[:order] = reverse_sql_order(original_scoped_order)
|
1555
|
-
end
|
1556
|
-
|
1557
|
-
begin
|
1558
|
-
find_initial(options.merge({ :order => order }))
|
1559
|
-
ensure
|
1560
|
-
scope[:order] = original_scoped_order if original_scoped_order
|
1561
|
-
end
|
1562
|
-
end
|
1563
|
-
|
1564
|
-
def reverse_sql_order(order_query)
|
1565
|
-
reversed_query = order_query.to_s.split(/,/).each { |s|
|
1566
|
-
if s.match(/\s(asc|ASC)$/)
|
1567
|
-
s.gsub!(/\s(asc|ASC)$/, ' DESC')
|
1568
|
-
elsif s.match(/\s(desc|DESC)$/)
|
1569
|
-
s.gsub!(/\s(desc|DESC)$/, ' ASC')
|
1570
|
-
elsif !s.match(/\s(asc|ASC|desc|DESC)$/)
|
1571
|
-
s.concat(' DESC')
|
1572
|
-
end
|
1573
|
-
}.join(',')
|
1574
|
-
end
|
1575
|
-
|
1576
|
-
def find_every(options)
|
1577
|
-
include_associations = merge_includes(scope(:find, :include), options[:include])
|
1578
|
-
|
1579
|
-
if include_associations.any? && references_eager_loaded_tables?(options)
|
1580
|
-
records = find_with_associations(options)
|
1581
|
-
else
|
1582
|
-
records = find_by_sql(construct_finder_sql(options))
|
1583
|
-
if include_associations.any?
|
1584
|
-
preload_associations(records, include_associations)
|
1585
|
-
end
|
1586
|
-
end
|
1587
|
-
|
1588
|
-
records.each { |record| record.readonly! } if options[:readonly]
|
1589
|
-
|
1590
|
-
records
|
1591
|
-
end
|
1592
|
-
|
1593
|
-
def find_from_ids(ids, options)
|
1594
|
-
expects_array = ids.first.kind_of?(Array)
|
1595
|
-
return ids.first if expects_array && ids.first.empty?
|
1596
|
-
|
1597
|
-
ids = ids.flatten.compact.uniq
|
1598
|
-
|
1599
|
-
case ids.size
|
1600
|
-
when 0
|
1601
|
-
raise RecordNotFound, "Couldn't find #{name} without an ID"
|
1602
|
-
when 1
|
1603
|
-
result = find_one(ids.first, options)
|
1604
|
-
expects_array ? [ result ] : result
|
1605
|
-
else
|
1606
|
-
find_some(ids, options)
|
1607
|
-
end
|
1608
|
-
end
|
1609
|
-
|
1610
|
-
def find_one(id, options)
|
1611
|
-
conditions = " AND (#{sanitize_sql(options[:conditions])})" if options[:conditions]
|
1612
|
-
options.update :conditions => "#{quoted_table_name}.#{connection.quote_column_name(primary_key)} = #{quote_value(id,columns_hash[primary_key])}#{conditions}"
|
1613
|
-
|
1614
|
-
# Use find_every(options).first since the primary key condition
|
1615
|
-
# already ensures we have a single record. Using find_initial adds
|
1616
|
-
# a superfluous :limit => 1.
|
1617
|
-
if result = find_every(options).first
|
1618
|
-
result
|
1619
|
-
else
|
1620
|
-
raise RecordNotFound, "Couldn't find #{name} with ID=#{id}#{conditions}"
|
1621
|
-
end
|
1622
|
-
end
|
1623
|
-
|
1624
|
-
def find_some(ids, options)
|
1625
|
-
conditions = " AND (#{sanitize_sql(options[:conditions])})" if options[:conditions]
|
1626
|
-
ids_list = ids.map { |id| quote_value(id,columns_hash[primary_key]) }.join(',')
|
1627
|
-
options.update :conditions => "#{quoted_table_name}.#{connection.quote_column_name(primary_key)} IN (#{ids_list})#{conditions}"
|
1628
|
-
|
1629
|
-
result = find_every(options)
|
1630
|
-
|
1631
|
-
# Determine expected size from limit and offset, not just ids.size.
|
1632
|
-
expected_size =
|
1633
|
-
if options[:limit] && ids.size > options[:limit]
|
1634
|
-
options[:limit]
|
1635
|
-
else
|
1636
|
-
ids.size
|
1637
|
-
end
|
1638
|
-
|
1639
|
-
# 11 ids with limit 3, offset 9 should give 2 results.
|
1640
|
-
if options[:offset] && (ids.size - options[:offset] < expected_size)
|
1641
|
-
expected_size = ids.size - options[:offset]
|
1642
|
-
end
|
1182
|
+
def arel_table
|
1183
|
+
@arel_table ||= Arel::Table.new(table_name, :engine => arel_engine)
|
1184
|
+
end
|
1643
1185
|
|
1644
|
-
|
1645
|
-
|
1186
|
+
def arel_engine
|
1187
|
+
@arel_engine ||= begin
|
1188
|
+
if self == ActiveRecord::Base
|
1189
|
+
Arel::Table.engine
|
1646
1190
|
else
|
1647
|
-
|
1191
|
+
connection_handler.connection_pools[name] ? Arel::Sql::Engine.new(self) : superclass.arel_engine
|
1648
1192
|
end
|
1649
1193
|
end
|
1194
|
+
end
|
1650
1195
|
|
1196
|
+
private
|
1651
1197
|
# Finder methods must instantiate through this method to work with the
|
1652
1198
|
# single-table inheritance model that makes it possible to create
|
1653
1199
|
# objects of different types from the same table.
|
1654
1200
|
def instantiate(record)
|
1655
|
-
object =
|
1656
|
-
if subclass_name = record[inheritance_column]
|
1657
|
-
# No type given.
|
1658
|
-
if subclass_name.empty?
|
1659
|
-
allocate
|
1201
|
+
object = find_sti_class(record[inheritance_column]).allocate
|
1660
1202
|
|
1661
|
-
|
1662
|
-
|
1663
|
-
# pulled in from a sloppy join.
|
1664
|
-
unless columns_hash.include?(inheritance_column)
|
1665
|
-
allocate
|
1666
|
-
|
1667
|
-
else
|
1668
|
-
begin
|
1669
|
-
compute_type(subclass_name).allocate
|
1670
|
-
rescue NameError
|
1671
|
-
raise SubclassNotFound,
|
1672
|
-
"The single-table inheritance mechanism failed to locate the subclass: '#{record[inheritance_column]}'. " +
|
1673
|
-
"This error is raised because the column '#{inheritance_column}' is reserved for storing the class in case of inheritance. " +
|
1674
|
-
"Please rename this column if you didn't intend it to be used for storing the inheritance class " +
|
1675
|
-
"or overwrite #{self.to_s}.inheritance_column to use another column for that information."
|
1676
|
-
end
|
1677
|
-
end
|
1678
|
-
end
|
1679
|
-
else
|
1680
|
-
allocate
|
1681
|
-
end
|
1203
|
+
object.instance_variable_set(:'@attributes', record)
|
1204
|
+
object.instance_variable_set(:'@attributes_cache', {})
|
1682
1205
|
|
1683
|
-
object.
|
1684
|
-
object.
|
1206
|
+
object.send(:_run_find_callbacks)
|
1207
|
+
object.send(:_run_initialize_callbacks)
|
1685
1208
|
|
1686
|
-
|
1687
|
-
|
1688
|
-
end
|
1209
|
+
object
|
1210
|
+
end
|
1689
1211
|
|
1690
|
-
|
1691
|
-
|
1212
|
+
def find_sti_class(type_name)
|
1213
|
+
if type_name.blank? || !columns_hash.include?(inheritance_column)
|
1214
|
+
self
|
1215
|
+
else
|
1216
|
+
begin
|
1217
|
+
compute_type(type_name)
|
1218
|
+
rescue NameError
|
1219
|
+
raise SubclassNotFound,
|
1220
|
+
"The single-table inheritance mechanism failed to locate the subclass: '#{type_name}'. " +
|
1221
|
+
"This error is raised because the column '#{inheritance_column}' is reserved for storing the class in case of inheritance. " +
|
1222
|
+
"Please rename this column if you didn't intend it to be used for storing the inheritance class " +
|
1223
|
+
"or overwrite #{name}.inheritance_column to use another column for that information."
|
1224
|
+
end
|
1692
1225
|
end
|
1693
|
-
|
1694
|
-
object
|
1695
1226
|
end
|
1696
1227
|
|
1697
1228
|
# Nest the type name in the same module as this class.
|
@@ -1704,149 +1235,18 @@ module ActiveRecord #:nodoc:
|
|
1704
1235
|
end
|
1705
1236
|
end
|
1706
1237
|
|
1707
|
-
def
|
1708
|
-
|
1709
|
-
|
1710
|
-
|
1711
|
-
'*'
|
1712
|
-
end
|
1238
|
+
def construct_finder_arel(options = {}, scope = nil)
|
1239
|
+
relation = options.is_a?(Hash) ? unscoped.apply_finder_options(options) : unscoped.merge(options)
|
1240
|
+
relation = scope.merge(relation) if scope
|
1241
|
+
relation
|
1713
1242
|
end
|
1714
1243
|
|
1715
|
-
def
|
1716
|
-
|
1717
|
-
|
1718
|
-
|
1719
|
-
|
1720
|
-
add_joins!(sql, options[:joins], scope)
|
1721
|
-
add_conditions!(sql, options[:conditions], scope)
|
1244
|
+
def type_condition
|
1245
|
+
sti_column = arel_table[inheritance_column]
|
1246
|
+
condition = sti_column.eq(sti_name)
|
1247
|
+
subclasses.each{|subclass| condition = condition.or(sti_column.eq(subclass.sti_name)) }
|
1722
1248
|
|
1723
|
-
|
1724
|
-
add_order!(sql, options[:order], scope)
|
1725
|
-
add_limit!(sql, options, scope)
|
1726
|
-
add_lock!(sql, options, scope)
|
1727
|
-
|
1728
|
-
sql
|
1729
|
-
end
|
1730
|
-
|
1731
|
-
# Merges includes so that the result is a valid +include+
|
1732
|
-
def merge_includes(first, second)
|
1733
|
-
(safe_to_array(first) + safe_to_array(second)).uniq
|
1734
|
-
end
|
1735
|
-
|
1736
|
-
def merge_joins(*joins)
|
1737
|
-
if joins.any?{|j| j.is_a?(String) || array_of_strings?(j) }
|
1738
|
-
joins = joins.collect do |join|
|
1739
|
-
join = [join] if join.is_a?(String)
|
1740
|
-
unless array_of_strings?(join)
|
1741
|
-
join_dependency = ActiveRecord::Associations::ClassMethods::InnerJoinDependency.new(self, join, nil)
|
1742
|
-
join = join_dependency.join_associations.collect { |assoc| assoc.association_join }
|
1743
|
-
end
|
1744
|
-
join
|
1745
|
-
end
|
1746
|
-
joins.flatten.map{|j| j.strip}.uniq
|
1747
|
-
else
|
1748
|
-
joins.collect{|j| safe_to_array(j)}.flatten.uniq
|
1749
|
-
end
|
1750
|
-
end
|
1751
|
-
|
1752
|
-
# Object#to_a is deprecated, though it does have the desired behavior
|
1753
|
-
def safe_to_array(o)
|
1754
|
-
case o
|
1755
|
-
when NilClass
|
1756
|
-
[]
|
1757
|
-
when Array
|
1758
|
-
o
|
1759
|
-
else
|
1760
|
-
[o]
|
1761
|
-
end
|
1762
|
-
end
|
1763
|
-
|
1764
|
-
def array_of_strings?(o)
|
1765
|
-
o.is_a?(Array) && o.all?{|obj| obj.is_a?(String)}
|
1766
|
-
end
|
1767
|
-
|
1768
|
-
def add_order!(sql, order, scope = :auto)
|
1769
|
-
scope = scope(:find) if :auto == scope
|
1770
|
-
scoped_order = scope[:order] if scope
|
1771
|
-
if order
|
1772
|
-
sql << " ORDER BY #{order}"
|
1773
|
-
if scoped_order && scoped_order != order
|
1774
|
-
sql << ", #{scoped_order}"
|
1775
|
-
end
|
1776
|
-
else
|
1777
|
-
sql << " ORDER BY #{scoped_order}" if scoped_order
|
1778
|
-
end
|
1779
|
-
end
|
1780
|
-
|
1781
|
-
def add_group!(sql, group, having, scope = :auto)
|
1782
|
-
if group
|
1783
|
-
sql << " GROUP BY #{group}"
|
1784
|
-
sql << " HAVING #{sanitize_sql_for_conditions(having)}" if having
|
1785
|
-
else
|
1786
|
-
scope = scope(:find) if :auto == scope
|
1787
|
-
if scope && (scoped_group = scope[:group])
|
1788
|
-
sql << " GROUP BY #{scoped_group}"
|
1789
|
-
sql << " HAVING #{sanitize_sql_for_conditions(scope[:having])}" if scope[:having]
|
1790
|
-
end
|
1791
|
-
end
|
1792
|
-
end
|
1793
|
-
|
1794
|
-
# The optional scope argument is for the current <tt>:find</tt> scope.
|
1795
|
-
def add_limit!(sql, options, scope = :auto)
|
1796
|
-
scope = scope(:find) if :auto == scope
|
1797
|
-
|
1798
|
-
if scope
|
1799
|
-
options[:limit] ||= scope[:limit]
|
1800
|
-
options[:offset] ||= scope[:offset]
|
1801
|
-
end
|
1802
|
-
|
1803
|
-
connection.add_limit_offset!(sql, options)
|
1804
|
-
end
|
1805
|
-
|
1806
|
-
# The optional scope argument is for the current <tt>:find</tt> scope.
|
1807
|
-
# The <tt>:lock</tt> option has precedence over a scoped <tt>:lock</tt>.
|
1808
|
-
def add_lock!(sql, options, scope = :auto)
|
1809
|
-
scope = scope(:find) if :auto == scope
|
1810
|
-
options = options.reverse_merge(:lock => scope[:lock]) if scope
|
1811
|
-
connection.add_lock!(sql, options)
|
1812
|
-
end
|
1813
|
-
|
1814
|
-
# The optional scope argument is for the current <tt>:find</tt> scope.
|
1815
|
-
def add_joins!(sql, joins, scope = :auto)
|
1816
|
-
scope = scope(:find) if :auto == scope
|
1817
|
-
merged_joins = scope && scope[:joins] && joins ? merge_joins(scope[:joins], joins) : (joins || scope && scope[:joins])
|
1818
|
-
case merged_joins
|
1819
|
-
when Symbol, Hash, Array
|
1820
|
-
if array_of_strings?(merged_joins)
|
1821
|
-
sql << merged_joins.join(' ') + " "
|
1822
|
-
else
|
1823
|
-
join_dependency = ActiveRecord::Associations::ClassMethods::InnerJoinDependency.new(self, merged_joins, nil)
|
1824
|
-
sql << " #{join_dependency.join_associations.collect { |assoc| assoc.association_join }.join} "
|
1825
|
-
end
|
1826
|
-
when String
|
1827
|
-
sql << " #{merged_joins} "
|
1828
|
-
end
|
1829
|
-
end
|
1830
|
-
|
1831
|
-
# Adds a sanitized version of +conditions+ to the +sql+ string. Note that the passed-in +sql+ string is changed.
|
1832
|
-
# The optional scope argument is for the current <tt>:find</tt> scope.
|
1833
|
-
def add_conditions!(sql, conditions, scope = :auto)
|
1834
|
-
scope = scope(:find) if :auto == scope
|
1835
|
-
conditions = [conditions]
|
1836
|
-
conditions << scope[:conditions] if scope
|
1837
|
-
conditions << type_condition if finder_needs_type_condition?
|
1838
|
-
merged_conditions = merge_conditions(*conditions)
|
1839
|
-
sql << "WHERE #{merged_conditions} " unless merged_conditions.blank?
|
1840
|
-
end
|
1841
|
-
|
1842
|
-
def type_condition(table_alias=nil)
|
1843
|
-
quoted_table_alias = self.connection.quote_table_name(table_alias || table_name)
|
1844
|
-
quoted_inheritance_column = connection.quote_column_name(inheritance_column)
|
1845
|
-
type_condition = subclasses.inject("#{quoted_table_alias}.#{quoted_inheritance_column} = '#{sti_name}' ") do |condition, subclass|
|
1846
|
-
condition << "OR #{quoted_table_alias}.#{quoted_inheritance_column} = '#{subclass.sti_name}' "
|
1847
|
-
end
|
1848
|
-
|
1849
|
-
" (#{type_condition}) "
|
1249
|
+
condition
|
1850
1250
|
end
|
1851
1251
|
|
1852
1252
|
# Guesses the table name, but does not decorate it with prefix and suffix information.
|
@@ -1857,9 +1257,8 @@ module ActiveRecord #:nodoc:
|
|
1857
1257
|
end
|
1858
1258
|
|
1859
1259
|
# Enables dynamic finders like <tt>find_by_user_name(user_name)</tt> and <tt>find_by_user_name_and_password(user_name, password)</tt>
|
1860
|
-
# that are turned into <tt>
|
1861
|
-
# <tt>
|
1862
|
-
# <tt>find(:all)</tt> by using <tt>find_all_by_amount(50)</tt> that is turned into <tt>find(:all, :conditions => ["amount = ?", 50])</tt>.
|
1260
|
+
# that are turned into <tt>where(:user_name => user_name).first</tt> and <tt>where(:user_name => user_name, :password => :password).first</tt>
|
1261
|
+
# respectively. Also works for <tt>all</tt> by using <tt>find_all_by_amount(50)</tt> that is turned into <tt>where(:amount => 50).all</tt>.
|
1863
1262
|
#
|
1864
1263
|
# It's even possible to use all the additional parameters to +find+. For example, the full interface for +find_all_by_amount+
|
1865
1264
|
# is actually <tt>find_all_by_amount(amount, options)</tt>.
|
@@ -1875,127 +1274,26 @@ module ActiveRecord #:nodoc:
|
|
1875
1274
|
attribute_names = match.attribute_names
|
1876
1275
|
super unless all_attributes_exists?(attribute_names)
|
1877
1276
|
if match.finder?
|
1878
|
-
|
1879
|
-
|
1880
|
-
|
1881
|
-
# options = args.extract_options!
|
1882
|
-
# attributes = construct_attributes_from_arguments(
|
1883
|
-
# [:login,:activated],
|
1884
|
-
# args
|
1885
|
-
# )
|
1886
|
-
# finder_options = { :conditions => attributes }
|
1887
|
-
# validate_find_options(options)
|
1888
|
-
# set_readonly_option!(options)
|
1889
|
-
#
|
1890
|
-
# if options[:conditions]
|
1891
|
-
# with_scope(:find => finder_options) do
|
1892
|
-
# find(:first, options)
|
1893
|
-
# end
|
1894
|
-
# else
|
1895
|
-
# find(:first, options.merge(finder_options))
|
1896
|
-
# end
|
1897
|
-
# end
|
1898
|
-
self.class_eval <<-EOS, __FILE__, __LINE__ + 1
|
1899
|
-
def self.#{method_id}(*args)
|
1900
|
-
options = if args.length > #{attribute_names.size}
|
1901
|
-
args.extract_options!
|
1902
|
-
else
|
1903
|
-
{}
|
1904
|
-
end
|
1905
|
-
attributes = construct_attributes_from_arguments(
|
1906
|
-
[:#{attribute_names.join(',:')}],
|
1907
|
-
args
|
1908
|
-
)
|
1909
|
-
finder_options = { :conditions => attributes }
|
1910
|
-
validate_find_options(options)
|
1911
|
-
set_readonly_option!(options)
|
1912
|
-
|
1913
|
-
#{'result = ' if bang}if options[:conditions]
|
1914
|
-
with_scope(:find => finder_options) do
|
1915
|
-
find(:#{finder}, options)
|
1916
|
-
end
|
1917
|
-
else
|
1918
|
-
find(:#{finder}, options.merge(finder_options))
|
1919
|
-
end
|
1920
|
-
#{'result || raise(RecordNotFound, "Couldn\'t find #{name} with #{attributes.to_a.collect {|pair| "#{pair.first} = #{pair.second}"}.join(\', \')}")' if bang}
|
1921
|
-
end
|
1922
|
-
EOS
|
1923
|
-
send(method_id, *arguments)
|
1277
|
+
options = arguments.extract_options!
|
1278
|
+
relation = options.any? ? construct_finder_arel(options, current_scoped_methods) : scoped
|
1279
|
+
relation.send :find_by_attributes, match, attribute_names, *arguments
|
1924
1280
|
elsif match.instantiator?
|
1925
|
-
|
1926
|
-
# def self.find_or_create_by_user_id(*args)
|
1927
|
-
# guard_protected_attributes = false
|
1928
|
-
#
|
1929
|
-
# if args[0].is_a?(Hash)
|
1930
|
-
# guard_protected_attributes = true
|
1931
|
-
# attributes = args[0].with_indifferent_access
|
1932
|
-
# find_attributes = attributes.slice(*[:user_id])
|
1933
|
-
# else
|
1934
|
-
# find_attributes = attributes = construct_attributes_from_arguments([:user_id], args)
|
1935
|
-
# end
|
1936
|
-
#
|
1937
|
-
# options = { :conditions => find_attributes }
|
1938
|
-
# set_readonly_option!(options)
|
1939
|
-
#
|
1940
|
-
# record = find(:first, options)
|
1941
|
-
#
|
1942
|
-
# if record.nil?
|
1943
|
-
# record = self.new { |r| r.send(:attributes=, attributes, guard_protected_attributes) }
|
1944
|
-
# yield(record) if block_given?
|
1945
|
-
# record.save
|
1946
|
-
# record
|
1947
|
-
# else
|
1948
|
-
# record
|
1949
|
-
# end
|
1950
|
-
# end
|
1951
|
-
self.class_eval <<-EOS, __FILE__, __LINE__ + 1
|
1952
|
-
def self.#{method_id}(*args)
|
1953
|
-
attributes = [:#{attribute_names.join(',:')}]
|
1954
|
-
protected_attributes_for_create, unprotected_attributes_for_create = {}, {}
|
1955
|
-
args.each_with_index do |arg, i|
|
1956
|
-
if arg.is_a?(Hash)
|
1957
|
-
protected_attributes_for_create = args[i].with_indifferent_access
|
1958
|
-
else
|
1959
|
-
unprotected_attributes_for_create[attributes[i]] = args[i]
|
1960
|
-
end
|
1961
|
-
end
|
1962
|
-
|
1963
|
-
find_attributes = (protected_attributes_for_create.merge(unprotected_attributes_for_create)).slice(*attributes)
|
1964
|
-
|
1965
|
-
options = { :conditions => find_attributes }
|
1966
|
-
set_readonly_option!(options)
|
1967
|
-
|
1968
|
-
record = find(:first, options)
|
1969
|
-
|
1970
|
-
if record.nil?
|
1971
|
-
record = self.new do |r|
|
1972
|
-
r.send(:attributes=, protected_attributes_for_create, true) unless protected_attributes_for_create.empty?
|
1973
|
-
r.send(:attributes=, unprotected_attributes_for_create, false) unless unprotected_attributes_for_create.empty?
|
1974
|
-
end
|
1975
|
-
#{'yield(record) if block_given?'}
|
1976
|
-
#{'record.save' if instantiator == :create}
|
1977
|
-
record
|
1978
|
-
else
|
1979
|
-
record
|
1980
|
-
end
|
1981
|
-
end
|
1982
|
-
EOS
|
1983
|
-
send(method_id, *arguments, &block)
|
1281
|
+
scoped.send :find_or_instantiator_by_attributes, match, attribute_names, *arguments, &block
|
1984
1282
|
end
|
1985
1283
|
elsif match = DynamicScopeMatch.match(method_id)
|
1986
1284
|
attribute_names = match.attribute_names
|
1987
1285
|
super unless all_attributes_exists?(attribute_names)
|
1988
1286
|
if match.scope?
|
1989
|
-
self.class_eval
|
1287
|
+
self.class_eval %{
|
1990
1288
|
def self.#{method_id}(*args) # def self.scoped_by_user_name_and_password(*args)
|
1991
1289
|
options = args.extract_options! # options = args.extract_options!
|
1992
1290
|
attributes = construct_attributes_from_arguments( # attributes = construct_attributes_from_arguments(
|
1993
1291
|
[:#{attribute_names.join(',:')}], args # [:user_name, :password], args
|
1994
1292
|
) # )
|
1995
|
-
#
|
1293
|
+
#
|
1996
1294
|
scoped(:conditions => attributes) # scoped(:conditions => attributes)
|
1997
1295
|
end # end
|
1998
|
-
|
1296
|
+
}, __FILE__, __LINE__
|
1999
1297
|
send(method_id, *arguments)
|
2000
1298
|
end
|
2001
1299
|
else
|
@@ -2042,44 +1340,6 @@ module ActiveRecord #:nodoc:
|
|
2042
1340
|
end
|
2043
1341
|
end
|
2044
1342
|
|
2045
|
-
# Interpret Array and Hash as conditions and anything else as an id.
|
2046
|
-
def expand_id_conditions(id_or_conditions)
|
2047
|
-
case id_or_conditions
|
2048
|
-
when Array, Hash then id_or_conditions
|
2049
|
-
else sanitize_sql(primary_key => id_or_conditions)
|
2050
|
-
end
|
2051
|
-
end
|
2052
|
-
|
2053
|
-
# Defines an "attribute" method (like +inheritance_column+ or
|
2054
|
-
# +table_name+). A new (class) method will be created with the
|
2055
|
-
# given name. If a value is specified, the new method will
|
2056
|
-
# return that value (as a string). Otherwise, the given block
|
2057
|
-
# will be used to compute the value of the method.
|
2058
|
-
#
|
2059
|
-
# The original method will be aliased, with the new name being
|
2060
|
-
# prefixed with "original_". This allows the new method to
|
2061
|
-
# access the original value.
|
2062
|
-
#
|
2063
|
-
# Example:
|
2064
|
-
#
|
2065
|
-
# class A < ActiveRecord::Base
|
2066
|
-
# define_attr_method :primary_key, "sysid"
|
2067
|
-
# define_attr_method( :inheritance_column ) do
|
2068
|
-
# original_inheritance_column + "_id"
|
2069
|
-
# end
|
2070
|
-
# end
|
2071
|
-
def define_attr_method(name, value=nil, &block)
|
2072
|
-
sing = class << self; self; end
|
2073
|
-
sing.send :alias_method, "original_#{name}", name
|
2074
|
-
if block_given?
|
2075
|
-
sing.send :define_method, name, &block
|
2076
|
-
else
|
2077
|
-
# use eval instead of a block to work around a memory leak in dev
|
2078
|
-
# mode in fcgi
|
2079
|
-
sing.class_eval "def #{name}; #{value.to_s.inspect}; end"
|
2080
|
-
end
|
2081
|
-
end
|
2082
|
-
|
2083
1343
|
protected
|
2084
1344
|
# Scope parameters to method calls within the block. Takes a hash of method_name => parameters hash.
|
2085
1345
|
# method_name may be <tt>:find</tt> or <tt>:create</tt>. <tt>:find</tt> parameters may include the <tt>:conditions</tt>, <tt>:joins</tt>,
|
@@ -2105,10 +1365,10 @@ module ActiveRecord #:nodoc:
|
|
2105
1365
|
# class Article < ActiveRecord::Base
|
2106
1366
|
# def self.find_with_scope
|
2107
1367
|
# with_scope(:find => { :conditions => "blog_id = 1", :limit => 1 }, :create => { :blog_id => 1 }) do
|
2108
|
-
# with_scope(:find => { :limit => 10 })
|
1368
|
+
# with_scope(:find => { :limit => 10 }) do
|
2109
1369
|
# find(:all) # => SELECT * from articles WHERE blog_id = 1 LIMIT 10
|
2110
1370
|
# end
|
2111
|
-
# with_scope(:find => { :conditions => "author_id = 3" })
|
1371
|
+
# with_scope(:find => { :conditions => "author_id = 3" }) do
|
2112
1372
|
# find(:all) # => SELECT * from articles WHERE blog_id = 1 AND author_id = 3 LIMIT 1
|
2113
1373
|
# end
|
2114
1374
|
# end
|
@@ -2132,55 +1392,35 @@ module ActiveRecord #:nodoc:
|
|
2132
1392
|
def with_scope(method_scoping = {}, action = :merge, &block)
|
2133
1393
|
method_scoping = method_scoping.method_scoping if method_scoping.respond_to?(:method_scoping)
|
2134
1394
|
|
2135
|
-
|
2136
|
-
|
2137
|
-
|
2138
|
-
|
2139
|
-
|
2140
|
-
|
2141
|
-
method_scoping.assert_valid_keys([ :find, :create ])
|
1395
|
+
if method_scoping.is_a?(Hash)
|
1396
|
+
# Dup first and second level of hash (method and params).
|
1397
|
+
method_scoping = method_scoping.inject({}) do |hash, (method, params)|
|
1398
|
+
hash[method] = (params == true) ? params : params.dup
|
1399
|
+
hash
|
1400
|
+
end
|
2142
1401
|
|
2143
|
-
|
2144
|
-
|
2145
|
-
set_readonly_option! f
|
2146
|
-
end
|
1402
|
+
method_scoping.assert_valid_keys([ :find, :create ])
|
1403
|
+
relation = construct_finder_arel(method_scoping[:find] || {})
|
2147
1404
|
|
2148
|
-
|
2149
|
-
|
2150
|
-
|
2151
|
-
|
2152
|
-
|
2153
|
-
if method == :find
|
2154
|
-
(hash[method].keys + params.keys).uniq.each do |key|
|
2155
|
-
merge = hash[method][key] && params[key] # merge if both scopes have the same key
|
2156
|
-
if key == :conditions && merge
|
2157
|
-
if params[key].is_a?(Hash) && hash[method][key].is_a?(Hash)
|
2158
|
-
hash[method][key] = merge_conditions(hash[method][key].deep_merge(params[key]))
|
2159
|
-
else
|
2160
|
-
hash[method][key] = merge_conditions(params[key], hash[method][key])
|
2161
|
-
end
|
2162
|
-
elsif key == :include && merge
|
2163
|
-
hash[method][key] = merge_includes(hash[method][key], params[key]).uniq
|
2164
|
-
elsif key == :joins && merge
|
2165
|
-
hash[method][key] = merge_joins(params[key], hash[method][key])
|
2166
|
-
else
|
2167
|
-
hash[method][key] = hash[method][key] || params[key]
|
2168
|
-
end
|
2169
|
-
end
|
2170
|
-
else
|
2171
|
-
if action == :reverse_merge
|
2172
|
-
hash[method] = hash[method].merge(params)
|
2173
|
-
else
|
2174
|
-
hash[method] = params.merge(hash[method])
|
2175
|
-
end
|
2176
|
-
end
|
2177
|
-
else
|
2178
|
-
hash[method] = params
|
1405
|
+
if current_scoped_methods && current_scoped_methods.create_with_value && method_scoping[:create]
|
1406
|
+
scope_for_create = if action == :merge
|
1407
|
+
current_scoped_methods.create_with_value.merge(method_scoping[:create])
|
1408
|
+
else
|
1409
|
+
method_scoping[:create]
|
2179
1410
|
end
|
2180
|
-
|
1411
|
+
|
1412
|
+
relation = relation.create_with(scope_for_create)
|
1413
|
+
else
|
1414
|
+
scope_for_create = method_scoping[:create]
|
1415
|
+
scope_for_create ||= current_scoped_methods.create_with_value if current_scoped_methods
|
1416
|
+
relation = relation.create_with(scope_for_create) if scope_for_create
|
2181
1417
|
end
|
1418
|
+
|
1419
|
+
method_scoping = relation
|
2182
1420
|
end
|
2183
1421
|
|
1422
|
+
method_scoping = current_scoped_methods.merge(method_scoping) if current_scoped_methods && action == :merge
|
1423
|
+
|
2184
1424
|
self.scoped_methods << method_scoping
|
2185
1425
|
begin
|
2186
1426
|
yield
|
@@ -2206,25 +1446,12 @@ module ActiveRecord #:nodoc:
|
|
2206
1446
|
# default_scope :order => 'last_name, first_name'
|
2207
1447
|
# end
|
2208
1448
|
def default_scope(options = {})
|
2209
|
-
self.default_scoping <<
|
2210
|
-
end
|
2211
|
-
|
2212
|
-
# Test whether the given method and optional key are scoped.
|
2213
|
-
def scoped?(method, key = nil) #:nodoc:
|
2214
|
-
if current_scoped_methods && (scope = current_scoped_methods[method])
|
2215
|
-
!key || !scope[key].nil?
|
2216
|
-
end
|
2217
|
-
end
|
2218
|
-
|
2219
|
-
# Retrieve the scope for the given method and optional key.
|
2220
|
-
def scope(method, key = nil) #:nodoc:
|
2221
|
-
if current_scoped_methods && (scope = current_scoped_methods[method])
|
2222
|
-
key ? scope[key] : scope
|
2223
|
-
end
|
1449
|
+
self.default_scoping << construct_finder_arel(options)
|
2224
1450
|
end
|
2225
1451
|
|
2226
1452
|
def scoped_methods #:nodoc:
|
2227
|
-
|
1453
|
+
key = :"#{self}_scoped_methods"
|
1454
|
+
Thread.current[key] = Thread.current[key].presence || self.default_scoping.dup
|
2228
1455
|
end
|
2229
1456
|
|
2230
1457
|
def current_scoped_methods #:nodoc:
|
@@ -2237,9 +1464,9 @@ module ActiveRecord #:nodoc:
|
|
2237
1464
|
modularized_name = type_name_with_module(type_name)
|
2238
1465
|
silence_warnings do
|
2239
1466
|
begin
|
2240
|
-
class_eval(modularized_name, __FILE__)
|
1467
|
+
class_eval(modularized_name, __FILE__, __LINE__)
|
2241
1468
|
rescue NameError
|
2242
|
-
class_eval(type_name, __FILE__)
|
1469
|
+
class_eval(type_name, __FILE__, __LINE__)
|
2243
1470
|
end
|
2244
1471
|
end
|
2245
1472
|
end
|
@@ -2266,7 +1493,7 @@ module ActiveRecord #:nodoc:
|
|
2266
1493
|
# ["name='%s' and group_id='%s'", "foo'bar", 4] returns "name='foo''bar' and group_id='4'"
|
2267
1494
|
# { :name => "foo'bar", :group_id => 4 } returns "name='foo''bar' and group_id='4'"
|
2268
1495
|
# "name='foo''bar' and group_id='4'" returns "name='foo''bar' and group_id='4'"
|
2269
|
-
def sanitize_sql_for_conditions(condition, table_name =
|
1496
|
+
def sanitize_sql_for_conditions(condition, table_name = self.table_name)
|
2270
1497
|
return nil if condition.blank?
|
2271
1498
|
|
2272
1499
|
case condition
|
@@ -2307,7 +1534,7 @@ module ActiveRecord #:nodoc:
|
|
2307
1534
|
def expand_hash_conditions_for_aggregates(attrs)
|
2308
1535
|
expanded_attrs = {}
|
2309
1536
|
attrs.each do |attr, value|
|
2310
|
-
unless (aggregation = reflect_on_aggregation(attr)).nil?
|
1537
|
+
unless (aggregation = reflect_on_aggregation(attr.to_sym)).nil?
|
2311
1538
|
mapping = aggregate_mapping(aggregation)
|
2312
1539
|
mapping.each do |field_attr, aggregate_attr|
|
2313
1540
|
if mapping.size == 1 && !value.respond_to?(aggregate_attr)
|
@@ -2337,34 +1564,12 @@ module ActiveRecord #:nodoc:
|
|
2337
1564
|
# And for value objects on a composed_of relationship:
|
2338
1565
|
# { :address => Address.new("123 abc st.", "chicago") }
|
2339
1566
|
# # => "address_street='123 abc st.' and address_city='chicago'"
|
2340
|
-
def sanitize_sql_hash_for_conditions(attrs, default_table_name =
|
1567
|
+
def sanitize_sql_hash_for_conditions(attrs, default_table_name = self.table_name)
|
2341
1568
|
attrs = expand_hash_conditions_for_aggregates(attrs)
|
2342
1569
|
|
2343
|
-
|
2344
|
-
|
2345
|
-
|
2346
|
-
table_name = default_table_name
|
2347
|
-
|
2348
|
-
if not value.is_a?(Hash)
|
2349
|
-
attr = attr.to_s
|
2350
|
-
|
2351
|
-
# Extract table name from qualified attribute names.
|
2352
|
-
if attr.include?('.') and top_level
|
2353
|
-
attr_table_name, attr = attr.split('.', 2)
|
2354
|
-
attr_table_name = connection.quote_table_name(attr_table_name)
|
2355
|
-
else
|
2356
|
-
attr_table_name = table_name
|
2357
|
-
end
|
2358
|
-
|
2359
|
-
attribute_condition("#{attr_table_name}.#{connection.quote_column_name(attr)}", value)
|
2360
|
-
elsif top_level
|
2361
|
-
sanitize_sql_hash_for_conditions(value, connection.quote_table_name(attr.to_s), false)
|
2362
|
-
else
|
2363
|
-
raise ActiveRecord::StatementInvalid
|
2364
|
-
end
|
2365
|
-
end.join(' AND ')
|
2366
|
-
|
2367
|
-
replace_bind_variables(conditions, expand_range_bind_variables(attrs.values))
|
1570
|
+
table = Arel::Table.new(self.table_name, :engine => arel_engine, :as => default_table_name)
|
1571
|
+
builder = PredicateBuilder.new(arel_engine)
|
1572
|
+
builder.build_from_hash(attrs, table).map(&:to_sql).join(' AND ')
|
2368
1573
|
end
|
2369
1574
|
alias_method :sanitize_sql_hash, :sanitize_sql_hash_for_conditions
|
2370
1575
|
|
@@ -2446,25 +1651,6 @@ module ActiveRecord #:nodoc:
|
|
2446
1651
|
end
|
2447
1652
|
end
|
2448
1653
|
|
2449
|
-
VALID_FIND_OPTIONS = [ :conditions, :include, :joins, :limit, :offset,
|
2450
|
-
:order, :select, :readonly, :group, :having, :from, :lock ]
|
2451
|
-
|
2452
|
-
def validate_find_options(options) #:nodoc:
|
2453
|
-
options.assert_valid_keys(VALID_FIND_OPTIONS)
|
2454
|
-
end
|
2455
|
-
|
2456
|
-
def set_readonly_option!(options) #:nodoc:
|
2457
|
-
# Inherit :readonly from finder scope if set. Otherwise,
|
2458
|
-
# if :joins is not blank then :readonly defaults to true.
|
2459
|
-
unless options.has_key?(:readonly)
|
2460
|
-
if scoped_readonly = scope(:find, :readonly)
|
2461
|
-
options[:readonly] = scoped_readonly
|
2462
|
-
elsif !options[:joins].blank? && !options[:select]
|
2463
|
-
options[:readonly] = true
|
2464
|
-
end
|
2465
|
-
end
|
2466
|
-
end
|
2467
|
-
|
2468
1654
|
def encode_quoted_value(value) #:nodoc:
|
2469
1655
|
quoted_value = connection.quote(value)
|
2470
1656
|
quoted_value = "'#{quoted_value[1..-2].gsub(/\'/, "\\\\'")}'" if quoted_value.include?("\\\'") # (for ruby mode) "
|
@@ -2483,22 +1669,42 @@ module ActiveRecord #:nodoc:
|
|
2483
1669
|
@new_record = true
|
2484
1670
|
ensure_proper_type
|
2485
1671
|
self.attributes = attributes unless attributes.nil?
|
2486
|
-
|
1672
|
+
|
1673
|
+
if scope = self.class.send(:current_scoped_methods)
|
1674
|
+
create_with = scope.scope_for_create
|
1675
|
+
create_with.each { |att,value| self.send("#{att}=", value) } if create_with
|
1676
|
+
end
|
1677
|
+
|
2487
1678
|
result = yield self if block_given?
|
2488
|
-
|
1679
|
+
_run_initialize_callbacks
|
2489
1680
|
result
|
2490
1681
|
end
|
2491
1682
|
|
2492
|
-
#
|
2493
|
-
#
|
2494
|
-
|
2495
|
-
|
2496
|
-
|
2497
|
-
|
2498
|
-
|
2499
|
-
#
|
2500
|
-
|
1683
|
+
# Cloned objects have no id assigned and are treated as new records. Note that this is a "shallow" clone
|
1684
|
+
# as it copies the object's attributes only, not its associations. The extent of a "deep" clone is
|
1685
|
+
# application specific and is therefore left to the application to implement according to its need.
|
1686
|
+
def initialize_copy(other)
|
1687
|
+
# Think the assertion which fails if the after_initialize callback goes at the end of the method is wrong. The
|
1688
|
+
# deleted clone method called new which therefore called the after_initialize callback. It then went on to copy
|
1689
|
+
# over the attributes. But if it's copying the attributes afterwards then it hasn't finished initializing right?
|
1690
|
+
# For example in the test suite the topic model's after_initialize method sets the author_email_address to
|
1691
|
+
# test@test.com. I would have thought this would mean that all cloned models would have an author email address
|
1692
|
+
# of test@test.com. However the test_clone test method seems to test that this is not the case. As a result the
|
1693
|
+
# after_initialize callback has to be run *before* the copying of the atrributes rather than afterwards in order
|
1694
|
+
# for all tests to pass. This makes no sense to me.
|
1695
|
+
callback(:after_initialize) if respond_to_without_attributes?(:after_initialize)
|
1696
|
+
cloned_attributes = other.clone_attributes(:read_attribute_before_type_cast)
|
1697
|
+
cloned_attributes.delete(self.class.primary_key)
|
1698
|
+
@attributes = cloned_attributes
|
1699
|
+
clear_aggregation_cache
|
1700
|
+
@attributes_cache = {}
|
1701
|
+
@new_record = true
|
1702
|
+
ensure_proper_type
|
2501
1703
|
|
1704
|
+
if scope = self.class.send(:current_scoped_methods)
|
1705
|
+
create_with = scope.scope_for_create
|
1706
|
+
create_with.each { |att,value| self.send("#{att}=", value) } if create_with
|
1707
|
+
end
|
2502
1708
|
end
|
2503
1709
|
|
2504
1710
|
# Returns a String, which Action Pack uses for constructing an URL to this
|
@@ -2520,7 +1726,7 @@ module ActiveRecord #:nodoc:
|
|
2520
1726
|
# name
|
2521
1727
|
# end
|
2522
1728
|
# end
|
2523
|
-
#
|
1729
|
+
#
|
2524
1730
|
# user = User.find_by_name('Phusion')
|
2525
1731
|
# user_path(user) # => "/users/Phusion"
|
2526
1732
|
def to_param
|
@@ -2546,41 +1752,37 @@ module ActiveRecord #:nodoc:
|
|
2546
1752
|
end
|
2547
1753
|
end
|
2548
1754
|
|
2549
|
-
def id_before_type_cast #:nodoc:
|
2550
|
-
read_attribute_before_type_cast(self.class.primary_key)
|
2551
|
-
end
|
2552
|
-
|
2553
1755
|
def quoted_id #:nodoc:
|
2554
1756
|
quote_value(id, column_for_attribute(self.class.primary_key))
|
2555
1757
|
end
|
2556
1758
|
|
2557
|
-
# Sets the primary ID.
|
2558
|
-
def id=(value)
|
2559
|
-
write_attribute(self.class.primary_key, value)
|
2560
|
-
end
|
2561
|
-
|
2562
1759
|
# Returns true if this object hasn't been saved yet -- that is, a record for the object doesn't exist yet; otherwise, returns false.
|
2563
1760
|
def new_record?
|
2564
1761
|
@new_record || false
|
2565
1762
|
end
|
2566
1763
|
|
1764
|
+
# Returns true if this object has been destroyed, otherwise returns false.
|
1765
|
+
def destroyed?
|
1766
|
+
@destroyed || false
|
1767
|
+
end
|
1768
|
+
|
2567
1769
|
# :call-seq:
|
2568
|
-
# save(
|
1770
|
+
# save(options)
|
2569
1771
|
#
|
2570
1772
|
# Saves the model.
|
2571
1773
|
#
|
2572
1774
|
# If the model is new a record gets created in the database, otherwise
|
2573
1775
|
# the existing record gets updated.
|
2574
1776
|
#
|
2575
|
-
#
|
2576
|
-
#
|
2577
|
-
# false validations are bypassed altogether. See
|
2578
|
-
# ActiveRecord::Validations for more information.
|
1777
|
+
# By default, save always run validations. If any of them fail the action
|
1778
|
+
# is cancelled and +save+ returns +false+. However, if you supply
|
1779
|
+
# :validate => false, validations are bypassed altogether. See
|
1780
|
+
# ActiveRecord::Validations for more information.
|
2579
1781
|
#
|
2580
1782
|
# There's a series of callbacks associated with +save+. If any of the
|
2581
1783
|
# <tt>before_*</tt> callbacks return +false+ the action is cancelled and
|
2582
1784
|
# +save+ returns +false+. See ActiveRecord::Callbacks for further
|
2583
|
-
# details.
|
1785
|
+
# details.
|
2584
1786
|
def save
|
2585
1787
|
create_or_update
|
2586
1788
|
end
|
@@ -2592,12 +1794,12 @@ module ActiveRecord #:nodoc:
|
|
2592
1794
|
#
|
2593
1795
|
# With <tt>save!</tt> validations always run. If any of them fail
|
2594
1796
|
# ActiveRecord::RecordInvalid gets raised. See ActiveRecord::Validations
|
2595
|
-
# for more information.
|
1797
|
+
# for more information.
|
2596
1798
|
#
|
2597
1799
|
# There's a series of callbacks associated with <tt>save!</tt>. If any of
|
2598
1800
|
# the <tt>before_*</tt> callbacks return +false+ the action is cancelled
|
2599
1801
|
# and <tt>save!</tt> raises ActiveRecord::RecordNotSaved. See
|
2600
|
-
# ActiveRecord::Callbacks for further details.
|
1802
|
+
# ActiveRecord::Callbacks for further details.
|
2601
1803
|
def save!
|
2602
1804
|
create_or_update || raise(RecordNotSaved)
|
2603
1805
|
end
|
@@ -2622,30 +1824,13 @@ module ActiveRecord #:nodoc:
|
|
2622
1824
|
# be made (since they can't be persisted).
|
2623
1825
|
def destroy
|
2624
1826
|
unless new_record?
|
2625
|
-
|
2626
|
-
"DELETE FROM #{self.class.quoted_table_name} " +
|
2627
|
-
"WHERE #{connection.quote_column_name(self.class.primary_key)} = #{quoted_id}",
|
2628
|
-
"#{self.class.name} Destroy"
|
2629
|
-
)
|
1827
|
+
self.class.unscoped.where(self.class.arel_table[self.class.primary_key].eq(id)).delete_all
|
2630
1828
|
end
|
2631
1829
|
|
2632
1830
|
@destroyed = true
|
2633
1831
|
freeze
|
2634
1832
|
end
|
2635
1833
|
|
2636
|
-
# Returns a clone of the record that hasn't been assigned an id yet and
|
2637
|
-
# is treated as a new record. Note that this is a "shallow" clone:
|
2638
|
-
# it copies the object's attributes only, not its associations.
|
2639
|
-
# The extent of a "deep" clone is application-specific and is therefore
|
2640
|
-
# left to the application to implement according to its need.
|
2641
|
-
def clone
|
2642
|
-
attrs = clone_attributes(:read_attribute_before_type_cast)
|
2643
|
-
attrs.delete(self.class.primary_key)
|
2644
|
-
record = self.class.new
|
2645
|
-
record.send :instance_variable_set, '@attributes', attrs
|
2646
|
-
record
|
2647
|
-
end
|
2648
|
-
|
2649
1834
|
# Returns an instance of the specified +klass+ with the attributes of the current record. This is mostly useful in relation to
|
2650
1835
|
# single-table inheritance structures where you want a subclass to appear as the superclass. This can be used along with record
|
2651
1836
|
# identification in Action Pack to allow, say, <tt>Client < Company</tt> to do something like render <tt>:partial => @client.becomes(Company)</tt>
|
@@ -2654,11 +1839,11 @@ module ActiveRecord #:nodoc:
|
|
2654
1839
|
# Note: The new instance will share a link to the same attributes as the original class. So any change to the attributes in either
|
2655
1840
|
# instance will affect the other.
|
2656
1841
|
def becomes(klass)
|
2657
|
-
klass.new
|
2658
|
-
|
2659
|
-
|
2660
|
-
|
2661
|
-
|
1842
|
+
became = klass.new
|
1843
|
+
became.instance_variable_set("@attributes", @attributes)
|
1844
|
+
became.instance_variable_set("@attributes_cache", @attributes_cache)
|
1845
|
+
became.instance_variable_set("@new_record", new_record?)
|
1846
|
+
became
|
2662
1847
|
end
|
2663
1848
|
|
2664
1849
|
# Updates a single attribute and saves the record without going through the normal validation procedure.
|
@@ -2666,26 +1851,18 @@ module ActiveRecord #:nodoc:
|
|
2666
1851
|
# in Base is replaced with this when the validations module is mixed in, which it is by default.
|
2667
1852
|
def update_attribute(name, value)
|
2668
1853
|
send(name.to_s + '=', value)
|
2669
|
-
save(false)
|
1854
|
+
save(:validate => false)
|
2670
1855
|
end
|
2671
1856
|
|
2672
1857
|
# Updates all the attributes from the passed-in Hash and saves the record. If the object is invalid, the saving will
|
2673
1858
|
# fail and false will be returned.
|
2674
1859
|
def update_attributes(attributes)
|
2675
|
-
with_transaction_returning_status(:update_attributes_inside_transaction, attributes)
|
2676
|
-
end
|
2677
|
-
|
2678
|
-
def update_attributes_inside_transaction(attributes) #:nodoc:
|
2679
1860
|
self.attributes = attributes
|
2680
1861
|
save
|
2681
1862
|
end
|
2682
1863
|
|
2683
1864
|
# Updates an object just like Base.update_attributes but calls save! instead of save so an exception is raised if the record is invalid.
|
2684
1865
|
def update_attributes!(attributes)
|
2685
|
-
with_transaction_returning_status(:update_attributes_inside_transaction!, attributes)
|
2686
|
-
end
|
2687
|
-
|
2688
|
-
def update_attributes_inside_transaction!(attributes) #:nodoc:
|
2689
1866
|
self.attributes = attributes
|
2690
1867
|
save!
|
2691
1868
|
end
|
@@ -2748,11 +1925,21 @@ module ActiveRecord #:nodoc:
|
|
2748
1925
|
def reload(options = nil)
|
2749
1926
|
clear_aggregation_cache
|
2750
1927
|
clear_association_cache
|
2751
|
-
@attributes.update(self.class.
|
1928
|
+
@attributes.update(self.class.find(self.id, options).instance_variable_get('@attributes'))
|
2752
1929
|
@attributes_cache = {}
|
2753
1930
|
self
|
2754
1931
|
end
|
2755
1932
|
|
1933
|
+
# Returns true if the given attribute is in the attributes hash
|
1934
|
+
def has_attribute?(attr_name)
|
1935
|
+
@attributes.has_key?(attr_name.to_s)
|
1936
|
+
end
|
1937
|
+
|
1938
|
+
# Returns an array of names for the attributes available on this object sorted alphabetically.
|
1939
|
+
def attribute_names
|
1940
|
+
@attributes.keys.sort
|
1941
|
+
end
|
1942
|
+
|
2756
1943
|
# Returns the value of the attribute identified by <tt>attr_name</tt> after it has been typecast (for example,
|
2757
1944
|
# "2004-12-12" in a data column is cast to a date object, like Date.new(2004, 12, 12)).
|
2758
1945
|
# (Alias for the protected read_attribute method).
|
@@ -2778,12 +1965,12 @@ module ActiveRecord #:nodoc:
|
|
2778
1965
|
# class User < ActiveRecord::Base
|
2779
1966
|
# attr_protected :is_admin
|
2780
1967
|
# end
|
2781
|
-
#
|
1968
|
+
#
|
2782
1969
|
# user = User.new
|
2783
1970
|
# user.attributes = { :username => 'Phusion', :is_admin => true }
|
2784
1971
|
# user.username # => "Phusion"
|
2785
1972
|
# user.is_admin? # => false
|
2786
|
-
#
|
1973
|
+
#
|
2787
1974
|
# user.send(:attributes=, { :username => 'Phusion', :is_admin => true }, false)
|
2788
1975
|
# user.is_admin? # => true
|
2789
1976
|
def attributes=(new_attributes, guard_protected_attributes = true)
|
@@ -2791,21 +1978,24 @@ module ActiveRecord #:nodoc:
|
|
2791
1978
|
attributes = new_attributes.dup
|
2792
1979
|
attributes.stringify_keys!
|
2793
1980
|
|
1981
|
+
multi_parameter_attributes = []
|
2794
1982
|
attributes = remove_attributes_protected_from_mass_assignment(attributes) if guard_protected_attributes
|
2795
|
-
|
1983
|
+
|
1984
|
+
attributes.each do |k, v|
|
1985
|
+
if k.include?("(")
|
1986
|
+
multi_parameter_attributes << [ k, v ]
|
1987
|
+
else
|
1988
|
+
respond_to?(:"#{k}=") ? send(:"#{k}=", v) : raise(UnknownAttributeError, "unknown attribute: #{k}")
|
1989
|
+
end
|
1990
|
+
end
|
1991
|
+
|
1992
|
+
assign_multiparameter_attributes(multi_parameter_attributes)
|
2796
1993
|
end
|
2797
1994
|
|
2798
1995
|
# Returns a hash of all the attributes with their names as keys and the values of the attributes as values.
|
2799
1996
|
def attributes
|
2800
|
-
attrs = {}
|
2801
|
-
attribute_names.each { |name| attrs[name] = read_attribute(name) }
|
2802
|
-
attrs
|
2803
|
-
end
|
2804
|
-
|
2805
|
-
# Returns a hash of attributes before typecasting and deserialization.
|
2806
|
-
def attributes_before_type_cast
|
2807
1997
|
self.attribute_names.inject({}) do |attrs, name|
|
2808
|
-
attrs[name] =
|
1998
|
+
attrs[name] = read_attribute(name)
|
2809
1999
|
attrs
|
2810
2000
|
end
|
2811
2001
|
end
|
@@ -2842,16 +2032,6 @@ module ActiveRecord #:nodoc:
|
|
2842
2032
|
!value.blank?
|
2843
2033
|
end
|
2844
2034
|
|
2845
|
-
# Returns true if the given attribute is in the attributes hash
|
2846
|
-
def has_attribute?(attr_name)
|
2847
|
-
@attributes.has_key?(attr_name.to_s)
|
2848
|
-
end
|
2849
|
-
|
2850
|
-
# Returns an array of names for the attributes available on this object sorted alphabetically.
|
2851
|
-
def attribute_names
|
2852
|
-
@attributes.keys.sort
|
2853
|
-
end
|
2854
|
-
|
2855
2035
|
# Returns the column object for the named attribute.
|
2856
2036
|
def column_for_attribute(name)
|
2857
2037
|
self.class.columns_hash[name.to_s]
|
@@ -2886,9 +2066,11 @@ module ActiveRecord #:nodoc:
|
|
2886
2066
|
@attributes.frozen?
|
2887
2067
|
end
|
2888
2068
|
|
2889
|
-
# Returns
|
2890
|
-
def
|
2891
|
-
|
2069
|
+
# Returns duplicated record with unfreezed attributes.
|
2070
|
+
def dup
|
2071
|
+
obj = super
|
2072
|
+
obj.instance_variable_set('@attributes', instance_variable_get('@attributes').dup)
|
2073
|
+
obj
|
2892
2074
|
end
|
2893
2075
|
|
2894
2076
|
# Returns +true+ if the record is read only. Records loaded through joins with piggy-back
|
@@ -2912,24 +2094,22 @@ module ActiveRecord #:nodoc:
|
|
2912
2094
|
"#<#{self.class} #{attributes_as_nice_string}>"
|
2913
2095
|
end
|
2914
2096
|
|
2915
|
-
|
2916
|
-
|
2917
|
-
|
2918
|
-
|
2919
|
-
|
2920
|
-
multiparameter_attributes = []
|
2921
|
-
|
2922
|
-
attributes.each do |k, v|
|
2923
|
-
if k.to_s.include?("(")
|
2924
|
-
multiparameter_attributes << [ k, v ]
|
2925
|
-
else
|
2926
|
-
respond_to?(:"#{k}=") ? send(:"#{k}=", v) : raise(UnknownAttributeError, "unknown attribute: #{k}")
|
2927
|
-
end
|
2097
|
+
protected
|
2098
|
+
def clone_attributes(reader_method = :read_attribute, attributes = {})
|
2099
|
+
self.attribute_names.inject(attributes) do |attrs, name|
|
2100
|
+
attrs[name] = clone_attribute_value(reader_method, name)
|
2101
|
+
attrs
|
2928
2102
|
end
|
2103
|
+
end
|
2929
2104
|
|
2930
|
-
|
2105
|
+
def clone_attribute_value(reader_method, attribute_name)
|
2106
|
+
value = send(reader_method, attribute_name)
|
2107
|
+
value.duplicable? ? value.clone : value
|
2108
|
+
rescue TypeError, NoMethodError
|
2109
|
+
value
|
2931
2110
|
end
|
2932
|
-
|
2111
|
+
|
2112
|
+
private
|
2933
2113
|
def create_or_update
|
2934
2114
|
raise ReadOnlyRecord if readonly?
|
2935
2115
|
result = new_record? ? create : update
|
@@ -2939,14 +2119,9 @@ module ActiveRecord #:nodoc:
|
|
2939
2119
|
# Updates the associated record with values matching those of the instance attributes.
|
2940
2120
|
# Returns the number of affected rows.
|
2941
2121
|
def update(attribute_names = @attributes.keys)
|
2942
|
-
|
2943
|
-
return 0 if
|
2944
|
-
|
2945
|
-
"UPDATE #{self.class.quoted_table_name} " +
|
2946
|
-
"SET #{quoted_comma_pair_list(connection, quoted_attributes)} " +
|
2947
|
-
"WHERE #{connection.quote_column_name(self.class.primary_key)} = #{quote_value(id)}",
|
2948
|
-
"#{self.class.name} Update"
|
2949
|
-
)
|
2122
|
+
attributes_with_values = arel_attributes_values(false, false, attribute_names)
|
2123
|
+
return 0 if attributes_with_values.empty?
|
2124
|
+
self.class.unscoped.where(self.class.arel_table[self.class.primary_key].eq(id)).arel.update(attributes_with_values)
|
2950
2125
|
end
|
2951
2126
|
|
2952
2127
|
# Creates a record with values matching those of the instance attributes
|
@@ -2956,18 +2131,15 @@ module ActiveRecord #:nodoc:
|
|
2956
2131
|
self.id = connection.next_sequence_value(self.class.sequence_name)
|
2957
2132
|
end
|
2958
2133
|
|
2959
|
-
|
2134
|
+
attributes_values = arel_attributes_values
|
2960
2135
|
|
2961
|
-
|
2962
|
-
|
2136
|
+
new_id = if attributes_values.empty?
|
2137
|
+
self.class.unscoped.insert connection.empty_insert_statement_value
|
2963
2138
|
else
|
2964
|
-
|
2965
|
-
"(#{quoted_column_names.join(', ')}) " +
|
2966
|
-
"VALUES(#{quoted_attributes.values.join(', ')})"
|
2139
|
+
self.class.unscoped.insert attributes_values
|
2967
2140
|
end
|
2968
2141
|
|
2969
|
-
self.id
|
2970
|
-
self.class.primary_key, self.id, self.class.sequence_name)
|
2142
|
+
self.id ||= new_id
|
2971
2143
|
|
2972
2144
|
@new_record = false
|
2973
2145
|
id
|
@@ -2983,26 +2155,14 @@ module ActiveRecord #:nodoc:
|
|
2983
2155
|
end
|
2984
2156
|
end
|
2985
2157
|
|
2986
|
-
def convert_number_column_value(value)
|
2987
|
-
if value == false
|
2988
|
-
0
|
2989
|
-
elsif value == true
|
2990
|
-
1
|
2991
|
-
elsif value.is_a?(String) && value.blank?
|
2992
|
-
nil
|
2993
|
-
else
|
2994
|
-
value
|
2995
|
-
end
|
2996
|
-
end
|
2997
|
-
|
2998
2158
|
def remove_attributes_protected_from_mass_assignment(attributes)
|
2999
2159
|
safe_attributes =
|
3000
2160
|
if self.class.accessible_attributes.nil? && self.class.protected_attributes.nil?
|
3001
|
-
attributes.reject { |key, value| attributes_protected_by_default.include?(key.gsub(/\(
|
2161
|
+
attributes.reject { |key, value| attributes_protected_by_default.include?(key.gsub(/\(.+/, "")) }
|
3002
2162
|
elsif self.class.protected_attributes.nil?
|
3003
|
-
attributes.reject { |key, value| !self.class.accessible_attributes.include?(key.gsub(/\(
|
2163
|
+
attributes.reject { |key, value| !self.class.accessible_attributes.include?(key.gsub(/\(.+/, "")) || attributes_protected_by_default.include?(key.gsub(/\(.+/, "")) }
|
3004
2164
|
elsif self.class.accessible_attributes.nil?
|
3005
|
-
attributes.reject { |key, value| self.class.protected_attributes.include?(key.gsub(/\(
|
2165
|
+
attributes.reject { |key, value| self.class.protected_attributes.include?(key.gsub(/\(.+/,"")) || attributes_protected_by_default.include?(key.gsub(/\(.+/, "")) }
|
3006
2166
|
else
|
3007
2167
|
raise "Declare either attr_protected or attr_accessible for #{self.class}, but not both."
|
3008
2168
|
end
|
@@ -3026,7 +2186,9 @@ module ActiveRecord #:nodoc:
|
|
3026
2186
|
end
|
3027
2187
|
|
3028
2188
|
def log_protected_attribute_removal(*attributes)
|
3029
|
-
logger
|
2189
|
+
if logger
|
2190
|
+
logger.debug "WARNING: Can't mass-assign these protected attributes: #{attributes.join(', ')}"
|
2191
|
+
end
|
3030
2192
|
end
|
3031
2193
|
|
3032
2194
|
# The primary key and inheritance column can never be set by mass-assignment for security reasons.
|
@@ -3056,6 +2218,26 @@ module ActiveRecord #:nodoc:
|
|
3056
2218
|
include_readonly_attributes ? quoted : remove_readonly_attributes(quoted)
|
3057
2219
|
end
|
3058
2220
|
|
2221
|
+
# Returns a copy of the attributes hash where all the values have been safely quoted for use in
|
2222
|
+
# an Arel insert/update method.
|
2223
|
+
def arel_attributes_values(include_primary_key = true, include_readonly_attributes = true, attribute_names = @attributes.keys)
|
2224
|
+
attrs = {}
|
2225
|
+
attribute_names.each do |name|
|
2226
|
+
if (column = column_for_attribute(name)) && (include_primary_key || !column.primary)
|
2227
|
+
|
2228
|
+
if include_readonly_attributes || (!include_readonly_attributes && !self.class.readonly_attributes.include?(name))
|
2229
|
+
value = read_attribute(name)
|
2230
|
+
|
2231
|
+
if value && ((self.class.serialized_attributes.has_key?(name) && (value.acts_like?(:date) || value.acts_like?(:time))) || value.is_a?(Hash) || value.is_a?(Array))
|
2232
|
+
value = value.to_yaml
|
2233
|
+
end
|
2234
|
+
attrs[self.class.arel_table[name]] = value
|
2235
|
+
end
|
2236
|
+
end
|
2237
|
+
end
|
2238
|
+
attrs
|
2239
|
+
end
|
2240
|
+
|
3059
2241
|
# Quote strings appropriately for SQL statements.
|
3060
2242
|
def quote_value(value, column = nil)
|
3061
2243
|
self.class.connection.quote(value, column)
|
@@ -3160,18 +2342,7 @@ module ActiveRecord #:nodoc:
|
|
3160
2342
|
|
3161
2343
|
# Returns a comma-separated pair list, like "key1 = val1, key2 = val2".
|
3162
2344
|
def comma_pair_list(hash)
|
3163
|
-
hash.
|
3164
|
-
end
|
3165
|
-
|
3166
|
-
def quoted_column_names(attributes = attributes_with_quotes)
|
3167
|
-
connection = self.class.connection
|
3168
|
-
attributes.keys.collect do |column_name|
|
3169
|
-
connection.quote_column_name(column_name)
|
3170
|
-
end
|
3171
|
-
end
|
3172
|
-
|
3173
|
-
def self.quoted_table_name
|
3174
|
-
self.connection.quote_table_name(self.table_name)
|
2345
|
+
hash.inject([]) { |list, pair| list << "#{pair.first} = #{pair.last}" }.join(", ")
|
3175
2346
|
end
|
3176
2347
|
|
3177
2348
|
def quote_columns(quoter, hash)
|
@@ -3185,40 +2356,46 @@ module ActiveRecord #:nodoc:
|
|
3185
2356
|
comma_pair_list(quote_columns(quoter, hash))
|
3186
2357
|
end
|
3187
2358
|
|
3188
|
-
def
|
3189
|
-
|
3190
|
-
|
3191
|
-
|
3192
|
-
|
3193
|
-
|
3194
|
-
|
3195
|
-
|
3196
|
-
|
2359
|
+
def convert_number_column_value(value)
|
2360
|
+
if value == false
|
2361
|
+
0
|
2362
|
+
elsif value == true
|
2363
|
+
1
|
2364
|
+
elsif value.is_a?(String) && value.blank?
|
2365
|
+
nil
|
2366
|
+
else
|
2367
|
+
value
|
3197
2368
|
end
|
3198
2369
|
end
|
3199
2370
|
|
3200
|
-
def
|
3201
|
-
|
3202
|
-
|
3203
|
-
rescue TypeError, NoMethodError
|
3204
|
-
value
|
2371
|
+
def object_from_yaml(string)
|
2372
|
+
return string unless string.is_a?(String) && string =~ /^---/
|
2373
|
+
YAML::load(string) rescue string
|
3205
2374
|
end
|
3206
2375
|
end
|
3207
2376
|
|
3208
2377
|
Base.class_eval do
|
2378
|
+
extend ActiveModel::Naming
|
3209
2379
|
extend QueryCache::ClassMethods
|
2380
|
+
extend ActiveSupport::Benchmarkable
|
2381
|
+
|
3210
2382
|
include Validations
|
3211
2383
|
include Locking::Optimistic, Locking::Pessimistic
|
3212
2384
|
include AttributeMethods
|
3213
|
-
include
|
3214
|
-
include
|
2385
|
+
include AttributeMethods::Read, AttributeMethods::Write, AttributeMethods::BeforeTypeCast, AttributeMethods::Query
|
2386
|
+
include AttributeMethods::PrimaryKey
|
2387
|
+
include AttributeMethods::TimeZoneConversion
|
2388
|
+
include AttributeMethods::Dirty
|
2389
|
+
include Callbacks, ActiveModel::Observing, Timestamp
|
3215
2390
|
include Associations, AssociationPreload, NamedScope
|
2391
|
+
include ActiveModel::Conversion
|
3216
2392
|
|
3217
2393
|
# AutosaveAssociation needs to be included before Transactions, because we want
|
3218
2394
|
# #save_with_autosave_associations to be wrapped inside a transaction.
|
3219
2395
|
include AutosaveAssociation, NestedAttributes
|
3220
2396
|
|
3221
|
-
include Aggregations, Transactions, Reflection, Batches,
|
2397
|
+
include Aggregations, Transactions, Reflection, Batches, Serialization
|
2398
|
+
|
3222
2399
|
end
|
3223
2400
|
end
|
3224
2401
|
|