activerecord 5.2.8.1 → 6.1.6.1
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of activerecord might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/CHANGELOG.md +1255 -596
- data/MIT-LICENSE +3 -1
- data/README.rdoc +7 -5
- data/examples/performance.rb +1 -1
- data/lib/active_record/aggregations.rb +9 -8
- data/lib/active_record/association_relation.rb +30 -10
- data/lib/active_record/associations/alias_tracker.rb +19 -16
- data/lib/active_record/associations/association.rb +100 -41
- data/lib/active_record/associations/association_scope.rb +23 -21
- data/lib/active_record/associations/belongs_to_association.rb +55 -48
- data/lib/active_record/associations/belongs_to_polymorphic_association.rb +7 -6
- data/lib/active_record/associations/builder/association.rb +45 -22
- data/lib/active_record/associations/builder/belongs_to.rb +29 -59
- data/lib/active_record/associations/builder/collection_association.rb +8 -17
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +17 -41
- data/lib/active_record/associations/builder/has_many.rb +8 -2
- data/lib/active_record/associations/builder/has_one.rb +33 -2
- data/lib/active_record/associations/builder/singular_association.rb +3 -1
- data/lib/active_record/associations/collection_association.rb +44 -34
- data/lib/active_record/associations/collection_proxy.rb +25 -21
- data/lib/active_record/associations/foreign_association.rb +20 -0
- data/lib/active_record/associations/has_many_association.rb +26 -13
- data/lib/active_record/associations/has_many_through_association.rb +24 -18
- data/lib/active_record/associations/has_one_association.rb +43 -31
- data/lib/active_record/associations/has_one_through_association.rb +5 -5
- data/lib/active_record/associations/join_dependency/join_association.rb +44 -22
- data/lib/active_record/associations/join_dependency/join_part.rb +5 -5
- data/lib/active_record/associations/join_dependency.rb +91 -60
- data/lib/active_record/associations/preloader/association.rb +69 -43
- data/lib/active_record/associations/preloader/through_association.rb +49 -40
- data/lib/active_record/associations/preloader.rb +47 -34
- data/lib/active_record/associations/singular_association.rb +3 -17
- data/lib/active_record/associations/through_association.rb +1 -1
- data/lib/active_record/associations.rb +137 -25
- data/lib/active_record/attribute_assignment.rb +17 -19
- data/lib/active_record/attribute_methods/before_type_cast.rb +13 -7
- data/lib/active_record/attribute_methods/dirty.rb +101 -40
- data/lib/active_record/attribute_methods/primary_key.rb +20 -25
- data/lib/active_record/attribute_methods/query.rb +4 -8
- data/lib/active_record/attribute_methods/read.rb +14 -56
- data/lib/active_record/attribute_methods/serialization.rb +12 -7
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +12 -15
- data/lib/active_record/attribute_methods/write.rb +18 -34
- data/lib/active_record/attribute_methods.rb +81 -143
- data/lib/active_record/attributes.rb +46 -9
- data/lib/active_record/autosave_association.rb +57 -42
- data/lib/active_record/base.rb +4 -17
- data/lib/active_record/callbacks.rb +158 -43
- data/lib/active_record/coders/yaml_column.rb +1 -2
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +272 -130
- data/lib/active_record/connection_adapters/abstract/database_limits.rb +7 -36
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +167 -146
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +18 -14
- data/lib/active_record/connection_adapters/abstract/quoting.rb +98 -47
- data/lib/active_record/connection_adapters/abstract/savepoints.rb +3 -3
- data/lib/active_record/connection_adapters/abstract/schema_creation.rb +153 -110
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +211 -90
- data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +2 -4
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +385 -144
- data/lib/active_record/connection_adapters/abstract/transaction.rb +167 -69
- data/lib/active_record/connection_adapters/abstract_adapter.rb +229 -99
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +243 -275
- data/lib/active_record/connection_adapters/column.rb +30 -12
- data/lib/active_record/connection_adapters/deduplicable.rb +29 -0
- data/lib/active_record/connection_adapters/legacy_pool_manager.rb +35 -0
- data/lib/active_record/connection_adapters/mysql/column.rb +1 -1
- data/lib/active_record/connection_adapters/mysql/database_statements.rb +88 -32
- data/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb +1 -2
- data/lib/active_record/connection_adapters/mysql/quoting.rb +59 -7
- data/lib/active_record/connection_adapters/mysql/schema_creation.rb +34 -10
- data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +48 -32
- data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +18 -7
- data/lib/active_record/connection_adapters/mysql/schema_statements.rb +142 -19
- data/lib/active_record/connection_adapters/mysql/type_metadata.rb +14 -9
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +53 -18
- data/lib/active_record/connection_adapters/pool_config.rb +73 -0
- data/lib/active_record/connection_adapters/pool_manager.rb +47 -0
- data/lib/active_record/connection_adapters/postgresql/column.rb +37 -28
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +40 -54
- data/lib/active_record/connection_adapters/postgresql/oid/array.rb +1 -2
- data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +1 -4
- data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +3 -5
- data/lib/active_record/connection_adapters/postgresql/oid/date.rb +2 -2
- data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +10 -2
- data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +0 -1
- data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +1 -2
- data/lib/active_record/connection_adapters/postgresql/oid/interval.rb +49 -0
- data/lib/active_record/connection_adapters/postgresql/oid/legacy_point.rb +3 -4
- data/lib/active_record/connection_adapters/postgresql/oid/macaddr.rb +25 -0
- data/lib/active_record/connection_adapters/postgresql/oid/oid.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/oid/point.rb +3 -4
- data/lib/active_record/connection_adapters/postgresql/oid/range.rb +25 -7
- data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +9 -7
- data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +15 -3
- data/lib/active_record/connection_adapters/postgresql/oid.rb +2 -0
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +47 -10
- data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +2 -2
- data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +19 -4
- data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +107 -91
- data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +0 -1
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +120 -100
- data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +31 -26
- data/lib/active_record/connection_adapters/postgresql/utils.rb +0 -1
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +224 -120
- data/lib/active_record/connection_adapters/schema_cache.rb +159 -21
- data/lib/active_record/connection_adapters/sql_type_metadata.rb +17 -6
- data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +146 -0
- data/lib/active_record/connection_adapters/sqlite3/quoting.rb +42 -7
- data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +5 -1
- data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +77 -13
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +174 -186
- data/lib/active_record/connection_adapters/statement_pool.rb +0 -1
- data/lib/active_record/connection_adapters.rb +52 -0
- data/lib/active_record/connection_handling.rb +293 -33
- data/lib/active_record/core.rb +333 -98
- data/lib/active_record/counter_cache.rb +8 -30
- data/lib/active_record/database_configurations/connection_url_resolver.rb +99 -0
- data/lib/active_record/database_configurations/database_config.rb +80 -0
- data/lib/active_record/database_configurations/hash_config.rb +96 -0
- data/lib/active_record/database_configurations/url_config.rb +53 -0
- data/lib/active_record/database_configurations.rb +273 -0
- data/lib/active_record/delegated_type.rb +209 -0
- data/lib/active_record/destroy_association_async_job.rb +36 -0
- data/lib/active_record/dynamic_matchers.rb +3 -4
- data/lib/active_record/enum.rb +108 -36
- data/lib/active_record/errors.rb +62 -19
- data/lib/active_record/explain.rb +10 -6
- data/lib/active_record/explain_subscriber.rb +1 -1
- data/lib/active_record/fixture_set/file.rb +10 -17
- data/lib/active_record/fixture_set/model_metadata.rb +32 -0
- data/lib/active_record/fixture_set/render_context.rb +17 -0
- data/lib/active_record/fixture_set/table_row.rb +152 -0
- data/lib/active_record/fixture_set/table_rows.rb +46 -0
- data/lib/active_record/fixtures.rb +200 -481
- data/lib/active_record/gem_version.rb +3 -3
- data/lib/active_record/inheritance.rb +53 -24
- data/lib/active_record/insert_all.rb +212 -0
- data/lib/active_record/integration.rb +67 -17
- data/lib/active_record/internal_metadata.rb +28 -9
- data/lib/active_record/legacy_yaml_adapter.rb +7 -3
- data/lib/active_record/locking/optimistic.rb +37 -23
- data/lib/active_record/locking/pessimistic.rb +9 -5
- data/lib/active_record/log_subscriber.rb +35 -35
- data/lib/active_record/middleware/database_selector/resolver/session.rb +48 -0
- data/lib/active_record/middleware/database_selector/resolver.rb +92 -0
- data/lib/active_record/middleware/database_selector.rb +77 -0
- data/lib/active_record/migration/command_recorder.rb +96 -44
- data/lib/active_record/migration/compatibility.rb +145 -64
- data/lib/active_record/migration/join_table.rb +0 -1
- data/lib/active_record/migration.rb +206 -157
- data/lib/active_record/model_schema.rb +148 -22
- data/lib/active_record/nested_attributes.rb +4 -7
- data/lib/active_record/no_touching.rb +8 -1
- data/lib/active_record/null_relation.rb +0 -1
- data/lib/active_record/persistence.rb +267 -59
- data/lib/active_record/query_cache.rb +21 -4
- data/lib/active_record/querying.rb +40 -23
- data/lib/active_record/railtie.rb +116 -59
- data/lib/active_record/railties/console_sandbox.rb +2 -4
- data/lib/active_record/railties/controller_runtime.rb +30 -35
- data/lib/active_record/railties/databases.rake +411 -80
- data/lib/active_record/readonly_attributes.rb +4 -0
- data/lib/active_record/reflection.rb +109 -93
- data/lib/active_record/relation/batches/batch_enumerator.rb +25 -9
- data/lib/active_record/relation/batches.rb +44 -35
- data/lib/active_record/relation/calculations.rb +157 -90
- data/lib/active_record/relation/delegation.rb +35 -50
- data/lib/active_record/relation/finder_methods.rb +64 -39
- data/lib/active_record/relation/from_clause.rb +5 -1
- data/lib/active_record/relation/merger.rb +32 -40
- data/lib/active_record/relation/predicate_builder/array_handler.rb +13 -13
- data/lib/active_record/relation/predicate_builder/association_query_value.rb +5 -9
- data/lib/active_record/relation/predicate_builder/basic_object_handler.rb +1 -2
- data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +11 -10
- data/lib/active_record/relation/predicate_builder/range_handler.rb +3 -23
- data/lib/active_record/relation/predicate_builder/relation_handler.rb +1 -1
- data/lib/active_record/relation/predicate_builder.rb +62 -45
- data/lib/active_record/relation/query_attribute.rb +13 -8
- data/lib/active_record/relation/query_methods.rb +476 -187
- data/lib/active_record/relation/record_fetch_warning.rb +3 -3
- data/lib/active_record/relation/spawn_methods.rb +9 -9
- data/lib/active_record/relation/where_clause.rb +115 -62
- data/lib/active_record/relation.rb +379 -115
- data/lib/active_record/result.rb +64 -38
- data/lib/active_record/runtime_registry.rb +2 -2
- data/lib/active_record/sanitization.rb +22 -41
- data/lib/active_record/schema.rb +2 -11
- data/lib/active_record/schema_dumper.rb +54 -9
- data/lib/active_record/schema_migration.rb +7 -9
- data/lib/active_record/scoping/default.rb +4 -8
- data/lib/active_record/scoping/named.rb +17 -24
- data/lib/active_record/scoping.rb +8 -9
- data/lib/active_record/secure_token.rb +16 -8
- data/lib/active_record/serialization.rb +5 -3
- data/lib/active_record/signed_id.rb +116 -0
- data/lib/active_record/statement_cache.rb +49 -6
- data/lib/active_record/store.rb +88 -9
- data/lib/active_record/suppressor.rb +2 -2
- data/lib/active_record/table_metadata.rb +42 -43
- data/lib/active_record/tasks/database_tasks.rb +277 -81
- data/lib/active_record/tasks/mysql_database_tasks.rb +37 -39
- data/lib/active_record/tasks/postgresql_database_tasks.rb +27 -32
- data/lib/active_record/tasks/sqlite_database_tasks.rb +14 -17
- data/lib/active_record/test_databases.rb +24 -0
- data/lib/active_record/test_fixtures.rb +287 -0
- data/lib/active_record/timestamp.rb +43 -32
- data/lib/active_record/touch_later.rb +23 -22
- data/lib/active_record/transactions.rb +62 -118
- data/lib/active_record/translation.rb +1 -1
- data/lib/active_record/type/adapter_specific_registry.rb +3 -13
- data/lib/active_record/type/hash_lookup_type_map.rb +0 -1
- data/lib/active_record/type/serialized.rb +6 -3
- data/lib/active_record/type/time.rb +10 -0
- data/lib/active_record/type/type_map.rb +0 -1
- data/lib/active_record/type/unsigned_integer.rb +0 -1
- data/lib/active_record/type.rb +10 -5
- data/lib/active_record/type_caster/connection.rb +15 -15
- data/lib/active_record/type_caster/map.rb +8 -8
- data/lib/active_record/validations/associated.rb +1 -2
- data/lib/active_record/validations/numericality.rb +35 -0
- data/lib/active_record/validations/uniqueness.rb +38 -30
- data/lib/active_record/validations.rb +4 -3
- data/lib/active_record.rb +13 -12
- data/lib/arel/alias_predication.rb +9 -0
- data/lib/arel/attributes/attribute.rb +41 -0
- data/lib/arel/collectors/bind.rb +29 -0
- data/lib/arel/collectors/composite.rb +39 -0
- data/lib/arel/collectors/plain_string.rb +20 -0
- data/lib/arel/collectors/sql_string.rb +27 -0
- data/lib/arel/collectors/substitute_binds.rb +35 -0
- data/lib/arel/crud.rb +42 -0
- data/lib/arel/delete_manager.rb +18 -0
- data/lib/arel/errors.rb +9 -0
- data/lib/arel/expressions.rb +29 -0
- data/lib/arel/factory_methods.rb +49 -0
- data/lib/arel/insert_manager.rb +49 -0
- data/lib/arel/math.rb +45 -0
- data/lib/arel/nodes/and.rb +32 -0
- data/lib/arel/nodes/ascending.rb +23 -0
- data/lib/arel/nodes/binary.rb +126 -0
- data/lib/arel/nodes/bind_param.rb +44 -0
- data/lib/arel/nodes/case.rb +55 -0
- data/lib/arel/nodes/casted.rb +62 -0
- data/lib/arel/nodes/comment.rb +29 -0
- data/lib/arel/nodes/count.rb +12 -0
- data/lib/arel/nodes/delete_statement.rb +45 -0
- data/lib/arel/nodes/descending.rb +23 -0
- data/lib/arel/nodes/equality.rb +15 -0
- data/lib/arel/nodes/extract.rb +24 -0
- data/lib/arel/nodes/false.rb +16 -0
- data/lib/arel/nodes/full_outer_join.rb +8 -0
- data/lib/arel/nodes/function.rb +44 -0
- data/lib/arel/nodes/grouping.rb +11 -0
- data/lib/arel/nodes/homogeneous_in.rb +76 -0
- data/lib/arel/nodes/in.rb +15 -0
- data/lib/arel/nodes/infix_operation.rb +92 -0
- data/lib/arel/nodes/inner_join.rb +8 -0
- data/lib/arel/nodes/insert_statement.rb +37 -0
- data/lib/arel/nodes/join_source.rb +20 -0
- data/lib/arel/nodes/matches.rb +18 -0
- data/lib/arel/nodes/named_function.rb +23 -0
- data/lib/arel/nodes/node.rb +51 -0
- data/lib/arel/nodes/node_expression.rb +13 -0
- data/lib/arel/nodes/ordering.rb +27 -0
- data/lib/arel/nodes/outer_join.rb +8 -0
- data/lib/arel/nodes/over.rb +15 -0
- data/lib/arel/nodes/regexp.rb +16 -0
- data/lib/arel/nodes/right_outer_join.rb +8 -0
- data/lib/arel/nodes/select_core.rb +67 -0
- data/lib/arel/nodes/select_statement.rb +41 -0
- data/lib/arel/nodes/sql_literal.rb +19 -0
- data/lib/arel/nodes/string_join.rb +11 -0
- data/lib/arel/nodes/table_alias.rb +31 -0
- data/lib/arel/nodes/terminal.rb +16 -0
- data/lib/arel/nodes/true.rb +16 -0
- data/lib/arel/nodes/unary.rb +44 -0
- data/lib/arel/nodes/unary_operation.rb +20 -0
- data/lib/arel/nodes/unqualified_column.rb +22 -0
- data/lib/arel/nodes/update_statement.rb +41 -0
- data/lib/arel/nodes/values_list.rb +9 -0
- data/lib/arel/nodes/window.rb +126 -0
- data/lib/arel/nodes/with.rb +11 -0
- data/lib/arel/nodes.rb +70 -0
- data/lib/arel/order_predications.rb +13 -0
- data/lib/arel/predications.rb +250 -0
- data/lib/arel/select_manager.rb +270 -0
- data/lib/arel/table.rb +118 -0
- data/lib/arel/tree_manager.rb +72 -0
- data/lib/arel/update_manager.rb +34 -0
- data/lib/arel/visitors/dot.rb +308 -0
- data/lib/arel/visitors/mysql.rb +93 -0
- data/lib/arel/visitors/postgresql.rb +120 -0
- data/lib/arel/visitors/sqlite.rb +38 -0
- data/lib/arel/visitors/to_sql.rb +899 -0
- data/lib/arel/visitors/visitor.rb +45 -0
- data/lib/arel/visitors.rb +13 -0
- data/lib/arel/window_predications.rb +9 -0
- data/lib/arel.rb +54 -0
- data/lib/rails/generators/active_record/application_record/application_record_generator.rb +0 -1
- data/lib/rails/generators/active_record/migration/migration_generator.rb +3 -5
- data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +3 -1
- data/lib/rails/generators/active_record/migration/templates/migration.rb.tt +7 -5
- data/lib/rails/generators/active_record/migration.rb +19 -2
- data/lib/rails/generators/active_record/model/model_generator.rb +39 -2
- data/lib/rails/generators/active_record/model/templates/abstract_base_class.rb.tt +7 -0
- data/lib/rails/generators/active_record/model/templates/model.rb.tt +10 -1
- metadata +116 -30
- data/lib/active_record/attribute_decorators.rb +0 -90
- data/lib/active_record/collection_cache_key.rb +0 -53
- data/lib/active_record/connection_adapters/connection_specification.rb +0 -287
- data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +0 -33
- data/lib/active_record/define_callbacks.rb +0 -22
- data/lib/active_record/relation/predicate_builder/base_handler.rb +0 -19
- data/lib/active_record/relation/where_clause_factory.rb +0 -34
@@ -5,7 +5,7 @@
|
|
5
5
|
module ActiveRecord::Associations::Builder # :nodoc:
|
6
6
|
class SingularAssociation < Association #:nodoc:
|
7
7
|
def self.valid_options(options)
|
8
|
-
super + [:
|
8
|
+
super + [:required, :touch]
|
9
9
|
end
|
10
10
|
|
11
11
|
def self.define_accessors(model, reflection)
|
@@ -38,5 +38,7 @@ module ActiveRecord::Associations::Builder # :nodoc:
|
|
38
38
|
end
|
39
39
|
CODE
|
40
40
|
end
|
41
|
+
|
42
|
+
private_class_method :valid_options, :define_accessors, :define_constructors
|
41
43
|
end
|
42
44
|
end
|
@@ -56,7 +56,7 @@ module ActiveRecord
|
|
56
56
|
def ids_writer(ids)
|
57
57
|
primary_key = reflection.association_primary_key
|
58
58
|
pk_type = klass.type_for_attribute(primary_key)
|
59
|
-
ids = Array(ids).
|
59
|
+
ids = Array(ids).compact_blank
|
60
60
|
ids.map! { |i| pk_type.cast(i) }
|
61
61
|
|
62
62
|
records = klass.where(primary_key => ids).index_by do |r|
|
@@ -75,6 +75,7 @@ module ActiveRecord
|
|
75
75
|
def reset
|
76
76
|
super
|
77
77
|
@target = []
|
78
|
+
@replaced_or_added_targets = Set.new
|
78
79
|
@association_ids = nil
|
79
80
|
end
|
80
81
|
|
@@ -101,11 +102,11 @@ module ActiveRecord
|
|
101
102
|
end
|
102
103
|
end
|
103
104
|
|
104
|
-
def build(attributes =
|
105
|
+
def build(attributes = nil, &block)
|
105
106
|
if attributes.is_a?(Array)
|
106
107
|
attributes.collect { |attr| build(attr, &block) }
|
107
108
|
else
|
108
|
-
add_to_target(build_record(attributes, &block))
|
109
|
+
add_to_target(build_record(attributes, &block), replace: true)
|
109
110
|
end
|
110
111
|
end
|
111
112
|
|
@@ -211,9 +212,11 @@ module ActiveRecord
|
|
211
212
|
def size
|
212
213
|
if !find_target? || loaded?
|
213
214
|
target.size
|
215
|
+
elsif @association_ids
|
216
|
+
@association_ids.size
|
214
217
|
elsif !association_scope.group_values.empty?
|
215
218
|
load_target.size
|
216
|
-
elsif !association_scope.distinct_value && target.
|
219
|
+
elsif !association_scope.distinct_value && !target.empty?
|
217
220
|
unsaved_records = target.select(&:new_record?)
|
218
221
|
unsaved_records.size + count_records
|
219
222
|
else
|
@@ -226,14 +229,14 @@ module ActiveRecord
|
|
226
229
|
# If the collection has been loaded
|
227
230
|
# it is equivalent to <tt>collection.size.zero?</tt>. If the
|
228
231
|
# collection has not been loaded, it is equivalent to
|
229
|
-
# <tt
|
232
|
+
# <tt>!collection.exists?</tt>. If the collection has not already been
|
230
233
|
# loaded and you are going to fetch the records anyway it is better to
|
231
234
|
# check <tt>collection.length.zero?</tt>.
|
232
235
|
def empty?
|
233
|
-
if loaded?
|
236
|
+
if loaded? || @association_ids || reflection.has_cached_counter?
|
234
237
|
size.zero?
|
235
238
|
else
|
236
|
-
|
239
|
+
target.empty? && !scope.exists?
|
237
240
|
end
|
238
241
|
end
|
239
242
|
|
@@ -276,11 +279,19 @@ module ActiveRecord
|
|
276
279
|
target
|
277
280
|
end
|
278
281
|
|
279
|
-
def add_to_target(record, skip_callbacks
|
280
|
-
|
281
|
-
|
282
|
+
def add_to_target(record, skip_callbacks: false, replace: false, &block)
|
283
|
+
replace_on_target(record, skip_callbacks, replace: replace || association_scope.distinct_value, &block)
|
284
|
+
end
|
285
|
+
|
286
|
+
def target=(record)
|
287
|
+
return super unless ActiveRecord::Base.has_many_inversing
|
288
|
+
|
289
|
+
case record
|
290
|
+
when Array
|
291
|
+
super
|
292
|
+
else
|
293
|
+
replace_on_target(record, true, replace: true, inversing: true)
|
282
294
|
end
|
283
|
-
replace_on_target(record, index, skip_callbacks, &block)
|
284
295
|
end
|
285
296
|
|
286
297
|
def scope
|
@@ -295,28 +306,13 @@ module ActiveRecord
|
|
295
306
|
|
296
307
|
def find_from_target?
|
297
308
|
loaded? ||
|
309
|
+
owner.strict_loading? ||
|
310
|
+
reflection.strict_loading? ||
|
298
311
|
owner.new_record? ||
|
299
312
|
target.any? { |record| record.new_record? || record.changed? }
|
300
313
|
end
|
301
314
|
|
302
315
|
private
|
303
|
-
|
304
|
-
def find_target
|
305
|
-
scope = self.scope
|
306
|
-
return scope.to_a if skip_statement_cache?(scope)
|
307
|
-
|
308
|
-
conn = klass.connection
|
309
|
-
sc = reflection.association_scope_cache(conn, owner) do |params|
|
310
|
-
as = AssociationScope.create { params.bind }
|
311
|
-
target_scope.merge!(as.scope(self))
|
312
|
-
end
|
313
|
-
|
314
|
-
binds = AssociationScope.get_bind_values(owner, reflection.chain)
|
315
|
-
sc.execute(binds, conn) do |record|
|
316
|
-
set_inverse_instance(record)
|
317
|
-
end
|
318
|
-
end
|
319
|
-
|
320
316
|
# We have some records loaded from the database (persisted) and some that are
|
321
317
|
# in-memory (memory). The same record may be represented in the persisted array
|
322
318
|
# and in the memory array.
|
@@ -344,7 +340,7 @@ module ActiveRecord
|
|
344
340
|
end
|
345
341
|
end
|
346
342
|
|
347
|
-
persisted + memory
|
343
|
+
persisted + memory.reject(&:persisted?)
|
348
344
|
end
|
349
345
|
|
350
346
|
def _create_record(attributes, raise = false, &block)
|
@@ -393,10 +389,12 @@ module ActiveRecord
|
|
393
389
|
end
|
394
390
|
|
395
391
|
def remove_records(existing_records, records, method)
|
396
|
-
|
392
|
+
catch(:abort) do
|
393
|
+
records.each { |record| callback(:before_remove, record) }
|
394
|
+
end || return
|
397
395
|
|
398
396
|
delete_records(existing_records, method) if existing_records.any?
|
399
|
-
|
397
|
+
@target -= records
|
400
398
|
@association_ids = nil
|
401
399
|
|
402
400
|
records.each { |record| callback(:after_remove, record) }
|
@@ -425,7 +423,7 @@ module ActiveRecord
|
|
425
423
|
common_records = intersection(new_target, original_target)
|
426
424
|
common_records.each do |record|
|
427
425
|
skip_callbacks = true
|
428
|
-
replace_on_target(record,
|
426
|
+
replace_on_target(record, skip_callbacks, replace: true)
|
429
427
|
end
|
430
428
|
end
|
431
429
|
|
@@ -448,8 +446,14 @@ module ActiveRecord
|
|
448
446
|
records
|
449
447
|
end
|
450
448
|
|
451
|
-
def replace_on_target(record,
|
452
|
-
|
449
|
+
def replace_on_target(record, skip_callbacks, replace:, inversing: false)
|
450
|
+
if replace && (!record.new_record? || @replaced_or_added_targets.include?(record))
|
451
|
+
index = @target.index(record)
|
452
|
+
end
|
453
|
+
|
454
|
+
catch(:abort) do
|
455
|
+
callback(:before_add, record)
|
456
|
+
end || return unless skip_callbacks
|
453
457
|
|
454
458
|
set_inverse_instance(record)
|
455
459
|
|
@@ -457,6 +461,12 @@ module ActiveRecord
|
|
457
461
|
|
458
462
|
yield(record) if block_given?
|
459
463
|
|
464
|
+
if !index && @replaced_or_added_targets.include?(record)
|
465
|
+
index = @target.index(record)
|
466
|
+
end
|
467
|
+
|
468
|
+
@replaced_or_added_targets << record if inversing || index || record.new_record?
|
469
|
+
|
460
470
|
if index
|
461
471
|
target[index] = record
|
462
472
|
elsif @_was_loaded || !loaded?
|
@@ -2,11 +2,8 @@
|
|
2
2
|
|
3
3
|
module ActiveRecord
|
4
4
|
module Associations
|
5
|
-
#
|
6
|
-
#
|
7
|
-
# object, known as the <tt>@target</tt>. The kind of association any proxy is
|
8
|
-
# about is available in <tt>@reflection</tt>. That's an instance of the class
|
9
|
-
# ActiveRecord::Reflection::AssociationReflection.
|
5
|
+
# Collection proxies in Active Record are middlemen between an
|
6
|
+
# <tt>association</tt>, and its <tt>target</tt> result set.
|
10
7
|
#
|
11
8
|
# For example, given
|
12
9
|
#
|
@@ -16,21 +13,21 @@ module ActiveRecord
|
|
16
13
|
#
|
17
14
|
# blog = Blog.first
|
18
15
|
#
|
19
|
-
#
|
20
|
-
# <tt
|
21
|
-
#
|
16
|
+
# The collection proxy returned by <tt>blog.posts</tt> is built from a
|
17
|
+
# <tt>:has_many</tt> <tt>association</tt>, and delegates to a collection
|
18
|
+
# of posts as the <tt>target</tt>.
|
22
19
|
#
|
23
|
-
# This class delegates unknown methods to <tt
|
24
|
-
#
|
20
|
+
# This class delegates unknown methods to the <tt>association</tt>'s
|
21
|
+
# relation class via a delegate cache.
|
25
22
|
#
|
26
|
-
# The <tt
|
23
|
+
# The <tt>target</tt> result set is not loaded until needed. For example,
|
27
24
|
#
|
28
25
|
# blog.posts.count
|
29
26
|
#
|
30
27
|
# is computed directly through SQL and does not trigger by itself the
|
31
28
|
# instantiation of the actual post records.
|
32
29
|
class CollectionProxy < Relation
|
33
|
-
def initialize(klass, association) #:nodoc:
|
30
|
+
def initialize(klass, association, **) #:nodoc:
|
34
31
|
@association = association
|
35
32
|
super klass
|
36
33
|
|
@@ -54,6 +51,7 @@ module ActiveRecord
|
|
54
51
|
def loaded?
|
55
52
|
@association.loaded?
|
56
53
|
end
|
54
|
+
alias :loaded :loaded?
|
57
55
|
|
58
56
|
##
|
59
57
|
# :method: select
|
@@ -103,7 +101,7 @@ module ActiveRecord
|
|
103
101
|
# converting them into an array and iterating through them using
|
104
102
|
# Array#select.
|
105
103
|
#
|
106
|
-
# person.pets.select { |pet| pet.name
|
104
|
+
# person.pets.select { |pet| /oo/.match?(pet.name) }
|
107
105
|
# # => [
|
108
106
|
# # #<Pet id: 2, name: "Spook", person_id: 1>,
|
109
107
|
# # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
|
@@ -376,7 +374,7 @@ module ActiveRecord
|
|
376
374
|
# person.pets
|
377
375
|
# # => [#<Pet id: 1, name: "Gorby", group: "cats", person_id: 1>]
|
378
376
|
#
|
379
|
-
# other_pets = [Pet.new(name: 'Puff', group: 'celebrities']
|
377
|
+
# other_pets = [Pet.new(name: 'Puff', group: 'celebrities')]
|
380
378
|
#
|
381
379
|
# person.pets.replace(other_pets)
|
382
380
|
#
|
@@ -923,7 +921,7 @@ module ActiveRecord
|
|
923
921
|
!!@association.include?(record)
|
924
922
|
end
|
925
923
|
|
926
|
-
def proxy_association
|
924
|
+
def proxy_association # :nodoc:
|
927
925
|
@association
|
928
926
|
end
|
929
927
|
|
@@ -1005,7 +1003,7 @@ module ActiveRecord
|
|
1005
1003
|
end
|
1006
1004
|
|
1007
1005
|
# Adds one or more +records+ to the collection by setting their foreign keys
|
1008
|
-
# to the association's primary key. Since
|
1006
|
+
# to the association's primary key. Since <tt><<</tt> flattens its argument list and
|
1009
1007
|
# inserts each record, +push+ and +concat+ behave identically. Returns +self+
|
1010
1008
|
# so several appends may be chained together.
|
1011
1009
|
#
|
@@ -1032,7 +1030,7 @@ module ActiveRecord
|
|
1032
1030
|
alias_method :append, :<<
|
1033
1031
|
alias_method :concat, :<<
|
1034
1032
|
|
1035
|
-
def prepend(*args)
|
1033
|
+
def prepend(*args) # :nodoc:
|
1036
1034
|
raise NoMethodError, "prepend on association is not defined. Please use <<, push or append"
|
1037
1035
|
end
|
1038
1036
|
|
@@ -1062,7 +1060,7 @@ module ActiveRecord
|
|
1062
1060
|
# person.pets.reload # fetches pets from the database
|
1063
1061
|
# # => [#<Pet id: 1, name: "Snoop", group: "dogs", person_id: 1>]
|
1064
1062
|
def reload
|
1065
|
-
proxy_association.reload
|
1063
|
+
proxy_association.reload(true)
|
1066
1064
|
reset_scope
|
1067
1065
|
end
|
1068
1066
|
|
@@ -1089,22 +1087,28 @@ module ActiveRecord
|
|
1089
1087
|
end
|
1090
1088
|
|
1091
1089
|
def reset_scope # :nodoc:
|
1092
|
-
@offsets =
|
1090
|
+
@offsets = @take = nil
|
1093
1091
|
@scope = nil
|
1094
1092
|
self
|
1095
1093
|
end
|
1096
1094
|
|
1095
|
+
def inspect # :nodoc:
|
1096
|
+
load_target if find_from_target?
|
1097
|
+
super
|
1098
|
+
end
|
1099
|
+
|
1097
1100
|
delegate_methods = [
|
1098
1101
|
QueryMethods,
|
1099
1102
|
SpawnMethods,
|
1100
1103
|
].flat_map { |klass|
|
1101
1104
|
klass.public_instance_methods(false)
|
1102
|
-
} - self.public_instance_methods(false) - [:select] + [
|
1105
|
+
} - self.public_instance_methods(false) - [:select] + [
|
1106
|
+
:scoping, :values, :insert, :insert_all, :insert!, :insert_all!, :upsert, :upsert_all
|
1107
|
+
]
|
1103
1108
|
|
1104
1109
|
delegate(*delegate_methods, to: :scope)
|
1105
1110
|
|
1106
1111
|
private
|
1107
|
-
|
1108
1112
|
def find_nth_with_limit(index, limit)
|
1109
1113
|
load_target if find_from_target?
|
1110
1114
|
super
|
@@ -9,5 +9,25 @@ module ActiveRecord::Associations
|
|
9
9
|
false
|
10
10
|
end
|
11
11
|
end
|
12
|
+
|
13
|
+
def nullified_owner_attributes
|
14
|
+
Hash.new.tap do |attrs|
|
15
|
+
attrs[reflection.foreign_key] = nil
|
16
|
+
attrs[reflection.type] = nil if reflection.type.present?
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
# Sets the owner attributes on the given record
|
22
|
+
def set_owner_attributes(record)
|
23
|
+
return if options[:through]
|
24
|
+
|
25
|
+
key = owner._read_attribute(reflection.join_foreign_key)
|
26
|
+
record._write_attribute(reflection.join_primary_key, key)
|
27
|
+
|
28
|
+
if reflection.type
|
29
|
+
record._write_attribute(reflection.type, owner.class.polymorphic_name)
|
30
|
+
end
|
31
|
+
end
|
12
32
|
end
|
13
33
|
end
|
@@ -26,6 +26,28 @@ module ActiveRecord
|
|
26
26
|
# No point in executing the counter update since we're going to destroy the parent anyway
|
27
27
|
load_target.each { |t| t.destroyed_by_association = reflection }
|
28
28
|
destroy_all
|
29
|
+
when :destroy_async
|
30
|
+
load_target.each do |t|
|
31
|
+
t.destroyed_by_association = reflection
|
32
|
+
end
|
33
|
+
|
34
|
+
unless target.empty?
|
35
|
+
association_class = target.first.class
|
36
|
+
primary_key_column = association_class.primary_key.to_sym
|
37
|
+
|
38
|
+
ids = target.collect do |assoc|
|
39
|
+
assoc.public_send(primary_key_column)
|
40
|
+
end
|
41
|
+
|
42
|
+
enqueue_destroy_association(
|
43
|
+
owner_model_name: owner.class.to_s,
|
44
|
+
owner_id: owner.id,
|
45
|
+
association_class: reflection.klass.to_s,
|
46
|
+
association_ids: ids,
|
47
|
+
association_primary_key_column: primary_key_column,
|
48
|
+
ensuring_owner_was_method: options.fetch(:ensuring_owner_was, nil)
|
49
|
+
)
|
50
|
+
end
|
29
51
|
else
|
30
52
|
delete_all
|
31
53
|
end
|
@@ -36,16 +58,7 @@ module ActiveRecord
|
|
36
58
|
super
|
37
59
|
end
|
38
60
|
|
39
|
-
def empty?
|
40
|
-
if reflection.has_cached_counter?
|
41
|
-
size.zero?
|
42
|
-
else
|
43
|
-
super
|
44
|
-
end
|
45
|
-
end
|
46
|
-
|
47
61
|
private
|
48
|
-
|
49
62
|
# Returns the number of records in this collection.
|
50
63
|
#
|
51
64
|
# If the association has a counter cache it gets that value. Otherwise
|
@@ -61,7 +74,7 @@ module ActiveRecord
|
|
61
74
|
# the loaded flag is set to true as well.
|
62
75
|
def count_records
|
63
76
|
count = if reflection.has_cached_counter?
|
64
|
-
owner.
|
77
|
+
owner.read_attribute(reflection.counter_cache_column).to_i
|
65
78
|
else
|
66
79
|
scope.count(:all)
|
67
80
|
end
|
@@ -69,7 +82,7 @@ module ActiveRecord
|
|
69
82
|
# If there's nothing in the database and @target has no new records
|
70
83
|
# we are certain the current target is an empty array. This is a
|
71
84
|
# documented side-effect of the method that may avoid an extra SELECT.
|
72
|
-
|
85
|
+
loaded! if count == 0
|
73
86
|
|
74
87
|
[association_scope.limit_value, count].compact.min
|
75
88
|
end
|
@@ -84,7 +97,7 @@ module ActiveRecord
|
|
84
97
|
if reflection.counter_must_be_updated_by_has_many?
|
85
98
|
counter = reflection.counter_cache_column
|
86
99
|
owner.increment(counter, difference)
|
87
|
-
owner.send(:
|
100
|
+
owner.send(:"clear_#{counter}_change")
|
88
101
|
end
|
89
102
|
end
|
90
103
|
|
@@ -92,7 +105,7 @@ module ActiveRecord
|
|
92
105
|
if method == :delete_all
|
93
106
|
scope.delete_all
|
94
107
|
else
|
95
|
-
scope.update_all(
|
108
|
+
scope.update_all(nullified_owner_attributes)
|
96
109
|
end
|
97
110
|
end
|
98
111
|
|
@@ -8,7 +8,7 @@ module ActiveRecord
|
|
8
8
|
|
9
9
|
def initialize(owner, reflection)
|
10
10
|
super
|
11
|
-
@through_records = {}
|
11
|
+
@through_records = {}.compare_by_identity
|
12
12
|
end
|
13
13
|
|
14
14
|
def concat(*records)
|
@@ -21,20 +21,6 @@ module ActiveRecord
|
|
21
21
|
super
|
22
22
|
end
|
23
23
|
|
24
|
-
def concat_records(records)
|
25
|
-
ensure_not_nested
|
26
|
-
|
27
|
-
records = super(records, true)
|
28
|
-
|
29
|
-
if owner.new_record? && records
|
30
|
-
records.flatten.each do |record|
|
31
|
-
build_through_record(record)
|
32
|
-
end
|
33
|
-
end
|
34
|
-
|
35
|
-
records
|
36
|
-
end
|
37
|
-
|
38
24
|
def insert_record(record, validate = true, raise = false)
|
39
25
|
ensure_not_nested
|
40
26
|
|
@@ -48,13 +34,27 @@ module ActiveRecord
|
|
48
34
|
end
|
49
35
|
|
50
36
|
private
|
37
|
+
def concat_records(records)
|
38
|
+
ensure_not_nested
|
39
|
+
|
40
|
+
records = super(records, true)
|
41
|
+
|
42
|
+
if owner.new_record? && records
|
43
|
+
records.flatten.each do |record|
|
44
|
+
build_through_record(record)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
records
|
49
|
+
end
|
50
|
+
|
51
51
|
# The through record (built with build_record) is temporarily cached
|
52
52
|
# so that it may be reused if insert_record is subsequently called.
|
53
53
|
#
|
54
54
|
# However, after insert_record has been called, the cache is cleared in
|
55
55
|
# order to allow multiple instances of the same record in an association.
|
56
56
|
def build_through_record(record)
|
57
|
-
@through_records[record
|
57
|
+
@through_records[record] ||= begin
|
58
58
|
ensure_mutable
|
59
59
|
|
60
60
|
attributes = through_scope_attributes
|
@@ -65,7 +65,10 @@ module ActiveRecord
|
|
65
65
|
end
|
66
66
|
end
|
67
67
|
|
68
|
+
attr_reader :through_scope
|
69
|
+
|
68
70
|
def through_scope_attributes
|
71
|
+
scope = through_scope || self.scope
|
69
72
|
scope.where_values_hash(through_association.reflection.name.to_s).
|
70
73
|
except!(through_association.reflection.foreign_key,
|
71
74
|
through_association.reflection.klass.inheritance_column)
|
@@ -77,12 +80,13 @@ module ActiveRecord
|
|
77
80
|
association.save!
|
78
81
|
end
|
79
82
|
ensure
|
80
|
-
@through_records.delete(record
|
83
|
+
@through_records.delete(record)
|
81
84
|
end
|
82
85
|
|
83
86
|
def build_record(attributes)
|
84
87
|
ensure_not_nested
|
85
88
|
|
89
|
+
@through_scope = scope
|
86
90
|
record = super
|
87
91
|
|
88
92
|
inverse = source_reflection.inverse_of
|
@@ -95,6 +99,8 @@ module ActiveRecord
|
|
95
99
|
end
|
96
100
|
|
97
101
|
record
|
102
|
+
ensure
|
103
|
+
@through_scope = nil
|
98
104
|
end
|
99
105
|
|
100
106
|
def remove_records(existing_records, records, method)
|
@@ -202,7 +208,7 @@ module ActiveRecord
|
|
202
208
|
end
|
203
209
|
end
|
204
210
|
|
205
|
-
@through_records.delete(record
|
211
|
+
@through_records.delete(record)
|
206
212
|
end
|
207
213
|
end
|
208
214
|
|
@@ -23,35 +23,6 @@ module ActiveRecord
|
|
23
23
|
end
|
24
24
|
end
|
25
25
|
|
26
|
-
def replace(record, save = true)
|
27
|
-
raise_on_type_mismatch!(record) if record
|
28
|
-
load_target
|
29
|
-
|
30
|
-
return target unless target || record
|
31
|
-
|
32
|
-
assigning_another_record = target != record
|
33
|
-
if assigning_another_record || record.has_changes_to_save?
|
34
|
-
save &&= owner.persisted?
|
35
|
-
|
36
|
-
transaction_if(save) do
|
37
|
-
remove_target!(options[:dependent]) if target && !target.destroyed? && assigning_another_record
|
38
|
-
|
39
|
-
if record
|
40
|
-
set_owner_attributes(record)
|
41
|
-
set_inverse_instance(record)
|
42
|
-
|
43
|
-
if save && !record.save
|
44
|
-
nullify_owner_attributes(record)
|
45
|
-
set_owner_attributes(target) if target
|
46
|
-
raise RecordNotSaved, "Failed to save the new associated #{reflection.name}."
|
47
|
-
end
|
48
|
-
end
|
49
|
-
end
|
50
|
-
end
|
51
|
-
|
52
|
-
self.target = record
|
53
|
-
end
|
54
|
-
|
55
26
|
def delete(method = options[:dependent])
|
56
27
|
if load_target
|
57
28
|
case method
|
@@ -61,13 +32,52 @@ module ActiveRecord
|
|
61
32
|
target.destroyed_by_association = reflection
|
62
33
|
target.destroy
|
63
34
|
throw(:abort) unless target.destroyed?
|
35
|
+
when :destroy_async
|
36
|
+
primary_key_column = target.class.primary_key.to_sym
|
37
|
+
id = target.public_send(primary_key_column)
|
38
|
+
|
39
|
+
enqueue_destroy_association(
|
40
|
+
owner_model_name: owner.class.to_s,
|
41
|
+
owner_id: owner.id,
|
42
|
+
association_class: reflection.klass.to_s,
|
43
|
+
association_ids: [id],
|
44
|
+
association_primary_key_column: primary_key_column,
|
45
|
+
ensuring_owner_was_method: options.fetch(:ensuring_owner_was, nil)
|
46
|
+
)
|
64
47
|
when :nullify
|
65
|
-
target.update_columns(
|
48
|
+
target.update_columns(nullified_owner_attributes) if target.persisted?
|
66
49
|
end
|
67
50
|
end
|
68
51
|
end
|
69
52
|
|
70
53
|
private
|
54
|
+
def replace(record, save = true)
|
55
|
+
raise_on_type_mismatch!(record) if record
|
56
|
+
|
57
|
+
return target unless load_target || record
|
58
|
+
|
59
|
+
assigning_another_record = target != record
|
60
|
+
if assigning_another_record || record.has_changes_to_save?
|
61
|
+
save &&= owner.persisted?
|
62
|
+
|
63
|
+
transaction_if(save) do
|
64
|
+
remove_target!(options[:dependent]) if target && !target.destroyed? && assigning_another_record
|
65
|
+
|
66
|
+
if record
|
67
|
+
set_owner_attributes(record)
|
68
|
+
set_inverse_instance(record)
|
69
|
+
|
70
|
+
if save && !record.save
|
71
|
+
nullify_owner_attributes(record)
|
72
|
+
set_owner_attributes(target) if target
|
73
|
+
raise RecordNotSaved, "Failed to save the new associated #{reflection.name}."
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
self.target = record
|
80
|
+
end
|
71
81
|
|
72
82
|
# The reason that the save param for replace is false, if for create (not just build),
|
73
83
|
# is because the setting of the foreign keys is actually handled by the scoping when
|
@@ -83,7 +93,9 @@ module ActiveRecord
|
|
83
93
|
target.delete
|
84
94
|
when :destroy
|
85
95
|
target.destroyed_by_association = reflection
|
86
|
-
target.
|
96
|
+
if target.persisted?
|
97
|
+
target.destroy
|
98
|
+
end
|
87
99
|
else
|
88
100
|
nullify_owner_attributes(target)
|
89
101
|
remove_inverse_instance(target)
|
@@ -6,12 +6,12 @@ module ActiveRecord
|
|
6
6
|
class HasOneThroughAssociation < HasOneAssociation #:nodoc:
|
7
7
|
include ThroughAssociation
|
8
8
|
|
9
|
-
def replace(record, save = true)
|
10
|
-
create_through_record(record, save)
|
11
|
-
self.target = record
|
12
|
-
end
|
13
|
-
|
14
9
|
private
|
10
|
+
def replace(record, save = true)
|
11
|
+
create_through_record(record, save)
|
12
|
+
self.target = record
|
13
|
+
end
|
14
|
+
|
15
15
|
def create_through_record(record, save)
|
16
16
|
ensure_not_nested
|
17
17
|
|