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.

Files changed (185) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +1162 -1792
  3. data/README.rdoc +15 -10
  4. data/lib/active_record.rb +4 -0
  5. data/lib/active_record/aggregations.rb +15 -8
  6. data/lib/active_record/association_relation.rb +13 -0
  7. data/lib/active_record/associations.rb +158 -49
  8. data/lib/active_record/associations/alias_tracker.rb +3 -12
  9. data/lib/active_record/associations/association.rb +16 -4
  10. data/lib/active_record/associations/association_scope.rb +83 -38
  11. data/lib/active_record/associations/belongs_to_association.rb +28 -10
  12. data/lib/active_record/associations/builder/association.rb +15 -4
  13. data/lib/active_record/associations/builder/belongs_to.rb +7 -29
  14. data/lib/active_record/associations/builder/collection_association.rb +5 -1
  15. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +8 -13
  16. data/lib/active_record/associations/builder/has_many.rb +1 -1
  17. data/lib/active_record/associations/builder/has_one.rb +2 -2
  18. data/lib/active_record/associations/builder/singular_association.rb +8 -1
  19. data/lib/active_record/associations/collection_association.rb +63 -27
  20. data/lib/active_record/associations/collection_proxy.rb +29 -35
  21. data/lib/active_record/associations/foreign_association.rb +11 -0
  22. data/lib/active_record/associations/has_many_association.rb +83 -22
  23. data/lib/active_record/associations/has_many_through_association.rb +49 -26
  24. data/lib/active_record/associations/has_one_association.rb +1 -1
  25. data/lib/active_record/associations/join_dependency.rb +26 -13
  26. data/lib/active_record/associations/join_dependency/join_association.rb +25 -15
  27. data/lib/active_record/associations/join_dependency/join_part.rb +0 -1
  28. data/lib/active_record/associations/preloader.rb +36 -26
  29. data/lib/active_record/associations/preloader/association.rb +14 -11
  30. data/lib/active_record/associations/preloader/through_association.rb +4 -3
  31. data/lib/active_record/associations/singular_association.rb +17 -2
  32. data/lib/active_record/associations/through_association.rb +5 -12
  33. data/lib/active_record/attribute.rb +163 -0
  34. data/lib/active_record/attribute_assignment.rb +19 -11
  35. data/lib/active_record/attribute_decorators.rb +66 -0
  36. data/lib/active_record/attribute_methods.rb +56 -94
  37. data/lib/active_record/attribute_methods/before_type_cast.rb +7 -2
  38. data/lib/active_record/attribute_methods/dirty.rb +107 -43
  39. data/lib/active_record/attribute_methods/primary_key.rb +7 -8
  40. data/lib/active_record/attribute_methods/query.rb +1 -1
  41. data/lib/active_record/attribute_methods/read.rb +22 -59
  42. data/lib/active_record/attribute_methods/serialization.rb +16 -150
  43. data/lib/active_record/attribute_methods/time_zone_conversion.rb +38 -40
  44. data/lib/active_record/attribute_methods/write.rb +9 -24
  45. data/lib/active_record/attribute_set.rb +81 -0
  46. data/lib/active_record/attribute_set/builder.rb +106 -0
  47. data/lib/active_record/attributes.rb +147 -0
  48. data/lib/active_record/autosave_association.rb +19 -12
  49. data/lib/active_record/base.rb +13 -24
  50. data/lib/active_record/callbacks.rb +6 -6
  51. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +84 -52
  52. data/lib/active_record/connection_adapters/abstract/database_statements.rb +52 -50
  53. data/lib/active_record/connection_adapters/abstract/query_cache.rb +1 -1
  54. data/lib/active_record/connection_adapters/abstract/quoting.rb +60 -60
  55. data/lib/active_record/connection_adapters/abstract/savepoints.rb +1 -1
  56. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +39 -4
  57. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +138 -56
  58. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +14 -34
  59. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +268 -71
  60. data/lib/active_record/connection_adapters/abstract/transaction.rb +125 -118
  61. data/lib/active_record/connection_adapters/abstract_adapter.rb +171 -59
  62. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +293 -139
  63. data/lib/active_record/connection_adapters/column.rb +29 -240
  64. data/lib/active_record/connection_adapters/connection_specification.rb +15 -24
  65. data/lib/active_record/connection_adapters/mysql2_adapter.rb +16 -32
  66. data/lib/active_record/connection_adapters/mysql_adapter.rb +67 -144
  67. data/lib/active_record/connection_adapters/postgresql/array_parser.rb +15 -27
  68. data/lib/active_record/connection_adapters/postgresql/column.rb +20 -0
  69. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +40 -25
  70. data/lib/active_record/connection_adapters/postgresql/oid.rb +29 -388
  71. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +100 -0
  72. data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +52 -0
  73. data/lib/active_record/connection_adapters/postgresql/oid/bit_varying.rb +13 -0
  74. data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +15 -0
  75. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +46 -0
  76. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +11 -0
  77. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +36 -0
  78. data/lib/active_record/connection_adapters/postgresql/oid/decimal.rb +13 -0
  79. data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +19 -0
  80. data/lib/active_record/connection_adapters/postgresql/oid/float.rb +21 -0
  81. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +59 -0
  82. data/lib/active_record/connection_adapters/postgresql/oid/inet.rb +13 -0
  83. data/lib/active_record/connection_adapters/postgresql/oid/infinity.rb +13 -0
  84. data/lib/active_record/connection_adapters/postgresql/oid/integer.rb +11 -0
  85. data/lib/active_record/connection_adapters/postgresql/oid/json.rb +35 -0
  86. data/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb +23 -0
  87. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +43 -0
  88. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +43 -0
  89. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +79 -0
  90. data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +19 -0
  91. data/lib/active_record/connection_adapters/postgresql/oid/time.rb +11 -0
  92. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +109 -0
  93. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +21 -0
  94. data/lib/active_record/connection_adapters/postgresql/oid/vector.rb +26 -0
  95. data/lib/active_record/connection_adapters/postgresql/oid/xml.rb +28 -0
  96. data/lib/active_record/connection_adapters/postgresql/quoting.rb +46 -136
  97. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +4 -4
  98. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +152 -0
  99. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +131 -43
  100. data/lib/active_record/connection_adapters/postgresql/utils.rb +77 -0
  101. data/lib/active_record/connection_adapters/postgresql_adapter.rb +224 -477
  102. data/lib/active_record/connection_adapters/schema_cache.rb +14 -28
  103. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +61 -75
  104. data/lib/active_record/connection_handling.rb +1 -1
  105. data/lib/active_record/core.rb +163 -39
  106. data/lib/active_record/counter_cache.rb +60 -6
  107. data/lib/active_record/enum.rb +9 -11
  108. data/lib/active_record/errors.rb +53 -30
  109. data/lib/active_record/explain.rb +1 -1
  110. data/lib/active_record/explain_subscriber.rb +1 -1
  111. data/lib/active_record/fixtures.rb +55 -69
  112. data/lib/active_record/gem_version.rb +4 -4
  113. data/lib/active_record/inheritance.rb +35 -10
  114. data/lib/active_record/integration.rb +4 -4
  115. data/lib/active_record/legacy_yaml_adapter.rb +30 -0
  116. data/lib/active_record/locking/optimistic.rb +46 -26
  117. data/lib/active_record/migration.rb +71 -46
  118. data/lib/active_record/migration/command_recorder.rb +19 -2
  119. data/lib/active_record/migration/join_table.rb +1 -1
  120. data/lib/active_record/model_schema.rb +52 -58
  121. data/lib/active_record/nested_attributes.rb +5 -5
  122. data/lib/active_record/no_touching.rb +1 -1
  123. data/lib/active_record/persistence.rb +46 -26
  124. data/lib/active_record/query_cache.rb +3 -3
  125. data/lib/active_record/querying.rb +10 -7
  126. data/lib/active_record/railtie.rb +18 -11
  127. data/lib/active_record/railties/databases.rake +50 -51
  128. data/lib/active_record/readonly_attributes.rb +0 -1
  129. data/lib/active_record/reflection.rb +273 -114
  130. data/lib/active_record/relation.rb +57 -25
  131. data/lib/active_record/relation/batches.rb +0 -2
  132. data/lib/active_record/relation/calculations.rb +41 -37
  133. data/lib/active_record/relation/finder_methods.rb +70 -47
  134. data/lib/active_record/relation/merger.rb +39 -29
  135. data/lib/active_record/relation/predicate_builder.rb +16 -8
  136. data/lib/active_record/relation/predicate_builder/array_handler.rb +32 -13
  137. data/lib/active_record/relation/predicate_builder/relation_handler.rb +1 -5
  138. data/lib/active_record/relation/query_methods.rb +114 -65
  139. data/lib/active_record/relation/spawn_methods.rb +3 -0
  140. data/lib/active_record/result.rb +18 -7
  141. data/lib/active_record/sanitization.rb +12 -2
  142. data/lib/active_record/schema.rb +0 -1
  143. data/lib/active_record/schema_dumper.rb +59 -28
  144. data/lib/active_record/schema_migration.rb +5 -4
  145. data/lib/active_record/scoping/default.rb +6 -4
  146. data/lib/active_record/scoping/named.rb +4 -0
  147. data/lib/active_record/serializers/xml_serializer.rb +3 -7
  148. data/lib/active_record/statement_cache.rb +95 -10
  149. data/lib/active_record/store.rb +5 -5
  150. data/lib/active_record/tasks/database_tasks.rb +61 -6
  151. data/lib/active_record/tasks/mysql_database_tasks.rb +32 -17
  152. data/lib/active_record/tasks/postgresql_database_tasks.rb +20 -9
  153. data/lib/active_record/timestamp.rb +9 -7
  154. data/lib/active_record/transactions.rb +53 -27
  155. data/lib/active_record/type.rb +23 -0
  156. data/lib/active_record/type/big_integer.rb +13 -0
  157. data/lib/active_record/type/binary.rb +50 -0
  158. data/lib/active_record/type/boolean.rb +31 -0
  159. data/lib/active_record/type/date.rb +50 -0
  160. data/lib/active_record/type/date_time.rb +54 -0
  161. data/lib/active_record/type/decimal.rb +64 -0
  162. data/lib/active_record/type/decimal_without_scale.rb +11 -0
  163. data/lib/active_record/type/decorator.rb +14 -0
  164. data/lib/active_record/type/float.rb +19 -0
  165. data/lib/active_record/type/hash_lookup_type_map.rb +23 -0
  166. data/lib/active_record/type/integer.rb +59 -0
  167. data/lib/active_record/type/mutable.rb +16 -0
  168. data/lib/active_record/type/numeric.rb +36 -0
  169. data/lib/active_record/type/serialized.rb +62 -0
  170. data/lib/active_record/type/string.rb +40 -0
  171. data/lib/active_record/type/text.rb +11 -0
  172. data/lib/active_record/type/time.rb +26 -0
  173. data/lib/active_record/type/time_value.rb +38 -0
  174. data/lib/active_record/type/type_map.rb +64 -0
  175. data/lib/active_record/type/unsigned_integer.rb +15 -0
  176. data/lib/active_record/type/value.rb +110 -0
  177. data/lib/active_record/validations.rb +25 -19
  178. data/lib/active_record/validations/associated.rb +5 -3
  179. data/lib/active_record/validations/presence.rb +5 -3
  180. data/lib/active_record/validations/uniqueness.rb +25 -29
  181. data/lib/rails/generators/active_record/migration/migration_generator.rb +8 -4
  182. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb +1 -1
  183. data/lib/rails/generators/active_record/model/templates/model.rb +1 -1
  184. metadata +66 -11
  185. data/lib/active_record/connection_adapters/postgresql/cast.rb +0 -168
@@ -19,7 +19,6 @@ module ActiveRecord
19
19
 
20
20
  def initialize(base_klass, children)
21
21
  @base_klass = base_klass
22
- @column_names_with_alias = nil
23
22
  @children = children
24
23
  end
25
24
 
@@ -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
- # Note that 'eager loading' and 'preloading' are actually the same thing.
6
- # However, there are two different eager loading strategies.
5
+ # Suppose that you have the following two Active Record models:
7
6
  #
8
- # The first one is by using table joins. This was only strategy available
9
- # prior to Rails 2.1. Suppose that you have an Author model with columns
10
- # 'name' and 'age', and a Book model with columns 'name' and 'sales'. Using
11
- # this strategy, Active Record would try to retrieve all data for an author
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
- # SELECT * FROM authors
15
- # LEFT OUTER JOIN books ON authors.id = books.author_id
16
- # WHERE authors.name = 'Ken Akamatsu'
12
+ # class Book < ActiveRecord::Base
13
+ # # columns: title, sales, author_id
14
+ # end
17
15
  #
18
- # However, this could result in many rows that contain redundant data. After
19
- # having received the first row, we already have enough data to instantiate
20
- # the Author object. In all subsequent rows, only the data for the joined
21
- # 'books' table is useful; the joined 'authors' data is just redundant, and
22
- # processing this redundant data takes memory and CPU time. The problem
23
- # quickly becomes worse and worse as the level of eager loading increases
24
- # (i.e. if Active Record is to eager load the associations' associations as
25
- # well).
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
- column = @klass.column_types[association_key_name.to_s]
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
- column = @model.column_types[owner_key_name.to_s]
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.key? :order
146
- scope.order! preload_values[:order]
147
- else
148
- if values.key? :order
149
- scope.order! values[:order]
150
- end
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.map { |(_,rec)| rec }.flatten
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
- # Dont cache the association - we would only be caching a subset
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! reflection_scope.values[:order] if scope.eager_loading?
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 = scope.take
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, :chain, :to => :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.macro == :belongs_to
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.macro == :belongs_to &&
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
- if source_reflection.macro != :belongs_to
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 Fixnum and
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, :column
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
- @column = object.class.reflect_on_aggregation(name.to_sym) || object.column_for_attribute(name)
121
- klass = column.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(klass)
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, column)
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 :timestamp) there is no need to validate if
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 column.type == :time
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(klass)
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
- set_values = values.values_at(*positions)
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