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.
- data/CHANGELOG.md +6749 -0
- data/MIT-LICENSE +20 -0
- data/README.rdoc +222 -0
- data/examples/associations.png +0 -0
- data/examples/performance.rb +177 -0
- data/examples/simple.rb +14 -0
- data/lib/active_record.rb +147 -0
- data/lib/active_record/aggregations.rb +255 -0
- data/lib/active_record/associations.rb +1604 -0
- data/lib/active_record/associations/alias_tracker.rb +79 -0
- data/lib/active_record/associations/association.rb +239 -0
- data/lib/active_record/associations/association_scope.rb +119 -0
- data/lib/active_record/associations/belongs_to_association.rb +79 -0
- data/lib/active_record/associations/belongs_to_polymorphic_association.rb +34 -0
- data/lib/active_record/associations/builder/association.rb +55 -0
- data/lib/active_record/associations/builder/belongs_to.rb +85 -0
- data/lib/active_record/associations/builder/collection_association.rb +75 -0
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +57 -0
- data/lib/active_record/associations/builder/has_many.rb +71 -0
- data/lib/active_record/associations/builder/has_one.rb +62 -0
- data/lib/active_record/associations/builder/singular_association.rb +32 -0
- data/lib/active_record/associations/collection_association.rb +574 -0
- data/lib/active_record/associations/collection_proxy.rb +132 -0
- data/lib/active_record/associations/has_and_belongs_to_many_association.rb +62 -0
- data/lib/active_record/associations/has_many_association.rb +108 -0
- data/lib/active_record/associations/has_many_through_association.rb +180 -0
- data/lib/active_record/associations/has_one_association.rb +73 -0
- data/lib/active_record/associations/has_one_through_association.rb +36 -0
- data/lib/active_record/associations/join_dependency.rb +214 -0
- data/lib/active_record/associations/join_dependency/join_association.rb +154 -0
- data/lib/active_record/associations/join_dependency/join_base.rb +24 -0
- data/lib/active_record/associations/join_dependency/join_part.rb +78 -0
- data/lib/active_record/associations/join_helper.rb +55 -0
- data/lib/active_record/associations/preloader.rb +177 -0
- data/lib/active_record/associations/preloader/association.rb +127 -0
- data/lib/active_record/associations/preloader/belongs_to.rb +17 -0
- data/lib/active_record/associations/preloader/collection_association.rb +24 -0
- data/lib/active_record/associations/preloader/has_and_belongs_to_many.rb +60 -0
- data/lib/active_record/associations/preloader/has_many.rb +17 -0
- data/lib/active_record/associations/preloader/has_many_through.rb +15 -0
- data/lib/active_record/associations/preloader/has_one.rb +23 -0
- data/lib/active_record/associations/preloader/has_one_through.rb +9 -0
- data/lib/active_record/associations/preloader/singular_association.rb +21 -0
- data/lib/active_record/associations/preloader/through_association.rb +67 -0
- data/lib/active_record/associations/singular_association.rb +64 -0
- data/lib/active_record/associations/through_association.rb +83 -0
- data/lib/active_record/attribute_assignment.rb +221 -0
- data/lib/active_record/attribute_methods.rb +272 -0
- data/lib/active_record/attribute_methods/before_type_cast.rb +31 -0
- data/lib/active_record/attribute_methods/deprecated_underscore_read.rb +32 -0
- data/lib/active_record/attribute_methods/dirty.rb +101 -0
- data/lib/active_record/attribute_methods/primary_key.rb +114 -0
- data/lib/active_record/attribute_methods/query.rb +39 -0
- data/lib/active_record/attribute_methods/read.rb +135 -0
- data/lib/active_record/attribute_methods/serialization.rb +93 -0
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +62 -0
- data/lib/active_record/attribute_methods/write.rb +69 -0
- data/lib/active_record/autosave_association.rb +422 -0
- data/lib/active_record/base.rb +716 -0
- data/lib/active_record/callbacks.rb +275 -0
- data/lib/active_record/coders/yaml_column.rb +41 -0
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +452 -0
- data/lib/active_record/connection_adapters/abstract/connection_specification.rb +188 -0
- data/lib/active_record/connection_adapters/abstract/database_limits.rb +58 -0
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +388 -0
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +82 -0
- data/lib/active_record/connection_adapters/abstract/quoting.rb +115 -0
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +492 -0
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +598 -0
- data/lib/active_record/connection_adapters/abstract_adapter.rb +296 -0
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +653 -0
- data/lib/active_record/connection_adapters/column.rb +270 -0
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +288 -0
- data/lib/active_record/connection_adapters/mysql_adapter.rb +426 -0
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +1261 -0
- data/lib/active_record/connection_adapters/schema_cache.rb +50 -0
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +55 -0
- data/lib/active_record/connection_adapters/sqlite_adapter.rb +577 -0
- data/lib/active_record/connection_adapters/statement_pool.rb +40 -0
- data/lib/active_record/counter_cache.rb +119 -0
- data/lib/active_record/dynamic_finder_match.rb +56 -0
- data/lib/active_record/dynamic_matchers.rb +79 -0
- data/lib/active_record/dynamic_scope_match.rb +23 -0
- data/lib/active_record/errors.rb +195 -0
- data/lib/active_record/explain.rb +85 -0
- data/lib/active_record/explain_subscriber.rb +21 -0
- data/lib/active_record/fixtures.rb +906 -0
- data/lib/active_record/fixtures/file.rb +65 -0
- data/lib/active_record/identity_map.rb +156 -0
- data/lib/active_record/inheritance.rb +167 -0
- data/lib/active_record/integration.rb +49 -0
- data/lib/active_record/locale/en.yml +40 -0
- data/lib/active_record/locking/optimistic.rb +183 -0
- data/lib/active_record/locking/pessimistic.rb +77 -0
- data/lib/active_record/log_subscriber.rb +68 -0
- data/lib/active_record/migration.rb +765 -0
- data/lib/active_record/migration/command_recorder.rb +105 -0
- data/lib/active_record/model_schema.rb +366 -0
- data/lib/active_record/nested_attributes.rb +469 -0
- data/lib/active_record/observer.rb +121 -0
- data/lib/active_record/persistence.rb +372 -0
- data/lib/active_record/query_cache.rb +74 -0
- data/lib/active_record/querying.rb +58 -0
- data/lib/active_record/railtie.rb +119 -0
- data/lib/active_record/railties/console_sandbox.rb +6 -0
- data/lib/active_record/railties/controller_runtime.rb +49 -0
- data/lib/active_record/railties/databases.rake +620 -0
- data/lib/active_record/railties/jdbcmysql_error.rb +16 -0
- data/lib/active_record/readonly_attributes.rb +26 -0
- data/lib/active_record/reflection.rb +534 -0
- data/lib/active_record/relation.rb +534 -0
- data/lib/active_record/relation/batches.rb +90 -0
- data/lib/active_record/relation/calculations.rb +354 -0
- data/lib/active_record/relation/delegation.rb +49 -0
- data/lib/active_record/relation/finder_methods.rb +398 -0
- data/lib/active_record/relation/predicate_builder.rb +58 -0
- data/lib/active_record/relation/query_methods.rb +417 -0
- data/lib/active_record/relation/spawn_methods.rb +148 -0
- data/lib/active_record/result.rb +34 -0
- data/lib/active_record/sanitization.rb +194 -0
- data/lib/active_record/schema.rb +58 -0
- data/lib/active_record/schema_dumper.rb +204 -0
- data/lib/active_record/scoping.rb +152 -0
- data/lib/active_record/scoping/default.rb +142 -0
- data/lib/active_record/scoping/named.rb +202 -0
- data/lib/active_record/serialization.rb +18 -0
- data/lib/active_record/serializers/xml_serializer.rb +202 -0
- data/lib/active_record/session_store.rb +358 -0
- data/lib/active_record/store.rb +50 -0
- data/lib/active_record/test_case.rb +73 -0
- data/lib/active_record/timestamp.rb +113 -0
- data/lib/active_record/transactions.rb +360 -0
- data/lib/active_record/translation.rb +22 -0
- data/lib/active_record/validations.rb +83 -0
- data/lib/active_record/validations/associated.rb +43 -0
- data/lib/active_record/validations/uniqueness.rb +180 -0
- data/lib/active_record/version.rb +10 -0
- data/lib/rails/generators/active_record.rb +25 -0
- data/lib/rails/generators/active_record/migration.rb +15 -0
- data/lib/rails/generators/active_record/migration/migration_generator.rb +25 -0
- data/lib/rails/generators/active_record/migration/templates/migration.rb +31 -0
- data/lib/rails/generators/active_record/model/model_generator.rb +43 -0
- data/lib/rails/generators/active_record/model/templates/migration.rb +15 -0
- data/lib/rails/generators/active_record/model/templates/model.rb +7 -0
- data/lib/rails/generators/active_record/model/templates/module.rb +7 -0
- data/lib/rails/generators/active_record/observer/observer_generator.rb +15 -0
- data/lib/rails/generators/active_record/observer/templates/observer.rb +4 -0
- data/lib/rails/generators/active_record/session_migration/session_migration_generator.rb +25 -0
- data/lib/rails/generators/active_record/session_migration/templates/migration.rb +12 -0
- 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
|