activerecord 1.9.1 → 1.10.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 +78 -0
- data/README +1 -1
- data/install.rb +7 -42
- data/lib/active_record.rb +2 -0
- data/lib/active_record/acts/list.rb +28 -4
- data/lib/active_record/acts/nested_set.rb +212 -0
- data/lib/active_record/associations.rb +203 -21
- data/lib/active_record/associations/association_proxy.rb +10 -2
- data/lib/active_record/associations/belongs_to_association.rb +0 -1
- data/lib/active_record/associations/has_and_belongs_to_many_association.rb +15 -9
- data/lib/active_record/associations/has_many_association.rb +25 -25
- data/lib/active_record/associations/has_one_association.rb +2 -2
- data/lib/active_record/base.rb +134 -110
- data/lib/active_record/connection_adapters/abstract_adapter.rb +9 -9
- data/lib/active_record/connection_adapters/mysql_adapter.rb +4 -0
- data/lib/active_record/connection_adapters/oci_adapter.rb +2 -2
- data/lib/active_record/connection_adapters/sqlserver_adapter.rb +1 -2
- data/lib/active_record/deprecated_associations.rb +1 -19
- data/lib/active_record/deprecated_finders.rb +41 -0
- data/lib/active_record/fixtures.rb +24 -11
- data/lib/active_record/observer.rb +17 -11
- data/lib/active_record/reflection.rb +5 -1
- data/lib/active_record/transactions.rb +7 -0
- data/lib/active_record/validations.rb +32 -33
- data/rakefile +30 -6
- data/test/associations_go_eager_test.rb +55 -0
- data/test/associations_test.rb +72 -15
- data/test/base_test.rb +15 -21
- data/test/deprecated_associations_test.rb +0 -24
- data/test/deprecated_finder_test.rb +147 -0
- data/test/finder_test.rb +37 -37
- data/test/fixtures/author.rb +3 -0
- data/test/fixtures/authors.yml +7 -0
- data/test/fixtures/categories.yml +7 -0
- data/test/fixtures/categories_posts.yml +11 -0
- data/test/fixtures/category.rb +3 -0
- data/test/fixtures/comment.rb +5 -0
- data/test/fixtures/comments.yml +17 -0
- data/test/fixtures/company.rb +3 -0
- data/test/fixtures/courses.yml +4 -4
- data/test/fixtures/db_definitions/db2.drop.sql +6 -0
- data/test/fixtures/db_definitions/db2.sql +46 -0
- data/test/fixtures/db_definitions/mysql.drop.sql +6 -1
- data/test/fixtures/db_definitions/mysql.sql +60 -12
- data/test/fixtures/db_definitions/mysql2.sql +1 -1
- data/test/fixtures/db_definitions/oci.drop.sql +5 -0
- data/test/fixtures/db_definitions/oci.sql +45 -0
- data/test/fixtures/db_definitions/postgresql.drop.sql +6 -0
- data/test/fixtures/db_definitions/postgresql.sql +45 -0
- data/test/fixtures/db_definitions/sqlite.drop.sql +6 -1
- data/test/fixtures/db_definitions/sqlite.sql +46 -0
- data/test/fixtures/db_definitions/sqlserver.drop.sql +7 -1
- data/test/fixtures/db_definitions/sqlserver.sql +46 -0
- data/test/fixtures/fk_test_has_fk.yml +3 -0
- data/test/fixtures/fk_test_has_pk.yml +2 -0
- data/test/fixtures/mixin.rb +18 -0
- data/test/fixtures/mixins.yml +30 -0
- data/test/fixtures/post.rb +8 -0
- data/test/fixtures/posts.yml +20 -0
- data/test/fixtures/task.rb +3 -0
- data/test/fixtures/tasks.yml +7 -0
- data/test/fixtures_test.rb +34 -2
- data/test/mixin_nested_set_test.rb +184 -0
- data/test/mixin_test.rb +28 -3
- data/test/validations_test.rb +16 -0
- metadata +21 -5
- data/test/fixtures/db_definitions/drop_oracle_tables.sql +0 -35
- data/test/fixtures/db_definitions/drop_oracle_tables2.sql +0 -3
@@ -25,13 +25,21 @@ module ActiveRecord
|
|
25
25
|
end
|
26
26
|
|
27
27
|
def respond_to?(symbol, include_priv = false)
|
28
|
-
load_target
|
29
|
-
proxy_respond_to?(symbol, include_priv) || @target.respond_to?(symbol, include_priv)
|
28
|
+
proxy_respond_to?(symbol, include_priv) || (load_target && @target.respond_to?(symbol, include_priv))
|
30
29
|
end
|
31
30
|
|
32
31
|
def loaded?
|
33
32
|
@loaded
|
34
33
|
end
|
34
|
+
|
35
|
+
def target
|
36
|
+
@target
|
37
|
+
end
|
38
|
+
|
39
|
+
def target=(t)
|
40
|
+
@target = t
|
41
|
+
@loaded = true
|
42
|
+
end
|
35
43
|
|
36
44
|
protected
|
37
45
|
def dependent?
|
@@ -7,7 +7,7 @@ module ActiveRecord
|
|
7
7
|
@association_foreign_key = options[:association_foreign_key] || Inflector.underscore(Inflector.demodulize(association_class_name)) + "_id"
|
8
8
|
@association_table_name = options[:table_name] || @association_class.table_name
|
9
9
|
@join_table = options[:join_table]
|
10
|
-
@order = options[:order]
|
10
|
+
@order = options[:order]
|
11
11
|
|
12
12
|
construct_sql
|
13
13
|
end
|
@@ -51,7 +51,7 @@ module ActiveRecord
|
|
51
51
|
|
52
52
|
# If no block is given, raise RecordNotFound.
|
53
53
|
if ids.empty?
|
54
|
-
raise RecordNotFound, "Couldn't find #{@association_class.name} without an ID
|
54
|
+
raise RecordNotFound, "Couldn't find #{@association_class.name} without an ID"
|
55
55
|
|
56
56
|
# If using a custom finder_sql, scan the entire collection.
|
57
57
|
elsif @options[:finder_sql]
|
@@ -66,7 +66,7 @@ module ActiveRecord
|
|
66
66
|
# Otherwise, construct a query.
|
67
67
|
else
|
68
68
|
ids_list = ids.map { |id| @owner.send(:quote, id) }.join(',')
|
69
|
-
records = find_target(@finder_sql.sub(/ORDER BY/, "AND j.#{@association_foreign_key} IN (#{ids_list})
|
69
|
+
records = find_target(@finder_sql.sub(/(ORDER BY|$)/, "AND j.#{@association_foreign_key} IN (#{ids_list}) \\1"))
|
70
70
|
if records.size == ids.size
|
71
71
|
if ids.size == 1 and !expects_array
|
72
72
|
records.first
|
@@ -146,12 +146,18 @@ module ActiveRecord
|
|
146
146
|
|
147
147
|
def construct_sql
|
148
148
|
interpolate_sql_options!(@options, :finder_sql, :delete_sql)
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
149
|
+
|
150
|
+
if @options[:finder_sql]
|
151
|
+
@finder_sql = @options[:finder_sql]
|
152
|
+
else
|
153
|
+
@finder_sql =
|
154
|
+
"SELECT t.*, j.* FROM #{@join_table} j, #{@association_table_name} t " +
|
155
|
+
"WHERE t.#{@association_class.primary_key} = j.#{@association_foreign_key} AND " +
|
156
|
+
"j.#{@association_class_primary_key_name} = #{@owner.quoted_id} "
|
157
|
+
|
158
|
+
@finder_sql << " AND #{interpolate_sql(@options[:conditions])}" if @options[:conditions]
|
159
|
+
@finder_sql << " ORDER BY #{@order}" if @order
|
160
|
+
end
|
155
161
|
end
|
156
162
|
end
|
157
163
|
end
|
@@ -20,6 +20,7 @@ module ActiveRecord
|
|
20
20
|
end
|
21
21
|
end
|
22
22
|
|
23
|
+
# DEPRECATED.
|
23
24
|
def find_all(runtime_conditions = nil, orderings = nil, limit = nil, joins = nil)
|
24
25
|
if @options[:finder_sql]
|
25
26
|
records = @association_class.find_by_sql(@finder_sql)
|
@@ -31,6 +32,11 @@ module ActiveRecord
|
|
31
32
|
end
|
32
33
|
end
|
33
34
|
|
35
|
+
# DEPRECATED. Find the first associated record. All arguments are optional.
|
36
|
+
def find_first(conditions = nil, orderings = nil)
|
37
|
+
find_all(conditions, orderings, 1).first
|
38
|
+
end
|
39
|
+
|
34
40
|
# Count the number of associated records. All arguments are optional.
|
35
41
|
def count(runtime_conditions = nil)
|
36
42
|
if @options[:counter_sql]
|
@@ -43,24 +49,15 @@ module ActiveRecord
|
|
43
49
|
@association_class.count(sql)
|
44
50
|
end
|
45
51
|
end
|
46
|
-
|
47
|
-
# Find the first associated record. All arguments are optional.
|
48
|
-
def find_first(conditions = nil, orderings = nil)
|
49
|
-
find_all(conditions, orderings, 1).first
|
50
|
-
end
|
51
52
|
|
52
53
|
def find(*args)
|
53
|
-
|
54
|
-
expects_array = args.first.kind_of?(Array)
|
55
|
-
|
56
|
-
ids = args.flatten.compact.uniq
|
57
|
-
|
58
|
-
# If no ids given, raise RecordNotFound.
|
59
|
-
if ids.empty?
|
60
|
-
raise RecordNotFound, "Couldn't find #{@association_class.name} without an ID"
|
54
|
+
options = Base.send(:extract_options_from_args!, args)
|
61
55
|
|
62
56
|
# If using a custom finder_sql, scan the entire collection.
|
63
|
-
|
57
|
+
if @options[:finder_sql]
|
58
|
+
expects_array = args.first.kind_of?(Array)
|
59
|
+
ids = args.flatten.compact.uniq
|
60
|
+
|
64
61
|
if ids.size == 1
|
65
62
|
id = ids.first
|
66
63
|
record = load_target.detect { |record| id == record.id }
|
@@ -68,11 +65,10 @@ module ActiveRecord
|
|
68
65
|
else
|
69
66
|
load_target.select { |record| ids.include?(record.id) }
|
70
67
|
end
|
71
|
-
|
72
|
-
# Otherwise, delegate to association class with conditions.
|
73
68
|
else
|
74
|
-
|
75
|
-
@
|
69
|
+
options[:conditions] = @finder_sql + (options[:conditions] ? " AND #{options[:conditions]}" : "")
|
70
|
+
options[:order] = options[:order] ? "#{options[:order]}, #{@options[:order]}" : @options[:order]
|
71
|
+
@association_class.find(args.size == 1 ? args.first : args, options)
|
76
72
|
end
|
77
73
|
end
|
78
74
|
|
@@ -113,11 +109,15 @@ module ActiveRecord
|
|
113
109
|
end
|
114
110
|
|
115
111
|
def delete_records(records)
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
112
|
+
if @options[:dependent]
|
113
|
+
records.each { |r| r.destroy }
|
114
|
+
else
|
115
|
+
ids = quoted_record_ids(records)
|
116
|
+
@association_class.update_all(
|
117
|
+
"#{@association_class_primary_key_name} = NULL",
|
118
|
+
"#{@association_class_primary_key_name} = #{@owner.quoted_id} AND #{@association_class.primary_key} IN (#{ids})"
|
119
|
+
)
|
120
|
+
end
|
121
121
|
end
|
122
122
|
|
123
123
|
def target_obsolete?
|
@@ -128,7 +128,7 @@ module ActiveRecord
|
|
128
128
|
if @options[:finder_sql]
|
129
129
|
@finder_sql = interpolate_sql(@options[:finder_sql])
|
130
130
|
else
|
131
|
-
@finder_sql = "#{@association_class_primary_key_name} = #{@owner.quoted_id}"
|
131
|
+
@finder_sql = "#{@association_class.table_name}.#{@association_class_primary_key_name} = #{@owner.quoted_id}"
|
132
132
|
@finder_sql << " AND #{interpolate_sql(@conditions)}" if @conditions
|
133
133
|
end
|
134
134
|
|
@@ -138,7 +138,7 @@ module ActiveRecord
|
|
138
138
|
@options[:counter_sql] = @options[:finder_sql].gsub(/SELECT (.*) FROM/i, "SELECT COUNT(*) FROM")
|
139
139
|
@counter_sql = interpolate_sql(@options[:counter_sql])
|
140
140
|
else
|
141
|
-
@counter_sql = "#{@association_class_primary_key_name} = #{@owner.quoted_id}"
|
141
|
+
@counter_sql = "#{@association_class.table_name}.#{@association_class_primary_key_name} = #{@owner.quoted_id}"
|
142
142
|
@counter_sql << " AND #{interpolate_sql(@conditions)}" if @conditions
|
143
143
|
end
|
144
144
|
end
|
@@ -38,7 +38,7 @@ module ActiveRecord
|
|
38
38
|
|
39
39
|
private
|
40
40
|
def find_target
|
41
|
-
@association_class.
|
41
|
+
@association_class.find(:first, :conditions => @finder_sql, :order => @options[:order])
|
42
42
|
end
|
43
43
|
|
44
44
|
def target_obsolete?
|
@@ -46,7 +46,7 @@ module ActiveRecord
|
|
46
46
|
end
|
47
47
|
|
48
48
|
def construct_sql
|
49
|
-
@finder_sql = "#{@association_class_primary_key_name} = #{@owner.quoted_id}#{@options[:conditions] ? " AND " + @options[:conditions] : ""}"
|
49
|
+
@finder_sql = "#{@association_class.table_name}.#{@association_class_primary_key_name} = #{@owner.quoted_id}#{@options[:conditions] ? " AND " + @options[:conditions] : ""}"
|
50
50
|
end
|
51
51
|
end
|
52
52
|
end
|
data/lib/active_record/base.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
require 'yaml'
|
2
|
+
require 'active_record/deprecated_finders'
|
2
3
|
|
3
4
|
module ActiveRecord #:nodoc:
|
4
5
|
class ActiveRecordError < StandardError #:nodoc:
|
@@ -54,7 +55,7 @@ module ActiveRecord #:nodoc:
|
|
54
55
|
# Active Records accepts constructor parameters either in a hash or as a block. The hash method is especially useful when
|
55
56
|
# you're receiving the data from somewhere else, like a HTTP request. It works like this:
|
56
57
|
#
|
57
|
-
# user = User.new(
|
58
|
+
# user = User.new(:name => "David", :occupation => "Code Artist")
|
58
59
|
# user.name # => "David"
|
59
60
|
#
|
60
61
|
# You can also use block initialization:
|
@@ -95,7 +96,7 @@ module ActiveRecord #:nodoc:
|
|
95
96
|
# question mark is supposed to represent. In those cases, you can resort to named bind variables instead. That's done by replacing
|
96
97
|
# the question marks with symbols and supplying a hash with values for the matching symbol keys:
|
97
98
|
#
|
98
|
-
# Company.
|
99
|
+
# Company.find(:first, [
|
99
100
|
# "id = :id AND name = :name AND division = :division AND created_at > :accounting_date",
|
100
101
|
# { :id => 3, :name => "37signals", :division => "First", :accounting_date => '2005-01-01' }
|
101
102
|
# ])
|
@@ -111,14 +112,17 @@ module ActiveRecord #:nodoc:
|
|
111
112
|
# # Uses an integer of seconds to hold the length of the song
|
112
113
|
#
|
113
114
|
# def length=(minutes)
|
114
|
-
# write_attribute(
|
115
|
+
# write_attribute(:length, minutes * 60)
|
115
116
|
# end
|
116
117
|
#
|
117
118
|
# def length
|
118
|
-
# read_attribute(
|
119
|
+
# read_attribute(:length) / 60
|
119
120
|
# end
|
120
121
|
# end
|
121
122
|
#
|
123
|
+
# You can alternatively use self[:attribute]=(value) and self[:attribute] instead of write_attribute(:attribute, vaule) and
|
124
|
+
# read_attribute(:attribute) as a shorter form.
|
125
|
+
#
|
122
126
|
# == Accessing attributes before they have been type casted
|
123
127
|
#
|
124
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.
|
@@ -133,16 +137,16 @@ module ActiveRecord #:nodoc:
|
|
133
137
|
#
|
134
138
|
# Dynamic attribute-based finders are a cleaner way of getting objects by simple queries without turning to SQL. They work by
|
135
139
|
# 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>.
|
136
|
-
# So instead of writing <tt>Person.
|
140
|
+
# 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>.
|
137
141
|
#
|
138
142
|
# It's also possible to use multiple attributes in the same find by separating them with "_and_", so you get finders like
|
139
143
|
# <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
|
140
|
-
# <tt>Person.
|
144
|
+
# <tt>Person.find(:first, ["user_name = ? AND password = ?", user_name, password])</tt>, you just do
|
141
145
|
# <tt>Person.find_by_user_name_and_password(user_name, password)</tt>.
|
142
146
|
#
|
143
|
-
# It's even possible to use all the additional parameters to
|
144
|
-
# is actually Payment.find_all_by_amount(amount,
|
145
|
-
# actually Person.find_by_user_name(user_name,
|
147
|
+
# It's even possible to use all the additional parameters to find. For example, the full interface for Payment.find_all_by_amount
|
148
|
+
# is actually Payment.find_all_by_amount(amount, options). And the full interface to Person.find_by_user_name is
|
149
|
+
# actually Person.find_by_user_name(user_name, options). So you could call <tt>Payment.find_all_by_amount(50, :order => "created_on")</tt>.
|
146
150
|
#
|
147
151
|
# == Saving arrays, hashes, and other non-mappable objects in text columns
|
148
152
|
#
|
@@ -153,7 +157,7 @@ module ActiveRecord #:nodoc:
|
|
153
157
|
# serialize :preferences
|
154
158
|
# end
|
155
159
|
#
|
156
|
-
# user = User.create(
|
160
|
+
# user = User.create(:preferences) => { "background" => "black", "display" => large })
|
157
161
|
# User.find(user.id).preferences # => { "background" => "black", "display" => large }
|
158
162
|
#
|
159
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
|
@@ -163,7 +167,7 @@ module ActiveRecord #:nodoc:
|
|
163
167
|
# serialize :preferences, Hash
|
164
168
|
# end
|
165
169
|
#
|
166
|
-
# user = User.create(
|
170
|
+
# user = User.create(:preferences => %w( one two three ))
|
167
171
|
# User.find(user.id).preferences # raises SerializationTypeMismatch
|
168
172
|
#
|
169
173
|
# == Single table inheritance
|
@@ -176,8 +180,8 @@ module ActiveRecord #:nodoc:
|
|
176
180
|
# class Client < Company; end
|
177
181
|
# class PriorityClient < Client; end
|
178
182
|
#
|
179
|
-
# When you do Firm.create(
|
180
|
-
# fetch this row again using Company.
|
183
|
+
# When you do Firm.create(:name => "37signals"), this record will be saved in the companies table with type = "Firm". You can then
|
184
|
+
# fetch this row again using Company.find(:first, "name = '37signals'") and it will return a Firm object.
|
181
185
|
#
|
182
186
|
# If you don't have a type column defined in your table, single-table inheritance won't be triggered. In that case, it'll work just
|
183
187
|
# like normal subclasses with no special magic for differentiating between them or reloading the right type with find.
|
@@ -285,93 +289,76 @@ module ActiveRecord #:nodoc:
|
|
285
289
|
@@default_timezone = :local
|
286
290
|
|
287
291
|
class << self # Class methods
|
288
|
-
#
|
289
|
-
#
|
290
|
-
#
|
292
|
+
# Find operates with three different retreval approaches:
|
293
|
+
#
|
294
|
+
# * Find by id: This can either be a specific id (1), a list of ids (1, 5, 6), or an array of ids ([5, 6, 10]).
|
295
|
+
# If no record can be found for all of the listed ids, then RecordNotFound will be raised.
|
296
|
+
# * Find first: This will return the first record matched by the options used. These options can either be specific
|
297
|
+
# conditions or merely an order. If no record can matched, nil is returned.
|
298
|
+
# * Find all: This will return all the records matched by the options used. If no records are found, an empty array is returned.
|
299
|
+
#
|
300
|
+
# All approaches accepts an option hash as their last parameter. The options are:
|
301
|
+
#
|
302
|
+
# * <tt>:conditions</tt>: An SQL fragment like "administrator = 1" or [ "user_name = ?", username ]. See conditions in the intro.
|
303
|
+
# * <tt>:order</tt>: An SQL fragment like "created_at DESC, name".
|
304
|
+
# * <tt>:limit</tt>: An integer determining the limit on the number of rows that should be returned.
|
305
|
+
# * <tt>:offset</tt>: An integer determining the offset from where the rows should be fetched. So at 5, it would skip the first 4 rows.
|
306
|
+
# * <tt>:joins</tt>: An SQL fragment for additional joins like "LEFT JOIN comments ON comments.post_id = id". (Rarely needed).
|
307
|
+
# * <tt>:include</tt>: Names associations that should be loaded alongside using LEFT OUTER JOINs. The symbols named refer
|
308
|
+
# to already defined associations. See eager loading under Associations.
|
309
|
+
#
|
310
|
+
# Examples for find by id:
|
291
311
|
# Person.find(1) # returns the object for ID = 1
|
292
312
|
# Person.find(1, 2, 6) # returns an array for objects with IDs in (1, 2, 6)
|
293
313
|
# Person.find([7, 17]) # returns an array for objects with IDs in (7, 17)
|
294
314
|
# Person.find([1]) # returns an array for objects the object with ID = 1
|
315
|
+
# Person.find(1, :conditions => "administrator = 1", :order => "created_on DESC")
|
295
316
|
#
|
296
|
-
#
|
297
|
-
# Person.find(
|
298
|
-
# Person.find(
|
299
|
-
# Person.find(
|
300
|
-
# Person.find(25, :conditions => ["name = :name AND age = :age", { :name => "Mary", :age => 22 }]
|
317
|
+
# Examples for find first:
|
318
|
+
# Person.find(:first) # returns the first object fetched by SELECT * FROM people
|
319
|
+
# Person.find(:first, :conditions => [ "user_name = ?", user_name])
|
320
|
+
# Person.find(:first, :order => "created_on DESC", :offset => 5)
|
301
321
|
#
|
302
|
-
#
|
322
|
+
# Examples for find all:
|
323
|
+
# Person.find(:all) # returns an array of objects for all the rows fetched by SELECT * FROM people
|
324
|
+
# Person.find(:all, :conditions => [ "category IN (?)", categories], :limit => 50)
|
325
|
+
# Person.find(:all, :offset => 10, :limit => 10)
|
326
|
+
# Person.find(:all, :include => [ :account, :friends ])
|
303
327
|
def find(*args)
|
304
|
-
# Return an Array if ids are passed in an Array.
|
305
|
-
expects_array = args.first.kind_of?(Array)
|
306
|
-
|
307
|
-
# Extract options hash from argument list.
|
308
328
|
options = extract_options_from_args!(args)
|
309
|
-
conditions = " AND #{sanitize_sql(options[:conditions])}" if options[:conditions]
|
310
|
-
|
311
|
-
ids = args.flatten.compact.uniq
|
312
|
-
case ids.size
|
313
|
-
|
314
|
-
# Raise if no ids passed.
|
315
|
-
when 0
|
316
|
-
raise RecordNotFound, "Couldn't find #{name} without an ID#{conditions}"
|
317
|
-
|
318
|
-
# Find a single id.
|
319
|
-
when 1
|
320
|
-
unless result = find_first("#{primary_key} = #{sanitize(ids.first)}#{conditions}")
|
321
|
-
raise RecordNotFound, "Couldn't find #{name} with ID=#{ids.first}#{conditions}"
|
322
|
-
end
|
323
|
-
|
324
|
-
# Box result if expecting array.
|
325
|
-
expects_array ? [result] : result
|
326
329
|
|
327
|
-
|
330
|
+
case args.first
|
331
|
+
when :first
|
332
|
+
find(:all, options.merge({ :limit => 1 })).first
|
333
|
+
when :all
|
334
|
+
options[:include] ? find_with_associations(options) : find_by_sql(construct_finder_sql(options))
|
328
335
|
else
|
329
|
-
|
330
|
-
|
331
|
-
|
332
|
-
|
333
|
-
|
334
|
-
|
336
|
+
expects_array = args.first.kind_of?(Array)
|
337
|
+
conditions = " AND #{sanitize_sql(options[:conditions])}" if options[:conditions]
|
338
|
+
|
339
|
+
ids = args.flatten.compact.uniq
|
340
|
+
case ids.size
|
341
|
+
when 0
|
342
|
+
raise RecordNotFound, "Couldn't find #{name} without an ID#{conditions}"
|
343
|
+
when 1
|
344
|
+
if result = find(:first, options.merge({ :conditions => "#{table_name}.#{primary_key} = #{sanitize(ids.first)}#{conditions}" }))
|
345
|
+
return expects_array ? [ result ] : result
|
346
|
+
else
|
347
|
+
raise RecordNotFound, "Couldn't find #{name} with ID=#{ids.first}#{conditions}"
|
348
|
+
end
|
349
|
+
else
|
350
|
+
# Find multiple ids
|
351
|
+
ids_list = ids.map { |id| sanitize(id) }.join(',')
|
352
|
+
result = find(:all, options.merge({ :conditions => "#{table_name}.#{primary_key} IN (#{ids_list})#{conditions}", :order => primary_key }))
|
353
|
+
if result.size == ids.size
|
354
|
+
return result
|
355
|
+
else
|
356
|
+
raise RecordNotFound, "Couldn't find all #{name.pluralize} with IDs (#{ids_list})#{conditions}"
|
357
|
+
end
|
335
358
|
end
|
336
359
|
end
|
337
360
|
end
|
338
361
|
|
339
|
-
# Returns true if the given +id+ represents the primary key of a record in the database, false otherwise.
|
340
|
-
# Example:
|
341
|
-
# Person.exists?(5)
|
342
|
-
def exists?(id)
|
343
|
-
!find_first("#{primary_key} = #{sanitize(id)}").nil? rescue false
|
344
|
-
end
|
345
|
-
|
346
|
-
# This method is deprecated in favor of find with the :conditions option.
|
347
|
-
# Works like find, but the record matching +id+ must also meet the +conditions+.
|
348
|
-
# +RecordNotFound+ is raised if no record can be found matching the +id+ or meeting the condition.
|
349
|
-
# Example:
|
350
|
-
# Person.find_on_conditions 5, "first_name LIKE '%dav%' AND last_name = 'heinemeier'"
|
351
|
-
def find_on_conditions(ids, conditions)
|
352
|
-
find(ids, :conditions => conditions)
|
353
|
-
end
|
354
|
-
|
355
|
-
# Returns an array of all the objects that could be instantiated from the associated
|
356
|
-
# table in the database. The +conditions+ can be used to narrow the selection of objects (WHERE-part),
|
357
|
-
# such as by "color = 'red'", and arrangement of the selection can be done through +orderings+ (ORDER BY-part),
|
358
|
-
# such as by "last_name, first_name DESC". A maximum of returned objects and their offset can be specified in
|
359
|
-
# +limit+ with either just a single integer as the limit or as an array with the first element as the limit,
|
360
|
-
# the second as the offset. Examples:
|
361
|
-
# Project.find_all "category = 'accounts'", "last_accessed DESC", 15
|
362
|
-
# Project.find_all ["category = ?", category_name], "created ASC", [15, 20]
|
363
|
-
def find_all(conditions = nil, orderings = nil, limit = nil, joins = nil)
|
364
|
-
sql = "SELECT * FROM #{table_name} "
|
365
|
-
sql << "#{joins} " if joins
|
366
|
-
add_conditions!(sql, conditions)
|
367
|
-
sql << "ORDER BY #{orderings} " unless orderings.nil?
|
368
|
-
|
369
|
-
limit = sanitize_sql(limit) if limit.is_a? Array and limit.first.is_a? String
|
370
|
-
connection.add_limit!(sql, limit) if limit
|
371
|
-
|
372
|
-
find_by_sql(sql)
|
373
|
-
end
|
374
|
-
|
375
362
|
# Works like find_all, but requires a complete SQL string. Examples:
|
376
363
|
# Post.find_by_sql "SELECT p.*, c.author FROM posts p, comments c WHERE p.id = c.post_id"
|
377
364
|
# Post.find_by_sql ["SELECT * FROM posts WHERE author = ? AND created > ?", author_id, start_date]
|
@@ -379,15 +366,13 @@ module ActiveRecord #:nodoc:
|
|
379
366
|
connection.select_all(sanitize_sql(sql), "#{name} Load").inject([]) { |objects, record| objects << instantiate(record) }
|
380
367
|
end
|
381
368
|
|
382
|
-
# Returns the
|
383
|
-
#
|
384
|
-
#
|
385
|
-
|
386
|
-
|
387
|
-
def find_first(conditions = nil, orderings = nil)
|
388
|
-
find_all(conditions, orderings, 1).first
|
369
|
+
# Returns true if the given +id+ represents the primary key of a record in the database, false otherwise.
|
370
|
+
# Example:
|
371
|
+
# Person.exists?(5)
|
372
|
+
def exists?(id)
|
373
|
+
!find(:first, :conditions => ["#{primary_key} = ?", id]).nil? rescue false
|
389
374
|
end
|
390
|
-
|
375
|
+
|
391
376
|
# Creates an object, instantly saves it as a record (if the validation permits it), and returns it. If the save
|
392
377
|
# fail under validations, the unsaved object is still returned.
|
393
378
|
def create(attributes = nil)
|
@@ -452,8 +437,10 @@ module ActiveRecord #:nodoc:
|
|
452
437
|
|
453
438
|
# Returns the number of records that meets the +conditions+. Zero is returned if no records match. Example:
|
454
439
|
# Product.count "sales > 1"
|
455
|
-
def count(conditions = nil)
|
456
|
-
|
440
|
+
def count(conditions = nil, joins = nil)
|
441
|
+
tbl_var_name = joins ? table_name[0,1].downcase : ""
|
442
|
+
sql = "SELECT COUNT(*) FROM #{table_name} #{tbl_var_name} "
|
443
|
+
sql << ", #{joins} " if joins
|
457
444
|
add_conditions!(sql, conditions)
|
458
445
|
count_by_sql(sql)
|
459
446
|
end
|
@@ -462,8 +449,14 @@ module ActiveRecord #:nodoc:
|
|
462
449
|
# Product.count "SELECT COUNT(*) FROM sales s, customers c WHERE s.customer_id = c.id"
|
463
450
|
def count_by_sql(sql)
|
464
451
|
sql = sanitize_conditions(sql)
|
465
|
-
|
466
|
-
|
452
|
+
rows = connection.select_one(sql, "#{name} Count")
|
453
|
+
|
454
|
+
if rows.nil?
|
455
|
+
return 0
|
456
|
+
else
|
457
|
+
count = rows.values.first
|
458
|
+
return count ? count.to_i : 0
|
459
|
+
end
|
467
460
|
end
|
468
461
|
|
469
462
|
# Increments the specified counter by one. So <tt>DiscussionBoard.increment_counter("post_count",
|
@@ -625,6 +618,10 @@ module ActiveRecord #:nodoc:
|
|
625
618
|
def columns_hash
|
626
619
|
@columns_hash ||= columns.inject({}) { |hash, column| hash[column.name] = column; hash }
|
627
620
|
end
|
621
|
+
|
622
|
+
def column_names
|
623
|
+
@column_names ||= columns_hash.keys
|
624
|
+
end
|
628
625
|
|
629
626
|
# Returns an array of columns objects where the primary id, all columns ending in "_id" or "_count",
|
630
627
|
# and columns used for single table inheritance has been removed.
|
@@ -647,7 +644,7 @@ module ActiveRecord #:nodoc:
|
|
647
644
|
|
648
645
|
# Resets all the cached information about columns, which will cause they to be reloaded on the next request.
|
649
646
|
def reset_column_information
|
650
|
-
@columns = @columns_hash = @content_columns = @dynamic_methods_hash = nil
|
647
|
+
@column_names = @columns = @columns_hash = @content_columns = @dynamic_methods_hash = nil
|
651
648
|
end
|
652
649
|
|
653
650
|
def reset_column_information_and_inheritable_attributes_for_all_subclasses#:nodoc:
|
@@ -685,16 +682,17 @@ module ActiveRecord #:nodoc:
|
|
685
682
|
def benchmark(title)
|
686
683
|
result = nil
|
687
684
|
bm = Benchmark.measure { result = silence { yield } }
|
688
|
-
logger.info "#{title} (#{sprintf("%f", bm.real)})"
|
685
|
+
logger.info "#{title} (#{sprintf("%f", bm.real)})" if logger
|
689
686
|
return result
|
690
687
|
end
|
691
688
|
|
692
689
|
# Silences the logger for the duration of the block.
|
693
690
|
def silence
|
694
691
|
result = nil
|
695
|
-
logger.level
|
692
|
+
old_logger_level = logger.level if logger
|
693
|
+
logger.level = Logger::ERROR if logger
|
696
694
|
result = yield
|
697
|
-
logger.level =
|
695
|
+
logger.level = old_logger_level if logger
|
698
696
|
return result
|
699
697
|
end
|
700
698
|
|
@@ -737,6 +735,24 @@ module ActiveRecord #:nodoc:
|
|
737
735
|
self.name =~ /::/ ? self.name.scan(/(.*)::/).first.first + "::" + type_name : type_name
|
738
736
|
end
|
739
737
|
|
738
|
+
def construct_finder_sql(options)
|
739
|
+
sql = "SELECT * FROM #{table_name} "
|
740
|
+
sql << "#{options[:joins]} " if options[:joins]
|
741
|
+
add_conditions!(sql, options[:conditions])
|
742
|
+
sql << "ORDER BY #{options[:order]} " if options[:order]
|
743
|
+
add_limit!(sql, options)
|
744
|
+
|
745
|
+
return sql
|
746
|
+
end
|
747
|
+
|
748
|
+
def add_limit!(sql, options)
|
749
|
+
if options[:limit] && options[:offset]
|
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
|
754
|
+
end
|
755
|
+
|
740
756
|
# Adds a sanitized version of +conditions+ to the +sql+ string. Note that it's the passed +sql+ string is changed.
|
741
757
|
def add_conditions!(sql, conditions)
|
742
758
|
sql << "WHERE #{sanitize_sql(conditions)} " unless conditions.nil?
|
@@ -744,9 +760,11 @@ module ActiveRecord #:nodoc:
|
|
744
760
|
end
|
745
761
|
|
746
762
|
def type_condition
|
747
|
-
|
748
|
-
condition << "OR #{inheritance_column} = '#{
|
749
|
-
end
|
763
|
+
type_condition = subclasses.inject("#{table_name}.#{inheritance_column} = '#{name.demodulize}' ") do |condition, subclass|
|
764
|
+
condition << "OR #{table_name}.#{inheritance_column} = '#{subclass.name.demodulize}' "
|
765
|
+
end
|
766
|
+
|
767
|
+
return " (#{type_condition}) "
|
750
768
|
end
|
751
769
|
|
752
770
|
# Guesses the table name, but does not decorate it with prefix and suffix information.
|
@@ -760,18 +778,24 @@ module ActiveRecord #:nodoc:
|
|
760
778
|
# find_first(["user_name = ?", user_name]) and find_first(["user_name = ? AND password = ?", user_name, password]) respectively. Also works
|
761
779
|
# for find_all, but using find_all_by_amount(50) that are turned into find_all(["amount = ?", 50]).
|
762
780
|
#
|
763
|
-
# It's even possible to use all the additional parameters to
|
764
|
-
# is actually find_all_by_amount(amount,
|
781
|
+
# It's even possible to use all the additional parameters to find. For example, the full interface for find_all_by_amount
|
782
|
+
# is actually find_all_by_amount(amount, options).
|
765
783
|
def method_missing(method_id, *arguments)
|
766
784
|
method_name = method_id.id2name
|
767
785
|
|
768
786
|
if method_name =~ /find_(all_by|by)_([_a-z][_a-z\d]*)/
|
769
|
-
finder, attributes = ($1 == "all_by" ? :
|
787
|
+
finder, attributes = ($1 == "all_by" ? :all : :first), $2.split("_and_")
|
770
788
|
attributes.each { |attr_name| super unless column_methods_hash[attr_name.intern] }
|
771
789
|
|
772
790
|
attr_index = -1
|
773
791
|
conditions = attributes.collect { |attr_name| attr_index += 1; "#{attr_name} #{arguments[attr_index].nil? ? "IS" : "="} ? " }.join(" AND ")
|
774
|
-
|
792
|
+
|
793
|
+
if arguments[attributes.length].is_a?(Hash)
|
794
|
+
find(finder, { :conditions => [conditions, *arguments[0...attributes.length]]}.merge(arguments[attributes.length]))
|
795
|
+
else
|
796
|
+
# deprecated API
|
797
|
+
send("find_#{finder}", [conditions, *arguments[0...attributes.length]], *arguments[attributes.length..-1])
|
798
|
+
end
|
775
799
|
else
|
776
800
|
super
|
777
801
|
end
|
@@ -1085,7 +1109,7 @@ module ActiveRecord #:nodoc:
|
|
1085
1109
|
# Delegates to id in order to allow two records of the same type and id to work with something like:
|
1086
1110
|
# [ Person.find(1), Person.find(2), Person.find(3) ] & [ Person.find(1), Person.find(4) ] # => [ Person.find(1) ]
|
1087
1111
|
def hash
|
1088
|
-
id
|
1112
|
+
id.hash
|
1089
1113
|
end
|
1090
1114
|
|
1091
1115
|
# For checking respond_to? without searching the attributes (which is faster).
|