activerecord 3.1.12 → 3.2.22.1
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of activerecord might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/CHANGELOG.md +804 -338
- data/README.rdoc +3 -3
- data/examples/performance.rb +20 -1
- data/lib/active_record/aggregations.rb +1 -1
- data/lib/active_record/associations/alias_tracker.rb +3 -6
- data/lib/active_record/associations/association.rb +13 -45
- data/lib/active_record/associations/association_scope.rb +3 -15
- data/lib/active_record/associations/belongs_to_association.rb +1 -1
- data/lib/active_record/associations/belongs_to_polymorphic_association.rb +2 -1
- data/lib/active_record/associations/builder/association.rb +6 -4
- data/lib/active_record/associations/builder/belongs_to.rb +7 -4
- data/lib/active_record/associations/builder/collection_association.rb +2 -2
- data/lib/active_record/associations/builder/has_many.rb +4 -4
- data/lib/active_record/associations/builder/has_one.rb +5 -6
- data/lib/active_record/associations/builder/singular_association.rb +3 -16
- data/lib/active_record/associations/collection_association.rb +65 -32
- data/lib/active_record/associations/collection_proxy.rb +8 -41
- data/lib/active_record/associations/has_and_belongs_to_many_association.rb +1 -0
- data/lib/active_record/associations/has_many_association.rb +11 -7
- data/lib/active_record/associations/has_many_through_association.rb +19 -9
- data/lib/active_record/associations/has_one_association.rb +23 -13
- data/lib/active_record/associations/join_dependency/join_association.rb +6 -1
- data/lib/active_record/associations/join_dependency.rb +3 -3
- data/lib/active_record/associations/preloader/through_association.rb +3 -3
- data/lib/active_record/associations/preloader.rb +14 -10
- data/lib/active_record/associations/through_association.rb +8 -4
- data/lib/active_record/associations.rb +92 -76
- data/lib/active_record/attribute_assignment.rb +221 -0
- data/lib/active_record/attribute_methods/deprecated_underscore_read.rb +32 -0
- data/lib/active_record/attribute_methods/dirty.rb +21 -11
- data/lib/active_record/attribute_methods/primary_key.rb +62 -25
- data/lib/active_record/attribute_methods/read.rb +73 -83
- data/lib/active_record/attribute_methods/serialization.rb +120 -0
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +12 -14
- data/lib/active_record/attribute_methods/write.rb +32 -6
- data/lib/active_record/attribute_methods.rb +231 -30
- data/lib/active_record/autosave_association.rb +44 -26
- data/lib/active_record/base.rb +227 -1708
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +150 -148
- data/lib/active_record/connection_adapters/abstract/connection_specification.rb +85 -29
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +7 -34
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +10 -2
- data/lib/active_record/connection_adapters/abstract/quoting.rb +7 -4
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +39 -28
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +48 -19
- data/lib/active_record/connection_adapters/abstract_adapter.rb +77 -42
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +676 -0
- data/lib/active_record/connection_adapters/column.rb +37 -11
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +133 -581
- data/lib/active_record/connection_adapters/mysql_adapter.rb +136 -693
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +209 -97
- data/lib/active_record/connection_adapters/schema_cache.rb +69 -0
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +2 -6
- data/lib/active_record/connection_adapters/sqlite_adapter.rb +62 -35
- data/lib/active_record/counter_cache.rb +9 -4
- data/lib/active_record/dynamic_finder_match.rb +12 -0
- data/lib/active_record/dynamic_matchers.rb +84 -0
- data/lib/active_record/errors.rb +11 -1
- data/lib/active_record/explain.rb +86 -0
- data/lib/active_record/explain_subscriber.rb +25 -0
- data/lib/active_record/fixtures/file.rb +65 -0
- data/lib/active_record/fixtures.rb +57 -86
- data/lib/active_record/identity_map.rb +3 -4
- data/lib/active_record/inheritance.rb +174 -0
- data/lib/active_record/integration.rb +60 -0
- data/lib/active_record/locking/optimistic.rb +33 -26
- data/lib/active_record/locking/pessimistic.rb +23 -1
- data/lib/active_record/log_subscriber.rb +8 -4
- data/lib/active_record/migration/command_recorder.rb +8 -8
- data/lib/active_record/migration.rb +68 -35
- data/lib/active_record/model_schema.rb +368 -0
- data/lib/active_record/nested_attributes.rb +60 -24
- data/lib/active_record/persistence.rb +57 -11
- data/lib/active_record/query_cache.rb +6 -6
- data/lib/active_record/querying.rb +58 -0
- data/lib/active_record/railtie.rb +37 -29
- data/lib/active_record/railties/controller_runtime.rb +3 -1
- data/lib/active_record/railties/databases.rake +213 -117
- data/lib/active_record/railties/jdbcmysql_error.rb +1 -1
- data/lib/active_record/readonly_attributes.rb +26 -0
- data/lib/active_record/reflection.rb +7 -15
- data/lib/active_record/relation/batches.rb +7 -4
- data/lib/active_record/relation/calculations.rb +55 -16
- data/lib/active_record/relation/delegation.rb +49 -0
- data/lib/active_record/relation/finder_methods.rb +16 -11
- data/lib/active_record/relation/predicate_builder.rb +8 -6
- data/lib/active_record/relation/query_methods.rb +75 -9
- data/lib/active_record/relation/spawn_methods.rb +48 -7
- data/lib/active_record/relation.rb +78 -32
- data/lib/active_record/result.rb +10 -4
- data/lib/active_record/sanitization.rb +194 -0
- data/lib/active_record/schema_dumper.rb +12 -5
- data/lib/active_record/scoping/default.rb +142 -0
- data/lib/active_record/scoping/named.rb +200 -0
- data/lib/active_record/scoping.rb +152 -0
- data/lib/active_record/serialization.rb +1 -43
- data/lib/active_record/serializers/xml_serializer.rb +4 -45
- data/lib/active_record/session_store.rb +18 -16
- data/lib/active_record/store.rb +52 -0
- data/lib/active_record/test_case.rb +11 -7
- data/lib/active_record/timestamp.rb +17 -3
- data/lib/active_record/transactions.rb +27 -6
- data/lib/active_record/translation.rb +22 -0
- data/lib/active_record/validations/associated.rb +5 -4
- data/lib/active_record/validations/uniqueness.rb +8 -8
- data/lib/active_record/validations.rb +1 -1
- data/lib/active_record/version.rb +3 -3
- data/lib/active_record.rb +38 -3
- data/lib/rails/generators/active_record/migration/migration_generator.rb +1 -1
- data/lib/rails/generators/active_record/migration/templates/migration.rb +12 -3
- data/lib/rails/generators/active_record/model/model_generator.rb +9 -1
- data/lib/rails/generators/active_record/model/templates/migration.rb +3 -5
- data/lib/rails/generators/active_record/model/templates/model.rb +5 -0
- data/lib/rails/generators/active_record/session_migration/templates/migration.rb +1 -5
- metadata +49 -28
- data/lib/active_record/named_scope.rb +0 -200
@@ -0,0 +1,120 @@
|
|
1
|
+
module ActiveRecord
|
2
|
+
module AttributeMethods
|
3
|
+
module Serialization
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
|
6
|
+
included do
|
7
|
+
# Returns a hash of all the attributes that have been specified for serialization as
|
8
|
+
# keys and their class restriction as values.
|
9
|
+
class_attribute :serialized_attributes
|
10
|
+
self.serialized_attributes = {}
|
11
|
+
end
|
12
|
+
|
13
|
+
class Attribute < Struct.new(:coder, :value, :state)
|
14
|
+
def unserialized_value
|
15
|
+
state == :serialized ? unserialize : value
|
16
|
+
end
|
17
|
+
|
18
|
+
def serialized_value
|
19
|
+
state == :unserialized ? serialize : value
|
20
|
+
end
|
21
|
+
|
22
|
+
def unserialize
|
23
|
+
self.state = :unserialized
|
24
|
+
self.value = coder.load(value)
|
25
|
+
end
|
26
|
+
|
27
|
+
def serialize
|
28
|
+
self.state = :serialized
|
29
|
+
self.value = coder.dump(value)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
module ClassMethods
|
34
|
+
# If you have an attribute that needs to be saved to the database as an object, and retrieved as the same object,
|
35
|
+
# then specify the name of that attribute using this method and it will be handled automatically.
|
36
|
+
# The serialization is done through YAML. If +class_name+ is specified, the serialized object must be of that
|
37
|
+
# class on retrieval or SerializationTypeMismatch will be raised.
|
38
|
+
#
|
39
|
+
# ==== Parameters
|
40
|
+
#
|
41
|
+
# * +attr_name+ - The field name that should be serialized.
|
42
|
+
# * +class_name+ - Optional, class name that the object type should be equal to.
|
43
|
+
#
|
44
|
+
# ==== Example
|
45
|
+
# # Serialize a preferences attribute
|
46
|
+
# class User < ActiveRecord::Base
|
47
|
+
# serialize :preferences
|
48
|
+
# end
|
49
|
+
def serialize(attr_name, class_name = Object)
|
50
|
+
coder = if [:load, :dump].all? { |x| class_name.respond_to?(x) }
|
51
|
+
class_name
|
52
|
+
else
|
53
|
+
Coders::YAMLColumn.new(class_name)
|
54
|
+
end
|
55
|
+
|
56
|
+
# merge new serialized attribute and create new hash to ensure that each class in inheritance hierarchy
|
57
|
+
# has its own hash of own serialized attributes
|
58
|
+
self.serialized_attributes = serialized_attributes.merge(attr_name.to_s => coder)
|
59
|
+
end
|
60
|
+
|
61
|
+
def initialize_attributes(attributes, options = {}) #:nodoc:
|
62
|
+
serialized = (options.delete(:serialized) { true }) ? :serialized : :unserialized
|
63
|
+
super(attributes, options)
|
64
|
+
|
65
|
+
serialized_attributes.each do |key, coder|
|
66
|
+
if attributes.key?(key)
|
67
|
+
attributes[key] = Attribute.new(coder, attributes[key], serialized)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
attributes
|
72
|
+
end
|
73
|
+
|
74
|
+
private
|
75
|
+
|
76
|
+
def attribute_cast_code(attr_name)
|
77
|
+
if serialized_attributes.include?(attr_name)
|
78
|
+
"v.unserialized_value"
|
79
|
+
else
|
80
|
+
super
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def type_cast_attribute_for_write(column, value)
|
86
|
+
if column && coder = self.class.serialized_attributes[column.name]
|
87
|
+
Attribute.new(coder, value, :unserialized)
|
88
|
+
else
|
89
|
+
super
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
def _field_changed?(attr, old, value)
|
94
|
+
if self.class.serialized_attributes.include?(attr)
|
95
|
+
old != value
|
96
|
+
else
|
97
|
+
super
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
def read_attribute_before_type_cast(attr_name)
|
102
|
+
if serialized_attributes.include?(attr_name)
|
103
|
+
super.unserialized_value
|
104
|
+
else
|
105
|
+
super
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
def attributes_before_type_cast
|
110
|
+
super.dup.tap do |attributes|
|
111
|
+
self.class.serialized_attributes.each_key do |key|
|
112
|
+
if attributes.key?(key)
|
113
|
+
attributes[key] = attributes[key].unserialized_value
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
@@ -16,21 +16,16 @@ module ActiveRecord
|
|
16
16
|
|
17
17
|
module ClassMethods
|
18
18
|
protected
|
19
|
-
#
|
20
|
-
# This enhanced read method automatically converts the UTC time stored in the database to the time
|
19
|
+
# The enhanced read method automatically converts the UTC time stored in the database to the time
|
21
20
|
# zone stored in Time.zone.
|
22
|
-
def
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
end
|
31
|
-
alias #{attr_name} _#{attr_name}
|
32
|
-
EOV
|
33
|
-
generated_attribute_methods.module_eval(method_body, __FILE__, line)
|
21
|
+
def attribute_cast_code(attr_name)
|
22
|
+
column = columns_hash[attr_name]
|
23
|
+
|
24
|
+
if create_time_zone_conversion_attribute?(attr_name, column)
|
25
|
+
typecast = "v = #{super}"
|
26
|
+
time_zone_conversion = "v.acts_like?(:time) ? v.in_time_zone : v"
|
27
|
+
|
28
|
+
"((#{typecast}) && (#{time_zone_conversion}))"
|
34
29
|
else
|
35
30
|
super
|
36
31
|
end
|
@@ -42,12 +37,15 @@ module ActiveRecord
|
|
42
37
|
if create_time_zone_conversion_attribute?(attr_name, columns_hash[attr_name])
|
43
38
|
method_body, line = <<-EOV, __LINE__ + 1
|
44
39
|
def #{attr_name}=(original_time)
|
40
|
+
original_time = nil if original_time.blank?
|
45
41
|
time = original_time
|
46
42
|
unless time.acts_like?(:time)
|
47
43
|
time = time.is_a?(String) ? Time.zone.parse(time) : time.to_time rescue time
|
48
44
|
end
|
49
45
|
time = time.in_time_zone rescue nil if time
|
46
|
+
previous_time = attribute_changed?("#{attr_name}") ? changed_attributes["#{attr_name}"] : read_attribute(:#{attr_name})
|
50
47
|
write_attribute(:#{attr_name}, original_time)
|
48
|
+
#{attr_name}_will_change! if previous_time != time
|
51
49
|
@attributes_cache["#{attr_name}"] = time
|
52
50
|
end
|
53
51
|
EOV
|
@@ -10,7 +10,7 @@ module ActiveRecord
|
|
10
10
|
module ClassMethods
|
11
11
|
protected
|
12
12
|
def define_method_attribute=(attr_name)
|
13
|
-
if attr_name =~ ActiveModel::AttributeMethods::
|
13
|
+
if attr_name =~ ActiveModel::AttributeMethods::NAME_COMPILABLE_REGEXP
|
14
14
|
generated_attribute_methods.module_eval("def #{attr_name}=(new_value); write_attribute('#{attr_name}', new_value); end", __FILE__, __LINE__)
|
15
15
|
else
|
16
16
|
generated_attribute_methods.send(:define_method, "#{attr_name}=") do |new_value|
|
@@ -24,13 +24,18 @@ module ActiveRecord
|
|
24
24
|
# for fixnum and float columns are turned into +nil+.
|
25
25
|
def write_attribute(attr_name, value)
|
26
26
|
attr_name = attr_name.to_s
|
27
|
-
attr_name = self.class.primary_key if attr_name == 'id'
|
27
|
+
attr_name = self.class.primary_key if attr_name == 'id' && self.class.primary_key
|
28
28
|
@attributes_cache.delete(attr_name)
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
29
|
+
column = column_for_attribute(attr_name)
|
30
|
+
|
31
|
+
unless column || @attributes.has_key?(attr_name)
|
32
|
+
ActiveSupport::Deprecation.warn(
|
33
|
+
"You're trying to create an attribute `#{attr_name}'. Writing arbitrary " \
|
34
|
+
"attributes on a model is deprecated. Please just use `attr_writer` etc."
|
35
|
+
)
|
33
36
|
end
|
37
|
+
|
38
|
+
@attributes[attr_name] = type_cast_attribute_for_write(column, value)
|
34
39
|
end
|
35
40
|
alias_method :raw_write_attribute, :write_attribute
|
36
41
|
|
@@ -39,6 +44,27 @@ module ActiveRecord
|
|
39
44
|
def attribute=(attribute_name, value)
|
40
45
|
write_attribute(attribute_name, value)
|
41
46
|
end
|
47
|
+
|
48
|
+
def type_cast_attribute_for_write(column, value)
|
49
|
+
if column && column.number?
|
50
|
+
convert_number_column_value(value)
|
51
|
+
else
|
52
|
+
value
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def convert_number_column_value(value)
|
57
|
+
case value
|
58
|
+
when FalseClass
|
59
|
+
0
|
60
|
+
when TrueClass
|
61
|
+
1
|
62
|
+
when String
|
63
|
+
value.presence
|
64
|
+
else
|
65
|
+
value
|
66
|
+
end
|
67
|
+
end
|
42
68
|
end
|
43
69
|
end
|
44
70
|
end
|
@@ -1,4 +1,5 @@
|
|
1
1
|
require 'active_support/core_ext/enumerable'
|
2
|
+
require 'active_support/deprecation'
|
2
3
|
|
3
4
|
module ActiveRecord
|
4
5
|
# = Active Record Attribute Methods
|
@@ -6,69 +7,269 @@ module ActiveRecord
|
|
6
7
|
extend ActiveSupport::Concern
|
7
8
|
include ActiveModel::AttributeMethods
|
8
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
|
+
|
9
35
|
module ClassMethods
|
10
36
|
# Generates all the attribute related methods for columns in the database
|
11
37
|
# accessors, mutators and query methods.
|
12
38
|
def define_attribute_methods
|
13
|
-
|
14
|
-
|
15
|
-
|
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
|
+
column_names.each { |name| define_external_attribute_method(name) }
|
68
|
+
@attribute_methods_generated = true
|
69
|
+
end
|
16
70
|
end
|
17
71
|
|
18
72
|
def attribute_methods_generated?
|
19
73
|
@attribute_methods_generated ||= false
|
20
74
|
end
|
21
75
|
|
22
|
-
|
76
|
+
# We will define the methods as instance methods, but will call them as singleton
|
77
|
+
# methods. This allows us to use method_defined? to check if the method exists,
|
78
|
+
# which is fast and won't give any false positives from the ancestors (because
|
79
|
+
# there are no ancestors).
|
80
|
+
def generated_external_attribute_methods
|
81
|
+
@generated_external_attribute_methods ||= Module.new { extend self }
|
82
|
+
end
|
83
|
+
|
84
|
+
def undefine_attribute_methods
|
23
85
|
super
|
24
86
|
@attribute_methods_generated = false
|
25
87
|
end
|
26
88
|
|
27
|
-
# Checks whether the method is defined in the model or any of its subclasses
|
28
|
-
# that also derive from Active Record. Raises DangerousAttributeError if the
|
29
|
-
# method is defined by Active Record though.
|
30
89
|
def instance_method_already_implemented?(method_name)
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
90
|
+
if dangerous_attribute_method?(method_name)
|
91
|
+
raise DangerousAttributeError, "#{method_name} is defined by ActiveRecord"
|
92
|
+
end
|
93
|
+
|
94
|
+
if superclass == Base
|
95
|
+
super
|
96
|
+
else
|
97
|
+
# If B < A and A defines its own attribute method, then we don't want to overwrite that.
|
98
|
+
defined = method_defined_within?(method_name, superclass, superclass.generated_attribute_methods)
|
99
|
+
defined && !ActiveRecord::Base.method_defined?(method_name) || super
|
100
|
+
end
|
101
|
+
end
|
36
102
|
|
37
|
-
|
38
|
-
|
39
|
-
|
103
|
+
# A method name is 'dangerous' if it is already defined by Active Record, but
|
104
|
+
# not by any ancestors. (So 'puts' is not dangerous but 'save' is.)
|
105
|
+
def dangerous_attribute_method?(name)
|
106
|
+
method_defined_within?(name, Base)
|
40
107
|
end
|
41
108
|
|
42
|
-
def
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
109
|
+
def method_defined_within?(name, klass, sup = klass.superclass)
|
110
|
+
if klass.method_defined?(name) || klass.private_method_defined?(name)
|
111
|
+
if sup.method_defined?(name) || sup.private_method_defined?(name)
|
112
|
+
klass.instance_method(name).owner != sup.instance_method(name).owner
|
113
|
+
else
|
114
|
+
true
|
115
|
+
end
|
116
|
+
else
|
117
|
+
false
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
def attribute_method?(attribute)
|
122
|
+
super || (table_exists? && column_names.include?(attribute.to_s.sub(/=$/, '')))
|
123
|
+
end
|
124
|
+
|
125
|
+
# Returns an array of column names as strings if it's not
|
126
|
+
# an abstract class and table exists.
|
127
|
+
# Otherwise it returns an empty array.
|
128
|
+
def attribute_names
|
129
|
+
@attribute_names ||= if !abstract_class? && table_exists?
|
130
|
+
column_names
|
131
|
+
else
|
132
|
+
[]
|
133
|
+
end
|
48
134
|
end
|
49
135
|
end
|
50
136
|
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
137
|
+
# If we haven't generated any methods yet, generate them, then
|
138
|
+
# see if we've created the method we're looking for.
|
139
|
+
def method_missing(method, *args, &block)
|
140
|
+
unless self.class.attribute_methods_generated?
|
55
141
|
self.class.define_attribute_methods
|
56
|
-
|
57
|
-
|
58
|
-
|
142
|
+
|
143
|
+
if respond_to_without_attributes?(method)
|
144
|
+
send(method, *args, &block)
|
145
|
+
else
|
146
|
+
super
|
147
|
+
end
|
59
148
|
else
|
60
149
|
super
|
61
150
|
end
|
62
151
|
end
|
63
152
|
|
153
|
+
def attribute_missing(match, *args, &block)
|
154
|
+
if self.class.columns_hash[match.attr_name]
|
155
|
+
ActiveSupport::Deprecation.warn(
|
156
|
+
"The method `#{match.method_name}', matching the attribute `#{match.attr_name}' has " \
|
157
|
+
"dispatched through method_missing. This shouldn't happen, because `#{match.attr_name}' " \
|
158
|
+
"is a column of the table. If this error has happened through normal usage of Active " \
|
159
|
+
"Record (rather than through your own code or external libraries), please report it as " \
|
160
|
+
"a bug."
|
161
|
+
)
|
162
|
+
end
|
163
|
+
|
164
|
+
super
|
165
|
+
end
|
166
|
+
|
64
167
|
def respond_to?(name, include_private = false)
|
65
168
|
self.class.define_attribute_methods unless self.class.attribute_methods_generated?
|
66
169
|
super
|
67
170
|
end
|
68
171
|
|
172
|
+
# Returns true if the given attribute is in the attributes hash
|
173
|
+
def has_attribute?(attr_name)
|
174
|
+
@attributes.has_key?(attr_name.to_s)
|
175
|
+
end
|
176
|
+
|
177
|
+
# Returns an array of names for the attributes available on this object.
|
178
|
+
def attribute_names
|
179
|
+
@attributes.keys
|
180
|
+
end
|
181
|
+
|
182
|
+
# Returns a hash of all the attributes with their names as keys and the values of the attributes as values.
|
183
|
+
def attributes
|
184
|
+
attrs = {}
|
185
|
+
attribute_names.each { |name| attrs[name] = read_attribute(name) }
|
186
|
+
attrs
|
187
|
+
end
|
188
|
+
|
189
|
+
# Returns an <tt>#inspect</tt>-like string for the value of the
|
190
|
+
# attribute +attr_name+. String attributes are truncated upto 50
|
191
|
+
# characters, and Date and Time attributes are returned in the
|
192
|
+
# <tt>:db</tt> format. Other attributes return the value of
|
193
|
+
# <tt>#inspect</tt> without modification.
|
194
|
+
#
|
195
|
+
# person = Person.create!(:name => "David Heinemeier Hansson " * 3)
|
196
|
+
#
|
197
|
+
# person.attribute_for_inspect(:name)
|
198
|
+
# # => '"David Heinemeier Hansson David Heinemeier Hansson D..."'
|
199
|
+
#
|
200
|
+
# person.attribute_for_inspect(:created_at)
|
201
|
+
# # => '"2009-01-12 04:48:57"'
|
202
|
+
def attribute_for_inspect(attr_name)
|
203
|
+
value = read_attribute(attr_name)
|
204
|
+
|
205
|
+
if value.is_a?(String) && value.length > 50
|
206
|
+
"#{value[0..50]}...".inspect
|
207
|
+
elsif value.is_a?(Date) || value.is_a?(Time)
|
208
|
+
%("#{value.to_s(:db)}")
|
209
|
+
else
|
210
|
+
value.inspect
|
211
|
+
end
|
212
|
+
end
|
213
|
+
|
214
|
+
# Returns true if the specified +attribute+ has been set by the user or by a database load and is neither
|
215
|
+
# nil nor empty? (the latter only applies to objects that respond to empty?, most notably Strings).
|
216
|
+
def attribute_present?(attribute)
|
217
|
+
value = read_attribute(attribute)
|
218
|
+
!value.nil? && !(value.respond_to?(:empty?) && value.empty?)
|
219
|
+
end
|
220
|
+
|
221
|
+
# Returns the column object for the named attribute.
|
222
|
+
def column_for_attribute(name)
|
223
|
+
self.class.columns_hash[name.to_s]
|
224
|
+
end
|
225
|
+
|
69
226
|
protected
|
70
|
-
|
71
|
-
|
227
|
+
|
228
|
+
def clone_attributes(reader_method = :read_attribute, attributes = {})
|
229
|
+
attribute_names.each do |name|
|
230
|
+
attributes[name] = clone_attribute_value(reader_method, name)
|
72
231
|
end
|
232
|
+
attributes
|
233
|
+
end
|
234
|
+
|
235
|
+
def clone_attribute_value(reader_method, attribute_name)
|
236
|
+
value = send(reader_method, attribute_name)
|
237
|
+
value.duplicable? ? value.clone : value
|
238
|
+
rescue TypeError, NoMethodError
|
239
|
+
value
|
240
|
+
end
|
241
|
+
|
242
|
+
# Returns a copy of the attributes hash where all the values have been safely quoted for use in
|
243
|
+
# an Arel insert/update method.
|
244
|
+
def arel_attributes_values(include_primary_key = true, include_readonly_attributes = true, attribute_names = @attributes.keys)
|
245
|
+
attrs = {}
|
246
|
+
klass = self.class
|
247
|
+
arel_table = klass.arel_table
|
248
|
+
|
249
|
+
attribute_names.each do |name|
|
250
|
+
if (column = column_for_attribute(name)) && (include_primary_key || !column.primary)
|
251
|
+
|
252
|
+
if include_readonly_attributes || !self.class.readonly_attributes.include?(name)
|
253
|
+
|
254
|
+
value = if klass.serialized_attributes.include?(name)
|
255
|
+
@attributes[name].serialized_value
|
256
|
+
else
|
257
|
+
# FIXME: we need @attributes to be used consistently.
|
258
|
+
# If the values stored in @attributes were already type
|
259
|
+
# casted, this code could be simplified
|
260
|
+
read_attribute(name)
|
261
|
+
end
|
262
|
+
|
263
|
+
attrs[arel_table[name]] = value
|
264
|
+
end
|
265
|
+
end
|
266
|
+
end
|
267
|
+
|
268
|
+
attrs
|
269
|
+
end
|
270
|
+
|
271
|
+
def attribute_method?(attr_name)
|
272
|
+
attr_name == 'id' || (defined?(@attributes) && @attributes.include?(attr_name))
|
273
|
+
end
|
73
274
|
end
|
74
275
|
end
|
@@ -21,6 +21,21 @@ module ActiveRecord
|
|
21
21
|
# Note that <tt>:autosave => false</tt> is not same as not declaring <tt>:autosave</tt>.
|
22
22
|
# When the <tt>:autosave</tt> option is not present new associations are saved.
|
23
23
|
#
|
24
|
+
# == Validation
|
25
|
+
#
|
26
|
+
# Children records are validated unless <tt>:validate</tt> is +false+.
|
27
|
+
#
|
28
|
+
# == Callbacks
|
29
|
+
#
|
30
|
+
# Association with autosave option defines several callbacks on your
|
31
|
+
# model (before_save, after_create, after_update). Please note that
|
32
|
+
# callbacks are executed in the order they were defined in
|
33
|
+
# model. You should avoid modyfing the association content, before
|
34
|
+
# autosave callbacks are executed. Placing your callbacks after
|
35
|
+
# associations is usually a good practice.
|
36
|
+
#
|
37
|
+
# == Examples
|
38
|
+
#
|
24
39
|
# === One-to-one Example
|
25
40
|
#
|
26
41
|
# class Post
|
@@ -109,10 +124,7 @@ module ActiveRecord
|
|
109
124
|
# Now it _is_ removed from the database:
|
110
125
|
#
|
111
126
|
# Comment.find_by_id(id).nil? # => true
|
112
|
-
|
113
|
-
# === Validation
|
114
|
-
#
|
115
|
-
# Children records are validated unless <tt>:validate</tt> is +false+.
|
127
|
+
|
116
128
|
module AutosaveAssociation
|
117
129
|
extend ActiveSupport::Concern
|
118
130
|
|
@@ -161,7 +173,7 @@ module ActiveRecord
|
|
161
173
|
#
|
162
174
|
# For performance reasons, we don't check whether to validate at runtime.
|
163
175
|
# However the validation and callback methods are lazy and those methods
|
164
|
-
# get created when they are invoked for the very first time.
|
176
|
+
# get created when they are invoked for the very first time. However,
|
165
177
|
# this can change, for instance, when using nested attributes, which is
|
166
178
|
# called _after_ the association has been defined. Since we don't want
|
167
179
|
# the callbacks to get defined multiple times, there are guards that
|
@@ -264,7 +276,7 @@ module ActiveRecord
|
|
264
276
|
# turned on for the association.
|
265
277
|
def validate_single_association(reflection)
|
266
278
|
association = association_instance_get(reflection.name)
|
267
|
-
record = association && association.
|
279
|
+
record = association && association.reader
|
268
280
|
association_valid?(reflection, record) if record
|
269
281
|
end
|
270
282
|
|
@@ -320,34 +332,37 @@ module ActiveRecord
|
|
320
332
|
|
321
333
|
if records = associated_records_to_validate_or_save(association, @new_record_before_save, autosave)
|
322
334
|
begin
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
if autosave && record.marked_for_destruction?
|
329
|
-
association.proxy.destroy(record)
|
330
|
-
elsif autosave != false && (@new_record_before_save || record.new_record?)
|
331
|
-
if autosave
|
332
|
-
saved = association.insert_record(record, false)
|
333
|
-
else
|
334
|
-
association.insert_record(record)
|
335
|
-
end
|
336
|
-
elsif autosave
|
337
|
-
saved = record.save(:validate => false)
|
335
|
+
if autosave
|
336
|
+
records_to_destroy = records.select(&:marked_for_destruction?)
|
337
|
+
records_to_destroy.each { |record| association.proxy.destroy(record) }
|
338
|
+
records -= records_to_destroy
|
338
339
|
end
|
339
340
|
|
340
|
-
|
341
|
-
|
341
|
+
records.each do |record|
|
342
|
+
next if record.destroyed?
|
343
|
+
|
344
|
+
saved = true
|
345
|
+
|
346
|
+
if autosave != false && (@new_record_before_save || record.new_record?)
|
347
|
+
if autosave
|
348
|
+
saved = association.insert_record(record, false)
|
349
|
+
else
|
350
|
+
association.insert_record(record) unless reflection.nested?
|
351
|
+
end
|
352
|
+
elsif autosave
|
353
|
+
saved = record.save(:validate => false)
|
354
|
+
end
|
355
|
+
|
356
|
+
raise ActiveRecord::Rollback unless saved
|
357
|
+
end
|
342
358
|
rescue
|
343
359
|
records.each {|x| IdentityMap.remove(x) } if IdentityMap.enabled?
|
344
360
|
raise
|
345
361
|
end
|
346
|
-
|
347
362
|
end
|
348
363
|
|
349
364
|
# reconstruct the scope now that we know the owner's id
|
350
|
-
association.
|
365
|
+
association.reset_scope if association.respond_to?(:reset_scope)
|
351
366
|
end
|
352
367
|
end
|
353
368
|
|
@@ -370,7 +385,10 @@ module ActiveRecord
|
|
370
385
|
else
|
371
386
|
key = reflection.options[:primary_key] ? send(reflection.options[:primary_key]) : id
|
372
387
|
if autosave != false && (new_record? || record.new_record? || record[reflection.foreign_key] != key || autosave)
|
373
|
-
|
388
|
+
unless reflection.through_reflection
|
389
|
+
record[reflection.foreign_key] = key
|
390
|
+
end
|
391
|
+
|
374
392
|
saved = record.save(:validate => !autosave)
|
375
393
|
raise ActiveRecord::Rollback if !saved && autosave
|
376
394
|
saved
|