activerecord 3.2.22.5 → 4.2.11.3

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of activerecord might be problematic. Click here for more details.

Files changed (236) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +1632 -609
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +37 -41
  5. data/examples/performance.rb +31 -19
  6. data/examples/simple.rb +4 -4
  7. data/lib/active_record/aggregations.rb +56 -42
  8. data/lib/active_record/association_relation.rb +35 -0
  9. data/lib/active_record/associations/alias_tracker.rb +47 -36
  10. data/lib/active_record/associations/association.rb +73 -55
  11. data/lib/active_record/associations/association_scope.rb +143 -82
  12. data/lib/active_record/associations/belongs_to_association.rb +65 -25
  13. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +7 -2
  14. data/lib/active_record/associations/builder/association.rb +125 -31
  15. data/lib/active_record/associations/builder/belongs_to.rb +89 -61
  16. data/lib/active_record/associations/builder/collection_association.rb +69 -49
  17. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +113 -42
  18. data/lib/active_record/associations/builder/has_many.rb +8 -64
  19. data/lib/active_record/associations/builder/has_one.rb +12 -51
  20. data/lib/active_record/associations/builder/singular_association.rb +23 -17
  21. data/lib/active_record/associations/collection_association.rb +251 -177
  22. data/lib/active_record/associations/collection_proxy.rb +963 -63
  23. data/lib/active_record/associations/foreign_association.rb +11 -0
  24. data/lib/active_record/associations/has_many_association.rb +113 -22
  25. data/lib/active_record/associations/has_many_through_association.rb +99 -39
  26. data/lib/active_record/associations/has_one_association.rb +43 -20
  27. data/lib/active_record/associations/has_one_through_association.rb +1 -1
  28. data/lib/active_record/associations/join_dependency/join_association.rb +76 -107
  29. data/lib/active_record/associations/join_dependency/join_base.rb +7 -9
  30. data/lib/active_record/associations/join_dependency/join_part.rb +30 -37
  31. data/lib/active_record/associations/join_dependency.rb +230 -156
  32. data/lib/active_record/associations/preloader/association.rb +96 -55
  33. data/lib/active_record/associations/preloader/collection_association.rb +3 -3
  34. data/lib/active_record/associations/preloader/has_many_through.rb +7 -3
  35. data/lib/active_record/associations/preloader/has_one.rb +1 -1
  36. data/lib/active_record/associations/preloader/singular_association.rb +3 -3
  37. data/lib/active_record/associations/preloader/through_association.rb +62 -33
  38. data/lib/active_record/associations/preloader.rb +101 -79
  39. data/lib/active_record/associations/singular_association.rb +29 -13
  40. data/lib/active_record/associations/through_association.rb +30 -16
  41. data/lib/active_record/associations.rb +463 -345
  42. data/lib/active_record/attribute.rb +163 -0
  43. data/lib/active_record/attribute_assignment.rb +142 -151
  44. data/lib/active_record/attribute_decorators.rb +66 -0
  45. data/lib/active_record/attribute_methods/before_type_cast.rb +52 -7
  46. data/lib/active_record/attribute_methods/dirty.rb +137 -57
  47. data/lib/active_record/attribute_methods/primary_key.rb +50 -36
  48. data/lib/active_record/attribute_methods/query.rb +5 -4
  49. data/lib/active_record/attribute_methods/read.rb +73 -106
  50. data/lib/active_record/attribute_methods/serialization.rb +44 -94
  51. data/lib/active_record/attribute_methods/time_zone_conversion.rb +49 -45
  52. data/lib/active_record/attribute_methods/write.rb +57 -44
  53. data/lib/active_record/attribute_methods.rb +301 -141
  54. data/lib/active_record/attribute_set/builder.rb +106 -0
  55. data/lib/active_record/attribute_set.rb +81 -0
  56. data/lib/active_record/attributes.rb +147 -0
  57. data/lib/active_record/autosave_association.rb +246 -217
  58. data/lib/active_record/base.rb +70 -474
  59. data/lib/active_record/callbacks.rb +66 -28
  60. data/lib/active_record/coders/json.rb +13 -0
  61. data/lib/active_record/coders/yaml_column.rb +18 -21
  62. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +396 -219
  63. data/lib/active_record/connection_adapters/abstract/database_limits.rb +9 -0
  64. data/lib/active_record/connection_adapters/abstract/database_statements.rb +167 -164
  65. data/lib/active_record/connection_adapters/abstract/query_cache.rb +29 -24
  66. data/lib/active_record/connection_adapters/abstract/quoting.rb +74 -55
  67. data/lib/active_record/connection_adapters/abstract/savepoints.rb +21 -0
  68. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +125 -0
  69. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +261 -169
  70. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +50 -0
  71. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +707 -259
  72. data/lib/active_record/connection_adapters/abstract/transaction.rb +215 -0
  73. data/lib/active_record/connection_adapters/abstract_adapter.rb +298 -89
  74. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +466 -196
  75. data/lib/active_record/connection_adapters/column.rb +31 -245
  76. data/lib/active_record/connection_adapters/connection_specification.rb +275 -0
  77. data/lib/active_record/connection_adapters/mysql2_adapter.rb +45 -57
  78. data/lib/active_record/connection_adapters/mysql_adapter.rb +180 -123
  79. data/lib/active_record/connection_adapters/postgresql/array_parser.rb +93 -0
  80. data/lib/active_record/connection_adapters/postgresql/column.rb +20 -0
  81. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +232 -0
  82. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +100 -0
  83. data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +52 -0
  84. data/lib/active_record/connection_adapters/postgresql/oid/bit_varying.rb +13 -0
  85. data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +15 -0
  86. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +46 -0
  87. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +11 -0
  88. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +36 -0
  89. data/lib/active_record/connection_adapters/postgresql/oid/decimal.rb +13 -0
  90. data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +19 -0
  91. data/lib/active_record/connection_adapters/postgresql/oid/float.rb +21 -0
  92. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +59 -0
  93. data/lib/active_record/connection_adapters/postgresql/oid/inet.rb +13 -0
  94. data/lib/active_record/connection_adapters/postgresql/oid/infinity.rb +13 -0
  95. data/lib/active_record/connection_adapters/postgresql/oid/integer.rb +11 -0
  96. data/lib/active_record/connection_adapters/postgresql/oid/json.rb +35 -0
  97. data/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb +23 -0
  98. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +43 -0
  99. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +43 -0
  100. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +79 -0
  101. data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +19 -0
  102. data/lib/active_record/connection_adapters/postgresql/oid/time.rb +11 -0
  103. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +109 -0
  104. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +21 -0
  105. data/lib/active_record/connection_adapters/postgresql/oid/vector.rb +26 -0
  106. data/lib/active_record/connection_adapters/postgresql/oid/xml.rb +28 -0
  107. data/lib/active_record/connection_adapters/postgresql/oid.rb +36 -0
  108. data/lib/active_record/connection_adapters/postgresql/quoting.rb +108 -0
  109. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +30 -0
  110. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +152 -0
  111. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +596 -0
  112. data/lib/active_record/connection_adapters/postgresql/utils.rb +77 -0
  113. data/lib/active_record/connection_adapters/postgresql_adapter.rb +430 -999
  114. data/lib/active_record/connection_adapters/schema_cache.rb +52 -27
  115. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +579 -22
  116. data/lib/active_record/connection_handling.rb +132 -0
  117. data/lib/active_record/core.rb +579 -0
  118. data/lib/active_record/counter_cache.rb +157 -105
  119. data/lib/active_record/dynamic_matchers.rb +119 -63
  120. data/lib/active_record/enum.rb +197 -0
  121. data/lib/active_record/errors.rb +94 -36
  122. data/lib/active_record/explain.rb +15 -63
  123. data/lib/active_record/explain_registry.rb +30 -0
  124. data/lib/active_record/explain_subscriber.rb +9 -5
  125. data/lib/active_record/fixture_set/file.rb +56 -0
  126. data/lib/active_record/fixtures.rb +302 -215
  127. data/lib/active_record/gem_version.rb +15 -0
  128. data/lib/active_record/inheritance.rb +143 -70
  129. data/lib/active_record/integration.rb +65 -12
  130. data/lib/active_record/legacy_yaml_adapter.rb +30 -0
  131. data/lib/active_record/locale/en.yml +8 -1
  132. data/lib/active_record/locking/optimistic.rb +73 -52
  133. data/lib/active_record/locking/pessimistic.rb +5 -5
  134. data/lib/active_record/log_subscriber.rb +24 -21
  135. data/lib/active_record/migration/command_recorder.rb +124 -32
  136. data/lib/active_record/migration/join_table.rb +15 -0
  137. data/lib/active_record/migration.rb +511 -213
  138. data/lib/active_record/model_schema.rb +91 -117
  139. data/lib/active_record/nested_attributes.rb +184 -130
  140. data/lib/active_record/no_touching.rb +52 -0
  141. data/lib/active_record/null_relation.rb +81 -0
  142. data/lib/active_record/persistence.rb +276 -117
  143. data/lib/active_record/query_cache.rb +19 -37
  144. data/lib/active_record/querying.rb +28 -18
  145. data/lib/active_record/railtie.rb +73 -40
  146. data/lib/active_record/railties/console_sandbox.rb +3 -4
  147. data/lib/active_record/railties/controller_runtime.rb +4 -3
  148. data/lib/active_record/railties/databases.rake +141 -416
  149. data/lib/active_record/railties/jdbcmysql_error.rb +1 -1
  150. data/lib/active_record/readonly_attributes.rb +1 -4
  151. data/lib/active_record/reflection.rb +513 -154
  152. data/lib/active_record/relation/batches.rb +91 -43
  153. data/lib/active_record/relation/calculations.rb +199 -161
  154. data/lib/active_record/relation/delegation.rb +116 -25
  155. data/lib/active_record/relation/finder_methods.rb +362 -248
  156. data/lib/active_record/relation/merger.rb +193 -0
  157. data/lib/active_record/relation/predicate_builder/array_handler.rb +48 -0
  158. data/lib/active_record/relation/predicate_builder/relation_handler.rb +13 -0
  159. data/lib/active_record/relation/predicate_builder.rb +135 -43
  160. data/lib/active_record/relation/query_methods.rb +928 -167
  161. data/lib/active_record/relation/spawn_methods.rb +48 -149
  162. data/lib/active_record/relation.rb +352 -207
  163. data/lib/active_record/result.rb +101 -10
  164. data/lib/active_record/runtime_registry.rb +22 -0
  165. data/lib/active_record/sanitization.rb +56 -59
  166. data/lib/active_record/schema.rb +19 -13
  167. data/lib/active_record/schema_dumper.rb +106 -63
  168. data/lib/active_record/schema_migration.rb +53 -0
  169. data/lib/active_record/scoping/default.rb +50 -57
  170. data/lib/active_record/scoping/named.rb +73 -109
  171. data/lib/active_record/scoping.rb +58 -123
  172. data/lib/active_record/serialization.rb +6 -2
  173. data/lib/active_record/serializers/xml_serializer.rb +12 -22
  174. data/lib/active_record/statement_cache.rb +111 -0
  175. data/lib/active_record/store.rb +168 -15
  176. data/lib/active_record/tasks/database_tasks.rb +299 -0
  177. data/lib/active_record/tasks/mysql_database_tasks.rb +159 -0
  178. data/lib/active_record/tasks/postgresql_database_tasks.rb +101 -0
  179. data/lib/active_record/tasks/sqlite_database_tasks.rb +55 -0
  180. data/lib/active_record/timestamp.rb +23 -16
  181. data/lib/active_record/transactions.rb +125 -79
  182. data/lib/active_record/type/big_integer.rb +13 -0
  183. data/lib/active_record/type/binary.rb +50 -0
  184. data/lib/active_record/type/boolean.rb +31 -0
  185. data/lib/active_record/type/date.rb +50 -0
  186. data/lib/active_record/type/date_time.rb +54 -0
  187. data/lib/active_record/type/decimal.rb +64 -0
  188. data/lib/active_record/type/decimal_without_scale.rb +11 -0
  189. data/lib/active_record/type/decorator.rb +14 -0
  190. data/lib/active_record/type/float.rb +19 -0
  191. data/lib/active_record/type/hash_lookup_type_map.rb +23 -0
  192. data/lib/active_record/type/integer.rb +59 -0
  193. data/lib/active_record/type/mutable.rb +16 -0
  194. data/lib/active_record/type/numeric.rb +36 -0
  195. data/lib/active_record/type/serialized.rb +62 -0
  196. data/lib/active_record/type/string.rb +40 -0
  197. data/lib/active_record/type/text.rb +11 -0
  198. data/lib/active_record/type/time.rb +26 -0
  199. data/lib/active_record/type/time_value.rb +38 -0
  200. data/lib/active_record/type/type_map.rb +64 -0
  201. data/lib/active_record/type/unsigned_integer.rb +15 -0
  202. data/lib/active_record/type/value.rb +110 -0
  203. data/lib/active_record/type.rb +23 -0
  204. data/lib/active_record/validations/associated.rb +24 -16
  205. data/lib/active_record/validations/presence.rb +67 -0
  206. data/lib/active_record/validations/uniqueness.rb +123 -64
  207. data/lib/active_record/validations.rb +36 -29
  208. data/lib/active_record/version.rb +5 -7
  209. data/lib/active_record.rb +66 -46
  210. data/lib/rails/generators/active_record/migration/migration_generator.rb +53 -8
  211. data/lib/rails/generators/active_record/{model/templates/migration.rb → migration/templates/create_table_migration.rb} +5 -1
  212. data/lib/rails/generators/active_record/migration/templates/migration.rb +20 -15
  213. data/lib/rails/generators/active_record/migration.rb +11 -8
  214. data/lib/rails/generators/active_record/model/model_generator.rb +9 -4
  215. data/lib/rails/generators/active_record/model/templates/model.rb +4 -6
  216. data/lib/rails/generators/active_record/model/templates/module.rb +1 -1
  217. data/lib/rails/generators/active_record.rb +3 -11
  218. metadata +101 -45
  219. data/examples/associations.png +0 -0
  220. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +0 -63
  221. data/lib/active_record/associations/join_helper.rb +0 -55
  222. data/lib/active_record/associations/preloader/has_and_belongs_to_many.rb +0 -60
  223. data/lib/active_record/attribute_methods/deprecated_underscore_read.rb +0 -32
  224. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +0 -191
  225. data/lib/active_record/connection_adapters/sqlite_adapter.rb +0 -583
  226. data/lib/active_record/dynamic_finder_match.rb +0 -68
  227. data/lib/active_record/dynamic_scope_match.rb +0 -23
  228. data/lib/active_record/fixtures/file.rb +0 -65
  229. data/lib/active_record/identity_map.rb +0 -162
  230. data/lib/active_record/observer.rb +0 -121
  231. data/lib/active_record/session_store.rb +0 -360
  232. data/lib/active_record/test_case.rb +0 -73
  233. data/lib/rails/generators/active_record/observer/observer_generator.rb +0 -15
  234. data/lib/rails/generators/active_record/observer/templates/observer.rb +0 -4
  235. data/lib/rails/generators/active_record/session_migration/session_migration_generator.rb +0 -25
  236. data/lib/rails/generators/active_record/session_migration/templates/migration.rb +0 -12
@@ -1,110 +1,190 @@
1
- require 'active_support/core_ext/class/attribute'
2
- require 'active_support/core_ext/object/blank'
1
+ require 'active_support/core_ext/module/attribute_accessors'
3
2
 
4
3
  module ActiveRecord
5
4
  module AttributeMethods
6
- module Dirty
5
+ module Dirty # :nodoc:
7
6
  extend ActiveSupport::Concern
7
+
8
8
  include ActiveModel::Dirty
9
- include AttributeMethods::Write
10
9
 
11
10
  included do
12
11
  if self < ::ActiveRecord::Timestamp
13
12
  raise "You cannot include Dirty after Timestamp"
14
13
  end
15
14
 
16
- class_attribute :partial_updates
17
- self.partial_updates = true
15
+ class_attribute :partial_writes, instance_writer: false
16
+ self.partial_writes = true
18
17
  end
19
18
 
20
19
  # Attempts to +save+ the record and clears changed attributes if successful.
21
- def save(*) #:nodoc:
20
+ def save(*)
22
21
  if status = super
23
- @previously_changed = changes
24
- @changed_attributes.clear
25
- elsif IdentityMap.enabled?
26
- IdentityMap.remove(self)
22
+ changes_applied
27
23
  end
28
24
  status
29
25
  end
30
26
 
31
27
  # Attempts to <tt>save!</tt> the record and clears changed attributes if successful.
32
- def save!(*) #:nodoc:
28
+ def save!(*)
33
29
  super.tap do
34
- @previously_changed = changes
35
- @changed_attributes.clear
30
+ changes_applied
36
31
  end
37
- rescue
38
- IdentityMap.remove(self) if IdentityMap.enabled?
39
- raise
40
32
  end
41
33
 
42
34
  # <tt>reload</tt> the record and clears changed attributes.
43
- def reload(*) #:nodoc:
35
+ def reload(*)
44
36
  super.tap do
45
- @previously_changed.clear
46
- @changed_attributes.clear
37
+ clear_changes_information
38
+ end
39
+ end
40
+
41
+ def initialize_dup(other) # :nodoc:
42
+ super
43
+ @original_raw_attributes = nil
44
+ calculate_changes_from_defaults
45
+ end
46
+
47
+ def changes_applied
48
+ super
49
+ store_original_raw_attributes
50
+ end
51
+
52
+ def clear_changes_information
53
+ super
54
+ original_raw_attributes.clear
55
+ end
56
+
57
+ def changed_attributes
58
+ # This should only be set by methods which will call changed_attributes
59
+ # multiple times when it is known that the computed value cannot change.
60
+ if defined?(@cached_changed_attributes)
61
+ @cached_changed_attributes
62
+ else
63
+ super.reverse_merge(attributes_changed_in_place).freeze
64
+ end
65
+ end
66
+
67
+ def changes
68
+ cache_changed_attributes do
69
+ super
70
+ end
71
+ end
72
+
73
+ def attribute_changed_in_place?(attr_name)
74
+ old_value = original_raw_attribute(attr_name)
75
+ @attributes[attr_name].changed_in_place_from?(old_value)
76
+ end
77
+
78
+ private
79
+
80
+ def changes_include?(attr_name)
81
+ super || attribute_changed_in_place?(attr_name)
82
+ end
83
+
84
+ def calculate_changes_from_defaults
85
+ @changed_attributes = nil
86
+ self.class.column_defaults.each do |attr, orig_value|
87
+ set_attribute_was(attr, orig_value) if _field_changed?(attr, orig_value)
47
88
  end
48
89
  end
49
90
 
50
- private
51
91
  # Wrap write_attribute to remember original attribute value.
52
92
  def write_attribute(attr, value)
53
93
  attr = attr.to_s
54
94
 
55
- # The attribute already has an unsaved change.
95
+ old_value = old_attribute_value(attr)
96
+
97
+ result = super
98
+ store_original_raw_attribute(attr)
99
+ save_changed_attribute(attr, old_value)
100
+ result
101
+ end
102
+
103
+ def raw_write_attribute(attr, value)
104
+ attr = attr.to_s
105
+
106
+ result = super
107
+ original_raw_attributes[attr] = value
108
+ result
109
+ end
110
+
111
+ def save_changed_attribute(attr, old_value)
112
+ clear_changed_attributes_cache
113
+ if attribute_changed_by_setter?(attr)
114
+ clear_attribute_changes(attr) unless _field_changed?(attr, old_value)
115
+ else
116
+ set_attribute_was(attr, old_value) if _field_changed?(attr, old_value)
117
+ end
118
+ end
119
+
120
+ def old_attribute_value(attr)
56
121
  if attribute_changed?(attr)
57
- old = @changed_attributes[attr]
58
- @changed_attributes.delete(attr) unless _field_changed?(attr, old, value)
122
+ changed_attributes[attr]
59
123
  else
60
- old = clone_attribute_value(:read_attribute, attr)
61
- # Save Time objects as TimeWithZone if time_zone_aware_attributes == true
62
- old = old.in_time_zone if clone_with_time_zone_conversion_attribute?(attr, old)
63
- @changed_attributes[attr] = old if _field_changed?(attr, old, value)
124
+ clone_attribute_value(:_read_attribute, attr)
64
125
  end
126
+ end
65
127
 
66
- # Carry on.
67
- super(attr, value)
128
+ def _update_record(*)
129
+ partial_writes? ? super(keys_for_partial_write) : super
68
130
  end
69
131
 
70
- def update(*)
71
- if partial_updates?
72
- # Serialized attributes should always be written in case they've been
73
- # changed in place.
74
- super(changed | (attributes.keys & self.class.serialized_attributes.keys))
75
- else
76
- super
132
+ def _create_record(*)
133
+ partial_writes? ? super(keys_for_partial_write) : super
134
+ end
135
+
136
+ # Serialized attributes should always be written in case they've been
137
+ # changed in place.
138
+ def keys_for_partial_write
139
+ changed & persistable_attribute_names
140
+ end
141
+
142
+ def _field_changed?(attr, old_value)
143
+ @attributes[attr].changed_from?(old_value)
144
+ end
145
+
146
+ def attributes_changed_in_place
147
+ changed_in_place.each_with_object({}) do |attr_name, h|
148
+ orig = @attributes[attr_name].original_value
149
+ h[attr_name] = orig
150
+ end
151
+ end
152
+
153
+ def changed_in_place
154
+ self.class.attribute_names.select do |attr_name|
155
+ attribute_changed_in_place?(attr_name)
77
156
  end
78
157
  end
79
158
 
80
- def _field_changed?(attr, old, value)
81
- if column = column_for_attribute(attr)
82
- if column.number? && (changes_from_nil_to_empty_string?(column, old, value) ||
83
- changes_from_zero_to_string?(old, value))
84
- value = nil
85
- else
86
- value = column.type_cast(value)
87
- end
159
+ def original_raw_attribute(attr_name)
160
+ original_raw_attributes.fetch(attr_name) do
161
+ read_attribute_before_type_cast(attr_name)
88
162
  end
163
+ end
89
164
 
90
- old != value
165
+ def original_raw_attributes
166
+ @original_raw_attributes ||= {}
91
167
  end
92
168
 
93
- def clone_with_time_zone_conversion_attribute?(attr, old)
94
- old.class.name == "Time" && time_zone_aware_attributes && !self.skip_time_zone_conversion_for_attributes.include?(attr.to_sym)
169
+ def store_original_raw_attribute(attr_name)
170
+ original_raw_attributes[attr_name] = @attributes[attr_name].value_for_database rescue nil
171
+ end
172
+
173
+ def store_original_raw_attributes
174
+ attribute_names.each do |attr|
175
+ store_original_raw_attribute(attr)
176
+ end
95
177
  end
96
178
 
97
- def changes_from_nil_to_empty_string?(column, old, value)
98
- # For nullable numeric columns, NULL gets stored in database for blank (i.e. '') values.
99
- # Hence we don't record it as a change if the value changes from nil to ''.
100
- # If an old value of 0 is set to '' we want this to get changed to nil as otherwise it'll
101
- # be typecast back to 0 (''.to_i => 0)
102
- column.null && (old.nil? || old == 0) && value.blank?
179
+ def cache_changed_attributes
180
+ @cached_changed_attributes = changed_attributes
181
+ yield
182
+ ensure
183
+ clear_changed_attributes_cache
103
184
  end
104
185
 
105
- def changes_from_zero_to_string?(old, value)
106
- # For columns with old 0 and value non-empty string
107
- old == 0 && value.is_a?(String) && value.present? && value != '0'
186
+ def clear_changed_attributes_cache
187
+ remove_instance_variable(:@cached_changed_attributes) if defined?(@cached_changed_attributes)
108
188
  end
109
189
  end
110
190
  end
@@ -1,56 +1,81 @@
1
+ require 'set'
2
+
1
3
  module ActiveRecord
2
4
  module AttributeMethods
3
5
  module PrimaryKey
4
6
  extend ActiveSupport::Concern
5
7
 
6
- # Returns this record's primary key value wrapped in an Array if one is available
8
+ # Returns this record's primary key value wrapped in an Array if one is
9
+ # available.
7
10
  def to_key
11
+ sync_with_transaction_state
8
12
  key = self.id
9
13
  [key] if key
10
14
  end
11
15
 
12
- # Returns the primary key value
16
+ # Returns the primary key value.
13
17
  def id
14
- read_attribute(self.class.primary_key)
18
+ if pk = self.class.primary_key
19
+ sync_with_transaction_state
20
+ _read_attribute(pk)
21
+ end
15
22
  end
16
23
 
17
- # Sets the primary key value
24
+ # Sets the primary key value.
18
25
  def id=(value)
19
- write_attribute(self.class.primary_key, value)
26
+ sync_with_transaction_state
27
+ write_attribute(self.class.primary_key, value) if self.class.primary_key
20
28
  end
21
29
 
22
- # Queries the primary key value
30
+ # Queries the primary key value.
23
31
  def id?
32
+ sync_with_transaction_state
24
33
  query_attribute(self.class.primary_key)
25
34
  end
26
35
 
36
+ # Returns the primary key value before type cast.
37
+ def id_before_type_cast
38
+ sync_with_transaction_state
39
+ read_attribute_before_type_cast(self.class.primary_key)
40
+ end
41
+
42
+ # Returns the primary key previous value.
43
+ def id_was
44
+ sync_with_transaction_state
45
+ attribute_was(self.class.primary_key)
46
+ end
47
+
48
+ protected
49
+
50
+ def attribute_method?(attr_name)
51
+ attr_name == 'id' || super
52
+ end
53
+
27
54
  module ClassMethods
28
55
  def define_method_attribute(attr_name)
29
56
  super
30
57
 
31
58
  if attr_name == primary_key && attr_name != 'id'
32
59
  generated_attribute_methods.send(:alias_method, :id, primary_key)
33
- generated_external_attribute_methods.module_eval <<-CODE, __FILE__, __LINE__
34
- def id(v, attributes, attributes_cache, attr_name)
35
- attr_name = '#{primary_key}'
36
- send(attr_name, attributes[attr_name], attributes, attributes_cache, attr_name)
37
- end
38
- CODE
39
60
  end
40
61
  end
41
62
 
63
+ ID_ATTRIBUTE_METHODS = %w(id id= id? id_before_type_cast id_was).to_set
64
+
42
65
  def dangerous_attribute_method?(method_name)
43
- super && !['id', 'id=', 'id?'].include?(method_name)
66
+ super && !ID_ATTRIBUTE_METHODS.include?(method_name)
44
67
  end
45
68
 
46
- # Defines the primary key field -- can be overridden in subclasses. Overwriting will negate any effect of the
47
- # primary_key_prefix_type setting, though.
69
+ # Defines the primary key field -- can be overridden in subclasses.
70
+ # Overwriting will negate any effect of the +primary_key_prefix_type+
71
+ # setting, though.
48
72
  def primary_key
49
73
  @primary_key = reset_primary_key unless defined? @primary_key
50
74
  @primary_key
51
75
  end
52
76
 
53
- # Returns a quoted version of the primary key name, used to construct SQL statements.
77
+ # Returns a quoted version of the primary key name, used to construct
78
+ # SQL statements.
54
79
  def quoted_primary_key
55
80
  @quoted_primary_key ||= connection.quote_column_name(primary_key)
56
81
  end
@@ -64,49 +89,38 @@ module ActiveRecord
64
89
  end
65
90
 
66
91
  def get_primary_key(base_name) #:nodoc:
67
- return 'id' unless base_name && !base_name.blank?
68
-
69
- case primary_key_prefix_type
70
- when :table_name
92
+ if base_name && primary_key_prefix_type == :table_name
71
93
  base_name.foreign_key(false)
72
- when :table_name_with_underscore
94
+ elsif base_name && primary_key_prefix_type == :table_name_with_underscore
73
95
  base_name.foreign_key
74
96
  else
75
97
  if ActiveRecord::Base != self && table_exists?
76
- connection.schema_cache.primary_keys[table_name]
98
+ connection.schema_cache.primary_keys(table_name)
77
99
  else
78
100
  'id'
79
101
  end
80
102
  end
81
103
  end
82
104
 
83
- def original_primary_key #:nodoc:
84
- deprecated_original_property_getter :primary_key
85
- end
86
-
87
105
  # Sets the name of the primary key column.
88
106
  #
89
107
  # class Project < ActiveRecord::Base
90
- # self.primary_key = "sysid"
108
+ # self.primary_key = 'sysid'
91
109
  # end
92
110
  #
93
- # You can also define the primary_key method yourself:
111
+ # You can also define the +primary_key+ method yourself:
94
112
  #
95
113
  # class Project < ActiveRecord::Base
96
114
  # def self.primary_key
97
- # "foo_" + super
115
+ # 'foo_' + super
98
116
  # end
99
117
  # end
118
+ #
100
119
  # Project.primary_key # => "foo_id"
101
120
  def primary_key=(value)
102
- @original_primary_key = @primary_key if defined?(@primary_key)
103
- @primary_key = value && value.to_s
104
- @quoted_primary_key = nil
105
- end
106
-
107
- def set_primary_key(value = nil, &block) #:nodoc:
108
- deprecated_property_setter :primary_key, value, block
121
+ @primary_key = value && value.to_s
109
122
  @quoted_primary_key = nil
123
+ @attributes_builder = nil
110
124
  end
111
125
  end
112
126
  end
@@ -1,5 +1,3 @@
1
- require 'active_support/core_ext/object/blank'
2
-
3
1
  module ActiveRecord
4
2
  module AttributeMethods
5
3
  module Query
@@ -10,8 +8,11 @@ module ActiveRecord
10
8
  end
11
9
 
12
10
  def query_attribute(attr_name)
13
- unless value = read_attribute(attr_name)
14
- false
11
+ value = self[attr_name]
12
+
13
+ case value
14
+ when true then true
15
+ when false, nil then false
15
16
  else
16
17
  column = self.class.columns_hash[attr_name]
17
18
  if column.nil?
@@ -1,136 +1,103 @@
1
+ require 'active_support/core_ext/module/method_transplanting'
2
+
1
3
  module ActiveRecord
2
4
  module AttributeMethods
3
5
  module Read
4
- extend ActiveSupport::Concern
5
-
6
- ATTRIBUTE_TYPES_CACHED_BY_DEFAULT = [:datetime, :timestamp, :time, :date]
7
-
8
- included do
9
- cattr_accessor :attribute_types_cached_by_default, :instance_writer => false
10
- self.attribute_types_cached_by_default = ATTRIBUTE_TYPES_CACHED_BY_DEFAULT
11
- end
12
-
13
- module ClassMethods
14
- # +cache_attributes+ allows you to declare which converted attribute values should
15
- # be cached. Usually caching only pays off for attributes with expensive conversion
16
- # methods, like time related columns (e.g. +created_at+, +updated_at+).
17
- def cache_attributes(*attribute_names)
18
- cached_attributes.merge attribute_names.map { |attr| attr.to_s }
19
- end
20
-
21
- # Returns the attributes which are cached. By default time related columns
22
- # with datatype <tt>:datetime, :timestamp, :time, :date</tt> are cached.
23
- def cached_attributes
24
- @cached_attributes ||= columns.select { |c| cacheable_column?(c) }.map { |col| col.name }.to_set
6
+ ReaderMethodCache = Class.new(AttributeMethodCache) {
7
+ private
8
+ # We want to generate the methods via module_eval rather than
9
+ # define_method, because define_method is slower on dispatch.
10
+ # Evaluating many similar methods may use more memory as the instruction
11
+ # sequences are duplicated and cached (in MRI). define_method may
12
+ # be slower on dispatch, but if you're careful about the closure
13
+ # created, then define_method will consume much less memory.
14
+ #
15
+ # But sometimes the database might return columns with
16
+ # characters that are not allowed in normal method names (like
17
+ # 'my_column(omg)'. So to work around this we first define with
18
+ # the __temp__ identifier, and then use alias method to rename
19
+ # it to what we want.
20
+ #
21
+ # We are also defining a constant to hold the frozen string of
22
+ # the attribute name. Using a constant means that we do not have
23
+ # to allocate an object on each call to the attribute method.
24
+ # Making it frozen means that it doesn't get duped when used to
25
+ # key the @attributes in read_attribute.
26
+ def method_body(method_name, const_name)
27
+ <<-EOMETHOD
28
+ def #{method_name}
29
+ name = ::ActiveRecord::AttributeMethods::AttrNames::ATTR_#{const_name}
30
+ _read_attribute(name) { |n| missing_attribute(n, caller) }
31
+ end
32
+ EOMETHOD
25
33
  end
34
+ }.new
26
35
 
27
- # Returns +true+ if the provided attribute is being cached.
28
- def cache_attribute?(attr_name)
29
- cached_attributes.include?(attr_name)
30
- end
36
+ extend ActiveSupport::Concern
31
37
 
32
- def undefine_attribute_methods
33
- generated_external_attribute_methods.module_eval do
34
- instance_methods.each { |m| undef_method(m) }
38
+ module ClassMethods
39
+ [:cache_attributes, :cached_attributes, :cache_attribute?].each do |method_name|
40
+ define_method method_name do |*|
41
+ cached_attributes_deprecation_warning(method_name)
42
+ true
35
43
  end
36
-
37
- super
38
44
  end
39
45
 
40
- def type_cast_attribute(attr_name, attributes, cache = {}) #:nodoc:
41
- return unless attr_name
42
- attr_name = attr_name.to_s
46
+ protected
43
47
 
44
- if generated_external_attribute_methods.method_defined?(attr_name)
45
- if attributes.has_key?(attr_name) || attr_name == 'id'
46
- generated_external_attribute_methods.send(attr_name, attributes[attr_name], attributes, cache, attr_name)
47
- end
48
- elsif !attribute_methods_generated?
49
- # If we haven't generated the caster methods yet, do that and
50
- # then try again
51
- define_attribute_methods
52
- type_cast_attribute(attr_name, attributes, cache)
53
- else
54
- # If we get here, the attribute has no associated DB column, so
55
- # just return it verbatim.
56
- attributes[attr_name]
57
- end
48
+ def cached_attributes_deprecation_warning(method_name)
49
+ ActiveSupport::Deprecation.warn "Calling `#{method_name}` is no longer necessary. All attributes are cached."
58
50
  end
59
51
 
60
- protected
61
- # We want to generate the methods via module_eval rather than define_method,
62
- # because define_method is slower on dispatch and uses more memory (because it
63
- # creates a closure).
64
- #
65
- # But sometimes the database might return columns with characters that are not
66
- # allowed in normal method names (like 'my_column(omg)'. So to work around this
67
- # we first define with the __temp__ identifier, and then use alias method to
68
- # rename it to what we want.
69
- def define_method_attribute(attr_name)
70
- generated_attribute_methods.module_eval <<-STR, __FILE__, __LINE__ + 1
71
- def __temp__
72
- #{internal_attribute_access_code(attr_name, attribute_cast_code(attr_name))}
73
- end
74
- alias_method '#{attr_name}', :__temp__
75
- undef_method :__temp__
76
- STR
52
+ if Module.methods_transplantable?
53
+ def define_method_attribute(name)
54
+ method = ReaderMethodCache[name]
55
+ generated_attribute_methods.module_eval { define_method name, method }
77
56
  end
57
+ else
58
+ def define_method_attribute(name)
59
+ safe_name = name.unpack('h*').first
60
+ temp_method = "__temp__#{safe_name}"
78
61
 
79
- private
62
+ ActiveRecord::AttributeMethods::AttrNames.set_name_cache safe_name, name
80
63
 
81
- def define_external_attribute_method(attr_name)
82
- generated_external_attribute_methods.module_eval <<-STR, __FILE__, __LINE__ + 1
83
- def __temp__(v, attributes, attributes_cache, attr_name)
84
- #{external_attribute_access_code(attr_name, attribute_cast_code(attr_name))}
64
+ generated_attribute_methods.module_eval <<-STR, __FILE__, __LINE__ + 1
65
+ def #{temp_method}
66
+ name = ::ActiveRecord::AttributeMethods::AttrNames::ATTR_#{safe_name}
67
+ _read_attribute(name) { |n| missing_attribute(n, caller) }
85
68
  end
86
- alias_method '#{attr_name}', :__temp__
87
- undef_method :__temp__
88
69
  STR
89
- end
90
-
91
- def cacheable_column?(column)
92
- attribute_types_cached_by_default.include?(column.type)
93
- end
94
-
95
- def internal_attribute_access_code(attr_name, cast_code)
96
- access_code = "(v=@attributes[attr_name]) && #{cast_code}"
97
70
 
98
- unless attr_name == primary_key
99
- access_code.insert(0, "missing_attribute(attr_name, caller) unless @attributes.has_key?(attr_name); ")
71
+ generated_attribute_methods.module_eval do
72
+ alias_method name, temp_method
73
+ undef_method temp_method
100
74
  end
101
-
102
- if cache_attribute?(attr_name)
103
- access_code = "@attributes_cache[attr_name] ||= (#{access_code})"
104
- end
105
-
106
- "attr_name = '#{attr_name}'; #{access_code}"
107
75
  end
76
+ end
77
+ end
108
78
 
109
- def external_attribute_access_code(attr_name, cast_code)
110
- access_code = "v && #{cast_code}"
111
-
112
- if cache_attribute?(attr_name)
113
- access_code = "attributes_cache[attr_name] ||= (#{access_code})"
114
- end
115
-
116
- access_code
117
- end
79
+ ID = 'id'.freeze
118
80
 
119
- def attribute_cast_code(attr_name)
120
- columns_hash[attr_name].type_cast_code('v')
121
- end
81
+ # Returns the value of the attribute identified by <tt>attr_name</tt> after
82
+ # it has been typecast (for example, "2004-12-12" in a date column is cast
83
+ # to a date object, like Date.new(2004, 12, 12)).
84
+ def read_attribute(attr_name, &block)
85
+ name = attr_name.to_s
86
+ name = self.class.primary_key if name == ID
87
+ _read_attribute(name, &block)
122
88
  end
123
89
 
124
- # Returns the value of the attribute identified by <tt>attr_name</tt> after it has been typecast (for example,
125
- # "2004-12-12" in a data column is cast to a date object, like Date.new(2004, 12, 12)).
126
- def read_attribute(attr_name)
127
- self.class.type_cast_attribute(attr_name, @attributes, @attributes_cache)
90
+ # This method exists to avoid the expensive primary_key check internally, without
91
+ # breaking compatibility with the read_attribute API
92
+ def _read_attribute(attr_name) # :nodoc:
93
+ @attributes.fetch_value(attr_name.to_s) { |n| yield n if block_given? }
128
94
  end
129
95
 
130
96
  private
131
- def attribute(attribute_name)
132
- read_attribute(attribute_name)
133
- end
97
+
98
+ def attribute(attribute_name)
99
+ _read_attribute(attribute_name)
100
+ end
134
101
  end
135
102
  end
136
103
  end