activerecord 4.2.8 → 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 +5 -5
- data/CHANGELOG.md +612 -1583
- 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 +134 -286
- 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 -88
- 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 -244
- 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 -627
- 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 -188
- 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 +5 -3
- 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 +626 -283
- 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 +314 -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 +147 -46
- 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 +330 -197
- 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 +308 -99
- 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 -60
- 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 -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/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 -58
- 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,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module ActiveRecord
|
2
4
|
module Locking
|
3
5
|
# Locking::Pessimistic provides support for row-level locking using
|
@@ -12,9 +14,9 @@ module ActiveRecord
|
|
12
14
|
# of your own such as 'LOCK IN SHARE MODE' or 'FOR UPDATE NOWAIT'. Example:
|
13
15
|
#
|
14
16
|
# Account.transaction do
|
15
|
-
# # select * from accounts where name = 'shugo' limit 1 for update
|
16
|
-
# shugo = Account.
|
17
|
-
# yuko = Account.
|
17
|
+
# # select * from accounts where name = 'shugo' limit 1 for update nowait
|
18
|
+
# shugo = Account.lock("FOR UPDATE NOWAIT").find_by(name: "shugo")
|
19
|
+
# yuko = Account.lock("FOR UPDATE NOWAIT").find_by(name: "yuko")
|
18
20
|
# shugo.balance -= 100
|
19
21
|
# shugo.save!
|
20
22
|
# yuko.balance += 100
|
@@ -51,15 +53,25 @@ module ActiveRecord
|
|
51
53
|
# end
|
52
54
|
#
|
53
55
|
# Database-specific information on row locking:
|
54
|
-
# MySQL:
|
55
|
-
# PostgreSQL:
|
56
|
+
# MySQL: https://dev.mysql.com/doc/refman/5.7/en/innodb-locking-reads.html
|
57
|
+
# PostgreSQL: https://www.postgresql.org/docs/current/interactive/sql-select.html#SQL-FOR-UPDATE-SHARE
|
56
58
|
module Pessimistic
|
57
59
|
# Obtain a row lock on this record. Reloads the record to obtain the requested
|
58
60
|
# lock. Pass an SQL locking clause to append the end of the SELECT statement
|
59
61
|
# or pass true for "FOR UPDATE" (the default, an exclusive row lock). Returns
|
60
62
|
# the locked record.
|
61
63
|
def lock!(lock = true)
|
62
|
-
|
64
|
+
if persisted?
|
65
|
+
if has_changes_to_save?
|
66
|
+
raise(<<-MSG.squish)
|
67
|
+
Locking a record with unpersisted changes is not supported. Use
|
68
|
+
`save` to persist the changes, or `reload` to discard them
|
69
|
+
explicitly.
|
70
|
+
MSG
|
71
|
+
end
|
72
|
+
|
73
|
+
reload(lock: lock)
|
74
|
+
end
|
63
75
|
self
|
64
76
|
end
|
65
77
|
|
@@ -1,7 +1,11 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module ActiveRecord
|
2
4
|
class LogSubscriber < ActiveSupport::LogSubscriber
|
3
5
|
IGNORE_PAYLOAD_NAMES = ["SCHEMA", "EXPLAIN"]
|
4
6
|
|
7
|
+
class_attribute :backtrace_cleaner, default: ActiveSupport::BacktraceCleaner.new
|
8
|
+
|
5
9
|
def self.runtime=(value)
|
6
10
|
ActiveRecord::RuntimeRegistry.sql_runtime = value
|
7
11
|
end
|
@@ -15,25 +19,6 @@ module ActiveRecord
|
|
15
19
|
rt
|
16
20
|
end
|
17
21
|
|
18
|
-
def initialize
|
19
|
-
super
|
20
|
-
@odd = false
|
21
|
-
end
|
22
|
-
|
23
|
-
def render_bind(column, value)
|
24
|
-
if column
|
25
|
-
if column.binary?
|
26
|
-
# This specifically deals with the PG adapter that casts bytea columns into a Hash.
|
27
|
-
value = value[:value] if value.is_a?(Hash)
|
28
|
-
value = value ? "<#{value.bytesize} bytes of binary data>" : "<NULL binary data>"
|
29
|
-
end
|
30
|
-
|
31
|
-
[column.name, value]
|
32
|
-
else
|
33
|
-
[nil, value]
|
34
|
-
end
|
35
|
-
end
|
36
|
-
|
37
22
|
def sql(event)
|
38
23
|
self.class.runtime += event.duration
|
39
24
|
return unless logger.debug?
|
@@ -43,32 +28,90 @@ module ActiveRecord
|
|
43
28
|
return if IGNORE_PAYLOAD_NAMES.include?(payload[:name])
|
44
29
|
|
45
30
|
name = "#{payload[:name]} (#{event.duration.round(1)}ms)"
|
31
|
+
name = "CACHE #{name}" if payload[:cached]
|
46
32
|
sql = payload[:sql]
|
47
33
|
binds = nil
|
48
34
|
|
49
35
|
unless (payload[:binds] || []).empty?
|
50
|
-
|
51
|
-
|
36
|
+
casted_params = type_casted_binds(payload[:type_casted_binds])
|
37
|
+
binds = " " + payload[:binds].zip(casted_params).map { |attr, value|
|
38
|
+
render_bind(attr, value)
|
52
39
|
}.inspect
|
53
40
|
end
|
54
41
|
|
55
|
-
|
56
|
-
|
57
|
-
sql = color(sql, nil, true)
|
58
|
-
else
|
59
|
-
name = color(name, MAGENTA, true)
|
60
|
-
end
|
42
|
+
name = colorize_payload_name(name, payload[:name])
|
43
|
+
sql = color(sql, sql_color(sql), true)
|
61
44
|
|
62
45
|
debug " #{name} #{sql}#{binds}"
|
63
46
|
end
|
64
47
|
|
65
|
-
|
66
|
-
|
67
|
-
|
48
|
+
private
|
49
|
+
def type_casted_binds(casted_binds)
|
50
|
+
casted_binds.respond_to?(:call) ? casted_binds.call : casted_binds
|
51
|
+
end
|
68
52
|
|
69
|
-
|
70
|
-
|
71
|
-
|
53
|
+
def render_bind(attr, value)
|
54
|
+
if attr.is_a?(Array)
|
55
|
+
attr = attr.first
|
56
|
+
elsif attr.type.binary? && attr.value
|
57
|
+
value = "<#{attr.value_for_database.to_s.bytesize} bytes of binary data>"
|
58
|
+
end
|
59
|
+
|
60
|
+
[attr && attr.name, value]
|
61
|
+
end
|
62
|
+
|
63
|
+
def colorize_payload_name(name, payload_name)
|
64
|
+
if payload_name.blank? || payload_name == "SQL" # SQL vs Model Load/Exists
|
65
|
+
color(name, MAGENTA, true)
|
66
|
+
else
|
67
|
+
color(name, CYAN, true)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def sql_color(sql)
|
72
|
+
case sql
|
73
|
+
when /\A\s*rollback/mi
|
74
|
+
RED
|
75
|
+
when /select .*for update/mi, /\A\s*lock/mi
|
76
|
+
WHITE
|
77
|
+
when /\A\s*select/i
|
78
|
+
BLUE
|
79
|
+
when /\A\s*insert/i
|
80
|
+
GREEN
|
81
|
+
when /\A\s*update/i
|
82
|
+
YELLOW
|
83
|
+
when /\A\s*delete/i
|
84
|
+
RED
|
85
|
+
when /transaction\s*\Z/i
|
86
|
+
CYAN
|
87
|
+
else
|
88
|
+
MAGENTA
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def logger
|
93
|
+
ActiveRecord::Base.logger
|
94
|
+
end
|
95
|
+
|
96
|
+
def debug(progname = nil, &block)
|
97
|
+
return unless super
|
98
|
+
|
99
|
+
if ActiveRecord::Base.verbose_query_logs
|
100
|
+
log_query_source
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
def log_query_source
|
105
|
+
source = extract_query_source_location(caller)
|
106
|
+
|
107
|
+
if source
|
108
|
+
logger.debug(" ↳ #{source}")
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
def extract_query_source_location(locations)
|
113
|
+
backtrace_cleaner.clean(locations.lazy).first
|
114
|
+
end
|
72
115
|
end
|
73
116
|
end
|
74
117
|
|
@@ -0,0 +1,75 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_record/middleware/database_selector/resolver"
|
4
|
+
|
5
|
+
module ActiveRecord
|
6
|
+
module Middleware
|
7
|
+
# The DatabaseSelector Middleware provides a framework for automatically
|
8
|
+
# swapping from the primary to the replica database connection. Rails
|
9
|
+
# provides a basic framework to determine when to swap and allows for
|
10
|
+
# applications to write custom strategy classes to override the default
|
11
|
+
# behavior.
|
12
|
+
#
|
13
|
+
# The resolver class defines when the application should switch (i.e. read
|
14
|
+
# from the primary if a write occurred less than 2 seconds ago) and a
|
15
|
+
# resolver context class that sets a value that helps the resolver class
|
16
|
+
# decide when to switch.
|
17
|
+
#
|
18
|
+
# Rails default middleware uses the request's session to set a timestamp
|
19
|
+
# that informs the application when to read from a primary or read from a
|
20
|
+
# replica.
|
21
|
+
#
|
22
|
+
# To use the DatabaseSelector in your application with default settings add
|
23
|
+
# the following options to your environment config:
|
24
|
+
#
|
25
|
+
# config.active_record.database_selector = { delay: 2.seconds }
|
26
|
+
# config.active_record.database_resolver = ActiveRecord::Middleware::DatabaseSelector::Resolver
|
27
|
+
# config.active_record.database_resolver_context = ActiveRecord::Middleware::DatabaseSelector::Resolver::Session
|
28
|
+
#
|
29
|
+
# New applications will include these lines commented out in the production.rb.
|
30
|
+
#
|
31
|
+
# The default behavior can be changed by setting the config options to a
|
32
|
+
# custom class:
|
33
|
+
#
|
34
|
+
# config.active_record.database_selector = { delay: 2.seconds }
|
35
|
+
# config.active_record.database_resolver = MyResolver
|
36
|
+
# config.active_record.database_resolver_context = MyResolver::MySession
|
37
|
+
class DatabaseSelector
|
38
|
+
def initialize(app, resolver_klass = nil, context_klass = nil, options = {})
|
39
|
+
@app = app
|
40
|
+
@resolver_klass = resolver_klass || Resolver
|
41
|
+
@context_klass = context_klass || Resolver::Session
|
42
|
+
@options = options
|
43
|
+
end
|
44
|
+
|
45
|
+
attr_reader :resolver_klass, :context_klass, :options
|
46
|
+
|
47
|
+
# Middleware that determines which database connection to use in a multiple
|
48
|
+
# database application.
|
49
|
+
def call(env)
|
50
|
+
request = ActionDispatch::Request.new(env)
|
51
|
+
|
52
|
+
select_database(request) do
|
53
|
+
@app.call(env)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
private
|
58
|
+
|
59
|
+
def select_database(request, &blk)
|
60
|
+
context = context_klass.call(request)
|
61
|
+
resolver = resolver_klass.call(context, options)
|
62
|
+
|
63
|
+
if reading_request?(request)
|
64
|
+
resolver.read(&blk)
|
65
|
+
else
|
66
|
+
resolver.write(&blk)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def reading_request?(request)
|
71
|
+
request.get? || request.head?
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,92 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_record/middleware/database_selector/resolver/session"
|
4
|
+
|
5
|
+
module ActiveRecord
|
6
|
+
module Middleware
|
7
|
+
class DatabaseSelector
|
8
|
+
# The Resolver class is used by the DatabaseSelector middleware to
|
9
|
+
# determine which database the request should use.
|
10
|
+
#
|
11
|
+
# To change the behavior of the Resolver class in your application,
|
12
|
+
# create a custom resolver class that inherits from
|
13
|
+
# DatabaseSelector::Resolver and implements the methods that need to
|
14
|
+
# be changed.
|
15
|
+
#
|
16
|
+
# By default the Resolver class will send read traffic to the replica
|
17
|
+
# if it's been 2 seconds since the last write.
|
18
|
+
class Resolver # :nodoc:
|
19
|
+
SEND_TO_REPLICA_DELAY = 2.seconds
|
20
|
+
|
21
|
+
def self.call(context, options = {})
|
22
|
+
new(context, options)
|
23
|
+
end
|
24
|
+
|
25
|
+
def initialize(context, options = {})
|
26
|
+
@context = context
|
27
|
+
@options = options
|
28
|
+
@delay = @options && @options[:delay] ? @options[:delay] : SEND_TO_REPLICA_DELAY
|
29
|
+
@instrumenter = ActiveSupport::Notifications.instrumenter
|
30
|
+
end
|
31
|
+
|
32
|
+
attr_reader :context, :delay, :instrumenter
|
33
|
+
|
34
|
+
def read(&blk)
|
35
|
+
if read_from_primary?
|
36
|
+
read_from_primary(&blk)
|
37
|
+
else
|
38
|
+
read_from_replica(&blk)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def write(&blk)
|
43
|
+
write_to_primary(&blk)
|
44
|
+
end
|
45
|
+
|
46
|
+
private
|
47
|
+
|
48
|
+
def read_from_primary(&blk)
|
49
|
+
ActiveRecord::Base.connected_to(role: ActiveRecord::Base.writing_role) do
|
50
|
+
ActiveRecord::Base.connection_handler.while_preventing_writes(true) do
|
51
|
+
instrumenter.instrument("database_selector.active_record.read_from_primary") do
|
52
|
+
yield
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def read_from_replica(&blk)
|
59
|
+
ActiveRecord::Base.connected_to(role: ActiveRecord::Base.reading_role) do
|
60
|
+
instrumenter.instrument("database_selector.active_record.read_from_replica") do
|
61
|
+
yield
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def write_to_primary(&blk)
|
67
|
+
ActiveRecord::Base.connected_to(role: ActiveRecord::Base.writing_role) do
|
68
|
+
ActiveRecord::Base.connection_handler.while_preventing_writes(false) do
|
69
|
+
instrumenter.instrument("database_selector.active_record.wrote_to_primary") do
|
70
|
+
yield
|
71
|
+
ensure
|
72
|
+
context.update_last_write_timestamp
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def read_from_primary?
|
79
|
+
!time_since_last_write_ok?
|
80
|
+
end
|
81
|
+
|
82
|
+
def send_to_replica_delay
|
83
|
+
delay
|
84
|
+
end
|
85
|
+
|
86
|
+
def time_since_last_write_ok?
|
87
|
+
Time.now - context.last_write_timestamp >= send_to_replica_delay
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveRecord
|
4
|
+
module Middleware
|
5
|
+
class DatabaseSelector
|
6
|
+
class Resolver
|
7
|
+
# The session class is used by the DatabaseSelector::Resolver to save
|
8
|
+
# timestamps of the last write in the session.
|
9
|
+
#
|
10
|
+
# The last_write is used to determine whether it's safe to read
|
11
|
+
# from the replica or the request needs to be sent to the primary.
|
12
|
+
class Session # :nodoc:
|
13
|
+
def self.call(request)
|
14
|
+
new(request.session)
|
15
|
+
end
|
16
|
+
|
17
|
+
# Converts time to a timestamp that represents milliseconds since
|
18
|
+
# epoch.
|
19
|
+
def self.convert_time_to_timestamp(time)
|
20
|
+
time.to_i * 1000 + time.usec / 1000
|
21
|
+
end
|
22
|
+
|
23
|
+
# Converts milliseconds since epoch timestamp into a time object.
|
24
|
+
def self.convert_timestamp_to_time(timestamp)
|
25
|
+
timestamp ? Time.at(timestamp / 1000, (timestamp % 1000) * 1000) : Time.at(0)
|
26
|
+
end
|
27
|
+
|
28
|
+
def initialize(session)
|
29
|
+
@session = session
|
30
|
+
end
|
31
|
+
|
32
|
+
attr_reader :session
|
33
|
+
|
34
|
+
def last_write_timestamp
|
35
|
+
self.class.convert_timestamp_to_time(session[:last_write])
|
36
|
+
end
|
37
|
+
|
38
|
+
def update_last_write_timestamp
|
39
|
+
session[:last_write] = self.class.convert_time_to_timestamp(Time.now)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -1,48 +1,190 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "benchmark"
|
4
|
+
require "set"
|
5
|
+
require "zlib"
|
1
6
|
require "active_support/core_ext/module/attribute_accessors"
|
2
|
-
require
|
7
|
+
require "active_support/actionable_error"
|
3
8
|
|
4
9
|
module ActiveRecord
|
5
|
-
class MigrationError < ActiveRecordError#:nodoc:
|
10
|
+
class MigrationError < ActiveRecordError #:nodoc:
|
6
11
|
def initialize(message = nil)
|
7
12
|
message = "\n\n#{message}\n\n" if message
|
8
13
|
super
|
9
14
|
end
|
10
15
|
end
|
11
16
|
|
12
|
-
# Exception that can be raised to stop migrations from
|
17
|
+
# Exception that can be raised to stop migrations from being rolled back.
|
18
|
+
# For example the following migration is not reversible.
|
19
|
+
# Rolling back this migration will raise an ActiveRecord::IrreversibleMigration error.
|
20
|
+
#
|
21
|
+
# class IrreversibleMigrationExample < ActiveRecord::Migration[5.0]
|
22
|
+
# def change
|
23
|
+
# create_table :distributors do |t|
|
24
|
+
# t.string :zipcode
|
25
|
+
# end
|
26
|
+
#
|
27
|
+
# execute <<~SQL
|
28
|
+
# ALTER TABLE distributors
|
29
|
+
# ADD CONSTRAINT zipchk
|
30
|
+
# CHECK (char_length(zipcode) = 5) NO INHERIT;
|
31
|
+
# SQL
|
32
|
+
# end
|
33
|
+
# end
|
34
|
+
#
|
35
|
+
# There are two ways to mitigate this problem.
|
36
|
+
#
|
37
|
+
# 1. Define <tt>#up</tt> and <tt>#down</tt> methods instead of <tt>#change</tt>:
|
38
|
+
#
|
39
|
+
# class ReversibleMigrationExample < ActiveRecord::Migration[5.0]
|
40
|
+
# def up
|
41
|
+
# create_table :distributors do |t|
|
42
|
+
# t.string :zipcode
|
43
|
+
# end
|
44
|
+
#
|
45
|
+
# execute <<~SQL
|
46
|
+
# ALTER TABLE distributors
|
47
|
+
# ADD CONSTRAINT zipchk
|
48
|
+
# CHECK (char_length(zipcode) = 5) NO INHERIT;
|
49
|
+
# SQL
|
50
|
+
# end
|
51
|
+
#
|
52
|
+
# def down
|
53
|
+
# execute <<~SQL
|
54
|
+
# ALTER TABLE distributors
|
55
|
+
# DROP CONSTRAINT zipchk
|
56
|
+
# SQL
|
57
|
+
#
|
58
|
+
# drop_table :distributors
|
59
|
+
# end
|
60
|
+
# end
|
61
|
+
#
|
62
|
+
# 2. Use the #reversible method in <tt>#change</tt> method:
|
63
|
+
#
|
64
|
+
# class ReversibleMigrationExample < ActiveRecord::Migration[5.0]
|
65
|
+
# def change
|
66
|
+
# create_table :distributors do |t|
|
67
|
+
# t.string :zipcode
|
68
|
+
# end
|
69
|
+
#
|
70
|
+
# reversible do |dir|
|
71
|
+
# dir.up do
|
72
|
+
# execute <<~SQL
|
73
|
+
# ALTER TABLE distributors
|
74
|
+
# ADD CONSTRAINT zipchk
|
75
|
+
# CHECK (char_length(zipcode) = 5) NO INHERIT;
|
76
|
+
# SQL
|
77
|
+
# end
|
78
|
+
#
|
79
|
+
# dir.down do
|
80
|
+
# execute <<~SQL
|
81
|
+
# ALTER TABLE distributors
|
82
|
+
# DROP CONSTRAINT zipchk
|
83
|
+
# SQL
|
84
|
+
# end
|
85
|
+
# end
|
86
|
+
# end
|
87
|
+
# end
|
13
88
|
class IrreversibleMigration < MigrationError
|
14
89
|
end
|
15
90
|
|
16
|
-
class DuplicateMigrationVersionError < MigrationError#:nodoc:
|
17
|
-
def initialize(version)
|
18
|
-
|
91
|
+
class DuplicateMigrationVersionError < MigrationError #:nodoc:
|
92
|
+
def initialize(version = nil)
|
93
|
+
if version
|
94
|
+
super("Multiple migrations have the version number #{version}.")
|
95
|
+
else
|
96
|
+
super("Duplicate migration version error.")
|
97
|
+
end
|
19
98
|
end
|
20
99
|
end
|
21
100
|
|
22
|
-
class DuplicateMigrationNameError < MigrationError#:nodoc:
|
23
|
-
def initialize(name)
|
24
|
-
|
101
|
+
class DuplicateMigrationNameError < MigrationError #:nodoc:
|
102
|
+
def initialize(name = nil)
|
103
|
+
if name
|
104
|
+
super("Multiple migrations have the name #{name}.")
|
105
|
+
else
|
106
|
+
super("Duplicate migration name.")
|
107
|
+
end
|
25
108
|
end
|
26
109
|
end
|
27
110
|
|
28
111
|
class UnknownMigrationVersionError < MigrationError #:nodoc:
|
29
|
-
def initialize(version)
|
30
|
-
|
112
|
+
def initialize(version = nil)
|
113
|
+
if version
|
114
|
+
super("No migration with version number #{version}.")
|
115
|
+
else
|
116
|
+
super("Unknown migration version.")
|
117
|
+
end
|
31
118
|
end
|
32
119
|
end
|
33
120
|
|
34
|
-
class IllegalMigrationNameError < MigrationError#:nodoc:
|
35
|
-
def initialize(name)
|
36
|
-
|
121
|
+
class IllegalMigrationNameError < MigrationError #:nodoc:
|
122
|
+
def initialize(name = nil)
|
123
|
+
if name
|
124
|
+
super("Illegal name for migration file: #{name}\n\t(only lower case letters, numbers, and '_' allowed).")
|
125
|
+
else
|
126
|
+
super("Illegal name for migration.")
|
127
|
+
end
|
37
128
|
end
|
38
129
|
end
|
39
130
|
|
40
|
-
class PendingMigrationError < MigrationError#:nodoc:
|
131
|
+
class PendingMigrationError < MigrationError #:nodoc:
|
132
|
+
include ActiveSupport::ActionableError
|
133
|
+
|
134
|
+
action "Run pending migrations" do
|
135
|
+
ActiveRecord::Tasks::DatabaseTasks.migrate
|
136
|
+
end
|
137
|
+
|
138
|
+
def initialize(message = nil)
|
139
|
+
if !message && defined?(Rails.env)
|
140
|
+
super("Migrations are pending. To resolve this issue, run:\n\n rails db:migrate RAILS_ENV=#{::Rails.env}")
|
141
|
+
elsif !message
|
142
|
+
super("Migrations are pending. To resolve this issue, run:\n\n rails db:migrate")
|
143
|
+
else
|
144
|
+
super
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
class ConcurrentMigrationError < MigrationError #:nodoc:
|
150
|
+
DEFAULT_MESSAGE = "Cannot run migrations because another migration process is currently running."
|
151
|
+
RELEASE_LOCK_FAILED_MESSAGE = "Failed to release advisory lock"
|
152
|
+
|
153
|
+
def initialize(message = DEFAULT_MESSAGE)
|
154
|
+
super
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
class NoEnvironmentInSchemaError < MigrationError #:nodoc:
|
41
159
|
def initialize
|
160
|
+
msg = "Environment data not found in the schema. To resolve this issue, run: \n\n rails db:environment:set"
|
161
|
+
if defined?(Rails.env)
|
162
|
+
super("#{msg} RAILS_ENV=#{::Rails.env}")
|
163
|
+
else
|
164
|
+
super(msg)
|
165
|
+
end
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
class ProtectedEnvironmentError < ActiveRecordError #:nodoc:
|
170
|
+
def initialize(env = "production")
|
171
|
+
msg = +"You are attempting to run a destructive action against your '#{env}' database.\n"
|
172
|
+
msg << "If you are sure you want to continue, run the same command with the environment variable:\n"
|
173
|
+
msg << "DISABLE_DATABASE_ENVIRONMENT_CHECK=1"
|
174
|
+
super(msg)
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
class EnvironmentMismatchError < ActiveRecordError
|
179
|
+
def initialize(current: nil, stored: nil)
|
180
|
+
msg = +"You are attempting to modify a database that was last run in `#{ stored }` environment.\n"
|
181
|
+
msg << "You are running in `#{ current }` environment. "
|
182
|
+
msg << "If you are sure you want to continue, first set the environment using:\n\n"
|
183
|
+
msg << " rails db:environment:set"
|
42
184
|
if defined?(Rails.env)
|
43
|
-
super("
|
185
|
+
super("#{msg} RAILS_ENV=#{::Rails.env}\n\n")
|
44
186
|
else
|
45
|
-
super("
|
187
|
+
super("#{msg}\n\n")
|
46
188
|
end
|
47
189
|
end
|
48
190
|
end
|
@@ -59,7 +201,7 @@ module ActiveRecord
|
|
59
201
|
#
|
60
202
|
# Example of a simple migration:
|
61
203
|
#
|
62
|
-
# class AddSsl < ActiveRecord::Migration
|
204
|
+
# class AddSsl < ActiveRecord::Migration[5.0]
|
63
205
|
# def up
|
64
206
|
# add_column :accounts, :ssl_enabled, :boolean, default: true
|
65
207
|
# end
|
@@ -79,7 +221,7 @@ module ActiveRecord
|
|
79
221
|
#
|
80
222
|
# Example of a more complex migration that also needs to initialize data:
|
81
223
|
#
|
82
|
-
# class AddSystemSettings < ActiveRecord::Migration
|
224
|
+
# class AddSystemSettings < ActiveRecord::Migration[5.0]
|
83
225
|
# def up
|
84
226
|
# create_table :system_settings do |t|
|
85
227
|
# t.string :name
|
@@ -106,17 +248,18 @@ module ActiveRecord
|
|
106
248
|
#
|
107
249
|
# == Available transformations
|
108
250
|
#
|
251
|
+
# === Creation
|
252
|
+
#
|
253
|
+
# * <tt>create_join_table(table_1, table_2, options)</tt>: Creates a join
|
254
|
+
# table having its name as the lexical order of the first two
|
255
|
+
# arguments. See
|
256
|
+
# ActiveRecord::ConnectionAdapters::SchemaStatements#create_join_table for
|
257
|
+
# details.
|
109
258
|
# * <tt>create_table(name, options)</tt>: Creates a table called +name+ and
|
110
259
|
# makes the table object available to a block that can then add columns to it,
|
111
260
|
# following the same format as +add_column+. See example above. The options hash
|
112
261
|
# is for fragments like "DEFAULT CHARSET=UTF-8" that are appended to the create
|
113
262
|
# table definition.
|
114
|
-
# * <tt>drop_table(name)</tt>: Drops the table called +name+.
|
115
|
-
# * <tt>change_table(name, options)</tt>: Allows to make column alterations to
|
116
|
-
# the table called +name+. It makes the table object available to a block that
|
117
|
-
# can then add/remove columns, indexes or foreign keys to it.
|
118
|
-
# * <tt>rename_table(old_name, new_name)</tt>: Renames the table called +old_name+
|
119
|
-
# to +new_name+.
|
120
263
|
# * <tt>add_column(table_name, column_name, type, options)</tt>: Adds a new column
|
121
264
|
# to the table called +table_name+
|
122
265
|
# named +column_name+ specified to be one of the following types:
|
@@ -127,21 +270,61 @@ module ActiveRecord
|
|
127
270
|
# Other options include <tt>:limit</tt> and <tt>:null</tt> (e.g.
|
128
271
|
# <tt>{ limit: 50, null: false }</tt>) -- see
|
129
272
|
# ActiveRecord::ConnectionAdapters::TableDefinition#column for details.
|
130
|
-
# * <tt>
|
131
|
-
#
|
132
|
-
#
|
133
|
-
# the column to a different type using the same parameters as add_column.
|
134
|
-
# * <tt>remove_column(table_name, column_name, type, options)</tt>: Removes the column
|
135
|
-
# named +column_name+ from the table called +table_name+.
|
273
|
+
# * <tt>add_foreign_key(from_table, to_table, options)</tt>: Adds a new
|
274
|
+
# foreign key. +from_table+ is the table with the key column, +to_table+ contains
|
275
|
+
# the referenced primary key.
|
136
276
|
# * <tt>add_index(table_name, column_names, options)</tt>: Adds a new index
|
137
277
|
# with the name of the column. Other options include
|
138
278
|
# <tt>:name</tt>, <tt>:unique</tt> (e.g.
|
139
279
|
# <tt>{ name: 'users_name_index', unique: true }</tt>) and <tt>:order</tt>
|
140
280
|
# (e.g. <tt>{ order: { name: :desc } }</tt>).
|
141
|
-
# * <tt>
|
142
|
-
#
|
281
|
+
# * <tt>add_reference(:table_name, :reference_name)</tt>: Adds a new column
|
282
|
+
# +reference_name_id+ by default an integer. See
|
283
|
+
# ActiveRecord::ConnectionAdapters::SchemaStatements#add_reference for details.
|
284
|
+
# * <tt>add_timestamps(table_name, options)</tt>: Adds timestamps (+created_at+
|
285
|
+
# and +updated_at+) columns to +table_name+.
|
286
|
+
#
|
287
|
+
# === Modification
|
288
|
+
#
|
289
|
+
# * <tt>change_column(table_name, column_name, type, options)</tt>: Changes
|
290
|
+
# the column to a different type using the same parameters as add_column.
|
291
|
+
# * <tt>change_column_default(table_name, column_name, default_or_changes)</tt>:
|
292
|
+
# Sets a default value for +column_name+ defined by +default_or_changes+ on
|
293
|
+
# +table_name+. Passing a hash containing <tt>:from</tt> and <tt>:to</tt>
|
294
|
+
# as +default_or_changes+ will make this change reversible in the migration.
|
295
|
+
# * <tt>change_column_null(table_name, column_name, null, default = nil)</tt>:
|
296
|
+
# Sets or removes a +NOT NULL+ constraint on +column_name+. The +null+ flag
|
297
|
+
# indicates whether the value can be +NULL+. See
|
298
|
+
# ActiveRecord::ConnectionAdapters::SchemaStatements#change_column_null for
|
299
|
+
# details.
|
300
|
+
# * <tt>change_table(name, options)</tt>: Allows to make column alterations to
|
301
|
+
# the table called +name+. It makes the table object available to a block that
|
302
|
+
# can then add/remove columns, indexes or foreign keys to it.
|
303
|
+
# * <tt>rename_column(table_name, column_name, new_column_name)</tt>: Renames
|
304
|
+
# a column but keeps the type and content.
|
305
|
+
# * <tt>rename_index(table_name, old_name, new_name)</tt>: Renames an index.
|
306
|
+
# * <tt>rename_table(old_name, new_name)</tt>: Renames the table called +old_name+
|
307
|
+
# to +new_name+.
|
308
|
+
#
|
309
|
+
# === Deletion
|
310
|
+
#
|
311
|
+
# * <tt>drop_table(name)</tt>: Drops the table called +name+.
|
312
|
+
# * <tt>drop_join_table(table_1, table_2, options)</tt>: Drops the join table
|
313
|
+
# specified by the given arguments.
|
314
|
+
# * <tt>remove_column(table_name, column_name, type, options)</tt>: Removes the column
|
315
|
+
# named +column_name+ from the table called +table_name+.
|
316
|
+
# * <tt>remove_columns(table_name, *column_names)</tt>: Removes the given
|
317
|
+
# columns from the table definition.
|
318
|
+
# * <tt>remove_foreign_key(from_table, to_table = nil, **options)</tt>: Removes the
|
319
|
+
# given foreign key from the table called +table_name+.
|
320
|
+
# * <tt>remove_index(table_name, column: column_names)</tt>: Removes the index
|
321
|
+
# specified by +column_names+.
|
143
322
|
# * <tt>remove_index(table_name, name: index_name)</tt>: Removes the index
|
144
323
|
# specified by +index_name+.
|
324
|
+
# * <tt>remove_reference(table_name, ref_name, options)</tt>: Removes the
|
325
|
+
# reference(s) on +table_name+ specified by +ref_name+.
|
326
|
+
# * <tt>remove_timestamps(table_name, options)</tt>: Removes the timestamp
|
327
|
+
# columns (+created_at+ and +updated_at+) from the table definition.
|
145
328
|
#
|
146
329
|
# == Irreversible transformations
|
147
330
|
#
|
@@ -165,24 +348,24 @@ module ActiveRecord
|
|
165
348
|
#
|
166
349
|
# rails generate migration add_fieldname_to_tablename fieldname:string
|
167
350
|
#
|
168
|
-
# This will generate the file <tt>timestamp_add_fieldname_to_tablename</tt>, which will look like this:
|
169
|
-
# class AddFieldnameToTablename < ActiveRecord::Migration
|
351
|
+
# This will generate the file <tt>timestamp_add_fieldname_to_tablename.rb</tt>, which will look like this:
|
352
|
+
# class AddFieldnameToTablename < ActiveRecord::Migration[5.0]
|
170
353
|
# def change
|
171
|
-
# add_column :tablenames, :
|
354
|
+
# add_column :tablenames, :fieldname, :string
|
172
355
|
# end
|
173
356
|
# end
|
174
357
|
#
|
175
358
|
# To run migrations against the currently configured database, use
|
176
|
-
# <tt>
|
359
|
+
# <tt>rails db:migrate</tt>. This will update the database by running all of the
|
177
360
|
# pending migrations, creating the <tt>schema_migrations</tt> table
|
178
361
|
# (see "About the schema_migrations table" section below) if missing. It will also
|
179
|
-
# invoke the db:schema:dump
|
362
|
+
# invoke the db:schema:dump command, which will update your db/schema.rb file
|
180
363
|
# to match the structure of your database.
|
181
364
|
#
|
182
365
|
# To roll the database back to a previous migration version, use
|
183
|
-
# <tt>
|
366
|
+
# <tt>rails db:rollback VERSION=X</tt> where <tt>X</tt> is the version to which
|
184
367
|
# you wish to downgrade. Alternatively, you can also use the STEP option if you
|
185
|
-
# wish to rollback last few migrations. <tt>
|
368
|
+
# wish to rollback last few migrations. <tt>rails db:rollback STEP=2</tt> will rollback
|
186
369
|
# the latest two migrations.
|
187
370
|
#
|
188
371
|
# If any of the migrations throw an <tt>ActiveRecord::IrreversibleMigration</tt> exception,
|
@@ -197,7 +380,7 @@ module ActiveRecord
|
|
197
380
|
#
|
198
381
|
# Not all migrations change the schema. Some just fix the data:
|
199
382
|
#
|
200
|
-
# class RemoveEmptyTags < ActiveRecord::Migration
|
383
|
+
# class RemoveEmptyTags < ActiveRecord::Migration[5.0]
|
201
384
|
# def up
|
202
385
|
# Tag.all.each { |tag| tag.destroy if tag.pages.empty? }
|
203
386
|
# end
|
@@ -210,7 +393,7 @@ module ActiveRecord
|
|
210
393
|
#
|
211
394
|
# Others remove columns when they migrate up instead of down:
|
212
395
|
#
|
213
|
-
# class RemoveUnnecessaryItemAttributes < ActiveRecord::Migration
|
396
|
+
# class RemoveUnnecessaryItemAttributes < ActiveRecord::Migration[5.0]
|
214
397
|
# def up
|
215
398
|
# remove_column :items, :incomplete_items_count
|
216
399
|
# remove_column :items, :completed_items_count
|
@@ -224,7 +407,7 @@ module ActiveRecord
|
|
224
407
|
#
|
225
408
|
# And sometimes you need to do something in SQL not abstracted directly by migrations:
|
226
409
|
#
|
227
|
-
# class MakeJoinUnique < ActiveRecord::Migration
|
410
|
+
# class MakeJoinUnique < ActiveRecord::Migration[5.0]
|
228
411
|
# def up
|
229
412
|
# execute "ALTER TABLE `pages_linked_pages` ADD UNIQUE `page_id_linked_page_id` (`page_id`,`linked_page_id`)"
|
230
413
|
# end
|
@@ -241,7 +424,7 @@ module ActiveRecord
|
|
241
424
|
# <tt>Base#reset_column_information</tt> in order to ensure that the model has the
|
242
425
|
# latest column data from after the new column was added. Example:
|
243
426
|
#
|
244
|
-
# class AddPeopleSalary < ActiveRecord::Migration
|
427
|
+
# class AddPeopleSalary < ActiveRecord::Migration[5.0]
|
245
428
|
# def up
|
246
429
|
# add_column :people, :salary, :integer
|
247
430
|
# Person.reset_column_information
|
@@ -275,21 +458,6 @@ module ActiveRecord
|
|
275
458
|
# The phrase "Updating salaries..." would then be printed, along with the
|
276
459
|
# benchmark for the block when the block completes.
|
277
460
|
#
|
278
|
-
# == About the schema_migrations table
|
279
|
-
#
|
280
|
-
# Rails versions 2.0 and prior used to create a table called
|
281
|
-
# <tt>schema_info</tt> when using migrations. This table contained the
|
282
|
-
# version of the schema as of the last applied migration.
|
283
|
-
#
|
284
|
-
# Starting with Rails 2.1, the <tt>schema_info</tt> table is
|
285
|
-
# (automatically) replaced by the <tt>schema_migrations</tt> table, which
|
286
|
-
# contains the version numbers of all the migrations applied.
|
287
|
-
#
|
288
|
-
# As a result, it is now possible to add migration files that are numbered
|
289
|
-
# lower than the current schema version: when migrating up, those
|
290
|
-
# never-applied "interleaved" migrations will be automatically applied, and
|
291
|
-
# when migrating down, never-applied "interleaved" migrations will be skipped.
|
292
|
-
#
|
293
461
|
# == Timestamped Migrations
|
294
462
|
#
|
295
463
|
# By default, Rails generates migrations that look like:
|
@@ -314,7 +482,7 @@ module ActiveRecord
|
|
314
482
|
# To define a reversible migration, define the +change+ method in your
|
315
483
|
# migration like this:
|
316
484
|
#
|
317
|
-
# class TenderloveMigration < ActiveRecord::Migration
|
485
|
+
# class TenderloveMigration < ActiveRecord::Migration[5.0]
|
318
486
|
# def change
|
319
487
|
# create_table(:horses) do |t|
|
320
488
|
# t.column :content, :text
|
@@ -326,9 +494,9 @@ module ActiveRecord
|
|
326
494
|
# This migration will create the horses table for you on the way up, and
|
327
495
|
# automatically figure out how to drop the table on the way down.
|
328
496
|
#
|
329
|
-
# Some commands
|
330
|
-
#
|
331
|
-
#
|
497
|
+
# Some commands cannot be reversed. If you care to define how to move up
|
498
|
+
# and down in these cases, you should define the +up+ and +down+ methods
|
499
|
+
# as before.
|
332
500
|
#
|
333
501
|
# If a command cannot be reversed, an
|
334
502
|
# <tt>ActiveRecord::IrreversibleMigration</tt> exception will be raised when
|
@@ -344,7 +512,7 @@ module ActiveRecord
|
|
344
512
|
# can't execute inside a transaction though, and for these situations
|
345
513
|
# you can turn the automatic transactions off.
|
346
514
|
#
|
347
|
-
# class ChangeEnum < ActiveRecord::Migration
|
515
|
+
# class ChangeEnum < ActiveRecord::Migration[5.0]
|
348
516
|
# disable_ddl_transaction!
|
349
517
|
#
|
350
518
|
# def up
|
@@ -355,11 +523,35 @@ module ActiveRecord
|
|
355
523
|
# Remember that you can still open your own transactions, even if you
|
356
524
|
# are in a Migration with <tt>self.disable_ddl_transaction!</tt>.
|
357
525
|
class Migration
|
358
|
-
autoload :CommandRecorder,
|
526
|
+
autoload :CommandRecorder, "active_record/migration/command_recorder"
|
527
|
+
autoload :Compatibility, "active_record/migration/compatibility"
|
359
528
|
|
529
|
+
# This must be defined before the inherited hook, below
|
530
|
+
class Current < Migration #:nodoc:
|
531
|
+
end
|
532
|
+
|
533
|
+
def self.inherited(subclass) #:nodoc:
|
534
|
+
super
|
535
|
+
if subclass.superclass == Migration
|
536
|
+
raise StandardError, "Directly inheriting from ActiveRecord::Migration is not supported. " \
|
537
|
+
"Please specify the Rails release the migration was written for:\n" \
|
538
|
+
"\n" \
|
539
|
+
" class #{subclass} < ActiveRecord::Migration[4.2]"
|
540
|
+
end
|
541
|
+
end
|
542
|
+
|
543
|
+
def self.[](version)
|
544
|
+
Compatibility.find(version)
|
545
|
+
end
|
546
|
+
|
547
|
+
def self.current_version
|
548
|
+
ActiveRecord::VERSION::STRING.to_f
|
549
|
+
end
|
550
|
+
|
551
|
+
MigrationFilenameRegexp = /\A([0-9]+)_([_a-z0-9]*)\.?([_a-z0-9]*)?\.rb\z/ #:nodoc:
|
360
552
|
|
361
553
|
# This class is used to verify that all migrations have been run before
|
362
|
-
# loading a web page if config.active_record.migration_error is set to :page_load
|
554
|
+
# loading a web page if <tt>config.active_record.migration_error</tt> is set to :page_load
|
363
555
|
class CheckPending
|
364
556
|
def initialize(app)
|
365
557
|
@app = app
|
@@ -367,53 +559,65 @@ module ActiveRecord
|
|
367
559
|
end
|
368
560
|
|
369
561
|
def call(env)
|
370
|
-
|
371
|
-
|
372
|
-
|
373
|
-
|
374
|
-
@last_check = mtime
|
375
|
-
end
|
562
|
+
mtime = ActiveRecord::Base.connection.migration_context.last_migration.mtime.to_i
|
563
|
+
if @last_check < mtime
|
564
|
+
ActiveRecord::Migration.check_pending!(connection)
|
565
|
+
@last_check = mtime
|
376
566
|
end
|
377
567
|
@app.call(env)
|
378
568
|
end
|
379
569
|
|
380
570
|
private
|
381
571
|
|
382
|
-
|
383
|
-
|
384
|
-
|
572
|
+
def connection
|
573
|
+
ActiveRecord::Base.connection
|
574
|
+
end
|
385
575
|
end
|
386
576
|
|
387
577
|
class << self
|
388
|
-
attr_accessor :delegate
|
389
|
-
attr_accessor :disable_ddl_transaction
|
578
|
+
attr_accessor :delegate #:nodoc:
|
579
|
+
attr_accessor :disable_ddl_transaction #:nodoc:
|
580
|
+
|
581
|
+
def nearest_delegate #:nodoc:
|
582
|
+
delegate || superclass.nearest_delegate
|
583
|
+
end
|
390
584
|
|
585
|
+
# Raises <tt>ActiveRecord::PendingMigrationError</tt> error if any migrations are pending.
|
391
586
|
def check_pending!(connection = Base.connection)
|
392
|
-
raise ActiveRecord::PendingMigrationError if
|
587
|
+
raise ActiveRecord::PendingMigrationError if connection.migration_context.needs_migration?
|
393
588
|
end
|
394
589
|
|
395
590
|
def load_schema_if_pending!
|
396
|
-
|
397
|
-
|
398
|
-
|
399
|
-
|
591
|
+
current_config = Base.connection_config
|
592
|
+
all_configs = ActiveRecord::Base.configurations.configs_for(env_name: Rails.env)
|
593
|
+
|
594
|
+
needs_update = !all_configs.all? do |db_config|
|
595
|
+
Tasks::DatabaseTasks.schema_up_to_date?(db_config.config, ActiveRecord::Base.schema_format, nil, Rails.env, db_config.spec_name)
|
596
|
+
end
|
597
|
+
|
598
|
+
if needs_update
|
599
|
+
# Roundtrip to Rake to allow plugins to hook into database initialization.
|
600
|
+
root = defined?(ENGINE_ROOT) ? ENGINE_ROOT : Rails.root
|
601
|
+
FileUtils.cd(root) do
|
400
602
|
Base.clear_all_connections!
|
401
|
-
system("bin/
|
402
|
-
# Establish a new connection, the old database may be gone (db:test:prepare uses purge)
|
403
|
-
Base.establish_connection(current_config)
|
603
|
+
system("bin/rails db:test:prepare")
|
404
604
|
end
|
405
|
-
check_pending!
|
406
605
|
end
|
606
|
+
|
607
|
+
# Establish a new connection, the old database may be gone (db:test:prepare uses purge)
|
608
|
+
Base.establish_connection(current_config)
|
609
|
+
|
610
|
+
check_pending!
|
407
611
|
end
|
408
612
|
|
409
|
-
def maintain_test_schema!
|
613
|
+
def maintain_test_schema! #:nodoc:
|
410
614
|
if ActiveRecord::Base.maintain_test_schema
|
411
615
|
suppress_messages { load_schema_if_pending! }
|
412
616
|
end
|
413
617
|
end
|
414
618
|
|
415
|
-
def method_missing(name, *args, &block)
|
416
|
-
|
619
|
+
def method_missing(name, *args, &block) #:nodoc:
|
620
|
+
nearest_delegate.send(name, *args, &block)
|
417
621
|
end
|
418
622
|
|
419
623
|
def migrate(direction)
|
@@ -429,7 +633,7 @@ module ActiveRecord
|
|
429
633
|
end
|
430
634
|
end
|
431
635
|
|
432
|
-
def disable_ddl_transaction
|
636
|
+
def disable_ddl_transaction #:nodoc:
|
433
637
|
self.class.disable_ddl_transaction
|
434
638
|
end
|
435
639
|
|
@@ -453,7 +657,7 @@ module ActiveRecord
|
|
453
657
|
# and create the table 'apples' on the way up, and the reverse
|
454
658
|
# on the way down.
|
455
659
|
#
|
456
|
-
# class FixTLMigration < ActiveRecord::Migration
|
660
|
+
# class FixTLMigration < ActiveRecord::Migration[5.0]
|
457
661
|
# def change
|
458
662
|
# revert do
|
459
663
|
# create_table(:horses) do |t|
|
@@ -470,9 +674,9 @@ module ActiveRecord
|
|
470
674
|
# Or equivalently, if +TenderloveMigration+ is defined as in the
|
471
675
|
# documentation for Migration:
|
472
676
|
#
|
473
|
-
# require_relative '
|
677
|
+
# require_relative '20121212123456_tenderlove_migration'
|
474
678
|
#
|
475
|
-
# class FixupTLMigration < ActiveRecord::Migration
|
679
|
+
# class FixupTLMigration < ActiveRecord::Migration[5.0]
|
476
680
|
# def change
|
477
681
|
# revert TenderloveMigration
|
478
682
|
#
|
@@ -486,27 +690,25 @@ module ActiveRecord
|
|
486
690
|
def revert(*migration_classes)
|
487
691
|
run(*migration_classes.reverse, revert: true) unless migration_classes.empty?
|
488
692
|
if block_given?
|
489
|
-
if
|
490
|
-
|
693
|
+
if connection.respond_to? :revert
|
694
|
+
connection.revert { yield }
|
491
695
|
else
|
492
|
-
recorder =
|
696
|
+
recorder = command_recorder
|
493
697
|
@connection = recorder
|
494
698
|
suppress_messages do
|
495
|
-
|
699
|
+
connection.revert { yield }
|
496
700
|
end
|
497
701
|
@connection = recorder.delegate
|
498
|
-
recorder.
|
499
|
-
send(cmd, *args, &block)
|
500
|
-
end
|
702
|
+
recorder.replay(self)
|
501
703
|
end
|
502
704
|
end
|
503
705
|
end
|
504
706
|
|
505
707
|
def reverting?
|
506
|
-
|
708
|
+
connection.respond_to?(:reverting) && connection.reverting
|
507
709
|
end
|
508
710
|
|
509
|
-
|
711
|
+
ReversibleBlockHelper = Struct.new(:reverting) do #:nodoc:
|
510
712
|
def up
|
511
713
|
yield unless reverting
|
512
714
|
end
|
@@ -525,7 +727,7 @@ module ActiveRecord
|
|
525
727
|
# when the three columns 'first_name', 'last_name' and 'full_name' exist,
|
526
728
|
# even when migrating down:
|
527
729
|
#
|
528
|
-
# class SplitNameMigration < ActiveRecord::Migration
|
730
|
+
# class SplitNameMigration < ActiveRecord::Migration[5.0]
|
529
731
|
# def change
|
530
732
|
# add_column :users, :first_name, :string
|
531
733
|
# add_column :users, :last_name, :string
|
@@ -544,7 +746,25 @@ module ActiveRecord
|
|
544
746
|
# end
|
545
747
|
def reversible
|
546
748
|
helper = ReversibleBlockHelper.new(reverting?)
|
547
|
-
execute_block{ yield helper }
|
749
|
+
execute_block { yield helper }
|
750
|
+
end
|
751
|
+
|
752
|
+
# Used to specify an operation that is only run when migrating up
|
753
|
+
# (for example, populating a new column with its initial values).
|
754
|
+
#
|
755
|
+
# In the following example, the new column +published+ will be given
|
756
|
+
# the value +true+ for all existing records.
|
757
|
+
#
|
758
|
+
# class AddPublishedToPosts < ActiveRecord::Migration[5.2]
|
759
|
+
# def change
|
760
|
+
# add_column :posts, :published, :boolean, default: false
|
761
|
+
# up_only do
|
762
|
+
# execute "update posts set published = 'true'"
|
763
|
+
# end
|
764
|
+
# end
|
765
|
+
# end
|
766
|
+
def up_only
|
767
|
+
execute_block { yield } unless reverting?
|
548
768
|
end
|
549
769
|
|
550
770
|
# Runs the given migration classes.
|
@@ -560,7 +780,7 @@ module ActiveRecord
|
|
560
780
|
revert { run(*migration_classes, direction: dir, revert: true) }
|
561
781
|
else
|
562
782
|
migration_classes.each do |migration_class|
|
563
|
-
migration_class.new.exec_migration(
|
783
|
+
migration_class.new.exec_migration(connection, dir)
|
564
784
|
end
|
565
785
|
end
|
566
786
|
end
|
@@ -586,7 +806,7 @@ module ActiveRecord
|
|
586
806
|
when :down then announce "reverting"
|
587
807
|
end
|
588
808
|
|
589
|
-
time
|
809
|
+
time = nil
|
590
810
|
ActiveRecord::Base.connection_pool.with_connection do |conn|
|
591
811
|
time = Benchmark.measure do
|
592
812
|
exec_migration(conn, direction)
|
@@ -614,7 +834,7 @@ module ActiveRecord
|
|
614
834
|
@connection = nil
|
615
835
|
end
|
616
836
|
|
617
|
-
def write(text="")
|
837
|
+
def write(text = "")
|
618
838
|
puts(text) if verbose
|
619
839
|
end
|
620
840
|
|
@@ -624,10 +844,14 @@ module ActiveRecord
|
|
624
844
|
write "== %s %s" % [text, "=" * length]
|
625
845
|
end
|
626
846
|
|
627
|
-
|
847
|
+
# Takes a message argument and outputs it as is.
|
848
|
+
# A second boolean argument can be passed to specify whether to indent or not.
|
849
|
+
def say(message, subitem = false)
|
628
850
|
write "#{subitem ? " ->" : "--"} #{message}"
|
629
851
|
end
|
630
852
|
|
853
|
+
# Outputs text along with how long it took to run its block.
|
854
|
+
# If the block returns an integer it assumes it is the number of rows affected.
|
631
855
|
def say_with_time(message)
|
632
856
|
say(message)
|
633
857
|
result = nil
|
@@ -637,6 +861,7 @@ module ActiveRecord
|
|
637
861
|
result
|
638
862
|
end
|
639
863
|
|
864
|
+
# Takes a block as an argument and suppresses any output generated by the block.
|
640
865
|
def suppress_messages
|
641
866
|
save, self.verbose = verbose, false
|
642
867
|
yield
|
@@ -649,10 +874,10 @@ module ActiveRecord
|
|
649
874
|
end
|
650
875
|
|
651
876
|
def method_missing(method, *arguments, &block)
|
652
|
-
arg_list = arguments.map
|
877
|
+
arg_list = arguments.map(&:inspect) * ", "
|
653
878
|
|
654
879
|
say_with_time "#{method}(#{arg_list})" do
|
655
|
-
unless
|
880
|
+
unless connection.respond_to? :revert
|
656
881
|
unless arguments.empty? || [:execute, :enable_extension, :disable_extension].include?(method)
|
657
882
|
arguments[0] = proper_table_name(arguments.first, table_name_options)
|
658
883
|
if [:rename_table, :add_foreign_key].include?(method) ||
|
@@ -668,26 +893,29 @@ module ActiveRecord
|
|
668
893
|
|
669
894
|
def copy(destination, sources, options = {})
|
670
895
|
copied = []
|
896
|
+
schema_migration = options[:schema_migration] || ActiveRecord::SchemaMigration
|
671
897
|
|
672
898
|
FileUtils.mkdir_p(destination) unless File.exist?(destination)
|
673
899
|
|
674
|
-
destination_migrations = ActiveRecord::
|
900
|
+
destination_migrations = ActiveRecord::MigrationContext.new(destination, schema_migration).migrations
|
675
901
|
last = destination_migrations.last
|
676
902
|
sources.each do |scope, path|
|
677
|
-
source_migrations = ActiveRecord::
|
903
|
+
source_migrations = ActiveRecord::MigrationContext.new(path, schema_migration).migrations
|
678
904
|
|
679
905
|
source_migrations.each do |migration|
|
680
906
|
source = File.binread(migration.filename)
|
681
907
|
inserted_comment = "# This migration comes from #{scope} (originally #{migration.version})\n"
|
682
|
-
|
908
|
+
magic_comments = +""
|
909
|
+
loop do
|
683
910
|
# If we have a magic comment in the original migration,
|
684
911
|
# insert our comment after the first newline(end of the magic comment line)
|
685
912
|
# so the magic keep working.
|
686
913
|
# Note that magic comments must be at the first line(except sh-bang).
|
687
|
-
source
|
688
|
-
|
689
|
-
|
914
|
+
source.sub!(/\A(?:#.*\b(?:en)?coding:\s*\S+|#\s*frozen_string_literal:\s*(?:true|false)).*\n/) do |magic_comment|
|
915
|
+
magic_comments << magic_comment; ""
|
916
|
+
end || break
|
690
917
|
end
|
918
|
+
source = "#{magic_comments}#{inserted_comment}#{source}"
|
691
919
|
|
692
920
|
if duplicate = destination_migrations.detect { |m| m.name == migration.name }
|
693
921
|
if options[:on_skip] && duplicate.scope != scope.to_s
|
@@ -731,7 +959,9 @@ module ActiveRecord
|
|
731
959
|
end
|
732
960
|
end
|
733
961
|
|
734
|
-
|
962
|
+
# Builds a hash for use in ActiveRecord::Migration#proper_table_name using
|
963
|
+
# the Active Record object's table_name prefix and suffix
|
964
|
+
def table_name_options(config = ActiveRecord::Base) #:nodoc:
|
735
965
|
{
|
736
966
|
table_name_prefix: config.table_name_prefix,
|
737
967
|
table_name_suffix: config.table_name_suffix
|
@@ -739,19 +969,22 @@ module ActiveRecord
|
|
739
969
|
end
|
740
970
|
|
741
971
|
private
|
742
|
-
|
743
|
-
|
744
|
-
|
745
|
-
|
746
|
-
|
972
|
+
def execute_block
|
973
|
+
if connection.respond_to? :execute_block
|
974
|
+
super # use normal delegation to record the block
|
975
|
+
else
|
976
|
+
yield
|
977
|
+
end
|
978
|
+
end
|
979
|
+
|
980
|
+
def command_recorder
|
981
|
+
CommandRecorder.new(connection)
|
747
982
|
end
|
748
|
-
end
|
749
983
|
end
|
750
984
|
|
751
985
|
# MigrationProxy is used to defer loading of the actual migration classes
|
752
986
|
# until they are needed
|
753
|
-
|
754
|
-
|
987
|
+
MigrationProxy = Struct.new(:name, :version, :filename, :scope) do
|
755
988
|
def initialize(name, version, filename, scope)
|
756
989
|
super
|
757
990
|
@migration = nil
|
@@ -777,7 +1010,6 @@ module ActiveRecord
|
|
777
1010
|
require(File.expand_path(filename))
|
778
1011
|
name.constantize.new(name, version)
|
779
1012
|
end
|
780
|
-
|
781
1013
|
end
|
782
1014
|
|
783
1015
|
class NullMigration < MigrationProxy #:nodoc:
|
@@ -790,139 +1022,189 @@ module ActiveRecord
|
|
790
1022
|
end
|
791
1023
|
end
|
792
1024
|
|
793
|
-
class
|
794
|
-
|
795
|
-
attr_writer :migrations_paths
|
796
|
-
alias :migrations_path= :migrations_paths=
|
797
|
-
|
798
|
-
def migrate(migrations_paths, target_version = nil, &block)
|
799
|
-
case
|
800
|
-
when target_version.nil?
|
801
|
-
up(migrations_paths, target_version, &block)
|
802
|
-
when current_version == 0 && target_version == 0
|
803
|
-
[]
|
804
|
-
when current_version > target_version
|
805
|
-
down(migrations_paths, target_version, &block)
|
806
|
-
else
|
807
|
-
up(migrations_paths, target_version, &block)
|
808
|
-
end
|
809
|
-
end
|
1025
|
+
class MigrationContext #:nodoc:
|
1026
|
+
attr_reader :migrations_paths, :schema_migration
|
810
1027
|
|
811
|
-
|
812
|
-
|
813
|
-
|
1028
|
+
def initialize(migrations_paths, schema_migration)
|
1029
|
+
@migrations_paths = migrations_paths
|
1030
|
+
@schema_migration = schema_migration
|
1031
|
+
end
|
814
1032
|
|
815
|
-
|
816
|
-
|
1033
|
+
def migrate(target_version = nil, &block)
|
1034
|
+
case
|
1035
|
+
when target_version.nil?
|
1036
|
+
up(target_version, &block)
|
1037
|
+
when current_version == 0 && target_version == 0
|
1038
|
+
[]
|
1039
|
+
when current_version > target_version
|
1040
|
+
down(target_version, &block)
|
1041
|
+
else
|
1042
|
+
up(target_version, &block)
|
817
1043
|
end
|
1044
|
+
end
|
1045
|
+
|
1046
|
+
def rollback(steps = 1)
|
1047
|
+
move(:down, steps)
|
1048
|
+
end
|
818
1049
|
|
819
|
-
|
820
|
-
|
821
|
-
|
1050
|
+
def forward(steps = 1)
|
1051
|
+
move(:up, steps)
|
1052
|
+
end
|
822
1053
|
|
823
|
-
|
1054
|
+
def up(target_version = nil)
|
1055
|
+
selected_migrations = if block_given?
|
1056
|
+
migrations.select { |m| yield m }
|
1057
|
+
else
|
1058
|
+
migrations
|
824
1059
|
end
|
825
1060
|
|
826
|
-
|
827
|
-
|
828
|
-
migrations.select! { |m| yield m } if block_given?
|
1061
|
+
Migrator.new(:up, selected_migrations, schema_migration, target_version).migrate
|
1062
|
+
end
|
829
1063
|
|
830
|
-
|
1064
|
+
def down(target_version = nil)
|
1065
|
+
selected_migrations = if block_given?
|
1066
|
+
migrations.select { |m| yield m }
|
1067
|
+
else
|
1068
|
+
migrations
|
831
1069
|
end
|
832
1070
|
|
833
|
-
|
834
|
-
|
835
|
-
end
|
1071
|
+
Migrator.new(:down, selected_migrations, schema_migration, target_version).migrate
|
1072
|
+
end
|
836
1073
|
|
837
|
-
|
838
|
-
|
839
|
-
|
1074
|
+
def run(direction, target_version)
|
1075
|
+
Migrator.new(direction, migrations, schema_migration, target_version).run
|
1076
|
+
end
|
840
1077
|
|
841
|
-
|
842
|
-
|
843
|
-
|
1078
|
+
def open
|
1079
|
+
Migrator.new(:up, migrations, schema_migration)
|
1080
|
+
end
|
844
1081
|
|
845
|
-
|
846
|
-
|
847
|
-
|
848
|
-
|
849
|
-
|
850
|
-
end
|
1082
|
+
def get_all_versions
|
1083
|
+
if schema_migration.table_exists?
|
1084
|
+
schema_migration.all_versions.map(&:to_i)
|
1085
|
+
else
|
1086
|
+
[]
|
851
1087
|
end
|
1088
|
+
end
|
852
1089
|
|
853
|
-
|
854
|
-
|
855
|
-
|
1090
|
+
def current_version
|
1091
|
+
get_all_versions.max || 0
|
1092
|
+
rescue ActiveRecord::NoDatabaseError
|
1093
|
+
end
|
856
1094
|
|
857
|
-
|
858
|
-
|
859
|
-
|
1095
|
+
def needs_migration?
|
1096
|
+
(migrations.collect(&:version) - get_all_versions).size > 0
|
1097
|
+
end
|
860
1098
|
|
861
|
-
|
862
|
-
|
863
|
-
|
1099
|
+
def any_migrations?
|
1100
|
+
migrations.any?
|
1101
|
+
end
|
864
1102
|
|
865
|
-
|
866
|
-
|
867
|
-
|
1103
|
+
def last_migration #:nodoc:
|
1104
|
+
migrations.last || NullMigration.new
|
1105
|
+
end
|
868
1106
|
|
869
|
-
|
870
|
-
|
871
|
-
|
1107
|
+
def migrations
|
1108
|
+
migrations = migration_files.map do |file|
|
1109
|
+
version, name, scope = parse_migration_filename(file)
|
1110
|
+
raise IllegalMigrationNameError.new(file) unless version
|
1111
|
+
version = version.to_i
|
1112
|
+
name = name.camelize
|
872
1113
|
|
873
|
-
|
874
|
-
@migrations_paths ||= ['db/migrate']
|
875
|
-
# just to not break things if someone uses: migration_path = some_string
|
876
|
-
Array(@migrations_paths)
|
1114
|
+
MigrationProxy.new(name, version, file, scope)
|
877
1115
|
end
|
878
1116
|
|
879
|
-
|
880
|
-
|
1117
|
+
migrations.sort_by(&:version)
|
1118
|
+
end
|
1119
|
+
|
1120
|
+
def migrations_status
|
1121
|
+
db_list = schema_migration.normalized_versions
|
1122
|
+
|
1123
|
+
file_list = migration_files.map do |file|
|
1124
|
+
version, name, scope = parse_migration_filename(file)
|
1125
|
+
raise IllegalMigrationNameError.new(file) unless version
|
1126
|
+
version = schema_migration.normalize_migration_number(version)
|
1127
|
+
status = db_list.delete(version) ? "up" : "down"
|
1128
|
+
[status, version, (name + scope).humanize]
|
1129
|
+
end.compact
|
1130
|
+
|
1131
|
+
db_list.map! do |version|
|
1132
|
+
["up", version, "********** NO FILE **********"]
|
881
1133
|
end
|
882
1134
|
|
883
|
-
|
884
|
-
|
1135
|
+
(db_list + file_list).sort_by { |_, version, _| version }
|
1136
|
+
end
|
885
1137
|
|
886
|
-
|
1138
|
+
def current_environment
|
1139
|
+
ActiveRecord::ConnectionHandling::DEFAULT_ENV.call
|
1140
|
+
end
|
887
1141
|
|
888
|
-
|
889
|
-
|
1142
|
+
def protected_environment?
|
1143
|
+
ActiveRecord::Base.protected_environments.include?(last_stored_environment) if last_stored_environment
|
1144
|
+
end
|
890
1145
|
|
891
|
-
|
892
|
-
|
893
|
-
|
1146
|
+
def last_stored_environment
|
1147
|
+
return nil if current_version == 0
|
1148
|
+
raise NoEnvironmentInSchemaError unless ActiveRecord::InternalMetadata.table_exists?
|
894
1149
|
|
895
|
-
|
896
|
-
|
1150
|
+
environment = ActiveRecord::InternalMetadata[:environment]
|
1151
|
+
raise NoEnvironmentInSchemaError unless environment
|
1152
|
+
environment
|
1153
|
+
end
|
897
1154
|
|
898
|
-
|
1155
|
+
private
|
1156
|
+
def migration_files
|
1157
|
+
paths = Array(migrations_paths)
|
1158
|
+
Dir[*paths.flat_map { |path| "#{path}/**/[0-9]*_*.rb" }]
|
899
1159
|
end
|
900
1160
|
|
901
|
-
|
1161
|
+
def parse_migration_filename(filename)
|
1162
|
+
File.basename(filename).scan(Migration::MigrationFilenameRegexp).first
|
1163
|
+
end
|
902
1164
|
|
903
|
-
def move(direction,
|
904
|
-
migrator = new(direction, migrations
|
905
|
-
start_index = migrator.migrations.index(migrator.current_migration)
|
1165
|
+
def move(direction, steps)
|
1166
|
+
migrator = Migrator.new(direction, migrations, schema_migration)
|
906
1167
|
|
907
|
-
if
|
908
|
-
|
909
|
-
version = finish ? finish.version : 0
|
910
|
-
send(direction, migrations_paths, version)
|
1168
|
+
if current_version != 0 && !migrator.current_migration
|
1169
|
+
raise UnknownMigrationVersionError.new(current_version)
|
911
1170
|
end
|
1171
|
+
|
1172
|
+
start_index =
|
1173
|
+
if current_version == 0
|
1174
|
+
0
|
1175
|
+
else
|
1176
|
+
migrator.migrations.index(migrator.current_migration)
|
1177
|
+
end
|
1178
|
+
|
1179
|
+
finish = migrator.migrations[start_index + steps]
|
1180
|
+
version = finish ? finish.version : 0
|
1181
|
+
send(direction, version)
|
1182
|
+
end
|
1183
|
+
end
|
1184
|
+
|
1185
|
+
class Migrator # :nodoc:
|
1186
|
+
class << self
|
1187
|
+
attr_accessor :migrations_paths
|
1188
|
+
|
1189
|
+
# For cases where a table doesn't exist like loading from schema cache
|
1190
|
+
def current_version
|
1191
|
+
MigrationContext.new(migrations_paths, SchemaMigration).current_version
|
912
1192
|
end
|
913
1193
|
end
|
914
1194
|
|
915
|
-
|
916
|
-
raise StandardError.new("This database does not yet support migrations") unless Base.connection.supports_migrations?
|
1195
|
+
self.migrations_paths = ["db/migrate"]
|
917
1196
|
|
1197
|
+
def initialize(direction, migrations, schema_migration, target_version = nil)
|
918
1198
|
@direction = direction
|
919
1199
|
@target_version = target_version
|
920
1200
|
@migrated_versions = nil
|
921
1201
|
@migrations = migrations
|
1202
|
+
@schema_migration = schema_migration
|
922
1203
|
|
923
1204
|
validate(@migrations)
|
924
1205
|
|
925
|
-
|
1206
|
+
@schema_migration.create_table
|
1207
|
+
ActiveRecord::InternalMetadata.create_table
|
926
1208
|
end
|
927
1209
|
|
928
1210
|
def current_version
|
@@ -935,32 +1217,18 @@ module ActiveRecord
|
|
935
1217
|
alias :current :current_migration
|
936
1218
|
|
937
1219
|
def run
|
938
|
-
|
939
|
-
|
940
|
-
|
941
|
-
|
942
|
-
execute_migration_in_transaction(migration, @direction)
|
943
|
-
rescue => e
|
944
|
-
canceled_msg = use_transaction?(migration) ? ", this migration was canceled" : ""
|
945
|
-
raise StandardError, "An error has occurred#{canceled_msg}:\n\n#{e}", e.backtrace
|
946
|
-
end
|
1220
|
+
if use_advisory_lock?
|
1221
|
+
with_advisory_lock { run_without_lock }
|
1222
|
+
else
|
1223
|
+
run_without_lock
|
947
1224
|
end
|
948
1225
|
end
|
949
1226
|
|
950
1227
|
def migrate
|
951
|
-
if
|
952
|
-
|
953
|
-
|
954
|
-
|
955
|
-
runnable.each do |migration|
|
956
|
-
Base.logger.info "Migrating to #{migration.name} (#{migration.version})" if Base.logger
|
957
|
-
|
958
|
-
begin
|
959
|
-
execute_migration_in_transaction(migration, @direction)
|
960
|
-
rescue => e
|
961
|
-
canceled_msg = use_transaction?(migration) ? "this and " : ""
|
962
|
-
raise StandardError, "An error has occurred, #{canceled_msg}all later migrations canceled:\n\n#{e}", e.backtrace
|
963
|
-
end
|
1228
|
+
if use_advisory_lock?
|
1229
|
+
with_advisory_lock { migrate_without_lock }
|
1230
|
+
else
|
1231
|
+
migrate_without_lock
|
964
1232
|
end
|
965
1233
|
end
|
966
1234
|
|
@@ -985,70 +1253,145 @@ module ActiveRecord
|
|
985
1253
|
end
|
986
1254
|
|
987
1255
|
def migrated
|
988
|
-
@migrated_versions
|
1256
|
+
@migrated_versions || load_migrated
|
989
1257
|
end
|
990
1258
|
|
991
|
-
|
992
|
-
|
993
|
-
migrated.include?(migration.version.to_i)
|
1259
|
+
def load_migrated
|
1260
|
+
@migrated_versions = Set.new(@schema_migration.all_versions.map(&:to_i))
|
994
1261
|
end
|
995
1262
|
|
996
|
-
|
997
|
-
|
998
|
-
|
999
|
-
|
1263
|
+
private
|
1264
|
+
|
1265
|
+
# Used for running a specific migration.
|
1266
|
+
def run_without_lock
|
1267
|
+
migration = migrations.detect { |m| m.version == @target_version }
|
1268
|
+
raise UnknownMigrationVersionError.new(@target_version) if migration.nil?
|
1269
|
+
result = execute_migration_in_transaction(migration, @direction)
|
1270
|
+
|
1271
|
+
record_environment
|
1272
|
+
result
|
1000
1273
|
end
|
1001
|
-
end
|
1002
1274
|
|
1003
|
-
|
1004
|
-
|
1005
|
-
|
1275
|
+
# Used for running multiple migrations up to or down to a certain value.
|
1276
|
+
def migrate_without_lock
|
1277
|
+
if invalid_target?
|
1278
|
+
raise UnknownMigrationVersionError.new(@target_version)
|
1279
|
+
end
|
1006
1280
|
|
1007
|
-
|
1008
|
-
|
1009
|
-
|
1281
|
+
result = runnable.each do |migration|
|
1282
|
+
execute_migration_in_transaction(migration, @direction)
|
1283
|
+
end
|
1010
1284
|
|
1011
|
-
|
1012
|
-
|
1013
|
-
|
1285
|
+
record_environment
|
1286
|
+
result
|
1287
|
+
end
|
1014
1288
|
|
1015
|
-
|
1016
|
-
|
1017
|
-
|
1289
|
+
# Stores the current environment in the database.
|
1290
|
+
def record_environment
|
1291
|
+
return if down?
|
1292
|
+
ActiveRecord::InternalMetadata[:environment] = ActiveRecord::Base.connection.migration_context.current_environment
|
1293
|
+
end
|
1018
1294
|
|
1019
|
-
|
1020
|
-
|
1021
|
-
|
1295
|
+
def ran?(migration)
|
1296
|
+
migrated.include?(migration.version.to_i)
|
1297
|
+
end
|
1022
1298
|
|
1023
|
-
|
1024
|
-
|
1025
|
-
|
1026
|
-
ActiveRecord::SchemaMigration.where(:version => version.to_s).delete_all
|
1027
|
-
else
|
1028
|
-
migrated << version
|
1029
|
-
ActiveRecord::SchemaMigration.create!(:version => version.to_s)
|
1299
|
+
# Return true if a valid version is not provided.
|
1300
|
+
def invalid_target?
|
1301
|
+
@target_version && @target_version != 0 && !target
|
1030
1302
|
end
|
1031
|
-
end
|
1032
1303
|
|
1033
|
-
|
1034
|
-
|
1035
|
-
|
1304
|
+
def execute_migration_in_transaction(migration, direction)
|
1305
|
+
return if down? && !migrated.include?(migration.version.to_i)
|
1306
|
+
return if up? && migrated.include?(migration.version.to_i)
|
1036
1307
|
|
1037
|
-
|
1038
|
-
@direction == :down
|
1039
|
-
end
|
1308
|
+
Base.logger.info "Migrating to #{migration.name} (#{migration.version})" if Base.logger
|
1040
1309
|
|
1041
|
-
|
1042
|
-
|
1043
|
-
|
1044
|
-
|
1045
|
-
|
1310
|
+
ddl_transaction(migration) do
|
1311
|
+
migration.migrate(direction)
|
1312
|
+
record_version_state_after_migrating(migration.version)
|
1313
|
+
end
|
1314
|
+
rescue => e
|
1315
|
+
msg = +"An error has occurred, "
|
1316
|
+
msg << "this and " if use_transaction?(migration)
|
1317
|
+
msg << "all later migrations canceled:\n\n#{e}"
|
1318
|
+
raise StandardError, msg, e.backtrace
|
1319
|
+
end
|
1320
|
+
|
1321
|
+
def target
|
1322
|
+
migrations.detect { |m| m.version == @target_version }
|
1323
|
+
end
|
1324
|
+
|
1325
|
+
def finish
|
1326
|
+
migrations.index(target) || migrations.size - 1
|
1327
|
+
end
|
1328
|
+
|
1329
|
+
def start
|
1330
|
+
up? ? 0 : (migrations.index(current) || 0)
|
1331
|
+
end
|
1332
|
+
|
1333
|
+
def validate(migrations)
|
1334
|
+
name, = migrations.group_by(&:name).find { |_, v| v.length > 1 }
|
1335
|
+
raise DuplicateMigrationNameError.new(name) if name
|
1336
|
+
|
1337
|
+
version, = migrations.group_by(&:version).find { |_, v| v.length > 1 }
|
1338
|
+
raise DuplicateMigrationVersionError.new(version) if version
|
1339
|
+
end
|
1340
|
+
|
1341
|
+
def record_version_state_after_migrating(version)
|
1342
|
+
if down?
|
1343
|
+
migrated.delete(version)
|
1344
|
+
@schema_migration.delete_by(version: version.to_s)
|
1345
|
+
else
|
1346
|
+
migrated << version
|
1347
|
+
@schema_migration.create!(version: version.to_s)
|
1348
|
+
end
|
1349
|
+
end
|
1350
|
+
|
1351
|
+
def up?
|
1352
|
+
@direction == :up
|
1353
|
+
end
|
1354
|
+
|
1355
|
+
def down?
|
1356
|
+
@direction == :down
|
1357
|
+
end
|
1358
|
+
|
1359
|
+
# Wrap the migration in a transaction only if supported by the adapter.
|
1360
|
+
def ddl_transaction(migration)
|
1361
|
+
if use_transaction?(migration)
|
1362
|
+
Base.transaction { yield }
|
1363
|
+
else
|
1364
|
+
yield
|
1365
|
+
end
|
1366
|
+
end
|
1367
|
+
|
1368
|
+
def use_transaction?(migration)
|
1369
|
+
!migration.disable_ddl_transaction && Base.connection.supports_ddl_transactions?
|
1370
|
+
end
|
1371
|
+
|
1372
|
+
def use_advisory_lock?
|
1373
|
+
Base.connection.advisory_locks_enabled?
|
1374
|
+
end
|
1375
|
+
|
1376
|
+
def with_advisory_lock
|
1377
|
+
lock_id = generate_migrator_advisory_lock_id
|
1378
|
+
connection = Base.connection
|
1379
|
+
got_lock = connection.get_advisory_lock(lock_id)
|
1380
|
+
raise ConcurrentMigrationError unless got_lock
|
1381
|
+
load_migrated # reload schema_migrations to be sure it wasn't changed by another process before we got the lock
|
1046
1382
|
yield
|
1383
|
+
ensure
|
1384
|
+
if got_lock && !connection.release_advisory_lock(lock_id)
|
1385
|
+
raise ConcurrentMigrationError.new(
|
1386
|
+
ConcurrentMigrationError::RELEASE_LOCK_FAILED_MESSAGE
|
1387
|
+
)
|
1388
|
+
end
|
1047
1389
|
end
|
1048
|
-
end
|
1049
1390
|
|
1050
|
-
|
1051
|
-
|
1052
|
-
|
1391
|
+
MIGRATOR_SALT = 2053462845
|
1392
|
+
def generate_migrator_advisory_lock_id
|
1393
|
+
db_name_hash = Zlib.crc32(Base.connection.current_database)
|
1394
|
+
MIGRATOR_SALT * db_name_hash
|
1395
|
+
end
|
1053
1396
|
end
|
1054
1397
|
end
|