activerecord 1.15.6 → 2.0.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 (185) hide show
  1. data/CHANGELOG +2454 -34
  2. data/README +1 -1
  3. data/RUNNING_UNIT_TESTS +3 -34
  4. data/Rakefile +98 -77
  5. data/install.rb +1 -1
  6. data/lib/active_record.rb +13 -22
  7. data/lib/active_record/aggregations.rb +38 -49
  8. data/lib/active_record/associations.rb +452 -333
  9. data/lib/active_record/associations/association_collection.rb +66 -20
  10. data/lib/active_record/associations/association_proxy.rb +9 -8
  11. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +46 -51
  12. data/lib/active_record/associations/has_many_association.rb +21 -57
  13. data/lib/active_record/associations/has_many_through_association.rb +38 -18
  14. data/lib/active_record/associations/has_one_association.rb +30 -14
  15. data/lib/active_record/attribute_methods.rb +253 -0
  16. data/lib/active_record/base.rb +719 -494
  17. data/lib/active_record/calculations.rb +62 -63
  18. data/lib/active_record/callbacks.rb +57 -83
  19. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +38 -9
  20. data/lib/active_record/connection_adapters/abstract/database_statements.rb +56 -15
  21. data/lib/active_record/connection_adapters/abstract/query_cache.rb +87 -0
  22. data/lib/active_record/connection_adapters/abstract/quoting.rb +23 -12
  23. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +191 -62
  24. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +37 -34
  25. data/lib/active_record/connection_adapters/abstract_adapter.rb +28 -17
  26. data/lib/active_record/connection_adapters/mysql_adapter.rb +119 -37
  27. data/lib/active_record/connection_adapters/postgresql_adapter.rb +473 -210
  28. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +34 -0
  29. data/lib/active_record/connection_adapters/sqlite_adapter.rb +91 -107
  30. data/lib/active_record/fixtures.rb +503 -113
  31. data/lib/active_record/locking/optimistic.rb +72 -34
  32. data/lib/active_record/migration.rb +80 -57
  33. data/lib/active_record/observer.rb +13 -10
  34. data/lib/active_record/query_cache.rb +16 -57
  35. data/lib/active_record/reflection.rb +35 -38
  36. data/lib/active_record/schema.rb +5 -5
  37. data/lib/active_record/schema_dumper.rb +35 -13
  38. data/lib/active_record/serialization.rb +98 -0
  39. data/lib/active_record/serializers/json_serializer.rb +71 -0
  40. data/lib/active_record/{xml_serialization.rb → serializers/xml_serializer.rb} +90 -83
  41. data/lib/active_record/timestamp.rb +20 -21
  42. data/lib/active_record/transactions.rb +39 -43
  43. data/lib/active_record/validations.rb +256 -107
  44. data/lib/active_record/version.rb +3 -3
  45. data/lib/activerecord.rb +1 -0
  46. data/test/aaa_create_tables_test.rb +15 -2
  47. data/test/abstract_unit.rb +24 -17
  48. data/test/active_schema_test_mysql.rb +20 -8
  49. data/test/adapter_test.rb +23 -5
  50. data/test/adapter_test_sqlserver.rb +15 -1
  51. data/test/aggregations_test.rb +16 -1
  52. data/test/all.sh +2 -2
  53. data/test/associations/ar_joins_test.rb +0 -0
  54. data/test/associations/callbacks_test.rb +51 -30
  55. data/test/associations/cascaded_eager_loading_test.rb +1 -29
  56. data/test/associations/eager_singularization_test.rb +145 -0
  57. data/test/associations/eager_test.rb +42 -6
  58. data/test/associations/extension_test.rb +6 -1
  59. data/test/associations/inner_join_association_test.rb +88 -0
  60. data/test/associations/join_model_test.rb +47 -16
  61. data/test/associations_test.rb +449 -226
  62. data/test/attribute_methods_test.rb +97 -0
  63. data/test/base_test.rb +251 -105
  64. data/test/binary_test.rb +22 -27
  65. data/test/calculations_test.rb +37 -5
  66. data/test/callbacks_test.rb +23 -0
  67. data/test/connection_test_firebird.rb +2 -2
  68. data/test/connection_test_mysql.rb +30 -0
  69. data/test/connections/native_mysql/connection.rb +3 -0
  70. data/test/connections/native_sqlite/connection.rb +5 -14
  71. data/test/connections/native_sqlite3/connection.rb +5 -14
  72. data/test/connections/native_sqlite3/in_memory_connection.rb +1 -1
  73. data/test/{copy_table_sqlite.rb → copy_table_test_sqlite.rb} +8 -3
  74. data/test/datatype_test_postgresql.rb +178 -27
  75. data/test/{empty_date_time_test.rb → date_time_test.rb} +13 -1
  76. data/test/defaults_test.rb +8 -1
  77. data/test/deprecated_finder_test.rb +7 -128
  78. data/test/finder_test.rb +192 -54
  79. data/test/fixtures/all/developers.yml +0 -0
  80. data/test/fixtures/all/people.csv +0 -0
  81. data/test/fixtures/all/tasks.yml +0 -0
  82. data/test/fixtures/author.rb +12 -5
  83. data/test/fixtures/binaries.yml +130 -435
  84. data/test/fixtures/category.rb +6 -0
  85. data/test/fixtures/company.rb +8 -1
  86. data/test/fixtures/computer.rb +1 -0
  87. data/test/fixtures/contact.rb +16 -0
  88. data/test/fixtures/customer.rb +2 -2
  89. data/test/fixtures/db_definitions/db2.drop.sql +1 -0
  90. data/test/fixtures/db_definitions/db2.sql +4 -0
  91. data/test/fixtures/db_definitions/firebird.drop.sql +3 -1
  92. data/test/fixtures/db_definitions/firebird.sql +6 -0
  93. data/test/fixtures/db_definitions/frontbase.drop.sql +1 -0
  94. data/test/fixtures/db_definitions/frontbase.sql +5 -0
  95. data/test/fixtures/db_definitions/openbase.sql +41 -25
  96. data/test/fixtures/db_definitions/oracle.drop.sql +2 -0
  97. data/test/fixtures/db_definitions/oracle.sql +5 -0
  98. data/test/fixtures/db_definitions/postgresql.drop.sql +7 -0
  99. data/test/fixtures/db_definitions/postgresql.sql +87 -58
  100. data/test/fixtures/db_definitions/postgresql2.sql +1 -2
  101. data/test/fixtures/db_definitions/schema.rb +280 -0
  102. data/test/fixtures/db_definitions/schema2.rb +11 -0
  103. data/test/fixtures/db_definitions/sqlite.drop.sql +1 -0
  104. data/test/fixtures/db_definitions/sqlite.sql +4 -0
  105. data/test/fixtures/db_definitions/sybase.drop.sql +1 -0
  106. data/test/fixtures/db_definitions/sybase.sql +4 -0
  107. data/test/fixtures/developer.rb +10 -0
  108. data/test/fixtures/example.log +1 -0
  109. data/test/fixtures/flowers.jpg +0 -0
  110. data/test/fixtures/item.rb +7 -0
  111. data/test/fixtures/items.yml +4 -0
  112. data/test/fixtures/joke.rb +0 -3
  113. data/test/fixtures/matey.rb +4 -0
  114. data/test/fixtures/mateys.yml +4 -0
  115. data/test/fixtures/minimalistic.rb +2 -0
  116. data/test/fixtures/minimalistics.yml +2 -0
  117. data/test/fixtures/mixins.yml +2 -100
  118. data/test/fixtures/parrot.rb +13 -0
  119. data/test/fixtures/parrots.yml +27 -0
  120. data/test/fixtures/parrots_pirates.yml +7 -0
  121. data/test/fixtures/pirate.rb +5 -0
  122. data/test/fixtures/pirates.yml +9 -0
  123. data/test/fixtures/post.rb +1 -0
  124. data/test/fixtures/project.rb +3 -2
  125. data/test/fixtures/reserved_words/distinct.yml +5 -0
  126. data/test/fixtures/reserved_words/distincts_selects.yml +11 -0
  127. data/test/fixtures/reserved_words/group.yml +14 -0
  128. data/test/fixtures/reserved_words/select.yml +8 -0
  129. data/test/fixtures/reserved_words/values.yml +7 -0
  130. data/test/fixtures/ship.rb +3 -0
  131. data/test/fixtures/ships.yml +5 -0
  132. data/test/fixtures/tagging.rb +4 -0
  133. data/test/fixtures/taggings.yml +8 -1
  134. data/test/fixtures/topic.rb +13 -1
  135. data/test/fixtures/treasure.rb +4 -0
  136. data/test/fixtures/treasures.yml +10 -0
  137. data/test/fixtures_test.rb +205 -24
  138. data/test/inheritance_test.rb +7 -1
  139. data/test/json_serialization_test.rb +180 -0
  140. data/test/lifecycle_test.rb +1 -1
  141. data/test/locking_test.rb +85 -2
  142. data/test/migration_test.rb +206 -40
  143. data/test/mixin_test.rb +13 -515
  144. data/test/pk_test.rb +3 -6
  145. data/test/query_cache_test.rb +104 -0
  146. data/test/reflection_test.rb +16 -0
  147. data/test/reserved_word_test_mysql.rb +177 -0
  148. data/test/schema_dumper_test.rb +38 -3
  149. data/test/serialization_test.rb +47 -0
  150. data/test/transactions_test.rb +74 -23
  151. data/test/unconnected_test.rb +1 -1
  152. data/test/validations_test.rb +322 -32
  153. data/test/xml_serialization_test.rb +121 -44
  154. metadata +48 -41
  155. data/examples/associations.rb +0 -87
  156. data/examples/shared_setup.rb +0 -15
  157. data/examples/validation.rb +0 -85
  158. data/lib/active_record/acts/list.rb +0 -256
  159. data/lib/active_record/acts/nested_set.rb +0 -211
  160. data/lib/active_record/acts/tree.rb +0 -96
  161. data/lib/active_record/connection_adapters/db2_adapter.rb +0 -228
  162. data/lib/active_record/connection_adapters/firebird_adapter.rb +0 -728
  163. data/lib/active_record/connection_adapters/frontbase_adapter.rb +0 -861
  164. data/lib/active_record/connection_adapters/openbase_adapter.rb +0 -350
  165. data/lib/active_record/connection_adapters/oracle_adapter.rb +0 -690
  166. data/lib/active_record/connection_adapters/sqlserver_adapter.rb +0 -591
  167. data/lib/active_record/connection_adapters/sybase_adapter.rb +0 -662
  168. data/lib/active_record/deprecated_associations.rb +0 -104
  169. data/lib/active_record/deprecated_finders.rb +0 -44
  170. data/lib/active_record/vendor/simple.rb +0 -693
  171. data/lib/active_record/wrappers/yaml_wrapper.rb +0 -15
  172. data/lib/active_record/wrappings.rb +0 -58
  173. data/test/connections/native_sqlserver/connection.rb +0 -23
  174. data/test/connections/native_sqlserver_odbc/connection.rb +0 -25
  175. data/test/deprecated_associations_test.rb +0 -396
  176. data/test/fixtures/db_definitions/mysql.drop.sql +0 -32
  177. data/test/fixtures/db_definitions/mysql.sql +0 -234
  178. data/test/fixtures/db_definitions/mysql2.drop.sql +0 -2
  179. data/test/fixtures/db_definitions/mysql2.sql +0 -5
  180. data/test/fixtures/db_definitions/sqlserver.drop.sql +0 -34
  181. data/test/fixtures/db_definitions/sqlserver.sql +0 -243
  182. data/test/fixtures/db_definitions/sqlserver2.drop.sql +0 -2
  183. data/test/fixtures/db_definitions/sqlserver2.sql +0 -5
  184. data/test/fixtures/mixin.rb +0 -63
  185. data/test/mixin_nested_set_test.rb +0 -196
@@ -1,7 +1,6 @@
1
1
  require 'base64'
2
2
  require 'yaml'
3
3
  require 'set'
4
- require 'active_record/deprecated_finders'
5
4
 
6
5
  module ActiveRecord #:nodoc:
7
6
  class ActiveRecordError < StandardError #:nodoc:
@@ -30,11 +29,21 @@ module ActiveRecord #:nodoc:
30
29
  end
31
30
  class StaleObjectError < ActiveRecordError #:nodoc:
32
31
  end
33
- class ConfigurationError < StandardError #:nodoc:
32
+ class ConfigurationError < ActiveRecordError #:nodoc:
34
33
  end
35
- class ReadOnlyRecord < StandardError #:nodoc:
34
+ class ReadOnlyRecord < ActiveRecordError #:nodoc:
36
35
  end
37
-
36
+ class Rollback < ActiveRecordError #:nodoc:
37
+ end
38
+ class DangerousAttributeError < ActiveRecordError #:nodoc:
39
+ end
40
+
41
+ # Raised when you've tried to access a column which wasn't
42
+ # loaded by your finder. Typically this is because :select
43
+ # has been specified
44
+ class MissingAttributeError < NoMethodError
45
+ end
46
+
38
47
  class AttributeAssignmentError < ActiveRecordError #:nodoc:
39
48
  attr_reader :exception, :attribute
40
49
  def initialize(message, exception, attribute)
@@ -61,7 +70,7 @@ module ActiveRecord #:nodoc:
61
70
  # == Creation
62
71
  #
63
72
  # Active Records accept constructor parameters either in a hash or as a block. The hash method is especially useful when
64
- # you're receiving the data from somewhere else, like a HTTP request. It works like this:
73
+ # you're receiving the data from somewhere else, like an HTTP request. It works like this:
65
74
  #
66
75
  # user = User.new(:name => "David", :occupation => "Code Artist")
67
76
  # user.name # => "David"
@@ -101,7 +110,7 @@ module ActiveRecord #:nodoc:
101
110
  # end
102
111
  #
103
112
  # The <tt>authenticate_unsafely</tt> method inserts the parameters directly into the query and is thus susceptible to SQL-injection
104
- # attacks if the <tt>user_name</tt> and +password+ parameters come directly from a HTTP request. The <tt>authenticate_safely</tt> and
113
+ # attacks if the <tt>user_name</tt> and +password+ parameters come directly from an HTTP request. The <tt>authenticate_safely</tt> and
105
114
  # <tt>authenticate_safely_simply</tt> both will sanitize the <tt>user_name</tt> and +password+ before inserting them in the query,
106
115
  # which will ensure that an attacker can't escape the query and fake the login (or worse).
107
116
  #
@@ -109,7 +118,7 @@ module ActiveRecord #:nodoc:
109
118
  # question mark is supposed to represent. In those cases, you can resort to named bind variables instead. That's done by replacing
110
119
  # the question marks with symbols and supplying a hash with values for the matching symbol keys:
111
120
  #
112
- # Company.find(:first, [
121
+ # Company.find(:first, :conditions => [
113
122
  # "id = :id AND name = :name AND division = :division AND created_at > :accounting_date",
114
123
  # { :id => 3, :name => "37signals", :division => "First", :accounting_date => '2005-01-01' }
115
124
  # ])
@@ -126,9 +135,9 @@ module ActiveRecord #:nodoc:
126
135
  #
127
136
  # == Overwriting default accessors
128
137
  #
129
- # All column values are automatically available through basic accessors on the Active Record object, but some times you
130
- # want to specialize this behavior. This can be done by either by overwriting the default accessors (using the same
131
- # name as the attribute) calling read_attribute(attr_name) and write_attribute(attr_name, value) to actually change things.
138
+ # All column values are automatically available through basic accessors on the Active Record object, but sometimes you
139
+ # want to specialize this behavior. This can be done by overwriting the default accessors (using the same
140
+ # name as the attribute) and calling read_attribute(attr_name) and write_attribute(attr_name, value) to actually change things.
132
141
  # Example:
133
142
  #
134
143
  # class Song < ActiveRecord::Base
@@ -143,9 +152,23 @@ module ActiveRecord #:nodoc:
143
152
  # end
144
153
  # end
145
154
  #
146
- # You can alternatively use self[:attribute]=(value) and self[:attribute] instead of write_attribute(:attribute, vaule) and
155
+ # You can alternatively use self[:attribute]=(value) and self[:attribute] instead of write_attribute(:attribute, value) and
147
156
  # read_attribute(:attribute) as a shorter form.
148
157
  #
158
+ # == Attribute query methods
159
+ #
160
+ # In addition to the basic accessors, query methods are also automatically available on the Active Record object.
161
+ # Query methods allow you to test whether an attribute value is present.
162
+ #
163
+ # For example, an Active Record User with the <tt>name</tt> attribute has a <tt>name?</tt> method that you can call
164
+ # to determine whether the user has a name:
165
+ #
166
+ # user = User.new(:name => "David")
167
+ # user.name? # => true
168
+ #
169
+ # anonymous = User.new(:name => "")
170
+ # anonymous.name? # => false
171
+ #
149
172
  # == Accessing attributes before they have been typecasted
150
173
  #
151
174
  # Sometimes you want to be able to read the raw attribute data without having the column-determined typecast run its course first.
@@ -161,12 +184,12 @@ module ActiveRecord #:nodoc:
161
184
  # Dynamic attribute-based finders are a cleaner way of getting (and/or creating) objects by simple queries without turning to SQL. They work by
162
185
  # 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,
163
186
  # Person.find_all_by_last_name, Payment.find_by_transaction_id. So instead of writing
164
- # <tt>Person.find(:first, ["user_name = ?", user_name])</tt>, you just do <tt>Person.find_by_user_name(user_name)</tt>.
165
- # 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>.
187
+ # <tt>Person.find(:first, :conditions => ["user_name = ?", user_name])</tt>, you just do <tt>Person.find_by_user_name(user_name)</tt>.
188
+ # And instead of writing <tt>Person.find(:all, :conditions => ["last_name = ?", last_name])</tt>, you just do <tt>Person.find_all_by_last_name(last_name)</tt>.
166
189
  #
167
190
  # It's also possible to use multiple attributes in the same find by separating them with "_and_", so you get finders like
168
191
  # <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
169
- # <tt>Person.find(:first, ["user_name = ? AND password = ?", user_name, password])</tt>, you just do
192
+ # <tt>Person.find(:first, :conditions => ["user_name = ? AND password = ?", user_name, password])</tt>, you just do
170
193
  # <tt>Person.find_by_user_name_and_password(user_name, password)</tt>.
171
194
  #
172
195
  # It's even possible to use all the additional parameters to find. For example, the full interface for Payment.find_all_by_amount
@@ -188,6 +211,13 @@ module ActiveRecord #:nodoc:
188
211
  # winter = Tag.find_or_initialize_by_name("Winter")
189
212
  # winter.new_record? # true
190
213
  #
214
+ # To find by a subset of the attributes to be used for instantiating a new object, pass a hash instead of
215
+ # a list of parameters. For example:
216
+ #
217
+ # Tag.find_or_create_by_name(:name => "rails", :creator => current_user)
218
+ #
219
+ # That will either find an existing tag named "rails", or create a new one while setting the user that created it.
220
+ #
191
221
  # == Saving arrays, hashes, and other non-mappable objects in text columns
192
222
  #
193
223
  # 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+.
@@ -212,7 +242,7 @@ module ActiveRecord #:nodoc:
212
242
  #
213
243
  # == Single table inheritance
214
244
  #
215
- # Active Record allows inheritance by storing the name of the class in a column that by default is called "type" (can be changed
245
+ # Active Record allows inheritance by storing the name of the class in a column that by default is named "type" (can be changed
216
246
  # by overwriting <tt>Base.inheritance_column</tt>). This means that an inheritance looking like this:
217
247
  #
218
248
  # class Company < ActiveRecord::Base; end
@@ -233,7 +263,7 @@ module ActiveRecord #:nodoc:
233
263
  #
234
264
  # Connections are usually created through ActiveRecord::Base.establish_connection and retrieved by ActiveRecord::Base.connection.
235
265
  # All classes inheriting from ActiveRecord::Base will use this connection. But you can also set a class-specific connection.
236
- # For example, if Course is a ActiveRecord::Base, but resides in a different database you can just say Course.establish_connection
266
+ # For example, if Course is an ActiveRecord::Base, but resides in a different database, you can just say Course.establish_connection
237
267
  # and Course *and all its subclasses* will use this connection instead.
238
268
  #
239
269
  # This feature is implemented by keeping a connection pool in ActiveRecord::Base that is a Hash indexed by the class. If a connection is
@@ -242,12 +272,12 @@ module ActiveRecord #:nodoc:
242
272
  # == Exceptions
243
273
  #
244
274
  # * +ActiveRecordError+ -- generic error class and superclass of all other errors raised by Active Record
245
- # * +AdapterNotSpecified+ -- the configuration hash used in <tt>establish_connection</tt> didn't include a
275
+ # * +AdapterNotSpecified+ -- the configuration hash used in <tt>establish_connection</tt> didn't include an
246
276
  # <tt>:adapter</tt> key.
247
- # * +AdapterNotFound+ -- the <tt>:adapter</tt> key used in <tt>establish_connection</tt> specified an non-existent adapter
277
+ # * +AdapterNotFound+ -- the <tt>:adapter</tt> key used in <tt>establish_connection</tt> specified a non-existent adapter
248
278
  # (or a bad spelling of an existing one).
249
279
  # * +AssociationTypeMismatch+ -- the object assigned to the association wasn't of the type specified in the association definition.
250
- # * +SerializationTypeMismatch+ -- the object serialized wasn't of the class specified as the second parameter.
280
+ # * +SerializationTypeMismatch+ -- the serialized object wasn't of the class specified as the second parameter.
251
281
  # * +ConnectionNotEstablished+ -- no connection has been established. Use <tt>establish_connection</tt> before querying.
252
282
  # * +RecordNotFound+ -- no record responded to the find* method.
253
283
  # Either the row with the given ID doesn't exist or the row didn't meet the additional restrictions.
@@ -266,15 +296,13 @@ module ActiveRecord #:nodoc:
266
296
  # Accepts a logger conforming to the interface of Log4r or the default Ruby 1.8+ Logger class, which is then passed
267
297
  # on to any new database connections made and which can be retrieved on both a class and instance level by calling +logger+.
268
298
  cattr_accessor :logger, :instance_writer => false
269
-
270
- include Reloadable::Deprecated
271
-
299
+
272
300
  def self.inherited(child) #:nodoc:
273
301
  @@subclasses[self] ||= []
274
302
  @@subclasses[self] << child
275
303
  super
276
304
  end
277
-
305
+
278
306
  def self.reset_subclasses #:nodoc:
279
307
  nonreloadables = []
280
308
  subclasses.each do |klass|
@@ -312,13 +340,13 @@ module ActiveRecord #:nodoc:
312
340
  cattr_accessor :table_name_suffix, :instance_writer => false
313
341
  @@table_name_suffix = ""
314
342
 
315
- # Indicates whether or not table names should be the pluralized versions of the corresponding class names.
343
+ # Indicates whether table names should be the pluralized versions of the corresponding class names.
316
344
  # If true, the default table name for a +Product+ class will be +products+. If false, it would just be +product+.
317
345
  # See table_name for the full rules on table/class naming. This is true, by default.
318
346
  cattr_accessor :pluralize_table_names, :instance_writer => false
319
347
  @@pluralize_table_names = true
320
348
 
321
- # Determines whether or not to use ANSI codes to colorize the logging statements committed by the connection adapter. These colors
349
+ # Determines whether to use ANSI codes to colorize the logging statements committed by the connection adapter. These colors
322
350
  # make it much easier to overview things during debugging (when used through a reader like +tail+ and on a black background), but
323
351
  # may complicate matters if you use software like syslog. This is true, by default.
324
352
  cattr_accessor :colorize_logging, :instance_writer => false
@@ -329,18 +357,11 @@ module ActiveRecord #:nodoc:
329
357
  cattr_accessor :default_timezone, :instance_writer => false
330
358
  @@default_timezone = :local
331
359
 
332
- # Determines whether or not to use a connection for each thread, or a single shared connection for all threads.
360
+ # Determines whether to use a connection for each thread, or a single shared connection for all threads.
333
361
  # Defaults to false. Set to true if you're writing a threaded application.
334
362
  cattr_accessor :allow_concurrency, :instance_writer => false
335
363
  @@allow_concurrency = false
336
364
 
337
- # Determines whether to speed up access by generating optimized reader
338
- # methods to avoid expensive calls to method_missing when accessing
339
- # attributes by name. You might want to set this to false in development
340
- # mode, because the methods would be regenerated on each request.
341
- cattr_accessor :generate_read_methods, :instance_writer => false
342
- @@generate_read_methods = true
343
-
344
365
  # Specifies the format to use when dumping the database schema with Rails'
345
366
  # Rakefile. If :sql, the schema is dumped as (potentially database-
346
367
  # specific) SQL statements. If :ruby, the schema is dumped as an
@@ -356,22 +377,24 @@ module ActiveRecord #:nodoc:
356
377
  # * 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]).
357
378
  # If no record can be found for all of the listed ids, then RecordNotFound will be raised.
358
379
  # * Find first: This will return the first record matched by the options used. These options can either be specific
359
- # conditions or merely an order. If no record can matched, nil is returned.
380
+ # conditions or merely an order. If no record can be matched, nil is returned.
360
381
  # * Find all: This will return all the records matched by the options used. If no records are found, an empty array is returned.
361
382
  #
362
- # All approaches accept an option hash as their last parameter. The options are:
383
+ # All approaches accept an options hash as their last parameter. The options are:
363
384
  #
364
385
  # * <tt>:conditions</tt>: An SQL fragment like "administrator = 1" or [ "user_name = ?", username ]. See conditions in the intro.
365
386
  # * <tt>:order</tt>: An SQL fragment like "created_at DESC, name".
366
387
  # * <tt>:group</tt>: An attribute name by which the result should be grouped. Uses the GROUP BY SQL-clause.
367
388
  # * <tt>:limit</tt>: An integer determining the limit on the number of rows that should be returned.
368
- # * <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.
369
- # * <tt>:joins</tt>: An SQL fragment for additional joins like "LEFT JOIN comments ON comments.post_id = id". (Rarely needed).
389
+ # * <tt>:offset</tt>: An integer determining the offset from where the rows should be fetched. So at 5, it would skip rows 0 through 4.
390
+ # * <tt>:joins</tt>: An SQL fragment for additional joins like "LEFT JOIN comments ON comments.post_id = id" (Rarely needed).
391
+ # Accepts named associations in the form of :include, which will perform an INNER JOIN on the associated table(s).
370
392
  # The records will be returned read-only since they will have attributes that do not correspond to the table's columns.
371
393
  # Pass :readonly => false to override.
394
+ # See adding joins for associations under Associations.
372
395
  # * <tt>:include</tt>: Names associations that should be loaded alongside using LEFT OUTER JOINs. The symbols named refer
373
396
  # to already defined associations. See eager loading under Associations.
374
- # * <tt>:select</tt>: By default, this is * as in SELECT * FROM, but can be changed if you for example want to do a join, but not
397
+ # * <tt>:select</tt>: By default, this is * as in SELECT * FROM, but can be changed if you, for example, want to do a join but not
375
398
  # include the joined columns.
376
399
  # * <tt>:from</tt>: By default, this is the table name of the class, but can be changed to an alternate table name (or even the name
377
400
  # of a database view).
@@ -383,9 +406,13 @@ module ActiveRecord #:nodoc:
383
406
  # Person.find(1) # returns the object for ID = 1
384
407
  # Person.find(1, 2, 6) # returns an array for objects with IDs in (1, 2, 6)
385
408
  # Person.find([7, 17]) # returns an array for objects with IDs in (7, 17)
386
- # Person.find([1]) # returns an array for objects the object with ID = 1
409
+ # Person.find([1]) # returns an array for the object with ID = 1
387
410
  # Person.find(1, :conditions => "administrator = 1", :order => "created_on DESC")
388
411
  #
412
+ # Note that returned records may not be in the same order as the ids you
413
+ # provide since database rows are unordered. Give an explicit :order
414
+ # to ensure the results are sorted.
415
+ #
389
416
  # Examples for find first:
390
417
  # Person.find(:first) # returns the first object fetched by SELECT * FROM people
391
418
  # Person.find(:first, :conditions => [ "user_name = ?", user_name])
@@ -409,7 +436,14 @@ module ActiveRecord #:nodoc:
409
436
  # person.save!
410
437
  # end
411
438
  def find(*args)
412
- options = extract_options_from_args!(args)
439
+ options = args.extract_options!
440
+ # Note: we extract any :joins option with a non-string value from the options, and turn it into
441
+ # an internal option :ar_joins. This allows code called from here to find the ar_joins, and
442
+ # it bypasses marking the result as read_only.
443
+ # A normal string join marks the result as read-only because it contains attributes from joined tables
444
+ # which are not in the base table and therefore prevent the result from being saved.
445
+ # In the case of an ar_join, the JoinDependency created to instantiate the results eliminates these
446
+ # bogus attributes. See JoinDependency#instantiate, and JoinBase#instantiate in associations.rb.
413
447
  validate_find_options(options)
414
448
  set_readonly_option!(options)
415
449
 
@@ -420,49 +454,90 @@ module ActiveRecord #:nodoc:
420
454
  end
421
455
  end
422
456
 
423
- # Works like find(:all), but requires a complete SQL string. Examples:
424
- # Post.find_by_sql "SELECT p.*, c.author FROM posts p, comments c WHERE p.id = c.post_id"
425
- # Post.find_by_sql ["SELECT * FROM posts WHERE author = ? AND created > ?", author_id, start_date]
457
+ #
458
+ # Executes a custom sql query against your database and returns all the results. The results will
459
+ # be returned as an array with columns requested encapsulated as attributes of the model you call
460
+ # this method from. If you call +Product.find_by_sql+ then the results will be returned in a Product
461
+ # object with the attributes you specified in the SQL query.
462
+ #
463
+ # If you call a complicated SQL query which spans multiple tables the columns specified by the
464
+ # SELECT will be attributes of the model, whether or not they are columns of the corresponding
465
+ # table.
466
+ #
467
+ # The +sql+ parameter is a full sql query as a string. It will be called as is, there will be
468
+ # no database agnostic conversions performed. This should be a last resort because using, for example,
469
+ # MySQL specific terms will lock you to using that particular database engine or require you to
470
+ # change your call if you switch engines
471
+ #
472
+ # ==== Examples
473
+ # # A simple sql query spanning multiple tables
474
+ # Post.find_by_sql "SELECT p.title, c.author FROM posts p, comments c WHERE p.id = c.post_id"
475
+ # > [#<Post:0x36bff9c @attributes={"title"=>"Ruby Meetup", "first_name"=>"Quentin"}>, ...]
476
+ #
477
+ # # You can use the same string replacement techniques as you can with ActiveRecord#find
478
+ # Post.find_by_sql ["SELECT title FROM posts WHERE author = ? AND created > ?", author_id, start_date]
479
+ # > [#<Post:0x36bff9c @attributes={"first_name"=>"The Cheap Man Buys Twice"}>, ...]
426
480
  def find_by_sql(sql)
427
481
  connection.select_all(sanitize_sql(sql), "#{name} Load").collect! { |record| instantiate(record) }
428
482
  end
429
483
 
430
- # Returns true if the given +id+ represents the primary key of a record in the database, false otherwise.
431
- # You can also pass a set of SQL conditions.
432
- # Example:
484
+ # Checks whether a record exists in the database that matches conditions given. These conditions
485
+ # can either be a single integer representing a primary key id to be found, or a condition to be
486
+ # matched like using ActiveRecord#find.
487
+ #
488
+ # The +id_or_conditions+ parameter can be an Integer or a String if you want to search the primary key
489
+ # column of the table for a matching id, or if you're looking to match against a condition you can use
490
+ # an Array or a Hash.
491
+ #
492
+ # Possible gotcha: You can't pass in a condition as a string e.g. "name = 'Jamie'", this would be
493
+ # sanitized and then queried against the primary key column as "id = 'name = \'Jamie"
494
+ #
495
+ # ==== Examples
433
496
  # Person.exists?(5)
434
497
  # Person.exists?('5')
435
498
  # Person.exists?(:name => "David")
436
499
  # Person.exists?(['name LIKE ?', "%#{query}%"])
437
500
  def exists?(id_or_conditions)
438
- !find(:first, :conditions => expand_id_conditions(id_or_conditions)).nil?
501
+ !find(:first, :select => "#{table_name}.#{primary_key}", :conditions => expand_id_conditions(id_or_conditions)).nil?
439
502
  rescue ActiveRecord::ActiveRecordError
440
503
  false
441
504
  end
442
505
 
443
- # Creates an object, instantly saves it as a record (if the validation permits it), and returns it. If the save
444
- # fails under validations, the unsaved object is still returned.
506
+ # Creates an object (or multiple objects) and saves it to the database, if validations pass.
507
+ # The resulting object is returned whether the object was saved successfully to the database or not.
508
+ #
509
+ # The +attributes+ parameter can be either be a Hash or an Array of Hashes. These Hashes describe the
510
+ # attributes on the objects that are to be created.
511
+ #
512
+ # ==== Examples
513
+ # # Create a single new object
514
+ # User.create(:first_name => 'Jamie')
515
+ # # Create an Array of new objects
516
+ # User.create([{:first_name => 'Jamie'}, {:first_name => 'Jeremy'}])
445
517
  def create(attributes = nil)
446
518
  if attributes.is_a?(Array)
447
519
  attributes.collect { |attr| create(attr) }
448
520
  else
449
521
  object = new(attributes)
450
- scope(:create).each { |att,value| object.send("#{att}=", value) } if scoped?(:create)
451
522
  object.save
452
523
  object
453
524
  end
454
525
  end
455
526
 
456
- # Finds the record from the passed +id+, instantly saves it with the passed +attributes+ (if the validation permits it),
457
- # and returns it. If the save fails under validations, the unsaved object is still returned.
527
+ # Updates an object (or multiple objects) and saves it to the database, if validations pass.
528
+ # The resulting object is returned whether the object was saved successfully to the database or not.
529
+ #
530
+ # ==== Options
531
+ #
532
+ # +id+ This should be the id or an array of ids to be updated
533
+ # +attributes+ This should be a Hash of attributes to be set on the object, or an array of Hashes.
458
534
  #
459
- # The arguments may also be given as arrays in which case the update method is called for each pair of +id+ and
460
- # +attributes+ and an array of objects is returned.
535
+ # ==== Examples
461
536
  #
462
- # Example of updating one record:
537
+ # # Updating one record:
463
538
  # Person.update(15, {:user_name => 'Samuel', :group => 'expert'})
464
539
  #
465
- # Example of updating multiple records:
540
+ # # Updating multiple records:
466
541
  # people = { 1 => { "first_name" => "David" }, 2 => { "first_name" => "Jeremy"} }
467
542
  # Person.update(people.keys, people.values)
468
543
  def update(id, attributes)
@@ -476,62 +551,178 @@ module ActiveRecord #:nodoc:
476
551
  end
477
552
  end
478
553
 
479
- # Deletes the record with the given +id+ without instantiating an object first. If an array of ids is provided, all of them
480
- # are deleted.
554
+ # Delete an object (or multiple objects) where the +id+ given matches the primary_key. A SQL +DELETE+ command
555
+ # is executed on the database which means that no callbacks are fired off running this. This is an efficient method
556
+ # of deleting records that don't need cleaning up after or other actions to be taken.
557
+ #
558
+ # Objects are _not_ instantiated with this method.
559
+ #
560
+ # ==== Options
561
+ #
562
+ # +id+ Can be either an Integer or an Array of Integers
563
+ #
564
+ # ==== Examples
565
+ #
566
+ # # Delete a single object
567
+ # Todo.delete(1)
568
+ #
569
+ # # Delete multiple objects
570
+ # todos = [1,2,3]
571
+ # Todo.delete(todos)
481
572
  def delete(id)
482
573
  delete_all([ "#{connection.quote_column_name(primary_key)} IN (?)", id ])
483
574
  end
484
575
 
485
- # Destroys the record with the given +id+ by instantiating the object and calling #destroy (all the callbacks are the triggered).
486
- # If an array of ids is provided, all of them are destroyed.
576
+ # Destroy an object (or multiple objects) that has the given id, the object is instantiated first,
577
+ # therefore all callbacks and filters are fired off before the object is deleted. This method is
578
+ # less efficient than ActiveRecord#delete but allows cleanup methods and other actions to be run.
579
+ #
580
+ # This essentially finds the object (or multiple objects) with the given id, creates a new object
581
+ # from the attributes, and then calls destroy on it.
582
+ #
583
+ # ==== Options
584
+ #
585
+ # +id+ Can be either an Integer or an Array of Integers
586
+ #
587
+ # ==== Examples
588
+ #
589
+ # # Destroy a single object
590
+ # Todo.destroy(1)
591
+ #
592
+ # # Destroy multiple objects
593
+ # todos = [1,2,3]
594
+ # Todo.destroy(todos)
487
595
  def destroy(id)
488
596
  id.is_a?(Array) ? id.each { |id| destroy(id) } : find(id).destroy
489
597
  end
490
598
 
491
- # Updates all records with the SET-part of an SQL update statement in +updates+ and returns an integer with the number of rows updated.
492
- # A subset of the records can be selected by specifying +conditions+. Example:
493
- # Billing.update_all "category = 'authorized', approved = 1", "author = 'David'"
494
- def update_all(updates, conditions = nil)
495
- sql = "UPDATE #{table_name} SET #{sanitize_sql(updates)} "
496
- add_conditions!(sql, conditions, scope(:find))
599
+ # Updates all records with details given if they match a set of conditions supplied, limits and order can
600
+ # also be supplied.
601
+ #
602
+ # ==== Options
603
+ #
604
+ # +updates+ A String of column and value pairs that will be set on any records that match conditions
605
+ # +conditions+ An SQL fragment like "administrator = 1" or [ "user_name = ?", username ].
606
+ # See conditions in the intro for more info.
607
+ # +options+ Additional options are :limit and/or :order, see the examples for usage.
608
+ #
609
+ # ==== Examples
610
+ #
611
+ # # Update all billing objects with the 3 different attributes given
612
+ # Billing.update_all( "category = 'authorized', approved = 1, author = 'David'" )
613
+ #
614
+ # # Update records that match our conditions
615
+ # Billing.update_all( "author = 'David'", "title LIKE '%Rails%'" )
616
+ #
617
+ # # Update records that match our conditions but limit it to 5 ordered by date
618
+ # Billing.update_all( "author = 'David'", "title LIKE '%Rails%'",
619
+ # :order => 'created_at', :limit => 5 )
620
+ def update_all(updates, conditions = nil, options = {})
621
+ sql = "UPDATE #{table_name} SET #{sanitize_sql_for_assignment(updates)} "
622
+ scope = scope(:find)
623
+ add_conditions!(sql, conditions, scope)
624
+ add_order!(sql, options[:order], scope)
625
+ add_limit!(sql, options, scope)
497
626
  connection.update(sql, "#{name} Update")
498
627
  end
499
628
 
500
- # Destroys the objects for all the records that match the +condition+ by instantiating each object and calling
629
+ # Destroys the objects for all the records that match the +conditions+ by instantiating each object and calling
501
630
  # the destroy method. Example:
502
631
  # Person.destroy_all "last_login < '2004-04-04'"
503
632
  def destroy_all(conditions = nil)
504
633
  find(:all, :conditions => conditions).each { |object| object.destroy }
505
634
  end
506
635
 
507
- # Deletes all the records that match the +condition+ without instantiating the objects first (and hence not
636
+ # Deletes all the records that match the +conditions+ without instantiating the objects first (and hence not
508
637
  # calling the destroy method). Example:
509
638
  # Post.delete_all "person_id = 5 AND (category = 'Something' OR category = 'Else')"
510
639
  def delete_all(conditions = nil)
511
- sql = "DELETE FROM #{table_name} "
640
+ sql = "DELETE FROM #{quoted_table_name} "
512
641
  add_conditions!(sql, conditions, scope(:find))
513
642
  connection.delete(sql, "#{name} Delete all")
514
643
  end
515
644
 
516
645
  # Returns the result of an SQL statement that should only include a COUNT(*) in the SELECT part.
646
+ # The use of this method should be restricted to complicated SQL queries that can't be executed
647
+ # using the ActiveRecord::Calculations class methods. Look into those before using this.
648
+ #
649
+ # ==== Options
650
+ #
651
+ # +sql+: An SQL statement which should return a count query from the database, see the example below
652
+ #
653
+ # ==== Examples
654
+ #
517
655
  # Product.count_by_sql "SELECT COUNT(*) FROM sales s, customers c WHERE s.customer_id = c.id"
518
656
  def count_by_sql(sql)
519
657
  sql = sanitize_conditions(sql)
520
658
  connection.select_value(sql, "#{name} Count").to_i
521
659
  end
522
660
 
523
- # Increments the specified counter by one. So <tt>DiscussionBoard.increment_counter("post_count",
524
- # discussion_board_id)</tt> would increment the "post_count" counter on the board responding to discussion_board_id.
525
- # This is used for caching aggregate values, so that they don't need to be computed every time. Especially important
526
- # for looping over a collection where each element require a number of aggregate values. Like the DiscussionBoard
527
- # that needs to list both the number of posts and comments.
661
+ # A generic "counter updater" implementation, intended primarily to be
662
+ # used by increment_counter and decrement_counter, but which may also
663
+ # be useful on its own. It simply does a direct SQL update for the record
664
+ # with the given ID, altering the given hash of counters by the amount
665
+ # given by the corresponding value:
666
+ #
667
+ # ==== Options
668
+ #
669
+ # +id+ The id of the object you wish to update a counter on
670
+ # +counters+ An Array of Hashes containing the names of the fields
671
+ # to update as keys and the amount to update the field by as
672
+ # values
673
+ #
674
+ # ==== Examples
675
+ #
676
+ # # For the Post with id of 5, decrement the comment_count by 1, and
677
+ # # increment the action_count by 1
678
+ # Post.update_counters 5, :comment_count => -1, :action_count => 1
679
+ # # Executes the following SQL:
680
+ # # UPDATE posts
681
+ # # SET comment_count = comment_count - 1,
682
+ # # action_count = action_count + 1
683
+ # # WHERE id = 5
684
+ def update_counters(id, counters)
685
+ updates = counters.inject([]) { |list, (counter_name, increment)|
686
+ sign = increment < 0 ? "-" : "+"
687
+ list << "#{connection.quote_column_name(counter_name)} = #{connection.quote_column_name(counter_name)} #{sign} #{increment.abs}"
688
+ }.join(", ")
689
+ update_all(updates, "#{connection.quote_column_name(primary_key)} = #{quote_value(id)}")
690
+ end
691
+
692
+ # Increment a number field by one, usually representing a count.
693
+ #
694
+ # This is used for caching aggregate values, so that they don't need to be computed every time.
695
+ # For example, a DiscussionBoard may cache post_count and comment_count otherwise every time the board is
696
+ # shown it would have to run an SQL query to find how many posts and comments there are.
697
+ #
698
+ # ==== Options
699
+ #
700
+ # +counter_name+ The name of the field that should be incremented
701
+ # +id+ The id of the object that should be incremented
702
+ #
703
+ # ==== Examples
704
+ #
705
+ # # Increment the post_count column for the record with an id of 5
706
+ # DiscussionBoard.increment_counter(:post_count, 5)
528
707
  def increment_counter(counter_name, id)
529
- update_all "#{connection.quote_column_name(counter_name)} = #{connection.quote_column_name(counter_name)} + 1", "#{connection.quote_column_name(primary_key)} = #{quote_value(id)}"
708
+ update_counters(id, counter_name => 1)
530
709
  end
531
710
 
532
- # Works like increment_counter, but decrements instead.
711
+ # Decrement a number field by one, usually representing a count.
712
+ #
713
+ # This works the same as increment_counter but reduces the column value by 1 instead of increasing it.
714
+ #
715
+ # ==== Options
716
+ #
717
+ # +counter_name+ The name of the field that should be decremented
718
+ # +id+ The id of the object that should be decremented
719
+ #
720
+ # ==== Examples
721
+ #
722
+ # # Decrement the post_count column for the record with an id of 5
723
+ # DiscussionBoard.decrement_counter(:post_count, 5)
533
724
  def decrement_counter(counter_name, id)
534
- update_all "#{connection.quote_column_name(counter_name)} = #{connection.quote_column_name(counter_name)} - 1", "#{connection.quote_column_name(primary_key)} = #{quote_value(id)}"
725
+ update_counters(id, counter_name => -1)
535
726
  end
536
727
 
537
728
 
@@ -550,8 +741,10 @@ module ActiveRecord #:nodoc:
550
741
  #
551
742
  # customer.credit_rating = "Average"
552
743
  # customer.credit_rating # => "Average"
744
+ #
745
+ # To start from an all-closed default and enable attributes as needed, have a look at attr_accessible.
553
746
  def attr_protected(*attributes)
554
- write_inheritable_array("attr_protected", attributes - (protected_attributes || []))
747
+ write_inheritable_attribute("attr_protected", Set.new(attributes.map(&:to_s)) + (protected_attributes || []))
555
748
  end
556
749
 
557
750
  # Returns an array of all the attributes that have been protected from mass-assignment.
@@ -559,12 +752,33 @@ module ActiveRecord #:nodoc:
559
752
  read_inheritable_attribute("attr_protected")
560
753
  end
561
754
 
562
- # If this macro is used, only those attributes named in it will be accessible for mass-assignment, such as
563
- # <tt>new(attributes)</tt> and <tt>attributes=(attributes)</tt>. This is the more conservative choice for mass-assignment
564
- # protection. If you'd rather start from an all-open default and restrict attributes as needed, have a look at
565
- # attr_protected.
755
+ # Similar to the attr_protected macro, this protects attributes of your model from mass-assignment,
756
+ # such as <tt>new(attributes)</tt> and <tt>attributes=(attributes)</tt>
757
+ # however, it does it in the opposite way. This locks all attributes and only allows access to the
758
+ # attributes specified. Assignment to attributes not in this list will be ignored and need to be set
759
+ # using the direct writer methods instead. This is meant to protect sensitive attributes from being
760
+ # overwritten by URL/form hackers. If you'd rather start from an all-open default and restrict
761
+ # attributes as needed, have a look at attr_protected.
762
+ #
763
+ # ==== Options
764
+ #
765
+ # <tt>*attributes</tt> A comma separated list of symbols that represent columns _not_ to be protected
766
+ #
767
+ # ==== Examples
768
+ #
769
+ # class Customer < ActiveRecord::Base
770
+ # attr_accessible :name, :nickname
771
+ # end
772
+ #
773
+ # customer = Customer.new(:name => "David", :nickname => "Dave", :credit_rating => "Excellent")
774
+ # customer.credit_rating # => nil
775
+ # customer.attributes = { :name => "Jolly fellow", :credit_rating => "Superb" }
776
+ # customer.credit_rating # => nil
777
+ #
778
+ # customer.credit_rating = "Average"
779
+ # customer.credit_rating # => "Average"
566
780
  def attr_accessible(*attributes)
567
- write_inheritable_array("attr_accessible", attributes - (accessible_attributes || []))
781
+ write_inheritable_attribute("attr_accessible", Set.new(attributes.map(&:to_s)) + (accessible_attributes || []))
568
782
  end
569
783
 
570
784
  # Returns an array of all the attributes that have been made accessible to mass-assignment.
@@ -572,10 +786,31 @@ module ActiveRecord #:nodoc:
572
786
  read_inheritable_attribute("attr_accessible")
573
787
  end
574
788
 
789
+ # Attributes listed as readonly can be set for a new record, but will be ignored in database updates afterwards.
790
+ def attr_readonly(*attributes)
791
+ write_inheritable_attribute("attr_readonly", Set.new(attributes.map(&:to_s)) + (readonly_attributes || []))
792
+ end
575
793
 
576
- # Specifies that the attribute by the name of +attr_name+ should be serialized before saving to the database and unserialized
577
- # after loading from the database. The serialization is done through YAML. If +class_name+ is specified, the serialized
578
- # object must be of that class on retrieval, or nil. Otherwise, +SerializationTypeMismatch+ will be raised.
794
+ # Returns an array of all the attributes that have been specified as readonly.
795
+ def readonly_attributes
796
+ read_inheritable_attribute("attr_readonly")
797
+ end
798
+
799
+ # If you have an attribute that needs to be saved to the database as an object, and retrieved as the same object,
800
+ # then specify the name of that attribute using this method and it will be handled automatically.
801
+ # The serialization is done through YAML. If +class_name+ is specified, the serialized object must be of that
802
+ # class on retrieval or +SerializationTypeMismatch+ will be raised.
803
+ #
804
+ # ==== Options
805
+ #
806
+ # +attr_name+ The field name that should be serialized
807
+ # +class_name+ Optional, class name that the object type should be equal to
808
+ #
809
+ # ==== Example
810
+ # # Serialize a preferences attribute
811
+ # class User
812
+ # serialize :preferences
813
+ # end
579
814
  def serialize(attr_name, class_name = Object)
580
815
  serialized_attributes[attr_name.to_s] = class_name
581
816
  end
@@ -588,14 +823,23 @@ module ActiveRecord #:nodoc:
588
823
 
589
824
  # Guesses the table name (in forced lower-case) based on the name of the class in the inheritance hierarchy descending
590
825
  # directly from ActiveRecord. So if the hierarchy looks like: Reply < Message < ActiveRecord, then Message is used
591
- # to guess the table name from even when called on Reply. The rules used to do the guess are handled by the Inflector class
592
- # in Active Support, which knows almost all common English inflections (report a bug if your inflection isn't covered).
826
+ # to guess the table name even when called on Reply. The rules used to do the guess are handled by the Inflector class
827
+ # in Active Support, which knows almost all common English inflections. You can add new inflections in config/initializers/inflections.rb.
593
828
  #
594
829
  # Nested classes are given table names prefixed by the singular form of
595
- # the parent's table name. Example:
830
+ # the parent's table name. Enclosing modules are not considered. Examples:
831
+ #
832
+ # class Invoice < ActiveRecord::Base; end;
596
833
  # file class table_name
597
834
  # invoice.rb Invoice invoices
598
- # invoice/lineitem.rb Invoice::Lineitem invoice_lineitems
835
+ #
836
+ # class Invoice < ActiveRecord::Base; class Lineitem < ActiveRecord::Base; end; end;
837
+ # file class table_name
838
+ # invoice.rb Invoice::Lineitem invoice_lineitems
839
+ #
840
+ # module Invoice; class Lineitem < ActiveRecord::Base; end; end;
841
+ # file class table_name
842
+ # invoice/lineitem.rb Invoice::Lineitem lineitems
599
843
  #
600
844
  # Additionally, the class-level table_name_prefix is prepended and the
601
845
  # table_name_suffix is appended. So if you have "myapp_" as a prefix,
@@ -796,15 +1040,10 @@ module ActiveRecord #:nodoc:
796
1040
  end
797
1041
  end
798
1042
 
799
- # Contains the names of the generated reader methods.
800
- def read_methods #:nodoc:
801
- @read_methods ||= Set.new
802
- end
803
-
804
1043
  # Resets all the cached information about columns, which will cause them to be reloaded on the next request.
805
1044
  def reset_column_information
806
- read_methods.each { |name| undef_method(name) }
807
- @column_names = @columns = @columns_hash = @content_columns = @dynamic_methods_hash = @read_methods = @inheritance_column = nil
1045
+ generated_methods.each { |name| undef_method(name) }
1046
+ @column_names = @columns = @columns_hash = @content_columns = @dynamic_methods_hash = @generated_methods = @inheritance_column = nil
808
1047
  end
809
1048
 
810
1049
  def reset_column_information_and_inheritable_attributes_for_all_subclasses#:nodoc:
@@ -818,21 +1057,40 @@ module ActiveRecord #:nodoc:
818
1057
  attribute_key_name.humanize
819
1058
  end
820
1059
 
821
- def descends_from_active_record? # :nodoc:
822
- superclass == Base || !columns_hash.include?(inheritance_column)
1060
+ # True if this isn't a concrete subclass needing a STI type condition.
1061
+ def descends_from_active_record?
1062
+ if superclass.abstract_class?
1063
+ superclass.descends_from_active_record?
1064
+ else
1065
+ superclass == Base || !columns_hash.include?(inheritance_column)
1066
+ end
823
1067
  end
824
1068
 
1069
+ def finder_needs_type_condition? #:nodoc:
1070
+ # This is like this because benchmarking justifies the strange :false stuff
1071
+ :true == (@finder_needs_type_condition ||= descends_from_active_record? ? :false : :true)
1072
+ end
825
1073
 
826
- def quote_value(value, column = nil) #:nodoc:
827
- connection.quote(value,column)
1074
+ # Returns a string like 'Post id:integer, title:string, body:text'
1075
+ def inspect
1076
+ if self == Base
1077
+ super
1078
+ elsif abstract_class?
1079
+ "#{super}(abstract)"
1080
+ elsif table_exists?
1081
+ attr_list = columns.map { |c| "#{c.name}: #{c.type}" } * ', '
1082
+ "#{super}(#{attr_list})"
1083
+ else
1084
+ "#{super}(Table doesn't exist)"
1085
+ end
828
1086
  end
829
1087
 
830
- def quote(value, column = nil) #:nodoc:
831
- connection.quote(value, column)
1088
+
1089
+ def quote_value(value, column = nil) #:nodoc:
1090
+ connection.quote(value,column)
832
1091
  end
833
- deprecate :quote => :quote_value
834
1092
 
835
- # Used to sanitize objects before they're used in an SELECT SQL-statement. Delegates to <tt>connection.quote</tt>.
1093
+ # Used to sanitize objects before they're used in an SQL SELECT statement. Delegates to <tt>connection.quote</tt>.
836
1094
  def sanitize(object) #:nodoc:
837
1095
  connection.quote(object)
838
1096
  end
@@ -869,106 +1127,11 @@ module ActiveRecord #:nodoc:
869
1127
  logger.level = old_logger_level if logger
870
1128
  end
871
1129
 
872
- # Scope parameters to method calls within the block. Takes a hash of method_name => parameters hash.
873
- # method_name may be :find or :create. :find parameters may include the <tt>:conditions</tt>, <tt>:joins</tt>,
874
- # <tt>:include</tt>, <tt>:offset</tt>, <tt>:limit</tt>, and <tt>:readonly</tt> options. :create parameters are an attributes hash.
875
- #
876
- # Article.with_scope(:find => { :conditions => "blog_id = 1" }, :create => { :blog_id => 1 }) do
877
- # Article.find(1) # => SELECT * from articles WHERE blog_id = 1 AND id = 1
878
- # a = Article.create(1)
879
- # a.blog_id # => 1
880
- # end
881
- #
882
- # In nested scopings, all previous parameters are overwritten by inner rule
883
- # except :conditions in :find, that are merged as hash.
884
- #
885
- # Article.with_scope(:find => { :conditions => "blog_id = 1", :limit => 1 }, :create => { :blog_id => 1 }) do
886
- # Article.with_scope(:find => { :limit => 10})
887
- # Article.find(:all) # => SELECT * from articles WHERE blog_id = 1 LIMIT 10
888
- # end
889
- # Article.with_scope(:find => { :conditions => "author_id = 3" })
890
- # Article.find(:all) # => SELECT * from articles WHERE blog_id = 1 AND author_id = 3 LIMIT 1
891
- # end
892
- # end
893
- #
894
- # You can ignore any previous scopings by using <tt>with_exclusive_scope</tt> method.
895
- #
896
- # Article.with_scope(:find => { :conditions => "blog_id = 1", :limit => 1 }) do
897
- # Article.with_exclusive_scope(:find => { :limit => 10 })
898
- # Article.find(:all) # => SELECT * from articles LIMIT 10
899
- # end
900
- # end
901
- def with_scope(method_scoping = {}, action = :merge, &block)
902
- method_scoping = method_scoping.method_scoping if method_scoping.respond_to?(:method_scoping)
903
-
904
- # Dup first and second level of hash (method and params).
905
- method_scoping = method_scoping.inject({}) do |hash, (method, params)|
906
- hash[method] = (params == true) ? params : params.dup
907
- hash
908
- end
909
-
910
- method_scoping.assert_valid_keys([ :find, :create ])
911
-
912
- if f = method_scoping[:find]
913
- f.assert_valid_keys([ :conditions, :joins, :select, :include, :from, :offset, :limit, :order, :readonly, :lock ])
914
- f[:readonly] = true if !f[:joins].blank? && !f.has_key?(:readonly)
915
- end
916
-
917
- # Merge scopings
918
- if action == :merge && current_scoped_methods
919
- method_scoping = current_scoped_methods.inject(method_scoping) do |hash, (method, params)|
920
- case hash[method]
921
- when Hash
922
- if method == :find
923
- (hash[method].keys + params.keys).uniq.each do |key|
924
- merge = hash[method][key] && params[key] # merge if both scopes have the same key
925
- if key == :conditions && merge
926
- hash[method][key] = [params[key], hash[method][key]].collect{ |sql| "( %s )" % sanitize_sql(sql) }.join(" AND ")
927
- elsif key == :include && merge
928
- hash[method][key] = merge_includes(hash[method][key], params[key]).uniq
929
- else
930
- hash[method][key] = hash[method][key] || params[key]
931
- end
932
- end
933
- else
934
- hash[method] = params.merge(hash[method])
935
- end
936
- else
937
- hash[method] = params
938
- end
939
- hash
940
- end
941
- end
942
-
943
- self.scoped_methods << method_scoping
944
-
945
- begin
946
- yield
947
- ensure
948
- self.scoped_methods.pop
949
- end
950
- end
951
-
952
- # Works like with_scope, but discards any nested properties.
953
- def with_exclusive_scope(method_scoping = {}, &block)
954
- with_scope(method_scoping, :overwrite, &block)
955
- end
956
-
957
1130
  # Overwrite the default class equality method to provide support for association proxies.
958
1131
  def ===(object)
959
1132
  object.is_a?(self)
960
1133
  end
961
1134
 
962
- # Deprecated
963
- def threaded_connections #:nodoc:
964
- allow_concurrency
965
- end
966
-
967
- # Deprecated
968
- def threaded_connections=(value) #:nodoc:
969
- self.allow_concurrency = value
970
- end
971
-
972
1135
  # Returns the base AR subclass that this class descends from. If A
973
1136
  # extends AR::Base, A.base_class will return A. If B descends from A
974
1137
  # through some arbitrarily deep hierarchy, B.base_class will return A.
@@ -1020,7 +1183,7 @@ module ActiveRecord #:nodoc:
1020
1183
 
1021
1184
  def find_one(id, options)
1022
1185
  conditions = " AND (#{sanitize_sql(options[:conditions])})" if options[:conditions]
1023
- options.update :conditions => "#{table_name}.#{connection.quote_column_name(primary_key)} = #{quote_value(id,columns_hash[primary_key])}#{conditions}"
1186
+ options.update :conditions => "#{quoted_table_name}.#{connection.quote_column_name(primary_key)} = #{quote_value(id,columns_hash[primary_key])}#{conditions}"
1024
1187
 
1025
1188
  # Use find_every(options).first since the primary key condition
1026
1189
  # already ensures we have a single record. Using find_initial adds
@@ -1035,14 +1198,27 @@ module ActiveRecord #:nodoc:
1035
1198
  def find_some(ids, options)
1036
1199
  conditions = " AND (#{sanitize_sql(options[:conditions])})" if options[:conditions]
1037
1200
  ids_list = ids.map { |id| quote_value(id,columns_hash[primary_key]) }.join(',')
1038
- options.update :conditions => "#{table_name}.#{connection.quote_column_name(primary_key)} IN (#{ids_list})#{conditions}"
1201
+ options.update :conditions => "#{quoted_table_name}.#{connection.quote_column_name(primary_key)} IN (#{ids_list})#{conditions}"
1039
1202
 
1040
1203
  result = find_every(options)
1041
1204
 
1042
- if result.size == ids.size
1205
+ # Determine expected size from limit and offset, not just ids.size.
1206
+ expected_size =
1207
+ if options[:limit] && ids.size > options[:limit]
1208
+ options[:limit]
1209
+ else
1210
+ ids.size
1211
+ end
1212
+
1213
+ # 11 ids with limit 3, offset 9 should give 2 results.
1214
+ if options[:offset] && (ids.size - options[:offset] < expected_size)
1215
+ expected_size = ids.size - options[:offset]
1216
+ end
1217
+
1218
+ if result.size == expected_size
1043
1219
  result
1044
1220
  else
1045
- raise RecordNotFound, "Couldn't find all #{name.pluralize} with IDs (#{ids_list})#{conditions}"
1221
+ raise RecordNotFound, "Couldn't find all #{name.pluralize} with IDs (#{ids_list})#{conditions} (found #{result.size} results, but was looking for #{expected_size})"
1046
1222
  end
1047
1223
  end
1048
1224
 
@@ -1052,9 +1228,10 @@ module ActiveRecord #:nodoc:
1052
1228
  def instantiate(record)
1053
1229
  object =
1054
1230
  if subclass_name = record[inheritance_column]
1231
+ # No type given.
1055
1232
  if subclass_name.empty?
1056
- # No type given.
1057
1233
  allocate
1234
+
1058
1235
  else
1059
1236
  # Ignore type if no column is present since it was probably
1060
1237
  # pulled in from a sloppy join.
@@ -1078,6 +1255,16 @@ module ActiveRecord #:nodoc:
1078
1255
  end
1079
1256
 
1080
1257
  object.instance_variable_set("@attributes", record)
1258
+ object.instance_variable_set("@attributes_cache", Hash.new)
1259
+
1260
+ if object.respond_to_without_attributes?(:after_find)
1261
+ object.send(:callback, :after_find)
1262
+ end
1263
+
1264
+ if object.respond_to_without_attributes?(:after_initialize)
1265
+ object.send(:callback, :after_initialize)
1266
+ end
1267
+
1081
1268
  object
1082
1269
  end
1083
1270
 
@@ -1089,14 +1276,13 @@ module ActiveRecord #:nodoc:
1089
1276
 
1090
1277
  def construct_finder_sql(options)
1091
1278
  scope = scope(:find)
1092
- sql = "SELECT #{(scope && scope[:select]) || options[:select] || '*'} "
1093
- sql << "FROM #{(scope && scope[:from]) || options[:from] || table_name} "
1279
+ sql = "SELECT #{(scope && scope[:select]) || options[:select] || (options[:joins] && quoted_table_name + '.*') || '*'} "
1280
+ sql << "FROM #{(scope && scope[:from]) || options[:from] || quoted_table_name} "
1094
1281
 
1095
1282
  add_joins!(sql, options, scope)
1096
1283
  add_conditions!(sql, options[:conditions], scope)
1097
1284
 
1098
- sql << " GROUP BY #{options[:group]} " if options[:group]
1099
-
1285
+ add_group!(sql, options[:group], scope)
1100
1286
  add_order!(sql, options[:order], scope)
1101
1287
  add_limit!(sql, options, scope)
1102
1288
  add_lock!(sql, options, scope)
@@ -1132,10 +1318,26 @@ module ActiveRecord #:nodoc:
1132
1318
  end
1133
1319
  end
1134
1320
 
1321
+ def add_group!(sql, group, scope = :auto)
1322
+ if group
1323
+ sql << " GROUP BY #{group}"
1324
+ else
1325
+ scope = scope(:find) if :auto == scope
1326
+ if scope && (scoped_group = scope[:group])
1327
+ sql << " GROUP BY #{scoped_group}"
1328
+ end
1329
+ end
1330
+ end
1331
+
1135
1332
  # The optional scope argument is for the current :find scope.
1136
1333
  def add_limit!(sql, options, scope = :auto)
1137
1334
  scope = scope(:find) if :auto == scope
1138
- options = options.reverse_merge(:limit => scope[:limit], :offset => scope[:offset]) if scope
1335
+
1336
+ if scope
1337
+ options[:limit] ||= scope[:limit]
1338
+ options[:offset] ||= scope[:offset]
1339
+ end
1340
+
1139
1341
  connection.add_limit_offset!(sql, options)
1140
1342
  end
1141
1343
 
@@ -1151,7 +1353,13 @@ module ActiveRecord #:nodoc:
1151
1353
  def add_joins!(sql, options, scope = :auto)
1152
1354
  scope = scope(:find) if :auto == scope
1153
1355
  join = (scope && scope[:joins]) || options[:joins]
1154
- sql << " #{join} " if join
1356
+ case join
1357
+ when Symbol, Hash, Array
1358
+ join_dependency = ActiveRecord::Associations::ClassMethods::InnerJoinDependency.new(self, join, nil)
1359
+ sql << " #{join_dependency.join_associations.collect{|join| join.association_join }.join} "
1360
+ else
1361
+ sql << " #{join} "
1362
+ end
1155
1363
  end
1156
1364
 
1157
1365
  # Adds a sanitized version of +conditions+ to the +sql+ string. Note that the passed-in +sql+ string is changed.
@@ -1159,17 +1367,17 @@ module ActiveRecord #:nodoc:
1159
1367
  def add_conditions!(sql, conditions, scope = :auto)
1160
1368
  scope = scope(:find) if :auto == scope
1161
1369
  segments = []
1162
- segments << sanitize_sql(scope[:conditions]) if scope && scope[:conditions]
1163
- segments << sanitize_sql(conditions) unless conditions.nil?
1164
- segments << type_condition unless descends_from_active_record?
1165
- segments.compact!
1370
+ segments << sanitize_sql(scope[:conditions]) if scope && !scope[:conditions].blank?
1371
+ segments << sanitize_sql(conditions) unless conditions.blank?
1372
+ segments << type_condition if finder_needs_type_condition?
1373
+ segments.delete_if{|s| s.blank?}
1166
1374
  sql << "WHERE (#{segments.join(") AND (")}) " unless segments.empty?
1167
1375
  end
1168
1376
 
1169
1377
  def type_condition
1170
1378
  quoted_inheritance_column = connection.quote_column_name(inheritance_column)
1171
- type_condition = subclasses.inject("#{table_name}.#{quoted_inheritance_column} = '#{name.demodulize}' ") do |condition, subclass|
1172
- condition << "OR #{table_name}.#{quoted_inheritance_column} = '#{subclass.name.demodulize}' "
1379
+ type_condition = subclasses.inject("#{quoted_table_name}.#{quoted_inheritance_column} = '#{name.demodulize}' ") do |condition, subclass|
1380
+ condition << "OR #{quoted_table_name}.#{quoted_inheritance_column} = '#{subclass.name.demodulize}' "
1173
1381
  end
1174
1382
 
1175
1383
  " (#{type_condition}) "
@@ -1184,56 +1392,69 @@ module ActiveRecord #:nodoc:
1184
1392
 
1185
1393
  # Enables dynamic finders like find_by_user_name(user_name) and find_by_user_name_and_password(user_name, password) that are turned into
1186
1394
  # find(:first, :conditions => ["user_name = ?", user_name]) and find(:first, :conditions => ["user_name = ? AND password = ?", user_name, password])
1187
- # respectively. Also works for find(:all), but using find_all_by_amount(50) that are turned into find(:all, :conditions => ["amount = ?", 50]).
1395
+ # respectively. Also works for find(:all) by using find_all_by_amount(50) that is turned into find(:all, :conditions => ["amount = ?", 50]).
1188
1396
  #
1189
1397
  # It's even possible to use all the additional parameters to find. For example, the full interface for find_all_by_amount
1190
1398
  # is actually find_all_by_amount(amount, options).
1191
1399
  #
1192
1400
  # This also enables you to initialize a record if it is not found, such as find_or_initialize_by_amount(amount)
1193
1401
  # or find_or_create_by_user_and_password(user, password).
1402
+ #
1403
+ # Each dynamic finder or initializer/creator is also defined in the class after it is first invoked, so that future
1404
+ # attempts to use it do not run through method_missing.
1194
1405
  def method_missing(method_id, *arguments)
1195
1406
  if match = /^find_(all_by|by)_([_a-zA-Z]\w*)$/.match(method_id.to_s)
1196
- finder, deprecated_finder = determine_finder(match), determine_deprecated_finder(match)
1407
+ finder = determine_finder(match)
1197
1408
 
1198
1409
  attribute_names = extract_attribute_names_from_match(match)
1199
1410
  super unless all_attributes_exists?(attribute_names)
1200
1411
 
1201
- attributes = construct_attributes_from_arguments(attribute_names, arguments)
1202
-
1203
- case extra_options = arguments[attribute_names.size]
1204
- when nil
1205
- options = { :conditions => attributes }
1412
+ self.class_eval %{
1413
+ def self.#{method_id}(*args)
1414
+ options = args.last.is_a?(Hash) ? args.pop : {}
1415
+ attributes = construct_attributes_from_arguments([:#{attribute_names.join(',:')}], args)
1416
+ finder_options = { :conditions => attributes }
1417
+ validate_find_options(options)
1206
1418
  set_readonly_option!(options)
1207
- ActiveSupport::Deprecation.silence { send(finder, options) }
1208
1419
 
1209
- when Hash
1210
- finder_options = extra_options.merge(:conditions => attributes)
1211
- validate_find_options(finder_options)
1212
- set_readonly_option!(finder_options)
1213
-
1214
- if extra_options[:conditions]
1215
- with_scope(:find => { :conditions => extra_options[:conditions] }) do
1216
- ActiveSupport::Deprecation.silence { send(finder, finder_options) }
1420
+ if options[:conditions]
1421
+ with_scope(:find => finder_options) do
1422
+ ActiveSupport::Deprecation.silence { send(:#{finder}, options) }
1217
1423
  end
1218
1424
  else
1219
- ActiveSupport::Deprecation.silence { send(finder, finder_options) }
1425
+ ActiveSupport::Deprecation.silence { send(:#{finder}, options.merge(finder_options)) }
1220
1426
  end
1221
-
1222
- else
1223
- ActiveSupport::Deprecation.silence do
1224
- send(deprecated_finder, sanitize_sql(attributes), *arguments[attribute_names.length..-1])
1225
- end
1226
- end
1427
+ end
1428
+ }, __FILE__, __LINE__
1429
+ send(method_id, *arguments)
1227
1430
  elsif match = /^find_or_(initialize|create)_by_([_a-zA-Z]\w*)$/.match(method_id.to_s)
1228
1431
  instantiator = determine_instantiator(match)
1229
1432
  attribute_names = extract_attribute_names_from_match(match)
1230
1433
  super unless all_attributes_exists?(attribute_names)
1231
1434
 
1232
- attributes = construct_attributes_from_arguments(attribute_names, arguments)
1233
- options = { :conditions => attributes }
1234
- set_readonly_option!(options)
1435
+ self.class_eval %{
1436
+ def self.#{method_id}(*args)
1437
+ if args[0].is_a?(Hash)
1438
+ attributes = args[0].with_indifferent_access
1439
+ find_attributes = attributes.slice(*[:#{attribute_names.join(',:')}])
1440
+ else
1441
+ find_attributes = attributes = construct_attributes_from_arguments([:#{attribute_names.join(',:')}], args)
1442
+ end
1443
+
1444
+ options = { :conditions => find_attributes }
1445
+ set_readonly_option!(options)
1235
1446
 
1236
- find_initial(options) || send(instantiator, attributes)
1447
+ record = find_initial(options)
1448
+ if record.nil?
1449
+ record = self.new { |r| r.send(:attributes=, attributes, false) }
1450
+ #{'record.save' if instantiator == :create}
1451
+ record
1452
+ else
1453
+ record
1454
+ end
1455
+ end
1456
+ }, __FILE__, __LINE__
1457
+ send(method_id, *arguments)
1237
1458
  else
1238
1459
  super
1239
1460
  end
@@ -1243,10 +1464,6 @@ module ActiveRecord #:nodoc:
1243
1464
  match.captures.first == 'all_by' ? :find_every : :find_initial
1244
1465
  end
1245
1466
 
1246
- def determine_deprecated_finder(match)
1247
- match.captures.first == 'all_by' ? :find_all : :find_first
1248
- end
1249
-
1250
1467
  def determine_instantiator(match)
1251
1468
  match.captures.first == 'initialize' ? :new : :create
1252
1469
  end
@@ -1263,12 +1480,12 @@ module ActiveRecord #:nodoc:
1263
1480
 
1264
1481
  def all_attributes_exists?(attribute_names)
1265
1482
  attribute_names.all? { |name| column_methods_hash.include?(name.to_sym) }
1266
- end
1483
+ end
1267
1484
 
1268
1485
  def attribute_condition(argument)
1269
1486
  case argument
1270
1487
  when nil then "IS ?"
1271
- when Array then "IN (?)"
1488
+ when Array, ActiveRecord::Associations::AssociationCollection then "IN (?)"
1272
1489
  when Range then "BETWEEN ? AND ?"
1273
1490
  else "= ?"
1274
1491
  end
@@ -1314,6 +1531,103 @@ module ActiveRecord #:nodoc:
1314
1531
  end
1315
1532
 
1316
1533
  protected
1534
+ # Scope parameters to method calls within the block. Takes a hash of method_name => parameters hash.
1535
+ # method_name may be :find or :create. :find parameters may include the <tt>:conditions</tt>, <tt>:joins</tt>,
1536
+ # <tt>:include</tt>, <tt>:offset</tt>, <tt>:limit</tt>, and <tt>:readonly</tt> options. :create parameters are an attributes hash.
1537
+ #
1538
+ # class Article < ActiveRecord::Base
1539
+ # def self.create_with_scope
1540
+ # with_scope(:find => { :conditions => "blog_id = 1" }, :create => { :blog_id => 1 }) do
1541
+ # find(1) # => SELECT * from articles WHERE blog_id = 1 AND id = 1
1542
+ # a = create(1)
1543
+ # a.blog_id # => 1
1544
+ # end
1545
+ # end
1546
+ # end
1547
+ #
1548
+ # In nested scopings, all previous parameters are overwritten by the innermost rule, with the exception of
1549
+ # :conditions and :include options in :find, which are merged.
1550
+ #
1551
+ # class Article < ActiveRecord::Base
1552
+ # def self.find_with_scope
1553
+ # with_scope(:find => { :conditions => "blog_id = 1", :limit => 1 }, :create => { :blog_id => 1 }) do
1554
+ # with_scope(:find => { :limit => 10})
1555
+ # find(:all) # => SELECT * from articles WHERE blog_id = 1 LIMIT 10
1556
+ # end
1557
+ # with_scope(:find => { :conditions => "author_id = 3" })
1558
+ # find(:all) # => SELECT * from articles WHERE blog_id = 1 AND author_id = 3 LIMIT 1
1559
+ # end
1560
+ # end
1561
+ # end
1562
+ # end
1563
+ #
1564
+ # You can ignore any previous scopings by using the <tt>with_exclusive_scope</tt> method.
1565
+ #
1566
+ # class Article < ActiveRecord::Base
1567
+ # def self.find_with_exclusive_scope
1568
+ # with_scope(:find => { :conditions => "blog_id = 1", :limit => 1 }) do
1569
+ # with_exclusive_scope(:find => { :limit => 10 })
1570
+ # find(:all) # => SELECT * from articles LIMIT 10
1571
+ # end
1572
+ # end
1573
+ # end
1574
+ # end
1575
+ def with_scope(method_scoping = {}, action = :merge, &block)
1576
+ method_scoping = method_scoping.method_scoping if method_scoping.respond_to?(:method_scoping)
1577
+
1578
+ # Dup first and second level of hash (method and params).
1579
+ method_scoping = method_scoping.inject({}) do |hash, (method, params)|
1580
+ hash[method] = (params == true) ? params : params.dup
1581
+ hash
1582
+ end
1583
+
1584
+ method_scoping.assert_valid_keys([ :find, :create ])
1585
+
1586
+ if f = method_scoping[:find]
1587
+ f.assert_valid_keys(VALID_FIND_OPTIONS)
1588
+ set_readonly_option! f
1589
+ end
1590
+
1591
+ # Merge scopings
1592
+ if action == :merge && current_scoped_methods
1593
+ method_scoping = current_scoped_methods.inject(method_scoping) do |hash, (method, params)|
1594
+ case hash[method]
1595
+ when Hash
1596
+ if method == :find
1597
+ (hash[method].keys + params.keys).uniq.each do |key|
1598
+ merge = hash[method][key] && params[key] # merge if both scopes have the same key
1599
+ if key == :conditions && merge
1600
+ hash[method][key] = [params[key], hash[method][key]].collect{ |sql| "( %s )" % sanitize_sql(sql) }.join(" AND ")
1601
+ elsif key == :include && merge
1602
+ hash[method][key] = merge_includes(hash[method][key], params[key]).uniq
1603
+ else
1604
+ hash[method][key] = hash[method][key] || params[key]
1605
+ end
1606
+ end
1607
+ else
1608
+ hash[method] = params.merge(hash[method])
1609
+ end
1610
+ else
1611
+ hash[method] = params
1612
+ end
1613
+ hash
1614
+ end
1615
+ end
1616
+
1617
+ self.scoped_methods << method_scoping
1618
+
1619
+ begin
1620
+ yield
1621
+ ensure
1622
+ self.scoped_methods.pop
1623
+ end
1624
+ end
1625
+
1626
+ # Works like with_scope, but discards any nested properties.
1627
+ def with_exclusive_scope(method_scoping = {}, &block)
1628
+ with_scope(method_scoping, :overwrite, &block)
1629
+ end
1630
+
1317
1631
  def subclasses #:nodoc:
1318
1632
  @@subclasses[self] ||= []
1319
1633
  @@subclasses[self] + extra = @@subclasses[self].inject([]) {|list, subclass| list + subclass.subclasses }
@@ -1381,32 +1695,66 @@ module ActiveRecord #:nodoc:
1381
1695
  end
1382
1696
 
1383
1697
  # Accepts an array, hash, or string of sql conditions and sanitizes
1384
- # them into a valid SQL fragment.
1698
+ # them into a valid SQL fragment for a WHERE clause.
1385
1699
  # ["name='%s' and group_id='%s'", "foo'bar", 4] returns "name='foo''bar' and group_id='4'"
1386
1700
  # { :name => "foo'bar", :group_id => 4 } returns "name='foo''bar' and group_id='4'"
1387
1701
  # "name='foo''bar' and group_id='4'" returns "name='foo''bar' and group_id='4'"
1388
- def sanitize_sql(condition)
1702
+ def sanitize_sql_for_conditions(condition)
1389
1703
  case condition
1390
1704
  when Array; sanitize_sql_array(condition)
1391
- when Hash; sanitize_sql_hash(condition)
1705
+ when Hash; sanitize_sql_hash_for_conditions(condition)
1392
1706
  else condition
1393
1707
  end
1394
1708
  end
1709
+ alias_method :sanitize_sql, :sanitize_sql_for_conditions
1395
1710
 
1396
- # Sanitizes a hash of attribute/value pairs into SQL conditions.
1711
+ # Accepts an array, hash, or string of sql conditions and sanitizes
1712
+ # them into a valid SQL fragment for a SET clause.
1713
+ # { :name => nil, :group_id => 4 } returns "name = NULL , group_id='4'"
1714
+ def sanitize_sql_for_assignment(assignments)
1715
+ case assignments
1716
+ when Array; sanitize_sql_array(assignments)
1717
+ when Hash; sanitize_sql_hash_for_assignment(assignments)
1718
+ else assignments
1719
+ end
1720
+ end
1721
+
1722
+ # Sanitizes a hash of attribute/value pairs into SQL conditions for a WHERE clause.
1397
1723
  # { :name => "foo'bar", :group_id => 4 }
1398
1724
  # # => "name='foo''bar' and group_id= 4"
1399
1725
  # { :status => nil, :group_id => [1,2,3] }
1400
1726
  # # => "status IS NULL and group_id IN (1,2,3)"
1401
1727
  # { :age => 13..18 }
1402
1728
  # # => "age BETWEEN 13 AND 18"
1403
- def sanitize_sql_hash(attrs)
1729
+ # { 'other_records.id' => 7 }
1730
+ # # => "`other_records`.`id` = 7"
1731
+ def sanitize_sql_hash_for_conditions(attrs)
1404
1732
  conditions = attrs.map do |attr, value|
1733
+ attr = attr.to_s
1734
+
1735
+ # Extract table name from qualified attribute names.
1736
+ if attr.include?('.')
1737
+ table_name, attr = attr.split('.', 2)
1738
+ table_name = connection.quote_table_name(table_name)
1739
+ else
1740
+ table_name = quoted_table_name
1741
+ end
1742
+
1405
1743
  "#{table_name}.#{connection.quote_column_name(attr)} #{attribute_condition(value)}"
1406
1744
  end.join(' AND ')
1407
1745
 
1408
1746
  replace_bind_variables(conditions, expand_range_bind_variables(attrs.values))
1409
1747
  end
1748
+ alias_method :sanitize_sql_hash, :sanitize_sql_hash_for_conditions
1749
+
1750
+ # Sanitizes a hash of attribute/value pairs into SQL conditions for a SET clause.
1751
+ # { :status => nil, :group_id => 1 }
1752
+ # # => "status = NULL , group_id = 1"
1753
+ def sanitize_sql_hash_for_assignment(attrs)
1754
+ conditions = attrs.map do |attr, value|
1755
+ "#{connection.quote_column_name(attr)} = #{quote_bound_value(value)}"
1756
+ end.join(', ')
1757
+ end
1410
1758
 
1411
1759
  # Accepts an array of conditions. The array has each value
1412
1760
  # sanitized and interpolated into the sql statement.
@@ -1466,10 +1814,6 @@ module ActiveRecord #:nodoc:
1466
1814
  end
1467
1815
  end
1468
1816
 
1469
- def extract_options_from_args!(args) #:nodoc:
1470
- args.last.is_a?(Hash) ? args.pop : {}
1471
- end
1472
-
1473
1817
  VALID_FIND_OPTIONS = [ :conditions, :include, :joins, :limit, :offset,
1474
1818
  :order, :select, :readonly, :group, :from, :lock ]
1475
1819
 
@@ -1481,8 +1825,8 @@ module ActiveRecord #:nodoc:
1481
1825
  # Inherit :readonly from finder scope if set. Otherwise,
1482
1826
  # if :joins is not blank then :readonly defaults to true.
1483
1827
  unless options.has_key?(:readonly)
1484
- if scoped?(:find, :readonly)
1485
- options[:readonly] = scope(:find, :readonly)
1828
+ if scoped_readonly = scope(:find, :readonly)
1829
+ options[:readonly] = scoped_readonly
1486
1830
  elsif !options[:joins].blank? && !options[:select]
1487
1831
  options[:readonly] = true
1488
1832
  end
@@ -1503,10 +1847,14 @@ module ActiveRecord #:nodoc:
1503
1847
  # hence you can't have attributes that aren't part of the table columns.
1504
1848
  def initialize(attributes = nil)
1505
1849
  @attributes = attributes_from_column_definition
1850
+ @attributes_cache = {}
1506
1851
  @new_record = true
1507
1852
  ensure_proper_type
1508
1853
  self.attributes = attributes unless attributes.nil?
1509
- yield self if block_given?
1854
+ self.class.send(:scope, :create).each { |att,value| self.send("#{att}=", value) } if self.class.send(:scoped?, :create)
1855
+ result = yield self if block_given?
1856
+ callback(:after_initialize) if respond_to_without_attributes?(:after_initialize)
1857
+ result
1510
1858
  end
1511
1859
 
1512
1860
  # A model instance's primary key is always available as model.id
@@ -1514,8 +1862,11 @@ module ActiveRecord #:nodoc:
1514
1862
  def id
1515
1863
  attr_name = self.class.primary_key
1516
1864
  column = column_for_attribute(attr_name)
1517
- define_read_method(:id, attr_name, column) if self.class.generate_read_methods
1518
- read_attribute(attr_name)
1865
+
1866
+ self.class.send(:define_read_method, :id, attr_name, column)
1867
+ # now that the method exists, call it
1868
+ self.send attr_name.to_sym
1869
+
1519
1870
  end
1520
1871
 
1521
1872
  # Enables Active Record objects to be used as URL parameters in Action Pack automatically.
@@ -1559,7 +1910,7 @@ module ActiveRecord #:nodoc:
1559
1910
  def destroy
1560
1911
  unless new_record?
1561
1912
  connection.delete <<-end_sql, "#{self.class.name} Destroy"
1562
- DELETE FROM #{self.class.table_name}
1913
+ DELETE FROM #{self.class.quoted_table_name}
1563
1914
  WHERE #{connection.quote_column_name(self.class.primary_key)} = #{quoted_id}
1564
1915
  end_sql
1565
1916
  end
@@ -1575,14 +1926,29 @@ module ActiveRecord #:nodoc:
1575
1926
  def clone
1576
1927
  attrs = self.attributes_before_type_cast
1577
1928
  attrs.delete(self.class.primary_key)
1578
- self.class.new do |record|
1579
- record.send :instance_variable_set, '@attributes', attrs
1929
+ record = self.class.new
1930
+ record.send :instance_variable_set, '@attributes', attrs
1931
+ record
1932
+ end
1933
+
1934
+ # Returns an instance of the specified klass with the attributes of the current record. This is mostly useful in relation to
1935
+ # single-table inheritance structures where you want a subclass to appear as the superclass. This can be used along with record
1936
+ # identification in Action Pack to allow, say, Client < Company to do something like render :partial => @client.becomes(Company)
1937
+ # to render that instance using the companies/company partial instead of clients/client.
1938
+ #
1939
+ # Note: The new instance will share a link to the same attributes as the original class. So any change to the attributes in either
1940
+ # instance will affect the other.
1941
+ def becomes(klass)
1942
+ returning klass.new do |became|
1943
+ became.instance_variable_set("@attributes", @attributes)
1944
+ became.instance_variable_set("@attributes_cache", @attributes_cache)
1945
+ became.instance_variable_set("@new_record", new_record?)
1580
1946
  end
1581
1947
  end
1582
1948
 
1583
1949
  # Updates a single attribute and saves the record. This is especially useful for boolean flags on existing records.
1584
1950
  # Note: This method is overwritten by the Validation module that'll make sure that updates made with this method
1585
- # doesn't get subjected to validation checks. Hence, attributes can be updated even if the full object isn't valid.
1951
+ # aren't subjected to validation checks. Hence, attributes can be updated even if the full object isn't valid.
1586
1952
  def update_attribute(name, value)
1587
1953
  send(name.to_s + '=', value)
1588
1954
  save
@@ -1644,6 +2010,7 @@ module ActiveRecord #:nodoc:
1644
2010
  clear_aggregation_cache
1645
2011
  clear_association_cache
1646
2012
  @attributes.update(self.class.find(self.id, options).instance_variable_get('@attributes'))
2013
+ @attributes_cache = {}
1647
2014
  self
1648
2015
  end
1649
2016
 
@@ -1663,15 +2030,17 @@ module ActiveRecord #:nodoc:
1663
2030
  # Allows you to set all the attributes at once by passing in a hash with keys
1664
2031
  # matching the attribute names (which again matches the column names). Sensitive attributes can be protected
1665
2032
  # from this form of mass-assignment by using the +attr_protected+ macro. Or you can alternatively
1666
- # specify which attributes *can* be accessed in with the +attr_accessible+ macro. Then all the
1667
- # attributes not included in that won't be allowed to be mass-assigned.
1668
- def attributes=(new_attributes)
2033
+ # specify which attributes *can* be accessed with the +attr_accessible+ macro. Then all the
2034
+ # attributes not included in that won't be allowed to be mass-assigned.
2035
+ def attributes=(new_attributes, guard_protected_attributes = true)
1669
2036
  return if new_attributes.nil?
1670
2037
  attributes = new_attributes.dup
1671
2038
  attributes.stringify_keys!
1672
2039
 
1673
2040
  multi_parameter_attributes = []
1674
- remove_attributes_protected_from_mass_assignment(attributes).each do |k, v|
2041
+ attributes = remove_attributes_protected_from_mass_assignment(attributes) if guard_protected_attributes
2042
+
2043
+ attributes.each do |k, v|
1675
2044
  k.include?("(") ? multi_parameter_attributes << [ k, v ] : send(k + "=", v)
1676
2045
  end
1677
2046
 
@@ -1705,11 +2074,24 @@ module ActiveRecord #:nodoc:
1705
2074
  clone_attributes :read_attribute_before_type_cast
1706
2075
  end
1707
2076
 
2077
+ # Format attributes nicely for inspect.
2078
+ def attribute_for_inspect(attr_name)
2079
+ value = read_attribute(attr_name)
2080
+
2081
+ if value.is_a?(String) && value.length > 50
2082
+ "#{value[0..50]}...".inspect
2083
+ elsif value.is_a?(Date) || value.is_a?(Time)
2084
+ %("#{value.to_s(:db)}")
2085
+ else
2086
+ value.inspect
2087
+ end
2088
+ end
2089
+
1708
2090
  # Returns true if the specified +attribute+ has been set by the user or by a database load and is neither
1709
2091
  # nil nor empty? (the latter only applies to objects that respond to empty?, most notably Strings).
1710
2092
  def attribute_present?(attribute)
1711
2093
  value = read_attribute(attribute)
1712
- !value.blank? or value == 0
2094
+ !value.blank?
1713
2095
  end
1714
2096
 
1715
2097
  # Returns true if the given attribute is in the attributes hash
@@ -1746,45 +2128,36 @@ module ActiveRecord #:nodoc:
1746
2128
  id.hash
1747
2129
  end
1748
2130
 
1749
- # For checking respond_to? without searching the attributes (which is faster).
1750
- alias_method :respond_to_without_attributes?, :respond_to?
1751
-
1752
- # A Person object with a name attribute can ask person.respond_to?("name"), person.respond_to?("name="), and
1753
- # person.respond_to?("name?") which will all return true.
1754
- def respond_to?(method, include_priv = false)
1755
- if @attributes.nil?
1756
- return super
1757
- elsif attr_name = self.class.column_methods_hash[method.to_sym]
1758
- return true if @attributes.include?(attr_name) || attr_name == self.class.primary_key
1759
- return false if self.class.read_methods.include?(attr_name)
1760
- elsif @attributes.include?(method_name = method.to_s)
1761
- return true
1762
- elsif md = self.class.match_attribute_method?(method.to_s)
1763
- return true if @attributes.include?(md.pre_match)
1764
- end
1765
- # super must be called at the end of the method, because the inherited respond_to?
1766
- # would return true for generated readers, even if the attribute wasn't present
1767
- super
1768
- end
1769
-
1770
- # Just freeze the attributes hash, such that associations are still accessible even on destroyed records.
2131
+ # Freeze the attributes hash such that associations are still accessible, even on destroyed records.
1771
2132
  def freeze
1772
2133
  @attributes.freeze; self
1773
2134
  end
1774
2135
 
2136
+ # Returns +true+ if the attributes hash has been frozen.
1775
2137
  def frozen?
1776
2138
  @attributes.frozen?
1777
2139
  end
1778
2140
 
1779
- # Records loaded through joins with piggy-back attributes will be marked as read only as they cannot be saved and return true to this query.
2141
+ # Returns +true+ if the record is read only. Records loaded through joins with piggy-back
2142
+ # attributes will be marked as read only since they cannot be saved.
1780
2143
  def readonly?
1781
2144
  @readonly == true
1782
2145
  end
1783
2146
 
1784
- def readonly! #:nodoc:
2147
+ # Marks this record as read only.
2148
+ def readonly!
1785
2149
  @readonly = true
1786
2150
  end
1787
2151
 
2152
+ # Returns the contents of the record as a nicely formatted string.
2153
+ def inspect
2154
+ attributes_as_nice_string = self.class.column_names.collect { |name|
2155
+ if has_attribute?(name) || new_record?
2156
+ "#{name}: #{attribute_for_inspect(name)}"
2157
+ end
2158
+ }.compact.join(", ")
2159
+ "#<#{self.class} #{attributes_as_nice_string}>"
2160
+ end
1788
2161
 
1789
2162
  private
1790
2163
  def create_or_update
@@ -1796,9 +2169,11 @@ module ActiveRecord #:nodoc:
1796
2169
  # Updates the associated record with values matching those of the instance attributes.
1797
2170
  # Returns the number of affected rows.
1798
2171
  def update
2172
+ quoted_attributes = attributes_with_quotes(false, false)
2173
+ return 0 if quoted_attributes.empty?
1799
2174
  connection.update(
1800
- "UPDATE #{self.class.table_name} " +
1801
- "SET #{quoted_comma_pair_list(connection, attributes_with_quotes(false))} " +
2175
+ "UPDATE #{self.class.quoted_table_name} " +
2176
+ "SET #{quoted_comma_pair_list(connection, quoted_attributes)} " +
1802
2177
  "WHERE #{connection.quote_column_name(self.class.primary_key)} = #{quote_value(id)}",
1803
2178
  "#{self.class.name} Update"
1804
2179
  )
@@ -1811,13 +2186,18 @@ module ActiveRecord #:nodoc:
1811
2186
  self.id = connection.next_sequence_value(self.class.sequence_name)
1812
2187
  end
1813
2188
 
1814
- self.id = connection.insert(
1815
- "INSERT INTO #{self.class.table_name} " +
2189
+ quoted_attributes = attributes_with_quotes
2190
+
2191
+ statement = if quoted_attributes.empty?
2192
+ connection.empty_insert_statement(self.class.table_name)
2193
+ else
2194
+ "INSERT INTO #{self.class.quoted_table_name} " +
1816
2195
  "(#{quoted_column_names.join(', ')}) " +
1817
- "VALUES(#{attributes_with_quotes.values.join(', ')})",
1818
- "#{self.class.name} Create",
1819
- self.class.primary_key, self.id, self.class.sequence_name
1820
- )
2196
+ "VALUES(#{quoted_attributes.values.join(', ')})"
2197
+ end
2198
+
2199
+ self.id = connection.insert(statement, "#{self.class.name} Create",
2200
+ self.class.primary_key, self.id, self.class.sequence_name)
1821
2201
 
1822
2202
  @new_record = false
1823
2203
  id
@@ -1833,189 +2213,42 @@ module ActiveRecord #:nodoc:
1833
2213
  end
1834
2214
  end
1835
2215
 
1836
-
1837
- # Allows access to the object attributes, which are held in the @attributes hash, as were
1838
- # they first-class methods. So a Person class with a name attribute can use Person#name and
1839
- # Person#name= and never directly use the attributes hash -- except for multiple assigns with
1840
- # ActiveRecord#attributes=. A Milestone class can also ask Milestone#completed? to test that
1841
- # the completed attribute is not nil or 0.
1842
- #
1843
- # It's also possible to instantiate related objects, so a Client class belonging to the clients
1844
- # table with a master_id foreign key can instantiate master through Client#master.
1845
- def method_missing(method_id, *args, &block)
1846
- method_name = method_id.to_s
1847
- if @attributes.include?(method_name) or
1848
- (md = /\?$/.match(method_name) and
1849
- @attributes.include?(query_method_name = md.pre_match) and
1850
- method_name = query_method_name)
1851
- define_read_methods if self.class.read_methods.empty? && self.class.generate_read_methods
1852
- md ? query_attribute(method_name) : read_attribute(method_name)
1853
- elsif self.class.primary_key.to_s == method_name
1854
- id
1855
- elsif md = self.class.match_attribute_method?(method_name)
1856
- attribute_name, method_type = md.pre_match, md.to_s
1857
- if @attributes.include?(attribute_name)
1858
- __send__("attribute#{method_type}", attribute_name, *args, &block)
1859
- else
1860
- super
1861
- end
1862
- else
1863
- super
2216
+ def convert_number_column_value(value)
2217
+ case value
2218
+ when FalseClass; 0
2219
+ when TrueClass; 1
2220
+ when ''; nil
2221
+ else value
1864
2222
  end
1865
2223
  end
1866
2224
 
1867
- # Returns the value of the attribute identified by <tt>attr_name</tt> after it has been typecast (for example,
1868
- # "2004-12-12" in a data column is cast to a date object, like Date.new(2004, 12, 12)).
1869
- def read_attribute(attr_name)
1870
- attr_name = attr_name.to_s
1871
- if !(value = @attributes[attr_name]).nil?
1872
- if column = column_for_attribute(attr_name)
1873
- if unserializable_attribute?(attr_name, column)
1874
- unserialize_attribute(attr_name)
1875
- else
1876
- column.type_cast(value)
1877
- end
2225
+ def remove_attributes_protected_from_mass_assignment(attributes)
2226
+ safe_attributes =
2227
+ if self.class.accessible_attributes.nil? && self.class.protected_attributes.nil?
2228
+ attributes.reject { |key, value| attributes_protected_by_default.include?(key.gsub(/\(.+/, "")) }
2229
+ elsif self.class.protected_attributes.nil?
2230
+ attributes.reject { |key, value| !self.class.accessible_attributes.include?(key.gsub(/\(.+/, "")) || attributes_protected_by_default.include?(key.gsub(/\(.+/, "")) }
2231
+ elsif self.class.accessible_attributes.nil?
2232
+ attributes.reject { |key, value| self.class.protected_attributes.include?(key.gsub(/\(.+/,"")) || attributes_protected_by_default.include?(key.gsub(/\(.+/, "")) }
1878
2233
  else
1879
- value
1880
- end
1881
- else
1882
- nil
1883
- end
1884
- end
1885
-
1886
- def read_attribute_before_type_cast(attr_name)
1887
- @attributes[attr_name]
1888
- end
1889
-
1890
- # Called on first read access to any given column and generates reader
1891
- # methods for all columns in the columns_hash if
1892
- # ActiveRecord::Base.generate_read_methods is set to true.
1893
- def define_read_methods
1894
- self.class.columns_hash.each do |name, column|
1895
- unless respond_to_without_attributes?(name)
1896
- if self.class.serialized_attributes[name]
1897
- define_read_method_for_serialized_attribute(name)
1898
- else
1899
- define_read_method(name.to_sym, name, column)
1900
- end
1901
- end
1902
-
1903
- unless respond_to_without_attributes?("#{name}?")
1904
- define_question_method(name)
1905
- end
1906
- end
1907
- end
1908
-
1909
- # Define an attribute reader method. Cope with nil column.
1910
- def define_read_method(symbol, attr_name, column)
1911
- cast_code = column.type_cast_code('v') if column
1912
- access_code = cast_code ? "(v=@attributes['#{attr_name}']) && #{cast_code}" : "@attributes['#{attr_name}']"
1913
-
1914
- unless attr_name.to_s == self.class.primary_key.to_s
1915
- access_code = access_code.insert(0, "raise NoMethodError, 'missing attribute: #{attr_name}', caller unless @attributes.has_key?('#{attr_name}'); ")
1916
- self.class.read_methods << attr_name
1917
- end
1918
-
1919
- evaluate_read_method attr_name, "def #{symbol}; #{access_code}; end"
1920
- end
1921
-
1922
- # Define read method for serialized attribute.
1923
- def define_read_method_for_serialized_attribute(attr_name)
1924
- unless attr_name.to_s == self.class.primary_key.to_s
1925
- self.class.read_methods << attr_name
1926
- end
1927
-
1928
- evaluate_read_method attr_name, "def #{attr_name}; unserialize_attribute('#{attr_name}'); end"
1929
- end
1930
-
1931
- # Define an attribute ? method.
1932
- def define_question_method(attr_name)
1933
- unless attr_name.to_s == self.class.primary_key.to_s
1934
- self.class.read_methods << "#{attr_name}?"
1935
- end
1936
-
1937
- evaluate_read_method attr_name, "def #{attr_name}?; query_attribute('#{attr_name}'); end"
1938
- end
1939
-
1940
- # Evaluate the definition for an attribute reader or ? method
1941
- def evaluate_read_method(attr_name, method_definition)
1942
- begin
1943
- self.class.class_eval(method_definition)
1944
- rescue SyntaxError => err
1945
- self.class.read_methods.delete(attr_name)
1946
- if logger
1947
- logger.warn "Exception occurred during reader method compilation."
1948
- logger.warn "Maybe #{attr_name} is not a valid Ruby identifier?"
1949
- logger.warn "#{err.message}"
2234
+ raise "Declare either attr_protected or attr_accessible for #{self.class}, but not both."
1950
2235
  end
1951
- end
1952
- end
1953
-
1954
- # Returns true if the attribute is of a text column and marked for serialization.
1955
- def unserializable_attribute?(attr_name, column)
1956
- column.text? && self.class.serialized_attributes[attr_name]
1957
- end
1958
2236
 
1959
- # Returns the unserialized object of the attribute.
1960
- def unserialize_attribute(attr_name)
1961
- unserialized_object = object_from_yaml(@attributes[attr_name])
2237
+ removed_attributes = attributes.keys - safe_attributes.keys
1962
2238
 
1963
- if unserialized_object.is_a?(self.class.serialized_attributes[attr_name]) || unserialized_object.nil?
1964
- @attributes[attr_name] = unserialized_object
1965
- else
1966
- raise SerializationTypeMismatch,
1967
- "#{attr_name} was supposed to be a #{self.class.serialized_attributes[attr_name]}, but was a #{unserialized_object.class.to_s}"
2239
+ if removed_attributes.any?
2240
+ logger.debug "WARNING: Can't mass-assign these protected attributes: #{removed_attributes.join(', ')}"
1968
2241
  end
1969
- end
1970
-
1971
- # Updates the attribute identified by <tt>attr_name</tt> with the specified +value+. Empty strings for fixnum and float
1972
- # columns are turned into nil.
1973
- def write_attribute(attr_name, value)
1974
- attr_name = attr_name.to_s
1975
- if (column = column_for_attribute(attr_name)) && column.number?
1976
- @attributes[attr_name] = convert_number_column_value(value)
1977
- else
1978
- @attributes[attr_name] = value
1979
- end
1980
- end
1981
2242
 
1982
- def convert_number_column_value(value)
1983
- case value
1984
- when FalseClass: 0
1985
- when TrueClass: 1
1986
- when '': nil
1987
- else value
1988
- end
2243
+ safe_attributes
1989
2244
  end
1990
2245
 
1991
- def query_attribute(attr_name)
1992
- attribute = @attributes[attr_name]
1993
- if attribute.kind_of?(Fixnum) && attribute == 0
1994
- false
1995
- elsif attribute.kind_of?(String) && attribute == "0"
1996
- false
1997
- elsif attribute.kind_of?(String) && attribute.empty?
1998
- false
1999
- elsif attribute.nil?
2000
- false
2001
- elsif attribute == false
2002
- false
2003
- elsif attribute == "f"
2004
- false
2005
- elsif attribute == "false"
2006
- false
2246
+ # Removes attributes which have been marked as readonly.
2247
+ def remove_readonly_attributes(attributes)
2248
+ unless self.class.readonly_attributes.nil?
2249
+ attributes.delete_if { |key, value| self.class.readonly_attributes.include?(key.gsub(/\(.+/,"")) }
2007
2250
  else
2008
- true
2009
- end
2010
- end
2011
-
2012
- def remove_attributes_protected_from_mass_assignment(attributes)
2013
- if self.class.accessible_attributes.nil? && self.class.protected_attributes.nil?
2014
- attributes.reject { |key, value| attributes_protected_by_default.include?(key.gsub(/\(.+/, "")) }
2015
- elsif self.class.protected_attributes.nil?
2016
- attributes.reject { |key, value| !self.class.accessible_attributes.include?(key.gsub(/\(.+/, "").intern) || attributes_protected_by_default.include?(key.gsub(/\(.+/, "")) }
2017
- elsif self.class.accessible_attributes.nil?
2018
- attributes.reject { |key, value| self.class.protected_attributes.include?(key.gsub(/\(.+/,"").intern) || attributes_protected_by_default.include?(key.gsub(/\(.+/, "")) }
2251
+ attributes
2019
2252
  end
2020
2253
  end
2021
2254
 
@@ -2026,15 +2259,16 @@ module ActiveRecord #:nodoc:
2026
2259
  default
2027
2260
  end
2028
2261
 
2029
- # Returns copy of the attributes hash where all the values have been safely quoted for use in
2262
+ # Returns a copy of the attributes hash where all the values have been safely quoted for use in
2030
2263
  # an SQL statement.
2031
- def attributes_with_quotes(include_primary_key = true)
2032
- attributes.inject({}) do |quoted, (name, value)|
2264
+ def attributes_with_quotes(include_primary_key = true, include_readonly_attributes = true)
2265
+ quoted = attributes.inject({}) do |quoted, (name, value)|
2033
2266
  if column = column_for_attribute(name)
2034
2267
  quoted[name] = quote_value(value, column) unless !include_primary_key && column.primary
2035
2268
  end
2036
2269
  quoted
2037
2270
  end
2271
+ include_readonly_attributes ? quoted : remove_readonly_attributes(quoted)
2038
2272
  end
2039
2273
 
2040
2274
  # Quote strings appropriately for SQL statements.
@@ -2042,13 +2276,6 @@ module ActiveRecord #:nodoc:
2042
2276
  self.class.connection.quote(value, column)
2043
2277
  end
2044
2278
 
2045
- # Deprecated, use quote_value
2046
- def quote(value, column = nil)
2047
- self.class.connection.quote(value, column)
2048
- end
2049
- deprecate :quote => :quote_value
2050
-
2051
-
2052
2279
  # Interpolate custom sql string in instance context.
2053
2280
  # Optional record argument is meant for custom insert_sql.
2054
2281
  def interpolate_sql(sql, record = nil)
@@ -2071,7 +2298,7 @@ module ActiveRecord #:nodoc:
2071
2298
  # So having the pairs written_on(1) = "2004", written_on(2) = "6", written_on(3) = "24", will instantiate
2072
2299
  # written_on (a date type) with Date.new("2004", "6", "24"). You can also specify a typecast character in the
2073
2300
  # parentheses to have the parameters typecasted before they're used in the constructor. Use i for Fixnum, f for Float,
2074
- # s for String, and a for Array. If all the values for a given attribute is empty, the attribute will be set to nil.
2301
+ # s for String, and a for Array. If all the values for a given attribute are empty, the attribute will be set to nil.
2075
2302
  def assign_multiparameter_attributes(pairs)
2076
2303
  execute_callstack_for_multiparameter_attributes(
2077
2304
  extract_callstack_for_multiparameter_attributes(pairs)
@@ -2134,6 +2361,10 @@ module ActiveRecord #:nodoc:
2134
2361
  end
2135
2362
  end
2136
2363
 
2364
+ def self.quoted_table_name
2365
+ self.connection.quote_table_name(self.table_name)
2366
+ end
2367
+
2137
2368
  def quote_columns(quoter, hash)
2138
2369
  hash.inject({}) do |quoted, (name, value)|
2139
2370
  quoted[quoter.quote_column_name(name)] = value
@@ -2159,13 +2390,7 @@ module ActiveRecord #:nodoc:
2159
2390
 
2160
2391
  def clone_attribute_value(reader_method, attribute_name)
2161
2392
  value = send(reader_method, attribute_name)
2162
-
2163
- case value
2164
- when nil, Fixnum, true, false
2165
- value
2166
- else
2167
- value.clone
2168
- end
2393
+ value.duplicable? ? value.clone : value
2169
2394
  rescue TypeError, NoMethodError
2170
2395
  value
2171
2396
  end