activerecord 4.2.11.3 → 6.0.0
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of activerecord might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/CHANGELOG.md +613 -1643
- data/MIT-LICENSE +4 -2
- data/README.rdoc +13 -12
- data/examples/performance.rb +33 -32
- data/examples/simple.rb +5 -4
- data/lib/active_record.rb +41 -22
- data/lib/active_record/aggregations.rb +267 -251
- data/lib/active_record/association_relation.rb +11 -6
- data/lib/active_record/associations.rb +1737 -1597
- data/lib/active_record/associations/alias_tracker.rb +29 -35
- data/lib/active_record/associations/association.rb +125 -58
- data/lib/active_record/associations/association_scope.rb +103 -132
- data/lib/active_record/associations/belongs_to_association.rb +65 -60
- data/lib/active_record/associations/belongs_to_polymorphic_association.rb +8 -12
- data/lib/active_record/associations/builder/association.rb +27 -40
- data/lib/active_record/associations/builder/belongs_to.rb +69 -55
- data/lib/active_record/associations/builder/collection_association.rb +10 -33
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +52 -66
- data/lib/active_record/associations/builder/has_many.rb +8 -4
- data/lib/active_record/associations/builder/has_one.rb +46 -5
- data/lib/active_record/associations/builder/singular_association.rb +16 -10
- data/lib/active_record/associations/collection_association.rb +131 -287
- data/lib/active_record/associations/collection_proxy.rb +241 -146
- data/lib/active_record/associations/foreign_association.rb +10 -1
- data/lib/active_record/associations/has_many_association.rb +34 -97
- data/lib/active_record/associations/has_many_through_association.rb +60 -87
- data/lib/active_record/associations/has_one_association.rb +61 -49
- data/lib/active_record/associations/has_one_through_association.rb +20 -11
- data/lib/active_record/associations/join_dependency.rb +137 -167
- data/lib/active_record/associations/join_dependency/join_association.rb +38 -86
- data/lib/active_record/associations/join_dependency/join_base.rb +10 -9
- data/lib/active_record/associations/join_dependency/join_part.rb +14 -14
- data/lib/active_record/associations/preloader.rb +90 -92
- data/lib/active_record/associations/preloader/association.rb +90 -123
- data/lib/active_record/associations/preloader/through_association.rb +85 -65
- data/lib/active_record/associations/singular_association.rb +18 -39
- data/lib/active_record/associations/through_association.rb +38 -18
- data/lib/active_record/attribute_assignment.rb +56 -183
- data/lib/active_record/attribute_decorators.rb +39 -15
- data/lib/active_record/attribute_methods.rb +120 -135
- data/lib/active_record/attribute_methods/before_type_cast.rb +13 -8
- data/lib/active_record/attribute_methods/dirty.rb +174 -144
- data/lib/active_record/attribute_methods/primary_key.rb +91 -83
- data/lib/active_record/attribute_methods/query.rb +6 -5
- data/lib/active_record/attribute_methods/read.rb +20 -76
- data/lib/active_record/attribute_methods/serialization.rb +40 -20
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +58 -36
- data/lib/active_record/attribute_methods/write.rb +32 -54
- data/lib/active_record/attributes.rb +214 -82
- data/lib/active_record/autosave_association.rb +91 -37
- data/lib/active_record/base.rb +57 -45
- data/lib/active_record/callbacks.rb +100 -74
- data/lib/active_record/coders/json.rb +3 -1
- data/lib/active_record/coders/yaml_column.rb +24 -12
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +796 -296
- data/lib/active_record/connection_adapters/abstract/database_limits.rb +26 -8
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +234 -115
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +82 -23
- data/lib/active_record/connection_adapters/abstract/quoting.rb +170 -53
- data/lib/active_record/connection_adapters/abstract/savepoints.rb +5 -3
- data/lib/active_record/connection_adapters/abstract/schema_creation.rb +74 -46
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +356 -227
- data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +79 -36
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +664 -243
- data/lib/active_record/connection_adapters/abstract/transaction.rb +191 -83
- data/lib/active_record/connection_adapters/abstract_adapter.rb +460 -204
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +510 -635
- data/lib/active_record/connection_adapters/column.rb +56 -43
- data/lib/active_record/connection_adapters/connection_specification.rb +174 -152
- data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +29 -0
- data/lib/active_record/connection_adapters/mysql/column.rb +27 -0
- data/lib/active_record/connection_adapters/mysql/database_statements.rb +200 -0
- data/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb +72 -0
- data/lib/active_record/connection_adapters/mysql/quoting.rb +81 -0
- data/lib/active_record/connection_adapters/mysql/schema_creation.rb +72 -0
- data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +95 -0
- data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +88 -0
- data/lib/active_record/connection_adapters/mysql/schema_statements.rb +264 -0
- data/lib/active_record/connection_adapters/mysql/type_metadata.rb +31 -0
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +58 -180
- data/lib/active_record/connection_adapters/postgresql/column.rb +21 -11
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +64 -114
- data/lib/active_record/connection_adapters/postgresql/explain_pretty_printer.rb +44 -0
- data/lib/active_record/connection_adapters/postgresql/oid.rb +23 -25
- data/lib/active_record/connection_adapters/postgresql/oid/array.rb +50 -58
- data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +9 -8
- data/lib/active_record/connection_adapters/postgresql/oid/bit_varying.rb +2 -0
- data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +4 -2
- data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +5 -1
- data/lib/active_record/connection_adapters/postgresql/oid/date.rb +13 -1
- data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +9 -22
- data/lib/active_record/connection_adapters/postgresql/oid/decimal.rb +3 -1
- data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +5 -3
- data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +31 -19
- data/lib/active_record/connection_adapters/postgresql/oid/inet.rb +2 -0
- data/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb +3 -11
- data/lib/active_record/connection_adapters/postgresql/oid/legacy_point.rb +45 -0
- data/lib/active_record/connection_adapters/postgresql/oid/money.rb +7 -9
- data/lib/active_record/connection_adapters/postgresql/oid/{integer.rb → oid.rb} +6 -2
- data/lib/active_record/connection_adapters/postgresql/oid/point.rb +33 -11
- data/lib/active_record/connection_adapters/postgresql/oid/range.rb +52 -34
- data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +4 -5
- data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +58 -54
- data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +10 -5
- data/lib/active_record/connection_adapters/postgresql/oid/vector.rb +3 -1
- data/lib/active_record/connection_adapters/postgresql/oid/xml.rb +3 -1
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +144 -47
- data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +27 -14
- data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +76 -0
- data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +178 -108
- data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +50 -0
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +470 -290
- data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +36 -0
- data/lib/active_record/connection_adapters/postgresql/utils.rb +12 -8
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +551 -356
- data/lib/active_record/connection_adapters/schema_cache.rb +72 -25
- data/lib/active_record/connection_adapters/sql_type_metadata.rb +37 -0
- data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +118 -0
- data/lib/active_record/connection_adapters/sqlite3/explain_pretty_printer.rb +21 -0
- data/lib/active_record/connection_adapters/sqlite3/quoting.rb +103 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +17 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +19 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +18 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +137 -0
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +290 -345
- data/lib/active_record/connection_adapters/statement_pool.rb +34 -13
- data/lib/active_record/connection_handling.rb +176 -41
- data/lib/active_record/core.rb +251 -231
- data/lib/active_record/counter_cache.rb +67 -49
- data/lib/active_record/database_configurations.rb +233 -0
- data/lib/active_record/database_configurations/database_config.rb +37 -0
- data/lib/active_record/database_configurations/hash_config.rb +50 -0
- data/lib/active_record/database_configurations/url_config.rb +79 -0
- data/lib/active_record/define_callbacks.rb +22 -0
- data/lib/active_record/dynamic_matchers.rb +87 -105
- data/lib/active_record/enum.rb +163 -86
- data/lib/active_record/errors.rb +188 -53
- data/lib/active_record/explain.rb +23 -11
- data/lib/active_record/explain_registry.rb +4 -2
- data/lib/active_record/explain_subscriber.rb +10 -5
- data/lib/active_record/fixture_set/file.rb +35 -9
- data/lib/active_record/fixture_set/model_metadata.rb +33 -0
- data/lib/active_record/fixture_set/render_context.rb +17 -0
- data/lib/active_record/fixture_set/table_row.rb +153 -0
- data/lib/active_record/fixture_set/table_rows.rb +47 -0
- data/lib/active_record/fixtures.rb +228 -499
- data/lib/active_record/gem_version.rb +6 -4
- data/lib/active_record/inheritance.rb +158 -112
- data/lib/active_record/insert_all.rb +179 -0
- data/lib/active_record/integration.rb +123 -29
- data/lib/active_record/internal_metadata.rb +53 -0
- data/lib/active_record/legacy_yaml_adapter.rb +21 -3
- data/lib/active_record/locale/en.yml +3 -2
- data/lib/active_record/locking/optimistic.rb +87 -96
- data/lib/active_record/locking/pessimistic.rb +18 -6
- data/lib/active_record/log_subscriber.rb +76 -33
- data/lib/active_record/middleware/database_selector.rb +75 -0
- data/lib/active_record/middleware/database_selector/resolver.rb +92 -0
- data/lib/active_record/middleware/database_selector/resolver/session.rb +45 -0
- data/lib/active_record/migration.rb +621 -303
- data/lib/active_record/migration/command_recorder.rb +177 -90
- data/lib/active_record/migration/compatibility.rb +244 -0
- data/lib/active_record/migration/join_table.rb +8 -6
- data/lib/active_record/model_schema.rb +312 -112
- data/lib/active_record/nested_attributes.rb +264 -222
- data/lib/active_record/no_touching.rb +14 -1
- data/lib/active_record/null_relation.rb +24 -37
- data/lib/active_record/persistence.rb +557 -125
- data/lib/active_record/query_cache.rb +19 -23
- data/lib/active_record/querying.rb +43 -29
- data/lib/active_record/railtie.rb +143 -44
- data/lib/active_record/railties/collection_cache_association_loading.rb +34 -0
- data/lib/active_record/railties/console_sandbox.rb +2 -0
- data/lib/active_record/railties/controller_runtime.rb +34 -33
- data/lib/active_record/railties/databases.rake +328 -185
- data/lib/active_record/readonly_attributes.rb +5 -4
- data/lib/active_record/reflection.rb +428 -279
- data/lib/active_record/relation.rb +518 -341
- data/lib/active_record/relation/batches.rb +207 -55
- data/lib/active_record/relation/batches/batch_enumerator.rb +69 -0
- data/lib/active_record/relation/calculations.rb +267 -253
- data/lib/active_record/relation/delegation.rb +70 -80
- data/lib/active_record/relation/finder_methods.rb +277 -241
- data/lib/active_record/relation/from_clause.rb +26 -0
- data/lib/active_record/relation/merger.rb +78 -87
- data/lib/active_record/relation/predicate_builder.rb +114 -119
- data/lib/active_record/relation/predicate_builder/array_handler.rb +27 -26
- data/lib/active_record/relation/predicate_builder/association_query_value.rb +43 -0
- data/lib/active_record/relation/predicate_builder/base_handler.rb +18 -0
- data/lib/active_record/relation/predicate_builder/basic_object_handler.rb +19 -0
- data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +53 -0
- data/lib/active_record/relation/predicate_builder/range_handler.rb +22 -0
- data/lib/active_record/relation/predicate_builder/relation_handler.rb +7 -1
- data/lib/active_record/relation/query_attribute.rb +50 -0
- data/lib/active_record/relation/query_methods.rb +575 -394
- data/lib/active_record/relation/record_fetch_warning.rb +51 -0
- data/lib/active_record/relation/spawn_methods.rb +11 -13
- data/lib/active_record/relation/where_clause.rb +190 -0
- data/lib/active_record/relation/where_clause_factory.rb +33 -0
- data/lib/active_record/result.rb +79 -42
- data/lib/active_record/runtime_registry.rb +6 -4
- data/lib/active_record/sanitization.rb +144 -121
- data/lib/active_record/schema.rb +21 -24
- data/lib/active_record/schema_dumper.rb +112 -93
- data/lib/active_record/schema_migration.rb +24 -17
- data/lib/active_record/scoping.rb +45 -26
- data/lib/active_record/scoping/default.rb +101 -85
- data/lib/active_record/scoping/named.rb +86 -33
- data/lib/active_record/secure_token.rb +40 -0
- data/lib/active_record/serialization.rb +5 -5
- data/lib/active_record/statement_cache.rb +73 -36
- data/lib/active_record/store.rb +127 -42
- data/lib/active_record/suppressor.rb +61 -0
- data/lib/active_record/table_metadata.rb +75 -0
- data/lib/active_record/tasks/database_tasks.rb +307 -100
- data/lib/active_record/tasks/mysql_database_tasks.rb +55 -99
- data/lib/active_record/tasks/postgresql_database_tasks.rb +81 -41
- data/lib/active_record/tasks/sqlite_database_tasks.rb +38 -16
- data/lib/active_record/test_databases.rb +23 -0
- data/lib/active_record/test_fixtures.rb +224 -0
- data/lib/active_record/timestamp.rb +86 -40
- data/lib/active_record/touch_later.rb +66 -0
- data/lib/active_record/transactions.rb +216 -150
- data/lib/active_record/translation.rb +3 -1
- data/lib/active_record/type.rb +78 -23
- data/lib/active_record/type/adapter_specific_registry.rb +129 -0
- data/lib/active_record/type/date.rb +4 -45
- data/lib/active_record/type/date_time.rb +4 -49
- data/lib/active_record/type/decimal_without_scale.rb +6 -2
- data/lib/active_record/type/hash_lookup_type_map.rb +5 -3
- data/lib/active_record/type/internal/timezone.rb +17 -0
- data/lib/active_record/type/json.rb +30 -0
- data/lib/active_record/type/serialized.rb +24 -15
- data/lib/active_record/type/text.rb +2 -2
- data/lib/active_record/type/time.rb +11 -16
- data/lib/active_record/type/type_map.rb +15 -17
- data/lib/active_record/type/unsigned_integer.rb +9 -7
- data/lib/active_record/type_caster.rb +9 -0
- data/lib/active_record/type_caster/connection.rb +34 -0
- data/lib/active_record/type_caster/map.rb +20 -0
- data/lib/active_record/validations.rb +39 -35
- data/lib/active_record/validations/absence.rb +25 -0
- data/lib/active_record/validations/associated.rb +13 -4
- data/lib/active_record/validations/length.rb +26 -0
- data/lib/active_record/validations/presence.rb +14 -13
- data/lib/active_record/validations/uniqueness.rb +42 -55
- data/lib/active_record/version.rb +3 -1
- data/lib/arel.rb +51 -0
- data/lib/arel/alias_predication.rb +9 -0
- data/lib/arel/attributes.rb +22 -0
- data/lib/arel/attributes/attribute.rb +37 -0
- data/lib/arel/collectors/bind.rb +24 -0
- data/lib/arel/collectors/composite.rb +31 -0
- data/lib/arel/collectors/plain_string.rb +20 -0
- data/lib/arel/collectors/sql_string.rb +20 -0
- data/lib/arel/collectors/substitute_binds.rb +28 -0
- data/lib/arel/crud.rb +42 -0
- data/lib/arel/delete_manager.rb +18 -0
- data/lib/arel/errors.rb +9 -0
- data/lib/arel/expressions.rb +29 -0
- data/lib/arel/factory_methods.rb +49 -0
- data/lib/arel/insert_manager.rb +49 -0
- data/lib/arel/math.rb +45 -0
- data/lib/arel/nodes.rb +68 -0
- data/lib/arel/nodes/and.rb +32 -0
- data/lib/arel/nodes/ascending.rb +23 -0
- data/lib/arel/nodes/binary.rb +52 -0
- data/lib/arel/nodes/bind_param.rb +36 -0
- data/lib/arel/nodes/case.rb +55 -0
- data/lib/arel/nodes/casted.rb +50 -0
- data/lib/arel/nodes/comment.rb +29 -0
- data/lib/arel/nodes/count.rb +12 -0
- data/lib/arel/nodes/delete_statement.rb +45 -0
- data/lib/arel/nodes/descending.rb +23 -0
- data/lib/arel/nodes/equality.rb +18 -0
- data/lib/arel/nodes/extract.rb +24 -0
- data/lib/arel/nodes/false.rb +16 -0
- data/lib/arel/nodes/full_outer_join.rb +8 -0
- data/lib/arel/nodes/function.rb +44 -0
- data/lib/arel/nodes/grouping.rb +8 -0
- data/lib/arel/nodes/in.rb +8 -0
- data/lib/arel/nodes/infix_operation.rb +80 -0
- data/lib/arel/nodes/inner_join.rb +8 -0
- data/lib/arel/nodes/insert_statement.rb +37 -0
- data/lib/arel/nodes/join_source.rb +20 -0
- data/lib/arel/nodes/matches.rb +18 -0
- data/lib/arel/nodes/named_function.rb +23 -0
- data/lib/arel/nodes/node.rb +50 -0
- data/lib/arel/nodes/node_expression.rb +13 -0
- data/lib/arel/nodes/outer_join.rb +8 -0
- data/lib/arel/nodes/over.rb +15 -0
- data/lib/arel/nodes/regexp.rb +16 -0
- data/lib/arel/nodes/right_outer_join.rb +8 -0
- data/lib/arel/nodes/select_core.rb +67 -0
- data/lib/arel/nodes/select_statement.rb +41 -0
- data/lib/arel/nodes/sql_literal.rb +16 -0
- data/lib/arel/nodes/string_join.rb +11 -0
- data/lib/arel/nodes/table_alias.rb +27 -0
- data/lib/arel/nodes/terminal.rb +16 -0
- data/lib/arel/nodes/true.rb +16 -0
- data/lib/arel/nodes/unary.rb +45 -0
- data/lib/arel/nodes/unary_operation.rb +20 -0
- data/lib/arel/nodes/unqualified_column.rb +22 -0
- data/lib/arel/nodes/update_statement.rb +41 -0
- data/lib/arel/nodes/values_list.rb +9 -0
- data/lib/arel/nodes/window.rb +126 -0
- data/lib/arel/nodes/with.rb +11 -0
- data/lib/arel/order_predications.rb +13 -0
- data/lib/arel/predications.rb +257 -0
- data/lib/arel/select_manager.rb +271 -0
- data/lib/arel/table.rb +110 -0
- data/lib/arel/tree_manager.rb +72 -0
- data/lib/arel/update_manager.rb +34 -0
- data/lib/arel/visitors.rb +20 -0
- data/lib/arel/visitors/depth_first.rb +204 -0
- data/lib/arel/visitors/dot.rb +297 -0
- data/lib/arel/visitors/ibm_db.rb +34 -0
- data/lib/arel/visitors/informix.rb +62 -0
- data/lib/arel/visitors/mssql.rb +157 -0
- data/lib/arel/visitors/mysql.rb +83 -0
- data/lib/arel/visitors/oracle.rb +159 -0
- data/lib/arel/visitors/oracle12.rb +66 -0
- data/lib/arel/visitors/postgresql.rb +110 -0
- data/lib/arel/visitors/sqlite.rb +39 -0
- data/lib/arel/visitors/to_sql.rb +889 -0
- data/lib/arel/visitors/visitor.rb +46 -0
- data/lib/arel/visitors/where_sql.rb +23 -0
- data/lib/arel/window_predications.rb +9 -0
- data/lib/rails/generators/active_record.rb +7 -5
- data/lib/rails/generators/active_record/application_record/application_record_generator.rb +27 -0
- data/lib/rails/generators/active_record/application_record/templates/application_record.rb.tt +5 -0
- data/lib/rails/generators/active_record/migration.rb +31 -1
- data/lib/rails/generators/active_record/migration/migration_generator.rb +42 -37
- data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +24 -0
- data/lib/rails/generators/active_record/migration/templates/{migration.rb → migration.rb.tt} +11 -2
- data/lib/rails/generators/active_record/model/model_generator.rb +19 -22
- data/lib/rails/generators/active_record/model/templates/model.rb.tt +22 -0
- data/lib/rails/generators/active_record/model/templates/{module.rb → module.rb.tt} +0 -0
- metadata +164 -59
- data/lib/active_record/associations/preloader/belongs_to.rb +0 -17
- data/lib/active_record/associations/preloader/collection_association.rb +0 -24
- data/lib/active_record/associations/preloader/has_many.rb +0 -17
- data/lib/active_record/associations/preloader/has_many_through.rb +0 -19
- data/lib/active_record/associations/preloader/has_one.rb +0 -23
- data/lib/active_record/associations/preloader/has_one_through.rb +0 -9
- data/lib/active_record/associations/preloader/singular_association.rb +0 -21
- data/lib/active_record/attribute.rb +0 -163
- data/lib/active_record/attribute_set.rb +0 -81
- data/lib/active_record/attribute_set/builder.rb +0 -106
- data/lib/active_record/connection_adapters/mysql_adapter.rb +0 -498
- data/lib/active_record/connection_adapters/postgresql/array_parser.rb +0 -93
- data/lib/active_record/connection_adapters/postgresql/oid/float.rb +0 -21
- data/lib/active_record/connection_adapters/postgresql/oid/infinity.rb +0 -13
- data/lib/active_record/connection_adapters/postgresql/oid/json.rb +0 -35
- data/lib/active_record/connection_adapters/postgresql/oid/time.rb +0 -11
- data/lib/active_record/railties/jdbcmysql_error.rb +0 -16
- data/lib/active_record/serializers/xml_serializer.rb +0 -193
- data/lib/active_record/type/big_integer.rb +0 -13
- data/lib/active_record/type/binary.rb +0 -50
- data/lib/active_record/type/boolean.rb +0 -31
- data/lib/active_record/type/decimal.rb +0 -64
- data/lib/active_record/type/decorator.rb +0 -14
- data/lib/active_record/type/float.rb +0 -19
- data/lib/active_record/type/integer.rb +0 -59
- data/lib/active_record/type/mutable.rb +0 -16
- data/lib/active_record/type/numeric.rb +0 -36
- data/lib/active_record/type/string.rb +0 -40
- data/lib/active_record/type/time_value.rb +0 -38
- data/lib/active_record/type/value.rb +0 -110
- data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb +0 -19
- data/lib/rails/generators/active_record/model/templates/model.rb +0 -10
@@ -1,8 +1,14 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_record/relation/batches/batch_enumerator"
|
4
|
+
|
1
5
|
module ActiveRecord
|
2
6
|
module Batches
|
7
|
+
ORDER_IGNORE_MESSAGE = "Scoped order is ignored, it's forced to be batch order."
|
8
|
+
|
3
9
|
# Looping through a collection of records from the database
|
4
|
-
# (using the
|
5
|
-
# since it will try to instantiate all the objects at once.
|
10
|
+
# (using the Scoping::Named::ClassMethods.all method, for example)
|
11
|
+
# is very inefficient since it will try to instantiate all the objects at once.
|
6
12
|
#
|
7
13
|
# In that case, batch processing methods allow you to work
|
8
14
|
# with the records in batches, thereby greatly reducing memory consumption.
|
@@ -26,38 +32,52 @@ module ActiveRecord
|
|
26
32
|
# end
|
27
33
|
#
|
28
34
|
# ==== Options
|
29
|
-
# * <tt>:batch_size</tt> - Specifies the size of the batch.
|
30
|
-
# * <tt>:start</tt> - Specifies the
|
31
|
-
#
|
32
|
-
# the
|
33
|
-
#
|
34
|
-
#
|
35
|
-
#
|
36
|
-
#
|
37
|
-
#
|
35
|
+
# * <tt>:batch_size</tt> - Specifies the size of the batch. Defaults to 1000.
|
36
|
+
# * <tt>:start</tt> - Specifies the primary key value to start from, inclusive of the value.
|
37
|
+
# * <tt>:finish</tt> - Specifies the primary key value to end at, inclusive of the value.
|
38
|
+
# * <tt>:error_on_ignore</tt> - Overrides the application config to specify if an error should be raised when
|
39
|
+
# an order is present in the relation.
|
40
|
+
#
|
41
|
+
# Limits are honored, and if present there is no requirement for the batch
|
42
|
+
# size: it can be less than, equal to, or greater than the limit.
|
43
|
+
#
|
44
|
+
# The options +start+ and +finish+ are especially useful if you want
|
45
|
+
# multiple workers dealing with the same processing queue. You can make
|
46
|
+
# worker 1 handle all the records between id 1 and 9999 and worker 2
|
47
|
+
# handle from 10000 and beyond by setting the +:start+ and +:finish+
|
48
|
+
# option on each worker.
|
49
|
+
#
|
50
|
+
# # In worker 1, let's process until 9999 records.
|
51
|
+
# Person.find_each(finish: 9_999) do |person|
|
52
|
+
# person.party_all_night!
|
53
|
+
# end
|
54
|
+
#
|
55
|
+
# # In worker 2, let's process from record 10_000 and onwards.
|
56
|
+
# Person.find_each(start: 10_000) do |person|
|
38
57
|
# person.party_all_night!
|
39
58
|
# end
|
40
59
|
#
|
41
60
|
# NOTE: It's not possible to set the order. That is automatically set to
|
42
61
|
# ascending on the primary key ("id ASC") to make the batch ordering
|
43
|
-
# work. This also means that this method only works
|
44
|
-
#
|
62
|
+
# work. This also means that this method only works when the primary key is
|
63
|
+
# orderable (e.g. an integer or string).
|
45
64
|
#
|
46
|
-
# NOTE:
|
47
|
-
# the
|
48
|
-
def find_each(
|
65
|
+
# NOTE: By its nature, batch processing is subject to race conditions if
|
66
|
+
# other processes are modifying the database.
|
67
|
+
def find_each(start: nil, finish: nil, batch_size: 1000, error_on_ignore: nil)
|
49
68
|
if block_given?
|
50
|
-
find_in_batches(
|
69
|
+
find_in_batches(start: start, finish: finish, batch_size: batch_size, error_on_ignore: error_on_ignore) do |records|
|
51
70
|
records.each { |record| yield record }
|
52
71
|
end
|
53
72
|
else
|
54
|
-
enum_for
|
55
|
-
|
73
|
+
enum_for(:find_each, start: start, finish: finish, batch_size: batch_size, error_on_ignore: error_on_ignore) do
|
74
|
+
relation = self
|
75
|
+
apply_limits(relation, start, finish).size
|
56
76
|
end
|
57
77
|
end
|
58
78
|
end
|
59
79
|
|
60
|
-
# Yields each batch of records that was found by the find
|
80
|
+
# Yields each batch of records that was found by the find options as
|
61
81
|
# an array.
|
62
82
|
#
|
63
83
|
# Person.where("age > 21").find_in_batches do |group|
|
@@ -76,63 +96,195 @@ module ActiveRecord
|
|
76
96
|
# To be yielded each record one by one, use #find_each instead.
|
77
97
|
#
|
78
98
|
# ==== Options
|
79
|
-
# * <tt>:batch_size</tt> - Specifies the size of the batch.
|
80
|
-
# * <tt>:start</tt> - Specifies the
|
81
|
-
#
|
82
|
-
# the
|
83
|
-
#
|
84
|
-
#
|
85
|
-
#
|
86
|
-
#
|
87
|
-
#
|
99
|
+
# * <tt>:batch_size</tt> - Specifies the size of the batch. Defaults to 1000.
|
100
|
+
# * <tt>:start</tt> - Specifies the primary key value to start from, inclusive of the value.
|
101
|
+
# * <tt>:finish</tt> - Specifies the primary key value to end at, inclusive of the value.
|
102
|
+
# * <tt>:error_on_ignore</tt> - Overrides the application config to specify if an error should be raised when
|
103
|
+
# an order is present in the relation.
|
104
|
+
#
|
105
|
+
# Limits are honored, and if present there is no requirement for the batch
|
106
|
+
# size: it can be less than, equal to, or greater than the limit.
|
107
|
+
#
|
108
|
+
# The options +start+ and +finish+ are especially useful if you want
|
109
|
+
# multiple workers dealing with the same processing queue. You can make
|
110
|
+
# worker 1 handle all the records between id 1 and 9999 and worker 2
|
111
|
+
# handle from 10000 and beyond by setting the +:start+ and +:finish+
|
112
|
+
# option on each worker.
|
113
|
+
#
|
114
|
+
# # Let's process from record 10_000 on.
|
115
|
+
# Person.find_in_batches(start: 10_000) do |group|
|
88
116
|
# group.each { |person| person.party_all_night! }
|
89
117
|
# end
|
90
118
|
#
|
91
119
|
# NOTE: It's not possible to set the order. That is automatically set to
|
92
120
|
# ascending on the primary key ("id ASC") to make the batch ordering
|
93
|
-
# work. This also means that this method only works
|
94
|
-
#
|
121
|
+
# work. This also means that this method only works when the primary key is
|
122
|
+
# orderable (e.g. an integer or string).
|
95
123
|
#
|
96
|
-
# NOTE:
|
97
|
-
# the
|
98
|
-
def find_in_batches(
|
99
|
-
options.assert_valid_keys(:start, :batch_size)
|
100
|
-
|
124
|
+
# NOTE: By its nature, batch processing is subject to race conditions if
|
125
|
+
# other processes are modifying the database.
|
126
|
+
def find_in_batches(start: nil, finish: nil, batch_size: 1000, error_on_ignore: nil)
|
101
127
|
relation = self
|
102
|
-
start = options[:start]
|
103
|
-
batch_size = options[:batch_size] || 1000
|
104
|
-
|
105
128
|
unless block_given?
|
106
|
-
return to_enum(:find_in_batches,
|
107
|
-
total = start
|
129
|
+
return to_enum(:find_in_batches, start: start, finish: finish, batch_size: batch_size, error_on_ignore: error_on_ignore) do
|
130
|
+
total = apply_limits(relation, start, finish).size
|
108
131
|
(total - 1).div(batch_size) + 1
|
109
132
|
end
|
110
133
|
end
|
111
134
|
|
112
|
-
|
113
|
-
|
135
|
+
in_batches(of: batch_size, start: start, finish: finish, load: true, error_on_ignore: error_on_ignore) do |batch|
|
136
|
+
yield batch.to_a
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
# Yields ActiveRecord::Relation objects to work with a batch of records.
|
141
|
+
#
|
142
|
+
# Person.where("age > 21").in_batches do |relation|
|
143
|
+
# relation.delete_all
|
144
|
+
# sleep(10) # Throttle the delete queries
|
145
|
+
# end
|
146
|
+
#
|
147
|
+
# If you do not provide a block to #in_batches, it will return a
|
148
|
+
# BatchEnumerator which is enumerable.
|
149
|
+
#
|
150
|
+
# Person.in_batches.each_with_index do |relation, batch_index|
|
151
|
+
# puts "Processing relation ##{batch_index}"
|
152
|
+
# relation.delete_all
|
153
|
+
# end
|
154
|
+
#
|
155
|
+
# Examples of calling methods on the returned BatchEnumerator object:
|
156
|
+
#
|
157
|
+
# Person.in_batches.delete_all
|
158
|
+
# Person.in_batches.update_all(awesome: true)
|
159
|
+
# Person.in_batches.each_record(&:party_all_night!)
|
160
|
+
#
|
161
|
+
# ==== Options
|
162
|
+
# * <tt>:of</tt> - Specifies the size of the batch. Defaults to 1000.
|
163
|
+
# * <tt>:load</tt> - Specifies if the relation should be loaded. Defaults to false.
|
164
|
+
# * <tt>:start</tt> - Specifies the primary key value to start from, inclusive of the value.
|
165
|
+
# * <tt>:finish</tt> - Specifies the primary key value to end at, inclusive of the value.
|
166
|
+
# * <tt>:error_on_ignore</tt> - Overrides the application config to specify if an error should be raised when
|
167
|
+
# an order is present in the relation.
|
168
|
+
#
|
169
|
+
# Limits are honored, and if present there is no requirement for the batch
|
170
|
+
# size, it can be less than, equal, or greater than the limit.
|
171
|
+
#
|
172
|
+
# The options +start+ and +finish+ are especially useful if you want
|
173
|
+
# multiple workers dealing with the same processing queue. You can make
|
174
|
+
# worker 1 handle all the records between id 1 and 9999 and worker 2
|
175
|
+
# handle from 10000 and beyond by setting the +:start+ and +:finish+
|
176
|
+
# option on each worker.
|
177
|
+
#
|
178
|
+
# # Let's process from record 10_000 on.
|
179
|
+
# Person.in_batches(start: 10_000).update_all(awesome: true)
|
180
|
+
#
|
181
|
+
# An example of calling where query method on the relation:
|
182
|
+
#
|
183
|
+
# Person.in_batches.each do |relation|
|
184
|
+
# relation.update_all('age = age + 1')
|
185
|
+
# relation.where('age > 21').update_all(should_party: true)
|
186
|
+
# relation.where('age <= 21').delete_all
|
187
|
+
# end
|
188
|
+
#
|
189
|
+
# NOTE: If you are going to iterate through each record, you should call
|
190
|
+
# #each_record on the yielded BatchEnumerator:
|
191
|
+
#
|
192
|
+
# Person.in_batches.each_record(&:party_all_night!)
|
193
|
+
#
|
194
|
+
# NOTE: It's not possible to set the order. That is automatically set to
|
195
|
+
# ascending on the primary key ("id ASC") to make the batch ordering
|
196
|
+
# consistent. Therefore the primary key must be orderable, e.g. an integer
|
197
|
+
# or a string.
|
198
|
+
#
|
199
|
+
# NOTE: By its nature, batch processing is subject to race conditions if
|
200
|
+
# other processes are modifying the database.
|
201
|
+
def in_batches(of: 1000, start: nil, finish: nil, load: false, error_on_ignore: nil)
|
202
|
+
relation = self
|
203
|
+
unless block_given?
|
204
|
+
return BatchEnumerator.new(of: of, start: start, finish: finish, relation: self)
|
205
|
+
end
|
206
|
+
|
207
|
+
if arel.orders.present?
|
208
|
+
act_on_ignored_order(error_on_ignore)
|
209
|
+
end
|
210
|
+
|
211
|
+
batch_limit = of
|
212
|
+
if limit_value
|
213
|
+
remaining = limit_value
|
214
|
+
batch_limit = remaining if remaining < batch_limit
|
114
215
|
end
|
115
216
|
|
116
|
-
relation = relation.reorder(batch_order).limit(
|
117
|
-
|
217
|
+
relation = relation.reorder(batch_order).limit(batch_limit)
|
218
|
+
relation = apply_limits(relation, start, finish)
|
219
|
+
relation.skip_query_cache! # Retaining the results in the query cache would undermine the point of batching
|
220
|
+
batch_relation = relation
|
221
|
+
|
222
|
+
loop do
|
223
|
+
if load
|
224
|
+
records = batch_relation.records
|
225
|
+
ids = records.map(&:id)
|
226
|
+
yielded_relation = where(primary_key => ids)
|
227
|
+
yielded_relation.load_records(records)
|
228
|
+
else
|
229
|
+
ids = batch_relation.pluck(primary_key)
|
230
|
+
yielded_relation = where(primary_key => ids)
|
231
|
+
end
|
232
|
+
|
233
|
+
break if ids.empty?
|
118
234
|
|
119
|
-
|
120
|
-
|
121
|
-
primary_key_offset = records.last.id
|
122
|
-
raise "Primary key not included in the custom select clause" unless primary_key_offset
|
235
|
+
primary_key_offset = ids.last
|
236
|
+
raise ArgumentError.new("Primary key not included in the custom select clause") unless primary_key_offset
|
123
237
|
|
124
|
-
yield
|
238
|
+
yield yielded_relation
|
125
239
|
|
126
|
-
break if
|
240
|
+
break if ids.length < batch_limit
|
127
241
|
|
128
|
-
|
242
|
+
if limit_value
|
243
|
+
remaining -= ids.length
|
244
|
+
|
245
|
+
if remaining == 0
|
246
|
+
# Saves a useless iteration when the limit is a multiple of the
|
247
|
+
# batch size.
|
248
|
+
break
|
249
|
+
elsif remaining < batch_limit
|
250
|
+
relation = relation.limit(remaining)
|
251
|
+
end
|
252
|
+
end
|
253
|
+
|
254
|
+
batch_relation = relation.where(
|
255
|
+
bind_attribute(primary_key, primary_key_offset) { |attr, bind| attr.gt(bind) }
|
256
|
+
)
|
129
257
|
end
|
130
258
|
end
|
131
259
|
|
132
260
|
private
|
133
261
|
|
134
|
-
|
135
|
-
|
136
|
-
|
262
|
+
def apply_limits(relation, start, finish)
|
263
|
+
relation = apply_start_limit(relation, start) if start
|
264
|
+
relation = apply_finish_limit(relation, finish) if finish
|
265
|
+
relation
|
266
|
+
end
|
267
|
+
|
268
|
+
def apply_start_limit(relation, start)
|
269
|
+
relation.where(bind_attribute(primary_key, start) { |attr, bind| attr.gteq(bind) })
|
270
|
+
end
|
271
|
+
|
272
|
+
def apply_finish_limit(relation, finish)
|
273
|
+
relation.where(bind_attribute(primary_key, finish) { |attr, bind| attr.lteq(bind) })
|
274
|
+
end
|
275
|
+
|
276
|
+
def batch_order
|
277
|
+
arel_attribute(primary_key).asc
|
278
|
+
end
|
279
|
+
|
280
|
+
def act_on_ignored_order(error_on_ignore)
|
281
|
+
raise_error = (error_on_ignore.nil? ? klass.error_on_ignored_order : error_on_ignore)
|
282
|
+
|
283
|
+
if raise_error
|
284
|
+
raise ArgumentError.new(ORDER_IGNORE_MESSAGE)
|
285
|
+
elsif logger
|
286
|
+
logger.warn(ORDER_IGNORE_MESSAGE)
|
287
|
+
end
|
288
|
+
end
|
137
289
|
end
|
138
290
|
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveRecord
|
4
|
+
module Batches
|
5
|
+
class BatchEnumerator
|
6
|
+
include Enumerable
|
7
|
+
|
8
|
+
def initialize(of: 1000, start: nil, finish: nil, relation:) #:nodoc:
|
9
|
+
@of = of
|
10
|
+
@relation = relation
|
11
|
+
@start = start
|
12
|
+
@finish = finish
|
13
|
+
end
|
14
|
+
|
15
|
+
# Looping through a collection of records from the database (using the
|
16
|
+
# +all+ method, for example) is very inefficient since it will try to
|
17
|
+
# instantiate all the objects at once.
|
18
|
+
#
|
19
|
+
# In that case, batch processing methods allow you to work with the
|
20
|
+
# records in batches, thereby greatly reducing memory consumption.
|
21
|
+
#
|
22
|
+
# Person.in_batches.each_record do |person|
|
23
|
+
# person.do_awesome_stuff
|
24
|
+
# end
|
25
|
+
#
|
26
|
+
# Person.where("age > 21").in_batches(of: 10).each_record do |person|
|
27
|
+
# person.party_all_night!
|
28
|
+
# end
|
29
|
+
#
|
30
|
+
# If you do not provide a block to #each_record, it will return an Enumerator
|
31
|
+
# for chaining with other methods:
|
32
|
+
#
|
33
|
+
# Person.in_batches.each_record.with_index do |person, index|
|
34
|
+
# person.award_trophy(index + 1)
|
35
|
+
# end
|
36
|
+
def each_record
|
37
|
+
return to_enum(:each_record) unless block_given?
|
38
|
+
|
39
|
+
@relation.to_enum(:in_batches, of: @of, start: @start, finish: @finish, load: true).each do |relation|
|
40
|
+
relation.records.each { |record| yield record }
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
# Delegates #delete_all, #update_all, #destroy_all methods to each batch.
|
45
|
+
#
|
46
|
+
# People.in_batches.delete_all
|
47
|
+
# People.where('age < 10').in_batches.destroy_all
|
48
|
+
# People.in_batches.update_all('age = age + 1')
|
49
|
+
[:delete_all, :update_all, :destroy_all].each do |method|
|
50
|
+
define_method(method) do |*args, &block|
|
51
|
+
@relation.to_enum(:in_batches, of: @of, start: @start, finish: @finish, load: false).each do |relation|
|
52
|
+
relation.send(method, *args, &block)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
# Yields an ActiveRecord::Relation object for each batch of records.
|
58
|
+
#
|
59
|
+
# Person.in_batches.each do |relation|
|
60
|
+
# relation.update_all(awesome: true)
|
61
|
+
# end
|
62
|
+
def each
|
63
|
+
enum = @relation.to_enum(:in_batches, of: @of, start: @start, finish: @finish, load: false)
|
64
|
+
return enum.each { |relation| yield relation } if block_given?
|
65
|
+
enum
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module ActiveRecord
|
2
4
|
module Calculations
|
3
5
|
# Count the records.
|
@@ -14,127 +16,134 @@ module ActiveRecord
|
|
14
16
|
# Person.distinct.count(:age)
|
15
17
|
# # => counts the number of different age values
|
16
18
|
#
|
17
|
-
# If
|
19
|
+
# If #count is used with {Relation#group}[rdoc-ref:QueryMethods#group],
|
20
|
+
# it returns a Hash whose keys represent the aggregated column,
|
18
21
|
# and the values are the respective amounts:
|
19
22
|
#
|
20
23
|
# Person.group(:city).count
|
21
24
|
# # => { 'Rome' => 5, 'Paris' => 3 }
|
22
25
|
#
|
23
|
-
# If
|
26
|
+
# If #count is used with {Relation#group}[rdoc-ref:QueryMethods#group] for multiple columns, it returns a Hash whose
|
24
27
|
# keys are an array containing the individual values of each column and the value
|
25
|
-
# of each key would be the
|
28
|
+
# of each key would be the #count.
|
26
29
|
#
|
27
30
|
# Article.group(:status, :category).count
|
28
31
|
# # => {["draft", "business"]=>10, ["draft", "technology"]=>4,
|
29
32
|
# ["published", "business"]=>0, ["published", "technology"]=>2}
|
30
33
|
#
|
31
|
-
# If
|
34
|
+
# If #count is used with {Relation#select}[rdoc-ref:QueryMethods#select], it will count the selected columns:
|
32
35
|
#
|
33
36
|
# Person.select(:age).count
|
34
37
|
# # => counts the number of different age values
|
35
38
|
#
|
36
|
-
# Note: not all valid
|
39
|
+
# Note: not all valid {Relation#select}[rdoc-ref:QueryMethods#select] expressions are valid #count expressions. The specifics differ
|
37
40
|
# between databases. In invalid cases, an error from the database is thrown.
|
38
|
-
def count(column_name = nil
|
39
|
-
if
|
40
|
-
|
41
|
-
|
42
|
-
|
41
|
+
def count(column_name = nil)
|
42
|
+
if block_given?
|
43
|
+
unless column_name.nil?
|
44
|
+
raise ArgumentError, "Column name argument is not supported when a block is passed."
|
45
|
+
end
|
43
46
|
|
47
|
+
super()
|
48
|
+
else
|
49
|
+
calculate(:count, column_name)
|
44
50
|
end
|
45
|
-
|
46
|
-
# TODO: Remove options argument as soon we remove support to
|
47
|
-
# activerecord-deprecated_finders.
|
48
|
-
calculate(:count, column_name, options)
|
49
51
|
end
|
50
52
|
|
51
53
|
# Calculates the average value on a given column. Returns +nil+ if there's
|
52
|
-
# no row. See
|
54
|
+
# no row. See #calculate for examples with options.
|
53
55
|
#
|
54
56
|
# Person.average(:age) # => 35.8
|
55
|
-
def average(column_name
|
56
|
-
|
57
|
-
# activerecord-deprecated_finders.
|
58
|
-
calculate(:average, column_name, options)
|
57
|
+
def average(column_name)
|
58
|
+
calculate(:average, column_name)
|
59
59
|
end
|
60
60
|
|
61
61
|
# Calculates the minimum value on a given column. The value is returned
|
62
62
|
# with the same data type of the column, or +nil+ if there's no row. See
|
63
|
-
#
|
63
|
+
# #calculate for examples with options.
|
64
64
|
#
|
65
65
|
# Person.minimum(:age) # => 7
|
66
|
-
def minimum(column_name
|
67
|
-
|
68
|
-
# activerecord-deprecated_finders.
|
69
|
-
calculate(:minimum, column_name, options)
|
66
|
+
def minimum(column_name)
|
67
|
+
calculate(:minimum, column_name)
|
70
68
|
end
|
71
69
|
|
72
70
|
# Calculates the maximum value on a given column. The value is returned
|
73
71
|
# with the same data type of the column, or +nil+ if there's no row. See
|
74
|
-
#
|
72
|
+
# #calculate for examples with options.
|
75
73
|
#
|
76
74
|
# Person.maximum(:age) # => 93
|
77
|
-
def maximum(column_name
|
78
|
-
|
79
|
-
# activerecord-deprecated_finders.
|
80
|
-
calculate(:maximum, column_name, options)
|
75
|
+
def maximum(column_name)
|
76
|
+
calculate(:maximum, column_name)
|
81
77
|
end
|
82
78
|
|
83
79
|
# Calculates the sum of values on a given column. The value is returned
|
84
|
-
# with the same data type of the column, 0 if there's no row. See
|
85
|
-
#
|
80
|
+
# with the same data type of the column, +0+ if there's no row. See
|
81
|
+
# #calculate for examples with options.
|
86
82
|
#
|
87
83
|
# Person.sum(:age) # => 4562
|
88
|
-
def sum(
|
89
|
-
|
84
|
+
def sum(column_name = nil)
|
85
|
+
if block_given?
|
86
|
+
unless column_name.nil?
|
87
|
+
raise ArgumentError, "Column name argument is not supported when a block is passed."
|
88
|
+
end
|
89
|
+
|
90
|
+
super()
|
91
|
+
else
|
92
|
+
calculate(:sum, column_name)
|
93
|
+
end
|
90
94
|
end
|
91
95
|
|
92
|
-
# This calculates aggregate values in the given column. Methods for count, sum, average,
|
93
|
-
# minimum, and maximum have been added as shortcuts.
|
96
|
+
# This calculates aggregate values in the given column. Methods for #count, #sum, #average,
|
97
|
+
# #minimum, and #maximum have been added as shortcuts.
|
94
98
|
#
|
95
|
-
#
|
99
|
+
# Person.calculate(:count, :all) # The same as Person.count
|
100
|
+
# Person.average(:age) # SELECT AVG(age) FROM people...
|
96
101
|
#
|
97
|
-
#
|
98
|
-
#
|
102
|
+
# # Selects the minimum age for any family without any minors
|
103
|
+
# Person.group(:last_name).having("min(age) > 17").minimum(:age)
|
99
104
|
#
|
100
|
-
# *
|
101
|
-
# takes either a column name, or the name of a belongs_to association.
|
105
|
+
# Person.sum("2 * age")
|
102
106
|
#
|
103
|
-
#
|
104
|
-
# puts values["Drake"]
|
105
|
-
# # => 43
|
107
|
+
# There are two basic forms of output:
|
106
108
|
#
|
107
|
-
#
|
108
|
-
#
|
109
|
-
# puts values[drake]
|
110
|
-
# # => 43
|
109
|
+
# * Single aggregate value: The single value is type cast to Integer for COUNT, Float
|
110
|
+
# for AVG, and the given column's type for everything else.
|
111
111
|
#
|
112
|
-
#
|
113
|
-
#
|
114
|
-
# end
|
112
|
+
# * Grouped values: This returns an ordered hash of the values and groups them. It
|
113
|
+
# takes either a column name, or the name of a belongs_to association.
|
115
114
|
#
|
116
|
-
#
|
117
|
-
#
|
115
|
+
# values = Person.group('last_name').maximum(:age)
|
116
|
+
# puts values["Drake"]
|
117
|
+
# # => 43
|
118
118
|
#
|
119
|
-
#
|
120
|
-
#
|
119
|
+
# drake = Family.find_by(last_name: 'Drake')
|
120
|
+
# values = Person.group(:family).maximum(:age) # Person belongs_to :family
|
121
|
+
# puts values[drake]
|
122
|
+
# # => 43
|
121
123
|
#
|
122
|
-
#
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
if column_name.is_a?(Symbol) && attribute_alias?(column_name)
|
127
|
-
column_name = attribute_alias(column_name)
|
128
|
-
end
|
129
|
-
|
124
|
+
# values.each do |family, max_age|
|
125
|
+
# ...
|
126
|
+
# end
|
127
|
+
def calculate(operation, column_name)
|
130
128
|
if has_include?(column_name)
|
131
|
-
|
129
|
+
relation = apply_join_dependency
|
130
|
+
|
131
|
+
if operation.to_s.downcase == "count"
|
132
|
+
unless distinct_value || distinct_select?(column_name || select_for_count)
|
133
|
+
relation.distinct!
|
134
|
+
relation.select_values = [ klass.primary_key || table[Arel.star] ]
|
135
|
+
end
|
136
|
+
# PostgreSQL: ORDER BY expressions must appear in SELECT list when using DISTINCT
|
137
|
+
relation.order_values = []
|
138
|
+
end
|
139
|
+
|
140
|
+
relation.calculate(operation, column_name)
|
132
141
|
else
|
133
|
-
perform_calculation(operation, column_name
|
142
|
+
perform_calculation(operation, column_name)
|
134
143
|
end
|
135
144
|
end
|
136
145
|
|
137
|
-
# Use
|
146
|
+
# Use #pluck as a shortcut to select one or more attributes without
|
138
147
|
# loading a bunch of records just to grab the attributes you want.
|
139
148
|
#
|
140
149
|
# Person.pluck(:name)
|
@@ -143,19 +152,19 @@ module ActiveRecord
|
|
143
152
|
#
|
144
153
|
# Person.all.map(&:name)
|
145
154
|
#
|
146
|
-
# Pluck returns an
|
155
|
+
# Pluck returns an Array of attribute values type-casted to match
|
147
156
|
# the plucked column names, if they can be deduced. Plucking an SQL fragment
|
148
157
|
# returns String values by default.
|
149
158
|
#
|
150
|
-
# Person.pluck(:
|
151
|
-
# # SELECT people.
|
152
|
-
# # => [
|
159
|
+
# Person.pluck(:name)
|
160
|
+
# # SELECT people.name FROM people
|
161
|
+
# # => ['David', 'Jeremy', 'Jose']
|
153
162
|
#
|
154
163
|
# Person.pluck(:id, :name)
|
155
164
|
# # SELECT people.id, people.name FROM people
|
156
165
|
# # => [[1, 'David'], [2, 'Jeremy'], [3, 'Jose']]
|
157
166
|
#
|
158
|
-
# Person.pluck(
|
167
|
+
# Person.distinct.pluck(:role)
|
159
168
|
# # SELECT DISTINCT role FROM people
|
160
169
|
# # => ['admin', 'member', 'guest']
|
161
170
|
#
|
@@ -167,27 +176,43 @@ module ActiveRecord
|
|
167
176
|
# # SELECT DATEDIFF(updated_at, created_at) FROM people
|
168
177
|
# # => ['0', '27761', '173']
|
169
178
|
#
|
179
|
+
# See also #ids.
|
180
|
+
#
|
170
181
|
def pluck(*column_names)
|
171
|
-
column_names.map
|
172
|
-
|
173
|
-
attribute_alias(column_name)
|
174
|
-
else
|
175
|
-
column_name.to_s
|
176
|
-
end
|
182
|
+
if loaded? && (column_names.map(&:to_s) - @klass.attribute_names - @klass.attribute_aliases.keys).empty?
|
183
|
+
return records.pluck(*column_names)
|
177
184
|
end
|
178
185
|
|
179
186
|
if has_include?(column_names.first)
|
180
|
-
|
187
|
+
relation = apply_join_dependency
|
188
|
+
relation.pluck(*column_names)
|
181
189
|
else
|
190
|
+
klass.disallow_raw_sql!(column_names)
|
182
191
|
relation = spawn
|
183
|
-
relation.select_values = column_names
|
184
|
-
|
185
|
-
|
186
|
-
result = klass.connection.select_all(relation.arel, nil, relation.arel.bind_values + bind_values)
|
187
|
-
result.cast_values(klass.column_types)
|
192
|
+
relation.select_values = column_names
|
193
|
+
result = skip_query_cache_if_necessary { klass.connection.select_all(relation.arel, nil) }
|
194
|
+
result.cast_values(klass.attribute_types)
|
188
195
|
end
|
189
196
|
end
|
190
197
|
|
198
|
+
# Pick the value(s) from the named column(s) in the current relation.
|
199
|
+
# This is short-hand for <tt>relation.limit(1).pluck(*column_names).first</tt>, and is primarily useful
|
200
|
+
# when you have a relation that's already narrowed down to a single row.
|
201
|
+
#
|
202
|
+
# Just like #pluck, #pick will only load the actual value, not the entire record object, so it's also
|
203
|
+
# more efficient. The value is, again like with pluck, typecast by the column type.
|
204
|
+
#
|
205
|
+
# Person.where(id: 1).pick(:name)
|
206
|
+
# # SELECT people.name FROM people WHERE id = 1 LIMIT 1
|
207
|
+
# # => 'David'
|
208
|
+
#
|
209
|
+
# Person.where(id: 1).pick(:name, :email_address)
|
210
|
+
# # SELECT people.name, people.email_address FROM people WHERE id = 1 LIMIT 1
|
211
|
+
# # => [ 'David', 'david@loudthinking.com' ]
|
212
|
+
def pick(*column_names)
|
213
|
+
limit(1).pluck(*column_names).first
|
214
|
+
end
|
215
|
+
|
191
216
|
# Pluck all the ID's for the relation using the table's primary key
|
192
217
|
#
|
193
218
|
# Person.ids # SELECT people.id FROM people
|
@@ -197,214 +222,203 @@ module ActiveRecord
|
|
197
222
|
end
|
198
223
|
|
199
224
|
private
|
225
|
+
def has_include?(column_name)
|
226
|
+
eager_loading? || (includes_values.present? && column_name && column_name != :all)
|
227
|
+
end
|
200
228
|
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
229
|
+
def perform_calculation(operation, column_name)
|
230
|
+
operation = operation.to_s.downcase
|
231
|
+
|
232
|
+
# If #count is used with #distinct (i.e. `relation.distinct.count`) it is
|
233
|
+
# considered distinct.
|
234
|
+
distinct = distinct_value
|
235
|
+
|
236
|
+
if operation == "count"
|
237
|
+
column_name ||= select_for_count
|
238
|
+
if column_name == :all
|
239
|
+
if !distinct
|
240
|
+
distinct = distinct_select?(select_for_count) if group_values.empty?
|
241
|
+
elsif group_values.any? || select_values.empty? && order_values.empty?
|
242
|
+
column_name = primary_key
|
243
|
+
end
|
244
|
+
elsif distinct_select?(column_name)
|
245
|
+
distinct = nil
|
246
|
+
end
|
218
247
|
end
|
219
248
|
|
220
|
-
|
221
|
-
|
249
|
+
if group_values.any?
|
250
|
+
execute_grouped_calculation(operation, column_name, distinct)
|
251
|
+
else
|
252
|
+
execute_simple_calculation(operation, column_name, distinct)
|
253
|
+
end
|
222
254
|
end
|
223
255
|
|
224
|
-
|
225
|
-
|
226
|
-
else
|
227
|
-
execute_simple_calculation(operation, column_name, distinct)
|
256
|
+
def distinct_select?(column_name)
|
257
|
+
column_name.is_a?(::String) && /\bDISTINCT[\s(]/i.match?(column_name)
|
228
258
|
end
|
229
|
-
end
|
230
259
|
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
260
|
+
def aggregate_column(column_name)
|
261
|
+
return column_name if Arel::Expressions === column_name
|
262
|
+
|
263
|
+
arel_column(column_name.to_s) do |name|
|
264
|
+
Arel.sql(column_name == :all ? "*" : name)
|
265
|
+
end
|
236
266
|
end
|
237
|
-
end
|
238
267
|
|
239
|
-
|
240
|
-
|
241
|
-
|
268
|
+
def operation_over_aggregate_column(column, operation, distinct)
|
269
|
+
operation == "count" ? column.count(distinct) : column.send(operation)
|
270
|
+
end
|
242
271
|
|
243
|
-
|
244
|
-
|
245
|
-
relation = unscope(:order)
|
272
|
+
def execute_simple_calculation(operation, column_name, distinct) #:nodoc:
|
273
|
+
column_alias = column_name
|
246
274
|
|
247
|
-
|
275
|
+
if operation == "count" && (column_name == :all && distinct || has_limit_or_offset?)
|
276
|
+
# Shortcut when limit is zero.
|
277
|
+
return 0 if limit_value == 0
|
248
278
|
|
249
|
-
|
279
|
+
query_builder = build_count_subquery(spawn, column_name, distinct)
|
280
|
+
else
|
281
|
+
# PostgreSQL doesn't like ORDER BY when there are no GROUP BY
|
282
|
+
relation = unscope(:order).distinct!(false)
|
250
283
|
|
251
|
-
|
252
|
-
# Shortcut when limit is zero.
|
253
|
-
return 0 if relation.limit_value == 0
|
284
|
+
column = aggregate_column(column_name)
|
254
285
|
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
286
|
+
select_value = operation_over_aggregate_column(column, operation, distinct)
|
287
|
+
if operation == "sum" && distinct
|
288
|
+
select_value.distinct = true
|
289
|
+
end
|
259
290
|
|
260
|
-
|
291
|
+
column_alias = select_value.alias
|
292
|
+
column_alias ||= @klass.connection.column_name_for_operation(operation, select_value)
|
293
|
+
relation.select_values = [select_value]
|
261
294
|
|
262
|
-
|
263
|
-
|
264
|
-
relation.select_values = [select_value]
|
295
|
+
query_builder = relation.arel
|
296
|
+
end
|
265
297
|
|
266
|
-
|
267
|
-
|
268
|
-
|
298
|
+
result = skip_query_cache_if_necessary { @klass.connection.select_all(query_builder, nil) }
|
299
|
+
row = result.first
|
300
|
+
value = row && row.values.first
|
301
|
+
type = result.column_types.fetch(column_alias) do
|
302
|
+
type_for(column_name)
|
303
|
+
end
|
269
304
|
|
270
|
-
|
271
|
-
row = result.first
|
272
|
-
value = row && row.values.first
|
273
|
-
column = result.column_types.fetch(column_alias) do
|
274
|
-
type_for(column_name)
|
305
|
+
type_cast_calculated_value(value, type, operation)
|
275
306
|
end
|
276
307
|
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
def execute_grouped_calculation(operation, column_name, distinct) #:nodoc:
|
281
|
-
group_attrs = group_values
|
308
|
+
def execute_grouped_calculation(operation, column_name, distinct) #:nodoc:
|
309
|
+
group_fields = group_values
|
282
310
|
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
group_fields =
|
289
|
-
end
|
290
|
-
group_fields = arel_columns(group_fields)
|
311
|
+
if group_fields.size == 1 && group_fields.first.respond_to?(:to_sym)
|
312
|
+
association = klass._reflect_on_association(group_fields.first)
|
313
|
+
associated = association && association.belongs_to? # only count belongs_to associations
|
314
|
+
group_fields = Array(association.foreign_key) if associated
|
315
|
+
end
|
316
|
+
group_fields = arel_columns(group_fields)
|
291
317
|
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
318
|
+
group_aliases = group_fields.map { |field|
|
319
|
+
field = connection.visitor.compile(field) if Arel.arel_node?(field)
|
320
|
+
column_alias_for(field.to_s.downcase)
|
321
|
+
}
|
322
|
+
group_columns = group_aliases.zip(group_fields)
|
323
|
+
|
324
|
+
aggregate_alias = column_alias_for("#{operation} #{column_name.to_s.downcase}")
|
325
|
+
|
326
|
+
select_values = [
|
327
|
+
operation_over_aggregate_column(
|
328
|
+
aggregate_column(column_name),
|
329
|
+
operation,
|
330
|
+
distinct).as(aggregate_alias)
|
331
|
+
]
|
332
|
+
select_values += self.select_values unless having_clause.empty?
|
333
|
+
|
334
|
+
select_values.concat group_columns.map { |aliaz, field|
|
335
|
+
if field.respond_to?(:as)
|
336
|
+
field.as(aliaz)
|
337
|
+
else
|
338
|
+
"#{field} AS #{aliaz}"
|
339
|
+
end
|
340
|
+
}
|
298
341
|
|
299
|
-
|
342
|
+
relation = except(:group).distinct!(false)
|
343
|
+
relation.group_values = group_fields
|
344
|
+
relation.select_values = select_values
|
300
345
|
|
301
|
-
|
302
|
-
aggregate_alias = 'count_all'
|
303
|
-
else
|
304
|
-
aggregate_alias = column_alias_for([operation, column_name].join(' '))
|
305
|
-
end
|
346
|
+
calculated_data = skip_query_cache_if_necessary { @klass.connection.select_all(relation.arel, nil) }
|
306
347
|
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
distinct).as(aggregate_alias)
|
312
|
-
]
|
313
|
-
select_values += self.select_values unless having_values.empty?
|
314
|
-
|
315
|
-
select_values.concat group_fields.zip(group_aliases).map { |field,aliaz|
|
316
|
-
if field.respond_to?(:as)
|
317
|
-
field.as(aliaz)
|
318
|
-
else
|
319
|
-
"#{field} AS #{aliaz}"
|
348
|
+
if association
|
349
|
+
key_ids = calculated_data.collect { |row| row[group_aliases.first] }
|
350
|
+
key_records = association.klass.base_class.where(association.klass.base_class.primary_key => key_ids)
|
351
|
+
key_records = Hash[key_records.map { |r| [r.id, r] }]
|
320
352
|
end
|
321
|
-
}
|
322
|
-
|
323
|
-
relation = except(:group)
|
324
|
-
relation.group_values = group
|
325
|
-
relation.select_values = select_values
|
326
|
-
|
327
|
-
calculated_data = @klass.connection.select_all(relation, nil, relation.arel.bind_values + bind_values)
|
328
353
|
|
329
|
-
|
330
|
-
|
331
|
-
|
332
|
-
|
354
|
+
Hash[calculated_data.map do |row|
|
355
|
+
key = group_columns.map { |aliaz, col_name|
|
356
|
+
type = type_for(col_name) do
|
357
|
+
calculated_data.column_types.fetch(aliaz, Type.default_value)
|
358
|
+
end
|
359
|
+
type_cast_calculated_value(row[aliaz], type)
|
360
|
+
}
|
361
|
+
key = key.first if key.size == 1
|
362
|
+
key = key_records[key] if associated
|
363
|
+
|
364
|
+
type = calculated_data.column_types.fetch(aggregate_alias) { type_for(column_name) }
|
365
|
+
[key, type_cast_calculated_value(row[aggregate_alias], type, operation)]
|
366
|
+
end]
|
333
367
|
end
|
334
368
|
|
335
|
-
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
|
342
|
-
|
343
|
-
|
344
|
-
|
345
|
-
|
346
|
-
|
347
|
-
|
348
|
-
|
349
|
-
|
350
|
-
# Converts the given keys to the value that the database adapter returns as
|
351
|
-
# a usable column name:
|
352
|
-
#
|
353
|
-
# column_alias_for("users.id") # => "users_id"
|
354
|
-
# column_alias_for("sum(id)") # => "sum_id"
|
355
|
-
# column_alias_for("count(distinct users.id)") # => "count_distinct_users_id"
|
356
|
-
# column_alias_for("count(*)") # => "count_all"
|
357
|
-
# column_alias_for("count", "id") # => "count_id"
|
358
|
-
def column_alias_for(keys)
|
359
|
-
if keys.respond_to? :name
|
360
|
-
keys = "#{keys.relation.name}.#{keys.name}"
|
369
|
+
# Converts the given field to the value that the database adapter returns as
|
370
|
+
# a usable column name:
|
371
|
+
#
|
372
|
+
# column_alias_for("users.id") # => "users_id"
|
373
|
+
# column_alias_for("sum(id)") # => "sum_id"
|
374
|
+
# column_alias_for("count(distinct users.id)") # => "count_distinct_users_id"
|
375
|
+
# column_alias_for("count(*)") # => "count_all"
|
376
|
+
def column_alias_for(field)
|
377
|
+
column_alias = +field
|
378
|
+
column_alias.gsub!(/\*/, "all")
|
379
|
+
column_alias.gsub!(/\W+/, " ")
|
380
|
+
column_alias.strip!
|
381
|
+
column_alias.gsub!(/ +/, "_")
|
382
|
+
|
383
|
+
connection.table_alias_for(column_alias)
|
361
384
|
end
|
362
385
|
|
363
|
-
|
364
|
-
|
365
|
-
|
366
|
-
|
367
|
-
table_name.gsub!(/ +/, '_')
|
368
|
-
|
369
|
-
@klass.connection.table_alias_for(table_name)
|
370
|
-
end
|
371
|
-
|
372
|
-
def type_for(field)
|
373
|
-
field_name = field.respond_to?(:name) ? field.name.to_s : field.to_s.split('.').last
|
374
|
-
@klass.type_for_attribute(field_name)
|
375
|
-
end
|
386
|
+
def type_for(field, &block)
|
387
|
+
field_name = field.respond_to?(:name) ? field.name.to_s : field.to_s.split(".").last
|
388
|
+
@klass.type_for_attribute(field_name, &block)
|
389
|
+
end
|
376
390
|
|
377
|
-
|
378
|
-
|
379
|
-
when
|
380
|
-
when
|
381
|
-
when
|
382
|
-
else type.
|
391
|
+
def type_cast_calculated_value(value, type, operation = nil)
|
392
|
+
case operation
|
393
|
+
when "count" then value.to_i
|
394
|
+
when "sum" then type.deserialize(value || 0)
|
395
|
+
when "average" then value&.respond_to?(:to_d) ? value.to_d : value
|
396
|
+
else type.deserialize(value)
|
397
|
+
end
|
383
398
|
end
|
384
|
-
end
|
385
399
|
|
386
|
-
|
387
|
-
|
388
|
-
|
389
|
-
|
390
|
-
|
391
|
-
|
400
|
+
def select_for_count
|
401
|
+
if select_values.present?
|
402
|
+
return select_values.first if select_values.one?
|
403
|
+
select_values.join(", ")
|
404
|
+
else
|
405
|
+
:all
|
406
|
+
end
|
392
407
|
end
|
393
|
-
end
|
394
408
|
|
395
|
-
|
396
|
-
|
397
|
-
|
409
|
+
def build_count_subquery(relation, column_name, distinct)
|
410
|
+
if column_name == :all
|
411
|
+
column_alias = Arel.star
|
412
|
+
relation.select_values = [ Arel.sql(FinderMethods::ONE_AS_ONE) ] unless distinct
|
413
|
+
else
|
414
|
+
column_alias = Arel.sql("count_column")
|
415
|
+
relation.select_values = [ aggregate_column(column_name).as(column_alias) ]
|
416
|
+
end
|
398
417
|
|
399
|
-
|
400
|
-
|
401
|
-
arel = relation.arel
|
402
|
-
subquery = arel.as(subquery_alias)
|
418
|
+
subquery_alias = Arel.sql("subquery_for_count")
|
419
|
+
select_value = operation_over_aggregate_column(column_alias, "count", false)
|
403
420
|
|
404
|
-
|
405
|
-
|
406
|
-
select_value = operation_over_aggregate_column(column_alias, 'count', distinct)
|
407
|
-
sm.project(select_value).from(subquery)
|
408
|
-
end
|
421
|
+
relation.build_subquery(subquery_alias, select_value)
|
422
|
+
end
|
409
423
|
end
|
410
424
|
end
|