activerecord 6.0.6.1 → 6.1.7.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +1152 -779
- data/MIT-LICENSE +1 -1
- data/README.rdoc +2 -2
- data/lib/active_record/aggregations.rb +5 -5
- data/lib/active_record/association_relation.rb +30 -12
- data/lib/active_record/associations/alias_tracker.rb +19 -15
- data/lib/active_record/associations/association.rb +49 -26
- data/lib/active_record/associations/association_scope.rb +18 -20
- data/lib/active_record/associations/belongs_to_association.rb +23 -10
- data/lib/active_record/associations/belongs_to_polymorphic_association.rb +8 -3
- data/lib/active_record/associations/builder/association.rb +32 -5
- data/lib/active_record/associations/builder/belongs_to.rb +10 -7
- data/lib/active_record/associations/builder/collection_association.rb +5 -4
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +0 -1
- data/lib/active_record/associations/builder/has_many.rb +6 -2
- data/lib/active_record/associations/builder/has_one.rb +11 -14
- data/lib/active_record/associations/builder/singular_association.rb +1 -1
- data/lib/active_record/associations/collection_association.rb +32 -18
- data/lib/active_record/associations/collection_proxy.rb +12 -5
- data/lib/active_record/associations/foreign_association.rb +13 -0
- data/lib/active_record/associations/has_many_association.rb +24 -2
- data/lib/active_record/associations/has_many_through_association.rb +10 -4
- data/lib/active_record/associations/has_one_association.rb +15 -1
- data/lib/active_record/associations/join_dependency/join_association.rb +37 -21
- data/lib/active_record/associations/join_dependency/join_part.rb +1 -1
- data/lib/active_record/associations/join_dependency.rb +63 -49
- data/lib/active_record/associations/preloader/association.rb +14 -8
- data/lib/active_record/associations/preloader/through_association.rb +1 -1
- data/lib/active_record/associations/preloader.rb +5 -3
- data/lib/active_record/associations/singular_association.rb +1 -1
- data/lib/active_record/associations.rb +118 -11
- data/lib/active_record/attribute_assignment.rb +10 -8
- data/lib/active_record/attribute_methods/before_type_cast.rb +13 -9
- data/lib/active_record/attribute_methods/dirty.rb +1 -11
- data/lib/active_record/attribute_methods/primary_key.rb +6 -2
- data/lib/active_record/attribute_methods/query.rb +3 -6
- data/lib/active_record/attribute_methods/read.rb +8 -11
- data/lib/active_record/attribute_methods/serialization.rb +11 -5
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +12 -13
- data/lib/active_record/attribute_methods/write.rb +12 -20
- data/lib/active_record/attribute_methods.rb +64 -54
- data/lib/active_record/attributes.rb +33 -8
- data/lib/active_record/autosave_association.rb +47 -30
- data/lib/active_record/base.rb +2 -14
- data/lib/active_record/callbacks.rb +152 -22
- data/lib/active_record/coders/yaml_column.rb +1 -1
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +185 -134
- data/lib/active_record/connection_adapters/abstract/database_limits.rb +2 -44
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +66 -23
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +3 -8
- data/lib/active_record/connection_adapters/abstract/quoting.rb +34 -34
- data/lib/active_record/connection_adapters/abstract/savepoints.rb +3 -3
- data/lib/active_record/connection_adapters/abstract/schema_creation.rb +153 -116
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +114 -26
- data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +3 -3
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +228 -83
- data/lib/active_record/connection_adapters/abstract/transaction.rb +92 -33
- data/lib/active_record/connection_adapters/abstract_adapter.rb +52 -76
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +123 -87
- data/lib/active_record/connection_adapters/column.rb +15 -1
- 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/database_statements.rb +24 -24
- data/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb +1 -1
- data/lib/active_record/connection_adapters/mysql/quoting.rb +18 -3
- data/lib/active_record/connection_adapters/mysql/schema_creation.rb +32 -6
- data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +8 -0
- data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +5 -2
- data/lib/active_record/connection_adapters/mysql/schema_statements.rb +7 -4
- data/lib/active_record/connection_adapters/mysql/type_metadata.rb +10 -1
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +31 -12
- 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 +24 -1
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +14 -53
- 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 +2 -2
- data/lib/active_record/connection_adapters/postgresql/oid/interval.rb +49 -0
- data/lib/active_record/connection_adapters/postgresql/oid/legacy_point.rb +2 -2
- data/lib/active_record/connection_adapters/postgresql/oid/macaddr.rb +25 -0
- data/lib/active_record/connection_adapters/postgresql/oid/point.rb +2 -2
- data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +11 -1
- data/lib/active_record/connection_adapters/postgresql/oid.rb +2 -0
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +30 -4
- data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +5 -1
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +61 -29
- data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +8 -0
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +75 -64
- data/lib/active_record/connection_adapters/schema_cache.rb +130 -15
- data/lib/active_record/connection_adapters/sql_type_metadata.rb +8 -0
- data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +32 -5
- data/lib/active_record/connection_adapters/sqlite3/quoting.rb +1 -1
- data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +5 -1
- data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +36 -3
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +48 -50
- data/lib/active_record/connection_adapters.rb +52 -0
- data/lib/active_record/connection_handling.rb +218 -71
- data/lib/active_record/core.rb +264 -63
- data/lib/active_record/database_configurations/connection_url_resolver.rb +99 -0
- data/lib/active_record/database_configurations/database_config.rb +52 -9
- data/lib/active_record/database_configurations/hash_config.rb +54 -8
- data/lib/active_record/database_configurations/url_config.rb +15 -40
- data/lib/active_record/database_configurations.rb +125 -85
- data/lib/active_record/delegated_type.rb +209 -0
- data/lib/active_record/destroy_association_async_job.rb +36 -0
- data/lib/active_record/enum.rb +69 -34
- data/lib/active_record/errors.rb +47 -12
- data/lib/active_record/explain.rb +9 -4
- 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 +1 -2
- data/lib/active_record/fixture_set/render_context.rb +1 -1
- data/lib/active_record/fixture_set/table_row.rb +2 -2
- data/lib/active_record/fixtures.rb +58 -9
- data/lib/active_record/gem_version.rb +3 -3
- data/lib/active_record/inheritance.rb +40 -18
- data/lib/active_record/insert_all.rb +38 -5
- data/lib/active_record/integration.rb +3 -5
- data/lib/active_record/internal_metadata.rb +18 -7
- data/lib/active_record/legacy_yaml_adapter.rb +7 -3
- data/lib/active_record/locking/optimistic.rb +24 -17
- data/lib/active_record/locking/pessimistic.rb +6 -2
- data/lib/active_record/log_subscriber.rb +27 -8
- data/lib/active_record/middleware/database_selector/resolver/session.rb +3 -0
- data/lib/active_record/middleware/database_selector/resolver.rb +5 -0
- data/lib/active_record/middleware/database_selector.rb +4 -1
- data/lib/active_record/migration/command_recorder.rb +47 -27
- data/lib/active_record/migration/compatibility.rb +72 -18
- data/lib/active_record/migration.rb +114 -84
- data/lib/active_record/model_schema.rb +89 -14
- data/lib/active_record/nested_attributes.rb +2 -3
- data/lib/active_record/no_touching.rb +1 -1
- data/lib/active_record/persistence.rb +50 -45
- data/lib/active_record/query_cache.rb +15 -5
- data/lib/active_record/querying.rb +11 -6
- data/lib/active_record/railtie.rb +64 -44
- data/lib/active_record/railties/console_sandbox.rb +2 -4
- data/lib/active_record/railties/databases.rake +279 -101
- data/lib/active_record/readonly_attributes.rb +4 -0
- data/lib/active_record/reflection.rb +60 -44
- data/lib/active_record/relation/batches/batch_enumerator.rb +25 -9
- data/lib/active_record/relation/batches.rb +38 -31
- data/lib/active_record/relation/calculations.rb +104 -43
- data/lib/active_record/relation/finder_methods.rb +44 -14
- data/lib/active_record/relation/from_clause.rb +1 -1
- data/lib/active_record/relation/merger.rb +20 -23
- data/lib/active_record/relation/predicate_builder/array_handler.rb +8 -9
- data/lib/active_record/relation/predicate_builder/association_query_value.rb +4 -5
- data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +10 -6
- data/lib/active_record/relation/predicate_builder/relation_handler.rb +1 -1
- data/lib/active_record/relation/predicate_builder.rb +61 -38
- data/lib/active_record/relation/query_methods.rb +322 -196
- data/lib/active_record/relation/record_fetch_warning.rb +3 -3
- data/lib/active_record/relation/spawn_methods.rb +8 -7
- data/lib/active_record/relation/where_clause.rb +111 -61
- data/lib/active_record/relation.rb +100 -81
- data/lib/active_record/result.rb +41 -33
- data/lib/active_record/runtime_registry.rb +2 -2
- data/lib/active_record/sanitization.rb +6 -17
- data/lib/active_record/schema_dumper.rb +34 -4
- data/lib/active_record/schema_migration.rb +2 -8
- data/lib/active_record/scoping/default.rb +1 -3
- data/lib/active_record/scoping/named.rb +1 -17
- 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 +20 -4
- data/lib/active_record/store.rb +8 -3
- data/lib/active_record/suppressor.rb +2 -2
- data/lib/active_record/table_metadata.rb +42 -51
- data/lib/active_record/tasks/database_tasks.rb +140 -113
- data/lib/active_record/tasks/mysql_database_tasks.rb +34 -35
- data/lib/active_record/tasks/postgresql_database_tasks.rb +24 -26
- data/lib/active_record/tasks/sqlite_database_tasks.rb +13 -9
- data/lib/active_record/test_databases.rb +5 -4
- data/lib/active_record/test_fixtures.rb +79 -31
- data/lib/active_record/timestamp.rb +4 -6
- data/lib/active_record/touch_later.rb +21 -21
- data/lib/active_record/transactions.rb +19 -66
- data/lib/active_record/type/serialized.rb +6 -2
- data/lib/active_record/type.rb +8 -1
- data/lib/active_record/type_caster/connection.rb +0 -1
- data/lib/active_record/type_caster/map.rb +8 -5
- data/lib/active_record/validations/associated.rb +1 -1
- data/lib/active_record/validations/numericality.rb +35 -0
- data/lib/active_record/validations/uniqueness.rb +24 -4
- data/lib/active_record/validations.rb +1 -0
- data/lib/active_record.rb +7 -14
- data/lib/arel/attributes/attribute.rb +4 -0
- data/lib/arel/collectors/bind.rb +5 -0
- data/lib/arel/collectors/composite.rb +8 -0
- data/lib/arel/collectors/sql_string.rb +7 -0
- data/lib/arel/collectors/substitute_binds.rb +7 -0
- data/lib/arel/nodes/binary.rb +82 -8
- data/lib/arel/nodes/bind_param.rb +8 -0
- data/lib/arel/nodes/casted.rb +21 -9
- data/lib/arel/nodes/equality.rb +6 -9
- data/lib/arel/nodes/grouping.rb +3 -0
- data/lib/arel/nodes/homogeneous_in.rb +76 -0
- data/lib/arel/nodes/in.rb +8 -1
- data/lib/arel/nodes/infix_operation.rb +13 -1
- data/lib/arel/nodes/join_source.rb +1 -1
- data/lib/arel/nodes/node.rb +7 -6
- data/lib/arel/nodes/ordering.rb +27 -0
- data/lib/arel/nodes/sql_literal.rb +3 -0
- data/lib/arel/nodes/table_alias.rb +7 -3
- data/lib/arel/nodes/unary.rb +0 -1
- data/lib/arel/nodes.rb +3 -1
- data/lib/arel/predications.rb +12 -18
- data/lib/arel/select_manager.rb +1 -2
- data/lib/arel/table.rb +13 -5
- data/lib/arel/visitors/dot.rb +14 -2
- data/lib/arel/visitors/mysql.rb +11 -1
- data/lib/arel/visitors/postgresql.rb +15 -4
- data/lib/arel/visitors/to_sql.rb +89 -78
- data/lib/arel/visitors.rb +0 -7
- data/lib/arel.rb +5 -13
- data/lib/rails/generators/active_record/migration/migration_generator.rb +1 -0
- data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +2 -0
- data/lib/rails/generators/active_record/migration/templates/migration.rb.tt +3 -3
- data/lib/rails/generators/active_record/migration.rb +6 -1
- 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
- metadata +25 -26
- data/lib/active_record/advisory_lock_base.rb +0 -18
- data/lib/active_record/attribute_decorators.rb +0 -88
- data/lib/active_record/connection_adapters/connection_specification.rb +0 -296
- data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +0 -29
- data/lib/active_record/define_callbacks.rb +0 -22
- data/lib/active_record/railties/collection_cache_association_loading.rb +0 -34
- data/lib/active_record/relation/predicate_builder/base_handler.rb +0 -18
- data/lib/active_record/relation/where_clause_factory.rb +0 -33
- data/lib/arel/attributes.rb +0 -22
- data/lib/arel/visitors/depth_first.rb +0 -203
- data/lib/arel/visitors/ibm_db.rb +0 -34
- data/lib/arel/visitors/informix.rb +0 -62
- data/lib/arel/visitors/mssql.rb +0 -156
- data/lib/arel/visitors/oracle.rb +0 -158
- data/lib/arel/visitors/oracle12.rb +0 -65
- data/lib/arel/visitors/where_sql.rb +0 -22
@@ -34,7 +34,7 @@ module ActiveRecord
|
|
34
34
|
Table = Struct.new(:node, :columns) do # :nodoc:
|
35
35
|
def column_aliases
|
36
36
|
t = node.table
|
37
|
-
columns.map { |column| t[column.name].as
|
37
|
+
columns.map { |column| t[column.name].as(column.alias) }
|
38
38
|
end
|
39
39
|
end
|
40
40
|
Column = Struct.new(:name, :alias)
|
@@ -78,14 +78,18 @@ module ActiveRecord
|
|
78
78
|
join_root.drop(1).map!(&:reflection)
|
79
79
|
end
|
80
80
|
|
81
|
-
def join_constraints(joins_to_add, alias_tracker)
|
81
|
+
def join_constraints(joins_to_add, alias_tracker, references)
|
82
82
|
@alias_tracker = alias_tracker
|
83
|
+
@joined_tables = {}
|
84
|
+
@references = {}
|
85
|
+
|
86
|
+
references.each do |table_name|
|
87
|
+
@references[table_name.to_sym] = table_name if table_name.is_a?(Arel::Nodes::SqlLiteral)
|
88
|
+
end unless references.empty?
|
83
89
|
|
84
|
-
construct_tables!(join_root)
|
85
90
|
joins = make_join_constraints(join_root, join_type)
|
86
91
|
|
87
92
|
joins.concat joins_to_add.flat_map { |oj|
|
88
|
-
construct_tables!(oj.join_root)
|
89
93
|
if join_root.match? oj.join_root
|
90
94
|
walk(join_root, oj.join_root, oj.join_type)
|
91
95
|
else
|
@@ -94,26 +98,33 @@ module ActiveRecord
|
|
94
98
|
}
|
95
99
|
end
|
96
100
|
|
97
|
-
def instantiate(result_set, &block)
|
101
|
+
def instantiate(result_set, strict_loading_value, &block)
|
98
102
|
primary_key = aliases.column_alias(join_root, join_root.primary_key)
|
99
103
|
|
100
|
-
seen = Hash.new { |i,
|
101
|
-
i[
|
104
|
+
seen = Hash.new { |i, parent|
|
105
|
+
i[parent] = Hash.new { |j, child_class|
|
102
106
|
j[child_class] = {}
|
103
107
|
}
|
104
|
-
}
|
108
|
+
}.compare_by_identity
|
105
109
|
|
106
110
|
model_cache = Hash.new { |h, klass| h[klass] = {} }
|
107
111
|
parents = model_cache[join_root]
|
108
112
|
|
109
113
|
column_aliases = aliases.column_aliases(join_root)
|
110
|
-
column_names =
|
114
|
+
column_names = []
|
115
|
+
|
116
|
+
result_set.columns.each do |name|
|
117
|
+
column_names << name unless /\At\d+_r\d+\z/.match?(name)
|
118
|
+
end
|
111
119
|
|
112
120
|
if column_names.empty?
|
113
121
|
column_types = {}
|
114
122
|
else
|
115
123
|
column_types = result_set.column_types
|
116
|
-
|
124
|
+
unless column_types.empty?
|
125
|
+
attribute_types = join_root.attribute_types
|
126
|
+
column_types = column_types.slice(*column_names).delete_if { |k, _| attribute_types.key?(k) }
|
127
|
+
end
|
117
128
|
column_aliases += column_names.map! { |name| Aliases::Column.new(name, name) }
|
118
129
|
end
|
119
130
|
|
@@ -128,7 +139,7 @@ module ActiveRecord
|
|
128
139
|
result_set.each { |row_hash|
|
129
140
|
parent_key = primary_key ? row_hash[primary_key] : row_hash
|
130
141
|
parent = parents[parent_key] ||= join_root.instantiate(row_hash, column_aliases, column_types, &block)
|
131
|
-
construct(parent, join_root, row_hash, seen, model_cache)
|
142
|
+
construct(parent, join_root, row_hash, seen, model_cache, strict_loading_value)
|
132
143
|
}
|
133
144
|
end
|
134
145
|
|
@@ -136,37 +147,36 @@ module ActiveRecord
|
|
136
147
|
end
|
137
148
|
|
138
149
|
def apply_column_aliases(relation)
|
150
|
+
@join_root_alias = relation.select_values.empty?
|
139
151
|
relation._select!(-> { aliases.columns })
|
140
152
|
end
|
141
153
|
|
154
|
+
def each(&block)
|
155
|
+
join_root.each(&block)
|
156
|
+
end
|
157
|
+
|
142
158
|
protected
|
143
159
|
attr_reader :join_root, :join_type
|
144
160
|
|
145
161
|
private
|
146
|
-
attr_reader :alias_tracker
|
147
|
-
|
148
|
-
def explicit_selections(root_column_aliases, result_set)
|
149
|
-
root_names = root_column_aliases.map(&:name).to_set
|
150
|
-
result_set.columns.each_with_object([]) do |name, result|
|
151
|
-
result << name unless /\At\d+_r\d+\z/.match?(name) || root_names.include?(name)
|
152
|
-
end
|
153
|
-
end
|
162
|
+
attr_reader :alias_tracker, :join_root_alias
|
154
163
|
|
155
164
|
def aliases
|
156
165
|
@aliases ||= Aliases.new join_root.each_with_index.map { |join_part, i|
|
157
|
-
|
166
|
+
column_names = if join_part == join_root && !join_root_alias
|
167
|
+
primary_key = join_root.primary_key
|
168
|
+
primary_key ? [primary_key] : []
|
169
|
+
else
|
170
|
+
join_part.column_names
|
171
|
+
end
|
172
|
+
|
173
|
+
columns = column_names.each_with_index.map { |column_name, j|
|
158
174
|
Aliases::Column.new column_name, "t#{i}_r#{j}"
|
159
175
|
}
|
160
176
|
Aliases::Table.new(join_part, columns)
|
161
177
|
}
|
162
178
|
end
|
163
179
|
|
164
|
-
def construct_tables!(join_root)
|
165
|
-
join_root.each_children do |parent, child|
|
166
|
-
child.tables = table_aliases_for(parent, child)
|
167
|
-
end
|
168
|
-
end
|
169
|
-
|
170
180
|
def make_join_constraints(join_root, join_type)
|
171
181
|
join_root.children.flat_map do |child|
|
172
182
|
make_constraints(join_root, child, join_type)
|
@@ -176,23 +186,25 @@ module ActiveRecord
|
|
176
186
|
def make_constraints(parent, child, join_type)
|
177
187
|
foreign_table = parent.table
|
178
188
|
foreign_klass = parent.base_klass
|
179
|
-
|
180
|
-
|
181
|
-
|
189
|
+
child.join_constraints(foreign_table, foreign_klass, join_type, alias_tracker) do |reflection|
|
190
|
+
table, terminated = @joined_tables[reflection]
|
191
|
+
root = reflection == child.reflection
|
182
192
|
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
193
|
+
if table && (!root || !terminated)
|
194
|
+
@joined_tables[reflection] = [table, root] if root
|
195
|
+
next table, true
|
196
|
+
end
|
197
|
+
|
198
|
+
table_name = @references[reflection.name.to_sym]&.to_s
|
199
|
+
|
200
|
+
table = alias_tracker.aliased_table_for(reflection.klass.arel_table, table_name) do
|
201
|
+
name = reflection.alias_candidate(parent.table_name)
|
202
|
+
root ? name : "#{name}_join"
|
203
|
+
end
|
192
204
|
|
193
|
-
|
194
|
-
|
195
|
-
|
205
|
+
@joined_tables[reflection] ||= [table, root] if join_type == Arel::Nodes::OuterJoin
|
206
|
+
table
|
207
|
+
end.concat child.children.flat_map { |c| make_constraints(child, c, join_type) }
|
196
208
|
end
|
197
209
|
|
198
210
|
def walk(left, right, join_type)
|
@@ -223,7 +235,7 @@ module ActiveRecord
|
|
223
235
|
end
|
224
236
|
end
|
225
237
|
|
226
|
-
def construct(ar_parent, parent, row, seen, model_cache)
|
238
|
+
def construct(ar_parent, parent, row, seen, model_cache, strict_loading_value)
|
227
239
|
return if ar_parent.nil?
|
228
240
|
|
229
241
|
parent.children.each do |node|
|
@@ -232,7 +244,7 @@ module ActiveRecord
|
|
232
244
|
other.loaded!
|
233
245
|
elsif ar_parent.association_cached?(node.reflection.name)
|
234
246
|
model = ar_parent.association(node.reflection.name).target
|
235
|
-
construct(model, node, row, seen, model_cache)
|
247
|
+
construct(model, node, row, seen, model_cache, strict_loading_value)
|
236
248
|
next
|
237
249
|
end
|
238
250
|
|
@@ -244,24 +256,25 @@ module ActiveRecord
|
|
244
256
|
next
|
245
257
|
end
|
246
258
|
|
247
|
-
model = seen[ar_parent
|
259
|
+
model = seen[ar_parent][node][id]
|
248
260
|
|
249
261
|
if model
|
250
|
-
construct(model, node, row, seen, model_cache)
|
262
|
+
construct(model, node, row, seen, model_cache, strict_loading_value)
|
251
263
|
else
|
252
|
-
model = construct_model(ar_parent, node, row, model_cache, id)
|
264
|
+
model = construct_model(ar_parent, node, row, model_cache, id, strict_loading_value)
|
253
265
|
|
254
|
-
seen[ar_parent
|
255
|
-
construct(model, node, row, seen, model_cache)
|
266
|
+
seen[ar_parent][node][id] = model
|
267
|
+
construct(model, node, row, seen, model_cache, strict_loading_value)
|
256
268
|
end
|
257
269
|
end
|
258
270
|
end
|
259
271
|
|
260
|
-
def construct_model(record, node, row, model_cache, id)
|
272
|
+
def construct_model(record, node, row, model_cache, id, strict_loading_value)
|
261
273
|
other = record.association(node.reflection.name)
|
262
274
|
|
263
275
|
model = model_cache[node][id] ||=
|
264
276
|
node.instantiate(row, aliases.column_aliases(node)) do |m|
|
277
|
+
m.strict_loading! if strict_loading_value
|
265
278
|
other.set_inverse_instance(m)
|
266
279
|
end
|
267
280
|
|
@@ -272,6 +285,7 @@ module ActiveRecord
|
|
272
285
|
end
|
273
286
|
|
274
287
|
model.readonly! if node.readonly?
|
288
|
+
model.strict_loading! if node.strict_loading?
|
275
289
|
model
|
276
290
|
end
|
277
291
|
end
|
@@ -45,18 +45,18 @@ module ActiveRecord
|
|
45
45
|
raw_records = owner_keys.empty? ? [] : records_for(owner_keys)
|
46
46
|
|
47
47
|
@preloaded_records = raw_records.select do |record|
|
48
|
-
assignments =
|
48
|
+
assignments = false
|
49
49
|
|
50
50
|
owners_by_key[convert_key(record[association_key_name])].each do |owner|
|
51
51
|
entries = (@records_by_owner[owner] ||= [])
|
52
52
|
|
53
53
|
if reflection.collection? || entries.empty?
|
54
54
|
entries << record
|
55
|
-
assignments
|
55
|
+
assignments = true
|
56
56
|
end
|
57
57
|
end
|
58
58
|
|
59
|
-
|
59
|
+
assignments
|
60
60
|
end
|
61
61
|
end
|
62
62
|
|
@@ -129,9 +129,7 @@ module ActiveRecord
|
|
129
129
|
end
|
130
130
|
|
131
131
|
def reflection_scope
|
132
|
-
@reflection_scope ||=
|
133
|
-
reflection.join_scopes(klass.arel_table, klass.predicate_builder, klass).inject(&:merge!) || klass.unscoped
|
134
|
-
end
|
132
|
+
@reflection_scope ||= reflection.join_scopes(klass.arel_table, klass.predicate_builder, klass).inject(klass.unscoped, &:merge!)
|
135
133
|
end
|
136
134
|
|
137
135
|
def build_scope
|
@@ -142,8 +140,16 @@ module ActiveRecord
|
|
142
140
|
end
|
143
141
|
|
144
142
|
scope.merge!(reflection_scope) unless reflection_scope.empty_scope?
|
145
|
-
|
146
|
-
|
143
|
+
|
144
|
+
if preload_scope && !preload_scope.empty_scope?
|
145
|
+
scope.merge!(preload_scope)
|
146
|
+
end
|
147
|
+
|
148
|
+
if preload_scope && preload_scope.strict_loading_value
|
149
|
+
scope.strict_loading
|
150
|
+
else
|
151
|
+
scope
|
152
|
+
end
|
147
153
|
end
|
148
154
|
end
|
149
155
|
end
|
@@ -1,5 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require "active_support/core_ext/enumerable"
|
4
|
+
|
3
5
|
module ActiveRecord
|
4
6
|
module Associations
|
5
7
|
# Implements the details of eager loading of Active Record associations.
|
@@ -58,7 +60,7 @@ module ActiveRecord
|
|
58
60
|
# == Parameters
|
59
61
|
# +records+ is an array of ActiveRecord::Base. This array needs not be flat,
|
60
62
|
# i.e. +records+ itself may also contain arrays of records. In any case,
|
61
|
-
# +preload_associations+ will preload
|
63
|
+
# +preload_associations+ will preload all associations records by
|
62
64
|
# flattening +records+.
|
63
65
|
#
|
64
66
|
# +associations+ specifies one or more associations that you want to
|
@@ -175,8 +177,8 @@ module ActiveRecord
|
|
175
177
|
end
|
176
178
|
|
177
179
|
def records_by_owner
|
178
|
-
@records_by_owner ||= owners.
|
179
|
-
|
180
|
+
@records_by_owner ||= owners.index_with do |owner|
|
181
|
+
Array(owner.association(reflection.name).target)
|
180
182
|
end
|
181
183
|
end
|
182
184
|
|
@@ -2,38 +2,116 @@
|
|
2
2
|
|
3
3
|
require "active_support/core_ext/enumerable"
|
4
4
|
require "active_support/core_ext/string/conversions"
|
5
|
-
require "active_support/core_ext/module/remove_method"
|
6
|
-
require "active_record/errors"
|
7
5
|
|
8
6
|
module ActiveRecord
|
9
7
|
class AssociationNotFoundError < ConfigurationError #:nodoc:
|
8
|
+
attr_reader :record, :association_name
|
10
9
|
def initialize(record = nil, association_name = nil)
|
10
|
+
@record = record
|
11
|
+
@association_name = association_name
|
11
12
|
if record && association_name
|
12
13
|
super("Association named '#{association_name}' was not found on #{record.class.name}; perhaps you misspelled it?")
|
13
14
|
else
|
14
15
|
super("Association was not found.")
|
15
16
|
end
|
16
17
|
end
|
18
|
+
|
19
|
+
class Correction
|
20
|
+
def initialize(error)
|
21
|
+
@error = error
|
22
|
+
end
|
23
|
+
|
24
|
+
def corrections
|
25
|
+
if @error.association_name
|
26
|
+
maybe_these = @error.record.class.reflections.keys
|
27
|
+
|
28
|
+
maybe_these.sort_by { |n|
|
29
|
+
DidYouMean::Jaro.distance(@error.association_name.to_s, n)
|
30
|
+
}.reverse.first(4)
|
31
|
+
else
|
32
|
+
[]
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
# We may not have DYM, and DYM might not let us register error handlers
|
38
|
+
if defined?(DidYouMean) && DidYouMean.respond_to?(:correct_error)
|
39
|
+
DidYouMean.correct_error(self, Correction)
|
40
|
+
end
|
17
41
|
end
|
18
42
|
|
19
43
|
class InverseOfAssociationNotFoundError < ActiveRecordError #:nodoc:
|
44
|
+
attr_reader :reflection, :associated_class
|
20
45
|
def initialize(reflection = nil, associated_class = nil)
|
21
46
|
if reflection
|
47
|
+
@reflection = reflection
|
48
|
+
@associated_class = associated_class.nil? ? reflection.klass : associated_class
|
22
49
|
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})")
|
23
50
|
else
|
24
51
|
super("Could not find the inverse association.")
|
25
52
|
end
|
26
53
|
end
|
54
|
+
|
55
|
+
class Correction
|
56
|
+
def initialize(error)
|
57
|
+
@error = error
|
58
|
+
end
|
59
|
+
|
60
|
+
def corrections
|
61
|
+
if @error.reflection && @error.associated_class
|
62
|
+
maybe_these = @error.associated_class.reflections.keys
|
63
|
+
|
64
|
+
maybe_these.sort_by { |n|
|
65
|
+
DidYouMean::Jaro.distance(@error.reflection.options[:inverse_of].to_s, n)
|
66
|
+
}.reverse.first(4)
|
67
|
+
else
|
68
|
+
[]
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
# We may not have DYM, and DYM might not let us register error handlers
|
74
|
+
if defined?(DidYouMean) && DidYouMean.respond_to?(:correct_error)
|
75
|
+
DidYouMean.correct_error(self, Correction)
|
76
|
+
end
|
27
77
|
end
|
28
78
|
|
29
79
|
class HasManyThroughAssociationNotFoundError < ActiveRecordError #:nodoc:
|
30
|
-
|
31
|
-
|
32
|
-
|
80
|
+
attr_reader :owner_class, :reflection
|
81
|
+
|
82
|
+
def initialize(owner_class = nil, reflection = nil)
|
83
|
+
if owner_class && reflection
|
84
|
+
@owner_class = owner_class
|
85
|
+
@reflection = reflection
|
86
|
+
super("Could not find the association #{reflection.options[:through].inspect} in model #{owner_class.name}")
|
33
87
|
else
|
34
88
|
super("Could not find the association.")
|
35
89
|
end
|
36
90
|
end
|
91
|
+
|
92
|
+
class Correction
|
93
|
+
def initialize(error)
|
94
|
+
@error = error
|
95
|
+
end
|
96
|
+
|
97
|
+
def corrections
|
98
|
+
if @error.reflection && @error.owner_class
|
99
|
+
maybe_these = @error.owner_class.reflections.keys
|
100
|
+
maybe_these -= [@error.reflection.name.to_s] # remove failing reflection
|
101
|
+
|
102
|
+
maybe_these.sort_by { |n|
|
103
|
+
DidYouMean::Jaro.distance(@error.reflection.options[:through].to_s, n)
|
104
|
+
}.reverse.first(4)
|
105
|
+
else
|
106
|
+
[]
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
# We may not have DYM, and DYM might not let us register error handlers
|
112
|
+
if defined?(DidYouMean) && DidYouMean.respond_to?(:correct_error)
|
113
|
+
DidYouMean.correct_error(self, Correction)
|
114
|
+
end
|
37
115
|
end
|
38
116
|
|
39
117
|
class HasManyThroughAssociationPolymorphicSourceError < ActiveRecordError #:nodoc:
|
@@ -702,9 +780,8 @@ module ActiveRecord
|
|
702
780
|
# inverse detection only works on #has_many, #has_one, and
|
703
781
|
# #belongs_to associations.
|
704
782
|
#
|
705
|
-
#
|
706
|
-
#
|
707
|
-
# constant, or a custom scope, will also prevent the association's inverse
|
783
|
+
# <tt>:foreign_key</tt> and <tt>:through</tt> options on the associations,
|
784
|
+
# or a custom scope, will also prevent the association's inverse
|
708
785
|
# from being found automatically.
|
709
786
|
#
|
710
787
|
# The automatic guessing of the inverse association uses a heuristic based
|
@@ -1292,7 +1369,11 @@ module ActiveRecord
|
|
1292
1369
|
# similar callbacks may affect the <tt>:dependent</tt> behavior, and the
|
1293
1370
|
# <tt>:dependent</tt> behavior may affect other callbacks.
|
1294
1371
|
#
|
1372
|
+
# * <tt>nil</tt> do nothing (default).
|
1295
1373
|
# * <tt>:destroy</tt> causes all the associated objects to also be destroyed.
|
1374
|
+
# * <tt>:destroy_async</tt> destroys all the associated objects in a background job. <b>WARNING:</b> Do not use
|
1375
|
+
# this option if the association is backed by foreign key constraints in your database. The foreign key
|
1376
|
+
# constraint actions will occur inside the same transaction that deletes its owner.
|
1296
1377
|
# * <tt>:delete_all</tt> causes all the associated objects to be deleted directly from the database (so callbacks will not be executed).
|
1297
1378
|
# * <tt>:nullify</tt> causes the foreign keys to be set to +NULL+. Polymorphic type will also be nullified
|
1298
1379
|
# on polymorphic associations. Callbacks are not executed.
|
@@ -1357,6 +1438,11 @@ module ActiveRecord
|
|
1357
1438
|
# Specifies a module or array of modules that will be extended into the association object returned.
|
1358
1439
|
# Useful for defining methods on associations, especially when they should be shared between multiple
|
1359
1440
|
# association objects.
|
1441
|
+
# [:strict_loading]
|
1442
|
+
# Enforces strict loading every time the associated record is loaded through this association.
|
1443
|
+
# [:ensuring_owner_was]
|
1444
|
+
# Specifies an instance method to be called on the owner. The method must return true in order for the
|
1445
|
+
# associated records to be deleted in a background job.
|
1360
1446
|
#
|
1361
1447
|
# Option examples:
|
1362
1448
|
# has_many :comments, -> { order("posted_on") }
|
@@ -1367,6 +1453,7 @@ module ActiveRecord
|
|
1367
1453
|
# has_many :tags, as: :taggable
|
1368
1454
|
# has_many :reports, -> { readonly }
|
1369
1455
|
# has_many :subscribers, through: :subscriptions, source: :user
|
1456
|
+
# has_many :comments, strict_loading: true
|
1370
1457
|
def has_many(name, scope = nil, **options, &extension)
|
1371
1458
|
reflection = Builder::HasMany.build(self, name, scope, options, &extension)
|
1372
1459
|
Reflection.add_reflection self, name, reflection
|
@@ -1436,7 +1523,11 @@ module ActiveRecord
|
|
1436
1523
|
# Controls what happens to the associated object when
|
1437
1524
|
# its owner is destroyed:
|
1438
1525
|
#
|
1526
|
+
# * <tt>nil</tt> do nothing (default).
|
1439
1527
|
# * <tt>:destroy</tt> causes the associated object to also be destroyed
|
1528
|
+
# * <tt>:destroy_async</tt> causes the associated object to be destroyed in a background job. <b>WARNING:</b> Do not use
|
1529
|
+
# this option if the association is backed by foreign key constraints in your database. The foreign key
|
1530
|
+
# constraint actions will occur inside the same transaction that deletes its owner.
|
1440
1531
|
# * <tt>:delete</tt> causes the associated object to be deleted directly from the database (so callbacks will not execute)
|
1441
1532
|
# * <tt>:nullify</tt> causes the foreign key to be set to +NULL+. Polymorphic type column is also nullified
|
1442
1533
|
# on polymorphic associations. Callbacks are not executed.
|
@@ -1495,6 +1586,11 @@ module ActiveRecord
|
|
1495
1586
|
# When set to +true+, the association will also have its presence validated.
|
1496
1587
|
# This will validate the association itself, not the id. You can use
|
1497
1588
|
# +:inverse_of+ to avoid an extra query during validation.
|
1589
|
+
# [:strict_loading]
|
1590
|
+
# Enforces strict loading every time the associated record is loaded through this association.
|
1591
|
+
# [:ensuring_owner_was]
|
1592
|
+
# Specifies an instance method to be called on the owner. The method must return true in order for the
|
1593
|
+
# associated records to be deleted in a background job.
|
1498
1594
|
#
|
1499
1595
|
# Option examples:
|
1500
1596
|
# has_one :credit_card, dependent: :destroy # destroys the associated credit card
|
@@ -1507,6 +1603,7 @@ module ActiveRecord
|
|
1507
1603
|
# has_one :club, through: :membership
|
1508
1604
|
# has_one :primary_address, -> { where(primary: true) }, through: :addressables, source: :addressable
|
1509
1605
|
# has_one :credit_card, required: true
|
1606
|
+
# has_one :credit_card, strict_loading: true
|
1510
1607
|
def has_one(name, scope = nil, **options)
|
1511
1608
|
reflection = Builder::HasOne.build(self, name, scope, options)
|
1512
1609
|
Reflection.add_reflection self, name, reflection
|
@@ -1588,7 +1685,8 @@ module ActiveRecord
|
|
1588
1685
|
# By default this is +id+.
|
1589
1686
|
# [:dependent]
|
1590
1687
|
# If set to <tt>:destroy</tt>, the associated object is destroyed when this object is. If set to
|
1591
|
-
# <tt>:delete</tt>, the associated object is deleted *without* calling its destroy method.
|
1688
|
+
# <tt>:delete</tt>, the associated object is deleted *without* calling its destroy method. If set to
|
1689
|
+
# <tt>:destroy_async</tt>, the associated object is scheduled to be destroyed in a background job.
|
1592
1690
|
# This option should not be specified when #belongs_to is used in conjunction with
|
1593
1691
|
# a #has_many relationship on another class because of the potential to leave
|
1594
1692
|
# orphaned records behind.
|
@@ -1640,6 +1738,11 @@ module ActiveRecord
|
|
1640
1738
|
# [:default]
|
1641
1739
|
# Provide a callable (i.e. proc or lambda) to specify that the association should
|
1642
1740
|
# be initialized with a particular record before validation.
|
1741
|
+
# [:strict_loading]
|
1742
|
+
# Enforces strict loading every time the associated record is loaded through this association.
|
1743
|
+
# [:ensuring_owner_was]
|
1744
|
+
# Specifies an instance method to be called on the owner. The method must return true in order for the
|
1745
|
+
# associated records to be deleted in a background job.
|
1643
1746
|
#
|
1644
1747
|
# Option examples:
|
1645
1748
|
# belongs_to :firm, foreign_key: "client_of"
|
@@ -1654,6 +1757,7 @@ module ActiveRecord
|
|
1654
1757
|
# belongs_to :company, touch: :employees_last_updated_at
|
1655
1758
|
# belongs_to :user, optional: true
|
1656
1759
|
# belongs_to :account, default: -> { company.account }
|
1760
|
+
# belongs_to :account, strict_loading: true
|
1657
1761
|
def belongs_to(name, scope = nil, **options)
|
1658
1762
|
reflection = Builder::BelongsTo.build(self, name, scope, options)
|
1659
1763
|
Reflection.add_reflection self, name, reflection
|
@@ -1676,7 +1780,7 @@ module ActiveRecord
|
|
1676
1780
|
# The join table should not have a primary key or a model associated with it. You must manually generate the
|
1677
1781
|
# join table with a migration such as this:
|
1678
1782
|
#
|
1679
|
-
# class CreateDevelopersProjectsJoinTable < ActiveRecord::Migration[
|
1783
|
+
# class CreateDevelopersProjectsJoinTable < ActiveRecord::Migration[6.0]
|
1680
1784
|
# def change
|
1681
1785
|
# create_join_table :developers, :projects
|
1682
1786
|
# end
|
@@ -1816,6 +1920,8 @@ module ActiveRecord
|
|
1816
1920
|
#
|
1817
1921
|
# Note that NestedAttributes::ClassMethods#accepts_nested_attributes_for sets
|
1818
1922
|
# <tt>:autosave</tt> to <tt>true</tt>.
|
1923
|
+
# [:strict_loading]
|
1924
|
+
# Enforces strict loading every time an associated record is loaded through this association.
|
1819
1925
|
#
|
1820
1926
|
# Option examples:
|
1821
1927
|
# has_and_belongs_to_many :projects
|
@@ -1823,6 +1929,7 @@ module ActiveRecord
|
|
1823
1929
|
# has_and_belongs_to_many :nations, class_name: "Country"
|
1824
1930
|
# has_and_belongs_to_many :categories, join_table: "prods_cats"
|
1825
1931
|
# has_and_belongs_to_many :categories, -> { readonly }
|
1932
|
+
# has_and_belongs_to_many :categories, strict_loading: true
|
1826
1933
|
def has_and_belongs_to_many(name, scope = nil, **options, &extension)
|
1827
1934
|
habtm_reflection = ActiveRecord::Reflection::HasAndBelongsToManyReflection.new(name, scope, options, self)
|
1828
1935
|
|
@@ -1853,7 +1960,7 @@ module ActiveRecord
|
|
1853
1960
|
hm_options[:through] = middle_reflection.name
|
1854
1961
|
hm_options[:source] = join_model.right_reflection.name
|
1855
1962
|
|
1856
|
-
[:before_add, :after_add, :before_remove, :after_remove, :autosave, :validate, :join_table, :class_name, :extend].each do |k|
|
1963
|
+
[:before_add, :after_add, :before_remove, :after_remove, :autosave, :validate, :join_table, :class_name, :extend, :strict_loading].each do |k|
|
1857
1964
|
hm_options[k] = options[k] if options.key? k
|
1858
1965
|
end
|
1859
1966
|
|
@@ -8,20 +8,22 @@ module ActiveRecord
|
|
8
8
|
|
9
9
|
private
|
10
10
|
def _assign_attributes(attributes)
|
11
|
-
multi_parameter_attributes
|
12
|
-
nested_parameter_attributes = {}
|
11
|
+
multi_parameter_attributes = nested_parameter_attributes = nil
|
13
12
|
|
14
13
|
attributes.each do |k, v|
|
15
|
-
|
16
|
-
|
14
|
+
key = k.to_s
|
15
|
+
|
16
|
+
if key.include?("(")
|
17
|
+
(multi_parameter_attributes ||= {})[key] = v
|
17
18
|
elsif v.is_a?(Hash)
|
18
|
-
nested_parameter_attributes[
|
19
|
+
(nested_parameter_attributes ||= {})[key] = v
|
20
|
+
else
|
21
|
+
_assign_attribute(key, v)
|
19
22
|
end
|
20
23
|
end
|
21
|
-
super(attributes)
|
22
24
|
|
23
|
-
assign_nested_parameter_attributes(nested_parameter_attributes)
|
24
|
-
assign_multiparameter_attributes(multi_parameter_attributes)
|
25
|
+
assign_nested_parameter_attributes(nested_parameter_attributes) if nested_parameter_attributes
|
26
|
+
assign_multiparameter_attributes(multi_parameter_attributes) if multi_parameter_attributes
|
25
27
|
end
|
26
28
|
|
27
29
|
# Assign any deferred nested attributes after the base attributes have been set.
|
@@ -29,7 +29,7 @@ module ActiveRecord
|
|
29
29
|
extend ActiveSupport::Concern
|
30
30
|
|
31
31
|
included do
|
32
|
-
attribute_method_suffix "_before_type_cast"
|
32
|
+
attribute_method_suffix "_before_type_cast", "_for_database"
|
33
33
|
attribute_method_suffix "_came_from_user?"
|
34
34
|
end
|
35
35
|
|
@@ -46,8 +46,10 @@ module ActiveRecord
|
|
46
46
|
# task.read_attribute_before_type_cast('completed_on') # => "2012-10-21"
|
47
47
|
# task.read_attribute_before_type_cast(:completed_on) # => "2012-10-21"
|
48
48
|
def read_attribute_before_type_cast(attr_name)
|
49
|
-
|
50
|
-
|
49
|
+
name = attr_name.to_s
|
50
|
+
name = self.class.attribute_aliases[name] || name
|
51
|
+
|
52
|
+
attribute_before_type_cast(name)
|
51
53
|
end
|
52
54
|
|
53
55
|
# Returns a hash of attributes before typecasting and deserialization.
|
@@ -61,19 +63,21 @@ module ActiveRecord
|
|
61
63
|
# task.attributes_before_type_cast
|
62
64
|
# # => {"id"=>nil, "title"=>nil, "is_done"=>true, "completed_on"=>"2012-10-21", "created_at"=>nil, "updated_at"=>nil}
|
63
65
|
def attributes_before_type_cast
|
64
|
-
sync_with_transaction_state if @transaction_state&.finalized?
|
65
66
|
@attributes.values_before_type_cast
|
66
67
|
end
|
67
68
|
|
68
69
|
private
|
69
70
|
# Dispatch target for <tt>*_before_type_cast</tt> attribute methods.
|
70
|
-
def attribute_before_type_cast(
|
71
|
-
|
71
|
+
def attribute_before_type_cast(attr_name)
|
72
|
+
@attributes[attr_name].value_before_type_cast
|
73
|
+
end
|
74
|
+
|
75
|
+
def attribute_for_database(attr_name)
|
76
|
+
@attributes[attr_name].value_for_database
|
72
77
|
end
|
73
78
|
|
74
|
-
def attribute_came_from_user?(
|
75
|
-
|
76
|
-
@attributes[attribute_name].came_from_user?
|
79
|
+
def attribute_came_from_user?(attr_name)
|
80
|
+
@attributes[attr_name].came_from_user?
|
77
81
|
end
|
78
82
|
end
|
79
83
|
end
|