activerecord 3.2.22.5 → 4.2.11.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 +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
@@ -1,12 +1,18 @@
|
|
1
|
-
require 'active_support/core_ext/array/wrap'
|
2
|
-
|
3
1
|
module ActiveRecord
|
4
2
|
module Associations
|
5
3
|
# = Active Record Association Collection
|
6
4
|
#
|
7
5
|
# CollectionAssociation is an abstract class that provides common stuff to
|
8
6
|
# ease the implementation of association proxies that represent
|
9
|
-
# collections. See the class hierarchy in
|
7
|
+
# collections. See the class hierarchy in Association.
|
8
|
+
#
|
9
|
+
# CollectionAssociation:
|
10
|
+
# HasManyAssociation => has_many
|
11
|
+
# HasManyThroughAssociation + ThroughAssociation => has_many :through
|
12
|
+
#
|
13
|
+
# CollectionAssociation class provides common methods to the collections
|
14
|
+
# defined by +has_and_belongs_to_many+, +has_many+ or +has_many+ with
|
15
|
+
# +:through association+ option.
|
10
16
|
#
|
11
17
|
# You need to be careful with assumptions regarding the target: The proxy
|
12
18
|
# does not fetch records from the database until it needs them, but new
|
@@ -18,12 +24,6 @@ module ActiveRecord
|
|
18
24
|
# If you need to work on all current children, new and existing records,
|
19
25
|
# +load_target+ and the +loaded+ flag are your friends.
|
20
26
|
class CollectionAssociation < Association #:nodoc:
|
21
|
-
attr_reader :proxy
|
22
|
-
|
23
|
-
def initialize(owner, reflection)
|
24
|
-
super
|
25
|
-
@proxy = CollectionProxy.new(self)
|
26
|
-
end
|
27
27
|
|
28
28
|
# Implements the reader method, e.g. foo.items for Foo.has_many :items
|
29
29
|
def reader(force_reload = false)
|
@@ -33,7 +33,13 @@ module ActiveRecord
|
|
33
33
|
reload
|
34
34
|
end
|
35
35
|
|
36
|
-
|
36
|
+
if owner.new_record?
|
37
|
+
# Cache the proxy separately before the owner has an id
|
38
|
+
# or else a post-save proxy will still lack the id
|
39
|
+
@new_record_proxy ||= CollectionProxy.create(klass, self)
|
40
|
+
else
|
41
|
+
@proxy ||= CollectionProxy.create(klass, self)
|
42
|
+
end
|
37
43
|
end
|
38
44
|
|
39
45
|
# Implements the writer method, e.g. foo.items= for Foo.has_many :items
|
@@ -43,45 +49,45 @@ module ActiveRecord
|
|
43
49
|
|
44
50
|
# Implements the ids reader method, e.g. foo.item_ids for Foo.has_many :items
|
45
51
|
def ids_reader
|
46
|
-
if
|
52
|
+
if loaded?
|
47
53
|
load_target.map do |record|
|
48
54
|
record.send(reflection.association_primary_key)
|
49
55
|
end
|
50
56
|
else
|
51
57
|
column = "#{reflection.quoted_table_name}.#{reflection.association_primary_key}"
|
52
|
-
|
53
|
-
|
54
|
-
including = (relation.eager_load_values + relation.includes_values).uniq
|
55
|
-
|
56
|
-
if including.any?
|
57
|
-
join_dependency = ActiveRecord::Associations::JoinDependency.new(reflection.klass, including, [])
|
58
|
-
relation = join_dependency.join_associations.inject(relation) do |r, association|
|
59
|
-
association.join_relation(r)
|
60
|
-
end
|
61
|
-
end
|
62
|
-
|
63
|
-
relation.pluck(column)
|
58
|
+
scope.pluck(column)
|
64
59
|
end
|
65
60
|
end
|
66
61
|
|
67
62
|
# Implements the ids writer method, e.g. foo.item_ids= for Foo.has_many :items
|
68
63
|
def ids_writer(ids)
|
69
|
-
pk_column = reflection.
|
70
|
-
|
71
|
-
ids.map
|
72
|
-
|
64
|
+
pk_column = reflection.association_primary_key
|
65
|
+
pk_type = klass.type_for_attribute(pk_column)
|
66
|
+
ids = Array(ids).reject(&:blank?).map do |i|
|
67
|
+
pk_type.type_cast_from_user(i)
|
68
|
+
end
|
69
|
+
|
70
|
+
objs = klass.where(pk_column => ids).index_by do |r|
|
71
|
+
r.send(pk_column)
|
72
|
+
end.values_at(*ids).compact
|
73
|
+
|
74
|
+
if objs.size == ids.size
|
75
|
+
replace(objs.index_by { |r| r.send(pk_column) }.values_at(*ids))
|
76
|
+
else
|
77
|
+
klass.all.raise_record_not_found_exception!(ids, objs.size, ids.size)
|
78
|
+
end
|
73
79
|
end
|
74
80
|
|
75
81
|
def reset
|
76
|
-
|
82
|
+
super
|
77
83
|
@target = []
|
78
84
|
end
|
79
85
|
|
80
|
-
def select(
|
86
|
+
def select(*fields)
|
81
87
|
if block_given?
|
82
88
|
load_target.select.each { |e| yield e }
|
83
89
|
else
|
84
|
-
|
90
|
+
scope.select(*fields)
|
85
91
|
end
|
86
92
|
end
|
87
93
|
|
@@ -89,46 +95,85 @@ module ActiveRecord
|
|
89
95
|
if block_given?
|
90
96
|
load_target.find(*args) { |*block_args| yield(*block_args) }
|
91
97
|
else
|
92
|
-
if options[:
|
93
|
-
|
98
|
+
if options[:inverse_of] && loaded?
|
99
|
+
args_flatten = args.flatten
|
100
|
+
raise RecordNotFound, "Couldn't find #{scope.klass.name} without an ID" if args_flatten.blank?
|
101
|
+
result = find_by_scan(*args)
|
102
|
+
|
103
|
+
result_size = Array(result).size
|
104
|
+
if !result || result_size != args_flatten.size
|
105
|
+
scope.raise_record_not_found_exception!(args_flatten, result_size, args_flatten.size)
|
106
|
+
else
|
107
|
+
result
|
108
|
+
end
|
94
109
|
else
|
95
|
-
|
110
|
+
scope.find(*args)
|
96
111
|
end
|
97
112
|
end
|
98
113
|
end
|
99
114
|
|
100
115
|
def first(*args)
|
101
|
-
|
116
|
+
first_nth_or_last(:first, *args)
|
117
|
+
end
|
118
|
+
|
119
|
+
def second(*args)
|
120
|
+
first_nth_or_last(:second, *args)
|
121
|
+
end
|
122
|
+
|
123
|
+
def third(*args)
|
124
|
+
first_nth_or_last(:third, *args)
|
125
|
+
end
|
126
|
+
|
127
|
+
def fourth(*args)
|
128
|
+
first_nth_or_last(:fourth, *args)
|
129
|
+
end
|
130
|
+
|
131
|
+
def fifth(*args)
|
132
|
+
first_nth_or_last(:fifth, *args)
|
133
|
+
end
|
134
|
+
|
135
|
+
def forty_two(*args)
|
136
|
+
first_nth_or_last(:forty_two, *args)
|
102
137
|
end
|
103
138
|
|
104
139
|
def last(*args)
|
105
|
-
|
140
|
+
first_nth_or_last(:last, *args)
|
141
|
+
end
|
142
|
+
|
143
|
+
def take(n = nil)
|
144
|
+
if loaded?
|
145
|
+
n ? target.take(n) : target.first
|
146
|
+
else
|
147
|
+
scope.take(n).tap do |record|
|
148
|
+
set_inverse_instance record if record.is_a? ActiveRecord::Base
|
149
|
+
end
|
150
|
+
end
|
106
151
|
end
|
107
152
|
|
108
|
-
def build(attributes = {},
|
153
|
+
def build(attributes = {}, &block)
|
109
154
|
if attributes.is_a?(Array)
|
110
|
-
attributes.collect { |attr| build(attr,
|
155
|
+
attributes.collect { |attr| build(attr, &block) }
|
111
156
|
else
|
112
|
-
add_to_target(build_record(attributes
|
157
|
+
add_to_target(build_record(attributes)) do |record|
|
113
158
|
yield(record) if block_given?
|
114
159
|
end
|
115
160
|
end
|
116
161
|
end
|
117
162
|
|
118
|
-
def create(attributes = {},
|
119
|
-
|
163
|
+
def create(attributes = {}, &block)
|
164
|
+
_create_record(attributes, &block)
|
120
165
|
end
|
121
166
|
|
122
|
-
def create!(attributes = {},
|
123
|
-
|
167
|
+
def create!(attributes = {}, &block)
|
168
|
+
_create_record(attributes, true, &block)
|
124
169
|
end
|
125
170
|
|
126
|
-
# Add +records+ to this association. Returns +self+ so method calls may
|
127
|
-
# Since << flattens its argument list and inserts each record,
|
171
|
+
# Add +records+ to this association. Returns +self+ so method calls may
|
172
|
+
# be chained. Since << flattens its argument list and inserts each record,
|
173
|
+
# +push+ and +concat+ behave identically.
|
128
174
|
def concat(*records)
|
129
|
-
load_target if owner.new_record?
|
130
|
-
|
131
175
|
if owner.new_record?
|
176
|
+
load_target
|
132
177
|
concat_records(records)
|
133
178
|
else
|
134
179
|
transaction { concat_records(records) }
|
@@ -150,23 +195,38 @@ module ActiveRecord
|
|
150
195
|
end
|
151
196
|
end
|
152
197
|
|
153
|
-
#
|
198
|
+
# Removes all records from the association without calling callbacks
|
199
|
+
# on the associated records. It honors the +:dependent+ option. However
|
200
|
+
# if the +:dependent+ value is +:destroy+ then in that case the +:delete_all+
|
201
|
+
# deletion strategy for the association is applied.
|
202
|
+
#
|
203
|
+
# You can force a particular deletion strategy by passing a parameter.
|
204
|
+
#
|
205
|
+
# Example:
|
206
|
+
#
|
207
|
+
# @author.books.delete_all(:nullify)
|
208
|
+
# @author.books.delete_all(:delete_all)
|
154
209
|
#
|
155
210
|
# See delete for more info.
|
156
|
-
def delete_all
|
157
|
-
|
211
|
+
def delete_all(dependent = nil)
|
212
|
+
if dependent && ![:nullify, :delete_all].include?(dependent)
|
213
|
+
raise ArgumentError, "Valid values are :nullify or :delete_all"
|
214
|
+
end
|
215
|
+
|
216
|
+
dependent = if dependent
|
217
|
+
dependent
|
218
|
+
elsif options[:dependent] == :destroy
|
219
|
+
:delete_all
|
220
|
+
else
|
221
|
+
options[:dependent]
|
222
|
+
end
|
223
|
+
|
224
|
+
delete_or_nullify_all_records(dependent).tap do
|
158
225
|
reset
|
159
226
|
loaded!
|
160
227
|
end
|
161
228
|
end
|
162
229
|
|
163
|
-
# Called when the association is declared as :dependent => :delete_all. This is
|
164
|
-
# an optimised version which avoids loading the records into memory. Not really
|
165
|
-
# for public consumption.
|
166
|
-
def delete_all_on_destroy
|
167
|
-
scoped.delete_all
|
168
|
-
end
|
169
|
-
|
170
230
|
# Destroy all the records from this association.
|
171
231
|
#
|
172
232
|
# See destroy for more info.
|
@@ -177,46 +237,29 @@ module ActiveRecord
|
|
177
237
|
end
|
178
238
|
end
|
179
239
|
|
180
|
-
#
|
181
|
-
def sum(*args)
|
182
|
-
if block_given?
|
183
|
-
scoped.sum(*args) { |*block_args| yield(*block_args) }
|
184
|
-
else
|
185
|
-
scoped.sum(*args)
|
186
|
-
end
|
187
|
-
end
|
188
|
-
|
189
|
-
# Count all records using SQL. If the +:counter_sql+ or +:finder_sql+ option is set for the
|
190
|
-
# association, it will be used for the query. Otherwise, construct options and pass them with
|
240
|
+
# Count all records using SQL. Construct options and pass them with
|
191
241
|
# scope to the target class's +count+.
|
192
242
|
def count(column_name = nil, count_options = {})
|
193
|
-
|
194
|
-
|
243
|
+
# TODO: Remove count_options argument as soon we remove support to
|
244
|
+
# activerecord-deprecated_finders.
|
195
245
|
column_name, count_options = nil, column_name if column_name.is_a?(Hash)
|
196
246
|
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
else
|
204
|
-
if options[:uniq]
|
205
|
-
# This is needed because 'SELECT count(DISTINCT *)..' is not valid SQL.
|
206
|
-
column_name ||= reflection.klass.primary_key
|
207
|
-
count_options.merge!(:distinct => true)
|
208
|
-
end
|
247
|
+
relation = scope
|
248
|
+
if association_scope.distinct_value
|
249
|
+
# This is needed because 'SELECT count(DISTINCT *)..' is not valid SQL.
|
250
|
+
column_name ||= reflection.klass.primary_key
|
251
|
+
relation = relation.distinct
|
252
|
+
end
|
209
253
|
|
210
|
-
|
254
|
+
value = relation.count(column_name)
|
211
255
|
|
212
|
-
|
213
|
-
|
256
|
+
limit = options[:limit]
|
257
|
+
offset = options[:offset]
|
214
258
|
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
end
|
259
|
+
if limit || offset
|
260
|
+
[ [value - offset.to_i, 0].max, limit.to_i ].min
|
261
|
+
else
|
262
|
+
value
|
220
263
|
end
|
221
264
|
end
|
222
265
|
|
@@ -228,16 +271,22 @@ module ActiveRecord
|
|
228
271
|
# are actually removed from the database, that depends precisely on
|
229
272
|
# +delete_records+. They are in any case removed from the collection.
|
230
273
|
def delete(*records)
|
231
|
-
|
274
|
+
return if records.empty?
|
275
|
+
_options = records.extract_options!
|
276
|
+
dependent = _options[:dependent] || options[:dependent]
|
277
|
+
|
278
|
+
records = find(records) if records.any? { |record| record.kind_of?(Integer) || record.kind_of?(String) }
|
279
|
+
delete_or_destroy(records, dependent)
|
232
280
|
end
|
233
281
|
|
234
|
-
#
|
235
|
-
# +before_remove+
|
282
|
+
# Deletes the +records+ and removes them from this association calling
|
283
|
+
# +before_remove+ , +after_remove+ , +before_destroy+ and +after_destroy+ callbacks.
|
236
284
|
#
|
237
|
-
# Note that this method
|
238
|
-
#
|
285
|
+
# Note that this method removes records from the database ignoring the
|
286
|
+
# +:dependent+ option.
|
239
287
|
def destroy(*records)
|
240
|
-
|
288
|
+
return if records.empty?
|
289
|
+
records = find(records) if records.any? { |record| record.kind_of?(Integer) || record.kind_of?(String) }
|
241
290
|
delete_or_destroy(records, :destroy)
|
242
291
|
end
|
243
292
|
|
@@ -252,11 +301,15 @@ module ActiveRecord
|
|
252
301
|
# This method is abstract in the sense that it relies on
|
253
302
|
# +count_records+, which is a method descendants have to provide.
|
254
303
|
def size
|
255
|
-
if !find_target? ||
|
256
|
-
|
257
|
-
|
304
|
+
if !find_target? || loaded?
|
305
|
+
if association_scope.distinct_value
|
306
|
+
target.uniq.size
|
307
|
+
else
|
308
|
+
target.size
|
309
|
+
end
|
310
|
+
elsif !loaded? && !association_scope.group_values.empty?
|
258
311
|
load_target.size
|
259
|
-
elsif !loaded? && !
|
312
|
+
elsif !loaded? && !association_scope.distinct_value && target.is_a?(Array)
|
260
313
|
unsaved_records = target.select { |r| r.new_record? }
|
261
314
|
unsaved_records.size + count_records
|
262
315
|
else
|
@@ -273,13 +326,24 @@ module ActiveRecord
|
|
273
326
|
load_target.size
|
274
327
|
end
|
275
328
|
|
276
|
-
#
|
277
|
-
#
|
278
|
-
#
|
329
|
+
# Returns true if the collection is empty.
|
330
|
+
#
|
331
|
+
# If the collection has been loaded
|
332
|
+
# it is equivalent to <tt>collection.size.zero?</tt>. If the
|
333
|
+
# collection has not been loaded, it is equivalent to
|
334
|
+
# <tt>collection.exists?</tt>. If the collection has not already been
|
335
|
+
# loaded and you are going to fetch the records anyway it is better to
|
336
|
+
# check <tt>collection.length.zero?</tt>.
|
279
337
|
def empty?
|
280
|
-
|
338
|
+
if loaded?
|
339
|
+
size.zero?
|
340
|
+
else
|
341
|
+
@target.blank? && !scope.exists?
|
342
|
+
end
|
281
343
|
end
|
282
344
|
|
345
|
+
# Returns true if the collections is not empty.
|
346
|
+
# Equivalent to +!collection.empty?+.
|
283
347
|
def any?
|
284
348
|
if block_given?
|
285
349
|
load_target.any? { |*block_args| yield(*block_args) }
|
@@ -288,7 +352,8 @@ module ActiveRecord
|
|
288
352
|
end
|
289
353
|
end
|
290
354
|
|
291
|
-
# Returns true if the collection has more than 1 record.
|
355
|
+
# Returns true if the collection has more than 1 record.
|
356
|
+
# Equivalent to +collection.size > 1+.
|
292
357
|
def many?
|
293
358
|
if block_given?
|
294
359
|
load_target.many? { |*block_args| yield(*block_args) }
|
@@ -297,23 +362,27 @@ module ActiveRecord
|
|
297
362
|
end
|
298
363
|
end
|
299
364
|
|
300
|
-
def
|
365
|
+
def distinct
|
301
366
|
seen = {}
|
302
|
-
|
367
|
+
load_target.find_all do |record|
|
303
368
|
seen[record.id] = true unless seen.key?(record.id)
|
304
369
|
end
|
305
370
|
end
|
371
|
+
alias uniq distinct
|
306
372
|
|
307
|
-
# Replace this collection with +other_array
|
308
|
-
#
|
373
|
+
# Replace this collection with +other_array+. This will perform a diff
|
374
|
+
# and delete/add only records that have changed.
|
309
375
|
def replace(other_array)
|
310
|
-
other_array.each { |val| raise_on_type_mismatch(val) }
|
376
|
+
other_array.each { |val| raise_on_type_mismatch!(val) }
|
311
377
|
original_target = load_target.dup
|
312
378
|
|
313
379
|
if owner.new_record?
|
314
380
|
replace_records(other_array, original_target)
|
315
381
|
else
|
316
|
-
|
382
|
+
replace_common_records_in_memory(other_array, original_target)
|
383
|
+
if other_array != original_target
|
384
|
+
transaction { replace_records(other_array, original_target) }
|
385
|
+
end
|
317
386
|
end
|
318
387
|
end
|
319
388
|
|
@@ -322,8 +391,7 @@ module ActiveRecord
|
|
322
391
|
if record.new_record?
|
323
392
|
include_in_memory?(record)
|
324
393
|
else
|
325
|
-
|
326
|
-
loaded? ? target.include?(record) : scoped.exists?(record)
|
394
|
+
loaded? ? target.include?(record) : scope.exists?(record.id)
|
327
395
|
end
|
328
396
|
else
|
329
397
|
false
|
@@ -339,50 +407,57 @@ module ActiveRecord
|
|
339
407
|
target
|
340
408
|
end
|
341
409
|
|
342
|
-
def add_to_target(record)
|
343
|
-
|
410
|
+
def add_to_target(record, skip_callbacks = false, &block)
|
411
|
+
if association_scope.distinct_value
|
412
|
+
index = @target.index(record)
|
413
|
+
end
|
414
|
+
replace_on_target(record, index, skip_callbacks, &block)
|
415
|
+
end
|
416
|
+
|
417
|
+
def replace_on_target(record, index, skip_callbacks)
|
418
|
+
callback(:before_add, record) unless skip_callbacks
|
344
419
|
yield(record) if block_given?
|
345
420
|
|
346
|
-
if
|
421
|
+
if index
|
347
422
|
@target[index] = record
|
348
423
|
else
|
349
424
|
@target << record
|
350
425
|
end
|
351
426
|
|
352
|
-
callback(:after_add, record)
|
427
|
+
callback(:after_add, record) unless skip_callbacks
|
353
428
|
set_inverse_instance(record)
|
354
429
|
|
355
430
|
record
|
356
431
|
end
|
357
432
|
|
433
|
+
def scope(opts = {})
|
434
|
+
scope = super()
|
435
|
+
scope.none! if opts.fetch(:nullify, true) && null_scope?
|
436
|
+
scope
|
437
|
+
end
|
438
|
+
|
439
|
+
def null_scope?
|
440
|
+
owner.new_record? && !foreign_key_present?
|
441
|
+
end
|
442
|
+
|
358
443
|
private
|
444
|
+
def get_records
|
445
|
+
return scope.to_a if skip_statement_cache?
|
359
446
|
|
360
|
-
|
361
|
-
|
362
|
-
|
363
|
-
|
364
|
-
|
365
|
-
|
366
|
-
count_with = $2.to_s
|
367
|
-
count_with = '*' if count_with.blank? || count_with =~ /,/ || count_with =~ /\.\*/
|
368
|
-
"SELECT #{$1}COUNT(#{count_with}) FROM"
|
369
|
-
end
|
370
|
-
end
|
447
|
+
conn = klass.connection
|
448
|
+
sc = reflection.association_scope_cache(conn, owner) do
|
449
|
+
StatementCache.create(conn) { |params|
|
450
|
+
as = AssociationScope.create { params.bind }
|
451
|
+
target_scope.merge as.scope(self, conn)
|
452
|
+
}
|
371
453
|
end
|
372
454
|
|
373
|
-
|
374
|
-
|
375
|
-
|
455
|
+
binds = AssociationScope.get_bind_values(owner, reflection.chain)
|
456
|
+
sc.execute binds, klass, klass.connection
|
457
|
+
end
|
376
458
|
|
377
459
|
def find_target
|
378
|
-
records =
|
379
|
-
if options[:finder_sql]
|
380
|
-
reflection.klass.find_by_sql(custom_finder_sql)
|
381
|
-
else
|
382
|
-
scoped.all
|
383
|
-
end
|
384
|
-
|
385
|
-
records = options[:uniq] ? uniq(records) : records
|
460
|
+
records = get_records
|
386
461
|
records.each { |record| set_inverse_instance(record) }
|
387
462
|
records
|
388
463
|
end
|
@@ -402,12 +477,7 @@ module ActiveRecord
|
|
402
477
|
return memory if persisted.empty?
|
403
478
|
|
404
479
|
persisted.map! do |record|
|
405
|
-
|
406
|
-
# record rather than memory.at(memory.index(record)). The behavior is fixed in 1.9.
|
407
|
-
mem_index = memory.index(record)
|
408
|
-
|
409
|
-
if mem_index
|
410
|
-
mem_record = memory.delete_at(mem_index)
|
480
|
+
if mem_record = memory.delete(record)
|
411
481
|
|
412
482
|
((record.attribute_names & mem_record.attribute_names) - mem_record.changes.keys).each do |name|
|
413
483
|
mem_record[name] = record[name]
|
@@ -422,16 +492,16 @@ module ActiveRecord
|
|
422
492
|
persisted + memory
|
423
493
|
end
|
424
494
|
|
425
|
-
def
|
495
|
+
def _create_record(attributes, raise = false, &block)
|
426
496
|
unless owner.persisted?
|
427
497
|
raise ActiveRecord::RecordNotSaved, "You cannot call create unless the parent is saved"
|
428
498
|
end
|
429
499
|
|
430
500
|
if attributes.is_a?(Array)
|
431
|
-
attributes.collect { |attr|
|
501
|
+
attributes.collect { |attr| _create_record(attr, raise, &block) }
|
432
502
|
else
|
433
503
|
transaction do
|
434
|
-
add_to_target(build_record(attributes
|
504
|
+
add_to_target(build_record(attributes)) do |record|
|
435
505
|
yield(record) if block_given?
|
436
506
|
insert_record(record, true, raise)
|
437
507
|
end
|
@@ -445,12 +515,12 @@ module ActiveRecord
|
|
445
515
|
end
|
446
516
|
|
447
517
|
def create_scope
|
448
|
-
|
518
|
+
scope.scope_for_create.stringify_keys
|
449
519
|
end
|
450
520
|
|
451
521
|
def delete_or_destroy(records, method)
|
452
522
|
records = records.flatten
|
453
|
-
records.each { |record| raise_on_type_mismatch(record) }
|
523
|
+
records.each { |record| raise_on_type_mismatch!(record) }
|
454
524
|
existing_records = records.reject { |r| r.new_record? }
|
455
525
|
|
456
526
|
if existing_records.empty?
|
@@ -487,13 +557,21 @@ module ActiveRecord
|
|
487
557
|
target
|
488
558
|
end
|
489
559
|
|
490
|
-
def
|
560
|
+
def replace_common_records_in_memory(new_target, original_target)
|
561
|
+
common_records = new_target & original_target
|
562
|
+
common_records.each do |record|
|
563
|
+
skip_callbacks = true
|
564
|
+
replace_on_target(record, @target.index(record), skip_callbacks)
|
565
|
+
end
|
566
|
+
end
|
567
|
+
|
568
|
+
def concat_records(records, should_raise = false)
|
491
569
|
result = true
|
492
570
|
|
493
571
|
records.flatten.each do |record|
|
494
|
-
raise_on_type_mismatch(record)
|
495
|
-
add_to_target(record) do |
|
496
|
-
result &&= insert_record(
|
572
|
+
raise_on_type_mismatch!(record)
|
573
|
+
add_to_target(record) do |rec|
|
574
|
+
result &&= insert_record(rec, true, should_raise) unless owner.new_record?
|
497
575
|
end
|
498
576
|
end
|
499
577
|
|
@@ -502,20 +580,13 @@ module ActiveRecord
|
|
502
580
|
|
503
581
|
def callback(method, record)
|
504
582
|
callbacks_for(method).each do |callback|
|
505
|
-
|
506
|
-
when Symbol
|
507
|
-
owner.send(callback, record)
|
508
|
-
when Proc
|
509
|
-
callback.call(owner, record)
|
510
|
-
else
|
511
|
-
callback.send(method, owner, record)
|
512
|
-
end
|
583
|
+
callback.call(method, owner, record)
|
513
584
|
end
|
514
585
|
end
|
515
586
|
|
516
587
|
def callbacks_for(callback_name)
|
517
588
|
full_callback_name = "#{callback_name}_for_#{reflection.name}"
|
518
|
-
owner.class.send(full_callback_name
|
589
|
+
owner.class.send(full_callback_name)
|
519
590
|
end
|
520
591
|
|
521
592
|
# Should we deal with assoc.first or assoc.last by issuing an independent query to
|
@@ -526,51 +597,54 @@ module ActiveRecord
|
|
526
597
|
# Otherwise, go to the database only if none of the following are true:
|
527
598
|
# * target already loaded
|
528
599
|
# * owner is new record
|
529
|
-
# * custom :finder_sql exists
|
530
600
|
# * target contains new or changed record(s)
|
531
|
-
|
532
|
-
def fetch_first_or_last_using_find?(args)
|
601
|
+
def fetch_first_nth_or_last_using_find?(args)
|
533
602
|
if args.first.is_a?(Hash)
|
534
603
|
true
|
535
604
|
else
|
536
605
|
!(loaded? ||
|
537
606
|
owner.new_record? ||
|
538
|
-
|
539
|
-
target.any? { |record| record.new_record? || record.changed? } ||
|
540
|
-
args.first.kind_of?(Integer))
|
607
|
+
target.any? { |record| record.new_record? || record.changed? })
|
541
608
|
end
|
542
609
|
end
|
543
610
|
|
544
611
|
def include_in_memory?(record)
|
545
612
|
if reflection.is_a?(ActiveRecord::Reflection::ThroughReflection)
|
546
|
-
owner.
|
547
|
-
|
548
|
-
|
613
|
+
assoc = owner.association(reflection.through_reflection.name)
|
614
|
+
assoc.reader.any? { |source|
|
615
|
+
target_association = source.send(reflection.source_reflection.name)
|
616
|
+
|
617
|
+
if target_association.respond_to?(:include?)
|
618
|
+
target_association.include?(record)
|
619
|
+
else
|
620
|
+
target_association == record
|
621
|
+
end
|
549
622
|
} || target.include?(record)
|
550
623
|
else
|
551
624
|
target.include?(record)
|
552
625
|
end
|
553
626
|
end
|
554
627
|
|
555
|
-
# If
|
628
|
+
# If the :inverse_of option has been
|
629
|
+
# specified, then #find scans the entire collection.
|
556
630
|
def find_by_scan(*args)
|
557
631
|
expects_array = args.first.kind_of?(Array)
|
558
|
-
ids = args.flatten.compact.
|
632
|
+
ids = args.flatten.compact.map{ |arg| arg.to_s }.uniq
|
559
633
|
|
560
634
|
if ids.size == 1
|
561
635
|
id = ids.first
|
562
|
-
record = load_target.detect { |r| id == r.id }
|
636
|
+
record = load_target.detect { |r| id == r.id.to_s }
|
563
637
|
expects_array ? [ record ] : record
|
564
638
|
else
|
565
|
-
load_target.select { |r| ids.include?(r.id) }
|
639
|
+
load_target.select { |r| ids.include?(r.id.to_s) }
|
566
640
|
end
|
567
641
|
end
|
568
642
|
|
569
643
|
# Fetches the first/last using SQL if possible, otherwise from the target array.
|
570
|
-
def
|
644
|
+
def first_nth_or_last(type, *args)
|
571
645
|
args.shift if args.first.is_a?(Hash) && args.first.empty?
|
572
646
|
|
573
|
-
collection =
|
647
|
+
collection = fetch_first_nth_or_last_using_find?(args) ? scope : load_target
|
574
648
|
collection.send(type, *args).tap do |record|
|
575
649
|
set_inverse_instance record if record.is_a? ActiveRecord::Base
|
576
650
|
end
|