activerecord 3.1.10 → 4.2.11
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 +6 -6
- data/CHANGELOG.md +1837 -338
- data/MIT-LICENSE +1 -1
- data/README.rdoc +39 -43
- data/examples/performance.rb +51 -20
- data/examples/simple.rb +4 -4
- data/lib/active_record/aggregations.rb +57 -43
- data/lib/active_record/association_relation.rb +35 -0
- data/lib/active_record/associations/alias_tracker.rb +47 -39
- data/lib/active_record/associations/association.rb +71 -85
- data/lib/active_record/associations/association_scope.rb +138 -89
- data/lib/active_record/associations/belongs_to_association.rb +65 -25
- data/lib/active_record/associations/belongs_to_polymorphic_association.rb +9 -3
- data/lib/active_record/associations/builder/association.rb +125 -29
- data/lib/active_record/associations/builder/belongs_to.rb +91 -60
- 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 -52
- data/lib/active_record/associations/builder/singular_association.rb +22 -29
- data/lib/active_record/associations/collection_association.rb +294 -187
- data/lib/active_record/associations/collection_proxy.rb +961 -94
- data/lib/active_record/associations/foreign_association.rb +11 -0
- data/lib/active_record/associations/has_many_association.rb +118 -23
- data/lib/active_record/associations/has_many_through_association.rb +115 -45
- data/lib/active_record/associations/has_one_association.rb +57 -24
- data/lib/active_record/associations/has_one_through_association.rb +1 -1
- data/lib/active_record/associations/join_dependency/join_association.rb +76 -102
- 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 +61 -32
- data/lib/active_record/associations/preloader.rb +113 -87
- data/lib/active_record/associations/singular_association.rb +29 -13
- data/lib/active_record/associations/through_association.rb +37 -19
- data/lib/active_record/associations.rb +505 -371
- data/lib/active_record/attribute.rb +163 -0
- data/lib/active_record/attribute_assignment.rb +212 -0
- 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 +141 -51
- data/lib/active_record/attribute_methods/primary_key.rb +87 -36
- data/lib/active_record/attribute_methods/query.rb +5 -4
- data/lib/active_record/attribute_methods/read.rb +74 -117
- data/lib/active_record/attribute_methods/serialization.rb +70 -0
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +49 -47
- data/lib/active_record/attribute_methods/write.rb +60 -21
- data/lib/active_record/attribute_methods.rb +409 -48
- 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 +279 -232
- data/lib/active_record/base.rb +84 -1969
- 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 +422 -243
- data/lib/active_record/connection_adapters/abstract/database_limits.rb +9 -0
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +170 -194
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +32 -19
- data/lib/active_record/connection_adapters/abstract/quoting.rb +79 -57
- 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 +273 -170
- data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +50 -0
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +731 -254
- data/lib/active_record/connection_adapters/abstract/transaction.rb +215 -0
- data/lib/active_record/connection_adapters/abstract_adapter.rb +339 -95
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +946 -0
- data/lib/active_record/connection_adapters/column.rb +33 -221
- data/lib/active_record/connection_adapters/connection_specification.rb +275 -0
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +140 -602
- data/lib/active_record/connection_adapters/mysql_adapter.rb +254 -756
- 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 +445 -902
- data/lib/active_record/connection_adapters/schema_cache.rb +94 -0
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +578 -25
- data/lib/active_record/connection_handling.rb +132 -0
- data/lib/active_record/core.rb +579 -0
- data/lib/active_record/counter_cache.rb +159 -102
- data/lib/active_record/dynamic_matchers.rb +140 -0
- data/lib/active_record/enum.rb +197 -0
- data/lib/active_record/errors.rb +102 -34
- data/lib/active_record/explain.rb +38 -0
- data/lib/active_record/explain_registry.rb +30 -0
- data/lib/active_record/explain_subscriber.rb +29 -0
- data/lib/active_record/fixture_set/file.rb +56 -0
- data/lib/active_record/fixtures.rb +318 -260
- data/lib/active_record/gem_version.rb +15 -0
- data/lib/active_record/inheritance.rb +247 -0
- data/lib/active_record/integration.rb +113 -0
- 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 +80 -52
- data/lib/active_record/locking/pessimistic.rb +27 -5
- data/lib/active_record/log_subscriber.rb +25 -18
- data/lib/active_record/migration/command_recorder.rb +130 -38
- data/lib/active_record/migration/join_table.rb +15 -0
- data/lib/active_record/migration.rb +532 -201
- data/lib/active_record/model_schema.rb +342 -0
- data/lib/active_record/nested_attributes.rb +229 -139
- data/lib/active_record/no_touching.rb +52 -0
- data/lib/active_record/null_relation.rb +81 -0
- data/lib/active_record/persistence.rb +304 -99
- data/lib/active_record/query_cache.rb +25 -43
- data/lib/active_record/querying.rb +68 -0
- data/lib/active_record/railtie.rb +86 -45
- data/lib/active_record/railties/console_sandbox.rb +3 -4
- data/lib/active_record/railties/controller_runtime.rb +7 -4
- data/lib/active_record/railties/databases.rake +198 -377
- data/lib/active_record/railties/jdbcmysql_error.rb +2 -2
- data/lib/active_record/readonly_attributes.rb +23 -0
- data/lib/active_record/reflection.rb +516 -165
- data/lib/active_record/relation/batches.rb +96 -45
- data/lib/active_record/relation/calculations.rb +221 -144
- data/lib/active_record/relation/delegation.rb +140 -0
- data/lib/active_record/relation/finder_methods.rb +362 -243
- 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 -41
- data/lib/active_record/relation/query_methods.rb +982 -155
- data/lib/active_record/relation/spawn_methods.rb +50 -110
- data/lib/active_record/relation.rb +371 -180
- data/lib/active_record/result.rb +109 -12
- data/lib/active_record/runtime_registry.rb +22 -0
- data/lib/active_record/sanitization.rb +191 -0
- data/lib/active_record/schema.rb +19 -13
- data/lib/active_record/schema_dumper.rb +111 -61
- data/lib/active_record/schema_migration.rb +53 -0
- data/lib/active_record/scoping/default.rb +135 -0
- data/lib/active_record/scoping/named.rb +164 -0
- data/lib/active_record/scoping.rb +87 -0
- data/lib/active_record/serialization.rb +7 -45
- data/lib/active_record/serializers/xml_serializer.rb +14 -65
- data/lib/active_record/statement_cache.rb +111 -0
- data/lib/active_record/store.rb +205 -0
- 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 +35 -14
- data/lib/active_record/transactions.rb +141 -74
- data/lib/active_record/translation.rb +22 -0
- 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 +27 -18
- data/lib/active_record/validations/presence.rb +67 -0
- data/lib/active_record/validations/uniqueness.rb +125 -66
- data/lib/active_record/validations.rb +37 -30
- data/lib/active_record/version.rb +5 -7
- data/lib/active_record.rb +80 -25
- data/lib/rails/generators/active_record/migration/migration_generator.rb +54 -9
- data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb +19 -0
- data/lib/rails/generators/active_record/migration/templates/migration.rb +25 -11
- data/lib/rails/generators/active_record/migration.rb +11 -8
- data/lib/rails/generators/active_record/model/model_generator.rb +17 -4
- data/lib/rails/generators/active_record/model/templates/model.rb +5 -2
- data/lib/rails/generators/active_record/model/templates/module.rb +1 -1
- data/lib/rails/generators/active_record.rb +3 -11
- metadata +132 -53
- data/examples/associations.png +0 -0
- data/lib/active_record/associations/has_and_belongs_to_many_association.rb +0 -62
- 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/connection_adapters/abstract/connection_specification.rb +0 -135
- data/lib/active_record/connection_adapters/sqlite_adapter.rb +0 -556
- data/lib/active_record/dynamic_finder_match.rb +0 -56
- data/lib/active_record/dynamic_scope_match.rb +0 -23
- data/lib/active_record/identity_map.rb +0 -163
- data/lib/active_record/named_scope.rb +0 -200
- data/lib/active_record/observer.rb +0 -121
- data/lib/active_record/session_store.rb +0 -358
- data/lib/active_record/test_case.rb +0 -69
- data/lib/rails/generators/active_record/model/templates/migration.rb +0 -17
- 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 -16
@@ -6,9 +6,34 @@ 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)
|
36
|
+
set_inverse_instance(record)
|
12
37
|
|
13
38
|
if raise
|
14
39
|
record.save!(:validate => validate)
|
@@ -17,13 +42,21 @@ module ActiveRecord
|
|
17
42
|
end
|
18
43
|
end
|
19
44
|
|
45
|
+
def empty?
|
46
|
+
if has_cached_counter?
|
47
|
+
size.zero?
|
48
|
+
else
|
49
|
+
super
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
20
53
|
private
|
21
54
|
|
22
55
|
# Returns the number of records in this collection.
|
23
56
|
#
|
24
57
|
# If the association has a counter cache it gets that value. Otherwise
|
25
58
|
# it will attempt to do a count via SQL, bounded to <tt>:limit</tt> if
|
26
|
-
# there's one.
|
59
|
+
# there's one. Some configuration options like :group make it impossible
|
27
60
|
# to do an SQL count, in those cases the array count will be used.
|
28
61
|
#
|
29
62
|
# That does not depend on whether the collection has already been loaded
|
@@ -34,11 +67,9 @@ module ActiveRecord
|
|
34
67
|
# the loaded flag is set to true as well.
|
35
68
|
def count_records
|
36
69
|
count = if has_cached_counter?
|
37
|
-
owner.
|
38
|
-
elsif options[:counter_sql] || options[:finder_sql]
|
39
|
-
reflection.klass.count_by_sql(custom_counter_sql)
|
70
|
+
owner._read_attribute cached_counter_attribute_name
|
40
71
|
else
|
41
|
-
|
72
|
+
scope.count
|
42
73
|
end
|
43
74
|
|
44
75
|
# If there's nothing in the database and @target has no new records
|
@@ -46,23 +77,45 @@ module ActiveRecord
|
|
46
77
|
# documented side-effect of the method that may avoid an extra SELECT.
|
47
78
|
@target ||= [] and loaded! if count == 0
|
48
79
|
|
49
|
-
[
|
80
|
+
[association_scope.limit_value, count].compact.min
|
50
81
|
end
|
51
82
|
|
52
|
-
|
53
|
-
|
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
|
54
92
|
end
|
55
93
|
|
56
|
-
def cached_counter_attribute_name(reflection = reflection)
|
57
|
-
|
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
|
58
100
|
end
|
59
101
|
|
60
|
-
def update_counter(difference, reflection = reflection)
|
102
|
+
def update_counter(difference, reflection = reflection())
|
103
|
+
update_counter_in_database(difference, reflection)
|
104
|
+
update_counter_in_memory(difference, reflection)
|
105
|
+
end
|
106
|
+
|
107
|
+
def update_counter_in_database(difference, reflection = reflection())
|
61
108
|
if has_cached_counter?(reflection)
|
62
109
|
counter = cached_counter_attribute_name(reflection)
|
63
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)
|
64
117
|
owner[counter] += difference
|
65
|
-
owner.
|
118
|
+
owner.send(:clear_attribute_changes, counter) # eww
|
66
119
|
end
|
67
120
|
end
|
68
121
|
|
@@ -76,28 +129,70 @@ module ActiveRecord
|
|
76
129
|
# it will be decremented twice.
|
77
130
|
#
|
78
131
|
# Hence this method.
|
79
|
-
def
|
132
|
+
def inverse_which_updates_counter_cache(reflection = reflection())
|
80
133
|
counter_name = cached_counter_attribute_name(reflection)
|
81
|
-
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? &&
|
82
141
|
inverse_reflection.counter_cache_column == counter_name
|
83
142
|
}
|
84
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
|
85
167
|
|
86
168
|
# Deletes the records according to the <tt>:dependent</tt> option.
|
87
169
|
def delete_records(records, method)
|
88
170
|
if method == :destroy
|
89
|
-
records.each
|
171
|
+
records.each(&:destroy!)
|
90
172
|
update_counter(-records.length) unless inverse_updates_counter_cache?
|
91
173
|
else
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
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
|
182
|
+
|
183
|
+
def _create_record(attributes, *)
|
184
|
+
if attributes.is_a?(Array)
|
185
|
+
super
|
186
|
+
else
|
187
|
+
update_counter_if_success(super, 1)
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
def update_counter_if_success(saved_successfully, difference)
|
192
|
+
if saved_successfully
|
193
|
+
update_counter_in_memory(difference)
|
100
194
|
end
|
195
|
+
saved_successfully
|
101
196
|
end
|
102
197
|
end
|
103
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
|
@@ -8,34 +8,50 @@ module ActiveRecord
|
|
8
8
|
|
9
9
|
def initialize(owner, reflection)
|
10
10
|
super
|
11
|
-
|
11
|
+
|
12
|
+
@through_records = {}
|
13
|
+
@through_association = nil
|
12
14
|
end
|
13
15
|
|
14
|
-
# Returns the size of the collection by executing a SELECT COUNT(*) query
|
15
|
-
#
|
16
|
-
#
|
17
|
-
#
|
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.
|
18
21
|
def size
|
19
22
|
if has_cached_counter?
|
20
|
-
owner.
|
23
|
+
owner._read_attribute cached_counter_attribute_name(reflection)
|
21
24
|
elsif loaded?
|
22
25
|
target.size
|
23
26
|
else
|
24
|
-
|
27
|
+
super
|
25
28
|
end
|
26
29
|
end
|
27
30
|
|
28
31
|
def concat(*records)
|
29
32
|
unless owner.new_record?
|
30
33
|
records.flatten.each do |record|
|
31
|
-
raise_on_type_mismatch(record)
|
32
|
-
record.save! if record.new_record?
|
34
|
+
raise_on_type_mismatch!(record)
|
33
35
|
end
|
34
36
|
end
|
35
37
|
|
36
38
|
super
|
37
39
|
end
|
38
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
|
+
|
39
55
|
def insert_record(record, validate = true, raise = false)
|
40
56
|
ensure_not_nested
|
41
57
|
|
@@ -48,51 +64,71 @@ module ActiveRecord
|
|
48
64
|
end
|
49
65
|
|
50
66
|
save_through_record(record)
|
51
|
-
|
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
|
52
77
|
record
|
53
78
|
end
|
54
79
|
|
55
|
-
# ActiveRecord::Relation#delete_all needs to support joins before we can use a
|
56
|
-
# SQL-only implementation.
|
57
|
-
alias delete_all_on_destroy delete_all
|
58
|
-
|
59
80
|
private
|
60
81
|
|
61
82
|
def through_association
|
62
|
-
owner.association(through_reflection.name)
|
83
|
+
@through_association ||= owner.association(through_reflection.name)
|
63
84
|
end
|
64
85
|
|
65
|
-
#
|
66
|
-
#
|
67
|
-
# 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.
|
68
88
|
#
|
69
|
-
# However, after insert_record has been called,
|
70
|
-
#
|
71
|
-
# 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.
|
72
91
|
def build_through_record(record)
|
73
92
|
@through_records[record.object_id] ||= begin
|
74
|
-
|
93
|
+
ensure_mutable
|
94
|
+
|
95
|
+
through_record = through_association.build(*options_for_through_record)
|
75
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
|
+
|
76
102
|
through_record
|
77
103
|
end
|
78
104
|
end
|
79
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
|
+
|
80
116
|
def save_through_record(record)
|
81
117
|
build_through_record(record).save!
|
82
118
|
ensure
|
83
119
|
@through_records.delete(record.object_id)
|
84
120
|
end
|
85
121
|
|
86
|
-
def build_record(attributes
|
122
|
+
def build_record(attributes)
|
87
123
|
ensure_not_nested
|
88
124
|
|
89
|
-
record = super(attributes
|
125
|
+
record = super(attributes)
|
90
126
|
|
91
127
|
inverse = source_reflection.inverse_of
|
92
128
|
if inverse
|
93
|
-
if inverse.
|
129
|
+
if inverse.collection?
|
94
130
|
record.send(inverse.name) << build_through_record(record)
|
95
|
-
elsif inverse.
|
131
|
+
elsif inverse.has_one?
|
96
132
|
record.send("#{inverse.name}=", build_through_record(record))
|
97
133
|
end
|
98
134
|
end
|
@@ -101,11 +137,7 @@ module ActiveRecord
|
|
101
137
|
end
|
102
138
|
|
103
139
|
def target_reflection_has_associated_record?
|
104
|
-
|
105
|
-
false
|
106
|
-
else
|
107
|
-
true
|
108
|
-
end
|
140
|
+
!(through_reflection.belongs_to? && owner[through_reflection.foreign_key].blank?)
|
109
141
|
end
|
110
142
|
|
111
143
|
def update_through_counter?(method)
|
@@ -119,44 +151,73 @@ module ActiveRecord
|
|
119
151
|
end
|
120
152
|
end
|
121
153
|
|
154
|
+
def delete_or_nullify_all_records(method)
|
155
|
+
delete_records(load_target, method)
|
156
|
+
end
|
157
|
+
|
122
158
|
def delete_records(records, method)
|
123
159
|
ensure_not_nested
|
124
160
|
|
125
|
-
|
126
|
-
scope
|
161
|
+
scope = through_association.scope
|
162
|
+
scope.where! construct_join_attributes(*records)
|
127
163
|
|
128
164
|
case method
|
129
165
|
when :destroy
|
130
|
-
|
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
|
131
181
|
when :nullify
|
132
182
|
count = scope.update_all(source_reflection.foreign_key => nil)
|
133
183
|
else
|
134
184
|
count = scope.delete_all
|
135
185
|
end
|
136
186
|
|
137
|
-
delete_through_records(
|
187
|
+
delete_through_records(records)
|
138
188
|
|
139
|
-
if
|
140
|
-
|
189
|
+
if source_reflection.options[:counter_cache] && method != :destroy
|
190
|
+
counter = source_reflection.counter_cache_column
|
191
|
+
klass.decrement_counter counter, records.map(&:id)
|
141
192
|
end
|
142
193
|
|
143
|
-
|
194
|
+
if through_reflection.collection? && update_through_counter?(method)
|
195
|
+
update_counter(-count, through_reflection)
|
196
|
+
else
|
197
|
+
update_counter(-count)
|
198
|
+
end
|
144
199
|
end
|
145
200
|
|
146
201
|
def through_records_for(record)
|
147
202
|
attributes = construct_join_attributes(record)
|
148
203
|
candidates = Array.wrap(through_association.target)
|
149
|
-
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
|
150
209
|
end
|
151
210
|
|
152
|
-
def delete_through_records(
|
211
|
+
def delete_through_records(records)
|
153
212
|
records.each do |record|
|
154
213
|
through_records = through_records_for(record)
|
155
214
|
|
156
|
-
if through_reflection.
|
157
|
-
through_records.each { |r|
|
215
|
+
if through_reflection.collection?
|
216
|
+
through_records.each { |r| through_association.target.delete(r) }
|
158
217
|
else
|
159
|
-
|
218
|
+
if through_records.include?(through_association.target)
|
219
|
+
through_association.target = nil
|
220
|
+
end
|
160
221
|
end
|
161
222
|
|
162
223
|
@through_records.delete(record.object_id)
|
@@ -165,13 +226,22 @@ module ActiveRecord
|
|
165
226
|
|
166
227
|
def find_target
|
167
228
|
return [] unless target_reflection_has_associated_record?
|
168
|
-
|
229
|
+
get_records
|
169
230
|
end
|
170
231
|
|
171
232
|
# NOTE - not sure that we can actually cope with inverses here
|
172
233
|
def invertible_for?(record)
|
173
234
|
false
|
174
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
|
175
245
|
end
|
176
246
|
end
|
177
247
|
end
|
@@ -1,26 +1,48 @@
|
|
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
|
-
|
14
|
-
|
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
|
+
|
34
|
+
transaction_if(save) do
|
35
|
+
remove_target!(options[:dependent]) if target && !target.destroyed? && assigning_another_record
|
15
36
|
|
16
|
-
|
17
|
-
|
18
|
-
|
37
|
+
if record
|
38
|
+
set_owner_attributes(record)
|
39
|
+
set_inverse_instance(record)
|
19
40
|
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
41
|
+
if save && !record.save
|
42
|
+
nullify_owner_attributes(record)
|
43
|
+
set_owner_attributes(target) if target
|
44
|
+
raise RecordNotSaved, "Failed to save the new associated #{reflection.name}."
|
45
|
+
end
|
24
46
|
end
|
25
47
|
end
|
26
48
|
end
|
@@ -36,7 +58,7 @@ module ActiveRecord
|
|
36
58
|
when :destroy
|
37
59
|
target.destroy
|
38
60
|
when :nullify
|
39
|
-
target.
|
61
|
+
target.update_columns(reflection.foreign_key => nil)
|
40
62
|
end
|
41
63
|
end
|
42
64
|
end
|
@@ -52,22 +74,33 @@ module ActiveRecord
|
|
52
74
|
end
|
53
75
|
|
54
76
|
def remove_target!(method)
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
77
|
+
case method
|
78
|
+
when :delete
|
79
|
+
target.delete
|
80
|
+
when :destroy
|
81
|
+
target.destroy
|
82
|
+
else
|
83
|
+
nullify_owner_attributes(target)
|
59
84
|
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
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
|
65
90
|
end
|
66
91
|
end
|
67
92
|
|
68
93
|
def nullify_owner_attributes(record)
|
69
94
|
record[reflection.foreign_key] = nil
|
70
95
|
end
|
96
|
+
|
97
|
+
def transaction_if(value)
|
98
|
+
if value
|
99
|
+
reflection.klass.transaction { yield }
|
100
|
+
else
|
101
|
+
yield
|
102
|
+
end
|
103
|
+
end
|
71
104
|
end
|
72
105
|
end
|
73
106
|
end
|