activerecord 3.2.22.5 → 4.2.11.3
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.
Potentially problematic release.
This version of activerecord might be problematic. Click here for more details.
- checksums.yaml +5 -5
- data/CHANGELOG.md +1632 -609
- data/MIT-LICENSE +1 -1
- data/README.rdoc +37 -41
- data/examples/performance.rb +31 -19
- data/examples/simple.rb +4 -4
- data/lib/active_record/aggregations.rb +56 -42
- data/lib/active_record/association_relation.rb +35 -0
- data/lib/active_record/associations/alias_tracker.rb +47 -36
- data/lib/active_record/associations/association.rb +73 -55
- data/lib/active_record/associations/association_scope.rb +143 -82
- data/lib/active_record/associations/belongs_to_association.rb +65 -25
- data/lib/active_record/associations/belongs_to_polymorphic_association.rb +7 -2
- data/lib/active_record/associations/builder/association.rb +125 -31
- data/lib/active_record/associations/builder/belongs_to.rb +89 -61
- data/lib/active_record/associations/builder/collection_association.rb +69 -49
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +113 -42
- data/lib/active_record/associations/builder/has_many.rb +8 -64
- data/lib/active_record/associations/builder/has_one.rb +12 -51
- data/lib/active_record/associations/builder/singular_association.rb +23 -17
- data/lib/active_record/associations/collection_association.rb +251 -177
- data/lib/active_record/associations/collection_proxy.rb +963 -63
- data/lib/active_record/associations/foreign_association.rb +11 -0
- data/lib/active_record/associations/has_many_association.rb +113 -22
- data/lib/active_record/associations/has_many_through_association.rb +99 -39
- data/lib/active_record/associations/has_one_association.rb +43 -20
- data/lib/active_record/associations/has_one_through_association.rb +1 -1
- data/lib/active_record/associations/join_dependency/join_association.rb +76 -107
- data/lib/active_record/associations/join_dependency/join_base.rb +7 -9
- data/lib/active_record/associations/join_dependency/join_part.rb +30 -37
- data/lib/active_record/associations/join_dependency.rb +230 -156
- data/lib/active_record/associations/preloader/association.rb +96 -55
- data/lib/active_record/associations/preloader/collection_association.rb +3 -3
- data/lib/active_record/associations/preloader/has_many_through.rb +7 -3
- data/lib/active_record/associations/preloader/has_one.rb +1 -1
- data/lib/active_record/associations/preloader/singular_association.rb +3 -3
- data/lib/active_record/associations/preloader/through_association.rb +62 -33
- data/lib/active_record/associations/preloader.rb +101 -79
- data/lib/active_record/associations/singular_association.rb +29 -13
- data/lib/active_record/associations/through_association.rb +30 -16
- data/lib/active_record/associations.rb +463 -345
- data/lib/active_record/attribute.rb +163 -0
- data/lib/active_record/attribute_assignment.rb +142 -151
- data/lib/active_record/attribute_decorators.rb +66 -0
- data/lib/active_record/attribute_methods/before_type_cast.rb +52 -7
- data/lib/active_record/attribute_methods/dirty.rb +137 -57
- data/lib/active_record/attribute_methods/primary_key.rb +50 -36
- data/lib/active_record/attribute_methods/query.rb +5 -4
- data/lib/active_record/attribute_methods/read.rb +73 -106
- data/lib/active_record/attribute_methods/serialization.rb +44 -94
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +49 -45
- data/lib/active_record/attribute_methods/write.rb +57 -44
- data/lib/active_record/attribute_methods.rb +301 -141
- data/lib/active_record/attribute_set/builder.rb +106 -0
- data/lib/active_record/attribute_set.rb +81 -0
- data/lib/active_record/attributes.rb +147 -0
- data/lib/active_record/autosave_association.rb +246 -217
- data/lib/active_record/base.rb +70 -474
- data/lib/active_record/callbacks.rb +66 -28
- data/lib/active_record/coders/json.rb +13 -0
- data/lib/active_record/coders/yaml_column.rb +18 -21
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +396 -219
- data/lib/active_record/connection_adapters/abstract/database_limits.rb +9 -0
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +167 -164
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +29 -24
- data/lib/active_record/connection_adapters/abstract/quoting.rb +74 -55
- data/lib/active_record/connection_adapters/abstract/savepoints.rb +21 -0
- data/lib/active_record/connection_adapters/abstract/schema_creation.rb +125 -0
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +261 -169
- data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +50 -0
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +707 -259
- data/lib/active_record/connection_adapters/abstract/transaction.rb +215 -0
- data/lib/active_record/connection_adapters/abstract_adapter.rb +298 -89
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +466 -196
- data/lib/active_record/connection_adapters/column.rb +31 -245
- data/lib/active_record/connection_adapters/connection_specification.rb +275 -0
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +45 -57
- data/lib/active_record/connection_adapters/mysql_adapter.rb +180 -123
- data/lib/active_record/connection_adapters/postgresql/array_parser.rb +93 -0
- data/lib/active_record/connection_adapters/postgresql/column.rb +20 -0
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +232 -0
- data/lib/active_record/connection_adapters/postgresql/oid/array.rb +100 -0
- data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +52 -0
- data/lib/active_record/connection_adapters/postgresql/oid/bit_varying.rb +13 -0
- data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +15 -0
- data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +46 -0
- data/lib/active_record/connection_adapters/postgresql/oid/date.rb +11 -0
- data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +36 -0
- data/lib/active_record/connection_adapters/postgresql/oid/decimal.rb +13 -0
- data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +19 -0
- data/lib/active_record/connection_adapters/postgresql/oid/float.rb +21 -0
- data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +59 -0
- data/lib/active_record/connection_adapters/postgresql/oid/inet.rb +13 -0
- data/lib/active_record/connection_adapters/postgresql/oid/infinity.rb +13 -0
- data/lib/active_record/connection_adapters/postgresql/oid/integer.rb +11 -0
- data/lib/active_record/connection_adapters/postgresql/oid/json.rb +35 -0
- data/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb +23 -0
- data/lib/active_record/connection_adapters/postgresql/oid/money.rb +43 -0
- data/lib/active_record/connection_adapters/postgresql/oid/point.rb +43 -0
- data/lib/active_record/connection_adapters/postgresql/oid/range.rb +79 -0
- data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +19 -0
- data/lib/active_record/connection_adapters/postgresql/oid/time.rb +11 -0
- data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +109 -0
- data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +21 -0
- data/lib/active_record/connection_adapters/postgresql/oid/vector.rb +26 -0
- data/lib/active_record/connection_adapters/postgresql/oid/xml.rb +28 -0
- data/lib/active_record/connection_adapters/postgresql/oid.rb +36 -0
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +108 -0
- data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +30 -0
- data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +152 -0
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +596 -0
- data/lib/active_record/connection_adapters/postgresql/utils.rb +77 -0
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +430 -999
- data/lib/active_record/connection_adapters/schema_cache.rb +52 -27
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +579 -22
- data/lib/active_record/connection_handling.rb +132 -0
- data/lib/active_record/core.rb +579 -0
- data/lib/active_record/counter_cache.rb +157 -105
- data/lib/active_record/dynamic_matchers.rb +119 -63
- data/lib/active_record/enum.rb +197 -0
- data/lib/active_record/errors.rb +94 -36
- data/lib/active_record/explain.rb +15 -63
- data/lib/active_record/explain_registry.rb +30 -0
- data/lib/active_record/explain_subscriber.rb +9 -5
- data/lib/active_record/fixture_set/file.rb +56 -0
- data/lib/active_record/fixtures.rb +302 -215
- data/lib/active_record/gem_version.rb +15 -0
- data/lib/active_record/inheritance.rb +143 -70
- data/lib/active_record/integration.rb +65 -12
- data/lib/active_record/legacy_yaml_adapter.rb +30 -0
- data/lib/active_record/locale/en.yml +8 -1
- data/lib/active_record/locking/optimistic.rb +73 -52
- data/lib/active_record/locking/pessimistic.rb +5 -5
- data/lib/active_record/log_subscriber.rb +24 -21
- data/lib/active_record/migration/command_recorder.rb +124 -32
- data/lib/active_record/migration/join_table.rb +15 -0
- data/lib/active_record/migration.rb +511 -213
- data/lib/active_record/model_schema.rb +91 -117
- data/lib/active_record/nested_attributes.rb +184 -130
- data/lib/active_record/no_touching.rb +52 -0
- data/lib/active_record/null_relation.rb +81 -0
- data/lib/active_record/persistence.rb +276 -117
- data/lib/active_record/query_cache.rb +19 -37
- data/lib/active_record/querying.rb +28 -18
- data/lib/active_record/railtie.rb +73 -40
- data/lib/active_record/railties/console_sandbox.rb +3 -4
- data/lib/active_record/railties/controller_runtime.rb +4 -3
- data/lib/active_record/railties/databases.rake +141 -416
- data/lib/active_record/railties/jdbcmysql_error.rb +1 -1
- data/lib/active_record/readonly_attributes.rb +1 -4
- data/lib/active_record/reflection.rb +513 -154
- data/lib/active_record/relation/batches.rb +91 -43
- data/lib/active_record/relation/calculations.rb +199 -161
- data/lib/active_record/relation/delegation.rb +116 -25
- data/lib/active_record/relation/finder_methods.rb +362 -248
- data/lib/active_record/relation/merger.rb +193 -0
- data/lib/active_record/relation/predicate_builder/array_handler.rb +48 -0
- data/lib/active_record/relation/predicate_builder/relation_handler.rb +13 -0
- data/lib/active_record/relation/predicate_builder.rb +135 -43
- data/lib/active_record/relation/query_methods.rb +928 -167
- data/lib/active_record/relation/spawn_methods.rb +48 -149
- data/lib/active_record/relation.rb +352 -207
- data/lib/active_record/result.rb +101 -10
- data/lib/active_record/runtime_registry.rb +22 -0
- data/lib/active_record/sanitization.rb +56 -59
- data/lib/active_record/schema.rb +19 -13
- data/lib/active_record/schema_dumper.rb +106 -63
- data/lib/active_record/schema_migration.rb +53 -0
- data/lib/active_record/scoping/default.rb +50 -57
- data/lib/active_record/scoping/named.rb +73 -109
- data/lib/active_record/scoping.rb +58 -123
- data/lib/active_record/serialization.rb +6 -2
- data/lib/active_record/serializers/xml_serializer.rb +12 -22
- data/lib/active_record/statement_cache.rb +111 -0
- data/lib/active_record/store.rb +168 -15
- data/lib/active_record/tasks/database_tasks.rb +299 -0
- data/lib/active_record/tasks/mysql_database_tasks.rb +159 -0
- data/lib/active_record/tasks/postgresql_database_tasks.rb +101 -0
- data/lib/active_record/tasks/sqlite_database_tasks.rb +55 -0
- data/lib/active_record/timestamp.rb +23 -16
- data/lib/active_record/transactions.rb +125 -79
- data/lib/active_record/type/big_integer.rb +13 -0
- data/lib/active_record/type/binary.rb +50 -0
- data/lib/active_record/type/boolean.rb +31 -0
- data/lib/active_record/type/date.rb +50 -0
- data/lib/active_record/type/date_time.rb +54 -0
- data/lib/active_record/type/decimal.rb +64 -0
- data/lib/active_record/type/decimal_without_scale.rb +11 -0
- data/lib/active_record/type/decorator.rb +14 -0
- data/lib/active_record/type/float.rb +19 -0
- data/lib/active_record/type/hash_lookup_type_map.rb +23 -0
- data/lib/active_record/type/integer.rb +59 -0
- data/lib/active_record/type/mutable.rb +16 -0
- data/lib/active_record/type/numeric.rb +36 -0
- data/lib/active_record/type/serialized.rb +62 -0
- data/lib/active_record/type/string.rb +40 -0
- data/lib/active_record/type/text.rb +11 -0
- data/lib/active_record/type/time.rb +26 -0
- data/lib/active_record/type/time_value.rb +38 -0
- data/lib/active_record/type/type_map.rb +64 -0
- data/lib/active_record/type/unsigned_integer.rb +15 -0
- data/lib/active_record/type/value.rb +110 -0
- data/lib/active_record/type.rb +23 -0
- data/lib/active_record/validations/associated.rb +24 -16
- data/lib/active_record/validations/presence.rb +67 -0
- data/lib/active_record/validations/uniqueness.rb +123 -64
- data/lib/active_record/validations.rb +36 -29
- data/lib/active_record/version.rb +5 -7
- data/lib/active_record.rb +66 -46
- data/lib/rails/generators/active_record/migration/migration_generator.rb +53 -8
- data/lib/rails/generators/active_record/{model/templates/migration.rb → migration/templates/create_table_migration.rb} +5 -1
- data/lib/rails/generators/active_record/migration/templates/migration.rb +20 -15
- data/lib/rails/generators/active_record/migration.rb +11 -8
- data/lib/rails/generators/active_record/model/model_generator.rb +9 -4
- data/lib/rails/generators/active_record/model/templates/model.rb +4 -6
- data/lib/rails/generators/active_record/model/templates/module.rb +1 -1
- data/lib/rails/generators/active_record.rb +3 -11
- metadata +101 -45
- data/examples/associations.png +0 -0
- data/lib/active_record/associations/has_and_belongs_to_many_association.rb +0 -63
- data/lib/active_record/associations/join_helper.rb +0 -55
- data/lib/active_record/associations/preloader/has_and_belongs_to_many.rb +0 -60
- data/lib/active_record/attribute_methods/deprecated_underscore_read.rb +0 -32
- data/lib/active_record/connection_adapters/abstract/connection_specification.rb +0 -191
- data/lib/active_record/connection_adapters/sqlite_adapter.rb +0 -583
- data/lib/active_record/dynamic_finder_match.rb +0 -68
- data/lib/active_record/dynamic_scope_match.rb +0 -23
- data/lib/active_record/fixtures/file.rb +0 -65
- data/lib/active_record/identity_map.rb +0 -162
- data/lib/active_record/observer.rb +0 -121
- data/lib/active_record/session_store.rb +0 -360
- data/lib/active_record/test_case.rb +0 -73
- data/lib/rails/generators/active_record/observer/observer_generator.rb +0 -15
- data/lib/rails/generators/active_record/observer/templates/observer.rb +0 -4
- data/lib/rails/generators/active_record/session_migration/session_migration_generator.rb +0 -25
- data/lib/rails/generators/active_record/session_migration/templates/migration.rb +0 -12
@@ -6,6 +6,30 @@ module ActiveRecord
|
|
6
6
|
# If the association has a <tt>:through</tt> option further specialization
|
7
7
|
# is provided by its child HasManyThroughAssociation.
|
8
8
|
class HasManyAssociation < CollectionAssociation #:nodoc:
|
9
|
+
include ForeignAssociation
|
10
|
+
|
11
|
+
def handle_dependency
|
12
|
+
case options[:dependent]
|
13
|
+
when :restrict_with_exception
|
14
|
+
raise ActiveRecord::DeleteRestrictionError.new(reflection.name) unless empty?
|
15
|
+
|
16
|
+
when :restrict_with_error
|
17
|
+
unless empty?
|
18
|
+
record = klass.human_attribute_name(reflection.name).downcase
|
19
|
+
owner.errors.add(:base, :"restrict_dependent_destroy.many", record: record)
|
20
|
+
false
|
21
|
+
end
|
22
|
+
|
23
|
+
else
|
24
|
+
if options[:dependent] == :destroy
|
25
|
+
# No point in executing the counter update since we're going to destroy the parent anyway
|
26
|
+
load_target.each { |t| t.destroyed_by_association = reflection }
|
27
|
+
destroy_all
|
28
|
+
else
|
29
|
+
delete_all
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
9
33
|
|
10
34
|
def insert_record(record, validate = true, raise = false)
|
11
35
|
set_owner_attributes(record)
|
@@ -18,6 +42,14 @@ module ActiveRecord
|
|
18
42
|
end
|
19
43
|
end
|
20
44
|
|
45
|
+
def empty?
|
46
|
+
if has_cached_counter?
|
47
|
+
size.zero?
|
48
|
+
else
|
49
|
+
super
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
21
53
|
private
|
22
54
|
|
23
55
|
# Returns the number of records in this collection.
|
@@ -35,11 +67,9 @@ module ActiveRecord
|
|
35
67
|
# the loaded flag is set to true as well.
|
36
68
|
def count_records
|
37
69
|
count = if has_cached_counter?
|
38
|
-
owner.
|
39
|
-
elsif options[:counter_sql] || options[:finder_sql]
|
40
|
-
reflection.klass.count_by_sql(custom_counter_sql)
|
70
|
+
owner._read_attribute cached_counter_attribute_name
|
41
71
|
else
|
42
|
-
|
72
|
+
scope.count
|
43
73
|
end
|
44
74
|
|
45
75
|
# If there's nothing in the database and @target has no new records
|
@@ -47,23 +77,45 @@ module ActiveRecord
|
|
47
77
|
# documented side-effect of the method that may avoid an extra SELECT.
|
48
78
|
@target ||= [] and loaded! if count == 0
|
49
79
|
|
50
|
-
[
|
80
|
+
[association_scope.limit_value, count].compact.min
|
81
|
+
end
|
82
|
+
|
83
|
+
|
84
|
+
# Returns whether a counter cache should be used for this association.
|
85
|
+
#
|
86
|
+
# The counter_cache option must be given on either the owner or inverse
|
87
|
+
# association, and the column must be present on the owner.
|
88
|
+
def has_cached_counter?(reflection = reflection())
|
89
|
+
if reflection.options[:counter_cache] || (inverse = inverse_which_updates_counter_cache(reflection)) && inverse.options[:counter_cache]
|
90
|
+
owner.attribute_present?(cached_counter_attribute_name(reflection))
|
91
|
+
end
|
51
92
|
end
|
52
93
|
|
53
|
-
def
|
54
|
-
|
94
|
+
def cached_counter_attribute_name(reflection = reflection())
|
95
|
+
if reflection.options[:counter_cache]
|
96
|
+
reflection.options[:counter_cache].to_s
|
97
|
+
else
|
98
|
+
"#{reflection.name}_count"
|
99
|
+
end
|
55
100
|
end
|
56
101
|
|
57
|
-
def
|
58
|
-
|
102
|
+
def update_counter(difference, reflection = reflection())
|
103
|
+
update_counter_in_database(difference, reflection)
|
104
|
+
update_counter_in_memory(difference, reflection)
|
59
105
|
end
|
60
106
|
|
61
|
-
def
|
107
|
+
def update_counter_in_database(difference, reflection = reflection())
|
62
108
|
if has_cached_counter?(reflection)
|
63
109
|
counter = cached_counter_attribute_name(reflection)
|
64
110
|
owner.class.update_counters(owner.id, counter => difference)
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
def update_counter_in_memory(difference, reflection = reflection())
|
115
|
+
if counter_must_be_updated_by_has_many?(reflection)
|
116
|
+
counter = cached_counter_attribute_name(reflection)
|
65
117
|
owner[counter] += difference
|
66
|
-
owner.
|
118
|
+
owner.send(:clear_attribute_changes, counter) # eww
|
67
119
|
end
|
68
120
|
end
|
69
121
|
|
@@ -77,31 +129,70 @@ module ActiveRecord
|
|
77
129
|
# it will be decremented twice.
|
78
130
|
#
|
79
131
|
# Hence this method.
|
80
|
-
def
|
132
|
+
def inverse_which_updates_counter_cache(reflection = reflection())
|
81
133
|
counter_name = cached_counter_attribute_name(reflection)
|
82
|
-
reflection
|
134
|
+
inverse_which_updates_counter_named(counter_name, reflection)
|
135
|
+
end
|
136
|
+
alias inverse_updates_counter_cache? inverse_which_updates_counter_cache
|
137
|
+
|
138
|
+
def inverse_which_updates_counter_named(counter_name, reflection)
|
139
|
+
reflection.klass._reflections.values.find { |inverse_reflection|
|
140
|
+
inverse_reflection.belongs_to? &&
|
83
141
|
inverse_reflection.counter_cache_column == counter_name
|
84
142
|
}
|
85
143
|
end
|
144
|
+
alias inverse_updates_counter_named? inverse_which_updates_counter_named
|
145
|
+
|
146
|
+
def inverse_updates_counter_in_memory?(reflection)
|
147
|
+
inverse = inverse_which_updates_counter_cache(reflection)
|
148
|
+
inverse && inverse == reflection.inverse_of
|
149
|
+
end
|
150
|
+
|
151
|
+
def counter_must_be_updated_by_has_many?(reflection)
|
152
|
+
!inverse_updates_counter_in_memory?(reflection) && has_cached_counter?(reflection)
|
153
|
+
end
|
154
|
+
|
155
|
+
def delete_count(method, scope)
|
156
|
+
if method == :delete_all
|
157
|
+
scope.delete_all
|
158
|
+
else
|
159
|
+
scope.update_all(reflection.foreign_key => nil)
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
def delete_or_nullify_all_records(method)
|
164
|
+
count = delete_count(method, self.scope)
|
165
|
+
update_counter(-count)
|
166
|
+
end
|
86
167
|
|
87
168
|
# Deletes the records according to the <tt>:dependent</tt> option.
|
88
169
|
def delete_records(records, method)
|
89
170
|
if method == :destroy
|
90
|
-
records.each
|
171
|
+
records.each(&:destroy!)
|
91
172
|
update_counter(-records.length) unless inverse_updates_counter_cache?
|
92
173
|
else
|
93
|
-
scope = self.
|
174
|
+
scope = self.scope.where(reflection.klass.primary_key => records)
|
175
|
+
update_counter(-delete_count(method, scope))
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
def concat_records(records, *)
|
180
|
+
update_counter_if_success(super, records.length)
|
181
|
+
end
|
94
182
|
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
183
|
+
def _create_record(attributes, *)
|
184
|
+
if attributes.is_a?(Array)
|
185
|
+
super
|
186
|
+
else
|
187
|
+
update_counter_if_success(super, 1)
|
100
188
|
end
|
101
189
|
end
|
102
190
|
|
103
|
-
def
|
104
|
-
|
191
|
+
def update_counter_if_success(saved_successfully, difference)
|
192
|
+
if saved_successfully
|
193
|
+
update_counter_in_memory(difference)
|
194
|
+
end
|
195
|
+
saved_successfully
|
105
196
|
end
|
106
197
|
end
|
107
198
|
end
|
@@ -1,4 +1,4 @@
|
|
1
|
-
require 'active_support/core_ext/
|
1
|
+
require 'active_support/core_ext/string/filters'
|
2
2
|
|
3
3
|
module ActiveRecord
|
4
4
|
# = Active Record Has Many Through Association
|
@@ -13,31 +13,45 @@ module ActiveRecord
|
|
13
13
|
@through_association = nil
|
14
14
|
end
|
15
15
|
|
16
|
-
# Returns the size of the collection by executing a SELECT COUNT(*) query
|
17
|
-
#
|
18
|
-
#
|
19
|
-
#
|
16
|
+
# Returns the size of the collection by executing a SELECT COUNT(*) query
|
17
|
+
# if the collection hasn't been loaded, and by calling collection.size if
|
18
|
+
# it has. If the collection will likely have a size greater than zero,
|
19
|
+
# and if fetching the collection will be needed afterwards, one less
|
20
|
+
# SELECT query will be generated by using #length instead.
|
20
21
|
def size
|
21
22
|
if has_cached_counter?
|
22
|
-
owner.
|
23
|
+
owner._read_attribute cached_counter_attribute_name(reflection)
|
23
24
|
elsif loaded?
|
24
25
|
target.size
|
25
26
|
else
|
26
|
-
|
27
|
+
super
|
27
28
|
end
|
28
29
|
end
|
29
30
|
|
30
31
|
def concat(*records)
|
31
32
|
unless owner.new_record?
|
32
33
|
records.flatten.each do |record|
|
33
|
-
raise_on_type_mismatch(record)
|
34
|
-
record.save! if record.new_record?
|
34
|
+
raise_on_type_mismatch!(record)
|
35
35
|
end
|
36
36
|
end
|
37
37
|
|
38
38
|
super
|
39
39
|
end
|
40
40
|
|
41
|
+
def concat_records(records)
|
42
|
+
ensure_not_nested
|
43
|
+
|
44
|
+
records = super(records, true)
|
45
|
+
|
46
|
+
if owner.new_record? && records
|
47
|
+
records.flatten.each do |record|
|
48
|
+
build_through_record(record)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
records
|
53
|
+
end
|
54
|
+
|
41
55
|
def insert_record(record, validate = true, raise = false)
|
42
56
|
ensure_not_nested
|
43
57
|
|
@@ -50,53 +64,71 @@ module ActiveRecord
|
|
50
64
|
end
|
51
65
|
|
52
66
|
save_through_record(record)
|
53
|
-
|
67
|
+
if has_cached_counter? && !through_reflection_updates_counter_cache?
|
68
|
+
ActiveSupport::Deprecation.warn(<<-MSG.squish)
|
69
|
+
Automatic updating of counter caches on through associations has been
|
70
|
+
deprecated, and will be removed in Rails 5. Instead, please set the
|
71
|
+
appropriate `counter_cache` options on the `has_many` and `belongs_to`
|
72
|
+
for your associations to #{through_reflection.name}.
|
73
|
+
MSG
|
74
|
+
|
75
|
+
update_counter_in_database(1)
|
76
|
+
end
|
54
77
|
record
|
55
78
|
end
|
56
79
|
|
57
|
-
# ActiveRecord::Relation#delete_all needs to support joins before we can use a
|
58
|
-
# SQL-only implementation.
|
59
|
-
alias delete_all_on_destroy delete_all
|
60
|
-
|
61
80
|
private
|
62
81
|
|
63
82
|
def through_association
|
64
83
|
@through_association ||= owner.association(through_reflection.name)
|
65
84
|
end
|
66
85
|
|
67
|
-
#
|
68
|
-
#
|
69
|
-
# want to use the exact same object.
|
86
|
+
# The through record (built with build_record) is temporarily cached
|
87
|
+
# so that it may be reused if insert_record is subsequently called.
|
70
88
|
#
|
71
|
-
# However, after insert_record has been called,
|
72
|
-
#
|
73
|
-
# association
|
89
|
+
# However, after insert_record has been called, the cache is cleared in
|
90
|
+
# order to allow multiple instances of the same record in an association.
|
74
91
|
def build_through_record(record)
|
75
92
|
@through_records[record.object_id] ||= begin
|
76
93
|
ensure_mutable
|
77
94
|
|
78
|
-
through_record = through_association.build
|
95
|
+
through_record = through_association.build(*options_for_through_record)
|
79
96
|
through_record.send("#{source_reflection.name}=", record)
|
97
|
+
|
98
|
+
if options[:source_type]
|
99
|
+
through_record.send("#{source_reflection.foreign_type}=", options[:source_type])
|
100
|
+
end
|
101
|
+
|
80
102
|
through_record
|
81
103
|
end
|
82
104
|
end
|
83
105
|
|
106
|
+
def options_for_through_record
|
107
|
+
[through_scope_attributes]
|
108
|
+
end
|
109
|
+
|
110
|
+
def through_scope_attributes
|
111
|
+
scope.where_values_hash(through_association.reflection.name.to_s).
|
112
|
+
except!(through_association.reflection.foreign_key,
|
113
|
+
through_association.reflection.klass.inheritance_column)
|
114
|
+
end
|
115
|
+
|
84
116
|
def save_through_record(record)
|
85
117
|
build_through_record(record).save!
|
86
118
|
ensure
|
87
119
|
@through_records.delete(record.object_id)
|
88
120
|
end
|
89
121
|
|
90
|
-
def build_record(attributes
|
122
|
+
def build_record(attributes)
|
91
123
|
ensure_not_nested
|
92
124
|
|
93
|
-
record = super(attributes
|
125
|
+
record = super(attributes)
|
94
126
|
|
95
127
|
inverse = source_reflection.inverse_of
|
96
128
|
if inverse
|
97
|
-
if inverse.
|
129
|
+
if inverse.collection?
|
98
130
|
record.send(inverse.name) << build_through_record(record)
|
99
|
-
elsif inverse.
|
131
|
+
elsif inverse.has_one?
|
100
132
|
record.send("#{inverse.name}=", build_through_record(record))
|
101
133
|
end
|
102
134
|
end
|
@@ -105,11 +137,7 @@ module ActiveRecord
|
|
105
137
|
end
|
106
138
|
|
107
139
|
def target_reflection_has_associated_record?
|
108
|
-
|
109
|
-
false
|
110
|
-
else
|
111
|
-
true
|
112
|
-
end
|
140
|
+
!(through_reflection.belongs_to? && owner[through_reflection.foreign_key].blank?)
|
113
141
|
end
|
114
142
|
|
115
143
|
def update_through_counter?(method)
|
@@ -123,14 +151,33 @@ module ActiveRecord
|
|
123
151
|
end
|
124
152
|
end
|
125
153
|
|
154
|
+
def delete_or_nullify_all_records(method)
|
155
|
+
delete_records(load_target, method)
|
156
|
+
end
|
157
|
+
|
126
158
|
def delete_records(records, method)
|
127
159
|
ensure_not_nested
|
128
160
|
|
129
|
-
scope = through_association.
|
161
|
+
scope = through_association.scope
|
162
|
+
scope.where! construct_join_attributes(*records)
|
130
163
|
|
131
164
|
case method
|
132
165
|
when :destroy
|
133
|
-
|
166
|
+
if scope.klass.primary_key
|
167
|
+
count = scope.destroy_all.length
|
168
|
+
else
|
169
|
+
scope.each do |record|
|
170
|
+
record._run_destroy_callbacks
|
171
|
+
end
|
172
|
+
|
173
|
+
arel = scope.arel
|
174
|
+
|
175
|
+
stmt = Arel::DeleteManager.new arel.engine
|
176
|
+
stmt.from scope.klass.arel_table
|
177
|
+
stmt.wheres = arel.constraints
|
178
|
+
|
179
|
+
count = scope.klass.connection.delete(stmt, 'SQL', scope.bind_values)
|
180
|
+
end
|
134
181
|
when :nullify
|
135
182
|
count = scope.update_all(source_reflection.foreign_key => nil)
|
136
183
|
else
|
@@ -139,29 +186,33 @@ module ActiveRecord
|
|
139
186
|
|
140
187
|
delete_through_records(records)
|
141
188
|
|
142
|
-
if source_reflection.options[:counter_cache]
|
189
|
+
if source_reflection.options[:counter_cache] && method != :destroy
|
143
190
|
counter = source_reflection.counter_cache_column
|
144
191
|
klass.decrement_counter counter, records.map(&:id)
|
145
192
|
end
|
146
193
|
|
147
|
-
if through_reflection.
|
194
|
+
if through_reflection.collection? && update_through_counter?(method)
|
148
195
|
update_counter(-count, through_reflection)
|
196
|
+
else
|
197
|
+
update_counter(-count)
|
149
198
|
end
|
150
|
-
|
151
|
-
update_counter(-count)
|
152
199
|
end
|
153
200
|
|
154
201
|
def through_records_for(record)
|
155
202
|
attributes = construct_join_attributes(record)
|
156
203
|
candidates = Array.wrap(through_association.target)
|
157
|
-
candidates.find_all
|
204
|
+
candidates.find_all do |c|
|
205
|
+
attributes.all? do |key, value|
|
206
|
+
c.public_send(key) == value
|
207
|
+
end
|
208
|
+
end
|
158
209
|
end
|
159
210
|
|
160
211
|
def delete_through_records(records)
|
161
212
|
records.each do |record|
|
162
213
|
through_records = through_records_for(record)
|
163
214
|
|
164
|
-
if through_reflection.
|
215
|
+
if through_reflection.collection?
|
165
216
|
through_records.each { |r| through_association.target.delete(r) }
|
166
217
|
else
|
167
218
|
if through_records.include?(through_association.target)
|
@@ -175,13 +226,22 @@ module ActiveRecord
|
|
175
226
|
|
176
227
|
def find_target
|
177
228
|
return [] unless target_reflection_has_associated_record?
|
178
|
-
|
229
|
+
get_records
|
179
230
|
end
|
180
231
|
|
181
232
|
# NOTE - not sure that we can actually cope with inverses here
|
182
233
|
def invertible_for?(record)
|
183
234
|
false
|
184
235
|
end
|
236
|
+
|
237
|
+
def has_cached_counter?(reflection = reflection())
|
238
|
+
owner.attribute_present?(cached_counter_attribute_name(reflection))
|
239
|
+
end
|
240
|
+
|
241
|
+
def through_reflection_updates_counter_cache?
|
242
|
+
counter_name = cached_counter_attribute_name
|
243
|
+
inverse_updates_counter_named?(counter_name, through_reflection)
|
244
|
+
end
|
185
245
|
end
|
186
246
|
end
|
187
247
|
end
|
@@ -1,24 +1,44 @@
|
|
1
|
-
require 'active_support/core_ext/object/inclusion'
|
2
|
-
|
3
1
|
module ActiveRecord
|
4
2
|
# = Active Record Belongs To Has One Association
|
5
3
|
module Associations
|
6
4
|
class HasOneAssociation < SingularAssociation #:nodoc:
|
5
|
+
include ForeignAssociation
|
6
|
+
|
7
|
+
def handle_dependency
|
8
|
+
case options[:dependent]
|
9
|
+
when :restrict_with_exception
|
10
|
+
raise ActiveRecord::DeleteRestrictionError.new(reflection.name) if load_target
|
11
|
+
|
12
|
+
when :restrict_with_error
|
13
|
+
if load_target
|
14
|
+
record = klass.human_attribute_name(reflection.name).downcase
|
15
|
+
owner.errors.add(:base, :"restrict_dependent_destroy.one", record: record)
|
16
|
+
false
|
17
|
+
end
|
18
|
+
|
19
|
+
else
|
20
|
+
delete
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
7
24
|
def replace(record, save = true)
|
8
|
-
raise_on_type_mismatch(record) if record
|
25
|
+
raise_on_type_mismatch!(record) if record
|
9
26
|
load_target
|
10
27
|
|
11
|
-
|
12
|
-
|
13
|
-
|
28
|
+
return self.target if !(target || record)
|
29
|
+
|
30
|
+
assigning_another_record = target != record
|
31
|
+
if assigning_another_record || record.changed?
|
32
|
+
save &&= owner.persisted?
|
33
|
+
|
14
34
|
transaction_if(save) do
|
15
|
-
remove_target!(options[:dependent]) if target && !target.destroyed?
|
16
|
-
|
35
|
+
remove_target!(options[:dependent]) if target && !target.destroyed? && assigning_another_record
|
36
|
+
|
17
37
|
if record
|
18
38
|
set_owner_attributes(record)
|
19
39
|
set_inverse_instance(record)
|
20
|
-
|
21
|
-
if
|
40
|
+
|
41
|
+
if save && !record.save
|
22
42
|
nullify_owner_attributes(record)
|
23
43
|
set_owner_attributes(target) if target
|
24
44
|
raise RecordNotSaved, "Failed to save the new associated #{reflection.name}."
|
@@ -38,7 +58,7 @@ module ActiveRecord
|
|
38
58
|
when :destroy
|
39
59
|
target.destroy
|
40
60
|
when :nullify
|
41
|
-
target.
|
61
|
+
target.update_columns(reflection.foreign_key => nil)
|
42
62
|
end
|
43
63
|
end
|
44
64
|
end
|
@@ -54,16 +74,19 @@ module ActiveRecord
|
|
54
74
|
end
|
55
75
|
|
56
76
|
def remove_target!(method)
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
77
|
+
case method
|
78
|
+
when :delete
|
79
|
+
target.delete
|
80
|
+
when :destroy
|
81
|
+
target.destroy
|
82
|
+
else
|
83
|
+
nullify_owner_attributes(target)
|
61
84
|
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
85
|
+
if target.persisted? && owner.persisted? && !target.save
|
86
|
+
set_owner_attributes(target)
|
87
|
+
raise RecordNotSaved, "Failed to remove the existing associated #{reflection.name}. " +
|
88
|
+
"The record failed to save after its foreign key was set to nil."
|
89
|
+
end
|
67
90
|
end
|
68
91
|
end
|
69
92
|
|