activerecord 4.2.0 → 6.1.7.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +5 -5
- data/CHANGELOG.md +1221 -796
- data/MIT-LICENSE +4 -2
- data/README.rdoc +15 -14
- data/examples/performance.rb +33 -32
- data/examples/simple.rb +5 -4
- data/lib/active_record/aggregations.rb +267 -249
- data/lib/active_record/association_relation.rb +45 -7
- data/lib/active_record/associations/alias_tracker.rb +40 -43
- data/lib/active_record/associations/association.rb +172 -67
- data/lib/active_record/associations/association_scope.rb +105 -129
- data/lib/active_record/associations/belongs_to_association.rb +85 -59
- data/lib/active_record/associations/belongs_to_polymorphic_association.rb +13 -12
- data/lib/active_record/associations/builder/association.rb +57 -43
- data/lib/active_record/associations/builder/belongs_to.rb +74 -57
- data/lib/active_record/associations/builder/collection_association.rb +15 -33
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +57 -70
- data/lib/active_record/associations/builder/has_many.rb +13 -5
- data/lib/active_record/associations/builder/has_one.rb +44 -6
- data/lib/active_record/associations/builder/singular_association.rb +16 -10
- data/lib/active_record/associations/collection_association.rb +168 -279
- data/lib/active_record/associations/collection_proxy.rb +263 -155
- data/lib/active_record/associations/foreign_association.rb +33 -0
- data/lib/active_record/associations/has_many_association.rb +57 -84
- data/lib/active_record/associations/has_many_through_association.rb +70 -82
- data/lib/active_record/associations/has_one_association.rb +74 -47
- data/lib/active_record/associations/has_one_through_association.rb +20 -11
- data/lib/active_record/associations/join_dependency/join_association.rb +54 -73
- 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/join_dependency.rb +175 -164
- data/lib/active_record/associations/preloader/association.rb +107 -112
- data/lib/active_record/associations/preloader/through_association.rb +85 -65
- data/lib/active_record/associations/preloader.rb +99 -96
- data/lib/active_record/associations/singular_association.rb +18 -45
- data/lib/active_record/associations/through_association.rb +49 -24
- data/lib/active_record/associations.rb +1845 -1597
- data/lib/active_record/attribute_assignment.rb +59 -185
- data/lib/active_record/attribute_methods/before_type_cast.rb +20 -7
- data/lib/active_record/attribute_methods/dirty.rb +168 -138
- data/lib/active_record/attribute_methods/primary_key.rb +93 -83
- data/lib/active_record/attribute_methods/query.rb +8 -10
- data/lib/active_record/attribute_methods/read.rb +19 -79
- data/lib/active_record/attribute_methods/serialization.rb +49 -24
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +59 -36
- data/lib/active_record/attribute_methods/write.rb +25 -56
- data/lib/active_record/attribute_methods.rb +153 -162
- data/lib/active_record/attributes.rb +234 -70
- data/lib/active_record/autosave_association.rb +157 -69
- data/lib/active_record/base.rb +49 -50
- data/lib/active_record/callbacks.rb +234 -79
- data/lib/active_record/coders/json.rb +3 -1
- data/lib/active_record/coders/yaml_column.rb +46 -13
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +887 -317
- data/lib/active_record/connection_adapters/abstract/database_limits.rb +17 -41
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +301 -113
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +78 -24
- data/lib/active_record/connection_adapters/abstract/quoting.rb +187 -60
- data/lib/active_record/connection_adapters/abstract/savepoints.rb +9 -7
- data/lib/active_record/connection_adapters/abstract/schema_creation.rb +157 -93
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +485 -253
- data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +79 -36
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +909 -263
- data/lib/active_record/connection_adapters/abstract/transaction.rb +254 -92
- data/lib/active_record/connection_adapters/abstract_adapter.rb +492 -221
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +580 -608
- data/lib/active_record/connection_adapters/column.rb +67 -40
- data/lib/active_record/connection_adapters/deduplicable.rb +29 -0
- data/lib/active_record/connection_adapters/legacy_pool_manager.rb +35 -0
- data/lib/active_record/connection_adapters/mysql/column.rb +27 -0
- data/lib/active_record/connection_adapters/mysql/database_statements.rb +196 -0
- data/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb +71 -0
- data/lib/active_record/connection_adapters/mysql/quoting.rb +96 -0
- data/lib/active_record/connection_adapters/mysql/schema_creation.rb +97 -0
- data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +103 -0
- data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +91 -0
- data/lib/active_record/connection_adapters/mysql/schema_statements.rb +271 -0
- data/lib/active_record/connection_adapters/mysql/type_metadata.rb +40 -0
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +81 -199
- data/lib/active_record/connection_adapters/pool_config.rb +73 -0
- data/lib/active_record/connection_adapters/pool_manager.rb +47 -0
- data/lib/active_record/connection_adapters/postgresql/column.rb +44 -11
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +78 -161
- data/lib/active_record/connection_adapters/postgresql/explain_pretty_printer.rb +44 -0
- data/lib/active_record/connection_adapters/postgresql/oid/array.rb +49 -57
- 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 +5 -2
- data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +8 -6
- data/lib/active_record/connection_adapters/postgresql/oid/date.rb +13 -1
- data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +17 -13
- data/lib/active_record/connection_adapters/postgresql/oid/decimal.rb +3 -1
- data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +6 -3
- data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +31 -20
- data/lib/active_record/connection_adapters/postgresql/oid/inet.rb +2 -0
- data/lib/active_record/connection_adapters/postgresql/oid/interval.rb +49 -0
- data/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb +3 -11
- data/lib/active_record/connection_adapters/postgresql/oid/legacy_point.rb +44 -0
- data/lib/active_record/connection_adapters/postgresql/oid/macaddr.rb +25 -0
- data/lib/active_record/connection_adapters/postgresql/oid/money.rb +7 -9
- data/lib/active_record/connection_adapters/postgresql/oid/{infinity.rb → oid.rb} +5 -3
- data/lib/active_record/connection_adapters/postgresql/oid/point.rb +32 -11
- data/lib/active_record/connection_adapters/postgresql/oid/range.rb +70 -34
- data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +4 -1
- data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +67 -51
- data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +18 -4
- 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/oid.rb +25 -25
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +171 -48
- data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +27 -14
- data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +80 -0
- data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +178 -108
- data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +49 -0
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +499 -293
- data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +44 -0
- data/lib/active_record/connection_adapters/postgresql/utils.rb +11 -8
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +595 -382
- data/lib/active_record/connection_adapters/schema_cache.rb +191 -29
- data/lib/active_record/connection_adapters/sql_type_metadata.rb +45 -0
- data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +146 -0
- data/lib/active_record/connection_adapters/sqlite3/explain_pretty_printer.rb +21 -0
- data/lib/active_record/connection_adapters/sqlite3/quoting.rb +102 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +21 -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 +170 -0
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +322 -389
- data/lib/active_record/connection_adapters/statement_pool.rb +33 -13
- data/lib/active_record/connection_adapters.rb +52 -0
- data/lib/active_record/connection_handling.rb +314 -41
- data/lib/active_record/core.rb +488 -243
- data/lib/active_record/counter_cache.rb +71 -50
- data/lib/active_record/database_configurations/connection_url_resolver.rb +99 -0
- data/lib/active_record/database_configurations/database_config.rb +80 -0
- data/lib/active_record/database_configurations/hash_config.rb +96 -0
- data/lib/active_record/database_configurations/url_config.rb +53 -0
- data/lib/active_record/database_configurations.rb +273 -0
- data/lib/active_record/delegated_type.rb +209 -0
- data/lib/active_record/destroy_association_async_job.rb +36 -0
- data/lib/active_record/dynamic_matchers.rb +87 -106
- data/lib/active_record/enum.rb +212 -94
- data/lib/active_record/errors.rb +225 -54
- data/lib/active_record/explain.rb +27 -11
- data/lib/active_record/explain_registry.rb +4 -2
- data/lib/active_record/explain_subscriber.rb +11 -6
- data/lib/active_record/fixture_set/file.rb +33 -14
- data/lib/active_record/fixture_set/model_metadata.rb +32 -0
- data/lib/active_record/fixture_set/render_context.rb +17 -0
- data/lib/active_record/fixture_set/table_row.rb +152 -0
- data/lib/active_record/fixture_set/table_rows.rb +46 -0
- data/lib/active_record/fixtures.rb +273 -496
- data/lib/active_record/gem_version.rb +6 -4
- data/lib/active_record/inheritance.rb +175 -110
- data/lib/active_record/insert_all.rb +212 -0
- data/lib/active_record/integration.rb +121 -29
- data/lib/active_record/internal_metadata.rb +64 -0
- data/lib/active_record/legacy_yaml_adapter.rb +52 -0
- data/lib/active_record/locale/en.yml +3 -2
- data/lib/active_record/locking/optimistic.rb +103 -95
- data/lib/active_record/locking/pessimistic.rb +22 -6
- data/lib/active_record/log_subscriber.rb +93 -31
- data/lib/active_record/middleware/database_selector/resolver/session.rb +48 -0
- data/lib/active_record/middleware/database_selector/resolver.rb +92 -0
- data/lib/active_record/middleware/database_selector.rb +77 -0
- data/lib/active_record/migration/command_recorder.rb +185 -90
- data/lib/active_record/migration/compatibility.rb +298 -0
- data/lib/active_record/migration/join_table.rb +8 -7
- data/lib/active_record/migration.rb +685 -309
- data/lib/active_record/model_schema.rb +420 -113
- data/lib/active_record/nested_attributes.rb +265 -216
- data/lib/active_record/no_touching.rb +15 -2
- data/lib/active_record/null_relation.rb +24 -38
- data/lib/active_record/persistence.rb +574 -135
- data/lib/active_record/query_cache.rb +29 -23
- data/lib/active_record/querying.rb +50 -31
- data/lib/active_record/railtie.rb +175 -54
- data/lib/active_record/railties/console_sandbox.rb +3 -3
- data/lib/active_record/railties/controller_runtime.rb +34 -33
- data/lib/active_record/railties/databases.rake +533 -216
- data/lib/active_record/readonly_attributes.rb +9 -4
- data/lib/active_record/reflection.rb +485 -310
- data/lib/active_record/relation/batches/batch_enumerator.rb +85 -0
- data/lib/active_record/relation/batches.rb +217 -59
- data/lib/active_record/relation/calculations.rb +326 -244
- data/lib/active_record/relation/delegation.rb +76 -84
- data/lib/active_record/relation/finder_methods.rb +318 -256
- data/lib/active_record/relation/from_clause.rb +30 -0
- data/lib/active_record/relation/merger.rb +99 -84
- data/lib/active_record/relation/predicate_builder/array_handler.rb +26 -25
- data/lib/active_record/relation/predicate_builder/association_query_value.rb +42 -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 +57 -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/predicate_builder.rb +139 -96
- data/lib/active_record/relation/query_attribute.rb +50 -0
- data/lib/active_record/relation/query_methods.rb +757 -409
- data/lib/active_record/relation/record_fetch_warning.rb +51 -0
- data/lib/active_record/relation/spawn_methods.rb +23 -21
- data/lib/active_record/relation/where_clause.rb +239 -0
- data/lib/active_record/relation.rb +554 -342
- data/lib/active_record/result.rb +91 -47
- data/lib/active_record/runtime_registry.rb +6 -4
- data/lib/active_record/sanitization.rb +134 -122
- data/lib/active_record/schema.rb +21 -24
- data/lib/active_record/schema_dumper.rb +141 -92
- data/lib/active_record/schema_migration.rb +24 -26
- data/lib/active_record/scoping/default.rb +96 -82
- data/lib/active_record/scoping/named.rb +78 -36
- data/lib/active_record/scoping.rb +45 -27
- data/lib/active_record/secure_token.rb +48 -0
- data/lib/active_record/serialization.rb +8 -6
- data/lib/active_record/signed_id.rb +116 -0
- data/lib/active_record/statement_cache.rb +89 -36
- data/lib/active_record/store.rb +133 -43
- data/lib/active_record/suppressor.rb +61 -0
- data/lib/active_record/table_metadata.rb +81 -0
- data/lib/active_record/tasks/database_tasks.rb +366 -129
- data/lib/active_record/tasks/mysql_database_tasks.rb +68 -100
- data/lib/active_record/tasks/postgresql_database_tasks.rb +87 -39
- data/lib/active_record/tasks/sqlite_database_tasks.rb +44 -19
- data/lib/active_record/test_databases.rb +24 -0
- data/lib/active_record/test_fixtures.rb +291 -0
- data/lib/active_record/timestamp.rb +86 -43
- data/lib/active_record/touch_later.rb +65 -0
- data/lib/active_record/transactions.rb +181 -152
- data/lib/active_record/translation.rb +3 -1
- data/lib/active_record/type/adapter_specific_registry.rb +126 -0
- data/lib/active_record/type/date.rb +4 -41
- data/lib/active_record/type/date_time.rb +4 -38
- data/lib/active_record/type/decimal_without_scale.rb +6 -2
- data/lib/active_record/type/hash_lookup_type_map.rb +12 -5
- 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 +33 -15
- data/lib/active_record/type/text.rb +2 -2
- data/lib/active_record/type/time.rb +21 -16
- data/lib/active_record/type/type_map.rb +16 -19
- data/lib/active_record/type/unsigned_integer.rb +9 -8
- data/lib/active_record/type.rb +84 -23
- data/lib/active_record/type_caster/connection.rb +33 -0
- data/lib/active_record/type_caster/map.rb +23 -0
- data/lib/active_record/type_caster.rb +9 -0
- data/lib/active_record/validations/absence.rb +25 -0
- data/lib/active_record/validations/associated.rb +12 -4
- data/lib/active_record/validations/length.rb +26 -0
- data/lib/active_record/validations/numericality.rb +35 -0
- data/lib/active_record/validations/presence.rb +14 -13
- data/lib/active_record/validations/uniqueness.rb +65 -48
- data/lib/active_record/validations.rb +39 -35
- data/lib/active_record/version.rb +3 -1
- data/lib/active_record.rb +44 -28
- data/lib/arel/alias_predication.rb +9 -0
- data/lib/arel/attributes/attribute.rb +41 -0
- data/lib/arel/collectors/bind.rb +29 -0
- data/lib/arel/collectors/composite.rb +39 -0
- data/lib/arel/collectors/plain_string.rb +20 -0
- data/lib/arel/collectors/sql_string.rb +27 -0
- data/lib/arel/collectors/substitute_binds.rb +35 -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/and.rb +32 -0
- data/lib/arel/nodes/ascending.rb +23 -0
- data/lib/arel/nodes/binary.rb +126 -0
- data/lib/arel/nodes/bind_param.rb +44 -0
- data/lib/arel/nodes/case.rb +55 -0
- data/lib/arel/nodes/casted.rb +62 -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 +15 -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 +11 -0
- data/lib/arel/nodes/homogeneous_in.rb +76 -0
- data/lib/arel/nodes/in.rb +15 -0
- data/lib/arel/nodes/infix_operation.rb +92 -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 +51 -0
- data/lib/arel/nodes/node_expression.rb +13 -0
- data/lib/arel/nodes/ordering.rb +27 -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 +19 -0
- data/lib/arel/nodes/string_join.rb +11 -0
- data/lib/arel/nodes/table_alias.rb +31 -0
- data/lib/arel/nodes/terminal.rb +16 -0
- data/lib/arel/nodes/true.rb +16 -0
- data/lib/arel/nodes/unary.rb +44 -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/nodes.rb +70 -0
- data/lib/arel/order_predications.rb +13 -0
- data/lib/arel/predications.rb +250 -0
- data/lib/arel/select_manager.rb +270 -0
- data/lib/arel/table.rb +118 -0
- data/lib/arel/tree_manager.rb +72 -0
- data/lib/arel/update_manager.rb +34 -0
- data/lib/arel/visitors/dot.rb +308 -0
- data/lib/arel/visitors/mysql.rb +93 -0
- data/lib/arel/visitors/postgresql.rb +120 -0
- data/lib/arel/visitors/sqlite.rb +38 -0
- data/lib/arel/visitors/to_sql.rb +899 -0
- data/lib/arel/visitors/visitor.rb +45 -0
- data/lib/arel/visitors.rb +13 -0
- data/lib/arel/window_predications.rb +9 -0
- data/lib/arel.rb +54 -0
- data/lib/rails/generators/active_record/application_record/application_record_generator.rb +26 -0
- data/lib/rails/generators/active_record/application_record/templates/application_record.rb.tt +5 -0
- data/lib/rails/generators/active_record/migration/migration_generator.rb +43 -37
- data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +26 -0
- data/lib/rails/generators/active_record/migration/templates/{migration.rb → migration.rb.tt} +13 -10
- data/lib/rails/generators/active_record/migration.rb +35 -1
- data/lib/rails/generators/active_record/model/model_generator.rb +55 -22
- data/lib/rails/generators/active_record/model/templates/abstract_base_class.rb.tt +7 -0
- data/lib/rails/generators/active_record/model/templates/model.rb.tt +22 -0
- data/lib/rails/generators/active_record.rb +7 -5
- metadata +175 -65
- 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 -149
- data/lib/active_record/attribute_decorators.rb +0 -66
- data/lib/active_record/attribute_set/builder.rb +0 -86
- data/lib/active_record/attribute_set.rb +0 -77
- data/lib/active_record/connection_adapters/connection_specification.rb +0 -275
- data/lib/active_record/connection_adapters/mysql_adapter.rb +0 -491
- 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/integer.rb +0 -11
- 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 -30
- data/lib/active_record/type/decimal.rb +0 -40
- 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 -55
- 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 -36
- data/lib/active_record/type/time_value.rb +0 -38
- data/lib/active_record/type/value.rb +0 -101
- data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb +0 -22
- data/lib/rails/generators/active_record/model/templates/model.rb +0 -10
- /data/lib/rails/generators/active_record/model/templates/{module.rb → module.rb.tt} +0 -0
@@ -1,5 +1,6 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_support/core_ext/string/filters"
|
3
4
|
|
4
5
|
module ActiveRecord
|
5
6
|
# = Active Record Reflection
|
@@ -7,41 +8,46 @@ module ActiveRecord
|
|
7
8
|
extend ActiveSupport::Concern
|
8
9
|
|
9
10
|
included do
|
10
|
-
class_attribute :_reflections
|
11
|
-
class_attribute :aggregate_reflections
|
12
|
-
self._reflections = {}
|
13
|
-
self.aggregate_reflections = {}
|
11
|
+
class_attribute :_reflections, instance_writer: false, default: {}
|
12
|
+
class_attribute :aggregate_reflections, instance_writer: false, default: {}
|
14
13
|
end
|
15
14
|
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
HasManyReflection
|
22
|
-
when :has_one
|
23
|
-
HasOneReflection
|
24
|
-
when :belongs_to
|
25
|
-
BelongsToReflection
|
26
|
-
else
|
27
|
-
raise "Unsupported Macro: #{macro}"
|
28
|
-
end
|
29
|
-
|
30
|
-
reflection = klass.new(name, scope, options, ar)
|
31
|
-
options[:through] ? ThroughReflection.new(reflection) : reflection
|
32
|
-
end
|
15
|
+
class << self
|
16
|
+
def create(macro, name, scope, options, ar)
|
17
|
+
reflection = reflection_class_for(macro).new(name, scope, options, ar)
|
18
|
+
options[:through] ? ThroughReflection.new(reflection) : reflection
|
19
|
+
end
|
33
20
|
|
34
|
-
|
35
|
-
|
36
|
-
|
21
|
+
def add_reflection(ar, name, reflection)
|
22
|
+
ar.clear_reflections_cache
|
23
|
+
name = -name.to_s
|
24
|
+
ar._reflections = ar._reflections.except(name).merge!(name => reflection)
|
25
|
+
end
|
37
26
|
|
38
|
-
|
39
|
-
|
27
|
+
def add_aggregate_reflection(ar, name, reflection)
|
28
|
+
ar.aggregate_reflections = ar.aggregate_reflections.merge(-name.to_s => reflection)
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
def reflection_class_for(macro)
|
33
|
+
case macro
|
34
|
+
when :composed_of
|
35
|
+
AggregateReflection
|
36
|
+
when :has_many
|
37
|
+
HasManyReflection
|
38
|
+
when :has_one
|
39
|
+
HasOneReflection
|
40
|
+
when :belongs_to
|
41
|
+
BelongsToReflection
|
42
|
+
else
|
43
|
+
raise "Unsupported Macro: #{macro}"
|
44
|
+
end
|
45
|
+
end
|
40
46
|
end
|
41
47
|
|
42
|
-
# \Reflection enables
|
43
|
-
#
|
44
|
-
#
|
48
|
+
# \Reflection enables the ability to examine the associations and aggregations of
|
49
|
+
# Active Record classes and objects. This information, for example,
|
50
|
+
# can be used in a form builder that takes an Active Record object
|
45
51
|
# and creates input fields for all of the attributes depending on their type
|
46
52
|
# and displays the associations to other objects.
|
47
53
|
#
|
@@ -61,22 +67,27 @@ module ActiveRecord
|
|
61
67
|
aggregate_reflections[aggregation.to_s]
|
62
68
|
end
|
63
69
|
|
64
|
-
# Returns a Hash of name of the reflection as the key and
|
70
|
+
# Returns a Hash of name of the reflection as the key and an AssociationReflection as the value.
|
65
71
|
#
|
66
72
|
# Account.reflections # => {"balance" => AggregateReflection}
|
67
73
|
#
|
68
|
-
# @api public
|
69
74
|
def reflections
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
75
|
+
@__reflections ||= begin
|
76
|
+
ref = {}
|
77
|
+
|
78
|
+
_reflections.each do |name, reflection|
|
79
|
+
parent_reflection = reflection.parent_reflection
|
80
|
+
|
81
|
+
if parent_reflection
|
82
|
+
parent_name = parent_reflection.name
|
83
|
+
ref[parent_name.to_s] = parent_reflection
|
84
|
+
else
|
85
|
+
ref[name] = reflection
|
86
|
+
end
|
77
87
|
end
|
88
|
+
|
89
|
+
ref
|
78
90
|
end
|
79
|
-
ref
|
80
91
|
end
|
81
92
|
|
82
93
|
# Returns an array of AssociationReflection objects for all the
|
@@ -89,10 +100,10 @@ module ActiveRecord
|
|
89
100
|
# Account.reflect_on_all_associations # returns an array of all associations
|
90
101
|
# Account.reflect_on_all_associations(:has_many) # returns an array of all has_many associations
|
91
102
|
#
|
92
|
-
# @api public
|
93
103
|
def reflect_on_all_associations(macro = nil)
|
94
104
|
association_reflections = reflections.values
|
95
|
-
|
105
|
+
association_reflections.select! { |reflection| reflection.macro == macro } if macro
|
106
|
+
association_reflections
|
96
107
|
end
|
97
108
|
|
98
109
|
# Returns the AssociationReflection object for the +association+ (use the symbol).
|
@@ -100,27 +111,42 @@ module ActiveRecord
|
|
100
111
|
# Account.reflect_on_association(:owner) # returns the owner AssociationReflection
|
101
112
|
# Invoice.reflect_on_association(:line_items).macro # returns :has_many
|
102
113
|
#
|
103
|
-
# @api public
|
104
114
|
def reflect_on_association(association)
|
105
115
|
reflections[association.to_s]
|
106
116
|
end
|
107
117
|
|
108
|
-
# @api private
|
109
118
|
def _reflect_on_association(association) #:nodoc:
|
110
119
|
_reflections[association.to_s]
|
111
120
|
end
|
112
121
|
|
113
122
|
# Returns an array of AssociationReflection objects for all associations which have <tt>:autosave</tt> enabled.
|
114
|
-
#
|
115
|
-
# @api public
|
116
123
|
def reflect_on_all_autosave_associations
|
117
124
|
reflections.values.select { |reflection| reflection.options[:autosave] }
|
118
125
|
end
|
126
|
+
|
127
|
+
def clear_reflections_cache # :nodoc:
|
128
|
+
@__reflections = nil
|
129
|
+
end
|
119
130
|
end
|
120
131
|
|
121
|
-
# Holds all the methods that are shared between MacroReflection
|
122
|
-
#
|
132
|
+
# Holds all the methods that are shared between MacroReflection and ThroughReflection.
|
133
|
+
#
|
134
|
+
# AbstractReflection
|
135
|
+
# MacroReflection
|
136
|
+
# AggregateReflection
|
137
|
+
# AssociationReflection
|
138
|
+
# HasManyReflection
|
139
|
+
# HasOneReflection
|
140
|
+
# BelongsToReflection
|
141
|
+
# HasAndBelongsToManyReflection
|
142
|
+
# ThroughReflection
|
143
|
+
# PolymorphicReflection
|
144
|
+
# RuntimeReflection
|
123
145
|
class AbstractReflection # :nodoc:
|
146
|
+
def through_reflection?
|
147
|
+
false
|
148
|
+
end
|
149
|
+
|
124
150
|
def table_name
|
125
151
|
klass.table_name
|
126
152
|
end
|
@@ -131,47 +157,159 @@ module ActiveRecord
|
|
131
157
|
klass.new(attributes, &block)
|
132
158
|
end
|
133
159
|
|
134
|
-
def quoted_table_name
|
135
|
-
klass.quoted_table_name
|
136
|
-
end
|
137
|
-
|
138
|
-
def primary_key_type
|
139
|
-
klass.type_for_attribute(klass.primary_key)
|
140
|
-
end
|
141
|
-
|
142
160
|
# Returns the class name for the macro.
|
143
161
|
#
|
144
162
|
# <tt>composed_of :balance, class_name: 'Money'</tt> returns <tt>'Money'</tt>
|
145
163
|
# <tt>has_many :clients</tt> returns <tt>'Client'</tt>
|
146
164
|
def class_name
|
147
|
-
@class_name ||= (options[:class_name] || derive_class_name).to_s
|
165
|
+
@class_name ||= -(options[:class_name] || derive_class_name).to_s
|
166
|
+
end
|
167
|
+
|
168
|
+
# Returns a list of scopes that should be applied for this Reflection
|
169
|
+
# object when querying the database.
|
170
|
+
def scopes
|
171
|
+
scope ? [scope] : []
|
172
|
+
end
|
173
|
+
|
174
|
+
def join_scope(table, foreign_table, foreign_klass)
|
175
|
+
predicate_builder = predicate_builder(table)
|
176
|
+
scope_chain_items = join_scopes(table, predicate_builder)
|
177
|
+
klass_scope = klass_join_scope(table, predicate_builder)
|
178
|
+
|
179
|
+
if type
|
180
|
+
klass_scope.where!(type => foreign_klass.polymorphic_name)
|
181
|
+
end
|
182
|
+
|
183
|
+
scope_chain_items.inject(klass_scope, &:merge!)
|
184
|
+
|
185
|
+
primary_key = join_primary_key
|
186
|
+
foreign_key = join_foreign_key
|
187
|
+
|
188
|
+
klass_scope.where!(table[primary_key].eq(foreign_table[foreign_key]))
|
189
|
+
|
190
|
+
if klass.finder_needs_type_condition?
|
191
|
+
klass_scope.where!(klass.send(:type_condition, table))
|
192
|
+
end
|
193
|
+
|
194
|
+
klass_scope
|
195
|
+
end
|
196
|
+
|
197
|
+
def join_scopes(table, predicate_builder, klass = self.klass) # :nodoc:
|
198
|
+
if scope
|
199
|
+
[scope_for(build_scope(table, predicate_builder, klass))]
|
200
|
+
else
|
201
|
+
[]
|
202
|
+
end
|
203
|
+
end
|
204
|
+
|
205
|
+
def klass_join_scope(table, predicate_builder) # :nodoc:
|
206
|
+
relation = build_scope(table, predicate_builder)
|
207
|
+
klass.scope_for_association(relation)
|
208
|
+
end
|
209
|
+
|
210
|
+
def constraints
|
211
|
+
chain.flat_map(&:scopes)
|
212
|
+
end
|
213
|
+
|
214
|
+
def counter_cache_column
|
215
|
+
@counter_cache_column ||= if belongs_to?
|
216
|
+
if options[:counter_cache] == true
|
217
|
+
-"#{active_record.name.demodulize.underscore.pluralize}_count"
|
218
|
+
elsif options[:counter_cache]
|
219
|
+
-options[:counter_cache].to_s
|
220
|
+
end
|
221
|
+
else
|
222
|
+
-(options[:counter_cache]&.to_s || "#{name}_count")
|
223
|
+
end
|
224
|
+
end
|
225
|
+
|
226
|
+
def inverse_of
|
227
|
+
return unless inverse_name
|
228
|
+
|
229
|
+
@inverse_of ||= klass._reflect_on_association inverse_name
|
230
|
+
end
|
231
|
+
|
232
|
+
def check_validity_of_inverse!
|
233
|
+
unless polymorphic?
|
234
|
+
if has_inverse? && inverse_of.nil?
|
235
|
+
raise InverseOfAssociationNotFoundError.new(self)
|
236
|
+
end
|
237
|
+
end
|
238
|
+
end
|
239
|
+
|
240
|
+
# This shit is nasty. We need to avoid the following situation:
|
241
|
+
#
|
242
|
+
# * An associated record is deleted via record.destroy
|
243
|
+
# * Hence the callbacks run, and they find a belongs_to on the record with a
|
244
|
+
# :counter_cache options which points back at our owner. So they update the
|
245
|
+
# counter cache.
|
246
|
+
# * In which case, we must make sure to *not* update the counter cache, or else
|
247
|
+
# it will be decremented twice.
|
248
|
+
#
|
249
|
+
# Hence this method.
|
250
|
+
def inverse_which_updates_counter_cache
|
251
|
+
return @inverse_which_updates_counter_cache if defined?(@inverse_which_updates_counter_cache)
|
252
|
+
@inverse_which_updates_counter_cache = klass.reflect_on_all_associations(:belongs_to).find do |inverse|
|
253
|
+
inverse.counter_cache_column == counter_cache_column
|
254
|
+
end
|
255
|
+
end
|
256
|
+
alias inverse_updates_counter_cache? inverse_which_updates_counter_cache
|
257
|
+
|
258
|
+
def inverse_updates_counter_in_memory?
|
259
|
+
inverse_of && inverse_which_updates_counter_cache == inverse_of
|
260
|
+
end
|
261
|
+
|
262
|
+
# Returns whether a counter cache should be used for this association.
|
263
|
+
#
|
264
|
+
# The counter_cache option must be given on either the owner or inverse
|
265
|
+
# association, and the column must be present on the owner.
|
266
|
+
def has_cached_counter?
|
267
|
+
options[:counter_cache] ||
|
268
|
+
inverse_which_updates_counter_cache && inverse_which_updates_counter_cache.options[:counter_cache] &&
|
269
|
+
active_record.has_attribute?(counter_cache_column)
|
148
270
|
end
|
149
271
|
|
150
|
-
|
272
|
+
def counter_must_be_updated_by_has_many?
|
273
|
+
!inverse_updates_counter_in_memory? && has_cached_counter?
|
274
|
+
end
|
151
275
|
|
152
|
-
def
|
153
|
-
|
276
|
+
def alias_candidate(name)
|
277
|
+
"#{plural_name}_#{name}"
|
154
278
|
end
|
155
279
|
|
156
|
-
def
|
157
|
-
|
158
|
-
|
159
|
-
without replacement.
|
160
|
-
MSG
|
280
|
+
def chain
|
281
|
+
collect_join_chain
|
282
|
+
end
|
161
283
|
|
162
|
-
|
284
|
+
def build_scope(table, predicate_builder = predicate_builder(table), klass = self.klass)
|
285
|
+
Relation.create(
|
286
|
+
klass,
|
287
|
+
table: table,
|
288
|
+
predicate_builder: predicate_builder
|
289
|
+
)
|
163
290
|
end
|
291
|
+
|
292
|
+
def strict_loading?
|
293
|
+
options[:strict_loading]
|
294
|
+
end
|
295
|
+
|
296
|
+
protected
|
297
|
+
def actual_source_reflection # FIXME: this is a horrible name
|
298
|
+
self
|
299
|
+
end
|
300
|
+
|
301
|
+
private
|
302
|
+
def predicate_builder(table)
|
303
|
+
PredicateBuilder.new(TableMetadata.new(klass, table))
|
304
|
+
end
|
305
|
+
|
306
|
+
def primary_key(klass)
|
307
|
+
klass.primary_key || raise(UnknownPrimaryKey.new(klass))
|
308
|
+
end
|
164
309
|
end
|
310
|
+
|
165
311
|
# Base class for AggregateReflection and AssociationReflection. Objects of
|
166
312
|
# AggregateReflection and AssociationReflection are returned by the Reflection::ClassMethods.
|
167
|
-
#
|
168
|
-
# MacroReflection
|
169
|
-
# AssociationReflection
|
170
|
-
# AggregateReflection
|
171
|
-
# HasManyReflection
|
172
|
-
# HasOneReflection
|
173
|
-
# BelongsToReflection
|
174
|
-
# ThroughReflection
|
175
313
|
class MacroReflection < AbstractReflection
|
176
314
|
# Returns the name of the macro.
|
177
315
|
#
|
@@ -196,15 +334,14 @@ module ActiveRecord
|
|
196
334
|
@scope = scope
|
197
335
|
@options = options
|
198
336
|
@active_record = active_record
|
199
|
-
@klass = options[:
|
337
|
+
@klass = options[:anonymous_class]
|
200
338
|
@plural_name = active_record.pluralize_table_names ?
|
201
339
|
name.to_s.pluralize : name.to_s
|
202
340
|
end
|
203
341
|
|
204
342
|
def autosave=(autosave)
|
205
|
-
@automatic_inverse_of = false
|
206
343
|
@options[:autosave] = autosave
|
207
|
-
|
344
|
+
parent_reflection = self.parent_reflection
|
208
345
|
if parent_reflection
|
209
346
|
parent_reflection.autosave = autosave
|
210
347
|
end
|
@@ -214,6 +351,17 @@ module ActiveRecord
|
|
214
351
|
#
|
215
352
|
# <tt>composed_of :balance, class_name: 'Money'</tt> returns the Money class
|
216
353
|
# <tt>has_many :clients</tt> returns the Client class
|
354
|
+
#
|
355
|
+
# class Company < ActiveRecord::Base
|
356
|
+
# has_many :clients
|
357
|
+
# end
|
358
|
+
#
|
359
|
+
# Company.reflect_on_association(:clients).klass
|
360
|
+
# # => Client
|
361
|
+
#
|
362
|
+
# <b>Note:</b> Do not call +klass.new+ or +klass.create+ to instantiate
|
363
|
+
# a new association object. Use +build_association+ or +create_association+
|
364
|
+
# instead. This allows plugins to hook into association object creation.
|
217
365
|
def klass
|
218
366
|
@klass ||= compute_class(class_name)
|
219
367
|
end
|
@@ -232,14 +380,17 @@ module ActiveRecord
|
|
232
380
|
active_record == other_aggregation.active_record
|
233
381
|
end
|
234
382
|
|
383
|
+
def scope_for(relation, owner = nil)
|
384
|
+
relation.instance_exec(owner, &scope) || relation
|
385
|
+
end
|
386
|
+
|
235
387
|
private
|
236
388
|
def derive_class_name
|
237
389
|
name.to_s.camelize
|
238
390
|
end
|
239
391
|
end
|
240
392
|
|
241
|
-
|
242
|
-
# Holds all the meta-data about an aggregation as it was specified in the
|
393
|
+
# Holds all the metadata about an aggregation as it was specified in the
|
243
394
|
# Active Record class.
|
244
395
|
class AggregateReflection < MacroReflection #:nodoc:
|
245
396
|
def mapping
|
@@ -248,50 +399,36 @@ module ActiveRecord
|
|
248
399
|
end
|
249
400
|
end
|
250
401
|
|
251
|
-
# Holds all the
|
402
|
+
# Holds all the metadata about an association as it was specified in the
|
252
403
|
# Active Record class.
|
253
404
|
class AssociationReflection < MacroReflection #:nodoc:
|
254
|
-
# Returns the target association's class.
|
255
|
-
#
|
256
|
-
# class Author < ActiveRecord::Base
|
257
|
-
# has_many :books
|
258
|
-
# end
|
259
|
-
#
|
260
|
-
# Author.reflect_on_association(:books).klass
|
261
|
-
# # => Book
|
262
|
-
#
|
263
|
-
# <b>Note:</b> Do not call +klass.new+ or +klass.create+ to instantiate
|
264
|
-
# a new association object. Use +build_association+ or +create_association+
|
265
|
-
# instead. This allows plugins to hook into association object creation.
|
266
|
-
def klass
|
267
|
-
@klass ||= compute_class(class_name)
|
268
|
-
end
|
269
|
-
|
270
405
|
def compute_class(name)
|
406
|
+
if polymorphic?
|
407
|
+
raise ArgumentError, "Polymorphic associations do not support computing the class."
|
408
|
+
end
|
271
409
|
active_record.send(:compute_type, name)
|
272
410
|
end
|
273
411
|
|
274
412
|
attr_reader :type, :foreign_type
|
275
|
-
attr_accessor :parent_reflection #
|
413
|
+
attr_accessor :parent_reflection # Reflection
|
276
414
|
|
277
415
|
def initialize(name, scope, options, active_record)
|
278
416
|
super
|
279
|
-
@
|
280
|
-
@
|
281
|
-
@foreign_type = options[:foreign_type] || "#{name}_type"
|
417
|
+
@type = -(options[:foreign_type]&.to_s || "#{options[:as]}_type") if options[:as]
|
418
|
+
@foreign_type = -(options[:foreign_type]&.to_s || "#{name}_type") if options[:polymorphic]
|
282
419
|
@constructable = calculate_constructable(macro, options)
|
283
|
-
|
284
|
-
|
420
|
+
|
421
|
+
if options[:class_name] && options[:class_name].class == Class
|
422
|
+
raise ArgumentError, "A class was passed to `:class_name` but we are expecting a string."
|
423
|
+
end
|
285
424
|
end
|
286
425
|
|
287
|
-
def association_scope_cache(
|
288
|
-
key =
|
426
|
+
def association_scope_cache(klass, owner, &block)
|
427
|
+
key = self
|
289
428
|
if polymorphic?
|
290
429
|
key = [key, owner._read_attribute(@foreign_type)]
|
291
430
|
end
|
292
|
-
|
293
|
-
@association_scope_cache[key] ||= yield
|
294
|
-
}
|
431
|
+
klass.cached_find_by_statement(key, &block)
|
295
432
|
end
|
296
433
|
|
297
434
|
def constructable? # :nodoc:
|
@@ -299,64 +436,52 @@ module ActiveRecord
|
|
299
436
|
end
|
300
437
|
|
301
438
|
def join_table
|
302
|
-
@join_table ||= options[:join_table] || derive_join_table
|
439
|
+
@join_table ||= -(options[:join_table]&.to_s || derive_join_table)
|
303
440
|
end
|
304
441
|
|
305
442
|
def foreign_key
|
306
|
-
@foreign_key ||= options[:foreign_key] || derive_foreign_key
|
443
|
+
@foreign_key ||= -(options[:foreign_key]&.to_s || derive_foreign_key)
|
307
444
|
end
|
308
445
|
|
309
446
|
def association_foreign_key
|
310
|
-
@association_foreign_key ||= options[:association_foreign_key] || class_name.foreign_key
|
447
|
+
@association_foreign_key ||= -(options[:association_foreign_key]&.to_s || class_name.foreign_key)
|
311
448
|
end
|
312
449
|
|
313
|
-
# klass option is necessary to support loading polymorphic associations
|
314
450
|
def association_primary_key(klass = nil)
|
315
|
-
|
451
|
+
primary_key(klass || self.klass)
|
316
452
|
end
|
317
453
|
|
318
454
|
def active_record_primary_key
|
319
|
-
@active_record_primary_key ||= options[:primary_key] || primary_key(active_record)
|
455
|
+
@active_record_primary_key ||= -(options[:primary_key]&.to_s || primary_key(active_record))
|
320
456
|
end
|
321
457
|
|
322
|
-
def
|
323
|
-
|
324
|
-
"#{active_record.name.demodulize.underscore.pluralize}_count"
|
325
|
-
elsif options[:counter_cache]
|
326
|
-
options[:counter_cache].to_s
|
327
|
-
end
|
458
|
+
def join_primary_key(klass = nil)
|
459
|
+
foreign_key
|
328
460
|
end
|
329
461
|
|
330
|
-
def
|
331
|
-
|
462
|
+
def join_foreign_key
|
463
|
+
active_record_primary_key
|
332
464
|
end
|
333
465
|
|
334
|
-
def
|
335
|
-
|
336
|
-
if has_inverse? && inverse_of.nil?
|
337
|
-
raise InverseOfAssociationNotFoundError.new(self)
|
338
|
-
end
|
339
|
-
end
|
466
|
+
def check_validity!
|
467
|
+
check_validity_of_inverse!
|
340
468
|
end
|
341
469
|
|
342
470
|
def check_preloadable!
|
343
471
|
return unless scope
|
344
472
|
|
345
|
-
|
346
|
-
|
473
|
+
unless scope.arity == 0
|
474
|
+
raise ArgumentError, <<-MSG.squish
|
347
475
|
The association scope '#{name}' is instance dependent (the scope
|
348
|
-
block takes an argument). Preloading
|
349
|
-
|
350
|
-
passed to the association scope. This will most likely result in
|
351
|
-
broken or incorrect behavior. Joining, Preloading and eager loading
|
352
|
-
of these associations is deprecated and will be removed in the future.
|
476
|
+
block takes an argument). Preloading instance dependent scopes is
|
477
|
+
not supported.
|
353
478
|
MSG
|
354
479
|
end
|
355
480
|
end
|
356
481
|
alias :check_eager_loadable! :check_preloadable!
|
357
482
|
|
358
483
|
def join_id_for(owner) # :nodoc:
|
359
|
-
owner[
|
484
|
+
owner[join_foreign_key]
|
360
485
|
end
|
361
486
|
|
362
487
|
def through_reflection
|
@@ -369,30 +494,28 @@ module ActiveRecord
|
|
369
494
|
|
370
495
|
# A chain of reflections from this one back to the owner. For more see the explanation in
|
371
496
|
# ThroughReflection.
|
372
|
-
def
|
497
|
+
def collect_join_chain
|
373
498
|
[self]
|
374
499
|
end
|
375
500
|
|
501
|
+
# This is for clearing cache on the reflection. Useful for tests that need to compare
|
502
|
+
# SQL queries on associations.
|
503
|
+
def clear_association_scope_cache # :nodoc:
|
504
|
+
klass.initialize_find_by_cache
|
505
|
+
end
|
506
|
+
|
376
507
|
def nested?
|
377
508
|
false
|
378
509
|
end
|
379
510
|
|
380
|
-
|
381
|
-
|
382
|
-
def scope_chain
|
383
|
-
scope ? [[scope]] : [[]]
|
511
|
+
def has_scope?
|
512
|
+
scope
|
384
513
|
end
|
385
514
|
|
386
515
|
def has_inverse?
|
387
516
|
inverse_name
|
388
517
|
end
|
389
518
|
|
390
|
-
def inverse_of
|
391
|
-
return unless inverse_name
|
392
|
-
|
393
|
-
@inverse_of ||= klass._reflect_on_association inverse_name
|
394
|
-
end
|
395
|
-
|
396
519
|
def polymorphic_inverse_of(associated_class)
|
397
520
|
if has_inverse?
|
398
521
|
if inverse_relationship = associated_class._reflect_on_association(options[:inverse_of])
|
@@ -434,72 +557,51 @@ module ActiveRecord
|
|
434
557
|
# Returns +true+ if +self+ is a +has_one+ reflection.
|
435
558
|
def has_one?; false; end
|
436
559
|
|
437
|
-
def association_class
|
438
|
-
case macro
|
439
|
-
when :belongs_to
|
440
|
-
if polymorphic?
|
441
|
-
Associations::BelongsToPolymorphicAssociation
|
442
|
-
else
|
443
|
-
Associations::BelongsToAssociation
|
444
|
-
end
|
445
|
-
when :has_many
|
446
|
-
if options[:through]
|
447
|
-
Associations::HasManyThroughAssociation
|
448
|
-
else
|
449
|
-
Associations::HasManyAssociation
|
450
|
-
end
|
451
|
-
when :has_one
|
452
|
-
if options[:through]
|
453
|
-
Associations::HasOneThroughAssociation
|
454
|
-
else
|
455
|
-
Associations::HasOneAssociation
|
456
|
-
end
|
457
|
-
end
|
458
|
-
end
|
560
|
+
def association_class; raise NotImplementedError; end
|
459
561
|
|
460
562
|
def polymorphic?
|
461
563
|
options[:polymorphic]
|
462
564
|
end
|
463
565
|
|
464
566
|
VALID_AUTOMATIC_INVERSE_MACROS = [:has_many, :has_one, :belongs_to]
|
465
|
-
INVALID_AUTOMATIC_INVERSE_OPTIONS = [:
|
567
|
+
INVALID_AUTOMATIC_INVERSE_OPTIONS = [:through, :foreign_key]
|
466
568
|
|
467
|
-
|
569
|
+
def add_as_source(seed)
|
570
|
+
seed
|
571
|
+
end
|
468
572
|
|
469
|
-
|
470
|
-
|
471
|
-
|
573
|
+
def add_as_polymorphic_through(reflection, seed)
|
574
|
+
seed + [PolymorphicReflection.new(self, reflection)]
|
575
|
+
end
|
472
576
|
|
473
|
-
|
577
|
+
def add_as_through(seed)
|
578
|
+
seed + [self]
|
579
|
+
end
|
580
|
+
|
581
|
+
def extensions
|
582
|
+
Array(options[:extend])
|
583
|
+
end
|
474
584
|
|
585
|
+
private
|
475
586
|
def calculate_constructable(macro, options)
|
476
|
-
|
477
|
-
when :belongs_to
|
478
|
-
!polymorphic?
|
479
|
-
when :has_one
|
480
|
-
!options[:through]
|
481
|
-
else
|
482
|
-
true
|
483
|
-
end
|
587
|
+
true
|
484
588
|
end
|
485
589
|
|
486
590
|
# Attempts to find the inverse association name automatically.
|
487
591
|
# If it cannot find a suitable inverse association name, it returns
|
488
|
-
# nil
|
592
|
+
# +nil+.
|
489
593
|
def inverse_name
|
490
|
-
|
491
|
-
|
492
|
-
nil
|
493
|
-
else
|
494
|
-
@automatic_inverse_of ||= automatic_inverse_of
|
495
|
-
end
|
594
|
+
unless defined?(@inverse_name)
|
595
|
+
@inverse_name = options.fetch(:inverse_of) { automatic_inverse_of }
|
496
596
|
end
|
597
|
+
|
598
|
+
@inverse_name
|
497
599
|
end
|
498
600
|
|
499
|
-
# returns either nil or the inverse association name that it finds.
|
601
|
+
# returns either +nil+ or the inverse association name that it finds.
|
500
602
|
def automatic_inverse_of
|
501
603
|
if can_find_inverse_of_automatically?(self)
|
502
|
-
inverse_name = ActiveSupport::Inflector.underscore(options[:as] || active_record.name).to_sym
|
604
|
+
inverse_name = ActiveSupport::Inflector.underscore(options[:as] || active_record.name.demodulize).to_sym
|
503
605
|
|
504
606
|
begin
|
505
607
|
reflection = klass._reflect_on_association(inverse_name)
|
@@ -510,23 +612,19 @@ module ActiveRecord
|
|
510
612
|
end
|
511
613
|
|
512
614
|
if valid_inverse_reflection?(reflection)
|
513
|
-
|
615
|
+
inverse_name
|
514
616
|
end
|
515
617
|
end
|
516
|
-
|
517
|
-
false
|
518
618
|
end
|
519
619
|
|
520
620
|
# Checks if the inverse reflection that is returned from the
|
521
621
|
# +automatic_inverse_of+ method is a valid reflection. We must
|
522
622
|
# make sure that the reflection's active_record name matches up
|
523
623
|
# with the current reflection's klass name.
|
524
|
-
#
|
525
|
-
# Note: klass will always be valid because when there's a NameError
|
526
|
-
# from calling +klass+, +reflection+ will already be set to false.
|
527
624
|
def valid_inverse_reflection?(reflection)
|
528
625
|
reflection &&
|
529
|
-
|
626
|
+
foreign_key == reflection.foreign_key &&
|
627
|
+
klass <= reflection.active_record &&
|
530
628
|
can_find_inverse_of_automatically?(reflection)
|
531
629
|
end
|
532
630
|
|
@@ -534,9 +632,8 @@ module ActiveRecord
|
|
534
632
|
# us from being able to guess the inverse automatically. First, the
|
535
633
|
# <tt>inverse_of</tt> option cannot be set to false. Second, we must
|
536
634
|
# have <tt>has_many</tt>, <tt>has_one</tt>, <tt>belongs_to</tt> associations.
|
537
|
-
# Third, we must not have options such as <tt>:
|
538
|
-
#
|
539
|
-
# inverse association.
|
635
|
+
# Third, we must not have options such as <tt>:foreign_key</tt>
|
636
|
+
# which prevent us from correctly guessing the inverse association.
|
540
637
|
#
|
541
638
|
# Anything with a scope can additionally ruin our attempt at finding an
|
542
639
|
# inverse, so we exclude reflections with scopes.
|
@@ -566,56 +663,86 @@ module ActiveRecord
|
|
566
663
|
def derive_join_table
|
567
664
|
ModelSchema.derive_join_table_name active_record.table_name, klass.table_name
|
568
665
|
end
|
569
|
-
|
570
|
-
def primary_key(klass)
|
571
|
-
klass.primary_key || raise(UnknownPrimaryKey.new(klass))
|
572
|
-
end
|
573
666
|
end
|
574
667
|
|
575
668
|
class HasManyReflection < AssociationReflection # :nodoc:
|
576
|
-
def initialize(name, scope, options, active_record)
|
577
|
-
super(name, scope, options, active_record)
|
578
|
-
end
|
579
|
-
|
580
669
|
def macro; :has_many; end
|
581
670
|
|
582
671
|
def collection?; true; end
|
583
|
-
end
|
584
672
|
|
585
|
-
|
586
|
-
|
587
|
-
|
673
|
+
def association_class
|
674
|
+
if options[:through]
|
675
|
+
Associations::HasManyThroughAssociation
|
676
|
+
else
|
677
|
+
Associations::HasManyAssociation
|
678
|
+
end
|
588
679
|
end
|
680
|
+
end
|
589
681
|
|
682
|
+
class HasOneReflection < AssociationReflection # :nodoc:
|
590
683
|
def macro; :has_one; end
|
591
684
|
|
592
685
|
def has_one?; true; end
|
593
|
-
end
|
594
686
|
|
595
|
-
|
596
|
-
|
597
|
-
|
687
|
+
def association_class
|
688
|
+
if options[:through]
|
689
|
+
Associations::HasOneThroughAssociation
|
690
|
+
else
|
691
|
+
Associations::HasOneAssociation
|
692
|
+
end
|
598
693
|
end
|
599
694
|
|
695
|
+
private
|
696
|
+
def calculate_constructable(macro, options)
|
697
|
+
!options[:through]
|
698
|
+
end
|
699
|
+
end
|
700
|
+
|
701
|
+
class BelongsToReflection < AssociationReflection # :nodoc:
|
600
702
|
def macro; :belongs_to; end
|
601
703
|
|
602
704
|
def belongs_to?; true; end
|
603
705
|
|
604
|
-
def
|
605
|
-
|
606
|
-
|
706
|
+
def association_class
|
707
|
+
if polymorphic?
|
708
|
+
Associations::BelongsToPolymorphicAssociation
|
709
|
+
else
|
710
|
+
Associations::BelongsToAssociation
|
711
|
+
end
|
607
712
|
end
|
608
713
|
|
609
|
-
|
610
|
-
|
714
|
+
# klass option is necessary to support loading polymorphic associations
|
715
|
+
def association_primary_key(klass = nil)
|
716
|
+
if primary_key = options[:primary_key]
|
717
|
+
@association_primary_key ||= -primary_key.to_s
|
718
|
+
else
|
719
|
+
primary_key(klass || self.klass)
|
720
|
+
end
|
611
721
|
end
|
612
|
-
end
|
613
722
|
|
614
|
-
|
615
|
-
|
616
|
-
|
723
|
+
def join_primary_key(klass = nil)
|
724
|
+
polymorphic? ? association_primary_key(klass) : association_primary_key
|
725
|
+
end
|
726
|
+
|
727
|
+
def join_foreign_key
|
728
|
+
foreign_key
|
729
|
+
end
|
730
|
+
|
731
|
+
def join_foreign_type
|
732
|
+
foreign_type
|
617
733
|
end
|
618
734
|
|
735
|
+
private
|
736
|
+
def can_find_inverse_of_automatically?(_)
|
737
|
+
!polymorphic? && super
|
738
|
+
end
|
739
|
+
|
740
|
+
def calculate_constructable(macro, options)
|
741
|
+
!polymorphic?
|
742
|
+
end
|
743
|
+
end
|
744
|
+
|
745
|
+
class HasAndBelongsToManyReflection < AssociationReflection # :nodoc:
|
619
746
|
def macro; :has_and_belongs_to_many; end
|
620
747
|
|
621
748
|
def collection?
|
@@ -623,19 +750,22 @@ module ActiveRecord
|
|
623
750
|
end
|
624
751
|
end
|
625
752
|
|
626
|
-
# Holds all the
|
753
|
+
# Holds all the metadata about a :through association as it was specified
|
627
754
|
# in the Active Record class.
|
628
755
|
class ThroughReflection < AbstractReflection #:nodoc:
|
629
|
-
|
630
|
-
|
631
|
-
:active_record_primary_key, :type, :to => :source_reflection
|
756
|
+
delegate :foreign_key, :foreign_type, :association_foreign_key, :join_id_for, :type,
|
757
|
+
:active_record_primary_key, :join_foreign_key, to: :source_reflection
|
632
758
|
|
633
759
|
def initialize(delegate_reflection)
|
634
760
|
@delegate_reflection = delegate_reflection
|
635
|
-
@klass
|
761
|
+
@klass = delegate_reflection.options[:anonymous_class]
|
636
762
|
@source_reflection_name = delegate_reflection.options[:source]
|
637
763
|
end
|
638
764
|
|
765
|
+
def through_reflection?
|
766
|
+
true
|
767
|
+
end
|
768
|
+
|
639
769
|
def klass
|
640
770
|
@klass ||= delegate_reflection.compute_class(class_name)
|
641
771
|
end
|
@@ -694,74 +824,35 @@ module ActiveRecord
|
|
694
824
|
# # => [<ActiveRecord::Reflection::ThroughReflection: @delegate_reflection=#<ActiveRecord::Reflection::HasManyReflection: @name=:tags...>,
|
695
825
|
# <ActiveRecord::Reflection::HasManyReflection: @name=:taggings, @options={}, @active_record=Post>]
|
696
826
|
#
|
697
|
-
def
|
698
|
-
|
699
|
-
a = source_reflection.chain
|
700
|
-
b = through_reflection.chain
|
701
|
-
chain = a + b
|
702
|
-
chain[0] = self # Use self so we don't lose the information from :source_type
|
703
|
-
chain
|
704
|
-
end
|
827
|
+
def collect_join_chain
|
828
|
+
collect_join_reflections [self]
|
705
829
|
end
|
706
830
|
|
707
|
-
#
|
708
|
-
#
|
709
|
-
#
|
710
|
-
|
711
|
-
|
712
|
-
|
713
|
-
#
|
714
|
-
# class Article
|
715
|
-
# has_many :comments
|
716
|
-
# has_many :comment_tags, through: :comments, source: :tags
|
717
|
-
# end
|
718
|
-
#
|
719
|
-
# class Comment
|
720
|
-
# has_many :tags
|
721
|
-
# end
|
722
|
-
#
|
723
|
-
# There may be scopes on Person.comment_tags, Article.comment_tags and/or Comment.tags,
|
724
|
-
# but only Comment.tags will be represented in the #chain. So this method creates an array
|
725
|
-
# of scopes corresponding to the chain.
|
726
|
-
def scope_chain
|
727
|
-
@scope_chain ||= begin
|
728
|
-
scope_chain = source_reflection.scope_chain.map(&:dup)
|
729
|
-
|
730
|
-
# Add to it the scope from this reflection (if any)
|
731
|
-
scope_chain.first << scope if scope
|
732
|
-
|
733
|
-
through_scope_chain = through_reflection.scope_chain.map(&:dup)
|
734
|
-
|
735
|
-
if options[:source_type]
|
736
|
-
type = foreign_type
|
737
|
-
source_type = options[:source_type]
|
738
|
-
through_scope_chain.first << lambda { |object|
|
739
|
-
where(type => source_type)
|
740
|
-
}
|
741
|
-
end
|
742
|
-
|
743
|
-
# Recursively fill out the rest of the array from the through reflection
|
744
|
-
scope_chain + through_scope_chain
|
745
|
-
end
|
831
|
+
# This is for clearing cache on the reflection. Useful for tests that need to compare
|
832
|
+
# SQL queries on associations.
|
833
|
+
def clear_association_scope_cache # :nodoc:
|
834
|
+
delegate_reflection.clear_association_scope_cache
|
835
|
+
source_reflection.clear_association_scope_cache
|
836
|
+
through_reflection.clear_association_scope_cache
|
746
837
|
end
|
747
838
|
|
748
|
-
def
|
749
|
-
source_reflection.
|
839
|
+
def scopes
|
840
|
+
source_reflection.scopes + super
|
750
841
|
end
|
751
842
|
|
752
|
-
|
753
|
-
|
754
|
-
|
755
|
-
ActiveRecord::Base.source_macro is deprecated and will be removed
|
756
|
-
without replacement.
|
757
|
-
MSG
|
843
|
+
def join_scopes(table, predicate_builder, klass = self.klass) # :nodoc:
|
844
|
+
source_reflection.join_scopes(table, predicate_builder, klass) + super
|
845
|
+
end
|
758
846
|
|
759
|
-
|
847
|
+
def has_scope?
|
848
|
+
scope || options[:source_type] ||
|
849
|
+
source_reflection.has_scope? ||
|
850
|
+
through_reflection.has_scope?
|
760
851
|
end
|
761
852
|
|
762
853
|
# A through association is nested if there would be more than one join table
|
763
854
|
def nested?
|
764
|
-
|
855
|
+
source_reflection.through_reflection? || through_reflection.through_reflection?
|
765
856
|
end
|
766
857
|
|
767
858
|
# We want to use the klass from this reflection, rather than just delegate straight to
|
@@ -770,7 +861,15 @@ module ActiveRecord
|
|
770
861
|
def association_primary_key(klass = nil)
|
771
862
|
# Get the "actual" source reflection if the immediate source reflection has a
|
772
863
|
# source reflection itself
|
773
|
-
actual_source_reflection.options[:primary_key]
|
864
|
+
if primary_key = actual_source_reflection.options[:primary_key]
|
865
|
+
@association_primary_key ||= -primary_key.to_s
|
866
|
+
else
|
867
|
+
primary_key(klass || self.klass)
|
868
|
+
end
|
869
|
+
end
|
870
|
+
|
871
|
+
def join_primary_key(klass = self.klass)
|
872
|
+
source_reflection.join_primary_key(klass)
|
774
873
|
end
|
775
874
|
|
776
875
|
# Gets an array of possible <tt>:through</tt> source reflection names in both singular and plural form.
|
@@ -791,21 +890,19 @@ module ActiveRecord
|
|
791
890
|
def source_reflection_name # :nodoc:
|
792
891
|
return @source_reflection_name if @source_reflection_name
|
793
892
|
|
794
|
-
names = [name.to_s.singularize, name].collect
|
893
|
+
names = [name.to_s.singularize, name].collect(&:to_sym).uniq
|
795
894
|
names = names.find_all { |n|
|
796
895
|
through_reflection.klass._reflect_on_association(n)
|
797
896
|
}
|
798
897
|
|
799
898
|
if names.length > 1
|
800
|
-
|
801
|
-
|
802
|
-
|
803
|
-
|
804
|
-
|
805
|
-
|
806
|
-
|
807
|
-
" #{macro} :#{name}, #{example_options}\n" \
|
808
|
-
" end"
|
899
|
+
raise AmbiguousSourceReflectionForThroughAssociation.new(
|
900
|
+
active_record.name,
|
901
|
+
macro,
|
902
|
+
name,
|
903
|
+
options,
|
904
|
+
source_reflection_names
|
905
|
+
)
|
809
906
|
end
|
810
907
|
|
811
908
|
@source_reflection_name = names.first
|
@@ -819,13 +916,9 @@ module ActiveRecord
|
|
819
916
|
through_reflection.options
|
820
917
|
end
|
821
918
|
|
822
|
-
def join_id_for(owner) # :nodoc:
|
823
|
-
source_reflection.join_id_for(owner)
|
824
|
-
end
|
825
|
-
|
826
919
|
def check_validity!
|
827
920
|
if through_reflection.nil?
|
828
|
-
raise HasManyThroughAssociationNotFoundError.new(active_record
|
921
|
+
raise HasManyThroughAssociationNotFoundError.new(active_record, self)
|
829
922
|
end
|
830
923
|
|
831
924
|
if through_reflection.polymorphic?
|
@@ -852,20 +945,54 @@ module ActiveRecord
|
|
852
945
|
raise HasOneThroughCantAssociateThroughCollection.new(active_record.name, self, through_reflection)
|
853
946
|
end
|
854
947
|
|
948
|
+
if parent_reflection.nil?
|
949
|
+
reflections = active_record.reflections.keys.map(&:to_sym)
|
950
|
+
|
951
|
+
if reflections.index(through_reflection.name) > reflections.index(name)
|
952
|
+
raise HasManyThroughOrderError.new(active_record.name, self, through_reflection)
|
953
|
+
end
|
954
|
+
end
|
955
|
+
|
855
956
|
check_validity_of_inverse!
|
856
957
|
end
|
857
958
|
|
858
|
-
|
959
|
+
def constraints
|
960
|
+
scope_chain = source_reflection.constraints
|
961
|
+
scope_chain << scope if scope
|
962
|
+
scope_chain
|
963
|
+
end
|
964
|
+
|
965
|
+
def add_as_source(seed)
|
966
|
+
collect_join_reflections seed
|
967
|
+
end
|
968
|
+
|
969
|
+
def add_as_polymorphic_through(reflection, seed)
|
970
|
+
collect_join_reflections(seed + [PolymorphicReflection.new(self, reflection)])
|
971
|
+
end
|
972
|
+
|
973
|
+
def add_as_through(seed)
|
974
|
+
collect_join_reflections(seed + [self])
|
975
|
+
end
|
859
976
|
|
977
|
+
protected
|
860
978
|
def actual_source_reflection # FIXME: this is a horrible name
|
861
|
-
source_reflection.
|
979
|
+
source_reflection.actual_source_reflection
|
862
980
|
end
|
863
981
|
|
864
|
-
|
865
|
-
|
982
|
+
private
|
983
|
+
attr_reader :delegate_reflection
|
984
|
+
|
985
|
+
def collect_join_reflections(seed)
|
986
|
+
a = source_reflection.add_as_source seed
|
987
|
+
if options[:source_type]
|
988
|
+
through_reflection.add_as_polymorphic_through self, a
|
989
|
+
else
|
990
|
+
through_reflection.add_as_through a
|
991
|
+
end
|
866
992
|
end
|
867
993
|
|
868
|
-
|
994
|
+
def inverse_name; delegate_reflection.send(:inverse_name); end
|
995
|
+
|
869
996
|
def derive_class_name
|
870
997
|
# get the class_name of the belongs_to association of the through reflection
|
871
998
|
options[:source_type] || source_reflection.class_name
|
@@ -875,7 +1002,55 @@ module ActiveRecord
|
|
875
1002
|
public_instance_methods
|
876
1003
|
|
877
1004
|
delegate(*delegate_methods, to: :delegate_reflection)
|
1005
|
+
end
|
1006
|
+
|
1007
|
+
class PolymorphicReflection < AbstractReflection # :nodoc:
|
1008
|
+
delegate :klass, :scope, :plural_name, :type, :join_primary_key, :join_foreign_key,
|
1009
|
+
:name, :scope_for, to: :@reflection
|
1010
|
+
|
1011
|
+
def initialize(reflection, previous_reflection)
|
1012
|
+
@reflection = reflection
|
1013
|
+
@previous_reflection = previous_reflection
|
1014
|
+
end
|
1015
|
+
|
1016
|
+
def join_scopes(table, predicate_builder, klass = self.klass) # :nodoc:
|
1017
|
+
scopes = @previous_reflection.join_scopes(table, predicate_builder) + super
|
1018
|
+
scopes << build_scope(table, predicate_builder, klass).instance_exec(nil, &source_type_scope)
|
1019
|
+
end
|
1020
|
+
|
1021
|
+
def constraints
|
1022
|
+
@reflection.constraints + [source_type_scope]
|
1023
|
+
end
|
1024
|
+
|
1025
|
+
private
|
1026
|
+
def source_type_scope
|
1027
|
+
type = @previous_reflection.foreign_type
|
1028
|
+
source_type = @previous_reflection.options[:source_type]
|
1029
|
+
lambda { |object| where(type => source_type) }
|
1030
|
+
end
|
1031
|
+
end
|
1032
|
+
|
1033
|
+
class RuntimeReflection < AbstractReflection # :nodoc:
|
1034
|
+
delegate :scope, :type, :constraints, :join_foreign_key, to: :@reflection
|
1035
|
+
|
1036
|
+
def initialize(reflection, association)
|
1037
|
+
@reflection = reflection
|
1038
|
+
@association = association
|
1039
|
+
end
|
1040
|
+
|
1041
|
+
def klass
|
1042
|
+
@association.klass
|
1043
|
+
end
|
1044
|
+
|
1045
|
+
def aliased_table
|
1046
|
+
klass.arel_table
|
1047
|
+
end
|
1048
|
+
|
1049
|
+
def join_primary_key(klass = self.klass)
|
1050
|
+
@reflection.join_primary_key(klass)
|
1051
|
+
end
|
878
1052
|
|
1053
|
+
def all_includes; yield; end
|
879
1054
|
end
|
880
1055
|
end
|
881
1056
|
end
|