loading
Generated 2020-08-24T21:39:47-04:00

All Files ( 87.23% covered at 14140.31 hits/line )

322 files in total.
19712 relevant lines, 17194 lines covered and 2518 lines missed. ( 87.23% )
File % covered Lines Relevant Lines Lines covered Lines missed Avg. Hits / Line
lib/active_record.rb 95.08 % 188 122 116 6 2.85
lib/active_record/aggregations.rb 100.00 % 284 53 53 0 47.49
lib/active_record/association_relation.rb 100.00 % 54 26 26 0 1639.85
lib/active_record/associations.rb 87.50 % 1930 200 175 25 22133.06
lib/active_record/associations/alias_tracker.rb 95.00 % 81 40 38 2 2496.75
lib/active_record/associations/association.rb 98.48 % 326 132 130 2 32454.48
lib/active_record/associations/association_scope.rb 100.00 % 168 98 98 0 4450.70
lib/active_record/associations/belongs_to_association.rb 100.00 % 122 64 64 0 14867.25
lib/active_record/associations/belongs_to_polymorphic_association.rb 100.00 % 36 18 18 0 890.78
lib/active_record/associations/builder/association.rb 96.49 % 138 57 55 2 1655.18
lib/active_record/associations/builder/belongs_to.rb 98.67 % 132 75 74 1 314.99
lib/active_record/associations/builder/collection_association.rb 96.97 % 71 33 32 1 1719.94
lib/active_record/associations/builder/has_and_belongs_to_many.rb 100.00 % 111 63 63 0 302.97
lib/active_record/associations/builder/has_many.rb 100.00 % 22 12 12 0 803.58
lib/active_record/associations/builder/has_one.rb 100.00 % 60 35 35 0 119.14
lib/active_record/associations/builder/singular_association.rb 100.00 % 44 13 13 0 903.00
lib/active_record/associations/collection_association.rb 98.28 % 515 232 228 4 1506.59
lib/active_record/associations/collection_proxy.rb 100.00 % 1130 96 96 0 1062.30
lib/active_record/associations/foreign_association.rb 100.00 % 33 17 17 0 764.76
lib/active_record/associations/has_many_association.rb 100.00 % 135 60 60 0 495.35
lib/active_record/associations/has_many_through_association.rb 99.11 % 220 112 111 1 465.43
lib/active_record/associations/has_one_association.rb 100.00 % 120 60 60 0 148.17
lib/active_record/associations/has_one_through_association.rb 100.00 % 45 25 25 0 30.24
lib/active_record/associations/join_dependency.rb 98.16 % 286 163 160 3 1527.12
lib/active_record/associations/join_dependency/join_association.rb 100.00 % 102 54 54 0 1238.48
lib/active_record/associations/join_dependency/join_base.rb 100.00 % 23 12 12 0 884.00
lib/active_record/associations/join_dependency/join_part.rb 87.50 % 71 32 28 4 4087.13
lib/active_record/associations/preloader.rb 100.00 % 206 64 64 0 13573.36
lib/active_record/associations/preloader/association.rb 100.00 % 159 82 82 0 30097.98
lib/active_record/associations/preloader/through_association.rb 98.41 % 116 63 62 1 397.41
lib/active_record/associations/singular_association.rb 96.77 % 59 31 30 1 896.32
lib/active_record/associations/through_association.rb 98.04 % 121 51 50 1 1426.69
lib/active_record/attribute_assignment.rb 100.00 % 86 43 43 0 3731.26
lib/active_record/attribute_methods.rb 100.00 % 424 133 133 0 26723.38
lib/active_record/attribute_methods/before_type_cast.rb 100.00 % 77 16 16 0 86.81
lib/active_record/attribute_methods/dirty.rb 98.67 % 211 75 74 1 1592.35
lib/active_record/attribute_methods/primary_key.rb 96.49 % 138 57 55 2 46719.56
lib/active_record/attribute_methods/query.rb 100.00 % 38 21 21 0 116.00
lib/active_record/attribute_methods/read.rb 100.00 % 43 18 18 0 86454.78
lib/active_record/attribute_methods/serialization.rb 100.00 % 89 21 21 0 138.05
lib/active_record/attribute_methods/time_zone_conversion.rb 93.75 % 88 48 45 3 2579.23
lib/active_record/attribute_methods/write.rb 100.00 % 52 23 23 0 10132.13
lib/active_record/attributes.rb 100.00 % 298 41 41 0 4756.10
lib/active_record/autosave_association.rb 100.00 % 512 172 172 0 16724.69
lib/active_record/base.rb 100.00 % 316 58 58 0 3.00
lib/active_record/callbacks.rb 100.00 % 348 29 29 0 2471.34
lib/active_record/coders/json.rb 100.00 % 15 7 7 0 18.43
lib/active_record/coders/yaml_column.rb 100.00 % 49 27 27 0 1546.56
lib/active_record/connection_adapters.rb 100.00 % 49 34 34 0 3.00
lib/active_record/connection_adapters/abstract/connection_pool.rb 97.13 % 1213 453 440 13 33297.40
lib/active_record/connection_adapters/abstract/database_limits.rb 100.00 % 85 38 38 0 584.84
lib/active_record/connection_adapters/abstract/database_statements.rb 95.57 % 557 203 194 9 60246.27
lib/active_record/connection_adapters/abstract/query_cache.rb 100.00 % 149 69 69 0 40498.29
lib/active_record/connection_adapters/abstract/quoting.rb 94.90 % 251 98 93 5 92962.30
lib/active_record/connection_adapters/abstract/savepoints.rb 100.00 % 23 11 11 0 2168.82
lib/active_record/connection_adapters/abstract/schema_creation.rb 98.17 % 189 109 107 2 8603.60
lib/active_record/connection_adapters/abstract/schema_definitions.rb 99.61 % 787 258 257 1 4692.78
lib/active_record/connection_adapters/abstract/schema_dumper.rb 94.55 % 93 55 52 3 6802.02
lib/active_record/connection_adapters/abstract/schema_statements.rb 92.09 % 1630 430 396 34 3624.45
lib/active_record/connection_adapters/abstract/transaction.rb 98.04 % 364 204 200 4 38418.24
lib/active_record/connection_adapters/abstract_adapter.rb 89.82 % 752 334 300 34 20048.50
lib/active_record/connection_adapters/abstract_mysql_adapter.rb 0.00 % 860 632 0 632 0.00
lib/active_record/connection_adapters/column.rb 100.00 % 109 53 53 0 45455.58
lib/active_record/connection_adapters/deduplicable.rb 100.00 % 29 15 15 0 109462.40
lib/active_record/connection_adapters/mysql/column.rb 0.00 % 27 21 0 21 0.00
lib/active_record/connection_adapters/mysql/database_statements.rb 0.00 % 194 158 0 158 0.00
lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb 0.00 % 71 48 0 48 0.00
lib/active_record/connection_adapters/mysql/quoting.rb 0.00 % 81 66 0 66 0.00
lib/active_record/connection_adapters/mysql/schema_creation.rb 0.00 % 98 75 0 75 0.00
lib/active_record/connection_adapters/mysql/schema_definitions.rb 0.00 % 103 48 0 48 0.00
lib/active_record/connection_adapters/mysql/schema_dumper.rb 0.00 % 88 75 0 75 0.00
lib/active_record/connection_adapters/mysql/schema_statements.rb 0.00 % 268 227 0 227 0.00
lib/active_record/connection_adapters/mysql/type_metadata.rb 0.00 % 40 32 0 32 0.00
lib/active_record/connection_adapters/mysql2_adapter.rb 0.00 % 153 112 0 112 0.00
lib/active_record/connection_adapters/pool_config.rb 100.00 % 63 34 34 0 87294.09
lib/active_record/connection_adapters/pool_manager.rb 100.00 % 27 13 13 0 112244.38
lib/active_record/connection_adapters/postgresql/column.rb 100.00 % 53 27 27 0 21618.04
lib/active_record/connection_adapters/postgresql/database_statements.rb 100.00 % 147 76 76 0 15770.83
lib/active_record/connection_adapters/postgresql/explain_pretty_printer.rb 100.00 % 44 16 16 0 4.69
lib/active_record/connection_adapters/postgresql/oid.rb 100.00 % 35 27 27 0 2.00
lib/active_record/connection_adapters/postgresql/oid/array.rb 97.83 % 91 46 45 1 250.43
lib/active_record/connection_adapters/postgresql/oid/bit.rb 100.00 % 53 26 26 0 7.35
lib/active_record/connection_adapters/postgresql/oid/bit_varying.rb 100.00 % 15 7 7 0 2.29
lib/active_record/connection_adapters/postgresql/oid/bytea.rb 100.00 % 17 9 9 0 11.56
lib/active_record/connection_adapters/postgresql/oid/cidr.rb 96.00 % 50 25 24 1 7.12
lib/active_record/connection_adapters/postgresql/oid/date.rb 100.00 % 23 12 12 0 213.33
lib/active_record/connection_adapters/postgresql/oid/date_time.rb 100.00 % 23 12 12 0 6074.67
lib/active_record/connection_adapters/postgresql/oid/decimal.rb 100.00 % 15 7 7 0 2.14
lib/active_record/connection_adapters/postgresql/oid/enum.rb 100.00 % 20 10 10 0 3.10
lib/active_record/connection_adapters/postgresql/oid/hstore.rb 100.00 % 70 36 36 0 127.83
lib/active_record/connection_adapters/postgresql/oid/inet.rb 100.00 % 15 7 7 0 2.00
lib/active_record/connection_adapters/postgresql/oid/jsonb.rb 100.00 % 15 7 7 0 14.43
lib/active_record/connection_adapters/postgresql/oid/legacy_point.rb 95.45 % 44 22 21 1 18.18
lib/active_record/connection_adapters/postgresql/oid/macaddr.rb 100.00 % 25 11 11 0 2.09
lib/active_record/connection_adapters/postgresql/oid/money.rb 100.00 % 41 16 16 0 17.88
lib/active_record/connection_adapters/postgresql/oid/oid.rb 100.00 % 15 7 7 0 4.14
lib/active_record/connection_adapters/postgresql/oid/point.rb 96.88 % 64 32 31 1 25.34
lib/active_record/connection_adapters/postgresql/oid/range.rb 98.33 % 115 60 59 1 135.77
lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb 100.00 % 18 9 9 0 1267.78
lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb 98.31 % 113 59 58 1 35987.63
lib/active_record/connection_adapters/postgresql/oid/uuid.rb 100.00 % 35 17 17 0 35.76
lib/active_record/connection_adapters/postgresql/oid/vector.rb 90.91 % 28 11 10 1 231.27
lib/active_record/connection_adapters/postgresql/oid/xml.rb 100.00 % 30 15 15 0 2.27
lib/active_record/connection_adapters/postgresql/quoting.rb 95.40 % 205 87 83 4 34066.41
lib/active_record/connection_adapters/postgresql/referential_integrity.rb 100.00 % 43 18 18 0 13795.11
lib/active_record/connection_adapters/postgresql/schema_creation.rb 100.00 % 76 41 41 0 287.98
lib/active_record/connection_adapters/postgresql/schema_definitions.rb 100.00 % 222 31 31 0 245.87
lib/active_record/connection_adapters/postgresql/schema_dumper.rb 100.00 % 49 27 27 0 1580.78
lib/active_record/connection_adapters/postgresql/schema_statements.rb 96.62 % 786 325 314 11 1479.33
lib/active_record/connection_adapters/postgresql/type_metadata.rb 100.00 % 44 21 21 0 8090.00
lib/active_record/connection_adapters/postgresql/utils.rb 100.00 % 80 35 35 0 4450.54
lib/active_record/connection_adapters/postgresql_adapter.rb 97.37 % 958 456 444 12 2391.02
lib/active_record/connection_adapters/schema_cache.rb 95.38 % 224 130 124 6 5374.96
lib/active_record/connection_adapters/sql_type_metadata.rb 100.00 % 47 21 21 0 110840.71
lib/active_record/connection_adapters/sqlite3/database_statements.rb 98.77 % 144 81 80 1 36579.10
lib/active_record/connection_adapters/sqlite3/explain_pretty_printer.rb 100.00 % 21 7 7 0 5.00
lib/active_record/connection_adapters/sqlite3/quoting.rb 97.50 % 102 40 39 1 30364.63
lib/active_record/connection_adapters/sqlite3/schema_creation.rb 100.00 % 21 11 11 0 8151.00
lib/active_record/connection_adapters/sqlite3/schema_definitions.rb 100.00 % 19 10 10 0 72.70
lib/active_record/connection_adapters/sqlite3/schema_dumper.rb 100.00 % 18 9 9 0 470.11
lib/active_record/connection_adapters/sqlite3/schema_statements.rb 98.73 % 170 79 78 1 13584.22
lib/active_record/connection_adapters/sqlite3_adapter.rb 97.49 % 570 279 272 7 7988.52
lib/active_record/connection_adapters/statement_pool.rb 81.25 % 60 32 26 6 3576.47
lib/active_record/connection_handling.rb 99.06 % 325 106 105 1 27362.17
lib/active_record/core.rb 98.41 % 639 252 248 4 23696.46
lib/active_record/counter_cache.rb 100.00 % 196 51 51 0 3720.84
lib/active_record/database_configurations.rb 99.26 % 304 136 135 1 197.43
lib/active_record/database_configurations/connection_url_resolver.rb 100.00 % 98 30 30 0 174.30
lib/active_record/database_configurations/database_config.rb 69.23 % 80 39 27 12 133.08
lib/active_record/database_configurations/hash_config.rb 93.94 % 96 33 31 2 350.15
lib/active_record/database_configurations/url_config.rb 100.00 % 53 13 13 0 98.92
lib/active_record/delegated_type.rb 100.00 % 209 25 25 0 7.32
lib/active_record/dynamic_matchers.rb 96.67 % 121 60 58 2 59651.63
lib/active_record/enum.rb 99.04 % 290 104 103 1 278.88
lib/active_record/errors.rb 96.91 % 408 97 94 3 94.84
lib/active_record/explain.rb 85.71 % 54 28 24 4 13.93
lib/active_record/explain_registry.rb 100.00 % 32 12 12 0 35710.92
lib/active_record/explain_subscriber.rb 100.00 % 34 13 13 0 32933.69
lib/active_record/fixture_set/file.rb 100.00 % 75 37 37 0 27640.89
lib/active_record/fixture_set/model_metadata.rb 100.00 % 32 16 16 0 75487.06
lib/active_record/fixture_set/render_context.rb 100.00 % 17 7 7 0 4068.86
lib/active_record/fixture_set/table_row.rb 98.72 % 152 78 77 1 174445.63
lib/active_record/fixture_set/table_rows.rb 100.00 % 46 20 20 0 76497.75
lib/active_record/fixtures.rb 95.63 % 773 160 153 7 8029.31
lib/active_record/gem_version.rb 88.89 % 17 9 8 1 2.67
lib/active_record/inheritance.rb 99.02 % 306 102 101 1 14263.78
lib/active_record/insert_all.rb 95.45 % 206 110 105 5 119.90
lib/active_record/integration.rb 98.15 % 205 54 53 1 24.46
lib/active_record/internal_metadata.rb 96.67 % 64 30 29 1 154.70
lib/active_record/legacy_yaml_adapter.rb 100.00 % 52 24 24 0 15.38
lib/active_record/locking/optimistic.rb 95.89 % 202 73 70 3 3274.75
lib/active_record/locking/pessimistic.rb 100.00 % 93 13 13 0 11.46
lib/active_record/log_subscriber.rb 97.01 % 127 67 65 2 149944.72
lib/active_record/middleware/database_selector.rb 100.00 % 77 25 25 0 4.56
lib/active_record/middleware/database_selector/resolver.rb 100.00 % 92 43 43 0 12.98
lib/active_record/middleware/database_selector/resolver/session.rb 100.00 % 48 19 19 0 14.16
lib/active_record/migration.rb 95.44 % 1408 504 481 23 258.26
lib/active_record/migration/command_recorder.rb 99.13 % 292 115 114 1 61.15
lib/active_record/migration/compatibility.rb 87.50 % 243 136 119 17 17.41
lib/active_record/migration/join_table.rb 100.00 % 16 8 8 0 25.00
lib/active_record/model_schema.rb 98.92 % 591 185 183 2 20729.24
lib/active_record/nested_attributes.rb 100.00 % 597 111 111 0 220.24
lib/active_record/no_touching.rb 100.00 % 65 22 22 0 189.55
lib/active_record/null_relation.rb 100.00 % 67 33 33 0 8.24
lib/active_record/persistence.rb 97.95 % 974 244 239 5 5013.93
lib/active_record/query_cache.rb 91.30 % 52 23 21 2 239.04
lib/active_record/querying.rb 100.00 % 87 17 17 0 28797.94
lib/active_record/railtie.rb 0.00 % 271 200 0 200 0.00
lib/active_record/railties/console_sandbox.rb 0.00 % 7 4 0 4 0.00
lib/active_record/railties/controller_runtime.rb 0.00 % 51 40 0 40 0.00
lib/active_record/readonly_attributes.rb 100.00 % 28 12 12 0 339.25
lib/active_record/reflection.rb 97.72 % 1065 439 429 10 18570.14
lib/active_record/relation.rb 99.42 % 900 347 345 2 8354.86
lib/active_record/relation/batches.rb 100.00 % 296 73 73 0 246.12
lib/active_record/relation/batches/batch_enumerator.rb 95.24 % 69 21 20 1 21.00
lib/active_record/relation/calculations.rb 100.00 % 481 184 184 0 1060.07
lib/active_record/relation/delegation.rb 98.51 % 132 67 66 1 7791.34
lib/active_record/relation/finder_methods.rb 98.94 % 574 188 186 2 1612.49
lib/active_record/relation/from_clause.rb 93.33 % 30 15 14 1 17499.33
lib/active_record/relation/merger.rb 100.00 % 185 93 93 0 24444.66
lib/active_record/relation/predicate_builder.rb 100.00 % 170 91 91 0 16433.34
lib/active_record/relation/predicate_builder/array_handler.rb 100.00 % 48 26 26 0 58168.19
lib/active_record/relation/predicate_builder/association_query_value.rb 100.00 % 43 21 21 0 142.24
lib/active_record/relation/predicate_builder/basic_object_handler.rb 100.00 % 19 10 10 0 10418.70
lib/active_record/relation/predicate_builder/polymorphic_array_value.rb 100.00 % 53 25 25 0 52.44
lib/active_record/relation/predicate_builder/range_handler.rb 100.00 % 22 12 12 0 716.67
lib/active_record/relation/predicate_builder/relation_handler.rb 100.00 % 19 9 9 0 36.33
lib/active_record/relation/query_attribute.rb 100.00 % 50 24 24 0 20552.46
lib/active_record/relation/query_methods.rb 98.83 % 1540 512 506 6 6383.99
lib/active_record/relation/record_fetch_warning.rb 100.00 % 51 19 19 0 27521.37
lib/active_record/relation/spawn_methods.rb 96.77 % 77 31 30 1 9069.23
lib/active_record/relation/where_clause.rb 99.22 % 238 128 127 1 15584.19
lib/active_record/result.rb 100.00 % 183 71 71 0 103858.59
lib/active_record/runtime_registry.rb 100.00 % 24 8 8 0 3.00
lib/active_record/sanitization.rb 92.31 % 215 78 72 6 2394.91
lib/active_record/schema.rb 100.00 % 61 11 11 0 26.09
lib/active_record/schema_dumper.rb 91.78 % 300 146 134 12 188414.03
lib/active_record/schema_migration.rb 96.15 % 56 26 25 1 199.04
lib/active_record/scoping.rb 97.73 % 105 44 43 1 53025.25
lib/active_record/scoping/default.rb 100.00 % 150 43 43 0 8365.74
lib/active_record/scoping/named.rb 98.00 % 214 50 49 1 8803.10
lib/active_record/secure_token.rb 100.00 % 48 14 14 0 16.29
lib/active_record/serialization.rb 100.00 % 24 12 12 0 83.50
lib/active_record/signed_id.rb 100.00 % 116 26 26 0 13.69
lib/active_record/statement_cache.rb 98.73 % 164 79 78 1 948.68
lib/active_record/store.rb 98.21 % 290 112 110 2 357.62
lib/active_record/suppressor.rb 100.00 % 61 18 18 0 921.89
lib/active_record/table_metadata.rb 100.00 % 74 42 42 0 8095.14
lib/active_record/tasks/database_tasks.rb 68.77 % 530 269 185 84 13.56
lib/active_record/tasks/mysql_database_tasks.rb 36.67 % 113 60 22 38 1.40
lib/active_record/tasks/postgresql_database_tasks.rb 100.00 % 138 80 80 0 6.44
lib/active_record/tasks/sqlite_database_tasks.rb 95.65 % 80 46 44 2 6.67
lib/active_record/test_databases.rb 100.00 % 24 12 12 0 4.92
lib/active_record/test_fixtures.rb 96.75 % 229 123 119 4 9011.81
lib/active_record/timestamp.rb 100.00 % 164 68 68 0 7321.85
lib/active_record/touch_later.rb 100.00 % 65 36 36 0 1587.75
lib/active_record/transactions.rb 100.00 % 444 117 117 0 4402.12
lib/active_record/translation.rb 100.00 % 24 12 12 0 710.33
lib/active_record/type.rb 100.00 % 84 52 52 0 582.60
lib/active_record/type/adapter_specific_registry.rb 100.00 % 126 67 67 0 605.42
lib/active_record/type/date.rb 100.00 % 9 4 4 0 3.00
lib/active_record/type/date_time.rb 100.00 % 9 4 4 0 3.00
lib/active_record/type/decimal_without_scale.rb 85.71 % 15 7 6 1 39.71
lib/active_record/type/hash_lookup_type_map.rb 100.00 % 24 12 12 0 67868.92
lib/active_record/type/internal/timezone.rb 100.00 % 17 8 8 0 9624.88
lib/active_record/type/json.rb 100.00 % 30 15 15 0 250.93
lib/active_record/type/serialized.rb 97.30 % 70 37 36 1 942.95
lib/active_record/type/text.rb 100.00 % 11 5 5 0 247.60
lib/active_record/type/time.rb 100.00 % 21 9 9 0 499.78
lib/active_record/type/type_map.rb 100.00 % 61 32 32 0 79974.50
lib/active_record/type/unsigned_integer.rb 100.00 % 16 8 8 0 161.75
lib/active_record/type_caster.rb 100.00 % 9 4 4 0 3.00
lib/active_record/type_caster/connection.rb 88.89 % 33 18 16 2 297.61
lib/active_record/type_caster/map.rb 100.00 % 23 12 12 0 13295.08
lib/active_record/validations.rb 100.00 % 94 35 35 0 3028.26
lib/active_record/validations/absence.rb 100.00 % 25 10 10 0 11.70
lib/active_record/validations/associated.rb 100.00 % 59 12 12 0 32.08
lib/active_record/validations/length.rb 100.00 % 26 11 11 0 66.09
lib/active_record/validations/numericality.rb 100.00 % 35 15 15 0 37.07
lib/active_record/validations/presence.rb 100.00 % 68 10 10 0 1165.10
lib/active_record/validations/uniqueness.rb 100.00 % 226 54 54 0 251.30
lib/active_record/version.rb 75.00 % 10 4 3 1 2.25
lib/arel.rb 100.00 % 54 30 30 0 4741.93
lib/arel/alias_predication.rb 100.00 % 9 4 4 0 2711.75
lib/arel/attributes/attribute.rb 100.00 % 41 24 24 0 286.42
lib/arel/collectors/bind.rb 100.00 % 29 15 15 0 87004.87
lib/arel/collectors/composite.rb 100.00 % 39 23 23 0 149256.65
lib/arel/collectors/plain_string.rb 100.00 % 20 10 10 0 1065400.00
lib/arel/collectors/sql_string.rb 100.00 % 27 15 15 0 49232.87
lib/arel/collectors/substitute_binds.rb 100.00 % 35 19 19 0 25718.42
lib/arel/crud.rb 100.00 % 42 26 26 0 2998.38
lib/arel/delete_manager.rb 100.00 % 18 10 10 0 1681.80
lib/arel/errors.rb 100.00 % 9 3 3 0 3.00
lib/arel/expressions.rb 100.00 % 29 14 14 0 323.29
lib/arel/factory_methods.rb 100.00 % 49 22 22 0 93.00
lib/arel/insert_manager.rb 100.00 % 49 27 27 0 55210.74
lib/arel/math.rb 100.00 % 45 22 22 0 56.82
lib/arel/nodes.rb 100.00 % 70 45 45 0 3.00
lib/arel/nodes/and.rb 100.00 % 32 16 16 0 1595.94
lib/arel/nodes/ascending.rb 100.00 % 23 11 11 0 27.27
lib/arel/nodes/binary.rb 100.00 % 126 61 61 0 8530.92
lib/arel/nodes/bind_param.rb 90.91 % 44 22 20 2 15902.68
lib/arel/nodes/case.rb 100.00 % 55 29 29 0 10.66
lib/arel/nodes/casted.rb 100.00 % 62 31 31 0 4965.19
lib/arel/nodes/comment.rb 86.67 % 29 15 13 2 20.60
lib/arel/nodes/count.rb 100.00 % 12 6 6 0 1341.00
lib/arel/nodes/delete_statement.rb 100.00 % 45 25 25 0 960.72
lib/arel/nodes/descending.rb 100.00 % 23 11 11 0 5.18
lib/arel/nodes/equality.rb 100.00 % 15 7 7 0 1223.57
lib/arel/nodes/extract.rb 100.00 % 24 12 12 0 7.00
lib/arel/nodes/false.rb 100.00 % 16 8 8 0 3.75
lib/arel/nodes/full_outer_join.rb 100.00 % 8 3 3 0 3.00
lib/arel/nodes/function.rb 100.00 % 44 20 20 0 1184.75
lib/arel/nodes/grouping.rb 100.00 % 11 5 5 0 25.20
lib/arel/nodes/homogeneous_in.rb 94.74 % 72 38 36 2 31818.66
lib/arel/nodes/in.rb 100.00 % 15 7 7 0 3.43
lib/arel/nodes/infix_operation.rb 100.00 % 92 48 48 0 81.13
lib/arel/nodes/inner_join.rb 100.00 % 8 3 3 0 3.00
lib/arel/nodes/insert_statement.rb 100.00 % 37 20 20 0 41494.70
lib/arel/nodes/join_source.rb 100.00 % 20 7 7 0 11463.57
lib/arel/nodes/matches.rb 100.00 % 18 10 10 0 40.80
lib/arel/nodes/named_function.rb 100.00 % 23 12 12 0 204.75
lib/arel/nodes/node.rb 100.00 % 51 18 18 0 117.39
lib/arel/nodes/node_expression.rb 100.00 % 13 8 8 0 3.00
lib/arel/nodes/ordering.rb 100.00 % 18 9 9 0 3.00
lib/arel/nodes/outer_join.rb 100.00 % 8 3 3 0 3.00
lib/arel/nodes/over.rb 100.00 % 15 7 7 0 6.43
lib/arel/nodes/regexp.rb 100.00 % 16 8 8 0 11.25
lib/arel/nodes/right_outer_join.rb 100.00 % 8 3 3 0 3.00
lib/arel/nodes/select_core.rb 100.00 % 67 35 35 0 12955.11
lib/arel/nodes/select_statement.rb 100.00 % 41 22 22 0 14415.68
lib/arel/nodes/sql_literal.rb 100.00 % 19 10 10 0 3.00
lib/arel/nodes/string_join.rb 100.00 % 11 5 5 0 23.40
lib/arel/nodes/table_alias.rb 93.75 % 31 16 15 1 170.25
lib/arel/nodes/terminal.rb 100.00 % 16 8 8 0 3.75
lib/arel/nodes/true.rb 100.00 % 16 8 8 0 3.75
lib/arel/nodes/unary.rb 100.00 % 44 15 15 0 30282.07
lib/arel/nodes/unary_operation.rb 100.00 % 20 10 10 0 7.80
lib/arel/nodes/unqualified_column.rb 81.82 % 22 11 9 2 655.18
lib/arel/nodes/update_statement.rb 100.00 % 41 21 21 0 1416.62
lib/arel/nodes/values_list.rb 100.00 % 9 4 4 0 3.00
lib/arel/nodes/window.rb 90.77 % 126 65 59 6 12.37
lib/arel/nodes/with.rb 100.00 % 11 5 5 0 3.00
lib/arel/order_predications.rb 100.00 % 13 6 6 0 833.00
lib/arel/predications.rb 99.25 % 250 134 133 1 979.62
lib/arel/select_manager.rb 99.31 % 270 144 143 1 5307.11
lib/arel/table.rb 98.31 % 118 59 58 1 46161.00
lib/arel/tree_manager.rb 90.24 % 72 41 37 4 8948.17
lib/arel/update_manager.rb 100.00 % 34 16 16 0 2471.69
lib/arel/visitors.rb 100.00 % 13 8 8 0 3.00
lib/arel/visitors/dot.rb 80.48 % 308 210 169 41 41.16
lib/arel/visitors/mysql.rb 79.17 % 93 48 38 10 4.50
lib/arel/visitors/postgresql.rb 100.00 % 120 71 71 0 6593.68
lib/arel/visitors/sqlite.rb 100.00 % 38 21 21 0 2140.43
lib/arel/visitors/to_sql.rb 98.89 % 899 541 535 6 28579.40
lib/arel/visitors/visitor.rb 100.00 % 45 25 25 0 168497.92
lib/arel/window_predications.rb 100.00 % 9 4 4 0 6.00
lib/rails/generators/active_record.rb 0.00 % 19 14 0 14 0.00
lib/rails/generators/active_record/application_record/application_record_generator.rb 0.00 % 26 20 0 20 0.00
lib/rails/generators/active_record/migration.rb 0.00 % 52 42 0 42 0.00
lib/rails/generators/active_record/migration/migration_generator.rb 0.00 % 76 58 0 58 0.00
lib/rails/generators/active_record/model/model_generator.rb 0.00 % 85 62 0 62 0.00

lib/active_record.rb

95.08% lines covered

122 relevant lines. 116 lines covered and 6 lines missed.
    
  1. # frozen_string_literal: true
  2. #--
  3. # Copyright (c) 2004-2020 David Heinemeier Hansson
  4. #
  5. # Permission is hereby granted, free of charge, to any person obtaining
  6. # a copy of this software and associated documentation files (the
  7. # "Software"), to deal in the Software without restriction, including
  8. # without limitation the rights to use, copy, modify, merge, publish,
  9. # distribute, sublicense, and/or sell copies of the Software, and to
  10. # permit persons to whom the Software is furnished to do so, subject to
  11. # the following conditions:
  12. #
  13. # The above copyright notice and this permission notice shall be
  14. # included in all copies or substantial portions of the Software.
  15. #
  16. # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  17. # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
  18. # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  19. # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
  20. # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
  21. # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
  22. # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  23. #++
  24. 3 require "active_support"
  25. 3 require "active_support/rails"
  26. 3 require "active_model"
  27. 3 require "arel"
  28. 3 require "yaml"
  29. 3 require "active_record/version"
  30. 3 require "active_model/attribute_set"
  31. 3 require "active_record/errors"
  32. 3 module ActiveRecord
  33. 3 extend ActiveSupport::Autoload
  34. 3 autoload :Base
  35. 3 autoload :Callbacks
  36. 3 autoload :Core
  37. 3 autoload :ConnectionHandling
  38. 3 autoload :CounterCache
  39. 3 autoload :DynamicMatchers
  40. 3 autoload :DelegatedType
  41. 3 autoload :Enum
  42. 3 autoload :InternalMetadata
  43. 3 autoload :Explain
  44. 3 autoload :Inheritance
  45. 3 autoload :Integration
  46. 3 autoload :Migration
  47. 3 autoload :Migrator, "active_record/migration"
  48. 3 autoload :ModelSchema
  49. 3 autoload :NestedAttributes
  50. 3 autoload :NoTouching
  51. 3 autoload :TouchLater
  52. 3 autoload :Persistence
  53. 3 autoload :QueryCache
  54. 3 autoload :Querying
  55. 3 autoload :ReadonlyAttributes
  56. 3 autoload :RecordInvalid, "active_record/validations"
  57. 3 autoload :Reflection
  58. 3 autoload :RuntimeRegistry
  59. 3 autoload :Sanitization
  60. 3 autoload :Schema
  61. 3 autoload :SchemaDumper
  62. 3 autoload :SchemaMigration
  63. 3 autoload :Scoping
  64. 3 autoload :Serialization
  65. 3 autoload :StatementCache
  66. 3 autoload :Store
  67. 3 autoload :SignedId
  68. 3 autoload :Suppressor
  69. 3 autoload :Timestamp
  70. 3 autoload :Transactions
  71. 3 autoload :Translation
  72. 3 autoload :Validations
  73. 3 autoload :SecureToken
  74. 3 eager_autoload do
  75. 3 autoload :ConnectionAdapters
  76. 3 autoload :Aggregations
  77. 3 autoload :Associations
  78. 3 autoload :AttributeAssignment
  79. 3 autoload :AttributeMethods
  80. 3 autoload :AutosaveAssociation
  81. 3 autoload :LegacyYamlAdapter
  82. 3 autoload :Relation
  83. 3 autoload :AssociationRelation
  84. 3 autoload :NullRelation
  85. 3 autoload_under "relation" do
  86. 3 autoload :QueryMethods
  87. 3 autoload :FinderMethods
  88. 3 autoload :Calculations
  89. 3 autoload :PredicateBuilder
  90. 3 autoload :SpawnMethods
  91. 3 autoload :Batches
  92. 3 autoload :Delegation
  93. end
  94. 3 autoload :Result
  95. 3 autoload :TableMetadata
  96. 3 autoload :Type
  97. end
  98. 3 module Coders
  99. 3 autoload :YAMLColumn, "active_record/coders/yaml_column"
  100. 3 autoload :JSON, "active_record/coders/json"
  101. end
  102. 3 module AttributeMethods
  103. 3 extend ActiveSupport::Autoload
  104. 3 eager_autoload do
  105. 3 autoload :BeforeTypeCast
  106. 3 autoload :Dirty
  107. 3 autoload :PrimaryKey
  108. 3 autoload :Query
  109. 3 autoload :Read
  110. 3 autoload :TimeZoneConversion
  111. 3 autoload :Write
  112. 3 autoload :Serialization
  113. end
  114. end
  115. 3 module Locking
  116. 3 extend ActiveSupport::Autoload
  117. 3 eager_autoload do
  118. 3 autoload :Optimistic
  119. 3 autoload :Pessimistic
  120. end
  121. end
  122. 3 module Scoping
  123. 3 extend ActiveSupport::Autoload
  124. 3 eager_autoload do
  125. 3 autoload :Named
  126. 3 autoload :Default
  127. end
  128. end
  129. 3 module Middleware
  130. 3 extend ActiveSupport::Autoload
  131. 3 autoload :DatabaseSelector, "active_record/middleware/database_selector"
  132. end
  133. 3 module Tasks
  134. 3 extend ActiveSupport::Autoload
  135. 3 autoload :DatabaseTasks
  136. 3 autoload :SQLiteDatabaseTasks, "active_record/tasks/sqlite_database_tasks"
  137. 3 autoload :MySQLDatabaseTasks, "active_record/tasks/mysql_database_tasks"
  138. 3 autoload :PostgreSQLDatabaseTasks,
  139. "active_record/tasks/postgresql_database_tasks"
  140. end
  141. 3 autoload :TestDatabases, "active_record/test_databases"
  142. 3 autoload :TestFixtures, "active_record/fixtures"
  143. 3 def self.eager_load!
  144. super
  145. ActiveRecord::Locking.eager_load!
  146. ActiveRecord::Scoping.eager_load!
  147. ActiveRecord::Associations.eager_load!
  148. ActiveRecord::AttributeMethods.eager_load!
  149. ActiveRecord::ConnectionAdapters.eager_load!
  150. end
  151. end
  152. 3 ActiveSupport.on_load(:active_record) do
  153. 3 Arel::Table.engine = self
  154. end
  155. 3 ActiveSupport.on_load(:i18n) do
  156. 3 I18n.load_path << File.expand_path("active_record/locale/en.yml", __dir__)
  157. end
  158. 3 YAML.load_tags["!ruby/object:ActiveRecord::AttributeSet"] = "ActiveModel::AttributeSet"
  159. 3 YAML.load_tags["!ruby/object:ActiveRecord::Attribute::FromDatabase"] = "ActiveModel::Attribute::FromDatabase"
  160. 3 YAML.load_tags["!ruby/object:ActiveRecord::LazyAttributeHash"] = "ActiveModel::LazyAttributeHash"
  161. 3 YAML.load_tags["!ruby/object:ActiveRecord::ConnectionAdapters::AbstractMysqlAdapter::MysqlString"] = "ActiveRecord::Type::String"

lib/active_record/aggregations.rb

100.0% lines covered

53 relevant lines. 53 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 module ActiveRecord
  3. # See ActiveRecord::Aggregations::ClassMethods for documentation
  4. 3 module Aggregations
  5. 3 def initialize_dup(*) # :nodoc:
  6. 3 @aggregation_cache = {}
  7. 3 super
  8. end
  9. 3 def reload(*) # :nodoc:
  10. 21 clear_aggregation_cache
  11. 21 super
  12. end
  13. 3 private
  14. 3 def clear_aggregation_cache
  15. 21 @aggregation_cache.clear if persisted?
  16. end
  17. 3 def init_internals
  18. 267 @aggregation_cache = {}
  19. 267 super
  20. end
  21. # Active Record implements aggregation through a macro-like class method called #composed_of
  22. # for representing attributes as value objects. It expresses relationships like "Account [is]
  23. # composed of Money [among other things]" or "Person [is] composed of [an] address". Each call
  24. # to the macro adds a description of how the value objects are created from the attributes of
  25. # the entity object (when the entity is initialized either as a new object or from finding an
  26. # existing object) and how it can be turned back into attributes (when the entity is saved to
  27. # the database).
  28. #
  29. # class Customer < ActiveRecord::Base
  30. # composed_of :balance, class_name: "Money", mapping: %w(balance amount)
  31. # composed_of :address, mapping: [ %w(address_street street), %w(address_city city) ]
  32. # end
  33. #
  34. # The customer class now has the following methods to manipulate the value objects:
  35. # * <tt>Customer#balance, Customer#balance=(money)</tt>
  36. # * <tt>Customer#address, Customer#address=(address)</tt>
  37. #
  38. # These methods will operate with value objects like the ones described below:
  39. #
  40. # class Money
  41. # include Comparable
  42. # attr_reader :amount, :currency
  43. # EXCHANGE_RATES = { "USD_TO_DKK" => 6 }
  44. #
  45. # def initialize(amount, currency = "USD")
  46. # @amount, @currency = amount, currency
  47. # end
  48. #
  49. # def exchange_to(other_currency)
  50. # exchanged_amount = (amount * EXCHANGE_RATES["#{currency}_TO_#{other_currency}"]).floor
  51. # Money.new(exchanged_amount, other_currency)
  52. # end
  53. #
  54. # def ==(other_money)
  55. # amount == other_money.amount && currency == other_money.currency
  56. # end
  57. #
  58. # def <=>(other_money)
  59. # if currency == other_money.currency
  60. # amount <=> other_money.amount
  61. # else
  62. # amount <=> other_money.exchange_to(currency).amount
  63. # end
  64. # end
  65. # end
  66. #
  67. # class Address
  68. # attr_reader :street, :city
  69. # def initialize(street, city)
  70. # @street, @city = street, city
  71. # end
  72. #
  73. # def close_to?(other_address)
  74. # city == other_address.city
  75. # end
  76. #
  77. # def ==(other_address)
  78. # city == other_address.city && street == other_address.street
  79. # end
  80. # end
  81. #
  82. # Now it's possible to access attributes from the database through the value objects instead. If
  83. # you choose to name the composition the same as the attribute's name, it will be the only way to
  84. # access that attribute. That's the case with our +balance+ attribute. You interact with the value
  85. # objects just like you would with any other attribute:
  86. #
  87. # customer.balance = Money.new(20) # sets the Money value object and the attribute
  88. # customer.balance # => Money value object
  89. # customer.balance.exchange_to("DKK") # => Money.new(120, "DKK")
  90. # customer.balance > Money.new(10) # => true
  91. # customer.balance == Money.new(20) # => true
  92. # customer.balance < Money.new(5) # => false
  93. #
  94. # Value objects can also be composed of multiple attributes, such as the case of Address. The order
  95. # of the mappings will determine the order of the parameters.
  96. #
  97. # customer.address_street = "Hyancintvej"
  98. # customer.address_city = "Copenhagen"
  99. # customer.address # => Address.new("Hyancintvej", "Copenhagen")
  100. #
  101. # customer.address = Address.new("May Street", "Chicago")
  102. # customer.address_street # => "May Street"
  103. # customer.address_city # => "Chicago"
  104. #
  105. # == Writing value objects
  106. #
  107. # Value objects are immutable and interchangeable objects that represent a given value, such as
  108. # a Money object representing $5. Two Money objects both representing $5 should be equal (through
  109. # methods such as <tt>==</tt> and <tt><=></tt> from Comparable if ranking makes sense). This is
  110. # unlike entity objects where equality is determined by identity. An entity class such as Customer can
  111. # easily have two different objects that both have an address on Hyancintvej. Entity identity is
  112. # determined by object or relational unique identifiers (such as primary keys). Normal
  113. # ActiveRecord::Base classes are entity objects.
  114. #
  115. # It's also important to treat the value objects as immutable. Don't allow the Money object to have
  116. # its amount changed after creation. Create a new Money object with the new value instead. The
  117. # <tt>Money#exchange_to</tt> method is an example of this. It returns a new value object instead of changing
  118. # its own values. Active Record won't persist value objects that have been changed through means
  119. # other than the writer method.
  120. #
  121. # The immutable requirement is enforced by Active Record by freezing any object assigned as a value
  122. # object. Attempting to change it afterwards will result in a +RuntimeError+.
  123. #
  124. # Read more about value objects on http://c2.com/cgi/wiki?ValueObject and on the dangers of not
  125. # keeping value objects immutable on http://c2.com/cgi/wiki?ValueObjectsShouldBeImmutable
  126. #
  127. # == Custom constructors and converters
  128. #
  129. # By default value objects are initialized by calling the <tt>new</tt> constructor of the value
  130. # class passing each of the mapped attributes, in the order specified by the <tt>:mapping</tt>
  131. # option, as arguments. If the value class doesn't support this convention then #composed_of allows
  132. # a custom constructor to be specified.
  133. #
  134. # When a new value is assigned to the value object, the default assumption is that the new value
  135. # is an instance of the value class. Specifying a custom converter allows the new value to be automatically
  136. # converted to an instance of value class if necessary.
  137. #
  138. # For example, the +NetworkResource+ model has +network_address+ and +cidr_range+ attributes that should be
  139. # aggregated using the +NetAddr::CIDR+ value class (https://www.rubydoc.info/gems/netaddr/1.5.0/NetAddr/CIDR).
  140. # The constructor for the value class is called +create+ and it expects a CIDR address string as a parameter.
  141. # New values can be assigned to the value object using either another +NetAddr::CIDR+ object, a string
  142. # or an array. The <tt>:constructor</tt> and <tt>:converter</tt> options can be used to meet
  143. # these requirements:
  144. #
  145. # class NetworkResource < ActiveRecord::Base
  146. # composed_of :cidr,
  147. # class_name: 'NetAddr::CIDR',
  148. # mapping: [ %w(network_address network), %w(cidr_range bits) ],
  149. # allow_nil: true,
  150. # constructor: Proc.new { |network_address, cidr_range| NetAddr::CIDR.create("#{network_address}/#{cidr_range}") },
  151. # converter: Proc.new { |value| NetAddr::CIDR.create(value.is_a?(Array) ? value.join('/') : value) }
  152. # end
  153. #
  154. # # This calls the :constructor
  155. # network_resource = NetworkResource.new(network_address: '192.168.0.1', cidr_range: 24)
  156. #
  157. # # These assignments will both use the :converter
  158. # network_resource.cidr = [ '192.168.2.1', 8 ]
  159. # network_resource.cidr = '192.168.0.1/24'
  160. #
  161. # # This assignment won't use the :converter as the value is already an instance of the value class
  162. # network_resource.cidr = NetAddr::CIDR.create('192.168.2.1/8')
  163. #
  164. # # Saving and then reloading will use the :constructor on reload
  165. # network_resource.save
  166. # network_resource.reload
  167. #
  168. # == Finding records by a value object
  169. #
  170. # Once a #composed_of relationship is specified for a model, records can be loaded from the database
  171. # by specifying an instance of the value object in the conditions hash. The following example
  172. # finds all customers with +address_street+ equal to "May Street" and +address_city+ equal to "Chicago":
  173. #
  174. # Customer.where(address: Address.new("May Street", "Chicago"))
  175. #
  176. 3 module ClassMethods
  177. # Adds reader and writer methods for manipulating a value object:
  178. # <tt>composed_of :address</tt> adds <tt>address</tt> and <tt>address=(new_address)</tt> methods.
  179. #
  180. # Options are:
  181. # * <tt>:class_name</tt> - Specifies the class name of the association. Use it only if that name
  182. # can't be inferred from the part id. So <tt>composed_of :address</tt> will by default be linked
  183. # to the Address class, but if the real class name is +CompanyAddress+, you'll have to specify it
  184. # with this option.
  185. # * <tt>:mapping</tt> - Specifies the mapping of entity attributes to attributes of the value
  186. # object. Each mapping is represented as an array where the first item is the name of the
  187. # entity attribute and the second item is the name of the attribute in the value object. The
  188. # order in which mappings are defined determines the order in which attributes are sent to the
  189. # value class constructor.
  190. # * <tt>:allow_nil</tt> - Specifies that the value object will not be instantiated when all mapped
  191. # attributes are +nil+. Setting the value object to +nil+ has the effect of writing +nil+ to all
  192. # mapped attributes.
  193. # This defaults to +false+.
  194. # * <tt>:constructor</tt> - A symbol specifying the name of the constructor method or a Proc that
  195. # is called to initialize the value object. The constructor is passed all of the mapped attributes,
  196. # in the order that they are defined in the <tt>:mapping option</tt>, as arguments and uses them
  197. # to instantiate a <tt>:class_name</tt> object.
  198. # The default is <tt>:new</tt>.
  199. # * <tt>:converter</tt> - A symbol specifying the name of a class method of <tt>:class_name</tt>
  200. # or a Proc that is called when a new value is assigned to the value object. The converter is
  201. # passed the single value that is used in the assignment and is only called if the new value is
  202. # not an instance of <tt>:class_name</tt>. If <tt>:allow_nil</tt> is set to true, the converter
  203. # can return +nil+ to skip the assignment.
  204. #
  205. # Option examples:
  206. # composed_of :temperature, mapping: %w(reading celsius)
  207. # composed_of :balance, class_name: "Money", mapping: %w(balance amount)
  208. # composed_of :address, mapping: [ %w(address_street street), %w(address_city city) ]
  209. # composed_of :gps_location
  210. # composed_of :gps_location, allow_nil: true
  211. # composed_of :ip_address,
  212. # class_name: 'IPAddr',
  213. # mapping: %w(ip to_i),
  214. # constructor: Proc.new { |ip| IPAddr.new(ip, Socket::AF_INET) },
  215. # converter: Proc.new { |ip| ip.is_a?(Integer) ? IPAddr.new(ip, Socket::AF_INET) : IPAddr.new(ip.to_s) }
  216. #
  217. 3 def composed_of(part_id, options = {})
  218. 27 options.assert_valid_keys(:class_name, :mapping, :allow_nil, :constructor, :converter)
  219. 27 unless self < Aggregations
  220. 9 include Aggregations
  221. end
  222. 27 name = part_id.id2name
  223. 27 class_name = options[:class_name] || name.camelize
  224. 27 mapping = options[:mapping] || [ name, name ]
  225. 27 mapping = [ mapping ] unless mapping.first.is_a?(Array)
  226. 27 allow_nil = options[:allow_nil] || false
  227. 27 constructor = options[:constructor] || :new
  228. 27 converter = options[:converter]
  229. 27 reader_method(name, class_name, mapping, allow_nil, constructor)
  230. 27 writer_method(name, class_name, mapping, allow_nil, converter)
  231. 27 reflection = ActiveRecord::Reflection.create(:composed_of, part_id, nil, options, self)
  232. 27 Reflection.add_aggregate_reflection self, part_id, reflection
  233. end
  234. 3 private
  235. 3 def reader_method(name, class_name, mapping, allow_nil, constructor)
  236. 27 define_method(name) do
  237. 261 if @aggregation_cache[name].nil? && (!allow_nil || mapping.any? { |key, _| !_read_attribute(key).nil? })
  238. 258 attrs = mapping.collect { |key, _| _read_attribute(key) }
  239. 90 object = constructor.respond_to?(:call) ?
  240. constructor.call(*attrs) :
  241. class_name.constantize.send(constructor, *attrs)
  242. 90 @aggregation_cache[name] = object
  243. end
  244. 177 @aggregation_cache[name]
  245. end
  246. end
  247. 3 def writer_method(name, class_name, mapping, allow_nil, converter)
  248. 27 define_method("#{name}=") do |part|
  249. 63 klass = class_name.constantize
  250. 63 unless part.is_a?(klass) || converter.nil? || part.nil?
  251. 9 part = converter.respond_to?(:call) ? converter.call(part) : klass.send(converter, part)
  252. end
  253. 63 hash_from_multiparameter_assignment = part.is_a?(Hash) &&
  254. 45 part.each_key.all? { |k| k.is_a?(Integer) }
  255. 63 if hash_from_multiparameter_assignment
  256. 15 raise ArgumentError unless part.size == part.each_key.max
  257. 9 part = klass.new(*part.sort.map(&:last))
  258. end
  259. 57 if part.nil? && allow_nil
  260. 60 mapping.each { |key, _| self[key] = nil }
  261. 21 @aggregation_cache[name] = nil
  262. else
  263. 90 mapping.each { |key, value| self[key] = part.send(value) }
  264. 30 @aggregation_cache[name] = part.freeze
  265. end
  266. end
  267. end
  268. end
  269. end
  270. end

lib/active_record/association_relation.rb

100.0% lines covered

26 relevant lines. 26 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 module ActiveRecord
  3. 3 class AssociationRelation < Relation # :nodoc:
  4. 3 def initialize(klass, association, **)
  5. 15191 super(klass)
  6. 15191 @association = association
  7. end
  8. 3 def proxy_association
  9. 78 @association
  10. end
  11. 3 def ==(other)
  12. 75 other == records
  13. end
  14. 3 def build(attributes = nil, &block)
  15. 39 block = _deprecated_scope_block("new", &block)
  16. 78 scoping { @association.build(attributes, &block) }
  17. end
  18. 3 alias new build
  19. 3 def create(attributes = nil, &block)
  20. 18 block = _deprecated_scope_block("create", &block)
  21. 36 scoping { @association.create(attributes, &block) }
  22. end
  23. 3 def create!(attributes = nil, &block)
  24. 12 block = _deprecated_scope_block("create!", &block)
  25. 24 scoping { @association.create!(attributes, &block) }
  26. end
  27. 3 %w(insert insert_all insert! insert_all! upsert upsert_all).each do |method|
  28. 18 class_eval <<~RUBY
  29. def #{method}(attributes, **kwargs)
  30. if @association.reflection.through_reflection?
  31. raise ArgumentError, "Bulk insert or upsert is currently not supported for has_many through association"
  32. end
  33. scoping { klass.#{method}(attributes, **kwargs) }
  34. end
  35. RUBY
  36. end
  37. 3 private
  38. 3 def exec_queries
  39. 2874 super do |record|
  40. 4483 @association.set_inverse_instance_from_queries(record)
  41. 4483 yield record if block_given?
  42. end
  43. end
  44. end
  45. end

lib/active_record/associations.rb

87.5% lines covered

200 relevant lines. 175 lines covered and 25 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 require "active_support/core_ext/enumerable"
  3. 3 require "active_support/core_ext/string/conversions"
  4. 3 module ActiveRecord
  5. 3 class AssociationNotFoundError < ConfigurationError #:nodoc:
  6. 3 attr_reader :record, :association_name
  7. 3 def initialize(record = nil, association_name = nil)
  8. 18 @record = record
  9. 18 @association_name = association_name
  10. 18 if record && association_name
  11. 15 super("Association named '#{association_name}' was not found on #{record.class.name}; perhaps you misspelled it?")
  12. else
  13. 3 super("Association was not found.")
  14. end
  15. end
  16. 3 class Correction
  17. 3 def initialize(error)
  18. @error = error
  19. end
  20. 3 def corrections
  21. if @error.association_name
  22. maybe_these = @error.record.class.reflections.keys
  23. maybe_these.sort_by { |n|
  24. DidYouMean::Jaro.distance(@error.association_name.to_s, n)
  25. }.reverse.first(4)
  26. else
  27. []
  28. end
  29. end
  30. end
  31. # We may not have DYM, and DYM might not let us register error handlers
  32. 3 if defined?(DidYouMean) && DidYouMean.respond_to?(:correct_error)
  33. DidYouMean.correct_error(self, Correction)
  34. end
  35. end
  36. 3 class InverseOfAssociationNotFoundError < ActiveRecordError #:nodoc:
  37. 3 def initialize(reflection = nil, associated_class = nil)
  38. 18 if reflection
  39. 15 super("Could not find the inverse association for #{reflection.name} (#{reflection.options[:inverse_of].inspect} in #{associated_class.nil? ? reflection.class_name : associated_class.name})")
  40. else
  41. 3 super("Could not find the inverse association.")
  42. end
  43. end
  44. end
  45. 3 class HasManyThroughAssociationNotFoundError < ActiveRecordError #:nodoc:
  46. 3 attr_reader :owner_class, :reflection
  47. 3 def initialize(owner_class = nil, reflection = nil)
  48. 6 if owner_class && reflection
  49. 3 @owner_class = owner_class
  50. 3 @reflection = reflection
  51. 3 super("Could not find the association #{reflection.options[:through].inspect} in model #{owner_class.name}")
  52. else
  53. 3 super("Could not find the association.")
  54. end
  55. end
  56. 3 class Correction
  57. 3 def initialize(error)
  58. @error = error
  59. end
  60. 3 def corrections
  61. if @error.reflection && @error.owner_class
  62. maybe_these = @error.owner_class.reflections.keys
  63. maybe_these -= [@error.reflection.name.to_s] # remove failing reflection
  64. maybe_these.sort_by { |n|
  65. DidYouMean::Jaro.distance(@error.reflection.options[:through].to_s, n)
  66. }.reverse.first(4)
  67. else
  68. []
  69. end
  70. end
  71. end
  72. # We may not have DYM, and DYM might not let us register error handlers
  73. 3 if defined?(DidYouMean) && DidYouMean.respond_to?(:correct_error)
  74. DidYouMean.correct_error(self, Correction)
  75. end
  76. end
  77. 3 class HasManyThroughAssociationPolymorphicSourceError < ActiveRecordError #:nodoc:
  78. 3 def initialize(owner_class_name = nil, reflection = nil, source_reflection = nil)
  79. 6 if owner_class_name && reflection && source_reflection
  80. 3 super("Cannot have a has_many :through association '#{owner_class_name}##{reflection.name}' on the polymorphic object '#{source_reflection.class_name}##{source_reflection.name}' without 'source_type'. Try adding 'source_type: \"#{reflection.name.to_s.classify}\"' to 'has_many :through' definition.")
  81. else
  82. 3 super("Cannot have a has_many :through association.")
  83. end
  84. end
  85. end
  86. 3 class HasManyThroughAssociationPolymorphicThroughError < ActiveRecordError #:nodoc:
  87. 3 def initialize(owner_class_name = nil, reflection = nil)
  88. 6 if owner_class_name && reflection
  89. 3 super("Cannot have a has_many :through association '#{owner_class_name}##{reflection.name}' which goes through the polymorphic association '#{owner_class_name}##{reflection.through_reflection.name}'.")
  90. else
  91. 3 super("Cannot have a has_many :through association.")
  92. end
  93. end
  94. end
  95. 3 class HasManyThroughAssociationPointlessSourceTypeError < ActiveRecordError #:nodoc:
  96. 3 def initialize(owner_class_name = nil, reflection = nil, source_reflection = nil)
  97. 3 if owner_class_name && reflection && source_reflection
  98. super("Cannot have a has_many :through association '#{owner_class_name}##{reflection.name}' with a :source_type option if the '#{reflection.through_reflection.class_name}##{source_reflection.name}' is not polymorphic. Try removing :source_type on your association.")
  99. else
  100. 3 super("Cannot have a has_many :through association.")
  101. end
  102. end
  103. end
  104. 3 class HasOneThroughCantAssociateThroughCollection < ActiveRecordError #:nodoc:
  105. 3 def initialize(owner_class_name = nil, reflection = nil, through_reflection = nil)
  106. 6 if owner_class_name && reflection && through_reflection
  107. 3 super("Cannot have a has_one :through association '#{owner_class_name}##{reflection.name}' where the :through association '#{owner_class_name}##{through_reflection.name}' is a collection. Specify a has_one or belongs_to association in the :through option instead.")
  108. else
  109. 3 super("Cannot have a has_one :through association.")
  110. end
  111. end
  112. end
  113. 3 class HasOneAssociationPolymorphicThroughError < ActiveRecordError #:nodoc:
  114. 3 def initialize(owner_class_name = nil, reflection = nil)
  115. 6 if owner_class_name && reflection
  116. 3 super("Cannot have a has_one :through association '#{owner_class_name}##{reflection.name}' which goes through the polymorphic association '#{owner_class_name}##{reflection.through_reflection.name}'.")
  117. else
  118. 3 super("Cannot have a has_one :through association.")
  119. end
  120. end
  121. end
  122. 3 class HasManyThroughSourceAssociationNotFoundError < ActiveRecordError #:nodoc:
  123. 3 def initialize(reflection = nil)
  124. 3 if reflection
  125. through_reflection = reflection.through_reflection
  126. source_reflection_names = reflection.source_reflection_names
  127. source_associations = reflection.through_reflection.klass._reflections.keys
  128. super("Could not find the source association(s) #{source_reflection_names.collect(&:inspect).to_sentence(two_words_connector: ' or ', last_word_connector: ', or ')} in model #{through_reflection.klass}. Try 'has_many #{reflection.name.inspect}, :through => #{through_reflection.name.inspect}, :source => <name>'. Is it one of #{source_associations.to_sentence(two_words_connector: ' or ', last_word_connector: ', or ')}?")
  129. else
  130. 3 super("Could not find the source association(s).")
  131. end
  132. end
  133. end
  134. 3 class HasManyThroughOrderError < ActiveRecordError #:nodoc:
  135. 3 def initialize(owner_class_name = nil, reflection = nil, through_reflection = nil)
  136. 6 if owner_class_name && reflection && through_reflection
  137. 3 super("Cannot have a has_many :through association '#{owner_class_name}##{reflection.name}' which goes through '#{owner_class_name}##{through_reflection.name}' before the through association is defined.")
  138. else
  139. 3 super("Cannot have a has_many :through association before the through association is defined.")
  140. end
  141. end
  142. end
  143. 3 class ThroughCantAssociateThroughHasOneOrManyReflection < ActiveRecordError #:nodoc:
  144. 3 def initialize(owner = nil, reflection = nil)
  145. 30 if owner && reflection
  146. 21 super("Cannot modify association '#{owner.class.name}##{reflection.name}' because the source reflection class '#{reflection.source_reflection.class_name}' is associated to '#{reflection.through_reflection.class_name}' via :#{reflection.source_reflection.macro}.")
  147. else
  148. 9 super("Cannot modify association.")
  149. end
  150. end
  151. end
  152. 3 class AmbiguousSourceReflectionForThroughAssociation < ActiveRecordError # :nodoc:
  153. 3 def initialize(klass, macro, association_name, options, possible_sources)
  154. example_options = options.dup
  155. example_options[:source] = possible_sources.first
  156. super("Ambiguous source reflection for through association. Please " \
  157. "specify a :source directive on your declaration like:\n" \
  158. "\n" \
  159. " class #{klass} < ActiveRecord::Base\n" \
  160. " #{macro} :#{association_name}, #{example_options}\n" \
  161. " end"
  162. )
  163. end
  164. end
  165. 3 class HasManyThroughCantAssociateThroughHasOneOrManyReflection < ThroughCantAssociateThroughHasOneOrManyReflection #:nodoc:
  166. end
  167. 3 class HasOneThroughCantAssociateThroughHasOneOrManyReflection < ThroughCantAssociateThroughHasOneOrManyReflection #:nodoc:
  168. end
  169. 3 class ThroughNestedAssociationsAreReadonly < ActiveRecordError #:nodoc:
  170. 3 def initialize(owner = nil, reflection = nil)
  171. 33 if owner && reflection
  172. 24 super("Cannot modify association '#{owner.class.name}##{reflection.name}' because it goes through more than one other association.")
  173. else
  174. 9 super("Through nested associations are read-only.")
  175. end
  176. end
  177. end
  178. 3 class HasManyThroughNestedAssociationsAreReadonly < ThroughNestedAssociationsAreReadonly #:nodoc:
  179. end
  180. 3 class HasOneThroughNestedAssociationsAreReadonly < ThroughNestedAssociationsAreReadonly #:nodoc:
  181. end
  182. # This error is raised when trying to eager load a polymorphic association using a JOIN.
  183. # Eager loading polymorphic associations is only possible with
  184. # {ActiveRecord::Relation#preload}[rdoc-ref:QueryMethods#preload].
  185. 3 class EagerLoadPolymorphicError < ActiveRecordError
  186. 3 def initialize(reflection = nil)
  187. 15 if reflection
  188. 12 super("Cannot eagerly load the polymorphic association #{reflection.name.inspect}")
  189. else
  190. 3 super("Eager load polymorphic error.")
  191. end
  192. end
  193. end
  194. # This error is raised when trying to destroy a parent instance in N:1 or 1:1 associations
  195. # (has_many, has_one) when there is at least 1 child associated instance.
  196. # ex: if @project.tasks.size > 0, DeleteRestrictionError will be raised when trying to destroy @project
  197. 3 class DeleteRestrictionError < ActiveRecordError #:nodoc:
  198. 3 def initialize(name = nil)
  199. 9 if name
  200. 6 super("Cannot delete record because of dependent #{name}")
  201. else
  202. 3 super("Delete restriction error.")
  203. end
  204. end
  205. end
  206. # See ActiveRecord::Associations::ClassMethods for documentation.
  207. 3 module Associations # :nodoc:
  208. 3 extend ActiveSupport::Autoload
  209. 3 extend ActiveSupport::Concern
  210. # These classes will be loaded when associations are created.
  211. # So there is no need to eager load them.
  212. 3 autoload :Association
  213. 3 autoload :SingularAssociation
  214. 3 autoload :CollectionAssociation
  215. 3 autoload :ForeignAssociation
  216. 3 autoload :CollectionProxy
  217. 3 autoload :ThroughAssociation
  218. 3 module Builder #:nodoc:
  219. 3 autoload :Association, "active_record/associations/builder/association"
  220. 3 autoload :SingularAssociation, "active_record/associations/builder/singular_association"
  221. 3 autoload :CollectionAssociation, "active_record/associations/builder/collection_association"
  222. 3 autoload :BelongsTo, "active_record/associations/builder/belongs_to"
  223. 3 autoload :HasOne, "active_record/associations/builder/has_one"
  224. 3 autoload :HasMany, "active_record/associations/builder/has_many"
  225. 3 autoload :HasAndBelongsToMany, "active_record/associations/builder/has_and_belongs_to_many"
  226. end
  227. 3 eager_autoload do
  228. 3 autoload :BelongsToAssociation
  229. 3 autoload :BelongsToPolymorphicAssociation
  230. 3 autoload :HasManyAssociation
  231. 3 autoload :HasManyThroughAssociation
  232. 3 autoload :HasOneAssociation
  233. 3 autoload :HasOneThroughAssociation
  234. 3 autoload :Preloader
  235. 3 autoload :JoinDependency
  236. 3 autoload :AssociationScope
  237. 3 autoload :AliasTracker
  238. end
  239. 3 def self.eager_load!
  240. super
  241. Preloader.eager_load!
  242. end
  243. # Returns the association instance for the given name, instantiating it if it doesn't already exist
  244. 3 def association(name) #:nodoc:
  245. 665346 association = association_instance_get(name)
  246. 665346 if association.nil?
  247. 228010 unless reflection = self.class._reflect_on_association(name)
  248. 15 raise AssociationNotFoundError.new(self, name)
  249. end
  250. 227995 association = reflection.association_class.new(self, reflection)
  251. 227968 association_instance_set(name, association)
  252. end
  253. 665304 association
  254. end
  255. 3 def association_cached?(name) # :nodoc:
  256. 1084 @association_cache.key?(name)
  257. end
  258. 3 def initialize_dup(*) # :nodoc:
  259. 111 @association_cache = {}
  260. 111 super
  261. end
  262. 3 def reload(*) # :nodoc:
  263. 2233 clear_association_cache
  264. 2233 super
  265. end
  266. 3 private
  267. # Clears out the association cache.
  268. 3 def clear_association_cache
  269. 2233 @association_cache.clear if persisted?
  270. end
  271. 3 def init_internals
  272. 261858 @association_cache = {}
  273. 261858 super
  274. end
  275. # Returns the specified association instance if it exists, +nil+ otherwise.
  276. 3 def association_instance_get(name)
  277. 973015 @association_cache[name]
  278. end
  279. # Set the specified association instance.
  280. 3 def association_instance_set(name, association)
  281. 227968 @association_cache[name] = association
  282. end
  283. # \Associations are a set of macro-like class methods for tying objects together through
  284. # foreign keys. They express relationships like "Project has one Project Manager"
  285. # or "Project belongs to a Portfolio". Each macro adds a number of methods to the
  286. # class which are specialized according to the collection or association symbol and the
  287. # options hash. It works much the same way as Ruby's own <tt>attr*</tt>
  288. # methods.
  289. #
  290. # class Project < ActiveRecord::Base
  291. # belongs_to :portfolio
  292. # has_one :project_manager
  293. # has_many :milestones
  294. # has_and_belongs_to_many :categories
  295. # end
  296. #
  297. # The project class now has the following methods (and more) to ease the traversal and
  298. # manipulation of its relationships:
  299. # * <tt>Project#portfolio</tt>, <tt>Project#portfolio=(portfolio)</tt>, <tt>Project#reload_portfolio</tt>
  300. # * <tt>Project#project_manager</tt>, <tt>Project#project_manager=(project_manager)</tt>, <tt>Project#reload_project_manager</tt>
  301. # * <tt>Project#milestones.empty?</tt>, <tt>Project#milestones.size</tt>, <tt>Project#milestones</tt>, <tt>Project#milestones<<(milestone)</tt>,
  302. # <tt>Project#milestones.delete(milestone)</tt>, <tt>Project#milestones.destroy(milestone)</tt>, <tt>Project#milestones.find(milestone_id)</tt>,
  303. # <tt>Project#milestones.build</tt>, <tt>Project#milestones.create</tt>
  304. # * <tt>Project#categories.empty?</tt>, <tt>Project#categories.size</tt>, <tt>Project#categories</tt>, <tt>Project#categories<<(category1)</tt>,
  305. # <tt>Project#categories.delete(category1)</tt>, <tt>Project#categories.destroy(category1)</tt>
  306. #
  307. # === A word of warning
  308. #
  309. # Don't create associations that have the same name as {instance methods}[rdoc-ref:ActiveRecord::Core] of
  310. # <tt>ActiveRecord::Base</tt>. Since the association adds a method with that name to
  311. # its model, using an association with the same name as one provided by <tt>ActiveRecord::Base</tt> will override the method inherited through <tt>ActiveRecord::Base</tt> and will break things.
  312. # For instance, +attributes+ and +connection+ would be bad choices for association names, because those names already exist in the list of <tt>ActiveRecord::Base</tt> instance methods.
  313. #
  314. # == Auto-generated methods
  315. # See also Instance Public methods below for more details.
  316. #
  317. # === Singular associations (one-to-one)
  318. # | | belongs_to |
  319. # generated methods | belongs_to | :polymorphic | has_one
  320. # ----------------------------------+------------+--------------+---------
  321. # other | X | X | X
  322. # other=(other) | X | X | X
  323. # build_other(attributes={}) | X | | X
  324. # create_other(attributes={}) | X | | X
  325. # create_other!(attributes={}) | X | | X
  326. # reload_other | X | X | X
  327. #
  328. # === Collection associations (one-to-many / many-to-many)
  329. # | | | has_many
  330. # generated methods | habtm | has_many | :through
  331. # ----------------------------------+-------+----------+----------
  332. # others | X | X | X
  333. # others=(other,other,...) | X | X | X
  334. # other_ids | X | X | X
  335. # other_ids=(id,id,...) | X | X | X
  336. # others<< | X | X | X
  337. # others.push | X | X | X
  338. # others.concat | X | X | X
  339. # others.build(attributes={}) | X | X | X
  340. # others.create(attributes={}) | X | X | X
  341. # others.create!(attributes={}) | X | X | X
  342. # others.size | X | X | X
  343. # others.length | X | X | X
  344. # others.count | X | X | X
  345. # others.sum(*args) | X | X | X
  346. # others.empty? | X | X | X
  347. # others.clear | X | X | X
  348. # others.delete(other,other,...) | X | X | X
  349. # others.delete_all | X | X | X
  350. # others.destroy(other,other,...) | X | X | X
  351. # others.destroy_all | X | X | X
  352. # others.find(*args) | X | X | X
  353. # others.exists? | X | X | X
  354. # others.distinct | X | X | X
  355. # others.reset | X | X | X
  356. # others.reload | X | X | X
  357. #
  358. # === Overriding generated methods
  359. #
  360. # Association methods are generated in a module included into the model
  361. # class, making overrides easy. The original generated method can thus be
  362. # called with +super+:
  363. #
  364. # class Car < ActiveRecord::Base
  365. # belongs_to :owner
  366. # belongs_to :old_owner
  367. #
  368. # def owner=(new_owner)
  369. # self.old_owner = self.owner
  370. # super
  371. # end
  372. # end
  373. #
  374. # The association methods module is included immediately after the
  375. # generated attributes methods module, meaning an association will
  376. # override the methods for an attribute with the same name.
  377. #
  378. # == Cardinality and associations
  379. #
  380. # Active Record associations can be used to describe one-to-one, one-to-many and many-to-many
  381. # relationships between models. Each model uses an association to describe its role in
  382. # the relation. The #belongs_to association is always used in the model that has
  383. # the foreign key.
  384. #
  385. # === One-to-one
  386. #
  387. # Use #has_one in the base, and #belongs_to in the associated model.
  388. #
  389. # class Employee < ActiveRecord::Base
  390. # has_one :office
  391. # end
  392. # class Office < ActiveRecord::Base
  393. # belongs_to :employee # foreign key - employee_id
  394. # end
  395. #
  396. # === One-to-many
  397. #
  398. # Use #has_many in the base, and #belongs_to in the associated model.
  399. #
  400. # class Manager < ActiveRecord::Base
  401. # has_many :employees
  402. # end
  403. # class Employee < ActiveRecord::Base
  404. # belongs_to :manager # foreign key - manager_id
  405. # end
  406. #
  407. # === Many-to-many
  408. #
  409. # There are two ways to build a many-to-many relationship.
  410. #
  411. # The first way uses a #has_many association with the <tt>:through</tt> option and a join model, so
  412. # there are two stages of associations.
  413. #
  414. # class Assignment < ActiveRecord::Base
  415. # belongs_to :programmer # foreign key - programmer_id
  416. # belongs_to :project # foreign key - project_id
  417. # end
  418. # class Programmer < ActiveRecord::Base
  419. # has_many :assignments
  420. # has_many :projects, through: :assignments
  421. # end
  422. # class Project < ActiveRecord::Base
  423. # has_many :assignments
  424. # has_many :programmers, through: :assignments
  425. # end
  426. #
  427. # For the second way, use #has_and_belongs_to_many in both models. This requires a join table
  428. # that has no corresponding model or primary key.
  429. #
  430. # class Programmer < ActiveRecord::Base
  431. # has_and_belongs_to_many :projects # foreign keys in the join table
  432. # end
  433. # class Project < ActiveRecord::Base
  434. # has_and_belongs_to_many :programmers # foreign keys in the join table
  435. # end
  436. #
  437. # Choosing which way to build a many-to-many relationship is not always simple.
  438. # If you need to work with the relationship model as its own entity,
  439. # use #has_many <tt>:through</tt>. Use #has_and_belongs_to_many when working with legacy schemas or when
  440. # you never work directly with the relationship itself.
  441. #
  442. # == Is it a #belongs_to or #has_one association?
  443. #
  444. # Both express a 1-1 relationship. The difference is mostly where to place the foreign
  445. # key, which goes on the table for the class declaring the #belongs_to relationship.
  446. #
  447. # class User < ActiveRecord::Base
  448. # # I reference an account.
  449. # belongs_to :account
  450. # end
  451. #
  452. # class Account < ActiveRecord::Base
  453. # # One user references me.
  454. # has_one :user
  455. # end
  456. #
  457. # The tables for these classes could look something like:
  458. #
  459. # CREATE TABLE users (
  460. # id bigint NOT NULL auto_increment,
  461. # account_id bigint default NULL,
  462. # name varchar default NULL,
  463. # PRIMARY KEY (id)
  464. # )
  465. #
  466. # CREATE TABLE accounts (
  467. # id bigint NOT NULL auto_increment,
  468. # name varchar default NULL,
  469. # PRIMARY KEY (id)
  470. # )
  471. #
  472. # == Unsaved objects and associations
  473. #
  474. # You can manipulate objects and associations before they are saved to the database, but
  475. # there is some special behavior you should be aware of, mostly involving the saving of
  476. # associated objects.
  477. #
  478. # You can set the <tt>:autosave</tt> option on a #has_one, #belongs_to,
  479. # #has_many, or #has_and_belongs_to_many association. Setting it
  480. # to +true+ will _always_ save the members, whereas setting it to +false+ will
  481. # _never_ save the members. More details about <tt>:autosave</tt> option is available at
  482. # AutosaveAssociation.
  483. #
  484. # === One-to-one associations
  485. #
  486. # * Assigning an object to a #has_one association automatically saves that object and
  487. # the object being replaced (if there is one), in order to update their foreign
  488. # keys - except if the parent object is unsaved (<tt>new_record? == true</tt>).
  489. # * If either of these saves fail (due to one of the objects being invalid), an
  490. # ActiveRecord::RecordNotSaved exception is raised and the assignment is
  491. # cancelled.
  492. # * If you wish to assign an object to a #has_one association without saving it,
  493. # use the <tt>#build_association</tt> method (documented below). The object being
  494. # replaced will still be saved to update its foreign key.
  495. # * Assigning an object to a #belongs_to association does not save the object, since
  496. # the foreign key field belongs on the parent. It does not save the parent either.
  497. #
  498. # === Collections
  499. #
  500. # * Adding an object to a collection (#has_many or #has_and_belongs_to_many) automatically
  501. # saves that object, except if the parent object (the owner of the collection) is not yet
  502. # stored in the database.
  503. # * If saving any of the objects being added to a collection (via <tt>push</tt> or similar)
  504. # fails, then <tt>push</tt> returns +false+.
  505. # * If saving fails while replacing the collection (via <tt>association=</tt>), an
  506. # ActiveRecord::RecordNotSaved exception is raised and the assignment is
  507. # cancelled.
  508. # * You can add an object to a collection without automatically saving it by using the
  509. # <tt>collection.build</tt> method (documented below).
  510. # * All unsaved (<tt>new_record? == true</tt>) members of the collection are automatically
  511. # saved when the parent is saved.
  512. #
  513. # == Customizing the query
  514. #
  515. # \Associations are built from <tt>Relation</tt> objects, and you can use the Relation syntax
  516. # to customize them. For example, to add a condition:
  517. #
  518. # class Blog < ActiveRecord::Base
  519. # has_many :published_posts, -> { where(published: true) }, class_name: 'Post'
  520. # end
  521. #
  522. # Inside the <tt>-> { ... }</tt> block you can use all of the usual Relation methods.
  523. #
  524. # === Accessing the owner object
  525. #
  526. # Sometimes it is useful to have access to the owner object when building the query. The owner
  527. # is passed as a parameter to the block. For example, the following association would find all
  528. # events that occur on the user's birthday:
  529. #
  530. # class User < ActiveRecord::Base
  531. # has_many :birthday_events, ->(user) { where(starts_on: user.birthday) }, class_name: 'Event'
  532. # end
  533. #
  534. # Note: Joining, eager loading and preloading of these associations is not possible.
  535. # These operations happen before instance creation and the scope will be called with a +nil+ argument.
  536. #
  537. # == Association callbacks
  538. #
  539. # Similar to the normal callbacks that hook into the life cycle of an Active Record object,
  540. # you can also define callbacks that get triggered when you add an object to or remove an
  541. # object from an association collection.
  542. #
  543. # class Project
  544. # has_and_belongs_to_many :developers, after_add: :evaluate_velocity
  545. #
  546. # def evaluate_velocity(developer)
  547. # ...
  548. # end
  549. # end
  550. #
  551. # It's possible to stack callbacks by passing them as an array. Example:
  552. #
  553. # class Project
  554. # has_and_belongs_to_many :developers,
  555. # after_add: [:evaluate_velocity, Proc.new { |p, d| p.shipping_date = Time.now}]
  556. # end
  557. #
  558. # Possible callbacks are: +before_add+, +after_add+, +before_remove+ and +after_remove+.
  559. #
  560. # If any of the +before_add+ callbacks throw an exception, the object will not be
  561. # added to the collection.
  562. #
  563. # Similarly, if any of the +before_remove+ callbacks throw an exception, the object
  564. # will not be removed from the collection.
  565. #
  566. # == Association extensions
  567. #
  568. # The proxy objects that control the access to associations can be extended through anonymous
  569. # modules. This is especially beneficial for adding new finders, creators, and other
  570. # factory-type methods that are only used as part of this association.
  571. #
  572. # class Account < ActiveRecord::Base
  573. # has_many :people do
  574. # def find_or_create_by_name(name)
  575. # first_name, last_name = name.split(" ", 2)
  576. # find_or_create_by(first_name: first_name, last_name: last_name)
  577. # end
  578. # end
  579. # end
  580. #
  581. # person = Account.first.people.find_or_create_by_name("David Heinemeier Hansson")
  582. # person.first_name # => "David"
  583. # person.last_name # => "Heinemeier Hansson"
  584. #
  585. # If you need to share the same extensions between many associations, you can use a named
  586. # extension module.
  587. #
  588. # module FindOrCreateByNameExtension
  589. # def find_or_create_by_name(name)
  590. # first_name, last_name = name.split(" ", 2)
  591. # find_or_create_by(first_name: first_name, last_name: last_name)
  592. # end
  593. # end
  594. #
  595. # class Account < ActiveRecord::Base
  596. # has_many :people, -> { extending FindOrCreateByNameExtension }
  597. # end
  598. #
  599. # class Company < ActiveRecord::Base
  600. # has_many :people, -> { extending FindOrCreateByNameExtension }
  601. # end
  602. #
  603. # Some extensions can only be made to work with knowledge of the association's internals.
  604. # Extensions can access relevant state using the following methods (where +items+ is the
  605. # name of the association):
  606. #
  607. # * <tt>record.association(:items).owner</tt> - Returns the object the association is part of.
  608. # * <tt>record.association(:items).reflection</tt> - Returns the reflection object that describes the association.
  609. # * <tt>record.association(:items).target</tt> - Returns the associated object for #belongs_to and #has_one, or
  610. # the collection of associated objects for #has_many and #has_and_belongs_to_many.
  611. #
  612. # However, inside the actual extension code, you will not have access to the <tt>record</tt> as
  613. # above. In this case, you can access <tt>proxy_association</tt>. For example,
  614. # <tt>record.association(:items)</tt> and <tt>record.items.proxy_association</tt> will return
  615. # the same object, allowing you to make calls like <tt>proxy_association.owner</tt> inside
  616. # association extensions.
  617. #
  618. # == Association Join Models
  619. #
  620. # Has Many associations can be configured with the <tt>:through</tt> option to use an
  621. # explicit join model to retrieve the data. This operates similarly to a
  622. # #has_and_belongs_to_many association. The advantage is that you're able to add validations,
  623. # callbacks, and extra attributes on the join model. Consider the following schema:
  624. #
  625. # class Author < ActiveRecord::Base
  626. # has_many :authorships
  627. # has_many :books, through: :authorships
  628. # end
  629. #
  630. # class Authorship < ActiveRecord::Base
  631. # belongs_to :author
  632. # belongs_to :book
  633. # end
  634. #
  635. # @author = Author.first
  636. # @author.authorships.collect { |a| a.book } # selects all books that the author's authorships belong to
  637. # @author.books # selects all books by using the Authorship join model
  638. #
  639. # You can also go through a #has_many association on the join model:
  640. #
  641. # class Firm < ActiveRecord::Base
  642. # has_many :clients
  643. # has_many :invoices, through: :clients
  644. # end
  645. #
  646. # class Client < ActiveRecord::Base
  647. # belongs_to :firm
  648. # has_many :invoices
  649. # end
  650. #
  651. # class Invoice < ActiveRecord::Base
  652. # belongs_to :client
  653. # end
  654. #
  655. # @firm = Firm.first
  656. # @firm.clients.flat_map { |c| c.invoices } # select all invoices for all clients of the firm
  657. # @firm.invoices # selects all invoices by going through the Client join model
  658. #
  659. # Similarly you can go through a #has_one association on the join model:
  660. #
  661. # class Group < ActiveRecord::Base
  662. # has_many :users
  663. # has_many :avatars, through: :users
  664. # end
  665. #
  666. # class User < ActiveRecord::Base
  667. # belongs_to :group
  668. # has_one :avatar
  669. # end
  670. #
  671. # class Avatar < ActiveRecord::Base
  672. # belongs_to :user
  673. # end
  674. #
  675. # @group = Group.first
  676. # @group.users.collect { |u| u.avatar }.compact # select all avatars for all users in the group
  677. # @group.avatars # selects all avatars by going through the User join model.
  678. #
  679. # An important caveat with going through #has_one or #has_many associations on the
  680. # join model is that these associations are *read-only*. For example, the following
  681. # would not work following the previous example:
  682. #
  683. # @group.avatars << Avatar.new # this would work if User belonged_to Avatar rather than the other way around
  684. # @group.avatars.delete(@group.avatars.last) # so would this
  685. #
  686. # == Setting Inverses
  687. #
  688. # If you are using a #belongs_to on the join model, it is a good idea to set the
  689. # <tt>:inverse_of</tt> option on the #belongs_to, which will mean that the following example
  690. # works correctly (where <tt>tags</tt> is a #has_many <tt>:through</tt> association):
  691. #
  692. # @post = Post.first
  693. # @tag = @post.tags.build name: "ruby"
  694. # @tag.save
  695. #
  696. # The last line ought to save the through record (a <tt>Tagging</tt>). This will only work if the
  697. # <tt>:inverse_of</tt> is set:
  698. #
  699. # class Tagging < ActiveRecord::Base
  700. # belongs_to :post
  701. # belongs_to :tag, inverse_of: :taggings
  702. # end
  703. #
  704. # If you do not set the <tt>:inverse_of</tt> record, the association will
  705. # do its best to match itself up with the correct inverse. Automatic
  706. # inverse detection only works on #has_many, #has_one, and
  707. # #belongs_to associations.
  708. #
  709. # <tt>:foreign_key</tt> and <tt>:through</tt> options on the associations,
  710. # or a custom scope, will also prevent the association's inverse
  711. # from being found automatically.
  712. #
  713. # The automatic guessing of the inverse association uses a heuristic based
  714. # on the name of the class, so it may not work for all associations,
  715. # especially the ones with non-standard names.
  716. #
  717. # You can turn off the automatic detection of inverse associations by setting
  718. # the <tt>:inverse_of</tt> option to <tt>false</tt> like so:
  719. #
  720. # class Tagging < ActiveRecord::Base
  721. # belongs_to :tag, inverse_of: false
  722. # end
  723. #
  724. # == Nested \Associations
  725. #
  726. # You can actually specify *any* association with the <tt>:through</tt> option, including an
  727. # association which has a <tt>:through</tt> option itself. For example:
  728. #
  729. # class Author < ActiveRecord::Base
  730. # has_many :posts
  731. # has_many :comments, through: :posts
  732. # has_many :commenters, through: :comments
  733. # end
  734. #
  735. # class Post < ActiveRecord::Base
  736. # has_many :comments
  737. # end
  738. #
  739. # class Comment < ActiveRecord::Base
  740. # belongs_to :commenter
  741. # end
  742. #
  743. # @author = Author.first
  744. # @author.commenters # => People who commented on posts written by the author
  745. #
  746. # An equivalent way of setting up this association this would be:
  747. #
  748. # class Author < ActiveRecord::Base
  749. # has_many :posts
  750. # has_many :commenters, through: :posts
  751. # end
  752. #
  753. # class Post < ActiveRecord::Base
  754. # has_many :comments
  755. # has_many :commenters, through: :comments
  756. # end
  757. #
  758. # class Comment < ActiveRecord::Base
  759. # belongs_to :commenter
  760. # end
  761. #
  762. # When using a nested association, you will not be able to modify the association because there
  763. # is not enough information to know what modification to make. For example, if you tried to
  764. # add a <tt>Commenter</tt> in the example above, there would be no way to tell how to set up the
  765. # intermediate <tt>Post</tt> and <tt>Comment</tt> objects.
  766. #
  767. # == Polymorphic \Associations
  768. #
  769. # Polymorphic associations on models are not restricted on what types of models they
  770. # can be associated with. Rather, they specify an interface that a #has_many association
  771. # must adhere to.
  772. #
  773. # class Asset < ActiveRecord::Base
  774. # belongs_to :attachable, polymorphic: true
  775. # end
  776. #
  777. # class Post < ActiveRecord::Base
  778. # has_many :assets, as: :attachable # The :as option specifies the polymorphic interface to use.
  779. # end
  780. #
  781. # @asset.attachable = @post
  782. #
  783. # This works by using a type column in addition to a foreign key to specify the associated
  784. # record. In the Asset example, you'd need an +attachable_id+ integer column and an
  785. # +attachable_type+ string column.
  786. #
  787. # Using polymorphic associations in combination with single table inheritance (STI) is
  788. # a little tricky. In order for the associations to work as expected, ensure that you
  789. # store the base model for the STI models in the type column of the polymorphic
  790. # association. To continue with the asset example above, suppose there are guest posts
  791. # and member posts that use the posts table for STI. In this case, there must be a +type+
  792. # column in the posts table.
  793. #
  794. # Note: The <tt>attachable_type=</tt> method is being called when assigning an +attachable+.
  795. # The +class_name+ of the +attachable+ is passed as a String.
  796. #
  797. # class Asset < ActiveRecord::Base
  798. # belongs_to :attachable, polymorphic: true
  799. #
  800. # def attachable_type=(class_name)
  801. # super(class_name.constantize.base_class.to_s)
  802. # end
  803. # end
  804. #
  805. # class Post < ActiveRecord::Base
  806. # # because we store "Post" in attachable_type now dependent: :destroy will work
  807. # has_many :assets, as: :attachable, dependent: :destroy
  808. # end
  809. #
  810. # class GuestPost < Post
  811. # end
  812. #
  813. # class MemberPost < Post
  814. # end
  815. #
  816. # == Caching
  817. #
  818. # All of the methods are built on a simple caching principle that will keep the result
  819. # of the last query around unless specifically instructed not to. The cache is even
  820. # shared across methods to make it even cheaper to use the macro-added methods without
  821. # worrying too much about performance at the first go.
  822. #
  823. # project.milestones # fetches milestones from the database
  824. # project.milestones.size # uses the milestone cache
  825. # project.milestones.empty? # uses the milestone cache
  826. # project.milestones.reload.size # fetches milestones from the database
  827. # project.milestones # uses the milestone cache
  828. #
  829. # == Eager loading of associations
  830. #
  831. # Eager loading is a way to find objects of a certain class and a number of named associations.
  832. # It is one of the easiest ways to prevent the dreaded N+1 problem in which fetching 100
  833. # posts that each need to display their author triggers 101 database queries. Through the
  834. # use of eager loading, the number of queries will be reduced from 101 to 2.
  835. #
  836. # class Post < ActiveRecord::Base
  837. # belongs_to :author
  838. # has_many :comments
  839. # end
  840. #
  841. # Consider the following loop using the class above:
  842. #
  843. # Post.all.each do |post|
  844. # puts "Post: " + post.title
  845. # puts "Written by: " + post.author.name
  846. # puts "Last comment on: " + post.comments.first.created_on
  847. # end
  848. #
  849. # To iterate over these one hundred posts, we'll generate 201 database queries. Let's
  850. # first just optimize it for retrieving the author:
  851. #
  852. # Post.includes(:author).each do |post|
  853. #
  854. # This references the name of the #belongs_to association that also used the <tt>:author</tt>
  855. # symbol. After loading the posts, +find+ will collect the +author_id+ from each one and load
  856. # all of the referenced authors with one query. Doing so will cut down the number of queries
  857. # from 201 to 102.
  858. #
  859. # We can improve upon the situation further by referencing both associations in the finder with:
  860. #
  861. # Post.includes(:author, :comments).each do |post|
  862. #
  863. # This will load all comments with a single query. This reduces the total number of queries
  864. # to 3. In general, the number of queries will be 1 plus the number of associations
  865. # named (except if some of the associations are polymorphic #belongs_to - see below).
  866. #
  867. # To include a deep hierarchy of associations, use a hash:
  868. #
  869. # Post.includes(:author, { comments: { author: :gravatar } }).each do |post|
  870. #
  871. # The above code will load all the comments and all of their associated
  872. # authors and gravatars. You can mix and match any combination of symbols,
  873. # arrays, and hashes to retrieve the associations you want to load.
  874. #
  875. # All of this power shouldn't fool you into thinking that you can pull out huge amounts
  876. # of data with no performance penalty just because you've reduced the number of queries.
  877. # The database still needs to send all the data to Active Record and it still needs to
  878. # be processed. So it's no catch-all for performance problems, but it's a great way to
  879. # cut down on the number of queries in a situation as the one described above.
  880. #
  881. # Since only one table is loaded at a time, conditions or orders cannot reference tables
  882. # other than the main one. If this is the case, Active Record falls back to the previously
  883. # used <tt>LEFT OUTER JOIN</tt> based strategy. For example:
  884. #
  885. # Post.includes([:author, :comments]).where(['comments.approved = ?', true])
  886. #
  887. # This will result in a single SQL query with joins along the lines of:
  888. # <tt>LEFT OUTER JOIN comments ON comments.post_id = posts.id</tt> and
  889. # <tt>LEFT OUTER JOIN authors ON authors.id = posts.author_id</tt>. Note that using conditions
  890. # like this can have unintended consequences.
  891. # In the above example, posts with no approved comments are not returned at all because
  892. # the conditions apply to the SQL statement as a whole and not just to the association.
  893. #
  894. # You must disambiguate column references for this fallback to happen, for example
  895. # <tt>order: "author.name DESC"</tt> will work but <tt>order: "name DESC"</tt> will not.
  896. #
  897. # If you want to load all posts (including posts with no approved comments), then write
  898. # your own <tt>LEFT OUTER JOIN</tt> query using <tt>ON</tt>:
  899. #
  900. # Post.joins("LEFT OUTER JOIN comments ON comments.post_id = posts.id AND comments.approved = '1'")
  901. #
  902. # In this case, it is usually more natural to include an association which has conditions defined on it:
  903. #
  904. # class Post < ActiveRecord::Base
  905. # has_many :approved_comments, -> { where(approved: true) }, class_name: 'Comment'
  906. # end
  907. #
  908. # Post.includes(:approved_comments)
  909. #
  910. # This will load posts and eager load the +approved_comments+ association, which contains
  911. # only those comments that have been approved.
  912. #
  913. # If you eager load an association with a specified <tt>:limit</tt> option, it will be ignored,
  914. # returning all the associated objects:
  915. #
  916. # class Picture < ActiveRecord::Base
  917. # has_many :most_recent_comments, -> { order('id DESC').limit(10) }, class_name: 'Comment'
  918. # end
  919. #
  920. # Picture.includes(:most_recent_comments).first.most_recent_comments # => returns all associated comments.
  921. #
  922. # Eager loading is supported with polymorphic associations.
  923. #
  924. # class Address < ActiveRecord::Base
  925. # belongs_to :addressable, polymorphic: true
  926. # end
  927. #
  928. # A call that tries to eager load the addressable model
  929. #
  930. # Address.includes(:addressable)
  931. #
  932. # This will execute one query to load the addresses and load the addressables with one
  933. # query per addressable type.
  934. # For example, if all the addressables are either of class Person or Company, then a total
  935. # of 3 queries will be executed. The list of addressable types to load is determined on
  936. # the back of the addresses loaded. This is not supported if Active Record has to fallback
  937. # to the previous implementation of eager loading and will raise ActiveRecord::EagerLoadPolymorphicError.
  938. # The reason is that the parent model's type is a column value so its corresponding table
  939. # name cannot be put in the +FROM+/+JOIN+ clauses of that query.
  940. #
  941. # == Table Aliasing
  942. #
  943. # Active Record uses table aliasing in the case that a table is referenced multiple times
  944. # in a join. If a table is referenced only once, the standard table name is used. The
  945. # second time, the table is aliased as <tt>#{reflection_name}_#{parent_table_name}</tt>.
  946. # Indexes are appended for any more successive uses of the table name.
  947. #
  948. # Post.joins(:comments)
  949. # # => SELECT ... FROM posts INNER JOIN comments ON ...
  950. # Post.joins(:special_comments) # STI
  951. # # => SELECT ... FROM posts INNER JOIN comments ON ... AND comments.type = 'SpecialComment'
  952. # Post.joins(:comments, :special_comments) # special_comments is the reflection name, posts is the parent table name
  953. # # => SELECT ... FROM posts INNER JOIN comments ON ... INNER JOIN comments special_comments_posts
  954. #
  955. # Acts as tree example:
  956. #
  957. # TreeMixin.joins(:children)
  958. # # => SELECT ... FROM mixins INNER JOIN mixins childrens_mixins ...
  959. # TreeMixin.joins(children: :parent)
  960. # # => SELECT ... FROM mixins INNER JOIN mixins childrens_mixins ...
  961. # INNER JOIN parents_mixins ...
  962. # TreeMixin.joins(children: {parent: :children})
  963. # # => SELECT ... FROM mixins INNER JOIN mixins childrens_mixins ...
  964. # INNER JOIN parents_mixins ...
  965. # INNER JOIN mixins childrens_mixins_2
  966. #
  967. # Has and Belongs to Many join tables use the same idea, but add a <tt>_join</tt> suffix:
  968. #
  969. # Post.joins(:categories)
  970. # # => SELECT ... FROM posts INNER JOIN categories_posts ... INNER JOIN categories ...
  971. # Post.joins(categories: :posts)
  972. # # => SELECT ... FROM posts INNER JOIN categories_posts ... INNER JOIN categories ...
  973. # INNER JOIN categories_posts posts_categories_join INNER JOIN posts posts_categories
  974. # Post.joins(categories: {posts: :categories})
  975. # # => SELECT ... FROM posts INNER JOIN categories_posts ... INNER JOIN categories ...
  976. # INNER JOIN categories_posts posts_categories_join INNER JOIN posts posts_categories
  977. # INNER JOIN categories_posts categories_posts_join INNER JOIN categories categories_posts_2
  978. #
  979. # If you wish to specify your own custom joins using ActiveRecord::QueryMethods#joins method, those table
  980. # names will take precedence over the eager associations:
  981. #
  982. # Post.joins(:comments).joins("inner join comments ...")
  983. # # => SELECT ... FROM posts INNER JOIN comments_posts ON ... INNER JOIN comments ...
  984. # Post.joins(:comments, :special_comments).joins("inner join comments ...")
  985. # # => SELECT ... FROM posts INNER JOIN comments comments_posts ON ...
  986. # INNER JOIN comments special_comments_posts ...
  987. # INNER JOIN comments ...
  988. #
  989. # Table aliases are automatically truncated according to the maximum length of table identifiers
  990. # according to the specific database.
  991. #
  992. # == Modules
  993. #
  994. # By default, associations will look for objects within the current module scope. Consider:
  995. #
  996. # module MyApplication
  997. # module Business
  998. # class Firm < ActiveRecord::Base
  999. # has_many :clients
  1000. # end
  1001. #
  1002. # class Client < ActiveRecord::Base; end
  1003. # end
  1004. # end
  1005. #
  1006. # When <tt>Firm#clients</tt> is called, it will in turn call
  1007. # <tt>MyApplication::Business::Client.find_all_by_firm_id(firm.id)</tt>.
  1008. # If you want to associate with a class in another module scope, this can be done by
  1009. # specifying the complete class name.
  1010. #
  1011. # module MyApplication
  1012. # module Business
  1013. # class Firm < ActiveRecord::Base; end
  1014. # end
  1015. #
  1016. # module Billing
  1017. # class Account < ActiveRecord::Base
  1018. # belongs_to :firm, class_name: "MyApplication::Business::Firm"
  1019. # end
  1020. # end
  1021. # end
  1022. #
  1023. # == Bi-directional associations
  1024. #
  1025. # When you specify an association, there is usually an association on the associated model
  1026. # that specifies the same relationship in reverse. For example, with the following models:
  1027. #
  1028. # class Dungeon < ActiveRecord::Base
  1029. # has_many :traps
  1030. # has_one :evil_wizard
  1031. # end
  1032. #
  1033. # class Trap < ActiveRecord::Base
  1034. # belongs_to :dungeon
  1035. # end
  1036. #
  1037. # class EvilWizard < ActiveRecord::Base
  1038. # belongs_to :dungeon
  1039. # end
  1040. #
  1041. # The +traps+ association on +Dungeon+ and the +dungeon+ association on +Trap+ are
  1042. # the inverse of each other, and the inverse of the +dungeon+ association on +EvilWizard+
  1043. # is the +evil_wizard+ association on +Dungeon+ (and vice-versa). By default,
  1044. # Active Record can guess the inverse of the association based on the name
  1045. # of the class. The result is the following:
  1046. #
  1047. # d = Dungeon.first
  1048. # t = d.traps.first
  1049. # d.object_id == t.dungeon.object_id # => true
  1050. #
  1051. # The +Dungeon+ instances +d+ and <tt>t.dungeon</tt> in the above example refer to
  1052. # the same in-memory instance since the association matches the name of the class.
  1053. # The result would be the same if we added +:inverse_of+ to our model definitions:
  1054. #
  1055. # class Dungeon < ActiveRecord::Base
  1056. # has_many :traps, inverse_of: :dungeon
  1057. # has_one :evil_wizard, inverse_of: :dungeon
  1058. # end
  1059. #
  1060. # class Trap < ActiveRecord::Base
  1061. # belongs_to :dungeon, inverse_of: :traps
  1062. # end
  1063. #
  1064. # class EvilWizard < ActiveRecord::Base
  1065. # belongs_to :dungeon, inverse_of: :evil_wizard
  1066. # end
  1067. #
  1068. # For more information, see the documentation for the +:inverse_of+ option.
  1069. #
  1070. # == Deleting from associations
  1071. #
  1072. # === Dependent associations
  1073. #
  1074. # #has_many, #has_one, and #belongs_to associations support the <tt>:dependent</tt> option.
  1075. # This allows you to specify that associated records should be deleted when the owner is
  1076. # deleted.
  1077. #
  1078. # For example:
  1079. #
  1080. # class Author
  1081. # has_many :posts, dependent: :destroy
  1082. # end
  1083. # Author.find(1).destroy # => Will destroy all of the author's posts, too
  1084. #
  1085. # The <tt>:dependent</tt> option can have different values which specify how the deletion
  1086. # is done. For more information, see the documentation for this option on the different
  1087. # specific association types. When no option is given, the behavior is to do nothing
  1088. # with the associated records when destroying a record.
  1089. #
  1090. # Note that <tt>:dependent</tt> is implemented using Rails' callback
  1091. # system, which works by processing callbacks in order. Therefore, other
  1092. # callbacks declared either before or after the <tt>:dependent</tt> option
  1093. # can affect what it does.
  1094. #
  1095. # Note that <tt>:dependent</tt> option is ignored for #has_one <tt>:through</tt> associations.
  1096. #
  1097. # === Delete or destroy?
  1098. #
  1099. # #has_many and #has_and_belongs_to_many associations have the methods <tt>destroy</tt>,
  1100. # <tt>delete</tt>, <tt>destroy_all</tt> and <tt>delete_all</tt>.
  1101. #
  1102. # For #has_and_belongs_to_many, <tt>delete</tt> and <tt>destroy</tt> are the same: they
  1103. # cause the records in the join table to be removed.
  1104. #
  1105. # For #has_many, <tt>destroy</tt> and <tt>destroy_all</tt> will always call the <tt>destroy</tt> method of the
  1106. # record(s) being removed so that callbacks are run. However <tt>delete</tt> and <tt>delete_all</tt> will either
  1107. # do the deletion according to the strategy specified by the <tt>:dependent</tt> option, or
  1108. # if no <tt>:dependent</tt> option is given, then it will follow the default strategy.
  1109. # The default strategy is to do nothing (leave the foreign keys with the parent ids set), except for
  1110. # #has_many <tt>:through</tt>, where the default strategy is <tt>delete_all</tt> (delete
  1111. # the join records, without running their callbacks).
  1112. #
  1113. # There is also a <tt>clear</tt> method which is the same as <tt>delete_all</tt>, except that
  1114. # it returns the association rather than the records which have been deleted.
  1115. #
  1116. # === What gets deleted?
  1117. #
  1118. # There is a potential pitfall here: #has_and_belongs_to_many and #has_many <tt>:through</tt>
  1119. # associations have records in join tables, as well as the associated records. So when we
  1120. # call one of these deletion methods, what exactly should be deleted?
  1121. #
  1122. # The answer is that it is assumed that deletion on an association is about removing the
  1123. # <i>link</i> between the owner and the associated object(s), rather than necessarily the
  1124. # associated objects themselves. So with #has_and_belongs_to_many and #has_many
  1125. # <tt>:through</tt>, the join records will be deleted, but the associated records won't.
  1126. #
  1127. # This makes sense if you think about it: if you were to call <tt>post.tags.delete(Tag.find_by(name: 'food'))</tt>
  1128. # you would want the 'food' tag to be unlinked from the post, rather than for the tag itself
  1129. # to be removed from the database.
  1130. #
  1131. # However, there are examples where this strategy doesn't make sense. For example, suppose
  1132. # a person has many projects, and each project has many tasks. If we deleted one of a person's
  1133. # tasks, we would probably not want the project to be deleted. In this scenario, the delete method
  1134. # won't actually work: it can only be used if the association on the join model is a
  1135. # #belongs_to. In other situations you are expected to perform operations directly on
  1136. # either the associated records or the <tt>:through</tt> association.
  1137. #
  1138. # With a regular #has_many there is no distinction between the "associated records"
  1139. # and the "link", so there is only one choice for what gets deleted.
  1140. #
  1141. # With #has_and_belongs_to_many and #has_many <tt>:through</tt>, if you want to delete the
  1142. # associated records themselves, you can always do something along the lines of
  1143. # <tt>person.tasks.each(&:destroy)</tt>.
  1144. #
  1145. # == Type safety with ActiveRecord::AssociationTypeMismatch
  1146. #
  1147. # If you attempt to assign an object to an association that doesn't match the inferred
  1148. # or specified <tt>:class_name</tt>, you'll get an ActiveRecord::AssociationTypeMismatch.
  1149. #
  1150. # == Options
  1151. #
  1152. # All of the association macros can be specialized through options. This makes cases
  1153. # more complex than the simple and guessable ones possible.
  1154. 3 module ClassMethods
  1155. # Specifies a one-to-many association. The following methods for retrieval and query of
  1156. # collections of associated objects will be added:
  1157. #
  1158. # +collection+ is a placeholder for the symbol passed as the +name+ argument, so
  1159. # <tt>has_many :clients</tt> would add among others <tt>clients.empty?</tt>.
  1160. #
  1161. # [collection]
  1162. # Returns a Relation of all the associated objects.
  1163. # An empty Relation is returned if none are found.
  1164. # [collection<<(object, ...)]
  1165. # Adds one or more objects to the collection by setting their foreign keys to the collection's primary key.
  1166. # Note that this operation instantly fires update SQL without waiting for the save or update call on the
  1167. # parent object, unless the parent object is a new record.
  1168. # This will also run validations and callbacks of associated object(s).
  1169. # [collection.delete(object, ...)]
  1170. # Removes one or more objects from the collection by setting their foreign keys to +NULL+.
  1171. # Objects will be in addition destroyed if they're associated with <tt>dependent: :destroy</tt>,
  1172. # and deleted if they're associated with <tt>dependent: :delete_all</tt>.
  1173. #
  1174. # If the <tt>:through</tt> option is used, then the join records are deleted (rather than
  1175. # nullified) by default, but you can specify <tt>dependent: :destroy</tt> or
  1176. # <tt>dependent: :nullify</tt> to override this.
  1177. # [collection.destroy(object, ...)]
  1178. # Removes one or more objects from the collection by running <tt>destroy</tt> on
  1179. # each record, regardless of any dependent option, ensuring callbacks are run.
  1180. #
  1181. # If the <tt>:through</tt> option is used, then the join records are destroyed
  1182. # instead, not the objects themselves.
  1183. # [collection=objects]
  1184. # Replaces the collections content by deleting and adding objects as appropriate. If the <tt>:through</tt>
  1185. # option is true callbacks in the join models are triggered except destroy callbacks, since deletion is
  1186. # direct by default. You can specify <tt>dependent: :destroy</tt> or
  1187. # <tt>dependent: :nullify</tt> to override this.
  1188. # [collection_singular_ids]
  1189. # Returns an array of the associated objects' ids
  1190. # [collection_singular_ids=ids]
  1191. # Replace the collection with the objects identified by the primary keys in +ids+. This
  1192. # method loads the models and calls <tt>collection=</tt>. See above.
  1193. # [collection.clear]
  1194. # Removes every object from the collection. This destroys the associated objects if they
  1195. # are associated with <tt>dependent: :destroy</tt>, deletes them directly from the
  1196. # database if <tt>dependent: :delete_all</tt>, otherwise sets their foreign keys to +NULL+.
  1197. # If the <tt>:through</tt> option is true no destroy callbacks are invoked on the join models.
  1198. # Join models are directly deleted.
  1199. # [collection.empty?]
  1200. # Returns +true+ if there are no associated objects.
  1201. # [collection.size]
  1202. # Returns the number of associated objects.
  1203. # [collection.find(...)]
  1204. # Finds an associated object according to the same rules as ActiveRecord::FinderMethods#find.
  1205. # [collection.exists?(...)]
  1206. # Checks whether an associated object with the given conditions exists.
  1207. # Uses the same rules as ActiveRecord::FinderMethods#exists?.
  1208. # [collection.build(attributes = {}, ...)]
  1209. # Returns one or more new objects of the collection type that have been instantiated
  1210. # with +attributes+ and linked to this object through a foreign key, but have not yet
  1211. # been saved.
  1212. # [collection.create(attributes = {})]
  1213. # Returns a new object of the collection type that has been instantiated
  1214. # with +attributes+, linked to this object through a foreign key, and that has already
  1215. # been saved (if it passed the validation). *Note*: This only works if the base model
  1216. # already exists in the DB, not if it is a new (unsaved) record!
  1217. # [collection.create!(attributes = {})]
  1218. # Does the same as <tt>collection.create</tt>, but raises ActiveRecord::RecordInvalid
  1219. # if the record is invalid.
  1220. # [collection.reload]
  1221. # Returns a Relation of all of the associated objects, forcing a database read.
  1222. # An empty Relation is returned if none are found.
  1223. #
  1224. # === Example
  1225. #
  1226. # A <tt>Firm</tt> class declares <tt>has_many :clients</tt>, which will add:
  1227. # * <tt>Firm#clients</tt> (similar to <tt>Client.where(firm_id: id)</tt>)
  1228. # * <tt>Firm#clients<<</tt>
  1229. # * <tt>Firm#clients.delete</tt>
  1230. # * <tt>Firm#clients.destroy</tt>
  1231. # * <tt>Firm#clients=</tt>
  1232. # * <tt>Firm#client_ids</tt>
  1233. # * <tt>Firm#client_ids=</tt>
  1234. # * <tt>Firm#clients.clear</tt>
  1235. # * <tt>Firm#clients.empty?</tt> (similar to <tt>firm.clients.size == 0</tt>)
  1236. # * <tt>Firm#clients.size</tt> (similar to <tt>Client.count "firm_id = #{id}"</tt>)
  1237. # * <tt>Firm#clients.find</tt> (similar to <tt>Client.where(firm_id: id).find(id)</tt>)
  1238. # * <tt>Firm#clients.exists?(name: 'ACME')</tt> (similar to <tt>Client.exists?(name: 'ACME', firm_id: firm.id)</tt>)
  1239. # * <tt>Firm#clients.build</tt> (similar to <tt>Client.new(firm_id: id)</tt>)
  1240. # * <tt>Firm#clients.create</tt> (similar to <tt>c = Client.new(firm_id: id); c.save; c</tt>)
  1241. # * <tt>Firm#clients.create!</tt> (similar to <tt>c = Client.new(firm_id: id); c.save!</tt>)
  1242. # * <tt>Firm#clients.reload</tt>
  1243. # The declaration can also include an +options+ hash to specialize the behavior of the association.
  1244. #
  1245. # === Scopes
  1246. #
  1247. # You can pass a second argument +scope+ as a callable (i.e. proc or
  1248. # lambda) to retrieve a specific set of records or customize the generated
  1249. # query when you access the associated collection.
  1250. #
  1251. # Scope examples:
  1252. # has_many :comments, -> { where(author_id: 1) }
  1253. # has_many :employees, -> { joins(:address) }
  1254. # has_many :posts, ->(blog) { where("max_post_length > ?", blog.max_post_length) }
  1255. #
  1256. # === Extensions
  1257. #
  1258. # The +extension+ argument allows you to pass a block into a has_many
  1259. # association. This is useful for adding new finders, creators and other
  1260. # factory-type methods to be used as part of the association.
  1261. #
  1262. # Extension examples:
  1263. # has_many :employees do
  1264. # def find_or_create_by_name(name)
  1265. # first_name, last_name = name.split(" ", 2)
  1266. # find_or_create_by(first_name: first_name, last_name: last_name)
  1267. # end
  1268. # end
  1269. #
  1270. # === Options
  1271. # [:class_name]
  1272. # Specify the class name of the association. Use it only if that name can't be inferred
  1273. # from the association name. So <tt>has_many :products</tt> will by default be linked
  1274. # to the +Product+ class, but if the real class name is +SpecialProduct+, you'll have to
  1275. # specify it with this option.
  1276. # [:foreign_key]
  1277. # Specify the foreign key used for the association. By default this is guessed to be the name
  1278. # of this class in lower-case and "_id" suffixed. So a Person class that makes a #has_many
  1279. # association will use "person_id" as the default <tt>:foreign_key</tt>.
  1280. #
  1281. # If you are going to modify the association (rather than just read from it), then it is
  1282. # a good idea to set the <tt>:inverse_of</tt> option.
  1283. # [:foreign_type]
  1284. # Specify the column used to store the associated object's type, if this is a polymorphic
  1285. # association. By default this is guessed to be the name of the polymorphic association
  1286. # specified on "as" option with a "_type" suffix. So a class that defines a
  1287. # <tt>has_many :tags, as: :taggable</tt> association will use "taggable_type" as the
  1288. # default <tt>:foreign_type</tt>.
  1289. # [:primary_key]
  1290. # Specify the name of the column to use as the primary key for the association. By default this is +id+.
  1291. # [:dependent]
  1292. # Controls what happens to the associated objects when
  1293. # their owner is destroyed. Note that these are implemented as
  1294. # callbacks, and Rails executes callbacks in order. Therefore, other
  1295. # similar callbacks may affect the <tt>:dependent</tt> behavior, and the
  1296. # <tt>:dependent</tt> behavior may affect other callbacks.
  1297. #
  1298. # * <tt>nil</tt> do nothing (default).
  1299. # * <tt>:destroy</tt> causes all the associated objects to also be destroyed.
  1300. # * <tt>:delete_all</tt> causes all the associated objects to be deleted directly from the database (so callbacks will not be executed).
  1301. # * <tt>:nullify</tt> causes the foreign keys to be set to +NULL+. Polymorphic type will also be nullified
  1302. # on polymorphic associations. Callbacks are not executed.
  1303. # * <tt>:restrict_with_exception</tt> causes an <tt>ActiveRecord::DeleteRestrictionError</tt> exception to be raised if there are any associated records.
  1304. # * <tt>:restrict_with_error</tt> causes an error to be added to the owner if there are any associated objects.
  1305. #
  1306. # If using with the <tt>:through</tt> option, the association on the join model must be
  1307. # a #belongs_to, and the records which get deleted are the join records, rather than
  1308. # the associated records.
  1309. #
  1310. # If using <tt>dependent: :destroy</tt> on a scoped association, only the scoped objects are destroyed.
  1311. # For example, if a Post model defines
  1312. # <tt>has_many :comments, -> { where published: true }, dependent: :destroy</tt> and <tt>destroy</tt> is
  1313. # called on a post, only published comments are destroyed. This means that any unpublished comments in the
  1314. # database would still contain a foreign key pointing to the now deleted post.
  1315. # [:counter_cache]
  1316. # This option can be used to configure a custom named <tt>:counter_cache.</tt> You only need this option,
  1317. # when you customized the name of your <tt>:counter_cache</tt> on the #belongs_to association.
  1318. # [:as]
  1319. # Specifies a polymorphic interface (See #belongs_to).
  1320. # [:through]
  1321. # Specifies an association through which to perform the query. This can be any other type
  1322. # of association, including other <tt>:through</tt> associations. Options for <tt>:class_name</tt>,
  1323. # <tt>:primary_key</tt> and <tt>:foreign_key</tt> are ignored, as the association uses the
  1324. # source reflection.
  1325. #
  1326. # If the association on the join model is a #belongs_to, the collection can be modified
  1327. # and the records on the <tt>:through</tt> model will be automatically created and removed
  1328. # as appropriate. Otherwise, the collection is read-only, so you should manipulate the
  1329. # <tt>:through</tt> association directly.
  1330. #
  1331. # If you are going to modify the association (rather than just read from it), then it is
  1332. # a good idea to set the <tt>:inverse_of</tt> option on the source association on the
  1333. # join model. This allows associated records to be built which will automatically create
  1334. # the appropriate join model records when they are saved. (See the 'Association Join Models'
  1335. # section above.)
  1336. # [:source]
  1337. # Specifies the source association name used by #has_many <tt>:through</tt> queries.
  1338. # Only use it if the name cannot be inferred from the association.
  1339. # <tt>has_many :subscribers, through: :subscriptions</tt> will look for either <tt>:subscribers</tt> or
  1340. # <tt>:subscriber</tt> on Subscription, unless a <tt>:source</tt> is given.
  1341. # [:source_type]
  1342. # Specifies type of the source association used by #has_many <tt>:through</tt> queries where the source
  1343. # association is a polymorphic #belongs_to.
  1344. # [:validate]
  1345. # When set to +true+, validates new objects added to association when saving the parent object. +true+ by default.
  1346. # If you want to ensure associated objects are revalidated on every update, use +validates_associated+.
  1347. # [:autosave]
  1348. # If true, always save the associated objects or destroy them if marked for destruction,
  1349. # when saving the parent object. If false, never save or destroy the associated objects.
  1350. # By default, only save associated objects that are new records. This option is implemented as a
  1351. # +before_save+ callback. Because callbacks are run in the order they are defined, associated objects
  1352. # may need to be explicitly saved in any user-defined +before_save+ callbacks.
  1353. #
  1354. # Note that NestedAttributes::ClassMethods#accepts_nested_attributes_for sets
  1355. # <tt>:autosave</tt> to <tt>true</tt>.
  1356. # [:inverse_of]
  1357. # Specifies the name of the #belongs_to association on the associated object
  1358. # that is the inverse of this #has_many association.
  1359. # See ActiveRecord::Associations::ClassMethods's overview on Bi-directional associations for more detail.
  1360. # [:extend]
  1361. # Specifies a module or array of modules that will be extended into the association object returned.
  1362. # Useful for defining methods on associations, especially when they should be shared between multiple
  1363. # association objects.
  1364. # [:strict_loading]
  1365. # Enforces strict loading every time the associated record is loaded through this association.
  1366. #
  1367. # Option examples:
  1368. # has_many :comments, -> { order("posted_on") }
  1369. # has_many :comments, -> { includes(:author) }
  1370. # has_many :people, -> { where(deleted: false).order("name") }, class_name: "Person"
  1371. # has_many :tracks, -> { order("position") }, dependent: :destroy
  1372. # has_many :comments, dependent: :nullify
  1373. # has_many :tags, as: :taggable
  1374. # has_many :reports, -> { readonly }
  1375. # has_many :subscribers, through: :subscriptions, source: :user
  1376. # has_many :comments, strict_loading: true
  1377. 3 def has_many(name, scope = nil, **options, &extension)
  1378. 1665 reflection = Builder::HasMany.build(self, name, scope, options, &extension)
  1379. 1650 Reflection.add_reflection self, name, reflection
  1380. end
  1381. # Specifies a one-to-one association with another class. This method should only be used
  1382. # if the other class contains the foreign key. If the current class contains the foreign key,
  1383. # then you should use #belongs_to instead. See also ActiveRecord::Associations::ClassMethods's overview
  1384. # on when to use #has_one and when to use #belongs_to.
  1385. #
  1386. # The following methods for retrieval and query of a single associated object will be added:
  1387. #
  1388. # +association+ is a placeholder for the symbol passed as the +name+ argument, so
  1389. # <tt>has_one :manager</tt> would add among others <tt>manager.nil?</tt>.
  1390. #
  1391. # [association]
  1392. # Returns the associated object. +nil+ is returned if none is found.
  1393. # [association=(associate)]
  1394. # Assigns the associate object, extracts the primary key, sets it as the foreign key,
  1395. # and saves the associate object. To avoid database inconsistencies, permanently deletes an existing
  1396. # associated object when assigning a new one, even if the new one isn't saved to database.
  1397. # [build_association(attributes = {})]
  1398. # Returns a new object of the associated type that has been instantiated
  1399. # with +attributes+ and linked to this object through a foreign key, but has not
  1400. # yet been saved.
  1401. # [create_association(attributes = {})]
  1402. # Returns a new object of the associated type that has been instantiated
  1403. # with +attributes+, linked to this object through a foreign key, and that
  1404. # has already been saved (if it passed the validation).
  1405. # [create_association!(attributes = {})]
  1406. # Does the same as <tt>create_association</tt>, but raises ActiveRecord::RecordInvalid
  1407. # if the record is invalid.
  1408. # [reload_association]
  1409. # Returns the associated object, forcing a database read.
  1410. #
  1411. # === Example
  1412. #
  1413. # An Account class declares <tt>has_one :beneficiary</tt>, which will add:
  1414. # * <tt>Account#beneficiary</tt> (similar to <tt>Beneficiary.where(account_id: id).first</tt>)
  1415. # * <tt>Account#beneficiary=(beneficiary)</tt> (similar to <tt>beneficiary.account_id = account.id; beneficiary.save</tt>)
  1416. # * <tt>Account#build_beneficiary</tt> (similar to <tt>Beneficiary.new(account_id: id)</tt>)
  1417. # * <tt>Account#create_beneficiary</tt> (similar to <tt>b = Beneficiary.new(account_id: id); b.save; b</tt>)
  1418. # * <tt>Account#create_beneficiary!</tt> (similar to <tt>b = Beneficiary.new(account_id: id); b.save!; b</tt>)
  1419. # * <tt>Account#reload_beneficiary</tt>
  1420. #
  1421. # === Scopes
  1422. #
  1423. # You can pass a second argument +scope+ as a callable (i.e. proc or
  1424. # lambda) to retrieve a specific record or customize the generated query
  1425. # when you access the associated object.
  1426. #
  1427. # Scope examples:
  1428. # has_one :author, -> { where(comment_id: 1) }
  1429. # has_one :employer, -> { joins(:company) }
  1430. # has_one :latest_post, ->(blog) { where("created_at > ?", blog.enabled_at) }
  1431. #
  1432. # === Options
  1433. #
  1434. # The declaration can also include an +options+ hash to specialize the behavior of the association.
  1435. #
  1436. # Options are:
  1437. # [:class_name]
  1438. # Specify the class name of the association. Use it only if that name can't be inferred
  1439. # from the association name. So <tt>has_one :manager</tt> will by default be linked to the Manager class, but
  1440. # if the real class name is Person, you'll have to specify it with this option.
  1441. # [:dependent]
  1442. # Controls what happens to the associated object when
  1443. # its owner is destroyed:
  1444. #
  1445. # * <tt>nil</tt> do nothing (default).
  1446. # * <tt>:destroy</tt> causes the associated object to also be destroyed
  1447. # * <tt>:delete</tt> causes the associated object to be deleted directly from the database (so callbacks will not execute)
  1448. # * <tt>:nullify</tt> causes the foreign key to be set to +NULL+. Polymorphic type column is also nullified
  1449. # on polymorphic associations. Callbacks are not executed.
  1450. # * <tt>:restrict_with_exception</tt> causes an <tt>ActiveRecord::DeleteRestrictionError</tt> exception to be raised if there is an associated record
  1451. # * <tt>:restrict_with_error</tt> causes an error to be added to the owner if there is an associated object
  1452. #
  1453. # Note that <tt>:dependent</tt> option is ignored when using <tt>:through</tt> option.
  1454. # [:foreign_key]
  1455. # Specify the foreign key used for the association. By default this is guessed to be the name
  1456. # of this class in lower-case and "_id" suffixed. So a Person class that makes a #has_one association
  1457. # will use "person_id" as the default <tt>:foreign_key</tt>.
  1458. #
  1459. # If you are going to modify the association (rather than just read from it), then it is
  1460. # a good idea to set the <tt>:inverse_of</tt> option.
  1461. # [:foreign_type]
  1462. # Specify the column used to store the associated object's type, if this is a polymorphic
  1463. # association. By default this is guessed to be the name of the polymorphic association
  1464. # specified on "as" option with a "_type" suffix. So a class that defines a
  1465. # <tt>has_one :tag, as: :taggable</tt> association will use "taggable_type" as the
  1466. # default <tt>:foreign_type</tt>.
  1467. # [:primary_key]
  1468. # Specify the method that returns the primary key used for the association. By default this is +id+.
  1469. # [:as]
  1470. # Specifies a polymorphic interface (See #belongs_to).
  1471. # [:through]
  1472. # Specifies a Join Model through which to perform the query. Options for <tt>:class_name</tt>,
  1473. # <tt>:primary_key</tt>, and <tt>:foreign_key</tt> are ignored, as the association uses the
  1474. # source reflection. You can only use a <tt>:through</tt> query through a #has_one
  1475. # or #belongs_to association on the join model.
  1476. #
  1477. # If you are going to modify the association (rather than just read from it), then it is
  1478. # a good idea to set the <tt>:inverse_of</tt> option.
  1479. # [:source]
  1480. # Specifies the source association name used by #has_one <tt>:through</tt> queries.
  1481. # Only use it if the name cannot be inferred from the association.
  1482. # <tt>has_one :favorite, through: :favorites</tt> will look for a
  1483. # <tt>:favorite</tt> on Favorite, unless a <tt>:source</tt> is given.
  1484. # [:source_type]
  1485. # Specifies type of the source association used by #has_one <tt>:through</tt> queries where the source
  1486. # association is a polymorphic #belongs_to.
  1487. # [:validate]
  1488. # When set to +true+, validates new objects added to association when saving the parent object. +false+ by default.
  1489. # If you want to ensure associated objects are revalidated on every update, use +validates_associated+.
  1490. # [:autosave]
  1491. # If true, always save the associated object or destroy it if marked for destruction,
  1492. # when saving the parent object. If false, never save or destroy the associated object.
  1493. # By default, only save the associated object if it's a new record.
  1494. #
  1495. # Note that NestedAttributes::ClassMethods#accepts_nested_attributes_for sets
  1496. # <tt>:autosave</tt> to <tt>true</tt>.
  1497. # [:inverse_of]
  1498. # Specifies the name of the #belongs_to association on the associated object
  1499. # that is the inverse of this #has_one association.
  1500. # See ActiveRecord::Associations::ClassMethods's overview on Bi-directional associations for more detail.
  1501. # [:required]
  1502. # When set to +true+, the association will also have its presence validated.
  1503. # This will validate the association itself, not the id. You can use
  1504. # +:inverse_of+ to avoid an extra query during validation.
  1505. # [:strict_loading]
  1506. # Enforces strict loading every time the associated record is loaded through this association.
  1507. #
  1508. # Option examples:
  1509. # has_one :credit_card, dependent: :destroy # destroys the associated credit card
  1510. # has_one :credit_card, dependent: :nullify # updates the associated records foreign
  1511. # # key value to NULL rather than destroying it
  1512. # has_one :last_comment, -> { order('posted_on') }, class_name: "Comment"
  1513. # has_one :project_manager, -> { where(role: 'project_manager') }, class_name: "Person"
  1514. # has_one :attachment, as: :attachable
  1515. # has_one :boss, -> { readonly }
  1516. # has_one :club, through: :membership
  1517. # has_one :primary_address, -> { where(primary: true) }, through: :addressables, source: :addressable
  1518. # has_one :credit_card, required: true
  1519. # has_one :credit_card, strict_loading: true
  1520. 3 def has_one(name, scope = nil, **options)
  1521. 423 reflection = Builder::HasOne.build(self, name, scope, options)
  1522. 405 Reflection.add_reflection self, name, reflection
  1523. end
  1524. # Specifies a one-to-one association with another class. This method should only be used
  1525. # if this class contains the foreign key. If the other class contains the foreign key,
  1526. # then you should use #has_one instead. See also ActiveRecord::Associations::ClassMethods's overview
  1527. # on when to use #has_one and when to use #belongs_to.
  1528. #
  1529. # Methods will be added for retrieval and query for a single associated object, for which
  1530. # this object holds an id:
  1531. #
  1532. # +association+ is a placeholder for the symbol passed as the +name+ argument, so
  1533. # <tt>belongs_to :author</tt> would add among others <tt>author.nil?</tt>.
  1534. #
  1535. # [association]
  1536. # Returns the associated object. +nil+ is returned if none is found.
  1537. # [association=(associate)]
  1538. # Assigns the associate object, extracts the primary key, and sets it as the foreign key.
  1539. # No modification or deletion of existing records takes place.
  1540. # [build_association(attributes = {})]
  1541. # Returns a new object of the associated type that has been instantiated
  1542. # with +attributes+ and linked to this object through a foreign key, but has not yet been saved.
  1543. # [create_association(attributes = {})]
  1544. # Returns a new object of the associated type that has been instantiated
  1545. # with +attributes+, linked to this object through a foreign key, and that
  1546. # has already been saved (if it passed the validation).
  1547. # [create_association!(attributes = {})]
  1548. # Does the same as <tt>create_association</tt>, but raises ActiveRecord::RecordInvalid
  1549. # if the record is invalid.
  1550. # [reload_association]
  1551. # Returns the associated object, forcing a database read.
  1552. #
  1553. # === Example
  1554. #
  1555. # A Post class declares <tt>belongs_to :author</tt>, which will add:
  1556. # * <tt>Post#author</tt> (similar to <tt>Author.find(author_id)</tt>)
  1557. # * <tt>Post#author=(author)</tt> (similar to <tt>post.author_id = author.id</tt>)
  1558. # * <tt>Post#build_author</tt> (similar to <tt>post.author = Author.new</tt>)
  1559. # * <tt>Post#create_author</tt> (similar to <tt>post.author = Author.new; post.author.save; post.author</tt>)
  1560. # * <tt>Post#create_author!</tt> (similar to <tt>post.author = Author.new; post.author.save!; post.author</tt>)
  1561. # * <tt>Post#reload_author</tt>
  1562. # The declaration can also include an +options+ hash to specialize the behavior of the association.
  1563. #
  1564. # === Scopes
  1565. #
  1566. # You can pass a second argument +scope+ as a callable (i.e. proc or
  1567. # lambda) to retrieve a specific record or customize the generated query
  1568. # when you access the associated object.
  1569. #
  1570. # Scope examples:
  1571. # belongs_to :firm, -> { where(id: 2) }
  1572. # belongs_to :user, -> { joins(:friends) }
  1573. # belongs_to :level, ->(game) { where("game_level > ?", game.current_level) }
  1574. #
  1575. # === Options
  1576. #
  1577. # [:class_name]
  1578. # Specify the class name of the association. Use it only if that name can't be inferred
  1579. # from the association name. So <tt>belongs_to :author</tt> will by default be linked to the Author class, but
  1580. # if the real class name is Person, you'll have to specify it with this option.
  1581. # [:foreign_key]
  1582. # Specify the foreign key used for the association. By default this is guessed to be the name
  1583. # of the association with an "_id" suffix. So a class that defines a <tt>belongs_to :person</tt>
  1584. # association will use "person_id" as the default <tt>:foreign_key</tt>. Similarly,
  1585. # <tt>belongs_to :favorite_person, class_name: "Person"</tt> will use a foreign key
  1586. # of "favorite_person_id".
  1587. #
  1588. # If you are going to modify the association (rather than just read from it), then it is
  1589. # a good idea to set the <tt>:inverse_of</tt> option.
  1590. # [:foreign_type]
  1591. # Specify the column used to store the associated object's type, if this is a polymorphic
  1592. # association. By default this is guessed to be the name of the association with a "_type"
  1593. # suffix. So a class that defines a <tt>belongs_to :taggable, polymorphic: true</tt>
  1594. # association will use "taggable_type" as the default <tt>:foreign_type</tt>.
  1595. # [:primary_key]
  1596. # Specify the method that returns the primary key of associated object used for the association.
  1597. # By default this is +id+.
  1598. # [:dependent]
  1599. # If set to <tt>:destroy</tt>, the associated object is destroyed when this object is. If set to
  1600. # <tt>:delete</tt>, the associated object is deleted *without* calling its destroy method.
  1601. # This option should not be specified when #belongs_to is used in conjunction with
  1602. # a #has_many relationship on another class because of the potential to leave
  1603. # orphaned records behind.
  1604. # [:counter_cache]
  1605. # Caches the number of belonging objects on the associate class through the use of CounterCache::ClassMethods#increment_counter
  1606. # and CounterCache::ClassMethods#decrement_counter. The counter cache is incremented when an object of this
  1607. # class is created and decremented when it's destroyed. This requires that a column
  1608. # named <tt>#{table_name}_count</tt> (such as +comments_count+ for a belonging Comment class)
  1609. # is used on the associate class (such as a Post class) - that is the migration for
  1610. # <tt>#{table_name}_count</tt> is created on the associate class (such that <tt>Post.comments_count</tt> will
  1611. # return the count cached, see note below). You can also specify a custom counter
  1612. # cache column by providing a column name instead of a +true+/+false+ value to this
  1613. # option (e.g., <tt>counter_cache: :my_custom_counter</tt>.)
  1614. # Note: Specifying a counter cache will add it to that model's list of readonly attributes
  1615. # using +attr_readonly+.
  1616. # [:polymorphic]
  1617. # Specify this association is a polymorphic association by passing +true+.
  1618. # Note: If you've enabled the counter cache, then you may want to add the counter cache attribute
  1619. # to the +attr_readonly+ list in the associated classes (e.g. <tt>class Post; attr_readonly :comments_count; end</tt>).
  1620. # [:validate]
  1621. # When set to +true+, validates new objects added to association when saving the parent object. +false+ by default.
  1622. # If you want to ensure associated objects are revalidated on every update, use +validates_associated+.
  1623. # [:autosave]
  1624. # If true, always save the associated object or destroy it if marked for destruction, when
  1625. # saving the parent object.
  1626. # If false, never save or destroy the associated object.
  1627. # By default, only save the associated object if it's a new record.
  1628. #
  1629. # Note that NestedAttributes::ClassMethods#accepts_nested_attributes_for
  1630. # sets <tt>:autosave</tt> to <tt>true</tt>.
  1631. # [:touch]
  1632. # If true, the associated object will be touched (the updated_at/on attributes set to current time)
  1633. # when this record is either saved or destroyed. If you specify a symbol, that attribute
  1634. # will be updated with the current time in addition to the updated_at/on attribute.
  1635. # Please note that with touching no validation is performed and only the +after_touch+,
  1636. # +after_commit+ and +after_rollback+ callbacks are executed.
  1637. # [:inverse_of]
  1638. # Specifies the name of the #has_one or #has_many association on the associated
  1639. # object that is the inverse of this #belongs_to association.
  1640. # See ActiveRecord::Associations::ClassMethods's overview on Bi-directional associations for more detail.
  1641. # [:optional]
  1642. # When set to +true+, the association will not have its presence validated.
  1643. # [:required]
  1644. # When set to +true+, the association will also have its presence validated.
  1645. # This will validate the association itself, not the id. You can use
  1646. # +:inverse_of+ to avoid an extra query during validation.
  1647. # NOTE: <tt>required</tt> is set to <tt>true</tt> by default and is deprecated. If
  1648. # you don't want to have association presence validated, use <tt>optional: true</tt>.
  1649. # [:default]
  1650. # Provide a callable (i.e. proc or lambda) to specify that the association should
  1651. # be initialized with a particular record before validation.
  1652. # [:strict_loading]
  1653. # Enforces strict loading every time the associated record is loaded through this association.
  1654. #
  1655. # Option examples:
  1656. # belongs_to :firm, foreign_key: "client_of"
  1657. # belongs_to :person, primary_key: "name", foreign_key: "person_name"
  1658. # belongs_to :author, class_name: "Person", foreign_key: "author_id"
  1659. # belongs_to :valid_coupon, ->(o) { where "discounts > ?", o.payments_count },
  1660. # class_name: "Coupon", foreign_key: "coupon_id"
  1661. # belongs_to :attachable, polymorphic: true
  1662. # belongs_to :project, -> { readonly }
  1663. # belongs_to :post, counter_cache: true
  1664. # belongs_to :comment, touch: true
  1665. # belongs_to :company, touch: :employees_last_updated_at
  1666. # belongs_to :user, optional: true
  1667. # belongs_to :account, default: -> { company.account }
  1668. # belongs_to :account, strict_loading: true
  1669. 3 def belongs_to(name, scope = nil, **options)
  1670. 1311 reflection = Builder::BelongsTo.build(self, name, scope, options)
  1671. 1293 Reflection.add_reflection self, name, reflection
  1672. end
  1673. # Specifies a many-to-many relationship with another class. This associates two classes via an
  1674. # intermediate join table. Unless the join table is explicitly specified as an option, it is
  1675. # guessed using the lexical order of the class names. So a join between Developer and Project
  1676. # will give the default join table name of "developers_projects" because "D" precedes "P" alphabetically.
  1677. # Note that this precedence is calculated using the <tt><</tt> operator for String. This
  1678. # means that if the strings are of different lengths, and the strings are equal when compared
  1679. # up to the shortest length, then the longer string is considered of higher
  1680. # lexical precedence than the shorter one. For example, one would expect the tables "paper_boxes" and "papers"
  1681. # to generate a join table name of "papers_paper_boxes" because of the length of the name "paper_boxes",
  1682. # but it in fact generates a join table name of "paper_boxes_papers". Be aware of this caveat, and use the
  1683. # custom <tt>:join_table</tt> option if you need to.
  1684. # If your tables share a common prefix, it will only appear once at the beginning. For example,
  1685. # the tables "catalog_categories" and "catalog_products" generate a join table name of "catalog_categories_products".
  1686. #
  1687. # The join table should not have a primary key or a model associated with it. You must manually generate the
  1688. # join table with a migration such as this:
  1689. #
  1690. # class CreateDevelopersProjectsJoinTable < ActiveRecord::Migration[6.0]
  1691. # def change
  1692. # create_join_table :developers, :projects
  1693. # end
  1694. # end
  1695. #
  1696. # It's also a good idea to add indexes to each of those columns to speed up the joins process.
  1697. # However, in MySQL it is advised to add a compound index for both of the columns as MySQL only
  1698. # uses one index per table during the lookup.
  1699. #
  1700. # Adds the following methods for retrieval and query:
  1701. #
  1702. # +collection+ is a placeholder for the symbol passed as the +name+ argument, so
  1703. # <tt>has_and_belongs_to_many :categories</tt> would add among others <tt>categories.empty?</tt>.
  1704. #
  1705. # [collection]
  1706. # Returns a Relation of all the associated objects.
  1707. # An empty Relation is returned if none are found.
  1708. # [collection<<(object, ...)]
  1709. # Adds one or more objects to the collection by creating associations in the join table
  1710. # (<tt>collection.push</tt> and <tt>collection.concat</tt> are aliases to this method).
  1711. # Note that this operation instantly fires update SQL without waiting for the save or update call on the
  1712. # parent object, unless the parent object is a new record.
  1713. # [collection.delete(object, ...)]
  1714. # Removes one or more objects from the collection by removing their associations from the join table.
  1715. # This does not destroy the objects.
  1716. # [collection.destroy(object, ...)]
  1717. # Removes one or more objects from the collection by running destroy on each association in the join table, overriding any dependent option.
  1718. # This does not destroy the objects.
  1719. # [collection=objects]
  1720. # Replaces the collection's content by deleting and adding objects as appropriate.
  1721. # [collection_singular_ids]
  1722. # Returns an array of the associated objects' ids.
  1723. # [collection_singular_ids=ids]
  1724. # Replace the collection by the objects identified by the primary keys in +ids+.
  1725. # [collection.clear]
  1726. # Removes every object from the collection. This does not destroy the objects.
  1727. # [collection.empty?]
  1728. # Returns +true+ if there are no associated objects.
  1729. # [collection.size]
  1730. # Returns the number of associated objects.
  1731. # [collection.find(id)]
  1732. # Finds an associated object responding to the +id+ and that
  1733. # meets the condition that it has to be associated with this object.
  1734. # Uses the same rules as ActiveRecord::FinderMethods#find.
  1735. # [collection.exists?(...)]
  1736. # Checks whether an associated object with the given conditions exists.
  1737. # Uses the same rules as ActiveRecord::FinderMethods#exists?.
  1738. # [collection.build(attributes = {})]
  1739. # Returns a new object of the collection type that has been instantiated
  1740. # with +attributes+ and linked to this object through the join table, but has not yet been saved.
  1741. # [collection.create(attributes = {})]
  1742. # Returns a new object of the collection type that has been instantiated
  1743. # with +attributes+, linked to this object through the join table, and that has already been
  1744. # saved (if it passed the validation).
  1745. # [collection.reload]
  1746. # Returns a Relation of all of the associated objects, forcing a database read.
  1747. # An empty Relation is returned if none are found.
  1748. #
  1749. # === Example
  1750. #
  1751. # A Developer class declares <tt>has_and_belongs_to_many :projects</tt>, which will add:
  1752. # * <tt>Developer#projects</tt>
  1753. # * <tt>Developer#projects<<</tt>
  1754. # * <tt>Developer#projects.delete</tt>
  1755. # * <tt>Developer#projects.destroy</tt>
  1756. # * <tt>Developer#projects=</tt>
  1757. # * <tt>Developer#project_ids</tt>
  1758. # * <tt>Developer#project_ids=</tt>
  1759. # * <tt>Developer#projects.clear</tt>
  1760. # * <tt>Developer#projects.empty?</tt>
  1761. # * <tt>Developer#projects.size</tt>
  1762. # * <tt>Developer#projects.find(id)</tt>
  1763. # * <tt>Developer#projects.exists?(...)</tt>
  1764. # * <tt>Developer#projects.build</tt> (similar to <tt>Project.new(developer_id: id)</tt>)
  1765. # * <tt>Developer#projects.create</tt> (similar to <tt>c = Project.new(developer_id: id); c.save; c</tt>)
  1766. # * <tt>Developer#projects.reload</tt>
  1767. # The declaration may include an +options+ hash to specialize the behavior of the association.
  1768. #
  1769. # === Scopes
  1770. #
  1771. # You can pass a second argument +scope+ as a callable (i.e. proc or
  1772. # lambda) to retrieve a specific set of records or customize the generated
  1773. # query when you access the associated collection.
  1774. #
  1775. # Scope examples:
  1776. # has_and_belongs_to_many :projects, -> { includes(:milestones, :manager) }
  1777. # has_and_belongs_to_many :categories, ->(post) {
  1778. # where("default_category = ?", post.default_category)
  1779. # }
  1780. #
  1781. # === Extensions
  1782. #
  1783. # The +extension+ argument allows you to pass a block into a
  1784. # has_and_belongs_to_many association. This is useful for adding new
  1785. # finders, creators and other factory-type methods to be used as part of
  1786. # the association.
  1787. #
  1788. # Extension examples:
  1789. # has_and_belongs_to_many :contractors do
  1790. # def find_or_create_by_name(name)
  1791. # first_name, last_name = name.split(" ", 2)
  1792. # find_or_create_by(first_name: first_name, last_name: last_name)
  1793. # end
  1794. # end
  1795. #
  1796. # === Options
  1797. #
  1798. # [:class_name]
  1799. # Specify the class name of the association. Use it only if that name can't be inferred
  1800. # from the association name. So <tt>has_and_belongs_to_many :projects</tt> will by default be linked to the
  1801. # Project class, but if the real class name is SuperProject, you'll have to specify it with this option.
  1802. # [:join_table]
  1803. # Specify the name of the join table if the default based on lexical order isn't what you want.
  1804. # <b>WARNING:</b> If you're overwriting the table name of either class, the +table_name+ method
  1805. # MUST be declared underneath any #has_and_belongs_to_many declaration in order to work.
  1806. # [:foreign_key]
  1807. # Specify the foreign key used for the association. By default this is guessed to be the name
  1808. # of this class in lower-case and "_id" suffixed. So a Person class that makes
  1809. # a #has_and_belongs_to_many association to Project will use "person_id" as the
  1810. # default <tt>:foreign_key</tt>.
  1811. #
  1812. # If you are going to modify the association (rather than just read from it), then it is
  1813. # a good idea to set the <tt>:inverse_of</tt> option.
  1814. # [:association_foreign_key]
  1815. # Specify the foreign key used for the association on the receiving side of the association.
  1816. # By default this is guessed to be the name of the associated class in lower-case and "_id" suffixed.
  1817. # So if a Person class makes a #has_and_belongs_to_many association to Project,
  1818. # the association will use "project_id" as the default <tt>:association_foreign_key</tt>.
  1819. # [:validate]
  1820. # When set to +true+, validates new objects added to association when saving the parent object. +true+ by default.
  1821. # If you want to ensure associated objects are revalidated on every update, use +validates_associated+.
  1822. # [:autosave]
  1823. # If true, always save the associated objects or destroy them if marked for destruction, when
  1824. # saving the parent object.
  1825. # If false, never save or destroy the associated objects.
  1826. # By default, only save associated objects that are new records.
  1827. #
  1828. # Note that NestedAttributes::ClassMethods#accepts_nested_attributes_for sets
  1829. # <tt>:autosave</tt> to <tt>true</tt>.
  1830. # [:strict_loading]
  1831. # Enforces strict loading every time an associated record is loaded through this association.
  1832. #
  1833. # Option examples:
  1834. # has_and_belongs_to_many :projects
  1835. # has_and_belongs_to_many :projects, -> { includes(:milestones, :manager) }
  1836. # has_and_belongs_to_many :nations, class_name: "Country"
  1837. # has_and_belongs_to_many :categories, join_table: "prods_cats"
  1838. # has_and_belongs_to_many :categories, -> { readonly }
  1839. # has_and_belongs_to_many :categories, strict_loading: true
  1840. 3 def has_and_belongs_to_many(name, scope = nil, **options, &extension)
  1841. 242 habtm_reflection = ActiveRecord::Reflection::HasAndBelongsToManyReflection.new(name, scope, options, self)
  1842. 242 builder = Builder::HasAndBelongsToMany.new name, self, options
  1843. 242 join_model = builder.through_model
  1844. 242 const_set join_model.name, join_model
  1845. 242 private_constant join_model.name
  1846. 242 middle_reflection = builder.middle_reflection join_model
  1847. 242 Builder::HasMany.define_callbacks self, middle_reflection
  1848. 242 Reflection.add_reflection self, middle_reflection.name, middle_reflection
  1849. 242 middle_reflection.parent_reflection = habtm_reflection
  1850. 242 include Module.new {
  1851. 242 class_eval <<-RUBY, __FILE__, __LINE__ + 1
  1852. def destroy_associations
  1853. association(:#{middle_reflection.name}).delete_all(:delete_all)
  1854. association(:#{name}).reset
  1855. super
  1856. end
  1857. RUBY
  1858. }
  1859. 242 hm_options = {}
  1860. 242 hm_options[:through] = middle_reflection.name
  1861. 242 hm_options[:source] = join_model.right_reflection.name
  1862. 242 [:before_add, :after_add, :before_remove, :after_remove, :autosave, :validate, :join_table, :class_name, :extend, :strict_loading].each do |k|
  1863. 2420 hm_options[k] = options[k] if options.key? k
  1864. end
  1865. 242 has_many name, scope, **hm_options, &extension
  1866. 242 _reflections[name.to_s].parent_reflection = habtm_reflection
  1867. end
  1868. end
  1869. end
  1870. end

lib/active_record/associations/alias_tracker.rb

95.0% lines covered

40 relevant lines. 38 lines covered and 2 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 require "active_support/core_ext/string/conversions"
  3. 3 module ActiveRecord
  4. 3 module Associations
  5. # Keeps track of table aliases for ActiveRecord::Associations::JoinDependency
  6. 3 class AliasTracker # :nodoc:
  7. 3 def self.create(connection, initial_table, joins, aliases = nil)
  8. 13139 if joins.empty?
  9. 13010 aliases ||= Hash.new(0)
  10. 129 elsif aliases
  11. 6 default_proc = aliases.default_proc || proc { 0 }
  12. 6 aliases.default_proc = proc { |h, k|
  13. h[k] = initial_count_for(connection, k, joins) + default_proc.call(h, k)
  14. }
  15. else
  16. 123 aliases = Hash.new { |h, k|
  17. 156 h[k] = initial_count_for(connection, k, joins)
  18. }
  19. end
  20. 13139 aliases[initial_table] = 1
  21. 13139 new(connection, aliases)
  22. end
  23. 3 def self.initial_count_for(connection, name, table_joins)
  24. 156 quoted_name = nil
  25. 156 counts = table_joins.map do |join|
  26. 156 if join.is_a?(Arel::Nodes::StringJoin)
  27. # quoted_name should be case ignored as some database adapters (Oracle) return quoted name in uppercase
  28. 42 quoted_name ||= connection.quote_table_name(name)
  29. # Table names + table aliases
  30. join.left.scan(
  31. /JOIN(?:\s+\w+)?\s+(?:\S+\s+)?(?:#{quoted_name}|#{name})\sON/i
  32. 42 ).size
  33. 114 elsif join.is_a?(Arel::Nodes::Join)
  34. 114 join.left.name == name ? 1 : 0
  35. else
  36. raise ArgumentError, "joins list should be initialized by list of Arel::Nodes::Join"
  37. end
  38. end
  39. 156 counts.sum
  40. end
  41. # table_joins is an array of arel joins which might conflict with the aliases we assign here
  42. 3 def initialize(connection, aliases)
  43. 13139 @aliases = aliases
  44. 13139 @connection = connection
  45. end
  46. 3 def aliased_table_for(arel_table)
  47. 6089 if aliases[arel_table.name] == 0
  48. # If it's zero, we can have our table_name
  49. 5603 aliases[arel_table.name] = 1
  50. else
  51. # Otherwise, we need to use an alias
  52. 486 aliased_name = @connection.table_alias_for(yield)
  53. # Update the count
  54. 486 count = aliases[aliased_name] += 1
  55. 486 aliased_name = "#{truncate(aliased_name)}_#{count}" if count > 1
  56. 486 arel_table = arel_table.alias(aliased_name)
  57. end
  58. 6089 arel_table
  59. end
  60. 3 attr_reader :aliases
  61. 3 private
  62. 3 def truncate(name)
  63. 51 name.slice(0, @connection.table_alias_length - 2)
  64. end
  65. end
  66. end
  67. end

lib/active_record/associations/association.rb

98.48% lines covered

132 relevant lines. 130 lines covered and 2 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 module ActiveRecord
  3. 3 module Associations
  4. # = Active Record Associations
  5. #
  6. # This is the root class of all associations ('+ Foo' signifies an included module Foo):
  7. #
  8. # Association
  9. # SingularAssociation
  10. # HasOneAssociation + ForeignAssociation
  11. # HasOneThroughAssociation + ThroughAssociation
  12. # BelongsToAssociation
  13. # BelongsToPolymorphicAssociation
  14. # CollectionAssociation
  15. # HasManyAssociation + ForeignAssociation
  16. # HasManyThroughAssociation + ThroughAssociation
  17. #
  18. # Associations in Active Record are middlemen between the object that
  19. # holds the association, known as the <tt>owner</tt>, and the associated
  20. # result set, known as the <tt>target</tt>. Association metadata is available in
  21. # <tt>reflection</tt>, which is an instance of <tt>ActiveRecord::Reflection::AssociationReflection</tt>.
  22. #
  23. # For example, given
  24. #
  25. # class Blog < ActiveRecord::Base
  26. # has_many :posts
  27. # end
  28. #
  29. # blog = Blog.first
  30. #
  31. # The association of <tt>blog.posts</tt> has the object +blog+ as its
  32. # <tt>owner</tt>, the collection of its posts as <tt>target</tt>, and
  33. # the <tt>reflection</tt> object represents a <tt>:has_many</tt> macro.
  34. 3 class Association #:nodoc:
  35. 3 attr_reader :owner, :target, :reflection
  36. 3 delegate :options, to: :reflection
  37. 3 def initialize(owner, reflection)
  38. 227995 reflection.check_validity!
  39. 227968 @owner, @reflection = owner, reflection
  40. 227968 reset
  41. 227968 reset_scope
  42. end
  43. # Resets the \loaded flag to +false+ and sets the \target to +nil+.
  44. 3 def reset
  45. 232254 @loaded = false
  46. 232254 @target = nil
  47. 232254 @stale_state = nil
  48. 232254 @inversed = false
  49. end
  50. 3 def reset_negative_cache # :nodoc:
  51. 57 reset if loaded? && target.nil?
  52. end
  53. # Reloads the \target and returns +self+ on success.
  54. # The QueryCache is cleared if +force+ is true.
  55. 3 def reload(force = false)
  56. 2395 klass.connection.clear_query_cache if force && klass
  57. 2395 reset
  58. 2395 reset_scope
  59. 2395 load_target
  60. 2377 self unless target.nil?
  61. end
  62. # Has the \target been already \loaded?
  63. 3 def loaded?
  64. 68652 @loaded
  65. end
  66. # Asserts the \target has been loaded setting the \loaded flag to +true+.
  67. 3 def loaded!
  68. 233189 @loaded = true
  69. 233189 @stale_state = stale_state
  70. 233186 @inversed = false
  71. end
  72. # The target is stale if the target no longer points to the record(s) that the
  73. # relevant foreign_key(s) refers to. If stale, the association accessor method
  74. # on the owner will reload the target. It's up to subclasses to implement the
  75. # stale_state method if relevant.
  76. #
  77. # Note that if the target has not been loaded, it is not considered stale.
  78. 3 def stale_target?
  79. 26190 !@inversed && loaded? && @stale_state != stale_state
  80. end
  81. # Sets the target of this association to <tt>\target</tt>, and the \loaded flag to +true+.
  82. 3 def target=(target)
  83. 217452 @target = target
  84. 217452 loaded!
  85. end
  86. 3 def scope
  87. 14563 if (scope = klass.current_scope) && scope.try(:proxy_association) == self
  88. 72 scope.spawn
  89. else
  90. 14491 target_scope.merge!(association_scope)
  91. end
  92. end
  93. 3 def reset_scope
  94. 231846 @association_scope = nil
  95. end
  96. # Set the inverse association, if possible
  97. 3 def set_inverse_instance(record)
  98. 22978 if inverse = inverse_association_for(record)
  99. 8617 inverse.inversed_from(owner)
  100. end
  101. 22972 record
  102. end
  103. 3 def set_inverse_instance_from_queries(record)
  104. 4483 if inverse = inverse_association_for(record)
  105. 1348 inverse.inversed_from_queries(owner)
  106. end
  107. 4483 record
  108. end
  109. # Remove the inverse association, if possible
  110. 3 def remove_inverse_instance(record)
  111. 80 if inverse = inverse_association_for(record)
  112. 57 inverse.inversed_from(nil)
  113. end
  114. end
  115. 3 def inversed_from(record)
  116. 10022 self.target = record
  117. 10022 @inversed = !!record
  118. end
  119. 3 alias :inversed_from_queries :inversed_from
  120. # Returns the class of the target. belongs_to polymorphic overrides this to look at the
  121. # polymorphic_type field on the owner.
  122. 3 def klass
  123. 544518 reflection.klass
  124. end
  125. 3 def extensions
  126. 6730 extensions = klass.default_extensions | reflection.extensions
  127. 6730 if reflection.scope
  128. 1640 extensions |= reflection.scope_for(klass.unscoped, owner).extensions
  129. end
  130. 6730 extensions
  131. end
  132. # Loads the \target if needed and returns it.
  133. #
  134. # This method is abstract in the sense that it relies on +find_target+,
  135. # which is expected to be provided by descendants.
  136. #
  137. # If the \target is already \loaded it is just returned. Thus, you can call
  138. # +load_target+ unconditionally to get the \target.
  139. #
  140. # ActiveRecord::RecordNotFound is rescued within the method, and it is
  141. # not reraised. The proxy is \reset and +nil+ is the return value.
  142. 3 def load_target
  143. 7905 @target = find_target if (@stale_state && stale_target?) || find_target?
  144. 7890 loaded! unless loaded?
  145. 7887 target
  146. rescue ActiveRecord::RecordNotFound
  147. reset
  148. end
  149. # We can't dump @reflection and @through_reflection since it contains the scope proc
  150. 3 def marshal_dump
  151. 339 ivars = (instance_variables - [:@reflection, :@through_reflection]).map { |name| [name, instance_variable_get(name)] }
  152. 39 [@reflection.name, ivars]
  153. end
  154. 3 def marshal_load(data)
  155. 46 reflection_name, ivars = data
  156. 402 ivars.each { |name, val| instance_variable_set(name, val) }
  157. 46 @reflection = @owner.class._reflect_on_association(reflection_name)
  158. end
  159. 3 def initialize_attributes(record, except_from_scope_attributes = nil) #:nodoc:
  160. 4271 except_from_scope_attributes ||= {}
  161. 4271 skip_assign = [reflection.foreign_key, reflection.type].compact
  162. 4271 assigned_keys = record.changed_attribute_names_to_save
  163. 4271 assigned_keys += except_from_scope_attributes.keys.map(&:to_s)
  164. 4271 attributes = scope_for_create.except!(*(assigned_keys - skip_assign))
  165. 4271 record.send(:_assign_attributes, attributes) if attributes.any?
  166. 4268 set_inverse_instance(record)
  167. end
  168. 3 def create(attributes = nil, &block)
  169. 1090 _create_record(attributes, &block)
  170. end
  171. 3 def create!(attributes = nil, &block)
  172. 967 _create_record(attributes, true, &block)
  173. end
  174. 3 private
  175. 3 def find_target
  176. 4943 if owner.strict_loading?
  177. 27 raise StrictLoadingViolationError, "#{owner.class} is marked as strict_loading and #{klass} cannot be lazily loaded."
  178. end
  179. 4916 if reflection.strict_loading?
  180. 12 raise StrictLoadingViolationError, "The #{reflection.name} association is marked as strict_loading and cannot be lazily loaded."
  181. end
  182. 4904 scope = self.scope
  183. 4904 return scope.to_a if skip_statement_cache?(scope)
  184. 3218 sc = reflection.association_scope_cache(klass, owner) do |params|
  185. 1484 as = AssociationScope.create { params.bind }
  186. 700 target_scope.merge!(as.scope(self))
  187. end
  188. 3218 binds = AssociationScope.get_bind_values(owner, reflection.chain)
  189. 6387 sc.execute(binds, klass.connection) { |record| set_inverse_instance(record) }
  190. end
  191. # The scope for this association.
  192. #
  193. # Note that the association_scope is merged into the target_scope only when the
  194. # scope method is called. This is because at that point the call may be surrounded
  195. # by scope.scoping { ... } or unscoped { ... } etc, which affects the scope which
  196. # actually gets built.
  197. 3 def association_scope
  198. 20390 if klass
  199. 20390 @association_scope ||= AssociationScope.scope(self)
  200. end
  201. end
  202. # Can be overridden (i.e. in ThroughAssociation) to merge in other scopes (i.e. the
  203. # through association's scope)
  204. 3 def target_scope
  205. 15191 AssociationRelation.create(klass, self).merge!(klass.scope_for_association)
  206. end
  207. 3 def scope_for_create
  208. 4271 scope.scope_for_create
  209. end
  210. 3 def find_target?
  211. 10212 !loaded? && (!owner.new_record? || foreign_key_present?) && klass
  212. end
  213. # Returns true if there is a foreign key present on the owner which
  214. # references the target. This is used to determine whether we can load
  215. # the target if the owner is currently a new record (and therefore
  216. # without a key). If the owner is a new record then foreign_key must
  217. # be present in order to load target.
  218. #
  219. # Currently implemented by belongs_to (vanilla and polymorphic) and
  220. # has_one/has_many :through associations which go through a belongs_to.
  221. 3 def foreign_key_present?
  222. false
  223. end
  224. # Raises ActiveRecord::AssociationTypeMismatch unless +record+ is of
  225. # the kind of the class of the associated objects. Meant to be used as
  226. # a sanity check when you are about to assign an associated record.
  227. 3 def raise_on_type_mismatch!(record)
  228. 5210 unless record.is_a?(reflection.klass)
  229. 45 fresh_class = reflection.class_name.safe_constantize
  230. 45 unless fresh_class && record.is_a?(fresh_class)
  231. 42 message = "#{reflection.class_name}(##{reflection.klass.object_id}) expected, "\
  232. "got #{record.inspect} which is an instance of #{record.class}(##{record.class.object_id})"
  233. 42 raise ActiveRecord::AssociationTypeMismatch, message
  234. end
  235. end
  236. end
  237. 3 def inverse_association_for(record)
  238. 27541 if invertible_for?(record)
  239. 10022 record.association(inverse_reflection_for(record).name)
  240. end
  241. end
  242. # Can be redefined by subclasses, notably polymorphic belongs_to
  243. # The record parameter is necessary to support polymorphic inverses as we must check for
  244. # the association in the specific class of the record.
  245. 3 def inverse_reflection_for(record)
  246. 31972 reflection.inverse_of
  247. end
  248. # Returns true if inverse association on the given record needs to be set.
  249. # This method is redefined by subclasses.
  250. 3 def invertible_for?(record)
  251. 17464 foreign_key_for?(record) && inverse_reflection_for(record)
  252. end
  253. # Returns true if record contains the foreign_key
  254. 3 def foreign_key_for?(record)
  255. 17464 record._has_attribute?(reflection.foreign_key)
  256. end
  257. # This should be implemented to return the values of the relevant key(s) on the owner,
  258. # so that when stale_state is different from the value stored on the last find_target,
  259. # the target is stale.
  260. #
  261. # This is only relevant to certain associations, which is why it returns +nil+ by default.
  262. 3 def stale_state
  263. end
  264. 3 def build_record(attributes)
  265. 4292 reflection.build_association(attributes) do |record|
  266. 4265 initialize_attributes(record, attributes)
  267. 4262 yield(record) if block_given?
  268. end
  269. end
  270. # Returns true if statement cache should be skipped on the association reader.
  271. 3 def skip_statement_cache?(scope)
  272. 4904 reflection.has_scope? ||
  273. scope.eager_loading? ||
  274. klass.scope_attributes? ||
  275. reflection.source_reflection.active_record.default_scopes.any?
  276. end
  277. end
  278. end
  279. end

lib/active_record/associations/association_scope.rb

100.0% lines covered

98 relevant lines. 98 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 module ActiveRecord
  3. 3 module Associations
  4. 3 class AssociationScope #:nodoc:
  5. 3 def self.scope(association)
  6. 10325 INSTANCE.scope(association)
  7. end
  8. 3 def self.create(&block)
  9. 11862 block ||= lambda { |val| val }
  10. 703 new(block)
  11. end
  12. 3 def initialize(value_transformation)
  13. 703 @value_transformation = value_transformation
  14. end
  15. 3 INSTANCE = create
  16. 3 def scope(association)
  17. 11025 klass = association.klass
  18. 11025 reflection = association.reflection
  19. 11025 scope = klass.unscoped
  20. 11025 owner = association.owner
  21. 11025 chain = get_chain(reflection, association, scope.alias_tracker)
  22. 11025 scope.extending! reflection.extensions
  23. 11025 scope = add_constraints(scope, owner, chain)
  24. 11025 scope.limit!(1) unless reflection.collection?
  25. 11025 scope
  26. end
  27. 3 def self.get_bind_values(owner, chain)
  28. 3218 binds = []
  29. 3218 last_reflection = chain.last
  30. 3218 binds << last_reflection.join_id_for(owner)
  31. 3218 if last_reflection.type
  32. 246 binds << owner.class.polymorphic_name
  33. end
  34. 3218 chain.each_cons(2).each do |reflection, next_reflection|
  35. 672 if reflection.type
  36. 15 binds << next_reflection.klass.polymorphic_name
  37. end
  38. end
  39. 3218 binds
  40. end
  41. 3 private
  42. 3 attr_reader :value_transformation
  43. 3 def join(table, constraint)
  44. 2950 Arel::Nodes::LeadingJoin.new(table, Arel::Nodes::On.new(constraint))
  45. end
  46. 3 def last_chain_scope(scope, reflection, owner)
  47. 11025 primary_key = reflection.join_primary_key
  48. 11025 foreign_key = reflection.join_foreign_key
  49. 11025 table = reflection.aliased_table
  50. 11025 value = transform_value(owner[foreign_key])
  51. 11025 scope = apply_scope(scope, table, primary_key, value)
  52. 11025 if reflection.type
  53. 855 polymorphic_type = transform_value(owner.class.polymorphic_name)
  54. 855 scope = apply_scope(scope, table, reflection.type, polymorphic_type)
  55. end
  56. 11025 scope
  57. end
  58. 3 def transform_value(value)
  59. 11943 value_transformation.call(value)
  60. end
  61. 3 def next_chain_scope(scope, reflection, next_reflection)
  62. 2950 primary_key = reflection.join_primary_key
  63. 2950 foreign_key = reflection.join_foreign_key
  64. 2950 table = reflection.aliased_table
  65. 2950 foreign_table = next_reflection.aliased_table
  66. 2950 constraint = table[primary_key].eq(foreign_table[foreign_key])
  67. 2950 if reflection.type
  68. 63 value = transform_value(next_reflection.klass.polymorphic_name)
  69. 63 scope = apply_scope(scope, table, reflection.type, value)
  70. end
  71. 2950 scope.joins!(join(foreign_table, constraint))
  72. end
  73. 3 class ReflectionProxy < SimpleDelegator # :nodoc:
  74. 3 attr_reader :aliased_table
  75. 3 def initialize(reflection, aliased_table)
  76. 2950 super(reflection)
  77. 2950 @aliased_table = aliased_table
  78. end
  79. 3 def all_includes; nil; end
  80. end
  81. 3 def get_chain(reflection, association, tracker)
  82. 11025 name = reflection.name
  83. 11025 chain = [Reflection::RuntimeReflection.new(reflection, association)]
  84. 11025 reflection.chain.drop(1).each do |refl|
  85. 2950 aliased_table = tracker.aliased_table_for(refl.klass.arel_table) do
  86. 39 refl.alias_candidate(name)
  87. end
  88. 2950 chain << ReflectionProxy.new(refl, aliased_table)
  89. end
  90. 11025 chain
  91. end
  92. 3 def add_constraints(scope, owner, chain)
  93. 11025 scope = last_chain_scope(scope, chain.last, owner)
  94. 11025 chain.each_cons(2) do |reflection, next_reflection|
  95. 2950 scope = next_chain_scope(scope, reflection, next_reflection)
  96. end
  97. 11025 chain_head = chain.first
  98. 11025 chain.reverse_each do |reflection|
  99. # Exclude the scope of the association itself, because that
  100. # was already merged in the #scope method.
  101. 13975 reflection.constraints.each do |scope_chain_item|
  102. 2675 item = eval_scope(reflection, scope_chain_item, owner)
  103. 2675 if scope_chain_item == chain_head.scope
  104. 2132 scope.merge! item.except(:where, :includes, :unscope, :order)
  105. 543 elsif !item.references_values.empty?
  106. 36 join_dependency = item.construct_join_dependency(
  107. item.eager_load_values | item.includes_values, Arel::Nodes::OuterJoin
  108. )
  109. 36 scope.joins!(*item.joins_values, join_dependency)
  110. 36 scope.left_outer_joins!(*item.left_outer_joins_values)
  111. end
  112. 2675 reflection.all_includes do
  113. 2429 scope.includes_values |= item.includes_values
  114. end
  115. 2675 scope.unscope!(*item.unscope_values)
  116. 2675 scope.where_clause += item.where_clause
  117. 2675 scope.order_values = item.order_values | scope.order_values
  118. end
  119. end
  120. 11025 scope
  121. end
  122. 3 def apply_scope(scope, table, key, value)
  123. 11943 if scope.table == table
  124. 8861 scope.where!(key => value)
  125. else
  126. 3082 scope.where!(table.name => { key => value })
  127. end
  128. end
  129. 3 def eval_scope(reflection, scope, owner)
  130. 2675 relation = reflection.build_scope(reflection.aliased_table)
  131. 2675 relation.instance_exec(owner, &scope) || relation
  132. end
  133. end
  134. end
  135. end

lib/active_record/associations/belongs_to_association.rb

100.0% lines covered

64 relevant lines. 64 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 module ActiveRecord
  3. 3 module Associations
  4. # = Active Record Belongs To Association
  5. 3 class BelongsToAssociation < SingularAssociation #:nodoc:
  6. 3 def handle_dependency
  7. 102 return unless load_target
  8. 45 case options[:dependent]
  9. when :destroy
  10. 33 target.destroy
  11. 33 raise ActiveRecord::Rollback unless target.destroyed?
  12. else
  13. 12 target.send(options[:dependent])
  14. end
  15. end
  16. 3 def inversed_from(record)
  17. 7872 replace_keys(record)
  18. 7872 super
  19. end
  20. 3 def default(&block)
  21. 15 writer(owner.instance_exec(&block)) if reader.nil?
  22. end
  23. 3 def reset
  24. 213445 super
  25. 213445 @updated = false
  26. end
  27. 3 def updated?
  28. 3863 @updated
  29. end
  30. 3 def decrement_counters
  31. 131 update_counters(-1)
  32. end
  33. 3 def increment_counters
  34. 950 update_counters(1)
  35. end
  36. 3 def decrement_counters_before_last_save
  37. 114 if reflection.polymorphic?
  38. 27 model_was = owner.attribute_before_last_save(reflection.foreign_type)&.constantize
  39. else
  40. 87 model_was = klass
  41. end
  42. 114 foreign_key_was = owner.attribute_before_last_save(reflection.foreign_key)
  43. 114 if foreign_key_was && model_was < ActiveRecord::Base
  44. 51 update_counters_via_scope(model_was, foreign_key_was, -1)
  45. end
  46. end
  47. 3 def target_changed?
  48. 527 owner.saved_change_to_attribute?(reflection.foreign_key)
  49. end
  50. 3 private
  51. 3 def replace(record)
  52. 2248 if record
  53. 2206 raise_on_type_mismatch!(record)
  54. 2197 set_inverse_instance(record)
  55. 2191 @updated = true
  56. end
  57. 2233 replace_keys(record)
  58. 2227 self.target = record
  59. end
  60. 3 def update_counters(by)
  61. 1081 if require_counter_update? && foreign_key_present?
  62. 593 if target && !stale_target?
  63. 345 target.increment!(reflection.counter_cache_column, by, touch: reflection.options[:touch])
  64. else
  65. 248 update_counters_via_scope(klass, owner._read_attribute(reflection.foreign_key), by)
  66. end
  67. end
  68. end
  69. 3 def update_counters_via_scope(klass, foreign_key, by)
  70. 299 scope = klass.unscoped.where!(primary_key(klass) => foreign_key)
  71. 299 scope.update_counters(reflection.counter_cache_column => by, touch: reflection.options[:touch])
  72. end
  73. 3 def find_target?
  74. 5822 !loaded? && foreign_key_present? && klass
  75. end
  76. 3 def require_counter_update?
  77. 1081 reflection.counter_cache_column && owner.persisted?
  78. end
  79. 3 def replace_keys(record)
  80. 10105 owner[reflection.foreign_key] = record ? record._read_attribute(primary_key(record.class)) : nil
  81. end
  82. 3 def primary_key(klass)
  83. 10305 reflection.association_primary_key(klass)
  84. end
  85. 3 def foreign_key_present?
  86. 2637 owner._read_attribute(reflection.foreign_key)
  87. end
  88. 3 def invertible_for?(record)
  89. 5482 inverse = inverse_reflection_for(record)
  90. 5476 inverse && (inverse.has_one? || ActiveRecord::Base.has_many_inversing)
  91. end
  92. 3 def stale_state
  93. 222757 result = owner._read_attribute(reflection.foreign_key) { |n| owner.send(:missing_attribute, n, caller) }
  94. 222751 result && result.to_s
  95. end
  96. end
  97. end
  98. end

lib/active_record/associations/belongs_to_polymorphic_association.rb

100.0% lines covered

18 relevant lines. 18 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 module ActiveRecord
  3. 3 module Associations
  4. # = Active Record Belongs To Polymorphic Association
  5. 3 class BelongsToPolymorphicAssociation < BelongsToAssociation #:nodoc:
  6. 3 def klass
  7. 3393 type = owner[reflection.foreign_type]
  8. 3393 type.presence && owner.class.polymorphic_class_for(type)
  9. end
  10. 3 def target_changed?
  11. 44 super || owner.saved_change_to_attribute?(reflection.foreign_type)
  12. end
  13. 3 private
  14. 3 def replace_keys(record)
  15. 909 super
  16. 909 owner[reflection.foreign_type] = record ? record.class.polymorphic_name : nil
  17. end
  18. 3 def inverse_reflection_for(record)
  19. 870 reflection.polymorphic_inverse_of(record.class)
  20. end
  21. 3 def raise_on_type_mismatch!(record)
  22. # A polymorphic association cannot have a type mismatch, by definition
  23. end
  24. 3 def stale_state
  25. 3243 foreign_key = super
  26. 3243 foreign_key && [foreign_key.to_s, owner[reflection.foreign_type].to_s]
  27. end
  28. end
  29. end
  30. end

lib/active_record/associations/builder/association.rb

96.49% lines covered

57 relevant lines. 55 lines covered and 2 lines missed.
    
  1. # frozen_string_literal: true
  2. # This is the parent Association class which defines the variables
  3. # used by all associations.
  4. #
  5. # The hierarchy is defined as follows:
  6. # Association
  7. # - SingularAssociation
  8. # - BelongsToAssociation
  9. # - HasOneAssociation
  10. # - CollectionAssociation
  11. # - HasManyAssociation
  12. 3 module ActiveRecord::Associations::Builder # :nodoc:
  13. 3 class Association #:nodoc:
  14. 3 class << self
  15. 3 attr_accessor :extensions
  16. end
  17. 3 self.extensions = []
  18. 3 VALID_OPTIONS = [
  19. :class_name, :anonymous_class, :primary_key, :foreign_key, :dependent, :validate, :inverse_of, :strict_loading
  20. ].freeze # :nodoc:
  21. 3 def self.build(model, name, scope, options, &block)
  22. 3399 if model.dangerous_attribute_method?(name)
  23. 36 raise ArgumentError, "You tried to define an association named #{name} on the model #{model.name}, but " \
  24. "this will conflict with a method #{name} already defined by Active Record. " \
  25. "Please choose a different association name."
  26. end
  27. 3363 reflection = create_reflection(model, name, scope, options, &block)
  28. 3351 define_accessors model, reflection
  29. 3351 define_callbacks model, reflection
  30. 3348 define_validations model, reflection
  31. 3348 reflection
  32. end
  33. 3 def self.create_reflection(model, name, scope, options, &block)
  34. 3605 raise ArgumentError, "association names must be a Symbol" unless name.kind_of?(Symbol)
  35. 3602 validate_options(options)
  36. 3593 extension = define_extensions(model, name, &block)
  37. 3593 options[:extend] = [*options[:extend], extension] if extension
  38. 3593 scope = build_scope(scope)
  39. 3593 ActiveRecord::Reflection.create(macro, name, scope, options, model)
  40. end
  41. 3 def self.build_scope(scope)
  42. 3593 if scope && scope.arity == 0
  43. 5713 proc { instance_exec(&scope) }
  44. else
  45. 3077 scope
  46. end
  47. end
  48. 3 def self.macro
  49. raise NotImplementedError
  50. end
  51. 3 def self.valid_options(options)
  52. 3602 VALID_OPTIONS + Association.extensions.flat_map(&:valid_options)
  53. end
  54. 3 def self.validate_options(options)
  55. 3602 options.assert_valid_keys(valid_options(options))
  56. end
  57. 3 def self.define_extensions(model, name)
  58. end
  59. 3 def self.define_callbacks(model, reflection)
  60. 3593 if dependent = reflection.options[:dependent]
  61. 225 check_dependent_options(dependent)
  62. 222 add_destroy_callbacks(model, reflection)
  63. end
  64. 3590 Association.extensions.each do |extension|
  65. 3590 extension.build model, reflection
  66. end
  67. end
  68. # Defines the setter and getter methods for the association
  69. # class Post < ActiveRecord::Base
  70. # has_many :comments
  71. # end
  72. #
  73. # Post.first.comments and Post.first.comments= methods are defined by this method...
  74. 3 def self.define_accessors(model, reflection)
  75. 3351 mixin = model.generated_association_methods
  76. 3351 name = reflection.name
  77. 3351 define_readers(mixin, name)
  78. 3351 define_writers(mixin, name)
  79. end
  80. 3 def self.define_readers(mixin, name)
  81. 3351 mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1
  82. def #{name}
  83. association(:#{name}).reader
  84. end
  85. CODE
  86. end
  87. 3 def self.define_writers(mixin, name)
  88. 3351 mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1
  89. def #{name}=(value)
  90. association(:#{name}).writer(value)
  91. end
  92. CODE
  93. end
  94. 3 def self.define_validations(model, reflection)
  95. # noop
  96. end
  97. 3 def self.valid_dependent_options
  98. raise NotImplementedError
  99. end
  100. 3 def self.check_dependent_options(dependent)
  101. 225 unless valid_dependent_options.include? dependent
  102. 3 raise ArgumentError, "The :dependent option must be one of #{valid_dependent_options}, but is :#{dependent}"
  103. end
  104. end
  105. 3 def self.add_destroy_callbacks(model, reflection)
  106. 207 name = reflection.name
  107. 1156 model.before_destroy lambda { |o| o.association(name).handle_dependency }
  108. end
  109. 3 private_class_method :build_scope, :macro, :valid_options, :validate_options, :define_extensions,
  110. :define_callbacks, :define_accessors, :define_readers, :define_writers, :define_validations,
  111. :valid_dependent_options, :check_dependent_options, :add_destroy_callbacks
  112. end
  113. end

lib/active_record/associations/builder/belongs_to.rb

98.67% lines covered

75 relevant lines. 74 lines covered and 1 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 module ActiveRecord::Associations::Builder # :nodoc:
  3. 3 class BelongsTo < SingularAssociation #:nodoc:
  4. 3 def self.macro
  5. 1296 :belongs_to
  6. end
  7. 3 def self.valid_options(options)
  8. 1296 valid = super + [:counter_cache, :optional, :default]
  9. 1296 valid += [:polymorphic, :foreign_type] if options[:polymorphic]
  10. 1296 valid
  11. end
  12. 3 def self.valid_dependent_options
  13. 21 [:destroy, :delete]
  14. end
  15. 3 def self.define_callbacks(model, reflection)
  16. 1296 super
  17. 1293 add_counter_cache_callbacks(model, reflection) if reflection.options[:counter_cache]
  18. 1293 add_touch_callbacks(model, reflection) if reflection.options[:touch]
  19. 1293 add_default_callbacks(model, reflection) if reflection.options[:default]
  20. end
  21. 3 def self.add_counter_cache_callbacks(model, reflection)
  22. 57 cache_column = reflection.counter_cache_column
  23. 57 model.after_update lambda { |record|
  24. 377 association = association(reflection.name)
  25. 377 if association.target_changed?
  26. 114 association.increment_counters
  27. 114 association.decrement_counters_before_last_save
  28. end
  29. }
  30. 57 klass = reflection.class_name.safe_constantize
  31. 57 klass.attr_readonly cache_column if klass && klass.respond_to?(:attr_readonly)
  32. end
  33. 3 def self.touch_record(o, changes, foreign_key, name, touch, touch_method) # :nodoc:
  34. 636 old_foreign_id = changes[foreign_key] && changes[foreign_key].first
  35. 636 if old_foreign_id
  36. 24 association = o.association(name)
  37. 24 reflection = association.reflection
  38. 24 if reflection.polymorphic?
  39. 6 foreign_type = reflection.foreign_type
  40. 6 klass = changes[foreign_type] && changes[foreign_type].first || o.public_send(foreign_type)
  41. 6 klass = klass.constantize
  42. else
  43. 18 klass = association.klass
  44. end
  45. 24 primary_key = reflection.association_primary_key(klass)
  46. 24 old_record = klass.find_by(primary_key => old_foreign_id)
  47. 24 if old_record
  48. 21 if touch != true
  49. old_record.send(touch_method, touch)
  50. else
  51. 21 old_record.send(touch_method)
  52. end
  53. end
  54. end
  55. 636 record = o.send name
  56. 636 if record && record.persisted?
  57. 387 if touch != true
  58. 6 record.send(touch_method, touch)
  59. else
  60. 381 record.send(touch_method)
  61. end
  62. end
  63. end
  64. 3 def self.add_touch_callbacks(model, reflection)
  65. 48 foreign_key = reflection.foreign_key
  66. 48 name = reflection.name
  67. 48 touch = reflection.options[:touch]
  68. 222 callback = lambda { |changes_method| lambda { |record|
  69. 588 BelongsTo.touch_record(record, record.send(changes_method), foreign_key, name, touch, belongs_to_touch_method)
  70. }}
  71. 48 if reflection.counter_cache_column
  72. 9 touch_callback = callback.(:saved_changes)
  73. 9 update_callback = lambda { |record|
  74. 150 instance_exec(record, &touch_callback) unless association(reflection.name).target_changed?
  75. }
  76. 9 model.after_update update_callback, if: :saved_changes?
  77. else
  78. 39 model.after_create callback.(:saved_changes), if: :saved_changes?
  79. 39 model.after_update callback.(:saved_changes), if: :saved_changes?
  80. 39 model.after_destroy callback.(:changes_to_save)
  81. end
  82. 48 model.after_touch callback.(:changes_to_save)
  83. end
  84. 3 def self.add_default_callbacks(model, reflection)
  85. 6 model.before_validation lambda { |o|
  86. 15 o.association(reflection.name).default(&reflection.options[:default])
  87. }
  88. end
  89. 3 def self.add_destroy_callbacks(model, reflection)
  90. 117 model.after_destroy lambda { |o| o.association(reflection.name).handle_dependency }
  91. end
  92. 3 def self.define_validations(model, reflection)
  93. 1293 if reflection.options.key?(:required)
  94. 490 reflection.options[:optional] = !reflection.options.delete(:required)
  95. end
  96. 1293 if reflection.options[:optional].nil?
  97. 794 required = model.belongs_to_required_by_default
  98. else
  99. 499 required = !reflection.options[:optional]
  100. end
  101. 1293 super
  102. 1293 if required
  103. 18 model.validates_presence_of reflection.name, message: :required
  104. end
  105. end
  106. 3 private_class_method :macro, :valid_options, :valid_dependent_options, :define_callbacks, :define_validations,
  107. :add_counter_cache_callbacks, :add_touch_callbacks, :add_default_callbacks, :add_destroy_callbacks
  108. end
  109. end

lib/active_record/associations/builder/collection_association.rb

96.97% lines covered

33 relevant lines. 32 lines covered and 1 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 require "active_record/associations"
  3. 3 module ActiveRecord::Associations::Builder # :nodoc:
  4. 3 class CollectionAssociation < Association #:nodoc:
  5. 3 CALLBACKS = [:before_add, :after_add, :before_remove, :after_remove]
  6. 3 def self.valid_options(options)
  7. 1895 super + [:before_add, :after_add, :before_remove, :after_remove, :extend]
  8. end
  9. 3 def self.define_callbacks(model, reflection)
  10. 1892 super
  11. 1892 name = reflection.name
  12. 1892 options = reflection.options
  13. 1892 CALLBACKS.each { |callback_name|
  14. 7568 define_callback(model, callback_name, name, options)
  15. }
  16. end
  17. 3 def self.define_extensions(model, name, &block)
  18. 1898 if block_given?
  19. 30 extension_module_name = "#{name.to_s.camelize}AssociationExtension"
  20. 30 extension = Module.new(&block)
  21. 30 model.const_set(extension_module_name, extension)
  22. end
  23. end
  24. 3 def self.define_callback(model, callback_name, name, options)
  25. 7568 full_callback_name = "#{callback_name}_for_#{name}"
  26. # TODO : why do i need method_defined? I think its because of the inheritance chain
  27. 7568 model.class_attribute full_callback_name unless model.method_defined?(full_callback_name)
  28. 7568 callbacks = Array(options[callback_name.to_sym]).map do |callback|
  29. 168 case callback
  30. when Symbol
  31. 336 ->(method, owner, record) { owner.send(callback, record) }
  32. when Proc
  33. 330 ->(method, owner, record) { callback.call(owner, record) }
  34. else
  35. ->(method, owner, record) { callback.send(method, owner, record) }
  36. end
  37. end
  38. 7568 model.send "#{full_callback_name}=", callbacks
  39. end
  40. # Defines the setter and getter methods for the collection_singular_ids.
  41. 3 def self.define_readers(mixin, name)
  42. 1650 super
  43. 1650 mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1
  44. def #{name.to_s.singularize}_ids
  45. association(:#{name}).ids_reader
  46. end
  47. CODE
  48. end
  49. 3 def self.define_writers(mixin, name)
  50. 1650 super
  51. 1650 mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1
  52. def #{name.to_s.singularize}_ids=(ids)
  53. association(:#{name}).ids_writer(ids)
  54. end
  55. CODE
  56. end
  57. 3 private_class_method :valid_options, :define_callback, :define_extensions, :define_readers, :define_writers
  58. end
  59. end

lib/active_record/associations/builder/has_and_belongs_to_many.rb

100.0% lines covered

63 relevant lines. 63 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 module ActiveRecord::Associations::Builder # :nodoc:
  3. 3 class HasAndBelongsToMany # :nodoc:
  4. 3 attr_reader :lhs_model, :association_name, :options
  5. 3 def initialize(association_name, lhs_model, options)
  6. 242 @association_name = association_name
  7. 242 @lhs_model = lhs_model
  8. 242 @options = options
  9. end
  10. 3 def through_model
  11. 242 join_model = Class.new(ActiveRecord::Base) {
  12. 242 class << self
  13. 242 attr_accessor :left_model
  14. 242 attr_accessor :name
  15. 242 attr_accessor :table_name_resolver
  16. 242 attr_accessor :left_reflection
  17. 242 attr_accessor :right_reflection
  18. end
  19. 242 def self.table_name
  20. # Table name needs to be resolved lazily
  21. # because RHS class might not have been loaded
  22. 2881 @table_name ||= table_name_resolver.call
  23. end
  24. 242 def self.compute_type(class_name)
  25. 190 left_model.compute_type class_name
  26. end
  27. 242 def self.add_left_association(name, options)
  28. 242 belongs_to name, required: false, **options
  29. 242 self.left_reflection = _reflect_on_association(name)
  30. end
  31. 242 def self.add_right_association(name, options)
  32. 242 rhs_name = name.to_s.singularize.to_sym
  33. 242 belongs_to rhs_name, required: false, **options
  34. 242 self.right_reflection = _reflect_on_association(rhs_name)
  35. end
  36. 242 def self.retrieve_connection
  37. 5174 left_model.retrieve_connection
  38. end
  39. 242 private
  40. 242 def self.suppress_composite_primary_key(pk)
  41. 159 pk unless pk.is_a?(Array)
  42. end
  43. }
  44. 242 join_model.name = "HABTM_#{association_name.to_s.camelize}"
  45. 438 join_model.table_name_resolver = -> { table_name }
  46. 242 join_model.left_model = lhs_model
  47. 242 join_model.add_left_association :left_side, anonymous_class: lhs_model
  48. 242 join_model.add_right_association association_name, belongs_to_options(options)
  49. 242 join_model
  50. end
  51. 3 def middle_reflection(join_model)
  52. 242 middle_name = [lhs_model.name.downcase.pluralize,
  53. association_name.to_s].sort.join("_").gsub("::", "_").to_sym
  54. 242 middle_options = middle_options join_model
  55. 242 HasMany.create_reflection(lhs_model,
  56. middle_name,
  57. nil,
  58. middle_options)
  59. end
  60. 3 private
  61. 3 def middle_options(join_model)
  62. 242 middle_options = {}
  63. 242 middle_options[:class_name] = "#{lhs_model.name}::#{join_model.name}"
  64. 242 if options.key? :foreign_key
  65. 54 middle_options[:foreign_key] = options[:foreign_key]
  66. end
  67. 242 middle_options
  68. end
  69. 3 def table_name
  70. 196 if options[:join_table]
  71. 75 options[:join_table].to_s
  72. else
  73. 121 class_name = options.fetch(:class_name) {
  74. 58 association_name.to_s.camelize.singularize
  75. }
  76. 121 klass = lhs_model.send(:compute_type, class_name.to_s)
  77. 121 [lhs_model.table_name, klass.table_name].sort.join("\0").gsub(/^(.*[._])(.+)\0\1(.+)/, '\1\2_\3').tr("\0", "_")
  78. end
  79. end
  80. 3 def belongs_to_options(options)
  81. 242 rhs_options = {}
  82. 242 if options.key? :class_name
  83. 114 rhs_options[:foreign_key] = options[:class_name].to_s.foreign_key
  84. 114 rhs_options[:class_name] = options[:class_name]
  85. end
  86. 242 if options.key? :association_foreign_key
  87. 45 rhs_options[:foreign_key] = options[:association_foreign_key]
  88. end
  89. 242 rhs_options
  90. end
  91. end
  92. end

lib/active_record/associations/builder/has_many.rb

100.0% lines covered

12 relevant lines. 12 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 module ActiveRecord::Associations::Builder # :nodoc:
  3. 3 class HasMany < CollectionAssociation #:nodoc:
  4. 3 def self.macro
  5. 1892 :has_many
  6. end
  7. 3 def self.valid_options(options)
  8. 1895 valid = super + [:counter_cache, :join_table, :index_errors]
  9. 1895 valid += [:as, :foreign_type] if options[:as]
  10. 1895 valid += [:through, :source, :source_type] if options[:through]
  11. 1895 valid
  12. end
  13. 3 def self.valid_dependent_options
  14. 153 [:destroy, :delete_all, :nullify, :restrict_with_error, :restrict_with_exception]
  15. end
  16. 3 private_class_method :macro, :valid_options, :valid_dependent_options
  17. end
  18. end

lib/active_record/associations/builder/has_one.rb

100.0% lines covered

35 relevant lines. 35 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 module ActiveRecord::Associations::Builder # :nodoc:
  3. 3 class HasOne < SingularAssociation #:nodoc:
  4. 3 def self.macro
  5. 405 :has_one
  6. end
  7. 3 def self.valid_options(options)
  8. 411 valid = super
  9. 411 valid += [:as, :foreign_type] if options[:as]
  10. 411 valid += [:through, :source, :source_type] if options[:through]
  11. 411 valid
  12. end
  13. 3 def self.valid_dependent_options
  14. 54 [:destroy, :delete, :nullify, :restrict_with_error, :restrict_with_exception]
  15. end
  16. 3 def self.define_callbacks(model, reflection)
  17. 405 super
  18. 405 add_touch_callbacks(model, reflection) if reflection.options[:touch]
  19. end
  20. 3 def self.add_destroy_callbacks(model, reflection)
  21. 54 super unless reflection.options[:through]
  22. end
  23. 3 def self.define_validations(model, reflection)
  24. 405 super
  25. 405 if reflection.options[:required]
  26. 6 model.validates_presence_of reflection.name, message: :required
  27. end
  28. end
  29. 3 def self.touch_record(record, name, touch)
  30. 69 instance = record.send(name)
  31. 69 if instance&.persisted?
  32. 21 touch != true ?
  33. 21 instance.touch(touch) : instance.touch
  34. end
  35. end
  36. 3 def self.add_touch_callbacks(model, reflection)
  37. 6 name = reflection.name
  38. 6 touch = reflection.options[:touch]
  39. 75 callback = -> (record) { HasOne.touch_record(record, name, touch) }
  40. 6 model.after_create callback, if: :saved_changes?
  41. 63 model.after_create_commit { association(name).reset_negative_cache }
  42. 6 model.after_update callback, if: :saved_changes?
  43. 6 model.after_destroy callback
  44. 6 model.after_touch callback
  45. end
  46. 3 private_class_method :macro, :valid_options, :valid_dependent_options, :add_destroy_callbacks,
  47. :define_callbacks, :define_validations, :add_touch_callbacks
  48. end
  49. end

lib/active_record/associations/builder/singular_association.rb

100.0% lines covered

13 relevant lines. 13 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. # This class is inherited by the has_one and belongs_to association classes
  3. 3 module ActiveRecord::Associations::Builder # :nodoc:
  4. 3 class SingularAssociation < Association #:nodoc:
  5. 3 def self.valid_options(options)
  6. 1707 super + [:required, :touch]
  7. end
  8. 3 def self.define_accessors(model, reflection)
  9. 1701 super
  10. 1701 mixin = model.generated_association_methods
  11. 1701 name = reflection.name
  12. 1701 define_constructors(mixin, name) if reflection.constructable?
  13. 1701 mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1
  14. def reload_#{name}
  15. association(:#{name}).force_reload_reader
  16. end
  17. CODE
  18. end
  19. # Defines the (build|create)_association methods for belongs_to or has_one association
  20. 3 def self.define_constructors(mixin, name)
  21. 1509 mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1
  22. def build_#{name}(*args, &block)
  23. association(:#{name}).build(*args, &block)
  24. end
  25. def create_#{name}(*args, &block)
  26. association(:#{name}).create(*args, &block)
  27. end
  28. def create_#{name}!(*args, &block)
  29. association(:#{name}).create!(*args, &block)
  30. end
  31. CODE
  32. end
  33. 3 private_class_method :valid_options, :define_accessors, :define_constructors
  34. end
  35. end

lib/active_record/associations/collection_association.rb

98.28% lines covered

232 relevant lines. 228 lines covered and 4 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 module ActiveRecord
  3. 3 module Associations
  4. # = Active Record Association Collection
  5. #
  6. # CollectionAssociation is an abstract class that provides common stuff to
  7. # ease the implementation of association proxies that represent
  8. # collections. See the class hierarchy in Association.
  9. #
  10. # CollectionAssociation:
  11. # HasManyAssociation => has_many
  12. # HasManyThroughAssociation + ThroughAssociation => has_many :through
  13. #
  14. # The CollectionAssociation class provides common methods to the collections
  15. # defined by +has_and_belongs_to_many+, +has_many+ or +has_many+ with
  16. # the +:through association+ option.
  17. #
  18. # You need to be careful with assumptions regarding the target: The proxy
  19. # does not fetch records from the database until it needs them, but new
  20. # ones created with +build+ are added to the target. So, the target may be
  21. # non-empty and still lack children waiting to be read from the database.
  22. # If you look directly to the database you cannot assume that's the entire
  23. # collection because new records may have been added to the target, etc.
  24. #
  25. # If you need to work on all current children, new and existing records,
  26. # +load_target+ and the +loaded+ flag are your friends.
  27. 3 class CollectionAssociation < Association #:nodoc:
  28. # Implements the reader method, e.g. foo.items for Foo.has_many :items
  29. 3 def reader
  30. 11483 if stale_target?
  31. 9 reload
  32. end
  33. 11483 @proxy ||= CollectionProxy.create(klass, self)
  34. 11480 @proxy.reset_scope
  35. end
  36. # Implements the writer method, e.g. foo.items= for Foo.has_many :items
  37. 3 def writer(records)
  38. 273 replace(records)
  39. end
  40. # Implements the ids reader method, e.g. foo.item_ids for Foo.has_many :items
  41. 3 def ids_reader
  42. 162 if loaded?
  43. 48 target.pluck(reflection.association_primary_key)
  44. 114 elsif !target.empty?
  45. 12 load_target.pluck(reflection.association_primary_key)
  46. else
  47. 102 @association_ids ||= scope.pluck(reflection.association_primary_key)
  48. end
  49. end
  50. # Implements the ids writer method, e.g. foo.item_ids= for Foo.has_many :items
  51. 3 def ids_writer(ids)
  52. 89 primary_key = reflection.association_primary_key
  53. 89 pk_type = klass.type_for_attribute(primary_key)
  54. 89 ids = Array(ids).compact_blank
  55. 203 ids.map! { |i| pk_type.cast(i) }
  56. 89 records = klass.where(primary_key => ids).index_by do |r|
  57. 108 r.public_send(primary_key)
  58. end.values_at(*ids).compact
  59. 89 if records.size != ids.size
  60. 12 found_ids = records.map { |record| record.public_send(primary_key) }
  61. 6 not_found_ids = ids - found_ids
  62. 6 klass.all.raise_record_not_found_exception!(ids, records.size, ids.size, primary_key, not_found_ids)
  63. else
  64. 83 replace(records)
  65. end
  66. end
  67. 3 def reset
  68. 15349 super
  69. 15349 @target = []
  70. 15349 @association_ids = nil
  71. end
  72. 3 def find(*args)
  73. 112 if options[:inverse_of] && loaded?
  74. 21 args_flatten = args.flatten
  75. 21 model = scope.klass
  76. 21 if args_flatten.blank?
  77. 3 error_message = "Couldn't find #{model.name} without an ID"
  78. 3 raise RecordNotFound.new(error_message, model.name, model.primary_key, args)
  79. end
  80. 18 result = find_by_scan(*args)
  81. 18 result_size = Array(result).size
  82. 18 if !result || result_size != args_flatten.size
  83. scope.raise_record_not_found_exception!(args_flatten, result_size, args_flatten.size)
  84. else
  85. 18 result
  86. end
  87. else
  88. 91 scope.find(*args)
  89. end
  90. end
  91. 3 def build(attributes = nil, &block)
  92. 1974 if attributes.is_a?(Array)
  93. 54 attributes.collect { |attr| build(attr, &block) }
  94. else
  95. 1956 add_to_target(build_record(attributes, &block))
  96. end
  97. end
  98. # Add +records+ to this association. Since +<<+ flattens its argument list
  99. # and inserts each record, +push+ and +concat+ behave identically.
  100. 3 def concat(*records)
  101. 967 records = records.flatten
  102. 967 if owner.new_record?
  103. 385 load_target
  104. 385 concat_records(records)
  105. else
  106. 1164 transaction { concat_records(records) }
  107. end
  108. end
  109. # Starts a transaction in the association class's database connection.
  110. #
  111. # class Author < ActiveRecord::Base
  112. # has_many :books
  113. # end
  114. #
  115. # Author.first.books.transaction do
  116. # # same effect as calling Book.transaction
  117. # end
  118. 3 def transaction(*args)
  119. 2775 reflection.klass.transaction(*args) do
  120. 2775 yield
  121. end
  122. end
  123. # Removes all records from the association without calling callbacks
  124. # on the associated records. It honors the +:dependent+ option. However
  125. # if the +:dependent+ value is +:destroy+ then in that case the +:delete_all+
  126. # deletion strategy for the association is applied.
  127. #
  128. # You can force a particular deletion strategy by passing a parameter.
  129. #
  130. # Example:
  131. #
  132. # @author.books.delete_all(:nullify)
  133. # @author.books.delete_all(:delete_all)
  134. #
  135. # See delete for more info.
  136. 3 def delete_all(dependent = nil)
  137. 735 if dependent && ![:nullify, :delete_all].include?(dependent)
  138. 3 raise ArgumentError, "Valid values are :nullify or :delete_all"
  139. end
  140. 732 dependent = if dependent
  141. 444 dependent
  142. 288 elsif options[:dependent] == :destroy
  143. 18 :delete_all
  144. else
  145. 270 options[:dependent]
  146. end
  147. 732 delete_or_nullify_all_records(dependent).tap do
  148. 729 reset
  149. 729 loaded!
  150. end
  151. end
  152. # Destroy all the records from this association.
  153. #
  154. # See destroy for more info.
  155. 3 def destroy_all
  156. 658 destroy(load_target).tap do
  157. 649 reset
  158. 649 loaded!
  159. end
  160. end
  161. # Removes +records+ from this association calling +before_remove+ and
  162. # +after_remove+ callbacks.
  163. #
  164. # This method is abstract in the sense that +delete_records+ has to be
  165. # provided by descendants. Note this method does not imply the records
  166. # are actually removed from the database, that depends precisely on
  167. # +delete_records+. They are in any case removed from the collection.
  168. 3 def delete(*records)
  169. 470 delete_or_destroy(records, options[:dependent])
  170. end
  171. # Deletes the +records+ and removes them from this association calling
  172. # +before_remove+ , +after_remove+ , +before_destroy+ and +after_destroy+ callbacks.
  173. #
  174. # Note that this method removes records from the database ignoring the
  175. # +:dependent+ option.
  176. 3 def destroy(*records)
  177. 808 delete_or_destroy(records, :destroy)
  178. end
  179. # Returns the size of the collection by executing a SELECT COUNT(*)
  180. # query if the collection hasn't been loaded, and calling
  181. # <tt>collection.size</tt> if it has.
  182. #
  183. # If the collection has been already loaded +size+ and +length+ are
  184. # equivalent. If not and you are going to need the records anyway
  185. # +length+ will take one less query. Otherwise +size+ is more efficient.
  186. #
  187. # This method is abstract in the sense that it relies on
  188. # +count_records+, which is a method descendants have to provide.
  189. 3 def size
  190. 1134 if !find_target? || loaded?
  191. 707 target.size
  192. 427 elsif @association_ids
  193. 18 @association_ids.size
  194. 409 elsif !association_scope.group_values.empty?
  195. 6 load_target.size
  196. 403 elsif !association_scope.distinct_value && !target.empty?
  197. 45 unsaved_records = target.select(&:new_record?)
  198. 45 unsaved_records.size + count_records
  199. else
  200. 358 count_records
  201. end
  202. end
  203. # Returns true if the collection is empty.
  204. #
  205. # If the collection has been loaded
  206. # it is equivalent to <tt>collection.size.zero?</tt>. If the
  207. # collection has not been loaded, it is equivalent to
  208. # <tt>collection.exists?</tt>. If the collection has not already been
  209. # loaded and you are going to fetch the records anyway it is better to
  210. # check <tt>collection.length.zero?</tt>.
  211. 3 def empty?
  212. 240 if loaded? || @association_ids || reflection.has_cached_counter?
  213. 111 size.zero?
  214. else
  215. 129 target.empty? && !scope.exists?
  216. end
  217. end
  218. # Replace this collection with +other_array+. This will perform a diff
  219. # and delete/add only records that have changed.
  220. 3 def replace(other_array)
  221. 809 other_array.each { |val| raise_on_type_mismatch!(val) }
  222. 362 original_target = load_target.dup
  223. 362 if owner.new_record?
  224. 201 replace_records(other_array, original_target)
  225. else
  226. 161 replace_common_records_in_memory(other_array, original_target)
  227. 161 if other_array != original_target
  228. 280 transaction { replace_records(other_array, original_target) }
  229. else
  230. 21 other_array
  231. end
  232. end
  233. end
  234. 3 def include?(record)
  235. 288 if record.is_a?(reflection.klass)
  236. 282 if record.new_record?
  237. 21 include_in_memory?(record)
  238. else
  239. 261 loaded? ? target.include?(record) : scope.exists?(record.id)
  240. end
  241. else
  242. 6 false
  243. end
  244. end
  245. 3 def load_target
  246. 6995 if find_target?
  247. 2821 @target = merge_target_lists(find_target, target)
  248. end
  249. 6968 loaded!
  250. 6968 target
  251. end
  252. 3 def add_to_target(record, skip_callbacks: false, replace: false, &block)
  253. 4702 if replace || association_scope.distinct_value
  254. 91 index = @target.index(record)
  255. end
  256. 4702 replace_on_target(record, index, skip_callbacks, &block)
  257. end
  258. 3 def target=(record)
  259. 3955 return super unless ActiveRecord::Base.has_many_inversing
  260. 18 case record
  261. when Array
  262. super
  263. else
  264. 18 add_to_target(record, skip_callbacks: true, replace: true)
  265. end
  266. end
  267. 3 def scope
  268. 11691 scope = super
  269. 11691 scope.none! if null_scope?
  270. 11691 scope
  271. end
  272. 3 def null_scope?
  273. 12645 owner.new_record? && !foreign_key_present?
  274. end
  275. 3 def find_from_target?
  276. 864 loaded? ||
  277. owner.strict_loading? ||
  278. reflection.strict_loading? ||
  279. owner.new_record? ||
  280. 90 target.any? { |record| record.new_record? || record.changed? }
  281. end
  282. 3 private
  283. # We have some records loaded from the database (persisted) and some that are
  284. # in-memory (memory). The same record may be represented in the persisted array
  285. # and in the memory array.
  286. #
  287. # So the task of this method is to merge them according to the following rules:
  288. #
  289. # * The final array must not have duplicates
  290. # * The order of the persisted array is to be preserved
  291. # * Any changes made to attributes on objects in the memory array are to be preserved
  292. # * Otherwise, attributes should have the value found in the database
  293. 3 def merge_target_lists(persisted, memory)
  294. 2794 return persisted if memory.empty?
  295. 562 return memory if persisted.empty?
  296. 535 persisted.map! do |record|
  297. 1006 if mem_record = memory.delete(record)
  298. 820 ((record.attribute_names & mem_record.attribute_names) - mem_record.changed_attribute_names_to_save).each do |name|
  299. 6156 mem_record[name] = record[name]
  300. end
  301. 820 mem_record
  302. else
  303. 186 record
  304. end
  305. end
  306. 535 persisted + memory
  307. end
  308. 3 def _create_record(attributes, raise = false, &block)
  309. 1601 unless owner.persisted?
  310. 15 raise ActiveRecord::RecordNotSaved, "You cannot call create unless the parent is saved"
  311. end
  312. 1586 if attributes.is_a?(Array)
  313. 18 attributes.collect { |attr| _create_record(attr, raise, &block) }
  314. else
  315. 1580 record = build_record(attributes, &block)
  316. 1577 transaction do
  317. 1577 result = nil
  318. 1577 add_to_target(record) do
  319. 1577 result = insert_record(record, true, raise) {
  320. 1544 @_was_loaded = loaded?
  321. }
  322. end
  323. 1571 raise ActiveRecord::Rollback unless result
  324. end
  325. 1571 record
  326. end
  327. end
  328. # Do the relevant stuff to insert the given record into the association collection.
  329. 3 def insert_record(record, validate = true, raise = false, &block)
  330. 2712 if raise
  331. 952 record.save!(validate: validate, &block)
  332. else
  333. 1760 record.save(validate: validate, &block)
  334. end
  335. end
  336. 3 def delete_or_destroy(records, method)
  337. 1278 return if records.empty?
  338. 2571 records = find(records) if records.any? { |record| record.kind_of?(Integer) || record.kind_of?(String) }
  339. 1278 records = records.flatten
  340. 1975 records.each { |record| raise_on_type_mismatch!(record) }
  341. 1269 existing_records = records.reject(&:new_record?)
  342. 1269 if existing_records.empty?
  343. 793 remove_records(existing_records, records, method)
  344. else
  345. 952 transaction { remove_records(existing_records, records, method) }
  346. end
  347. end
  348. 3 def remove_records(existing_records, records, method)
  349. catch(:abort) do
  350. 1957 records.each { |record| callback(:before_remove, record) }
  351. 1269 end || return
  352. 1266 delete_records(existing_records, method) if existing_records.any?
  353. 1224 @target -= records
  354. 1224 @association_ids = nil
  355. 1828 records.each { |record| callback(:after_remove, record) }
  356. end
  357. # Delete the given records from the association,
  358. # using one of the methods +:destroy+, +:delete_all+
  359. # or +:nullify+ (or +nil+, in which case a default is used).
  360. 3 def delete_records(records, method)
  361. raise NotImplementedError
  362. end
  363. 3 def replace_records(new_target, original_target)
  364. 341 delete(difference(target, new_target))
  365. 323 unless concat(difference(new_target, target))
  366. 3 @target = original_target
  367. 3 raise RecordNotSaved, "Failed to replace #{reflection.name} because one or more of the " \
  368. "new records could not be saved."
  369. end
  370. 317 target
  371. end
  372. 3 def replace_common_records_in_memory(new_target, original_target)
  373. 161 common_records = intersection(new_target, original_target)
  374. 161 common_records.each do |record|
  375. 57 skip_callbacks = true
  376. 57 replace_on_target(record, @target.index(record), skip_callbacks)
  377. end
  378. end
  379. 3 def concat_records(records, raise = false)
  380. 964 result = true
  381. 964 records.each do |record|
  382. 1070 raise_on_type_mismatch!(record)
  383. 1061 add_to_target(record) do
  384. 1055 unless owner.new_record?
  385. 586 result &&= insert_record(record, true, raise) {
  386. 270 @_was_loaded = loaded?
  387. }
  388. end
  389. end
  390. end
  391. 931 raise ActiveRecord::Rollback unless result
  392. 895 records
  393. end
  394. 3 def replace_on_target(record, index, skip_callbacks)
  395. catch(:abort) do
  396. 4579 callback(:before_add, record)
  397. 4759 end || return unless skip_callbacks
  398. 4753 set_inverse_instance(record)
  399. 4753 @_was_loaded = true
  400. 4753 yield(record) if block_given?
  401. 4726 if index
  402. 72 target[index] = record
  403. 4654 elsif @_was_loaded || !loaded?
  404. 4651 @association_ids = nil
  405. 4651 target << record
  406. end
  407. 4726 callback(:after_add, record) unless skip_callbacks
  408. 4726 record
  409. ensure
  410. 4759 @_was_loaded = nil
  411. end
  412. 3 def callback(method, record)
  413. 10417 callbacks_for(method).each do |callback|
  414. 498 callback.call(method, owner, record)
  415. end
  416. end
  417. 3 def callbacks_for(callback_name)
  418. 10417 full_callback_name = "#{callback_name}_for_#{reflection.name}"
  419. 10417 owner.class.send(full_callback_name)
  420. end
  421. 3 def include_in_memory?(record)
  422. 21 if reflection.is_a?(ActiveRecord::Reflection::ThroughReflection)
  423. 12 assoc = owner.association(reflection.through_reflection.name)
  424. assoc.reader.any? { |source|
  425. 9 target_reflection = source.send(reflection.source_reflection.name)
  426. 9 target_reflection.respond_to?(:include?) ? target_reflection.include?(record) : target_reflection == record
  427. 12 } || target.include?(record)
  428. else
  429. 9 target.include?(record)
  430. end
  431. end
  432. # If the :inverse_of option has been
  433. # specified, then #find scans the entire collection.
  434. 3 def find_by_scan(*args)
  435. 18 expects_array = args.first.kind_of?(Array)
  436. 18 ids = args.flatten.compact.map(&:to_s).uniq
  437. 18 if ids.size == 1
  438. 18 id = ids.first
  439. 36 record = load_target.detect { |r| id == r.id.to_s }
  440. 18 expects_array ? [ record ] : record
  441. else
  442. load_target.select { |r| ids.include?(r.id.to_s) }
  443. end
  444. end
  445. end
  446. end
  447. end

lib/active_record/associations/collection_proxy.rb

100.0% lines covered

96 relevant lines. 96 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 module ActiveRecord
  3. 3 module Associations
  4. # Collection proxies in Active Record are middlemen between an
  5. # <tt>association</tt>, and its <tt>target</tt> result set.
  6. #
  7. # For example, given
  8. #
  9. # class Blog < ActiveRecord::Base
  10. # has_many :posts
  11. # end
  12. #
  13. # blog = Blog.first
  14. #
  15. # The collection proxy returned by <tt>blog.posts</tt> is built from a
  16. # <tt>:has_many</tt> <tt>association</tt>, and delegates to a collection
  17. # of posts as the <tt>target</tt>.
  18. #
  19. # This class delegates unknown methods to the <tt>association</tt>'s
  20. # relation class via a delegate cache.
  21. #
  22. # The <tt>target</tt> result set is not loaded until needed. For example,
  23. #
  24. # blog.posts.count
  25. #
  26. # is computed directly through SQL and does not trigger by itself the
  27. # instantiation of the actual post records.
  28. 3 class CollectionProxy < Relation
  29. 3 def initialize(klass, association, **) #:nodoc:
  30. 6730 @association = association
  31. 6730 super klass
  32. 6730 extensions = association.extensions
  33. 6730 extend(*extensions) if extensions.any?
  34. end
  35. 3 def target
  36. 39 @association.target
  37. end
  38. 3 def load_target
  39. 4486 @association.load_target
  40. end
  41. # Returns +true+ if the association has been loaded, otherwise +false+.
  42. #
  43. # person.pets.loaded? # => false
  44. # person.pets
  45. # person.pets.loaded? # => true
  46. 3 def loaded?
  47. 1930 @association.loaded?
  48. end
  49. 3 alias :loaded :loaded?
  50. ##
  51. # :method: select
  52. #
  53. # :call-seq:
  54. # select(*fields, &block)
  55. #
  56. # Works in two ways.
  57. #
  58. # *First:* Specify a subset of fields to be selected from the result set.
  59. #
  60. # class Person < ActiveRecord::Base
  61. # has_many :pets
  62. # end
  63. #
  64. # person.pets
  65. # # => [
  66. # # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
  67. # # #<Pet id: 2, name: "Spook", person_id: 1>,
  68. # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
  69. # # ]
  70. #
  71. # person.pets.select(:name)
  72. # # => [
  73. # # #<Pet id: nil, name: "Fancy-Fancy">,
  74. # # #<Pet id: nil, name: "Spook">,
  75. # # #<Pet id: nil, name: "Choo-Choo">
  76. # # ]
  77. #
  78. # person.pets.select(:id, :name)
  79. # # => [
  80. # # #<Pet id: 1, name: "Fancy-Fancy">,
  81. # # #<Pet id: 2, name: "Spook">,
  82. # # #<Pet id: 3, name: "Choo-Choo">
  83. # # ]
  84. #
  85. # Be careful because this also means you're initializing a model
  86. # object with only the fields that you've selected. If you attempt
  87. # to access a field except +id+ that is not in the initialized record you'll
  88. # receive:
  89. #
  90. # person.pets.select(:name).first.person_id
  91. # # => ActiveModel::MissingAttributeError: missing attribute: person_id
  92. #
  93. # *Second:* You can pass a block so it can be used just like Array#select.
  94. # This builds an array of objects from the database for the scope,
  95. # converting them into an array and iterating through them using
  96. # Array#select.
  97. #
  98. # person.pets.select { |pet| /oo/.match?(pet.name) }
  99. # # => [
  100. # # #<Pet id: 2, name: "Spook", person_id: 1>,
  101. # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
  102. # # ]
  103. # Finds an object in the collection responding to the +id+. Uses the same
  104. # rules as ActiveRecord::Base.find. Returns ActiveRecord::RecordNotFound
  105. # error if the object cannot be found.
  106. #
  107. # class Person < ActiveRecord::Base
  108. # has_many :pets
  109. # end
  110. #
  111. # person.pets
  112. # # => [
  113. # # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
  114. # # #<Pet id: 2, name: "Spook", person_id: 1>,
  115. # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
  116. # # ]
  117. #
  118. # person.pets.find(1) # => #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>
  119. # person.pets.find(4) # => ActiveRecord::RecordNotFound: Couldn't find Pet with 'id'=4
  120. #
  121. # person.pets.find(2) { |pet| pet.name.downcase! }
  122. # # => #<Pet id: 2, name: "fancy-fancy", person_id: 1>
  123. #
  124. # person.pets.find(2, 3)
  125. # # => [
  126. # # #<Pet id: 2, name: "Spook", person_id: 1>,
  127. # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
  128. # # ]
  129. 3 def find(*args)
  130. 91 return super if block_given?
  131. 91 @association.find(*args)
  132. end
  133. ##
  134. # :method: first
  135. #
  136. # :call-seq:
  137. # first(limit = nil)
  138. #
  139. # Returns the first record, or the first +n+ records, from the collection.
  140. # If the collection is empty, the first form returns +nil+, and the second
  141. # form returns an empty array.
  142. #
  143. # class Person < ActiveRecord::Base
  144. # has_many :pets
  145. # end
  146. #
  147. # person.pets
  148. # # => [
  149. # # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
  150. # # #<Pet id: 2, name: "Spook", person_id: 1>,
  151. # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
  152. # # ]
  153. #
  154. # person.pets.first # => #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>
  155. #
  156. # person.pets.first(2)
  157. # # => [
  158. # # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
  159. # # #<Pet id: 2, name: "Spook", person_id: 1>
  160. # # ]
  161. #
  162. # another_person_without.pets # => []
  163. # another_person_without.pets.first # => nil
  164. # another_person_without.pets.first(3) # => []
  165. ##
  166. # :method: second
  167. #
  168. # :call-seq:
  169. # second()
  170. #
  171. # Same as #first except returns only the second record.
  172. ##
  173. # :method: third
  174. #
  175. # :call-seq:
  176. # third()
  177. #
  178. # Same as #first except returns only the third record.
  179. ##
  180. # :method: fourth
  181. #
  182. # :call-seq:
  183. # fourth()
  184. #
  185. # Same as #first except returns only the fourth record.
  186. ##
  187. # :method: fifth
  188. #
  189. # :call-seq:
  190. # fifth()
  191. #
  192. # Same as #first except returns only the fifth record.
  193. ##
  194. # :method: forty_two
  195. #
  196. # :call-seq:
  197. # forty_two()
  198. #
  199. # Same as #first except returns only the forty second record.
  200. # Also known as accessing "the reddit".
  201. ##
  202. # :method: third_to_last
  203. #
  204. # :call-seq:
  205. # third_to_last()
  206. #
  207. # Same as #first except returns only the third-to-last record.
  208. ##
  209. # :method: second_to_last
  210. #
  211. # :call-seq:
  212. # second_to_last()
  213. #
  214. # Same as #first except returns only the second-to-last record.
  215. # Returns the last record, or the last +n+ records, from the collection.
  216. # If the collection is empty, the first form returns +nil+, and the second
  217. # form returns an empty array.
  218. #
  219. # class Person < ActiveRecord::Base
  220. # has_many :pets
  221. # end
  222. #
  223. # person.pets
  224. # # => [
  225. # # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
  226. # # #<Pet id: 2, name: "Spook", person_id: 1>,
  227. # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
  228. # # ]
  229. #
  230. # person.pets.last # => #<Pet id: 3, name: "Choo-Choo", person_id: 1>
  231. #
  232. # person.pets.last(2)
  233. # # => [
  234. # # #<Pet id: 2, name: "Spook", person_id: 1>,
  235. # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
  236. # # ]
  237. #
  238. # another_person_without.pets # => []
  239. # another_person_without.pets.last # => nil
  240. # another_person_without.pets.last(3) # => []
  241. 3 def last(limit = nil)
  242. 141 load_target if find_from_target?
  243. 141 super
  244. end
  245. # Gives a record (or N records if a parameter is supplied) from the collection
  246. # using the same rules as <tt>ActiveRecord::Base.take</tt>.
  247. #
  248. # class Person < ActiveRecord::Base
  249. # has_many :pets
  250. # end
  251. #
  252. # person.pets
  253. # # => [
  254. # # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
  255. # # #<Pet id: 2, name: "Spook", person_id: 1>,
  256. # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
  257. # # ]
  258. #
  259. # person.pets.take # => #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>
  260. #
  261. # person.pets.take(2)
  262. # # => [
  263. # # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
  264. # # #<Pet id: 2, name: "Spook", person_id: 1>
  265. # # ]
  266. #
  267. # another_person_without.pets # => []
  268. # another_person_without.pets.take # => nil
  269. # another_person_without.pets.take(2) # => []
  270. 3 def take(limit = nil)
  271. 45 load_target if find_from_target?
  272. 45 super
  273. end
  274. # Returns a new object of the collection type that has been instantiated
  275. # with +attributes+ and linked to this object, but have not yet been saved.
  276. # You can pass an array of attributes hashes, this will return an array
  277. # with the new objects.
  278. #
  279. # class Person
  280. # has_many :pets
  281. # end
  282. #
  283. # person.pets.build
  284. # # => #<Pet id: nil, name: nil, person_id: 1>
  285. #
  286. # person.pets.build(name: 'Fancy-Fancy')
  287. # # => #<Pet id: nil, name: "Fancy-Fancy", person_id: 1>
  288. #
  289. # person.pets.build([{name: 'Spook'}, {name: 'Choo-Choo'}, {name: 'Brain'}])
  290. # # => [
  291. # # #<Pet id: nil, name: "Spook", person_id: 1>,
  292. # # #<Pet id: nil, name: "Choo-Choo", person_id: 1>,
  293. # # #<Pet id: nil, name: "Brain", person_id: 1>
  294. # # ]
  295. #
  296. # person.pets.size # => 5 # size of the collection
  297. # person.pets.count # => 0 # count from database
  298. 3 def build(attributes = {}, &block)
  299. 821 @association.build(attributes, &block)
  300. end
  301. 3 alias_method :new, :build
  302. # Returns a new object of the collection type that has been instantiated with
  303. # attributes, linked to this object and that has already been saved (if it
  304. # passes the validations).
  305. #
  306. # class Person
  307. # has_many :pets
  308. # end
  309. #
  310. # person.pets.create(name: 'Fancy-Fancy')
  311. # # => #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>
  312. #
  313. # person.pets.create([{name: 'Spook'}, {name: 'Choo-Choo'}])
  314. # # => [
  315. # # #<Pet id: 2, name: "Spook", person_id: 1>,
  316. # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
  317. # # ]
  318. #
  319. # person.pets.size # => 3
  320. # person.pets.count # => 3
  321. #
  322. # person.pets.find(1, 2, 3)
  323. # # => [
  324. # # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
  325. # # #<Pet id: 2, name: "Spook", person_id: 1>,
  326. # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
  327. # # ]
  328. 3 def create(attributes = {}, &block)
  329. 658 @association.create(attributes, &block)
  330. end
  331. # Like #create, except that if the record is invalid, raises an exception.
  332. #
  333. # class Person
  334. # has_many :pets
  335. # end
  336. #
  337. # class Pet
  338. # validates :name, presence: true
  339. # end
  340. #
  341. # person.pets.create!(name: nil)
  342. # # => ActiveRecord::RecordInvalid: Validation failed: Name can't be blank
  343. 3 def create!(attributes = {}, &block)
  344. 901 @association.create!(attributes, &block)
  345. end
  346. # Replaces this collection with +other_array+. This will perform a diff
  347. # and delete/add only records that have changed.
  348. #
  349. # class Person < ActiveRecord::Base
  350. # has_many :pets
  351. # end
  352. #
  353. # person.pets
  354. # # => [#<Pet id: 1, name: "Gorby", group: "cats", person_id: 1>]
  355. #
  356. # other_pets = [Pet.new(name: 'Puff', group: 'celebrities')]
  357. #
  358. # person.pets.replace(other_pets)
  359. #
  360. # person.pets
  361. # # => [#<Pet id: 2, name: "Puff", group: "celebrities", person_id: 1>]
  362. #
  363. # If the supplied array has an incorrect association type, it raises
  364. # an <tt>ActiveRecord::AssociationTypeMismatch</tt> error:
  365. #
  366. # person.pets.replace(["doo", "ggie", "gaga"])
  367. # # => ActiveRecord::AssociationTypeMismatch: Pet expected, got String
  368. 3 def replace(other_array)
  369. 6 @association.replace(other_array)
  370. end
  371. # Deletes all the records from the collection according to the strategy
  372. # specified by the +:dependent+ option. If no +:dependent+ option is given,
  373. # then it will follow the default strategy.
  374. #
  375. # For <tt>has_many :through</tt> associations, the default deletion strategy is
  376. # +:delete_all+.
  377. #
  378. # For +has_many+ associations, the default deletion strategy is +:nullify+.
  379. # This sets the foreign keys to +NULL+.
  380. #
  381. # class Person < ActiveRecord::Base
  382. # has_many :pets # dependent: :nullify option by default
  383. # end
  384. #
  385. # person.pets.size # => 3
  386. # person.pets
  387. # # => [
  388. # # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
  389. # # #<Pet id: 2, name: "Spook", person_id: 1>,
  390. # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
  391. # # ]
  392. #
  393. # person.pets.delete_all
  394. # # => [
  395. # # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
  396. # # #<Pet id: 2, name: "Spook", person_id: 1>,
  397. # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
  398. # # ]
  399. #
  400. # person.pets.size # => 0
  401. # person.pets # => []
  402. #
  403. # Pet.find(1, 2, 3)
  404. # # => [
  405. # # #<Pet id: 1, name: "Fancy-Fancy", person_id: nil>,
  406. # # #<Pet id: 2, name: "Spook", person_id: nil>,
  407. # # #<Pet id: 3, name: "Choo-Choo", person_id: nil>
  408. # # ]
  409. #
  410. # Both +has_many+ and <tt>has_many :through</tt> dependencies default to the
  411. # +:delete_all+ strategy if the +:dependent+ option is set to +:destroy+.
  412. # Records are not instantiated and callbacks will not be fired.
  413. #
  414. # class Person < ActiveRecord::Base
  415. # has_many :pets, dependent: :destroy
  416. # end
  417. #
  418. # person.pets.size # => 3
  419. # person.pets
  420. # # => [
  421. # # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
  422. # # #<Pet id: 2, name: "Spook", person_id: 1>,
  423. # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
  424. # # ]
  425. #
  426. # person.pets.delete_all
  427. #
  428. # Pet.find(1, 2, 3)
  429. # # => ActiveRecord::RecordNotFound: Couldn't find all Pets with 'id': (1, 2, 3)
  430. #
  431. # If it is set to <tt>:delete_all</tt>, all the objects are deleted
  432. # *without* calling their +destroy+ method.
  433. #
  434. # class Person < ActiveRecord::Base
  435. # has_many :pets, dependent: :delete_all
  436. # end
  437. #
  438. # person.pets.size # => 3
  439. # person.pets
  440. # # => [
  441. # # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
  442. # # #<Pet id: 2, name: "Spook", person_id: 1>,
  443. # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
  444. # # ]
  445. #
  446. # person.pets.delete_all
  447. #
  448. # Pet.find(1, 2, 3)
  449. # # => ActiveRecord::RecordNotFound: Couldn't find all Pets with 'id': (1, 2, 3)
  450. 3 def delete_all(dependent = nil)
  451. 210 @association.delete_all(dependent).tap { reset_scope }
  452. end
  453. # Deletes the records of the collection directly from the database
  454. # ignoring the +:dependent+ option. Records are instantiated and it
  455. # invokes +before_remove+, +after_remove+ , +before_destroy+ and
  456. # +after_destroy+ callbacks.
  457. #
  458. # class Person < ActiveRecord::Base
  459. # has_many :pets
  460. # end
  461. #
  462. # person.pets.size # => 3
  463. # person.pets
  464. # # => [
  465. # # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
  466. # # #<Pet id: 2, name: "Spook", person_id: 1>,
  467. # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
  468. # # ]
  469. #
  470. # person.pets.destroy_all
  471. #
  472. # person.pets.size # => 0
  473. # person.pets # => []
  474. #
  475. # Pet.find(1) # => Couldn't find Pet with id=1
  476. 3 def destroy_all
  477. 66 @association.destroy_all.tap { reset_scope }
  478. end
  479. # Deletes the +records+ supplied from the collection according to the strategy
  480. # specified by the +:dependent+ option. If no +:dependent+ option is given,
  481. # then it will follow the default strategy. Returns an array with the
  482. # deleted records.
  483. #
  484. # For <tt>has_many :through</tt> associations, the default deletion strategy is
  485. # +:delete_all+.
  486. #
  487. # For +has_many+ associations, the default deletion strategy is +:nullify+.
  488. # This sets the foreign keys to +NULL+.
  489. #
  490. # class Person < ActiveRecord::Base
  491. # has_many :pets # dependent: :nullify option by default
  492. # end
  493. #
  494. # person.pets.size # => 3
  495. # person.pets
  496. # # => [
  497. # # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
  498. # # #<Pet id: 2, name: "Spook", person_id: 1>,
  499. # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
  500. # # ]
  501. #
  502. # person.pets.delete(Pet.find(1))
  503. # # => [#<Pet id: 1, name: "Fancy-Fancy", person_id: 1>]
  504. #
  505. # person.pets.size # => 2
  506. # person.pets
  507. # # => [
  508. # # #<Pet id: 2, name: "Spook", person_id: 1>,
  509. # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
  510. # # ]
  511. #
  512. # Pet.find(1)
  513. # # => #<Pet id: 1, name: "Fancy-Fancy", person_id: nil>
  514. #
  515. # If it is set to <tt>:destroy</tt> all the +records+ are removed by calling
  516. # their +destroy+ method. See +destroy+ for more information.
  517. #
  518. # class Person < ActiveRecord::Base
  519. # has_many :pets, dependent: :destroy
  520. # end
  521. #
  522. # person.pets.size # => 3
  523. # person.pets
  524. # # => [
  525. # # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
  526. # # #<Pet id: 2, name: "Spook", person_id: 1>,
  527. # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
  528. # # ]
  529. #
  530. # person.pets.delete(Pet.find(1), Pet.find(3))
  531. # # => [
  532. # # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
  533. # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
  534. # # ]
  535. #
  536. # person.pets.size # => 1
  537. # person.pets
  538. # # => [#<Pet id: 2, name: "Spook", person_id: 1>]
  539. #
  540. # Pet.find(1, 3)
  541. # # => ActiveRecord::RecordNotFound: Couldn't find all Pets with 'id': (1, 3)
  542. #
  543. # If it is set to <tt>:delete_all</tt>, all the +records+ are deleted
  544. # *without* calling their +destroy+ method.
  545. #
  546. # class Person < ActiveRecord::Base
  547. # has_many :pets, dependent: :delete_all
  548. # end
  549. #
  550. # person.pets.size # => 3
  551. # person.pets
  552. # # => [
  553. # # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
  554. # # #<Pet id: 2, name: "Spook", person_id: 1>,
  555. # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
  556. # # ]
  557. #
  558. # person.pets.delete(Pet.find(1))
  559. # # => [#<Pet id: 1, name: "Fancy-Fancy", person_id: 1>]
  560. #
  561. # person.pets.size # => 2
  562. # person.pets
  563. # # => [
  564. # # #<Pet id: 2, name: "Spook", person_id: 1>,
  565. # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
  566. # # ]
  567. #
  568. # Pet.find(1)
  569. # # => ActiveRecord::RecordNotFound: Couldn't find Pet with 'id'=1
  570. #
  571. # You can pass +Integer+ or +String+ values, it finds the records
  572. # responding to the +id+ and executes delete on them.
  573. #
  574. # class Person < ActiveRecord::Base
  575. # has_many :pets
  576. # end
  577. #
  578. # person.pets.size # => 3
  579. # person.pets
  580. # # => [
  581. # # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
  582. # # #<Pet id: 2, name: "Spook", person_id: 1>,
  583. # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
  584. # # ]
  585. #
  586. # person.pets.delete("1")
  587. # # => [#<Pet id: 1, name: "Fancy-Fancy", person_id: 1>]
  588. #
  589. # person.pets.delete(2, 3)
  590. # # => [
  591. # # #<Pet id: 2, name: "Spook", person_id: 1>,
  592. # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
  593. # # ]
  594. 3 def delete(*records)
  595. 243 @association.delete(*records).tap { reset_scope }
  596. end
  597. # Destroys the +records+ supplied and removes them from the collection.
  598. # This method will _always_ remove record from the database ignoring
  599. # the +:dependent+ option. Returns an array with the removed records.
  600. #
  601. # class Person < ActiveRecord::Base
  602. # has_many :pets
  603. # end
  604. #
  605. # person.pets.size # => 3
  606. # person.pets
  607. # # => [
  608. # # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
  609. # # #<Pet id: 2, name: "Spook", person_id: 1>,
  610. # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
  611. # # ]
  612. #
  613. # person.pets.destroy(Pet.find(1))
  614. # # => [#<Pet id: 1, name: "Fancy-Fancy", person_id: 1>]
  615. #
  616. # person.pets.size # => 2
  617. # person.pets
  618. # # => [
  619. # # #<Pet id: 2, name: "Spook", person_id: 1>,
  620. # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
  621. # # ]
  622. #
  623. # person.pets.destroy(Pet.find(2), Pet.find(3))
  624. # # => [
  625. # # #<Pet id: 2, name: "Spook", person_id: 1>,
  626. # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
  627. # # ]
  628. #
  629. # person.pets.size # => 0
  630. # person.pets # => []
  631. #
  632. # Pet.find(1, 2, 3) # => ActiveRecord::RecordNotFound: Couldn't find all Pets with 'id': (1, 2, 3)
  633. #
  634. # You can pass +Integer+ or +String+ values, it finds the records
  635. # responding to the +id+ and then deletes them from the database.
  636. #
  637. # person.pets.size # => 3
  638. # person.pets
  639. # # => [
  640. # # #<Pet id: 4, name: "Benny", person_id: 1>,
  641. # # #<Pet id: 5, name: "Brain", person_id: 1>,
  642. # # #<Pet id: 6, name: "Boss", person_id: 1>
  643. # # ]
  644. #
  645. # person.pets.destroy("4")
  646. # # => #<Pet id: 4, name: "Benny", person_id: 1>
  647. #
  648. # person.pets.size # => 2
  649. # person.pets
  650. # # => [
  651. # # #<Pet id: 5, name: "Brain", person_id: 1>,
  652. # # #<Pet id: 6, name: "Boss", person_id: 1>
  653. # # ]
  654. #
  655. # person.pets.destroy(5, 6)
  656. # # => [
  657. # # #<Pet id: 5, name: "Brain", person_id: 1>,
  658. # # #<Pet id: 6, name: "Boss", person_id: 1>
  659. # # ]
  660. #
  661. # person.pets.size # => 0
  662. # person.pets # => []
  663. #
  664. # Pet.find(4, 5, 6) # => ActiveRecord::RecordNotFound: Couldn't find all Pets with 'id': (4, 5, 6)
  665. 3 def destroy(*records)
  666. 96 @association.destroy(*records).tap { reset_scope }
  667. end
  668. ##
  669. # :method: distinct
  670. #
  671. # :call-seq:
  672. # distinct(value = true)
  673. #
  674. # Specifies whether the records should be unique or not.
  675. #
  676. # class Person < ActiveRecord::Base
  677. # has_many :pets
  678. # end
  679. #
  680. # person.pets.select(:name)
  681. # # => [
  682. # # #<Pet name: "Fancy-Fancy">,
  683. # # #<Pet name: "Fancy-Fancy">
  684. # # ]
  685. #
  686. # person.pets.select(:name).distinct
  687. # # => [#<Pet name: "Fancy-Fancy">]
  688. #
  689. # person.pets.select(:name).distinct.distinct(false)
  690. # # => [
  691. # # #<Pet name: "Fancy-Fancy">,
  692. # # #<Pet name: "Fancy-Fancy">
  693. # # ]
  694. #--
  695. 3 def calculate(operation, column_name)
  696. 924 null_scope? ? scope.calculate(operation, column_name) : super
  697. end
  698. 3 def pluck(*column_names)
  699. 30 null_scope? ? scope.pluck(*column_names) : super
  700. end
  701. ##
  702. # :method: count
  703. #
  704. # :call-seq:
  705. # count(column_name = nil, &block)
  706. #
  707. # Count all records.
  708. #
  709. # class Person < ActiveRecord::Base
  710. # has_many :pets
  711. # end
  712. #
  713. # # This will perform the count using SQL.
  714. # person.pets.count # => 3
  715. # person.pets
  716. # # => [
  717. # # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
  718. # # #<Pet id: 2, name: "Spook", person_id: 1>,
  719. # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
  720. # # ]
  721. #
  722. # Passing a block will select all of a person's pets in SQL and then
  723. # perform the count using Ruby.
  724. #
  725. # person.pets.count { |pet| pet.name.include?('-') } # => 2
  726. # Returns the size of the collection. If the collection hasn't been loaded,
  727. # it executes a <tt>SELECT COUNT(*)</tt> query. Else it calls <tt>collection.size</tt>.
  728. #
  729. # If the collection has been already loaded +size+ and +length+ are
  730. # equivalent. If not and you are going to need the records anyway
  731. # +length+ will take one less query. Otherwise +size+ is more efficient.
  732. #
  733. # class Person < ActiveRecord::Base
  734. # has_many :pets
  735. # end
  736. #
  737. # person.pets.size # => 3
  738. # # executes something like SELECT COUNT(*) FROM "pets" WHERE "pets"."person_id" = 1
  739. #
  740. # person.pets # This will execute a SELECT * FROM query
  741. # # => [
  742. # # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
  743. # # #<Pet id: 2, name: "Spook", person_id: 1>,
  744. # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
  745. # # ]
  746. #
  747. # person.pets.size # => 3
  748. # # Because the collection is already loaded, this will behave like
  749. # # collection.size and no SQL count query is executed.
  750. 3 def size
  751. 1023 @association.size
  752. end
  753. ##
  754. # :method: length
  755. #
  756. # :call-seq:
  757. # length()
  758. #
  759. # Returns the size of the collection calling +size+ on the target.
  760. # If the collection has been already loaded, +length+ and +size+ are
  761. # equivalent. If not and you are going to need the records anyway this
  762. # method will take one less query. Otherwise +size+ is more efficient.
  763. #
  764. # class Person < ActiveRecord::Base
  765. # has_many :pets
  766. # end
  767. #
  768. # person.pets.length # => 3
  769. # # executes something like SELECT "pets".* FROM "pets" WHERE "pets"."person_id" = 1
  770. #
  771. # # Because the collection is loaded, you can
  772. # # call the collection with no additional queries:
  773. # person.pets
  774. # # => [
  775. # # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
  776. # # #<Pet id: 2, name: "Spook", person_id: 1>,
  777. # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
  778. # # ]
  779. # Returns +true+ if the collection is empty. If the collection has been
  780. # loaded it is equivalent
  781. # to <tt>collection.size.zero?</tt>. If the collection has not been loaded,
  782. # it is equivalent to <tt>!collection.exists?</tt>. If the collection has
  783. # not already been loaded and you are going to fetch the records anyway it
  784. # is better to check <tt>collection.length.zero?</tt>.
  785. #
  786. # class Person < ActiveRecord::Base
  787. # has_many :pets
  788. # end
  789. #
  790. # person.pets.count # => 1
  791. # person.pets.empty? # => false
  792. #
  793. # person.pets.delete_all
  794. #
  795. # person.pets.count # => 0
  796. # person.pets.empty? # => true
  797. 3 def empty?
  798. 231 @association.empty?
  799. end
  800. ##
  801. # :method: any?
  802. #
  803. # :call-seq:
  804. # any?()
  805. #
  806. # Returns +true+ if the collection is not empty.
  807. #
  808. # class Person < ActiveRecord::Base
  809. # has_many :pets
  810. # end
  811. #
  812. # person.pets.count # => 0
  813. # person.pets.any? # => false
  814. #
  815. # person.pets << Pet.new(name: 'Snoop')
  816. # person.pets.count # => 1
  817. # person.pets.any? # => true
  818. #
  819. # You can also pass a +block+ to define criteria. The behavior
  820. # is the same, it returns true if the collection based on the
  821. # criteria is not empty.
  822. #
  823. # person.pets
  824. # # => [#<Pet name: "Snoop", group: "dogs">]
  825. #
  826. # person.pets.any? do |pet|
  827. # pet.group == 'cats'
  828. # end
  829. # # => false
  830. #
  831. # person.pets.any? do |pet|
  832. # pet.group == 'dogs'
  833. # end
  834. # # => true
  835. ##
  836. # :method: many?
  837. #
  838. # :call-seq:
  839. # many?()
  840. #
  841. # Returns true if the collection has more than one record.
  842. # Equivalent to <tt>collection.size > 1</tt>.
  843. #
  844. # class Person < ActiveRecord::Base
  845. # has_many :pets
  846. # end
  847. #
  848. # person.pets.count # => 1
  849. # person.pets.many? # => false
  850. #
  851. # person.pets << Pet.new(name: 'Snoopy')
  852. # person.pets.count # => 2
  853. # person.pets.many? # => true
  854. #
  855. # You can also pass a +block+ to define criteria. The
  856. # behavior is the same, it returns true if the collection
  857. # based on the criteria has more than one record.
  858. #
  859. # person.pets
  860. # # => [
  861. # # #<Pet name: "Gorby", group: "cats">,
  862. # # #<Pet name: "Puff", group: "cats">,
  863. # # #<Pet name: "Snoop", group: "dogs">
  864. # # ]
  865. #
  866. # person.pets.many? do |pet|
  867. # pet.group == 'dogs'
  868. # end
  869. # # => false
  870. #
  871. # person.pets.many? do |pet|
  872. # pet.group == 'cats'
  873. # end
  874. # # => true
  875. # Returns +true+ if the given +record+ is present in the collection.
  876. #
  877. # class Person < ActiveRecord::Base
  878. # has_many :pets
  879. # end
  880. #
  881. # person.pets # => [#<Pet id: 20, name: "Snoop">]
  882. #
  883. # person.pets.include?(Pet.find(20)) # => true
  884. # person.pets.include?(Pet.find(21)) # => false
  885. 3 def include?(record)
  886. 288 !!@association.include?(record)
  887. end
  888. 3 def proxy_association # :nodoc:
  889. 1048 @association
  890. end
  891. # Returns a <tt>Relation</tt> object for the records in this association
  892. 3 def scope
  893. 16402 @scope ||= @association.scope
  894. end
  895. # Equivalent to <tt>Array#==</tt>. Returns +true+ if the two arrays
  896. # contain the same number of elements and if each element is equal
  897. # to the corresponding element in the +other+ array, otherwise returns
  898. # +false+.
  899. #
  900. # class Person < ActiveRecord::Base
  901. # has_many :pets
  902. # end
  903. #
  904. # person.pets
  905. # # => [
  906. # # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
  907. # # #<Pet id: 2, name: "Spook", person_id: 1>
  908. # # ]
  909. #
  910. # other = person.pets.to_ary
  911. #
  912. # person.pets == other
  913. # # => true
  914. #
  915. # other = [Pet.new(id: 1), Pet.new(id: 2)]
  916. #
  917. # person.pets == other
  918. # # => false
  919. 3 def ==(other)
  920. 522 load_target == other
  921. end
  922. ##
  923. # :method: to_ary
  924. #
  925. # :call-seq:
  926. # to_ary()
  927. #
  928. # Returns a new array of objects from the collection. If the collection
  929. # hasn't been loaded, it fetches the records from the database.
  930. #
  931. # class Person < ActiveRecord::Base
  932. # has_many :pets
  933. # end
  934. #
  935. # person.pets
  936. # # => [
  937. # # #<Pet id: 4, name: "Benny", person_id: 1>,
  938. # # #<Pet id: 5, name: "Brain", person_id: 1>,
  939. # # #<Pet id: 6, name: "Boss", person_id: 1>
  940. # # ]
  941. #
  942. # other_pets = person.pets.to_ary
  943. # # => [
  944. # # #<Pet id: 4, name: "Benny", person_id: 1>,
  945. # # #<Pet id: 5, name: "Brain", person_id: 1>,
  946. # # #<Pet id: 6, name: "Boss", person_id: 1>
  947. # # ]
  948. #
  949. # other_pets.replace([Pet.new(name: 'BooGoo')])
  950. #
  951. # other_pets
  952. # # => [#<Pet id: nil, name: "BooGoo", person_id: 1>]
  953. #
  954. # person.pets
  955. # # This is not affected by replace
  956. # # => [
  957. # # #<Pet id: 4, name: "Benny", person_id: 1>,
  958. # # #<Pet id: 5, name: "Brain", person_id: 1>,
  959. # # #<Pet id: 6, name: "Boss", person_id: 1>
  960. # # ]
  961. 3 def records # :nodoc:
  962. 3312 load_target
  963. end
  964. # Adds one or more +records+ to the collection by setting their foreign keys
  965. # to the association's primary key. Since <tt><<</tt> flattens its argument list and
  966. # inserts each record, +push+ and +concat+ behave identically. Returns +self+
  967. # so several appends may be chained together.
  968. #
  969. # class Person < ActiveRecord::Base
  970. # has_many :pets
  971. # end
  972. #
  973. # person.pets.size # => 0
  974. # person.pets << Pet.new(name: 'Fancy-Fancy')
  975. # person.pets << [Pet.new(name: 'Spook'), Pet.new(name: 'Choo-Choo')]
  976. # person.pets.size # => 3
  977. #
  978. # person.id # => 1
  979. # person.pets
  980. # # => [
  981. # # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
  982. # # #<Pet id: 2, name: "Spook", person_id: 1>,
  983. # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
  984. # # ]
  985. 3 def <<(*records)
  986. 653 proxy_association.concat(records) && self
  987. end
  988. 3 alias_method :push, :<<
  989. 3 alias_method :append, :<<
  990. 3 alias_method :concat, :<<
  991. 3 def prepend(*args) # :nodoc:
  992. 3 raise NoMethodError, "prepend on association is not defined. Please use <<, push or append"
  993. end
  994. # Equivalent to +delete_all+. The difference is that returns +self+, instead
  995. # of an array with the deleted objects, so methods can be chained. See
  996. # +delete_all+ for more information.
  997. # Note that because +delete_all+ removes records by directly
  998. # running an SQL query into the database, the +updated_at+ column of
  999. # the object is not changed.
  1000. 3 def clear
  1001. 54 delete_all
  1002. 51 self
  1003. end
  1004. # Reloads the collection from the database. Returns +self+.
  1005. #
  1006. # class Person < ActiveRecord::Base
  1007. # has_many :pets
  1008. # end
  1009. #
  1010. # person.pets # fetches pets from the database
  1011. # # => [#<Pet id: 1, name: "Snoop", group: "dogs", person_id: 1>]
  1012. #
  1013. # person.pets # uses the pets cache
  1014. # # => [#<Pet id: 1, name: "Snoop", group: "dogs", person_id: 1>]
  1015. #
  1016. # person.pets.reload # fetches pets from the database
  1017. # # => [#<Pet id: 1, name: "Snoop", group: "dogs", person_id: 1>]
  1018. 3 def reload
  1019. 341 proxy_association.reload(true)
  1020. 341 reset_scope
  1021. end
  1022. # Unloads the association. Returns +self+.
  1023. #
  1024. # class Person < ActiveRecord::Base
  1025. # has_many :pets
  1026. # end
  1027. #
  1028. # person.pets # fetches pets from the database
  1029. # # => [#<Pet id: 1, name: "Snoop", group: "dogs", person_id: 1>]
  1030. #
  1031. # person.pets # uses the pets cache
  1032. # # => [#<Pet id: 1, name: "Snoop", group: "dogs", person_id: 1>]
  1033. #
  1034. # person.pets.reset # clears the pets cache
  1035. #
  1036. # person.pets # fetches pets from the database
  1037. # # => [#<Pet id: 1, name: "Snoop", group: "dogs", person_id: 1>]
  1038. 3 def reset
  1039. 24 proxy_association.reset
  1040. 24 proxy_association.reset_scope
  1041. 24 reset_scope
  1042. end
  1043. 3 def reset_scope # :nodoc:
  1044. 12139 @offsets = @take = nil
  1045. 12139 @scope = nil
  1046. 12139 self
  1047. end
  1048. 3 delegate_methods = [
  1049. QueryMethods,
  1050. SpawnMethods,
  1051. ].flat_map { |klass|
  1052. 6 klass.public_instance_methods(false)
  1053. } - self.public_instance_methods(false) - [:select] + [
  1054. :scoping, :values, :insert, :insert_all, :insert!, :insert_all!, :upsert, :upsert_all
  1055. ]
  1056. 3 delegate(*delegate_methods, to: :scope)
  1057. 3 private
  1058. 3 def find_nth_with_limit(index, limit)
  1059. 660 load_target if find_from_target?
  1060. 642 super
  1061. end
  1062. 3 def find_nth_from_last(index)
  1063. 18 load_target if find_from_target?
  1064. 18 super
  1065. end
  1066. 3 def null_scope?
  1067. 954 @association.null_scope?
  1068. end
  1069. 3 def find_from_target?
  1070. 864 @association.find_from_target?
  1071. end
  1072. 3 def exec_queries
  1073. 39 load_target
  1074. end
  1075. end
  1076. end
  1077. end

lib/active_record/associations/foreign_association.rb

100.0% lines covered

17 relevant lines. 17 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 module ActiveRecord::Associations
  3. 3 module ForeignAssociation # :nodoc:
  4. 3 def foreign_key_present?
  5. 1170 if reflection.klass.primary_key
  6. 1062 owner.attribute_present?(reflection.active_record_primary_key)
  7. else
  8. 108 false
  9. end
  10. end
  11. 3 def nullified_owner_attributes
  12. 125 Hash.new.tap do |attrs|
  13. 125 attrs[reflection.foreign_key] = nil
  14. 125 attrs[reflection.type] = nil if reflection.type.present?
  15. end
  16. end
  17. 3 private
  18. # Sets the owner attributes on the given record
  19. 3 def set_owner_attributes(record)
  20. 3044 return if options[:through]
  21. 2366 key = owner._read_attribute(reflection.join_foreign_key)
  22. 2366 record._write_attribute(reflection.join_primary_key, key)
  23. 2366 if reflection.type
  24. 126 record._write_attribute(reflection.type, owner.class.polymorphic_name)
  25. end
  26. end
  27. end
  28. end

lib/active_record/associations/has_many_association.rb

100.0% lines covered

60 relevant lines. 60 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 module ActiveRecord
  3. 3 module Associations
  4. # = Active Record Has Many Association
  5. # This is the proxy that handles a has many association.
  6. #
  7. # If the association has a <tt>:through</tt> option further specialization
  8. # is provided by its child HasManyThroughAssociation.
  9. 3 class HasManyAssociation < CollectionAssociation #:nodoc:
  10. 3 include ForeignAssociation
  11. 3 def handle_dependency
  12. 817 case options[:dependent]
  13. when :restrict_with_exception
  14. 3 raise ActiveRecord::DeleteRestrictionError.new(reflection.name) unless empty?
  15. when :restrict_with_error
  16. 6 unless empty?
  17. 6 record = owner.class.human_attribute_name(reflection.name).downcase
  18. 6 owner.errors.add(:base, :'restrict_dependent_destroy.has_many', record: record)
  19. 6 throw(:abort)
  20. end
  21. when :destroy
  22. # No point in executing the counter update since we're going to destroy the parent anyway
  23. 778 load_target.each { |t| t.destroyed_by_association = reflection }
  24. 622 destroy_all
  25. else
  26. 186 delete_all
  27. end
  28. end
  29. 3 def insert_record(record, validate = true, raise = false)
  30. 2712 set_owner_attributes(record)
  31. 2712 super
  32. end
  33. 3 private
  34. # Returns the number of records in this collection.
  35. #
  36. # If the association has a counter cache it gets that value. Otherwise
  37. # it will attempt to do a count via SQL, bounded to <tt>:limit</tt> if
  38. # there's one. Some configuration options like :group make it impossible
  39. # to do an SQL count, in those cases the array count will be used.
  40. #
  41. # That does not depend on whether the collection has already been loaded
  42. # or not. The +size+ method is the one that takes the loaded flag into
  43. # account and delegates to +count_records+ if needed.
  44. #
  45. # If the collection is empty the target is set to an empty array and
  46. # the loaded flag is set to true as well.
  47. 3 def count_records
  48. 403 count = if reflection.has_cached_counter?
  49. 120 owner.read_attribute(reflection.counter_cache_column).to_i
  50. else
  51. 283 scope.count(:all)
  52. end
  53. # If there's nothing in the database and @target has no new records
  54. # we are certain the current target is an empty array. This is a
  55. # documented side-effect of the method that may avoid an extra SELECT.
  56. 403 loaded! if count == 0
  57. 403 [association_scope.limit_value, count].compact.min
  58. end
  59. 3 def update_counter(difference, reflection = reflection())
  60. 1106 if reflection.has_cached_counter?
  61. 90 owner.increment!(reflection.counter_cache_column, difference)
  62. end
  63. end
  64. 3 def update_counter_in_memory(difference, reflection = reflection())
  65. 2466 if reflection.counter_must_be_updated_by_has_many?
  66. 136 counter = reflection.counter_cache_column
  67. 136 owner.increment(counter, difference)
  68. 136 owner.send(:"clear_#{counter}_change")
  69. end
  70. end
  71. 3 def delete_count(method, scope)
  72. 704 if method == :delete_all
  73. 588 scope.delete_all
  74. else
  75. 116 scope.update_all(nullified_owner_attributes)
  76. end
  77. end
  78. 3 def delete_or_nullify_all_records(method)
  79. 636 count = delete_count(method, scope)
  80. 636 update_counter(-count)
  81. 636 count
  82. end
  83. # Deletes the records according to the <tt>:dependent</tt> option.
  84. 3 def delete_records(records, method)
  85. 266 if method == :destroy
  86. 198 records.each(&:destroy!)
  87. 180 update_counter(-records.length) unless reflection.inverse_updates_counter_cache?
  88. else
  89. 68 scope = self.scope.where(reflection.klass.primary_key => records)
  90. 68 update_counter(-delete_count(method, scope))
  91. end
  92. end
  93. 3 def concat_records(records, *)
  94. 964 update_counter_if_success(super, records.length)
  95. end
  96. 3 def _create_record(attributes, *)
  97. 1601 if attributes.is_a?(Array)
  98. 6 super
  99. else
  100. 1595 update_counter_if_success(super, 1)
  101. end
  102. end
  103. 3 def update_counter_if_success(saved_successfully, difference)
  104. 2466 if saved_successfully
  105. 2466 update_counter_in_memory(difference)
  106. end
  107. 2466 saved_successfully
  108. end
  109. 3 def difference(a, b)
  110. 391 a - b
  111. end
  112. 3 def intersection(a, b)
  113. 80 a & b
  114. end
  115. end
  116. end
  117. end

lib/active_record/associations/has_many_through_association.rb

99.11% lines covered

112 relevant lines. 111 lines covered and 1 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 module ActiveRecord
  3. 3 module Associations
  4. # = Active Record Has Many Through Association
  5. 3 class HasManyThroughAssociation < HasManyAssociation #:nodoc:
  6. 3 include ThroughAssociation
  7. 3 def initialize(owner, reflection)
  8. 4292 super
  9. 4280 @through_records = {}.compare_by_identity
  10. end
  11. 3 def concat(*records)
  12. 421 unless owner.new_record?
  13. 298 records.flatten.each do |record|
  14. 319 raise_on_type_mismatch!(record)
  15. end
  16. end
  17. 412 super
  18. end
  19. 3 def insert_record(record, validate = true, raise = false)
  20. 1045 ensure_not_nested
  21. 1045 if record.new_record? || record.has_changes_to_save?
  22. 678 return unless super
  23. end
  24. 1030 save_through_record(record)
  25. 1018 record
  26. end
  27. 3 private
  28. 3 def concat_records(records)
  29. 412 ensure_not_nested
  30. 409 records = super(records, true)
  31. 394 if owner.new_record? && records
  32. 123 records.flatten.each do |record|
  33. 144 build_through_record(record)
  34. end
  35. end
  36. 394 records
  37. end
  38. # The through record (built with build_record) is temporarily cached
  39. # so that it may be reused if insert_record is subsequently called.
  40. #
  41. # However, after insert_record has been called, the cache is cleared in
  42. # order to allow multiple instances of the same record in an association.
  43. 3 def build_through_record(record)
  44. 1234 @through_records[record] ||= begin
  45. 1084 ensure_mutable
  46. 1078 attributes = through_scope_attributes
  47. 1078 attributes[source_reflection.name] = record
  48. 1078 attributes[source_reflection.foreign_type] = options[:source_type] if options[:source_type]
  49. 1078 through_association.build(attributes)
  50. end
  51. end
  52. 3 def through_scope_attributes
  53. scope.where_values_hash(through_association.reflection.name.to_s).
  54. 1354 except!(through_association.reflection.foreign_key,
  55. through_association.reflection.klass.inheritance_column)
  56. end
  57. 3 def save_through_record(record)
  58. 1030 association = build_through_record(record)
  59. 1024 if association.changed?
  60. 874 association.save!
  61. end
  62. ensure
  63. 1030 @through_records.delete(record)
  64. end
  65. 3 def build_record(attributes)
  66. 747 ensure_not_nested
  67. 741 record = super
  68. 735 inverse = source_reflection.inverse_of
  69. 735 if inverse
  70. 63 if inverse.collection?
  71. 54 record.send(inverse.name) << build_through_record(record)
  72. 9 elsif inverse.has_one?
  73. 6 record.send("#{inverse.name}=", build_through_record(record))
  74. end
  75. end
  76. 735 record
  77. end
  78. 3 def remove_records(existing_records, records, method)
  79. 336 super
  80. 312 delete_through_records(records)
  81. end
  82. 3 def target_reflection_has_associated_record?
  83. 1149 !(through_reflection.belongs_to? && owner[through_reflection.foreign_key].blank?)
  84. end
  85. 3 def update_through_counter?(method)
  86. 273 case method
  87. when :destroy
  88. 96 !through_reflection.inverse_updates_counter_cache?
  89. when :nullify
  90. 45 false
  91. else
  92. 132 true
  93. end
  94. end
  95. 3 def delete_or_nullify_all_records(method)
  96. 96 delete_records(load_target, method)
  97. end
  98. 3 def delete_records(records, method)
  99. 303 ensure_not_nested
  100. 291 scope = through_association.scope
  101. 291 scope.where! construct_join_attributes(*records)
  102. 276 scope = scope.where(through_scope_attributes)
  103. 276 case method
  104. when :destroy
  105. 96 if scope.klass.primary_key
  106. 36 count = scope.destroy_all.count(&:destroyed?)
  107. else
  108. 60 scope.each(&:_run_destroy_callbacks)
  109. 60 count = scope.delete_all
  110. end
  111. when :nullify
  112. 45 count = scope.update_all(source_reflection.foreign_key => nil)
  113. else
  114. 135 count = scope.delete_all
  115. end
  116. 276 delete_through_records(records)
  117. 276 if source_reflection.options[:counter_cache] && method != :destroy
  118. 12 counter = source_reflection.counter_cache_column
  119. 12 klass.decrement_counter counter, records.map(&:id)
  120. end
  121. 276 if through_reflection.collection? && update_through_counter?(method)
  122. 225 update_counter(-count, through_reflection)
  123. else
  124. 51 update_counter(-count)
  125. end
  126. 276 count
  127. end
  128. 3 def difference(a, b)
  129. 273 distribution = distribution(b)
  130. 519 a.reject { |record| mark_occurrence(distribution, record) }
  131. end
  132. 3 def intersection(a, b)
  133. 81 distribution = distribution(b)
  134. 186 a.select { |record| mark_occurrence(distribution, record) }
  135. end
  136. 3 def mark_occurrence(distribution, record)
  137. 351 distribution[record] > 0 && distribution[record] -= 1
  138. end
  139. 3 def distribution(array)
  140. 354 array.each_with_object(Hash.new(0)) do |record, distribution|
  141. 291 distribution[record] += 1
  142. end
  143. end
  144. 3 def through_records_for(record)
  145. 523 attributes = construct_join_attributes(record)
  146. 523 candidates = Array.wrap(through_association.target)
  147. 523 candidates.find_all do |c|
  148. 264 attributes.all? do |key, value|
  149. 270 c.public_send(key) == value
  150. end
  151. end
  152. end
  153. 3 def delete_through_records(records)
  154. 588 records.each do |record|
  155. 523 through_records = through_records_for(record)
  156. 523 if through_reflection.collection?
  157. 646 through_records.each { |r| through_association.target.delete(r) }
  158. else
  159. 6 if through_records.include?(through_association.target)
  160. through_association.target = nil
  161. end
  162. end
  163. 523 @through_records.delete(record)
  164. end
  165. end
  166. 3 def find_target
  167. 1149 return [] unless target_reflection_has_associated_record?
  168. 1146 super
  169. end
  170. # NOTE - not sure that we can actually cope with inverses here
  171. 3 def invertible_for?(record)
  172. 4595 false
  173. end
  174. end
  175. end
  176. end

lib/active_record/associations/has_one_association.rb

100.0% lines covered

60 relevant lines. 60 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 module ActiveRecord
  3. 3 module Associations
  4. # = Active Record Has One Association
  5. 3 class HasOneAssociation < SingularAssociation #:nodoc:
  6. 3 include ForeignAssociation
  7. 3 def handle_dependency
  8. 132 case options[:dependent]
  9. when :restrict_with_exception
  10. 6 raise ActiveRecord::DeleteRestrictionError.new(reflection.name) if load_target
  11. when :restrict_with_error
  12. 12 if load_target
  13. 6 record = owner.class.human_attribute_name(reflection.name).downcase
  14. 6 owner.errors.add(:base, :'restrict_dependent_destroy.has_one', record: record)
  15. 6 throw(:abort)
  16. end
  17. else
  18. 114 delete
  19. end
  20. end
  21. 3 def delete(method = options[:dependent])
  22. 114 if load_target
  23. 63 case method
  24. when :delete
  25. 12 target.delete
  26. when :destroy
  27. 39 target.destroyed_by_association = reflection
  28. 39 target.destroy
  29. 39 throw(:abort) unless target.destroyed?
  30. when :nullify
  31. 12 target.update_columns(nullified_owner_attributes) if target.persisted?
  32. end
  33. end
  34. end
  35. 3 private
  36. 3 def replace(record, save = true)
  37. 681 raise_on_type_mismatch!(record) if record
  38. 675 return target unless load_target || record
  39. 672 assigning_another_record = target != record
  40. 672 if assigning_another_record || record.has_changes_to_save?
  41. 338 save &&= owner.persisted?
  42. 338 transaction_if(save) do
  43. 338 remove_target!(options[:dependent]) if target && !target.destroyed? && assigning_another_record
  44. 335 if record
  45. 326 set_owner_attributes(record)
  46. 326 set_inverse_instance(record)
  47. 326 if save && !record.save
  48. 6 nullify_owner_attributes(record)
  49. 6 set_owner_attributes(target) if target
  50. 6 raise RecordNotSaved, "Failed to save the new associated #{reflection.name}."
  51. end
  52. end
  53. end
  54. end
  55. 663 self.target = record
  56. end
  57. # The reason that the save param for replace is false, if for create (not just build),
  58. # is because the setting of the foreign keys is actually handled by the scoping when
  59. # the record is instantiated, and so they are set straight away and do not need to be
  60. # updated within replace.
  61. 3 def set_new_record(record)
  62. 498 replace(record, false)
  63. end
  64. 3 def remove_target!(method)
  65. 113 case method
  66. when :delete
  67. 3 target.delete
  68. when :destroy
  69. 30 target.destroyed_by_association = reflection
  70. 30 if target.persisted?
  71. 27 target.destroy
  72. end
  73. else
  74. 80 nullify_owner_attributes(target)
  75. 80 remove_inverse_instance(target)
  76. 80 if target.persisted? && owner.persisted? && !target.save
  77. 3 set_owner_attributes(target)
  78. 3 raise RecordNotSaved, "Failed to remove the existing associated #{reflection.name}. " \
  79. "The record failed to save after its foreign key was set to nil."
  80. end
  81. end
  82. end
  83. 3 def nullify_owner_attributes(record)
  84. 86 record[reflection.foreign_key] = nil
  85. end
  86. 3 def transaction_if(value)
  87. 338 if value
  88. 156 reflection.klass.transaction { yield }
  89. else
  90. 260 yield
  91. end
  92. end
  93. 3 def _create_record(attributes, raise_error = false, &block)
  94. 378 unless owner.persisted?
  95. 3 raise ActiveRecord::RecordNotSaved, "You cannot call create unless the parent is saved"
  96. end
  97. 375 super
  98. end
  99. end
  100. end
  101. end

lib/active_record/associations/has_one_through_association.rb

100.0% lines covered

25 relevant lines. 25 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 module ActiveRecord
  3. 3 module Associations
  4. # = Active Record Has One Through Association
  5. 3 class HasOneThroughAssociation < HasOneAssociation #:nodoc:
  6. 3 include ThroughAssociation
  7. 3 private
  8. 3 def replace(record, save = true)
  9. 66 create_through_record(record, save)
  10. 63 self.target = record
  11. end
  12. 3 def create_through_record(record, save)
  13. 66 ensure_not_nested
  14. 63 through_proxy = through_association
  15. 63 through_record = through_proxy.load_target
  16. 63 if through_record && !record
  17. 6 through_record.destroy
  18. 57 elsif record
  19. 57 attributes = construct_join_attributes(record)
  20. 57 if through_record && through_record.destroyed?
  21. 3 through_record = through_proxy.tap(&:reload).target
  22. end
  23. 57 if through_record
  24. 24 if through_record.new_record?
  25. 6 through_record.assign_attributes(attributes)
  26. else
  27. 18 through_record.update(attributes)
  28. end
  29. 33 elsif owner.new_record? || !save
  30. 24 through_proxy.build(attributes)
  31. else
  32. 9 through_proxy.create(attributes)
  33. end
  34. end
  35. end
  36. end
  37. end
  38. end

lib/active_record/associations/join_dependency.rb

98.16% lines covered

163 relevant lines. 160 lines covered and 3 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 module ActiveRecord
  3. 3 module Associations
  4. 3 class JoinDependency # :nodoc:
  5. 3 autoload :JoinBase, "active_record/associations/join_dependency/join_base"
  6. 3 autoload :JoinAssociation, "active_record/associations/join_dependency/join_association"
  7. 3 class Aliases # :nodoc:
  8. 3 def initialize(tables)
  9. 535 @tables = tables
  10. 535 @alias_cache = tables.each_with_object({}) { |table, h|
  11. 1235 h[table.node] = table.columns.each_with_object({}) { |column, i|
  12. 10559 i[column.name] = column.alias
  13. }
  14. }
  15. 535 @columns_cache = tables.each_with_object({}) { |table, h|
  16. 1235 h[table.node] = table.columns
  17. }
  18. end
  19. 3 def columns
  20. 535 @tables.flat_map(&:column_aliases)
  21. end
  22. 3 def column_aliases(node)
  23. 2053 @columns_cache[node]
  24. end
  25. 3 def column_alias(node, column)
  26. 3017 @alias_cache[node][column]
  27. end
  28. 3 Table = Struct.new(:node, :columns) do # :nodoc:
  29. 3 def column_aliases
  30. 1235 t = node.table
  31. 11794 columns.map { |column| t[column.name].as(column.alias) }
  32. end
  33. end
  34. 3 Column = Struct.new(:name, :alias)
  35. end
  36. 3 def self.make_tree(associations)
  37. 4018 hash = {}
  38. 4018 walk_tree associations, hash
  39. 4018 hash
  40. end
  41. 3 def self.walk_tree(associations, hash)
  42. 6527 case associations
  43. when Symbol, String
  44. 2191 hash[associations.to_sym] ||= {}
  45. when Array
  46. 4057 associations.each do |assoc|
  47. 2212 walk_tree assoc, hash
  48. end
  49. when Hash
  50. 279 associations.each do |k, v|
  51. 297 cache = hash[k] ||= {}
  52. 297 walk_tree v, cache
  53. end
  54. else
  55. raise ConfigurationError, associations.inspect
  56. end
  57. end
  58. 3 def initialize(base, table, associations, join_type)
  59. 4018 tree = self.class.make_tree associations
  60. 4018 @join_root = JoinBase.new(base, table, build(tree, base))
  61. 3991 @join_type = join_type
  62. end
  63. 3 def base_klass
  64. 1211 join_root.base_klass
  65. end
  66. 3 def reflections
  67. 958 join_root.drop(1).map!(&:reflection)
  68. end
  69. 3 def join_constraints(joins_to_add, alias_tracker)
  70. 2114 @alias_tracker = alias_tracker
  71. 2114 @joined_tables = {}
  72. 2114 joins = make_join_constraints(join_root, join_type)
  73. 2114 joins.concat joins_to_add.flat_map { |oj|
  74. 1301 if join_root.match? oj.join_root
  75. 1208 walk(join_root, oj.join_root, oj.join_type)
  76. else
  77. 93 make_join_constraints(oj.join_root, oj.join_type)
  78. end
  79. }
  80. end
  81. 3 def instantiate(result_set, strict_loading_value, &block)
  82. 526 primary_key = aliases.column_alias(join_root, join_root.primary_key)
  83. 526 seen = Hash.new { |i, parent|
  84. 1086 i[parent] = Hash.new { |j, child_class|
  85. 1170 j[child_class] = {}
  86. }
  87. }.compare_by_identity
  88. 1667 model_cache = Hash.new { |h, klass| h[klass] = {} }
  89. 526 parents = model_cache[join_root]
  90. 526 column_aliases = aliases.column_aliases(join_root)
  91. 526 column_names = []
  92. 526 result_set.columns.each do |name|
  93. 10448 column_names << name unless /\At\d+_r\d+\z/.match?(name)
  94. end
  95. 526 if column_names.empty?
  96. 496 column_types = {}
  97. else
  98. 30 column_types = result_set.column_types
  99. 30 unless column_types.empty?
  100. attribute_types = join_root.attribute_types
  101. column_types = column_types.slice(*column_names).delete_if { |k, _| attribute_types.key?(k) }
  102. end
  103. 162 column_aliases += column_names.map! { |name| Aliases::Column.new(name, name) }
  104. end
  105. 526 message_bus = ActiveSupport::Notifications.instrumenter
  106. 526 payload = {
  107. record_count: result_set.length,
  108. class_name: join_root.base_klass.name
  109. }
  110. 526 message_bus.instrument("instantiation.active_record", payload) do
  111. 526 result_set.each { |row_hash|
  112. 2316 parent_key = primary_key ? row_hash[primary_key] : row_hash
  113. 2316 parent = parents[parent_key] ||= join_root.instantiate(row_hash, column_aliases, column_types, &block)
  114. 2316 construct(parent, join_root, row_hash, seen, model_cache, strict_loading_value)
  115. }
  116. end
  117. 526 parents.values
  118. end
  119. 3 def apply_column_aliases(relation)
  120. 535 @join_root_alias = relation.select_values.empty?
  121. 1070 relation._select!(-> { aliases.columns })
  122. end
  123. 3 def each(&block)
  124. 628 join_root.each(&block)
  125. end
  126. 3 protected
  127. 3 attr_reader :join_root, :join_type
  128. 3 private
  129. 3 attr_reader :alias_tracker, :join_root_alias
  130. 3 def aliases
  131. 5605 @aliases ||= Aliases.new join_root.each_with_index.map { |join_part, i|
  132. 1235 column_names = if join_part == join_root && !join_root_alias
  133. 30 primary_key = join_root.primary_key
  134. 30 primary_key ? [primary_key] : []
  135. else
  136. 1205 join_part.column_names
  137. end
  138. 1235 columns = column_names.each_with_index.map { |column_name, j|
  139. 10559 Aliases::Column.new column_name, "t#{i}_r#{j}"
  140. }
  141. 1235 Aliases::Table.new(join_part, columns)
  142. }
  143. end
  144. 3 def make_join_constraints(join_root, join_type)
  145. 2207 join_root.children.flat_map do |child|
  146. 1086 make_constraints(join_root, child, join_type)
  147. end
  148. end
  149. 3 def make_constraints(parent, child, join_type)
  150. 2528 foreign_table = parent.table
  151. 2528 foreign_klass = parent.base_klass
  152. 2528 child.join_constraints(foreign_table, foreign_klass, join_type, alias_tracker) do |reflection|
  153. 3151 table, terminated = @joined_tables[reflection]
  154. 3151 root = reflection == child.reflection
  155. 3151 if table && (!root || !terminated)
  156. 12 @joined_tables[reflection] = [table, root] if root
  157. 12 next table, true
  158. end
  159. 3139 table = alias_tracker.aliased_table_for(reflection.klass.arel_table) do
  160. 447 name = reflection.alias_candidate(parent.table_name)
  161. 447 root ? name : "#{name}_join"
  162. end
  163. 3139 @joined_tables[reflection] ||= [table, root] if join_type == Arel::Nodes::OuterJoin
  164. 3139 table
  165. 288 end.concat child.children.flat_map { |c| make_constraints(child, c, join_type) }
  166. end
  167. 3 def walk(left, right, join_type)
  168. 1253 intersection, missing = right.children.map { |node1|
  169. 1271 [left.children.find { |node2| node1.match? node2 }, node1]
  170. }.partition(&:first)
  171. 1298 joins = intersection.flat_map { |l, r| r.table = l.table; walk(l, r, join_type) }
  172. 2407 joins.concat missing.flat_map { |_, n| make_constraints(left, n, join_type) }
  173. end
  174. 3 def find_reflection(klass, name)
  175. 2464 klass._reflect_on_association(name) ||
  176. raise(ConfigurationError, "Can't join '#{klass.name}' to association named '#{name}'; perhaps you misspelled it?")
  177. end
  178. 3 def build(associations, base_klass)
  179. 6455 associations.map do |name, right|
  180. 2464 reflection = find_reflection base_klass, name
  181. 2455 reflection.check_validity!
  182. 2455 reflection.check_eager_loadable!
  183. 2449 if reflection.polymorphic?
  184. 12 raise EagerLoadPolymorphicError.new(reflection)
  185. end
  186. 2437 JoinAssociation.new(reflection, build(right, reflection.klass))
  187. end
  188. end
  189. 3 def construct(ar_parent, parent, row, seen, model_cache, strict_loading_value)
  190. 4817 return if ar_parent.nil?
  191. 4817 parent.children.each do |node|
  192. 2887 if node.reflection.collection?
  193. 1803 other = ar_parent.association(node.reflection.name)
  194. 1803 other.loaded!
  195. 1084 elsif ar_parent.association_cached?(node.reflection.name)
  196. 396 model = ar_parent.association(node.reflection.name).target
  197. 396 construct(model, node, row, seen, model_cache, strict_loading_value)
  198. 396 next
  199. end
  200. 2491 key = aliases.column_alias(node, node.primary_key)
  201. 2491 id = row[key]
  202. 2491 if id.nil?
  203. 386 nil_association = ar_parent.association(node.reflection.name)
  204. 386 nil_association.loaded!
  205. 386 next
  206. end
  207. 2105 model = seen[ar_parent][node][id]
  208. 2105 if model
  209. 339 construct(model, node, row, seen, model_cache, strict_loading_value)
  210. else
  211. 1766 model = construct_model(ar_parent, node, row, model_cache, id, strict_loading_value)
  212. 1766 seen[ar_parent][node][id] = model
  213. 1766 construct(model, node, row, seen, model_cache, strict_loading_value)
  214. end
  215. end
  216. end
  217. 3 def construct_model(record, node, row, model_cache, id, strict_loading_value)
  218. 1766 other = record.association(node.reflection.name)
  219. 1766 model = model_cache[node][id] ||=
  220. node.instantiate(row, aliases.column_aliases(node)) do |m|
  221. 1527 m.strict_loading! if strict_loading_value
  222. 1527 other.set_inverse_instance(m)
  223. end
  224. 1766 if node.reflection.collection?
  225. 1204 other.target.push(model)
  226. else
  227. 562 other.target = model
  228. end
  229. 1766 model.readonly! if node.readonly?
  230. 1766 model.strict_loading! if node.strict_loading?
  231. 1766 model
  232. end
  233. end
  234. end
  235. end

lib/active_record/associations/join_dependency/join_association.rb

100.0% lines covered

54 relevant lines. 54 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 require "active_record/associations/join_dependency/join_part"
  3. 3 require "active_support/core_ext/array/extract"
  4. 3 module ActiveRecord
  5. 3 module Associations
  6. 3 class JoinDependency # :nodoc:
  7. 3 class JoinAssociation < JoinPart # :nodoc:
  8. 3 attr_reader :reflection, :tables
  9. 3 attr_accessor :table
  10. 3 def initialize(reflection, children)
  11. 2437 super(reflection.klass, children)
  12. 2437 @reflection = reflection
  13. end
  14. 3 def match?(other)
  15. 72 return true if self == other
  16. 72 super && reflection == other.reflection
  17. end
  18. 3 def join_constraints(foreign_table, foreign_klass, join_type, alias_tracker)
  19. 2528 joins = []
  20. 2528 chain = []
  21. 2528 reflection.chain.each do |reflection|
  22. 3151 table, terminated = yield reflection
  23. 3151 @table ||= table
  24. 3151 if terminated
  25. 12 foreign_table, foreign_klass = table, reflection.klass
  26. 12 break
  27. end
  28. 3139 chain << [reflection, table]
  29. end
  30. # The chain starts with the target table, but we want to end with it here (makes
  31. # more sense in this context), so we reverse
  32. 2528 chain.reverse_each do |reflection, table|
  33. 3139 klass = reflection.klass
  34. 3139 join_scope = reflection.join_scope(table, foreign_table, foreign_klass)
  35. 3139 unless join_scope.references_values.empty?
  36. 141 join_dependency = join_scope.construct_join_dependency(
  37. join_scope.eager_load_values | join_scope.includes_values, Arel::Nodes::OuterJoin
  38. )
  39. 141 join_scope.joins!(join_dependency)
  40. end
  41. 3139 arel = join_scope.arel(alias_tracker.aliases)
  42. 3139 nodes = arel.constraints.first
  43. 3139 if nodes.is_a?(Arel::Nodes::And)
  44. 549 others = nodes.children.extract! do |node|
  45. 2400 !Arel.fetch_attribute(node) { |attr| attr.relation.name == table.name }
  46. end
  47. end
  48. 3139 joins << join_type.new(table, Arel::Nodes::On.new(nodes))
  49. 3139 if others && !others.empty?
  50. 63 joins.concat arel.join_sources
  51. 63 append_constraints(joins.last, others)
  52. end
  53. # The current table in this iteration becomes the foreign table in the next
  54. 3139 foreign_table, foreign_klass = table, klass
  55. end
  56. 2528 joins
  57. end
  58. 3 def readonly?
  59. 1766 return @readonly if defined?(@readonly)
  60. 615 @readonly = reflection.scope && reflection.scope_for(base_klass.unscoped).readonly_value
  61. end
  62. 3 def strict_loading?
  63. 1766 return @strict_loading if defined?(@strict_loading)
  64. 615 @strict_loading = reflection.scope && reflection.scope_for(base_klass.unscoped).strict_loading_value
  65. end
  66. 3 private
  67. 3 def append_constraints(join, constraints)
  68. 63 if join.is_a?(Arel::Nodes::StringJoin)
  69. 6 join_string = Arel::Nodes::And.new(constraints.unshift join.left)
  70. 6 join.left = Arel.sql(base_klass.connection.visitor.compile(join_string))
  71. else
  72. 57 right = join.right
  73. 57 right.expr = Arel::Nodes::And.new(constraints.unshift right.expr)
  74. end
  75. end
  76. end
  77. end
  78. end
  79. end

lib/active_record/associations/join_dependency/join_base.rb

100.0% lines covered

12 relevant lines. 12 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 require "active_record/associations/join_dependency/join_part"
  3. 3 module ActiveRecord
  4. 3 module Associations
  5. 3 class JoinDependency # :nodoc:
  6. 3 class JoinBase < JoinPart # :nodoc:
  7. 3 attr_reader :table
  8. 3 def initialize(base_klass, table, children)
  9. 3991 super(base_klass, children)
  10. 3991 @table = table
  11. end
  12. 3 def match?(other)
  13. 1301 return true if self == other
  14. 1301 super && base_klass == other.base_klass
  15. end
  16. end
  17. end
  18. end
  19. end

lib/active_record/associations/join_dependency/join_part.rb

87.5% lines covered

32 relevant lines. 28 lines covered and 4 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 module ActiveRecord
  3. 3 module Associations
  4. 3 class JoinDependency # :nodoc:
  5. # A JoinPart represents a part of a JoinDependency. It is inherited
  6. # by JoinBase and JoinAssociation. A JoinBase represents the Active Record which
  7. # everything else is being joined onto. A JoinAssociation represents an association which
  8. # is joining to the base. A JoinAssociation may result in more than one actual join
  9. # operations (for example a has_and_belongs_to_many JoinAssociation would result in
  10. # two; one for the join table and one for the target table).
  11. 3 class JoinPart # :nodoc:
  12. 3 include Enumerable
  13. # The Active Record class which this join part is associated 'about'; for a JoinBase
  14. # this is the actual base model, for a JoinAssociation this is the target model of the
  15. # association.
  16. 3 attr_reader :base_klass, :children
  17. 3 delegate :table_name, :column_names, :primary_key, :attribute_types, to: :base_klass
  18. 3 def initialize(base_klass, children)
  19. 6428 @base_klass = base_klass
  20. 6428 @children = children
  21. end
  22. 3 def match?(other)
  23. 1373 self.class == other.class
  24. end
  25. 3 def each(&block)
  26. 3998 yield self
  27. 5767 children.each { |child| child.each(&block) }
  28. end
  29. 3 def each_children(&block)
  30. children.each do |child|
  31. yield self, child
  32. child.each_children(&block)
  33. end
  34. end
  35. # An Arel::Table for the active_record
  36. 3 def table
  37. raise NotImplementedError
  38. end
  39. 3 def extract_record(row, column_names_with_alias)
  40. # This code is performance critical as it is called per row.
  41. # see: https://github.com/rails/rails/pull/12185
  42. 2812 hash = {}
  43. 2812 index = 0
  44. 2812 length = column_names_with_alias.length
  45. 2812 while index < length
  46. 29960 column = column_names_with_alias[index]
  47. 29960 hash[column.name] = row[column.alias]
  48. 29960 index += 1
  49. end
  50. 2812 hash
  51. end
  52. 3 def instantiate(row, aliases, column_types = {}, &block)
  53. 2812 base_klass.instantiate(extract_record(row, aliases), column_types, &block)
  54. end
  55. end
  56. end
  57. end
  58. end

lib/active_record/associations/preloader.rb

100.0% lines covered

64 relevant lines. 64 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 require "active_support/core_ext/enumerable"
  3. 3 module ActiveRecord
  4. 3 module Associations
  5. # Implements the details of eager loading of Active Record associations.
  6. #
  7. # Suppose that you have the following two Active Record models:
  8. #
  9. # class Author < ActiveRecord::Base
  10. # # columns: name, age
  11. # has_many :books
  12. # end
  13. #
  14. # class Book < ActiveRecord::Base
  15. # # columns: title, sales, author_id
  16. # end
  17. #
  18. # When you load an author with all associated books Active Record will make
  19. # multiple queries like this:
  20. #
  21. # Author.includes(:books).where(name: ['bell hooks', 'Homer']).to_a
  22. #
  23. # => SELECT `authors`.* FROM `authors` WHERE `name` IN ('bell hooks', 'Homer')
  24. # => SELECT `books`.* FROM `books` WHERE `author_id` IN (2, 5)
  25. #
  26. # Active Record saves the ids of the records from the first query to use in
  27. # the second. Depending on the number of associations involved there can be
  28. # arbitrarily many SQL queries made.
  29. #
  30. # However, if there is a WHERE clause that spans across tables Active
  31. # Record will fall back to a slightly more resource-intensive single query:
  32. #
  33. # Author.includes(:books).where(books: {title: 'Illiad'}).to_a
  34. # => SELECT `authors`.`id` AS t0_r0, `authors`.`name` AS t0_r1, `authors`.`age` AS t0_r2,
  35. # `books`.`id` AS t1_r0, `books`.`title` AS t1_r1, `books`.`sales` AS t1_r2
  36. # FROM `authors`
  37. # LEFT OUTER JOIN `books` ON `authors`.`id` = `books`.`author_id`
  38. # WHERE `books`.`title` = 'Illiad'
  39. #
  40. # This could result in many rows that contain redundant data and it performs poorly at scale
  41. # and is therefore only used when necessary.
  42. #
  43. 3 class Preloader #:nodoc:
  44. 3 extend ActiveSupport::Autoload
  45. 3 eager_autoload do
  46. 3 autoload :Association, "active_record/associations/preloader/association"
  47. 3 autoload :ThroughAssociation, "active_record/associations/preloader/through_association"
  48. end
  49. # Eager loads the named associations for the given Active Record record(s).
  50. #
  51. # In this description, 'association name' shall refer to the name passed
  52. # to an association creation method. For example, a model that specifies
  53. # <tt>belongs_to :author</tt>, <tt>has_many :buyers</tt> has association
  54. # names +:author+ and +:buyers+.
  55. #
  56. # == Parameters
  57. # +records+ is an array of ActiveRecord::Base. This array needs not be flat,
  58. # i.e. +records+ itself may also contain arrays of records. In any case,
  59. # +preload_associations+ will preload all associations records by
  60. # flattening +records+.
  61. #
  62. # +associations+ specifies one or more associations that you want to
  63. # preload. It may be:
  64. # - a Symbol or a String which specifies a single association name. For
  65. # example, specifying +:books+ allows this method to preload all books
  66. # for an Author.
  67. # - an Array which specifies multiple association names. This array
  68. # is processed recursively. For example, specifying <tt>[:avatar, :books]</tt>
  69. # allows this method to preload an author's avatar as well as all of his
  70. # books.
  71. # - a Hash which specifies multiple association names, as well as
  72. # association names for the to-be-preloaded association objects. For
  73. # example, specifying <tt>{ author: :avatar }</tt> will preload a
  74. # book's author, as well as that author's avatar.
  75. #
  76. # +:associations+ has the same format as the +:include+ option for
  77. # <tt>ActiveRecord::Base.find</tt>. So +associations+ could look like this:
  78. #
  79. # :books
  80. # [ :books, :author ]
  81. # { author: :avatar }
  82. # [ :books, { author: :avatar } ]
  83. 3 def preload(records, associations, preload_scope = nil)
  84. 2554 records = Array.wrap(records).compact
  85. 2554 if records.empty?
  86. 141 []
  87. else
  88. 2413 Array.wrap(associations).flat_map { |association|
  89. 2413 preloaders_on association, records, preload_scope
  90. }
  91. end
  92. end
  93. 3 def initialize(associate_by_default: true)
  94. 1330 @associate_by_default = associate_by_default
  95. end
  96. 3 private
  97. # Loads all the given data into +records+ for the +association+.
  98. 3 def preloaders_on(association, records, scope, polymorphic_parent = false)
  99. 2602 case association
  100. when Hash
  101. 183 preloaders_for_hash(association, records, scope, polymorphic_parent)
  102. when Symbol, String
  103. 2416 preloaders_for_one(association, records, scope, polymorphic_parent)
  104. else
  105. 3 raise ArgumentError, "#{association.inspect} was not recognized for preload"
  106. end
  107. end
  108. 3 def preloaders_for_hash(association, records, scope, polymorphic_parent)
  109. 183 association.flat_map { |parent, child|
  110. 186 grouped_records(parent, records, polymorphic_parent).flat_map do |reflection, reflection_records|
  111. 177 loaders = preloaders_for_reflection(reflection, reflection_records, scope)
  112. 177 recs = loaders.flat_map(&:preloaded_records).uniq
  113. 177 child_polymorphic_parent = reflection && reflection.options[:polymorphic]
  114. 177 loaders.concat Array.wrap(child).flat_map { |assoc|
  115. 189 preloaders_on assoc, recs, scope, child_polymorphic_parent
  116. }
  117. 171 loaders
  118. end
  119. }
  120. end
  121. # Loads all the given data into +records+ for a singular +association+.
  122. #
  123. # Functions by instantiating a preloader class such as Preloader::Association and
  124. # call the +run+ method for each passed in class in the +records+ argument.
  125. #
  126. # Not all records have the same class, so group then preload group on the reflection
  127. # itself so that if various subclass share the same association then we do not split
  128. # them unnecessarily
  129. #
  130. # Additionally, polymorphic belongs_to associations can have multiple associated
  131. # classes, depending on the polymorphic_type field. So we group by the classes as
  132. # well.
  133. 3 def preloaders_for_one(association, records, scope, polymorphic_parent)
  134. grouped_records(association, records, polymorphic_parent)
  135. 2416 .flat_map do |reflection, reflection_records|
  136. 2398 preloaders_for_reflection reflection, reflection_records, scope
  137. end
  138. end
  139. 3 def preloaders_for_reflection(reflection, records, scope)
  140. 207321 records.group_by { |record| record.association(reflection.name).klass }.map do |rhs_klass, rs|
  141. 2605 preloader_for(reflection, rs).new(rhs_klass, rs, reflection, scope, @associate_by_default).run
  142. end
  143. end
  144. 3 def grouped_records(association, records, polymorphic_parent)
  145. 2602 h = {}
  146. 2602 records.each do |record|
  147. 204833 reflection = record.class._reflect_on_association(association)
  148. 204833 next if polymorphic_parent && !reflection || !record.association(association).klass
  149. 204746 (h[reflection] ||= []) << record
  150. end
  151. 2587 h
  152. end
  153. 3 class AlreadyLoaded # :nodoc:
  154. 3 def initialize(klass, owners, reflection, preload_scope, associate_by_default = true)
  155. 141 @owners = owners
  156. 141 @reflection = reflection
  157. end
  158. 3 def run
  159. 141 self
  160. end
  161. 3 def preloaded_records
  162. 42 @preloaded_records ||= records_by_owner.flat_map(&:last)
  163. end
  164. 3 def records_by_owner
  165. 129 @records_by_owner ||= owners.index_with do |owner|
  166. 180 Array(owner.association(reflection.name).target)
  167. end
  168. end
  169. 3 private
  170. 3 attr_reader :owners, :reflection
  171. end
  172. # Returns a class containing the logic needed to load preload the data
  173. # and attach it to a relation. The class returned implements a `run` method
  174. # that accepts a preloader.
  175. 3 def preloader_for(reflection, owners)
  176. 5357 if owners.all? { |o| o.association(reflection.name).loaded? }
  177. 141 return AlreadyLoaded
  178. end
  179. 2464 reflection.check_preloadable!
  180. 2449 if reflection.options[:through]
  181. 522 ThroughAssociation
  182. else
  183. 1927 Association
  184. end
  185. end
  186. end
  187. end
  188. end

lib/active_record/associations/preloader/association.rb

100.0% lines covered

82 relevant lines. 82 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 module ActiveRecord
  3. 3 module Associations
  4. 3 class Preloader
  5. 3 class Association #:nodoc:
  6. 3 def initialize(klass, owners, reflection, preload_scope, associate_by_default = true)
  7. 2449 @klass = klass
  8. 2449 @owners = owners.uniq(&:__id__)
  9. 2449 @reflection = reflection
  10. 2449 @preload_scope = preload_scope
  11. 2449 @associate = associate_by_default || !preload_scope || preload_scope.empty_scope?
  12. 2449 @model = owners.first && owners.first.class
  13. end
  14. 3 def run
  15. 2449 records = records_by_owner
  16. owners.each do |owner|
  17. 203933 associate_records_to_owner(owner, records[owner] || [])
  18. 2449 end if @associate
  19. 2449 self
  20. end
  21. 3 def records_by_owner
  22. 2800 load_records unless defined?(@records_by_owner)
  23. 2800 @records_by_owner
  24. end
  25. 3 def preloaded_records
  26. 723 load_records unless defined?(@preloaded_records)
  27. 723 @preloaded_records
  28. end
  29. 3 private
  30. 3 attr_reader :owners, :reflection, :preload_scope, :model, :klass
  31. 3 def load_records
  32. # owners can be duplicated when a relation has a collection association join
  33. # #compare_by_identity makes such owners different hash keys
  34. 1927 @records_by_owner = {}.compare_by_identity
  35. 1927 raw_records = owner_keys.empty? ? [] : records_for(owner_keys)
  36. 1927 @preloaded_records = raw_records.select do |record|
  37. 6738 assignments = false
  38. 6738 owners_by_key[convert_key(record[association_key_name])].each do |owner|
  39. 7916 entries = (@records_by_owner[owner] ||= [])
  40. 7916 if reflection.collection? || entries.empty?
  41. 7100 entries << record
  42. 7100 assignments = true
  43. end
  44. end
  45. 6738 assignments
  46. end
  47. end
  48. # The name of the key on the associated records
  49. 3 def association_key_name
  50. 17315 reflection.join_primary_key(klass)
  51. end
  52. # The name of the key on the model which declares the association
  53. 3 def owner_key_name
  54. 204810 reflection.join_foreign_key
  55. end
  56. 3 def associate_records_to_owner(owner, records)
  57. 203933 association = owner.association(reflection.name)
  58. 203933 if reflection.collection?
  59. 3934 association.target = records
  60. else
  61. 199999 association.target = records.first
  62. end
  63. end
  64. 3 def owner_keys
  65. 3839 @owner_keys ||= owners_by_key.keys
  66. end
  67. 3 def owners_by_key
  68. 15403 @owners_by_key ||= owners.each_with_object({}) do |owner, result|
  69. 202883 key = convert_key(owner[owner_key_name])
  70. 202883 (result[key] ||= []) << owner if key
  71. end
  72. end
  73. 3 def key_conversion_required?
  74. 216359 unless defined?(@key_conversion_required)
  75. 1927 @key_conversion_required = (association_key_type != owner_key_type)
  76. end
  77. 216359 @key_conversion_required
  78. end
  79. 3 def convert_key(key)
  80. 216359 if key_conversion_required?
  81. 18 key.to_s
  82. else
  83. 216341 key
  84. end
  85. end
  86. 3 def association_key_type
  87. 1927 @klass.type_for_attribute(association_key_name).type
  88. end
  89. 3 def owner_key_type
  90. 1927 @model.type_for_attribute(owner_key_name).type
  91. end
  92. 3 def records_for(ids)
  93. 1912 scope.where(association_key_name => ids).load do |record|
  94. # Processing only the first owner
  95. # because the record is modified but not an owner
  96. 6738 owner = owners_by_key[convert_key(record[association_key_name])].first
  97. 6738 association = owner.association(reflection.name)
  98. 6738 association.set_inverse_instance(record)
  99. end
  100. end
  101. 3 def scope
  102. 5476 @scope ||= build_scope
  103. end
  104. 3 def reflection_scope
  105. 3814 @reflection_scope ||= begin
  106. 2434 reflection.join_scopes(klass.arel_table, klass.predicate_builder, klass).inject(&:merge!) || klass.unscoped
  107. end
  108. end
  109. 3 def build_scope
  110. 2434 scope = klass.scope_for_association
  111. 2434 if reflection.type && !reflection.through_reflection?
  112. 93 scope.where!(reflection.type => model.polymorphic_name)
  113. end
  114. 2434 scope.merge!(reflection_scope) unless reflection_scope.empty_scope?
  115. 2434 if preload_scope && !preload_scope.empty_scope?
  116. 219 scope.merge!(preload_scope)
  117. end
  118. 2434 if preload_scope && preload_scope.strict_loading_value
  119. 3 scope.strict_loading
  120. else
  121. 2431 scope
  122. end
  123. end
  124. end
  125. end
  126. end
  127. end

lib/active_record/associations/preloader/through_association.rb

98.41% lines covered

63 relevant lines. 62 lines covered and 1 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 module ActiveRecord
  3. 3 module Associations
  4. 3 class Preloader
  5. 3 class ThroughAssociation < Association # :nodoc:
  6. 3 PRELOADER = ActiveRecord::Associations::Preloader.new(associate_by_default: false)
  7. 3 def initialize(*)
  8. 522 super
  9. 522 @already_loaded = owners.first.association(through_reflection.name).loaded?
  10. end
  11. 3 def preloaded_records
  12. 132 @preloaded_records ||= source_preloaders.flat_map(&:preloaded_records)
  13. end
  14. 3 def records_by_owner
  15. 582 return @records_by_owner if defined?(@records_by_owner)
  16. 522 source_records_by_owner = source_preloaders.map(&:records_by_owner).reduce(:merge)
  17. 522 through_records_by_owner = through_preloaders.map(&:records_by_owner).reduce(:merge)
  18. 522 @records_by_owner = owners.each_with_object({}) do |owner, result|
  19. 1521 through_records = through_records_by_owner[owner] || []
  20. 1521 if @already_loaded
  21. 42 if source_type = reflection.options[:source_type]
  22. 6 through_records = through_records.select do |record|
  23. 12 record[reflection.foreign_type] == source_type
  24. end
  25. end
  26. end
  27. 1521 records = through_records.flat_map do |record|
  28. 1790 source_records_by_owner[record]
  29. end
  30. 1521 records.compact!
  31. 1689 records.sort_by! { |rhs| preload_index[rhs] } if scope.order_values.any?
  32. 1521 records.uniq! if scope.distinct_value
  33. 1521 result[owner] = records
  34. end
  35. end
  36. 3 private
  37. 3 def source_preloaders
  38. 654 @source_preloaders ||= PRELOADER.preload(middle_records, source_reflection.name, scope)
  39. end
  40. 3 def middle_records
  41. 522 through_preloaders.flat_map(&:preloaded_records)
  42. end
  43. 3 def through_preloaders
  44. 1044 @through_preloaders ||= PRELOADER.preload(owners, through_reflection.name, through_scope)
  45. end
  46. 3 def through_reflection
  47. 1566 reflection.through_reflection
  48. end
  49. 3 def source_reflection
  50. 636 reflection.source_reflection
  51. end
  52. 3 def preload_index
  53. 168 @preload_index ||= preloaded_records.each_with_object({}).with_index do |(record, result), index|
  54. 144 result[record] = index
  55. end
  56. end
  57. 3 def through_scope
  58. 522 scope = through_reflection.klass.unscoped
  59. 522 options = reflection.options
  60. 522 values = reflection_scope.values
  61. 522 if annotations = values[:annotate]
  62. 3 scope.annotate!(*annotations)
  63. end
  64. 522 if options[:source_type]
  65. 33 scope.where! reflection.foreign_type => options[:source_type]
  66. 489 elsif !reflection_scope.where_clause.empty?
  67. 75 scope.where_clause = reflection_scope.where_clause
  68. 75 if includes = values[:includes]
  69. 12 scope.includes!(source_reflection.name => includes)
  70. else
  71. 63 scope.includes!(source_reflection.name)
  72. end
  73. 75 if values[:references] && !values[:references].empty?
  74. 39 scope.references_values |= values[:references]
  75. else
  76. 36 scope.references!(source_reflection.table_name)
  77. end
  78. 75 if joins = values[:joins]
  79. scope.joins!(source_reflection.name => joins)
  80. end
  81. 75 if left_outer_joins = values[:left_outer_joins]
  82. 3 scope.left_outer_joins!(source_reflection.name => left_outer_joins)
  83. end
  84. 75 if scope.eager_loading? && order_values = values[:order]
  85. 6 scope = scope.order(order_values)
  86. end
  87. end
  88. 522 scope
  89. end
  90. end
  91. end
  92. end
  93. end

lib/active_record/associations/singular_association.rb

96.77% lines covered

31 relevant lines. 30 lines covered and 1 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 module ActiveRecord
  3. 3 module Associations
  4. 3 class SingularAssociation < Association #:nodoc:
  5. # Implements the reader method, e.g. foo.bar for Foo.has_one :bar
  6. 3 def reader
  7. 8608 if !loaded? || stale_target?
  8. 1976 reload
  9. end
  10. 8590 target
  11. end
  12. # Implements the writer method, e.g. foo.bar= for Foo.belongs_to :bar
  13. 3 def writer(record)
  14. 2251 replace(record)
  15. end
  16. 3 def build(attributes = nil, &block)
  17. 297 record = build_record(attributes, &block)
  18. 282 set_new_record(record)
  19. 282 record
  20. end
  21. # Implements the reload reader method, e.g. foo.reload_bar for
  22. # Foo.has_one :bar
  23. 3 def force_reload_reader
  24. 15 reload(true)
  25. 15 target
  26. end
  27. 3 private
  28. 3 def scope_for_create
  29. 753 super.except!(klass.primary_key)
  30. end
  31. 3 def find_target
  32. 2125 super.first
  33. end
  34. 3 def replace(record)
  35. raise NotImplementedError, "Subclasses must implement a replace(record) method"
  36. end
  37. 3 def set_new_record(record)
  38. 246 replace(record)
  39. end
  40. 3 def _create_record(attributes, raise_error = false, &block)
  41. 465 record = build_record(attributes, &block)
  42. 462 saved = record.save
  43. 462 set_new_record(record)
  44. 462 raise RecordInvalid.new(record) if !saved && raise_error
  45. 456 record
  46. end
  47. end
  48. end
  49. end

lib/active_record/associations/through_association.rb

98.04% lines covered

51 relevant lines. 50 lines covered and 1 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 module ActiveRecord
  3. 3 module Associations
  4. # = Active Record Through Association
  5. 3 module ThroughAssociation #:nodoc:
  6. 3 delegate :source_reflection, to: :reflection
  7. 3 private
  8. 3 def through_reflection
  9. 10526 @through_reflection ||= begin
  10. 3557 refl = reflection.through_reflection
  11. 3557 while refl.through_reflection?
  12. 141 refl = refl.through_reflection
  13. end
  14. 3557 refl
  15. end
  16. end
  17. 3 def through_association
  18. 6896 @through_association ||= owner.association(through_reflection.name)
  19. end
  20. # We merge in these scopes for two reasons:
  21. #
  22. # 1. To get the default_scope conditions for any of the other reflections in the chain
  23. # 2. To get the type conditions for any STI models in the chain
  24. 3 def target_scope
  25. 4692 scope = super
  26. 4692 reflection.chain.drop(1).each do |reflection|
  27. 4899 relation = reflection.klass.scope_for_association
  28. 4899 scope.merge!(
  29. relation.except(:select, :create_with, :includes, :preload, :eager_load, :joins, :left_outer_joins)
  30. )
  31. end
  32. 4692 scope
  33. end
  34. # Construct attributes for :through pointing to owner and associate. This is used by the
  35. # methods which create and delete records on the association.
  36. #
  37. # We only support indirectly modifying through associations which have a belongs_to source.
  38. # This is the "has_many :tags, through: :taggings" situation, where the join model
  39. # typically has a belongs_to on both side. In other words, associations which could also
  40. # be represented as has_and_belongs_to_many associations.
  41. #
  42. # We do not support creating/deleting records on the association where the source has
  43. # some other type, because this opens up a whole can of worms, and in basically any
  44. # situation it is more natural for the user to just create or modify their join records
  45. # directly as required.
  46. 3 def construct_join_attributes(*records)
  47. 871 ensure_mutable
  48. 856 association_primary_key = source_reflection.association_primary_key(reflection.klass)
  49. 856 if association_primary_key == reflection.klass.primary_key && !options[:source_type]
  50. 808 join_attributes = { source_reflection.name => records }
  51. else
  52. 48 join_attributes = {
  53. source_reflection.foreign_key => records.map(&association_primary_key.to_sym)
  54. }
  55. end
  56. 856 if options[:source_type]
  57. 39 join_attributes[source_reflection.foreign_type] = [ options[:source_type] ]
  58. end
  59. 856 if records.count == 1
  60. 751 join_attributes.transform_values!(&:first)
  61. else
  62. 105 join_attributes
  63. end
  64. end
  65. # Note: this does not capture all cases, for example it would be crazy to try to
  66. # properly support stale-checking for nested associations.
  67. 3 def stale_state
  68. 6341 if through_reflection.belongs_to?
  69. 240 owner[through_reflection.foreign_key] && owner[through_reflection.foreign_key].to_s
  70. end
  71. end
  72. 3 def foreign_key_present?
  73. 387 through_reflection.belongs_to? && !owner[through_reflection.foreign_key].nil?
  74. end
  75. 3 def ensure_mutable
  76. 1955 unless source_reflection.belongs_to?
  77. 21 if reflection.has_one?
  78. raise HasOneThroughCantAssociateThroughHasOneOrManyReflection.new(owner, reflection)
  79. else
  80. 21 raise HasManyThroughCantAssociateThroughHasOneOrManyReflection.new(owner, reflection)
  81. end
  82. end
  83. end
  84. 3 def ensure_not_nested
  85. 2573 if reflection.nested?
  86. 24 if reflection.has_one?
  87. 3 raise HasOneThroughNestedAssociationsAreReadonly.new(owner, reflection)
  88. else
  89. 21 raise HasManyThroughNestedAssociationsAreReadonly.new(owner, reflection)
  90. end
  91. end
  92. end
  93. 3 def build_record(attributes)
  94. 744 inverse = source_reflection.inverse_of
  95. 744 target = through_association.target
  96. 744 if inverse && target && !target.is_a?(Array)
  97. 3 attributes[inverse.foreign_key] = target.id
  98. end
  99. 744 super
  100. end
  101. end
  102. end
  103. end

lib/active_record/attribute_assignment.rb

100.0% lines covered

43 relevant lines. 43 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 require "active_model/forbidden_attributes_protection"
  3. 3 module ActiveRecord
  4. 3 module AttributeAssignment
  5. 3 include ActiveModel::AttributeAssignment
  6. 3 private
  7. 3 def _assign_attributes(attributes)
  8. 16818 multi_parameter_attributes = nested_parameter_attributes = nil
  9. 16818 attributes.each do |k, v|
  10. 22064 key = k.to_s
  11. 22064 if key.include?("(")
  12. 543 (multi_parameter_attributes ||= {})[key] = v
  13. 21521 elsif v.is_a?(Hash)
  14. 457 (nested_parameter_attributes ||= {})[key] = v
  15. else
  16. 21064 _assign_attribute(key, v)
  17. end
  18. end
  19. 16770 assign_nested_parameter_attributes(nested_parameter_attributes) if nested_parameter_attributes
  20. 16755 assign_multiparameter_attributes(multi_parameter_attributes) if multi_parameter_attributes
  21. end
  22. # Assign any deferred nested attributes after the base attributes have been set.
  23. 3 def assign_nested_parameter_attributes(pairs)
  24. 914 pairs.each { |k, v| _assign_attribute(k, v) }
  25. end
  26. # Instantiates objects for all attribute classes that needs more than one constructor parameter. This is done
  27. # by calling new on the column type or aggregation type (through composed_of) object with these parameters.
  28. # So having the pairs written_on(1) = "2004", written_on(2) = "6", written_on(3) = "24", will instantiate
  29. # written_on (a date type) with Date.new("2004", "6", "24"). You can also specify a typecast character in the
  30. # parentheses to have the parameters typecasted before they're used in the constructor. Use i for Integer and
  31. # f for Float. If all the values for a given attribute are empty, the attribute will be set to +nil+.
  32. 3 def assign_multiparameter_attributes(pairs)
  33. 138 execute_callstack_for_multiparameter_attributes(
  34. extract_callstack_for_multiparameter_attributes(pairs)
  35. )
  36. end
  37. 3 def execute_callstack_for_multiparameter_attributes(callstack)
  38. 138 errors = []
  39. 138 callstack.each do |name, values_with_empty_parameters|
  40. 138 if values_with_empty_parameters.each_value.all?(&:nil?)
  41. 3 values = nil
  42. else
  43. 135 values = values_with_empty_parameters
  44. end
  45. 138 send("#{name}=", values)
  46. rescue => ex
  47. 24 errors << AttributeAssignmentError.new("error on assignment #{values_with_empty_parameters.values.inspect} to #{name} (#{ex.message})", ex, name)
  48. end
  49. 138 unless errors.empty?
  50. 24 error_descriptions = errors.map(&:message).join(",")
  51. 24 raise MultiparameterAssignmentErrors.new(errors), "#{errors.size} error(s) on assignment of multiparameter attributes [#{error_descriptions}]"
  52. end
  53. end
  54. 3 def extract_callstack_for_multiparameter_attributes(pairs)
  55. 138 attributes = {}
  56. 138 pairs.each do |(multiparameter_name, value)|
  57. 543 attribute_name = multiparameter_name.split("(").first
  58. 543 attributes[attribute_name] ||= {}
  59. 543 parameter_value = value.empty? ? nil : type_cast_attribute_value(multiparameter_name, value)
  60. 543 attributes[attribute_name][find_parameter_position(multiparameter_name)] ||= parameter_value
  61. end
  62. 138 attributes
  63. end
  64. 3 def type_cast_attribute_value(multiparameter_name, value)
  65. 453 multiparameter_name =~ /\([0-9]*([if])\)/ ? value.send("to_" + $1) : value
  66. end
  67. 3 def find_parameter_position(multiparameter_name)
  68. 543 multiparameter_name.scan(/\(([0-9]*).*\)/).first.first.to_i
  69. end
  70. end
  71. end

lib/active_record/attribute_methods.rb

100.0% lines covered

133 relevant lines. 133 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 require "mutex_m"
  3. 3 require "active_support/core_ext/enumerable"
  4. 3 module ActiveRecord
  5. # = Active Record Attribute Methods
  6. 3 module AttributeMethods
  7. 3 extend ActiveSupport::Concern
  8. 3 include ActiveModel::AttributeMethods
  9. 3 included do
  10. 9 initialize_generated_modules
  11. 9 include Read
  12. 9 include Write
  13. 9 include BeforeTypeCast
  14. 9 include Query
  15. 9 include PrimaryKey
  16. 9 include TimeZoneConversion
  17. 9 include Dirty
  18. 9 include Serialization
  19. end
  20. 3 RESTRICTED_CLASS_METHODS = %w(private public protected allocate new name parent superclass)
  21. 3 class GeneratedAttributeMethods < Module #:nodoc:
  22. 3 include Mutex_m
  23. end
  24. 3 class << self
  25. 3 def dangerous_attribute_methods # :nodoc:
  26. 428652 @dangerous_attribute_methods ||= (
  27. Base.instance_methods +
  28. Base.private_instance_methods -
  29. 3 Base.superclass.instance_methods -
  30. Base.superclass.private_instance_methods
  31. 1026 ).map { |m| -m.to_s }.to_set.freeze
  32. end
  33. end
  34. 3 module ClassMethods
  35. 3 def inherited(child_class) #:nodoc:
  36. 2940 child_class.initialize_generated_modules
  37. 2940 super
  38. end
  39. 3 def initialize_generated_modules # :nodoc:
  40. 2949 @generated_attribute_methods = const_set(:GeneratedAttributeMethods, GeneratedAttributeMethods.new)
  41. 2949 private_constant :GeneratedAttributeMethods
  42. 2949 @attribute_methods_generated = false
  43. 2949 include @generated_attribute_methods
  44. 2949 super
  45. end
  46. # Generates all the attribute related methods for columns in the database
  47. # accessors, mutators and query methods.
  48. 3 def define_attribute_methods # :nodoc:
  49. 262466 return false if @attribute_methods_generated
  50. # Use a mutex; we don't want two threads simultaneously trying to define
  51. # attribute methods.
  52. 2752 generated_attribute_methods.synchronize do
  53. 2752 return false if @attribute_methods_generated
  54. 2749 superclass.define_attribute_methods unless base_class?
  55. 2749 super(attribute_names)
  56. 2749 @attribute_methods_generated = true
  57. end
  58. end
  59. 3 def undefine_attribute_methods # :nodoc:
  60. 3779 generated_attribute_methods.synchronize do
  61. 3779 super if defined?(@attribute_methods_generated) && @attribute_methods_generated
  62. 3779 @attribute_methods_generated = false
  63. end
  64. end
  65. # Raises an ActiveRecord::DangerousAttributeError exception when an
  66. # \Active \Record method is defined in the model, otherwise +false+.
  67. #
  68. # class Person < ActiveRecord::Base
  69. # def save
  70. # 'already defined by Active Record'
  71. # end
  72. # end
  73. #
  74. # Person.instance_method_already_implemented?(:save)
  75. # # => ActiveRecord::DangerousAttributeError: save is defined by Active Record. Check to make sure that you don't have an attribute or method with the same name.
  76. #
  77. # Person.instance_method_already_implemented?(:name)
  78. # # => false
  79. 3 def instance_method_already_implemented?(method_name)
  80. 424449 if dangerous_attribute_method?(method_name)
  81. 6 raise DangerousAttributeError, "#{method_name} is defined by Active Record. Check to make sure that you don't have an attribute or method with the same name."
  82. end
  83. 424443 if superclass == Base
  84. 295900 super
  85. else
  86. # If ThisClass < ... < SomeSuperClass < ... < Base and SomeSuperClass
  87. # defines its own attribute method, then we don't want to overwrite that.
  88. 128543 defined = method_defined_within?(method_name, superclass, Base) &&
  89. ! superclass.instance_method(method_name).owner.is_a?(GeneratedAttributeMethods)
  90. 128543 defined || super
  91. end
  92. end
  93. # A method name is 'dangerous' if it is already (re)defined by Active Record, but
  94. # not by any ancestors. (So 'puts' is not dangerous but 'save' is.)
  95. 3 def dangerous_attribute_method?(name) # :nodoc:
  96. 428652 ::ActiveRecord::AttributeMethods.dangerous_attribute_methods.include?(name.to_s)
  97. end
  98. 3 def method_defined_within?(name, klass, superklass = klass.superclass) # :nodoc:
  99. 130673 if klass.method_defined?(name) || klass.private_method_defined?(name)
  100. 121962 if superklass.method_defined?(name) || superklass.private_method_defined?(name)
  101. 3633 klass.instance_method(name).owner != superklass.instance_method(name).owner
  102. else
  103. 118329 true
  104. end
  105. else
  106. 8711 false
  107. end
  108. end
  109. # A class method is 'dangerous' if it is already (re)defined by Active Record, but
  110. # not by any ancestors. (So 'puts' is not dangerous but 'new' is.)
  111. 3 def dangerous_class_method?(method_name)
  112. 1422 return true if RESTRICTED_CLASS_METHODS.include?(method_name.to_s)
  113. 1359 if Base.respond_to?(method_name, true)
  114. 33 if Object.respond_to?(method_name, true)
  115. 12 Base.method(method_name).owner != Object.method(method_name).owner
  116. else
  117. 21 true
  118. end
  119. else
  120. 1326 false
  121. end
  122. end
  123. # Returns +true+ if +attribute+ is an attribute method and table exists,
  124. # +false+ otherwise.
  125. #
  126. # class Person < ActiveRecord::Base
  127. # end
  128. #
  129. # Person.attribute_method?('name') # => true
  130. # Person.attribute_method?(:age=) # => true
  131. # Person.attribute_method?(:nothing) # => false
  132. 3 def attribute_method?(attribute)
  133. 18 super || (table_exists? && column_names.include?(attribute.to_s.delete_suffix("=")))
  134. end
  135. # Returns an array of column names as strings if it's not an abstract class and
  136. # table exists. Otherwise it returns an empty array.
  137. #
  138. # class Person < ActiveRecord::Base
  139. # end
  140. #
  141. # Person.attribute_names
  142. # # => ["id", "created_at", "updated_at", "name", "age"]
  143. 3 def attribute_names
  144. 3094 @attribute_names ||= if !abstract_class? && table_exists?
  145. 2719 attribute_types.keys
  146. else
  147. 15 []
  148. end.freeze
  149. end
  150. # Returns true if the given attribute exists, otherwise false.
  151. #
  152. # class Person < ActiveRecord::Base
  153. # alias_attribute :new_name, :name
  154. # end
  155. #
  156. # Person.has_attribute?('name') # => true
  157. # Person.has_attribute?('new_name') # => true
  158. # Person.has_attribute?(:age) # => true
  159. # Person.has_attribute?(:nothing) # => false
  160. 3 def has_attribute?(attr_name)
  161. 352 attr_name = attr_name.to_s
  162. 352 attr_name = attribute_aliases[attr_name] || attr_name
  163. 352 attribute_types.key?(attr_name)
  164. end
  165. 3 def _has_attribute?(attr_name) # :nodoc:
  166. 35542 attribute_types.key?(attr_name)
  167. end
  168. end
  169. # A Person object with a name attribute can ask <tt>person.respond_to?(:name)</tt>,
  170. # <tt>person.respond_to?(:name=)</tt>, and <tt>person.respond_to?(:name?)</tt>
  171. # which will all return +true+. It also defines the attribute methods if they have
  172. # not been generated.
  173. #
  174. # class Person < ActiveRecord::Base
  175. # end
  176. #
  177. # person = Person.new
  178. # person.respond_to?(:name) # => true
  179. # person.respond_to?(:name=) # => true
  180. # person.respond_to?(:name?) # => true
  181. # person.respond_to?('age') # => true
  182. # person.respond_to?('age=') # => true
  183. # person.respond_to?('age?') # => true
  184. # person.respond_to?(:nothing) # => false
  185. 3 def respond_to?(name, include_private = false)
  186. 27515 return false unless super
  187. # If the result is true then check for the select case.
  188. # For queries selecting a subset of columns, return false for unselected columns.
  189. # We check defined?(@attributes) not to issue warnings if called on objects that
  190. # have been allocated but not yet initialized.
  191. 24730 if defined?(@attributes)
  192. 24662 if name = self.class.symbol_column_to_string(name.to_sym)
  193. 60 return _has_attribute?(name)
  194. end
  195. end
  196. 24670 true
  197. end
  198. # Returns +true+ if the given attribute is in the attributes hash, otherwise +false+.
  199. #
  200. # class Person < ActiveRecord::Base
  201. # alias_attribute :new_name, :name
  202. # end
  203. #
  204. # person = Person.new
  205. # person.has_attribute?(:name) # => true
  206. # person.has_attribute?(:new_name) # => true
  207. # person.has_attribute?('age') # => true
  208. # person.has_attribute?(:nothing) # => false
  209. 3 def has_attribute?(attr_name)
  210. 146 attr_name = attr_name.to_s
  211. 146 attr_name = self.class.attribute_aliases[attr_name] || attr_name
  212. 146 @attributes.key?(attr_name)
  213. end
  214. 3 def _has_attribute?(attr_name) # :nodoc:
  215. 20662 @attributes.key?(attr_name)
  216. end
  217. # Returns an array of names for the attributes available on this object.
  218. #
  219. # class Person < ActiveRecord::Base
  220. # end
  221. #
  222. # person = Person.new
  223. # person.attribute_names
  224. # # => ["id", "created_at", "updated_at", "name", "age"]
  225. 3 def attribute_names
  226. 1685 @attributes.keys
  227. end
  228. # Returns a hash of all the attributes with their names as keys and the values of the attributes as values.
  229. #
  230. # class Person < ActiveRecord::Base
  231. # end
  232. #
  233. # person = Person.create(name: 'Francesco', age: 22)
  234. # person.attributes
  235. # # => {"id"=>3, "created_at"=>Sun, 21 Oct 2012 04:53:04, "updated_at"=>Sun, 21 Oct 2012 04:53:04, "name"=>"Francesco", "age"=>22}
  236. 3 def attributes
  237. 816 @attributes.to_hash
  238. end
  239. # Returns an <tt>#inspect</tt>-like string for the value of the
  240. # attribute +attr_name+. String attributes are truncated up to 50
  241. # characters, Date and Time attributes are returned in the
  242. # <tt>:db</tt> format. Other attributes return the value of
  243. # <tt>#inspect</tt> without modification.
  244. #
  245. # person = Person.create!(name: 'David Heinemeier Hansson ' * 3)
  246. #
  247. # person.attribute_for_inspect(:name)
  248. # # => "\"David Heinemeier Hansson David Heinemeier Hansson ...\""
  249. #
  250. # person.attribute_for_inspect(:created_at)
  251. # # => "\"2012-10-22 00:15:07\""
  252. #
  253. # person.attribute_for_inspect(:tag_ids)
  254. # # => "[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]"
  255. 3 def attribute_for_inspect(attr_name)
  256. 20 attr_name = attr_name.to_s
  257. 20 attr_name = self.class.attribute_aliases[attr_name] || attr_name
  258. 20 value = _read_attribute(attr_name)
  259. 20 format_for_inspect(value)
  260. end
  261. # Returns +true+ if the specified +attribute+ has been set by the user or by a
  262. # database load and is neither +nil+ nor <tt>empty?</tt> (the latter only applies
  263. # to objects that respond to <tt>empty?</tt>, most notably Strings). Otherwise, +false+.
  264. # Note that it always returns +true+ with boolean attributes.
  265. #
  266. # class Task < ActiveRecord::Base
  267. # end
  268. #
  269. # task = Task.new(title: '', is_done: false)
  270. # task.attribute_present?(:title) # => false
  271. # task.attribute_present?(:is_done) # => true
  272. # task.title = 'Buy milk'
  273. # task.is_done = true
  274. # task.attribute_present?(:title) # => true
  275. # task.attribute_present?(:is_done) # => true
  276. 3 def attribute_present?(attr_name)
  277. 2042 attr_name = attr_name.to_s
  278. 2042 attr_name = self.class.attribute_aliases[attr_name] || attr_name
  279. 2042 value = _read_attribute(attr_name)
  280. 2042 !value.nil? && !(value.respond_to?(:empty?) && value.empty?)
  281. end
  282. # Returns the value of the attribute identified by <tt>attr_name</tt> after it has been typecast (for example,
  283. # "2004-12-12" in a date column is cast to a date object, like Date.new(2004, 12, 12)). It raises
  284. # <tt>ActiveModel::MissingAttributeError</tt> if the identified attribute is missing.
  285. #
  286. # Note: +:id+ is always present.
  287. #
  288. # class Person < ActiveRecord::Base
  289. # belongs_to :organization
  290. # end
  291. #
  292. # person = Person.new(name: 'Francesco', age: '22')
  293. # person[:name] # => "Francesco"
  294. # person[:age] # => 22
  295. #
  296. # person = Person.select('id').first
  297. # person[:name] # => ActiveModel::MissingAttributeError: missing attribute: name
  298. # person[:organization_id] # => ActiveModel::MissingAttributeError: missing attribute: organization_id
  299. 3 def [](attr_name)
  300. 246684 read_attribute(attr_name) { |n| missing_attribute(n, caller) }
  301. end
  302. # Updates the attribute identified by <tt>attr_name</tt> with the specified +value+.
  303. # (Alias for the protected #write_attribute method).
  304. #
  305. # class Person < ActiveRecord::Base
  306. # end
  307. #
  308. # person = Person.new
  309. # person[:age] = '22'
  310. # person[:age] # => 22
  311. # person[:age].class # => Integer
  312. 3 def []=(attr_name, value)
  313. 21079 write_attribute(attr_name, value)
  314. end
  315. # Returns the name of all database fields which have been read from this
  316. # model. This can be useful in development mode to determine which fields
  317. # need to be selected. For performance critical pages, selecting only the
  318. # required fields can be an easy performance win (assuming you aren't using
  319. # all of the fields on the model).
  320. #
  321. # For example:
  322. #
  323. # class PostsController < ActionController::Base
  324. # after_action :print_accessed_fields, only: :index
  325. #
  326. # def index
  327. # @posts = Post.all
  328. # end
  329. #
  330. # private
  331. #
  332. # def print_accessed_fields
  333. # p @posts.first.accessed_fields
  334. # end
  335. # end
  336. #
  337. # Which allows you to quickly change your code to:
  338. #
  339. # class PostsController < ActionController::Base
  340. # def index
  341. # @posts = Post.select(:id, :title, :author_id, :updated_at)
  342. # end
  343. # end
  344. 3 def accessed_fields
  345. 6 @attributes.accessed
  346. end
  347. 3 private
  348. 3 def attribute_method?(attr_name)
  349. # We check defined? because Syck calls respond_to? before actually calling initialize.
  350. 812 defined?(@attributes) && @attributes.key?(attr_name)
  351. end
  352. 3 def attributes_with_values(attribute_names)
  353. 14836 attribute_names.index_with do |name|
  354. 35096 _read_attribute(name)
  355. end
  356. end
  357. # Filters the primary keys and readonly attributes from the attribute names.
  358. 3 def attributes_for_update(attribute_names)
  359. 3413 attribute_names &= self.class.column_names
  360. 3413 attribute_names.delete_if do |name|
  361. 3137 self.class.readonly_attribute?(name)
  362. end
  363. end
  364. # Filters out the primary keys, from the attribute names, when the primary
  365. # key is to be generated (e.g. the id attribute has no value).
  366. 3 def attributes_for_create(attribute_names)
  367. 12437 attribute_names &= self.class.column_names
  368. 12437 attribute_names.delete_if do |name|
  369. 31064 pk_attribute?(name) && id.nil?
  370. end
  371. end
  372. 3 def format_for_inspect(value)
  373. 2241 if value.is_a?(String) && value.length > 50
  374. 6 "#{value[0, 50]}...".inspect
  375. 2235 elsif value.is_a?(Date) || value.is_a?(Time)
  376. 96 %("#{value.to_s(:inspect)}")
  377. else
  378. 2139 value.inspect
  379. end
  380. end
  381. 3 def pk_attribute?(name)
  382. 31064 name == @primary_key
  383. end
  384. end
  385. end

lib/active_record/attribute_methods/before_type_cast.rb

100.0% lines covered

16 relevant lines. 16 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 module ActiveRecord
  3. 3 module AttributeMethods
  4. # = Active Record Attribute Methods Before Type Cast
  5. #
  6. # ActiveRecord::AttributeMethods::BeforeTypeCast provides a way to
  7. # read the value of the attributes before typecasting and deserialization.
  8. #
  9. # class Task < ActiveRecord::Base
  10. # end
  11. #
  12. # task = Task.new(id: '1', completed_on: '2012-10-21')
  13. # task.id # => 1
  14. # task.completed_on # => Sun, 21 Oct 2012
  15. #
  16. # task.attributes_before_type_cast
  17. # # => {"id"=>"1", "completed_on"=>"2012-10-21", ... }
  18. # task.read_attribute_before_type_cast('id') # => "1"
  19. # task.read_attribute_before_type_cast('completed_on') # => "2012-10-21"
  20. #
  21. # In addition to #read_attribute_before_type_cast and #attributes_before_type_cast,
  22. # it declares a method for all attributes with the <tt>*_before_type_cast</tt>
  23. # suffix.
  24. #
  25. # task.id_before_type_cast # => "1"
  26. # task.completed_on_before_type_cast # => "2012-10-21"
  27. 3 module BeforeTypeCast
  28. 3 extend ActiveSupport::Concern
  29. 3 included do
  30. 9 attribute_method_suffix "_before_type_cast"
  31. 9 attribute_method_suffix "_came_from_user?"
  32. end
  33. # Returns the value of the attribute identified by +attr_name+ before
  34. # typecasting and deserialization.
  35. #
  36. # class Task < ActiveRecord::Base
  37. # end
  38. #
  39. # task = Task.new(id: '1', completed_on: '2012-10-21')
  40. # task.read_attribute('id') # => 1
  41. # task.read_attribute_before_type_cast('id') # => '1'
  42. # task.read_attribute('completed_on') # => Sun, 21 Oct 2012
  43. # task.read_attribute_before_type_cast('completed_on') # => "2012-10-21"
  44. # task.read_attribute_before_type_cast(:completed_on) # => "2012-10-21"
  45. 3 def read_attribute_before_type_cast(attr_name)
  46. 514 attribute_before_type_cast(attr_name.to_s)
  47. end
  48. # Returns a hash of attributes before typecasting and deserialization.
  49. #
  50. # class Task < ActiveRecord::Base
  51. # end
  52. #
  53. # task = Task.new(title: nil, is_done: true, completed_on: '2012-10-21')
  54. # task.attributes
  55. # # => {"id"=>nil, "title"=>nil, "is_done"=>true, "completed_on"=>Sun, 21 Oct 2012, "created_at"=>nil, "updated_at"=>nil}
  56. # task.attributes_before_type_cast
  57. # # => {"id"=>nil, "title"=>nil, "is_done"=>true, "completed_on"=>"2012-10-21", "created_at"=>nil, "updated_at"=>nil}
  58. 3 def attributes_before_type_cast
  59. 3 @attributes.values_before_type_cast
  60. end
  61. 3 private
  62. # Dispatch target for <tt>*_before_type_cast</tt> attribute methods.
  63. 3 def attribute_before_type_cast(attr_name)
  64. 683 @attributes[attr_name].value_before_type_cast
  65. end
  66. 3 def attribute_came_from_user?(attr_name)
  67. 141 @attributes[attr_name].came_from_user?
  68. end
  69. end
  70. end
  71. end

lib/active_record/attribute_methods/dirty.rb

98.67% lines covered

75 relevant lines. 74 lines covered and 1 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 require "active_support/core_ext/module/attribute_accessors"
  3. 3 module ActiveRecord
  4. 3 module AttributeMethods
  5. 3 module Dirty
  6. 3 extend ActiveSupport::Concern
  7. 3 include ActiveModel::Dirty
  8. 3 included do
  9. 9 if self < ::ActiveRecord::Timestamp
  10. raise "You cannot include Dirty after Timestamp"
  11. end
  12. 9 class_attribute :partial_writes, instance_writer: false, default: true
  13. # Attribute methods for "changed in last call to save?"
  14. 9 attribute_method_affix(prefix: "saved_change_to_", suffix: "?")
  15. 9 attribute_method_prefix("saved_change_to_")
  16. 9 attribute_method_suffix("_before_last_save")
  17. # Attribute methods for "will change if I call save?"
  18. 9 attribute_method_affix(prefix: "will_save_change_to_", suffix: "?")
  19. 9 attribute_method_suffix("_change_to_be_saved", "_in_database")
  20. end
  21. # <tt>reload</tt> the record and clears changed attributes.
  22. 3 def reload(*)
  23. 2233 super.tap do
  24. 2215 @mutations_before_last_save = nil
  25. 2215 @mutations_from_database = nil
  26. end
  27. end
  28. # Did this attribute change when we last saved?
  29. #
  30. # This method is useful in after callbacks to determine if an attribute
  31. # was changed during the save that triggered the callbacks to run. It can
  32. # be invoked as +saved_change_to_name?+ instead of
  33. # <tt>saved_change_to_attribute?("name")</tt>.
  34. #
  35. # ==== Options
  36. #
  37. # +from+ When passed, this method will return false unless the original
  38. # value is equal to the given option
  39. #
  40. # +to+ When passed, this method will return false unless the value was
  41. # changed to the given value
  42. 3 def saved_change_to_attribute?(attr_name, **options)
  43. 577 mutations_before_last_save.changed?(attr_name.to_s, **options)
  44. end
  45. # Returns the change to an attribute during the last save. If the
  46. # attribute was changed, the result will be an array containing the
  47. # original value and the saved value.
  48. #
  49. # This method is useful in after callbacks, to see the change in an
  50. # attribute during the save that triggered the callbacks to run. It can be
  51. # invoked as +saved_change_to_name+ instead of
  52. # <tt>saved_change_to_attribute("name")</tt>.
  53. 3 def saved_change_to_attribute(attr_name)
  54. 36 mutations_before_last_save.change_to_attribute(attr_name.to_s)
  55. end
  56. # Returns the original value of an attribute before the last save.
  57. #
  58. # This method is useful in after callbacks to get the original value of an
  59. # attribute before the save that triggered the callbacks to run. It can be
  60. # invoked as +name_before_last_save+ instead of
  61. # <tt>attribute_before_last_save("name")</tt>.
  62. 3 def attribute_before_last_save(attr_name)
  63. 159 mutations_before_last_save.original_value(attr_name.to_s)
  64. end
  65. # Did the last call to +save+ have any changes to change?
  66. 3 def saved_changes?
  67. 633 mutations_before_last_save.any_changes?
  68. end
  69. # Returns a hash containing all the changes that were just saved.
  70. 3 def saved_changes
  71. 536 mutations_before_last_save.changes
  72. end
  73. # Will this attribute change the next time we save?
  74. #
  75. # This method is useful in validations and before callbacks to determine
  76. # if the next call to +save+ will change a particular attribute. It can be
  77. # invoked as +will_save_change_to_name?+ instead of
  78. # <tt>will_save_change_to_attribute?("name")</tt>.
  79. #
  80. # ==== Options
  81. #
  82. # +from+ When passed, this method will return false unless the original
  83. # value is equal to the given option
  84. #
  85. # +to+ When passed, this method will return false unless the value will be
  86. # changed to the given value
  87. 3 def will_save_change_to_attribute?(attr_name, **options)
  88. 2627 mutations_from_database.changed?(attr_name.to_s, **options)
  89. end
  90. # Returns the change to an attribute that will be persisted during the
  91. # next save.
  92. #
  93. # This method is useful in validations and before callbacks, to see the
  94. # change to an attribute that will occur when the record is saved. It can
  95. # be invoked as +name_change_to_be_saved+ instead of
  96. # <tt>attribute_change_to_be_saved("name")</tt>.
  97. #
  98. # If the attribute will change, the result will be an array containing the
  99. # original value and the new value about to be saved.
  100. 3 def attribute_change_to_be_saved(attr_name)
  101. 6 mutations_from_database.change_to_attribute(attr_name.to_s)
  102. end
  103. # Returns the value of an attribute in the database, as opposed to the
  104. # in-memory value that will be persisted the next time the record is
  105. # saved.
  106. #
  107. # This method is useful in validations and before callbacks, to see the
  108. # original value of an attribute prior to any changes about to be
  109. # saved. It can be invoked as +name_in_database+ instead of
  110. # <tt>attribute_in_database("name")</tt>.
  111. 3 def attribute_in_database(attr_name)
  112. 4591 mutations_from_database.original_value(attr_name.to_s)
  113. end
  114. # Will the next call to +save+ have any changes to persist?
  115. 3 def has_changes_to_save?
  116. 11007 mutations_from_database.any_changes?
  117. end
  118. # Returns a hash containing all the changes that will be persisted during
  119. # the next save.
  120. 3 def changes_to_save
  121. 138 mutations_from_database.changes
  122. end
  123. # Returns an array of the names of any attributes that will change when
  124. # the record is next saved.
  125. 3 def changed_attribute_names_to_save
  126. 20918 mutations_from_database.changed_attribute_names
  127. end
  128. # Returns a hash of the attributes that will change when the record is
  129. # next saved.
  130. #
  131. # The hash keys are the attribute names, and the hash values are the
  132. # original attribute values in the database (as opposed to the in-memory
  133. # values about to be saved).
  134. 3 def attributes_in_database
  135. 6 mutations_from_database.changed_values
  136. end
  137. 3 private
  138. 3 def write_attribute_without_type_cast(attr_name, value)
  139. 449 result = super
  140. 446 clear_attribute_change(attr_name)
  141. 446 result
  142. end
  143. 3 def _touch_row(attribute_names, time)
  144. 504 @_touch_attr_names = Set.new(attribute_names)
  145. 504 affected_rows = super
  146. 489 if @_skip_dirty_tracking ||= false
  147. 342 clear_attribute_changes(@_touch_attr_names)
  148. 342 return affected_rows
  149. end
  150. 147 changes = {}
  151. 147 @attributes.keys.each do |attr_name|
  152. 1803 next if @_touch_attr_names.include?(attr_name)
  153. 1593 if attribute_changed?(attr_name)
  154. 3 changes[attr_name] = _read_attribute(attr_name)
  155. 3 _write_attribute(attr_name, attribute_was(attr_name))
  156. 3 clear_attribute_change(attr_name)
  157. end
  158. end
  159. 147 changes_applied
  160. 150 changes.each { |attr_name, value| _write_attribute(attr_name, value) }
  161. 147 affected_rows
  162. ensure
  163. 504 @_touch_attr_names, @_skip_dirty_tracking = nil, nil
  164. end
  165. 3 def _update_record(attribute_names = attribute_names_for_partial_writes)
  166. 3413 affected_rows = super
  167. 3372 changes_applied
  168. 3372 affected_rows
  169. end
  170. 3 def _create_record(attribute_names = attribute_names_for_partial_writes)
  171. 12437 id = super
  172. 12384 changes_applied
  173. 12384 id
  174. end
  175. 3 def attribute_names_for_partial_writes
  176. 15857 partial_writes? ? changed_attribute_names_to_save : attribute_names
  177. end
  178. end
  179. end
  180. end

lib/active_record/attribute_methods/primary_key.rb

96.49% lines covered

57 relevant lines. 55 lines covered and 2 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 require "set"
  3. 3 module ActiveRecord
  4. 3 module AttributeMethods
  5. 3 module PrimaryKey
  6. 3 extend ActiveSupport::Concern
  7. # Returns this record's primary key value wrapped in an array if one is
  8. # available.
  9. 3 def to_key
  10. 100 key = id
  11. 100 [key] if key
  12. end
  13. # Returns the primary key column's value.
  14. 3 def id
  15. 155721 _read_attribute(@primary_key)
  16. end
  17. # Sets the primary key column's value.
  18. 3 def id=(value)
  19. 11242 _write_attribute(@primary_key, value)
  20. end
  21. # Queries the primary key column's value.
  22. 3 def id?
  23. query_attribute(@primary_key)
  24. end
  25. # Returns the primary key column's value before type cast.
  26. 3 def id_before_type_cast
  27. 3 read_attribute_before_type_cast(@primary_key)
  28. end
  29. # Returns the primary key column's previous value.
  30. 3 def id_was
  31. attribute_was(@primary_key)
  32. end
  33. # Returns the primary key column's value from the database.
  34. 3 def id_in_database
  35. 4103 attribute_in_database(@primary_key)
  36. end
  37. 3 def id_for_database # :nodoc:
  38. 24 @attributes[@primary_key].value_for_database
  39. end
  40. 3 private
  41. 3 def attribute_method?(attr_name)
  42. 815 attr_name == "id" || super
  43. end
  44. 3 module ClassMethods
  45. 3 ID_ATTRIBUTE_METHODS = %w(id id= id? id_before_type_cast id_was id_in_database).to_set
  46. 3 def instance_method_already_implemented?(method_name)
  47. 424449 super || primary_key && ID_ATTRIBUTE_METHODS.include?(method_name)
  48. end
  49. 3 def dangerous_attribute_method?(method_name)
  50. 428652 super && !ID_ATTRIBUTE_METHODS.include?(method_name)
  51. end
  52. # Defines the primary key field -- can be overridden in subclasses.
  53. # Overwriting will negate any effect of the +primary_key_prefix_type+
  54. # setting, though.
  55. 3 def primary_key
  56. 802349 @primary_key = reset_primary_key unless defined? @primary_key
  57. 802343 @primary_key
  58. end
  59. # Returns a quoted version of the primary key name, used to construct
  60. # SQL statements.
  61. 3 def quoted_primary_key
  62. 21 @quoted_primary_key ||= connection.quote_column_name(primary_key)
  63. end
  64. 3 def reset_primary_key #:nodoc:
  65. 2086 if base_class?
  66. 1597 self.primary_key = get_primary_key(base_class.name)
  67. else
  68. 489 self.primary_key = base_class.primary_key
  69. end
  70. end
  71. 3 def get_primary_key(base_name) #:nodoc:
  72. 4865 if base_name && primary_key_prefix_type == :table_name
  73. 6 base_name.foreign_key(false)
  74. 4859 elsif base_name && primary_key_prefix_type == :table_name_with_underscore
  75. 6 base_name.foreign_key
  76. else
  77. 4853 if ActiveRecord::Base != self && table_exists?
  78. 1567 pk = connection.schema_cache.primary_keys(table_name)
  79. 1567 suppress_composite_primary_key(pk)
  80. else
  81. 3280 "id"
  82. end
  83. end
  84. end
  85. # Sets the name of the primary key column.
  86. #
  87. # class Project < ActiveRecord::Base
  88. # self.primary_key = 'sysid'
  89. # end
  90. #
  91. # You can also define the #primary_key method yourself:
  92. #
  93. # class Project < ActiveRecord::Base
  94. # def self.primary_key
  95. # 'foo_' + super
  96. # end
  97. # end
  98. #
  99. # Project.primary_key # => "foo_id"
  100. 3 def primary_key=(value)
  101. 2143 @primary_key = value && -value.to_s
  102. 2143 @quoted_primary_key = nil
  103. 2143 @attributes_builder = nil
  104. end
  105. 3 private
  106. 3 def suppress_composite_primary_key(pk)
  107. 1408 return pk unless pk.is_a?(Array)
  108. 3 warn <<~WARNING
  109. WARNING: Active Record does not support composite primary key.
  110. #{table_name} has composite primary key. Composite primary key is ignored.
  111. WARNING
  112. end
  113. end
  114. end
  115. end
  116. end

lib/active_record/attribute_methods/query.rb

100.0% lines covered

21 relevant lines. 21 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 module ActiveRecord
  3. 3 module AttributeMethods
  4. 3 module Query
  5. 3 extend ActiveSupport::Concern
  6. 3 included do
  7. 9 attribute_method_suffix "?"
  8. end
  9. 3 def query_attribute(attr_name)
  10. 687 value = self[attr_name]
  11. 684 case value
  12. 213 when true then true
  13. 308 when false, nil then false
  14. else
  15. 179 if !type_for_attribute(attr_name) { false }
  16. 16 if Numeric === value || !value.match?(/[^0-9]/)
  17. 10 !value.to_i.zero?
  18. else
  19. 6 return false if ActiveModel::Type::Boolean::FALSE_VALUES.include?(value)
  20. 6 !value.blank?
  21. end
  22. 147 elsif value.respond_to?(:zero?)
  23. 12 !value.zero?
  24. else
  25. 135 !value.blank?
  26. end
  27. end
  28. end
  29. 3 alias :attribute? :query_attribute
  30. 3 private :attribute?
  31. end
  32. end
  33. end

lib/active_record/attribute_methods/read.rb

100.0% lines covered

18 relevant lines. 18 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 module ActiveRecord
  3. 3 module AttributeMethods
  4. 3 module Read
  5. 3 extend ActiveSupport::Concern
  6. 3 module ClassMethods # :nodoc:
  7. 3 private
  8. 3 def define_method_attribute(name, owner:)
  9. ActiveModel::AttributeMethods::AttrNames.define_attribute_accessor_method(
  10. owner, name
  11. 18732 ) do |temp_method_name, attr_name_expr|
  12. owner <<
  13. "def #{temp_method_name}" <<
  14. 18732 " _read_attribute(#{attr_name_expr}) { |n| missing_attribute(n, caller) }" <<
  15. "end"
  16. end
  17. end
  18. end
  19. # Returns the value of the attribute identified by <tt>attr_name</tt> after
  20. # it has been typecast (for example, "2004-12-12" in a date column is cast
  21. # to a date object, like Date.new(2004, 12, 12)).
  22. 3 def read_attribute(attr_name, &block)
  23. 247105 name = attr_name.to_s
  24. 247105 name = self.class.attribute_aliases[name] || name
  25. 247105 name = @primary_key if name == "id" && @primary_key
  26. 247105 @attributes.fetch_value(name, &block)
  27. end
  28. # This method exists to avoid the expensive primary_key check internally, without
  29. # breaking compatibility with the read_attribute API
  30. 3 def _read_attribute(attr_name, &block) # :nodoc
  31. 530269 @attributes.fetch_value(attr_name, &block)
  32. end
  33. 3 alias :attribute :_read_attribute
  34. 3 private :attribute
  35. end
  36. end
  37. end

lib/active_record/attribute_methods/serialization.rb

100.0% lines covered

21 relevant lines. 21 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 module ActiveRecord
  3. 3 module AttributeMethods
  4. 3 module Serialization
  5. 3 extend ActiveSupport::Concern
  6. 3 class ColumnNotSerializableError < StandardError
  7. 3 def initialize(name, type)
  8. 8 super <<~EOS
  9. Column `#{name}` of type #{type.class} does not support `serialize` feature.
  10. Usually it means that you are trying to use `serialize`
  11. on a column that already implements serialization natively.
  12. EOS
  13. end
  14. end
  15. 3 module ClassMethods
  16. # If you have an attribute that needs to be saved to the database as an
  17. # object, and retrieved as the same object, then specify the name of that
  18. # attribute using this method and it will be handled automatically. The
  19. # serialization is done through YAML. If +class_name+ is specified, the
  20. # serialized object must be of that class on assignment and retrieval.
  21. # Otherwise SerializationTypeMismatch will be raised.
  22. #
  23. # Empty objects as <tt>{}</tt>, in the case of +Hash+, or <tt>[]</tt>, in the case of
  24. # +Array+, will always be persisted as null.
  25. #
  26. # Keep in mind that database adapters handle certain serialization tasks
  27. # for you. For instance: +json+ and +jsonb+ types in PostgreSQL will be
  28. # converted between JSON object/array syntax and Ruby +Hash+ or +Array+
  29. # objects transparently. There is no need to use #serialize in this
  30. # case.
  31. #
  32. # For more complex cases, such as conversion to or from your application
  33. # domain objects, consider using the ActiveRecord::Attributes API.
  34. #
  35. # ==== Parameters
  36. #
  37. # * +attr_name+ - The field name that should be serialized.
  38. # * +class_name_or_coder+ - Optional, a coder object, which responds to +.load+ and +.dump+
  39. # or a class name that the object type should be equal to.
  40. #
  41. # ==== Example
  42. #
  43. # # Serialize a preferences attribute.
  44. # class User < ActiveRecord::Base
  45. # serialize :preferences
  46. # end
  47. #
  48. # # Serialize preferences using JSON as coder.
  49. # class User < ActiveRecord::Base
  50. # serialize :preferences, JSON
  51. # end
  52. #
  53. # # Serialize preferences as Hash using YAML coder.
  54. # class User < ActiveRecord::Base
  55. # serialize :preferences, Hash
  56. # end
  57. 3 def serialize(attr_name, class_name_or_coder = Object)
  58. # When ::JSON is used, force it to go through the Active Support JSON encoder
  59. # to ensure special objects (e.g. Active Record models) are dumped correctly
  60. # using the #as_json hook.
  61. 273 coder = if class_name_or_coder == ::JSON
  62. 22 Coders::JSON
  63. 548 elsif [:load, :dump].all? { |x| class_name_or_coder.respond_to?(x) }
  64. 46 class_name_or_coder
  65. else
  66. 205 Coders::YAMLColumn.new(attr_name, class_name_or_coder)
  67. end
  68. 270 decorate_attribute_type(attr_name.to_s) do |cast_type|
  69. 499 if type_incompatible_with_serialize?(cast_type, class_name_or_coder)
  70. 8 raise ColumnNotSerializableError.new(attr_name, cast_type)
  71. end
  72. 491 Type::Serialized.new(cast_type, coder)
  73. end
  74. end
  75. 3 private
  76. 3 def type_incompatible_with_serialize?(type, class_name)
  77. 499 type.is_a?(ActiveRecord::Type::Json) && class_name == ::JSON ||
  78. type.respond_to?(:type_cast_array, true) && class_name == ::Array
  79. end
  80. end
  81. end
  82. end
  83. end

lib/active_record/attribute_methods/time_zone_conversion.rb

93.75% lines covered

48 relevant lines. 45 lines covered and 3 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 require "active_support/core_ext/object/try"
  3. 3 module ActiveRecord
  4. 3 module AttributeMethods
  5. 3 module TimeZoneConversion
  6. 3 class TimeZoneConverter < DelegateClass(Type::Value) # :nodoc:
  7. 3 def self.new(subtype)
  8. 342 self === subtype ? subtype : super
  9. end
  10. 3 def deserialize(value)
  11. 536 convert_time_to_time_zone(super)
  12. end
  13. 3 def cast(value)
  14. 311 return if value.nil?
  15. 305 if value.is_a?(Hash)
  16. 12 set_time_zone_without_conversion(super)
  17. 293 elsif value.respond_to?(:in_time_zone)
  18. 286 begin
  19. 286 super(user_input_in_time_zone(value)) || super
  20. rescue ArgumentError
  21. nil
  22. end
  23. else
  24. 14 map_avoiding_infinite_recursion(super) { |v| cast(v) }
  25. end
  26. end
  27. 3 private
  28. 3 def convert_time_to_time_zone(value)
  29. 543 return if value.nil?
  30. 119 if value.acts_like?(:time)
  31. 114 value.in_time_zone
  32. 5 elsif value.is_a?(::Float)
  33. value
  34. else
  35. 12 map_avoiding_infinite_recursion(value) { |v| convert_time_to_time_zone(v) }
  36. end
  37. end
  38. 3 def set_time_zone_without_conversion(value)
  39. 12 ::Time.zone.local_to_utc(value).try(:in_time_zone) if value
  40. end
  41. 3 def map_avoiding_infinite_recursion(value)
  42. 12 map(value) do |v|
  43. 17 if value.equal?(v)
  44. 3 nil
  45. else
  46. 14 yield(v)
  47. end
  48. end
  49. end
  50. end
  51. 3 extend ActiveSupport::Concern
  52. 3 included do
  53. 9 mattr_accessor :time_zone_aware_attributes, instance_writer: false, default: false
  54. 9 class_attribute :skip_time_zone_conversion_for_attributes, instance_writer: false, default: []
  55. 9 class_attribute :time_zone_aware_types, instance_writer: false, default: [ :datetime, :time ]
  56. end
  57. 3 module ClassMethods # :nodoc:
  58. 3 def define_attribute(name, cast_type, **)
  59. 30036 if create_time_zone_conversion_attribute?(name, cast_type)
  60. 342 cast_type = TimeZoneConverter.new(cast_type)
  61. end
  62. 30036 super
  63. end
  64. 3 private
  65. 3 def create_time_zone_conversion_attribute?(name, cast_type)
  66. 30036 enabled_for_column = time_zone_aware_attributes &&
  67. !skip_time_zone_conversion_for_attributes.include?(name.to_sym)
  68. 30036 enabled_for_column && time_zone_aware_types.include?(cast_type.type)
  69. end
  70. end
  71. end
  72. end
  73. end

lib/active_record/attribute_methods/write.rb

100.0% lines covered

23 relevant lines. 23 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 module ActiveRecord
  3. 3 module AttributeMethods
  4. 3 module Write
  5. 3 extend ActiveSupport::Concern
  6. 3 included do
  7. 9 attribute_method_suffix "="
  8. end
  9. 3 module ClassMethods # :nodoc:
  10. 3 private
  11. 3 def define_method_attribute=(name, owner:)
  12. ActiveModel::AttributeMethods::AttrNames.define_attribute_accessor_method(
  13. owner, name, writer: true,
  14. 18623 ) do |temp_method_name, attr_name_expr|
  15. owner <<
  16. "def #{temp_method_name}(value)" <<
  17. 18623 " _write_attribute(#{attr_name_expr}, value)" <<
  18. "end"
  19. end
  20. end
  21. end
  22. # Updates the attribute identified by <tt>attr_name</tt> with the
  23. # specified +value+. Empty strings for Integer and Float columns are
  24. # turned into +nil+.
  25. 3 def write_attribute(attr_name, value)
  26. 21400 name = attr_name.to_s
  27. 21400 name = self.class.attribute_aliases[name] || name
  28. 21400 name = @primary_key if name == "id" && @primary_key
  29. 21400 @attributes.write_from_user(name, value)
  30. end
  31. # This method exists to avoid the expensive primary_key check internally, without
  32. # breaking compatibility with the write_attribute API
  33. 3 def _write_attribute(attr_name, value) # :nodoc:
  34. 109693 @attributes.write_from_user(attr_name, value)
  35. end
  36. 3 alias :attribute= :_write_attribute
  37. 3 private :attribute=
  38. 3 private
  39. 3 def write_attribute_without_type_cast(attr_name, value)
  40. 449 @attributes.write_cast_value(attr_name, value)
  41. end
  42. end
  43. end
  44. end

lib/active_record/attributes.rb

100.0% lines covered

41 relevant lines. 41 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 require "active_model/attribute/user_provided_default"
  3. 3 module ActiveRecord
  4. # See ActiveRecord::Attributes::ClassMethods for documentation
  5. 3 module Attributes
  6. 3 extend ActiveSupport::Concern
  7. 3 included do
  8. 3 class_attribute :attributes_to_define_after_schema_loads, instance_accessor: false, default: {} # :internal:
  9. end
  10. 3 module ClassMethods
  11. # Defines an attribute with a type on this model. It will override the
  12. # type of existing attributes if needed. This allows control over how
  13. # values are converted to and from SQL when assigned to a model. It also
  14. # changes the behavior of values passed to
  15. # {ActiveRecord::Base.where}[rdoc-ref:QueryMethods#where]. This will let you use
  16. # your domain objects across much of Active Record, without having to
  17. # rely on implementation details or monkey patching.
  18. #
  19. # +name+ The name of the methods to define attribute methods for, and the
  20. # column which this will persist to.
  21. #
  22. # +cast_type+ A symbol such as +:string+ or +:integer+, or a type object
  23. # to be used for this attribute. See the examples below for more
  24. # information about providing custom type objects.
  25. #
  26. # ==== Options
  27. #
  28. # The following options are accepted:
  29. #
  30. # +default+ The default value to use when no value is provided. If this option
  31. # is not passed, the previous default value (if any) will be used.
  32. # Otherwise, the default will be +nil+.
  33. #
  34. # +array+ (PostgreSQL only) specifies that the type should be an array (see the
  35. # examples below).
  36. #
  37. # +range+ (PostgreSQL only) specifies that the type should be a range (see the
  38. # examples below).
  39. #
  40. # When using a symbol for +cast_type+, extra options are forwarded to the
  41. # constructor of the type object.
  42. #
  43. # ==== Examples
  44. #
  45. # The type detected by Active Record can be overridden.
  46. #
  47. # # db/schema.rb
  48. # create_table :store_listings, force: true do |t|
  49. # t.decimal :price_in_cents
  50. # end
  51. #
  52. # # app/models/store_listing.rb
  53. # class StoreListing < ActiveRecord::Base
  54. # end
  55. #
  56. # store_listing = StoreListing.new(price_in_cents: '10.1')
  57. #
  58. # # before
  59. # store_listing.price_in_cents # => BigDecimal(10.1)
  60. #
  61. # class StoreListing < ActiveRecord::Base
  62. # attribute :price_in_cents, :integer
  63. # end
  64. #
  65. # # after
  66. # store_listing.price_in_cents # => 10
  67. #
  68. # A default can also be provided.
  69. #
  70. # # db/schema.rb
  71. # create_table :store_listings, force: true do |t|
  72. # t.string :my_string, default: "original default"
  73. # end
  74. #
  75. # StoreListing.new.my_string # => "original default"
  76. #
  77. # # app/models/store_listing.rb
  78. # class StoreListing < ActiveRecord::Base
  79. # attribute :my_string, :string, default: "new default"
  80. # end
  81. #
  82. # StoreListing.new.my_string # => "new default"
  83. #
  84. # class Product < ActiveRecord::Base
  85. # attribute :my_default_proc, :datetime, default: -> { Time.now }
  86. # end
  87. #
  88. # Product.new.my_default_proc # => 2015-05-30 11:04:48 -0600
  89. # sleep 1
  90. # Product.new.my_default_proc # => 2015-05-30 11:04:49 -0600
  91. #
  92. # \Attributes do not need to be backed by a database column.
  93. #
  94. # # app/models/my_model.rb
  95. # class MyModel < ActiveRecord::Base
  96. # attribute :my_string, :string
  97. # attribute :my_int_array, :integer, array: true
  98. # attribute :my_float_range, :float, range: true
  99. # end
  100. #
  101. # model = MyModel.new(
  102. # my_string: "string",
  103. # my_int_array: ["1", "2", "3"],
  104. # my_float_range: "[1,3.5]",
  105. # )
  106. # model.attributes
  107. # # =>
  108. # {
  109. # my_string: "string",
  110. # my_int_array: [1, 2, 3],
  111. # my_float_range: 1.0..3.5
  112. # }
  113. #
  114. # Passing options to the type constructor
  115. #
  116. # # app/models/my_model.rb
  117. # class MyModel < ActiveRecord::Base
  118. # attribute :small_int, :integer, limit: 2
  119. # end
  120. #
  121. # MyModel.create(small_int: 65537)
  122. # # => Error: 65537 is out of range for the limit of two bytes
  123. #
  124. # ==== Creating Custom Types
  125. #
  126. # Users may also define their own custom types, as long as they respond
  127. # to the methods defined on the value type. The method +deserialize+ or
  128. # +cast+ will be called on your type object, with raw input from the
  129. # database or from your controllers. See ActiveModel::Type::Value for the
  130. # expected API. It is recommended that your type objects inherit from an
  131. # existing type, or from ActiveRecord::Type::Value
  132. #
  133. # class MoneyType < ActiveRecord::Type::Integer
  134. # def cast(value)
  135. # if !value.kind_of?(Numeric) && value.include?('$')
  136. # price_in_dollars = value.gsub(/\$/, '').to_f
  137. # super(price_in_dollars * 100)
  138. # else
  139. # super
  140. # end
  141. # end
  142. # end
  143. #
  144. # # config/initializers/types.rb
  145. # ActiveRecord::Type.register(:money, MoneyType)
  146. #
  147. # # app/models/store_listing.rb
  148. # class StoreListing < ActiveRecord::Base
  149. # attribute :price_in_cents, :money
  150. # end
  151. #
  152. # store_listing = StoreListing.new(price_in_cents: '$10.00')
  153. # store_listing.price_in_cents # => 1000
  154. #
  155. # For more details on creating custom types, see the documentation for
  156. # ActiveModel::Type::Value. For more details on registering your types
  157. # to be referenced by a symbol, see ActiveRecord::Type.register. You can
  158. # also pass a type object directly, in place of a symbol.
  159. #
  160. # ==== \Querying
  161. #
  162. # When {ActiveRecord::Base.where}[rdoc-ref:QueryMethods#where] is called, it will
  163. # use the type defined by the model class to convert the value to SQL,
  164. # calling +serialize+ on your type object. For example:
  165. #
  166. # class Money < Struct.new(:amount, :currency)
  167. # end
  168. #
  169. # class MoneyType < Type::Value
  170. # def initialize(currency_converter:)
  171. # @currency_converter = currency_converter
  172. # end
  173. #
  174. # # value will be the result of +deserialize+ or
  175. # # +cast+. Assumed to be an instance of +Money+ in
  176. # # this case.
  177. # def serialize(value)
  178. # value_in_bitcoins = @currency_converter.convert_to_bitcoins(value)
  179. # value_in_bitcoins.amount
  180. # end
  181. # end
  182. #
  183. # # config/initializers/types.rb
  184. # ActiveRecord::Type.register(:money, MoneyType)
  185. #
  186. # # app/models/product.rb
  187. # class Product < ActiveRecord::Base
  188. # currency_converter = ConversionRatesFromTheInternet.new
  189. # attribute :price_in_bitcoins, :money, currency_converter: currency_converter
  190. # end
  191. #
  192. # Product.where(price_in_bitcoins: Money.new(5, "USD"))
  193. # # => SELECT * FROM products WHERE price_in_bitcoins = 0.02230
  194. #
  195. # Product.where(price_in_bitcoins: Money.new(5, "GBP"))
  196. # # => SELECT * FROM products WHERE price_in_bitcoins = 0.03412
  197. #
  198. # ==== Dirty Tracking
  199. #
  200. # The type of an attribute is given the opportunity to change how dirty
  201. # tracking is performed. The methods +changed?+ and +changed_in_place?+
  202. # will be called from ActiveModel::Dirty. See the documentation for those
  203. # methods in ActiveModel::Type::Value for more details.
  204. 3 def attribute(name, cast_type = nil, **options, &block)
  205. 569 name = name.to_s
  206. 569 reload_schema_from_cache
  207. 569 self.attributes_to_define_after_schema_loads =
  208. attributes_to_define_after_schema_loads.merge(
  209. name => [cast_type || block, options]
  210. )
  211. end
  212. # This is the low level API which sits beneath +attribute+. It only
  213. # accepts type objects, and will do its work immediately instead of
  214. # waiting for the schema to load. Automatic schema detection and
  215. # ClassMethods#attribute both call this under the hood. While this method
  216. # is provided so it can be used by plugin authors, application code
  217. # should probably use ClassMethods#attribute.
  218. #
  219. # +name+ The name of the attribute being defined. Expected to be a +String+.
  220. #
  221. # +cast_type+ The type object to use for this attribute.
  222. #
  223. # +default+ The default value to use when no value is provided. If this option
  224. # is not passed, the previous default value (if any) will be used.
  225. # Otherwise, the default will be +nil+. A proc can also be passed, and
  226. # will be called once each time a new value is needed.
  227. #
  228. # +user_provided_default+ Whether the default value should be cast using
  229. # +cast+ or +deserialize+.
  230. 3 def define_attribute(
  231. name,
  232. cast_type,
  233. default: NO_DEFAULT_PROVIDED,
  234. user_provided_default: true
  235. )
  236. 30036 attribute_types[name] = cast_type
  237. 30036 define_default_attribute(name, default, cast_type, from_user: user_provided_default)
  238. end
  239. 3 def load_schema! # :nodoc:
  240. 3471 super
  241. 3462 attributes_to_define_after_schema_loads.each do |name, (type, options)|
  242. 1458 define_attribute(name, _lookup_cast_type(name, type, options), **options.slice(:default))
  243. end
  244. end
  245. 3 private
  246. 3 NO_DEFAULT_PROVIDED = Object.new # :nodoc:
  247. 3 private_constant :NO_DEFAULT_PROVIDED
  248. 3 def define_default_attribute(name, value, type, from_user:)
  249. 30036 if value == NO_DEFAULT_PROVIDED
  250. 1324 default_attribute = _default_attributes[name].with_type(type)
  251. 28712 elsif from_user
  252. 126 default_attribute = ActiveModel::Attribute::UserProvidedDefault.new(
  253. name,
  254. value,
  255. type,
  256. 39 _default_attributes.fetch(name.to_s) { nil },
  257. )
  258. else
  259. 28586 default_attribute = ActiveModel::Attribute.from_database(name, value, type)
  260. end
  261. 30036 _default_attributes[name] = default_attribute
  262. end
  263. 3 def decorate_attribute_type(attr_name, **default)
  264. 396 type, options = attributes_to_define_after_schema_loads[attr_name]
  265. 396 attribute(attr_name, **default) do |cast_type|
  266. 790 if type && !type.is_a?(Proc)
  267. 9 cast_type = _lookup_cast_type(attr_name, type, options)
  268. end
  269. 790 yield cast_type
  270. end
  271. end
  272. 3 def _lookup_cast_type(name, type, options)
  273. 1467 case type
  274. when Symbol
  275. 608 adapter_name = ActiveRecord::Type.adapter_name_from(self)
  276. 608 ActiveRecord::Type.lookup(type, **options.except(:default), adapter: adapter_name)
  277. when Proc
  278. 790 type[type_for_attribute(name)]
  279. else
  280. 69 type || type_for_attribute(name)
  281. end
  282. end
  283. end
  284. end
  285. end

lib/active_record/autosave_association.rb

100.0% lines covered

172 relevant lines. 172 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 module ActiveRecord
  3. # = Active Record Autosave Association
  4. #
  5. # AutosaveAssociation is a module that takes care of automatically saving
  6. # associated records when their parent is saved. In addition to saving, it
  7. # also destroys any associated records that were marked for destruction.
  8. # (See #mark_for_destruction and #marked_for_destruction?).
  9. #
  10. # Saving of the parent, its associations, and the destruction of marked
  11. # associations, all happen inside a transaction. This should never leave the
  12. # database in an inconsistent state.
  13. #
  14. # If validations for any of the associations fail, their error messages will
  15. # be applied to the parent.
  16. #
  17. # Note that it also means that associations marked for destruction won't
  18. # be destroyed directly. They will however still be marked for destruction.
  19. #
  20. # Note that <tt>autosave: false</tt> is not same as not declaring <tt>:autosave</tt>.
  21. # When the <tt>:autosave</tt> option is not present then new association records are
  22. # saved but the updated association records are not saved.
  23. #
  24. # == Validation
  25. #
  26. # Child records are validated unless <tt>:validate</tt> is +false+.
  27. #
  28. # == Callbacks
  29. #
  30. # Association with autosave option defines several callbacks on your
  31. # model (around_save, before_save, after_create, after_update). Please note that
  32. # callbacks are executed in the order they were defined in
  33. # model. You should avoid modifying the association content before
  34. # autosave callbacks are executed. Placing your callbacks after
  35. # associations is usually a good practice.
  36. #
  37. # === One-to-one Example
  38. #
  39. # class Post < ActiveRecord::Base
  40. # has_one :author, autosave: true
  41. # end
  42. #
  43. # Saving changes to the parent and its associated model can now be performed
  44. # automatically _and_ atomically:
  45. #
  46. # post = Post.find(1)
  47. # post.title # => "The current global position of migrating ducks"
  48. # post.author.name # => "alloy"
  49. #
  50. # post.title = "On the migration of ducks"
  51. # post.author.name = "Eloy Duran"
  52. #
  53. # post.save
  54. # post.reload
  55. # post.title # => "On the migration of ducks"
  56. # post.author.name # => "Eloy Duran"
  57. #
  58. # Destroying an associated model, as part of the parent's save action, is as
  59. # simple as marking it for destruction:
  60. #
  61. # post.author.mark_for_destruction
  62. # post.author.marked_for_destruction? # => true
  63. #
  64. # Note that the model is _not_ yet removed from the database:
  65. #
  66. # id = post.author.id
  67. # Author.find_by(id: id).nil? # => false
  68. #
  69. # post.save
  70. # post.reload.author # => nil
  71. #
  72. # Now it _is_ removed from the database:
  73. #
  74. # Author.find_by(id: id).nil? # => true
  75. #
  76. # === One-to-many Example
  77. #
  78. # When <tt>:autosave</tt> is not declared new children are saved when their parent is saved:
  79. #
  80. # class Post < ActiveRecord::Base
  81. # has_many :comments # :autosave option is not declared
  82. # end
  83. #
  84. # post = Post.new(title: 'ruby rocks')
  85. # post.comments.build(body: 'hello world')
  86. # post.save # => saves both post and comment
  87. #
  88. # post = Post.create(title: 'ruby rocks')
  89. # post.comments.build(body: 'hello world')
  90. # post.save # => saves both post and comment
  91. #
  92. # post = Post.create(title: 'ruby rocks')
  93. # comment = post.comments.create(body: 'hello world')
  94. # comment.body = 'hi everyone'
  95. # post.save # => saves post, but not comment
  96. #
  97. # When <tt>:autosave</tt> is true all children are saved, no matter whether they
  98. # are new records or not:
  99. #
  100. # class Post < ActiveRecord::Base
  101. # has_many :comments, autosave: true
  102. # end
  103. #
  104. # post = Post.create(title: 'ruby rocks')
  105. # comment = post.comments.create(body: 'hello world')
  106. # comment.body = 'hi everyone'
  107. # post.comments.build(body: "good morning.")
  108. # post.save # => saves post and both comments.
  109. #
  110. # Destroying one of the associated models as part of the parent's save action
  111. # is as simple as marking it for destruction:
  112. #
  113. # post.comments # => [#<Comment id: 1, ...>, #<Comment id: 2, ...]>
  114. # post.comments[1].mark_for_destruction
  115. # post.comments[1].marked_for_destruction? # => true
  116. # post.comments.length # => 2
  117. #
  118. # Note that the model is _not_ yet removed from the database:
  119. #
  120. # id = post.comments.last.id
  121. # Comment.find_by(id: id).nil? # => false
  122. #
  123. # post.save
  124. # post.reload.comments.length # => 1
  125. #
  126. # Now it _is_ removed from the database:
  127. #
  128. # Comment.find_by(id: id).nil? # => true
  129. #
  130. # === Caveats
  131. #
  132. # Note that autosave will only trigger for already-persisted association records
  133. # if the records themselves have been changed. This is to protect against
  134. # <tt>SystemStackError</tt> caused by circular association validations. The one
  135. # exception is if a custom validation context is used, in which case the validations
  136. # will always fire on the associated records.
  137. 3 module AutosaveAssociation
  138. 3 extend ActiveSupport::Concern
  139. 3 module AssociationBuilderExtension #:nodoc:
  140. 3 def self.build(model, reflection)
  141. 3590 model.send(:add_autosave_association_callbacks, reflection)
  142. end
  143. 3 def self.valid_options
  144. 3602 [ :autosave ]
  145. end
  146. end
  147. 3 included do
  148. 3 Associations::Builder::Association.extensions << AssociationBuilderExtension
  149. 3 mattr_accessor :index_nested_attribute_errors, instance_writer: false, default: false
  150. end
  151. 3 module ClassMethods # :nodoc:
  152. 3 private
  153. 3 def define_non_cyclic_method(name, &block)
  154. 5086 return if instance_methods(false).include?(name)
  155. 5059 define_method(name) do |*args|
  156. 268793 result = true; @_already_called ||= {}
  157. # Loop prevention for validation of associations
  158. 268793 unless @_already_called[name]
  159. 267955 begin
  160. 267955 @_already_called[name] = true
  161. 267955 result = instance_eval(&block)
  162. ensure
  163. 267955 @_already_called[name] = false
  164. end
  165. end
  166. 268730 result
  167. end
  168. end
  169. # Adds validation and save callbacks for the association as specified by
  170. # the +reflection+.
  171. #
  172. # For performance reasons, we don't check whether to validate at runtime.
  173. # However the validation and callback methods are lazy and those methods
  174. # get created when they are invoked for the very first time. However,
  175. # this can change, for instance, when using nested attributes, which is
  176. # called _after_ the association has been defined. Since we don't want
  177. # the callbacks to get defined multiple times, there are guards that
  178. # check if the save or validation methods have already been defined
  179. # before actually defining them.
  180. 3 def add_autosave_association_callbacks(reflection)
  181. 3602 save_method = :"autosave_associated_records_for_#{reflection.name}"
  182. 3602 if reflection.collection?
  183. 1898 around_save :around_save_collection_association
  184. 113971 define_non_cyclic_method(save_method) { save_collection_association(reflection) }
  185. # Doesn't use after_save as that would save associations added in after_create/after_update twice
  186. 1898 after_create save_method
  187. 1898 after_update save_method
  188. 1704 elsif reflection.has_one?
  189. 18633 define_method(save_method) { save_has_one_association(reflection) } unless method_defined?(save_method)
  190. # Configures two callbacks instead of a single after_save so that
  191. # the model may rely on their execution order relative to its
  192. # own callbacks.
  193. #
  194. # For example, given that after_creates run before after_saves, if
  195. # we configured instead an after_save there would be no way to fire
  196. # a custom after_create callback after the child association gets
  197. # created.
  198. 408 after_create save_method
  199. 408 after_update save_method
  200. else
  201. 28215 define_non_cyclic_method(save_method) { throw(:abort) if save_belongs_to_association(reflection) == false }
  202. 1296 before_save save_method
  203. end
  204. 3602 define_autosave_validation_callbacks(reflection)
  205. end
  206. 3 def define_autosave_validation_callbacks(reflection)
  207. 3920 validation_method = :"validate_associated_records_for_#{reflection.name}"
  208. 3920 if reflection.validate? && !method_defined?(validation_method)
  209. 1892 if reflection.collection?
  210. 1823 method = :validate_collection_association
  211. else
  212. 69 method = :validate_single_association
  213. end
  214. 130855 define_non_cyclic_method(validation_method) { send(method, reflection) }
  215. 1892 validate validation_method
  216. 1892 after_validation :_ensure_no_duplicate_errors
  217. end
  218. end
  219. end
  220. # Reloads the attributes of the object as usual and clears <tt>marked_for_destruction</tt> flag.
  221. 3 def reload(options = nil)
  222. 2233 @marked_for_destruction = false
  223. 2233 @destroyed_by_association = nil
  224. 2233 super
  225. end
  226. # Marks this record to be destroyed as part of the parent's save transaction.
  227. # This does _not_ actually destroy the record instantly, rather child record will be destroyed
  228. # when <tt>parent.save</tt> is called.
  229. #
  230. # Only useful if the <tt>:autosave</tt> option on the parent is enabled for this associated model.
  231. 3 def mark_for_destruction
  232. 225 @marked_for_destruction = true
  233. end
  234. # Returns whether or not this record will be destroyed as part of the parent's save transaction.
  235. #
  236. # Only useful if the <tt>:autosave</tt> option on the parent is enabled for this associated model.
  237. 3 def marked_for_destruction?
  238. 9877 @marked_for_destruction
  239. end
  240. # Records the association that is being destroyed and destroying this
  241. # record in the process.
  242. 3 def destroyed_by_association=(reflection)
  243. 225 @destroyed_by_association = reflection
  244. end
  245. # Returns the association for the parent being destroyed.
  246. #
  247. # Used to avoid updating the counter cache unnecessarily.
  248. 3 def destroyed_by_association
  249. 281 @destroyed_by_association
  250. end
  251. # Returns whether or not this record has been changed in any way (including whether
  252. # any of its nested autosave associations are likewise changed)
  253. 3 def changed_for_autosave?
  254. 8098 new_record? || has_changes_to_save? || marked_for_destruction? || nested_records_changed_for_autosave?
  255. end
  256. 3 private
  257. # Returns the record for an association collection that should be validated
  258. # or saved. If +autosave+ is +false+ only new records will be returned,
  259. # unless the parent is/was a new record itself.
  260. 3 def associated_records_to_validate_or_save(association, new_record, autosave)
  261. 3031 if new_record || custom_validation_context?
  262. 1358 association && association.target
  263. 1673 elsif autosave
  264. 948 association.target.find_all(&:changed_for_autosave?)
  265. else
  266. 725 association.target.find_all(&:new_record?)
  267. end
  268. end
  269. # Go through nested autosave associations that are loaded in memory (without loading
  270. # any new ones), and return true if any are changed for autosave.
  271. # Returns false if already called to prevent an infinite loop.
  272. 3 def nested_records_changed_for_autosave?
  273. 5542 @_nested_records_changed_for_autosave_already_called ||= false
  274. 5542 return false if @_nested_records_changed_for_autosave_already_called
  275. 4177 begin
  276. 4177 @_nested_records_changed_for_autosave_already_called = true
  277. 4177 self.class._reflections.values.any? do |reflection|
  278. 47377 if reflection.options[:autosave]
  279. 21468 association = association_instance_get(reflection.name)
  280. 21468 association && Array.wrap(association.target).any?(&:changed_for_autosave?)
  281. end
  282. end
  283. ensure
  284. 4177 @_nested_records_changed_for_autosave_already_called = false
  285. end
  286. end
  287. # Validate the association if <tt>:validate</tt> or <tt>:autosave</tt> is
  288. # turned on for the association.
  289. 3 def validate_single_association(reflection)
  290. 8316 association = association_instance_get(reflection.name)
  291. 8316 record = association && association.reader
  292. 8316 association_valid?(reflection, record) if record && (record.changed_for_autosave? || custom_validation_context?)
  293. end
  294. # Validate the associated records if <tt>:validate</tt> or
  295. # <tt>:autosave</tt> is turned on for the association specified by
  296. # +reflection+.
  297. 3 def validate_collection_association(reflection)
  298. 120647 if association = association_instance_get(reflection.name)
  299. 1572 if records = associated_records_to_validate_or_save(association, new_record?, reflection.options[:autosave])
  300. 3068 records.each_with_index { |record, index| association_valid?(reflection, record, index) }
  301. end
  302. end
  303. end
  304. # Returns whether or not the association is valid and applies any errors to
  305. # the parent, <tt>self</tt>, if it wasn't. Skips any <tt>:autosave</tt>
  306. # enabled records if they're marked_for_destruction? or destroyed.
  307. 3 def association_valid?(reflection, record, index = nil)
  308. 2402 return true if record.destroyed? || (reflection.options[:autosave] && record.marked_for_destruction?)
  309. 2119 context = validation_context if custom_validation_context?
  310. 2119 unless valid = record.valid?(context)
  311. 301 if reflection.options[:autosave]
  312. 247 indexed_attribute = !index.nil? && (reflection.options[:index_errors] || ActiveRecord::Base.index_nested_attribute_errors)
  313. 247 record.errors.group_by_attribute.each { |attribute, errors|
  314. 256 attribute = normalize_reflection_attribute(indexed_attribute, reflection, index, attribute)
  315. 256 errors.each { |error|
  316. 259 self.errors.import(
  317. error,
  318. attribute: attribute
  319. )
  320. }
  321. }
  322. else
  323. 54 errors.add(reflection.name)
  324. end
  325. end
  326. 2119 valid
  327. end
  328. 3 def normalize_reflection_attribute(indexed_attribute, reflection, index, attribute)
  329. 256 if indexed_attribute
  330. 12 "#{reflection.name}[#{index}].#{attribute}"
  331. else
  332. 244 "#{reflection.name}.#{attribute}"
  333. end
  334. end
  335. # Is used as an around_save callback to check while saving a collection
  336. # association whether or not the parent was a new record before saving.
  337. 3 def around_save_collection_association
  338. 9857 previously_new_record_before_save = (@new_record_before_save ||= false)
  339. 9857 @new_record_before_save = !previously_new_record_before_save && new_record?
  340. 9857 yield
  341. ensure
  342. 9857 @new_record_before_save = previously_new_record_before_save
  343. end
  344. # Saves any new associated records, or all loaded autosave associations if
  345. # <tt>:autosave</tt> is enabled on the association.
  346. #
  347. # In addition, it destroys all children that were marked for destruction
  348. # with #mark_for_destruction.
  349. #
  350. # This all happens inside a transaction, _if_ the Transactions module is included into
  351. # ActiveRecord::Base after the AutosaveAssociation module, which it does by default.
  352. 3 def save_collection_association(reflection)
  353. 112073 if association = association_instance_get(reflection.name)
  354. 1459 autosave = reflection.options[:autosave]
  355. # By saving the instance variable in a local variable,
  356. # we make the whole callback re-entrant.
  357. 1459 new_record_before_save = @new_record_before_save
  358. # reconstruct the scope now that we know the owner's id
  359. 1459 association.reset_scope
  360. 1459 if records = associated_records_to_validate_or_save(association, new_record_before_save, autosave)
  361. 1459 if autosave
  362. 614 records_to_destroy = records.select(&:marked_for_destruction?)
  363. 713 records_to_destroy.each { |record| association.destroy(record) }
  364. 608 records -= records_to_destroy
  365. end
  366. 1453 records.each do |record|
  367. 1198 next if record.destroyed?
  368. 1198 saved = true
  369. 1198 if autosave != false && (new_record_before_save || record.new_record?)
  370. 931 if autosave
  371. 299 saved = association.insert_record(record, false)
  372. 632 elsif !reflection.nested?
  373. 626 association_saved = association.insert_record(record)
  374. 626 if reflection.validate?
  375. 623 errors.add(reflection.name) unless association_saved
  376. 623 saved = association_saved
  377. end
  378. end
  379. 267 elsif autosave
  380. 264 saved = record.save(validate: false)
  381. end
  382. 1189 raise(RecordInvalid.new(association.owner)) unless saved
  383. end
  384. end
  385. end
  386. end
  387. # Saves the associated record if it's new or <tt>:autosave</tt> is enabled
  388. # on the association.
  389. #
  390. # In addition, it will destroy the association if it was marked for
  391. # destruction with #mark_for_destruction.
  392. #
  393. # This all happens inside a transaction, _if_ the Transactions module is included into
  394. # ActiveRecord::Base after the AutosaveAssociation module, which it does by default.
  395. 3 def save_has_one_association(reflection)
  396. 18225 association = association_instance_get(reflection.name)
  397. 18225 record = association && association.load_target
  398. 18225 if record && !record.destroyed?
  399. 598 autosave = reflection.options[:autosave]
  400. 598 if autosave && record.marked_for_destruction?
  401. 27 record.destroy
  402. 571 elsif autosave != false
  403. 568 key = reflection.options[:primary_key] ? send(reflection.options[:primary_key]) : id
  404. 568 if (autosave && record.changed_for_autosave?) || new_record? || record_changed?(reflection, record, key)
  405. 439 unless reflection.through_reflection
  406. 439 record[reflection.foreign_key] = key
  407. 439 if inverse_reflection = reflection.inverse_of
  408. 403 record.association(inverse_reflection.name).loaded!
  409. end
  410. end
  411. 439 saved = record.save(validate: !autosave)
  412. 424 raise ActiveRecord::Rollback if !saved && autosave
  413. 419 saved
  414. end
  415. end
  416. end
  417. end
  418. # If the record is new or it has changed, returns true.
  419. 3 def record_changed?(reflection, record, key)
  420. 273 record.new_record? ||
  421. association_foreign_key_changed?(reflection, record, key) ||
  422. record.will_save_change_to_attribute?(reflection.foreign_key)
  423. end
  424. 3 def association_foreign_key_changed?(reflection, record, key)
  425. 201 return false if reflection.through_reflection?
  426. 177 record._has_attribute?(reflection.foreign_key) && record._read_attribute(reflection.foreign_key) != key
  427. end
  428. # Saves the associated record if it's new or <tt>:autosave</tt> is enabled.
  429. #
  430. # In addition, it will destroy the association if it was marked for destruction.
  431. 3 def save_belongs_to_association(reflection)
  432. 26919 association = association_instance_get(reflection.name)
  433. 26919 return unless association && association.loaded? && !association.stale_target?
  434. 4245 record = association.load_target
  435. 4245 if record && !record.destroyed?
  436. 3911 autosave = reflection.options[:autosave]
  437. 3911 if autosave && record.marked_for_destruction?
  438. 30 self[reflection.foreign_key] = nil
  439. 30 record.destroy
  440. 3881 elsif autosave != false
  441. 3878 saved = record.save(validate: !autosave) if record.new_record? || (autosave && record.changed_for_autosave?)
  442. 3863 if association.updated?
  443. 1933 association_id = record.send(reflection.options[:primary_key] || :id)
  444. 1933 self[reflection.foreign_key] = association_id
  445. 1933 association.loaded!
  446. end
  447. 3863 saved if autosave
  448. end
  449. end
  450. end
  451. 3 def custom_validation_context?
  452. 4553 validation_context && [:create, :update].exclude?(validation_context)
  453. end
  454. 3 def _ensure_no_duplicate_errors
  455. 12137 errors.uniq!
  456. end
  457. end
  458. end

lib/active_record/base.rb

100.0% lines covered

58 relevant lines. 58 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 require "active_support/benchmarkable"
  3. 3 require "active_support/dependencies"
  4. 3 require "active_support/descendants_tracker"
  5. 3 require "active_support/time"
  6. 3 require "active_support/core_ext/class/subclasses"
  7. 3 require "active_record/log_subscriber"
  8. 3 require "active_record/explain_subscriber"
  9. 3 require "active_record/relation/delegation"
  10. 3 require "active_record/attributes"
  11. 3 require "active_record/type_caster"
  12. 3 require "active_record/database_configurations"
  13. 3 module ActiveRecord #:nodoc:
  14. # = Active Record
  15. #
  16. # Active Record objects don't specify their attributes directly, but rather infer them from
  17. # the table definition with which they're linked. Adding, removing, and changing attributes
  18. # and their type is done directly in the database. Any change is instantly reflected in the
  19. # Active Record objects. The mapping that binds a given Active Record class to a certain
  20. # database table will happen automatically in most common cases, but can be overwritten for the uncommon ones.
  21. #
  22. # See the mapping rules in table_name and the full example in link:files/activerecord/README_rdoc.html for more insight.
  23. #
  24. # == Creation
  25. #
  26. # Active Records accept constructor parameters either in a hash or as a block. The hash
  27. # method is especially useful when you're receiving the data from somewhere else, like an
  28. # HTTP request. It works like this:
  29. #
  30. # user = User.new(name: "David", occupation: "Code Artist")
  31. # user.name # => "David"
  32. #
  33. # You can also use block initialization:
  34. #
  35. # user = User.new do |u|
  36. # u.name = "David"
  37. # u.occupation = "Code Artist"
  38. # end
  39. #
  40. # And of course you can just create a bare object and specify the attributes after the fact:
  41. #
  42. # user = User.new
  43. # user.name = "David"
  44. # user.occupation = "Code Artist"
  45. #
  46. # == Conditions
  47. #
  48. # Conditions can either be specified as a string, array, or hash representing the WHERE-part of an SQL statement.
  49. # The array form is to be used when the condition input is tainted and requires sanitization. The string form can
  50. # be used for statements that don't involve tainted data. The hash form works much like the array form, except
  51. # only equality and range is possible. Examples:
  52. #
  53. # class User < ActiveRecord::Base
  54. # def self.authenticate_unsafely(user_name, password)
  55. # where("user_name = '#{user_name}' AND password = '#{password}'").first
  56. # end
  57. #
  58. # def self.authenticate_safely(user_name, password)
  59. # where("user_name = ? AND password = ?", user_name, password).first
  60. # end
  61. #
  62. # def self.authenticate_safely_simply(user_name, password)
  63. # where(user_name: user_name, password: password).first
  64. # end
  65. # end
  66. #
  67. # The <tt>authenticate_unsafely</tt> method inserts the parameters directly into the query
  68. # and is thus susceptible to SQL-injection attacks if the <tt>user_name</tt> and +password+
  69. # parameters come directly from an HTTP request. The <tt>authenticate_safely</tt> and
  70. # <tt>authenticate_safely_simply</tt> both will sanitize the <tt>user_name</tt> and +password+
  71. # before inserting them in the query, which will ensure that an attacker can't escape the
  72. # query and fake the login (or worse).
  73. #
  74. # When using multiple parameters in the conditions, it can easily become hard to read exactly
  75. # what the fourth or fifth question mark is supposed to represent. In those cases, you can
  76. # resort to named bind variables instead. That's done by replacing the question marks with
  77. # symbols and supplying a hash with values for the matching symbol keys:
  78. #
  79. # Company.where(
  80. # "id = :id AND name = :name AND division = :division AND created_at > :accounting_date",
  81. # { id: 3, name: "37signals", division: "First", accounting_date: '2005-01-01' }
  82. # ).first
  83. #
  84. # Similarly, a simple hash without a statement will generate conditions based on equality with the SQL AND
  85. # operator. For instance:
  86. #
  87. # Student.where(first_name: "Harvey", status: 1)
  88. # Student.where(params[:student])
  89. #
  90. # A range may be used in the hash to use the SQL BETWEEN operator:
  91. #
  92. # Student.where(grade: 9..12)
  93. #
  94. # An array may be used in the hash to use the SQL IN operator:
  95. #
  96. # Student.where(grade: [9,11,12])
  97. #
  98. # When joining tables, nested hashes or keys written in the form 'table_name.column_name'
  99. # can be used to qualify the table name of a particular condition. For instance:
  100. #
  101. # Student.joins(:schools).where(schools: { category: 'public' })
  102. # Student.joins(:schools).where('schools.category' => 'public' )
  103. #
  104. # == Overwriting default accessors
  105. #
  106. # All column values are automatically available through basic accessors on the Active Record
  107. # object, but sometimes you want to specialize this behavior. This can be done by overwriting
  108. # the default accessors (using the same name as the attribute) and calling
  109. # +super+ to actually change things.
  110. #
  111. # class Song < ActiveRecord::Base
  112. # # Uses an integer of seconds to hold the length of the song
  113. #
  114. # def length=(minutes)
  115. # super(minutes.to_i * 60)
  116. # end
  117. #
  118. # def length
  119. # super / 60
  120. # end
  121. # end
  122. #
  123. # == Attribute query methods
  124. #
  125. # In addition to the basic accessors, query methods are also automatically available on the Active Record object.
  126. # Query methods allow you to test whether an attribute value is present.
  127. # Additionally, when dealing with numeric values, a query method will return false if the value is zero.
  128. #
  129. # For example, an Active Record User with the <tt>name</tt> attribute has a <tt>name?</tt> method that you can call
  130. # to determine whether the user has a name:
  131. #
  132. # user = User.new(name: "David")
  133. # user.name? # => true
  134. #
  135. # anonymous = User.new(name: "")
  136. # anonymous.name? # => false
  137. #
  138. # == Accessing attributes before they have been typecasted
  139. #
  140. # Sometimes you want to be able to read the raw attribute data without having the column-determined
  141. # typecast run its course first. That can be done by using the <tt><attribute>_before_type_cast</tt>
  142. # accessors that all attributes have. For example, if your Account model has a <tt>balance</tt> attribute,
  143. # you can call <tt>account.balance_before_type_cast</tt> or <tt>account.id_before_type_cast</tt>.
  144. #
  145. # This is especially useful in validation situations where the user might supply a string for an
  146. # integer field and you want to display the original string back in an error message. Accessing the
  147. # attribute normally would typecast the string to 0, which isn't what you want.
  148. #
  149. # == Dynamic attribute-based finders
  150. #
  151. # Dynamic attribute-based finders are a mildly deprecated way of getting (and/or creating) objects
  152. # by simple queries without turning to SQL. They work by appending the name of an attribute
  153. # to <tt>find_by_</tt> like <tt>Person.find_by_user_name</tt>.
  154. # Instead of writing <tt>Person.find_by(user_name: user_name)</tt>, you can use
  155. # <tt>Person.find_by_user_name(user_name)</tt>.
  156. #
  157. # It's possible to add an exclamation point (!) on the end of the dynamic finders to get them to raise an
  158. # ActiveRecord::RecordNotFound error if they do not return any records,
  159. # like <tt>Person.find_by_last_name!</tt>.
  160. #
  161. # It's also possible to use multiple attributes in the same <tt>find_by_</tt> by separating them with
  162. # "_and_".
  163. #
  164. # Person.find_by(user_name: user_name, password: password)
  165. # Person.find_by_user_name_and_password(user_name, password) # with dynamic finder
  166. #
  167. # It's even possible to call these dynamic finder methods on relations and named scopes.
  168. #
  169. # Payment.order("created_on").find_by_amount(50)
  170. #
  171. # == Saving arrays, hashes, and other non-mappable objects in text columns
  172. #
  173. # Active Record can serialize any object in text columns using YAML. To do so, you must
  174. # specify this with a call to the class method
  175. # {serialize}[rdoc-ref:AttributeMethods::Serialization::ClassMethods#serialize].
  176. # This makes it possible to store arrays, hashes, and other non-mappable objects without doing
  177. # any additional work.
  178. #
  179. # class User < ActiveRecord::Base
  180. # serialize :preferences
  181. # end
  182. #
  183. # user = User.create(preferences: { "background" => "black", "display" => large })
  184. # User.find(user.id).preferences # => { "background" => "black", "display" => large }
  185. #
  186. # You can also specify a class option as the second parameter that'll raise an exception
  187. # if a serialized object is retrieved as a descendant of a class not in the hierarchy.
  188. #
  189. # class User < ActiveRecord::Base
  190. # serialize :preferences, Hash
  191. # end
  192. #
  193. # user = User.create(preferences: %w( one two three ))
  194. # User.find(user.id).preferences # raises SerializationTypeMismatch
  195. #
  196. # When you specify a class option, the default value for that attribute will be a new
  197. # instance of that class.
  198. #
  199. # class User < ActiveRecord::Base
  200. # serialize :preferences, OpenStruct
  201. # end
  202. #
  203. # user = User.new
  204. # user.preferences.theme_color = "red"
  205. #
  206. #
  207. # == Single table inheritance
  208. #
  209. # Active Record allows inheritance by storing the name of the class in a
  210. # column that is named "type" by default. See ActiveRecord::Inheritance for
  211. # more details.
  212. #
  213. # == Connection to multiple databases in different models
  214. #
  215. # Connections are usually created through
  216. # {ActiveRecord::Base.establish_connection}[rdoc-ref:ConnectionHandling#establish_connection] and retrieved
  217. # by ActiveRecord::Base.connection. All classes inheriting from ActiveRecord::Base will use this
  218. # connection. But you can also set a class-specific connection. For example, if Course is an
  219. # ActiveRecord::Base, but resides in a different database, you can just say <tt>Course.establish_connection</tt>
  220. # and Course and all of its subclasses will use this connection instead.
  221. #
  222. # This feature is implemented by keeping a connection pool in ActiveRecord::Base that is
  223. # a hash indexed by the class. If a connection is requested, the
  224. # {ActiveRecord::Base.retrieve_connection}[rdoc-ref:ConnectionHandling#retrieve_connection] method
  225. # will go up the class-hierarchy until a connection is found in the connection pool.
  226. #
  227. # == Exceptions
  228. #
  229. # * ActiveRecordError - Generic error class and superclass of all other errors raised by Active Record.
  230. # * AdapterNotSpecified - The configuration hash used in
  231. # {ActiveRecord::Base.establish_connection}[rdoc-ref:ConnectionHandling#establish_connection]
  232. # didn't include an <tt>:adapter</tt> key.
  233. # * AdapterNotFound - The <tt>:adapter</tt> key used in
  234. # {ActiveRecord::Base.establish_connection}[rdoc-ref:ConnectionHandling#establish_connection]
  235. # specified a non-existent adapter
  236. # (or a bad spelling of an existing one).
  237. # * AssociationTypeMismatch - The object assigned to the association wasn't of the type
  238. # specified in the association definition.
  239. # * AttributeAssignmentError - An error occurred while doing a mass assignment through the
  240. # {ActiveRecord::Base#attributes=}[rdoc-ref:AttributeAssignment#attributes=] method.
  241. # You can inspect the +attribute+ property of the exception object to determine which attribute
  242. # triggered the error.
  243. # * ConnectionNotEstablished - No connection has been established.
  244. # Use {ActiveRecord::Base.establish_connection}[rdoc-ref:ConnectionHandling#establish_connection] before querying.
  245. # * MultiparameterAssignmentErrors - Collection of errors that occurred during a mass assignment using the
  246. # {ActiveRecord::Base#attributes=}[rdoc-ref:AttributeAssignment#attributes=] method.
  247. # The +errors+ property of this exception contains an array of
  248. # AttributeAssignmentError
  249. # objects that should be inspected to determine which attributes triggered the errors.
  250. # * RecordInvalid - raised by {ActiveRecord::Base#save!}[rdoc-ref:Persistence#save!] and
  251. # {ActiveRecord::Base.create!}[rdoc-ref:Persistence::ClassMethods#create!]
  252. # when the record is invalid.
  253. # * RecordNotFound - No record responded to the {ActiveRecord::Base.find}[rdoc-ref:FinderMethods#find] method.
  254. # Either the row with the given ID doesn't exist or the row didn't meet the additional restrictions.
  255. # Some {ActiveRecord::Base.find}[rdoc-ref:FinderMethods#find] calls do not raise this exception to signal
  256. # nothing was found, please check its documentation for further details.
  257. # * SerializationTypeMismatch - The serialized object wasn't of the class specified as the second parameter.
  258. # * StatementInvalid - The database server rejected the SQL statement. The precise error is added in the message.
  259. #
  260. # *Note*: The attributes listed are class-level attributes (accessible from both the class and instance level).
  261. # So it's possible to assign a logger to the class through <tt>Base.logger=</tt> which will then be used by all
  262. # instances in the current object space.
  263. 3 class Base
  264. 3 extend ActiveModel::Naming
  265. 3 extend ActiveSupport::Benchmarkable
  266. 3 extend ActiveSupport::DescendantsTracker
  267. 3 extend ConnectionHandling
  268. 3 extend QueryCache::ClassMethods
  269. 3 extend Querying
  270. 3 extend Translation
  271. 3 extend DynamicMatchers
  272. 3 extend DelegatedType
  273. 3 extend Explain
  274. 3 extend Enum
  275. 3 extend Delegation::DelegateCache
  276. 3 extend Aggregations::ClassMethods
  277. 3 include Core
  278. 3 include Persistence
  279. 3 include ReadonlyAttributes
  280. 3 include ModelSchema
  281. 3 include Inheritance
  282. 3 include Scoping
  283. 3 include Sanitization
  284. 3 include AttributeAssignment
  285. 3 include ActiveModel::Conversion
  286. 3 include Integration
  287. 3 include Validations
  288. 3 include CounterCache
  289. 3 include Attributes
  290. 3 include Locking::Optimistic
  291. 3 include Locking::Pessimistic
  292. 3 include AttributeMethods
  293. 3 include Callbacks
  294. 3 include Timestamp
  295. 3 include Associations
  296. 3 include ActiveModel::SecurePassword
  297. 3 include AutosaveAssociation
  298. 3 include NestedAttributes
  299. 3 include Transactions
  300. 3 include TouchLater
  301. 3 include NoTouching
  302. 3 include Reflection
  303. 3 include Serialization
  304. 3 include Store
  305. 3 include SecureToken
  306. 3 include SignedId
  307. 3 include Suppressor
  308. end
  309. 3 ActiveSupport.run_load_hooks(:active_record, Base)
  310. end

lib/active_record/callbacks.rb

100.0% lines covered

29 relevant lines. 29 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 module ActiveRecord
  3. # = Active Record \Callbacks
  4. #
  5. # \Callbacks are hooks into the life cycle of an Active Record object that allow you to trigger logic
  6. # before or after a change in the object state. This can be used to make sure that associated and
  7. # dependent objects are deleted when {ActiveRecord::Base#destroy}[rdoc-ref:Persistence#destroy] is called (by overwriting +before_destroy+) or
  8. # to massage attributes before they're validated (by overwriting +before_validation+).
  9. # As an example of the callbacks initiated, consider the {ActiveRecord::Base#save}[rdoc-ref:Persistence#save] call for a new record:
  10. #
  11. # * (-) <tt>save</tt>
  12. # * (-) <tt>valid</tt>
  13. # * (1) <tt>before_validation</tt>
  14. # * (-) <tt>validate</tt>
  15. # * (2) <tt>after_validation</tt>
  16. # * (3) <tt>before_save</tt>
  17. # * (4) <tt>before_create</tt>
  18. # * (-) <tt>create</tt>
  19. # * (5) <tt>after_create</tt>
  20. # * (6) <tt>after_save</tt>
  21. # * (7) <tt>after_commit</tt>
  22. #
  23. # Also, an <tt>after_rollback</tt> callback can be configured to be triggered whenever a rollback is issued.
  24. # Check out ActiveRecord::Transactions for more details about <tt>after_commit</tt> and
  25. # <tt>after_rollback</tt>.
  26. #
  27. # Additionally, an <tt>after_touch</tt> callback is triggered whenever an
  28. # object is touched.
  29. #
  30. # Lastly an <tt>after_find</tt> and <tt>after_initialize</tt> callback is triggered for each object that
  31. # is found and instantiated by a finder, with <tt>after_initialize</tt> being triggered after new objects
  32. # are instantiated as well.
  33. #
  34. # There are nineteen callbacks in total, which give a lot of control over how to react and prepare for each state in the
  35. # Active Record life cycle. The sequence for calling {ActiveRecord::Base#save}[rdoc-ref:Persistence#save] for an existing record is similar,
  36. # except that each <tt>_create</tt> callback is replaced by the corresponding <tt>_update</tt> callback.
  37. #
  38. # Examples:
  39. # class CreditCard < ActiveRecord::Base
  40. # # Strip everything but digits, so the user can specify "555 234 34" or
  41. # # "5552-3434" and both will mean "55523434"
  42. # before_validation(on: :create) do
  43. # self.number = number.gsub(/[^0-9]/, "") if attribute_present?("number")
  44. # end
  45. # end
  46. #
  47. # class Subscription < ActiveRecord::Base
  48. # before_create :record_signup
  49. #
  50. # private
  51. # def record_signup
  52. # self.signed_up_on = Date.today
  53. # end
  54. # end
  55. #
  56. # class Firm < ActiveRecord::Base
  57. # # Disables access to the system, for associated clients and people when the firm is destroyed
  58. # before_destroy { |record| Person.where(firm_id: record.id).update_all(access: 'disabled') }
  59. # before_destroy { |record| Client.where(client_of: record.id).update_all(access: 'disabled') }
  60. # end
  61. #
  62. # == Inheritable callback queues
  63. #
  64. # Besides the overwritable callback methods, it's also possible to register callbacks through the
  65. # use of the callback macros. Their main advantage is that the macros add behavior into a callback
  66. # queue that is kept intact through an inheritance hierarchy.
  67. #
  68. # class Topic < ActiveRecord::Base
  69. # before_destroy :destroy_author
  70. # end
  71. #
  72. # class Reply < Topic
  73. # before_destroy :destroy_readers
  74. # end
  75. #
  76. # When <tt>Topic#destroy</tt> is run only +destroy_author+ is called. When <tt>Reply#destroy</tt> is
  77. # run, both +destroy_author+ and +destroy_readers+ are called.
  78. #
  79. # *IMPORTANT:* In order for inheritance to work for the callback queues, you must specify the
  80. # callbacks before specifying the associations. Otherwise, you might trigger the loading of a
  81. # child before the parent has registered the callbacks and they won't be inherited.
  82. #
  83. # == Types of callbacks
  84. #
  85. # There are three types of callbacks accepted by the callback macros: method references (symbol), callback objects,
  86. # inline methods (using a proc). Method references and callback objects are the recommended approaches,
  87. # inline methods using a proc are sometimes appropriate (such as for creating mix-ins).
  88. #
  89. # The method reference callbacks work by specifying a protected or private method available in the object, like this:
  90. #
  91. # class Topic < ActiveRecord::Base
  92. # before_destroy :delete_parents
  93. #
  94. # private
  95. # def delete_parents
  96. # self.class.delete_by(parent_id: id)
  97. # end
  98. # end
  99. #
  100. # The callback objects have methods named after the callback called with the record as the only parameter, such as:
  101. #
  102. # class BankAccount < ActiveRecord::Base
  103. # before_save EncryptionWrapper.new
  104. # after_save EncryptionWrapper.new
  105. # after_initialize EncryptionWrapper.new
  106. # end
  107. #
  108. # class EncryptionWrapper
  109. # def before_save(record)
  110. # record.credit_card_number = encrypt(record.credit_card_number)
  111. # end
  112. #
  113. # def after_save(record)
  114. # record.credit_card_number = decrypt(record.credit_card_number)
  115. # end
  116. #
  117. # alias_method :after_initialize, :after_save
  118. #
  119. # private
  120. # def encrypt(value)
  121. # # Secrecy is committed
  122. # end
  123. #
  124. # def decrypt(value)
  125. # # Secrecy is unveiled
  126. # end
  127. # end
  128. #
  129. # So you specify the object you want to be messaged on a given callback. When that callback is triggered, the object has
  130. # a method by the name of the callback messaged. You can make these callbacks more flexible by passing in other
  131. # initialization data such as the name of the attribute to work with:
  132. #
  133. # class BankAccount < ActiveRecord::Base
  134. # before_save EncryptionWrapper.new("credit_card_number")
  135. # after_save EncryptionWrapper.new("credit_card_number")
  136. # after_initialize EncryptionWrapper.new("credit_card_number")
  137. # end
  138. #
  139. # class EncryptionWrapper
  140. # def initialize(attribute)
  141. # @attribute = attribute
  142. # end
  143. #
  144. # def before_save(record)
  145. # record.send("#{@attribute}=", encrypt(record.send("#{@attribute}")))
  146. # end
  147. #
  148. # def after_save(record)
  149. # record.send("#{@attribute}=", decrypt(record.send("#{@attribute}")))
  150. # end
  151. #
  152. # alias_method :after_initialize, :after_save
  153. #
  154. # private
  155. # def encrypt(value)
  156. # # Secrecy is committed
  157. # end
  158. #
  159. # def decrypt(value)
  160. # # Secrecy is unveiled
  161. # end
  162. # end
  163. #
  164. # == <tt>before_validation*</tt> returning statements
  165. #
  166. # If the +before_validation+ callback throws +:abort+, the process will be
  167. # aborted and {ActiveRecord::Base#save}[rdoc-ref:Persistence#save] will return +false+.
  168. # If {ActiveRecord::Base#save!}[rdoc-ref:Persistence#save!] is called it will raise an ActiveRecord::RecordInvalid exception.
  169. # Nothing will be appended to the errors object.
  170. #
  171. # == Canceling callbacks
  172. #
  173. # If a <tt>before_*</tt> callback throws +:abort+, all the later callbacks and
  174. # the associated action are cancelled.
  175. # Callbacks are generally run in the order they are defined, with the exception of callbacks defined as
  176. # methods on the model, which are called last.
  177. #
  178. # == Ordering callbacks
  179. #
  180. # Sometimes application code requires that callbacks execute in a specific order. For example, a +before_destroy+
  181. # callback (+log_children+ in this case) should be executed before records in the +children+ association are destroyed by the
  182. # <tt>dependent: :destroy</tt> option.
  183. #
  184. # Let's look at the code below:
  185. #
  186. # class Topic < ActiveRecord::Base
  187. # has_many :children, dependent: :destroy
  188. #
  189. # before_destroy :log_children
  190. #
  191. # private
  192. # def log_children
  193. # # Child processing
  194. # end
  195. # end
  196. #
  197. # In this case, the problem is that when the +before_destroy+ callback is executed, records in the +children+ association no
  198. # longer exist because the {ActiveRecord::Base#destroy}[rdoc-ref:Persistence#destroy] callback was executed first.
  199. # You can use the +prepend+ option on the +before_destroy+ callback to avoid this.
  200. #
  201. # class Topic < ActiveRecord::Base
  202. # has_many :children, dependent: :destroy
  203. #
  204. # before_destroy :log_children, prepend: true
  205. #
  206. # private
  207. # def log_children
  208. # # Child processing
  209. # end
  210. # end
  211. #
  212. # This way, the +before_destroy+ is executed before the <tt>dependent: :destroy</tt> is called, and the data is still available.
  213. #
  214. # Also, there are cases when you want several callbacks of the same type to
  215. # be executed in order.
  216. #
  217. # For example:
  218. #
  219. # class Topic < ActiveRecord::Base
  220. # has_many :children
  221. #
  222. # after_save :log_children
  223. # after_save :do_something_else
  224. #
  225. # private
  226. #
  227. # def log_children
  228. # # Child processing
  229. # end
  230. #
  231. # def do_something_else
  232. # # Something else
  233. # end
  234. # end
  235. #
  236. # In this case the +log_children+ is executed before +do_something_else+.
  237. # The same applies to all non-transactional callbacks.
  238. #
  239. # As seen below, in case there are multiple transactional callbacks the order
  240. # is reversed.
  241. #
  242. # For example:
  243. #
  244. # class Topic < ActiveRecord::Base
  245. # has_many :children
  246. #
  247. # after_commit :log_children
  248. # after_commit :do_something_else
  249. #
  250. # private
  251. #
  252. # def log_children
  253. # # Child processing
  254. # end
  255. #
  256. # def do_something_else
  257. # # Something else
  258. # end
  259. # end
  260. #
  261. # In this case the +do_something_else+ is executed before +log_children+.
  262. #
  263. # == \Transactions
  264. #
  265. # The entire callback chain of a {#save}[rdoc-ref:Persistence#save], {#save!}[rdoc-ref:Persistence#save!],
  266. # or {#destroy}[rdoc-ref:Persistence#destroy] call runs within a transaction. That includes <tt>after_*</tt> hooks.
  267. # If everything goes fine a +COMMIT+ is executed once the chain has been completed.
  268. #
  269. # If a <tt>before_*</tt> callback cancels the action a +ROLLBACK+ is issued. You
  270. # can also trigger a +ROLLBACK+ raising an exception in any of the callbacks,
  271. # including <tt>after_*</tt> hooks. Note, however, that in that case the client
  272. # needs to be aware of it because an ordinary {#save}[rdoc-ref:Persistence#save] will raise such exception
  273. # instead of quietly returning +false+.
  274. #
  275. # == Debugging callbacks
  276. #
  277. # The callback chain is accessible via the <tt>_*_callbacks</tt> method on an object. Active Model \Callbacks support
  278. # <tt>:before</tt>, <tt>:after</tt> and <tt>:around</tt> as values for the <tt>kind</tt> property. The <tt>kind</tt> property
  279. # defines what part of the chain the callback runs in.
  280. #
  281. # To find all callbacks in the +before_save+ callback chain:
  282. #
  283. # Topic._save_callbacks.select { |cb| cb.kind.eql?(:before) }
  284. #
  285. # Returns an array of callback objects that form the +before_save+ chain.
  286. #
  287. # To further check if the before_save chain contains a proc defined as <tt>rest_when_dead</tt> use the <tt>filter</tt> property of the callback object:
  288. #
  289. # Topic._save_callbacks.select { |cb| cb.kind.eql?(:before) }.collect(&:filter).include?(:rest_when_dead)
  290. #
  291. # Returns true or false depending on whether the proc is contained in the +before_save+ callback chain on a Topic model.
  292. #
  293. 3 module Callbacks
  294. 3 extend ActiveSupport::Concern
  295. 3 CALLBACKS = [
  296. :after_initialize, :after_find, :after_touch, :before_validation, :after_validation,
  297. :before_save, :around_save, :after_save, :before_create, :around_create,
  298. :after_create, :before_update, :around_update, :after_update,
  299. :before_destroy, :around_destroy, :after_destroy, :after_commit, :after_rollback
  300. ]
  301. 3 module ClassMethods # :nodoc:
  302. 3 include ActiveModel::Callbacks
  303. end
  304. 3 included do
  305. 3 include ActiveModel::Validations::Callbacks
  306. 3 define_model_callbacks :initialize, :find, :touch, only: :after
  307. 3 define_model_callbacks :save, :create, :update, :destroy
  308. end
  309. 3 def destroy #:nodoc:
  310. 1074 @_destroy_callback_already_called ||= false
  311. 1074 return if @_destroy_callback_already_called
  312. 1062 @_destroy_callback_already_called = true
  313. 2039 _run_destroy_callbacks { super }
  314. rescue RecordNotDestroyed => e
  315. 6 @_association_destroy_exception = e
  316. 6 false
  317. ensure
  318. 1074 @_destroy_callback_already_called = false
  319. end
  320. 3 def touch(*, **) #:nodoc:
  321. 1074 _run_touch_callbacks { super }
  322. end
  323. 3 def increment!(attribute, by = 1, touch: nil) # :nodoc:
  324. 528 touch ? _run_touch_callbacks { super } : super
  325. end
  326. 3 private
  327. 3 def create_or_update(**)
  328. 31958 _run_save_callbacks { super }
  329. end
  330. 3 def _create_record
  331. 24891 _run_create_callbacks { super }
  332. end
  333. 3 def _update_record
  334. 6832 _run_update_callbacks { super }
  335. end
  336. end
  337. end

lib/active_record/coders/json.rb

100.0% lines covered

7 relevant lines. 7 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 module ActiveRecord
  3. 3 module Coders # :nodoc:
  4. 3 class JSON # :nodoc:
  5. 3 def self.dump(obj)
  6. 18 ActiveSupport::JSON.encode(obj)
  7. end
  8. 3 def self.load(json)
  9. 96 ActiveSupport::JSON.decode(json) unless json.blank?
  10. end
  11. end
  12. end
  13. end

lib/active_record/coders/yaml_column.rb

100.0% lines covered

27 relevant lines. 27 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 require "yaml"
  3. 3 module ActiveRecord
  4. 3 module Coders # :nodoc:
  5. 3 class YAMLColumn # :nodoc:
  6. 3 attr_accessor :object_class
  7. 3 def initialize(attr_name, object_class = Object)
  8. 250 @attr_name = attr_name
  9. 250 @object_class = object_class
  10. 250 check_arity_of_constructor
  11. end
  12. 3 def dump(obj)
  13. 3085 return if obj.nil?
  14. 3085 assert_valid_value(obj, action: "dump")
  15. 3082 YAML.dump obj
  16. end
  17. 3 def load(yaml)
  18. 10030 return object_class.new if object_class != Object && yaml.nil?
  19. 9495 return yaml unless yaml.is_a?(String) && yaml.start_with?("---")
  20. 1665 obj = YAML.load(yaml)
  21. 1659 assert_valid_value(obj, action: "load")
  22. 1650 obj ||= object_class.new if object_class != Object
  23. 1650 obj
  24. end
  25. 3 def assert_valid_value(obj, action:)
  26. 5305 unless obj.nil? || obj.is_a?(object_class)
  27. 15 raise SerializationTypeMismatch,
  28. "can't #{action} `#{@attr_name}`: was supposed to be a #{object_class}, but was a #{obj.class}. -- #{obj.inspect}"
  29. end
  30. end
  31. 3 private
  32. 3 def check_arity_of_constructor
  33. 250 load(nil)
  34. rescue ArgumentError
  35. 3 raise ArgumentError, "Cannot serialize #{object_class}. Classes passed to `serialize` must have a 0 argument constructor."
  36. end
  37. end
  38. end
  39. end

lib/active_record/connection_adapters.rb

100.0% lines covered

34 relevant lines. 34 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 module ActiveRecord
  3. 3 module ConnectionAdapters
  4. 3 extend ActiveSupport::Autoload
  5. 3 eager_autoload do
  6. 3 autoload :AbstractAdapter
  7. end
  8. 3 autoload :Column
  9. 3 autoload :PoolConfig
  10. 3 autoload :PoolManager
  11. 3 autoload_at "active_record/connection_adapters/abstract/schema_definitions" do
  12. 3 autoload :IndexDefinition
  13. 3 autoload :ColumnDefinition
  14. 3 autoload :ChangeColumnDefinition
  15. 3 autoload :ForeignKeyDefinition
  16. 3 autoload :CheckConstraintDefinition
  17. 3 autoload :TableDefinition
  18. 3 autoload :Table
  19. 3 autoload :AlterTable
  20. 3 autoload :ReferenceDefinition
  21. end
  22. 3 autoload_at "active_record/connection_adapters/abstract/connection_pool" do
  23. 3 autoload :ConnectionHandler
  24. end
  25. 3 autoload_under "abstract" do
  26. 3 autoload :SchemaStatements
  27. 3 autoload :DatabaseStatements
  28. 3 autoload :DatabaseLimits
  29. 3 autoload :Quoting
  30. 3 autoload :ConnectionPool
  31. 3 autoload :QueryCache
  32. 3 autoload :Savepoints
  33. end
  34. 3 autoload_at "active_record/connection_adapters/abstract/transaction" do
  35. 3 autoload :TransactionManager
  36. 3 autoload :NullTransaction
  37. 3 autoload :RealTransaction
  38. 3 autoload :SavepointTransaction
  39. 3 autoload :TransactionState
  40. end
  41. end
  42. end

lib/active_record/connection_adapters/abstract/connection_pool.rb

97.13% lines covered

453 relevant lines. 440 lines covered and 13 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 require "thread"
  3. 3 require "concurrent/map"
  4. 3 require "monitor"
  5. 3 require "weakref"
  6. 3 module ActiveRecord
  7. # Raised when a connection could not be obtained within the connection
  8. # acquisition timeout period: because max connections in pool
  9. # are in use.
  10. 3 class ConnectionTimeoutError < ConnectionNotEstablished
  11. end
  12. # Raised when a pool was unable to get ahold of all its connections
  13. # to perform a "group" action such as
  14. # {ActiveRecord::Base.connection_pool.disconnect!}[rdoc-ref:ConnectionAdapters::ConnectionPool#disconnect!]
  15. # or {ActiveRecord::Base.clear_reloadable_connections!}[rdoc-ref:ConnectionAdapters::ConnectionHandler#clear_reloadable_connections!].
  16. 3 class ExclusiveConnectionTimeoutError < ConnectionTimeoutError
  17. end
  18. 3 module ConnectionAdapters
  19. 3 module AbstractPool # :nodoc:
  20. 3 def get_schema_cache(connection)
  21. 183851 self.schema_cache ||= SchemaCache.new(connection)
  22. 183851 schema_cache.connection = connection
  23. 183851 schema_cache
  24. end
  25. 3 def set_schema_cache(cache)
  26. self.schema_cache = cache
  27. end
  28. end
  29. 3 class NullPool # :nodoc:
  30. 3 include ConnectionAdapters::AbstractPool
  31. 3 attr_accessor :schema_cache
  32. end
  33. # Connection pool base class for managing Active Record database
  34. # connections.
  35. #
  36. # == Introduction
  37. #
  38. # A connection pool synchronizes thread access to a limited number of
  39. # database connections. The basic idea is that each thread checks out a
  40. # database connection from the pool, uses that connection, and checks the
  41. # connection back in. ConnectionPool is completely thread-safe, and will
  42. # ensure that a connection cannot be used by two threads at the same time,
  43. # as long as ConnectionPool's contract is correctly followed. It will also
  44. # handle cases in which there are more threads than connections: if all
  45. # connections have been checked out, and a thread tries to checkout a
  46. # connection anyway, then ConnectionPool will wait until some other thread
  47. # has checked in a connection.
  48. #
  49. # == Obtaining (checking out) a connection
  50. #
  51. # Connections can be obtained and used from a connection pool in several
  52. # ways:
  53. #
  54. # 1. Simply use {ActiveRecord::Base.connection}[rdoc-ref:ConnectionHandling.connection]
  55. # as with Active Record 2.1 and
  56. # earlier (pre-connection-pooling). Eventually, when you're done with
  57. # the connection(s) and wish it to be returned to the pool, you call
  58. # {ActiveRecord::Base.clear_active_connections!}[rdoc-ref:ConnectionAdapters::ConnectionHandler#clear_active_connections!].
  59. # This will be the default behavior for Active Record when used in conjunction with
  60. # Action Pack's request handling cycle.
  61. # 2. Manually check out a connection from the pool with
  62. # {ActiveRecord::Base.connection_pool.checkout}[rdoc-ref:#checkout]. You are responsible for
  63. # returning this connection to the pool when finished by calling
  64. # {ActiveRecord::Base.connection_pool.checkin(connection)}[rdoc-ref:#checkin].
  65. # 3. Use {ActiveRecord::Base.connection_pool.with_connection(&block)}[rdoc-ref:#with_connection], which
  66. # obtains a connection, yields it as the sole argument to the block,
  67. # and returns it to the pool after the block completes.
  68. #
  69. # Connections in the pool are actually AbstractAdapter objects (or objects
  70. # compatible with AbstractAdapter's interface).
  71. #
  72. # == Options
  73. #
  74. # There are several connection-pooling-related options that you can add to
  75. # your database connection configuration:
  76. #
  77. # * +pool+: maximum number of connections the pool may manage (default 5).
  78. # * +idle_timeout+: number of seconds that a connection will be kept
  79. # unused in the pool before it is automatically disconnected (default
  80. # 300 seconds). Set this to zero to keep connections forever.
  81. # * +checkout_timeout+: number of seconds to wait for a connection to
  82. # become available before giving up and raising a timeout error (default
  83. # 5 seconds).
  84. #
  85. #--
  86. # Synchronization policy:
  87. # * all public methods can be called outside +synchronize+
  88. # * access to these instance variables needs to be in +synchronize+:
  89. # * @connections
  90. # * @now_connecting
  91. # * private methods that require being called in a +synchronize+ blocks
  92. # are now explicitly documented
  93. 3 class ConnectionPool
  94. # Threadsafe, fair, LIFO queue. Meant to be used by ConnectionPool
  95. # with which it shares a Monitor.
  96. 3 class Queue
  97. 3 def initialize(lock = Monitor.new)
  98. 975 @lock = lock
  99. 975 @cond = @lock.new_cond
  100. 975 @num_waiting = 0
  101. 975 @queue = []
  102. end
  103. # Test if any threads are currently waiting on the queue.
  104. 3 def any_waiting?
  105. 14 synchronize do
  106. 14 @num_waiting > 0
  107. end
  108. end
  109. # Returns the number of threads currently waiting on this
  110. # queue.
  111. 3 def num_waiting
  112. 866 synchronize do
  113. 866 @num_waiting
  114. end
  115. end
  116. # Add +element+ to the queue. Never blocks.
  117. 3 def add(element)
  118. 112930 synchronize do
  119. 112930 @queue.push element
  120. 112930 @cond.signal
  121. end
  122. end
  123. # If +element+ is in the queue, remove and return it, or +nil+.
  124. 3 def delete(element)
  125. 29 synchronize do
  126. 29 @queue.delete(element)
  127. end
  128. end
  129. # Remove all elements from the queue.
  130. 3 def clear
  131. 1118 synchronize do
  132. 1118 @queue.clear
  133. end
  134. end
  135. # Remove the head of the queue.
  136. #
  137. # If +timeout+ is not given, remove and return the head of the
  138. # queue if the number of available elements is strictly
  139. # greater than the number of threads currently waiting (that
  140. # is, don't jump ahead in line). Otherwise, return +nil+.
  141. #
  142. # If +timeout+ is given, block if there is no element
  143. # available, waiting up to +timeout+ seconds for an element to
  144. # become available.
  145. #
  146. # Raises:
  147. # - ActiveRecord::ConnectionTimeoutError if +timeout+ is given and no element
  148. # becomes available within +timeout+ seconds,
  149. 3 def poll(timeout = nil)
  150. 226256 synchronize { internal_poll(timeout) }
  151. end
  152. 3 private
  153. 3 def internal_poll(timeout)
  154. 113128 no_wait_poll || (timeout && wait_poll(timeout))
  155. end
  156. 3 def synchronize(&block)
  157. 229209 @lock.synchronize(&block)
  158. end
  159. # Test if the queue currently contains any elements.
  160. 3 def any?
  161. 129 !@queue.empty?
  162. end
  163. # A thread can remove an element from the queue without
  164. # waiting if and only if the number of currently available
  165. # connections is strictly greater than the number of waiting
  166. # threads.
  167. 3 def can_remove_no_wait?
  168. 113128 @queue.size > @num_waiting
  169. end
  170. # Removes and returns the head of the queue if possible, or +nil+.
  171. 3 def remove
  172. 112103 @queue.pop
  173. end
  174. # Remove and return the head of the queue if the number of
  175. # available elements is strictly greater than the number of
  176. # threads currently waiting. Otherwise, return +nil+.
  177. 3 def no_wait_poll
  178. 113128 remove if can_remove_no_wait?
  179. end
  180. # Waits on the queue up to +timeout+ seconds, then removes and
  181. # returns the head of the queue.
  182. 3 def wait_poll(timeout)
  183. 123 @num_waiting += 1
  184. 123 t0 = Concurrent.monotonic_time
  185. 123 elapsed = 0
  186. 123 loop do
  187. 129 ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
  188. 129 @cond.wait(timeout - elapsed)
  189. end
  190. 129 return remove if any?
  191. 21 elapsed = Concurrent.monotonic_time - t0
  192. 21 if elapsed >= timeout
  193. 15 msg = "could not obtain a connection from the pool within %0.3f seconds (waited %0.3f seconds); all pooled connections were in use" %
  194. [timeout, elapsed]
  195. 15 raise ConnectionTimeoutError, msg
  196. end
  197. end
  198. ensure
  199. 123 @num_waiting -= 1
  200. end
  201. end
  202. # Adds the ability to turn a basic fair FIFO queue into one
  203. # biased to some thread.
  204. 3 module BiasableQueue # :nodoc:
  205. 3 class BiasedConditionVariable # :nodoc:
  206. # semantics of condition variables guarantee that +broadcast+, +broadcast_on_biased+,
  207. # +signal+ and +wait+ methods are only called while holding a lock
  208. 3 def initialize(lock, other_cond, preferred_thread)
  209. 562 @real_cond = lock.new_cond
  210. 562 @other_cond = other_cond
  211. 562 @preferred_thread = preferred_thread
  212. 562 @num_waiting_on_real_cond = 0
  213. end
  214. 3 def broadcast
  215. broadcast_on_biased
  216. @other_cond.broadcast
  217. end
  218. 3 def broadcast_on_biased
  219. 562 @num_waiting_on_real_cond = 0
  220. 562 @real_cond.broadcast
  221. end
  222. 3 def signal
  223. 121 if @num_waiting_on_real_cond > 0
  224. 24 @num_waiting_on_real_cond -= 1
  225. 24 @real_cond
  226. else
  227. 97 @other_cond
  228. 121 end.signal
  229. end
  230. 3 def wait(timeout)
  231. 36 if Thread.current == @preferred_thread
  232. 36 @num_waiting_on_real_cond += 1
  233. 36 @real_cond
  234. else
  235. @other_cond
  236. 36 end.wait(timeout)
  237. end
  238. end
  239. 3 def with_a_bias_for(thread)
  240. 562 previous_cond = nil
  241. 562 new_cond = nil
  242. 562 synchronize do
  243. 562 previous_cond = @cond
  244. 562 @cond = new_cond = BiasedConditionVariable.new(@lock, @cond, thread)
  245. end
  246. 562 yield
  247. ensure
  248. 562 synchronize do
  249. 562 @cond = previous_cond if previous_cond
  250. 562 new_cond.broadcast_on_biased if new_cond # wake up any remaining sleepers
  251. end
  252. end
  253. end
  254. # Connections must be leased while holding the main pool mutex. This is
  255. # an internal subclass that also +.leases+ returned connections while
  256. # still in queue's critical section (queue synchronizes with the same
  257. # <tt>@lock</tt> as the main pool) so that a returned connection is already
  258. # leased and there is no need to re-enter synchronized block.
  259. 3 class ConnectionLeasingQueue < Queue # :nodoc:
  260. 3 include BiasableQueue
  261. 3 private
  262. 3 def internal_poll(timeout)
  263. 113128 conn = super
  264. 113113 conn.lease if conn
  265. 113113 conn
  266. end
  267. end
  268. # Every +frequency+ seconds, the reaper will call +reap+ and +flush+ on
  269. # +pool+. A reaper instantiated with a zero frequency will never reap
  270. # the connection pool.
  271. #
  272. # Configure the frequency by setting +reaping_frequency+ in your database
  273. # yaml file (default 60 seconds).
  274. 3 class Reaper
  275. 3 attr_reader :pool, :frequency
  276. 3 def initialize(pool, frequency)
  277. 987 @pool = pool
  278. 987 @frequency = frequency
  279. end
  280. 3 @mutex = Mutex.new
  281. 3 @pools = {}
  282. 3 @threads = {}
  283. 3 class << self
  284. 3 def register_pool(pool, frequency) # :nodoc:
  285. 984 @mutex.synchronize do
  286. 984 unless @threads[frequency]&.alive?
  287. 19 @threads[frequency] = spawn_thread(frequency)
  288. end
  289. 984 @pools[frequency] ||= []
  290. 984 @pools[frequency] << WeakRef.new(pool)
  291. end
  292. end
  293. 3 private
  294. 3 def spawn_thread(frequency)
  295. 19 Thread.new(frequency) do |t|
  296. 19 running = true
  297. 19 while running
  298. 1521 sleep t
  299. 1518 @mutex.synchronize do
  300. 1518 @pools[frequency].select! do |pool|
  301. 2328 pool.weakref_alive? && !pool.discarded?
  302. end
  303. 1518 @pools[frequency].each do |p|
  304. 1608 p.reap
  305. 1608 p.flush
  306. rescue WeakRef::RefError
  307. end
  308. 1518 if @pools[frequency].empty?
  309. 16 @pools.delete(frequency)
  310. 16 @threads.delete(frequency)
  311. 16 running = false
  312. end
  313. end
  314. end
  315. end
  316. end
  317. end
  318. 3 def run
  319. 987 return unless frequency && frequency > 0
  320. 984 self.class.register_pool(pool, frequency)
  321. end
  322. end
  323. 3 include MonitorMixin
  324. 3 include QueryCache::ConnectionPoolConfiguration
  325. 3 include ConnectionAdapters::AbstractPool
  326. 3 attr_accessor :automatic_reconnect, :checkout_timeout
  327. 3 attr_reader :db_config, :size, :reaper, :pool_config
  328. 3 delegate :schema_cache, :schema_cache=, to: :pool_config
  329. # Creates a new ConnectionPool object. +pool_config+ is a PoolConfig
  330. # object which describes database connection information (e.g. adapter,
  331. # host name, username, password, etc), as well as the maximum size for
  332. # this ConnectionPool.
  333. #
  334. # The default ConnectionPool maximum size is 5.
  335. 3 def initialize(pool_config)
  336. 975 super()
  337. 975 @pool_config = pool_config
  338. 975 @db_config = pool_config.db_config
  339. 975 @checkout_timeout = db_config.checkout_timeout
  340. 975 @idle_timeout = db_config.idle_timeout
  341. 975 @size = db_config.pool
  342. # This variable tracks the cache of threads mapped to reserved connections, with the
  343. # sole purpose of speeding up the +connection+ method. It is not the authoritative
  344. # registry of which thread owns which connection. Connection ownership is tracked by
  345. # the +connection.owner+ attr on each +connection+ instance.
  346. # The invariant works like this: if there is mapping of <tt>thread => conn</tt>,
  347. # then that +thread+ does indeed own that +conn+. However, an absence of a such
  348. # mapping does not mean that the +thread+ doesn't own the said connection. In
  349. # that case +conn.owner+ attr should be consulted.
  350. # Access and modification of <tt>@thread_cached_conns</tt> does not require
  351. # synchronization.
  352. 975 @thread_cached_conns = Concurrent::Map.new(initial_capacity: @size)
  353. 975 @connections = []
  354. 975 @automatic_reconnect = true
  355. # Connection pool allows for concurrent (outside the main +synchronize+ section)
  356. # establishment of new connections. This variable tracks the number of threads
  357. # currently in the process of independently establishing connections to the DB.
  358. 975 @now_connecting = 0
  359. 975 @threads_blocking_new_connections = 0
  360. 975 @available = ConnectionLeasingQueue.new self
  361. 975 @lock_thread = false
  362. 975 @reaper = Reaper.new(self, db_config.reaping_frequency)
  363. 975 @reaper.run
  364. end
  365. 3 def lock_thread=(lock_thread)
  366. 218634 if lock_thread
  367. 109317 @lock_thread = Thread.current
  368. else
  369. 109317 @lock_thread = nil
  370. end
  371. end
  372. # Retrieve the connection associated with the current thread, or call
  373. # #checkout to obtain one if necessary.
  374. #
  375. # #connection can be called any number of times; the connection is
  376. # held in a cache keyed by a thread.
  377. 3 def connection
  378. 1328083 @thread_cached_conns[connection_cache_key(current_thread)] ||= checkout
  379. end
  380. # Returns true if there is an open connection being used for the current thread.
  381. #
  382. # This method only works for connections that have been obtained through
  383. # #connection or #with_connection methods. Connections obtained through
  384. # #checkout will not be detected by #active_connection?
  385. 3 def active_connection?
  386. 974923 @thread_cached_conns[connection_cache_key(current_thread)]
  387. end
  388. # Signal that the thread is finished with the current connection.
  389. # #release_connection releases the connection-thread association
  390. # and returns the connection to the pool.
  391. #
  392. # This method only works for connections that have been obtained through
  393. # #connection or #with_connection methods, connections obtained through
  394. # #checkout will not be automatically released.
  395. 3 def release_connection(owner_thread = Thread.current)
  396. 126177 if conn = @thread_cached_conns.delete(connection_cache_key(owner_thread))
  397. 111989 checkin conn
  398. end
  399. end
  400. # If a connection obtained through #connection or #with_connection methods
  401. # already exists yield it to the block. If no such connection
  402. # exists checkout a connection, yield it to the block, and checkin the
  403. # connection when finished.
  404. 3 def with_connection
  405. 927 unless conn = @thread_cached_conns[connection_cache_key(Thread.current)]
  406. 211 conn = connection
  407. 208 fresh_connection = true
  408. end
  409. 924 yield conn
  410. ensure
  411. 927 release_connection if fresh_connection
  412. end
  413. # Returns true if a connection has already been opened.
  414. 3 def connected?
  415. 3182 synchronize { @connections.any? }
  416. end
  417. # Returns an array containing the connections currently in the pool.
  418. # Access to the array does not require synchronization on the pool because
  419. # the array is newly created and not retained by the pool.
  420. #
  421. # However; this method bypasses the ConnectionPool's thread-safe connection
  422. # access pattern. A returned connection may be owned by another thread,
  423. # unowned, or by happen-stance owned by the calling thread.
  424. #
  425. # Calling methods on a connection without ownership is subject to the
  426. # thread-safety guarantees of the underlying method. Many of the methods
  427. # on connection adapter classes are inherently multi-thread unsafe.
  428. 3 def connections
  429. 164 synchronize { @connections.dup }
  430. end
  431. # Disconnects all connections in the pool, and clears the pool.
  432. #
  433. # Raises:
  434. # - ActiveRecord::ExclusiveConnectionTimeoutError if unable to gain ownership of all
  435. # connections in the pool within a timeout interval (default duration is
  436. # <tt>spec.db_config.checkout_timeout * 2</tt> seconds).
  437. 3 def disconnect(raise_on_acquisition_timeout = true)
  438. 525 with_exclusively_acquired_all_connections(raise_on_acquisition_timeout) do
  439. 522 synchronize do
  440. 522 @connections.each do |conn|
  441. 566 if conn.in_use?
  442. 566 conn.steal!
  443. 566 checkin conn
  444. end
  445. 566 conn.disconnect!
  446. end
  447. 522 @connections = []
  448. 522 @available.clear
  449. end
  450. end
  451. end
  452. # Disconnects all connections in the pool, and clears the pool.
  453. #
  454. # The pool first tries to gain ownership of all connections. If unable to
  455. # do so within a timeout interval (default duration is
  456. # <tt>spec.db_config.checkout_timeout * 2</tt> seconds), then the pool is forcefully
  457. # disconnected without any regard for other connection owning threads.
  458. 3 def disconnect!
  459. 516 disconnect(false)
  460. end
  461. # Discards all connections in the pool (even if they're currently
  462. # leased!), along with the pool itself. Any further interaction with the
  463. # pool (except #spec and #schema_cache) is undefined.
  464. #
  465. # See AbstractAdapter#discard!
  466. 3 def discard! # :nodoc:
  467. 109 synchronize do
  468. 109 return if self.discarded?
  469. 109 @connections.each do |conn|
  470. 36 conn.discard!
  471. end
  472. 109 @connections = @available = @thread_cached_conns = nil
  473. end
  474. end
  475. 3 def discarded? # :nodoc:
  476. 5134 @connections.nil?
  477. end
  478. # Clears the cache which maps classes and re-connects connections that
  479. # require reloading.
  480. #
  481. # Raises:
  482. # - ActiveRecord::ExclusiveConnectionTimeoutError if unable to gain ownership of all
  483. # connections in the pool within a timeout interval (default duration is
  484. # <tt>spec.db_config.checkout_timeout * 2</tt> seconds).
  485. 3 def clear_reloadable_connections(raise_on_acquisition_timeout = true)
  486. 37 with_exclusively_acquired_all_connections(raise_on_acquisition_timeout) do
  487. 34 synchronize do
  488. 34 @connections.each do |conn|
  489. 38 if conn.in_use?
  490. 38 conn.steal!
  491. 38 checkin conn
  492. end
  493. 38 conn.disconnect! if conn.requires_reloading?
  494. end
  495. 34 @connections.delete_if(&:requires_reloading?)
  496. 34 @available.clear
  497. end
  498. end
  499. end
  500. # Clears the cache which maps classes and re-connects connections that
  501. # require reloading.
  502. #
  503. # The pool first tries to gain ownership of all connections. If unable to
  504. # do so within a timeout interval (default duration is
  505. # <tt>spec.db_config.checkout_timeout * 2</tt> seconds), then the pool forcefully
  506. # clears the cache and reloads connections without any regard for other
  507. # connection owning threads.
  508. 3 def clear_reloadable_connections!
  509. 25 clear_reloadable_connections(false)
  510. end
  511. # Check-out a database connection from the pool, indicating that you want
  512. # to use it. You should call #checkin when you no longer need this.
  513. #
  514. # This is done by either returning and leasing existing connection, or by
  515. # creating a new connection and leasing it.
  516. #
  517. # If all connections are leased and the pool is at capacity (meaning the
  518. # number of currently leased connections is greater than or equal to the
  519. # size limit set), an ActiveRecord::ConnectionTimeoutError exception will be raised.
  520. #
  521. # Returns: an AbstractAdapter object.
  522. #
  523. # Raises:
  524. # - ActiveRecord::ConnectionTimeoutError no connection can be obtained from the pool.
  525. 3 def checkout(checkout_timeout = @checkout_timeout)
  526. 112974 checkout_and_verify(acquire_connection(checkout_timeout))
  527. end
  528. # Check-in a database connection back into the pool, indicating that you
  529. # no longer need this connection.
  530. #
  531. # +conn+: an AbstractAdapter object, which was obtained by earlier by
  532. # calling #checkout on this pool.
  533. 3 def checkin(conn)
  534. 112912 conn.lock.synchronize do
  535. 112912 synchronize do
  536. 112912 remove_connection_from_thread_cache conn
  537. 112912 conn._run_checkin_callbacks do
  538. 112912 conn.expire
  539. end
  540. 112912 @available.add conn
  541. end
  542. end
  543. end
  544. # Remove a connection from the connection pool. The connection will
  545. # remain open and active but will no longer be managed by this pool.
  546. 3 def remove(conn)
  547. 14 needs_new_connection = false
  548. 14 synchronize do
  549. 14 remove_connection_from_thread_cache conn
  550. 14 @connections.delete conn
  551. 14 @available.delete conn
  552. # @available.any_waiting? => true means that prior to removing this
  553. # conn, the pool was at its max size (@connections.size == @size).
  554. # This would mean that any threads stuck waiting in the queue wouldn't
  555. # know they could checkout_new_connection, so let's do it for them.
  556. # Because condition-wait loop is encapsulated in the Queue class
  557. # (that in turn is oblivious to ConnectionPool implementation), threads
  558. # that are "stuck" there are helpless. They have no way of creating
  559. # new connections and are completely reliant on us feeding available
  560. # connections into the Queue.
  561. 14 needs_new_connection = @available.any_waiting?
  562. end
  563. # This is intentionally done outside of the synchronized section as we
  564. # would like not to hold the main mutex while checking out new connections.
  565. # Thus there is some chance that needs_new_connection information is now
  566. # stale, we can live with that (bulk_make_new_connections will make
  567. # sure not to exceed the pool's @size limit).
  568. 14 bulk_make_new_connections(1) if needs_new_connection
  569. end
  570. # Recover lost connections for the pool. A lost connection can occur if
  571. # a programmer forgets to checkin a connection at the end of a thread
  572. # or a thread dies unexpectedly.
  573. 3 def reap
  574. 1768 stale_connections = synchronize do
  575. 1768 return if self.discarded?
  576. @connections.select do |conn|
  577. 1944 conn.in_use? && !conn.owner.alive?
  578. 1763 end.each do |conn|
  579. 122 conn.steal!
  580. end
  581. end
  582. 1763 stale_connections.each do |conn|
  583. 122 if conn.active?
  584. 122 conn.reset!
  585. 122 checkin conn
  586. else
  587. remove conn
  588. end
  589. end
  590. end
  591. # Disconnect all connections that have been idle for at least
  592. # +minimum_idle+ seconds. Connections currently checked out, or that were
  593. # checked in less than +minimum_idle+ seconds ago, are unaffected.
  594. 3 def flush(minimum_idle = @idle_timeout)
  595. 1620 return if minimum_idle.nil?
  596. 1616 idle_connections = synchronize do
  597. 1616 return if self.discarded?
  598. @connections.select do |conn|
  599. 1105 !conn.in_use? && conn.seconds_idle >= minimum_idle
  600. 1611 end.each do |conn|
  601. 15 conn.lease
  602. 15 @available.delete conn
  603. 15 @connections.delete conn
  604. end
  605. end
  606. 1611 idle_connections.each do |conn|
  607. 15 conn.disconnect!
  608. end
  609. end
  610. # Disconnect all currently idle connections. Connections currently checked
  611. # out are unaffected.
  612. 3 def flush!
  613. 3 reap
  614. 3 flush(-1)
  615. end
  616. 3 def num_waiting_in_queue # :nodoc:
  617. 866 @available.num_waiting
  618. end
  619. # Return connection pool's usage statistic
  620. # Example:
  621. #
  622. # ActiveRecord::Base.connection_pool.stat # => { size: 15, connections: 1, busy: 1, dead: 0, idle: 0, waiting: 0, checkout_timeout: 5 }
  623. 3 def stat
  624. 9 synchronize do
  625. 9 {
  626. size: size,
  627. connections: @connections.size,
  628. 9 busy: @connections.count { |c| c.in_use? && c.owner.alive? },
  629. 9 dead: @connections.count { |c| c.in_use? && !c.owner.alive? },
  630. 9 idle: @connections.count { |c| !c.in_use? },
  631. waiting: num_waiting_in_queue,
  632. checkout_timeout: checkout_timeout
  633. }
  634. end
  635. end
  636. 3 private
  637. #--
  638. # this is unfortunately not concurrent
  639. 3 def bulk_make_new_connections(num_new_conns_needed)
  640. 16 num_new_conns_needed.times do
  641. # try_to_checkout_new_connection will not exceed pool's @size limit
  642. 16 if new_conn = try_to_checkout_new_connection
  643. # make the new_conn available to the starving threads stuck @available Queue
  644. 16 checkin(new_conn)
  645. end
  646. end
  647. end
  648. #--
  649. # From the discussion on GitHub:
  650. # https://github.com/rails/rails/pull/14938#commitcomment-6601951
  651. # This hook-in method allows for easier monkey-patching fixes needed by
  652. # JRuby users that use Fibers.
  653. 3 def connection_cache_key(thread)
  654. 2657895 thread
  655. end
  656. 3 def current_thread
  657. 2417734 @lock_thread || Thread.current
  658. end
  659. # Take control of all existing connections so a "group" action such as
  660. # reload/disconnect can be performed safely. It is no longer enough to
  661. # wrap it in +synchronize+ because some pool's actions are allowed
  662. # to be performed outside of the main +synchronize+ block.
  663. 3 def with_exclusively_acquired_all_connections(raise_on_acquisition_timeout = true)
  664. 562 with_new_connections_blocked do
  665. 562 attempt_to_checkout_all_existing_connections(raise_on_acquisition_timeout)
  666. 556 yield
  667. end
  668. end
  669. 3 def attempt_to_checkout_all_existing_connections(raise_on_acquisition_timeout = true)
  670. 562 collected_conns = synchronize do
  671. # account for our own connections
  672. 1172 @connections.select { |conn| conn.owner == Thread.current }
  673. end
  674. 562 newly_checked_out = []
  675. 562 timeout_time = Concurrent.monotonic_time + (@checkout_timeout * 2)
  676. 562 @available.with_a_bias_for(Thread.current) do
  677. 562 loop do
  678. 851 synchronize do
  679. 851 return if collected_conns.size == @connections.size && @now_connecting == 0
  680. 301 remaining_timeout = timeout_time - Concurrent.monotonic_time
  681. 301 remaining_timeout = 0 if remaining_timeout < 0
  682. 301 conn = checkout_for_exclusive_access(remaining_timeout)
  683. 289 collected_conns << conn
  684. 289 newly_checked_out << conn
  685. end
  686. end
  687. end
  688. rescue ExclusiveConnectionTimeoutError
  689. # <tt>raise_on_acquisition_timeout == false</tt> means we are directed to ignore any
  690. # timeouts and are expected to just give up: we've obtained as many connections
  691. # as possible, note that in a case like that we don't return any of the
  692. # +newly_checked_out+ connections.
  693. 12 if raise_on_acquisition_timeout
  694. 6 release_newly_checked_out = true
  695. 6 raise
  696. end
  697. rescue Exception # if something else went wrong
  698. # this can't be a "naked" rescue, because we have should return conns
  699. # even for non-StandardErrors
  700. release_newly_checked_out = true
  701. raise
  702. ensure
  703. 562 if release_newly_checked_out && newly_checked_out
  704. # releasing only those conns that were checked out in this method, conns
  705. # checked outside this method (before it was called) are not for us to release
  706. 6 newly_checked_out.each { |conn| checkin(conn) }
  707. end
  708. end
  709. #--
  710. # Must be called in a synchronize block.
  711. 3 def checkout_for_exclusive_access(checkout_timeout)
  712. 301 checkout(checkout_timeout)
  713. rescue ConnectionTimeoutError
  714. # this block can't be easily moved into attempt_to_checkout_all_existing_connections's
  715. # rescue block, because doing so would put it outside of synchronize section, without
  716. # being in a critical section thread_report might become inaccurate
  717. 12 msg = +"could not obtain ownership of all database connections in #{checkout_timeout} seconds"
  718. 12 thread_report = []
  719. 12 @connections.each do |conn|
  720. 12 unless conn.owner == Thread.current
  721. 12 thread_report << "#{conn} is owned by #{conn.owner}"
  722. end
  723. end
  724. 12 msg << " (#{thread_report.join(', ')})" if thread_report.any?
  725. 12 raise ExclusiveConnectionTimeoutError, msg
  726. end
  727. 3 def with_new_connections_blocked
  728. 562 synchronize do
  729. 562 @threads_blocking_new_connections += 1
  730. end
  731. 562 yield
  732. ensure
  733. 562 num_new_conns_required = 0
  734. 562 synchronize do
  735. 562 @threads_blocking_new_connections -= 1
  736. 562 if @threads_blocking_new_connections.zero?
  737. 562 @available.clear
  738. 562 num_new_conns_required = num_waiting_in_queue
  739. 562 @connections.each do |conn|
  740. 21 next if conn.in_use?
  741. 15 @available.add conn
  742. 15 num_new_conns_required -= 1
  743. end
  744. end
  745. end
  746. 562 bulk_make_new_connections(num_new_conns_required) if num_new_conns_required > 0
  747. end
  748. # Acquire a connection by one of 1) immediately removing one
  749. # from the queue of available connections, 2) creating a new
  750. # connection if the pool is not at capacity, 3) waiting on the
  751. # queue for a connection to become available.
  752. #
  753. # Raises:
  754. # - ActiveRecord::ConnectionTimeoutError if a connection could not be acquired
  755. #
  756. #--
  757. # Implementation detail: the connection returned by +acquire_connection+
  758. # will already be "+connection.lease+ -ed" to the current thread.
  759. 3 def acquire_connection(checkout_timeout)
  760. # NOTE: we rely on <tt>@available.poll</tt> and +try_to_checkout_new_connection+ to
  761. # +conn.lease+ the returned connection (and to do this in a +synchronized+
  762. # section). This is not the cleanest implementation, as ideally we would
  763. # <tt>synchronize { conn.lease }</tt> in this method, but by leaving it to <tt>@available.poll</tt>
  764. # and +try_to_checkout_new_connection+ we can piggyback on +synchronize+ sections
  765. # of the said methods and avoid an additional +synchronize+ overhead.
  766. 112974 if conn = @available.poll || try_to_checkout_new_connection
  767. 112814 conn
  768. else
  769. 154 reap
  770. 154 @available.poll(checkout_timeout)
  771. end
  772. end
  773. #--
  774. # if owner_thread param is omitted, this must be called in synchronize block
  775. 3 def remove_connection_from_thread_cache(conn, owner_thread = conn.owner)
  776. 113057 @thread_cached_conns.delete_pair(connection_cache_key(owner_thread), conn)
  777. end
  778. 3 alias_method :release, :remove_connection_from_thread_cache
  779. 3 def new_connection
  780. 866 Base.send(db_config.adapter_method, db_config.configuration_hash).tap do |conn|
  781. 866 conn.check_version
  782. end
  783. end
  784. # If the pool is not at a <tt>@size</tt> limit, establish new connection. Connecting
  785. # to the DB is done outside main synchronized section.
  786. #--
  787. # Implementation constraint: a newly established connection returned by this
  788. # method must be in the +.leased+ state.
  789. 3 def try_to_checkout_new_connection
  790. # first in synchronized section check if establishing new conns is allowed
  791. # and increment @now_connecting, to prevent overstepping this pool's @size
  792. # constraint
  793. 1026 do_checkout = synchronize do
  794. 1026 if @threads_blocking_new_connections.zero? && (@connections.size + @now_connecting) < @size
  795. 872 @now_connecting += 1
  796. end
  797. end
  798. 1026 if do_checkout
  799. 872 begin
  800. # if successfully incremented @now_connecting establish new connection
  801. # outside of synchronized section
  802. 872 conn = checkout_new_connection
  803. ensure
  804. 872 synchronize do
  805. 872 if conn
  806. 866 adopt_connection(conn)
  807. # returned conn needs to be already leased
  808. 866 conn.lease
  809. end
  810. 872 @now_connecting -= 1
  811. end
  812. end
  813. end
  814. end
  815. 3 def adopt_connection(conn)
  816. 869 conn.pool = self
  817. 869 @connections << conn
  818. end
  819. 3 def checkout_new_connection
  820. 872 raise ConnectionNotEstablished unless @automatic_reconnect
  821. 866 new_connection
  822. end
  823. 3 def checkout_and_verify(c)
  824. 112953 c._run_checkout_callbacks do
  825. 112953 c.verify!
  826. end
  827. 112953 c
  828. rescue
  829. remove c
  830. c.disconnect!
  831. raise
  832. end
  833. end
  834. # ConnectionHandler is a collection of ConnectionPool objects. It is used
  835. # for keeping separate connection pools that connect to different databases.
  836. #
  837. # For example, suppose that you have 5 models, with the following hierarchy:
  838. #
  839. # class Author < ActiveRecord::Base
  840. # end
  841. #
  842. # class BankAccount < ActiveRecord::Base
  843. # end
  844. #
  845. # class Book < ActiveRecord::Base
  846. # establish_connection :library_db
  847. # end
  848. #
  849. # class ScaryBook < Book
  850. # end
  851. #
  852. # class GoodBook < Book
  853. # end
  854. #
  855. # And a database.yml that looked like this:
  856. #
  857. # development:
  858. # database: my_application
  859. # host: localhost
  860. #
  861. # library_db:
  862. # database: library
  863. # host: some.library.org
  864. #
  865. # Your primary database in the development environment is "my_application"
  866. # but the Book model connects to a separate database called "library_db"
  867. # (this can even be a database on a different machine).
  868. #
  869. # Book, ScaryBook and GoodBook will all use the same connection pool to
  870. # "library_db" while Author, BankAccount, and any other models you create
  871. # will use the default connection pool to "my_application".
  872. #
  873. # The various connection pools are managed by a single instance of
  874. # ConnectionHandler accessible via ActiveRecord::Base.connection_handler.
  875. # All Active Record models use this handler to determine the connection pool that they
  876. # should use.
  877. #
  878. # The ConnectionHandler class is not coupled with the Active models, as it has no knowledge
  879. # about the model. The model needs to pass a connection specification name to the handler,
  880. # in order to look up the correct connection pool.
  881. 3 class ConnectionHandler
  882. 431 FINALIZER = lambda { |_| ActiveSupport::ForkTracker.check! }
  883. 3 private_constant :FINALIZER
  884. 3 def initialize
  885. # These caches are keyed by pool_config.connection_specification_name (PoolConfig#connection_specification_name).
  886. 444 @owner_to_pool_manager = Concurrent::Map.new(initial_capacity: 2)
  887. # Backup finalizer: if the forked child skipped Kernel#fork the early discard has not occurred
  888. 444 ObjectSpace.define_finalizer self, FINALIZER
  889. end
  890. 3 def prevent_writes # :nodoc:
  891. 291250 Thread.current[:prevent_writes]
  892. end
  893. 3 def prevent_writes=(prevent_writes) # :nodoc:
  894. 442 Thread.current[:prevent_writes] = prevent_writes
  895. end
  896. # Prevent writing to the database regardless of role.
  897. #
  898. # In some cases you may want to prevent writes to the database
  899. # even if you are on a database that can write. `while_preventing_writes`
  900. # will prevent writes to the database for the duration of the block.
  901. 3 def while_preventing_writes(enabled = true)
  902. 221 original, self.prevent_writes = self.prevent_writes, enabled
  903. 221 yield
  904. ensure
  905. 221 self.prevent_writes = original
  906. end
  907. 3 def connection_pool_names # :nodoc:
  908. 6 owner_to_pool_manager.keys
  909. end
  910. 3 def connection_pool_list
  911. 1356382 owner_to_pool_manager.values.flat_map { |m| m.pool_configs.map(&:pool) }
  912. end
  913. 3 alias :connection_pools :connection_pool_list
  914. 3 def establish_connection(config, owner_name: Base.name, shard: Base.default_shard)
  915. 765 owner_name = config.to_s if config.is_a?(Symbol)
  916. 765 pool_config = resolve_pool_config(config, owner_name)
  917. 756 db_config = pool_config.db_config
  918. # Protects the connection named `ActiveRecord::Base` from being removed
  919. # if the user calls `establish_connection :primary`.
  920. 756 if owner_to_pool_manager.key?(pool_config.connection_specification_name)
  921. 292 remove_connection_pool(pool_config.connection_specification_name, shard: shard)
  922. end
  923. 756 message_bus = ActiveSupport::Notifications.instrumenter
  924. 756 payload = {}
  925. 756 if pool_config
  926. 756 payload[:spec_name] = pool_config.connection_specification_name
  927. 756 payload[:config] = db_config.configuration_hash
  928. end
  929. 756 owner_to_pool_manager[pool_config.connection_specification_name] ||= PoolManager.new
  930. 756 pool_manager = get_pool_manager(pool_config.connection_specification_name)
  931. 756 pool_manager.set_pool_config(shard, pool_config)
  932. 756 message_bus.instrument("!connection.active_record", payload) do
  933. 756 pool_config.pool
  934. end
  935. end
  936. # Returns true if there are any active connections among the connection
  937. # pools that the ConnectionHandler is managing.
  938. 3 def active_connections?
  939. 81 connection_pool_list.any?(&:active_connection?)
  940. end
  941. # Returns any connections in use by the current thread back to the pool,
  942. # and also returns connections to the pool cached by threads that are no
  943. # longer alive.
  944. 3 def clear_active_connections!
  945. 19061 connection_pool_list.each(&:release_connection)
  946. end
  947. # Clears the cache which maps classes.
  948. #
  949. # See ConnectionPool#clear_reloadable_connections! for details.
  950. 3 def clear_reloadable_connections!
  951. connection_pool_list.each(&:clear_reloadable_connections!)
  952. end
  953. 3 def clear_all_connections!
  954. 10 connection_pool_list.each(&:disconnect!)
  955. end
  956. # Disconnects all currently idle connections.
  957. #
  958. # See ConnectionPool#flush! for details.
  959. 3 def flush_idle_connections!
  960. connection_pool_list.each(&:flush!)
  961. end
  962. # Locate the connection of the nearest super class. This can be an
  963. # active or defined connection: if it is the latter, it will be
  964. # opened and set as the active connection for the class it was defined
  965. # for (not necessarily the current class).
  966. 3 def retrieve_connection(spec_name, shard: ActiveRecord::Base.default_shard) # :nodoc:
  967. 266780 pool = retrieve_connection_pool(spec_name, shard: shard)
  968. 266780 unless pool
  969. 14 if shard != ActiveRecord::Base.default_shard
  970. 2 message = "No connection pool for '#{spec_name}' found for the '#{shard}' shard."
  971. 12 elsif ActiveRecord::Base.connection_handler != ActiveRecord::Base.default_connection_handler
  972. 3 message = "No connection pool for '#{spec_name}' found for the '#{ActiveRecord::Base.current_role}' role."
  973. else
  974. 9 message = "No connection pool for '#{spec_name}' found."
  975. end
  976. 14 raise ConnectionNotEstablished, message
  977. end
  978. 266766 pool.connection
  979. end
  980. # Returns true if a connection that's accessible to this class has
  981. # already been opened.
  982. 3 def connected?(spec_name, shard: ActiveRecord::Base.default_shard)
  983. 1591 pool = retrieve_connection_pool(spec_name, shard: shard)
  984. 1591 pool && pool.connected?
  985. end
  986. # Remove the connection for this class. This will close the active
  987. # connection and the defined connection (if they exist). The result
  988. # can be used as an argument for #establish_connection, for easily
  989. # re-establishing the connection.
  990. 3 def remove_connection(owner, shard: ActiveRecord::Base.default_shard)
  991. 2 remove_connection_pool(owner, shard: shard)&.configuration_hash
  992. end
  993. 3 deprecate remove_connection: "Use #remove_connection_pool, which now returns a DatabaseConfig object instead of a Hash"
  994. 3 def remove_connection_pool(owner, shard: ActiveRecord::Base.default_shard)
  995. 398 if pool_manager = get_pool_manager(owner)
  996. 393 pool_config = pool_manager.remove_pool_config(shard)
  997. 393 if pool_config
  998. 283 pool_config.disconnect!
  999. 283 pool_config.db_config
  1000. end
  1001. end
  1002. end
  1003. # Retrieving the connection pool happens a lot, so we cache it in @owner_to_pool_manager.
  1004. # This makes retrieving the connection pool O(1) once the process is warm.
  1005. # When a connection is established or removed, we invalidate the cache.
  1006. 3 def retrieve_connection_pool(owner, shard: ActiveRecord::Base.default_shard)
  1007. 269784 pool_config = get_pool_manager(owner)&.get_pool_config(shard)
  1008. 269784 pool_config&.pool
  1009. end
  1010. 3 private
  1011. 3 attr_reader :owner_to_pool_manager
  1012. # Returns the pool manager for an owner.
  1013. #
  1014. # Using `"primary"` to look up the pool manager for `ActiveRecord::Base` is
  1015. # deprecated in favor of looking it up by `"ActiveRecord::Base"`.
  1016. #
  1017. # During the deprecation period, if `"primary"` is passed, the pool manager
  1018. # for `ActiveRecord::Base` will still be returned.
  1019. 3 def get_pool_manager(owner)
  1020. 270938 return owner_to_pool_manager[owner] if owner_to_pool_manager.key?(owner)
  1021. 24 if owner == "primary"
  1022. 4 ActiveSupport::Deprecation.warn("Using `\"primary\"` as a `connection_specification_name` is deprecated and will be removed in Rails 6.2.0. Please use `ActiveRecord::Base`.")
  1023. 4 owner_to_pool_manager[Base.name]
  1024. end
  1025. end
  1026. # Returns an instance of PoolConfig for a given adapter.
  1027. # Accepts a hash one layer deep that contains all connection information.
  1028. #
  1029. # == Example
  1030. #
  1031. # config = { "production" => { "host" => "localhost", "database" => "foo", "adapter" => "sqlite3" } }
  1032. # pool_config = Base.configurations.resolve_pool_config(:production)
  1033. # pool_config.db_config.configuration_hash
  1034. # # => { host: "localhost", database: "foo", adapter: "sqlite3" }
  1035. #
  1036. 3 def resolve_pool_config(config, owner_name)
  1037. 765 db_config = Base.configurations.resolve(config)
  1038. 762 raise(AdapterNotSpecified, "database configuration does not specify adapter") unless db_config.adapter
  1039. # Require the adapter itself and give useful feedback about
  1040. # 1. Missing adapter gems and
  1041. # 2. Adapter gems' missing dependencies.
  1042. 762 path_to_adapter = "active_record/connection_adapters/#{db_config.adapter}_adapter"
  1043. 762 begin
  1044. 762 require path_to_adapter
  1045. rescue LoadError => e
  1046. # We couldn't require the adapter itself. Raise an exception that
  1047. # points out config typos and missing gems.
  1048. 3 if e.path == path_to_adapter
  1049. # We can assume that a non-builtin adapter was specified, so it's
  1050. # either misspelled or missing from Gemfile.
  1051. 3 raise LoadError, "Could not load the '#{db_config.adapter}' Active Record adapter. Ensure that the adapter is spelled correctly in config/database.yml and that you've added the necessary adapter gem to your Gemfile.", e.backtrace
  1052. # Bubbled up from the adapter require. Prefix the exception message
  1053. # with some guidance about how to address it and reraise.
  1054. else
  1055. raise LoadError, "Error loading the '#{db_config.adapter}' Active Record adapter. Missing a gem it depends on? #{e.message}", e.backtrace
  1056. end
  1057. end
  1058. 759 unless ActiveRecord::Base.respond_to?(db_config.adapter_method)
  1059. 3 raise AdapterNotFound, "database configuration specifies nonexistent #{db_config.adapter} adapter"
  1060. end
  1061. 756 ConnectionAdapters::PoolConfig.new(owner_name, db_config)
  1062. end
  1063. end
  1064. end
  1065. end

lib/active_record/connection_adapters/abstract/database_limits.rb

100.0% lines covered

38 relevant lines. 38 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 module ActiveRecord
  3. 3 module ConnectionAdapters # :nodoc:
  4. 3 module DatabaseLimits
  5. 3 def max_identifier_length # :nodoc:
  6. 1607 64
  7. end
  8. # Returns the maximum length of a table alias.
  9. 3 def table_alias_length
  10. 1078 max_identifier_length
  11. end
  12. # Returns the maximum length of a column name.
  13. 3 def column_name_length
  14. 3 max_identifier_length
  15. end
  16. 3 deprecate :column_name_length
  17. # Returns the maximum length of a table name.
  18. 3 def table_name_length
  19. 3 max_identifier_length
  20. end
  21. 3 deprecate :table_name_length
  22. # Returns the maximum allowed length for an index name. This
  23. # limit is enforced by \Rails and is less than or equal to
  24. # #index_name_length. The gap between
  25. # #index_name_length is to allow internal \Rails
  26. # operations to use prefixes in temporary operations.
  27. 3 def allowed_index_name_length
  28. 3 index_name_length
  29. end
  30. 3 deprecate :allowed_index_name_length
  31. # Returns the maximum length of an index name.
  32. 3 def index_name_length
  33. 1406 max_identifier_length
  34. end
  35. # Returns the maximum number of columns per table.
  36. 3 def columns_per_table
  37. 3 1024
  38. end
  39. 3 deprecate :columns_per_table
  40. # Returns the maximum number of indexes per table.
  41. 3 def indexes_per_table
  42. 3 16
  43. end
  44. 3 deprecate :indexes_per_table
  45. # Returns the maximum number of columns in a multicolumn index.
  46. 3 def columns_per_multicolumn_index
  47. 3 16
  48. end
  49. 3 deprecate :columns_per_multicolumn_index
  50. # Returns the maximum number of elements in an IN (x,y,z) clause.
  51. # +nil+ means no limit.
  52. 3 def in_clause_length
  53. nil
  54. end
  55. 3 deprecate :in_clause_length
  56. # Returns the maximum length of an SQL query.
  57. 3 def sql_query_length
  58. 3 1048575
  59. end
  60. 3 deprecate :sql_query_length
  61. # Returns maximum number of joins in a single query.
  62. 3 def joins_per_query
  63. 3 256
  64. end
  65. 3 deprecate :joins_per_query
  66. 3 private
  67. 3 def bind_params_length
  68. 18031 65535
  69. end
  70. end
  71. end
  72. end

lib/active_record/connection_adapters/abstract/database_statements.rb

95.57% lines covered

203 relevant lines. 194 lines covered and 9 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 module ActiveRecord
  3. 3 module ConnectionAdapters # :nodoc:
  4. 3 module DatabaseStatements
  5. 3 def initialize
  6. 1052 super
  7. 1052 reset_transaction
  8. end
  9. # Converts an arel AST to SQL
  10. 3 def to_sql(arel_or_sql_string, binds = [])
  11. 517 sql, _ = to_sql_and_binds(arel_or_sql_string, binds)
  12. 517 sql
  13. end
  14. 3 def to_sql_and_binds(arel_or_sql_string, binds = [], preparable = nil) # :nodoc:
  15. 59386 if arel_or_sql_string.respond_to?(:ast)
  16. 52346 unless binds.empty?
  17. raise "Passing bind parameters with an arel AST is forbidden. " \
  18. "The values must be stored on the AST directly"
  19. end
  20. 52346 collector = collector()
  21. 52346 if prepared_statements
  22. 51854 collector.preparable = true
  23. 51854 sql, binds = visitor.compile(arel_or_sql_string.ast, collector)
  24. 51854 if binds.length > bind_params_length
  25. 11 unprepared_statement do
  26. 11 return to_sql_and_binds(arel_or_sql_string)
  27. end
  28. end
  29. 51843 preparable = collector.preparable
  30. else
  31. 492 sql = visitor.compile(arel_or_sql_string.ast, collector)
  32. end
  33. 52335 [sql.freeze, binds, preparable]
  34. else
  35. 7040 arel_or_sql_string = arel_or_sql_string.dup.freeze unless arel_or_sql_string.frozen?
  36. 7040 [arel_or_sql_string, binds, preparable]
  37. end
  38. end
  39. 3 private :to_sql_and_binds
  40. # This is used in the StatementCache object. It returns an object that
  41. # can be used to query the database repeatedly.
  42. 3 def cacheable_query(klass, arel) # :nodoc:
  43. 1277 if prepared_statements
  44. 1262 sql, binds = visitor.compile(arel.ast, collector)
  45. 1262 query = klass.query(sql)
  46. else
  47. 15 collector = klass.partial_query_collector
  48. 15 parts, binds = visitor.compile(arel.ast, collector)
  49. 15 query = klass.partial_query(parts)
  50. end
  51. 1277 [query, binds]
  52. end
  53. # Returns an ActiveRecord::Result instance.
  54. 3 def select_all(arel, name = nil, binds = [], preparable: nil)
  55. 38289 arel = arel_from_relation(arel)
  56. 38289 sql, binds, preparable = to_sql_and_binds(arel, binds, preparable)
  57. 38289 if prepared_statements && preparable
  58. 27053 select_prepared(sql, name, binds)
  59. else
  60. 11236 select(sql, name, binds)
  61. end
  62. rescue ::RangeError
  63. 16 ActiveRecord::Result.new([], [])
  64. end
  65. # Returns a record hash with the column names as keys and column values
  66. # as values.
  67. 3 def select_one(arel, name = nil, binds = [])
  68. 60 select_all(arel, name, binds).first
  69. end
  70. # Returns a single value from a record
  71. 3 def select_value(arel, name = nil, binds = [])
  72. 88 single_value_from_rows(select_rows(arel, name, binds))
  73. end
  74. # Returns an array of the values of the first column in a select:
  75. # select_values("SELECT id FROM companies LIMIT 3") => [1,2,3]
  76. 3 def select_values(arel, name = nil, binds = [])
  77. 31 select_rows(arel, name, binds).map(&:first)
  78. end
  79. # Returns an array of arrays containing the field values.
  80. # Order is the same as that returned by +columns+.
  81. 3 def select_rows(arel, name = nil, binds = [])
  82. 1728 select_all(arel, name, binds).rows
  83. end
  84. 3 def query_value(sql, name = nil) # :nodoc:
  85. 56868 single_value_from_rows(query(sql, name))
  86. end
  87. 3 def query_values(sql, name = nil) # :nodoc:
  88. 6916 query(sql, name).map(&:first)
  89. end
  90. 3 def query(sql, name = nil) # :nodoc:
  91. 49775 exec_query(sql, name).rows
  92. end
  93. # Determines whether the SQL statement is a write query.
  94. 3 def write_query?(sql)
  95. raise NotImplementedError
  96. end
  97. # Executes the SQL statement in the context of this connection and returns
  98. # the raw result from the connection adapter.
  99. # Note: depending on your database connector, the result returned by this
  100. # method may be manually memory managed. Consider using the exec_query
  101. # wrapper instead.
  102. 3 def execute(sql, name = nil)
  103. raise NotImplementedError
  104. end
  105. # Executes +sql+ statement in the context of this connection using
  106. # +binds+ as the bind substitutes. +name+ is logged along with
  107. # the executed +sql+ statement.
  108. 3 def exec_query(sql, name = "SQL", binds = [], prepare: false)
  109. raise NotImplementedError
  110. end
  111. # Executes insert +sql+ statement in the context of this connection using
  112. # +binds+ as the bind substitutes. +name+ is logged along with
  113. # the executed +sql+ statement.
  114. 3 def exec_insert(sql, name = nil, binds = [], pk = nil, sequence_name = nil)
  115. 12492 sql, binds = sql_for_insert(sql, pk, binds)
  116. 12492 exec_query(sql, name, binds)
  117. end
  118. # Executes delete +sql+ statement in the context of this connection using
  119. # +binds+ as the bind substitutes. +name+ is logged along with
  120. # the executed +sql+ statement.
  121. 3 def exec_delete(sql, name = nil, binds = [])
  122. exec_query(sql, name, binds)
  123. end
  124. # Executes update +sql+ statement in the context of this connection using
  125. # +binds+ as the bind substitutes. +name+ is logged along with
  126. # the executed +sql+ statement.
  127. 3 def exec_update(sql, name = nil, binds = [])
  128. exec_query(sql, name, binds)
  129. end
  130. 3 def exec_insert_all(sql, name) # :nodoc:
  131. 155 exec_query(sql, name)
  132. end
  133. # Executes an INSERT query and returns the new record's ID
  134. #
  135. # +id_value+ will be returned unless the value is +nil+, in
  136. # which case the database will attempt to calculate the last inserted
  137. # id and return that value.
  138. #
  139. # If the next id was calculated in advance (as in Oracle), it should be
  140. # passed in as +id_value+.
  141. 3 def insert(arel, name = nil, pk = nil, id_value = nil, sequence_name = nil, binds = [])
  142. 12487 sql, binds = to_sql_and_binds(arel, binds)
  143. 12487 value = exec_insert(sql, name, binds, pk, sequence_name)
  144. 12430 id_value || last_inserted_id(value)
  145. end
  146. 3 alias create insert
  147. # Executes the update statement and returns the number of rows affected.
  148. 3 def update(arel, name = nil, binds = [])
  149. 4135 sql, binds = to_sql_and_binds(arel, binds)
  150. 4135 exec_update(sql, name, binds)
  151. end
  152. # Executes the delete statement and returns the number of rows affected.
  153. 3 def delete(arel, name = nil, binds = [])
  154. 3546 sql, binds = to_sql_and_binds(arel, binds)
  155. 3546 exec_delete(sql, name, binds)
  156. end
  157. # Executes the truncate statement.
  158. 3 def truncate(table_name, name = nil)
  159. 6 execute(build_truncate_statement(table_name), name)
  160. end
  161. 3 def truncate_tables(*table_names) # :nodoc:
  162. 12 table_names -= [schema_migration.table_name, InternalMetadata.table_name]
  163. 12 return if table_names.empty?
  164. 12 with_multi_statements do
  165. 12 disable_referential_integrity do
  166. 12 statements = build_truncate_statements(table_names)
  167. 12 execute_batch(statements, "Truncate Tables")
  168. end
  169. end
  170. end
  171. # Runs the given block in a database transaction, and returns the result
  172. # of the block.
  173. #
  174. # == Nested transactions support
  175. #
  176. # #transaction calls can be nested. By default, this makes all database
  177. # statements in the nested transaction block become part of the parent
  178. # transaction. For example, the following behavior may be surprising:
  179. #
  180. # ActiveRecord::Base.transaction do
  181. # Post.create(title: 'first')
  182. # ActiveRecord::Base.transaction do
  183. # Post.create(title: 'second')
  184. # raise ActiveRecord::Rollback
  185. # end
  186. # end
  187. #
  188. # This creates both "first" and "second" posts. Reason is the
  189. # ActiveRecord::Rollback exception in the nested block does not issue a
  190. # ROLLBACK. Since these exceptions are captured in transaction blocks,
  191. # the parent block does not see it and the real transaction is committed.
  192. #
  193. # Most databases don't support true nested transactions. At the time of
  194. # writing, the only database that supports true nested transactions that
  195. # we're aware of, is MS-SQL.
  196. #
  197. # In order to get around this problem, #transaction will emulate the effect
  198. # of nested transactions, by using savepoints:
  199. # https://dev.mysql.com/doc/refman/en/savepoint.html.
  200. #
  201. # It is safe to call this method if a database transaction is already open,
  202. # i.e. if #transaction is called within another #transaction block. In case
  203. # of a nested call, #transaction will behave as follows:
  204. #
  205. # - The block will be run without doing anything. All database statements
  206. # that happen within the block are effectively appended to the already
  207. # open database transaction.
  208. # - However, if +:requires_new+ is set, the block will be wrapped in a
  209. # database savepoint acting as a sub-transaction.
  210. #
  211. # In order to get a ROLLBACK for the nested transaction you may ask for a
  212. # real sub-transaction by passing <tt>requires_new: true</tt>.
  213. # If anything goes wrong, the database rolls back to the beginning of
  214. # the sub-transaction without rolling back the parent transaction.
  215. # If we add it to the previous example:
  216. #
  217. # ActiveRecord::Base.transaction do
  218. # Post.create(title: 'first')
  219. # ActiveRecord::Base.transaction(requires_new: true) do
  220. # Post.create(title: 'second')
  221. # raise ActiveRecord::Rollback
  222. # end
  223. # end
  224. #
  225. # only post with title "first" is created.
  226. #
  227. # See ActiveRecord::Transactions to learn more.
  228. #
  229. # === Caveats
  230. #
  231. # MySQL doesn't support DDL transactions. If you perform a DDL operation,
  232. # then any created savepoints will be automatically released. For example,
  233. # if you've created a savepoint, then you execute a CREATE TABLE statement,
  234. # then the savepoint that was created will be automatically released.
  235. #
  236. # This means that, on MySQL, you shouldn't execute DDL operations inside
  237. # a #transaction call that you know might create a savepoint. Otherwise,
  238. # #transaction will raise exceptions when it tries to release the
  239. # already-automatically-released savepoints:
  240. #
  241. # Model.connection.transaction do # BEGIN
  242. # Model.connection.transaction(requires_new: true) do # CREATE SAVEPOINT active_record_1
  243. # Model.connection.create_table(...)
  244. # # active_record_1 now automatically released
  245. # end # RELEASE SAVEPOINT active_record_1 <--- BOOM! database error!
  246. # end
  247. #
  248. # == Transaction isolation
  249. #
  250. # If your database supports setting the isolation level for a transaction, you can set
  251. # it like so:
  252. #
  253. # Post.transaction(isolation: :serializable) do
  254. # # ...
  255. # end
  256. #
  257. # Valid isolation levels are:
  258. #
  259. # * <tt>:read_uncommitted</tt>
  260. # * <tt>:read_committed</tt>
  261. # * <tt>:repeatable_read</tt>
  262. # * <tt>:serializable</tt>
  263. #
  264. # You should consult the documentation for your database to understand the
  265. # semantics of these different levels:
  266. #
  267. # * https://www.postgresql.org/docs/current/static/transaction-iso.html
  268. # * https://dev.mysql.com/doc/refman/en/set-transaction.html
  269. #
  270. # An ActiveRecord::TransactionIsolationError will be raised if:
  271. #
  272. # * The adapter does not support setting the isolation level
  273. # * You are joining an existing open transaction
  274. # * You are creating a nested (savepoint) transaction
  275. #
  276. # The mysql2 and postgresql adapters support setting the transaction
  277. # isolation level.
  278. 3 def transaction(requires_new: nil, isolation: nil, joinable: true)
  279. 26964 if !requires_new && current_transaction.joinable?
  280. 7782 if isolation
  281. 1 raise ActiveRecord::TransactionIsolationError, "cannot set isolation when joining a transaction"
  282. end
  283. 7781 yield
  284. else
  285. 38363 transaction_manager.within_new_transaction(isolation: isolation, joinable: joinable) { yield }
  286. end
  287. rescue ActiveRecord::Rollback
  288. # rollbacks are silently swallowed
  289. end
  290. 3 attr_reader :transaction_manager #:nodoc:
  291. 3 delegate :within_new_transaction, :open_transactions, :current_transaction, :begin_transaction,
  292. :commit_transaction, :rollback_transaction, :materialize_transactions,
  293. :disable_lazy_transactions!, :enable_lazy_transactions!, to: :transaction_manager
  294. 3 def mark_transaction_written_if_write(sql) # :nodoc:
  295. 316824 transaction = current_transaction
  296. 316824 if transaction.open?
  297. 188737 transaction.written ||= write_query?(sql)
  298. end
  299. end
  300. 3 def transaction_open?
  301. 128340 current_transaction.open?
  302. end
  303. 3 def reset_transaction #:nodoc:
  304. 1993 @transaction_manager = ConnectionAdapters::TransactionManager.new(self)
  305. end
  306. # Register a record with the current transaction so that its after_commit and after_rollback callbacks
  307. # can be called.
  308. 3 def add_transaction_record(record, ensure_finalize = true)
  309. 19259 current_transaction.add_record(record, ensure_finalize)
  310. end
  311. # Begins the transaction (and turns off auto-committing).
  312. 3 def begin_db_transaction() end
  313. 3 def transaction_isolation_levels
  314. 8 {
  315. read_uncommitted: "READ UNCOMMITTED",
  316. read_committed: "READ COMMITTED",
  317. repeatable_read: "REPEATABLE READ",
  318. serializable: "SERIALIZABLE"
  319. }
  320. end
  321. # Begins the transaction with the isolation level set. Raises an error by
  322. # default; adapters that support setting the isolation level should implement
  323. # this method.
  324. 3 def begin_isolated_db_transaction(isolation)
  325. raise ActiveRecord::TransactionIsolationError, "adapter does not support setting transaction isolation"
  326. end
  327. # Commits the transaction (and turns on auto-committing).
  328. 3 def commit_db_transaction() end
  329. # Rolls back the transaction (and turns on auto-committing). Must be
  330. # done if the transaction block raises an exception or returns false.
  331. 3 def rollback_db_transaction
  332. 109343 exec_rollback_db_transaction
  333. end
  334. 3 def exec_rollback_db_transaction() end #:nodoc:
  335. 3 def rollback_to_savepoint(name = nil)
  336. 317 exec_rollback_to_savepoint(name)
  337. end
  338. 3 def default_sequence_name(table, column)
  339. nil
  340. end
  341. # Set the sequence to the max value of the table's column.
  342. 3 def reset_sequence!(table, column, sequence = nil)
  343. # Do nothing by default. Implement for PostgreSQL, Oracle, ...
  344. end
  345. # Inserts the given fixture into the table. Overridden in adapters that require
  346. # something beyond a simple insert (e.g. Oracle).
  347. # Most of adapters should implement `insert_fixtures_set` that leverages bulk SQL insert.
  348. # We keep this method to provide fallback
  349. # for databases like sqlite that do not support bulk inserts.
  350. 3 def insert_fixture(fixture, table_name)
  351. 1 execute(build_fixture_sql(Array.wrap(fixture), table_name), "Fixture Insert")
  352. end
  353. 3 def insert_fixtures_set(fixture_set, tables_to_delete = [])
  354. 1538 fixture_inserts = build_fixture_statements(fixture_set)
  355. 8283 table_deletes = tables_to_delete.map { |table| "DELETE FROM #{quote_table_name(table)}" }
  356. 1535 statements = table_deletes + fixture_inserts
  357. 1535 with_multi_statements do
  358. 1535 disable_referential_integrity do
  359. 1535 transaction(requires_new: true) do
  360. 1535 execute_batch(statements, "Fixtures Load")
  361. end
  362. end
  363. end
  364. end
  365. 3 def empty_insert_statement_value(primary_key = nil)
  366. 1294 "DEFAULT VALUES"
  367. end
  368. # Sanitizes the given LIMIT parameter in order to prevent SQL injection.
  369. #
  370. # The +limit+ may be anything that can evaluate to a string via #to_s. It
  371. # should look like an integer, or an Arel SQL literal.
  372. #
  373. # Returns Integer and Arel::Nodes::SqlLiteral limits as is.
  374. 3 def sanitize_limit(limit)
  375. 20267 if limit.is_a?(Integer) || limit.is_a?(Arel::Nodes::SqlLiteral)
  376. 20255 limit
  377. else
  378. 12 Integer(limit)
  379. end
  380. end
  381. # Fixture value is quoted by Arel, however scalar values
  382. # are not quotable. In this case we want to convert
  383. # the column value to YAML.
  384. 3 def with_yaml_fallback(value) # :nodoc:
  385. 700068 if value.is_a?(Hash) || value.is_a?(Array)
  386. 283 YAML.dump(value)
  387. else
  388. 699785 value
  389. end
  390. end
  391. 3 private
  392. 3 def execute_batch(statements, name = nil)
  393. statements.each do |statement|
  394. execute(statement, name)
  395. end
  396. end
  397. 3 DEFAULT_INSERT_VALUE = Arel.sql("DEFAULT").freeze
  398. 3 private_constant :DEFAULT_INSERT_VALUE
  399. 3 def default_insert_value(column)
  400. 644295 DEFAULT_INSERT_VALUE
  401. end
  402. 3 def build_fixture_sql(fixtures, table_name)
  403. 153450 columns = schema_cache.columns_hash(table_name)
  404. 153450 values_list = fixtures.map do |fixture|
  405. 295117 fixture = fixture.stringify_keys
  406. 295117 unknown_columns = fixture.keys - columns.keys
  407. 295117 if unknown_columns.any?
  408. 3 raise Fixture::FixtureError, %(table "#{table_name}" has no columns named #{unknown_columns.map(&:inspect).join(', ')}.)
  409. end
  410. 295114 columns.map do |name, column|
  411. 1343857 if fixture.key?(name)
  412. 699562 type = lookup_cast_type_from_column(column)
  413. 699562 with_yaml_fallback(type.serialize(fixture[name]))
  414. else
  415. 644295 default_insert_value(column)
  416. end
  417. end
  418. end
  419. 153447 table = Arel::Table.new(table_name)
  420. 153447 manager = Arel::InsertManager.new
  421. 153447 manager.into(table)
  422. 153447 if values_list.size == 1
  423. 151207 values = values_list.shift
  424. 151207 new_values = []
  425. 151207 columns.each_key.with_index { |column, i|
  426. 699272 unless values[i].equal?(DEFAULT_INSERT_VALUE)
  427. 366497 new_values << values[i]
  428. 366497 manager.columns << table[column]
  429. end
  430. }
  431. 151207 values_list << new_values
  432. else
  433. 21298 columns.each_key { |column| manager.columns << table[column] }
  434. end
  435. 153447 manager.values = manager.create_values_list(values_list)
  436. 153447 visitor.compile(manager.ast)
  437. end
  438. 3 def build_fixture_statements(fixture_set)
  439. fixture_set.map do |table_name, fixtures|
  440. 2692 next if fixtures.empty?
  441. 2692 build_fixture_sql(fixtures, table_name)
  442. 603 end.compact
  443. end
  444. 3 def build_truncate_statement(table_name)
  445. 2 "TRUNCATE TABLE #{quote_table_name(table_name)}"
  446. end
  447. 3 def build_truncate_statements(table_names)
  448. 7 table_names.map do |table_name|
  449. 582 build_truncate_statement(table_name)
  450. end
  451. end
  452. 3 def with_multi_statements
  453. 1547 yield
  454. end
  455. 3 def combine_multi_statements(total_sql)
  456. 1547 total_sql.join(";\n")
  457. end
  458. # Returns an ActiveRecord::Result instance.
  459. 3 def select(sql, name = nil, binds = [])
  460. 11236 exec_query(sql, name, binds, prepare: false)
  461. end
  462. 3 def select_prepared(sql, name = nil, binds = [])
  463. 27053 exec_query(sql, name, binds, prepare: true)
  464. end
  465. 3 def sql_for_insert(sql, pk, binds)
  466. 12492 [sql, binds]
  467. end
  468. 3 def last_inserted_id(result)
  469. 4137 single_value_from_rows(result.rows)
  470. end
  471. 3 def single_value_from_rows(rows)
  472. 61086 row = rows.first
  473. 61086 row && row.first
  474. end
  475. 3 def arel_from_relation(relation)
  476. 38690 if relation.is_a?(Relation)
  477. 57 relation.arel
  478. else
  479. 38633 relation
  480. end
  481. end
  482. end
  483. end
  484. end

lib/active_record/connection_adapters/abstract/query_cache.rb

100.0% lines covered

69 relevant lines. 69 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 require "concurrent/map"
  3. 3 module ActiveRecord
  4. 3 module ConnectionAdapters # :nodoc:
  5. 3 module QueryCache
  6. 3 class << self
  7. 3 def included(base) #:nodoc:
  8. 3 dirties_query_cache base, :insert, :update, :delete, :truncate, :truncate_tables,
  9. :rollback_to_savepoint, :rollback_db_transaction, :exec_insert_all
  10. 3 base.set_callback :checkout, :after, :configure_query_cache!
  11. 3 base.set_callback :checkin, :after, :disable_query_cache!
  12. end
  13. 3 def dirties_query_cache(base, *method_names)
  14. 3 method_names.each do |method_name|
  15. 24 base.class_eval <<-end_code, __FILE__, __LINE__ + 1
  16. def #{method_name}(*)
  17. ActiveRecord::Base.clear_query_caches_for_current_thread
  18. super
  19. end
  20. end_code
  21. end
  22. end
  23. end
  24. 3 module ConnectionPoolConfiguration
  25. 3 def initialize(*)
  26. 975 super
  27. 114475 @query_cache_enabled = Concurrent::Map.new { false }
  28. end
  29. 3 def enable_query_cache!
  30. 581 @query_cache_enabled[connection_cache_key(current_thread)] = true
  31. 581 connection.enable_query_cache! if active_connection?
  32. end
  33. 3 def disable_query_cache!
  34. 581 @query_cache_enabled.delete connection_cache_key(current_thread)
  35. 581 connection.disable_query_cache! if active_connection?
  36. end
  37. 3 def query_cache_enabled
  38. 113566 @query_cache_enabled[connection_cache_key(current_thread)]
  39. end
  40. end
  41. 3 attr_reader :query_cache, :query_cache_enabled
  42. 3 def initialize(*)
  43. 1052 super
  44. 1316 @query_cache = Hash.new { |h, sql| h[sql] = {} }
  45. 1052 @query_cache_enabled = false
  46. end
  47. # Enable the query cache within the block.
  48. 3 def cache
  49. 164 old, @query_cache_enabled = @query_cache_enabled, true
  50. 164 yield
  51. ensure
  52. 164 @query_cache_enabled = old
  53. 164 clear_query_cache unless @query_cache_enabled
  54. end
  55. 3 def enable_query_cache!
  56. 129 @query_cache_enabled = true
  57. end
  58. 3 def disable_query_cache!
  59. 113142 @query_cache_enabled = false
  60. 113142 clear_query_cache
  61. end
  62. # Disable the query cache within the block.
  63. 3 def uncached
  64. 1152 old, @query_cache_enabled = @query_cache_enabled, false
  65. 1152 yield
  66. ensure
  67. 1152 @query_cache_enabled = old
  68. end
  69. # Clears the query cache.
  70. #
  71. # One reason you may wish to call this method explicitly is between queries
  72. # that ask the database to randomize results. Otherwise the cache would see
  73. # the same SQL query and repeatedly return the same result each time, silently
  74. # undermining the randomness you were expecting.
  75. 3 def clear_query_cache
  76. 1067483 @lock.synchronize do
  77. 1067483 @query_cache.clear
  78. end
  79. end
  80. 3 def select_all(arel, name = nil, binds = [], preparable: nil)
  81. 38417 if @query_cache_enabled && !locked?(arel)
  82. 401 arel = arel_from_relation(arel)
  83. 401 sql, binds, preparable = to_sql_and_binds(arel, binds, preparable)
  84. 674 cache_sql(sql, name, binds) { super(sql, name, binds, preparable: preparable) }
  85. else
  86. 38016 super
  87. end
  88. end
  89. 3 private
  90. 3 def cache_sql(sql, name, binds)
  91. 401 @lock.synchronize do
  92. 401 result =
  93. 401 if @query_cache[sql].key?(binds)
  94. 128 ActiveSupport::Notifications.instrument(
  95. "sql.active_record",
  96. cache_notification_info(sql, name, binds)
  97. )
  98. 128 @query_cache[sql][binds]
  99. else
  100. 273 @query_cache[sql][binds] = yield
  101. end
  102. 398 result.dup
  103. end
  104. end
  105. # Database adapters can override this method to
  106. # provide custom cache information.
  107. 3 def cache_notification_info(sql, name, binds)
  108. 128 {
  109. sql: sql,
  110. binds: binds,
  111. 86 type_casted_binds: -> { type_casted_binds(binds) },
  112. name: name,
  113. connection: self,
  114. cached: true
  115. }
  116. end
  117. # If arel is locked this is a SELECT ... FOR UPDATE or somesuch. Such
  118. # queries should not be cached.
  119. 3 def locked?(arel)
  120. 407 arel = arel.arel if arel.is_a?(Relation)
  121. 407 arel.respond_to?(:locked) && arel.locked
  122. end
  123. 3 def configure_query_cache!
  124. 112953 enable_query_cache! if pool.query_cache_enabled
  125. end
  126. end
  127. end
  128. end

lib/active_record/connection_adapters/abstract/quoting.rb

94.9% lines covered

98 relevant lines. 93 lines covered and 5 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 require "active_support/core_ext/big_decimal/conversions"
  3. 3 require "active_support/multibyte/chars"
  4. 3 module ActiveRecord
  5. 3 module ConnectionAdapters # :nodoc:
  6. 3 module Quoting
  7. # Quotes the column value to help prevent
  8. # {SQL injection attacks}[https://en.wikipedia.org/wiki/SQL_injection].
  9. 3 def quote(value)
  10. 1288061 if value.is_a?(Base)
  11. 3 ActiveSupport::Deprecation.warn(<<~MSG)
  12. Passing an Active Record object to `quote` directly is deprecated
  13. and will be no longer quoted as id value in Rails 6.2.
  14. MSG
  15. 3 value = value.id_for_database
  16. end
  17. 1288061 _quote(value)
  18. end
  19. # Cast a +value+ to a type that the database understands. For example,
  20. # SQLite does not understand dates, so this method will convert a Date
  21. # to a String.
  22. 3 def type_cast(value, column = nil)
  23. 309245 if value.is_a?(Base)
  24. 3 ActiveSupport::Deprecation.warn(<<~MSG)
  25. Passing an Active Record object to `type_cast` directly is deprecated
  26. and will be no longer type casted as id value in Rails 6.2.
  27. MSG
  28. 3 value = value.id_for_database
  29. end
  30. 309245 if column
  31. 18 ActiveSupport::Deprecation.warn(<<~MSG)
  32. Passing a column to `type_cast` is deprecated and will be removed in Rails 6.2.
  33. MSG
  34. 18 type = lookup_cast_type_from_column(column)
  35. 18 value = type.serialize(value)
  36. end
  37. 309245 _type_cast(value)
  38. end
  39. # If you are having to call this function, you are likely doing something
  40. # wrong. The column does not have sufficient type information if the user
  41. # provided a custom type on the class level either explicitly (via
  42. # Attributes::ClassMethods#attribute) or implicitly (via
  43. # AttributeMethods::Serialization::ClassMethods#serialize, +time_zone_aware_attributes+).
  44. # In almost all cases, the sql type should only be used to change quoting behavior, when the primitive to
  45. # represent the type doesn't sufficiently reflect the differences
  46. # (varchar vs binary) for example. The type used to get this primitive
  47. # should have been provided before reaching the connection adapter.
  48. 3 def lookup_cast_type_from_column(column) # :nodoc:
  49. 384692 lookup_cast_type(column.sql_type)
  50. end
  51. # Quotes a string, escaping any ' (single quote) and \ (backslash)
  52. # characters.
  53. 3 def quote_string(s)
  54. 24 s.gsub('\\', '\&\&').gsub("'", "''") # ' (for ruby-mode)
  55. end
  56. # Quotes the column name. Defaults to no quoting.
  57. 3 def quote_column_name(column_name)
  58. 3259 column_name.to_s
  59. end
  60. # Quotes the table name. Defaults to column name quoting.
  61. 3 def quote_table_name(table_name)
  62. 1012 quote_column_name(table_name)
  63. end
  64. # Override to return the quoted table name for assignment. Defaults to
  65. # table quoting.
  66. #
  67. # This works for mysql2 where table.column can be used to
  68. # resolve ambiguity.
  69. #
  70. # We override this in the sqlite3 and postgresql adapters to use only
  71. # the column name (as per syntax requirements).
  72. 3 def quote_table_name_for_assignment(table, attr)
  73. quote_table_name("#{table}.#{attr}")
  74. end
  75. 3 def quote_default_expression(value, column) # :nodoc:
  76. 28317 if value.is_a?(Proc)
  77. value.call
  78. else
  79. 28317 value = lookup_cast_type(column.sql_type).serialize(value)
  80. 28317 quote(value)
  81. end
  82. end
  83. 3 def quoted_true
  84. 1131 "TRUE"
  85. end
  86. 3 def unquoted_true
  87. 155 true
  88. end
  89. 3 def quoted_false
  90. 340 "FALSE"
  91. end
  92. 3 def unquoted_false
  93. 116 false
  94. end
  95. # Quote date/time values for use in SQL input. Includes microseconds
  96. # if the value is a Time responding to usec.
  97. 3 def quoted_date(value)
  98. 57326 if value.acts_like?(:time)
  99. 55833 if ActiveRecord::Base.default_timezone == :utc
  100. 55780 value = value.getutc if value.respond_to?(:getutc) && !value.utc?
  101. else
  102. 53 value = value.getlocal if value.respond_to?(:getlocal)
  103. end
  104. end
  105. 57326 result = value.to_s(:db)
  106. 57326 if value.respond_to?(:usec) && value.usec > 0
  107. 49361 result << "." << sprintf("%06d", value.usec)
  108. else
  109. 7965 result
  110. end
  111. end
  112. 3 def quoted_time(value) # :nodoc:
  113. 296 value = value.change(year: 2000, month: 1, day: 1)
  114. 296 quoted_date(value).sub(/\A\d\d\d\d-\d\d-\d\d /, "")
  115. end
  116. 3 def quoted_binary(value) # :nodoc:
  117. "'#{quote_string(value.to_s)}'"
  118. end
  119. 3 def sanitize_as_sql_comment(value) # :nodoc:
  120. 135 value.to_s.gsub(%r{ (/ (?: | \g<1>) \*) \+? \s* | \s* (\* (?: | \g<2>) /) }x, "")
  121. end
  122. 3 def column_name_matcher # :nodoc:
  123. COLUMN_NAME
  124. end
  125. 3 def column_name_with_order_matcher # :nodoc:
  126. COLUMN_NAME_WITH_ORDER
  127. end
  128. # Regexp for column names (with or without a table name prefix).
  129. # Matches the following:
  130. #
  131. # "#{table_name}.#{column_name}"
  132. # "#{column_name}"
  133. 3 COLUMN_NAME = /
  134. \A
  135. (
  136. (?:
  137. # table_name.column_name | function(one or no argument)
  138. ((?:\w+\.)?\w+) | \w+\((?:|\g<2>)\)
  139. )
  140. (?:(?:\s+AS)?\s+\w+)?
  141. )
  142. (?:\s*,\s*\g<1>)*
  143. \z
  144. /ix
  145. # Regexp for column names with order (with or without a table name prefix,
  146. # with or without various order modifiers). Matches the following:
  147. #
  148. # "#{table_name}.#{column_name}"
  149. # "#{table_name}.#{column_name} #{direction}"
  150. # "#{table_name}.#{column_name} #{direction} NULLS FIRST"
  151. # "#{table_name}.#{column_name} NULLS LAST"
  152. # "#{column_name}"
  153. # "#{column_name} #{direction}"
  154. # "#{column_name} #{direction} NULLS FIRST"
  155. # "#{column_name} NULLS LAST"
  156. 3 COLUMN_NAME_WITH_ORDER = /
  157. \A
  158. (
  159. (?:
  160. # table_name.column_name | function(one or no argument)
  161. ((?:\w+\.)?\w+) | \w+\((?:|\g<2>)\)
  162. )
  163. (?:\s+ASC|\s+DESC)?
  164. (?:\s+NULLS\s+(?:FIRST|LAST))?
  165. )
  166. (?:\s*,\s*\g<1>)*
  167. \z
  168. /ix
  169. 3 private_constant :COLUMN_NAME, :COLUMN_NAME_WITH_ORDER
  170. 3 private
  171. 3 def type_casted_binds(binds)
  172. 168367 case binds.first
  173. when Array
  174. 36 binds.map { |column, value| type_cast(value, column) }
  175. else
  176. 168349 binds.map do |value|
  177. 309099 if ActiveModel::Attribute === value
  178. 112821 type_cast(value.value_for_database)
  179. else
  180. 196278 type_cast(value)
  181. end
  182. end
  183. end
  184. end
  185. 3 def lookup_cast_type(sql_type)
  186. 639400 type_map.lookup(sql_type)
  187. end
  188. 3 def _quote(value)
  189. 1288042 case value
  190. when String, Symbol, ActiveSupport::Multibyte::Chars
  191. 150268 "'#{quote_string(value.to_s)}'"
  192. 2673 when true then quoted_true
  193. 821 when false then quoted_false
  194. 18770 when nil then "NULL"
  195. # BigDecimals need to be put in a non-normalized form and quoted.
  196. 40 when BigDecimal then value.to_s("F")
  197. 1070709 when Numeric, ActiveSupport::Duration then value.to_s
  198. 214 when Type::Binary::Data then quoted_binary(value)
  199. 609 when Type::Time::Value then "'#{quoted_time(value)}'"
  200. 43932 when Date, Time then "'#{quoted_date(value)}'"
  201. 3 when Class then "'#{value}'"
  202. 3 else raise TypeError, "can't quote #{value.class.name}"
  203. end
  204. end
  205. 3 def _type_cast(value)
  206. 309239 case value
  207. when Symbol, ActiveSupport::Multibyte::Chars, Type::Binary::Data
  208. 23 value.to_s
  209. 432 when true then unquoted_true
  210. 314 when false then unquoted_false
  211. # BigDecimals need to be put in a non-normalized form and quoted.
  212. 35 when BigDecimal then value.to_s("F")
  213. 295681 when nil, Numeric, String then value
  214. 53 when Type::Time::Value then quoted_time(value)
  215. 12698 when Date, Time then quoted_date(value)
  216. 3 else raise TypeError, "can't cast #{value.class.name}"
  217. end
  218. end
  219. end
  220. end
  221. end

lib/active_record/connection_adapters/abstract/savepoints.rb

100.0% lines covered

11 relevant lines. 11 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 module ActiveRecord
  3. 3 module ConnectionAdapters
  4. 3 module Savepoints
  5. 3 def current_savepoint_name
  6. 16 current_transaction.savepoint_name
  7. end
  8. 3 def create_savepoint(name = current_savepoint_name)
  9. 11906 execute("SAVEPOINT #{name}", "TRANSACTION")
  10. end
  11. 3 def exec_rollback_to_savepoint(name = current_savepoint_name)
  12. 317 execute("ROLLBACK TO SAVEPOINT #{name}", "TRANSACTION")
  13. end
  14. 3 def release_savepoint(name = current_savepoint_name)
  15. 11597 execute("RELEASE SAVEPOINT #{name}", "TRANSACTION")
  16. end
  17. end
  18. end
  19. end

lib/active_record/connection_adapters/abstract/schema_creation.rb

98.17% lines covered

109 relevant lines. 107 lines covered and 2 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 module ActiveRecord
  3. 3 module ConnectionAdapters
  4. 3 class SchemaCreation # :nodoc:
  5. 3 def initialize(conn)
  6. 17900 @conn = conn
  7. 17900 @cache = {}
  8. end
  9. 3 def accept(o)
  10. 66089 m = @cache[o.class] ||= "visit_#{o.class.name.split('::').last}"
  11. 66089 send m, o
  12. end
  13. 3 delegate :quote_column_name, :quote_table_name, :quote_default_expression, :type_to_sql,
  14. :options_include_default?, :supports_indexes_in_create?, :supports_foreign_keys?, :foreign_key_options,
  15. :quoted_columns_for_index, :supports_partial_index?, :supports_check_constraints?, :check_constraint_options,
  16. to: :@conn, private: true
  17. 3 private
  18. 3 def visit_AlterTable(o)
  19. 771 sql = +"ALTER TABLE #{quote_table_name(o.name)} "
  20. 1460 sql << o.adds.map { |col| accept col }.join(" ")
  21. 814 sql << o.foreign_key_adds.map { |fk| visit_AddForeignKey fk }.join(" ")
  22. 790 sql << o.foreign_key_drops.map { |fk| visit_DropForeignKey fk }.join(" ")
  23. 772 sql << o.check_constraint_adds.map { |con| visit_AddCheckConstraint con }.join(" ")
  24. 767 sql << o.check_constraint_drops.map { |con| visit_DropCheckConstraint con }.join(" ")
  25. end
  26. 3 def visit_ColumnDefinition(o)
  27. 47127 o.sql_type = type_to_sql(o.type, **o.options)
  28. 47112 column_sql = +"#{quote_column_name(o.name)} #{o.sql_type}"
  29. 47112 add_column_options!(column_sql, column_options(o)) unless o.type == :primary_key
  30. 47112 column_sql
  31. end
  32. 3 def visit_AddColumnDefinition(o)
  33. 716 +"ADD #{accept(o.column)}"
  34. end
  35. 3 def visit_TableDefinition(o)
  36. 6715 create_sql = +"CREATE#{table_modifier_in_create(o)} TABLE "
  37. 6715 create_sql << "IF NOT EXISTS " if o.if_not_exists
  38. 6715 create_sql << "#{quote_table_name(o.name)} "
  39. 53117 statements = o.columns.map { |c| accept c }
  40. 6703 statements << accept(o.primary_keys) if o.primary_keys
  41. 6703 if supports_indexes_in_create?
  42. statements.concat(o.indexes.map { |column_name, options| index_in_create(o.name, column_name, options) })
  43. end
  44. 6703 if supports_foreign_keys?
  45. 6949 statements.concat(o.foreign_keys.map { |to_table, options| foreign_key_in_create(o.name, to_table, options) })
  46. end
  47. 6699 if supports_check_constraints?
  48. 6716 statements.concat(o.check_constraints.map { |expression, options| check_constraint_in_create(o.name, expression, options) })
  49. end
  50. 6699 create_sql << "(#{statements.join(', ')})" if statements.present?
  51. 6699 add_table_options!(create_sql, o)
  52. 6699 create_sql << " AS #{to_sql(o.as)}" if o.as
  53. 6699 create_sql
  54. end
  55. 3 def visit_PrimaryKeyDefinition(o)
  56. 201 "PRIMARY KEY (#{o.name.map { |name| quote_column_name(name) }.join(', ')})"
  57. end
  58. 3 def visit_ForeignKeyDefinition(o)
  59. 292 sql = +<<~SQL
  60. CONSTRAINT #{quote_column_name(o.name)}
  61. FOREIGN KEY (#{quote_column_name(o.column)})
  62. REFERENCES #{quote_table_name(o.to_table)} (#{quote_column_name(o.primary_key)})
  63. SQL
  64. 292 sql << " #{action_sql('DELETE', o.on_delete)}" if o.on_delete
  65. 289 sql << " #{action_sql('UPDATE', o.on_update)}" if o.on_update
  66. 286 sql
  67. end
  68. 3 def visit_AddForeignKey(o)
  69. 46 "ADD #{accept(o)}"
  70. end
  71. 3 def visit_DropForeignKey(name)
  72. 24 "DROP CONSTRAINT #{quote_column_name(name)}"
  73. end
  74. 3 def visit_CreateIndexDefinition(o)
  75. 10343 index = o.index
  76. 10343 sql = ["CREATE"]
  77. 10343 sql << "UNIQUE" if index.unique
  78. 10343 sql << "INDEX"
  79. 10343 sql << "IF NOT EXISTS" if o.if_not_exists
  80. 10343 sql << o.algorithm if o.algorithm
  81. 10343 sql << index.type if index.type
  82. 10343 sql << "#{quote_column_name(index.name)} ON #{quote_table_name(index.table)}"
  83. 10343 sql << "USING #{index.using}" if supports_index_using? && index.using
  84. 10343 sql << "(#{quoted_columns(index)})"
  85. 10343 sql << "WHERE #{index.where}" if supports_partial_index? && index.where
  86. 10343 sql.join(" ")
  87. end
  88. 3 def visit_CheckConstraintDefinition(o)
  89. 23 "CONSTRAINT #{o.name} CHECK (#{o.expression})"
  90. end
  91. 3 def visit_AddCheckConstraint(o)
  92. 6 "ADD #{accept(o)}"
  93. end
  94. 3 def visit_DropCheckConstraint(name)
  95. 1 "DROP CONSTRAINT #{quote_column_name(name)}"
  96. end
  97. 3 def quoted_columns(o)
  98. 10343 String === o.columns ? o.columns : quoted_columns_for_index(o.columns, o.column_options)
  99. end
  100. 3 def supports_index_using?
  101. 499 true
  102. end
  103. 3 def add_table_options!(create_sql, o)
  104. 6699 create_sql << " #{o.options}" if o.options
  105. 6699 create_sql
  106. end
  107. 3 def column_options(o)
  108. 43825 o.options.merge(column: o)
  109. end
  110. 3 def add_column_options!(sql, options)
  111. 43790 sql << " DEFAULT #{quote_default_expression(options[:default], options[:column])}" if options_include_default?(options)
  112. # must explicitly check for :null to allow change_column to work on migrations
  113. 43790 if options[:null] == false
  114. 12523 sql << " NOT NULL"
  115. end
  116. 43790 if options[:auto_increment] == true
  117. sql << " AUTO_INCREMENT"
  118. end
  119. 43790 if options[:primary_key] == true
  120. 2977 sql << " PRIMARY KEY"
  121. end
  122. 43790 sql
  123. end
  124. 3 def to_sql(sql)
  125. 6 sql = sql.to_sql if sql.respond_to?(:to_sql)
  126. 6 sql
  127. end
  128. # Returns any SQL string to go between CREATE and TABLE. May be nil.
  129. 3 def table_modifier_in_create(o)
  130. 5202 " TEMPORARY" if o.temporary
  131. end
  132. 3 def foreign_key_in_create(from_table, to_table, options)
  133. 246 prefix = ActiveRecord::Base.table_name_prefix
  134. 246 suffix = ActiveRecord::Base.table_name_suffix
  135. 246 to_table = "#{prefix}#{to_table}#{suffix}"
  136. 246 options = foreign_key_options(from_table, to_table, options)
  137. 246 accept ForeignKeyDefinition.new(from_table, to_table, options)
  138. end
  139. 3 def check_constraint_in_create(table_name, expression, options)
  140. 17 options = check_constraint_options(table_name, expression, options)
  141. 17 accept CheckConstraintDefinition.new(table_name, expression, options)
  142. end
  143. 3 def action_sql(action, dependency)
  144. 37 case dependency
  145. 9 when :nullify then "ON #{action} SET NULL"
  146. 19 when :cascade then "ON #{action} CASCADE"
  147. 3 when :restrict then "ON #{action} RESTRICT"
  148. else
  149. 6 raise ArgumentError, <<~MSG
  150. '#{dependency}' is not supported for :on_update or :on_delete.
  151. Supported values are: :nullify, :cascade, :restrict
  152. MSG
  153. end
  154. end
  155. end
  156. end
  157. end

lib/active_record/connection_adapters/abstract/schema_definitions.rb

99.61% lines covered

258 relevant lines. 257 lines covered and 1 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 module ActiveRecord
  3. 3 module ConnectionAdapters #:nodoc:
  4. # Abstract representation of an index definition on a table. Instances of
  5. # this type are typically created and returned by methods in database
  6. # adapters. e.g. ActiveRecord::ConnectionAdapters::MySQL::SchemaStatements#indexes
  7. 3 class IndexDefinition # :nodoc:
  8. 3 attr_reader :table, :name, :unique, :columns, :lengths, :orders, :opclasses, :where, :type, :using, :comment
  9. 3 def initialize(
  10. table, name,
  11. unique = false,
  12. columns = [],
  13. lengths: {},
  14. orders: {},
  15. opclasses: {},
  16. where: nil,
  17. type: nil,
  18. using: nil,
  19. comment: nil
  20. )
  21. 22287 @table = table
  22. 22287 @name = name
  23. 22287 @unique = unique
  24. 22287 @columns = columns
  25. 22287 @lengths = concise_options(lengths)
  26. 22287 @orders = concise_options(orders)
  27. 22287 @opclasses = concise_options(opclasses)
  28. 22287 @where = where
  29. 22287 @type = type
  30. 22287 @using = using
  31. 22287 @comment = comment
  32. end
  33. 3 def column_options
  34. 10308 {
  35. length: lengths,
  36. order: orders,
  37. opclass: opclasses,
  38. }
  39. end
  40. 3 private
  41. 3 def concise_options(options)
  42. 66861 if columns.size == options.size && options.values.uniq.size == 1
  43. 75 options.values.first
  44. else
  45. 66786 options
  46. end
  47. end
  48. end
  49. # Abstract representation of a column definition. Instances of this type
  50. # are typically created by methods in TableDefinition, and added to the
  51. # +columns+ attribute of said TableDefinition object, in order to be used
  52. # for generating a number of table creation or table changing SQL statements.
  53. 3 ColumnDefinition = Struct.new(:name, :type, :options, :sql_type) do # :nodoc:
  54. 3 def primary_key?
  55. 9 options[:primary_key]
  56. end
  57. 3 [:limit, :precision, :scale, :default, :null, :collation, :comment].each do |option_name|
  58. 21 module_eval <<-CODE, __FILE__, __LINE__ + 1
  59. def #{option_name}
  60. options[:#{option_name}]
  61. end
  62. def #{option_name}=(value)
  63. options[:#{option_name}] = value
  64. end
  65. CODE
  66. end
  67. end
  68. 3 AddColumnDefinition = Struct.new(:column) # :nodoc:
  69. 3 ChangeColumnDefinition = Struct.new(:column, :name) #:nodoc:
  70. 3 CreateIndexDefinition = Struct.new(:index, :algorithm, :if_not_exists) # :nodoc:
  71. 3 PrimaryKeyDefinition = Struct.new(:name) # :nodoc:
  72. 3 ForeignKeyDefinition = Struct.new(:from_table, :to_table, :options) do #:nodoc:
  73. 3 def name
  74. 661 options[:name]
  75. end
  76. 3 def column
  77. 592 options[:column]
  78. end
  79. 3 def primary_key
  80. 338 options[:primary_key] || default_primary_key
  81. end
  82. 3 def on_delete
  83. 583 options[:on_delete]
  84. end
  85. 3 def on_update
  86. 530 options[:on_update]
  87. end
  88. 3 def custom_primary_key?
  89. 226 options[:primary_key] != default_primary_key
  90. end
  91. 3 def validate?
  92. 56 options.fetch(:validate, true)
  93. end
  94. 3 alias validated? validate?
  95. 3 def export_name_on_schema_dump?
  96. 226 !ActiveRecord::SchemaDumper.fk_ignore_pattern.match?(name) if name
  97. end
  98. 3 def defined_for?(to_table: nil, validate: nil, **options)
  99. 51 (to_table.nil? || to_table.to_s == self.to_table) &&
  100. 48 (validate.nil? || validate == options.fetch(:validate, validate)) &&
  101. 41 options.all? { |k, v| self.options[k].to_s == v.to_s }
  102. end
  103. 3 private
  104. 3 def default_primary_key
  105. 482 "id"
  106. end
  107. end
  108. 3 CheckConstraintDefinition = Struct.new(:table_name, :expression, :options) do
  109. 3 def name
  110. 120 options[:name]
  111. end
  112. 3 def export_name_on_schema_dump?
  113. 25 !ActiveRecord::SchemaDumper.chk_ignore_pattern.match?(name) if name
  114. end
  115. end
  116. 3 class ReferenceDefinition # :nodoc:
  117. 3 def initialize(
  118. name,
  119. polymorphic: false,
  120. index: true,
  121. foreign_key: false,
  122. type: :bigint,
  123. **options
  124. )
  125. 1056 @name = name
  126. 1056 @polymorphic = polymorphic
  127. 1056 @index = index
  128. 1056 @foreign_key = foreign_key
  129. 1056 @type = type
  130. 1056 @options = options
  131. 1056 if polymorphic && foreign_key
  132. 6 raise ArgumentError, "Cannot add a foreign key to a polymorphic relation"
  133. end
  134. end
  135. 3 def add_to(table)
  136. 1050 columns.each do |name, type, options|
  137. 1120 table.column(name, type, **options)
  138. end
  139. 1050 if index
  140. 811 table.index(column_names, **index_options)
  141. end
  142. 1050 if foreign_key
  143. 150 table.foreign_key(foreign_table_name, **foreign_key_options)
  144. end
  145. end
  146. 3 private
  147. 3 attr_reader :name, :polymorphic, :index, :foreign_key, :type, :options
  148. 3 def as_options(value)
  149. 1223 value.is_a?(Hash) ? value : {}
  150. end
  151. 3 def polymorphic_options
  152. 112 as_options(polymorphic).merge(options.slice(:null, :first, :after))
  153. end
  154. 3 def index_options
  155. 811 as_options(index)
  156. end
  157. 3 def foreign_key_options
  158. 300 as_options(foreign_key).merge(column: column_name)
  159. end
  160. 3 def columns
  161. 1861 result = [[column_name, type, options]]
  162. 1861 if polymorphic
  163. 112 result.unshift(["#{name}_type", :string, polymorphic_options])
  164. end
  165. 1861 result
  166. end
  167. 3 def column_name
  168. 2161 "#{name}_id"
  169. end
  170. 3 def column_names
  171. 811 columns.map(&:first)
  172. end
  173. 3 def foreign_table_name
  174. 150 foreign_key_options.fetch(:to_table) do
  175. 126 Base.pluralize_table_names ? name.to_s.pluralize : name
  176. end
  177. end
  178. end
  179. 3 module ColumnMethods
  180. 3 extend ActiveSupport::Concern
  181. # Appends a primary key definition to the table definition.
  182. # Can be called multiple times, but this is probably not a good idea.
  183. 3 def primary_key(name, type = :primary_key, **options)
  184. 3426 column(name, type, **options.merge(primary_key: true))
  185. end
  186. ##
  187. # :method: column
  188. # :call-seq: column(name, type, **options)
  189. #
  190. # Appends a column or columns of a specified type.
  191. #
  192. # t.string(:goat)
  193. # t.string(:goat, :sheep)
  194. #
  195. # See TableDefinition#column
  196. 3 included do
  197. 6 define_column_methods :bigint, :binary, :boolean, :date, :datetime, :decimal,
  198. :float, :integer, :json, :string, :text, :time, :timestamp, :virtual
  199. 6 alias :numeric :decimal
  200. end
  201. 3 class_methods do
  202. 3 def define_column_methods(*column_types) # :nodoc:
  203. 10 column_types.each do |column_type|
  204. 204 module_eval <<-RUBY, __FILE__, __LINE__ + 1
  205. def #{column_type}(*names, **options)
  206. raise ArgumentError, "Missing column name(s) for #{column_type}" if names.empty?
  207. names.each { |name| column(name, :#{column_type}, **options) }
  208. end
  209. RUBY
  210. end
  211. end
  212. 3 private :define_column_methods
  213. end
  214. end
  215. # Represents the schema of an SQL table in an abstract way. This class
  216. # provides methods for manipulating the schema representation.
  217. #
  218. # Inside migration files, the +t+ object in {create_table}[rdoc-ref:SchemaStatements#create_table]
  219. # is actually of this type:
  220. #
  221. # class SomeMigration < ActiveRecord::Migration[6.0]
  222. # def up
  223. # create_table :foo do |t|
  224. # puts t.class # => "ActiveRecord::ConnectionAdapters::TableDefinition"
  225. # end
  226. # end
  227. #
  228. # def down
  229. # ...
  230. # end
  231. # end
  232. #
  233. 3 class TableDefinition
  234. 3 include ColumnMethods
  235. 3 attr_reader :name, :temporary, :if_not_exists, :options, :as, :comment, :indexes, :foreign_keys, :check_constraints
  236. 3 def initialize(
  237. conn,
  238. name,
  239. temporary: false,
  240. if_not_exists: false,
  241. options: nil,
  242. as: nil,
  243. comment: nil,
  244. **
  245. )
  246. 7564 @conn = conn
  247. 7564 @columns_hash = {}
  248. 7564 @indexes = []
  249. 7564 @foreign_keys = []
  250. 7564 @primary_keys = nil
  251. 7564 @check_constraints = []
  252. 7564 @temporary = temporary
  253. 7564 @if_not_exists = if_not_exists
  254. 7564 @options = options
  255. 7564 @as = as
  256. 7564 @name = name
  257. 7564 @comment = comment
  258. end
  259. 3 def primary_keys(name = nil) # :nodoc:
  260. 6837 @primary_keys = PrimaryKeyDefinition.new(name) if name
  261. 6837 @primary_keys
  262. end
  263. # Returns an array of ColumnDefinition objects for the columns of the table.
  264. 11104 def columns; @columns_hash.values; end
  265. # Returns a ColumnDefinition for the column with name +name+.
  266. 3 def [](name)
  267. 78 @columns_hash[name.to_s]
  268. end
  269. # Instantiates a new column for the table.
  270. # See {connection.add_column}[rdoc-ref:ConnectionAdapters::SchemaStatements#add_column]
  271. # for available options.
  272. #
  273. # Additional options are:
  274. # * <tt>:index</tt> -
  275. # Create an index for the column. Can be either <tt>true</tt> or an options hash.
  276. #
  277. # This method returns <tt>self</tt>.
  278. #
  279. # == Examples
  280. #
  281. # # Assuming +td+ is an instance of TableDefinition
  282. # td.column(:granted, :boolean, index: true)
  283. #
  284. # == Short-hand examples
  285. #
  286. # Instead of calling #column directly, you can also work with the short-hand definitions for the default types.
  287. # They use the type as the method name instead of as a parameter and allow for multiple columns to be defined
  288. # in a single statement.
  289. #
  290. # What can be written like this with the regular calls to column:
  291. #
  292. # create_table :products do |t|
  293. # t.column :shop_id, :integer
  294. # t.column :creator_id, :integer
  295. # t.column :item_number, :string
  296. # t.column :name, :string, default: "Untitled"
  297. # t.column :value, :string, default: "Untitled"
  298. # t.column :created_at, :datetime
  299. # t.column :updated_at, :datetime
  300. # end
  301. # add_index :products, :item_number
  302. #
  303. # can also be written as follows using the short-hand:
  304. #
  305. # create_table :products do |t|
  306. # t.integer :shop_id, :creator_id
  307. # t.string :item_number, index: true
  308. # t.string :name, :value, default: "Untitled"
  309. # t.timestamps null: false
  310. # end
  311. #
  312. # There's a short-hand method for each of the type values declared at the top. And then there's
  313. # TableDefinition#timestamps that'll add +created_at+ and +updated_at+ as datetimes.
  314. #
  315. # TableDefinition#references will add an appropriately-named _id column, plus a corresponding _type
  316. # column if the <tt>:polymorphic</tt> option is supplied. If <tt>:polymorphic</tt> is a hash of
  317. # options, these will be used when creating the <tt>_type</tt> column. The <tt>:index</tt> option
  318. # will also create an index, similar to calling {add_index}[rdoc-ref:ConnectionAdapters::SchemaStatements#add_index].
  319. # So what can be written like this:
  320. #
  321. # create_table :taggings do |t|
  322. # t.integer :tag_id, :tagger_id, :taggable_id
  323. # t.string :tagger_type
  324. # t.string :taggable_type, default: 'Photo'
  325. # end
  326. # add_index :taggings, :tag_id, name: 'index_taggings_on_tag_id'
  327. # add_index :taggings, [:tagger_id, :tagger_type]
  328. #
  329. # Can also be written as follows using references:
  330. #
  331. # create_table :taggings do |t|
  332. # t.references :tag, index: { name: 'index_taggings_on_tag_id' }
  333. # t.references :tagger, polymorphic: true
  334. # t.references :taggable, polymorphic: { default: 'Photo' }, index: false
  335. # end
  336. 3 def column(name, type, index: nil, **options)
  337. 46640 name = name.to_s
  338. 46640 type = type.to_sym if type
  339. 46640 if @columns_hash[name]
  340. 9 if @columns_hash[name].primary_key?
  341. 6 raise ArgumentError, "you can't redefine the primary key column '#{name}'. To define a custom primary key, pass { id: false } to create_table."
  342. else
  343. 3 raise ArgumentError, "you can't define an already defined column '#{name}'."
  344. end
  345. end
  346. 46631 @columns_hash[name] = new_column_definition(name, type, **options)
  347. 46631 if index
  348. 9 index_options = index.is_a?(Hash) ? index : {}
  349. 9 index(name, **index_options)
  350. end
  351. 46631 self
  352. end
  353. # remove the column +name+ from the table.
  354. # remove_column(:account_id)
  355. 3 def remove_column(name)
  356. 1164 @columns_hash.delete name.to_s
  357. end
  358. # Adds index options to the indexes hash, keyed by column name
  359. # This is primarily used to track indexes that need to be created after the table
  360. #
  361. # index(:account_id, name: 'index_projects_on_account_id')
  362. 3 def index(column_name, **options)
  363. 955 indexes << [column_name, options]
  364. end
  365. 3 def foreign_key(table_name, **options) # :nodoc:
  366. 254 foreign_keys << [table_name, options]
  367. end
  368. 3 def check_constraint(expression, **options)
  369. 17 check_constraints << [expression, options]
  370. end
  371. # Appends <tt>:datetime</tt> columns <tt>:created_at</tt> and
  372. # <tt>:updated_at</tt> to the table. See {connection.add_timestamps}[rdoc-ref:SchemaStatements#add_timestamps]
  373. #
  374. # t.timestamps null: false
  375. 3 def timestamps(**options)
  376. 343 options[:null] = false if options[:null].nil?
  377. 343 if !options.key?(:precision) && @conn.supports_datetime_with_precision?
  378. 324 options[:precision] = 6
  379. end
  380. 343 column(:created_at, :datetime, **options)
  381. 343 column(:updated_at, :datetime, **options)
  382. end
  383. # Adds a reference.
  384. #
  385. # t.references(:user)
  386. # t.belongs_to(:supplier, foreign_key: true)
  387. # t.belongs_to(:supplier, foreign_key: true, type: :integer)
  388. #
  389. # See {connection.add_reference}[rdoc-ref:SchemaStatements#add_reference] for details of the options you can use.
  390. 3 def references(*args, **options)
  391. 981 args.each do |ref_name|
  392. 981 ReferenceDefinition.new(ref_name, **options).add_to(self)
  393. end
  394. end
  395. 3 alias :belongs_to :references
  396. 3 def new_column_definition(name, type, **options) # :nodoc:
  397. 47382 if integer_like_primary_key?(type, options)
  398. 53 type = integer_like_primary_key_type(type, options)
  399. end
  400. 47382 type = aliased_types(type.to_s, type)
  401. 47382 options[:primary_key] ||= type == :primary_key
  402. 47382 options[:null] = false if options[:primary_key]
  403. 47382 create_column_definition(name, type, options)
  404. end
  405. 3 private
  406. 3 def create_column_definition(name, type, options)
  407. 47382 ColumnDefinition.new(name, type, options)
  408. end
  409. 3 def aliased_types(name, fallback)
  410. 47382 "timestamp" == name ? :datetime : fallback
  411. end
  412. 3 def integer_like_primary_key?(type, options)
  413. 47382 options[:primary_key] && [:integer, :bigint].include?(type) && !options.key?(:default)
  414. end
  415. 3 def integer_like_primary_key_type(type, options)
  416. type
  417. end
  418. end
  419. 3 class AlterTable # :nodoc:
  420. 3 attr_reader :adds
  421. 3 attr_reader :foreign_key_adds, :foreign_key_drops
  422. 3 attr_reader :check_constraint_adds, :check_constraint_drops
  423. 3 def initialize(td)
  424. 771 @td = td
  425. 771 @adds = []
  426. 771 @foreign_key_adds = []
  427. 771 @foreign_key_drops = []
  428. 771 @check_constraint_adds = []
  429. 771 @check_constraint_drops = []
  430. end
  431. 826 def name; @td.name; end
  432. 3 def add_foreign_key(to_table, options)
  433. 46 @foreign_key_adds << ForeignKeyDefinition.new(name, to_table, options)
  434. end
  435. 3 def drop_foreign_key(name)
  436. 24 @foreign_key_drops << name
  437. end
  438. 3 def add_check_constraint(expression, options)
  439. 6 @check_constraint_adds << CheckConstraintDefinition.new(name, expression, options)
  440. end
  441. 3 def drop_check_constraint(constraint_name)
  442. 1 @check_constraint_drops << constraint_name
  443. end
  444. 3 def add_column(name, type, **options)
  445. 689 name = name.to_s
  446. 689 type = type.to_sym
  447. 689 @adds << AddColumnDefinition.new(@td.new_column_definition(name, type, **options))
  448. end
  449. end
  450. # Represents an SQL table in an abstract way for updating a table.
  451. # Also see TableDefinition and {connection.create_table}[rdoc-ref:SchemaStatements#create_table]
  452. #
  453. # Available transformations are:
  454. #
  455. # change_table :table do |t|
  456. # t.primary_key
  457. # t.column
  458. # t.index
  459. # t.rename_index
  460. # t.timestamps
  461. # t.change
  462. # t.change_default
  463. # t.change_null
  464. # t.rename
  465. # t.references
  466. # t.belongs_to
  467. # t.check_constraint
  468. # t.string
  469. # t.text
  470. # t.integer
  471. # t.bigint
  472. # t.float
  473. # t.decimal
  474. # t.numeric
  475. # t.datetime
  476. # t.timestamp
  477. # t.time
  478. # t.date
  479. # t.binary
  480. # t.boolean
  481. # t.foreign_key
  482. # t.json
  483. # t.virtual
  484. # t.remove
  485. # t.remove_foreign_key
  486. # t.remove_references
  487. # t.remove_belongs_to
  488. # t.remove_index
  489. # t.remove_check_constraint
  490. # t.remove_timestamps
  491. # end
  492. #
  493. 3 class Table
  494. 3 include ColumnMethods
  495. 3 attr_reader :name
  496. 3 def initialize(table_name, base)
  497. 309 @name = table_name
  498. 309 @base = base
  499. end
  500. # Adds a new column to the named table.
  501. #
  502. # t.column(:name, :string)
  503. #
  504. # See TableDefinition#column for details of the options you can use.
  505. 3 def column(column_name, type, index: nil, **options)
  506. 173 @base.add_column(name, column_name, type, **options)
  507. 173 if index
  508. 3 index_options = index.is_a?(Hash) ? index : {}
  509. 3 index(column_name, **index_options)
  510. end
  511. end
  512. # Checks to see if a column exists.
  513. #
  514. # t.string(:name) unless t.column_exists?(:name, :string)
  515. #
  516. # See {connection.column_exists?}[rdoc-ref:SchemaStatements#column_exists?]
  517. 3 def column_exists?(column_name, type = nil, **options)
  518. 6 @base.column_exists?(name, column_name, type, **options)
  519. end
  520. # Adds a new index to the table. +column_name+ can be a single Symbol, or
  521. # an Array of Symbols.
  522. #
  523. # t.index(:name)
  524. # t.index([:branch_id, :party_id], unique: true)
  525. # t.index([:branch_id, :party_id], unique: true, name: 'by_branch_party')
  526. #
  527. # See {connection.add_index}[rdoc-ref:SchemaStatements#add_index] for details of the options you can use.
  528. 3 def index(column_name, **options)
  529. 81 @base.add_index(name, column_name, **options)
  530. end
  531. # Checks to see if an index exists.
  532. #
  533. # unless t.index_exists?(:branch_id)
  534. # t.index(:branch_id)
  535. # end
  536. #
  537. # See {connection.index_exists?}[rdoc-ref:SchemaStatements#index_exists?]
  538. 3 def index_exists?(column_name, options = {})
  539. 6 @base.index_exists?(name, column_name, options)
  540. end
  541. # Renames the given index on the table.
  542. #
  543. # t.rename_index(:user_id, :account_id)
  544. #
  545. # See {connection.rename_index}[rdoc-ref:SchemaStatements#rename_index]
  546. 3 def rename_index(index_name, new_index_name)
  547. 3 @base.rename_index(name, index_name, new_index_name)
  548. end
  549. # Adds timestamps (+created_at+ and +updated_at+) columns to the table.
  550. #
  551. # t.timestamps(null: false)
  552. #
  553. # See {connection.add_timestamps}[rdoc-ref:SchemaStatements#add_timestamps]
  554. 3 def timestamps(**options)
  555. 20 @base.add_timestamps(name, **options)
  556. end
  557. # Changes the column's definition according to the new options.
  558. #
  559. # t.change(:name, :string, limit: 80)
  560. # t.change(:description, :text)
  561. #
  562. # See TableDefinition#column for details of the options you can use.
  563. 3 def change(column_name, type, **options)
  564. 8 @base.change_column(name, column_name, type, **options)
  565. end
  566. # Sets a new default value for a column.
  567. #
  568. # t.change_default(:qualification, 'new')
  569. # t.change_default(:authorized, 1)
  570. # t.change_default(:status, from: nil, to: "draft")
  571. #
  572. # See {connection.change_column_default}[rdoc-ref:SchemaStatements#change_column_default]
  573. 3 def change_default(column_name, default_or_changes)
  574. 3 @base.change_column_default(name, column_name, default_or_changes)
  575. end
  576. # Sets or removes a NOT NULL constraint on a column.
  577. #
  578. # t.change_null(:qualification, true)
  579. # t.change_null(:qualification, false, 0)
  580. #
  581. # See {connection.change_column_null}[rdoc-ref:SchemaStatements#change_column_null]
  582. 3 def change_null(column_name, null, default = nil)
  583. 3 @base.change_column_null(name, column_name, null, default)
  584. end
  585. # Removes the column(s) from the table definition.
  586. #
  587. # t.remove(:qualification)
  588. # t.remove(:qualification, :experience)
  589. #
  590. # See {connection.remove_columns}[rdoc-ref:SchemaStatements#remove_columns]
  591. 3 def remove(*column_names, **options)
  592. 19 @base.remove_columns(name, *column_names, **options)
  593. end
  594. # Removes the given index from the table.
  595. #
  596. # t.remove_index(:branch_id)
  597. # t.remove_index(column: [:branch_id, :party_id])
  598. # t.remove_index(name: :by_branch_party)
  599. # t.remove_index(:branch_id, name: :by_branch_party)
  600. #
  601. # See {connection.remove_index}[rdoc-ref:SchemaStatements#remove_index]
  602. 3 def remove_index(column_name = nil, **options)
  603. 17 @base.remove_index(name, column_name, **options)
  604. end
  605. # Removes the timestamp columns (+created_at+ and +updated_at+) from the table.
  606. #
  607. # t.remove_timestamps
  608. #
  609. # See {connection.remove_timestamps}[rdoc-ref:SchemaStatements#remove_timestamps]
  610. 3 def remove_timestamps(**options)
  611. 3 @base.remove_timestamps(name, **options)
  612. end
  613. # Renames a column.
  614. #
  615. # t.rename(:description, :name)
  616. #
  617. # See {connection.rename_column}[rdoc-ref:SchemaStatements#rename_column]
  618. 3 def rename(column_name, new_column_name)
  619. 7 @base.rename_column(name, column_name, new_column_name)
  620. end
  621. # Adds a reference.
  622. #
  623. # t.references(:user)
  624. # t.belongs_to(:supplier, foreign_key: true)
  625. #
  626. # See {connection.add_reference}[rdoc-ref:SchemaStatements#add_reference] for details of the options you can use.
  627. 3 def references(*args, **options)
  628. 54 args.each do |ref_name|
  629. 54 @base.add_reference(name, ref_name, **options)
  630. end
  631. end
  632. 3 alias :belongs_to :references
  633. # Removes a reference. Optionally removes a +type+ column.
  634. #
  635. # t.remove_references(:user)
  636. # t.remove_belongs_to(:supplier, polymorphic: true)
  637. #
  638. # See {connection.remove_reference}[rdoc-ref:SchemaStatements#remove_reference]
  639. 3 def remove_references(*args, **options)
  640. 15 args.each do |ref_name|
  641. 15 @base.remove_reference(name, ref_name, **options)
  642. end
  643. end
  644. 3 alias :remove_belongs_to :remove_references
  645. # Adds a foreign key to the table using a supplied table name.
  646. #
  647. # t.foreign_key(:authors)
  648. # t.foreign_key(:authors, column: :author_id, primary_key: "id")
  649. #
  650. # See {connection.add_foreign_key}[rdoc-ref:SchemaStatements#add_foreign_key]
  651. 3 def foreign_key(*args, **options)
  652. 18 @base.add_foreign_key(name, *args, **options)
  653. end
  654. # Removes the given foreign key from the table.
  655. #
  656. # t.remove_foreign_key(:authors)
  657. # t.remove_foreign_key(column: :author_id)
  658. #
  659. # See {connection.remove_foreign_key}[rdoc-ref:SchemaStatements#remove_foreign_key]
  660. 3 def remove_foreign_key(*args, **options)
  661. 9 @base.remove_foreign_key(name, *args, **options)
  662. end
  663. # Checks to see if a foreign key exists.
  664. #
  665. # t.foreign_key(:authors) unless t.foreign_key_exists?(:authors)
  666. #
  667. # See {connection.foreign_key_exists?}[rdoc-ref:SchemaStatements#foreign_key_exists?]
  668. 3 def foreign_key_exists?(*args, **options)
  669. 8 @base.foreign_key_exists?(name, *args, **options)
  670. end
  671. # Adds a check constraint.
  672. #
  673. # t.check_constraint("price > 0", name: "price_check")
  674. #
  675. # See {connection.add_check_constraint}[rdoc-ref:SchemaStatements#add_check_constraint]
  676. 3 def check_constraint(*args)
  677. 3 @base.add_check_constraint(name, *args)
  678. end
  679. # Removes the given check constraint from the table.
  680. #
  681. # t.remove_check_constraint(name: "price_check")
  682. #
  683. # See {connection.remove_check_constraint}[rdoc-ref:SchemaStatements#remove_check_constraint]
  684. 3 def remove_check_constraint(*args)
  685. 3 @base.remove_check_constraint(name, *args)
  686. end
  687. end
  688. end
  689. end

lib/active_record/connection_adapters/abstract/schema_dumper.rb

94.55% lines covered

55 relevant lines. 52 lines covered and 3 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 module ActiveRecord
  3. 3 module ConnectionAdapters # :nodoc:
  4. 3 class SchemaDumper < SchemaDumper # :nodoc:
  5. 3 def self.create(connection, options)
  6. 197 new(connection, options)
  7. end
  8. 3 private
  9. 3 def column_spec(column)
  10. 12909 [schema_type_with_virtual(column), prepare_column_options(column)]
  11. end
  12. 3 def column_spec_for_primary_key(column)
  13. 4020 spec = {}
  14. 4020 spec[:id] = schema_type(column).inspect unless default_primary_key?(column)
  15. 4020 spec.merge!(prepare_column_options(column).except!(:null))
  16. 4020 spec[:default] ||= "nil" if explicit_primary_key_default?(column)
  17. 4020 spec
  18. end
  19. 3 def prepare_column_options(column)
  20. 16929 spec = {}
  21. 16929 spec[:limit] = schema_limit(column)
  22. 16929 spec[:precision] = schema_precision(column)
  23. 16929 spec[:scale] = schema_scale(column)
  24. 16929 spec[:default] = schema_default(column)
  25. 16929 spec[:null] = "false" unless column.null
  26. 16929 spec[:collation] = schema_collation(column)
  27. 16929 spec[:comment] = column.comment.inspect if column.comment.present?
  28. 16929 spec.compact!
  29. 16929 spec
  30. end
  31. 3 def default_primary_key?(column)
  32. schema_type(column) == :bigint
  33. end
  34. 3 def explicit_primary_key_default?(column)
  35. false
  36. end
  37. 3 def schema_type_with_virtual(column)
  38. 12909 if @connection.supports_virtual_columns? && column.virtual?
  39. :virtual
  40. else
  41. 12909 schema_type(column)
  42. end
  43. end
  44. 3 def schema_type(column)
  45. 15367 if column.bigint?
  46. 811 :bigint
  47. else
  48. 14556 column.type
  49. end
  50. end
  51. 3 def schema_limit(column)
  52. 16929 limit = column.limit unless column.bigint?
  53. 16929 limit.inspect if limit && limit != @connection.native_database_types[column.type][:limit]
  54. end
  55. 3 def schema_precision(column)
  56. 16929 column.precision.inspect if column.precision
  57. end
  58. 3 def schema_scale(column)
  59. 16929 column.scale.inspect if column.scale
  60. end
  61. 3 def schema_default(column)
  62. 16929 return unless column.has_default?
  63. 3333 type = @connection.lookup_cast_type_from_column(column)
  64. 3333 default = type.deserialize(column.default)
  65. 3333 if default.nil?
  66. 1877 schema_expression(column)
  67. else
  68. 1456 type.type_cast_for_schema(default)
  69. end
  70. end
  71. 3 def schema_expression(column)
  72. 103 "-> { #{column.default_function.inspect} }" if column.default_function
  73. end
  74. 3 def schema_collation(column)
  75. 16929 column.collation.inspect if column.collation
  76. end
  77. end
  78. end
  79. end

lib/active_record/connection_adapters/abstract/schema_statements.rb

92.09% lines covered

430 relevant lines. 396 lines covered and 34 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 require "active_support/core_ext/string/access"
  3. 3 require "digest/sha2"
  4. 3 module ActiveRecord
  5. 3 module ConnectionAdapters # :nodoc:
  6. 3 module SchemaStatements
  7. 3 include ActiveRecord::Migration::JoinTable
  8. # Returns a hash of mappings from the abstract data types to the native
  9. # database types. See TableDefinition#column for details on the recognized
  10. # abstract data types.
  11. 3 def native_database_types
  12. {}
  13. end
  14. 3 def table_options(table_name)
  15. nil
  16. end
  17. # Returns the table comment that's stored in database metadata.
  18. 3 def table_comment(table_name)
  19. nil
  20. end
  21. # Truncates a table alias according to the limits of the current adapter.
  22. 3 def table_alias_for(table_name)
  23. 1031 table_name[0...table_alias_length].tr(".", "_")
  24. end
  25. # Returns the relation names useable to back Active Record models.
  26. # For most adapters this means all #tables and #views.
  27. 3 def data_sources
  28. 506 query_values(data_source_sql, "SCHEMA")
  29. rescue NotImplementedError
  30. tables | views
  31. end
  32. # Checks to see if the data source +name+ exists on the database.
  33. #
  34. # data_source_exists?(:ebooks)
  35. #
  36. 3 def data_source_exists?(name)
  37. 990 query_values(data_source_sql(name), "SCHEMA").any? if name.present?
  38. rescue NotImplementedError
  39. data_sources.include?(name.to_s)
  40. end
  41. # Returns an array of table names defined in the database.
  42. 3 def tables
  43. 1451 query_values(data_source_sql(type: "BASE TABLE"), "SCHEMA")
  44. end
  45. # Checks to see if the table +table_name+ exists on the database.
  46. #
  47. # table_exists?(:developers)
  48. #
  49. 3 def table_exists?(table_name)
  50. 316 query_values(data_source_sql(table_name, type: "BASE TABLE"), "SCHEMA").any? if table_name.present?
  51. rescue NotImplementedError
  52. tables.include?(table_name.to_s)
  53. end
  54. # Returns an array of view names defined in the database.
  55. 3 def views
  56. 7 query_values(data_source_sql(type: "VIEW"), "SCHEMA")
  57. end
  58. # Checks to see if the view +view_name+ exists on the database.
  59. #
  60. # view_exists?(:ebooks)
  61. #
  62. 3 def view_exists?(view_name)
  63. 70 query_values(data_source_sql(view_name, type: "VIEW"), "SCHEMA").any? if view_name.present?
  64. rescue NotImplementedError
  65. views.include?(view_name.to_s)
  66. end
  67. # Returns an array of indexes for the given table.
  68. 3 def indexes(table_name)
  69. raise NotImplementedError, "#indexes is not implemented"
  70. end
  71. # Checks to see if an index exists on a table for a given index definition.
  72. #
  73. # # Check an index exists
  74. # index_exists?(:suppliers, :company_id)
  75. #
  76. # # Check an index on multiple columns exists
  77. # index_exists?(:suppliers, [:company_id, :company_type])
  78. #
  79. # # Check a unique index exists
  80. # index_exists?(:suppliers, :company_id, unique: true)
  81. #
  82. # # Check an index with a custom name exists
  83. # index_exists?(:suppliers, :company_id, name: "idx_company_id")
  84. #
  85. 3 def index_exists?(table_name, column_name, **options)
  86. 147 checks = []
  87. 147 if column_name.present?
  88. 144 column_names = Array(column_name).map(&:to_s)
  89. 260 checks << lambda { |i| Array(i.columns) == column_names }
  90. end
  91. 156 checks << lambda { |i| i.unique } if options[:unique]
  92. 208 checks << lambda { |i| i.name == options[:name].to_s } if options[:name]
  93. 452 indexes(table_name).any? { |i| checks.all? { |check| check[i] } }
  94. end
  95. # Returns an array of +Column+ objects for the table specified by +table_name+.
  96. 3 def columns(table_name)
  97. 25680 table_name = table_name.to_s
  98. 25680 column_definitions(table_name).map do |field|
  99. 247260 new_column_from_field(table_name, field)
  100. end
  101. end
  102. # Checks to see if a column exists in a given table.
  103. #
  104. # # Check a column exists
  105. # column_exists?(:suppliers, :name)
  106. #
  107. # # Check a column exists of a particular type
  108. # column_exists?(:suppliers, :name, :string)
  109. #
  110. # # Check a column exists with a specific definition
  111. # column_exists?(:suppliers, :name, :string, limit: 100)
  112. # column_exists?(:suppliers, :name, :string, default: 'default')
  113. # column_exists?(:suppliers, :name, :string, null: false)
  114. # column_exists?(:suppliers, :tax, :decimal, precision: 8, scale: 2)
  115. #
  116. 3 def column_exists?(table_name, column_name, type = nil, **options)
  117. 211 column_name = column_name.to_s
  118. 211 checks = []
  119. 1047 checks << lambda { |c| c.name == column_name }
  120. 286 checks << lambda { |c| c.type == type.to_sym rescue nil } if type
  121. 211 column_options_keys.each do |attr|
  122. 1640 checks << lambda { |c| c.send(attr) == options[attr] } if options.key?(attr)
  123. end
  124. 2121 columns(table_name).any? { |c| checks.all? { |check| check[c] } }
  125. end
  126. # Returns just a table's primary key
  127. 3 def primary_key(table_name)
  128. 11352 pk = primary_keys(table_name)
  129. 11352 pk = pk.first unless pk.size > 1
  130. 11352 pk
  131. end
  132. # Creates a new table with the name +table_name+. +table_name+ may either
  133. # be a String or a Symbol.
  134. #
  135. # There are two ways to work with #create_table. You can use the block
  136. # form or the regular form, like this:
  137. #
  138. # === Block form
  139. #
  140. # # create_table() passes a TableDefinition object to the block.
  141. # # This form will not only create the table, but also columns for the
  142. # # table.
  143. #
  144. # create_table(:suppliers) do |t|
  145. # t.column :name, :string, limit: 60
  146. # # Other fields here
  147. # end
  148. #
  149. # === Block form, with shorthand
  150. #
  151. # # You can also use the column types as method calls, rather than calling the column method.
  152. # create_table(:suppliers) do |t|
  153. # t.string :name, limit: 60
  154. # # Other fields here
  155. # end
  156. #
  157. # === Regular form
  158. #
  159. # # Creates a table called 'suppliers' with no columns.
  160. # create_table(:suppliers)
  161. # # Add a column to 'suppliers'.
  162. # add_column(:suppliers, :name, :string, {limit: 60})
  163. #
  164. # The +options+ hash can include the following keys:
  165. # [<tt>:id</tt>]
  166. # Whether to automatically add a primary key column. Defaults to true.
  167. # Join tables for {ActiveRecord::Base.has_and_belongs_to_many}[rdoc-ref:Associations::ClassMethods#has_and_belongs_to_many] should set it to false.
  168. #
  169. # A Symbol can be used to specify the type of the generated primary key column.
  170. # [<tt>:primary_key</tt>]
  171. # The name of the primary key, if one is to be added automatically.
  172. # Defaults to +id+. If <tt>:id</tt> is false, then this option is ignored.
  173. #
  174. # If an array is passed, a composite primary key will be created.
  175. #
  176. # Note that Active Record models will automatically detect their
  177. # primary key. This can be avoided by using
  178. # {self.primary_key=}[rdoc-ref:AttributeMethods::PrimaryKey::ClassMethods#primary_key=] on the model
  179. # to define the key explicitly.
  180. #
  181. # [<tt>:options</tt>]
  182. # Any extra options you want appended to the table definition.
  183. # [<tt>:temporary</tt>]
  184. # Make a temporary table.
  185. # [<tt>:force</tt>]
  186. # Set to true to drop the table before creating it.
  187. # Set to +:cascade+ to drop dependent objects as well.
  188. # Defaults to false.
  189. # [<tt>:if_not_exists</tt>]
  190. # Set to true to avoid raising an error when the table already exists.
  191. # Defaults to false.
  192. # [<tt>:as</tt>]
  193. # SQL to use to generate the table. When this option is used, the block is
  194. # ignored, as are the <tt>:id</tt> and <tt>:primary_key</tt> options.
  195. #
  196. # ====== Add a backend specific option to the generated SQL (MySQL)
  197. #
  198. # create_table(:suppliers, options: 'ENGINE=InnoDB DEFAULT CHARSET=utf8mb4')
  199. #
  200. # generates:
  201. #
  202. # CREATE TABLE suppliers (
  203. # id bigint auto_increment PRIMARY KEY
  204. # ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
  205. #
  206. # ====== Rename the primary key column
  207. #
  208. # create_table(:objects, primary_key: 'guid') do |t|
  209. # t.column :name, :string, limit: 80
  210. # end
  211. #
  212. # generates:
  213. #
  214. # CREATE TABLE objects (
  215. # guid bigint auto_increment PRIMARY KEY,
  216. # name varchar(80)
  217. # )
  218. #
  219. # ====== Change the primary key column type
  220. #
  221. # create_table(:tags, id: :string) do |t|
  222. # t.column :label, :string
  223. # end
  224. #
  225. # generates:
  226. #
  227. # CREATE TABLE tags (
  228. # id varchar PRIMARY KEY,
  229. # label varchar
  230. # )
  231. #
  232. # ====== Create a composite primary key
  233. #
  234. # create_table(:orders, primary_key: [:product_id, :client_id]) do |t|
  235. # t.belongs_to :product
  236. # t.belongs_to :client
  237. # end
  238. #
  239. # generates:
  240. #
  241. # CREATE TABLE order (
  242. # product_id bigint NOT NULL,
  243. # client_id bigint NOT NULL
  244. # );
  245. #
  246. # ALTER TABLE ONLY "orders"
  247. # ADD CONSTRAINT orders_pkey PRIMARY KEY (product_id, client_id);
  248. #
  249. # ====== Do not add a primary key column
  250. #
  251. # create_table(:categories_suppliers, id: false) do |t|
  252. # t.column :category_id, :bigint
  253. # t.column :supplier_id, :bigint
  254. # end
  255. #
  256. # generates:
  257. #
  258. # CREATE TABLE categories_suppliers (
  259. # category_id bigint,
  260. # supplier_id bigint
  261. # )
  262. #
  263. # ====== Create a temporary table based on a query
  264. #
  265. # create_table(:long_query, temporary: true,
  266. # as: "SELECT * FROM orders INNER JOIN line_items ON order_id=orders.id")
  267. #
  268. # generates:
  269. #
  270. # CREATE TEMPORARY TABLE long_query AS
  271. # SELECT * FROM orders INNER JOIN line_items ON order_id=orders.id
  272. #
  273. # See also TableDefinition#column for details on how to create columns.
  274. 3 def create_table(table_name, id: :primary_key, primary_key: nil, force: nil, **options)
  275. 6731 td = create_table_definition(table_name, **extract_table_options!(options))
  276. 6731 if id && !td.as
  277. 3416 pk = primary_key || Base.get_primary_key(table_name.to_s.singularize)
  278. 3416 if id.is_a?(Hash)
  279. options.merge!(id.except(:type))
  280. id = id.fetch(:type, :primary_key)
  281. end
  282. 3416 if pk.is_a?(Array)
  283. 63 td.primary_keys pk
  284. else
  285. 3353 td.primary_key pk, id, **options
  286. end
  287. end
  288. 6731 yield td if block_given?
  289. 6716 if force
  290. 2246 drop_table(table_name, force: force, if_exists: true)
  291. else
  292. 4470 schema_cache.clear_data_source_cache!(table_name.to_s)
  293. end
  294. 6715 result = execute schema_creation.accept td
  295. 6696 unless supports_indexes_in_create?
  296. 6696 td.indexes.each do |column_name, index_options|
  297. 955 add_index(table_name, column_name, **index_options, if_not_exists: td.if_not_exists)
  298. end
  299. end
  300. 6696 if supports_comments? && !supports_comments_in_create?
  301. 1506 if table_comment = td.comment.presence
  302. 35 change_table_comment(table_name, table_comment)
  303. end
  304. 1506 td.columns.each do |column|
  305. 4873 change_column_comment(table_name, column.name, column.comment) if column.comment.present?
  306. end
  307. end
  308. 6696 result
  309. end
  310. # Creates a new join table with the name created using the lexical order of the first two
  311. # arguments. These arguments can be a String or a Symbol.
  312. #
  313. # # Creates a table called 'assemblies_parts' with no id.
  314. # create_join_table(:assemblies, :parts)
  315. #
  316. # You can pass an +options+ hash which can include the following keys:
  317. # [<tt>:table_name</tt>]
  318. # Sets the table name, overriding the default.
  319. # [<tt>:column_options</tt>]
  320. # Any extra options you want appended to the columns definition.
  321. # [<tt>:options</tt>]
  322. # Any extra options you want appended to the table definition.
  323. # [<tt>:temporary</tt>]
  324. # Make a temporary table.
  325. # [<tt>:force</tt>]
  326. # Set to true to drop the table before creating it.
  327. # Defaults to false.
  328. #
  329. # Note that #create_join_table does not create any indices by default; you can use
  330. # its block form to do so yourself:
  331. #
  332. # create_join_table :products, :categories do |t|
  333. # t.index :product_id
  334. # t.index :category_id
  335. # end
  336. #
  337. # ====== Add a backend specific option to the generated SQL (MySQL)
  338. #
  339. # create_join_table(:assemblies, :parts, options: 'ENGINE=InnoDB DEFAULT CHARSET=utf8')
  340. #
  341. # generates:
  342. #
  343. # CREATE TABLE assemblies_parts (
  344. # assembly_id bigint NOT NULL,
  345. # part_id bigint NOT NULL,
  346. # ) ENGINE=InnoDB DEFAULT CHARSET=utf8
  347. #
  348. 3 def create_join_table(table_1, table_2, column_options: {}, **options)
  349. 67 join_table_name = find_join_table_name(table_1, table_2, options)
  350. 67 column_options.reverse_merge!(null: false, index: false)
  351. 201 t1_ref, t2_ref = [table_1, table_2].map { |t| t.to_s.singularize }
  352. 67 create_table(join_table_name, **options.merge!(id: false)) do |td|
  353. 67 td.references t1_ref, **column_options
  354. 67 td.references t2_ref, **column_options
  355. 67 yield td if block_given?
  356. end
  357. end
  358. # Drops the join table specified by the given arguments.
  359. # See #create_join_table for details.
  360. #
  361. # Although this command ignores the block if one is given, it can be helpful
  362. # to provide one in a migration's +change+ method so it can be reverted.
  363. # In that case, the block will be used by #create_join_table.
  364. 3 def drop_join_table(table_1, table_2, **options)
  365. 33 join_table_name = find_join_table_name(table_1, table_2, options)
  366. 33 drop_table(join_table_name)
  367. end
  368. # A block for changing columns in +table+.
  369. #
  370. # # change_table() yields a Table instance
  371. # change_table(:suppliers) do |t|
  372. # t.column :name, :string, limit: 60
  373. # # Other column alterations here
  374. # end
  375. #
  376. # The +options+ hash can include the following keys:
  377. # [<tt>:bulk</tt>]
  378. # Set this to true to make this a bulk alter query, such as
  379. #
  380. # ALTER TABLE `users` ADD COLUMN age INT, ADD COLUMN birthdate DATETIME ...
  381. #
  382. # Defaults to false.
  383. #
  384. # Only supported on the MySQL and PostgreSQL adapter, ignored elsewhere.
  385. #
  386. # ====== Add a column
  387. #
  388. # change_table(:suppliers) do |t|
  389. # t.column :name, :string, limit: 60
  390. # end
  391. #
  392. # ====== Change type of a column
  393. #
  394. # change_table(:suppliers) do |t|
  395. # t.change :metadata, :json
  396. # end
  397. #
  398. # ====== Add 2 integer columns
  399. #
  400. # change_table(:suppliers) do |t|
  401. # t.integer :width, :height, null: false, default: 0
  402. # end
  403. #
  404. # ====== Add created_at/updated_at columns
  405. #
  406. # change_table(:suppliers) do |t|
  407. # t.timestamps
  408. # end
  409. #
  410. # ====== Add a foreign key column
  411. #
  412. # change_table(:suppliers) do |t|
  413. # t.references :company
  414. # end
  415. #
  416. # Creates a <tt>company_id(bigint)</tt> column.
  417. #
  418. # ====== Add a polymorphic foreign key column
  419. #
  420. # change_table(:suppliers) do |t|
  421. # t.belongs_to :company, polymorphic: true
  422. # end
  423. #
  424. # Creates <tt>company_type(varchar)</tt> and <tt>company_id(bigint)</tt> columns.
  425. #
  426. # ====== Remove a column
  427. #
  428. # change_table(:suppliers) do |t|
  429. # t.remove :company
  430. # end
  431. #
  432. # ====== Remove several columns
  433. #
  434. # change_table(:suppliers) do |t|
  435. # t.remove :company_id
  436. # t.remove :width, :height
  437. # end
  438. #
  439. # ====== Remove an index
  440. #
  441. # change_table(:suppliers) do |t|
  442. # t.remove_index :company_id
  443. # end
  444. #
  445. # See also Table for details on all of the various column transformations.
  446. 3 def change_table(table_name, **options)
  447. 105 if supports_bulk_alter? && options[:bulk]
  448. 17 recorder = ActiveRecord::Migration::CommandRecorder.new(self)
  449. 17 yield update_table_definition(table_name, recorder)
  450. 17 bulk_change_table(table_name, recorder.commands)
  451. else
  452. 88 yield update_table_definition(table_name, self)
  453. end
  454. end
  455. # Renames a table.
  456. #
  457. # rename_table('octopuses', 'octopi')
  458. #
  459. 3 def rename_table(table_name, new_name)
  460. raise NotImplementedError, "rename_table is not implemented"
  461. end
  462. # Drops a table from the database.
  463. #
  464. # [<tt>:force</tt>]
  465. # Set to +:cascade+ to drop dependent objects as well.
  466. # Defaults to false.
  467. # [<tt>:if_exists</tt>]
  468. # Set to +true+ to only drop the table if it exists.
  469. # Defaults to false.
  470. #
  471. # Although this command ignores most +options+ and the block if one is given,
  472. # it can be helpful to provide these in a migration's +change+ method so it can be reverted.
  473. # In that case, +options+ and the block will be used by #create_table.
  474. 3 def drop_table(table_name, **options)
  475. 6684 schema_cache.clear_data_source_cache!(table_name.to_s)
  476. 6684 execute "DROP TABLE#{' IF EXISTS' if options[:if_exists]} #{quote_table_name(table_name)}"
  477. end
  478. # Add a new +type+ column named +column_name+ to +table_name+.
  479. #
  480. # The +type+ parameter is normally one of the migrations native types,
  481. # which is one of the following:
  482. # <tt>:primary_key</tt>, <tt>:string</tt>, <tt>:text</tt>,
  483. # <tt>:integer</tt>, <tt>:bigint</tt>, <tt>:float</tt>, <tt>:decimal</tt>, <tt>:numeric</tt>,
  484. # <tt>:datetime</tt>, <tt>:time</tt>, <tt>:date</tt>,
  485. # <tt>:binary</tt>, <tt>:boolean</tt>.
  486. #
  487. # You may use a type not in this list as long as it is supported by your
  488. # database (for example, "polygon" in MySQL), but this will not be database
  489. # agnostic and should usually be avoided.
  490. #
  491. # Available options are (none of these exists by default):
  492. # * <tt>:limit</tt> -
  493. # Requests a maximum column length. This is the number of characters for a <tt>:string</tt> column
  494. # and number of bytes for <tt>:text</tt>, <tt>:binary</tt>, and <tt>:integer</tt> columns.
  495. # This option is ignored by some backends.
  496. # * <tt>:default</tt> -
  497. # The column's default value. Use +nil+ for +NULL+.
  498. # * <tt>:null</tt> -
  499. # Allows or disallows +NULL+ values in the column.
  500. # * <tt>:precision</tt> -
  501. # Specifies the precision for the <tt>:decimal</tt>, <tt>:numeric</tt>,
  502. # <tt>:datetime</tt>, and <tt>:time</tt> columns.
  503. # * <tt>:scale</tt> -
  504. # Specifies the scale for the <tt>:decimal</tt> and <tt>:numeric</tt> columns.
  505. # * <tt>:collation</tt> -
  506. # Specifies the collation for a <tt>:string</tt> or <tt>:text</tt> column. If not specified, the
  507. # column will have the same collation as the table.
  508. # * <tt>:comment</tt> -
  509. # Specifies the comment for the column. This option is ignored by some backends.
  510. # * <tt>:if_not_exists</tt> -
  511. # Specifies if the column already exists to not try to re-add it. This will avoid
  512. # duplicate column errors.
  513. #
  514. # Note: The precision is the total number of significant digits,
  515. # and the scale is the number of digits that can be stored following
  516. # the decimal point. For example, the number 123.45 has a precision of 5
  517. # and a scale of 2. A decimal with a precision of 5 and a scale of 2 can
  518. # range from -999.99 to 999.99.
  519. #
  520. # Please be aware of different RDBMS implementations behavior with
  521. # <tt>:decimal</tt> columns:
  522. # * The SQL standard says the default scale should be 0, <tt>:scale</tt> <=
  523. # <tt>:precision</tt>, and makes no comments about the requirements of
  524. # <tt>:precision</tt>.
  525. # * MySQL: <tt>:precision</tt> [1..63], <tt>:scale</tt> [0..30].
  526. # Default is (10,0).
  527. # * PostgreSQL: <tt>:precision</tt> [1..infinity],
  528. # <tt>:scale</tt> [0..infinity]. No default.
  529. # * SQLite3: No restrictions on <tt>:precision</tt> and <tt>:scale</tt>,
  530. # but the maximum supported <tt>:precision</tt> is 16. No default.
  531. # * Oracle: <tt>:precision</tt> [1..38], <tt>:scale</tt> [-84..127].
  532. # Default is (38,0).
  533. # * SqlServer: <tt>:precision</tt> [1..38], <tt>:scale</tt> [0..38].
  534. # Default (38,0).
  535. #
  536. # == Examples
  537. #
  538. # add_column(:users, :picture, :binary, limit: 2.megabytes)
  539. # # ALTER TABLE "users" ADD "picture" blob(2097152)
  540. #
  541. # add_column(:articles, :status, :string, limit: 20, default: 'draft', null: false)
  542. # # ALTER TABLE "articles" ADD "status" varchar(20) DEFAULT 'draft' NOT NULL
  543. #
  544. # add_column(:answers, :bill_gates_money, :decimal, precision: 15, scale: 2)
  545. # # ALTER TABLE "answers" ADD "bill_gates_money" decimal(15,2)
  546. #
  547. # add_column(:measurements, :sensor_reading, :decimal, precision: 30, scale: 20)
  548. # # ALTER TABLE "measurements" ADD "sensor_reading" decimal(30,20)
  549. #
  550. # # While :scale defaults to zero on most databases, it
  551. # # probably wouldn't hurt to include it.
  552. # add_column(:measurements, :huge_integer, :decimal, precision: 30)
  553. # # ALTER TABLE "measurements" ADD "huge_integer" decimal(30)
  554. #
  555. # # Defines a column that stores an array of a type.
  556. # add_column(:users, :skills, :text, array: true)
  557. # # ALTER TABLE "users" ADD "skills" text[]
  558. #
  559. # # Defines a column with a database-specific type.
  560. # add_column(:shapes, :triangle, 'polygon')
  561. # # ALTER TABLE "shapes" ADD "triangle" polygon
  562. #
  563. # # Ignores the method call if the column exists
  564. # add_column(:shapes, :triangle, 'polygon', if_not_exists: true)
  565. 3 def add_column(table_name, column_name, type, **options)
  566. 692 return if options[:if_not_exists] == true && column_exists?(table_name, column_name, type)
  567. 689 at = create_alter_table table_name
  568. 689 at.add_column(column_name, type, **options)
  569. 689 execute schema_creation.accept at
  570. end
  571. 3 def add_columns(table_name, *column_names, type:, **options) # :nodoc:
  572. 3 column_names.each do |column_name|
  573. 3 add_column(table_name, column_name, type, **options)
  574. end
  575. end
  576. # Removes the given columns from the table definition.
  577. #
  578. # remove_columns(:suppliers, :qualification, :experience)
  579. #
  580. # +type+ and other column options can be passed to make migration reversible.
  581. #
  582. # remove_columns(:suppliers, :qualification, :experience, type: :string, null: false)
  583. 3 def remove_columns(table_name, *column_names, type: nil, **options)
  584. 3 if column_names.empty?
  585. raise ArgumentError.new("You must specify at least one column name. Example: remove_columns(:people, :first_name)")
  586. end
  587. 3 column_names.each do |column_name|
  588. 3 remove_column(table_name, column_name, type, **options)
  589. end
  590. end
  591. # Removes the column from the table definition.
  592. #
  593. # remove_column(:suppliers, :qualification)
  594. #
  595. # The +type+ and +options+ parameters will be ignored if present. It can be helpful
  596. # to provide these in a migration's +change+ method so it can be reverted.
  597. # In that case, +type+ and +options+ will be used by #add_column.
  598. # Indexes on the column are automatically removed.
  599. #
  600. # If the options provided include an +if_exists+ key, it will be used to check if the
  601. # column does not exist. This will silently ignore the migration rather than raising
  602. # if the column was already used.
  603. #
  604. # remove_column(:suppliers, :qualification, if_exists: true)
  605. 3 def remove_column(table_name, column_name, type = nil, **options)
  606. 697 return if options[:if_exists] == true && !column_exists?(table_name, column_name)
  607. 696 execute "ALTER TABLE #{quote_table_name(table_name)} #{remove_column_for_alter(table_name, column_name, type, **options)}"
  608. end
  609. # Changes the column's definition according to the new options.
  610. # See TableDefinition#column for details of the options you can use.
  611. #
  612. # change_column(:suppliers, :name, :string, limit: 80)
  613. # change_column(:accounts, :description, :text)
  614. #
  615. 3 def change_column(table_name, column_name, type, **options)
  616. raise NotImplementedError, "change_column is not implemented"
  617. end
  618. # Sets a new default value for a column:
  619. #
  620. # change_column_default(:suppliers, :qualification, 'new')
  621. # change_column_default(:accounts, :authorized, 1)
  622. #
  623. # Setting the default to +nil+ effectively drops the default:
  624. #
  625. # change_column_default(:users, :email, nil)
  626. #
  627. # Passing a hash containing +:from+ and +:to+ will make this change
  628. # reversible in migration:
  629. #
  630. # change_column_default(:posts, :state, from: nil, to: "draft")
  631. #
  632. 3 def change_column_default(table_name, column_name, default_or_changes)
  633. raise NotImplementedError, "change_column_default is not implemented"
  634. end
  635. # Sets or removes a <tt>NOT NULL</tt> constraint on a column. The +null+ flag
  636. # indicates whether the value can be +NULL+. For example
  637. #
  638. # change_column_null(:users, :nickname, false)
  639. #
  640. # says nicknames cannot be +NULL+ (adds the constraint), whereas
  641. #
  642. # change_column_null(:users, :nickname, true)
  643. #
  644. # allows them to be +NULL+ (drops the constraint).
  645. #
  646. # The method accepts an optional fourth argument to replace existing
  647. # <tt>NULL</tt>s with some other value. Use that one when enabling the
  648. # constraint if needed, since otherwise those rows would not be valid.
  649. #
  650. # Please note the fourth argument does not set a column's default.
  651. 3 def change_column_null(table_name, column_name, null, default = nil)
  652. raise NotImplementedError, "change_column_null is not implemented"
  653. end
  654. # Renames a column.
  655. #
  656. # rename_column(:suppliers, :description, :name)
  657. #
  658. 3 def rename_column(table_name, column_name, new_column_name)
  659. raise NotImplementedError, "rename_column is not implemented"
  660. end
  661. # Adds a new index to the table. +column_name+ can be a single Symbol, or
  662. # an Array of Symbols.
  663. #
  664. # The index will be named after the table and the column name(s), unless
  665. # you pass <tt>:name</tt> as an option.
  666. #
  667. # ====== Creating a simple index
  668. #
  669. # add_index(:suppliers, :name)
  670. #
  671. # generates:
  672. #
  673. # CREATE INDEX index_suppliers_on_name ON suppliers(name)
  674. #
  675. # ====== Creating a index which already exists
  676. #
  677. # add_index(:suppliers, :name, if_not_exists: true)
  678. #
  679. # generates:
  680. #
  681. # CREATE INDEX IF NOT EXISTS index_suppliers_on_name ON suppliers(name)
  682. #
  683. # Note: Not supported by MySQL.
  684. #
  685. # ====== Creating a unique index
  686. #
  687. # add_index(:accounts, [:branch_id, :party_id], unique: true)
  688. #
  689. # generates:
  690. #
  691. # CREATE UNIQUE INDEX index_accounts_on_branch_id_and_party_id ON accounts(branch_id, party_id)
  692. #
  693. # ====== Creating a named index
  694. #
  695. # add_index(:accounts, [:branch_id, :party_id], unique: true, name: 'by_branch_party')
  696. #
  697. # generates:
  698. #
  699. # CREATE UNIQUE INDEX by_branch_party ON accounts(branch_id, party_id)
  700. #
  701. # ====== Creating an index with specific key length
  702. #
  703. # add_index(:accounts, :name, name: 'by_name', length: 10)
  704. #
  705. # generates:
  706. #
  707. # CREATE INDEX by_name ON accounts(name(10))
  708. #
  709. # ====== Creating an index with specific key lengths for multiple keys
  710. #
  711. # add_index(:accounts, [:name, :surname], name: 'by_name_surname', length: {name: 10, surname: 15})
  712. #
  713. # generates:
  714. #
  715. # CREATE INDEX by_name_surname ON accounts(name(10), surname(15))
  716. #
  717. # Note: SQLite doesn't support index length.
  718. #
  719. # ====== Creating an index with a sort order (desc or asc, asc is the default)
  720. #
  721. # add_index(:accounts, [:branch_id, :party_id, :surname], name: 'by_branch_desc_party', order: {branch_id: :desc, party_id: :asc})
  722. #
  723. # generates:
  724. #
  725. # CREATE INDEX by_branch_desc_party ON accounts(branch_id DESC, party_id ASC, surname)
  726. #
  727. # Note: MySQL only supports index order from 8.0.1 onwards (earlier versions accepted the syntax but ignored it).
  728. #
  729. # ====== Creating a partial index
  730. #
  731. # add_index(:accounts, [:branch_id, :party_id], unique: true, where: "active")
  732. #
  733. # generates:
  734. #
  735. # CREATE UNIQUE INDEX index_accounts_on_branch_id_and_party_id ON accounts(branch_id, party_id) WHERE active
  736. #
  737. # Note: Partial indexes are only supported for PostgreSQL and SQLite.
  738. #
  739. # ====== Creating an index with a specific method
  740. #
  741. # add_index(:developers, :name, using: 'btree')
  742. #
  743. # generates:
  744. #
  745. # CREATE INDEX index_developers_on_name ON developers USING btree (name) -- PostgreSQL
  746. # CREATE INDEX index_developers_on_name USING btree ON developers (name) -- MySQL
  747. #
  748. # Note: only supported by PostgreSQL and MySQL
  749. #
  750. # ====== Creating an index with a specific operator class
  751. #
  752. # add_index(:developers, :name, using: 'gist', opclass: :gist_trgm_ops)
  753. # # CREATE INDEX developers_on_name ON developers USING gist (name gist_trgm_ops) -- PostgreSQL
  754. #
  755. # add_index(:developers, [:name, :city], using: 'gist', opclass: { city: :gist_trgm_ops })
  756. # # CREATE INDEX developers_on_name_and_city ON developers USING gist (name, city gist_trgm_ops) -- PostgreSQL
  757. #
  758. # add_index(:developers, [:name, :city], using: 'gist', opclass: :gist_trgm_ops)
  759. # # CREATE INDEX developers_on_name_and_city ON developers USING gist (name gist_trgm_ops, city gist_trgm_ops) -- PostgreSQL
  760. #
  761. # Note: only supported by PostgreSQL
  762. #
  763. # ====== Creating an index with a specific type
  764. #
  765. # add_index(:developers, :name, type: :fulltext)
  766. #
  767. # generates:
  768. #
  769. # CREATE FULLTEXT INDEX index_developers_on_name ON developers (name) -- MySQL
  770. #
  771. # Note: only supported by MySQL.
  772. #
  773. # ====== Creating an index with a specific algorithm
  774. #
  775. # add_index(:developers, :name, algorithm: :concurrently)
  776. # # CREATE INDEX CONCURRENTLY developers_on_name on developers (name)
  777. #
  778. # Note: only supported by PostgreSQL.
  779. #
  780. # Concurrently adding an index is not supported in a transaction.
  781. #
  782. # For more information see the {"Transactional Migrations" section}[rdoc-ref:Migration].
  783. 3 def add_index(table_name, column_name, **options)
  784. 9848 index, algorithm, if_not_exists = add_index_options(table_name, column_name, **options)
  785. 9844 create_index = CreateIndexDefinition.new(index, algorithm, if_not_exists)
  786. 9844 execute schema_creation.accept(create_index)
  787. end
  788. # Removes the given index from the table.
  789. #
  790. # Removes the index on +branch_id+ in the +accounts+ table if exactly one such index exists.
  791. #
  792. # remove_index :accounts, :branch_id
  793. #
  794. # Removes the index on +branch_id+ in the +accounts+ table if exactly one such index exists.
  795. #
  796. # remove_index :accounts, column: :branch_id
  797. #
  798. # Removes the index on +branch_id+ and +party_id+ in the +accounts+ table if exactly one such index exists.
  799. #
  800. # remove_index :accounts, column: [:branch_id, :party_id]
  801. #
  802. # Removes the index named +by_branch_party+ in the +accounts+ table.
  803. #
  804. # remove_index :accounts, name: :by_branch_party
  805. #
  806. # Removes the index on +branch_id+ named +by_branch_party+ in the +accounts+ table.
  807. #
  808. # remove_index :accounts, :branch_id, name: :by_branch_party
  809. #
  810. # Checks if the index exists before trying to remove it. Will silently ignore indexes that
  811. # don't exist.
  812. #
  813. # remove_index :accounts, if_exists: true
  814. #
  815. # Removes the index named +by_branch_party+ in the +accounts+ table +concurrently+.
  816. #
  817. # remove_index :accounts, name: :by_branch_party, algorithm: :concurrently
  818. #
  819. # Note: only supported by PostgreSQL.
  820. #
  821. # Concurrently removing an index is not supported in a transaction.
  822. #
  823. # For more information see the {"Transactional Migrations" section}[rdoc-ref:Migration].
  824. 3 def remove_index(table_name, column_name = nil, **options)
  825. return if options[:if_exists] && !index_exists?(table_name, column_name, **options)
  826. index_name = index_name_for_remove(table_name, column_name, options)
  827. execute "DROP INDEX #{quote_column_name(index_name)} ON #{quote_table_name(table_name)}"
  828. end
  829. # Renames an index.
  830. #
  831. # Rename the +index_people_on_last_name+ index to +index_users_on_last_name+:
  832. #
  833. # rename_index :people, 'index_people_on_last_name', 'index_users_on_last_name'
  834. #
  835. 3 def rename_index(table_name, old_name, new_name)
  836. 22 validate_index_length!(table_name, new_name)
  837. # this is a naive implementation; some DBs may support this more efficiently (PostgreSQL, for instance)
  838. 40 old_index_def = indexes(table_name).detect { |i| i.name == old_name }
  839. 20 return unless old_index_def
  840. 20 add_index(table_name, old_index_def.columns, name: new_name, unique: old_index_def.unique)
  841. 20 remove_index(table_name, name: old_name)
  842. end
  843. 3 def index_name(table_name, options) #:nodoc:
  844. 2743 if Hash === options
  845. 1403 if options[:column]
  846. 1403 "index_#{table_name}_on_#{Array(options[:column]) * '_and_'}"
  847. elsif options[:name]
  848. options[:name]
  849. else
  850. raise ArgumentError, "You must specify the index name"
  851. end
  852. else
  853. 1340 index_name(table_name, index_name_options(options))
  854. end
  855. end
  856. # Verifies the existence of an index with a given name.
  857. 3 def index_name_exists?(table_name, index_name)
  858. 22 index_name = index_name.to_s
  859. 42 indexes(table_name).detect { |i| i.name == index_name }
  860. end
  861. # Adds a reference. The reference column is a bigint by default,
  862. # the <tt>:type</tt> option can be used to specify a different type.
  863. # Optionally adds a +_type+ column, if <tt>:polymorphic</tt> option is provided.
  864. # #add_reference and #add_belongs_to are acceptable.
  865. #
  866. # The +options+ hash can include the following keys:
  867. # [<tt>:type</tt>]
  868. # The reference column type. Defaults to +:bigint+.
  869. # [<tt>:index</tt>]
  870. # Add an appropriate index. Defaults to true.
  871. # See #add_index for usage of this option.
  872. # [<tt>:foreign_key</tt>]
  873. # Add an appropriate foreign key constraint. Defaults to false, pass true
  874. # to add. In case the join table can't be inferred from the association
  875. # pass <tt>:to_table</tt> with the appropriate table name.
  876. # [<tt>:polymorphic</tt>]
  877. # Whether an additional +_type+ column should be added. Defaults to false.
  878. # [<tt>:null</tt>]
  879. # Whether the column allows nulls. Defaults to true.
  880. #
  881. # ====== Create a user_id bigint column without an index
  882. #
  883. # add_reference(:products, :user, index: false)
  884. #
  885. # ====== Create a user_id string column
  886. #
  887. # add_reference(:products, :user, type: :string)
  888. #
  889. # ====== Create supplier_id, supplier_type columns
  890. #
  891. # add_reference(:products, :supplier, polymorphic: true)
  892. #
  893. # ====== Create a supplier_id column with a unique index
  894. #
  895. # add_reference(:products, :supplier, index: { unique: true })
  896. #
  897. # ====== Create a supplier_id column with a named index
  898. #
  899. # add_reference(:products, :supplier, index: { name: "my_supplier_index" })
  900. #
  901. # ====== Create a supplier_id column and appropriate foreign key
  902. #
  903. # add_reference(:products, :supplier, foreign_key: true)
  904. #
  905. # ====== Create a supplier_id column and a foreign key to the firms table
  906. #
  907. # add_reference(:products, :supplier, foreign_key: { to_table: :firms })
  908. #
  909. 3 def add_reference(table_name, ref_name, **options)
  910. 75 ReferenceDefinition.new(ref_name, **options).add_to(update_table_definition(table_name, self))
  911. end
  912. 3 alias :add_belongs_to :add_reference
  913. # Removes the reference(s). Also removes a +type+ column if one exists.
  914. # #remove_reference and #remove_belongs_to are acceptable.
  915. #
  916. # ====== Remove the reference
  917. #
  918. # remove_reference(:products, :user, index: false)
  919. #
  920. # ====== Remove polymorphic reference
  921. #
  922. # remove_reference(:products, :supplier, polymorphic: true)
  923. #
  924. # ====== Remove the reference with a foreign key
  925. #
  926. # remove_reference(:products, :user, foreign_key: true)
  927. #
  928. 3 def remove_reference(table_name, ref_name, foreign_key: false, polymorphic: false, **options)
  929. 30 if foreign_key
  930. 12 reference_name = Base.pluralize_table_names ? ref_name.to_s.pluralize : ref_name
  931. 12 if foreign_key.is_a?(Hash)
  932. 6 foreign_key_options = foreign_key
  933. else
  934. 6 foreign_key_options = { to_table: reference_name }
  935. end
  936. 12 foreign_key_options[:column] ||= "#{ref_name}_id"
  937. 12 remove_foreign_key(table_name, **foreign_key_options)
  938. end
  939. 30 remove_column(table_name, "#{ref_name}_id")
  940. 30 remove_column(table_name, "#{ref_name}_type") if polymorphic
  941. end
  942. 3 alias :remove_belongs_to :remove_reference
  943. # Returns an array of foreign keys for the given table.
  944. # The foreign keys are represented as ForeignKeyDefinition objects.
  945. 3 def foreign_keys(table_name)
  946. raise NotImplementedError, "foreign_keys is not implemented"
  947. end
  948. # Adds a new foreign key. +from_table+ is the table with the key column,
  949. # +to_table+ contains the referenced primary key.
  950. #
  951. # The foreign key will be named after the following pattern: <tt>fk_rails_<identifier></tt>.
  952. # +identifier+ is a 10 character long string which is deterministically generated from the
  953. # +from_table+ and +column+. A custom name can be specified with the <tt>:name</tt> option.
  954. #
  955. # ====== Creating a simple foreign key
  956. #
  957. # add_foreign_key :articles, :authors
  958. #
  959. # generates:
  960. #
  961. # ALTER TABLE "articles" ADD CONSTRAINT fk_rails_e74ce85cbc FOREIGN KEY ("author_id") REFERENCES "authors" ("id")
  962. #
  963. # ====== Creating a foreign key on a specific column
  964. #
  965. # add_foreign_key :articles, :users, column: :author_id, primary_key: "lng_id"
  966. #
  967. # generates:
  968. #
  969. # ALTER TABLE "articles" ADD CONSTRAINT fk_rails_58ca3d3a82 FOREIGN KEY ("author_id") REFERENCES "users" ("lng_id")
  970. #
  971. # ====== Creating a cascading foreign key
  972. #
  973. # add_foreign_key :articles, :authors, on_delete: :cascade
  974. #
  975. # generates:
  976. #
  977. # ALTER TABLE "articles" ADD CONSTRAINT fk_rails_e74ce85cbc FOREIGN KEY ("author_id") REFERENCES "authors" ("id") ON DELETE CASCADE
  978. #
  979. # The +options+ hash can include the following keys:
  980. # [<tt>:column</tt>]
  981. # The foreign key column name on +from_table+. Defaults to <tt>to_table.singularize + "_id"</tt>
  982. # [<tt>:primary_key</tt>]
  983. # The primary key column name on +to_table+. Defaults to +id+.
  984. # [<tt>:name</tt>]
  985. # The constraint name. Defaults to <tt>fk_rails_<identifier></tt>.
  986. # [<tt>:on_delete</tt>]
  987. # Action that happens <tt>ON DELETE</tt>. Valid values are +:nullify+, +:cascade+ and +:restrict+
  988. # [<tt>:on_update</tt>]
  989. # Action that happens <tt>ON UPDATE</tt>. Valid values are +:nullify+, +:cascade+ and +:restrict+
  990. # [<tt>:validate</tt>]
  991. # (PostgreSQL only) Specify whether or not the constraint should be validated. Defaults to +true+.
  992. 3 def add_foreign_key(from_table, to_table, **options)
  993. 46 return unless supports_foreign_keys?
  994. 46 options = foreign_key_options(from_table, to_table, options)
  995. 46 at = create_alter_table from_table
  996. 46 at.add_foreign_key to_table, options
  997. 46 execute schema_creation.accept(at)
  998. end
  999. # Removes the given foreign key from the table. Any option parameters provided
  1000. # will be used to re-add the foreign key in case of a migration rollback.
  1001. # It is recommended that you provide any options used when creating the foreign
  1002. # key so that the migration can be reverted properly.
  1003. #
  1004. # Removes the foreign key on +accounts.branch_id+.
  1005. #
  1006. # remove_foreign_key :accounts, :branches
  1007. #
  1008. # Removes the foreign key on +accounts.owner_id+.
  1009. #
  1010. # remove_foreign_key :accounts, column: :owner_id
  1011. #
  1012. # Removes the foreign key on +accounts.owner_id+.
  1013. #
  1014. # remove_foreign_key :accounts, to_table: :owners
  1015. #
  1016. # Removes the foreign key named +special_fk_name+ on the +accounts+ table.
  1017. #
  1018. # remove_foreign_key :accounts, name: :special_fk_name
  1019. #
  1020. # The +options+ hash accepts the same keys as SchemaStatements#add_foreign_key
  1021. # with an addition of
  1022. # [<tt>:to_table</tt>]
  1023. # The name of the table that contains the referenced primary key.
  1024. 3 def remove_foreign_key(from_table, to_table = nil, **options)
  1025. 25 return unless supports_foreign_keys?
  1026. 25 fk_name_to_delete = foreign_key_for!(from_table, to_table: to_table, **options).name
  1027. 24 at = create_alter_table from_table
  1028. 24 at.drop_foreign_key fk_name_to_delete
  1029. 24 execute schema_creation.accept(at)
  1030. end
  1031. # Checks to see if a foreign key exists on a table for a given foreign key definition.
  1032. #
  1033. # # Checks to see if a foreign key exists.
  1034. # foreign_key_exists?(:accounts, :branches)
  1035. #
  1036. # # Checks to see if a foreign key on a specified column exists.
  1037. # foreign_key_exists?(:accounts, column: :owner_id)
  1038. #
  1039. # # Checks to see if a foreign key with a custom name exists.
  1040. # foreign_key_exists?(:accounts, name: "special_fk_name")
  1041. #
  1042. 3 def foreign_key_exists?(from_table, to_table = nil, **options)
  1043. 22 foreign_key_for(from_table, to_table: to_table, **options).present?
  1044. end
  1045. 3 def foreign_key_column_for(table_name) # :nodoc:
  1046. 274 name = strip_table_name_prefix_and_suffix(table_name)
  1047. 274 "#{name.singularize}_id"
  1048. end
  1049. 3 def foreign_key_options(from_table, to_table, options) # :nodoc:
  1050. 292 options = options.dup
  1051. 292 options[:column] ||= foreign_key_column_for(to_table)
  1052. 292 options[:name] ||= foreign_key_name(from_table, options)
  1053. 292 options
  1054. end
  1055. # Returns an array of check constraints for the given table.
  1056. # The check constraints are represented as CheckConstraintDefinition objects.
  1057. 3 def check_constraints(table_name)
  1058. raise NotImplementedError
  1059. end
  1060. # Adds a new check constraint to the table. +expression+ is a String
  1061. # representation of verifiable boolean condition.
  1062. #
  1063. # add_check_constraint :products, "price > 0", name: "price_check"
  1064. #
  1065. # generates:
  1066. #
  1067. # ALTER TABLE "products" ADD CONSTRAINT price_check CHECK (price > 0)
  1068. #
  1069. 3 def add_check_constraint(table_name, expression, **options)
  1070. 6 return unless supports_check_constraints?
  1071. 6 options = check_constraint_options(table_name, expression, options)
  1072. 6 at = create_alter_table(table_name)
  1073. 6 at.add_check_constraint(expression, options)
  1074. 6 execute schema_creation.accept(at)
  1075. end
  1076. 3 def check_constraint_options(table_name, expression, options) # :nodoc:
  1077. 23 options = options.dup
  1078. 23 options[:name] ||= check_constraint_name(table_name, expression: expression, **options)
  1079. 23 options
  1080. end
  1081. # Removes the given check constraint from the table.
  1082. #
  1083. # remove_check_constraint :products, name: "price_check"
  1084. #
  1085. # The +expression+ parameter will be ignored if present. It can be helpful
  1086. # to provide this in a migration's +change+ method so it can be reverted.
  1087. # In that case, +expression+ will be used by #add_check_constraint.
  1088. 3 def remove_check_constraint(table_name, expression = nil, **options)
  1089. 2 return unless supports_check_constraints?
  1090. 2 chk_name_to_delete = check_constraint_for!(table_name, expression: expression, **options).name
  1091. 1 at = create_alter_table(table_name)
  1092. 1 at.drop_check_constraint(chk_name_to_delete)
  1093. 1 execute schema_creation.accept(at)
  1094. end
  1095. 3 def dump_schema_information # :nodoc:
  1096. 6 versions = schema_migration.all_versions
  1097. 6 insert_versions_sql(versions) if versions.any?
  1098. end
  1099. 3 def internal_string_options_for_primary_key # :nodoc:
  1100. 46 { primary_key: true }
  1101. end
  1102. 3 def assume_migrated_upto_version(version, migrations_paths = nil)
  1103. 15 unless migrations_paths.nil?
  1104. 3 ActiveSupport::Deprecation.warn(<<~MSG.squish)
  1105. Passing migrations_paths to #assume_migrated_upto_version is deprecated and will be removed in Rails 6.1.
  1106. MSG
  1107. end
  1108. 15 version = version.to_i
  1109. 15 sm_table = quote_table_name(schema_migration.table_name)
  1110. 15 migrated = migration_context.get_all_versions
  1111. 15 versions = migration_context.migrations.map(&:version)
  1112. 15 unless migrated.include?(version)
  1113. 15 execute "INSERT INTO #{sm_table} (version) VALUES (#{quote(version)})"
  1114. end
  1115. 24 inserting = (versions - migrated).select { |v| v < version }
  1116. 15 if inserting.any?
  1117. 9 if (duplicate = inserting.detect { |v| inserting.count(v) > 1 })
  1118. raise "Duplicate migration #{duplicate}. Please renumber your migrations to resolve the conflict."
  1119. end
  1120. 3 execute insert_versions_sql(inserting)
  1121. end
  1122. end
  1123. 3 def type_to_sql(type, limit: nil, precision: nil, scale: nil, **) # :nodoc:
  1124. 46574 type = type.to_sym if type
  1125. 46574 if native = native_database_types[type]
  1126. 46065 column_type_sql = (native.is_a?(Hash) ? native[:name] : native).dup
  1127. 46065 if type == :decimal # ignore limit, use precision and scale
  1128. 217 scale ||= native[:scale]
  1129. 217 if precision ||= native[:precision]
  1130. 162 if scale
  1131. 142 column_type_sql << "(#{precision},#{scale})"
  1132. else
  1133. 20 column_type_sql << "(#{precision})"
  1134. end
  1135. 55 elsif scale
  1136. 3 raise ArgumentError, "Error adding decimal column: precision cannot be empty if scale is specified"
  1137. end
  1138. 45848 elsif [:datetime, :timestamp, :time, :interval].include?(type) && precision ||= native[:precision]
  1139. 5570 if (0..6) === precision
  1140. 5564 column_type_sql << "(#{precision})"
  1141. else
  1142. 6 raise ArgumentError, "No #{native[:name]} type has precision of #{precision}. The allowed range of precision is from 0 to 6"
  1143. end
  1144. 40278 elsif (type != :primary_key) && (limit ||= native.is_a?(Hash) && native[:limit])
  1145. 2765 column_type_sql << "(#{limit})"
  1146. end
  1147. 46056 column_type_sql
  1148. else
  1149. 509 type.to_s
  1150. end
  1151. end
  1152. # Given a set of columns and an ORDER BY clause, returns the columns for a SELECT DISTINCT.
  1153. # PostgreSQL, MySQL, and Oracle override this for custom DISTINCT syntax - they
  1154. # require the order columns appear in the SELECT.
  1155. #
  1156. # columns_for_distinct("posts.id", ["posts.created_at desc"])
  1157. #
  1158. 3 def columns_for_distinct(columns, orders) # :nodoc:
  1159. 261 columns
  1160. end
  1161. # Adds timestamps (+created_at+ and +updated_at+) columns to +table_name+.
  1162. # Additional options (like +:null+) are forwarded to #add_column.
  1163. #
  1164. # add_timestamps(:suppliers, null: true)
  1165. #
  1166. 3 def add_timestamps(table_name, **options)
  1167. 24 options[:null] = false if options[:null].nil?
  1168. 24 if !options.key?(:precision) && supports_datetime_with_precision?
  1169. 12 options[:precision] = 6
  1170. end
  1171. 24 add_column table_name, :created_at, :datetime, **options
  1172. 24 add_column table_name, :updated_at, :datetime, **options
  1173. end
  1174. # Removes the timestamp columns (+created_at+ and +updated_at+) from the table definition.
  1175. #
  1176. # remove_timestamps(:suppliers)
  1177. #
  1178. 3 def remove_timestamps(table_name, **options)
  1179. remove_column table_name, :updated_at
  1180. remove_column table_name, :created_at
  1181. end
  1182. 3 def update_table_definition(table_name, base) #:nodoc:
  1183. 190 Table.new(table_name, base)
  1184. end
  1185. 3 def add_index_options(table_name, column_name, name: nil, if_not_exists: false, internal: false, **options) # :nodoc:
  1186. 10351 options.assert_valid_keys(:unique, :length, :order, :opclass, :where, :type, :using, :comment, :algorithm)
  1187. 10348 column_names = index_column_names(column_name)
  1188. 10348 index_name = name&.to_s
  1189. 10348 index_name ||= index_name(table_name, column_names)
  1190. 10348 validate_index_length!(table_name, index_name, internal)
  1191. 10345 index = IndexDefinition.new(
  1192. table_name, index_name,
  1193. options[:unique],
  1194. column_names,
  1195. lengths: options[:length] || {},
  1196. orders: options[:order] || {},
  1197. opclasses: options[:opclass] || {},
  1198. where: options[:where],
  1199. type: options[:type],
  1200. using: options[:using],
  1201. comment: options[:comment]
  1202. )
  1203. 10345 [index, index_algorithm(options[:algorithm]), if_not_exists]
  1204. end
  1205. 3 def index_algorithm(algorithm) # :nodoc:
  1206. 9 index_algorithms.fetch(algorithm) do
  1207. 2 raise ArgumentError, "Algorithm must be one of the following: #{index_algorithms.keys.map(&:inspect).join(', ')}"
  1208. 10391 end if algorithm
  1209. end
  1210. 3 def quoted_columns_for_index(column_names, options) # :nodoc:
  1211. 10308 quoted_columns = column_names.each_with_object({}) do |name, result|
  1212. 10575 result[name.to_sym] = quote_column_name(name).dup
  1213. end
  1214. 10308 add_options_for_index_columns(quoted_columns, **options).values.join(", ")
  1215. end
  1216. 3 def options_include_default?(options)
  1217. 43790 options.include?(:default) && !(options[:null] == false && options[:default].nil?)
  1218. end
  1219. # Changes the comment for a table or removes it if +nil+.
  1220. #
  1221. # Passing a hash containing +:from+ and +:to+ will make this change
  1222. # reversible in migration:
  1223. #
  1224. # change_table_comment(:posts, from: "old_comment", to: "new_comment")
  1225. 3 def change_table_comment(table_name, comment_or_changes)
  1226. raise NotImplementedError, "#{self.class} does not support changing table comments"
  1227. end
  1228. # Changes the comment for a column or removes it if +nil+.
  1229. #
  1230. # Passing a hash containing +:from+ and +:to+ will make this change
  1231. # reversible in migration:
  1232. #
  1233. # change_column_comment(:posts, :state, from: "old_comment", to: "new_comment")
  1234. 3 def change_column_comment(table_name, column_name, comment_or_changes)
  1235. raise NotImplementedError, "#{self.class} does not support changing column comments"
  1236. end
  1237. 3 def create_schema_dumper(options) # :nodoc:
  1238. SchemaDumper.create(self, options)
  1239. end
  1240. 3 private
  1241. 3 def column_options_keys
  1242. 211 [:limit, :precision, :scale, :default, :null, :collation, :comment]
  1243. end
  1244. 3 def add_index_sort_order(quoted_columns, **options)
  1245. 10308 orders = options_for_index_columns(options[:order])
  1246. 10308 quoted_columns.each do |name, column|
  1247. 10575 column << " #{orders[name].upcase}" if orders[name].present?
  1248. end
  1249. end
  1250. 3 def options_for_index_columns(options)
  1251. 10796 if options.is_a?(Hash)
  1252. 10780 options.symbolize_keys
  1253. else
  1254. 42 Hash.new { |hash, column| hash[column] = options }
  1255. end
  1256. end
  1257. # Overridden by the MySQL adapter for supporting index lengths and by
  1258. # the PostgreSQL adapter for supporting operator classes.
  1259. 3 def add_options_for_index_columns(quoted_columns, **options)
  1260. 10308 if supports_index_sort_order?
  1261. 10308 quoted_columns = add_index_sort_order(quoted_columns, **options)
  1262. end
  1263. 10308 quoted_columns
  1264. end
  1265. 3 def index_name_for_remove(table_name, column_name, options)
  1266. 145 return options[:name] if can_remove_index_by_name?(column_name, options)
  1267. 91 checks = []
  1268. 115 checks << lambda { |i| i.name == options[:name].to_s } if options.key?(:name)
  1269. 91 column_names = index_column_names(column_name || options[:column])
  1270. 91 if column_names.present?
  1271. 165 checks << lambda { |i| index_name(table_name, i.columns) == index_name(table_name, column_names) }
  1272. end
  1273. 91 raise ArgumentError, "No name or columns specified" if checks.none?
  1274. 285 matching_indexes = indexes(table_name).select { |i| checks.all? { |check| check[i] } }
  1275. 90 if matching_indexes.count > 1
  1276. raise ArgumentError, "Multiple indexes found on #{table_name} columns #{column_names}. " \
  1277. "Specify an index name from #{matching_indexes.map(&:name).join(', ')}"
  1278. 90 elsif matching_indexes.none?
  1279. 15 raise ArgumentError, "No indexes found on #{table_name} with the options provided."
  1280. else
  1281. 75 matching_indexes.first.name
  1282. end
  1283. end
  1284. 3 def rename_table_indexes(table_name, new_name)
  1285. 30 indexes(new_name).each do |index|
  1286. 15 generated_index_name = index_name(table_name, column: index.columns)
  1287. 15 if generated_index_name == index.name
  1288. 9 rename_index new_name, generated_index_name, index_name(new_name, column: index.columns)
  1289. end
  1290. end
  1291. end
  1292. 3 def rename_column_indexes(table_name, column_name, new_column_name)
  1293. 56 column_name, new_column_name = column_name.to_s, new_column_name.to_s
  1294. 56 indexes(table_name).each do |index|
  1295. 34 next unless index.columns.include?(new_column_name)
  1296. 21 old_columns = index.columns.dup
  1297. 21 old_columns[old_columns.index(new_column_name)] = column_name
  1298. 21 generated_index_name = index_name(table_name, column: old_columns)
  1299. 21 if generated_index_name == index.name
  1300. 18 rename_index table_name, generated_index_name, index_name(table_name, column: index.columns)
  1301. end
  1302. end
  1303. end
  1304. 3 def schema_creation
  1305. 9 SchemaCreation.new(self)
  1306. end
  1307. 3 def create_table_definition(name, **options)
  1308. TableDefinition.new(self, name, **options)
  1309. end
  1310. 3 def create_alter_table(name)
  1311. 379 AlterTable.new create_table_definition(name)
  1312. end
  1313. 3 def extract_table_options!(options)
  1314. 6731 options.extract!(:temporary, :if_not_exists, :options, :as, :comment, :charset, :collation)
  1315. end
  1316. 3 def fetch_type_metadata(sql_type)
  1317. 226391 cast_type = lookup_cast_type(sql_type)
  1318. 226391 SqlTypeMetadata.new(
  1319. sql_type: sql_type,
  1320. type: cast_type.type,
  1321. limit: cast_type.limit,
  1322. precision: cast_type.precision,
  1323. scale: cast_type.scale,
  1324. )
  1325. end
  1326. 3 def index_column_names(column_names)
  1327. 10439 if column_names.is_a?(String) && /\W/.match?(column_names)
  1328. 35 column_names
  1329. else
  1330. 10404 Array(column_names)
  1331. end
  1332. end
  1333. 3 def index_name_options(column_names)
  1334. 1340 if column_names.is_a?(String) && /\W/.match?(column_names)
  1335. 13 column_names = column_names.scan(/\w+/).join("_")
  1336. end
  1337. 1340 { column: column_names }
  1338. end
  1339. 3 def strip_table_name_prefix_and_suffix(table_name)
  1340. 476 prefix = Base.table_name_prefix
  1341. 476 suffix = Base.table_name_suffix
  1342. 476 table_name.to_s =~ /#{prefix}(.+)#{suffix}/ ? $1 : table_name.to_s
  1343. end
  1344. 3 def foreign_key_name(table_name, options)
  1345. 272 options.fetch(:name) do
  1346. 272 identifier = "#{table_name}_#{options.fetch(:column)}_fk"
  1347. 272 hashed_identifier = Digest::SHA256.hexdigest(identifier).first(10)
  1348. 272 "fk_rails_#{hashed_identifier}"
  1349. end
  1350. end
  1351. 3 def foreign_key_for(from_table, **options)
  1352. 52 return unless supports_foreign_keys?
  1353. 103 foreign_keys(from_table).detect { |fk| fk.defined_for?(**options) }
  1354. end
  1355. 3 def foreign_key_for!(from_table, to_table: nil, **options)
  1356. 30 foreign_key_for(from_table, to_table: to_table, **options) ||
  1357. raise(ArgumentError, "Table '#{from_table}' has no foreign key for #{to_table || options}")
  1358. end
  1359. 3 def extract_foreign_key_action(specifier)
  1360. 608 case specifier
  1361. 24 when "CASCADE"; :cascade
  1362. 6 when "SET NULL"; :nullify
  1363. 2 when "RESTRICT"; :restrict
  1364. end
  1365. end
  1366. 3 def check_constraint_name(table_name, **options)
  1367. 9 options.fetch(:name) do
  1368. 3 expression = options.fetch(:expression)
  1369. 3 identifier = "#{table_name}_#{expression}_chk"
  1370. 3 hashed_identifier = Digest::SHA256.hexdigest(identifier).first(10)
  1371. 3 "chk_rails_#{hashed_identifier}"
  1372. end
  1373. end
  1374. 3 def check_constraint_for(table_name, **options)
  1375. 6 return unless supports_check_constraints?
  1376. 6 chk_name = check_constraint_name(table_name, **options)
  1377. 12 check_constraints(table_name).detect { |chk| chk.name == chk_name }
  1378. end
  1379. 3 def check_constraint_for!(table_name, expression: nil, **options)
  1380. 6 check_constraint_for(table_name, expression: expression, **options) ||
  1381. raise(ArgumentError, "Table '#{table_name}' has no check constraint for #{expression || options}")
  1382. end
  1383. 3 def validate_index_length!(table_name, new_name, internal = false)
  1384. 1367 if new_name.length > index_name_length
  1385. 6 raise ArgumentError, "Index name '#{new_name}' on table '#{table_name}' is too long; the limit is #{index_name_length} characters"
  1386. end
  1387. end
  1388. 3 def extract_new_default_value(default_or_changes)
  1389. 141 if default_or_changes.is_a?(Hash) && default_or_changes.has_key?(:from) && default_or_changes.has_key?(:to)
  1390. 13 default_or_changes[:to]
  1391. else
  1392. 128 default_or_changes
  1393. end
  1394. end
  1395. 3 alias :extract_new_comment_value :extract_new_default_value
  1396. 3 def can_remove_index_by_name?(column_name, options)
  1397. 145 column_name.nil? && options.key?(:name) && options.except(:name, :algorithm).empty?
  1398. end
  1399. 3 def bulk_change_table(table_name, operations)
  1400. 17 sql_fragments = []
  1401. 17 non_combinable_operations = []
  1402. 17 operations.each do |command, args|
  1403. 34 table, arguments = args.shift, args
  1404. 34 method = :"#{command}_for_alter"
  1405. 34 if respond_to?(method, true)
  1406. 58 sqls, procs = Array(send(method, table, *arguments)).partition { |v| v.is_a?(String) }
  1407. 25 sql_fragments << sqls
  1408. 25 non_combinable_operations.concat(procs)
  1409. else
  1410. 9 execute "ALTER TABLE #{quote_table_name(table_name)} #{sql_fragments.join(", ")}" unless sql_fragments.empty?
  1411. 9 non_combinable_operations.each(&:call)
  1412. 9 sql_fragments = []
  1413. 9 non_combinable_operations = []
  1414. 9 send(command, table, *arguments)
  1415. end
  1416. end
  1417. 17 execute "ALTER TABLE #{quote_table_name(table_name)} #{sql_fragments.join(", ")}" unless sql_fragments.empty?
  1418. 17 non_combinable_operations.each(&:call)
  1419. end
  1420. 3 def add_column_for_alter(table_name, column_name, type, **options)
  1421. 27 td = create_table_definition(table_name)
  1422. 27 cd = td.new_column_definition(column_name, type, **options)
  1423. 27 schema_creation.accept(AddColumnDefinition.new(cd))
  1424. end
  1425. 3 def rename_column_sql(table_name, column_name, new_column_name)
  1426. 21 "RENAME COLUMN #{quote_column_name(column_name)} TO #{quote_column_name(new_column_name)}"
  1427. end
  1428. 3 def remove_column_for_alter(table_name, column_name, type = nil, **options)
  1429. 698 "DROP COLUMN #{quote_column_name(column_name)}"
  1430. end
  1431. 3 def remove_columns_for_alter(table_name, *column_names, **options)
  1432. 3 column_names.map { |column_name| remove_column_for_alter(table_name, column_name) }
  1433. end
  1434. 3 def add_timestamps_for_alter(table_name, **options)
  1435. 5 options[:null] = false if options[:null].nil?
  1436. 5 if !options.key?(:precision) && supports_datetime_with_precision?
  1437. 3 options[:precision] = 6
  1438. end
  1439. 5 [
  1440. add_column_for_alter(table_name, :created_at, :datetime, **options),
  1441. add_column_for_alter(table_name, :updated_at, :datetime, **options)
  1442. ]
  1443. end
  1444. 3 def remove_timestamps_for_alter(table_name, **options)
  1445. remove_columns_for_alter(table_name, :updated_at, :created_at)
  1446. end
  1447. 3 def insert_versions_sql(versions)
  1448. 6 sm_table = quote_table_name(schema_migration.table_name)
  1449. 6 if versions.is_a?(Array)
  1450. 6 sql = +"INSERT INTO #{sm_table} (version) VALUES\n"
  1451. 21 sql << versions.map { |v| "(#{quote(v)})" }.join(",\n")
  1452. 6 sql << ";\n\n"
  1453. 6 sql
  1454. else
  1455. "INSERT INTO #{sm_table} (version) VALUES (#{quote(versions)});"
  1456. end
  1457. end
  1458. 3 def data_source_sql(name = nil, type: nil)
  1459. raise NotImplementedError
  1460. end
  1461. 3 def quoted_scope(name = nil, type: nil)
  1462. raise NotImplementedError
  1463. end
  1464. end
  1465. end
  1466. end

lib/active_record/connection_adapters/abstract/transaction.rb

98.04% lines covered

204 relevant lines. 200 lines covered and 4 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 module ActiveRecord
  3. 3 module ConnectionAdapters
  4. 3 class TransactionState
  5. 3 def initialize(state = nil)
  6. 128520 @state = state
  7. 128520 @children = nil
  8. end
  9. 3 def add_child(state)
  10. 12745 @children ||= []
  11. 12745 @children << state
  12. end
  13. 3 def finalized?
  14. @state
  15. end
  16. 3 def committed?
  17. 36 @state == :committed || @state == :fully_committed
  18. end
  19. 3 def fully_committed?
  20. @state == :fully_committed
  21. end
  22. 3 def rolledback?
  23. 27 @state == :rolledback || @state == :fully_rolledback
  24. end
  25. 3 def fully_rolledback?
  26. @state == :fully_rolledback
  27. end
  28. 3 def fully_completed?
  29. completed?
  30. end
  31. 3 def completed?
  32. 20 committed? || rolledback?
  33. end
  34. 3 def rollback!
  35. 13512 @children&.each { |c| c.rollback! }
  36. 13476 @state = :rolledback
  37. end
  38. 3 def full_rollback!
  39. 122077 @children&.each { |c| c.rollback! }
  40. 109424 @state = :fully_rolledback
  41. end
  42. 3 def commit!
  43. 11965 @state = :committed
  44. end
  45. 3 def full_commit!
  46. 6159 @state = :fully_committed
  47. end
  48. 3 def nullify!
  49. 4 @state = nil
  50. end
  51. end
  52. 3 class NullTransaction #:nodoc:
  53. 3 def initialize; end
  54. 3 def state; end
  55. 3 def closed?; true; end
  56. 129827 def open?; false; end
  57. 119465 def joinable?; false; end
  58. 3 def add_record(record, _ = true); end
  59. end
  60. 3 class Transaction #:nodoc:
  61. 3 attr_reader :connection, :state, :savepoint_name, :isolation_level
  62. 3 attr_accessor :written
  63. 3 def initialize(connection, isolation: nil, joinable: true, run_commit_callbacks: false)
  64. 128520 @connection = connection
  65. 128520 @state = TransactionState.new
  66. 128520 @records = nil
  67. 128520 @isolation_level = isolation
  68. 128520 @materialized = false
  69. 128520 @joinable = joinable
  70. 128520 @run_commit_callbacks = run_commit_callbacks
  71. 128520 @lazy_enrollment_records = nil
  72. end
  73. 3 def add_record(record, ensure_finalize = true)
  74. 19255 @records ||= []
  75. 19255 if ensure_finalize
  76. 2742 @records << record
  77. else
  78. 16513 @lazy_enrollment_records ||= ObjectSpace::WeakMap.new
  79. 16513 @lazy_enrollment_records[record] = record
  80. end
  81. end
  82. 3 def records
  83. 174193 if @lazy_enrollment_records
  84. 13026 @records.concat @lazy_enrollment_records.values
  85. 13026 @lazy_enrollment_records = nil
  86. end
  87. 174193 @records
  88. end
  89. 3 def materialize!
  90. 127283 @materialized = true
  91. end
  92. 3 def materialized?
  93. 158597 @materialized
  94. end
  95. 3 def rollback_records
  96. 110199 return unless records
  97. 971 ite = records.uniq(&:__id__)
  98. 971 already_run_callbacks = {}
  99. 971 while record = ite.shift
  100. 1182 trigger_callbacks = record.trigger_transactional_callbacks?
  101. 1182 should_run_callbacks = !already_run_callbacks[record] && trigger_callbacks
  102. 1182 already_run_callbacks[record] ||= trigger_callbacks
  103. 1182 record.rolledback!(force_restore_state: full_rollback?, should_run_callbacks: should_run_callbacks)
  104. end
  105. ensure
  106. 110199 ite&.each do |i|
  107. 3 i.rolledback!(force_restore_state: full_rollback?, should_run_callbacks: false)
  108. end
  109. end
  110. 3 def before_commit_records
  111. 18119 records.uniq.each(&:before_committed!) if records && @run_commit_callbacks
  112. end
  113. 3 def commit_records
  114. 18108 return unless records
  115. 13401 ite = records.uniq(&:__id__)
  116. 13401 already_run_callbacks = {}
  117. 13401 while record = ite.shift
  118. 16273 if @run_commit_callbacks
  119. 16260 trigger_callbacks = record.trigger_transactional_callbacks?
  120. 16260 should_run_callbacks = !already_run_callbacks[record] && trigger_callbacks
  121. 16260 already_run_callbacks[record] ||= trigger_callbacks
  122. 16260 record.committed!(should_run_callbacks: should_run_callbacks)
  123. else
  124. # if not running callbacks, only adds the record to the parent transaction
  125. 13 connection.add_transaction_record(record)
  126. end
  127. end
  128. ensure
  129. 18117 ite&.each { |i| i.committed!(should_run_callbacks: false) }
  130. end
  131. 349 def full_rollback?; true; end
  132. 33166 def joinable?; @joinable; end
  133. 315351 def closed?; false; end
  134. 315351 def open?; !closed?; end
  135. end
  136. 3 class SavepointTransaction < Transaction
  137. 3 def initialize(connection, savepoint_name, parent_transaction, **options)
  138. 12745 super(connection, **options)
  139. 12745 parent_transaction.state.add_child(@state)
  140. 12745 if isolation_level
  141. 1 raise ActiveRecord::TransactionIsolationError, "cannot set transaction isolation in a nested transaction"
  142. end
  143. 12744 @savepoint_name = savepoint_name
  144. end
  145. 3 def materialize!
  146. 11898 connection.create_savepoint(savepoint_name)
  147. 11898 super
  148. end
  149. 3 def rollback
  150. 783 connection.rollback_to_savepoint(savepoint_name) if materialized?
  151. 783 @state.rollback!
  152. end
  153. 3 def commit
  154. 11961 connection.release_savepoint(savepoint_name) if materialized?
  155. 11961 @state.commit!
  156. end
  157. 842 def full_rollback?; false; end
  158. end
  159. 3 class RealTransaction < Transaction
  160. 3 def materialize!
  161. 115391 if isolation_level
  162. 20 connection.begin_isolated_db_transaction(isolation_level)
  163. else
  164. 115371 connection.begin_db_transaction
  165. end
  166. 115385 super
  167. end
  168. 3 def rollback
  169. 109424 connection.rollback_db_transaction if materialized?
  170. 109424 @state.full_rollback!
  171. end
  172. 3 def commit
  173. 6164 connection.commit_db_transaction if materialized?
  174. 6159 @state.full_commit!
  175. end
  176. end
  177. 3 class TransactionManager #:nodoc:
  178. 3 def initialize(connection)
  179. 2013 @stack = []
  180. 2013 @connection = connection
  181. 2013 @has_unmaterialized_transactions = false
  182. 2013 @materializing_transactions = false
  183. 2013 @lazy_transactions_enabled = true
  184. end
  185. 3 def begin_transaction(isolation: nil, joinable: true, _lazy: true)
  186. 128520 @connection.lock.synchronize do
  187. 128520 run_commit_callbacks = !current_transaction.joinable?
  188. 128520 transaction =
  189. 128520 if @stack.empty?
  190. 115775 RealTransaction.new(
  191. @connection,
  192. isolation: isolation,
  193. joinable: joinable,
  194. run_commit_callbacks: run_commit_callbacks
  195. )
  196. else
  197. 12745 SavepointTransaction.new(
  198. @connection,
  199. "active_record_#{@stack.size}",
  200. @stack.last,
  201. isolation: isolation,
  202. joinable: joinable,
  203. run_commit_callbacks: run_commit_callbacks
  204. )
  205. end
  206. 128519 if @connection.supports_lazy_transactions? && lazy_transactions_enabled? && _lazy
  207. 19184 @has_unmaterialized_transactions = true
  208. else
  209. 109335 transaction.materialize!
  210. end
  211. 128519 @stack.push(transaction)
  212. 128519 transaction
  213. end
  214. end
  215. 3 def disable_lazy_transactions!
  216. 34 materialize_transactions
  217. 34 @lazy_transactions_enabled = false
  218. end
  219. 3 def enable_lazy_transactions!
  220. 112912 @lazy_transactions_enabled = true
  221. end
  222. 3 def lazy_transactions_enabled?
  223. 95347 @lazy_transactions_enabled
  224. end
  225. 3 def materialize_transactions
  226. 320134 return if @materializing_transactions
  227. 305438 return unless @has_unmaterialized_transactions
  228. 18778 @connection.lock.synchronize do
  229. 18778 begin
  230. 18778 @materializing_transactions = true
  231. 49043 @stack.each { |t| t.materialize! unless t.materialized? }
  232. ensure
  233. 18778 @materializing_transactions = false
  234. end
  235. 18772 @has_unmaterialized_transactions = false
  236. end
  237. end
  238. 3 def commit_transaction
  239. 18119 @connection.lock.synchronize do
  240. 18119 transaction = @stack.last
  241. 18119 begin
  242. 18119 transaction.before_commit_records
  243. ensure
  244. 18119 @stack.pop
  245. end
  246. 18116 transaction.commit
  247. 18108 transaction.commit_records
  248. end
  249. end
  250. 3 def rollback_transaction(transaction = nil)
  251. 110199 @connection.lock.synchronize do
  252. 110199 transaction ||= @stack.pop
  253. 110199 transaction.rollback
  254. 110199 transaction.rollback_records
  255. end
  256. end
  257. 3 def within_new_transaction(isolation: nil, joinable: true)
  258. 19182 @connection.lock.synchronize do
  259. 19182 transaction = begin_transaction(isolation: isolation, joinable: joinable)
  260. 19181 ret = yield
  261. 18107 completed = true
  262. 18107 ret
  263. rescue Exception => error
  264. 1060 if transaction
  265. 1059 rollback_transaction
  266. 1046 after_failure_actions(transaction, error)
  267. end
  268. 1047 raise
  269. ensure
  270. 19182 if !error && transaction
  271. 18122 if Thread.current.status == "aborting"
  272. 3 rollback_transaction
  273. else
  274. 18119 if !completed && transaction.written
  275. 8 ActiveSupport::Deprecation.warn(<<~EOW)
  276. Using `return`, `break` or `throw` to exit a transaction block is
  277. deprecated without replacement. If the `throw` came from
  278. `Timeout.timeout(duration)`, pass an exception class as a second
  279. argument so it doesn't use `throw` to abort its block. This results
  280. in the transaction being committed, but in the next release of Rails
  281. it will rollback.
  282. EOW
  283. end
  284. 18119 begin
  285. 18119 commit_transaction
  286. rescue Exception
  287. 20 rollback_transaction(transaction) unless transaction.state.completed?
  288. 20 raise
  289. end
  290. end
  291. end
  292. end
  293. end
  294. 3 def open_transactions
  295. 17 @stack.size
  296. end
  297. 3 def current_transaction
  298. 617089 @stack.last || NULL_TRANSACTION
  299. end
  300. 3 private
  301. 3 NULL_TRANSACTION = NullTransaction.new
  302. # Deallocate invalidated prepared statements outside of the transaction
  303. 3 def after_failure_actions(transaction, error)
  304. 1046 return unless transaction.is_a?(RealTransaction)
  305. 277 return unless error.is_a?(ActiveRecord::PreparedStatementCacheExpired)
  306. 2 @connection.clear_cache!
  307. end
  308. end
  309. end
  310. end

lib/active_record/connection_adapters/abstract_adapter.rb

89.82% lines covered

334 relevant lines. 300 lines covered and 34 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 require "set"
  3. 3 require "active_record/connection_adapters/schema_cache"
  4. 3 require "active_record/connection_adapters/sql_type_metadata"
  5. 3 require "active_record/connection_adapters/abstract/schema_dumper"
  6. 3 require "active_record/connection_adapters/abstract/schema_creation"
  7. 3 require "active_support/concurrency/load_interlock_aware_monitor"
  8. 3 require "arel/collectors/bind"
  9. 3 require "arel/collectors/composite"
  10. 3 require "arel/collectors/sql_string"
  11. 3 require "arel/collectors/substitute_binds"
  12. 3 module ActiveRecord
  13. 3 module ConnectionAdapters # :nodoc:
  14. # Active Record supports multiple database systems. AbstractAdapter and
  15. # related classes form the abstraction layer which makes this possible.
  16. # An AbstractAdapter represents a connection to a database, and provides an
  17. # abstract interface for database-specific functionality such as establishing
  18. # a connection, escaping values, building the right SQL fragments for +:offset+
  19. # and +:limit+ options, etc.
  20. #
  21. # All the concrete database adapters follow the interface laid down in this class.
  22. # {ActiveRecord::Base.connection}[rdoc-ref:ConnectionHandling#connection] returns an AbstractAdapter object, which
  23. # you can use.
  24. #
  25. # Most of the methods in the adapter are useful during migrations. Most
  26. # notably, the instance methods provided by SchemaStatements are very useful.
  27. 3 class AbstractAdapter
  28. 3 ADAPTER_NAME = "Abstract"
  29. 3 include ActiveSupport::Callbacks
  30. 3 define_callbacks :checkout, :checkin
  31. 3 include Quoting, DatabaseStatements, SchemaStatements
  32. 3 include DatabaseLimits
  33. 3 include QueryCache
  34. 3 include Savepoints
  35. 3 SIMPLE_INT = /\A\d+\z/
  36. 3 COMMENT_REGEX = %r{/\*(?:[^\*]|\*[^/])*\*/}m
  37. 3 attr_accessor :pool
  38. 3 attr_reader :visitor, :owner, :logger, :lock
  39. 3 alias :in_use? :owner
  40. 3 set_callback :checkin, :after, :enable_lazy_transactions!
  41. 3 def self.type_cast_config_to_integer(config)
  42. 1336 if config.is_a?(Integer)
  43. 325 config
  44. 1011 elsif SIMPLE_INT.match?(config)
  45. config.to_i
  46. else
  47. 1011 config
  48. end
  49. end
  50. 3 def self.type_cast_config_to_boolean(config)
  51. 2108 if config == "false"
  52. false
  53. else
  54. 2108 config
  55. end
  56. end
  57. 3 DEFAULT_READ_QUERY = [:begin, :commit, :explain, :release, :rollback, :savepoint, :select, :with] # :nodoc:
  58. 3 private_constant :DEFAULT_READ_QUERY
  59. 3 def self.build_read_query_regexp(*parts) # :nodoc:
  60. 5 parts += DEFAULT_READ_QUERY
  61. 60 parts = parts.map { |part| /#{part}/i }
  62. 5 /\A(?:[\(\s]|#{COMMENT_REGEX})*#{Regexp.union(*parts)}/
  63. end
  64. 3 def self.quoted_column_names # :nodoc:
  65. 675704 @quoted_column_names ||= {}
  66. end
  67. 3 def self.quoted_table_names # :nodoc:
  68. 684746 @quoted_table_names ||= {}
  69. end
  70. 3 def initialize(connection, logger = nil, config = {}) # :nodoc:
  71. 1052 super()
  72. 1052 @connection = connection
  73. 1052 @owner = nil
  74. 1052 @instrumenter = ActiveSupport::Notifications.instrumenter
  75. 1052 @logger = logger
  76. 1052 @config = config
  77. 1052 @pool = ActiveRecord::ConnectionAdapters::NullPool.new
  78. 1052 @idle_since = Concurrent.monotonic_time
  79. 1052 @visitor = arel_visitor
  80. 1052 @statements = build_statement_pool
  81. 1052 @lock = ActiveSupport::Concurrency::LoadInterlockAwareMonitor.new
  82. 1052 @prepared_statements = self.class.type_cast_config_to_boolean(
  83. config.fetch(:prepared_statements, true)
  84. )
  85. 1052 @advisory_locks_enabled = self.class.type_cast_config_to_boolean(
  86. config.fetch(:advisory_locks, true)
  87. )
  88. end
  89. 3 def replica?
  90. 291045 @config[:replica] || false
  91. end
  92. 3 def use_metadata_table?
  93. 806 @config.fetch(:use_metadata_table, true)
  94. end
  95. # Determines whether writes are currently being prevents.
  96. #
  97. # Returns true if the connection is a replica, or if +prevent_writes+
  98. # is set to true.
  99. 3 def preventing_writes?
  100. 291045 replica? || ActiveRecord::Base.connection_handler.prevent_writes
  101. end
  102. 3 def migrations_paths # :nodoc:
  103. 585 @config[:migrations_paths] || Migrator.migrations_paths
  104. end
  105. 3 def migration_context # :nodoc:
  106. 585 MigrationContext.new(migrations_paths, schema_migration)
  107. end
  108. 3 def schema_migration # :nodoc:
  109. 1173 @schema_migration ||= begin
  110. 1092 conn = self
  111. 1092 spec_name = conn.pool.pool_config.connection_specification_name
  112. 1092 return ActiveRecord::SchemaMigration if spec_name == "ActiveRecord::Base"
  113. 3 schema_migration_name = "#{spec_name}::SchemaMigration"
  114. 3 Class.new(ActiveRecord::SchemaMigration) do
  115. 189 define_singleton_method(:name) { schema_migration_name }
  116. 75 define_singleton_method(:to_s) { schema_migration_name }
  117. 3 self.connection_specification_name = spec_name
  118. end
  119. end
  120. end
  121. 3 def prepared_statements
  122. 302431 @prepared_statements && !prepared_statements_disabled_cache.include?(object_id)
  123. end
  124. 3 def prepared_statements_disabled_cache # :nodoc:
  125. 302902 Thread.current[:ar_prepared_statements_disabled_cache] ||= Set.new
  126. end
  127. 3 class Version
  128. 3 include Comparable
  129. 3 attr_reader :full_version_string
  130. 3 def initialize(version_string, full_version_string = nil)
  131. 504 @version = version_string.split(".").map(&:to_i)
  132. 504 @full_version_string = full_version_string
  133. end
  134. 3 def <=>(version_string)
  135. 627 @version <=> version_string.split(".").map(&:to_i)
  136. end
  137. 3 def to_s
  138. 36 @version.join(".")
  139. end
  140. end
  141. 3 def valid_type?(type) # :nodoc:
  142. 16996 !native_database_types[type].nil?
  143. end
  144. # this method must only be called while holding connection pool's mutex
  145. 3 def lease
  146. 112996 if in_use?
  147. 3 msg = +"Cannot lease connection, "
  148. 3 if @owner == Thread.current
  149. 3 msg << "it is already leased by the current thread."
  150. else
  151. msg << "it is already in use by a different thread: #{@owner}. " \
  152. "Current thread: #{Thread.current}."
  153. end
  154. 3 raise ActiveRecordError, msg
  155. end
  156. 112993 @owner = Thread.current
  157. end
  158. 3 def schema_cache
  159. 183851 @pool.get_schema_cache(self)
  160. end
  161. 3 def schema_cache=(cache)
  162. cache.connection = self
  163. @pool.set_schema_cache(cache)
  164. end
  165. # this method must only be called while holding connection pool's mutex
  166. 3 def expire
  167. 112915 if in_use?
  168. 112915 if @owner != Thread.current
  169. raise ActiveRecordError, "Cannot expire connection, " \
  170. "it is owned by a different thread: #{@owner}. " \
  171. "Current thread: #{Thread.current}."
  172. end
  173. 112915 @idle_since = Concurrent.monotonic_time
  174. 112915 @owner = nil
  175. else
  176. raise ActiveRecordError, "Cannot expire connection, it is not currently leased."
  177. end
  178. end
  179. # this method must only be called while holding connection pool's mutex (and a desire for segfaults)
  180. 3 def steal! # :nodoc:
  181. 726 if in_use?
  182. 726 if @owner != Thread.current
  183. 131 pool.send :remove_connection_from_thread_cache, self, @owner
  184. 131 @owner = Thread.current
  185. end
  186. else
  187. raise ActiveRecordError, "Cannot steal connection, it is not currently leased."
  188. end
  189. end
  190. # Seconds since this connection was returned to the pool
  191. 3 def seconds_idle # :nodoc:
  192. 65 return 0 if in_use?
  193. 65 Concurrent.monotonic_time - @idle_since
  194. end
  195. 3 def unprepared_statement
  196. 486 cache = prepared_statements_disabled_cache.add(object_id) if @prepared_statements
  197. 486 yield
  198. ensure
  199. 486 cache&.delete(object_id)
  200. end
  201. # Returns the human-readable name of the adapter. Use mixed case - one
  202. # can always use downcase if needed.
  203. 3 def adapter_name
  204. 865 self.class::ADAPTER_NAME
  205. end
  206. # Does the database for this adapter exist?
  207. 3 def self.database_exists?(config)
  208. raise NotImplementedError
  209. end
  210. # Does this adapter support DDL rollbacks in transactions? That is, would
  211. # CREATE TABLE or ALTER TABLE get rolled back by a transaction?
  212. 3 def supports_ddl_transactions?
  213. false
  214. end
  215. 3 def supports_bulk_alter?
  216. 66 false
  217. end
  218. # Does this adapter support savepoints?
  219. 3 def supports_savepoints?
  220. false
  221. end
  222. # Does this adapter support application-enforced advisory locking?
  223. 3 def supports_advisory_locks?
  224. 214 false
  225. end
  226. # Should primary key values be selected from their corresponding
  227. # sequence before the insert statement? If true, next_sequence_value
  228. # is called before each insert to set the record's primary key.
  229. 3 def prefetch_primary_key?(table_name = nil)
  230. 11108 false
  231. end
  232. 3 def supports_partitioned_indexes?
  233. 2 false
  234. end
  235. # Does this adapter support index sort order?
  236. 3 def supports_index_sort_order?
  237. false
  238. end
  239. # Does this adapter support partial indices?
  240. 3 def supports_partial_index?
  241. false
  242. end
  243. # Does this adapter support expression indices?
  244. 3 def supports_expression_index?
  245. false
  246. end
  247. # Does this adapter support explain?
  248. 3 def supports_explain?
  249. false
  250. end
  251. # Does this adapter support setting the isolation level for a transaction?
  252. 3 def supports_transaction_isolation?
  253. false
  254. end
  255. # Does this adapter support database extensions?
  256. 3 def supports_extensions?
  257. 2 false
  258. end
  259. # Does this adapter support creating indexes in the same statement as
  260. # creating the table?
  261. 3 def supports_indexes_in_create?
  262. 13399 false
  263. end
  264. # Does this adapter support creating foreign key constraints?
  265. 3 def supports_foreign_keys?
  266. false
  267. end
  268. # Does this adapter support creating invalid constraints?
  269. 3 def supports_validate_constraints?
  270. 2 false
  271. end
  272. # Does this adapter support creating foreign key constraints
  273. # in the same statement as creating the table?
  274. 3 def supports_foreign_keys_in_create?
  275. 3 supports_foreign_keys?
  276. end
  277. 3 deprecate :supports_foreign_keys_in_create?
  278. # Does this adapter support creating check constraints?
  279. 3 def supports_check_constraints?
  280. false
  281. end
  282. # Does this adapter support views?
  283. 3 def supports_views?
  284. false
  285. end
  286. # Does this adapter support materialized views?
  287. 3 def supports_materialized_views?
  288. 2 false
  289. end
  290. # Does this adapter support datetime with precision?
  291. 3 def supports_datetime_with_precision?
  292. false
  293. end
  294. # Does this adapter support json data type?
  295. 3 def supports_json?
  296. false
  297. end
  298. # Does this adapter support metadata comments on database objects (tables, columns, indexes)?
  299. 3 def supports_comments?
  300. 5198 false
  301. end
  302. # Can comments for tables, columns, and indexes be specified in create/alter table statements?
  303. 3 def supports_comments_in_create?
  304. 1506 false
  305. end
  306. # Does this adapter support multi-value insert?
  307. 3 def supports_multi_insert?
  308. 3 true
  309. end
  310. 3 deprecate :supports_multi_insert?
  311. # Does this adapter support virtual columns?
  312. 3 def supports_virtual_columns?
  313. 12909 false
  314. end
  315. # Does this adapter support foreign/external tables?
  316. 3 def supports_foreign_tables?
  317. false
  318. end
  319. # Does this adapter support optimizer hints?
  320. 3 def supports_optimizer_hints?
  321. false
  322. end
  323. 3 def supports_common_table_expressions?
  324. false
  325. end
  326. 3 def supports_lazy_transactions?
  327. 33172 false
  328. end
  329. 3 def supports_insert_returning?
  330. 124 false
  331. end
  332. 3 def supports_insert_on_duplicate_skip?
  333. false
  334. end
  335. 3 def supports_insert_on_duplicate_update?
  336. false
  337. end
  338. 3 def supports_insert_conflict_target?
  339. false
  340. end
  341. # This is meant to be implemented by the adapters that support extensions
  342. 3 def disable_extension(name)
  343. end
  344. # This is meant to be implemented by the adapters that support extensions
  345. 3 def enable_extension(name)
  346. end
  347. 3 def advisory_locks_enabled? # :nodoc:
  348. 325 supports_advisory_locks? && @advisory_locks_enabled
  349. end
  350. # This is meant to be implemented by the adapters that support advisory
  351. # locks
  352. #
  353. # Return true if we got the lock, otherwise false
  354. 3 def get_advisory_lock(lock_id) # :nodoc:
  355. end
  356. # This is meant to be implemented by the adapters that support advisory
  357. # locks.
  358. #
  359. # Return true if we released the lock, otherwise false
  360. 3 def release_advisory_lock(lock_id) # :nodoc:
  361. end
  362. # A list of extensions, to be filled in by adapters that support them.
  363. 3 def extensions
  364. []
  365. end
  366. # A list of index algorithms, to be filled by adapters that support them.
  367. 3 def index_algorithms
  368. {}
  369. end
  370. # REFERENTIAL INTEGRITY ====================================
  371. # Override to turn off referential integrity while executing <tt>&block</tt>.
  372. 3 def disable_referential_integrity
  373. yield
  374. end
  375. # CONNECTION MANAGEMENT ====================================
  376. # Checks whether the connection to the database is still active. This includes
  377. # checking whether the database is actually capable of responding, i.e. whether
  378. # the connection isn't stale.
  379. 3 def active?
  380. end
  381. # Disconnects from the database if already connected, and establishes a
  382. # new connection with the database. Implementors should call super if they
  383. # override the default implementation.
  384. 3 def reconnect!
  385. 266 clear_cache!
  386. 266 reset_transaction
  387. end
  388. # Disconnects from the database if already connected. Otherwise, this
  389. # method does nothing.
  390. 3 def disconnect!
  391. 630 clear_cache!
  392. 630 reset_transaction
  393. end
  394. # Immediately forget this connection ever existed. Unlike disconnect!,
  395. # this will not communicate with the server.
  396. #
  397. # After calling this method, the behavior of all other methods becomes
  398. # undefined. This is called internally just before a forked process gets
  399. # rid of a connection that belonged to its parent.
  400. 3 def discard!
  401. # This should be overridden by concrete adapters.
  402. #
  403. # Prevent @connection's finalizer from touching the socket, or
  404. # otherwise communicating with its server, when it is collected.
  405. 36 if schema_cache.connection == self
  406. 36 schema_cache.connection = nil
  407. end
  408. end
  409. # Reset the state of this connection, directing the DBMS to clear
  410. # transactions and other connection-related server-side state. Usually a
  411. # database-dependent operation.
  412. #
  413. # The default implementation does nothing; the implementation should be
  414. # overridden by concrete adapters.
  415. 3 def reset!
  416. # this should be overridden by concrete adapters
  417. end
  418. # Clear any caching the database adapter may be doing.
  419. 3 def clear_cache!
  420. 7024 @lock.synchronize { @statements.clear } if @statements
  421. end
  422. # Returns true if its required to reload the connection between requests for development mode.
  423. 3 def requires_reloading?
  424. 30 false
  425. end
  426. # Checks whether the connection to the database is still active (i.e. not stale).
  427. # This is done under the hood by calling #active?. If the connection
  428. # is no longer active, then this method will reconnect to the database.
  429. 3 def verify!
  430. 112963 reconnect! unless active?
  431. end
  432. # Provides access to the underlying database driver for this adapter. For
  433. # example, this method returns a Mysql2::Client object in case of Mysql2Adapter,
  434. # and a PG::Connection object in case of PostgreSQLAdapter.
  435. #
  436. # This is useful for when you need to call a proprietary method such as
  437. # PostgreSQL's lo_* methods.
  438. 3 def raw_connection
  439. 34 disable_lazy_transactions!
  440. 34 @connection
  441. end
  442. 3 def default_uniqueness_comparison(attribute, value, klass) # :nodoc:
  443. 427 attribute.eq(value)
  444. end
  445. 3 def case_sensitive_comparison(attribute, value) # :nodoc:
  446. 33 attribute.eq(value)
  447. end
  448. 3 def case_insensitive_comparison(attribute, value) # :nodoc:
  449. 65 column = column_for_attribute(attribute)
  450. 65 if can_perform_case_insensitive_comparison_for?(column)
  451. 56 attribute.lower.eq(attribute.relation.lower(value))
  452. else
  453. 9 attribute.eq(value)
  454. end
  455. end
  456. 3 def can_perform_case_insensitive_comparison_for?(column)
  457. 38 true
  458. end
  459. 3 private :can_perform_case_insensitive_comparison_for?
  460. # Check the connection back in to the connection pool
  461. 3 def close
  462. 57 pool.checkin self
  463. end
  464. 3 def default_index_type?(index) # :nodoc:
  465. 813 index.using.nil?
  466. end
  467. # Called by ActiveRecord::InsertAll,
  468. # Passed an instance of ActiveRecord::InsertAll::Builder,
  469. # This method implements standard bulk inserts for all databases, but
  470. # should be overridden by adapters to implement common features with
  471. # non-standard syntax like handling duplicates or returning values.
  472. 3 def build_insert_sql(insert) # :nodoc:
  473. if insert.skip_duplicates? || insert.update_duplicates?
  474. raise NotImplementedError, "#{self.class} should define `build_insert_sql` to implement adapter-specific logic for handling duplicates during INSERT"
  475. end
  476. "INSERT #{insert.into} #{insert.values_list}"
  477. end
  478. 3 def get_database_version # :nodoc:
  479. end
  480. 3 def database_version # :nodoc:
  481. 1179 schema_cache.database_version
  482. end
  483. 3 def check_version # :nodoc:
  484. end
  485. 3 private
  486. 3 def type_map
  487. 1289062 @type_map ||= Type::TypeMap.new.tap do |mapping|
  488. 97 initialize_type_map(mapping)
  489. end
  490. end
  491. 3 def initialize_type_map(m = type_map)
  492. 99 register_class_with_limit m, %r(boolean)i, Type::Boolean
  493. 99 register_class_with_limit m, %r(char)i, Type::String
  494. 99 register_class_with_limit m, %r(binary)i, Type::Binary
  495. 99 register_class_with_limit m, %r(text)i, Type::Text
  496. 99 register_class_with_precision m, %r(date)i, Type::Date
  497. 99 register_class_with_precision m, %r(time)i, Type::Time
  498. 99 register_class_with_precision m, %r(datetime)i, Type::DateTime
  499. 99 register_class_with_limit m, %r(float)i, Type::Float
  500. 99 register_class_with_limit m, %r(int)i, Type::Integer
  501. 99 m.alias_type %r(blob)i, "binary"
  502. 99 m.alias_type %r(clob)i, "text"
  503. 99 m.alias_type %r(timestamp)i, "datetime"
  504. 99 m.alias_type %r(numeric)i, "decimal"
  505. 99 m.alias_type %r(number)i, "decimal"
  506. 99 m.alias_type %r(double)i, "float"
  507. 99 m.register_type %r(^json)i, Type::Json.new
  508. 99 m.register_type(%r(decimal)i) do |sql_type|
  509. 85 scale = extract_scale(sql_type)
  510. 85 precision = extract_precision(sql_type)
  511. 85 if scale == 0
  512. # FIXME: Remove this class as well
  513. 34 Type::DecimalWithoutScale.new(precision: precision)
  514. else
  515. 51 Type::Decimal.new(precision: precision, scale: scale)
  516. end
  517. end
  518. end
  519. 3 def reload_type_map
  520. 226 type_map.clear
  521. 226 initialize_type_map
  522. end
  523. 3 def register_class_with_limit(mapping, key, klass)
  524. 2575 mapping.register_type(key) do |*args|
  525. 2911 limit = extract_limit(args.last)
  526. 2911 klass.new(limit: limit)
  527. end
  528. end
  529. 3 def register_class_with_precision(mapping, key, klass)
  530. 1561 mapping.register_type(key) do |*args|
  531. 1558 precision = extract_precision(args.last)
  532. 1558 klass.new(precision: precision)
  533. end
  534. end
  535. 3 def extract_scale(sql_type)
  536. 435 case sql_type
  537. 16 when /\((\d+)\)/ then 0
  538. 177 when /\((\d+)(,(\d+))\)/ then $3.to_i
  539. end
  540. end
  541. 3 def extract_precision(sql_type)
  542. 2003 $1.to_i if sql_type =~ /\((\d+)(,\d+)?\)/
  543. end
  544. 3 def extract_limit(sql_type)
  545. 2911 $1.to_i if sql_type =~ /\((.*)\)/
  546. end
  547. 3 def translate_exception_class(e, sql, binds)
  548. 2289 message = "#{e.class.name}: #{e.message}"
  549. 2289 exception = translate_exception(
  550. e, message: message, sql: sql, binds: binds
  551. )
  552. 2289 exception.set_backtrace e.backtrace
  553. 2289 exception
  554. end
  555. 3 def log(sql, name = "SQL", binds = [], type_casted_binds = [], statement_name = nil) # :doc:
  556. @instrumenter.instrument(
  557. "sql.active_record",
  558. sql: sql,
  559. name: name,
  560. binds: binds,
  561. type_casted_binds: type_casted_binds,
  562. statement_name: statement_name,
  563. 427899 connection: self) do
  564. 427899 @lock.synchronize do
  565. 427899 yield
  566. end
  567. rescue => e
  568. 2285 raise translate_exception_class(e, sql, binds)
  569. end
  570. end
  571. 3 def translate_exception(exception, message:, sql:, binds:)
  572. # override in derived class
  573. 2209 case exception
  574. when RuntimeError
  575. exception
  576. else
  577. 2209 ActiveRecord::StatementInvalid.new(message, sql: sql, binds: binds)
  578. end
  579. end
  580. 3 def without_prepared_statement?(binds)
  581. 150689 !prepared_statements || binds.empty?
  582. end
  583. 3 def column_for(table_name, column_name)
  584. 53 column_name = column_name.to_s
  585. 241 columns(table_name).detect { |c| c.name == column_name } ||
  586. raise(ActiveRecordError, "No such column: #{table_name}.#{column_name}")
  587. end
  588. 3 def column_for_attribute(attribute)
  589. 65 table_name = attribute.relation.name
  590. 65 schema_cache.columns_hash(table_name)[attribute.name.to_s]
  591. end
  592. 3 def collector
  593. 53620 if prepared_statements
  594. 53122 Arel::Collectors::Composite.new(
  595. Arel::Collectors::SQLString.new,
  596. Arel::Collectors::Bind.new,
  597. )
  598. else
  599. 498 Arel::Collectors::SubstituteBinds.new(
  600. self,
  601. Arel::Collectors::SQLString.new,
  602. )
  603. end
  604. end
  605. 3 def arel_visitor
  606. 43 Arel::Visitors::ToSql.new(self)
  607. end
  608. 3 def build_statement_pool
  609. end
  610. # Builds the result object.
  611. #
  612. # This is an internal hook to make possible connection adapters to build
  613. # custom result objects with connection-specific data.
  614. 3 def build_result(columns:, rows:, column_types: {})
  615. 164288 ActiveRecord::Result.new(columns, rows, column_types)
  616. end
  617. end
  618. end
  619. end

lib/active_record/connection_adapters/abstract_mysql_adapter.rb

0.0% lines covered

632 relevant lines. 0 lines covered and 632 lines missed.
    
  1. # frozen_string_literal: true
  2. require "active_record/connection_adapters/abstract_adapter"
  3. require "active_record/connection_adapters/statement_pool"
  4. require "active_record/connection_adapters/mysql/column"
  5. require "active_record/connection_adapters/mysql/explain_pretty_printer"
  6. require "active_record/connection_adapters/mysql/quoting"
  7. require "active_record/connection_adapters/mysql/schema_creation"
  8. require "active_record/connection_adapters/mysql/schema_definitions"
  9. require "active_record/connection_adapters/mysql/schema_dumper"
  10. require "active_record/connection_adapters/mysql/schema_statements"
  11. require "active_record/connection_adapters/mysql/type_metadata"
  12. module ActiveRecord
  13. module ConnectionAdapters
  14. class AbstractMysqlAdapter < AbstractAdapter
  15. include MySQL::Quoting
  16. include MySQL::SchemaStatements
  17. ##
  18. # :singleton-method:
  19. # By default, the Mysql2Adapter will consider all columns of type <tt>tinyint(1)</tt>
  20. # as boolean. If you wish to disable this emulation you can add the following line
  21. # to your application.rb file:
  22. #
  23. # ActiveRecord::ConnectionAdapters::Mysql2Adapter.emulate_booleans = false
  24. class_attribute :emulate_booleans, default: true
  25. NATIVE_DATABASE_TYPES = {
  26. primary_key: "bigint auto_increment PRIMARY KEY",
  27. string: { name: "varchar", limit: 255 },
  28. text: { name: "text" },
  29. integer: { name: "int", limit: 4 },
  30. float: { name: "float", limit: 24 },
  31. decimal: { name: "decimal" },
  32. datetime: { name: "datetime" },
  33. timestamp: { name: "timestamp" },
  34. time: { name: "time" },
  35. date: { name: "date" },
  36. binary: { name: "blob" },
  37. blob: { name: "blob" },
  38. boolean: { name: "tinyint", limit: 1 },
  39. json: { name: "json" },
  40. }
  41. class StatementPool < ConnectionAdapters::StatementPool # :nodoc:
  42. private
  43. def dealloc(stmt)
  44. stmt.close
  45. end
  46. end
  47. def initialize(connection, logger, connection_options, config)
  48. super(connection, logger, config)
  49. end
  50. def get_database_version #:nodoc:
  51. full_version_string = get_full_version
  52. version_string = version_string(full_version_string)
  53. Version.new(version_string, full_version_string)
  54. end
  55. def mariadb? # :nodoc:
  56. /mariadb/i.match?(full_version)
  57. end
  58. def supports_bulk_alter?
  59. true
  60. end
  61. def supports_index_sort_order?
  62. !mariadb? && database_version >= "8.0.1"
  63. end
  64. def supports_expression_index?
  65. !mariadb? && database_version >= "8.0.13"
  66. end
  67. def supports_transaction_isolation?
  68. true
  69. end
  70. def supports_explain?
  71. true
  72. end
  73. def supports_indexes_in_create?
  74. true
  75. end
  76. def supports_foreign_keys?
  77. true
  78. end
  79. def supports_check_constraints?
  80. if mariadb?
  81. database_version >= "10.2.1"
  82. else
  83. database_version >= "8.0.16"
  84. end
  85. end
  86. def supports_views?
  87. true
  88. end
  89. def supports_datetime_with_precision?
  90. mariadb? || database_version >= "5.6.4"
  91. end
  92. def supports_virtual_columns?
  93. mariadb? || database_version >= "5.7.5"
  94. end
  95. # See https://dev.mysql.com/doc/refman/en/optimizer-hints.html for more details.
  96. def supports_optimizer_hints?
  97. !mariadb? && database_version >= "5.7.7"
  98. end
  99. def supports_common_table_expressions?
  100. if mariadb?
  101. database_version >= "10.2.1"
  102. else
  103. database_version >= "8.0.1"
  104. end
  105. end
  106. def supports_advisory_locks?
  107. true
  108. end
  109. def supports_insert_on_duplicate_skip?
  110. true
  111. end
  112. def supports_insert_on_duplicate_update?
  113. true
  114. end
  115. def get_advisory_lock(lock_name, timeout = 0) # :nodoc:
  116. query_value("SELECT GET_LOCK(#{quote(lock_name.to_s)}, #{timeout})") == 1
  117. end
  118. def release_advisory_lock(lock_name) # :nodoc:
  119. query_value("SELECT RELEASE_LOCK(#{quote(lock_name.to_s)})") == 1
  120. end
  121. def native_database_types
  122. NATIVE_DATABASE_TYPES
  123. end
  124. def index_algorithms
  125. {
  126. default: "ALGORITHM = DEFAULT",
  127. copy: "ALGORITHM = COPY",
  128. inplace: "ALGORITHM = INPLACE",
  129. instant: "ALGORITHM = INSTANT",
  130. }
  131. end
  132. # HELPER METHODS ===========================================
  133. # The two drivers have slightly different ways of yielding hashes of results, so
  134. # this method must be implemented to provide a uniform interface.
  135. def each_hash(result) # :nodoc:
  136. raise NotImplementedError
  137. end
  138. # Must return the MySQL error number from the exception, if the exception has an
  139. # error number.
  140. def error_number(exception) # :nodoc:
  141. raise NotImplementedError
  142. end
  143. # REFERENTIAL INTEGRITY ====================================
  144. def disable_referential_integrity #:nodoc:
  145. old = query_value("SELECT @@FOREIGN_KEY_CHECKS")
  146. begin
  147. update("SET FOREIGN_KEY_CHECKS = 0")
  148. yield
  149. ensure
  150. update("SET FOREIGN_KEY_CHECKS = #{old}")
  151. end
  152. end
  153. # CONNECTION MANAGEMENT ====================================
  154. def clear_cache! # :nodoc:
  155. reload_type_map
  156. super
  157. end
  158. #--
  159. # DATABASE STATEMENTS ======================================
  160. #++
  161. # Executes the SQL statement in the context of this connection.
  162. def execute(sql, name = nil)
  163. materialize_transactions
  164. mark_transaction_written_if_write(sql)
  165. log(sql, name) do
  166. ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
  167. @connection.query(sql)
  168. end
  169. end
  170. end
  171. # Mysql2Adapter doesn't have to free a result after using it, but we use this method
  172. # to write stuff in an abstract way without concerning ourselves about whether it
  173. # needs to be explicitly freed or not.
  174. def execute_and_free(sql, name = nil) # :nodoc:
  175. yield execute(sql, name)
  176. end
  177. def begin_db_transaction
  178. execute("BEGIN", "TRANSACTION")
  179. end
  180. def begin_isolated_db_transaction(isolation)
  181. execute "SET TRANSACTION ISOLATION LEVEL #{transaction_isolation_levels.fetch(isolation)}"
  182. begin_db_transaction
  183. end
  184. def commit_db_transaction #:nodoc:
  185. execute("COMMIT", "TRANSACTION")
  186. end
  187. def exec_rollback_db_transaction #:nodoc:
  188. execute("ROLLBACK", "TRANSACTION")
  189. end
  190. def empty_insert_statement_value(primary_key = nil)
  191. "VALUES ()"
  192. end
  193. # SCHEMA STATEMENTS ========================================
  194. # Drops the database specified on the +name+ attribute
  195. # and creates it again using the provided +options+.
  196. def recreate_database(name, options = {})
  197. drop_database(name)
  198. sql = create_database(name, options)
  199. reconnect!
  200. sql
  201. end
  202. # Create a new MySQL database with optional <tt>:charset</tt> and <tt>:collation</tt>.
  203. # Charset defaults to utf8mb4.
  204. #
  205. # Example:
  206. # create_database 'charset_test', charset: 'latin1', collation: 'latin1_bin'
  207. # create_database 'matt_development'
  208. # create_database 'matt_development', charset: :big5
  209. def create_database(name, options = {})
  210. if options[:collation]
  211. execute "CREATE DATABASE #{quote_table_name(name)} DEFAULT COLLATE #{quote_table_name(options[:collation])}"
  212. elsif options[:charset]
  213. execute "CREATE DATABASE #{quote_table_name(name)} DEFAULT CHARACTER SET #{quote_table_name(options[:charset])}"
  214. elsif row_format_dynamic_by_default?
  215. execute "CREATE DATABASE #{quote_table_name(name)} DEFAULT CHARACTER SET `utf8mb4`"
  216. else
  217. raise "Configure a supported :charset and ensure innodb_large_prefix is enabled to support indexes on varchar(255) string columns."
  218. end
  219. end
  220. # Drops a MySQL database.
  221. #
  222. # Example:
  223. # drop_database('sebastian_development')
  224. def drop_database(name) #:nodoc:
  225. execute "DROP DATABASE IF EXISTS #{quote_table_name(name)}"
  226. end
  227. def current_database
  228. query_value("SELECT database()", "SCHEMA")
  229. end
  230. # Returns the database character set.
  231. def charset
  232. show_variable "character_set_database"
  233. end
  234. # Returns the database collation strategy.
  235. def collation
  236. show_variable "collation_database"
  237. end
  238. def table_comment(table_name) # :nodoc:
  239. scope = quoted_scope(table_name)
  240. query_value(<<~SQL, "SCHEMA").presence
  241. SELECT table_comment
  242. FROM information_schema.tables
  243. WHERE table_schema = #{scope[:schema]}
  244. AND table_name = #{scope[:name]}
  245. SQL
  246. end
  247. def change_table_comment(table_name, comment_or_changes) # :nodoc:
  248. comment = extract_new_comment_value(comment_or_changes)
  249. comment = "" if comment.nil?
  250. execute("ALTER TABLE #{quote_table_name(table_name)} COMMENT #{quote(comment)}")
  251. end
  252. # Renames a table.
  253. #
  254. # Example:
  255. # rename_table('octopuses', 'octopi')
  256. def rename_table(table_name, new_name)
  257. schema_cache.clear_data_source_cache!(table_name.to_s)
  258. schema_cache.clear_data_source_cache!(new_name.to_s)
  259. execute "RENAME TABLE #{quote_table_name(table_name)} TO #{quote_table_name(new_name)}"
  260. rename_table_indexes(table_name, new_name)
  261. end
  262. # Drops a table from the database.
  263. #
  264. # [<tt>:force</tt>]
  265. # Set to +:cascade+ to drop dependent objects as well.
  266. # Defaults to false.
  267. # [<tt>:if_exists</tt>]
  268. # Set to +true+ to only drop the table if it exists.
  269. # Defaults to false.
  270. # [<tt>:temporary</tt>]
  271. # Set to +true+ to drop temporary table.
  272. # Defaults to false.
  273. #
  274. # Although this command ignores most +options+ and the block if one is given,
  275. # it can be helpful to provide these in a migration's +change+ method so it can be reverted.
  276. # In that case, +options+ and the block will be used by create_table.
  277. def drop_table(table_name, **options)
  278. schema_cache.clear_data_source_cache!(table_name.to_s)
  279. execute "DROP#{' TEMPORARY' if options[:temporary]} TABLE#{' IF EXISTS' if options[:if_exists]} #{quote_table_name(table_name)}#{' CASCADE' if options[:force] == :cascade}"
  280. end
  281. def rename_index(table_name, old_name, new_name)
  282. if supports_rename_index?
  283. validate_index_length!(table_name, new_name)
  284. execute "ALTER TABLE #{quote_table_name(table_name)} RENAME INDEX #{quote_table_name(old_name)} TO #{quote_table_name(new_name)}"
  285. else
  286. super
  287. end
  288. end
  289. def change_column_default(table_name, column_name, default_or_changes) #:nodoc:
  290. default = extract_new_default_value(default_or_changes)
  291. change_column table_name, column_name, nil, default: default
  292. end
  293. def change_column_null(table_name, column_name, null, default = nil) #:nodoc:
  294. unless null || default.nil?
  295. execute("UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote(default)} WHERE #{quote_column_name(column_name)} IS NULL")
  296. end
  297. change_column table_name, column_name, nil, null: null
  298. end
  299. def change_column_comment(table_name, column_name, comment_or_changes) # :nodoc:
  300. comment = extract_new_comment_value(comment_or_changes)
  301. change_column table_name, column_name, nil, comment: comment
  302. end
  303. def change_column(table_name, column_name, type, **options) #:nodoc:
  304. execute("ALTER TABLE #{quote_table_name(table_name)} #{change_column_for_alter(table_name, column_name, type, **options)}")
  305. end
  306. def rename_column(table_name, column_name, new_column_name) #:nodoc:
  307. execute("ALTER TABLE #{quote_table_name(table_name)} #{rename_column_for_alter(table_name, column_name, new_column_name)}")
  308. rename_column_indexes(table_name, column_name, new_column_name)
  309. end
  310. def add_index(table_name, column_name, **options) #:nodoc:
  311. index, algorithm, if_not_exists = add_index_options(table_name, column_name, **options)
  312. return if if_not_exists && index_exists?(table_name, column_name, name: index.name)
  313. create_index = CreateIndexDefinition.new(index, algorithm)
  314. execute schema_creation.accept(create_index)
  315. end
  316. def add_sql_comment!(sql, comment) # :nodoc:
  317. sql << " COMMENT #{quote(comment)}" if comment.present?
  318. sql
  319. end
  320. def foreign_keys(table_name)
  321. raise ArgumentError unless table_name.present?
  322. scope = quoted_scope(table_name)
  323. fk_info = exec_query(<<~SQL, "SCHEMA")
  324. SELECT fk.referenced_table_name AS 'to_table',
  325. fk.referenced_column_name AS 'primary_key',
  326. fk.column_name AS 'column',
  327. fk.constraint_name AS 'name',
  328. rc.update_rule AS 'on_update',
  329. rc.delete_rule AS 'on_delete'
  330. FROM information_schema.referential_constraints rc
  331. JOIN information_schema.key_column_usage fk
  332. USING (constraint_schema, constraint_name)
  333. WHERE fk.referenced_column_name IS NOT NULL
  334. AND fk.table_schema = #{scope[:schema]}
  335. AND fk.table_name = #{scope[:name]}
  336. AND rc.constraint_schema = #{scope[:schema]}
  337. AND rc.table_name = #{scope[:name]}
  338. SQL
  339. fk_info.map do |row|
  340. options = {
  341. column: row["column"],
  342. name: row["name"],
  343. primary_key: row["primary_key"]
  344. }
  345. options[:on_update] = extract_foreign_key_action(row["on_update"])
  346. options[:on_delete] = extract_foreign_key_action(row["on_delete"])
  347. ForeignKeyDefinition.new(table_name, row["to_table"], options)
  348. end
  349. end
  350. def check_constraints(table_name)
  351. scope = quoted_scope(table_name)
  352. chk_info = exec_query(<<~SQL, "SCHEMA")
  353. SELECT cc.constraint_name AS 'name',
  354. cc.check_clause AS 'expression'
  355. FROM information_schema.check_constraints cc
  356. JOIN information_schema.table_constraints tc
  357. USING (constraint_schema, constraint_name)
  358. WHERE tc.table_schema = #{scope[:schema]}
  359. AND tc.table_name = #{scope[:name]}
  360. AND cc.constraint_schema = #{scope[:schema]}
  361. SQL
  362. chk_info.map do |row|
  363. options = {
  364. name: row["name"]
  365. }
  366. expression = row["expression"]
  367. expression = expression[1..-2] unless mariadb? # remove parentheses added by mysql
  368. CheckConstraintDefinition.new(table_name, expression, options)
  369. end
  370. end
  371. def table_options(table_name) # :nodoc:
  372. create_table_info = create_table_info(table_name)
  373. # strip create_definitions and partition_options
  374. # Be aware that `create_table_info` might not include any table options due to `NO_TABLE_OPTIONS` sql mode.
  375. raw_table_options = create_table_info.sub(/\A.*\n\) ?/m, "").sub(/\n\/\*!.*\*\/\n\z/m, "").strip
  376. return if raw_table_options.empty?
  377. table_options = {}
  378. if / DEFAULT CHARSET=(?<charset>\w+)(?: COLLATE=(?<collation>\w+))?/ =~ raw_table_options
  379. raw_table_options = $` + $' # before part + after part
  380. table_options[:charset] = charset
  381. table_options[:collation] = collation if collation
  382. end
  383. # strip AUTO_INCREMENT
  384. raw_table_options.sub!(/(ENGINE=\w+)(?: AUTO_INCREMENT=\d+)/, '\1')
  385. # strip COMMENT
  386. if raw_table_options.sub!(/ COMMENT='.+'/, "")
  387. table_options[:comment] = table_comment(table_name)
  388. end
  389. table_options[:options] = raw_table_options unless raw_table_options == "ENGINE=InnoDB"
  390. table_options
  391. end
  392. # SHOW VARIABLES LIKE 'name'
  393. def show_variable(name)
  394. query_value("SELECT @@#{name}", "SCHEMA")
  395. rescue ActiveRecord::StatementInvalid
  396. nil
  397. end
  398. def primary_keys(table_name) # :nodoc:
  399. raise ArgumentError unless table_name.present?
  400. scope = quoted_scope(table_name)
  401. query_values(<<~SQL, "SCHEMA")
  402. SELECT column_name
  403. FROM information_schema.statistics
  404. WHERE index_name = 'PRIMARY'
  405. AND table_schema = #{scope[:schema]}
  406. AND table_name = #{scope[:name]}
  407. ORDER BY seq_in_index
  408. SQL
  409. end
  410. def default_uniqueness_comparison(attribute, value, klass) # :nodoc:
  411. column = column_for_attribute(attribute)
  412. if column.collation && !column.case_sensitive? && !value.nil?
  413. ActiveSupport::Deprecation.warn(<<~MSG.squish)
  414. Uniqueness validator will no longer enforce case sensitive comparison in Rails 6.1.
  415. To continue case sensitive comparison on the :#{attribute.name} attribute in #{klass} model,
  416. pass `case_sensitive: true` option explicitly to the uniqueness validator.
  417. MSG
  418. attribute.eq(Arel::Nodes::Bin.new(value))
  419. else
  420. super
  421. end
  422. end
  423. def case_sensitive_comparison(attribute, value) # :nodoc:
  424. column = column_for_attribute(attribute)
  425. if column.collation && !column.case_sensitive?
  426. attribute.eq(Arel::Nodes::Bin.new(value))
  427. else
  428. super
  429. end
  430. end
  431. def can_perform_case_insensitive_comparison_for?(column)
  432. column.case_sensitive?
  433. end
  434. private :can_perform_case_insensitive_comparison_for?
  435. # In MySQL 5.7.5 and up, ONLY_FULL_GROUP_BY affects handling of queries that use
  436. # DISTINCT and ORDER BY. It requires the ORDER BY columns in the select list for
  437. # distinct queries, and requires that the ORDER BY include the distinct column.
  438. # See https://dev.mysql.com/doc/refman/en/group-by-handling.html
  439. def columns_for_distinct(columns, orders) # :nodoc:
  440. order_columns = orders.compact_blank.map { |s|
  441. # Convert Arel node to string
  442. s = visitor.compile(s) unless s.is_a?(String)
  443. # Remove any ASC/DESC modifiers
  444. s.gsub(/\s+(?:ASC|DESC)\b/i, "")
  445. }.compact_blank.map.with_index { |column, i| "#{column} AS alias_#{i}" }
  446. (order_columns << super).join(", ")
  447. end
  448. def strict_mode?
  449. self.class.type_cast_config_to_boolean(@config.fetch(:strict, true))
  450. end
  451. def default_index_type?(index) # :nodoc:
  452. index.using == :btree || super
  453. end
  454. def build_insert_sql(insert) # :nodoc:
  455. sql = +"INSERT #{insert.into} #{insert.values_list}"
  456. if insert.skip_duplicates?
  457. no_op_column = quote_column_name(insert.keys.first)
  458. sql << " ON DUPLICATE KEY UPDATE #{no_op_column}=#{no_op_column}"
  459. elsif insert.update_duplicates?
  460. sql << " ON DUPLICATE KEY UPDATE "
  461. sql << insert.touch_model_timestamps_unless { |column| "#{column}<=>VALUES(#{column})" }
  462. sql << insert.updatable_columns.map { |column| "#{column}=VALUES(#{column})" }.join(",")
  463. end
  464. sql
  465. end
  466. def check_version # :nodoc:
  467. if database_version < "5.5.8"
  468. raise "Your version of MySQL (#{database_version}) is too old. Active Record supports MySQL >= 5.5.8."
  469. end
  470. end
  471. private
  472. def initialize_type_map(m = type_map)
  473. super
  474. m.register_type(%r(char)i) do |sql_type|
  475. limit = extract_limit(sql_type)
  476. Type.lookup(:string, adapter: :mysql2, limit: limit)
  477. end
  478. m.register_type %r(tinytext)i, Type::Text.new(limit: 2**8 - 1)
  479. m.register_type %r(tinyblob)i, Type::Binary.new(limit: 2**8 - 1)
  480. m.register_type %r(text)i, Type::Text.new(limit: 2**16 - 1)
  481. m.register_type %r(blob)i, Type::Binary.new(limit: 2**16 - 1)
  482. m.register_type %r(mediumtext)i, Type::Text.new(limit: 2**24 - 1)
  483. m.register_type %r(mediumblob)i, Type::Binary.new(limit: 2**24 - 1)
  484. m.register_type %r(longtext)i, Type::Text.new(limit: 2**32 - 1)
  485. m.register_type %r(longblob)i, Type::Binary.new(limit: 2**32 - 1)
  486. m.register_type %r(^float)i, Type::Float.new(limit: 24)
  487. m.register_type %r(^double)i, Type::Float.new(limit: 53)
  488. register_integer_type m, %r(^bigint)i, limit: 8
  489. register_integer_type m, %r(^int)i, limit: 4
  490. register_integer_type m, %r(^mediumint)i, limit: 3
  491. register_integer_type m, %r(^smallint)i, limit: 2
  492. register_integer_type m, %r(^tinyint)i, limit: 1
  493. m.register_type %r(^tinyint\(1\))i, Type::Boolean.new if emulate_booleans
  494. m.alias_type %r(year)i, "integer"
  495. m.alias_type %r(bit)i, "binary"
  496. m.register_type %r(^enum)i, Type.lookup(:string, adapter: :mysql2)
  497. m.register_type %r(^set)i, Type.lookup(:string, adapter: :mysql2)
  498. end
  499. def register_integer_type(mapping, key, **options)
  500. mapping.register_type(key) do |sql_type|
  501. if /\bunsigned\b/.match?(sql_type)
  502. Type::UnsignedInteger.new(**options)
  503. else
  504. Type::Integer.new(**options)
  505. end
  506. end
  507. end
  508. def extract_precision(sql_type)
  509. if /\A(?:date)?time(?:stamp)?\b/.match?(sql_type)
  510. super || 0
  511. else
  512. super
  513. end
  514. end
  515. # See https://dev.mysql.com/doc/refman/en/server-error-reference.html
  516. ER_DB_CREATE_EXISTS = 1007
  517. ER_FILSORT_ABORT = 1028
  518. ER_DUP_ENTRY = 1062
  519. ER_NOT_NULL_VIOLATION = 1048
  520. ER_NO_REFERENCED_ROW = 1216
  521. ER_ROW_IS_REFERENCED = 1217
  522. ER_DO_NOT_HAVE_DEFAULT = 1364
  523. ER_ROW_IS_REFERENCED_2 = 1451
  524. ER_NO_REFERENCED_ROW_2 = 1452
  525. ER_DATA_TOO_LONG = 1406
  526. ER_OUT_OF_RANGE = 1264
  527. ER_LOCK_DEADLOCK = 1213
  528. ER_CANNOT_ADD_FOREIGN = 1215
  529. ER_CANNOT_CREATE_TABLE = 1005
  530. ER_LOCK_WAIT_TIMEOUT = 1205
  531. ER_QUERY_INTERRUPTED = 1317
  532. ER_QUERY_TIMEOUT = 3024
  533. ER_FK_INCOMPATIBLE_COLUMNS = 3780
  534. def translate_exception(exception, message:, sql:, binds:)
  535. case error_number(exception)
  536. when ER_DB_CREATE_EXISTS
  537. DatabaseAlreadyExists.new(message, sql: sql, binds: binds)
  538. when ER_DUP_ENTRY
  539. RecordNotUnique.new(message, sql: sql, binds: binds)
  540. when ER_NO_REFERENCED_ROW, ER_ROW_IS_REFERENCED, ER_ROW_IS_REFERENCED_2, ER_NO_REFERENCED_ROW_2
  541. InvalidForeignKey.new(message, sql: sql, binds: binds)
  542. when ER_CANNOT_ADD_FOREIGN, ER_FK_INCOMPATIBLE_COLUMNS
  543. mismatched_foreign_key(message, sql: sql, binds: binds)
  544. when ER_CANNOT_CREATE_TABLE
  545. if message.include?("errno: 150")
  546. mismatched_foreign_key(message, sql: sql, binds: binds)
  547. else
  548. super
  549. end
  550. when ER_DATA_TOO_LONG
  551. ValueTooLong.new(message, sql: sql, binds: binds)
  552. when ER_OUT_OF_RANGE
  553. RangeError.new(message, sql: sql, binds: binds)
  554. when ER_NOT_NULL_VIOLATION, ER_DO_NOT_HAVE_DEFAULT
  555. NotNullViolation.new(message, sql: sql, binds: binds)
  556. when ER_LOCK_DEADLOCK
  557. Deadlocked.new(message, sql: sql, binds: binds)
  558. when ER_LOCK_WAIT_TIMEOUT
  559. LockWaitTimeout.new(message, sql: sql, binds: binds)
  560. when ER_QUERY_TIMEOUT, ER_FILSORT_ABORT
  561. StatementTimeout.new(message, sql: sql, binds: binds)
  562. when ER_QUERY_INTERRUPTED
  563. QueryCanceled.new(message, sql: sql, binds: binds)
  564. else
  565. super
  566. end
  567. end
  568. def change_column_for_alter(table_name, column_name, type, **options)
  569. column = column_for(table_name, column_name)
  570. type ||= column.sql_type
  571. unless options.key?(:default)
  572. options[:default] = column.default
  573. end
  574. unless options.key?(:null)
  575. options[:null] = column.null
  576. end
  577. unless options.key?(:comment)
  578. options[:comment] = column.comment
  579. end
  580. td = create_table_definition(table_name)
  581. cd = td.new_column_definition(column.name, type, **options)
  582. schema_creation.accept(ChangeColumnDefinition.new(cd, column.name))
  583. end
  584. def rename_column_for_alter(table_name, column_name, new_column_name)
  585. return rename_column_sql(table_name, column_name, new_column_name) if supports_rename_column?
  586. column = column_for(table_name, column_name)
  587. options = {
  588. default: column.default,
  589. null: column.null,
  590. auto_increment: column.auto_increment?,
  591. comment: column.comment
  592. }
  593. current_type = exec_query("SHOW COLUMNS FROM #{quote_table_name(table_name)} LIKE #{quote(column_name)}", "SCHEMA").first["Type"]
  594. td = create_table_definition(table_name)
  595. cd = td.new_column_definition(new_column_name, current_type, **options)
  596. schema_creation.accept(ChangeColumnDefinition.new(cd, column.name))
  597. end
  598. def add_index_for_alter(table_name, column_name, **options)
  599. index, algorithm, _ = add_index_options(table_name, column_name, **options)
  600. algorithm = ", #{algorithm}" if algorithm
  601. "ADD #{schema_creation.accept(index)}#{algorithm}"
  602. end
  603. def remove_index_for_alter(table_name, column_name = nil, **options)
  604. index_name = index_name_for_remove(table_name, column_name, options)
  605. "DROP INDEX #{quote_column_name(index_name)}"
  606. end
  607. def supports_rename_index?
  608. if mariadb?
  609. database_version >= "10.5.2"
  610. else
  611. database_version >= "5.7.6"
  612. end
  613. end
  614. def supports_rename_column?
  615. if mariadb?
  616. database_version >= "10.5.2"
  617. else
  618. database_version >= "8.0.3"
  619. end
  620. end
  621. def configure_connection
  622. variables = @config.fetch(:variables, {}).stringify_keys
  623. # By default, MySQL 'where id is null' selects the last inserted id; Turn this off.
  624. variables["sql_auto_is_null"] = 0
  625. # Increase timeout so the server doesn't disconnect us.
  626. wait_timeout = self.class.type_cast_config_to_integer(@config[:wait_timeout])
  627. wait_timeout = 2147483 unless wait_timeout.is_a?(Integer)
  628. variables["wait_timeout"] = wait_timeout
  629. defaults = [":default", :default].to_set
  630. # Make MySQL reject illegal values rather than truncating or blanking them, see
  631. # https://dev.mysql.com/doc/refman/en/sql-mode.html#sqlmode_strict_all_tables
  632. # If the user has provided another value for sql_mode, don't replace it.
  633. if sql_mode = variables.delete("sql_mode")
  634. sql_mode = quote(sql_mode)
  635. elsif !defaults.include?(strict_mode?)
  636. if strict_mode?
  637. sql_mode = "CONCAT(@@sql_mode, ',STRICT_ALL_TABLES')"
  638. else
  639. sql_mode = "REPLACE(@@sql_mode, 'STRICT_TRANS_TABLES', '')"
  640. sql_mode = "REPLACE(#{sql_mode}, 'STRICT_ALL_TABLES', '')"
  641. sql_mode = "REPLACE(#{sql_mode}, 'TRADITIONAL', '')"
  642. end
  643. sql_mode = "CONCAT(#{sql_mode}, ',NO_AUTO_VALUE_ON_ZERO')"
  644. end
  645. sql_mode_assignment = "@@SESSION.sql_mode = #{sql_mode}, " if sql_mode
  646. # NAMES does not have an equals sign, see
  647. # https://dev.mysql.com/doc/refman/en/set-names.html
  648. # (trailing comma because variable_assignments will always have content)
  649. if @config[:encoding]
  650. encoding = +"NAMES #{@config[:encoding]}"
  651. encoding << " COLLATE #{@config[:collation]}" if @config[:collation]
  652. encoding << ", "
  653. end
  654. # Gather up all of the SET variables...
  655. variable_assignments = variables.map do |k, v|
  656. if defaults.include?(v)
  657. "@@SESSION.#{k} = DEFAULT" # Sets the value to the global or compile default
  658. elsif !v.nil?
  659. "@@SESSION.#{k} = #{quote(v)}"
  660. end
  661. # or else nil; compact to clear nils out
  662. end.compact.join(", ")
  663. # ...and send them all in one query
  664. execute("SET #{encoding} #{sql_mode_assignment} #{variable_assignments}", "SCHEMA")
  665. end
  666. def column_definitions(table_name) # :nodoc:
  667. execute_and_free("SHOW FULL FIELDS FROM #{quote_table_name(table_name)}", "SCHEMA") do |result|
  668. each_hash(result)
  669. end
  670. end
  671. def create_table_info(table_name) # :nodoc:
  672. exec_query("SHOW CREATE TABLE #{quote_table_name(table_name)}", "SCHEMA").first["Create Table"]
  673. end
  674. def arel_visitor
  675. Arel::Visitors::MySQL.new(self)
  676. end
  677. def build_statement_pool
  678. StatementPool.new(self.class.type_cast_config_to_integer(@config[:statement_limit]))
  679. end
  680. def mismatched_foreign_key(message, sql:, binds:)
  681. match = %r/
  682. (?:CREATE|ALTER)\s+TABLE\s*(?:`?\w+`?\.)?`?(?<table>\w+)`?.+?
  683. FOREIGN\s+KEY\s*\(`?(?<foreign_key>\w+)`?\)\s*
  684. REFERENCES\s*(`?(?<target_table>\w+)`?)\s*\(`?(?<primary_key>\w+)`?\)
  685. /xmi.match(sql)
  686. options = {
  687. message: message,
  688. sql: sql,
  689. binds: binds,
  690. }
  691. if match
  692. options[:table] = match[:table]
  693. options[:foreign_key] = match[:foreign_key]
  694. options[:target_table] = match[:target_table]
  695. options[:primary_key] = match[:primary_key]
  696. options[:primary_key_column] = column_for(match[:target_table], match[:primary_key])
  697. end
  698. MismatchedForeignKey.new(**options)
  699. end
  700. def version_string(full_version_string)
  701. full_version_string.match(/^(?:5\.5\.5-)?(\d+\.\d+\.\d+)/)[1]
  702. end
  703. # Alias MysqlString to work Mashal.load(File.read("legacy_record.dump")).
  704. # TODO: Remove the constant alias once Rails 6.1 has released.
  705. MysqlString = Type::String # :nodoc:
  706. ActiveRecord::Type.register(:immutable_string, adapter: :mysql2) do |_, **args|
  707. Type::ImmutableString.new(true: "1", false: "0", **args)
  708. end
  709. ActiveRecord::Type.register(:string, adapter: :mysql2) do |_, **args|
  710. Type::String.new(true: "1", false: "0", **args)
  711. end
  712. ActiveRecord::Type.register(:unsigned_integer, Type::UnsignedInteger, adapter: :mysql2)
  713. end
  714. end
  715. end

lib/active_record/connection_adapters/column.rb

100.0% lines covered

53 relevant lines. 53 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 module ActiveRecord
  3. # :stopdoc:
  4. 3 module ConnectionAdapters
  5. # An abstract definition of a column in a table.
  6. 3 class Column
  7. 3 include Deduplicable
  8. 3 attr_reader :name, :default, :sql_type_metadata, :null, :default_function, :collation, :comment
  9. 3 delegate :precision, :scale, :limit, :type, :sql_type, to: :sql_type_metadata, allow_nil: true
  10. # Instantiates a new column in the table.
  11. #
  12. # +name+ is the column's name, such as <tt>supplier_id</tt> in <tt>supplier_id bigint</tt>.
  13. # +default+ is the type-casted default value, such as +new+ in <tt>sales_stage varchar(20) default 'new'</tt>.
  14. # +sql_type_metadata+ is various information about the type of the column
  15. # +null+ determines if this column allows +NULL+ values.
  16. 3 def initialize(name, default, sql_type_metadata = nil, null = true, default_function = nil, collation: nil, comment: nil, **)
  17. 247320 @name = name.freeze
  18. 247320 @sql_type_metadata = sql_type_metadata
  19. 247320 @null = null
  20. 247320 @default = default
  21. 247320 @default_function = default_function
  22. 247320 @collation = collation
  23. 247320 @comment = comment
  24. end
  25. 3 def has_default?
  26. 16929 !default.nil? || default_function
  27. end
  28. 3 def bigint?
  29. 36255 /\Abigint\b/.match?(sql_type)
  30. end
  31. # Returns the human name of the column name.
  32. #
  33. # ===== Examples
  34. # Column.new('sales_stage', ...).human_name # => 'Sales stage'
  35. 3 def human_name
  36. 3 Base.human_attribute_name(@name)
  37. end
  38. 3 def init_with(coder)
  39. 4074 @name = coder["name"]
  40. 4074 @sql_type_metadata = coder["sql_type_metadata"]
  41. 4074 @null = coder["null"]
  42. 4074 @default = coder["default"]
  43. 4074 @default_function = coder["default_function"]
  44. 4074 @collation = coder["collation"]
  45. 4074 @comment = coder["comment"]
  46. end
  47. 3 def encode_with(coder)
  48. 3972 coder["name"] = @name
  49. 3972 coder["sql_type_metadata"] = @sql_type_metadata
  50. 3972 coder["null"] = @null
  51. 3972 coder["default"] = @default
  52. 3972 coder["default_function"] = @default_function
  53. 3972 coder["collation"] = @collation
  54. 3972 coder["comment"] = @comment
  55. end
  56. 3 def ==(other)
  57. 258252 other.is_a?(Column) &&
  58. name == other.name &&
  59. default == other.default &&
  60. sql_type_metadata == other.sql_type_metadata &&
  61. null == other.null &&
  62. default_function == other.default_function &&
  63. collation == other.collation &&
  64. comment == other.comment
  65. end
  66. 3 alias :eql? :==
  67. 3 def hash
  68. Column.hash ^
  69. name.hash ^
  70. name.encoding.hash ^
  71. default.hash ^
  72. sql_type_metadata.hash ^
  73. null.hash ^
  74. default_function.hash ^
  75. 293128 collation.hash ^
  76. comment.hash
  77. end
  78. 3 private
  79. 3 def deduplicated
  80. 2422 @name = -name
  81. 2422 @sql_type_metadata = sql_type_metadata.deduplicate if sql_type_metadata
  82. 2422 @default = -default if default
  83. 2422 @default_function = -default_function if default_function
  84. 2422 @collation = -collation if collation
  85. 2422 @comment = -comment if comment
  86. 2422 super
  87. end
  88. end
  89. 3 class NullColumn < Column
  90. 3 def initialize(name, **)
  91. 6 super(name, nil)
  92. end
  93. end
  94. end
  95. # :startdoc:
  96. end

lib/active_record/connection_adapters/deduplicable.rb

100.0% lines covered

15 relevant lines. 15 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 module ActiveRecord
  3. 3 module ConnectionAdapters # :nodoc:
  4. 3 module Deduplicable
  5. 3 extend ActiveSupport::Concern
  6. 3 module ClassMethods
  7. 3 def registry
  8. 561685 @registry ||= {}
  9. end
  10. 3 def new(*, **)
  11. 515557 super.deduplicate
  12. end
  13. end
  14. 3 def deduplicate
  15. 561685 self.class.registry[self] ||= deduplicated
  16. end
  17. 3 alias :-@ :deduplicate
  18. 3 private
  19. 3 def deduplicated
  20. 2976 freeze
  21. end
  22. end
  23. end
  24. end

lib/active_record/connection_adapters/mysql/column.rb

0.0% lines covered

21 relevant lines. 0 lines covered and 21 lines missed.
    
  1. # frozen_string_literal: true
  2. module ActiveRecord
  3. module ConnectionAdapters
  4. module MySQL
  5. class Column < ConnectionAdapters::Column # :nodoc:
  6. delegate :extra, to: :sql_type_metadata, allow_nil: true
  7. def unsigned?
  8. /\bunsigned(?: zerofill)?\z/.match?(sql_type)
  9. end
  10. def case_sensitive?
  11. collation && !collation.end_with?("_ci")
  12. end
  13. def auto_increment?
  14. extra == "auto_increment"
  15. end
  16. def virtual?
  17. /\b(?:VIRTUAL|STORED|PERSISTENT)\b/.match?(extra)
  18. end
  19. end
  20. end
  21. end
  22. end

lib/active_record/connection_adapters/mysql/database_statements.rb

0.0% lines covered

158 relevant lines. 0 lines covered and 158 lines missed.
    
  1. # frozen_string_literal: true
  2. module ActiveRecord
  3. module ConnectionAdapters
  4. module MySQL
  5. module DatabaseStatements
  6. # Returns an ActiveRecord::Result instance.
  7. def select_all(*, **) # :nodoc:
  8. result = if ExplainRegistry.collect? && prepared_statements
  9. unprepared_statement { super }
  10. else
  11. super
  12. end
  13. @connection.abandon_results!
  14. result
  15. end
  16. def query(sql, name = nil) # :nodoc:
  17. execute(sql, name).to_a
  18. end
  19. READ_QUERY = ActiveRecord::ConnectionAdapters::AbstractAdapter.build_read_query_regexp(
  20. :desc, :describe, :set, :show, :use
  21. ) # :nodoc:
  22. private_constant :READ_QUERY
  23. def write_query?(sql) # :nodoc:
  24. !READ_QUERY.match?(sql)
  25. end
  26. def explain(arel, binds = [])
  27. sql = "EXPLAIN #{to_sql(arel, binds)}"
  28. start = Concurrent.monotonic_time
  29. result = exec_query(sql, "EXPLAIN", binds)
  30. elapsed = Concurrent.monotonic_time - start
  31. MySQL::ExplainPrettyPrinter.new.pp(result, elapsed)
  32. end
  33. # Executes the SQL statement in the context of this connection.
  34. def execute(sql, name = nil)
  35. if preventing_writes? && write_query?(sql)
  36. raise ActiveRecord::ReadOnlyError, "Write query attempted while in readonly mode: #{sql}"
  37. end
  38. # make sure we carry over any changes to ActiveRecord::Base.default_timezone that have been
  39. # made since we established the connection
  40. @connection.query_options[:database_timezone] = ActiveRecord::Base.default_timezone
  41. super
  42. end
  43. def exec_query(sql, name = "SQL", binds = [], prepare: false)
  44. if without_prepared_statement?(binds)
  45. execute_and_free(sql, name) do |result|
  46. if result
  47. build_result(columns: result.fields, rows: result.to_a)
  48. else
  49. build_result(columns: [], rows: [])
  50. end
  51. end
  52. else
  53. exec_stmt_and_free(sql, name, binds, cache_stmt: prepare) do |_, result|
  54. if result
  55. build_result(columns: result.fields, rows: result.to_a)
  56. else
  57. build_result(columns: [], rows: [])
  58. end
  59. end
  60. end
  61. end
  62. def exec_delete(sql, name = nil, binds = [])
  63. if without_prepared_statement?(binds)
  64. @lock.synchronize do
  65. execute_and_free(sql, name) { @connection.affected_rows }
  66. end
  67. else
  68. exec_stmt_and_free(sql, name, binds) { |stmt| stmt.affected_rows }
  69. end
  70. end
  71. alias :exec_update :exec_delete
  72. private
  73. def execute_batch(statements, name = nil)
  74. combine_multi_statements(statements).each do |statement|
  75. execute(statement, name)
  76. end
  77. @connection.abandon_results!
  78. end
  79. def default_insert_value(column)
  80. super unless column.auto_increment?
  81. end
  82. def last_inserted_id(result)
  83. @connection.last_id
  84. end
  85. def multi_statements_enabled?
  86. flags = @config[:flags]
  87. if flags.is_a?(Array)
  88. flags.include?("MULTI_STATEMENTS")
  89. else
  90. flags.anybits?(Mysql2::Client::MULTI_STATEMENTS)
  91. end
  92. end
  93. def with_multi_statements
  94. multi_statements_was = multi_statements_enabled?
  95. unless multi_statements_was
  96. @connection.set_server_option(Mysql2::Client::OPTION_MULTI_STATEMENTS_ON)
  97. end
  98. yield
  99. ensure
  100. unless multi_statements_was
  101. @connection.set_server_option(Mysql2::Client::OPTION_MULTI_STATEMENTS_OFF)
  102. end
  103. end
  104. def combine_multi_statements(total_sql)
  105. total_sql.each_with_object([]) do |sql, total_sql_chunks|
  106. previous_packet = total_sql_chunks.last
  107. if max_allowed_packet_reached?(sql, previous_packet)
  108. total_sql_chunks << +sql
  109. else
  110. previous_packet << ";\n"
  111. previous_packet << sql
  112. end
  113. end
  114. end
  115. def max_allowed_packet_reached?(current_packet, previous_packet)
  116. if current_packet.bytesize > max_allowed_packet
  117. raise ActiveRecordError,
  118. "Fixtures set is too large #{current_packet.bytesize}. Consider increasing the max_allowed_packet variable."
  119. elsif previous_packet.nil?
  120. true
  121. else
  122. (current_packet.bytesize + previous_packet.bytesize + 2) > max_allowed_packet
  123. end
  124. end
  125. def max_allowed_packet
  126. @max_allowed_packet ||= show_variable("max_allowed_packet")
  127. end
  128. def exec_stmt_and_free(sql, name, binds, cache_stmt: false)
  129. if preventing_writes? && write_query?(sql)
  130. raise ActiveRecord::ReadOnlyError, "Write query attempted while in readonly mode: #{sql}"
  131. end
  132. materialize_transactions
  133. mark_transaction_written_if_write(sql)
  134. # make sure we carry over any changes to ActiveRecord::Base.default_timezone that have been
  135. # made since we established the connection
  136. @connection.query_options[:database_timezone] = ActiveRecord::Base.default_timezone
  137. type_casted_binds = type_casted_binds(binds)
  138. log(sql, name, binds, type_casted_binds) do
  139. if cache_stmt
  140. stmt = @statements[sql] ||= @connection.prepare(sql)
  141. else
  142. stmt = @connection.prepare(sql)
  143. end
  144. begin
  145. result = ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
  146. stmt.execute(*type_casted_binds)
  147. end
  148. rescue Mysql2::Error => e
  149. if cache_stmt
  150. @statements.delete(sql)
  151. else
  152. stmt.close
  153. end
  154. raise e
  155. end
  156. ret = yield stmt, result
  157. result.free if result
  158. stmt.close unless cache_stmt
  159. ret
  160. end
  161. end
  162. end
  163. end
  164. end
  165. end

lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb

0.0% lines covered

48 relevant lines. 0 lines covered and 48 lines missed.
    
  1. # frozen_string_literal: true
  2. module ActiveRecord
  3. module ConnectionAdapters
  4. module MySQL
  5. class ExplainPrettyPrinter # :nodoc:
  6. # Pretty prints the result of an EXPLAIN in a way that resembles the output of the
  7. # MySQL shell:
  8. #
  9. # +----+-------------+-------+-------+---------------+---------+---------+-------+------+-------------+
  10. # | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
  11. # +----+-------------+-------+-------+---------------+---------+---------+-------+------+-------------+
  12. # | 1 | SIMPLE | users | const | PRIMARY | PRIMARY | 4 | const | 1 | |
  13. # | 1 | SIMPLE | posts | ALL | NULL | NULL | NULL | NULL | 1 | Using where |
  14. # +----+-------------+-------+-------+---------------+---------+---------+-------+------+-------------+
  15. # 2 rows in set (0.00 sec)
  16. #
  17. # This is an exercise in Ruby hyperrealism :).
  18. def pp(result, elapsed)
  19. widths = compute_column_widths(result)
  20. separator = build_separator(widths)
  21. pp = []
  22. pp << separator
  23. pp << build_cells(result.columns, widths)
  24. pp << separator
  25. result.rows.each do |row|
  26. pp << build_cells(row, widths)
  27. end
  28. pp << separator
  29. pp << build_footer(result.rows.length, elapsed)
  30. pp.join("\n") + "\n"
  31. end
  32. private
  33. def compute_column_widths(result)
  34. [].tap do |widths|
  35. result.columns.each_with_index do |column, i|
  36. cells_in_column = [column] + result.rows.map { |r| r[i].nil? ? "NULL" : r[i].to_s }
  37. widths << cells_in_column.map(&:length).max
  38. end
  39. end
  40. end
  41. def build_separator(widths)
  42. padding = 1
  43. "+" + widths.map { |w| "-" * (w + (padding * 2)) }.join("+") + "+"
  44. end
  45. def build_cells(items, widths)
  46. cells = []
  47. items.each_with_index do |item, i|
  48. item = "NULL" if item.nil?
  49. justifier = item.is_a?(Numeric) ? "rjust" : "ljust"
  50. cells << item.to_s.send(justifier, widths[i])
  51. end
  52. "| " + cells.join(" | ") + " |"
  53. end
  54. def build_footer(nrows, elapsed)
  55. rows_label = nrows == 1 ? "row" : "rows"
  56. "#{nrows} #{rows_label} in set (%.2f sec)" % elapsed
  57. end
  58. end
  59. end
  60. end
  61. end

lib/active_record/connection_adapters/mysql/quoting.rb

0.0% lines covered

66 relevant lines. 0 lines covered and 66 lines missed.
    
  1. # frozen_string_literal: true
  2. module ActiveRecord
  3. module ConnectionAdapters
  4. module MySQL
  5. module Quoting # :nodoc:
  6. def quote_column_name(name)
  7. self.class.quoted_column_names[name] ||= "`#{super.gsub('`', '``')}`"
  8. end
  9. def quote_table_name(name)
  10. self.class.quoted_table_names[name] ||= super.gsub(".", "`.`").freeze
  11. end
  12. def unquoted_true
  13. 1
  14. end
  15. def unquoted_false
  16. 0
  17. end
  18. def quoted_date(value)
  19. if supports_datetime_with_precision?
  20. super
  21. else
  22. super.sub(/\.\d{6}\z/, "")
  23. end
  24. end
  25. def quoted_binary(value)
  26. "x'#{value.hex}'"
  27. end
  28. def column_name_matcher
  29. COLUMN_NAME
  30. end
  31. def column_name_with_order_matcher
  32. COLUMN_NAME_WITH_ORDER
  33. end
  34. COLUMN_NAME = /
  35. \A
  36. (
  37. (?:
  38. # `table_name`.`column_name` | function(one or no argument)
  39. ((?:\w+\.|`\w+`\.)?(?:\w+|`\w+`)) | \w+\((?:|\g<2>)\)
  40. )
  41. (?:(?:\s+AS)?\s+(?:\w+|`\w+`))?
  42. )
  43. (?:\s*,\s*\g<1>)*
  44. \z
  45. /ix
  46. COLUMN_NAME_WITH_ORDER = /
  47. \A
  48. (
  49. (?:
  50. # `table_name`.`column_name` | function(one or no argument)
  51. ((?:\w+\.|`\w+`\.)?(?:\w+|`\w+`)) | \w+\((?:|\g<2>)\)
  52. )
  53. (?:\s+ASC|\s+DESC)?
  54. )
  55. (?:\s*,\s*\g<1>)*
  56. \z
  57. /ix
  58. private_constant :COLUMN_NAME, :COLUMN_NAME_WITH_ORDER
  59. private
  60. def _type_cast(value)
  61. case value
  62. when Date, Time then value
  63. else super
  64. end
  65. end
  66. end
  67. end
  68. end
  69. end

lib/active_record/connection_adapters/mysql/schema_creation.rb

0.0% lines covered

75 relevant lines. 0 lines covered and 75 lines missed.
    
  1. # frozen_string_literal: true
  2. module ActiveRecord
  3. module ConnectionAdapters
  4. module MySQL
  5. class SchemaCreation < SchemaCreation # :nodoc:
  6. delegate :add_sql_comment!, :mariadb?, to: :@conn, private: true
  7. private
  8. def visit_DropForeignKey(name)
  9. "DROP FOREIGN KEY #{name}"
  10. end
  11. def visit_DropCheckConstraint(name)
  12. "DROP #{mariadb? ? 'CONSTRAINT' : 'CHECK'} #{name}"
  13. end
  14. def visit_AddColumnDefinition(o)
  15. add_column_position!(super, column_options(o.column))
  16. end
  17. def visit_ChangeColumnDefinition(o)
  18. change_column_sql = +"CHANGE #{quote_column_name(o.name)} #{accept(o.column)}"
  19. add_column_position!(change_column_sql, column_options(o.column))
  20. end
  21. def visit_CreateIndexDefinition(o)
  22. sql = visit_IndexDefinition(o.index, true)
  23. sql << " #{o.algorithm}" if o.algorithm
  24. sql
  25. end
  26. def visit_IndexDefinition(o, create = false)
  27. index_type = o.type&.to_s&.upcase || o.unique && "UNIQUE"
  28. sql = create ? ["CREATE"] : []
  29. sql << index_type if index_type
  30. sql << "INDEX"
  31. sql << quote_column_name(o.name)
  32. sql << "USING #{o.using}" if o.using
  33. sql << "ON #{quote_table_name(o.table)}" if create
  34. sql << "(#{quoted_columns(o)})"
  35. add_sql_comment!(sql.join(" "), o.comment)
  36. end
  37. def add_table_options!(create_sql, o)
  38. create_sql = super
  39. create_sql << " DEFAULT CHARSET=#{o.charset}" if o.charset
  40. create_sql << " COLLATE=#{o.collation}" if o.collation
  41. add_sql_comment!(create_sql, o.comment)
  42. end
  43. def add_column_options!(sql, options)
  44. # By default, TIMESTAMP columns are NOT NULL, cannot contain NULL values,
  45. # and assigning NULL assigns the current timestamp. To permit a TIMESTAMP
  46. # column to contain NULL, explicitly declare it with the NULL attribute.
  47. # See https://dev.mysql.com/doc/refman/en/timestamp-initialization.html
  48. if /\Atimestamp\b/.match?(options[:column].sql_type) && !options[:primary_key]
  49. sql << " NULL" unless options[:null] == false || options_include_default?(options)
  50. end
  51. if charset = options[:charset]
  52. sql << " CHARACTER SET #{charset}"
  53. end
  54. if collation = options[:collation]
  55. sql << " COLLATE #{collation}"
  56. end
  57. if as = options[:as]
  58. sql << " AS (#{as})"
  59. if options[:stored]
  60. sql << (mariadb? ? " PERSISTENT" : " STORED")
  61. end
  62. end
  63. add_sql_comment!(super, options[:comment])
  64. end
  65. def add_column_position!(sql, options)
  66. if options[:first]
  67. sql << " FIRST"
  68. elsif options[:after]
  69. sql << " AFTER #{quote_column_name(options[:after])}"
  70. end
  71. sql
  72. end
  73. def index_in_create(table_name, column_name, options)
  74. index, _ = @conn.add_index_options(table_name, column_name, **options)
  75. accept(index)
  76. end
  77. end
  78. end
  79. end
  80. end

lib/active_record/connection_adapters/mysql/schema_definitions.rb

0.0% lines covered

48 relevant lines. 0 lines covered and 48 lines missed.
    
  1. # frozen_string_literal: true
  2. module ActiveRecord
  3. module ConnectionAdapters
  4. module MySQL
  5. module ColumnMethods
  6. extend ActiveSupport::Concern
  7. ##
  8. # :method: blob
  9. # :call-seq: blob(*names, **options)
  10. ##
  11. # :method: tinyblob
  12. # :call-seq: tinyblob(*names, **options)
  13. ##
  14. # :method: mediumblob
  15. # :call-seq: mediumblob(*names, **options)
  16. ##
  17. # :method: longblob
  18. # :call-seq: longblob(*names, **options)
  19. ##
  20. # :method: tinytext
  21. # :call-seq: tinytext(*names, **options)
  22. ##
  23. # :method: mediumtext
  24. # :call-seq: mediumtext(*names, **options)
  25. ##
  26. # :method: longtext
  27. # :call-seq: longtext(*names, **options)
  28. ##
  29. # :method: unsigned_integer
  30. # :call-seq: unsigned_integer(*names, **options)
  31. ##
  32. # :method: unsigned_bigint
  33. # :call-seq: unsigned_bigint(*names, **options)
  34. ##
  35. # :method: unsigned_float
  36. # :call-seq: unsigned_float(*names, **options)
  37. ##
  38. # :method: unsigned_decimal
  39. # :call-seq: unsigned_decimal(*names, **options)
  40. included do
  41. define_column_methods :blob, :tinyblob, :mediumblob, :longblob,
  42. :tinytext, :mediumtext, :longtext, :unsigned_integer, :unsigned_bigint,
  43. :unsigned_float, :unsigned_decimal
  44. end
  45. end
  46. class TableDefinition < ActiveRecord::ConnectionAdapters::TableDefinition
  47. include ColumnMethods
  48. attr_reader :charset, :collation
  49. def initialize(conn, name, charset: nil, collation: nil, **)
  50. super
  51. @charset = charset
  52. @collation = collation
  53. end
  54. def new_column_definition(name, type, **options) # :nodoc:
  55. case type
  56. when :virtual
  57. type = options[:type]
  58. when :primary_key
  59. type = :integer
  60. options[:limit] ||= 8
  61. options[:primary_key] = true
  62. when /\Aunsigned_(?<type>.+)\z/
  63. type = $~[:type].to_sym
  64. options[:unsigned] = true
  65. end
  66. super
  67. end
  68. private
  69. def aliased_types(name, fallback)
  70. fallback
  71. end
  72. def integer_like_primary_key_type(type, options)
  73. options[:auto_increment] = true
  74. type
  75. end
  76. end
  77. class Table < ActiveRecord::ConnectionAdapters::Table
  78. include ColumnMethods
  79. end
  80. end
  81. end
  82. end

lib/active_record/connection_adapters/mysql/schema_dumper.rb

0.0% lines covered

75 relevant lines. 0 lines covered and 75 lines missed.
    
  1. # frozen_string_literal: true
  2. module ActiveRecord
  3. module ConnectionAdapters
  4. module MySQL
  5. class SchemaDumper < ConnectionAdapters::SchemaDumper # :nodoc:
  6. private
  7. def prepare_column_options(column)
  8. spec = super
  9. spec[:unsigned] = "true" if column.unsigned?
  10. spec[:auto_increment] = "true" if column.auto_increment?
  11. if /\A(?<size>tiny|medium|long)(?:text|blob)/ =~ column.sql_type
  12. spec = { size: size.to_sym.inspect }.merge!(spec)
  13. end
  14. if @connection.supports_virtual_columns? && column.virtual?
  15. spec[:as] = extract_expression_for_virtual_column(column)
  16. spec[:stored] = "true" if /\b(?:STORED|PERSISTENT)\b/.match?(column.extra)
  17. spec = { type: schema_type(column).inspect }.merge!(spec)
  18. end
  19. spec
  20. end
  21. def column_spec_for_primary_key(column)
  22. spec = super
  23. spec.delete(:auto_increment) if column.type == :integer && column.auto_increment?
  24. spec
  25. end
  26. def default_primary_key?(column)
  27. super && column.auto_increment? && !column.unsigned?
  28. end
  29. def explicit_primary_key_default?(column)
  30. column.type == :integer && !column.auto_increment?
  31. end
  32. def schema_type(column)
  33. case column.sql_type
  34. when /\Atimestamp\b/
  35. :timestamp
  36. when /\A(?:enum|set)\b/
  37. column.sql_type
  38. else
  39. super
  40. end
  41. end
  42. def schema_limit(column)
  43. super unless /\A(?:tiny|medium|long)?(?:text|blob)\b/.match?(column.sql_type)
  44. end
  45. def schema_precision(column)
  46. super unless /\A(?:date)?time(?:stamp)?\b/.match?(column.sql_type) && column.precision == 0
  47. end
  48. def schema_collation(column)
  49. if column.collation
  50. @table_collation_cache ||= {}
  51. @table_collation_cache[table_name] ||=
  52. @connection.exec_query("SHOW TABLE STATUS LIKE #{@connection.quote(table_name)}", "SCHEMA").first["Collation"]
  53. column.collation.inspect if column.collation != @table_collation_cache[table_name]
  54. end
  55. end
  56. def extract_expression_for_virtual_column(column)
  57. if @connection.mariadb? && @connection.database_version < "10.2.5"
  58. create_table_info = @connection.send(:create_table_info, table_name)
  59. column_name = @connection.quote_column_name(column.name)
  60. if %r/#{column_name} #{Regexp.quote(column.sql_type)}(?: COLLATE \w+)? AS \((?<expression>.+?)\) #{column.extra}/ =~ create_table_info
  61. $~[:expression].inspect
  62. end
  63. else
  64. scope = @connection.send(:quoted_scope, table_name)
  65. column_name = @connection.quote(column.name)
  66. sql = "SELECT generation_expression FROM information_schema.columns" \
  67. " WHERE table_schema = #{scope[:schema]}" \
  68. " AND table_name = #{scope[:name]}" \
  69. " AND column_name = #{column_name}"
  70. @connection.query_value(sql, "SCHEMA").inspect
  71. end
  72. end
  73. end
  74. end
  75. end
  76. end

lib/active_record/connection_adapters/mysql/schema_statements.rb

0.0% lines covered

227 relevant lines. 0 lines covered and 227 lines missed.
    
  1. # frozen_string_literal: true
  2. module ActiveRecord
  3. module ConnectionAdapters
  4. module MySQL
  5. module SchemaStatements # :nodoc:
  6. # Returns an array of indexes for the given table.
  7. def indexes(table_name)
  8. indexes = []
  9. current_index = nil
  10. execute_and_free("SHOW KEYS FROM #{quote_table_name(table_name)}", "SCHEMA") do |result|
  11. each_hash(result) do |row|
  12. if current_index != row[:Key_name]
  13. next if row[:Key_name] == "PRIMARY" # skip the primary key
  14. current_index = row[:Key_name]
  15. mysql_index_type = row[:Index_type].downcase.to_sym
  16. case mysql_index_type
  17. when :fulltext, :spatial
  18. index_type = mysql_index_type
  19. when :btree, :hash
  20. index_using = mysql_index_type
  21. end
  22. indexes << [
  23. row[:Table],
  24. row[:Key_name],
  25. row[:Non_unique].to_i == 0,
  26. [],
  27. lengths: {},
  28. orders: {},
  29. type: index_type,
  30. using: index_using,
  31. comment: row[:Index_comment].presence
  32. ]
  33. end
  34. if row[:Expression]
  35. expression = row[:Expression]
  36. expression = +"(#{expression})" unless expression.start_with?("(")
  37. indexes.last[-2] << expression
  38. indexes.last[-1][:expressions] ||= {}
  39. indexes.last[-1][:expressions][expression] = expression
  40. indexes.last[-1][:orders][expression] = :desc if row[:Collation] == "D"
  41. else
  42. indexes.last[-2] << row[:Column_name]
  43. indexes.last[-1][:lengths][row[:Column_name]] = row[:Sub_part].to_i if row[:Sub_part]
  44. indexes.last[-1][:orders][row[:Column_name]] = :desc if row[:Collation] == "D"
  45. end
  46. end
  47. end
  48. indexes.map do |index|
  49. options = index.pop
  50. if expressions = options.delete(:expressions)
  51. orders = options.delete(:orders)
  52. lengths = options.delete(:lengths)
  53. columns = index[-1].map { |name|
  54. [ name.to_sym, expressions[name] || +quote_column_name(name) ]
  55. }.to_h
  56. index[-1] = add_options_for_index_columns(
  57. columns, order: orders, length: lengths
  58. ).values.join(", ")
  59. end
  60. IndexDefinition.new(*index, **options)
  61. end
  62. end
  63. def remove_column(table_name, column_name, type = nil, **options)
  64. if foreign_key_exists?(table_name, column: column_name)
  65. remove_foreign_key(table_name, column: column_name)
  66. end
  67. super
  68. end
  69. def create_table(table_name, options: default_row_format, **)
  70. super
  71. end
  72. def internal_string_options_for_primary_key
  73. super.tap do |options|
  74. if !row_format_dynamic_by_default? && CHARSETS_OF_4BYTES_MAXLEN.include?(charset)
  75. options[:collation] = collation.sub(/\A[^_]+/, "utf8")
  76. end
  77. end
  78. end
  79. def update_table_definition(table_name, base)
  80. MySQL::Table.new(table_name, base)
  81. end
  82. def create_schema_dumper(options)
  83. MySQL::SchemaDumper.create(self, options)
  84. end
  85. # Maps logical Rails types to MySQL-specific data types.
  86. def type_to_sql(type, limit: nil, precision: nil, scale: nil, size: limit_to_size(limit, type), unsigned: nil, **)
  87. sql =
  88. case type.to_s
  89. when "integer"
  90. integer_to_sql(limit)
  91. when "text"
  92. type_with_size_to_sql("text", size)
  93. when "blob"
  94. type_with_size_to_sql("blob", size)
  95. when "binary"
  96. if (0..0xfff) === limit
  97. "varbinary(#{limit})"
  98. else
  99. type_with_size_to_sql("blob", size)
  100. end
  101. else
  102. super
  103. end
  104. sql = "#{sql} unsigned" if unsigned && type != :primary_key
  105. sql
  106. end
  107. def table_alias_length
  108. 256 # https://dev.mysql.com/doc/refman/en/identifiers.html
  109. end
  110. private
  111. CHARSETS_OF_4BYTES_MAXLEN = ["utf8mb4", "utf16", "utf16le", "utf32"]
  112. def row_format_dynamic_by_default?
  113. if mariadb?
  114. database_version >= "10.2.2"
  115. else
  116. database_version >= "5.7.9"
  117. end
  118. end
  119. def default_row_format
  120. return if row_format_dynamic_by_default?
  121. unless defined?(@default_row_format)
  122. if query_value("SELECT @@innodb_file_per_table = 1 AND @@innodb_file_format = 'Barracuda'") == 1
  123. @default_row_format = "ROW_FORMAT=DYNAMIC"
  124. else
  125. @default_row_format = nil
  126. end
  127. end
  128. @default_row_format
  129. end
  130. def schema_creation
  131. MySQL::SchemaCreation.new(self)
  132. end
  133. def create_table_definition(name, **options)
  134. MySQL::TableDefinition.new(self, name, **options)
  135. end
  136. def new_column_from_field(table_name, field)
  137. type_metadata = fetch_type_metadata(field[:Type], field[:Extra])
  138. default, default_function = field[:Default], nil
  139. if type_metadata.type == :datetime && /\ACURRENT_TIMESTAMP(?:\([0-6]?\))?\z/i.match?(default)
  140. default, default_function = nil, default
  141. elsif type_metadata.extra == "DEFAULT_GENERATED"
  142. default = +"(#{default})" unless default.start_with?("(")
  143. default, default_function = nil, default
  144. end
  145. MySQL::Column.new(
  146. field[:Field],
  147. default,
  148. type_metadata,
  149. field[:Null] == "YES",
  150. default_function,
  151. collation: field[:Collation],
  152. comment: field[:Comment].presence
  153. )
  154. end
  155. def fetch_type_metadata(sql_type, extra = "")
  156. MySQL::TypeMetadata.new(super(sql_type), extra: extra)
  157. end
  158. def extract_foreign_key_action(specifier)
  159. super unless specifier == "RESTRICT"
  160. end
  161. def add_index_length(quoted_columns, **options)
  162. lengths = options_for_index_columns(options[:length])
  163. quoted_columns.each do |name, column|
  164. column << "(#{lengths[name]})" if lengths[name].present?
  165. end
  166. end
  167. def add_options_for_index_columns(quoted_columns, **options)
  168. quoted_columns = add_index_length(quoted_columns, **options)
  169. super
  170. end
  171. def data_source_sql(name = nil, type: nil)
  172. scope = quoted_scope(name, type: type)
  173. sql = +"SELECT table_name FROM (SELECT * FROM information_schema.tables "
  174. sql << " WHERE table_schema = #{scope[:schema]}) _subquery"
  175. if scope[:type] || scope[:name]
  176. conditions = []
  177. conditions << "_subquery.table_type = #{scope[:type]}" if scope[:type]
  178. conditions << "_subquery.table_name = #{scope[:name]}" if scope[:name]
  179. sql << " WHERE #{conditions.join(" AND ")}"
  180. end
  181. sql
  182. end
  183. def quoted_scope(name = nil, type: nil)
  184. schema, name = extract_schema_qualified_name(name)
  185. scope = {}
  186. scope[:schema] = schema ? quote(schema) : "database()"
  187. scope[:name] = quote(name) if name
  188. scope[:type] = quote(type) if type
  189. scope
  190. end
  191. def extract_schema_qualified_name(string)
  192. schema, name = string.to_s.scan(/[^`.\s]+|`[^`]*`/)
  193. schema, name = nil, schema unless name
  194. [schema, name]
  195. end
  196. def type_with_size_to_sql(type, size)
  197. case size&.to_s
  198. when nil, "tiny", "medium", "long"
  199. "#{size}#{type}"
  200. else
  201. raise ArgumentError,
  202. "#{size.inspect} is invalid :size value. Only :tiny, :medium, and :long are allowed."
  203. end
  204. end
  205. def limit_to_size(limit, type)
  206. case type.to_s
  207. when "text", "blob", "binary"
  208. case limit
  209. when 0..0xff; "tiny"
  210. when nil, 0x100..0xffff; nil
  211. when 0x10000..0xffffff; "medium"
  212. when 0x1000000..0xffffffff; "long"
  213. else raise ArgumentError, "No #{type} type has byte size #{limit}"
  214. end
  215. end
  216. end
  217. def integer_to_sql(limit)
  218. case limit
  219. when 1; "tinyint"
  220. when 2; "smallint"
  221. when 3; "mediumint"
  222. when nil, 4; "int"
  223. when 5..8; "bigint"
  224. else raise ArgumentError, "No integer type has byte size #{limit}. Use a decimal with scale 0 instead."
  225. end
  226. end
  227. end
  228. end
  229. end
  230. end

lib/active_record/connection_adapters/mysql/type_metadata.rb

0.0% lines covered

32 relevant lines. 0 lines covered and 32 lines missed.
    
  1. # frozen_string_literal: true
  2. module ActiveRecord
  3. module ConnectionAdapters
  4. module MySQL
  5. class TypeMetadata < DelegateClass(SqlTypeMetadata) # :nodoc:
  6. undef to_yaml if method_defined?(:to_yaml)
  7. include Deduplicable
  8. attr_reader :extra
  9. def initialize(type_metadata, extra: nil)
  10. super(type_metadata)
  11. @extra = extra
  12. end
  13. def ==(other)
  14. other.is_a?(TypeMetadata) &&
  15. __getobj__ == other.__getobj__ &&
  16. extra == other.extra
  17. end
  18. alias eql? ==
  19. def hash
  20. TypeMetadata.hash ^
  21. __getobj__.hash ^
  22. extra.hash
  23. end
  24. private
  25. def deduplicated
  26. __setobj__(__getobj__.deduplicate)
  27. @extra = -extra if extra
  28. super
  29. end
  30. end
  31. end
  32. end
  33. end

lib/active_record/connection_adapters/mysql2_adapter.rb

0.0% lines covered

112 relevant lines. 0 lines covered and 112 lines missed.
    
  1. # frozen_string_literal: true
  2. require "active_record/connection_adapters/abstract_mysql_adapter"
  3. require "active_record/connection_adapters/mysql/database_statements"
  4. gem "mysql2", "~> 0.5"
  5. require "mysql2"
  6. module ActiveRecord
  7. module ConnectionHandling # :nodoc:
  8. ER_BAD_DB_ERROR = 1049
  9. # Establishes a connection to the database that's used by all Active Record objects.
  10. def mysql2_connection(config)
  11. config = config.symbolize_keys
  12. config[:flags] ||= 0
  13. if config[:flags].kind_of? Array
  14. config[:flags].push "FOUND_ROWS"
  15. else
  16. config[:flags] |= Mysql2::Client::FOUND_ROWS
  17. end
  18. client = Mysql2::Client.new(config)
  19. ConnectionAdapters::Mysql2Adapter.new(client, logger, nil, config)
  20. rescue Mysql2::Error => error
  21. if error.error_number == ER_BAD_DB_ERROR
  22. raise ActiveRecord::NoDatabaseError
  23. else
  24. raise
  25. end
  26. end
  27. end
  28. module ConnectionAdapters
  29. class Mysql2Adapter < AbstractMysqlAdapter
  30. ADAPTER_NAME = "Mysql2"
  31. include MySQL::DatabaseStatements
  32. def initialize(connection, logger, connection_options, config)
  33. superclass_config = config.reverse_merge(prepared_statements: false)
  34. super(connection, logger, connection_options, superclass_config)
  35. configure_connection
  36. end
  37. def self.database_exists?(config)
  38. !!ActiveRecord::Base.mysql2_connection(config)
  39. rescue ActiveRecord::NoDatabaseError
  40. false
  41. end
  42. def supports_json?
  43. !mariadb? && database_version >= "5.7.8"
  44. end
  45. def supports_comments?
  46. true
  47. end
  48. def supports_comments_in_create?
  49. true
  50. end
  51. def supports_savepoints?
  52. true
  53. end
  54. def supports_lazy_transactions?
  55. true
  56. end
  57. # HELPER METHODS ===========================================
  58. def each_hash(result) # :nodoc:
  59. if block_given?
  60. result.each(as: :hash, symbolize_keys: true) do |row|
  61. yield row
  62. end
  63. else
  64. to_enum(:each_hash, result)
  65. end
  66. end
  67. def error_number(exception)
  68. exception.error_number if exception.respond_to?(:error_number)
  69. end
  70. #--
  71. # QUOTING ==================================================
  72. #++
  73. def quote_string(string)
  74. @connection.escape(string)
  75. end
  76. #--
  77. # CONNECTION MANAGEMENT ====================================
  78. #++
  79. def active?
  80. @connection.ping
  81. end
  82. def reconnect!
  83. super
  84. disconnect!
  85. connect
  86. end
  87. alias :reset! :reconnect!
  88. # Disconnects from the database if already connected.
  89. # Otherwise, this method does nothing.
  90. def disconnect!
  91. super
  92. @connection.close
  93. end
  94. def discard! # :nodoc:
  95. super
  96. @connection.automatic_close = false
  97. @connection = nil
  98. end
  99. private
  100. def connect
  101. @connection = Mysql2::Client.new(@config)
  102. configure_connection
  103. end
  104. def configure_connection
  105. @connection.query_options[:as] = :array
  106. super
  107. end
  108. def full_version
  109. schema_cache.database_version.full_version_string
  110. end
  111. def get_full_version
  112. @connection.server_info[:version]
  113. end
  114. def translate_exception(exception, message:, sql:, binds:)
  115. if exception.is_a?(Mysql2::Error::TimeoutError) && !exception.error_number
  116. ActiveRecord::AdapterTimeout.new(message, sql: sql, binds: binds)
  117. else
  118. super
  119. end
  120. end
  121. end
  122. end
  123. end

lib/active_record/connection_adapters/pool_config.rb

100.0% lines covered

34 relevant lines. 34 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 module ActiveRecord
  3. 3 module ConnectionAdapters
  4. 3 class PoolConfig # :nodoc:
  5. 3 include Mutex_m
  6. 3 attr_reader :db_config, :connection_specification_name
  7. 3 attr_accessor :schema_cache
  8. 3 INSTANCES = ObjectSpace::WeakMap.new
  9. 3 private_constant :INSTANCES
  10. 3 class << self
  11. 3 def discard_pools!
  12. 9 INSTANCES.each_key(&:discard_pool!)
  13. end
  14. end
  15. 3 def initialize(connection_specification_name, db_config)
  16. 897 super()
  17. 897 @connection_specification_name = connection_specification_name
  18. 897 @db_config = db_config
  19. 897 @pool = nil
  20. 897 INSTANCES[self] = self
  21. end
  22. 3 def disconnect!
  23. 283 ActiveSupport::ForkTracker.check!
  24. 283 return unless @pool
  25. 283 synchronize do
  26. 283 return unless @pool
  27. 283 @pool.automatic_reconnect = false
  28. 283 @pool.disconnect!
  29. end
  30. nil
  31. end
  32. 3 def pool
  33. 1480231 ActiveSupport::ForkTracker.check!
  34. 1481050 @pool || synchronize { @pool ||= ConnectionAdapters::ConnectionPool.new(self) }
  35. end
  36. 3 def discard_pool!
  37. 129 return unless @pool
  38. 88 synchronize do
  39. 88 return unless @pool
  40. 88 @pool.discard!
  41. 88 @pool = nil
  42. end
  43. end
  44. end
  45. end
  46. end
  47. 3 ActiveSupport::ForkTracker.after_fork { ActiveRecord::ConnectionAdapters::PoolConfig.discard_pools! }

lib/active_record/connection_adapters/pool_manager.rb

100.0% lines covered

13 relevant lines. 13 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 module ActiveRecord
  3. 3 module ConnectionAdapters
  4. 3 class PoolManager # :nodoc:
  5. 3 def initialize
  6. 464 @name_to_pool_config = {}
  7. end
  8. 3 def pool_configs
  9. 1187756 @name_to_pool_config.values
  10. end
  11. 3 def remove_pool_config(key)
  12. 393 @name_to_pool_config.delete(key)
  13. end
  14. 3 def get_pool_config(key)
  15. 269778 @name_to_pool_config[key]
  16. end
  17. 3 def set_pool_config(key, pool_config)
  18. 762 @name_to_pool_config[key] = pool_config
  19. end
  20. end
  21. end
  22. end

lib/active_record/connection_adapters/postgresql/column.rb

100.0% lines covered

27 relevant lines. 27 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 2 module ActiveRecord
  3. 2 module ConnectionAdapters
  4. 2 module PostgreSQL
  5. 2 class Column < ConnectionAdapters::Column # :nodoc:
  6. 2 delegate :oid, :fmod, to: :sql_type_metadata
  7. 2 def initialize(*, serial: nil, **)
  8. 20923 super
  9. 20923 @serial = serial
  10. end
  11. 2 def serial?
  12. 99581 @serial
  13. end
  14. 2 def array
  15. 8040 sql_type_metadata.sql_type.end_with?("[]")
  16. end
  17. 2 alias :array? :array
  18. 2 def sql_type
  19. 363969 super.delete_suffix("[]")
  20. end
  21. 2 def init_with(coder)
  22. 1707 @serial = coder["serial"]
  23. 1707 super
  24. end
  25. 2 def encode_with(coder)
  26. 1706 coder["serial"] = @serial
  27. 1706 super
  28. end
  29. 2 def ==(other)
  30. 24321 other.is_a?(Column) &&
  31. super &&
  32. serial? == other.serial?
  33. end
  34. 2 alias :eql? :==
  35. 2 def hash
  36. Column.hash ^
  37. 39072 super.hash ^
  38. serial?.hash
  39. end
  40. end
  41. end
  42. 2 PostgreSQLColumn = PostgreSQL::Column # :nodoc:
  43. end
  44. end

lib/active_record/connection_adapters/postgresql/database_statements.rb

100.0% lines covered

76 relevant lines. 76 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 2 module ActiveRecord
  3. 2 module ConnectionAdapters
  4. 2 module PostgreSQL
  5. 2 module DatabaseStatements
  6. 2 def explain(arel, binds = [])
  7. 5 sql = "EXPLAIN #{to_sql(arel, binds)}"
  8. 5 PostgreSQL::ExplainPrettyPrinter.new.pp(exec_query(sql, "EXPLAIN", binds))
  9. end
  10. # Queries the database and returns the results in an Array-like object
  11. 2 def query(sql, name = nil) #:nodoc:
  12. 25860 materialize_transactions
  13. 25860 mark_transaction_written_if_write(sql)
  14. 25860 log(sql, name) do
  15. 25860 ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
  16. 25860 @connection.async_exec(sql).map_types!(@type_map_for_results).values
  17. end
  18. end
  19. end
  20. 2 READ_QUERY = ActiveRecord::ConnectionAdapters::AbstractAdapter.build_read_query_regexp(
  21. :close, :declare, :fetch, :move, :set, :show
  22. ) # :nodoc:
  23. 2 private_constant :READ_QUERY
  24. 2 def write_query?(sql) # :nodoc:
  25. 33791 !READ_QUERY.match?(sql)
  26. end
  27. # Executes an SQL statement, returning a PG::Result object on success
  28. # or raising a PG::Error exception otherwise.
  29. # Note: the PG::Result object is manually memory managed; if you don't
  30. # need it specifically, you may want consider the <tt>exec_query</tt> wrapper.
  31. 2 def execute(sql, name = nil)
  32. 73301 if preventing_writes? && write_query?(sql)
  33. 3 raise ActiveRecord::ReadOnlyError, "Write query attempted while in readonly mode: #{sql}"
  34. end
  35. 73298 materialize_transactions
  36. 73298 mark_transaction_written_if_write(sql)
  37. 73298 log(sql, name) do
  38. 73298 ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
  39. 73298 @connection.async_exec(sql)
  40. end
  41. end
  42. end
  43. 2 def exec_query(sql, name = "SQL", binds = [], prepare: false)
  44. 22554 execute_and_clear(sql, name, binds, prepare: prepare) do |result|
  45. 22504 types = {}
  46. 22504 fields = result.fields
  47. 22504 fields.each_with_index do |fname, i|
  48. 129242 ftype = result.ftype i
  49. 129242 fmod = result.fmod i
  50. 129242 case type = get_oid_type(ftype, fmod, fname)
  51. when Type::Integer, Type::Float, Type::Decimal, Type::String, Type::DateTime, Type::Boolean
  52. # skip if a column has already been type casted by pg decoders
  53. 5915 else types[fname] = type
  54. end
  55. end
  56. 22504 build_result(columns: fields, rows: result.values, column_types: types)
  57. end
  58. end
  59. 2 def exec_delete(sql, name = nil, binds = [])
  60. 5381 execute_and_clear(sql, name, binds) { |result| result.cmd_tuples }
  61. end
  62. 2 alias :exec_update :exec_delete
  63. 2 def sql_for_insert(sql, pk, binds) # :nodoc:
  64. 4340 if pk.nil?
  65. # Extract the table from the insert sql. Yuck.
  66. 14 table_ref = extract_table_ref_from_insert_sql(sql)
  67. 14 pk = primary_key(table_ref) if table_ref
  68. end
  69. 4340 if pk = suppress_composite_primary_key(pk)
  70. 4067 sql = "#{sql} RETURNING #{quote_column_name(pk)}"
  71. end
  72. 4340 super
  73. end
  74. 2 private :sql_for_insert
  75. 2 def exec_insert(sql, name = nil, binds = [], pk = nil, sequence_name = nil)
  76. 4344 if use_insert_returning? || pk == false
  77. 4340 super
  78. else
  79. 4 result = exec_query(sql, name, binds)
  80. 4 unless sequence_name
  81. 3 table_ref = extract_table_ref_from_insert_sql(sql)
  82. 3 if table_ref
  83. 3 pk = primary_key(table_ref) if pk.nil?
  84. 3 pk = suppress_composite_primary_key(pk)
  85. 3 sequence_name = default_sequence_name(table_ref, pk)
  86. end
  87. 3 return result unless sequence_name
  88. end
  89. 4 last_insert_id_result(sequence_name)
  90. end
  91. end
  92. # Begins a transaction.
  93. 2 def begin_db_transaction
  94. 26653 execute("BEGIN", "TRANSACTION")
  95. end
  96. 2 def begin_isolated_db_transaction(isolation)
  97. 6 begin_db_transaction
  98. 6 execute "SET TRANSACTION ISOLATION LEVEL #{transaction_isolation_levels.fetch(isolation)}"
  99. end
  100. # Commits a transaction.
  101. 2 def commit_db_transaction
  102. 2788 execute("COMMIT", "TRANSACTION")
  103. end
  104. # Aborts a transaction.
  105. 2 def exec_rollback_db_transaction
  106. 23805 execute("ROLLBACK", "TRANSACTION")
  107. end
  108. 2 private
  109. 2 def execute_batch(statements, name = nil)
  110. 607 execute(combine_multi_statements(statements))
  111. end
  112. 2 def build_truncate_statements(table_names)
  113. 5 ["TRUNCATE TABLE #{table_names.map(&method(:quote_table_name)).join(", ")}"]
  114. end
  115. # Returns the current ID of a table's sequence.
  116. 2 def last_insert_id_result(sequence_name)
  117. 4 exec_query("SELECT currval(#{quote(sequence_name)})", "SQL")
  118. end
  119. 2 def suppress_composite_primary_key(pk)
  120. 4343 pk unless pk.is_a?(Array)
  121. end
  122. end
  123. end
  124. end
  125. end

lib/active_record/connection_adapters/postgresql/explain_pretty_printer.rb

100.0% lines covered

16 relevant lines. 16 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 2 module ActiveRecord
  3. 2 module ConnectionAdapters
  4. 2 module PostgreSQL
  5. 2 class ExplainPrettyPrinter # :nodoc:
  6. # Pretty prints the result of an EXPLAIN in a way that resembles the output of the
  7. # PostgreSQL shell:
  8. #
  9. # QUERY PLAN
  10. # ------------------------------------------------------------------------------
  11. # Nested Loop Left Join (cost=0.00..37.24 rows=8 width=0)
  12. # Join Filter: (posts.user_id = users.id)
  13. # -> Index Scan using users_pkey on users (cost=0.00..8.27 rows=1 width=4)
  14. # Index Cond: (id = 1)
  15. # -> Seq Scan on posts (cost=0.00..28.88 rows=8 width=4)
  16. # Filter: (posts.user_id = 1)
  17. # (6 rows)
  18. #
  19. 2 def pp(result)
  20. 5 header = result.columns.first
  21. 5 lines = result.rows.map(&:first)
  22. # We add 2 because there's one char of padding at both sides, note
  23. # the extra hyphens in the example above.
  24. 5 width = [header, *lines].map(&:length).max + 2
  25. 5 pp = []
  26. 5 pp << header.center(width).rstrip
  27. 5 pp << "-" * width
  28. 15 pp += lines.map { |line| " #{line}" }
  29. 5 nrows = result.rows.length
  30. 5 rows_label = nrows == 1 ? "row" : "rows"
  31. 5 pp << "(#{nrows} #{rows_label})"
  32. 5 pp.join("\n") + "\n"
  33. end
  34. end
  35. end
  36. end
  37. end

lib/active_record/connection_adapters/postgresql/oid.rb

100.0% lines covered

27 relevant lines. 27 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 2 require "active_record/connection_adapters/postgresql/oid/array"
  3. 2 require "active_record/connection_adapters/postgresql/oid/bit"
  4. 2 require "active_record/connection_adapters/postgresql/oid/bit_varying"
  5. 2 require "active_record/connection_adapters/postgresql/oid/bytea"
  6. 2 require "active_record/connection_adapters/postgresql/oid/cidr"
  7. 2 require "active_record/connection_adapters/postgresql/oid/date"
  8. 2 require "active_record/connection_adapters/postgresql/oid/date_time"
  9. 2 require "active_record/connection_adapters/postgresql/oid/decimal"
  10. 2 require "active_record/connection_adapters/postgresql/oid/enum"
  11. 2 require "active_record/connection_adapters/postgresql/oid/hstore"
  12. 2 require "active_record/connection_adapters/postgresql/oid/inet"
  13. 2 require "active_record/connection_adapters/postgresql/oid/jsonb"
  14. 2 require "active_record/connection_adapters/postgresql/oid/macaddr"
  15. 2 require "active_record/connection_adapters/postgresql/oid/money"
  16. 2 require "active_record/connection_adapters/postgresql/oid/oid"
  17. 2 require "active_record/connection_adapters/postgresql/oid/point"
  18. 2 require "active_record/connection_adapters/postgresql/oid/legacy_point"
  19. 2 require "active_record/connection_adapters/postgresql/oid/range"
  20. 2 require "active_record/connection_adapters/postgresql/oid/specialized_string"
  21. 2 require "active_record/connection_adapters/postgresql/oid/uuid"
  22. 2 require "active_record/connection_adapters/postgresql/oid/vector"
  23. 2 require "active_record/connection_adapters/postgresql/oid/xml"
  24. 2 require "active_record/connection_adapters/postgresql/oid/type_map_initializer"
  25. 2 module ActiveRecord
  26. 2 module ConnectionAdapters
  27. 2 module PostgreSQL
  28. 2 module OID # :nodoc:
  29. end
  30. end
  31. end
  32. end

lib/active_record/connection_adapters/postgresql/oid/array.rb

97.83% lines covered

46 relevant lines. 45 lines covered and 1 lines missed.
    
  1. # frozen_string_literal: true
  2. 2 module ActiveRecord
  3. 2 module ConnectionAdapters
  4. 2 module PostgreSQL
  5. 2 module OID # :nodoc:
  6. 2 class Array < Type::Value # :nodoc:
  7. 2 include ActiveModel::Type::Helpers::Mutable
  8. 2 Data = Struct.new(:encoder, :values) # :nodoc:
  9. 2 attr_reader :subtype, :delimiter
  10. 2 delegate :type, :user_input_in_time_zone, :limit, :precision, :scale, to: :subtype
  11. 2 def initialize(subtype, delimiter = ",")
  12. 916 @subtype = subtype
  13. 916 @delimiter = delimiter
  14. 916 @pg_encoder = PG::TextEncoder::Array.new name: "#{type}[]", delimiter: delimiter
  15. 916 @pg_decoder = PG::TextDecoder::Array.new name: "#{type}[]", delimiter: delimiter
  16. end
  17. 2 def deserialize(value)
  18. 551 case value
  19. when ::String
  20. 168 type_cast_array(@pg_decoder.decode(value), :deserialize)
  21. when Data
  22. 72 type_cast_array(value.values, :deserialize)
  23. else
  24. 311 super
  25. end
  26. end
  27. 2 def cast(value)
  28. 380 if value.is_a?(::String)
  29. 3 value = begin
  30. 3 @pg_decoder.decode(value)
  31. rescue TypeError
  32. # malformed array string is treated as [], will raise in PG 2.0 gem
  33. # this keeps a consistent implementation
  34. []
  35. end
  36. end
  37. 380 type_cast_array(value, :cast)
  38. end
  39. 2 def serialize(value)
  40. 569 if value.is_a?(::Array)
  41. 338 casted_values = type_cast_array(value, :serialize)
  42. 337 Data.new(@pg_encoder, casted_values)
  43. else
  44. 231 super
  45. end
  46. end
  47. 2 def ==(other)
  48. 3 other.is_a?(Array) &&
  49. subtype == other.subtype &&
  50. delimiter == other.delimiter
  51. end
  52. 2 def type_cast_for_schema(value)
  53. 15 return super unless value.is_a?(::Array)
  54. 39 "[" + value.map { |v| subtype.type_cast_for_schema(v) }.join(", ") + "]"
  55. end
  56. 2 def map(value, &block)
  57. 3 value.map(&block)
  58. end
  59. 2 def changed_in_place?(raw_old_value, new_value)
  60. 6 deserialize(raw_old_value) != new_value
  61. end
  62. 2 def force_equality?(value)
  63. 1 value.is_a?(::Array)
  64. end
  65. 2 private
  66. 2 def type_cast_array(value, method)
  67. 1788 if value.is_a?(::Array)
  68. 1638 value.map { |item| type_cast_array(item, method) }
  69. else
  70. 980 @subtype.public_send(method, value)
  71. end
  72. end
  73. end
  74. end
  75. end
  76. end
  77. end

lib/active_record/connection_adapters/postgresql/oid/bit.rb

100.0% lines covered

26 relevant lines. 26 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 2 module ActiveRecord
  3. 2 module ConnectionAdapters
  4. 2 module PostgreSQL
  5. 2 module OID # :nodoc:
  6. 2 class Bit < Type::Value # :nodoc:
  7. 2 def type
  8. 4 :bit
  9. end
  10. 2 def cast_value(value)
  11. 25 if ::String === value
  12. 20 case value
  13. when /^0x/i
  14. 1 value[2..-1].hex.to_s(2) # Hexadecimal notation
  15. else
  16. 19 value # Bit-string notation
  17. end
  18. else
  19. 5 value.to_s
  20. end
  21. end
  22. 2 def serialize(value)
  23. 27 Data.new(super) if value
  24. end
  25. 2 class Data
  26. 2 def initialize(value)
  27. 23 @value = value
  28. end
  29. 2 def to_s
  30. 23 value
  31. end
  32. 2 def binary?
  33. 13 /\A[01]*\Z/.match?(value)
  34. end
  35. 2 def hex?
  36. 1 /\A[0-9A-F]*\Z/i.match?(value)
  37. end
  38. 2 private
  39. 2 attr_reader :value
  40. end
  41. end
  42. end
  43. end
  44. end
  45. end

lib/active_record/connection_adapters/postgresql/oid/bit_varying.rb

100.0% lines covered

7 relevant lines. 7 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 2 module ActiveRecord
  3. 2 module ConnectionAdapters
  4. 2 module PostgreSQL
  5. 2 module OID # :nodoc:
  6. 2 class BitVarying < OID::Bit # :nodoc:
  7. 2 def type
  8. 4 :bit_varying
  9. end
  10. end
  11. end
  12. end
  13. end
  14. end

lib/active_record/connection_adapters/postgresql/oid/bytea.rb

100.0% lines covered

9 relevant lines. 9 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 2 module ActiveRecord
  3. 2 module ConnectionAdapters
  4. 2 module PostgreSQL
  5. 2 module OID # :nodoc:
  6. 2 class Bytea < Type::Binary # :nodoc:
  7. 2 def deserialize(value)
  8. 55 return if value.nil?
  9. 24 return value.to_s if value.is_a?(Type::Binary::Data)
  10. 13 PG::Connection.unescape_bytea(super)
  11. end
  12. end
  13. end
  14. end
  15. end
  16. end

lib/active_record/connection_adapters/postgresql/oid/cidr.rb

96.0% lines covered

25 relevant lines. 24 lines covered and 1 lines missed.
    
  1. # frozen_string_literal: true
  2. 2 require "ipaddr"
  3. 2 module ActiveRecord
  4. 2 module ConnectionAdapters
  5. 2 module PostgreSQL
  6. 2 module OID # :nodoc:
  7. 2 class Cidr < Type::Value # :nodoc:
  8. 2 def type
  9. 2 :cidr
  10. end
  11. 2 def type_cast_for_schema(value)
  12. 2 subnet_mask = value.instance_variable_get(:@mask_addr)
  13. # If the subnet mask is equal to /32, don't output it
  14. 2 if subnet_mask == (2**32 - 1)
  15. 1 "\"#{value}\""
  16. else
  17. 1 "\"#{value}/#{subnet_mask.to_s(2).count('1')}\""
  18. end
  19. end
  20. 2 def serialize(value)
  21. 31 if IPAddr === value
  22. 12 "#{value}/#{value.instance_variable_get(:@mask_addr).to_s(2).count('1')}"
  23. else
  24. 19 value
  25. end
  26. end
  27. 2 def cast_value(value)
  28. 21 if value.nil?
  29. nil
  30. 21 elsif String === value
  31. 21 begin
  32. 21 IPAddr.new(value)
  33. 2 rescue ArgumentError
  34. 2 nil
  35. end
  36. else
  37. value
  38. end
  39. end
  40. end
  41. end
  42. end
  43. end
  44. end

lib/active_record/connection_adapters/postgresql/oid/date.rb

100.0% lines covered

12 relevant lines. 12 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 2 module ActiveRecord
  3. 2 module ConnectionAdapters
  4. 2 module PostgreSQL
  5. 2 module OID # :nodoc:
  6. 2 class Date < Type::Date # :nodoc:
  7. 2 def cast_value(value)
  8. 1271 case value
  9. 5 when "infinity" then ::Float::INFINITY
  10. 2 when "-infinity" then -::Float::INFINITY
  11. when / BC$/
  12. 6 value = value.sub(/^\d+/) { |year| format("%04d", -year.to_i + 1) }
  13. 3 super(value.delete_suffix!(" BC"))
  14. else
  15. 1261 super
  16. end
  17. end
  18. end
  19. end
  20. end
  21. end
  22. end

lib/active_record/connection_adapters/postgresql/oid/date_time.rb

100.0% lines covered

12 relevant lines. 12 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 2 module ActiveRecord
  3. 2 module ConnectionAdapters
  4. 2 module PostgreSQL
  5. 2 module OID # :nodoc:
  6. 2 class DateTime < Type::DateTime # :nodoc:
  7. 2 def cast_value(value)
  8. 36440 case value
  9. 8 when "infinity" then ::Float::INFINITY
  10. 2 when "-infinity" then -::Float::INFINITY
  11. when / BC$/
  12. 4 value = value.sub(/^\d+/) { |year| format("%04d", -year.to_i + 1) }
  13. 2 super(value.delete_suffix!(" BC"))
  14. else
  15. 36428 super
  16. end
  17. end
  18. end
  19. end
  20. end
  21. end
  22. end

lib/active_record/connection_adapters/postgresql/oid/decimal.rb

100.0% lines covered

7 relevant lines. 7 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 2 module ActiveRecord
  3. 2 module ConnectionAdapters
  4. 2 module PostgreSQL
  5. 2 module OID # :nodoc:
  6. 2 class Decimal < Type::Decimal # :nodoc:
  7. 2 def infinity(options = {})
  8. 3 BigDecimal("Infinity") * (options[:negative] ? -1 : 1)
  9. end
  10. end
  11. end
  12. end
  13. end
  14. end

lib/active_record/connection_adapters/postgresql/oid/enum.rb

100.0% lines covered

10 relevant lines. 10 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 2 module ActiveRecord
  3. 2 module ConnectionAdapters
  4. 2 module PostgreSQL
  5. 2 module OID # :nodoc:
  6. 2 class Enum < Type::Value # :nodoc:
  7. 2 def type
  8. 4 :enum
  9. end
  10. 2 private
  11. 2 def cast_value(value)
  12. 11 value.to_s
  13. end
  14. end
  15. end
  16. end
  17. end
  18. end

lib/active_record/connection_adapters/postgresql/oid/hstore.rb

100.0% lines covered

36 relevant lines. 36 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 2 module ActiveRecord
  3. 2 module ConnectionAdapters
  4. 2 module PostgreSQL
  5. 2 module OID # :nodoc:
  6. 2 class Hstore < Type::Value # :nodoc:
  7. 2 include ActiveModel::Type::Helpers::Mutable
  8. 2 def type
  9. 677 :hstore
  10. end
  11. 2 def deserialize(value)
  12. 393 if value.is_a?(::String)
  13. 222 ::Hash[value.scan(HstorePair).map { |k, v|
  14. 188 v = v.upcase == "NULL" ? nil : v.gsub(/\A"(.*)"\Z/m, '\1').gsub(/\\(.)/, '\1')
  15. 188 k = k.gsub(/\A"(.*)"\Z/m, '\1').gsub(/\\(.)/, '\1')
  16. 188 [k, v]
  17. }]
  18. else
  19. 171 value
  20. end
  21. end
  22. 2 def serialize(value)
  23. 404 if value.is_a?(::Hash)
  24. 410 value.map { |k, v| "#{escape_hstore(k)}=>#{escape_hstore(v)}" }.join(", ")
  25. 209 elsif value.respond_to?(:to_unsafe_h)
  26. 1 serialize(value.to_unsafe_h)
  27. else
  28. 208 value
  29. end
  30. end
  31. 2 def accessor
  32. 22 ActiveRecord::Store::StringKeyedHashAccessor
  33. end
  34. # Will compare the Hash equivalents of +raw_old_value+ and +new_value+.
  35. # By comparing hashes, this avoids an edge case where the order of
  36. # the keys change between the two hashes, and they would not be marked
  37. # as equal.
  38. 2 def changed_in_place?(raw_old_value, new_value)
  39. 15 deserialize(raw_old_value) != new_value
  40. end
  41. 2 private
  42. 2 HstorePair = begin
  43. 2 quoted_string = /"[^"\\]*(?:\\.[^"\\]*)*"/
  44. 2 unquoted_string = /(?:\\.|[^\s,])[^\s=,\\]*(?:\\.[^\s=,\\]*|=[^,>])*/
  45. 2 /(#{quoted_string}|#{unquoted_string})\s*=>\s*(#{quoted_string}|#{unquoted_string})/
  46. end
  47. 2 def escape_hstore(value)
  48. 430 if value.nil?
  49. 18 "NULL"
  50. else
  51. 412 if value == ""
  52. 4 '""'
  53. else
  54. 408 '"%s"' % value.to_s.gsub(/(["\\])/, '\\\\\1')
  55. end
  56. end
  57. end
  58. end
  59. end
  60. end
  61. end
  62. end

lib/active_record/connection_adapters/postgresql/oid/inet.rb

100.0% lines covered

7 relevant lines. 7 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 2 module ActiveRecord
  3. 2 module ConnectionAdapters
  4. 2 module PostgreSQL
  5. 2 module OID # :nodoc:
  6. 2 class Inet < Cidr # :nodoc:
  7. 2 def type
  8. 2 :inet
  9. end
  10. end
  11. end
  12. end
  13. end
  14. end

lib/active_record/connection_adapters/postgresql/oid/jsonb.rb

100.0% lines covered

7 relevant lines. 7 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 2 module ActiveRecord
  3. 2 module ConnectionAdapters
  4. 2 module PostgreSQL
  5. 2 module OID # :nodoc:
  6. 2 class Jsonb < Type::Json # :nodoc:
  7. 2 def type
  8. 89 :jsonb
  9. end
  10. end
  11. end
  12. end
  13. end
  14. end

lib/active_record/connection_adapters/postgresql/oid/legacy_point.rb

95.45% lines covered

22 relevant lines. 21 lines covered and 1 lines missed.
    
  1. # frozen_string_literal: true
  2. 2 module ActiveRecord
  3. 2 module ConnectionAdapters
  4. 2 module PostgreSQL
  5. 2 module OID # :nodoc:
  6. 2 class LegacyPoint < Type::Value # :nodoc:
  7. 2 include ActiveModel::Type::Helpers::Mutable
  8. 2 def type
  9. :point
  10. end
  11. 2 def cast(value)
  12. 67 case value
  13. when ::String
  14. 28 if value.start_with?("(") && value.end_with?(")")
  15. 28 value = value[1...-1]
  16. end
  17. 28 cast(value.split(","))
  18. when ::Array
  19. 93 value.map { |v| Float(v) }
  20. else
  21. 8 value
  22. end
  23. end
  24. 2 def serialize(value)
  25. 34 if value.is_a?(::Array)
  26. 29 "(#{number_for_point(value[0])},#{number_for_point(value[1])})"
  27. else
  28. 5 super
  29. end
  30. end
  31. 2 private
  32. 2 def number_for_point(number)
  33. 58 number.to_s.delete_suffix(".0")
  34. end
  35. end
  36. end
  37. end
  38. end
  39. end

lib/active_record/connection_adapters/postgresql/oid/macaddr.rb

100.0% lines covered

11 relevant lines. 11 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 2 module ActiveRecord
  3. 2 module ConnectionAdapters
  4. 2 module PostgreSQL
  5. 2 module OID # :nodoc:
  6. 2 class Macaddr < Type::String # :nodoc:
  7. 2 def type
  8. 2 :macaddr
  9. end
  10. 2 def changed?(old_value, new_value, _new_value_before_type_cast)
  11. 4 old_value.class != new_value.class ||
  12. new_value && old_value.casecmp(new_value) != 0
  13. end
  14. 2 def changed_in_place?(raw_old_value, new_value)
  15. 1 raw_old_value.class != new_value.class ||
  16. new_value && raw_old_value.casecmp(new_value) != 0
  17. end
  18. end
  19. end
  20. end
  21. end
  22. end

lib/active_record/connection_adapters/postgresql/oid/money.rb

100.0% lines covered

16 relevant lines. 16 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 2 module ActiveRecord
  3. 2 module ConnectionAdapters
  4. 2 module PostgreSQL
  5. 2 module OID # :nodoc:
  6. 2 class Money < Type::Decimal # :nodoc:
  7. 2 def type
  8. 4 :money
  9. end
  10. 2 def scale
  11. 77 2
  12. end
  13. 2 def cast_value(value)
  14. 53 return value unless ::String === value
  15. # Because money output is formatted according to the locale, there are two
  16. # cases to consider (note the decimal separators):
  17. # (1) $12,345,678.12
  18. # (2) $12.345.678,12
  19. # Negative values are represented as follows:
  20. # (3) -$2.55
  21. # (4) ($2.55)
  22. 34 value = value.sub(/^\((.+)\)$/, '-\1') # (4)
  23. 34 case value
  24. when /^-?\D*[\d,]+\.\d{2}$/ # (1)
  25. 32 value.gsub!(/[^-\d.]/, "")
  26. when /^-?\D*[\d.]+,\d{2}$/ # (2)
  27. 2 value.gsub!(/[^-\d,]/, "").sub!(/,/, ".")
  28. end
  29. 34 super(value)
  30. end
  31. end
  32. end
  33. end
  34. end
  35. end

lib/active_record/connection_adapters/postgresql/oid/oid.rb

100.0% lines covered

7 relevant lines. 7 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 2 module ActiveRecord
  3. 2 module ConnectionAdapters
  4. 2 module PostgreSQL
  5. 2 module OID # :nodoc:
  6. 2 class Oid < Type::UnsignedInteger # :nodoc:
  7. 2 def type
  8. 17 :oid
  9. end
  10. end
  11. end
  12. end
  13. end
  14. end

lib/active_record/connection_adapters/postgresql/oid/point.rb

96.88% lines covered

32 relevant lines. 31 lines covered and 1 lines missed.
    
  1. # frozen_string_literal: true
  2. 2 module ActiveRecord
  3. 2 Point = Struct.new(:x, :y)
  4. 2 module ConnectionAdapters
  5. 2 module PostgreSQL
  6. 2 module OID # :nodoc:
  7. 2 class Point < Type::Value # :nodoc:
  8. 2 include ActiveModel::Type::Helpers::Mutable
  9. 2 def type
  10. 31 :point
  11. end
  12. 2 def cast(value)
  13. 66 case value
  14. when ::String
  15. 41 return if value.blank?
  16. 40 if value.start_with?("(") && value.end_with?(")")
  17. 40 value = value[1...-1]
  18. end
  19. 40 x, y = value.split(",")
  20. 40 build_point(x, y)
  21. when ::Array
  22. 2 build_point(*value)
  23. else
  24. 23 value
  25. end
  26. end
  27. 2 def serialize(value)
  28. 124 case value
  29. when ActiveRecord::Point
  30. 63 "(#{number_for_point(value.x)},#{number_for_point(value.y)})"
  31. when ::Array
  32. 28 serialize(build_point(*value))
  33. else
  34. 33 super
  35. end
  36. end
  37. 2 def type_cast_for_schema(value)
  38. 8 if ActiveRecord::Point === value
  39. 8 [value.x, value.y]
  40. else
  41. super
  42. end
  43. end
  44. 2 private
  45. 2 def number_for_point(number)
  46. 126 number.to_s.delete_suffix(".0")
  47. end
  48. 2 def build_point(x, y)
  49. 70 ActiveRecord::Point.new(Float(x), Float(y))
  50. end
  51. end
  52. end
  53. end
  54. end
  55. end

lib/active_record/connection_adapters/postgresql/oid/range.rb

98.33% lines covered

60 relevant lines. 59 lines covered and 1 lines missed.
    
  1. # frozen_string_literal: true
  2. 2 module ActiveRecord
  3. 2 module ConnectionAdapters
  4. 2 module PostgreSQL
  5. 2 module OID # :nodoc:
  6. 2 class Range < Type::Value # :nodoc:
  7. 2 attr_reader :subtype, :type
  8. 2 delegate :user_input_in_time_zone, to: :subtype
  9. 2 def initialize(subtype, type = :range)
  10. 835 @subtype = subtype
  11. 835 @type = type
  12. end
  13. 2 def type_cast_for_schema(value)
  14. value.inspect.gsub("Infinity", "::Float::INFINITY")
  15. end
  16. 2 def cast_value(value)
  17. 267 return if value == "empty"
  18. 252 return value unless value.is_a?(::String)
  19. 210 extracted = extract_bounds(value)
  20. 210 from = type_cast_single extracted[:from]
  21. 210 to = type_cast_single extracted[:to]
  22. 210 if !infinity?(from) && extracted[:exclude_start]
  23. 14 raise ArgumentError, "The Ruby Range object does not support excluding the beginning of a Range. (unsupported value: '#{value}')"
  24. end
  25. 196 ::Range.new(from, to, extracted[:exclude_end])
  26. end
  27. 2 def serialize(value)
  28. 305 if value.is_a?(::Range)
  29. 183 from = type_cast_single_for_database(value.begin)
  30. 183 to = type_cast_single_for_database(value.end)
  31. 182 ::Range.new(from, to, value.exclude_end?)
  32. else
  33. 122 super
  34. end
  35. end
  36. 2 def ==(other)
  37. 3 other.is_a?(Range) &&
  38. other.subtype == subtype &&
  39. other.type == type
  40. end
  41. 2 def map(value) # :nodoc:
  42. 6 new_begin = yield(value.begin)
  43. 6 new_end = yield(value.end)
  44. 6 ::Range.new(new_begin, new_end, value.exclude_end?)
  45. end
  46. 2 def force_equality?(value)
  47. 3 value.is_a?(::Range)
  48. end
  49. 2 private
  50. 2 def type_cast_single(value)
  51. 420 infinity?(value) ? value : @subtype.deserialize(value)
  52. end
  53. 2 def type_cast_single_for_database(value)
  54. 366 infinity?(value) ? value : @subtype.serialize(@subtype.cast(value))
  55. end
  56. 2 def extract_bounds(value)
  57. 210 from, to = value[1..-2].split(",", 2)
  58. 210 {
  59. 210 from: (from == "" || from == "-infinity") ? infinity(negative: true) : unquote(from),
  60. 210 to: (to == "" || to == "infinity") ? infinity : unquote(to),
  61. exclude_start: value.start_with?("("),
  62. exclude_end: value.end_with?(")")
  63. }
  64. end
  65. # When formatting the bound values of range types, PostgreSQL quotes
  66. # the bound value using double-quotes in certain conditions. Within
  67. # a double-quoted string, literal " and \ characters are themselves
  68. # escaped. In input, PostgreSQL accepts multiple escape styles for "
  69. # (either \" or "") but in output always uses "".
  70. # See:
  71. # * https://www.postgresql.org/docs/current/rangetypes.html#RANGETYPES-IO
  72. # * https://www.postgresql.org/docs/current/rowtypes.html#ROWTYPES-IO-SYNTAX
  73. 2 def unquote(value)
  74. 397 if value.start_with?('"') && value.end_with?('"')
  75. 128 unquoted_value = value[1..-2]
  76. 128 unquoted_value.gsub!('""', '"')
  77. 128 unquoted_value.gsub!('\\\\', '\\')
  78. 128 unquoted_value
  79. else
  80. 269 value
  81. end
  82. end
  83. 2 def infinity(negative: false)
  84. 23 if subtype.respond_to?(:infinity)
  85. 3 subtype.infinity(negative: negative)
  86. 20 elsif negative
  87. 8 -::Float::INFINITY
  88. else
  89. 12 ::Float::INFINITY
  90. end
  91. end
  92. 2 def infinity?(value)
  93. 996 value.respond_to?(:infinite?) && value.infinite?
  94. end
  95. end
  96. end
  97. end
  98. end
  99. end

lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb

100.0% lines covered

9 relevant lines. 9 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 2 module ActiveRecord
  3. 2 module ConnectionAdapters
  4. 2 module PostgreSQL
  5. 2 module OID # :nodoc:
  6. 2 class SpecializedString < Type::String # :nodoc:
  7. 2 attr_reader :type
  8. 2 def initialize(type, **options)
  9. 5698 @type = type
  10. 5698 super(**options)
  11. end
  12. end
  13. end
  14. end
  15. end
  16. end

lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb

98.31% lines covered

59 relevant lines. 58 lines covered and 1 lines missed.
    
  1. # frozen_string_literal: true
  2. 2 require "active_support/core_ext/array/extract"
  3. 2 module ActiveRecord
  4. 2 module ConnectionAdapters
  5. 2 module PostgreSQL
  6. 2 module OID # :nodoc:
  7. # This class uses the data from PostgreSQL pg_type table to build
  8. # the OID -> Type mapping.
  9. # - OID is an integer representing the type.
  10. # - Type is an OID::Type object.
  11. # This class has side effects on the +store+ passed during initialization.
  12. 2 class TypeMapInitializer # :nodoc:
  13. 2 def initialize(store)
  14. 726 @store = store
  15. end
  16. 2 def run(records)
  17. 212844 nodes = records.reject { |row| @store.key? row["oid"].to_i }
  18. 212844 mapped = nodes.extract! { |row| @store.key? row["typname"] }
  19. 188320 ranges = nodes.extract! { |row| row["typtype"] == "r" }
  20. 184456 enums = nodes.extract! { |row| row["typtype"] == "e" }
  21. 184449 domains = nodes.extract! { |row| row["typtype"] == "d" }
  22. 181288 arrays = nodes.extract! { |row| row["typinput"] == "array_in" }
  23. 1995 composites = nodes.extract! { |row| row["typelem"].to_i != 0 }
  24. 25250 mapped.each { |row| register_mapped_type(row) }
  25. 733 enums.each { |row| register_enum_type(row) }
  26. 3887 domains.each { |row| register_domain_type(row) }
  27. 180019 arrays.each { |row| register_array_type(row) }
  28. 4590 ranges.each { |row| register_range_type(row) }
  29. 1990 composites.each { |row| register_composite_type(row) }
  30. end
  31. 2 def query_conditions_for_initial_load
  32. 25912 known_type_names = @store.keys.map { |n| "'#{n}'" }
  33. 632 known_type_types = %w('r' 'e' 'd')
  34. 632 <<~SQL % [known_type_names.join(", "), known_type_types.join(", ")]
  35. WHERE
  36. t.typname IN (%s)
  37. OR t.typtype IN (%s)
  38. OR t.typinput = 'array_in(cstring,oid,integer)'::regprocedure
  39. OR t.typelem != 0
  40. SQL
  41. end
  42. 2 private
  43. 2 def register_mapped_type(row)
  44. 24524 alias_type row["oid"], row["typname"]
  45. end
  46. 2 def register_enum_type(row)
  47. 7 register row["oid"], OID::Enum.new
  48. end
  49. 2 def register_array_type(row)
  50. 179293 register_with_subtype(row["oid"], row["typelem"].to_i) do |subtype|
  51. 909 OID::Array.new(subtype, row["typdelim"])
  52. end
  53. end
  54. 2 def register_range_type(row)
  55. 3864 register_with_subtype(row["oid"], row["rngsubtype"].to_i) do |subtype|
  56. 830 OID::Range.new(subtype, row["typname"].to_sym)
  57. end
  58. end
  59. 2 def register_domain_type(row)
  60. 3161 if base_type = @store.lookup(row["typbasetype"].to_i)
  61. 3161 register row["oid"], base_type
  62. else
  63. warn "unknown base type (OID: #{row["typbasetype"]}) for domain #{row["typname"]}."
  64. end
  65. end
  66. 2 def register_composite_type(row)
  67. 1264 if subtype = @store.lookup(row["typelem"].to_i)
  68. 1264 register row["oid"], OID::Vector.new(row["typdelim"], subtype)
  69. end
  70. end
  71. 2 def register(oid, oid_type = nil, &block)
  72. 35971 oid = assert_valid_registration(oid, oid_type || block)
  73. 35971 if block_given?
  74. 31539 @store.register_type(oid, &block)
  75. else
  76. 4432 @store.register_type(oid, oid_type)
  77. end
  78. end
  79. 2 def alias_type(oid, target)
  80. 24524 oid = assert_valid_registration(oid, target)
  81. 24524 @store.alias_type(oid, target)
  82. end
  83. 2 def register_with_subtype(oid, target_oid)
  84. 183157 if @store.key?(target_oid)
  85. 31539 register(oid) do |_, *args|
  86. 1739 yield @store.lookup(target_oid, *args)
  87. end
  88. end
  89. end
  90. 2 def assert_valid_registration(oid, oid_type)
  91. 60495 raise ArgumentError, "can't register nil type for OID #{oid}" if oid_type.nil?
  92. 60495 oid.to_i
  93. end
  94. end
  95. end
  96. end
  97. end
  98. end

lib/active_record/connection_adapters/postgresql/oid/uuid.rb

100.0% lines covered

17 relevant lines. 17 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 2 module ActiveRecord
  3. 2 module ConnectionAdapters
  4. 2 module PostgreSQL
  5. 2 module OID # :nodoc:
  6. 2 class Uuid < Type::Value # :nodoc:
  7. 2 ACCEPTABLE_UUID = %r{\A(\{)?([a-fA-F0-9]{4}-?){8}(?(1)\}|)\z}
  8. 2 alias :serialize :deserialize
  9. 2 def type
  10. 182 :uuid
  11. end
  12. 2 def changed?(old_value, new_value, _new_value_before_type_cast)
  13. 13 old_value.class != new_value.class ||
  14. new_value && old_value.casecmp(new_value) != 0
  15. end
  16. 2 def changed_in_place?(raw_old_value, new_value)
  17. 13 raw_old_value.class != new_value.class ||
  18. new_value && raw_old_value.casecmp(new_value) != 0
  19. end
  20. 2 private
  21. 2 def cast_value(value)
  22. 188 casted = value.to_s
  23. 188 casted if casted.match?(ACCEPTABLE_UUID)
  24. end
  25. end
  26. end
  27. end
  28. end
  29. end

lib/active_record/connection_adapters/postgresql/oid/vector.rb

90.91% lines covered

11 relevant lines. 10 lines covered and 1 lines missed.
    
  1. # frozen_string_literal: true
  2. 2 module ActiveRecord
  3. 2 module ConnectionAdapters
  4. 2 module PostgreSQL
  5. 2 module OID # :nodoc:
  6. 2 class Vector < Type::Value # :nodoc:
  7. 2 attr_reader :delim, :subtype
  8. # +delim+ corresponds to the `typdelim` column in the pg_types
  9. # table. +subtype+ is derived from the `typelem` column in the
  10. # pg_types table.
  11. 2 def initialize(delim, subtype)
  12. 1264 @delim = delim
  13. 1264 @subtype = subtype
  14. end
  15. # FIXME: this should probably split on +delim+ and use +subtype+
  16. # to cast the values. Unfortunately, the current Rails behavior
  17. # is to just return the string.
  18. 2 def cast(value)
  19. value
  20. end
  21. end
  22. end
  23. end
  24. end
  25. end

lib/active_record/connection_adapters/postgresql/oid/xml.rb

100.0% lines covered

15 relevant lines. 15 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 2 module ActiveRecord
  3. 2 module ConnectionAdapters
  4. 2 module PostgreSQL
  5. 2 module OID # :nodoc:
  6. 2 class Xml < Type::String # :nodoc:
  7. 2 def type
  8. 2 :xml
  9. end
  10. 2 def serialize(value)
  11. 4 return unless value
  12. 3 Data.new(super)
  13. end
  14. 2 class Data # :nodoc:
  15. 2 def initialize(value)
  16. 3 @value = value
  17. end
  18. 2 def to_s
  19. 2 @value
  20. end
  21. end
  22. end
  23. end
  24. end
  25. end
  26. end

lib/active_record/connection_adapters/postgresql/quoting.rb

95.4% lines covered

87 relevant lines. 83 lines covered and 4 lines missed.
    
  1. # frozen_string_literal: true
  2. 2 module ActiveRecord
  3. 2 module ConnectionAdapters
  4. 2 module PostgreSQL
  5. 2 module Quoting
  6. # Escapes binary strings for bytea input to the database.
  7. 2 def escape_bytea(value)
  8. 78 @connection.escape_bytea(value) if value
  9. end
  10. # Unescapes bytea output from a database to the binary string it represents.
  11. # NOTE: This is NOT an inverse of escape_bytea! This is only to be used
  12. # on escaped binary output from database drive.
  13. 2 def unescape_bytea(value)
  14. @connection.unescape_bytea(value) if value
  15. end
  16. # Quotes strings for use in SQL input.
  17. 2 def quote_string(s) #:nodoc:
  18. 52552 @connection.escape(s)
  19. end
  20. # Checks the following cases:
  21. #
  22. # - table_name
  23. # - "table.name"
  24. # - schema_name.table_name
  25. # - schema_name."table.name"
  26. # - "schema.name".table_name
  27. # - "schema.name"."table.name"
  28. 2 def quote_table_name(name) # :nodoc:
  29. 342680 self.class.quoted_table_names[name] ||= Utils.extract_schema_qualified_name(name.to_s).quoted.freeze
  30. end
  31. # Quotes schema names for use in SQL queries.
  32. 2 def quote_schema_name(name)
  33. 120 PG::Connection.quote_ident(name)
  34. end
  35. 2 def quote_table_name_for_assignment(table, attr)
  36. quote_column_name(attr)
  37. end
  38. # Quotes column names for use in SQL queries.
  39. 2 def quote_column_name(name) # :nodoc:
  40. 91379 self.class.quoted_column_names[name] ||= PG::Connection.quote_ident(super).freeze
  41. end
  42. # Quote date/time values for use in SQL input.
  43. 2 def quoted_date(value) #:nodoc:
  44. 22983 if value.year <= 0
  45. 8 bce_year = format("%04d", -value.year + 1)
  46. 8 super.sub(/^-?\d+/, bce_year) + " BC"
  47. else
  48. 22975 super
  49. end
  50. end
  51. 2 def quoted_binary(value) # :nodoc:
  52. 78 "'#{escape_bytea(value.to_s)}'"
  53. end
  54. 2 def quote_default_expression(value, column) # :nodoc:
  55. 603 if value.is_a?(Proc)
  56. 18 value.call
  57. 585 elsif column.type == :uuid && value.is_a?(String) && /\(\)/.match?(value)
  58. 56 value # Does not quote function default values for UUID columns
  59. 529 elsif column.respond_to?(:array?)
  60. 12 type = lookup_cast_type_from_column(column)
  61. 12 quote(type.serialize(value))
  62. else
  63. 517 super
  64. end
  65. end
  66. 2 def lookup_cast_type_from_column(column) # :nodoc:
  67. 347637 type_map.lookup(column.oid, column.fmod, column.sql_type)
  68. end
  69. 2 def column_name_matcher
  70. 690 COLUMN_NAME
  71. end
  72. 2 def column_name_with_order_matcher
  73. 4665 COLUMN_NAME_WITH_ORDER
  74. end
  75. 2 COLUMN_NAME = /
  76. \A
  77. (
  78. (?:
  79. # "table_name"."column_name"::type_name | function(one or no argument)::type_name
  80. ((?:\w+\.|"\w+"\.)?(?:\w+|"\w+")(?:::\w+)?) | \w+\((?:|\g<2>)\)(?:::\w+)?
  81. )
  82. (?:(?:\s+AS)?\s+(?:\w+|"\w+"))?
  83. )
  84. (?:\s*,\s*\g<1>)*
  85. \z
  86. /ix
  87. 2 COLUMN_NAME_WITH_ORDER = /
  88. \A
  89. (
  90. (?:
  91. # "table_name"."column_name"::type_name | function(one or no argument)::type_name
  92. ((?:\w+\.|"\w+"\.)?(?:\w+|"\w+")(?:::\w+)?) | \w+\((?:|\g<2>)\)(?:::\w+)?
  93. )
  94. (?:\s+ASC|\s+DESC)?
  95. (?:\s+NULLS\s+(?:FIRST|LAST))?
  96. )
  97. (?:\s*,\s*\g<1>)*
  98. \z
  99. /ix
  100. 2 private_constant :COLUMN_NAME, :COLUMN_NAME_WITH_ORDER
  101. 2 private
  102. 2 def lookup_cast_type(sql_type)
  103. 517 super(query_value("SELECT #{quote(sql_type)}::regtype::oid", "SCHEMA").to_i)
  104. end
  105. 2 def _quote(value)
  106. 561839 case value
  107. when OID::Xml::Data
  108. "xml '#{quote_string(value.to_s)}'"
  109. when OID::Bit::Data
  110. 13 if value.binary?
  111. 12 "B'#{value}'"
  112. 1 elsif value.hex?
  113. "X'#{value}'"
  114. end
  115. when Numeric
  116. 489015 if value.finite?
  117. 489009 super
  118. else
  119. 6 "'#{value}'"
  120. end
  121. when OID::Array::Data
  122. 94 _quote(encode_array(value))
  123. when Range
  124. 1 _quote(encode_range(value))
  125. else
  126. 72716 super
  127. end
  128. end
  129. 2 def _type_cast(value)
  130. 229838 case value
  131. when Type::Binary::Data
  132. # Return a bind param hash with format as binary.
  133. # See https://deveiate.org/code/pg/PG/Connection.html#method-i-exec_prepared-doc
  134. # for more information
  135. 14 { value: value.to_s, format: 1 }
  136. when OID::Xml::Data, OID::Bit::Data
  137. 8 value.to_s
  138. when OID::Array::Data
  139. 75 encode_array(value)
  140. when Range
  141. 39 encode_range(value)
  142. else
  143. 229702 super
  144. end
  145. end
  146. 2 def encode_array(array_data)
  147. 169 encoder = array_data.encoder
  148. 169 values = type_cast_array(array_data.values)
  149. 169 result = encoder.encode(values)
  150. 169 if encoding = determine_encoding_of_strings_in_array(values)
  151. 56 result.force_encoding(encoding)
  152. end
  153. 169 result
  154. end
  155. 2 def encode_range(range)
  156. 40 "[#{type_cast_range_value(range.begin)},#{type_cast_range_value(range.end)}#{range.exclude_end? ? ')' : ']'}"
  157. end
  158. 2 def determine_encoding_of_strings_in_array(value)
  159. 354 case value
  160. 185 when ::Array then determine_encoding_of_strings_in_array(value.first)
  161. 56 when ::String then value.encoding
  162. end
  163. end
  164. 2 def type_cast_array(values)
  165. 369 case values
  166. 409 when ::Array then values.map { |item| type_cast_array(item) }
  167. 160 else _type_cast(values)
  168. end
  169. end
  170. 2 def type_cast_range_value(value)
  171. 80 infinity?(value) ? "" : type_cast(value)
  172. end
  173. 2 def infinity?(value)
  174. 80 value.respond_to?(:infinite?) && value.infinite?
  175. end
  176. end
  177. end
  178. end
  179. end

lib/active_record/connection_adapters/postgresql/referential_integrity.rb

100.0% lines covered

18 relevant lines. 18 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 2 module ActiveRecord
  3. 2 module ConnectionAdapters
  4. 2 module PostgreSQL
  5. 2 module ReferentialIntegrity # :nodoc:
  6. 2 def disable_referential_integrity # :nodoc:
  7. 619 original_exception = nil
  8. 619 begin
  9. 619 transaction(requires_new: true) do
  10. 122399 execute(tables.collect { |name| "ALTER TABLE #{quote_table_name(name)} DISABLE TRIGGER ALL" }.join(";"))
  11. end
  12. rescue ActiveRecord::ActiveRecordError => e
  13. 4 original_exception = e
  14. end
  15. 618 begin
  16. 618 yield
  17. rescue ActiveRecord::InvalidForeignKey => e
  18. 1 warn <<-WARNING
  19. WARNING: Rails was not able to disable referential integrity.
  20. This is most likely caused due to missing permissions.
  21. Rails needs superuser privileges to disable referential integrity.
  22. cause: #{original_exception&.message}
  23. WARNING
  24. 1 raise e
  25. end
  26. 615 begin
  27. 615 transaction(requires_new: true) do
  28. 121572 execute(tables.collect { |name| "ALTER TABLE #{quote_table_name(name)} ENABLE TRIGGER ALL" }.join(";"))
  29. end
  30. 2 rescue ActiveRecord::ActiveRecordError
  31. end
  32. end
  33. end
  34. end
  35. end
  36. end

lib/active_record/connection_adapters/postgresql/schema_creation.rb

100.0% lines covered

41 relevant lines. 41 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 2 module ActiveRecord
  3. 2 module ConnectionAdapters
  4. 2 module PostgreSQL
  5. 2 class SchemaCreation < SchemaCreation # :nodoc:
  6. 2 private
  7. 2 def visit_AlterTable(o)
  8. 397 super << o.constraint_validations.map { |fk| visit_ValidateConstraint fk }.join(" ")
  9. end
  10. 2 def visit_AddForeignKey(o)
  11. 90 super.dup.tap { |sql| sql << " NOT VALID" unless o.validate? }
  12. end
  13. 2 def visit_ValidateConstraint(name)
  14. 5 "VALIDATE CONSTRAINT #{quote_column_name(name)}"
  15. end
  16. 2 def visit_ChangeColumnDefinition(o)
  17. 35 column = o.column
  18. 35 column.sql_type = type_to_sql(column.type, **column.options)
  19. 35 quoted_column_name = quote_column_name(o.name)
  20. 35 change_column_sql = +"ALTER COLUMN #{quoted_column_name} TYPE #{column.sql_type}"
  21. 35 options = column_options(column)
  22. 35 if options[:collation]
  23. 1 change_column_sql << " COLLATE \"#{options[:collation]}\""
  24. end
  25. 35 if options[:using]
  26. 2 change_column_sql << " USING #{options[:using]}"
  27. 33 elsif options[:cast_as]
  28. 2 cast_as_type = type_to_sql(options[:cast_as], **options)
  29. 2 change_column_sql << " USING CAST(#{quoted_column_name} AS #{cast_as_type})"
  30. end
  31. 35 if options.key?(:default)
  32. 15 if options[:default].nil?
  33. 4 change_column_sql << ", ALTER COLUMN #{quoted_column_name} DROP DEFAULT"
  34. else
  35. 11 quoted_default = quote_default_expression(options[:default], column)
  36. 11 change_column_sql << ", ALTER COLUMN #{quoted_column_name} SET DEFAULT #{quoted_default}"
  37. end
  38. end
  39. 35 if options.key?(:null)
  40. 11 change_column_sql << ", ALTER COLUMN #{quoted_column_name} #{options[:null] ? 'DROP' : 'SET'} NOT NULL"
  41. end
  42. 35 change_column_sql
  43. end
  44. 2 def add_column_options!(sql, options)
  45. 3906 if options[:collation]
  46. 11 sql << " COLLATE \"#{options[:collation]}\""
  47. end
  48. 3906 super
  49. end
  50. # Returns any SQL string to go between CREATE and TABLE. May be nil.
  51. 2 def table_modifier_in_create(o)
  52. # A table cannot be both TEMPORARY and UNLOGGED, since all TEMPORARY
  53. # tables are already UNLOGGED.
  54. 1513 if o.temporary
  55. 1 " TEMPORARY"
  56. 1512 elsif o.unlogged
  57. 2 " UNLOGGED"
  58. end
  59. end
  60. end
  61. end
  62. end
  63. end

lib/active_record/connection_adapters/postgresql/schema_definitions.rb

100.0% lines covered

31 relevant lines. 31 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 2 module ActiveRecord
  3. 2 module ConnectionAdapters
  4. 2 module PostgreSQL
  5. 2 module ColumnMethods
  6. 2 extend ActiveSupport::Concern
  7. # Defines the primary key field.
  8. # Use of the native PostgreSQL UUID type is supported, and can be used
  9. # by defining your tables as such:
  10. #
  11. # create_table :stuffs, id: :uuid do |t|
  12. # t.string :content
  13. # t.timestamps
  14. # end
  15. #
  16. # By default, this will use the <tt>gen_random_uuid()</tt> function from the
  17. # +pgcrypto+ extension. As that extension is only available in
  18. # PostgreSQL 9.4+, for earlier versions an explicit default can be set
  19. # to use <tt>uuid_generate_v4()</tt> from the +uuid-ossp+ extension instead:
  20. #
  21. # create_table :stuffs, id: false do |t|
  22. # t.primary_key :id, :uuid, default: "uuid_generate_v4()"
  23. # t.uuid :foo_id
  24. # t.timestamps
  25. # end
  26. #
  27. # To enable the appropriate extension, which is a requirement, use
  28. # the +enable_extension+ method in your migrations.
  29. #
  30. # To use a UUID primary key without any of the extensions, set the
  31. # +:default+ option to +nil+:
  32. #
  33. # create_table :stuffs, id: false do |t|
  34. # t.primary_key :id, :uuid, default: nil
  35. # t.uuid :foo_id
  36. # t.timestamps
  37. # end
  38. #
  39. # You may also pass a custom stored procedure that returns a UUID or use a
  40. # different UUID generation function from another library.
  41. #
  42. # Note that setting the UUID primary key default value to +nil+ will
  43. # require you to assure that you always provide a UUID value before saving
  44. # a record (as primary keys cannot be +nil+). This might be done via the
  45. # +SecureRandom.uuid+ method and a +before_save+ callback, for instance.
  46. 2 def primary_key(name, type = :primary_key, **options)
  47. 1385 if type == :uuid
  48. 41 options[:default] = options.fetch(:default, "gen_random_uuid()")
  49. end
  50. 1385 super
  51. end
  52. ##
  53. # :method: bigserial
  54. # :call-seq: bigserial(*names, **options)
  55. ##
  56. # :method: bit
  57. # :call-seq: bit(*names, **options)
  58. ##
  59. # :method: bit_varying
  60. # :call-seq: bit_varying(*names, **options)
  61. ##
  62. # :method: cidr
  63. # :call-seq: cidr(*names, **options)
  64. ##
  65. # :method: citext
  66. # :call-seq: citext(*names, **options)
  67. ##
  68. # :method: daterange
  69. # :call-seq: daterange(*names, **options)
  70. ##
  71. # :method: hstore
  72. # :call-seq: hstore(*names, **options)
  73. ##
  74. # :method: inet
  75. # :call-seq: inet(*names, **options)
  76. ##
  77. # :method: interval
  78. # :call-seq: interval(*names, **options)
  79. ##
  80. # :method: int4range
  81. # :call-seq: int4range(*names, **options)
  82. ##
  83. # :method: int8range
  84. # :call-seq: int8range(*names, **options)
  85. ##
  86. # :method: jsonb
  87. # :call-seq: jsonb(*names, **options)
  88. ##
  89. # :method: ltree
  90. # :call-seq: ltree(*names, **options)
  91. ##
  92. # :method: macaddr
  93. # :call-seq: macaddr(*names, **options)
  94. ##
  95. # :method: money
  96. # :call-seq: money(*names, **options)
  97. ##
  98. # :method: numrange
  99. # :call-seq: numrange(*names, **options)
  100. ##
  101. # :method: oid
  102. # :call-seq: oid(*names, **options)
  103. ##
  104. # :method: point
  105. # :call-seq: point(*names, **options)
  106. ##
  107. # :method: line
  108. # :call-seq: line(*names, **options)
  109. ##
  110. # :method: lseg
  111. # :call-seq: lseg(*names, **options)
  112. ##
  113. # :method: box
  114. # :call-seq: box(*names, **options)
  115. ##
  116. # :method: path
  117. # :call-seq: path(*names, **options)
  118. ##
  119. # :method: polygon
  120. # :call-seq: polygon(*names, **options)
  121. ##
  122. # :method: circle
  123. # :call-seq: circle(*names, **options)
  124. ##
  125. # :method: serial
  126. # :call-seq: serial(*names, **options)
  127. ##
  128. # :method: tsrange
  129. # :call-seq: tsrange(*names, **options)
  130. ##
  131. # :method: tstzrange
  132. # :call-seq: tstzrange(*names, **options)
  133. ##
  134. # :method: tsvector
  135. # :call-seq: tsvector(*names, **options)
  136. ##
  137. # :method: uuid
  138. # :call-seq: uuid(*names, **options)
  139. ##
  140. # :method: xml
  141. # :call-seq: xml(*names, **options)
  142. 2 included do
  143. 4 define_column_methods :bigserial, :bit, :bit_varying, :cidr, :citext, :daterange,
  144. :hstore, :inet, :interval, :int4range, :int8range, :jsonb, :ltree, :macaddr,
  145. :money, :numrange, :oid, :point, :line, :lseg, :box, :path, :polygon, :circle,
  146. :serial, :tsrange, :tstzrange, :tsvector, :uuid, :xml
  147. end
  148. end
  149. 2 class TableDefinition < ActiveRecord::ConnectionAdapters::TableDefinition
  150. 2 include ColumnMethods
  151. 2 attr_reader :unlogged
  152. 2 def initialize(*, **)
  153. 1973 super
  154. 1973 @unlogged = ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.create_unlogged_tables
  155. end
  156. 2 private
  157. 2 def integer_like_primary_key_type(type, options)
  158. 17 if type == :bigint || options[:limit] == 8
  159. 2 :bigserial
  160. else
  161. 15 :serial
  162. end
  163. end
  164. end
  165. 2 class Table < ActiveRecord::ConnectionAdapters::Table
  166. 2 include ColumnMethods
  167. end
  168. 2 class AlterTable < ActiveRecord::ConnectionAdapters::AlterTable
  169. 2 attr_reader :constraint_validations
  170. 2 def initialize(td)
  171. 392 super
  172. 392 @constraint_validations = []
  173. end
  174. 2 def validate_constraint(name)
  175. 5 @constraint_validations << name
  176. end
  177. end
  178. end
  179. end
  180. end

lib/active_record/connection_adapters/postgresql/schema_dumper.rb

100.0% lines covered

27 relevant lines. 27 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 2 module ActiveRecord
  3. 2 module ConnectionAdapters
  4. 2 module PostgreSQL
  5. 2 class SchemaDumper < ConnectionAdapters::SchemaDumper # :nodoc:
  6. 2 private
  7. 2 def extensions(stream)
  8. 109 extensions = @connection.extensions
  9. 109 if extensions.any?
  10. 108 stream.puts " # These are extensions that must be enabled in order to support this database"
  11. 108 extensions.sort.each do |extension|
  12. 506 stream.puts " enable_extension #{extension.inspect}"
  13. end
  14. 108 stream.puts
  15. end
  16. end
  17. 2 def prepare_column_options(column)
  18. 8015 spec = super
  19. 8015 spec[:array] = "true" if column.array?
  20. 8015 spec
  21. end
  22. 2 def default_primary_key?(column)
  23. 1915 schema_type(column) == :bigserial
  24. end
  25. 2 def explicit_primary_key_default?(column)
  26. 1915 column.type == :uuid || (column.type == :integer && !column.serial?)
  27. end
  28. 2 def schema_type(column)
  29. 8213 return super unless column.serial?
  30. 1823 if column.bigint?
  31. 1721 :bigserial
  32. else
  33. 102 :serial
  34. end
  35. end
  36. 2 def schema_expression(column)
  37. 1877 super unless column.serial?
  38. end
  39. end
  40. end
  41. end
  42. end

lib/active_record/connection_adapters/postgresql/schema_statements.rb

96.62% lines covered

325 relevant lines. 314 lines covered and 11 lines missed.
    
  1. # frozen_string_literal: true
  2. 2 module ActiveRecord
  3. 2 module ConnectionAdapters
  4. 2 module PostgreSQL
  5. 2 module SchemaStatements
  6. # Drops the database specified on the +name+ attribute
  7. # and creates it again using the provided +options+.
  8. 2 def recreate_database(name, options = {}) #:nodoc:
  9. drop_database(name)
  10. create_database(name, options)
  11. end
  12. # Create a new PostgreSQL database. Options include <tt>:owner</tt>, <tt>:template</tt>,
  13. # <tt>:encoding</tt> (defaults to utf8), <tt>:collation</tt>, <tt>:ctype</tt>,
  14. # <tt>:tablespace</tt>, and <tt>:connection_limit</tt> (note that MySQL uses
  15. # <tt>:charset</tt> while PostgreSQL uses <tt>:encoding</tt>).
  16. #
  17. # Example:
  18. # create_database config[:database], config
  19. # create_database 'foo_development', encoding: 'unicode'
  20. 2 def create_database(name, options = {})
  21. 4 options = { encoding: "utf8" }.merge!(options.symbolize_keys)
  22. 4 option_string = options.each_with_object(+"") do |(key, value), memo|
  23. 6 memo << case key
  24. when :owner
  25. " OWNER = \"#{value}\""
  26. when :template
  27. " TEMPLATE = \"#{value}\""
  28. when :encoding
  29. 4 " ENCODING = '#{value}'"
  30. when :collation
  31. 1 " LC_COLLATE = '#{value}'"
  32. when :ctype
  33. 1 " LC_CTYPE = '#{value}'"
  34. when :tablespace
  35. " TABLESPACE = \"#{value}\""
  36. when :connection_limit
  37. " CONNECTION LIMIT = #{value}"
  38. else
  39. ""
  40. end
  41. end
  42. 4 execute "CREATE DATABASE #{quote_table_name(name)}#{option_string}"
  43. end
  44. # Drops a PostgreSQL database.
  45. #
  46. # Example:
  47. # drop_database 'matt_development'
  48. 2 def drop_database(name) #:nodoc:
  49. execute "DROP DATABASE IF EXISTS #{quote_table_name(name)}"
  50. end
  51. 2 def drop_table(table_name, **options) # :nodoc:
  52. 2529 schema_cache.clear_data_source_cache!(table_name.to_s)
  53. 2529 execute "DROP TABLE#{' IF EXISTS' if options[:if_exists]} #{quote_table_name(table_name)}#{' CASCADE' if options[:force] == :cascade}"
  54. end
  55. # Returns true if schema exists.
  56. 2 def schema_exists?(name)
  57. 4 query_value("SELECT COUNT(*) FROM pg_namespace WHERE nspname = #{quote(name)}", "SCHEMA").to_i > 0
  58. end
  59. # Verifies existence of an index with a given name.
  60. 2 def index_name_exists?(table_name, index_name)
  61. 19 table = quoted_scope(table_name)
  62. 19 index = quoted_scope(index_name)
  63. 19 query_value(<<~SQL, "SCHEMA").to_i > 0
  64. SELECT COUNT(*)
  65. FROM pg_class t
  66. INNER JOIN pg_index d ON t.oid = d.indrelid
  67. INNER JOIN pg_class i ON d.indexrelid = i.oid
  68. LEFT JOIN pg_namespace n ON n.oid = i.relnamespace
  69. WHERE i.relkind IN ('i', 'I')
  70. AND i.relname = #{index[:name]}
  71. AND t.relname = #{table[:name]}
  72. AND n.nspname = #{index[:schema]}
  73. SQL
  74. end
  75. # Returns an array of indexes for the given table.
  76. 2 def indexes(table_name) # :nodoc:
  77. 3348 scope = quoted_scope(table_name)
  78. 3348 result = query(<<~SQL, "SCHEMA")
  79. SELECT distinct i.relname, d.indisunique, d.indkey, pg_get_indexdef(d.indexrelid), t.oid,
  80. pg_catalog.obj_description(i.oid, 'pg_class') AS comment
  81. FROM pg_class t
  82. INNER JOIN pg_index d ON t.oid = d.indrelid
  83. INNER JOIN pg_class i ON d.indexrelid = i.oid
  84. LEFT JOIN pg_namespace n ON n.oid = i.relnamespace
  85. WHERE i.relkind IN ('i', 'I')
  86. AND d.indisprimary = 'f'
  87. AND t.relname = #{scope[:name]}
  88. AND n.nspname = #{scope[:schema]}
  89. ORDER BY i.relname
  90. SQL
  91. 3348 result.map do |row|
  92. 1170 index_name = row[0]
  93. 1170 unique = row[1]
  94. 1170 indkey = row[2].split(" ").map(&:to_i)
  95. 1170 inddef = row[3]
  96. 1170 oid = row[4]
  97. 1170 comment = row[5]
  98. 1170 using, expressions, where = inddef.scan(/ USING (\w+?) \((.+?)\)(?: WHERE (.+))?\z/m).flatten
  99. 1170 orders = {}
  100. 1170 opclasses = {}
  101. 1170 if indkey.include?(0)
  102. 43 columns = expressions
  103. else
  104. 1127 columns = Hash[query(<<~SQL, "SCHEMA")].values_at(*indkey).compact
  105. SELECT a.attnum, a.attname
  106. FROM pg_attribute a
  107. WHERE a.attrelid = #{oid}
  108. AND a.attnum IN (#{indkey.join(",")})
  109. SQL
  110. # add info on sort order (only desc order is explicitly specified, asc is the default)
  111. # and non-default opclasses
  112. 1127 expressions.scan(/(?<column>\w+)"?\s?(?<opclass>\w+_ops)?\s?(?<desc>DESC)?\s?(?<nulls>NULLS (?:FIRST|LAST))?/).each do |column, opclass, desc, nulls|
  113. 1397 opclasses[column] = opclass.to_sym if opclass
  114. 1397 if nulls
  115. 2 orders[column] = [desc, nulls].compact.join(" ")
  116. else
  117. 1395 orders[column] = :desc if desc
  118. end
  119. end
  120. end
  121. 1170 IndexDefinition.new(
  122. table_name,
  123. index_name,
  124. unique,
  125. columns,
  126. orders: orders,
  127. opclasses: opclasses,
  128. where: where,
  129. using: using.to_sym,
  130. comment: comment.presence
  131. )
  132. end
  133. end
  134. 2 def table_options(table_name) # :nodoc:
  135. 2152 if comment = table_comment(table_name)
  136. 2 { comment: comment }
  137. end
  138. end
  139. # Returns a comment stored in database for given table
  140. 2 def table_comment(table_name) # :nodoc:
  141. 2159 scope = quoted_scope(table_name, type: "BASE TABLE")
  142. 2159 if scope[:name]
  143. 2159 query_value(<<~SQL, "SCHEMA")
  144. SELECT pg_catalog.obj_description(c.oid, 'pg_class')
  145. FROM pg_catalog.pg_class c
  146. LEFT JOIN pg_namespace n ON n.oid = c.relnamespace
  147. WHERE c.relname = #{scope[:name]}
  148. AND c.relkind IN (#{scope[:type]})
  149. AND n.nspname = #{scope[:schema]}
  150. SQL
  151. end
  152. end
  153. # Returns the current database name.
  154. 2 def current_database
  155. 122 query_value("SELECT current_database()", "SCHEMA")
  156. end
  157. # Returns the current schema name.
  158. 2 def current_schema
  159. 4 query_value("SELECT current_schema", "SCHEMA")
  160. end
  161. # Returns the current database encoding format.
  162. 2 def encoding
  163. 2 query_value("SELECT pg_encoding_to_char(encoding) FROM pg_database WHERE datname = current_database()", "SCHEMA")
  164. end
  165. # Returns the current database collation.
  166. 2 def collation
  167. 1 query_value("SELECT datcollate FROM pg_database WHERE datname = current_database()", "SCHEMA")
  168. end
  169. # Returns the current database ctype.
  170. 2 def ctype
  171. 1 query_value("SELECT datctype FROM pg_database WHERE datname = current_database()", "SCHEMA")
  172. end
  173. # Returns an array of schema names.
  174. 2 def schema_names
  175. 6 query_values(<<~SQL, "SCHEMA")
  176. SELECT nspname
  177. FROM pg_namespace
  178. WHERE nspname !~ '^pg_.*'
  179. AND nspname NOT IN ('information_schema')
  180. ORDER by nspname;
  181. SQL
  182. end
  183. # Creates a schema for the given schema name.
  184. 2 def create_schema(schema_name)
  185. 9 execute "CREATE SCHEMA #{quote_schema_name(schema_name)}"
  186. end
  187. # Drops the schema for the given schema name.
  188. 2 def drop_schema(schema_name, **options)
  189. 111 execute "DROP SCHEMA#{' IF EXISTS' if options[:if_exists]} #{quote_schema_name(schema_name)} CASCADE"
  190. end
  191. # Sets the schema search path to a string of comma-separated schema names.
  192. # Names beginning with $ have to be quoted (e.g. $user => '$user').
  193. # See: https://www.postgresql.org/docs/current/static/ddl-schemas.html
  194. #
  195. # This should be not be called manually but set in database.yml.
  196. 2 def schema_search_path=(schema_csv)
  197. 770 if schema_csv
  198. 59 execute("SET search_path TO #{schema_csv}", "SCHEMA")
  199. 58 @schema_search_path = schema_csv
  200. end
  201. end
  202. # Returns the active schema search path.
  203. 2 def schema_search_path
  204. 8741 @schema_search_path ||= query_value("SHOW search_path", "SCHEMA")
  205. end
  206. # Returns the current client message level.
  207. 2 def client_min_messages
  208. 5 query_value("SHOW client_min_messages", "SCHEMA")
  209. end
  210. # Set the client message level.
  211. 2 def client_min_messages=(level)
  212. 718 execute("SET client_min_messages TO '#{level}'", "SCHEMA")
  213. end
  214. # Returns the sequence name for a table's primary key or some other specified key.
  215. 2 def default_sequence_name(table_name, pk = "id") #:nodoc:
  216. 87 result = serial_sequence(table_name, pk)
  217. 81 return nil unless result
  218. 81 Utils.extract_schema_qualified_name(result).to_s
  219. rescue ActiveRecord::StatementInvalid
  220. 6 PostgreSQL::Name.new(nil, "#{table_name}_#{pk}_seq").to_s
  221. end
  222. 2 def serial_sequence(table, column)
  223. 89 query_value("SELECT pg_get_serial_sequence(#{quote(table)}, #{quote(column)})", "SCHEMA")
  224. end
  225. # Sets the sequence of a table's primary key to the specified value.
  226. 2 def set_pk_sequence!(table, value) #:nodoc:
  227. 1 pk, sequence = pk_and_sequence_for(table)
  228. 1 if pk
  229. 1 if sequence
  230. 1 quoted_sequence = quote_table_name(sequence)
  231. 1 query_value("SELECT setval(#{quote(quoted_sequence)}, #{value})", "SCHEMA")
  232. else
  233. @logger.warn "#{table} has primary key #{pk} with no default sequence." if @logger
  234. end
  235. end
  236. end
  237. # Resets the sequence of a table's primary key to the maximum value.
  238. 2 def reset_pk_sequence!(table, pk = nil, sequence = nil) #:nodoc:
  239. 2476 unless pk && sequence
  240. 2473 default_pk, default_sequence = pk_and_sequence_for(table)
  241. 2473 pk ||= default_pk
  242. 2473 sequence ||= default_sequence
  243. end
  244. 2476 if @logger && pk && !sequence
  245. @logger.warn "#{table} has primary key #{pk} with no default sequence."
  246. end
  247. 2476 if pk && sequence
  248. 2284 quoted_sequence = quote_table_name(sequence)
  249. 2284 max_pk = query_value("SELECT MAX(#{quote_column_name pk}) FROM #{quote_table_name(table)}", "SCHEMA")
  250. 2284 if max_pk.nil?
  251. 9 if database_version >= 100000
  252. 9 minvalue = query_value("SELECT seqmin FROM pg_sequence WHERE seqrelid = #{quote(quoted_sequence)}::regclass", "SCHEMA")
  253. else
  254. minvalue = query_value("SELECT min_value FROM #{quoted_sequence}", "SCHEMA")
  255. end
  256. end
  257. 2284 query_value("SELECT setval(#{quote(quoted_sequence)}, #{max_pk ? max_pk : minvalue}, #{max_pk ? true : false})", "SCHEMA")
  258. end
  259. end
  260. # Returns a table's primary key and belonging sequence.
  261. 2 def pk_and_sequence_for(table) #:nodoc:
  262. # First try looking for a sequence with a dependency on the
  263. # given table's primary key.
  264. 2499 result = query(<<~SQL, "SCHEMA")[0]
  265. SELECT attr.attname, nsp.nspname, seq.relname
  266. FROM pg_class seq,
  267. pg_attribute attr,
  268. pg_depend dep,
  269. pg_constraint cons,
  270. pg_namespace nsp
  271. WHERE seq.oid = dep.objid
  272. AND seq.relkind = 'S'
  273. AND attr.attrelid = dep.refobjid
  274. AND attr.attnum = dep.refobjsubid
  275. AND attr.attrelid = cons.conrelid
  276. AND attr.attnum = cons.conkey[1]
  277. AND seq.relnamespace = nsp.oid
  278. AND cons.contype = 'p'
  279. AND dep.classid = 'pg_class'::regclass
  280. AND dep.refobjid = #{quote(quote_table_name(table))}::regclass
  281. SQL
  282. 2498 if result.nil? || result.empty?
  283. 199 result = query(<<~SQL, "SCHEMA")[0]
  284. SELECT attr.attname, nsp.nspname,
  285. CASE
  286. WHEN pg_get_expr(def.adbin, def.adrelid) !~* 'nextval' THEN NULL
  287. WHEN split_part(pg_get_expr(def.adbin, def.adrelid), '''', 2) ~ '.' THEN
  288. substr(split_part(pg_get_expr(def.adbin, def.adrelid), '''', 2),
  289. strpos(split_part(pg_get_expr(def.adbin, def.adrelid), '''', 2), '.')+1)
  290. ELSE split_part(pg_get_expr(def.adbin, def.adrelid), '''', 2)
  291. END
  292. FROM pg_class t
  293. JOIN pg_attribute attr ON (t.oid = attrelid)
  294. JOIN pg_attrdef def ON (adrelid = attrelid AND adnum = attnum)
  295. JOIN pg_constraint cons ON (conrelid = adrelid AND adnum = conkey[1])
  296. JOIN pg_namespace nsp ON (t.relnamespace = nsp.oid)
  297. WHERE t.oid = #{quote(quote_table_name(table))}::regclass
  298. AND cons.contype = 'p'
  299. AND pg_get_expr(def.adbin, def.adrelid) ~* 'nextval|uuid_generate'
  300. SQL
  301. end
  302. 2498 pk = result.shift
  303. 2304 if result.last
  304. 2301 [pk, PostgreSQL::Name.new(*result)]
  305. else
  306. 3 [pk, nil]
  307. end
  308. rescue
  309. 195 nil
  310. end
  311. 2 def primary_keys(table_name) # :nodoc:
  312. 3582 query_values(<<~SQL, "SCHEMA")
  313. SELECT a.attname
  314. FROM (
  315. SELECT indrelid, indkey, generate_subscripts(indkey, 1) idx
  316. FROM pg_index
  317. WHERE indrelid = #{quote(quote_table_name(table_name))}::regclass
  318. AND indisprimary
  319. ) i
  320. JOIN pg_attribute a
  321. ON a.attrelid = i.indrelid
  322. AND a.attnum = i.indkey[i.idx]
  323. ORDER BY i.idx
  324. SQL
  325. end
  326. # Renames a table.
  327. # Also renames a table's primary key sequence if the sequence name exists and
  328. # matches the Active Record default.
  329. #
  330. # Example:
  331. # rename_table('octopuses', 'octopi')
  332. 2 def rename_table(table_name, new_name)
  333. 14 clear_cache!
  334. 14 schema_cache.clear_data_source_cache!(table_name.to_s)
  335. 14 schema_cache.clear_data_source_cache!(new_name.to_s)
  336. 14 execute "ALTER TABLE #{quote_table_name(table_name)} RENAME TO #{quote_table_name(new_name)}"
  337. 14 pk, seq = pk_and_sequence_for(new_name)
  338. 14 if pk
  339. 14 idx = "#{table_name}_pkey"
  340. 14 new_idx = "#{new_name}_pkey"
  341. 14 execute "ALTER INDEX #{quote_table_name(idx)} RENAME TO #{quote_table_name(new_idx)}"
  342. 14 if seq && seq.identifier == "#{table_name}_#{pk}_seq"
  343. 12 new_seq = "#{new_name}_#{pk}_seq"
  344. 12 execute "ALTER TABLE #{seq.quoted} RENAME TO #{quote_table_name(new_seq)}"
  345. end
  346. end
  347. 14 rename_table_indexes(table_name, new_name)
  348. end
  349. 2 def add_column(table_name, column_name, type, **options) #:nodoc:
  350. 311 clear_cache!
  351. 311 super
  352. 305 change_column_comment(table_name, column_name, options[:comment]) if options.key?(:comment)
  353. end
  354. 2 def change_column(table_name, column_name, type, **options) #:nodoc:
  355. 33 clear_cache!
  356. 70 sqls, procs = Array(change_column_for_alter(table_name, column_name, type, **options)).partition { |v| v.is_a?(String) }
  357. 33 execute "ALTER TABLE #{quote_table_name(table_name)} #{sqls.join(", ")}"
  358. 32 procs.each(&:call)
  359. end
  360. # Changes the default value of a table column.
  361. 2 def change_column_default(table_name, column_name, default_or_changes) # :nodoc:
  362. 13 execute "ALTER TABLE #{quote_table_name(table_name)} #{change_column_default_for_alter(table_name, column_name, default_or_changes)}"
  363. end
  364. 2 def change_column_null(table_name, column_name, null, default = nil) #:nodoc:
  365. 7 clear_cache!
  366. 7 unless null || default.nil?
  367. 2 column = column_for(table_name, column_name)
  368. 2 execute "UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote_default_expression(default, column)} WHERE #{quote_column_name(column_name)} IS NULL" if column
  369. end
  370. 7 execute "ALTER TABLE #{quote_table_name(table_name)} #{change_column_null_for_alter(table_name, column_name, null, default)}"
  371. end
  372. # Adds comment for given table column or drops it if +comment+ is a +nil+
  373. 2 def change_column_comment(table_name, column_name, comment_or_changes) # :nodoc:
  374. 66 clear_cache!
  375. 66 comment = extract_new_comment_value(comment_or_changes)
  376. 66 execute "COMMENT ON COLUMN #{quote_table_name(table_name)}.#{quote_column_name(column_name)} IS #{quote(comment)}"
  377. end
  378. # Adds comment for given table or drops it if +comment+ is a +nil+
  379. 2 def change_table_comment(table_name, comment_or_changes) # :nodoc:
  380. 40 clear_cache!
  381. 40 comment = extract_new_comment_value(comment_or_changes)
  382. 40 execute "COMMENT ON TABLE #{quote_table_name(table_name)} IS #{quote(comment)}"
  383. end
  384. # Renames a column in a table.
  385. 2 def rename_column(table_name, column_name, new_column_name) #:nodoc:
  386. 21 clear_cache!
  387. 21 execute("ALTER TABLE #{quote_table_name(table_name)} #{rename_column_sql(table_name, column_name, new_column_name)}")
  388. 20 rename_column_indexes(table_name, column_name, new_column_name)
  389. end
  390. 2 def add_index(table_name, column_name, **options) #:nodoc:
  391. 503 index, algorithm, if_not_exists = add_index_options(table_name, column_name, **options)
  392. 499 create_index = CreateIndexDefinition.new(index, algorithm, if_not_exists)
  393. 499 result = execute schema_creation.accept(create_index)
  394. 499 execute "COMMENT ON INDEX #{quote_column_name(index.name)} IS #{quote(index.comment)}" if index.comment
  395. 499 result
  396. end
  397. 2 def remove_index(table_name, column_name = nil, **options) # :nodoc:
  398. 55 table = Utils.extract_schema_qualified_name(table_name.to_s)
  399. 55 if options.key?(:name)
  400. 28 provided_index = Utils.extract_schema_qualified_name(options[:name].to_s)
  401. 28 options[:name] = provided_index.identifier
  402. 28 table = PostgreSQL::Name.new(provided_index.schema, table.identifier) unless table.schema.present?
  403. 28 if provided_index.schema.present? && table.schema != provided_index.schema
  404. 2 raise ArgumentError.new("Index schema '#{provided_index.schema}' does not match table schema '#{table.schema}'")
  405. end
  406. end
  407. 53 return if options[:if_exists] && !index_exists?(table_name, column_name, **options)
  408. 52 index_to_remove = PostgreSQL::Name.new(table.schema, index_name_for_remove(table.to_s, column_name, options))
  409. 46 execute "DROP INDEX #{index_algorithm(options[:algorithm])} #{quote_table_name(index_to_remove)}"
  410. end
  411. # Renames an index of a table. Raises error if length of new
  412. # index name is greater than allowed limit.
  413. 2 def rename_index(table_name, old_name, new_name)
  414. 11 validate_index_length!(table_name, new_name)
  415. 10 execute "ALTER INDEX #{quote_column_name(old_name)} RENAME TO #{quote_table_name(new_name)}"
  416. end
  417. 2 def foreign_keys(table_name)
  418. 2263 scope = quoted_scope(table_name)
  419. 2263 fk_info = exec_query(<<~SQL, "SCHEMA")
  420. SELECT t2.oid::regclass::text AS to_table, a1.attname AS column, a2.attname AS primary_key, c.conname AS name, c.confupdtype AS on_update, c.confdeltype AS on_delete, c.convalidated AS valid
  421. FROM pg_constraint c
  422. JOIN pg_class t1 ON c.conrelid = t1.oid
  423. JOIN pg_class t2 ON c.confrelid = t2.oid
  424. JOIN pg_attribute a1 ON a1.attnum = c.conkey[1] AND a1.attrelid = t1.oid
  425. JOIN pg_attribute a2 ON a2.attnum = c.confkey[1] AND a2.attrelid = t2.oid
  426. JOIN pg_namespace t3 ON c.connamespace = t3.oid
  427. WHERE c.contype = 'f'
  428. AND t1.relname = #{scope[:name]}
  429. AND t3.nspname = #{scope[:schema]}
  430. ORDER BY c.conname
  431. SQL
  432. 2263 fk_info.map do |row|
  433. 196 options = {
  434. column: row["column"],
  435. name: row["name"],
  436. primary_key: row["primary_key"]
  437. }
  438. 196 options[:on_delete] = extract_foreign_key_action(row["on_delete"])
  439. 196 options[:on_update] = extract_foreign_key_action(row["on_update"])
  440. 196 options[:validate] = row["valid"]
  441. 196 ForeignKeyDefinition.new(table_name, row["to_table"], options)
  442. end
  443. end
  444. 2 def foreign_tables
  445. 1 query_values(data_source_sql(type: "FOREIGN TABLE"), "SCHEMA")
  446. end
  447. 2 def foreign_table_exists?(table_name)
  448. 5 query_values(data_source_sql(table_name, type: "FOREIGN TABLE"), "SCHEMA").any? if table_name.present?
  449. end
  450. 2 def check_constraints(table_name) # :nodoc:
  451. 2159 scope = quoted_scope(table_name)
  452. 2159 check_info = exec_query(<<-SQL, "SCHEMA")
  453. SELECT conname, pg_get_constraintdef(c.oid) AS constraintdef
  454. FROM pg_constraint c
  455. JOIN pg_class t ON c.conrelid = t.oid
  456. WHERE c.contype = 'c'
  457. AND t.relname = #{scope[:name]}
  458. SQL
  459. 2159 check_info.map do |row|
  460. 19 options = {
  461. name: row["conname"]
  462. }
  463. 19 expression = row["constraintdef"][/CHECK \({2}(.+)\){2}/, 1]
  464. 19 CheckConstraintDefinition.new(table_name, expression, options)
  465. end
  466. end
  467. # Maps logical Rails types to PostgreSQL-specific data types.
  468. 2 def type_to_sql(type, limit: nil, precision: nil, scale: nil, array: nil, **) # :nodoc:
  469. 5266 sql = \
  470. case type.to_s
  471. when "binary"
  472. # PostgreSQL doesn't support limits on binary (bytea) columns.
  473. # The hard limit is 1GB, because of a 32-bit size field, and TOAST.
  474. 39 case limit
  475. 36 when nil, 0..0x3fffffff; super(type)
  476. 3 else raise ArgumentError, "No binary type has byte size #{limit}. The limit on binary can be at most 1GB - 1byte."
  477. end
  478. when "text"
  479. # PostgreSQL doesn't support limits on text columns.
  480. # The hard limit is 1GB, according to section 8.3 in the manual.
  481. 72 case limit
  482. 69 when nil, 0..0x3fffffff; super(type)
  483. 3 else raise ArgumentError, "No text type has byte size #{limit}. The limit on text can be at most 1GB - 1byte."
  484. end
  485. when "integer"
  486. 593 case limit
  487. 6 when 1, 2; "smallint"
  488. 573 when nil, 3, 4; "integer"
  489. 12 when 5..8; "bigint"
  490. 2 else raise ArgumentError, "No integer type has byte size #{limit}. Use a numeric with scale 0 instead."
  491. end
  492. else
  493. 4562 super
  494. end
  495. 5255 sql = "#{sql}[]" if array && type != :primary_key
  496. 5255 sql
  497. end
  498. # PostgreSQL requires the ORDER BY columns in the select list for distinct queries, and
  499. # requires that the ORDER BY include the distinct column.
  500. 2 def columns_for_distinct(columns, orders) #:nodoc:
  501. 95 order_columns = orders.compact_blank.map { |s|
  502. # Convert Arel node to string
  503. 76 s = visitor.compile(s) unless s.is_a?(String)
  504. # Remove any ASC/DESC modifiers
  505. s.gsub(/\s+(?:ASC|DESC)\b/i, "")
  506. 76 .gsub(/\s+NULLS\s+(?:FIRST|LAST)\b/i, "")
  507. 76 }.compact_blank.map.with_index { |column, i| "#{column} AS alias_#{i}" }
  508. 95 (order_columns << super).join(", ")
  509. end
  510. 2 def update_table_definition(table_name, base) # :nodoc:
  511. 119 PostgreSQL::Table.new(table_name, base)
  512. end
  513. 2 def create_schema_dumper(options) # :nodoc:
  514. 109 PostgreSQL::SchemaDumper.create(self, options)
  515. end
  516. # Validates the given constraint.
  517. #
  518. # Validates the constraint named +constraint_name+ on +accounts+.
  519. #
  520. # validate_constraint :accounts, :constraint_name
  521. 2 def validate_constraint(table_name, constraint_name)
  522. 5 return unless supports_validate_constraints?
  523. 5 at = create_alter_table table_name
  524. 5 at.validate_constraint constraint_name
  525. 5 execute schema_creation.accept(at)
  526. end
  527. # Validates the given foreign key.
  528. #
  529. # Validates the foreign key on +accounts.branch_id+.
  530. #
  531. # validate_foreign_key :accounts, :branches
  532. #
  533. # Validates the foreign key on +accounts.owner_id+.
  534. #
  535. # validate_foreign_key :accounts, column: :owner_id
  536. #
  537. # Validates the foreign key named +special_fk_name+ on the +accounts+ table.
  538. #
  539. # validate_foreign_key :accounts, name: :special_fk_name
  540. #
  541. # The +options+ hash accepts the same keys as SchemaStatements#add_foreign_key.
  542. 2 def validate_foreign_key(from_table, to_table = nil, **options)
  543. 5 return unless supports_validate_constraints?
  544. 5 fk_name_to_validate = foreign_key_for!(from_table, to_table: to_table, **options).name
  545. 4 validate_constraint from_table, fk_name_to_validate
  546. end
  547. 2 private
  548. 2 def schema_creation
  549. 2466 PostgreSQL::SchemaCreation.new(self)
  550. end
  551. 2 def create_table_definition(name, **options)
  552. 1973 PostgreSQL::TableDefinition.new(self, name, **options)
  553. end
  554. 2 def create_alter_table(name)
  555. 392 PostgreSQL::AlterTable.new create_table_definition(name)
  556. end
  557. 2 def new_column_from_field(table_name, field)
  558. 20923 column_name, type, default, notnull, oid, fmod, collation, comment = field
  559. 20923 type_metadata = fetch_type_metadata(column_name, type, oid.to_i, fmod.to_i)
  560. 20923 default_value = extract_value_from_default(default)
  561. 20923 default_function = extract_default_function(default_value, default)
  562. 20923 if match = default_function&.match(/\Anextval\('"?(?<sequence_name>.+_(?<suffix>seq\d*))"?'::regclass\)\z/)
  563. 3974 serial = sequence_name_from_parts(table_name, column_name, match[:suffix]) == match[:sequence_name]
  564. end
  565. 20923 PostgreSQL::Column.new(
  566. column_name,
  567. default_value,
  568. type_metadata,
  569. !notnull,
  570. default_function,
  571. collation: collation,
  572. comment: comment.presence,
  573. serial: serial
  574. )
  575. end
  576. 2 def fetch_type_metadata(column_name, sql_type, oid, fmod)
  577. 20923 cast_type = get_oid_type(oid, fmod, column_name, sql_type)
  578. 20923 simple_type = SqlTypeMetadata.new(
  579. sql_type: sql_type,
  580. type: cast_type.type,
  581. limit: cast_type.limit,
  582. precision: cast_type.precision,
  583. scale: cast_type.scale,
  584. )
  585. 20923 PostgreSQL::TypeMetadata.new(simple_type, oid: oid, fmod: fmod)
  586. end
  587. 2 def sequence_name_from_parts(table_name, column_name, suffix)
  588. 3974 over_length = [table_name, column_name, suffix].sum(&:length) + 2 - max_identifier_length
  589. 3974 if over_length > 0
  590. 6 column_name_length = [(max_identifier_length - suffix.length - 2) / 2, column_name.length].min
  591. 6 over_length -= column_name.length - column_name_length
  592. 6 column_name = column_name[0, column_name_length - [over_length, 0].min]
  593. end
  594. 3974 if over_length > 0
  595. 6 table_name = table_name[0, table_name.length - over_length]
  596. end
  597. 3974 "#{table_name}_#{column_name}_#{suffix}"
  598. end
  599. 2 def extract_foreign_key_action(specifier)
  600. 392 case specifier
  601. 16 when "c"; :cascade
  602. 3 when "n"; :nullify
  603. 1 when "r"; :restrict
  604. end
  605. end
  606. 2 def add_column_for_alter(table_name, column_name, type, **options)
  607. 27 return super unless options.key?(:comment)
  608. 2 [super, Proc.new { change_column_comment(table_name, column_name, options[:comment]) }]
  609. end
  610. 2 def change_column_for_alter(table_name, column_name, type, **options)
  611. 35 td = create_table_definition(table_name)
  612. 35 cd = td.new_column_definition(column_name, type, **options)
  613. 35 sqls = [schema_creation.accept(ChangeColumnDefinition.new(cd, column_name))]
  614. 40 sqls << Proc.new { change_column_comment(table_name, column_name, options[:comment]) } if options.key?(:comment)
  615. 35 sqls
  616. end
  617. 2 def change_column_default_for_alter(table_name, column_name, default_or_changes)
  618. 13 column = column_for(table_name, column_name)
  619. 13 return unless column
  620. 13 default = extract_new_default_value(default_or_changes)
  621. 13 alter_column_query = "ALTER COLUMN #{quote_column_name(column_name)} %s"
  622. 13 if default.nil?
  623. # <tt>DEFAULT NULL</tt> results in the same behavior as <tt>DROP DEFAULT</tt>. However, PostgreSQL will
  624. # cast the default to the columns type, which leaves us with a default like "default NULL::character varying".
  625. 3 alter_column_query % "DROP DEFAULT"
  626. else
  627. 10 alter_column_query % "SET DEFAULT #{quote_default_expression(default, column)}"
  628. end
  629. end
  630. 2 def change_column_null_for_alter(table_name, column_name, null, default = nil)
  631. 7 "ALTER COLUMN #{quote_column_name(column_name)} #{null ? 'DROP' : 'SET'} NOT NULL"
  632. end
  633. 2 def add_index_opclass(quoted_columns, **options)
  634. 488 opclasses = options_for_index_columns(options[:opclass])
  635. 488 quoted_columns.each do |name, column|
  636. 548 column << " #{opclasses[name]}" if opclasses[name].present?
  637. end
  638. end
  639. 2 def add_options_for_index_columns(quoted_columns, **options)
  640. 488 quoted_columns = add_index_opclass(quoted_columns, **options)
  641. 488 super
  642. end
  643. 2 def data_source_sql(name = nil, type: nil)
  644. 2219 scope = quoted_scope(name, type: type)
  645. 2219 scope[:type] ||= "'r','v','m','p','f'" # (r)elation/table, (v)iew, (m)aterialized view, (p)artitioned table, (f)oreign table
  646. 2219 sql = +"SELECT c.relname FROM pg_class c LEFT JOIN pg_namespace n ON n.oid = c.relnamespace"
  647. 2219 sql << " WHERE n.nspname = #{scope[:schema]}"
  648. 2219 sql << " AND c.relname = #{scope[:name]}" if scope[:name]
  649. 2219 sql << " AND c.relkind IN (#{scope[:type]})"
  650. 2219 sql
  651. end
  652. 2 def quoted_scope(name = nil, type: nil)
  653. 12186 schema, name = extract_schema_qualified_name(name)
  654. 12186 type = \
  655. case type
  656. when "BASE TABLE"
  657. 3626 "'r','p'"
  658. when "VIEW"
  659. 35 "'v','m'"
  660. when "FOREIGN TABLE"
  661. 5 "'f'"
  662. end
  663. 12186 scope = {}
  664. 12186 scope[:schema] = schema ? quote(schema) : "ANY (current_schemas(false))"
  665. 12186 scope[:name] = quote(name) if name
  666. 12186 scope[:type] = type if type
  667. 12186 scope
  668. end
  669. 2 def extract_schema_qualified_name(string)
  670. 12186 name = Utils.extract_schema_qualified_name(string.to_s)
  671. 12186 [name.schema, name.identifier]
  672. end
  673. end
  674. end
  675. end
  676. end

lib/active_record/connection_adapters/postgresql/type_metadata.rb

100.0% lines covered

21 relevant lines. 21 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 2 module ActiveRecord
  3. # :stopdoc:
  4. 2 module ConnectionAdapters
  5. 2 module PostgreSQL
  6. 2 class TypeMetadata < DelegateClass(SqlTypeMetadata)
  7. 2 undef to_yaml if method_defined?(:to_yaml)
  8. 2 include Deduplicable
  9. 2 attr_reader :oid, :fmod
  10. 2 def initialize(type_metadata, oid: nil, fmod: nil)
  11. 20923 super(type_metadata)
  12. 20923 @oid = oid
  13. 20923 @fmod = fmod
  14. end
  15. 2 def ==(other)
  16. 44915 other.is_a?(TypeMetadata) &&
  17. __getobj__ == other.__getobj__ &&
  18. oid == other.oid &&
  19. fmod == other.fmod
  20. end
  21. 2 alias eql? ==
  22. 2 def hash
  23. TypeMetadata.hash ^
  24. __getobj__.hash ^
  25. 61540 oid.hash ^
  26. fmod.hash
  27. end
  28. 2 private
  29. 2 def deduplicated
  30. 319 __setobj__(__getobj__.deduplicate)
  31. 319 super
  32. end
  33. end
  34. end
  35. 2 PostgreSQLTypeMetadata = PostgreSQL::TypeMetadata
  36. end
  37. end

lib/active_record/connection_adapters/postgresql/utils.rb

100.0% lines covered

35 relevant lines. 35 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 2 module ActiveRecord
  3. 2 module ConnectionAdapters
  4. 2 module PostgreSQL
  5. # Value Object to hold a schema qualified name.
  6. # This is usually the name of a PostgreSQL relation but it can also represent
  7. # schema qualified type names. +schema+ and +identifier+ are unquoted to prevent
  8. # double quoting.
  9. 2 class Name # :nodoc:
  10. 2 SEPARATOR = "."
  11. 2 attr_reader :schema, :identifier
  12. 2 def initialize(schema, identifier)
  13. 15469 @schema, @identifier = unquote(schema), unquote(identifier)
  14. end
  15. 2 def to_s
  16. 258 parts.join SEPARATOR
  17. end
  18. 2 def quoted
  19. 720 if schema
  20. 135 PG::Connection.quote_ident(schema) << SEPARATOR << PG::Connection.quote_ident(identifier)
  21. else
  22. 585 PG::Connection.quote_ident(identifier)
  23. end
  24. end
  25. 2 def ==(o)
  26. 2233 o.class == self.class && o.parts == parts
  27. end
  28. 2 alias_method :eql?, :==
  29. 2 def hash
  30. 2445 parts.hash
  31. end
  32. 2 protected
  33. 2 def parts
  34. 7169 @parts ||= [@schema, @identifier].compact
  35. end
  36. 2 private
  37. 2 def unquote(part)
  38. 30938 if part && part.start_with?('"')
  39. 33 part[1..-2]
  40. else
  41. 30905 part
  42. end
  43. end
  44. end
  45. 2 module Utils # :nodoc:
  46. 2 extend self
  47. # Returns an instance of <tt>ActiveRecord::ConnectionAdapters::PostgreSQL::Name</tt>
  48. # extracted from +string+.
  49. # +schema+ is +nil+ if not specified in +string+.
  50. # +schema+ and +identifier+ exclude surrounding quotes (regardless of whether provided in +string+)
  51. # +string+ supports the range of schema/table references understood by PostgreSQL, for example:
  52. #
  53. # * <tt>table_name</tt>
  54. # * <tt>"table.name"</tt>
  55. # * <tt>schema_name.table_name</tt>
  56. # * <tt>schema_name."table.name"</tt>
  57. # * <tt>"schema_name".table_name</tt>
  58. # * <tt>"schema.name"."table name"</tt>
  59. 2 def extract_schema_qualified_name(string)
  60. 13063 schema, table = string.scan(/[^".]+|"[^"]*"/)
  61. 13063 if table.nil?
  62. 12826 table = schema
  63. 12826 schema = nil
  64. end
  65. 13063 PostgreSQL::Name.new(schema, table)
  66. end
  67. end
  68. end
  69. end
  70. end

lib/active_record/connection_adapters/postgresql_adapter.rb

97.37% lines covered

456 relevant lines. 444 lines covered and 12 lines missed.
    
  1. # frozen_string_literal: true
  2. 2 gem "pg", "~> 1.1"
  3. 2 require "pg"
  4. 2 require "active_support/core_ext/object/try"
  5. 2 require "active_record/connection_adapters/abstract_adapter"
  6. 2 require "active_record/connection_adapters/statement_pool"
  7. 2 require "active_record/connection_adapters/postgresql/column"
  8. 2 require "active_record/connection_adapters/postgresql/database_statements"
  9. 2 require "active_record/connection_adapters/postgresql/explain_pretty_printer"
  10. 2 require "active_record/connection_adapters/postgresql/oid"
  11. 2 require "active_record/connection_adapters/postgresql/quoting"
  12. 2 require "active_record/connection_adapters/postgresql/referential_integrity"
  13. 2 require "active_record/connection_adapters/postgresql/schema_creation"
  14. 2 require "active_record/connection_adapters/postgresql/schema_definitions"
  15. 2 require "active_record/connection_adapters/postgresql/schema_dumper"
  16. 2 require "active_record/connection_adapters/postgresql/schema_statements"
  17. 2 require "active_record/connection_adapters/postgresql/type_metadata"
  18. 2 require "active_record/connection_adapters/postgresql/utils"
  19. 2 module ActiveRecord
  20. 2 module ConnectionHandling # :nodoc:
  21. # Establishes a connection to the database that's used by all Active Record objects
  22. 2 def postgresql_connection(config)
  23. 410 conn_params = config.symbolize_keys.compact
  24. # Map ActiveRecords param names to PGs.
  25. 410 conn_params[:user] = conn_params.delete(:username) if conn_params[:username]
  26. 410 conn_params[:dbname] = conn_params.delete(:database) if conn_params[:database]
  27. # Forward only valid config params to PG::Connection.connect.
  28. 410 valid_conn_param_keys = PG::Connection.conndefaults_hash.keys + [:requiressl]
  29. 410 conn_params.slice!(*valid_conn_param_keys)
  30. 410 conn = PG.connect(conn_params)
  31. 408 ConnectionAdapters::PostgreSQLAdapter.new(conn, logger, conn_params, config)
  32. rescue ::PG::Error => error
  33. 2 if error.message.include?(conn_params[:dbname])
  34. 2 raise ActiveRecord::NoDatabaseError
  35. else
  36. raise
  37. end
  38. end
  39. end
  40. 2 module ConnectionAdapters
  41. # The PostgreSQL adapter works with the native C (https://github.com/ged/ruby-pg) driver.
  42. #
  43. # Options:
  44. #
  45. # * <tt>:host</tt> - Defaults to a Unix-domain socket in /tmp. On machines without Unix-domain sockets,
  46. # the default is to connect to localhost.
  47. # * <tt>:port</tt> - Defaults to 5432.
  48. # * <tt>:username</tt> - Defaults to be the same as the operating system name of the user running the application.
  49. # * <tt>:password</tt> - Password to be used if the server demands password authentication.
  50. # * <tt>:database</tt> - Defaults to be the same as the user name.
  51. # * <tt>:schema_search_path</tt> - An optional schema search path for the connection given
  52. # as a string of comma-separated schema names. This is backward-compatible with the <tt>:schema_order</tt> option.
  53. # * <tt>:encoding</tt> - An optional client encoding that is used in a <tt>SET client_encoding TO
  54. # <encoding></tt> call on the connection.
  55. # * <tt>:min_messages</tt> - An optional client min messages that is used in a
  56. # <tt>SET client_min_messages TO <min_messages></tt> call on the connection.
  57. # * <tt>:variables</tt> - An optional hash of additional parameters that
  58. # will be used in <tt>SET SESSION key = val</tt> calls on the connection.
  59. # * <tt>:insert_returning</tt> - An optional boolean to control the use of <tt>RETURNING</tt> for <tt>INSERT</tt> statements
  60. # defaults to true.
  61. #
  62. # Any further options are used as connection parameters to libpq. See
  63. # https://www.postgresql.org/docs/current/static/libpq-connect.html for the
  64. # list of parameters.
  65. #
  66. # In addition, default connection parameters of libpq can be set per environment variables.
  67. # See https://www.postgresql.org/docs/current/static/libpq-envars.html .
  68. 2 class PostgreSQLAdapter < AbstractAdapter
  69. 2 ADAPTER_NAME = "PostgreSQL"
  70. ##
  71. # :singleton-method:
  72. # PostgreSQL allows the creation of "unlogged" tables, which do not record
  73. # data in the PostgreSQL Write-Ahead Log. This can make the tables faster,
  74. # but significantly increases the risk of data loss if the database
  75. # crashes. As a result, this should not be used in production
  76. # environments. If you would like all created tables to be unlogged in
  77. # the test environment you can add the following line to your test.rb
  78. # file:
  79. #
  80. # ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.create_unlogged_tables = true
  81. 2 class_attribute :create_unlogged_tables, default: false
  82. 2 NATIVE_DATABASE_TYPES = {
  83. primary_key: "bigserial primary key",
  84. string: { name: "character varying" },
  85. text: { name: "text" },
  86. integer: { name: "integer", limit: 4 },
  87. float: { name: "float" },
  88. decimal: { name: "decimal" },
  89. datetime: { name: "timestamp" },
  90. time: { name: "time" },
  91. date: { name: "date" },
  92. daterange: { name: "daterange" },
  93. numrange: { name: "numrange" },
  94. tsrange: { name: "tsrange" },
  95. tstzrange: { name: "tstzrange" },
  96. int4range: { name: "int4range" },
  97. int8range: { name: "int8range" },
  98. binary: { name: "bytea" },
  99. boolean: { name: "boolean" },
  100. xml: { name: "xml" },
  101. tsvector: { name: "tsvector" },
  102. hstore: { name: "hstore" },
  103. inet: { name: "inet" },
  104. cidr: { name: "cidr" },
  105. macaddr: { name: "macaddr" },
  106. uuid: { name: "uuid" },
  107. json: { name: "json" },
  108. jsonb: { name: "jsonb" },
  109. ltree: { name: "ltree" },
  110. citext: { name: "citext" },
  111. point: { name: "point" },
  112. line: { name: "line" },
  113. lseg: { name: "lseg" },
  114. box: { name: "box" },
  115. path: { name: "path" },
  116. polygon: { name: "polygon" },
  117. circle: { name: "circle" },
  118. bit: { name: "bit" },
  119. bit_varying: { name: "bit varying" },
  120. money: { name: "money" },
  121. interval: { name: "interval" },
  122. oid: { name: "oid" },
  123. }
  124. 2 OID = PostgreSQL::OID #:nodoc:
  125. 2 include PostgreSQL::Quoting
  126. 2 include PostgreSQL::ReferentialIntegrity
  127. 2 include PostgreSQL::SchemaStatements
  128. 2 include PostgreSQL::DatabaseStatements
  129. 2 def supports_bulk_alter?
  130. 54 true
  131. end
  132. 2 def supports_index_sort_order?
  133. 490 true
  134. end
  135. 2 def supports_partitioned_indexes?
  136. 7 database_version >= 110_000
  137. end
  138. 2 def supports_partial_index?
  139. 501 true
  140. end
  141. 2 def supports_expression_index?
  142. 6 true
  143. end
  144. 2 def supports_transaction_isolation?
  145. 2 true
  146. end
  147. 2 def supports_foreign_keys?
  148. 1734 true
  149. end
  150. 2 def supports_check_constraints?
  151. 3671 true
  152. end
  153. 2 def supports_validate_constraints?
  154. 11 true
  155. end
  156. 2 def supports_views?
  157. 1 true
  158. end
  159. 2 def supports_datetime_with_precision?
  160. 138 true
  161. end
  162. 2 def supports_json?
  163. true
  164. end
  165. 2 def supports_comments?
  166. 1510 true
  167. end
  168. 2 def supports_savepoints?
  169. 21 true
  170. end
  171. 2 def supports_insert_returning?
  172. 117 true
  173. end
  174. 2 def supports_insert_on_conflict?
  175. 86 database_version >= 90500
  176. end
  177. 2 alias supports_insert_on_duplicate_skip? supports_insert_on_conflict?
  178. 2 alias supports_insert_on_duplicate_update? supports_insert_on_conflict?
  179. 2 alias supports_insert_conflict_target? supports_insert_on_conflict?
  180. 2 def index_algorithms
  181. 11 { concurrently: "CONCURRENTLY" }
  182. end
  183. 2 class StatementPool < ConnectionAdapters::StatementPool # :nodoc:
  184. 2 def initialize(connection, max)
  185. 410 super(max)
  186. 410 @connection = connection
  187. 410 @counter = 0
  188. end
  189. 2 def next_key
  190. 1869 "a#{@counter + 1}"
  191. end
  192. 2 def []=(sql, key)
  193. 3734 super.tap { @counter += 1 }
  194. end
  195. 2 private
  196. 2 def dealloc(key)
  197. 1859 @connection.query "DEALLOCATE #{key}" if connection_active?
  198. rescue PG::Error
  199. end
  200. 2 def connection_active?
  201. 1859 @connection.status == PG::CONNECTION_OK
  202. rescue PG::Error
  203. 35 false
  204. end
  205. end
  206. # Initializes and connects a PostgreSQL adapter.
  207. 2 def initialize(connection, logger, connection_parameters, config)
  208. 408 super(connection, logger, config)
  209. 408 @connection_parameters = connection_parameters
  210. # @local_tz is initialized as nil to avoid warnings when connect tries to use it
  211. 408 @local_tz = nil
  212. 408 @max_identifier_length = nil
  213. 408 configure_connection
  214. 408 add_pg_encoders
  215. 408 add_pg_decoders
  216. 408 @type_map = Type::HashLookupTypeMap.new
  217. 408 initialize_type_map
  218. 408 @local_tz = execute("SHOW TIME ZONE", "SCHEMA").first["TimeZone"]
  219. 408 @use_insert_returning = @config.key?(:insert_returning) ? self.class.type_cast_config_to_boolean(@config[:insert_returning]) : true
  220. end
  221. 2 def self.database_exists?(config)
  222. 2 !!ActiveRecord::Base.postgresql_connection(config)
  223. rescue ActiveRecord::NoDatabaseError
  224. 1 false
  225. end
  226. # Is this connection alive and ready for queries?
  227. 2 def active?
  228. 25381 @lock.synchronize do
  229. 25381 @connection.query "SELECT 1"
  230. end
  231. 25378 true
  232. rescue PG::Error
  233. 3 false
  234. end
  235. # Close then reopen the connection.
  236. 2 def reconnect!
  237. 257 @lock.synchronize do
  238. 257 super
  239. 257 @connection.reset
  240. 255 configure_connection
  241. rescue PG::ConnectionBad
  242. 2 connect
  243. end
  244. end
  245. 2 def reset!
  246. 45 @lock.synchronize do
  247. 45 clear_cache!
  248. 45 reset_transaction
  249. 45 unless @connection.transaction_status == ::PG::PQTRANS_IDLE
  250. 1 @connection.query "ROLLBACK"
  251. end
  252. 45 @connection.query "DISCARD ALL"
  253. 45 configure_connection
  254. end
  255. end
  256. # Disconnects from the database if already connected. Otherwise, this
  257. # method does nothing.
  258. 2 def disconnect!
  259. 259 @lock.synchronize do
  260. 259 super
  261. 259 @connection.close rescue nil
  262. end
  263. end
  264. 2 def discard! # :nodoc:
  265. 7 super
  266. 7 @connection.socket_io.reopen(IO::NULL) rescue nil
  267. 7 @connection = nil
  268. end
  269. 2 def native_database_types #:nodoc:
  270. 14881 NATIVE_DATABASE_TYPES
  271. end
  272. 2 def set_standard_conforming_strings
  273. 710 execute("SET standard_conforming_strings = on", "SCHEMA")
  274. end
  275. 2 def supports_ddl_transactions?
  276. 137 true
  277. end
  278. 2 def supports_advisory_locks?
  279. 117 true
  280. end
  281. 2 def supports_explain?
  282. 2 true
  283. end
  284. 2 def supports_extensions?
  285. 249 true
  286. end
  287. 2 def supports_ranges?
  288. 1 true
  289. end
  290. 2 deprecate :supports_ranges?
  291. 2 def supports_materialized_views?
  292. 1 true
  293. end
  294. 2 def supports_foreign_tables?
  295. 1 true
  296. end
  297. 2 def supports_pgcrypto_uuid?
  298. 42 database_version >= 90400
  299. end
  300. 2 def supports_optimizer_hints?
  301. 2 unless defined?(@has_pg_hint_plan)
  302. 2 @has_pg_hint_plan = extension_available?("pg_hint_plan")
  303. end
  304. 2 @has_pg_hint_plan
  305. end
  306. 2 def supports_common_table_expressions?
  307. 1 true
  308. end
  309. 2 def supports_lazy_transactions?
  310. 31108 true
  311. end
  312. 2 def get_advisory_lock(lock_id) # :nodoc:
  313. 118 unless lock_id.is_a?(Integer) && lock_id.bit_length <= 63
  314. raise(ArgumentError, "PostgreSQL requires advisory lock ids to be a signed 64 bit integer")
  315. end
  316. 118 query_value("SELECT pg_try_advisory_lock(#{lock_id})")
  317. end
  318. 2 def release_advisory_lock(lock_id) # :nodoc:
  319. 118 unless lock_id.is_a?(Integer) && lock_id.bit_length <= 63
  320. raise(ArgumentError, "PostgreSQL requires advisory lock ids to be a signed 64 bit integer")
  321. end
  322. 118 query_value("SELECT pg_advisory_unlock(#{lock_id})")
  323. end
  324. 2 def enable_extension(name)
  325. 112 exec_query("CREATE EXTENSION IF NOT EXISTS \"#{name}\"").tap {
  326. 112 reload_type_map
  327. }
  328. end
  329. 2 def disable_extension(name)
  330. 111 exec_query("DROP EXTENSION IF EXISTS \"#{name}\" CASCADE").tap {
  331. 111 reload_type_map
  332. }
  333. end
  334. 2 def extension_available?(name)
  335. 3 query_value("SELECT true FROM pg_available_extensions WHERE name = #{quote(name)}", "SCHEMA")
  336. end
  337. 2 def extension_enabled?(name)
  338. 259 query_value("SELECT installed_version IS NOT NULL FROM pg_available_extensions WHERE name = #{quote(name)}", "SCHEMA")
  339. end
  340. 2 def extensions
  341. 106 exec_query("SELECT extname FROM pg_extension", "SCHEMA").cast_values
  342. end
  343. # Returns the configured supported identifier length supported by PostgreSQL
  344. 2 def max_identifier_length
  345. 4873 @max_identifier_length ||= query_value("SHOW max_identifier_length", "SCHEMA").to_i
  346. end
  347. # Set the authorized user for this session
  348. 2 def session_auth=(user)
  349. 56 clear_cache!
  350. 56 execute("SET SESSION AUTHORIZATION #{user}")
  351. end
  352. 2 def use_insert_returning?
  353. 4344 @use_insert_returning
  354. end
  355. # Returns the version of the connected PostgreSQL server.
  356. 2 def get_database_version # :nodoc:
  357. 448 @connection.server_version
  358. end
  359. 2 alias :postgresql_version :database_version
  360. 2 def default_index_type?(index) # :nodoc:
  361. 689 index.using == :btree || super
  362. end
  363. 2 def build_insert_sql(insert) # :nodoc:
  364. 56 sql = +"INSERT #{insert.into} #{insert.values_list}"
  365. 55 if insert.skip_duplicates?
  366. 20 sql << " ON CONFLICT #{insert.conflict_target} DO NOTHING"
  367. 35 elsif insert.update_duplicates?
  368. 21 sql << " ON CONFLICT #{insert.conflict_target} DO UPDATE SET "
  369. 91 sql << insert.touch_model_timestamps_unless { |column| "#{insert.model.quoted_table_name}.#{column} IS NOT DISTINCT FROM excluded.#{column}" }
  370. 63 sql << insert.updatable_columns.map { |column| "#{column}=excluded.#{column}" }.join(",")
  371. end
  372. 55 sql << " RETURNING #{insert.returning}" if insert.returning
  373. 55 sql
  374. end
  375. 2 def check_version # :nodoc:
  376. 403 if database_version < 90300
  377. raise "Your version of PostgreSQL (#{database_version}) is too old. Active Record supports PostgreSQL >= 9.3."
  378. end
  379. end
  380. 2 private
  381. # See https://www.postgresql.org/docs/current/static/errcodes-appendix.html
  382. 2 VALUE_LIMIT_VIOLATION = "22001"
  383. 2 NUMERIC_VALUE_OUT_OF_RANGE = "22003"
  384. 2 NOT_NULL_VIOLATION = "23502"
  385. 2 FOREIGN_KEY_VIOLATION = "23503"
  386. 2 UNIQUE_VIOLATION = "23505"
  387. 2 SERIALIZATION_FAILURE = "40001"
  388. 2 DEADLOCK_DETECTED = "40P01"
  389. 2 DUPLICATE_DATABASE = "42P04"
  390. 2 LOCK_NOT_AVAILABLE = "55P03"
  391. 2 QUERY_CANCELED = "57014"
  392. 2 def translate_exception(exception, message:, sql:, binds:)
  393. 1252 return exception unless exception.respond_to?(:result)
  394. 1250 case exception.result.try(:error_field, PG::PG_DIAG_SQLSTATE)
  395. when UNIQUE_VIOLATION
  396. 13 RecordNotUnique.new(message, sql: sql, binds: binds)
  397. when FOREIGN_KEY_VIOLATION
  398. 3 InvalidForeignKey.new(message, sql: sql, binds: binds)
  399. when VALUE_LIMIT_VIOLATION
  400. 3 ValueTooLong.new(message, sql: sql, binds: binds)
  401. when NUMERIC_VALUE_OUT_OF_RANGE
  402. 1 RangeError.new(message, sql: sql, binds: binds)
  403. when NOT_NULL_VIOLATION
  404. 9 NotNullViolation.new(message, sql: sql, binds: binds)
  405. when SERIALIZATION_FAILURE
  406. 1 SerializationFailure.new(message, sql: sql, binds: binds)
  407. when DEADLOCK_DETECTED
  408. 1 Deadlocked.new(message, sql: sql, binds: binds)
  409. when DUPLICATE_DATABASE
  410. DatabaseAlreadyExists.new(message, sql: sql, binds: binds)
  411. when LOCK_NOT_AVAILABLE
  412. 1 LockWaitTimeout.new(message, sql: sql, binds: binds)
  413. when QUERY_CANCELED
  414. 2 QueryCanceled.new(message, sql: sql, binds: binds)
  415. else
  416. 1216 super
  417. end
  418. end
  419. 2 def get_oid_type(oid, fmod, column_name, sql_type = "")
  420. 150165 if !type_map.key?(oid)
  421. 94 load_additional_types([oid])
  422. end
  423. 150165 type_map.fetch(oid, fmod, sql_type) {
  424. 5 warn "unknown OID #{oid}: failed to recognize type of '#{column_name}'. It will be treated as String."
  425. 5 Type.default_value.tap do |cast_type|
  426. 5 type_map.register_type(oid, cast_type)
  427. end
  428. }
  429. end
  430. 2 def initialize_type_map(m = type_map)
  431. 632 m.register_type "int2", Type::Integer.new(limit: 2)
  432. 632 m.register_type "int4", Type::Integer.new(limit: 4)
  433. 632 m.register_type "int8", Type::Integer.new(limit: 8)
  434. 632 m.register_type "oid", OID::Oid.new
  435. 632 m.register_type "float4", Type::Float.new
  436. 632 m.alias_type "float8", "float4"
  437. 632 m.register_type "text", Type::Text.new
  438. 632 register_class_with_limit m, "varchar", Type::String
  439. 632 m.alias_type "char", "varchar"
  440. 632 m.alias_type "name", "varchar"
  441. 632 m.alias_type "bpchar", "varchar"
  442. 632 m.register_type "bool", Type::Boolean.new
  443. 632 register_class_with_limit m, "bit", OID::Bit
  444. 632 register_class_with_limit m, "varbit", OID::BitVarying
  445. 632 m.alias_type "timestamptz", "timestamp"
  446. 632 m.register_type "date", OID::Date.new
  447. 632 m.register_type "money", OID::Money.new
  448. 632 m.register_type "bytea", OID::Bytea.new
  449. 632 m.register_type "point", OID::Point.new
  450. 632 m.register_type "hstore", OID::Hstore.new
  451. 632 m.register_type "json", Type::Json.new
  452. 632 m.register_type "jsonb", OID::Jsonb.new
  453. 632 m.register_type "cidr", OID::Cidr.new
  454. 632 m.register_type "inet", OID::Inet.new
  455. 632 m.register_type "uuid", OID::Uuid.new
  456. 632 m.register_type "xml", OID::Xml.new
  457. 632 m.register_type "tsvector", OID::SpecializedString.new(:tsvector)
  458. 632 m.register_type "macaddr", OID::Macaddr.new
  459. 632 m.register_type "citext", OID::SpecializedString.new(:citext)
  460. 632 m.register_type "ltree", OID::SpecializedString.new(:ltree)
  461. 632 m.register_type "line", OID::SpecializedString.new(:line)
  462. 632 m.register_type "lseg", OID::SpecializedString.new(:lseg)
  463. 632 m.register_type "box", OID::SpecializedString.new(:box)
  464. 632 m.register_type "path", OID::SpecializedString.new(:path)
  465. 632 m.register_type "polygon", OID::SpecializedString.new(:polygon)
  466. 632 m.register_type "circle", OID::SpecializedString.new(:circle)
  467. 632 m.register_type "interval" do |_, _, sql_type|
  468. 10 precision = extract_precision(sql_type)
  469. 10 OID::SpecializedString.new(:interval, precision: precision)
  470. end
  471. 632 register_class_with_precision m, "time", Type::Time
  472. 632 register_class_with_precision m, "timestamp", OID::DateTime
  473. 632 m.register_type "numeric" do |_, fmod, sql_type|
  474. 350 precision = extract_precision(sql_type)
  475. 350 scale = extract_scale(sql_type)
  476. # The type for the numeric depends on the width of the field,
  477. # so we'll do something special here.
  478. #
  479. # When dealing with decimal columns:
  480. #
  481. # places after decimal = fmod - 4 & 0xffff
  482. # places before decimal = (fmod - 4) >> 16 & 0xffff
  483. 350 if fmod && (fmod - 4 & 0xffff).zero?
  484. # FIXME: Remove this class, and the second argument to
  485. # lookups on PG
  486. 28 Type::DecimalWithoutScale.new(precision: precision)
  487. else
  488. 322 OID::Decimal.new(precision: precision, scale: scale)
  489. end
  490. end
  491. 632 load_additional_types
  492. end
  493. # Extracts the value from a PostgreSQL column default definition.
  494. 2 def extract_value_from_default(default)
  495. 20923 case default
  496. # Quoted types
  497. when /\A[\(B]?'(.*)'.*::"?([\w. ]+)"?(?:\[\])?\z/m
  498. # The default 'now'::date is CURRENT_DATE
  499. 591 if $1 == "now" && $2 == "date"
  500. nil
  501. else
  502. 591 $1.gsub("''", "'")
  503. end
  504. # Boolean types
  505. when "true", "false"
  506. 176 default
  507. # Numeric types
  508. when /\A\(?(-?\d+(\.\d*)?)\)?(::bigint)?\z/
  509. 1911 $1
  510. # Object identifier types
  511. when /\A-?\d+\z/
  512. $1
  513. else
  514. # Anything else is blank, some user type, or some function
  515. # and we can't know the value of that, so return nil.
  516. nil
  517. end
  518. end
  519. 2 def extract_default_function(default_value, default)
  520. 20923 default if has_default_function?(default_value, default)
  521. end
  522. 2 def has_default_function?(default_value, default)
  523. 20923 !default_value && %r{\w+\(.*\)|\(.*\)::\w+|CURRENT_DATE|CURRENT_TIMESTAMP}.match?(default)
  524. end
  525. 2 def load_additional_types(oids = nil)
  526. 726 initializer = OID::TypeMapInitializer.new(type_map)
  527. 726 query = <<~SQL
  528. SELECT t.oid, t.typname, t.typelem, t.typdelim, t.typinput, r.rngsubtype, t.typtype, t.typbasetype
  529. FROM pg_type as t
  530. LEFT JOIN pg_range as r ON oid = rngtypid
  531. SQL
  532. 726 if oids
  533. 94 query += "WHERE t.oid IN (%s)" % oids.join(", ")
  534. else
  535. 632 query += initializer.query_conditions_for_initial_load
  536. end
  537. 726 execute_and_clear(query, "SCHEMA", []) do |records|
  538. 726 initializer.run(records)
  539. end
  540. end
  541. 2 FEATURE_NOT_SUPPORTED = "0A000" #:nodoc:
  542. 2 def execute_and_clear(sql, name, binds, prepare: false)
  543. 26409 if preventing_writes? && write_query?(sql)
  544. 10 raise ActiveRecord::ReadOnlyError, "Write query attempted while in readonly mode: #{sql}"
  545. end
  546. 26399 if without_prepared_statement?(binds)
  547. 8750 result = exec_no_cache(sql, name, [])
  548. 17649 elsif !prepare
  549. 8932 result = exec_no_cache(sql, name, binds)
  550. else
  551. 8717 result = exec_cache(sql, name, binds)
  552. end
  553. 26328 begin
  554. 26328 ret = yield result
  555. ensure
  556. 26328 result.clear
  557. end
  558. 26328 ret
  559. end
  560. 2 def exec_no_cache(sql, name, binds)
  561. 17682 materialize_transactions
  562. 17682 mark_transaction_written_if_write(sql)
  563. # make sure we carry over any changes to ActiveRecord::Base.default_timezone that have been
  564. # made since we established the connection
  565. 17682 update_typemap_for_default_timezone
  566. 17682 type_casted_binds = type_casted_binds(binds)
  567. 17682 log(sql, name, binds, type_casted_binds) do
  568. 17682 ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
  569. 17682 @connection.exec_params(sql, type_casted_binds)
  570. end
  571. end
  572. end
  573. 2 def exec_cache(sql, name, binds)
  574. 8717 materialize_transactions
  575. 8717 mark_transaction_written_if_write(sql)
  576. 8717 update_typemap_for_default_timezone
  577. 8717 stmt_key = prepare_statement(sql, binds)
  578. 8713 type_casted_binds = type_casted_binds(binds)
  579. 8707 log(sql, name, binds, type_casted_binds, stmt_key) do
  580. 8707 ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
  581. 8707 @connection.exec_prepared(stmt_key, type_casted_binds)
  582. end
  583. end
  584. rescue ActiveRecord::StatementInvalid => e
  585. 8 raise unless is_cached_plan_failure?(e)
  586. # Nothing we can do if we are in a transaction because all commands
  587. # will raise InFailedSQLTransaction
  588. 2 if in_transaction?
  589. 2 raise ActiveRecord::PreparedStatementCacheExpired.new(e.cause.message)
  590. else
  591. @lock.synchronize do
  592. # outside of transactions we can simply flush this query and retry
  593. @statements.delete sql_key(sql)
  594. end
  595. retry
  596. end
  597. end
  598. # Annoyingly, the code for prepared statements whose return value may
  599. # have changed is FEATURE_NOT_SUPPORTED.
  600. #
  601. # This covers various different error types so we need to do additional
  602. # work to classify the exception definitively as a
  603. # ActiveRecord::PreparedStatementCacheExpired
  604. #
  605. # Check here for more details:
  606. # https://git.postgresql.org/gitweb/?p=postgresql.git;a=blob;f=src/backend/utils/cache/plancache.c#l573
  607. 2 def is_cached_plan_failure?(e)
  608. 8 pgerror = e.cause
  609. 8 pgerror.result.result_error_field(PG::PG_DIAG_SQLSTATE) == FEATURE_NOT_SUPPORTED &&
  610. pgerror.result.result_error_field(PG::PG_DIAG_SOURCE_FUNCTION) == "RevalidateCachedQuery"
  611. rescue
  612. false
  613. end
  614. 2 def in_transaction?
  615. 2 open_transactions > 0
  616. end
  617. # Returns the statement identifier for the client side cache
  618. # of statements
  619. 2 def sql_key(sql)
  620. 8728 "#{schema_search_path}-#{sql}"
  621. end
  622. # Prepare the statement if it hasn't been prepared, return
  623. # the statement key.
  624. 2 def prepare_statement(sql, binds)
  625. 8717 @lock.synchronize do
  626. 8717 sql_key = sql_key(sql)
  627. 8717 unless @statements.key? sql_key
  628. 1869 nextkey = @statements.next_key
  629. 1869 begin
  630. 1869 @connection.prepare nextkey, sql
  631. rescue => e
  632. 4 raise translate_exception_class(e, sql, binds)
  633. end
  634. # Clear the queue
  635. 1865 @connection.get_last_result
  636. 1865 @statements[sql_key] = nextkey
  637. end
  638. 8713 @statements[sql_key]
  639. end
  640. end
  641. # Connects to a PostgreSQL server and sets up the adapter depending on the
  642. # connected server's characteristics.
  643. 2 def connect
  644. 2 @connection = PG.connect(@connection_parameters)
  645. 2 configure_connection
  646. 2 add_pg_encoders
  647. 2 add_pg_decoders
  648. end
  649. # Configures the encoding, verbosity, schema search path, and time zone of the connection.
  650. # This is called by #connect and should not be called manually.
  651. 2 def configure_connection
  652. 710 if @config[:encoding]
  653. @connection.set_client_encoding(@config[:encoding])
  654. end
  655. 710 self.client_min_messages = @config[:min_messages] || "warning"
  656. 710 self.schema_search_path = @config[:schema_search_path] || @config[:schema_order]
  657. # Use standard-conforming strings so we don't have to do the E'...' dance.
  658. 710 set_standard_conforming_strings
  659. 710 variables = @config.fetch(:variables, {}).stringify_keys
  660. # If using Active Record's time zone support configure the connection to return
  661. # TIMESTAMP WITH ZONE types in UTC.
  662. 710 unless variables["timezone"]
  663. 709 if ActiveRecord::Base.default_timezone == :utc
  664. 708 variables["timezone"] = "UTC"
  665. 1 elsif @local_tz
  666. 1 variables["timezone"] = @local_tz
  667. end
  668. end
  669. # SET statements from :variables config hash
  670. # https://www.postgresql.org/docs/current/static/sql-set.html
  671. 710 variables.map do |k, v|
  672. 714 if v == ":default" || v == :default
  673. # Sets the value to the global or compile default
  674. 1 execute("SET SESSION #{k} TO DEFAULT", "SCHEMA")
  675. 713 elsif !v.nil?
  676. 712 execute("SET SESSION #{k} TO #{quote(v)}", "SCHEMA")
  677. end
  678. end
  679. end
  680. # Returns the list of a table's column names, data types, and default values.
  681. #
  682. # The underlying query is roughly:
  683. # SELECT column.name, column.type, default.value, column.comment
  684. # FROM column LEFT JOIN default
  685. # ON column.table_id = default.table_id
  686. # AND column.num = default.column_num
  687. # WHERE column.table_id = get_table_id('table_name')
  688. # AND column.num > 0
  689. # AND NOT column.is_dropped
  690. # ORDER BY column.num
  691. #
  692. # If the table name is not prefixed with a schema, the database will
  693. # take the first match from the schema search path.
  694. #
  695. # Query implementation notes:
  696. # - format_type includes the column size constraint, e.g. varchar(50)
  697. # - ::regclass is a function that gives the id for a table name
  698. 2 def column_definitions(table_name)
  699. 4657 query(<<~SQL, "SCHEMA")
  700. SELECT a.attname, format_type(a.atttypid, a.atttypmod),
  701. pg_get_expr(d.adbin, d.adrelid), a.attnotnull, a.atttypid, a.atttypmod,
  702. c.collname, col_description(a.attrelid, a.attnum) AS comment
  703. FROM pg_attribute a
  704. LEFT JOIN pg_attrdef d ON a.attrelid = d.adrelid AND a.attnum = d.adnum
  705. LEFT JOIN pg_type t ON a.atttypid = t.oid
  706. LEFT JOIN pg_collation c ON a.attcollation = c.oid AND a.attcollation <> t.typcollation
  707. WHERE a.attrelid = #{quote(quote_table_name(table_name))}::regclass
  708. AND a.attnum > 0 AND NOT a.attisdropped
  709. ORDER BY a.attnum
  710. SQL
  711. end
  712. 2 def extract_table_ref_from_insert_sql(sql)
  713. 17 sql[/into\s("[A-Za-z0-9_."\[\]\s]+"|[A-Za-z0-9_."\[\]]+)\s*/im]
  714. 17 $1.strip if $1
  715. end
  716. 2 def arel_visitor
  717. 408 Arel::Visitors::PostgreSQL.new(self)
  718. end
  719. 2 def build_statement_pool
  720. 408 StatementPool.new(@connection, self.class.type_cast_config_to_integer(@config[:statement_limit]))
  721. end
  722. 2 def can_perform_case_insensitive_comparison_for?(column)
  723. 27 @case_insensitive_cache ||= {}
  724. 27 @case_insensitive_cache[column.sql_type] ||= begin
  725. 13 sql = <<~SQL
  726. SELECT exists(
  727. SELECT * FROM pg_proc
  728. WHERE proname = 'lower'
  729. AND proargtypes = ARRAY[#{quote column.sql_type}::regtype]::oidvector
  730. ) OR exists(
  731. SELECT * FROM pg_proc
  732. INNER JOIN pg_cast
  733. ON ARRAY[casttarget]::oidvector = proargtypes
  734. WHERE proname = 'lower'
  735. AND castsource = #{quote column.sql_type}::regtype
  736. )
  737. SQL
  738. 13 execute_and_clear(sql, "SCHEMA", []) do |result|
  739. 13 result.getvalue(0, 0)
  740. end
  741. end
  742. end
  743. 2 def add_pg_encoders
  744. 410 map = PG::TypeMapByClass.new
  745. 410 map[Integer] = PG::TextEncoder::Integer.new
  746. 410 map[TrueClass] = PG::TextEncoder::Boolean.new
  747. 410 map[FalseClass] = PG::TextEncoder::Boolean.new
  748. 410 @connection.type_map_for_queries = map
  749. end
  750. 2 def update_typemap_for_default_timezone
  751. 26809 if @default_timezone != ActiveRecord::Base.default_timezone && @timestamp_decoder
  752. 430 decoder_class = ActiveRecord::Base.default_timezone == :utc ?
  753. PG::TextDecoder::TimestampUtc :
  754. PG::TextDecoder::TimestampWithoutTimeZone
  755. 430 @timestamp_decoder = decoder_class.new(@timestamp_decoder.to_h)
  756. 430 @connection.type_map_for_results.add_coder(@timestamp_decoder)
  757. 430 @default_timezone = ActiveRecord::Base.default_timezone
  758. end
  759. end
  760. 2 def add_pg_decoders
  761. 410 @default_timezone = nil
  762. 410 @timestamp_decoder = nil
  763. 410 coders_by_name = {
  764. "int2" => PG::TextDecoder::Integer,
  765. "int4" => PG::TextDecoder::Integer,
  766. "int8" => PG::TextDecoder::Integer,
  767. "oid" => PG::TextDecoder::Integer,
  768. "float4" => PG::TextDecoder::Float,
  769. "float8" => PG::TextDecoder::Float,
  770. "numeric" => PG::TextDecoder::Numeric,
  771. "bool" => PG::TextDecoder::Boolean,
  772. "timestamp" => PG::TextDecoder::TimestampUtc,
  773. "timestamptz" => PG::TextDecoder::TimestampWithTimeZone,
  774. }
  775. 4510 known_coder_types = coders_by_name.keys.map { |n| quote(n) }
  776. 410 query = <<~SQL % known_coder_types.join(", ")
  777. SELECT t.oid, t.typname
  778. FROM pg_type as t
  779. WHERE t.typname IN (%s)
  780. SQL
  781. 410 coders = execute_and_clear(query, "SCHEMA", []) do |result|
  782. result
  783. 4100 .map { |row| construct_coder(row, coders_by_name[row["typname"]]) }
  784. 410 .compact
  785. end
  786. 410 map = PG::TypeMapByOid.new
  787. 4510 coders.each { |coder| map.add_coder(coder) }
  788. 410 @connection.type_map_for_results = map
  789. 410 @type_map_for_results = PG::TypeMapByOid.new
  790. 410 @type_map_for_results.default_type_map = map
  791. 410 @type_map_for_results.add_coder(PG::TextDecoder::Bytea.new(oid: 17, name: "bytea"))
  792. 410 @type_map_for_results.add_coder(MoneyDecoder.new(oid: 790, name: "money"))
  793. # extract timestamp decoder for use in update_typemap_for_default_timezone
  794. 3690 @timestamp_decoder = coders.find { |coder| coder.name == "timestamp" }
  795. 410 update_typemap_for_default_timezone
  796. end
  797. 2 def construct_coder(row, coder_class)
  798. 4100 return unless coder_class
  799. 4100 coder_class.new(oid: row["oid"].to_i, name: row["typname"])
  800. end
  801. 2 class MoneyDecoder < PG::SimpleDecoder # :nodoc:
  802. 2 TYPE = OID::Money.new
  803. 2 def decode(value, tuple = nil, field = nil)
  804. 2 TYPE.deserialize(value)
  805. end
  806. end
  807. 2 ActiveRecord::Type.add_modifier({ array: true }, OID::Array, adapter: :postgresql)
  808. 2 ActiveRecord::Type.add_modifier({ range: true }, OID::Range, adapter: :postgresql)
  809. 2 ActiveRecord::Type.register(:bit, OID::Bit, adapter: :postgresql)
  810. 2 ActiveRecord::Type.register(:bit_varying, OID::BitVarying, adapter: :postgresql)
  811. 2 ActiveRecord::Type.register(:binary, OID::Bytea, adapter: :postgresql)
  812. 2 ActiveRecord::Type.register(:cidr, OID::Cidr, adapter: :postgresql)
  813. 2 ActiveRecord::Type.register(:date, OID::Date, adapter: :postgresql)
  814. 2 ActiveRecord::Type.register(:datetime, OID::DateTime, adapter: :postgresql)
  815. 2 ActiveRecord::Type.register(:decimal, OID::Decimal, adapter: :postgresql)
  816. 2 ActiveRecord::Type.register(:enum, OID::Enum, adapter: :postgresql)
  817. 2 ActiveRecord::Type.register(:hstore, OID::Hstore, adapter: :postgresql)
  818. 2 ActiveRecord::Type.register(:inet, OID::Inet, adapter: :postgresql)
  819. 2 ActiveRecord::Type.register(:jsonb, OID::Jsonb, adapter: :postgresql)
  820. 2 ActiveRecord::Type.register(:money, OID::Money, adapter: :postgresql)
  821. 2 ActiveRecord::Type.register(:point, OID::Point, adapter: :postgresql)
  822. 2 ActiveRecord::Type.register(:legacy_point, OID::LegacyPoint, adapter: :postgresql)
  823. 2 ActiveRecord::Type.register(:uuid, OID::Uuid, adapter: :postgresql)
  824. 2 ActiveRecord::Type.register(:vector, OID::Vector, adapter: :postgresql)
  825. 2 ActiveRecord::Type.register(:xml, OID::Xml, adapter: :postgresql)
  826. end
  827. end
  828. end

lib/active_record/connection_adapters/schema_cache.rb

95.38% lines covered

130 relevant lines. 124 lines covered and 6 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 require "active_support/core_ext/file/atomic"
  3. 3 module ActiveRecord
  4. 3 module ConnectionAdapters
  5. 3 class SchemaCache
  6. 3 def self.load_from(filename)
  7. 21 return unless File.file?(filename)
  8. 21 read(filename) do |file|
  9. 21 filename.include?(".dump") ? Marshal.load(file) : YAML.load(file)
  10. end
  11. end
  12. 3 def self.read(filename, &block)
  13. 21 if File.extname(filename) == ".gz"
  14. 6 Zlib::GzipReader.open(filename) { |gz|
  15. 6 yield gz.read
  16. }
  17. else
  18. 15 yield File.read(filename)
  19. end
  20. end
  21. 3 private_class_method :read
  22. 3 attr_reader :version
  23. 3 attr_accessor :connection
  24. 3 def initialize(conn)
  25. 1154 @connection = conn
  26. 1154 @columns = {}
  27. 1154 @columns_hash = {}
  28. 1154 @primary_keys = {}
  29. 1154 @data_sources = {}
  30. 1154 @indexes = {}
  31. end
  32. 3 def initialize_dup(other)
  33. super
  34. @columns = @columns.dup
  35. @columns_hash = @columns_hash.dup
  36. @primary_keys = @primary_keys.dup
  37. @data_sources = @data_sources.dup
  38. @indexes = @indexes.dup
  39. end
  40. 3 def encode_with(coder)
  41. 9 reset_version!
  42. 9 coder["columns"] = @columns
  43. 9 coder["primary_keys"] = @primary_keys
  44. 9 coder["data_sources"] = @data_sources
  45. 9 coder["indexes"] = @indexes
  46. 9 coder["version"] = @version
  47. 9 coder["database_version"] = database_version
  48. end
  49. 3 def init_with(coder)
  50. 18 @columns = coder["columns"]
  51. 18 @primary_keys = coder["primary_keys"]
  52. 18 @data_sources = coder["data_sources"]
  53. 18 @indexes = coder["indexes"] || {}
  54. 18 @version = coder["version"]
  55. 18 @database_version = coder["database_version"]
  56. 18 derive_columns_hash_and_deduplicate_values
  57. end
  58. 3 def primary_keys(table_name)
  59. 5148 @primary_keys.fetch(table_name) do
  60. 3933 if data_source_exists?(table_name)
  61. 3930 @primary_keys[deep_deduplicate(table_name)] = deep_deduplicate(connection.primary_key(table_name))
  62. end
  63. end
  64. end
  65. # A cached lookup for table existence.
  66. 3 def data_source_exists?(name)
  67. 13944 prepare_data_sources if @data_sources.empty?
  68. 13944 return @data_sources[name] if @data_sources.key? name
  69. 960 @data_sources[deep_deduplicate(name)] = connection.data_source_exists?(name)
  70. end
  71. # Add internal cache for table with +table_name+.
  72. 3 def add(table_name)
  73. 2958 if data_source_exists?(table_name)
  74. 2957 primary_keys(table_name)
  75. 2957 columns(table_name)
  76. 2957 columns_hash(table_name)
  77. 2957 indexes(table_name)
  78. end
  79. end
  80. 3 def data_sources(name)
  81. 27 @data_sources[name]
  82. end
  83. # Get the columns for a table
  84. 3 def columns(table_name)
  85. 8971 @columns.fetch(table_name) do
  86. 5972 @columns[deep_deduplicate(table_name)] = deep_deduplicate(connection.columns(table_name))
  87. end
  88. end
  89. # Get the columns for a table as a hash, key is the column name
  90. # value is the column object.
  91. 3 def columns_hash(table_name)
  92. 160901 @columns_hash.fetch(table_name) do
  93. 5969 @columns_hash[deep_deduplicate(table_name)] = columns(table_name).index_by(&:name).freeze
  94. end
  95. end
  96. # Checks whether the columns hash is already cached for a table.
  97. 3 def columns_hash?(table_name)
  98. 12 @columns_hash.key?(table_name)
  99. end
  100. 3 def indexes(table_name)
  101. 3169 @indexes.fetch(table_name) do
  102. 2957 @indexes[deep_deduplicate(table_name)] = deep_deduplicate(connection.indexes(table_name))
  103. end
  104. end
  105. 3 def database_version # :nodoc:
  106. 1227 @database_version ||= connection.get_database_version
  107. end
  108. # Clears out internal caches
  109. 3 def clear!
  110. 299 @columns.clear
  111. 299 @columns_hash.clear
  112. 299 @primary_keys.clear
  113. 299 @data_sources.clear
  114. 299 @indexes.clear
  115. 299 @version = nil
  116. 299 @database_version = nil
  117. end
  118. 3 def size
  119. 21 [@columns, @columns_hash, @primary_keys, @data_sources].sum(&:size)
  120. end
  121. # Clear out internal caches for the data source +name+.
  122. 3 def clear_data_source_cache!(name)
  123. 15752 @columns.delete name
  124. 15752 @columns_hash.delete name
  125. 15752 @primary_keys.delete name
  126. 15752 @data_sources.delete name
  127. 15752 @indexes.delete name
  128. end
  129. 3 def dump_to(filename)
  130. 15 clear!
  131. 2943 connection.data_sources.each { |table| add(table) }
  132. 15 open(filename) { |f|
  133. 15 if filename.include?(".dump")
  134. 6 f.write(Marshal.dump(self))
  135. else
  136. 9 f.write(YAML.dump(self))
  137. end
  138. }
  139. end
  140. 3 def marshal_dump
  141. 9 reset_version!
  142. 9 [@version, @columns, {}, @primary_keys, @data_sources, @indexes, database_version]
  143. end
  144. 3 def marshal_load(array)
  145. 12 @version, @columns, _columns_hash, @primary_keys, @data_sources, @indexes, @database_version = array
  146. 12 @indexes ||= {}
  147. 12 derive_columns_hash_and_deduplicate_values
  148. end
  149. 3 private
  150. 3 def reset_version!
  151. 18 @version = connection.migration_context.current_version
  152. end
  153. 3 def derive_columns_hash_and_deduplicate_values
  154. 30 @columns = deep_deduplicate(@columns)
  155. 3558 @columns_hash = @columns.transform_values { |columns| columns.index_by(&:name) }
  156. 30 @primary_keys = deep_deduplicate(@primary_keys)
  157. 30 @data_sources = deep_deduplicate(@data_sources)
  158. 30 @indexes = deep_deduplicate(@indexes)
  159. end
  160. 3 def deep_deduplicate(value)
  161. 110723 case value
  162. when Hash
  163. 32552 value.transform_keys { |k| deep_deduplicate(k) }.transform_values { |v| deep_deduplicate(v) }
  164. when Array
  165. 61539 value.map { |i| deep_deduplicate(i) }
  166. when String, Deduplicable
  167. 85900 -value
  168. else
  169. 8688 value
  170. end
  171. end
  172. 3 def prepare_data_sources
  173. 58477 connection.data_sources.each { |source| @data_sources[source] = true }
  174. end
  175. 3 def open(filename)
  176. 15 File.atomic_write(filename) do |file|
  177. 15 if File.extname(filename) == ".gz"
  178. 6 zipper = Zlib::GzipWriter.new file
  179. 6 yield zipper
  180. 6 zipper.flush
  181. 6 zipper.close
  182. else
  183. 9 yield file
  184. end
  185. end
  186. end
  187. end
  188. end
  189. end

lib/active_record/connection_adapters/sql_type_metadata.rb

100.0% lines covered

21 relevant lines. 21 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 require "active_record/connection_adapters/deduplicable"
  3. 3 module ActiveRecord
  4. # :stopdoc:
  5. 3 module ConnectionAdapters
  6. 3 class SqlTypeMetadata
  7. 3 include Deduplicable
  8. 3 attr_reader :sql_type, :type, :limit, :precision, :scale
  9. 3 def initialize(sql_type: nil, type: nil, limit: nil, precision: nil, scale: nil)
  10. 247314 @sql_type = sql_type
  11. 247314 @type = type
  12. 247314 @limit = limit
  13. 247314 @precision = precision
  14. 247314 @scale = scale
  15. end
  16. 3 def ==(other)
  17. 525938 other.is_a?(SqlTypeMetadata) &&
  18. sql_type == other.sql_type &&
  19. type == other.type &&
  20. limit == other.limit &&
  21. precision == other.precision &&
  22. scale == other.scale
  23. end
  24. 3 alias eql? ==
  25. 3 def hash
  26. SqlTypeMetadata.hash ^
  27. sql_type.hash ^
  28. type.hash ^
  29. limit.hash ^
  30. 564641 precision.hash >> 1 ^
  31. scale.hash >> 2
  32. end
  33. 3 private
  34. 3 def deduplicated
  35. 235 @sql_type = -sql_type
  36. 235 super
  37. end
  38. end
  39. end
  40. end

lib/active_record/connection_adapters/sqlite3/database_statements.rb

98.77% lines covered

81 relevant lines. 80 lines covered and 1 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 module ActiveRecord
  3. 3 module ConnectionAdapters
  4. 3 module SQLite3
  5. 3 module DatabaseStatements
  6. 3 READ_QUERY = ActiveRecord::ConnectionAdapters::AbstractAdapter.build_read_query_regexp(
  7. :pragma
  8. ) # :nodoc:
  9. 3 private_constant :READ_QUERY
  10. 3 def write_query?(sql) # :nodoc:
  11. 71257 !READ_QUERY.match?(sql)
  12. end
  13. 3 def explain(arel, binds = [])
  14. 10 sql = "EXPLAIN QUERY PLAN #{to_sql(arel, binds)}"
  15. 10 SQLite3::ExplainPrettyPrinter.new.pp(exec_query(sql, "EXPLAIN", []))
  16. end
  17. 3 def execute(sql, name = nil) #:nodoc:
  18. 48455 if preventing_writes? && write_query?(sql)
  19. 8 raise ActiveRecord::ReadOnlyError, "Write query attempted while in readonly mode: #{sql}"
  20. end
  21. 48447 materialize_transactions
  22. 48447 mark_transaction_written_if_write(sql)
  23. 48447 log(sql, name) do
  24. 48447 ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
  25. 48447 @connection.execute(sql)
  26. end
  27. end
  28. end
  29. 3 def exec_query(sql, name = nil, binds = [], prepare: false)
  30. 141898 if preventing_writes? && write_query?(sql)
  31. 18 raise ActiveRecord::ReadOnlyError, "Write query attempted while in readonly mode: #{sql}"
  32. end
  33. 141880 materialize_transactions
  34. 141880 mark_transaction_written_if_write(sql)
  35. 141880 type_casted_binds = type_casted_binds(binds)
  36. 141870 log(sql, name, binds, type_casted_binds) do
  37. 141870 ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
  38. # Don't cache statements if they are not prepared
  39. 141870 unless prepare
  40. 124326 stmt = @connection.prepare(sql)
  41. 124290 begin
  42. 124290 cols = stmt.columns
  43. 124290 unless without_prepared_statement?(binds)
  44. 16814 stmt.bind_params(type_casted_binds)
  45. end
  46. 124290 records = stmt.to_a
  47. ensure
  48. 124290 stmt.close
  49. end
  50. else
  51. 17544 stmt = @statements[sql] ||= @connection.prepare(sql)
  52. 17536 cols = stmt.columns
  53. 17536 stmt.reset!
  54. 17536 stmt.bind_params(type_casted_binds)
  55. 17536 records = stmt.to_a
  56. end
  57. 141784 build_result(columns: cols, rows: records)
  58. end
  59. end
  60. end
  61. 3 def exec_delete(sql, name = "SQL", binds = [])
  62. 4975 exec_query(sql, name, binds)
  63. 4929 @connection.changes
  64. end
  65. 3 alias :exec_update :exec_delete
  66. 3 def begin_isolated_db_transaction(isolation) #:nodoc
  67. 14 raise TransactionIsolationError, "SQLite3 only supports the `read_uncommitted` transaction isolation level" if isolation != :read_uncommitted
  68. 10 raise StandardError, "You need to enable the shared-cache mode in SQLite mode before attempting to change the transaction isolation level" unless shared_cache?
  69. 8 Thread.current.thread_variable_set("read_uncommitted", @connection.get_first_value("PRAGMA read_uncommitted"))
  70. 8 @connection.read_uncommitted = true
  71. 8 begin_db_transaction
  72. end
  73. 3 def begin_db_transaction #:nodoc:
  74. 111116 log("begin transaction", "TRANSACTION") { @connection.transaction }
  75. end
  76. 3 def commit_db_transaction #:nodoc:
  77. 6320 log("commit transaction", "TRANSACTION") { @connection.commit }
  78. 3160 reset_read_uncommitted
  79. end
  80. 3 def exec_rollback_db_transaction #:nodoc:
  81. 104740 log("rollback transaction", "TRANSACTION") { @connection.rollback }
  82. 52370 reset_read_uncommitted
  83. end
  84. 3 private
  85. 3 def reset_read_uncommitted
  86. 55530 read_uncommitted = Thread.current.thread_variable_get("read_uncommitted")
  87. 55530 return unless read_uncommitted
  88. 42163 @connection.read_uncommitted = read_uncommitted
  89. end
  90. 3 def execute_batch(statements, name = nil)
  91. 940 sql = combine_multi_statements(statements)
  92. 940 if preventing_writes? && write_query?(sql)
  93. raise ActiveRecord::ReadOnlyError, "Write query attempted while in readonly mode: #{sql}"
  94. end
  95. 940 materialize_transactions
  96. 940 mark_transaction_written_if_write(sql)
  97. 940 log(sql, name) do
  98. 940 ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
  99. 940 @connection.execute_batch2(sql)
  100. end
  101. end
  102. end
  103. 3 def last_inserted_id(result)
  104. 7777 @connection.last_insert_row_id
  105. end
  106. 3 def build_fixture_statements(fixture_set)
  107. fixture_set.flat_map do |table_name, fixtures|
  108. 4061 next if fixtures.empty?
  109. 154818 fixtures.map { |fixture| build_fixture_sql([fixture], table_name) }
  110. 935 end.compact
  111. end
  112. 3 def build_truncate_statement(table_name)
  113. 586 "DELETE FROM #{quote_table_name(table_name)}"
  114. end
  115. end
  116. end
  117. end
  118. end

lib/active_record/connection_adapters/sqlite3/explain_pretty_printer.rb

100.0% lines covered

7 relevant lines. 7 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 module ActiveRecord
  3. 3 module ConnectionAdapters
  4. 3 module SQLite3
  5. 3 class ExplainPrettyPrinter # :nodoc:
  6. # Pretty prints the result of an EXPLAIN QUERY PLAN in a way that resembles
  7. # the output of the SQLite shell:
  8. #
  9. # 0|0|0|SEARCH TABLE users USING INTEGER PRIMARY KEY (rowid=?) (~1 rows)
  10. # 0|1|1|SCAN TABLE posts (~100000 rows)
  11. #
  12. 3 def pp(result)
  13. result.rows.map do |row|
  14. 10 row.join("|")
  15. 10 end.join("\n") + "\n"
  16. end
  17. end
  18. end
  19. end
  20. end

lib/active_record/connection_adapters/sqlite3/quoting.rb

97.5% lines covered

40 relevant lines. 39 lines covered and 1 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 module ActiveRecord
  3. 3 module ConnectionAdapters
  4. 3 module SQLite3
  5. 3 module Quoting # :nodoc:
  6. 3 def quote_string(s)
  7. 97748 @connection.class.quote(s)
  8. end
  9. 3 def quote_table_name_for_assignment(table, attr)
  10. quote_column_name(attr)
  11. end
  12. 3 def quote_table_name(name)
  13. 342066 self.class.quoted_table_names[name] ||= super.gsub(".", "\".\"").freeze
  14. end
  15. 3 def quote_column_name(name)
  16. 584325 self.class.quoted_column_names[name] ||= %Q("#{super.gsub('"', '""')}")
  17. end
  18. 3 def quoted_time(value)
  19. 385 value = value.change(year: 2000, month: 1, day: 1)
  20. 385 quoted_date(value).sub(/\A\d\d\d\d-\d\d-\d\d /, "2000-01-01 ")
  21. end
  22. 3 def quoted_binary(value)
  23. 136 "x'#{value.hex}'"
  24. end
  25. 3 def quoted_true
  26. 1548 "1"
  27. end
  28. 3 def unquoted_true
  29. 277 1
  30. end
  31. 3 def quoted_false
  32. 487 "0"
  33. end
  34. 3 def unquoted_false
  35. 198 0
  36. end
  37. 3 def column_name_matcher
  38. 1238 COLUMN_NAME
  39. end
  40. 3 def column_name_with_order_matcher
  41. 9035 COLUMN_NAME_WITH_ORDER
  42. end
  43. 3 COLUMN_NAME = /
  44. \A
  45. (
  46. (?:
  47. # "table_name"."column_name" | function(one or no argument)
  48. ((?:\w+\.|"\w+"\.)?(?:\w+|"\w+")) | \w+\((?:|\g<2>)\)
  49. )
  50. (?:(?:\s+AS)?\s+(?:\w+|"\w+"))?
  51. )
  52. (?:\s*,\s*\g<1>)*
  53. \z
  54. /ix
  55. 3 COLUMN_NAME_WITH_ORDER = /
  56. \A
  57. (
  58. (?:
  59. # "table_name"."column_name" | function(one or no argument)
  60. ((?:\w+\.|"\w+"\.)?(?:\w+|"\w+")) | \w+\((?:|\g<2>)\)
  61. )
  62. (?:\s+ASC|\s+DESC)?
  63. )
  64. (?:\s*,\s*\g<1>)*
  65. \z
  66. /ix
  67. 3 private_constant :COLUMN_NAME, :COLUMN_NAME_WITH_ORDER
  68. 3 private
  69. 3 def _type_cast(value)
  70. 79567 case value
  71. when BigDecimal
  72. 30 value.to_f
  73. when String
  74. 17560 if value.encoding == Encoding::ASCII_8BIT
  75. 6 super(value.encode(Encoding::UTF_8))
  76. else
  77. 17554 super
  78. end
  79. else
  80. 61977 super
  81. end
  82. end
  83. end
  84. end
  85. end
  86. end

lib/active_record/connection_adapters/sqlite3/schema_creation.rb

100.0% lines covered

11 relevant lines. 11 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 module ActiveRecord
  3. 3 module ConnectionAdapters
  4. 3 module SQLite3
  5. 3 class SchemaCreation < SchemaCreation # :nodoc:
  6. 3 private
  7. 3 def supports_index_using?
  8. 9844 false
  9. end
  10. 3 def add_column_options!(sql, options)
  11. 39875 if options[:collation]
  12. 46 sql << " COLLATE \"#{options[:collation]}\""
  13. end
  14. 39875 super
  15. end
  16. end
  17. end
  18. end
  19. end

lib/active_record/connection_adapters/sqlite3/schema_definitions.rb

100.0% lines covered

10 relevant lines. 10 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 module ActiveRecord
  3. 3 module ConnectionAdapters
  4. 3 module SQLite3
  5. 3 class TableDefinition < ActiveRecord::ConnectionAdapters::TableDefinition
  6. 3 def references(*args, **options)
  7. 667 super(*args, type: :integer, **options)
  8. end
  9. 3 alias :belongs_to :references
  10. 3 private
  11. 3 def integer_like_primary_key_type(type, options)
  12. 36 :primary_key
  13. end
  14. end
  15. end
  16. end
  17. end

lib/active_record/connection_adapters/sqlite3/schema_dumper.rb

100.0% lines covered

9 relevant lines. 9 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 module ActiveRecord
  3. 3 module ConnectionAdapters
  4. 3 module SQLite3
  5. 3 class SchemaDumper < ConnectionAdapters::SchemaDumper # :nodoc:
  6. 3 private
  7. 3 def default_primary_key?(column)
  8. 2105 schema_type(column) == :integer
  9. end
  10. 3 def explicit_primary_key_default?(column)
  11. 2105 column.bigint?
  12. end
  13. end
  14. end
  15. end
  16. end

lib/active_record/connection_adapters/sqlite3/schema_statements.rb

98.73% lines covered

79 relevant lines. 78 lines covered and 1 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 module ActiveRecord
  3. 3 module ConnectionAdapters
  4. 3 module SQLite3
  5. 3 module SchemaStatements # :nodoc:
  6. # Returns an array of indexes for the given table.
  7. 3 def indexes(table_name)
  8. exec_query("PRAGMA index_list(#{quote_table_name(table_name)})", "SCHEMA").map do |row|
  9. # Indexes SQLite creates implicitly for internal use start with "sqlite_".
  10. # See https://www.sqlite.org/fileformat2.html#intschema
  11. 10937 next if row["name"].start_with?("sqlite_")
  12. 10766 index_sql = query_value(<<~SQL, "SCHEMA")
  13. SELECT sql
  14. FROM sqlite_master
  15. WHERE name = #{quote(row['name'])} AND type = 'index'
  16. UNION ALL
  17. SELECT sql
  18. FROM sqlite_temp_master
  19. WHERE name = #{quote(row['name'])} AND type = 'index'
  20. SQL
  21. 10766 /\bON\b\s*"?(\w+?)"?\s*\((?<expressions>.+?)\)(?:\s*WHERE\b\s*(?<where>.+))?\z/i =~ index_sql
  22. 10766 columns = exec_query("PRAGMA index_info(#{quote(row['name'])})", "SCHEMA").map do |col|
  23. 11225 col["name"]
  24. end
  25. 10766 orders = {}
  26. 10766 if columns.any?(&:nil?) # index created with an expression
  27. 73 columns = expressions
  28. else
  29. # Add info on sort order for columns (only desc order is explicitly specified,
  30. # asc is the default)
  31. 10693 if index_sql # index_sql can be null in case of primary key indexes
  32. 10693 index_sql.scan(/"(\w+)" DESC/).flatten.each { |order_column|
  33. 112 orders[order_column] = :desc
  34. }
  35. end
  36. end
  37. 10766 IndexDefinition.new(
  38. table_name,
  39. row["name"],
  40. row["unique"] != 0,
  41. columns,
  42. where: where,
  43. orders: orders
  44. )
  45. 7460 end.compact
  46. end
  47. 3 def add_foreign_key(from_table, to_table, **options)
  48. 78 alter_table(from_table) do |definition|
  49. 78 to_table = strip_table_name_prefix_and_suffix(to_table)
  50. 78 definition.foreign_key(to_table, **options)
  51. end
  52. end
  53. 3 def remove_foreign_key(from_table, to_table = nil, **options)
  54. 48 to_table ||= options[:to_table]
  55. 48 options = options.except(:name, :to_table, :validate)
  56. 48 foreign_keys = foreign_keys(from_table)
  57. 48 fkey = foreign_keys.detect do |fk|
  58. 48 table = to_table || begin
  59. 16 table = options[:column].to_s.delete_suffix("_id")
  60. 16 Base.pluralize_table_names ? table.pluralize : table
  61. end
  62. 48 table = strip_table_name_prefix_and_suffix(table)
  63. 48 fk_to_table = strip_table_name_prefix_and_suffix(fk.to_table)
  64. 88 fk_to_table == table && options.all? { |k, v| fk.options[k].to_s == v.to_s }
  65. end || raise(ArgumentError, "Table '#{from_table}' has no foreign key for #{to_table || options}")
  66. 46 foreign_keys.delete(fkey)
  67. 46 alter_table(from_table, foreign_keys)
  68. end
  69. 3 def check_constraints(table_name)
  70. 3796 table_sql = query_value(<<-SQL, "SCHEMA")
  71. SELECT sql
  72. FROM sqlite_master
  73. WHERE name = #{quote_table_name(table_name)} AND type = 'table'
  74. UNION ALL
  75. SELECT sql
  76. FROM sqlite_temp_master
  77. WHERE name = #{quote_table_name(table_name)} AND type = 'table'
  78. SQL
  79. 3796 table_sql.scan(/CONSTRAINT\s+(?<name>\w+)\s+CHECK\s+\((?<expression>(:?[^()]|\(\g<expression>\))+)\)/i).map do |name, expression|
  80. 36 CheckConstraintDefinition.new(table_name, expression, name: name)
  81. end
  82. end
  83. 3 def add_check_constraint(table_name, expression, **options)
  84. 13 alter_table(table_name) do |definition|
  85. 13 definition.check_constraint(expression, **options)
  86. end
  87. end
  88. 3 def remove_check_constraint(table_name, expression = nil, **options)
  89. 4 check_constraints = check_constraints(table_name)
  90. 4 chk_name_to_delete = check_constraint_for!(table_name, expression: expression, **options).name
  91. 6 check_constraints.delete_if { |chk| chk.name == chk_name_to_delete }
  92. 2 alter_table(table_name, foreign_keys(table_name), check_constraints)
  93. end
  94. 3 def create_schema_dumper(options)
  95. 88 SQLite3::SchemaDumper.create(self, options)
  96. end
  97. 3 private
  98. 3 def schema_creation
  99. 15425 SQLite3::SchemaCreation.new(self)
  100. end
  101. 3 def create_table_definition(name, **options)
  102. 5591 SQLite3::TableDefinition.new(self, name, **options)
  103. end
  104. 3 def validate_index_length!(table_name, new_name, internal = false)
  105. 9868 super unless internal
  106. end
  107. 3 def new_column_from_field(table_name, field)
  108. 226337 default = \
  109. case field["dflt_value"]
  110. when /^null$/i
  111. 106298 nil
  112. when /^'(.*)'$/m
  113. 628 $1.gsub("''", "'")
  114. when /^"(.*)"$/m
  115. $1.gsub('""', '"')
  116. else
  117. 119411 field["dflt_value"]
  118. end
  119. 226337 type_metadata = fetch_type_metadata(field["type"])
  120. 226337 Column.new(field["name"], default, type_metadata, field["notnull"].to_i == 0, collation: field["collation"])
  121. end
  122. 3 def data_source_sql(name = nil, type: nil)
  123. 1109 scope = quoted_scope(name, type: type)
  124. 1109 scope[:type] ||= "'table','view'"
  125. 1109 sql = +"SELECT name FROM sqlite_master WHERE name <> 'sqlite_sequence'"
  126. 1109 sql << " AND name = #{scope[:name]}" if scope[:name]
  127. 1109 sql << " AND type IN (#{scope[:type]})"
  128. 1109 sql
  129. end
  130. 3 def quoted_scope(name = nil, type: nil)
  131. 1109 type = \
  132. case type
  133. when "BASE TABLE"
  134. 297 "'table'"
  135. when "VIEW"
  136. 42 "'view'"
  137. end
  138. 1109 scope = {}
  139. 1109 scope[:name] = quote(name) if name
  140. 1109 scope[:type] = type if type
  141. 1109 scope
  142. end
  143. end
  144. end
  145. end
  146. end

lib/active_record/connection_adapters/sqlite3_adapter.rb

97.49% lines covered

279 relevant lines. 272 lines covered and 7 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 require "active_record/connection_adapters/abstract_adapter"
  3. 3 require "active_record/connection_adapters/statement_pool"
  4. 3 require "active_record/connection_adapters/sqlite3/explain_pretty_printer"
  5. 3 require "active_record/connection_adapters/sqlite3/quoting"
  6. 3 require "active_record/connection_adapters/sqlite3/database_statements"
  7. 3 require "active_record/connection_adapters/sqlite3/schema_creation"
  8. 3 require "active_record/connection_adapters/sqlite3/schema_definitions"
  9. 3 require "active_record/connection_adapters/sqlite3/schema_dumper"
  10. 3 require "active_record/connection_adapters/sqlite3/schema_statements"
  11. 3 gem "sqlite3", "~> 1.4"
  12. 3 require "sqlite3"
  13. 3 module ActiveRecord
  14. 3 module ConnectionHandling # :nodoc:
  15. 3 def sqlite3_connection(config)
  16. 603 config = config.symbolize_keys
  17. # Require database.
  18. 603 unless config[:database]
  19. raise ArgumentError, "No database file specified. Missing argument: database"
  20. end
  21. # Allow database path relative to Rails.root, but only if the database
  22. # path is not the special path that tells sqlite to build a database only
  23. # in memory.
  24. 603 if ":memory:" != config[:database] && !config[:database].to_s.start_with?("file:")
  25. 289 config[:database] = File.expand_path(config[:database], Rails.root) if defined?(Rails.root)
  26. 289 dirname = File.dirname(config[:database])
  27. 289 Dir.mkdir(dirname) unless File.directory?(dirname)
  28. end
  29. 601 db = SQLite3::Database.new(
  30. config[:database].to_s,
  31. config.merge(results_as_hash: true)
  32. )
  33. 601 ConnectionAdapters::SQLite3Adapter.new(db, logger, nil, config)
  34. rescue Errno::ENOENT => error
  35. 2 if error.message.include?("No such file or directory")
  36. 2 raise ActiveRecord::NoDatabaseError
  37. else
  38. raise
  39. end
  40. end
  41. end
  42. 3 module ConnectionAdapters #:nodoc:
  43. # The SQLite3 adapter works with the sqlite3-ruby drivers
  44. # (available as gem from https://rubygems.org/gems/sqlite3).
  45. #
  46. # Options:
  47. #
  48. # * <tt>:database</tt> - Path to the database file.
  49. 3 class SQLite3Adapter < AbstractAdapter
  50. 3 ADAPTER_NAME = "SQLite"
  51. 3 include SQLite3::Quoting
  52. 3 include SQLite3::SchemaStatements
  53. 3 include SQLite3::DatabaseStatements
  54. 3 NATIVE_DATABASE_TYPES = {
  55. primary_key: "integer PRIMARY KEY AUTOINCREMENT NOT NULL",
  56. string: { name: "varchar" },
  57. text: { name: "text" },
  58. integer: { name: "integer" },
  59. float: { name: "float" },
  60. decimal: { name: "decimal" },
  61. datetime: { name: "datetime" },
  62. time: { name: "time" },
  63. date: { name: "date" },
  64. binary: { name: "blob" },
  65. boolean: { name: "boolean" },
  66. json: { name: "json" },
  67. }
  68. 3 def self.represent_boolean_as_integer=(value) # :nodoc:
  69. if value == false
  70. raise "`.represent_boolean_as_integer=` is now always true, so make sure your application can work with it and remove this settings."
  71. end
  72. ActiveSupport::Deprecation.warn(
  73. "`.represent_boolean_as_integer=` is now always true, so setting this is deprecated and will be removed in Rails 6.1."
  74. )
  75. end
  76. 3 class StatementPool < ConnectionAdapters::StatementPool # :nodoc:
  77. 3 private
  78. 3 def dealloc(stmt)
  79. 3651 stmt.close unless stmt.closed?
  80. end
  81. end
  82. 3 def initialize(connection, logger, connection_options, config)
  83. 601 super(connection, logger, config)
  84. 601 configure_connection
  85. end
  86. 3 def self.database_exists?(config)
  87. 6 config = config.symbolize_keys
  88. 6 if config[:database] == ":memory:"
  89. 3 true
  90. else
  91. 3 database_file = defined?(Rails.root) ? File.expand_path(config[:database], Rails.root) : config[:database]
  92. 3 File.exist?(database_file)
  93. end
  94. end
  95. 3 def supports_ddl_transactions?
  96. 265 true
  97. end
  98. 3 def supports_savepoints?
  99. 42 true
  100. end
  101. 3 def supports_transaction_isolation?
  102. 4 true
  103. end
  104. 3 def supports_partial_index?
  105. 9848 true
  106. end
  107. 3 def supports_expression_index?
  108. 16 database_version >= "3.9.0"
  109. end
  110. 3 def requires_reloading?
  111. 40 true
  112. end
  113. 3 def supports_foreign_keys?
  114. 5310 true
  115. end
  116. 3 def supports_check_constraints?
  117. 7547 true
  118. end
  119. 3 def supports_views?
  120. 2 true
  121. end
  122. 3 def supports_datetime_with_precision?
  123. 280 true
  124. end
  125. 3 def supports_json?
  126. true
  127. end
  128. 3 def supports_common_table_expressions?
  129. 2 database_version >= "3.8.3"
  130. end
  131. 3 def supports_insert_on_conflict?
  132. 168 database_version >= "3.24.0"
  133. end
  134. 3 alias supports_insert_on_duplicate_skip? supports_insert_on_conflict?
  135. 3 alias supports_insert_on_duplicate_update? supports_insert_on_conflict?
  136. 3 alias supports_insert_conflict_target? supports_insert_on_conflict?
  137. 3 def active?
  138. 54528 !@connection.closed?
  139. end
  140. 3 def reconnect!
  141. 3 super
  142. 3 connect if @connection.closed?
  143. end
  144. # Disconnects from the database if already connected. Otherwise, this
  145. # method does nothing.
  146. 3 def disconnect!
  147. 361 super
  148. 361 @connection.close rescue nil
  149. end
  150. 3 def supports_index_sort_order?
  151. 9824 true
  152. end
  153. 3 def native_database_types #:nodoc:
  154. 51170 NATIVE_DATABASE_TYPES
  155. end
  156. # Returns the current database encoding format as a string, eg: 'UTF-8'
  157. 3 def encoding
  158. 2 @connection.encoding.to_s
  159. end
  160. 3 def supports_explain?
  161. 4 true
  162. end
  163. 3 def supports_lazy_transactions?
  164. 64239 true
  165. end
  166. # REFERENTIAL INTEGRITY ====================================
  167. 3 def disable_referential_integrity # :nodoc:
  168. 2390 old_foreign_keys = query_value("PRAGMA foreign_keys")
  169. 2390 old_defer_foreign_keys = query_value("PRAGMA defer_foreign_keys")
  170. 2390 begin
  171. 2390 execute("PRAGMA defer_foreign_keys = ON")
  172. 2390 execute("PRAGMA foreign_keys = OFF")
  173. 2390 yield
  174. ensure
  175. 2390 execute("PRAGMA defer_foreign_keys = #{old_defer_foreign_keys}")
  176. 2390 execute("PRAGMA foreign_keys = #{old_foreign_keys}")
  177. end
  178. end
  179. # SCHEMA STATEMENTS ========================================
  180. 3 def primary_keys(table_name) # :nodoc:
  181. 62002 pks = table_structure(table_name).select { |f| f["pk"] > 0 }
  182. 22357 pks.sort_by { |f| f["pk"] }.map { |f| f["name"] }
  183. end
  184. 3 def remove_index(table_name, column_name = nil, **options) # :nodoc:
  185. 96 return if options[:if_exists] && !index_exists?(table_name, column_name, **options)
  186. 94 index_name = index_name_for_remove(table_name, column_name, options)
  187. 84 exec_query "DROP INDEX #{quote_column_name(index_name)}"
  188. end
  189. # Renames a table.
  190. #
  191. # Example:
  192. # rename_table('octopuses', 'octopi')
  193. 3 def rename_table(table_name, new_name)
  194. 16 schema_cache.clear_data_source_cache!(table_name.to_s)
  195. 16 schema_cache.clear_data_source_cache!(new_name.to_s)
  196. 16 exec_query "ALTER TABLE #{quote_table_name(table_name)} RENAME TO #{quote_table_name(new_name)}"
  197. 16 rename_table_indexes(table_name, new_name)
  198. end
  199. 3 def add_column(table_name, column_name, type, **options) #:nodoc:
  200. 397 if invalid_alter_table_type?(type, options)
  201. 16 alter_table(table_name) do |definition|
  202. 16 definition.column(column_name, type, **options)
  203. end
  204. else
  205. 381 super
  206. end
  207. end
  208. 3 def remove_column(table_name, column_name, type = nil, **options) #:nodoc:
  209. 1164 alter_table(table_name) do |definition|
  210. 1164 definition.remove_column column_name
  211. 1164 definition.foreign_keys.delete_if do |_, fk_options|
  212. 10 fk_options[:column] == column_name.to_s
  213. end
  214. end
  215. end
  216. 3 def change_column_default(table_name, column_name, default_or_changes) #:nodoc:
  217. 22 default = extract_new_default_value(default_or_changes)
  218. 22 alter_table(table_name) do |definition|
  219. 22 definition[column_name].default = default
  220. end
  221. end
  222. 3 def change_column_null(table_name, column_name, null, default = nil) #:nodoc:
  223. 12 unless null || default.nil?
  224. 2 exec_query("UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote(default)} WHERE #{quote_column_name(column_name)} IS NULL")
  225. end
  226. 12 alter_table(table_name) do |definition|
  227. 12 definition[column_name].null = null
  228. end
  229. end
  230. 3 def change_column(table_name, column_name, type, **options) #:nodoc:
  231. 44 alter_table(table_name) do |definition|
  232. 44 definition[column_name].instance_eval do
  233. 44 self.type = type
  234. 44 self.options.merge!(options)
  235. end
  236. end
  237. end
  238. 3 def rename_column(table_name, column_name, new_column_name) #:nodoc:
  239. 38 column = column_for(table_name, column_name)
  240. 36 alter_table(table_name, rename: { column.name => new_column_name.to_s })
  241. 36 rename_column_indexes(table_name, column.name, new_column_name)
  242. end
  243. 3 def add_reference(table_name, ref_name, **options) # :nodoc:
  244. 50 super(table_name, ref_name, type: :integer, **options)
  245. end
  246. 3 alias :add_belongs_to :add_reference
  247. 3 def foreign_keys(table_name)
  248. 3912 fk_info = exec_query("PRAGMA foreign_key_list(#{quote(table_name)})", "SCHEMA")
  249. 3912 fk_info.map do |row|
  250. 304 options = {
  251. column: row["from"],
  252. primary_key: row["to"],
  253. on_delete: extract_foreign_key_action(row["on_delete"]),
  254. on_update: extract_foreign_key_action(row["on_update"])
  255. }
  256. 304 ForeignKeyDefinition.new(table_name, row["table"], options)
  257. end
  258. end
  259. 3 def build_insert_sql(insert) # :nodoc:
  260. 102 sql = +"INSERT #{insert.into} #{insert.values_list}"
  261. 100 if insert.skip_duplicates?
  262. 40 sql << " ON CONFLICT #{insert.conflict_target} DO NOTHING"
  263. 60 elsif insert.update_duplicates?
  264. 40 sql << " ON CONFLICT #{insert.conflict_target} DO UPDATE SET "
  265. 180 sql << insert.touch_model_timestamps_unless { |column| "#{column} IS excluded.#{column}" }
  266. 120 sql << insert.updatable_columns.map { |column| "#{column}=excluded.#{column}" }.join(",")
  267. end
  268. 100 sql
  269. end
  270. 3 def shared_cache? # :nodoc:
  271. 14 @config.fetch(:flags, 0).anybits?(::SQLite3::Constants::Open::SHAREDCACHE)
  272. end
  273. 3 def get_database_version # :nodoc:
  274. 504 SQLite3Adapter::Version.new(query_value("SELECT sqlite_version(*)"))
  275. end
  276. 3 def check_version # :nodoc:
  277. 441 if database_version < "3.8.0"
  278. raise "Your version of SQLite (#{database_version}) is too old. Active Record supports SQLite >= 3.8."
  279. end
  280. end
  281. 3 private
  282. # See https://www.sqlite.org/limits.html,
  283. # the default value is 999 when not configured.
  284. 3 def bind_params_length
  285. 33829 999
  286. end
  287. 3 def initialize_type_map(m = type_map)
  288. 85 super
  289. 85 register_class_with_limit m, %r(int)i, SQLite3Integer
  290. end
  291. 3 def table_structure(table_name)
  292. 28820 structure = exec_query("PRAGMA table_info(#{quote_table_name(table_name)})", "SCHEMA")
  293. 28820 raise(ActiveRecord::StatementInvalid, "Could not find table '#{table_name}'") if structure.empty?
  294. 28820 table_structure_with_collation(table_name, structure)
  295. end
  296. 3 alias column_definitions table_structure
  297. # See: https://www.sqlite.org/lang_altertable.html
  298. # SQLite has an additional restriction on the ALTER TABLE statement
  299. 3 def invalid_alter_table_type?(type, options)
  300. 397 type.to_sym == :primary_key || options[:primary_key] ||
  301. options[:null] == false && options[:default].nil?
  302. end
  303. 3 def alter_table(
  304. table_name,
  305. foreign_keys = foreign_keys(table_name),
  306. check_constraints = check_constraints(table_name),
  307. **options
  308. )
  309. 1433 altered_table_name = "a#{table_name}"
  310. 1433 caller = lambda do |definition|
  311. 1433 rename = options[:rename] || {}
  312. 1433 foreign_keys.each do |fk|
  313. 28 if column = rename[fk.options[:column]]
  314. 6 fk.options[:column] = column
  315. end
  316. 28 to_table = strip_table_name_prefix_and_suffix(fk.to_table)
  317. 28 definition.foreign_key(to_table, **fk.options)
  318. end
  319. 1433 check_constraints.each do |chk|
  320. 4 definition.check_constraint(chk.expression, **chk.options)
  321. end
  322. 1433 yield definition if block_given?
  323. end
  324. 1433 transaction do
  325. 1433 disable_referential_integrity do
  326. 1433 move_table(table_name, altered_table_name, options.merge(temporary: true))
  327. 1433 move_table(altered_table_name, table_name, &caller)
  328. end
  329. end
  330. end
  331. 3 def move_table(from, to, options = {}, &block)
  332. 2866 copy_table(from, to, options, &block)
  333. 2862 drop_table(from)
  334. end
  335. 3 def copy_table(from, to, options = {})
  336. 2884 from_primary_key = primary_key(from)
  337. 2884 options[:id] = false
  338. 2884 create_table(to, **options) do |definition|
  339. 2884 @definition = definition
  340. 2884 if from_primary_key.is_a?(Array)
  341. 4 @definition.primary_keys from_primary_key
  342. end
  343. 2884 columns(from).each do |column|
  344. 34736 column_name = options[:rename] ?
  345. 142 (options[:rename][column.name] ||
  346. options[:rename][column.name.to_sym] ||
  347. column.name) : column.name
  348. 34736 @definition.column(column_name, column.type,
  349. limit: column.limit, default: column.default,
  350. precision: column.precision, scale: column.scale,
  351. null: column.null, collation: column.collation,
  352. primary_key: column_name == from_primary_key
  353. )
  354. end
  355. 2884 yield @definition if block_given?
  356. end
  357. 2880 copy_table_indexes(from, to, options[:rename] || {})
  358. 2880 copy_table_contents(from, to,
  359. @definition.columns.map(&:name),
  360. options[:rename] || {})
  361. end
  362. 3 def copy_table_indexes(from, to, rename = {})
  363. 2880 indexes(from).each do |index|
  364. 9046 name = index.name
  365. 9046 if to == "a#{from}"
  366. 4522 name = "t#{name}"
  367. 4524 elsif from == "a#{to}"
  368. 4518 name = name[1..-1]
  369. end
  370. 9046 columns = index.columns
  371. 9046 if columns.is_a?(Array)
  372. 9038 to_column_names = columns(to).map(&:name)
  373. 18166 columns = columns.map { |c| rename[c] || c }.select do |column|
  374. 9128 to_column_names.include?(column)
  375. end
  376. end
  377. 9046 unless columns.empty?
  378. # index name can't be the same
  379. 9012 options = { name: name.gsub(/(^|_)(#{from})_/, "\\1#{to}_"), internal: true }
  380. 9012 options[:unique] = true if index.unique
  381. 9012 options[:where] = index.where if index.where
  382. 9012 add_index(to, columns, **options)
  383. end
  384. end
  385. end
  386. 3 def copy_table_contents(from, to, columns, rename = {})
  387. 37416 column_mappings = Hash[columns.map { |name| [name, name] }]
  388. 2918 rename.each { |a| column_mappings[a.last] = a.first }
  389. 2880 from_columns = columns(from).collect(&:name)
  390. 37416 columns = columns.find_all { |col| from_columns.include?(column_mappings[col]) }
  391. 37400 from_columns_to_copy = columns.map { |col| column_mappings[col] }
  392. 37400 quoted_columns = columns.map { |col| quote_column_name(col) } * ","
  393. 37400 quoted_from_columns = from_columns_to_copy.map { |col| quote_column_name(col) } * ","
  394. 2880 exec_query("INSERT INTO #{quote_table_name(to)} (#{quoted_columns})
  395. SELECT #{quoted_from_columns} FROM #{quote_table_name(from)}")
  396. end
  397. 3 def translate_exception(exception, message:, sql:, binds:)
  398. 1037 case exception.message
  399. # SQLite 3.8.2 returns a newly formatted error message:
  400. # UNIQUE constraint failed: *table_name*.*column_name*
  401. # Older versions of SQLite return:
  402. # column *column_name* is not unique
  403. when /column(s)? .* (is|are) not unique/, /UNIQUE constraint failed: .*/
  404. 28 RecordNotUnique.new(message, sql: sql, binds: binds)
  405. when /.* may not be NULL/, /NOT NULL constraint failed: .*/
  406. 10 NotNullViolation.new(message, sql: sql, binds: binds)
  407. when /FOREIGN KEY constraint failed/i
  408. 6 InvalidForeignKey.new(message, sql: sql, binds: binds)
  409. else
  410. 993 super
  411. end
  412. end
  413. 3 COLLATE_REGEX = /.*\"(\w+)\".*collate\s+\"(\w+)\".*/i.freeze
  414. 3 def table_structure_with_collation(table_name, basic_structure)
  415. 28820 collation_hash = {}
  416. 28820 sql = <<~SQL
  417. SELECT sql FROM
  418. (SELECT * FROM sqlite_master UNION ALL
  419. SELECT * FROM sqlite_temp_master)
  420. WHERE type = 'table' AND name = #{quote(table_name)}
  421. SQL
  422. # Result will have following sample string
  423. # CREATE TABLE "users" ("id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
  424. # "password_digest" varchar COLLATE "NOCASE");
  425. 28820 result = query_value(sql, "SCHEMA")
  426. 28820 if result
  427. # Splitting with left parentheses and discarding the first part will return all
  428. # columns separated with comma(,).
  429. 28812 columns_string = result.split("(", 2).last
  430. 28812 columns_string.split(",").each do |column_string|
  431. # This regex will match the column name and collation type and will save
  432. # the value in $1 and $2 respectively.
  433. 282249 collation_hash[$1] = $2 if COLLATE_REGEX =~ column_string
  434. end
  435. 28812 basic_structure.map do |column|
  436. 280610 column_name = column["name"]
  437. 280610 if collation_hash.has_key? column_name
  438. 82 column["collation"] = collation_hash[column_name]
  439. end
  440. 280610 column
  441. end
  442. else
  443. 8 basic_structure.to_a
  444. end
  445. end
  446. 3 def arel_visitor
  447. 601 Arel::Visitors::SQLite.new(self)
  448. end
  449. 3 def build_statement_pool
  450. 601 StatementPool.new(self.class.type_cast_config_to_integer(@config[:statement_limit]))
  451. end
  452. 3 def connect
  453. 2 @connection = ::SQLite3::Database.new(
  454. @config[:database].to_s,
  455. @config.merge(results_as_hash: true)
  456. )
  457. 2 configure_connection
  458. end
  459. 3 def configure_connection
  460. 603 @connection.busy_timeout(self.class.type_cast_config_to_integer(@config[:timeout])) if @config[:timeout]
  461. 601 execute("PRAGMA foreign_keys = ON", "SCHEMA")
  462. end
  463. 3 class SQLite3Integer < Type::Integer # :nodoc:
  464. 3 private
  465. 3 def _limit
  466. # INTEGER storage class can be stored 8 bytes value.
  467. # See https://www.sqlite.org/datatype3.html#storage_classes_and_datatypes
  468. 340 limit || 8
  469. end
  470. end
  471. 3 ActiveRecord::Type.register(:integer, SQLite3Integer, adapter: :sqlite3)
  472. end
  473. 3 ActiveSupport.run_load_hooks(:active_record_sqlite3adapter, SQLite3Adapter)
  474. end
  475. end

lib/active_record/connection_adapters/statement_pool.rb

81.25% lines covered

32 relevant lines. 26 lines covered and 6 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 module ActiveRecord
  3. 3 module ConnectionAdapters
  4. 3 class StatementPool # :nodoc:
  5. 3 include Enumerable
  6. 3 DEFAULT_STATEMENT_LIMIT = 1000
  7. 3 def initialize(statement_limit = nil)
  8. 1649 @cache = Hash.new { |h, pid| h[pid] = {} }
  9. 1013 @statement_limit = statement_limit || DEFAULT_STATEMENT_LIMIT
  10. end
  11. 3 def each(&block)
  12. cache.each(&block)
  13. end
  14. 3 def key?(key)
  15. 8717 cache.key?(key)
  16. end
  17. 3 def [](key)
  18. 26260 cache[key]
  19. end
  20. 3 def length
  21. cache.length
  22. end
  23. 3 def []=(sql, stmt)
  24. 5555 while @statement_limit <= cache.size
  25. dealloc(cache.shift.last)
  26. end
  27. 5555 cache[sql] = stmt
  28. end
  29. 3 def clear
  30. 3505 cache.each_value do |stmt|
  31. 5510 dealloc stmt
  32. end
  33. 3505 cache.clear
  34. end
  35. 3 def delete(key)
  36. dealloc cache[key]
  37. cache.delete(key)
  38. end
  39. 3 private
  40. 3 def cache
  41. 53130 @cache[Process.pid]
  42. end
  43. 3 def dealloc(stmt)
  44. raise NotImplementedError
  45. end
  46. end
  47. end
  48. end

lib/active_record/connection_handling.rb

99.06% lines covered

106 relevant lines. 105 lines covered and 1 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 module ActiveRecord
  3. 3 module ConnectionHandling
  4. 2630 RAILS_ENV = -> { (Rails.env if defined?(Rails.env)) || ENV["RAILS_ENV"].presence || ENV["RACK_ENV"].presence }
  5. 2630 DEFAULT_ENV = -> { RAILS_ENV.call || "default_env" }
  6. # Establishes the connection to the database. Accepts a hash as input where
  7. # the <tt>:adapter</tt> key must be specified with the name of a database adapter (in lower-case)
  8. # example for regular databases (MySQL, PostgreSQL, etc):
  9. #
  10. # ActiveRecord::Base.establish_connection(
  11. # adapter: "mysql2",
  12. # host: "localhost",
  13. # username: "myuser",
  14. # password: "mypass",
  15. # database: "somedatabase"
  16. # )
  17. #
  18. # Example for SQLite database:
  19. #
  20. # ActiveRecord::Base.establish_connection(
  21. # adapter: "sqlite3",
  22. # database: "path/to/dbfile"
  23. # )
  24. #
  25. # Also accepts keys as strings (for parsing from YAML for example):
  26. #
  27. # ActiveRecord::Base.establish_connection(
  28. # "adapter" => "sqlite3",
  29. # "database" => "path/to/dbfile"
  30. # )
  31. #
  32. # Or a URL:
  33. #
  34. # ActiveRecord::Base.establish_connection(
  35. # "postgres://myuser:mypass@localhost/somedatabase"
  36. # )
  37. #
  38. # In case {ActiveRecord::Base.configurations}[rdoc-ref:Core.configurations]
  39. # is set (Rails automatically loads the contents of config/database.yml into it),
  40. # a symbol can also be given as argument, representing a key in the
  41. # configuration hash:
  42. #
  43. # ActiveRecord::Base.establish_connection(:production)
  44. #
  45. # The exceptions AdapterNotSpecified, AdapterNotFound and +ArgumentError+
  46. # may be returned on an error.
  47. 3 def establish_connection(config_or_env = nil)
  48. 273 config_or_env ||= DEFAULT_ENV.call.to_sym
  49. 273 db_config, owner_name = resolve_config_for_connection(config_or_env)
  50. 270 connection_handler.establish_connection(db_config, owner_name: owner_name, shard: current_shard)
  51. end
  52. # Connects a model to the databases specified. The +database+ keyword
  53. # takes a hash consisting of a +role+ and a +database_key+.
  54. #
  55. # This will create a connection handler for switching between connections,
  56. # look up the config hash using the +database_key+ and finally
  57. # establishes a connection to that config.
  58. #
  59. # class AnimalsModel < ApplicationRecord
  60. # self.abstract_class = true
  61. #
  62. # connects_to database: { writing: :primary, reading: :primary_replica }
  63. # end
  64. #
  65. # +connects_to+ also supports horizontal sharding. The horizontal sharding API
  66. # also supports read replicas. Connect a model to a list of shards like this:
  67. #
  68. # class AnimalsModel < ApplicationRecord
  69. # self.abstract_class = true
  70. #
  71. # connects_to shards: {
  72. # default: { writing: :primary, reading: :primary_replica },
  73. # shard_two: { writing: :primary_shard_two, reading: :primary_shard_replica_two }
  74. # }
  75. # end
  76. #
  77. # Returns an array of database connections.
  78. 3 def connects_to(database: {}, shards: {})
  79. 57 if database.present? && shards.present?
  80. 2 raise ArgumentError, "connects_to can only accept a `database` or `shards` argument, but not both arguments."
  81. end
  82. 55 connections = []
  83. 55 database.each do |role, database_key|
  84. 50 db_config, owner_name = resolve_config_for_connection(database_key)
  85. 50 handler = lookup_connection_handler(role.to_sym)
  86. 50 connections << handler.establish_connection(db_config, owner_name: owner_name)
  87. end
  88. 55 shards.each do |shard, database_keys|
  89. 42 database_keys.each do |role, database_key|
  90. 62 db_config, owner_name = resolve_config_for_connection(database_key)
  91. 62 handler = lookup_connection_handler(role.to_sym)
  92. 62 connections << handler.establish_connection(db_config, owner_name: owner_name, shard: shard.to_sym)
  93. end
  94. end
  95. 55 connections
  96. end
  97. # Connects to a role (ex writing, reading or a custom role) and/or
  98. # shard for the duration of the block. At the end of the block the
  99. # connection will be returned to the original role / shard.
  100. #
  101. # If only a role is passed, Active Record will look up the connection
  102. # based on the requested role. If a non-established role is requested
  103. # an `ActiveRecord::ConnectionNotEstablished` error will be raised:
  104. #
  105. # ActiveRecord::Base.connected_to(role: :writing) do
  106. # Dog.create! # creates dog using dog writing connection
  107. # end
  108. #
  109. # ActiveRecord::Base.connected_to(role: :reading) do
  110. # Dog.create! # throws exception because we're on a replica
  111. # end
  112. #
  113. # If only a shard is passed, Active Record will look up the shard on the
  114. # current role. If a non-existent shard is passed, an
  115. # `ActiveRecord::ConnectionNotEstablished` error will be raised.
  116. #
  117. # ActiveRecord::Base.connected_to(shard: :default) do
  118. # # Dog.create! # creates dog in shard with the default key
  119. # end
  120. #
  121. # If a shard and role is passed, Active Record will first lookup the role,
  122. # and then look up the connection by shard key.
  123. #
  124. # ActiveRecord::Base.connected_to(role: :reading, shard: :shard_one_replica) do
  125. # # Dog.create! # would raise as we're on a readonly connection
  126. # end
  127. #
  128. # The database kwarg is deprecated and will be removed in 6.2.0 without replacement.
  129. 3 def connected_to(database: nil, role: nil, shard: nil, prevent_writes: false, &blk)
  130. 166 raise NotImplementedError, "connected_to can only be called on ActiveRecord::Base" unless self == Base
  131. 163 if database
  132. 4 ActiveSupport::Deprecation.warn("The database key in `connected_to` is deprecated. It will be removed in Rails 6.2.0 without replacement.")
  133. end
  134. 163 if database && (role || shard)
  135. 2 raise ArgumentError, "`connected_to` cannot accept a `database` argument with any other arguments."
  136. 161 elsif database
  137. 2 if database.is_a?(Hash)
  138. 2 role, database = database.first
  139. 2 role = role.to_sym
  140. end
  141. 2 db_config, owner_name = resolve_config_for_connection(database)
  142. 2 handler = lookup_connection_handler(role)
  143. 2 handler.establish_connection(db_config, owner_name: owner_name)
  144. 2 with_handler(role, &blk)
  145. 159 elsif shard
  146. 57 with_shard(shard, role || current_role, prevent_writes, &blk)
  147. 102 elsif role
  148. 98 with_role(role, prevent_writes, &blk)
  149. else
  150. 4 raise ArgumentError, "must provide a `shard` and/or `role`."
  151. end
  152. end
  153. # Returns true if role is the current connected role.
  154. #
  155. # ActiveRecord::Base.connected_to(role: :writing) do
  156. # ActiveRecord::Base.connected_to?(role: :writing) #=> true
  157. # ActiveRecord::Base.connected_to?(role: :reading) #=> false
  158. # end
  159. 3 def connected_to?(role:, shard: ActiveRecord::Base.default_shard)
  160. 107 current_role == role.to_sym && current_shard == shard.to_sym
  161. end
  162. # Returns the symbol representing the current connected role.
  163. #
  164. # ActiveRecord::Base.connected_to(role: :writing) do
  165. # ActiveRecord::Base.current_role #=> :writing
  166. # end
  167. #
  168. # ActiveRecord::Base.connected_to(role: :reading) do
  169. # ActiveRecord::Base.current_role #=> :reading
  170. # end
  171. 3 def current_role
  172. 170 connection_handlers.key(connection_handler)
  173. end
  174. 3 def lookup_connection_handler(handler_key) # :nodoc:
  175. 271 handler_key ||= ActiveRecord::Base.writing_role
  176. 271 connection_handlers[handler_key] ||= ActiveRecord::ConnectionAdapters::ConnectionHandler.new
  177. end
  178. # Clears the query cache for all connections associated with the current thread.
  179. 3 def clear_query_caches_for_current_thread
  180. 132709 ActiveRecord::Base.connection_handlers.each_value do |handler|
  181. 132721 handler.connection_pool_list.each do |pool|
  182. 972917 pool.connection.clear_query_cache if pool.active_connection?
  183. end
  184. end
  185. end
  186. # Returns the connection currently associated with the class. This can
  187. # also be used to "borrow" the connection to do database work unrelated
  188. # to any of the specific Active Records.
  189. 3 def connection
  190. 266512 retrieve_connection
  191. end
  192. 3 attr_writer :connection_specification_name
  193. # Return the connection specification name from the current class or its parent.
  194. 3 def connection_specification_name
  195. 556299 if !defined?(@connection_specification_name) || @connection_specification_name.nil?
  196. 286786 return self == Base ? Base.name : superclass.connection_specification_name
  197. end
  198. 269513 @connection_specification_name
  199. end
  200. 3 def primary_class? # :nodoc:
  201. 384 self == Base || defined?(ApplicationRecord) && self == ApplicationRecord
  202. end
  203. # Returns the configuration of the associated connection as a hash:
  204. #
  205. # ActiveRecord::Base.connection_config
  206. # # => {pool: 5, timeout: 5000, database: "db/development.sqlite3", adapter: "sqlite3"}
  207. #
  208. # Please use only for reading.
  209. 3 def connection_config
  210. connection_pool.db_config.configuration_hash
  211. end
  212. 3 deprecate connection_config: "Use connection_db_config instead"
  213. # Returns the db_config object from the associated connection:
  214. #
  215. # ActiveRecord::Base.connection_db_config
  216. # #<ActiveRecord::DatabaseConfigurations::HashConfig:0x00007fd1acbded10 @env_name="development",
  217. # @name="primary", @config={pool: 5, timeout: 5000, database: "db/development.sqlite3", adapter: "sqlite3"}>
  218. #
  219. # Use only for reading.
  220. 3 def connection_db_config
  221. 117 connection_pool.db_config
  222. end
  223. 3 def connection_pool
  224. 1206 connection_handler.retrieve_connection_pool(connection_specification_name, shard: current_shard) || raise(ConnectionNotEstablished)
  225. end
  226. 3 def retrieve_connection
  227. 266686 connection_handler.retrieve_connection(connection_specification_name, shard: current_shard)
  228. end
  229. # Returns +true+ if Active Record is connected.
  230. 3 def connected?
  231. 1585 connection_handler.connected?(connection_specification_name, shard: current_shard)
  232. end
  233. 3 def remove_connection(name = nil)
  234. 96 name ||= @connection_specification_name if defined?(@connection_specification_name)
  235. # if removing a connection that has a pool, we reset the
  236. # connection_specification_name so it will use the parent
  237. # pool.
  238. 96 if connection_handler.retrieve_connection_pool(name, shard: current_shard)
  239. 93 self.connection_specification_name = nil
  240. end
  241. 96 connection_handler.remove_connection_pool(name, shard: current_shard)
  242. end
  243. 3 def clear_cache! # :nodoc:
  244. 97 connection.schema_cache.clear!
  245. end
  246. 3 delegate :clear_active_connections!, :clear_reloadable_connections!,
  247. :clear_all_connections!, :flush_idle_connections!, to: :connection_handler
  248. 3 private
  249. 3 def resolve_config_for_connection(config_or_env)
  250. 387 raise "Anonymous class is not allowed." unless name
  251. 384 owner_name = primary_class? ? Base.name : name
  252. 384 self.connection_specification_name = owner_name
  253. 384 db_config = Base.configurations.resolve(config_or_env)
  254. 384 [db_config, owner_name]
  255. end
  256. 3 def with_handler(handler_key, &blk)
  257. 157 handler = lookup_connection_handler(handler_key)
  258. 157 swap_connection_handler(handler, &blk)
  259. end
  260. 3 def with_role(role, prevent_writes, &blk)
  261. 155 prevent_writes = true if role == reading_role
  262. 155 with_handler(role.to_sym) do
  263. 155 connection_handler.while_preventing_writes(prevent_writes, &blk)
  264. end
  265. end
  266. 3 def with_shard(shard, role, prevent_writes)
  267. 57 old_shard = current_shard
  268. 57 with_role(role, prevent_writes) do
  269. 57 self.current_shard = shard
  270. 57 yield
  271. end
  272. ensure
  273. 57 self.current_shard = old_shard
  274. end
  275. 3 def swap_connection_handler(handler, &blk) # :nodoc:
  276. 157 old_handler, ActiveRecord::Base.connection_handler = ActiveRecord::Base.connection_handler, handler
  277. 157 return_value = yield
  278. 145 return_value.load if return_value.is_a? ActiveRecord::Relation
  279. 145 return_value
  280. ensure
  281. 157 ActiveRecord::Base.connection_handler = old_handler
  282. end
  283. end
  284. end

lib/active_record/core.rb

98.41% lines covered

252 relevant lines. 248 lines covered and 4 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 require "active_support/core_ext/enumerable"
  3. 3 require "active_support/core_ext/hash/indifferent_access"
  4. 3 require "active_support/core_ext/string/filters"
  5. 3 require "active_support/parameter_filter"
  6. 3 require "concurrent/map"
  7. 3 module ActiveRecord
  8. 3 module Core
  9. 3 extend ActiveSupport::Concern
  10. 3 included do
  11. ##
  12. # :singleton-method:
  13. #
  14. # Accepts a logger conforming to the interface of Log4r which is then
  15. # passed on to any new database connections made and which can be
  16. # retrieved on both a class and instance level by calling +logger+.
  17. 3 mattr_accessor :logger, instance_writer: false
  18. ##
  19. # :singleton-method:
  20. #
  21. # Specifies if the methods calling database queries should be logged below
  22. # their relevant queries. Defaults to false.
  23. 3 mattr_accessor :verbose_query_logs, instance_writer: false, default: false
  24. ##
  25. # Contains the database configuration - as is typically stored in config/database.yml -
  26. # as an ActiveRecord::DatabaseConfigurations object.
  27. #
  28. # For example, the following database.yml...
  29. #
  30. # development:
  31. # adapter: sqlite3
  32. # database: db/development.sqlite3
  33. #
  34. # production:
  35. # adapter: sqlite3
  36. # database: db/production.sqlite3
  37. #
  38. # ...would result in ActiveRecord::Base.configurations to look like this:
  39. #
  40. # #<ActiveRecord::DatabaseConfigurations:0x00007fd1acbdf800 @configurations=[
  41. # #<ActiveRecord::DatabaseConfigurations::HashConfig:0x00007fd1acbded10 @env_name="development",
  42. # @name="primary", @config={adapter: "sqlite3", database: "db/development.sqlite3"}>,
  43. # #<ActiveRecord::DatabaseConfigurations::HashConfig:0x00007fd1acbdea90 @env_name="production",
  44. # @name="primary", @config={adapter: "sqlite3", database: "db/production.sqlite3"}>
  45. # ]>
  46. 3 def self.configurations=(config)
  47. 388 @@configurations = ActiveRecord::DatabaseConfigurations.new(config)
  48. end
  49. 3 self.configurations = {}
  50. # Returns fully resolved ActiveRecord::DatabaseConfigurations object
  51. 3 def self.configurations
  52. 2063 @@configurations
  53. end
  54. ##
  55. # :singleton-method:
  56. # Determines whether to use Time.utc (using :utc) or Time.local (using :local) when pulling
  57. # dates and times from the database. This is set to :utc by default.
  58. 3 mattr_accessor :default_timezone, instance_writer: false, default: :utc
  59. ##
  60. # :singleton-method:
  61. # Specifies the format to use when dumping the database schema with Rails'
  62. # Rakefile. If :sql, the schema is dumped as (potentially database-
  63. # specific) SQL statements. If :ruby, the schema is dumped as an
  64. # ActiveRecord::Schema file which can be loaded into any database that
  65. # supports migrations. Use :ruby if you want to have different database
  66. # adapters for, e.g., your development and test environments.
  67. 3 mattr_accessor :schema_format, instance_writer: false, default: :ruby
  68. ##
  69. # :singleton-method:
  70. # Specifies if an error should be raised if the query has an order being
  71. # ignored when doing batch queries. Useful in applications where the
  72. # scope being ignored is error-worthy, rather than a warning.
  73. 3 mattr_accessor :error_on_ignored_order, instance_writer: false, default: false
  74. # :singleton-method:
  75. # Specify the behavior for unsafe raw query methods. Values are as follows
  76. # deprecated - Warnings are logged when unsafe raw SQL is passed to
  77. # query methods.
  78. # disabled - Unsafe raw SQL passed to query methods results in
  79. # UnknownAttributeReference exception.
  80. 3 mattr_accessor :allow_unsafe_raw_sql, instance_writer: false, default: :deprecated
  81. ##
  82. # :singleton-method:
  83. # Specify whether or not to use timestamps for migration versions
  84. 3 mattr_accessor :timestamped_migrations, instance_writer: false, default: true
  85. ##
  86. # :singleton-method:
  87. # Specify whether schema dump should happen at the end of the
  88. # db:migrate rails command. This is true by default, which is useful for the
  89. # development environment. This should ideally be false in the production
  90. # environment where dumping schema is rarely needed.
  91. 3 mattr_accessor :dump_schema_after_migration, instance_writer: false, default: true
  92. ##
  93. # :singleton-method:
  94. # Specifies which database schemas to dump when calling db:schema:dump.
  95. # If the value is :schema_search_path (the default), any schemas listed in
  96. # schema_search_path are dumped. Use :all to dump all schemas regardless
  97. # of schema_search_path, or a string of comma separated schemas for a
  98. # custom list.
  99. 3 mattr_accessor :dump_schemas, instance_writer: false, default: :schema_search_path
  100. ##
  101. # :singleton-method:
  102. # Specify a threshold for the size of query result sets. If the number of
  103. # records in the set exceeds the threshold, a warning is logged. This can
  104. # be used to identify queries which load thousands of records and
  105. # potentially cause memory bloat.
  106. 3 mattr_accessor :warn_on_records_fetched_greater_than, instance_writer: false
  107. 3 mattr_accessor :maintain_test_schema, instance_accessor: false
  108. 3 class_attribute :belongs_to_required_by_default, instance_accessor: false
  109. 3 class_attribute :strict_loading_by_default, instance_accessor: false, default: false
  110. 3 mattr_accessor :connection_handlers, instance_accessor: false, default: {}
  111. 3 mattr_accessor :writing_role, instance_accessor: false, default: :writing
  112. 3 mattr_accessor :reading_role, instance_accessor: false, default: :reading
  113. 3 mattr_accessor :has_many_inversing, instance_accessor: false, default: false
  114. 3 class_attribute :default_connection_handler, instance_writer: false
  115. 3 class_attribute :default_shard, instance_writer: false
  116. 3 self.filter_attributes = []
  117. 3 def self.connection_handler
  118. 597752 Thread.current.thread_variable_get(:ar_connection_handler) || default_connection_handler
  119. end
  120. 3 def self.connection_handler=(handler)
  121. 331 Thread.current.thread_variable_set(:ar_connection_handler, handler)
  122. end
  123. 3 def self.current_shard
  124. 270083 Thread.current.thread_variable_get(:ar_shard) || default_shard
  125. end
  126. 3 def self.current_shard=(shard)
  127. 114 Thread.current.thread_variable_set(:ar_shard, shard)
  128. end
  129. 3 self.default_connection_handler = ConnectionAdapters::ConnectionHandler.new
  130. 3 self.default_shard = :default
  131. end
  132. 3 module ClassMethods
  133. 3 def initialize_find_by_cache # :nodoc:
  134. 5033 @find_by_statement_cache = { true => Concurrent::Map.new, false => Concurrent::Map.new }
  135. end
  136. 3 def inherited(child_class) # :nodoc:
  137. # initialize cache at class definition for thread safety
  138. 2940 child_class.initialize_find_by_cache
  139. 2940 unless child_class.base_class?
  140. 628 klass = self
  141. 628 until klass.base_class?
  142. 69 klass.initialize_find_by_cache
  143. 69 klass = klass.superclass
  144. end
  145. end
  146. 2940 super
  147. end
  148. 3 def find(*ids) # :nodoc:
  149. # We don't have cache keys for this stuff yet
  150. 14129 return super unless ids.length == 1
  151. 13798 return super if block_given? || primary_key.nil? || scope_attributes?
  152. 2612 id = ids.first
  153. 2612 return super if StatementCache.unsupported_value?(id)
  154. 2552 key = primary_key
  155. 2552 statement = cached_find_by_statement(key) { |params|
  156. 401 where(key => params.bind).limit(1)
  157. }
  158. 2552 statement.execute([id], connection).first ||
  159. raise(RecordNotFound.new("Couldn't find #{name} with '#{key}'=#{id}", name, key, id))
  160. end
  161. 3 def find_by(*args) # :nodoc:
  162. 554 return super if scope_attributes?
  163. 464 hash = args.first
  164. 464 return super unless Hash === hash
  165. 965 values = hash.values.map! { |value| value.is_a?(Base) ? value.id : value }
  166. 962 return super if values.any? { |v| StatementCache.unsupported_value?(v) }
  167. 422 keys = hash.keys.map! do |key|
  168. 483 attribute_aliases[name = key.to_s] || begin
  169. 480 reflection = _reflect_on_association(name)
  170. 480 if reflection&.belongs_to? && !reflection.polymorphic?
  171. 6 reflection.join_foreign_key
  172. 474 elsif reflect_on_aggregation(name)
  173. 18 return super
  174. else
  175. 456 name
  176. end
  177. end
  178. end
  179. 869 return super unless keys.all? { |k| columns_hash.key?(k) }
  180. 392 statement = cached_find_by_statement(keys) { |params|
  181. 331 wheres = keys.index_with { params.bind }
  182. 161 where(wheres).limit(1)
  183. }
  184. 392 begin
  185. 392 statement.execute(values, connection).first
  186. 3 rescue TypeError
  187. raise ActiveRecord::StatementInvalid
  188. end
  189. end
  190. 3 def find_by!(*args) # :nodoc:
  191. 48 find_by(*args) || raise(RecordNotFound.new("Couldn't find #{name}", name))
  192. end
  193. 3 def initialize_generated_modules # :nodoc:
  194. 2943 generated_association_methods
  195. end
  196. 3 def generated_association_methods # :nodoc:
  197. 8316 @generated_association_methods ||= begin
  198. 2943 mod = const_set(:GeneratedAssociationMethods, Module.new)
  199. 2943 private_constant :GeneratedAssociationMethods
  200. 2943 include mod
  201. 2943 mod
  202. end
  203. end
  204. # Returns columns which shouldn't be exposed while calling +#inspect+.
  205. 3 def filter_attributes
  206. 559 if defined?(@filter_attributes)
  207. 278 @filter_attributes
  208. else
  209. 281 superclass.filter_attributes
  210. end
  211. end
  212. # Specifies columns which shouldn't be exposed while calling +#inspect+.
  213. 3 attr_writer :filter_attributes
  214. # Returns a string like 'Post(id:integer, title:string, body:text)'
  215. 3 def inspect # :nodoc:
  216. 21 if self == Base
  217. 3 super
  218. 18 elsif abstract_class?
  219. 3 "#{super}(abstract)"
  220. 15 elsif !connected?
  221. "#{super} (call '#{super}.connection' to establish a connection)"
  222. 15 elsif table_exists?
  223. 120 attr_list = attribute_types.map { |name, type| "#{name}: #{type.type}" } * ", "
  224. 12 "#{super}(#{attr_list})"
  225. else
  226. 3 "#{super}(Table doesn't exist)"
  227. end
  228. end
  229. # Overwrite the default class equality method to provide support for decorated models.
  230. 3 def ===(object) # :nodoc:
  231. 16811 object.is_a?(self)
  232. end
  233. # Returns an instance of <tt>Arel::Table</tt> loaded with the current table name.
  234. #
  235. # class Post < ActiveRecord::Base
  236. # scope :published_and_commented, -> { published.and(arel_table[:comments_count].gt(0)) }
  237. # end
  238. 3 def arel_table # :nodoc:
  239. 210653 @arel_table ||= Arel::Table.new(table_name, klass: self)
  240. end
  241. 3 def arel_attribute(name, table = arel_table) # :nodoc:
  242. 3 table[name]
  243. end
  244. 3 deprecate :arel_attribute
  245. 3 def predicate_builder # :nodoc:
  246. 171269 @predicate_builder ||= PredicateBuilder.new(table_metadata)
  247. end
  248. 3 def type_caster # :nodoc:
  249. 2548 TypeCaster::Map.new(self)
  250. end
  251. 3 def _internal? # :nodoc:
  252. false
  253. end
  254. 3 def cached_find_by_statement(key, &block) # :nodoc:
  255. 6174 cache = @find_by_statement_cache[connection.prepared_statements]
  256. 7436 cache.compute_if_absent(key) { StatementCache.create(connection, &block) }
  257. end
  258. 3 private
  259. 3 def relation
  260. 85519 relation = Relation.create(self)
  261. 85519 if finder_needs_type_condition? && !ignore_default_scope?
  262. 7281 relation.where!(type_condition)
  263. else
  264. 78238 relation
  265. end
  266. end
  267. 3 def table_metadata
  268. 1882 TableMetadata.new(self, arel_table)
  269. end
  270. end
  271. # New objects can be instantiated as either empty (pass no construction parameter) or pre-set with
  272. # attributes but not yet saved (pass a hash with key names matching the associated table column names).
  273. # In both instances, valid attribute keys are determined by the column names of the associated table --
  274. # hence you can't have attributes that aren't part of the table columns.
  275. #
  276. # ==== Example:
  277. # # Instantiates a single new object
  278. # User.new(first_name: 'Jamie')
  279. 3 def initialize(attributes = nil)
  280. 15758 @new_record = true
  281. 15758 @attributes = self.class._default_attributes.deep_dup
  282. 15758 init_internals
  283. 15758 initialize_internals_callback
  284. 15758 assign_attributes(attributes) if attributes
  285. 15713 yield self if block_given?
  286. 15710 _run_initialize_callbacks
  287. end
  288. # Initialize an empty model object from +coder+. +coder+ should be
  289. # the result of previously encoding an Active Record model, using
  290. # #encode_with.
  291. #
  292. # class Post < ActiveRecord::Base
  293. # end
  294. #
  295. # old_post = Post.new(title: "hello world")
  296. # coder = {}
  297. # old_post.encode_with(coder)
  298. #
  299. # post = Post.allocate
  300. # post.init_with(coder)
  301. # post.title # => 'hello world'
  302. 3 def init_with(coder, &block)
  303. 68 coder = LegacyYamlAdapter.convert(self.class, coder)
  304. 68 attributes = self.class.yaml_encoder.decode(coder)
  305. 68 init_with_attributes(attributes, coder["new_record"], &block)
  306. end
  307. ##
  308. # Initialize an empty model object from +attributes+.
  309. # +attributes+ should be an attributes object, and unlike the
  310. # `initialize` method, no assignment calls are made per attribute.
  311. 3 def init_with_attributes(attributes, new_record = false) # :nodoc:
  312. 246100 @new_record = new_record
  313. 246100 @attributes = attributes
  314. 246100 init_internals
  315. 246100 yield self if block_given?
  316. 246100 _run_find_callbacks
  317. 246100 _run_initialize_callbacks
  318. 246100 self
  319. end
  320. ##
  321. # :method: clone
  322. # Identical to Ruby's clone method. This is a "shallow" copy. Be warned that your attributes are not copied.
  323. # That means that modifying attributes of the clone will modify the original, since they will both point to the
  324. # same attributes hash. If you need a copy of your attributes hash, please use the #dup method.
  325. #
  326. # user = User.first
  327. # new_user = user.clone
  328. # user.name # => "Bob"
  329. # new_user.name = "Joe"
  330. # user.name # => "Joe"
  331. #
  332. # user.object_id == new_user.object_id # => false
  333. # user.name.object_id == new_user.name.object_id # => true
  334. #
  335. # user.name.object_id == user.dup.name.object_id # => false
  336. ##
  337. # :method: dup
  338. # Duped objects have no id assigned and are treated as new records. Note
  339. # that this is a "shallow" copy as it copies the object's attributes
  340. # only, not its associations. The extent of a "deep" copy is application
  341. # specific and is therefore left to the application to implement according
  342. # to its need.
  343. # The dup method does not preserve the timestamps (created|updated)_(at|on).
  344. ##
  345. 3 def initialize_dup(other) # :nodoc:
  346. 111 @attributes = @attributes.deep_dup
  347. 111 @attributes.reset(@primary_key)
  348. 111 _run_initialize_callbacks
  349. 111 @new_record = true
  350. 111 @previously_new_record = false
  351. 111 @destroyed = false
  352. 111 @_start_transaction_state = nil
  353. 111 super
  354. end
  355. # Populate +coder+ with attributes about this record that should be
  356. # serialized. The structure of +coder+ defined in this method is
  357. # guaranteed to match the structure of +coder+ passed to the #init_with
  358. # method.
  359. #
  360. # Example:
  361. #
  362. # class Post < ActiveRecord::Base
  363. # end
  364. # coder = {}
  365. # Post.new.encode_with(coder)
  366. # coder # => {"attributes" => {"id" => nil, ... }}
  367. 3 def encode_with(coder)
  368. 77 self.class.yaml_encoder.encode(@attributes, coder)
  369. 77 coder["new_record"] = new_record?
  370. 77 coder["active_record_yaml_version"] = 2
  371. end
  372. # Returns true if +comparison_object+ is the same exact object, or +comparison_object+
  373. # is of the same type and +self+ has an ID and it is equal to +comparison_object.id+.
  374. #
  375. # Note that new records are different from any other record by definition, unless the
  376. # other record is the receiver itself. Besides, if you fetch existing records with
  377. # +select+ and leave the ID out, you're on your own, this predicate will return false.
  378. #
  379. # Note also that destroying a record preserves its ID in the model instance, so deleted
  380. # models are still comparable.
  381. 3 def ==(comparison_object)
  382. 10382 super ||
  383. comparison_object.instance_of?(self.class) &&
  384. !id.nil? &&
  385. comparison_object.id == id
  386. end
  387. 3 alias :eql? :==
  388. # Delegates to id in order to allow two records of the same type and id to work with something like:
  389. # [ Person.find(1), Person.find(2), Person.find(3) ] & [ Person.find(1), Person.find(4) ] # => [ Person.find(1) ]
  390. 3 def hash
  391. 35231 if id
  392. 31629 self.class.hash ^ id.hash
  393. else
  394. 3602 super
  395. end
  396. end
  397. # Clone and freeze the attributes hash such that associations are still
  398. # accessible, even on destroyed records, but cloned models will not be
  399. # frozen.
  400. 3 def freeze
  401. 1294 @attributes = @attributes.clone.freeze
  402. 1294 self
  403. end
  404. # Returns +true+ if the attributes hash has been frozen.
  405. 3 def frozen?
  406. 17482 @attributes.frozen?
  407. end
  408. # Allows sort on objects
  409. 3 def <=>(other_object)
  410. 53 if other_object.is_a?(self.class)
  411. 41 to_key <=> other_object.to_key
  412. else
  413. 12 super
  414. end
  415. end
  416. 3 def present? # :nodoc:
  417. 75 true
  418. end
  419. 3 def blank? # :nodoc:
  420. 78 false
  421. end
  422. # Returns +true+ if the record is read only.
  423. 3 def readonly?
  424. 17591 @readonly
  425. end
  426. # Returns +true+ if the record is in strict_loading mode.
  427. 3 def strict_loading?
  428. 5563 @strict_loading
  429. end
  430. # Sets the record to strict_loading mode. This will raise an error
  431. # if the record tries to lazily load an association.
  432. #
  433. # user = User.first
  434. # user.strict_loading!
  435. # user.comments.to_a
  436. # => ActiveRecord::StrictLoadingViolationError
  437. 3 def strict_loading!
  438. 75 @strict_loading = true
  439. end
  440. # Marks this record as read only.
  441. 3 def readonly!
  442. 381 @readonly = true
  443. end
  444. 3 def connection_handler
  445. self.class.connection_handler
  446. end
  447. # Returns the contents of the record as a nicely formatted string.
  448. 3 def inspect
  449. # We check defined?(@attributes) not to issue warnings if the object is
  450. # allocated but not initialized.
  451. 268 inspection = if defined?(@attributes) && @attributes
  452. self.class.attribute_names.collect do |name|
  453. 2700 if _has_attribute?(name)
  454. 2601 attr = _read_attribute(name)
  455. 2601 value = if attr.nil?
  456. 380 attr.inspect
  457. else
  458. 2221 attr = format_for_inspect(attr)
  459. 2221 inspection_filter.filter_param(name, attr)
  460. end
  461. 2601 "#{name}: #{value}"
  462. end
  463. 265 end.compact.join(", ")
  464. else
  465. 3 "not initialized"
  466. end
  467. 268 "#<#{self.class} #{inspection}>"
  468. end
  469. # Takes a PP and prettily prints this record to it, allowing you to get a nice result from <tt>pp record</tt>
  470. # when pp is required.
  471. 3 def pretty_print(pp)
  472. 31 return super if custom_inspect_method_defined?
  473. 28 pp.object_address_group(self) do
  474. 28 if defined?(@attributes) && @attributes
  475. 286 attr_names = self.class.attribute_names.select { |name| _has_attribute?(name) }
  476. 261 pp.seplist(attr_names, proc { pp.text "," }) do |attr_name|
  477. 261 pp.breakable " "
  478. 261 pp.group(1) do
  479. 261 pp.text attr_name
  480. 261 pp.text ":"
  481. 261 pp.breakable
  482. 261 value = _read_attribute(attr_name)
  483. 261 value = inspection_filter.filter_param(attr_name, value) unless value.nil?
  484. 261 pp.pp value
  485. end
  486. end
  487. else
  488. 3 pp.breakable " "
  489. 3 pp.text "not initialized"
  490. end
  491. end
  492. end
  493. # Returns a hash of the given methods with their names as keys and returned values as values.
  494. 3 def slice(*methods)
  495. 24 Hash[methods.flatten.map! { |method| [method, public_send(method)] }].with_indifferent_access
  496. end
  497. 3 private
  498. # +Array#flatten+ will call +#to_ary+ (recursively) on each of the elements of
  499. # the array, and then rescues from the possible +NoMethodError+. If those elements are
  500. # +ActiveRecord::Base+'s, then this triggers the various +method_missing+'s that we have,
  501. # which significantly impacts upon performance.
  502. #
  503. # So we can avoid the +method_missing+ hit by explicitly defining +#to_ary+ as +nil+ here.
  504. #
  505. # See also https://tenderlovemaking.com/2011/06/28/til-its-ok-to-return-nil-from-to_ary.html
  506. 3 def to_ary
  507. nil
  508. end
  509. 3 def init_internals
  510. 261858 @primary_key = self.class.primary_key
  511. 261858 @readonly = false
  512. 261858 @previously_new_record = false
  513. 261858 @destroyed = false
  514. 261858 @marked_for_destruction = false
  515. 261858 @destroyed_by_association = nil
  516. 261858 @_start_transaction_state = nil
  517. 261858 @strict_loading = self.class.strict_loading_by_default
  518. 261858 self.class.define_attribute_methods
  519. end
  520. 3 def initialize_internals_callback
  521. end
  522. 3 def custom_inspect_method_defined?
  523. 31 self.class.instance_method(:inspect).owner != ActiveRecord::Base.instance_method(:inspect).owner
  524. end
  525. 3 class InspectionMask < DelegateClass(::String)
  526. 3 def pretty_print(pp)
  527. 6 pp.text __getobj__
  528. end
  529. end
  530. 3 private_constant :InspectionMask
  531. 3 def inspection_filter
  532. 2388 @inspection_filter ||= begin
  533. 248 mask = InspectionMask.new(ActiveSupport::ParameterFilter::FILTERED)
  534. 248 ActiveSupport::ParameterFilter.new(self.class.filter_attributes, mask: mask)
  535. end
  536. end
  537. end
  538. end

lib/active_record/counter_cache.rb

100.0% lines covered

51 relevant lines. 51 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 module ActiveRecord
  3. # = Active Record Counter Cache
  4. 3 module CounterCache
  5. 3 extend ActiveSupport::Concern
  6. 3 module ClassMethods
  7. # Resets one or more counter caches to their correct value using an SQL
  8. # count query. This is useful when adding new counter caches, or if the
  9. # counter has been corrupted or modified directly by SQL.
  10. #
  11. # ==== Parameters
  12. #
  13. # * +id+ - The id of the object you wish to reset a counter on.
  14. # * +counters+ - One or more association counters to reset. Association name or counter name can be given.
  15. # * <tt>:touch</tt> - Touch timestamp columns when updating.
  16. # Pass +true+ to touch +updated_at+ and/or +updated_on+. Pass a symbol to
  17. # touch that column or an array of symbols to touch just those ones.
  18. #
  19. # ==== Examples
  20. #
  21. # # For the Post with id #1, reset the comments_count
  22. # Post.reset_counters(1, :comments)
  23. #
  24. # # Like above, but also touch the +updated_at+ and/or +updated_on+
  25. # # attributes.
  26. # Post.reset_counters(1, :comments, touch: true)
  27. 3 def reset_counters(id, *counters, touch: nil)
  28. 60 object = find(id)
  29. 60 counters.each do |counter_association|
  30. 72 has_many_association = _reflect_on_association(counter_association)
  31. 72 unless has_many_association
  32. 6 has_many = reflect_on_all_associations(:has_many)
  33. 24 has_many_association = has_many.find { |association| association.counter_cache_column && association.counter_cache_column.to_sym == counter_association.to_sym }
  34. 6 counter_association = has_many_association.plural_name if has_many_association
  35. end
  36. 72 raise ArgumentError, "'#{name}' has no association called '#{counter_association}'" unless has_many_association
  37. 69 if has_many_association.is_a? ActiveRecord::Reflection::ThroughReflection
  38. 6 has_many_association = has_many_association.through_reflection
  39. end
  40. 69 foreign_key = has_many_association.foreign_key.to_s
  41. 69 child_class = has_many_association.klass
  42. 324 reflection = child_class._reflections.values.find { |e| e.belongs_to? && e.foreign_key.to_s == foreign_key && e.options[:counter_cache].present? }
  43. 69 counter_name = reflection.counter_cache_column
  44. 69 updates = { counter_name => object.send(counter_association).count(:all) }
  45. 69 if touch
  46. 27 names = touch if touch != true
  47. 27 names = Array.wrap(names)
  48. 27 options = names.extract_options!
  49. 27 touch_updates = touch_attributes_with_time(*names, **options)
  50. 27 updates.merge!(touch_updates)
  51. end
  52. 69 unscoped.where(primary_key => object.id).update_all(updates)
  53. end
  54. 57 true
  55. end
  56. # A generic "counter updater" implementation, intended primarily to be
  57. # used by #increment_counter and #decrement_counter, but which may also
  58. # be useful on its own. It simply does a direct SQL update for the record
  59. # with the given ID, altering the given hash of counters by the amount
  60. # given by the corresponding value:
  61. #
  62. # ==== Parameters
  63. #
  64. # * +id+ - The id of the object you wish to update a counter on or an array of ids.
  65. # * +counters+ - A Hash containing the names of the fields
  66. # to update as keys and the amount to update the field by as values.
  67. # * <tt>:touch</tt> option - Touch timestamp columns when updating.
  68. # If attribute names are passed, they are updated along with updated_at/on
  69. # attributes.
  70. #
  71. # ==== Examples
  72. #
  73. # # For the Post with id of 5, decrement the comment_count by 1, and
  74. # # increment the action_count by 1
  75. # Post.update_counters 5, comment_count: -1, action_count: 1
  76. # # Executes the following SQL:
  77. # # UPDATE posts
  78. # # SET comment_count = COALESCE(comment_count, 0) - 1,
  79. # # action_count = COALESCE(action_count, 0) + 1
  80. # # WHERE id = 5
  81. #
  82. # # For the Posts with id of 10 and 15, increment the comment_count by 1
  83. # Post.update_counters [10, 15], comment_count: 1
  84. # # Executes the following SQL:
  85. # # UPDATE posts
  86. # # SET comment_count = COALESCE(comment_count, 0) + 1
  87. # # WHERE id IN (10, 15)
  88. #
  89. # # For the Posts with id of 10 and 15, increment the comment_count by 1
  90. # # and update the updated_at value for each counter.
  91. # Post.update_counters [10, 15], comment_count: 1, touch: true
  92. # # Executes the following SQL:
  93. # # UPDATE posts
  94. # # SET comment_count = COALESCE(comment_count, 0) + 1,
  95. # # `updated_at` = '2016-10-13T09:59:23-05:00'
  96. # # WHERE id IN (10, 15)
  97. 3 def update_counters(id, counters)
  98. 618 unscoped.where!(primary_key => id).update_counters(counters)
  99. end
  100. # Increment a numeric field by one, via a direct SQL update.
  101. #
  102. # This method is used primarily for maintaining counter_cache columns that are
  103. # used to store aggregate values. For example, a +DiscussionBoard+ may cache
  104. # posts_count and comments_count to avoid running an SQL query to calculate the
  105. # number of posts and comments there are, each time it is displayed.
  106. #
  107. # ==== Parameters
  108. #
  109. # * +counter_name+ - The name of the field that should be incremented.
  110. # * +id+ - The id of the object that should be incremented or an array of ids.
  111. # * <tt>:touch</tt> - Touch timestamp columns when updating.
  112. # Pass +true+ to touch +updated_at+ and/or +updated_on+. Pass a symbol to
  113. # touch that column or an array of symbols to touch just those ones.
  114. #
  115. # ==== Examples
  116. #
  117. # # Increment the posts_count column for the record with an id of 5
  118. # DiscussionBoard.increment_counter(:posts_count, 5)
  119. #
  120. # # Increment the posts_count column for the record with an id of 5
  121. # # and update the updated_at value.
  122. # DiscussionBoard.increment_counter(:posts_count, 5, touch: true)
  123. 3 def increment_counter(counter_name, id, touch: nil)
  124. 42 update_counters(id, counter_name => 1, touch: touch)
  125. end
  126. # Decrement a numeric field by one, via a direct SQL update.
  127. #
  128. # This works the same as #increment_counter but reduces the column value by
  129. # 1 instead of increasing it.
  130. #
  131. # ==== Parameters
  132. #
  133. # * +counter_name+ - The name of the field that should be decremented.
  134. # * +id+ - The id of the object that should be decremented or an array of ids.
  135. # * <tt>:touch</tt> - Touch timestamp columns when updating.
  136. # Pass +true+ to touch +updated_at+ and/or +updated_on+. Pass a symbol to
  137. # touch that column or an array of symbols to touch just those ones.
  138. #
  139. # ==== Examples
  140. #
  141. # # Decrement the posts_count column for the record with an id of 5
  142. # DiscussionBoard.decrement_counter(:posts_count, 5)
  143. #
  144. # # Decrement the posts_count column for the record with an id of 5
  145. # # and update the updated_at value.
  146. # DiscussionBoard.decrement_counter(:posts_count, 5, touch: true)
  147. 3 def decrement_counter(counter_name, id, touch: nil)
  148. 30 update_counters(id, counter_name => -1, touch: touch)
  149. end
  150. end
  151. 3 private
  152. 3 def _create_record(attribute_names = self.attribute_names)
  153. 12437 id = super
  154. 12384 each_counter_cached_associations do |association|
  155. 836 association.increment_counters
  156. end
  157. 12384 id
  158. end
  159. 3 def destroy_row
  160. 886 affected_rows = super
  161. 883 if affected_rows > 0
  162. 871 each_counter_cached_associations do |association|
  163. 182 foreign_key = association.reflection.foreign_key.to_sym
  164. 182 unless destroyed_by_association && destroyed_by_association.foreign_key.to_sym == foreign_key
  165. 131 association.decrement_counters
  166. end
  167. end
  168. end
  169. 883 affected_rows
  170. end
  171. 3 def each_counter_cached_associations
  172. 13255 _reflections.each do |name, reflection|
  173. 132346 yield association(name.to_sym) if reflection.belongs_to? && reflection.counter_cache_column
  174. end
  175. end
  176. end
  177. end

lib/active_record/database_configurations.rb

99.26% lines covered

136 relevant lines. 135 lines covered and 1 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 require "active_record/database_configurations/database_config"
  3. 3 require "active_record/database_configurations/hash_config"
  4. 3 require "active_record/database_configurations/url_config"
  5. 3 require "active_record/database_configurations/connection_url_resolver"
  6. 3 module ActiveRecord
  7. # ActiveRecord::DatabaseConfigurations returns an array of DatabaseConfig
  8. # objects (either a HashConfig or UrlConfig) that are constructed from the
  9. # application's database configuration hash or URL string.
  10. 3 class DatabaseConfigurations
  11. 3 class InvalidConfigurationError < StandardError; end
  12. 3 attr_reader :configurations
  13. 3 delegate :any?, to: :configurations
  14. 3 def initialize(configurations = {})
  15. 538 @configurations = build_configs(configurations)
  16. end
  17. # Collects the configs for the environment and optionally the specification
  18. # name passed in. To include replica configurations pass <tt>include_replicas: true</tt>.
  19. #
  20. # If a name is provided a single DatabaseConfig object will be
  21. # returned, otherwise an array of DatabaseConfig objects will be
  22. # returned that corresponds with the environment and type requested.
  23. #
  24. # ==== Options
  25. #
  26. # * <tt>env_name:</tt> The environment name. Defaults to +nil+ which will collect
  27. # configs for all environments.
  28. # * <tt>name:</tt> The db config name (i.e. primary, animals, etc.). Defaults
  29. # to +nil+. If no +env_name+ is specified the config for the default env and the
  30. # passed +name+ will be returned.
  31. # * <tt>include_replicas:</tt> Determines whether to include replicas in
  32. # the returned list. Most of the time we're only iterating over the write
  33. # connection (i.e. migrations don't need to run for the write and read connection).
  34. # Defaults to +false+.
  35. 3 def configs_for(env_name: nil, spec_name: nil, name: nil, include_replicas: false)
  36. 613 if spec_name
  37. 3 name = spec_name
  38. 3 ActiveSupport::Deprecation.warn("The kwarg `spec_name` is deprecated in favor of `name`. `spec_name` will be removed in Rails 6.2")
  39. end
  40. 613 env_name ||= default_env if name
  41. 613 configs = env_with_configs(env_name)
  42. 613 unless include_replicas
  43. 610 configs = configs.select do |db_config|
  44. 768 !db_config.replica?
  45. end
  46. end
  47. 613 if name
  48. 443 configs.find do |db_config|
  49. 494 db_config.name == name
  50. end
  51. else
  52. 170 configs
  53. end
  54. end
  55. # Returns the config hash that corresponds with the environment
  56. #
  57. # If the application has multiple databases +default_hash+ will
  58. # return the first config hash for the environment.
  59. #
  60. # { database: "my_db", adapter: "mysql2" }
  61. 3 def default_hash(env = default_env)
  62. 3 default = find_db_config(env)
  63. 3 default.configuration_hash if default
  64. end
  65. 3 alias :[] :default_hash
  66. 3 deprecate "[]": "Use configs_for", default_hash: "Use configs_for"
  67. # Returns a single DatabaseConfig object based on the requested environment.
  68. #
  69. # If the application has multiple databases +find_db_config+ will return
  70. # the first DatabaseConfig for the environment.
  71. 3 def find_db_config(env)
  72. configurations
  73. 765 .sort_by { |db_config| db_config.for_current_env? ? 0 : 1 }
  74. 281 .find do |db_config|
  75. 396 db_config.env_name == env.to_s ||
  76. 183 (db_config.for_current_env? && db_config.name == env.to_s)
  77. end
  78. end
  79. # Returns the DatabaseConfigurations object as a Hash.
  80. 3 def to_h
  81. 12 configurations.inject({}) do |memo, db_config|
  82. 36 memo.merge(db_config.env_name => db_config.configuration_hash.stringify_keys)
  83. end
  84. end
  85. 3 deprecate to_h: "You can use `ActiveRecord::Base.configurations.configs_for(env_name: 'env', name: 'primary').configuration_hash` to get the configuration hashes."
  86. # Checks if the application's configurations are empty.
  87. #
  88. # Aliased to blank?
  89. 3 def empty?
  90. 6 configurations.empty?
  91. end
  92. 3 alias :blank? :empty?
  93. 3 def each
  94. 3 throw_getter_deprecation(:each)
  95. 3 configurations.each { |config|
  96. 9 yield [config.env_name, config.configuration_hash]
  97. }
  98. end
  99. 3 def first
  100. 3 throw_getter_deprecation(:first)
  101. 3 config = configurations.first
  102. 3 [config.env_name, config.configuration_hash]
  103. end
  104. # Returns fully resolved connection, accepts hash, string or symbol.
  105. # Always returns a DatabaseConfiguration::DatabaseConfig
  106. #
  107. # == Examples
  108. #
  109. # Symbol representing current environment.
  110. #
  111. # DatabaseConfigurations.new("production" => {}).resolve(:production)
  112. # # => DatabaseConfigurations::HashConfig.new(env_name: "production", config: {})
  113. #
  114. # One layer deep hash of connection values.
  115. #
  116. # DatabaseConfigurations.new({}).resolve("adapter" => "sqlite3")
  117. # # => DatabaseConfigurations::HashConfig.new(config: {"adapter" => "sqlite3"})
  118. #
  119. # Connection URL.
  120. #
  121. # DatabaseConfigurations.new({}).resolve("postgresql://localhost/foo")
  122. # # => DatabaseConfigurations::UrlConfig.new(config: {"adapter" => "postgresql", "host" => "localhost", "database" => "foo"})
  123. 3 def resolve(config) # :nodoc:
  124. 1356 return config if DatabaseConfigurations::DatabaseConfig === config
  125. 528 case config
  126. when Symbol
  127. 272 resolve_symbol_connection(config)
  128. when Hash, String
  129. 253 build_db_config_from_raw_config(default_env, "primary", config)
  130. else
  131. 3 raise TypeError, "Invalid type for configuration. Expected Symbol, String, or Hash. Got #{config.inspect}"
  132. end
  133. end
  134. 3 private
  135. 3 def default_env
  136. 835 ActiveRecord::ConnectionHandling::DEFAULT_ENV.call.to_s
  137. end
  138. 3 def env_with_configs(env = nil)
  139. 613 if env
  140. 2500 configurations.select { |db_config| db_config.env_name == env }
  141. else
  142. 47 configurations
  143. end
  144. end
  145. 3 def build_configs(configs)
  146. 540 return configs.configurations if configs.is_a?(DatabaseConfigurations)
  147. 349 return configs if configs.is_a?(Array)
  148. 347 db_configs = configs.flat_map do |env_name, config|
  149. 1082 if config.is_a?(Hash) && config.all? { |_, v| v.is_a?(Hash) }
  150. 187 walk_configs(env_name.to_s, config)
  151. else
  152. 282 build_db_config_from_raw_config(env_name.to_s, "primary", config)
  153. end
  154. end
  155. 341 unless db_configs.find(&:for_current_env?)
  156. 232 db_configs << environment_url_config(default_env, "primary", {})
  157. end
  158. 341 merge_db_environment_variables(default_env, db_configs.compact)
  159. end
  160. 3 def walk_configs(env_name, config)
  161. 187 config.map do |name, sub_config|
  162. 343 build_db_config_from_raw_config(env_name, name.to_s, sub_config)
  163. end
  164. end
  165. 3 def resolve_symbol_connection(name)
  166. 272 if db_config = find_db_config(name)
  167. 269 db_config
  168. else
  169. 3 raise AdapterNotSpecified, <<~MSG
  170. The `#{name}` database is not configured for the `#{default_env}` environment.
  171. Available databases configurations are:
  172. #{build_configuration_sentence}
  173. MSG
  174. end
  175. end
  176. 3 def build_configuration_sentence
  177. 3 configs = configs_for(include_replicas: true)
  178. configs.group_by(&:env_name).map do |env, config|
  179. 6 names = config.map(&:name)
  180. 6 if names.size > 1
  181. "#{env}: #{names.join(", ")}"
  182. else
  183. 6 env
  184. end
  185. 3 end.join("\n")
  186. end
  187. 3 def build_db_config_from_raw_config(env_name, name, config)
  188. 878 case config
  189. when String
  190. 49 build_db_config_from_string(env_name, name, config)
  191. when Hash
  192. 826 build_db_config_from_hash(env_name, name, config.symbolize_keys)
  193. else
  194. 3 raise InvalidConfigurationError, "'{ #{env_name} => #{config} }' is not a valid configuration. Expected '#{config}' to be a URL string or a Hash."
  195. end
  196. end
  197. 3 def build_db_config_from_string(env_name, name, config)
  198. 49 url = config
  199. 49 uri = URI.parse(url)
  200. 49 if uri.scheme
  201. 43 UrlConfig.new(env_name, name, url)
  202. else
  203. 6 raise InvalidConfigurationError, "'{ #{env_name} => #{config} }' is not a valid configuration. Expected '#{config}' to be a URL string or a Hash."
  204. end
  205. end
  206. 3 def build_db_config_from_hash(env_name, name, config)
  207. 826 if config.has_key?(:url)
  208. 129 url = config[:url]
  209. 129 config_without_url = config.dup
  210. 129 config_without_url.delete :url
  211. 129 UrlConfig.new(env_name, name, url, config_without_url)
  212. else
  213. 697 HashConfig.new(env_name, name, config)
  214. end
  215. end
  216. 3 def merge_db_environment_variables(current_env, configs)
  217. 341 configs.map do |config|
  218. 658 next config if config.is_a?(UrlConfig) || config.env_name != current_env
  219. 149 url_config = environment_url_config(current_env, config.name, config.configuration_hash)
  220. 149 url_config || config
  221. end
  222. end
  223. 3 def environment_url_config(env, name, config)
  224. 381 url = environment_value_for(name)
  225. 381 return unless url
  226. 81 UrlConfig.new(env, name, url, config)
  227. end
  228. 3 def environment_value_for(name)
  229. 381 name_env_key = "#{name.upcase}_DATABASE_URL"
  230. 381 url = ENV[name_env_key]
  231. 381 url ||= ENV["DATABASE_URL"] if name == "primary"
  232. 381 url
  233. end
  234. 3 def method_missing(method, *args, &blk)
  235. 14 case method
  236. when :fetch
  237. 3 throw_getter_deprecation(method)
  238. 3 configs_for(env_name: args.first)
  239. when :values
  240. 6 throw_getter_deprecation(method)
  241. 6 configurations.map(&:configuration_hash)
  242. when :[]=
  243. 2 throw_setter_deprecation(method)
  244. 2 env_name = args[0]
  245. 2 config = args[1]
  246. 8 remaining_configs = configurations.reject { |db_config| db_config.env_name == env_name }
  247. 2 new_config = build_configs(env_name => config)
  248. 2 new_configs = remaining_configs + new_config
  249. 2 ActiveRecord::Base.configurations = new_configs
  250. else
  251. 3 raise NotImplementedError, "`ActiveRecord::Base.configurations` in Rails 6 now returns an object instead of a hash. The `#{method}` method is not supported. Please use `configs_for` or consult the documentation for supported methods."
  252. end
  253. end
  254. 3 def throw_setter_deprecation(method)
  255. 2 ActiveSupport::Deprecation.warn("Setting `ActiveRecord::Base.configurations` with `#{method}` is deprecated. Use `ActiveRecord::Base.configurations=` directly to set the configurations instead.")
  256. end
  257. 3 def throw_getter_deprecation(method)
  258. 15 ActiveSupport::Deprecation.warn("`ActiveRecord::Base.configurations` no longer returns a hash. Methods that act on the hash like `#{method}` are deprecated and will be removed in Rails 6.1. Use the `configs_for` method to collect and iterate over the database configurations.")
  259. end
  260. end
  261. end

lib/active_record/database_configurations/connection_url_resolver.rb

100.0% lines covered

30 relevant lines. 30 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 require "active_support/core_ext/enumerable"
  3. 3 module ActiveRecord
  4. 3 class DatabaseConfigurations
  5. # Expands a connection string into a hash.
  6. 3 class ConnectionUrlResolver # :nodoc:
  7. # == Example
  8. #
  9. # url = "postgresql://foo:bar@localhost:9000/foo_test?pool=5&timeout=3000"
  10. # ConnectionUrlResolver.new(url).to_hash
  11. # # => {
  12. # adapter: "postgresql",
  13. # host: "localhost",
  14. # port: 9000,
  15. # database: "foo_test",
  16. # username: "foo",
  17. # password: "bar",
  18. # pool: "5",
  19. # timeout: "3000"
  20. # }
  21. 3 def initialize(url)
  22. 247 raise "Database URL cannot be empty" if url.blank?
  23. 247 @uri = uri_parser.parse(url)
  24. 247 @adapter = @uri.scheme && @uri.scheme.tr("-", "_")
  25. 247 @adapter = "postgresql" if @adapter == "postgres"
  26. 247 if @uri.opaque
  27. 10 @uri.opaque, @query = @uri.opaque.split("?", 2)
  28. else
  29. 237 @query = @uri.query
  30. end
  31. end
  32. # Converts the given URL to a full connection hash.
  33. 3 def to_hash
  34. 247 config = raw_config.compact_blank
  35. 989 config.map { |key, value| config[key] = uri_parser.unescape(value) if value.is_a? String }
  36. 247 config
  37. end
  38. 3 private
  39. 3 attr_reader :uri
  40. 3 def uri_parser
  41. 983 @uri_parser ||= URI::Parser.new
  42. end
  43. # Converts the query parameters of the URI into a hash.
  44. #
  45. # "localhost?pool=5&reaping_frequency=2"
  46. # # => { pool: "5", reaping_frequency: "2" }
  47. #
  48. # returns empty hash if no query present.
  49. #
  50. # "localhost"
  51. # # => {}
  52. 3 def query_hash
  53. 277 Hash[(@query || "").split("&").map { |pair| pair.split("=", 2) }].symbolize_keys
  54. end
  55. 3 def raw_config
  56. 247 if uri.opaque
  57. 10 query_hash.merge(
  58. adapter: @adapter,
  59. database: uri.opaque
  60. )
  61. else
  62. 237 query_hash.merge(
  63. adapter: @adapter,
  64. username: uri.user,
  65. password: uri.password,
  66. port: uri.port,
  67. database: database_from_path,
  68. host: uri.hostname
  69. )
  70. end
  71. end
  72. # Returns name of the database.
  73. 3 def database_from_path
  74. 237 if @adapter == "sqlite3"
  75. # 'sqlite3:/foo' is absolute, because that makes sense. The
  76. # corresponding relative version, 'sqlite3:foo', is handled
  77. # elsewhere, as an "opaque".
  78. 7 uri.path
  79. else
  80. # Only SQLite uses a filename as the "database" name; for
  81. # anything else, a leading slash would be silly.
  82. 230 uri.path.delete_prefix("/")
  83. end
  84. end
  85. end
  86. end
  87. end

lib/active_record/database_configurations/database_config.rb

69.23% lines covered

39 relevant lines. 27 lines covered and 12 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 module ActiveRecord
  3. 3 class DatabaseConfigurations
  4. # ActiveRecord::Base.configurations will return either a HashConfig or
  5. # UrlConfig respectively. It will never return a DatabaseConfig object,
  6. # as this is the parent class for the types of database configuration objects.
  7. 3 class DatabaseConfig # :nodoc:
  8. 3 attr_reader :env_name, :name
  9. 3 attr_accessor :owner_name
  10. 3 def initialize(env_name, name)
  11. 1028 @env_name = env_name
  12. 1028 @name = name
  13. end
  14. 3 def spec_name
  15. 3 @name
  16. end
  17. 3 deprecate spec_name: "please use name instead"
  18. 3 def config
  19. raise NotImplementedError
  20. end
  21. 3 def adapter_method
  22. 1625 "#{adapter}_connection"
  23. end
  24. 3 def host
  25. raise NotImplementedError
  26. end
  27. 3 def database
  28. raise NotImplementedError
  29. end
  30. 3 def _database=(database)
  31. raise NotImplementedError
  32. end
  33. 3 def adapter
  34. raise NotImplementedError
  35. end
  36. 3 def pool
  37. raise NotImplementedError
  38. end
  39. 3 def checkout_timeout
  40. raise NotImplementedError
  41. end
  42. 3 def reaping_frequency
  43. raise NotImplementedError
  44. end
  45. 3 def idle_timeout
  46. raise NotImplementedError
  47. end
  48. 3 def replica?
  49. raise NotImplementedError
  50. end
  51. 3 def migrations_paths
  52. raise NotImplementedError
  53. end
  54. 3 def for_current_env?
  55. 1440 env_name == ActiveRecord::ConnectionHandling::DEFAULT_ENV.call
  56. end
  57. 3 def schema_cache_path
  58. raise NotImplementedError
  59. end
  60. end
  61. end
  62. end

lib/active_record/database_configurations/hash_config.rb

93.94% lines covered

33 relevant lines. 31 lines covered and 2 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 module ActiveRecord
  3. 3 class DatabaseConfigurations
  4. # A HashConfig object is created for each database configuration entry that
  5. # is created from a hash.
  6. #
  7. # A hash config:
  8. #
  9. # { "development" => { "database" => "db_name" } }
  10. #
  11. # Becomes:
  12. #
  13. # #<ActiveRecord::DatabaseConfigurations::HashConfig:0x00007fd1acbded10
  14. # @env_name="development", @name="primary", @config={database: "db_name"}>
  15. #
  16. # ==== Options
  17. #
  18. # * <tt>:env_name</tt> - The Rails environment, i.e. "development".
  19. # * <tt>:name</tt> - The db config name. In a standard two-tier
  20. # database configuration this will default to "primary". In a multiple
  21. # database three-tier database configuration this corresponds to the name
  22. # used in the second tier, for example "primary_readonly".
  23. # * <tt>:config</tt> - The config hash. This is the hash that contains the
  24. # database adapter, name, and other important information for database
  25. # connections.
  26. 3 class HashConfig < DatabaseConfig
  27. 3 attr_reader :configuration_hash
  28. 3 def initialize(env_name, name, configuration_hash)
  29. 1028 super(env_name, name)
  30. 1028 @configuration_hash = configuration_hash.symbolize_keys.freeze
  31. end
  32. 3 def config
  33. 3 ActiveSupport::Deprecation.warn("DatabaseConfig#config will be removed in 6.2.0 in favor of DatabaseConfigurations#configuration_hash which returns a hash with symbol keys")
  34. 3 configuration_hash.stringify_keys
  35. end
  36. # Determines whether a database configuration is for a replica / readonly
  37. # connection. If the +replica+ key is present in the config, +replica?+ will
  38. # return +true+.
  39. 3 def replica?
  40. 768 configuration_hash[:replica]
  41. end
  42. # The migrations paths for a database configuration. If the
  43. # +migrations_paths+ key is present in the config, +migrations_paths+
  44. # will return its value.
  45. 3 def migrations_paths
  46. configuration_hash[:migrations_paths]
  47. end
  48. 3 def host
  49. 44 configuration_hash[:host]
  50. end
  51. 3 def database
  52. 409 configuration_hash[:database]
  53. end
  54. 3 def _database=(database) # :nodoc:
  55. 8 @configuration_hash = configuration_hash.merge(database: database).freeze
  56. end
  57. 3 def pool
  58. 984 (configuration_hash[:pool] || 5).to_i
  59. end
  60. 3 def checkout_timeout
  61. 984 (configuration_hash[:checkout_timeout] || 5).to_f
  62. end
  63. # +reaping_frequency+ is configurable mostly for historical reasons, but it could
  64. # also be useful if someone wants a very low +idle_timeout+.
  65. 3 def reaping_frequency
  66. 984 configuration_hash.fetch(:reaping_frequency, 60)&.to_f
  67. end
  68. 3 def idle_timeout
  69. 987 timeout = configuration_hash.fetch(:idle_timeout, 300).to_f
  70. 987 timeout if timeout > 0
  71. end
  72. 3 def adapter
  73. 3287 configuration_hash[:adapter]
  74. end
  75. # The path to the schema cache dump file for a database.
  76. # If omitted, the filename will be read from ENV or a
  77. # default will be derived.
  78. 3 def schema_cache_path
  79. configuration_hash[:schema_cache_path]
  80. end
  81. end
  82. end
  83. end

lib/active_record/database_configurations/url_config.rb

100.0% lines covered

13 relevant lines. 13 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 module ActiveRecord
  3. 3 class DatabaseConfigurations
  4. # A UrlConfig object is created for each database configuration
  5. # entry that is created from a URL. This can either be a URL string
  6. # or a hash with a URL in place of the config hash.
  7. #
  8. # A URL config:
  9. #
  10. # postgres://localhost/foo
  11. #
  12. # Becomes:
  13. #
  14. # #<ActiveRecord::DatabaseConfigurations::UrlConfig:0x00007fdc3238f340
  15. # @env_name="default_env", @name="primary",
  16. # @config={adapter: "postgresql", database: "foo", host: "localhost"},
  17. # @url="postgres://localhost/foo">
  18. #
  19. # ==== Options
  20. #
  21. # * <tt>:env_name</tt> - The Rails environment, ie "development".
  22. # * <tt>:name</tt> - The db config name. In a standard two-tier
  23. # database configuration this will default to "primary". In a multiple
  24. # database three-tier database configuration this corresponds to the name
  25. # used in the second tier, for example "primary_readonly".
  26. # * <tt>:url</tt> - The database URL.
  27. # * <tt>:config</tt> - The config hash. This is the hash that contains the
  28. # database adapter, name, and other important information for database
  29. # connections.
  30. 3 class UrlConfig < HashConfig
  31. 3 attr_reader :url
  32. 3 def initialize(env_name, name, url, configuration_hash = {})
  33. 253 super(env_name, name, configuration_hash)
  34. 253 @url = url
  35. 253 @configuration_hash = @configuration_hash.merge(build_url_hash).freeze
  36. end
  37. 3 private
  38. # Return a Hash that can be merged into the main config that represents
  39. # the passed in url
  40. 3 def build_url_hash
  41. 253 if url.nil? || url.start_with?("jdbc:")
  42. 6 { url: url }
  43. else
  44. 247 ConnectionUrlResolver.new(url).to_hash
  45. end
  46. end
  47. end
  48. end
  49. end

lib/active_record/delegated_type.rb

100.0% lines covered

25 relevant lines. 25 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 require "active_support/core_ext/string/inquiry"
  3. 3 module ActiveRecord
  4. # == Delegated types
  5. #
  6. # Class hierarchies can map to relational database tables in many ways. Active Record, for example, offers
  7. # purely abstract classes, where the superclass doesn't persist any attributes, and single-table inheritance,
  8. # where all attributes from all levels of the hierarchy are represented in a single table. Both have their
  9. # places, but neither are without their drawbacks.
  10. #
  11. # The problem with purely abstract classes is that all concrete subclasses must persist all the shared
  12. # attributes themselves in their own tables (also known as class-table inheritance). This makes it hard to
  13. # do queries across the hierarchy. For example, imagine you have the following hierarchy:
  14. #
  15. # Entry < ApplicationRecord
  16. # Message < Entry
  17. # Comment < Entry
  18. #
  19. # How do you show a feed that has both +Message+ and +Comment+ records, which can be easily paginated?
  20. # Well, you can't! Messages are backed by a messages table and comments by a comments table. You can't
  21. # pull from both tables at once and use a consistent OFFSET/LIMIT scheme.
  22. #
  23. # You can get around the pagination problem by using single-table inheritance, but now you're forced into
  24. # a single mega table with all the attributes from all subclasses. No matter how divergent. If a Message
  25. # has a subject, but the comment does not, well, now the comment does anyway! So STI works best when there's
  26. # little divergence between the subclasses and their attributes.
  27. #
  28. # But there's a third way: Delegated types. With this approach, the "superclass" is a concrete class
  29. # that is represented by its own table, where all the superclass attributes that are shared amongst all the
  30. # "subclasses" are stored. And then each of the subclasses have their own individual tables for additional
  31. # attributes that are particular to their implementation. This is similar to what's called multi-table
  32. # inheritance in Django, but instead of actual inheritance, this approach uses delegation to form the
  33. # hierarchy and share responsibilities.
  34. #
  35. # Let's look at that entry/message/comment example using delegated types:
  36. #
  37. # # Schema: entries[ id, account_id, creator_id, created_at, updated_at, entryable_type, entryable_id ]
  38. # class Entry < ApplicationRecord
  39. # belongs_to :account
  40. # belongs_to :creator
  41. # delegated_type :entryable, types: %w[ Message Comment ]
  42. # end
  43. #
  44. # module Entryable
  45. # extend ActiveSupport::Concern
  46. #
  47. # included do
  48. # has_one :entry, as: :entryable, touch: true
  49. # end
  50. # end
  51. #
  52. # # Schema: messages[ id, subject ]
  53. # class Message < ApplicationRecord
  54. # include Entryable
  55. # has_rich_text :content
  56. # end
  57. #
  58. # # Schema: comments[ id, content ]
  59. # class Comment < ApplicationRecord
  60. # include Entryable
  61. # end
  62. #
  63. # As you can see, neither +Message+ nor +Comment+ are meant to stand alone. Crucial metadata for both classes
  64. # resides in the +Entry+ "superclass". But the +Entry+ absolutely can stand alone in terms of querying capacity
  65. # in particular. You can now easily do things like:
  66. #
  67. # Account.entries.order(created_at: :desc).limit(50)
  68. #
  69. # Which is exactly what you want when displaying both comments and messages together. The entry itself can
  70. # be rendered as its delegated type easily, like so:
  71. #
  72. # # entries/_entry.html.erb
  73. # <%= render "entries/entryables/#{entry.entryable_name}", entry: entry %>
  74. #
  75. # # entries/entryables/_message.html.erb
  76. # <div class="message">
  77. # Posted on <%= entry.created_at %> by <%= entry.creator.name %>: <%= entry.message.content %>
  78. # </div>
  79. #
  80. # # entries/entryables/_comment.html.erb
  81. # <div class="comment">
  82. # <%= entry.creator.name %> said: <%= entry.comment.content %>
  83. # </div>
  84. #
  85. # == Sharing behavior with concerns and controllers
  86. #
  87. # The entry "superclass" also serves as a perfect place to put all that shared logic that applies to both
  88. # messages and comments, and which acts primarily on the shared attributes. Imagine:
  89. #
  90. # class Entry < ApplicationRecord
  91. # include Eventable, Forwardable, Redeliverable
  92. # end
  93. #
  94. # Which allows you to have controllers for things like +ForwardsController+ and +RedeliverableController+
  95. # that both act on entries, and thus provide the shared functionality to both messages and comments.
  96. #
  97. # == Creating new records
  98. #
  99. # You create a new record that uses delegated typing by creating the delegator and delegatee at the same time,
  100. # like so:
  101. #
  102. # Entry.create! entryable: Comment.new(content: "Hello!"), creator: Current.user
  103. #
  104. # If you need more complicated composition, or you need to perform dependent validation, you should build a factory
  105. # method or class to take care of the complicated needs. This could be as simple as:
  106. #
  107. # class Entry < ApplicationRecord
  108. # def self.create_with_comment(content, creator: Current.user)
  109. # create! entryable: Comment.new(content: content), creator: creator
  110. # end
  111. # end
  112. #
  113. # == Adding further delegation
  114. #
  115. # The delegated type shouldn't just answer the question of what the underlying class is called. In fact, that's
  116. # an anti-pattern most of the time. The reason you're building this hierarchy is to take advantage of polymorphism.
  117. # So here's a simple example of that:
  118. #
  119. # class Entry < ApplicationRecord
  120. # delegated_type :entryable, types: %w[ Message Comment ]
  121. # delegate :title, to: :entryable
  122. # end
  123. #
  124. # class Message < ApplicationRecord
  125. # def title
  126. # subject
  127. # end
  128. # end
  129. #
  130. # class Comment < ApplicationRecord
  131. # def title
  132. # content.truncate(20)
  133. # end
  134. # end
  135. #
  136. # Now you can list a bunch of entries, call +Entry#title+, and polymorphism will provide you with the answer.
  137. 3 module DelegatedType
  138. # Defines this as a class that'll delegate its type for the passed +role+ to the class references in +types+.
  139. # That'll create a polymorphic +belongs_to+ relationship to that +role+, and it'll add all the delegated
  140. # type convenience methods:
  141. #
  142. # class Entry < ApplicationRecord
  143. # delegated_type :entryable, types: %w[ Message Comment ], dependent: :destroy
  144. # end
  145. #
  146. # Entry#entryable_class # => +Message+ or +Comment+
  147. # Entry#entryable_name # => "message" or "comment"
  148. # Entry.messages # => Entry.where(entryable_type: "Message")
  149. # Entry#message? # => true when entryable_type == "Message"
  150. # Entry#message # => returns the message record, when entryable_type == "Message", otherwise nil
  151. # Entry#message_id # => returns entryable_id, when entryable_type == "Message", otherwise nil
  152. # Entry.comments # => Entry.where(entryable_type: "Comment")
  153. # Entry#comment? # => true when entryable_type == "Comment"
  154. # Entry#comment # => returns the comment record, when entryable_type == "Comment", otherwise nil
  155. # Entry#comment_id # => returns entryable_id, when entryable_type == "Comment", otherwise nil
  156. #
  157. # The +options+ are passed directly to the +belongs_to+ call, so this is where you declare +dependent+ etc.
  158. #
  159. # You can also declare namespaced types:
  160. #
  161. # class Entry < ApplicationRecord
  162. # delegated_type :entryable, types: %w[ Message Comment Access::NoticeMessage ], dependent: :destroy
  163. # end
  164. #
  165. # Entry.access_notice_messages
  166. # entry.access_notice_message
  167. # entry.access_notice_message?
  168. 3 def delegated_type(role, types:, **options)
  169. 3 belongs_to role, options.delete(:scope), **options.merge(polymorphic: true)
  170. 3 define_delegated_type_methods role, types: types
  171. end
  172. 3 private
  173. 3 def define_delegated_type_methods(role, types:)
  174. 3 role_type = "#{role}_type"
  175. 3 role_id = "#{role}_id"
  176. 3 define_method "#{role}_class" do
  177. 18 public_send("#{role}_type").constantize
  178. end
  179. 3 define_method "#{role}_name" do
  180. 12 public_send("#{role}_class").model_name.singular.inquiry
  181. end
  182. 3 types.each do |type|
  183. 6 scope_name = type.tableize.gsub("/", "_")
  184. 6 singular = scope_name.singularize
  185. 6 query = "#{singular}?"
  186. 12 scope scope_name, -> { where(role_type => type) }
  187. 6 define_method query do
  188. 42 public_send(role_type) == type
  189. end
  190. 6 define_method singular do
  191. 12 public_send(role) if public_send(query)
  192. end
  193. 6 define_method "#{singular}_id" do
  194. 12 public_send(role_id) if public_send(query)
  195. end
  196. end
  197. end
  198. end
  199. end

lib/active_record/dynamic_matchers.rb

96.67% lines covered

60 relevant lines. 58 lines covered and 2 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 module ActiveRecord
  3. 3 module DynamicMatchers #:nodoc:
  4. 3 private
  5. 3 def respond_to_missing?(name, _)
  6. 398295 if self == Base
  7. 1332 super
  8. else
  9. 396963 match = Method.match(self, name)
  10. 396963 match && match.valid? || super
  11. end
  12. end
  13. 3 def method_missing(name, *arguments, &block)
  14. 148 match = Method.match(self, name)
  15. 148 if match && match.valid?
  16. 130 match.define
  17. 130 send(name, *arguments, &block)
  18. else
  19. 18 super
  20. end
  21. end
  22. 3 class Method
  23. 3 @matchers = []
  24. 3 class << self
  25. 3 attr_reader :matchers
  26. 3 def match(model, name)
  27. 1191161 klass = matchers.find { |k| k.pattern.match?(name) }
  28. 397111 klass.new(model, name) if klass
  29. end
  30. 3 def pattern
  31. 794241 @pattern ||= /\A#{prefix}_([_a-zA-Z]\w*)#{suffix}\Z/
  32. end
  33. 3 def prefix
  34. raise NotImplementedError
  35. end
  36. 3 def suffix
  37. 3 ""
  38. end
  39. end
  40. 3 attr_reader :model, :name, :attribute_names
  41. 3 def initialize(model, method_name)
  42. 191 @model = model
  43. 191 @name = method_name.to_s
  44. 191 @attribute_names = @name.match(self.class.pattern)[1].split("_and_")
  45. 414 @attribute_names.map! { |name| @model.attribute_aliases[name] || name }
  46. end
  47. 3 def valid?
  48. 414 attribute_names.all? { |name| model.columns_hash[name] || model.reflect_on_aggregation(name.to_sym) }
  49. end
  50. 3 def define
  51. 130 model.class_eval <<-CODE, __FILE__, __LINE__ + 1
  52. def self.#{name}(#{signature})
  53. #{body}
  54. end
  55. CODE
  56. end
  57. 3 private
  58. 3 def body
  59. 130 "#{finder}(#{attributes_hash})"
  60. end
  61. # The parameters in the signature may have reserved Ruby words, in order
  62. # to prevent errors, we start each param name with `_`.
  63. 3 def signature
  64. 281 attribute_names.map { |name| "_#{name}" }.join(", ")
  65. end
  66. # Given that the parameters starts with `_`, the finder needs to use the
  67. # same parameter name.
  68. 3 def attributes_hash
  69. 281 "{" + attribute_names.map { |name| ":#{name} => _#{name}" }.join(",") + "}"
  70. end
  71. 3 def finder
  72. raise NotImplementedError
  73. end
  74. end
  75. 3 class FindBy < Method
  76. 3 Method.matchers << self
  77. 3 def self.prefix
  78. 3 "find_by"
  79. end
  80. 3 def finder
  81. 120 "find_by"
  82. end
  83. end
  84. 3 class FindByBang < Method
  85. 3 Method.matchers << self
  86. 3 def self.prefix
  87. 3 "find_by"
  88. end
  89. 3 def self.suffix
  90. 3 "!"
  91. end
  92. 3 def finder
  93. 10 "find_by!"
  94. end
  95. end
  96. end
  97. end

lib/active_record/enum.rb

99.04% lines covered

104 relevant lines. 103 lines covered and 1 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 require "active_support/core_ext/object/deep_dup"
  3. 3 module ActiveRecord
  4. # Declare an enum attribute where the values map to integers in the database,
  5. # but can be queried by name. Example:
  6. #
  7. # class Conversation < ActiveRecord::Base
  8. # enum status: [ :active, :archived ]
  9. # end
  10. #
  11. # # conversation.update! status: 0
  12. # conversation.active!
  13. # conversation.active? # => true
  14. # conversation.status # => "active"
  15. #
  16. # # conversation.update! status: 1
  17. # conversation.archived!
  18. # conversation.archived? # => true
  19. # conversation.status # => "archived"
  20. #
  21. # # conversation.status = 1
  22. # conversation.status = "archived"
  23. #
  24. # conversation.status = nil
  25. # conversation.status.nil? # => true
  26. # conversation.status # => nil
  27. #
  28. # Scopes based on the allowed values of the enum field will be provided
  29. # as well. With the above example:
  30. #
  31. # Conversation.active
  32. # Conversation.not_active
  33. # Conversation.archived
  34. # Conversation.not_archived
  35. #
  36. # Of course, you can also query them directly if the scopes don't fit your
  37. # needs:
  38. #
  39. # Conversation.where(status: [:active, :archived])
  40. # Conversation.where.not(status: :active)
  41. #
  42. # Defining scopes can be disabled by setting +:_scopes+ to +false+.
  43. #
  44. # class Conversation < ActiveRecord::Base
  45. # enum status: [ :active, :archived ], _scopes: false
  46. # end
  47. #
  48. # You can set the default enum value by setting +:_default+, like:
  49. #
  50. # class Conversation < ActiveRecord::Base
  51. # enum status: [ :active, :archived ], _default: "active"
  52. # end
  53. #
  54. # conversation = Conversation.new
  55. # conversation.status # => "active"
  56. #
  57. # Finally, it's also possible to explicitly map the relation between attribute and
  58. # database integer with a hash:
  59. #
  60. # class Conversation < ActiveRecord::Base
  61. # enum status: { active: 0, archived: 1 }
  62. # end
  63. #
  64. # Note that when an array is used, the implicit mapping from the values to database
  65. # integers is derived from the order the values appear in the array. In the example,
  66. # <tt>:active</tt> is mapped to +0+ as it's the first element, and <tt>:archived</tt>
  67. # is mapped to +1+. In general, the +i+-th element is mapped to <tt>i-1</tt> in the
  68. # database.
  69. #
  70. # Therefore, once a value is added to the enum array, its position in the array must
  71. # be maintained, and new values should only be added to the end of the array. To
  72. # remove unused values, the explicit hash syntax should be used.
  73. #
  74. # In rare circumstances you might need to access the mapping directly.
  75. # The mappings are exposed through a class method with the pluralized attribute
  76. # name, which return the mapping in a +HashWithIndifferentAccess+:
  77. #
  78. # Conversation.statuses[:active] # => 0
  79. # Conversation.statuses["archived"] # => 1
  80. #
  81. # Use that class method when you need to know the ordinal value of an enum.
  82. # For example, you can use that when manually building SQL strings:
  83. #
  84. # Conversation.where("status <> ?", Conversation.statuses[:archived])
  85. #
  86. # You can use the +:_prefix+ or +:_suffix+ options when you need to define
  87. # multiple enums with same values. If the passed value is +true+, the methods
  88. # are prefixed/suffixed with the name of the enum. It is also possible to
  89. # supply a custom value:
  90. #
  91. # class Conversation < ActiveRecord::Base
  92. # enum status: [:active, :archived], _suffix: true
  93. # enum comments_status: [:active, :inactive], _prefix: :comments
  94. # end
  95. #
  96. # With the above example, the bang and predicate methods along with the
  97. # associated scopes are now prefixed and/or suffixed accordingly:
  98. #
  99. # conversation.active_status!
  100. # conversation.archived_status? # => false
  101. #
  102. # conversation.comments_inactive!
  103. # conversation.comments_active? # => false
  104. 3 module Enum
  105. 3 def self.extended(base) # :nodoc:
  106. 3 base.class_attribute(:defined_enums, instance_writer: false, default: {})
  107. end
  108. 3 def inherited(base) # :nodoc:
  109. 2940 base.defined_enums = defined_enums.deep_dup
  110. 2940 super
  111. end
  112. 3 class EnumType < Type::Value # :nodoc:
  113. 3 delegate :type, to: :subtype
  114. 3 def initialize(name, mapping, subtype)
  115. 291 @name = name
  116. 291 @mapping = mapping
  117. 291 @subtype = subtype
  118. end
  119. 3 def cast(value)
  120. 381 if mapping.has_key?(value)
  121. 156 value.to_s
  122. 225 elsif mapping.has_value?(value)
  123. 207 mapping.key(value)
  124. 18 elsif value.blank?
  125. nil
  126. else
  127. assert_valid_value(value)
  128. end
  129. end
  130. 3 def deserialize(value)
  131. 2684 mapping.key(subtype.deserialize(value))
  132. end
  133. 3 def serializable?(value)
  134. 309 (value.blank? || mapping.has_key?(value) || mapping.has_value?(value)) && super
  135. end
  136. 3 def serialize(value)
  137. 2340 mapping.fetch(value, value)
  138. end
  139. 3 def assert_valid_value(value)
  140. 267 unless serializable?(value)
  141. 3 raise ArgumentError, "'#{value}' is not a valid #{name}"
  142. end
  143. end
  144. 3 private
  145. 3 attr_reader :name, :mapping, :subtype
  146. end
  147. 3 def enum(definitions)
  148. 141 klass = self
  149. 141 enum_prefix = definitions.delete(:_prefix)
  150. 141 enum_suffix = definitions.delete(:_suffix)
  151. 141 enum_scopes = definitions.delete(:_scopes)
  152. 141 default = {}
  153. 141 default[:default] = definitions.delete(:_default) if definitions.key?(:_default)
  154. 141 definitions.each do |name, values|
  155. 144 assert_valid_enum_definition_values(values)
  156. # statuses = { }
  157. 135 enum_values = ActiveSupport::HashWithIndifferentAccess.new
  158. 135 name = name.to_s
  159. # def self.statuses() statuses end
  160. 135 detect_enum_conflict!(name, name.pluralize, true)
  161. 159 singleton_class.define_method(name.pluralize) { enum_values }
  162. 132 defined_enums[name] = enum_values
  163. 132 detect_enum_conflict!(name, name)
  164. 126 detect_enum_conflict!(name, "#{name}=")
  165. 126 attr = attribute_alias?(name) ? attribute_alias(name) : name
  166. 126 decorate_attribute_type(attr, **default) do |subtype|
  167. 291 EnumType.new(attr, enum_values, subtype)
  168. end
  169. 126 _enum_methods_module.module_eval do
  170. 126 pairs = values.respond_to?(:each_pair) ? values.each_pair : values.each_with_index
  171. 126 pairs.each do |label, value|
  172. 276 if enum_prefix == true
  173. 12 prefix = "#{name}_"
  174. 264 elsif enum_prefix
  175. 18 prefix = "#{enum_prefix}_"
  176. end
  177. 276 if enum_suffix == true
  178. 9 suffix = "_#{name}"
  179. 267 elsif enum_suffix
  180. 9 suffix = "_#{enum_suffix}"
  181. end
  182. 276 value_method_name = "#{prefix}#{label}#{suffix}"
  183. 276 enum_values[label] = value
  184. 276 label = label.to_s
  185. # def active?() status == "active" end
  186. 276 klass.send(:detect_enum_conflict!, name, "#{value_method_name}?")
  187. 465 define_method("#{value_method_name}?") { self[attr] == label }
  188. # def active!() update!(status: 0) end
  189. 270 klass.send(:detect_enum_conflict!, name, "#{value_method_name}!")
  190. 288 define_method("#{value_method_name}!") { update!(attr => value) }
  191. # scope :active, -> { where(status: 0) }
  192. # scope :not_active, -> { where.not(status: 0) }
  193. 267 if enum_scopes != false
  194. 261 klass.send(:detect_negative_condition!, value_method_name)
  195. 261 klass.send(:detect_enum_conflict!, name, value_method_name, true)
  196. 309 klass.scope value_method_name, -> { where(attr => value) }
  197. 231 klass.send(:detect_enum_conflict!, name, "not_#{value_method_name}", true)
  198. 237 klass.scope "not_#{value_method_name}", -> { where.not(attr => value) }
  199. end
  200. end
  201. end
  202. 87 enum_values.freeze
  203. end
  204. end
  205. 3 private
  206. 3 def _enum_methods_module
  207. 918 @_enum_methods_module ||= begin
  208. 66 mod = Module.new
  209. 66 include mod
  210. 66 mod
  211. end
  212. end
  213. 3 def assert_valid_enum_definition_values(values)
  214. 417 unless values.is_a?(Hash) || values.all? { |v| v.is_a?(Symbol) } || values.all? { |v| v.is_a?(String) }
  215. 3 error_message = <<~MSG
  216. Enum values #{values} must be either a hash, an array of symbols, or an array of strings.
  217. MSG
  218. 3 raise ArgumentError, error_message
  219. end
  220. 141 if values.is_a?(Hash) && values.keys.any?(&:blank?) || values.is_a?(Array) && values.any?(&:blank?)
  221. 6 raise ArgumentError, "Enum label name must not be blank."
  222. end
  223. end
  224. 3 ENUM_CONFLICT_MESSAGE = \
  225. "You tried to define an enum named \"%{enum}\" on the model \"%{klass}\", but " \
  226. "this will generate a %{type} method \"%{method}\", which is already defined " \
  227. "by %{source}."
  228. 3 private_constant :ENUM_CONFLICT_MESSAGE
  229. 3 def detect_enum_conflict!(enum_name, method_name, klass_method = false)
  230. 1431 if klass_method && dangerous_class_method?(method_name)
  231. 24 raise_conflict_error(enum_name, method_name, type: "class")
  232. 1407 elsif klass_method && method_defined_within?(method_name, Relation)
  233. 9 raise_conflict_error(enum_name, method_name, type: "class", source: Relation.name)
  234. 1398 elsif !klass_method && dangerous_attribute_method?(method_name)
  235. 12 raise_conflict_error(enum_name, method_name)
  236. 1386 elsif !klass_method && method_defined_within?(method_name, _enum_methods_module, Module)
  237. 3 raise_conflict_error(enum_name, method_name, source: "another enum")
  238. end
  239. end
  240. 3 def raise_conflict_error(enum_name, method_name, type: "instance", source: "Active Record")
  241. 48 raise ArgumentError, ENUM_CONFLICT_MESSAGE % {
  242. enum: enum_name,
  243. klass: name,
  244. type: type,
  245. method: method_name,
  246. source: source
  247. }
  248. end
  249. 3 def detect_negative_condition!(method_name)
  250. 261 if method_name.start_with?("not_") && logger
  251. 3 logger.warn "An enum element in #{self.name} uses the prefix 'not_'." \
  252. " This will cause a conflict with auto generated negative scopes."
  253. end
  254. end
  255. end
  256. end

lib/active_record/errors.rb

96.91% lines covered

97 relevant lines. 94 lines covered and 3 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 module ActiveRecord
  3. # = Active Record Errors
  4. #
  5. # Generic Active Record exception class.
  6. 3 class ActiveRecordError < StandardError
  7. end
  8. # Raised when the single-table inheritance mechanism fails to locate the subclass
  9. # (for example due to improper usage of column that
  10. # {ActiveRecord::Base.inheritance_column}[rdoc-ref:ModelSchema::ClassMethods#inheritance_column]
  11. # points to).
  12. 3 class SubclassNotFound < ActiveRecordError
  13. end
  14. # Raised when an object assigned to an association has an incorrect type.
  15. #
  16. # class Ticket < ActiveRecord::Base
  17. # has_many :patches
  18. # end
  19. #
  20. # class Patch < ActiveRecord::Base
  21. # belongs_to :ticket
  22. # end
  23. #
  24. # # Comments are not patches, this assignment raises AssociationTypeMismatch.
  25. # @ticket.patches << Comment.new(content: "Please attach tests to your patch.")
  26. 3 class AssociationTypeMismatch < ActiveRecordError
  27. end
  28. # Raised when unserialized object's type mismatches one specified for serializable field.
  29. 3 class SerializationTypeMismatch < ActiveRecordError
  30. end
  31. # Raised when adapter not specified on connection (or configuration file
  32. # +config/database.yml+ misses adapter field).
  33. 3 class AdapterNotSpecified < ActiveRecordError
  34. end
  35. # Raised when a model makes a query but it has not specified an associated table.
  36. 3 class TableNotSpecified < ActiveRecordError
  37. end
  38. # Raised when Active Record cannot find database adapter specified in
  39. # +config/database.yml+ or programmatically.
  40. 3 class AdapterNotFound < ActiveRecordError
  41. end
  42. # Raised when connection to the database could not been established (for example when
  43. # {ActiveRecord::Base.connection=}[rdoc-ref:ConnectionHandling#connection]
  44. # is given a +nil+ object).
  45. 3 class ConnectionNotEstablished < ActiveRecordError
  46. end
  47. # Raised when a write to the database is attempted on a read only connection.
  48. 3 class ReadOnlyError < ActiveRecordError
  49. end
  50. # Raised when Active Record cannot find a record by given id or set of ids.
  51. 3 class RecordNotFound < ActiveRecordError
  52. 3 attr_reader :model, :primary_key, :id
  53. 3 def initialize(message = nil, model = nil, primary_key = nil, id = nil)
  54. 332 @primary_key = primary_key
  55. 332 @model = model
  56. 332 @id = id
  57. 332 super(message)
  58. end
  59. end
  60. # Raised by {ActiveRecord::Base#save!}[rdoc-ref:Persistence#save!] and
  61. # {ActiveRecord::Base.create!}[rdoc-ref:Persistence::ClassMethods#create!]
  62. # methods when a record is invalid and cannot be saved.
  63. 3 class RecordNotSaved < ActiveRecordError
  64. 3 attr_reader :record
  65. 3 def initialize(message = nil, record = nil)
  66. 52 @record = record
  67. 52 super(message)
  68. end
  69. end
  70. # Raised by {ActiveRecord::Base#destroy!}[rdoc-ref:Persistence#destroy!]
  71. # when a call to {#destroy}[rdoc-ref:Persistence#destroy!]
  72. # would return false.
  73. #
  74. # begin
  75. # complex_operation_that_internally_calls_destroy!
  76. # rescue ActiveRecord::RecordNotDestroyed => invalid
  77. # puts invalid.record.errors
  78. # end
  79. #
  80. 3 class RecordNotDestroyed < ActiveRecordError
  81. 3 attr_reader :record
  82. 3 def initialize(message = nil, record = nil)
  83. 18 @record = record
  84. 18 super(message)
  85. end
  86. end
  87. # Superclass for all database execution errors.
  88. #
  89. # Wraps the underlying database error as +cause+.
  90. 3 class StatementInvalid < ActiveRecordError
  91. 3 def initialize(message = nil, sql: nil, binds: nil)
  92. 2367 super(message || $!&.message)
  93. 2367 @sql = sql
  94. 2367 @binds = binds
  95. end
  96. 3 attr_reader :sql, :binds
  97. end
  98. # Defunct wrapper class kept for compatibility.
  99. # StatementInvalid wraps the original exception now.
  100. 3 class WrappedDatabaseException < StatementInvalid
  101. end
  102. # Raised when a record cannot be inserted or updated because it would violate a uniqueness constraint.
  103. 3 class RecordNotUnique < WrappedDatabaseException
  104. end
  105. # Raised when a record cannot be inserted or updated because it references a non-existent record,
  106. # or when a record cannot be deleted because a parent record references it.
  107. 3 class InvalidForeignKey < WrappedDatabaseException
  108. end
  109. # Raised when a foreign key constraint cannot be added because the column type does not match the referenced column type.
  110. 3 class MismatchedForeignKey < StatementInvalid
  111. 3 def initialize(
  112. message: nil,
  113. sql: nil,
  114. binds: nil,
  115. table: nil,
  116. foreign_key: nil,
  117. target_table: nil,
  118. primary_key: nil,
  119. primary_key_column: nil
  120. )
  121. 3 if table
  122. type = primary_key_column.bigint? ? :bigint : primary_key_column.type
  123. msg = <<~EOM.squish
  124. Column `#{foreign_key}` on table `#{table}` does not match column `#{primary_key}` on `#{target_table}`,
  125. which has type `#{primary_key_column.sql_type}`.
  126. To resolve this issue, change the type of the `#{foreign_key}` column on `#{table}` to be :#{type}.
  127. (For example `t.#{type} :#{foreign_key}`).
  128. EOM
  129. else
  130. 3 msg = <<~EOM.squish
  131. There is a mismatch between the foreign key and primary key column types.
  132. Verify that the foreign key column type and the primary key of the associated table match types.
  133. EOM
  134. end
  135. 3 if message
  136. msg << "\nOriginal message: #{message}"
  137. end
  138. 3 super(msg, sql: sql, binds: binds)
  139. end
  140. end
  141. # Raised when a record cannot be inserted or updated because it would violate a not null constraint.
  142. 3 class NotNullViolation < StatementInvalid
  143. end
  144. # Raised when a record cannot be inserted or updated because a value too long for a column type.
  145. 3 class ValueTooLong < StatementInvalid
  146. end
  147. # Raised when values that executed are out of range.
  148. 3 class RangeError < StatementInvalid
  149. end
  150. # Raised when the number of placeholders in an SQL fragment passed to
  151. # {ActiveRecord::Base.where}[rdoc-ref:QueryMethods#where]
  152. # does not match the number of values supplied.
  153. #
  154. # For example, when there are two placeholders with only one value supplied:
  155. #
  156. # Location.where("lat = ? AND lng = ?", 53.7362)
  157. 3 class PreparedStatementInvalid < ActiveRecordError
  158. end
  159. # Raised when a given database does not exist.
  160. 3 class NoDatabaseError < StatementInvalid
  161. end
  162. # Raised when creating a database if it exists.
  163. 3 class DatabaseAlreadyExists < StatementInvalid
  164. end
  165. # Raised when PostgreSQL returns 'cached plan must not change result type' and
  166. # we cannot retry gracefully (e.g. inside a transaction)
  167. 3 class PreparedStatementCacheExpired < StatementInvalid
  168. end
  169. # Raised on attempt to save stale record. Record is stale when it's being saved in another query after
  170. # instantiation, for example, when two users edit the same wiki page and one starts editing and saves
  171. # the page before the other.
  172. #
  173. # Read more about optimistic locking in ActiveRecord::Locking module
  174. # documentation.
  175. 3 class StaleObjectError < ActiveRecordError
  176. 3 attr_reader :record, :attempted_action
  177. 3 def initialize(record = nil, attempted_action = nil)
  178. 57 if record && attempted_action
  179. 54 @record = record
  180. 54 @attempted_action = attempted_action
  181. 54 super("Attempted to #{attempted_action} a stale object: #{record.class.name}.")
  182. else
  183. 3 super("Stale object error.")
  184. end
  185. end
  186. end
  187. # Raised when association is being configured improperly or user tries to use
  188. # offset and limit together with
  189. # {ActiveRecord::Base.has_many}[rdoc-ref:Associations::ClassMethods#has_many] or
  190. # {ActiveRecord::Base.has_and_belongs_to_many}[rdoc-ref:Associations::ClassMethods#has_and_belongs_to_many]
  191. # associations.
  192. 3 class ConfigurationError < ActiveRecordError
  193. end
  194. # Raised on attempt to update record that is instantiated as read only.
  195. 3 class ReadOnlyRecord < ActiveRecordError
  196. end
  197. # Raised on attempt to lazily load records that are marked as strict loading.
  198. 3 class StrictLoadingViolationError < ActiveRecordError
  199. end
  200. # {ActiveRecord::Base.transaction}[rdoc-ref:Transactions::ClassMethods#transaction]
  201. # uses this exception to distinguish a deliberate rollback from other exceptional situations.
  202. # Normally, raising an exception will cause the
  203. # {.transaction}[rdoc-ref:Transactions::ClassMethods#transaction] method to rollback
  204. # the database transaction *and* pass on the exception. But if you raise an
  205. # ActiveRecord::Rollback exception, then the database transaction will be rolled back,
  206. # without passing on the exception.
  207. #
  208. # For example, you could do this in your controller to rollback a transaction:
  209. #
  210. # class BooksController < ActionController::Base
  211. # def create
  212. # Book.transaction do
  213. # book = Book.new(params[:book])
  214. # book.save!
  215. # if today_is_friday?
  216. # # The system must fail on Friday so that our support department
  217. # # won't be out of job. We silently rollback this transaction
  218. # # without telling the user.
  219. # raise ActiveRecord::Rollback, "Call tech support!"
  220. # end
  221. # end
  222. # # ActiveRecord::Rollback is the only exception that won't be passed on
  223. # # by ActiveRecord::Base.transaction, so this line will still be reached
  224. # # even on Friday.
  225. # redirect_to root_url
  226. # end
  227. # end
  228. 3 class Rollback < ActiveRecordError
  229. end
  230. # Raised when attribute has a name reserved by Active Record (when attribute
  231. # has name of one of Active Record instance methods).
  232. 3 class DangerousAttributeError < ActiveRecordError
  233. end
  234. # Raised when unknown attributes are supplied via mass assignment.
  235. 3 UnknownAttributeError = ActiveModel::UnknownAttributeError
  236. # Raised when an error occurred while doing a mass assignment to an attribute through the
  237. # {ActiveRecord::Base#attributes=}[rdoc-ref:AttributeAssignment#attributes=] method.
  238. # The exception has an +attribute+ property that is the name of the offending attribute.
  239. 3 class AttributeAssignmentError < ActiveRecordError
  240. 3 attr_reader :exception, :attribute
  241. 3 def initialize(message = nil, exception = nil, attribute = nil)
  242. 27 super(message)
  243. 27 @exception = exception
  244. 27 @attribute = attribute
  245. end
  246. end
  247. # Raised when there are multiple errors while doing a mass assignment through the
  248. # {ActiveRecord::Base#attributes=}[rdoc-ref:AttributeAssignment#attributes=]
  249. # method. The exception has an +errors+ property that contains an array of AttributeAssignmentError
  250. # objects, each corresponding to the error while assigning to an attribute.
  251. 3 class MultiparameterAssignmentErrors < ActiveRecordError
  252. 3 attr_reader :errors
  253. 3 def initialize(errors = nil)
  254. 27 @errors = errors
  255. end
  256. end
  257. # Raised when a primary key is needed, but not specified in the schema or model.
  258. 3 class UnknownPrimaryKey < ActiveRecordError
  259. 3 attr_reader :model
  260. 3 def initialize(model = nil, description = nil)
  261. 21 if model
  262. 18 message = "Unknown primary key for table #{model.table_name} in model #{model}."
  263. 18 message += "\n#{description}" if description
  264. 18 @model = model
  265. 18 super(message)
  266. else
  267. 3 super("Unknown primary key.")
  268. end
  269. end
  270. end
  271. # Raised when a relation cannot be mutated because it's already loaded.
  272. #
  273. # class Task < ActiveRecord::Base
  274. # end
  275. #
  276. # relation = Task.all
  277. # relation.loaded? # => true
  278. #
  279. # # Methods which try to mutate a loaded relation fail.
  280. # relation.where!(title: 'TODO') # => ActiveRecord::ImmutableRelation
  281. # relation.limit!(5) # => ActiveRecord::ImmutableRelation
  282. 3 class ImmutableRelation < ActiveRecordError
  283. end
  284. # TransactionIsolationError will be raised under the following conditions:
  285. #
  286. # * The adapter does not support setting the isolation level
  287. # * You are joining an existing open transaction
  288. # * You are creating a nested (savepoint) transaction
  289. #
  290. # The mysql2 and postgresql adapters support setting the transaction isolation level.
  291. 3 class TransactionIsolationError < ActiveRecordError
  292. end
  293. # TransactionRollbackError will be raised when a transaction is rolled
  294. # back by the database due to a serialization failure or a deadlock.
  295. #
  296. # See the following:
  297. #
  298. # * https://www.postgresql.org/docs/current/static/transaction-iso.html
  299. # * https://dev.mysql.com/doc/refman/en/server-error-reference.html#error_er_lock_deadlock
  300. 3 class TransactionRollbackError < StatementInvalid
  301. end
  302. # SerializationFailure will be raised when a transaction is rolled
  303. # back by the database due to a serialization failure.
  304. 3 class SerializationFailure < TransactionRollbackError
  305. end
  306. # Deadlocked will be raised when a transaction is rolled
  307. # back by the database when a deadlock is encountered.
  308. 3 class Deadlocked < TransactionRollbackError
  309. end
  310. # IrreversibleOrderError is raised when a relation's order is too complex for
  311. # +reverse_order+ to automatically reverse.
  312. 3 class IrreversibleOrderError < ActiveRecordError
  313. end
  314. # Superclass for errors that have been aborted (either by client or server).
  315. 3 class QueryAborted < StatementInvalid
  316. end
  317. # LockWaitTimeout will be raised when lock wait timeout exceeded.
  318. 3 class LockWaitTimeout < StatementInvalid
  319. end
  320. # StatementTimeout will be raised when statement timeout exceeded.
  321. 3 class StatementTimeout < QueryAborted
  322. end
  323. # QueryCanceled will be raised when canceling statement due to user request.
  324. 3 class QueryCanceled < QueryAborted
  325. end
  326. # AdapterTimeout will be raised when database clients times out while waiting from the server.
  327. 3 class AdapterTimeout < QueryAborted
  328. end
  329. # UnknownAttributeReference is raised when an unknown and potentially unsafe
  330. # value is passed to a query method when allow_unsafe_raw_sql is set to
  331. # :disabled. For example, passing a non column name value to a relation's
  332. # #order method might cause this exception.
  333. #
  334. # When working around this exception, caution should be taken to avoid SQL
  335. # injection vulnerabilities when passing user-provided values to query
  336. # methods. Known-safe values can be passed to query methods by wrapping them
  337. # in Arel.sql.
  338. #
  339. # For example, with allow_unsafe_raw_sql set to :disabled, the following
  340. # code would raise this exception:
  341. #
  342. # Post.order("length(title)").first
  343. #
  344. # The desired result can be accomplished by wrapping the known-safe string
  345. # in Arel.sql:
  346. #
  347. # Post.order(Arel.sql("length(title)")).first
  348. #
  349. # Again, such a workaround should *not* be used when passing user-provided
  350. # values, such as request parameters or model attributes to query methods.
  351. 3 class UnknownAttributeReference < ActiveRecordError
  352. end
  353. end

lib/active_record/explain.rb

85.71% lines covered

28 relevant lines. 24 lines covered and 4 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 require "active_record/explain_registry"
  3. 3 module ActiveRecord
  4. 3 module Explain
  5. # Executes the block with the collect flag enabled. Queries are collected
  6. # asynchronously by the subscriber and returned.
  7. 3 def collecting_queries_for_explain # :nodoc:
  8. 15 ExplainRegistry.collect = true
  9. 15 yield
  10. 15 ExplainRegistry.queries
  11. ensure
  12. 15 ExplainRegistry.reset
  13. end
  14. # Makes the adapter execute EXPLAIN for the tuples of queries and bindings.
  15. # Returns a formatted string ready to be logged.
  16. 3 def exec_explain(queries) # :nodoc:
  17. 18 str = queries.map do |sql, binds|
  18. 27 msg = +"EXPLAIN for: #{sql}"
  19. 27 unless binds.empty?
  20. 21 msg << " "
  21. 42 msg << binds.map { |attr| render_bind(attr) }.inspect
  22. end
  23. 27 msg << "\n"
  24. 27 msg << connection.explain(sql, binds)
  25. end.join("\n")
  26. # Overriding inspect to be more human readable, especially in the console.
  27. 18 def str.inspect
  28. self
  29. end
  30. 18 str
  31. end
  32. 3 private
  33. 3 def render_bind(attr)
  34. 21 if ActiveModel::Attribute === attr
  35. 21 value = if attr.type.binary? && attr.value
  36. "<#{attr.value_for_database.to_s.bytesize} bytes of binary data>"
  37. else
  38. 21 connection.type_cast(attr.value_for_database)
  39. end
  40. else
  41. value = connection.type_cast(attr)
  42. attr = nil
  43. end
  44. 21 [attr&.name, value]
  45. end
  46. end
  47. end

lib/active_record/explain_registry.rb

100.0% lines covered

12 relevant lines. 12 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 require "active_support/per_thread_registry"
  3. 3 module ActiveRecord
  4. # This is a thread locals registry for EXPLAIN. For example
  5. #
  6. # ActiveRecord::ExplainRegistry.queries
  7. #
  8. # returns the collected queries local to the current thread.
  9. #
  10. # See the documentation of ActiveSupport::PerThreadRegistry
  11. # for further details.
  12. 3 class ExplainRegistry # :nodoc:
  13. 3 extend ActiveSupport::PerThreadRegistry
  14. 3 attr_accessor :queries, :collect
  15. 3 def initialize
  16. 116 reset
  17. end
  18. 3 def collect?
  19. 428045 @collect
  20. end
  21. 3 def reset
  22. 173 @collect = false
  23. 173 @queries = []
  24. end
  25. end
  26. end

lib/active_record/explain_subscriber.rb

100.0% lines covered

13 relevant lines. 13 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 require "active_support/notifications"
  3. 3 require "active_record/explain_registry"
  4. 3 module ActiveRecord
  5. 3 class ExplainSubscriber # :nodoc:
  6. 3 def start(name, id, payload)
  7. # unused
  8. end
  9. 3 def finish(name, id, payload)
  10. 428045 if ExplainRegistry.collect? && !ignore_payload?(payload)
  11. 24 ExplainRegistry.queries << payload.values_at(:sql, :binds)
  12. end
  13. end
  14. # SCHEMA queries cannot be EXPLAINed, also we do not want to run EXPLAIN on
  15. # our own EXPLAINs no matter how loopingly beautiful that would be.
  16. #
  17. # On the other hand, we want to monitor the performance of our real database
  18. # queries, not the performance of the access to the query cache.
  19. 3 IGNORED_PAYLOADS = %w(SCHEMA EXPLAIN)
  20. 3 EXPLAINED_SQLS = /\A\s*(with|select|update|delete|insert)\b/i
  21. 3 def ignore_payload?(payload)
  22. 39 payload[:exception] ||
  23. payload[:cached] ||
  24. IGNORED_PAYLOADS.include?(payload[:name]) ||
  25. !payload[:sql].match?(EXPLAINED_SQLS)
  26. end
  27. 3 ActiveSupport::Notifications.subscribe("sql.active_record", new)
  28. end
  29. end

lib/active_record/fixture_set/file.rb

100.0% lines covered

37 relevant lines. 37 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 require "active_support/configuration_file"
  3. 3 module ActiveRecord
  4. 3 class FixtureSet
  5. 3 class File # :nodoc:
  6. 3 include Enumerable
  7. ##
  8. # Open a fixture file named +file+. When called with a block, the block
  9. # is called with the filehandle and the filehandle is automatically closed
  10. # when the block finishes.
  11. 3 def self.open(file)
  12. 7094 x = new file
  13. 7094 block_given? ? yield(x) : x
  14. end
  15. 3 def initialize(file)
  16. 7094 @file = file
  17. end
  18. 3 def each(&block)
  19. 7079 rows.each(&block)
  20. end
  21. 3 def model_class
  22. 7073 config_row["model_class"]
  23. end
  24. 3 def ignored_fixtures
  25. 6278 config_row["ignore"]
  26. end
  27. 3 private
  28. 3 def rows
  29. 301640 @rows ||= raw_rows.reject { |fixture_name, _| fixture_name == "_fixture" }
  30. end
  31. 3 def config_row
  32. 13351 @config_row ||= begin
  33. 300659 row = raw_rows.find { |fixture_name, _| fixture_name == "_fixture" }
  34. 7043 if row
  35. 138 row.last
  36. else
  37. 6905 { 'model_class': nil, 'ignore': nil }
  38. end
  39. end
  40. end
  41. 3 def raw_rows
  42. 14134 @raw_rows ||= begin
  43. 7094 data = ActiveSupport::ConfigurationFile.parse(@file, context:
  44. ActiveRecord::FixtureSet::RenderContext.create_subclass.new.get_binding)
  45. 7085 data ? validate(data).to_a : []
  46. rescue RuntimeError => error
  47. 3 raise Fixture::FormatError, error.message
  48. end
  49. end
  50. # Validate our unmarshalled data.
  51. 3 def validate(data)
  52. 7085 unless Hash === data || YAML::Omap === data
  53. 6 raise Fixture::FormatError, "fixture is not a hash: #{@file}"
  54. end
  55. 301655 invalid = data.reject { |_, row| Hash === row }
  56. 7079 if invalid.any?
  57. 6 raise Fixture::FormatError, "fixture key is not a hash: #{@file}, keys: #{invalid.keys.inspect}"
  58. end
  59. 7073 data
  60. end
  61. end
  62. end
  63. end

lib/active_record/fixture_set/model_metadata.rb

100.0% lines covered

16 relevant lines. 16 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 module ActiveRecord
  3. 3 class FixtureSet
  4. 3 class ModelMetadata # :nodoc:
  5. 3 def initialize(model_class)
  6. 5831 @model_class = model_class
  7. end
  8. 3 def primary_key_name
  9. 307412 @primary_key_name ||= @model_class && @model_class.primary_key
  10. end
  11. 3 def primary_key_type
  12. 2393 @primary_key_type ||= @model_class && @model_class.type_for_attribute(@model_class.primary_key).type
  13. end
  14. 3 def has_primary_key_column?
  15. 291744 @has_primary_key_column ||= primary_key_name &&
  16. 5741 @model_class.columns.any? { |col| col.name == primary_key_name }
  17. end
  18. 3 def timestamp_column_names
  19. 291570 @model_class.all_timestamp_attributes_in_model
  20. end
  21. 3 def inheritance_column_name
  22. 303075 @inheritance_column_name ||= @model_class && @model_class.inheritance_column
  23. end
  24. end
  25. end
  26. end

lib/active_record/fixture_set/render_context.rb

100.0% lines covered

7 relevant lines. 7 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. # NOTE: This class has to be defined in compact style in
  3. # order for rendering context subclassing to work correctly.
  4. 3 class ActiveRecord::FixtureSet::RenderContext # :nodoc:
  5. 3 def self.create_subclass
  6. 7094 Class.new(ActiveRecord::FixtureSet.context_class) do
  7. 7094 def get_binding
  8. 7094 binding()
  9. end
  10. 7094 def binary(path)
  11. 100 %(!!binary "#{Base64.strict_encode64(File.binread(path))}")
  12. end
  13. end
  14. end
  15. end

lib/active_record/fixture_set/table_row.rb

98.72% lines covered

78 relevant lines. 77 lines covered and 1 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 module ActiveRecord
  3. 3 class FixtureSet
  4. 3 class TableRow # :nodoc:
  5. 3 class ReflectionProxy # :nodoc:
  6. 3 def initialize(association)
  7. 382484 @association = association
  8. end
  9. 3 def join_table
  10. @association.join_table
  11. end
  12. 3 def name
  13. 382484 @association.name
  14. end
  15. 3 def primary_key_type
  16. 1175 @association.klass.type_for_attribute(@association.klass.primary_key).type
  17. end
  18. end
  19. 3 class HasManyThroughProxy < ReflectionProxy # :nodoc:
  20. 3 def rhs_key
  21. 1175 @association.foreign_key
  22. end
  23. 3 def lhs_key
  24. 1175 @association.through_reflection.foreign_key
  25. end
  26. 3 def join_table
  27. 1175 @association.through_reflection.table_name
  28. end
  29. end
  30. 3 def initialize(fixture, table_rows:, label:, now:)
  31. 293509 @table_rows = table_rows
  32. 293509 @label = label
  33. 293509 @now = now
  34. 293509 @row = fixture.to_hash
  35. 293509 fill_row_model_attributes
  36. end
  37. 3 def to_hash
  38. 293509 @row
  39. end
  40. 3 private
  41. 3 def model_metadata
  42. 1184595 @table_rows.model_metadata
  43. end
  44. 3 def model_class
  45. 1157566 @table_rows.model_class
  46. end
  47. 3 def fill_row_model_attributes
  48. 293509 return unless model_class
  49. 291744 fill_timestamps
  50. 291744 interpolate_label
  51. 291744 generate_primary_key
  52. 291744 resolve_enums
  53. 291744 resolve_sti_reflections
  54. end
  55. 3 def reflection_class
  56. 293001 @reflection_class ||= if @row.include?(model_metadata.inheritance_column_name)
  57. 11331 @row[model_metadata.inheritance_column_name].constantize rescue model_class
  58. else
  59. 280413 model_class
  60. end
  61. end
  62. 3 def fill_timestamps
  63. # fill in timestamp columns if they aren't specified and the model is set to record_timestamps
  64. 291744 if model_class.record_timestamps
  65. 291570 model_metadata.timestamp_column_names.each do |c_name|
  66. 39812 @row[c_name] = @now unless @row.key?(c_name)
  67. end
  68. end
  69. end
  70. 3 def interpolate_label
  71. # interpolate the fixture label
  72. 291744 @row.each do |key, value|
  73. 691617 @row[key] = value.gsub("$LABEL", @label.to_s) if value.is_a?(String)
  74. end
  75. end
  76. 3 def generate_primary_key
  77. # generate a primary key if necessary
  78. 291744 if model_metadata.has_primary_key_column? && !@row.include?(model_metadata.primary_key_name)
  79. 2393 @row[model_metadata.primary_key_name] = ActiveRecord::FixtureSet.identify(
  80. @label, model_metadata.primary_key_type
  81. )
  82. end
  83. end
  84. 3 def resolve_enums
  85. 291744 model_class.defined_enums.each do |name, values|
  86. 5106 if @row.include?(name)
  87. 1554 @row[name] = values.fetch(@row[name], @row[name])
  88. end
  89. end
  90. end
  91. 3 def resolve_sti_reflections
  92. # If STI is used, find the correct subclass for association reflection
  93. 291744 reflection_class._reflections.each_value do |association|
  94. 1435473 case association.macro
  95. when :belongs_to
  96. # Do not replace association name with association foreign key if they are named the same
  97. 339703 fk_name = association.join_foreign_key
  98. 339703 if association.name.to_s != fk_name && value = @row.delete(association.name.to_s)
  99. 1257 if association.polymorphic? && value.sub!(/\s*\(([^\)]*)\)\s*$/, "")
  100. # support polymorphic belongs_to as "label (Type)"
  101. 220 @row[association.join_foreign_type] = $1
  102. end
  103. 1257 fk_type = reflection_class.type_for_attribute(fk_name).type
  104. 1257 @row[fk_name] = ActiveRecord::FixtureSet.identify(value, fk_type)
  105. end
  106. when :has_many
  107. 1012199 if association.options[:through]
  108. 382484 add_join_records(HasManyThroughProxy.new(association))
  109. end
  110. end
  111. end
  112. end
  113. 3 def add_join_records(association)
  114. # This is the case when the join table has no fixtures file
  115. 382484 if (targets = @row.delete(association.name.to_s))
  116. 1175 table_name = association.join_table
  117. 1175 column_type = association.primary_key_type
  118. 1175 lhs_key = association.lhs_key
  119. 1175 rhs_key = association.rhs_key
  120. 1175 targets = targets.is_a?(Array) ? targets : targets.split(/\s*,\s*/)
  121. 1175 joins = targets.map do |target|
  122. 1793 { lhs_key => @row[model_metadata.primary_key_name],
  123. rhs_key => ActiveRecord::FixtureSet.identify(target, column_type) }
  124. end
  125. 1175 @table_rows.tables[table_name].concat(joins)
  126. end
  127. end
  128. end
  129. end
  130. end

lib/active_record/fixture_set/table_rows.rb

100.0% lines covered

20 relevant lines. 20 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 require "active_record/fixture_set/table_row"
  3. 3 require "active_record/fixture_set/model_metadata"
  4. 3 module ActiveRecord
  5. 3 class FixtureSet
  6. 3 class TableRows # :nodoc:
  7. 3 def initialize(table_name, model_class:, fixtures:, config:)
  8. 6269 @model_class = model_class
  9. # track any join tables we need to insert later
  10. 7102 @tables = Hash.new { |h, table| h[table] = [] }
  11. # ensure this table is loaded before any HABTM associations
  12. 6269 @tables[table_name] = nil
  13. 6269 build_table_rows_from(table_name, fixtures, config)
  14. end
  15. 3 attr_reader :tables, :model_class
  16. 3 def to_hash
  17. 13371 @tables.transform_values { |rows| rows.map(&:to_hash) }
  18. end
  19. 3 def model_metadata
  20. 1184595 @model_metadata ||= ModelMetadata.new(model_class)
  21. end
  22. 3 private
  23. 3 def build_table_rows_from(table_name, fixtures, config)
  24. 6269 now = config.default_timezone == :utc ? Time.now.utc : Time.now
  25. 6269 @tables[table_name] = fixtures.map do |label, fixture|
  26. 293509 TableRow.new(
  27. fixture,
  28. table_rows: self,
  29. label: label,
  30. now: now,
  31. )
  32. end
  33. end
  34. end
  35. end
  36. end

lib/active_record/fixtures.rb

95.63% lines covered

160 relevant lines. 153 lines covered and 7 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 require "erb"
  3. 3 require "yaml"
  4. 3 require "zlib"
  5. 3 require "set"
  6. 3 require "active_support/dependencies"
  7. 3 require "active_support/core_ext/digest/uuid"
  8. 3 require "active_record/fixture_set/file"
  9. 3 require "active_record/fixture_set/render_context"
  10. 3 require "active_record/fixture_set/table_rows"
  11. 3 require "active_record/test_fixtures"
  12. 3 module ActiveRecord
  13. 3 class FixtureClassNotFound < ActiveRecord::ActiveRecordError #:nodoc:
  14. end
  15. # \Fixtures are a way of organizing data that you want to test against; in short, sample data.
  16. #
  17. # They are stored in YAML files, one file per model, which are placed in the directory
  18. # appointed by <tt>ActiveSupport::TestCase.fixture_path=(path)</tt> (this is automatically
  19. # configured for Rails, so you can just put your files in <tt><your-rails-app>/test/fixtures/</tt>).
  20. # The fixture file ends with the +.yml+ file extension, for example:
  21. # <tt><your-rails-app>/test/fixtures/web_sites.yml</tt>).
  22. #
  23. # The format of a fixture file looks like this:
  24. #
  25. # rubyonrails:
  26. # id: 1
  27. # name: Ruby on Rails
  28. # url: http://www.rubyonrails.org
  29. #
  30. # google:
  31. # id: 2
  32. # name: Google
  33. # url: http://www.google.com
  34. #
  35. # This fixture file includes two fixtures. Each YAML fixture (ie. record) is given a name and
  36. # is followed by an indented list of key/value pairs in the "key: value" format. Records are
  37. # separated by a blank line for your viewing pleasure.
  38. #
  39. # Note: Fixtures are unordered. If you want ordered fixtures, use the omap YAML type.
  40. # See https://yaml.org/type/omap.html
  41. # for the specification. You will need ordered fixtures when you have foreign key constraints
  42. # on keys in the same table. This is commonly needed for tree structures. Example:
  43. #
  44. # --- !omap
  45. # - parent:
  46. # id: 1
  47. # parent_id: NULL
  48. # title: Parent
  49. # - child:
  50. # id: 2
  51. # parent_id: 1
  52. # title: Child
  53. #
  54. # = Using Fixtures in Test Cases
  55. #
  56. # Since fixtures are a testing construct, we use them in our unit and functional tests. There
  57. # are two ways to use the fixtures, but first let's take a look at a sample unit test:
  58. #
  59. # require "test_helper"
  60. #
  61. # class WebSiteTest < ActiveSupport::TestCase
  62. # test "web_site_count" do
  63. # assert_equal 2, WebSite.count
  64. # end
  65. # end
  66. #
  67. # By default, +test_helper.rb+ will load all of your fixtures into your test
  68. # database, so this test will succeed.
  69. #
  70. # The testing environment will automatically load all the fixtures into the database before each
  71. # test. To ensure consistent data, the environment deletes the fixtures before running the load.
  72. #
  73. # In addition to being available in the database, the fixture's data may also be accessed by
  74. # using a special dynamic method, which has the same name as the model.
  75. #
  76. # Passing in a fixture name to this dynamic method returns the fixture matching this name:
  77. #
  78. # test "find one" do
  79. # assert_equal "Ruby on Rails", web_sites(:rubyonrails).name
  80. # end
  81. #
  82. # Passing in multiple fixture names returns all fixtures matching these names:
  83. #
  84. # test "find all by name" do
  85. # assert_equal 2, web_sites(:rubyonrails, :google).length
  86. # end
  87. #
  88. # Passing in no arguments returns all fixtures:
  89. #
  90. # test "find all" do
  91. # assert_equal 2, web_sites.length
  92. # end
  93. #
  94. # Passing in any fixture name that does not exist will raise <tt>StandardError</tt>:
  95. #
  96. # test "find by name that does not exist" do
  97. # assert_raise(StandardError) { web_sites(:reddit) }
  98. # end
  99. #
  100. # Alternatively, you may enable auto-instantiation of the fixture data. For instance, take the
  101. # following tests:
  102. #
  103. # test "find_alt_method_1" do
  104. # assert_equal "Ruby on Rails", @web_sites['rubyonrails']['name']
  105. # end
  106. #
  107. # test "find_alt_method_2" do
  108. # assert_equal "Ruby on Rails", @rubyonrails.name
  109. # end
  110. #
  111. # In order to use these methods to access fixtured data within your test cases, you must specify one of the
  112. # following in your ActiveSupport::TestCase-derived class:
  113. #
  114. # - to fully enable instantiated fixtures (enable alternate methods #1 and #2 above)
  115. # self.use_instantiated_fixtures = true
  116. #
  117. # - create only the hash for the fixtures, do not 'find' each instance (enable alternate method #1 only)
  118. # self.use_instantiated_fixtures = :no_instances
  119. #
  120. # Using either of these alternate methods incurs a performance hit, as the fixtured data must be fully
  121. # traversed in the database to create the fixture hash and/or instance variables. This is expensive for
  122. # large sets of fixtured data.
  123. #
  124. # = Dynamic fixtures with ERB
  125. #
  126. # Sometimes you don't care about the content of the fixtures as much as you care about the volume.
  127. # In these cases, you can mix ERB in with your YAML fixtures to create a bunch of fixtures for load
  128. # testing, like:
  129. #
  130. # <% 1.upto(1000) do |i| %>
  131. # fix_<%= i %>:
  132. # id: <%= i %>
  133. # name: guy_<%= i %>
  134. # <% end %>
  135. #
  136. # This will create 1000 very simple fixtures.
  137. #
  138. # Using ERB, you can also inject dynamic values into your fixtures with inserts like
  139. # <tt><%= Date.today.strftime("%Y-%m-%d") %></tt>.
  140. # This is however a feature to be used with some caution. The point of fixtures are that they're
  141. # stable units of predictable sample data. If you feel that you need to inject dynamic values, then
  142. # perhaps you should reexamine whether your application is properly testable. Hence, dynamic values
  143. # in fixtures are to be considered a code smell.
  144. #
  145. # Helper methods defined in a fixture will not be available in other fixtures, to prevent against
  146. # unwanted inter-test dependencies. Methods used by multiple fixtures should be defined in a module
  147. # that is included in ActiveRecord::FixtureSet.context_class.
  148. #
  149. # - define a helper method in <tt>test_helper.rb</tt>
  150. # module FixtureFileHelpers
  151. # def file_sha(path)
  152. # Digest::SHA2.hexdigest(File.read(Rails.root.join('test/fixtures', path)))
  153. # end
  154. # end
  155. # ActiveRecord::FixtureSet.context_class.include FixtureFileHelpers
  156. #
  157. # - use the helper method in a fixture
  158. # photo:
  159. # name: kitten.png
  160. # sha: <%= file_sha 'files/kitten.png' %>
  161. #
  162. # = Transactional Tests
  163. #
  164. # Test cases can use begin+rollback to isolate their changes to the database instead of having to
  165. # delete+insert for every test case.
  166. #
  167. # class FooTest < ActiveSupport::TestCase
  168. # self.use_transactional_tests = true
  169. #
  170. # test "godzilla" do
  171. # assert_not_empty Foo.all
  172. # Foo.destroy_all
  173. # assert_empty Foo.all
  174. # end
  175. #
  176. # test "godzilla aftermath" do
  177. # assert_not_empty Foo.all
  178. # end
  179. # end
  180. #
  181. # If you preload your test database with all fixture data (probably by running `bin/rails db:fixtures:load`)
  182. # and use transactional tests, then you may omit all fixtures declarations in your test cases since
  183. # all the data's already there and every case rolls back its changes.
  184. #
  185. # In order to use instantiated fixtures with preloaded data, set +self.pre_loaded_fixtures+ to
  186. # true. This will provide access to fixture data for every table that has been loaded through
  187. # fixtures (depending on the value of +use_instantiated_fixtures+).
  188. #
  189. # When *not* to use transactional tests:
  190. #
  191. # 1. You're testing whether a transaction works correctly. Nested transactions don't commit until
  192. # all parent transactions commit, particularly, the fixtures transaction which is begun in setup
  193. # and rolled back in teardown. Thus, you won't be able to verify
  194. # the results of your transaction until Active Record supports nested transactions or savepoints (in progress).
  195. # 2. Your database does not support transactions. Every Active Record database supports transactions except MySQL MyISAM.
  196. # Use InnoDB, MaxDB, or NDB instead.
  197. #
  198. # = Advanced Fixtures
  199. #
  200. # Fixtures that don't specify an ID get some extra features:
  201. #
  202. # * Stable, autogenerated IDs
  203. # * Label references for associations (belongs_to, has_one, has_many)
  204. # * HABTM associations as inline lists
  205. #
  206. # There are some more advanced features available even if the id is specified:
  207. #
  208. # * Autofilled timestamp columns
  209. # * Fixture label interpolation
  210. # * Support for YAML defaults
  211. #
  212. # == Stable, Autogenerated IDs
  213. #
  214. # Here, have a monkey fixture:
  215. #
  216. # george:
  217. # id: 1
  218. # name: George the Monkey
  219. #
  220. # reginald:
  221. # id: 2
  222. # name: Reginald the Pirate
  223. #
  224. # Each of these fixtures has two unique identifiers: one for the database
  225. # and one for the humans. Why don't we generate the primary key instead?
  226. # Hashing each fixture's label yields a consistent ID:
  227. #
  228. # george: # generated id: 503576764
  229. # name: George the Monkey
  230. #
  231. # reginald: # generated id: 324201669
  232. # name: Reginald the Pirate
  233. #
  234. # Active Record looks at the fixture's model class, discovers the correct
  235. # primary key, and generates it right before inserting the fixture
  236. # into the database.
  237. #
  238. # The generated ID for a given label is constant, so we can discover
  239. # any fixture's ID without loading anything, as long as we know the label.
  240. #
  241. # == Label references for associations (belongs_to, has_one, has_many)
  242. #
  243. # Specifying foreign keys in fixtures can be very fragile, not to
  244. # mention difficult to read. Since Active Record can figure out the ID of
  245. # any fixture from its label, you can specify FK's by label instead of ID.
  246. #
  247. # === belongs_to
  248. #
  249. # Let's break out some more monkeys and pirates.
  250. #
  251. # ### in pirates.yml
  252. #
  253. # reginald:
  254. # id: 1
  255. # name: Reginald the Pirate
  256. # monkey_id: 1
  257. #
  258. # ### in monkeys.yml
  259. #
  260. # george:
  261. # id: 1
  262. # name: George the Monkey
  263. # pirate_id: 1
  264. #
  265. # Add a few more monkeys and pirates and break this into multiple files,
  266. # and it gets pretty hard to keep track of what's going on. Let's
  267. # use labels instead of IDs:
  268. #
  269. # ### in pirates.yml
  270. #
  271. # reginald:
  272. # name: Reginald the Pirate
  273. # monkey: george
  274. #
  275. # ### in monkeys.yml
  276. #
  277. # george:
  278. # name: George the Monkey
  279. # pirate: reginald
  280. #
  281. # Pow! All is made clear. Active Record reflects on the fixture's model class,
  282. # finds all the +belongs_to+ associations, and allows you to specify
  283. # a target *label* for the *association* (monkey: george) rather than
  284. # a target *id* for the *FK* (<tt>monkey_id: 1</tt>).
  285. #
  286. # ==== Polymorphic belongs_to
  287. #
  288. # Supporting polymorphic relationships is a little bit more complicated, since
  289. # Active Record needs to know what type your association is pointing at. Something
  290. # like this should look familiar:
  291. #
  292. # ### in fruit.rb
  293. #
  294. # belongs_to :eater, polymorphic: true
  295. #
  296. # ### in fruits.yml
  297. #
  298. # apple:
  299. # id: 1
  300. # name: apple
  301. # eater_id: 1
  302. # eater_type: Monkey
  303. #
  304. # Can we do better? You bet!
  305. #
  306. # apple:
  307. # eater: george (Monkey)
  308. #
  309. # Just provide the polymorphic target type and Active Record will take care of the rest.
  310. #
  311. # === has_and_belongs_to_many
  312. #
  313. # Time to give our monkey some fruit.
  314. #
  315. # ### in monkeys.yml
  316. #
  317. # george:
  318. # id: 1
  319. # name: George the Monkey
  320. #
  321. # ### in fruits.yml
  322. #
  323. # apple:
  324. # id: 1
  325. # name: apple
  326. #
  327. # orange:
  328. # id: 2
  329. # name: orange
  330. #
  331. # grape:
  332. # id: 3
  333. # name: grape
  334. #
  335. # ### in fruits_monkeys.yml
  336. #
  337. # apple_george:
  338. # fruit_id: 1
  339. # monkey_id: 1
  340. #
  341. # orange_george:
  342. # fruit_id: 2
  343. # monkey_id: 1
  344. #
  345. # grape_george:
  346. # fruit_id: 3
  347. # monkey_id: 1
  348. #
  349. # Let's make the HABTM fixture go away.
  350. #
  351. # ### in monkeys.yml
  352. #
  353. # george:
  354. # id: 1
  355. # name: George the Monkey
  356. # fruits: apple, orange, grape
  357. #
  358. # ### in fruits.yml
  359. #
  360. # apple:
  361. # name: apple
  362. #
  363. # orange:
  364. # name: orange
  365. #
  366. # grape:
  367. # name: grape
  368. #
  369. # Zap! No more fruits_monkeys.yml file. We've specified the list of fruits
  370. # on George's fixture, but we could've just as easily specified a list
  371. # of monkeys on each fruit. As with +belongs_to+, Active Record reflects on
  372. # the fixture's model class and discovers the +has_and_belongs_to_many+
  373. # associations.
  374. #
  375. # == Autofilled Timestamp Columns
  376. #
  377. # If your table/model specifies any of Active Record's
  378. # standard timestamp columns (+created_at+, +created_on+, +updated_at+, +updated_on+),
  379. # they will automatically be set to <tt>Time.now</tt>.
  380. #
  381. # If you've set specific values, they'll be left alone.
  382. #
  383. # == Fixture label interpolation
  384. #
  385. # The label of the current fixture is always available as a column value:
  386. #
  387. # geeksomnia:
  388. # name: Geeksomnia's Account
  389. # subdomain: $LABEL
  390. # email: $LABEL@email.com
  391. #
  392. # Also, sometimes (like when porting older join table fixtures) you'll need
  393. # to be able to get a hold of the identifier for a given label. ERB
  394. # to the rescue:
  395. #
  396. # george_reginald:
  397. # monkey_id: <%= ActiveRecord::FixtureSet.identify(:reginald) %>
  398. # pirate_id: <%= ActiveRecord::FixtureSet.identify(:george) %>
  399. #
  400. # == Support for YAML defaults
  401. #
  402. # You can set and reuse defaults in your fixtures YAML file.
  403. # This is the same technique used in the +database.yml+ file to specify
  404. # defaults:
  405. #
  406. # DEFAULTS: &DEFAULTS
  407. # created_on: <%= 3.weeks.ago.to_s(:db) %>
  408. #
  409. # first:
  410. # name: Smurf
  411. # <<: *DEFAULTS
  412. #
  413. # second:
  414. # name: Fraggle
  415. # <<: *DEFAULTS
  416. #
  417. # Any fixture labeled "DEFAULTS" is safely ignored.
  418. #
  419. # Besides using "DEFAULTS", you can also specify what fixtures will
  420. # be ignored by setting "ignore" in "_fixture" section.
  421. #
  422. # # users.yml
  423. # _fixture:
  424. # ignore:
  425. # - base
  426. # # or use "ignore: base" when there is only one fixture needs to be ignored.
  427. #
  428. # base: &base
  429. # admin: false
  430. # introduction: "This is a default description"
  431. #
  432. # admin:
  433. # <<: *base
  434. # admin: true
  435. #
  436. # visitor:
  437. # <<: *base
  438. #
  439. # In the above example, 'base' will be ignored when creating fixtures.
  440. # This can be used for common attributes inheriting.
  441. #
  442. # == Configure the fixture model class
  443. #
  444. # It's possible to set the fixture's model class directly in the YAML file.
  445. # This is helpful when fixtures are loaded outside tests and
  446. # +set_fixture_class+ is not available (e.g.
  447. # when running <tt>bin/rails db:fixtures:load</tt>).
  448. #
  449. # _fixture:
  450. # model_class: User
  451. # david:
  452. # name: David
  453. #
  454. # Any fixtures labeled "_fixture" are safely ignored.
  455. 3 class FixtureSet
  456. #--
  457. # An instance of FixtureSet is normally stored in a single YAML file and
  458. # possibly in a folder with the same name.
  459. #++
  460. 3 MAX_ID = 2**30 - 1
  461. 2691 @@all_cached_fixtures = Hash.new { |h, k| h[k] = {} }
  462. 3 cattr_accessor :all_loaded_fixtures, default: {}
  463. 3 class ClassCache
  464. 3 def initialize(class_names, config)
  465. 3599 @class_names = class_names.stringify_keys
  466. 3599 @config = config
  467. # Remove string values that aren't constants or subclasses of AR
  468. 3599 @class_names.delete_if do |klass_name, klass|
  469. 45 !insert_class(@class_names, klass_name, klass)
  470. end
  471. end
  472. 3 def [](fs_name)
  473. 6257 @class_names.fetch(fs_name) do
  474. 6212 klass = default_fixture_model(fs_name, @config).safe_constantize
  475. 6212 insert_class(@class_names, fs_name, klass)
  476. end
  477. end
  478. 3 private
  479. 3 def insert_class(class_names, name, klass)
  480. # We only want to deal with AR objects.
  481. 6257 if klass && klass < ActiveRecord::Base
  482. 5798 class_names[name] = klass
  483. else
  484. 459 class_names[name] = nil
  485. end
  486. end
  487. 3 def default_fixture_model(fs_name, config)
  488. 6212 ActiveRecord::FixtureSet.default_fixture_model_name(fs_name, config)
  489. end
  490. end
  491. 3 class << self
  492. 3 def default_fixture_model_name(fixture_set_name, config = ActiveRecord::Base) # :nodoc:
  493. 6212 config.pluralize_table_names ?
  494. fixture_set_name.singularize.camelize :
  495. fixture_set_name.camelize
  496. end
  497. 3 def default_fixture_table_name(fixture_set_name, config = ActiveRecord::Base) # :nodoc:
  498. "#{ config.table_name_prefix }"\
  499. "#{ fixture_set_name.tr('/', '_') }"\
  500. 438 "#{ config.table_name_suffix }".to_sym
  501. end
  502. 3 def reset_cache
  503. 4915 @@all_cached_fixtures.clear
  504. end
  505. 3 def cache_for_connection(connection)
  506. 11744 @@all_cached_fixtures[connection]
  507. end
  508. 3 def fixture_is_cached?(connection, table_name)
  509. 6627 cache_for_connection(connection)[table_name]
  510. end
  511. 3 def cached_fixtures(connection, keys_to_fetch = nil)
  512. 3593 if keys_to_fetch
  513. 3593 cache_for_connection(connection).values_at(*keys_to_fetch)
  514. else
  515. cache_for_connection(connection).values
  516. end
  517. end
  518. 3 def cache_fixtures(connection, fixtures_map)
  519. 1524 cache_for_connection(connection).update(fixtures_map)
  520. end
  521. 3 def instantiate_fixtures(object, fixture_set, load_instances = true)
  522. 791 return unless load_instances
  523. 782 fixture_set.each do |fixture_name, fixture|
  524. 3522 object.instance_variable_set "@#{fixture_name}", fixture.find
  525. rescue FixtureClassNotFound
  526. 194 nil
  527. end
  528. end
  529. 3 def instantiate_all_loaded_fixtures(object, load_instances = true)
  530. all_loaded_fixtures.each_value do |fixture_set|
  531. instantiate_fixtures(object, fixture_set, load_instances)
  532. end
  533. end
  534. 3 def create_fixtures(fixtures_directory, fixture_set_names, class_names = {}, config = ActiveRecord::Base, &block)
  535. 3599 fixture_set_names = Array(fixture_set_names).map(&:to_s)
  536. 3599 class_names = ClassCache.new class_names, config
  537. # FIXME: Apparently JK uses this.
  538. 15595 connection = block_given? ? block : lambda { ActiveRecord::Base.connection }
  539. 3599 fixture_files_to_read = fixture_set_names.reject do |fs_name|
  540. 6618 fixture_is_cached?(connection.call, fs_name)
  541. end
  542. 3599 if fixture_files_to_read.any?
  543. 1530 fixtures_map = read_and_insert(
  544. fixtures_directory,
  545. fixture_files_to_read,
  546. class_names,
  547. connection,
  548. )
  549. 1524 cache_fixtures(connection.call, fixtures_map)
  550. end
  551. 3593 cached_fixtures(connection.call, fixture_set_names)
  552. end
  553. # Returns a consistent, platform-independent identifier for +label+.
  554. # Integer identifiers are values less than 2^30. UUIDs are RFC 4122 version 5 SHA-1 hashes.
  555. 3 def identify(label, column_type = :integer)
  556. 5977 if column_type == :uuid
  557. 87 Digest::UUID.uuid_v5(Digest::UUID::OID_NAMESPACE, label.to_s)
  558. else
  559. 5890 Zlib.crc32(label.to_s) % MAX_ID
  560. end
  561. end
  562. # Superclass for the evaluation contexts used by ERB fixtures.
  563. 3 def context_class
  564. 7100 @context_class ||= Class.new
  565. end
  566. 3 private
  567. 3 def read_and_insert(fixtures_directory, fixture_files, class_names, connection) # :nodoc:
  568. 1530 fixtures_map = {}
  569. 1530 fixture_sets = fixture_files.map do |fixture_set_name|
  570. 6257 klass = class_names[fixture_set_name]
  571. 6257 fixtures_map[fixture_set_name] = new( # ActiveRecord::FixtureSet.new
  572. nil,
  573. fixture_set_name,
  574. klass,
  575. ::File.join(fixtures_directory, fixture_set_name)
  576. )
  577. end
  578. 1527 update_all_loaded_fixtures(fixtures_map)
  579. 1527 insert(fixture_sets, connection)
  580. 1524 fixtures_map
  581. end
  582. 3 def insert(fixture_sets, connection) # :nodoc:
  583. 1527 fixture_sets_by_connection = fixture_sets.group_by do |fixture_set|
  584. 6254 if fixture_set.model_class
  585. 5816 fixture_set.model_class.connection
  586. else
  587. 438 connection.call
  588. end
  589. end
  590. 1527 fixture_sets_by_connection.each do |conn, set|
  591. 8281 table_rows_for_connection = Hash.new { |h, k| h[k] = [] }
  592. 1533 set.each do |fixture_set|
  593. 6254 fixture_set.table_rows.each do |table, rows|
  594. 7072 table_rows_for_connection[table].unshift(*rows)
  595. end
  596. end
  597. 1533 conn.insert_fixtures_set(table_rows_for_connection, table_rows_for_connection.keys)
  598. # Cap primary key sequences to max(pk).
  599. 1530 if conn.respond_to?(:reset_pk_sequence!)
  600. 3065 set.each { |fs| conn.reset_pk_sequence!(fs.table_name) }
  601. end
  602. end
  603. end
  604. 3 def update_all_loaded_fixtures(fixtures_map) # :nodoc:
  605. 1527 all_loaded_fixtures.update(fixtures_map)
  606. end
  607. end
  608. 3 attr_reader :table_name, :name, :fixtures, :model_class, :ignored_fixtures, :config
  609. 3 def initialize(_, name, class_name, path, config = ActiveRecord::Base)
  610. 6290 @name = name
  611. 6290 @path = path
  612. 6290 @config = config
  613. 6290 self.model_class = class_name
  614. 6290 @fixtures = read_fixture_files(path)
  615. 6278 @table_name = model_class&.table_name || self.class.default_fixture_table_name(name, config)
  616. end
  617. 3 def [](x)
  618. 13129 fixtures[x]
  619. end
  620. 3 def []=(k, v)
  621. fixtures[k] = v
  622. end
  623. 3 def each(&block)
  624. 821 fixtures.each(&block)
  625. end
  626. 3 def size
  627. fixtures.size
  628. end
  629. # Returns a hash of rows to be inserted. The key is the table, the value is
  630. # a list of rows to insert to that table.
  631. 3 def table_rows
  632. # allow specifying fixtures to be ignored by setting `ignore` in `_fixture` section
  633. 6269 fixtures.except!(*ignored_fixtures)
  634. TableRows.new(
  635. table_name,
  636. model_class: model_class,
  637. fixtures: fixtures,
  638. config: config,
  639. 6269 ).to_hash
  640. end
  641. 3 private
  642. 3 def model_class=(class_name)
  643. 6308 if class_name.is_a?(Class) # TODO: Should be an AR::Base type class, or any?
  644. 5831 @model_class = class_name
  645. else
  646. 477 @model_class = class_name.safe_constantize if class_name
  647. end
  648. end
  649. 3 def ignored_fixtures=(base)
  650. 6278 @ignored_fixtures =
  651. case base
  652. when Array
  653. 6 base
  654. when String
  655. 114 [base]
  656. else
  657. 6158 []
  658. end
  659. 6278 @ignored_fixtures << "DEFAULTS" unless @ignored_fixtures.include?("DEFAULTS")
  660. 6278 @ignored_fixtures.compact
  661. end
  662. # Loads the fixtures from the YAML file at +path+.
  663. # If the file sets the +model_class+ and current instance value is not set,
  664. # it uses the file value.
  665. 3 def read_fixture_files(path)
  666. 6290 yaml_files = Dir["#{path}/{**,*}/*.yml"].select { |f|
  667. 762 ::File.file?(f)
  668. } + [yaml_file_path(path)]
  669. 6290 yaml_files.each_with_object({}) do |file, fixtures|
  670. 7052 FixtureSet::File.open(file) do |fh|
  671. 7052 self.model_class ||= fh.model_class if fh.model_class
  672. 7040 self.ignored_fixtures ||= fh.ignored_fixtures
  673. 7040 fh.each do |fixture_name, row|
  674. 294309 fixtures[fixture_name] = ActiveRecord::Fixture.new(row, model_class)
  675. end
  676. end
  677. end
  678. end
  679. 3 def yaml_file_path(path)
  680. 6290 "#{path}.yml"
  681. end
  682. end
  683. 3 class Fixture #:nodoc:
  684. 3 include Enumerable
  685. 3 class FixtureError < StandardError #:nodoc:
  686. end
  687. 3 class FormatError < FixtureError #:nodoc:
  688. end
  689. 3 attr_reader :model_class, :fixture
  690. 3 def initialize(fixture, model_class)
  691. 294309 @fixture = fixture
  692. 294309 @model_class = model_class
  693. end
  694. 3 def class_name
  695. model_class.name if model_class
  696. end
  697. 3 def each
  698. 945 fixture.each { |item| yield item }
  699. end
  700. 3 def [](key)
  701. 325 fixture[key]
  702. end
  703. 3 alias :to_hash :fixture
  704. 3 def find
  705. 9083 raise FixtureClassNotFound, "No class attached to find." unless model_class
  706. 8886 model_class.unscoped do
  707. 8886 model_class.find(fixture[model_class.primary_key])
  708. end
  709. end
  710. end
  711. end

lib/active_record/gem_version.rb

88.89% lines covered

9 relevant lines. 8 lines covered and 1 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 module ActiveRecord
  3. # Returns the version of the currently loaded Active Record as a <tt>Gem::Version</tt>
  4. 3 def self.gem_version
  5. Gem::Version.new VERSION::STRING
  6. end
  7. 3 module VERSION
  8. 3 MAJOR = 6
  9. 3 MINOR = 1
  10. 3 TINY = 0
  11. 3 PRE = "alpha"
  12. 3 STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
  13. end
  14. end

lib/active_record/inheritance.rb

99.02% lines covered

102 relevant lines. 101 lines covered and 1 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 require "active_support/core_ext/hash/indifferent_access"
  3. 3 module ActiveRecord
  4. # == Single table inheritance
  5. #
  6. # Active Record allows inheritance by storing the name of the class in a column that by
  7. # default is named "type" (can be changed by overwriting <tt>Base.inheritance_column</tt>).
  8. # This means that an inheritance looking like this:
  9. #
  10. # class Company < ActiveRecord::Base; end
  11. # class Firm < Company; end
  12. # class Client < Company; end
  13. # class PriorityClient < Client; end
  14. #
  15. # When you do <tt>Firm.create(name: "37signals")</tt>, this record will be saved in
  16. # the companies table with type = "Firm". You can then fetch this row again using
  17. # <tt>Company.where(name: '37signals').first</tt> and it will return a Firm object.
  18. #
  19. # Be aware that because the type column is an attribute on the record every new
  20. # subclass will instantly be marked as dirty and the type column will be included
  21. # in the list of changed attributes on the record. This is different from non
  22. # Single Table Inheritance(STI) classes:
  23. #
  24. # Company.new.changed? # => false
  25. # Firm.new.changed? # => true
  26. # Firm.new.changes # => {"type"=>["","Firm"]}
  27. #
  28. # If you don't have a type column defined in your table, single-table inheritance won't
  29. # be triggered. In that case, it'll work just like normal subclasses with no special magic
  30. # for differentiating between them or reloading the right type with find.
  31. #
  32. # Note, all the attributes for all the cases are kept in the same table. Read more:
  33. # https://www.martinfowler.com/eaaCatalog/singleTableInheritance.html
  34. #
  35. 3 module Inheritance
  36. 3 extend ActiveSupport::Concern
  37. 3 included do
  38. # Determines whether to store the full constant name including namespace when using STI.
  39. # This is true, by default.
  40. 6 class_attribute :store_full_sti_class, instance_writer: false, default: true
  41. end
  42. 3 module ClassMethods
  43. # Determines if one of the attributes passed in is the inheritance column,
  44. # and if the inheritance column is attr accessible, it initializes an
  45. # instance of the given subclass instead of the base class.
  46. 3 def new(attributes = nil, &block)
  47. 15769 if abstract_class? || self == Base
  48. 6 raise NotImplementedError, "#{self} is an abstract class and cannot be instantiated."
  49. end
  50. 15763 if _has_attribute?(inheritance_column)
  51. 4516 subclass = subclass_from_attributes(attributes)
  52. 4489 if subclass.nil? && scope_attributes = current_scope&.scope_for_create
  53. 126 subclass = subclass_from_attributes(scope_attributes)
  54. end
  55. 4471 if subclass.nil? && base_class?
  56. 3105 subclass = subclass_from_attributes(column_defaults)
  57. end
  58. end
  59. 15707 if subclass && subclass != self
  60. 54 subclass.new(attributes, &block)
  61. else
  62. 15653 super
  63. end
  64. end
  65. # Returns +true+ if this does not need STI type condition. Returns
  66. # +false+ if STI type condition needs to be applied.
  67. 3 def descends_from_active_record?
  68. 2290 if self == Base
  69. 6 false
  70. 2284 elsif superclass.abstract_class?
  71. 57 superclass.descends_from_active_record?
  72. else
  73. 2227 superclass == Base || !columns_hash.include?(inheritance_column)
  74. end
  75. end
  76. 3 def finder_needs_type_condition? #:nodoc:
  77. # This is like this because benchmarking justifies the strange :false stuff
  78. 109841 :true == (@finder_needs_type_condition ||= descends_from_active_record? ? :false : :true)
  79. end
  80. # Returns the class descending directly from ActiveRecord::Base, or
  81. # an abstract class, if any, in the inheritance hierarchy.
  82. #
  83. # If A extends ActiveRecord::Base, A.base_class will return A. If B descends from A
  84. # through some arbitrarily deep hierarchy, B.base_class will return A.
  85. #
  86. # If B < A and C < B and if A is an abstract_class then both B.base_class
  87. # and C.base_class would return B as the answer since A is an abstract_class.
  88. 3 def base_class
  89. 239027 unless self < Base
  90. 3 raise ActiveRecordError, "#{name} doesn't belong in a hierarchy descending from ActiveRecord"
  91. end
  92. 239024 if superclass == Base || superclass.abstract_class?
  93. 205796 self
  94. else
  95. 33228 superclass.base_class
  96. end
  97. end
  98. # Returns whether the class is a base class.
  99. # See #base_class for more information.
  100. 3 def base_class?
  101. 27788 base_class == self
  102. end
  103. # Set this to +true+ if this is an abstract class (see
  104. # <tt>abstract_class?</tt>).
  105. # If you are using inheritance with Active Record and don't want a class
  106. # to be considered as part of the STI hierarchy, you must set this to
  107. # true.
  108. # +ApplicationRecord+, for example, is generated as an abstract class.
  109. #
  110. # Consider the following default behaviour:
  111. #
  112. # Shape = Class.new(ActiveRecord::Base)
  113. # Polygon = Class.new(Shape)
  114. # Square = Class.new(Polygon)
  115. #
  116. # Shape.table_name # => "shapes"
  117. # Polygon.table_name # => "shapes"
  118. # Square.table_name # => "shapes"
  119. # Shape.create! # => #<Shape id: 1, type: nil>
  120. # Polygon.create! # => #<Polygon id: 2, type: "Polygon">
  121. # Square.create! # => #<Square id: 3, type: "Square">
  122. #
  123. # However, when using <tt>abstract_class</tt>, +Shape+ is omitted from
  124. # the hierarchy:
  125. #
  126. # class Shape < ActiveRecord::Base
  127. # self.abstract_class = true
  128. # end
  129. # Polygon = Class.new(Shape)
  130. # Square = Class.new(Polygon)
  131. #
  132. # Shape.table_name # => nil
  133. # Polygon.table_name # => "polygons"
  134. # Square.table_name # => "polygons"
  135. # Shape.create! # => NotImplementedError: Shape is an abstract class and cannot be instantiated.
  136. # Polygon.create! # => #<Polygon id: 1, type: nil>
  137. # Square.create! # => #<Square id: 2, type: "Square">
  138. #
  139. # Note that in the above example, to disallow the creation of a plain
  140. # +Polygon+, you should use <tt>validates :type, presence: true</tt>,
  141. # instead of setting it as an abstract class. This way, +Polygon+ will
  142. # stay in the hierarchy, and Active Record will continue to correctly
  143. # derive the table name.
  144. 3 attr_accessor :abstract_class
  145. # Returns whether this class is an abstract class or not.
  146. 3 def abstract_class?
  147. 135856 defined?(@abstract_class) && @abstract_class == true
  148. end
  149. # Returns the value to be stored in the inheritance column for STI.
  150. 3 def sti_name
  151. 22178 store_full_sti_class ? name : name.demodulize
  152. end
  153. # Returns the class for the provided +type_name+.
  154. #
  155. # It is used to find the class correspondent to the value stored in the inheritance column.
  156. 3 def sti_class_for(type_name)
  157. 19668 if store_full_sti_class
  158. 19593 ActiveSupport::Dependencies.constantize(type_name)
  159. else
  160. 75 compute_type(type_name)
  161. end
  162. rescue NameError
  163. 30 raise SubclassNotFound,
  164. "The single-table inheritance mechanism failed to locate the subclass: '#{type_name}'. " \
  165. "This error is raised because the column '#{inheritance_column}' is reserved for storing the class in case of inheritance. " \
  166. "Please rename this column if you didn't intend it to be used for storing the inheritance class " \
  167. "or overwrite #{name}.inheritance_column to use another column for that information."
  168. end
  169. # Returns the value to be stored in the polymorphic type column for Polymorphic Associations.
  170. 3 def polymorphic_name
  171. 2421 base_class.name
  172. end
  173. # Returns the class for the provided +name+.
  174. #
  175. # It is used to find the class correspondent to the value stored in the polymorphic type column.
  176. 3 def polymorphic_class_for(name)
  177. 3325 name.constantize
  178. end
  179. 3 def inherited(subclass)
  180. 2940 subclass.instance_variable_set(:@_type_candidates_cache, Concurrent::Map.new)
  181. 2940 super
  182. end
  183. 3 protected
  184. # Returns the class type of the record using the current module as a prefix. So descendants of
  185. # MyApp::Business::Account would appear as MyApp::Business::AccountSubclass.
  186. 3 def compute_type(type_name)
  187. 2891 if type_name.start_with?("::")
  188. # If the type is prefixed with a scope operator then we assume that
  189. # the type_name is an absolute reference.
  190. ActiveSupport::Dependencies.constantize(type_name)
  191. else
  192. 2891 type_candidate = @_type_candidates_cache[type_name]
  193. 2891 if type_candidate && type_constant = ActiveSupport::Dependencies.safe_constantize(type_candidate)
  194. 1277 return type_constant
  195. end
  196. # Build a list of candidates to search for
  197. 1614 candidates = []
  198. 3475 name.scan(/::|$/) { candidates.unshift "#{$`}::#{type_name}" }
  199. 1614 candidates << type_name
  200. 1614 candidates.each do |candidate|
  201. 3286 constant = ActiveSupport::Dependencies.safe_constantize(candidate)
  202. 3277 if candidate == constant.to_s
  203. 1596 @_type_candidates_cache[type_name] = candidate
  204. 1596 return constant
  205. end
  206. end
  207. 9 raise NameError.new("uninitialized constant #{candidates.first}", candidates.first)
  208. end
  209. end
  210. 3 private
  211. # Called by +instantiate+ to decide which class to use for a new
  212. # record instance. For single-table inheritance, we check the record
  213. # for a +type+ column and return the corresponding class.
  214. 3 def discriminate_class_for_record(record)
  215. 27284 if using_single_table_inheritance?(record)
  216. 19512 find_sti_class(record[inheritance_column])
  217. else
  218. 7772 super
  219. end
  220. end
  221. 3 def using_single_table_inheritance?(record)
  222. 27284 record[inheritance_column].present? && _has_attribute?(inheritance_column)
  223. end
  224. 3 def find_sti_class(type_name)
  225. 19668 type_name = base_class.type_for_attribute(inheritance_column).cast(type_name)
  226. 19668 subclass = sti_class_for(type_name)
  227. 19638 unless subclass == self || descendants.include?(subclass)
  228. 24 raise SubclassNotFound, "Invalid single-table inheritance type: #{subclass.name} is not a subclass of #{name}"
  229. end
  230. 19614 subclass
  231. end
  232. 3 def type_condition(table = arel_table)
  233. 7473 sti_column = table[inheritance_column]
  234. 7473 sti_names = ([self] + descendants).map(&:sti_name)
  235. 7473 predicate_builder.build(sti_column, sti_names)
  236. end
  237. # Detect the subclass from the inheritance column of attrs. If the inheritance column value
  238. # is not self or a valid subclass, raises ActiveRecord::SubclassNotFound
  239. 3 def subclass_from_attributes(attrs)
  240. 7747 attrs = attrs.to_h if attrs.respond_to?(:permitted?)
  241. 7747 if attrs.is_a?(Hash)
  242. 6571 subclass_name = attrs[inheritance_column] || attrs[inheritance_column.to_sym]
  243. 6571 if subclass_name.present?
  244. 156 find_sti_class(subclass_name)
  245. end
  246. end
  247. end
  248. end
  249. 3 def initialize_dup(other)
  250. 111 super
  251. 111 ensure_proper_type
  252. end
  253. 3 private
  254. 3 def initialize_internals_callback
  255. 15758 super
  256. 15758 ensure_proper_type
  257. end
  258. # Sets the attribute used for single table inheritance to this class name if this is not the
  259. # ActiveRecord::Base descendant.
  260. # Considering the hierarchy Reply < Message < ActiveRecord::Base, this makes it possible to
  261. # do Reply.new without having to set <tt>Reply[Reply.inheritance_column] = "Reply"</tt> yourself.
  262. # No such attribute would be set for objects of the Message class in that example.
  263. 3 def ensure_proper_type
  264. 15869 klass = self.class
  265. 15869 if klass.finder_needs_type_condition?
  266. 1372 _write_attribute(klass.inheritance_column, klass.sti_name)
  267. end
  268. end
  269. end
  270. end

lib/active_record/insert_all.rb

95.45% lines covered

110 relevant lines. 105 lines covered and 5 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 require "active_support/core_ext/enumerable"
  3. 3 module ActiveRecord
  4. 3 class InsertAll # :nodoc:
  5. 3 attr_reader :model, :connection, :inserts, :keys
  6. 3 attr_reader :on_duplicate, :returning, :unique_by
  7. 3 def initialize(model, inserts, on_duplicate:, returning: nil, unique_by: nil)
  8. 182 raise ArgumentError, "Empty list of attributes passed" if inserts.blank?
  9. 179 @model, @connection, @inserts, @keys = model, model.connection, inserts, inserts.first.keys.map(&:to_s)
  10. 179 @on_duplicate, @returning, @unique_by = on_duplicate, returning, unique_by
  11. 179 if model.scope_attributes?
  12. 18 @scope_attributes = model.scope_attributes
  13. 18 @keys |= @scope_attributes.keys
  14. end
  15. 179 @keys = @keys.to_set
  16. 179 @returning = (connection.supports_insert_returning? ? primary_keys : false) if @returning.nil?
  17. 179 @returning = false if @returning == []
  18. 179 @unique_by = find_unique_index_for(unique_by)
  19. 158 @on_duplicate = :skip if @on_duplicate == :update && updatable_columns.empty?
  20. 158 ensure_valid_options_for_connection!
  21. end
  22. 3 def execute
  23. 158 message = +"#{model} "
  24. 158 message << "Bulk " if inserts.many?
  25. 158 message << (on_duplicate == :update ? "Upsert" : "Insert")
  26. 158 connection.exec_insert_all to_sql, message
  27. end
  28. 3 def updatable_columns
  29. 335 keys - readonly_columns - unique_by_columns
  30. end
  31. 3 def primary_keys
  32. 591 Array(connection.schema_cache.primary_keys(model.table_name))
  33. end
  34. 3 def skip_duplicates?
  35. 313 on_duplicate == :skip
  36. end
  37. 3 def update_duplicates?
  38. 448 on_duplicate == :update
  39. end
  40. 3 def map_key_with_value
  41. 155 inserts.map do |attributes|
  42. 211 attributes = attributes.stringify_keys
  43. 211 attributes.merge!(scope_attributes) if scope_attributes
  44. 211 verify_attributes(attributes)
  45. 211 keys.map do |key|
  46. 506 yield key, attributes[key]
  47. end
  48. end
  49. end
  50. 3 private
  51. 3 attr_reader :scope_attributes
  52. 3 def find_unique_index_for(unique_by)
  53. 179 name_or_columns = unique_by || model.primary_key
  54. 179 match = Array(name_or_columns).map(&:to_s)
  55. 625 if index = unique_indexes.find { |i| match.include?(i.name) || i.columns == match }
  56. 28 index
  57. 151 elsif match == primary_keys
  58. 130 unique_by.nil? ? nil : ActiveRecord::ConnectionAdapters::IndexDefinition.new(model.table_name, "#{model.table_name}_primary_key", true, match)
  59. else
  60. 21 raise ArgumentError, "No unique index found for #{name_or_columns}"
  61. end
  62. end
  63. 3 def unique_indexes
  64. 179 connection.schema_cache.indexes(model.table_name).select(&:unique)
  65. end
  66. 3 def ensure_valid_options_for_connection!
  67. 158 if returning && !connection.supports_insert_returning?
  68. raise ArgumentError, "#{connection.class} does not support :returning"
  69. end
  70. 158 if skip_duplicates? && !connection.supports_insert_on_duplicate_skip?
  71. raise ArgumentError, "#{connection.class} does not support skipping duplicates"
  72. end
  73. 158 if update_duplicates? && !connection.supports_insert_on_duplicate_update?
  74. raise ArgumentError, "#{connection.class} does not support upsert"
  75. end
  76. 158 if unique_by && !connection.supports_insert_conflict_target?
  77. raise ArgumentError, "#{connection.class} does not support :unique_by"
  78. end
  79. end
  80. 3 def to_sql
  81. 158 connection.build_insert_sql(ActiveRecord::InsertAll::Builder.new(self))
  82. end
  83. 3 def readonly_columns
  84. 335 primary_keys + model.readonly_attributes.to_a
  85. end
  86. 3 def unique_by_columns
  87. 335 Array(unique_by&.columns)
  88. end
  89. 3 def verify_attributes(attributes)
  90. 211 if keys != attributes.keys.to_set
  91. raise ArgumentError, "All objects being inserted must have the same keys"
  92. end
  93. end
  94. 3 class Builder # :nodoc:
  95. 3 attr_reader :model
  96. 3 delegate :skip_duplicates?, :update_duplicates?, :keys, to: :insert_all
  97. 3 def initialize(insert_all)
  98. 158 @insert_all, @model, @connection = insert_all, insert_all.model, insert_all.connection
  99. end
  100. 3 def into
  101. 158 "INTO #{model.quoted_table_name} (#{columns_list})"
  102. end
  103. 3 def values_list
  104. 158 types = extract_types_from_columns_on(model.table_name, keys: keys)
  105. 155 values_list = insert_all.map_key_with_value do |key, value|
  106. 506 connection.with_yaml_fallback(types[key].serialize(value))
  107. end
  108. 155 connection.visitor.compile(Arel::Nodes::ValuesList.new(values_list))
  109. end
  110. 3 def returning
  111. 107 format_columns(insert_all.returning) if insert_all.returning
  112. end
  113. 3 def conflict_target
  114. 121 if index = insert_all.unique_by
  115. 34 sql = +"(#{format_columns(index.columns)})"
  116. 34 sql << " WHERE #{index.where}" if index.where
  117. 34 sql
  118. 87 elsif update_duplicates?
  119. 45 "(#{format_columns(insert_all.primary_keys)})"
  120. end
  121. end
  122. 3 def updatable_columns
  123. 163 quote_columns(insert_all.updatable_columns)
  124. end
  125. 3 def touch_model_timestamps_unless(&block)
  126. model.send(:timestamp_attributes_for_update_in_model).map do |column_name|
  127. 108 if touch_timestamp_attribute?(column_name)
  128. 102 "#{column_name}=(CASE WHEN (#{updatable_columns.map(&block).join(" AND ")}) THEN #{model.quoted_table_name}.#{column_name} ELSE CURRENT_TIMESTAMP END),"
  129. end
  130. 61 end.compact.join
  131. end
  132. 3 private
  133. 3 attr_reader :connection, :insert_all
  134. 3 def touch_timestamp_attribute?(column_name)
  135. 108 update_duplicates? && !insert_all.updatable_columns.include?(column_name)
  136. end
  137. 3 def columns_list
  138. 158 format_columns(insert_all.keys)
  139. end
  140. 3 def extract_types_from_columns_on(table_name, keys:)
  141. 158 columns = connection.schema_cache.columns_hash(table_name)
  142. 158 unknown_column = (keys - columns.keys).first
  143. 158 raise UnknownAttributeError.new(model.new, unknown_column) if unknown_column
  144. 536 keys.index_with { |key| model.type_for_attribute(key) }
  145. end
  146. 3 def format_columns(columns)
  147. 341 columns.respond_to?(:map) ? quote_columns(columns).join(",") : columns
  148. end
  149. 3 def quote_columns(columns)
  150. 498 columns.map(&connection.method(:quote_column_name))
  151. end
  152. end
  153. end
  154. end

lib/active_record/integration.rb

98.15% lines covered

54 relevant lines. 53 lines covered and 1 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 require "active_support/core_ext/string/filters"
  3. 3 module ActiveRecord
  4. 3 module Integration
  5. 3 extend ActiveSupport::Concern
  6. 3 included do
  7. ##
  8. # :singleton-method:
  9. # Indicates the format used to generate the timestamp in the cache key, if
  10. # versioning is off. Accepts any of the symbols in <tt>Time::DATE_FORMATS</tt>.
  11. #
  12. # This is +:usec+, by default.
  13. 3 class_attribute :cache_timestamp_format, instance_writer: false, default: :usec
  14. ##
  15. # :singleton-method:
  16. # Indicates whether to use a stable #cache_key method that is accompanied
  17. # by a changing version in the #cache_version method.
  18. #
  19. # This is +true+, by default on Rails 5.2 and above.
  20. 3 class_attribute :cache_versioning, instance_writer: false, default: false
  21. ##
  22. # :singleton-method:
  23. # Indicates whether to use a stable #cache_key method that is accompanied
  24. # by a changing version in the #cache_version method on collections.
  25. #
  26. # This is +false+, by default until Rails 6.1.
  27. 3 class_attribute :collection_cache_versioning, instance_writer: false, default: false
  28. end
  29. # Returns a +String+, which Action Pack uses for constructing a URL to this
  30. # object. The default implementation returns this record's id as a +String+,
  31. # or +nil+ if this record's unsaved.
  32. #
  33. # For example, suppose that you have a User model, and that you have a
  34. # <tt>resources :users</tt> route. Normally, +user_path+ will
  35. # construct a path with the user object's 'id' in it:
  36. #
  37. # user = User.find_by(name: 'Phusion')
  38. # user_path(user) # => "/users/1"
  39. #
  40. # You can override +to_param+ in your model to make +user_path+ construct
  41. # a path using the user's name instead of the user's id:
  42. #
  43. # class User < ActiveRecord::Base
  44. # def to_param # overridden
  45. # name
  46. # end
  47. # end
  48. #
  49. # user = User.find_by(name: 'Phusion')
  50. # user_path(user) # => "/users/Phusion"
  51. 3 def to_param
  52. # We can't use alias_method here, because method 'id' optimizes itself on the fly.
  53. 48 id && id.to_s # Be sure to stringify the id for routes
  54. end
  55. # Returns a stable cache key that can be used to identify this record.
  56. #
  57. # Product.new.cache_key # => "products/new"
  58. # Product.find(5).cache_key # => "products/5"
  59. #
  60. # If ActiveRecord::Base.cache_versioning is turned off, as it was in Rails 5.1 and earlier,
  61. # the cache key will also include a version.
  62. #
  63. # Product.cache_versioning = false
  64. # Product.find(5).cache_key # => "products/5-20071224150000" (updated_at available)
  65. 3 def cache_key
  66. 69 if new_record?
  67. "#{model_name.cache_key}/new"
  68. else
  69. 69 if cache_version
  70. 18 "#{model_name.cache_key}/#{id}"
  71. else
  72. 51 timestamp = max_updated_column_timestamp
  73. 51 if timestamp
  74. 48 timestamp = timestamp.utc.to_s(cache_timestamp_format)
  75. 48 "#{model_name.cache_key}/#{id}-#{timestamp}"
  76. else
  77. 3 "#{model_name.cache_key}/#{id}"
  78. end
  79. end
  80. end
  81. end
  82. # Returns a cache version that can be used together with the cache key to form
  83. # a recyclable caching scheme. By default, the #updated_at column is used for the
  84. # cache_version, but this method can be overwritten to return something else.
  85. #
  86. # Note, this method will return nil if ActiveRecord::Base.cache_versioning is set to
  87. # +false+.
  88. 3 def cache_version
  89. 134 return unless cache_versioning
  90. 77 if has_attribute?("updated_at")
  91. 74 timestamp = updated_at_before_type_cast
  92. 74 if can_use_fast_cache_version?(timestamp)
  93. 22 raw_timestamp_to_cache_version(timestamp)
  94. 52 elsif timestamp = updated_at
  95. 40 timestamp.utc.to_s(cache_timestamp_format)
  96. end
  97. 3 elsif self.class.has_attribute?("updated_at")
  98. 3 raise ActiveModel::MissingAttributeError, "missing attribute: updated_at"
  99. end
  100. end
  101. # Returns a cache key along with the version.
  102. 3 def cache_key_with_version
  103. 12 if version = cache_version
  104. 9 "#{cache_key}-#{version}"
  105. else
  106. 3 cache_key
  107. end
  108. end
  109. 3 module ClassMethods
  110. # Defines your model's +to_param+ method to generate "pretty" URLs
  111. # using +method_name+, which can be any attribute or method that
  112. # responds to +to_s+.
  113. #
  114. # class User < ActiveRecord::Base
  115. # to_param :name
  116. # end
  117. #
  118. # user = User.find_by(name: 'Fancy Pants')
  119. # user.id # => 123
  120. # user_path(user) # => "/users/123-fancy-pants"
  121. #
  122. # Values longer than 20 characters will be truncated. The value
  123. # is truncated word by word.
  124. #
  125. # user = User.find_by(name: 'David Heinemeier Hansson')
  126. # user.id # => 125
  127. # user_path(user) # => "/users/125-david-heinemeier"
  128. #
  129. # Because the generated param begins with the record's +id+, it is
  130. # suitable for passing to +find+. In a controller, for example:
  131. #
  132. # params[:id] # => "123-fancy-pants"
  133. # User.find(params[:id]).id # => 123
  134. 3 def to_param(method_name = nil)
  135. 6 if method_name.nil?
  136. 3 super()
  137. else
  138. 3 define_method :to_param do
  139. 36 if (default = super()) &&
  140. 33 (result = send(method_name).to_s).present? &&
  141. 27 (param = result.squish.parameterize.truncate(20, separator: /-/, omission: "")).present?
  142. 24 "#{default}-#{param}"
  143. else
  144. 12 default
  145. end
  146. end
  147. end
  148. end
  149. 3 def collection_cache_key(collection = all, timestamp_column = :updated_at) # :nodoc:
  150. 75 collection.send(:compute_cache_key, timestamp_column)
  151. end
  152. end
  153. 3 private
  154. # Detects if the value before type cast
  155. # can be used to generate a cache_version.
  156. #
  157. # The fast cache version only works with a
  158. # string value directly from the database.
  159. #
  160. # We also must check if the timestamp format has been changed
  161. # or if the timezone is not set to UTC then
  162. # we cannot apply our transformations correctly.
  163. 3 def can_use_fast_cache_version?(timestamp)
  164. 74 timestamp.is_a?(String) &&
  165. cache_timestamp_format == :usec &&
  166. default_timezone == :utc &&
  167. !updated_at_came_from_user?
  168. end
  169. # Converts a raw database string to `:usec`
  170. # format.
  171. #
  172. # Example:
  173. #
  174. # timestamp = "2018-10-15 20:02:15.266505"
  175. # raw_timestamp_to_cache_version(timestamp)
  176. # # => "20181015200215266505"
  177. #
  178. # PostgreSQL truncates trailing zeros,
  179. # https://github.com/postgres/postgres/commit/3e1beda2cde3495f41290e1ece5d544525810214
  180. # to account for this we pad the output with zeros
  181. 3 def raw_timestamp_to_cache_version(timestamp)
  182. 22 key = timestamp.delete("- :.")
  183. 22 if key.length < 20
  184. 4 key.ljust(20, "0")
  185. else
  186. 18 key
  187. end
  188. end
  189. end
  190. end

lib/active_record/internal_metadata.rb

96.67% lines covered

30 relevant lines. 29 lines covered and 1 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 require "active_record/scoping/default"
  3. 3 require "active_record/scoping/named"
  4. 3 module ActiveRecord
  5. # This class is used to create a table that keeps track of values and keys such
  6. # as which environment migrations were run in.
  7. #
  8. # This is enabled by default. To disable this functionality set
  9. # `use_metadata_table` to false in your database configuration.
  10. 3 class InternalMetadata < ActiveRecord::Base # :nodoc:
  11. 3 class << self
  12. 3 def enabled?
  13. 806 ActiveRecord::Base.connection.use_metadata_table?
  14. end
  15. 3 def _internal?
  16. true
  17. end
  18. 3 def primary_key
  19. 1606 "key"
  20. end
  21. 3 def table_name
  22. 631 "#{table_name_prefix}#{internal_metadata_table_name}#{table_name_suffix}"
  23. end
  24. 3 def []=(key, value)
  25. 266 return unless enabled?
  26. 263 find_or_initialize_by(key: key).update!(value: value)
  27. end
  28. 3 def [](key)
  29. 51 return unless enabled?
  30. 48 where(key: key).pluck(:value).first
  31. end
  32. # Creates an internal metadata table with columns +key+ and +value+
  33. 3 def create_table
  34. 444 return unless enabled?
  35. 441 unless table_exists?
  36. 8 key_options = connection.internal_string_options_for_primary_key
  37. 8 connection.create_table(table_name, id: false) do |t|
  38. 8 t.string :key, **key_options
  39. 8 t.string :value
  40. 8 t.timestamps
  41. end
  42. end
  43. end
  44. 3 def drop_table
  45. 3 return unless enabled?
  46. 3 connection.drop_table table_name, if_exists: true
  47. end
  48. end
  49. end
  50. end

lib/active_record/legacy_yaml_adapter.rb

100.0% lines covered

24 relevant lines. 24 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 module ActiveRecord
  3. 3 module LegacyYamlAdapter # :nodoc:
  4. 3 def self.convert(klass, coder)
  5. 68 return coder unless coder.is_a?(Psych::Coder)
  6. 68 case coder["active_record_yaml_version"]
  7. 62 when 1, 2 then coder
  8. else
  9. 6 ActiveSupport::Deprecation.warn(<<-MSG.squish)
  10. YAML loading from legacy format older than Rails 5.0 is deprecated
  11. and will be removed in Rails 6.2.
  12. MSG
  13. 6 if coder["attributes"].is_a?(ActiveModel::AttributeSet)
  14. 3 Rails420.convert(klass, coder)
  15. else
  16. 3 Rails41.convert(klass, coder)
  17. end
  18. end
  19. end
  20. 3 module Rails420 # :nodoc:
  21. 3 def self.convert(klass, coder)
  22. 3 attribute_set = coder["attributes"]
  23. 3 klass.attribute_names.each do |attr_name|
  24. 54 attribute = attribute_set[attr_name]
  25. 54 if attribute.type.is_a?(Delegator)
  26. 3 type_from_klass = klass.type_for_attribute(attr_name)
  27. 3 attribute_set[attr_name] = attribute.with_type(type_from_klass)
  28. end
  29. end
  30. 3 coder
  31. end
  32. end
  33. 3 module Rails41 # :nodoc:
  34. 3 def self.convert(klass, coder)
  35. 3 attributes = klass.attributes_builder
  36. .build_from_database(coder["attributes"])
  37. 3 new_record = coder["attributes"][klass.primary_key].blank?
  38. 3 {
  39. "attributes" => attributes,
  40. "new_record" => new_record,
  41. }
  42. end
  43. end
  44. end
  45. end

lib/active_record/locking/optimistic.rb

95.89% lines covered

73 relevant lines. 70 lines covered and 3 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 module ActiveRecord
  3. 3 module Locking
  4. # == What is Optimistic Locking
  5. #
  6. # Optimistic locking allows multiple users to access the same record for edits, and assumes a minimum of
  7. # conflicts with the data. It does this by checking whether another process has made changes to a record since
  8. # it was opened, an <tt>ActiveRecord::StaleObjectError</tt> exception is thrown if that has occurred
  9. # and the update is ignored.
  10. #
  11. # Check out <tt>ActiveRecord::Locking::Pessimistic</tt> for an alternative.
  12. #
  13. # == Usage
  14. #
  15. # Active Record supports optimistic locking if the +lock_version+ field is present. Each update to the
  16. # record increments the +lock_version+ column and the locking facilities ensure that records instantiated twice
  17. # will let the last one saved raise a +StaleObjectError+ if the first was also updated. Example:
  18. #
  19. # p1 = Person.find(1)
  20. # p2 = Person.find(1)
  21. #
  22. # p1.first_name = "Michael"
  23. # p1.save
  24. #
  25. # p2.first_name = "should fail"
  26. # p2.save # Raises an ActiveRecord::StaleObjectError
  27. #
  28. # Optimistic locking will also check for stale data when objects are destroyed. Example:
  29. #
  30. # p1 = Person.find(1)
  31. # p2 = Person.find(1)
  32. #
  33. # p1.first_name = "Michael"
  34. # p1.save
  35. #
  36. # p2.destroy # Raises an ActiveRecord::StaleObjectError
  37. #
  38. # You're then responsible for dealing with the conflict by rescuing the exception and either rolling back, merging,
  39. # or otherwise apply the business logic needed to resolve the conflict.
  40. #
  41. # This locking mechanism will function inside a single Ruby process. To make it work across all
  42. # web requests, the recommended approach is to add +lock_version+ as a hidden field to your form.
  43. #
  44. # This behavior can be turned off by setting <tt>ActiveRecord::Base.lock_optimistically = false</tt>.
  45. # To override the name of the +lock_version+ column, set the <tt>locking_column</tt> class attribute:
  46. #
  47. # class Person < ActiveRecord::Base
  48. # self.locking_column = :lock_person
  49. # end
  50. #
  51. 3 module Optimistic
  52. 3 extend ActiveSupport::Concern
  53. 3 included do
  54. 3 class_attribute :lock_optimistically, instance_writer: false, default: true
  55. end
  56. 3 def locking_enabled? #:nodoc:
  57. 16784 self.class.locking_enabled?
  58. end
  59. 3 def increment!(*, **) #:nodoc:
  60. 486 super.tap do
  61. 486 if locking_enabled?
  62. 33 self[self.class.locking_column] += 1
  63. 33 clear_attribute_change(self.class.locking_column)
  64. end
  65. end
  66. end
  67. 3 private
  68. 3 def _create_record(attribute_names = self.attribute_names)
  69. 12437 if locking_enabled?
  70. # We always want to persist the locking version, even if we don't detect
  71. # a change from the default, since the database might have no default
  72. 1035 attribute_names |= [self.class.locking_column]
  73. end
  74. 12437 super
  75. end
  76. 3 def _touch_row(attribute_names, time)
  77. 504 @_touch_attr_names << self.class.locking_column if locking_enabled?
  78. 504 super
  79. end
  80. 3 def _update_row(attribute_names, attempted_action = "update")
  81. 2399 return super unless locking_enabled?
  82. 355 begin
  83. 355 locking_column = self.class.locking_column
  84. 355 previous_lock_value = read_attribute_before_type_cast(locking_column)
  85. 355 attribute_names = attribute_names.dup if attribute_names.frozen?
  86. 355 attribute_names << locking_column
  87. 355 self[locking_column] += 1
  88. 355 affected_rows = self.class._update_record(
  89. attributes_with_values(attribute_names),
  90. @primary_key => id_in_database,
  91. locking_column => @attributes[locking_column].original_value_for_database
  92. )
  93. 352 if affected_rows != 1
  94. 45 raise ActiveRecord::StaleObjectError.new(self, attempted_action)
  95. end
  96. 307 affected_rows
  97. # If something went wrong, revert the locking_column value.
  98. 48 rescue Exception
  99. 48 self[locking_column] = previous_lock_value.to_i
  100. 48 raise
  101. end
  102. end
  103. 3 def destroy_row
  104. 958 return super unless locking_enabled?
  105. 72 locking_column = self.class.locking_column
  106. 72 affected_rows = self.class._delete_record(
  107. @primary_key => id_in_database,
  108. locking_column => read_attribute_before_type_cast(locking_column)
  109. )
  110. 72 if affected_rows != 1
  111. 9 raise ActiveRecord::StaleObjectError.new(self, "destroy")
  112. end
  113. 63 affected_rows
  114. end
  115. 3 module ClassMethods
  116. 3 DEFAULT_LOCKING_COLUMN = "lock_version"
  117. # Returns true if the +lock_optimistically+ flag is set to true
  118. # (which it is, by default) and the table includes the
  119. # +locking_column+ column (defaults to +lock_version+).
  120. 3 def locking_enabled?
  121. 18668 lock_optimistically && columns_hash[locking_column]
  122. end
  123. # Set the column to use for optimistic locking. Defaults to +lock_version+.
  124. 3 def locking_column=(value)
  125. 15 reload_schema_from_cache
  126. 15 @locking_column = value.to_s
  127. end
  128. # The version column used for optimistic locking. Defaults to +lock_version+.
  129. 3 def locking_column
  130. 50754 @locking_column = DEFAULT_LOCKING_COLUMN unless defined?(@locking_column)
  131. 50754 @locking_column
  132. end
  133. # Reset the column used for optimistic locking back to the +lock_version+ default.
  134. 3 def reset_locking_column
  135. self.locking_column = DEFAULT_LOCKING_COLUMN
  136. end
  137. # Make sure the lock version column gets updated when counters are
  138. # updated.
  139. 3 def update_counters(id, counters)
  140. 618 counters = counters.merge(locking_column => 1) if locking_enabled?
  141. 618 super
  142. end
  143. 3 def define_attribute(name, cast_type, **) # :nodoc:
  144. 30036 if lock_optimistically && name == locking_column
  145. 350 cast_type = LockingType.new(cast_type)
  146. end
  147. 30036 super
  148. end
  149. end
  150. end
  151. # In de/serialize we change `nil` to 0, so that we can allow passing
  152. # `nil` values to `lock_version`, and not result in `ActiveRecord::StaleObjectError`
  153. # during update record.
  154. 3 class LockingType < DelegateClass(Type::Value) # :nodoc:
  155. 3 def self.new(subtype)
  156. 350 self === subtype ? subtype : super
  157. end
  158. 3 def deserialize(value)
  159. 1721 super.to_i
  160. end
  161. 3 def serialize(value)
  162. 3324 super.to_i
  163. end
  164. 3 def init_with(coder)
  165. __setobj__(coder["subtype"])
  166. end
  167. 3 def encode_with(coder)
  168. coder["subtype"] = __getobj__
  169. end
  170. end
  171. end
  172. end

lib/active_record/locking/pessimistic.rb

100.0% lines covered

13 relevant lines. 13 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 module ActiveRecord
  3. 3 module Locking
  4. # Locking::Pessimistic provides support for row-level locking using
  5. # SELECT ... FOR UPDATE and other lock types.
  6. #
  7. # Chain <tt>ActiveRecord::Base#find</tt> to <tt>ActiveRecord::QueryMethods#lock</tt> to obtain an exclusive
  8. # lock on the selected rows:
  9. # # select * from accounts where id=1 for update
  10. # Account.lock.find(1)
  11. #
  12. # Call <tt>lock('some locking clause')</tt> to use a database-specific locking clause
  13. # of your own such as 'LOCK IN SHARE MODE' or 'FOR UPDATE NOWAIT'. Example:
  14. #
  15. # Account.transaction do
  16. # # select * from accounts where name = 'shugo' limit 1 for update nowait
  17. # shugo = Account.lock("FOR UPDATE NOWAIT").find_by(name: "shugo")
  18. # yuko = Account.lock("FOR UPDATE NOWAIT").find_by(name: "yuko")
  19. # shugo.balance -= 100
  20. # shugo.save!
  21. # yuko.balance += 100
  22. # yuko.save!
  23. # end
  24. #
  25. # You can also use <tt>ActiveRecord::Base#lock!</tt> method to lock one record by id.
  26. # This may be better if you don't need to lock every row. Example:
  27. #
  28. # Account.transaction do
  29. # # select * from accounts where ...
  30. # accounts = Account.where(...)
  31. # account1 = accounts.detect { |account| ... }
  32. # account2 = accounts.detect { |account| ... }
  33. # # select * from accounts where id=? for update
  34. # account1.lock!
  35. # account2.lock!
  36. # account1.balance -= 100
  37. # account1.save!
  38. # account2.balance += 100
  39. # account2.save!
  40. # end
  41. #
  42. # You can start a transaction and acquire the lock in one go by calling
  43. # <tt>with_lock</tt> with a block. The block is called from within
  44. # a transaction, the object is already locked. Example:
  45. #
  46. # account = Account.first
  47. # account.with_lock do
  48. # # This block is called within a transaction,
  49. # # account is already locked.
  50. # account.balance -= 100
  51. # account.save!
  52. # end
  53. #
  54. # Database-specific information on row locking:
  55. #
  56. # [MySQL]
  57. # https://dev.mysql.com/doc/refman/en/innodb-locking-reads.html
  58. #
  59. # [PostgreSQL]
  60. # https://www.postgresql.org/docs/current/interactive/sql-select.html#SQL-FOR-UPDATE-SHARE
  61. 3 module Pessimistic
  62. # Obtain a row lock on this record. Reloads the record to obtain the requested
  63. # lock. Pass an SQL locking clause to append the end of the SELECT statement
  64. # or pass true for "FOR UPDATE" (the default, an exclusive row lock). Returns
  65. # the locked record.
  66. 3 def lock!(lock = true)
  67. 25 if persisted?
  68. 25 if has_changes_to_save?
  69. 2 raise(<<-MSG.squish)
  70. Locking a record with unpersisted changes is not supported. Use
  71. `save` to persist the changes, or `reload` to discard them
  72. explicitly.
  73. MSG
  74. end
  75. 23 reload(lock: lock)
  76. end
  77. 23 self
  78. end
  79. # Wraps the passed block in a transaction, locking the object
  80. # before yielding. You can pass the SQL locking clause
  81. # as argument (see <tt>lock!</tt>).
  82. 3 def with_lock(lock = true)
  83. 12 transaction do
  84. 12 lock!(lock)
  85. 12 yield
  86. end
  87. end
  88. end
  89. end
  90. end

lib/active_record/log_subscriber.rb

97.01% lines covered

67 relevant lines. 65 lines covered and 2 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 module ActiveRecord
  3. 3 class LogSubscriber < ActiveSupport::LogSubscriber
  4. 3 IGNORE_PAYLOAD_NAMES = ["SCHEMA", "EXPLAIN"]
  5. 3 class_attribute :backtrace_cleaner, default: ActiveSupport::BacktraceCleaner.new
  6. 3 def self.runtime=(value)
  7. 428212 ActiveRecord::RuntimeRegistry.sql_runtime = value
  8. end
  9. 3 def self.runtime
  10. 428215 ActiveRecord::RuntimeRegistry.sql_runtime ||= 0
  11. end
  12. 3 def self.reset_runtime
  13. rt, self.runtime = runtime, 0
  14. rt
  15. end
  16. 3 def sql(event)
  17. 428212 self.class.runtime += event.duration
  18. 428212 return unless logger.debug?
  19. 428188 payload = event.payload
  20. 428188 return if IGNORE_PAYLOAD_NAMES.include?(payload[:name])
  21. 298245 name = "#{payload[:name]} (#{event.duration.round(1)}ms)"
  22. 298245 name = "CACHE #{name}" if payload[:cached]
  23. 298245 sql = payload[:sql]
  24. 298245 binds = nil
  25. 298245 if payload[:binds]&.any?
  26. 50631 casted_params = type_casted_binds(payload[:type_casted_binds])
  27. 50631 binds = []
  28. 50631 payload[:binds].each_with_index do |attr, i|
  29. 309086 binds << render_bind(attr, casted_params[i])
  30. end
  31. 50631 binds = binds.inspect
  32. 50631 binds.prepend(" ")
  33. end
  34. 298245 name = colorize_payload_name(name, payload[:name])
  35. 298245 sql = color(sql, sql_color(sql), true) if colorize_logging
  36. 298245 debug " #{name} #{sql}#{binds}"
  37. end
  38. 3 private
  39. 3 def type_casted_binds(casted_binds)
  40. 50631 casted_binds.respond_to?(:call) ? casted_binds.call : casted_binds
  41. end
  42. 3 def render_bind(attr, value)
  43. 309086 case attr
  44. when ActiveModel::Attribute
  45. 112784 if attr.type.binary? && attr.value
  46. 34 value = "<#{attr.value_for_database.to_s.bytesize} bytes of binary data>"
  47. end
  48. when Array
  49. 18 attr = attr.first
  50. else
  51. 196284 attr = nil
  52. end
  53. 309086 [attr&.name, value]
  54. end
  55. 3 def colorize_payload_name(name, payload_name)
  56. 298245 if payload_name.blank? || payload_name == "SQL" # SQL vs Model Load/Exists
  57. 57996 color(name, MAGENTA, true)
  58. else
  59. 240249 color(name, CYAN, true)
  60. end
  61. end
  62. 3 def sql_color(sql)
  63. 238594 case sql
  64. when /\A\s*rollback/mi
  65. 60748 RED
  66. when /select .*for update/mi, /\A\s*lock/mi
  67. 43 WHITE
  68. when /\A\s*select/i
  69. 33982 BLUE
  70. when /\A\s*insert/i
  71. 13159 GREEN
  72. when /\A\s*update/i
  73. 3542 YELLOW
  74. when /\A\s*delete/i
  75. 4464 RED
  76. when /transaction\s*\Z/i
  77. 41011 CYAN
  78. else
  79. 81645 MAGENTA
  80. end
  81. end
  82. 3 def logger
  83. 1880711 ActiveRecord::Base.logger
  84. end
  85. 3 def debug(progname = nil, &block)
  86. 298239 return unless super
  87. 298239 if ActiveRecord::Base.verbose_query_logs
  88. 6 log_query_source
  89. end
  90. end
  91. 3 def log_query_source
  92. 6 source = extract_query_source_location(caller)
  93. 6 if source
  94. 3 logger.debug(" ↳ #{source}")
  95. end
  96. end
  97. 3 def extract_query_source_location(locations)
  98. 3 backtrace_cleaner.clean(locations.lazy).first
  99. end
  100. end
  101. end
  102. 3 ActiveRecord::LogSubscriber.attach_to :active_record

lib/active_record/middleware/database_selector.rb

100.0% lines covered

25 relevant lines. 25 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 require "active_record/middleware/database_selector/resolver"
  3. 3 module ActiveRecord
  4. 3 module Middleware
  5. # The DatabaseSelector Middleware provides a framework for automatically
  6. # swapping from the primary to the replica database connection. Rails
  7. # provides a basic framework to determine when to swap and allows for
  8. # applications to write custom strategy classes to override the default
  9. # behavior.
  10. #
  11. # The resolver class defines when the application should switch (i.e. read
  12. # from the primary if a write occurred less than 2 seconds ago) and a
  13. # resolver context class that sets a value that helps the resolver class
  14. # decide when to switch.
  15. #
  16. # Rails default middleware uses the request's session to set a timestamp
  17. # that informs the application when to read from a primary or read from a
  18. # replica.
  19. #
  20. # To use the DatabaseSelector in your application with default settings add
  21. # the following options to your environment config:
  22. #
  23. # config.active_record.database_selector = { delay: 2.seconds }
  24. # config.active_record.database_resolver = ActiveRecord::Middleware::DatabaseSelector::Resolver
  25. # config.active_record.database_resolver_context = ActiveRecord::Middleware::DatabaseSelector::Resolver::Session
  26. #
  27. # New applications will include these lines commented out in the production.rb.
  28. #
  29. # The default behavior can be changed by setting the config options to a
  30. # custom class:
  31. #
  32. # config.active_record.database_selector = { delay: 2.seconds }
  33. # config.active_record.database_resolver = MyResolver
  34. # config.active_record.database_resolver_context = MyResolver::MySession
  35. 3 class DatabaseSelector
  36. 3 def initialize(app, resolver_klass = nil, context_klass = nil, options = {})
  37. 6 @app = app
  38. 6 @resolver_klass = resolver_klass || Resolver
  39. 6 @context_klass = context_klass || Resolver::Session
  40. 6 @options = options
  41. end
  42. 3 attr_reader :resolver_klass, :context_klass, :options
  43. # Middleware that determines which database connection to use in a multiple
  44. # database application.
  45. 3 def call(env)
  46. 6 request = ActionDispatch::Request.new(env)
  47. 6 select_database(request) do
  48. 6 @app.call(env)
  49. end
  50. end
  51. 3 private
  52. 3 def select_database(request, &blk)
  53. 6 context = context_klass.call(request)
  54. 6 resolver = resolver_klass.call(context, options)
  55. 6 response = if reading_request?(request)
  56. 3 resolver.read(&blk)
  57. else
  58. 3 resolver.write(&blk)
  59. end
  60. 6 resolver.update_context(response)
  61. 6 response
  62. end
  63. 3 def reading_request?(request)
  64. 6 request.get? || request.head?
  65. end
  66. end
  67. end
  68. end

lib/active_record/middleware/database_selector/resolver.rb

100.0% lines covered

43 relevant lines. 43 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 require "active_record/middleware/database_selector/resolver/session"
  3. 3 require "active_support/core_ext/numeric/time"
  4. 3 module ActiveRecord
  5. 3 module Middleware
  6. 3 class DatabaseSelector
  7. # The Resolver class is used by the DatabaseSelector middleware to
  8. # determine which database the request should use.
  9. #
  10. # To change the behavior of the Resolver class in your application,
  11. # create a custom resolver class that inherits from
  12. # DatabaseSelector::Resolver and implements the methods that need to
  13. # be changed.
  14. #
  15. # By default the Resolver class will send read traffic to the replica
  16. # if it's been 2 seconds since the last write.
  17. 3 class Resolver # :nodoc:
  18. 3 SEND_TO_REPLICA_DELAY = 2.seconds
  19. 3 def self.call(context, options = {})
  20. 6 new(context, options)
  21. end
  22. 3 def initialize(context, options = {})
  23. 35 @context = context
  24. 35 @options = options
  25. 35 @delay = @options && @options[:delay] ? @options[:delay] : SEND_TO_REPLICA_DELAY
  26. 35 @instrumenter = ActiveSupport::Notifications.instrumenter
  27. end
  28. 3 attr_reader :context, :delay, :instrumenter
  29. 3 def read(&blk)
  30. 26 if read_from_primary?
  31. 15 read_from_primary(&blk)
  32. else
  33. 11 read_from_replica(&blk)
  34. end
  35. end
  36. 3 def write(&blk)
  37. 27 write_to_primary(&blk)
  38. end
  39. 3 def update_context(response)
  40. 9 context.save(response)
  41. end
  42. 3 private
  43. 3 def read_from_primary(&blk)
  44. 15 ActiveRecord::Base.connected_to(role: ActiveRecord::Base.writing_role, prevent_writes: true) do
  45. 15 instrumenter.instrument("database_selector.active_record.read_from_primary") do
  46. 15 yield
  47. end
  48. end
  49. end
  50. 3 def read_from_replica(&blk)
  51. 11 ActiveRecord::Base.connected_to(role: ActiveRecord::Base.reading_role, prevent_writes: true) do
  52. 11 instrumenter.instrument("database_selector.active_record.read_from_replica") do
  53. 11 yield
  54. end
  55. end
  56. end
  57. 3 def write_to_primary(&blk)
  58. 27 ActiveRecord::Base.connected_to(role: ActiveRecord::Base.writing_role, prevent_writes: false) do
  59. 27 instrumenter.instrument("database_selector.active_record.wrote_to_primary") do
  60. 27 yield
  61. ensure
  62. 27 context.update_last_write_timestamp
  63. end
  64. end
  65. end
  66. 3 def read_from_primary?
  67. 26 !time_since_last_write_ok?
  68. end
  69. 3 def send_to_replica_delay
  70. 26 delay
  71. end
  72. 3 def time_since_last_write_ok?
  73. 26 Time.now - context.last_write_timestamp >= send_to_replica_delay
  74. end
  75. end
  76. end
  77. end
  78. end

lib/active_record/middleware/database_selector/resolver/session.rb

100.0% lines covered

19 relevant lines. 19 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 module ActiveRecord
  3. 3 module Middleware
  4. 3 class DatabaseSelector
  5. 3 class Resolver
  6. # The session class is used by the DatabaseSelector::Resolver to save
  7. # timestamps of the last write in the session.
  8. #
  9. # The last_write is used to determine whether it's safe to read
  10. # from the replica or the request needs to be sent to the primary.
  11. 3 class Session # :nodoc:
  12. 3 def self.call(request)
  13. 6 new(request.session)
  14. end
  15. # Converts time to a timestamp that represents milliseconds since
  16. # epoch.
  17. 3 def self.convert_time_to_timestamp(time)
  18. 47 time.to_i * 1000 + time.usec / 1000
  19. end
  20. # Converts milliseconds since epoch timestamp into a time object.
  21. 3 def self.convert_timestamp_to_time(timestamp)
  22. 41 timestamp ? Time.at(timestamp / 1000, (timestamp % 1000) * 1000) : Time.at(0)
  23. end
  24. 3 def initialize(session)
  25. 56 @session = session
  26. end
  27. 3 attr_reader :session
  28. 3 def last_write_timestamp
  29. 41 self.class.convert_timestamp_to_time(session[:last_write])
  30. end
  31. 3 def update_last_write_timestamp
  32. 39 session[:last_write] = self.class.convert_time_to_timestamp(Time.now)
  33. end
  34. 3 def save(response)
  35. end
  36. end
  37. end
  38. end
  39. end
  40. end

lib/active_record/migration.rb

95.44% lines covered

504 relevant lines. 481 lines covered and 23 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 require "benchmark"
  3. 3 require "set"
  4. 3 require "zlib"
  5. 3 require "active_support/core_ext/array/access"
  6. 3 require "active_support/core_ext/enumerable"
  7. 3 require "active_support/core_ext/module/attribute_accessors"
  8. 3 require "active_support/actionable_error"
  9. 3 module ActiveRecord
  10. 3 class MigrationError < ActiveRecordError #:nodoc:
  11. 3 def initialize(message = nil)
  12. 101 message = "\n\n#{message}\n\n" if message
  13. 101 super
  14. end
  15. end
  16. # Exception that can be raised to stop migrations from being rolled back.
  17. # For example the following migration is not reversible.
  18. # Rolling back this migration will raise an ActiveRecord::IrreversibleMigration error.
  19. #
  20. # class IrreversibleMigrationExample < ActiveRecord::Migration[6.0]
  21. # def change
  22. # create_table :distributors do |t|
  23. # t.string :zipcode
  24. # end
  25. #
  26. # execute <<~SQL
  27. # ALTER TABLE distributors
  28. # ADD CONSTRAINT zipchk
  29. # CHECK (char_length(zipcode) = 5) NO INHERIT;
  30. # SQL
  31. # end
  32. # end
  33. #
  34. # There are two ways to mitigate this problem.
  35. #
  36. # 1. Define <tt>#up</tt> and <tt>#down</tt> methods instead of <tt>#change</tt>:
  37. #
  38. # class ReversibleMigrationExample < ActiveRecord::Migration[6.0]
  39. # def up
  40. # create_table :distributors do |t|
  41. # t.string :zipcode
  42. # end
  43. #
  44. # execute <<~SQL
  45. # ALTER TABLE distributors
  46. # ADD CONSTRAINT zipchk
  47. # CHECK (char_length(zipcode) = 5) NO INHERIT;
  48. # SQL
  49. # end
  50. #
  51. # def down
  52. # execute <<~SQL
  53. # ALTER TABLE distributors
  54. # DROP CONSTRAINT zipchk
  55. # SQL
  56. #
  57. # drop_table :distributors
  58. # end
  59. # end
  60. #
  61. # 2. Use the #reversible method in <tt>#change</tt> method:
  62. #
  63. # class ReversibleMigrationExample < ActiveRecord::Migration[6.0]
  64. # def change
  65. # create_table :distributors do |t|
  66. # t.string :zipcode
  67. # end
  68. #
  69. # reversible do |dir|
  70. # dir.up do
  71. # execute <<~SQL
  72. # ALTER TABLE distributors
  73. # ADD CONSTRAINT zipchk
  74. # CHECK (char_length(zipcode) = 5) NO INHERIT;
  75. # SQL
  76. # end
  77. #
  78. # dir.down do
  79. # execute <<~SQL
  80. # ALTER TABLE distributors
  81. # DROP CONSTRAINT zipchk
  82. # SQL
  83. # end
  84. # end
  85. # end
  86. # end
  87. 3 class IrreversibleMigration < MigrationError
  88. end
  89. 3 class DuplicateMigrationVersionError < MigrationError #:nodoc:
  90. 3 def initialize(version = nil)
  91. 6 if version
  92. 3 super("Multiple migrations have the version number #{version}.")
  93. else
  94. 3 super("Duplicate migration version error.")
  95. end
  96. end
  97. end
  98. 3 class DuplicateMigrationNameError < MigrationError #:nodoc:
  99. 3 def initialize(name = nil)
  100. 6 if name
  101. 3 super("Multiple migrations have the name #{name}.")
  102. else
  103. 3 super("Duplicate migration name.")
  104. end
  105. end
  106. end
  107. 3 class UnknownMigrationVersionError < MigrationError #:nodoc:
  108. 3 def initialize(version = nil)
  109. 18 if version
  110. 15 super("No migration with version number #{version}.")
  111. else
  112. 3 super("Unknown migration version.")
  113. end
  114. end
  115. end
  116. 3 class IllegalMigrationNameError < MigrationError #:nodoc:
  117. 3 def initialize(name = nil)
  118. 3 if name
  119. super("Illegal name for migration file: #{name}\n\t(only lower case letters, numbers, and '_' allowed).")
  120. else
  121. 3 super("Illegal name for migration.")
  122. end
  123. end
  124. end
  125. 3 class PendingMigrationError < MigrationError #:nodoc:
  126. 3 include ActiveSupport::ActionableError
  127. 3 action "Run pending migrations" do
  128. 1 ActiveRecord::Tasks::DatabaseTasks.migrate
  129. end
  130. 3 def initialize(message = nil)
  131. 6 if !message && defined?(Rails.env)
  132. super("Migrations are pending. To resolve this issue, run:\n\n bin/rails db:migrate RAILS_ENV=#{::Rails.env}")
  133. 6 elsif !message
  134. 6 super("Migrations are pending. To resolve this issue, run:\n\n bin/rails db:migrate")
  135. else
  136. super
  137. end
  138. end
  139. end
  140. 3 class ConcurrentMigrationError < MigrationError #:nodoc:
  141. 3 DEFAULT_MESSAGE = "Cannot run migrations because another migration process is currently running."
  142. 3 RELEASE_LOCK_FAILED_MESSAGE = "Failed to release advisory lock"
  143. 3 def initialize(message = DEFAULT_MESSAGE)
  144. 6 super
  145. end
  146. end
  147. 3 class NoEnvironmentInSchemaError < MigrationError #:nodoc:
  148. 3 def initialize
  149. 6 msg = "Environment data not found in the schema. To resolve this issue, run: \n\n bin/rails db:environment:set"
  150. 6 if defined?(Rails.env)
  151. super("#{msg} RAILS_ENV=#{::Rails.env}")
  152. else
  153. 6 super(msg)
  154. end
  155. end
  156. end
  157. 3 class ProtectedEnvironmentError < ActiveRecordError #:nodoc:
  158. 3 def initialize(env = "production")
  159. 9 msg = +"You are attempting to run a destructive action against your '#{env}' database.\n"
  160. 9 msg << "If you are sure you want to continue, run the same command with the environment variable:\n"
  161. 9 msg << "DISABLE_DATABASE_ENVIRONMENT_CHECK=1"
  162. 9 super(msg)
  163. end
  164. end
  165. 3 class EnvironmentMismatchError < ActiveRecordError
  166. 3 def initialize(current: nil, stored: nil)
  167. 3 msg = +"You are attempting to modify a database that was last run in `#{ stored }` environment.\n"
  168. 3 msg << "You are running in `#{ current }` environment. "
  169. 3 msg << "If you are sure you want to continue, first set the environment using:\n\n"
  170. 3 msg << " bin/rails db:environment:set"
  171. 3 if defined?(Rails.env)
  172. super("#{msg} RAILS_ENV=#{::Rails.env}\n\n")
  173. else
  174. 3 super("#{msg}\n\n")
  175. end
  176. end
  177. end
  178. 3 class EnvironmentStorageError < ActiveRecordError # :nodoc:
  179. 3 def initialize
  180. 3 msg = +"You are attempting to store the environment in a database where metadata is disabled.\n"
  181. 3 msg << "Check your database configuration to see if this is intended."
  182. 3 super(msg)
  183. end
  184. end
  185. # = Active Record Migrations
  186. #
  187. # Migrations can manage the evolution of a schema used by several physical
  188. # databases. It's a solution to the common problem of adding a field to make
  189. # a new feature work in your local database, but being unsure of how to
  190. # push that change to other developers and to the production server. With
  191. # migrations, you can describe the transformations in self-contained classes
  192. # that can be checked into version control systems and executed against
  193. # another database that might be one, two, or five versions behind.
  194. #
  195. # Example of a simple migration:
  196. #
  197. # class AddSsl < ActiveRecord::Migration[6.0]
  198. # def up
  199. # add_column :accounts, :ssl_enabled, :boolean, default: true
  200. # end
  201. #
  202. # def down
  203. # remove_column :accounts, :ssl_enabled
  204. # end
  205. # end
  206. #
  207. # This migration will add a boolean flag to the accounts table and remove it
  208. # if you're backing out of the migration. It shows how all migrations have
  209. # two methods +up+ and +down+ that describes the transformations
  210. # required to implement or remove the migration. These methods can consist
  211. # of both the migration specific methods like +add_column+ and +remove_column+,
  212. # but may also contain regular Ruby code for generating data needed for the
  213. # transformations.
  214. #
  215. # Example of a more complex migration that also needs to initialize data:
  216. #
  217. # class AddSystemSettings < ActiveRecord::Migration[6.0]
  218. # def up
  219. # create_table :system_settings do |t|
  220. # t.string :name
  221. # t.string :label
  222. # t.text :value
  223. # t.string :type
  224. # t.integer :position
  225. # end
  226. #
  227. # SystemSetting.create name: 'notice',
  228. # label: 'Use notice?',
  229. # value: 1
  230. # end
  231. #
  232. # def down
  233. # drop_table :system_settings
  234. # end
  235. # end
  236. #
  237. # This migration first adds the +system_settings+ table, then creates the very
  238. # first row in it using the Active Record model that relies on the table. It
  239. # also uses the more advanced +create_table+ syntax where you can specify a
  240. # complete table schema in one block call.
  241. #
  242. # == Available transformations
  243. #
  244. # === Creation
  245. #
  246. # * <tt>create_join_table(table_1, table_2, options)</tt>: Creates a join
  247. # table having its name as the lexical order of the first two
  248. # arguments. See
  249. # ActiveRecord::ConnectionAdapters::SchemaStatements#create_join_table for
  250. # details.
  251. # * <tt>create_table(name, options)</tt>: Creates a table called +name+ and
  252. # makes the table object available to a block that can then add columns to it,
  253. # following the same format as +add_column+. See example above. The options hash
  254. # is for fragments like "DEFAULT CHARSET=UTF-8" that are appended to the create
  255. # table definition.
  256. # * <tt>add_column(table_name, column_name, type, options)</tt>: Adds a new column
  257. # to the table called +table_name+
  258. # named +column_name+ specified to be one of the following types:
  259. # <tt>:string</tt>, <tt>:text</tt>, <tt>:integer</tt>, <tt>:float</tt>,
  260. # <tt>:decimal</tt>, <tt>:datetime</tt>, <tt>:timestamp</tt>, <tt>:time</tt>,
  261. # <tt>:date</tt>, <tt>:binary</tt>, <tt>:boolean</tt>. A default value can be
  262. # specified by passing an +options+ hash like <tt>{ default: 11 }</tt>.
  263. # Other options include <tt>:limit</tt> and <tt>:null</tt> (e.g.
  264. # <tt>{ limit: 50, null: false }</tt>) -- see
  265. # ActiveRecord::ConnectionAdapters::TableDefinition#column for details.
  266. # * <tt>add_foreign_key(from_table, to_table, options)</tt>: Adds a new
  267. # foreign key. +from_table+ is the table with the key column, +to_table+ contains
  268. # the referenced primary key.
  269. # * <tt>add_index(table_name, column_names, options)</tt>: Adds a new index
  270. # with the name of the column. Other options include
  271. # <tt>:name</tt>, <tt>:unique</tt> (e.g.
  272. # <tt>{ name: 'users_name_index', unique: true }</tt>) and <tt>:order</tt>
  273. # (e.g. <tt>{ order: { name: :desc } }</tt>).
  274. # * <tt>add_reference(:table_name, :reference_name)</tt>: Adds a new column
  275. # +reference_name_id+ by default an integer. See
  276. # ActiveRecord::ConnectionAdapters::SchemaStatements#add_reference for details.
  277. # * <tt>add_timestamps(table_name, options)</tt>: Adds timestamps (+created_at+
  278. # and +updated_at+) columns to +table_name+.
  279. #
  280. # === Modification
  281. #
  282. # * <tt>change_column(table_name, column_name, type, options)</tt>: Changes
  283. # the column to a different type using the same parameters as add_column.
  284. # * <tt>change_column_default(table_name, column_name, default_or_changes)</tt>:
  285. # Sets a default value for +column_name+ defined by +default_or_changes+ on
  286. # +table_name+. Passing a hash containing <tt>:from</tt> and <tt>:to</tt>
  287. # as +default_or_changes+ will make this change reversible in the migration.
  288. # * <tt>change_column_null(table_name, column_name, null, default = nil)</tt>:
  289. # Sets or removes a +NOT NULL+ constraint on +column_name+. The +null+ flag
  290. # indicates whether the value can be +NULL+. See
  291. # ActiveRecord::ConnectionAdapters::SchemaStatements#change_column_null for
  292. # details.
  293. # * <tt>change_table(name, options)</tt>: Allows to make column alterations to
  294. # the table called +name+. It makes the table object available to a block that
  295. # can then add/remove columns, indexes or foreign keys to it.
  296. # * <tt>rename_column(table_name, column_name, new_column_name)</tt>: Renames
  297. # a column but keeps the type and content.
  298. # * <tt>rename_index(table_name, old_name, new_name)</tt>: Renames an index.
  299. # * <tt>rename_table(old_name, new_name)</tt>: Renames the table called +old_name+
  300. # to +new_name+.
  301. #
  302. # === Deletion
  303. #
  304. # * <tt>drop_table(name)</tt>: Drops the table called +name+.
  305. # * <tt>drop_join_table(table_1, table_2, options)</tt>: Drops the join table
  306. # specified by the given arguments.
  307. # * <tt>remove_column(table_name, column_name, type, options)</tt>: Removes the column
  308. # named +column_name+ from the table called +table_name+.
  309. # * <tt>remove_columns(table_name, *column_names)</tt>: Removes the given
  310. # columns from the table definition.
  311. # * <tt>remove_foreign_key(from_table, to_table = nil, **options)</tt>: Removes the
  312. # given foreign key from the table called +table_name+.
  313. # * <tt>remove_index(table_name, column: column_names)</tt>: Removes the index
  314. # specified by +column_names+.
  315. # * <tt>remove_index(table_name, name: index_name)</tt>: Removes the index
  316. # specified by +index_name+.
  317. # * <tt>remove_reference(table_name, ref_name, options)</tt>: Removes the
  318. # reference(s) on +table_name+ specified by +ref_name+.
  319. # * <tt>remove_timestamps(table_name, options)</tt>: Removes the timestamp
  320. # columns (+created_at+ and +updated_at+) from the table definition.
  321. #
  322. # == Irreversible transformations
  323. #
  324. # Some transformations are destructive in a manner that cannot be reversed.
  325. # Migrations of that kind should raise an <tt>ActiveRecord::IrreversibleMigration</tt>
  326. # exception in their +down+ method.
  327. #
  328. # == Running migrations from within Rails
  329. #
  330. # The Rails package has several tools to help create and apply migrations.
  331. #
  332. # To generate a new migration, you can use
  333. # bin/rails generate migration MyNewMigration
  334. #
  335. # where MyNewMigration is the name of your migration. The generator will
  336. # create an empty migration file <tt>timestamp_my_new_migration.rb</tt>
  337. # in the <tt>db/migrate/</tt> directory where <tt>timestamp</tt> is the
  338. # UTC formatted date and time that the migration was generated.
  339. #
  340. # There is a special syntactic shortcut to generate migrations that add fields to a table.
  341. #
  342. # bin/rails generate migration add_fieldname_to_tablename fieldname:string
  343. #
  344. # This will generate the file <tt>timestamp_add_fieldname_to_tablename.rb</tt>, which will look like this:
  345. # class AddFieldnameToTablename < ActiveRecord::Migration[6.0]
  346. # def change
  347. # add_column :tablenames, :fieldname, :string
  348. # end
  349. # end
  350. #
  351. # To run migrations against the currently configured database, use
  352. # <tt>bin/rails db:migrate</tt>. This will update the database by running all of the
  353. # pending migrations, creating the <tt>schema_migrations</tt> table
  354. # (see "About the schema_migrations table" section below) if missing. It will also
  355. # invoke the db:schema:dump command, which will update your db/schema.rb file
  356. # to match the structure of your database.
  357. #
  358. # To roll the database back to a previous migration version, use
  359. # <tt>bin/rails db:rollback VERSION=X</tt> where <tt>X</tt> is the version to which
  360. # you wish to downgrade. Alternatively, you can also use the STEP option if you
  361. # wish to rollback last few migrations. <tt>bin/rails db:rollback STEP=2</tt> will rollback
  362. # the latest two migrations.
  363. #
  364. # If any of the migrations throw an <tt>ActiveRecord::IrreversibleMigration</tt> exception,
  365. # that step will fail and you'll have some manual work to do.
  366. #
  367. # == More examples
  368. #
  369. # Not all migrations change the schema. Some just fix the data:
  370. #
  371. # class RemoveEmptyTags < ActiveRecord::Migration[6.0]
  372. # def up
  373. # Tag.all.each { |tag| tag.destroy if tag.pages.empty? }
  374. # end
  375. #
  376. # def down
  377. # # not much we can do to restore deleted data
  378. # raise ActiveRecord::IrreversibleMigration, "Can't recover the deleted tags"
  379. # end
  380. # end
  381. #
  382. # Others remove columns when they migrate up instead of down:
  383. #
  384. # class RemoveUnnecessaryItemAttributes < ActiveRecord::Migration[6.0]
  385. # def up
  386. # remove_column :items, :incomplete_items_count
  387. # remove_column :items, :completed_items_count
  388. # end
  389. #
  390. # def down
  391. # add_column :items, :incomplete_items_count
  392. # add_column :items, :completed_items_count
  393. # end
  394. # end
  395. #
  396. # And sometimes you need to do something in SQL not abstracted directly by migrations:
  397. #
  398. # class MakeJoinUnique < ActiveRecord::Migration[6.0]
  399. # def up
  400. # execute "ALTER TABLE `pages_linked_pages` ADD UNIQUE `page_id_linked_page_id` (`page_id`,`linked_page_id`)"
  401. # end
  402. #
  403. # def down
  404. # execute "ALTER TABLE `pages_linked_pages` DROP INDEX `page_id_linked_page_id`"
  405. # end
  406. # end
  407. #
  408. # == Using a model after changing its table
  409. #
  410. # Sometimes you'll want to add a column in a migration and populate it
  411. # immediately after. In that case, you'll need to make a call to
  412. # <tt>Base#reset_column_information</tt> in order to ensure that the model has the
  413. # latest column data from after the new column was added. Example:
  414. #
  415. # class AddPeopleSalary < ActiveRecord::Migration[6.0]
  416. # def up
  417. # add_column :people, :salary, :integer
  418. # Person.reset_column_information
  419. # Person.all.each do |p|
  420. # p.update_attribute :salary, SalaryCalculator.compute(p)
  421. # end
  422. # end
  423. # end
  424. #
  425. # == Controlling verbosity
  426. #
  427. # By default, migrations will describe the actions they are taking, writing
  428. # them to the console as they happen, along with benchmarks describing how
  429. # long each step took.
  430. #
  431. # You can quiet them down by setting ActiveRecord::Migration.verbose = false.
  432. #
  433. # You can also insert your own messages and benchmarks by using the +say_with_time+
  434. # method:
  435. #
  436. # def up
  437. # ...
  438. # say_with_time "Updating salaries..." do
  439. # Person.all.each do |p|
  440. # p.update_attribute :salary, SalaryCalculator.compute(p)
  441. # end
  442. # end
  443. # ...
  444. # end
  445. #
  446. # The phrase "Updating salaries..." would then be printed, along with the
  447. # benchmark for the block when the block completes.
  448. #
  449. # == Timestamped Migrations
  450. #
  451. # By default, Rails generates migrations that look like:
  452. #
  453. # 20080717013526_your_migration_name.rb
  454. #
  455. # The prefix is a generation timestamp (in UTC).
  456. #
  457. # If you'd prefer to use numeric prefixes, you can turn timestamped migrations
  458. # off by setting:
  459. #
  460. # config.active_record.timestamped_migrations = false
  461. #
  462. # In application.rb.
  463. #
  464. # == Reversible Migrations
  465. #
  466. # Reversible migrations are migrations that know how to go +down+ for you.
  467. # You simply supply the +up+ logic, and the Migration system figures out
  468. # how to execute the down commands for you.
  469. #
  470. # To define a reversible migration, define the +change+ method in your
  471. # migration like this:
  472. #
  473. # class TenderloveMigration < ActiveRecord::Migration[6.0]
  474. # def change
  475. # create_table(:horses) do |t|
  476. # t.column :content, :text
  477. # t.column :remind_at, :datetime
  478. # end
  479. # end
  480. # end
  481. #
  482. # This migration will create the horses table for you on the way up, and
  483. # automatically figure out how to drop the table on the way down.
  484. #
  485. # Some commands cannot be reversed. If you care to define how to move up
  486. # and down in these cases, you should define the +up+ and +down+ methods
  487. # as before.
  488. #
  489. # If a command cannot be reversed, an
  490. # <tt>ActiveRecord::IrreversibleMigration</tt> exception will be raised when
  491. # the migration is moving down.
  492. #
  493. # For a list of commands that are reversible, please see
  494. # <tt>ActiveRecord::Migration::CommandRecorder</tt>.
  495. #
  496. # == Transactional Migrations
  497. #
  498. # If the database adapter supports DDL transactions, all migrations will
  499. # automatically be wrapped in a transaction. There are queries that you
  500. # can't execute inside a transaction though, and for these situations
  501. # you can turn the automatic transactions off.
  502. #
  503. # class ChangeEnum < ActiveRecord::Migration[6.0]
  504. # disable_ddl_transaction!
  505. #
  506. # def up
  507. # execute "ALTER TYPE model_size ADD VALUE 'new_value'"
  508. # end
  509. # end
  510. #
  511. # Remember that you can still open your own transactions, even if you
  512. # are in a Migration with <tt>self.disable_ddl_transaction!</tt>.
  513. 3 class Migration
  514. 3 autoload :CommandRecorder, "active_record/migration/command_recorder"
  515. 3 autoload :Compatibility, "active_record/migration/compatibility"
  516. 3 autoload :JoinTable, "active_record/migration/join_table"
  517. # This must be defined before the inherited hook, below
  518. 3 class Current < Migration #:nodoc:
  519. end
  520. 3 def self.inherited(subclass) #:nodoc:
  521. 289 super
  522. 289 if subclass.superclass == Migration
  523. 3 raise StandardError, "Directly inheriting from ActiveRecord::Migration is not supported. " \
  524. "Please specify the Rails release the migration was written for:\n" \
  525. "\n" \
  526. " class #{subclass} < ActiveRecord::Migration[4.2]"
  527. end
  528. end
  529. 3 def self.[](version)
  530. 84 Compatibility.find(version)
  531. end
  532. 3 def self.current_version
  533. 3 ActiveRecord::VERSION::STRING.to_f
  534. end
  535. 3 MigrationFilenameRegexp = /\A([0-9]+)_([_a-z0-9]*)\.?([_a-z0-9]*)?\.rb\z/ #:nodoc:
  536. # This class is used to verify that all migrations have been run before
  537. # loading a web page if <tt>config.active_record.migration_error</tt> is set to :page_load
  538. 3 class CheckPending
  539. 3 def initialize(app, file_watcher: ActiveSupport::FileUpdateChecker)
  540. 8 @app = app
  541. 8 @needs_check = true
  542. 8 @mutex = Mutex.new
  543. 8 @file_watcher = file_watcher
  544. end
  545. 3 def call(env)
  546. 11 @mutex.synchronize do
  547. 11 @watcher ||= build_watcher do
  548. 9 @needs_check = true
  549. 9 ActiveRecord::Migration.check_pending!(connection)
  550. 6 @needs_check = false
  551. end
  552. 11 if @needs_check
  553. 8 @watcher.execute
  554. else
  555. 3 @watcher.execute_if_updated
  556. end
  557. end
  558. 8 @app.call(env)
  559. end
  560. 3 private
  561. 3 def build_watcher(&block)
  562. 8 paths = Array(connection.migration_context.migrations_paths)
  563. 8 @file_watcher.new([], paths.index_with(["rb"]), &block)
  564. end
  565. 3 def connection
  566. 17 ActiveRecord::Base.connection
  567. end
  568. end
  569. 3 class << self
  570. 3 attr_accessor :delegate #:nodoc:
  571. 3 attr_accessor :disable_ddl_transaction #:nodoc:
  572. 3 def nearest_delegate #:nodoc:
  573. 186 delegate || superclass.nearest_delegate
  574. end
  575. # Raises <tt>ActiveRecord::PendingMigrationError</tt> error if any migrations are pending.
  576. 3 def check_pending!(connection = Base.connection)
  577. 9 raise ActiveRecord::PendingMigrationError if connection.migration_context.needs_migration?
  578. end
  579. 3 def load_schema_if_pending!
  580. current_db_config = Base.connection_db_config
  581. all_configs = ActiveRecord::Base.configurations.configs_for(env_name: Rails.env)
  582. needs_update = !all_configs.all? do |db_config|
  583. Tasks::DatabaseTasks.schema_up_to_date?(db_config, ActiveRecord::Base.schema_format)
  584. end
  585. if needs_update
  586. # Roundtrip to Rake to allow plugins to hook into database initialization.
  587. root = defined?(ENGINE_ROOT) ? ENGINE_ROOT : Rails.root
  588. FileUtils.cd(root) do
  589. Base.clear_all_connections!
  590. system("bin/rails db:test:prepare")
  591. end
  592. end
  593. # Establish a new connection, the old database may be gone (db:test:prepare uses purge)
  594. Base.establish_connection(current_db_config)
  595. check_pending!
  596. end
  597. 3 def maintain_test_schema! #:nodoc:
  598. if ActiveRecord::Base.maintain_test_schema
  599. suppress_messages { load_schema_if_pending! }
  600. end
  601. end
  602. 3 def method_missing(name, *args, &block) #:nodoc:
  603. 158 nearest_delegate.send(name, *args, &block)
  604. end
  605. 3 ruby2_keywords(:method_missing) if respond_to?(:ruby2_keywords, true)
  606. 3 def migrate(direction)
  607. 20 new.migrate direction
  608. end
  609. # Disable the transaction wrapping this migration.
  610. # You can still create your own transactions even after calling #disable_ddl_transaction!
  611. #
  612. # For more details read the {"Transactional Migrations" section above}[rdoc-ref:Migration].
  613. 3 def disable_ddl_transaction!
  614. 3 @disable_ddl_transaction = true
  615. end
  616. end
  617. 3 def disable_ddl_transaction #:nodoc:
  618. 394 self.class.disable_ddl_transaction
  619. end
  620. 3 cattr_accessor :verbose
  621. 3 attr_accessor :name, :version
  622. 3 def initialize(name = self.class.name, version = nil)
  623. 763 @name = name
  624. 763 @version = version
  625. 763 @connection = nil
  626. end
  627. 3 self.verbose = true
  628. # instantiate the delegate object after initialize is defined
  629. 3 self.delegate = new
  630. # Reverses the migration commands for the given block and
  631. # the given migrations.
  632. #
  633. # The following migration will remove the table 'horses'
  634. # and create the table 'apples' on the way up, and the reverse
  635. # on the way down.
  636. #
  637. # class FixTLMigration < ActiveRecord::Migration[6.0]
  638. # def change
  639. # revert do
  640. # create_table(:horses) do |t|
  641. # t.text :content
  642. # t.datetime :remind_at
  643. # end
  644. # end
  645. # create_table(:apples) do |t|
  646. # t.string :variety
  647. # end
  648. # end
  649. # end
  650. #
  651. # Or equivalently, if +TenderloveMigration+ is defined as in the
  652. # documentation for Migration:
  653. #
  654. # require_relative "20121212123456_tenderlove_migration"
  655. #
  656. # class FixupTLMigration < ActiveRecord::Migration[6.0]
  657. # def change
  658. # revert TenderloveMigration
  659. #
  660. # create_table(:apples) do |t|
  661. # t.string :variety
  662. # end
  663. # end
  664. # end
  665. #
  666. # This command can be nested.
  667. 3 def revert(*migration_classes)
  668. 243 run(*migration_classes.reverse, revert: true) unless migration_classes.empty?
  669. 243 if block_given?
  670. 225 if connection.respond_to? :revert
  671. 54 connection.revert { yield }
  672. else
  673. 198 recorder = command_recorder
  674. 198 @connection = recorder
  675. 198 suppress_messages do
  676. 396 connection.revert { yield }
  677. end
  678. 195 @connection = recorder.delegate
  679. 195 recorder.replay(self)
  680. end
  681. end
  682. end
  683. 3 def reverting?
  684. 39 connection.respond_to?(:reverting) && connection.reverting
  685. end
  686. 3 ReversibleBlockHelper = Struct.new(:reverting) do #:nodoc:
  687. 3 def up
  688. 6 yield unless reverting
  689. end
  690. 3 def down
  691. 6 yield if reverting
  692. end
  693. end
  694. # Used to specify an operation that can be run in one direction or another.
  695. # Call the methods +up+ and +down+ of the yielded object to run a block
  696. # only in one given direction.
  697. # The whole block will be called in the right order within the migration.
  698. #
  699. # In the following example, the looping on users will always be done
  700. # when the three columns 'first_name', 'last_name' and 'full_name' exist,
  701. # even when migrating down:
  702. #
  703. # class SplitNameMigration < ActiveRecord::Migration[6.0]
  704. # def change
  705. # add_column :users, :first_name, :string
  706. # add_column :users, :last_name, :string
  707. #
  708. # reversible do |dir|
  709. # User.reset_column_information
  710. # User.all.each do |u|
  711. # dir.up { u.first_name, u.last_name = u.full_name.split(' ') }
  712. # dir.down { u.full_name = "#{u.first_name} #{u.last_name}" }
  713. # u.save
  714. # end
  715. # end
  716. #
  717. # revert { add_column :users, :full_name, :string }
  718. # end
  719. # end
  720. 3 def reversible
  721. 6 helper = ReversibleBlockHelper.new(reverting?)
  722. 12 execute_block { yield helper }
  723. end
  724. # Used to specify an operation that is only run when migrating up
  725. # (for example, populating a new column with its initial values).
  726. #
  727. # In the following example, the new column +published+ will be given
  728. # the value +true+ for all existing records.
  729. #
  730. # class AddPublishedToPosts < ActiveRecord::Migration[6.0]
  731. # def change
  732. # add_column :posts, :published, :boolean, default: false
  733. # up_only do
  734. # execute "update posts set published = 'true'"
  735. # end
  736. # end
  737. # end
  738. 3 def up_only
  739. 9 execute_block { yield } unless reverting?
  740. end
  741. # Runs the given migration classes.
  742. # Last argument can specify options:
  743. # - :direction (default is :up)
  744. # - :revert (default is false)
  745. 3 def run(*migration_classes)
  746. 27 opts = migration_classes.extract_options!
  747. 27 dir = opts[:direction] || :up
  748. 27 dir = (dir == :down ? :up : :down) if opts[:revert]
  749. 27 if reverting?
  750. # If in revert and going :up, say, we want to execute :down without reverting, so
  751. 18 revert { run(*migration_classes, direction: dir, revert: true) }
  752. else
  753. 18 migration_classes.each do |migration_class|
  754. 18 migration_class.new.exec_migration(connection, dir)
  755. end
  756. end
  757. end
  758. 3 def up
  759. 66 self.class.delegate = self
  760. 66 return unless self.class.respond_to?(:up)
  761. 62 self.class.up
  762. end
  763. 3 def down
  764. 21 self.class.delegate = self
  765. 21 return unless self.class.respond_to?(:down)
  766. 18 self.class.down
  767. end
  768. # Execute this migration in the named direction
  769. 3 def migrate(direction)
  770. 715 return unless respond_to?(direction)
  771. 715 case direction
  772. 432 when :up then announce "migrating"
  773. 283 when :down then announce "reverting"
  774. end
  775. 715 time = nil
  776. 715 ActiveRecord::Base.connection_pool.with_connection do |conn|
  777. 715 time = Benchmark.measure do
  778. 715 exec_migration(conn, direction)
  779. end
  780. end
  781. 712 case direction
  782. 432 when :up then announce "migrated (%.4fs)" % time.real; write
  783. 280 when :down then announce "reverted (%.4fs)" % time.real; write
  784. end
  785. end
  786. 3 def exec_migration(conn, direction)
  787. 733 @connection = conn
  788. 733 if respond_to?(:change)
  789. 403 if direction == :down
  790. 380 revert { change }
  791. else
  792. 213 change
  793. end
  794. else
  795. 330 send(direction)
  796. end
  797. ensure
  798. 733 @connection = nil
  799. end
  800. 3 def write(text = "")
  801. 6427 puts(text) if verbose
  802. end
  803. 3 def announce(message)
  804. 1427 text = "#{version} #{name}: #{message}"
  805. 1427 length = [0, 75 - text.length].max
  806. 1427 write "== %s %s" % [text, "=" * length]
  807. end
  808. # Takes a message argument and outputs it as is.
  809. # A second boolean argument can be passed to specify whether to indent or not.
  810. 3 def say(message, subitem = false)
  811. 5194 write "#{subitem ? " ->" : "--"} #{message}"
  812. end
  813. # Outputs text along with how long it took to run its block.
  814. # If the block returns an integer it assumes it is the number of rows affected.
  815. 3 def say_with_time(message)
  816. 2600 say(message)
  817. 2600 result = nil
  818. 5200 time = Benchmark.measure { result = yield }
  819. 2587 say "%.4fs" % time.real, :subitem
  820. 2587 say("#{result} rows", :subitem) if result.is_a?(Integer)
  821. 2587 result
  822. end
  823. # Takes a block as an argument and suppresses any output generated by the block.
  824. 3 def suppress_messages
  825. 202 save, self.verbose = verbose, false
  826. 202 yield
  827. ensure
  828. 202 self.verbose = save
  829. end
  830. 3 def connection
  831. 8940 @connection || ActiveRecord::Base.connection
  832. end
  833. 3 def method_missing(method, *arguments, &block)
  834. 2600 arg_list = arguments.map(&:inspect) * ", "
  835. 2600 say_with_time "#{method}(#{arg_list})" do
  836. 2600 unless connection.respond_to? :revert
  837. 2279 unless arguments.empty? || [:execute, :enable_extension, :disable_extension].include?(method)
  838. 2201 arguments[0] = proper_table_name(arguments.first, table_name_options)
  839. 2201 if [:rename_table, :add_foreign_key].include?(method) ||
  840. (method == :remove_foreign_key && !arguments.second.is_a?(Hash))
  841. 65 arguments[1] = proper_table_name(arguments.second, table_name_options)
  842. end
  843. end
  844. end
  845. 2600 return super unless connection.respond_to?(method)
  846. 2600 connection.send(method, *arguments, &block)
  847. end
  848. end
  849. 3 ruby2_keywords(:method_missing) if respond_to?(:ruby2_keywords, true)
  850. 3 def copy(destination, sources, options = {})
  851. 51 copied = []
  852. 51 schema_migration = options[:schema_migration] || ActiveRecord::SchemaMigration
  853. 51 FileUtils.mkdir_p(destination) unless File.exist?(destination)
  854. 51 destination_migrations = ActiveRecord::MigrationContext.new(destination, schema_migration).migrations
  855. 51 last = destination_migrations.last
  856. 51 sources.each do |scope, path|
  857. 66 source_migrations = ActiveRecord::MigrationContext.new(path, schema_migration).migrations
  858. 66 source_migrations.each do |migration|
  859. 123 source = File.binread(migration.filename)
  860. 123 inserted_comment = "# This migration comes from #{scope} (originally #{migration.version})\n"
  861. 123 magic_comments = +""
  862. 123 loop do
  863. # If we have a magic comment in the original migration,
  864. # insert our comment after the first newline(end of the magic comment line)
  865. # so the magic keep working.
  866. # Note that magic comments must be at the first line(except sh-bang).
  867. source.sub!(/\A(?:#.*\b(?:en)?coding:\s*\S+|#\s*frozen_string_literal:\s*(?:true|false)).*\n/) do |magic_comment|
  868. 129 magic_comments << magic_comment; ""
  869. 252 end || break
  870. end
  871. 123 source = "#{magic_comments}#{inserted_comment}#{source}"
  872. 615 if duplicate = destination_migrations.detect { |m| m.name == migration.name }
  873. 54 if options[:on_skip] && duplicate.scope != scope.to_s
  874. 3 options[:on_skip].call(scope, migration)
  875. end
  876. 54 next
  877. end
  878. 69 migration.version = next_migration_number(last ? last.version + 1 : 0).to_i
  879. 69 new_path = File.join(destination, "#{migration.version}_#{migration.name.underscore}.#{scope}.rb")
  880. 69 old_path, migration.filename = migration.filename, new_path
  881. 69 last = migration
  882. 69 File.binwrite(migration.filename, source)
  883. 69 copied << migration
  884. 69 options[:on_copy].call(scope, migration, old_path) if options[:on_copy]
  885. 69 destination_migrations << migration
  886. end
  887. end
  888. 51 copied
  889. end
  890. # Finds the correct table name given an Active Record object.
  891. # Uses the Active Record object's own table_name, or pre/suffix from the
  892. # options passed in.
  893. 3 def proper_table_name(name, options = {})
  894. 2287 if name.respond_to? :table_name
  895. 9 name.table_name
  896. else
  897. 2278 "#{options[:table_name_prefix]}#{name}#{options[:table_name_suffix]}"
  898. end
  899. end
  900. # Determines the version number of the next migration.
  901. 3 def next_migration_number(number)
  902. 69 if ActiveRecord::Base.timestamped_migrations
  903. 48 [Time.now.utc.strftime("%Y%m%d%H%M%S"), "%.14d" % number].max
  904. else
  905. 21 SchemaMigration.normalize_migration_number(number)
  906. end
  907. end
  908. # Builds a hash for use in ActiveRecord::Migration#proper_table_name using
  909. # the Active Record object's table_name prefix and suffix
  910. 3 def table_name_options(config = ActiveRecord::Base) #:nodoc:
  911. 2272 {
  912. table_name_prefix: config.table_name_prefix,
  913. table_name_suffix: config.table_name_suffix
  914. }
  915. end
  916. 3 private
  917. 3 def execute_block
  918. 12 if connection.respond_to? :execute_block
  919. 3 super # use normal delegation to record the block
  920. else
  921. 9 yield
  922. end
  923. end
  924. 3 def command_recorder
  925. 198 CommandRecorder.new(connection)
  926. end
  927. end
  928. # MigrationProxy is used to defer loading of the actual migration classes
  929. # until they are needed
  930. 3 MigrationProxy = Struct.new(:name, :version, :filename, :scope) do
  931. 3 def initialize(name, version, filename, scope)
  932. 621 super
  933. 621 @migration = nil
  934. end
  935. 3 def basename
  936. File.basename(filename)
  937. end
  938. 3 delegate :migrate, :announce, :write, :disable_ddl_transaction, to: :migration
  939. 3 private
  940. 3 def migration
  941. 126 @migration ||= load_migration
  942. end
  943. 3 def load_migration
  944. 63 require(File.expand_path(filename))
  945. 63 name.constantize.new(name, version)
  946. end
  947. end
  948. 3 class MigrationContext #:nodoc:
  949. 3 attr_reader :migrations_paths, :schema_migration
  950. 3 def initialize(migrations_paths, schema_migration)
  951. 849 @migrations_paths = migrations_paths
  952. 849 @schema_migration = schema_migration
  953. end
  954. 3 def migrate(target_version = nil, &block)
  955. case
  956. when target_version.nil?
  957. 31 up(target_version, &block)
  958. when current_version == 0 && target_version == 0
  959. []
  960. when current_version > target_version
  961. 3 down(target_version, &block)
  962. else
  963. 22 up(target_version, &block)
  964. 56 end
  965. end
  966. 3 def rollback(steps = 1)
  967. 39 move(:down, steps)
  968. end
  969. 3 def forward(steps = 1)
  970. 18 move(:up, steps)
  971. end
  972. 3 def up(target_version = nil)
  973. 98 selected_migrations = if block_given?
  974. 39 migrations.select { |m| yield m }
  975. else
  976. 86 migrations
  977. end
  978. 98 Migrator.new(:up, selected_migrations, schema_migration, target_version).migrate
  979. end
  980. 3 def down(target_version = nil)
  981. 48 selected_migrations = if block_given?
  982. 12 migrations.select { |m| yield m }
  983. else
  984. 45 migrations
  985. end
  986. 48 Migrator.new(:down, selected_migrations, schema_migration, target_version).migrate
  987. end
  988. 3 def run(direction, target_version)
  989. 3 Migrator.new(direction, migrations, schema_migration, target_version).run
  990. end
  991. 3 def open
  992. Migrator.new(:up, migrations, schema_migration)
  993. end
  994. 3 def get_all_versions
  995. 511 if schema_migration.table_exists?
  996. 406 schema_migration.all_versions.map(&:to_i)
  997. else
  998. 105 []
  999. end
  1000. end
  1001. 3 def current_version
  1002. 442 get_all_versions.max || 0
  1003. rescue ActiveRecord::NoDatabaseError
  1004. end
  1005. 3 def needs_migration?
  1006. 21 (migrations.collect(&:version) - get_all_versions).size > 0
  1007. end
  1008. 3 def any_migrations?
  1009. 6 migrations.any?
  1010. end
  1011. 3 def migrations
  1012. 257 migrations = migration_files.map do |file|
  1013. 621 version, name, scope = parse_migration_filename(file)
  1014. 621 raise IllegalMigrationNameError.new(file) unless version
  1015. 621 version = version.to_i
  1016. 621 name = name.camelize
  1017. 621 MigrationProxy.new(name, version, file, scope)
  1018. end
  1019. 257 migrations.sort_by(&:version)
  1020. end
  1021. 3 def migrations_status
  1022. 19 db_list = schema_migration.normalized_versions
  1023. 19 file_list = migration_files.map do |file|
  1024. 60 version, name, scope = parse_migration_filename(file)
  1025. 60 raise IllegalMigrationNameError.new(file) unless version
  1026. 60 version = schema_migration.normalize_migration_number(version)
  1027. 60 status = db_list.delete(version) ? "up" : "down"
  1028. 60 [status, version, (name + scope).humanize]
  1029. end.compact
  1030. 19 db_list.map! do |version|
  1031. 15 ["up", version, "********** NO FILE **********"]
  1032. end
  1033. 94 (db_list + file_list).sort_by { |_, version, _| version }
  1034. end
  1035. 3 def current_environment
  1036. 278 ActiveRecord::ConnectionHandling::DEFAULT_ENV.call
  1037. end
  1038. 3 def protected_environment?
  1039. 12 ActiveRecord::Base.protected_environments.include?(last_stored_environment) if last_stored_environment
  1040. end
  1041. 3 def last_stored_environment
  1042. 39 return nil unless ActiveRecord::InternalMetadata.enabled?
  1043. 39 return nil if current_version == 0
  1044. 39 raise NoEnvironmentInSchemaError unless ActiveRecord::InternalMetadata.table_exists?
  1045. 36 environment = ActiveRecord::InternalMetadata[:environment]
  1046. 36 raise NoEnvironmentInSchemaError unless environment
  1047. 36 environment
  1048. end
  1049. 3 private
  1050. 3 def migration_files
  1051. 276 paths = Array(migrations_paths)
  1052. 558 Dir[*paths.flat_map { |path| "#{path}/**/[0-9]*_*.rb" }]
  1053. end
  1054. 3 def parse_migration_filename(filename)
  1055. 681 File.basename(filename).scan(Migration::MigrationFilenameRegexp).first
  1056. end
  1057. 3 def move(direction, steps)
  1058. 57 migrator = Migrator.new(direction, migrations, schema_migration)
  1059. 57 if current_version != 0 && !migrator.current_migration
  1060. raise UnknownMigrationVersionError.new(current_version)
  1061. end
  1062. 57 start_index =
  1063. 57 if current_version == 0
  1064. 3 0
  1065. else
  1066. 54 migrator.migrations.index(migrator.current_migration)
  1067. end
  1068. 57 finish = migrator.migrations[start_index + steps]
  1069. 57 version = finish ? finish.version : 0
  1070. 57 send(direction, version)
  1071. end
  1072. end
  1073. 3 class Migrator # :nodoc:
  1074. 3 class << self
  1075. 3 attr_accessor :migrations_paths
  1076. # For cases where a table doesn't exist like loading from schema cache
  1077. 3 def current_version
  1078. MigrationContext.new(migrations_paths, SchemaMigration).current_version
  1079. end
  1080. end
  1081. 3 self.migrations_paths = ["db/migrate"]
  1082. 3 def initialize(direction, migrations, schema_migration, target_version = nil)
  1083. 392 @direction = direction
  1084. 392 @target_version = target_version
  1085. 392 @migrated_versions = nil
  1086. 392 @migrations = migrations
  1087. 392 @schema_migration = schema_migration
  1088. 392 validate(@migrations)
  1089. 386 @schema_migration.create_table
  1090. 386 ActiveRecord::InternalMetadata.create_table
  1091. end
  1092. 3 def current_version
  1093. 366 migrated.max || 0
  1094. end
  1095. 3 def current_migration
  1096. 528 migrations.detect { |m| m.version == current_version }
  1097. end
  1098. 3 alias :current :current_migration
  1099. 3 def run
  1100. 25 if use_advisory_lock?
  1101. 17 with_advisory_lock { run_without_lock }
  1102. else
  1103. 16 run_without_lock
  1104. end
  1105. end
  1106. 3 def migrate
  1107. 297 if use_advisory_lock?
  1108. 205 with_advisory_lock { migrate_without_lock }
  1109. else
  1110. 194 migrate_without_lock
  1111. end
  1112. end
  1113. 3 def runnable
  1114. 290 runnable = migrations[start..finish]
  1115. 290 if up?
  1116. 587 runnable.reject { |m| ran?(m) }
  1117. else
  1118. # skip the last migration if we're headed down, but not ALL the way down
  1119. 72 runnable.pop if target
  1120. 171 runnable.find_all { |m| ran?(m) }
  1121. end
  1122. end
  1123. 3 def migrations
  1124. 1608 down? ? @migrations.reverse : @migrations.sort_by(&:version)
  1125. end
  1126. 3 def pending_migrations
  1127. 9 already_migrated = migrated
  1128. 27 migrations.reject { |m| already_migrated.include?(m.version) }
  1129. end
  1130. 3 def migrated
  1131. 1595 @migrated_versions || load_migrated
  1132. end
  1133. 3 def load_migrated
  1134. 370 @migrated_versions = Set.new(@schema_migration.all_versions.map(&:to_i))
  1135. end
  1136. 3 private
  1137. # Used for running a specific migration.
  1138. 3 def run_without_lock
  1139. 69 migration = migrations.detect { |m| m.version == @target_version }
  1140. 24 raise UnknownMigrationVersionError.new(@target_version) if migration.nil?
  1141. 15 result = execute_migration_in_transaction(migration)
  1142. 12 record_environment
  1143. 12 result
  1144. end
  1145. # Used for running multiple migrations up to or down to a certain value.
  1146. 3 def migrate_without_lock
  1147. 296 if invalid_target?
  1148. 6 raise UnknownMigrationVersionError.new(@target_version)
  1149. end
  1150. 290 result = runnable.each(&method(:execute_migration_in_transaction))
  1151. 274 record_environment
  1152. 274 result
  1153. end
  1154. # Stores the current environment in the database.
  1155. 3 def record_environment
  1156. 286 return if down?
  1157. 208 ActiveRecord::InternalMetadata[:environment] = ActiveRecord::Base.connection.migration_context.current_environment
  1158. end
  1159. 3 def ran?(migration)
  1160. 468 migrated.include?(migration.version.to_i)
  1161. end
  1162. # Return true if a valid version is not provided.
  1163. 3 def invalid_target?
  1164. 296 @target_version && @target_version != 0 && !target
  1165. end
  1166. 3 def execute_migration_in_transaction(migration)
  1167. 387 return if down? && !migrated.include?(migration.version.to_i)
  1168. 384 return if up? && migrated.include?(migration.version.to_i)
  1169. 384 Base.logger.info "Migrating to #{migration.name} (#{migration.version})" if Base.logger
  1170. 384 ddl_transaction(migration) do
  1171. 384 migration.migrate(@direction)
  1172. 365 record_version_state_after_migrating(migration.version)
  1173. end
  1174. rescue => e
  1175. 19 msg = +"An error has occurred, "
  1176. 19 msg << "this and " if use_transaction?(migration)
  1177. 19 msg << "all later migrations canceled:\n\n#{e}"
  1178. 19 raise StandardError, msg, e.backtrace
  1179. end
  1180. 3 def target
  1181. 1460 migrations.detect { |m| m.version == @target_version }
  1182. end
  1183. 3 def finish
  1184. 290 migrations.index(target) || migrations.size - 1
  1185. end
  1186. 3 def start
  1187. 290 up? ? 0 : (migrations.index(current) || 0)
  1188. end
  1189. 3 def validate(migrations)
  1190. 876 name, = migrations.group_by(&:name).find { |_, v| v.length > 1 }
  1191. 392 raise DuplicateMigrationNameError.new(name) if name
  1192. 1290 version, = migrations.group_by(&:version).find { |_, v| v.length > 1 }
  1193. 389 raise DuplicateMigrationVersionError.new(version) if version
  1194. end
  1195. 3 def record_version_state_after_migrating(version)
  1196. 365 if down?
  1197. 87 migrated.delete(version)
  1198. 87 @schema_migration.delete_by(version: version.to_s)
  1199. else
  1200. 278 migrated << version
  1201. 278 @schema_migration.create!(version: version.to_s)
  1202. end
  1203. end
  1204. 3 def up?
  1205. 964 @direction == :up
  1206. end
  1207. 3 def down?
  1208. 2646 @direction == :down
  1209. end
  1210. # Wrap the migration in a transaction only if supported by the adapter.
  1211. 3 def ddl_transaction(migration)
  1212. 384 if use_transaction?(migration)
  1213. 762 Base.transaction { yield }
  1214. else
  1215. 3 yield
  1216. end
  1217. end
  1218. 3 def use_transaction?(migration)
  1219. 403 !migration.disable_ddl_transaction && Base.connection.supports_ddl_transactions?
  1220. end
  1221. 3 def use_advisory_lock?
  1222. 322 Base.connection.advisory_locks_enabled?
  1223. end
  1224. 3 def with_advisory_lock
  1225. 114 lock_id = generate_migrator_advisory_lock_id
  1226. 114 with_advisory_lock_connection do |connection|
  1227. 114 got_lock = connection.get_advisory_lock(lock_id)
  1228. 114 raise ConcurrentMigrationError unless got_lock
  1229. 112 load_migrated # reload schema_migrations to be sure it wasn't changed by another process before we got the lock
  1230. 112 yield
  1231. ensure
  1232. 114 if got_lock && !connection.release_advisory_lock(lock_id)
  1233. 1 raise ConcurrentMigrationError.new(
  1234. ConcurrentMigrationError::RELEASE_LOCK_FAILED_MESSAGE
  1235. )
  1236. end
  1237. end
  1238. end
  1239. 3 def with_advisory_lock_connection
  1240. 113 pool = ActiveRecord::ConnectionAdapters::ConnectionHandler.new.establish_connection(
  1241. ActiveRecord::Base.connection_db_config
  1242. )
  1243. 226 pool.with_connection { |connection| yield(connection) }
  1244. end
  1245. 3 MIGRATOR_SALT = 2053462845
  1246. 3 def generate_migrator_advisory_lock_id
  1247. 119 db_name_hash = Zlib.crc32(Base.connection.current_database)
  1248. 119 MIGRATOR_SALT * db_name_hash
  1249. end
  1250. end
  1251. end

lib/active_record/migration/command_recorder.rb

99.13% lines covered

115 relevant lines. 114 lines covered and 1 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 module ActiveRecord
  3. 3 class Migration
  4. # <tt>ActiveRecord::Migration::CommandRecorder</tt> records commands done during
  5. # a migration and knows how to reverse those commands. The CommandRecorder
  6. # knows how to invert the following commands:
  7. #
  8. # * add_column
  9. # * add_foreign_key
  10. # * add_check_constraint
  11. # * add_index
  12. # * add_reference
  13. # * add_timestamps
  14. # * change_column
  15. # * change_column_default (must supply a :from and :to option)
  16. # * change_column_null
  17. # * change_column_comment (must supply a :from and :to option)
  18. # * change_table_comment (must supply a :from and :to option)
  19. # * create_join_table
  20. # * create_table
  21. # * disable_extension
  22. # * drop_join_table
  23. # * drop_table (must supply a block)
  24. # * enable_extension
  25. # * remove_column (must supply a type)
  26. # * remove_columns (must specify at least one column name or more)
  27. # * remove_foreign_key (must supply a second table)
  28. # * remove_check_constraint
  29. # * remove_index
  30. # * remove_reference
  31. # * remove_timestamps
  32. # * rename_column
  33. # * rename_index
  34. # * rename_table
  35. 3 class CommandRecorder
  36. 3 ReversibleAndIrreversibleMethods = [
  37. :create_table, :create_join_table, :rename_table, :add_column, :remove_column,
  38. :rename_index, :rename_column, :add_index, :remove_index, :add_timestamps, :remove_timestamps,
  39. :change_column_default, :add_reference, :remove_reference, :transaction,
  40. :drop_join_table, :drop_table, :execute_block, :enable_extension, :disable_extension,
  41. :change_column, :execute, :remove_columns, :change_column_null,
  42. :add_foreign_key, :remove_foreign_key,
  43. :change_column_comment, :change_table_comment,
  44. :add_check_constraint, :remove_check_constraint
  45. ]
  46. 3 include JoinTable
  47. 3 attr_accessor :commands, :delegate, :reverting
  48. 3 def initialize(delegate = nil)
  49. 419 @commands = []
  50. 419 @delegate = delegate
  51. 419 @reverting = false
  52. end
  53. # While executing the given block, the recorded will be in reverting mode.
  54. # All commands recorded will end up being recorded reverted
  55. # and in reverse order.
  56. # For example:
  57. #
  58. # recorder.revert{ recorder.record(:rename_table, [:old, :new]) }
  59. # # same effect as recorder.record(:rename_table, [:new, :old])
  60. 3 def revert
  61. 267 @reverting = !@reverting
  62. 267 previous = @commands
  63. 267 @commands = []
  64. 267 yield
  65. ensure
  66. 267 @commands = previous.concat(@commands.reverse)
  67. 267 @reverting = !@reverting
  68. end
  69. # Record +command+. +command+ should be a method name and arguments.
  70. # For example:
  71. #
  72. # recorder.record(:method_name, [:arg1, :arg2])
  73. 3 def record(*command, &block)
  74. 436 if @reverting
  75. 363 @commands << inverse_of(*command, &block)
  76. else
  77. 73 @commands << (command << block)
  78. end
  79. end
  80. # Returns the inverse of the given command. For example:
  81. #
  82. # recorder.inverse_of(:rename_table, [:old, :new])
  83. # # => [:rename_table, [:new, :old]]
  84. #
  85. # If the inverse of a command requires several commands, returns array of commands.
  86. #
  87. # recorder.inverse_of(:remove_columns, [:some_table, :foo, :bar, type: :string])
  88. # # => [[:add_column, :some_table, :foo, :string], [:add_column, :some_table, :bar, :string]]
  89. #
  90. # This method will raise an +IrreversibleMigration+ exception if it cannot
  91. # invert the +command+.
  92. 3 def inverse_of(command, args, &block)
  93. 525 method = :"invert_#{command}"
  94. 525 raise IrreversibleMigration, <<~MSG unless respond_to?(method, true)
  95. This migration uses #{command}, which is not automatically reversible.
  96. To make the migration reversible you can either:
  97. 1. Define #up and #down methods in place of the #change method.
  98. 2. Use the #reversible method to define reversible behavior.
  99. MSG
  100. 513 send(method, args, &block)
  101. end
  102. 3 ReversibleAndIrreversibleMethods.each do |method|
  103. 90 class_eval <<-EOV, __FILE__, __LINE__ + 1
  104. def #{method}(*args, &block) # def create_table(*args, &block)
  105. record(:"#{method}", args, &block) # record(:create_table, args, &block)
  106. end # end
  107. EOV
  108. 90 ruby2_keywords(method) if respond_to?(:ruby2_keywords, true)
  109. end
  110. 3 alias :add_belongs_to :add_reference
  111. 3 alias :remove_belongs_to :remove_reference
  112. 3 def change_table(table_name, **options) # :nodoc:
  113. 22 yield delegate.update_table_definition(table_name, self)
  114. end
  115. 3 def replay(migration)
  116. 198 commands.each do |cmd, args, block|
  117. 321 migration.send(cmd, *args, &block)
  118. end
  119. end
  120. 3 private
  121. 3 module StraightReversions # :nodoc:
  122. 3 private
  123. {
  124. execute_block: :execute_block,
  125. create_table: :drop_table,
  126. create_join_table: :drop_join_table,
  127. add_column: :remove_column,
  128. add_index: :remove_index,
  129. add_timestamps: :remove_timestamps,
  130. add_reference: :remove_reference,
  131. add_foreign_key: :remove_foreign_key,
  132. add_check_constraint: :remove_check_constraint,
  133. enable_extension: :disable_extension
  134. 3 }.each do |cmd, inv|
  135. 30 [[inv, cmd], [cmd, inv]].uniq.each do |method, inverse|
  136. 57 class_eval <<-EOV, __FILE__, __LINE__ + 1
  137. def invert_#{method}(args, &block) # def invert_create_table(args, &block)
  138. [:#{inverse}, args, block] # [:drop_table, args, block]
  139. end # end
  140. EOV
  141. end
  142. end
  143. end
  144. 3 include StraightReversions
  145. 3 def invert_transaction(args)
  146. 6 sub_recorder = CommandRecorder.new(delegate)
  147. 12 sub_recorder.revert { yield }
  148. 3 invertions_proc = proc {
  149. 3 sub_recorder.replay(self)
  150. }
  151. 3 [:transaction, args, invertions_proc]
  152. end
  153. 3 def invert_drop_table(args, &block)
  154. 6 if args.size == 1 && block == nil
  155. 3 raise ActiveRecord::IrreversibleMigration, "To avoid mistakes, drop_table is only reversible if given options or a block (can be empty)."
  156. end
  157. 3 super
  158. end
  159. 3 def invert_rename_table(args)
  160. 3 [:rename_table, args.reverse]
  161. end
  162. 3 def invert_remove_column(args)
  163. 9 raise ActiveRecord::IrreversibleMigration, "remove_column is only reversible if given a type." if args.size <= 2
  164. 3 super
  165. end
  166. 3 def invert_remove_columns(args)
  167. 6 unless args[-1].is_a?(Hash) && args[-1].has_key?(:type)
  168. 3 raise ActiveRecord::IrreversibleMigration, "remove_columns is only reversible if given a type."
  169. end
  170. 3 [:add_columns, args]
  171. end
  172. 3 def invert_rename_index(args)
  173. 3 table_name, old_name, new_name = args
  174. 3 [:rename_index, [table_name, new_name, old_name]]
  175. end
  176. 3 def invert_rename_column(args)
  177. 6 table_name, old_name, new_name = args
  178. 6 [:rename_column, [table_name, new_name, old_name]]
  179. end
  180. 3 def invert_remove_index(args)
  181. 24 options = args.extract_options!
  182. 24 table, columns = args
  183. 24 columns ||= options.delete(:column)
  184. 24 unless columns
  185. 3 raise ActiveRecord::IrreversibleMigration, "remove_index is only reversible if given a :column option."
  186. end
  187. 21 options.delete(:if_exists)
  188. 21 args = [table, columns]
  189. 21 args << options unless options.empty?
  190. 21 [:add_index, args]
  191. end
  192. 3 alias :invert_add_belongs_to :invert_add_reference
  193. 3 alias :invert_remove_belongs_to :invert_remove_reference
  194. 3 def invert_change_column_default(args)
  195. 12 table, column, options = args
  196. 12 unless options.is_a?(Hash) && options.has_key?(:from) && options.has_key?(:to)
  197. 3 raise ActiveRecord::IrreversibleMigration, "change_column_default is only reversible if given a :from and :to option."
  198. end
  199. 9 [:change_column_default, [table, column, from: options[:to], to: options[:from]]]
  200. end
  201. 3 def invert_change_column_null(args)
  202. 6 args[2] = !args[2]
  203. 6 [:change_column_null, args]
  204. end
  205. 3 def invert_remove_foreign_key(args)
  206. 39 options = args.extract_options!
  207. 39 from_table, to_table = args
  208. 39 to_table ||= options.delete(:to_table)
  209. 39 raise ActiveRecord::IrreversibleMigration, "remove_foreign_key is only reversible if given a second table" if to_table.nil?
  210. 30 reversed_args = [from_table, to_table]
  211. 30 reversed_args << options unless options.empty?
  212. 30 [:add_foreign_key, reversed_args]
  213. end
  214. 3 def invert_change_column_comment(args)
  215. 5 table, column, options = args
  216. 5 unless options.is_a?(Hash) && options.has_key?(:from) && options.has_key?(:to)
  217. 2 raise ActiveRecord::IrreversibleMigration, "change_column_comment is only reversible if given a :from and :to option."
  218. end
  219. 3 [:change_column_comment, [table, column, from: options[:to], to: options[:from]]]
  220. end
  221. 3 def invert_change_table_comment(args)
  222. 3 table, options = args
  223. 3 unless options.is_a?(Hash) && options.has_key?(:from) && options.has_key?(:to)
  224. raise ActiveRecord::IrreversibleMigration, "change_table_comment is only reversible if given a :from and :to option."
  225. end
  226. 3 [:change_table_comment, [table, from: options[:to], to: options[:from]]]
  227. end
  228. 3 def invert_remove_check_constraint(args)
  229. 6 raise ActiveRecord::IrreversibleMigration, "remove_check_constraint is only reversible if given an expression." if args.size < 2
  230. 3 super
  231. end
  232. 3 def respond_to_missing?(method, _)
  233. 15 super || delegate.respond_to?(method)
  234. end
  235. # Forwards any missing method call to the \target.
  236. 3 def method_missing(method, *args, &block)
  237. 102 if delegate.respond_to?(method)
  238. 99 delegate.public_send(method, *args, &block)
  239. else
  240. 3 super
  241. end
  242. end
  243. 3 ruby2_keywords(:method_missing) if respond_to?(:ruby2_keywords, true)
  244. end
  245. end
  246. end

lib/active_record/migration/compatibility.rb

87.5% lines covered

136 relevant lines. 119 lines covered and 17 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 module ActiveRecord
  3. 3 class Migration
  4. 3 module Compatibility # :nodoc: all
  5. 3 def self.find(version)
  6. 84 version = version.to_s
  7. 84 name = "V#{version.tr('.', '_')}"
  8. 84 unless const_defined?(name)
  9. 21 versions = constants.grep(/\AV[0-9_]+\z/).map { |s| s.to_s.delete("V").tr("_", ".").inspect }
  10. 3 raise ArgumentError, "Unknown migration version #{version.inspect}; expected one of #{versions.sort.join(', ')}"
  11. end
  12. 81 const_get(name)
  13. end
  14. 3 V6_1 = Current
  15. 3 class V6_0 < V6_1
  16. end
  17. 3 class V5_2 < V6_0
  18. 3 module TableDefinition
  19. 3 def timestamps(**options)
  20. 14 options[:precision] ||= nil
  21. 14 super
  22. end
  23. end
  24. 3 module CommandRecorder
  25. 3 def invert_transaction(args, &block)
  26. 3 [:transaction, args, block]
  27. end
  28. 3 def invert_change_column_comment(args)
  29. 1 [:change_column_comment, args]
  30. end
  31. 3 def invert_change_table_comment(args)
  32. 1 [:change_table_comment, args]
  33. end
  34. end
  35. 3 def create_table(table_name, **options)
  36. 75 if block_given?
  37. 114 super { |t| yield compatible_table_definition(t) }
  38. else
  39. 2 super
  40. end
  41. end
  42. 3 def change_table(table_name, **options)
  43. 20 if block_given?
  44. 40 super { |t| yield compatible_table_definition(t) }
  45. else
  46. super
  47. end
  48. end
  49. 3 def create_join_table(table_1, table_2, **options)
  50. 24 if block_given?
  51. 36 super { |t| yield compatible_table_definition(t) }
  52. else
  53. super
  54. end
  55. end
  56. 3 def add_timestamps(table_name, **options)
  57. 6 options[:precision] ||= nil
  58. 6 super
  59. end
  60. 3 private
  61. 3 def compatible_table_definition(t)
  62. 73 class << t
  63. 73 prepend TableDefinition
  64. end
  65. 73 t
  66. end
  67. 3 def command_recorder
  68. 49 recorder = super
  69. 49 class << recorder
  70. 49 prepend CommandRecorder
  71. end
  72. 49 recorder
  73. end
  74. end
  75. 3 class V5_1 < V5_2
  76. 3 def change_column(table_name, column_name, type, **options)
  77. 1 if connection.adapter_name == "PostgreSQL"
  78. 1 super(table_name, column_name, type, **options.except(:default, :null, :comment))
  79. 1 connection.change_column_default(table_name, column_name, options[:default]) if options.key?(:default)
  80. 1 connection.change_column_null(table_name, column_name, options[:null], options[:default]) if options.key?(:null)
  81. 1 connection.change_column_comment(table_name, column_name, options[:comment]) if options.key?(:comment)
  82. else
  83. super
  84. end
  85. end
  86. 3 def create_table(table_name, **options)
  87. 72 if connection.adapter_name == "Mysql2"
  88. super(table_name, options: "ENGINE=InnoDB", **options)
  89. else
  90. 72 super
  91. end
  92. end
  93. end
  94. 3 class V5_0 < V5_1
  95. 3 module TableDefinition
  96. 3 def primary_key(name, type = :primary_key, **options)
  97. 18 type = :integer if type == :primary_key
  98. 18 super
  99. end
  100. 3 def references(*args, **options)
  101. 12 super(*args, type: :integer, **options)
  102. end
  103. 3 alias :belongs_to :references
  104. end
  105. 3 def create_table(table_name, **options)
  106. 72 if connection.adapter_name == "PostgreSQL"
  107. 28 if options[:id] == :uuid && !options.key?(:default)
  108. 1 options[:default] = "uuid_generate_v4()"
  109. end
  110. end
  111. 72 unless connection.adapter_name == "Mysql2" && options[:id] == :bigint
  112. 72 if [:integer, :bigint].include?(options[:id]) && !options.key?(:default)
  113. 16 options[:default] = nil
  114. end
  115. end
  116. # Since 5.1 PostgreSQL adapter uses bigserial type for primary
  117. # keys by default and MySQL uses bigint. This compat layer makes old migrations utilize
  118. # serial/int type instead -- the way it used to work before 5.1.
  119. 72 unless options.key?(:id)
  120. 18 options[:id] = :integer
  121. end
  122. 72 super
  123. end
  124. 3 def create_join_table(table_1, table_2, column_options: {}, **options)
  125. 24 column_options.reverse_merge!(type: :integer)
  126. 24 super
  127. end
  128. 3 def add_column(table_name, column_name, type, **options)
  129. 12 if type == :primary_key
  130. 12 type = :integer
  131. 12 options[:primary_key] = true
  132. end
  133. 12 super
  134. end
  135. 3 def add_reference(table_name, ref_name, **options)
  136. super(table_name, ref_name, type: :integer, **options)
  137. end
  138. 3 alias :add_belongs_to :add_reference
  139. 3 private
  140. 3 def compatible_table_definition(t)
  141. 66 class << t
  142. 66 prepend TableDefinition
  143. end
  144. 66 super
  145. end
  146. end
  147. 3 class V4_2 < V5_0
  148. 3 module TableDefinition
  149. 3 def references(*, **options)
  150. 9 options[:index] ||= false
  151. 9 super
  152. end
  153. 3 alias :belongs_to :references
  154. 3 def timestamps(**options)
  155. 7 options[:null] = true if options[:null].nil?
  156. 7 super
  157. end
  158. end
  159. 3 def add_reference(table_name, ref_name, **options)
  160. options[:index] ||= false
  161. super
  162. end
  163. 3 alias :add_belongs_to :add_reference
  164. 3 def add_timestamps(table_name, **options)
  165. 3 options[:null] = true if options[:null].nil?
  166. 3 super
  167. end
  168. 3 def index_exists?(table_name, column_name, **options)
  169. column_names = Array(column_name).map(&:to_s)
  170. options[:name] =
  171. if options[:name].present?
  172. options[:name].to_s
  173. else
  174. connection.index_name(table_name, column: column_names)
  175. end
  176. super
  177. end
  178. 3 def remove_index(table_name, column_name = nil, **options)
  179. 6 options[:name] = index_name_for_remove(table_name, column_name, options)
  180. 3 super
  181. end
  182. 3 private
  183. 3 def compatible_table_definition(t)
  184. 38 class << t
  185. 38 prepend TableDefinition
  186. end
  187. 38 super
  188. end
  189. 3 def index_name_for_remove(table_name, column_name, options)
  190. 6 index_name = connection.index_name(table_name, column_name || options)
  191. 6 unless connection.index_name_exists?(table_name, index_name)
  192. 3 if options.key?(:name)
  193. options_without_column = options.except(:column)
  194. index_name_without_column = connection.index_name(table_name, options_without_column)
  195. if connection.index_name_exists?(table_name, index_name_without_column)
  196. return index_name_without_column
  197. end
  198. end
  199. 3 raise ArgumentError, "Index name '#{index_name}' on table '#{table_name}' does not exist"
  200. end
  201. 3 index_name
  202. end
  203. end
  204. end
  205. end
  206. end

lib/active_record/migration/join_table.rb

100.0% lines covered

8 relevant lines. 8 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 module ActiveRecord
  3. 3 class Migration
  4. 3 module JoinTable #:nodoc:
  5. 3 private
  6. 3 def find_join_table_name(table_1, table_2, options = {})
  7. 100 options.delete(:table_name) || join_table_name(table_1, table_2)
  8. end
  9. 3 def join_table_name(table_1, table_2)
  10. 82 ModelSchema.derive_join_table_name(table_1, table_2).to_sym
  11. end
  12. end
  13. end
  14. end

lib/active_record/model_schema.rb

98.92% lines covered

185 relevant lines. 183 lines covered and 2 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 require "monitor"
  3. 3 module ActiveRecord
  4. 3 module ModelSchema
  5. 3 extend ActiveSupport::Concern
  6. ##
  7. # :singleton-method: primary_key_prefix_type
  8. # :call-seq: primary_key_prefix_type
  9. #
  10. # The prefix type that will be prepended to every primary key column name.
  11. # The options are +:table_name+ and +:table_name_with_underscore+. If the first is specified,
  12. # the Product class will look for "productid" instead of "id" as the primary column. If the
  13. # latter is specified, the Product class will look for "product_id" instead of "id". Remember
  14. # that this is a global setting for all Active Records.
  15. ##
  16. # :singleton-method: primary_key_prefix_type=
  17. # :call-seq: primary_key_prefix_type=(prefix_type)
  18. #
  19. # Sets the prefix type that will be prepended to every primary key column name.
  20. # The options are +:table_name+ and +:table_name_with_underscore+. If the first is specified,
  21. # the Product class will look for "productid" instead of "id" as the primary column. If the
  22. # latter is specified, the Product class will look for "product_id" instead of "id". Remember
  23. # that this is a global setting for all Active Records.
  24. ##
  25. # :singleton-method: table_name_prefix
  26. # :call-seq: table_name_prefix
  27. #
  28. # The prefix string to prepend to every table name.
  29. ##
  30. # :singleton-method: table_name_prefix=
  31. # :call-seq: table_name_prefix=(prefix)
  32. #
  33. # Sets the prefix string to prepend to every table name. So if set to "basecamp_", all table
  34. # names will be named like "basecamp_projects", "basecamp_people", etc. This is a convenient
  35. # way of creating a namespace for tables in a shared database. By default, the prefix is the
  36. # empty string.
  37. #
  38. # If you are organising your models within modules you can add a prefix to the models within
  39. # a namespace by defining a singleton method in the parent module called table_name_prefix which
  40. # returns your chosen prefix.
  41. ##
  42. # :singleton-method: table_name_suffix
  43. # :call-seq: table_name_suffix
  44. #
  45. # The suffix string to append to every table name.
  46. ##
  47. # :singleton-method: table_name_suffix=
  48. # :call-seq: table_name_suffix=(suffix)
  49. #
  50. # Works like +table_name_prefix=+, but appends instead of prepends (set to "_basecamp" gives "projects_basecamp",
  51. # "people_basecamp"). By default, the suffix is the empty string.
  52. #
  53. # If you are organising your models within modules, you can add a suffix to the models within
  54. # a namespace by defining a singleton method in the parent module called table_name_suffix which
  55. # returns your chosen suffix.
  56. ##
  57. # :singleton-method: schema_migrations_table_name
  58. # :call-seq: schema_migrations_table_name
  59. #
  60. # The name of the schema migrations table. By default, the value is <tt>"schema_migrations"</tt>.
  61. ##
  62. # :singleton-method: schema_migrations_table_name=
  63. # :call-seq: schema_migrations_table_name=(table_name)
  64. #
  65. # Sets the name of the schema migrations table.
  66. ##
  67. # :singleton-method: internal_metadata_table_name
  68. # :call-seq: internal_metadata_table_name
  69. #
  70. # The name of the internal metadata table. By default, the value is <tt>"ar_internal_metadata"</tt>.
  71. ##
  72. # :singleton-method: internal_metadata_table_name=
  73. # :call-seq: internal_metadata_table_name=(table_name)
  74. #
  75. # Sets the name of the internal metadata table.
  76. ##
  77. # :singleton-method: pluralize_table_names
  78. # :call-seq: pluralize_table_names
  79. #
  80. # Indicates whether table names should be the pluralized versions of the corresponding class names.
  81. # If true, the default table name for a Product class will be "products". If false, it would just be "product".
  82. # See table_name for the full rules on table/class naming. This is true, by default.
  83. ##
  84. # :singleton-method: pluralize_table_names=
  85. # :call-seq: pluralize_table_names=(value)
  86. #
  87. # Set whether table names should be the pluralized versions of the corresponding class names.
  88. # If true, the default table name for a Product class will be "products". If false, it would just be "product".
  89. # See table_name for the full rules on table/class naming. This is true, by default.
  90. ##
  91. # :singleton-method: implicit_order_column
  92. # :call-seq: implicit_order_column
  93. #
  94. # The name of the column records are ordered by if no explicit order clause
  95. # is used during an ordered finder call. If not set the primary key is used.
  96. ##
  97. # :singleton-method: implicit_order_column=
  98. # :call-seq: implicit_order_column=(column_name)
  99. #
  100. # Sets the column to sort records by when no explicit order clause is used
  101. # during an ordered finder call. Useful when the primary key is not an
  102. # auto-incrementing integer, for example when it's a UUID. Records are subsorted
  103. # by the primary key if it exists to ensure deterministic results.
  104. ##
  105. # :singleton-method: immutable_strings_by_default=
  106. # :call-seq: immutable_strings_by_default=(bool)
  107. #
  108. # Determines whether columns should infer their type as `:string` or
  109. # `:immutable_string`. This setting does not affect the behavior of
  110. # `attribute :foo, :string`. Defaults to false.
  111. 3 included do
  112. 3 mattr_accessor :primary_key_prefix_type, instance_writer: false
  113. 3 class_attribute :table_name_prefix, instance_writer: false, default: ""
  114. 3 class_attribute :table_name_suffix, instance_writer: false, default: ""
  115. 3 class_attribute :schema_migrations_table_name, instance_accessor: false, default: "schema_migrations"
  116. 3 class_attribute :internal_metadata_table_name, instance_accessor: false, default: "ar_internal_metadata"
  117. 3 class_attribute :pluralize_table_names, instance_writer: false, default: true
  118. 3 class_attribute :implicit_order_column, instance_accessor: false
  119. 3 class_attribute :immutable_strings_by_default, instance_accessor: false
  120. 3 self.protected_environments = ["production"]
  121. 3 self.inheritance_column = "type"
  122. 3 self.ignored_columns = [].freeze
  123. 3 delegate :type_for_attribute, :column_for_attribute, to: :class
  124. 3 initialize_load_schema_monitor
  125. end
  126. # Derives the join table name for +first_table+ and +second_table+. The
  127. # table names appear in alphabetical order. A common prefix is removed
  128. # (useful for namespaced models like Music::Artist and Music::Record):
  129. #
  130. # artists, records => artists_records
  131. # records, artists => artists_records
  132. # music_artists, music_records => music_artists_records
  133. 3 def self.derive_join_table_name(first_table, second_table) # :nodoc:
  134. 100 [first_table.to_s, second_table.to_s].sort.join("\0").gsub(/^(.*_)(.+)\0\1(.+)/, '\1\2_\3').tr("\0", "_")
  135. end
  136. 3 module ClassMethods
  137. # Guesses the table name (in forced lower-case) based on the name of the class in the
  138. # inheritance hierarchy descending directly from ActiveRecord::Base. So if the hierarchy
  139. # looks like: Reply < Message < ActiveRecord::Base, then Message is used
  140. # to guess the table name even when called on Reply. The rules used to do the guess
  141. # are handled by the Inflector class in Active Support, which knows almost all common
  142. # English inflections. You can add new inflections in config/initializers/inflections.rb.
  143. #
  144. # Nested classes are given table names prefixed by the singular form of
  145. # the parent's table name. Enclosing modules are not considered.
  146. #
  147. # ==== Examples
  148. #
  149. # class Invoice < ActiveRecord::Base
  150. # end
  151. #
  152. # file class table_name
  153. # invoice.rb Invoice invoices
  154. #
  155. # class Invoice < ActiveRecord::Base
  156. # class Lineitem < ActiveRecord::Base
  157. # end
  158. # end
  159. #
  160. # file class table_name
  161. # invoice.rb Invoice::Lineitem invoice_lineitems
  162. #
  163. # module Invoice
  164. # class Lineitem < ActiveRecord::Base
  165. # end
  166. # end
  167. #
  168. # file class table_name
  169. # invoice/lineitem.rb Invoice::Lineitem lineitems
  170. #
  171. # Additionally, the class-level +table_name_prefix+ is prepended and the
  172. # +table_name_suffix+ is appended. So if you have "myapp_" as a prefix,
  173. # the table name guess for an Invoice class becomes "myapp_invoices".
  174. # Invoice::Lineitem becomes "myapp_invoice_lineitems".
  175. #
  176. # You can also set your own table name explicitly:
  177. #
  178. # class Mouse < ActiveRecord::Base
  179. # self.table_name = "mice"
  180. # end
  181. 3 def table_name
  182. 41780 reset_table_name unless defined?(@table_name)
  183. 41780 @table_name
  184. end
  185. # Sets the table name explicitly. Example:
  186. #
  187. # class Project < ActiveRecord::Base
  188. # self.table_name = "project"
  189. # end
  190. 3 def table_name=(value)
  191. 3380 value = value && value.to_s
  192. 3380 if defined?(@table_name)
  193. 932 return if value == @table_name
  194. 270 reset_column_information if connected?
  195. end
  196. 2718 @table_name = value
  197. 2718 @quoted_table_name = nil
  198. 2718 @arel_table = nil
  199. 2718 @sequence_name = nil unless defined?(@explicit_sequence_name) && @explicit_sequence_name
  200. 2718 @predicate_builder = nil
  201. end
  202. # Returns a quoted version of the table name, used to construct SQL statements.
  203. 3 def quoted_table_name
  204. 390 @quoted_table_name ||= connection.quote_table_name(table_name)
  205. end
  206. # Computes the table name, (re)sets it internally, and returns it.
  207. 3 def reset_table_name #:nodoc:
  208. 2234 self.table_name = if abstract_class?
  209. 27 superclass == Base ? nil : superclass.table_name
  210. 2207 elsif superclass.abstract_class?
  211. 39 superclass.table_name || compute_table_name
  212. else
  213. 2168 compute_table_name
  214. end
  215. end
  216. 3 def full_table_name_prefix #:nodoc:
  217. 4780 (module_parents.detect { |p| p.respond_to?(:table_name_prefix) } || self).table_name_prefix
  218. end
  219. 3 def full_table_name_suffix #:nodoc:
  220. 4792 (module_parents.detect { |p| p.respond_to?(:table_name_suffix) } || self).table_name_suffix
  221. end
  222. # The array of names of environments where destructive actions should be prohibited. By default,
  223. # the value is <tt>["production"]</tt>.
  224. 3 def protected_environments
  225. 27 if defined?(@protected_environments)
  226. 27 @protected_environments
  227. else
  228. superclass.protected_environments
  229. end
  230. end
  231. # Sets an array of names of environments where destructive actions should be prohibited.
  232. 3 def protected_environments=(environments)
  233. 21 @protected_environments = environments.map(&:to_s)
  234. end
  235. # Defines the name of the table column which will store the class name on single-table
  236. # inheritance situations.
  237. #
  238. # The default inheritance column name is +type+, which means it's a
  239. # reserved word inside Active Record. To be able to use single-table
  240. # inheritance with another column name, or to use the column +type+ in
  241. # your own model for something else, you can set +inheritance_column+:
  242. #
  243. # self.inheritance_column = 'zoink'
  244. 3 def inheritance_column
  245. 367107 (@inheritance_column ||= nil) || superclass.inheritance_column
  246. end
  247. # Sets the value of inheritance_column
  248. 3 def inheritance_column=(value)
  249. 48 @inheritance_column = value.to_s
  250. 48 @explicit_inheritance_column = true
  251. end
  252. # The list of columns names the model should ignore. Ignored columns won't have attribute
  253. # accessors defined, and won't be referenced in SQL queries.
  254. 3 def ignored_columns
  255. 74751 if defined?(@ignored_columns)
  256. 36032 @ignored_columns
  257. else
  258. 38719 superclass.ignored_columns
  259. end
  260. end
  261. # Sets the columns names the model should ignore. Ignored columns won't have attribute
  262. # accessors defined, and won't be referenced in SQL queries.
  263. 3 def ignored_columns=(columns)
  264. 15 reload_schema_from_cache
  265. 15 @ignored_columns = columns.map(&:to_s).freeze
  266. end
  267. 3 def sequence_name
  268. 18 if base_class?
  269. 12 @sequence_name ||= reset_sequence_name
  270. else
  271. 6 (@sequence_name ||= nil) || base_class.sequence_name
  272. end
  273. end
  274. 3 def reset_sequence_name #:nodoc:
  275. 204 @explicit_sequence_name = false
  276. 204 @sequence_name = connection.default_sequence_name(table_name, primary_key)
  277. end
  278. # Sets the name of the sequence to use when generating ids to the given
  279. # value, or (if the value is +nil+ or +false+) to the value returned by the
  280. # given block. This is required for Oracle and is useful for any
  281. # database which relies on sequences for primary key generation.
  282. #
  283. # If a sequence name is not explicitly set when using Oracle,
  284. # it will default to the commonly used pattern of: #{table_name}_seq
  285. #
  286. # If a sequence name is not explicitly set when using PostgreSQL, it
  287. # will discover the sequence corresponding to your primary key for you.
  288. #
  289. # class Project < ActiveRecord::Base
  290. # self.sequence_name = "projectseq" # default would have been "project_seq"
  291. # end
  292. 3 def sequence_name=(value)
  293. 6 @sequence_name = value.to_s
  294. 6 @explicit_sequence_name = true
  295. end
  296. # Determines if the primary key values should be selected from their
  297. # corresponding sequence before the insert statement.
  298. 3 def prefetch_primary_key?
  299. 11099 connection.prefetch_primary_key?(table_name)
  300. end
  301. # Returns the next value that will be used as the primary key on
  302. # an insert statement.
  303. 3 def next_sequence_value
  304. connection.next_sequence_value(sequence_name)
  305. end
  306. # Indicates whether the table associated with this class exists
  307. 3 def table_exists?
  308. 6154 connection.schema_cache.data_source_exists?(table_name)
  309. end
  310. 3 def attributes_builder # :nodoc:
  311. 246035 unless defined?(@attributes_builder) && @attributes_builder
  312. 1574 defaults = _default_attributes.except(*(column_names - [primary_key]))
  313. 1574 @attributes_builder = ActiveModel::AttributeSet::Builder.new(attribute_types, defaults)
  314. end
  315. 246035 @attributes_builder
  316. end
  317. 3 def columns_hash # :nodoc:
  318. 37475 load_schema
  319. 37469 @columns_hash
  320. end
  321. 3 def columns
  322. 8582 load_schema
  323. 8582 @columns ||= columns_hash.values.freeze
  324. end
  325. 3 def attribute_types # :nodoc:
  326. 265094 load_schema
  327. 265083 @attribute_types ||= Hash.new(Type.default_value)
  328. end
  329. 3 def yaml_encoder # :nodoc:
  330. 145 @yaml_encoder ||= ActiveModel::AttributeSet::YAMLEncoder.new(attribute_types)
  331. end
  332. # Returns the type of the attribute with the given name, after applying
  333. # all modifiers. This method is the only valid source of information for
  334. # anything related to the types of a model's attributes. This method will
  335. # access the database and load the model's schema if it is required.
  336. #
  337. # The return value of this method will implement the interface described
  338. # by ActiveModel::Type::Value (though the object itself may not subclass
  339. # it).
  340. #
  341. # +attr_name+ The name of the attribute to retrieve the type for. Must be
  342. # a string or a symbol.
  343. 3 def type_for_attribute(attr_name, &block)
  344. 188654 attr_name = attr_name.to_s
  345. 188654 attr_name = attribute_aliases[attr_name] || attr_name
  346. 188654 if block
  347. 440 attribute_types.fetch(attr_name, &block)
  348. else
  349. 188214 attribute_types[attr_name]
  350. end
  351. end
  352. # Returns the column object for the named attribute.
  353. # Returns an +ActiveRecord::ConnectionAdapters::NullColumn+ if the
  354. # named attribute does not exist.
  355. #
  356. # class Person < ActiveRecord::Base
  357. # end
  358. #
  359. # person = Person.new
  360. # person.column_for_attribute(:name) # the result depends on the ConnectionAdapter
  361. # # => #<ActiveRecord::ConnectionAdapters::Column:0x007ff4ab083980 @name="name", @sql_type="varchar(255)", @null=true, ...>
  362. #
  363. # person.column_for_attribute(:nothing)
  364. # # => #<ActiveRecord::ConnectionAdapters::NullColumn:0xXXX @name=nil, @sql_type=nil, @cast_type=#<Type::Value>, ...>
  365. 3 def column_for_attribute(name)
  366. 45 name = name.to_s
  367. 45 columns_hash.fetch(name) do
  368. 6 ConnectionAdapters::NullColumn.new(name)
  369. end
  370. end
  371. # Returns a hash where the keys are column names and the values are
  372. # default values when instantiating the Active Record object for this table.
  373. 3 def column_defaults
  374. 3147 load_schema
  375. 3147 @column_defaults ||= _default_attributes.deep_dup.to_hash.freeze
  376. end
  377. 3 def _default_attributes # :nodoc:
  378. 49300 load_schema
  379. 49300 @default_attributes ||= ActiveModel::AttributeSet.new({})
  380. end
  381. # Returns an array of column names as strings.
  382. 3 def column_names
  383. 26919 @column_names ||= columns.map(&:name).freeze
  384. end
  385. 3 def symbol_column_to_string(name_symbol) # :nodoc:
  386. 24662 @symbol_column_to_string_name_hash ||= column_names.index_by(&:to_sym)
  387. 24662 @symbol_column_to_string_name_hash[name_symbol]
  388. end
  389. # Returns an array of column objects where the primary id, all columns ending in "_id" or "_count",
  390. # and columns used for single table inheritance have been removed.
  391. 3 def content_columns
  392. 3 @content_columns ||= columns.reject do |c|
  393. 54 c.name == primary_key ||
  394. c.name == inheritance_column ||
  395. c.name.end_with?("_id", "_count")
  396. end.freeze
  397. end
  398. # Resets all the cached information about columns, which will cause them
  399. # to be reloaded on the next request.
  400. #
  401. # The most common usage pattern for this method is probably in a migration,
  402. # when just after creating a table you want to populate it with some default
  403. # values, eg:
  404. #
  405. # class CreateJobLevels < ActiveRecord::Migration[6.0]
  406. # def up
  407. # create_table :job_levels do |t|
  408. # t.integer :id
  409. # t.string :name
  410. #
  411. # t.timestamps
  412. # end
  413. #
  414. # JobLevel.reset_column_information
  415. # %w{assistant executive manager director}.each do |type|
  416. # JobLevel.create(name: type)
  417. # end
  418. # end
  419. #
  420. # def down
  421. # drop_table :job_levels
  422. # end
  423. # end
  424. 3 def reset_column_information
  425. 2006 connection.clear_cache!
  426. 2006 ([self] + descendants).each(&:undefine_attribute_methods)
  427. 2006 connection.schema_cache.clear_data_source_cache!(table_name)
  428. 2006 reload_schema_from_cache
  429. 2006 initialize_find_by_cache
  430. end
  431. 3 protected
  432. 3 def initialize_load_schema_monitor
  433. 2943 @load_schema_monitor = Monitor.new
  434. end
  435. 3 private
  436. 3 def inherited(child_class)
  437. 2940 super
  438. 2940 child_class.initialize_load_schema_monitor
  439. end
  440. 3 def schema_loaded?
  441. 363598 defined?(@schema_loaded) && @schema_loaded
  442. end
  443. 3 def load_schema
  444. 363598 return if schema_loaded?
  445. 65838 @load_schema_monitor.synchronize do
  446. 65838 return if defined?(@columns_hash) && @columns_hash
  447. 3471 load_schema!
  448. 3454 @schema_loaded = true
  449. rescue
  450. 17 reload_schema_from_cache # If the schema loading failed half way through, we must reset the state.
  451. 17 raise
  452. end
  453. end
  454. 3 def load_schema!
  455. 3471 unless table_name
  456. 3 raise ActiveRecord::TableNotSpecified, "#{self} has no table configured. Set one with #{self}.table_name="
  457. end
  458. 3468 columns_hash = connection.schema_cache.columns_hash(table_name)
  459. 3465 columns_hash = columns_hash.except(*ignored_columns) unless ignored_columns.empty?
  460. 3465 @columns_hash = columns_hash.freeze
  461. 3465 @columns_hash.each do |name, column|
  462. 28589 type = connection.lookup_cast_type_from_column(column)
  463. 28586 type = _convert_type_from_options(type)
  464. 28586 define_attribute(
  465. name,
  466. type,
  467. default: column.default,
  468. user_provided_default: false
  469. )
  470. end
  471. end
  472. 3 def reload_schema_from_cache
  473. 4568 @arel_table = nil
  474. 4568 @column_names = nil
  475. 4568 @symbol_column_to_string_name_hash = nil
  476. 4568 @attribute_types = nil
  477. 4568 @content_columns = nil
  478. 4568 @default_attributes = nil
  479. 4568 @column_defaults = nil
  480. 4568 @inheritance_column = nil unless defined?(@explicit_inheritance_column) && @explicit_inheritance_column
  481. 4568 @attributes_builder = nil
  482. 4568 @columns = nil
  483. 4568 @columns_hash = nil
  484. 4568 @schema_loaded = false
  485. 4568 @attribute_names = nil
  486. 4568 @yaml_encoder = nil
  487. 4568 direct_descendants.each do |descendant|
  488. 1946 descendant.send(:reload_schema_from_cache)
  489. end
  490. end
  491. # Guesses the table name, but does not decorate it with prefix and suffix information.
  492. 3 def undecorated_table_name(class_name = base_class.name)
  493. 1606 table_name = class_name.to_s.demodulize.underscore
  494. 1606 pluralize_table_names ? table_name.pluralize : table_name
  495. end
  496. # Computes and returns a table name according to default conventions.
  497. 3 def compute_table_name
  498. 2195 if base_class?
  499. # Nested classes are prefixed with singular parent table name.
  500. 1606 if module_parent < Base && !module_parent.abstract_class?
  501. 58 contained = module_parent.table_name
  502. 58 contained = contained.singularize if module_parent.pluralize_table_names
  503. 58 contained += "_"
  504. end
  505. 1606 "#{full_table_name_prefix}#{contained}#{undecorated_table_name(name)}#{full_table_name_suffix}"
  506. else
  507. # STI subclasses always use their superclass' table.
  508. 589 base_class.table_name
  509. end
  510. end
  511. 3 def _convert_type_from_options(type)
  512. 28586 if immutable_strings_by_default && type.respond_to?(:to_immutable_string)
  513. 27 type.to_immutable_string
  514. else
  515. 28559 type
  516. end
  517. end
  518. end
  519. end
  520. end

lib/active_record/nested_attributes.rb

100.0% lines covered

111 relevant lines. 111 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 require "active_support/core_ext/hash/except"
  3. 3 require "active_support/core_ext/module/redefine_method"
  4. 3 require "active_support/core_ext/hash/indifferent_access"
  5. 3 module ActiveRecord
  6. 3 module NestedAttributes #:nodoc:
  7. 3 class TooManyRecords < ActiveRecordError
  8. end
  9. 3 extend ActiveSupport::Concern
  10. 3 included do
  11. 3 class_attribute :nested_attributes_options, instance_writer: false, default: {}
  12. end
  13. # = Active Record Nested Attributes
  14. #
  15. # Nested attributes allow you to save attributes on associated records
  16. # through the parent. By default nested attribute updating is turned off
  17. # and you can enable it using the accepts_nested_attributes_for class
  18. # method. When you enable nested attributes an attribute writer is
  19. # defined on the model.
  20. #
  21. # The attribute writer is named after the association, which means that
  22. # in the following example, two new methods are added to your model:
  23. #
  24. # <tt>author_attributes=(attributes)</tt> and
  25. # <tt>pages_attributes=(attributes)</tt>.
  26. #
  27. # class Book < ActiveRecord::Base
  28. # has_one :author
  29. # has_many :pages
  30. #
  31. # accepts_nested_attributes_for :author, :pages
  32. # end
  33. #
  34. # Note that the <tt>:autosave</tt> option is automatically enabled on every
  35. # association that accepts_nested_attributes_for is used for.
  36. #
  37. # === One-to-one
  38. #
  39. # Consider a Member model that has one Avatar:
  40. #
  41. # class Member < ActiveRecord::Base
  42. # has_one :avatar
  43. # accepts_nested_attributes_for :avatar
  44. # end
  45. #
  46. # Enabling nested attributes on a one-to-one association allows you to
  47. # create the member and avatar in one go:
  48. #
  49. # params = { member: { name: 'Jack', avatar_attributes: { icon: 'smiling' } } }
  50. # member = Member.create(params[:member])
  51. # member.avatar.id # => 2
  52. # member.avatar.icon # => 'smiling'
  53. #
  54. # It also allows you to update the avatar through the member:
  55. #
  56. # params = { member: { avatar_attributes: { id: '2', icon: 'sad' } } }
  57. # member.update params[:member]
  58. # member.avatar.icon # => 'sad'
  59. #
  60. # If you want to update the current avatar without providing the id, you must add <tt>:update_only</tt> option.
  61. #
  62. # class Member < ActiveRecord::Base
  63. # has_one :avatar
  64. # accepts_nested_attributes_for :avatar, update_only: true
  65. # end
  66. #
  67. # params = { member: { avatar_attributes: { icon: 'sad' } } }
  68. # member.update params[:member]
  69. # member.avatar.id # => 2
  70. # member.avatar.icon # => 'sad'
  71. #
  72. # By default you will only be able to set and update attributes on the
  73. # associated model. If you want to destroy the associated model through the
  74. # attributes hash, you have to enable it first using the
  75. # <tt>:allow_destroy</tt> option.
  76. #
  77. # class Member < ActiveRecord::Base
  78. # has_one :avatar
  79. # accepts_nested_attributes_for :avatar, allow_destroy: true
  80. # end
  81. #
  82. # Now, when you add the <tt>_destroy</tt> key to the attributes hash, with a
  83. # value that evaluates to +true+, you will destroy the associated model:
  84. #
  85. # member.avatar_attributes = { id: '2', _destroy: '1' }
  86. # member.avatar.marked_for_destruction? # => true
  87. # member.save
  88. # member.reload.avatar # => nil
  89. #
  90. # Note that the model will _not_ be destroyed until the parent is saved.
  91. #
  92. # Also note that the model will not be destroyed unless you also specify
  93. # its id in the updated hash.
  94. #
  95. # === One-to-many
  96. #
  97. # Consider a member that has a number of posts:
  98. #
  99. # class Member < ActiveRecord::Base
  100. # has_many :posts
  101. # accepts_nested_attributes_for :posts
  102. # end
  103. #
  104. # You can now set or update attributes on the associated posts through
  105. # an attribute hash for a member: include the key +:posts_attributes+
  106. # with an array of hashes of post attributes as a value.
  107. #
  108. # For each hash that does _not_ have an <tt>id</tt> key a new record will
  109. # be instantiated, unless the hash also contains a <tt>_destroy</tt> key
  110. # that evaluates to +true+.
  111. #
  112. # params = { member: {
  113. # name: 'joe', posts_attributes: [
  114. # { title: 'Kari, the awesome Ruby documentation browser!' },
  115. # { title: 'The egalitarian assumption of the modern citizen' },
  116. # { title: '', _destroy: '1' } # this will be ignored
  117. # ]
  118. # }}
  119. #
  120. # member = Member.create(params[:member])
  121. # member.posts.length # => 2
  122. # member.posts.first.title # => 'Kari, the awesome Ruby documentation browser!'
  123. # member.posts.second.title # => 'The egalitarian assumption of the modern citizen'
  124. #
  125. # You may also set a +:reject_if+ proc to silently ignore any new record
  126. # hashes if they fail to pass your criteria. For example, the previous
  127. # example could be rewritten as:
  128. #
  129. # class Member < ActiveRecord::Base
  130. # has_many :posts
  131. # accepts_nested_attributes_for :posts, reject_if: proc { |attributes| attributes['title'].blank? }
  132. # end
  133. #
  134. # params = { member: {
  135. # name: 'joe', posts_attributes: [
  136. # { title: 'Kari, the awesome Ruby documentation browser!' },
  137. # { title: 'The egalitarian assumption of the modern citizen' },
  138. # { title: '' } # this will be ignored because of the :reject_if proc
  139. # ]
  140. # }}
  141. #
  142. # member = Member.create(params[:member])
  143. # member.posts.length # => 2
  144. # member.posts.first.title # => 'Kari, the awesome Ruby documentation browser!'
  145. # member.posts.second.title # => 'The egalitarian assumption of the modern citizen'
  146. #
  147. # Alternatively, +:reject_if+ also accepts a symbol for using methods:
  148. #
  149. # class Member < ActiveRecord::Base
  150. # has_many :posts
  151. # accepts_nested_attributes_for :posts, reject_if: :new_record?
  152. # end
  153. #
  154. # class Member < ActiveRecord::Base
  155. # has_many :posts
  156. # accepts_nested_attributes_for :posts, reject_if: :reject_posts
  157. #
  158. # def reject_posts(attributes)
  159. # attributes['title'].blank?
  160. # end
  161. # end
  162. #
  163. # If the hash contains an <tt>id</tt> key that matches an already
  164. # associated record, the matching record will be modified:
  165. #
  166. # member.attributes = {
  167. # name: 'Joe',
  168. # posts_attributes: [
  169. # { id: 1, title: '[UPDATED] An, as of yet, undisclosed awesome Ruby documentation browser!' },
  170. # { id: 2, title: '[UPDATED] other post' }
  171. # ]
  172. # }
  173. #
  174. # member.posts.first.title # => '[UPDATED] An, as of yet, undisclosed awesome Ruby documentation browser!'
  175. # member.posts.second.title # => '[UPDATED] other post'
  176. #
  177. # However, the above applies if the parent model is being updated as well.
  178. # For example, If you wanted to create a +member+ named _joe_ and wanted to
  179. # update the +posts+ at the same time, that would give an
  180. # ActiveRecord::RecordNotFound error.
  181. #
  182. # By default the associated records are protected from being destroyed. If
  183. # you want to destroy any of the associated records through the attributes
  184. # hash, you have to enable it first using the <tt>:allow_destroy</tt>
  185. # option. This will allow you to also use the <tt>_destroy</tt> key to
  186. # destroy existing records:
  187. #
  188. # class Member < ActiveRecord::Base
  189. # has_many :posts
  190. # accepts_nested_attributes_for :posts, allow_destroy: true
  191. # end
  192. #
  193. # params = { member: {
  194. # posts_attributes: [{ id: '2', _destroy: '1' }]
  195. # }}
  196. #
  197. # member.attributes = params[:member]
  198. # member.posts.detect { |p| p.id == 2 }.marked_for_destruction? # => true
  199. # member.posts.length # => 2
  200. # member.save
  201. # member.reload.posts.length # => 1
  202. #
  203. # Nested attributes for an associated collection can also be passed in
  204. # the form of a hash of hashes instead of an array of hashes:
  205. #
  206. # Member.create(
  207. # name: 'joe',
  208. # posts_attributes: {
  209. # first: { title: 'Foo' },
  210. # second: { title: 'Bar' }
  211. # }
  212. # )
  213. #
  214. # has the same effect as
  215. #
  216. # Member.create(
  217. # name: 'joe',
  218. # posts_attributes: [
  219. # { title: 'Foo' },
  220. # { title: 'Bar' }
  221. # ]
  222. # )
  223. #
  224. # The keys of the hash which is the value for +:posts_attributes+ are
  225. # ignored in this case.
  226. # However, it is not allowed to use <tt>'id'</tt> or <tt>:id</tt> for one of
  227. # such keys, otherwise the hash will be wrapped in an array and
  228. # interpreted as an attribute hash for a single post.
  229. #
  230. # Passing attributes for an associated collection in the form of a hash
  231. # of hashes can be used with hashes generated from HTTP/HTML parameters,
  232. # where there may be no natural way to submit an array of hashes.
  233. #
  234. # === Saving
  235. #
  236. # All changes to models, including the destruction of those marked for
  237. # destruction, are saved and destroyed automatically and atomically when
  238. # the parent model is saved. This happens inside the transaction initiated
  239. # by the parent's save method. See ActiveRecord::AutosaveAssociation.
  240. #
  241. # === Validating the presence of a parent model
  242. #
  243. # If you want to validate that a child record is associated with a parent
  244. # record, you can use the +validates_presence_of+ method and the +:inverse_of+
  245. # key as this example illustrates:
  246. #
  247. # class Member < ActiveRecord::Base
  248. # has_many :posts, inverse_of: :member
  249. # accepts_nested_attributes_for :posts
  250. # end
  251. #
  252. # class Post < ActiveRecord::Base
  253. # belongs_to :member, inverse_of: :posts
  254. # validates_presence_of :member
  255. # end
  256. #
  257. # Note that if you do not specify the +:inverse_of+ option, then
  258. # Active Record will try to automatically guess the inverse association
  259. # based on heuristics.
  260. #
  261. # For one-to-one nested associations, if you build the new (in-memory)
  262. # child object yourself before assignment, then this module will not
  263. # overwrite it, e.g.:
  264. #
  265. # class Member < ActiveRecord::Base
  266. # has_one :avatar
  267. # accepts_nested_attributes_for :avatar
  268. #
  269. # def avatar
  270. # super || build_avatar(width: 200)
  271. # end
  272. # end
  273. #
  274. # member = Member.new
  275. # member.avatar_attributes = {icon: 'sad'}
  276. # member.avatar.width # => 200
  277. 3 module ClassMethods
  278. 30 REJECT_ALL_BLANK_PROC = proc { |attributes| attributes.all? { |key, value| key == "_destroy" || value.blank? } }
  279. # Defines an attributes writer for the specified association(s).
  280. #
  281. # Supported options:
  282. # [:allow_destroy]
  283. # If true, destroys any members from the attributes hash with a
  284. # <tt>_destroy</tt> key and a value that evaluates to +true+
  285. # (e.g. 1, '1', true, or 'true'). This option is off by default.
  286. # [:reject_if]
  287. # Allows you to specify a Proc or a Symbol pointing to a method
  288. # that checks whether a record should be built for a certain attribute
  289. # hash. The hash is passed to the supplied Proc or the method
  290. # and it should return either +true+ or +false+. When no +:reject_if+
  291. # is specified, a record will be built for all attribute hashes that
  292. # do not have a <tt>_destroy</tt> value that evaluates to true.
  293. # Passing <tt>:all_blank</tt> instead of a Proc will create a proc
  294. # that will reject a record where all the attributes are blank excluding
  295. # any value for +_destroy+.
  296. # [:limit]
  297. # Allows you to specify the maximum number of associated records that
  298. # can be processed with the nested attributes. Limit also can be specified
  299. # as a Proc or a Symbol pointing to a method that should return a number.
  300. # If the size of the nested attributes array exceeds the specified limit,
  301. # NestedAttributes::TooManyRecords exception is raised. If omitted, any
  302. # number of associations can be processed.
  303. # Note that the +:limit+ option is only applicable to one-to-many
  304. # associations.
  305. # [:update_only]
  306. # For a one-to-one association, this option allows you to specify how
  307. # nested attributes are going to be used when an associated record already
  308. # exists. In general, an existing record may either be updated with the
  309. # new set of attribute values or be replaced by a wholly new record
  310. # containing those values. By default the +:update_only+ option is +false+
  311. # and the nested attributes are used to update the existing record only
  312. # if they include the record's <tt>:id</tt> value. Otherwise a new
  313. # record will be instantiated and used to replace the existing one.
  314. # However if the +:update_only+ option is +true+, the nested attributes
  315. # are used to update the record's attributes always, regardless of
  316. # whether the <tt>:id</tt> is present. The option is ignored for collection
  317. # associations.
  318. #
  319. # Examples:
  320. # # creates avatar_attributes=
  321. # accepts_nested_attributes_for :avatar, reject_if: proc { |attributes| attributes['name'].blank? }
  322. # # creates avatar_attributes=
  323. # accepts_nested_attributes_for :avatar, reject_if: :all_blank
  324. # # creates avatar_attributes= and posts_attributes=
  325. # accepts_nested_attributes_for :avatar, :posts, allow_destroy: true
  326. 3 def accepts_nested_attributes_for(*attr_names)
  327. 294 options = { allow_destroy: false, update_only: false }
  328. 294 options.update(attr_names.extract_options!)
  329. 294 options.assert_valid_keys(:allow_destroy, :reject_if, :limit, :update_only)
  330. 294 options[:reject_if] = REJECT_ALL_BLANK_PROC if options[:reject_if] == :all_blank
  331. 294 attr_names.each do |association_name|
  332. 321 if reflection = _reflect_on_association(association_name)
  333. 318 reflection.autosave = true
  334. 318 define_autosave_validation_callbacks(reflection)
  335. 318 nested_attributes_options = self.nested_attributes_options.dup
  336. 318 nested_attributes_options[association_name.to_sym] = options
  337. 318 self.nested_attributes_options = nested_attributes_options
  338. 318 type = (reflection.collection? ? :collection : :one_to_one)
  339. 318 generate_association_writer(association_name, type)
  340. else
  341. 3 raise ArgumentError, "No association found for name `#{association_name}'. Has it been defined yet?"
  342. end
  343. end
  344. end
  345. 3 private
  346. # Generates a writer method for this association. Serves as a point for
  347. # accessing the objects in the association. For example, this method
  348. # could generate the following:
  349. #
  350. # def pirate_attributes=(attributes)
  351. # assign_nested_attributes_for_one_to_one_association(:pirate, attributes)
  352. # end
  353. #
  354. # This redirects the attempts to write objects in an association through
  355. # the helper methods defined below. Makes it seem like the nested
  356. # associations are just regular associations.
  357. 3 def generate_association_writer(association_name, type)
  358. 318 generated_association_methods.module_eval <<-eoruby, __FILE__, __LINE__ + 1
  359. silence_redefinition_of_method :#{association_name}_attributes=
  360. def #{association_name}_attributes=(attributes)
  361. assign_nested_attributes_for_#{type}_association(:#{association_name}, attributes)
  362. end
  363. eoruby
  364. end
  365. end
  366. # Returns ActiveRecord::AutosaveAssociation::marked_for_destruction? It's
  367. # used in conjunction with fields_for to build a form element for the
  368. # destruction of this association.
  369. #
  370. # See ActionView::Helpers::FormHelper::fields_for for more info.
  371. 3 def _destroy
  372. 6 marked_for_destruction?
  373. end
  374. 3 private
  375. # Attribute hash keys that should not be assigned as normal attributes.
  376. # These hash keys are nested attributes implementation details.
  377. 3 UNASSIGNABLE_KEYS = %w( id _destroy )
  378. # Assigns the given attributes to the association.
  379. #
  380. # If an associated record does not yet exist, one will be instantiated. If
  381. # an associated record already exists, the method's behavior depends on
  382. # the value of the update_only option. If update_only is +false+ and the
  383. # given attributes include an <tt>:id</tt> that matches the existing record's
  384. # id, then the existing record will be modified. If no <tt>:id</tt> is provided
  385. # it will be replaced with a new record. If update_only is +true+ the existing
  386. # record will be modified regardless of whether an <tt>:id</tt> is provided.
  387. #
  388. # If the given attributes include a matching <tt>:id</tt> attribute, or
  389. # update_only is true, and a <tt>:_destroy</tt> key set to a truthy value,
  390. # then the existing record will be marked for destruction.
  391. 3 def assign_nested_attributes_for_one_to_one_association(association_name, attributes)
  392. 240 options = nested_attributes_options[association_name]
  393. 240 if attributes.respond_to?(:permitted?)
  394. 3 attributes = attributes.to_h
  395. end
  396. 240 attributes = attributes.with_indifferent_access
  397. 240 existing_record = send(association_name)
  398. 240 if (options[:update_only] || !attributes["id"].blank?) && existing_record &&
  399. (options[:update_only] || existing_record.id.to_s == attributes["id"].to_s)
  400. 135 assign_to_or_mark_for_destruction(existing_record, attributes, options[:allow_destroy]) unless call_reject_if(association_name, attributes)
  401. 105 elsif attributes["id"].present?
  402. 6 raise_nested_attributes_record_not_found!(association_name, attributes["id"])
  403. 99 elsif !reject_new_record?(association_name, attributes)
  404. 72 assignable_attributes = attributes.except(*UNASSIGNABLE_KEYS)
  405. 72 if existing_record && existing_record.new_record?
  406. 6 existing_record.assign_attributes(assignable_attributes)
  407. 6 association(association_name).initialize_attributes(existing_record)
  408. else
  409. 66 method = :"build_#{association_name}"
  410. 66 if respond_to?(method)
  411. 63 send(method, assignable_attributes)
  412. else
  413. 3 raise ArgumentError, "Cannot build association `#{association_name}'. Are you trying to build a polymorphic one-to-one association?"
  414. end
  415. end
  416. end
  417. end
  418. # Assigns the given attributes to the collection association.
  419. #
  420. # Hashes with an <tt>:id</tt> value matching an existing associated record
  421. # will update that record. Hashes without an <tt>:id</tt> value will build
  422. # a new record for the association. Hashes with a matching <tt>:id</tt>
  423. # value and a <tt>:_destroy</tt> key set to a truthy value will mark the
  424. # matched record for destruction.
  425. #
  426. # For example:
  427. #
  428. # assign_nested_attributes_for_collection_association(:people, {
  429. # '1' => { id: '1', name: 'Peter' },
  430. # '2' => { name: 'John' },
  431. # '3' => { id: '2', _destroy: true }
  432. # })
  433. #
  434. # Will update the name of the Person with ID 1, build a new associated
  435. # person with the name 'John', and mark the associated Person with ID 2
  436. # for destruction.
  437. #
  438. # Also accepts an Array of attribute hashes:
  439. #
  440. # assign_nested_attributes_for_collection_association(:people, [
  441. # { id: '1', name: 'Peter' },
  442. # { name: 'John' },
  443. # { id: '2', _destroy: true }
  444. # ])
  445. 3 def assign_nested_attributes_for_collection_association(association_name, attributes_collection)
  446. 405 options = nested_attributes_options[association_name]
  447. 405 if attributes_collection.respond_to?(:permitted?)
  448. 3 attributes_collection = attributes_collection.to_h
  449. end
  450. 405 unless attributes_collection.is_a?(Hash) || attributes_collection.is_a?(Array)
  451. 6 raise ArgumentError, "Hash or Array expected for attribute `#{association_name}`, got #{attributes_collection.class.name} (#{attributes_collection.inspect})"
  452. end
  453. 399 check_record_limit!(options[:limit], attributes_collection)
  454. 390 if attributes_collection.is_a? Hash
  455. 198 keys = attributes_collection.keys
  456. 198 attributes_collection = if keys.include?("id") || keys.include?(:id)
  457. 15 [attributes_collection]
  458. else
  459. 183 attributes_collection.values
  460. end
  461. end
  462. 390 association = association(association_name)
  463. 390 existing_records = if association.loaded?
  464. 150 association.target
  465. else
  466. 609 attribute_ids = attributes_collection.map { |a| a["id"] || a[:id] }.compact
  467. 240 attribute_ids.empty? ? [] : association.scope.where(association.klass.primary_key => attribute_ids)
  468. end
  469. 390 attributes_collection.each do |attributes|
  470. 627 if attributes.respond_to?(:permitted?)
  471. 6 attributes = attributes.to_h
  472. end
  473. 627 attributes = attributes.with_indifferent_access
  474. 627 if attributes["id"].blank?
  475. 231 unless reject_new_record?(association_name, attributes)
  476. 213 association.reader.build(attributes.except(*UNASSIGNABLE_KEYS))
  477. end
  478. 978 elsif existing_record = existing_records.detect { |record| record.id.to_s == attributes["id"].to_s }
  479. 384 unless call_reject_if(association_name, attributes)
  480. # Make sure we are operating on the actual object which is in the association's
  481. # proxy_target array (either by finding it, or adding it if not found)
  482. # Take into account that the proxy_target may have changed due to callbacks
  483. 834 target_record = association.target.detect { |record| record.id.to_s == attributes["id"].to_s }
  484. 381 if target_record
  485. 276 existing_record = target_record
  486. else
  487. 105 association.add_to_target(existing_record, skip_callbacks: true)
  488. end
  489. 381 assign_to_or_mark_for_destruction(existing_record, attributes, options[:allow_destroy])
  490. end
  491. else
  492. 12 raise_nested_attributes_record_not_found!(association_name, attributes["id"])
  493. end
  494. end
  495. end
  496. # Takes in a limit and checks if the attributes_collection has too many
  497. # records. It accepts limit in the form of symbol, proc, or
  498. # number-like object (anything that can be compared with an integer).
  499. #
  500. # Raises TooManyRecords error if the attributes_collection is
  501. # larger than the limit.
  502. 3 def check_record_limit!(limit, attributes_collection)
  503. 399 if limit
  504. 27 limit = \
  505. case limit
  506. when Symbol
  507. 9 send(limit)
  508. when Proc
  509. 9 limit.call
  510. else
  511. 9 limit
  512. end
  513. 27 if limit && attributes_collection.size > limit
  514. 9 raise TooManyRecords, "Maximum #{limit} records are allowed. Got #{attributes_collection.size} records instead."
  515. end
  516. end
  517. end
  518. # Updates a record with the +attributes+ or marks it for destruction if
  519. # +allow_destroy+ is +true+ and has_destroy_flag? returns +true+.
  520. 3 def assign_to_or_mark_for_destruction(record, attributes, allow_destroy)
  521. 510 record.assign_attributes(attributes.except(*UNASSIGNABLE_KEYS))
  522. 510 record.mark_for_destruction if has_destroy_flag?(attributes) && allow_destroy
  523. end
  524. # Determines if a hash contains a truthy _destroy key.
  525. 3 def has_destroy_flag?(hash)
  526. 1380 Type::Boolean.new.cast(hash["_destroy"])
  527. end
  528. # Determines if a new record should be rejected by checking
  529. # has_destroy_flag? or if a <tt>:reject_if</tt> proc exists for this
  530. # association and evaluates to +true+.
  531. 3 def reject_new_record?(association_name, attributes)
  532. 330 will_be_destroyed?(association_name, attributes) || call_reject_if(association_name, attributes)
  533. end
  534. # Determines if a record with the particular +attributes+ should be
  535. # rejected by calling the reject_if Symbol or Proc (if defined).
  536. # The reject_if option is defined by +accepts_nested_attributes_for+.
  537. #
  538. # Returns false if there is a +destroy_flag+ on the attributes.
  539. 3 def call_reject_if(association_name, attributes)
  540. 831 return false if will_be_destroyed?(association_name, attributes)
  541. 723 case callback = nested_attributes_options[association_name][:reject_if]
  542. when Symbol
  543. 9 method(callback).arity == 0 ? send(callback) : send(callback, attributes)
  544. when Proc
  545. 495 callback.call(attributes)
  546. end
  547. end
  548. # Only take into account the destroy flag if <tt>:allow_destroy</tt> is true
  549. 3 def will_be_destroyed?(association_name, attributes)
  550. 1161 allow_destroy?(association_name) && has_destroy_flag?(attributes)
  551. end
  552. 3 def allow_destroy?(association_name)
  553. 1161 nested_attributes_options[association_name][:allow_destroy]
  554. end
  555. 3 def raise_nested_attributes_record_not_found!(association_name, record_id)
  556. 18 model = self.class._reflect_on_association(association_name).klass.name
  557. 18 raise RecordNotFound.new("Couldn't find #{model} with ID=#{record_id} for #{self.class.name} with ID=#{id}",
  558. model, "id", record_id)
  559. end
  560. end
  561. end

lib/active_record/no_touching.rb

100.0% lines covered

22 relevant lines. 22 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 module ActiveRecord
  3. # = Active Record No Touching
  4. 3 module NoTouching
  5. 3 extend ActiveSupport::Concern
  6. 3 module ClassMethods
  7. # Lets you selectively disable calls to +touch+ for the
  8. # duration of a block.
  9. #
  10. # ==== Examples
  11. # ActiveRecord::Base.no_touching do
  12. # Project.first.touch # does nothing
  13. # Message.first.touch # does nothing
  14. # end
  15. #
  16. # Project.no_touching do
  17. # Project.first.touch # does nothing
  18. # Message.first.touch # works, but does not touch the associated project
  19. # end
  20. #
  21. 3 def no_touching(&block)
  22. 18 NoTouching.apply_to(self, &block)
  23. end
  24. end
  25. 3 class << self
  26. 3 def apply_to(klass) #:nodoc:
  27. 18 klasses.push(klass)
  28. 18 yield
  29. ensure
  30. 18 klasses.pop
  31. end
  32. 3 def applied_to?(klass) #:nodoc:
  33. 1038 klasses.any? { |k| k >= klass }
  34. end
  35. 3 private
  36. 3 def klasses
  37. 1041 Thread.current[:no_touching_classes] ||= []
  38. end
  39. end
  40. # Returns +true+ if the class has +no_touching+ set, +false+ otherwise.
  41. #
  42. # Project.no_touching do
  43. # Project.first.no_touching? # true
  44. # Message.first.no_touching? # false
  45. # end
  46. #
  47. 3 def no_touching?
  48. 1005 NoTouching.applied_to?(self.class)
  49. end
  50. 3 def touch_later(*) # :nodoc:
  51. 429 super unless no_touching?
  52. end
  53. 3 def touch(*, **) # :nodoc:
  54. 546 super unless no_touching?
  55. end
  56. end
  57. end

lib/active_record/null_relation.rb

100.0% lines covered

33 relevant lines. 33 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 module ActiveRecord
  3. 3 module NullRelation # :nodoc:
  4. 3 def pluck(*column_names)
  5. 30 []
  6. end
  7. 3 def delete_all
  8. 3 0
  9. end
  10. 3 def update_all(_updates)
  11. 3 0
  12. end
  13. 3 def delete(_id_or_array)
  14. 3 0
  15. end
  16. 3 def empty?
  17. 3 true
  18. end
  19. 3 def none?
  20. 3 true
  21. end
  22. 3 def any?
  23. 15 false
  24. end
  25. 3 def one?
  26. 3 false
  27. end
  28. 3 def many?
  29. 3 false
  30. end
  31. 3 def to_sql
  32. 3 ""
  33. end
  34. 3 def calculate(operation, _column_name)
  35. 54 case operation
  36. when :count, :sum
  37. 36 group_values.any? ? Hash.new : 0
  38. when :average, :minimum, :maximum
  39. 18 group_values.any? ? Hash.new : nil
  40. end
  41. end
  42. 3 def exists?(_conditions = :none)
  43. 8 false
  44. end
  45. 3 def or(other)
  46. 6 other.spawn
  47. end
  48. 3 private
  49. 3 def exec_queries
  50. 30 @records = [].freeze
  51. end
  52. end
  53. end

lib/active_record/persistence.rb

97.95% lines covered

244 relevant lines. 239 lines covered and 5 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 require "active_record/insert_all"
  3. 3 module ActiveRecord
  4. # = Active Record \Persistence
  5. 3 module Persistence
  6. 3 extend ActiveSupport::Concern
  7. 3 module ClassMethods
  8. # Creates an object (or multiple objects) and saves it to the database, if validations pass.
  9. # The resulting object is returned whether the object was saved successfully to the database or not.
  10. #
  11. # The +attributes+ parameter can be either a Hash or an Array of Hashes. These Hashes describe the
  12. # attributes on the objects that are to be created.
  13. #
  14. # ==== Examples
  15. # # Create a single new object
  16. # User.create(first_name: 'Jamie')
  17. #
  18. # # Create an Array of new objects
  19. # User.create([{ first_name: 'Jamie' }, { first_name: 'Jeremy' }])
  20. #
  21. # # Create a single object and pass it into a block to set other attributes.
  22. # User.create(first_name: 'Jamie') do |u|
  23. # u.is_admin = false
  24. # end
  25. #
  26. # # Creating an Array of new objects using a block, where the block is executed for each object:
  27. # User.create([{ first_name: 'Jamie' }, { first_name: 'Jeremy' }]) do |u|
  28. # u.is_admin = false
  29. # end
  30. 3 def create(attributes = nil, &block)
  31. 2872 if attributes.is_a?(Array)
  32. 75 attributes.collect { |attr| create(attr, &block) }
  33. else
  34. 2851 object = new(attributes, &block)
  35. 2842 object.save
  36. 2824 object
  37. end
  38. end
  39. # Creates an object (or multiple objects) and saves it to the database,
  40. # if validations pass. Raises a RecordInvalid error if validations fail,
  41. # unlike Base#create.
  42. #
  43. # The +attributes+ parameter can be either a Hash or an Array of Hashes.
  44. # These describe which attributes to be created on the object, or
  45. # multiple objects when given an Array of Hashes.
  46. 3 def create!(attributes = nil, &block)
  47. 4671 if attributes.is_a?(Array)
  48. 21 attributes.collect { |attr| create!(attr, &block) }
  49. else
  50. 4662 object = new(attributes, &block)
  51. 4656 object.save!
  52. 4581 object
  53. end
  54. end
  55. # Inserts a single record into the database in a single SQL INSERT
  56. # statement. It does not instantiate any models nor does it trigger
  57. # Active Record callbacks or validations. Though passed values
  58. # go through Active Record's type casting and serialization.
  59. #
  60. # See <tt>ActiveRecord::Persistence#insert_all</tt> for documentation.
  61. 3 def insert(attributes, returning: nil, unique_by: nil)
  62. 12 insert_all([ attributes ], returning: returning, unique_by: unique_by)
  63. end
  64. # Inserts multiple records into the database in a single SQL INSERT
  65. # statement. It does not instantiate any models nor does it trigger
  66. # Active Record callbacks or validations. Though passed values
  67. # go through Active Record's type casting and serialization.
  68. #
  69. # The +attributes+ parameter is an Array of Hashes. Every Hash determines
  70. # the attributes for a single row and must have the same keys.
  71. #
  72. # Rows are considered to be unique by every unique index on the table. Any
  73. # duplicate rows are skipped.
  74. # Override with <tt>:unique_by</tt> (see below).
  75. #
  76. # Returns an <tt>ActiveRecord::Result</tt> with its contents based on
  77. # <tt>:returning</tt> (see below).
  78. #
  79. # ==== Options
  80. #
  81. # [:returning]
  82. # (PostgreSQL only) An array of attributes to return for all successfully
  83. # inserted records, which by default is the primary key.
  84. # Pass <tt>returning: %w[ id name ]</tt> for both id and name
  85. # or <tt>returning: false</tt> to omit the underlying <tt>RETURNING</tt> SQL
  86. # clause entirely.
  87. #
  88. # [:unique_by]
  89. # (PostgreSQL and SQLite only) By default rows are considered to be unique
  90. # by every unique index on the table. Any duplicate rows are skipped.
  91. #
  92. # To skip rows according to just one unique index pass <tt>:unique_by</tt>.
  93. #
  94. # Consider a Book model where no duplicate ISBNs make sense, but if any
  95. # row has an existing id, or is not unique by another unique index,
  96. # <tt>ActiveRecord::RecordNotUnique</tt> is raised.
  97. #
  98. # Unique indexes can be identified by columns or name:
  99. #
  100. # unique_by: :isbn
  101. # unique_by: %i[ author_id name ]
  102. # unique_by: :index_books_on_isbn
  103. #
  104. # Because it relies on the index information from the database
  105. # <tt>:unique_by</tt> is recommended to be paired with
  106. # Active Record's schema_cache.
  107. #
  108. # ==== Example
  109. #
  110. # # Insert records and skip inserting any duplicates.
  111. # # Here "Eloquent Ruby" is skipped because its id is not unique.
  112. #
  113. # Book.insert_all([
  114. # { id: 1, title: "Rework", author: "David" },
  115. # { id: 1, title: "Eloquent Ruby", author: "Russ" }
  116. # ])
  117. 3 def insert_all(attributes, returning: nil, unique_by: nil)
  118. 66 InsertAll.new(self, attributes, on_duplicate: :skip, returning: returning, unique_by: unique_by).execute
  119. end
  120. # Inserts a single record into the database in a single SQL INSERT
  121. # statement. It does not instantiate any models nor does it trigger
  122. # Active Record callbacks or validations. Though passed values
  123. # go through Active Record's type casting and serialization.
  124. #
  125. # See <tt>ActiveRecord::Persistence#insert_all!</tt> for more.
  126. 3 def insert!(attributes, returning: nil)
  127. 6 insert_all!([ attributes ], returning: returning)
  128. end
  129. # Inserts multiple records into the database in a single SQL INSERT
  130. # statement. It does not instantiate any models nor does it trigger
  131. # Active Record callbacks or validations. Though passed values
  132. # go through Active Record's type casting and serialization.
  133. #
  134. # The +attributes+ parameter is an Array of Hashes. Every Hash determines
  135. # the attributes for a single row and must have the same keys.
  136. #
  137. # Raises <tt>ActiveRecord::RecordNotUnique</tt> if any rows violate a
  138. # unique index on the table. In that case, no rows are inserted.
  139. #
  140. # To skip duplicate rows, see <tt>ActiveRecord::Persistence#insert_all</tt>.
  141. # To replace them, see <tt>ActiveRecord::Persistence#upsert_all</tt>.
  142. #
  143. # Returns an <tt>ActiveRecord::Result</tt> with its contents based on
  144. # <tt>:returning</tt> (see below).
  145. #
  146. # ==== Options
  147. #
  148. # [:returning]
  149. # (PostgreSQL only) An array of attributes to return for all successfully
  150. # inserted records, which by default is the primary key.
  151. # Pass <tt>returning: %w[ id name ]</tt> for both id and name
  152. # or <tt>returning: false</tt> to omit the underlying <tt>RETURNING</tt> SQL
  153. # clause entirely.
  154. #
  155. # ==== Examples
  156. #
  157. # # Insert multiple records
  158. # Book.insert_all!([
  159. # { title: "Rework", author: "David" },
  160. # { title: "Eloquent Ruby", author: "Russ" }
  161. # ])
  162. #
  163. # # Raises ActiveRecord::RecordNotUnique because "Eloquent Ruby"
  164. # # does not have a unique id.
  165. # Book.insert_all!([
  166. # { id: 1, title: "Rework", author: "David" },
  167. # { id: 1, title: "Eloquent Ruby", author: "Russ" }
  168. # ])
  169. 3 def insert_all!(attributes, returning: nil)
  170. 40 InsertAll.new(self, attributes, on_duplicate: :raise, returning: returning).execute
  171. end
  172. # Updates or inserts (upserts) a single record into the database in a
  173. # single SQL INSERT statement. It does not instantiate any models nor does
  174. # it trigger Active Record callbacks or validations. Though passed values
  175. # go through Active Record's type casting and serialization.
  176. #
  177. # See <tt>ActiveRecord::Persistence#upsert_all</tt> for documentation.
  178. 3 def upsert(attributes, returning: nil, unique_by: nil)
  179. 9 upsert_all([ attributes ], returning: returning, unique_by: unique_by)
  180. end
  181. # Updates or inserts (upserts) multiple records into the database in a
  182. # single SQL INSERT statement. It does not instantiate any models nor does
  183. # it trigger Active Record callbacks or validations. Though passed values
  184. # go through Active Record's type casting and serialization.
  185. #
  186. # The +attributes+ parameter is an Array of Hashes. Every Hash determines
  187. # the attributes for a single row and must have the same keys.
  188. #
  189. # Returns an <tt>ActiveRecord::Result</tt> with its contents based on
  190. # <tt>:returning</tt> (see below).
  191. #
  192. # ==== Options
  193. #
  194. # [:returning]
  195. # (PostgreSQL only) An array of attributes to return for all successfully
  196. # inserted records, which by default is the primary key.
  197. # Pass <tt>returning: %w[ id name ]</tt> for both id and name
  198. # or <tt>returning: false</tt> to omit the underlying <tt>RETURNING</tt> SQL
  199. # clause entirely.
  200. #
  201. # [:unique_by]
  202. # (PostgreSQL and SQLite only) By default rows are considered to be unique
  203. # by every unique index on the table. Any duplicate rows are skipped.
  204. #
  205. # To skip rows according to just one unique index pass <tt>:unique_by</tt>.
  206. #
  207. # Consider a Book model where no duplicate ISBNs make sense, but if any
  208. # row has an existing id, or is not unique by another unique index,
  209. # <tt>ActiveRecord::RecordNotUnique</tt> is raised.
  210. #
  211. # Unique indexes can be identified by columns or name:
  212. #
  213. # unique_by: :isbn
  214. # unique_by: %i[ author_id name ]
  215. # unique_by: :index_books_on_isbn
  216. #
  217. # Because it relies on the index information from the database
  218. # <tt>:unique_by</tt> is recommended to be paired with
  219. # Active Record's schema_cache.
  220. #
  221. # ==== Examples
  222. #
  223. # # Inserts multiple records, performing an upsert when records have duplicate ISBNs.
  224. # # Here "Eloquent Ruby" overwrites "Rework" because its ISBN is duplicate.
  225. #
  226. # Book.upsert_all([
  227. # { title: "Rework", author: "David", isbn: "1" },
  228. # { title: "Eloquent Ruby", author: "Russ", isbn: "1" }
  229. # ], unique_by: :isbn)
  230. #
  231. # Book.find_by(isbn: "1").title # => "Eloquent Ruby"
  232. 3 def upsert_all(attributes, returning: nil, unique_by: nil)
  233. 76 InsertAll.new(self, attributes, on_duplicate: :update, returning: returning, unique_by: unique_by).execute
  234. end
  235. # Given an attributes hash, +instantiate+ returns a new instance of
  236. # the appropriate class. Accepts only keys as strings.
  237. #
  238. # For example, +Post.all+ may return Comments, Messages, and Emails
  239. # by storing the record's subclass in a +type+ attribute. By calling
  240. # +instantiate+ instead of +new+, finder methods ensure they get new
  241. # instances of the appropriate class for each record.
  242. #
  243. # See <tt>ActiveRecord::Inheritance#discriminate_class_for_record</tt> to see
  244. # how this "single-table" inheritance mapping is implemented.
  245. 3 def instantiate(attributes, column_types = {}, &block)
  246. 27284 klass = discriminate_class_for_record(attributes)
  247. 27275 instantiate_instance_of(klass, attributes, column_types, &block)
  248. end
  249. # Updates an object (or multiple objects) and saves it to the database, if validations pass.
  250. # The resulting object is returned whether the object was saved successfully to the database or not.
  251. #
  252. # ==== Parameters
  253. #
  254. # * +id+ - This should be the id or an array of ids to be updated.
  255. # * +attributes+ - This should be a hash of attributes or an array of hashes.
  256. #
  257. # ==== Examples
  258. #
  259. # # Updates one record
  260. # Person.update(15, user_name: "Samuel", group: "expert")
  261. #
  262. # # Updates multiple records
  263. # people = { 1 => { "first_name" => "David" }, 2 => { "first_name" => "Jeremy" } }
  264. # Person.update(people.keys, people.values)
  265. #
  266. # # Updates multiple records from the result of a relation
  267. # people = Person.where(group: "expert")
  268. # people.update(group: "masters")
  269. #
  270. # Note: Updating a large number of records will run an UPDATE
  271. # query for each record, which may cause a performance issue.
  272. # When running callbacks is not needed for each record update,
  273. # it is preferred to use {update_all}[rdoc-ref:Relation#update_all]
  274. # for updating all records in a single query.
  275. 3 def update(id = :all, attributes)
  276. 24 if id.is_a?(Array)
  277. 48 id.map { |one_id| find(one_id) }.each_with_index { |object, idx|
  278. 21 object.update(attributes[idx])
  279. }
  280. 9 elsif id == :all
  281. 18 all.each { |record| record.update(attributes) }
  282. else
  283. 6 if ActiveRecord::Base === id
  284. 3 raise ArgumentError,
  285. "You are passing an instance of ActiveRecord::Base to `update`. " \
  286. "Please pass the id of the object by calling `.id`."
  287. end
  288. 3 object = find(id)
  289. object.update(attributes)
  290. object
  291. end
  292. end
  293. # Destroy an object (or multiple objects) that has the given id. The object is instantiated first,
  294. # therefore all callbacks and filters are fired off before the object is deleted. This method is
  295. # less efficient than #delete but allows cleanup methods and other actions to be run.
  296. #
  297. # This essentially finds the object (or multiple objects) with the given id, creates a new object
  298. # from the attributes, and then calls destroy on it.
  299. #
  300. # ==== Parameters
  301. #
  302. # * +id+ - This should be the id or an array of ids to be destroyed.
  303. #
  304. # ==== Examples
  305. #
  306. # # Destroy a single object
  307. # Todo.destroy(1)
  308. #
  309. # # Destroy multiple objects
  310. # todos = [1,2,3]
  311. # Todo.destroy(todos)
  312. 3 def destroy(id)
  313. 18 if id.is_a?(Array)
  314. 6 find(id).each(&:destroy)
  315. else
  316. 12 find(id).destroy
  317. end
  318. end
  319. # Deletes the row with a primary key matching the +id+ argument, using an
  320. # SQL +DELETE+ statement, and returns the number of rows deleted. Active
  321. # Record objects are not instantiated, so the object's callbacks are not
  322. # executed, including any <tt>:dependent</tt> association options.
  323. #
  324. # You can delete multiple rows at once by passing an Array of <tt>id</tt>s.
  325. #
  326. # Note: Although it is often much faster than the alternative, #destroy,
  327. # skipping callbacks might bypass business logic in your application
  328. # that ensures referential integrity or performs other essential jobs.
  329. #
  330. # ==== Examples
  331. #
  332. # # Delete a single row
  333. # Todo.delete(1)
  334. #
  335. # # Delete multiple rows
  336. # Todo.delete([2,3,4])
  337. 3 def delete(id_or_array)
  338. 15 delete_by(primary_key => id_or_array)
  339. end
  340. 3 def _insert_record(values) # :nodoc:
  341. 12437 primary_key = self.primary_key
  342. 12437 primary_key_value = nil
  343. 12437 if primary_key && Hash === values
  344. 11634 primary_key_value = values[primary_key]
  345. 11634 if !primary_key_value && prefetch_primary_key?
  346. primary_key_value = next_sequence_value
  347. values[primary_key] = primary_key_value
  348. end
  349. end
  350. 12437 if values.empty?
  351. 1294 im = arel_table.compile_insert(connection.empty_insert_statement_value(primary_key))
  352. 1294 im.into arel_table
  353. else
  354. 11143 im = arel_table.compile_insert(_substitute_values(values))
  355. end
  356. 12437 connection.insert(im, "#{self} Create", primary_key || false, primary_key_value)
  357. end
  358. 3 def _update_record(values, constraints) # :nodoc:
  359. 5937 constraints = _substitute_values(constraints).map { |attr, bind| attr.eq(bind) }
  360. 2791 um = arel_table.where(
  361. constraints.reduce(&:and)
  362. ).compile_update(_substitute_values(values), primary_key)
  363. 2791 connection.update(um, "#{self} Update")
  364. end
  365. 3 def _delete_record(constraints) # :nodoc:
  366. 2506 constraints = _substitute_values(constraints).map { |attr, bind| attr.eq(bind) }
  367. 1217 dm = Arel::DeleteManager.new
  368. 1217 dm.from(arel_table)
  369. 1217 dm.wheres = constraints
  370. 1217 connection.delete(dm, "#{self} Destroy")
  371. end
  372. 3 private
  373. # Given a class, an attributes hash, +instantiate_instance_of+ returns a
  374. # new instance of the class. Accepts only keys as strings.
  375. 3 def instantiate_instance_of(klass, attributes, column_types = {}, &block)
  376. 246032 attributes = klass.attributes_builder.build_from_database(attributes, column_types)
  377. 246032 klass.allocate.init_with_attributes(attributes, &block)
  378. end
  379. # Called by +instantiate+ to decide which class to use for a new
  380. # record instance.
  381. #
  382. # See +ActiveRecord::Inheritance#discriminate_class_for_record+ for
  383. # the single-table inheritance discriminator.
  384. 3 def discriminate_class_for_record(record)
  385. 7772 self
  386. end
  387. 3 def _substitute_values(values)
  388. 17942 values.map do |name, value|
  389. 39962 attr = arel_table[name]
  390. 39962 bind = predicate_builder.build_bind_attribute(attr.name, value)
  391. 39962 [attr, bind]
  392. end
  393. end
  394. end
  395. # Returns true if this object hasn't been saved yet -- that is, a record
  396. # for the object doesn't exist in the database yet; otherwise, returns false.
  397. 3 def new_record?
  398. 83400 @new_record
  399. end
  400. # Returns true if this object was just created -- that is, prior to the last
  401. # save, the object didn't exist in the database and new_record? would have
  402. # returned true.
  403. 3 def previously_new_record?
  404. 19 @previously_new_record
  405. end
  406. # Returns true if this object has been destroyed, otherwise returns false.
  407. 3 def destroyed?
  408. 25981 @destroyed
  409. end
  410. # Returns true if the record is persisted, i.e. it's not a new record and it was
  411. # not destroyed, otherwise returns false.
  412. 3 def persisted?
  413. 32797 !(@new_record || @destroyed)
  414. end
  415. ##
  416. # :call-seq:
  417. # save(**options)
  418. #
  419. # Saves the model.
  420. #
  421. # If the model is new, a record gets created in the database, otherwise
  422. # the existing record gets updated.
  423. #
  424. # By default, save always runs validations. If any of them fail the action
  425. # is cancelled and #save returns +false+, and the record won't be saved. However, if you supply
  426. # <tt>validate: false</tt>, validations are bypassed altogether. See
  427. # ActiveRecord::Validations for more information.
  428. #
  429. # By default, #save also sets the +updated_at+/+updated_on+ attributes to
  430. # the current time. However, if you supply <tt>touch: false</tt>, these
  431. # timestamps will not be updated.
  432. #
  433. # There's a series of callbacks associated with #save. If any of the
  434. # <tt>before_*</tt> callbacks throws +:abort+ the action is cancelled and
  435. # #save returns +false+. See ActiveRecord::Callbacks for further
  436. # details.
  437. #
  438. # Attributes marked as readonly are silently ignored if the record is
  439. # being updated.
  440. 3 def save(**options, &block)
  441. 7870 create_or_update(**options, &block)
  442. rescue ActiveRecord::RecordInvalid
  443. 31 false
  444. end
  445. ##
  446. # :call-seq:
  447. # save!(**options)
  448. #
  449. # Saves the model.
  450. #
  451. # If the model is new, a record gets created in the database, otherwise
  452. # the existing record gets updated.
  453. #
  454. # By default, #save! always runs validations. If any of them fail
  455. # ActiveRecord::RecordInvalid gets raised, and the record won't be saved. However, if you supply
  456. # <tt>validate: false</tt>, validations are bypassed altogether. See
  457. # ActiveRecord::Validations for more information.
  458. #
  459. # By default, #save! also sets the +updated_at+/+updated_on+ attributes to
  460. # the current time. However, if you supply <tt>touch: false</tt>, these
  461. # timestamps will not be updated.
  462. #
  463. # There's a series of callbacks associated with #save!. If any of
  464. # the <tt>before_*</tt> callbacks throws +:abort+ the action is cancelled
  465. # and #save! raises ActiveRecord::RecordNotSaved. See
  466. # ActiveRecord::Callbacks for further details.
  467. #
  468. # Attributes marked as readonly are silently ignored if the record is
  469. # being updated.
  470. #
  471. # Unless an error is raised, returns true.
  472. 3 def save!(**options, &block)
  473. 8146 create_or_update(**options, &block) || raise(RecordNotSaved.new("Failed to save the record", self))
  474. end
  475. # Deletes the record in the database and freezes this instance to
  476. # reflect that no changes should be made (since they can't be
  477. # persisted). Returns the frozen instance.
  478. #
  479. # The row is simply removed with an SQL +DELETE+ statement on the
  480. # record's primary key, and no callbacks are executed.
  481. #
  482. # Note that this will also delete records marked as {#readonly?}[rdoc-ref:Core#readonly?].
  483. #
  484. # To enforce the object's +before_destroy+ and +after_destroy+
  485. # callbacks or any <tt>:dependent</tt> association
  486. # options, use #destroy.
  487. 3 def delete
  488. 280 _delete_row if persisted?
  489. 280 @destroyed = true
  490. 280 freeze
  491. end
  492. # Deletes the record in the database and freezes this instance to reflect
  493. # that no changes should be made (since they can't be persisted).
  494. #
  495. # There's a series of callbacks associated with #destroy. If the
  496. # <tt>before_destroy</tt> callback throws +:abort+ the action is cancelled
  497. # and #destroy returns +false+.
  498. # See ActiveRecord::Callbacks for further details.
  499. 3 def destroy
  500. 977 _raise_readonly_record_error if readonly?
  501. 974 destroy_associations
  502. 974 @_trigger_destroy_callback = if persisted?
  503. 958 destroy_row > 0
  504. else
  505. 16 true
  506. end
  507. 962 @destroyed = true
  508. 962 freeze
  509. end
  510. # Deletes the record in the database and freezes this instance to reflect
  511. # that no changes should be made (since they can't be persisted).
  512. #
  513. # There's a series of callbacks associated with #destroy!. If the
  514. # <tt>before_destroy</tt> callback throws +:abort+ the action is cancelled
  515. # and #destroy! raises ActiveRecord::RecordNotDestroyed.
  516. # See ActiveRecord::Callbacks for further details.
  517. 3 def destroy!
  518. 309 destroy || _raise_record_not_destroyed
  519. end
  520. # Returns an instance of the specified +klass+ with the attributes of the
  521. # current record. This is mostly useful in relation to single-table
  522. # inheritance structures where you want a subclass to appear as the
  523. # superclass. This can be used along with record identification in
  524. # Action Pack to allow, say, <tt>Client < Company</tt> to do something
  525. # like render <tt>partial: @client.becomes(Company)</tt> to render that
  526. # instance using the companies/company partial instead of clients/client.
  527. #
  528. # Note: The new instance will share a link to the same attributes as the original class.
  529. # Therefore the sti column value will still be the same.
  530. # Any change to the attributes on either instance will affect both instances.
  531. # If you want to change the sti column as well, use #becomes! instead.
  532. 3 def becomes(klass)
  533. 105 became = klass.allocate
  534. 105 became.send(:initialize)
  535. 105 became.instance_variable_set(:@attributes, @attributes)
  536. 105 became.instance_variable_set(:@mutations_from_database, @mutations_from_database ||= nil)
  537. 105 became.instance_variable_set(:@new_record, new_record?)
  538. 105 became.instance_variable_set(:@destroyed, destroyed?)
  539. 105 became.errors.copy!(errors)
  540. 105 became
  541. end
  542. # Wrapper around #becomes that also changes the instance's sti column value.
  543. # This is especially useful if you want to persist the changed class in your
  544. # database.
  545. #
  546. # Note: The old instance's sti column value will be changed too, as both objects
  547. # share the same set of attributes.
  548. 3 def becomes!(klass)
  549. 18 became = becomes(klass)
  550. 18 sti_type = nil
  551. 18 if !klass.descends_from_active_record?
  552. 12 sti_type = klass.sti_name
  553. end
  554. 18 became.public_send("#{klass.inheritance_column}=", sti_type)
  555. 18 became
  556. end
  557. # Updates a single attribute and saves the record.
  558. # This is especially useful for boolean flags on existing records. Also note that
  559. #
  560. # * Validation is skipped.
  561. # * \Callbacks are invoked.
  562. # * updated_at/updated_on column is updated if that column is available.
  563. # * Updates all the attributes that are dirty in this object.
  564. #
  565. # This method raises an ActiveRecord::ActiveRecordError if the
  566. # attribute is marked as readonly.
  567. #
  568. # Also see #update_column.
  569. 3 def update_attribute(name, value)
  570. 57 name = name.to_s
  571. 57 verify_readonly_attribute(name)
  572. 54 public_send("#{name}=", value)
  573. 54 save(validate: false)
  574. end
  575. # Updates the attributes of the model from the passed-in hash and saves the
  576. # record, all wrapped in a transaction. If the object is invalid, the saving
  577. # will fail and false will be returned.
  578. 3 def update(attributes)
  579. # The following transaction covers any possible database side-effects of the
  580. # attributes assignment. For example, setting the IDs of a child collection.
  581. 409 with_transaction_returning_status do
  582. 409 assign_attributes(attributes)
  583. 403 save
  584. end
  585. end
  586. 3 alias update_attributes update
  587. 3 deprecate update_attributes: "please, use update instead"
  588. # Updates its receiver just like #update but calls #save! instead
  589. # of +save+, so an exception is raised if the record is invalid and saving will fail.
  590. 3 def update!(attributes)
  591. # The following transaction covers any possible database side-effects of the
  592. # attributes assignment. For example, setting the IDs of a child collection.
  593. 381 with_transaction_returning_status do
  594. 381 assign_attributes(attributes)
  595. 381 save!
  596. end
  597. end
  598. 3 alias update_attributes! update!
  599. 3 deprecate update_attributes!: "please, use update! instead"
  600. # Equivalent to <code>update_columns(name => value)</code>.
  601. 3 def update_column(name, value)
  602. 83 update_columns(name => value)
  603. end
  604. # Updates the attributes directly in the database issuing an UPDATE SQL
  605. # statement and sets them in the receiver:
  606. #
  607. # user.update_columns(last_request_at: Time.current)
  608. #
  609. # This is the fastest way to update attributes because it goes straight to
  610. # the database, but take into account that in consequence the regular update
  611. # procedures are totally bypassed. In particular:
  612. #
  613. # * \Validations are skipped.
  614. # * \Callbacks are skipped.
  615. # * +updated_at+/+updated_on+ are not updated.
  616. # * However, attributes are serialized with the same rules as ActiveRecord::Relation#update_all
  617. #
  618. # This method raises an ActiveRecord::ActiveRecordError when called on new
  619. # objects, or when at least one of the attributes is marked as readonly.
  620. 3 def update_columns(attributes)
  621. 407 raise ActiveRecordError, "cannot update a new record" if new_record?
  622. 401 raise ActiveRecordError, "cannot update a destroyed record" if destroyed?
  623. 401 attributes = attributes.transform_keys do |key|
  624. 458 name = key.to_s
  625. 458 name = self.class.attribute_aliases[name] || name
  626. 458 verify_readonly_attribute(name) || name
  627. end
  628. 395 id_in_database = self.id_in_database
  629. 395 attributes.each do |k, v|
  630. 449 write_attribute_without_type_cast(k, v)
  631. end
  632. 392 affected_rows = self.class._update_record(
  633. attributes,
  634. @primary_key => id_in_database
  635. )
  636. 392 affected_rows == 1
  637. end
  638. # Initializes +attribute+ to zero if +nil+ and adds the value passed as +by+ (default is 1).
  639. # The increment is performed directly on the underlying attribute, no setter is invoked.
  640. # Only makes sense for number-based attributes. Returns +self+.
  641. 3 def increment(attribute, by = 1)
  642. 637 self[attribute] ||= 0
  643. 637 self[attribute] += by
  644. 637 self
  645. end
  646. # Wrapper around #increment that writes the update to the database.
  647. # Only +attribute+ is updated; the record itself is not saved.
  648. # This means that any other modified attributes will still be dirty.
  649. # Validations and callbacks are skipped. Supports the +touch+ option from
  650. # +update_counters+, see that for more.
  651. # Returns +self+.
  652. 3 def increment!(attribute, by = 1, touch: nil)
  653. 486 increment(attribute, by)
  654. 486 change = public_send(attribute) - (public_send(:"#{attribute}_in_database") || 0)
  655. 486 self.class.update_counters(id, attribute => change, touch: touch)
  656. 486 public_send(:"clear_#{attribute}_change")
  657. 486 self
  658. end
  659. # Initializes +attribute+ to zero if +nil+ and subtracts the value passed as +by+ (default is 1).
  660. # The decrement is performed directly on the underlying attribute, no setter is invoked.
  661. # Only makes sense for number-based attributes. Returns +self+.
  662. 3 def decrement(attribute, by = 1)
  663. 6 increment(attribute, -by)
  664. end
  665. # Wrapper around #decrement that writes the update to the database.
  666. # Only +attribute+ is updated; the record itself is not saved.
  667. # This means that any other modified attributes will still be dirty.
  668. # Validations and callbacks are skipped. Supports the +touch+ option from
  669. # +update_counters+, see that for more.
  670. # Returns +self+.
  671. 3 def decrement!(attribute, by = 1, touch: nil)
  672. 18 increment!(attribute, -by, touch: touch)
  673. end
  674. # Assigns to +attribute+ the boolean opposite of <tt>attribute?</tt>. So
  675. # if the predicate returns +true+ the attribute will become +false+. This
  676. # method toggles directly the underlying value without calling any setter.
  677. # Returns +self+.
  678. #
  679. # Example:
  680. #
  681. # user = User.first
  682. # user.banned? # => false
  683. # user.toggle(:banned)
  684. # user.banned? # => true
  685. #
  686. 3 def toggle(attribute)
  687. 6 self[attribute] = !public_send("#{attribute}?")
  688. 6 self
  689. end
  690. # Wrapper around #toggle that saves the record. This method differs from
  691. # its non-bang version in the sense that it passes through the attribute setter.
  692. # Saving is not subjected to validation checks. Returns +true+ if the
  693. # record could be saved.
  694. 3 def toggle!(attribute)
  695. 3 toggle(attribute).update_attribute(attribute, self[attribute])
  696. end
  697. # Reloads the record from the database.
  698. #
  699. # This method finds the record by its primary key (which could be assigned
  700. # manually) and modifies the receiver in-place:
  701. #
  702. # account = Account.new
  703. # # => #<Account id: nil, email: nil>
  704. # account.id = 1
  705. # account.reload
  706. # # Account Load (1.2ms) SELECT "accounts".* FROM "accounts" WHERE "accounts"."id" = $1 LIMIT 1 [["id", 1]]
  707. # # => #<Account id: 1, email: 'account@example.com'>
  708. #
  709. # Attributes are reloaded from the database, and caches busted, in
  710. # particular the associations cache and the QueryCache.
  711. #
  712. # If the record no longer exists in the database ActiveRecord::RecordNotFound
  713. # is raised. Otherwise, in addition to the in-place modification the method
  714. # returns +self+ for convenience.
  715. #
  716. # The optional <tt>:lock</tt> flag option allows you to lock the reloaded record:
  717. #
  718. # reload(lock: true) # reload with pessimistic locking
  719. #
  720. # Reloading is commonly used in test suites to test something is actually
  721. # written to the database, or when some action modifies the corresponding
  722. # row in the database but not the object in memory:
  723. #
  724. # assert account.deposit!(25)
  725. # assert_equal 25, account.credit # check it is updated in memory
  726. # assert_equal 25, account.reload.credit # check it is also persisted
  727. #
  728. # Another common use case is optimistic locking handling:
  729. #
  730. # def with_optimistic_retry
  731. # begin
  732. # yield
  733. # rescue ActiveRecord::StaleObjectError
  734. # begin
  735. # # Reload lock_version in particular.
  736. # reload
  737. # rescue ActiveRecord::RecordNotFound
  738. # # If the record is gone there is nothing to do.
  739. # else
  740. # retry
  741. # end
  742. # end
  743. # end
  744. #
  745. 3 def reload(options = nil)
  746. 2233 self.class.connection.clear_query_cache
  747. 2233 fresh_object =
  748. 2233 if options && options[:lock]
  749. 46 self.class.unscoped { self.class.lock(options[:lock]).find(id) }
  750. else
  751. 4420 self.class.unscoped { self.class.find(id) }
  752. end
  753. 2215 @attributes = fresh_object.instance_variable_get(:@attributes)
  754. 2215 @new_record = false
  755. 2215 @previously_new_record = false
  756. 2215 self
  757. end
  758. # Saves the record with the updated_at/on attributes set to the current time
  759. # or the time specified.
  760. # Please note that no validation is performed and only the +after_touch+,
  761. # +after_commit+ and +after_rollback+ callbacks are executed.
  762. #
  763. # This method can be passed attribute names and an optional time argument.
  764. # If attribute names are passed, they are updated along with updated_at/on
  765. # attributes. If no time argument is passed, the current time is used as default.
  766. #
  767. # product.touch # updates updated_at/on with current time
  768. # product.touch(time: Time.new(2015, 2, 16, 0, 0, 0)) # updates updated_at/on with specified time
  769. # product.touch(:designed_at) # updates the designed_at attribute and updated_at/on
  770. # product.touch(:started_at, :ended_at) # updates started_at, ended_at and updated_at/on attributes
  771. #
  772. # If used along with {belongs_to}[rdoc-ref:Associations::ClassMethods#belongs_to]
  773. # then +touch+ will invoke +touch+ method on associated object.
  774. #
  775. # class Brake < ActiveRecord::Base
  776. # belongs_to :car, touch: true
  777. # end
  778. #
  779. # class Car < ActiveRecord::Base
  780. # belongs_to :corporation, touch: true
  781. # end
  782. #
  783. # # triggers @brake.car.touch and @brake.car.corporation.touch
  784. # @brake.touch
  785. #
  786. # Note that +touch+ must be used on a persisted object, or else an
  787. # ActiveRecordError will be thrown. For example:
  788. #
  789. # ball = Ball.new
  790. # ball.touch(:updated_at) # => raises ActiveRecordError
  791. #
  792. 3 def touch(*names, time: nil)
  793. 537 _raise_record_not_touched_error unless persisted?
  794. 534 attribute_names = timestamp_attributes_for_update_in_model
  795. attribute_names |= names.map! do |name|
  796. 381 name = name.to_s
  797. 381 self.class.attribute_aliases[name] || name
  798. 534 end unless names.empty?
  799. 534 unless attribute_names.empty?
  800. 504 affected_rows = _touch_row(attribute_names, time)
  801. 489 @_trigger_update_callback = affected_rows == 1
  802. else
  803. 30 true
  804. end
  805. end
  806. 3 private
  807. # A hook to be overridden by association modules.
  808. 3 def destroy_associations
  809. end
  810. 3 def destroy_row
  811. 886 _delete_row
  812. end
  813. 3 def _delete_row
  814. 1145 self.class._delete_record(@primary_key => id_in_database)
  815. end
  816. 3 def _touch_row(attribute_names, time)
  817. 504 time ||= current_time_from_proper_timezone
  818. 504 attribute_names.each do |attr_name|
  819. 567 _write_attribute(attr_name, time)
  820. end
  821. 504 _update_row(attribute_names, "touch")
  822. end
  823. 3 def _update_row(attribute_names, attempted_action = "update")
  824. 2044 self.class._update_record(
  825. attributes_with_values(attribute_names),
  826. @primary_key => id_in_database
  827. )
  828. end
  829. 3 def create_or_update(**, &block)
  830. 15942 _raise_readonly_record_error if readonly?
  831. 15894 return false if destroyed?
  832. 15870 result = new_record? ? _create_record(&block) : _update_record(&block)
  833. 15684 result != false
  834. end
  835. # Updates the associated record with values matching those of the instance attributes.
  836. # Returns the number of affected rows.
  837. 3 def _update_record(attribute_names = self.attribute_names)
  838. 3413 attribute_names = attributes_for_update(attribute_names)
  839. 3413 if attribute_names.empty?
  840. 1518 affected_rows = 0
  841. 1518 @_trigger_update_callback = true
  842. else
  843. 1895 affected_rows = _update_row(attribute_names)
  844. 1854 @_trigger_update_callback = affected_rows == 1
  845. end
  846. 3372 @previously_new_record = false
  847. 3372 yield(self) if block_given?
  848. 3372 affected_rows
  849. end
  850. # Creates a record with values matching those of the instance attributes
  851. # and returns its id.
  852. 3 def _create_record(attribute_names = self.attribute_names)
  853. 12437 attribute_names = attributes_for_create(attribute_names)
  854. 12437 new_id = self.class._insert_record(
  855. attributes_with_values(attribute_names)
  856. )
  857. 12384 self.id ||= new_id if @primary_key
  858. 12384 @new_record = false
  859. 12384 @previously_new_record = true
  860. 12384 yield(self) if block_given?
  861. 12384 id
  862. end
  863. 3 def verify_readonly_attribute(name)
  864. 515 raise ActiveRecordError, "#{name} is marked as readonly" if self.class.readonly_attribute?(name)
  865. end
  866. 3 def _raise_record_not_destroyed
  867. 18 @_association_destroy_exception ||= nil
  868. 18 raise @_association_destroy_exception || RecordNotDestroyed.new("Failed to destroy the record", self)
  869. ensure
  870. 18 @_association_destroy_exception = nil
  871. end
  872. 3 def _raise_readonly_record_error
  873. 51 raise ReadOnlyRecord, "#{self.class} is marked as readonly"
  874. end
  875. 3 def _raise_record_not_touched_error
  876. 6 raise ActiveRecordError, <<~MSG.squish
  877. Cannot touch on a new or destroyed record object. Consider using
  878. persisted?, new_record?, or destroyed? before touching.
  879. MSG
  880. end
  881. # The name of the method used to touch a +belongs_to+ association when the
  882. # +:touch+ option is used.
  883. 3 def belongs_to_touch_method
  884. :touch
  885. end
  886. end
  887. end

lib/active_record/query_cache.rb

91.3% lines covered

23 relevant lines. 21 lines covered and 2 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 module ActiveRecord
  3. # = Active Record Query Cache
  4. 3 class QueryCache
  5. 3 module ClassMethods
  6. # Enable the query cache within the block if Active Record is configured.
  7. # If it's not, it will execute the given block.
  8. 3 def cache(&block)
  9. 161 if connected? || !configurations.empty?
  10. 161 connection.cache(&block)
  11. else
  12. yield
  13. end
  14. end
  15. # Disable the query cache within the block if Active Record is configured.
  16. # If it's not, it will execute the given block.
  17. 3 def uncached(&block)
  18. 1137 if connected? || !configurations.empty?
  19. 1137 connection.uncached(&block)
  20. else
  21. yield
  22. end
  23. end
  24. end
  25. 3 def self.run
  26. 68 pools = []
  27. 68 ActiveRecord::Base.connection_handlers.each do |key, handler|
  28. 1235 pools.concat(handler.connection_pool_list.reject { |p| p.query_cache_enabled }.each { |p| p.enable_query_cache! })
  29. end
  30. 68 pools
  31. end
  32. 3 def self.complete(pools)
  33. 649 pools.each { |pool| pool.disable_query_cache! }
  34. 68 ActiveRecord::Base.connection_handlers.each do |_, handler|
  35. 73 handler.connection_pool_list.each do |pool|
  36. 581 pool.release_connection if pool.active_connection? && !pool.connection.transaction_open?
  37. end
  38. end
  39. end
  40. 3 def self.install_executor_hooks(executor = ActiveSupport::Executor)
  41. 68 executor.register_hook(self)
  42. end
  43. end
  44. end

lib/active_record/querying.rb

100.0% lines covered

17 relevant lines. 17 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 module ActiveRecord
  3. 3 module Querying
  4. 3 QUERYING_METHODS = [
  5. :find, :find_by, :find_by!, :take, :take!, :first, :first!, :last, :last!,
  6. :second, :second!, :third, :third!, :fourth, :fourth!, :fifth, :fifth!,
  7. :forty_two, :forty_two!, :third_to_last, :third_to_last!, :second_to_last, :second_to_last!,
  8. :exists?, :any?, :many?, :none?, :one?,
  9. :first_or_create, :first_or_create!, :first_or_initialize,
  10. :find_or_create_by, :find_or_create_by!, :find_or_initialize_by,
  11. :create_or_find_by, :create_or_find_by!,
  12. :destroy_all, :delete_all, :update_all, :touch_all, :destroy_by, :delete_by,
  13. :find_each, :find_in_batches, :in_batches,
  14. :select, :reselect, :order, :reorder, :group, :limit, :offset, :joins, :left_joins, :left_outer_joins,
  15. :where, :rewhere, :preload, :extract_associated, :eager_load, :includes, :from, :lock, :readonly,
  16. :and, :or, :annotate, :optimizer_hints, :extending,
  17. :having, :create_with, :distinct, :references, :none, :unscope, :merge, :except, :only,
  18. :count, :average, :minimum, :maximum, :sum, :calculate,
  19. :pluck, :pick, :ids, :strict_loading
  20. ].freeze # :nodoc:
  21. 3 delegate(*QUERYING_METHODS, to: :all)
  22. # Executes a custom SQL query against your database and returns all the results. The results will
  23. # be returned as an array, with the requested columns encapsulated as attributes of the model you call
  24. # this method from. For example, if you call <tt>Product.find_by_sql</tt>, then the results will be returned in
  25. # a +Product+ object with the attributes you specified in the SQL query.
  26. #
  27. # If you call a complicated SQL query which spans multiple tables, the columns specified by the
  28. # SELECT will be attributes of the model, whether or not they are columns of the corresponding
  29. # table.
  30. #
  31. # The +sql+ parameter is a full SQL query as a string. It will be called as is; there will be
  32. # no database agnostic conversions performed. This should be a last resort because using
  33. # database-specific terms will lock you into using that particular database engine, or require you to
  34. # change your call if you switch engines.
  35. #
  36. # # A simple SQL query spanning multiple tables
  37. # Post.find_by_sql "SELECT p.title, c.author FROM posts p, comments c WHERE p.id = c.post_id"
  38. # # => [#<Post:0x36bff9c @attributes={"title"=>"Ruby Meetup", "author"=>"Quentin"}>, ...]
  39. #
  40. # You can use the same string replacement techniques as you can with <tt>ActiveRecord::QueryMethods#where</tt>:
  41. #
  42. # Post.find_by_sql ["SELECT title FROM posts WHERE author = ? AND created > ?", author_id, start_date]
  43. # Post.find_by_sql ["SELECT body FROM comments WHERE author = :user_id OR approved_by = :user_id", { :user_id => user_id }]
  44. 3 def find_by_sql(sql, binds = [], preparable: nil, &block)
  45. 29797 result_set = connection.select_all(sanitize_sql(sql), "#{name} Load", binds, preparable: preparable)
  46. 29768 column_types = result_set.column_types
  47. 29768 unless column_types.empty?
  48. 8133 column_types = column_types.reject { |k, _| attribute_types.key?(k) }
  49. end
  50. 29768 message_bus = ActiveSupport::Notifications.instrumenter
  51. 29768 payload = {
  52. record_count: result_set.length,
  53. class_name: name
  54. }
  55. 29768 message_bus.instrument("instantiation.active_record", payload) do
  56. 29768 if result_set.includes_column?(inheritance_column)
  57. 38867 result_set.map { |record| instantiate(record, column_types, &block) }
  58. else
  59. # Instantiate a homogeneous set
  60. 234118 result_set.map { |record| instantiate_instance_of(self, record, column_types, &block) }
  61. end
  62. end
  63. end
  64. # Returns the result of an SQL statement that should only include a COUNT(*) in the SELECT part.
  65. # The use of this method should be restricted to complicated SQL queries that can't be executed
  66. # using the ActiveRecord::Calculations class methods. Look into those before using this method,
  67. # as it could lock you into a specific database engine or require a code change to switch
  68. # database engines.
  69. #
  70. # Product.count_by_sql "SELECT COUNT(*) FROM sales s, customers c WHERE s.customer_id = c.id"
  71. # # => 12
  72. #
  73. # ==== Parameters
  74. #
  75. # * +sql+ - An SQL statement which should return a count query from the database, see the example above.
  76. 3 def count_by_sql(sql)
  77. 24 connection.select_value(sanitize_sql(sql), "#{name} Count").to_i
  78. end
  79. end
  80. end

lib/active_record/railtie.rb

0.0% lines covered

200 relevant lines. 0 lines covered and 200 lines missed.
    
  1. # frozen_string_literal: true
  2. require "active_record"
  3. require "rails"
  4. require "active_support/core_ext/object/try"
  5. require "active_model/railtie"
  6. # For now, action_controller must always be present with
  7. # Rails, so let's make sure that it gets required before
  8. # here. This is needed for correctly setting up the middleware.
  9. # In the future, this might become an optional require.
  10. require "action_controller/railtie"
  11. module ActiveRecord
  12. # = Active Record Railtie
  13. class Railtie < Rails::Railtie # :nodoc:
  14. config.active_record = ActiveSupport::OrderedOptions.new
  15. config.app_generators.orm :active_record, migration: true,
  16. timestamps: true
  17. config.action_dispatch.rescue_responses.merge!(
  18. "ActiveRecord::RecordNotFound" => :not_found,
  19. "ActiveRecord::StaleObjectError" => :conflict,
  20. "ActiveRecord::RecordInvalid" => :unprocessable_entity,
  21. "ActiveRecord::RecordNotSaved" => :unprocessable_entity
  22. )
  23. config.active_record.use_schema_cache_dump = true
  24. config.active_record.maintain_test_schema = true
  25. config.active_record.has_many_inversing = false
  26. config.active_record.sqlite3 = ActiveSupport::OrderedOptions.new
  27. config.active_record.sqlite3.represent_boolean_as_integer = nil
  28. config.eager_load_namespaces << ActiveRecord
  29. rake_tasks do
  30. namespace :db do
  31. task :load_config do
  32. ActiveRecord::Tasks::DatabaseTasks.database_configuration = Rails.application.config.database_configuration
  33. if defined?(ENGINE_ROOT) && engine = Rails::Engine.find(ENGINE_ROOT)
  34. if engine.paths["db/migrate"].existent
  35. ActiveRecord::Tasks::DatabaseTasks.migrations_paths += engine.paths["db/migrate"].to_a
  36. end
  37. end
  38. end
  39. end
  40. load "active_record/railties/databases.rake"
  41. end
  42. # When loading console, force ActiveRecord::Base to be loaded
  43. # to avoid cross references when loading a constant for the
  44. # first time. Also, make it output to STDERR.
  45. console do |app|
  46. require "active_record/railties/console_sandbox" if app.sandbox?
  47. require "active_record/base"
  48. unless ActiveSupport::Logger.logger_outputs_to?(Rails.logger, STDERR, STDOUT)
  49. console = ActiveSupport::Logger.new(STDERR)
  50. console.level = Rails.logger.level
  51. Rails.logger.extend ActiveSupport::Logger.broadcast console
  52. end
  53. ActiveRecord::Base.verbose_query_logs = false
  54. end
  55. runner do
  56. require "active_record/base"
  57. end
  58. initializer "active_record.initialize_timezone" do
  59. ActiveSupport.on_load(:active_record) do
  60. self.time_zone_aware_attributes = true
  61. self.default_timezone = :utc
  62. end
  63. end
  64. initializer "active_record.logger" do
  65. ActiveSupport.on_load(:active_record) { self.logger ||= ::Rails.logger }
  66. end
  67. initializer "active_record.backtrace_cleaner" do
  68. ActiveSupport.on_load(:active_record) { LogSubscriber.backtrace_cleaner = ::Rails.backtrace_cleaner }
  69. end
  70. initializer "active_record.migration_error" do |app|
  71. if config.active_record.delete(:migration_error) == :page_load
  72. config.app_middleware.insert_after ::ActionDispatch::Callbacks,
  73. ActiveRecord::Migration::CheckPending,
  74. file_watcher: app.config.file_watcher
  75. end
  76. end
  77. initializer "active_record.database_selector" do
  78. if options = config.active_record.delete(:database_selector)
  79. resolver = config.active_record.delete(:database_resolver)
  80. operations = config.active_record.delete(:database_resolver_context)
  81. config.app_middleware.use ActiveRecord::Middleware::DatabaseSelector, resolver, operations, options
  82. end
  83. end
  84. initializer "Check for cache versioning support" do
  85. config.after_initialize do |app|
  86. ActiveSupport.on_load(:active_record) do
  87. if app.config.active_record.cache_versioning && Rails.cache
  88. unless Rails.cache.class.try(:supports_cache_versioning?)
  89. raise <<-end_error
  90. You're using a cache store that doesn't support native cache versioning.
  91. Your best option is to upgrade to a newer version of #{Rails.cache.class}
  92. that supports cache versioning (#{Rails.cache.class}.supports_cache_versioning? #=> true).
  93. Next best, switch to a different cache store that does support cache versioning:
  94. https://guides.rubyonrails.org/caching_with_rails.html#cache-stores.
  95. To keep using the current cache store, you can turn off cache versioning entirely:
  96. config.active_record.cache_versioning = false
  97. end_error
  98. end
  99. end
  100. end
  101. end
  102. end
  103. initializer "active_record.check_schema_cache_dump" do
  104. if config.active_record.delete(:use_schema_cache_dump)
  105. config.after_initialize do |app|
  106. ActiveSupport.on_load(:active_record) do
  107. db_config = ActiveRecord::Base.configurations.configs_for(env_name: Rails.env).first
  108. filename = ActiveRecord::Tasks::DatabaseTasks.cache_dump_filename(
  109. db_config.name,
  110. schema_cache_path: db_config&.schema_cache_path
  111. )
  112. cache = ActiveRecord::ConnectionAdapters::SchemaCache.load_from(filename)
  113. next if cache.nil?
  114. current_version = ActiveRecord::Migrator.current_version
  115. next if current_version.nil?
  116. if cache.version != current_version
  117. warn "Ignoring #{filename} because it has expired. The current schema version is #{current_version}, but the one in the cache is #{cache.version}."
  118. next
  119. end
  120. connection_pool.set_schema_cache(cache.dup)
  121. end
  122. end
  123. end
  124. end
  125. initializer "active_record.define_attribute_methods" do |app|
  126. config.after_initialize do
  127. ActiveSupport.on_load(:active_record) do
  128. if app.config.eager_load
  129. descendants.each do |model|
  130. # SchemaMigration and InternalMetadata both override `table_exists?`
  131. # to bypass the schema cache, so skip them to avoid the extra queries.
  132. next if model._internal?
  133. # If there's no connection yet, or the schema cache doesn't have the columns
  134. # hash for the model cached, `define_attribute_methods` would trigger a query.
  135. next unless model.connected? && model.connection.schema_cache.columns_hash?(model.table_name)
  136. model.define_attribute_methods
  137. end
  138. end
  139. end
  140. end
  141. end
  142. initializer "active_record.warn_on_records_fetched_greater_than" do
  143. if config.active_record.warn_on_records_fetched_greater_than
  144. ActiveSupport.on_load(:active_record) do
  145. require "active_record/relation/record_fetch_warning"
  146. end
  147. end
  148. end
  149. initializer "active_record.set_configs" do |app|
  150. ActiveSupport.on_load(:active_record) do
  151. configs = app.config.active_record
  152. represent_boolean_as_integer = configs.sqlite3.delete(:represent_boolean_as_integer)
  153. unless represent_boolean_as_integer.nil?
  154. ActiveSupport.on_load(:active_record_sqlite3adapter) do
  155. ActiveRecord::ConnectionAdapters::SQLite3Adapter.represent_boolean_as_integer = represent_boolean_as_integer
  156. end
  157. end
  158. configs.delete(:sqlite3)
  159. configs.each do |k, v|
  160. send "#{k}=", v
  161. end
  162. end
  163. end
  164. # This sets the database configuration from Configuration#database_configuration
  165. # and then establishes the connection.
  166. initializer "active_record.initialize_database" do
  167. ActiveSupport.on_load(:active_record) do
  168. self.connection_handlers = { writing_role => ActiveRecord::Base.default_connection_handler }
  169. self.configurations = Rails.application.config.database_configuration
  170. establish_connection
  171. end
  172. end
  173. # Expose database runtime to controller for logging.
  174. initializer "active_record.log_runtime" do
  175. require "active_record/railties/controller_runtime"
  176. ActiveSupport.on_load(:action_controller) do
  177. include ActiveRecord::Railties::ControllerRuntime
  178. end
  179. end
  180. initializer "active_record.set_reloader_hooks" do
  181. ActiveSupport.on_load(:active_record) do
  182. ActiveSupport::Reloader.before_class_unload do
  183. if ActiveRecord::Base.connected?
  184. ActiveRecord::Base.clear_cache!
  185. ActiveRecord::Base.clear_reloadable_connections!
  186. end
  187. end
  188. end
  189. end
  190. initializer "active_record.set_executor_hooks" do
  191. ActiveRecord::QueryCache.install_executor_hooks
  192. end
  193. initializer "active_record.add_watchable_files" do |app|
  194. path = app.paths["db"].first
  195. config.watchable_files.concat ["#{path}/schema.rb", "#{path}/structure.sql"]
  196. end
  197. initializer "active_record.clear_active_connections" do
  198. config.after_initialize do
  199. ActiveSupport.on_load(:active_record) do
  200. # Ideally the application doesn't connect to the database during boot,
  201. # but sometimes it does. In case it did, we want to empty out the
  202. # connection pools so that a non-database-using process (e.g. a master
  203. # process in a forking server model) doesn't retain a needless
  204. # connection. If it was needed, the incremental cost of reestablishing
  205. # this connection is trivial: the rest of the pool would need to be
  206. # populated anyway.
  207. clear_active_connections!
  208. flush_idle_connections!
  209. end
  210. end
  211. end
  212. initializer "active_record.set_filter_attributes" do
  213. ActiveSupport.on_load(:active_record) do
  214. self.filter_attributes += Rails.application.config.filter_parameters
  215. end
  216. end
  217. initializer "active_record.set_signed_id_verifier_secret" do
  218. ActiveSupport.on_load(:active_record) do
  219. self.signed_id_verifier_secret ||= -> { Rails.application.key_generator.generate_key("active_record/signed_id") }
  220. end
  221. end
  222. end
  223. end

lib/active_record/railties/console_sandbox.rb

0.0% lines covered

4 relevant lines. 0 lines covered and 4 lines missed.
    
  1. # frozen_string_literal: true
  2. ActiveRecord::Base.connection.begin_transaction(joinable: false)
  3. at_exit do
  4. ActiveRecord::Base.connection.rollback_transaction
  5. end

lib/active_record/railties/controller_runtime.rb

0.0% lines covered

40 relevant lines. 0 lines covered and 40 lines missed.
    
  1. # frozen_string_literal: true
  2. require "active_support/core_ext/module/attr_internal"
  3. require "active_record/log_subscriber"
  4. module ActiveRecord
  5. module Railties # :nodoc:
  6. module ControllerRuntime #:nodoc:
  7. extend ActiveSupport::Concern
  8. module ClassMethods # :nodoc:
  9. def log_process_action(payload)
  10. messages, db_runtime = super, payload[:db_runtime]
  11. messages << ("ActiveRecord: %.1fms" % db_runtime.to_f) if db_runtime
  12. messages
  13. end
  14. end
  15. private
  16. attr_internal :db_runtime
  17. def process_action(action, *args)
  18. # We also need to reset the runtime before each action
  19. # because of queries in middleware or in cases we are streaming
  20. # and it won't be cleaned up by the method below.
  21. ActiveRecord::LogSubscriber.reset_runtime
  22. super
  23. end
  24. def cleanup_view_runtime
  25. if logger && logger.info? && ActiveRecord::Base.connected?
  26. db_rt_before_render = ActiveRecord::LogSubscriber.reset_runtime
  27. self.db_runtime = (db_runtime || 0) + db_rt_before_render
  28. runtime = super
  29. db_rt_after_render = ActiveRecord::LogSubscriber.reset_runtime
  30. self.db_runtime += db_rt_after_render
  31. runtime - db_rt_after_render
  32. else
  33. super
  34. end
  35. end
  36. def append_info_to_payload(payload)
  37. super
  38. if ActiveRecord::Base.connected?
  39. payload[:db_runtime] = (db_runtime || 0) + ActiveRecord::LogSubscriber.reset_runtime
  40. end
  41. end
  42. end
  43. end
  44. end

lib/active_record/readonly_attributes.rb

100.0% lines covered

12 relevant lines. 12 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 module ActiveRecord
  3. 3 module ReadonlyAttributes
  4. 3 extend ActiveSupport::Concern
  5. 3 included do
  6. 3 class_attribute :_attr_readonly, instance_accessor: false, default: []
  7. end
  8. 3 module ClassMethods
  9. # Attributes listed as readonly will be used to create a new record but update operations will
  10. # ignore these fields.
  11. 3 def attr_readonly(*attributes)
  12. 51 self._attr_readonly = Set.new(attributes.map(&:to_s)) + (_attr_readonly || [])
  13. end
  14. # Returns an array of all the attributes that have been specified as readonly.
  15. 3 def readonly_attributes
  16. 341 _attr_readonly
  17. end
  18. 3 def readonly_attribute?(name) # :nodoc:
  19. 3652 _attr_readonly.include?(name)
  20. end
  21. end
  22. end
  23. end

lib/active_record/reflection.rb

97.72% lines covered

439 relevant lines. 429 lines covered and 10 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 require "active_support/core_ext/string/filters"
  3. 3 module ActiveRecord
  4. # = Active Record Reflection
  5. 3 module Reflection # :nodoc:
  6. 3 extend ActiveSupport::Concern
  7. 3 included do
  8. 6 class_attribute :_reflections, instance_writer: false, default: {}
  9. 6 class_attribute :aggregate_reflections, instance_writer: false, default: {}
  10. end
  11. 3 class << self
  12. 3 def create(macro, name, scope, options, ar)
  13. 3701 reflection = reflection_class_for(macro).new(name, scope, options, ar)
  14. 3698 options[:through] ? ThroughReflection.new(reflection) : reflection
  15. end
  16. 3 def add_reflection(ar, name, reflection)
  17. 3590 ar.clear_reflections_cache
  18. 3590 name = -name.to_s
  19. 3590 ar._reflections = ar._reflections.except(name).merge!(name => reflection)
  20. end
  21. 3 def add_aggregate_reflection(ar, name, reflection)
  22. 27 ar.aggregate_reflections = ar.aggregate_reflections.merge(-name.to_s => reflection)
  23. end
  24. 3 private
  25. 3 def reflection_class_for(macro)
  26. 3701 case macro
  27. when :composed_of
  28. 27 AggregateReflection
  29. when :has_many
  30. 1946 HasManyReflection
  31. when :has_one
  32. 420 HasOneReflection
  33. when :belongs_to
  34. 1308 BelongsToReflection
  35. else
  36. raise "Unsupported Macro: #{macro}"
  37. end
  38. end
  39. end
  40. # \Reflection enables the ability to examine the associations and aggregations of
  41. # Active Record classes and objects. This information, for example,
  42. # can be used in a form builder that takes an Active Record object
  43. # and creates input fields for all of the attributes depending on their type
  44. # and displays the associations to other objects.
  45. #
  46. # MacroReflection class has info for AggregateReflection and AssociationReflection
  47. # classes.
  48. 3 module ClassMethods
  49. # Returns an array of AggregateReflection objects for all the aggregations in the class.
  50. 3 def reflect_on_all_aggregations
  51. 9 aggregate_reflections.values
  52. end
  53. # Returns the AggregateReflection object for the named +aggregation+ (use the symbol).
  54. #
  55. # Account.reflect_on_aggregation(:balance) # => the balance AggregateReflection
  56. #
  57. 3 def reflect_on_aggregation(aggregation)
  58. 48792 aggregate_reflections[aggregation.to_s]
  59. end
  60. # Returns a Hash of name of the reflection as the key and an AssociationReflection as the value.
  61. #
  62. # Account.reflections # => {"balance" => AggregateReflection}
  63. #
  64. 3 def reflections
  65. 4582 @__reflections ||= begin
  66. 582 ref = {}
  67. 582 _reflections.each do |name, reflection|
  68. 3746 parent_reflection = reflection.parent_reflection
  69. 3746 if parent_reflection
  70. 465 parent_name = parent_reflection.name
  71. 465 ref[parent_name.to_s] = parent_reflection
  72. else
  73. 3281 ref[name] = reflection
  74. end
  75. end
  76. 582 ref
  77. end
  78. end
  79. # Returns an array of AssociationReflection objects for all the
  80. # associations in the class. If you only want to reflect on a certain
  81. # association type, pass in the symbol (<tt>:has_many</tt>, <tt>:has_one</tt>,
  82. # <tt>:belongs_to</tt>) as the first parameter.
  83. #
  84. # Example:
  85. #
  86. # Account.reflect_on_all_associations # returns an array of all associations
  87. # Account.reflect_on_all_associations(:has_many) # returns an array of all has_many associations
  88. #
  89. 3 def reflect_on_all_associations(macro = nil)
  90. 1072 association_reflections = reflections.values
  91. 11828 association_reflections.select! { |reflection| reflection.macro == macro } if macro
  92. 1072 association_reflections
  93. end
  94. # Returns the AssociationReflection object for the +association+ (use the symbol).
  95. #
  96. # Account.reflect_on_association(:owner) # returns the owner AssociationReflection
  97. # Invoice.reflect_on_association(:line_items).macro # returns :has_many
  98. #
  99. 3 def reflect_on_association(association)
  100. 333 reflections[association.to_s]
  101. end
  102. 3 def _reflect_on_association(association) #:nodoc:
  103. 682410 _reflections[association.to_s]
  104. end
  105. # Returns an array of AssociationReflection objects for all associations which have <tt>:autosave</tt> enabled.
  106. 3 def reflect_on_all_autosave_associations
  107. 60 reflections.values.select { |reflection| reflection.options[:autosave] }
  108. end
  109. 3 def clear_reflections_cache # :nodoc:
  110. 3605 @__reflections = nil
  111. end
  112. end
  113. # Holds all the methods that are shared between MacroReflection and ThroughReflection.
  114. #
  115. # AbstractReflection
  116. # MacroReflection
  117. # AggregateReflection
  118. # AssociationReflection
  119. # HasManyReflection
  120. # HasOneReflection
  121. # BelongsToReflection
  122. # HasAndBelongsToManyReflection
  123. # ThroughReflection
  124. # PolymorphicReflection
  125. # RuntimeReflection
  126. 3 class AbstractReflection # :nodoc:
  127. 3 def through_reflection?
  128. 9463 false
  129. end
  130. 3 def table_name
  131. 1250 klass.table_name
  132. end
  133. # Returns a new, unsaved instance of the associated class. +attributes+ will
  134. # be passed to the class's constructor.
  135. 3 def build_association(attributes, &block)
  136. 4292 klass.new(attributes, &block)
  137. end
  138. # Returns the class name for the macro.
  139. #
  140. # <tt>composed_of :balance, class_name: 'Money'</tt> returns <tt>'Money'</tt>
  141. # <tt>has_many :clients</tt> returns <tt>'Client'</tt>
  142. 3 def class_name
  143. 3521 @class_name ||= -(options[:class_name]&.to_s || derive_class_name)
  144. end
  145. # Returns a list of scopes that should be applied for this Reflection
  146. # object when querying the database.
  147. 3 def scopes
  148. 13975 scope ? [scope] : []
  149. end
  150. 3 def join_scope(table, foreign_table, foreign_klass)
  151. 3139 predicate_builder = predicate_builder(table)
  152. 3139 scope_chain_items = join_scopes(table, predicate_builder)
  153. 3139 klass_scope = klass_join_scope(table, predicate_builder)
  154. 3139 if type
  155. 198 klass_scope.where!(type => foreign_klass.polymorphic_name)
  156. end
  157. 3139 scope_chain_items.inject(klass_scope, &:merge!)
  158. 3139 primary_key = join_primary_key
  159. 3139 foreign_key = join_foreign_key
  160. 3139 klass_scope.where!(table[primary_key].eq(foreign_table[foreign_key]))
  161. 3139 if klass.finder_needs_type_condition?
  162. 192 klass_scope.where!(klass.send(:type_condition, table))
  163. end
  164. 3139 klass_scope
  165. end
  166. 3 def join_scopes(table, predicate_builder, klass = self.klass) # :nodoc:
  167. 6778 if scope
  168. 753 [scope_for(build_scope(table, predicate_builder, klass))]
  169. else
  170. 6025 []
  171. end
  172. end
  173. 3 def klass_join_scope(table, predicate_builder) # :nodoc:
  174. 3139 relation = build_scope(table, predicate_builder)
  175. 3139 klass.scope_for_association(relation)
  176. end
  177. 3 def constraints
  178. 13975 chain.flat_map(&:scopes)
  179. end
  180. 3 def counter_cache_column
  181. 28634 @counter_cache_column ||= if belongs_to?
  182. 23469 if options[:counter_cache] == true
  183. 24 -"#{active_record.name.demodulize.underscore.pluralize}_count"
  184. 23445 elsif options[:counter_cache]
  185. 33 -options[:counter_cache].to_s
  186. end
  187. else
  188. 520 -(options[:counter_cache]&.to_s || "#{name}_count")
  189. end
  190. end
  191. 3 def inverse_of
  192. 48772 return unless inverse_name
  193. 33941 @inverse_of ||= klass._reflect_on_association inverse_name
  194. end
  195. 3 def check_validity_of_inverse!
  196. 230432 unless polymorphic?
  197. 228679 if has_inverse? && inverse_of.nil?
  198. 9 raise InverseOfAssociationNotFoundError.new(self)
  199. end
  200. end
  201. end
  202. # This shit is nasty. We need to avoid the following situation:
  203. #
  204. # * An associated record is deleted via record.destroy
  205. # * Hence the callbacks run, and they find a belongs_to on the record with a
  206. # :counter_cache options which points back at our owner. So they update the
  207. # counter cache.
  208. # * In which case, we must make sure to *not* update the counter cache, or else
  209. # it will be decremented twice.
  210. #
  211. # Hence this method.
  212. 3 def inverse_which_updates_counter_cache
  213. 5381 return @inverse_which_updates_counter_cache if defined?(@inverse_which_updates_counter_cache)
  214. 631 @inverse_which_updates_counter_cache = klass.reflect_on_all_associations(:belongs_to).find do |inverse|
  215. 1550 inverse.counter_cache_column == counter_cache_column
  216. end
  217. end
  218. 3 alias inverse_updates_counter_cache? inverse_which_updates_counter_cache
  219. 3 def inverse_updates_counter_in_memory?
  220. 2466 inverse_of && inverse_which_updates_counter_cache == inverse_of
  221. end
  222. # Returns whether a counter cache should be used for this association.
  223. #
  224. # The counter_cache option must be given on either the owner or inverse
  225. # association, and the column must be present on the owner.
  226. 3 def has_cached_counter?
  227. 4053 options[:counter_cache] ||
  228. inverse_which_updates_counter_cache && inverse_which_updates_counter_cache.options[:counter_cache] &&
  229. active_record.has_attribute?(counter_cache_column)
  230. end
  231. 3 def counter_must_be_updated_by_has_many?
  232. 2466 !inverse_updates_counter_in_memory? && has_cached_counter?
  233. end
  234. 3 def alias_candidate(name)
  235. 486 "#{plural_name}_#{name}"
  236. end
  237. 3 def chain
  238. 35441 collect_join_chain
  239. end
  240. 3 def build_scope(table, predicate_builder = predicate_builder(table), klass = self.klass)
  241. 6585 Relation.create(
  242. klass,
  243. table: table,
  244. predicate_builder: predicate_builder
  245. )
  246. end
  247. 3 def strict_loading?
  248. 5335 options[:strict_loading]
  249. end
  250. 3 protected
  251. 3 def actual_source_reflection # FIXME: this is a horrible name
  252. 162 self
  253. end
  254. 3 private
  255. 3 def predicate_builder(table)
  256. 5814 PredicateBuilder.new(TableMetadata.new(klass, table))
  257. end
  258. 3 def primary_key(klass)
  259. 22995 klass.primary_key || raise(UnknownPrimaryKey.new(klass))
  260. end
  261. end
  262. # Base class for AggregateReflection and AssociationReflection. Objects of
  263. # AggregateReflection and AssociationReflection are returned by the Reflection::ClassMethods.
  264. 3 class MacroReflection < AbstractReflection
  265. # Returns the name of the macro.
  266. #
  267. # <tt>composed_of :balance, class_name: 'Money'</tt> returns <tt>:balance</tt>
  268. # <tt>has_many :clients</tt> returns <tt>:clients</tt>
  269. 3 attr_reader :name
  270. 3 attr_reader :scope
  271. # Returns the hash of options used for the macro.
  272. #
  273. # <tt>composed_of :balance, class_name: 'Money'</tt> returns <tt>{ class_name: "Money" }</tt>
  274. # <tt>has_many :clients</tt> returns <tt>{}</tt>
  275. 3 attr_reader :options
  276. 3 attr_reader :active_record
  277. 3 attr_reader :plural_name # :nodoc:
  278. 3 def initialize(name, scope, options, active_record)
  279. 3952 @name = name
  280. 3952 @scope = scope
  281. 3952 @options = options
  282. 3952 @active_record = active_record
  283. 3952 @klass = options[:anonymous_class]
  284. 3952 @plural_name = active_record.pluralize_table_names ?
  285. name.to_s.pluralize : name.to_s
  286. end
  287. 3 def autosave=(autosave)
  288. 384 @options[:autosave] = autosave
  289. 384 parent_reflection = self.parent_reflection
  290. 384 if parent_reflection
  291. 66 parent_reflection.autosave = autosave
  292. end
  293. end
  294. # Returns the class for the macro.
  295. #
  296. # <tt>composed_of :balance, class_name: 'Money'</tt> returns the Money class
  297. # <tt>has_many :clients</tt> returns the Client class
  298. #
  299. # class Company < ActiveRecord::Base
  300. # has_many :clients
  301. # end
  302. #
  303. # Company.reflect_on_association(:clients).klass
  304. # # => Client
  305. #
  306. # <b>Note:</b> Do not call +klass.new+ or +klass.create+ to instantiate
  307. # a new association object. Use +build_association+ or +create_association+
  308. # instead. This allows plugins to hook into association object creation.
  309. 3 def klass
  310. 608804 @klass ||= compute_class(class_name)
  311. end
  312. 3 def compute_class(name)
  313. 6 name.constantize
  314. end
  315. # Returns +true+ if +self+ and +other_aggregation+ have the same +name+ attribute, +active_record+ attribute,
  316. # and +other_aggregation+ has an options hash assigned to it.
  317. 3 def ==(other_aggregation)
  318. 2825 super ||
  319. other_aggregation.kind_of?(self.class) &&
  320. name == other_aggregation.name &&
  321. !other_aggregation.options.nil? &&
  322. active_record == other_aggregation.active_record
  323. end
  324. 3 def scope_for(relation, owner = nil)
  325. 2597 relation.instance_exec(owner, &scope) || relation
  326. end
  327. 3 private
  328. 3 def derive_class_name
  329. 3 name.to_s.camelize
  330. end
  331. end
  332. # Holds all the metadata about an aggregation as it was specified in the
  333. # Active Record class.
  334. 3 class AggregateReflection < MacroReflection #:nodoc:
  335. 3 def mapping
  336. 75 mapping = options[:mapping] || [name, name]
  337. 75 mapping.first.is_a?(Array) ? mapping : [mapping]
  338. end
  339. end
  340. # Holds all the metadata about an association as it was specified in the
  341. # Active Record class.
  342. 3 class AssociationReflection < MacroReflection #:nodoc:
  343. 3 def compute_class(name)
  344. 2683 if polymorphic?
  345. 6 raise ArgumentError, "Polymorphic associations do not support computing the class."
  346. end
  347. 2677 active_record.send(:compute_type, name)
  348. end
  349. 3 attr_reader :type, :foreign_type
  350. 3 attr_accessor :parent_reflection # Reflection
  351. 3 def initialize(name, scope, options, active_record)
  352. 3916 super
  353. 3916 @type = -(options[:foreign_type]&.to_s || "#{options[:as]}_type") if options[:as]
  354. 3916 @foreign_type = -(options[:foreign_type]&.to_s || "#{name}_type") if options[:polymorphic]
  355. 3916 @constructable = calculate_constructable(macro, options)
  356. 3916 if options[:class_name] && options[:class_name].class == Class
  357. 3 raise ArgumentError, "A class was passed to `:class_name` but we are expecting a string."
  358. end
  359. end
  360. 3 def association_scope_cache(klass, owner, &block)
  361. 3218 key = self
  362. 3218 if polymorphic?
  363. 141 key = [key, owner._read_attribute(@foreign_type)]
  364. end
  365. 3218 klass.cached_find_by_statement(key, &block)
  366. end
  367. 3 def constructable? # :nodoc:
  368. 1701 @constructable
  369. end
  370. 3 def join_table
  371. 27 @join_table ||= -(options[:join_table]&.to_s || derive_join_table)
  372. end
  373. 3 def foreign_key
  374. 839058 @foreign_key ||= -(options[:foreign_key]&.to_s || derive_foreign_key)
  375. end
  376. 3 def association_foreign_key
  377. @association_foreign_key ||= -(options[:association_foreign_key]&.to_s || class_name.foreign_key)
  378. end
  379. 3 def association_primary_key(klass = nil)
  380. 107 primary_key(klass || self.klass)
  381. end
  382. 3 def active_record_primary_key
  383. 22229 @active_record_primary_key ||= -(options[:primary_key]&.to_s || primary_key(active_record))
  384. end
  385. 3 def join_primary_key(klass = nil)
  386. 26438 foreign_key
  387. end
  388. 3 def join_foreign_key
  389. 21158 active_record_primary_key
  390. end
  391. 3 def check_validity!
  392. 225473 check_validity_of_inverse!
  393. end
  394. 3 def check_preloadable!
  395. 4919 return unless scope
  396. 543 unless scope.arity == 0
  397. 21 raise ArgumentError, <<-MSG.squish
  398. The association scope '#{name}' is instance dependent (the scope
  399. block takes an argument). Preloading instance dependent scopes is
  400. not supported.
  401. MSG
  402. end
  403. end
  404. 3 alias :check_eager_loadable! :check_preloadable!
  405. 3 def join_id_for(owner) # :nodoc:
  406. 3218 owner[join_foreign_key]
  407. end
  408. 3 def through_reflection
  409. nil
  410. end
  411. 3 def source_reflection
  412. 2660 self
  413. end
  414. # A chain of reflections from this one back to the owner. For more see the explanation in
  415. # ThroughReflection.
  416. 3 def collect_join_chain
  417. 26948 [self]
  418. end
  419. # This is for clearing cache on the reflection. Useful for tests that need to compare
  420. # SQL queries on associations.
  421. 3 def clear_association_scope_cache # :nodoc:
  422. 15 klass.initialize_find_by_cache
  423. end
  424. 3 def nested?
  425. 548 false
  426. end
  427. 3 def has_scope?
  428. 5333 scope
  429. end
  430. 3 def has_inverse?
  431. 229603 inverse_name
  432. end
  433. 3 def polymorphic_inverse_of(associated_class)
  434. 870 if has_inverse?
  435. 57 if inverse_relationship = associated_class._reflect_on_association(options[:inverse_of])
  436. 51 inverse_relationship
  437. else
  438. 6 raise InverseOfAssociationNotFoundError.new(self, associated_class)
  439. end
  440. end
  441. end
  442. # Returns the macro type.
  443. #
  444. # <tt>has_many :clients</tt> returns <tt>:has_many</tt>
  445. 3 def macro; raise NotImplementedError; end
  446. # Returns whether or not this association reflection is for a collection
  447. # association. Returns +true+ if the +macro+ is either +has_many+ or
  448. # +has_and_belongs_to_many+, +false+ otherwise.
  449. 3 def collection?
  450. 213434 false
  451. end
  452. # Returns whether or not the association should be validated as part of
  453. # the parent's validation.
  454. #
  455. # Unless you explicitly disable validation with
  456. # <tt>validate: false</tt>, validation will take place when:
  457. #
  458. # * you explicitly enable validation; <tt>validate: true</tt>
  459. # * you use autosave; <tt>autosave: true</tt>
  460. # * the association is a +has_many+ association
  461. 3 def validate?
  462. 4582 !options[:validate].nil? ? options[:validate] : (options[:autosave] == true || collection?)
  463. end
  464. # Returns +true+ if +self+ is a +belongs_to+ reflection.
  465. 118518 def belongs_to?; false; end
  466. # Returns +true+ if +self+ is a +has_one+ reflection.
  467. 6177 def has_one?; false; end
  468. 3 def association_class; raise NotImplementedError; end
  469. 3 def polymorphic?
  470. 479338 options[:polymorphic]
  471. end
  472. 3 VALID_AUTOMATIC_INVERSE_MACROS = [:has_many, :has_one, :belongs_to]
  473. 3 INVALID_AUTOMATIC_INVERSE_OPTIONS = [:through, :foreign_key]
  474. 3 def add_as_source(seed)
  475. 8829 seed
  476. end
  477. 3 def add_as_polymorphic_through(reflection, seed)
  478. 96 seed + [PolymorphicReflection.new(self, reflection)]
  479. end
  480. 3 def add_as_through(seed)
  481. 8718 seed + [self]
  482. end
  483. 3 def extensions
  484. 17755 Array(options[:extend])
  485. end
  486. 3 private
  487. 3 def calculate_constructable(macro, options)
  488. 2188 true
  489. end
  490. # Attempts to find the inverse association name automatically.
  491. # If it cannot find a suitable inverse association name, it returns
  492. # +nil+.
  493. 3 def inverse_name
  494. 278828 unless defined?(@inverse_name)
  495. 4980 @inverse_name = options.fetch(:inverse_of) { automatic_inverse_of }
  496. end
  497. 278828 @inverse_name
  498. end
  499. # returns either +nil+ or the inverse association name that it finds.
  500. 3 def automatic_inverse_of
  501. 2422 if can_find_inverse_of_automatically?(self)
  502. 906 inverse_name = ActiveSupport::Inflector.underscore(options[:as] || active_record.name.demodulize).to_sym
  503. 906 begin
  504. 906 reflection = klass._reflect_on_association(inverse_name)
  505. rescue NameError
  506. # Give up: we couldn't compute the klass type so we won't be able
  507. # to find any associations either.
  508. reflection = false
  509. end
  510. 906 if valid_inverse_reflection?(reflection)
  511. 365 inverse_name
  512. end
  513. end
  514. end
  515. # Checks if the inverse reflection that is returned from the
  516. # +automatic_inverse_of+ method is a valid reflection. We must
  517. # make sure that the reflection's active_record name matches up
  518. # with the current reflection's klass name.
  519. 3 def valid_inverse_reflection?(reflection)
  520. 906 reflection &&
  521. klass <= reflection.active_record &&
  522. can_find_inverse_of_automatically?(reflection)
  523. end
  524. # Checks to see if the reflection doesn't have any options that prevent
  525. # us from being able to guess the inverse automatically. First, the
  526. # <tt>inverse_of</tt> option cannot be set to false. Second, we must
  527. # have <tt>has_many</tt>, <tt>has_one</tt>, <tt>belongs_to</tt> associations.
  528. # Third, we must not have options such as <tt>:foreign_key</tt>
  529. # which prevent us from correctly guessing the inverse association.
  530. #
  531. # Anything with a scope can additionally ruin our attempt at finding an
  532. # inverse, so we exclude reflections with scopes.
  533. 3 def can_find_inverse_of_automatically?(reflection)
  534. 2745 reflection.options[:inverse_of] != false &&
  535. VALID_AUTOMATIC_INVERSE_MACROS.include?(reflection.macro) &&
  536. 4772 !INVALID_AUTOMATIC_INVERSE_OPTIONS.any? { |opt| reflection.options[opt] } &&
  537. !reflection.scope
  538. end
  539. 3 def derive_class_name
  540. 968 class_name = name.to_s
  541. 968 class_name = class_name.singularize if collection?
  542. 968 class_name.camelize
  543. end
  544. 3 def derive_foreign_key
  545. 1423 if belongs_to?
  546. 600 "#{name}_id"
  547. 823 elsif options[:as]
  548. 108 "#{options[:as]}_id"
  549. else
  550. 715 active_record.name.foreign_key
  551. end
  552. end
  553. 3 def derive_join_table
  554. 18 ModelSchema.derive_join_table_name active_record.table_name, klass.table_name
  555. end
  556. end
  557. 3 class HasManyReflection < AssociationReflection # :nodoc:
  558. 1022946 def macro; :has_many; end
  559. 27098 def collection?; true; end
  560. 3 def association_class
  561. 13171 if options[:through]
  562. 4292 Associations::HasManyThroughAssociation
  563. else
  564. 8879 Associations::HasManyAssociation
  565. end
  566. end
  567. end
  568. 3 class HasOneReflection < AssociationReflection # :nodoc:
  569. 85266 def macro; :has_one; end
  570. 1576 def has_one?; true; end
  571. 3 def association_class
  572. 2830 if options[:through]
  573. 303 Associations::HasOneThroughAssociation
  574. else
  575. 2527 Associations::HasOneAssociation
  576. end
  577. end
  578. 3 private
  579. 3 def calculate_constructable(macro, options)
  580. 420 !options[:through]
  581. end
  582. end
  583. 3 class BelongsToReflection < AssociationReflection # :nodoc:
  584. 343710 def macro; :belongs_to; end
  585. 49366 def belongs_to?; true; end
  586. 3 def association_class
  587. 211994 if polymorphic?
  588. 1741 Associations::BelongsToPolymorphicAssociation
  589. else
  590. 210253 Associations::BelongsToAssociation
  591. end
  592. end
  593. # klass option is necessary to support loading polymorphic associations
  594. 3 def association_primary_key(klass = nil)
  595. 21990 if primary_key = options[:primary_key]
  596. 342 @association_primary_key ||= -primary_key.to_s
  597. else
  598. 21648 primary_key(klass || self.klass)
  599. end
  600. end
  601. 3 def join_primary_key(klass = nil)
  602. 10796 polymorphic? ? association_primary_key(klass) : association_primary_key
  603. end
  604. 3 def join_foreign_key
  605. 546504 foreign_key
  606. end
  607. 3 def join_foreign_type
  608. 304 foreign_type
  609. end
  610. 3 private
  611. 3 def can_find_inverse_of_automatically?(_)
  612. 736 !polymorphic? && super
  613. end
  614. 3 def calculate_constructable(macro, options)
  615. 1308 !polymorphic?
  616. end
  617. end
  618. 3 class HasAndBelongsToManyReflection < AssociationReflection # :nodoc:
  619. 1004 def macro; :has_and_belongs_to_many; end
  620. 3 def collection?
  621. 9 true
  622. end
  623. end
  624. # Holds all the metadata about a :through association as it was specified
  625. # in the Active Record class.
  626. 3 class ThroughReflection < AbstractReflection #:nodoc:
  627. 3 delegate :foreign_key, :foreign_type, :association_foreign_key, :join_id_for, :type,
  628. :active_record_primary_key, :join_foreign_key, to: :source_reflection
  629. 3 def initialize(delegate_reflection)
  630. 878 @delegate_reflection = delegate_reflection
  631. 878 @klass = delegate_reflection.options[:anonymous_class]
  632. 878 @source_reflection_name = delegate_reflection.options[:source]
  633. end
  634. 3 def through_reflection?
  635. 228 true
  636. end
  637. 3 def klass
  638. 59602 @klass ||= delegate_reflection.compute_class(class_name).tap do |klass|
  639. 736 if !parent_reflection.is_a?(HasAndBelongsToManyReflection) &&
  640. 528 !(klass.reflections.key?(options[:through].to_s) ||
  641. klass.reflections.key?(options[:through].to_s.pluralize)) &&
  642. active_record.type_for_attribute(active_record.primary_key).type != :integer
  643. 6 raise NotImplementedError, <<~MSG.squish
  644. In order to correctly type cast #{active_record}.#{active_record.primary_key},
  645. #{klass} needs to define a :#{options[:through]} association.
  646. MSG
  647. end
  648. end
  649. end
  650. # Returns the source of the through reflection. It checks both a singularized
  651. # and pluralized form for <tt>:belongs_to</tt> or <tt>:has_many</tt>.
  652. #
  653. # class Post < ActiveRecord::Base
  654. # has_many :taggings
  655. # has_many :tags, through: :taggings
  656. # end
  657. #
  658. # class Tagging < ActiveRecord::Base
  659. # belongs_to :post
  660. # belongs_to :tag
  661. # end
  662. #
  663. # tags_reflection = Post.reflect_on_association(:tags)
  664. # tags_reflection.source_reflection
  665. # # => <ActiveRecord::Reflection::BelongsToReflection: @name=:tag, @active_record=Tagging, @plural_name="tags">
  666. #
  667. 3 def source_reflection
  668. 50608 through_reflection.klass._reflect_on_association(source_reflection_name)
  669. end
  670. # Returns the AssociationReflection object specified in the <tt>:through</tt> option
  671. # of a HasManyThrough or HasOneThrough association.
  672. #
  673. # class Post < ActiveRecord::Base
  674. # has_many :taggings
  675. # has_many :tags, through: :taggings
  676. # end
  677. #
  678. # tags_reflection = Post.reflect_on_association(:tags)
  679. # tags_reflection.through_reflection
  680. # # => <ActiveRecord::Reflection::HasManyReflection: @name=:taggings, @active_record=Post, @plural_name="taggings">
  681. #
  682. 3 def through_reflection
  683. 83901 active_record._reflect_on_association(options[:through])
  684. end
  685. # Returns an array of reflections which are involved in this association. Each item in the
  686. # array corresponds to a table which will be part of the query for this association.
  687. #
  688. # The chain is built by recursively calling #chain on the source reflection and the through
  689. # reflection. The base case for the recursion is a normal association, which just returns
  690. # [self] as its #chain.
  691. #
  692. # class Post < ActiveRecord::Base
  693. # has_many :taggings
  694. # has_many :tags, through: :taggings
  695. # end
  696. #
  697. # tags_reflection = Post.reflect_on_association(:tags)
  698. # tags_reflection.chain
  699. # # => [<ActiveRecord::Reflection::ThroughReflection: @delegate_reflection=#<ActiveRecord::Reflection::HasManyReflection: @name=:tags...>,
  700. # <ActiveRecord::Reflection::HasManyReflection: @name=:taggings, @options={}, @active_record=Post>]
  701. #
  702. 3 def collect_join_chain
  703. 8493 collect_join_reflections [self]
  704. end
  705. # This is for clearing cache on the reflection. Useful for tests that need to compare
  706. # SQL queries on associations.
  707. 3 def clear_association_scope_cache # :nodoc:
  708. 6 delegate_reflection.clear_association_scope_cache
  709. 6 source_reflection.clear_association_scope_cache
  710. 6 through_reflection.clear_association_scope_cache
  711. end
  712. 3 def scopes
  713. source_reflection.scopes + super
  714. end
  715. 3 def join_scopes(table, predicate_builder, klass = self.klass) # :nodoc:
  716. 1187 source_reflection.join_scopes(table, predicate_builder, klass) + super
  717. end
  718. 3 def has_scope?
  719. 1341 scope || options[:source_type] ||
  720. source_reflection.has_scope? ||
  721. through_reflection.has_scope?
  722. end
  723. # A through association is nested if there would be more than one join table
  724. 3 def nested?
  725. 2666 source_reflection.through_reflection? || through_reflection.through_reflection?
  726. end
  727. # We want to use the klass from this reflection, rather than just delegate straight to
  728. # the source_reflection, because the source_reflection may be polymorphic. We still
  729. # need to respect the source_reflection's :primary_key option, though.
  730. 3 def association_primary_key(klass = nil)
  731. # Get the "actual" source reflection if the immediate source reflection has a
  732. # source reflection itself
  733. 162 if primary_key = actual_source_reflection.options[:primary_key]
  734. 27 @association_primary_key ||= -primary_key.to_s
  735. else
  736. 135 primary_key(klass || self.klass)
  737. end
  738. end
  739. 3 def join_primary_key(klass = self.klass)
  740. 3573 source_reflection.join_primary_key(klass)
  741. end
  742. # Gets an array of possible <tt>:through</tt> source reflection names in both singular and plural form.
  743. #
  744. # class Post < ActiveRecord::Base
  745. # has_many :taggings
  746. # has_many :tags, through: :taggings
  747. # end
  748. #
  749. # tags_reflection = Post.reflect_on_association(:tags)
  750. # tags_reflection.source_reflection_names
  751. # # => [:tag, :tags]
  752. #
  753. 3 def source_reflection_names
  754. options[:source] ? [options[:source]] : [name.to_s.singularize, name].uniq
  755. end
  756. 3 def source_reflection_name # :nodoc:
  757. 50608 return @source_reflection_name if @source_reflection_name
  758. 228 names = [name.to_s.singularize, name].collect(&:to_sym).uniq
  759. 228 names = names.find_all { |n|
  760. 405 through_reflection.klass._reflect_on_association(n)
  761. }
  762. 228 if names.length > 1
  763. raise AmbiguousSourceReflectionForThroughAssociation.new(
  764. active_record.name,
  765. macro,
  766. name,
  767. options,
  768. source_reflection_names
  769. )
  770. end
  771. 228 @source_reflection_name = names.first
  772. end
  773. 3 def source_options
  774. source_reflection.options
  775. end
  776. 3 def through_options
  777. through_reflection.options
  778. end
  779. 3 def check_validity!
  780. 4977 if through_reflection.nil?
  781. 3 raise HasManyThroughAssociationNotFoundError.new(active_record, self)
  782. end
  783. 4974 if through_reflection.polymorphic?
  784. 6 if has_one?
  785. 3 raise HasOneAssociationPolymorphicThroughError.new(active_record.name, self)
  786. else
  787. 3 raise HasManyThroughAssociationPolymorphicThroughError.new(active_record.name, self)
  788. end
  789. end
  790. 4968 if source_reflection.nil?
  791. raise HasManyThroughSourceAssociationNotFoundError.new(self)
  792. end
  793. 4968 if options[:source_type] && !source_reflection.polymorphic?
  794. raise HasManyThroughAssociationPointlessSourceTypeError.new(active_record.name, self, source_reflection)
  795. end
  796. 4968 if source_reflection.polymorphic? && options[:source_type].nil?
  797. 3 raise HasManyThroughAssociationPolymorphicSourceError.new(active_record.name, self, source_reflection)
  798. end
  799. 4965 if has_one? && through_reflection.collection?
  800. 3 raise HasOneThroughCantAssociateThroughCollection.new(active_record.name, self, through_reflection)
  801. end
  802. 4962 if parent_reflection.nil?
  803. 2331 reflections = active_record.reflections.keys.map(&:to_sym)
  804. 2331 if reflections.index(through_reflection.name) > reflections.index(name)
  805. 3 raise HasManyThroughOrderError.new(active_record.name, self, through_reflection)
  806. end
  807. end
  808. 4959 check_validity_of_inverse!
  809. end
  810. 3 def constraints
  811. 2950 scope_chain = source_reflection.constraints
  812. 2950 scope_chain << scope if scope
  813. 2950 scope_chain
  814. end
  815. 3 def add_as_source(seed)
  816. 321 collect_join_reflections seed
  817. end
  818. 3 def add_as_polymorphic_through(reflection, seed)
  819. 36 collect_join_reflections(seed + [PolymorphicReflection.new(self, reflection)])
  820. end
  821. 3 def add_as_through(seed)
  822. 300 collect_join_reflections(seed + [self])
  823. end
  824. 3 protected
  825. 3 def actual_source_reflection # FIXME: this is a horrible name
  826. 165 source_reflection.actual_source_reflection
  827. end
  828. 3 private
  829. 3 attr_reader :delegate_reflection
  830. 3 def collect_join_reflections(seed)
  831. 9150 a = source_reflection.add_as_source seed
  832. 9150 if options[:source_type]
  833. 132 through_reflection.add_as_polymorphic_through self, a
  834. else
  835. 9018 through_reflection.add_as_through a
  836. end
  837. end
  838. 961 def inverse_name; delegate_reflection.send(:inverse_name); end
  839. 3 def derive_class_name
  840. # get the class_name of the belongs_to association of the through reflection
  841. 634 options[:source_type] || source_reflection.class_name
  842. end
  843. 3 delegate_methods = AssociationReflection.public_instance_methods -
  844. public_instance_methods
  845. 3 delegate(*delegate_methods, to: :delegate_reflection)
  846. end
  847. 3 class PolymorphicReflection < AbstractReflection # :nodoc:
  848. 3 delegate :klass, :scope, :plural_name, :type, :join_primary_key, :join_foreign_key,
  849. :scope_for, to: :@reflection
  850. 3 def initialize(reflection, previous_reflection)
  851. 132 @reflection = reflection
  852. 132 @previous_reflection = previous_reflection
  853. end
  854. 3 def join_scopes(table, predicate_builder, klass = self.klass) # :nodoc:
  855. 18 scopes = @previous_reflection.join_scopes(table, predicate_builder) + super
  856. 18 scopes << build_scope(table, predicate_builder, klass).instance_exec(nil, &source_type_scope)
  857. end
  858. 3 def constraints
  859. 42 @reflection.constraints + [source_type_scope]
  860. end
  861. 3 private
  862. 3 def source_type_scope
  863. 60 type = @previous_reflection.foreign_type
  864. 60 source_type = @previous_reflection.options[:source_type]
  865. 120 lambda { |object| where(type => source_type) }
  866. end
  867. end
  868. 3 class RuntimeReflection < AbstractReflection # :nodoc:
  869. 3 delegate :scope, :type, :constraints, :join_foreign_key, to: :@reflection
  870. 3 def initialize(reflection, association)
  871. 11025 @reflection = reflection
  872. 11025 @association = association
  873. end
  874. 3 def klass
  875. 29337 @association.klass
  876. end
  877. 3 def aliased_table
  878. 13454 klass.arel_table
  879. end
  880. 3 def join_primary_key(klass = self.klass)
  881. 11025 @reflection.join_primary_key(klass)
  882. end
  883. 2432 def all_includes; yield; end
  884. end
  885. end
  886. end

lib/active_record/relation.rb

99.42% lines covered

347 relevant lines. 345 lines covered and 2 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 module ActiveRecord
  3. # = Active Record \Relation
  4. 3 class Relation
  5. 3 MULTI_VALUE_METHODS = [:includes, :eager_load, :preload, :select, :group,
  6. :order, :joins, :left_outer_joins, :references,
  7. :extending, :unscope, :optimizer_hints, :annotate]
  8. 3 SINGLE_VALUE_METHODS = [:limit, :offset, :lock, :readonly, :reordering, :strict_loading,
  9. :reverse_order, :distinct, :create_with, :skip_query_cache]
  10. 3 CLAUSE_METHODS = [:where, :having, :from]
  11. 3 INVALID_METHODS_FOR_DELETE_ALL = [:distinct, :group, :having]
  12. 3 VALUE_METHODS = MULTI_VALUE_METHODS + SINGLE_VALUE_METHODS + CLAUSE_METHODS
  13. 3 include Enumerable
  14. 3 include FinderMethods, Calculations, SpawnMethods, QueryMethods, Batches, Explain, Delegation
  15. 3 attr_reader :table, :klass, :loaded, :predicate_builder
  16. 3 attr_accessor :skip_preloading_value
  17. 3 alias :model :klass
  18. 3 alias :loaded? :loaded
  19. 3 alias :locked? :lock_value
  20. 3 def initialize(klass, table: klass.arel_table, predicate_builder: klass.predicate_builder, values: {})
  21. 125255 @klass = klass
  22. 125255 @table = table
  23. 125255 @values = values
  24. 125255 @loaded = false
  25. 125255 @predicate_builder = predicate_builder
  26. 125255 @delegate_to_klass = false
  27. end
  28. 3 def initialize_copy(other)
  29. 93533 @values = @values.dup
  30. 93533 reset
  31. end
  32. 3 def arel_attribute(name) # :nodoc:
  33. 3 table[name]
  34. end
  35. 3 deprecate :arel_attribute
  36. 3 def bind_attribute(name, value) # :nodoc:
  37. 523 if reflection = klass._reflect_on_association(name)
  38. 24 name = reflection.foreign_key
  39. 24 value = value.read_attribute(reflection.klass.primary_key) unless value.nil?
  40. end
  41. 523 attr = table[name]
  42. 523 bind = predicate_builder.build_bind_attribute(attr.name, value)
  43. 523 yield attr, bind
  44. end
  45. # Initializes new record from relation while maintaining the current
  46. # scope.
  47. #
  48. # Expects arguments in the same format as {ActiveRecord::Base.new}[rdoc-ref:Core.new].
  49. #
  50. # users = User.where(name: 'DHH')
  51. # user = users.new # => #<User id: nil, name: "DHH", created_at: nil, updated_at: nil>
  52. #
  53. # You can also pass a block to new with the new record as argument:
  54. #
  55. # user = users.new { |user| user.name = 'Oscar' }
  56. # user.name # => Oscar
  57. 3 def new(attributes = nil, &block)
  58. 160 block = _deprecated_scope_block("new", &block)
  59. 320 scoping { klass.new(attributes, &block) }
  60. end
  61. 3 alias build new
  62. # Tries to create a new record with the same scoped attributes
  63. # defined in the relation. Returns the initialized object if validation fails.
  64. #
  65. # Expects arguments in the same format as
  66. # {ActiveRecord::Base.create}[rdoc-ref:Persistence::ClassMethods#create].
  67. #
  68. # ==== Examples
  69. #
  70. # users = User.where(name: 'Oscar')
  71. # users.create # => #<User id: 3, name: "Oscar", ...>
  72. #
  73. # users.create(name: 'fxn')
  74. # users.create # => #<User id: 4, name: "fxn", ...>
  75. #
  76. # users.create { |user| user.name = 'tenderlove' }
  77. # # => #<User id: 5, name: "tenderlove", ...>
  78. #
  79. # users.create(name: nil) # validation on name
  80. # # => #<User id: nil, name: nil, ...>
  81. 3 def create(attributes = nil, &block)
  82. 81 if attributes.is_a?(Array)
  83. 9 attributes.collect { |attr| create(attr, &block) }
  84. else
  85. 78 block = _deprecated_scope_block("create", &block)
  86. 156 scoping { klass.create(attributes, &block) }
  87. end
  88. end
  89. # Similar to #create, but calls
  90. # {create!}[rdoc-ref:Persistence::ClassMethods#create!]
  91. # on the base class. Raises an exception if a validation error occurs.
  92. #
  93. # Expects arguments in the same format as
  94. # {ActiveRecord::Base.create!}[rdoc-ref:Persistence::ClassMethods#create!].
  95. 3 def create!(attributes = nil, &block)
  96. 124 if attributes.is_a?(Array)
  97. 18 attributes.collect { |attr| create!(attr, &block) }
  98. else
  99. 118 block = _deprecated_scope_block("create!", &block)
  100. 236 scoping { klass.create!(attributes, &block) }
  101. end
  102. end
  103. 3 def first_or_create(attributes = nil, &block) # :nodoc:
  104. 33 first || create(attributes, &block)
  105. end
  106. 3 def first_or_create!(attributes = nil, &block) # :nodoc:
  107. 36 first || create!(attributes, &block)
  108. end
  109. 3 def first_or_initialize(attributes = nil, &block) # :nodoc:
  110. 24 first || new(attributes, &block)
  111. end
  112. # Finds the first record with the given attributes, or creates a record
  113. # with the attributes if one is not found:
  114. #
  115. # # Find the first user named "Penélope" or create a new one.
  116. # User.find_or_create_by(first_name: 'Penélope')
  117. # # => #<User id: 1, first_name: "Penélope", last_name: nil>
  118. #
  119. # # Find the first user named "Penélope" or create a new one.
  120. # # We already have one so the existing record will be returned.
  121. # User.find_or_create_by(first_name: 'Penélope')
  122. # # => #<User id: 1, first_name: "Penélope", last_name: nil>
  123. #
  124. # # Find the first user named "Scarlett" or create a new one with
  125. # # a particular last name.
  126. # User.create_with(last_name: 'Johansson').find_or_create_by(first_name: 'Scarlett')
  127. # # => #<User id: 2, first_name: "Scarlett", last_name: "Johansson">
  128. #
  129. # This method accepts a block, which is passed down to #create. The last example
  130. # above can be alternatively written this way:
  131. #
  132. # # Find the first user named "Scarlett" or create a new one with a
  133. # # different last name.
  134. # User.find_or_create_by(first_name: 'Scarlett') do |user|
  135. # user.last_name = 'Johansson'
  136. # end
  137. # # => #<User id: 2, first_name: "Scarlett", last_name: "Johansson">
  138. #
  139. # This method always returns a record, but if creation was attempted and
  140. # failed due to validation errors it won't be persisted, you get what
  141. # #create returns in such situation.
  142. #
  143. # Please note <b>this method is not atomic</b>, it runs first a SELECT, and if
  144. # there are no results an INSERT is attempted. If there are other threads
  145. # or processes there is a race condition between both calls and it could
  146. # be the case that you end up with two similar records.
  147. #
  148. # If this might be a problem for your application, please see #create_or_find_by.
  149. 3 def find_or_create_by(attributes, &block)
  150. 18 find_by(attributes) || create(attributes, &block)
  151. end
  152. # Like #find_or_create_by, but calls
  153. # {create!}[rdoc-ref:Persistence::ClassMethods#create!] so an exception
  154. # is raised if the created record is invalid.
  155. 3 def find_or_create_by!(attributes, &block)
  156. 6 find_by(attributes) || create!(attributes, &block)
  157. end
  158. # Attempts to create a record with the given attributes in a table that has a unique constraint
  159. # on one or several of its columns. If a row already exists with one or several of these
  160. # unique constraints, the exception such an insertion would normally raise is caught,
  161. # and the existing record with those attributes is found using #find_by!.
  162. #
  163. # This is similar to #find_or_create_by, but avoids the problem of stale reads between the SELECT
  164. # and the INSERT, as that method needs to first query the table, then attempt to insert a row
  165. # if none is found.
  166. #
  167. # There are several drawbacks to #create_or_find_by, though:
  168. #
  169. # * The underlying table must have the relevant columns defined with unique constraints.
  170. # * A unique constraint violation may be triggered by only one, or at least less than all,
  171. # of the given attributes. This means that the subsequent #find_by! may fail to find a
  172. # matching record, which will then raise an <tt>ActiveRecord::RecordNotFound</tt> exception,
  173. # rather than a record with the given attributes.
  174. # * While we avoid the race condition between SELECT -> INSERT from #find_or_create_by,
  175. # we actually have another race condition between INSERT -> SELECT, which can be triggered
  176. # if a DELETE between those two statements is run by another client. But for most applications,
  177. # that's a significantly less likely condition to hit.
  178. # * It relies on exception handling to handle control flow, which may be marginally slower.
  179. # * The primary key may auto-increment on each create, even if it fails. This can accelerate
  180. # the problem of running out of integers, if the underlying table is still stuck on a primary
  181. # key of type int (note: All Rails apps since 5.1+ have defaulted to bigint, which is not liable
  182. # to this problem).
  183. #
  184. # This method will return a record if all given attributes are covered by unique constraints
  185. # (unless the INSERT -> DELETE -> SELECT race condition is triggered), but if creation was attempted
  186. # and failed due to validation errors it won't be persisted, you get what #create returns in
  187. # such situation.
  188. 3 def create_or_find_by(attributes, &block)
  189. 36 transaction(requires_new: true) { create(attributes, &block) }
  190. rescue ActiveRecord::RecordNotUnique
  191. 9 find_by!(attributes)
  192. end
  193. # Like #create_or_find_by, but calls
  194. # {create!}[rdoc-ref:Persistence::ClassMethods#create!] so an exception
  195. # is raised if the created record is invalid.
  196. 3 def create_or_find_by!(attributes, &block)
  197. 36 transaction(requires_new: true) { create!(attributes, &block) }
  198. rescue ActiveRecord::RecordNotUnique
  199. 9 find_by!(attributes)
  200. end
  201. # Like #find_or_create_by, but calls {new}[rdoc-ref:Core#new]
  202. # instead of {create}[rdoc-ref:Persistence::ClassMethods#create].
  203. 3 def find_or_initialize_by(attributes, &block)
  204. 269 find_by(attributes) || new(attributes, &block)
  205. end
  206. # Runs EXPLAIN on the query or queries triggered by this relation and
  207. # returns the result as a string. The string is formatted imitating the
  208. # ones printed by the database shell.
  209. #
  210. # Note that this method actually runs the queries, since the results of some
  211. # are needed by the next ones when eager loading is going on.
  212. #
  213. # Please see further details in the
  214. # {Active Record Query Interface guide}[https://guides.rubyonrails.org/active_record_querying.html#running-explain].
  215. 3 def explain
  216. 24 exec_explain(collecting_queries_for_explain { exec_queries })
  217. end
  218. # Converts relation objects to Array.
  219. 3 def to_ary
  220. 8131 records.dup
  221. end
  222. 3 alias to_a to_ary
  223. 3 def records # :nodoc:
  224. 24779 load
  225. 24699 @records
  226. end
  227. # Serializes the relation objects Array.
  228. 3 def encode_with(coder)
  229. 6 coder.represent_seq(nil, records)
  230. end
  231. # Returns size of the records.
  232. 3 def size
  233. 231 loaded? ? @records.length : count(:all)
  234. end
  235. # Returns true if there are no records.
  236. 3 def empty?
  237. 206 return @records.empty? if loaded?
  238. 194 !exists?
  239. end
  240. # Returns true if there are no records.
  241. 3 def none?
  242. 45 return super if block_given?
  243. 21 empty?
  244. end
  245. # Returns true if there are any records.
  246. 3 def any?
  247. 126 return super if block_given?
  248. 69 !empty?
  249. end
  250. # Returns true if there is exactly one record.
  251. 3 def one?
  252. 36 return super if block_given?
  253. 27 limit_value ? records.one? : size == 1
  254. end
  255. # Returns true if there is more than one record.
  256. 3 def many?
  257. 66 return super if block_given?
  258. 54 limit_value ? records.many? : size > 1
  259. end
  260. # Returns a stable cache key that can be used to identify this query.
  261. # The cache key is built with a fingerprint of the SQL query.
  262. #
  263. # Product.where("name like ?", "%Cosmic Encounter%").cache_key
  264. # # => "products/query-1850ab3d302391b85b8693e941286659"
  265. #
  266. # If ActiveRecord::Base.collection_cache_versioning is turned off, as it was
  267. # in Rails 6.0 and earlier, the cache key will also include a version.
  268. #
  269. # ActiveRecord::Base.collection_cache_versioning = false
  270. # Product.where("name like ?", "%Cosmic Encounter%").cache_key
  271. # # => "products/query-1850ab3d302391b85b8693e941286659-1-20150714212553907087000"
  272. #
  273. # You can also pass a custom timestamp column to fetch the timestamp of the
  274. # last updated record.
  275. #
  276. # Product.where("name like ?", "%Game%").cache_key(:last_reviewed_at)
  277. 3 def cache_key(timestamp_column = "updated_at")
  278. 93 @cache_keys ||= {}
  279. 93 @cache_keys[timestamp_column] ||= klass.collection_cache_key(self, timestamp_column)
  280. end
  281. 3 def compute_cache_key(timestamp_column = :updated_at) # :nodoc:
  282. 75 query_signature = ActiveSupport::Digest.hexdigest(to_sql)
  283. 75 key = "#{klass.model_name.cache_key}/query-#{query_signature}"
  284. 75 if collection_cache_versioning
  285. 9 key
  286. else
  287. 66 "#{key}-#{compute_cache_version(timestamp_column)}"
  288. end
  289. end
  290. 3 private :compute_cache_key
  291. # Returns a cache version that can be used together with the cache key to form
  292. # a recyclable caching scheme. The cache version is built with the number of records
  293. # matching the query, and the timestamp of the last updated record. When a new record
  294. # comes to match the query, or any of the existing records is updated or deleted,
  295. # the cache version changes.
  296. #
  297. # If the collection is loaded, the method will iterate through the records
  298. # to generate the timestamp, otherwise it will trigger one SQL query like:
  299. #
  300. # SELECT COUNT(*), MAX("products"."updated_at") FROM "products" WHERE (name like '%Cosmic Encounter%')
  301. 3 def cache_version(timestamp_column = :updated_at)
  302. 12 if collection_cache_versioning
  303. 9 @cache_versions ||= {}
  304. 9 @cache_versions[timestamp_column] ||= compute_cache_version(timestamp_column)
  305. end
  306. end
  307. 3 def compute_cache_version(timestamp_column) # :nodoc:
  308. 72 timestamp_column = timestamp_column.to_s
  309. 72 if loaded? || distinct_value
  310. 18 size = records.size
  311. 18 if size > 0
  312. 81 timestamp = records.map { |record| record.read_attribute(timestamp_column) }.max
  313. end
  314. else
  315. 54 collection = eager_loading? ? apply_join_dependency : self
  316. 54 column = connection.visitor.compile(table[timestamp_column])
  317. 54 select_values = "COUNT(*) AS #{connection.quote_column_name("size")}, MAX(%s) AS timestamp"
  318. 54 if collection.has_limit_or_offset?
  319. 12 query = collection.select("#{column} AS collection_cache_key_timestamp")
  320. 12 subquery_alias = "subquery_for_cache_key"
  321. 12 subquery_column = "#{subquery_alias}.collection_cache_key_timestamp"
  322. 12 arel = query.build_subquery(subquery_alias, select_values % subquery_column)
  323. else
  324. 42 query = collection.unscope(:order)
  325. 42 query.select_values = [select_values % column]
  326. 42 arel = query.arel
  327. end
  328. 54 size, timestamp = connection.select_rows(arel, nil).first
  329. 51 if size
  330. 51 column_type = klass.type_for_attribute(timestamp_column)
  331. 51 timestamp = column_type.deserialize(timestamp)
  332. else
  333. size = 0
  334. end
  335. end
  336. 69 if timestamp
  337. 60 "#{size}-#{timestamp.utc.to_s(cache_timestamp_format)}"
  338. else
  339. 9 "#{size}"
  340. end
  341. end
  342. 3 private :compute_cache_version
  343. # Returns a cache key along with the version.
  344. 3 def cache_key_with_version
  345. 6 if version = cache_version
  346. 3 "#{cache_key}-#{version}"
  347. else
  348. 3 cache_key
  349. end
  350. end
  351. # Scope all queries to the current scope.
  352. #
  353. # Comment.where(post_id: 1).scoping do
  354. # Comment.first
  355. # end
  356. # # => SELECT "comments".* FROM "comments" WHERE "comments"."post_id" = 1 ORDER BY "comments"."id" ASC LIMIT 1
  357. #
  358. # Please check unscoped if you want to remove all previous scopes (including
  359. # the default_scope) during the execution of a block.
  360. 3 def scoping
  361. 146545 already_in_scope? ? yield : _scoping(self) { yield }
  362. end
  363. 3 def _exec_scope(name, *args, &block) # :nodoc:
  364. 798 @delegate_to_klass = true
  365. 1596 _scoping(_deprecated_spawn(name)) { instance_exec(*args, &block) || self }
  366. ensure
  367. 798 @delegate_to_klass = false
  368. end
  369. # Updates all records in the current relation with details given. This method constructs a single SQL UPDATE
  370. # statement and sends it straight to the database. It does not instantiate the involved models and it does not
  371. # trigger Active Record callbacks or validations. However, values passed to #update_all will still go through
  372. # Active Record's normal type casting and serialization. Returns the number of rows affected.
  373. #
  374. # Note: As Active Record callbacks are not triggered, this method will not automatically update +updated_at+/+updated_on+ columns.
  375. #
  376. # ==== Parameters
  377. #
  378. # * +updates+ - A string, array, or hash representing the SET part of an SQL statement.
  379. #
  380. # ==== Examples
  381. #
  382. # # Update all customers with the given attributes
  383. # Customer.update_all wants_email: true
  384. #
  385. # # Update all books with 'Rails' in their title
  386. # Book.where('title LIKE ?', '%Rails%').update_all(author: 'David')
  387. #
  388. # # Update all books that match conditions, but limit it to 5 ordered by date
  389. # Book.where('title LIKE ?', '%Rails%').order(:created_at).limit(5).update_all(author: 'David')
  390. #
  391. # # Update all invoices and set the number column to its id value.
  392. # Invoice.update_all('number = id')
  393. 3 def update_all(updates)
  394. 1338 raise ArgumentError, "Empty list of attributes to change" if updates.blank?
  395. 1335 if eager_loading?
  396. 3 relation = apply_join_dependency
  397. 3 return relation.update_all(updates)
  398. end
  399. 1332 stmt = Arel::UpdateManager.new
  400. 1332 stmt.table(arel.join_sources.empty? ? table : arel.source)
  401. 1332 stmt.key = table[primary_key]
  402. 1332 stmt.take(arel.limit)
  403. 1332 stmt.offset(arel.offset)
  404. 1332 stmt.order(*arel.orders)
  405. 1332 stmt.wheres = arel.constraints
  406. 1332 if updates.is_a?(Hash)
  407. 1266 if klass.locking_enabled? &&
  408. !updates.key?(klass.locking_column) &&
  409. !updates.key?(klass.locking_column.to_sym)
  410. 54 attr = table[klass.locking_column]
  411. 54 updates[attr.name] = _increment_attribute(attr)
  412. end
  413. 1266 stmt.set _substitute_values(updates)
  414. else
  415. 66 stmt.set Arel.sql(klass.sanitize_sql_for_assignment(updates, table.name))
  416. end
  417. 1332 @klass.connection.update stmt, "#{@klass} Update All"
  418. end
  419. 3 def update(id = :all, attributes) # :nodoc:
  420. 9 if id == :all
  421. 9 each { |record| record.update(attributes) }
  422. else
  423. 6 klass.update(id, attributes)
  424. end
  425. end
  426. # Updates the counters of the records in the current relation.
  427. #
  428. # ==== Parameters
  429. #
  430. # * +counter+ - A Hash containing the names of the fields to update as keys and the amount to update as values.
  431. # * <tt>:touch</tt> option - Touch the timestamp columns when updating.
  432. # * If attributes names are passed, they are updated along with update_at/on attributes.
  433. #
  434. # ==== Examples
  435. #
  436. # # For Posts by a given author increment the comment_count by 1.
  437. # Post.where(author_id: author.id).update_counters(comment_count: 1)
  438. 3 def update_counters(counters)
  439. 923 touch = counters.delete(:touch)
  440. 923 updates = {}
  441. 923 counters.each do |counter_name, value|
  442. 995 attr = table[counter_name]
  443. 995 updates[attr.name] = _increment_attribute(attr, value)
  444. end
  445. 923 if touch
  446. 114 names = touch if touch != true
  447. 114 names = Array.wrap(names)
  448. 114 options = names.extract_options!
  449. 114 touch_updates = klass.touch_attributes_with_time(*names, **options)
  450. 114 updates.merge!(touch_updates) unless touch_updates.empty?
  451. end
  452. 923 update_all updates
  453. end
  454. # Touches all records in the current relation, setting the +updated_at+/+updated_on+ attributes to the current time or the time specified.
  455. # It does not instantiate the involved models, and it does not trigger Active Record callbacks or validations.
  456. # This method can be passed attribute names and an optional time argument.
  457. # If attribute names are passed, they are updated along with +updated_at+/+updated_on+ attributes.
  458. # If no time argument is passed, the current time is used as default.
  459. #
  460. # === Examples
  461. #
  462. # # Touch all records
  463. # Person.all.touch_all
  464. # # => "UPDATE \"people\" SET \"updated_at\" = '2018-01-04 22:55:23.132670'"
  465. #
  466. # # Touch multiple records with a custom attribute
  467. # Person.all.touch_all(:created_at)
  468. # # => "UPDATE \"people\" SET \"updated_at\" = '2018-01-04 22:55:23.132670', \"created_at\" = '2018-01-04 22:55:23.132670'"
  469. #
  470. # # Touch multiple records with a specified time
  471. # Person.all.touch_all(time: Time.new(2020, 5, 16, 0, 0, 0))
  472. # # => "UPDATE \"people\" SET \"updated_at\" = '2020-05-16 00:00:00'"
  473. #
  474. # # Touch records with scope
  475. # Person.where(name: 'David').touch_all
  476. # # => "UPDATE \"people\" SET \"updated_at\" = '2018-01-04 22:55:23.132670' WHERE \"people\".\"name\" = 'David'"
  477. 3 def touch_all(*names, time: nil)
  478. 15 update_all klass.touch_attributes_with_time(*names, time: time)
  479. end
  480. # Destroys the records by instantiating each
  481. # record and calling its {#destroy}[rdoc-ref:Persistence#destroy] method.
  482. # Each object's callbacks are executed (including <tt>:dependent</tt> association options).
  483. # Returns the collection of objects that were destroyed; each will be frozen, to
  484. # reflect that no changes should be made (since they can't be persisted).
  485. #
  486. # Note: Instantiation, callback execution, and deletion of each
  487. # record can be time consuming when you're removing many records at
  488. # once. It generates at least one SQL +DELETE+ query per record (or
  489. # possibly more, to enforce your callbacks). If you want to delete many
  490. # rows quickly, without concern for their associations or callbacks, use
  491. # #delete_all instead.
  492. #
  493. # ==== Examples
  494. #
  495. # Person.where(age: 0..18).destroy_all
  496. 3 def destroy_all
  497. 130 records.each(&:destroy).tap { reset }
  498. end
  499. # Deletes the records without instantiating the records
  500. # first, and hence not calling the {#destroy}[rdoc-ref:Persistence#destroy]
  501. # method nor invoking callbacks.
  502. # This is a single SQL DELETE statement that goes straight to the database, much more
  503. # efficient than #destroy_all. Be careful with relations though, in particular
  504. # <tt>:dependent</tt> rules defined on associations are not honored. Returns the
  505. # number of rows affected.
  506. #
  507. # Post.where(person_id: 5).where(category: ['Something', 'Else']).delete_all
  508. #
  509. # Both calls delete the affected posts all at once with a single DELETE statement.
  510. # If you need to destroy dependent associations or call your <tt>before_*</tt> or
  511. # +after_destroy+ callbacks, use the #destroy_all method instead.
  512. #
  513. # If an invalid method is supplied, #delete_all raises an ActiveRecordError:
  514. #
  515. # Post.distinct.delete_all
  516. # # => ActiveRecord::ActiveRecordError: delete_all doesn't support distinct
  517. 3 def delete_all
  518. 2131 invalid_methods = INVALID_METHODS_FOR_DELETE_ALL.select do |method|
  519. 6393 value = @values[method]
  520. 6393 method == :distinct ? value : value&.any?
  521. end
  522. 2131 if invalid_methods.any?
  523. 9 raise ActiveRecordError.new("delete_all doesn't support #{invalid_methods.join(', ')}")
  524. end
  525. 2122 if eager_loading?
  526. 3 relation = apply_join_dependency
  527. 3 return relation.delete_all
  528. end
  529. 2119 stmt = Arel::DeleteManager.new
  530. 2119 stmt.from(arel.join_sources.empty? ? table : arel.source)
  531. 2119 stmt.key = table[primary_key]
  532. 2119 stmt.take(arel.limit)
  533. 2119 stmt.offset(arel.offset)
  534. 2119 stmt.order(*arel.orders)
  535. 2119 stmt.wheres = arel.constraints
  536. 2119 affected = @klass.connection.delete(stmt, "#{@klass} Destroy")
  537. 2068 reset
  538. 2068 affected
  539. end
  540. # Finds and destroys all records matching the specified conditions.
  541. # This is short-hand for <tt>relation.where(condition).destroy_all</tt>.
  542. # Returns the collection of objects that were destroyed.
  543. #
  544. # If no record is found, returns empty array.
  545. #
  546. # Person.destroy_by(id: 13)
  547. # Person.destroy_by(name: 'Spartacus', rating: 4)
  548. # Person.destroy_by("published_at < ?", 2.weeks.ago)
  549. 3 def destroy_by(*args)
  550. 6 where(*args).destroy_all
  551. end
  552. # Finds and deletes all records matching the specified conditions.
  553. # This is short-hand for <tt>relation.where(condition).delete_all</tt>.
  554. # Returns the number of rows affected.
  555. #
  556. # If no record is found, returns <tt>0</tt> as zero rows were affected.
  557. #
  558. # Person.delete_by(id: 13)
  559. # Person.delete_by(name: 'Spartacus', rating: 4)
  560. # Person.delete_by("published_at < ?", 2.weeks.ago)
  561. 3 def delete_by(*args)
  562. 400 where(*args).delete_all
  563. end
  564. # Causes the records to be loaded from the database if they have not
  565. # been loaded already. You can use this if for some reason you need
  566. # to explicitly load some records before actually using them. The
  567. # return value is the relation itself, not the records.
  568. #
  569. # Post.where(published: true).load # => #<ActiveRecord::Relation>
  570. 3 def load(&block)
  571. 26922 unless loaded?
  572. 24226 @records = exec_queries(&block)
  573. 24146 @loaded = true
  574. end
  575. 26842 self
  576. end
  577. # Forces reloading of relation.
  578. 3 def reload
  579. 6 reset
  580. 6 load
  581. end
  582. 3 def reset
  583. 95672 @delegate_to_klass = false
  584. 95672 @_deprecated_scope_source = nil
  585. 95672 @to_sql = @arel = @loaded = @should_eager_load = nil
  586. 95672 @offsets = @take = nil
  587. 95672 @records = [].freeze
  588. 95672 self
  589. end
  590. # Returns sql statement for the relation.
  591. #
  592. # User.where(name: 'Oscar').to_sql
  593. # # => SELECT "users".* FROM "users" WHERE "users"."name" = 'Oscar'
  594. 3 def to_sql
  595. 514 @to_sql ||= begin
  596. 466 if eager_loading?
  597. 9 apply_join_dependency do |relation, join_dependency|
  598. 9 relation = join_dependency.apply_column_aliases(relation)
  599. 9 relation.to_sql
  600. end
  601. else
  602. 457 conn = klass.connection
  603. 914 conn.unprepared_statement { conn.to_sql(arel) }
  604. end
  605. end
  606. end
  607. # Returns a hash of where conditions.
  608. #
  609. # User.where(name: 'Oscar').where_values_hash
  610. # # => {name: "Oscar"}
  611. 3 def where_values_hash(relation_table_name = klass.table_name)
  612. 6731 where_clause.to_h(relation_table_name)
  613. end
  614. 3 def scope_for_create
  615. 5317 hash = where_values_hash
  616. 5317 hash.delete(klass.inheritance_column) if klass.finder_needs_type_condition?
  617. 5452 create_with_value.each { |k, v| hash[k.to_s] = v } unless create_with_value.empty?
  618. 5317 hash
  619. end
  620. # Returns true if relation needs eager loading.
  621. 3 def eager_loading?
  622. 63790 @should_eager_load ||=
  623. eager_load_values.any? ||
  624. 2940 includes_values.any? && (joined_includes_values.any? || references_eager_loaded_tables?)
  625. end
  626. # Joins that are also marked for preloading. In which case we should just eager load them.
  627. # Note that this is a naive implementation because we could have strings and symbols which
  628. # represent the same association, but that aren't matched by this. Also, we could have
  629. # nested hashes which partially match, e.g. { a: :b } & { a: [:b, :c] }
  630. 3 def joined_includes_values
  631. 2940 includes_values & joins_values
  632. end
  633. # Compares two relations for equality.
  634. 3 def ==(other)
  635. 597 case other
  636. when Associations::CollectionProxy, AssociationRelation
  637. 27 self == other.records
  638. when Relation
  639. 48 other.to_sql == to_sql
  640. when Array
  641. 522 records == other
  642. end
  643. end
  644. 3 def pretty_print(q)
  645. q.pp(records)
  646. end
  647. # Returns true if relation is blank.
  648. 3 def blank?
  649. 247 records.blank?
  650. end
  651. 3 def values
  652. 61316 @values.dup
  653. end
  654. 3 def inspect
  655. 18 subject = loaded? ? records : self
  656. 18 entries = subject.take([limit_value, 11].compact.min).map!(&:inspect)
  657. 18 entries[10] = "..." if entries.size == 11
  658. 18 "#<#{self.class.name} [#{entries.join(', ')}]>"
  659. end
  660. 3 def empty_scope? # :nodoc:
  661. 4714 @values == klass.unscoped.values
  662. end
  663. 3 def has_limit_or_offset? # :nodoc:
  664. 4695 limit_value || offset_value
  665. end
  666. 3 def alias_tracker(joins = [], aliases = nil) # :nodoc:
  667. 13139 ActiveRecord::Associations::AliasTracker.create(connection, table.name, joins, aliases)
  668. end
  669. 3 class StrictLoadingScope # :nodoc:
  670. 3 def self.empty_scope?
  671. 3 true
  672. end
  673. 3 def self.strict_loading_value
  674. 3 true
  675. end
  676. end
  677. 3 def preload_associations(records) # :nodoc:
  678. 24122 preload = preload_values
  679. 24122 preload += includes_values unless eager_loading?
  680. 24122 preloader = nil
  681. 24122 scope = strict_loading_value ? StrictLoadingScope : nil
  682. 24122 preload.each do |associations|
  683. 1507 preloader ||= build_preloader
  684. 1507 preloader.preload records, associations, scope
  685. end
  686. end
  687. 3 attr_reader :_deprecated_scope_source # :nodoc:
  688. 3 protected
  689. 3 attr_writer :_deprecated_scope_source # :nodoc:
  690. 3 def load_records(records)
  691. 609 @records = records.freeze
  692. 609 @loaded = true
  693. end
  694. 3 def null_relation? # :nodoc:
  695. 535 is_a?(NullRelation)
  696. end
  697. 3 private
  698. 3 def already_in_scope?
  699. 154054 @delegate_to_klass && begin
  700. 1599 scope = klass.current_scope(true)
  701. 1599 scope && !scope._deprecated_scope_source
  702. end
  703. end
  704. 3 def _deprecated_spawn(name)
  705. 2410 spawn.tap { |scope| scope._deprecated_scope_source = name }
  706. end
  707. 3 def _deprecated_scope_block(name, &block)
  708. 425 -> record do
  709. 407 klass.current_scope = _deprecated_spawn(name)
  710. 407 yield record if block_given?
  711. end
  712. end
  713. 3 def _scoping(scope)
  714. 74069 previous, klass.current_scope = klass.current_scope(true), scope
  715. 74069 yield
  716. ensure
  717. 74069 klass.current_scope = previous
  718. end
  719. 3 def _substitute_values(values)
  720. 1266 values.map do |name, value|
  721. 1623 attr = table[name]
  722. 1623 unless Arel.arel_node?(value)
  723. 574 type = klass.type_for_attribute(attr.name)
  724. 574 value = predicate_builder.build_bind_attribute(attr.name, type.cast(value))
  725. end
  726. 1623 [attr, value]
  727. end
  728. end
  729. 3 def _increment_attribute(attribute, value = 1)
  730. 1049 bind = predicate_builder.build_bind_attribute(attribute.name, value.abs)
  731. 1049 expr = table.coalesce(Arel::Nodes::UnqualifiedColumn.new(attribute), 0)
  732. 1049 expr = value < 0 ? expr - bind : expr + bind
  733. 1049 expr.expr
  734. end
  735. 3 def exec_queries(&block)
  736. 24169 skip_query_cache_if_necessary do
  737. 24169 records =
  738. 24169 if where_clause.contradiction?
  739. 32 []
  740. 24137 elsif eager_loading?
  741. 547 apply_join_dependency do |relation, join_dependency|
  742. 535 if relation.null_relation?
  743. 9 []
  744. else
  745. 526 relation = join_dependency.apply_column_aliases(relation)
  746. 526 rows = connection.select_all(relation.arel, "SQL")
  747. 526 join_dependency.instantiate(rows, strict_loading_value, &block)
  748. 535 end.freeze
  749. end
  750. else
  751. 23590 klass.find_by_sql(arel, &block).freeze
  752. end
  753. 24122 preload_associations(records) unless skip_preloading_value
  754. 24089 records.each(&:readonly!) if readonly_value
  755. 24089 records.each(&:strict_loading!) if strict_loading_value
  756. 24089 records
  757. end
  758. end
  759. 3 def skip_query_cache_if_necessary
  760. 31918 if skip_query_cache_value
  761. 1131 uncached do
  762. 1131 yield
  763. end
  764. else
  765. 30787 yield
  766. end
  767. end
  768. 3 def build_preloader
  769. 1324 ActiveRecord::Associations::Preloader.new
  770. end
  771. 3 def references_eager_loaded_tables?
  772. 2922 joined_tables = arel.join_sources.map do |join|
  773. 678 if join.is_a?(Arel::Nodes::StringJoin)
  774. 30 tables_in_string(join.left)
  775. else
  776. 648 [join.left.table_name, join.left.table_alias]
  777. end
  778. end
  779. 2922 joined_tables += [table.name, table.table_alias]
  780. # always convert table names to downcase as in Oracle quoted table names are in uppercase
  781. 2922 joined_tables = joined_tables.flatten.compact.map(&:downcase).uniq
  782. 2922 (references_values - joined_tables).any?
  783. end
  784. 3 def tables_in_string(string)
  785. 30 return [] if string.blank?
  786. # always convert table names to downcase as in Oracle quoted table names are in uppercase
  787. # ignore raw_sql_ that is used by Oracle adapter as alias for limit/offset subqueries
  788. 30 string.scan(/([a-zA-Z_][.\w]+).?\./).flatten.map(&:downcase).uniq - ["raw_sql_"]
  789. end
  790. end
  791. end

lib/active_record/relation/batches.rb

100.0% lines covered

73 relevant lines. 73 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 require "active_record/relation/batches/batch_enumerator"
  3. 3 module ActiveRecord
  4. 3 module Batches
  5. 3 ORDER_IGNORE_MESSAGE = "Scoped order is ignored, it's forced to be batch order."
  6. # Looping through a collection of records from the database
  7. # (using the Scoping::Named::ClassMethods.all method, for example)
  8. # is very inefficient since it will try to instantiate all the objects at once.
  9. #
  10. # In that case, batch processing methods allow you to work
  11. # with the records in batches, thereby greatly reducing memory consumption.
  12. #
  13. # The #find_each method uses #find_in_batches with a batch size of 1000 (or as
  14. # specified by the +:batch_size+ option).
  15. #
  16. # Person.find_each do |person|
  17. # person.do_awesome_stuff
  18. # end
  19. #
  20. # Person.where("age > 21").find_each do |person|
  21. # person.party_all_night!
  22. # end
  23. #
  24. # If you do not provide a block to #find_each, it will return an Enumerator
  25. # for chaining with other methods:
  26. #
  27. # Person.find_each.with_index do |person, index|
  28. # person.award_trophy(index + 1)
  29. # end
  30. #
  31. # ==== Options
  32. # * <tt>:batch_size</tt> - Specifies the size of the batch. Defaults to 1000.
  33. # * <tt>:start</tt> - Specifies the primary key value to start from, inclusive of the value.
  34. # * <tt>:finish</tt> - Specifies the primary key value to end at, inclusive of the value.
  35. # * <tt>:error_on_ignore</tt> - Overrides the application config to specify if an error should be raised when
  36. # an order is present in the relation.
  37. # * <tt>:order</tt> - Specifies the primary key order (can be :asc or :desc). Defaults to :asc.
  38. #
  39. # Limits are honored, and if present there is no requirement for the batch
  40. # size: it can be less than, equal to, or greater than the limit.
  41. #
  42. # The options +start+ and +finish+ are especially useful if you want
  43. # multiple workers dealing with the same processing queue. You can make
  44. # worker 1 handle all the records between id 1 and 9999 and worker 2
  45. # handle from 10000 and beyond by setting the +:start+ and +:finish+
  46. # option on each worker.
  47. #
  48. # # In worker 1, let's process until 9999 records.
  49. # Person.find_each(finish: 9_999) do |person|
  50. # person.party_all_night!
  51. # end
  52. #
  53. # # In worker 2, let's process from record 10_000 and onwards.
  54. # Person.find_each(start: 10_000) do |person|
  55. # person.party_all_night!
  56. # end
  57. #
  58. # NOTE: Order can be ascending (:asc) or descending (:desc). It is automatically set to
  59. # ascending on the primary key ("id ASC").
  60. # This also means that this method only works when the primary key is
  61. # orderable (e.g. an integer or string).
  62. #
  63. # NOTE: By its nature, batch processing is subject to race conditions if
  64. # other processes are modifying the database.
  65. 3 def find_each(start: nil, finish: nil, batch_size: 1000, error_on_ignore: nil, order: :asc)
  66. 72 if block_given?
  67. 54 find_in_batches(start: start, finish: finish, batch_size: batch_size, error_on_ignore: error_on_ignore, order: order) do |records|
  68. 555 records.each { |record| yield record }
  69. end
  70. else
  71. 18 enum_for(:find_each, start: start, finish: finish, batch_size: batch_size, error_on_ignore: error_on_ignore, order: order) do
  72. 9 relation = self
  73. 9 apply_limits(relation, start, finish, order).size
  74. end
  75. end
  76. end
  77. # Yields each batch of records that was found by the find options as
  78. # an array.
  79. #
  80. # Person.where("age > 21").find_in_batches do |group|
  81. # sleep(50) # Make sure it doesn't get too crowded in there!
  82. # group.each { |person| person.party_all_night! }
  83. # end
  84. #
  85. # If you do not provide a block to #find_in_batches, it will return an Enumerator
  86. # for chaining with other methods:
  87. #
  88. # Person.find_in_batches.with_index do |group, batch|
  89. # puts "Processing group ##{batch}"
  90. # group.each(&:recover_from_last_night!)
  91. # end
  92. #
  93. # To be yielded each record one by one, use #find_each instead.
  94. #
  95. # ==== Options
  96. # * <tt>:batch_size</tt> - Specifies the size of the batch. Defaults to 1000.
  97. # * <tt>:start</tt> - Specifies the primary key value to start from, inclusive of the value.
  98. # * <tt>:finish</tt> - Specifies the primary key value to end at, inclusive of the value.
  99. # * <tt>:error_on_ignore</tt> - Overrides the application config to specify if an error should be raised when
  100. # an order is present in the relation.
  101. # * <tt>:order</tt> - Specifies the primary key order (can be :asc or :desc). Defaults to :asc.
  102. #
  103. # Limits are honored, and if present there is no requirement for the batch
  104. # size: it can be less than, equal to, or greater than the limit.
  105. #
  106. # The options +start+ and +finish+ are especially useful if you want
  107. # multiple workers dealing with the same processing queue. You can make
  108. # worker 1 handle all the records between id 1 and 9999 and worker 2
  109. # handle from 10000 and beyond by setting the +:start+ and +:finish+
  110. # option on each worker.
  111. #
  112. # # Let's process from record 10_000 on.
  113. # Person.find_in_batches(start: 10_000) do |group|
  114. # group.each { |person| person.party_all_night! }
  115. # end
  116. #
  117. # NOTE: Order can be ascending (:asc) or descending (:desc). It is automatically set to
  118. # ascending on the primary key ("id ASC").
  119. # This also means that this method only works when the primary key is
  120. # orderable (e.g. an integer or string).
  121. #
  122. # NOTE: By its nature, batch processing is subject to race conditions if
  123. # other processes are modifying the database.
  124. 3 def find_in_batches(start: nil, finish: nil, batch_size: 1000, error_on_ignore: nil, order: :asc)
  125. 147 relation = self
  126. 147 unless block_given?
  127. return to_enum(:find_in_batches, start: start, finish: finish, batch_size: batch_size, error_on_ignore: error_on_ignore, order: order) do
  128. 15 total = apply_limits(relation, start, finish, order).size
  129. 15 (total - 1).div(batch_size) + 1
  130. 21 end
  131. end
  132. 126 in_batches(of: batch_size, start: start, finish: finish, load: true, error_on_ignore: error_on_ignore, order: order) do |batch|
  133. 387 yield batch.to_a
  134. end
  135. end
  136. # Yields ActiveRecord::Relation objects to work with a batch of records.
  137. #
  138. # Person.where("age > 21").in_batches do |relation|
  139. # relation.delete_all
  140. # sleep(10) # Throttle the delete queries
  141. # end
  142. #
  143. # If you do not provide a block to #in_batches, it will return a
  144. # BatchEnumerator which is enumerable.
  145. #
  146. # Person.in_batches.each_with_index do |relation, batch_index|
  147. # puts "Processing relation ##{batch_index}"
  148. # relation.delete_all
  149. # end
  150. #
  151. # Examples of calling methods on the returned BatchEnumerator object:
  152. #
  153. # Person.in_batches.delete_all
  154. # Person.in_batches.update_all(awesome: true)
  155. # Person.in_batches.each_record(&:party_all_night!)
  156. #
  157. # ==== Options
  158. # * <tt>:of</tt> - Specifies the size of the batch. Defaults to 1000.
  159. # * <tt>:load</tt> - Specifies if the relation should be loaded. Defaults to false.
  160. # * <tt>:start</tt> - Specifies the primary key value to start from, inclusive of the value.
  161. # * <tt>:finish</tt> - Specifies the primary key value to end at, inclusive of the value.
  162. # * <tt>:error_on_ignore</tt> - Overrides the application config to specify if an error should be raised when
  163. # an order is present in the relation.
  164. # * <tt>:order</tt> - Specifies the primary key order (can be :asc or :desc). Defaults to :asc.
  165. #
  166. # Limits are honored, and if present there is no requirement for the batch
  167. # size, it can be less than, equal, or greater than the limit.
  168. #
  169. # The options +start+ and +finish+ are especially useful if you want
  170. # multiple workers dealing with the same processing queue. You can make
  171. # worker 1 handle all the records between id 1 and 9999 and worker 2
  172. # handle from 10000 and beyond by setting the +:start+ and +:finish+
  173. # option on each worker.
  174. #
  175. # # Let's process from record 10_000 on.
  176. # Person.in_batches(start: 10_000).update_all(awesome: true)
  177. #
  178. # An example of calling where query method on the relation:
  179. #
  180. # Person.in_batches.each do |relation|
  181. # relation.update_all('age = age + 1')
  182. # relation.where('age > 21').update_all(should_party: true)
  183. # relation.where('age <= 21').delete_all
  184. # end
  185. #
  186. # NOTE: If you are going to iterate through each record, you should call
  187. # #each_record on the yielded BatchEnumerator:
  188. #
  189. # Person.in_batches.each_record(&:party_all_night!)
  190. #
  191. # NOTE: Order can be ascending (:asc) or descending (:desc). It is automatically set to
  192. # ascending on the primary key ("id ASC").
  193. # This also means that this method only works when the primary key is
  194. # orderable (e.g. an integer or string).
  195. #
  196. # NOTE: By its nature, batch processing is subject to race conditions if
  197. # other processes are modifying the database.
  198. 3 def in_batches(of: 1000, start: nil, finish: nil, load: false, error_on_ignore: nil, order: :asc)
  199. 273 relation = self
  200. 273 unless block_given?
  201. 30 return BatchEnumerator.new(of: of, start: start, finish: finish, relation: self)
  202. end
  203. 243 unless [:asc, :desc].include?(order)
  204. 3 raise ArgumentError, ":order must be :asc or :desc, got #{order.inspect}"
  205. end
  206. 240 if arel.orders.present?
  207. 30 act_on_ignored_order(error_on_ignore)
  208. end
  209. 234 batch_limit = of
  210. 234 if limit_value
  211. 42 remaining = limit_value
  212. 42 batch_limit = remaining if remaining < batch_limit
  213. end
  214. 234 relation = relation.reorder(batch_order(order)).limit(batch_limit)
  215. 234 relation = apply_limits(relation, start, finish, order)
  216. 234 relation.skip_query_cache! # Retaining the results in the query cache would undermine the point of batching
  217. 234 batch_relation = relation
  218. 234 loop do
  219. 1083 if load
  220. 609 records = batch_relation.records
  221. 609 ids = records.map(&:id)
  222. 609 yielded_relation = where(primary_key => ids)
  223. 609 yielded_relation.load_records(records)
  224. else
  225. 474 ids = batch_relation.pluck(primary_key)
  226. 474 yielded_relation = where(primary_key => ids)
  227. end
  228. 1083 break if ids.empty?
  229. 990 primary_key_offset = ids.last
  230. 990 raise ArgumentError.new("Primary key not included in the custom select clause") unless primary_key_offset
  231. 987 yield yielded_relation
  232. 978 break if ids.length < batch_limit
  233. 873 if limit_value
  234. 102 remaining -= ids.length
  235. 102 if remaining == 0
  236. # Saves a useless iteration when the limit is a multiple of the
  237. # batch size.
  238. 24 break
  239. 78 elsif remaining < batch_limit
  240. 6 relation = relation.limit(remaining)
  241. end
  242. end
  243. 849 batch_relation = relation.where(
  244. 849 predicate_builder[primary_key, primary_key_offset, order == :desc ? :lt : :gt]
  245. )
  246. end
  247. end
  248. 3 private
  249. 3 def apply_limits(relation, start, finish, order)
  250. 258 relation = apply_start_limit(relation, start, order) if start
  251. 258 relation = apply_finish_limit(relation, finish, order) if finish
  252. 258 relation
  253. end
  254. 3 def apply_start_limit(relation, start, order)
  255. 27 relation.where(predicate_builder[primary_key, start, order == :desc ? :lteq : :gteq])
  256. end
  257. 3 def apply_finish_limit(relation, finish, order)
  258. 15 relation.where(predicate_builder[primary_key, finish, order == :desc ? :gteq : :lteq])
  259. end
  260. 3 def batch_order(order)
  261. 234 table[primary_key].public_send(order)
  262. end
  263. 3 def act_on_ignored_order(error_on_ignore)
  264. 30 raise_error = (error_on_ignore.nil? ? klass.error_on_ignored_order : error_on_ignore)
  265. 30 if raise_error
  266. 6 raise ArgumentError.new(ORDER_IGNORE_MESSAGE)
  267. 24 elsif logger
  268. 21 logger.warn(ORDER_IGNORE_MESSAGE)
  269. end
  270. end
  271. end
  272. end

lib/active_record/relation/batches/batch_enumerator.rb

95.24% lines covered

21 relevant lines. 20 lines covered and 1 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 module ActiveRecord
  3. 3 module Batches
  4. 3 class BatchEnumerator
  5. 3 include Enumerable
  6. 3 def initialize(of: 1000, start: nil, finish: nil, relation:) #:nodoc:
  7. 30 @of = of
  8. 30 @relation = relation
  9. 30 @start = start
  10. 30 @finish = finish
  11. end
  12. # Looping through a collection of records from the database (using the
  13. # +all+ method, for example) is very inefficient since it will try to
  14. # instantiate all the objects at once.
  15. #
  16. # In that case, batch processing methods allow you to work with the
  17. # records in batches, thereby greatly reducing memory consumption.
  18. #
  19. # Person.in_batches.each_record do |person|
  20. # person.do_awesome_stuff
  21. # end
  22. #
  23. # Person.where("age > 21").in_batches(of: 10).each_record do |person|
  24. # person.party_all_night!
  25. # end
  26. #
  27. # If you do not provide a block to #each_record, it will return an Enumerator
  28. # for chaining with other methods:
  29. #
  30. # Person.in_batches.each_record.with_index do |person, index|
  31. # person.award_trophy(index + 1)
  32. # end
  33. 3 def each_record
  34. 15 return to_enum(:each_record) unless block_given?
  35. 9 @relation.to_enum(:in_batches, of: @of, start: @start, finish: @finish, load: true).each do |relation|
  36. 153 relation.records.each { |record| yield record }
  37. end
  38. end
  39. # Delegates #delete_all, #update_all, #destroy_all methods to each batch.
  40. #
  41. # People.in_batches.delete_all
  42. # People.where('age < 10').in_batches.destroy_all
  43. # People.in_batches.update_all('age = age + 1')
  44. 3 [:delete_all, :update_all, :destroy_all].each do |method|
  45. 9 define_method(method) do |*args, &block|
  46. 6 @relation.to_enum(:in_batches, of: @of, start: @start, finish: @finish, load: false).each do |relation|
  47. 33 relation.send(method, *args, &block)
  48. end
  49. end
  50. end
  51. # Yields an ActiveRecord::Relation object for each batch of records.
  52. #
  53. # Person.in_batches.each do |relation|
  54. # relation.update_all(awesome: true)
  55. # end
  56. 3 def each
  57. 12 enum = @relation.to_enum(:in_batches, of: @of, start: @start, finish: @finish, load: false)
  58. 60 return enum.each { |relation| yield relation } if block_given?
  59. enum
  60. end
  61. end
  62. end
  63. end

lib/active_record/relation/calculations.rb

100.0% lines covered

184 relevant lines. 184 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 require "active_support/core_ext/enumerable"
  3. 3 module ActiveRecord
  4. 3 module Calculations
  5. # Count the records.
  6. #
  7. # Person.count
  8. # # => the total count of all people
  9. #
  10. # Person.count(:age)
  11. # # => returns the total count of all people whose age is present in database
  12. #
  13. # Person.count(:all)
  14. # # => performs a COUNT(*) (:all is an alias for '*')
  15. #
  16. # Person.distinct.count(:age)
  17. # # => counts the number of different age values
  18. #
  19. # If #count is used with {Relation#group}[rdoc-ref:QueryMethods#group],
  20. # it returns a Hash whose keys represent the aggregated column,
  21. # and the values are the respective amounts:
  22. #
  23. # Person.group(:city).count
  24. # # => { 'Rome' => 5, 'Paris' => 3 }
  25. #
  26. # If #count is used with {Relation#group}[rdoc-ref:QueryMethods#group] for multiple columns, it returns a Hash whose
  27. # keys are an array containing the individual values of each column and the value
  28. # of each key would be the #count.
  29. #
  30. # Article.group(:status, :category).count
  31. # # => {["draft", "business"]=>10, ["draft", "technology"]=>4,
  32. # ["published", "business"]=>0, ["published", "technology"]=>2}
  33. #
  34. # If #count is used with {Relation#select}[rdoc-ref:QueryMethods#select], it will count the selected columns:
  35. #
  36. # Person.select(:age).count
  37. # # => counts the number of different age values
  38. #
  39. # Note: not all valid {Relation#select}[rdoc-ref:QueryMethods#select] expressions are valid #count expressions. The specifics differ
  40. # between databases. In invalid cases, an error from the database is thrown.
  41. 3 def count(column_name = nil)
  42. 3966 if block_given?
  43. 9 unless column_name.nil?
  44. 3 raise ArgumentError, "Column name argument is not supported when a block is passed."
  45. end
  46. 6 super()
  47. else
  48. 3957 calculate(:count, column_name)
  49. end
  50. end
  51. # Calculates the average value on a given column. Returns +nil+ if there's
  52. # no row. See #calculate for examples with options.
  53. #
  54. # Person.average(:age) # => 35.8
  55. 3 def average(column_name)
  56. 45 calculate(:average, column_name)
  57. end
  58. # Calculates the minimum value on a given column. The value is returned
  59. # with the same data type of the column, or +nil+ if there's no row. See
  60. # #calculate for examples with options.
  61. #
  62. # Person.minimum(:age) # => 7
  63. 3 def minimum(column_name)
  64. 78 calculate(:minimum, column_name)
  65. end
  66. # Calculates the maximum value on a given column. The value is returned
  67. # with the same data type of the column, or +nil+ if there's no row. See
  68. # #calculate for examples with options.
  69. #
  70. # Person.maximum(:age) # => 93
  71. 3 def maximum(column_name)
  72. 100 calculate(:maximum, column_name)
  73. end
  74. # Calculates the sum of values on a given column. The value is returned
  75. # with the same data type of the column, +0+ if there's no row. See
  76. # #calculate for examples with options.
  77. #
  78. # Person.sum(:age) # => 4562
  79. 3 def sum(column_name = nil)
  80. 157 if block_given?
  81. 6 unless column_name.nil?
  82. 3 raise ArgumentError, "Column name argument is not supported when a block is passed."
  83. end
  84. 3 super()
  85. else
  86. 151 calculate(:sum, column_name)
  87. end
  88. end
  89. # This calculates aggregate values in the given column. Methods for #count, #sum, #average,
  90. # #minimum, and #maximum have been added as shortcuts.
  91. #
  92. # Person.calculate(:count, :all) # The same as Person.count
  93. # Person.average(:age) # SELECT AVG(age) FROM people...
  94. #
  95. # # Selects the minimum age for any family without any minors
  96. # Person.group(:last_name).having("min(age) > 17").minimum(:age)
  97. #
  98. # Person.sum("2 * age")
  99. #
  100. # There are two basic forms of output:
  101. #
  102. # * Single aggregate value: The single value is type cast to Integer for COUNT, Float
  103. # for AVG, and the given column's type for everything else.
  104. #
  105. # * Grouped values: This returns an ordered hash of the values and groups them. It
  106. # takes either a column name, or the name of a belongs_to association.
  107. #
  108. # values = Person.group('last_name').maximum(:age)
  109. # puts values["Drake"]
  110. # # => 43
  111. #
  112. # drake = Family.find_by(last_name: 'Drake')
  113. # values = Person.group(:family).maximum(:age) # Person belongs_to :family
  114. # puts values[drake]
  115. # # => 43
  116. #
  117. # values.each do |family, max_age|
  118. # ...
  119. # end
  120. 3 def calculate(operation, column_name)
  121. 4436 if has_include?(column_name)
  122. 126 relation = apply_join_dependency
  123. 123 if operation.to_s.downcase == "count"
  124. 108 unless distinct_value || distinct_select?(column_name || select_for_count)
  125. 93 relation.distinct!
  126. 93 relation.select_values = [ klass.primary_key || table[Arel.star] ]
  127. end
  128. # PostgreSQL: ORDER BY expressions must appear in SELECT list when using DISTINCT
  129. 108 relation.order_values = [] if group_values.empty?
  130. end
  131. 123 relation.calculate(operation, column_name)
  132. else
  133. 4310 perform_calculation(operation, column_name)
  134. end
  135. end
  136. # Use #pluck as a shortcut to select one or more attributes without
  137. # loading a bunch of records just to grab the attributes you want.
  138. #
  139. # Person.pluck(:name)
  140. #
  141. # instead of
  142. #
  143. # Person.all.map(&:name)
  144. #
  145. # Pluck returns an Array of attribute values type-casted to match
  146. # the plucked column names, if they can be deduced. Plucking an SQL fragment
  147. # returns String values by default.
  148. #
  149. # Person.pluck(:name)
  150. # # SELECT people.name FROM people
  151. # # => ['David', 'Jeremy', 'Jose']
  152. #
  153. # Person.pluck(:id, :name)
  154. # # SELECT people.id, people.name FROM people
  155. # # => [[1, 'David'], [2, 'Jeremy'], [3, 'Jose']]
  156. #
  157. # Person.distinct.pluck(:role)
  158. # # SELECT DISTINCT role FROM people
  159. # # => ['admin', 'member', 'guest']
  160. #
  161. # Person.where(age: 21).limit(5).pluck(:id)
  162. # # SELECT people.id FROM people WHERE people.age = 21 LIMIT 5
  163. # # => [2, 3]
  164. #
  165. # Person.pluck(Arel.sql('DATEDIFF(updated_at, created_at)'))
  166. # # SELECT DATEDIFF(updated_at, created_at) FROM people
  167. # # => ['0', '27761', '173']
  168. #
  169. # See also #ids.
  170. #
  171. 3 def pluck(*column_names)
  172. 2003 if loaded? && all_attributes?(column_names)
  173. 18 return records.pluck(*column_names)
  174. end
  175. 1985 if has_include?(column_names.first)
  176. 57 relation = apply_join_dependency
  177. 57 relation.pluck(*column_names)
  178. else
  179. 1928 klass.disallow_raw_sql!(column_names)
  180. 1919 columns = arel_columns(column_names)
  181. 1919 relation = spawn
  182. 1919 relation.select_values = columns
  183. 1919 result = skip_query_cache_if_necessary do
  184. 1919 if where_clause.contradiction?
  185. 9 ActiveRecord::Result.new([], [])
  186. else
  187. 1910 klass.connection.select_all(relation.arel, nil)
  188. end
  189. end
  190. 1919 type_cast_pluck_values(result, columns)
  191. end
  192. end
  193. # Pick the value(s) from the named column(s) in the current relation.
  194. # This is short-hand for <tt>relation.limit(1).pluck(*column_names).first</tt>, and is primarily useful
  195. # when you have a relation that's already narrowed down to a single row.
  196. #
  197. # Just like #pluck, #pick will only load the actual value, not the entire record object, so it's also
  198. # more efficient. The value is, again like with pluck, typecast by the column type.
  199. #
  200. # Person.where(id: 1).pick(:name)
  201. # # SELECT people.name FROM people WHERE id = 1 LIMIT 1
  202. # # => 'David'
  203. #
  204. # Person.where(id: 1).pick(:name, :email_address)
  205. # # SELECT people.name, people.email_address FROM people WHERE id = 1 LIMIT 1
  206. # # => [ 'David', 'david@loudthinking.com' ]
  207. 3 def pick(*column_names)
  208. 42 if loaded? && all_attributes?(column_names)
  209. 15 return records.pick(*column_names)
  210. end
  211. 27 limit(1).pluck(*column_names).first
  212. end
  213. # Pluck all the ID's for the relation using the table's primary key
  214. #
  215. # Person.ids # SELECT people.id FROM people
  216. # Person.joins(:companies).ids # SELECT people.id FROM people INNER JOIN companies ON companies.person_id = people.id
  217. 3 def ids
  218. 18 pluck primary_key
  219. end
  220. 3 private
  221. 3 def all_attributes?(column_names)
  222. 39 (column_names.map(&:to_s) - @klass.attribute_names - @klass.attribute_aliases.keys).empty?
  223. end
  224. 3 def has_include?(column_name)
  225. 6421 eager_loading? || (includes_values.present? && column_name && column_name != :all)
  226. end
  227. 3 def perform_calculation(operation, column_name)
  228. 4310 operation = operation.to_s.downcase
  229. # If #count is used with #distinct (i.e. `relation.distinct.count`) it is
  230. # considered distinct.
  231. 4310 distinct = distinct_value
  232. 4310 if operation == "count"
  233. 3942 column_name ||= select_for_count
  234. 3942 if column_name == :all
  235. 3480 if !distinct
  236. 3357 distinct = distinct_select?(select_for_count) if group_values.empty?
  237. 123 elsif group_values.any? || select_values.empty? && order_values.empty?
  238. 45 column_name = primary_key
  239. end
  240. 462 elsif distinct_select?(column_name)
  241. 18 distinct = nil
  242. end
  243. end
  244. 4310 if group_values.any?
  245. 259 execute_grouped_calculation(operation, column_name, distinct)
  246. else
  247. 4051 execute_simple_calculation(operation, column_name, distinct)
  248. end
  249. end
  250. 3 def distinct_select?(column_name)
  251. 3848 column_name.is_a?(::String) && /\bDISTINCT[\s(]/i.match?(column_name)
  252. end
  253. 3 def aggregate_column(column_name)
  254. 4055 return column_name if Arel::Expressions === column_name
  255. 4010 arel_column(column_name.to_s) do |name|
  256. 3255 Arel.sql(column_name == :all ? "*" : name)
  257. end
  258. end
  259. 3 def operation_over_aggregate_column(column, operation, distinct)
  260. 4304 operation == "count" ? column.count(distinct) : column.send(operation)
  261. end
  262. 3 def execute_simple_calculation(operation, column_name, distinct) #:nodoc:
  263. 4051 if operation == "count" && (column_name == :all && distinct || has_limit_or_offset?)
  264. # Shortcut when limit is zero.
  265. 267 return 0 if limit_value == 0
  266. 261 query_builder = build_count_subquery(spawn, column_name, distinct)
  267. else
  268. # PostgreSQL doesn't like ORDER BY when there are no GROUP BY
  269. 3784 relation = unscope(:order).distinct!(false)
  270. 3784 column = aggregate_column(column_name)
  271. 3784 select_value = operation_over_aggregate_column(column, operation, distinct)
  272. 3784 select_value.distinct = true if operation == "sum" && distinct
  273. 3784 relation.select_values = [select_value]
  274. 3784 query_builder = relation.arel
  275. end
  276. 8090 result = skip_query_cache_if_necessary { @klass.connection.select_all(query_builder) }
  277. 4042 type_cast_calculated_value(result.cast_values.first, operation) do |value|
  278. 194 type = column.try(:type_caster) ||
  279. lookup_cast_type_from_join_dependencies(column_name.to_s) || Type.default_value
  280. 194 type.deserialize(value)
  281. end
  282. end
  283. 3 def execute_grouped_calculation(operation, column_name, distinct) #:nodoc:
  284. 259 group_fields = group_values
  285. 259 group_fields = group_fields.uniq if group_fields.size > 1
  286. 259 unless group_fields == group_values
  287. 6 ActiveSupport::Deprecation.warn(<<-MSG.squish)
  288. `#{operation}` with group by duplicated fields does no longer affect to result in Rails 6.2.
  289. To migrate to Rails 6.2's behavior, use `uniq!(:group)` to deduplicate group fields
  290. (`#{klass.name&.tableize || klass.table_name}.uniq!(:group).#{operation}(#{column_name.inspect})`).
  291. MSG
  292. 6 group_fields = group_values
  293. end
  294. 259 if group_fields.size == 1 && group_fields.first.respond_to?(:to_sym)
  295. 241 association = klass._reflect_on_association(group_fields.first)
  296. 241 associated = association && association.belongs_to? # only count belongs_to associations
  297. 241 group_fields = Array(association.foreign_key) if associated
  298. end
  299. 259 group_fields = arel_columns(group_fields)
  300. 259 group_aliases = group_fields.map { |field|
  301. 277 field = connection.visitor.compile(field) if Arel.arel_node?(field)
  302. 277 column_alias_for(field.to_s.downcase)
  303. }
  304. 259 group_columns = group_aliases.zip(group_fields)
  305. 259 column = aggregate_column(column_name)
  306. 259 column_alias = column_alias_for("#{operation} #{column_name.to_s.downcase}")
  307. 259 select_value = operation_over_aggregate_column(column, operation, distinct)
  308. 259 select_value.as(column_alias)
  309. 259 select_values = [select_value]
  310. 259 select_values += self.select_values unless having_clause.empty?
  311. 259 select_values.concat group_columns.map { |aliaz, field|
  312. 277 if field.respond_to?(:as)
  313. 237 field.as(aliaz)
  314. else
  315. 40 "#{field} AS #{aliaz}"
  316. end
  317. }
  318. 259 relation = except(:group).distinct!(false)
  319. 259 relation.group_values = group_fields
  320. 259 relation.select_values = select_values
  321. 518 calculated_data = skip_query_cache_if_necessary { @klass.connection.select_all(relation.arel, nil) }
  322. 259 if association
  323. 69 key_ids = calculated_data.collect { |row| row[group_aliases.first] }
  324. 15 key_records = association.klass.base_class.where(association.klass.base_class.primary_key => key_ids)
  325. 15 key_records = key_records.index_by(&:id)
  326. end
  327. 259 key_types = group_columns.each_with_object({}) do |(aliaz, col_name), types|
  328. 277 types[aliaz] = type_for(col_name) do
  329. 37 calculated_data.column_types.fetch(aliaz, Type.default_value)
  330. end
  331. end
  332. 259 hash_rows = calculated_data.cast_values(key_types).map! do |row|
  333. 889 calculated_data.columns.each_with_object({}).with_index do |(col_name, hash), i|
  334. 1875 hash[col_name] = row[i]
  335. end
  336. end
  337. 259 type = nil
  338. 259 hash_rows.each_with_object({}) do |row, result|
  339. 1871 key = group_aliases.map { |aliaz| row[aliaz] }
  340. 889 key = key.first if key.size == 1
  341. 889 key = key_records[key] if associated
  342. 889 result[key] = type_cast_calculated_value(row[column_alias], operation) do |value|
  343. 420 type ||= column.try(:type_caster) ||
  344. lookup_cast_type_from_join_dependencies(column_name.to_s) || Type.default_value
  345. 420 type.deserialize(value)
  346. end
  347. end
  348. end
  349. # Converts the given field to the value that the database adapter returns as
  350. # a usable column name:
  351. #
  352. # column_alias_for("users.id") # => "users_id"
  353. # column_alias_for("sum(id)") # => "sum_id"
  354. # column_alias_for("count(distinct users.id)") # => "count_distinct_users_id"
  355. # column_alias_for("count(*)") # => "count_all"
  356. 3 def column_alias_for(field)
  357. 536 column_alias = +field
  358. 536 column_alias.gsub!(/\*/, "all")
  359. 536 column_alias.gsub!(/\W+/, " ")
  360. 536 column_alias.strip!
  361. 536 column_alias.gsub!(/ +/, "_")
  362. 536 connection.table_alias_for(column_alias)
  363. end
  364. 3 def type_for(field, &block)
  365. 277 field_name = field.respond_to?(:name) ? field.name.to_s : field.to_s.split(".").last
  366. 277 @klass.type_for_attribute(field_name, &block)
  367. end
  368. 3 def lookup_cast_type_from_join_dependencies(name, join_dependencies = build_join_dependencies)
  369. 87 each_join_dependencies(join_dependencies) do |join|
  370. 201 type = join.base_klass.attribute_types.fetch(name, nil)
  371. 201 return type if type
  372. end
  373. nil
  374. end
  375. 3 def type_cast_pluck_values(result, columns)
  376. 1919 cast_types = if result.columns.size != columns.size
  377. 15 klass.attribute_types
  378. else
  379. 1904 join_dependencies = nil
  380. 1904 columns.map.with_index do |column, i|
  381. 1974 column.try(:type_caster) ||
  382. klass.attribute_types.fetch(name = result.columns[i]) do
  383. 45 join_dependencies ||= build_join_dependencies
  384. 45 lookup_cast_type_from_join_dependencies(name, join_dependencies) ||
  385. result.column_types[name] || Type.default_value
  386. end
  387. end
  388. end
  389. 1919 result.cast_values(cast_types)
  390. end
  391. 3 def type_cast_calculated_value(value, operation)
  392. 4931 case operation
  393. when "count"
  394. 4260 value.to_i
  395. when "sum"
  396. 375 yield value || 0
  397. when "average"
  398. 57 value&.respond_to?(:to_d) ? value.to_d : value
  399. else # "minimum", "maximum"
  400. 239 yield value
  401. end
  402. end
  403. 3 def select_for_count
  404. 6346 if select_values.present?
  405. 160 return select_values.first if select_values.one?
  406. 9 select_values.join(", ")
  407. else
  408. 6186 :all
  409. end
  410. end
  411. 3 def build_count_subquery(relation, column_name, distinct)
  412. 261 if column_name == :all
  413. 249 column_alias = Arel.star
  414. 249 relation.select_values = [ Arel.sql(FinderMethods::ONE_AS_ONE) ] unless distinct
  415. else
  416. 12 column_alias = Arel.sql("count_column")
  417. 12 relation.select_values = [ aggregate_column(column_name).as(column_alias) ]
  418. end
  419. 261 subquery_alias = Arel.sql("subquery_for_count")
  420. 261 select_value = operation_over_aggregate_column(column_alias, "count", false)
  421. 261 relation.build_subquery(subquery_alias, select_value)
  422. end
  423. end
  424. end

lib/active_record/relation/delegation.rb

98.51% lines covered

67 relevant lines. 66 lines covered and 1 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 require "mutex_m"
  3. 3 require "active_support/core_ext/module/delegation"
  4. 3 module ActiveRecord
  5. 3 module Delegation # :nodoc:
  6. 3 module DelegateCache # :nodoc:
  7. 3 def relation_delegate_class(klass)
  8. 125087 @relation_delegate_cache[klass]
  9. end
  10. 3 def initialize_relation_delegate_cache
  11. 2946 @relation_delegate_cache = cache = {}
  12. [
  13. ActiveRecord::Relation,
  14. ActiveRecord::Associations::CollectionProxy,
  15. ActiveRecord::AssociationRelation
  16. 2946 ].each do |klass|
  17. 8838 delegate = Class.new(klass) {
  18. 8838 include ClassSpecificRelation
  19. }
  20. 8838 include_relation_methods(delegate)
  21. 8838 mangled_name = klass.name.gsub("::", "_")
  22. 8838 const_set mangled_name, delegate
  23. 8838 private_constant mangled_name
  24. 8838 cache[klass] = delegate
  25. end
  26. end
  27. 3 def inherited(child_class)
  28. 2946 child_class.initialize_relation_delegate_cache
  29. 2946 super
  30. end
  31. 3 def generate_relation_method(method)
  32. 3548 generated_relation_methods.generate_method(method)
  33. end
  34. 3 protected
  35. 3 def include_relation_methods(delegate)
  36. 10929 superclass.include_relation_methods(delegate) unless base_class?
  37. 10929 delegate.include generated_relation_methods
  38. end
  39. 3 private
  40. 3 def generated_relation_methods
  41. 14480 @generated_relation_methods ||= GeneratedRelationMethods.new.tap do |mod|
  42. 2946 const_set(:GeneratedRelationMethods, mod)
  43. 2946 private_constant :GeneratedRelationMethods
  44. end
  45. end
  46. end
  47. 3 class GeneratedRelationMethods < Module # :nodoc:
  48. 3 include Mutex_m
  49. 3 def generate_method(method)
  50. 3548 synchronize do
  51. 3548 return if method_defined?(method)
  52. 3533 if /\A[a-zA-Z_]\w*[!?]?\z/.match?(method) && !DELEGATION_RESERVED_METHOD_NAMES.include?(method.to_s)
  53. 3524 definition = RUBY_VERSION >= "2.7" ? "..." : "*args, &block"
  54. 3524 module_eval <<-RUBY, __FILE__, __LINE__ + 1
  55. def #{method}(#{definition})
  56. scoping { klass.#{method}(#{definition}) }
  57. end
  58. RUBY
  59. else
  60. 9 define_method(method) do |*args, &block|
  61. 6 scoping { klass.public_send(method, *args, &block) }
  62. end
  63. 9 ruby2_keywords(method) if respond_to?(:ruby2_keywords, true)
  64. end
  65. end
  66. end
  67. end
  68. 3 private_constant :GeneratedRelationMethods
  69. 3 extend ActiveSupport::Concern
  70. # This module creates compiled delegation methods dynamically at runtime, which makes
  71. # subsequent calls to that method faster by avoiding method_missing. The delegations
  72. # may vary depending on the klass of a relation, so we create a subclass of Relation
  73. # for each different klass, and the delegations are compiled into that subclass only.
  74. 3 delegate :to_xml, :encode_with, :length, :each, :join,
  75. :[], :&, :|, :+, :-, :sample, :reverse, :rotate, :compact, :in_groups, :in_groups_of,
  76. :to_sentence, :to_formatted_s, :as_json,
  77. :shuffle, :split, :slice, :index, :rindex, to: :records
  78. 3 delegate :primary_key, :connection, to: :klass
  79. 3 module ClassSpecificRelation # :nodoc:
  80. 3 extend ActiveSupport::Concern
  81. 3 module ClassMethods # :nodoc:
  82. 3 def name
  83. 24 superclass.name
  84. end
  85. end
  86. 3 private
  87. 3 def method_missing(method, *args, &block)
  88. 2301 if @klass.respond_to?(method)
  89. 2301 @klass.generate_relation_method(method)
  90. 4602 scoping { @klass.public_send(method, *args, &block) }
  91. else
  92. super
  93. end
  94. end
  95. 3 ruby2_keywords(:method_missing) if respond_to?(:ruby2_keywords, true)
  96. end
  97. 3 module ClassMethods # :nodoc:
  98. 3 def create(klass, *args, **kwargs)
  99. 125087 relation_class_for(klass).new(klass, *args, **kwargs)
  100. end
  101. 3 private
  102. 3 def relation_class_for(klass)
  103. 125087 klass.relation_delegate_class(self)
  104. end
  105. end
  106. 3 private
  107. 3 def respond_to_missing?(method, _)
  108. 303 super || @klass.respond_to?(method)
  109. end
  110. end
  111. end

lib/active_record/relation/finder_methods.rb

98.94% lines covered

188 relevant lines. 186 lines covered and 2 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 require "active_support/core_ext/string/filters"
  3. 3 module ActiveRecord
  4. 3 module FinderMethods
  5. 3 ONE_AS_ONE = "1 AS one"
  6. # Find by id - This can either be a specific id (1), a list of ids (1, 5, 6), or an array of ids ([5, 6, 10]).
  7. # If one or more records cannot be found for the requested ids, then ActiveRecord::RecordNotFound will be raised.
  8. # If the primary key is an integer, find by id coerces its arguments by using +to_i+.
  9. #
  10. # Person.find(1) # returns the object for ID = 1
  11. # Person.find("1") # returns the object for ID = 1
  12. # Person.find("31-sarah") # returns the object for ID = 31
  13. # Person.find(1, 2, 6) # returns an array for objects with IDs in (1, 2, 6)
  14. # Person.find([7, 17]) # returns an array for objects with IDs in (7, 17)
  15. # Person.find([1]) # returns an array for the object with ID = 1
  16. # Person.where("administrator = 1").order("created_on DESC").find(1)
  17. #
  18. # NOTE: The returned records are in the same order as the ids you provide.
  19. # If you want the results to be sorted by database, you can use ActiveRecord::QueryMethods#where
  20. # method and provide an explicit ActiveRecord::QueryMethods#order option.
  21. # But ActiveRecord::QueryMethods#where method doesn't raise ActiveRecord::RecordNotFound.
  22. #
  23. # ==== Find with lock
  24. #
  25. # Example for find with a lock: Imagine two concurrent transactions:
  26. # each will read <tt>person.visits == 2</tt>, add 1 to it, and save, resulting
  27. # in two saves of <tt>person.visits = 3</tt>. By locking the row, the second
  28. # transaction has to wait until the first is finished; we get the
  29. # expected <tt>person.visits == 4</tt>.
  30. #
  31. # Person.transaction do
  32. # person = Person.lock(true).find(1)
  33. # person.visits += 1
  34. # person.save!
  35. # end
  36. #
  37. # ==== Variations of #find
  38. #
  39. # Person.where(name: 'Spartacus', rating: 4)
  40. # # returns a chainable list (which can be empty).
  41. #
  42. # Person.find_by(name: 'Spartacus', rating: 4)
  43. # # returns the first item or nil.
  44. #
  45. # Person.find_or_initialize_by(name: 'Spartacus', rating: 4)
  46. # # returns the first item or returns a new instance (requires you call .save to persist against the database).
  47. #
  48. # Person.find_or_create_by(name: 'Spartacus', rating: 4)
  49. # # returns the first item or creates it and returns it.
  50. #
  51. # ==== Alternatives for #find
  52. #
  53. # Person.where(name: 'Spartacus', rating: 4).exists?(conditions = :none)
  54. # # returns a boolean indicating if any record with the given conditions exist.
  55. #
  56. # Person.where(name: 'Spartacus', rating: 4).select("field1, field2, field3")
  57. # # returns a chainable list of instances with only the mentioned fields.
  58. #
  59. # Person.where(name: 'Spartacus', rating: 4).ids
  60. # # returns an Array of ids.
  61. #
  62. # Person.where(name: 'Spartacus', rating: 4).pluck(:field1, :field2)
  63. # # returns an Array of the required fields.
  64. 3 def find(*args)
  65. 12159 return super if block_given?
  66. 12129 find_with_ids(*args)
  67. end
  68. # Finds the first record matching the specified conditions. There
  69. # is no implied ordering so if order matters, you should specify it
  70. # yourself.
  71. #
  72. # If no record is found, returns <tt>nil</tt>.
  73. #
  74. # Post.find_by name: 'Spartacus', rating: 4
  75. # Post.find_by "published_at < ?", 2.weeks.ago
  76. 3 def find_by(arg, *args)
  77. 485 where(arg, *args).take
  78. end
  79. # Like #find_by, except that if no record is found, raises
  80. # an ActiveRecord::RecordNotFound error.
  81. 3 def find_by!(arg, *args)
  82. 39 where(arg, *args).take!
  83. end
  84. # Gives a record (or N records if a parameter is supplied) without any implied
  85. # order. The order will depend on the database implementation.
  86. # If an order is supplied it will be respected.
  87. #
  88. # Person.take # returns an object fetched by SELECT * FROM people LIMIT 1
  89. # Person.take(5) # returns 5 objects fetched by SELECT * FROM people LIMIT 5
  90. # Person.where(["name LIKE '%?'", name]).take
  91. 3 def take(limit = nil)
  92. 12350 limit ? find_take_with_limit(limit) : find_take
  93. end
  94. # Same as #take but raises ActiveRecord::RecordNotFound if no record
  95. # is found. Note that #take! accepts no arguments.
  96. 3 def take!
  97. 63 take || raise_record_not_found_exception!
  98. end
  99. # Find the first record (or first N records if a parameter is supplied).
  100. # If no order is defined it will order by primary key.
  101. #
  102. # Person.first # returns the first object fetched by SELECT * FROM people ORDER BY people.id LIMIT 1
  103. # Person.where(["user_name = ?", user_name]).first
  104. # Person.where(["user_name = :u", { u: user_name }]).first
  105. # Person.order("created_on DESC").offset(5).first
  106. # Person.first(3) # returns the first three objects fetched by SELECT * FROM people ORDER BY people.id LIMIT 3
  107. #
  108. 3 def first(limit = nil)
  109. 3795 check_reorder_deprecation unless loaded?
  110. 3795 if limit
  111. 66 find_nth_with_limit(0, limit)
  112. else
  113. 3729 find_nth 0
  114. end
  115. end
  116. # Same as #first but raises ActiveRecord::RecordNotFound if no record
  117. # is found. Note that #first! accepts no arguments.
  118. 3 def first!
  119. 57 first || raise_record_not_found_exception!
  120. end
  121. # Find the last record (or last N records if a parameter is supplied).
  122. # If no order is defined it will order by primary key.
  123. #
  124. # Person.last # returns the last object fetched by SELECT * FROM people
  125. # Person.where(["user_name = ?", user_name]).last
  126. # Person.order("created_on DESC").offset(5).last
  127. # Person.last(3) # returns the last three objects fetched by SELECT * FROM people.
  128. #
  129. # Take note that in that last case, the results are sorted in ascending order:
  130. #
  131. # [#<Person id:2>, #<Person id:3>, #<Person id:4>]
  132. #
  133. # and not:
  134. #
  135. # [#<Person id:4>, #<Person id:3>, #<Person id:2>]
  136. 3 def last(limit = nil)
  137. 423 return find_last(limit) if loaded? || has_limit_or_offset?
  138. 291 result = ordered_relation.limit(limit)
  139. 291 result = result.reverse_order!
  140. 288 limit ? result.reverse : result.first
  141. end
  142. # Same as #last but raises ActiveRecord::RecordNotFound if no record
  143. # is found. Note that #last! accepts no arguments.
  144. 3 def last!
  145. 24 last || raise_record_not_found_exception!
  146. end
  147. # Find the second record.
  148. # If no order is defined it will order by primary key.
  149. #
  150. # Person.second # returns the second object fetched by SELECT * FROM people
  151. # Person.offset(3).second # returns the second object from OFFSET 3 (which is OFFSET 4)
  152. # Person.where(["user_name = :u", { u: user_name }]).second
  153. 3 def second
  154. 56 find_nth 1
  155. end
  156. # Same as #second but raises ActiveRecord::RecordNotFound if no record
  157. # is found.
  158. 3 def second!
  159. 6 second || raise_record_not_found_exception!
  160. end
  161. # Find the third record.
  162. # If no order is defined it will order by primary key.
  163. #
  164. # Person.third # returns the third object fetched by SELECT * FROM people
  165. # Person.offset(3).third # returns the third object from OFFSET 3 (which is OFFSET 5)
  166. # Person.where(["user_name = :u", { u: user_name }]).third
  167. 3 def third
  168. 51 find_nth 2
  169. end
  170. # Same as #third but raises ActiveRecord::RecordNotFound if no record
  171. # is found.
  172. 3 def third!
  173. 9 third || raise_record_not_found_exception!
  174. end
  175. # Find the fourth record.
  176. # If no order is defined it will order by primary key.
  177. #
  178. # Person.fourth # returns the fourth object fetched by SELECT * FROM people
  179. # Person.offset(3).fourth # returns the fourth object from OFFSET 3 (which is OFFSET 6)
  180. # Person.where(["user_name = :u", { u: user_name }]).fourth
  181. 3 def fourth
  182. 30 find_nth 3
  183. end
  184. # Same as #fourth but raises ActiveRecord::RecordNotFound if no record
  185. # is found.
  186. 3 def fourth!
  187. 9 fourth || raise_record_not_found_exception!
  188. end
  189. # Find the fifth record.
  190. # If no order is defined it will order by primary key.
  191. #
  192. # Person.fifth # returns the fifth object fetched by SELECT * FROM people
  193. # Person.offset(3).fifth # returns the fifth object from OFFSET 3 (which is OFFSET 7)
  194. # Person.where(["user_name = :u", { u: user_name }]).fifth
  195. 3 def fifth
  196. 30 find_nth 4
  197. end
  198. # Same as #fifth but raises ActiveRecord::RecordNotFound if no record
  199. # is found.
  200. 3 def fifth!
  201. 9 fifth || raise_record_not_found_exception!
  202. end
  203. # Find the forty-second record. Also known as accessing "the reddit".
  204. # If no order is defined it will order by primary key.
  205. #
  206. # Person.forty_two # returns the forty-second object fetched by SELECT * FROM people
  207. # Person.offset(3).forty_two # returns the forty-second object from OFFSET 3 (which is OFFSET 44)
  208. # Person.where(["user_name = :u", { u: user_name }]).forty_two
  209. 3 def forty_two
  210. 3 find_nth 41
  211. end
  212. # Same as #forty_two but raises ActiveRecord::RecordNotFound if no record
  213. # is found.
  214. 3 def forty_two!
  215. forty_two || raise_record_not_found_exception!
  216. end
  217. # Find the third-to-last record.
  218. # If no order is defined it will order by primary key.
  219. #
  220. # Person.third_to_last # returns the third-to-last object fetched by SELECT * FROM people
  221. # Person.offset(3).third_to_last # returns the third-to-last object from OFFSET 3
  222. # Person.where(["user_name = :u", { u: user_name }]).third_to_last
  223. 3 def third_to_last
  224. 42 find_nth_from_last 3
  225. end
  226. # Same as #third_to_last but raises ActiveRecord::RecordNotFound if no record
  227. # is found.
  228. 3 def third_to_last!
  229. 9 third_to_last || raise_record_not_found_exception!
  230. end
  231. # Find the second-to-last record.
  232. # If no order is defined it will order by primary key.
  233. #
  234. # Person.second_to_last # returns the second-to-last object fetched by SELECT * FROM people
  235. # Person.offset(3).second_to_last # returns the second-to-last object from OFFSET 3
  236. # Person.where(["user_name = :u", { u: user_name }]).second_to_last
  237. 3 def second_to_last
  238. 39 find_nth_from_last 2
  239. end
  240. # Same as #second_to_last but raises ActiveRecord::RecordNotFound if no record
  241. # is found.
  242. 3 def second_to_last!
  243. 9 second_to_last || raise_record_not_found_exception!
  244. end
  245. # Returns true if a record exists in the table that matches the +id+ or
  246. # conditions given, or false otherwise. The argument can take six forms:
  247. #
  248. # * Integer - Finds the record with this primary key.
  249. # * String - Finds the record with a primary key corresponding to this
  250. # string (such as <tt>'5'</tt>).
  251. # * Array - Finds the record that matches these +where+-style conditions
  252. # (such as <tt>['name LIKE ?', "%#{query}%"]</tt>).
  253. # * Hash - Finds the record that matches these +where+-style conditions
  254. # (such as <tt>{name: 'David'}</tt>).
  255. # * +false+ - Returns always +false+.
  256. # * No args - Returns +false+ if the relation is empty, +true+ otherwise.
  257. #
  258. # For more information about specifying conditions as a hash or array,
  259. # see the Conditions section in the introduction to ActiveRecord::Base.
  260. #
  261. # Note: You can't pass in a condition as a string (like <tt>name =
  262. # 'Jamie'</tt>), since it would be sanitized and then queried against
  263. # the primary key column, like <tt>id = 'name = \'Jamie\''</tt>.
  264. #
  265. # Person.exists?(5)
  266. # Person.exists?('5')
  267. # Person.exists?(['name LIKE ?', "%#{query}%"])
  268. # Person.exists?(id: [1, 4, 8])
  269. # Person.exists?(name: 'David')
  270. # Person.exists?(false)
  271. # Person.exists?
  272. # Person.where(name: 'Spartacus', rating: 4).exists?
  273. 3 def exists?(conditions = :none)
  274. 1357 if Base === conditions
  275. 3 raise ArgumentError, <<-MSG.squish
  276. You are passing an instance of ActiveRecord::Base to `exists?`.
  277. Please pass the id of the object by calling `.id`.
  278. MSG
  279. end
  280. 1354 return false if !conditions || limit_value == 0
  281. 1324 if eager_loading?
  282. 42 relation = apply_join_dependency(eager_loading: false)
  283. 39 return relation.exists?(conditions)
  284. end
  285. 1282 relation = construct_relation_for_exists(conditions)
  286. 2552 skip_query_cache_if_necessary { connection.select_rows(relation.arel, "#{name} Exists?").size == 1 }
  287. end
  288. # This method is called whenever no records are found with either a single
  289. # id or multiple ids and raises an ActiveRecord::RecordNotFound exception.
  290. #
  291. # The error message is different depending on whether a single id or
  292. # multiple ids are provided. If multiple ids are provided, then the number
  293. # of results obtained should be provided in the +result_size+ argument and
  294. # the expected number of results should be provided in the +expected_size+
  295. # argument.
  296. 3 def raise_record_not_found_exception!(ids = nil, result_size = nil, expected_size = nil, key = primary_key, not_found_ids = nil) # :nodoc:
  297. 166 conditions = " [#{arel.where_sql(klass)}]" unless where_clause.empty?
  298. 166 name = @klass.name
  299. 166 if ids.nil?
  300. 51 error = +"Couldn't find #{name}"
  301. 51 error << " with#{conditions}" if conditions
  302. 51 raise RecordNotFound.new(error, name, key)
  303. 115 elsif Array(ids).size == 1
  304. 88 error = "Couldn't find #{name} with '#{key}'=#{ids}#{conditions}"
  305. 88 raise RecordNotFound.new(error, name, key, ids)
  306. else
  307. 27 error = +"Couldn't find all #{name.pluralize} with '#{key}': "
  308. 27 error << "(#{ids.join(", ")})#{conditions} (found #{result_size} results, but was looking for #{expected_size})."
  309. 27 error << " Couldn't find #{name.pluralize(not_found_ids.size)} with #{key.to_s.pluralize(not_found_ids.size)} #{not_found_ids.join(', ')}." if not_found_ids
  310. 27 raise RecordNotFound.new(error, name, key, ids)
  311. end
  312. end
  313. 3 private
  314. 3 def check_reorder_deprecation
  315. 3438 if !order_values.empty? && order_values.all?(&:blank?)
  316. 3 blank_value = order_values.first
  317. 3 ActiveSupport::Deprecation.warn(<<~MSG.squish)
  318. `.reorder(#{blank_value.inspect})` with `.first` / `.first!` no longer
  319. takes non-deterministic result in Rails 6.2.
  320. To continue taking non-deterministic result, use `.take` / `.take!` instead.
  321. MSG
  322. end
  323. end
  324. 3 def construct_relation_for_exists(conditions)
  325. 1282 conditions = sanitize_forbidden_attributes(conditions)
  326. 1279 if distinct_value && offset_value
  327. 18 relation = except(:order).limit!(1)
  328. else
  329. 1261 relation = except(:select, :distinct, :order)._select!(ONE_AS_ONE).limit!(1)
  330. end
  331. 1279 case conditions
  332. when Array, Hash
  333. 87 relation.where!(conditions) unless conditions.empty?
  334. else
  335. 1192 relation.where!(primary_key => conditions) unless conditions == :none
  336. end
  337. 1276 relation
  338. end
  339. 3 def apply_join_dependency(eager_loading: group_values.empty?)
  340. 802 join_dependency = construct_join_dependency(
  341. eager_load_values | includes_values, Arel::Nodes::OuterJoin
  342. )
  343. 784 relation = except(:includes, :eager_load, :preload).joins!(join_dependency)
  344. 784 if eager_loading && !(
  345. 733 using_limitable_reflections?(join_dependency.reflections) &&
  346. using_limitable_reflections?(
  347. construct_join_dependency(
  348. select_association_list(joins_values).concat(
  349. select_association_list(left_outer_joins_values)
  350. ), nil
  351. ).reflections
  352. )
  353. )
  354. 520 if has_limit_or_offset?
  355. 250 limited_ids = limited_ids_for(relation)
  356. 250 limited_ids.empty? ? relation.none! : relation.where!(primary_key => limited_ids)
  357. end
  358. 520 relation.limit_value = relation.offset_value = nil
  359. end
  360. 784 if block_given?
  361. 544 yield relation, join_dependency
  362. else
  363. 240 relation
  364. end
  365. end
  366. 3 def limited_ids_for(relation)
  367. 250 values = @klass.connection.columns_for_distinct(
  368. connection.visitor.compile(table[primary_key]),
  369. relation.order_values
  370. )
  371. 250 relation = relation.except(:select).select(values).distinct!
  372. 500 id_rows = skip_query_cache_if_necessary { @klass.connection.select_rows(relation.arel, "SQL") }
  373. 250 id_rows.map(&:last)
  374. end
  375. 3 def using_limitable_reflections?(reflections)
  376. 967 reflections.none?(&:collection?)
  377. end
  378. 3 def find_with_ids(*ids)
  379. 12129 raise UnknownPrimaryKey.new(@klass) if primary_key.nil?
  380. 12126 expects_array = ids.first.kind_of?(Array)
  381. 12126 return [] if expects_array && ids.first.empty?
  382. 12120 ids = ids.flatten.compact.uniq
  383. 12120 model_name = @klass.name
  384. 12120 case ids.size
  385. when 0
  386. 9 error_message = "Couldn't find #{model_name} without an ID"
  387. 9 raise RecordNotFound.new(error_message, model_name, primary_key)
  388. when 1
  389. 11693 result = find_one(ids.first)
  390. 11585 expects_array ? [ result ] : result
  391. else
  392. 418 find_some(ids)
  393. end
  394. end
  395. 3 def find_one(id)
  396. 11693 if ActiveRecord::Base === id
  397. 3 raise ArgumentError, <<-MSG.squish
  398. You are passing an instance of ActiveRecord::Base to `find`.
  399. Please pass the id of the object by calling `.id`.
  400. MSG
  401. end
  402. 11690 relation = where(primary_key => id)
  403. 11690 record = relation.take
  404. 11673 raise_record_not_found_exception!(id, 0, 1) unless record
  405. 11585 record
  406. end
  407. 3 def find_some(ids)
  408. 418 return find_some_ordered(ids) unless order_values.present?
  409. 27 result = where(primary_key => ids).to_a
  410. 27 expected_size =
  411. 27 if limit_value && ids.size > limit_value
  412. 3 limit_value
  413. else
  414. 24 ids.size
  415. end
  416. # 11 ids with limit 3, offset 9 should give 2 results.
  417. 27 if offset_value && (ids.size - offset_value < expected_size)
  418. expected_size = ids.size - offset_value
  419. end
  420. 27 if result.size == expected_size
  421. 18 result
  422. else
  423. 9 raise_record_not_found_exception!(ids, result.size, expected_size)
  424. end
  425. end
  426. 3 def find_some_ordered(ids)
  427. 391 ids = ids.slice(offset_value || 0, limit_value || ids.size) || []
  428. 391 result = except(:limit, :offset).where(primary_key => ids).records
  429. 391 if result.size == ids.size
  430. 379 pk_type = @klass.type_for_attribute(primary_key)
  431. 379 records_by_id = result.index_by(&:id)
  432. 1159 ids.map { |id| records_by_id.fetch(pk_type.cast(id)) }
  433. else
  434. 12 raise_record_not_found_exception!(ids, result.size, ids.size)
  435. end
  436. end
  437. 3 def find_take
  438. 12311 if loaded?
  439. 12 records.first
  440. else
  441. 12299 @take ||= limit(1).records.first
  442. end
  443. end
  444. 3 def find_take_with_limit(limit)
  445. 39 if loaded?
  446. 21 records.take(limit)
  447. else
  448. 18 limit(limit).to_a
  449. end
  450. end
  451. 3 def find_nth(index)
  452. 3899 @offsets ||= {}
  453. 3899 @offsets[index] ||= find_nth_with_limit(index, 1).first
  454. end
  455. 3 def find_nth_with_limit(index, limit)
  456. 3926 if loaded?
  457. 418 records[index, limit] || []
  458. else
  459. 3508 relation = ordered_relation
  460. 3503 if limit_value
  461. 69 limit = [limit_value - index, limit].min
  462. end
  463. 3503 if limit > 0
  464. 3494 relation = relation.offset((offset_value || 0) + index) unless index.zero?
  465. 3494 relation.limit(limit).to_a
  466. else
  467. 9 []
  468. end
  469. end
  470. end
  471. 3 def find_nth_from_last(index)
  472. 81 if loaded?
  473. 18 records[-index]
  474. else
  475. 63 relation = ordered_relation
  476. 63 if equal?(relation) || has_limit_or_offset?
  477. 39 relation.records[-index]
  478. else
  479. 24 relation.last(index)[-index]
  480. end
  481. end
  482. end
  483. 3 def find_last(limit)
  484. 132 limit ? records.last(limit) : records.last
  485. end
  486. 3 def ordered_relation
  487. 3862 if order_values.empty? && (implicit_order_column || primary_key)
  488. 3112 if implicit_order_column && primary_key && implicit_order_column != primary_key
  489. 9 order(table[implicit_order_column].asc, table[primary_key].asc)
  490. else
  491. 3103 order(table[implicit_order_column || primary_key].asc)
  492. end
  493. else
  494. 750 self
  495. end
  496. end
  497. end
  498. end

lib/active_record/relation/from_clause.rb

93.33% lines covered

15 relevant lines. 14 lines covered and 1 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 module ActiveRecord
  3. 3 class Relation
  4. 3 class FromClause # :nodoc:
  5. 3 attr_reader :value, :name
  6. 3 def initialize(value, name)
  7. 141 @value = value
  8. 141 @name = name
  9. end
  10. 3 def merge(other)
  11. self
  12. end
  13. 3 def empty?
  14. 120565 value.nil?
  15. end
  16. 3 def ==(other)
  17. 3 self.class == other.class && value == other.value && name == other.name
  18. end
  19. 3 def self.empty
  20. 141613 @empty ||= new(nil, nil).freeze
  21. end
  22. end
  23. end
  24. end

lib/active_record/relation/merger.rb

100.0% lines covered

93 relevant lines. 93 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 require "active_support/core_ext/hash/keys"
  3. 3 module ActiveRecord
  4. 3 class Relation
  5. 3 class HashMerger # :nodoc:
  6. 3 attr_reader :relation, :hash
  7. 3 def initialize(relation, hash, rewhere = nil)
  8. 756 hash.assert_valid_keys(*Relation::VALUE_METHODS)
  9. 753 @relation = relation
  10. 753 @hash = hash
  11. 753 @rewhere = rewhere
  12. end
  13. 3 def merge
  14. 753 Merger.new(relation, other, @rewhere).merge
  15. end
  16. # Applying values to a relation has some side effects. E.g.
  17. # interpolation might take place for where values. So we should
  18. # build a relation to merge in rather than directly merging
  19. # the values.
  20. 3 def other
  21. 753 other = Relation.create(
  22. relation.klass,
  23. table: relation.table,
  24. predicate_builder: relation.predicate_builder
  25. )
  26. 753 hash.each do |k, v|
  27. 1371 k = :_select if k == :select
  28. 1371 if Array === v
  29. 156 other.send("#{k}!", *v)
  30. else
  31. 1215 other.send("#{k}!", v)
  32. end
  33. end
  34. 753 other
  35. end
  36. end
  37. 3 class Merger # :nodoc:
  38. 3 attr_reader :relation, :values, :other
  39. 3 def initialize(relation, other, rewhere = nil)
  40. 39613 @relation = relation
  41. 39613 @values = other.values
  42. 39613 @other = other
  43. 39613 @rewhere = rewhere
  44. end
  45. 3 NORMAL_VALUES = Relation::VALUE_METHODS -
  46. Relation::CLAUSE_METHODS -
  47. [:includes, :preload, :joins, :left_outer_joins, :order, :reverse_order, :lock, :create_with, :reordering] # :nodoc:
  48. 3 def normal_values
  49. 39613 NORMAL_VALUES
  50. end
  51. 3 def merge
  52. 39613 normal_values.each do |name|
  53. 554582 value = values[name]
  54. # The unless clause is here mostly for performance reasons (since the `send` call might be moderately
  55. # expensive), most of the time the value is going to be `nil` or `.blank?`, the only catch is that
  56. # `false.blank?` returns `true`, so there needs to be an extra check so that explicit `false` values
  57. # don't fall through the cracks.
  58. 554582 unless value.nil? || (value.blank? && false != value)
  59. 13463 if name == :select
  60. 188 relation._select!(*value)
  61. else
  62. 13275 relation.send("#{name}!", *value)
  63. end
  64. end
  65. end
  66. 39613 merge_multi_values
  67. 39613 merge_single_values
  68. 39613 merge_clauses
  69. 39610 merge_preloads
  70. 39610 merge_joins
  71. 39610 merge_outer_joins
  72. 39610 relation
  73. end
  74. 3 private
  75. 3 def merge_preloads
  76. 39610 return if other.preload_values.empty? && other.includes_values.empty?
  77. 1272 if other.klass == relation.klass
  78. 1266 relation.preload_values |= other.preload_values unless other.preload_values.empty?
  79. 1266 relation.includes_values |= other.includes_values unless other.includes_values.empty?
  80. else
  81. 6 reflection = relation.klass.reflect_on_all_associations.find do |r|
  82. 6 r.class_name == other.klass.name
  83. end || return
  84. 6 unless other.preload_values.empty?
  85. 3 relation.preload! reflection.name => other.preload_values
  86. end
  87. 6 unless other.includes_values.empty?
  88. 3 relation.includes! reflection.name => other.includes_values
  89. end
  90. end
  91. end
  92. 3 def merge_joins
  93. 39610 return if other.joins_values.empty?
  94. 4854 if other.klass == relation.klass
  95. 4818 relation.joins_values |= other.joins_values
  96. else
  97. 36 associations, others = other.joins_values.partition do |join|
  98. 39 case join
  99. 30 when Hash, Symbol, Array; true
  100. end
  101. end
  102. 36 join_dependency = other.construct_join_dependency(
  103. associations, Arel::Nodes::InnerJoin
  104. )
  105. 36 relation.joins!(join_dependency, *others)
  106. end
  107. end
  108. 3 def merge_outer_joins
  109. 39610 return if other.left_outer_joins_values.empty?
  110. 57 if other.klass == relation.klass
  111. 36 relation.left_outer_joins_values |= other.left_outer_joins_values
  112. else
  113. 21 associations, others = other.left_outer_joins_values.partition do |join|
  114. 27 case join
  115. 21 when Hash, Symbol, Array; true
  116. end
  117. end
  118. 21 join_dependency = other.construct_join_dependency(
  119. associations, Arel::Nodes::OuterJoin
  120. )
  121. 21 relation.left_outer_joins!(join_dependency, *others)
  122. end
  123. end
  124. 3 def merge_multi_values
  125. 39613 if other.reordering_value
  126. # override any order specified in the original relation
  127. 9 relation.reorder!(*other.order_values)
  128. 39604 elsif other.order_values.any?
  129. # merge in order_values from relation
  130. 4030 relation.order!(*other.order_values)
  131. end
  132. 39613 extensions = other.extensions - relation.extensions
  133. 39613 relation.extending!(*extensions) if extensions.any?
  134. end
  135. 3 def merge_single_values
  136. 39613 relation.lock_value ||= other.lock_value if other.lock_value
  137. 39613 unless other.create_with_value.blank?
  138. 3 relation.create_with_value = (relation.create_with_value || {}).merge(other.create_with_value)
  139. end
  140. end
  141. 3 def merge_clauses
  142. 39613 relation.from_clause = other.from_clause if replace_from_clause?
  143. 39613 where_clause = relation.where_clause.merge(other.where_clause, @rewhere)
  144. 39613 relation.where_clause = where_clause unless where_clause.empty?
  145. 39610 having_clause = relation.having_clause.merge(other.having_clause)
  146. 39610 relation.having_clause = having_clause unless having_clause.empty?
  147. end
  148. 3 def replace_from_clause?
  149. 39613 relation.from_clause.empty? && !other.from_clause.empty? &&
  150. relation.klass.base_class == other.klass.base_class
  151. end
  152. end
  153. end
  154. end

lib/active_record/relation/predicate_builder.rb

100.0% lines covered

91 relevant lines. 91 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 module ActiveRecord
  3. 3 class PredicateBuilder # :nodoc:
  4. 3 require "active_record/relation/predicate_builder/array_handler"
  5. 3 require "active_record/relation/predicate_builder/basic_object_handler"
  6. 3 require "active_record/relation/predicate_builder/range_handler"
  7. 3 require "active_record/relation/predicate_builder/relation_handler"
  8. 3 require "active_record/relation/predicate_builder/association_query_value"
  9. 3 require "active_record/relation/predicate_builder/polymorphic_array_value"
  10. # No-op BaseHandler to work Mashal.load(File.read("legacy_relation.dump")).
  11. # TODO: Remove the constant alias once Rails 6.1 has released.
  12. 3 BaseHandler = BasicObjectHandler
  13. 3 def initialize(table)
  14. 8168 @table = table
  15. 8168 @handlers = []
  16. 8168 register_handler(BasicObject, BasicObjectHandler.new(self))
  17. 8168 register_handler(Range, RangeHandler.new(self))
  18. 8168 register_handler(Relation, RelationHandler.new)
  19. 8168 register_handler(Array, ArrayHandler.new(self))
  20. 8168 register_handler(Set, ArrayHandler.new(self))
  21. end
  22. 3 def build_from_hash(attributes, &block)
  23. 42337 attributes = convert_dot_notation_to_hash(attributes)
  24. 42337 expand_from_hash(attributes, &block)
  25. end
  26. 3 def self.references(attributes)
  27. 42337 attributes.each_with_object([]) do |(key, value), result|
  28. 48586 if value.is_a?(Hash)
  29. 3574 result << key
  30. 45012 elsif key.include?(".")
  31. 408 result << key.split(".").first
  32. end
  33. end
  34. end
  35. # Define how a class is converted to Arel nodes when passed to +where+.
  36. # The handler can be any object that responds to +call+, and will be used
  37. # for any value that +===+ the class given. For example:
  38. #
  39. # MyCustomDateRange = Struct.new(:start, :end)
  40. # handler = proc do |column, range|
  41. # Arel::Nodes::Between.new(column,
  42. # Arel::Nodes::And.new([range.start, range.end])
  43. # )
  44. # end
  45. # ActiveRecord::PredicateBuilder.new("users").register_handler(MyCustomDateRange, handler)
  46. 3 def register_handler(klass, handler)
  47. 40846 @handlers.unshift([klass, handler])
  48. end
  49. 3 def [](attr_name, value, operator = nil)
  50. 891 build(table.arel_table[attr_name], value, operator)
  51. end
  52. 3 def build(attribute, value, operator = nil)
  53. 61268 value = value.id if value.is_a?(Base)
  54. 61268 if operator ||= table.type(attribute.name).force_equality?(value) && :eq
  55. 909 bind = build_bind_attribute(attribute.name, value)
  56. 909 attribute.public_send(operator, bind)
  57. else
  58. 60359 handler_for(value).call(attribute, value)
  59. end
  60. end
  61. 3 def build_bind_attribute(column_name, value)
  62. 91288 attr = Relation::QueryAttribute.new(column_name, value, table.type(column_name))
  63. 91288 Arel::Nodes::BindParam.new(attr)
  64. end
  65. 3 def resolve_arel_attribute(table_name, column_name, &block)
  66. 192 table.associated_table(table_name, &block).arel_table[column_name]
  67. end
  68. 3 protected
  69. 3 def expand_from_hash(attributes, &block)
  70. 46728 return ["1=0"] if attributes.empty?
  71. 46722 attributes.flat_map do |key, value|
  72. 53064 if value.is_a?(Hash) && !table.has_column?(key)
  73. table.associated_table(key, &block)
  74. 3955 .predicate_builder.expand_from_hash(value.stringify_keys)
  75. 49109 elsif table.associated_with?(key)
  76. # Find the foreign key when using queries such as:
  77. # Post.where(author: author)
  78. #
  79. # For polymorphic relationships, find the foreign key and type:
  80. # PriceEstimate.where(estimate_of: treasure)
  81. 439 associated_table = table.associated_table(key)
  82. 439 if associated_table.polymorphic_association?
  83. 91 case value.is_a?(Array) ? value.first : value
  84. when Base, Relation
  85. 78 value = [value] unless value.is_a?(Array)
  86. 78 klass = PolymorphicArrayValue
  87. end
  88. 348 elsif associated_table.through_association?
  89. next associated_table.predicate_builder.expand_from_hash(
  90. associated_table.join_foreign_key => value
  91. 3 )
  92. end
  93. 436 klass ||= AssociationQueryValue
  94. 436 queries = klass.new(associated_table, value).queries.map! do |query|
  95. 442 expand_from_hash(query)
  96. end
  97. 436 grouping_queries(queries)
  98. 48670 elsif table.aggregated_with?(key)
  99. 75 mapping = table.reflect_on_aggregation(key).mapping
  100. 75 values = value.nil? ? [nil] : Array.wrap(value)
  101. 75 if mapping.length == 1 || values.empty?
  102. 30 column_name, aggr_attr = mapping.first
  103. 30 values = values.map do |object|
  104. 33 object.respond_to?(aggr_attr) ? object.public_send(aggr_attr) : object
  105. end
  106. 30 build(table.arel_table[column_name], values)
  107. else
  108. 45 queries = values.map do |object|
  109. 51 mapping.map do |field_attr, aggregate_attr|
  110. 153 build(table.arel_table[field_attr], object.try!(aggregate_attr))
  111. end
  112. end
  113. 45 grouping_queries(queries)
  114. end
  115. else
  116. 48595 build(table.arel_table[key], value)
  117. end
  118. end
  119. end
  120. 3 private
  121. 3 attr_reader :table
  122. 3 def grouping_queries(queries)
  123. 481 if queries.one?
  124. 472 queries.first
  125. else
  126. 30 queries.map! { |query| query.reduce(&:and) }
  127. 21 queries = queries.reduce { |result, query| Arel::Nodes::Or.new(result, query) }
  128. 9 Arel::Nodes::Grouping.new(queries)
  129. end
  130. end
  131. 3 def convert_dot_notation_to_hash(attributes)
  132. 42337 dot_notation = attributes.select do |k, v|
  133. 48586 k.include?(".") && !v.is_a?(Hash)
  134. end
  135. 42337 dot_notation.each_key do |key|
  136. 408 table_name, column_name = key.split(".")
  137. 408 value = attributes.delete(key)
  138. 408 attributes[table_name] ||= {}
  139. 408 attributes[table_name] = attributes[table_name].merge(column_name => value)
  140. end
  141. 42337 attributes
  142. end
  143. 3 def handler_for(object)
  144. 325424 @handlers.detect { |klass, _| klass === object }.last
  145. end
  146. end
  147. end

lib/active_record/relation/predicate_builder/array_handler.rb

100.0% lines covered

26 relevant lines. 26 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 require "active_support/core_ext/array/extract"
  3. 3 module ActiveRecord
  4. 3 class PredicateBuilder
  5. 3 class ArrayHandler # :nodoc:
  6. 3 def initialize(predicate_builder)
  7. 16336 @predicate_builder = predicate_builder
  8. end
  9. 3 def call(attribute, value)
  10. 12128 return attribute.in([]) if value.empty?
  11. 706055 values = value.map { |x| x.is_a?(Base) ? x.id : x }
  12. 11952 nils = values.extract!(&:nil?)
  13. 705971 ranges = values.extract! { |v| v.is_a?(Range) }
  14. 11952 values_predicate =
  15. case values.length
  16. 37 when 0 then NullPredicate
  17. 4113 when 1 then predicate_builder.build(attribute, values.first)
  18. 7802 else Arel::Nodes::HomogeneousIn.new(values, attribute, :in)
  19. end
  20. 11952 unless nils.empty?
  21. 84 values_predicate = values_predicate.or(attribute.eq(nil))
  22. end
  23. 11952 if ranges.empty?
  24. 11945 values_predicate
  25. else
  26. 20 array_predicates = ranges.map! { |range| predicate_builder.build(attribute, range) }
  27. 7 array_predicates.inject(values_predicate, &:or)
  28. end
  29. end
  30. 3 private
  31. 3 attr_reader :predicate_builder
  32. 3 module NullPredicate # :nodoc:
  33. 3 def self.or(other)
  34. 37 other
  35. end
  36. end
  37. end
  38. end
  39. end

lib/active_record/relation/predicate_builder/association_query_value.rb

100.0% lines covered

21 relevant lines. 21 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 module ActiveRecord
  3. 3 class PredicateBuilder
  4. 3 class AssociationQueryValue # :nodoc:
  5. 3 def initialize(associated_table, value)
  6. 358 @associated_table = associated_table
  7. 358 @value = value
  8. end
  9. 3 def queries
  10. 358 [associated_table.join_foreign_key => ids]
  11. end
  12. 3 private
  13. 3 attr_reader :associated_table, :value
  14. 3 def ids
  15. 358 case value
  16. when Relation
  17. 27 value.select_values.empty? ? value.select(primary_key) : value
  18. when Array
  19. 232 value.map { |v| convert_to_id(v) }
  20. else
  21. 223 convert_to_id(value)
  22. end
  23. end
  24. 3 def primary_key
  25. 349 associated_table.join_primary_key
  26. end
  27. 3 def convert_to_id(value)
  28. 347 case value
  29. when Base
  30. 325 value._read_attribute(primary_key)
  31. else
  32. 22 value
  33. end
  34. end
  35. end
  36. end
  37. end

lib/active_record/relation/predicate_builder/basic_object_handler.rb

100.0% lines covered

10 relevant lines. 10 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 module ActiveRecord
  3. 3 class PredicateBuilder
  4. 3 class BasicObjectHandler # :nodoc:
  5. 3 def initialize(predicate_builder)
  6. 8168 @predicate_builder = predicate_builder
  7. end
  8. 3 def call(attribute, value)
  9. 47999 bind = predicate_builder.build_bind_attribute(attribute.name, value)
  10. 47999 attribute.eq(bind)
  11. end
  12. 3 private
  13. 3 attr_reader :predicate_builder
  14. end
  15. end
  16. end

lib/active_record/relation/predicate_builder/polymorphic_array_value.rb

100.0% lines covered

25 relevant lines. 25 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 module ActiveRecord
  3. 3 class PredicateBuilder
  4. 3 class PolymorphicArrayValue # :nodoc:
  5. 3 def initialize(associated_table, values)
  6. 78 @associated_table = associated_table
  7. 78 @values = values
  8. end
  9. 3 def queries
  10. 78 type_to_ids_mapping.map do |type, ids|
  11. 84 {
  12. associated_table.join_foreign_type => type,
  13. associated_table.join_foreign_key => ids
  14. }
  15. end
  16. end
  17. 3 private
  18. 3 attr_reader :associated_table, :values
  19. 3 def type_to_ids_mapping
  20. 162 default_hash = Hash.new { |hsh, key| hsh[key] = [] }
  21. 78 values.each_with_object(default_hash) do |value, hash|
  22. 90 hash[klass(value).polymorphic_name] << convert_to_id(value)
  23. end
  24. end
  25. 3 def primary_key(value)
  26. 90 associated_table.join_primary_key(klass(value))
  27. end
  28. 3 def klass(value)
  29. 180 case value
  30. when Base
  31. 156 value.class
  32. when Relation
  33. 24 value.klass
  34. end
  35. end
  36. 3 def convert_to_id(value)
  37. 90 case value
  38. when Base
  39. 78 value._read_attribute(primary_key(value))
  40. when Relation
  41. 12 value.select(primary_key(value))
  42. end
  43. end
  44. end
  45. end
  46. end

lib/active_record/relation/predicate_builder/range_handler.rb

100.0% lines covered

12 relevant lines. 12 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 module ActiveRecord
  3. 3 class PredicateBuilder
  4. 3 class RangeHandler # :nodoc:
  5. 3 RangeWithBinds = Struct.new(:begin, :end, :exclude_end?)
  6. 3 def initialize(predicate_builder)
  7. 8168 @predicate_builder = predicate_builder
  8. end
  9. 3 def call(attribute, value)
  10. 136 begin_bind = predicate_builder.build_bind_attribute(attribute.name, value.begin)
  11. 136 end_bind = predicate_builder.build_bind_attribute(attribute.name, value.end)
  12. 136 attribute.between(RangeWithBinds.new(begin_bind, end_bind, value.exclude_end?))
  13. end
  14. 3 private
  15. 3 attr_reader :predicate_builder
  16. end
  17. end
  18. end

lib/active_record/relation/predicate_builder/relation_handler.rb

100.0% lines covered

9 relevant lines. 9 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 module ActiveRecord
  3. 3 class PredicateBuilder
  4. 3 class RelationHandler # :nodoc:
  5. 3 def call(attribute, value)
  6. 90 if value.eager_loading?
  7. 3 value = value.send(:apply_join_dependency)
  8. end
  9. 90 if value.select_values.empty?
  10. 42 value = value.select(value.table[value.klass.primary_key])
  11. end
  12. 90 attribute.in(value.arel)
  13. end
  14. end
  15. end
  16. end

lib/active_record/relation/query_attribute.rb

100.0% lines covered

24 relevant lines. 24 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 require "active_model/attribute"
  3. 3 module ActiveRecord
  4. 3 class Relation
  5. 3 class QueryAttribute < ActiveModel::Attribute # :nodoc:
  6. 3 def type_cast(value)
  7. 88167 value
  8. end
  9. 3 def value_for_database
  10. 131777 @value_for_database ||= super
  11. end
  12. 3 def with_cast_value(value)
  13. 6496 QueryAttribute.new(name, value, type)
  14. end
  15. 3 def nil?
  16. 45894 unless value_before_type_cast.is_a?(StatementCache::Substitute)
  17. 44533 value_before_type_cast.nil? ||
  18. type.respond_to?(:subtype, true) && value_for_database.nil?
  19. end
  20. rescue ::RangeError
  21. end
  22. 3 def infinite?
  23. 254 infinity?(value_before_type_cast) || infinity?(value_for_database)
  24. rescue ::RangeError
  25. end
  26. 3 def unboundable?
  27. 66010 if defined?(@_unboundable)
  28. 22393 @_unboundable
  29. else
  30. 43617 value_for_database unless value_before_type_cast.is_a?(StatementCache::Substitute)
  31. 43561 @_unboundable = nil
  32. end
  33. rescue ::RangeError
  34. 56 @_unboundable = type.cast(value_before_type_cast) <=> 0
  35. end
  36. 3 private
  37. 3 def infinity?(value)
  38. 465 value.respond_to?(:infinite?) && value.infinite?
  39. end
  40. end
  41. end
  42. end

lib/active_record/relation/query_methods.rb

98.83% lines covered

512 relevant lines. 506 lines covered and 6 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 require "active_record/relation/from_clause"
  3. 3 require "active_record/relation/query_attribute"
  4. 3 require "active_record/relation/where_clause"
  5. 3 require "active_model/forbidden_attributes_protection"
  6. 3 require "active_support/core_ext/array/wrap"
  7. 3 module ActiveRecord
  8. 3 module QueryMethods
  9. 3 extend ActiveSupport::Concern
  10. 3 include ActiveModel::ForbiddenAttributesProtection
  11. # WhereChain objects act as placeholder for queries in which #where does not have any parameter.
  12. # In this case, #where must be chained with #not to return a new relation.
  13. 3 class WhereChain
  14. 3 def initialize(scope)
  15. 226 @scope = scope
  16. end
  17. # Returns a new relation expressing WHERE + NOT condition according to
  18. # the conditions in the arguments.
  19. #
  20. # #not accepts conditions as a string, array, or hash. See QueryMethods#where for
  21. # more details on each format.
  22. #
  23. # User.where.not("name = 'Jon'")
  24. # # SELECT * FROM users WHERE NOT (name = 'Jon')
  25. #
  26. # User.where.not(["name = ?", "Jon"])
  27. # # SELECT * FROM users WHERE NOT (name = 'Jon')
  28. #
  29. # User.where.not(name: "Jon")
  30. # # SELECT * FROM users WHERE name != 'Jon'
  31. #
  32. # User.where.not(name: nil)
  33. # # SELECT * FROM users WHERE name IS NOT NULL
  34. #
  35. # User.where.not(name: %w(Ko1 Nobu))
  36. # # SELECT * FROM users WHERE name NOT IN ('Ko1', 'Nobu')
  37. 3 def not(opts, *rest)
  38. 217 where_clause = @scope.send(:build_where_clause, opts, rest)
  39. 211 if not_behaves_as_nor?(opts)
  40. 6 ActiveSupport::Deprecation.warn(<<~MSG.squish)
  41. NOT conditions will no longer behave as NOR in Rails 6.1.
  42. To continue using NOR conditions, NOT each condition individually
  43. (`#{
  44. opts.flat_map { |key, value|
  45. 9 if value.is_a?(Hash) && value.size > 1
  46. 9 value.map { |k, v| ".where.not(#{key.inspect} => { #{k.inspect} => ... })" }
  47. else
  48. 6 ".where.not(#{key.inspect} => ...)"
  49. end
  50. }.join
  51. }`).
  52. MSG
  53. 6 @scope.where_clause += where_clause.invert(:nor)
  54. else
  55. 205 @scope.where_clause += where_clause.invert
  56. end
  57. 211 @scope
  58. end
  59. # Returns a new relation with left outer joins and where clause to identify
  60. # missing relations.
  61. #
  62. # For example, posts that are missing a related author:
  63. #
  64. # Post.where.missing(:author)
  65. # # SELECT "posts".* FROM "posts"
  66. # # LEFT OUTER JOIN "authors" ON "authors"."id" = "posts"."author_id"
  67. # # WHERE "authors"."id" IS NULL
  68. #
  69. # Additionally, multiple relations can be combined. This will return posts
  70. # that are missing both an author and any comments:
  71. #
  72. # Post.where.missing(:author, :comments)
  73. # # SELECT "posts".* FROM "posts"
  74. # # LEFT OUTER JOIN "authors" ON "authors"."id" = "posts"."author_id"
  75. # # LEFT OUTER JOIN "comments" ON "comments"."post_id" = "posts"."id"
  76. # # WHERE "authors"."id" IS NULL AND "comments"."id" IS NULL
  77. 3 def missing(*args)
  78. 9 args.each do |arg|
  79. 12 reflection = @scope.klass._reflect_on_association(arg)
  80. 12 opts = { reflection.table_name => { reflection.association_primary_key => nil } }
  81. 12 @scope.left_outer_joins!(arg)
  82. 12 @scope.where!(opts)
  83. end
  84. 9 @scope
  85. end
  86. 3 private
  87. 3 def not_behaves_as_nor?(opts)
  88. 208 return false unless opts.is_a?(Hash)
  89. 413 opts.any? { |k, v| v.is_a?(Hash) && v.size > 1 } ||
  90. opts.size > 1
  91. end
  92. end
  93. 3 FROZEN_EMPTY_ARRAY = [].freeze
  94. 3 FROZEN_EMPTY_HASH = {}.freeze
  95. 3 Relation::VALUE_METHODS.each do |name|
  96. 78 method_name, default =
  97. case name
  98. when *Relation::MULTI_VALUE_METHODS
  99. 39 ["#{name}_values", "FROZEN_EMPTY_ARRAY"]
  100. when *Relation::SINGLE_VALUE_METHODS
  101. 30 ["#{name}_value", name == :create_with ? "FROZEN_EMPTY_HASH" : "nil"]
  102. when *Relation::CLAUSE_METHODS
  103. 9 ["#{name}_clause", name == :from ? "Relation::FromClause.empty" : "Relation::WhereClause.empty"]
  104. end
  105. 78 class_eval <<-CODE, __FILE__, __LINE__ + 1
  106. def #{method_name} # def includes_values
  107. @values.fetch(:#{name}, #{default}) # @values.fetch(:includes, FROZEN_EMPTY_ARRAY)
  108. end # end
  109. def #{method_name}=(value) # def includes_values=(value)
  110. assert_mutability! # assert_mutability!
  111. @values[:#{name}] = value # @values[:includes] = value
  112. end # end
  113. CODE
  114. end
  115. 3 alias extensions extending_values
  116. # Specify relationships to be included in the result set. For
  117. # example:
  118. #
  119. # users = User.includes(:address)
  120. # users.each do |user|
  121. # user.address.city
  122. # end
  123. #
  124. # allows you to access the +address+ attribute of the +User+ model without
  125. # firing an additional query. This will often result in a
  126. # performance improvement over a simple join.
  127. #
  128. # You can also specify multiple relationships, like this:
  129. #
  130. # users = User.includes(:address, :friends)
  131. #
  132. # Loading nested relationships is possible using a Hash:
  133. #
  134. # users = User.includes(:address, friends: [:address, :followers])
  135. #
  136. # === conditions
  137. #
  138. # If you want to add string conditions to your included models, you'll have
  139. # to explicitly reference them. For example:
  140. #
  141. # User.includes(:posts).where('posts.name = ?', 'example')
  142. #
  143. # Will throw an error, but this will work:
  144. #
  145. # User.includes(:posts).where('posts.name = ?', 'example').references(:posts)
  146. #
  147. # Note that #includes works with association names while #references needs
  148. # the actual table name.
  149. #
  150. # If you pass the conditions via hash, you don't need to call #references
  151. # explicitly, as #where references the tables for you. For example, this
  152. # will work correctly:
  153. #
  154. # User.includes(:posts).where(posts: { name: 'example' })
  155. 3 def includes(*args)
  156. 1412 check_if_method_has_arguments!(:includes, args)
  157. 1409 spawn.includes!(*args)
  158. end
  159. 3 def includes!(*args) # :nodoc:
  160. 2027 self.includes_values |= args
  161. 2027 self
  162. end
  163. # Forces eager loading by performing a LEFT OUTER JOIN on +args+:
  164. #
  165. # User.eager_load(:posts)
  166. # # SELECT "users"."id" AS t0_r0, "users"."name" AS t0_r1, ...
  167. # # FROM "users" LEFT OUTER JOIN "posts" ON "posts"."user_id" =
  168. # # "users"."id"
  169. 3 def eager_load(*args)
  170. 243 check_if_method_has_arguments!(:eager_load, args)
  171. 240 spawn.eager_load!(*args)
  172. end
  173. 3 def eager_load!(*args) # :nodoc:
  174. 249 self.eager_load_values |= args
  175. 249 self
  176. end
  177. # Allows preloading of +args+, in the same way that #includes does:
  178. #
  179. # User.preload(:posts)
  180. # # SELECT "posts".* FROM "posts" WHERE "posts"."user_id" IN (1, 2, 3)
  181. 3 def preload(*args)
  182. 240 check_if_method_has_arguments!(:preload, args)
  183. 237 spawn.preload!(*args)
  184. end
  185. 3 def preload!(*args) # :nodoc:
  186. 243 self.preload_values |= args
  187. 243 self
  188. end
  189. # Extracts a named +association+ from the relation. The named association is first preloaded,
  190. # then the individual association records are collected from the relation. Like so:
  191. #
  192. # account.memberships.extract_associated(:user)
  193. # # => Returns collection of User records
  194. #
  195. # This is short-hand for:
  196. #
  197. # account.memberships.preload(:user).collect(&:user)
  198. 3 def extract_associated(association)
  199. 6 preload(association).collect(&association)
  200. end
  201. # Use to indicate that the given +table_names+ are referenced by an SQL string,
  202. # and should therefore be JOINed in any query rather than loaded separately.
  203. # This method only works in conjunction with #includes.
  204. # See #includes for more details.
  205. #
  206. # User.includes(:posts).where("posts.name = 'foo'")
  207. # # Doesn't JOIN the posts table, resulting in an error.
  208. #
  209. # User.includes(:posts).where("posts.name = 'foo'").references(:posts)
  210. # # Query now knows the string references posts, so adds a JOIN
  211. 3 def references(*table_names)
  212. 199 check_if_method_has_arguments!(:references, table_names)
  213. 196 spawn.references!(*table_names)
  214. end
  215. 3 def references!(*table_names) # :nodoc:
  216. 7509 table_names.map!(&:to_s)
  217. 7509 self.references_values |= table_names
  218. 7509 self
  219. end
  220. # Works in two unique ways.
  221. #
  222. # First: takes a block so it can be used just like <tt>Array#select</tt>.
  223. #
  224. # Model.all.select { |m| m.field == value }
  225. #
  226. # This will build an array of objects from the database for the scope,
  227. # converting them into an array and iterating through them using
  228. # <tt>Array#select</tt>.
  229. #
  230. # Second: Modifies the SELECT statement for the query so that only certain
  231. # fields are retrieved:
  232. #
  233. # Model.select(:field)
  234. # # => [#<Model id: nil, field: "value">]
  235. #
  236. # Although in the above example it looks as though this method returns an
  237. # array, it actually returns a relation object and can have other query
  238. # methods appended to it, such as the other methods in ActiveRecord::QueryMethods.
  239. #
  240. # The argument to the method can also be an array of fields.
  241. #
  242. # Model.select(:field, :other_field, :and_one_more)
  243. # # => [#<Model id: nil, field: "value", other_field: "value", and_one_more: "value">]
  244. #
  245. # You can also use one or more strings, which will be used unchanged as SELECT fields.
  246. #
  247. # Model.select('field AS field_one', 'other_field AS field_two')
  248. # # => [#<Model id: nil, field: "value", other_field: "value">]
  249. #
  250. # If an alias was specified, it will be accessible from the resulting objects:
  251. #
  252. # Model.select('field AS field_one').first.field_one
  253. # # => "value"
  254. #
  255. # Accessing attributes of an object that do not have fields retrieved by a select
  256. # except +id+ will throw ActiveModel::MissingAttributeError:
  257. #
  258. # Model.select(:field).first.other_field
  259. # # => ActiveModel::MissingAttributeError: missing attribute: other_field
  260. 3 def select(*fields)
  261. 2898 if block_given?
  262. 1939 if fields.any?
  263. 3 raise ArgumentError, "`select' with block doesn't take arguments."
  264. end
  265. 1936 return super()
  266. end
  267. 959 check_if_method_has_arguments!(:select, fields, "Call `select' with at least one field.")
  268. 956 spawn._select!(*fields)
  269. end
  270. 3 def _select!(*fields) # :nodoc:
  271. 2988 self.select_values |= fields
  272. 2988 self
  273. end
  274. # Allows you to change a previously set select statement.
  275. #
  276. # Post.select(:title, :body)
  277. # # SELECT `posts`.`title`, `posts`.`body` FROM `posts`
  278. #
  279. # Post.select(:title, :body).reselect(:created_at)
  280. # # SELECT `posts`.`created_at` FROM `posts`
  281. #
  282. # This is short-hand for <tt>unscope(:select).select(fields)</tt>.
  283. # Note that we're unscoping the entire select statement.
  284. 3 def reselect(*args)
  285. 15 check_if_method_has_arguments!(:reselect, args)
  286. 12 spawn.reselect!(*args)
  287. end
  288. # Same as #reselect but operates on relation in-place instead of copying.
  289. 3 def reselect!(*args) # :nodoc:
  290. 12 self.select_values = args
  291. 12 self
  292. end
  293. # Allows to specify a group attribute:
  294. #
  295. # User.group(:name)
  296. # # SELECT "users".* FROM "users" GROUP BY name
  297. #
  298. # Returns an array with distinct records based on the +group+ attribute:
  299. #
  300. # User.select([:id, :name])
  301. # # => [#<User id: 1, name: "Oscar">, #<User id: 2, name: "Oscar">, #<User id: 3, name: "Foo">]
  302. #
  303. # User.group(:name)
  304. # # => [#<User id: 3, name: "Foo", ...>, #<User id: 2, name: "Oscar", ...>]
  305. #
  306. # User.group('name AS grouped_name, age')
  307. # # => [#<User id: 3, name: "Foo", age: 21, ...>, #<User id: 2, name: "Oscar", age: 21, ...>, #<User id: 5, name: "Foo", age: 23, ...>]
  308. #
  309. # Passing in an array of attributes to group by is also supported.
  310. #
  311. # User.select([:id, :first_name]).group(:id, :first_name).first(3)
  312. # # => [#<User id: 1, first_name: "Bill">, #<User id: 2, first_name: "Earl">, #<User id: 3, first_name: "Beto">]
  313. 3 def group(*args)
  314. 385 check_if_method_has_arguments!(:group, args)
  315. 382 spawn.group!(*args)
  316. end
  317. 3 def group!(*args) # :nodoc:
  318. 472 self.group_values += args
  319. 472 self
  320. end
  321. # Allows to specify an order attribute:
  322. #
  323. # User.order(:name)
  324. # # SELECT "users".* FROM "users" ORDER BY "users"."name" ASC
  325. #
  326. # User.order(email: :desc)
  327. # # SELECT "users".* FROM "users" ORDER BY "users"."email" DESC
  328. #
  329. # User.order(:name, email: :desc)
  330. # # SELECT "users".* FROM "users" ORDER BY "users"."name" ASC, "users"."email" DESC
  331. #
  332. # User.order('name')
  333. # # SELECT "users".* FROM "users" ORDER BY name
  334. #
  335. # User.order('name DESC')
  336. # # SELECT "users".* FROM "users" ORDER BY name DESC
  337. #
  338. # User.order('name DESC, email')
  339. # # SELECT "users".* FROM "users" ORDER BY name DESC, email
  340. 3 def order(*args)
  341. 9084 check_if_method_has_arguments!(:order, args) do
  342. 9081 sanitize_order_arguments(args)
  343. end
  344. 9078 spawn.order!(*args)
  345. end
  346. # Same as #order but operates on relation in-place instead of copying.
  347. 3 def order!(*args) # :nodoc:
  348. 13411 preprocess_order_args(args) unless args.empty?
  349. 13391 self.order_values |= args
  350. 13391 self
  351. end
  352. # Replaces any existing order defined on the relation with the specified order.
  353. #
  354. # User.order('email DESC').reorder('id ASC') # generated SQL has 'ORDER BY id ASC'
  355. #
  356. # Subsequent calls to order on the same relation will be appended. For example:
  357. #
  358. # User.order('email DESC').reorder('id ASC').order('name ASC')
  359. #
  360. # generates a query with 'ORDER BY id ASC, name ASC'.
  361. 3 def reorder(*args)
  362. 306 check_if_method_has_arguments!(:reorder, args) do
  363. 303 sanitize_order_arguments(args) unless args.all?(&:blank?)
  364. end
  365. 303 spawn.reorder!(*args)
  366. end
  367. # Same as #reorder but operates on relation in-place instead of copying.
  368. 3 def reorder!(*args) # :nodoc:
  369. 318 preprocess_order_args(args) unless args.all?(&:blank?)
  370. 318 self.reordering_value = true
  371. 318 self.order_values = args
  372. 318 self
  373. end
  374. 3 VALID_UNSCOPING_VALUES = Set.new([:where, :select, :group, :order, :lock,
  375. :limit, :offset, :joins, :left_outer_joins, :annotate,
  376. :includes, :from, :readonly, :having, :optimizer_hints])
  377. # Removes an unwanted relation that is already defined on a chain of relations.
  378. # This is useful when passing around chains of relations and would like to
  379. # modify the relations without reconstructing the entire chain.
  380. #
  381. # User.order('email DESC').unscope(:order) == User.all
  382. #
  383. # The method arguments are symbols which correspond to the names of the methods
  384. # which should be unscoped. The valid arguments are given in VALID_UNSCOPING_VALUES.
  385. # The method can also be called with multiple arguments. For example:
  386. #
  387. # User.order('email DESC').select('id').where(name: "John")
  388. # .unscope(:order, :select, :where) == User.all
  389. #
  390. # One can additionally pass a hash as an argument to unscope specific +:where+ values.
  391. # This is done by passing a hash with a single key-value pair. The key should be
  392. # +:where+ and the value should be the where value to unscope. For example:
  393. #
  394. # User.where(name: "John", active: true).unscope(where: :name)
  395. # == User.where(active: true)
  396. #
  397. # This method is similar to #except, but unlike
  398. # #except, it persists across merges:
  399. #
  400. # User.order('email').merge(User.except(:order))
  401. # == User.order('email')
  402. #
  403. # User.order('email').merge(User.unscope(:order))
  404. # == User.all
  405. #
  406. # This means it can be used in association definitions:
  407. #
  408. # has_many :comments, -> { unscope(where: :trashed) }
  409. #
  410. 3 def unscope(*args)
  411. 4135 check_if_method_has_arguments!(:unscope, args)
  412. 4129 spawn.unscope!(*args)
  413. end
  414. 3 def unscope!(*args) # :nodoc:
  415. 6981 self.unscope_values += args
  416. 6981 args.each do |scope|
  417. 4357 case scope
  418. when Symbol
  419. 4012 scope = :left_outer_joins if scope == :left_joins
  420. 4012 if !VALID_UNSCOPING_VALUES.include?(scope)
  421. 9 raise ArgumentError, "Called unscope() with invalid unscoping argument ':#{scope}'. Valid arguments are :#{VALID_UNSCOPING_VALUES.to_a.join(", :")}."
  422. end
  423. 4003 assert_mutability!
  424. 4003 @values.delete(scope)
  425. when Hash
  426. 336 scope.each do |key, target_value|
  427. 336 if key != :where
  428. 6 raise ArgumentError, "Hash arguments in .unscope(*args) must have :where as the key."
  429. end
  430. 330 target_values = resolve_arel_attributes(Array.wrap(target_value))
  431. 330 self.where_clause = where_clause.except(*target_values)
  432. end
  433. else
  434. 9 raise ArgumentError, "Unrecognized scoping: #{args.inspect}. Use .unscope(where: :attribute_name) or .unscope(:order), for example."
  435. end
  436. end
  437. 6957 self
  438. end
  439. # Performs a joins on +args+. The given symbol(s) should match the name of
  440. # the association(s).
  441. #
  442. # User.joins(:posts)
  443. # # SELECT "users".*
  444. # # FROM "users"
  445. # # INNER JOIN "posts" ON "posts"."user_id" = "users"."id"
  446. #
  447. # Multiple joins:
  448. #
  449. # User.joins(:posts, :account)
  450. # # SELECT "users".*
  451. # # FROM "users"
  452. # # INNER JOIN "posts" ON "posts"."user_id" = "users"."id"
  453. # # INNER JOIN "accounts" ON "accounts"."id" = "users"."account_id"
  454. #
  455. # Nested joins:
  456. #
  457. # User.joins(posts: [:comments])
  458. # # SELECT "users".*
  459. # # FROM "users"
  460. # # INNER JOIN "posts" ON "posts"."user_id" = "users"."id"
  461. # # INNER JOIN "comments" ON "comments"."post_id" = "posts"."id"
  462. #
  463. # You can use strings in order to customize your joins:
  464. #
  465. # User.joins("LEFT JOIN bookmarks ON bookmarks.bookmarkable_type = 'Post' AND bookmarks.user_id = users.id")
  466. # # SELECT "users".* FROM "users" LEFT JOIN bookmarks ON bookmarks.bookmarkable_type = 'Post' AND bookmarks.user_id = users.id
  467. 3 def joins(*args)
  468. 927 check_if_method_has_arguments!(:joins, args)
  469. 924 spawn.joins!(*args)
  470. end
  471. 3 def joins!(*args) # :nodoc:
  472. 4916 self.joins_values |= args
  473. 4916 self
  474. end
  475. # Performs a left outer joins on +args+:
  476. #
  477. # User.left_outer_joins(:posts)
  478. # => SELECT "users".* FROM "users" LEFT OUTER JOIN "posts" ON "posts"."user_id" = "users"."id"
  479. #
  480. 3 def left_outer_joins(*args)
  481. 207 check_if_method_has_arguments!(__callee__, args)
  482. 204 spawn.left_outer_joins!(*args)
  483. end
  484. 3 alias :left_joins :left_outer_joins
  485. 3 def left_outer_joins!(*args) # :nodoc:
  486. 279 self.left_outer_joins_values |= args
  487. 279 self
  488. end
  489. # Returns a new relation, which is the result of filtering the current relation
  490. # according to the conditions in the arguments.
  491. #
  492. # #where accepts conditions in one of several formats. In the examples below, the resulting
  493. # SQL is given as an illustration; the actual query generated may be different depending
  494. # on the database adapter.
  495. #
  496. # === string
  497. #
  498. # A single string, without additional arguments, is passed to the query
  499. # constructor as an SQL fragment, and used in the where clause of the query.
  500. #
  501. # Client.where("orders_count = '2'")
  502. # # SELECT * from clients where orders_count = '2';
  503. #
  504. # Note that building your own string from user input may expose your application
  505. # to injection attacks if not done properly. As an alternative, it is recommended
  506. # to use one of the following methods.
  507. #
  508. # === array
  509. #
  510. # If an array is passed, then the first element of the array is treated as a template, and
  511. # the remaining elements are inserted into the template to generate the condition.
  512. # Active Record takes care of building the query to avoid injection attacks, and will
  513. # convert from the ruby type to the database type where needed. Elements are inserted
  514. # into the string in the order in which they appear.
  515. #
  516. # User.where(["name = ? and email = ?", "Joe", "joe@example.com"])
  517. # # SELECT * FROM users WHERE name = 'Joe' AND email = 'joe@example.com';
  518. #
  519. # Alternatively, you can use named placeholders in the template, and pass a hash as the
  520. # second element of the array. The names in the template are replaced with the corresponding
  521. # values from the hash.
  522. #
  523. # User.where(["name = :name and email = :email", { name: "Joe", email: "joe@example.com" }])
  524. # # SELECT * FROM users WHERE name = 'Joe' AND email = 'joe@example.com';
  525. #
  526. # This can make for more readable code in complex queries.
  527. #
  528. # Lastly, you can use sprintf-style % escapes in the template. This works slightly differently
  529. # than the previous methods; you are responsible for ensuring that the values in the template
  530. # are properly quoted. The values are passed to the connector for quoting, but the caller
  531. # is responsible for ensuring they are enclosed in quotes in the resulting SQL. After quoting,
  532. # the values are inserted using the same escapes as the Ruby core method +Kernel::sprintf+.
  533. #
  534. # User.where(["name = '%s' and email = '%s'", "Joe", "joe@example.com"])
  535. # # SELECT * FROM users WHERE name = 'Joe' AND email = 'joe@example.com';
  536. #
  537. # If #where is called with multiple arguments, these are treated as if they were passed as
  538. # the elements of a single array.
  539. #
  540. # User.where("name = :name and email = :email", { name: "Joe", email: "joe@example.com" })
  541. # # SELECT * FROM users WHERE name = 'Joe' AND email = 'joe@example.com';
  542. #
  543. # When using strings to specify conditions, you can use any operator available from
  544. # the database. While this provides the most flexibility, you can also unintentionally introduce
  545. # dependencies on the underlying database. If your code is intended for general consumption,
  546. # test with multiple database backends.
  547. #
  548. # === hash
  549. #
  550. # #where will also accept a hash condition, in which the keys are fields and the values
  551. # are values to be searched for.
  552. #
  553. # Fields can be symbols or strings. Values can be single values, arrays, or ranges.
  554. #
  555. # User.where({ name: "Joe", email: "joe@example.com" })
  556. # # SELECT * FROM users WHERE name = 'Joe' AND email = 'joe@example.com'
  557. #
  558. # User.where({ name: ["Alice", "Bob"]})
  559. # # SELECT * FROM users WHERE name IN ('Alice', 'Bob')
  560. #
  561. # User.where({ created_at: (Time.now.midnight - 1.day)..Time.now.midnight })
  562. # # SELECT * FROM users WHERE (created_at BETWEEN '2012-06-09 07:00:00.000000' AND '2012-06-10 07:00:00.000000')
  563. #
  564. # In the case of a belongs_to relationship, an association key can be used
  565. # to specify the model if an ActiveRecord object is used as the value.
  566. #
  567. # author = Author.find(1)
  568. #
  569. # # The following queries will be equivalent:
  570. # Post.where(author: author)
  571. # Post.where(author_id: author)
  572. #
  573. # This also works with polymorphic belongs_to relationships:
  574. #
  575. # treasure = Treasure.create(name: 'gold coins')
  576. # treasure.price_estimates << PriceEstimate.create(price: 125)
  577. #
  578. # # The following queries will be equivalent:
  579. # PriceEstimate.where(estimate_of: treasure)
  580. # PriceEstimate.where(estimate_of_type: 'Treasure', estimate_of_id: treasure)
  581. #
  582. # === Joins
  583. #
  584. # If the relation is the result of a join, you may create a condition which uses any of the
  585. # tables in the join. For string and array conditions, use the table name in the condition.
  586. #
  587. # User.joins(:posts).where("posts.created_at < ?", Time.now)
  588. #
  589. # For hash conditions, you can either use the table name in the key, or use a sub-hash.
  590. #
  591. # User.joins(:posts).where({ "posts.published" => true })
  592. # User.joins(:posts).where({ posts: { published: true } })
  593. #
  594. # === no argument
  595. #
  596. # If no argument is passed, #where returns a new instance of WhereChain, that
  597. # can be chained with #not to return a new relation that negates the where clause.
  598. #
  599. # User.where.not(name: "Jon")
  600. # # SELECT * FROM users WHERE name != 'Jon'
  601. #
  602. # See WhereChain for more details on #not.
  603. #
  604. # === blank condition
  605. #
  606. # If the condition is any blank-ish object, then #where is a no-op and returns
  607. # the current relation.
  608. 3 def where(opts = :chain, *rest)
  609. 30560 if :chain == opts
  610. 226 WhereChain.new(spawn)
  611. 30334 elsif opts.blank?
  612. 237 self
  613. else
  614. 30097 spawn.where!(opts, *rest)
  615. end
  616. end
  617. 3 def where!(opts, *rest) # :nodoc:
  618. 56495 self.where_clause += build_where_clause(opts, rest)
  619. 56462 self
  620. end
  621. # Allows you to change a previously set where condition for a given attribute, instead of appending to that condition.
  622. #
  623. # Post.where(trashed: true).where(trashed: false)
  624. # # WHERE `trashed` = 1 AND `trashed` = 0
  625. #
  626. # Post.where(trashed: true).rewhere(trashed: false)
  627. # # WHERE `trashed` = 0
  628. #
  629. # Post.where(active: true).where(trashed: true).rewhere(trashed: false)
  630. # # WHERE `active` = 1 AND `trashed` = 0
  631. #
  632. # This is short-hand for <tt>unscope(where: conditions.keys).where(conditions)</tt>.
  633. # Note that unlike reorder, we're only unscoping the named conditions -- not the entire where statement.
  634. 3 def rewhere(conditions)
  635. 81 scope = spawn
  636. 81 where_clause = scope.build_where_clause(conditions)
  637. 81 scope.unscope!(where: where_clause.extract_attributes)
  638. 81 scope.where_clause += where_clause
  639. 81 scope
  640. end
  641. # Returns a new relation, which is the logical intersection of this relation and the one passed
  642. # as an argument.
  643. #
  644. # The two relations must be structurally compatible: they must be scoping the same model, and
  645. # they must differ only by #where (if no #group has been defined) or #having (if a #group is
  646. # present).
  647. #
  648. # Post.where(id: [1, 2]).and(Post.where(id: [2, 3]))
  649. # # SELECT `posts`.* FROM `posts` WHERE `posts`.`id` IN (1, 2) AND `posts`.`id` IN (2, 3)
  650. #
  651. 3 def and(other)
  652. 27 if other.is_a?(Relation)
  653. 27 spawn.and!(other)
  654. else
  655. raise ArgumentError, "You have passed #{other.class.name} object to #and. Pass an ActiveRecord::Relation object instead."
  656. end
  657. end
  658. 3 def and!(other) # :nodoc:
  659. 27 incompatible_values = structurally_incompatible_values_for(other)
  660. 27 unless incompatible_values.empty?
  661. raise ArgumentError, "Relation passed to #and must be structurally compatible. Incompatible values: #{incompatible_values}"
  662. end
  663. 27 self.where_clause |= other.where_clause
  664. 27 self.having_clause |= other.having_clause
  665. 27 self.references_values |= other.references_values
  666. 27 self
  667. end
  668. # Returns a new relation, which is the logical union of this relation and the one passed as an
  669. # argument.
  670. #
  671. # The two relations must be structurally compatible: they must be scoping the same model, and
  672. # they must differ only by #where (if no #group has been defined) or #having (if a #group is
  673. # present).
  674. #
  675. # Post.where("id = 1").or(Post.where("author_id = 3"))
  676. # # SELECT `posts`.* FROM `posts` WHERE ((id = 1) OR (author_id = 3))
  677. #
  678. 3 def or(other)
  679. 6149 if other.is_a?(Relation)
  680. 6146 spawn.or!(other)
  681. else
  682. 3 raise ArgumentError, "You have passed #{other.class.name} object to #or. Pass an ActiveRecord::Relation object instead."
  683. end
  684. end
  685. 3 def or!(other) # :nodoc:
  686. 6146 incompatible_values = structurally_incompatible_values_for(other)
  687. 6146 unless incompatible_values.empty?
  688. 6 raise ArgumentError, "Relation passed to #or must be structurally compatible. Incompatible values: #{incompatible_values}"
  689. end
  690. 6140 self.where_clause = self.where_clause.or(other.where_clause)
  691. 6140 self.having_clause = having_clause.or(other.having_clause)
  692. 6140 self.references_values |= other.references_values
  693. 6140 self
  694. end
  695. # Allows to specify a HAVING clause. Note that you can't use HAVING
  696. # without also specifying a GROUP clause.
  697. #
  698. # Order.having('SUM(price) > 30').group('user_id')
  699. 3 def having(opts, *rest)
  700. 77 opts.blank? ? self : spawn.having!(opts, *rest)
  701. end
  702. 3 def having!(opts, *rest) # :nodoc:
  703. 71 self.having_clause += build_having_clause(opts, rest)
  704. 68 self
  705. end
  706. # Specifies a limit for the number of records to retrieve.
  707. #
  708. # User.limit(10) # generated SQL has 'LIMIT 10'
  709. #
  710. # User.limit(10).limit(20) # generated SQL has 'LIMIT 20'
  711. 3 def limit(value)
  712. 17545 spawn.limit!(value)
  713. end
  714. 3 def limit!(value) # :nodoc:
  715. 25089 self.limit_value = value
  716. 25083 self
  717. end
  718. # Specifies the number of rows to skip before returning rows.
  719. #
  720. # User.offset(10) # generated SQL has "OFFSET 10"
  721. #
  722. # Should be used with order.
  723. #
  724. # User.offset(10).order("name ASC")
  725. 3 def offset(value)
  726. 303 spawn.offset!(value)
  727. end
  728. 3 def offset!(value) # :nodoc:
  729. 390 self.offset_value = value
  730. 390 self
  731. end
  732. # Specifies locking settings (default to +true+). For more information
  733. # on locking, please see ActiveRecord::Locking.
  734. 3 def lock(locks = true)
  735. 38 spawn.lock!(locks)
  736. end
  737. 3 def lock!(locks = true) # :nodoc:
  738. 44 case locks
  739. when String, TrueClass, NilClass
  740. 41 self.lock_value = locks || true
  741. else
  742. 3 self.lock_value = false
  743. end
  744. 44 self
  745. end
  746. # Returns a chainable relation with zero records.
  747. #
  748. # The returned relation implements the Null Object pattern. It is an
  749. # object with defined null behavior and always returns an empty array of
  750. # records without querying the database.
  751. #
  752. # Any subsequent condition chained to the returned relation will continue
  753. # generating an empty relation and will not fire any query to the database.
  754. #
  755. # Used in cases where a method or scope could return zero records but the
  756. # result needs to be chainable.
  757. #
  758. # For example:
  759. #
  760. # @posts = current_user.visible_posts.where(name: params[:name])
  761. # # the visible_posts method is expected to return a chainable Relation
  762. #
  763. # def visible_posts
  764. # case role
  765. # when 'Country Manager'
  766. # Post.where(country: country)
  767. # when 'Reviewer'
  768. # Post.published
  769. # when 'Bad User'
  770. # Post.none # It can't be chained if [] is returned.
  771. # end
  772. # end
  773. #
  774. 3 def none
  775. 135 spawn.none!
  776. end
  777. 3 def none! # :nodoc:
  778. 925 where!("1=0").extending!(NullRelation)
  779. end
  780. # Sets readonly attributes for the returned relation. If value is
  781. # true (default), attempting to update a record will result in an error.
  782. #
  783. # users = User.readonly
  784. # users.first.save
  785. # => ActiveRecord::ReadOnlyRecord: User is marked as readonly
  786. 3 def readonly(value = true)
  787. 153 spawn.readonly!(value)
  788. end
  789. 3 def readonly!(value = true) # :nodoc:
  790. 237 self.readonly_value = value
  791. 237 self
  792. end
  793. # Sets the returned relation to strict_loading mode. This will raise an error
  794. # if the record tries to lazily load an association.
  795. #
  796. # user = User.strict_loading.first
  797. # user.comments.to_a
  798. # => ActiveRecord::StrictLoadingViolationError
  799. 3 def strict_loading(value = true)
  800. 33 spawn.strict_loading!(value)
  801. end
  802. 3 def strict_loading!(value = true) # :nodoc:
  803. 39 self.strict_loading_value = value
  804. 39 self
  805. end
  806. # Sets attributes to be used when creating new records from a
  807. # relation object.
  808. #
  809. # users = User.where(name: 'Oscar')
  810. # users.new.name # => 'Oscar'
  811. #
  812. # users = users.create_with(name: 'DHH')
  813. # users.new.name # => 'DHH'
  814. #
  815. # You can pass +nil+ to #create_with to reset attributes:
  816. #
  817. # users = users.create_with(nil)
  818. # users.new.name # => 'Oscar'
  819. 3 def create_with(value)
  820. 90 spawn.create_with!(value)
  821. end
  822. 3 def create_with!(value) # :nodoc:
  823. 93 if value
  824. 90 value = sanitize_forbidden_attributes(value)
  825. 87 self.create_with_value = create_with_value.merge(value)
  826. else
  827. 3 self.create_with_value = FROZEN_EMPTY_HASH
  828. end
  829. 90 self
  830. end
  831. # Specifies table from which the records will be fetched. For example:
  832. #
  833. # Topic.select('title').from('posts')
  834. # # SELECT title FROM posts
  835. #
  836. # Can accept other relation objects. For example:
  837. #
  838. # Topic.select('title').from(Topic.approved)
  839. # # SELECT title FROM (SELECT * FROM topics WHERE approved = 't') subquery
  840. #
  841. # Topic.select('a.title').from(Topic.approved, :a)
  842. # # SELECT a.title FROM (SELECT * FROM topics WHERE approved = 't') a
  843. #
  844. 3 def from(value, subquery_name = nil)
  845. 135 spawn.from!(value, subquery_name)
  846. end
  847. 3 def from!(value, subquery_name = nil) # :nodoc:
  848. 138 self.from_clause = Relation::FromClause.new(value, subquery_name)
  849. 138 self
  850. end
  851. # Specifies whether the records should be unique or not. For example:
  852. #
  853. # User.select(:name)
  854. # # Might return two records with the same name
  855. #
  856. # User.select(:name).distinct
  857. # # Returns 1 record per distinct name
  858. #
  859. # User.select(:name).distinct.distinct(false)
  860. # # You can also remove the uniqueness
  861. 3 def distinct(value = true)
  862. 571 spawn.distinct!(value)
  863. end
  864. # Like #distinct, but modifies relation in place.
  865. 3 def distinct!(value = true) # :nodoc:
  866. 5428 self.distinct_value = value
  867. 5428 self
  868. end
  869. # Used to extend a scope with additional methods, either through
  870. # a module or through a block provided.
  871. #
  872. # The object returned is a relation, which can be further extended.
  873. #
  874. # === Using a module
  875. #
  876. # module Pagination
  877. # def page(number)
  878. # # pagination code goes here
  879. # end
  880. # end
  881. #
  882. # scope = Model.all.extending(Pagination)
  883. # scope.page(params[:page])
  884. #
  885. # You can also pass a list of modules:
  886. #
  887. # scope = Model.all.extending(Pagination, SomethingElse)
  888. #
  889. # === Using a block
  890. #
  891. # scope = Model.all.extending do
  892. # def page(number)
  893. # # pagination code goes here
  894. # end
  895. # end
  896. # scope.page(params[:page])
  897. #
  898. # You can also use a block and a module list:
  899. #
  900. # scope = Model.all.extending(Pagination) do
  901. # def per_page(number)
  902. # # pagination code goes here
  903. # end
  904. # end
  905. 3 def extending(*modules, &block)
  906. 2904 if modules.any? || block
  907. 2904 spawn.extending!(*modules, &block)
  908. else
  909. self
  910. end
  911. end
  912. 3 def extending!(*modules, &block) # :nodoc:
  913. 16627 modules << Module.new(&block) if block
  914. 16627 modules.flatten!
  915. 16627 self.extending_values += modules
  916. 16624 extend(*extending_values) if extending_values.any?
  917. 16624 self
  918. end
  919. # Specify optimizer hints to be used in the SELECT statement.
  920. #
  921. # Example (for MySQL):
  922. #
  923. # Topic.optimizer_hints("MAX_EXECUTION_TIME(50000)", "NO_INDEX_MERGE(topics)")
  924. # # SELECT /*+ MAX_EXECUTION_TIME(50000) NO_INDEX_MERGE(topics) */ `topics`.* FROM `topics`
  925. #
  926. # Example (for PostgreSQL with pg_hint_plan):
  927. #
  928. # Topic.optimizer_hints("SeqScan(topics)", "Parallel(topics 8)")
  929. # # SELECT /*+ SeqScan(topics) Parallel(topics 8) */ "topics".* FROM "topics"
  930. 3 def optimizer_hints(*args)
  931. 21 check_if_method_has_arguments!(:optimizer_hints, args)
  932. 18 spawn.optimizer_hints!(*args)
  933. end
  934. 3 def optimizer_hints!(*args) # :nodoc:
  935. 24 self.optimizer_hints_values |= args
  936. 24 self
  937. end
  938. # Reverse the existing order clause on the relation.
  939. #
  940. # User.order('name ASC').reverse_order # generated SQL has 'ORDER BY name DESC'
  941. 3 def reverse_order
  942. 80 spawn.reverse_order!
  943. end
  944. 3 def reverse_order! # :nodoc:
  945. 377 orders = order_values.compact_blank
  946. 377 self.order_values = reverse_sql_order(orders)
  947. 354 self
  948. end
  949. 3 def skip_query_cache!(value = true) # :nodoc:
  950. 291 self.skip_query_cache_value = value
  951. 291 self
  952. end
  953. 3 def skip_preloading! # :nodoc:
  954. 6 self.skip_preloading_value = true
  955. 6 self
  956. end
  957. # Adds an SQL comment to queries generated from this relation. For example:
  958. #
  959. # User.annotate("selecting user names").select(:name)
  960. # # SELECT "users"."name" FROM "users" /* selecting user names */
  961. #
  962. # User.annotate("selecting", "user", "names").select(:name)
  963. # # SELECT "users"."name" FROM "users" /* selecting */ /* user */ /* names */
  964. #
  965. # The SQL block comment delimiters, "/*" and "*/", will be added automatically.
  966. 3 def annotate(*args)
  967. 144 check_if_method_has_arguments!(:annotate, args)
  968. 141 spawn.annotate!(*args)
  969. end
  970. # Like #annotate, but modifies relation in place.
  971. 3 def annotate!(*args) # :nodoc:
  972. 210 self.annotate_values += args
  973. 210 self
  974. end
  975. # Deduplicate multiple values.
  976. 3 def uniq!(name)
  977. 6 if values = @values[name]
  978. 6 values.uniq! if values.is_a?(Array) && !values.empty?
  979. end
  980. 6 self
  981. end
  982. # Returns the Arel object associated with the relation.
  983. 3 def arel(aliases = nil) # :nodoc:
  984. 57617 @arel ||= build_arel(aliases)
  985. end
  986. 3 def construct_join_dependency(associations, join_type) # :nodoc:
  987. 4018 ActiveRecord::Associations::JoinDependency.new(
  988. klass, table, associations, join_type
  989. )
  990. end
  991. 3 protected
  992. 3 def build_subquery(subquery_alias, select_value) # :nodoc:
  993. 273 subquery = except(:optimizer_hints).arel.as(subquery_alias)
  994. 273 Arel::SelectManager.new(subquery).project(select_value).tap do |arel|
  995. 273 arel.optimizer_hints(*optimizer_hints_values) unless optimizer_hints_values.empty?
  996. end
  997. end
  998. 3 def build_where_clause(opts, rest = []) # :nodoc:
  999. 56864 opts = sanitize_forbidden_attributes(opts)
  1000. 56852 case opts
  1001. when String, Array
  1002. 2419 parts = [klass.sanitize_sql(rest.empty? ? opts : [opts, *rest])]
  1003. when Hash
  1004. 42337 opts = opts.stringify_keys
  1005. 42337 references = PredicateBuilder.references(opts)
  1006. 42337 self.references_values |= references unless references.empty?
  1007. 42337 parts = predicate_builder.build_from_hash(opts) do |table_name|
  1008. 493 lookup_reflection_from_join_dependencies(table_name)
  1009. end
  1010. when Arel::Nodes::Node
  1011. 12090 parts = [opts]
  1012. else
  1013. 6 raise ArgumentError, "Unsupported argument type: #{opts} (#{opts.class})"
  1014. end
  1015. 56828 Relation::WhereClause.new(parts)
  1016. end
  1017. 3 alias :build_having_clause :build_where_clause
  1018. 3 private
  1019. 3 def lookup_reflection_from_join_dependencies(table_name)
  1020. 520 each_join_dependencies do |join|
  1021. 640 return join.reflection if table_name == join.table_name
  1022. end
  1023. nil
  1024. end
  1025. 3 def each_join_dependencies(join_dependencies = build_join_dependencies)
  1026. 598 join_dependencies.each do |join_dependency|
  1027. 628 join_dependency.each do |join|
  1028. 841 yield join
  1029. end
  1030. end
  1031. end
  1032. 3 def build_join_dependencies
  1033. 595 associations = joins_values | left_outer_joins_values
  1034. 595 associations |= eager_load_values unless eager_load_values.empty?
  1035. 595 associations |= includes_values unless includes_values.empty?
  1036. 595 join_dependencies = []
  1037. 595 join_dependencies.unshift construct_join_dependency(
  1038. select_association_list(associations, join_dependencies), nil
  1039. )
  1040. end
  1041. 3 def assert_mutability!
  1042. 220434 raise ImmutableRelation if @loaded
  1043. 220422 raise ImmutableRelation if defined?(@arel) && @arel
  1044. end
  1045. 3 def build_arel(aliases)
  1046. 41345 arel = Arel::SelectManager.new(table)
  1047. 41345 build_joins(arel, aliases)
  1048. 41342 arel.where(where_clause.ast) unless where_clause.empty?
  1049. 41342 arel.having(having_clause.ast) unless having_clause.empty?
  1050. 41342 arel.take(build_cast_value("LIMIT", connection.sanitize_limit(limit_value))) if limit_value
  1051. 41333 arel.skip(build_cast_value("OFFSET", offset_value.to_i)) if offset_value
  1052. 41333 arel.group(*arel_columns(group_values.uniq)) unless group_values.empty?
  1053. 41333 build_order(arel)
  1054. 41333 build_select(arel)
  1055. 41333 arel.optimizer_hints(*optimizer_hints_values) unless optimizer_hints_values.empty?
  1056. 41333 arel.distinct(distinct_value)
  1057. 41333 arel.from(build_from) unless from_clause.empty?
  1058. 41333 arel.lock(lock_value) if lock_value
  1059. 41333 unless annotate_values.empty?
  1060. 87 annotates = annotate_values
  1061. 87 annotates = annotates.uniq if annotates.size > 1
  1062. 87 unless annotates == annotate_values
  1063. 9 ActiveSupport::Deprecation.warn(<<-MSG.squish)
  1064. Duplicated query annotations are no longer shown in queries in Rails 6.2.
  1065. To migrate to Rails 6.2's behavior, use `uniq!(:annotate)` to deduplicate query annotations
  1066. (`#{klass.name&.tableize || klass.table_name}.uniq!(:annotate)`).
  1067. MSG
  1068. 9 annotates = annotate_values
  1069. end
  1070. 87 arel.comment(*annotates)
  1071. end
  1072. 41333 arel
  1073. end
  1074. 3 def build_cast_value(name, value)
  1075. 20666 cast_value = ActiveModel::Attribute.with_cast_value(name, value, Type.default_value)
  1076. 20666 Arel::Nodes::BindParam.new(cast_value)
  1077. end
  1078. 3 def build_from
  1079. 123 opts = from_clause.value
  1080. 123 name = from_clause.name
  1081. 123 case opts
  1082. when Relation
  1083. 54 if opts.eager_loading?
  1084. 9 opts = opts.send(:apply_join_dependency)
  1085. end
  1086. 54 name ||= "subquery"
  1087. 54 opts.arel.as(name.to_s)
  1088. else
  1089. 69 opts
  1090. end
  1091. end
  1092. 3 def select_association_list(associations, stashed_joins = nil)
  1093. 5186 result = []
  1094. 5186 associations.each do |association|
  1095. 1377 case association
  1096. when Hash, Symbol, Array
  1097. 1200 result << association
  1098. when ActiveRecord::Associations::JoinDependency
  1099. 123 stashed_joins&.<< association
  1100. else
  1101. 54 yield association if block_given?
  1102. end
  1103. end
  1104. 5183 result
  1105. end
  1106. 3 class ::Arel::Nodes::LeadingJoin < Arel::Nodes::InnerJoin # :nodoc:
  1107. end
  1108. 3 def build_join_buckets
  1109. 16234 buckets = Hash.new { |h, k| h[k] = [] }
  1110. 4093 unless left_outer_joins_values.empty?
  1111. 180 stashed_left_joins = []
  1112. 180 left_joins = select_association_list(left_outer_joins_values, stashed_left_joins) do
  1113. 3 raise ArgumentError, "only Hash, Symbol and Array are allowed"
  1114. end
  1115. 177 if joins_values.empty?
  1116. 129 buckets[:association_join] = left_joins
  1117. 129 buckets[:stashed_join] = stashed_left_joins
  1118. 129 return buckets, Arel::Nodes::OuterJoin
  1119. else
  1120. 48 stashed_left_joins.unshift construct_join_dependency(left_joins, Arel::Nodes::OuterJoin)
  1121. end
  1122. end
  1123. 3961 joins = joins_values.dup
  1124. 3961 if joins.last.is_a?(ActiveRecord::Associations::JoinDependency)
  1125. 1211 stashed_eager_load = joins.pop if joins.last.base_klass == klass
  1126. end
  1127. 3961 joins.each_with_index do |join, i|
  1128. 3242 joins[i] = Arel::Nodes::StringJoin.new(Arel.sql(join.strip)) if join.is_a?(String)
  1129. end
  1130. 3961 while joins.first.is_a?(Arel::Nodes::Join)
  1131. 2255 join_node = joins.shift
  1132. 2255 if !join_node.is_a?(Arel::Nodes::LeadingJoin) && (stashed_eager_load || stashed_left_joins)
  1133. 18 buckets[:join_node] << join_node
  1134. else
  1135. 2237 buckets[:leading_join] << join_node
  1136. end
  1137. end
  1138. 3961 buckets[:association_join] = select_association_list(joins, buckets[:stashed_join]) do |join|
  1139. 18 if join.is_a?(Arel::Nodes::Join)
  1140. 18 buckets[:join_node] << join
  1141. else
  1142. raise "unknown class: %s" % join.class.name
  1143. end
  1144. end
  1145. 3961 buckets[:stashed_join].concat stashed_left_joins if stashed_left_joins
  1146. 3961 buckets[:stashed_join] << stashed_eager_load if stashed_eager_load
  1147. 3961 return buckets, Arel::Nodes::InnerJoin
  1148. end
  1149. 3 def build_joins(manager, aliases)
  1150. 41345 return if joins_values.empty? && left_outer_joins_values.empty?
  1151. 4093 buckets, join_type = build_join_buckets
  1152. 4090 association_joins = buckets[:association_join]
  1153. 4090 stashed_joins = buckets[:stashed_join]
  1154. 4090 leading_joins = buckets[:leading_join]
  1155. 4090 join_nodes = buckets[:join_node]
  1156. 4090 join_sources = manager.join_sources
  1157. 4090 join_sources.concat(leading_joins) unless leading_joins.empty?
  1158. 4090 unless association_joins.empty? && stashed_joins.empty?
  1159. 2114 alias_tracker = alias_tracker(leading_joins + join_nodes, aliases)
  1160. 2114 join_dependency = construct_join_dependency(association_joins, join_type)
  1161. 2114 join_sources.concat(join_dependency.join_constraints(stashed_joins, alias_tracker))
  1162. end
  1163. 4090 join_sources.concat(join_nodes) unless join_nodes.empty?
  1164. end
  1165. 3 def build_select(arel)
  1166. 41333 if select_values.any?
  1167. 8814 arel.project(*arel_columns(select_values))
  1168. 32519 elsif klass.ignored_columns.any?
  1169. 25220 arel.project(*klass.column_names.map { |field| table[field] })
  1170. else
  1171. 29997 arel.project(table[Arel.star])
  1172. end
  1173. end
  1174. 3 def arel_columns(columns)
  1175. 11353 columns.flat_map do |field|
  1176. 11892 case field
  1177. when Symbol
  1178. 1778 arel_column(field.to_s) do |attr_name|
  1179. 66 connection.quote_table_name(attr_name)
  1180. end
  1181. when String
  1182. 3121 arel_column(field, &:itself)
  1183. when Proc
  1184. 535 field.call
  1185. else
  1186. 6458 field
  1187. end
  1188. end
  1189. end
  1190. 3 def arel_column(field)
  1191. 10419 field = klass.attribute_aliases[field] || field
  1192. 10419 from = from_clause.name || from_clause.value
  1193. 10419 if klass.columns_hash.key?(field) && (!from || table_name_matches?(from))
  1194. 4660 table[field]
  1195. 5759 elsif field.match?(/\A\w+\.\w+\z/)
  1196. 180 table, column = field.split(".")
  1197. 180 predicate_builder.resolve_arel_attribute(table, column) do
  1198. 27 lookup_reflection_from_join_dependencies(table)
  1199. end
  1200. else
  1201. 5579 yield field
  1202. end
  1203. end
  1204. 3 def table_name_matches?(from)
  1205. 90 table_name = Regexp.escape(table.name)
  1206. 90 quoted_table_name = Regexp.escape(connection.quote_table_name(table.name))
  1207. 90 /(?:\A|(?<!FROM)\s)(?:\b#{table_name}\b|#{quoted_table_name})(?!\.)/i.match?(from.to_s)
  1208. end
  1209. 3 def reverse_sql_order(order_query)
  1210. 377 if order_query.empty?
  1211. 3 return [table[primary_key].desc] if primary_key
  1212. 3 raise IrreversibleOrderError,
  1213. "Relation has no current order and table has no primary key to be used as default order"
  1214. end
  1215. 374 order_query.flat_map do |o|
  1216. 395 case o
  1217. when Arel::Attribute
  1218. o.desc
  1219. when Arel::Nodes::Ordering
  1220. 279 o.reverse
  1221. when String
  1222. 116 if does_not_support_reverse?(o)
  1223. 20 raise IrreversibleOrderError, "Order #{o.inspect} cannot be reversed automatically"
  1224. end
  1225. 96 o.split(",").map! do |s|
  1226. 120 s.strip!
  1227. 120 s.gsub!(/\sasc\Z/i, " DESC") || s.gsub!(/\sdesc\Z/i, " ASC") || (s << " DESC")
  1228. end
  1229. else
  1230. o
  1231. end
  1232. end
  1233. end
  1234. 3 def does_not_support_reverse?(order)
  1235. # Account for String subclasses like Arel::Nodes::SqlLiteral that
  1236. # override methods like #count.
  1237. 116 order = String.new(order) unless order.instance_of?(String)
  1238. # Uses SQL function with multiple arguments.
  1239. 177 (order.include?(",") && order.split(",").find { |section| section.count("(") != section.count(")") }) ||
  1240. # Uses "nulls first" like construction.
  1241. /\bnulls\s+(?:first|last)\b/i.match?(order)
  1242. end
  1243. 3 def build_order(arel)
  1244. 41333 orders = order_values.compact_blank
  1245. 41333 arel.order(*orders) unless orders.empty?
  1246. end
  1247. 3 VALID_DIRECTIONS = [:asc, :desc, :ASC, :DESC,
  1248. "asc", "desc", "ASC", "DESC"].to_set # :nodoc:
  1249. 3 def validate_order_args(args)
  1250. 13670 args.each do |arg|
  1251. 13769 next unless arg.is_a?(Hash)
  1252. 207 arg.each do |_key, value|
  1253. 213 unless VALID_DIRECTIONS.include?(value)
  1254. 6 raise ArgumentError,
  1255. "Direction \"#{value}\" is invalid. Valid directions are: #{VALID_DIRECTIONS.to_a.inspect}"
  1256. end
  1257. end
  1258. end
  1259. end
  1260. 3 def preprocess_order_args(order_args)
  1261. 13684 @klass.disallow_raw_sql!(
  1262. 13786 order_args.flat_map { |a| a.is_a?(Hash) ? a.keys : a },
  1263. permit: connection.column_name_with_order_matcher
  1264. )
  1265. 13670 validate_order_args(order_args)
  1266. 13664 references = column_references(order_args)
  1267. 13664 self.references_values |= references unless references.empty?
  1268. # if a symbol is given we prepend the quoted table name
  1269. order_args.map! do |arg|
  1270. 13757 case arg
  1271. when Symbol
  1272. 1327 order_column(arg.to_s).asc
  1273. when Hash
  1274. 201 arg.map { |field, dir|
  1275. 207 case field
  1276. when Arel::Nodes::SqlLiteral
  1277. 24 field.send(dir.downcase)
  1278. else
  1279. 183 order_column(field.to_s).send(dir.downcase)
  1280. end
  1281. }
  1282. else
  1283. 12229 arg
  1284. end
  1285. 13664 end.flatten!
  1286. end
  1287. 3 def sanitize_order_arguments(order_args)
  1288. 9369 order_args.map! do |arg|
  1289. 9423 klass.sanitize_sql_for_order(arg)
  1290. end
  1291. 9366 order_args.flatten!
  1292. 9366 order_args.compact_blank!
  1293. end
  1294. 3 def column_references(order_args)
  1295. 13664 references = order_args.grep(String)
  1296. 22330 references.map! { |arg| arg =~ /^\W?(\w+)\W?\./ && $1 }.compact!
  1297. 13664 references
  1298. end
  1299. 3 def order_column(field)
  1300. 1510 arel_column(field) do |attr_name|
  1301. 22 if attr_name == "count" && !group_values.empty?
  1302. 1 table[attr_name]
  1303. else
  1304. 21 Arel.sql(connection.quote_table_name(attr_name))
  1305. end
  1306. end
  1307. end
  1308. 3 def resolve_arel_attributes(attrs)
  1309. 330 attrs.flat_map do |attr|
  1310. 342 case attr
  1311. when Arel::Predications
  1312. 135 attr
  1313. when Hash
  1314. 6 attr.flat_map do |table, columns|
  1315. 6 table = table.to_s
  1316. 6 Array(columns).map do |column|
  1317. 6 predicate_builder.resolve_arel_attribute(table, column)
  1318. end
  1319. end
  1320. else
  1321. 201 attr = attr.to_s
  1322. 201 if attr.include?(".")
  1323. 6 table, column = attr.split(".", 2)
  1324. 6 predicate_builder.resolve_arel_attribute(table, column)
  1325. else
  1326. 195 attr
  1327. end
  1328. end
  1329. end
  1330. end
  1331. # Checks to make sure that the arguments are not blank. Note that if some
  1332. # blank-like object were initially passed into the query method, then this
  1333. # method will not raise an error.
  1334. #
  1335. # Example:
  1336. #
  1337. # Post.references() # raises an error
  1338. # Post.references([]) # does not raise an error
  1339. #
  1340. # This particular method should be called with a method_name and the args
  1341. # passed into that method as an input. For example:
  1342. #
  1343. # def references(*args)
  1344. # check_if_method_has_arguments!("references", args)
  1345. # ...
  1346. # end
  1347. 3 def check_if_method_has_arguments!(method_name, args, message = "The method .#{method_name}() must contain arguments.")
  1348. 18277 if args.blank?
  1349. 45 raise ArgumentError, message
  1350. 18232 elsif block_given?
  1351. 9384 yield args
  1352. else
  1353. 8848 args.flatten!
  1354. 8848 args.compact_blank!
  1355. end
  1356. end
  1357. 3 STRUCTURAL_VALUE_METHODS = (
  1358. 3 Relation::VALUE_METHODS -
  1359. [:extending, :where, :having, :unscope, :references, :annotate, :optimizer_hints]
  1360. ).freeze # :nodoc:
  1361. 3 def structurally_incompatible_values_for(other)
  1362. 6173 values = other.values
  1363. 6173 STRUCTURAL_VALUE_METHODS.reject do |method|
  1364. 117287 v1, v2 = @values[method], values[method]
  1365. 117287 if v1.is_a?(Array)
  1366. 96 next true unless v2.is_a?(Array)
  1367. 75 v1 = v1.uniq
  1368. 75 v2 = v2.uniq
  1369. end
  1370. 117266 v1 == v2 || (!v1 || v1.empty?) && (!v2 || v2.empty?)
  1371. end
  1372. end
  1373. end
  1374. 3 class Relation # :nodoc:
  1375. # No-op WhereClauseFactory to work Mashal.load(File.read("legacy_relation.dump")).
  1376. # TODO: Remove the class once Rails 6.1 has released.
  1377. 3 class WhereClauseFactory # :nodoc:
  1378. end
  1379. end
  1380. end

lib/active_record/relation/record_fetch_warning.rb

100.0% lines covered

19 relevant lines. 19 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 module ActiveRecord
  3. 3 class Relation
  4. 3 module RecordFetchWarning
  5. # When this module is prepended to ActiveRecord::Relation and
  6. # +config.active_record.warn_on_records_fetched_greater_than+ is
  7. # set to an integer, if the number of records a query returns is
  8. # greater than the value of +warn_on_records_fetched_greater_than+,
  9. # a warning is logged. This allows for the detection of queries that
  10. # return a large number of records, which could cause memory bloat.
  11. #
  12. # In most cases, fetching large number of records can be performed
  13. # efficiently using the ActiveRecord::Batches methods.
  14. # See ActiveRecord::Batches for more information.
  15. 3 def exec_queries
  16. 24163 QueryRegistry.reset
  17. 24163 super.tap do |records|
  18. 24083 if logger && warn_on_records_fetched_greater_than
  19. 6 if records.length > warn_on_records_fetched_greater_than
  20. 3 logger.warn "Query fetched #{records.size} #{@klass} records: #{QueryRegistry.queries.join(";")}"
  21. end
  22. end
  23. end
  24. end
  25. # :stopdoc:
  26. 3 ActiveSupport::Notifications.subscribe("sql.active_record") do |*, payload|
  27. 426176 QueryRegistry.queries << payload[:sql]
  28. end
  29. # :startdoc:
  30. 3 class QueryRegistry # :nodoc:
  31. 3 extend ActiveSupport::PerThreadRegistry
  32. 3 attr_reader :queries
  33. 3 def initialize
  34. 116 @queries = []
  35. end
  36. 3 def reset
  37. 24163 @queries.clear
  38. end
  39. end
  40. end
  41. end
  42. end
  43. 3 ActiveRecord::Relation.prepend ActiveRecord::Relation::RecordFetchWarning

lib/active_record/relation/spawn_methods.rb

96.77% lines covered

31 relevant lines. 30 lines covered and 1 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 require "active_support/core_ext/hash/except"
  3. 3 require "active_support/core_ext/hash/slice"
  4. 3 require "active_record/relation/merger"
  5. 3 module ActiveRecord
  6. 3 module SpawnMethods
  7. # This is overridden by Associations::CollectionProxy
  8. 3 def spawn #:nodoc:
  9. 80780 already_in_scope? ? klass.all : clone
  10. end
  11. # Merges in the conditions from <tt>other</tt>, if <tt>other</tt> is an ActiveRecord::Relation.
  12. # Returns an array representing the intersection of the resulting records with <tt>other</tt>, if <tt>other</tt> is an array.
  13. #
  14. # Post.where(published: true).joins(:comments).merge( Comment.where(spam: false) )
  15. # # Performs a single join query with both where conditions.
  16. #
  17. # recent_posts = Post.order('created_at DESC').first(5)
  18. # Post.where(published: true).merge(recent_posts)
  19. # # Returns the intersection of all published posts with the 5 most recently created posts.
  20. # # (This is just an example. You'd probably want to do this with a single query!)
  21. #
  22. # Procs will be evaluated by merge:
  23. #
  24. # Post.where(published: true).merge(-> { joins(:comments) })
  25. # # => Post.where(published: true).joins(:comments)
  26. #
  27. # This is mainly intended for sharing common conditions between multiple associations.
  28. 3 def merge(other, *rest)
  29. 459 if other.is_a?(Array)
  30. records & other
  31. 459 elsif other
  32. 453 spawn.merge!(other, *rest)
  33. else
  34. 6 raise ArgumentError, "invalid argument: #{other.inspect}."
  35. end
  36. end
  37. 3 def merge!(other, *rest) # :nodoc:
  38. 39631 options = rest.extract_options!
  39. 39631 if other.is_a?(Hash)
  40. 753 Relation::HashMerger.new(self, other, options[:rewhere]).merge
  41. 38878 elsif other.is_a?(Relation)
  42. 38860 Relation::Merger.new(self, other, options[:rewhere]).merge
  43. 18 elsif other.respond_to?(:to_proc)
  44. 15 instance_exec(&other)
  45. else
  46. 3 raise ArgumentError, "#{other.inspect} is not an ActiveRecord::Relation"
  47. end
  48. end
  49. # Removes from the query the condition(s) specified in +skips+.
  50. #
  51. # Post.order('id asc').except(:order) # discards the order condition
  52. # Post.where('id > 10').order('id asc').except(:where) # discards the where condition but keeps the order
  53. 3 def except(*skips)
  54. 10279 relation_with values.except(*skips)
  55. end
  56. # Removes any condition from the query other than the one(s) specified in +onlies+.
  57. #
  58. # Post.order('id asc').only(:where) # discards the order condition
  59. # Post.order('id asc').only(:where, :order) # uses the specified order
  60. 3 def only(*onlies)
  61. 12 relation_with values.slice(*onlies)
  62. end
  63. 3 private
  64. 3 def relation_with(values)
  65. 10291 result = Relation.create(klass, values: values)
  66. 10291 result.extend(*extending_values) if extending_values.any?
  67. 10291 result
  68. end
  69. end
  70. end

lib/active_record/relation/where_clause.rb

99.22% lines covered

128 relevant lines. 127 lines covered and 1 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 require "active_support/core_ext/array/extract"
  3. 3 module ActiveRecord
  4. 3 class Relation
  5. 3 class WhereClause # :nodoc:
  6. 3 delegate :any?, :empty?, to: :predicates
  7. 3 def initialize(predicates)
  8. 233400 @predicates = predicates
  9. end
  10. 3 def +(other)
  11. 59581 WhereClause.new(predicates + other.predicates)
  12. end
  13. 3 def -(other)
  14. 36966 WhereClause.new(predicates - other.predicates)
  15. end
  16. 3 def |(other)
  17. 54 WhereClause.new(predicates | other.predicates)
  18. end
  19. 3 def merge(other, rewhere = nil)
  20. 79235 predicates = if rewhere
  21. 72 except_predicates(other.extract_attributes)
  22. else
  23. 79163 predicates_unreferenced_by(other)
  24. end
  25. 79235 WhereClause.new(predicates | other.predicates)
  26. end
  27. 3 def except(*columns)
  28. 336 WhereClause.new(except_predicates(columns))
  29. end
  30. 3 def or(other)
  31. 12322 left = self - other
  32. 12322 common = self - left
  33. 12322 right = other - common
  34. 12322 if left.empty? || right.empty?
  35. 6197 common
  36. else
  37. 6125 left = left.ast
  38. 6125 left = left.expr if left.is_a?(Arel::Nodes::Grouping)
  39. 6125 right = right.ast
  40. 6125 right = right.expr if right.is_a?(Arel::Nodes::Grouping)
  41. 6125 or_clause = Arel::Nodes::Or.new(left, right)
  42. 6125 common.predicates << Arel::Nodes::Grouping.new(or_clause)
  43. 6125 common
  44. end
  45. end
  46. 3 def to_h(table_name = nil)
  47. 6734 equalities(predicates).each_with_object({}) do |node, hash|
  48. 7768 next if table_name&.!= node.left.relation.name
  49. 6724 name = node.left.name.to_s
  50. 6724 value = extract_node_value(node.right)
  51. 6724 hash[name] = value
  52. end
  53. end
  54. 3 def ast
  55. 44734 predicates = predicates_with_wrapped_sql_literals
  56. 44734 predicates.one? ? predicates.first : Arel::Nodes::And.new(predicates)
  57. end
  58. 3 def ==(other)
  59. 246 other.is_a?(WhereClause) &&
  60. predicates == other.predicates
  61. end
  62. 3 def invert(as = :nand)
  63. 232 if predicates.size == 1
  64. 217 inverted_predicates = [ invert_predicate(predicates.first) ]
  65. 15 elsif as == :nor
  66. 54 inverted_predicates = predicates.map { |node| invert_predicate(node) }
  67. else
  68. 6 inverted_predicates = [ Arel::Nodes::Not.new(ast) ]
  69. end
  70. 229 WhereClause.new(inverted_predicates)
  71. end
  72. 3 def self.empty
  73. 394691 @empty ||= new([]).freeze
  74. end
  75. 3 def contradiction?
  76. 26088 predicates.any? do |x|
  77. 24517 case x
  78. when Arel::Nodes::In
  79. 95 Array === x.right && x.right.empty?
  80. when Arel::Nodes::Equality
  81. 19483 x.right.respond_to?(:unboundable?) && x.right.unboundable?
  82. end
  83. end
  84. end
  85. 3 def extract_attributes
  86. 153 predicates.each_with_object([]) do |node, attrs|
  87. 159 attr = extract_attribute(node) || begin
  88. 15 node.left if node.equality? && node.left.is_a?(Arel::Predications)
  89. end
  90. 159 attrs << attr if attr
  91. end
  92. end
  93. 3 protected
  94. 3 attr_reader :predicates
  95. 3 def referenced_columns
  96. 79163 predicates.each_with_object({}) do |node, hash|
  97. 22779 attr = extract_attribute(node) || begin
  98. 504 node.left if equality_node?(node) && node.left.is_a?(Arel::Predications)
  99. end
  100. 22779 hash[attr] = node if attr
  101. end
  102. end
  103. 3 private
  104. 3 def extract_attribute(node)
  105. 28966 attr_node = nil
  106. 28966 Arel.fetch_attribute(node) do |attr|
  107. 28498 return if attr_node&.!= attr # all attr nodes should be the same
  108. 28480 attr_node = attr
  109. end
  110. 28948 attr_node
  111. end
  112. 3 def equalities(predicates)
  113. 6734 equalities = []
  114. 6734 predicates.each do |node|
  115. 8523 if equality_node?(node)
  116. 7768 equalities << node
  117. 755 elsif node.is_a?(Arel::Nodes::And)
  118. equalities.concat equalities(node.children)
  119. end
  120. end
  121. 6734 equalities
  122. end
  123. 3 def predicates_unreferenced_by(other)
  124. 79163 referenced_columns = other.referenced_columns
  125. 79163 predicates.reject do |node|
  126. 6028 attr = extract_attribute(node) || begin
  127. 30 node.left if equality_node?(node) && node.left.is_a?(Arel::Predications)
  128. end
  129. 6028 next false unless attr
  130. 6001 ref = referenced_columns[attr]
  131. 6001 next false unless ref
  132. 2267 if equality_node?(node) && equality_node?(ref) || node == ref
  133. 2225 true
  134. else
  135. 42 ActiveSupport::Deprecation.warn(<<-MSG.squish)
  136. Merging (#{node.to_sql}) and (#{ref.to_sql}) no longer maintain
  137. both conditions, and will be replaced by the latter in Rails 6.2.
  138. To migrate to Rails 6.2's behavior, use `relation.merge(other, rewhere: true)`.
  139. MSG
  140. 42 false
  141. end
  142. end
  143. end
  144. 3 def equality_node?(node)
  145. 13558 !node.is_a?(String) && node.equality?
  146. end
  147. 3 def invert_predicate(node)
  148. 262 case node
  149. when NilClass
  150. 3 raise ArgumentError, "Invalid argument for .where.not(), got nil."
  151. when String
  152. 3 Arel::Nodes::Not.new(Arel::Nodes::SqlLiteral.new(node))
  153. else
  154. 256 node.invert
  155. end
  156. end
  157. 3 def except_predicates(columns)
  158. 834 attrs = columns.extract! { |node| node.is_a?(Arel::Attribute) }
  159. 630 non_attrs = columns.extract! { |node| node.is_a?(Arel::Predications) }
  160. 408 predicates.reject do |node|
  161. 414 if !non_attrs.empty? && node.equality? && node.left.is_a?(Arel::Predications)
  162. 6 non_attrs.include?(node.left)
  163. 414 end || Arel.fetch_attribute(node) do |attr|
  164. 429 attrs.include?(attr) || columns.include?(attr.name.to_s)
  165. end
  166. end
  167. end
  168. 3 def predicates_with_wrapped_sql_literals
  169. 44734 non_empty_predicates.map do |node|
  170. 57759 case node
  171. when Arel::Nodes::SqlLiteral, ::String
  172. 1632 wrap_sql_literal(node)
  173. 56127 else node
  174. end
  175. end
  176. end
  177. 3 ARRAY_WITH_EMPTY_STRING = [""]
  178. 3 def non_empty_predicates
  179. 44734 predicates - ARRAY_WITH_EMPTY_STRING
  180. end
  181. 3 def wrap_sql_literal(node)
  182. 1632 if ::String === node
  183. 1632 node = Arel.sql(node)
  184. end
  185. 1632 Arel::Nodes::Grouping.new(node)
  186. end
  187. 3 def extract_node_value(node)
  188. 8025 case node
  189. when Array
  190. 1644 node.map { |v| extract_node_value(v) }
  191. when Arel::Nodes::BindParam, Arel::Nodes::Casted, Arel::Nodes::Quoted
  192. 7682 node.value_before_type_cast
  193. end
  194. end
  195. end
  196. end
  197. end

lib/active_record/result.rb

100.0% lines covered

71 relevant lines. 71 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 module ActiveRecord
  3. ###
  4. # This class encapsulates a result returned from calling
  5. # {#exec_query}[rdoc-ref:ConnectionAdapters::DatabaseStatements#exec_query]
  6. # on any database connection adapter. For example:
  7. #
  8. # result = ActiveRecord::Base.connection.exec_query('SELECT id, title, body FROM posts')
  9. # result # => #<ActiveRecord::Result:0xdeadbeef>
  10. #
  11. # # Get the column names of the result:
  12. # result.columns
  13. # # => ["id", "title", "body"]
  14. #
  15. # # Get the record values of the result:
  16. # result.rows
  17. # # => [[1, "title_1", "body_1"],
  18. # [2, "title_2", "body_2"],
  19. # ...
  20. # ]
  21. #
  22. # # Get an array of hashes representing the result (column => value):
  23. # result.to_a
  24. # # => [{"id" => 1, "title" => "title_1", "body" => "body_1"},
  25. # {"id" => 2, "title" => "title_2", "body" => "body_2"},
  26. # ...
  27. # ]
  28. #
  29. # # ActiveRecord::Result also includes Enumerable.
  30. # result.each do |row|
  31. # puts row['title'] + " " + row['body']
  32. # end
  33. 3 class Result
  34. 3 include Enumerable
  35. 3 attr_reader :columns, :rows, :column_types
  36. 3 def initialize(columns, rows, column_types = {})
  37. 164391 @columns = columns
  38. 164391 @rows = rows
  39. 164391 @hash_rows = nil
  40. 164391 @column_types = column_types
  41. end
  42. # Returns true if this result set includes the column named +name+
  43. 3 def includes_column?(name)
  44. 29774 @columns.include? name
  45. end
  46. # Returns the number of elements in the rows array.
  47. 3 def length
  48. 30303 @rows.length
  49. end
  50. # Calls the given block once for each element in row collection, passing
  51. # row as parameter.
  52. #
  53. # Returns an +Enumerator+ if no block is given.
  54. 3 def each
  55. 85791 if block_given?
  56. 634797 hash_rows.each { |row| yield row }
  57. else
  58. 9 hash_rows.to_enum { @rows.size }
  59. end
  60. end
  61. 3 def to_hash
  62. 3 ActiveSupport::Deprecation.warn(<<-MSG.squish)
  63. `ActiveRecord::Result#to_hash` has been renamed to `to_a`.
  64. `to_hash` is deprecated and will be removed in Rails 6.1.
  65. MSG
  66. 3 to_a
  67. end
  68. 3 alias :map! :map
  69. 3 alias :collect! :map
  70. 3 deprecate "map!": :map
  71. 3 deprecate "collect!": :map
  72. # Returns true if there are no records, otherwise false.
  73. 3 def empty?
  74. 28844 rows.empty?
  75. end
  76. # Returns an array of hashes representing each row record.
  77. 3 def to_ary
  78. 23 hash_rows
  79. end
  80. 3 alias :to_a :to_ary
  81. 3 def [](idx)
  82. 3 hash_rows[idx]
  83. end
  84. # Returns the last record from the rows collection.
  85. 3 def last(n = nil)
  86. 12 n ? hash_rows.last(n) : hash_rows.last
  87. end
  88. 3 def cast_values(type_overrides = {}) # :nodoc:
  89. 6338 if columns.one?
  90. # Separated to avoid allocating an array per row
  91. 5990 type = if type_overrides.is_a?(Array)
  92. 1839 type_overrides.first
  93. else
  94. 4151 column_type(columns.first, type_overrides)
  95. end
  96. 5990 rows.map do |(value)|
  97. 9409 type.deserialize(value)
  98. end
  99. else
  100. 348 types = if type_overrides.is_a?(Array)
  101. 65 type_overrides
  102. else
  103. 872 columns.map { |name| column_type(name, type_overrides) }
  104. end
  105. 348 rows.map do |values|
  106. 4585 Array.new(values.size) { |i| types[i].deserialize(values[i]) }
  107. end
  108. end
  109. end
  110. 3 def initialize_copy(other)
  111. 398 @columns = columns.dup
  112. 398 @rows = rows.dup
  113. 398 @column_types = column_types.dup
  114. 398 @hash_rows = nil
  115. end
  116. 3 private
  117. 3 def column_type(name, type_overrides = {})
  118. 4740 type_overrides.fetch(name) do
  119. 4427 column_types.fetch(name, Type.default_value)
  120. end
  121. end
  122. 3 def hash_rows
  123. 85829 @hash_rows ||=
  124. begin
  125. # We freeze the strings to prevent them getting duped when
  126. # used as keys in ActiveRecord::Base's @attributes hash
  127. 85829 columns = @columns.map(&:-@)
  128. 85829 length = columns.length
  129. 85829 template = nil
  130. 85829 @rows.map { |row|
  131. 549218 if template
  132. # We use transform_values to build subsequent rows from the
  133. # hash of the first row. This is faster because we avoid any
  134. # reallocs and in Ruby 2.7+ avoid hashing entirely.
  135. 477064 index = -1
  136. 477064 template.transform_values do
  137. 2566450 row[index += 1]
  138. end
  139. else
  140. # In the past we used Hash[columns.zip(row)]
  141. # though elegant, the verbose way is much more efficient
  142. # both time and memory wise cause it avoids a big array allocation
  143. # this method is called a lot and needs to be micro optimised
  144. 72154 hash = {}
  145. 72154 index = 0
  146. 72154 while index < length
  147. 498180 hash[columns[index]] = row[index]
  148. 498180 index += 1
  149. end
  150. # It's possible to select the same column twice, in which case
  151. # we can't use a template
  152. 72154 template = hash if hash.length == length
  153. 72154 hash
  154. end
  155. }
  156. end
  157. end
  158. end
  159. end

lib/active_record/runtime_registry.rb

100.0% lines covered

8 relevant lines. 8 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 require "active_support/per_thread_registry"
  3. 3 module ActiveRecord
  4. # This is a thread locals registry for Active Record. For example:
  5. #
  6. # ActiveRecord::RuntimeRegistry.connection_handler
  7. #
  8. # returns the connection handler local to the current thread.
  9. #
  10. # See the documentation of ActiveSupport::PerThreadRegistry
  11. # for further details.
  12. 3 class RuntimeRegistry # :nodoc:
  13. 3 extend ActiveSupport::PerThreadRegistry
  14. 3 attr_accessor :sql_runtime
  15. 3 [:sql_runtime].each do |val|
  16. 3 class_eval %{ def self.#{val}; instance.#{val}; end }, __FILE__, __LINE__
  17. 3 class_eval %{ def self.#{val}=(x); instance.#{val}=x; end }, __FILE__, __LINE__
  18. end
  19. end
  20. end

lib/active_record/sanitization.rb

92.31% lines covered

78 relevant lines. 72 lines covered and 6 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 module ActiveRecord
  3. 3 module Sanitization
  4. 3 extend ActiveSupport::Concern
  5. 3 module ClassMethods
  6. # Accepts an array or string of SQL conditions and sanitizes
  7. # them into a valid SQL fragment for a WHERE clause.
  8. #
  9. # sanitize_sql_for_conditions(["name=? and group_id=?", "foo'bar", 4])
  10. # # => "name='foo''bar' and group_id=4"
  11. #
  12. # sanitize_sql_for_conditions(["name=:name and group_id=:group_id", name: "foo'bar", group_id: 4])
  13. # # => "name='foo''bar' and group_id='4'"
  14. #
  15. # sanitize_sql_for_conditions(["name='%s' and group_id='%s'", "foo'bar", 4])
  16. # # => "name='foo''bar' and group_id='4'"
  17. #
  18. # sanitize_sql_for_conditions("name='foo''bar' and group_id='4'")
  19. # # => "name='foo''bar' and group_id='4'"
  20. 3 def sanitize_sql_for_conditions(condition)
  21. 32234 return nil if condition.blank?
  22. 32234 case condition
  23. 417 when Array; sanitize_sql_array(condition)
  24. 31817 else condition
  25. end
  26. end
  27. 3 alias :sanitize_sql :sanitize_sql_for_conditions
  28. # Accepts an array, hash, or string of SQL conditions and sanitizes
  29. # them into a valid SQL fragment for a SET clause.
  30. #
  31. # sanitize_sql_for_assignment(["name=? and group_id=?", nil, 4])
  32. # # => "name=NULL and group_id=4"
  33. #
  34. # sanitize_sql_for_assignment(["name=:name and group_id=:group_id", name: nil, group_id: 4])
  35. # # => "name=NULL and group_id=4"
  36. #
  37. # Post.sanitize_sql_for_assignment({ name: nil, group_id: 4 })
  38. # # => "`posts`.`name` = NULL, `posts`.`group_id` = 4"
  39. #
  40. # sanitize_sql_for_assignment("name=NULL and group_id='4'")
  41. # # => "name=NULL and group_id='4'"
  42. 3 def sanitize_sql_for_assignment(assignments, default_table_name = table_name)
  43. 66 case assignments
  44. 18 when Array; sanitize_sql_array(assignments)
  45. when Hash; sanitize_sql_hash_for_assignment(assignments, default_table_name)
  46. 48 else assignments
  47. end
  48. end
  49. # Accepts an array, or string of SQL conditions and sanitizes
  50. # them into a valid SQL fragment for an ORDER clause.
  51. #
  52. # sanitize_sql_for_order(["field(id, ?)", [1,3,2]])
  53. # # => "field(id, 1,3,2)"
  54. #
  55. # sanitize_sql_for_order("id ASC")
  56. # # => "id ASC"
  57. 3 def sanitize_sql_for_order(condition)
  58. 9420 if condition.is_a?(Array) && condition.first.to_s.include?("?")
  59. 21 disallow_raw_sql!(
  60. [condition.first],
  61. permit: connection.column_name_with_order_matcher
  62. )
  63. # Ensure we aren't dealing with a subclass of String that might
  64. # override methods we use (e.g. Arel::Nodes::SqlLiteral).
  65. 18 if condition.first.kind_of?(String) && !condition.first.instance_of?(String)
  66. 18 condition = [String.new(condition.first), *condition[1..-1]]
  67. end
  68. 18 Arel.sql(sanitize_sql_array(condition))
  69. else
  70. 9399 condition
  71. end
  72. end
  73. # Sanitizes a hash of attribute/value pairs into SQL conditions for a SET clause.
  74. #
  75. # sanitize_sql_hash_for_assignment({ status: nil, group_id: 1 }, "posts")
  76. # # => "`posts`.`status` = NULL, `posts`.`group_id` = 1"
  77. 3 def sanitize_sql_hash_for_assignment(attrs, table)
  78. c = connection
  79. attrs.map do |attr, value|
  80. type = type_for_attribute(attr)
  81. value = type.serialize(type.cast(value))
  82. "#{c.quote_table_name_for_assignment(table, attr)} = #{c.quote(value)}"
  83. end.join(", ")
  84. end
  85. # Sanitizes a +string+ so that it is safe to use within an SQL
  86. # LIKE statement. This method uses +escape_character+ to escape all occurrences of "\", "_" and "%".
  87. #
  88. # sanitize_sql_like("100%")
  89. # # => "100\\%"
  90. #
  91. # sanitize_sql_like("snake_cased_string")
  92. # # => "snake\\_cased\\_string"
  93. #
  94. # sanitize_sql_like("100%", "!")
  95. # # => "100!%"
  96. #
  97. # sanitize_sql_like("snake_cased_string", "!")
  98. # # => "snake!_cased!_string"
  99. 3 def sanitize_sql_like(string, escape_character = "\\")
  100. 33 pattern = Regexp.union(escape_character, "%", "_")
  101. 84 string.gsub(pattern) { |x| [escape_character, x].join }
  102. end
  103. # Accepts an array of conditions. The array has each value
  104. # sanitized and interpolated into the SQL statement.
  105. #
  106. # sanitize_sql_array(["name=? and group_id=?", "foo'bar", 4])
  107. # # => "name='foo''bar' and group_id=4"
  108. #
  109. # sanitize_sql_array(["name=:name and group_id=:group_id", name: "foo'bar", group_id: 4])
  110. # # => "name='foo''bar' and group_id=4"
  111. #
  112. # sanitize_sql_array(["name='%s' and group_id='%s'", "foo'bar", 4])
  113. # # => "name='foo''bar' and group_id='4'"
  114. 3 def sanitize_sql_array(ary)
  115. 498 statement, *values = ary
  116. 498 if values.first.is_a?(Hash) && /:\w+/.match?(statement)
  117. 57 replace_named_bind_variables(statement, values.first)
  118. 441 elsif statement.include?("?")
  119. 399 replace_bind_variables(statement, values)
  120. 39 elsif statement.blank?
  121. 3 statement
  122. else
  123. 72 statement % values.collect { |value| connection.quote_string(value.to_s) }
  124. end
  125. end
  126. 3 def disallow_raw_sql!(args, permit: connection.column_name_matcher) # :nodoc:
  127. 15610 unexpected = nil
  128. 15610 args.each do |arg|
  129. 15794 next if arg.is_a?(Symbol) || Arel.arel_node?(arg) || permit.match?(arg.to_s)
  130. 27 (unexpected ||= []) << arg
  131. end
  132. 15610 return unless unexpected
  133. 27 if allow_unsafe_raw_sql == :deprecated
  134. 6 ActiveSupport::Deprecation.warn(
  135. "Dangerous query method (method whose arguments are used as raw " \
  136. "SQL) called with non-attribute argument(s): " \
  137. "#{unexpected.map(&:inspect).join(", ")}. Non-attribute " \
  138. "arguments will be disallowed in Rails 6.1. This method should " \
  139. "not be called with user-provided values, such as request " \
  140. "parameters or model attributes. Known-safe values can be passed " \
  141. "by wrapping them in Arel.sql()."
  142. )
  143. else
  144. 21 raise(ActiveRecord::UnknownAttributeReference,
  145. "Query method called with non-attribute argument(s): " +
  146. unexpected.map(&:inspect).join(", ")
  147. )
  148. end
  149. end
  150. 3 private
  151. 3 def replace_bind_variables(statement, values)
  152. 465 raise_if_bind_arity_mismatch(statement, statement.count("?"), values.size)
  153. 450 bound = values.dup
  154. 450 c = connection
  155. 450 statement.gsub(/\?/) do
  156. 459 replace_bind_variable(bound.shift, c)
  157. end
  158. end
  159. 3 def replace_bind_variable(value, c = connection)
  160. 555 if ActiveRecord::Relation === value
  161. 18 value.to_sql
  162. else
  163. 537 quote_bound_value(value, c)
  164. end
  165. end
  166. 3 def replace_named_bind_variables(statement, bind_vars)
  167. 93 statement.gsub(/(:?):([a-zA-Z]\w*)/) do |match|
  168. 111 if $1 == ":" # skip postgresql casts
  169. 12 match # return the whole match
  170. 99 elsif bind_vars.include?(match = $2.to_sym)
  171. 96 replace_bind_variable(bind_vars[match])
  172. else
  173. 3 raise PreparedStatementInvalid, "missing value for :#{match} in #{statement}"
  174. end
  175. end
  176. end
  177. 3 def quote_bound_value(value, c = connection)
  178. 537 if value.respond_to?(:map) && !value.acts_like?(:string)
  179. 186 values = value.map { |v| v.respond_to?(:id_for_database) ? v.id_for_database : v }
  180. 69 if values.empty?
  181. 24 c.quote(nil)
  182. else
  183. 162 values.map! { |v| c.quote(v) }.join(",")
  184. end
  185. else
  186. 468 value = value.id_for_database if value.respond_to?(:id_for_database)
  187. 468 c.quote(value)
  188. end
  189. end
  190. 3 def raise_if_bind_arity_mismatch(statement, expected, provided)
  191. 465 unless expected == provided
  192. 15 raise PreparedStatementInvalid, "wrong number of bind variables (#{provided} for #{expected}) in: #{statement}"
  193. end
  194. end
  195. end
  196. end
  197. end

lib/active_record/schema.rb

100.0% lines covered

11 relevant lines. 11 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 module ActiveRecord
  3. # = Active Record \Schema
  4. #
  5. # Allows programmers to programmatically define a schema in a portable
  6. # DSL. This means you can define tables, indexes, etc. without using SQL
  7. # directly, so your applications can more easily support multiple
  8. # databases.
  9. #
  10. # Usage:
  11. #
  12. # ActiveRecord::Schema.define do
  13. # create_table :authors do |t|
  14. # t.string :name, null: false
  15. # end
  16. #
  17. # add_index :authors, :name, :unique
  18. #
  19. # create_table :posts do |t|
  20. # t.integer :author_id, null: false
  21. # t.string :subject
  22. # t.text :body
  23. # t.boolean :private, default: false
  24. # end
  25. #
  26. # add_index :posts, :author_id
  27. # end
  28. #
  29. # ActiveRecord::Schema is only supported by database adapters that also
  30. # support migrations, the two features being very similar.
  31. 3 class Schema < Migration::Current
  32. # Eval the given block. All methods available to the current connection
  33. # adapter are available within the block, so you can easily use the
  34. # database definition DSL to build up your schema (
  35. # {create_table}[rdoc-ref:ConnectionAdapters::SchemaStatements#create_table],
  36. # {add_index}[rdoc-ref:ConnectionAdapters::SchemaStatements#add_index], etc.).
  37. #
  38. # The +info+ hash is optional, and if given is used to define metadata
  39. # about the current schema (currently, only the schema's version):
  40. #
  41. # ActiveRecord::Schema.define(version: 2038_01_19_000001) do
  42. # ...
  43. # end
  44. 3 def self.define(info = {}, &block)
  45. 52 new.define(info, &block)
  46. end
  47. 3 def define(info, &block) # :nodoc:
  48. 52 instance_eval(&block)
  49. 49 if info[:version].present?
  50. 12 connection.schema_migration.create_table
  51. 12 connection.assume_migrated_upto_version(info[:version])
  52. end
  53. 49 ActiveRecord::InternalMetadata.create_table
  54. 49 ActiveRecord::InternalMetadata[:environment] = connection.migration_context.current_environment
  55. end
  56. end
  57. end

lib/active_record/schema_dumper.rb

91.78% lines covered

146 relevant lines. 134 lines covered and 12 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 require "stringio"
  3. 3 module ActiveRecord
  4. # = Active Record Schema Dumper
  5. #
  6. # This class is used to dump the database schema for some connection to some
  7. # output format (i.e., ActiveRecord::Schema).
  8. 3 class SchemaDumper #:nodoc:
  9. 3 private_class_method :new
  10. ##
  11. # :singleton-method:
  12. # A list of tables which should not be dumped to the schema.
  13. # Acceptable values are strings as well as regexp if ActiveRecord::Base.schema_format == :ruby.
  14. # Only strings are accepted if ActiveRecord::Base.schema_format == :sql.
  15. 3 cattr_accessor :ignore_tables, default: []
  16. ##
  17. # :singleton-method:
  18. # Specify a custom regular expression matching foreign keys which name
  19. # should not be dumped to db/schema.rb.
  20. 3 cattr_accessor :fk_ignore_pattern, default: /^fk_rails_[0-9a-f]{10}$/
  21. ##
  22. # :singleton-method:
  23. # Specify a custom regular expression matching check constraints which name
  24. # should not be dumped to db/schema.rb.
  25. 3 cattr_accessor :chk_ignore_pattern, default: /^chk_rails_[0-9a-f]{10}$/
  26. 3 class << self
  27. 3 def dump(connection = ActiveRecord::Base.connection, stream = STDOUT, config = ActiveRecord::Base)
  28. 197 connection.create_schema_dumper(generate_options(config)).dump(stream)
  29. 197 stream
  30. end
  31. 3 private
  32. 3 def generate_options(config)
  33. 197 {
  34. table_name_prefix: config.table_name_prefix,
  35. table_name_suffix: config.table_name_suffix
  36. }
  37. end
  38. end
  39. 3 def dump(stream)
  40. 197 header(stream)
  41. 197 extensions(stream)
  42. 197 tables(stream)
  43. 197 trailer(stream)
  44. 197 stream
  45. end
  46. 3 private
  47. 3 attr_accessor :table_name
  48. 3 def initialize(connection, options = {})
  49. 197 @connection = connection
  50. 197 @version = connection.migration_context.current_version rescue nil
  51. 197 @options = options
  52. end
  53. # turns 20170404131909 into "2017_04_04_131909"
  54. 3 def formatted_version
  55. 197 stringified = @version.to_s
  56. 197 return stringified unless stringified.length == 14
  57. stringified.insert(4, "_").insert(7, "_").insert(10, "_")
  58. end
  59. 3 def define_params
  60. 197 @version ? "version: #{formatted_version}" : ""
  61. end
  62. 3 def header(stream)
  63. 197 stream.puts <<HEADER
  64. # This file is auto-generated from the current state of the database. Instead
  65. # of editing this file, please use the migrations feature of Active Record to
  66. # incrementally modify your database, and then regenerate this schema definition.
  67. #
  68. # This file is the source Rails uses to define your schema when running `bin/rails
  69. # db:schema:load`. When creating a new database, `bin/rails db:schema:load` tends to
  70. # be faster and is potentially less error prone than running all of your
  71. # migrations from scratch. Old migrations may fail to apply correctly if those
  72. # migrations use external dependencies or application code.
  73. #
  74. # It's strongly recommended that you check this file into your version control system.
  75. ActiveRecord::Schema.define(#{define_params}) do
  76. HEADER
  77. end
  78. 3 def trailer(stream)
  79. 197 stream.puts "end"
  80. end
  81. # extensions are only supported by PostgreSQL
  82. 3 def extensions(stream)
  83. end
  84. 3 def tables(stream)
  85. 197 sorted_tables = @connection.tables.sort
  86. 197 sorted_tables.each do |table_name|
  87. 39252 table(table_name, stream) unless ignored?(table_name)
  88. end
  89. # dump foreign keys at the end to make sure all dependent tables exist.
  90. 197 if @connection.supports_foreign_keys?
  91. 197 sorted_tables.each do |tbl|
  92. 39252 foreign_keys(tbl, stream) unless ignored?(tbl)
  93. end
  94. end
  95. end
  96. 3 def table(table, stream)
  97. 4499 columns = @connection.columns(table)
  98. 4499 begin
  99. 4499 self.table_name = table
  100. 4499 tbl = StringIO.new
  101. # first dump primary key column
  102. 4499 pk = @connection.primary_key(table)
  103. 4499 tbl.print " create_table #{remove_prefix_and_suffix(table).inspect}"
  104. 4499 case pk
  105. when String
  106. 4020 tbl.print ", primary_key: #{pk.inspect}" unless pk == "id"
  107. 8044 pkcol = columns.detect { |c| c.name == pk }
  108. 4020 pkcolspec = column_spec_for_primary_key(pkcol)
  109. 4020 unless pkcolspec.empty?
  110. 262 if pkcolspec != pkcolspec.slice(:id, :default)
  111. 4 pkcolspec = { id: { type: pkcolspec.delete(:id), **pkcolspec }.compact }
  112. end
  113. 262 tbl.print ", #{format_colspec(pkcolspec)}"
  114. end
  115. when Array
  116. 28 tbl.print ", primary_key: #{pk.inspect}"
  117. else
  118. 451 tbl.print ", id: false"
  119. end
  120. 4499 table_options = @connection.table_options(table)
  121. 4499 if table_options.present?
  122. 2 tbl.print ", #{format_options(table_options)}"
  123. end
  124. 4499 tbl.puts ", force: :cascade do |t|"
  125. # then dump all non-primary key columns
  126. 4499 columns.each do |column|
  127. 16929 raise StandardError, "Unknown type '#{column.sql_type}' for column '#{column.name}'" unless @connection.valid_type?(column.type)
  128. 16929 next if column.name == pk
  129. 12909 type, colspec = column_spec(column)
  130. 12909 if type.is_a?(Symbol)
  131. 12909 tbl.print " t.#{type} #{column.name.inspect}"
  132. else
  133. tbl.print " t.column #{column.name.inspect}, #{type.inspect}"
  134. end
  135. 12909 tbl.print ", #{format_colspec(colspec)}" if colspec.present?
  136. 12909 tbl.puts
  137. end
  138. 4499 indexes_in_create(table, tbl)
  139. 4499 check_constraints_in_create(table, tbl) if @connection.supports_check_constraints?
  140. 4499 tbl.puts " end"
  141. 4499 tbl.puts
  142. 4499 tbl.rewind
  143. 4499 stream.print tbl.read
  144. rescue => e
  145. stream.puts "# Could not dump table #{table.inspect} because of following #{e.class}"
  146. stream.puts "# #{e.message}"
  147. stream.puts
  148. ensure
  149. 4499 self.table_name = nil
  150. end
  151. end
  152. # Keep it for indexing materialized views
  153. 3 def indexes(table, stream)
  154. if (indexes = @connection.indexes(table)).any?
  155. add_index_statements = indexes.map do |index|
  156. table_name = remove_prefix_and_suffix(index.table).inspect
  157. " add_index #{([table_name] + index_parts(index)).join(', ')}"
  158. end
  159. stream.puts add_index_statements.sort.join("\n")
  160. stream.puts
  161. end
  162. end
  163. 3 def indexes_in_create(table, stream)
  164. 4499 if (indexes = @connection.indexes(table)).any?
  165. 812 index_statements = indexes.map do |index|
  166. 1501 " t.index #{index_parts(index).join(', ')}"
  167. end
  168. 812 stream.puts index_statements.sort.join("\n")
  169. end
  170. end
  171. 3 def index_parts(index)
  172. 1501 index_parts = [
  173. index.columns.inspect,
  174. "name: #{index.name.inspect}",
  175. ]
  176. 1501 index_parts << "unique: true" if index.unique
  177. 1501 index_parts << "length: #{format_index_parts(index.lengths)}" if index.lengths.present?
  178. 1501 index_parts << "order: #{format_index_parts(index.orders)}" if index.orders.present?
  179. 1501 index_parts << "opclass: #{format_index_parts(index.opclasses)}" if index.opclasses.present?
  180. 1501 index_parts << "where: #{index.where.inspect}" if index.where
  181. 1501 index_parts << "using: #{index.using.inspect}" if !@connection.default_index_type?(index)
  182. 1501 index_parts << "type: #{index.type.inspect}" if index.type
  183. 1501 index_parts << "comment: #{index.comment.inspect}" if index.comment
  184. 1501 index_parts
  185. end
  186. 3 def check_constraints_in_create(table, stream)
  187. 4499 if (check_constraints = @connection.check_constraints(table)).any?
  188. 25 add_check_constraint_statements = check_constraints.map do |check_constraint|
  189. 25 parts = [
  190. "t.check_constraint #{check_constraint.expression.inspect}"
  191. ]
  192. 25 if check_constraint.export_name_on_schema_dump?
  193. 25 parts << "name: #{check_constraint.name.inspect}"
  194. end
  195. 25 " #{parts.join(', ')}"
  196. end
  197. 25 stream.puts add_check_constraint_statements.sort.join("\n")
  198. end
  199. end
  200. 3 def foreign_keys(table, stream)
  201. 4499 if (foreign_keys = @connection.foreign_keys(table)).any?
  202. 160 add_foreign_key_statements = foreign_keys.map do |foreign_key|
  203. 226 parts = [
  204. "add_foreign_key #{remove_prefix_and_suffix(foreign_key.from_table).inspect}",
  205. remove_prefix_and_suffix(foreign_key.to_table).inspect,
  206. ]
  207. 226 if foreign_key.column != @connection.foreign_key_column_for(foreign_key.to_table)
  208. 32 parts << "column: #{foreign_key.column.inspect}"
  209. end
  210. 226 if foreign_key.custom_primary_key?
  211. 25 parts << "primary_key: #{foreign_key.primary_key.inspect}"
  212. end
  213. 226 if foreign_key.export_name_on_schema_dump?
  214. 11 parts << "name: #{foreign_key.name.inspect}"
  215. end
  216. 226 parts << "on_update: #{foreign_key.on_update.inspect}" if foreign_key.on_update
  217. 226 parts << "on_delete: #{foreign_key.on_delete.inspect}" if foreign_key.on_delete
  218. 226 " #{parts.join(', ')}"
  219. end
  220. 160 stream.puts add_foreign_key_statements.sort.join("\n")
  221. end
  222. end
  223. 3 def format_colspec(colspec)
  224. colspec.map do |key, value|
  225. 5032 "#{key}: #{ value.is_a?(Hash) ? "{ #{format_colspec(value)} }" : value }"
  226. 4111 end.join(", ")
  227. end
  228. 3 def format_options(options)
  229. 86 options.map { |key, value| "#{key}: #{value.inspect}" }.join(", ")
  230. end
  231. 3 def format_index_parts(options)
  232. 80 if options.is_a?(Hash)
  233. 41 "{ #{format_options(options)} }"
  234. else
  235. 39 options.inspect
  236. end
  237. end
  238. 3 def remove_prefix_and_suffix(table)
  239. 6777179 prefix = Regexp.escape(@options[:table_name_prefix].to_s)
  240. 6777179 suffix = Regexp.escape(@options[:table_name_suffix].to_s)
  241. 6777179 table.sub(/\A#{prefix}(.+)#{suffix}\z/, "\\1")
  242. end
  243. 3 def ignored?(table_name)
  244. 78504 [ActiveRecord::Base.schema_migrations_table_name, ActiveRecord::Base.internal_metadata_table_name, ignore_tables].flatten.any? do |ignored|
  245. 6772228 ignored === remove_prefix_and_suffix(table_name)
  246. end
  247. end
  248. end
  249. end

lib/active_record/schema_migration.rb

96.15% lines covered

26 relevant lines. 25 lines covered and 1 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 require "active_record/scoping/default"
  3. 3 require "active_record/scoping/named"
  4. 3 module ActiveRecord
  5. # This class is used to create a table that keeps track of which migrations
  6. # have been applied to a given database. When a migration is run, its schema
  7. # number is inserted in to the `SchemaMigration.table_name` so it doesn't need
  8. # to be executed the next time.
  9. 3 class SchemaMigration < ActiveRecord::Base # :nodoc:
  10. 3 class << self
  11. 3 def _internal?
  12. true
  13. end
  14. 3 def primary_key
  15. 1658 "version"
  16. end
  17. 3 def table_name
  18. 1536 "#{table_name_prefix}#{schema_migrations_table_name}#{table_name_suffix}"
  19. end
  20. 3 def create_table
  21. 780 unless table_exists?
  22. 38 version_options = connection.internal_string_options_for_primary_key
  23. 38 connection.create_table(table_name, id: false) do |t|
  24. 38 t.string :version, **version_options
  25. end
  26. end
  27. end
  28. 3 def drop_table
  29. 57 connection.drop_table table_name, if_exists: true
  30. end
  31. 3 def normalize_migration_number(number)
  32. 129 "%.3d" % number.to_i
  33. end
  34. 3 def normalized_versions
  35. 55 all_versions.map { |v| normalize_migration_number v }
  36. end
  37. 3 def all_versions
  38. 801 order(:version).pluck(:version)
  39. end
  40. end
  41. 3 def version
  42. 3 super.to_i
  43. end
  44. end
  45. end

lib/active_record/scoping.rb

97.73% lines covered

44 relevant lines. 43 lines covered and 1 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 require "active_support/per_thread_registry"
  3. 3 module ActiveRecord
  4. 3 module Scoping
  5. 3 extend ActiveSupport::Concern
  6. 3 included do
  7. 3 include Default
  8. 3 include Named
  9. end
  10. 3 module ClassMethods # :nodoc:
  11. # Collects attributes from scopes that should be applied when creating
  12. # an AR instance for the particular class this is called on.
  13. 3 def scope_attributes
  14. 905 all.scope_for_create
  15. end
  16. # Are there attributes associated with this scope?
  17. 3 def scope_attributes?
  18. 33849 current_scope
  19. end
  20. 3 def current_scope(skip_inherited_scope = false)
  21. 196676 ScopeRegistry.value_for(:current_scope, self, skip_inherited_scope)
  22. end
  23. 3 def current_scope=(scope)
  24. 148554 ScopeRegistry.set_value_for(:current_scope, self, scope)
  25. end
  26. end
  27. 3 def populate_with_current_scope_attributes # :nodoc:
  28. 15758 return unless self.class.scope_attributes?
  29. 887 attributes = self.class.scope_attributes
  30. 887 _assign_attributes(attributes) if attributes.any?
  31. end
  32. 3 def initialize_internals_callback # :nodoc:
  33. 15758 super
  34. 15758 populate_with_current_scope_attributes
  35. end
  36. # This class stores the +:current_scope+ and +:ignore_default_scope+ values
  37. # for different classes. The registry is stored as a thread local, which is
  38. # accessed through +ScopeRegistry.current+.
  39. #
  40. # This class allows you to store and get the scope values on different
  41. # classes and different types of scopes. For example, if you are attempting
  42. # to get the current_scope for the +Board+ model, then you would use the
  43. # following code:
  44. #
  45. # registry = ActiveRecord::Scoping::ScopeRegistry
  46. # registry.set_value_for(:current_scope, Board, some_new_scope)
  47. #
  48. # Now when you run:
  49. #
  50. # registry.value_for(:current_scope, Board)
  51. #
  52. # You will obtain whatever was defined in +some_new_scope+. The #value_for
  53. # and #set_value_for methods are delegated to the current ScopeRegistry
  54. # object, so the above example code can also be called as:
  55. #
  56. # ActiveRecord::Scoping::ScopeRegistry.set_value_for(:current_scope,
  57. # Board, some_new_scope)
  58. 3 class ScopeRegistry # :nodoc:
  59. 3 extend ActiveSupport::PerThreadRegistry
  60. 3 VALID_SCOPE_TYPES = [:current_scope, :ignore_default_scope]
  61. 3 def initialize
  62. 107 @registry = Hash.new { |hash, key| hash[key] = {} }
  63. end
  64. # Obtains the value for a given +scope_type+ and +model+.
  65. 3 def value_for(scope_type, model, skip_inherited_scope = false)
  66. 208400 raise_invalid_scope_type!(scope_type)
  67. 208400 return @registry[scope_type][model.name] if skip_inherited_scope
  68. 132732 klass = model
  69. 132732 base = model.base_class
  70. 132732 while klass <= base
  71. 144780 value = @registry[scope_type][klass.name]
  72. 144780 return value if value
  73. 119068 klass = klass.superclass
  74. end
  75. end
  76. # Sets the +value+ for a given +scope_type+ and +model+.
  77. 3 def set_value_for(scope_type, model, value)
  78. 157294 raise_invalid_scope_type!(scope_type)
  79. 157294 @registry[scope_type][model.name] = value
  80. end
  81. 3 private
  82. 3 def raise_invalid_scope_type!(scope_type)
  83. 365694 if !VALID_SCOPE_TYPES.include?(scope_type)
  84. raise ArgumentError, "Invalid scope type '#{scope_type}' sent to the registry. Scope types must be included in VALID_SCOPE_TYPES"
  85. end
  86. end
  87. end
  88. end
  89. end

lib/active_record/scoping/default.rb

100.0% lines covered

43 relevant lines. 43 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 module ActiveRecord
  3. 3 module Scoping
  4. 3 module Default
  5. 3 extend ActiveSupport::Concern
  6. 3 included do
  7. # Stores the default scope for the class.
  8. 3 class_attribute :default_scopes, instance_writer: false, instance_predicate: false, default: []
  9. 3 class_attribute :default_scope_override, instance_writer: false, instance_predicate: false, default: nil
  10. end
  11. 3 module ClassMethods
  12. # Returns a scope for the model without the previously set scopes.
  13. #
  14. # class Post < ActiveRecord::Base
  15. # def self.default_scope
  16. # where(published: true)
  17. # end
  18. # end
  19. #
  20. # Post.all # Fires "SELECT * FROM posts WHERE published = true"
  21. # Post.unscoped.all # Fires "SELECT * FROM posts"
  22. # Post.where(published: false).unscoped.all # Fires "SELECT * FROM posts"
  23. #
  24. # This method also accepts a block. All queries inside the block will
  25. # not use the previously set scopes.
  26. #
  27. # Post.unscoped {
  28. # Post.limit(10) # Fires "SELECT * FROM posts LIMIT 10"
  29. # }
  30. 3 def unscoped
  31. 44948 block_given? ? relation.scoping { yield } : relation
  32. end
  33. # Are there attributes associated with this scope?
  34. 3 def scope_attributes? # :nodoc:
  35. 33849 super || default_scopes.any? || respond_to?(:default_scope)
  36. end
  37. 3 def before_remove_const #:nodoc:
  38. 6 self.current_scope = nil
  39. end
  40. 3 private
  41. # Use this macro in your model to set a default scope for all operations on
  42. # the model.
  43. #
  44. # class Article < ActiveRecord::Base
  45. # default_scope { where(published: true) }
  46. # end
  47. #
  48. # Article.all # => SELECT * FROM articles WHERE published = true
  49. #
  50. # The #default_scope is also applied while creating/building a record.
  51. # It is not applied while updating a record.
  52. #
  53. # Article.new.published # => true
  54. # Article.create.published # => true
  55. #
  56. # (You can also pass any object which responds to +call+ to the
  57. # +default_scope+ macro, and it will be called when building the
  58. # default scope.)
  59. #
  60. # If you use multiple #default_scope declarations in your model then
  61. # they will be merged together:
  62. #
  63. # class Article < ActiveRecord::Base
  64. # default_scope { where(published: true) }
  65. # default_scope { where(rating: 'G') }
  66. # end
  67. #
  68. # Article.all # => SELECT * FROM articles WHERE published = true AND rating = 'G'
  69. #
  70. # This is also the case with inheritance and module includes where the
  71. # parent or module defines a #default_scope and the child or including
  72. # class defines a second one.
  73. #
  74. # If you need to do more complex things with a default scope, you can
  75. # alternatively define it as a class method:
  76. #
  77. # class Article < ActiveRecord::Base
  78. # def self.default_scope
  79. # # Should return a scope, you can call 'super' here etc.
  80. # end
  81. # end
  82. 3 def default_scope(scope = nil, &block) # :doc:
  83. 141 scope = block if block_given?
  84. 141 if scope.is_a?(Relation) || !scope.respond_to?(:call)
  85. 3 raise ArgumentError,
  86. "Support for calling #default_scope without a block is removed. For example instead " \
  87. "of `default_scope where(color: 'red')`, please use " \
  88. "`default_scope { where(color: 'red') }`. (Alternatively you can just redefine " \
  89. "self.default_scope.)"
  90. end
  91. 138 self.default_scopes += [scope]
  92. end
  93. 3 def build_default_scope(relation = relation())
  94. 54778 return if abstract_class?
  95. 54775 if default_scope_override.nil?
  96. 1212 self.default_scope_override = !Base.is_a?(method(:default_scope).owner)
  97. end
  98. 54775 if default_scope_override
  99. # The user has defined their own default scope method, so call that
  100. 98 evaluate_default_scope do
  101. 49 if scope = default_scope
  102. 49 relation.merge!(scope)
  103. end
  104. end
  105. 54677 elsif default_scopes.any?
  106. 4336 evaluate_default_scope do
  107. 4321 default_scopes.inject(relation) do |default_scope, scope|
  108. 4504 scope = scope.respond_to?(:to_proc) ? scope : scope.method(:call)
  109. 4504 default_scope.instance_exec(&scope) || default_scope
  110. end
  111. end
  112. end
  113. end
  114. 3 def ignore_default_scope?
  115. 11718 ScopeRegistry.value_for(:ignore_default_scope, base_class)
  116. end
  117. 3 def ignore_default_scope=(ignore)
  118. 8740 ScopeRegistry.set_value_for(:ignore_default_scope, base_class, ignore)
  119. end
  120. # The ignore_default_scope flag is used to prevent an infinite recursion
  121. # situation where a default scope references a scope which has a default
  122. # scope which references a scope...
  123. 3 def evaluate_default_scope
  124. 4434 return if ignore_default_scope?
  125. 4370 begin
  126. 4370 self.ignore_default_scope = true
  127. 4370 yield
  128. ensure
  129. 4370 self.ignore_default_scope = false
  130. end
  131. end
  132. end
  133. end
  134. end
  135. end

lib/active_record/scoping/named.rb

98.0% lines covered

50 relevant lines. 49 lines covered and 1 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 module ActiveRecord
  3. # = Active Record \Named \Scopes
  4. 3 module Scoping
  5. 3 module Named
  6. 3 extend ActiveSupport::Concern
  7. 3 module ClassMethods
  8. # Returns an ActiveRecord::Relation scope object.
  9. #
  10. # posts = Post.all
  11. # posts.size # Fires "select count(*) from posts" and returns the count
  12. # posts.each {|p| puts p.name } # Fires "select * from posts" and loads post objects
  13. #
  14. # fruits = Fruit.all
  15. # fruits = fruits.where(color: 'red') if options[:red_only]
  16. # fruits = fruits.limit(10) if limited?
  17. #
  18. # You can define a scope that applies to all finders using
  19. # {default_scope}[rdoc-ref:Scoping::Default::ClassMethods#default_scope].
  20. 3 def all
  21. 35786 scope = current_scope
  22. 35786 if scope
  23. 13134 if scope._deprecated_scope_source
  24. 30 ActiveSupport::Deprecation.warn(<<~MSG.squish)
  25. Class level methods will no longer inherit scoping from `#{scope._deprecated_scope_source}`
  26. in Rails 6.1. To continue using the scoped relation, pass it into the block directly.
  27. To instead access the full set of models, as Rails 6.1 will, use `#{name}.default_scoped`.
  28. MSG
  29. end
  30. 13134 if self == scope.klass
  31. 13104 scope.clone
  32. else
  33. 30 relation.merge!(scope)
  34. end
  35. else
  36. 22652 default_scoped
  37. end
  38. end
  39. 3 def scope_for_association(scope = relation) # :nodoc:
  40. 32393 if current_scope&.empty_scope?
  41. 270 scope
  42. else
  43. 32123 default_scoped(scope)
  44. end
  45. end
  46. # Returns a scope for the model with default scopes.
  47. 3 def default_scoped(scope = relation)
  48. 54778 build_default_scope(scope) || scope
  49. end
  50. 3 def default_extensions # :nodoc:
  51. 6730 if scope = scope_for_association || build_default_scope
  52. 6730 scope.extensions
  53. else
  54. []
  55. end
  56. end
  57. # Adds a class method for retrieving and querying objects.
  58. # The method is intended to return an ActiveRecord::Relation
  59. # object, which is composable with other scopes.
  60. # If it returns +nil+ or +false+, an
  61. # {all}[rdoc-ref:Scoping::Named::ClassMethods#all] scope is returned instead.
  62. #
  63. # A \scope represents a narrowing of a database query, such as
  64. # <tt>where(color: :red).select('shirts.*').includes(:washing_instructions)</tt>.
  65. #
  66. # class Shirt < ActiveRecord::Base
  67. # scope :red, -> { where(color: 'red') }
  68. # scope :dry_clean_only, -> { joins(:washing_instructions).where('washing_instructions.dry_clean_only = ?', true) }
  69. # end
  70. #
  71. # The above calls to #scope define class methods <tt>Shirt.red</tt> and
  72. # <tt>Shirt.dry_clean_only</tt>. <tt>Shirt.red</tt>, in effect,
  73. # represents the query <tt>Shirt.where(color: 'red')</tt>.
  74. #
  75. # Note that this is simply 'syntactic sugar' for defining an actual
  76. # class method:
  77. #
  78. # class Shirt < ActiveRecord::Base
  79. # def self.red
  80. # where(color: 'red')
  81. # end
  82. # end
  83. #
  84. # Unlike <tt>Shirt.find(...)</tt>, however, the object returned by
  85. # <tt>Shirt.red</tt> is not an Array but an ActiveRecord::Relation,
  86. # which is composable with other scopes; it resembles the association object
  87. # constructed by a {has_many}[rdoc-ref:Associations::ClassMethods#has_many]
  88. # declaration. For instance, you can invoke <tt>Shirt.red.first</tt>, <tt>Shirt.red.count</tt>,
  89. # <tt>Shirt.red.where(size: 'small')</tt>. Also, just as with the
  90. # association objects, named \scopes act like an Array, implementing
  91. # Enumerable; <tt>Shirt.red.each(&block)</tt>, <tt>Shirt.red.first</tt>,
  92. # and <tt>Shirt.red.inject(memo, &block)</tt> all behave as if
  93. # <tt>Shirt.red</tt> really was an array.
  94. #
  95. # These named \scopes are composable. For instance,
  96. # <tt>Shirt.red.dry_clean_only</tt> will produce all shirts that are
  97. # both red and dry clean only. Nested finds and calculations also work
  98. # with these compositions: <tt>Shirt.red.dry_clean_only.count</tt>
  99. # returns the number of garments for which these criteria obtain.
  100. # Similarly with <tt>Shirt.red.dry_clean_only.average(:thread_count)</tt>.
  101. #
  102. # All scopes are available as class methods on the ActiveRecord::Base
  103. # descendant upon which the \scopes were defined. But they are also
  104. # available to {has_many}[rdoc-ref:Associations::ClassMethods#has_many]
  105. # associations. If,
  106. #
  107. # class Person < ActiveRecord::Base
  108. # has_many :shirts
  109. # end
  110. #
  111. # then <tt>elton.shirts.red.dry_clean_only</tt> will return all of
  112. # Elton's red, dry clean only shirts.
  113. #
  114. # \Named scopes can also have extensions, just as with
  115. # {has_many}[rdoc-ref:Associations::ClassMethods#has_many] declarations:
  116. #
  117. # class Shirt < ActiveRecord::Base
  118. # scope :red, -> { where(color: 'red') } do
  119. # def dom_id
  120. # 'red_shirts'
  121. # end
  122. # end
  123. # end
  124. #
  125. # Scopes can also be used while creating/building a record.
  126. #
  127. # class Article < ActiveRecord::Base
  128. # scope :published, -> { where(published: true) }
  129. # end
  130. #
  131. # Article.published.new.published # => true
  132. # Article.published.create.published # => true
  133. #
  134. # \Class methods on your model are automatically available
  135. # on scopes. Assuming the following setup:
  136. #
  137. # class Article < ActiveRecord::Base
  138. # scope :published, -> { where(published: true) }
  139. # scope :featured, -> { where(featured: true) }
  140. #
  141. # def self.latest_article
  142. # order('published_at desc').first
  143. # end
  144. #
  145. # def self.titles
  146. # pluck(:title)
  147. # end
  148. # end
  149. #
  150. # We are able to call the methods like this:
  151. #
  152. # Article.published.featured.latest_article
  153. # Article.featured.titles
  154. 3 def scope(name, body, &block)
  155. 798 unless body.respond_to?(:call)
  156. 3 raise ArgumentError, "The scope body needs to be callable."
  157. end
  158. 795 if dangerous_class_method?(name)
  159. 60 raise ArgumentError, "You tried to define a scope named \"#{name}\" " \
  160. "on the model \"#{self.name}\", but Active Record already defined " \
  161. "a class method with the same name."
  162. end
  163. 735 if method_defined_within?(name, Relation)
  164. 12 raise ArgumentError, "You tried to define a scope named \"#{name}\" " \
  165. "on the model \"#{self.name}\", but ActiveRecord::Relation already defined " \
  166. "an instance method with the same name."
  167. end
  168. 723 valid_scope_name?(name)
  169. 723 extension = Module.new(&block) if block
  170. 723 if body.respond_to?(:to_proc)
  171. 720 singleton_class.define_method(name) do |*args|
  172. 798 scope = all._exec_scope(name, *args, &body)
  173. 798 scope = scope.extending(extension) if extension
  174. 798 scope
  175. end
  176. else
  177. 3 singleton_class.define_method(name) do |*args|
  178. 3 scope = body.call(*args) || all
  179. 3 scope = scope.extending(extension) if extension
  180. 3 scope
  181. end
  182. end
  183. 723 singleton_class.send(:ruby2_keywords, name) if respond_to?(:ruby2_keywords, true)
  184. 723 generate_relation_method(name)
  185. end
  186. 3 private
  187. 3 def singleton_method_added(name)
  188. 163518 generate_relation_method(name) if Kernel.respond_to?(name) && !ActiveRecord::Relation.method_defined?(name)
  189. end
  190. 3 def valid_scope_name?(name)
  191. 723 if respond_to?(name, true) && logger
  192. 51 logger.warn "Creating scope :#{name}. " \
  193. "Overwriting existing method #{self.name}.#{name}."
  194. end
  195. end
  196. end
  197. end
  198. end
  199. end

lib/active_record/secure_token.rb

100.0% lines covered

14 relevant lines. 14 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 module ActiveRecord
  3. 3 module SecureToken
  4. 3 class MinimumLengthError < StandardError; end
  5. 3 MINIMUM_TOKEN_LENGTH = 24
  6. 3 extend ActiveSupport::Concern
  7. 3 module ClassMethods
  8. # Example using #has_secure_token
  9. #
  10. # # Schema: User(token:string, auth_token:string)
  11. # class User < ActiveRecord::Base
  12. # has_secure_token
  13. # has_secure_token :auth_token, length: 36
  14. # end
  15. #
  16. # user = User.new
  17. # user.save
  18. # user.token # => "pX27zsMN2ViQKta1bGfLmVJE"
  19. # user.auth_token # => "tU9bLuZseefXQ4yQxQo8wjtBvsAfPc78os6R"
  20. # user.regenerate_token # => true
  21. # user.regenerate_auth_token # => true
  22. #
  23. # <tt>SecureRandom::base58</tt> is used to generate at minimum a 24-character unique token, so collisions are highly unlikely.
  24. #
  25. # Note that it's still possible to generate a race condition in the database in the same way that
  26. # {validates_uniqueness_of}[rdoc-ref:Validations::ClassMethods#validates_uniqueness_of] can.
  27. # You're encouraged to add a unique index in the database to deal with this even more unlikely scenario.
  28. 3 def has_secure_token(attribute = :token, length: MINIMUM_TOKEN_LENGTH)
  29. 9 if length < MINIMUM_TOKEN_LENGTH
  30. 3 raise MinimumLengthError, "Token requires a minimum length of #{MINIMUM_TOKEN_LENGTH} characters."
  31. end
  32. # Load securerandom only when has_secure_token is used.
  33. 6 require "active_support/core_ext/securerandom"
  34. 12 define_method("regenerate_#{attribute}") { update! attribute => self.class.generate_unique_secure_token(length: length) }
  35. 90 before_create { send("#{attribute}=", self.class.generate_unique_secure_token(length: length)) unless send("#{attribute}?") }
  36. end
  37. 3 def generate_unique_secure_token(length: MINIMUM_TOKEN_LENGTH)
  38. 84 SecureRandom.base58(length)
  39. end
  40. end
  41. end
  42. end

lib/active_record/serialization.rb

100.0% lines covered

12 relevant lines. 12 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 module ActiveRecord #:nodoc:
  3. # = Active Record \Serialization
  4. 3 module Serialization
  5. 3 extend ActiveSupport::Concern
  6. 3 include ActiveModel::Serializers::JSON
  7. 3 included do
  8. 3 self.include_root_in_json = false
  9. end
  10. 3 def serializable_hash(options = nil)
  11. 252 if self.class._has_attribute?(self.class.inheritance_column)
  12. 159 options = options ? options.dup : {}
  13. 159 options[:except] = Array(options[:except]).map(&:to_s)
  14. 159 options[:except] |= Array(self.class.inheritance_column)
  15. end
  16. 252 super(options)
  17. end
  18. end
  19. end

lib/active_record/signed_id.rb

100.0% lines covered

26 relevant lines. 26 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 module ActiveRecord
  3. # = Active Record Signed Id
  4. 3 module SignedId
  5. 3 extend ActiveSupport::Concern
  6. 3 included do
  7. ##
  8. # :singleton-method:
  9. # Set the secret used for the signed id verifier instance when using Active Record outside of Rails.
  10. # Within Rails, this is automatically set using the Rails application key generator.
  11. 3 mattr_accessor :signed_id_verifier_secret, instance_writer: false
  12. end
  13. 3 module ClassMethods
  14. # Lets you find a record based on a signed id that's safe to put into the world without risk of tampering.
  15. # This is particularly useful for things like password reset or email verification, where you want
  16. # the bearer of the signed id to be able to interact with the underlying record, but usually only within
  17. # a certain time period.
  18. #
  19. # You set the time period that the signed id is valid for during generation, using the instance method
  20. # +signed_id(expires_in: 15.minutes)+. If the time has elapsed before a signed find is attempted,
  21. # the signed id will no longer be valid, and nil is returned.
  22. #
  23. # It's possible to further restrict the use of a signed id with a purpose. This helps when you have a
  24. # general base model, like a User, which might have signed ids for several things, like password reset
  25. # or email verification. The purpose that was set during generation must match the purpose set when
  26. # finding. If there's a mismatch, nil is again returned.
  27. #
  28. # ==== Examples
  29. #
  30. # signed_id = User.first.signed_id expires_in: 15.minutes, purpose: :password_reset
  31. #
  32. # User.find_signed signed_id # => nil, since the purpose does not match
  33. #
  34. # travel 16.minutes
  35. # User.find_signed signed_id, purpose: :password_reset # => nil, since the signed id has expired
  36. #
  37. # travel_back
  38. # User.find_signed signed_id, purpose: :password_reset # => User.first
  39. 3 def find_signed(signed_id, purpose: nil)
  40. 24 raise UnknownPrimaryKey.new(self) if primary_key.nil?
  41. 21 if id = signed_id_verifier.verified(signed_id, purpose: combine_signed_id_purposes(purpose))
  42. 15 find_by primary_key => id
  43. end
  44. end
  45. # Works like +find_signed+, but will raise an +ActiveSupport::MessageVerifier::InvalidSignature+
  46. # exception if the +signed_id+ has either expired, has a purpose mismatch, is for another record,
  47. # or has been tampered with. It will also raise an +ActiveRecord::RecordNotFound+ exception if
  48. # the valid signed id can't find a record.
  49. #
  50. # === Examples
  51. #
  52. # User.find_signed! "bad data" # => ActiveSupport::MessageVerifier::InvalidSignature
  53. #
  54. # signed_id = User.first.signed_id
  55. # User.first.destroy
  56. # User.find_signed! signed_id # => ActiveRecord::RecordNotFound
  57. 3 def find_signed!(signed_id, purpose: nil)
  58. 12 if id = signed_id_verifier.verify(signed_id, purpose: combine_signed_id_purposes(purpose))
  59. 6 find(id)
  60. end
  61. end
  62. # The verifier instance that all signed ids are generated and verified from. By default, it'll be initialized
  63. # with the class-level +signed_id_verifier_secret+, which within Rails comes from the
  64. # Rails.application.key_generator. By default, it's SHA256 for the digest and JSON for the serialization.
  65. 3 def signed_id_verifier
  66. 75 @signed_id_verifier ||= begin
  67. 17 secret = signed_id_verifier_secret
  68. 17 secret = secret.call if secret.respond_to?(:call)
  69. 17 if secret.nil?
  70. 6 raise ArgumentError, "You must set ActiveRecord::Base.signed_id_verifier_secret to use signed ids"
  71. else
  72. 11 ActiveSupport::MessageVerifier.new secret, digest: "SHA256", serializer: JSON
  73. end
  74. end
  75. end
  76. # Allows you to pass in a custom verifier used for the signed ids. This also allows you to use different
  77. # verifiers for different classes. This is also helpful if you need to rotate keys, as you can prepare
  78. # your custom verifier for that in advance. See +ActiveSupport::MessageVerifier+ for details.
  79. 3 def signed_id_verifier=(verifier)
  80. 6 @signed_id_verifier = verifier
  81. end
  82. # :nodoc:
  83. 3 def combine_signed_id_purposes(purpose)
  84. 60 [ name.underscore, purpose.to_s ].compact_blank.join("/")
  85. end
  86. end
  87. # Returns a signed id that's generated using a preconfigured +ActiveSupport::MessageVerifier+ instance.
  88. # This signed id is tamper proof, so it's safe to send in an email or otherwise share with the outside world.
  89. # It can further more be set to expire (the default is not to expire), and scoped down with a specific purpose.
  90. # If the expiration date has been exceeded before +find_signed+ is called, the id won't find the designated
  91. # record. If a purpose is set, this too must match.
  92. #
  93. # If you accidentally let a signed id out in the wild that you wish to retract sooner than its expiration date
  94. # (or maybe you forgot to set an expiration date while meaning to!), you can use the purpose to essentially
  95. # version the signed_id, like so:
  96. #
  97. # user.signed_id purpose: :v2
  98. #
  99. # And you then change your +find_signed+ calls to require this new purpose. Any old signed ids that were not
  100. # created with the purpose will no longer find the record.
  101. 3 def signed_id(expires_in: nil, purpose: nil)
  102. 33 self.class.signed_id_verifier.generate id, expires_in: expires_in, purpose: self.class.combine_signed_id_purposes(purpose)
  103. end
  104. end
  105. end

lib/active_record/statement_cache.rb

98.73% lines covered

79 relevant lines. 78 lines covered and 1 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 module ActiveRecord
  3. # Statement cache is used to cache a single statement in order to avoid creating the AST again.
  4. # Initializing the cache is done by passing the statement in the create block:
  5. #
  6. # cache = StatementCache.create(Book.connection) do |params|
  7. # Book.where(name: "my book").where("author_id > 3")
  8. # end
  9. #
  10. # The cached statement is executed by using the
  11. # {connection.execute}[rdoc-ref:ConnectionAdapters::DatabaseStatements#execute] method:
  12. #
  13. # cache.execute([], Book.connection)
  14. #
  15. # The relation returned by the block is cached, and for each
  16. # {execute}[rdoc-ref:ConnectionAdapters::DatabaseStatements#execute]
  17. # call the cached relation gets duped. Database is queried when +to_a+ is called on the relation.
  18. #
  19. # If you want to cache the statement without the values you can use the +bind+ method of the
  20. # block parameter.
  21. #
  22. # cache = StatementCache.create(Book.connection) do |params|
  23. # Book.where(name: params.bind)
  24. # end
  25. #
  26. # And pass the bind values as the first argument of +execute+ call.
  27. #
  28. # cache.execute(["my book"], Book.connection)
  29. 3 class StatementCache # :nodoc:
  30. 3 class Substitute; end # :nodoc:
  31. 3 class Query # :nodoc:
  32. 3 def initialize(sql)
  33. 1262 @sql = sql
  34. end
  35. 3 def sql_for(binds, connection)
  36. 6171 @sql
  37. end
  38. end
  39. 3 class PartialQuery < Query # :nodoc:
  40. 3 def initialize(values)
  41. 15 @values = values
  42. 15 @indexes = values.each_with_index.find_all { |thing, i|
  43. 309 Substitute === thing
  44. }.map(&:last)
  45. end
  46. 3 def sql_for(binds, connection)
  47. 15 val = @values.dup
  48. 15 @indexes.each do |i|
  49. 48 value = binds.shift
  50. 48 if ActiveModel::Attribute === value
  51. 24 value = value.value_for_database
  52. end
  53. 48 val[i] = connection.quote(value)
  54. end
  55. 15 val.join
  56. end
  57. end
  58. 3 class PartialQueryCollector
  59. 3 attr_accessor :preparable
  60. 3 def initialize
  61. 15 @parts = []
  62. 15 @binds = []
  63. end
  64. 3 def <<(str)
  65. 243 @parts << str
  66. 243 self
  67. end
  68. 3 def add_bind(obj)
  69. 24 @binds << obj
  70. 24 @parts << Substitute.new
  71. 24 self
  72. end
  73. 3 def add_binds(binds)
  74. 6 @binds.concat binds
  75. 6 binds.size.times do |i|
  76. 24 @parts << ", " unless i == 0
  77. 24 @parts << Substitute.new
  78. end
  79. 6 self
  80. end
  81. 3 def value
  82. 15 [@parts, @binds]
  83. end
  84. end
  85. 3 def self.query(sql)
  86. 1262 Query.new(sql)
  87. end
  88. 3 def self.partial_query(values)
  89. 15 PartialQuery.new(values)
  90. end
  91. 3 def self.partial_query_collector
  92. 15 PartialQueryCollector.new
  93. end
  94. 3 class Params # :nodoc:
  95. 1364 def bind; Substitute.new; end
  96. end
  97. 3 class BindMap # :nodoc:
  98. 3 def initialize(bound_attributes)
  99. 1277 @indexes = []
  100. 1277 @bound_attributes = bound_attributes
  101. 1277 bound_attributes.each_with_index do |attr, i|
  102. 2594 if ActiveModel::Attribute === attr && Substitute === attr.value
  103. 1361 @indexes << i
  104. end
  105. end
  106. end
  107. 3 def bind(values)
  108. 6186 bas = @bound_attributes.dup
  109. 12682 @indexes.each_with_index { |offset, i| bas[offset] = bas[offset].with_cast_value(values[i]) }
  110. 6186 bas
  111. end
  112. end
  113. 3 def self.create(connection, callable = nil, &block)
  114. 1277 relation = (callable || block).call Params.new
  115. 1277 query_builder, binds = connection.cacheable_query(self, relation.arel)
  116. 1277 bind_map = BindMap.new(binds)
  117. 1277 new(query_builder, bind_map, relation.klass)
  118. end
  119. 3 def initialize(query_builder, bind_map, klass)
  120. 1277 @query_builder = query_builder
  121. 1277 @bind_map = bind_map
  122. 1277 @klass = klass
  123. end
  124. 3 def execute(params, connection, &block)
  125. 6186 bind_values = bind_map.bind params
  126. 6186 sql = query_builder.sql_for bind_values, connection
  127. 6186 klass.find_by_sql(sql, bind_values, preparable: true, &block)
  128. rescue ::RangeError
  129. []
  130. end
  131. 3 def self.unsupported_value?(value)
  132. 3128 case value
  133. 84 when NilClass, Array, Range, Hash, Relation, Base then true
  134. end
  135. end
  136. 3 private
  137. 3 attr_reader :query_builder, :bind_map, :klass
  138. end
  139. end

lib/active_record/store.rb

98.21% lines covered

112 relevant lines. 110 lines covered and 2 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 require "active_support/core_ext/hash/indifferent_access"
  3. 3 module ActiveRecord
  4. # Store gives you a thin wrapper around serialize for the purpose of storing hashes in a single column.
  5. # It's like a simple key/value store baked into your record when you don't care about being able to
  6. # query that store outside the context of a single record.
  7. #
  8. # You can then declare accessors to this store that are then accessible just like any other attribute
  9. # of the model. This is very helpful for easily exposing store keys to a form or elsewhere that's
  10. # already built around just accessing attributes on the model.
  11. #
  12. # Every accessor comes with dirty tracking methods (+key_changed?+, +key_was+ and +key_change+) and
  13. # methods to access the changes made during the last save (+saved_change_to_key?+, +saved_change_to_key+ and
  14. # +key_before_last_save+).
  15. #
  16. # NOTE: There is no +key_will_change!+ method for accessors, use +store_will_change!+ instead.
  17. #
  18. # Make sure that you declare the database column used for the serialized store as a text, so there's
  19. # plenty of room.
  20. #
  21. # You can set custom coder to encode/decode your serialized attributes to/from different formats.
  22. # JSON, YAML, Marshal are supported out of the box. Generally it can be any wrapper that provides +load+ and +dump+.
  23. #
  24. # NOTE: If you are using structured database data types (e.g. PostgreSQL +hstore+/+json+, or MySQL 5.7+
  25. # +json+) there is no need for the serialization provided by {.store}[rdoc-ref:rdoc-ref:ClassMethods#store].
  26. # Simply use {.store_accessor}[rdoc-ref:ClassMethods#store_accessor] instead to generate
  27. # the accessor methods. Be aware that these columns use a string keyed hash and do not allow access
  28. # using a symbol.
  29. #
  30. # NOTE: The default validations with the exception of +uniqueness+ will work.
  31. # For example, if you want to check for +uniqueness+ with +hstore+ you will
  32. # need to use a custom validation to handle it.
  33. #
  34. # Examples:
  35. #
  36. # class User < ActiveRecord::Base
  37. # store :settings, accessors: [ :color, :homepage ], coder: JSON
  38. # store :parent, accessors: [ :name ], coder: JSON, prefix: true
  39. # store :spouse, accessors: [ :name ], coder: JSON, prefix: :partner
  40. # store :settings, accessors: [ :two_factor_auth ], suffix: true
  41. # store :settings, accessors: [ :login_retry ], suffix: :config
  42. # end
  43. #
  44. # u = User.new(color: 'black', homepage: '37signals.com', parent_name: 'Mary', partner_name: 'Lily')
  45. # u.color # Accessor stored attribute
  46. # u.parent_name # Accessor stored attribute with prefix
  47. # u.partner_name # Accessor stored attribute with custom prefix
  48. # u.two_factor_auth_settings # Accessor stored attribute with suffix
  49. # u.login_retry_config # Accessor stored attribute with custom suffix
  50. # u.settings[:country] = 'Denmark' # Any attribute, even if not specified with an accessor
  51. #
  52. # # There is no difference between strings and symbols for accessing custom attributes
  53. # u.settings[:country] # => 'Denmark'
  54. # u.settings['country'] # => 'Denmark'
  55. #
  56. # # Dirty tracking
  57. # u.color = 'green'
  58. # u.color_changed? # => true
  59. # u.color_was # => 'black'
  60. # u.color_change # => ['black', 'red']
  61. #
  62. # # Add additional accessors to an existing store through store_accessor
  63. # class SuperUser < User
  64. # store_accessor :settings, :privileges, :servants
  65. # store_accessor :parent, :birthday, prefix: true
  66. # store_accessor :settings, :secret_question, suffix: :config
  67. # end
  68. #
  69. # The stored attribute names can be retrieved using {.stored_attributes}[rdoc-ref:rdoc-ref:ClassMethods#stored_attributes].
  70. #
  71. # User.stored_attributes[:settings] # [:color, :homepage, :two_factor_auth, :login_retry]
  72. #
  73. # == Overwriting default accessors
  74. #
  75. # All stored values are automatically available through accessors on the Active Record
  76. # object, but sometimes you want to specialize this behavior. This can be done by overwriting
  77. # the default accessors (using the same name as the attribute) and calling <tt>super</tt>
  78. # to actually change things.
  79. #
  80. # class Song < ActiveRecord::Base
  81. # # Uses a stored integer to hold the volume adjustment of the song
  82. # store :settings, accessors: [:volume_adjustment]
  83. #
  84. # def volume_adjustment=(decibels)
  85. # super(decibels.to_i)
  86. # end
  87. #
  88. # def volume_adjustment
  89. # super.to_i
  90. # end
  91. # end
  92. 3 module Store
  93. 3 extend ActiveSupport::Concern
  94. 3 included do
  95. 3 class << self
  96. 3 attr_accessor :local_stored_attributes
  97. end
  98. end
  99. 3 module ClassMethods
  100. 3 def store(store_attribute, options = {})
  101. 27 serialize store_attribute, IndifferentCoder.new(store_attribute, options[:coder])
  102. 27 store_accessor(store_attribute, options[:accessors], **options.slice(:prefix, :suffix)) if options.has_key? :accessors
  103. end
  104. 3 def store_accessor(store_attribute, *keys, prefix: nil, suffix: nil)
  105. 61 keys = keys.flatten
  106. 61 accessor_prefix =
  107. case prefix
  108. when String, Symbol
  109. 6 "#{prefix}_"
  110. when TrueClass
  111. 3 "#{store_attribute}_"
  112. else
  113. 52 ""
  114. end
  115. 61 accessor_suffix =
  116. case suffix
  117. when String, Symbol
  118. 3 "_#{suffix}"
  119. when TrueClass
  120. 3 "_#{store_attribute}"
  121. else
  122. 55 ""
  123. end
  124. 61 _store_accessors_module.module_eval do
  125. 61 keys.each do |key|
  126. 80 accessor_key = "#{accessor_prefix}#{key}#{accessor_suffix}"
  127. 80 define_method("#{accessor_key}=") do |value|
  128. 1029 write_store_attribute(store_attribute, key, value)
  129. end
  130. 80 define_method(accessor_key) do
  131. 144 read_store_attribute(store_attribute, key)
  132. end
  133. 80 define_method("#{accessor_key}_changed?") do
  134. 30 return false unless attribute_changed?(store_attribute)
  135. 23 prev_store, new_store = changes[store_attribute]
  136. 23 prev_store&.dig(key) != new_store&.dig(key)
  137. end
  138. 80 define_method("#{accessor_key}_change") do
  139. 26 return unless attribute_changed?(store_attribute)
  140. 20 prev_store, new_store = changes[store_attribute]
  141. 20 [prev_store&.dig(key), new_store&.dig(key)]
  142. end
  143. 80 define_method("#{accessor_key}_was") do
  144. 20 return unless attribute_changed?(store_attribute)
  145. 17 prev_store, _new_store = changes[store_attribute]
  146. 17 prev_store&.dig(key)
  147. end
  148. 80 define_method("saved_change_to_#{accessor_key}?") do
  149. 3 return false unless saved_change_to_attribute?(store_attribute)
  150. 3 prev_store, new_store = saved_change_to_attribute(store_attribute)
  151. 3 prev_store&.dig(key) != new_store&.dig(key)
  152. end
  153. 80 define_method("saved_change_to_#{accessor_key}") do
  154. 3 return unless saved_change_to_attribute?(store_attribute)
  155. 3 prev_store, new_store = saved_change_to_attribute(store_attribute)
  156. 3 [prev_store&.dig(key), new_store&.dig(key)]
  157. end
  158. 80 define_method("#{accessor_key}_before_last_save") do
  159. 3 return unless saved_change_to_attribute?(store_attribute)
  160. 3 prev_store, _new_store = saved_change_to_attribute(store_attribute)
  161. 3 prev_store&.dig(key)
  162. end
  163. end
  164. end
  165. # assign new store attribute and create new hash to ensure that each class in the hierarchy
  166. # has its own hash of stored attributes.
  167. 61 self.local_stored_attributes ||= {}
  168. 61 self.local_stored_attributes[store_attribute] ||= []
  169. 61 self.local_stored_attributes[store_attribute] |= keys
  170. end
  171. 3 def _store_accessors_module # :nodoc:
  172. 61 @_store_accessors_module ||= begin
  173. 28 mod = Module.new
  174. 28 include mod
  175. 28 mod
  176. end
  177. end
  178. 3 def stored_attributes
  179. 54 parent = superclass.respond_to?(:stored_attributes) ? superclass.stored_attributes : {}
  180. 54 if local_stored_attributes
  181. 36 parent.merge!(local_stored_attributes) { |k, a, b| a | b }
  182. end
  183. 54 parent
  184. end
  185. end
  186. 3 private
  187. 3 def read_store_attribute(store_attribute, key) # :doc:
  188. 147 accessor = store_accessor_for(store_attribute)
  189. 147 accessor.read(self, store_attribute, key)
  190. end
  191. 3 def write_store_attribute(store_attribute, key, value) # :doc:
  192. 1032 accessor = store_accessor_for(store_attribute)
  193. 1032 accessor.write(self, store_attribute, key, value)
  194. end
  195. 3 def store_accessor_for(store_attribute)
  196. 1179 type_for_attribute(store_attribute).accessor
  197. end
  198. 3 class HashAccessor # :nodoc:
  199. 3 def self.read(object, attribute, key)
  200. 1179 prepare(object, attribute)
  201. 1179 object.public_send(attribute)[key]
  202. end
  203. 3 def self.write(object, attribute, key, value)
  204. 1032 prepare(object, attribute)
  205. 1032 if value != read(object, attribute, key)
  206. 1026 object.public_send :"#{attribute}_will_change!"
  207. 1026 object.public_send(attribute)[key] = value
  208. end
  209. end
  210. 3 def self.prepare(object, attribute)
  211. 135 object.public_send :"#{attribute}=", {} unless object.send(attribute)
  212. end
  213. end
  214. 3 class StringKeyedHashAccessor < HashAccessor # :nodoc:
  215. 3 def self.read(object, attribute, key)
  216. 99 super object, attribute, key.to_s
  217. end
  218. 3 def self.write(object, attribute, key, value)
  219. 36 super object, attribute, key.to_s, value
  220. end
  221. end
  222. 3 class IndifferentHashAccessor < ActiveRecord::Store::HashAccessor # :nodoc:
  223. 3 def self.prepare(object, store_attribute)
  224. 2076 attribute = object.send(store_attribute)
  225. 2076 unless attribute.is_a?(ActiveSupport::HashWithIndifferentAccess)
  226. attribute = IndifferentCoder.as_indifferent_hash(attribute)
  227. object.send :"#{store_attribute}=", attribute
  228. end
  229. 2076 attribute
  230. end
  231. end
  232. 3 class IndifferentCoder # :nodoc:
  233. 3 def initialize(attr_name, coder_or_class_name)
  234. 27 @coder =
  235. 27 if coder_or_class_name.respond_to?(:load) && coder_or_class_name.respond_to?(:dump)
  236. 9 coder_or_class_name
  237. else
  238. 18 ActiveRecord::Coders::YAMLColumn.new(attr_name, coder_or_class_name || Object)
  239. end
  240. end
  241. 3 def dump(obj)
  242. 1740 @coder.dump self.class.as_indifferent_hash(obj)
  243. end
  244. 3 def load(yaml)
  245. 5010 self.class.as_indifferent_hash(@coder.load(yaml || ""))
  246. end
  247. 3 def self.as_indifferent_hash(obj)
  248. 6750 case obj
  249. when ActiveSupport::HashWithIndifferentAccess
  250. 1914 obj
  251. when Hash
  252. 1263 obj.with_indifferent_access
  253. else
  254. 3573 ActiveSupport::HashWithIndifferentAccess.new
  255. end
  256. end
  257. end
  258. end
  259. end

lib/active_record/suppressor.rb

100.0% lines covered

18 relevant lines. 18 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 module ActiveRecord
  3. # ActiveRecord::Suppressor prevents the receiver from being saved during
  4. # a given block.
  5. #
  6. # For example, here's a pattern of creating notifications when new comments
  7. # are posted. (The notification may in turn trigger an email, a push
  8. # notification, or just appear in the UI somewhere):
  9. #
  10. # class Comment < ActiveRecord::Base
  11. # belongs_to :commentable, polymorphic: true
  12. # after_create -> { Notification.create! comment: self,
  13. # recipients: commentable.recipients }
  14. # end
  15. #
  16. # That's what you want the bulk of the time. New comment creates a new
  17. # Notification. But there may well be off cases, like copying a commentable
  18. # and its comments, where you don't want that. So you'd have a concern
  19. # something like this:
  20. #
  21. # module Copyable
  22. # def copy_to(destination)
  23. # Notification.suppress do
  24. # # Copy logic that creates new comments that we do not want
  25. # # triggering notifications.
  26. # end
  27. # end
  28. # end
  29. 3 module Suppressor
  30. 3 extend ActiveSupport::Concern
  31. 3 module ClassMethods
  32. 3 def suppress(&block)
  33. 21 previous_state = SuppressorRegistry.suppressed[name]
  34. 21 SuppressorRegistry.suppressed[name] = true
  35. 21 yield
  36. ensure
  37. 21 SuppressorRegistry.suppressed[name] = previous_state
  38. end
  39. end
  40. 3 def save(**) # :nodoc:
  41. 8192 SuppressorRegistry.suppressed[self.class.name] ? true : super
  42. end
  43. 3 def save!(**) # :nodoc:
  44. 8271 SuppressorRegistry.suppressed[self.class.name] ? true : super
  45. end
  46. end
  47. 3 class SuppressorRegistry # :nodoc:
  48. 3 extend ActiveSupport::PerThreadRegistry
  49. 3 attr_reader :suppressed
  50. 3 def initialize
  51. 14 @suppressed = {}
  52. end
  53. end
  54. end

lib/active_record/table_metadata.rb

100.0% lines covered

42 relevant lines. 42 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 module ActiveRecord
  3. 3 class TableMetadata # :nodoc:
  4. 3 delegate :join_primary_key, :join_foreign_key, :join_foreign_type, to: :reflection
  5. 3 def initialize(klass, arel_table, reflection = nil)
  6. 12018 @klass = klass
  7. 12018 @arel_table = arel_table
  8. 12018 @reflection = reflection
  9. end
  10. 3 def type(column_name)
  11. 151665 arel_table.type_for_attribute(column_name)
  12. end
  13. 3 def has_column?(column_name)
  14. 3976 klass&.columns_hash.key?(column_name)
  15. end
  16. 3 def associated_with?(table_name)
  17. 49109 klass&._reflect_on_association(table_name) || klass&._reflect_on_association(table_name.singularize)
  18. end
  19. 3 def associated_table(table_name)
  20. 4586 reflection = klass._reflect_on_association(table_name) || klass._reflect_on_association(table_name.singularize)
  21. 4586 if !reflection && table_name == arel_table.name
  22. 273 return self
  23. end
  24. 4313 reflection ||= yield table_name if block_given?
  25. 4304 if reflection && !reflection.polymorphic?
  26. 3750 association_klass = reflection.klass
  27. 3750 arel_table = association_klass.arel_table
  28. 3750 arel_table = arel_table.alias(table_name) if arel_table.name != table_name
  29. 3750 TableMetadata.new(association_klass, arel_table, reflection)
  30. else
  31. 554 type_caster = TypeCaster::Connection.new(klass, table_name)
  32. 554 arel_table = Arel::Table.new(table_name, type_caster: type_caster)
  33. 554 TableMetadata.new(nil, arel_table, reflection)
  34. end
  35. end
  36. 3 def polymorphic_association?
  37. 439 reflection&.polymorphic?
  38. end
  39. 3 def through_association?
  40. 348 reflection&.through_reflection?
  41. end
  42. 3 def reflect_on_aggregation(aggregation_name)
  43. 48745 klass&.reflect_on_aggregation(aggregation_name)
  44. end
  45. 3 alias :aggregated_with? :reflect_on_aggregation
  46. 3 def predicate_builder
  47. 3949 if klass
  48. 3495 predicate_builder = klass.predicate_builder.dup
  49. 3495 predicate_builder.instance_variable_set(:@table, self)
  50. 3495 predicate_builder
  51. else
  52. 454 PredicateBuilder.new(self)
  53. end
  54. end
  55. 3 attr_reader :arel_table
  56. 3 private
  57. 3 attr_reader :klass, :reflection
  58. end
  59. end

lib/active_record/tasks/database_tasks.rb

68.77% lines covered

269 relevant lines. 185 lines covered and 84 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 require "active_record/database_configurations"
  3. 3 module ActiveRecord
  4. 3 module Tasks # :nodoc:
  5. 3 class DatabaseNotSupported < StandardError; end # :nodoc:
  6. # ActiveRecord::Tasks::DatabaseTasks is a utility class, which encapsulates
  7. # logic behind common tasks used to manage database and migrations.
  8. #
  9. # The tasks defined here are used with Rails commands provided by Active Record.
  10. #
  11. # In order to use DatabaseTasks, a few config values need to be set. All the needed
  12. # config values are set by Rails already, so it's necessary to do it only if you
  13. # want to change the defaults or when you want to use Active Record outside of Rails
  14. # (in such case after configuring the database tasks, you can also use the rake tasks
  15. # defined in Active Record).
  16. #
  17. # The possible config values are:
  18. #
  19. # * +env+: current environment (like Rails.env).
  20. # * +database_configuration+: configuration of your databases (as in +config/database.yml+).
  21. # * +db_dir+: your +db+ directory.
  22. # * +fixtures_path+: a path to fixtures directory.
  23. # * +migrations_paths+: a list of paths to directories with migrations.
  24. # * +seed_loader+: an object which will load seeds, it needs to respond to the +load_seed+ method.
  25. # * +root+: a path to the root of the application.
  26. #
  27. # Example usage of DatabaseTasks outside Rails could look as such:
  28. #
  29. # include ActiveRecord::Tasks
  30. # DatabaseTasks.database_configuration = YAML.load_file('my_database_config.yml')
  31. # DatabaseTasks.db_dir = 'db'
  32. # # other settings...
  33. #
  34. # DatabaseTasks.create_current('production')
  35. 3 module DatabaseTasks
  36. ##
  37. # :singleton-method:
  38. # Extra flags passed to database CLI tool (mysqldump/pg_dump) when calling db:schema:dump
  39. 3 mattr_accessor :structure_dump_flags, instance_accessor: false
  40. ##
  41. # :singleton-method:
  42. # Extra flags passed to database CLI tool when calling db:schema:load
  43. 3 mattr_accessor :structure_load_flags, instance_accessor: false
  44. 3 extend self
  45. 3 attr_writer :current_config, :db_dir, :migrations_paths, :fixtures_path, :root, :env, :seed_loader
  46. 3 deprecate :current_config=
  47. 3 attr_accessor :database_configuration
  48. 3 LOCAL_HOSTS = ["127.0.0.1", "localhost"]
  49. 3 def check_protected_environments!
  50. 15 unless ENV["DISABLE_DATABASE_ENVIRONMENT_CHECK"]
  51. 15 current = ActiveRecord::Base.connection.migration_context.current_environment
  52. 15 stored = ActiveRecord::Base.connection.migration_context.last_stored_environment
  53. 12 if ActiveRecord::Base.connection.migration_context.protected_environment?
  54. 6 raise ActiveRecord::ProtectedEnvironmentError.new(stored)
  55. end
  56. 6 if stored && stored != current
  57. raise ActiveRecord::EnvironmentMismatchError.new(current: current, stored: stored)
  58. end
  59. end
  60. rescue ActiveRecord::NoDatabaseError
  61. end
  62. 3 def register_task(pattern, task)
  63. 12 @tasks ||= {}
  64. 12 @tasks[pattern] = task
  65. end
  66. 3 register_task(/mysql/, "ActiveRecord::Tasks::MySQLDatabaseTasks")
  67. 3 register_task(/postgresql/, "ActiveRecord::Tasks::PostgreSQLDatabaseTasks")
  68. 3 register_task(/sqlite/, "ActiveRecord::Tasks::SQLiteDatabaseTasks")
  69. 3 def db_dir
  70. @db_dir ||= Rails.application.config.paths["db"].first
  71. end
  72. 3 def migrations_paths
  73. @migrations_paths ||= Rails.application.paths["db/migrate"].to_a
  74. end
  75. 3 def fixtures_path
  76. @fixtures_path ||= if ENV["FIXTURES_PATH"]
  77. File.join(root, ENV["FIXTURES_PATH"])
  78. else
  79. File.join(root, "test", "fixtures")
  80. end
  81. end
  82. 3 def root
  83. @root ||= Rails.root
  84. end
  85. 3 def env
  86. @env ||= Rails.env
  87. end
  88. 3 def spec
  89. @spec ||= "primary"
  90. end
  91. 3 deprecate spec: "please use name instead"
  92. 3 def name
  93. @name ||= "primary"
  94. end
  95. 3 def seed_loader
  96. @seed_loader ||= Rails.application
  97. end
  98. 3 def current_config(options = {})
  99. 18 if options.has_key?(:config)
  100. 6 @current_config = options[:config]
  101. else
  102. 12 env_name = options[:env] || env
  103. 12 name = options[:spec] || "primary"
  104. 12 @current_config ||= ActiveRecord::Base.configurations.configs_for(env_name: env_name, name: name)&.configuration_hash
  105. end
  106. end
  107. 3 deprecate :current_config
  108. 3 def create(configuration, *arguments)
  109. 29 db_config = resolve_configuration(configuration)
  110. 29 database_adapter_for(db_config, *arguments).create
  111. 21 $stdout.puts "Created database '#{db_config.database}'" if verbose?
  112. rescue DatabaseAlreadyExists
  113. 5 $stderr.puts "Database '#{db_config.database}' already exists" if verbose?
  114. rescue Exception => error
  115. 3 $stderr.puts error
  116. 3 $stderr.puts "Couldn't create '#{db_config.database}' database. Please check your configuration."
  117. 3 raise
  118. end
  119. 3 def create_all
  120. 18 old_pool = ActiveRecord::Base.connection_handler.retrieve_connection_pool(ActiveRecord::Base.connection_specification_name)
  121. 27 each_local_configuration { |db_config| create(db_config) }
  122. 18 if old_pool
  123. 18 ActiveRecord::Base.connection_handler.establish_connection(old_pool.db_config)
  124. end
  125. end
  126. 3 def setup_initial_database_yaml
  127. return {} unless defined?(Rails)
  128. begin
  129. Rails.application.config.load_database_yaml
  130. rescue
  131. $stderr.puts "Rails couldn't infer whether you are using multiple databases from your database.yml and can't generate the tasks for the non-primary databases. If you'd like to use this feature, please simplify your ERB."
  132. {}
  133. end
  134. end
  135. 3 def for_each(databases)
  136. return {} unless defined?(Rails)
  137. database_configs = ActiveRecord::DatabaseConfigurations.new(databases).configs_for(env_name: Rails.env)
  138. # if this is a single database application we don't want tasks for each primary database
  139. return if database_configs.count == 1
  140. database_configs.each do |db_config|
  141. yield db_config.name
  142. end
  143. end
  144. 3 def raise_for_multi_db(environment = env, command:)
  145. db_configs = ActiveRecord::Base.configurations.configs_for(env_name: environment)
  146. if db_configs.count > 1
  147. dbs_list = []
  148. db_configs.each do |db|
  149. dbs_list << "#{command}:#{db.name}"
  150. end
  151. raise "You're using a multiple database application. To use `#{command}` you must run the namespaced task with a VERSION. Available tasks are #{dbs_list.to_sentence}."
  152. end
  153. end
  154. 3 def create_current(environment = env, name = nil)
  155. 84 each_current_configuration(environment, name) { |db_config| create(db_config) }
  156. 30 ActiveRecord::Base.establish_connection(environment.to_sym)
  157. end
  158. 3 def drop(configuration, *arguments)
  159. 22 db_config = resolve_configuration(configuration)
  160. 22 database_adapter_for(db_config, *arguments).drop
  161. 18 $stdout.puts "Dropped database '#{db_config.database}'" if verbose?
  162. rescue ActiveRecord::NoDatabaseError
  163. 4 $stderr.puts "Database '#{db_config.database}' does not exist"
  164. rescue Exception => error
  165. $stderr.puts error
  166. $stderr.puts "Couldn't drop database '#{db_config.database}'"
  167. raise
  168. end
  169. 3 def drop_all
  170. 27 each_local_configuration { |db_config| drop(db_config) }
  171. end
  172. 3 def drop_current(environment = env)
  173. 78 each_current_configuration(environment) { |db_config| drop(db_config) }
  174. end
  175. 3 def truncate_tables(db_config)
  176. 6 ActiveRecord::Base.establish_connection(db_config)
  177. 6 connection = ActiveRecord::Base.connection
  178. 6 connection.truncate_tables(*connection.tables)
  179. end
  180. 3 private :truncate_tables
  181. 3 def truncate_all(environment = env)
  182. 18 ActiveRecord::Base.configurations.configs_for(env_name: environment).each do |db_config|
  183. 30 truncate_tables(db_config)
  184. end
  185. end
  186. 3 def migrate
  187. 33 check_target_version
  188. 9 scope = ENV["SCOPE"]
  189. 9 verbose_was, Migration.verbose = Migration.verbose, verbose?
  190. 9 Base.connection.migration_context.migrate(target_version) do |migration|
  191. 18 scope.blank? || scope == migration.scope
  192. end
  193. 9 ActiveRecord::Base.clear_cache!
  194. ensure
  195. 33 Migration.verbose = verbose_was
  196. end
  197. 3 def migrate_status
  198. 1 unless ActiveRecord::Base.connection.schema_migration.table_exists?
  199. Kernel.abort "Schema migrations table does not exist yet."
  200. end
  201. # output
  202. 1 puts "\ndatabase: #{ActiveRecord::Base.connection_db_config.database}\n\n"
  203. 1 puts "#{'Status'.center(8)} #{'Migration ID'.ljust(14)} Migration Name"
  204. 1 puts "-" * 50
  205. 1 ActiveRecord::Base.connection.migration_context.migrations_status.each do |status, version, name|
  206. 3 puts "#{status.center(8)} #{version.ljust(14)} #{name}"
  207. end
  208. 1 puts
  209. end
  210. 3 def check_target_version
  211. 69 if target_version && !(Migration::MigrationFilenameRegexp.match?(ENV["VERSION"]) || /\A\d+\z/.match?(ENV["VERSION"]))
  212. 42 raise "Invalid format of target version: `VERSION=#{ENV['VERSION']}`"
  213. end
  214. end
  215. 3 def target_version
  216. 93 ENV["VERSION"].to_i if ENV["VERSION"] && !ENV["VERSION"].empty?
  217. end
  218. 3 def charset_current(env_name = env, db_name = name)
  219. 3 db_config = ActiveRecord::Base.configurations.configs_for(env_name: env_name, name: db_name)
  220. 3 charset(db_config)
  221. end
  222. 3 def charset(configuration, *arguments)
  223. 12 db_config = resolve_configuration(configuration)
  224. 12 database_adapter_for(db_config, *arguments).charset
  225. end
  226. 3 def collation_current(env_name = env, db_name = name)
  227. 3 db_config = ActiveRecord::Base.configurations.configs_for(env_name: env_name, name: db_name)
  228. 3 collation(db_config)
  229. end
  230. 3 def collation(configuration, *arguments)
  231. 12 db_config = resolve_configuration(configuration)
  232. 12 database_adapter_for(db_config, *arguments).collation
  233. end
  234. 3 def purge(configuration)
  235. 14 db_config = resolve_configuration(configuration)
  236. 14 database_adapter_for(db_config).purge
  237. end
  238. 3 def purge_all
  239. 6 each_local_configuration { |db_config| purge(db_config) }
  240. end
  241. 3 def purge_current(environment = env)
  242. 6 each_current_configuration(environment) { |db_config| purge(db_config) }
  243. 3 ActiveRecord::Base.establish_connection(environment.to_sym)
  244. end
  245. 3 def structure_dump(configuration, *arguments)
  246. 29 db_config = resolve_configuration(configuration)
  247. 29 filename = arguments.delete_at(0)
  248. 29 database_adapter_for(db_config, *arguments).structure_dump(filename, structure_dump_flags)
  249. end
  250. 3 def structure_load(configuration, *arguments)
  251. 14 db_config = resolve_configuration(configuration)
  252. 14 filename = arguments.delete_at(0)
  253. 14 database_adapter_for(db_config, *arguments).structure_load(filename, structure_load_flags)
  254. end
  255. 3 def load_schema(db_config, format = ActiveRecord::Base.schema_format, file = nil) # :nodoc:
  256. file ||= dump_filename(db_config.name, format)
  257. verbose_was, Migration.verbose = Migration.verbose, verbose? && ENV["VERBOSE"]
  258. check_schema_file(file)
  259. ActiveRecord::Base.establish_connection(db_config)
  260. case format
  261. when :ruby
  262. load(file)
  263. when :sql
  264. structure_load(db_config, file)
  265. else
  266. raise ArgumentError, "unknown format #{format.inspect}"
  267. end
  268. ActiveRecord::InternalMetadata.create_table
  269. ActiveRecord::InternalMetadata[:environment] = db_config.env_name
  270. ActiveRecord::InternalMetadata[:schema_sha1] = schema_sha1(file)
  271. ensure
  272. Migration.verbose = verbose_was
  273. end
  274. 3 def schema_up_to_date?(configuration, format = ActiveRecord::Base.schema_format, file = nil, environment = nil, name = nil)
  275. db_config = resolve_configuration(configuration)
  276. if environment || name
  277. ActiveSupport::Deprecation.warn("`environment` and `name` will be removed as parameters in 6.2.0, you may now pass an ActiveRecord::DatabaseConfigurations::DatabaseConfig as `configuration` instead.")
  278. end
  279. name ||= db_config.name
  280. file ||= dump_filename(name, format)
  281. return true unless File.exist?(file)
  282. ActiveRecord::Base.establish_connection(db_config)
  283. return false unless ActiveRecord::InternalMetadata.enabled?
  284. return false unless ActiveRecord::InternalMetadata.table_exists?
  285. ActiveRecord::InternalMetadata[:schema_sha1] == schema_sha1(file)
  286. end
  287. 3 def reconstruct_from_schema(db_config, format = ActiveRecord::Base.schema_format, file = nil) # :nodoc:
  288. file ||= dump_filename(db_config.name, format)
  289. check_schema_file(file)
  290. ActiveRecord::Base.establish_connection(db_config)
  291. if schema_up_to_date?(db_config, format, file)
  292. truncate_tables(db_config)
  293. else
  294. purge(db_config)
  295. load_schema(db_config, format, file)
  296. end
  297. rescue ActiveRecord::NoDatabaseError
  298. create(db_config)
  299. load_schema(db_config, format, file)
  300. end
  301. 3 def dump_schema(db_config, format = ActiveRecord::Base.schema_format) # :nodoc:
  302. require "active_record/schema_dumper"
  303. filename = dump_filename(db_config.name, format)
  304. connection = ActiveRecord::Base.connection
  305. case format
  306. when :ruby
  307. File.open(filename, "w:utf-8") do |file|
  308. ActiveRecord::SchemaDumper.dump(ActiveRecord::Base.connection, file)
  309. end
  310. when :sql
  311. structure_dump(db_config, filename)
  312. if connection.schema_migration.table_exists?
  313. File.open(filename, "a") do |f|
  314. f.puts connection.dump_schema_information
  315. f.print "\n"
  316. end
  317. end
  318. end
  319. end
  320. 3 def schema_file(format = ActiveRecord::Base.schema_format)
  321. 9 File.join(db_dir, schema_file_type(format))
  322. end
  323. 3 def schema_file_type(format = ActiveRecord::Base.schema_format)
  324. 9 case format
  325. when :ruby
  326. 6 "schema.rb"
  327. when :sql
  328. 3 "structure.sql"
  329. end
  330. end
  331. 3 def dump_filename(name, format = ActiveRecord::Base.schema_format)
  332. filename = if name == "primary"
  333. schema_file_type(format)
  334. else
  335. "#{name}_#{schema_file_type(format)}"
  336. end
  337. ENV["SCHEMA"] || File.join(ActiveRecord::Tasks::DatabaseTasks.db_dir, filename)
  338. end
  339. 3 def cache_dump_filename(name, schema_cache_path: nil)
  340. 12 filename = if name == "primary"
  341. 9 "schema_cache.yml"
  342. else
  343. 3 "#{name}_schema_cache.yml"
  344. end
  345. 12 schema_cache_path || ENV["SCHEMA_CACHE"] || File.join(ActiveRecord::Tasks::DatabaseTasks.db_dir, filename)
  346. end
  347. 3 def load_schema_current(format = ActiveRecord::Base.schema_format, file = nil, environment = env)
  348. each_current_configuration(environment) do |db_config|
  349. load_schema(db_config, format, file)
  350. end
  351. ActiveRecord::Base.establish_connection(environment.to_sym)
  352. end
  353. 3 def check_schema_file(filename)
  354. 3 unless File.exist?(filename)
  355. 3 message = +%{#{filename} doesn't exist yet. Run `bin/rails db:migrate` to create it, then try again.}
  356. 3 message << %{ If you do not intend to use a database, you should instead alter #{Rails.root}/config/application.rb to limit the frameworks that will be loaded.} if defined?(::Rails.root)
  357. 3 Kernel.abort message
  358. end
  359. end
  360. 3 def load_seed
  361. if seed_loader
  362. seed_loader.load_seed
  363. else
  364. raise "You tried to load seed data, but no seed loader is specified. Please specify seed " \
  365. "loader with ActiveRecord::Tasks::DatabaseTasks.seed_loader = your_seed_loader\n" \
  366. "Seed loader should respond to load_seed method"
  367. end
  368. end
  369. # Dumps the schema cache in YAML format for the connection into the file
  370. #
  371. # ==== Examples:
  372. # ActiveRecord::Tasks::DatabaseTasks.dump_schema_cache(ActiveRecord::Base.connection, "tmp/schema_dump.yaml")
  373. 3 def dump_schema_cache(conn, filename)
  374. 3 conn.schema_cache.dump_to(filename)
  375. end
  376. 3 def clear_schema_cache(filename)
  377. 3 FileUtils.rm_f filename, verbose: false
  378. end
  379. 3 private
  380. 3 def resolve_configuration(configuration)
  381. 132 Base.configurations.resolve(configuration)
  382. end
  383. 3 def verbose?
  384. 53 ENV["VERBOSE"] ? ENV["VERBOSE"] != "false" : true
  385. end
  386. # Create a new instance for the specified db configuration object
  387. # For classes that have been converted to use db_config objects, pass a
  388. # `DatabaseConfig`, otherwise pass a `Hash`
  389. 3 def database_adapter_for(db_config, *arguments)
  390. 132 klass = class_for_adapter(db_config.adapter)
  391. 129 converted = klass.respond_to?(:using_database_configurations?) && klass.using_database_configurations?
  392. 129 config = converted ? db_config : db_config.configuration_hash
  393. 129 klass.new(config, *arguments)
  394. end
  395. 3 def class_for_adapter(adapter)
  396. 440 _key, task = @tasks.each_pair.detect { |pattern, _task| adapter[pattern] }
  397. 132 unless task
  398. 3 raise DatabaseNotSupported, "Rake tasks not supported by '#{adapter}' adapter"
  399. end
  400. 129 task.is_a?(String) ? task.constantize : task
  401. end
  402. 3 def each_current_configuration(environment, name = nil)
  403. 57 environments = [environment]
  404. 57 environments << "test" if environment == "development" && !ENV["DATABASE_URL"]
  405. 57 environments.each do |env|
  406. 87 ActiveRecord::Base.configurations.configs_for(env_name: env).each do |db_config|
  407. 111 next if name && name != db_config.name
  408. 111 yield db_config
  409. end
  410. end
  411. end
  412. 3 def each_local_configuration
  413. 39 ActiveRecord::Base.configurations.configs_for.each do |db_config|
  414. 39 next unless db_config.database
  415. 33 if local_database?(db_config)
  416. 21 yield db_config
  417. else
  418. 12 $stderr.puts "This task only modifies local databases. #{db_config.database} is on a remote host."
  419. end
  420. end
  421. end
  422. 3 def local_database?(db_config)
  423. 33 host = db_config.host
  424. 33 host.blank? || LOCAL_HOSTS.include?(host)
  425. end
  426. 3 def schema_sha1(file)
  427. Digest::SHA1.hexdigest(File.read(file))
  428. end
  429. end
  430. end
  431. end

lib/active_record/tasks/mysql_database_tasks.rb

36.67% lines covered

60 relevant lines. 22 lines covered and 38 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 module ActiveRecord
  3. 3 module Tasks # :nodoc:
  4. 3 class MySQLDatabaseTasks # :nodoc:
  5. 3 ER_DB_CREATE_EXISTS = 1007
  6. 3 delegate :connection, :establish_connection, to: ActiveRecord::Base
  7. 3 def self.using_database_configurations?
  8. 21 true
  9. end
  10. 3 def initialize(db_config)
  11. @db_config = db_config
  12. @configuration_hash = db_config.configuration_hash
  13. end
  14. 3 def create
  15. establish_connection(configuration_hash_without_database)
  16. connection.create_database(db_config.database, creation_options)
  17. establish_connection(db_config)
  18. end
  19. 3 def drop
  20. establish_connection(db_config)
  21. connection.drop_database(db_config.database)
  22. end
  23. 3 def purge
  24. establish_connection(db_config)
  25. connection.recreate_database(db_config.database, creation_options)
  26. end
  27. 3 def charset
  28. connection.charset
  29. end
  30. 3 def collation
  31. connection.collation
  32. end
  33. 3 def structure_dump(filename, extra_flags)
  34. args = prepare_command_options
  35. args.concat(["--result-file", "#{filename}"])
  36. args.concat(["--no-data"])
  37. args.concat(["--routines"])
  38. args.concat(["--skip-comments"])
  39. ignore_tables = ActiveRecord::SchemaDumper.ignore_tables
  40. if ignore_tables.any?
  41. args += ignore_tables.map { |table| "--ignore-table=#{db_config.database}.#{table}" }
  42. end
  43. args.concat([db_config.database.to_s])
  44. args.unshift(*extra_flags) if extra_flags
  45. run_cmd("mysqldump", args, "dumping")
  46. end
  47. 3 def structure_load(filename, extra_flags)
  48. args = prepare_command_options
  49. args.concat(["--execute", %{SET FOREIGN_KEY_CHECKS = 0; SOURCE #{filename}; SET FOREIGN_KEY_CHECKS = 1}])
  50. args.concat(["--database", db_config.database.to_s])
  51. args.unshift(*extra_flags) if extra_flags
  52. run_cmd("mysql", args, "loading")
  53. end
  54. 3 private
  55. 3 attr_reader :db_config, :configuration_hash
  56. 3 def configuration_hash_without_database
  57. configuration_hash.merge(database: nil)
  58. end
  59. 3 def creation_options
  60. Hash.new.tap do |options|
  61. options[:charset] = configuration_hash[:encoding] if configuration_hash.include?(:encoding)
  62. options[:collation] = configuration_hash[:collation] if configuration_hash.include?(:collation)
  63. end
  64. end
  65. 3 def prepare_command_options
  66. args = {
  67. host: "--host",
  68. port: "--port",
  69. socket: "--socket",
  70. username: "--user",
  71. password: "--password",
  72. encoding: "--default-character-set",
  73. sslca: "--ssl-ca",
  74. sslcert: "--ssl-cert",
  75. sslcapath: "--ssl-capath",
  76. sslcipher: "--ssl-cipher",
  77. sslkey: "--ssl-key"
  78. }.map { |opt, arg| "#{arg}=#{configuration_hash[opt]}" if configuration_hash[opt] }.compact
  79. args
  80. end
  81. 3 def run_cmd(cmd, args, action)
  82. fail run_cmd_error(cmd, args, action) unless Kernel.system(cmd, *args)
  83. end
  84. 3 def run_cmd_error(cmd, args, action)
  85. msg = +"failed to execute: `#{cmd}`\n"
  86. msg << "Please check the output above for any errors and make sure that `#{cmd}` is installed in your PATH and has proper permissions.\n\n"
  87. msg
  88. end
  89. end
  90. end
  91. end

lib/active_record/tasks/postgresql_database_tasks.rb

100.0% lines covered

80 relevant lines. 80 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 require "tempfile"
  3. 3 module ActiveRecord
  4. 3 module Tasks # :nodoc:
  5. 3 class PostgreSQLDatabaseTasks # :nodoc:
  6. 3 DEFAULT_ENCODING = ENV["CHARSET"] || "utf8"
  7. 3 ON_ERROR_STOP_1 = "ON_ERROR_STOP=1"
  8. 3 SQL_COMMENT_BEGIN = "--"
  9. 3 delegate :connection, :establish_connection, :clear_active_connections!,
  10. to: ActiveRecord::Base
  11. 3 def self.using_database_configurations?
  12. 50 true
  13. end
  14. 3 def initialize(db_config)
  15. 29 @db_config = db_config
  16. 29 @configuration_hash = db_config.configuration_hash
  17. end
  18. 3 def create(master_established = false)
  19. 13 establish_master_connection unless master_established
  20. 12 connection.create_database(db_config.database, configuration_hash.merge(encoding: encoding))
  21. 11 establish_connection(db_config)
  22. end
  23. 3 def drop
  24. 8 establish_master_connection
  25. 8 connection.drop_database(db_config.database)
  26. end
  27. 3 def charset
  28. 1 connection.encoding
  29. end
  30. 3 def collation
  31. 1 connection.collation
  32. end
  33. 3 def purge
  34. 5 clear_active_connections!
  35. 5 drop
  36. 5 create true
  37. end
  38. 3 def structure_dump(filename, extra_flags)
  39. 8 set_psql_env
  40. 8 search_path = \
  41. case ActiveRecord::Base.dump_schemas
  42. when :schema_search_path
  43. 6 configuration_hash[:schema_search_path]
  44. when :all
  45. 1 nil
  46. when String
  47. 1 ActiveRecord::Base.dump_schemas
  48. end
  49. 8 args = ["--schema-only", "--no-privileges", "--no-owner", "--file", filename]
  50. 8 args.concat(Array(extra_flags)) if extra_flags
  51. 8 unless search_path.blank?
  52. 2 args += search_path.split(",").map do |part|
  53. 4 "--schema=#{part.strip}"
  54. end
  55. end
  56. 8 ignore_tables = ActiveRecord::SchemaDumper.ignore_tables
  57. 8 if ignore_tables.any?
  58. 3 args += ignore_tables.flat_map { |table| ["-T", table] }
  59. end
  60. 8 args << db_config.database
  61. 8 run_cmd("pg_dump", args, "dumping")
  62. 7 remove_sql_header_comments(filename)
  63. 14 File.open(filename, "a") { |f| f << "SET search_path TO #{connection.schema_search_path};\n\n" }
  64. end
  65. 3 def structure_load(filename, extra_flags)
  66. 3 set_psql_env
  67. 3 args = ["--set", ON_ERROR_STOP_1, "--quiet", "--no-psqlrc", "--file", filename]
  68. 3 args.concat(Array(extra_flags)) if extra_flags
  69. 3 args << db_config.database
  70. 3 run_cmd("psql", args, "loading")
  71. end
  72. 3 private
  73. 3 attr_reader :db_config, :configuration_hash
  74. 3 def encoding
  75. 12 configuration_hash[:encoding] || DEFAULT_ENCODING
  76. end
  77. 3 def establish_master_connection
  78. 16 establish_connection configuration_hash.merge(
  79. database: "postgres",
  80. schema_search_path: "public"
  81. )
  82. end
  83. 3 def set_psql_env
  84. 11 ENV["PGHOST"] = db_config.host if db_config.host
  85. 11 ENV["PGPORT"] = configuration_hash[:port].to_s if configuration_hash[:port]
  86. 11 ENV["PGPASSWORD"] = configuration_hash[:password].to_s if configuration_hash[:password]
  87. 11 ENV["PGUSER"] = configuration_hash[:username].to_s if configuration_hash[:username]
  88. end
  89. 3 def run_cmd(cmd, args, action)
  90. 11 fail run_cmd_error(cmd, args, action) unless Kernel.system(cmd, *args)
  91. end
  92. 3 def run_cmd_error(cmd, args, action)
  93. 1 msg = +"failed to execute:\n"
  94. 1 msg << "#{cmd} #{args.join(' ')}\n\n"
  95. 1 msg << "Please check the output above for any errors and make sure that `#{cmd}` is installed in your PATH and has proper permissions.\n\n"
  96. 1 msg
  97. end
  98. 3 def remove_sql_header_comments(filename)
  99. 7 removing_comments = true
  100. 7 tempfile = Tempfile.open("uncommented_structure.sql")
  101. 7 begin
  102. 7 File.foreach(filename) do |line|
  103. 5 unless removing_comments && (line.start_with?(SQL_COMMENT_BEGIN) || line.blank?)
  104. 2 tempfile << line
  105. 2 removing_comments = false
  106. end
  107. end
  108. ensure
  109. 7 tempfile.close
  110. end
  111. 7 FileUtils.cp(tempfile.path, filename)
  112. end
  113. end
  114. end
  115. end

lib/active_record/tasks/sqlite_database_tasks.rb

95.65% lines covered

46 relevant lines. 44 lines covered and 2 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 module ActiveRecord
  3. 3 module Tasks # :nodoc:
  4. 3 class SQLiteDatabaseTasks # :nodoc:
  5. 3 delegate :connection, :establish_connection, to: ActiveRecord::Base
  6. 3 def self.using_database_configurations?
  7. 55 true
  8. end
  9. 3 def initialize(db_config, root = ActiveRecord::Tasks::DatabaseTasks.root)
  10. 34 @db_config = db_config
  11. 34 @root = root
  12. end
  13. 3 def create
  14. 12 raise DatabaseAlreadyExists if File.exist?(db_config.database)
  15. 8 establish_connection(db_config)
  16. 6 connection
  17. end
  18. 3 def drop
  19. 10 require "pathname"
  20. 10 path = Pathname.new(db_config.database)
  21. 10 file = path.absolute? ? path.to_s : File.join(root, path)
  22. 10 FileUtils.rm(file)
  23. rescue Errno::ENOENT => error
  24. 4 raise NoDatabaseError.new(error.message)
  25. end
  26. 3 def purge
  27. drop
  28. rescue NoDatabaseError
  29. ensure
  30. create
  31. end
  32. 3 def charset
  33. 2 connection.encoding
  34. end
  35. 3 def structure_dump(filename, extra_flags)
  36. 6 args = []
  37. 6 args.concat(Array(extra_flags)) if extra_flags
  38. 6 args << db_config.database
  39. 6 ignore_tables = ActiveRecord::SchemaDumper.ignore_tables
  40. 6 if ignore_tables.any?
  41. 4 condition = ignore_tables.map { |table| connection.quote(table) }.join(", ")
  42. 2 args << "SELECT sql FROM sqlite_master WHERE tbl_name NOT IN (#{condition}) ORDER BY tbl_name, type DESC, name"
  43. else
  44. 4 args << ".schema"
  45. end
  46. 6 run_cmd("sqlite3", args, filename)
  47. end
  48. 3 def structure_load(filename, extra_flags)
  49. 2 flags = extra_flags.join(" ") if extra_flags
  50. 2 `sqlite3 #{flags} #{db_config.database} < "#{filename}"`
  51. end
  52. 3 private
  53. 3 attr_reader :db_config, :root
  54. 3 def run_cmd(cmd, args, out)
  55. 6 fail run_cmd_error(cmd, args) unless Kernel.system(cmd, *args, out: out)
  56. end
  57. 3 def run_cmd_error(cmd, args)
  58. 2 msg = +"failed to execute:\n"
  59. 2 msg << "#{cmd} #{args.join(' ')}\n\n"
  60. 2 msg << "Please check the output above for any errors and make sure that `#{cmd}` is installed in your PATH and has proper permissions.\n\n"
  61. 2 msg
  62. end
  63. end
  64. end
  65. end

lib/active_record/test_databases.rb

100.0% lines covered

12 relevant lines. 12 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 require "active_support/testing/parallelization"
  3. 3 module ActiveRecord
  4. 3 module TestDatabases # :nodoc:
  5. 3 ActiveSupport::Testing::Parallelization.after_fork_hook do |i|
  6. 4 create_and_load_schema(i, env_name: ActiveRecord::ConnectionHandling::DEFAULT_ENV.call)
  7. end
  8. 3 def self.create_and_load_schema(i, env_name:)
  9. 6 old, ENV["VERBOSE"] = ENV["VERBOSE"], "false"
  10. 6 ActiveRecord::Base.configurations.configs_for(env_name: env_name).each do |db_config|
  11. 8 db_config._database = "#{db_config.database}-#{i}"
  12. 8 ActiveRecord::Tasks::DatabaseTasks.reconstruct_from_schema(db_config, ActiveRecord::Base.schema_format, nil)
  13. end
  14. ensure
  15. 6 ActiveRecord::Base.establish_connection
  16. 6 ENV["VERBOSE"] = old
  17. end
  18. end
  19. end

lib/active_record/test_fixtures.rb

96.75% lines covered

123 relevant lines. 119 lines covered and 4 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 require "active_support/core_ext/enumerable"
  3. 3 module ActiveRecord
  4. 3 module TestFixtures
  5. 3 extend ActiveSupport::Concern
  6. 3 def before_setup # :nodoc:
  7. 19030 setup_fixtures
  8. 19030 super
  9. end
  10. 3 def after_teardown # :nodoc:
  11. 19030 super
  12. 19030 teardown_fixtures
  13. end
  14. 3 included do
  15. 13 class_attribute :fixture_path, instance_writer: false
  16. 13 class_attribute :fixture_table_names, default: []
  17. 13 class_attribute :fixture_class_names, default: {}
  18. 13 class_attribute :use_transactional_tests, default: true
  19. 13 class_attribute :use_instantiated_fixtures, default: false # true, false, or :no_instances
  20. 13 class_attribute :pre_loaded_fixtures, default: false
  21. 13 class_attribute :lock_threads, default: true
  22. end
  23. 3 module ClassMethods
  24. # Sets the model class for a fixture when the class name cannot be inferred from the fixture name.
  25. #
  26. # Examples:
  27. #
  28. # set_fixture_class some_fixture: SomeModel,
  29. # 'namespaced/fixture' => Another::Model
  30. #
  31. # The keys must be the fixture names, that coincide with the short paths to the fixture files.
  32. 3 def set_fixture_class(class_names = {})
  33. 24 self.fixture_class_names = fixture_class_names.merge(class_names.stringify_keys)
  34. end
  35. 3 def fixtures(*fixture_set_names)
  36. 479 if fixture_set_names.first == :all
  37. 14 raise StandardError, "No fixture path found. Please set `#{self}.fixture_path`." if fixture_path.blank?
  38. 11 fixture_set_names = Dir[::File.join(fixture_path, "{**,*}/*.{yml}")].uniq
  39. 29 fixture_set_names.reject! { |f| f.start_with?(file_fixture_path.to_s) } if defined?(file_fixture_path) && file_fixture_path
  40. 61 fixture_set_names.map! { |f| f[fixture_path.to_s.size..-5].delete_prefix("/") }
  41. else
  42. 465 fixture_set_names = fixture_set_names.flatten.map(&:to_s)
  43. end
  44. 476 self.fixture_table_names |= fixture_set_names
  45. 476 setup_fixture_accessors(fixture_set_names)
  46. end
  47. 3 def setup_fixture_accessors(fixture_set_names = nil)
  48. 479 fixture_set_names = Array(fixture_set_names || fixture_table_names)
  49. 479 methods = Module.new do
  50. 479 fixture_set_names.each do |fs_name|
  51. 2043 fs_name = fs_name.to_s
  52. 2043 accessor_name = fs_name.tr("/", "_").to_sym
  53. 2043 define_method(accessor_name) do |*fixture_names|
  54. 7382 force_reload = fixture_names.pop if fixture_names.last == true || fixture_names.last == :reload
  55. 7382 return_single_record = fixture_names.size == 1
  56. 7382 fixture_names = @loaded_fixtures[fs_name].fixtures.keys if fixture_names.empty?
  57. 7382 @fixture_cache[fs_name] ||= {}
  58. 7382 instances = fixture_names.map do |f_name|
  59. 7553 f_name = f_name.to_s if f_name.is_a?(Symbol)
  60. 7553 @fixture_cache[fs_name].delete(f_name) if force_reload
  61. 7553 if @loaded_fixtures[fs_name][f_name]
  62. 7526 @fixture_cache[fs_name][f_name] ||= @loaded_fixtures[fs_name][f_name].find
  63. else
  64. 27 raise StandardError, "No fixture named '#{f_name}' found for fixture set '#{fs_name}'"
  65. end
  66. end
  67. 7346 return_single_record ? instances.first : instances
  68. end
  69. 2043 private accessor_name
  70. end
  71. end
  72. 479 include methods
  73. end
  74. 3 def uses_transaction(*methods)
  75. 5 @uses_transaction = [] unless defined?(@uses_transaction)
  76. 5 @uses_transaction.concat methods.map(&:to_s)
  77. end
  78. 3 def uses_transaction?(method)
  79. 33195 @uses_transaction = [] unless defined?(@uses_transaction)
  80. 33195 @uses_transaction.include?(method.to_s)
  81. end
  82. end
  83. 3 def run_in_transaction?
  84. 38063 use_transactional_tests &&
  85. !self.class.uses_transaction?(name)
  86. end
  87. 3 def setup_fixtures(config = ActiveRecord::Base)
  88. 19030 if pre_loaded_fixtures && !use_transactional_tests
  89. raise RuntimeError, "pre_loaded_fixtures requires use_transactional_tests"
  90. end
  91. 19030 @fixture_cache = {}
  92. 19030 @fixture_connections = []
  93. 19030 @@already_loaded_fixtures ||= {}
  94. 19030 @connection_subscriber = nil
  95. # Load fixtures once and begin transaction.
  96. 19030 if run_in_transaction?
  97. 16591 if @@already_loaded_fixtures[self.class]
  98. 15644 @loaded_fixtures = @@already_loaded_fixtures[self.class]
  99. else
  100. 947 @loaded_fixtures = load_fixtures(config)
  101. 944 @@already_loaded_fixtures[self.class] = @loaded_fixtures
  102. end
  103. # Begin transactions for connections already established
  104. 16588 @fixture_connections = enlist_fixture_connections
  105. 16588 @fixture_connections.each do |connection|
  106. 109250 connection.begin_transaction joinable: false, _lazy: false
  107. 109250 connection.pool.lock_thread = true if lock_threads
  108. end
  109. # When connections are established in the future, begin a transaction too
  110. 16588 @connection_subscriber = ActiveSupport::Notifications.subscribe("!connection.active_record") do |_, _, _, _, payload|
  111. 78 spec_name = payload[:spec_name] if payload.key?(:spec_name)
  112. 78 setup_shared_connection_pool
  113. 78 if spec_name
  114. 78 begin
  115. 78 connection = ActiveRecord::Base.connection_handler.retrieve_connection(spec_name)
  116. rescue ConnectionNotEstablished
  117. connection = nil
  118. end
  119. 78 if connection && !@fixture_connections.include?(connection)
  120. 70 connection.begin_transaction joinable: false, _lazy: false
  121. 70 connection.pool.lock_thread = true if lock_threads
  122. 70 @fixture_connections << connection
  123. end
  124. end
  125. end
  126. # Load fixtures for every test.
  127. else
  128. 2439 ActiveRecord::FixtureSet.reset_cache
  129. 2439 @@already_loaded_fixtures[self.class] = nil
  130. 2439 @loaded_fixtures = load_fixtures(config)
  131. end
  132. # Instantiate fixtures for every test if requested.
  133. 19027 instantiate_fixtures if use_instantiated_fixtures
  134. end
  135. 3 def teardown_fixtures
  136. # Rollback changes if a transaction is active.
  137. 19033 if run_in_transaction?
  138. 16594 ActiveSupport::Notifications.unsubscribe(@connection_subscriber) if @connection_subscriber
  139. 16594 @fixture_connections.each do |connection|
  140. 109320 connection.rollback_transaction if connection.transaction_open?
  141. 109320 connection.pool.lock_thread = false
  142. end
  143. 16594 @fixture_connections.clear
  144. else
  145. 2439 ActiveRecord::FixtureSet.reset_cache
  146. end
  147. 19033 ActiveRecord::Base.clear_active_connections!
  148. end
  149. 3 def enlist_fixture_connections
  150. 16588 setup_shared_connection_pool
  151. 16588 ActiveRecord::Base.connection_handler.connection_pool_list.map(&:connection)
  152. end
  153. 3 private
  154. # Shares the writing connection pool with connections on
  155. # other handlers.
  156. #
  157. # In an application with a primary and replica the test fixtures
  158. # need to share a connection pool so that the reading connection
  159. # can see data in the open transaction on the writing connection.
  160. 3 def setup_shared_connection_pool
  161. 16666 writing_handler = ActiveRecord::Base.connection_handlers[ActiveRecord::Base.writing_role]
  162. 16666 ActiveRecord::Base.connection_handlers.values.each do |handler|
  163. 16668 if handler != writing_handler
  164. 6 handler.connection_pool_names.each do |name|
  165. 6 writing_pool_manager = writing_handler.send(:owner_to_pool_manager)[name]
  166. 6 return unless writing_pool_manager
  167. 6 writing_pool_config = writing_pool_manager.get_pool_config(:default)
  168. 6 pool_manager = handler.send(:owner_to_pool_manager)[name]
  169. 6 pool_manager.set_pool_config(:default, writing_pool_config)
  170. end
  171. end
  172. end
  173. end
  174. 3 def load_fixtures(config)
  175. 3383 ActiveRecord::FixtureSet.create_fixtures(fixture_path, fixture_table_names, fixture_class_names, config).index_by(&:name)
  176. end
  177. 3 def instantiate_fixtures
  178. 142 if pre_loaded_fixtures
  179. raise RuntimeError, "Load fixtures before instantiating them." if ActiveRecord::FixtureSet.all_loaded_fixtures.empty?
  180. ActiveRecord::FixtureSet.instantiate_all_loaded_fixtures(self, load_instances?)
  181. else
  182. 142 raise RuntimeError, "Load fixtures before instantiating them." if @loaded_fixtures.nil?
  183. 142 @loaded_fixtures.each_value do |fixture_set|
  184. 791 ActiveRecord::FixtureSet.instantiate_fixtures(self, fixture_set, load_instances?)
  185. end
  186. end
  187. end
  188. 3 def load_instances?
  189. 791 use_instantiated_fixtures != :no_instances
  190. end
  191. end
  192. end

lib/active_record/timestamp.rb

100.0% lines covered

68 relevant lines. 68 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 module ActiveRecord
  3. # = Active Record \Timestamp
  4. #
  5. # Active Record automatically timestamps create and update operations if the
  6. # table has fields named <tt>created_at/created_on</tt> or
  7. # <tt>updated_at/updated_on</tt>.
  8. #
  9. # Timestamping can be turned off by setting:
  10. #
  11. # config.active_record.record_timestamps = false
  12. #
  13. # Timestamps are in UTC by default but you can use the local timezone by setting:
  14. #
  15. # config.active_record.default_timezone = :local
  16. #
  17. # == Time Zone aware attributes
  18. #
  19. # Active Record keeps all the <tt>datetime</tt> and <tt>time</tt> columns
  20. # timezone aware. By default, these values are stored in the database as UTC
  21. # and converted back to the current <tt>Time.zone</tt> when pulled from the database.
  22. #
  23. # This feature can be turned off completely by setting:
  24. #
  25. # config.active_record.time_zone_aware_attributes = false
  26. #
  27. # You can also specify that only <tt>datetime</tt> columns should be time-zone
  28. # aware (while <tt>time</tt> should not) by setting:
  29. #
  30. # ActiveRecord::Base.time_zone_aware_types = [:datetime]
  31. #
  32. # You can also add database specific timezone aware types. For example, for PostgreSQL:
  33. #
  34. # ActiveRecord::Base.time_zone_aware_types += [:tsrange, :tstzrange]
  35. #
  36. # Finally, you can indicate specific attributes of a model for which time zone
  37. # conversion should not applied, for instance by setting:
  38. #
  39. # class Topic < ActiveRecord::Base
  40. # self.skip_time_zone_conversion_for_attributes = [:written_on]
  41. # end
  42. 3 module Timestamp
  43. 3 extend ActiveSupport::Concern
  44. 3 included do
  45. 3 class_attribute :record_timestamps, default: true
  46. end
  47. 3 def initialize_dup(other) # :nodoc:
  48. 111 super
  49. 111 clear_timestamp_attributes
  50. end
  51. 3 module ClassMethods # :nodoc:
  52. 3 def touch_attributes_with_time(*names, time: nil)
  53. 156 attribute_names = timestamp_attributes_for_update_in_model
  54. 156 attribute_names |= names.map(&:to_s)
  55. 156 attribute_names.index_with(time || current_time_from_proper_timezone)
  56. end
  57. 3 def timestamp_attributes_for_create_in_model
  58. 1925 @timestamp_attributes_for_create_in_model ||=
  59. 1922 (timestamp_attributes_for_create & column_names).freeze
  60. end
  61. 3 def timestamp_attributes_for_update_in_model
  62. 4823 @timestamp_attributes_for_update_in_model ||=
  63. 1991 (timestamp_attributes_for_update & column_names).freeze
  64. end
  65. 3 def all_timestamp_attributes_in_model
  66. 303700 @all_timestamp_attributes_in_model ||=
  67. 1922 (timestamp_attributes_for_create_in_model + timestamp_attributes_for_update_in_model).freeze
  68. end
  69. 3 def current_time_from_proper_timezone
  70. 14424 default_timezone == :utc ? Time.now.utc : Time.now
  71. end
  72. 3 private
  73. 3 def timestamp_attributes_for_create
  74. 5766 ["created_at", "created_on"].map! { |name| attribute_aliases[name] || name }
  75. end
  76. 3 def timestamp_attributes_for_update
  77. 5973 ["updated_at", "updated_on"].map! { |name| attribute_aliases[name] || name }
  78. end
  79. 3 def reload_schema_from_cache
  80. 4565 @timestamp_attributes_for_create_in_model = nil
  81. 4565 @timestamp_attributes_for_update_in_model = nil
  82. 4565 @all_timestamp_attributes_in_model = nil
  83. 4565 super
  84. end
  85. end
  86. 3 private
  87. 3 def _create_record
  88. 12451 if record_timestamps
  89. 12013 current_time = current_time_from_proper_timezone
  90. 12013 all_timestamp_attributes_in_model.each do |column|
  91. 9745 _write_attribute(column, current_time) unless _read_attribute(column)
  92. end
  93. end
  94. 12447 super
  95. end
  96. 3 def _update_record
  97. 3419 if @_touch_record && should_record_timestamps?
  98. 1724 current_time = current_time_from_proper_timezone
  99. 1724 timestamp_attributes_for_update_in_model.each do |column|
  100. 1160 next if will_save_change_to_attribute?(column)
  101. 1143 _write_attribute(column, current_time)
  102. end
  103. end
  104. 3419 super
  105. end
  106. 3 def create_or_update(touch: true, **)
  107. 16016 @_touch_record = touch
  108. 16016 super
  109. end
  110. 3 def should_record_timestamps?
  111. 3416 record_timestamps && (!partial_writes? || has_changes_to_save?)
  112. end
  113. 3 def timestamp_attributes_for_create_in_model
  114. 3 self.class.timestamp_attributes_for_create_in_model
  115. end
  116. 3 def timestamp_attributes_for_update_in_model
  117. 2684 self.class.timestamp_attributes_for_update_in_model
  118. end
  119. 3 def all_timestamp_attributes_in_model
  120. 12130 self.class.all_timestamp_attributes_in_model
  121. end
  122. 3 def current_time_from_proper_timezone
  123. 14286 self.class.current_time_from_proper_timezone
  124. end
  125. 3 def max_updated_column_timestamp
  126. timestamp_attributes_for_update_in_model
  127. 87 .map { |attr| self[attr]&.to_time }
  128. .compact
  129. 51 .max
  130. end
  131. # Clear attributes and changed_attributes
  132. 3 def clear_timestamp_attributes
  133. 114 all_timestamp_attributes_in_model.each do |attribute_name|
  134. 174 self[attribute_name] = nil
  135. 174 clear_attribute_change(attribute_name)
  136. end
  137. end
  138. end
  139. end

lib/active_record/touch_later.rb

100.0% lines covered

36 relevant lines. 36 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 module ActiveRecord
  3. # = Active Record Touch Later
  4. 3 module TouchLater # :nodoc:
  5. 3 def before_committed!
  6. 16238 touch_deferred_attributes if has_defer_touch_attrs? && persisted?
  7. 16238 super
  8. end
  9. 3 def touch_later(*names) # :nodoc:
  10. 423 _raise_record_not_touched_error unless persisted?
  11. 420 @_defer_touch_attrs ||= timestamp_attributes_for_update_in_model
  12. @_defer_touch_attrs |= names.map! do |name|
  13. 15 name = name.to_s
  14. 15 self.class.attribute_aliases[name] || name
  15. 420 end unless names.empty?
  16. 420 @_touch_time = current_time_from_proper_timezone
  17. 420 surreptitiously_touch @_defer_touch_attrs
  18. 420 add_to_transaction
  19. 420 @_new_record_before_last_commit ||= false
  20. # touch the parents as we are not calling the after_save callbacks
  21. 420 self.class.reflect_on_all_associations(:belongs_to).each do |r|
  22. 105 if touch = r.options[:touch]
  23. 48 ActiveRecord::Associations::Builder::BelongsTo.touch_record(self, changes_to_save, r.foreign_key, r.name, touch, :touch_later)
  24. end
  25. end
  26. end
  27. 3 def touch(*names, time: nil) # :nodoc:
  28. 537 if has_defer_touch_attrs?
  29. 348 names |= @_defer_touch_attrs
  30. 348 super(*names, time: time)
  31. 348 @_defer_touch_attrs, @_touch_time = nil, nil
  32. else
  33. 189 super
  34. end
  35. end
  36. 3 private
  37. 3 def surreptitiously_touch(attr_names)
  38. 420 attr_names.each do |attr_name|
  39. 435 _write_attribute(attr_name, @_touch_time)
  40. 435 clear_attribute_change(attr_name)
  41. end
  42. end
  43. 3 def touch_deferred_attributes
  44. 342 @_skip_dirty_tracking = true
  45. 342 touch(time: @_touch_time)
  46. end
  47. 3 def has_defer_touch_attrs?
  48. 16775 defined?(@_defer_touch_attrs) && @_defer_touch_attrs.present?
  49. end
  50. 3 def belongs_to_touch_method
  51. 588 :touch_later
  52. end
  53. end
  54. end

lib/active_record/transactions.rb

100.0% lines covered

117 relevant lines. 117 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 module ActiveRecord
  3. # See ActiveRecord::Transactions::ClassMethods for documentation.
  4. 3 module Transactions
  5. 3 extend ActiveSupport::Concern
  6. #:nodoc:
  7. 3 ACTIONS = [:create, :destroy, :update]
  8. 3 included do
  9. 3 define_callbacks :commit, :rollback,
  10. :before_commit,
  11. scope: [:kind, :name]
  12. end
  13. # = Active Record Transactions
  14. #
  15. # \Transactions are protective blocks where SQL statements are only permanent
  16. # if they can all succeed as one atomic action. The classic example is a
  17. # transfer between two accounts where you can only have a deposit if the
  18. # withdrawal succeeded and vice versa. \Transactions enforce the integrity of
  19. # the database and guard the data against program errors or database
  20. # break-downs. So basically you should use transaction blocks whenever you
  21. # have a number of statements that must be executed together or not at all.
  22. #
  23. # For example:
  24. #
  25. # ActiveRecord::Base.transaction do
  26. # david.withdrawal(100)
  27. # mary.deposit(100)
  28. # end
  29. #
  30. # This example will only take money from David and give it to Mary if neither
  31. # +withdrawal+ nor +deposit+ raise an exception. Exceptions will force a
  32. # ROLLBACK that returns the database to the state before the transaction
  33. # began. Be aware, though, that the objects will _not_ have their instance
  34. # data returned to their pre-transactional state.
  35. #
  36. # == Different Active Record classes in a single transaction
  37. #
  38. # Though the #transaction class method is called on some Active Record class,
  39. # the objects within the transaction block need not all be instances of
  40. # that class. This is because transactions are per-database connection, not
  41. # per-model.
  42. #
  43. # In this example a +balance+ record is transactionally saved even
  44. # though #transaction is called on the +Account+ class:
  45. #
  46. # Account.transaction do
  47. # balance.save!
  48. # account.save!
  49. # end
  50. #
  51. # The #transaction method is also available as a model instance method.
  52. # For example, you can also do this:
  53. #
  54. # balance.transaction do
  55. # balance.save!
  56. # account.save!
  57. # end
  58. #
  59. # == Transactions are not distributed across database connections
  60. #
  61. # A transaction acts on a single database connection. If you have
  62. # multiple class-specific databases, the transaction will not protect
  63. # interaction among them. One workaround is to begin a transaction
  64. # on each class whose models you alter:
  65. #
  66. # Student.transaction do
  67. # Course.transaction do
  68. # course.enroll(student)
  69. # student.units += course.units
  70. # end
  71. # end
  72. #
  73. # This is a poor solution, but fully distributed transactions are beyond
  74. # the scope of Active Record.
  75. #
  76. # == +save+ and +destroy+ are automatically wrapped in a transaction
  77. #
  78. # Both {#save}[rdoc-ref:Persistence#save] and
  79. # {#destroy}[rdoc-ref:Persistence#destroy] come wrapped in a transaction that ensures
  80. # that whatever you do in validations or callbacks will happen under its
  81. # protected cover. So you can use validations to check for values that
  82. # the transaction depends on or you can raise exceptions in the callbacks
  83. # to rollback, including <tt>after_*</tt> callbacks.
  84. #
  85. # As a consequence changes to the database are not seen outside your connection
  86. # until the operation is complete. For example, if you try to update the index
  87. # of a search engine in +after_save+ the indexer won't see the updated record.
  88. # The #after_commit callback is the only one that is triggered once the update
  89. # is committed. See below.
  90. #
  91. # == Exception handling and rolling back
  92. #
  93. # Also have in mind that exceptions thrown within a transaction block will
  94. # be propagated (after triggering the ROLLBACK), so you should be ready to
  95. # catch those in your application code.
  96. #
  97. # One exception is the ActiveRecord::Rollback exception, which will trigger
  98. # a ROLLBACK when raised, but not be re-raised by the transaction block.
  99. #
  100. # *Warning*: one should not catch ActiveRecord::StatementInvalid exceptions
  101. # inside a transaction block. ActiveRecord::StatementInvalid exceptions indicate that an
  102. # error occurred at the database level, for example when a unique constraint
  103. # is violated. On some database systems, such as PostgreSQL, database errors
  104. # inside a transaction cause the entire transaction to become unusable
  105. # until it's restarted from the beginning. Here is an example which
  106. # demonstrates the problem:
  107. #
  108. # # Suppose that we have a Number model with a unique column called 'i'.
  109. # Number.transaction do
  110. # Number.create(i: 0)
  111. # begin
  112. # # This will raise a unique constraint error...
  113. # Number.create(i: 0)
  114. # rescue ActiveRecord::StatementInvalid
  115. # # ...which we ignore.
  116. # end
  117. #
  118. # # On PostgreSQL, the transaction is now unusable. The following
  119. # # statement will cause a PostgreSQL error, even though the unique
  120. # # constraint is no longer violated:
  121. # Number.create(i: 1)
  122. # # => "PG::Error: ERROR: current transaction is aborted, commands
  123. # # ignored until end of transaction block"
  124. # end
  125. #
  126. # One should restart the entire transaction if an
  127. # ActiveRecord::StatementInvalid occurred.
  128. #
  129. # == Nested transactions
  130. #
  131. # #transaction calls can be nested. By default, this makes all database
  132. # statements in the nested transaction block become part of the parent
  133. # transaction. For example, the following behavior may be surprising:
  134. #
  135. # User.transaction do
  136. # User.create(username: 'Kotori')
  137. # User.transaction do
  138. # User.create(username: 'Nemu')
  139. # raise ActiveRecord::Rollback
  140. # end
  141. # end
  142. #
  143. # creates both "Kotori" and "Nemu". Reason is the ActiveRecord::Rollback
  144. # exception in the nested block does not issue a ROLLBACK. Since these exceptions
  145. # are captured in transaction blocks, the parent block does not see it and the
  146. # real transaction is committed.
  147. #
  148. # In order to get a ROLLBACK for the nested transaction you may ask for a real
  149. # sub-transaction by passing <tt>requires_new: true</tt>. If anything goes wrong,
  150. # the database rolls back to the beginning of the sub-transaction without rolling
  151. # back the parent transaction. If we add it to the previous example:
  152. #
  153. # User.transaction do
  154. # User.create(username: 'Kotori')
  155. # User.transaction(requires_new: true) do
  156. # User.create(username: 'Nemu')
  157. # raise ActiveRecord::Rollback
  158. # end
  159. # end
  160. #
  161. # only "Kotori" is created.
  162. #
  163. # Most databases don't support true nested transactions. At the time of
  164. # writing, the only database that we're aware of that supports true nested
  165. # transactions, is MS-SQL. Because of this, Active Record emulates nested
  166. # transactions by using savepoints. See
  167. # https://dev.mysql.com/doc/refman/en/savepoint.html
  168. # for more information about savepoints.
  169. #
  170. # === \Callbacks
  171. #
  172. # There are two types of callbacks associated with committing and rolling back transactions:
  173. # #after_commit and #after_rollback.
  174. #
  175. # #after_commit callbacks are called on every record saved or destroyed within a
  176. # transaction immediately after the transaction is committed. #after_rollback callbacks
  177. # are called on every record saved or destroyed within a transaction immediately after the
  178. # transaction or savepoint is rolled back.
  179. #
  180. # These callbacks are useful for interacting with other systems since you will be guaranteed
  181. # that the callback is only executed when the database is in a permanent state. For example,
  182. # #after_commit is a good spot to put in a hook to clearing a cache since clearing it from
  183. # within a transaction could trigger the cache to be regenerated before the database is updated.
  184. #
  185. # === Caveats
  186. #
  187. # If you're on MySQL, then do not use Data Definition Language (DDL) operations in nested
  188. # transactions blocks that are emulated with savepoints. That is, do not execute statements
  189. # like 'CREATE TABLE' inside such blocks. This is because MySQL automatically
  190. # releases all savepoints upon executing a DDL operation. When +transaction+
  191. # is finished and tries to release the savepoint it created earlier, a
  192. # database error will occur because the savepoint has already been
  193. # automatically released. The following example demonstrates the problem:
  194. #
  195. # Model.connection.transaction do # BEGIN
  196. # Model.connection.transaction(requires_new: true) do # CREATE SAVEPOINT active_record_1
  197. # Model.connection.create_table(...) # active_record_1 now automatically released
  198. # end # RELEASE SAVEPOINT active_record_1
  199. # # ^^^^ BOOM! database error!
  200. # end
  201. #
  202. # Note that "TRUNCATE" is also a MySQL DDL statement!
  203. 3 module ClassMethods
  204. # See the ConnectionAdapters::DatabaseStatements#transaction API docs.
  205. 3 def transaction(**options, &block)
  206. 3739 connection.transaction(**options, &block)
  207. end
  208. 3 def before_commit(*args, &block) # :nodoc:
  209. 9 set_options_for_callbacks!(args)
  210. 9 set_callback(:before_commit, :before, *args, &block)
  211. end
  212. # This callback is called after a record has been created, updated, or destroyed.
  213. #
  214. # You can specify that the callback should only be fired by a certain action with
  215. # the +:on+ option:
  216. #
  217. # after_commit :do_foo, on: :create
  218. # after_commit :do_bar, on: :update
  219. # after_commit :do_baz, on: :destroy
  220. #
  221. # after_commit :do_foo_bar, on: [:create, :update]
  222. # after_commit :do_bar_baz, on: [:update, :destroy]
  223. #
  224. 3 def after_commit(*args, &block)
  225. 49 set_options_for_callbacks!(args)
  226. 43 set_callback(:commit, :after, *args, &block)
  227. end
  228. # Shortcut for <tt>after_commit :hook, on: [ :create, :update ]</tt>.
  229. 3 def after_save_commit(*args, &block)
  230. 3 set_options_for_callbacks!(args, on: [ :create, :update ])
  231. 3 set_callback(:commit, :after, *args, &block)
  232. end
  233. # Shortcut for <tt>after_commit :hook, on: :create</tt>.
  234. 3 def after_create_commit(*args, &block)
  235. 12 set_options_for_callbacks!(args, on: :create)
  236. 12 set_callback(:commit, :after, *args, &block)
  237. end
  238. # Shortcut for <tt>after_commit :hook, on: :update</tt>.
  239. 3 def after_update_commit(*args, &block)
  240. 6 set_options_for_callbacks!(args, on: :update)
  241. 6 set_callback(:commit, :after, *args, &block)
  242. end
  243. # Shortcut for <tt>after_commit :hook, on: :destroy</tt>.
  244. 3 def after_destroy_commit(*args, &block)
  245. 6 set_options_for_callbacks!(args, on: :destroy)
  246. 6 set_callback(:commit, :after, *args, &block)
  247. end
  248. # This callback is called after a create, update, or destroy are rolled back.
  249. #
  250. # Please check the documentation of #after_commit for options.
  251. 3 def after_rollback(*args, &block)
  252. 36 set_options_for_callbacks!(args)
  253. 30 set_callback(:rollback, :after, *args, &block)
  254. end
  255. 3 private
  256. 3 def set_options_for_callbacks!(args, enforced_options = {})
  257. 121 options = args.extract_options!.merge!(enforced_options)
  258. 121 args << options
  259. 121 if options[:on]
  260. 75 fire_on = Array(options[:on])
  261. 75 assert_valid_transaction_action(fire_on)
  262. 63 options[:if] = Array(options[:if])
  263. 705 options[:if].unshift(-> { transaction_include_any_action?(fire_on) })
  264. end
  265. end
  266. 3 def assert_valid_transaction_action(actions)
  267. 75 if (actions - ACTIONS).any?
  268. 12 raise ArgumentError, ":on conditions for after_commit and after_rollback callbacks have to be one of #{ACTIONS}"
  269. end
  270. end
  271. end
  272. # See ActiveRecord::Transactions::ClassMethods for detailed documentation.
  273. 3 def transaction(**options, &block)
  274. 46 self.class.transaction(**options, &block)
  275. end
  276. 3 def destroy #:nodoc:
  277. 2148 with_transaction_returning_status { super }
  278. end
  279. 3 def save(**) #:nodoc:
  280. 16348 with_transaction_returning_status { super }
  281. end
  282. 3 def save!(**) #:nodoc:
  283. 16494 with_transaction_returning_status { super }
  284. end
  285. 3 def touch(*, **) #:nodoc:
  286. 1074 with_transaction_returning_status { super }
  287. end
  288. 3 def before_committed! # :nodoc:
  289. 16238 _run_before_commit_callbacks
  290. end
  291. # Call the #after_commit callbacks.
  292. #
  293. # Ensure that it is not called if the object was never persisted (failed create),
  294. # but call it after the commit of a destroyed object.
  295. 3 def committed!(should_run_callbacks: true) #:nodoc:
  296. 16269 force_clear_transaction_record_state
  297. 16269 if should_run_callbacks
  298. 16143 @_committed_already_called = true
  299. 16143 _run_commit_callbacks
  300. end
  301. ensure
  302. 16269 @_committed_already_called = @_trigger_update_callback = @_trigger_destroy_callback = false
  303. end
  304. # Call the #after_rollback callbacks. The +force_restore_state+ argument indicates if the record
  305. # state should be rolled back to the beginning or just to the last savepoint.
  306. 3 def rolledback!(force_restore_state: false, should_run_callbacks: true) #:nodoc:
  307. 1185 if should_run_callbacks
  308. 438 _run_rollback_callbacks
  309. end
  310. ensure
  311. 1185 restore_transaction_record_state(force_restore_state)
  312. 1178 clear_transaction_record_state
  313. 1178 @_trigger_update_callback = @_trigger_destroy_callback = false if force_restore_state
  314. end
  315. # Executes +method+ within a transaction and captures its return value as a
  316. # status flag. If the status is true the transaction is committed, otherwise
  317. # a ROLLBACK is issued. In any case the status flag is returned.
  318. #
  319. # This method is available within the context of an ActiveRecord::Base
  320. # instance.
  321. 3 def with_transaction_returning_status
  322. 18822 status = nil
  323. 18822 connection = self.class.connection
  324. 18822 ensure_finalize = !connection.transaction_open?
  325. 18822 connection.transaction do
  326. 18822 add_to_transaction(ensure_finalize || has_transactional_callbacks?)
  327. 18822 remember_transaction_record_state
  328. 18822 status = yield
  329. 18380 raise ActiveRecord::Rollback unless status
  330. end
  331. 18402 status
  332. end
  333. 3 def trigger_transactional_callbacks? # :nodoc:
  334. 17442 (@_new_record_before_last_commit || _trigger_update_callback) && persisted? ||
  335. _trigger_destroy_callback && destroyed?
  336. end
  337. 3 private
  338. 3 attr_reader :_committed_already_called, :_trigger_update_callback, :_trigger_destroy_callback
  339. # Save the new record state and id of a record so it can be restored later if a transaction fails.
  340. 3 def remember_transaction_record_state
  341. 18822 @_start_transaction_state ||= {
  342. id: id,
  343. new_record: @new_record,
  344. previously_new_record: @previously_new_record,
  345. destroyed: @destroyed,
  346. attributes: @attributes,
  347. frozen?: frozen?,
  348. level: 0
  349. }
  350. 18822 @_start_transaction_state[:level] += 1
  351. 18822 if _committed_already_called
  352. 9 @_new_record_before_last_commit = false
  353. else
  354. 18813 @_new_record_before_last_commit = @_start_transaction_state[:new_record]
  355. end
  356. end
  357. # Clear the new record state and id of a record.
  358. 3 def clear_transaction_record_state
  359. 1178 return unless @_start_transaction_state
  360. 1172 @_start_transaction_state[:level] -= 1
  361. 1172 force_clear_transaction_record_state if @_start_transaction_state[:level] < 1
  362. end
  363. # Force to clear the transaction record state.
  364. 3 def force_clear_transaction_record_state
  365. 17318 @_start_transaction_state = nil
  366. end
  367. # Restore the new record state and id of a record that was previously saved by a call to save_record_state.
  368. 3 def restore_transaction_record_state(force_restore_state = false)
  369. 1185 if restore_state = @_start_transaction_state
  370. 1179 if force_restore_state || restore_state[:level] <= 1
  371. 1119 @new_record = restore_state[:new_record]
  372. 1119 @previously_new_record = restore_state[:previously_new_record]
  373. 1119 @destroyed = restore_state[:destroyed]
  374. 1119 @attributes = restore_state[:attributes].map do |attr|
  375. 12915 value = @attributes.fetch_value(attr.name)
  376. 12908 attr = attr.with_value_from_user(value) if attr.value != value
  377. 12908 attr
  378. end
  379. 1112 @mutations_from_database = nil
  380. 1112 @mutations_before_last_save = nil
  381. 1112 if @attributes.fetch_value(@primary_key) != restore_state[:id]
  382. 170 @attributes.write_from_user(@primary_key, restore_state[:id])
  383. end
  384. 1112 freeze if restore_state[:frozen?]
  385. end
  386. end
  387. end
  388. # Determine if a transaction included an action for :create, :update, or :destroy. Used in filtering callbacks.
  389. 3 def transaction_include_any_action?(actions)
  390. 642 actions.any? do |action|
  391. 720 case action
  392. when :create
  393. 351 persisted? && @_new_record_before_last_commit
  394. when :update
  395. 210 !(@_new_record_before_last_commit || destroyed?) && _trigger_update_callback
  396. when :destroy
  397. 159 _trigger_destroy_callback
  398. end
  399. end
  400. end
  401. # Add the record to the current transaction so that the #after_rollback and #after_commit
  402. # callbacks can be called.
  403. 3 def add_to_transaction(ensure_finalize = true)
  404. 19246 self.class.connection.add_transaction_record(self, ensure_finalize)
  405. end
  406. 3 def has_transactional_callbacks?
  407. 17372 !_rollback_callbacks.empty? || !_commit_callbacks.empty? || !_before_commit_callbacks.empty?
  408. end
  409. end
  410. end

lib/active_record/translation.rb

100.0% lines covered

12 relevant lines. 12 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 module ActiveRecord
  3. 3 module Translation
  4. 3 include ActiveModel::Translation
  5. # Set the lookup ancestors for ActiveModel.
  6. 3 def lookup_ancestors #:nodoc:
  7. 1312 klass = self
  8. 1312 classes = [klass]
  9. 1312 return classes if klass == ActiveRecord::Base
  10. 1309 while !klass.base_class?
  11. 493 classes << klass = klass.superclass
  12. end
  13. 1309 classes
  14. end
  15. # Set the i18n scope to overwrite ActiveModel.
  16. 3 def i18n_scope #:nodoc:
  17. 1462 :activerecord
  18. end
  19. end
  20. end

lib/active_record/type.rb

100.0% lines covered

52 relevant lines. 52 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 require "active_model/type"
  3. 3 require "active_record/type/internal/timezone"
  4. 3 require "active_record/type/date"
  5. 3 require "active_record/type/date_time"
  6. 3 require "active_record/type/decimal_without_scale"
  7. 3 require "active_record/type/json"
  8. 3 require "active_record/type/time"
  9. 3 require "active_record/type/text"
  10. 3 require "active_record/type/unsigned_integer"
  11. 3 require "active_record/type/serialized"
  12. 3 require "active_record/type/adapter_specific_registry"
  13. 3 require "active_record/type/type_map"
  14. 3 require "active_record/type/hash_lookup_type_map"
  15. 3 module ActiveRecord
  16. 3 module Type
  17. 3 @registry = AdapterSpecificRegistry.new
  18. 3 class << self
  19. 3 attr_accessor :registry # :nodoc:
  20. 3 delegate :add_modifier, to: :registry
  21. # Add a new type to the registry, allowing it to be referenced as a
  22. # symbol by {ActiveRecord::Base.attribute}[rdoc-ref:Attributes::ClassMethods#attribute].
  23. # If your type is only meant to be used with a specific database adapter, you can
  24. # do so by passing <tt>adapter: :postgresql</tt>. If your type has the same
  25. # name as a native type for the current adapter, an exception will be
  26. # raised unless you specify an +:override+ option. <tt>override: true</tt> will
  27. # cause your type to be used instead of the native type. <tt>override:
  28. # false</tt> will cause the native type to be used over yours if one exists.
  29. 3 def register(type_name, klass = nil, **options, &block)
  30. 97 registry.register(type_name, klass, **options, &block)
  31. end
  32. 3 def lookup(*args, adapter: current_adapter_name, **kwargs) # :nodoc:
  33. 632 registry.lookup(*args, adapter: adapter, **kwargs)
  34. end
  35. 3 def default_value # :nodoc:
  36. 28781 @default_value ||= Value.new
  37. end
  38. 3 def adapter_name_from(model) # :nodoc:
  39. # TODO: this shouldn't depend on a connection to the database
  40. 626 model.connection.adapter_name.downcase.to_sym
  41. end
  42. 3 private
  43. 3 def current_adapter_name
  44. 18 adapter_name_from(ActiveRecord::Base)
  45. end
  46. end
  47. 3 BigInteger = ActiveModel::Type::BigInteger
  48. 3 Binary = ActiveModel::Type::Binary
  49. 3 Boolean = ActiveModel::Type::Boolean
  50. 3 Decimal = ActiveModel::Type::Decimal
  51. 3 Float = ActiveModel::Type::Float
  52. 3 Integer = ActiveModel::Type::Integer
  53. 3 ImmutableString = ActiveModel::Type::ImmutableString
  54. 3 String = ActiveModel::Type::String
  55. 3 Value = ActiveModel::Type::Value
  56. 3 register(:big_integer, Type::BigInteger, override: false)
  57. 3 register(:binary, Type::Binary, override: false)
  58. 3 register(:boolean, Type::Boolean, override: false)
  59. 3 register(:date, Type::Date, override: false)
  60. 3 register(:datetime, Type::DateTime, override: false)
  61. 3 register(:decimal, Type::Decimal, override: false)
  62. 3 register(:float, Type::Float, override: false)
  63. 3 register(:integer, Type::Integer, override: false)
  64. 3 register(:immutable_string, Type::ImmutableString, override: false)
  65. 3 register(:json, Type::Json, override: false)
  66. 3 register(:string, Type::String, override: false)
  67. 3 register(:text, Type::Text, override: false)
  68. 3 register(:time, Type::Time, override: false)
  69. end
  70. end

lib/active_record/type/adapter_specific_registry.rb

100.0% lines covered

67 relevant lines. 67 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 require "active_model/type/registry"
  3. 3 module ActiveRecord
  4. # :stopdoc:
  5. 3 module Type
  6. 3 class AdapterSpecificRegistry < ActiveModel::Type::Registry
  7. 3 def add_modifier(options, klass, **args)
  8. 16 registrations << DecorationRegistration.new(options, klass, **args)
  9. end
  10. 3 private
  11. 3 def registration_klass
  12. 148 Registration
  13. end
  14. 3 def find_registration(symbol, *args, **kwargs)
  15. registrations
  16. 16406 .select { |registration| registration.matches?(symbol, *args, **kwargs) }
  17. 739 .max
  18. end
  19. end
  20. 3 class Registration
  21. 3 def initialize(name, block, adapter: nil, override: nil)
  22. 148 @name = name
  23. 148 @block = block
  24. 148 @adapter = adapter
  25. 148 @override = override
  26. end
  27. 3 def call(_registry, *args, adapter: nil, **kwargs)
  28. 707 if kwargs.any? # https://bugs.ruby-lang.org/issues/10856
  29. 83 block.call(*args, **kwargs)
  30. else
  31. 624 block.call(*args)
  32. end
  33. end
  34. 3 def matches?(type_name, *args, **kwargs)
  35. 15554 type_name == name && matches_adapter?(**kwargs)
  36. end
  37. 3 def <=>(other)
  38. 74 if conflicts_with?(other)
  39. 3 raise TypeConflictError.new("Type #{name} was registered for all
  40. adapters, but shadows a native type with
  41. the same name for #{other.adapter}".squish)
  42. end
  43. 71 priority <=> other.priority
  44. end
  45. 3 protected
  46. 3 attr_reader :name, :block, :adapter, :override
  47. 3 def priority
  48. 290 result = 0
  49. 290 if adapter
  50. 105 result |= 1
  51. end
  52. 290 if override
  53. 6 result |= 2
  54. end
  55. 290 result
  56. end
  57. 3 def priority_except_adapter
  58. 148 priority & 0b111111100
  59. end
  60. 3 private
  61. 3 def matches_adapter?(adapter: nil, **)
  62. 1744 (self.adapter.nil? || adapter == self.adapter)
  63. end
  64. 3 def conflicts_with?(other)
  65. 74 same_priority_except_adapter?(other) &&
  66. has_adapter_conflict?(other)
  67. end
  68. 3 def same_priority_except_adapter?(other)
  69. 74 priority_except_adapter == other.priority_except_adapter
  70. end
  71. 3 def has_adapter_conflict?(other)
  72. 48 (override.nil? && other.adapter) ||
  73. 48 (adapter && other.override.nil?)
  74. end
  75. end
  76. 3 class DecorationRegistration < Registration
  77. 3 def initialize(options, klass, adapter: nil)
  78. 16 @options = options
  79. 16 @klass = klass
  80. 16 @adapter = adapter
  81. end
  82. 3 def call(registry, *args, **kwargs)
  83. 26 subtype = registry.lookup(*args, **kwargs.except(*options.keys))
  84. 26 klass.new(subtype)
  85. end
  86. 3 def matches?(*args, **kwargs)
  87. 852 matches_adapter?(**kwargs) && matches_options?(**kwargs)
  88. end
  89. 3 def priority
  90. 64 super | 4
  91. end
  92. 3 private
  93. 3 attr_reader :options, :klass
  94. 3 def matches_options?(**kwargs)
  95. 515 options.all? do |key, value|
  96. 515 kwargs[key] == value
  97. end
  98. end
  99. end
  100. end
  101. 3 class TypeConflictError < StandardError
  102. end
  103. # :startdoc:
  104. end

lib/active_record/type/date.rb

100.0% lines covered

4 relevant lines. 4 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 module ActiveRecord
  3. 3 module Type
  4. 3 class Date < ActiveModel::Type::Date
  5. 3 include Internal::Timezone
  6. end
  7. end
  8. end

lib/active_record/type/date_time.rb

100.0% lines covered

4 relevant lines. 4 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 module ActiveRecord
  3. 3 module Type
  4. 3 class DateTime < ActiveModel::Type::DateTime
  5. 3 include Internal::Timezone
  6. end
  7. end
  8. end

lib/active_record/type/decimal_without_scale.rb

85.71% lines covered

7 relevant lines. 6 lines covered and 1 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 module ActiveRecord
  3. 3 module Type
  4. 3 class DecimalWithoutScale < ActiveModel::Type::BigInteger # :nodoc:
  5. 3 def type
  6. 263 :decimal
  7. end
  8. 3 def type_cast_for_schema(value)
  9. value.to_s.inspect
  10. end
  11. end
  12. end
  13. end

lib/active_record/type/hash_lookup_type_map.rb

100.0% lines covered

12 relevant lines. 12 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 module ActiveRecord
  3. 3 module Type
  4. 3 class HashLookupTypeMap < TypeMap # :nodoc:
  5. 3 def alias_type(type, alias_type)
  6. 37160 register_type(type) { |_, *args| lookup(alias_type, *args) }
  7. end
  8. 3 def key?(key)
  9. 757558 @mapping.key?(key)
  10. end
  11. 3 def keys
  12. 632 @mapping.keys
  13. end
  14. 3 private
  15. 3 def perform_fetch(type, *args, &block)
  16. 19053 @mapping.fetch(type, block).call(type, *args)
  17. end
  18. end
  19. end
  20. end

lib/active_record/type/internal/timezone.rb

100.0% lines covered

8 relevant lines. 8 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 module ActiveRecord
  3. 3 module Type
  4. 3 module Internal
  5. 3 module Timezone
  6. 3 def is_utc?
  7. 76834 ActiveRecord::Base.default_timezone == :utc
  8. end
  9. 3 def default_timezone
  10. 147 ActiveRecord::Base.default_timezone
  11. end
  12. end
  13. end
  14. end
  15. end

lib/active_record/type/json.rb

100.0% lines covered

15 relevant lines. 15 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 module ActiveRecord
  3. 3 module Type
  4. 3 class Json < ActiveModel::Type::Value
  5. 3 include ActiveModel::Type::Helpers::Mutable
  6. 3 def type
  7. 194 :json
  8. end
  9. 3 def deserialize(value)
  10. 1079 return value unless value.is_a?(::String)
  11. 639 ActiveSupport::JSON.decode(value) rescue nil
  12. end
  13. 3 def serialize(value)
  14. 1667 ActiveSupport::JSON.encode(value) unless value.nil?
  15. end
  16. 3 def changed_in_place?(raw_old_value, new_value)
  17. 81 deserialize(raw_old_value) != new_value
  18. end
  19. 3 def accessor
  20. 77 ActiveRecord::Store::StringKeyedHashAccessor
  21. end
  22. end
  23. end
  24. end

lib/active_record/type/serialized.rb

97.3% lines covered

37 relevant lines. 36 lines covered and 1 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 module ActiveRecord
  3. 3 module Type
  4. 3 class Serialized < DelegateClass(ActiveModel::Type::Value) # :nodoc:
  5. 3 undef to_yaml if method_defined?(:to_yaml)
  6. 3 include ActiveModel::Type::Helpers::Mutable
  7. 3 attr_reader :subtype, :coder
  8. 3 def initialize(subtype, coder)
  9. 491 @subtype = subtype
  10. 491 @coder = coder
  11. 491 super(subtype)
  12. end
  13. 3 def deserialize(value)
  14. 4557 if default_value?(value)
  15. 1347 value
  16. else
  17. 3210 coder.load(super)
  18. end
  19. end
  20. 3 def serialize(value)
  21. 4367 return if value.nil?
  22. 3714 unless default_value?(value)
  23. 3402 super coder.dump(value)
  24. end
  25. end
  26. 3 def inspect
  27. Kernel.instance_method(:inspect).bind(self).call
  28. end
  29. 3 def changed_in_place?(raw_old_value, value)
  30. 410 return false if value.nil?
  31. 365 raw_new_value = encoded(value)
  32. 365 raw_old_value.nil? != raw_new_value.nil? ||
  33. subtype.changed_in_place?(raw_old_value, raw_new_value)
  34. end
  35. 3 def accessor
  36. 1080 ActiveRecord::Store::IndifferentHashAccessor
  37. end
  38. 3 def assert_valid_value(value)
  39. 612 if coder.respond_to?(:assert_valid_value)
  40. 561 coder.assert_valid_value(value, action: "serialize")
  41. end
  42. end
  43. 3 def force_equality?(value)
  44. 27 coder.respond_to?(:object_class) && value.is_a?(coder.object_class)
  45. end
  46. 3 private
  47. 3 def default_value?(value)
  48. 8636 value == coder.load(nil)
  49. end
  50. 3 def encoded(value)
  51. 365 unless default_value?(value)
  52. 347 coder.dump(value)
  53. end
  54. end
  55. end
  56. end
  57. end

lib/active_record/type/text.rb

100.0% lines covered

5 relevant lines. 5 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 module ActiveRecord
  3. 3 module Type
  4. 3 class Text < ActiveModel::Type::String # :nodoc:
  5. 3 def type
  6. 1226 :text
  7. end
  8. end
  9. end
  10. end

lib/active_record/type/time.rb

100.0% lines covered

9 relevant lines. 9 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 module ActiveRecord
  3. 3 module Type
  4. 3 class Time < ActiveModel::Type::Time
  5. 3 include Internal::Timezone
  6. 3 class Value < DelegateClass(::Time) # :nodoc:
  7. end
  8. 3 def serialize(value)
  9. 2240 case value = super
  10. when ::Time
  11. 910 Value.new(value)
  12. else
  13. 1330 value
  14. end
  15. end
  16. end
  17. end
  18. end

lib/active_record/type/type_map.rb

100.0% lines covered

32 relevant lines. 32 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 require "concurrent/map"
  3. 3 module ActiveRecord
  4. 3 module Type
  5. 3 class TypeMap # :nodoc:
  6. 3 def initialize
  7. 553 @mapping = {}
  8. 553 @cache = Concurrent::Map.new do |h, key|
  9. 15178 h.fetch_or_store(key, Concurrent::Map.new)
  10. end
  11. end
  12. 3 def lookup(lookup_key, *args)
  13. 1002922 fetch(lookup_key, *args) { Type.default_value }
  14. end
  15. 3 def fetch(lookup_key, *args, &block)
  16. 1153086 @cache[lookup_key].fetch_or_store(args) do
  17. 19936 perform_fetch(lookup_key, *args, &block)
  18. end
  19. end
  20. 3 def register_type(key, value = nil, &block)
  21. 87619 raise ::ArgumentError unless value || block
  22. 87616 @cache.clear
  23. 87616 if block
  24. 65346 @mapping[key] = block
  25. else
  26. 25950 @mapping[key] = proc { value }
  27. end
  28. end
  29. 3 def alias_type(key, target_key)
  30. 606 register_type(key) do |sql_type, *args|
  31. 64 metadata = sql_type[/\(.*\)/, 0]
  32. 64 lookup("#{target_key}#{metadata}", *args)
  33. end
  34. end
  35. 3 def clear
  36. 226 @mapping.clear
  37. end
  38. 3 private
  39. 3 def perform_fetch(lookup_key, *args)
  40. 883 matching_pair = @mapping.reverse_each.detect do |key, _|
  41. 9164 key === lookup_key
  42. end
  43. 883 if matching_pair
  44. 859 matching_pair.last.call(lookup_key, *args)
  45. else
  46. 24 yield lookup_key, *args
  47. end
  48. end
  49. end
  50. end
  51. end

lib/active_record/type/unsigned_integer.rb

100.0% lines covered

8 relevant lines. 8 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 module ActiveRecord
  3. 3 module Type
  4. 3 class UnsignedInteger < ActiveModel::Type::Integer # :nodoc:
  5. 3 private
  6. 3 def max_value
  7. 638 super * 2
  8. end
  9. 3 def min_value
  10. 638 0
  11. end
  12. end
  13. end
  14. end

lib/active_record/type_caster.rb

100.0% lines covered

4 relevant lines. 4 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 require "active_record/type_caster/map"
  3. 3 require "active_record/type_caster/connection"
  4. 3 module ActiveRecord
  5. 3 module TypeCaster # :nodoc:
  6. end
  7. end

lib/active_record/type_caster/connection.rb

88.89% lines covered

18 relevant lines. 16 lines covered and 2 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 module ActiveRecord
  3. 3 module TypeCaster
  4. 3 class Connection # :nodoc:
  5. 3 def initialize(klass, table_name)
  6. 554 @klass = klass
  7. 554 @table_name = table_name
  8. end
  9. 3 def type_cast_for_database(attr_name, value)
  10. type = type_for_attribute(attr_name)
  11. type.serialize(value)
  12. end
  13. 3 def type_for_attribute(attr_name)
  14. 896 schema_cache = connection.schema_cache
  15. 896 if schema_cache.data_source_exists?(table_name)
  16. 767 column = schema_cache.columns_hash(table_name)[attr_name.to_s]
  17. 767 type = connection.lookup_cast_type_from_column(column) if column
  18. end
  19. 896 type || Type.default_value
  20. end
  21. 3 delegate :connection, to: :@klass, private: true
  22. 3 private
  23. 3 attr_reader :table_name
  24. end
  25. end
  26. end

lib/active_record/type_caster/map.rb

100.0% lines covered

12 relevant lines. 12 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 module ActiveRecord
  3. 3 module TypeCaster
  4. 3 class Map # :nodoc:
  5. 3 def initialize(klass)
  6. 2548 @klass = klass
  7. end
  8. 3 def type_cast_for_database(attr_name, value)
  9. 15 type = type_for_attribute(attr_name)
  10. 15 type.serialize(value)
  11. end
  12. 3 def type_for_attribute(name)
  13. 156939 klass.type_for_attribute(name)
  14. end
  15. 3 private
  16. 3 attr_reader :klass
  17. end
  18. end
  19. end

lib/active_record/validations.rb

100.0% lines covered

35 relevant lines. 35 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 module ActiveRecord
  3. # = Active Record \RecordInvalid
  4. #
  5. # Raised by {ActiveRecord::Base#save!}[rdoc-ref:Persistence#save!] and
  6. # {ActiveRecord::Base#create!}[rdoc-ref:Persistence::ClassMethods#create!] when the record is invalid.
  7. # Use the #record method to retrieve the record which did not validate.
  8. #
  9. # begin
  10. # complex_operation_that_internally_calls_save!
  11. # rescue ActiveRecord::RecordInvalid => invalid
  12. # puts invalid.record.errors
  13. # end
  14. 3 class RecordInvalid < ActiveRecordError
  15. 3 attr_reader :record
  16. 3 def initialize(record = nil)
  17. 153 if record
  18. 150 @record = record
  19. 150 errors = @record.errors.full_messages.join(", ")
  20. 150 message = I18n.t(:"#{@record.class.i18n_scope}.errors.messages.record_invalid", errors: errors, default: :"errors.messages.record_invalid")
  21. else
  22. 3 message = "Record invalid"
  23. end
  24. 153 super(message)
  25. end
  26. end
  27. # = Active Record \Validations
  28. #
  29. # Active Record includes the majority of its validations from ActiveModel::Validations
  30. # all of which accept the <tt>:on</tt> argument to define the context where the
  31. # validations are active. Active Record will always supply either the context of
  32. # <tt>:create</tt> or <tt>:update</tt> dependent on whether the model is a
  33. # {new_record?}[rdoc-ref:Persistence#new_record?].
  34. 3 module Validations
  35. 3 extend ActiveSupport::Concern
  36. 3 include ActiveModel::Validations
  37. # The validation process on save can be skipped by passing <tt>validate: false</tt>.
  38. # The validation context can be changed by passing <tt>context: context</tt>.
  39. # The regular {ActiveRecord::Base#save}[rdoc-ref:Persistence#save] method is replaced
  40. # with this when the validations module is mixed in, which it is by default.
  41. 3 def save(**options)
  42. 8174 perform_validations(options) ? super : false
  43. end
  44. # Attempts to save the record just like {ActiveRecord::Base#save}[rdoc-ref:Base#save] but
  45. # will raise an ActiveRecord::RecordInvalid exception instead of returning +false+ if the record is not valid.
  46. 3 def save!(**options)
  47. 8247 perform_validations(options) ? super : raise_validation_error
  48. end
  49. # Runs all the validations within the specified context. Returns +true+ if
  50. # no errors are found, +false+ otherwise.
  51. #
  52. # Aliased as #validate.
  53. #
  54. # If the argument is +false+ (default is +nil+), the context is set to <tt>:create</tt> if
  55. # {new_record?}[rdoc-ref:Persistence#new_record?] is +true+, and to <tt>:update</tt> if it is not.
  56. #
  57. # \Validations with no <tt>:on</tt> option will run no matter the context. \Validations with
  58. # some <tt>:on</tt> option will only run in the specified context.
  59. 3 def valid?(context = nil)
  60. 18068 context ||= default_validation_context
  61. 18068 output = super(context)
  62. 18065 errors.empty? && output
  63. end
  64. 3 alias_method :validate, :valid?
  65. 3 private
  66. 3 def default_validation_context
  67. 18020 new_record? ? :create : :update
  68. end
  69. 3 def raise_validation_error
  70. 104 raise(RecordInvalid.new(self))
  71. end
  72. 3 def perform_validations(options = {})
  73. 16421 options[:validate] == false || valid?(options[:context])
  74. end
  75. end
  76. end
  77. 3 require "active_record/validations/associated"
  78. 3 require "active_record/validations/uniqueness"
  79. 3 require "active_record/validations/presence"
  80. 3 require "active_record/validations/absence"
  81. 3 require "active_record/validations/length"
  82. 3 require "active_record/validations/numericality"

lib/active_record/validations/absence.rb

100.0% lines covered

10 relevant lines. 10 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 module ActiveRecord
  3. 3 module Validations
  4. 3 class AbsenceValidator < ActiveModel::Validations::AbsenceValidator # :nodoc:
  5. 3 def validate_each(record, attribute, association_or_value)
  6. 33 if record.class._reflect_on_association(attribute)
  7. 18 association_or_value = Array.wrap(association_or_value).reject(&:marked_for_destruction?)
  8. end
  9. 33 super
  10. end
  11. end
  12. 3 module ClassMethods
  13. # Validates that the specified attributes are not present (as defined by
  14. # Object#present?). If the attribute is an association, the associated object
  15. # is considered absent if it was marked for destruction.
  16. #
  17. # See ActiveModel::Validations::HelperMethods.validates_absence_of for more information.
  18. 3 def validates_absence_of(*attr_names)
  19. 15 validates_with AbsenceValidator, _merge_attributes(attr_names)
  20. end
  21. end
  22. end
  23. end

lib/active_record/validations/associated.rb

100.0% lines covered

12 relevant lines. 12 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 module ActiveRecord
  3. 3 module Validations
  4. 3 class AssociatedValidator < ActiveModel::EachValidator #:nodoc:
  5. 3 def validate_each(record, attribute, value)
  6. 223 if Array(value).reject { |r| valid_object?(r) }.any?
  7. 36 record.errors.add(attribute, :invalid, **options.merge(value: value))
  8. end
  9. end
  10. 3 private
  11. 3 def valid_object?(record)
  12. 66 (record.respond_to?(:marked_for_destruction?) && record.marked_for_destruction?) || record.valid?
  13. end
  14. end
  15. 3 module ClassMethods
  16. # Validates whether the associated object or objects are all valid.
  17. # Works with any kind of association.
  18. #
  19. # class Book < ActiveRecord::Base
  20. # has_many :pages
  21. # belongs_to :library
  22. #
  23. # validates_associated :pages, :library
  24. # end
  25. #
  26. # WARNING: This validation must not be used on both ends of an association.
  27. # Doing so will lead to a circular dependency and cause infinite recursion.
  28. #
  29. # NOTE: This validation will not fail if the association hasn't been
  30. # assigned. If you want to ensure that the association is both present and
  31. # guaranteed to be valid, you also need to use
  32. # {validates_presence_of}[rdoc-ref:Validations::ClassMethods#validates_presence_of].
  33. #
  34. # Configuration options:
  35. #
  36. # * <tt>:message</tt> - A custom error message (default is: "is invalid").
  37. # * <tt>:on</tt> - Specifies the contexts where this validation is active.
  38. # Runs in all validation contexts by default +nil+. You can pass a symbol
  39. # or an array of symbols. (e.g. <tt>on: :create</tt> or
  40. # <tt>on: :custom_validation_context</tt> or
  41. # <tt>on: [:create, :custom_validation_context]</tt>)
  42. # * <tt>:if</tt> - Specifies a method, proc or string to call to determine
  43. # if the validation should occur (e.g. <tt>if: :allow_validation</tt>,
  44. # or <tt>if: Proc.new { |user| user.signup_step > 2 }</tt>). The method,
  45. # proc or string should return or evaluate to a +true+ or +false+ value.
  46. # * <tt>:unless</tt> - Specifies a method, proc or string to call to
  47. # determine if the validation should not occur (e.g. <tt>unless: :skip_validation</tt>,
  48. # or <tt>unless: Proc.new { |user| user.signup_step <= 2 }</tt>). The
  49. # method, proc or string should return or evaluate to a +true+ or +false+
  50. # value.
  51. 3 def validates_associated(*attr_names)
  52. 36 validates_with AssociatedValidator, _merge_attributes(attr_names)
  53. end
  54. end
  55. end
  56. end

lib/active_record/validations/length.rb

100.0% lines covered

11 relevant lines. 11 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 module ActiveRecord
  3. 3 module Validations
  4. 3 class LengthValidator < ActiveModel::Validations::LengthValidator # :nodoc:
  5. 3 def validate_each(record, attribute, association_or_value)
  6. 323 if association_or_value.respond_to?(:loaded?) && association_or_value.loaded?
  7. 36 association_or_value = association_or_value.target.reject(&:marked_for_destruction?)
  8. end
  9. 323 super
  10. end
  11. end
  12. 3 module ClassMethods
  13. # Validates that the specified attributes match the length restrictions supplied.
  14. # If the attribute is an association, records that are marked for destruction are not counted.
  15. #
  16. # See ActiveModel::Validations::HelperMethods.validates_length_of for more information.
  17. 3 def validates_length_of(*attr_names)
  18. 24 validates_with LengthValidator, _merge_attributes(attr_names)
  19. end
  20. 3 alias_method :validates_size_of, :validates_length_of
  21. end
  22. end
  23. end

lib/active_record/validations/numericality.rb

100.0% lines covered

15 relevant lines. 15 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 module ActiveRecord
  3. 3 module Validations
  4. 3 class NumericalityValidator < ActiveModel::Validations::NumericalityValidator # :nodoc:
  5. 3 def validate_each(record, attribute, value, precision: nil, scale: nil)
  6. 98 precision = [column_precision_for(record, attribute) || BigDecimal.double_fig, BigDecimal.double_fig].min
  7. 98 scale = column_scale_for(record, attribute)
  8. 98 super(record, attribute, value, precision: precision, scale: scale)
  9. end
  10. 3 private
  11. 3 def column_precision_for(record, attribute)
  12. 98 record.class.type_for_attribute(attribute.to_s)&.precision
  13. end
  14. 3 def column_scale_for(record, attribute)
  15. 98 record.class.type_for_attribute(attribute.to_s)&.scale
  16. end
  17. end
  18. 3 module ClassMethods
  19. # Validates whether the value of the specified attribute is numeric by
  20. # trying to convert it to a float with Kernel.Float (if <tt>only_integer</tt>
  21. # is +false+) or applying it to the regular expression <tt>/\A[\+\-]?\d+\z/</tt>
  22. # (if <tt>only_integer</tt> is set to +true+). Kernel.Float precision
  23. # defaults to the column's precision value or 15.
  24. #
  25. # See ActiveModel::Validations::HelperMethods.validates_numericality_of for more information.
  26. 3 def validates_numericality_of(*attr_names)
  27. 39 validates_with NumericalityValidator, _merge_attributes(attr_names)
  28. end
  29. end
  30. end
  31. end

lib/active_record/validations/presence.rb

100.0% lines covered

10 relevant lines. 10 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 module ActiveRecord
  3. 3 module Validations
  4. 3 class PresenceValidator < ActiveModel::Validations::PresenceValidator # :nodoc:
  5. 3 def validate_each(record, attribute, association_or_value)
  6. 5689 if record.class._reflect_on_association(attribute)
  7. 84 association_or_value = Array.wrap(association_or_value).reject(&:marked_for_destruction?)
  8. end
  9. 5689 super
  10. end
  11. end
  12. 3 module ClassMethods
  13. # Validates that the specified attributes are not blank (as defined by
  14. # Object#blank?), and, if the attribute is an association, that the
  15. # associated object is not marked for destruction. Happens by default
  16. # on save.
  17. #
  18. # class Person < ActiveRecord::Base
  19. # has_one :face
  20. # validates_presence_of :face
  21. # end
  22. #
  23. # The face attribute must be in the object and it cannot be blank or marked
  24. # for destruction.
  25. #
  26. # If you want to validate the presence of a boolean field (where the real values
  27. # are true and false), you will want to use
  28. # <tt>validates_inclusion_of :field_name, in: [true, false]</tt>.
  29. #
  30. # This is due to the way Object#blank? handles boolean values:
  31. # <tt>false.blank? # => true</tt>.
  32. #
  33. # This validator defers to the Active Model validation for presence, adding the
  34. # check to see that an associated object is not marked for destruction. This
  35. # prevents the parent object from validating successfully and saving, which then
  36. # deletes the associated object, thus putting the parent object into an invalid
  37. # state.
  38. #
  39. # NOTE: This validation will not fail while using it with an association
  40. # if the latter was assigned but not valid. If you want to ensure that
  41. # it is both present and valid, you also need to use
  42. # {validates_associated}[rdoc-ref:Validations::ClassMethods#validates_associated].
  43. #
  44. # Configuration options:
  45. # * <tt>:message</tt> - A custom error message (default is: "can't be blank").
  46. # * <tt>:on</tt> - Specifies the contexts where this validation is active.
  47. # Runs in all validation contexts by default +nil+. You can pass a symbol
  48. # or an array of symbols. (e.g. <tt>on: :create</tt> or
  49. # <tt>on: :custom_validation_context</tt> or
  50. # <tt>on: [:create, :custom_validation_context]</tt>)
  51. # * <tt>:if</tt> - Specifies a method, proc or string to call to determine if
  52. # the validation should occur (e.g. <tt>if: :allow_validation</tt>, or
  53. # <tt>if: Proc.new { |user| user.signup_step > 2 }</tt>). The method, proc
  54. # or string should return or evaluate to a +true+ or +false+ value.
  55. # * <tt>:unless</tt> - Specifies a method, proc or string to call to determine
  56. # if the validation should not occur (e.g. <tt>unless: :skip_validation</tt>,
  57. # or <tt>unless: Proc.new { |user| user.signup_step <= 2 }</tt>). The method,
  58. # proc or string should return or evaluate to a +true+ or +false+ value.
  59. # * <tt>:strict</tt> - Specifies whether validation should be strict.
  60. # See ActiveModel::Validations#validates! for more information.
  61. 3 def validates_presence_of(*attr_names)
  62. 171 validates_with PresenceValidator, _merge_attributes(attr_names)
  63. end
  64. end
  65. end
  66. end

lib/active_record/validations/uniqueness.rb

100.0% lines covered

54 relevant lines. 54 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 module ActiveRecord
  3. 3 module Validations
  4. 3 class UniquenessValidator < ActiveModel::EachValidator # :nodoc:
  5. 3 def initialize(options)
  6. 140 if options[:conditions] && !options[:conditions].respond_to?(:call)
  7. 3 raise ArgumentError, "#{options[:conditions]} was passed as :conditions but is not callable. " \
  8. "Pass a callable instead: `conditions: -> { where(approved: true) }`"
  9. end
  10. 164 unless Array(options[:scope]).all? { |scope| scope.respond_to?(:to_sym) }
  11. 3 raise ArgumentError, "#{options[:scope]} is not supported format for :scope option. " \
  12. "Pass a symbol or an array of symbols instead: `scope: :user_id`"
  13. end
  14. 134 super
  15. 134 @klass = options[:class]
  16. end
  17. 3 def validate_each(record, attribute, value)
  18. 523 finder_class = find_finder_class_for(record)
  19. 523 value = map_enum_attribute(finder_class, attribute, value)
  20. 523 relation = build_relation(finder_class, attribute, value)
  21. 523 if record.persisted?
  22. 85 if finder_class.primary_key
  23. 82 relation = relation.where.not(finder_class.primary_key => record.id_in_database)
  24. else
  25. 3 raise UnknownPrimaryKey.new(finder_class, "Cannot validate uniqueness for persisted record without primary key.")
  26. end
  27. end
  28. 520 relation = scope_relation(record, relation)
  29. 520 relation = relation.merge(options[:conditions]) if options[:conditions]
  30. 520 if relation.exists?
  31. 177 error_options = options.except(:case_sensitive, :scope, :conditions)
  32. 177 error_options[:value] = value
  33. 177 record.errors.add(attribute, :taken, **error_options)
  34. end
  35. end
  36. 3 private
  37. # The check for an existing value should be run from a class that
  38. # isn't abstract. This means working down from the current class
  39. # (self), to the first non-abstract class. Since classes don't know
  40. # their subclasses, we have to build the hierarchy between self and
  41. # the record's class.
  42. 3 def find_finder_class_for(record)
  43. 523 class_hierarchy = [record.class]
  44. 523 while class_hierarchy.first != @klass
  45. 66 class_hierarchy.unshift(class_hierarchy.first.superclass)
  46. end
  47. 1070 class_hierarchy.detect { |klass| !klass.abstract_class? }
  48. end
  49. 3 def build_relation(klass, attribute, value)
  50. 523 relation = klass.unscoped
  51. 523 comparison = relation.bind_attribute(attribute, value) do |attr, bind|
  52. 523 return relation.none! if bind.unboundable?
  53. 521 if !options.key?(:case_sensitive) || bind.nil?
  54. 427 klass.connection.default_uniqueness_comparison(attr, bind, klass)
  55. 94 elsif options[:case_sensitive]
  56. 33 klass.connection.case_sensitive_comparison(attr, bind)
  57. else
  58. # will use SQL LOWER function before comparison, unless it detects a case insensitive collation
  59. 61 klass.connection.case_insensitive_comparison(attr, bind)
  60. end
  61. end
  62. 521 relation.where!(comparison)
  63. end
  64. 3 def scope_relation(record, relation)
  65. 520 Array(options[:scope]).each do |scope_item|
  66. 154 scope_value = if record.class._reflect_on_association(scope_item)
  67. 28 record.association(scope_item).reader
  68. else
  69. 126 record.read_attribute(scope_item)
  70. end
  71. 154 relation = relation.where(scope_item => scope_value)
  72. end
  73. 520 relation
  74. end
  75. 3 def map_enum_attribute(klass, attribute, value)
  76. 523 mapping = klass.defined_enums[attribute.to_s]
  77. 523 value = mapping[value] if value && mapping
  78. 523 value
  79. end
  80. end
  81. 3 module ClassMethods
  82. # Validates whether the value of the specified attributes are unique
  83. # across the system. Useful for making sure that only one user
  84. # can be named "davidhh".
  85. #
  86. # class Person < ActiveRecord::Base
  87. # validates_uniqueness_of :user_name
  88. # end
  89. #
  90. # It can also validate whether the value of the specified attributes are
  91. # unique based on a <tt>:scope</tt> parameter:
  92. #
  93. # class Person < ActiveRecord::Base
  94. # validates_uniqueness_of :user_name, scope: :account_id
  95. # end
  96. #
  97. # Or even multiple scope parameters. For example, making sure that a
  98. # teacher can only be on the schedule once per semester for a particular
  99. # class.
  100. #
  101. # class TeacherSchedule < ActiveRecord::Base
  102. # validates_uniqueness_of :teacher_id, scope: [:semester_id, :class_id]
  103. # end
  104. #
  105. # It is also possible to limit the uniqueness constraint to a set of
  106. # records matching certain conditions. In this example archived articles
  107. # are not being taken into consideration when validating uniqueness
  108. # of the title attribute:
  109. #
  110. # class Article < ActiveRecord::Base
  111. # validates_uniqueness_of :title, conditions: -> { where.not(status: 'archived') }
  112. # end
  113. #
  114. # When the record is created, a check is performed to make sure that no
  115. # record exists in the database with the given value for the specified
  116. # attribute (that maps to a column). When the record is updated,
  117. # the same check is made but disregarding the record itself.
  118. #
  119. # Configuration options:
  120. #
  121. # * <tt>:message</tt> - Specifies a custom error message (default is:
  122. # "has already been taken").
  123. # * <tt>:scope</tt> - One or more columns by which to limit the scope of
  124. # the uniqueness constraint.
  125. # * <tt>:conditions</tt> - Specify the conditions to be included as a
  126. # <tt>WHERE</tt> SQL fragment to limit the uniqueness constraint lookup
  127. # (e.g. <tt>conditions: -> { where(status: 'active') }</tt>).
  128. # * <tt>:case_sensitive</tt> - Looks for an exact match. Ignored by
  129. # non-text columns (+true+ by default).
  130. # * <tt>:allow_nil</tt> - If set to +true+, skips this validation if the
  131. # attribute is +nil+ (default is +false+).
  132. # * <tt>:allow_blank</tt> - If set to +true+, skips this validation if the
  133. # attribute is blank (default is +false+).
  134. # * <tt>:if</tt> - Specifies a method, proc or string to call to determine
  135. # if the validation should occur (e.g. <tt>if: :allow_validation</tt>,
  136. # or <tt>if: Proc.new { |user| user.signup_step > 2 }</tt>). The method,
  137. # proc or string should return or evaluate to a +true+ or +false+ value.
  138. # * <tt>:unless</tt> - Specifies a method, proc or string to call to
  139. # determine if the validation should not occur (e.g. <tt>unless: :skip_validation</tt>,
  140. # or <tt>unless: Proc.new { |user| user.signup_step <= 2 }</tt>). The
  141. # method, proc or string should return or evaluate to a +true+ or +false+
  142. # value.
  143. #
  144. # === Concurrency and integrity
  145. #
  146. # Using this validation method in conjunction with
  147. # {ActiveRecord::Base#save}[rdoc-ref:Persistence#save]
  148. # does not guarantee the absence of duplicate record insertions, because
  149. # uniqueness checks on the application level are inherently prone to race
  150. # conditions. For example, suppose that two users try to post a Comment at
  151. # the same time, and a Comment's title must be unique. At the database-level,
  152. # the actions performed by these users could be interleaved in the following manner:
  153. #
  154. # User 1 | User 2
  155. # ------------------------------------+--------------------------------------
  156. # # User 1 checks whether there's |
  157. # # already a comment with the title |
  158. # # 'My Post'. This is not the case. |
  159. # SELECT * FROM comments |
  160. # WHERE title = 'My Post' |
  161. # |
  162. # | # User 2 does the same thing and also
  163. # | # infers that their title is unique.
  164. # | SELECT * FROM comments
  165. # | WHERE title = 'My Post'
  166. # |
  167. # # User 1 inserts their comment. |
  168. # INSERT INTO comments |
  169. # (title, content) VALUES |
  170. # ('My Post', 'hi!') |
  171. # |
  172. # | # User 2 does the same thing.
  173. # | INSERT INTO comments
  174. # | (title, content) VALUES
  175. # | ('My Post', 'hello!')
  176. # |
  177. # | # ^^^^^^
  178. # | # Boom! We now have a duplicate
  179. # | # title!
  180. #
  181. # The best way to work around this problem is to add a unique index to the database table using
  182. # {connection.add_index}[rdoc-ref:ConnectionAdapters::SchemaStatements#add_index].
  183. # In the rare case that a race condition occurs, the database will guarantee
  184. # the field's uniqueness.
  185. #
  186. # When the database catches such a duplicate insertion,
  187. # {ActiveRecord::Base#save}[rdoc-ref:Persistence#save] will raise an ActiveRecord::StatementInvalid
  188. # exception. You can either choose to let this error propagate (which
  189. # will result in the default Rails exception page being shown), or you
  190. # can catch it and restart the transaction (e.g. by telling the user
  191. # that the title already exists, and asking them to re-enter the title).
  192. # This technique is also known as
  193. # {optimistic concurrency control}[https://en.wikipedia.org/wiki/Optimistic_concurrency_control].
  194. #
  195. # The bundled ActiveRecord::ConnectionAdapters distinguish unique index
  196. # constraint errors from other types of database errors by throwing an
  197. # ActiveRecord::RecordNotUnique exception. For other adapters you will
  198. # have to parse the (database-specific) exception message to detect such
  199. # a case.
  200. #
  201. # The following bundled adapters throw the ActiveRecord::RecordNotUnique exception:
  202. #
  203. # * ActiveRecord::ConnectionAdapters::Mysql2Adapter.
  204. # * ActiveRecord::ConnectionAdapters::SQLite3Adapter.
  205. # * ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.
  206. 3 def validates_uniqueness_of(*attr_names)
  207. 124 validates_with UniquenessValidator, _merge_attributes(attr_names)
  208. end
  209. end
  210. end
  211. end

lib/active_record/version.rb

75.0% lines covered

4 relevant lines. 3 lines covered and 1 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 require_relative "gem_version"
  3. 3 module ActiveRecord
  4. # Returns the version of the currently loaded ActiveRecord as a <tt>Gem::Version</tt>
  5. 3 def self.version
  6. gem_version
  7. end
  8. end

lib/arel.rb

100.0% lines covered

30 relevant lines. 30 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 require "arel/errors"
  3. 3 require "arel/crud"
  4. 3 require "arel/factory_methods"
  5. 3 require "arel/expressions"
  6. 3 require "arel/predications"
  7. 3 require "arel/window_predications"
  8. 3 require "arel/math"
  9. 3 require "arel/alias_predication"
  10. 3 require "arel/order_predications"
  11. 3 require "arel/table"
  12. 3 require "arel/attributes/attribute"
  13. 3 require "arel/visitors"
  14. 3 require "arel/collectors/sql_string"
  15. 3 require "arel/tree_manager"
  16. 3 require "arel/insert_manager"
  17. 3 require "arel/select_manager"
  18. 3 require "arel/update_manager"
  19. 3 require "arel/delete_manager"
  20. 3 require "arel/nodes"
  21. 3 module Arel
  22. 3 VERSION = "10.0.0"
  23. # Wrap a known-safe SQL string for passing to query methods, e.g.
  24. #
  25. # Post.order(Arel.sql("length(title)")).last
  26. #
  27. # Great caution should be taken to avoid SQL injection vulnerabilities.
  28. # This method should not be used with unsafe values such as request
  29. # parameters or model attributes.
  30. 3 def self.sql(raw_sql)
  31. 36315 Arel::Nodes::SqlLiteral.new raw_sql
  32. end
  33. 3 def self.star # :nodoc:
  34. 30348 sql "*"
  35. end
  36. 3 def self.arel_node?(value) # :nodoc:
  37. 14882 value.is_a?(Arel::Nodes::Node) || value.is_a?(Arel::Attribute) || value.is_a?(Arel::Nodes::SqlLiteral)
  38. end
  39. 3 def self.fetch_attribute(value, &block) # :nodoc:
  40. 30580 unless String === value
  41. 30058 value.fetch_attribute(&block)
  42. end
  43. end
  44. end

lib/arel/alias_predication.rb

100.0% lines covered

4 relevant lines. 4 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 module Arel # :nodoc: all
  3. 3 module AliasPredication
  4. 3 def as(other)
  5. 10838 Nodes::As.new self, Nodes::SqlLiteral.new(other)
  6. end
  7. end
  8. end

lib/arel/attributes/attribute.rb

100.0% lines covered

24 relevant lines. 24 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 module Arel # :nodoc: all
  3. 3 module Attributes
  4. 3 class Attribute < Struct.new :relation, :name
  5. 3 include Arel::Expressions
  6. 3 include Arel::Predications
  7. 3 include Arel::AliasPredication
  8. 3 include Arel::OrderPredications
  9. 3 include Arel::Math
  10. 3 def type_caster
  11. 6155 relation.type_for_attribute(name)
  12. end
  13. ###
  14. # Create a node for lowering this attribute
  15. 3 def lower
  16. 59 relation.lower self
  17. end
  18. 3 def type_cast_for_database(value)
  19. 21 relation.type_cast_for_database(name, value)
  20. end
  21. 3 def able_to_type_cast?
  22. 579 relation.able_to_type_cast?
  23. end
  24. end
  25. 3 class String < Attribute; end
  26. 3 class Time < Attribute; end
  27. 3 class Boolean < Attribute; end
  28. 3 class Decimal < Attribute; end
  29. 3 class Float < Attribute; end
  30. 3 class Integer < Attribute; end
  31. 3 class Undefined < Attribute; end
  32. end
  33. 3 Attribute = Attributes::Attribute
  34. end

lib/arel/collectors/bind.rb

100.0% lines covered

15 relevant lines. 15 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 module Arel # :nodoc: all
  3. 3 module Collectors
  4. 3 class Bind
  5. 3 def initialize
  6. 53134 @binds = []
  7. end
  8. 3 def <<(str)
  9. 983723 self
  10. end
  11. 3 def add_bind(bind)
  12. 103661 @binds << bind
  13. 103661 self
  14. end
  15. 3 def add_binds(binds)
  16. 3868 @binds.concat binds
  17. 3868 self
  18. end
  19. 3 def value
  20. 53134 @binds
  21. end
  22. end
  23. end
  24. end

lib/arel/collectors/composite.rb

100.0% lines covered

23 relevant lines. 23 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 module Arel # :nodoc: all
  3. 3 module Collectors
  4. 3 class Composite
  5. 3 attr_accessor :preparable
  6. 3 def initialize(left, right)
  7. 53128 @left = left
  8. 53128 @right = right
  9. end
  10. 3 def <<(str)
  11. 983645 left << str
  12. 983645 right << str
  13. 983645 self
  14. end
  15. 3 def add_bind(bind, &block)
  16. 103649 left.add_bind bind, &block
  17. 103649 right.add_bind bind, &block
  18. 103649 self
  19. end
  20. 3 def add_binds(binds, &block)
  21. 3868 left.add_binds(binds, &block)
  22. 3868 right.add_binds(binds, &block)
  23. 3868 self
  24. end
  25. 3 def value
  26. 53128 [left.value, right.value]
  27. end
  28. 3 private
  29. 3 attr_reader :left, :right
  30. end
  31. end
  32. end

lib/arel/collectors/plain_string.rb

100.0% lines covered

10 relevant lines. 10 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 module Arel # :nodoc: all
  3. 3 module Collectors
  4. 3 class PlainString
  5. 3 def initialize
  6. 209482 @str = +""
  7. end
  8. 3 def value
  9. 209368 @str
  10. end
  11. 3 def <<(str)
  12. 5117566 @str << str
  13. 5117566 self
  14. end
  15. end
  16. end
  17. end

lib/arel/collectors/sql_string.rb

100.0% lines covered

15 relevant lines. 15 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 require "arel/collectors/plain_string"
  3. 3 module Arel # :nodoc: all
  4. 3 module Collectors
  5. 3 class SQLString < PlainString
  6. 3 attr_accessor :preparable
  7. 3 def initialize(*)
  8. 209377 super
  9. 209377 @bind_index = 1
  10. end
  11. 3 def add_bind(bind)
  12. 103973 self << yield(@bind_index)
  13. 103973 @bind_index += 1
  14. 103973 self
  15. end
  16. 3 def add_binds(binds, &block)
  17. 3898 self << (@bind_index...@bind_index += binds.size).map(&block).join(", ")
  18. 3898 self
  19. end
  20. end
  21. end
  22. end

lib/arel/collectors/substitute_binds.rb

100.0% lines covered

19 relevant lines. 19 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 module Arel # :nodoc: all
  3. 3 module Collectors
  4. 3 class SubstituteBinds
  5. 3 attr_accessor :preparable
  6. 3 def initialize(quoter, delegate_collector)
  7. 504 @quoter = quoter
  8. 504 @delegate = delegate_collector
  9. end
  10. 3 def <<(str)
  11. 11706 delegate << str
  12. 11706 self
  13. end
  14. 3 def add_bind(bind)
  15. 363 bind = bind.value_for_database if bind.respond_to?(:value_for_database)
  16. 363 self << quoter.quote(bind)
  17. end
  18. 3 def add_binds(binds)
  19. 462967 self << binds.map { |bind| quoter.quote(bind) }.join(", ")
  20. end
  21. 3 def value
  22. 504 delegate.value
  23. end
  24. 3 private
  25. 3 attr_reader :quoter, :delegate
  26. end
  27. end
  28. end

lib/arel/crud.rb

100.0% lines covered

26 relevant lines. 26 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 module Arel # :nodoc: all
  3. ###
  4. # FIXME hopefully we can remove this
  5. 3 module Crud
  6. 3 def compile_update(values, pk)
  7. 2812 um = UpdateManager.new
  8. 2812 if Nodes::SqlLiteral === values
  9. 9 relation = @ctx.from
  10. else
  11. 2803 relation = values.first.first.relation
  12. end
  13. 2812 um.key = pk
  14. 2812 um.table relation
  15. 2812 um.set values
  16. 2812 um.take @ast.limit.expr if @ast.limit
  17. 2812 um.order(*@ast.orders)
  18. 2812 um.wheres = @ctx.wheres
  19. 2812 um
  20. end
  21. 3 def compile_insert(values)
  22. 12443 im = create_insert
  23. 12443 im.insert values
  24. 12443 im
  25. end
  26. 3 def create_insert
  27. 12446 InsertManager.new
  28. end
  29. 3 def compile_delete
  30. 9 dm = DeleteManager.new
  31. 9 dm.take @ast.limit.expr if @ast.limit
  32. 9 dm.wheres = @ctx.wheres
  33. 9 dm.from @ctx.froms
  34. 9 dm
  35. end
  36. end
  37. end

lib/arel/delete_manager.rb

100.0% lines covered

10 relevant lines. 10 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 module Arel # :nodoc: all
  3. 3 class DeleteManager < Arel::TreeManager
  4. 3 include TreeManager::StatementMethods
  5. 3 def initialize
  6. 3363 super
  7. 3363 @ast = Nodes::DeleteStatement.new
  8. 3363 @ctx = @ast
  9. end
  10. 3 def from(relation)
  11. 3357 @ast.relation = relation
  12. 3357 self
  13. end
  14. end
  15. end

lib/arel/errors.rb

100.0% lines covered

3 relevant lines. 3 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 module Arel # :nodoc: all
  3. 3 class ArelError < StandardError
  4. end
  5. 3 class EmptyJoinError < ArelError
  6. end
  7. end

lib/arel/expressions.rb

100.0% lines covered

14 relevant lines. 14 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 module Arel # :nodoc: all
  3. 3 module Expressions
  4. 3 def count(distinct = false)
  5. 3999 Nodes::Count.new [self], distinct
  6. end
  7. 3 def sum
  8. 175 Nodes::Sum.new [self]
  9. end
  10. 3 def maximum
  11. 127 Nodes::Max.new [self]
  12. end
  13. 3 def minimum
  14. 105 Nodes::Min.new [self]
  15. end
  16. 3 def average
  17. 75 Nodes::Avg.new [self]
  18. end
  19. 3 def extract(field)
  20. 21 Nodes::Extract.new [self], field
  21. end
  22. end
  23. end

lib/arel/factory_methods.rb

100.0% lines covered

22 relevant lines. 22 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 module Arel # :nodoc: all
  3. ###
  4. # Methods for creating various nodes
  5. 3 module FactoryMethods
  6. 3 def create_true
  7. 6 Arel::Nodes::True.new
  8. end
  9. 3 def create_false
  10. 6 Arel::Nodes::False.new
  11. end
  12. 3 def create_table_alias(relation, name)
  13. 354 Nodes::TableAlias.new(relation, name)
  14. end
  15. 3 def create_join(to, constraint = nil, klass = Nodes::InnerJoin)
  16. 90 klass.new(to, constraint)
  17. end
  18. 3 def create_string_join(to)
  19. 3 create_join to, nil, Nodes::StringJoin
  20. end
  21. 3 def create_and(clauses)
  22. 12 Nodes::And.new clauses
  23. end
  24. 3 def create_on(expr)
  25. 18 Nodes::On.new expr
  26. end
  27. 3 def grouping(expr)
  28. 354 Nodes::Grouping.new expr
  29. end
  30. ###
  31. # Create a LOWER() function
  32. 3 def lower(column)
  33. 118 Nodes::NamedFunction.new "LOWER", [Nodes.build_quoted(column)]
  34. end
  35. 3 def coalesce(*exprs)
  36. 1049 Nodes::NamedFunction.new "COALESCE", exprs
  37. end
  38. end
  39. end

lib/arel/insert_manager.rb

100.0% lines covered

27 relevant lines. 27 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 module Arel # :nodoc: all
  3. 3 class InsertManager < Arel::TreeManager
  4. 3 def initialize
  5. 165953 super
  6. 165953 @ast = Nodes::InsertStatement.new
  7. end
  8. 3 def into(table)
  9. 154780 @ast.relation = table
  10. 154780 self
  11. end
  12. 385585 def columns; @ast.columns end
  13. 153471 def values=(val); @ast.values = val; end
  14. 3 def select(select)
  15. 3 @ast.select = select
  16. end
  17. 3 def insert(fields)
  18. 12467 return if fields.empty?
  19. 12464 if String === fields
  20. 1297 @ast.values = Nodes::SqlLiteral.new(fields)
  21. else
  22. 11167 @ast.relation ||= fields.first.first.relation
  23. 11167 values = []
  24. 11167 fields.each do |column, value|
  25. 31076 @ast.columns << column
  26. 31076 values << value
  27. end
  28. 11167 @ast.values = create_values(values)
  29. end
  30. 12464 self
  31. end
  32. 3 def create_values(values)
  33. 11170 Nodes::ValuesList.new([values])
  34. end
  35. 3 def create_values_list(rows)
  36. 153459 Nodes::ValuesList.new(rows)
  37. end
  38. end
  39. end

lib/arel/math.rb

100.0% lines covered

22 relevant lines. 22 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 module Arel # :nodoc: all
  3. 3 module Math
  4. 3 def *(other)
  5. 18 Arel::Nodes::Multiplication.new(self, other)
  6. end
  7. 3 def +(other)
  8. 844 Arel::Nodes::Grouping.new(Arel::Nodes::Addition.new(self, other))
  9. end
  10. 3 def -(other)
  11. 241 Arel::Nodes::Grouping.new(Arel::Nodes::Subtraction.new(self, other))
  12. end
  13. 3 def /(other)
  14. 18 Arel::Nodes::Division.new(self, other)
  15. end
  16. 3 def &(other)
  17. 18 Arel::Nodes::Grouping.new(Arel::Nodes::BitwiseAnd.new(self, other))
  18. end
  19. 3 def |(other)
  20. 18 Arel::Nodes::Grouping.new(Arel::Nodes::BitwiseOr.new(self, other))
  21. end
  22. 3 def ^(other)
  23. 18 Arel::Nodes::Grouping.new(Arel::Nodes::BitwiseXor.new(self, other))
  24. end
  25. 3 def <<(other)
  26. 18 Arel::Nodes::Grouping.new(Arel::Nodes::BitwiseShiftLeft.new(self, other))
  27. end
  28. 3 def >>(other)
  29. 18 Arel::Nodes::Grouping.new(Arel::Nodes::BitwiseShiftRight.new(self, other))
  30. end
  31. 3 def ~@
  32. 3 Arel::Nodes::BitwiseNot.new(self)
  33. end
  34. end
  35. end

lib/arel/nodes.rb

100.0% lines covered

45 relevant lines. 45 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. # node
  3. 3 require "arel/nodes/node"
  4. 3 require "arel/nodes/node_expression"
  5. 3 require "arel/nodes/select_statement"
  6. 3 require "arel/nodes/select_core"
  7. 3 require "arel/nodes/insert_statement"
  8. 3 require "arel/nodes/update_statement"
  9. 3 require "arel/nodes/bind_param"
  10. # terminal
  11. 3 require "arel/nodes/terminal"
  12. 3 require "arel/nodes/true"
  13. 3 require "arel/nodes/false"
  14. # unary
  15. 3 require "arel/nodes/unary"
  16. 3 require "arel/nodes/grouping"
  17. 3 require "arel/nodes/homogeneous_in"
  18. 3 require "arel/nodes/ordering"
  19. 3 require "arel/nodes/ascending"
  20. 3 require "arel/nodes/descending"
  21. 3 require "arel/nodes/unqualified_column"
  22. 3 require "arel/nodes/with"
  23. # binary
  24. 3 require "arel/nodes/binary"
  25. 3 require "arel/nodes/equality"
  26. 3 require "arel/nodes/in"
  27. 3 require "arel/nodes/join_source"
  28. 3 require "arel/nodes/delete_statement"
  29. 3 require "arel/nodes/table_alias"
  30. 3 require "arel/nodes/infix_operation"
  31. 3 require "arel/nodes/unary_operation"
  32. 3 require "arel/nodes/over"
  33. 3 require "arel/nodes/matches"
  34. 3 require "arel/nodes/regexp"
  35. # nary
  36. 3 require "arel/nodes/and"
  37. # function
  38. # FIXME: Function + Alias can be rewritten as a Function and Alias node.
  39. # We should make Function a Unary node and deprecate the use of "aliaz"
  40. 3 require "arel/nodes/function"
  41. 3 require "arel/nodes/count"
  42. 3 require "arel/nodes/extract"
  43. 3 require "arel/nodes/values_list"
  44. 3 require "arel/nodes/named_function"
  45. # windows
  46. 3 require "arel/nodes/window"
  47. # conditional expressions
  48. 3 require "arel/nodes/case"
  49. # joins
  50. 3 require "arel/nodes/full_outer_join"
  51. 3 require "arel/nodes/inner_join"
  52. 3 require "arel/nodes/outer_join"
  53. 3 require "arel/nodes/right_outer_join"
  54. 3 require "arel/nodes/string_join"
  55. 3 require "arel/nodes/comment"
  56. 3 require "arel/nodes/sql_literal"
  57. 3 require "arel/nodes/casted"

lib/arel/nodes/and.rb

100.0% lines covered

16 relevant lines. 16 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 module Arel # :nodoc: all
  3. 3 module Nodes
  4. 3 class And < Arel::Nodes::NodeExpression
  5. 3 attr_reader :children
  6. 3 def initialize(children)
  7. 12718 super()
  8. 12718 @children = children
  9. end
  10. 3 def left
  11. 3 children.first
  12. end
  13. 3 def right
  14. 3 children[1]
  15. end
  16. 3 def hash
  17. 12 children.hash
  18. end
  19. 3 def eql?(other)
  20. 51 self.class == other.class &&
  21. self.children == other.children
  22. end
  23. 3 alias :== :eql?
  24. end
  25. end
  26. end

lib/arel/nodes/ascending.rb

100.0% lines covered

11 relevant lines. 11 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 module Arel # :nodoc: all
  3. 3 module Nodes
  4. 3 class Ascending < Ordering
  5. 3 def reverse
  6. 264 Descending.new(expr)
  7. end
  8. 3 def direction
  9. 3 :asc
  10. end
  11. 3 def ascending?
  12. 9 true
  13. end
  14. 3 def descending?
  15. 3 false
  16. end
  17. end
  18. end
  19. end

lib/arel/nodes/binary.rb

100.0% lines covered

61 relevant lines. 61 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 module Arel # :nodoc: all
  3. 3 module Nodes
  4. 3 class Binary < Arel::Nodes::NodeExpression
  5. 3 attr_accessor :left, :right
  6. 3 def initialize(left, right)
  7. 139957 super()
  8. 139957 @left = left
  9. 139957 @right = right
  10. end
  11. 3 def initialize_copy(other)
  12. 30 super
  13. 30 @left = @left.clone if @left
  14. 30 @right = @right.clone if @right
  15. end
  16. 3 def hash
  17. 135 [self.class, @left, @right].hash
  18. end
  19. 3 def eql?(other)
  20. 52645 self.class == other.class &&
  21. self.left == other.left &&
  22. self.right == other.right
  23. end
  24. 3 alias :== :eql?
  25. end
  26. 3 module FetchAttribute
  27. 3 def fetch_attribute
  28. 23686 if left.is_a?(Arel::Attributes::Attribute)
  29. 23668 yield left
  30. 18 elsif right.is_a?(Arel::Attributes::Attribute)
  31. 3 yield right
  32. end
  33. end
  34. end
  35. 6 class Between < Binary; include FetchAttribute; end
  36. 3 class GreaterThan < Binary
  37. 3 include FetchAttribute
  38. 3 def invert
  39. 3 Arel::Nodes::LessThanOrEqual.new(left, right)
  40. end
  41. end
  42. 3 class GreaterThanOrEqual < Binary
  43. 3 include FetchAttribute
  44. 3 def invert
  45. 6 Arel::Nodes::LessThan.new(left, right)
  46. end
  47. end
  48. 3 class LessThan < Binary
  49. 3 include FetchAttribute
  50. 3 def invert
  51. 3 Arel::Nodes::GreaterThanOrEqual.new(left, right)
  52. end
  53. end
  54. 3 class LessThanOrEqual < Binary
  55. 3 include FetchAttribute
  56. 3 def invert
  57. 3 Arel::Nodes::GreaterThan.new(left, right)
  58. end
  59. end
  60. 3 class IsDistinctFrom < Binary
  61. 3 include FetchAttribute
  62. 3 def invert
  63. 3 Arel::Nodes::IsNotDistinctFrom.new(left, right)
  64. end
  65. end
  66. 3 class IsNotDistinctFrom < Binary
  67. 3 include FetchAttribute
  68. 3 def invert
  69. 3 Arel::Nodes::IsDistinctFrom.new(left, right)
  70. end
  71. end
  72. 3 class NotEqual < Binary
  73. 3 include FetchAttribute
  74. 3 def invert
  75. 3 Arel::Nodes::Equality.new(left, right)
  76. end
  77. end
  78. 3 class NotIn < Binary
  79. 3 include FetchAttribute
  80. 3 def invert
  81. 3 Arel::Nodes::In.new(left, right)
  82. end
  83. end
  84. 3 class Or < Binary
  85. 3 def fetch_attribute(&block)
  86. 102 left.fetch_attribute(&block) && right.fetch_attribute(&block)
  87. end
  88. end
  89. %w{
  90. As
  91. Assignment
  92. Join
  93. Union
  94. UnionAll
  95. Intersect
  96. Except
  97. 3 }.each do |name|
  98. 21 const_set name, Class.new(Binary)
  99. end
  100. end
  101. end

lib/arel/nodes/bind_param.rb

90.91% lines covered

22 relevant lines. 20 lines covered and 2 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 module Arel # :nodoc: all
  3. 3 module Nodes
  4. 3 class BindParam < Node
  5. 3 attr_reader :value
  6. 3 def initialize(value)
  7. 112245 @value = value
  8. 112245 super()
  9. end
  10. 3 def hash
  11. [self.class, self.value].hash
  12. end
  13. 3 def eql?(other)
  14. 275 other.is_a?(BindParam) &&
  15. value == other.value
  16. end
  17. 3 alias :== :eql?
  18. 3 def nil?
  19. 45963 value.nil?
  20. end
  21. 3 def value_before_type_cast
  22. 6381 if value.respond_to?(:value_before_type_cast)
  23. 6381 value.value_before_type_cast
  24. else
  25. value
  26. end
  27. end
  28. 3 def infinite?
  29. 254 value.respond_to?(:infinite?) && value.infinite?
  30. end
  31. 3 def unboundable?
  32. 66079 value.respond_to?(:unboundable?) && value.unboundable?
  33. end
  34. end
  35. end
  36. end

lib/arel/nodes/case.rb

100.0% lines covered

29 relevant lines. 29 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 module Arel # :nodoc: all
  3. 3 module Nodes
  4. 3 class Case < Arel::Nodes::NodeExpression
  5. 3 attr_accessor :case, :conditions, :default
  6. 3 def initialize(expression = nil, default = nil)
  7. 42 @case = expression
  8. 42 @conditions = []
  9. 42 @default = default
  10. end
  11. 3 def when(condition, expression = nil)
  12. 24 @conditions << When.new(Nodes.build_quoted(condition), expression)
  13. 24 self
  14. end
  15. 3 def then(expression)
  16. 18 @conditions.last.right = Nodes.build_quoted(expression)
  17. 18 self
  18. end
  19. 3 def else(expression)
  20. 12 @default = Else.new Nodes.build_quoted(expression)
  21. 12 self
  22. end
  23. 3 def initialize_copy(other)
  24. 3 super
  25. 3 @case = @case.clone if @case
  26. 6 @conditions = @conditions.map { |x| x.clone }
  27. 3 @default = @default.clone if @default
  28. end
  29. 3 def hash
  30. 12 [@case, @conditions, @default].hash
  31. end
  32. 3 def eql?(other)
  33. 6 self.class == other.class &&
  34. self.case == other.case &&
  35. self.conditions == other.conditions &&
  36. self.default == other.default
  37. end
  38. 3 alias :== :eql?
  39. end
  40. 3 class When < Binary # :nodoc:
  41. end
  42. 3 class Else < Unary # :nodoc:
  43. end
  44. end
  45. end

lib/arel/nodes/casted.rb

100.0% lines covered

31 relevant lines. 31 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 module Arel # :nodoc: all
  3. 3 module Nodes
  4. 3 class Casted < Arel::Nodes::NodeExpression # :nodoc:
  5. 3 attr_reader :value, :attribute
  6. 3 alias :value_before_type_cast :value
  7. 3 def initialize(value, attribute)
  8. 2501 @value = value
  9. 2501 @attribute = attribute
  10. 2501 super()
  11. end
  12. 252 def nil?; value.nil?; end
  13. 3 def value_for_database
  14. 579 if attribute.able_to_type_cast?
  15. 21 attribute.type_cast_for_database(value)
  16. else
  17. 558 value
  18. end
  19. end
  20. 3 def hash
  21. 6 [self.class, value, attribute].hash
  22. end
  23. 3 def eql?(other)
  24. 168 self.class == other.class &&
  25. self.value == other.value &&
  26. self.attribute == other.attribute
  27. end
  28. 3 alias :== :eql?
  29. end
  30. 3 class Quoted < Arel::Nodes::Unary # :nodoc:
  31. 3 alias :value_for_database :value
  32. 3 alias :value_before_type_cast :value
  33. 93 def nil?; value.nil?; end
  34. 3 def infinite?
  35. 48 value.respond_to?(:infinite?) && value.infinite?
  36. end
  37. end
  38. 3 def self.build_quoted(other, attribute = nil)
  39. 69604 case other
  40. when Arel::Nodes::Node, Arel::Attributes::Attribute, Arel::Table, Arel::Nodes::BindParam, Arel::SelectManager, Arel::Nodes::Quoted, Arel::Nodes::SqlLiteral
  41. 64164 other
  42. else
  43. 5440 case attribute
  44. when Arel::Attributes::Attribute
  45. 2426 Casted.new other, attribute
  46. else
  47. 3014 Quoted.new other
  48. end
  49. end
  50. end
  51. end
  52. end

lib/arel/nodes/comment.rb

86.67% lines covered

15 relevant lines. 13 lines covered and 2 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 module Arel # :nodoc: all
  3. 3 module Nodes
  4. 3 class Comment < Arel::Nodes::Node
  5. 3 attr_reader :values
  6. 3 def initialize(values)
  7. 123 super()
  8. 123 @values = values
  9. end
  10. 3 def initialize_copy(other)
  11. super
  12. @values = @values.clone
  13. end
  14. 3 def hash
  15. 30 [@values].hash
  16. end
  17. 3 def eql?(other)
  18. 6 self.class == other.class &&
  19. self.values == other.values
  20. end
  21. 3 alias :== :eql?
  22. end
  23. end
  24. end

lib/arel/nodes/count.rb

100.0% lines covered

6 relevant lines. 6 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 module Arel # :nodoc: all
  3. 3 module Nodes
  4. 3 class Count < Arel::Nodes::Function
  5. 3 def initialize(expr, distinct = false, aliaz = nil)
  6. 4017 super(expr, aliaz)
  7. 4017 @distinct = distinct
  8. end
  9. end
  10. end
  11. end

lib/arel/nodes/delete_statement.rb

100.0% lines covered

25 relevant lines. 25 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 module Arel # :nodoc: all
  3. 3 module Nodes
  4. 3 class DeleteStatement < Arel::Nodes::Node
  5. 3 attr_accessor :left, :right, :orders, :limit, :offset, :key
  6. 3 alias :relation :left
  7. 3 alias :relation= :left=
  8. 3 alias :wheres :right
  9. 3 alias :wheres= :right=
  10. 3 def initialize(relation = nil, wheres = [])
  11. 3381 super()
  12. 3381 @left = relation
  13. 3381 @right = wheres
  14. 3381 @orders = []
  15. 3381 @limit = nil
  16. 3381 @offset = nil
  17. 3381 @key = nil
  18. end
  19. 3 def initialize_copy(other)
  20. 99 super
  21. 99 @left = @left.clone if @left
  22. 99 @right = @right.clone if @right
  23. end
  24. 3 def hash
  25. 12 [self.class, @left, @right, @orders, @limit, @offset, @key].hash
  26. end
  27. 3 def eql?(other)
  28. 3 self.class == other.class &&
  29. self.left == other.left &&
  30. self.right == other.right &&
  31. self.orders == other.orders &&
  32. self.limit == other.limit &&
  33. self.offset == other.offset &&
  34. self.key == other.key
  35. end
  36. 3 alias :== :eql?
  37. end
  38. end
  39. end

lib/arel/nodes/descending.rb

100.0% lines covered

11 relevant lines. 11 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 module Arel # :nodoc: all
  3. 3 module Nodes
  4. 3 class Descending < Ordering
  5. 3 def reverse
  6. 21 Ascending.new(expr)
  7. end
  8. 3 def direction
  9. 3 :desc
  10. end
  11. 3 def ascending?
  12. 3 false
  13. end
  14. 3 def descending?
  15. 9 true
  16. end
  17. end
  18. end
  19. end

lib/arel/nodes/equality.rb

100.0% lines covered

7 relevant lines. 7 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 module Arel # :nodoc: all
  3. 3 module Nodes
  4. 3 class Equality < Arel::Nodes::Binary
  5. 3 include FetchAttribute
  6. 8351 def equality?; true; end
  7. 3 def invert
  8. 199 Arel::Nodes::NotEqual.new(left, right)
  9. end
  10. end
  11. end
  12. end

lib/arel/nodes/extract.rb

100.0% lines covered

12 relevant lines. 12 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 module Arel # :nodoc: all
  3. 3 module Nodes
  4. 3 class Extract < Arel::Nodes::Unary
  5. 3 attr_accessor :field
  6. 3 def initialize(expr, field)
  7. 21 super(expr)
  8. 21 @field = field
  9. end
  10. 3 def hash
  11. 12 super ^ @field.hash
  12. end
  13. 3 def eql?(other)
  14. 6 super &&
  15. self.field == other.field
  16. end
  17. 3 alias :== :eql?
  18. end
  19. end
  20. end

lib/arel/nodes/false.rb

100.0% lines covered

8 relevant lines. 8 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 module Arel # :nodoc: all
  3. 3 module Nodes
  4. 3 class False < Arel::Nodes::NodeExpression
  5. 3 def hash
  6. 9 self.class.hash
  7. end
  8. 3 def eql?(other)
  9. 3 self.class == other.class
  10. end
  11. 3 alias :== :eql?
  12. end
  13. end
  14. end

lib/arel/nodes/full_outer_join.rb

100.0% lines covered

3 relevant lines. 3 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 module Arel # :nodoc: all
  3. 3 module Nodes
  4. 3 class FullOuterJoin < Arel::Nodes::Join
  5. end
  6. end
  7. end

lib/arel/nodes/function.rb

100.0% lines covered

20 relevant lines. 20 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 module Arel # :nodoc: all
  3. 3 module Nodes
  4. 3 class Function < Arel::Nodes::NodeExpression
  5. 3 include Arel::WindowPredications
  6. 3 attr_accessor :expressions, :alias, :distinct
  7. 3 def initialize(expr, aliaz = nil)
  8. 5762 super()
  9. 5762 @expressions = expr
  10. 5762 @alias = aliaz && SqlLiteral.new(aliaz)
  11. 5762 @distinct = false
  12. end
  13. 3 def as(aliaz)
  14. 274 self.alias = SqlLiteral.new(aliaz)
  15. 274 self
  16. end
  17. 3 def hash
  18. 42 [@expressions, @alias, @distinct].hash
  19. end
  20. 3 def eql?(other)
  21. 9 self.class == other.class &&
  22. self.expressions == other.expressions &&
  23. self.alias == other.alias &&
  24. self.distinct == other.distinct
  25. end
  26. 3 alias :== :eql?
  27. end
  28. %w{
  29. Sum
  30. Exists
  31. Max
  32. Min
  33. Avg
  34. 3 }.each do |name|
  35. 15 const_set(name, Class.new(Function))
  36. end
  37. end
  38. end

lib/arel/nodes/grouping.rb

100.0% lines covered

5 relevant lines. 5 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 module Arel # :nodoc: all
  3. 3 module Nodes
  4. 3 class Grouping < Unary
  5. 3 def fetch_attribute(&block)
  6. 114 expr.fetch_attribute(&block)
  7. end
  8. end
  9. end
  10. end

lib/arel/nodes/homogeneous_in.rb

94.74% lines covered

38 relevant lines. 36 lines covered and 2 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 module Arel # :nodoc: all
  3. 3 module Nodes
  4. 3 class HomogeneousIn < Node
  5. 3 attr_reader :attribute, :values, :type
  6. 3 def initialize(values, attribute, type)
  7. 7823 @values = values
  8. 7823 @attribute = attribute
  9. 7823 @type = type
  10. end
  11. 3 def hash
  12. ivars.hash
  13. end
  14. 3 def eql?(other)
  15. 4189 super || (self.class == other.class && self.ivars == other.ivars)
  16. end
  17. 3 alias :== :eql?
  18. 3 def equality?
  19. 3903 type == :in
  20. end
  21. 3 def invert
  22. 21 Arel::Nodes::HomogeneousIn.new(values, attribute, type == :in ? :notin : :in)
  23. end
  24. 3 def left
  25. 686 attribute
  26. end
  27. 3 def right
  28. 343 attribute.quoted_array(values)
  29. end
  30. 3 def table_name
  31. 3975 attribute.relation.table_alias || attribute.relation.name
  32. end
  33. 3 def column_name
  34. 3975 attribute.name
  35. end
  36. 3 def casted_values
  37. 3975 type = attribute.type_caster
  38. 3975 casted_values = values.map do |raw_value|
  39. 1139484 type.serialize(raw_value) if type.serializable?(raw_value)
  40. end
  41. 3975 casted_values.compact!
  42. 3975 casted_values
  43. end
  44. 3 def fetch_attribute(&block)
  45. 6450 if attribute
  46. 6450 yield attribute
  47. else
  48. expr.fetch_attribute(&block)
  49. end
  50. end
  51. 3 protected
  52. 3 def ivars
  53. 210 [@attribute, @values, @type]
  54. end
  55. end
  56. end
  57. end

lib/arel/nodes/in.rb

100.0% lines covered

7 relevant lines. 7 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 module Arel # :nodoc: all
  3. 3 module Nodes
  4. 3 class In < Arel::Nodes::Binary
  5. 3 include FetchAttribute
  6. 3 def equality?; true; end
  7. 3 def invert
  8. 6 Arel::Nodes::NotIn.new(left, right)
  9. end
  10. end
  11. end
  12. end

lib/arel/nodes/infix_operation.rb

100.0% lines covered

48 relevant lines. 48 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 module Arel # :nodoc: all
  3. 3 module Nodes
  4. 3 class InfixOperation < Binary
  5. 3 include Arel::Expressions
  6. 3 include Arel::Predications
  7. 3 include Arel::OrderPredications
  8. 3 include Arel::AliasPredication
  9. 3 include Arel::Math
  10. 3 attr_reader :operator
  11. 3 def initialize(operator, left, right)
  12. 1274 super(left, right)
  13. 1274 @operator = operator
  14. end
  15. end
  16. 3 class Multiplication < InfixOperation
  17. 3 def initialize(left, right)
  18. 18 super(:*, left, right)
  19. end
  20. end
  21. 3 class Division < InfixOperation
  22. 3 def initialize(left, right)
  23. 18 super(:/, left, right)
  24. end
  25. end
  26. 3 class Addition < InfixOperation
  27. 3 def initialize(left, right)
  28. 844 super(:+, left, right)
  29. end
  30. end
  31. 3 class Subtraction < InfixOperation
  32. 3 def initialize(left, right)
  33. 241 super(:-, left, right)
  34. end
  35. end
  36. 3 class Concat < InfixOperation
  37. 3 def initialize(left, right)
  38. 9 super(:"||", left, right)
  39. end
  40. end
  41. 3 class Contains < InfixOperation
  42. 3 def initialize(left, right)
  43. 12 super(:"@>", left, right)
  44. end
  45. end
  46. 3 class Overlaps < InfixOperation
  47. 3 def initialize(left, right)
  48. 12 super(:"&&", left, right)
  49. end
  50. end
  51. 3 class BitwiseAnd < InfixOperation
  52. 3 def initialize(left, right)
  53. 18 super(:&, left, right)
  54. end
  55. end
  56. 3 class BitwiseOr < InfixOperation
  57. 3 def initialize(left, right)
  58. 18 super(:|, left, right)
  59. end
  60. end
  61. 3 class BitwiseXor < InfixOperation
  62. 3 def initialize(left, right)
  63. 18 super(:^, left, right)
  64. end
  65. end
  66. 3 class BitwiseShiftLeft < InfixOperation
  67. 3 def initialize(left, right)
  68. 18 super(:<<, left, right)
  69. end
  70. end
  71. 3 class BitwiseShiftRight < InfixOperation
  72. 3 def initialize(left, right)
  73. 18 super(:>>, left, right)
  74. end
  75. end
  76. end
  77. end

lib/arel/nodes/inner_join.rb

100.0% lines covered

3 relevant lines. 3 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 module Arel # :nodoc: all
  3. 3 module Nodes
  4. 3 class InnerJoin < Arel::Nodes::Join
  5. end
  6. end
  7. end

lib/arel/nodes/insert_statement.rb

100.0% lines covered

20 relevant lines. 20 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 module Arel # :nodoc: all
  3. 3 module Nodes
  4. 3 class InsertStatement < Arel::Nodes::Node
  5. 3 attr_accessor :relation, :columns, :values, :select
  6. 3 def initialize
  7. 165968 super()
  8. 165968 @relation = nil
  9. 165968 @columns = []
  10. 165968 @values = nil
  11. 165968 @select = nil
  12. end
  13. 3 def initialize_copy(other)
  14. 3 super
  15. 3 @columns = @columns.clone
  16. 3 @values = @values.clone if @values
  17. 3 @select = @select.clone if @select
  18. end
  19. 3 def hash
  20. 12 [@relation, @columns, @values, @select].hash
  21. end
  22. 3 def eql?(other)
  23. 3 self.class == other.class &&
  24. self.relation == other.relation &&
  25. self.columns == other.columns &&
  26. self.select == other.select &&
  27. self.values == other.values
  28. end
  29. 3 alias :== :eql?
  30. end
  31. end
  32. end

lib/arel/nodes/join_source.rb

100.0% lines covered

7 relevant lines. 7 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 module Arel # :nodoc: all
  3. 3 module Nodes
  4. ###
  5. # Class that represents a join source
  6. #
  7. # https://www.sqlite.org/syntaxdiagrams.html#join-source
  8. 3 class JoinSource < Arel::Nodes::Binary
  9. 3 def initialize(single_source, joinop = [])
  10. 45309 super
  11. end
  12. 3 def empty?
  13. 34921 !left && right.empty?
  14. end
  15. end
  16. end
  17. end

lib/arel/nodes/matches.rb

100.0% lines covered

10 relevant lines. 10 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 module Arel # :nodoc: all
  3. 3 module Nodes
  4. 3 class Matches < Binary
  5. 3 attr_reader :escape
  6. 3 attr_accessor :case_sensitive
  7. 3 def initialize(left, right, escape = nil, case_sensitive = false)
  8. 129 super(left, right)
  9. 129 @escape = escape && Nodes.build_quoted(escape)
  10. 129 @case_sensitive = case_sensitive
  11. end
  12. end
  13. 3 class DoesNotMatch < Matches; end
  14. end
  15. end

lib/arel/nodes/named_function.rb

100.0% lines covered

12 relevant lines. 12 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 module Arel # :nodoc: all
  3. 3 module Nodes
  4. 3 class NamedFunction < Arel::Nodes::Function
  5. 3 attr_accessor :name
  6. 3 def initialize(name, expr, aliaz = nil)
  7. 1206 super(expr, aliaz)
  8. 1206 @name = name
  9. end
  10. 3 def hash
  11. 18 super ^ @name.hash
  12. end
  13. 3 def eql?(other)
  14. 3 super && self.name == other.name
  15. end
  16. 3 alias :== :eql?
  17. end
  18. end
  19. end

lib/arel/nodes/node.rb

100.0% lines covered

18 relevant lines. 18 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 module Arel # :nodoc: all
  3. 3 module Nodes
  4. ###
  5. # Abstract base class for all AST nodes
  6. 3 class Node
  7. 3 include Arel::FactoryMethods
  8. ###
  9. # Factory method to create a Nodes::Not node that has the recipient of
  10. # the caller as a child.
  11. 3 def not
  12. 3 Nodes::Not.new self
  13. end
  14. ###
  15. # Factory method to create a Nodes::Grouping node that has an Nodes::Or
  16. # node as a child.
  17. 3 def or(right)
  18. 84 Nodes::Grouping.new Nodes::Or.new(self, right)
  19. end
  20. ###
  21. # Factory method to create an Nodes::And node.
  22. 3 def and(right)
  23. 511 Nodes::And.new [self, right]
  24. end
  25. 3 def invert
  26. 3 Arel::Nodes::Not.new(self)
  27. end
  28. # FIXME: this method should go away. I don't like people calling
  29. # to_sql on non-head nodes. This forces us to walk the AST until we
  30. # can find a node that has a "relation" member.
  31. #
  32. # Maybe we should just use `Table.engine`? :'(
  33. 3 def to_sql(engine = Table.engine)
  34. 469 collector = Arel::Collectors::SQLString.new
  35. 469 collector = engine.connection.visitor.accept self, collector
  36. 469 collector.value
  37. end
  38. 3 def fetch_attribute
  39. end
  40. 75 def equality?; false; end
  41. end
  42. end
  43. end

lib/arel/nodes/node_expression.rb

100.0% lines covered

8 relevant lines. 8 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 module Arel # :nodoc: all
  3. 3 module Nodes
  4. 3 class NodeExpression < Arel::Nodes::Node
  5. 3 include Arel::Expressions
  6. 3 include Arel::Predications
  7. 3 include Arel::AliasPredication
  8. 3 include Arel::OrderPredications
  9. 3 include Arel::Math
  10. end
  11. end
  12. end

lib/arel/nodes/ordering.rb

100.0% lines covered

9 relevant lines. 9 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 module Arel # :nodoc: all
  3. 3 module Nodes
  4. 3 class Ordering < Unary
  5. 3 def nulls_first
  6. 3 NullsFirst.new(self)
  7. end
  8. 3 def nulls_last
  9. 3 NullsLast.new(self)
  10. end
  11. end
  12. 3 class NullsFirst < Ordering; end
  13. 3 class NullsLast < Ordering; end
  14. end
  15. end

lib/arel/nodes/outer_join.rb

100.0% lines covered

3 relevant lines. 3 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 module Arel # :nodoc: all
  3. 3 module Nodes
  4. 3 class OuterJoin < Arel::Nodes::Join
  5. end
  6. end
  7. end

lib/arel/nodes/over.rb

100.0% lines covered

7 relevant lines. 7 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 module Arel # :nodoc: all
  3. 3 module Nodes
  4. 3 class Over < Binary
  5. 3 include Arel::AliasPredication
  6. 3 def initialize(left, right = nil)
  7. 27 super(left, right)
  8. end
  9. 3 def operator; "OVER" end
  10. end
  11. end
  12. end

lib/arel/nodes/regexp.rb

100.0% lines covered

8 relevant lines. 8 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 module Arel # :nodoc: all
  3. 3 module Nodes
  4. 3 class Regexp < Binary
  5. 3 attr_accessor :case_sensitive
  6. 3 def initialize(left, right, case_sensitive = true)
  7. 36 super(left, right)
  8. 36 @case_sensitive = case_sensitive
  9. end
  10. end
  11. 3 class NotRegexp < Regexp; end
  12. end
  13. end

lib/arel/nodes/right_outer_join.rb

100.0% lines covered

3 relevant lines. 3 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 module Arel # :nodoc: all
  3. 3 module Nodes
  4. 3 class RightOuterJoin < Arel::Nodes::Join
  5. end
  6. end
  7. end

lib/arel/nodes/select_core.rb

100.0% lines covered

35 relevant lines. 35 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 module Arel # :nodoc: all
  3. 3 module Nodes
  4. 3 class SelectCore < Arel::Nodes::Node
  5. 3 attr_accessor :projections, :wheres, :groups, :windows, :comment
  6. 3 attr_accessor :havings, :source, :set_quantifier, :optimizer_hints
  7. 3 def initialize
  8. 45303 super()
  9. 45303 @source = JoinSource.new nil
  10. # https://ronsavage.github.io/SQL/sql-92.bnf.html#set%20quantifier
  11. 45303 @set_quantifier = nil
  12. 45303 @optimizer_hints = nil
  13. 45303 @projections = []
  14. 45303 @wheres = []
  15. 45303 @groups = []
  16. 45303 @havings = []
  17. 45303 @windows = []
  18. 45303 @comment = nil
  19. end
  20. 3 def from
  21. 45 @source.left
  22. end
  23. 3 def from=(value)
  24. 207 @source.left = value
  25. end
  26. 3 alias :froms= :from=
  27. 3 alias :froms :from
  28. 3 def initialize_copy(other)
  29. 12 super
  30. 12 @source = @source.clone if @source
  31. 12 @projections = @projections.clone
  32. 12 @wheres = @wheres.clone
  33. 12 @groups = @groups.clone
  34. 12 @havings = @havings.clone
  35. 12 @windows = @windows.clone
  36. end
  37. 3 def hash
  38. [
  39. @source, @set_quantifier, @projections, @optimizer_hints,
  40. @wheres, @groups, @havings, @windows, @comment
  41. 18 ].hash
  42. end
  43. 3 def eql?(other)
  44. 3 self.class == other.class &&
  45. self.source == other.source &&
  46. self.set_quantifier == other.set_quantifier &&
  47. self.optimizer_hints == other.optimizer_hints &&
  48. self.projections == other.projections &&
  49. self.wheres == other.wheres &&
  50. self.groups == other.groups &&
  51. self.havings == other.havings &&
  52. self.windows == other.windows &&
  53. self.comment == other.comment
  54. end
  55. 3 alias :== :eql?
  56. end
  57. end
  58. end

lib/arel/nodes/select_statement.rb

100.0% lines covered

22 relevant lines. 22 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 module Arel # :nodoc: all
  3. 3 module Nodes
  4. 3 class SelectStatement < Arel::Nodes::NodeExpression
  5. 3 attr_reader :cores
  6. 3 attr_accessor :limit, :orders, :lock, :offset, :with
  7. 3 def initialize(cores = [SelectCore.new])
  8. 45291 super()
  9. 45291 @cores = cores
  10. 45291 @orders = []
  11. 45291 @limit = nil
  12. 45291 @lock = nil
  13. 45291 @offset = nil
  14. 45291 @with = nil
  15. end
  16. 3 def initialize_copy(other)
  17. 12 super
  18. 30 @cores = @cores.map { |x| x.clone }
  19. 12 @orders = @orders.map { |x| x.clone }
  20. end
  21. 3 def hash
  22. 12 [@cores, @orders, @limit, @lock, @offset, @with].hash
  23. end
  24. 3 def eql?(other)
  25. 12 self.class == other.class &&
  26. self.cores == other.cores &&
  27. self.orders == other.orders &&
  28. self.limit == other.limit &&
  29. self.lock == other.lock &&
  30. self.offset == other.offset &&
  31. self.with == other.with
  32. end
  33. 3 alias :== :eql?
  34. end
  35. end
  36. end

lib/arel/nodes/sql_literal.rb

100.0% lines covered

10 relevant lines. 10 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 module Arel # :nodoc: all
  3. 3 module Nodes
  4. 3 class SqlLiteral < String
  5. 3 include Arel::Expressions
  6. 3 include Arel::Predications
  7. 3 include Arel::AliasPredication
  8. 3 include Arel::OrderPredications
  9. 3 def encode_with(coder)
  10. 3 coder.scalar = self.to_s
  11. end
  12. 3 def fetch_attribute
  13. end
  14. end
  15. end
  16. end

lib/arel/nodes/string_join.rb

100.0% lines covered

5 relevant lines. 5 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 module Arel # :nodoc: all
  3. 3 module Nodes
  4. 3 class StringJoin < Arel::Nodes::Join
  5. 3 def initialize(left, right = nil)
  6. 105 super
  7. end
  8. end
  9. end
  10. end

lib/arel/nodes/table_alias.rb

93.75% lines covered

16 relevant lines. 15 lines covered and 1 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 module Arel # :nodoc: all
  3. 3 module Nodes
  4. 3 class TableAlias < Arel::Nodes::Binary
  5. 3 alias :name :right
  6. 3 alias :relation :left
  7. 3 alias :table_alias :name
  8. 3 def [](name)
  9. 2508 relation.is_a?(Table) ? relation[name, self] : Attribute.new(self, name)
  10. end
  11. 3 def table_name
  12. 6 relation.respond_to?(:name) ? relation.name : name
  13. end
  14. 3 def type_cast_for_database(attr_name, value)
  15. relation.type_cast_for_database(attr_name, value)
  16. end
  17. 3 def type_for_attribute(name)
  18. 171 relation.type_for_attribute(name)
  19. end
  20. 3 def able_to_type_cast?
  21. 6 relation.respond_to?(:able_to_type_cast?) && relation.able_to_type_cast?
  22. end
  23. end
  24. end
  25. end

lib/arel/nodes/terminal.rb

100.0% lines covered

8 relevant lines. 8 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 module Arel # :nodoc: all
  3. 3 module Nodes
  4. 3 class Distinct < Arel::Nodes::NodeExpression
  5. 3 def hash
  6. 9 self.class.hash
  7. end
  8. 3 def eql?(other)
  9. 3 self.class == other.class
  10. end
  11. 3 alias :== :eql?
  12. end
  13. end
  14. end

lib/arel/nodes/true.rb

100.0% lines covered

8 relevant lines. 8 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 module Arel # :nodoc: all
  3. 3 module Nodes
  4. 3 class True < Arel::Nodes::NodeExpression
  5. 3 def hash
  6. 9 self.class.hash
  7. end
  8. 3 def eql?(other)
  9. 3 self.class == other.class
  10. end
  11. 3 alias :== :eql?
  12. end
  13. end
  14. end

lib/arel/nodes/unary.rb

100.0% lines covered

15 relevant lines. 15 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 module Arel # :nodoc: all
  3. 3 module Nodes
  4. 3 class Unary < Arel::Nodes::NodeExpression
  5. 3 attr_accessor :expr
  6. 3 alias :value :expr
  7. 3 def initialize(expr)
  8. 217719 super()
  9. 217719 @expr = expr
  10. end
  11. 3 def hash
  12. 144 @expr.hash
  13. end
  14. 3 def eql?(other)
  15. 18577 self.class == other.class &&
  16. self.expr == other.expr
  17. end
  18. 3 alias :== :eql?
  19. end
  20. %w{
  21. Bin
  22. Cube
  23. DistinctOn
  24. Group
  25. GroupingElement
  26. GroupingSet
  27. Lateral
  28. Limit
  29. Lock
  30. Not
  31. Offset
  32. On
  33. OptimizerHints
  34. RollUp
  35. 3 }.each do |name|
  36. 42 const_set(name, Class.new(Unary))
  37. end
  38. end
  39. end

lib/arel/nodes/unary_operation.rb

100.0% lines covered

10 relevant lines. 10 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 module Arel # :nodoc: all
  3. 3 module Nodes
  4. 3 class UnaryOperation < Unary
  5. 3 attr_reader :operator
  6. 3 def initialize(operator, operand)
  7. 27 super(operand)
  8. 27 @operator = operator
  9. end
  10. end
  11. 3 class BitwiseNot < UnaryOperation
  12. 3 def initialize(operand)
  13. 3 super(:~, operand)
  14. end
  15. end
  16. end
  17. end

lib/arel/nodes/unqualified_column.rb

81.82% lines covered

11 relevant lines. 9 lines covered and 2 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 module Arel # :nodoc: all
  3. 3 module Nodes
  4. 3 class UnqualifiedColumn < Arel::Nodes::Unary
  5. 3 alias :attribute :expr
  6. 3 alias :attribute= :expr=
  7. 3 def relation
  8. @expr.relation
  9. end
  10. 3 def column
  11. @expr.column
  12. end
  13. 3 def name
  14. 7183 @expr.name
  15. end
  16. end
  17. end
  18. end

lib/arel/nodes/update_statement.rb

100.0% lines covered

21 relevant lines. 21 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 module Arel # :nodoc: all
  3. 3 module Nodes
  4. 3 class UpdateStatement < Arel::Nodes::Node
  5. 3 attr_accessor :relation, :wheres, :values, :orders, :limit, :offset, :key
  6. 3 def initialize
  7. 4204 @relation = nil
  8. 4204 @wheres = []
  9. 4204 @values = []
  10. 4204 @orders = []
  11. 4204 @limit = nil
  12. 4204 @offset = nil
  13. 4204 @key = nil
  14. end
  15. 3 def initialize_copy(other)
  16. 93 super
  17. 93 @wheres = @wheres.clone
  18. 93 @values = @values.clone
  19. end
  20. 3 def hash
  21. 12 [@relation, @wheres, @values, @orders, @limit, @offset, @key].hash
  22. end
  23. 3 def eql?(other)
  24. 3 self.class == other.class &&
  25. self.relation == other.relation &&
  26. self.wheres == other.wheres &&
  27. self.values == other.values &&
  28. self.orders == other.orders &&
  29. self.limit == other.limit &&
  30. self.offset == other.offset &&
  31. self.key == other.key
  32. end
  33. 3 alias :== :eql?
  34. end
  35. end
  36. end

lib/arel/nodes/values_list.rb

100.0% lines covered

4 relevant lines. 4 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 module Arel # :nodoc: all
  3. 3 module Nodes
  4. 3 class ValuesList < Unary
  5. 3 alias :rows :expr
  6. end
  7. end
  8. end

lib/arel/nodes/window.rb

90.77% lines covered

65 relevant lines. 59 lines covered and 6 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 module Arel # :nodoc: all
  3. 3 module Nodes
  4. 3 class Window < Arel::Nodes::Node
  5. 3 attr_accessor :orders, :framing, :partitions
  6. 3 def initialize
  7. 81 @orders = []
  8. 81 @partitions = []
  9. 81 @framing = nil
  10. end
  11. 3 def order(*expr)
  12. # FIXME: We SHOULD NOT be converting these to SqlLiteral automatically
  13. 12 @orders.concat expr.map { |x|
  14. 15 String === x || Symbol === x ? Nodes::SqlLiteral.new(x.to_s) : x
  15. }
  16. 12 self
  17. end
  18. 3 def partition(*expr)
  19. # FIXME: We SHOULD NOT be converting these to SqlLiteral automatically
  20. 9 @partitions.concat expr.map { |x|
  21. 12 String === x || Symbol === x ? Nodes::SqlLiteral.new(x.to_s) : x
  22. }
  23. 9 self
  24. end
  25. 3 def frame(expr)
  26. 66 @framing = expr
  27. end
  28. 3 def rows(expr = nil)
  29. 18 if @framing
  30. Rows.new(expr)
  31. else
  32. 18 frame(Rows.new(expr))
  33. end
  34. end
  35. 3 def range(expr = nil)
  36. 18 if @framing
  37. Range.new(expr)
  38. else
  39. 18 frame(Range.new(expr))
  40. end
  41. end
  42. 3 def initialize_copy(other)
  43. super
  44. @orders = @orders.map { |x| x.clone }
  45. end
  46. 3 def hash
  47. 24 [@orders, @framing].hash
  48. end
  49. 3 def eql?(other)
  50. 6 self.class == other.class &&
  51. self.orders == other.orders &&
  52. self.framing == other.framing &&
  53. self.partitions == other.partitions
  54. end
  55. 3 alias :== :eql?
  56. end
  57. 3 class NamedWindow < Window
  58. 3 attr_accessor :name
  59. 3 def initialize(name)
  60. 66 super()
  61. 66 @name = name
  62. end
  63. 3 def initialize_copy(other)
  64. super
  65. @name = other.name.clone
  66. end
  67. 3 def hash
  68. 12 super ^ @name.hash
  69. end
  70. 3 def eql?(other)
  71. 3 super && self.name == other.name
  72. end
  73. 3 alias :== :eql?
  74. end
  75. 3 class Rows < Unary
  76. 3 def initialize(expr = nil)
  77. 18 super(expr)
  78. end
  79. end
  80. 3 class Range < Unary
  81. 3 def initialize(expr = nil)
  82. 18 super(expr)
  83. end
  84. end
  85. 3 class CurrentRow < Node
  86. 3 def hash
  87. 9 self.class.hash
  88. end
  89. 3 def eql?(other)
  90. 3 self.class == other.class
  91. end
  92. 3 alias :== :eql?
  93. end
  94. 3 class Preceding < Unary
  95. 3 def initialize(expr = nil)
  96. 18 super(expr)
  97. end
  98. end
  99. 3 class Following < Unary
  100. 3 def initialize(expr = nil)
  101. 12 super(expr)
  102. end
  103. end
  104. end
  105. end

lib/arel/nodes/with.rb

100.0% lines covered

5 relevant lines. 5 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 module Arel # :nodoc: all
  3. 3 module Nodes
  4. 3 class With < Arel::Nodes::Unary
  5. 3 alias children expr
  6. end
  7. 3 class WithRecursive < With; end
  8. end
  9. end

lib/arel/order_predications.rb

100.0% lines covered

6 relevant lines. 6 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 module Arel # :nodoc: all
  3. 3 module OrderPredications
  4. 3 def asc
  5. 4799 Nodes::Ascending.new self
  6. end
  7. 3 def desc
  8. 187 Nodes::Descending.new self
  9. end
  10. end
  11. end

lib/arel/predications.rb

99.25% lines covered

134 relevant lines. 133 lines covered and 1 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 module Arel # :nodoc: all
  3. 3 module Predications
  4. 3 def not_eq(other)
  5. 45 Nodes::NotEqual.new self, quoted_node(other)
  6. end
  7. 3 def not_eq_any(others)
  8. 6 grouping_any :not_eq, others
  9. end
  10. 3 def not_eq_all(others)
  11. 6 grouping_all :not_eq, others
  12. end
  13. 3 def eq(other)
  14. 59717 Nodes::Equality.new self, quoted_node(other)
  15. end
  16. 3 def is_not_distinct_from(other)
  17. 30 Nodes::IsNotDistinctFrom.new self, quoted_node(other)
  18. end
  19. 3 def is_distinct_from(other)
  20. 27 Nodes::IsDistinctFrom.new self, quoted_node(other)
  21. end
  22. 3 def eq_any(others)
  23. 12 grouping_any :eq, others
  24. end
  25. 3 def eq_all(others)
  26. 18 grouping_all :eq, quoted_array(others)
  27. end
  28. 3 def between(other)
  29. 190 if unboundable?(other.begin) == 1 || unboundable?(other.end) == -1
  30. 9 self.in([])
  31. 181 elsif open_ended?(other.begin)
  32. 49 if open_ended?(other.end)
  33. 19 not_in([])
  34. 30 elsif other.exclude_end?
  35. 12 lt(other.end)
  36. else
  37. 18 lteq(other.end)
  38. end
  39. 132 elsif open_ended?(other.end)
  40. 24 gteq(other.begin)
  41. 108 elsif other.exclude_end?
  42. 15 gteq(other.begin).and(lt(other.end))
  43. else
  44. 93 left = quoted_node(other.begin)
  45. 93 right = quoted_node(other.end)
  46. 93 Nodes::Between.new(self, left.and(right))
  47. end
  48. end
  49. 3 def in(other)
  50. 386 case other
  51. when Arel::SelectManager
  52. 123 Arel::Nodes::In.new(self, other.ast)
  53. when Enumerable
  54. 245 Nodes::In.new self, quoted_array(other)
  55. else
  56. 18 Nodes::In.new self, quoted_node(other)
  57. end
  58. end
  59. 3 def in_any(others)
  60. 6 grouping_any :in, others
  61. end
  62. 3 def in_all(others)
  63. 6 grouping_all :in, others
  64. end
  65. 3 def not_between(other)
  66. 48 if unboundable?(other.begin) == 1 || unboundable?(other.end) == -1
  67. not_in([])
  68. 48 elsif open_ended?(other.begin)
  69. 27 if open_ended?(other.end)
  70. 9 self.in([])
  71. 18 elsif other.exclude_end?
  72. 9 gteq(other.end)
  73. else
  74. 9 gt(other.end)
  75. end
  76. 21 elsif open_ended?(other.end)
  77. 9 lt(other.begin)
  78. else
  79. 12 left = lt(other.begin)
  80. 12 right = if other.exclude_end?
  81. 6 gteq(other.end)
  82. else
  83. 6 gt(other.end)
  84. end
  85. 12 left.or(right)
  86. end
  87. end
  88. 3 def not_in(other)
  89. 70 case other
  90. when Arel::SelectManager
  91. 6 Arel::Nodes::NotIn.new(self, other.ast)
  92. when Enumerable
  93. 49 Nodes::NotIn.new self, quoted_array(other)
  94. else
  95. 15 Nodes::NotIn.new self, quoted_node(other)
  96. end
  97. end
  98. 3 def not_in_any(others)
  99. 6 grouping_any :not_in, others
  100. end
  101. 3 def not_in_all(others)
  102. 6 grouping_all :not_in, others
  103. end
  104. 3 def matches(other, escape = nil, case_sensitive = false)
  105. 60 Nodes::Matches.new self, quoted_node(other), escape, case_sensitive
  106. end
  107. 3 def matches_regexp(other, case_sensitive = true)
  108. 15 Nodes::Regexp.new self, quoted_node(other), case_sensitive
  109. end
  110. 3 def matches_any(others, escape = nil, case_sensitive = false)
  111. 6 grouping_any :matches, others, escape, case_sensitive
  112. end
  113. 3 def matches_all(others, escape = nil, case_sensitive = false)
  114. 6 grouping_all :matches, others, escape, case_sensitive
  115. end
  116. 3 def does_not_match(other, escape = nil, case_sensitive = false)
  117. 63 Nodes::DoesNotMatch.new self, quoted_node(other), escape, case_sensitive
  118. end
  119. 3 def does_not_match_regexp(other, case_sensitive = true)
  120. 15 Nodes::NotRegexp.new self, quoted_node(other), case_sensitive
  121. end
  122. 3 def does_not_match_any(others, escape = nil)
  123. 6 grouping_any :does_not_match, others, escape
  124. end
  125. 3 def does_not_match_all(others, escape = nil)
  126. 12 grouping_all :does_not_match, others, escape
  127. end
  128. 3 def gteq(right)
  129. 129 Nodes::GreaterThanOrEqual.new self, quoted_node(right)
  130. end
  131. 3 def gteq_any(others)
  132. 6 grouping_any :gteq, others
  133. end
  134. 3 def gteq_all(others)
  135. 6 grouping_all :gteq, others
  136. end
  137. 3 def gt(right)
  138. 864 Nodes::GreaterThan.new self, quoted_node(right)
  139. end
  140. 3 def gt_any(others)
  141. 6 grouping_any :gt, others
  142. end
  143. 3 def gt_all(others)
  144. 6 grouping_all :gt, others
  145. end
  146. 3 def lt(right)
  147. 165 Nodes::LessThan.new self, quoted_node(right)
  148. end
  149. 3 def lt_any(others)
  150. 6 grouping_any :lt, others
  151. end
  152. 3 def lt_all(others)
  153. 6 grouping_all :lt, others
  154. end
  155. 3 def lteq(right)
  156. 75 Nodes::LessThanOrEqual.new self, quoted_node(right)
  157. end
  158. 3 def lteq_any(others)
  159. 6 grouping_any :lteq, others
  160. end
  161. 3 def lteq_all(others)
  162. 6 grouping_all :lteq, others
  163. end
  164. 3 def when(right)
  165. 3 Nodes::Case.new(self).when quoted_node(right)
  166. end
  167. 3 def concat(other)
  168. 9 Nodes::Concat.new self, other
  169. end
  170. 3 def contains(other)
  171. 9 Arel::Nodes::Contains.new(self, other)
  172. end
  173. 3 def overlaps(other)
  174. 9 Arel::Nodes::Overlaps.new(self, other)
  175. end
  176. 3 def quoted_array(others)
  177. 2190 others.map { |v| quoted_node(v) }
  178. end
  179. 3 private
  180. 3 def grouping_any(method_id, others, *extras)
  181. 198 nodes = others.map { |expr| send(method_id, expr, *extras) }
  182. 66 Nodes::Grouping.new nodes.inject { |memo, node|
  183. 66 Nodes::Or.new(memo, node)
  184. }
  185. end
  186. 3 def grouping_all(method_id, others, *extras)
  187. 234 nodes = others.map { |expr| send(method_id, expr, *extras) }
  188. 78 Nodes::Grouping.new Nodes::And.new(nodes)
  189. end
  190. 3 def quoted_node(other)
  191. 62962 Nodes.build_quoted(other, self)
  192. end
  193. 3 def infinity?(value)
  194. 458 value.respond_to?(:infinite?) && value.infinite?
  195. end
  196. 3 def unboundable?(value)
  197. 806 value.respond_to?(:unboundable?) && value.unboundable?
  198. end
  199. 3 def open_ended?(value)
  200. 458 value.nil? || infinity?(value) || unboundable?(value)
  201. end
  202. end
  203. end

lib/arel/select_manager.rb

99.31% lines covered

144 relevant lines. 143 lines covered and 1 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 module Arel # :nodoc: all
  3. 3 class SelectManager < Arel::TreeManager
  4. 3 include Arel::Crud
  5. 3 STRING_OR_SYMBOL_CLASS = [Symbol, String]
  6. 3 def initialize(table = nil)
  7. 45075 super()
  8. 45075 @ast = Nodes::SelectStatement.new
  9. 45075 @ctx = @ast.cores.last
  10. 45075 from table
  11. end
  12. 3 def initialize_copy(other)
  13. 9 super
  14. 9 @ctx = @ast.cores.last
  15. end
  16. 3 def limit
  17. 3454 @ast.limit && @ast.limit.expr
  18. end
  19. 3 alias :taken :limit
  20. 3 def constraints
  21. 6590 @ctx.wheres
  22. end
  23. 3 def offset
  24. 3454 @ast.offset && @ast.offset.expr
  25. end
  26. 3 def skip(amount)
  27. 432 if amount
  28. 429 @ast.offset = Nodes::Offset.new(amount)
  29. else
  30. 3 @ast.offset = nil
  31. end
  32. 432 self
  33. end
  34. 3 alias :offset= :skip
  35. ###
  36. # Produces an Arel::Nodes::Exists node
  37. 3 def exists
  38. 6 Arel::Nodes::Exists.new @ast
  39. end
  40. 3 def as(other)
  41. 354 create_table_alias grouping(@ast), Nodes::SqlLiteral.new(other)
  42. end
  43. 3 def lock(locking = Arel.sql("FOR UPDATE"))
  44. 35 case locking
  45. when true
  46. 31 locking = Arel.sql("FOR UPDATE")
  47. when Arel::Nodes::SqlLiteral
  48. when String
  49. 1 locking = Arel.sql locking
  50. end
  51. 35 @ast.lock = Nodes::Lock.new(locking)
  52. 35 self
  53. end
  54. 3 def locked
  55. 262 @ast.lock
  56. end
  57. 3 def on(*exprs)
  58. 42 @ctx.source.right.last.right = Nodes::On.new(collapse(exprs))
  59. 42 self
  60. end
  61. 3 def group(*columns)
  62. 382 columns.each do |column|
  63. # FIXME: backwards compat
  64. 409 column = Nodes::SqlLiteral.new(column) if String === column
  65. 409 column = Nodes::SqlLiteral.new(column.to_s) if Symbol === column
  66. 409 @ctx.groups.push Nodes::Group.new column
  67. end
  68. 382 self
  69. end
  70. 3 def from(table)
  71. 45408 table = Nodes::SqlLiteral.new(table) if String === table
  72. 45408 case table
  73. when Nodes::Join
  74. 9 @ctx.source.right << table
  75. else
  76. 45399 @ctx.source.left = table
  77. end
  78. 45408 self
  79. end
  80. 3 def froms
  81. 6 @ast.cores.map { |x| x.from }.compact
  82. end
  83. 3 def join(relation, klass = Nodes::InnerJoin)
  84. 51 return self unless relation
  85. 45 case relation
  86. when String, Nodes::SqlLiteral
  87. 3 raise EmptyJoinError if relation.empty?
  88. klass = Nodes::StringJoin
  89. end
  90. 42 @ctx.source.right << create_join(relation, nil, klass)
  91. 42 self
  92. end
  93. 3 def outer_join(relation)
  94. 6 join(relation, Nodes::OuterJoin)
  95. end
  96. 3 def having(expr)
  97. 56 @ctx.havings << expr
  98. 56 self
  99. end
  100. 3 def window(name)
  101. 54 window = Nodes::NamedWindow.new(name)
  102. 54 @ctx.windows.push window
  103. 54 window
  104. end
  105. 3 def project(*projections)
  106. # FIXME: converting these to SQLLiterals is probably not good, but
  107. # rails tests require it.
  108. 41966 @ctx.projections.concat projections.map { |x|
  109. 72611 STRING_OR_SYMBOL_CLASS.include?(x.class) ? Nodes::SqlLiteral.new(x.to_s) : x
  110. }
  111. 41966 self
  112. end
  113. 3 def projections
  114. 3 @ctx.projections
  115. end
  116. 3 def projections=(projections)
  117. 3 @ctx.projections = projections
  118. end
  119. 3 def optimizer_hints(*hints)
  120. 9 unless hints.empty?
  121. 9 @ctx.optimizer_hints = Arel::Nodes::OptimizerHints.new(hints)
  122. end
  123. 9 self
  124. end
  125. 3 def distinct(value = true)
  126. 41345 if value
  127. 565 @ctx.set_quantifier = Arel::Nodes::Distinct.new
  128. else
  129. 40780 @ctx.set_quantifier = nil
  130. end
  131. 41345 self
  132. end
  133. 3 def distinct_on(value)
  134. 12 if value
  135. 6 @ctx.set_quantifier = Arel::Nodes::DistinctOn.new(value)
  136. else
  137. 6 @ctx.set_quantifier = nil
  138. end
  139. 12 self
  140. end
  141. 3 def order(*expr)
  142. # FIXME: We SHOULD NOT be converting these to SqlLiteral automatically
  143. 9078 @ast.orders.concat expr.map { |x|
  144. 9309 STRING_OR_SYMBOL_CLASS.include?(x.class) ? Nodes::SqlLiteral.new(x.to_s) : x
  145. }
  146. 9078 self
  147. end
  148. 3 def orders
  149. 3694 @ast.orders
  150. end
  151. 3 def where_sql(engine = Table.engine)
  152. 114 return if @ctx.wheres.empty?
  153. 111 Nodes::SqlLiteral.new("WHERE #{Nodes::And.new(@ctx.wheres).to_sql(engine)}")
  154. end
  155. 3 def union(operation, other = nil)
  156. 12 if other
  157. 3 node_class = Nodes.const_get("Union#{operation.to_s.capitalize}")
  158. else
  159. 9 other = operation
  160. 9 node_class = Nodes::Union
  161. end
  162. 12 node_class.new self.ast, other.ast
  163. end
  164. 3 def intersect(other)
  165. 3 Nodes::Intersect.new ast, other.ast
  166. end
  167. 3 def except(other)
  168. 3 Nodes::Except.new ast, other.ast
  169. end
  170. 3 alias :minus :except
  171. 3 def lateral(table_name = nil)
  172. 6 base = table_name.nil? ? ast : as(table_name)
  173. 6 Nodes::Lateral.new(base)
  174. end
  175. 3 def with(*subqueries)
  176. 12 if subqueries.first.is_a? Symbol
  177. 6 node_class = Nodes.const_get("With#{subqueries.shift.to_s.capitalize}")
  178. else
  179. 6 node_class = Nodes::With
  180. end
  181. 12 @ast.with = node_class.new(subqueries.flatten)
  182. 12 self
  183. end
  184. 3 def take(limit)
  185. 20285 if limit
  186. 20282 @ast.limit = Nodes::Limit.new(limit)
  187. else
  188. 3 @ast.limit = nil
  189. end
  190. 20285 self
  191. end
  192. 3 alias limit= take
  193. 3 def join_sources
  194. 10529 @ctx.source.right
  195. end
  196. 3 def source
  197. 45 @ctx.source
  198. end
  199. 3 def comment(*values)
  200. 96 @ctx.comment = Nodes::Comment.new(values)
  201. 96 self
  202. end
  203. 3 private
  204. 3 def collapse(exprs)
  205. 42 exprs = exprs.compact
  206. 42 exprs.map! { |expr|
  207. 54 if String === expr
  208. # FIXME: Don't do this automatically
  209. 9 Arel.sql(expr)
  210. else
  211. 45 expr
  212. end
  213. }
  214. 42 if exprs.length == 1
  215. 33 exprs.first
  216. else
  217. 9 create_and exprs
  218. end
  219. end
  220. end
  221. end

lib/arel/table.rb

98.31% lines covered

59 relevant lines. 58 lines covered and 1 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 module Arel # :nodoc: all
  3. 3 class Table
  4. 3 include Arel::Crud
  5. 3 include Arel::FactoryMethods
  6. 3 include Arel::AliasPredication
  7. 3 @engine = nil
  8. 6 class << self; attr_accessor :engine; end
  9. 3 attr_accessor :name, :table_alias
  10. # TableAlias and Table both have a #table_name which is the name of the underlying table
  11. 3 alias :table_name :name
  12. 3 def initialize(name, as: nil, klass: nil, type_caster: klass&.type_caster)
  13. 158436 @name = name.to_s
  14. 158436 @klass = klass
  15. 158436 @type_caster = type_caster
  16. # Sometime AR sends an :as parameter to table, to let the table know
  17. # that it is an Alias. We may want to override new, and return a
  18. # TableAlias node?
  19. 158436 if as.to_s == @name
  20. 6 as = nil
  21. end
  22. 158436 @table_alias = as
  23. end
  24. 3 def alias(name = "#{self.name}_2")
  25. 915 Nodes::TableAlias.new(self, name)
  26. end
  27. 3 def from
  28. 3106 SelectManager.new(self)
  29. end
  30. 3 def join(relation, klass = Nodes::InnerJoin)
  31. 18 return from unless relation
  32. 15 case relation
  33. when String, Nodes::SqlLiteral
  34. 3 raise EmptyJoinError if relation.empty?
  35. klass = Nodes::StringJoin
  36. end
  37. 12 from.join(relation, klass)
  38. end
  39. 3 def outer_join(relation)
  40. 3 join(relation, Nodes::OuterJoin)
  41. end
  42. 3 def group(*columns)
  43. 3 from.group(*columns)
  44. end
  45. 3 def order(*expr)
  46. 6 from.order(*expr)
  47. end
  48. 3 def where(condition)
  49. 2797 from.where condition
  50. end
  51. 3 def project(*things)
  52. 228 from.project(*things)
  53. end
  54. 3 def take(amount)
  55. 3 from.take amount
  56. end
  57. 3 def skip(amount)
  58. 3 from.skip amount
  59. end
  60. 3 def having(expr)
  61. 3 from.having expr
  62. end
  63. 3 def [](name, table = self)
  64. 575647 name = name.to_s if name.is_a?(Symbol)
  65. 575647 name = @klass.attribute_aliases[name] || name if @klass
  66. 575647 Attribute.new(table, name)
  67. end
  68. 3 def hash
  69. # Perf note: aliases and table alias is excluded from the hash
  70. # aliases can have a loop back to this table breaking hashes in parent
  71. # relations, for the vast majority of cases @name is unique to a query
  72. 25241 @name.hash
  73. end
  74. 3 def eql?(other)
  75. 13497 self.class == other.class &&
  76. self.name == other.name &&
  77. self.table_alias == other.table_alias
  78. end
  79. 3 alias :== :eql?
  80. 3 def type_cast_for_database(attr_name, value)
  81. 21 type_caster.type_cast_for_database(attr_name, value)
  82. end
  83. 3 def type_for_attribute(name)
  84. 157820 type_caster.type_for_attribute(name)
  85. end
  86. 3 def able_to_type_cast?
  87. 585 !type_caster.nil?
  88. end
  89. 3 private
  90. 3 attr_reader :type_caster
  91. end
  92. end

lib/arel/tree_manager.rb

90.24% lines covered

41 relevant lines. 37 lines covered and 4 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 module Arel # :nodoc: all
  3. 3 class TreeManager
  4. 3 include Arel::FactoryMethods
  5. 3 module StatementMethods
  6. 3 def take(limit)
  7. 3463 @ast.limit = Nodes::Limit.new(Nodes.build_quoted(limit)) if limit
  8. 3463 self
  9. end
  10. 3 def offset(offset)
  11. 3451 @ast.offset = Nodes::Offset.new(Nodes.build_quoted(offset)) if offset
  12. 3451 self
  13. end
  14. 3 def order(*expr)
  15. 6263 @ast.orders = expr
  16. 6263 self
  17. end
  18. 3 def key=(key)
  19. 6281 @ast.key = Nodes.build_quoted(key)
  20. end
  21. 3 def key
  22. 3 @ast.key
  23. end
  24. 3 def wheres=(exprs)
  25. 7489 @ast.wheres = exprs
  26. end
  27. 3 def where(expr)
  28. 12 @ast.wheres << expr
  29. 12 self
  30. end
  31. end
  32. 3 attr_reader :ast
  33. 3 def initialize
  34. 218577 @ctx = nil
  35. end
  36. 3 def to_dot
  37. collector = Arel::Collectors::PlainString.new
  38. collector = Visitors::Dot.new.accept @ast, collector
  39. collector.value
  40. end
  41. 3 def to_sql(engine = Table.engine)
  42. 528 collector = Arel::Collectors::SQLString.new
  43. 528 collector = engine.connection.visitor.accept @ast, collector
  44. 528 collector.value
  45. end
  46. 3 def initialize_copy(other)
  47. 9 super
  48. 9 @ast = @ast.clone
  49. end
  50. 3 def where(expr)
  51. 35498 if Arel::TreeManager === expr
  52. expr = expr.ast
  53. end
  54. 35498 @ctx.wheres << expr
  55. 35498 self
  56. end
  57. end
  58. end

lib/arel/update_manager.rb

100.0% lines covered

16 relevant lines. 16 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 module Arel # :nodoc: all
  3. 3 class UpdateManager < Arel::TreeManager
  4. 3 include TreeManager::StatementMethods
  5. 3 def initialize
  6. 4186 super
  7. 4186 @ast = Nodes::UpdateStatement.new
  8. 4186 @ctx = @ast
  9. end
  10. ###
  11. # UPDATE +table+
  12. 3 def table(table)
  13. 4174 @ast.relation = table
  14. 4174 self
  15. end
  16. 3 def set(values)
  17. 4162 if String === values
  18. 78 @ast.values = [values]
  19. else
  20. 4084 @ast.values = values.map { |column, value|
  21. 6137 Nodes::Assignment.new(
  22. Nodes::UnqualifiedColumn.new(column),
  23. value
  24. )
  25. }
  26. end
  27. 4162 self
  28. end
  29. end
  30. end

lib/arel/visitors.rb

100.0% lines covered

8 relevant lines. 8 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 require "arel/visitors/visitor"
  3. 3 require "arel/visitors/to_sql"
  4. 3 require "arel/visitors/sqlite"
  5. 3 require "arel/visitors/postgresql"
  6. 3 require "arel/visitors/mysql"
  7. 3 require "arel/visitors/dot"
  8. 3 module Arel # :nodoc: all
  9. 3 module Visitors
  10. end
  11. end

lib/arel/visitors/dot.rb

80.48% lines covered

210 relevant lines. 169 lines covered and 41 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 module Arel # :nodoc: all
  3. 3 module Visitors
  4. 3 class Dot < Arel::Visitors::Visitor
  5. 3 class Node # :nodoc:
  6. 3 attr_accessor :name, :id, :fields
  7. 3 def initialize(name, id, fields = [])
  8. 300 @name = name
  9. 300 @id = id
  10. 300 @fields = fields
  11. end
  12. end
  13. 3 class Edge < Struct.new :name, :from, :to # :nodoc:
  14. end
  15. 3 def initialize
  16. 105 super()
  17. 105 @nodes = []
  18. 105 @edges = []
  19. 105 @node_stack = []
  20. 105 @edge_stack = []
  21. 105 @seen = {}
  22. end
  23. 3 def accept(object, collector)
  24. 105 visit object
  25. 105 collector << to_dot
  26. end
  27. 3 private
  28. 3 def visit_Arel_Nodes_Ordering(o)
  29. 3 visit_edge o, "expr"
  30. end
  31. 3 def visit_Arel_Nodes_TableAlias(o)
  32. 3 visit_edge o, "name"
  33. 3 visit_edge o, "relation"
  34. end
  35. 3 def visit_Arel_Nodes_Count(o)
  36. visit_edge o, "expressions"
  37. visit_edge o, "distinct"
  38. end
  39. 3 def visit_Arel_Nodes_ValuesList(o)
  40. 3 visit_edge o, "rows"
  41. end
  42. 3 def visit_Arel_Nodes_StringJoin(o)
  43. visit_edge o, "left"
  44. end
  45. 3 def visit_Arel_Nodes_InnerJoin(o)
  46. visit_edge o, "left"
  47. visit_edge o, "right"
  48. end
  49. 3 alias :visit_Arel_Nodes_FullOuterJoin :visit_Arel_Nodes_InnerJoin
  50. 3 alias :visit_Arel_Nodes_OuterJoin :visit_Arel_Nodes_InnerJoin
  51. 3 alias :visit_Arel_Nodes_RightOuterJoin :visit_Arel_Nodes_InnerJoin
  52. 3 def visit_Arel_Nodes_DeleteStatement(o)
  53. 3 visit_edge o, "relation"
  54. 3 visit_edge o, "wheres"
  55. end
  56. 3 def unary(o)
  57. 21 visit_edge o, "expr"
  58. end
  59. 3 alias :visit_Arel_Nodes_Group :unary
  60. 3 alias :visit_Arel_Nodes_Cube :unary
  61. 3 alias :visit_Arel_Nodes_RollUp :unary
  62. 3 alias :visit_Arel_Nodes_GroupingSet :unary
  63. 3 alias :visit_Arel_Nodes_GroupingElement :unary
  64. 3 alias :visit_Arel_Nodes_Grouping :unary
  65. 3 alias :visit_Arel_Nodes_Having :unary
  66. 3 alias :visit_Arel_Nodes_Limit :unary
  67. 3 alias :visit_Arel_Nodes_Not :unary
  68. 3 alias :visit_Arel_Nodes_Offset :unary
  69. 3 alias :visit_Arel_Nodes_On :unary
  70. 3 alias :visit_Arel_Nodes_UnqualifiedColumn :unary
  71. 3 alias :visit_Arel_Nodes_OptimizerHints :unary
  72. 3 alias :visit_Arel_Nodes_Preceding :unary
  73. 3 alias :visit_Arel_Nodes_Following :unary
  74. 3 alias :visit_Arel_Nodes_Rows :unary
  75. 3 alias :visit_Arel_Nodes_Range :unary
  76. 3 def window(o)
  77. visit_edge o, "partitions"
  78. visit_edge o, "orders"
  79. visit_edge o, "framing"
  80. end
  81. 3 alias :visit_Arel_Nodes_Window :window
  82. 3 def named_window(o)
  83. visit_edge o, "partitions"
  84. visit_edge o, "orders"
  85. visit_edge o, "framing"
  86. visit_edge o, "name"
  87. end
  88. 3 alias :visit_Arel_Nodes_NamedWindow :named_window
  89. 3 def function(o)
  90. 15 visit_edge o, "expressions"
  91. 15 visit_edge o, "distinct"
  92. 15 visit_edge o, "alias"
  93. end
  94. 3 alias :visit_Arel_Nodes_Exists :function
  95. 3 alias :visit_Arel_Nodes_Min :function
  96. 3 alias :visit_Arel_Nodes_Max :function
  97. 3 alias :visit_Arel_Nodes_Avg :function
  98. 3 alias :visit_Arel_Nodes_Sum :function
  99. 3 def extract(o)
  100. visit_edge o, "expressions"
  101. visit_edge o, "alias"
  102. end
  103. 3 alias :visit_Arel_Nodes_Extract :extract
  104. 3 def visit_Arel_Nodes_NamedFunction(o)
  105. 3 visit_edge o, "name"
  106. 3 visit_edge o, "expressions"
  107. 3 visit_edge o, "distinct"
  108. 3 visit_edge o, "alias"
  109. end
  110. 3 def visit_Arel_Nodes_InsertStatement(o)
  111. visit_edge o, "relation"
  112. visit_edge o, "columns"
  113. visit_edge o, "values"
  114. end
  115. 3 def visit_Arel_Nodes_SelectCore(o)
  116. visit_edge o, "source"
  117. visit_edge o, "projections"
  118. visit_edge o, "wheres"
  119. visit_edge o, "windows"
  120. end
  121. 3 def visit_Arel_Nodes_SelectStatement(o)
  122. visit_edge o, "cores"
  123. visit_edge o, "limit"
  124. visit_edge o, "orders"
  125. visit_edge o, "offset"
  126. end
  127. 3 def visit_Arel_Nodes_UpdateStatement(o)
  128. visit_edge o, "relation"
  129. visit_edge o, "wheres"
  130. visit_edge o, "values"
  131. end
  132. 3 def visit_Arel_Table(o)
  133. visit_edge o, "name"
  134. end
  135. 3 def visit_Arel_Nodes_Casted(o)
  136. 3 visit_edge o, "value"
  137. 3 visit_edge o, "attribute"
  138. end
  139. 3 def visit_Arel_Nodes_HomogeneousIn(o)
  140. visit_edge o, "values"
  141. visit_edge o, "type"
  142. visit_edge o, "attribute"
  143. end
  144. 3 def visit_Arel_Attribute(o)
  145. visit_edge o, "relation"
  146. visit_edge o, "name"
  147. end
  148. 3 alias :visit_Arel_Attributes_Integer :visit_Arel_Attribute
  149. 3 alias :visit_Arel_Attributes_Float :visit_Arel_Attribute
  150. 3 alias :visit_Arel_Attributes_String :visit_Arel_Attribute
  151. 3 alias :visit_Arel_Attributes_Time :visit_Arel_Attribute
  152. 3 alias :visit_Arel_Attributes_Boolean :visit_Arel_Attribute
  153. 3 alias :visit_Arel_Attributes_Attribute :visit_Arel_Attribute
  154. 3 def nary(o)
  155. o.children.each_with_index do |x, i|
  156. edge(i) { visit x }
  157. end
  158. end
  159. 3 alias :visit_Arel_Nodes_And :nary
  160. 3 def binary(o)
  161. 45 visit_edge o, "left"
  162. 45 visit_edge o, "right"
  163. end
  164. 3 alias :visit_Arel_Nodes_As :binary
  165. 3 alias :visit_Arel_Nodes_Assignment :binary
  166. 3 alias :visit_Arel_Nodes_Between :binary
  167. 3 alias :visit_Arel_Nodes_Concat :binary
  168. 3 alias :visit_Arel_Nodes_DoesNotMatch :binary
  169. 3 alias :visit_Arel_Nodes_Equality :binary
  170. 3 alias :visit_Arel_Nodes_GreaterThan :binary
  171. 3 alias :visit_Arel_Nodes_GreaterThanOrEqual :binary
  172. 3 alias :visit_Arel_Nodes_In :binary
  173. 3 alias :visit_Arel_Nodes_JoinSource :binary
  174. 3 alias :visit_Arel_Nodes_LessThan :binary
  175. 3 alias :visit_Arel_Nodes_LessThanOrEqual :binary
  176. 3 alias :visit_Arel_Nodes_IsNotDistinctFrom :binary
  177. 3 alias :visit_Arel_Nodes_IsDistinctFrom :binary
  178. 3 alias :visit_Arel_Nodes_Matches :binary
  179. 3 alias :visit_Arel_Nodes_NotEqual :binary
  180. 3 alias :visit_Arel_Nodes_NotIn :binary
  181. 3 alias :visit_Arel_Nodes_Or :binary
  182. 3 alias :visit_Arel_Nodes_Over :binary
  183. 3 def visit_String(o)
  184. 195 @node_stack.last.fields << o
  185. end
  186. 3 alias :visit_Time :visit_String
  187. 3 alias :visit_Date :visit_String
  188. 3 alias :visit_DateTime :visit_String
  189. 3 alias :visit_NilClass :visit_String
  190. 3 alias :visit_TrueClass :visit_String
  191. 3 alias :visit_FalseClass :visit_String
  192. 3 alias :visit_Integer :visit_String
  193. 3 alias :visit_BigDecimal :visit_String
  194. 3 alias :visit_Float :visit_String
  195. 3 alias :visit_Symbol :visit_String
  196. 3 alias :visit_Arel_Nodes_SqlLiteral :visit_String
  197. 3 def visit_Arel_Nodes_BindParam(o)
  198. 6 edge("value") { visit o.value }
  199. end
  200. 3 def visit_ActiveModel_Attribute(o)
  201. 6 edge("value_before_type_cast") { visit o.value_before_type_cast }
  202. end
  203. 3 def visit_Hash(o)
  204. o.each_with_index do |pair, i|
  205. edge("pair_#{i}") { visit pair }
  206. end
  207. end
  208. 3 def visit_Array(o)
  209. o.each_with_index do |x, i|
  210. edge(i) { visit x }
  211. end
  212. end
  213. 3 alias :visit_Set :visit_Array
  214. 3 def visit_Arel_Nodes_Comment(o)
  215. visit_edge(o, "values")
  216. end
  217. 3 def visit_edge(o, method)
  218. 384 edge(method) { visit o.send(method) }
  219. end
  220. 3 def visit(o)
  221. 303 if node = @seen[o.object_id]
  222. 3 @edge_stack.last.to = node
  223. 3 return
  224. end
  225. 300 node = Node.new(o.class.name, o.object_id)
  226. 300 @seen[node.id] = node
  227. 300 @nodes << node
  228. 300 with_node node do
  229. 300 super
  230. end
  231. end
  232. 3 def edge(name)
  233. 198 edge = Edge.new(name, @node_stack.last)
  234. 198 @edge_stack.push edge
  235. 198 @edges << edge
  236. 198 yield
  237. 198 @edge_stack.pop
  238. end
  239. 3 def with_node(node)
  240. 300 if edge = @edge_stack.last
  241. 195 edge.to = node
  242. end
  243. 300 @node_stack.push node
  244. 300 yield
  245. 300 @node_stack.pop
  246. end
  247. 3 def quote(string)
  248. 195 string.to_s.gsub('"', '\"')
  249. end
  250. 3 def to_dot
  251. "digraph \"Arel\" {\nnode [width=0.375,height=0.25,shape=record];\n" +
  252. @nodes.map { |node|
  253. 300 label = "<f0>#{node.name}"
  254. 300 node.fields.each_with_index do |field, i|
  255. 195 label += "|<f#{i + 1}>#{quote field}"
  256. end
  257. 300 "#{node.id} [label=\"#{label}\"];"
  258. }.join("\n") + "\n" + @edges.map { |edge|
  259. 198 "#{edge.from.id} -> #{edge.to.id} [label=\"#{edge.name}\"];"
  260. 105 }.join("\n") + "\n}"
  261. end
  262. end
  263. end
  264. end

lib/arel/visitors/mysql.rb

79.17% lines covered

48 relevant lines. 38 lines covered and 10 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 module Arel # :nodoc: all
  3. 3 module Visitors
  4. 3 class MySQL < Arel::Visitors::ToSql
  5. 3 private
  6. 3 def visit_Arel_Nodes_Bin(o, collector)
  7. 3 collector << "BINARY "
  8. 3 visit o.expr, collector
  9. end
  10. 3 def visit_Arel_Nodes_UnqualifiedColumn(o, collector)
  11. visit o.expr, collector
  12. end
  13. ###
  14. # :'(
  15. # To retrieve all rows from a certain offset up to the end of the result set,
  16. # you can use some large number for the second parameter.
  17. # https://dev.mysql.com/doc/refman/en/select.html
  18. 3 def visit_Arel_Nodes_SelectStatement(o, collector)
  19. 12 if o.offset && !o.limit
  20. 3 o.limit = Arel::Nodes::Limit.new(18446744073709551615)
  21. end
  22. 12 super
  23. end
  24. 3 def visit_Arel_Nodes_SelectCore(o, collector)
  25. 12 o.froms ||= Arel.sql("DUAL")
  26. 12 super
  27. end
  28. 3 def visit_Arel_Nodes_Concat(o, collector)
  29. 6 collector << " CONCAT("
  30. 6 visit o.left, collector
  31. 6 collector << ", "
  32. 6 visit o.right, collector
  33. 6 collector << ") "
  34. 6 collector
  35. end
  36. 3 def visit_Arel_Nodes_IsNotDistinctFrom(o, collector)
  37. 15 collector = visit o.left, collector
  38. 15 collector << " <=> "
  39. 15 visit o.right, collector
  40. end
  41. 3 def visit_Arel_Nodes_IsDistinctFrom(o, collector)
  42. 6 collector << "NOT "
  43. 6 visit_Arel_Nodes_IsNotDistinctFrom o, collector
  44. end
  45. 3 def visit_Arel_Nodes_Regexp(o, collector)
  46. 6 infix_value o, collector, " REGEXP "
  47. end
  48. 3 def visit_Arel_Nodes_NotRegexp(o, collector)
  49. 6 infix_value o, collector, " NOT REGEXP "
  50. end
  51. # In the simple case, MySQL allows us to place JOINs directly into the UPDATE
  52. # query. However, this does not allow for LIMIT, OFFSET and ORDER. To support
  53. # these, we must use a subquery.
  54. 3 def prepare_update_statement(o)
  55. 3 if o.offset || has_join_sources?(o) && has_limit_or_offset_or_orders?(o)
  56. super
  57. else
  58. 3 o
  59. end
  60. end
  61. 3 alias :prepare_delete_statement :prepare_update_statement
  62. # MySQL is too stupid to create a temporary table for use subquery, so we have
  63. # to give it some prompting in the form of a subsubquery.
  64. 3 def build_subselect(key, o)
  65. subselect = super
  66. # Materialize subquery by adding distinct
  67. # to work with MySQL 5.7.6 which sets optimizer_switch='derived_merge=on'
  68. unless has_limit_or_offset_or_orders?(subselect)
  69. core = subselect.cores.last
  70. core.set_quantifier = Arel::Nodes::Distinct.new
  71. end
  72. Nodes::SelectStatement.new.tap do |stmt|
  73. core = stmt.cores.last
  74. core.froms = Nodes::Grouping.new(subselect).as("__active_record_temp")
  75. core.projections = [Arel.sql(quote_column_name(key.name))]
  76. end
  77. end
  78. end
  79. end
  80. end

lib/arel/visitors/postgresql.rb

100.0% lines covered

71 relevant lines. 71 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 module Arel # :nodoc: all
  3. 3 module Visitors
  4. 3 class PostgreSQL < Arel::Visitors::ToSql
  5. 3 private
  6. 3 def visit_Arel_Nodes_Matches(o, collector)
  7. 21 op = o.case_sensitive ? " LIKE " : " ILIKE "
  8. 21 collector = infix_value o, collector, op
  9. 21 if o.escape
  10. 3 collector << " ESCAPE "
  11. 3 visit o.escape, collector
  12. else
  13. 18 collector
  14. end
  15. end
  16. 3 def visit_Arel_Nodes_DoesNotMatch(o, collector)
  17. 12 op = o.case_sensitive ? " NOT LIKE " : " NOT ILIKE "
  18. 12 collector = infix_value o, collector, op
  19. 12 if o.escape
  20. 3 collector << " ESCAPE "
  21. 3 visit o.escape, collector
  22. else
  23. 9 collector
  24. end
  25. end
  26. 3 def visit_Arel_Nodes_Regexp(o, collector)
  27. 9 op = o.case_sensitive ? " ~ " : " ~* "
  28. 9 infix_value o, collector, op
  29. end
  30. 3 def visit_Arel_Nodes_NotRegexp(o, collector)
  31. 9 op = o.case_sensitive ? " !~ " : " !~* "
  32. 9 infix_value o, collector, op
  33. end
  34. 3 def visit_Arel_Nodes_DistinctOn(o, collector)
  35. 3 collector << "DISTINCT ON ( "
  36. 3 visit(o.expr, collector) << " )"
  37. end
  38. 3 def visit_Arel_Nodes_GroupingElement(o, collector)
  39. 27 collector << "( "
  40. 27 visit(o.expr, collector) << " )"
  41. end
  42. 3 def visit_Arel_Nodes_Cube(o, collector)
  43. 9 collector << "CUBE"
  44. 9 grouping_array_or_grouping_element o, collector
  45. end
  46. 3 def visit_Arel_Nodes_RollUp(o, collector)
  47. 9 collector << "ROLLUP"
  48. 9 grouping_array_or_grouping_element o, collector
  49. end
  50. 3 def visit_Arel_Nodes_GroupingSet(o, collector)
  51. 9 collector << "GROUPING SETS"
  52. 9 grouping_array_or_grouping_element o, collector
  53. end
  54. 3 def visit_Arel_Nodes_Lateral(o, collector)
  55. 6 collector << "LATERAL "
  56. 6 grouping_parentheses o, collector
  57. end
  58. 3 def visit_Arel_Nodes_IsNotDistinctFrom(o, collector)
  59. 9 collector = visit o.left, collector
  60. 9 collector << " IS NOT DISTINCT FROM "
  61. 9 visit o.right, collector
  62. end
  63. 3 def visit_Arel_Nodes_IsDistinctFrom(o, collector)
  64. 8 collector = visit o.left, collector
  65. 8 collector << " IS DISTINCT FROM "
  66. 8 visit o.right, collector
  67. end
  68. 3 def visit_Arel_Nodes_NullsFirst(o, collector)
  69. 3 visit o.expr, collector
  70. 3 collector << " NULLS FIRST"
  71. end
  72. 3 def visit_Arel_Nodes_NullsLast(o, collector)
  73. 3 visit o.expr, collector
  74. 3 collector << " NULLS LAST"
  75. end
  76. 422343 BIND_BLOCK = proc { |i| "$#{i}" }
  77. 3 private_constant :BIND_BLOCK
  78. 45274 def bind_block; BIND_BLOCK; end
  79. # Used by Lateral visitor to enclose select queries in parentheses
  80. 3 def grouping_parentheses(o, collector)
  81. 6 if o.expr.is_a? Nodes::SelectStatement
  82. 3 collector << "("
  83. 3 visit o.expr, collector
  84. 3 collector << ")"
  85. else
  86. 3 visit o.expr, collector
  87. end
  88. end
  89. # Utilized by GroupingSet, Cube & RollUp visitors to
  90. # handle grouping aggregation semantics
  91. 3 def grouping_array_or_grouping_element(o, collector)
  92. 27 if o.expr.is_a? Array
  93. 18 collector << "( "
  94. 18 visit o.expr, collector
  95. 18 collector << " )"
  96. else
  97. 9 visit o.expr, collector
  98. end
  99. end
  100. end
  101. end
  102. end

lib/arel/visitors/sqlite.rb

100.0% lines covered

21 relevant lines. 21 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 module Arel # :nodoc: all
  3. 3 module Visitors
  4. 3 class SQLite < Arel::Visitors::ToSql
  5. 3 private
  6. # Locks are not supported in SQLite
  7. 3 def visit_Arel_Nodes_Lock(o, collector)
  8. 16 collector
  9. end
  10. 3 def visit_Arel_Nodes_SelectStatement(o, collector)
  11. 22420 o.limit = Arel::Nodes::Limit.new(-1) if o.offset && !o.limit
  12. 22420 super
  13. end
  14. 3 def visit_Arel_Nodes_True(o, collector)
  15. 3 collector << "1"
  16. end
  17. 3 def visit_Arel_Nodes_False(o, collector)
  18. 3 collector << "0"
  19. end
  20. 3 def visit_Arel_Nodes_IsNotDistinctFrom(o, collector)
  21. 9 collector = visit o.left, collector
  22. 9 collector << " IS "
  23. 9 visit o.right, collector
  24. end
  25. 3 def visit_Arel_Nodes_IsDistinctFrom(o, collector)
  26. 10 collector = visit o.left, collector
  27. 10 collector << " IS NOT "
  28. 10 visit o.right, collector
  29. end
  30. end
  31. end
  32. end

lib/arel/visitors/to_sql.rb

98.89% lines covered

541 relevant lines. 535 lines covered and 6 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 module Arel # :nodoc: all
  3. 3 module Visitors
  4. 3 class UnsupportedVisitError < StandardError
  5. 3 def initialize(object)
  6. 3 super "Unsupported argument type: #{object.class.name}. Construct an Arel node instead."
  7. end
  8. end
  9. 3 class ToSql < Arel::Visitors::Visitor
  10. 3 def initialize(connection)
  11. 3377 super()
  12. 3377 @connection = connection
  13. end
  14. 3 def compile(node, collector = Arel::Collectors::SQLString.new)
  15. 207825 accept(node, collector).value
  16. end
  17. 3 private
  18. 3 def visit_Arel_Nodes_DeleteStatement(o, collector)
  19. 3351 o = prepare_delete_statement(o)
  20. 3351 if has_join_sources?(o)
  21. collector << "DELETE "
  22. visit o.relation.left, collector
  23. collector << " FROM "
  24. else
  25. 3351 collector << "DELETE FROM "
  26. end
  27. 3351 collector = visit o.relation, collector
  28. 3351 collect_nodes_for o.wheres, collector, " WHERE ", " AND "
  29. 3351 collect_nodes_for o.orders, collector, " ORDER BY "
  30. 3351 maybe_visit o.limit, collector
  31. end
  32. 3 def visit_Arel_Nodes_UpdateStatement(o, collector)
  33. 4168 o = prepare_update_statement(o)
  34. 4168 collector << "UPDATE "
  35. 4168 collector = visit o.relation, collector
  36. 4168 collect_nodes_for o.values, collector, " SET "
  37. 4168 collect_nodes_for o.wheres, collector, " WHERE ", " AND "
  38. 4168 collect_nodes_for o.orders, collector, " ORDER BY "
  39. 4168 maybe_visit o.limit, collector
  40. end
  41. 3 def visit_Arel_Nodes_InsertStatement(o, collector)
  42. 165935 collector << "INSERT INTO "
  43. 165935 collector = visit o.relation, collector
  44. 165935 unless o.columns.empty?
  45. 164626 collector << " ("
  46. 164626 o.columns.each_with_index do |x, i|
  47. 416652 collector << ", " unless i == 0
  48. 416652 collector << quote_column_name(x.name)
  49. end
  50. 164626 collector << ")"
  51. end
  52. 165935 if o.values
  53. 165926 maybe_visit o.values, collector
  54. 9 elsif o.select
  55. 3 maybe_visit o.select, collector
  56. else
  57. 6 collector
  58. end
  59. end
  60. 3 def visit_Arel_Nodes_Exists(o, collector)
  61. 6 collector << "EXISTS ("
  62. 6 collector = visit(o.expressions, collector) << ")"
  63. 6 if o.alias
  64. 3 collector << " AS "
  65. 3 visit o.alias, collector
  66. else
  67. 3 collector
  68. end
  69. end
  70. 3 def visit_Arel_Nodes_Casted(o, collector)
  71. 757 collector << quote(o.value_for_database).to_s
  72. end
  73. 3 alias :visit_Arel_Nodes_Quoted :visit_Arel_Nodes_Casted
  74. 3 def visit_Arel_Nodes_True(o, collector)
  75. 9 collector << "TRUE"
  76. end
  77. 3 def visit_Arel_Nodes_False(o, collector)
  78. 9 collector << "FALSE"
  79. end
  80. 3 def visit_Arel_Nodes_ValuesList(o, collector)
  81. 164784 collector << "VALUES "
  82. 164784 o.rows.each_with_index do |row, i|
  83. 306528 collector << ", " unless i == 0
  84. 306528 collector << "("
  85. 306528 row.each_with_index do |value, k|
  86. 1042715 collector << ", " unless k == 0
  87. 1042715 case value
  88. when Nodes::SqlLiteral, Nodes::BindParam
  89. 342584 collector = visit(value, collector)
  90. else
  91. 700131 collector << quote(value).to_s
  92. end
  93. end
  94. 306528 collector << ")"
  95. end
  96. 164784 collector
  97. end
  98. 3 def visit_Arel_Nodes_SelectStatement(o, collector)
  99. 34912 if o.with
  100. 12 collector = visit o.with, collector
  101. 12 collector << " "
  102. end
  103. 34912 collector = o.cores.inject(collector) { |c, x|
  104. 34912 visit_Arel_Nodes_SelectCore(x, c)
  105. }
  106. 34912 unless o.orders.empty?
  107. 8717 collector << " ORDER BY "
  108. 8717 o.orders.each_with_index do |x, i|
  109. 8909 collector << ", " unless i == 0
  110. 8909 collector = visit(x, collector)
  111. end
  112. end
  113. 34912 visit_Arel_Nodes_SelectOptions(o, collector)
  114. end
  115. 3 def visit_Arel_Nodes_SelectOptions(o, collector)
  116. 34912 collector = maybe_visit o.limit, collector
  117. 34912 collector = maybe_visit o.offset, collector
  118. 34912 maybe_visit o.lock, collector
  119. end
  120. 3 def visit_Arel_Nodes_SelectCore(o, collector)
  121. 34924 collector << "SELECT"
  122. 34924 collector = collect_optimizer_hints(o, collector)
  123. 34924 collector = maybe_visit o.set_quantifier, collector
  124. 34921 collect_nodes_for o.projections, collector, " "
  125. 34921 if o.source && !o.source.empty?
  126. 34876 collector << " FROM "
  127. 34876 collector = visit o.source, collector
  128. end
  129. 34921 collect_nodes_for o.wheres, collector, " WHERE ", " AND "
  130. 34921 collect_nodes_for o.groups, collector, " GROUP BY "
  131. 34921 collect_nodes_for o.havings, collector, " HAVING ", " AND "
  132. 34921 collect_nodes_for o.windows, collector, " WINDOW "
  133. 34921 maybe_visit o.comment, collector
  134. end
  135. 3 def visit_Arel_Nodes_OptimizerHints(o, collector)
  136. 18 hints = o.expr.map { |v| sanitize_as_sql_comment(v) }.join(" ")
  137. 9 collector << "/*+ #{hints} */"
  138. end
  139. 3 def visit_Arel_Nodes_Comment(o, collector)
  140. 231 collector << o.values.map { |v| "/* #{sanitize_as_sql_comment(v)} */" }.join(" ")
  141. end
  142. 3 def collect_nodes_for(nodes, collector, spacer, connector = ", ")
  143. 193868 unless nodes.empty?
  144. 72532 collector << spacer
  145. 72532 inject_join nodes, collector, connector
  146. end
  147. end
  148. 3 def visit_Arel_Nodes_Bin(o, collector)
  149. 3 visit o.expr, collector
  150. end
  151. 3 def visit_Arel_Nodes_Distinct(o, collector)
  152. 499 collector << "DISTINCT"
  153. end
  154. 3 def visit_Arel_Nodes_DistinctOn(o, collector)
  155. 3 raise NotImplementedError, "DISTINCT ON not implemented for this db"
  156. end
  157. 3 def visit_Arel_Nodes_With(o, collector)
  158. 6 collector << "WITH "
  159. 6 collect_ctes(o.children, collector)
  160. end
  161. 3 def visit_Arel_Nodes_WithRecursive(o, collector)
  162. 6 collector << "WITH RECURSIVE "
  163. 6 collect_ctes(o.children, collector)
  164. end
  165. 3 def visit_Arel_Nodes_Union(o, collector)
  166. 21 infix_value_with_paren(o, collector, " UNION ")
  167. end
  168. 3 def visit_Arel_Nodes_UnionAll(o, collector)
  169. 9 infix_value_with_paren(o, collector, " UNION ALL ")
  170. end
  171. 3 def visit_Arel_Nodes_Intersect(o, collector)
  172. 3 collector << "( "
  173. 3 infix_value(o, collector, " INTERSECT ") << " )"
  174. end
  175. 3 def visit_Arel_Nodes_Except(o, collector)
  176. 3 collector << "( "
  177. 3 infix_value(o, collector, " EXCEPT ") << " )"
  178. end
  179. 3 def visit_Arel_Nodes_NamedWindow(o, collector)
  180. 54 collector << quote_column_name(o.name)
  181. 54 collector << " AS "
  182. 54 visit_Arel_Nodes_Window o, collector
  183. end
  184. 3 def visit_Arel_Nodes_Window(o, collector)
  185. 57 collector << "("
  186. 57 collect_nodes_for o.partitions, collector, "PARTITION BY "
  187. 57 if o.orders.any?
  188. 12 collector << " " if o.partitions.any?
  189. 12 collector << "ORDER BY "
  190. 12 collector = inject_join o.orders, collector, ", "
  191. end
  192. 57 if o.framing
  193. 36 collector << " " if o.partitions.any? || o.orders.any?
  194. 36 collector = visit o.framing, collector
  195. end
  196. 57 collector << ")"
  197. end
  198. 3 def visit_Arel_Nodes_Rows(o, collector)
  199. 18 if o.expr
  200. 15 collector << "ROWS "
  201. 15 visit o.expr, collector
  202. else
  203. 3 collector << "ROWS"
  204. end
  205. end
  206. 3 def visit_Arel_Nodes_Range(o, collector)
  207. 18 if o.expr
  208. 15 collector << "RANGE "
  209. 15 visit o.expr, collector
  210. else
  211. 3 collector << "RANGE"
  212. end
  213. end
  214. 3 def visit_Arel_Nodes_Preceding(o, collector)
  215. 18 collector = if o.expr
  216. 6 visit o.expr, collector
  217. else
  218. 12 collector << "UNBOUNDED"
  219. end
  220. 18 collector << " PRECEDING"
  221. end
  222. 3 def visit_Arel_Nodes_Following(o, collector)
  223. 12 collector = if o.expr
  224. 6 visit o.expr, collector
  225. else
  226. 6 collector << "UNBOUNDED"
  227. end
  228. 12 collector << " FOLLOWING"
  229. end
  230. 3 def visit_Arel_Nodes_CurrentRow(o, collector)
  231. 12 collector << "CURRENT ROW"
  232. end
  233. 3 def visit_Arel_Nodes_Over(o, collector)
  234. 15 case o.right
  235. when nil
  236. 6 visit(o.left, collector) << " OVER ()"
  237. when Arel::Nodes::SqlLiteral
  238. 3 infix_value o, collector, " OVER "
  239. when String, Symbol
  240. 3 visit(o.left, collector) << " OVER #{quote_column_name o.right.to_s}"
  241. else
  242. 3 infix_value o, collector, " OVER "
  243. end
  244. end
  245. 3 def visit_Arel_Nodes_Offset(o, collector)
  246. 393 collector << "OFFSET "
  247. 393 visit o.expr, collector
  248. end
  249. 3 def visit_Arel_Nodes_Limit(o, collector)
  250. 20147 collector << "LIMIT "
  251. 20147 visit o.expr, collector
  252. end
  253. 3 def visit_Arel_Nodes_Lock(o, collector)
  254. 34 visit o.expr, collector
  255. end
  256. 3 def visit_Arel_Nodes_Grouping(o, collector)
  257. 2209 if o.expr.is_a? Nodes::Grouping
  258. 3 visit(o.expr, collector)
  259. else
  260. 2206 collector << "("
  261. 2206 visit(o.expr, collector) << ")"
  262. end
  263. end
  264. 3 def visit_Arel_Nodes_HomogeneousIn(o, collector)
  265. 3975 collector.preparable = false
  266. 3975 collector << quote_table_name(o.table_name) << "." << quote_column_name(o.column_name)
  267. 3975 if o.type == :in
  268. 3942 collector << " IN ("
  269. else
  270. 33 collector << " NOT IN ("
  271. end
  272. 3975 values = o.casted_values
  273. 3975 if values.empty?
  274. collector << @connection.quote(nil)
  275. else
  276. 3975 collector.add_binds(values, &bind_block)
  277. end
  278. 3975 collector << ")"
  279. 3975 collector
  280. end
  281. 3 def visit_Arel_SelectManager(o, collector)
  282. 15 collector << "("
  283. 15 visit(o.ast, collector) << ")"
  284. end
  285. 3 def visit_Arel_Nodes_Ascending(o, collector)
  286. 5745 visit(o.expr, collector) << " ASC"
  287. end
  288. 3 def visit_Arel_Nodes_Descending(o, collector)
  289. 513 visit(o.expr, collector) << " DESC"
  290. end
  291. 3 def visit_Arel_Nodes_Group(o, collector)
  292. 406 visit o.expr, collector
  293. end
  294. 3 def visit_Arel_Nodes_NamedFunction(o, collector)
  295. 1182 collector << o.name
  296. 1182 collector << "("
  297. 1182 collector << "DISTINCT " if o.distinct
  298. 1182 collector = inject_join(o.expressions, collector, ", ") << ")"
  299. 1182 if o.alias
  300. collector << " AS "
  301. visit o.alias, collector
  302. else
  303. 1182 collector
  304. end
  305. end
  306. 3 def visit_Arel_Nodes_Extract(o, collector)
  307. 6 collector << "EXTRACT(#{o.field.to_s.upcase} FROM "
  308. 6 visit(o.expr, collector) << ")"
  309. end
  310. 3 def visit_Arel_Nodes_Count(o, collector)
  311. 4005 aggregate "COUNT", o, collector
  312. end
  313. 3 def visit_Arel_Nodes_Sum(o, collector)
  314. 175 aggregate "SUM", o, collector
  315. end
  316. 3 def visit_Arel_Nodes_Max(o, collector)
  317. 130 aggregate "MAX", o, collector
  318. end
  319. 3 def visit_Arel_Nodes_Min(o, collector)
  320. 108 aggregate "MIN", o, collector
  321. end
  322. 3 def visit_Arel_Nodes_Avg(o, collector)
  323. 78 aggregate "AVG", o, collector
  324. end
  325. 3 def visit_Arel_Nodes_TableAlias(o, collector)
  326. 885 collector = visit o.relation, collector
  327. 885 collector << " "
  328. 885 collector << quote_table_name(o.name)
  329. end
  330. 3 def visit_Arel_Nodes_Between(o, collector)
  331. 183 collector = visit o.left, collector
  332. 183 collector << " BETWEEN "
  333. 183 visit o.right, collector
  334. end
  335. 3 def visit_Arel_Nodes_GreaterThanOrEqual(o, collector)
  336. 126 collector = visit o.left, collector
  337. 126 collector << " >= "
  338. 126 visit o.right, collector
  339. end
  340. 3 def visit_Arel_Nodes_GreaterThan(o, collector)
  341. 837 collector = visit o.left, collector
  342. 837 collector << " > "
  343. 837 visit o.right, collector
  344. end
  345. 3 def visit_Arel_Nodes_LessThanOrEqual(o, collector)
  346. 75 collector = visit o.left, collector
  347. 75 collector << " <= "
  348. 75 visit o.right, collector
  349. end
  350. 3 def visit_Arel_Nodes_LessThan(o, collector)
  351. 147 collector = visit o.left, collector
  352. 147 collector << " < "
  353. 147 visit o.right, collector
  354. end
  355. 3 def visit_Arel_Nodes_Matches(o, collector)
  356. 24 collector = visit o.left, collector
  357. 24 collector << " LIKE "
  358. 24 collector = visit o.right, collector
  359. 24 if o.escape
  360. 3 collector << " ESCAPE "
  361. 3 visit o.escape, collector
  362. else
  363. 21 collector
  364. end
  365. end
  366. 3 def visit_Arel_Nodes_DoesNotMatch(o, collector)
  367. 24 collector = visit o.left, collector
  368. 24 collector << " NOT LIKE "
  369. 24 collector = visit o.right, collector
  370. 24 if o.escape
  371. 3 collector << " ESCAPE "
  372. 3 visit o.escape, collector
  373. else
  374. 21 collector
  375. end
  376. end
  377. 3 def visit_Arel_Nodes_JoinSource(o, collector)
  378. 34921 if o.left
  379. 34909 collector = visit o.left, collector
  380. end
  381. 34921 if o.right.any?
  382. 3910 collector << " " if o.left
  383. 3910 collector = inject_join o.right, collector, " "
  384. end
  385. 34921 collector
  386. end
  387. 3 def visit_Arel_Nodes_Regexp(o, collector)
  388. 3 raise NotImplementedError, "~ not implemented for this db"
  389. end
  390. 3 def visit_Arel_Nodes_NotRegexp(o, collector)
  391. 3 raise NotImplementedError, "!~ not implemented for this db"
  392. end
  393. 3 def visit_Arel_Nodes_StringJoin(o, collector)
  394. 102 visit o.left, collector
  395. end
  396. 3 def visit_Arel_Nodes_FullOuterJoin(o, collector)
  397. 3 collector << "FULL OUTER JOIN "
  398. 3 collector = visit o.left, collector
  399. 3 collector << " "
  400. 3 visit o.right, collector
  401. end
  402. 3 def visit_Arel_Nodes_OuterJoin(o, collector)
  403. 1813 collector << "LEFT OUTER JOIN "
  404. 1813 collector = visit o.left, collector
  405. 1813 collector << " "
  406. 1813 visit o.right, collector
  407. end
  408. 3 def visit_Arel_Nodes_RightOuterJoin(o, collector)
  409. 3 collector << "RIGHT OUTER JOIN "
  410. 3 collector = visit o.left, collector
  411. 3 collector << " "
  412. 3 visit o.right, collector
  413. end
  414. 3 def visit_Arel_Nodes_InnerJoin(o, collector)
  415. 3434 collector << "INNER JOIN "
  416. 3434 collector = visit o.left, collector
  417. 3434 if o.right
  418. 3431 collector << " "
  419. 3431 visit(o.right, collector)
  420. else
  421. 3 collector
  422. end
  423. end
  424. 3 def visit_Arel_Nodes_On(o, collector)
  425. 5244 collector << "ON "
  426. 5244 visit o.expr, collector
  427. end
  428. 3 def visit_Arel_Nodes_Not(o, collector)
  429. 15 collector << "NOT ("
  430. 15 visit(o.expr, collector) << ")"
  431. end
  432. 3 def visit_Arel_Table(o, collector)
  433. 213145 if o.table_alias
  434. 21 collector << quote_table_name(o.name) << " " << quote_table_name(o.table_alias)
  435. else
  436. 213124 collector << quote_table_name(o.name)
  437. end
  438. end
  439. 3 def visit_Arel_Nodes_In(o, collector)
  440. 393 collector.preparable = false
  441. 393 attr, values = o.left, o.right
  442. 393 if Array === values
  443. 276 unless values.empty?
  444. 441 values.delete_if { |value| unboundable?(value) }
  445. end
  446. 276 return collector << "1=0" if values.empty?
  447. end
  448. 324 visit(attr, collector) << " IN ("
  449. 324 visit(values, collector) << ")"
  450. end
  451. 3 def visit_Arel_Nodes_NotIn(o, collector)
  452. 41 collector.preparable = false
  453. 41 attr, values = o.left, o.right
  454. 41 if Array === values
  455. 35 unless values.empty?
  456. 60 values.delete_if { |value| unboundable?(value) }
  457. end
  458. 35 return collector << "1=1" if values.empty?
  459. end
  460. 24 visit(attr, collector) << " NOT IN ("
  461. 24 visit(values, collector) << ")"
  462. end
  463. 3 def visit_Arel_Nodes_And(o, collector)
  464. 12553 inject_join o.children, collector, " AND "
  465. end
  466. 3 def visit_Arel_Nodes_Or(o, collector)
  467. 292 stack = [o.right, o.left]
  468. 292 while o = stack.pop
  469. 12634 if o.is_a?(Arel::Nodes::Or)
  470. 6025 stack.push o.right, o.left
  471. else
  472. 6609 visit o, collector
  473. 6609 collector << " OR " unless stack.empty?
  474. end
  475. end
  476. 292 collector
  477. end
  478. 3 def visit_Arel_Nodes_Assignment(o, collector)
  479. 6131 case o.right
  480. when Arel::Nodes::Node, Arel::Attributes::Attribute
  481. 6110 collector = visit o.left, collector
  482. 6110 collector << " = "
  483. 6110 visit o.right, collector
  484. else
  485. 21 collector = visit o.left, collector
  486. 21 collector << " = "
  487. 21 collector << quote(o.right).to_s
  488. end
  489. end
  490. 3 def visit_Arel_Nodes_Equality(o, collector)
  491. 51022 right = o.right
  492. 51022 return collector << "1=0" if unboundable?(right)
  493. 51010 collector = visit o.left, collector
  494. 51010 if right.nil?
  495. 361 collector << " IS NULL"
  496. else
  497. 50649 collector << " = "
  498. 50649 visit right, collector
  499. end
  500. end
  501. 3 def visit_Arel_Nodes_IsNotDistinctFrom(o, collector)
  502. 9 if o.right.nil?
  503. 3 collector = visit o.left, collector
  504. 3 collector << " IS NULL"
  505. else
  506. 6 collector = is_distinct_from(o, collector)
  507. 6 collector << " = 0"
  508. end
  509. end
  510. 3 def visit_Arel_Nodes_IsDistinctFrom(o, collector)
  511. 6 if o.right.nil?
  512. 3 collector = visit o.left, collector
  513. 3 collector << " IS NOT NULL"
  514. else
  515. 3 collector = is_distinct_from(o, collector)
  516. 3 collector << " = 1"
  517. end
  518. end
  519. 3 def visit_Arel_Nodes_NotEqual(o, collector)
  520. 205 right = o.right
  521. 205 return collector << "1=1" if unboundable?(right)
  522. 202 collector = visit o.left, collector
  523. 202 if right.nil?
  524. 33 collector << " IS NOT NULL"
  525. else
  526. 169 collector << " != "
  527. 169 visit right, collector
  528. end
  529. end
  530. 3 def visit_Arel_Nodes_As(o, collector)
  531. 10820 collector = visit o.left, collector
  532. 10820 collector << " AS "
  533. 10820 visit o.right, collector
  534. end
  535. 3 def visit_Arel_Nodes_Case(o, collector)
  536. 18 collector << "CASE "
  537. 18 if o.case
  538. 15 visit o.case, collector
  539. 15 collector << " "
  540. end
  541. 18 o.conditions.each do |condition|
  542. 24 visit condition, collector
  543. 24 collector << " "
  544. end
  545. 18 if o.default
  546. 12 visit o.default, collector
  547. 12 collector << " "
  548. end
  549. 18 collector << "END"
  550. end
  551. 3 def visit_Arel_Nodes_When(o, collector)
  552. 24 collector << "WHEN "
  553. 24 visit o.left, collector
  554. 24 collector << " THEN "
  555. 24 visit o.right, collector
  556. end
  557. 3 def visit_Arel_Nodes_Else(o, collector)
  558. 12 collector << "ELSE "
  559. 12 visit o.expr, collector
  560. end
  561. 3 def visit_Arel_Nodes_UnqualifiedColumn(o, collector)
  562. 7183 collector << quote_column_name(o.name)
  563. end
  564. 3 def visit_Arel_Attributes_Attribute(o, collector)
  565. 124534 join_name = o.relation.table_alias || o.relation.name
  566. 124534 collector << quote_table_name(join_name) << "." << quote_column_name(o.name)
  567. end
  568. 338963 BIND_BLOCK = proc { "?" }
  569. 3 private_constant :BIND_BLOCK
  570. 63079 def bind_block; BIND_BLOCK; end
  571. 3 def visit_Arel_Nodes_BindParam(o, collector)
  572. 104372 collector.add_bind(o.value, &bind_block)
  573. end
  574. 3 def visit_Arel_Nodes_SqlLiteral(o, collector)
  575. 334477 collector.preparable = false
  576. 334477 collector << o.to_s
  577. end
  578. 3 def visit_Integer(o, collector)
  579. 1335 collector << o.to_s
  580. end
  581. 3 def unsupported(o, collector)
  582. 3 raise UnsupportedVisitError.new(o)
  583. end
  584. 3 alias :visit_ActiveSupport_Multibyte_Chars :unsupported
  585. 3 alias :visit_ActiveSupport_StringInquirer :unsupported
  586. 3 alias :visit_BigDecimal :unsupported
  587. 3 alias :visit_Class :unsupported
  588. 3 alias :visit_Date :unsupported
  589. 3 alias :visit_DateTime :unsupported
  590. 3 alias :visit_FalseClass :unsupported
  591. 3 alias :visit_Float :unsupported
  592. 3 alias :visit_Hash :unsupported
  593. 3 alias :visit_NilClass :unsupported
  594. 3 alias :visit_String :unsupported
  595. 3 alias :visit_Symbol :unsupported
  596. 3 alias :visit_Time :unsupported
  597. 3 alias :visit_TrueClass :unsupported
  598. 3 def visit_Arel_Nodes_InfixOperation(o, collector)
  599. 1241 collector = visit o.left, collector
  600. 1241 collector << " #{o.operator} "
  601. 1241 visit o.right, collector
  602. end
  603. 3 def visit_Arel_Nodes_UnaryOperation(o, collector)
  604. 6 collector << " #{o.operator} "
  605. 6 visit o.expr, collector
  606. end
  607. 3 def visit_Array(o, collector)
  608. 279 inject_join o, collector, ", "
  609. end
  610. 3 alias :visit_Set :visit_Array
  611. 3 def quote(value)
  612. 700909 return value if Arel::Nodes::SqlLiteral === value
  613. 700909 @connection.quote value
  614. end
  615. 3 def quote_table_name(name)
  616. 342575 return name if Arel::Nodes::SqlLiteral === name
  617. 342221 @connection.quote_table_name(name)
  618. end
  619. 3 def quote_column_name(name)
  620. 552401 return name if Arel::Nodes::SqlLiteral === name
  621. 529743 @connection.quote_column_name(name)
  622. end
  623. 3 def sanitize_as_sql_comment(value)
  624. 147 return value if Arel::Nodes::SqlLiteral === value
  625. 147 @connection.sanitize_as_sql_comment(value)
  626. end
  627. 3 def collect_optimizer_hints(o, collector)
  628. 34924 maybe_visit o.optimizer_hints, collector
  629. end
  630. 3 def maybe_visit(thing, collector)
  631. 382953 return collector unless thing
  632. 187111 collector << " "
  633. 187111 visit thing, collector
  634. end
  635. 3 def inject_join(list, collector, join_str)
  636. 94964 list.each_with_index do |x, i|
  637. 143068 collector << join_str unless i == 0
  638. 143068 collector = visit(x, collector)
  639. end
  640. 94964 collector
  641. end
  642. 3 def unboundable?(value)
  643. 51503 value.respond_to?(:unboundable?) && value.unboundable?
  644. end
  645. 3 def has_join_sources?(o)
  646. 9656 o.relation.is_a?(Nodes::JoinSource) && !o.relation.right.empty?
  647. end
  648. 3 def has_limit_or_offset_or_orders?(o)
  649. 6266 o.limit || o.offset || !o.orders.empty?
  650. end
  651. # The default strategy for an UPDATE with joins is to use a subquery. This doesn't work
  652. # on MySQL (even when aliasing the tables), but MySQL allows using JOIN directly in
  653. # an UPDATE statement, so in the MySQL visitor we redefine this to do that.
  654. 3 def prepare_update_statement(o)
  655. 7516 if o.key && (has_limit_or_offset_or_orders?(o) || has_join_sources?(o))
  656. 186 stmt = o.clone
  657. 186 stmt.limit = nil
  658. 186 stmt.offset = nil
  659. 186 stmt.orders = []
  660. 186 stmt.wheres = [Nodes::In.new(o.key, [build_subselect(o.key, o)])]
  661. 186 stmt.relation = o.relation.left if has_join_sources?(o)
  662. 186 stmt
  663. else
  664. 7330 o
  665. end
  666. end
  667. 3 alias :prepare_delete_statement :prepare_update_statement
  668. # FIXME: we should probably have a 2-pass visitor for this
  669. 3 def build_subselect(key, o)
  670. 186 stmt = Nodes::SelectStatement.new
  671. 186 core = stmt.cores.first
  672. 186 core.froms = o.relation
  673. 186 core.wheres = o.wheres
  674. 186 core.projections = [key]
  675. 186 stmt.limit = o.limit
  676. 186 stmt.offset = o.offset
  677. 186 stmt.orders = o.orders
  678. 186 stmt
  679. end
  680. 3 def infix_value(o, collector, value)
  681. 75 collector = visit o.left, collector
  682. 75 collector << value
  683. 75 visit o.right, collector
  684. end
  685. 3 def infix_value_with_paren(o, collector, value, suppress_parens = false)
  686. 42 collector << "( " unless suppress_parens
  687. 42 collector = if o.left.class == o.class
  688. 6 infix_value_with_paren(o.left, collector, value, true)
  689. else
  690. 36 visit o.left, collector
  691. end
  692. 42 collector << value
  693. 42 collector = if o.right.class == o.class
  694. 6 infix_value_with_paren(o.right, collector, value, true)
  695. else
  696. 36 visit o.right, collector
  697. end
  698. 42 collector << " )" unless suppress_parens
  699. 42 collector
  700. end
  701. 3 def aggregate(name, o, collector)
  702. 4496 collector << "#{name}("
  703. 4496 if o.distinct
  704. 180 collector << "DISTINCT "
  705. end
  706. 4496 collector = inject_join(o.expressions, collector, ", ") << ")"
  707. 4496 if o.alias
  708. 268 collector << " AS "
  709. 268 visit o.alias, collector
  710. else
  711. 4228 collector
  712. end
  713. end
  714. 3 def is_distinct_from(o, collector)
  715. 9 collector << "CASE WHEN "
  716. 9 collector = visit o.left, collector
  717. 9 collector << " = "
  718. 9 collector = visit o.right, collector
  719. 9 collector << " OR ("
  720. 9 collector = visit o.left, collector
  721. 9 collector << " IS NULL AND "
  722. 9 collector = visit o.right, collector
  723. 9 collector << " IS NULL)"
  724. 9 collector << " THEN 0 ELSE 1 END"
  725. end
  726. 3 def collect_ctes(children, collector)
  727. 12 children.each_with_index do |child, i|
  728. 15 collector << ", " unless i == 0
  729. 15 case child
  730. when Arel::Nodes::As
  731. 6 name = child.left.name
  732. 6 relation = child.right
  733. when Arel::Nodes::TableAlias
  734. 9 name = child.name
  735. 9 relation = child.relation
  736. end
  737. 15 collector << quote_table_name(name)
  738. 15 collector << " AS "
  739. 15 visit relation, collector
  740. end
  741. 12 collector
  742. end
  743. end
  744. end
  745. end

lib/arel/visitors/visitor.rb

100.0% lines covered

25 relevant lines. 25 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 module Arel # :nodoc: all
  3. 3 module Visitors
  4. 3 class Visitor
  5. 3 def initialize
  6. 3491 @dispatch = get_dispatch_cache
  7. end
  8. 3 def accept(object, collector = nil)
  9. 209407 visit object, collector
  10. end
  11. 3 private
  12. 3 attr_reader :dispatch
  13. 3 def self.dispatch_cache
  14. 3491 @dispatch_cache ||= Hash.new do |hash, klass|
  15. 777 hash[klass] = "visit_#{(klass.name || '').gsub('::', '_')}"
  16. end
  17. end
  18. 3 def get_dispatch_cache
  19. 3491 self.class.dispatch_cache
  20. end
  21. 3 def visit(object, collector = nil)
  22. 1330413 dispatch_method = dispatch[object.class]
  23. 1330413 if collector
  24. 1330095 send dispatch_method, object, collector
  25. else
  26. 318 send dispatch_method, object
  27. end
  28. rescue NoMethodError => e
  29. 75 raise e if respond_to?(dispatch_method, true)
  30. 75 superklass = object.class.ancestors.find { |klass|
  31. 147 respond_to?(dispatch[klass], true)
  32. }
  33. 75 raise(TypeError, "Cannot visit #{object.class}") unless superklass
  34. 75 dispatch[object.class] = dispatch[superklass]
  35. 75 retry
  36. end
  37. end
  38. end
  39. end

lib/arel/window_predications.rb

100.0% lines covered

4 relevant lines. 4 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 3 module Arel # :nodoc: all
  3. 3 module WindowPredications
  4. 3 def over(expr = nil)
  5. 15 Nodes::Over.new(self, expr)
  6. end
  7. end
  8. end

lib/rails/generators/active_record.rb

0.0% lines covered

14 relevant lines. 0 lines covered and 14 lines missed.
    
  1. # frozen_string_literal: true
  2. require "rails/generators/named_base"
  3. require "rails/generators/active_model"
  4. require "rails/generators/active_record/migration"
  5. require "active_record"
  6. module ActiveRecord
  7. module Generators # :nodoc:
  8. class Base < Rails::Generators::NamedBase # :nodoc:
  9. include ActiveRecord::Generators::Migration
  10. # Set the current directory as base for the inherited generators.
  11. def self.base_root
  12. __dir__
  13. end
  14. end
  15. end
  16. end

lib/rails/generators/active_record/application_record/application_record_generator.rb

0.0% lines covered

20 relevant lines. 0 lines covered and 20 lines missed.
    
  1. # frozen_string_literal: true
  2. require "rails/generators/active_record"
  3. module ActiveRecord
  4. module Generators # :nodoc:
  5. class ApplicationRecordGenerator < ::Rails::Generators::Base # :nodoc:
  6. source_root File.expand_path("templates", __dir__)
  7. # FIXME: Change this file to a symlink once RubyGems 2.5.0 is required.
  8. def create_application_record
  9. template "application_record.rb", application_record_file_name
  10. end
  11. private
  12. def application_record_file_name
  13. @application_record_file_name ||=
  14. if namespaced?
  15. "app/models/#{namespaced_path}/application_record.rb"
  16. else
  17. "app/models/application_record.rb"
  18. end
  19. end
  20. end
  21. end
  22. end

lib/rails/generators/active_record/migration.rb

0.0% lines covered

42 relevant lines. 0 lines covered and 42 lines missed.
    
  1. # frozen_string_literal: true
  2. require "rails/generators/migration"
  3. module ActiveRecord
  4. module Generators # :nodoc:
  5. module Migration
  6. extend ActiveSupport::Concern
  7. include Rails::Generators::Migration
  8. module ClassMethods
  9. # Implement the required interface for Rails::Generators::Migration.
  10. def next_migration_number(dirname)
  11. next_migration_number = current_migration_number(dirname) + 1
  12. ActiveRecord::Migration.next_migration_number(next_migration_number)
  13. end
  14. end
  15. private
  16. def primary_key_type
  17. key_type = options[:primary_key_type]
  18. ", id: :#{key_type}" if key_type
  19. end
  20. def foreign_key_type
  21. key_type = options[:primary_key_type]
  22. ", type: :#{key_type}" if key_type
  23. end
  24. def db_migrate_path
  25. if defined?(Rails.application) && Rails.application
  26. configured_migrate_path || default_migrate_path
  27. else
  28. "db/migrate"
  29. end
  30. end
  31. def default_migrate_path
  32. Rails.application.config.paths["db/migrate"].to_ary.first
  33. end
  34. def configured_migrate_path
  35. return unless database = options[:database]
  36. config = ActiveRecord::Base.configurations.configs_for(
  37. env_name: Rails.env,
  38. name: database
  39. )
  40. config&.migrations_paths
  41. end
  42. end
  43. end
  44. end

lib/rails/generators/active_record/migration/migration_generator.rb

0.0% lines covered

58 relevant lines. 0 lines covered and 58 lines missed.
    
  1. # frozen_string_literal: true
  2. require "rails/generators/active_record"
  3. module ActiveRecord
  4. module Generators # :nodoc:
  5. class MigrationGenerator < Base # :nodoc:
  6. argument :attributes, type: :array, default: [], banner: "field[:type][:index] field[:type][:index]"
  7. class_option :timestamps, type: :boolean
  8. class_option :primary_key_type, type: :string, desc: "The type for primary key"
  9. class_option :database, type: :string, aliases: %i(--db), desc: "The database for your migration. By default, the current environment's primary database is used."
  10. def create_migration_file
  11. set_local_assigns!
  12. validate_file_name!
  13. migration_template @migration_template, File.join(db_migrate_path, "#{file_name}.rb")
  14. end
  15. private
  16. attr_reader :migration_action, :join_tables
  17. # Sets the default migration template that is being used for the generation of the migration.
  18. # Depending on command line arguments, the migration template and the table name instance
  19. # variables are set up.
  20. def set_local_assigns!
  21. @migration_template = "migration.rb"
  22. case file_name
  23. when /^(add)_.*_to_(.*)/, /^(remove)_.*?_from_(.*)/
  24. @migration_action = $1
  25. @table_name = normalize_table_name($2)
  26. when /join_table/
  27. if attributes.length == 2
  28. @migration_action = "join"
  29. @join_tables = pluralize_table_names? ? attributes.map(&:plural_name) : attributes.map(&:singular_name)
  30. set_index_names
  31. end
  32. when /^create_(.+)/
  33. @table_name = normalize_table_name($1)
  34. @migration_template = "create_table_migration.rb"
  35. end
  36. end
  37. def set_index_names
  38. attributes.each_with_index do |attr, i|
  39. attr.index_name = [attr, attributes[i - 1]].map { |a| index_name_for(a) }
  40. end
  41. end
  42. def index_name_for(attribute)
  43. if attribute.foreign_key?
  44. attribute.name
  45. else
  46. attribute.name.singularize.foreign_key
  47. end.to_sym
  48. end
  49. def attributes_with_index
  50. attributes.select { |a| !a.reference? && a.has_index? }
  51. end
  52. # A migration file name can only contain underscores (_), lowercase characters,
  53. # and numbers 0-9. Any other file name will raise an IllegalMigrationNameError.
  54. def validate_file_name!
  55. unless /^[_a-z0-9]+$/.match?(file_name)
  56. raise IllegalMigrationNameError.new(file_name)
  57. end
  58. end
  59. def normalize_table_name(_table_name)
  60. pluralize_table_names? ? _table_name.pluralize : _table_name.singularize
  61. end
  62. end
  63. end
  64. end

lib/rails/generators/active_record/model/model_generator.rb

0.0% lines covered

62 relevant lines. 0 lines covered and 62 lines missed.
    
  1. # frozen_string_literal: true
  2. require "rails/generators/active_record"
  3. module ActiveRecord
  4. module Generators # :nodoc:
  5. class ModelGenerator < Base # :nodoc:
  6. argument :attributes, type: :array, default: [], banner: "field[:type][:index] field[:type][:index]"
  7. check_class_collision
  8. class_option :migration, type: :boolean
  9. class_option :timestamps, type: :boolean
  10. class_option :parent, type: :string, desc: "The parent class for the generated model"
  11. class_option :indexes, type: :boolean, default: true, desc: "Add indexes for references and belongs_to columns"
  12. class_option :primary_key_type, type: :string, desc: "The type for primary key"
  13. class_option :database, type: :string, aliases: %i(--db), desc: "The database for your model's migration. By default, the current environment's primary database is used."
  14. # creates the migration file for the model.
  15. def create_migration_file
  16. return if skip_migration_creation?
  17. attributes.each { |a| a.attr_options.delete(:index) if a.reference? && !a.has_index? } if options[:indexes] == false
  18. migration_template "../../migration/templates/create_table_migration.rb", File.join(db_migrate_path, "create_#{table_name}.rb")
  19. end
  20. def create_model_file
  21. generate_abstract_class if database && !parent
  22. template "model.rb", File.join("app/models", class_path, "#{file_name}.rb")
  23. end
  24. def create_module_file
  25. return if regular_class_path.empty?
  26. template "module.rb", File.join("app/models", "#{class_path.join('/')}.rb") if behavior == :invoke
  27. end
  28. hook_for :test_framework
  29. private
  30. # Skip creating migration file if:
  31. # - options parent is present and database option is not present
  32. # - migrations option is nil or false
  33. def skip_migration_creation?
  34. parent && !database || !migration
  35. end
  36. def attributes_with_index
  37. attributes.select { |a| !a.reference? && a.has_index? }
  38. end
  39. # Used by the migration template to determine the parent name of the model
  40. def parent_class_name
  41. if parent
  42. parent
  43. elsif database
  44. abstract_class_name
  45. else
  46. "ApplicationRecord"
  47. end
  48. end
  49. def generate_abstract_class
  50. path = File.join("app/models", "#{database.underscore}_record.rb")
  51. return if File.exist?(path)
  52. template "abstract_base_class.rb", path
  53. end
  54. def abstract_class_name
  55. "#{database.camelize}Record"
  56. end
  57. def database
  58. options[:database]
  59. end
  60. def parent
  61. options[:parent]
  62. end
  63. def migration
  64. options[:migration]
  65. end
  66. end
  67. end
  68. end