activerecord 1.3.0 → 1.4.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 +77 -2
- data/install.rb +5 -0
- data/lib/active_record.rb +6 -2
- data/lib/active_record/acts/list.rb +56 -45
- data/lib/active_record/acts/tree.rb +3 -2
- data/lib/active_record/associations.rb +10 -62
- data/lib/active_record/associations/association_collection.rb +20 -23
- data/lib/active_record/associations/has_and_belongs_to_many_association.rb +36 -10
- data/lib/active_record/associations/has_many_association.rb +50 -25
- data/lib/active_record/base.rb +118 -80
- data/lib/active_record/callbacks.rb +51 -50
- data/lib/active_record/connection_adapters/abstract_adapter.rb +33 -12
- data/lib/active_record/connection_adapters/db2_adapter.rb +129 -0
- data/lib/active_record/connection_adapters/sqlite_adapter.rb +23 -1
- data/lib/active_record/connection_adapters/sqlserver_adapter.rb +80 -90
- data/lib/active_record/fixtures.rb +1 -1
- data/lib/active_record/locking.rb +57 -0
- data/lib/active_record/support/class_attribute_accessors.rb +19 -5
- data/lib/active_record/support/class_inheritable_attributes.rb +4 -3
- data/lib/active_record/support/dependencies.rb +71 -0
- data/lib/active_record/support/inflector.rb +1 -0
- data/lib/active_record/support/misc.rb +29 -3
- data/lib/active_record/support/module_attribute_accessors.rb +57 -0
- data/lib/active_record/transactions.rb +18 -11
- data/lib/active_record/validations.rb +3 -3
- data/lib/active_record/vendor/db2.rb +357 -0
- data/lib/active_record/vendor/mysql.rb +1 -1
- data/rakefile +17 -5
- data/test/associations_test.rb +39 -4
- data/test/base_test.rb +13 -4
- data/test/binary_test.rb +43 -0
- data/test/callbacks_test.rb +230 -0
- data/test/connections/native_db2/connection.rb +24 -0
- data/test/connections/native_sqlserver/connection.rb +9 -3
- data/test/deprecated_associations_test.rb +16 -10
- data/test/finder_test.rb +65 -13
- data/test/fixtures/associations.png +0 -0
- data/test/fixtures/binary.rb +2 -0
- data/test/fixtures/companies.yml +21 -0
- data/test/fixtures/courses.yml +7 -0
- data/test/fixtures/customers.yml +7 -0
- data/test/fixtures/db_definitions/db2.sql +124 -0
- data/test/fixtures/db_definitions/db22.sql +4 -0
- data/test/fixtures/db_definitions/mysql.sql +12 -0
- data/test/fixtures/db_definitions/postgresql.sql +13 -0
- data/test/fixtures/db_definitions/sqlite.sql +9 -0
- data/test/fixtures/db_definitions/sqlserver.sql +13 -0
- data/test/fixtures/developers_projects.yml +13 -0
- data/test/fixtures/entrants.yml +14 -0
- data/test/fixtures/fixture_database.sqlite +0 -0
- data/test/fixtures/movies.yml +7 -0
- data/test/fixtures/people.yml +3 -0
- data/test/fixtures/person.rb +1 -0
- data/test/fixtures/projects.yml +7 -0
- data/test/fixtures/topics.yml +21 -0
- data/test/locking_test.rb +34 -0
- data/test/mixin_test.rb +6 -0
- data/test/validations_test.rb +1 -1
- metadata +33 -29
- data/test/fixtures/companies/first_client +0 -6
- data/test/fixtures/companies/first_firm +0 -4
- data/test/fixtures/companies/second_client +0 -6
- data/test/fixtures/courses/java +0 -2
- data/test/fixtures/courses/ruby +0 -2
- data/test/fixtures/customers/david +0 -6
- data/test/fixtures/entrants/first +0 -3
- data/test/fixtures/entrants/second +0 -3
- data/test/fixtures/entrants/third +0 -3
- data/test/fixtures/movies/first +0 -2
- data/test/fixtures/movies/second +0 -2
- data/test/fixtures/projects/action_controller +0 -2
- data/test/fixtures/projects/active_record +0 -2
- data/test/fixtures/topics/first +0 -10
- data/test/fixtures/topics/second +0 -8
- data/test/inflector_test.rb +0 -122
@@ -38,15 +38,42 @@ module ActiveRecord
|
|
38
38
|
self
|
39
39
|
end
|
40
40
|
|
41
|
-
def
|
42
|
-
|
43
|
-
|
44
|
-
|
41
|
+
def find_first
|
42
|
+
load_collection.first
|
43
|
+
end
|
44
|
+
|
45
|
+
def find(*args)
|
46
|
+
# Return an Array if multiple ids are given.
|
47
|
+
expects_array = args.first.kind_of?(Array)
|
48
|
+
|
49
|
+
ids = args.flatten.compact.uniq
|
50
|
+
|
51
|
+
# If no block is given, raise RecordNotFound.
|
52
|
+
if ids.empty?
|
53
|
+
raise RecordNotFound, "Couldn't find #{@association_class.name} without an ID#{conditions}"
|
54
|
+
|
55
|
+
# If using a custom finder_sql, scan the entire collection.
|
56
|
+
elsif @options[:finder_sql]
|
57
|
+
if ids.size == 1
|
58
|
+
id = ids.first
|
59
|
+
record = load_collection.detect { |record| id == record.id }
|
60
|
+
expects_array? ? [record] : record
|
61
|
+
else
|
62
|
+
load_collection.select { |record| ids.include?(record.id) }
|
63
|
+
end
|
64
|
+
|
65
|
+
# Otherwise, construct a query.
|
45
66
|
else
|
46
|
-
|
47
|
-
|
67
|
+
ids_list = ids.map { |id| @owner.send(:quote, id) }.join(',')
|
68
|
+
records = find_all_records(@finder_sql.sub(/ORDER BY/, "AND j.#{@association_foreign_key} IN (#{ids_list}) ORDER BY"))
|
69
|
+
if records.size == ids.size
|
70
|
+
if ids.size == 1 and !expects_array
|
71
|
+
records.first
|
72
|
+
else
|
73
|
+
records
|
74
|
+
end
|
48
75
|
else
|
49
|
-
|
76
|
+
raise RecordNotFound, "Couldn't find #{@association_class.name} with ID in (#{ids_list})"
|
50
77
|
end
|
51
78
|
end
|
52
79
|
end
|
@@ -70,10 +97,9 @@ module ActiveRecord
|
|
70
97
|
records = @association_class.find_by_sql(sql)
|
71
98
|
@options[:uniq] ? uniq(records) : records
|
72
99
|
end
|
73
|
-
|
100
|
+
|
74
101
|
def count_records
|
75
|
-
load_collection
|
76
|
-
@collection.size
|
102
|
+
load_collection.size
|
77
103
|
end
|
78
104
|
|
79
105
|
def insert_record(record)
|
@@ -3,12 +3,13 @@ module ActiveRecord
|
|
3
3
|
class HasManyAssociation < AssociationCollection #:nodoc:
|
4
4
|
def initialize(owner, association_name, association_class_name, association_class_primary_key_name, options)
|
5
5
|
super(owner, association_name, association_class_name, association_class_primary_key_name, options)
|
6
|
-
@conditions =
|
6
|
+
@conditions = sanitize_sql(options[:conditions])
|
7
7
|
|
8
8
|
if options[:finder_sql]
|
9
9
|
@finder_sql = interpolate_sql(options[:finder_sql])
|
10
10
|
else
|
11
|
-
@finder_sql = "#{@association_class_primary_key_name} = #{@owner.quoted_id}
|
11
|
+
@finder_sql = "#{@association_class_primary_key_name} = #{@owner.quoted_id}"
|
12
|
+
@finder_sql << " AND #{@conditions}" if @conditions
|
12
13
|
end
|
13
14
|
|
14
15
|
if options[:counter_sql]
|
@@ -35,29 +36,57 @@ module ActiveRecord
|
|
35
36
|
record
|
36
37
|
end
|
37
38
|
|
38
|
-
def find_all(runtime_conditions = nil, orderings = nil, limit = nil, joins = nil
|
39
|
-
if
|
40
|
-
|
41
|
-
@collection.find_all(&block)
|
39
|
+
def find_all(runtime_conditions = nil, orderings = nil, limit = nil, joins = nil)
|
40
|
+
if @options[:finder_sql]
|
41
|
+
records = @association_class.find_by_sql(@finder_sql)
|
42
42
|
else
|
43
|
-
@
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
limit,
|
48
|
-
joins
|
49
|
-
)
|
43
|
+
sql = @finder_sql
|
44
|
+
sql << " AND #{sanitize_sql(runtime_conditions)}" if runtime_conditions
|
45
|
+
orderings ||= @options[:order]
|
46
|
+
records = @association_class.find_all(sql, orderings, limit, joins)
|
50
47
|
end
|
51
48
|
end
|
52
49
|
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
@
|
50
|
+
# Count the number of associated records. All arguments are optional.
|
51
|
+
def count(runtime_conditions = nil)
|
52
|
+
if @options[:finder_sql]
|
53
|
+
@association_class.count_by_sql(@finder_sql)
|
57
54
|
else
|
58
|
-
@
|
59
|
-
|
60
|
-
)
|
55
|
+
sql = @finder_sql
|
56
|
+
sql << " AND #{sanitize_sql(runtime_conditions)}" if runtime_conditions
|
57
|
+
@association_class.count(sql)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
# Find the first associated record. All arguments are optional.
|
62
|
+
def find_first(conditions = nil, orderings = nil)
|
63
|
+
find_all(conditions, orderings, 1).first
|
64
|
+
end
|
65
|
+
|
66
|
+
def find(*args)
|
67
|
+
# Return an Array if multiple ids are given.
|
68
|
+
expects_array = args.first.kind_of?(Array)
|
69
|
+
|
70
|
+
ids = args.flatten.compact.uniq
|
71
|
+
|
72
|
+
# If no ids given, raise RecordNotFound.
|
73
|
+
if ids.empty?
|
74
|
+
raise RecordNotFound, "Couldn't find #{@association_class.name} without an ID"
|
75
|
+
|
76
|
+
# If using a custom finder_sql, scan the entire collection.
|
77
|
+
elsif @options[:finder_sql]
|
78
|
+
if ids.size == 1
|
79
|
+
id = ids.first
|
80
|
+
record = load_collection.detect { |record| id == record.id }
|
81
|
+
expects_array? ? [record] : record
|
82
|
+
else
|
83
|
+
load_collection.select { |record| ids.include?(record.id) }
|
84
|
+
end
|
85
|
+
|
86
|
+
# Otherwise, delegate to association class with conditions.
|
87
|
+
else
|
88
|
+
args << { :conditions => "#{@association_class_primary_key_name} = #{@owner.quoted_id} #{@conditions ? " AND " + @conditions : ""}" }
|
89
|
+
@association_class.find(*args)
|
61
90
|
end
|
62
91
|
end
|
63
92
|
|
@@ -71,11 +100,7 @@ module ActiveRecord
|
|
71
100
|
|
72
101
|
protected
|
73
102
|
def find_all_records
|
74
|
-
|
75
|
-
@association_class.find_by_sql(@finder_sql)
|
76
|
-
else
|
77
|
-
@association_class.find_all(@finder_sql, @options[:order] ? @options[:order] : nil)
|
78
|
-
end
|
103
|
+
find_all
|
79
104
|
end
|
80
105
|
|
81
106
|
def count_records
|
data/lib/active_record/base.rb
CHANGED
@@ -26,6 +26,8 @@ module ActiveRecord #:nodoc:
|
|
26
26
|
end
|
27
27
|
class PreparedStatementInvalid < ActiveRecordError #:nodoc:
|
28
28
|
end
|
29
|
+
class StaleObjectError < ActiveRecordError #:nodoc:
|
30
|
+
end
|
29
31
|
|
30
32
|
# Active Record objects doesn't specify their attributes directly, but rather infer them from the table definition with
|
31
33
|
# which they're linked. Adding, removing, and changing attributes and their type is done directly in the database. Any change
|
@@ -95,6 +97,21 @@ module ActiveRecord #:nodoc:
|
|
95
97
|
# end
|
96
98
|
# end
|
97
99
|
#
|
100
|
+
# == Dynamic attribute-based finders
|
101
|
+
#
|
102
|
+
# Dynamic attribute-based finders are a cleaner way of getting objects by simple queries without turning to SQL. They work by
|
103
|
+
# appending the name of an attribute to <tt>find_by_</tt>, so you get finders like <tt>Person.find_by_user_name, Payment.find_by_transaction_id</tt>.
|
104
|
+
# So instead of writing <tt>Person.find_first(["user_name = ?", user_name])</tt>, you just do <tt>Person.find_by_user_name(user_name)</tt>.
|
105
|
+
#
|
106
|
+
# It's also possible to use multiple attributes in the same find by separating them with "_and_", so you get finders like
|
107
|
+
# <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
|
108
|
+
# <tt>Person.find_first(["user_name = ? AND password = ?", user_name, password])</tt>, you just do
|
109
|
+
# <tt>Person.find_by_user_name_and_password(user_name, password)</tt>.
|
110
|
+
#
|
111
|
+
# It's even possible to use all the additional parameters to find_first and find_all. For example, the full interface for Payment.find_all_by_amount
|
112
|
+
# is actually Payment.find_all_by_amount(amount, orderings = nil, limit = nil, joins = nil). And the full interface to Person.find_by_user_name is
|
113
|
+
# actually Person.find_by_user_name(user_name, orderings = nil)
|
114
|
+
#
|
98
115
|
# == Saving arrays, hashes, and other non-mappeable objects in text columns
|
99
116
|
#
|
100
117
|
# 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+.
|
@@ -221,14 +238,10 @@ module ActiveRecord #:nodoc:
|
|
221
238
|
cattr_accessor :pluralize_table_names
|
222
239
|
@@pluralize_table_names = true
|
223
240
|
|
224
|
-
#
|
225
|
-
#
|
226
|
-
|
227
|
-
@@
|
228
|
-
cattr_accessor :reload_associations
|
229
|
-
|
230
|
-
@@associations_loaded = []
|
231
|
-
cattr_accessor :associations_loaded
|
241
|
+
# Determines whether to use Time.local (using :local) or Time.utc (using :utc) when pulling dates and times from the database.
|
242
|
+
# This is set to :local by default.
|
243
|
+
cattr_accessor :default_timezone
|
244
|
+
@@default_timezone = :local
|
232
245
|
|
233
246
|
class << self # Class methods
|
234
247
|
# Returns objects for the records responding to either a specific id (1), a list of ids (1, 5, 6) or an array of ids.
|
@@ -238,44 +251,58 @@ module ActiveRecord #:nodoc:
|
|
238
251
|
# Person.find(1, 2, 6) # returns an array for objects with IDs in (1, 2, 6)
|
239
252
|
# Person.find([7, 17]) # returns an array for objects with IDs in (7, 17)
|
240
253
|
# Person.find([1]) # returns an array for objects the object with ID = 1
|
254
|
+
#
|
255
|
+
# The last argument may be a Hash of find options. Currently, +conditions+ is the only option, behaving the same as with +find_all+.
|
256
|
+
# Person.find(1, :conditions => "associate_id = 5"
|
257
|
+
# Person.find(1, 2, 6, :conditions => "status = 'active'"
|
258
|
+
# Person.find([7, 17], :conditions => ["sanitize_me = ?", "bare'quote"]
|
259
|
+
#
|
241
260
|
# +RecordNotFound+ is raised if no record can be found.
|
242
|
-
def find(*
|
243
|
-
|
244
|
-
|
261
|
+
def find(*args)
|
262
|
+
# Return an Array if ids are passed in an Array.
|
263
|
+
expects_array = args.first.kind_of?(Array)
|
264
|
+
|
265
|
+
# Extract options hash from argument list.
|
266
|
+
options = extract_options_from_args!(args)
|
267
|
+
conditions = " AND #{sanitize_sql(options[:conditions])}" if options[:conditions]
|
268
|
+
|
269
|
+
ids = args.flatten.compact.uniq
|
270
|
+
case ids.size
|
245
271
|
|
246
|
-
|
247
|
-
|
248
|
-
|
272
|
+
# Raise if no ids passed.
|
273
|
+
when 0
|
274
|
+
raise RecordNotFound, "Couldn't find #{name} without an ID#{conditions}"
|
249
275
|
|
250
|
-
|
251
|
-
|
276
|
+
# Find a single id.
|
277
|
+
when 1
|
278
|
+
unless result = find_first("#{primary_key} = #{sanitize(ids.first)}#{conditions}")
|
279
|
+
raise RecordNotFound, "Couldn't find #{name} with ID=#{ids.first}#{conditions}"
|
280
|
+
end
|
281
|
+
|
282
|
+
# Box result if expecting array.
|
283
|
+
expects_array ? [result] : result
|
284
|
+
|
285
|
+
# Find multiple ids.
|
252
286
|
else
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
if record = connection.select_one(sql, "#{name} Find")
|
261
|
-
expects_array ? [instantiate(record)] : instantiate(record)
|
262
|
-
else
|
263
|
-
raise RecordNotFound, "Couldn't find #{name} with ID = #{id}"
|
264
|
-
end
|
265
|
-
else
|
266
|
-
raise RecordNotFound, "Couldn't find #{name} without an ID"
|
287
|
+
ids_list = ids.map { |id| sanitize(id) }.join(',')
|
288
|
+
result = find_all("#{primary_key} IN (#{ids_list})#{conditions}", primary_key)
|
289
|
+
if result.size == ids.size
|
290
|
+
result
|
291
|
+
else
|
292
|
+
raise RecordNotFound, "Couldn't find #{name} with ID in (#{ids_list})#{conditions}"
|
293
|
+
end
|
267
294
|
end
|
268
295
|
end
|
269
296
|
|
297
|
+
# This method is deprecated in favor of find with the :conditions option.
|
270
298
|
# Works like find, but the record matching +id+ must also meet the +conditions+.
|
271
299
|
# +RecordNotFound+ is raised if no record can be found matching the +id+ or meeting the condition.
|
272
300
|
# Example:
|
273
301
|
# Person.find_on_conditions 5, "first_name LIKE '%dav%' AND last_name = 'heinemeier'"
|
274
|
-
def find_on_conditions(
|
275
|
-
|
276
|
-
raise(RecordNotFound, "Couldn't find #{name} with #{primary_key} = #{id} on the condition of #{conditions}")
|
302
|
+
def find_on_conditions(ids, conditions)
|
303
|
+
find(ids, :conditions => conditions)
|
277
304
|
end
|
278
|
-
|
305
|
+
|
279
306
|
# Returns an array of all the objects that could be instantiated from the associated
|
280
307
|
# table in the database. The +conditions+ can be used to narrow the selection of objects (WHERE-part),
|
281
308
|
# such as by "color = 'red'", and arrangement of the selection can be done through +orderings+ (ORDER BY-part),
|
@@ -288,8 +315,9 @@ module ActiveRecord #:nodoc:
|
|
288
315
|
sql << "#{joins} " if joins
|
289
316
|
add_conditions!(sql, conditions)
|
290
317
|
sql << "ORDER BY #{orderings} " unless orderings.nil?
|
291
|
-
|
292
|
-
|
318
|
+
|
319
|
+
connection.add_limit!(sql, sanitize_sql(limit)) unless limit.nil?
|
320
|
+
|
293
321
|
find_by_sql(sql)
|
294
322
|
end
|
295
323
|
|
@@ -297,8 +325,7 @@ module ActiveRecord #:nodoc:
|
|
297
325
|
# Post.find_by_sql "SELECT p.*, c.author FROM posts p, comments c WHERE p.id = c.post_id"
|
298
326
|
# Post.find_by_sql ["SELECT * FROM posts WHERE author = ? AND created > ?", author_id, start_date]
|
299
327
|
def find_by_sql(sql)
|
300
|
-
sql
|
301
|
-
connection.select_all(sql, "#{name} Load").inject([]) { |objects, record| objects << instantiate(record) }
|
328
|
+
connection.select_all(sanitize_sql(sql), "#{name} Load").inject([]) { |objects, record| objects << instantiate(record) }
|
302
329
|
end
|
303
330
|
|
304
331
|
# Returns the object for the first record responding to the conditions in +conditions+,
|
@@ -307,13 +334,7 @@ module ActiveRecord #:nodoc:
|
|
307
334
|
# +orderings+, like "income DESC, name", to control exactly which record is to be used. Example:
|
308
335
|
# Employee.find_first "income > 50000", "income DESC, name"
|
309
336
|
def find_first(conditions = nil, orderings = nil)
|
310
|
-
|
311
|
-
add_conditions!(sql, conditions)
|
312
|
-
sql << "ORDER BY #{orderings} " unless orderings.nil?
|
313
|
-
sql << "LIMIT 1"
|
314
|
-
|
315
|
-
record = connection.select_one(sql, "#{name} Load First")
|
316
|
-
instantiate(record) unless record.nil?
|
337
|
+
find_all(conditions, orderings, 1).first
|
317
338
|
end
|
318
339
|
|
319
340
|
# Creates an object, instantly saves it as a record (if the validation permits it), and returns it. If the save
|
@@ -613,7 +634,7 @@ module ActiveRecord #:nodoc:
|
|
613
634
|
|
614
635
|
# Adds a sanitized version of +conditions+ to the +sql+ string. Note that it's the passed +sql+ string is changed.
|
615
636
|
def add_conditions!(sql, conditions)
|
616
|
-
sql << "WHERE #{
|
637
|
+
sql << "WHERE #{sanitize_sql(conditions)} " unless conditions.nil?
|
617
638
|
sql << (conditions.nil? ? "WHERE " : " AND ") + type_condition unless descends_from_active_record?
|
618
639
|
end
|
619
640
|
|
@@ -630,6 +651,24 @@ module ActiveRecord #:nodoc:
|
|
630
651
|
return table_name
|
631
652
|
end
|
632
653
|
|
654
|
+
# Enables dynamic finders like find_by_user_name(user_name) and find_by_user_name_and_password(user_name, password) that are turned into
|
655
|
+
# find_first(["user_name = ?", user_name]) and find_first(["user_name = ? AND password = ?", user_name, password]) respectively. Also works
|
656
|
+
# for find_all, but using find_all_by_amount(50) that are turned into find_all(["amount = ?", 50]).
|
657
|
+
#
|
658
|
+
# It's even possible to use all the additional parameters to find_first and find_all. For example, the full interface for find_all_by_amount
|
659
|
+
# is actually find_all_by_amount(amount, orderings = nil, limit = nil, joins = nil).
|
660
|
+
def method_missing(method_id, *arguments)
|
661
|
+
method_name = method_id.id2name
|
662
|
+
|
663
|
+
if method_name =~ /find_(all_by|by)_([_a-z]+)/
|
664
|
+
finder, attributes = ($1 == "all_by" ? :find_all : :find_first), $2.split("_and_")
|
665
|
+
attributes.each { |attr_name| super unless column_methods_hash[attr_name.intern] }
|
666
|
+
conditions = attributes.collect { |attr_name| "#{attr_name} = ? "}.join(" AND ")
|
667
|
+
send(finder, [conditions, *arguments[0...attributes.length]], *arguments[attributes.length..-1])
|
668
|
+
else
|
669
|
+
super
|
670
|
+
end
|
671
|
+
end
|
633
672
|
|
634
673
|
protected
|
635
674
|
def subclasses
|
@@ -656,51 +695,50 @@ module ActiveRecord #:nodoc:
|
|
656
695
|
end
|
657
696
|
end
|
658
697
|
|
659
|
-
# Accepts
|
660
|
-
# the
|
661
|
-
|
662
|
-
|
663
|
-
|
664
|
-
statement, *values = conditions
|
698
|
+
# Accepts an array or string. The string is returned untouched, but the array has each value
|
699
|
+
# sanitized and interpolated into the sql statement.
|
700
|
+
# ["name='%s' and group_id='%s'", "foo'bar", 4] returns "name='foo''bar' and group_id='4'"
|
701
|
+
def sanitize_sql(ary)
|
702
|
+
return ary unless ary.is_a?(Array)
|
665
703
|
|
666
|
-
|
667
|
-
|
668
|
-
|
704
|
+
statement, *values = ary
|
705
|
+
if values.first.is_a?(Hash) and statement =~ /:\w+/
|
706
|
+
replace_named_bind_variables(statement, values.first)
|
707
|
+
elsif statement.include?('?')
|
669
708
|
replace_bind_variables(statement, values)
|
670
709
|
else
|
671
710
|
statement % values.collect { |value| connection.quote_string(value.to_s) }
|
672
711
|
end
|
673
712
|
end
|
674
713
|
|
675
|
-
|
676
|
-
orig_statement = statement.clone
|
677
|
-
expected_number_of_variables = statement.count('?')
|
678
|
-
provided_number_of_variables = values.size
|
679
|
-
|
680
|
-
unless expected_number_of_variables == provided_number_of_variables
|
681
|
-
raise PreparedStatementInvalid, "wrong number of bind variables (#{provided_number_of_variables} for #{expected_number_of_variables})"
|
682
|
-
end
|
714
|
+
alias_method :sanitize_conditions, :sanitize_sql
|
683
715
|
|
684
|
-
|
685
|
-
|
686
|
-
|
687
|
-
|
688
|
-
statement.gsub('?') { |all, match| connection.quote(values.shift) }
|
716
|
+
def replace_bind_variables(statement, values)
|
717
|
+
raise_if_bind_arity_mismatch(statement, statement.count('?'), values.size)
|
718
|
+
bound = values.dup
|
719
|
+
statement.gsub('?') { connection.quote(bound.shift) }
|
689
720
|
end
|
690
721
|
|
691
|
-
def replace_named_bind_variables(statement,
|
692
|
-
|
693
|
-
|
694
|
-
|
695
|
-
|
722
|
+
def replace_named_bind_variables(statement, bind_vars)
|
723
|
+
raise_if_bind_arity_mismatch(statement, statement.scan(/:(\w+)/).uniq.size, bind_vars.size)
|
724
|
+
statement.gsub(/:(\w+)/) do
|
725
|
+
match = $1.to_sym
|
726
|
+
if bind_vars.has_key?(match)
|
727
|
+
connection.quote(bind_vars[match])
|
728
|
+
else
|
729
|
+
raise PreparedStatementInvalid, "missing value for :#{match} in #{statement}"
|
696
730
|
end
|
697
731
|
end
|
732
|
+
end
|
698
733
|
|
699
|
-
|
700
|
-
|
734
|
+
def raise_if_bind_arity_mismatch(statement, expected, provided)
|
735
|
+
unless expected == provided
|
736
|
+
raise PreparedStatementInvalid, "wrong number of bind variables (#{provided} for #{expected}) in: #{statement}"
|
701
737
|
end
|
738
|
+
end
|
702
739
|
|
703
|
-
|
740
|
+
def extract_options_from_args!(args)
|
741
|
+
if args.last.is_a?(Hash) then args.pop else {} end
|
704
742
|
end
|
705
743
|
|
706
744
|
def encode_quoted_value(value)
|
@@ -867,8 +905,8 @@ module ActiveRecord #:nodoc:
|
|
867
905
|
|
868
906
|
# A Person object with a name attribute can ask person.respond_to?("name"), person.respond_to?("name="), and
|
869
907
|
# person.respond_to?("name?") which will all return true.
|
870
|
-
def respond_to?(method)
|
871
|
-
self.class.column_methods_hash[method.to_sym] || respond_to_without_attributes?(method)
|
908
|
+
def respond_to?(method, include_priv = false)
|
909
|
+
self.class.column_methods_hash[method.to_sym] || respond_to_without_attributes?(method, include_priv)
|
872
910
|
end
|
873
911
|
|
874
912
|
private
|
@@ -919,7 +957,7 @@ module ActiveRecord #:nodoc:
|
|
919
957
|
# table with a master_id foreign key can instantiate master through Client#master.
|
920
958
|
def method_missing(method_id, *arguments)
|
921
959
|
method_name = method_id.id2name
|
922
|
-
|
960
|
+
|
923
961
|
if method_name =~ read_method? && @attributes.include?($1)
|
924
962
|
return read_attribute($1)
|
925
963
|
elsif method_name =~ read_untyped_method? && @attributes.include?($1)
|
@@ -959,7 +997,7 @@ module ActiveRecord #:nodoc:
|
|
959
997
|
|
960
998
|
# Returns true if the attribute is of a text column and marked for serialization.
|
961
999
|
def unserializable_attribute?(attr_name, column)
|
962
|
-
@attributes[attr_name] && column.send(:type)
|
1000
|
+
@attributes[attr_name] && [:text, :string].include?(column.send(:type)) && @attributes[attr_name].is_a?(String) && self.class.serialized_attributes[attr_name]
|
963
1001
|
end
|
964
1002
|
|
965
1003
|
# Returns the unserialized object of the attribute.
|