activerecord 4.1.15 → 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 +1162 -1792
- data/README.rdoc +15 -10
- data/lib/active_record.rb +4 -0
- data/lib/active_record/aggregations.rb +15 -8
- data/lib/active_record/association_relation.rb +13 -0
- data/lib/active_record/associations.rb +158 -49
- data/lib/active_record/associations/alias_tracker.rb +3 -12
- data/lib/active_record/associations/association.rb +16 -4
- data/lib/active_record/associations/association_scope.rb +83 -38
- data/lib/active_record/associations/belongs_to_association.rb +28 -10
- data/lib/active_record/associations/builder/association.rb +15 -4
- data/lib/active_record/associations/builder/belongs_to.rb +7 -29
- data/lib/active_record/associations/builder/collection_association.rb +5 -1
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +8 -13
- data/lib/active_record/associations/builder/has_many.rb +1 -1
- data/lib/active_record/associations/builder/has_one.rb +2 -2
- data/lib/active_record/associations/builder/singular_association.rb +8 -1
- data/lib/active_record/associations/collection_association.rb +63 -27
- data/lib/active_record/associations/collection_proxy.rb +29 -35
- data/lib/active_record/associations/foreign_association.rb +11 -0
- data/lib/active_record/associations/has_many_association.rb +83 -22
- data/lib/active_record/associations/has_many_through_association.rb +49 -26
- data/lib/active_record/associations/has_one_association.rb +1 -1
- data/lib/active_record/associations/join_dependency.rb +26 -13
- data/lib/active_record/associations/join_dependency/join_association.rb +25 -15
- data/lib/active_record/associations/join_dependency/join_part.rb +0 -1
- data/lib/active_record/associations/preloader.rb +36 -26
- data/lib/active_record/associations/preloader/association.rb +14 -11
- data/lib/active_record/associations/preloader/through_association.rb +4 -3
- data/lib/active_record/associations/singular_association.rb +17 -2
- data/lib/active_record/associations/through_association.rb +5 -12
- data/lib/active_record/attribute.rb +163 -0
- data/lib/active_record/attribute_assignment.rb +19 -11
- data/lib/active_record/attribute_decorators.rb +66 -0
- data/lib/active_record/attribute_methods.rb +56 -94
- data/lib/active_record/attribute_methods/before_type_cast.rb +7 -2
- data/lib/active_record/attribute_methods/dirty.rb +107 -43
- data/lib/active_record/attribute_methods/primary_key.rb +7 -8
- data/lib/active_record/attribute_methods/query.rb +1 -1
- data/lib/active_record/attribute_methods/read.rb +22 -59
- data/lib/active_record/attribute_methods/serialization.rb +16 -150
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +38 -40
- data/lib/active_record/attribute_methods/write.rb +9 -24
- data/lib/active_record/attribute_set.rb +81 -0
- data/lib/active_record/attribute_set/builder.rb +106 -0
- data/lib/active_record/attributes.rb +147 -0
- data/lib/active_record/autosave_association.rb +19 -12
- data/lib/active_record/base.rb +13 -24
- data/lib/active_record/callbacks.rb +6 -6
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +84 -52
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +52 -50
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +1 -1
- data/lib/active_record/connection_adapters/abstract/quoting.rb +60 -60
- data/lib/active_record/connection_adapters/abstract/savepoints.rb +1 -1
- data/lib/active_record/connection_adapters/abstract/schema_creation.rb +39 -4
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +138 -56
- data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +14 -34
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +268 -71
- data/lib/active_record/connection_adapters/abstract/transaction.rb +125 -118
- data/lib/active_record/connection_adapters/abstract_adapter.rb +171 -59
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +293 -139
- data/lib/active_record/connection_adapters/column.rb +29 -240
- data/lib/active_record/connection_adapters/connection_specification.rb +15 -24
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +16 -32
- data/lib/active_record/connection_adapters/mysql_adapter.rb +67 -144
- data/lib/active_record/connection_adapters/postgresql/array_parser.rb +15 -27
- data/lib/active_record/connection_adapters/postgresql/column.rb +20 -0
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +40 -25
- data/lib/active_record/connection_adapters/postgresql/oid.rb +29 -388
- 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/quoting.rb +46 -136
- data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +4 -4
- data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +152 -0
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +131 -43
- data/lib/active_record/connection_adapters/postgresql/utils.rb +77 -0
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +224 -477
- data/lib/active_record/connection_adapters/schema_cache.rb +14 -28
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +61 -75
- data/lib/active_record/connection_handling.rb +1 -1
- data/lib/active_record/core.rb +163 -39
- data/lib/active_record/counter_cache.rb +60 -6
- data/lib/active_record/enum.rb +9 -11
- data/lib/active_record/errors.rb +53 -30
- data/lib/active_record/explain.rb +1 -1
- data/lib/active_record/explain_subscriber.rb +1 -1
- data/lib/active_record/fixtures.rb +55 -69
- data/lib/active_record/gem_version.rb +4 -4
- data/lib/active_record/inheritance.rb +35 -10
- data/lib/active_record/integration.rb +4 -4
- data/lib/active_record/legacy_yaml_adapter.rb +30 -0
- data/lib/active_record/locking/optimistic.rb +46 -26
- data/lib/active_record/migration.rb +71 -46
- data/lib/active_record/migration/command_recorder.rb +19 -2
- data/lib/active_record/migration/join_table.rb +1 -1
- data/lib/active_record/model_schema.rb +52 -58
- data/lib/active_record/nested_attributes.rb +5 -5
- data/lib/active_record/no_touching.rb +1 -1
- data/lib/active_record/persistence.rb +46 -26
- data/lib/active_record/query_cache.rb +3 -3
- data/lib/active_record/querying.rb +10 -7
- data/lib/active_record/railtie.rb +18 -11
- data/lib/active_record/railties/databases.rake +50 -51
- data/lib/active_record/readonly_attributes.rb +0 -1
- data/lib/active_record/reflection.rb +273 -114
- data/lib/active_record/relation.rb +57 -25
- data/lib/active_record/relation/batches.rb +0 -2
- data/lib/active_record/relation/calculations.rb +41 -37
- data/lib/active_record/relation/finder_methods.rb +70 -47
- data/lib/active_record/relation/merger.rb +39 -29
- data/lib/active_record/relation/predicate_builder.rb +16 -8
- data/lib/active_record/relation/predicate_builder/array_handler.rb +32 -13
- data/lib/active_record/relation/predicate_builder/relation_handler.rb +1 -5
- data/lib/active_record/relation/query_methods.rb +114 -65
- data/lib/active_record/relation/spawn_methods.rb +3 -0
- data/lib/active_record/result.rb +18 -7
- data/lib/active_record/sanitization.rb +12 -2
- data/lib/active_record/schema.rb +0 -1
- data/lib/active_record/schema_dumper.rb +59 -28
- data/lib/active_record/schema_migration.rb +5 -4
- data/lib/active_record/scoping/default.rb +6 -4
- data/lib/active_record/scoping/named.rb +4 -0
- data/lib/active_record/serializers/xml_serializer.rb +3 -7
- data/lib/active_record/statement_cache.rb +95 -10
- data/lib/active_record/store.rb +5 -5
- data/lib/active_record/tasks/database_tasks.rb +61 -6
- data/lib/active_record/tasks/mysql_database_tasks.rb +32 -17
- data/lib/active_record/tasks/postgresql_database_tasks.rb +20 -9
- data/lib/active_record/timestamp.rb +9 -7
- data/lib/active_record/transactions.rb +53 -27
- data/lib/active_record/type.rb +23 -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/validations.rb +25 -19
- data/lib/active_record/validations/associated.rb +5 -3
- data/lib/active_record/validations/presence.rb +5 -3
- data/lib/active_record/validations/uniqueness.rb +25 -29
- data/lib/rails/generators/active_record/migration/migration_generator.rb +8 -4
- data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb +1 -1
- data/lib/rails/generators/active_record/model/templates/model.rb +1 -1
- metadata +66 -11
- data/lib/active_record/connection_adapters/postgresql/cast.rb +0 -168
@@ -2,33 +2,42 @@ module ActiveRecord
|
|
2
2
|
module Associations
|
3
3
|
# Implements the details of eager loading of Active Record associations.
|
4
4
|
#
|
5
|
-
#
|
6
|
-
# However, there are two different eager loading strategies.
|
5
|
+
# Suppose that you have the following two Active Record models:
|
7
6
|
#
|
8
|
-
#
|
9
|
-
#
|
10
|
-
#
|
11
|
-
#
|
12
|
-
# and all of its books via a single query:
|
7
|
+
# class Author < ActiveRecord::Base
|
8
|
+
# # columns: name, age
|
9
|
+
# has_many :books
|
10
|
+
# end
|
13
11
|
#
|
14
|
-
#
|
15
|
-
#
|
16
|
-
#
|
12
|
+
# class Book < ActiveRecord::Base
|
13
|
+
# # columns: title, sales, author_id
|
14
|
+
# end
|
17
15
|
#
|
18
|
-
#
|
19
|
-
#
|
20
|
-
#
|
21
|
-
# '
|
22
|
-
#
|
23
|
-
#
|
24
|
-
#
|
25
|
-
#
|
16
|
+
# When you load an author with all associated books Active Record will make
|
17
|
+
# multiple queries like this:
|
18
|
+
#
|
19
|
+
# Author.includes(:books).where(name: ['bell hooks', 'Homer']).to_a
|
20
|
+
#
|
21
|
+
# => SELECT `authors`.* FROM `authors` WHERE `name` IN ('bell hooks', 'Homer')
|
22
|
+
# => SELECT `books`.* FROM `books` WHERE `author_id` IN (2, 5)
|
23
|
+
#
|
24
|
+
# Active Record saves the ids of the records from the first query to use in
|
25
|
+
# the second. Depending on the number of associations involved there can be
|
26
|
+
# arbitrarily many SQL queries made.
|
27
|
+
#
|
28
|
+
# However, if there is a WHERE clause that spans across tables Active
|
29
|
+
# Record will fall back to a slightly more resource-intensive single query:
|
30
|
+
#
|
31
|
+
# Author.includes(:books).where(books: {title: 'Illiad'}).to_a
|
32
|
+
# => SELECT `authors`.`id` AS t0_r0, `authors`.`name` AS t0_r1, `authors`.`age` AS t0_r2,
|
33
|
+
# `books`.`id` AS t1_r0, `books`.`title` AS t1_r1, `books`.`sales` AS t1_r2
|
34
|
+
# FROM `authors`
|
35
|
+
# LEFT OUTER JOIN `books` ON `authors`.`id` = `books`.`author_id`
|
36
|
+
# WHERE `books`.`title` = 'Illiad'
|
37
|
+
#
|
38
|
+
# This could result in many rows that contain redundant data and it performs poorly at scale
|
39
|
+
# and is therefore only used when necessary.
|
26
40
|
#
|
27
|
-
# The second strategy is to use multiple database queries, one for each
|
28
|
-
# level of association. Since Rails 2.1, this is the default strategy. In
|
29
|
-
# situations where a table join is necessary (e.g. when the +:conditions+
|
30
|
-
# option references an association's column), it will fallback to the table
|
31
|
-
# join strategy.
|
32
41
|
class Preloader #:nodoc:
|
33
42
|
extend ActiveSupport::Autoload
|
34
43
|
|
@@ -80,7 +89,7 @@ module ActiveRecord
|
|
80
89
|
# { author: :avatar }
|
81
90
|
# [ :books, { author: :avatar } ]
|
82
91
|
|
83
|
-
NULL_RELATION = Struct.new(:values).new({})
|
92
|
+
NULL_RELATION = Struct.new(:values, :bind_values).new({}, [])
|
84
93
|
|
85
94
|
def preload(records, associations, preload_scope = nil)
|
86
95
|
records = Array.wrap(records).compact.uniq
|
@@ -151,7 +160,7 @@ module ActiveRecord
|
|
151
160
|
h
|
152
161
|
end
|
153
162
|
|
154
|
-
class AlreadyLoaded
|
163
|
+
class AlreadyLoaded # :nodoc:
|
155
164
|
attr_reader :owners, :reflection
|
156
165
|
|
157
166
|
def initialize(klass, owners, reflection, preload_scope)
|
@@ -166,7 +175,7 @@ module ActiveRecord
|
|
166
175
|
end
|
167
176
|
end
|
168
177
|
|
169
|
-
class NullPreloader
|
178
|
+
class NullPreloader # :nodoc:
|
170
179
|
def self.new(klass, owners, reflection, preload_scope); self; end
|
171
180
|
def self.run(preloader); end
|
172
181
|
def self.preloaded_records; []; end
|
@@ -178,6 +187,7 @@ module ActiveRecord
|
|
178
187
|
if owners.first.association(reflection.name).loaded?
|
179
188
|
return AlreadyLoaded
|
180
189
|
end
|
190
|
+
reflection.check_preloadable!
|
181
191
|
|
182
192
|
case reflection.macro
|
183
193
|
when :has_many
|
@@ -104,13 +104,11 @@ module ActiveRecord
|
|
104
104
|
end
|
105
105
|
|
106
106
|
def association_key_type
|
107
|
-
|
108
|
-
column && column.type
|
107
|
+
@klass.type_for_attribute(association_key_name.to_s).type
|
109
108
|
end
|
110
109
|
|
111
110
|
def owner_key_type
|
112
|
-
|
113
|
-
column && column.type
|
111
|
+
@model.type_for_attribute(owner_key_name.to_s).type
|
114
112
|
end
|
115
113
|
|
116
114
|
def load_slices(slices)
|
@@ -134,27 +132,32 @@ module ActiveRecord
|
|
134
132
|
scope = klass.unscoped
|
135
133
|
|
136
134
|
values = reflection_scope.values
|
135
|
+
reflection_binds = reflection_scope.bind_values
|
137
136
|
preload_values = preload_scope.values
|
137
|
+
preload_binds = preload_scope.bind_values
|
138
138
|
|
139
139
|
scope.where_values = Array(values[:where]) + Array(preload_values[:where])
|
140
140
|
scope.references_values = Array(values[:references]) + Array(preload_values[:references])
|
141
|
+
scope.bind_values = (reflection_binds + preload_binds)
|
141
142
|
|
142
143
|
scope._select! preload_values[:select] || values[:select] || table[Arel.star]
|
143
144
|
scope.includes! preload_values[:includes] || values[:includes]
|
145
|
+
scope.joins! preload_values[:joins] || values[:joins]
|
146
|
+
scope.order! preload_values[:order] || values[:order]
|
144
147
|
|
145
|
-
if preload_values
|
146
|
-
scope.
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
148
|
+
if preload_values[:reordering] || values[:reordering]
|
149
|
+
scope.reordering_value = true
|
150
|
+
end
|
151
|
+
|
152
|
+
if preload_values[:readonly] || values[:readonly]
|
153
|
+
scope.readonly!
|
151
154
|
end
|
152
155
|
|
153
156
|
if options[:as]
|
154
157
|
scope.where!(klass.table_name => { reflection.type => model.base_class.sti_name })
|
155
158
|
end
|
156
159
|
|
157
|
-
scope.unscope_values = Array(values[:unscope])
|
160
|
+
scope.unscope_values = Array(values[:unscope]) + Array(preload_values[:unscope])
|
158
161
|
klass.default_scoped.merge(scope)
|
159
162
|
end
|
160
163
|
end
|
@@ -23,7 +23,7 @@ module ActiveRecord
|
|
23
23
|
|
24
24
|
reset_association owners, through_reflection.name
|
25
25
|
|
26
|
-
middle_records = through_records.
|
26
|
+
middle_records = through_records.flat_map { |(_,rec)| rec }
|
27
27
|
|
28
28
|
preloaders = preloader.preload(middle_records,
|
29
29
|
source_reflection.name,
|
@@ -63,7 +63,7 @@ module ActiveRecord
|
|
63
63
|
should_reset = (through_scope != through_reflection.klass.unscoped) ||
|
64
64
|
(reflection.options[:source_type] && through_reflection.collection?)
|
65
65
|
|
66
|
-
#
|
66
|
+
# Don't cache the association - we would only be caching a subset
|
67
67
|
if should_reset
|
68
68
|
owners.each { |owner|
|
69
69
|
owner.association(association_name).reset
|
@@ -81,10 +81,11 @@ module ActiveRecord
|
|
81
81
|
unless reflection_scope.where_values.empty?
|
82
82
|
scope.includes_values = Array(reflection_scope.values[:includes] || options[:source])
|
83
83
|
scope.where_values = reflection_scope.values[:where]
|
84
|
+
scope.bind_values = reflection_scope.bind_values
|
84
85
|
end
|
85
86
|
|
86
87
|
scope.references! reflection_scope.values[:references]
|
87
|
-
scope.order
|
88
|
+
scope = scope.order reflection_scope.values[:order] if scope.eager_loading?
|
88
89
|
end
|
89
90
|
|
90
91
|
scope
|
@@ -3,7 +3,7 @@ module ActiveRecord
|
|
3
3
|
class SingularAssociation < Association #:nodoc:
|
4
4
|
# Implements the reader method, e.g. foo.bar for Foo.has_one :bar
|
5
5
|
def reader(force_reload = false)
|
6
|
-
if force_reload
|
6
|
+
if force_reload && klass
|
7
7
|
klass.uncached { reload }
|
8
8
|
elsif !loaded? || stale_target?
|
9
9
|
reload
|
@@ -38,8 +38,23 @@ module ActiveRecord
|
|
38
38
|
scope.scope_for_create.stringify_keys.except(klass.primary_key)
|
39
39
|
end
|
40
40
|
|
41
|
+
def get_records
|
42
|
+
return scope.limit(1).to_a if skip_statement_cache?
|
43
|
+
|
44
|
+
conn = klass.connection
|
45
|
+
sc = reflection.association_scope_cache(conn, owner) do
|
46
|
+
StatementCache.create(conn) { |params|
|
47
|
+
as = AssociationScope.create { params.bind }
|
48
|
+
target_scope.merge(as.scope(self, conn)).limit(1)
|
49
|
+
}
|
50
|
+
end
|
51
|
+
|
52
|
+
binds = AssociationScope.get_bind_values(owner, reflection.chain)
|
53
|
+
sc.execute binds, klass, klass.connection
|
54
|
+
end
|
55
|
+
|
41
56
|
def find_target
|
42
|
-
if record =
|
57
|
+
if record = get_records.first
|
43
58
|
set_inverse_instance record
|
44
59
|
end
|
45
60
|
end
|
@@ -3,7 +3,7 @@ module ActiveRecord
|
|
3
3
|
module Associations
|
4
4
|
module ThroughAssociation #:nodoc:
|
5
5
|
|
6
|
-
delegate :source_reflection, :through_reflection, :
|
6
|
+
delegate :source_reflection, :through_reflection, :to => :reflection
|
7
7
|
|
8
8
|
protected
|
9
9
|
|
@@ -13,14 +13,8 @@ module ActiveRecord
|
|
13
13
|
# 2. To get the type conditions for any STI models in the chain
|
14
14
|
def target_scope
|
15
15
|
scope = super
|
16
|
-
chain.drop(1).each do |reflection|
|
16
|
+
reflection.chain.drop(1).each do |reflection|
|
17
17
|
relation = reflection.klass.all
|
18
|
-
|
19
|
-
reflection_scope = reflection.scope
|
20
|
-
if reflection_scope && reflection_scope.arity.zero?
|
21
|
-
relation.merge!(reflection_scope)
|
22
|
-
end
|
23
|
-
|
24
18
|
scope.merge!(
|
25
19
|
relation.except(:select, :create_with, :includes, :preload, :joins, :eager_load)
|
26
20
|
)
|
@@ -71,18 +65,17 @@ module ActiveRecord
|
|
71
65
|
# Note: this does not capture all cases, for example it would be crazy to try to
|
72
66
|
# properly support stale-checking for nested associations.
|
73
67
|
def stale_state
|
74
|
-
if through_reflection.
|
68
|
+
if through_reflection.belongs_to?
|
75
69
|
owner[through_reflection.foreign_key] && owner[through_reflection.foreign_key].to_s
|
76
70
|
end
|
77
71
|
end
|
78
72
|
|
79
73
|
def foreign_key_present?
|
80
|
-
through_reflection.
|
81
|
-
!owner[through_reflection.foreign_key].nil?
|
74
|
+
through_reflection.belongs_to? && !owner[through_reflection.foreign_key].nil?
|
82
75
|
end
|
83
76
|
|
84
77
|
def ensure_mutable
|
85
|
-
|
78
|
+
unless source_reflection.belongs_to?
|
86
79
|
raise HasManyThroughCantAssociateThroughHasOneOrManyReflection.new(owner, reflection)
|
87
80
|
end
|
88
81
|
end
|
@@ -0,0 +1,163 @@
|
|
1
|
+
module ActiveRecord
|
2
|
+
class Attribute # :nodoc:
|
3
|
+
class << self
|
4
|
+
def from_database(name, value, type)
|
5
|
+
FromDatabase.new(name, value, type)
|
6
|
+
end
|
7
|
+
|
8
|
+
def from_user(name, value, type)
|
9
|
+
FromUser.new(name, value, type)
|
10
|
+
end
|
11
|
+
|
12
|
+
def with_cast_value(name, value, type)
|
13
|
+
WithCastValue.new(name, value, type)
|
14
|
+
end
|
15
|
+
|
16
|
+
def null(name)
|
17
|
+
Null.new(name)
|
18
|
+
end
|
19
|
+
|
20
|
+
def uninitialized(name, type)
|
21
|
+
Uninitialized.new(name, type)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
attr_reader :name, :value_before_type_cast, :type
|
26
|
+
|
27
|
+
# This method should not be called directly.
|
28
|
+
# Use #from_database or #from_user
|
29
|
+
def initialize(name, value_before_type_cast, type)
|
30
|
+
@name = name
|
31
|
+
@value_before_type_cast = value_before_type_cast
|
32
|
+
@type = type
|
33
|
+
end
|
34
|
+
|
35
|
+
def value
|
36
|
+
# `defined?` is cheaper than `||=` when we get back falsy values
|
37
|
+
@value = original_value unless defined?(@value)
|
38
|
+
@value
|
39
|
+
end
|
40
|
+
|
41
|
+
def original_value
|
42
|
+
type_cast(value_before_type_cast)
|
43
|
+
end
|
44
|
+
|
45
|
+
def value_for_database
|
46
|
+
type.type_cast_for_database(value)
|
47
|
+
end
|
48
|
+
|
49
|
+
def changed_from?(old_value)
|
50
|
+
type.changed?(old_value, value, value_before_type_cast)
|
51
|
+
end
|
52
|
+
|
53
|
+
def changed_in_place_from?(old_value)
|
54
|
+
has_been_read? && type.changed_in_place?(old_value, value)
|
55
|
+
end
|
56
|
+
|
57
|
+
def with_value_from_user(value)
|
58
|
+
self.class.from_user(name, value, type)
|
59
|
+
end
|
60
|
+
|
61
|
+
def with_value_from_database(value)
|
62
|
+
self.class.from_database(name, value, type)
|
63
|
+
end
|
64
|
+
|
65
|
+
def with_cast_value(value)
|
66
|
+
self.class.with_cast_value(name, value, type)
|
67
|
+
end
|
68
|
+
|
69
|
+
def type_cast(*)
|
70
|
+
raise NotImplementedError
|
71
|
+
end
|
72
|
+
|
73
|
+
def initialized?
|
74
|
+
true
|
75
|
+
end
|
76
|
+
|
77
|
+
def came_from_user?
|
78
|
+
false
|
79
|
+
end
|
80
|
+
|
81
|
+
def ==(other)
|
82
|
+
self.class == other.class &&
|
83
|
+
name == other.name &&
|
84
|
+
value_before_type_cast == other.value_before_type_cast &&
|
85
|
+
type == other.type
|
86
|
+
end
|
87
|
+
|
88
|
+
protected
|
89
|
+
|
90
|
+
def initialize_dup(other)
|
91
|
+
if defined?(@value) && @value.duplicable?
|
92
|
+
@value = @value.dup
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
private
|
97
|
+
|
98
|
+
def has_been_read?
|
99
|
+
defined?(@value)
|
100
|
+
end
|
101
|
+
|
102
|
+
class FromDatabase < Attribute # :nodoc:
|
103
|
+
def type_cast(value)
|
104
|
+
type.type_cast_from_database(value)
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
class FromUser < Attribute # :nodoc:
|
109
|
+
def type_cast(value)
|
110
|
+
type.type_cast_from_user(value)
|
111
|
+
end
|
112
|
+
|
113
|
+
def came_from_user?
|
114
|
+
true
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
class WithCastValue < Attribute # :nodoc:
|
119
|
+
def type_cast(value)
|
120
|
+
value
|
121
|
+
end
|
122
|
+
|
123
|
+
def changed_in_place_from?(old_value)
|
124
|
+
false
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
class Null < Attribute # :nodoc:
|
129
|
+
def initialize(name)
|
130
|
+
super(name, nil, Type::Value.new)
|
131
|
+
end
|
132
|
+
|
133
|
+
def value
|
134
|
+
nil
|
135
|
+
end
|
136
|
+
|
137
|
+
def with_value_from_database(value)
|
138
|
+
raise ActiveModel::MissingAttributeError, "can't write unknown attribute `#{name}`"
|
139
|
+
end
|
140
|
+
alias_method :with_value_from_user, :with_value_from_database
|
141
|
+
end
|
142
|
+
|
143
|
+
class Uninitialized < Attribute # :nodoc:
|
144
|
+
def initialize(name, type)
|
145
|
+
super(name, nil, type)
|
146
|
+
end
|
147
|
+
|
148
|
+
def value
|
149
|
+
if block_given?
|
150
|
+
yield name
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
def value_for_database
|
155
|
+
end
|
156
|
+
|
157
|
+
def initialized?
|
158
|
+
false
|
159
|
+
end
|
160
|
+
end
|
161
|
+
private_constant :FromDatabase, :FromUser, :Null, :Uninitialized
|
162
|
+
end
|
163
|
+
end
|
@@ -11,6 +11,15 @@ module ActiveRecord
|
|
11
11
|
# If the passed hash responds to <tt>permitted?</tt> method and the return value
|
12
12
|
# of this method is +false+ an <tt>ActiveModel::ForbiddenAttributesError</tt>
|
13
13
|
# exception is raised.
|
14
|
+
#
|
15
|
+
# cat = Cat.new(name: "Gorby", status: "yawning")
|
16
|
+
# cat.attributes # => { "name" => "Gorby", "status" => "yawning", "created_at" => nil, "updated_at" => nil}
|
17
|
+
# cat.assign_attributes(status: "sleeping")
|
18
|
+
# cat.attributes # => { "name" => "Gorby", "status" => "sleeping", "created_at" => nil, "updated_at" => nil }
|
19
|
+
#
|
20
|
+
# New attributes will be persisted in the database when the object is saved.
|
21
|
+
#
|
22
|
+
# Aliased to <tt>attributes=</tt>.
|
14
23
|
def assign_attributes(new_attributes)
|
15
24
|
if !new_attributes.respond_to?(:stringify_keys)
|
16
25
|
raise ArgumentError, "When assigning attributes, you must pass a hash as an argument."
|
@@ -60,7 +69,7 @@ module ActiveRecord
|
|
60
69
|
# by calling new on the column type or aggregation type (through composed_of) object with these parameters.
|
61
70
|
# So having the pairs written_on(1) = "2004", written_on(2) = "6", written_on(3) = "24", will instantiate
|
62
71
|
# written_on (a date type) with Date.new("2004", "6", "24"). You can also specify a typecast character in the
|
63
|
-
# parentheses to have the parameters typecasted before they're used in the constructor. Use i for
|
72
|
+
# parentheses to have the parameters typecasted before they're used in the constructor. Use i for Integer and
|
64
73
|
# f for Float. If all the values for a given attribute are empty, the attribute will be set to +nil+.
|
65
74
|
def assign_multiparameter_attributes(pairs)
|
66
75
|
execute_callstack_for_multiparameter_attributes(
|
@@ -106,7 +115,7 @@ module ActiveRecord
|
|
106
115
|
end
|
107
116
|
|
108
117
|
class MultiparameterAttribute #:nodoc:
|
109
|
-
attr_reader :object, :name, :values, :
|
118
|
+
attr_reader :object, :name, :values, :cast_type
|
110
119
|
|
111
120
|
def initialize(object, name, values)
|
112
121
|
@object = object
|
@@ -117,22 +126,22 @@ module ActiveRecord
|
|
117
126
|
def read_value
|
118
127
|
return if values.values.compact.empty?
|
119
128
|
|
120
|
-
@
|
121
|
-
klass
|
129
|
+
@cast_type = object.type_for_attribute(name)
|
130
|
+
klass = cast_type.klass
|
122
131
|
|
123
132
|
if klass == Time
|
124
133
|
read_time
|
125
134
|
elsif klass == Date
|
126
135
|
read_date
|
127
136
|
else
|
128
|
-
read_other
|
137
|
+
read_other
|
129
138
|
end
|
130
139
|
end
|
131
140
|
|
132
141
|
private
|
133
142
|
|
134
143
|
def instantiate_time_object(set_values)
|
135
|
-
if object.class.send(:create_time_zone_conversion_attribute?, name,
|
144
|
+
if object.class.send(:create_time_zone_conversion_attribute?, name, cast_type)
|
136
145
|
Time.zone.local(*set_values)
|
137
146
|
else
|
138
147
|
Time.send(object.class.default_timezone, *set_values)
|
@@ -140,9 +149,9 @@ module ActiveRecord
|
|
140
149
|
end
|
141
150
|
|
142
151
|
def read_time
|
143
|
-
# If column is a :time (and not :date or :
|
152
|
+
# If column is a :time (and not :date or :datetime) there is no need to validate if
|
144
153
|
# there are year/month/day fields
|
145
|
-
if
|
154
|
+
if cast_type.type == :time
|
146
155
|
# if the column is a time set the values to their defaults as January 1, 1970, but only if they're nil
|
147
156
|
{ 1 => 1970, 2 => 1, 3 => 1 }.each do |key,value|
|
148
157
|
values[key] ||= value
|
@@ -172,13 +181,12 @@ module ActiveRecord
|
|
172
181
|
end
|
173
182
|
end
|
174
183
|
|
175
|
-
def read_other
|
184
|
+
def read_other
|
176
185
|
max_position = extract_max_param
|
177
186
|
positions = (1..max_position)
|
178
187
|
validate_required_parameters!(positions)
|
179
188
|
|
180
|
-
|
181
|
-
klass.new(*set_values)
|
189
|
+
values.slice(*positions)
|
182
190
|
end
|
183
191
|
|
184
192
|
# Checks whether some blank date parameter exists. Note that this is different
|