sskirby-activerecord 3.2.1

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.
Files changed (150) hide show
  1. data/CHANGELOG.md +6749 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.rdoc +222 -0
  4. data/examples/associations.png +0 -0
  5. data/examples/performance.rb +177 -0
  6. data/examples/simple.rb +14 -0
  7. data/lib/active_record.rb +147 -0
  8. data/lib/active_record/aggregations.rb +255 -0
  9. data/lib/active_record/associations.rb +1604 -0
  10. data/lib/active_record/associations/alias_tracker.rb +79 -0
  11. data/lib/active_record/associations/association.rb +239 -0
  12. data/lib/active_record/associations/association_scope.rb +119 -0
  13. data/lib/active_record/associations/belongs_to_association.rb +79 -0
  14. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +34 -0
  15. data/lib/active_record/associations/builder/association.rb +55 -0
  16. data/lib/active_record/associations/builder/belongs_to.rb +85 -0
  17. data/lib/active_record/associations/builder/collection_association.rb +75 -0
  18. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +57 -0
  19. data/lib/active_record/associations/builder/has_many.rb +71 -0
  20. data/lib/active_record/associations/builder/has_one.rb +62 -0
  21. data/lib/active_record/associations/builder/singular_association.rb +32 -0
  22. data/lib/active_record/associations/collection_association.rb +574 -0
  23. data/lib/active_record/associations/collection_proxy.rb +132 -0
  24. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +62 -0
  25. data/lib/active_record/associations/has_many_association.rb +108 -0
  26. data/lib/active_record/associations/has_many_through_association.rb +180 -0
  27. data/lib/active_record/associations/has_one_association.rb +73 -0
  28. data/lib/active_record/associations/has_one_through_association.rb +36 -0
  29. data/lib/active_record/associations/join_dependency.rb +214 -0
  30. data/lib/active_record/associations/join_dependency/join_association.rb +154 -0
  31. data/lib/active_record/associations/join_dependency/join_base.rb +24 -0
  32. data/lib/active_record/associations/join_dependency/join_part.rb +78 -0
  33. data/lib/active_record/associations/join_helper.rb +55 -0
  34. data/lib/active_record/associations/preloader.rb +177 -0
  35. data/lib/active_record/associations/preloader/association.rb +127 -0
  36. data/lib/active_record/associations/preloader/belongs_to.rb +17 -0
  37. data/lib/active_record/associations/preloader/collection_association.rb +24 -0
  38. data/lib/active_record/associations/preloader/has_and_belongs_to_many.rb +60 -0
  39. data/lib/active_record/associations/preloader/has_many.rb +17 -0
  40. data/lib/active_record/associations/preloader/has_many_through.rb +15 -0
  41. data/lib/active_record/associations/preloader/has_one.rb +23 -0
  42. data/lib/active_record/associations/preloader/has_one_through.rb +9 -0
  43. data/lib/active_record/associations/preloader/singular_association.rb +21 -0
  44. data/lib/active_record/associations/preloader/through_association.rb +67 -0
  45. data/lib/active_record/associations/singular_association.rb +64 -0
  46. data/lib/active_record/associations/through_association.rb +83 -0
  47. data/lib/active_record/attribute_assignment.rb +221 -0
  48. data/lib/active_record/attribute_methods.rb +272 -0
  49. data/lib/active_record/attribute_methods/before_type_cast.rb +31 -0
  50. data/lib/active_record/attribute_methods/deprecated_underscore_read.rb +32 -0
  51. data/lib/active_record/attribute_methods/dirty.rb +101 -0
  52. data/lib/active_record/attribute_methods/primary_key.rb +114 -0
  53. data/lib/active_record/attribute_methods/query.rb +39 -0
  54. data/lib/active_record/attribute_methods/read.rb +135 -0
  55. data/lib/active_record/attribute_methods/serialization.rb +93 -0
  56. data/lib/active_record/attribute_methods/time_zone_conversion.rb +62 -0
  57. data/lib/active_record/attribute_methods/write.rb +69 -0
  58. data/lib/active_record/autosave_association.rb +422 -0
  59. data/lib/active_record/base.rb +716 -0
  60. data/lib/active_record/callbacks.rb +275 -0
  61. data/lib/active_record/coders/yaml_column.rb +41 -0
  62. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +452 -0
  63. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +188 -0
  64. data/lib/active_record/connection_adapters/abstract/database_limits.rb +58 -0
  65. data/lib/active_record/connection_adapters/abstract/database_statements.rb +388 -0
  66. data/lib/active_record/connection_adapters/abstract/query_cache.rb +82 -0
  67. data/lib/active_record/connection_adapters/abstract/quoting.rb +115 -0
  68. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +492 -0
  69. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +598 -0
  70. data/lib/active_record/connection_adapters/abstract_adapter.rb +296 -0
  71. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +653 -0
  72. data/lib/active_record/connection_adapters/column.rb +270 -0
  73. data/lib/active_record/connection_adapters/mysql2_adapter.rb +288 -0
  74. data/lib/active_record/connection_adapters/mysql_adapter.rb +426 -0
  75. data/lib/active_record/connection_adapters/postgresql_adapter.rb +1261 -0
  76. data/lib/active_record/connection_adapters/schema_cache.rb +50 -0
  77. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +55 -0
  78. data/lib/active_record/connection_adapters/sqlite_adapter.rb +577 -0
  79. data/lib/active_record/connection_adapters/statement_pool.rb +40 -0
  80. data/lib/active_record/counter_cache.rb +119 -0
  81. data/lib/active_record/dynamic_finder_match.rb +56 -0
  82. data/lib/active_record/dynamic_matchers.rb +79 -0
  83. data/lib/active_record/dynamic_scope_match.rb +23 -0
  84. data/lib/active_record/errors.rb +195 -0
  85. data/lib/active_record/explain.rb +85 -0
  86. data/lib/active_record/explain_subscriber.rb +21 -0
  87. data/lib/active_record/fixtures.rb +906 -0
  88. data/lib/active_record/fixtures/file.rb +65 -0
  89. data/lib/active_record/identity_map.rb +156 -0
  90. data/lib/active_record/inheritance.rb +167 -0
  91. data/lib/active_record/integration.rb +49 -0
  92. data/lib/active_record/locale/en.yml +40 -0
  93. data/lib/active_record/locking/optimistic.rb +183 -0
  94. data/lib/active_record/locking/pessimistic.rb +77 -0
  95. data/lib/active_record/log_subscriber.rb +68 -0
  96. data/lib/active_record/migration.rb +765 -0
  97. data/lib/active_record/migration/command_recorder.rb +105 -0
  98. data/lib/active_record/model_schema.rb +366 -0
  99. data/lib/active_record/nested_attributes.rb +469 -0
  100. data/lib/active_record/observer.rb +121 -0
  101. data/lib/active_record/persistence.rb +372 -0
  102. data/lib/active_record/query_cache.rb +74 -0
  103. data/lib/active_record/querying.rb +58 -0
  104. data/lib/active_record/railtie.rb +119 -0
  105. data/lib/active_record/railties/console_sandbox.rb +6 -0
  106. data/lib/active_record/railties/controller_runtime.rb +49 -0
  107. data/lib/active_record/railties/databases.rake +620 -0
  108. data/lib/active_record/railties/jdbcmysql_error.rb +16 -0
  109. data/lib/active_record/readonly_attributes.rb +26 -0
  110. data/lib/active_record/reflection.rb +534 -0
  111. data/lib/active_record/relation.rb +534 -0
  112. data/lib/active_record/relation/batches.rb +90 -0
  113. data/lib/active_record/relation/calculations.rb +354 -0
  114. data/lib/active_record/relation/delegation.rb +49 -0
  115. data/lib/active_record/relation/finder_methods.rb +398 -0
  116. data/lib/active_record/relation/predicate_builder.rb +58 -0
  117. data/lib/active_record/relation/query_methods.rb +417 -0
  118. data/lib/active_record/relation/spawn_methods.rb +148 -0
  119. data/lib/active_record/result.rb +34 -0
  120. data/lib/active_record/sanitization.rb +194 -0
  121. data/lib/active_record/schema.rb +58 -0
  122. data/lib/active_record/schema_dumper.rb +204 -0
  123. data/lib/active_record/scoping.rb +152 -0
  124. data/lib/active_record/scoping/default.rb +142 -0
  125. data/lib/active_record/scoping/named.rb +202 -0
  126. data/lib/active_record/serialization.rb +18 -0
  127. data/lib/active_record/serializers/xml_serializer.rb +202 -0
  128. data/lib/active_record/session_store.rb +358 -0
  129. data/lib/active_record/store.rb +50 -0
  130. data/lib/active_record/test_case.rb +73 -0
  131. data/lib/active_record/timestamp.rb +113 -0
  132. data/lib/active_record/transactions.rb +360 -0
  133. data/lib/active_record/translation.rb +22 -0
  134. data/lib/active_record/validations.rb +83 -0
  135. data/lib/active_record/validations/associated.rb +43 -0
  136. data/lib/active_record/validations/uniqueness.rb +180 -0
  137. data/lib/active_record/version.rb +10 -0
  138. data/lib/rails/generators/active_record.rb +25 -0
  139. data/lib/rails/generators/active_record/migration.rb +15 -0
  140. data/lib/rails/generators/active_record/migration/migration_generator.rb +25 -0
  141. data/lib/rails/generators/active_record/migration/templates/migration.rb +31 -0
  142. data/lib/rails/generators/active_record/model/model_generator.rb +43 -0
  143. data/lib/rails/generators/active_record/model/templates/migration.rb +15 -0
  144. data/lib/rails/generators/active_record/model/templates/model.rb +7 -0
  145. data/lib/rails/generators/active_record/model/templates/module.rb +7 -0
  146. data/lib/rails/generators/active_record/observer/observer_generator.rb +15 -0
  147. data/lib/rails/generators/active_record/observer/templates/observer.rb +4 -0
  148. data/lib/rails/generators/active_record/session_migration/session_migration_generator.rb +25 -0
  149. data/lib/rails/generators/active_record/session_migration/templates/migration.rb +12 -0
  150. metadata +242 -0
@@ -0,0 +1,272 @@
1
+ require 'active_support/core_ext/enumerable'
2
+ require 'active_support/deprecation'
3
+
4
+ module ActiveRecord
5
+ # = Active Record Attribute Methods
6
+ module AttributeMethods #:nodoc:
7
+ extend ActiveSupport::Concern
8
+ include ActiveModel::AttributeMethods
9
+
10
+ included do
11
+ include Read
12
+ include Write
13
+ include BeforeTypeCast
14
+ include Query
15
+ include PrimaryKey
16
+ include TimeZoneConversion
17
+ include Dirty
18
+ include Serialization
19
+ include DeprecatedUnderscoreRead
20
+
21
+ # Returns the value of the attribute identified by <tt>attr_name</tt> after it has been typecast (for example,
22
+ # "2004-12-12" in a data column is cast to a date object, like Date.new(2004, 12, 12)).
23
+ # (Alias for the protected read_attribute method).
24
+ def [](attr_name)
25
+ read_attribute(attr_name)
26
+ end
27
+
28
+ # Updates the attribute identified by <tt>attr_name</tt> with the specified +value+.
29
+ # (Alias for the protected write_attribute method).
30
+ def []=(attr_name, value)
31
+ write_attribute(attr_name, value)
32
+ end
33
+ end
34
+
35
+ module ClassMethods
36
+ # Generates all the attribute related methods for columns in the database
37
+ # accessors, mutators and query methods.
38
+ def define_attribute_methods
39
+ unless defined?(@attribute_methods_mutex)
40
+ msg = "It looks like something (probably a gem/plugin) is overriding the " \
41
+ "ActiveRecord::Base.inherited method. It is important that this hook executes so " \
42
+ "that your models are set up correctly. A workaround has been added to stop this " \
43
+ "causing an error in 3.2, but future versions will simply not work if the hook is " \
44
+ "overridden. If you are using Kaminari, please upgrade as it is known to have had " \
45
+ "this problem.\n\n"
46
+ msg << "The following may help track down the problem:"
47
+
48
+ meth = method(:inherited)
49
+ if meth.respond_to?(:source_location)
50
+ msg << " #{meth.source_location.inspect}"
51
+ else
52
+ msg << " #{meth.inspect}"
53
+ end
54
+ msg << "\n\n"
55
+
56
+ ActiveSupport::Deprecation.warn(msg)
57
+
58
+ @attribute_methods_mutex = Mutex.new
59
+ end
60
+
61
+ # Use a mutex; we don't want two thread simaltaneously trying to define
62
+ # attribute methods.
63
+ @attribute_methods_mutex.synchronize do
64
+ return if attribute_methods_generated?
65
+ superclass.define_attribute_methods unless self == base_class
66
+ super(column_names)
67
+ @attribute_methods_generated = true
68
+ end
69
+ end
70
+
71
+ def attribute_methods_generated?
72
+ @attribute_methods_generated ||= false
73
+ end
74
+
75
+ # We will define the methods as instance methods, but will call them as singleton
76
+ # methods. This allows us to use method_defined? to check if the method exists,
77
+ # which is fast and won't give any false positives from the ancestors (because
78
+ # there are no ancestors).
79
+ def generated_external_attribute_methods
80
+ @generated_external_attribute_methods ||= Module.new { extend self }
81
+ end
82
+
83
+ def undefine_attribute_methods
84
+ super
85
+ @attribute_methods_generated = false
86
+ end
87
+
88
+ def instance_method_already_implemented?(method_name)
89
+ if dangerous_attribute_method?(method_name)
90
+ raise DangerousAttributeError, "#{method_name} is defined by ActiveRecord"
91
+ end
92
+
93
+ if superclass == Base
94
+ super
95
+ else
96
+ # If B < A and A defines its own attribute method, then we don't want to overwrite that.
97
+ defined = method_defined_within?(method_name, superclass, superclass.generated_attribute_methods)
98
+ defined && !ActiveRecord::Base.method_defined?(method_name) || super
99
+ end
100
+ end
101
+
102
+ # A method name is 'dangerous' if it is already defined by Active Record, but
103
+ # not by any ancestors. (So 'puts' is not dangerous but 'save' is.)
104
+ def dangerous_attribute_method?(name)
105
+ method_defined_within?(name, Base)
106
+ end
107
+
108
+ def method_defined_within?(name, klass, sup = klass.superclass)
109
+ if klass.method_defined?(name) || klass.private_method_defined?(name)
110
+ if sup.method_defined?(name) || sup.private_method_defined?(name)
111
+ klass.instance_method(name).owner != sup.instance_method(name).owner
112
+ else
113
+ true
114
+ end
115
+ else
116
+ false
117
+ end
118
+ end
119
+
120
+ def attribute_method?(attribute)
121
+ super || (table_exists? && column_names.include?(attribute.to_s.sub(/=$/, '')))
122
+ end
123
+
124
+ # Returns an array of column names as strings if it's not
125
+ # an abstract class and table exists.
126
+ # Otherwise it returns an empty array.
127
+ def attribute_names
128
+ @attribute_names ||= if !abstract_class? && table_exists?
129
+ column_names
130
+ else
131
+ []
132
+ end
133
+ end
134
+ end
135
+
136
+ # If we haven't generated any methods yet, generate them, then
137
+ # see if we've created the method we're looking for.
138
+ def method_missing(method, *args, &block)
139
+ unless self.class.attribute_methods_generated?
140
+ self.class.define_attribute_methods
141
+
142
+ if respond_to_without_attributes?(method)
143
+ send(method, *args, &block)
144
+ else
145
+ super
146
+ end
147
+ else
148
+ super
149
+ end
150
+ end
151
+
152
+ def attribute_missing(match, *args, &block)
153
+ if self.class.columns_hash[match.attr_name]
154
+ ActiveSupport::Deprecation.warn(
155
+ "The method `#{match.method_name}', matching the attribute `#{match.attr_name}' has " \
156
+ "dispatched through method_missing. This shouldn't happen, because `#{match.attr_name}' " \
157
+ "is a column of the table. If this error has happened through normal usage of Active " \
158
+ "Record (rather than through your own code or external libraries), please report it as " \
159
+ "a bug."
160
+ )
161
+ end
162
+
163
+ super
164
+ end
165
+
166
+ def respond_to?(name, include_private = false)
167
+ self.class.define_attribute_methods unless self.class.attribute_methods_generated?
168
+ super
169
+ end
170
+
171
+ # Returns true if the given attribute is in the attributes hash
172
+ def has_attribute?(attr_name)
173
+ @attributes.has_key?(attr_name.to_s)
174
+ end
175
+
176
+ # Returns an array of names for the attributes available on this object.
177
+ def attribute_names
178
+ @attributes.keys
179
+ end
180
+
181
+ # Returns a hash of all the attributes with their names as keys and the values of the attributes as values.
182
+ def attributes
183
+ Hash[@attributes.map { |name, _| [name, read_attribute(name)] }]
184
+ end
185
+
186
+ # Returns an <tt>#inspect</tt>-like string for the value of the
187
+ # attribute +attr_name+. String attributes are truncated upto 50
188
+ # characters, and Date and Time attributes are returned in the
189
+ # <tt>:db</tt> format. Other attributes return the value of
190
+ # <tt>#inspect</tt> without modification.
191
+ #
192
+ # person = Person.create!(:name => "David Heinemeier Hansson " * 3)
193
+ #
194
+ # person.attribute_for_inspect(:name)
195
+ # # => '"David Heinemeier Hansson David Heinemeier Hansson D..."'
196
+ #
197
+ # person.attribute_for_inspect(:created_at)
198
+ # # => '"2009-01-12 04:48:57"'
199
+ def attribute_for_inspect(attr_name)
200
+ value = read_attribute(attr_name)
201
+
202
+ if value.is_a?(String) && value.length > 50
203
+ "#{value[0..50]}...".inspect
204
+ elsif value.is_a?(Date) || value.is_a?(Time)
205
+ %("#{value.to_s(:db)}")
206
+ else
207
+ value.inspect
208
+ end
209
+ end
210
+
211
+ # Returns true if the specified +attribute+ has been set by the user or by a database load and is neither
212
+ # nil nor empty? (the latter only applies to objects that respond to empty?, most notably Strings).
213
+ def attribute_present?(attribute)
214
+ value = read_attribute(attribute)
215
+ !value.nil? || (value.respond_to?(:empty?) && !value.empty?)
216
+ end
217
+
218
+ # Returns the column object for the named attribute.
219
+ def column_for_attribute(name)
220
+ self.class.columns_hash[name.to_s]
221
+ end
222
+
223
+ protected
224
+
225
+ def clone_attributes(reader_method = :read_attribute, attributes = {})
226
+ attribute_names.each do |name|
227
+ attributes[name] = clone_attribute_value(reader_method, name)
228
+ end
229
+ attributes
230
+ end
231
+
232
+ def clone_attribute_value(reader_method, attribute_name)
233
+ value = send(reader_method, attribute_name)
234
+ value.duplicable? ? value.clone : value
235
+ rescue TypeError, NoMethodError
236
+ value
237
+ end
238
+
239
+ # Returns a copy of the attributes hash where all the values have been safely quoted for use in
240
+ # an Arel insert/update method.
241
+ def arel_attributes_values(include_primary_key = true, include_readonly_attributes = true, attribute_names = @attributes.keys)
242
+ attrs = {}
243
+ klass = self.class
244
+ arel_table = klass.arel_table
245
+
246
+ attribute_names.each do |name|
247
+ if (column = column_for_attribute(name)) && (include_primary_key || !column.primary)
248
+
249
+ if include_readonly_attributes || !self.class.readonly_attributes.include?(name)
250
+
251
+ value = if klass.serialized_attributes.include?(name)
252
+ @attributes[name].serialized_value
253
+ else
254
+ # FIXME: we need @attributes to be used consistently.
255
+ # If the values stored in @attributes were already type
256
+ # casted, this code could be simplified
257
+ read_attribute(name)
258
+ end
259
+
260
+ attrs[arel_table[name]] = value
261
+ end
262
+ end
263
+ end
264
+
265
+ attrs
266
+ end
267
+
268
+ def attribute_method?(attr_name)
269
+ attr_name == 'id' || (defined?(@attributes) && @attributes.include?(attr_name))
270
+ end
271
+ end
272
+ end
@@ -0,0 +1,31 @@
1
+ module ActiveRecord
2
+ module AttributeMethods
3
+ module BeforeTypeCast
4
+ extend ActiveSupport::Concern
5
+
6
+ included do
7
+ attribute_method_suffix "_before_type_cast"
8
+ end
9
+
10
+ def read_attribute_before_type_cast(attr_name)
11
+ @attributes[attr_name]
12
+ end
13
+
14
+ # Returns a hash of attributes before typecasting and deserialization.
15
+ def attributes_before_type_cast
16
+ @attributes
17
+ end
18
+
19
+ private
20
+
21
+ # Handle *_before_type_cast for method_missing.
22
+ def attribute_before_type_cast(attribute_name)
23
+ if attribute_name == 'id'
24
+ read_attribute_before_type_cast(self.class.primary_key)
25
+ else
26
+ read_attribute_before_type_cast(attribute_name)
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,32 @@
1
+ require 'active_support/concern'
2
+ require 'active_support/deprecation'
3
+
4
+ module ActiveRecord
5
+ module AttributeMethods
6
+ module DeprecatedUnderscoreRead
7
+ extend ActiveSupport::Concern
8
+
9
+ included do
10
+ attribute_method_prefix "_"
11
+ end
12
+
13
+ module ClassMethods
14
+ protected
15
+
16
+ def define_method__attribute(attr_name)
17
+ # Do nothing, let it hit method missing instead.
18
+ end
19
+ end
20
+
21
+ protected
22
+
23
+ def _attribute(attr_name)
24
+ ActiveSupport::Deprecation.warn(
25
+ "You have called '_#{attr_name}'. This is deprecated. Please use " \
26
+ "either '#{attr_name}' or read_attribute('#{attr_name}')."
27
+ )
28
+ read_attribute(attr_name)
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,101 @@
1
+ require 'active_support/core_ext/class/attribute'
2
+ require 'active_support/core_ext/object/blank'
3
+
4
+ module ActiveRecord
5
+ module AttributeMethods
6
+ module Dirty
7
+ extend ActiveSupport::Concern
8
+ include ActiveModel::Dirty
9
+ include AttributeMethods::Write
10
+
11
+ included do
12
+ if self < ::ActiveRecord::Timestamp
13
+ raise "You cannot include Dirty after Timestamp"
14
+ end
15
+
16
+ class_attribute :partial_updates
17
+ self.partial_updates = true
18
+ end
19
+
20
+ # Attempts to +save+ the record and clears changed attributes if successful.
21
+ def save(*) #:nodoc:
22
+ if status = super
23
+ @previously_changed = changes
24
+ @changed_attributes.clear
25
+ elsif IdentityMap.enabled?
26
+ IdentityMap.remove(self)
27
+ end
28
+ status
29
+ end
30
+
31
+ # Attempts to <tt>save!</tt> the record and clears changed attributes if successful.
32
+ def save!(*) #:nodoc:
33
+ super.tap do
34
+ @previously_changed = changes
35
+ @changed_attributes.clear
36
+ end
37
+ rescue
38
+ IdentityMap.remove(self) if IdentityMap.enabled?
39
+ raise
40
+ end
41
+
42
+ # <tt>reload</tt> the record and clears changed attributes.
43
+ def reload(*) #:nodoc:
44
+ super.tap do
45
+ @previously_changed.clear
46
+ @changed_attributes.clear
47
+ end
48
+ end
49
+
50
+ private
51
+ # Wrap write_attribute to remember original attribute value.
52
+ def write_attribute(attr, value)
53
+ attr = attr.to_s
54
+
55
+ # The attribute already has an unsaved change.
56
+ if attribute_changed?(attr)
57
+ old = @changed_attributes[attr]
58
+ @changed_attributes.delete(attr) unless field_changed?(attr, old, value)
59
+ 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)
64
+ end
65
+
66
+ # Carry on.
67
+ super(attr, value)
68
+ end
69
+
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
77
+ end
78
+ end
79
+
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
91
+ end
92
+
93
+ old != value
94
+ end
95
+
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)
98
+ end
99
+ end
100
+ end
101
+ end
@@ -0,0 +1,114 @@
1
+ module ActiveRecord
2
+ module AttributeMethods
3
+ module PrimaryKey
4
+ extend ActiveSupport::Concern
5
+
6
+ # Returns this record's primary key value wrapped in an Array if one is available
7
+ def to_key
8
+ key = self.id
9
+ [key] if key
10
+ end
11
+
12
+ # Returns the primary key value
13
+ def id
14
+ read_attribute(self.class.primary_key)
15
+ end
16
+
17
+ # Sets the primary key value
18
+ def id=(value)
19
+ write_attribute(self.class.primary_key, value)
20
+ end
21
+
22
+ # Queries the primary key value
23
+ def id?
24
+ query_attribute(self.class.primary_key)
25
+ end
26
+
27
+ module ClassMethods
28
+ def define_method_attribute(attr_name)
29
+ super
30
+
31
+ if attr_name == primary_key && attr_name != 'id'
32
+ 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
+ end
40
+ end
41
+
42
+ def dangerous_attribute_method?(method_name)
43
+ super && !['id', 'id=', 'id?'].include?(method_name)
44
+ end
45
+
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.
48
+ def primary_key
49
+ @primary_key = reset_primary_key unless defined? @primary_key
50
+ @primary_key
51
+ end
52
+
53
+ # Returns a quoted version of the primary key name, used to construct SQL statements.
54
+ def quoted_primary_key
55
+ @quoted_primary_key ||= connection.quote_column_name(primary_key)
56
+ end
57
+
58
+ def reset_primary_key #:nodoc:
59
+ if self == base_class
60
+ self.primary_key = get_primary_key(base_class.name)
61
+ else
62
+ self.primary_key = base_class.primary_key
63
+ end
64
+ end
65
+
66
+ 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
71
+ base_name.foreign_key(false)
72
+ when :table_name_with_underscore
73
+ base_name.foreign_key
74
+ else
75
+ if ActiveRecord::Base != self && table_exists?
76
+ connection.schema_cache.primary_keys[table_name]
77
+ else
78
+ 'id'
79
+ end
80
+ end
81
+ end
82
+
83
+ def original_primary_key #:nodoc:
84
+ deprecated_original_property_getter :primary_key
85
+ end
86
+
87
+ # Sets the name of the primary key column.
88
+ #
89
+ # class Project < ActiveRecord::Base
90
+ # self.primary_key = "sysid"
91
+ # end
92
+ #
93
+ # You can also define the primary_key method yourself:
94
+ #
95
+ # class Project < ActiveRecord::Base
96
+ # def self.primary_key
97
+ # "foo_" + super
98
+ # end
99
+ # end
100
+ # Project.primary_key # => "foo_id"
101
+ 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
109
+ @quoted_primary_key = nil
110
+ end
111
+ end
112
+ end
113
+ end
114
+ end