activerecord 3.1.12 → 3.2.0.rc1
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.
- data/CHANGELOG.md +6263 -103
- data/README.rdoc +2 -2
- data/examples/performance.rb +55 -31
- data/lib/active_record.rb +28 -2
- data/lib/active_record/aggregations.rb +2 -2
- data/lib/active_record/associations.rb +82 -69
- data/lib/active_record/associations/association.rb +2 -37
- data/lib/active_record/associations/association_scope.rb +3 -30
- data/lib/active_record/associations/builder/association.rb +6 -4
- data/lib/active_record/associations/builder/belongs_to.rb +3 -3
- 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 +55 -28
- data/lib/active_record/associations/collection_proxy.rb +1 -35
- data/lib/active_record/associations/has_many_association.rb +5 -1
- data/lib/active_record/associations/has_many_through_association.rb +11 -8
- data/lib/active_record/associations/join_dependency.rb +1 -1
- data/lib/active_record/associations/preloader/association.rb +3 -1
- data/lib/active_record/attribute_assignment.rb +221 -0
- data/lib/active_record/attribute_methods.rb +212 -32
- data/lib/active_record/attribute_methods/deprecated_underscore_read.rb +32 -0
- data/lib/active_record/attribute_methods/dirty.rb +3 -3
- data/lib/active_record/attribute_methods/primary_key.rb +62 -25
- data/lib/active_record/attribute_methods/read.rb +69 -80
- data/lib/active_record/attribute_methods/serialization.rb +89 -0
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +9 -14
- data/lib/active_record/attribute_methods/write.rb +27 -5
- data/lib/active_record/autosave_association.rb +23 -8
- data/lib/active_record/base.rb +223 -1712
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +98 -132
- data/lib/active_record/connection_adapters/abstract/connection_specification.rb +82 -29
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +13 -42
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +1 -1
- data/lib/active_record/connection_adapters/abstract/quoting.rb +7 -4
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +36 -25
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +41 -13
- data/lib/active_record/connection_adapters/abstract_adapter.rb +78 -43
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +653 -0
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +138 -578
- data/lib/active_record/connection_adapters/mysql_adapter.rb +86 -658
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +144 -94
- data/lib/active_record/connection_adapters/schema_cache.rb +50 -0
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +2 -6
- data/lib/active_record/connection_adapters/sqlite_adapter.rb +43 -22
- data/lib/active_record/counter_cache.rb +1 -1
- data/lib/active_record/dynamic_matchers.rb +79 -0
- data/lib/active_record/errors.rb +11 -1
- data/lib/active_record/explain.rb +83 -0
- data/lib/active_record/explain_subscriber.rb +21 -0
- data/lib/active_record/fixtures.rb +31 -76
- data/lib/active_record/fixtures/file.rb +65 -0
- data/lib/active_record/identity_map.rb +1 -7
- data/lib/active_record/inheritance.rb +167 -0
- data/lib/active_record/integration.rb +49 -0
- data/lib/active_record/locking/optimistic.rb +19 -11
- data/lib/active_record/locking/pessimistic.rb +1 -1
- data/lib/active_record/log_subscriber.rb +3 -3
- data/lib/active_record/migration.rb +38 -29
- data/lib/active_record/migration/command_recorder.rb +7 -7
- data/lib/active_record/model_schema.rb +362 -0
- data/lib/active_record/nested_attributes.rb +3 -2
- data/lib/active_record/persistence.rb +51 -1
- data/lib/active_record/querying.rb +58 -0
- data/lib/active_record/railtie.rb +24 -28
- data/lib/active_record/railties/controller_runtime.rb +3 -1
- data/lib/active_record/railties/databases.rake +133 -77
- data/lib/active_record/readonly_attributes.rb +26 -0
- data/lib/active_record/reflection.rb +7 -15
- data/lib/active_record/relation.rb +78 -35
- data/lib/active_record/relation/batches.rb +5 -2
- data/lib/active_record/relation/calculations.rb +27 -6
- data/lib/active_record/relation/delegation.rb +49 -0
- data/lib/active_record/relation/finder_methods.rb +5 -4
- data/lib/active_record/relation/predicate_builder.rb +13 -16
- data/lib/active_record/relation/query_methods.rb +59 -4
- data/lib/active_record/result.rb +1 -1
- data/lib/active_record/sanitization.rb +194 -0
- data/lib/active_record/schema_dumper.rb +5 -2
- data/lib/active_record/scoping.rb +152 -0
- data/lib/active_record/scoping/default.rb +140 -0
- data/lib/active_record/scoping/named.rb +202 -0
- data/lib/active_record/serialization.rb +1 -43
- data/lib/active_record/serializers/xml_serializer.rb +2 -44
- data/lib/active_record/session_store.rb +11 -11
- data/lib/active_record/store.rb +50 -0
- data/lib/active_record/test_case.rb +11 -7
- data/lib/active_record/timestamp.rb +16 -3
- data/lib/active_record/transactions.rb +5 -5
- data/lib/active_record/translation.rb +22 -0
- data/lib/active_record/validations.rb +1 -1
- data/lib/active_record/validations/associated.rb +5 -4
- data/lib/active_record/validations/uniqueness.rb +4 -4
- data/lib/active_record/version.rb +3 -3
- data/lib/rails/generators/active_record/session_migration/templates/migration.rb +1 -5
- metadata +48 -38
- checksums.yaml +0 -7
- data/lib/active_record/named_scope.rb +0 -200
@@ -23,7 +23,7 @@ module ActiveRecord
|
|
23
23
|
#
|
24
24
|
# If the association has a counter cache it gets that value. Otherwise
|
25
25
|
# it will attempt to do a count via SQL, bounded to <tt>:limit</tt> if
|
26
|
-
# there's one.
|
26
|
+
# there's one. Some configuration options like :group make it impossible
|
27
27
|
# to do an SQL count, in those cases the array count will be used.
|
28
28
|
#
|
29
29
|
# That does not depend on whether the collection has already been loaded
|
@@ -99,6 +99,10 @@ module ActiveRecord
|
|
99
99
|
end
|
100
100
|
end
|
101
101
|
end
|
102
|
+
|
103
|
+
def foreign_key_present?
|
104
|
+
owner.attribute_present?(reflection.association_primary_key)
|
105
|
+
end
|
102
106
|
end
|
103
107
|
end
|
104
108
|
end
|
@@ -8,7 +8,9 @@ module ActiveRecord
|
|
8
8
|
|
9
9
|
def initialize(owner, reflection)
|
10
10
|
super
|
11
|
-
|
11
|
+
|
12
|
+
@through_records = {}
|
13
|
+
@through_association = nil
|
12
14
|
end
|
13
15
|
|
14
16
|
# Returns the size of the collection by executing a SELECT COUNT(*) query if the collection hasn't been
|
@@ -59,7 +61,7 @@ module ActiveRecord
|
|
59
61
|
private
|
60
62
|
|
61
63
|
def through_association
|
62
|
-
owner.association(through_reflection.name)
|
64
|
+
@through_association ||= owner.association(through_reflection.name)
|
63
65
|
end
|
64
66
|
|
65
67
|
# We temporarily cache through record that has been build, because if we build a
|
@@ -122,8 +124,7 @@ module ActiveRecord
|
|
122
124
|
def delete_records(records, method)
|
123
125
|
ensure_not_nested
|
124
126
|
|
125
|
-
|
126
|
-
scope = through.scoped.where(construct_join_attributes(*records))
|
127
|
+
scope = through_association.scoped.where(construct_join_attributes(*records))
|
127
128
|
|
128
129
|
case method
|
129
130
|
when :destroy
|
@@ -134,7 +135,7 @@ module ActiveRecord
|
|
134
135
|
count = scope.delete_all
|
135
136
|
end
|
136
137
|
|
137
|
-
delete_through_records(
|
138
|
+
delete_through_records(records)
|
138
139
|
|
139
140
|
if through_reflection.macro == :has_many && update_through_counter?(method)
|
140
141
|
update_counter(-count, through_reflection)
|
@@ -149,14 +150,16 @@ module ActiveRecord
|
|
149
150
|
candidates.find_all { |c| c.attributes.slice(*attributes.keys) == attributes }
|
150
151
|
end
|
151
152
|
|
152
|
-
def delete_through_records(
|
153
|
+
def delete_through_records(records)
|
153
154
|
records.each do |record|
|
154
155
|
through_records = through_records_for(record)
|
155
156
|
|
156
157
|
if through_reflection.macro == :has_many
|
157
|
-
through_records.each { |r|
|
158
|
+
through_records.each { |r| through_association.target.delete(r) }
|
158
159
|
else
|
159
|
-
|
160
|
+
if through_records.include?(through_association.target)
|
161
|
+
through_association.target = nil
|
162
|
+
end
|
160
163
|
end
|
161
164
|
|
162
165
|
@through_records.delete(record.object_id)
|
@@ -184,7 +184,7 @@ module ActiveRecord
|
|
184
184
|
|
185
185
|
macro = join_part.reflection.macro
|
186
186
|
if macro == :has_one
|
187
|
-
return if record.association_cache.key?(join_part.reflection.name)
|
187
|
+
return record.association(join_part.reflection.name).target if record.association_cache.key?(join_part.reflection.name)
|
188
188
|
association = join_part.instantiate(row) unless row[join_part.aliased_primary_key].nil?
|
189
189
|
set_target_and_inverse(join_part, association, record)
|
190
190
|
else
|
@@ -0,0 +1,221 @@
|
|
1
|
+
require 'active_support/concern'
|
2
|
+
|
3
|
+
module ActiveRecord
|
4
|
+
module AttributeAssignment
|
5
|
+
extend ActiveSupport::Concern
|
6
|
+
include ActiveModel::MassAssignmentSecurity
|
7
|
+
|
8
|
+
module ClassMethods
|
9
|
+
private
|
10
|
+
|
11
|
+
# The primary key and inheritance column can never be set by mass-assignment for security reasons.
|
12
|
+
def attributes_protected_by_default
|
13
|
+
default = [ primary_key, inheritance_column ]
|
14
|
+
default << 'id' unless primary_key.eql? 'id'
|
15
|
+
default
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
# Allows you to set all the attributes at once by passing in a hash with keys
|
20
|
+
# matching the attribute names (which again matches the column names).
|
21
|
+
#
|
22
|
+
# If any attributes are protected by either +attr_protected+ or
|
23
|
+
# +attr_accessible+ then only settable attributes will be assigned.
|
24
|
+
#
|
25
|
+
# class User < ActiveRecord::Base
|
26
|
+
# attr_protected :is_admin
|
27
|
+
# end
|
28
|
+
#
|
29
|
+
# user = User.new
|
30
|
+
# user.attributes = { :username => 'Phusion', :is_admin => true }
|
31
|
+
# user.username # => "Phusion"
|
32
|
+
# user.is_admin? # => false
|
33
|
+
def attributes=(new_attributes)
|
34
|
+
return unless new_attributes.is_a?(Hash)
|
35
|
+
|
36
|
+
assign_attributes(new_attributes)
|
37
|
+
end
|
38
|
+
|
39
|
+
# Allows you to set all the attributes for a particular mass-assignment
|
40
|
+
# security role by passing in a hash of attributes with keys matching
|
41
|
+
# the attribute names (which again matches the column names) and the role
|
42
|
+
# name using the :as option.
|
43
|
+
#
|
44
|
+
# To bypass mass-assignment security you can use the :without_protection => true
|
45
|
+
# option.
|
46
|
+
#
|
47
|
+
# class User < ActiveRecord::Base
|
48
|
+
# attr_accessible :name
|
49
|
+
# attr_accessible :name, :is_admin, :as => :admin
|
50
|
+
# end
|
51
|
+
#
|
52
|
+
# user = User.new
|
53
|
+
# user.assign_attributes({ :name => 'Josh', :is_admin => true })
|
54
|
+
# user.name # => "Josh"
|
55
|
+
# user.is_admin? # => false
|
56
|
+
#
|
57
|
+
# user = User.new
|
58
|
+
# user.assign_attributes({ :name => 'Josh', :is_admin => true }, :as => :admin)
|
59
|
+
# user.name # => "Josh"
|
60
|
+
# user.is_admin? # => true
|
61
|
+
#
|
62
|
+
# user = User.new
|
63
|
+
# user.assign_attributes({ :name => 'Josh', :is_admin => true }, :without_protection => true)
|
64
|
+
# user.name # => "Josh"
|
65
|
+
# user.is_admin? # => true
|
66
|
+
def assign_attributes(new_attributes, options = {})
|
67
|
+
return unless new_attributes
|
68
|
+
|
69
|
+
attributes = new_attributes.stringify_keys
|
70
|
+
multi_parameter_attributes = []
|
71
|
+
nested_parameter_attributes = []
|
72
|
+
@mass_assignment_options = options
|
73
|
+
|
74
|
+
unless options[:without_protection]
|
75
|
+
attributes = sanitize_for_mass_assignment(attributes, mass_assignment_role)
|
76
|
+
end
|
77
|
+
|
78
|
+
attributes.each do |k, v|
|
79
|
+
if k.include?("(")
|
80
|
+
multi_parameter_attributes << [ k, v ]
|
81
|
+
elsif respond_to?("#{k}=")
|
82
|
+
if v.is_a?(Hash)
|
83
|
+
nested_parameter_attributes << [ k, v ]
|
84
|
+
else
|
85
|
+
send("#{k}=", v)
|
86
|
+
end
|
87
|
+
else
|
88
|
+
raise(UnknownAttributeError, "unknown attribute: #{k}")
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
# assign any deferred nested attributes after the base attributes have been set
|
93
|
+
nested_parameter_attributes.each do |k,v|
|
94
|
+
send("#{k}=", v)
|
95
|
+
end
|
96
|
+
|
97
|
+
@mass_assignment_options = nil
|
98
|
+
assign_multiparameter_attributes(multi_parameter_attributes)
|
99
|
+
end
|
100
|
+
|
101
|
+
protected
|
102
|
+
|
103
|
+
def mass_assignment_options
|
104
|
+
@mass_assignment_options ||= {}
|
105
|
+
end
|
106
|
+
|
107
|
+
def mass_assignment_role
|
108
|
+
mass_assignment_options[:as] || :default
|
109
|
+
end
|
110
|
+
|
111
|
+
private
|
112
|
+
|
113
|
+
# Instantiates objects for all attribute classes that needs more than one constructor parameter. This is done
|
114
|
+
# by calling new on the column type or aggregation type (through composed_of) object with these parameters.
|
115
|
+
# So having the pairs written_on(1) = "2004", written_on(2) = "6", written_on(3) = "24", will instantiate
|
116
|
+
# written_on (a date type) with Date.new("2004", "6", "24"). You can also specify a typecast character in the
|
117
|
+
# parentheses to have the parameters typecasted before they're used in the constructor. Use i for Fixnum,
|
118
|
+
# f for Float, s for String, and a for Array. If all the values for a given attribute are empty, the
|
119
|
+
# attribute will be set to nil.
|
120
|
+
def assign_multiparameter_attributes(pairs)
|
121
|
+
execute_callstack_for_multiparameter_attributes(
|
122
|
+
extract_callstack_for_multiparameter_attributes(pairs)
|
123
|
+
)
|
124
|
+
end
|
125
|
+
|
126
|
+
def instantiate_time_object(name, values)
|
127
|
+
if self.class.send(:create_time_zone_conversion_attribute?, name, column_for_attribute(name))
|
128
|
+
Time.zone.local(*values)
|
129
|
+
else
|
130
|
+
Time.time_with_datetime_fallback(self.class.default_timezone, *values)
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
def execute_callstack_for_multiparameter_attributes(callstack)
|
135
|
+
errors = []
|
136
|
+
callstack.each do |name, values_with_empty_parameters|
|
137
|
+
begin
|
138
|
+
send(name + "=", read_value_from_parameter(name, values_with_empty_parameters))
|
139
|
+
rescue => ex
|
140
|
+
errors << AttributeAssignmentError.new("error on assignment #{values_with_empty_parameters.values.inspect} to #{name}", ex, name)
|
141
|
+
end
|
142
|
+
end
|
143
|
+
unless errors.empty?
|
144
|
+
raise MultiparameterAssignmentErrors.new(errors), "#{errors.size} error(s) on assignment of multiparameter attributes"
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
def read_value_from_parameter(name, values_hash_from_param)
|
149
|
+
klass = (self.class.reflect_on_aggregation(name.to_sym) || column_for_attribute(name)).klass
|
150
|
+
if values_hash_from_param.values.all?{|v|v.nil?}
|
151
|
+
nil
|
152
|
+
elsif klass == Time
|
153
|
+
read_time_parameter_value(name, values_hash_from_param)
|
154
|
+
elsif klass == Date
|
155
|
+
read_date_parameter_value(name, values_hash_from_param)
|
156
|
+
else
|
157
|
+
read_other_parameter_value(klass, name, values_hash_from_param)
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
def read_time_parameter_value(name, values_hash_from_param)
|
162
|
+
# If Date bits were not provided, error
|
163
|
+
raise "Missing Parameter" if [1,2,3].any?{|position| !values_hash_from_param.has_key?(position)}
|
164
|
+
max_position = extract_max_param_for_multiparameter_attributes(values_hash_from_param, 6)
|
165
|
+
# If Date bits were provided but blank, then return nil
|
166
|
+
return nil if (1..3).any? {|position| values_hash_from_param[position].blank?}
|
167
|
+
|
168
|
+
set_values = (1..max_position).collect{|position| values_hash_from_param[position] }
|
169
|
+
# If Time bits are not there, then default to 0
|
170
|
+
(3..5).each {|i| set_values[i] = set_values[i].blank? ? 0 : set_values[i]}
|
171
|
+
instantiate_time_object(name, set_values)
|
172
|
+
end
|
173
|
+
|
174
|
+
def read_date_parameter_value(name, values_hash_from_param)
|
175
|
+
return nil if (1..3).any? {|position| values_hash_from_param[position].blank?}
|
176
|
+
set_values = [values_hash_from_param[1], values_hash_from_param[2], values_hash_from_param[3]]
|
177
|
+
begin
|
178
|
+
Date.new(*set_values)
|
179
|
+
rescue ArgumentError # if Date.new raises an exception on an invalid date
|
180
|
+
instantiate_time_object(name, set_values).to_date # we instantiate Time object and convert it back to a date thus using Time's logic in handling invalid dates
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
def read_other_parameter_value(klass, name, values_hash_from_param)
|
185
|
+
max_position = extract_max_param_for_multiparameter_attributes(values_hash_from_param)
|
186
|
+
values = (1..max_position).collect do |position|
|
187
|
+
raise "Missing Parameter" if !values_hash_from_param.has_key?(position)
|
188
|
+
values_hash_from_param[position]
|
189
|
+
end
|
190
|
+
klass.new(*values)
|
191
|
+
end
|
192
|
+
|
193
|
+
def extract_max_param_for_multiparameter_attributes(values_hash_from_param, upper_cap = 100)
|
194
|
+
[values_hash_from_param.keys.max,upper_cap].min
|
195
|
+
end
|
196
|
+
|
197
|
+
def extract_callstack_for_multiparameter_attributes(pairs)
|
198
|
+
attributes = { }
|
199
|
+
|
200
|
+
pairs.each do |pair|
|
201
|
+
multiparameter_name, value = pair
|
202
|
+
attribute_name = multiparameter_name.split("(").first
|
203
|
+
attributes[attribute_name] = {} unless attributes.include?(attribute_name)
|
204
|
+
|
205
|
+
parameter_value = value.empty? ? nil : type_cast_attribute_value(multiparameter_name, value)
|
206
|
+
attributes[attribute_name][find_parameter_position(multiparameter_name)] ||= parameter_value
|
207
|
+
end
|
208
|
+
|
209
|
+
attributes
|
210
|
+
end
|
211
|
+
|
212
|
+
def type_cast_attribute_value(multiparameter_name, value)
|
213
|
+
multiparameter_name =~ /\([0-9]*([if])\)/ ? value.send("to_" + $1) : value
|
214
|
+
end
|
215
|
+
|
216
|
+
def find_parameter_position(multiparameter_name)
|
217
|
+
multiparameter_name.scan(/\(([0-9]*).*\)/).first.first.to_i
|
218
|
+
end
|
219
|
+
|
220
|
+
end
|
221
|
+
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,248 @@ 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
|
+
alias [] read_attribute
|
25
|
+
|
26
|
+
# Updates the attribute identified by <tt>attr_name</tt> with the specified +value+.
|
27
|
+
# (Alias for the protected write_attribute method).
|
28
|
+
alias []= write_attribute
|
29
|
+
|
30
|
+
public :[], :[]=
|
31
|
+
end
|
32
|
+
|
9
33
|
module ClassMethods
|
10
34
|
# Generates all the attribute related methods for columns in the database
|
11
35
|
# accessors, mutators and query methods.
|
12
36
|
def define_attribute_methods
|
13
37
|
return if attribute_methods_generated?
|
14
|
-
|
15
|
-
|
38
|
+
|
39
|
+
if base_class == self
|
40
|
+
super(column_names)
|
41
|
+
@attribute_methods_generated = true
|
42
|
+
else
|
43
|
+
base_class.define_attribute_methods
|
44
|
+
end
|
16
45
|
end
|
17
46
|
|
18
47
|
def attribute_methods_generated?
|
19
|
-
|
48
|
+
if base_class == self
|
49
|
+
@attribute_methods_generated ||= false
|
50
|
+
else
|
51
|
+
base_class.attribute_methods_generated?
|
52
|
+
end
|
20
53
|
end
|
21
54
|
|
22
|
-
def
|
23
|
-
super
|
24
|
-
|
55
|
+
def generated_attribute_methods
|
56
|
+
@generated_attribute_methods ||= (base_class == self ? super : base_class.generated_attribute_methods)
|
57
|
+
end
|
58
|
+
|
59
|
+
def generated_external_attribute_methods
|
60
|
+
@generated_external_attribute_methods ||= begin
|
61
|
+
if base_class == self
|
62
|
+
# We will define the methods as instance methods, but will call them as singleton
|
63
|
+
# methods. This allows us to use method_defined? to check if the method exists,
|
64
|
+
# which is fast and won't give any false positives from the ancestors (because
|
65
|
+
# there are no ancestors).
|
66
|
+
Module.new { extend self }
|
67
|
+
else
|
68
|
+
base_class.generated_external_attribute_methods
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def undefine_attribute_methods
|
74
|
+
if base_class == self
|
75
|
+
super
|
76
|
+
@attribute_methods_generated = false
|
77
|
+
else
|
78
|
+
base_class.undefine_attribute_methods
|
79
|
+
end
|
25
80
|
end
|
26
81
|
|
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
82
|
def instance_method_already_implemented?(method_name)
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
@@_defined_activerecord_methods ||= defined_activerecord_methods
|
38
|
-
raise DangerousAttributeError, "#{method_name} is defined by ActiveRecord" if @@_defined_activerecord_methods.include?(method_name)
|
39
|
-
@_defined_class_methods.include?(method_name)
|
83
|
+
if dangerous_attribute_method?(method_name)
|
84
|
+
raise DangerousAttributeError, "#{method_name} is defined by ActiveRecord"
|
85
|
+
end
|
86
|
+
|
87
|
+
super
|
40
88
|
end
|
41
89
|
|
42
|
-
|
90
|
+
# A method name is 'dangerous' if it is already defined by Active Record, but
|
91
|
+
# not by any ancestors. (So 'puts' is not dangerous but 'save' is.)
|
92
|
+
def dangerous_attribute_method?(method_name)
|
43
93
|
active_record = ActiveRecord::Base
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
94
|
+
superclass = ActiveRecord::Base.superclass
|
95
|
+
|
96
|
+
(active_record.method_defined?(method_name) ||
|
97
|
+
active_record.private_method_defined?(method_name)) &&
|
98
|
+
!superclass.method_defined?(method_name) &&
|
99
|
+
!superclass.private_method_defined?(method_name)
|
100
|
+
end
|
101
|
+
|
102
|
+
def attribute_method?(attribute)
|
103
|
+
super || (table_exists? && column_names.include?(attribute.to_s.sub(/=$/, '')))
|
104
|
+
end
|
105
|
+
|
106
|
+
# Returns an array of column names as strings if it's not
|
107
|
+
# an abstract class and table exists.
|
108
|
+
# Otherwise it returns an empty array.
|
109
|
+
def attribute_names
|
110
|
+
@attribute_names ||= if !abstract_class? && table_exists?
|
111
|
+
column_names
|
112
|
+
else
|
113
|
+
[]
|
114
|
+
end
|
48
115
|
end
|
49
116
|
end
|
50
117
|
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
118
|
+
# If we haven't generated any methods yet, generate them, then
|
119
|
+
# see if we've created the method we're looking for.
|
120
|
+
def method_missing(method, *args, &block)
|
121
|
+
unless self.class.attribute_methods_generated?
|
55
122
|
self.class.define_attribute_methods
|
56
|
-
|
57
|
-
|
58
|
-
|
123
|
+
|
124
|
+
if respond_to_without_attributes?(method)
|
125
|
+
send(method, *args, &block)
|
126
|
+
else
|
127
|
+
super
|
128
|
+
end
|
59
129
|
else
|
60
130
|
super
|
61
131
|
end
|
62
132
|
end
|
63
133
|
|
134
|
+
def attribute_missing(match, *args, &block)
|
135
|
+
if self.class.columns_hash[match.attr_name]
|
136
|
+
ActiveSupport::Deprecation.warn(
|
137
|
+
"The method `#{match.method_name}', matching the attribute `#{match.attr_name}' has " \
|
138
|
+
"dispatched through method_missing. This shouldn't happen, because `#{match.attr_name}' " \
|
139
|
+
"is a column of the table. If this error has happened through normal usage of Active " \
|
140
|
+
"Record (rather than through your own code or external libraries), please report it as " \
|
141
|
+
"a bug."
|
142
|
+
)
|
143
|
+
end
|
144
|
+
|
145
|
+
super
|
146
|
+
end
|
147
|
+
|
64
148
|
def respond_to?(name, include_private = false)
|
65
149
|
self.class.define_attribute_methods unless self.class.attribute_methods_generated?
|
66
150
|
super
|
67
151
|
end
|
68
152
|
|
153
|
+
# Returns true if the given attribute is in the attributes hash
|
154
|
+
def has_attribute?(attr_name)
|
155
|
+
@attributes.has_key?(attr_name.to_s)
|
156
|
+
end
|
157
|
+
|
158
|
+
# Returns an array of names for the attributes available on this object.
|
159
|
+
def attribute_names
|
160
|
+
@attributes.keys
|
161
|
+
end
|
162
|
+
|
163
|
+
# Returns a hash of all the attributes with their names as keys and the values of the attributes as values.
|
164
|
+
def attributes
|
165
|
+
Hash[@attributes.map { |name, _| [name, read_attribute(name)] }]
|
166
|
+
end
|
167
|
+
|
168
|
+
# Returns an <tt>#inspect</tt>-like string for the value of the
|
169
|
+
# attribute +attr_name+. String attributes are truncated upto 50
|
170
|
+
# characters, and Date and Time attributes are returned in the
|
171
|
+
# <tt>:db</tt> format. Other attributes return the value of
|
172
|
+
# <tt>#inspect</tt> without modification.
|
173
|
+
#
|
174
|
+
# person = Person.create!(:name => "David Heinemeier Hansson " * 3)
|
175
|
+
#
|
176
|
+
# person.attribute_for_inspect(:name)
|
177
|
+
# # => '"David Heinemeier Hansson David Heinemeier Hansson D..."'
|
178
|
+
#
|
179
|
+
# person.attribute_for_inspect(:created_at)
|
180
|
+
# # => '"2009-01-12 04:48:57"'
|
181
|
+
def attribute_for_inspect(attr_name)
|
182
|
+
value = read_attribute(attr_name)
|
183
|
+
|
184
|
+
if value.is_a?(String) && value.length > 50
|
185
|
+
"#{value[0..50]}...".inspect
|
186
|
+
elsif value.is_a?(Date) || value.is_a?(Time)
|
187
|
+
%("#{value.to_s(:db)}")
|
188
|
+
else
|
189
|
+
value.inspect
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
# Returns true if the specified +attribute+ has been set by the user or by a database load and is neither
|
194
|
+
# nil nor empty? (the latter only applies to objects that respond to empty?, most notably Strings).
|
195
|
+
def attribute_present?(attribute)
|
196
|
+
value = read_attribute(attribute)
|
197
|
+
!value.nil? || (value.respond_to?(:empty?) && !value.empty?)
|
198
|
+
end
|
199
|
+
|
200
|
+
# Returns the column object for the named attribute.
|
201
|
+
def column_for_attribute(name)
|
202
|
+
self.class.columns_hash[name.to_s]
|
203
|
+
end
|
204
|
+
|
69
205
|
protected
|
70
|
-
|
71
|
-
|
206
|
+
|
207
|
+
def clone_attributes(reader_method = :read_attribute, attributes = {})
|
208
|
+
attribute_names.each do |name|
|
209
|
+
attributes[name] = clone_attribute_value(reader_method, name)
|
72
210
|
end
|
211
|
+
attributes
|
212
|
+
end
|
213
|
+
|
214
|
+
def clone_attribute_value(reader_method, attribute_name)
|
215
|
+
value = send(reader_method, attribute_name)
|
216
|
+
value.duplicable? ? value.clone : value
|
217
|
+
rescue TypeError, NoMethodError
|
218
|
+
value
|
219
|
+
end
|
220
|
+
|
221
|
+
# Returns a copy of the attributes hash where all the values have been safely quoted for use in
|
222
|
+
# an Arel insert/update method.
|
223
|
+
def arel_attributes_values(include_primary_key = true, include_readonly_attributes = true, attribute_names = @attributes.keys)
|
224
|
+
attrs = {}
|
225
|
+
klass = self.class
|
226
|
+
arel_table = klass.arel_table
|
227
|
+
|
228
|
+
attribute_names.each do |name|
|
229
|
+
if (column = column_for_attribute(name)) && (include_primary_key || !column.primary)
|
230
|
+
|
231
|
+
if include_readonly_attributes || !self.class.readonly_attributes.include?(name)
|
232
|
+
|
233
|
+
value = if klass.serialized_attributes.include?(name)
|
234
|
+
@attributes[name].serialized_value
|
235
|
+
else
|
236
|
+
# FIXME: we need @attributes to be used consistently.
|
237
|
+
# If the values stored in @attributes were already type
|
238
|
+
# casted, this code could be simplified
|
239
|
+
read_attribute(name)
|
240
|
+
end
|
241
|
+
|
242
|
+
attrs[arel_table[name]] = value
|
243
|
+
end
|
244
|
+
end
|
245
|
+
end
|
246
|
+
|
247
|
+
attrs
|
248
|
+
end
|
249
|
+
|
250
|
+
def attribute_method?(attr_name)
|
251
|
+
attr_name == 'id' || (defined?(@attributes) && @attributes.include?(attr_name))
|
252
|
+
end
|
73
253
|
end
|
74
254
|
end
|