activerecord 2.3.18 → 3.2.22
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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +1014 -0
- data/MIT-LICENSE +20 -0
- data/README.rdoc +222 -0
- data/examples/performance.rb +100 -126
- data/examples/simple.rb +14 -0
- data/lib/active_record/aggregations.rb +93 -99
- data/lib/active_record/associations/alias_tracker.rb +76 -0
- data/lib/active_record/associations/association.rb +247 -0
- data/lib/active_record/associations/association_scope.rb +134 -0
- data/lib/active_record/associations/belongs_to_association.rb +54 -61
- data/lib/active_record/associations/belongs_to_polymorphic_association.rb +17 -59
- data/lib/active_record/associations/builder/association.rb +55 -0
- data/lib/active_record/associations/builder/belongs_to.rb +88 -0
- data/lib/active_record/associations/builder/collection_association.rb +75 -0
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +57 -0
- data/lib/active_record/associations/builder/has_many.rb +71 -0
- data/lib/active_record/associations/builder/has_one.rb +62 -0
- data/lib/active_record/associations/builder/singular_association.rb +32 -0
- data/lib/active_record/associations/collection_association.rb +580 -0
- data/lib/active_record/associations/collection_proxy.rb +133 -0
- data/lib/active_record/associations/has_and_belongs_to_many_association.rb +39 -119
- data/lib/active_record/associations/has_many_association.rb +60 -79
- data/lib/active_record/associations/has_many_through_association.rb +127 -206
- data/lib/active_record/associations/has_one_association.rb +55 -114
- data/lib/active_record/associations/has_one_through_association.rb +25 -26
- data/lib/active_record/associations/join_dependency/join_association.rb +159 -0
- data/lib/active_record/associations/join_dependency/join_base.rb +24 -0
- data/lib/active_record/associations/join_dependency/join_part.rb +78 -0
- data/lib/active_record/associations/join_dependency.rb +214 -0
- data/lib/active_record/associations/join_helper.rb +55 -0
- data/lib/active_record/associations/preloader/association.rb +125 -0
- data/lib/active_record/associations/preloader/belongs_to.rb +17 -0
- data/lib/active_record/associations/preloader/collection_association.rb +24 -0
- data/lib/active_record/associations/preloader/has_and_belongs_to_many.rb +60 -0
- data/lib/active_record/associations/preloader/has_many.rb +17 -0
- data/lib/active_record/associations/preloader/has_many_through.rb +15 -0
- data/lib/active_record/associations/preloader/has_one.rb +23 -0
- data/lib/active_record/associations/preloader/has_one_through.rb +9 -0
- data/lib/active_record/associations/preloader/singular_association.rb +21 -0
- data/lib/active_record/associations/preloader/through_association.rb +67 -0
- data/lib/active_record/associations/preloader.rb +181 -0
- data/lib/active_record/associations/singular_association.rb +64 -0
- data/lib/active_record/associations/through_association.rb +87 -0
- data/lib/active_record/associations.rb +693 -1337
- data/lib/active_record/attribute_assignment.rb +221 -0
- data/lib/active_record/attribute_methods/before_type_cast.rb +31 -0
- data/lib/active_record/attribute_methods/deprecated_underscore_read.rb +32 -0
- data/lib/active_record/attribute_methods/dirty.rb +111 -0
- data/lib/active_record/attribute_methods/primary_key.rb +114 -0
- data/lib/active_record/attribute_methods/query.rb +39 -0
- data/lib/active_record/attribute_methods/read.rb +136 -0
- data/lib/active_record/attribute_methods/serialization.rb +120 -0
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +65 -0
- data/lib/active_record/attribute_methods/write.rb +70 -0
- data/lib/active_record/attribute_methods.rb +211 -339
- data/lib/active_record/autosave_association.rb +179 -149
- data/lib/active_record/base.rb +401 -2907
- data/lib/active_record/callbacks.rb +91 -176
- data/lib/active_record/coders/yaml_column.rb +41 -0
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +236 -119
- data/lib/active_record/connection_adapters/abstract/connection_specification.rb +110 -58
- data/lib/active_record/connection_adapters/abstract/database_limits.rb +12 -11
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +175 -74
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +31 -35
- data/lib/active_record/connection_adapters/abstract/quoting.rb +71 -21
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +81 -311
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +194 -78
- data/lib/active_record/connection_adapters/abstract_adapter.rb +130 -83
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +676 -0
- data/lib/active_record/connection_adapters/column.rb +296 -0
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +280 -0
- data/lib/active_record/connection_adapters/mysql_adapter.rb +272 -493
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +650 -405
- data/lib/active_record/connection_adapters/schema_cache.rb +69 -0
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +30 -9
- data/lib/active_record/connection_adapters/sqlite_adapter.rb +276 -147
- data/lib/active_record/connection_adapters/statement_pool.rb +40 -0
- data/lib/active_record/counter_cache.rb +123 -0
- data/lib/active_record/dynamic_finder_match.rb +41 -14
- data/lib/active_record/dynamic_matchers.rb +84 -0
- data/lib/active_record/dynamic_scope_match.rb +13 -15
- data/lib/active_record/errors.rb +195 -0
- data/lib/active_record/explain.rb +86 -0
- data/lib/active_record/explain_subscriber.rb +25 -0
- data/lib/active_record/fixtures/file.rb +65 -0
- data/lib/active_record/fixtures.rb +695 -770
- data/lib/active_record/identity_map.rb +162 -0
- data/lib/active_record/inheritance.rb +174 -0
- data/lib/active_record/integration.rb +60 -0
- data/lib/active_record/locale/en.yml +9 -27
- data/lib/active_record/locking/optimistic.rb +76 -73
- data/lib/active_record/locking/pessimistic.rb +32 -10
- data/lib/active_record/log_subscriber.rb +72 -0
- data/lib/active_record/migration/command_recorder.rb +105 -0
- data/lib/active_record/migration.rb +415 -205
- data/lib/active_record/model_schema.rb +368 -0
- data/lib/active_record/nested_attributes.rb +153 -63
- data/lib/active_record/observer.rb +27 -103
- data/lib/active_record/persistence.rb +376 -0
- data/lib/active_record/query_cache.rb +49 -8
- data/lib/active_record/querying.rb +58 -0
- data/lib/active_record/railtie.rb +131 -0
- data/lib/active_record/railties/console_sandbox.rb +6 -0
- data/lib/active_record/railties/controller_runtime.rb +49 -0
- data/lib/active_record/railties/databases.rake +659 -0
- data/lib/active_record/railties/jdbcmysql_error.rb +16 -0
- data/lib/active_record/readonly_attributes.rb +26 -0
- data/lib/active_record/reflection.rb +269 -120
- data/lib/active_record/relation/batches.rb +90 -0
- data/lib/active_record/relation/calculations.rb +372 -0
- data/lib/active_record/relation/delegation.rb +49 -0
- data/lib/active_record/relation/finder_methods.rb +402 -0
- data/lib/active_record/relation/predicate_builder.rb +63 -0
- data/lib/active_record/relation/query_methods.rb +417 -0
- data/lib/active_record/relation/spawn_methods.rb +180 -0
- data/lib/active_record/relation.rb +537 -0
- data/lib/active_record/result.rb +40 -0
- data/lib/active_record/sanitization.rb +194 -0
- data/lib/active_record/schema.rb +9 -6
- data/lib/active_record/schema_dumper.rb +55 -32
- data/lib/active_record/scoping/default.rb +142 -0
- data/lib/active_record/scoping/named.rb +200 -0
- data/lib/active_record/scoping.rb +152 -0
- data/lib/active_record/serialization.rb +8 -91
- data/lib/active_record/serializers/xml_serializer.rb +43 -197
- data/lib/active_record/session_store.rb +129 -103
- data/lib/active_record/store.rb +52 -0
- data/lib/active_record/test_case.rb +30 -23
- data/lib/active_record/timestamp.rb +95 -52
- data/lib/active_record/transactions.rb +212 -66
- data/lib/active_record/translation.rb +22 -0
- data/lib/active_record/validations/associated.rb +43 -0
- data/lib/active_record/validations/uniqueness.rb +180 -0
- data/lib/active_record/validations.rb +43 -1106
- data/lib/active_record/version.rb +5 -4
- data/lib/active_record.rb +121 -48
- data/lib/rails/generators/active_record/migration/migration_generator.rb +25 -0
- data/lib/rails/generators/active_record/migration/templates/migration.rb +34 -0
- data/lib/rails/generators/active_record/migration.rb +15 -0
- data/lib/rails/generators/active_record/model/model_generator.rb +47 -0
- data/lib/rails/generators/active_record/model/templates/migration.rb +15 -0
- data/lib/rails/generators/active_record/model/templates/model.rb +12 -0
- data/lib/rails/generators/active_record/model/templates/module.rb +7 -0
- data/lib/rails/generators/active_record/observer/observer_generator.rb +15 -0
- data/lib/rails/generators/active_record/observer/templates/observer.rb +4 -0
- data/lib/rails/generators/active_record/session_migration/session_migration_generator.rb +25 -0
- data/lib/rails/generators/active_record/session_migration/templates/migration.rb +12 -0
- data/lib/rails/generators/active_record.rb +25 -0
- metadata +187 -363
- data/CHANGELOG +0 -5904
- data/README +0 -351
- data/RUNNING_UNIT_TESTS +0 -36
- data/Rakefile +0 -268
- data/install.rb +0 -30
- data/lib/active_record/association_preload.rb +0 -406
- data/lib/active_record/associations/association_collection.rb +0 -533
- data/lib/active_record/associations/association_proxy.rb +0 -288
- data/lib/active_record/batches.rb +0 -85
- data/lib/active_record/calculations.rb +0 -321
- data/lib/active_record/dirty.rb +0 -183
- data/lib/active_record/named_scope.rb +0 -197
- 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/special_categories.yml +0 -9
- data/test/fixtures/categories/subsubdir/arbitrary_filename.yml +0 -4
- data/test/fixtures/categories.yml +0 -14
- 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
@@ -1,288 +0,0 @@
|
|
1
|
-
module ActiveRecord
|
2
|
-
module Associations
|
3
|
-
# This is the root class of all association proxies:
|
4
|
-
#
|
5
|
-
# AssociationProxy
|
6
|
-
# BelongsToAssociation
|
7
|
-
# HasOneAssociation
|
8
|
-
# BelongsToPolymorphicAssociation
|
9
|
-
# AssociationCollection
|
10
|
-
# HasAndBelongsToManyAssociation
|
11
|
-
# HasManyAssociation
|
12
|
-
# HasManyThroughAssociation
|
13
|
-
# HasOneThroughAssociation
|
14
|
-
#
|
15
|
-
# Association proxies in Active Record are middlemen between the object that
|
16
|
-
# holds the association, known as the <tt>@owner</tt>, and the actual associated
|
17
|
-
# object, known as the <tt>@target</tt>. The kind of association any proxy is
|
18
|
-
# about is available in <tt>@reflection</tt>. That's an instance of the class
|
19
|
-
# ActiveRecord::Reflection::AssociationReflection.
|
20
|
-
#
|
21
|
-
# For example, given
|
22
|
-
#
|
23
|
-
# class Blog < ActiveRecord::Base
|
24
|
-
# has_many :posts
|
25
|
-
# end
|
26
|
-
#
|
27
|
-
# blog = Blog.find(:first)
|
28
|
-
#
|
29
|
-
# the association proxy in <tt>blog.posts</tt> has the object in +blog+ as
|
30
|
-
# <tt>@owner</tt>, the collection of its posts as <tt>@target</tt>, and
|
31
|
-
# the <tt>@reflection</tt> object represents a <tt>:has_many</tt> macro.
|
32
|
-
#
|
33
|
-
# This class has most of the basic instance methods removed, and delegates
|
34
|
-
# unknown methods to <tt>@target</tt> via <tt>method_missing</tt>. As a
|
35
|
-
# corner case, it even removes the +class+ method and that's why you get
|
36
|
-
#
|
37
|
-
# blog.posts.class # => Array
|
38
|
-
#
|
39
|
-
# though the object behind <tt>blog.posts</tt> is not an Array, but an
|
40
|
-
# ActiveRecord::Associations::HasManyAssociation.
|
41
|
-
#
|
42
|
-
# The <tt>@target</tt> object is not \loaded until needed. For example,
|
43
|
-
#
|
44
|
-
# blog.posts.count
|
45
|
-
#
|
46
|
-
# is computed directly through SQL and does not trigger by itself the
|
47
|
-
# instantiation of the actual post records.
|
48
|
-
class AssociationProxy #:nodoc:
|
49
|
-
alias_method :proxy_respond_to?, :respond_to?
|
50
|
-
alias_method :proxy_extend, :extend
|
51
|
-
delegate :to_param, :to => :proxy_target
|
52
|
-
instance_methods.each { |m| undef_method m unless m =~ /(^__|^nil\?$|^send$|proxy_|^object_id$)/ }
|
53
|
-
|
54
|
-
def initialize(owner, reflection)
|
55
|
-
@owner, @reflection = owner, reflection
|
56
|
-
reflection.check_validity!
|
57
|
-
Array(reflection.options[:extend]).each { |ext| proxy_extend(ext) }
|
58
|
-
reset
|
59
|
-
end
|
60
|
-
|
61
|
-
# Returns the owner of the proxy.
|
62
|
-
def proxy_owner
|
63
|
-
@owner
|
64
|
-
end
|
65
|
-
|
66
|
-
# Returns the reflection object that represents the association handled
|
67
|
-
# by the proxy.
|
68
|
-
def proxy_reflection
|
69
|
-
@reflection
|
70
|
-
end
|
71
|
-
|
72
|
-
# Returns the \target of the proxy, same as +target+.
|
73
|
-
def proxy_target
|
74
|
-
@target
|
75
|
-
end
|
76
|
-
|
77
|
-
# Does the proxy or its \target respond to +symbol+?
|
78
|
-
def respond_to?(*args)
|
79
|
-
proxy_respond_to?(*args) || (load_target && @target.respond_to?(*args))
|
80
|
-
end
|
81
|
-
|
82
|
-
# Forwards <tt>===</tt> explicitly to the \target because the instance method
|
83
|
-
# removal above doesn't catch it. Loads the \target if needed.
|
84
|
-
def ===(other)
|
85
|
-
load_target
|
86
|
-
other === @target
|
87
|
-
end
|
88
|
-
|
89
|
-
# Returns the name of the table of the related class:
|
90
|
-
#
|
91
|
-
# post.comments.aliased_table_name # => "comments"
|
92
|
-
#
|
93
|
-
def aliased_table_name
|
94
|
-
@reflection.klass.table_name
|
95
|
-
end
|
96
|
-
|
97
|
-
# Returns the SQL string that corresponds to the <tt>:conditions</tt>
|
98
|
-
# option of the macro, if given, or +nil+ otherwise.
|
99
|
-
def conditions
|
100
|
-
@conditions ||= interpolate_sql(@reflection.sanitized_conditions) if @reflection.sanitized_conditions
|
101
|
-
end
|
102
|
-
alias :sql_conditions :conditions
|
103
|
-
|
104
|
-
# Resets the \loaded flag to +false+ and sets the \target to +nil+.
|
105
|
-
def reset
|
106
|
-
@loaded = false
|
107
|
-
@target = nil
|
108
|
-
end
|
109
|
-
|
110
|
-
# Reloads the \target and returns +self+ on success.
|
111
|
-
def reload
|
112
|
-
reset
|
113
|
-
load_target
|
114
|
-
self unless @target.nil?
|
115
|
-
end
|
116
|
-
|
117
|
-
# Has the \target been already \loaded?
|
118
|
-
def loaded?
|
119
|
-
@loaded
|
120
|
-
end
|
121
|
-
|
122
|
-
# Asserts the \target has been loaded setting the \loaded flag to +true+.
|
123
|
-
def loaded
|
124
|
-
@loaded = true
|
125
|
-
end
|
126
|
-
|
127
|
-
# Returns the target of this proxy, same as +proxy_target+.
|
128
|
-
def target
|
129
|
-
@target
|
130
|
-
end
|
131
|
-
|
132
|
-
# Sets the target of this proxy to <tt>\target</tt>, and the \loaded flag to +true+.
|
133
|
-
def target=(target)
|
134
|
-
@target = target
|
135
|
-
loaded
|
136
|
-
end
|
137
|
-
|
138
|
-
# Forwards the call to the target. Loads the \target if needed.
|
139
|
-
def inspect
|
140
|
-
load_target
|
141
|
-
@target.inspect
|
142
|
-
end
|
143
|
-
|
144
|
-
def send(method, *args)
|
145
|
-
if proxy_respond_to?(method)
|
146
|
-
super
|
147
|
-
else
|
148
|
-
load_target
|
149
|
-
@target.send(method, *args)
|
150
|
-
end
|
151
|
-
end
|
152
|
-
|
153
|
-
protected
|
154
|
-
# Does the association have a <tt>:dependent</tt> option?
|
155
|
-
def dependent?
|
156
|
-
@reflection.options[:dependent]
|
157
|
-
end
|
158
|
-
|
159
|
-
# Returns a string with the IDs of +records+ joined with a comma, quoted
|
160
|
-
# if needed. The result is ready to be inserted into a SQL IN clause.
|
161
|
-
#
|
162
|
-
# quoted_record_ids(records) # => "23,56,58,67"
|
163
|
-
#
|
164
|
-
def quoted_record_ids(records)
|
165
|
-
records.map { |record| record.quoted_id }.join(',')
|
166
|
-
end
|
167
|
-
|
168
|
-
def interpolate_sql(sql, record = nil)
|
169
|
-
@owner.send(:interpolate_sql, sql, record)
|
170
|
-
end
|
171
|
-
|
172
|
-
# Forwards the call to the reflection class.
|
173
|
-
def sanitize_sql(sql, table_name = @reflection.klass.quoted_table_name)
|
174
|
-
@reflection.klass.send(:sanitize_sql, sql, table_name)
|
175
|
-
end
|
176
|
-
|
177
|
-
# Assigns the ID of the owner to the corresponding foreign key in +record+.
|
178
|
-
# If the association is polymorphic the type of the owner is also set.
|
179
|
-
def set_belongs_to_association_for(record)
|
180
|
-
if @reflection.options[:as]
|
181
|
-
record["#{@reflection.options[:as]}_id"] = @owner.id unless @owner.new_record?
|
182
|
-
record["#{@reflection.options[:as]}_type"] = @owner.class.base_class.name.to_s
|
183
|
-
else
|
184
|
-
unless @owner.new_record?
|
185
|
-
primary_key = @reflection.options[:primary_key] || :id
|
186
|
-
record[@reflection.primary_key_name] = @owner.send(primary_key)
|
187
|
-
end
|
188
|
-
end
|
189
|
-
end
|
190
|
-
|
191
|
-
# Merges into +options+ the ones coming from the reflection.
|
192
|
-
def merge_options_from_reflection!(options)
|
193
|
-
options.reverse_merge!(
|
194
|
-
:group => @reflection.options[:group],
|
195
|
-
:having => @reflection.options[:having],
|
196
|
-
:limit => @reflection.options[:limit],
|
197
|
-
:offset => @reflection.options[:offset],
|
198
|
-
:joins => @reflection.options[:joins],
|
199
|
-
:include => @reflection.options[:include],
|
200
|
-
:select => @reflection.options[:select],
|
201
|
-
:readonly => @reflection.options[:readonly]
|
202
|
-
)
|
203
|
-
end
|
204
|
-
|
205
|
-
# Forwards +with_scope+ to the reflection.
|
206
|
-
def with_scope(*args, &block)
|
207
|
-
@reflection.klass.send :with_scope, *args, &block
|
208
|
-
end
|
209
|
-
|
210
|
-
private
|
211
|
-
# Forwards any missing method call to the \target.
|
212
|
-
def method_missing(method, *args, &block)
|
213
|
-
if load_target
|
214
|
-
if @target.respond_to?(method)
|
215
|
-
@target.send(method, *args, &block)
|
216
|
-
else
|
217
|
-
super
|
218
|
-
end
|
219
|
-
end
|
220
|
-
end
|
221
|
-
|
222
|
-
# Loads the \target if needed and returns it.
|
223
|
-
#
|
224
|
-
# This method is abstract in the sense that it relies on +find_target+,
|
225
|
-
# which is expected to be provided by descendants.
|
226
|
-
#
|
227
|
-
# If the \target is already \loaded it is just returned. Thus, you can call
|
228
|
-
# +load_target+ unconditionally to get the \target.
|
229
|
-
#
|
230
|
-
# ActiveRecord::RecordNotFound is rescued within the method, and it is
|
231
|
-
# not reraised. The proxy is \reset and +nil+ is the return value.
|
232
|
-
def load_target
|
233
|
-
return nil unless defined?(@loaded)
|
234
|
-
|
235
|
-
if !loaded? and (!@owner.new_record? || foreign_key_present)
|
236
|
-
@target = find_target
|
237
|
-
end
|
238
|
-
|
239
|
-
@loaded = true
|
240
|
-
@target
|
241
|
-
rescue ActiveRecord::RecordNotFound
|
242
|
-
reset
|
243
|
-
end
|
244
|
-
|
245
|
-
# Can be overwritten by associations that might have the foreign key
|
246
|
-
# available for an association without having the object itself (and
|
247
|
-
# still being a new record). Currently, only +belongs_to+ presents
|
248
|
-
# this scenario (both vanilla and polymorphic).
|
249
|
-
def foreign_key_present
|
250
|
-
false
|
251
|
-
end
|
252
|
-
|
253
|
-
# Raises ActiveRecord::AssociationTypeMismatch unless +record+ is of
|
254
|
-
# the kind of the class of the associated objects. Meant to be used as
|
255
|
-
# a sanity check when you are about to assign an associated record.
|
256
|
-
def raise_on_type_mismatch(record)
|
257
|
-
unless record.is_a?(@reflection.klass) || record.is_a?(@reflection.class_name.constantize)
|
258
|
-
message = "#{@reflection.class_name}(##{@reflection.klass.object_id}) expected, got #{record.class}(##{record.class.object_id})"
|
259
|
-
raise ActiveRecord::AssociationTypeMismatch, message
|
260
|
-
end
|
261
|
-
end
|
262
|
-
|
263
|
-
# Array#flatten has problems with recursive arrays. Going one level
|
264
|
-
# deeper solves the majority of the problems.
|
265
|
-
def flatten_deeper(array)
|
266
|
-
array.collect { |element| (element.respond_to?(:flatten) && !element.is_a?(Hash)) ? element.flatten : element }.flatten
|
267
|
-
end
|
268
|
-
|
269
|
-
# Returns the ID of the owner, quoted if needed.
|
270
|
-
def owner_quoted_id
|
271
|
-
@owner.quoted_id
|
272
|
-
end
|
273
|
-
|
274
|
-
def set_inverse_instance(record, instance)
|
275
|
-
return if record.nil? || !we_can_set_the_inverse_on_this?(record)
|
276
|
-
inverse_relationship = @reflection.inverse_of
|
277
|
-
unless inverse_relationship.nil?
|
278
|
-
record.send(:"set_#{inverse_relationship.name}_target", instance)
|
279
|
-
end
|
280
|
-
end
|
281
|
-
|
282
|
-
# Override in subclasses
|
283
|
-
def we_can_set_the_inverse_on_this?(record)
|
284
|
-
false
|
285
|
-
end
|
286
|
-
end
|
287
|
-
end
|
288
|
-
end
|
@@ -1,85 +0,0 @@
|
|
1
|
-
module ActiveRecord
|
2
|
-
module Batches # :nodoc:
|
3
|
-
def self.included(base)
|
4
|
-
base.extend(ClassMethods)
|
5
|
-
end
|
6
|
-
|
7
|
-
# When processing large numbers of records, it's often a good idea to do
|
8
|
-
# so in batches to prevent memory ballooning.
|
9
|
-
module ClassMethods
|
10
|
-
# Yields each record that was found by the find +options+. The find is
|
11
|
-
# performed by find_in_batches with a batch size of 1000 (or as
|
12
|
-
# specified by the <tt>:batch_size</tt> option).
|
13
|
-
#
|
14
|
-
# Example:
|
15
|
-
#
|
16
|
-
# Person.find_each(:conditions => "age > 21") do |person|
|
17
|
-
# person.party_all_night!
|
18
|
-
# end
|
19
|
-
#
|
20
|
-
# Note: This method is only intended to use for batch processing of
|
21
|
-
# large amounts of records that wouldn't fit in memory all at once. If
|
22
|
-
# you just need to loop over less than 1000 records, it's probably
|
23
|
-
# better just to use the regular find methods.
|
24
|
-
def find_each(options = {})
|
25
|
-
find_in_batches(options) do |records|
|
26
|
-
records.each { |record| yield record }
|
27
|
-
end
|
28
|
-
|
29
|
-
self
|
30
|
-
end
|
31
|
-
|
32
|
-
# Yields each batch of records that was found by the find +options+ as
|
33
|
-
# an array. The size of each batch is set by the <tt>:batch_size</tt>
|
34
|
-
# option; the default is 1000.
|
35
|
-
#
|
36
|
-
# You can control the starting point for the batch processing by
|
37
|
-
# supplying the <tt>:start</tt> option. This is especially useful if you
|
38
|
-
# want multiple workers dealing with the same processing queue. You can
|
39
|
-
# make worker 1 handle all the records between id 0 and 10,000 and
|
40
|
-
# worker 2 handle from 10,000 and beyond (by setting the <tt>:start</tt>
|
41
|
-
# option on that worker).
|
42
|
-
#
|
43
|
-
# It's not possible to set the order. That is automatically set to
|
44
|
-
# ascending on the primary key ("id ASC") to make the batch ordering
|
45
|
-
# work. This also mean that this method only works with integer-based
|
46
|
-
# primary keys. You can't set the limit either, that's used to control
|
47
|
-
# the the batch sizes.
|
48
|
-
#
|
49
|
-
# Example:
|
50
|
-
#
|
51
|
-
# Person.find_in_batches(:conditions => "age > 21") do |group|
|
52
|
-
# sleep(50) # Make sure it doesn't get too crowded in there!
|
53
|
-
# group.each { |person| person.party_all_night! }
|
54
|
-
# end
|
55
|
-
def find_in_batches(options = {})
|
56
|
-
raise "You can't specify an order, it's forced to be #{batch_order}" if options[:order]
|
57
|
-
raise "You can't specify a limit, it's forced to be the batch_size" if options[:limit]
|
58
|
-
|
59
|
-
start = options.delete(:start).to_i
|
60
|
-
batch_size = options.delete(:batch_size) || 1000
|
61
|
-
|
62
|
-
proxy = scoped(options.merge(:order => batch_order, :limit => batch_size))
|
63
|
-
records = proxy.find(:all, :conditions => [ "#{table_name}.#{primary_key} >= ?", start ])
|
64
|
-
|
65
|
-
while records.any?
|
66
|
-
yield records
|
67
|
-
|
68
|
-
break if records.size < batch_size
|
69
|
-
|
70
|
-
last_value = records.last.id
|
71
|
-
|
72
|
-
raise "You must include the primary key if you define a select" unless last_value.present?
|
73
|
-
|
74
|
-
records = proxy.find(:all, :conditions => [ "#{table_name}.#{primary_key} > ?", last_value ])
|
75
|
-
end
|
76
|
-
end
|
77
|
-
|
78
|
-
|
79
|
-
private
|
80
|
-
def batch_order
|
81
|
-
"#{table_name}.#{primary_key} ASC"
|
82
|
-
end
|
83
|
-
end
|
84
|
-
end
|
85
|
-
end
|
@@ -1,321 +0,0 @@
|
|
1
|
-
module ActiveRecord
|
2
|
-
module Calculations #:nodoc:
|
3
|
-
CALCULATIONS_OPTIONS = [:conditions, :joins, :order, :select, :group, :having, :distinct, :limit, :offset, :include, :from]
|
4
|
-
def self.included(base)
|
5
|
-
base.extend(ClassMethods)
|
6
|
-
end
|
7
|
-
|
8
|
-
module ClassMethods
|
9
|
-
# Count operates using three different approaches.
|
10
|
-
#
|
11
|
-
# * Count all: By not passing any parameters to count, it will return a count of all the rows for the model.
|
12
|
-
# * Count using column: By passing a column name to count, it will return a count of all the rows for the model with supplied column present
|
13
|
-
# * Count using options will find the row count matched by the options used.
|
14
|
-
#
|
15
|
-
# The third approach, count using options, accepts an option hash as the only parameter. The options are:
|
16
|
-
#
|
17
|
-
# * <tt>:conditions</tt>: An SQL fragment like "administrator = 1" or [ "user_name = ?", username ]. See conditions in the intro to ActiveRecord::Base.
|
18
|
-
# * <tt>:joins</tt>: Either an SQL fragment for additional joins like "LEFT JOIN comments ON comments.post_id = id" (rarely needed)
|
19
|
-
# or named associations in the same form used for the <tt>:include</tt> option, which will perform an INNER JOIN on the associated table(s).
|
20
|
-
# 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.
|
21
|
-
# Pass <tt>:readonly => false</tt> to override.
|
22
|
-
# * <tt>:include</tt>: Named associations that should be loaded alongside using LEFT OUTER JOINs. The symbols named refer
|
23
|
-
# to already defined associations. When using named associations, count returns the number of DISTINCT items for the model you're counting.
|
24
|
-
# See eager loading under Associations.
|
25
|
-
# * <tt>:order</tt>: An SQL fragment like "created_at DESC, name" (really only used with GROUP BY calculations).
|
26
|
-
# * <tt>:group</tt>: An attribute name by which the result should be grouped. Uses the GROUP BY SQL-clause.
|
27
|
-
# * <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
|
28
|
-
# include the joined columns.
|
29
|
-
# * <tt>:distinct</tt>: Set this to true to make this a distinct calculation, such as SELECT COUNT(DISTINCT posts.id) ...
|
30
|
-
# * <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
|
31
|
-
# of a database view).
|
32
|
-
#
|
33
|
-
# Examples for counting all:
|
34
|
-
# Person.count # returns the total count of all people
|
35
|
-
#
|
36
|
-
# Examples for counting by column:
|
37
|
-
# Person.count(:age) # returns the total count of all people whose age is present in database
|
38
|
-
#
|
39
|
-
# Examples for count with options:
|
40
|
-
# Person.count(:conditions => "age > 26")
|
41
|
-
# Person.count(:conditions => "age > 26 AND job.salary > 60000", :include => :job) # because of the named association, it finds the DISTINCT count using LEFT OUTER JOIN.
|
42
|
-
# Person.count(:conditions => "age > 26 AND job.salary > 60000", :joins => "LEFT JOIN jobs on jobs.person_id = person.id") # finds the number of rows matching the conditions and joins.
|
43
|
-
# Person.count('id', :conditions => "age > 26") # Performs a COUNT(id)
|
44
|
-
# Person.count(:all, :conditions => "age > 26") # Performs a COUNT(*) (:all is an alias for '*')
|
45
|
-
#
|
46
|
-
# Note: <tt>Person.count(:all)</tt> will not work because it will use <tt>:all</tt> as the condition. Use Person.count instead.
|
47
|
-
def count(*args)
|
48
|
-
calculate(:count, *construct_count_options_from_args(*args))
|
49
|
-
end
|
50
|
-
|
51
|
-
# Calculates the average value on a given column. The value is returned as
|
52
|
-
# a float, or +nil+ if there's no row. See +calculate+ for examples with
|
53
|
-
# options.
|
54
|
-
#
|
55
|
-
# Person.average('age') # => 35.8
|
56
|
-
def average(column_name, options = {})
|
57
|
-
calculate(:avg, column_name, options)
|
58
|
-
end
|
59
|
-
|
60
|
-
# Calculates the minimum value on a given column. The value is returned
|
61
|
-
# with the same data type of the column, or +nil+ if there's no row. See
|
62
|
-
# +calculate+ for examples with options.
|
63
|
-
#
|
64
|
-
# Person.minimum('age') # => 7
|
65
|
-
def minimum(column_name, options = {})
|
66
|
-
calculate(:min, column_name, options)
|
67
|
-
end
|
68
|
-
|
69
|
-
# Calculates the maximum value on a given column. The value is returned
|
70
|
-
# with the same data type of the column, or +nil+ if there's no row. See
|
71
|
-
# +calculate+ for examples with options.
|
72
|
-
#
|
73
|
-
# Person.maximum('age') # => 93
|
74
|
-
def maximum(column_name, options = {})
|
75
|
-
calculate(:max, column_name, options)
|
76
|
-
end
|
77
|
-
|
78
|
-
# Calculates the sum of values on a given column. The value is returned
|
79
|
-
# with the same data type of the column, 0 if there's no row. See
|
80
|
-
# +calculate+ for examples with options.
|
81
|
-
#
|
82
|
-
# Person.sum('age') # => 4562
|
83
|
-
def sum(column_name, options = {})
|
84
|
-
calculate(:sum, column_name, options)
|
85
|
-
end
|
86
|
-
|
87
|
-
# This calculates aggregate values in the given column. Methods for count, sum, average, minimum, and maximum have been added as shortcuts.
|
88
|
-
# Options such as <tt>:conditions</tt>, <tt>:order</tt>, <tt>:group</tt>, <tt>:having</tt>, and <tt>:joins</tt> can be passed to customize the query.
|
89
|
-
#
|
90
|
-
# There are two basic forms of output:
|
91
|
-
# * Single aggregate value: The single value is type cast to Fixnum for COUNT, Float for AVG, and the given column's type for everything else.
|
92
|
-
# * Grouped values: This returns an ordered hash of the values and groups them by the <tt>:group</tt> option. It takes either a column name, or the name
|
93
|
-
# of a belongs_to association.
|
94
|
-
#
|
95
|
-
# values = Person.maximum(:age, :group => 'last_name')
|
96
|
-
# puts values["Drake"]
|
97
|
-
# => 43
|
98
|
-
#
|
99
|
-
# drake = Family.find_by_last_name('Drake')
|
100
|
-
# values = Person.maximum(:age, :group => :family) # Person belongs_to :family
|
101
|
-
# puts values[drake]
|
102
|
-
# => 43
|
103
|
-
#
|
104
|
-
# values.each do |family, max_age|
|
105
|
-
# ...
|
106
|
-
# end
|
107
|
-
#
|
108
|
-
# Options:
|
109
|
-
# * <tt>:conditions</tt> - An SQL fragment like "administrator = 1" or [ "user_name = ?", username ]. See conditions in the intro to ActiveRecord::Base.
|
110
|
-
# * <tt>:include</tt>: Eager loading, see Associations for details. Since calculations don't load anything, the purpose of this is to access fields on joined tables in your conditions, order, or group clauses.
|
111
|
-
# * <tt>:joins</tt> - An SQL fragment for additional joins like "LEFT JOIN comments ON comments.post_id = id". (Rarely needed).
|
112
|
-
# The records will be returned read-only since they will have attributes that do not correspond to the table's columns.
|
113
|
-
# * <tt>:order</tt> - An SQL fragment like "created_at DESC, name" (really only used with GROUP BY calculations).
|
114
|
-
# * <tt>:group</tt> - An attribute name by which the result should be grouped. Uses the GROUP BY SQL-clause.
|
115
|
-
# * <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
|
116
|
-
# include the joined columns.
|
117
|
-
# * <tt>:distinct</tt> - Set this to true to make this a distinct calculation, such as SELECT COUNT(DISTINCT posts.id) ...
|
118
|
-
#
|
119
|
-
# Examples:
|
120
|
-
# Person.calculate(:count, :all) # The same as Person.count
|
121
|
-
# Person.average(:age) # SELECT AVG(age) FROM people...
|
122
|
-
# Person.minimum(:age, :conditions => ['last_name != ?', 'Drake']) # Selects the minimum age for everyone with a last name other than 'Drake'
|
123
|
-
# Person.minimum(:age, :having => 'min(age) > 17', :group => :last_name) # Selects the minimum age for any family without any minors
|
124
|
-
# Person.sum("2 * age")
|
125
|
-
def calculate(operation, column_name, options = {})
|
126
|
-
validate_calculation_options(operation, options)
|
127
|
-
column_name = options[:select] if options[:select]
|
128
|
-
column_name = '*' if column_name == :all
|
129
|
-
column = column_for column_name
|
130
|
-
catch :invalid_query do
|
131
|
-
if options[:group]
|
132
|
-
return execute_grouped_calculation(operation, column_name, column, options)
|
133
|
-
else
|
134
|
-
return execute_simple_calculation(operation, column_name, column, options)
|
135
|
-
end
|
136
|
-
end
|
137
|
-
0
|
138
|
-
end
|
139
|
-
|
140
|
-
protected
|
141
|
-
def construct_count_options_from_args(*args)
|
142
|
-
options = {}
|
143
|
-
column_name = :all
|
144
|
-
|
145
|
-
# We need to handle
|
146
|
-
# count()
|
147
|
-
# count(:column_name=:all)
|
148
|
-
# count(options={})
|
149
|
-
# count(column_name=:all, options={})
|
150
|
-
case args.size
|
151
|
-
when 1
|
152
|
-
args[0].is_a?(Hash) ? options = args[0] : column_name = args[0]
|
153
|
-
when 2
|
154
|
-
column_name, options = args
|
155
|
-
else
|
156
|
-
raise ArgumentError, "Unexpected parameters passed to count(): #{args.inspect}"
|
157
|
-
end if args.size > 0
|
158
|
-
|
159
|
-
[column_name, options]
|
160
|
-
end
|
161
|
-
|
162
|
-
def construct_calculation_sql(operation, column_name, options) #:nodoc:
|
163
|
-
operation = operation.to_s.downcase
|
164
|
-
options = options.symbolize_keys
|
165
|
-
|
166
|
-
scope = scope(:find)
|
167
|
-
merged_includes = merge_includes(scope ? scope[:include] : [], options[:include])
|
168
|
-
aggregate_alias = column_alias_for(operation, column_name)
|
169
|
-
column_name = "#{connection.quote_table_name(table_name)}.#{column_name}" if column_names.include?(column_name.to_s)
|
170
|
-
|
171
|
-
if operation == 'count'
|
172
|
-
if merged_includes.any?
|
173
|
-
options[:distinct] = true
|
174
|
-
column_name = options[:select] || [connection.quote_table_name(table_name), primary_key] * '.'
|
175
|
-
end
|
176
|
-
|
177
|
-
if options[:distinct]
|
178
|
-
use_workaround = !connection.supports_count_distinct?
|
179
|
-
end
|
180
|
-
end
|
181
|
-
|
182
|
-
if options[:distinct] && column_name.to_s !~ /\s*DISTINCT\s+/i
|
183
|
-
distinct = 'DISTINCT '
|
184
|
-
end
|
185
|
-
sql = "SELECT #{operation}(#{distinct}#{column_name}) AS #{aggregate_alias}"
|
186
|
-
|
187
|
-
# A (slower) workaround if we're using a backend, like sqlite, that doesn't support COUNT DISTINCT.
|
188
|
-
sql = "SELECT COUNT(*) AS #{aggregate_alias}" if use_workaround
|
189
|
-
|
190
|
-
options[:group_fields].each_index{|i| sql << ", #{options[:group_fields][i]} AS #{options[:group_aliases][i]}" } if options[:group]
|
191
|
-
if options[:from]
|
192
|
-
sql << " FROM #{options[:from]} "
|
193
|
-
elsif scope && scope[:from] && !use_workaround
|
194
|
-
sql << " FROM #{scope[:from]} "
|
195
|
-
else
|
196
|
-
sql << " FROM (SELECT #{distinct}#{column_name}" if use_workaround
|
197
|
-
sql << " FROM #{connection.quote_table_name(table_name)} "
|
198
|
-
end
|
199
|
-
|
200
|
-
joins = ""
|
201
|
-
add_joins!(joins, options[:joins], scope)
|
202
|
-
|
203
|
-
if merged_includes.any?
|
204
|
-
join_dependency = ActiveRecord::Associations::ClassMethods::JoinDependency.new(self, merged_includes, joins)
|
205
|
-
sql << join_dependency.join_associations.collect{|join| join.association_join }.join
|
206
|
-
end
|
207
|
-
|
208
|
-
sql << joins unless joins.blank?
|
209
|
-
|
210
|
-
add_conditions!(sql, options[:conditions], scope)
|
211
|
-
add_limited_ids_condition!(sql, options, join_dependency) if join_dependency && !using_limitable_reflections?(join_dependency.reflections) && ((scope && scope[:limit]) || options[:limit])
|
212
|
-
|
213
|
-
if options[:group]
|
214
|
-
group_key = connection.adapter_name == 'FrontBase' ? :group_aliases : :group_fields
|
215
|
-
sql << " GROUP BY #{options[group_key].join(',')} "
|
216
|
-
end
|
217
|
-
|
218
|
-
if options[:group] && options[:having]
|
219
|
-
having = sanitize_sql_for_conditions(options[:having])
|
220
|
-
|
221
|
-
# FrontBase requires identifiers in the HAVING clause and chokes on function calls
|
222
|
-
if connection.adapter_name == 'FrontBase'
|
223
|
-
having.downcase!
|
224
|
-
having.gsub!(/#{operation}\s*\(\s*#{column_name}\s*\)/, aggregate_alias)
|
225
|
-
end
|
226
|
-
|
227
|
-
sql << " HAVING #{having} "
|
228
|
-
end
|
229
|
-
|
230
|
-
sql << " ORDER BY #{options[:order]} " if options[:order]
|
231
|
-
add_limit!(sql, options, scope)
|
232
|
-
sql << ") #{aggregate_alias}_subquery" if use_workaround
|
233
|
-
sql
|
234
|
-
end
|
235
|
-
|
236
|
-
def execute_simple_calculation(operation, column_name, column, options) #:nodoc:
|
237
|
-
value = connection.select_value(construct_calculation_sql(operation, column_name, options))
|
238
|
-
type_cast_calculated_value(value, column, operation)
|
239
|
-
end
|
240
|
-
|
241
|
-
def execute_grouped_calculation(operation, column_name, column, options) #:nodoc:
|
242
|
-
group_attr = options[:group]
|
243
|
-
association = reflect_on_association(group_attr.to_s.to_sym)
|
244
|
-
associated = association && association.macro == :belongs_to # only count belongs_to associations
|
245
|
-
group_fields = Array(associated ? association.primary_key_name : group_attr)
|
246
|
-
group_aliases = []
|
247
|
-
group_columns = {}
|
248
|
-
|
249
|
-
group_fields.each do |field|
|
250
|
-
group_aliases << column_alias_for(field)
|
251
|
-
group_columns[column_alias_for(field)] = column_for(field)
|
252
|
-
end
|
253
|
-
|
254
|
-
sql = construct_calculation_sql(operation, column_name, options.merge(:group_fields => group_fields, :group_aliases => group_aliases))
|
255
|
-
calculated_data = connection.select_all(sql)
|
256
|
-
aggregate_alias = column_alias_for(operation, column_name)
|
257
|
-
|
258
|
-
if association
|
259
|
-
key_ids = calculated_data.collect { |row| row[group_aliases.first] }
|
260
|
-
key_records = association.klass.base_class.find(key_ids)
|
261
|
-
key_records = key_records.inject({}) { |hsh, r| hsh.merge(r.id => r) }
|
262
|
-
end
|
263
|
-
|
264
|
-
calculated_data.inject(ActiveSupport::OrderedHash.new) do |all, row|
|
265
|
-
key = group_aliases.map{|group_alias| type_cast_calculated_value(row[group_alias], group_columns[group_alias])}
|
266
|
-
key = key.first if key.size == 1
|
267
|
-
key = key_records[key] if associated
|
268
|
-
value = row[aggregate_alias]
|
269
|
-
all[key] = type_cast_calculated_value(value, column, operation)
|
270
|
-
all
|
271
|
-
end
|
272
|
-
end
|
273
|
-
|
274
|
-
private
|
275
|
-
def validate_calculation_options(operation, options = {})
|
276
|
-
options.assert_valid_keys(CALCULATIONS_OPTIONS)
|
277
|
-
end
|
278
|
-
|
279
|
-
# Converts the given keys to the value that the database adapter returns as
|
280
|
-
# a usable column name:
|
281
|
-
#
|
282
|
-
# column_alias_for("users.id") # => "users_id"
|
283
|
-
# column_alias_for("sum(id)") # => "sum_id"
|
284
|
-
# column_alias_for("count(distinct users.id)") # => "count_distinct_users_id"
|
285
|
-
# column_alias_for("count(*)") # => "count_all"
|
286
|
-
# column_alias_for("count", "id") # => "count_id"
|
287
|
-
def column_alias_for(*keys)
|
288
|
-
table_name = keys.join(' ')
|
289
|
-
table_name.downcase!
|
290
|
-
table_name.gsub!(/\*/, 'all')
|
291
|
-
table_name.gsub!(/\W+/, ' ')
|
292
|
-
table_name.strip!
|
293
|
-
table_name.gsub!(/ +/, '_')
|
294
|
-
|
295
|
-
connection.table_alias_for(table_name)
|
296
|
-
end
|
297
|
-
|
298
|
-
def column_for(field)
|
299
|
-
field_name = field.to_s.split('.').last
|
300
|
-
columns.detect { |c| c.name.to_s == field_name }
|
301
|
-
end
|
302
|
-
|
303
|
-
def type_cast_calculated_value(value, column, operation = nil)
|
304
|
-
if value.is_a?(String) || value.nil?
|
305
|
-
case operation.to_s.downcase
|
306
|
-
when 'count' then value.to_i
|
307
|
-
when 'sum' then type_cast_using_column(value || '0', column)
|
308
|
-
when 'avg' then value.try(:to_d)
|
309
|
-
else type_cast_using_column(value, column)
|
310
|
-
end
|
311
|
-
else
|
312
|
-
value
|
313
|
-
end
|
314
|
-
end
|
315
|
-
|
316
|
-
def type_cast_using_column(value, column)
|
317
|
-
column ? column.type_cast(value) : value
|
318
|
-
end
|
319
|
-
end
|
320
|
-
end
|
321
|
-
end
|