activerecord 1.10.1 → 1.11.0
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 +187 -19
- data/RUNNING_UNIT_TESTS +11 -0
- data/lib/active_record.rb +3 -1
- data/lib/active_record/acts/list.rb +25 -14
- data/lib/active_record/acts/nested_set.rb +4 -4
- data/lib/active_record/acts/tree.rb +18 -1
- data/lib/active_record/associations.rb +90 -17
- data/lib/active_record/associations/association_collection.rb +44 -5
- data/lib/active_record/associations/has_and_belongs_to_many_association.rb +17 -4
- data/lib/active_record/associations/has_many_association.rb +13 -3
- data/lib/active_record/associations/has_one_association.rb +19 -0
- data/lib/active_record/base.rb +292 -268
- data/lib/active_record/callbacks.rb +14 -14
- data/lib/active_record/connection_adapters/abstract_adapter.rb +137 -75
- data/lib/active_record/connection_adapters/db2_adapter.rb +10 -8
- data/lib/active_record/connection_adapters/mysql_adapter.rb +91 -64
- data/lib/active_record/connection_adapters/oci_adapter.rb +6 -6
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +113 -60
- data/lib/active_record/connection_adapters/sqlite_adapter.rb +15 -12
- data/lib/active_record/connection_adapters/sqlserver_adapter.rb +159 -132
- data/lib/active_record/fixtures.rb +59 -12
- data/lib/active_record/locking.rb +10 -9
- data/lib/active_record/migration.rb +112 -5
- data/lib/active_record/query_cache.rb +64 -0
- data/lib/active_record/timestamp.rb +10 -8
- data/lib/active_record/validations.rb +121 -26
- data/rakefile +16 -10
- data/test/aaa_create_tables_test.rb +26 -48
- data/test/abstract_unit.rb +3 -0
- data/test/aggregations_test.rb +19 -19
- data/test/association_callbacks_test.rb +110 -0
- data/test/associations_go_eager_test.rb +48 -14
- data/test/associations_test.rb +344 -142
- data/test/base_test.rb +150 -31
- data/test/binary_test.rb +7 -0
- data/test/callbacks_test.rb +24 -5
- data/test/column_alias_test.rb +2 -2
- data/test/connections/native_sqlserver_odbc/connection.rb +26 -0
- data/test/deprecated_associations_test.rb +27 -28
- data/test/deprecated_finder_test.rb +8 -9
- data/test/finder_test.rb +52 -17
- data/test/fixtures/author.rb +39 -0
- data/test/fixtures/categories.yml +7 -0
- data/test/fixtures/categories_posts.yml +8 -0
- data/test/fixtures/category.rb +2 -0
- data/test/fixtures/comment.rb +3 -1
- data/test/fixtures/comments.yml +43 -1
- data/test/fixtures/companies.yml +14 -0
- data/test/fixtures/company.rb +1 -1
- data/test/fixtures/computers.yml +2 -1
- data/test/fixtures/db_definitions/db2.sql +7 -2
- data/test/fixtures/db_definitions/mysql.drop.sql +2 -0
- data/test/fixtures/db_definitions/mysql.sql +11 -6
- data/test/fixtures/db_definitions/oci.sql +7 -2
- data/test/fixtures/db_definitions/postgresql.drop.sql +3 -1
- data/test/fixtures/db_definitions/postgresql.sql +8 -5
- data/test/fixtures/db_definitions/sqlite.drop.sql +2 -0
- data/test/fixtures/db_definitions/sqlite.sql +9 -4
- data/test/fixtures/db_definitions/sqlserver.drop.sql +2 -0
- data/test/fixtures/db_definitions/sqlserver.sql +12 -7
- data/test/fixtures/developer.rb +8 -1
- data/test/fixtures/migrations/3_innocent_jointable.rb +12 -0
- data/test/fixtures/post.rb +8 -2
- data/test/fixtures/posts.yml +21 -0
- data/test/fixtures/project.rb +14 -1
- data/test/fixtures/subscriber.rb +3 -0
- data/test/fixtures_test.rb +14 -0
- data/test/inheritance_test.rb +30 -22
- data/test/lifecycle_test.rb +3 -4
- data/test/locking_test.rb +2 -4
- data/test/migration_test.rb +186 -0
- data/test/mixin_nested_set_test.rb +19 -19
- data/test/mixin_test.rb +88 -88
- data/test/modules_test.rb +5 -10
- data/test/multiple_db_test.rb +2 -0
- data/test/pk_test.rb +8 -12
- data/test/reflection_test.rb +8 -4
- data/test/schema_test_postgresql.rb +63 -0
- data/test/thread_safety_test.rb +4 -1
- data/test/transactions_test.rb +9 -2
- data/test/unconnected_test.rb +1 -0
- data/test/validations_test.rb +151 -8
- metadata +11 -5
- data/test/migration_mysql.rb +0 -104
@@ -66,7 +66,11 @@ module ActiveRecord
|
|
66
66
|
load_target.select { |record| ids.include?(record.id) }
|
67
67
|
end
|
68
68
|
else
|
69
|
-
|
69
|
+
conditions = "#{@finder_sql}"
|
70
|
+
if sanitized_conditions = sanitize_sql(options[:conditions])
|
71
|
+
conditions << " AND #{sanitized_conditions}"
|
72
|
+
end
|
73
|
+
options[:conditions] = conditions
|
70
74
|
|
71
75
|
if options[:order] && @options[:order]
|
72
76
|
options[:order] = "#{options[:order]}, #{@options[:order]}"
|
@@ -74,7 +78,9 @@ module ActiveRecord
|
|
74
78
|
options[:order] = @options[:order]
|
75
79
|
end
|
76
80
|
|
77
|
-
|
81
|
+
# Pass through args exactly as we received them.
|
82
|
+
args << options
|
83
|
+
@association_class.find(*args)
|
78
84
|
end
|
79
85
|
end
|
80
86
|
|
@@ -92,13 +98,17 @@ module ActiveRecord
|
|
92
98
|
end
|
93
99
|
|
94
100
|
def count_records
|
95
|
-
if has_cached_counter?
|
101
|
+
count = if has_cached_counter?
|
96
102
|
@owner.send(:read_attribute, cached_counter_attribute_name)
|
97
103
|
elsif @options[:counter_sql]
|
98
104
|
@association_class.count_by_sql(@counter_sql)
|
99
105
|
else
|
100
106
|
@association_class.count(@counter_sql)
|
101
107
|
end
|
108
|
+
|
109
|
+
@target = [] and loaded if count == 0
|
110
|
+
|
111
|
+
return count
|
102
112
|
end
|
103
113
|
|
104
114
|
def has_cached_counter?
|
@@ -7,6 +7,25 @@ module ActiveRecord
|
|
7
7
|
construct_sql
|
8
8
|
end
|
9
9
|
|
10
|
+
def create(attributes = {}, replace_existing = true)
|
11
|
+
record = build(attributes, replace_existing)
|
12
|
+
record.save
|
13
|
+
record
|
14
|
+
end
|
15
|
+
|
16
|
+
def build(attributes = {}, replace_existing = true)
|
17
|
+
record = @association_class.new(attributes)
|
18
|
+
|
19
|
+
if replace_existing
|
20
|
+
replace(record, true)
|
21
|
+
else
|
22
|
+
record[@association_class_primary_key_name] = @owner.id unless @owner.new_record?
|
23
|
+
self.target = record
|
24
|
+
end
|
25
|
+
|
26
|
+
record
|
27
|
+
end
|
28
|
+
|
10
29
|
def replace(obj, dont_save = false)
|
11
30
|
load_target
|
12
31
|
unless @target.nil?
|
data/lib/active_record/base.rb
CHANGED
@@ -35,91 +35,91 @@ module ActiveRecord #:nodoc:
|
|
35
35
|
@message = message
|
36
36
|
end
|
37
37
|
end
|
38
|
-
|
38
|
+
|
39
39
|
class MultiparameterAssignmentErrors < ActiveRecordError #:nodoc:
|
40
40
|
attr_reader :errors
|
41
41
|
def initialize(errors)
|
42
42
|
@errors = errors
|
43
43
|
end
|
44
44
|
end
|
45
|
-
|
45
|
+
|
46
46
|
# Active Record objects doesn't specify their attributes directly, but rather infer them from the table definition with
|
47
47
|
# which they're linked. Adding, removing, and changing attributes and their type is done directly in the database. Any change
|
48
48
|
# is instantly reflected in the Active Record objects. The mapping that binds a given Active Record class to a certain
|
49
|
-
# database table will happen automatically in most common cases, but can be overwritten for the uncommon ones.
|
50
|
-
#
|
49
|
+
# database table will happen automatically in most common cases, but can be overwritten for the uncommon ones.
|
50
|
+
#
|
51
51
|
# See the mapping rules in table_name and the full example in link:files/README.html for more insight.
|
52
|
-
#
|
52
|
+
#
|
53
53
|
# == Creation
|
54
|
-
#
|
54
|
+
#
|
55
55
|
# Active Records accepts constructor parameters either in a hash or as a block. The hash method is especially useful when
|
56
56
|
# you're receiving the data from somewhere else, like a HTTP request. It works like this:
|
57
|
-
#
|
57
|
+
#
|
58
58
|
# user = User.new(:name => "David", :occupation => "Code Artist")
|
59
59
|
# user.name # => "David"
|
60
|
-
#
|
60
|
+
#
|
61
61
|
# You can also use block initialization:
|
62
|
-
#
|
62
|
+
#
|
63
63
|
# user = User.new do |u|
|
64
64
|
# u.name = "David"
|
65
65
|
# u.occupation = "Code Artist"
|
66
66
|
# end
|
67
|
-
#
|
67
|
+
#
|
68
68
|
# And of course you can just create a bare object and specify the attributes after the fact:
|
69
|
-
#
|
69
|
+
#
|
70
70
|
# user = User.new
|
71
71
|
# user.name = "David"
|
72
72
|
# user.occupation = "Code Artist"
|
73
|
-
#
|
73
|
+
#
|
74
74
|
# == Conditions
|
75
|
-
#
|
75
|
+
#
|
76
76
|
# Conditions can either be specified as a string or an array representing the WHERE-part of an SQL statement.
|
77
77
|
# The array form is to be used when the condition input is tainted and requires sanitization. The string form can
|
78
78
|
# be used for statements that doesn't involve tainted data. Examples:
|
79
|
-
#
|
79
|
+
#
|
80
80
|
# User < ActiveRecord::Base
|
81
81
|
# def self.authenticate_unsafely(user_name, password)
|
82
|
-
#
|
82
|
+
# find(:first, :conditions => "user_name = '#{user_name}' AND password = '#{password}'")
|
83
83
|
# end
|
84
|
-
#
|
84
|
+
#
|
85
85
|
# def self.authenticate_safely(user_name, password)
|
86
|
-
#
|
86
|
+
# find(:first, :conditions => [ "user_name = ? AND password = ?", user_name, password ])
|
87
87
|
# end
|
88
88
|
# end
|
89
|
-
#
|
89
|
+
#
|
90
90
|
# The <tt>authenticate_unsafely</tt> method inserts the parameters directly into the query and is thus susceptible to SQL-injection
|
91
|
-
# attacks if the <tt>user_name</tt> and +password+ parameters come directly from a HTTP request. The <tt>authenticate_safely</tt> method,
|
91
|
+
# attacks if the <tt>user_name</tt> and +password+ parameters come directly from a HTTP request. The <tt>authenticate_safely</tt> method,
|
92
92
|
# on the other hand, will sanitize the <tt>user_name</tt> and +password+ before inserting them in the query, which will ensure that
|
93
93
|
# an attacker can't escape the query and fake the login (or worse).
|
94
94
|
#
|
95
95
|
# When using multiple parameters in the conditions, it can easily become hard to read exactly what the fourth or fifth
|
96
|
-
# question mark is supposed to represent. In those cases, you can resort to named bind variables instead. That's done by replacing
|
96
|
+
# question mark is supposed to represent. In those cases, you can resort to named bind variables instead. That's done by replacing
|
97
97
|
# the question marks with symbols and supplying a hash with values for the matching symbol keys:
|
98
98
|
#
|
99
|
-
# Company.find(:first, [
|
100
|
-
# "id = :id AND name = :name AND division = :division AND created_at > :accounting_date",
|
99
|
+
# Company.find(:first, [
|
100
|
+
# "id = :id AND name = :name AND division = :division AND created_at > :accounting_date",
|
101
101
|
# { :id => 3, :name => "37signals", :division => "First", :accounting_date => '2005-01-01' }
|
102
102
|
# ])
|
103
103
|
#
|
104
104
|
# == Overwriting default accessors
|
105
|
-
#
|
105
|
+
#
|
106
106
|
# All column values are automatically available through basic accessors on the Active Record object, but some times you
|
107
107
|
# want to specialize this behavior. This can be done by either by overwriting the default accessors (using the same
|
108
108
|
# name as the attribute) calling read_attribute(attr_name) and write_attribute(attr_name, value) to actually change things.
|
109
109
|
# Example:
|
110
|
-
#
|
110
|
+
#
|
111
111
|
# class Song < ActiveRecord::Base
|
112
112
|
# # Uses an integer of seconds to hold the length of the song
|
113
|
-
#
|
113
|
+
#
|
114
114
|
# def length=(minutes)
|
115
115
|
# write_attribute(:length, minutes * 60)
|
116
116
|
# end
|
117
|
-
#
|
117
|
+
#
|
118
118
|
# def length
|
119
119
|
# read_attribute(:length) / 60
|
120
120
|
# end
|
121
121
|
# end
|
122
|
-
#
|
122
|
+
#
|
123
123
|
# You can alternatively use self[:attribute]=(value) and self[:attribute] instead of write_attribute(:attribute, vaule) and
|
124
124
|
# read_attribute(:attribute) as a shorter form.
|
125
125
|
#
|
@@ -127,7 +127,7 @@ module ActiveRecord #:nodoc:
|
|
127
127
|
#
|
128
128
|
# Some times you want to be able to read the raw attribute data without having the column-determined type cast run its course first.
|
129
129
|
# That can be done by using the <attribute>_before_type_cast accessors that all attributes have. For example, if your Account model
|
130
|
-
# has a balance attribute, you can call account.balance_before_type_cast or account.id_before_type_cast.
|
130
|
+
# has a balance attribute, you can call account.balance_before_type_cast or account.id_before_type_cast.
|
131
131
|
#
|
132
132
|
# This is especially useful in validation situations where the user might supply a string for an integer field and you want to display
|
133
133
|
# the original string back in an error message. Accessing the attribute normally would type cast the string to 0, which isn't what you
|
@@ -136,43 +136,45 @@ module ActiveRecord #:nodoc:
|
|
136
136
|
# == Dynamic attribute-based finders
|
137
137
|
#
|
138
138
|
# Dynamic attribute-based finders are a cleaner way of getting objects by simple queries without turning to SQL. They work by
|
139
|
-
# appending the name of an attribute to <tt>find_by_</tt>, so you get finders like
|
140
|
-
#
|
141
|
-
#
|
139
|
+
# appending the name of an attribute to <tt>find_by_</tt> or <tt>find_all_by_</tt>, so you get finders like Person.find_by_user_name,
|
140
|
+
# Person.find_all_by_last_name, Payment.find_by_transaction_id. So instead of writing
|
141
|
+
# <tt>Person.find(:first, ["user_name = ?", user_name])</tt>, you just do <tt>Person.find_by_user_name(user_name)</tt>.
|
142
|
+
# And instead of writing <tt>Person.find(:all, ["last_name = ?", last_name])</tt>, you just do <tt>Person.find_all_by_last_name(last_name)</tt>.
|
143
|
+
#
|
142
144
|
# It's also possible to use multiple attributes in the same find by separating them with "_and_", so you get finders like
|
143
145
|
# <tt>Person.find_by_user_name_and_password</tt> or even <tt>Payment.find_by_purchaser_and_state_and_country</tt>. So instead of writing
|
144
|
-
# <tt>Person.find(:first, ["user_name = ? AND password = ?", user_name, password])</tt>, you just do
|
146
|
+
# <tt>Person.find(:first, ["user_name = ? AND password = ?", user_name, password])</tt>, you just do
|
145
147
|
# <tt>Person.find_by_user_name_and_password(user_name, password)</tt>.
|
146
|
-
#
|
148
|
+
#
|
147
149
|
# It's even possible to use all the additional parameters to find. For example, the full interface for Payment.find_all_by_amount
|
148
150
|
# is actually Payment.find_all_by_amount(amount, options). And the full interface to Person.find_by_user_name is
|
149
151
|
# actually Person.find_by_user_name(user_name, options). So you could call <tt>Payment.find_all_by_amount(50, :order => "created_on")</tt>.
|
150
152
|
#
|
151
153
|
# == Saving arrays, hashes, and other non-mappable objects in text columns
|
152
|
-
#
|
153
|
-
# Active Record can serialize any object in text columns using YAML. To do so, you must specify this with a call to the class method +serialize+.
|
154
|
+
#
|
155
|
+
# Active Record can serialize any object in text columns using YAML. To do so, you must specify this with a call to the class method +serialize+.
|
154
156
|
# This makes it possible to store arrays, hashes, and other non-mappeable objects without doing any additional work. Example:
|
155
|
-
#
|
157
|
+
#
|
156
158
|
# class User < ActiveRecord::Base
|
157
159
|
# serialize :preferences
|
158
160
|
# end
|
159
|
-
#
|
161
|
+
#
|
160
162
|
# user = User.create(:preferences) => { "background" => "black", "display" => large })
|
161
163
|
# User.find(user.id).preferences # => { "background" => "black", "display" => large }
|
162
|
-
#
|
163
|
-
# You can also specify an class option as the second parameter that'll raise an exception if a serialized object is retrieved as a
|
164
|
+
#
|
165
|
+
# You can also specify an class option as the second parameter that'll raise an exception if a serialized object is retrieved as a
|
164
166
|
# descendent of a class not in the hierarchy. Example:
|
165
|
-
#
|
167
|
+
#
|
166
168
|
# class User < ActiveRecord::Base
|
167
169
|
# serialize :preferences, Hash
|
168
170
|
# end
|
169
|
-
#
|
171
|
+
#
|
170
172
|
# user = User.create(:preferences => %w( one two three ))
|
171
173
|
# User.find(user.id).preferences # raises SerializationTypeMismatch
|
172
|
-
#
|
174
|
+
#
|
173
175
|
# == Single table inheritance
|
174
176
|
#
|
175
|
-
# Active Record allows inheritance by storing the name of the class in a column that by default is called "type" (can be changed
|
177
|
+
# Active Record allows inheritance by storing the name of the class in a column that by default is called "type" (can be changed
|
176
178
|
# by overwriting <tt>Base.inheritance_column</tt>). This means that an inheritance looking like this:
|
177
179
|
#
|
178
180
|
# class Company < ActiveRecord::Base; end
|
@@ -188,11 +190,11 @@ module ActiveRecord #:nodoc:
|
|
188
190
|
#
|
189
191
|
# Note, all the attributes for all the cases are kept in the same table. Read more:
|
190
192
|
# http://www.martinfowler.com/eaaCatalog/singleTableInheritance.html
|
191
|
-
#
|
193
|
+
#
|
192
194
|
# == Connection to multiple databases in different models
|
193
195
|
#
|
194
196
|
# Connections are usually created through ActiveRecord::Base.establish_connection and retrieved by ActiveRecord::Base.connection.
|
195
|
-
# All classes inheriting from ActiveRecord::Base will use this connection. But you can also set a class-specific connection.
|
197
|
+
# All classes inheriting from ActiveRecord::Base will use this connection. But you can also set a class-specific connection.
|
196
198
|
# For example, if Course is a ActiveRecord::Base, but resides in a different database you can just say Course.establish_connection
|
197
199
|
# and Course *and all its subclasses* will use this connection instead.
|
198
200
|
#
|
@@ -200,44 +202,44 @@ module ActiveRecord #:nodoc:
|
|
200
202
|
# requested, the retrieve_connection method will go up the class-hierarchy until a connection is found in the connection pool.
|
201
203
|
#
|
202
204
|
# == Exceptions
|
203
|
-
#
|
205
|
+
#
|
204
206
|
# * +ActiveRecordError+ -- generic error class and superclass of all other errors raised by Active Record
|
205
|
-
# * +AdapterNotSpecified+ -- the configuration hash used in <tt>establish_connection</tt> didn't include a
|
207
|
+
# * +AdapterNotSpecified+ -- the configuration hash used in <tt>establish_connection</tt> didn't include a
|
206
208
|
# <tt>:adapter</tt> key.
|
207
209
|
# * +AdapterNotSpecified+ -- the <tt>:adapter</tt> key used in <tt>establish_connection</tt> specified an non-existent adapter
|
208
|
-
# (or a bad spelling of an existing one).
|
209
|
-
# * +AssociationTypeMismatch+ -- the object assigned to the association wasn't of the type specified in the association definition.
|
210
|
-
# * +SerializationTypeMismatch+ -- the object serialized wasn't of the class specified as the second parameter.
|
210
|
+
# (or a bad spelling of an existing one).
|
211
|
+
# * +AssociationTypeMismatch+ -- the object assigned to the association wasn't of the type specified in the association definition.
|
212
|
+
# * +SerializationTypeMismatch+ -- the object serialized wasn't of the class specified as the second parameter.
|
211
213
|
# * +ConnectionNotEstablished+ -- no connection has been established. Use <tt>establish_connection</tt> before querying.
|
212
|
-
# * +RecordNotFound+ -- no record responded to the find* method.
|
214
|
+
# * +RecordNotFound+ -- no record responded to the find* method.
|
213
215
|
# Either the row with the given ID doesn't exist or the row didn't meet the additional restrictions.
|
214
216
|
# * +StatementInvalid+ -- the database server rejected the SQL statement. The precise error is added in the message.
|
215
217
|
# Either the record with the given ID doesn't exist or the record didn't meet the additional restrictions.
|
216
|
-
# * +MultiparameterAssignmentErrors+ -- collection of errors that occurred during a mass assignment using the
|
217
|
-
# +attributes=+ method. The +errors+ property of this exception contains an array of +AttributeAssignmentError+
|
218
|
+
# * +MultiparameterAssignmentErrors+ -- collection of errors that occurred during a mass assignment using the
|
219
|
+
# +attributes=+ method. The +errors+ property of this exception contains an array of +AttributeAssignmentError+
|
218
220
|
# objects that should be inspected to determine which attributes triggered the errors.
|
219
221
|
# * +AttributeAssignmentError+ -- an error occurred while doing a mass assignment through the +attributes=+ method.
|
220
222
|
# You can inspect the +attribute+ property of the exception object to determine which attribute triggered the error.
|
221
|
-
# *Note*: The attributes listed are class-level attributes (accessible from both the class and instance level).
|
223
|
+
# *Note*: The attributes listed are class-level attributes (accessible from both the class and instance level).
|
222
224
|
# So it's possible to assign a logger to the class through Base.logger= which will then be used by all
|
223
225
|
# instances in the current object space.
|
224
226
|
class Base
|
225
227
|
include ClassInheritableAttributes
|
226
|
-
|
228
|
+
|
227
229
|
# Accepts a logger conforming to the interface of Log4r or the default Ruby 1.8+ Logger class, which is then passed
|
228
230
|
# on to any new database connections made and which can be retrieved on both a class and instance level by calling +logger+.
|
229
231
|
cattr_accessor :logger
|
230
232
|
|
231
233
|
# Returns the connection currently associated with the class. This can
|
232
234
|
# also be used to "borrow" the connection to do database work unrelated
|
233
|
-
# to any of the specific Active Records.
|
235
|
+
# to any of the specific Active Records.
|
234
236
|
def self.connection
|
235
237
|
retrieve_connection
|
236
238
|
end
|
237
239
|
|
238
240
|
# Returns the connection currently associated with the class. This can
|
239
|
-
# also be used to "borrow" the connection to do database work that isn't
|
240
|
-
# easily done without going straight to SQL.
|
241
|
+
# also be used to "borrow" the connection to do database work that isn't
|
242
|
+
# easily done without going straight to SQL.
|
241
243
|
def connection
|
242
244
|
self.class.connection
|
243
245
|
end
|
@@ -249,18 +251,18 @@ module ActiveRecord #:nodoc:
|
|
249
251
|
end
|
250
252
|
|
251
253
|
@@subclasses = {}
|
252
|
-
|
254
|
+
|
253
255
|
cattr_accessor :configurations
|
254
|
-
@@
|
255
|
-
|
256
|
-
# Accessor for the prefix type that will be prepended to every primary key column name. The options are :table_name and
|
256
|
+
@@configurations = {}
|
257
|
+
|
258
|
+
# Accessor for the prefix type that will be prepended to every primary key column name. The options are :table_name and
|
257
259
|
# :table_name_with_underscore. If the first is specified, the Product class will look for "productid" instead of "id" as
|
258
260
|
# the primary column. If the latter is specified, the Product class will look for "product_id" instead of "id". Remember
|
259
|
-
# that this is a global setting for all Active Records.
|
261
|
+
# that this is a global setting for all Active Records.
|
260
262
|
cattr_accessor :primary_key_prefix_type
|
261
263
|
@@primary_key_prefix_type = nil
|
262
264
|
|
263
|
-
# Accessor for the name of the prefix string to prepend to every table name. So if set to "basecamp_", all
|
265
|
+
# Accessor for the name of the prefix string to prepend to every table name. So if set to "basecamp_", all
|
264
266
|
# table names will be named like "basecamp_projects", "basecamp_people", etc. This is a convenient way of creating a namespace
|
265
267
|
# for tables in a shared database. By default, the prefix is the empty string.
|
266
268
|
cattr_accessor :table_name_prefix
|
@@ -333,6 +335,7 @@ module ActiveRecord #:nodoc:
|
|
333
335
|
when :all
|
334
336
|
options[:include] ? find_with_associations(options) : find_by_sql(construct_finder_sql(options))
|
335
337
|
else
|
338
|
+
return args.first if args.first.kind_of?(Array) && args.first.empty?
|
336
339
|
expects_array = args.first.kind_of?(Array)
|
337
340
|
conditions = " AND #{sanitize_sql(options[:conditions])}" if options[:conditions]
|
338
341
|
|
@@ -349,7 +352,7 @@ module ActiveRecord #:nodoc:
|
|
349
352
|
else
|
350
353
|
# Find multiple ids
|
351
354
|
ids_list = ids.map { |id| sanitize(id) }.join(',')
|
352
|
-
result = find(:all, options.merge({ :conditions => "#{table_name}.#{primary_key} IN (#{ids_list})#{conditions}"
|
355
|
+
result = find(:all, options.merge({ :conditions => "#{table_name}.#{primary_key} IN (#{ids_list})#{conditions}"}))
|
353
356
|
if result.size == ids.size
|
354
357
|
return result
|
355
358
|
else
|
@@ -359,13 +362,13 @@ module ActiveRecord #:nodoc:
|
|
359
362
|
end
|
360
363
|
end
|
361
364
|
|
362
|
-
# Works like
|
365
|
+
# Works like find(:all), but requires a complete SQL string. Examples:
|
363
366
|
# Post.find_by_sql "SELECT p.*, c.author FROM posts p, comments c WHERE p.id = c.post_id"
|
364
367
|
# Post.find_by_sql ["SELECT * FROM posts WHERE author = ? AND created > ?", author_id, start_date]
|
365
368
|
def find_by_sql(sql)
|
366
|
-
connection.select_all(sanitize_sql(sql), "#{name} Load").
|
369
|
+
connection.select_all(sanitize_sql(sql), "#{name} Load").collect! { |record| instantiate(record) }
|
367
370
|
end
|
368
|
-
|
371
|
+
|
369
372
|
# Returns true if the given +id+ represents the primary key of a record in the database, false otherwise.
|
370
373
|
# Example:
|
371
374
|
# Person.exists?(5)
|
@@ -385,7 +388,7 @@ module ActiveRecord #:nodoc:
|
|
385
388
|
end
|
386
389
|
end
|
387
390
|
|
388
|
-
# Finds the record from the passed +id+, instantly saves it with the passed +attributes+ (if the validation permits it),
|
391
|
+
# Finds the record from the passed +id+, instantly saves it with the passed +attributes+ (if the validation permits it),
|
389
392
|
# and returns it. If the save fail under validations, the unsaved object is still returned.
|
390
393
|
def update(id, attributes)
|
391
394
|
if id.is_a?(Array)
|
@@ -403,7 +406,7 @@ module ActiveRecord #:nodoc:
|
|
403
406
|
def delete(id)
|
404
407
|
delete_all([ "#{primary_key} IN (?)", id ])
|
405
408
|
end
|
406
|
-
|
409
|
+
|
407
410
|
# Destroys the record with the given +id+ by instantiating the object and calling #destroy (all the callbacks are the triggered).
|
408
411
|
# If an array of ids is provided, all of them are destroyed.
|
409
412
|
def destroy(id)
|
@@ -416,17 +419,17 @@ module ActiveRecord #:nodoc:
|
|
416
419
|
def update_all(updates, conditions = nil)
|
417
420
|
sql = "UPDATE #{table_name} SET #{sanitize_sql(updates)} "
|
418
421
|
add_conditions!(sql, conditions)
|
419
|
-
|
422
|
+
connection.update(sql, "#{name} Update")
|
420
423
|
end
|
421
424
|
|
422
425
|
# Destroys the objects for all the records that matches the +condition+ by instantiating each object and calling
|
423
426
|
# the destroy method. Example:
|
424
427
|
# Person.destroy_all "last_login < '2004-04-04'"
|
425
428
|
def destroy_all(conditions = nil)
|
426
|
-
|
429
|
+
find(:all, :conditions => conditions).each { |object| object.destroy }
|
427
430
|
end
|
428
|
-
|
429
|
-
# Deletes all the records that matches the +condition+ without instantiating the objects first (and hence not
|
431
|
+
|
432
|
+
# Deletes all the records that matches the +condition+ without instantiating the objects first (and hence not
|
430
433
|
# calling the destroy method). Example:
|
431
434
|
# Post.destroy_all "person_id = 5 AND (category = 'Something' OR category = 'Else')"
|
432
435
|
def delete_all(conditions = nil)
|
@@ -434,13 +437,12 @@ module ActiveRecord #:nodoc:
|
|
434
437
|
add_conditions!(sql, conditions)
|
435
438
|
connection.delete(sql, "#{name} Delete all")
|
436
439
|
end
|
437
|
-
|
440
|
+
|
438
441
|
# Returns the number of records that meets the +conditions+. Zero is returned if no records match. Example:
|
439
442
|
# Product.count "sales > 1"
|
440
443
|
def count(conditions = nil, joins = nil)
|
441
|
-
|
442
|
-
sql
|
443
|
-
sql << ", #{joins} " if joins
|
444
|
+
sql = "SELECT COUNT(*) FROM #{table_name} "
|
445
|
+
sql << " #{joins} " if joins
|
444
446
|
add_conditions!(sql, conditions)
|
445
447
|
count_by_sql(sql)
|
446
448
|
end
|
@@ -451,15 +453,14 @@ module ActiveRecord #:nodoc:
|
|
451
453
|
sql = sanitize_conditions(sql)
|
452
454
|
rows = connection.select_one(sql, "#{name} Count")
|
453
455
|
|
454
|
-
if rows.nil?
|
455
|
-
|
456
|
+
if !rows.nil? and count = rows.values.first
|
457
|
+
count.to_i
|
456
458
|
else
|
457
|
-
|
458
|
-
return count ? count.to_i : 0
|
459
|
+
0
|
459
460
|
end
|
460
461
|
end
|
461
|
-
|
462
|
-
# Increments the specified counter by one. So <tt>DiscussionBoard.increment_counter("post_count",
|
462
|
+
|
463
|
+
# Increments the specified counter by one. So <tt>DiscussionBoard.increment_counter("post_count",
|
463
464
|
# discussion_board_id)</tt> would increment the "post_count" counter on the board responding to discussion_board_id.
|
464
465
|
# This is used for caching aggregate values, so that they doesn't need to be computed every time. Especially important
|
465
466
|
# for looping over a collection where each element require a number of aggregate values. Like the DiscussionBoard
|
@@ -473,7 +474,7 @@ module ActiveRecord #:nodoc:
|
|
473
474
|
update_all "#{counter_name} = #{counter_name} - 1", "#{primary_key} = #{quote(id)}"
|
474
475
|
end
|
475
476
|
|
476
|
-
# Attributes named in this macro are protected from mass-assignment, such as <tt>new(attributes)</tt> and
|
477
|
+
# Attributes named in this macro are protected from mass-assignment, such as <tt>new(attributes)</tt> and
|
477
478
|
# <tt>attributes=(attributes)</tt>. Their assignment will simply be ignored. Instead, you can use the direct writer
|
478
479
|
# methods to do assignment. This is meant to protect sensitive attributes to be overwritten by URL/form hackers. Example:
|
479
480
|
#
|
@@ -491,20 +492,20 @@ module ActiveRecord #:nodoc:
|
|
491
492
|
def attr_protected(*attributes)
|
492
493
|
write_inheritable_array("attr_protected", attributes)
|
493
494
|
end
|
494
|
-
|
495
|
+
|
495
496
|
# Returns an array of all the attributes that have been protected from mass-assignment.
|
496
497
|
def protected_attributes # :nodoc:
|
497
498
|
read_inheritable_attribute("attr_protected")
|
498
499
|
end
|
499
500
|
|
500
|
-
# If this macro is used, only those attributed named in it will be accessible for mass-assignment, such as
|
501
|
+
# If this macro is used, only those attributed named in it will be accessible for mass-assignment, such as
|
501
502
|
# <tt>new(attributes)</tt> and <tt>attributes=(attributes)</tt>. This is the more conservative choice for mass-assignment
|
502
503
|
# protection. If you'd rather start from an all-open default and restrict attributes as needed, have a look at
|
503
504
|
# attr_protected.
|
504
505
|
def attr_accessible(*attributes)
|
505
506
|
write_inheritable_array("attr_accessible", attributes)
|
506
507
|
end
|
507
|
-
|
508
|
+
|
508
509
|
# Returns an array of all the attributes that have been made accessible to mass-assignment.
|
509
510
|
def accessible_attributes # :nodoc:
|
510
511
|
read_inheritable_attribute("attr_accessible")
|
@@ -514,12 +515,12 @@ module ActiveRecord #:nodoc:
|
|
514
515
|
# after loading from the database. The serialization is done through YAML. If +class_name+ is specified, the serialized
|
515
516
|
# object must be of that class on retrieval or +SerializationTypeMismatch+ will be raised.
|
516
517
|
def serialize(attr_name, class_name = Object)
|
517
|
-
|
518
|
+
serialized_attributes[attr_name.to_s] = class_name
|
518
519
|
end
|
519
|
-
|
520
|
+
|
520
521
|
# Returns a hash of all the attributes that have been specified for serialization as keys and their class restriction as values.
|
521
522
|
def serialized_attributes
|
522
|
-
read_inheritable_attribute("attr_serialized")
|
523
|
+
read_inheritable_attribute("attr_serialized") or write_inheritable_attribute("attr_serialized", {})
|
523
524
|
end
|
524
525
|
|
525
526
|
# Guesses the table name (in forced lower-case) based on the name of the class in the inheritance hierarchy descending
|
@@ -537,7 +538,7 @@ module ActiveRecord #:nodoc:
|
|
537
538
|
# set_table_name "mice"
|
538
539
|
# end
|
539
540
|
def table_name
|
540
|
-
table_name_prefix
|
541
|
+
"#{table_name_prefix}#{undecorated_table_name(class_name_of_active_record_descendant(self))}#{table_name_suffix}"
|
541
542
|
end
|
542
543
|
|
543
544
|
# Defines the primary key field -- can be overridden in subclasses. Overwriting will negate any effect of the
|
@@ -604,26 +605,26 @@ module ActiveRecord #:nodoc:
|
|
604
605
|
# Turns the +table_name+ back into a class name following the reverse rules of +table_name+.
|
605
606
|
def class_name(table_name = table_name) # :nodoc:
|
606
607
|
# remove any prefix and/or suffix from the table name
|
607
|
-
class_name =
|
608
|
-
class_name =
|
609
|
-
|
608
|
+
class_name = table_name[table_name_prefix.length..-(table_name_suffix.length + 1)].camelize
|
609
|
+
class_name = class_name.singularize if pluralize_table_names
|
610
|
+
class_name
|
610
611
|
end
|
611
612
|
|
612
613
|
# Returns an array of column objects for the table associated with this class.
|
613
614
|
def columns
|
614
615
|
@columns ||= connection.columns(table_name, "#{name} Columns")
|
615
616
|
end
|
616
|
-
|
617
|
+
|
617
618
|
# Returns an array of column objects for the table associated with this class.
|
618
619
|
def columns_hash
|
619
620
|
@columns_hash ||= columns.inject({}) { |hash, column| hash[column.name] = column; hash }
|
620
621
|
end
|
621
|
-
|
622
|
+
|
622
623
|
def column_names
|
623
|
-
@column_names ||=
|
624
|
+
@column_names ||= columns.map { |column| column.name }
|
624
625
|
end
|
625
626
|
|
626
|
-
# Returns an array of columns objects where the primary id, all columns ending in "_id" or "_count",
|
627
|
+
# Returns an array of columns objects where the primary id, all columns ending in "_id" or "_count",
|
627
628
|
# and columns used for single table inheritance has been removed.
|
628
629
|
def content_columns
|
629
630
|
@content_columns ||= columns.reject { |c| c.name == primary_key || c.name =~ /(_id|_count)$/ || c.name == inheritance_column }
|
@@ -631,9 +632,9 @@ module ActiveRecord #:nodoc:
|
|
631
632
|
|
632
633
|
# Returns a hash of all the methods added to query each of the columns in the table with the name of the method as the key
|
633
634
|
# and true as the value. This makes it possible to do O(1) lookups in respond_to? to check if a given method for attribute
|
634
|
-
# is available.
|
635
|
+
# is available.
|
635
636
|
def column_methods_hash
|
636
|
-
@dynamic_methods_hash ||=
|
637
|
+
@dynamic_methods_hash ||= column_names.inject(Hash.new(false)) do |methods, attr|
|
637
638
|
methods[attr.to_sym] = true
|
638
639
|
methods["#{attr}=".to_sym] = true
|
639
640
|
methods["#{attr}?".to_sym] = true
|
@@ -641,7 +642,7 @@ module ActiveRecord #:nodoc:
|
|
641
642
|
methods
|
642
643
|
end
|
643
644
|
end
|
644
|
-
|
645
|
+
|
645
646
|
# Resets all the cached information about columns, which will cause they to be reloaded on the next request.
|
646
647
|
def reset_column_information
|
647
648
|
@column_names = @columns = @columns_hash = @content_columns = @dynamic_methods_hash = nil
|
@@ -657,9 +658,9 @@ module ActiveRecord #:nodoc:
|
|
657
658
|
def human_attribute_name(attribute_key_name) #:nodoc:
|
658
659
|
attribute_key_name.humanize
|
659
660
|
end
|
660
|
-
|
661
|
+
|
661
662
|
def descends_from_active_record? # :nodoc:
|
662
|
-
superclass == Base || !columns_hash.
|
663
|
+
superclass == Base || !columns_hash.include?(inheritance_column)
|
663
664
|
end
|
664
665
|
|
665
666
|
def quote(object) #:nodoc:
|
@@ -671,29 +672,27 @@ module ActiveRecord #:nodoc:
|
|
671
672
|
connection.quote(object)
|
672
673
|
end
|
673
674
|
|
674
|
-
#
|
675
|
+
# Log and benchmark multiple statements in a single block.
|
675
676
|
# Usage (hides all the SQL calls for the individual actions and calculates total runtime for them all):
|
676
677
|
#
|
677
678
|
# Project.benchmark("Creating project") do
|
678
679
|
# project = Project.create("name" => "stuff")
|
679
680
|
# project.create_manager("name" => "David")
|
680
|
-
# project.milestones << Milestone.
|
681
|
+
# project.milestones << Milestone.find(:all)
|
681
682
|
# end
|
682
683
|
def benchmark(title)
|
683
684
|
result = nil
|
684
|
-
|
685
|
-
logger.info "#{title} (#{sprintf("%f",
|
685
|
+
seconds = Benchmark.realtime { result = silence { yield } }
|
686
|
+
logger.info "#{title} (#{sprintf("%f", seconds)})" if logger
|
686
687
|
return result
|
687
688
|
end
|
688
|
-
|
689
|
+
|
689
690
|
# Silences the logger for the duration of the block.
|
690
691
|
def silence
|
691
|
-
|
692
|
-
|
693
|
-
|
694
|
-
result = yield
|
692
|
+
old_logger_level, logger.level = logger.level, Logger::ERROR if logger
|
693
|
+
yield
|
694
|
+
ensure
|
695
695
|
logger.level = old_logger_level if logger
|
696
|
-
return result
|
697
696
|
end
|
698
697
|
|
699
698
|
# Overwrite the default class equality method to provide support for association proxies.
|
@@ -705,30 +704,27 @@ module ActiveRecord #:nodoc:
|
|
705
704
|
# Finder methods must instantiate through this method to work with the single-table inheritance model
|
706
705
|
# that makes it possible to create objects of different types from the same table.
|
707
706
|
def instantiate(record)
|
708
|
-
|
707
|
+
subclass_name = record[inheritance_column]
|
708
|
+
require_association_class(subclass_name)
|
709
709
|
|
710
|
-
|
711
|
-
|
712
|
-
|
713
|
-
|
714
|
-
|
715
|
-
|
716
|
-
|
717
|
-
|
718
|
-
|
719
|
-
|
710
|
+
object = if subclass_name.blank?
|
711
|
+
allocate
|
712
|
+
else
|
713
|
+
begin
|
714
|
+
compute_type(subclass_name).allocate
|
715
|
+
rescue NameError
|
716
|
+
raise SubclassNotFound,
|
717
|
+
"The single-table inheritance mechanism failed to locate the subclass: '#{record[inheritance_column]}'. " +
|
718
|
+
"This error is raised because the column '#{inheritance_column}' is reserved for storing the class in case of inheritance. " +
|
719
|
+
"Please rename this column if you didn't intend it to be used for storing the inheritance class " +
|
720
|
+
"or overwrite #{self.to_s}.inheritance_column to use another column for that information."
|
721
|
+
end
|
720
722
|
end
|
721
723
|
|
722
724
|
object.instance_variable_set("@attributes", record)
|
723
|
-
|
724
|
-
end
|
725
|
-
|
726
|
-
# Returns true if the +record+ has a single table inheritance column and is using it.
|
727
|
-
def record_with_type?(record)
|
728
|
-
record.include?(inheritance_column) && !record[inheritance_column].nil? &&
|
729
|
-
!record[inheritance_column].empty?
|
725
|
+
object
|
730
726
|
end
|
731
|
-
|
727
|
+
|
732
728
|
# Returns the name of the type of the record using the current module as a prefix. So descendents of
|
733
729
|
# MyApp::Business::Account would be appear as "MyApp::Business::AccountSubclass".
|
734
730
|
def type_name_with_module(type_name)
|
@@ -736,21 +732,16 @@ module ActiveRecord #:nodoc:
|
|
736
732
|
end
|
737
733
|
|
738
734
|
def construct_finder_sql(options)
|
739
|
-
sql = "SELECT * FROM #{table_name} "
|
740
|
-
sql << "#{options[:joins]} " if options[:joins]
|
735
|
+
sql = "SELECT * FROM #{table_name} "
|
736
|
+
sql << " #{options[:joins]} " if options[:joins]
|
741
737
|
add_conditions!(sql, options[:conditions])
|
742
738
|
sql << "ORDER BY #{options[:order]} " if options[:order]
|
743
739
|
add_limit!(sql, options)
|
744
|
-
|
745
|
-
return sql
|
740
|
+
sql
|
746
741
|
end
|
747
742
|
|
748
743
|
def add_limit!(sql, options)
|
749
|
-
|
750
|
-
connection.add_limit_with_offset!(sql, options[:limit].to_i, options[:offset].to_i)
|
751
|
-
elsif options[:limit]
|
752
|
-
connection.add_limit_without_offset!(sql, options[:limit].to_i)
|
753
|
-
end
|
744
|
+
connection.add_limit_offset!(sql, options)
|
754
745
|
end
|
755
746
|
|
756
747
|
# Adds a sanitized version of +conditions+ to the +sql+ string. Note that it's the passed +sql+ string is changed.
|
@@ -758,40 +749,41 @@ module ActiveRecord #:nodoc:
|
|
758
749
|
sql << "WHERE #{sanitize_sql(conditions)} " unless conditions.nil?
|
759
750
|
sql << (conditions.nil? ? "WHERE " : " AND ") + type_condition unless descends_from_active_record?
|
760
751
|
end
|
761
|
-
|
752
|
+
|
762
753
|
def type_condition
|
763
|
-
type_condition = subclasses.inject("#{table_name}.#{inheritance_column} = '#{name.demodulize}' ") do |condition, subclass|
|
754
|
+
type_condition = subclasses.inject("#{table_name}.#{inheritance_column} = '#{name.demodulize}' ") do |condition, subclass|
|
764
755
|
condition << "OR #{table_name}.#{inheritance_column} = '#{subclass.name.demodulize}' "
|
765
756
|
end
|
766
|
-
|
767
|
-
|
757
|
+
|
758
|
+
" (#{type_condition}) "
|
768
759
|
end
|
769
760
|
|
770
761
|
# Guesses the table name, but does not decorate it with prefix and suffix information.
|
771
762
|
def undecorated_table_name(class_name = class_name_of_active_record_descendant(self))
|
772
763
|
table_name = Inflector.underscore(Inflector.demodulize(class_name))
|
773
764
|
table_name = Inflector.pluralize(table_name) if pluralize_table_names
|
774
|
-
|
765
|
+
table_name
|
775
766
|
end
|
776
767
|
|
777
|
-
# Enables dynamic finders like find_by_user_name(user_name) and find_by_user_name_and_password(user_name, password) that are turned into
|
778
|
-
#
|
779
|
-
# for
|
780
|
-
#
|
768
|
+
# Enables dynamic finders like find_by_user_name(user_name) and find_by_user_name_and_password(user_name, password) that are turned into
|
769
|
+
# find(:first, :conditions => ["user_name = ?", user_name]) and find(:first, :conditions => ["user_name = ? AND password = ?", user_name, password])
|
770
|
+
# respectively. Also works for find(:all), but using find_all_by_amount(50) that are turned into find(:all, :conditions => ["amount = ?", 50]).
|
771
|
+
#
|
781
772
|
# It's even possible to use all the additional parameters to find. For example, the full interface for find_all_by_amount
|
782
773
|
# is actually find_all_by_amount(amount, options).
|
783
774
|
def method_missing(method_id, *arguments)
|
784
775
|
method_name = method_id.id2name
|
785
776
|
|
786
|
-
if
|
787
|
-
finder
|
788
|
-
attributes
|
777
|
+
if md = /find_(all_by|by)_([_a-zA-Z]\w*)/.match(method_id.to_s)
|
778
|
+
finder = md.captures.first == 'all_by' ? :all : :first
|
779
|
+
attributes = md.captures.last.split('_and_')
|
780
|
+
attributes.each { |attr_name| super unless column_methods_hash.include?(attr_name.to_sym) }
|
789
781
|
|
790
782
|
attr_index = -1
|
791
|
-
conditions = attributes.collect { |attr_name| attr_index += 1; "#{attr_name} #{arguments[attr_index]
|
792
|
-
|
783
|
+
conditions = attributes.collect { |attr_name| attr_index += 1; "#{attr_name} #{attribute_condition(arguments[attr_index])} " }.join(" AND ")
|
784
|
+
|
793
785
|
if arguments[attributes.length].is_a?(Hash)
|
794
|
-
find(finder, { :conditions => [conditions, *arguments[0...attributes.length]]}.
|
786
|
+
find(finder, { :conditions => [conditions, *arguments[0...attributes.length]] }.update(arguments[attributes.length]))
|
795
787
|
else
|
796
788
|
# deprecated API
|
797
789
|
send("find_#{finder}", [conditions, *arguments[0...attributes.length]], *arguments[attributes.length..-1])
|
@@ -801,6 +793,14 @@ module ActiveRecord #:nodoc:
|
|
801
793
|
end
|
802
794
|
end
|
803
795
|
|
796
|
+
def attribute_condition(argument)
|
797
|
+
case argument
|
798
|
+
when nil then "IS ?"
|
799
|
+
when Array then "IN (?)"
|
800
|
+
else "= ?"
|
801
|
+
end
|
802
|
+
end
|
803
|
+
|
804
804
|
# Defines an "attribute" method (like #inheritance_column or
|
805
805
|
# #table_name). A new (class) method will be created with the
|
806
806
|
# given name. If a value is specified, the new method will
|
@@ -831,11 +831,11 @@ module ActiveRecord #:nodoc:
|
|
831
831
|
@@subclasses[self] ||= []
|
832
832
|
@@subclasses[self] + extra = @@subclasses[self].inject([]) {|list, subclass| list + subclass.subclasses }
|
833
833
|
end
|
834
|
-
|
834
|
+
|
835
835
|
# Returns the class type of the record using the current module as a prefix. So descendents of
|
836
836
|
# MyApp::Business::Account would be appear as MyApp::Business::AccountSubclass.
|
837
837
|
def compute_type(type_name)
|
838
|
-
type_name_with_module(type_name).split("::").inject(Object) do |final_type, part|
|
838
|
+
type_name_with_module(type_name).split("::").inject(Object) do |final_type, part|
|
839
839
|
final_type = final_type.const_get(part)
|
840
840
|
end
|
841
841
|
end
|
@@ -843,7 +843,7 @@ module ActiveRecord #:nodoc:
|
|
843
843
|
# Returns the name of the class descending directly from ActiveRecord in the inheritance hierarchy.
|
844
844
|
def class_name_of_active_record_descendant(klass)
|
845
845
|
if klass.superclass == Base
|
846
|
-
|
846
|
+
klass.name
|
847
847
|
elsif klass.superclass.nil?
|
848
848
|
raise ActiveRecordError, "#{name} doesn't belong in a hierarchy descending from ActiveRecord"
|
849
849
|
else
|
@@ -879,7 +879,7 @@ module ActiveRecord #:nodoc:
|
|
879
879
|
raise_if_bind_arity_mismatch(statement, statement.scan(/:(\w+)/).uniq.size, bind_vars.size)
|
880
880
|
statement.gsub(/:(\w+)/) do
|
881
881
|
match = $1.to_sym
|
882
|
-
if bind_vars.
|
882
|
+
if bind_vars.include?(match)
|
883
883
|
quote_bound_value(bind_vars[match])
|
884
884
|
else
|
885
885
|
raise PreparedStatementInvalid, "missing value for :#{match} in #{statement}"
|
@@ -888,11 +888,10 @@ module ActiveRecord #:nodoc:
|
|
888
888
|
end
|
889
889
|
|
890
890
|
def quote_bound_value(value)
|
891
|
-
|
892
|
-
|
893
|
-
|
894
|
-
|
895
|
-
connection.quote(value)
|
891
|
+
if (value.respond_to?(:map) && !value.is_a?(String))
|
892
|
+
value.map { |v| connection.quote(v) }.join(',')
|
893
|
+
else
|
894
|
+
connection.quote(value)
|
896
895
|
end
|
897
896
|
end
|
898
897
|
|
@@ -905,10 +904,10 @@ module ActiveRecord #:nodoc:
|
|
905
904
|
def extract_options_from_args!(args)
|
906
905
|
if args.last.is_a?(Hash) then args.pop else {} end
|
907
906
|
end
|
908
|
-
|
907
|
+
|
909
908
|
def encode_quoted_value(value)
|
910
909
|
quoted_value = connection.quote(value)
|
911
|
-
quoted_value = "'#{quoted_value[1..-2].gsub(/\'/, "\\\\'")}'" if quoted_value.include?("\\\'")
|
910
|
+
quoted_value = "'#{quoted_value[1..-2].gsub(/\'/, "\\\\'")}'" if quoted_value.include?("\\\'")
|
912
911
|
quoted_value
|
913
912
|
end
|
914
913
|
end
|
@@ -916,7 +915,7 @@ module ActiveRecord #:nodoc:
|
|
916
915
|
public
|
917
916
|
# New objects can be instantiated as either empty (pass no construction parameter) or pre-set with
|
918
917
|
# attributes but not yet saved (pass a hash with key names matching the associated table column names).
|
919
|
-
# In both instances, valid attribute keys are determined by the column names of the associated table --
|
918
|
+
# In both instances, valid attribute keys are determined by the column names of the associated table --
|
920
919
|
# hence you can't have attributes that aren't part of the table columns.
|
921
920
|
def initialize(attributes = nil)
|
922
921
|
@attributes = attributes_from_column_definition
|
@@ -925,49 +924,48 @@ module ActiveRecord #:nodoc:
|
|
925
924
|
self.attributes = attributes unless attributes.nil?
|
926
925
|
yield self if block_given?
|
927
926
|
end
|
928
|
-
|
927
|
+
|
929
928
|
# Every Active Record class must use "id" as their primary ID. This getter overwrites the native
|
930
929
|
# id method, which isn't being used in this context.
|
931
930
|
def id
|
932
931
|
read_attribute(self.class.primary_key)
|
933
932
|
end
|
934
|
-
|
933
|
+
|
935
934
|
# Enables Active Record objects to be used as URL parameters in Action Pack automatically.
|
936
935
|
alias_method :to_param, :id
|
937
|
-
|
936
|
+
|
938
937
|
def id_before_type_cast #:nodoc:
|
939
938
|
read_attribute_before_type_cast(self.class.primary_key)
|
940
939
|
end
|
941
940
|
|
942
941
|
def quoted_id #:nodoc:
|
943
|
-
quote(id, self.class.
|
942
|
+
quote(id, column_for_attribute(self.class.primary_key))
|
944
943
|
end
|
945
|
-
|
944
|
+
|
946
945
|
# Sets the primary ID.
|
947
946
|
def id=(value)
|
948
947
|
write_attribute(self.class.primary_key, value)
|
949
948
|
end
|
950
|
-
|
949
|
+
|
951
950
|
# Returns true if this object hasn't been saved yet -- that is, a record for the object doesn't exist yet.
|
952
951
|
def new_record?
|
953
952
|
@new_record
|
954
953
|
end
|
955
|
-
|
954
|
+
|
956
955
|
# * No record exists: Creates a new record with values matching those of the object attributes.
|
957
956
|
# * A record does exist: Updates the record with values matching those of the object attributes.
|
958
957
|
def save
|
959
958
|
create_or_update
|
960
959
|
end
|
961
|
-
|
960
|
+
|
962
961
|
# Deletes the record in the database and freezes this instance to reflect that no changes should
|
963
962
|
# be made (since they can't be persisted).
|
964
963
|
def destroy
|
965
964
|
unless new_record?
|
966
|
-
connection.delete
|
967
|
-
|
968
|
-
|
969
|
-
|
970
|
-
)
|
965
|
+
connection.delete <<-end_sql, "#{self.class.name} Destroy"
|
966
|
+
DELETE FROM #{self.class.table_name}
|
967
|
+
WHERE #{self.class.primary_key} = #{quoted_id}
|
968
|
+
end_sql
|
971
969
|
end
|
972
970
|
|
973
971
|
freeze
|
@@ -975,12 +973,13 @@ module ActiveRecord #:nodoc:
|
|
975
973
|
|
976
974
|
# Returns a clone of the record that hasn't been assigned an id yet and is treated as a new record.
|
977
975
|
def clone
|
978
|
-
attrs = self.
|
976
|
+
attrs = self.attributes_before_type_cast
|
979
977
|
attrs.delete(self.class.primary_key)
|
980
|
-
|
981
|
-
|
978
|
+
self.class.new do |record|
|
979
|
+
record.send :instance_variable_set, '@attributes', attrs
|
980
|
+
end
|
982
981
|
end
|
983
|
-
|
982
|
+
|
984
983
|
# Updates a single attribute and saves the record. This is especially useful for boolean flags on existing records.
|
985
984
|
# Note: This method is overwritten by the Validation module that'll make sure that updates made with this method
|
986
985
|
# doesn't get subjected to validation checks. Hence, attributes can be updated even if the full object isn't valid.
|
@@ -993,7 +992,7 @@ module ActiveRecord #:nodoc:
|
|
993
992
|
# fail and false will be returned.
|
994
993
|
def update_attributes(attributes)
|
995
994
|
self.attributes = attributes
|
996
|
-
|
995
|
+
save
|
997
996
|
end
|
998
997
|
|
999
998
|
# Initializes the +attribute+ to zero if nil and adds one. Only makes sense for number-based attributes. Returns self.
|
@@ -1002,7 +1001,7 @@ module ActiveRecord #:nodoc:
|
|
1002
1001
|
self[attribute] += 1
|
1003
1002
|
self
|
1004
1003
|
end
|
1005
|
-
|
1004
|
+
|
1006
1005
|
# Increments the +attribute+ and saves the record.
|
1007
1006
|
def increment!(attribute)
|
1008
1007
|
increment(attribute).update_attribute(attribute, self[attribute])
|
@@ -1019,7 +1018,7 @@ module ActiveRecord #:nodoc:
|
|
1019
1018
|
def decrement!(attribute)
|
1020
1019
|
decrement(attribute).update_attribute(attribute, self[attribute])
|
1021
1020
|
end
|
1022
|
-
|
1021
|
+
|
1023
1022
|
# Turns an +attribute+ that's currently true into false and vice versa. Returns self.
|
1024
1023
|
def toggle(attribute)
|
1025
1024
|
self[attribute] = quote(!send("#{attribute}?", column_for_attribute(attribute)))
|
@@ -1035,19 +1034,19 @@ module ActiveRecord #:nodoc:
|
|
1035
1034
|
def reload
|
1036
1035
|
clear_association_cache
|
1037
1036
|
@attributes.update(self.class.find(self.id).instance_variable_get('@attributes'))
|
1038
|
-
|
1037
|
+
self
|
1039
1038
|
end
|
1040
1039
|
|
1041
|
-
# Returns the value of attribute identified by <tt>attr_name</tt> after it has been type cast (for example,
|
1040
|
+
# Returns the value of attribute identified by <tt>attr_name</tt> after it has been type cast (for example,
|
1042
1041
|
# "2004-12-12" in a data column is cast to a date object, like Date.new(2004, 12, 12)).
|
1043
1042
|
# (Alias for the protected read_attribute method).
|
1044
|
-
def [](attr_name)
|
1043
|
+
def [](attr_name)
|
1045
1044
|
read_attribute(attr_name.to_s)
|
1046
1045
|
end
|
1047
|
-
|
1046
|
+
|
1048
1047
|
# Updates the attribute identified by <tt>attr_name</tt> with the specified +value+.
|
1049
1048
|
# (Alias for the protected write_attribute method).
|
1050
|
-
def []=
|
1049
|
+
def []=(attr_name, value)
|
1051
1050
|
write_attribute(attr_name.to_s, value)
|
1052
1051
|
end
|
1053
1052
|
|
@@ -1061,7 +1060,7 @@ module ActiveRecord #:nodoc:
|
|
1061
1060
|
attributes.stringify_keys!
|
1062
1061
|
|
1063
1062
|
multi_parameter_attributes = []
|
1064
|
-
remove_attributes_protected_from_mass_assignment(attributes).each do |k, v|
|
1063
|
+
remove_attributes_protected_from_mass_assignment(attributes).each do |k, v|
|
1065
1064
|
k.include?("(") ? multi_parameter_attributes << [ k, v ] : send(k + "=", v)
|
1066
1065
|
end
|
1067
1066
|
assign_multiparameter_attributes(multi_parameter_attributes)
|
@@ -1069,21 +1068,19 @@ module ActiveRecord #:nodoc:
|
|
1069
1068
|
|
1070
1069
|
# Returns a hash of all the attributes with their names as keys and clones of their objects as values.
|
1071
1070
|
def attributes
|
1072
|
-
|
1073
|
-
|
1074
|
-
|
1075
|
-
|
1076
|
-
|
1077
|
-
|
1078
|
-
attributes
|
1079
|
-
end
|
1071
|
+
clone_attributes :read_attribute
|
1072
|
+
end
|
1073
|
+
|
1074
|
+
# Returns a hash of cloned attributes before typecasting and deserialization.
|
1075
|
+
def attributes_before_type_cast
|
1076
|
+
clone_attributes :read_attribute_before_type_cast
|
1080
1077
|
end
|
1081
1078
|
|
1082
1079
|
# Returns true if the specified +attribute+ has been set by the user or by a database load and is neither
|
1083
1080
|
# nil nor empty? (the latter only applies to objects that responds to empty?, most notably Strings).
|
1084
1081
|
def attribute_present?(attribute)
|
1085
|
-
|
1086
|
-
|
1082
|
+
value = read_attribute(attribute)
|
1083
|
+
!value.blank? or value == 0
|
1087
1084
|
end
|
1088
1085
|
|
1089
1086
|
# Returns an array of names for the attributes available on this object sorted alphabetically.
|
@@ -1095,7 +1092,7 @@ module ActiveRecord #:nodoc:
|
|
1095
1092
|
def column_for_attribute(name)
|
1096
1093
|
self.class.columns_hash[name.to_s]
|
1097
1094
|
end
|
1098
|
-
|
1095
|
+
|
1099
1096
|
# Returns true if the +comparison_object+ is the same object, or is of the same type and has the same id.
|
1100
1097
|
def ==(comparison_object)
|
1101
1098
|
comparison_object.equal?(self) or (comparison_object.instance_of?(self.class) and comparison_object.id == id)
|
@@ -1105,7 +1102,7 @@ module ActiveRecord #:nodoc:
|
|
1105
1102
|
def eql?(comparison_object)
|
1106
1103
|
self == (comparison_object)
|
1107
1104
|
end
|
1108
|
-
|
1105
|
+
|
1109
1106
|
# Delegates to id in order to allow two records of the same type and id to work with something like:
|
1110
1107
|
# [ Person.find(1), Person.find(2), Person.find(3) ] & [ Person.find(1), Person.find(4) ] # => [ Person.find(1) ]
|
1111
1108
|
def hash
|
@@ -1120,11 +1117,20 @@ module ActiveRecord #:nodoc:
|
|
1120
1117
|
def respond_to?(method, include_priv = false)
|
1121
1118
|
self.class.column_methods_hash[method.to_sym] || respond_to_without_attributes?(method, include_priv)
|
1122
1119
|
end
|
1123
|
-
|
1120
|
+
|
1121
|
+
# Just freeze the attributes hash, such that associations are still accessible even on destroyed records.
|
1122
|
+
def freeze
|
1123
|
+
@attributes.freeze
|
1124
|
+
end
|
1125
|
+
|
1126
|
+
def frozen?
|
1127
|
+
@attributes.frozen?
|
1128
|
+
end
|
1129
|
+
|
1124
1130
|
private
|
1125
1131
|
def create_or_update
|
1126
1132
|
if new_record? then create else update end
|
1127
|
-
|
1133
|
+
true
|
1128
1134
|
end
|
1129
1135
|
|
1130
1136
|
# Updates the associated record with values matching those of the instant attributes.
|
@@ -1146,13 +1152,13 @@ module ActiveRecord #:nodoc:
|
|
1146
1152
|
"#{self.class.name} Create",
|
1147
1153
|
self.class.primary_key, self.id
|
1148
1154
|
)
|
1149
|
-
|
1155
|
+
|
1150
1156
|
@new_record = false
|
1151
1157
|
end
|
1152
1158
|
|
1153
|
-
# Sets the attribute used for single table inheritance to this class name if this is not the ActiveRecord descendant.
|
1154
|
-
# Considering the hierarchy Reply < Message < ActiveRecord, this makes it possible to do Reply.new without having to
|
1155
|
-
# set Reply[Reply.inheritance_column] = "Reply" yourself. No such attribute would be set for objects of the
|
1159
|
+
# Sets the attribute used for single table inheritance to this class name if this is not the ActiveRecord descendant.
|
1160
|
+
# Considering the hierarchy Reply < Message < ActiveRecord, this makes it possible to do Reply.new without having to
|
1161
|
+
# set Reply[Reply.inheritance_column] = "Reply" yourself. No such attribute would be set for objects of the
|
1156
1162
|
# Message class in that example.
|
1157
1163
|
def ensure_proper_type
|
1158
1164
|
unless self.class.descends_from_active_record?
|
@@ -1164,40 +1170,45 @@ module ActiveRecord #:nodoc:
|
|
1164
1170
|
# they first-class methods. So a Person class with a name attribute can use Person#name and
|
1165
1171
|
# Person#name= and never directly use the attributes hash -- except for multiple assigns with
|
1166
1172
|
# ActiveRecord#attributes=. A Milestone class can also ask Milestone#completed? to test that
|
1167
|
-
# the completed attribute is not nil or 0.
|
1173
|
+
# the completed attribute is not nil or 0.
|
1168
1174
|
#
|
1169
1175
|
# It's also possible to instantiate related objects, so a Client class belonging to the clients
|
1170
1176
|
# table with a master_id foreign key can instantiate master through Client#master.
|
1171
|
-
def method_missing(method_id, *
|
1172
|
-
method_name = method_id.
|
1173
|
-
|
1174
|
-
|
1175
|
-
|
1176
|
-
|
1177
|
-
|
1178
|
-
|
1179
|
-
|
1180
|
-
|
1181
|
-
|
1177
|
+
def method_missing(method_id, *args, &block)
|
1178
|
+
method_name = method_id.to_s
|
1179
|
+
if @attributes.include?(method_name)
|
1180
|
+
read_attribute(method_name)
|
1181
|
+
elsif md = /(=|\?|_before_type_cast)$/.match(method_name)
|
1182
|
+
attribute_name, method_type = md.pre_match, md.to_s
|
1183
|
+
if @attributes.include?(attribute_name)
|
1184
|
+
case method_type
|
1185
|
+
when '='
|
1186
|
+
write_attribute(attribute_name, args.first)
|
1187
|
+
when '?'
|
1188
|
+
query_attribute(attribute_name)
|
1189
|
+
when '_before_type_cast'
|
1190
|
+
read_attribute_before_type_cast(attribute_name)
|
1191
|
+
end
|
1192
|
+
else
|
1193
|
+
super
|
1194
|
+
end
|
1182
1195
|
else
|
1183
1196
|
super
|
1184
1197
|
end
|
1185
1198
|
end
|
1186
1199
|
|
1187
|
-
def read_method?() /^([a-zA-Z][-_\w]*)[^=?]*$/ end
|
1188
|
-
def read_untyped_method?() /^([a-zA-Z][-_\w]*)_before_type_cast$/ end
|
1189
|
-
def write_method?() /^([a-zA-Z][-_\w]*)=.*$/ end
|
1190
|
-
def query_method?() /^([a-zA-Z][-_\w]*)\?$/ end
|
1191
|
-
|
1192
1200
|
# Returns the value of attribute identified by <tt>attr_name</tt> after it has been type cast (for example,
|
1193
1201
|
# "2004-12-12" in a data column is cast to a date object, like Date.new(2004, 12, 12)).
|
1194
1202
|
def read_attribute(attr_name)
|
1195
|
-
if @attributes.
|
1203
|
+
if !(value = @attributes[attr_name]).nil?
|
1196
1204
|
if column = column_for_attribute(attr_name)
|
1197
|
-
unserializable_attribute?(attr_name, column)
|
1198
|
-
unserialize_attribute(attr_name)
|
1205
|
+
if unserializable_attribute?(attr_name, column)
|
1206
|
+
unserialize_attribute(attr_name)
|
1207
|
+
else
|
1208
|
+
column.type_cast(value)
|
1209
|
+
end
|
1199
1210
|
else
|
1200
|
-
|
1211
|
+
value
|
1201
1212
|
end
|
1202
1213
|
else
|
1203
1214
|
nil
|
@@ -1210,7 +1221,9 @@ module ActiveRecord #:nodoc:
|
|
1210
1221
|
|
1211
1222
|
# Returns true if the attribute is of a text column and marked for serialization.
|
1212
1223
|
def unserializable_attribute?(attr_name, column)
|
1213
|
-
|
1224
|
+
if value = @attributes[attr_name]
|
1225
|
+
[:text, :string].include?(column.send(:type)) && value.is_a?(String) && self.class.serialized_attributes[attr_name]
|
1226
|
+
end
|
1214
1227
|
end
|
1215
1228
|
|
1216
1229
|
# Returns the unserialized object of the attribute.
|
@@ -1220,18 +1233,15 @@ module ActiveRecord #:nodoc:
|
|
1220
1233
|
if unserialized_object.is_a?(self.class.serialized_attributes[attr_name])
|
1221
1234
|
@attributes[attr_name] = unserialized_object
|
1222
1235
|
else
|
1223
|
-
raise
|
1224
|
-
|
1225
|
-
"#{attr_name} was supposed to be a #{self.class.serialized_attributes[attr_name]}, " +
|
1226
|
-
"but was a #{unserialized_object.class.to_s}"
|
1227
|
-
)
|
1236
|
+
raise SerializationTypeMismatch,
|
1237
|
+
"#{attr_name} was supposed to be a #{self.class.serialized_attributes[attr_name]}, but was a #{unserialized_object.class.to_s}"
|
1228
1238
|
end
|
1229
1239
|
end
|
1230
1240
|
|
1231
1241
|
# Updates the attribute identified by <tt>attr_name</tt> with the specified +value+. Empty strings for fixnum and float
|
1232
1242
|
# columns are turned into nil.
|
1233
1243
|
def write_attribute(attr_name, value)
|
1234
|
-
@attributes[attr_name] = empty_string_for_number_column?(attr_name, value) ? nil : value
|
1244
|
+
@attributes[attr_name.to_s] = empty_string_for_number_column?(attr_name.to_s, value) ? nil : value
|
1235
1245
|
end
|
1236
1246
|
|
1237
1247
|
def empty_string_for_number_column?(attr_name, value)
|
@@ -1262,11 +1272,11 @@ module ActiveRecord #:nodoc:
|
|
1262
1272
|
|
1263
1273
|
def remove_attributes_protected_from_mass_assignment(attributes)
|
1264
1274
|
if self.class.accessible_attributes.nil? && self.class.protected_attributes.nil?
|
1265
|
-
attributes.reject { |key, value| attributes_protected_by_default.include?(key) }
|
1275
|
+
attributes.reject { |key, value| attributes_protected_by_default.include?(key.gsub(/\(.+/, "")) }
|
1266
1276
|
elsif self.class.protected_attributes.nil?
|
1267
|
-
attributes.reject { |key, value| !self.class.accessible_attributes.include?(key.intern) || attributes_protected_by_default.include?(key) }
|
1277
|
+
attributes.reject { |key, value| !self.class.accessible_attributes.include?(key.gsub(/\(.+/, "").intern) || attributes_protected_by_default.include?(key.gsub(/\(.+/, "")) }
|
1268
1278
|
elsif self.class.accessible_attributes.nil?
|
1269
|
-
attributes.reject { |key, value| self.class.protected_attributes.include?(key.intern) || attributes_protected_by_default.include?(key) }
|
1279
|
+
attributes.reject { |key, value| self.class.protected_attributes.include?(key.gsub(/\(.+/,"").intern) || attributes_protected_by_default.include?(key.gsub(/\(.+/, "")) }
|
1270
1280
|
end
|
1271
1281
|
end
|
1272
1282
|
|
@@ -1276,21 +1286,19 @@ module ActiveRecord #:nodoc:
|
|
1276
1286
|
end
|
1277
1287
|
|
1278
1288
|
# Returns copy of the attributes hash where all the values have been safely quoted for use in
|
1279
|
-
# an SQL statement.
|
1289
|
+
# an SQL statement.
|
1280
1290
|
def attributes_with_quotes(include_primary_key = true)
|
1281
|
-
|
1282
|
-
|
1283
|
-
|
1284
|
-
|
1285
|
-
|
1291
|
+
attributes.inject({}) do |quoted, (name, value)|
|
1292
|
+
if column = column_for_attribute(name)
|
1293
|
+
quoted[name] = quote(value, column) unless !include_primary_key && name == self.class.primary_key
|
1294
|
+
end
|
1295
|
+
quoted
|
1286
1296
|
end
|
1287
|
-
|
1288
|
-
attrs_quoted.delete_if { |key, value| !self.class.columns_hash.keys.include?(key) }
|
1289
1297
|
end
|
1290
|
-
|
1298
|
+
|
1291
1299
|
# Quote strings appropriately for SQL statements.
|
1292
1300
|
def quote(value, column = nil)
|
1293
|
-
connection.quote(value, column)
|
1301
|
+
self.class.connection.quote(value, column)
|
1294
1302
|
end
|
1295
1303
|
|
1296
1304
|
# Interpolate custom sql string in instance context.
|
@@ -1304,7 +1312,7 @@ module ActiveRecord #:nodoc:
|
|
1304
1312
|
# that a new instance, or one populated from a passed-in Hash, still has all the attributes
|
1305
1313
|
# that instances loaded from the database would.
|
1306
1314
|
def attributes_from_column_definition
|
1307
|
-
connection.columns(self.class.table_name, "#{self.class.name} Columns").inject({}) do |attributes, column|
|
1315
|
+
connection.columns(self.class.table_name, "#{self.class.name} Columns").inject({}) do |attributes, column|
|
1308
1316
|
attributes[column.name] = column.default unless column.name == self.class.primary_key
|
1309
1317
|
attributes
|
1310
1318
|
end
|
@@ -1321,7 +1329,7 @@ module ActiveRecord #:nodoc:
|
|
1321
1329
|
extract_callstack_for_multiparameter_attributes(pairs)
|
1322
1330
|
)
|
1323
1331
|
end
|
1324
|
-
|
1332
|
+
|
1325
1333
|
# Includes an ugly hack for Time.local instead of Time.new because the latter is reserved by Time itself.
|
1326
1334
|
def execute_callstack_for_multiparameter_attributes(callstack)
|
1327
1335
|
errors = []
|
@@ -1341,7 +1349,7 @@ module ActiveRecord #:nodoc:
|
|
1341
1349
|
raise MultiparameterAssignmentErrors.new(errors), "#{errors.size} error(s) on assignment of multiparameter attributes"
|
1342
1350
|
end
|
1343
1351
|
end
|
1344
|
-
|
1352
|
+
|
1345
1353
|
def extract_callstack_for_multiparameter_attributes(pairs)
|
1346
1354
|
attributes = { }
|
1347
1355
|
|
@@ -1351,40 +1359,42 @@ module ActiveRecord #:nodoc:
|
|
1351
1359
|
attributes[attribute_name] = [] unless attributes.include?(attribute_name)
|
1352
1360
|
|
1353
1361
|
unless value.empty?
|
1354
|
-
attributes[attribute_name] <<
|
1362
|
+
attributes[attribute_name] <<
|
1355
1363
|
[ find_parameter_position(multiparameter_name), type_cast_attribute_value(multiparameter_name, value) ]
|
1356
1364
|
end
|
1357
1365
|
end
|
1358
1366
|
|
1359
1367
|
attributes.each { |name, values| attributes[name] = values.sort_by{ |v| v.first }.collect { |v| v.last } }
|
1360
1368
|
end
|
1361
|
-
|
1369
|
+
|
1362
1370
|
def type_cast_attribute_value(multiparameter_name, value)
|
1363
1371
|
multiparameter_name =~ /\([0-9]*([a-z])\)/ ? value.send("to_" + $1) : value
|
1364
1372
|
end
|
1365
|
-
|
1373
|
+
|
1366
1374
|
def find_parameter_position(multiparameter_name)
|
1367
1375
|
multiparameter_name.scan(/\(([0-9]*).*\)/).first.first
|
1368
1376
|
end
|
1369
|
-
|
1377
|
+
|
1370
1378
|
# Returns a comma-separated pair list, like "key1 = val1, key2 = val2".
|
1371
1379
|
def comma_pair_list(hash)
|
1372
1380
|
hash.inject([]) { |list, pair| list << "#{pair.first} = #{pair.last}" }.join(", ")
|
1373
1381
|
end
|
1374
1382
|
|
1375
1383
|
def quoted_column_names(attributes = attributes_with_quotes)
|
1376
|
-
attributes.keys.collect
|
1384
|
+
attributes.keys.collect do |column_name|
|
1385
|
+
self.class.connection.quote_column_name(column_name)
|
1386
|
+
end
|
1377
1387
|
end
|
1378
1388
|
|
1379
|
-
def quote_columns(
|
1380
|
-
hash.inject({}) do |
|
1381
|
-
|
1382
|
-
|
1389
|
+
def quote_columns(quoter, hash)
|
1390
|
+
hash.inject({}) do |quoted, (name, value)|
|
1391
|
+
quoted[quoter.quote_column_name(name)] = value
|
1392
|
+
quoted
|
1383
1393
|
end
|
1384
1394
|
end
|
1385
1395
|
|
1386
|
-
def quoted_comma_pair_list(
|
1387
|
-
comma_pair_list(quote_columns(
|
1396
|
+
def quoted_comma_pair_list(quoter, hash)
|
1397
|
+
comma_pair_list(quote_columns(quoter, hash))
|
1388
1398
|
end
|
1389
1399
|
|
1390
1400
|
def object_from_yaml(string)
|
@@ -1404,5 +1414,19 @@ module ActiveRecord #:nodoc:
|
|
1404
1414
|
def has_yaml_encoding_header?(string)
|
1405
1415
|
string[0..3] == "--- "
|
1406
1416
|
end
|
1417
|
+
|
1418
|
+
def clone_attributes(reader_method = :read_attribute, attributes = {})
|
1419
|
+
self.attribute_names.inject(attributes) do |attributes, name|
|
1420
|
+
attributes[name] = clone_attribute_value(reader_method, name)
|
1421
|
+
attributes
|
1422
|
+
end
|
1423
|
+
end
|
1424
|
+
|
1425
|
+
def clone_attribute_value(reader_method, attribute_name)
|
1426
|
+
value = send(reader_method, attribute_name)
|
1427
|
+
value.clone
|
1428
|
+
rescue TypeError, NoMethodError
|
1429
|
+
value
|
1430
|
+
end
|
1407
1431
|
end
|
1408
1432
|
end
|