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.

Files changed (84) hide show
  1. data/CHANGELOG +187 -19
  2. data/RUNNING_UNIT_TESTS +11 -0
  3. data/lib/active_record.rb +3 -1
  4. data/lib/active_record/acts/list.rb +25 -14
  5. data/lib/active_record/acts/nested_set.rb +4 -4
  6. data/lib/active_record/acts/tree.rb +18 -1
  7. data/lib/active_record/associations.rb +90 -17
  8. data/lib/active_record/associations/association_collection.rb +44 -5
  9. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +17 -4
  10. data/lib/active_record/associations/has_many_association.rb +13 -3
  11. data/lib/active_record/associations/has_one_association.rb +19 -0
  12. data/lib/active_record/base.rb +292 -268
  13. data/lib/active_record/callbacks.rb +14 -14
  14. data/lib/active_record/connection_adapters/abstract_adapter.rb +137 -75
  15. data/lib/active_record/connection_adapters/db2_adapter.rb +10 -8
  16. data/lib/active_record/connection_adapters/mysql_adapter.rb +91 -64
  17. data/lib/active_record/connection_adapters/oci_adapter.rb +6 -6
  18. data/lib/active_record/connection_adapters/postgresql_adapter.rb +113 -60
  19. data/lib/active_record/connection_adapters/sqlite_adapter.rb +15 -12
  20. data/lib/active_record/connection_adapters/sqlserver_adapter.rb +159 -132
  21. data/lib/active_record/fixtures.rb +59 -12
  22. data/lib/active_record/locking.rb +10 -9
  23. data/lib/active_record/migration.rb +112 -5
  24. data/lib/active_record/query_cache.rb +64 -0
  25. data/lib/active_record/timestamp.rb +10 -8
  26. data/lib/active_record/validations.rb +121 -26
  27. data/rakefile +16 -10
  28. data/test/aaa_create_tables_test.rb +26 -48
  29. data/test/abstract_unit.rb +3 -0
  30. data/test/aggregations_test.rb +19 -19
  31. data/test/association_callbacks_test.rb +110 -0
  32. data/test/associations_go_eager_test.rb +48 -14
  33. data/test/associations_test.rb +344 -142
  34. data/test/base_test.rb +150 -31
  35. data/test/binary_test.rb +7 -0
  36. data/test/callbacks_test.rb +24 -5
  37. data/test/column_alias_test.rb +2 -2
  38. data/test/connections/native_sqlserver_odbc/connection.rb +26 -0
  39. data/test/deprecated_associations_test.rb +27 -28
  40. data/test/deprecated_finder_test.rb +8 -9
  41. data/test/finder_test.rb +52 -17
  42. data/test/fixtures/author.rb +39 -0
  43. data/test/fixtures/categories.yml +7 -0
  44. data/test/fixtures/categories_posts.yml +8 -0
  45. data/test/fixtures/category.rb +2 -0
  46. data/test/fixtures/comment.rb +3 -1
  47. data/test/fixtures/comments.yml +43 -1
  48. data/test/fixtures/companies.yml +14 -0
  49. data/test/fixtures/company.rb +1 -1
  50. data/test/fixtures/computers.yml +2 -1
  51. data/test/fixtures/db_definitions/db2.sql +7 -2
  52. data/test/fixtures/db_definitions/mysql.drop.sql +2 -0
  53. data/test/fixtures/db_definitions/mysql.sql +11 -6
  54. data/test/fixtures/db_definitions/oci.sql +7 -2
  55. data/test/fixtures/db_definitions/postgresql.drop.sql +3 -1
  56. data/test/fixtures/db_definitions/postgresql.sql +8 -5
  57. data/test/fixtures/db_definitions/sqlite.drop.sql +2 -0
  58. data/test/fixtures/db_definitions/sqlite.sql +9 -4
  59. data/test/fixtures/db_definitions/sqlserver.drop.sql +2 -0
  60. data/test/fixtures/db_definitions/sqlserver.sql +12 -7
  61. data/test/fixtures/developer.rb +8 -1
  62. data/test/fixtures/migrations/3_innocent_jointable.rb +12 -0
  63. data/test/fixtures/post.rb +8 -2
  64. data/test/fixtures/posts.yml +21 -0
  65. data/test/fixtures/project.rb +14 -1
  66. data/test/fixtures/subscriber.rb +3 -0
  67. data/test/fixtures_test.rb +14 -0
  68. data/test/inheritance_test.rb +30 -22
  69. data/test/lifecycle_test.rb +3 -4
  70. data/test/locking_test.rb +2 -4
  71. data/test/migration_test.rb +186 -0
  72. data/test/mixin_nested_set_test.rb +19 -19
  73. data/test/mixin_test.rb +88 -88
  74. data/test/modules_test.rb +5 -10
  75. data/test/multiple_db_test.rb +2 -0
  76. data/test/pk_test.rb +8 -12
  77. data/test/reflection_test.rb +8 -4
  78. data/test/schema_test_postgresql.rb +63 -0
  79. data/test/thread_safety_test.rb +4 -1
  80. data/test/transactions_test.rb +9 -2
  81. data/test/unconnected_test.rb +1 -0
  82. data/test/validations_test.rb +151 -8
  83. metadata +11 -5
  84. 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
- options[:conditions] = @finder_sql + (options[:conditions] ? " AND #{options[:conditions]}" : "")
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
- @association_class.find(args.size == 1 ? args.first : args, options)
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?
@@ -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
- # find_first("user_name = '#{user_name}' AND password = '#{password}'")
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
- # find_first([ "user_name = ? AND password = ?", user_name, password ])
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 <tt>Person.find_by_user_name, Payment.find_by_transaction_id</tt>.
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>.
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
- @@primary_key_prefix_type = {}
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}", :order => primary_key }))
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 find_all, but requires a complete SQL string. Examples:
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").inject([]) { |objects, record| objects << instantiate(record) }
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
- return connection.update(sql, "#{name} Update")
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
- find_all(conditions).each { |object| object.destroy }
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
- tbl_var_name = joins ? table_name[0,1].downcase : ""
442
- sql = "SELECT COUNT(*) FROM #{table_name} #{tbl_var_name} "
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
- return 0
456
+ if !rows.nil? and count = rows.values.first
457
+ count.to_i
456
458
  else
457
- count = rows.values.first
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
- write_inheritable_attribute("attr_serialized", serialized_attributes.update(attr_name.to_s => class_name))
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 + undecorated_table_name(class_name_of_active_record_descendant(self)) + table_name_suffix
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 = Inflector.camelize(table_name[table_name_prefix.length..-(table_name_suffix.length + 1)])
608
- class_name = Inflector.singularize(class_name) if pluralize_table_names
609
- return class_name
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 ||= columns_hash.keys
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 ||= columns_hash.keys.inject(Hash.new(false)) do |methods, attr|
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.has_key?(inheritance_column)
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
- # Used to aggregate logging and benchmark, so you can measure and represent multiple statements in a single block.
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.find_all
681
+ # project.milestones << Milestone.find(:all)
681
682
  # end
682
683
  def benchmark(title)
683
684
  result = nil
684
- bm = Benchmark.measure { result = silence { yield } }
685
- logger.info "#{title} (#{sprintf("%f", bm.real)})" if logger
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
- result = nil
692
- old_logger_level = logger.level if logger
693
- logger.level = Logger::ERROR if logger
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
- require_association_class(record[inheritance_column])
707
+ subclass_name = record[inheritance_column]
708
+ require_association_class(subclass_name)
709
709
 
710
- begin
711
- object = record_with_type?(record) ? compute_type(record[inheritance_column]).allocate : allocate
712
- rescue NameError
713
- raise(
714
- SubclassNotFound,
715
- "The single-table inheritance mechanism failed to locate the subclass: '#{record[inheritance_column]}'. " +
716
- "This error is raised because the column '#{inheritance_column}' is reserved for storing the class in case of inheritance. " +
717
- "Please rename this column if you didn't intend it to be used for storing the inheritance class " +
718
- "or overwrite #{self.to_s}.inheritance_column to use another column for that information."
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
- return object
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
- 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
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
- return " (#{type_condition}) "
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
- return table_name
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
- # find_first(["user_name = ?", user_name]) and find_first(["user_name = ? AND password = ?", user_name, password]) respectively. Also works
779
- # for find_all, but using find_all_by_amount(50) that are turned into find_all(["amount = ?", 50]).
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 method_name =~ /find_(all_by|by)_([_a-z][_a-z\d]*)/
787
- finder, attributes = ($1 == "all_by" ? :all : :first), $2.split("_and_")
788
- attributes.each { |attr_name| super unless column_methods_hash[attr_name.intern] }
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].nil? ? "IS" : "="} ? " }.join(" AND ")
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]]}.merge(arguments[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
- return klass.name
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.has_key?(match)
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
- case value
892
- when Array
893
- value.map { |v| connection.quote(v) }.join(',')
894
- else
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.columns_hash[self.class.primary_key])
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
- "DELETE FROM #{self.class.table_name} " +
968
- "WHERE #{self.class.primary_key} = #{quote(id)}",
969
- "#{self.class.name} Destroy"
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.attributes
976
+ attrs = self.attributes_before_type_cast
979
977
  attrs.delete(self.class.primary_key)
980
- cloned_record = self.class.new(attrs)
981
- cloned_record
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
- return save
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
- return self
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 []= (attr_name, value)
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
- self.attribute_names.inject({}) do |attributes, name|
1073
- begin
1074
- attributes[name] = read_attribute(name).clone
1075
- rescue TypeError, NoMethodError
1076
- attributes[name] = read_attribute(name)
1077
- end
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
- is_empty = read_attribute(attribute).respond_to?("empty?") ? read_attribute(attribute).empty? : false
1086
- @attributes.include?(attribute) && !@attributes[attribute].nil? && !is_empty
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
- return true
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, *arguments)
1172
- method_name = method_id.id2name
1173
-
1174
- if method_name =~ read_method? && @attributes.include?($1)
1175
- return read_attribute($1)
1176
- elsif method_name =~ read_untyped_method? && @attributes.include?($1)
1177
- return read_attribute_before_type_cast($1)
1178
- elsif method_name =~ write_method? && @attributes.include?($1)
1179
- write_attribute($1, arguments[0])
1180
- elsif method_name =~ query_method? && @attributes.include?($1)
1181
- return query_attribute($1)
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.keys.include? attr_name
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) : column.type_cast(@attributes[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
- @attributes[attr_name]
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
- @attributes[attr_name] && [:text, :string].include?(column.send(:type)) && @attributes[attr_name].is_a?(String) && self.class.serialized_attributes[attr_name]
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
- SerializationTypeMismatch,
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
- columns_hash = self.class.columns_hash
1282
-
1283
- attrs_quoted = attributes.inject({}) do |attrs_quoted, pair|
1284
- attrs_quoted[pair.first] = quote(pair.last, columns_hash[pair.first]) unless !include_primary_key && pair.first == self.class.primary_key
1285
- attrs_quoted
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 { |column_name| connection.quote_column_name(column_name) }
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(column_quoter, hash)
1380
- hash.inject({}) do |list, pair|
1381
- list[column_quoter.quote_column_name(pair.first)] = pair.last
1382
- list
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(column_quoter, hash)
1387
- comma_pair_list(quote_columns(column_quoter, hash))
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