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
@@ -0,0 +1,27 @@
|
|
1
|
+
module ActiveRecord
|
2
|
+
module Railties
|
3
|
+
class Subscriber < Rails::Subscriber
|
4
|
+
def sql(event)
|
5
|
+
name = '%s (%.1fms)' % [event.payload[:name], event.duration]
|
6
|
+
sql = event.payload[:sql].squeeze(' ')
|
7
|
+
|
8
|
+
if odd?
|
9
|
+
name = color(name, :cyan, true)
|
10
|
+
sql = color(sql, nil, true)
|
11
|
+
else
|
12
|
+
name = color(name, :magenta, true)
|
13
|
+
end
|
14
|
+
|
15
|
+
debug " #{name} #{sql}"
|
16
|
+
end
|
17
|
+
|
18
|
+
def odd?
|
19
|
+
@odd_or_even = !@odd_or_even
|
20
|
+
end
|
21
|
+
|
22
|
+
def logger
|
23
|
+
ActiveRecord::Base.logger
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -1,8 +1,6 @@
|
|
1
1
|
module ActiveRecord
|
2
2
|
module Reflection # :nodoc:
|
3
|
-
|
4
|
-
base.extend(ClassMethods)
|
5
|
-
end
|
3
|
+
extend ActiveSupport::Concern
|
6
4
|
|
7
5
|
# Reflection allows you to interrogate Active Record classes and objects about their associations and aggregations.
|
8
6
|
# This information can, for example, be used in a form builder that took an Active Record object and created input
|
@@ -18,7 +16,7 @@ module ActiveRecord
|
|
18
16
|
when :composed_of
|
19
17
|
reflection = AggregateReflection.new(macro, name, options, active_record)
|
20
18
|
end
|
21
|
-
|
19
|
+
write_inheritable_hash :reflections, name => reflection
|
22
20
|
reflection
|
23
21
|
end
|
24
22
|
|
@@ -277,17 +275,6 @@ module ActiveRecord
|
|
277
275
|
!options[:validate].nil? ? options[:validate] : (options[:autosave] == true || macro == :has_many)
|
278
276
|
end
|
279
277
|
|
280
|
-
def dependent_conditions(record, base_class, extra_conditions)
|
281
|
-
dependent_conditions = []
|
282
|
-
dependent_conditions << "#{primary_key_name} = #{record.send(name).send(:owner_quoted_id)}"
|
283
|
-
dependent_conditions << "#{options[:as]}_type = '#{base_class.name}'" if options[:as]
|
284
|
-
dependent_conditions << klass.send(:sanitize_sql, options[:conditions]) if options[:conditions]
|
285
|
-
dependent_conditions = dependent_conditions.collect {|where| "(#{where})" }.join(" AND ")
|
286
|
-
dependent_conditions << extra_conditions if extra_conditions
|
287
|
-
dependent_conditions = dependent_conditions.gsub('@', '\@')
|
288
|
-
dependent_conditions
|
289
|
-
end
|
290
|
-
|
291
278
|
private
|
292
279
|
def derive_class_name
|
293
280
|
class_name = name.to_s.camelize
|
@@ -0,0 +1,339 @@
|
|
1
|
+
module ActiveRecord
|
2
|
+
class Relation
|
3
|
+
JoinOperation = Struct.new(:relation, :join_class, :on)
|
4
|
+
ASSOCIATION_METHODS = [:includes, :eager_load, :preload]
|
5
|
+
MULTI_VALUE_METHODS = [:select, :group, :order, :joins, :where, :having]
|
6
|
+
SINGLE_VALUE_METHODS = [:limit, :offset, :lock, :readonly, :create_with, :from]
|
7
|
+
|
8
|
+
include FinderMethods, Calculations, SpawnMethods, QueryMethods
|
9
|
+
|
10
|
+
delegate :length, :collect, :map, :each, :all?, :include?, :to => :to_a
|
11
|
+
delegate :insert, :to => :arel
|
12
|
+
|
13
|
+
attr_reader :table, :klass
|
14
|
+
|
15
|
+
def initialize(klass, table)
|
16
|
+
@klass, @table = klass, table
|
17
|
+
(ASSOCIATION_METHODS + MULTI_VALUE_METHODS).each {|v| instance_variable_set(:"@#{v}_values", [])}
|
18
|
+
end
|
19
|
+
|
20
|
+
def new(*args, &block)
|
21
|
+
with_create_scope { @klass.new(*args, &block) }
|
22
|
+
end
|
23
|
+
|
24
|
+
alias build new
|
25
|
+
|
26
|
+
def create(*args, &block)
|
27
|
+
with_create_scope { @klass.create(*args, &block) }
|
28
|
+
end
|
29
|
+
|
30
|
+
def create!(*args, &block)
|
31
|
+
with_create_scope { @klass.create!(*args, &block) }
|
32
|
+
end
|
33
|
+
|
34
|
+
def respond_to?(method, include_private = false)
|
35
|
+
return true if arel.respond_to?(method, include_private) || Array.method_defined?(method) || @klass.respond_to?(method, include_private)
|
36
|
+
|
37
|
+
if match = DynamicFinderMatch.match(method)
|
38
|
+
return true if @klass.send(:all_attributes_exists?, match.attribute_names)
|
39
|
+
elsif match = DynamicScopeMatch.match(method)
|
40
|
+
return true if @klass.send(:all_attributes_exists?, match.attribute_names)
|
41
|
+
else
|
42
|
+
super
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def to_a
|
47
|
+
return @records if loaded?
|
48
|
+
|
49
|
+
@records = eager_loading? ? find_with_associations : @klass.find_by_sql(arel.to_sql)
|
50
|
+
|
51
|
+
preload = @preload_values
|
52
|
+
preload += @includes_values unless eager_loading?
|
53
|
+
preload.each {|associations| @klass.send(:preload_associations, @records, associations) }
|
54
|
+
|
55
|
+
# @readonly_value is true only if set explicity. @implicit_readonly is true if there are JOINS and no explicit SELECT.
|
56
|
+
readonly = @readonly_value.nil? ? @implicit_readonly : @readonly_value
|
57
|
+
@records.each { |record| record.readonly! } if readonly
|
58
|
+
|
59
|
+
@loaded = true
|
60
|
+
@records
|
61
|
+
end
|
62
|
+
|
63
|
+
def size
|
64
|
+
loaded? ? @records.length : count
|
65
|
+
end
|
66
|
+
|
67
|
+
def empty?
|
68
|
+
loaded? ? @records.empty? : count.zero?
|
69
|
+
end
|
70
|
+
|
71
|
+
def any?
|
72
|
+
if block_given?
|
73
|
+
to_a.any? { |*block_args| yield(*block_args) }
|
74
|
+
else
|
75
|
+
!empty?
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def many?
|
80
|
+
if block_given?
|
81
|
+
to_a.many? { |*block_args| yield(*block_args) }
|
82
|
+
else
|
83
|
+
@limit_value.present? ? to_a.many? : size > 1
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
# Updates all records with details given if they match a set of conditions supplied, limits and order can
|
88
|
+
# also be supplied. This method constructs a single SQL UPDATE statement and sends it straight to the
|
89
|
+
# database. It does not instantiate the involved models and it does not trigger Active Record callbacks
|
90
|
+
# or validations.
|
91
|
+
#
|
92
|
+
# ==== Parameters
|
93
|
+
#
|
94
|
+
# * +updates+ - A string, array, or hash representing the SET part of an SQL statement.
|
95
|
+
# * +conditions+ - A string, array, or hash representing the WHERE part of an SQL statement. See conditions in the intro.
|
96
|
+
# * +options+ - Additional options are <tt>:limit</tt> and <tt>:order</tt>, see the examples for usage.
|
97
|
+
#
|
98
|
+
# ==== Examples
|
99
|
+
#
|
100
|
+
# # Update all customers with the given attributes
|
101
|
+
# Customer.update_all :wants_email => true
|
102
|
+
#
|
103
|
+
# # Update all books with 'Rails' in their title
|
104
|
+
# Book.update_all "author = 'David'", "title LIKE '%Rails%'"
|
105
|
+
#
|
106
|
+
# # Update all avatars migrated more than a week ago
|
107
|
+
# Avatar.update_all ['migrated_at = ?', Time.now.utc], ['migrated_at > ?', 1.week.ago]
|
108
|
+
#
|
109
|
+
# # Update all books that match our conditions, but limit it to 5 ordered by date
|
110
|
+
# Book.update_all "author = 'David'", "title LIKE '%Rails%'", :order => 'created_at', :limit => 5
|
111
|
+
def update_all(updates, conditions = nil, options = {})
|
112
|
+
if conditions || options.present?
|
113
|
+
where(conditions).apply_finder_options(options.slice(:limit, :order)).update_all(updates)
|
114
|
+
else
|
115
|
+
# Apply limit and order only if they're both present
|
116
|
+
if @limit_value.present? == @order_values.present?
|
117
|
+
arel.update(@klass.send(:sanitize_sql_for_assignment, updates))
|
118
|
+
else
|
119
|
+
except(:limit, :order).update_all(updates)
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
# Updates an object (or multiple objects) and saves it to the database, if validations pass.
|
125
|
+
# The resulting object is returned whether the object was saved successfully to the database or not.
|
126
|
+
#
|
127
|
+
# ==== Parameters
|
128
|
+
#
|
129
|
+
# * +id+ - This should be the id or an array of ids to be updated.
|
130
|
+
# * +attributes+ - This should be a hash of attributes to be set on the object, or an array of hashes.
|
131
|
+
#
|
132
|
+
# ==== Examples
|
133
|
+
#
|
134
|
+
# # Updating one record:
|
135
|
+
# Person.update(15, :user_name => 'Samuel', :group => 'expert')
|
136
|
+
#
|
137
|
+
# # Updating multiple records:
|
138
|
+
# people = { 1 => { "first_name" => "David" }, 2 => { "first_name" => "Jeremy" } }
|
139
|
+
# Person.update(people.keys, people.values)
|
140
|
+
def update(id, attributes)
|
141
|
+
if id.is_a?(Array)
|
142
|
+
idx = -1
|
143
|
+
id.collect { |one_id| idx += 1; update(one_id, attributes[idx]) }
|
144
|
+
else
|
145
|
+
object = find(id)
|
146
|
+
object.update_attributes(attributes)
|
147
|
+
object
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
# Destroys the records matching +conditions+ by instantiating each
|
152
|
+
# record and calling its +destroy+ method. Each object's callbacks are
|
153
|
+
# executed (including <tt>:dependent</tt> association options and
|
154
|
+
# +before_destroy+/+after_destroy+ Observer methods). Returns the
|
155
|
+
# collection of objects that were destroyed; each will be frozen, to
|
156
|
+
# reflect that no changes should be made (since they can't be
|
157
|
+
# persisted).
|
158
|
+
#
|
159
|
+
# Note: Instantiation, callback execution, and deletion of each
|
160
|
+
# record can be time consuming when you're removing many records at
|
161
|
+
# once. It generates at least one SQL +DELETE+ query per record (or
|
162
|
+
# possibly more, to enforce your callbacks). If you want to delete many
|
163
|
+
# rows quickly, without concern for their associations or callbacks, use
|
164
|
+
# +delete_all+ instead.
|
165
|
+
#
|
166
|
+
# ==== Parameters
|
167
|
+
#
|
168
|
+
# * +conditions+ - A string, array, or hash that specifies which records
|
169
|
+
# to destroy. If omitted, all records are destroyed. See the
|
170
|
+
# Conditions section in the introduction to ActiveRecord::Base for
|
171
|
+
# more information.
|
172
|
+
#
|
173
|
+
# ==== Examples
|
174
|
+
#
|
175
|
+
# Person.destroy_all("last_login < '2004-04-04'")
|
176
|
+
# Person.destroy_all(:status => "inactive")
|
177
|
+
def destroy_all(conditions = nil)
|
178
|
+
if conditions
|
179
|
+
where(conditions).destroy_all
|
180
|
+
else
|
181
|
+
to_a.each {|object| object.destroy}
|
182
|
+
reset
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
# Destroy an object (or multiple objects) that has the given id, the object is instantiated first,
|
187
|
+
# therefore all callbacks and filters are fired off before the object is deleted. This method is
|
188
|
+
# less efficient than ActiveRecord#delete but allows cleanup methods and other actions to be run.
|
189
|
+
#
|
190
|
+
# This essentially finds the object (or multiple objects) with the given id, creates a new object
|
191
|
+
# from the attributes, and then calls destroy on it.
|
192
|
+
#
|
193
|
+
# ==== Parameters
|
194
|
+
#
|
195
|
+
# * +id+ - Can be either an Integer or an Array of Integers.
|
196
|
+
#
|
197
|
+
# ==== Examples
|
198
|
+
#
|
199
|
+
# # Destroy a single object
|
200
|
+
# Todo.destroy(1)
|
201
|
+
#
|
202
|
+
# # Destroy multiple objects
|
203
|
+
# todos = [1,2,3]
|
204
|
+
# Todo.destroy(todos)
|
205
|
+
def destroy(id)
|
206
|
+
if id.is_a?(Array)
|
207
|
+
id.map { |one_id| destroy(one_id) }
|
208
|
+
else
|
209
|
+
find(id).destroy
|
210
|
+
end
|
211
|
+
end
|
212
|
+
|
213
|
+
# Deletes the records matching +conditions+ without instantiating the records first, and hence not
|
214
|
+
# calling the +destroy+ method nor invoking callbacks. This is a single SQL DELETE statement that
|
215
|
+
# goes straight to the database, much more efficient than +destroy_all+. Be careful with relations
|
216
|
+
# though, in particular <tt>:dependent</tt> rules defined on associations are not honored. Returns
|
217
|
+
# the number of rows affected.
|
218
|
+
#
|
219
|
+
# ==== Parameters
|
220
|
+
#
|
221
|
+
# * +conditions+ - Conditions are specified the same way as with +find+ method.
|
222
|
+
#
|
223
|
+
# ==== Example
|
224
|
+
#
|
225
|
+
# Post.delete_all("person_id = 5 AND (category = 'Something' OR category = 'Else')")
|
226
|
+
# Post.delete_all(["person_id = ? AND (category = ? OR category = ?)", 5, 'Something', 'Else'])
|
227
|
+
#
|
228
|
+
# Both calls delete the affected posts all at once with a single DELETE statement. If you need to destroy dependent
|
229
|
+
# associations or call your <tt>before_*</tt> or +after_destroy+ callbacks, use the +destroy_all+ method instead.
|
230
|
+
def delete_all(conditions = nil)
|
231
|
+
conditions ? where(conditions).delete_all : arel.delete.tap { reset }
|
232
|
+
end
|
233
|
+
|
234
|
+
# Deletes the row with a primary key matching the +id+ argument, using a
|
235
|
+
# SQL +DELETE+ statement, and returns the number of rows deleted. Active
|
236
|
+
# Record objects are not instantiated, so the object's callbacks are not
|
237
|
+
# executed, including any <tt>:dependent</tt> association options or
|
238
|
+
# Observer methods.
|
239
|
+
#
|
240
|
+
# You can delete multiple rows at once by passing an Array of <tt>id</tt>s.
|
241
|
+
#
|
242
|
+
# Note: Although it is often much faster than the alternative,
|
243
|
+
# <tt>#destroy</tt>, skipping callbacks might bypass business logic in
|
244
|
+
# your application that ensures referential integrity or performs other
|
245
|
+
# essential jobs.
|
246
|
+
#
|
247
|
+
# ==== Examples
|
248
|
+
#
|
249
|
+
# # Delete a single row
|
250
|
+
# Todo.delete(1)
|
251
|
+
#
|
252
|
+
# # Delete multiple rows
|
253
|
+
# Todo.delete([2,3,4])
|
254
|
+
def delete(id_or_array)
|
255
|
+
where(@klass.primary_key => id_or_array).delete_all
|
256
|
+
end
|
257
|
+
|
258
|
+
def loaded?
|
259
|
+
@loaded
|
260
|
+
end
|
261
|
+
|
262
|
+
def reload
|
263
|
+
reset
|
264
|
+
to_a # force reload
|
265
|
+
self
|
266
|
+
end
|
267
|
+
|
268
|
+
def reset
|
269
|
+
@first = @last = @to_sql = @order_clause = @scope_for_create = @arel = @loaded = nil
|
270
|
+
@should_eager_load = @join_dependency = nil
|
271
|
+
@records = []
|
272
|
+
self
|
273
|
+
end
|
274
|
+
|
275
|
+
def primary_key
|
276
|
+
@primary_key ||= table[@klass.primary_key]
|
277
|
+
end
|
278
|
+
|
279
|
+
def to_sql
|
280
|
+
@to_sql ||= arel.to_sql
|
281
|
+
end
|
282
|
+
|
283
|
+
def scope_for_create
|
284
|
+
@scope_for_create ||= begin
|
285
|
+
@create_with_value || @where_values.inject({}) do |hash, where|
|
286
|
+
if where.is_a?(Arel::Predicates::Equality)
|
287
|
+
hash[where.operand1.name] = where.operand2.respond_to?(:value) ? where.operand2.value : where.operand2
|
288
|
+
end
|
289
|
+
|
290
|
+
hash
|
291
|
+
end
|
292
|
+
end
|
293
|
+
end
|
294
|
+
|
295
|
+
def eager_loading?
|
296
|
+
@should_eager_load ||= (@eager_load_values.any? || (@includes_values.any? && references_eager_loaded_tables?))
|
297
|
+
end
|
298
|
+
|
299
|
+
protected
|
300
|
+
|
301
|
+
def method_missing(method, *args, &block)
|
302
|
+
if Array.method_defined?(method)
|
303
|
+
to_a.send(method, *args, &block)
|
304
|
+
elsif @klass.respond_to?(method)
|
305
|
+
@klass.send(:with_scope, self) { @klass.send(method, *args, &block) }
|
306
|
+
elsif arel.respond_to?(method)
|
307
|
+
arel.send(method, *args, &block)
|
308
|
+
elsif match = DynamicFinderMatch.match(method)
|
309
|
+
attributes = match.attribute_names
|
310
|
+
super unless @klass.send(:all_attributes_exists?, attributes)
|
311
|
+
|
312
|
+
if match.finder?
|
313
|
+
find_by_attributes(match, attributes, *args)
|
314
|
+
elsif match.instantiator?
|
315
|
+
find_or_instantiator_by_attributes(match, attributes, *args, &block)
|
316
|
+
end
|
317
|
+
else
|
318
|
+
super
|
319
|
+
end
|
320
|
+
end
|
321
|
+
|
322
|
+
private
|
323
|
+
|
324
|
+
def with_create_scope
|
325
|
+
@klass.send(:with_scope, :create => scope_for_create, :find => {}) { yield }
|
326
|
+
end
|
327
|
+
|
328
|
+
def references_eager_loaded_tables?
|
329
|
+
joined_tables = (tables_in_string(arel.joins(arel)) + [table.name, table.table_alias]).compact.uniq
|
330
|
+
(tables_in_string(to_sql) - joined_tables).any?
|
331
|
+
end
|
332
|
+
|
333
|
+
def tables_in_string(string)
|
334
|
+
return [] if string.blank?
|
335
|
+
string.scan(/([a-zA-Z_][\.\w]+).?\./).flatten.uniq
|
336
|
+
end
|
337
|
+
|
338
|
+
end
|
339
|
+
end
|
@@ -0,0 +1,259 @@
|
|
1
|
+
module ActiveRecord
|
2
|
+
module Calculations
|
3
|
+
# Count operates using three different approaches.
|
4
|
+
#
|
5
|
+
# * Count all: By not passing any parameters to count, it will return a count of all the rows for the model.
|
6
|
+
# * 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
|
7
|
+
# * Count using options will find the row count matched by the options used.
|
8
|
+
#
|
9
|
+
# The third approach, count using options, accepts an option hash as the only parameter. The options are:
|
10
|
+
#
|
11
|
+
# * <tt>:conditions</tt>: An SQL fragment like "administrator = 1" or [ "user_name = ?", username ]. See conditions in the intro to ActiveRecord::Base.
|
12
|
+
# * <tt>:joins</tt>: Either an SQL fragment for additional joins like "LEFT JOIN comments ON comments.post_id = id" (rarely needed)
|
13
|
+
# 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).
|
14
|
+
# 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.
|
15
|
+
# Pass <tt>:readonly => false</tt> to override.
|
16
|
+
# * <tt>:include</tt>: Named associations that should be loaded alongside using LEFT OUTER JOINs. The symbols named refer
|
17
|
+
# to already defined associations. When using named associations, count returns the number of DISTINCT items for the model you're counting.
|
18
|
+
# See eager loading under Associations.
|
19
|
+
# * <tt>:order</tt>: An SQL fragment like "created_at DESC, name" (really only used with GROUP BY calculations).
|
20
|
+
# * <tt>:group</tt>: An attribute name by which the result should be grouped. Uses the GROUP BY SQL-clause.
|
21
|
+
# * <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
|
22
|
+
# include the joined columns.
|
23
|
+
# * <tt>:distinct</tt>: Set this to true to make this a distinct calculation, such as SELECT COUNT(DISTINCT posts.id) ...
|
24
|
+
# * <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
|
25
|
+
# of a database view).
|
26
|
+
#
|
27
|
+
# Examples for counting all:
|
28
|
+
# Person.count # returns the total count of all people
|
29
|
+
#
|
30
|
+
# Examples for counting by column:
|
31
|
+
# Person.count(:age) # returns the total count of all people whose age is present in database
|
32
|
+
#
|
33
|
+
# Examples for count with options:
|
34
|
+
# Person.count(:conditions => "age > 26")
|
35
|
+
# 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.
|
36
|
+
# 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.
|
37
|
+
# Person.count('id', :conditions => "age > 26") # Performs a COUNT(id)
|
38
|
+
# Person.count(:all, :conditions => "age > 26") # Performs a COUNT(*) (:all is an alias for '*')
|
39
|
+
#
|
40
|
+
# Note: <tt>Person.count(:all)</tt> will not work because it will use <tt>:all</tt> as the condition. Use Person.count instead.
|
41
|
+
def count(column_name = nil, options = {})
|
42
|
+
column_name, options = nil, column_name if column_name.is_a?(Hash)
|
43
|
+
calculate(:count, column_name, options)
|
44
|
+
end
|
45
|
+
|
46
|
+
# Calculates the average value on a given column. The value is returned as
|
47
|
+
# a float, or +nil+ if there's no row. See +calculate+ for examples with
|
48
|
+
# options.
|
49
|
+
#
|
50
|
+
# Person.average('age') # => 35.8
|
51
|
+
def average(column_name, options = {})
|
52
|
+
calculate(:average, column_name, options)
|
53
|
+
end
|
54
|
+
|
55
|
+
# Calculates the minimum value on a given column. The value is returned
|
56
|
+
# with the same data type of the column, or +nil+ if there's no row. See
|
57
|
+
# +calculate+ for examples with options.
|
58
|
+
#
|
59
|
+
# Person.minimum('age') # => 7
|
60
|
+
def minimum(column_name, options = {})
|
61
|
+
calculate(:minimum, column_name, options)
|
62
|
+
end
|
63
|
+
|
64
|
+
# Calculates the maximum value on a given column. The value is returned
|
65
|
+
# with the same data type of the column, or +nil+ if there's no row. See
|
66
|
+
# +calculate+ for examples with options.
|
67
|
+
#
|
68
|
+
# Person.maximum('age') # => 93
|
69
|
+
def maximum(column_name, options = {})
|
70
|
+
calculate(:maximum, column_name, options)
|
71
|
+
end
|
72
|
+
|
73
|
+
# Calculates the sum of values on a given column. The value is returned
|
74
|
+
# with the same data type of the column, 0 if there's no row. See
|
75
|
+
# +calculate+ for examples with options.
|
76
|
+
#
|
77
|
+
# Person.sum('age') # => 4562
|
78
|
+
def sum(column_name, options = {})
|
79
|
+
calculate(:sum, column_name, options)
|
80
|
+
end
|
81
|
+
|
82
|
+
# This calculates aggregate values in the given column. Methods for count, sum, average, minimum, and maximum have been added as shortcuts.
|
83
|
+
# 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.
|
84
|
+
#
|
85
|
+
# There are two basic forms of output:
|
86
|
+
# * 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.
|
87
|
+
# * 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
|
88
|
+
# of a belongs_to association.
|
89
|
+
#
|
90
|
+
# values = Person.maximum(:age, :group => 'last_name')
|
91
|
+
# puts values["Drake"]
|
92
|
+
# => 43
|
93
|
+
#
|
94
|
+
# drake = Family.find_by_last_name('Drake')
|
95
|
+
# values = Person.maximum(:age, :group => :family) # Person belongs_to :family
|
96
|
+
# puts values[drake]
|
97
|
+
# => 43
|
98
|
+
#
|
99
|
+
# values.each do |family, max_age|
|
100
|
+
# ...
|
101
|
+
# end
|
102
|
+
#
|
103
|
+
# Options:
|
104
|
+
# * <tt>:conditions</tt> - An SQL fragment like "administrator = 1" or [ "user_name = ?", username ]. See conditions in the intro to ActiveRecord::Base.
|
105
|
+
# * <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.
|
106
|
+
# * <tt>:joins</tt> - An SQL fragment for additional joins like "LEFT JOIN comments ON comments.post_id = id". (Rarely needed).
|
107
|
+
# The records will be returned read-only since they will have attributes that do not correspond to the table's columns.
|
108
|
+
# * <tt>:order</tt> - An SQL fragment like "created_at DESC, name" (really only used with GROUP BY calculations).
|
109
|
+
# * <tt>:group</tt> - An attribute name by which the result should be grouped. Uses the GROUP BY SQL-clause.
|
110
|
+
# * <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
|
111
|
+
# include the joined columns.
|
112
|
+
# * <tt>:distinct</tt> - Set this to true to make this a distinct calculation, such as SELECT COUNT(DISTINCT posts.id) ...
|
113
|
+
#
|
114
|
+
# Examples:
|
115
|
+
# Person.calculate(:count, :all) # The same as Person.count
|
116
|
+
# Person.average(:age) # SELECT AVG(age) FROM people...
|
117
|
+
# Person.minimum(:age, :conditions => ['last_name != ?', 'Drake']) # Selects the minimum age for everyone with a last name other than 'Drake'
|
118
|
+
# Person.minimum(:age, :having => 'min(age) > 17', :group => :last_name) # Selects the minimum age for any family without any minors
|
119
|
+
# Person.sum("2 * age")
|
120
|
+
def calculate(operation, column_name, options = {})
|
121
|
+
if options.except(:distinct).present?
|
122
|
+
apply_finder_options(options.except(:distinct)).calculate(operation, column_name, :distinct => options[:distinct])
|
123
|
+
else
|
124
|
+
if eager_loading? || includes_values.present?
|
125
|
+
construct_relation_for_association_calculations.calculate(operation, column_name, options)
|
126
|
+
else
|
127
|
+
perform_calculation(operation, column_name, options)
|
128
|
+
end
|
129
|
+
end
|
130
|
+
rescue ThrowResult
|
131
|
+
0
|
132
|
+
end
|
133
|
+
|
134
|
+
private
|
135
|
+
|
136
|
+
def perform_calculation(operation, column_name, options = {})
|
137
|
+
operation = operation.to_s.downcase
|
138
|
+
|
139
|
+
if operation == "count"
|
140
|
+
column_name ||= (select_for_count || :all)
|
141
|
+
|
142
|
+
joins = arel.joins(arel)
|
143
|
+
if joins.present? && joins =~ /LEFT OUTER/i
|
144
|
+
distinct = true
|
145
|
+
column_name = @klass.primary_key if column_name == :all
|
146
|
+
end
|
147
|
+
|
148
|
+
distinct = nil if column_name.to_s =~ /\s*DISTINCT\s+/i
|
149
|
+
distinct ||= options[:distinct]
|
150
|
+
else
|
151
|
+
distinct = nil
|
152
|
+
end
|
153
|
+
|
154
|
+
distinct = options[:distinct] || distinct
|
155
|
+
column_name = :all if column_name.blank? && operation == "count"
|
156
|
+
|
157
|
+
if @group_values.any?
|
158
|
+
return execute_grouped_calculation(operation, column_name)
|
159
|
+
else
|
160
|
+
return execute_simple_calculation(operation, column_name, distinct)
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
def execute_simple_calculation(operation, column_name, distinct) #:nodoc:
|
165
|
+
column = if @klass.column_names.include?(column_name.to_s)
|
166
|
+
Arel::Attribute.new(@klass.unscoped, column_name)
|
167
|
+
else
|
168
|
+
Arel::SqlLiteral.new(column_name == :all ? "*" : column_name.to_s)
|
169
|
+
end
|
170
|
+
|
171
|
+
# Postgresql doesn't like ORDER BY when there are no GROUP BY
|
172
|
+
relation = except(:order).select(operation == 'count' ? column.count(distinct) : column.send(operation))
|
173
|
+
type_cast_calculated_value(@klass.connection.select_value(relation.to_sql), column_for(column_name), operation)
|
174
|
+
end
|
175
|
+
|
176
|
+
def execute_grouped_calculation(operation, column_name) #:nodoc:
|
177
|
+
group_attr = @group_values.first
|
178
|
+
association = @klass.reflect_on_association(group_attr.to_sym)
|
179
|
+
associated = association && association.macro == :belongs_to # only count belongs_to associations
|
180
|
+
group_field = associated ? association.primary_key_name : group_attr
|
181
|
+
group_alias = column_alias_for(group_field)
|
182
|
+
group_column = column_for(group_field)
|
183
|
+
|
184
|
+
group = @klass.connection.adapter_name == 'FrontBase' ? group_alias : group_field
|
185
|
+
|
186
|
+
aggregate_alias = column_alias_for(operation, column_name)
|
187
|
+
|
188
|
+
select_statement = if operation == 'count' && column_name == :all
|
189
|
+
"COUNT(*) AS count_all"
|
190
|
+
else
|
191
|
+
Arel::Attribute.new(@klass.unscoped, column_name).send(operation).as(aggregate_alias).to_sql
|
192
|
+
end
|
193
|
+
|
194
|
+
select_statement << ", #{group_field} AS #{group_alias}"
|
195
|
+
|
196
|
+
relation = select(select_statement).group(group)
|
197
|
+
|
198
|
+
calculated_data = @klass.connection.select_all(relation.to_sql)
|
199
|
+
|
200
|
+
if association
|
201
|
+
key_ids = calculated_data.collect { |row| row[group_alias] }
|
202
|
+
key_records = association.klass.base_class.find(key_ids)
|
203
|
+
key_records = key_records.inject({}) { |hsh, r| hsh.merge(r.id => r) }
|
204
|
+
end
|
205
|
+
|
206
|
+
calculated_data.inject(ActiveSupport::OrderedHash.new) do |all, row|
|
207
|
+
key = type_cast_calculated_value(row[group_alias], group_column)
|
208
|
+
key = key_records[key] if associated
|
209
|
+
value = row[aggregate_alias]
|
210
|
+
all[key] = type_cast_calculated_value(value, column_for(column_name), operation)
|
211
|
+
all
|
212
|
+
end
|
213
|
+
end
|
214
|
+
|
215
|
+
# Converts the given keys to the value that the database adapter returns as
|
216
|
+
# a usable column name:
|
217
|
+
#
|
218
|
+
# column_alias_for("users.id") # => "users_id"
|
219
|
+
# column_alias_for("sum(id)") # => "sum_id"
|
220
|
+
# column_alias_for("count(distinct users.id)") # => "count_distinct_users_id"
|
221
|
+
# column_alias_for("count(*)") # => "count_all"
|
222
|
+
# column_alias_for("count", "id") # => "count_id"
|
223
|
+
def column_alias_for(*keys)
|
224
|
+
table_name = keys.join(' ')
|
225
|
+
table_name.downcase!
|
226
|
+
table_name.gsub!(/\*/, 'all')
|
227
|
+
table_name.gsub!(/\W+/, ' ')
|
228
|
+
table_name.strip!
|
229
|
+
table_name.gsub!(/ +/, '_')
|
230
|
+
|
231
|
+
@klass.connection.table_alias_for(table_name)
|
232
|
+
end
|
233
|
+
|
234
|
+
def column_for(field)
|
235
|
+
field_name = field.to_s.split('.').last
|
236
|
+
@klass.columns.detect { |c| c.name.to_s == field_name }
|
237
|
+
end
|
238
|
+
|
239
|
+
def type_cast_calculated_value(value, column, operation = nil)
|
240
|
+
case operation
|
241
|
+
when 'count' then value.to_i
|
242
|
+
when 'sum' then type_cast_using_column(value || '0', column)
|
243
|
+
when 'average' then value && (value.is_a?(Fixnum) ? value.to_f : value).to_d
|
244
|
+
else type_cast_using_column(value, column)
|
245
|
+
end
|
246
|
+
end
|
247
|
+
|
248
|
+
def type_cast_using_column(value, column)
|
249
|
+
column ? column.type_cast(value) : value
|
250
|
+
end
|
251
|
+
|
252
|
+
def select_for_count
|
253
|
+
if @select_values.present?
|
254
|
+
select = @select_values.join(", ")
|
255
|
+
select if select !~ /(,|\*)/
|
256
|
+
end
|
257
|
+
end
|
258
|
+
end
|
259
|
+
end
|