activerecord 3.1.10 → 4.2.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


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

Files changed (237) hide show
  1. checksums.yaml +6 -6
  2. data/CHANGELOG.md +1837 -338
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +39 -43
  5. data/examples/performance.rb +51 -20
  6. data/examples/simple.rb +4 -4
  7. data/lib/active_record/aggregations.rb +57 -43
  8. data/lib/active_record/association_relation.rb +35 -0
  9. data/lib/active_record/associations/alias_tracker.rb +47 -39
  10. data/lib/active_record/associations/association.rb +71 -85
  11. data/lib/active_record/associations/association_scope.rb +138 -89
  12. data/lib/active_record/associations/belongs_to_association.rb +65 -25
  13. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +9 -3
  14. data/lib/active_record/associations/builder/association.rb +125 -29
  15. data/lib/active_record/associations/builder/belongs_to.rb +91 -60
  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 -52
  20. data/lib/active_record/associations/builder/singular_association.rb +22 -29
  21. data/lib/active_record/associations/collection_association.rb +294 -187
  22. data/lib/active_record/associations/collection_proxy.rb +961 -94
  23. data/lib/active_record/associations/foreign_association.rb +11 -0
  24. data/lib/active_record/associations/has_many_association.rb +118 -23
  25. data/lib/active_record/associations/has_many_through_association.rb +115 -45
  26. data/lib/active_record/associations/has_one_association.rb +57 -24
  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 -102
  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 +61 -32
  38. data/lib/active_record/associations/preloader.rb +113 -87
  39. data/lib/active_record/associations/singular_association.rb +29 -13
  40. data/lib/active_record/associations/through_association.rb +37 -19
  41. data/lib/active_record/associations.rb +505 -371
  42. data/lib/active_record/attribute.rb +163 -0
  43. data/lib/active_record/attribute_assignment.rb +212 -0
  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 +141 -51
  47. data/lib/active_record/attribute_methods/primary_key.rb +87 -36
  48. data/lib/active_record/attribute_methods/query.rb +5 -4
  49. data/lib/active_record/attribute_methods/read.rb +74 -117
  50. data/lib/active_record/attribute_methods/serialization.rb +70 -0
  51. data/lib/active_record/attribute_methods/time_zone_conversion.rb +49 -47
  52. data/lib/active_record/attribute_methods/write.rb +60 -21
  53. data/lib/active_record/attribute_methods.rb +409 -48
  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 +279 -232
  58. data/lib/active_record/base.rb +84 -1969
  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 +422 -243
  63. data/lib/active_record/connection_adapters/abstract/database_limits.rb +9 -0
  64. data/lib/active_record/connection_adapters/abstract/database_statements.rb +170 -194
  65. data/lib/active_record/connection_adapters/abstract/query_cache.rb +32 -19
  66. data/lib/active_record/connection_adapters/abstract/quoting.rb +79 -57
  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 +273 -170
  70. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +50 -0
  71. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +731 -254
  72. data/lib/active_record/connection_adapters/abstract/transaction.rb +215 -0
  73. data/lib/active_record/connection_adapters/abstract_adapter.rb +339 -95
  74. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +946 -0
  75. data/lib/active_record/connection_adapters/column.rb +33 -221
  76. data/lib/active_record/connection_adapters/connection_specification.rb +275 -0
  77. data/lib/active_record/connection_adapters/mysql2_adapter.rb +140 -602
  78. data/lib/active_record/connection_adapters/mysql_adapter.rb +254 -756
  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 +445 -902
  114. data/lib/active_record/connection_adapters/schema_cache.rb +94 -0
  115. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +578 -25
  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 +159 -102
  119. data/lib/active_record/dynamic_matchers.rb +140 -0
  120. data/lib/active_record/enum.rb +197 -0
  121. data/lib/active_record/errors.rb +102 -34
  122. data/lib/active_record/explain.rb +38 -0
  123. data/lib/active_record/explain_registry.rb +30 -0
  124. data/lib/active_record/explain_subscriber.rb +29 -0
  125. data/lib/active_record/fixture_set/file.rb +56 -0
  126. data/lib/active_record/fixtures.rb +318 -260
  127. data/lib/active_record/gem_version.rb +15 -0
  128. data/lib/active_record/inheritance.rb +247 -0
  129. data/lib/active_record/integration.rb +113 -0
  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 +80 -52
  133. data/lib/active_record/locking/pessimistic.rb +27 -5
  134. data/lib/active_record/log_subscriber.rb +25 -18
  135. data/lib/active_record/migration/command_recorder.rb +130 -38
  136. data/lib/active_record/migration/join_table.rb +15 -0
  137. data/lib/active_record/migration.rb +532 -201
  138. data/lib/active_record/model_schema.rb +342 -0
  139. data/lib/active_record/nested_attributes.rb +229 -139
  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 +304 -99
  143. data/lib/active_record/query_cache.rb +25 -43
  144. data/lib/active_record/querying.rb +68 -0
  145. data/lib/active_record/railtie.rb +86 -45
  146. data/lib/active_record/railties/console_sandbox.rb +3 -4
  147. data/lib/active_record/railties/controller_runtime.rb +7 -4
  148. data/lib/active_record/railties/databases.rake +198 -377
  149. data/lib/active_record/railties/jdbcmysql_error.rb +2 -2
  150. data/lib/active_record/readonly_attributes.rb +23 -0
  151. data/lib/active_record/reflection.rb +516 -165
  152. data/lib/active_record/relation/batches.rb +96 -45
  153. data/lib/active_record/relation/calculations.rb +221 -144
  154. data/lib/active_record/relation/delegation.rb +140 -0
  155. data/lib/active_record/relation/finder_methods.rb +362 -243
  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 -41
  160. data/lib/active_record/relation/query_methods.rb +982 -155
  161. data/lib/active_record/relation/spawn_methods.rb +50 -110
  162. data/lib/active_record/relation.rb +371 -180
  163. data/lib/active_record/result.rb +109 -12
  164. data/lib/active_record/runtime_registry.rb +22 -0
  165. data/lib/active_record/sanitization.rb +191 -0
  166. data/lib/active_record/schema.rb +19 -13
  167. data/lib/active_record/schema_dumper.rb +111 -61
  168. data/lib/active_record/schema_migration.rb +53 -0
  169. data/lib/active_record/scoping/default.rb +135 -0
  170. data/lib/active_record/scoping/named.rb +164 -0
  171. data/lib/active_record/scoping.rb +87 -0
  172. data/lib/active_record/serialization.rb +7 -45
  173. data/lib/active_record/serializers/xml_serializer.rb +14 -65
  174. data/lib/active_record/statement_cache.rb +111 -0
  175. data/lib/active_record/store.rb +205 -0
  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 +35 -14
  181. data/lib/active_record/transactions.rb +141 -74
  182. data/lib/active_record/translation.rb +22 -0
  183. data/lib/active_record/type/big_integer.rb +13 -0
  184. data/lib/active_record/type/binary.rb +50 -0
  185. data/lib/active_record/type/boolean.rb +31 -0
  186. data/lib/active_record/type/date.rb +50 -0
  187. data/lib/active_record/type/date_time.rb +54 -0
  188. data/lib/active_record/type/decimal.rb +64 -0
  189. data/lib/active_record/type/decimal_without_scale.rb +11 -0
  190. data/lib/active_record/type/decorator.rb +14 -0
  191. data/lib/active_record/type/float.rb +19 -0
  192. data/lib/active_record/type/hash_lookup_type_map.rb +23 -0
  193. data/lib/active_record/type/integer.rb +59 -0
  194. data/lib/active_record/type/mutable.rb +16 -0
  195. data/lib/active_record/type/numeric.rb +36 -0
  196. data/lib/active_record/type/serialized.rb +62 -0
  197. data/lib/active_record/type/string.rb +40 -0
  198. data/lib/active_record/type/text.rb +11 -0
  199. data/lib/active_record/type/time.rb +26 -0
  200. data/lib/active_record/type/time_value.rb +38 -0
  201. data/lib/active_record/type/type_map.rb +64 -0
  202. data/lib/active_record/type/unsigned_integer.rb +15 -0
  203. data/lib/active_record/type/value.rb +110 -0
  204. data/lib/active_record/type.rb +23 -0
  205. data/lib/active_record/validations/associated.rb +27 -18
  206. data/lib/active_record/validations/presence.rb +67 -0
  207. data/lib/active_record/validations/uniqueness.rb +125 -66
  208. data/lib/active_record/validations.rb +37 -30
  209. data/lib/active_record/version.rb +5 -7
  210. data/lib/active_record.rb +80 -25
  211. data/lib/rails/generators/active_record/migration/migration_generator.rb +54 -9
  212. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb +19 -0
  213. data/lib/rails/generators/active_record/migration/templates/migration.rb +25 -11
  214. data/lib/rails/generators/active_record/migration.rb +11 -8
  215. data/lib/rails/generators/active_record/model/model_generator.rb +17 -4
  216. data/lib/rails/generators/active_record/model/templates/model.rb +5 -2
  217. data/lib/rails/generators/active_record/model/templates/module.rb +1 -1
  218. data/lib/rails/generators/active_record.rb +3 -11
  219. metadata +132 -53
  220. data/examples/associations.png +0 -0
  221. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +0 -62
  222. data/lib/active_record/associations/join_helper.rb +0 -55
  223. data/lib/active_record/associations/preloader/has_and_belongs_to_many.rb +0 -60
  224. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +0 -135
  225. data/lib/active_record/connection_adapters/sqlite_adapter.rb +0 -556
  226. data/lib/active_record/dynamic_finder_match.rb +0 -56
  227. data/lib/active_record/dynamic_scope_match.rb +0 -23
  228. data/lib/active_record/identity_map.rb +0 -163
  229. data/lib/active_record/named_scope.rb +0 -200
  230. data/lib/active_record/observer.rb +0 -121
  231. data/lib/active_record/session_store.rb +0 -358
  232. data/lib/active_record/test_case.rb +0 -69
  233. data/lib/rails/generators/active_record/model/templates/migration.rb +0 -17
  234. data/lib/rails/generators/active_record/observer/observer_generator.rb +0 -15
  235. data/lib/rails/generators/active_record/observer/templates/observer.rb +0 -4
  236. data/lib/rails/generators/active_record/session_migration/session_migration_generator.rb +0 -25
  237. data/lib/rails/generators/active_record/session_migration/templates/migration.rb +0 -16
@@ -1,100 +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
77
150
  end
78
151
  end
79
152
 
80
- def field_changed?(attr, old, value)
81
- if column = column_for_attribute(attr)
82
- if column.number? && column.null && (old.nil? || old == 0) && value.blank?
83
- # For nullable numeric columns, NULL gets stored in database for blank (i.e. '') values.
84
- # Hence we don't record it as a change if the value changes from nil to ''.
85
- # If an old value of 0 is set to '' we want this to get changed to nil as otherwise it'll
86
- # be typecast back to 0 (''.to_i => 0)
87
- value = nil
88
- else
89
- value = column.type_cast(value)
90
- end
153
+ def changed_in_place
154
+ self.class.attribute_names.select do |attr_name|
155
+ attribute_changed_in_place?(attr_name)
91
156
  end
157
+ end
158
+
159
+ def original_raw_attribute(attr_name)
160
+ original_raw_attributes.fetch(attr_name) do
161
+ read_attribute_before_type_cast(attr_name)
162
+ end
163
+ end
164
+
165
+ def original_raw_attributes
166
+ @original_raw_attributes ||= {}
167
+ end
168
+
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
177
+ end
92
178
 
93
- old != value
179
+ def cache_changed_attributes
180
+ @cached_changed_attributes = changed_attributes
181
+ yield
182
+ ensure
183
+ clear_changed_attributes_cache
94
184
  end
95
185
 
96
- def clone_with_time_zone_conversion_attribute?(attr, old)
97
- old.class.name == "Time" && time_zone_aware_attributes && !self.skip_time_zone_conversion_for_attributes.include?(attr.to_sym)
186
+ def clear_changed_attributes_cache
187
+ remove_instance_variable(:@cached_changed_attributes) if defined?(@cached_changed_attributes)
98
188
  end
99
189
  end
100
190
  end
@@ -1,75 +1,126 @@
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
8
- key = send(self.class.primary_key)
11
+ sync_with_transaction_state
12
+ key = self.id
9
13
  [key] if key
10
14
  end
11
15
 
16
+ # Returns the primary key value.
17
+ def id
18
+ if pk = self.class.primary_key
19
+ sync_with_transaction_state
20
+ _read_attribute(pk)
21
+ end
22
+ end
23
+
24
+ # Sets the primary key value.
25
+ def id=(value)
26
+ sync_with_transaction_state
27
+ write_attribute(self.class.primary_key, value) if self.class.primary_key
28
+ end
29
+
30
+ # Queries the primary key value.
31
+ def id?
32
+ sync_with_transaction_state
33
+ query_attribute(self.class.primary_key)
34
+ end
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
+
12
54
  module ClassMethods
13
- # Defines the primary key field -- can be overridden in subclasses. Overwriting will negate any effect of the
14
- # primary_key_prefix_type setting, though.
55
+ def define_method_attribute(attr_name)
56
+ super
57
+
58
+ if attr_name == primary_key && attr_name != 'id'
59
+ generated_attribute_methods.send(:alias_method, :id, primary_key)
60
+ end
61
+ end
62
+
63
+ ID_ATTRIBUTE_METHODS = %w(id id= id? id_before_type_cast id_was).to_set
64
+
65
+ def dangerous_attribute_method?(method_name)
66
+ super && !ID_ATTRIBUTE_METHODS.include?(method_name)
67
+ end
68
+
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.
15
72
  def primary_key
16
73
  @primary_key = reset_primary_key unless defined? @primary_key
17
74
  @primary_key
18
75
  end
19
76
 
20
- # 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.
21
79
  def quoted_primary_key
22
80
  @quoted_primary_key ||= connection.quote_column_name(primary_key)
23
81
  end
24
82
 
25
83
  def reset_primary_key #:nodoc:
26
- key = self == base_class ? get_primary_key(base_class.name) :
27
- base_class.primary_key
28
-
29
- set_primary_key(key)
30
- key
84
+ if self == base_class
85
+ self.primary_key = get_primary_key(base_class.name)
86
+ else
87
+ self.primary_key = base_class.primary_key
88
+ end
31
89
  end
32
90
 
33
91
  def get_primary_key(base_name) #:nodoc:
34
- return 'id' unless base_name && !base_name.blank?
35
-
36
- case primary_key_prefix_type
37
- when :table_name
92
+ if base_name && primary_key_prefix_type == :table_name
38
93
  base_name.foreign_key(false)
39
- when :table_name_with_underscore
94
+ elsif base_name && primary_key_prefix_type == :table_name_with_underscore
40
95
  base_name.foreign_key
41
96
  else
42
- if ActiveRecord::Base != self && connection.table_exists?(table_name)
43
- connection.primary_key(table_name)
97
+ if ActiveRecord::Base != self && table_exists?
98
+ connection.schema_cache.primary_keys(table_name)
44
99
  else
45
100
  'id'
46
101
  end
47
102
  end
48
103
  end
49
104
 
50
- attr_accessor :original_primary_key
51
-
52
- # Attribute writer for the primary key column
53
- def primary_key=(value)
54
- @quoted_primary_key = nil
55
- @primary_key = value
56
-
57
- connection_pool.primary_keys[table_name] = @primary_key if connected?
58
- end
59
-
60
- # Sets the name of the primary key column to use to the given value,
61
- # or (if the value is nil or false) to the value returned by the given
62
- # block.
105
+ # Sets the name of the primary key column.
63
106
  #
64
107
  # class Project < ActiveRecord::Base
65
- # set_primary_key "sysid"
108
+ # self.primary_key = 'sysid'
66
109
  # end
67
- def set_primary_key(value = nil, &block)
110
+ #
111
+ # You can also define the +primary_key+ method yourself:
112
+ #
113
+ # class Project < ActiveRecord::Base
114
+ # def self.primary_key
115
+ # 'foo_' + super
116
+ # end
117
+ # end
118
+ #
119
+ # Project.primary_key # => "foo_id"
120
+ def primary_key=(value)
121
+ @primary_key = value && value.to_s
68
122
  @quoted_primary_key = nil
69
- @primary_key ||= ''
70
- self.original_primary_key = @primary_key
71
- value &&= value.to_s
72
- self.primary_key = block_given? ? instance_eval(&block) : value
123
+ @attributes_builder = nil
73
124
  end
74
125
  end
75
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,146 +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
- attribute_method_suffix ""
10
-
11
- cattr_accessor :attribute_types_cached_by_default, :instance_writer => false
12
- self.attribute_types_cached_by_default = ATTRIBUTE_TYPES_CACHED_BY_DEFAULT
13
-
14
- # Undefine id so it can be used as an attribute name
15
- undef_method(:id) if method_defined?(:id)
16
- end
17
-
18
- module ClassMethods
19
- # +cache_attributes+ allows you to declare which converted attribute values should
20
- # be cached. Usually caching only pays off for attributes with expensive conversion
21
- # methods, like time related columns (e.g. +created_at+, +updated_at+).
22
- def cache_attributes(*attribute_names)
23
- cached_attributes.merge attribute_names.map { |attr| attr.to_s }
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
24
33
  end
34
+ }.new
25
35
 
26
- # Returns the attributes which are cached. By default time related columns
27
- # with datatype <tt>:datetime, :timestamp, :time, :date</tt> are cached.
28
- def cached_attributes
29
- @cached_attributes ||= columns.select { |c| cacheable_column?(c) }.map { |col| col.name }.to_set
30
- end
36
+ extend ActiveSupport::Concern
31
37
 
32
- # Returns +true+ if the provided attribute is being cached.
33
- def cache_attribute?(attr_name)
34
- cached_attributes.include?(attr_name)
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
43
+ end
35
44
  end
36
45
 
37
46
  protected
38
- def define_method_attribute(attr_name)
39
- if serialized_attributes.include?(attr_name)
40
- define_read_method_for_serialized_attribute(attr_name)
41
- else
42
- define_read_method(attr_name, attr_name, columns_hash[attr_name])
43
- end
44
47
 
45
- if attr_name == primary_key && attr_name != "id"
46
- define_read_method('id', attr_name, columns_hash[attr_name])
47
- end
48
- end
49
-
50
- private
51
- def cacheable_column?(column)
52
- serialized_attributes.include?(column.name) || attribute_types_cached_by_default.include?(column.type)
53
- end
48
+ def cached_attributes_deprecation_warning(method_name)
49
+ ActiveSupport::Deprecation.warn "Calling `#{method_name}` is no longer necessary. All attributes are cached."
50
+ end
54
51
 
55
- # Define read method for serialized attribute.
56
- def define_read_method_for_serialized_attribute(attr_name)
57
- access_code = "@attributes_cache['#{attr_name}'] ||= @attributes['#{attr_name}']"
58
- generated_attribute_methods.module_eval("def _#{attr_name}; #{access_code}; end; alias #{attr_name} _#{attr_name}", __FILE__, __LINE__)
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 }
59
56
  end
57
+ else
58
+ def define_method_attribute(name)
59
+ safe_name = name.unpack('h*').first
60
+ temp_method = "__temp__#{safe_name}"
60
61
 
61
- # Define an attribute reader method. Cope with nil column.
62
- # method_name is the same as attr_name except when a non-standard primary key is used,
63
- # we still define #id as an accessor for the key
64
- def define_read_method(method_name, attr_name, column)
65
- cast_code = column.type_cast_code('v')
66
- access_code = "(v=@attributes['#{attr_name}']) && #{cast_code}"
67
-
68
- unless attr_name.to_s == self.primary_key.to_s
69
- access_code.insert(0, "missing_attribute('#{attr_name}', caller) unless @attributes.has_key?('#{attr_name}'); ")
70
- end
71
-
72
- if cache_attribute?(attr_name)
73
- access_code = "@attributes_cache['#{attr_name}'] ||= (#{access_code})"
74
- end
75
-
76
- # Where possible, generate the method by evalling a string, as this will result in
77
- # faster accesses because it avoids the block eval and then string eval incurred
78
- # by the second branch.
79
- #
80
- # The second, slower, branch is necessary to support instances where the database
81
- # returns columns with extra stuff in (like 'my_column(omg)').
82
- if method_name =~ ActiveModel::AttributeMethods::COMPILABLE_REGEXP
83
- generated_attribute_methods.module_eval <<-STR, __FILE__, __LINE__
84
- def _#{method_name}
85
- #{access_code}
86
- end
62
+ ActiveRecord::AttributeMethods::AttrNames.set_name_cache safe_name, name
87
63
 
88
- alias #{method_name} _#{method_name}
89
- STR
90
- else
91
- generated_attribute_methods.module_eval do
92
- define_method("_#{method_name}") { eval(access_code) }
93
- alias_method(method_name, "_#{method_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) }
94
68
  end
95
- end
96
- end
97
- end
98
-
99
- # Returns the value of the attribute identified by <tt>attr_name</tt> after it has been typecast (for example,
100
- # "2004-12-12" in a data column is cast to a date object, like Date.new(2004, 12, 12)).
101
- def read_attribute(attr_name)
102
- method = "_#{attr_name}"
103
- if respond_to? method
104
- send method if @attributes.has_key?(attr_name.to_s)
105
- else
106
- _read_attribute attr_name
107
- end
108
- end
69
+ STR
109
70
 
110
- def _read_attribute(attr_name)
111
- attr_name = attr_name.to_s
112
- attr_name = self.class.primary_key if attr_name == 'id'
113
- value = @attributes[attr_name]
114
- unless value.nil?
115
- if column = column_for_attribute(attr_name)
116
- if unserializable_attribute?(attr_name, column)
117
- unserialize_attribute(attr_name)
118
- else
119
- column.type_cast(value)
71
+ generated_attribute_methods.module_eval do
72
+ alias_method name, temp_method
73
+ undef_method temp_method
120
74
  end
121
- else
122
- value
123
75
  end
124
76
  end
125
77
  end
126
78
 
127
- # Returns true if the attribute is of a text column and marked for serialization.
128
- def unserializable_attribute?(attr_name, column)
129
- column.text? && self.class.serialized_attributes.include?(attr_name)
130
- end
79
+ ID = 'id'.freeze
131
80
 
132
- # Returns the unserialized object of the attribute.
133
- def unserialize_attribute(attr_name)
134
- coder = self.class.serialized_attributes[attr_name]
135
- unserialized_object = coder.load(@attributes[attr_name])
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)
88
+ end
136
89
 
137
- @attributes.frozen? ? unserialized_object : @attributes[attr_name] = unserialized_object
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? }
138
94
  end
139
95
 
140
96
  private
141
- def attribute(attribute_name)
142
- read_attribute(attribute_name)
143
- end
97
+
98
+ def attribute(attribute_name)
99
+ _read_attribute(attribute_name)
100
+ end
144
101
  end
145
102
  end
146
103
  end