activerecord 5.2.3
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 +7 -0
- data/CHANGELOG.md +937 -0
- data/MIT-LICENSE +20 -0
- data/README.rdoc +217 -0
- data/examples/performance.rb +185 -0
- data/examples/simple.rb +15 -0
- data/lib/active_record.rb +188 -0
- data/lib/active_record/aggregations.rb +283 -0
- data/lib/active_record/association_relation.rb +40 -0
- data/lib/active_record/associations.rb +1860 -0
- data/lib/active_record/associations/alias_tracker.rb +81 -0
- data/lib/active_record/associations/association.rb +299 -0
- data/lib/active_record/associations/association_scope.rb +168 -0
- data/lib/active_record/associations/belongs_to_association.rb +130 -0
- data/lib/active_record/associations/belongs_to_polymorphic_association.rb +40 -0
- data/lib/active_record/associations/builder/association.rb +140 -0
- data/lib/active_record/associations/builder/belongs_to.rb +163 -0
- data/lib/active_record/associations/builder/collection_association.rb +82 -0
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +135 -0
- data/lib/active_record/associations/builder/has_many.rb +17 -0
- data/lib/active_record/associations/builder/has_one.rb +30 -0
- data/lib/active_record/associations/builder/singular_association.rb +42 -0
- data/lib/active_record/associations/collection_association.rb +513 -0
- data/lib/active_record/associations/collection_proxy.rb +1131 -0
- data/lib/active_record/associations/foreign_association.rb +13 -0
- data/lib/active_record/associations/has_many_association.rb +144 -0
- data/lib/active_record/associations/has_many_through_association.rb +227 -0
- data/lib/active_record/associations/has_one_association.rb +120 -0
- data/lib/active_record/associations/has_one_through_association.rb +45 -0
- data/lib/active_record/associations/join_dependency.rb +262 -0
- data/lib/active_record/associations/join_dependency/join_association.rb +60 -0
- data/lib/active_record/associations/join_dependency/join_base.rb +23 -0
- data/lib/active_record/associations/join_dependency/join_part.rb +71 -0
- data/lib/active_record/associations/preloader.rb +193 -0
- data/lib/active_record/associations/preloader/association.rb +131 -0
- data/lib/active_record/associations/preloader/through_association.rb +107 -0
- data/lib/active_record/associations/singular_association.rb +73 -0
- data/lib/active_record/associations/through_association.rb +121 -0
- data/lib/active_record/attribute_assignment.rb +88 -0
- data/lib/active_record/attribute_decorators.rb +90 -0
- data/lib/active_record/attribute_methods.rb +492 -0
- data/lib/active_record/attribute_methods/before_type_cast.rb +78 -0
- data/lib/active_record/attribute_methods/dirty.rb +150 -0
- data/lib/active_record/attribute_methods/primary_key.rb +143 -0
- data/lib/active_record/attribute_methods/query.rb +42 -0
- data/lib/active_record/attribute_methods/read.rb +85 -0
- data/lib/active_record/attribute_methods/serialization.rb +90 -0
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +91 -0
- data/lib/active_record/attribute_methods/write.rb +68 -0
- data/lib/active_record/attributes.rb +266 -0
- data/lib/active_record/autosave_association.rb +498 -0
- data/lib/active_record/base.rb +329 -0
- data/lib/active_record/callbacks.rb +353 -0
- data/lib/active_record/coders/json.rb +15 -0
- data/lib/active_record/coders/yaml_column.rb +50 -0
- data/lib/active_record/collection_cache_key.rb +53 -0
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +1068 -0
- data/lib/active_record/connection_adapters/abstract/database_limits.rb +72 -0
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +540 -0
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +145 -0
- data/lib/active_record/connection_adapters/abstract/quoting.rb +200 -0
- data/lib/active_record/connection_adapters/abstract/savepoints.rb +23 -0
- data/lib/active_record/connection_adapters/abstract/schema_creation.rb +146 -0
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +685 -0
- data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +95 -0
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +1396 -0
- data/lib/active_record/connection_adapters/abstract/transaction.rb +283 -0
- data/lib/active_record/connection_adapters/abstract_adapter.rb +628 -0
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +887 -0
- data/lib/active_record/connection_adapters/column.rb +91 -0
- data/lib/active_record/connection_adapters/connection_specification.rb +287 -0
- data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +33 -0
- data/lib/active_record/connection_adapters/mysql/column.rb +27 -0
- data/lib/active_record/connection_adapters/mysql/database_statements.rb +140 -0
- data/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb +72 -0
- data/lib/active_record/connection_adapters/mysql/quoting.rb +44 -0
- data/lib/active_record/connection_adapters/mysql/schema_creation.rb +73 -0
- data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +87 -0
- data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +80 -0
- data/lib/active_record/connection_adapters/mysql/schema_statements.rb +148 -0
- data/lib/active_record/connection_adapters/mysql/type_metadata.rb +35 -0
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +129 -0
- data/lib/active_record/connection_adapters/postgresql/column.rb +44 -0
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +163 -0
- data/lib/active_record/connection_adapters/postgresql/explain_pretty_printer.rb +44 -0
- data/lib/active_record/connection_adapters/postgresql/oid.rb +34 -0
- data/lib/active_record/connection_adapters/postgresql/oid/array.rb +92 -0
- data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +56 -0
- data/lib/active_record/connection_adapters/postgresql/oid/bit_varying.rb +15 -0
- data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +17 -0
- data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +50 -0
- data/lib/active_record/connection_adapters/postgresql/oid/date.rb +23 -0
- data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +23 -0
- data/lib/active_record/connection_adapters/postgresql/oid/decimal.rb +15 -0
- data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +21 -0
- data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +71 -0
- data/lib/active_record/connection_adapters/postgresql/oid/inet.rb +15 -0
- data/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb +15 -0
- data/lib/active_record/connection_adapters/postgresql/oid/legacy_point.rb +45 -0
- data/lib/active_record/connection_adapters/postgresql/oid/money.rb +41 -0
- data/lib/active_record/connection_adapters/postgresql/oid/oid.rb +15 -0
- data/lib/active_record/connection_adapters/postgresql/oid/point.rb +65 -0
- data/lib/active_record/connection_adapters/postgresql/oid/range.rb +97 -0
- data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +18 -0
- data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +111 -0
- data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +23 -0
- data/lib/active_record/connection_adapters/postgresql/oid/vector.rb +28 -0
- data/lib/active_record/connection_adapters/postgresql/oid/xml.rb +30 -0
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +168 -0
- data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +43 -0
- data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +65 -0
- data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +206 -0
- data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +50 -0
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +774 -0
- data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +39 -0
- data/lib/active_record/connection_adapters/postgresql/utils.rb +81 -0
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +863 -0
- data/lib/active_record/connection_adapters/schema_cache.rb +118 -0
- data/lib/active_record/connection_adapters/sql_type_metadata.rb +34 -0
- data/lib/active_record/connection_adapters/sqlite3/explain_pretty_printer.rb +21 -0
- data/lib/active_record/connection_adapters/sqlite3/quoting.rb +67 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +17 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +19 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +18 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +106 -0
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +573 -0
- data/lib/active_record/connection_adapters/statement_pool.rb +61 -0
- data/lib/active_record/connection_handling.rb +145 -0
- data/lib/active_record/core.rb +559 -0
- data/lib/active_record/counter_cache.rb +218 -0
- data/lib/active_record/define_callbacks.rb +22 -0
- data/lib/active_record/dynamic_matchers.rb +122 -0
- data/lib/active_record/enum.rb +244 -0
- data/lib/active_record/errors.rb +380 -0
- data/lib/active_record/explain.rb +50 -0
- data/lib/active_record/explain_registry.rb +32 -0
- data/lib/active_record/explain_subscriber.rb +34 -0
- data/lib/active_record/fixture_set/file.rb +82 -0
- data/lib/active_record/fixtures.rb +1065 -0
- data/lib/active_record/gem_version.rb +17 -0
- data/lib/active_record/inheritance.rb +283 -0
- data/lib/active_record/integration.rb +155 -0
- data/lib/active_record/internal_metadata.rb +45 -0
- data/lib/active_record/legacy_yaml_adapter.rb +48 -0
- data/lib/active_record/locale/en.yml +48 -0
- data/lib/active_record/locking/optimistic.rb +198 -0
- data/lib/active_record/locking/pessimistic.rb +89 -0
- data/lib/active_record/log_subscriber.rb +137 -0
- data/lib/active_record/migration.rb +1378 -0
- data/lib/active_record/migration/command_recorder.rb +240 -0
- data/lib/active_record/migration/compatibility.rb +217 -0
- data/lib/active_record/migration/join_table.rb +17 -0
- data/lib/active_record/model_schema.rb +521 -0
- data/lib/active_record/nested_attributes.rb +600 -0
- data/lib/active_record/no_touching.rb +58 -0
- data/lib/active_record/null_relation.rb +68 -0
- data/lib/active_record/persistence.rb +763 -0
- data/lib/active_record/query_cache.rb +45 -0
- data/lib/active_record/querying.rb +70 -0
- data/lib/active_record/railtie.rb +226 -0
- data/lib/active_record/railties/console_sandbox.rb +7 -0
- data/lib/active_record/railties/controller_runtime.rb +56 -0
- data/lib/active_record/railties/databases.rake +377 -0
- data/lib/active_record/readonly_attributes.rb +24 -0
- data/lib/active_record/reflection.rb +1044 -0
- data/lib/active_record/relation.rb +629 -0
- data/lib/active_record/relation/batches.rb +287 -0
- data/lib/active_record/relation/batches/batch_enumerator.rb +69 -0
- data/lib/active_record/relation/calculations.rb +417 -0
- data/lib/active_record/relation/delegation.rb +147 -0
- data/lib/active_record/relation/finder_methods.rb +565 -0
- data/lib/active_record/relation/from_clause.rb +26 -0
- data/lib/active_record/relation/merger.rb +193 -0
- data/lib/active_record/relation/predicate_builder.rb +152 -0
- data/lib/active_record/relation/predicate_builder/array_handler.rb +48 -0
- data/lib/active_record/relation/predicate_builder/association_query_value.rb +46 -0
- data/lib/active_record/relation/predicate_builder/base_handler.rb +19 -0
- data/lib/active_record/relation/predicate_builder/basic_object_handler.rb +20 -0
- data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +56 -0
- data/lib/active_record/relation/predicate_builder/range_handler.rb +42 -0
- data/lib/active_record/relation/predicate_builder/relation_handler.rb +19 -0
- data/lib/active_record/relation/query_attribute.rb +45 -0
- data/lib/active_record/relation/query_methods.rb +1231 -0
- data/lib/active_record/relation/record_fetch_warning.rb +51 -0
- data/lib/active_record/relation/spawn_methods.rb +77 -0
- data/lib/active_record/relation/where_clause.rb +186 -0
- data/lib/active_record/relation/where_clause_factory.rb +34 -0
- data/lib/active_record/result.rb +149 -0
- data/lib/active_record/runtime_registry.rb +24 -0
- data/lib/active_record/sanitization.rb +222 -0
- data/lib/active_record/schema.rb +70 -0
- data/lib/active_record/schema_dumper.rb +255 -0
- data/lib/active_record/schema_migration.rb +56 -0
- data/lib/active_record/scoping.rb +106 -0
- data/lib/active_record/scoping/default.rb +152 -0
- data/lib/active_record/scoping/named.rb +213 -0
- data/lib/active_record/secure_token.rb +40 -0
- data/lib/active_record/serialization.rb +22 -0
- data/lib/active_record/statement_cache.rb +121 -0
- data/lib/active_record/store.rb +211 -0
- data/lib/active_record/suppressor.rb +61 -0
- data/lib/active_record/table_metadata.rb +82 -0
- data/lib/active_record/tasks/database_tasks.rb +337 -0
- data/lib/active_record/tasks/mysql_database_tasks.rb +115 -0
- data/lib/active_record/tasks/postgresql_database_tasks.rb +143 -0
- data/lib/active_record/tasks/sqlite_database_tasks.rb +83 -0
- data/lib/active_record/timestamp.rb +153 -0
- data/lib/active_record/touch_later.rb +64 -0
- data/lib/active_record/transactions.rb +502 -0
- data/lib/active_record/translation.rb +24 -0
- data/lib/active_record/type.rb +79 -0
- data/lib/active_record/type/adapter_specific_registry.rb +136 -0
- data/lib/active_record/type/date.rb +9 -0
- data/lib/active_record/type/date_time.rb +9 -0
- data/lib/active_record/type/decimal_without_scale.rb +15 -0
- data/lib/active_record/type/hash_lookup_type_map.rb +25 -0
- data/lib/active_record/type/internal/timezone.rb +17 -0
- data/lib/active_record/type/json.rb +30 -0
- data/lib/active_record/type/serialized.rb +71 -0
- data/lib/active_record/type/text.rb +11 -0
- data/lib/active_record/type/time.rb +21 -0
- data/lib/active_record/type/type_map.rb +62 -0
- data/lib/active_record/type/unsigned_integer.rb +17 -0
- data/lib/active_record/type_caster.rb +9 -0
- data/lib/active_record/type_caster/connection.rb +33 -0
- data/lib/active_record/type_caster/map.rb +23 -0
- data/lib/active_record/validations.rb +93 -0
- data/lib/active_record/validations/absence.rb +25 -0
- data/lib/active_record/validations/associated.rb +60 -0
- data/lib/active_record/validations/length.rb +26 -0
- data/lib/active_record/validations/presence.rb +68 -0
- data/lib/active_record/validations/uniqueness.rb +238 -0
- data/lib/active_record/version.rb +10 -0
- data/lib/rails/generators/active_record.rb +19 -0
- data/lib/rails/generators/active_record/application_record/application_record_generator.rb +27 -0
- data/lib/rails/generators/active_record/application_record/templates/application_record.rb.tt +5 -0
- data/lib/rails/generators/active_record/migration.rb +35 -0
- data/lib/rails/generators/active_record/migration/migration_generator.rb +78 -0
- data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +24 -0
- data/lib/rails/generators/active_record/migration/templates/migration.rb.tt +46 -0
- data/lib/rails/generators/active_record/model/model_generator.rb +48 -0
- data/lib/rails/generators/active_record/model/templates/model.rb.tt +13 -0
- data/lib/rails/generators/active_record/model/templates/module.rb.tt +7 -0
- metadata +333 -0
@@ -0,0 +1,13 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveRecord::Associations
|
4
|
+
module ForeignAssociation # :nodoc:
|
5
|
+
def foreign_key_present?
|
6
|
+
if reflection.klass.primary_key
|
7
|
+
owner.attribute_present?(reflection.active_record_primary_key)
|
8
|
+
else
|
9
|
+
false
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,144 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveRecord
|
4
|
+
module Associations
|
5
|
+
# = Active Record Has Many Association
|
6
|
+
# This is the proxy that handles a has many association.
|
7
|
+
#
|
8
|
+
# If the association has a <tt>:through</tt> option further specialization
|
9
|
+
# is provided by its child HasManyThroughAssociation.
|
10
|
+
class HasManyAssociation < CollectionAssociation #:nodoc:
|
11
|
+
include ForeignAssociation
|
12
|
+
|
13
|
+
def handle_dependency
|
14
|
+
case options[:dependent]
|
15
|
+
when :restrict_with_exception
|
16
|
+
raise ActiveRecord::DeleteRestrictionError.new(reflection.name) unless empty?
|
17
|
+
|
18
|
+
when :restrict_with_error
|
19
|
+
unless empty?
|
20
|
+
record = owner.class.human_attribute_name(reflection.name).downcase
|
21
|
+
owner.errors.add(:base, :'restrict_dependent_destroy.has_many', record: record)
|
22
|
+
throw(:abort)
|
23
|
+
end
|
24
|
+
|
25
|
+
when :destroy
|
26
|
+
# No point in executing the counter update since we're going to destroy the parent anyway
|
27
|
+
load_target.each { |t| t.destroyed_by_association = reflection }
|
28
|
+
destroy_all
|
29
|
+
else
|
30
|
+
delete_all
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def insert_record(record, validate = true, raise = false)
|
35
|
+
set_owner_attributes(record)
|
36
|
+
super
|
37
|
+
end
|
38
|
+
|
39
|
+
def empty?
|
40
|
+
if reflection.has_cached_counter?
|
41
|
+
size.zero?
|
42
|
+
else
|
43
|
+
super
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
private
|
48
|
+
|
49
|
+
# Returns the number of records in this collection.
|
50
|
+
#
|
51
|
+
# If the association has a counter cache it gets that value. Otherwise
|
52
|
+
# it will attempt to do a count via SQL, bounded to <tt>:limit</tt> if
|
53
|
+
# there's one. Some configuration options like :group make it impossible
|
54
|
+
# to do an SQL count, in those cases the array count will be used.
|
55
|
+
#
|
56
|
+
# That does not depend on whether the collection has already been loaded
|
57
|
+
# or not. The +size+ method is the one that takes the loaded flag into
|
58
|
+
# account and delegates to +count_records+ if needed.
|
59
|
+
#
|
60
|
+
# If the collection is empty the target is set to an empty array and
|
61
|
+
# the loaded flag is set to true as well.
|
62
|
+
def count_records
|
63
|
+
count = if reflection.has_cached_counter?
|
64
|
+
owner._read_attribute(reflection.counter_cache_column).to_i
|
65
|
+
else
|
66
|
+
scope.count(:all)
|
67
|
+
end
|
68
|
+
|
69
|
+
# If there's nothing in the database and @target has no new records
|
70
|
+
# we are certain the current target is an empty array. This is a
|
71
|
+
# documented side-effect of the method that may avoid an extra SELECT.
|
72
|
+
(@target ||= []) && loaded! if count == 0
|
73
|
+
|
74
|
+
[association_scope.limit_value, count].compact.min
|
75
|
+
end
|
76
|
+
|
77
|
+
def update_counter(difference, reflection = reflection())
|
78
|
+
if reflection.has_cached_counter?
|
79
|
+
owner.increment!(reflection.counter_cache_column, difference)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def update_counter_in_memory(difference, reflection = reflection())
|
84
|
+
if reflection.counter_must_be_updated_by_has_many?
|
85
|
+
counter = reflection.counter_cache_column
|
86
|
+
owner.increment(counter, difference)
|
87
|
+
owner.send(:clear_attribute_change, counter) # eww
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
def delete_count(method, scope)
|
92
|
+
if method == :delete_all
|
93
|
+
scope.delete_all
|
94
|
+
else
|
95
|
+
scope.update_all(reflection.foreign_key => nil)
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
def delete_or_nullify_all_records(method)
|
100
|
+
count = delete_count(method, scope)
|
101
|
+
update_counter(-count)
|
102
|
+
count
|
103
|
+
end
|
104
|
+
|
105
|
+
# Deletes the records according to the <tt>:dependent</tt> option.
|
106
|
+
def delete_records(records, method)
|
107
|
+
if method == :destroy
|
108
|
+
records.each(&:destroy!)
|
109
|
+
update_counter(-records.length) unless reflection.inverse_updates_counter_cache?
|
110
|
+
else
|
111
|
+
scope = self.scope.where(reflection.klass.primary_key => records)
|
112
|
+
update_counter(-delete_count(method, scope))
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
def concat_records(records, *)
|
117
|
+
update_counter_if_success(super, records.length)
|
118
|
+
end
|
119
|
+
|
120
|
+
def _create_record(attributes, *)
|
121
|
+
if attributes.is_a?(Array)
|
122
|
+
super
|
123
|
+
else
|
124
|
+
update_counter_if_success(super, 1)
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
def update_counter_if_success(saved_successfully, difference)
|
129
|
+
if saved_successfully
|
130
|
+
update_counter_in_memory(difference)
|
131
|
+
end
|
132
|
+
saved_successfully
|
133
|
+
end
|
134
|
+
|
135
|
+
def difference(a, b)
|
136
|
+
a - b
|
137
|
+
end
|
138
|
+
|
139
|
+
def intersection(a, b)
|
140
|
+
a & b
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
@@ -0,0 +1,227 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveRecord
|
4
|
+
module Associations
|
5
|
+
# = Active Record Has Many Through Association
|
6
|
+
class HasManyThroughAssociation < HasManyAssociation #:nodoc:
|
7
|
+
include ThroughAssociation
|
8
|
+
|
9
|
+
def initialize(owner, reflection)
|
10
|
+
super
|
11
|
+
@through_records = {}
|
12
|
+
end
|
13
|
+
|
14
|
+
def concat(*records)
|
15
|
+
unless owner.new_record?
|
16
|
+
records.flatten.each do |record|
|
17
|
+
raise_on_type_mismatch!(record)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
super
|
22
|
+
end
|
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
|
+
def insert_record(record, validate = true, raise = false)
|
39
|
+
ensure_not_nested
|
40
|
+
|
41
|
+
if record.new_record? || record.has_changes_to_save?
|
42
|
+
return unless super
|
43
|
+
end
|
44
|
+
|
45
|
+
save_through_record(record)
|
46
|
+
|
47
|
+
record
|
48
|
+
end
|
49
|
+
|
50
|
+
private
|
51
|
+
# The through record (built with build_record) is temporarily cached
|
52
|
+
# so that it may be reused if insert_record is subsequently called.
|
53
|
+
#
|
54
|
+
# However, after insert_record has been called, the cache is cleared in
|
55
|
+
# order to allow multiple instances of the same record in an association.
|
56
|
+
def build_through_record(record)
|
57
|
+
@through_records[record.object_id] ||= begin
|
58
|
+
ensure_mutable
|
59
|
+
|
60
|
+
through_record = through_association.build(*options_for_through_record)
|
61
|
+
through_record.send("#{source_reflection.name}=", record)
|
62
|
+
|
63
|
+
if options[:source_type]
|
64
|
+
through_record.send("#{source_reflection.foreign_type}=", options[:source_type])
|
65
|
+
end
|
66
|
+
|
67
|
+
through_record
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def options_for_through_record
|
72
|
+
[through_scope_attributes]
|
73
|
+
end
|
74
|
+
|
75
|
+
def through_scope_attributes
|
76
|
+
scope.where_values_hash(through_association.reflection.name.to_s).
|
77
|
+
except!(through_association.reflection.foreign_key,
|
78
|
+
through_association.reflection.klass.inheritance_column)
|
79
|
+
end
|
80
|
+
|
81
|
+
def save_through_record(record)
|
82
|
+
association = build_through_record(record)
|
83
|
+
if association.changed?
|
84
|
+
association.save!
|
85
|
+
end
|
86
|
+
ensure
|
87
|
+
@through_records.delete(record.object_id)
|
88
|
+
end
|
89
|
+
|
90
|
+
def build_record(attributes)
|
91
|
+
ensure_not_nested
|
92
|
+
|
93
|
+
record = super
|
94
|
+
|
95
|
+
inverse = source_reflection.inverse_of
|
96
|
+
if inverse
|
97
|
+
if inverse.collection?
|
98
|
+
record.send(inverse.name) << build_through_record(record)
|
99
|
+
elsif inverse.has_one?
|
100
|
+
record.send("#{inverse.name}=", build_through_record(record))
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
record
|
105
|
+
end
|
106
|
+
|
107
|
+
def remove_records(existing_records, records, method)
|
108
|
+
super
|
109
|
+
delete_through_records(records)
|
110
|
+
end
|
111
|
+
|
112
|
+
def target_reflection_has_associated_record?
|
113
|
+
!(through_reflection.belongs_to? && owner[through_reflection.foreign_key].blank?)
|
114
|
+
end
|
115
|
+
|
116
|
+
def update_through_counter?(method)
|
117
|
+
case method
|
118
|
+
when :destroy
|
119
|
+
!through_reflection.inverse_updates_counter_cache?
|
120
|
+
when :nullify
|
121
|
+
false
|
122
|
+
else
|
123
|
+
true
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
def delete_or_nullify_all_records(method)
|
128
|
+
delete_records(load_target, method)
|
129
|
+
end
|
130
|
+
|
131
|
+
def delete_records(records, method)
|
132
|
+
ensure_not_nested
|
133
|
+
|
134
|
+
scope = through_association.scope
|
135
|
+
scope.where! construct_join_attributes(*records)
|
136
|
+
scope = scope.where(through_scope_attributes)
|
137
|
+
|
138
|
+
case method
|
139
|
+
when :destroy
|
140
|
+
if scope.klass.primary_key
|
141
|
+
count = scope.destroy_all.count(&:destroyed?)
|
142
|
+
else
|
143
|
+
scope.each(&:_run_destroy_callbacks)
|
144
|
+
count = scope.delete_all
|
145
|
+
end
|
146
|
+
when :nullify
|
147
|
+
count = scope.update_all(source_reflection.foreign_key => nil)
|
148
|
+
else
|
149
|
+
count = scope.delete_all
|
150
|
+
end
|
151
|
+
|
152
|
+
delete_through_records(records)
|
153
|
+
|
154
|
+
if source_reflection.options[:counter_cache] && method != :destroy
|
155
|
+
counter = source_reflection.counter_cache_column
|
156
|
+
klass.decrement_counter counter, records.map(&:id)
|
157
|
+
end
|
158
|
+
|
159
|
+
if through_reflection.collection? && update_through_counter?(method)
|
160
|
+
update_counter(-count, through_reflection)
|
161
|
+
else
|
162
|
+
update_counter(-count)
|
163
|
+
end
|
164
|
+
|
165
|
+
count
|
166
|
+
end
|
167
|
+
|
168
|
+
def difference(a, b)
|
169
|
+
distribution = distribution(b)
|
170
|
+
|
171
|
+
a.reject { |record| mark_occurrence(distribution, record) }
|
172
|
+
end
|
173
|
+
|
174
|
+
def intersection(a, b)
|
175
|
+
distribution = distribution(b)
|
176
|
+
|
177
|
+
a.select { |record| mark_occurrence(distribution, record) }
|
178
|
+
end
|
179
|
+
|
180
|
+
def mark_occurrence(distribution, record)
|
181
|
+
distribution[record] > 0 && distribution[record] -= 1
|
182
|
+
end
|
183
|
+
|
184
|
+
def distribution(array)
|
185
|
+
array.each_with_object(Hash.new(0)) do |record, distribution|
|
186
|
+
distribution[record] += 1
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
def through_records_for(record)
|
191
|
+
attributes = construct_join_attributes(record)
|
192
|
+
candidates = Array.wrap(through_association.target)
|
193
|
+
candidates.find_all do |c|
|
194
|
+
attributes.all? do |key, value|
|
195
|
+
c.public_send(key) == value
|
196
|
+
end
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
200
|
+
def delete_through_records(records)
|
201
|
+
records.each do |record|
|
202
|
+
through_records = through_records_for(record)
|
203
|
+
|
204
|
+
if through_reflection.collection?
|
205
|
+
through_records.each { |r| through_association.target.delete(r) }
|
206
|
+
else
|
207
|
+
if through_records.include?(through_association.target)
|
208
|
+
through_association.target = nil
|
209
|
+
end
|
210
|
+
end
|
211
|
+
|
212
|
+
@through_records.delete(record.object_id)
|
213
|
+
end
|
214
|
+
end
|
215
|
+
|
216
|
+
def find_target
|
217
|
+
return [] unless target_reflection_has_associated_record?
|
218
|
+
super
|
219
|
+
end
|
220
|
+
|
221
|
+
# NOTE - not sure that we can actually cope with inverses here
|
222
|
+
def invertible_for?(record)
|
223
|
+
false
|
224
|
+
end
|
225
|
+
end
|
226
|
+
end
|
227
|
+
end
|
@@ -0,0 +1,120 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveRecord
|
4
|
+
module Associations
|
5
|
+
# = Active Record Has One Association
|
6
|
+
class HasOneAssociation < SingularAssociation #:nodoc:
|
7
|
+
include ForeignAssociation
|
8
|
+
|
9
|
+
def handle_dependency
|
10
|
+
case options[:dependent]
|
11
|
+
when :restrict_with_exception
|
12
|
+
raise ActiveRecord::DeleteRestrictionError.new(reflection.name) if load_target
|
13
|
+
|
14
|
+
when :restrict_with_error
|
15
|
+
if load_target
|
16
|
+
record = owner.class.human_attribute_name(reflection.name).downcase
|
17
|
+
owner.errors.add(:base, :'restrict_dependent_destroy.has_one', record: record)
|
18
|
+
throw(:abort)
|
19
|
+
end
|
20
|
+
|
21
|
+
else
|
22
|
+
delete
|
23
|
+
end
|
24
|
+
end
|
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
|
+
def delete(method = options[:dependent])
|
56
|
+
if load_target
|
57
|
+
case method
|
58
|
+
when :delete
|
59
|
+
target.delete
|
60
|
+
when :destroy
|
61
|
+
target.destroyed_by_association = reflection
|
62
|
+
target.destroy
|
63
|
+
throw(:abort) unless target.destroyed?
|
64
|
+
when :nullify
|
65
|
+
target.update_columns(reflection.foreign_key => nil) if target.persisted?
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
private
|
71
|
+
|
72
|
+
# The reason that the save param for replace is false, if for create (not just build),
|
73
|
+
# is because the setting of the foreign keys is actually handled by the scoping when
|
74
|
+
# the record is instantiated, and so they are set straight away and do not need to be
|
75
|
+
# updated within replace.
|
76
|
+
def set_new_record(record)
|
77
|
+
replace(record, false)
|
78
|
+
end
|
79
|
+
|
80
|
+
def remove_target!(method)
|
81
|
+
case method
|
82
|
+
when :delete
|
83
|
+
target.delete
|
84
|
+
when :destroy
|
85
|
+
target.destroyed_by_association = reflection
|
86
|
+
target.destroy
|
87
|
+
else
|
88
|
+
nullify_owner_attributes(target)
|
89
|
+
remove_inverse_instance(target)
|
90
|
+
|
91
|
+
if target.persisted? && owner.persisted? && !target.save
|
92
|
+
set_owner_attributes(target)
|
93
|
+
raise RecordNotSaved, "Failed to remove the existing associated #{reflection.name}. " \
|
94
|
+
"The record failed to save after its foreign key was set to nil."
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
def nullify_owner_attributes(record)
|
100
|
+
record[reflection.foreign_key] = nil
|
101
|
+
end
|
102
|
+
|
103
|
+
def transaction_if(value)
|
104
|
+
if value
|
105
|
+
reflection.klass.transaction { yield }
|
106
|
+
else
|
107
|
+
yield
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
def _create_record(attributes, raise_error = false, &block)
|
112
|
+
unless owner.persisted?
|
113
|
+
raise ActiveRecord::RecordNotSaved, "You cannot call create unless the parent is saved"
|
114
|
+
end
|
115
|
+
|
116
|
+
super
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|