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
@@ -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,37 +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 loaded?
|
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
|
-
scoped.select(column).except(:includes).map! do |record|
|
54
|
-
record.send(reflection.association_primary_key)
|
55
|
-
end
|
58
|
+
scope.pluck(column)
|
56
59
|
end
|
57
60
|
end
|
58
61
|
|
59
62
|
# Implements the ids writer method, e.g. foo.item_ids= for Foo.has_many :items
|
60
63
|
def ids_writer(ids)
|
61
|
-
pk_column = reflection.
|
62
|
-
|
63
|
-
ids.map
|
64
|
-
|
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
|
65
79
|
end
|
66
80
|
|
67
81
|
def reset
|
68
|
-
|
82
|
+
super
|
69
83
|
@target = []
|
70
84
|
end
|
71
85
|
|
72
|
-
def select(
|
86
|
+
def select(*fields)
|
73
87
|
if block_given?
|
74
88
|
load_target.select.each { |e| yield e }
|
75
89
|
else
|
76
|
-
|
90
|
+
scope.select(*fields)
|
77
91
|
end
|
78
92
|
end
|
79
93
|
|
@@ -81,56 +95,89 @@ module ActiveRecord
|
|
81
95
|
if block_given?
|
82
96
|
load_target.find(*args) { |*block_args| yield(*block_args) }
|
83
97
|
else
|
84
|
-
if options[:
|
85
|
-
|
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
|
86
109
|
else
|
87
|
-
|
110
|
+
scope.find(*args)
|
88
111
|
end
|
89
112
|
end
|
90
113
|
end
|
91
114
|
|
92
115
|
def first(*args)
|
93
|
-
|
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)
|
94
137
|
end
|
95
138
|
|
96
139
|
def last(*args)
|
97
|
-
|
140
|
+
first_nth_or_last(:last, *args)
|
98
141
|
end
|
99
142
|
|
100
|
-
def
|
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
|
151
|
+
end
|
152
|
+
|
153
|
+
def build(attributes = {}, &block)
|
101
154
|
if attributes.is_a?(Array)
|
102
|
-
attributes.collect { |attr| build(attr,
|
155
|
+
attributes.collect { |attr| build(attr, &block) }
|
103
156
|
else
|
104
|
-
add_to_target(build_record(attributes
|
157
|
+
add_to_target(build_record(attributes)) do |record|
|
105
158
|
yield(record) if block_given?
|
106
159
|
end
|
107
160
|
end
|
108
161
|
end
|
109
162
|
|
110
|
-
def create(attributes = {},
|
111
|
-
|
163
|
+
def create(attributes = {}, &block)
|
164
|
+
_create_record(attributes, &block)
|
112
165
|
end
|
113
166
|
|
114
|
-
def create!(attributes = {},
|
115
|
-
|
167
|
+
def create!(attributes = {}, &block)
|
168
|
+
_create_record(attributes, true, &block)
|
116
169
|
end
|
117
170
|
|
118
|
-
# Add +records+ to this association.
|
119
|
-
# 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.
|
120
174
|
def concat(*records)
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
records
|
126
|
-
raise_on_type_mismatch(record)
|
127
|
-
add_to_target(record) do |r|
|
128
|
-
result &&= insert_record(record) unless owner.new_record?
|
129
|
-
end
|
130
|
-
end
|
175
|
+
if owner.new_record?
|
176
|
+
load_target
|
177
|
+
concat_records(records)
|
178
|
+
else
|
179
|
+
transaction { concat_records(records) }
|
131
180
|
end
|
132
|
-
|
133
|
-
result && records
|
134
181
|
end
|
135
182
|
|
136
183
|
# Starts a transaction in the association class's database connection.
|
@@ -148,23 +195,38 @@ module ActiveRecord
|
|
148
195
|
end
|
149
196
|
end
|
150
197
|
|
151
|
-
#
|
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)
|
152
209
|
#
|
153
210
|
# See delete for more info.
|
154
|
-
def delete_all
|
155
|
-
|
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
|
156
225
|
reset
|
157
226
|
loaded!
|
158
227
|
end
|
159
228
|
end
|
160
229
|
|
161
|
-
# Called when the association is declared as :dependent => :delete_all. This is
|
162
|
-
# an optimised version which avoids loading the records into memory. Not really
|
163
|
-
# for public consumption.
|
164
|
-
def delete_all_on_destroy
|
165
|
-
scoped.delete_all
|
166
|
-
end
|
167
|
-
|
168
230
|
# Destroy all the records from this association.
|
169
231
|
#
|
170
232
|
# See destroy for more info.
|
@@ -175,44 +237,29 @@ module ActiveRecord
|
|
175
237
|
end
|
176
238
|
end
|
177
239
|
|
178
|
-
#
|
179
|
-
def sum(*args)
|
180
|
-
if block_given?
|
181
|
-
scoped.sum(*args) { |*block_args| yield(*block_args) }
|
182
|
-
else
|
183
|
-
scoped.sum(*args)
|
184
|
-
end
|
185
|
-
end
|
186
|
-
|
187
|
-
# Count all records using SQL. If the +:counter_sql+ or +:finder_sql+ option is set for the
|
188
|
-
# 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
|
189
241
|
# scope to the target class's +count+.
|
190
242
|
def count(column_name = nil, count_options = {})
|
243
|
+
# TODO: Remove count_options argument as soon we remove support to
|
244
|
+
# activerecord-deprecated_finders.
|
191
245
|
column_name, count_options = nil, column_name if column_name.is_a?(Hash)
|
192
246
|
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
else
|
200
|
-
if options[:uniq]
|
201
|
-
# This is needed because 'SELECT count(DISTINCT *)..' is not valid SQL.
|
202
|
-
column_name ||= reflection.klass.primary_key
|
203
|
-
count_options.merge!(:distinct => true)
|
204
|
-
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
|
205
253
|
|
206
|
-
|
254
|
+
value = relation.count(column_name)
|
207
255
|
|
208
|
-
|
209
|
-
|
256
|
+
limit = options[:limit]
|
257
|
+
offset = options[:offset]
|
210
258
|
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
end
|
259
|
+
if limit || offset
|
260
|
+
[ [value - offset.to_i, 0].max, limit.to_i ].min
|
261
|
+
else
|
262
|
+
value
|
216
263
|
end
|
217
264
|
end
|
218
265
|
|
@@ -224,16 +271,22 @@ module ActiveRecord
|
|
224
271
|
# are actually removed from the database, that depends precisely on
|
225
272
|
# +delete_records+. They are in any case removed from the collection.
|
226
273
|
def delete(*records)
|
227
|
-
|
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)
|
228
280
|
end
|
229
281
|
|
230
|
-
#
|
231
|
-
# +before_remove+
|
282
|
+
# Deletes the +records+ and removes them from this association calling
|
283
|
+
# +before_remove+ , +after_remove+ , +before_destroy+ and +after_destroy+ callbacks.
|
232
284
|
#
|
233
|
-
# Note that this method
|
234
|
-
#
|
285
|
+
# Note that this method removes records from the database ignoring the
|
286
|
+
# +:dependent+ option.
|
235
287
|
def destroy(*records)
|
236
|
-
|
288
|
+
return if records.empty?
|
289
|
+
records = find(records) if records.any? { |record| record.kind_of?(Integer) || record.kind_of?(String) }
|
237
290
|
delete_or_destroy(records, :destroy)
|
238
291
|
end
|
239
292
|
|
@@ -248,11 +301,15 @@ module ActiveRecord
|
|
248
301
|
# This method is abstract in the sense that it relies on
|
249
302
|
# +count_records+, which is a method descendants have to provide.
|
250
303
|
def size
|
251
|
-
if
|
252
|
-
|
253
|
-
|
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?
|
254
311
|
load_target.size
|
255
|
-
elsif !loaded? && !
|
312
|
+
elsif !loaded? && !association_scope.distinct_value && target.is_a?(Array)
|
256
313
|
unsaved_records = target.select { |r| r.new_record? }
|
257
314
|
unsaved_records.size + count_records
|
258
315
|
else
|
@@ -269,13 +326,24 @@ module ActiveRecord
|
|
269
326
|
load_target.size
|
270
327
|
end
|
271
328
|
|
272
|
-
#
|
273
|
-
#
|
274
|
-
#
|
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>.
|
275
337
|
def empty?
|
276
|
-
|
338
|
+
if loaded?
|
339
|
+
size.zero?
|
340
|
+
else
|
341
|
+
@target.blank? && !scope.exists?
|
342
|
+
end
|
277
343
|
end
|
278
344
|
|
345
|
+
# Returns true if the collections is not empty.
|
346
|
+
# Equivalent to +!collection.empty?+.
|
279
347
|
def any?
|
280
348
|
if block_given?
|
281
349
|
load_target.any? { |*block_args| yield(*block_args) }
|
@@ -284,7 +352,8 @@ module ActiveRecord
|
|
284
352
|
end
|
285
353
|
end
|
286
354
|
|
287
|
-
# 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+.
|
288
357
|
def many?
|
289
358
|
if block_given?
|
290
359
|
load_target.many? { |*block_args| yield(*block_args) }
|
@@ -293,26 +362,26 @@ module ActiveRecord
|
|
293
362
|
end
|
294
363
|
end
|
295
364
|
|
296
|
-
def
|
365
|
+
def distinct
|
297
366
|
seen = {}
|
298
|
-
|
367
|
+
load_target.find_all do |record|
|
299
368
|
seen[record.id] = true unless seen.key?(record.id)
|
300
369
|
end
|
301
370
|
end
|
371
|
+
alias uniq distinct
|
302
372
|
|
303
|
-
# Replace this collection with +other_array
|
304
|
-
#
|
373
|
+
# Replace this collection with +other_array+. This will perform a diff
|
374
|
+
# and delete/add only records that have changed.
|
305
375
|
def replace(other_array)
|
306
|
-
other_array.each { |val| raise_on_type_mismatch(val) }
|
376
|
+
other_array.each { |val| raise_on_type_mismatch!(val) }
|
307
377
|
original_target = load_target.dup
|
308
378
|
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
|
313
|
-
|
314
|
-
|
315
|
-
"new records could not be saved."
|
379
|
+
if owner.new_record?
|
380
|
+
replace_records(other_array, original_target)
|
381
|
+
else
|
382
|
+
replace_common_records_in_memory(other_array, original_target)
|
383
|
+
if other_array != original_target
|
384
|
+
transaction { replace_records(other_array, original_target) }
|
316
385
|
end
|
317
386
|
end
|
318
387
|
end
|
@@ -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 =~ /,/
|
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,14 +477,9 @@ 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)
|
480
|
+
if mem_record = memory.delete(record)
|
408
481
|
|
409
|
-
|
410
|
-
mem_record = memory.delete_at(mem_index)
|
411
|
-
|
412
|
-
(record.attribute_names - mem_record.changes.keys).each do |name|
|
482
|
+
((record.attribute_names & mem_record.attribute_names) - mem_record.changes.keys).each do |name|
|
413
483
|
mem_record[name] = record[name]
|
414
484
|
end
|
415
485
|
|
@@ -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,22 +515,28 @@ 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
|
-
|
457
|
-
|
526
|
+
if existing_records.empty?
|
527
|
+
remove_records(existing_records, records, method)
|
528
|
+
else
|
529
|
+
transaction { remove_records(existing_records, records, method) }
|
530
|
+
end
|
531
|
+
end
|
458
532
|
|
459
|
-
|
460
|
-
|
533
|
+
def remove_records(existing_records, records, method)
|
534
|
+
records.each { |record| callback(:before_remove, record) }
|
461
535
|
|
462
|
-
|
463
|
-
|
536
|
+
delete_records(existing_records, method) if existing_records.any?
|
537
|
+
records.each { |record| target.delete(record) }
|
538
|
+
|
539
|
+
records.each { |record| callback(:after_remove, record) }
|
464
540
|
end
|
465
541
|
|
466
542
|
# Delete the given records from the association, using one of the methods :destroy,
|
@@ -469,22 +545,48 @@ module ActiveRecord
|
|
469
545
|
raise NotImplementedError
|
470
546
|
end
|
471
547
|
|
548
|
+
def replace_records(new_target, original_target)
|
549
|
+
delete(target - new_target)
|
550
|
+
|
551
|
+
unless concat(new_target - target)
|
552
|
+
@target = original_target
|
553
|
+
raise RecordNotSaved, "Failed to replace #{reflection.name} because one or more of the " \
|
554
|
+
"new records could not be saved."
|
555
|
+
end
|
556
|
+
|
557
|
+
target
|
558
|
+
end
|
559
|
+
|
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)
|
569
|
+
result = true
|
570
|
+
|
571
|
+
records.flatten.each do |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?
|
575
|
+
end
|
576
|
+
end
|
577
|
+
|
578
|
+
result && records
|
579
|
+
end
|
580
|
+
|
472
581
|
def callback(method, record)
|
473
582
|
callbacks_for(method).each do |callback|
|
474
|
-
|
475
|
-
when Symbol
|
476
|
-
owner.send(callback, record)
|
477
|
-
when Proc
|
478
|
-
callback.call(owner, record)
|
479
|
-
else
|
480
|
-
callback.send(method, owner, record)
|
481
|
-
end
|
583
|
+
callback.call(method, owner, record)
|
482
584
|
end
|
483
585
|
end
|
484
586
|
|
485
587
|
def callbacks_for(callback_name)
|
486
588
|
full_callback_name = "#{callback_name}_for_#{reflection.name}"
|
487
|
-
owner.class.send(full_callback_name
|
589
|
+
owner.class.send(full_callback_name)
|
488
590
|
end
|
489
591
|
|
490
592
|
# Should we deal with assoc.first or assoc.last by issuing an independent query to
|
@@ -495,52 +597,57 @@ module ActiveRecord
|
|
495
597
|
# Otherwise, go to the database only if none of the following are true:
|
496
598
|
# * target already loaded
|
497
599
|
# * owner is new record
|
498
|
-
# * custom :finder_sql exists
|
499
600
|
# * target contains new or changed record(s)
|
500
|
-
|
501
|
-
def fetch_first_or_last_using_find?(args)
|
601
|
+
def fetch_first_nth_or_last_using_find?(args)
|
502
602
|
if args.first.is_a?(Hash)
|
503
603
|
true
|
504
604
|
else
|
505
605
|
!(loaded? ||
|
506
606
|
owner.new_record? ||
|
507
|
-
|
508
|
-
target.any? { |record| record.new_record? || record.changed? } ||
|
509
|
-
args.first.kind_of?(Integer))
|
607
|
+
target.any? { |record| record.new_record? || record.changed? })
|
510
608
|
end
|
511
609
|
end
|
512
610
|
|
513
611
|
def include_in_memory?(record)
|
514
612
|
if reflection.is_a?(ActiveRecord::Reflection::ThroughReflection)
|
515
|
-
owner.
|
516
|
-
|
517
|
-
|
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
|
518
622
|
} || target.include?(record)
|
519
623
|
else
|
520
624
|
target.include?(record)
|
521
625
|
end
|
522
626
|
end
|
523
627
|
|
524
|
-
# If
|
628
|
+
# If the :inverse_of option has been
|
629
|
+
# specified, then #find scans the entire collection.
|
525
630
|
def find_by_scan(*args)
|
526
631
|
expects_array = args.first.kind_of?(Array)
|
527
|
-
ids = args.flatten.compact.
|
632
|
+
ids = args.flatten.compact.map{ |arg| arg.to_s }.uniq
|
528
633
|
|
529
634
|
if ids.size == 1
|
530
635
|
id = ids.first
|
531
|
-
record = load_target.detect { |r| id == r.id }
|
636
|
+
record = load_target.detect { |r| id == r.id.to_s }
|
532
637
|
expects_array ? [ record ] : record
|
533
638
|
else
|
534
|
-
load_target.select { |r| ids.include?(r.id) }
|
639
|
+
load_target.select { |r| ids.include?(r.id.to_s) }
|
535
640
|
end
|
536
641
|
end
|
537
642
|
|
538
643
|
# Fetches the first/last using SQL if possible, otherwise from the target array.
|
539
|
-
def
|
644
|
+
def first_nth_or_last(type, *args)
|
540
645
|
args.shift if args.first.is_a?(Hash) && args.first.empty?
|
541
646
|
|
542
|
-
collection =
|
543
|
-
collection.send(type, *args)
|
647
|
+
collection = fetch_first_nth_or_last_using_find?(args) ? scope : load_target
|
648
|
+
collection.send(type, *args).tap do |record|
|
649
|
+
set_inverse_instance record if record.is_a? ActiveRecord::Base
|
650
|
+
end
|
544
651
|
end
|
545
652
|
end
|
546
653
|
end
|