activerecord 7.0.8 → 7.1.3.4
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 +1554 -1452
- data/MIT-LICENSE +1 -1
- data/README.rdoc +16 -16
- data/lib/active_record/aggregations.rb +16 -13
- data/lib/active_record/association_relation.rb +1 -1
- data/lib/active_record/associations/association.rb +20 -4
- data/lib/active_record/associations/association_scope.rb +16 -9
- data/lib/active_record/associations/belongs_to_association.rb +14 -6
- data/lib/active_record/associations/builder/association.rb +3 -3
- data/lib/active_record/associations/builder/belongs_to.rb +21 -8
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +1 -5
- data/lib/active_record/associations/builder/singular_association.rb +4 -0
- data/lib/active_record/associations/collection_association.rb +15 -9
- data/lib/active_record/associations/collection_proxy.rb +15 -10
- data/lib/active_record/associations/foreign_association.rb +10 -3
- data/lib/active_record/associations/has_many_association.rb +20 -13
- data/lib/active_record/associations/has_many_through_association.rb +10 -6
- data/lib/active_record/associations/has_one_association.rb +10 -3
- data/lib/active_record/associations/join_dependency.rb +10 -8
- data/lib/active_record/associations/preloader/association.rb +31 -7
- data/lib/active_record/associations/preloader.rb +13 -10
- data/lib/active_record/associations/singular_association.rb +1 -1
- data/lib/active_record/associations/through_association.rb +22 -11
- data/lib/active_record/associations.rb +313 -217
- data/lib/active_record/attribute_assignment.rb +0 -2
- data/lib/active_record/attribute_methods/before_type_cast.rb +17 -0
- data/lib/active_record/attribute_methods/dirty.rb +52 -34
- data/lib/active_record/attribute_methods/primary_key.rb +76 -24
- data/lib/active_record/attribute_methods/query.rb +28 -16
- data/lib/active_record/attribute_methods/read.rb +18 -5
- data/lib/active_record/attribute_methods/serialization.rb +150 -31
- data/lib/active_record/attribute_methods/write.rb +3 -3
- data/lib/active_record/attribute_methods.rb +105 -21
- data/lib/active_record/attributes.rb +3 -3
- data/lib/active_record/autosave_association.rb +55 -9
- data/lib/active_record/base.rb +7 -2
- data/lib/active_record/callbacks.rb +10 -24
- data/lib/active_record/coders/column_serializer.rb +61 -0
- data/lib/active_record/coders/json.rb +1 -1
- data/lib/active_record/coders/yaml_column.rb +70 -42
- data/lib/active_record/connection_adapters/abstract/connection_handler.rb +163 -88
- data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +2 -0
- data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +3 -1
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +74 -51
- data/lib/active_record/connection_adapters/abstract/database_limits.rb +5 -0
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +129 -31
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +62 -23
- data/lib/active_record/connection_adapters/abstract/quoting.rb +41 -6
- data/lib/active_record/connection_adapters/abstract/savepoints.rb +4 -3
- data/lib/active_record/connection_adapters/abstract/schema_creation.rb +18 -4
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +137 -11
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +289 -124
- data/lib/active_record/connection_adapters/abstract/transaction.rb +287 -58
- data/lib/active_record/connection_adapters/abstract_adapter.rb +511 -91
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +207 -108
- data/lib/active_record/connection_adapters/column.rb +9 -0
- data/lib/active_record/connection_adapters/mysql/column.rb +1 -0
- data/lib/active_record/connection_adapters/mysql/database_statements.rb +22 -143
- data/lib/active_record/connection_adapters/mysql/quoting.rb +16 -12
- data/lib/active_record/connection_adapters/mysql/schema_creation.rb +9 -0
- data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +6 -0
- data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +1 -1
- data/lib/active_record/connection_adapters/mysql/schema_statements.rb +18 -13
- data/lib/active_record/connection_adapters/mysql2/database_statements.rb +151 -0
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +98 -53
- data/lib/active_record/connection_adapters/pool_config.rb +14 -5
- data/lib/active_record/connection_adapters/pool_manager.rb +19 -9
- data/lib/active_record/connection_adapters/postgresql/column.rb +14 -3
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +74 -40
- data/lib/active_record/connection_adapters/postgresql/oid/money.rb +3 -2
- data/lib/active_record/connection_adapters/postgresql/oid/range.rb +11 -2
- data/lib/active_record/connection_adapters/postgresql/oid/timestamp_with_time_zone.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +10 -6
- data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +3 -9
- data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +76 -6
- data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +131 -2
- data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +53 -0
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +361 -60
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +353 -192
- data/lib/active_record/connection_adapters/schema_cache.rb +287 -59
- data/lib/active_record/connection_adapters/sqlite3/column.rb +49 -0
- data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +52 -39
- data/lib/active_record/connection_adapters/sqlite3/quoting.rb +4 -3
- data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +1 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +26 -7
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +209 -79
- data/lib/active_record/connection_adapters/statement_pool.rb +7 -0
- data/lib/active_record/connection_adapters/trilogy/database_statements.rb +99 -0
- data/lib/active_record/connection_adapters/trilogy_adapter.rb +262 -0
- data/lib/active_record/connection_adapters.rb +3 -1
- data/lib/active_record/connection_handling.rb +72 -95
- data/lib/active_record/core.rb +175 -153
- data/lib/active_record/counter_cache.rb +46 -25
- data/lib/active_record/database_configurations/database_config.rb +9 -3
- data/lib/active_record/database_configurations/hash_config.rb +22 -12
- data/lib/active_record/database_configurations/url_config.rb +17 -11
- data/lib/active_record/database_configurations.rb +86 -33
- data/lib/active_record/delegated_type.rb +9 -4
- data/lib/active_record/deprecator.rb +7 -0
- data/lib/active_record/destroy_association_async_job.rb +2 -0
- data/lib/active_record/encryption/auto_filtered_parameters.rb +66 -0
- data/lib/active_record/encryption/cipher/aes256_gcm.rb +4 -1
- data/lib/active_record/encryption/config.rb +25 -1
- data/lib/active_record/encryption/configurable.rb +12 -19
- data/lib/active_record/encryption/context.rb +10 -3
- data/lib/active_record/encryption/contexts.rb +5 -1
- data/lib/active_record/encryption/derived_secret_key_provider.rb +8 -2
- data/lib/active_record/encryption/encryptable_record.rb +42 -18
- data/lib/active_record/encryption/encrypted_attribute_type.rb +21 -6
- data/lib/active_record/encryption/extended_deterministic_queries.rb +66 -69
- data/lib/active_record/encryption/extended_deterministic_uniqueness_validator.rb +3 -3
- data/lib/active_record/encryption/key_generator.rb +12 -1
- data/lib/active_record/encryption/message_serializer.rb +2 -0
- data/lib/active_record/encryption/properties.rb +3 -3
- data/lib/active_record/encryption/scheme.rb +19 -22
- data/lib/active_record/encryption.rb +1 -0
- data/lib/active_record/enum.rb +112 -28
- data/lib/active_record/errors.rb +112 -18
- data/lib/active_record/explain.rb +23 -3
- data/lib/active_record/fixture_set/model_metadata.rb +14 -4
- data/lib/active_record/fixture_set/render_context.rb +2 -0
- data/lib/active_record/fixture_set/table_row.rb +29 -8
- data/lib/active_record/fixtures.rb +135 -71
- data/lib/active_record/future_result.rb +31 -5
- data/lib/active_record/gem_version.rb +4 -4
- data/lib/active_record/inheritance.rb +30 -16
- data/lib/active_record/insert_all.rb +57 -10
- data/lib/active_record/integration.rb +8 -8
- data/lib/active_record/internal_metadata.rb +120 -30
- data/lib/active_record/locking/pessimistic.rb +5 -2
- data/lib/active_record/log_subscriber.rb +29 -12
- data/lib/active_record/marshalling.rb +56 -0
- data/lib/active_record/message_pack.rb +124 -0
- data/lib/active_record/middleware/database_selector/resolver.rb +4 -0
- data/lib/active_record/middleware/database_selector.rb +6 -8
- data/lib/active_record/middleware/shard_selector.rb +3 -1
- data/lib/active_record/migration/command_recorder.rb +104 -5
- data/lib/active_record/migration/compatibility.rb +139 -5
- data/lib/active_record/migration/default_strategy.rb +23 -0
- data/lib/active_record/migration/execution_strategy.rb +19 -0
- data/lib/active_record/migration/pending_migration_connection.rb +21 -0
- data/lib/active_record/migration.rb +219 -111
- data/lib/active_record/model_schema.rb +64 -44
- data/lib/active_record/nested_attributes.rb +24 -6
- data/lib/active_record/normalization.rb +167 -0
- data/lib/active_record/persistence.rb +188 -37
- data/lib/active_record/promise.rb +84 -0
- data/lib/active_record/query_cache.rb +3 -21
- data/lib/active_record/query_logs.rb +77 -52
- data/lib/active_record/query_logs_formatter.rb +41 -0
- data/lib/active_record/querying.rb +15 -2
- data/lib/active_record/railtie.rb +109 -47
- data/lib/active_record/railties/controller_runtime.rb +12 -6
- data/lib/active_record/railties/databases.rake +142 -148
- data/lib/active_record/railties/job_runtime.rb +23 -0
- data/lib/active_record/readonly_attributes.rb +32 -5
- data/lib/active_record/reflection.rb +174 -44
- data/lib/active_record/relation/batches/batch_enumerator.rb +5 -3
- data/lib/active_record/relation/batches.rb +190 -61
- data/lib/active_record/relation/calculations.rb +187 -63
- data/lib/active_record/relation/delegation.rb +23 -9
- data/lib/active_record/relation/finder_methods.rb +77 -16
- data/lib/active_record/relation/merger.rb +2 -0
- data/lib/active_record/relation/predicate_builder/association_query_value.rb +11 -2
- data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +4 -6
- data/lib/active_record/relation/predicate_builder/relation_handler.rb +5 -1
- data/lib/active_record/relation/predicate_builder.rb +26 -14
- data/lib/active_record/relation/query_attribute.rb +2 -1
- data/lib/active_record/relation/query_methods.rb +352 -63
- data/lib/active_record/relation/spawn_methods.rb +18 -1
- data/lib/active_record/relation.rb +91 -35
- data/lib/active_record/result.rb +19 -5
- data/lib/active_record/runtime_registry.rb +24 -1
- data/lib/active_record/sanitization.rb +51 -11
- data/lib/active_record/schema.rb +2 -3
- data/lib/active_record/schema_dumper.rb +46 -7
- data/lib/active_record/schema_migration.rb +68 -33
- data/lib/active_record/scoping/default.rb +15 -5
- data/lib/active_record/scoping/named.rb +2 -2
- data/lib/active_record/scoping.rb +2 -1
- data/lib/active_record/secure_password.rb +60 -0
- data/lib/active_record/secure_token.rb +21 -3
- data/lib/active_record/signed_id.rb +7 -5
- data/lib/active_record/store.rb +8 -8
- data/lib/active_record/suppressor.rb +3 -1
- data/lib/active_record/table_metadata.rb +10 -1
- data/lib/active_record/tasks/database_tasks.rb +127 -105
- data/lib/active_record/tasks/mysql_database_tasks.rb +15 -6
- data/lib/active_record/tasks/postgresql_database_tasks.rb +16 -13
- data/lib/active_record/tasks/sqlite_database_tasks.rb +15 -7
- data/lib/active_record/test_fixtures.rb +113 -96
- data/lib/active_record/timestamp.rb +27 -15
- data/lib/active_record/token_for.rb +113 -0
- data/lib/active_record/touch_later.rb +11 -6
- data/lib/active_record/transactions.rb +36 -10
- data/lib/active_record/type/adapter_specific_registry.rb +1 -8
- data/lib/active_record/type/internal/timezone.rb +7 -2
- data/lib/active_record/type/time.rb +4 -0
- data/lib/active_record/validations/absence.rb +1 -1
- data/lib/active_record/validations/numericality.rb +5 -4
- data/lib/active_record/validations/presence.rb +5 -28
- data/lib/active_record/validations/uniqueness.rb +47 -2
- data/lib/active_record/validations.rb +8 -4
- data/lib/active_record/version.rb +1 -1
- data/lib/active_record.rb +121 -16
- data/lib/arel/errors.rb +10 -0
- data/lib/arel/factory_methods.rb +4 -0
- data/lib/arel/nodes/binary.rb +6 -1
- data/lib/arel/nodes/bound_sql_literal.rb +61 -0
- data/lib/arel/nodes/cte.rb +36 -0
- data/lib/arel/nodes/fragments.rb +35 -0
- data/lib/arel/nodes/homogeneous_in.rb +1 -9
- data/lib/arel/nodes/leading_join.rb +8 -0
- data/lib/arel/nodes/node.rb +111 -2
- data/lib/arel/nodes/sql_literal.rb +6 -0
- data/lib/arel/nodes/table_alias.rb +4 -0
- data/lib/arel/nodes.rb +4 -0
- data/lib/arel/predications.rb +2 -0
- data/lib/arel/table.rb +9 -5
- data/lib/arel/visitors/mysql.rb +8 -1
- data/lib/arel/visitors/to_sql.rb +81 -17
- data/lib/arel/visitors/visitor.rb +2 -2
- data/lib/arel.rb +16 -2
- data/lib/rails/generators/active_record/application_record/USAGE +8 -0
- data/lib/rails/generators/active_record/migration.rb +3 -1
- data/lib/rails/generators/active_record/model/USAGE +113 -0
- data/lib/rails/generators/active_record/model/model_generator.rb +15 -6
- metadata +48 -12
- data/lib/active_record/connection_adapters/legacy_pool_manager.rb +0 -35
- data/lib/active_record/null_relation.rb +0 -63
data/MIT-LICENSE
CHANGED
data/README.rdoc
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
= Active Record -- Object-relational mapping in Rails
|
1
|
+
= Active Record -- Object-relational mapping in \Rails
|
2
2
|
|
3
3
|
Active Record connects classes to relational database tables to establish an
|
4
4
|
almost zero-configuration persistence layer for applications. The library
|
@@ -13,29 +13,28 @@ columns. Although these mappings can be defined explicitly, it's recommended
|
|
13
13
|
to follow naming conventions, especially when getting started with the
|
14
14
|
library.
|
15
15
|
|
16
|
-
You can read more about Active Record in the {Active Record Basics}[https://
|
16
|
+
You can read more about Active Record in the {Active Record Basics}[https://guides.rubyonrails.org/active_record_basics.html] guide.
|
17
17
|
|
18
18
|
A short rundown of some of the major features:
|
19
19
|
|
20
20
|
* Automated mapping between classes and tables, attributes and columns.
|
21
21
|
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
{Learn more}[link:classes/ActiveRecord/Base.html]
|
22
|
+
class Product < ActiveRecord::Base
|
23
|
+
end
|
26
24
|
|
27
|
-
The Product class is automatically mapped to the table named "products",
|
28
|
-
which might look like this:
|
25
|
+
The Product class is automatically mapped to the table named "products",
|
26
|
+
which might look like this:
|
29
27
|
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
28
|
+
CREATE TABLE products (
|
29
|
+
id bigint NOT NULL auto_increment,
|
30
|
+
name varchar(255),
|
31
|
+
PRIMARY KEY (id)
|
32
|
+
);
|
35
33
|
|
36
|
-
This would also define the following accessors: <tt>Product#name</tt> and
|
37
|
-
<tt>Product#name=(new_name)</tt>.
|
34
|
+
This would also define the following accessors: <tt>Product#name</tt> and
|
35
|
+
<tt>Product#name=(new_name)</tt>.
|
38
36
|
|
37
|
+
{Learn more}[link:classes/ActiveRecord/Base.html]
|
39
38
|
|
40
39
|
* Associations between objects defined by simple class methods.
|
41
40
|
|
@@ -140,7 +139,7 @@ This would also define the following accessors: <tt>Product#name</tt> and
|
|
140
139
|
|
141
140
|
* Database agnostic schema management with Migrations.
|
142
141
|
|
143
|
-
class AddSystemSettings < ActiveRecord::Migration[7.
|
142
|
+
class AddSystemSettings < ActiveRecord::Migration[7.1]
|
144
143
|
def up
|
145
144
|
create_table :system_settings do |t|
|
146
145
|
t.string :name
|
@@ -167,6 +166,7 @@ Active Record is an implementation of the object-relational mapping (ORM)
|
|
167
166
|
pattern[https://www.martinfowler.com/eaaCatalog/activeRecord.html] by the same
|
168
167
|
name described by Martin Fowler:
|
169
168
|
|
169
|
+
>>>
|
170
170
|
"An object that wraps a row in a database table or view,
|
171
171
|
encapsulates the database access, and adds domain logic on that data."
|
172
172
|
|
@@ -4,7 +4,7 @@ module ActiveRecord
|
|
4
4
|
# See ActiveRecord::Aggregations::ClassMethods for documentation
|
5
5
|
module Aggregations
|
6
6
|
def initialize_dup(*) # :nodoc:
|
7
|
-
@aggregation_cache =
|
7
|
+
@aggregation_cache = @aggregation_cache.dup
|
8
8
|
super
|
9
9
|
end
|
10
10
|
|
@@ -19,10 +19,12 @@ module ActiveRecord
|
|
19
19
|
end
|
20
20
|
|
21
21
|
def init_internals
|
22
|
-
@aggregation_cache = {}
|
23
22
|
super
|
23
|
+
@aggregation_cache = {}
|
24
24
|
end
|
25
25
|
|
26
|
+
# = Active Record \Aggregations
|
27
|
+
#
|
26
28
|
# Active Record implements aggregation through a macro-like class method called #composed_of
|
27
29
|
# for representing attributes as value objects. It expresses relationships like "Account [is]
|
28
30
|
# composed of Money [among other things]" or "Person [is] composed of [an] address". Each call
|
@@ -32,8 +34,8 @@ module ActiveRecord
|
|
32
34
|
# the database).
|
33
35
|
#
|
34
36
|
# class Customer < ActiveRecord::Base
|
35
|
-
# composed_of :balance, class_name: "Money", mapping:
|
36
|
-
# composed_of :address, mapping:
|
37
|
+
# composed_of :balance, class_name: "Money", mapping: { balance: :amount }
|
38
|
+
# composed_of :address, mapping: { address_street: :street, address_city: :city }
|
37
39
|
# end
|
38
40
|
#
|
39
41
|
# The customer class now has the following methods to manipulate the value objects:
|
@@ -150,7 +152,7 @@ module ActiveRecord
|
|
150
152
|
# class NetworkResource < ActiveRecord::Base
|
151
153
|
# composed_of :cidr,
|
152
154
|
# class_name: 'NetAddr::CIDR',
|
153
|
-
# mapping:
|
155
|
+
# mapping: { network_address: :network, cidr_range: :bits },
|
154
156
|
# allow_nil: true,
|
155
157
|
# constructor: Proc.new { |network_address, cidr_range| NetAddr::CIDR.create("#{network_address}/#{cidr_range}") },
|
156
158
|
# converter: Proc.new { |value| NetAddr::CIDR.create(value.is_a?(Array) ? value.join('/') : value) }
|
@@ -188,10 +190,10 @@ module ActiveRecord
|
|
188
190
|
# to the Address class, but if the real class name is +CompanyAddress+, you'll have to specify it
|
189
191
|
# with this option.
|
190
192
|
# * <tt>:mapping</tt> - Specifies the mapping of entity attributes to attributes of the value
|
191
|
-
# object. Each mapping is represented as
|
192
|
-
# entity attribute and the
|
193
|
+
# object. Each mapping is represented as a key-value pair where the key is the name of the
|
194
|
+
# entity attribute and the value is the name of the attribute in the value object. The
|
193
195
|
# order in which mappings are defined determines the order in which attributes are sent to the
|
194
|
-
# value class constructor.
|
196
|
+
# value class constructor. The mapping can be written as a hash or as an array of pairs.
|
195
197
|
# * <tt>:allow_nil</tt> - Specifies that the value object will not be instantiated when all mapped
|
196
198
|
# attributes are +nil+. Setting the value object to +nil+ has the effect of writing +nil+ to all
|
197
199
|
# mapped attributes.
|
@@ -208,14 +210,15 @@ module ActiveRecord
|
|
208
210
|
# can return +nil+ to skip the assignment.
|
209
211
|
#
|
210
212
|
# Option examples:
|
211
|
-
# composed_of :temperature, mapping:
|
212
|
-
# composed_of :balance, class_name: "Money", mapping:
|
213
|
+
# composed_of :temperature, mapping: { reading: :celsius }
|
214
|
+
# composed_of :balance, class_name: "Money", mapping: { balance: :amount }
|
215
|
+
# composed_of :address, mapping: { address_street: :street, address_city: :city }
|
213
216
|
# composed_of :address, mapping: [ %w(address_street street), %w(address_city city) ]
|
214
217
|
# composed_of :gps_location
|
215
218
|
# composed_of :gps_location, allow_nil: true
|
216
219
|
# composed_of :ip_address,
|
217
220
|
# class_name: 'IPAddr',
|
218
|
-
# mapping:
|
221
|
+
# mapping: { ip: :to_i },
|
219
222
|
# constructor: Proc.new { |ip| IPAddr.new(ip, Socket::AF_INET) },
|
220
223
|
# converter: Proc.new { |ip| ip.is_a?(Integer) ? IPAddr.new(ip, Socket::AF_INET) : IPAddr.new(ip.to_s) }
|
221
224
|
#
|
@@ -249,7 +252,7 @@ module ActiveRecord
|
|
249
252
|
object = constructor.respond_to?(:call) ?
|
250
253
|
constructor.call(*attrs) :
|
251
254
|
class_name.constantize.send(constructor, *attrs)
|
252
|
-
@aggregation_cache[name] = object
|
255
|
+
@aggregation_cache[name] = object.freeze
|
253
256
|
end
|
254
257
|
@aggregation_cache[name]
|
255
258
|
end
|
@@ -275,7 +278,7 @@ module ActiveRecord
|
|
275
278
|
@aggregation_cache[name] = nil
|
276
279
|
else
|
277
280
|
mapping.each { |key, value| write_attribute(key, part.send(value)) }
|
278
|
-
@aggregation_cache[name] = part.freeze
|
281
|
+
@aggregation_cache[name] = part.dup.freeze
|
279
282
|
end
|
280
283
|
end
|
281
284
|
end
|
@@ -16,7 +16,7 @@ module ActiveRecord
|
|
16
16
|
end
|
17
17
|
|
18
18
|
%w(insert insert_all insert! insert_all! upsert upsert_all).each do |method|
|
19
|
-
class_eval <<~RUBY
|
19
|
+
class_eval <<~RUBY, __FILE__, __LINE__ + 1
|
20
20
|
def #{method}(attributes, **kwargs)
|
21
21
|
if @association.reflection.through_reflection?
|
22
22
|
raise ArgumentError, "Bulk insert or upsert is currently not supported for has_many through association"
|
@@ -19,7 +19,7 @@ module ActiveRecord
|
|
19
19
|
# Associations in Active Record are middlemen between the object that
|
20
20
|
# holds the association, known as the <tt>owner</tt>, and the associated
|
21
21
|
# result set, known as the <tt>target</tt>. Association metadata is available in
|
22
|
-
# <tt>reflection</tt>, which is an instance of
|
22
|
+
# <tt>reflection</tt>, which is an instance of +ActiveRecord::Reflection::AssociationReflection+.
|
23
23
|
#
|
24
24
|
# For example, given
|
25
25
|
#
|
@@ -33,7 +33,8 @@ module ActiveRecord
|
|
33
33
|
# <tt>owner</tt>, the collection of its posts as <tt>target</tt>, and
|
34
34
|
# the <tt>reflection</tt> object represents a <tt>:has_many</tt> macro.
|
35
35
|
class Association # :nodoc:
|
36
|
-
|
36
|
+
attr_accessor :owner
|
37
|
+
attr_reader :target, :reflection, :disable_joins
|
37
38
|
|
38
39
|
delegate :options, to: :reflection
|
39
40
|
|
@@ -45,6 +46,8 @@ module ActiveRecord
|
|
45
46
|
|
46
47
|
reset
|
47
48
|
reset_scope
|
49
|
+
|
50
|
+
@skip_strict_loading = nil
|
48
51
|
end
|
49
52
|
|
50
53
|
# Resets the \loaded flag to +false+ and sets the \target to +nil+.
|
@@ -216,7 +219,7 @@ module ActiveRecord
|
|
216
219
|
end
|
217
220
|
|
218
221
|
def find_target
|
219
|
-
if violates_strict_loading?
|
222
|
+
if violates_strict_loading?
|
220
223
|
Base.strict_loading_violation!(owner: owner.class, reflection: reflection)
|
221
224
|
end
|
222
225
|
|
@@ -239,7 +242,19 @@ module ActiveRecord
|
|
239
242
|
end
|
240
243
|
end
|
241
244
|
|
245
|
+
def skip_strict_loading(&block)
|
246
|
+
skip_strict_loading_was = @skip_strict_loading
|
247
|
+
@skip_strict_loading = true
|
248
|
+
yield
|
249
|
+
ensure
|
250
|
+
@skip_strict_loading = skip_strict_loading_was
|
251
|
+
end
|
252
|
+
|
242
253
|
def violates_strict_loading?
|
254
|
+
return if @skip_strict_loading
|
255
|
+
|
256
|
+
return unless owner.validation_context.nil?
|
257
|
+
|
243
258
|
return reflection.strict_loading? if reflection.options.key?(:strict_loading)
|
244
259
|
|
245
260
|
owner.strict_loading? && !owner.strict_loading_n_plus_one_only?
|
@@ -322,7 +337,8 @@ module ActiveRecord
|
|
322
337
|
|
323
338
|
# Returns true if record contains the foreign_key
|
324
339
|
def foreign_key_for?(record)
|
325
|
-
|
340
|
+
foreign_key = Array(reflection.foreign_key)
|
341
|
+
foreign_key.all? { |key| record._has_attribute?(key) }
|
326
342
|
end
|
327
343
|
|
328
344
|
# This should be implemented to return the values of the relevant key(s) on the owner,
|
@@ -35,7 +35,7 @@ module ActiveRecord
|
|
35
35
|
binds = []
|
36
36
|
last_reflection = chain.last
|
37
37
|
|
38
|
-
binds
|
38
|
+
binds.push(*last_reflection.join_id_for(owner))
|
39
39
|
if last_reflection.type
|
40
40
|
binds << owner.class.polymorphic_name
|
41
41
|
end
|
@@ -56,12 +56,15 @@ module ActiveRecord
|
|
56
56
|
end
|
57
57
|
|
58
58
|
def last_chain_scope(scope, reflection, owner)
|
59
|
-
primary_key = reflection.join_primary_key
|
60
|
-
foreign_key = reflection.join_foreign_key
|
59
|
+
primary_key = Array(reflection.join_primary_key)
|
60
|
+
foreign_key = Array(reflection.join_foreign_key)
|
61
61
|
|
62
62
|
table = reflection.aliased_table
|
63
|
-
|
64
|
-
|
63
|
+
primary_key_foreign_key_pairs = primary_key.zip(foreign_key)
|
64
|
+
primary_key_foreign_key_pairs.each do |join_key, foreign_key|
|
65
|
+
value = transform_value(owner._read_attribute(foreign_key))
|
66
|
+
scope = apply_scope(scope, table, join_key, value)
|
67
|
+
end
|
65
68
|
|
66
69
|
if reflection.type
|
67
70
|
polymorphic_type = transform_value(owner.class.polymorphic_name)
|
@@ -76,19 +79,23 @@ module ActiveRecord
|
|
76
79
|
end
|
77
80
|
|
78
81
|
def next_chain_scope(scope, reflection, next_reflection)
|
79
|
-
primary_key = reflection.join_primary_key
|
80
|
-
foreign_key = reflection.join_foreign_key
|
82
|
+
primary_key = Array(reflection.join_primary_key)
|
83
|
+
foreign_key = Array(reflection.join_foreign_key)
|
81
84
|
|
82
85
|
table = reflection.aliased_table
|
83
86
|
foreign_table = next_reflection.aliased_table
|
84
|
-
|
87
|
+
|
88
|
+
primary_key_foreign_key_pairs = primary_key.zip(foreign_key)
|
89
|
+
constraints = primary_key_foreign_key_pairs.map do |join_primary_key, foreign_key|
|
90
|
+
table[join_primary_key].eq(foreign_table[foreign_key])
|
91
|
+
end.inject(&:and)
|
85
92
|
|
86
93
|
if reflection.type
|
87
94
|
value = transform_value(next_reflection.klass.polymorphic_name)
|
88
95
|
scope = apply_scope(scope, table, reflection.type, value)
|
89
96
|
end
|
90
97
|
|
91
|
-
scope.joins!(join(foreign_table,
|
98
|
+
scope.joins!(join(foreign_table, constraints))
|
92
99
|
end
|
93
100
|
|
94
101
|
class ReflectionProxy < SimpleDelegator # :nodoc:
|
@@ -11,8 +11,13 @@ module ActiveRecord
|
|
11
11
|
when :destroy
|
12
12
|
raise ActiveRecord::Rollback unless target.destroy
|
13
13
|
when :destroy_async
|
14
|
-
|
15
|
-
|
14
|
+
if reflection.foreign_key.is_a?(Array)
|
15
|
+
primary_key_column = reflection.active_record_primary_key.map(&:to_sym)
|
16
|
+
id = reflection.foreign_key.map { |col| owner.public_send(col.to_sym) }
|
17
|
+
else
|
18
|
+
primary_key_column = reflection.active_record_primary_key.to_sym
|
19
|
+
id = owner.public_send(reflection.foreign_key.to_sym)
|
20
|
+
end
|
16
21
|
|
17
22
|
enqueue_destroy_association(
|
18
23
|
owner_model_name: owner.class.to_s,
|
@@ -119,10 +124,13 @@ module ActiveRecord
|
|
119
124
|
end
|
120
125
|
|
121
126
|
def replace_keys(record, force: false)
|
122
|
-
|
127
|
+
target_key_values = record ? Array(primary_key(record.class)).map { |key| record._read_attribute(key) } : []
|
128
|
+
reflection_fk = Array(reflection.foreign_key)
|
123
129
|
|
124
|
-
if force || owner._read_attribute(
|
125
|
-
|
130
|
+
if force || reflection_fk.map { |fk| owner._read_attribute(fk) } != target_key_values
|
131
|
+
reflection_fk.zip(target_key_values).each do |key, value|
|
132
|
+
owner[key] = value
|
133
|
+
end
|
126
134
|
end
|
127
135
|
end
|
128
136
|
|
@@ -131,7 +139,7 @@ module ActiveRecord
|
|
131
139
|
end
|
132
140
|
|
133
141
|
def foreign_key_present?
|
134
|
-
|
142
|
+
Array(reflection.foreign_key).all? { |fk| owner._read_attribute(fk) }
|
135
143
|
end
|
136
144
|
|
137
145
|
def invertible_for?(record)
|
@@ -19,7 +19,7 @@ module ActiveRecord::Associations::Builder # :nodoc:
|
|
19
19
|
self.extensions = []
|
20
20
|
|
21
21
|
VALID_OPTIONS = [
|
22
|
-
:class_name, :anonymous_class, :primary_key, :foreign_key, :dependent, :validate, :inverse_of, :strict_loading
|
22
|
+
:class_name, :anonymous_class, :primary_key, :foreign_key, :dependent, :validate, :inverse_of, :strict_loading, :query_constraints
|
23
23
|
].freeze # :nodoc:
|
24
24
|
|
25
25
|
def self.build(model, name, scope, options, &block)
|
@@ -128,8 +128,8 @@ module ActiveRecord::Associations::Builder # :nodoc:
|
|
128
128
|
|
129
129
|
def self.check_dependent_options(dependent, model)
|
130
130
|
if dependent == :destroy_async && !model.destroy_association_async_job
|
131
|
-
err_message = "
|
132
|
-
raise ActiveRecord::
|
131
|
+
err_message = "A valid destroy_association_async_job is required to use `dependent: :destroy_async` on associations"
|
132
|
+
raise ActiveRecord::ConfigurationError, err_message
|
133
133
|
end
|
134
134
|
unless valid_dependent_options.include? dependent
|
135
135
|
raise ArgumentError, "The :dependent option must be one of #{valid_dependent_options}, but is :#{dependent}"
|
@@ -37,10 +37,10 @@ module ActiveRecord::Associations::Builder # :nodoc:
|
|
37
37
|
}
|
38
38
|
|
39
39
|
klass = reflection.class_name.safe_constantize
|
40
|
-
klass.
|
40
|
+
klass._counter_cache_columns |= [cache_column] if klass && klass.respond_to?(:_counter_cache_columns)
|
41
41
|
end
|
42
42
|
|
43
|
-
def self.touch_record(o, changes, foreign_key, name, touch
|
43
|
+
def self.touch_record(o, changes, foreign_key, name, touch) # :nodoc:
|
44
44
|
old_foreign_id = changes[foreign_key] && changes[foreign_key].first
|
45
45
|
|
46
46
|
if old_foreign_id
|
@@ -58,9 +58,9 @@ module ActiveRecord::Associations::Builder # :nodoc:
|
|
58
58
|
|
59
59
|
if old_record
|
60
60
|
if touch != true
|
61
|
-
old_record.
|
61
|
+
old_record.touch_later(touch)
|
62
62
|
else
|
63
|
-
old_record.
|
63
|
+
old_record.touch_later
|
64
64
|
end
|
65
65
|
end
|
66
66
|
end
|
@@ -68,9 +68,9 @@ module ActiveRecord::Associations::Builder # :nodoc:
|
|
68
68
|
record = o.public_send name
|
69
69
|
if record && record.persisted?
|
70
70
|
if touch != true
|
71
|
-
record.
|
71
|
+
record.touch_later(touch)
|
72
72
|
else
|
73
|
-
record.
|
73
|
+
record.touch_later
|
74
74
|
end
|
75
75
|
end
|
76
76
|
end
|
@@ -81,7 +81,7 @@ module ActiveRecord::Associations::Builder # :nodoc:
|
|
81
81
|
touch = reflection.options[:touch]
|
82
82
|
|
83
83
|
callback = lambda { |changes_method| lambda { |record|
|
84
|
-
BelongsTo.touch_record(record, record.send(changes_method), foreign_key, name, touch
|
84
|
+
BelongsTo.touch_record(record, record.send(changes_method), foreign_key, name, touch)
|
85
85
|
}}
|
86
86
|
|
87
87
|
if reflection.counter_cache_column
|
@@ -123,7 +123,20 @@ module ActiveRecord::Associations::Builder # :nodoc:
|
|
123
123
|
super
|
124
124
|
|
125
125
|
if required
|
126
|
-
|
126
|
+
if ActiveRecord.belongs_to_required_validates_foreign_key
|
127
|
+
model.validates_presence_of reflection.name, message: :required
|
128
|
+
else
|
129
|
+
condition = lambda { |record|
|
130
|
+
foreign_key = reflection.foreign_key
|
131
|
+
foreign_type = reflection.foreign_type
|
132
|
+
|
133
|
+
record.read_attribute(foreign_key).nil? ||
|
134
|
+
record.attribute_changed?(foreign_key) ||
|
135
|
+
(reflection.polymorphic? && (record.read_attribute(foreign_type).nil? || record.attribute_changed?(foreign_type)))
|
136
|
+
}
|
137
|
+
|
138
|
+
model.validates_presence_of reflection.name, message: :required, if: condition
|
139
|
+
end
|
127
140
|
end
|
128
141
|
end
|
129
142
|
|
@@ -20,6 +20,7 @@ module ActiveRecord::Associations::Builder # :nodoc:
|
|
20
20
|
attr_accessor :right_reflection
|
21
21
|
end
|
22
22
|
|
23
|
+
@table_name = nil
|
23
24
|
def self.table_name
|
24
25
|
# Table name needs to be resolved lazily
|
25
26
|
# because RHS class might not have been loaded
|
@@ -44,11 +45,6 @@ module ActiveRecord::Associations::Builder # :nodoc:
|
|
44
45
|
def self.retrieve_connection
|
45
46
|
left_model.retrieve_connection
|
46
47
|
end
|
47
|
-
|
48
|
-
private
|
49
|
-
def self.suppress_composite_primary_key(pk)
|
50
|
-
pk unless pk.is_a?(Array)
|
51
|
-
end
|
52
48
|
}
|
53
49
|
|
54
50
|
join_model.name = "HABTM_#{association_name.to_s.camelize}"
|
@@ -16,7 +16,7 @@ module ActiveRecord
|
|
16
16
|
#
|
17
17
|
# The CollectionAssociation class provides common methods to the collections
|
18
18
|
# defined by +has_and_belongs_to_many+, +has_many+ or +has_many+ with
|
19
|
-
# the
|
19
|
+
# the <tt>:through association</tt> option.
|
20
20
|
#
|
21
21
|
# You need to be careful with assumptions regarding the target: The proxy
|
22
22
|
# does not fetch records from the database until it needs them, but new
|
@@ -61,14 +61,20 @@ module ActiveRecord
|
|
61
61
|
primary_key = reflection.association_primary_key
|
62
62
|
pk_type = klass.type_for_attribute(primary_key)
|
63
63
|
ids = Array(ids).compact_blank
|
64
|
-
ids.map! { |
|
64
|
+
ids.map! { |id| pk_type.cast(id) }
|
65
65
|
|
66
|
-
records = klass.
|
67
|
-
|
66
|
+
records = if klass.composite_primary_key?
|
67
|
+
klass.where(primary_key => ids).index_by do |record|
|
68
|
+
primary_key.map { |primary_key| record._read_attribute(primary_key) }
|
69
|
+
end
|
70
|
+
else
|
71
|
+
klass.where(primary_key => ids).index_by do |record|
|
72
|
+
record._read_attribute(primary_key)
|
73
|
+
end
|
68
74
|
end.values_at(*ids).compact
|
69
75
|
|
70
76
|
if records.size != ids.size
|
71
|
-
found_ids = records.map { |record| record.
|
77
|
+
found_ids = records.map { |record| record._read_attribute(primary_key) }
|
72
78
|
not_found_ids = ids - found_ids
|
73
79
|
klass.all.raise_record_not_found_exception!(ids, records.size, ids.size, primary_key, not_found_ids)
|
74
80
|
else
|
@@ -119,7 +125,7 @@ module ActiveRecord
|
|
119
125
|
def concat(*records)
|
120
126
|
records = records.flatten
|
121
127
|
if owner.new_record?
|
122
|
-
load_target
|
128
|
+
skip_strict_loading { load_target }
|
123
129
|
concat_records(records)
|
124
130
|
else
|
125
131
|
transaction { concat_records(records) }
|
@@ -233,7 +239,7 @@ module ActiveRecord
|
|
233
239
|
# and delete/add only records that have changed.
|
234
240
|
def replace(other_array)
|
235
241
|
other_array.each { |val| raise_on_type_mismatch!(val) }
|
236
|
-
original_target = load_target.dup
|
242
|
+
original_target = skip_strict_loading { load_target }.dup
|
237
243
|
|
238
244
|
if owner.new_record?
|
239
245
|
replace_records(other_array, original_target)
|
@@ -324,8 +330,8 @@ module ActiveRecord
|
|
324
330
|
persisted.map! do |record|
|
325
331
|
if mem_record = memory.delete(record)
|
326
332
|
|
327
|
-
((record.attribute_names & mem_record.attribute_names) - mem_record.changed_attribute_names_to_save).each do |name|
|
328
|
-
mem_record
|
333
|
+
((record.attribute_names & mem_record.attribute_names) - mem_record.changed_attribute_names_to_save - mem_record.class._attr_readonly).each do |name|
|
334
|
+
mem_record._write_attribute(name, record[name])
|
329
335
|
end
|
330
336
|
|
331
337
|
mem_record
|
@@ -2,6 +2,8 @@
|
|
2
2
|
|
3
3
|
module ActiveRecord
|
4
4
|
module Associations
|
5
|
+
# = Active Record Collection Proxy
|
6
|
+
#
|
5
7
|
# Collection proxies in Active Record are middlemen between an
|
6
8
|
# <tt>association</tt>, and its <tt>target</tt> result set.
|
7
9
|
#
|
@@ -94,12 +96,12 @@ module ActiveRecord
|
|
94
96
|
# receive:
|
95
97
|
#
|
96
98
|
# person.pets.select(:name).first.person_id
|
97
|
-
# # => ActiveModel::MissingAttributeError: missing attribute
|
99
|
+
# # => ActiveModel::MissingAttributeError: missing attribute 'person_id' for Pet
|
98
100
|
#
|
99
|
-
# *Second:* You can pass a block so it can be used just like Array#select
|
101
|
+
# *Second:* You can pass a block so it can be used just like <tt>Array#select</tt>.
|
100
102
|
# This builds an array of objects from the database for the scope,
|
101
103
|
# converting them into an array and iterating through them using
|
102
|
-
# Array#select
|
104
|
+
# <tt>Array#select</tt>.
|
103
105
|
#
|
104
106
|
# person.pets.select { |pet| /oo/.match?(pet.name) }
|
105
107
|
# # => [
|
@@ -108,7 +110,7 @@ module ActiveRecord
|
|
108
110
|
# # ]
|
109
111
|
|
110
112
|
# Finds an object in the collection responding to the +id+. Uses the same
|
111
|
-
# rules as ActiveRecord::
|
113
|
+
# rules as ActiveRecord::FinderMethods.find. Returns ActiveRecord::RecordNotFound
|
112
114
|
# error if the object cannot be found.
|
113
115
|
#
|
114
116
|
# class Person < ActiveRecord::Base
|
@@ -218,7 +220,7 @@ module ActiveRecord
|
|
218
220
|
# :call-seq:
|
219
221
|
# third_to_last()
|
220
222
|
#
|
221
|
-
# Same as #
|
223
|
+
# Same as #last except returns only the third-to-last record.
|
222
224
|
|
223
225
|
##
|
224
226
|
# :method: second_to_last
|
@@ -226,7 +228,7 @@ module ActiveRecord
|
|
226
228
|
# :call-seq:
|
227
229
|
# second_to_last()
|
228
230
|
#
|
229
|
-
# Same as #
|
231
|
+
# Same as #last except returns only the second-to-last record.
|
230
232
|
|
231
233
|
# Returns the last record, or the last +n+ records, from the collection.
|
232
234
|
# If the collection is empty, the first form returns +nil+, and the second
|
@@ -260,7 +262,7 @@ module ActiveRecord
|
|
260
262
|
end
|
261
263
|
|
262
264
|
# Gives a record (or N records if a parameter is supplied) from the collection
|
263
|
-
# using the same rules as
|
265
|
+
# using the same rules as ActiveRecord::FinderMethods.take.
|
264
266
|
#
|
265
267
|
# class Person < ActiveRecord::Base
|
266
268
|
# has_many :pets
|
@@ -382,7 +384,7 @@ module ActiveRecord
|
|
382
384
|
# # => [#<Pet id: 2, name: "Puff", group: "celebrities", person_id: 1>]
|
383
385
|
#
|
384
386
|
# If the supplied array has an incorrect association type, it raises
|
385
|
-
# an
|
387
|
+
# an ActiveRecord::AssociationTypeMismatch error:
|
386
388
|
#
|
387
389
|
# person.pets.replace(["doo", "ggie", "gaga"])
|
388
390
|
# # => ActiveRecord::AssociationTypeMismatch: Pet expected, got String
|
@@ -930,7 +932,7 @@ module ActiveRecord
|
|
930
932
|
@association
|
931
933
|
end
|
932
934
|
|
933
|
-
# Returns a
|
935
|
+
# Returns a Relation object for the records in this association
|
934
936
|
def scope
|
935
937
|
@scope ||= @association.scope
|
936
938
|
end
|
@@ -955,10 +957,13 @@ module ActiveRecord
|
|
955
957
|
# person.pets == other
|
956
958
|
# # => true
|
957
959
|
#
|
960
|
+
#
|
961
|
+
# Note that unpersisted records can still be seen as equal:
|
962
|
+
#
|
958
963
|
# other = [Pet.new(id: 1), Pet.new(id: 2)]
|
959
964
|
#
|
960
965
|
# person.pets == other
|
961
|
-
# # =>
|
966
|
+
# # => true
|
962
967
|
def ==(other)
|
963
968
|
load_target == other
|
964
969
|
end
|
@@ -12,7 +12,7 @@ module ActiveRecord::Associations
|
|
12
12
|
|
13
13
|
def nullified_owner_attributes
|
14
14
|
Hash.new.tap do |attrs|
|
15
|
-
|
15
|
+
Array(reflection.foreign_key).each { |foreign_key| attrs[foreign_key] = nil }
|
16
16
|
attrs[reflection.type] = nil if reflection.type.present?
|
17
17
|
end
|
18
18
|
end
|
@@ -22,8 +22,15 @@ module ActiveRecord::Associations
|
|
22
22
|
def set_owner_attributes(record)
|
23
23
|
return if options[:through]
|
24
24
|
|
25
|
-
|
26
|
-
|
25
|
+
primary_key_attribute_names = Array(reflection.join_primary_key)
|
26
|
+
foreign_key_attribute_names = Array(reflection.join_foreign_key)
|
27
|
+
|
28
|
+
primary_key_foreign_key_pairs = primary_key_attribute_names.zip(foreign_key_attribute_names)
|
29
|
+
|
30
|
+
primary_key_foreign_key_pairs.each do |primary_key, foreign_key|
|
31
|
+
value = owner._read_attribute(foreign_key)
|
32
|
+
record._write_attribute(primary_key, value)
|
33
|
+
end
|
27
34
|
|
28
35
|
if reflection.type
|
29
36
|
record._write_attribute(reflection.type, owner.class.polymorphic_name)
|