activerecord 1.0.0 → 3.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 (178) hide show
  1. data/CHANGELOG +5518 -76
  2. data/README.rdoc +222 -0
  3. data/examples/performance.rb +162 -0
  4. data/examples/simple.rb +14 -0
  5. data/lib/active_record/aggregations.rb +192 -80
  6. data/lib/active_record/association_preload.rb +403 -0
  7. data/lib/active_record/associations/association_collection.rb +545 -53
  8. data/lib/active_record/associations/association_proxy.rb +295 -0
  9. data/lib/active_record/associations/belongs_to_association.rb +91 -0
  10. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +78 -0
  11. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +127 -36
  12. data/lib/active_record/associations/has_many_association.rb +108 -84
  13. data/lib/active_record/associations/has_many_through_association.rb +116 -0
  14. data/lib/active_record/associations/has_one_association.rb +143 -0
  15. data/lib/active_record/associations/has_one_through_association.rb +40 -0
  16. data/lib/active_record/associations/through_association_scope.rb +154 -0
  17. data/lib/active_record/associations.rb +2086 -368
  18. data/lib/active_record/attribute_methods/before_type_cast.rb +33 -0
  19. data/lib/active_record/attribute_methods/dirty.rb +95 -0
  20. data/lib/active_record/attribute_methods/primary_key.rb +50 -0
  21. data/lib/active_record/attribute_methods/query.rb +39 -0
  22. data/lib/active_record/attribute_methods/read.rb +116 -0
  23. data/lib/active_record/attribute_methods/time_zone_conversion.rb +61 -0
  24. data/lib/active_record/attribute_methods/write.rb +37 -0
  25. data/lib/active_record/attribute_methods.rb +60 -0
  26. data/lib/active_record/autosave_association.rb +369 -0
  27. data/lib/active_record/base.rb +1603 -721
  28. data/lib/active_record/callbacks.rb +176 -225
  29. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +365 -0
  30. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +113 -0
  31. data/lib/active_record/connection_adapters/abstract/database_limits.rb +57 -0
  32. data/lib/active_record/connection_adapters/abstract/database_statements.rb +329 -0
  33. data/lib/active_record/connection_adapters/abstract/query_cache.rb +81 -0
  34. data/lib/active_record/connection_adapters/abstract/quoting.rb +72 -0
  35. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +739 -0
  36. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +543 -0
  37. data/lib/active_record/connection_adapters/abstract_adapter.rb +165 -279
  38. data/lib/active_record/connection_adapters/mysql_adapter.rb +594 -82
  39. data/lib/active_record/connection_adapters/postgresql_adapter.rb +988 -135
  40. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +53 -0
  41. data/lib/active_record/connection_adapters/sqlite_adapter.rb +365 -71
  42. data/lib/active_record/counter_cache.rb +115 -0
  43. data/lib/active_record/dynamic_finder_match.rb +53 -0
  44. data/lib/active_record/dynamic_scope_match.rb +32 -0
  45. data/lib/active_record/errors.rb +172 -0
  46. data/lib/active_record/fixtures.rb +941 -105
  47. data/lib/active_record/locale/en.yml +40 -0
  48. data/lib/active_record/locking/optimistic.rb +172 -0
  49. data/lib/active_record/locking/pessimistic.rb +55 -0
  50. data/lib/active_record/log_subscriber.rb +48 -0
  51. data/lib/active_record/migration.rb +617 -0
  52. data/lib/active_record/named_scope.rb +138 -0
  53. data/lib/active_record/nested_attributes.rb +417 -0
  54. data/lib/active_record/observer.rb +105 -36
  55. data/lib/active_record/persistence.rb +291 -0
  56. data/lib/active_record/query_cache.rb +36 -0
  57. data/lib/active_record/railtie.rb +91 -0
  58. data/lib/active_record/railties/controller_runtime.rb +38 -0
  59. data/lib/active_record/railties/databases.rake +512 -0
  60. data/lib/active_record/reflection.rb +364 -87
  61. data/lib/active_record/relation/batches.rb +89 -0
  62. data/lib/active_record/relation/calculations.rb +286 -0
  63. data/lib/active_record/relation/finder_methods.rb +355 -0
  64. data/lib/active_record/relation/predicate_builder.rb +41 -0
  65. data/lib/active_record/relation/query_methods.rb +261 -0
  66. data/lib/active_record/relation/spawn_methods.rb +112 -0
  67. data/lib/active_record/relation.rb +393 -0
  68. data/lib/active_record/schema.rb +59 -0
  69. data/lib/active_record/schema_dumper.rb +195 -0
  70. data/lib/active_record/serialization.rb +60 -0
  71. data/lib/active_record/serializers/xml_serializer.rb +244 -0
  72. data/lib/active_record/session_store.rb +340 -0
  73. data/lib/active_record/test_case.rb +67 -0
  74. data/lib/active_record/timestamp.rb +88 -0
  75. data/lib/active_record/transactions.rb +329 -75
  76. data/lib/active_record/validations/associated.rb +48 -0
  77. data/lib/active_record/validations/uniqueness.rb +185 -0
  78. data/lib/active_record/validations.rb +58 -179
  79. data/lib/active_record/version.rb +9 -0
  80. data/lib/active_record.rb +100 -24
  81. data/lib/rails/generators/active_record/migration/migration_generator.rb +25 -0
  82. data/lib/rails/generators/active_record/migration/templates/migration.rb +17 -0
  83. data/lib/rails/generators/active_record/model/model_generator.rb +38 -0
  84. data/lib/rails/generators/active_record/model/templates/migration.rb +16 -0
  85. data/lib/rails/generators/active_record/model/templates/model.rb +5 -0
  86. data/lib/rails/generators/active_record/model/templates/module.rb +5 -0
  87. data/lib/rails/generators/active_record/observer/observer_generator.rb +15 -0
  88. data/lib/rails/generators/active_record/observer/templates/observer.rb +2 -0
  89. data/lib/rails/generators/active_record/session_migration/session_migration_generator.rb +24 -0
  90. data/lib/rails/generators/active_record/session_migration/templates/migration.rb +16 -0
  91. data/lib/rails/generators/active_record.rb +27 -0
  92. metadata +216 -158
  93. data/README +0 -361
  94. data/RUNNING_UNIT_TESTS +0 -36
  95. data/dev-utils/eval_debugger.rb +0 -9
  96. data/examples/associations.rb +0 -87
  97. data/examples/shared_setup.rb +0 -15
  98. data/examples/validation.rb +0 -88
  99. data/install.rb +0 -60
  100. data/lib/active_record/deprecated_associations.rb +0 -70
  101. data/lib/active_record/support/class_attribute_accessors.rb +0 -43
  102. data/lib/active_record/support/class_inheritable_attributes.rb +0 -37
  103. data/lib/active_record/support/clean_logger.rb +0 -10
  104. data/lib/active_record/support/inflector.rb +0 -70
  105. data/lib/active_record/vendor/mysql.rb +0 -1117
  106. data/lib/active_record/vendor/simple.rb +0 -702
  107. data/lib/active_record/wrappers/yaml_wrapper.rb +0 -15
  108. data/lib/active_record/wrappings.rb +0 -59
  109. data/rakefile +0 -122
  110. data/test/abstract_unit.rb +0 -16
  111. data/test/aggregations_test.rb +0 -34
  112. data/test/all.sh +0 -8
  113. data/test/associations_test.rb +0 -477
  114. data/test/base_test.rb +0 -513
  115. data/test/class_inheritable_attributes_test.rb +0 -33
  116. data/test/connections/native_mysql/connection.rb +0 -24
  117. data/test/connections/native_postgresql/connection.rb +0 -24
  118. data/test/connections/native_sqlite/connection.rb +0 -24
  119. data/test/deprecated_associations_test.rb +0 -336
  120. data/test/finder_test.rb +0 -67
  121. data/test/fixtures/accounts/signals37 +0 -3
  122. data/test/fixtures/accounts/unknown +0 -2
  123. data/test/fixtures/auto_id.rb +0 -4
  124. data/test/fixtures/column_name.rb +0 -3
  125. data/test/fixtures/companies/first_client +0 -6
  126. data/test/fixtures/companies/first_firm +0 -4
  127. data/test/fixtures/companies/second_client +0 -6
  128. data/test/fixtures/company.rb +0 -37
  129. data/test/fixtures/company_in_module.rb +0 -33
  130. data/test/fixtures/course.rb +0 -3
  131. data/test/fixtures/courses/java +0 -2
  132. data/test/fixtures/courses/ruby +0 -2
  133. data/test/fixtures/customer.rb +0 -30
  134. data/test/fixtures/customers/david +0 -6
  135. data/test/fixtures/db_definitions/mysql.sql +0 -96
  136. data/test/fixtures/db_definitions/mysql2.sql +0 -4
  137. data/test/fixtures/db_definitions/postgresql.sql +0 -113
  138. data/test/fixtures/db_definitions/postgresql2.sql +0 -4
  139. data/test/fixtures/db_definitions/sqlite.sql +0 -85
  140. data/test/fixtures/db_definitions/sqlite2.sql +0 -4
  141. data/test/fixtures/default.rb +0 -2
  142. data/test/fixtures/developer.rb +0 -8
  143. data/test/fixtures/developers/david +0 -2
  144. data/test/fixtures/developers/jamis +0 -2
  145. data/test/fixtures/developers_projects/david_action_controller +0 -2
  146. data/test/fixtures/developers_projects/david_active_record +0 -2
  147. data/test/fixtures/developers_projects/jamis_active_record +0 -2
  148. data/test/fixtures/entrant.rb +0 -3
  149. data/test/fixtures/entrants/first +0 -3
  150. data/test/fixtures/entrants/second +0 -3
  151. data/test/fixtures/entrants/third +0 -3
  152. data/test/fixtures/fixture_database.sqlite +0 -0
  153. data/test/fixtures/fixture_database_2.sqlite +0 -0
  154. data/test/fixtures/movie.rb +0 -5
  155. data/test/fixtures/movies/first +0 -2
  156. data/test/fixtures/movies/second +0 -2
  157. data/test/fixtures/project.rb +0 -3
  158. data/test/fixtures/projects/action_controller +0 -2
  159. data/test/fixtures/projects/active_record +0 -2
  160. data/test/fixtures/reply.rb +0 -21
  161. data/test/fixtures/subscriber.rb +0 -5
  162. data/test/fixtures/subscribers/first +0 -2
  163. data/test/fixtures/subscribers/second +0 -2
  164. data/test/fixtures/topic.rb +0 -20
  165. data/test/fixtures/topics/first +0 -9
  166. data/test/fixtures/topics/second +0 -8
  167. data/test/fixtures_test.rb +0 -20
  168. data/test/inflector_test.rb +0 -104
  169. data/test/inheritance_test.rb +0 -125
  170. data/test/lifecycle_test.rb +0 -110
  171. data/test/modules_test.rb +0 -21
  172. data/test/multiple_db_test.rb +0 -46
  173. data/test/pk_test.rb +0 -57
  174. data/test/reflection_test.rb +0 -78
  175. data/test/thread_safety_test.rb +0 -33
  176. data/test/transactions_test.rb +0 -83
  177. data/test/unconnected_test.rb +0 -24
  178. data/test/validations_test.rb +0 -126
@@ -1,924 +1,1781 @@
1
- require 'active_record/support/class_attribute_accessors'
2
- require 'active_record/support/class_inheritable_attributes'
3
- require 'active_record/support/inflector'
4
1
  require 'yaml'
2
+ require 'set'
3
+ require 'active_support/benchmarkable'
4
+ require 'active_support/dependencies'
5
+ require 'active_support/descendants_tracker'
6
+ require 'active_support/time'
7
+ require 'active_support/core_ext/class/attribute'
8
+ require 'active_support/core_ext/class/attribute_accessors'
9
+ require 'active_support/core_ext/class/delegating_attributes'
10
+ require 'active_support/core_ext/class/inheritable_attributes'
11
+ require 'active_support/core_ext/array/extract_options'
12
+ require 'active_support/core_ext/hash/deep_merge'
13
+ require 'active_support/core_ext/hash/indifferent_access'
14
+ require 'active_support/core_ext/hash/slice'
15
+ require 'active_support/core_ext/string/behavior'
16
+ require 'active_support/core_ext/kernel/singleton_class'
17
+ require 'active_support/core_ext/module/delegation'
18
+ require 'active_support/core_ext/module/deprecation'
19
+ require 'active_support/core_ext/module/introspection'
20
+ require 'active_support/core_ext/object/duplicable'
21
+ require 'active_support/core_ext/object/blank'
22
+ require 'arel'
23
+ require 'active_record/errors'
24
+ require 'active_record/log_subscriber'
5
25
 
6
26
  module ActiveRecord #:nodoc:
7
- class ActiveRecordError < StandardError #:nodoc:
8
- end
9
- class AssociationTypeMismatch < ActiveRecordError #:nodoc:
10
- end
11
- class SerializationTypeMismatch < ActiveRecordError #:nodoc:
12
- end
13
- class AdapterNotSpecified < ActiveRecordError # :nodoc:
14
- end
15
- class AdapterNotFound < ActiveRecordError # :nodoc:
16
- end
17
- class ConnectionNotEstablished < ActiveRecordError #:nodoc:
18
- end
19
- class ConnectionFailed < ActiveRecordError #:nodoc:
20
- end
21
- class RecordNotFound < ActiveRecordError #:nodoc:
22
- end
23
- class StatementInvalid < ActiveRecordError #:nodoc:
24
- end
25
-
26
- # Active Record objects doesn't specify their attributes directly, but rather infer them from the table definition with
27
- # which they're linked. Adding, removing, and changing attributes and their type is done directly in the database. Any change
28
- # is instantly reflected in the Active Record objects. The mapping that binds a given Active Record class to a certain
29
- # database table will happen automatically in most common cases, but can be overwritten for the uncommon ones.
30
- #
31
- # See the mapping rules in table_name and the full example in link:files/README.html for more insight.
32
- #
27
+ # = Active Record
28
+ #
29
+ # Active Record objects don't specify their attributes directly, but rather infer them from
30
+ # the table definition with which they're linked. Adding, removing, and changing attributes
31
+ # and their type is done directly in the database. Any change is instantly reflected in the
32
+ # Active Record objects. The mapping that binds a given Active Record class to a certain
33
+ # database table will happen automatically in most common cases, but can be overwritten for the uncommon ones.
34
+ #
35
+ # See the mapping rules in table_name and the full example in link:files/activerecord/README_rdoc.html for more insight.
36
+ #
33
37
  # == Creation
34
- #
35
- # Active Records accepts constructor parameters either in a hash or as a block. The hash method is especially useful when
36
- # you're receiving the data from somewhere else, like a HTTP request. It works like this:
37
- #
38
- # user = User.new("name" => "David", "occupation" => "Code Artist")
38
+ #
39
+ # Active Records accept constructor parameters either in a hash or as a block. The hash
40
+ # method is especially useful when you're receiving the data from somewhere else, like an
41
+ # HTTP request. It works like this:
42
+ #
43
+ # user = User.new(:name => "David", :occupation => "Code Artist")
39
44
  # user.name # => "David"
40
- #
45
+ #
41
46
  # You can also use block initialization:
42
- #
47
+ #
43
48
  # user = User.new do |u|
44
49
  # u.name = "David"
45
50
  # u.occupation = "Code Artist"
46
51
  # end
47
- #
52
+ #
48
53
  # And of course you can just create a bare object and specify the attributes after the fact:
49
- #
54
+ #
50
55
  # user = User.new
51
56
  # user.name = "David"
52
57
  # user.occupation = "Code Artist"
53
- #
58
+ #
54
59
  # == Conditions
55
- #
56
- # Conditions can either be specified as a string or an array representing the WHERE-part of an SQL statement.
60
+ #
61
+ # Conditions can either be specified as a string, array, or hash representing the WHERE-part of an SQL statement.
57
62
  # The array form is to be used when the condition input is tainted and requires sanitization. The string form can
58
- # be used for statements that doesn't involve tainted data. Examples:
59
- #
60
- # User < ActiveRecord::Base
63
+ # be used for statements that don't involve tainted data. The hash form works much like the array form, except
64
+ # only equality and range is possible. Examples:
65
+ #
66
+ # class User < ActiveRecord::Base
61
67
  # def self.authenticate_unsafely(user_name, password)
62
- # find_first("user_name = '#{user_name}' AND password = '#{password}'")
68
+ # where("user_name = '#{user_name}' AND password = '#{password}'").first
63
69
  # end
64
- #
70
+ #
65
71
  # def self.authenticate_safely(user_name, password)
66
- # find_first([ "user_name = '%s' AND password = '%s'", user_name, password ])
72
+ # where("user_name = ? AND password = ?", user_name, password).first
73
+ # end
74
+ #
75
+ # def self.authenticate_safely_simply(user_name, password)
76
+ # where(:user_name => user_name, :password => password).first
67
77
  # end
68
78
  # end
69
- #
70
- # The +authenticate_unsafely+ method inserts the parameters directly into the query and is thus susceptible to SQL-injection
71
- # attacks if the +user_name+ and +password+ parameters come directly from a HTTP request. The +authenticate_safely+ method, on
72
- # the other hand, will sanitize the +user_name+ and +password+ before inserting them in the query, which will ensure that
73
- # an attacker can't escape the query and fake the login (or worse).
74
- #
79
+ #
80
+ # The <tt>authenticate_unsafely</tt> method inserts the parameters directly into the query
81
+ # and is thus susceptible to SQL-injection attacks if the <tt>user_name</tt> and +password+
82
+ # parameters come directly from an HTTP request. The <tt>authenticate_safely</tt> and
83
+ # <tt>authenticate_safely_simply</tt> both will sanitize the <tt>user_name</tt> and +password+
84
+ # before inserting them in the query, which will ensure that an attacker can't escape the
85
+ # query and fake the login (or worse).
86
+ #
87
+ # When using multiple parameters in the conditions, it can easily become hard to read exactly
88
+ # what the fourth or fifth question mark is supposed to represent. In those cases, you can
89
+ # resort to named bind variables instead. That's done by replacing the question marks with
90
+ # symbols and supplying a hash with values for the matching symbol keys:
91
+ #
92
+ # Company.where(
93
+ # "id = :id AND name = :name AND division = :division AND created_at > :accounting_date",
94
+ # { :id => 3, :name => "37signals", :division => "First", :accounting_date => '2005-01-01' }
95
+ # ).first
96
+ #
97
+ # Similarly, a simple hash without a statement will generate conditions based on equality with the SQL AND
98
+ # operator. For instance:
99
+ #
100
+ # Student.where(:first_name => "Harvey", :status => 1)
101
+ # Student.where(params[:student])
102
+ #
103
+ # A range may be used in the hash to use the SQL BETWEEN operator:
104
+ #
105
+ # Student.where(:grade => 9..12)
106
+ #
107
+ # An array may be used in the hash to use the SQL IN operator:
108
+ #
109
+ # Student.where(:grade => [9,11,12])
110
+ #
111
+ # When joining tables, nested hashes or keys written in the form 'table_name.column_name'
112
+ # can be used to qualify the table name of a particular condition. For instance:
113
+ #
114
+ # Student.joins(:schools).where(:schools => { :type => 'public' })
115
+ # Student.joins(:schools).where('schools.type' => 'public' )
116
+ #
75
117
  # == Overwriting default accessors
76
- #
77
- # All column values are automatically available through basic accessors on the Active Record object, but some times you
78
- # want to specialize this behavior. This can be done by either by overwriting the default accessors (using the same
79
- # name as the attribute) calling read_attribute(attr_name) and write_attribute(attr_name, value) to actually change things.
80
- # Example:
81
- #
118
+ #
119
+ # All column values are automatically available through basic accessors on the Active Record
120
+ # object, but sometimes you want to specialize this behavior. This can be done by overwriting
121
+ # the default accessors (using the same name as the attribute) and calling
122
+ # <tt>read_attribute(attr_name)</tt> and <tt>write_attribute(attr_name, value)</tt> to actually
123
+ # change things.
124
+ #
82
125
  # class Song < ActiveRecord::Base
83
126
  # # Uses an integer of seconds to hold the length of the song
84
- #
127
+ #
85
128
  # def length=(minutes)
86
- # write_attribute("length", minutes * 60)
129
+ # write_attribute(:length, minutes.to_i * 60)
87
130
  # end
88
- #
131
+ #
89
132
  # def length
90
- # read_attribute("length") / 60
133
+ # read_attribute(:length) / 60
91
134
  # end
92
135
  # end
93
- #
94
- # == Saving arrays, hashes, and other non-mappeable objects in text columns
95
- #
96
- # 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+.
97
- # This makes it possible to store arrays, hashes, and other non-mappeable objects without doing any additional work. Example:
98
- #
136
+ #
137
+ # You can alternatively use <tt>self[:attribute]=(value)</tt> and <tt>self[:attribute]</tt>
138
+ # instead of <tt>write_attribute(:attribute, value)</tt> and <tt>read_attribute(:attribute)</tt>.
139
+ #
140
+ # == Attribute query methods
141
+ #
142
+ # In addition to the basic accessors, query methods are also automatically available on the Active Record object.
143
+ # Query methods allow you to test whether an attribute value is present.
144
+ #
145
+ # For example, an Active Record User with the <tt>name</tt> attribute has a <tt>name?</tt> method that you can call
146
+ # to determine whether the user has a name:
147
+ #
148
+ # user = User.new(:name => "David")
149
+ # user.name? # => true
150
+ #
151
+ # anonymous = User.new(:name => "")
152
+ # anonymous.name? # => false
153
+ #
154
+ # == Accessing attributes before they have been typecasted
155
+ #
156
+ # Sometimes you want to be able to read the raw attribute data without having the column-determined
157
+ # typecast run its course first. That can be done by using the <tt><attribute>_before_type_cast</tt>
158
+ # accessors that all attributes have. For example, if your Account model has a <tt>balance</tt> attribute,
159
+ # you can call <tt>account.balance_before_type_cast</tt> or <tt>account.id_before_type_cast</tt>.
160
+ #
161
+ # This is especially useful in validation situations where the user might supply a string for an
162
+ # integer field and you want to display the original string back in an error message. Accessing the
163
+ # attribute normally would typecast the string to 0, which isn't what you want.
164
+ #
165
+ # == Dynamic attribute-based finders
166
+ #
167
+ # Dynamic attribute-based finders are a cleaner way of getting (and/or creating) objects
168
+ # by simple queries without turning to SQL. They work by appending the name of an attribute
169
+ # to <tt>find_by_</tt>, <tt>find_last_by_</tt>, or <tt>find_all_by_</tt> and thus produces finders
170
+ # like <tt>Person.find_by_user_name</tt>, <tt>Person.find_all_by_last_name</tt>, and
171
+ # <tt>Payment.find_by_transaction_id</tt>. Instead of writing
172
+ # <tt>Person.where(:user_name => user_name).first</tt>, you just do <tt>Person.find_by_user_name(user_name)</tt>.
173
+ # And instead of writing <tt>Person.where(:last_name => last_name).all</tt>, you just do
174
+ # <tt>Person.find_all_by_last_name(last_name)</tt>.
175
+ #
176
+ # It's also possible to use multiple attributes in the same find by separating them with "_and_".
177
+ #
178
+ # Person.where(:user_name => user_name, :password => password).first
179
+ # Person.find_by_user_name_and_password #with dynamic finder
180
+ #
181
+ # Person.where(:user_name => user_name, :password => password, :gender => 'male').first
182
+ # Payment.find_by_user_name_and_password_and_gender
183
+ #
184
+ # It's even possible to call these dynamic finder methods on relations and named scopes.
185
+ #
186
+ # Payment.order("created_on").find_all_by_amount(50)
187
+ # Payment.pending.find_last_by_amount(100)
188
+ #
189
+ # The same dynamic finder style can be used to create the object if it doesn't already exist.
190
+ # This dynamic finder is called with <tt>find_or_create_by_</tt> and will return the object if
191
+ # it already exists and otherwise creates it, then returns it. Protected attributes won't be set
192
+ # unless they are given in a block.
193
+ #
194
+ # # No 'Summer' tag exists
195
+ # Tag.find_or_create_by_name("Summer") # equal to Tag.create(:name => "Summer")
196
+ #
197
+ # # Now the 'Summer' tag does exist
198
+ # Tag.find_or_create_by_name("Summer") # equal to Tag.find_by_name("Summer")
199
+ #
200
+ # # Now 'Bob' exist and is an 'admin'
201
+ # User.find_or_create_by_name('Bob', :age => 40) { |u| u.admin = true }
202
+ #
203
+ # Use the <tt>find_or_initialize_by_</tt> finder if you want to return a new record without
204
+ # saving it first. Protected attributes won't be set unless they are given in a block.
205
+ #
206
+ # # No 'Winter' tag exists
207
+ # winter = Tag.find_or_initialize_by_name("Winter")
208
+ # winter.new_record? # true
209
+ #
210
+ # To find by a subset of the attributes to be used for instantiating a new object, pass a hash instead of
211
+ # a list of parameters.
212
+ #
213
+ # Tag.find_or_create_by_name(:name => "rails", :creator => current_user)
214
+ #
215
+ # That will either find an existing tag named "rails", or create a new one while setting the
216
+ # user that created it.
217
+ #
218
+ # Just like <tt>find_by_*</tt>, you can also use <tt>scoped_by_*</tt> to retrieve data. The good thing about
219
+ # using this feature is that the very first time result is returned using <tt>method_missing</tt> technique
220
+ # but after that the method is declared on the class. Henceforth <tt>method_missing</tt> will not be hit.
221
+ #
222
+ # User.scoped_by_user_name('David')
223
+ #
224
+ # == Saving arrays, hashes, and other non-mappable objects in text columns
225
+ #
226
+ # Active Record can serialize any object in text columns using YAML. To do so, you must
227
+ # specify this with a call to the class method +serialize+.
228
+ # This makes it possible to store arrays, hashes, and other non-mappable objects without doing
229
+ # any additional work.
230
+ #
99
231
  # class User < ActiveRecord::Base
100
232
  # serialize :preferences
101
233
  # end
102
- #
103
- # user = User.create("preferences" => { "background" => "black", "display" => large })
234
+ #
235
+ # user = User.create(:preferences => { "background" => "black", "display" => large })
104
236
  # User.find(user.id).preferences # => { "background" => "black", "display" => large }
105
- #
106
- # You can also specify an optional :class_name option that'll raise an exception if a serialized object is retrieved as a
107
- # descendent of a class not in the hierarchy. Example:
108
- #
237
+ #
238
+ # You can also specify a class option as the second parameter that'll raise an exception
239
+ # if a serialized object is retrieved as a descendant of a class not in the hierarchy.
240
+ #
109
241
  # class User < ActiveRecord::Base
110
- # serialize :preferences, :class_name => "Hash"
242
+ # serialize :preferences, Hash
111
243
  # end
112
- #
113
- # user = User.create("preferences" => %w( one two three ))
114
- # User.find(user.id).preferences # => raises SerializationTypeMismatch
115
- #
244
+ #
245
+ # user = User.create(:preferences => %w( one two three ))
246
+ # User.find(user.id).preferences # raises SerializationTypeMismatch
247
+ #
116
248
  # == Single table inheritance
117
249
  #
118
- # Active Record allows inheritance by storing the name of the class in a column that by default is called "type" (can be changed
119
- # by overwriting <tt>Base.inheritance_column</tt>). This means that an inheritance looking like this:
250
+ # Active Record allows inheritance by storing the name of the class in a column that by
251
+ # default is named "type" (can be changed by overwriting <tt>Base.inheritance_column</tt>).
252
+ # This means that an inheritance looking like this:
120
253
  #
121
254
  # class Company < ActiveRecord::Base; end
122
255
  # class Firm < Company; end
123
256
  # class Client < Company; end
124
257
  # class PriorityClient < Client; end
125
258
  #
126
- # When you do Firm.create("name" => "37signals"), this record with be saved in the companies table with type = "Firm". You can then
127
- # fetch this row again using Company.find_first "name = '37signals'" and it will return a Firm object.
259
+ # When you do <tt>Firm.create(:name => "37signals")</tt>, this record will be saved in
260
+ # the companies table with type = "Firm". You can then fetch this row again using
261
+ # <tt>Company.where(:name => '37signals').first</tt> and it will return a Firm object.
262
+ #
263
+ # If you don't have a type column defined in your table, single-table inheritance won't
264
+ # be triggered. In that case, it'll work just like normal subclasses with no special magic
265
+ # for differentiating between them or reloading the right type with find.
128
266
  #
129
267
  # Note, all the attributes for all the cases are kept in the same table. Read more:
130
268
  # http://www.martinfowler.com/eaaCatalog/singleTableInheritance.html
131
- #
269
+ #
132
270
  # == Connection to multiple databases in different models
133
271
  #
134
- # Connections are usually created through ActiveRecord::Base.establish_connection and retrieved by ActiveRecord::Base.connection.
135
- # All classes inheriting from ActiveRecord::Base will use this connection. But you can also set a class-specific connection.
136
- # For example, if Course is a ActiveRecord::Base, but resides in a different database you can just say Course.establish_connection
137
- # and Course *and all its subclasses* will use this connection instead.
272
+ # Connections are usually created through ActiveRecord::Base.establish_connection and retrieved
273
+ # by ActiveRecord::Base.connection. All classes inheriting from ActiveRecord::Base will use this
274
+ # connection. But you can also set a class-specific connection. For example, if Course is an
275
+ # ActiveRecord::Base, but resides in a different database, you can just say <tt>Course.establish_connection</tt>
276
+ # and Course and all of its subclasses will use this connection instead.
138
277
  #
139
- # This feature is implemented by keeping a connection pool in ActiveRecord::Base that is a Hash indexed by the class. If a connection is
140
- # requested, the retrieve_connection method will go up the class-hierarchy until a connection is found in the connection pool.
278
+ # This feature is implemented by keeping a connection pool in ActiveRecord::Base that is
279
+ # a Hash indexed by the class. If a connection is requested, the retrieve_connection method
280
+ # will go up the class-hierarchy until a connection is found in the connection pool.
141
281
  #
142
282
  # == Exceptions
143
- #
144
- # * +ActiveRecordError+ -- generic error class and superclass of all other errors raised by Active Record
145
- # * +AdapterNotSpecified+ -- the configuration hash used in <tt>establish_connection</tt> didn't include a
283
+ #
284
+ # * ActiveRecordError - Generic error class and superclass of all other errors raised by Active Record.
285
+ # * AdapterNotSpecified - The configuration hash used in <tt>establish_connection</tt> didn't include an
146
286
  # <tt>:adapter</tt> key.
147
- # * +AdapterNotSpecified+ -- the <tt>:adapter</tt> key used in <tt>establish_connection</tt> specified an unexisting adapter
148
- # (or a bad spelling of an existing one).
149
- # * +AssociationTypeMismatch+ -- the object assigned to the association wasn't of the type specified in the association definition.
150
- # * +SerializationTypeMismatch+ -- the object serialized wasn't of the class specified in the <tt>:class_name</tt> option of
151
- # the serialize definition.
152
- # * +ConnectionNotEstablished+ -- no connection has been established. Use <tt>establish_connection</tt> before querying.
153
- # * +RecordNotFound+ -- no record responded to the find* method.
154
- # Either the row with the given ID doesn't exist or the row didn't meet the additional restrictions.
155
- # * +StatementInvalid+ -- the database server rejected the SQL statement. The precise error is added in the message.
156
- # Either the record with the given ID doesn't exist or the record didn't meet the additional restrictions.
157
- #
158
- # *Note*: The attributes listed are class-level attributes (accessible from both the class and instance level).
159
- # So it's possible to assign a logger to the class through Base.logger= which will then be used by all
287
+ # * AdapterNotFound - The <tt>:adapter</tt> key used in <tt>establish_connection</tt> specified a
288
+ # non-existent adapter
289
+ # (or a bad spelling of an existing one).
290
+ # * AssociationTypeMismatch - The object assigned to the association wasn't of the type
291
+ # specified in the association definition.
292
+ # * SerializationTypeMismatch - The serialized object wasn't of the class specified as the second parameter.
293
+ # * ConnectionNotEstablished+ - No connection has been established. Use <tt>establish_connection</tt>
294
+ # before querying.
295
+ # * RecordNotFound - No record responded to the +find+ method. Either the row with the given ID doesn't exist
296
+ # or the row didn't meet the additional restrictions. Some +find+ calls do not raise this exception to signal
297
+ # nothing was found, please check its documentation for further details.
298
+ # * StatementInvalid - The database server rejected the SQL statement. The precise error is added in the message.
299
+ # * MultiparameterAssignmentErrors - Collection of errors that occurred during a mass assignment using the
300
+ # <tt>attributes=</tt> method. The +errors+ property of this exception contains an array of
301
+ # AttributeAssignmentError
302
+ # objects that should be inspected to determine which attributes triggered the errors.
303
+ # * AttributeAssignmentError - An error occurred while doing a mass assignment through the
304
+ # <tt>attributes=</tt> method.
305
+ # You can inspect the +attribute+ property of the exception object to determine which attribute
306
+ # triggered the error.
307
+ #
308
+ # *Note*: The attributes listed are class-level attributes (accessible from both the class and instance level).
309
+ # So it's possible to assign a logger to the class through <tt>Base.logger=</tt> which will then be used by all
160
310
  # instances in the current object space.
161
311
  class Base
162
- include ClassInheritableAttributes
163
-
164
- # Accepts a logger conforming to the interface of Log4r or the default Ruby 1.8+ Logger class, which is then passed
165
- # on to any new database connections made and which can be retrieved on both a class and instance level by calling +logger+.
166
- cattr_accessor :logger
167
-
168
- # Returns the connection currently associated with the class. This can
169
- # also be used to "borrow" the connection to do database work unrelated
170
- # to any of the specific Active Records.
171
- def self.connection
172
- retrieve_connection
173
- end
312
+ ##
313
+ # :singleton-method:
314
+ # Accepts a logger conforming to the interface of Log4r or the default Ruby 1.8+ Logger class,
315
+ # which is then passed on to any new database connections made and which can be retrieved on both
316
+ # a class and instance level by calling +logger+.
317
+ cattr_accessor :logger, :instance_writer => false
174
318
 
175
- # Returns the connection currently associated with the class. This can
176
- # also be used to "borrow" the connection to do database work that isn't
177
- # easily done without going straight to SQL.
178
- def connection
179
- self.class.connection
180
- end
319
+ class << self
320
+ def reset_subclasses #:nodoc:
321
+ ActiveSupport::Deprecation.warn 'ActiveRecord::Base.reset_subclasses no longer does anything in Rails 3. It will be removed in the final release; please update your apps and plugins.', caller
322
+ end
323
+
324
+ def subclasses
325
+ descendants
326
+ end
181
327
 
182
- def self.inherited(child) #:nodoc:
183
- @@subclasses[self] ||= []
184
- @@subclasses[self] << child
185
- super
328
+ deprecate :subclasses => :descendants
186
329
  end
187
330
 
188
- @@subclasses = {}
189
-
190
- # Accessor for the prefix type that will be prepended to every primary key column name. The options are :table_name and
191
- # :table_name_with_underscore. If the first is specified, the Product class will look for "productid" instead of "id" as
192
- # the primary column. If the latter is specified, the Product class will look for "product_id" instead of "id". Remember
193
- # that this is a global setting for all Active Records.
194
- cattr_accessor :primary_key_prefix_type
331
+ ##
332
+ # :singleton-method:
333
+ # Contains the database configuration - as is typically stored in config/database.yml -
334
+ # as a Hash.
335
+ #
336
+ # For example, the following database.yml...
337
+ #
338
+ # development:
339
+ # adapter: sqlite3
340
+ # database: db/development.sqlite3
341
+ #
342
+ # production:
343
+ # adapter: sqlite3
344
+ # database: db/production.sqlite3
345
+ #
346
+ # ...would result in ActiveRecord::Base.configurations to look like this:
347
+ #
348
+ # {
349
+ # 'development' => {
350
+ # 'adapter' => 'sqlite3',
351
+ # 'database' => 'db/development.sqlite3'
352
+ # },
353
+ # 'production' => {
354
+ # 'adapter' => 'sqlite3',
355
+ # 'database' => 'db/production.sqlite3'
356
+ # }
357
+ # }
358
+ cattr_accessor :configurations, :instance_writer => false
359
+ @@configurations = {}
360
+
361
+ ##
362
+ # :singleton-method:
363
+ # Accessor for the prefix type that will be prepended to every primary key column name.
364
+ # The options are :table_name and :table_name_with_underscore. If the first is specified,
365
+ # the Product class will look for "productid" instead of "id" as the primary column. If the
366
+ # latter is specified, the Product class will look for "product_id" instead of "id". Remember
367
+ # that this is a global setting for all Active Records.
368
+ cattr_accessor :primary_key_prefix_type, :instance_writer => false
195
369
  @@primary_key_prefix_type = nil
196
370
 
197
- # Accessor for the name of the prefix string to prepend to every table name. So if set to "basecamp_", all
198
- # table names will be named like "basecamp_projects", "basecamp_people", etc. This is a convinient way of creating a namespace
199
- # for tables in a shared database. By default, the prefix is the empty string.
200
- cattr_accessor :table_name_prefix
201
- @@table_name_prefix = ""
202
-
371
+ ##
372
+ # :singleton-method:
373
+ # Accessor for the name of the prefix string to prepend to every table name. So if set
374
+ # to "basecamp_", all table names will be named like "basecamp_projects", "basecamp_people",
375
+ # etc. This is a convenient way of creating a namespace for tables in a shared database.
376
+ # By default, the prefix is the empty string.
377
+ #
378
+ # If you are organising your models within modules you can add a prefix to the models within
379
+ # a namespace by defining a singleton method in the parent module called table_name_prefix which
380
+ # returns your chosen prefix.
381
+ class_attribute :table_name_prefix, :instance_writer => false
382
+ self.table_name_prefix = ""
383
+
384
+ ##
385
+ # :singleton-method:
203
386
  # Works like +table_name_prefix+, but appends instead of prepends (set to "_basecamp" gives "projects_basecamp",
204
387
  # "people_basecamp"). By default, the suffix is the empty string.
205
- cattr_accessor :table_name_suffix
206
- @@table_name_suffix = ""
388
+ class_attribute :table_name_suffix, :instance_writer => false
389
+ self.table_name_suffix = ""
207
390
 
208
- # Indicate whether or not table names should be the pluralized versions of the corresponding class names.
209
- # If true, this the default table name for a +Product+ class will be +products+. If false, it would just be +product+.
391
+ ##
392
+ # :singleton-method:
393
+ # Indicates whether table names should be the pluralized versions of the corresponding class names.
394
+ # If true, the default table name for a Product class will be +products+. If false, it would just be +product+.
210
395
  # See table_name for the full rules on table/class naming. This is true, by default.
211
- cattr_accessor :pluralize_table_names
396
+ cattr_accessor :pluralize_table_names, :instance_writer => false
212
397
  @@pluralize_table_names = true
213
398
 
399
+ ##
400
+ # :singleton-method:
401
+ # Determines whether to use Time.local (using :local) or Time.utc (using :utc) when pulling
402
+ # dates and times from the database. This is set to :local by default.
403
+ cattr_accessor :default_timezone, :instance_writer => false
404
+ @@default_timezone = :local
405
+
406
+ ##
407
+ # :singleton-method:
408
+ # Specifies the format to use when dumping the database schema with Rails'
409
+ # Rakefile. If :sql, the schema is dumped as (potentially database-
410
+ # specific) SQL statements. If :ruby, the schema is dumped as an
411
+ # ActiveRecord::Schema file which can be loaded into any database that
412
+ # supports migrations. Use :ruby if you want to have different database
413
+ # adapters for, e.g., your development and test environments.
414
+ cattr_accessor :schema_format , :instance_writer => false
415
+ @@schema_format = :ruby
416
+
417
+ ##
418
+ # :singleton-method:
419
+ # Specify whether or not to use timestamps for migration versions
420
+ cattr_accessor :timestamped_migrations , :instance_writer => false
421
+ @@timestamped_migrations = true
422
+
423
+ # Determine whether to store the full constant name including namespace when using STI
424
+ superclass_delegating_accessor :store_full_sti_class
425
+ self.store_full_sti_class = true
426
+
427
+ # Stores the default scope for the class
428
+ class_inheritable_accessor :default_scoping, :instance_writer => false
429
+ self.default_scoping = []
430
+
214
431
  class << self # Class methods
215
- # Returns objects for the records responding to either a specific id (1), a list of ids (1, 5, 6) or an array of ids.
216
- # If only one ID is specified, that object is returned directly. If more than one ID is specified, an array is returned.
217
- # Examples:
218
- # Person.find(1) # returns the object for ID = 1
219
- # Person.find(1, 2, 6) # returns an array for objects with IDs in (1, 2, 6)
220
- # Person.find([7, 17]) # returns an array for objects with IDs in (7, 17)
221
- # +RecordNotFound+ is raised if no record can be found.
222
- def find(*ids)
223
- ids = [ ids ].flatten.compact
224
-
225
- if ids.length > 1
226
- ids_list = ids.map{ |id| "'#{sanitize(id)}'" }.join(", ")
227
- objects = find_all("#{primary_key} IN (#{ids_list})", primary_key)
228
-
229
- if objects.length == ids.length
230
- return objects
231
- else
232
- raise RecordNotFound, "Couldn't find #{name} with ID in (#{ids_list})"
233
- end
234
- elsif ids.length == 1
235
- id = ids.first
236
- sql = "SELECT * FROM #{table_name} WHERE #{primary_key} = '#{sanitize(id)}'"
237
- sql << " AND #{type_condition}" unless descents_from_active_record?
238
-
239
- if record = connection.select_one(sql, "#{name} Find")
240
- instantiate(record)
241
- else
242
- raise RecordNotFound, "Couldn't find #{name} with ID = #{id}"
243
- end
244
- else
245
- raise RecordNotFound, "Couldn't find #{name} without an ID"
246
- end
247
- end
248
-
249
- # Works like find, but the record matching +id+ must also meet the +conditions+.
250
- # +RecordNotFound+ is raised if no record can be found matching the +id+ or meeting the condition.
251
- # Example:
252
- # Person.find_on_conditions 5, "first_name LIKE '%dav%' AND last_name = 'heinemeier'"
253
- def find_on_conditions(id, conditions)
254
- find_first("#{primary_key} = '#{sanitize(id)}' AND #{sanitize_conditions(conditions)}") ||
255
- raise(RecordNotFound, "Couldn't find #{name} with #{primary_key} = #{id} on the condition of #{conditions}")
256
- end
257
-
258
- # Returns an array of all the objects that could be instantiated from the associated
259
- # table in the database. The +conditions+ can be used to narrow the selection of objects (WHERE-part),
260
- # such as by "color = 'red'", and arrangement of the selection can be done through +orderings+ (ORDER BY-part),
261
- # such as by "last_name, first_name DESC". A maximum of returned objects can be specified in +limit+. Example:
262
- # Project.find_all "category = 'accounts'", "last_accessed DESC", 15
263
- def find_all(conditions = nil, orderings = nil, limit = nil, joins = nil)
264
- sql = "SELECT * FROM #{table_name} "
265
- sql << "#{joins} " if joins
266
- add_conditions!(sql, conditions)
267
- sql << "ORDER BY #{orderings} " unless orderings.nil?
268
- sql << "LIMIT #{limit} " unless limit.nil?
269
-
270
- find_by_sql(sql)
271
- end
272
-
273
- # Works like find_all, but requires a complete SQL string. Example:
274
- # Post.find_by_sql "SELECT p.*, c.author FROM posts p, comments c WHERE p.id = c.post_id"
432
+ def colorize_logging(*args)
433
+ ActiveSupport::Deprecation.warn "ActiveRecord::Base.colorize_logging and " <<
434
+ "config.active_record.colorize_logging are deprecated. Please use " <<
435
+ "Rails::LogSubscriber.colorize_logging or config.colorize_logging instead", caller
436
+ end
437
+ alias :colorize_logging= :colorize_logging
438
+
439
+ delegate :find, :first, :last, :all, :destroy, :destroy_all, :exists?, :delete, :delete_all, :update, :update_all, :to => :scoped
440
+ delegate :find_each, :find_in_batches, :to => :scoped
441
+ delegate :select, :group, :order, :reorder, :limit, :joins, :where, :preload, :eager_load, :includes, :from, :lock, :readonly, :having, :create_with, :to => :scoped
442
+ delegate :count, :average, :minimum, :maximum, :sum, :calculate, :to => :scoped
443
+
444
+ # Executes a custom SQL query against your database and returns all the results. The results will
445
+ # be returned as an array with columns requested encapsulated as attributes of the model you call
446
+ # this method from. If you call <tt>Product.find_by_sql</tt> then the results will be returned in
447
+ # a Product object with the attributes you specified in the SQL query.
448
+ #
449
+ # If you call a complicated SQL query which spans multiple tables the columns specified by the
450
+ # SELECT will be attributes of the model, whether or not they are columns of the corresponding
451
+ # table.
452
+ #
453
+ # The +sql+ parameter is a full SQL query as a string. It will be called as is, there will be
454
+ # no database agnostic conversions performed. This should be a last resort because using, for example,
455
+ # MySQL specific terms will lock you to using that particular database engine or require you to
456
+ # change your call if you switch engines.
457
+ #
458
+ # ==== Examples
459
+ # # A simple SQL query spanning multiple tables
460
+ # Post.find_by_sql "SELECT p.title, c.author FROM posts p, comments c WHERE p.id = c.post_id"
461
+ # > [#<Post:0x36bff9c @attributes={"title"=>"Ruby Meetup", "first_name"=>"Quentin"}>, ...]
462
+ #
463
+ # # You can use the same string replacement techniques as you can with ActiveRecord#find
464
+ # Post.find_by_sql ["SELECT title FROM posts WHERE author = ? AND created > ?", author_id, start_date]
465
+ # > [#<Post:0x36bff9c @attributes={"first_name"=>"The Cheap Man Buys Twice"}>, ...]
275
466
  def find_by_sql(sql)
276
- connection.select_all(sql, "#{name} Load").inject([]) { |objects, record| objects << instantiate(record) }
277
- end
278
-
279
- # Returns the object for the first record responding to the conditions in +conditions+,
280
- # such as "group = 'master'". If more than one record is returned from the query, it's the first that'll
281
- # be used to create the object. In such cases, it might be beneficial to also specify
282
- # +orderings+, like "income DESC, name", to control exactly which record is to be used. Example:
283
- # Employee.find_first "income > 50000", "income DESC, name"
284
- def find_first(conditions = nil, orderings = nil)
285
- sql = "SELECT * FROM #{table_name} "
286
- add_conditions!(sql, conditions)
287
- sql << "ORDER BY #{orderings} " unless orderings.nil?
288
- sql << "LIMIT 1"
289
-
290
- record = connection.select_one(sql, "#{name} Load First")
291
- instantiate(record) unless record.nil?
292
- end
293
-
294
- # Creates an object, instantly saves it as a record (if the validation permits it), and returns it. If the save
295
- # fail under validations, the unsaved object is still returned.
296
- def create(attributes = nil)
297
- object = new(attributes)
298
- object.save
299
- object
300
- end
301
-
302
- # Finds the record from the passed +id+, instantly saves it with the passed +attributes+ (if the validation permits it),
303
- # and returns it. If the save fail under validations, the unsaved object is still returned.
304
- def update(id, attributes)
305
- object = find(id)
306
- object.attributes = attributes
307
- object.save
308
- object
309
- end
310
-
311
- # Updates all records with the SET-part of an SQL update statement in +updates+. A subset of the records can be selected
312
- # by specifying +conditions+. Example:
313
- # Billing.update_all "category = 'authorized', approved = 1", "author = 'David'"
314
- def update_all(updates, conditions = nil)
315
- sql = "UPDATE #{table_name} SET #{updates} "
316
- add_conditions!(sql, conditions)
317
- connection.update(sql, "#{name} Update")
318
- end
319
-
320
- # Destroys the objects for all the records that matches the +condition+ by instantiating each object and calling
321
- # the destroy method. Example:
322
- # Person.destroy_all "last_login < '2004-04-04'"
323
- def destroy_all(conditions = nil)
324
- find_all(conditions).each { |object| object.destroy }
325
- end
326
-
327
- # Deletes all the records that matches the +condition+ without instantiating the objects first (and hence not
328
- # calling the destroy method). Example:
329
- # Post.destroy_all "person_id = 5 AND (category = 'Something' OR category = 'Else')"
330
- def delete_all(conditions = nil)
331
- sql = "DELETE FROM #{table_name} "
332
- add_conditions!(sql, conditions)
333
- connection.delete(sql, "#{name} Delete all")
334
- end
335
-
336
- # Returns the number of records that meets the +conditions+. Zero is returned if no records match. Example:
337
- # Product.count "sales > 1"
338
- def count(conditions = nil)
339
- sql = "SELECT COUNT(*) FROM #{table_name} "
340
- add_conditions!(sql, conditions)
341
- count_by_sql(sql)
467
+ connection.select_all(sanitize_sql(sql), "#{name} Load").collect! { |record| instantiate(record) }
468
+ end
469
+
470
+ # Creates an object (or multiple objects) and saves it to the database, if validations pass.
471
+ # The resulting object is returned whether the object was saved successfully to the database or not.
472
+ #
473
+ # The +attributes+ parameter can be either be a Hash or an Array of Hashes. These Hashes describe the
474
+ # attributes on the objects that are to be created.
475
+ #
476
+ # ==== Examples
477
+ # # Create a single new object
478
+ # User.create(:first_name => 'Jamie')
479
+ #
480
+ # # Create an Array of new objects
481
+ # User.create([{ :first_name => 'Jamie' }, { :first_name => 'Jeremy' }])
482
+ #
483
+ # # Create a single object and pass it into a block to set other attributes.
484
+ # User.create(:first_name => 'Jamie') do |u|
485
+ # u.is_admin = false
486
+ # end
487
+ #
488
+ # # Creating an Array of new objects using a block, where the block is executed for each object:
489
+ # User.create([{ :first_name => 'Jamie' }, { :first_name => 'Jeremy' }]) do |u|
490
+ # u.is_admin = false
491
+ # end
492
+ def create(attributes = nil, &block)
493
+ if attributes.is_a?(Array)
494
+ attributes.collect { |attr| create(attr, &block) }
495
+ else
496
+ object = new(attributes)
497
+ yield(object) if block_given?
498
+ object.save
499
+ object
500
+ end
342
501
  end
343
502
 
344
503
  # Returns the result of an SQL statement that should only include a COUNT(*) in the SELECT part.
345
- # Product.count "SELECT COUNT(*) FROM sales s, customers c WHERE s.customer_id = c.id"
504
+ # The use of this method should be restricted to complicated SQL queries that can't be executed
505
+ # using the ActiveRecord::Calculations class methods. Look into those before using this.
506
+ #
507
+ # ==== Parameters
508
+ #
509
+ # * +sql+ - An SQL statement which should return a count query from the database, see the example below.
510
+ #
511
+ # ==== Examples
512
+ #
513
+ # Product.count_by_sql "SELECT COUNT(*) FROM sales s, customers c WHERE s.customer_id = c.id"
346
514
  def count_by_sql(sql)
347
- count = connection.select_one(sql, "#{name} Count").values.first
348
- return count ? count.to_i : 0
515
+ sql = sanitize_conditions(sql)
516
+ connection.select_value(sql, "#{name} Count").to_i
349
517
  end
350
-
351
- # Increments the specified counter by one. So <tt>DiscussionBoard.increment_counter("post_count",
352
- # discussion_board_id)</tt> would increment the "post_count" counter on the board responding to discussion_board_id.
353
- # This is used for caching aggregate values, so that they doesn't need to be computed every time. Especially important
354
- # for looping over a collection where each element require a number of aggregate values. Like the DiscussionBoard
355
- # that needs to list both the number of posts and comments.
356
- def increment_counter(counter_name, id)
357
- update_all "#{counter_name} = #{counter_name} + 1", "#{primary_key} = #{id}"
518
+
519
+ # Attributes listed as readonly will be used to create a new record but update operations will
520
+ # ignore these fields.
521
+ def attr_readonly(*attributes)
522
+ write_inheritable_attribute(:attr_readonly, Set.new(attributes.map { |a| a.to_s }) + (readonly_attributes || []))
358
523
  end
359
524
 
360
- # Works like increment_counter, but decrements instead.
361
- def decrement_counter(counter_name, id)
362
- update_all "#{counter_name} = #{counter_name} - 1", "#{primary_key} = #{id}"
525
+ # Returns an array of all the attributes that have been specified as readonly.
526
+ def readonly_attributes
527
+ read_inheritable_attribute(:attr_readonly) || []
363
528
  end
364
529
 
365
- # Attributes named in this macro are protected from mass-assignment, such as <tt>new(attributes)</tt> and
366
- # <tt>attributes=(attributes)</tt>. Their assignment will simply be ignored. Instead, you can use the direct writer
367
- # methods to do assignment. This is meant to protect sensitive attributes to be overwritten by URL/form hackers. Example:
530
+ # If you have an attribute that needs to be saved to the database as an object, and retrieved as the same object,
531
+ # then specify the name of that attribute using this method and it will be handled automatically.
532
+ # The serialization is done through YAML. If +class_name+ is specified, the serialized object must be of that
533
+ # class on retrieval or SerializationTypeMismatch will be raised.
534
+ #
535
+ # ==== Parameters
368
536
  #
369
- # class Customer < ActiveRecord::Base
370
- # attr_protected :credit_rating
537
+ # * +attr_name+ - The field name that should be serialized.
538
+ # * +class_name+ - Optional, class name that the object type should be equal to.
539
+ #
540
+ # ==== Example
541
+ # # Serialize a preferences attribute
542
+ # class User
543
+ # serialize :preferences
371
544
  # end
545
+ def serialize(attr_name, class_name = Object)
546
+ serialized_attributes[attr_name.to_s] = class_name
547
+ end
548
+
549
+ # Returns a hash of all the attributes that have been specified for serialization as
550
+ # keys and their class restriction as values.
551
+ def serialized_attributes
552
+ read_inheritable_attribute(:attr_serialized) or write_inheritable_attribute(:attr_serialized, {})
553
+ end
554
+
555
+ # Guesses the table name (in forced lower-case) based on the name of the class in the
556
+ # inheritance hierarchy descending directly from ActiveRecord::Base. So if the hierarchy
557
+ # looks like: Reply < Message < ActiveRecord::Base, then Message is used
558
+ # to guess the table name even when called on Reply. The rules used to do the guess
559
+ # are handled by the Inflector class in Active Support, which knows almost all common
560
+ # English inflections. You can add new inflections in config/initializers/inflections.rb.
561
+ #
562
+ # Nested classes are given table names prefixed by the singular form of
563
+ # the parent's table name. Enclosing modules are not considered.
564
+ #
565
+ # ==== Examples
566
+ #
567
+ # class Invoice < ActiveRecord::Base; end;
568
+ # file class table_name
569
+ # invoice.rb Invoice invoices
570
+ #
571
+ # class Invoice < ActiveRecord::Base; class Lineitem < ActiveRecord::Base; end; end;
572
+ # file class table_name
573
+ # invoice.rb Invoice::Lineitem invoice_lineitems
574
+ #
575
+ # module Invoice; class Lineitem < ActiveRecord::Base; end; end;
576
+ # file class table_name
577
+ # invoice/lineitem.rb Invoice::Lineitem lineitems
372
578
  #
373
- # customer = Customer.new("name" => David, "credit_rating" => "Excellent")
374
- # customer.credit_rating # => nil
375
- # customer.attributes = { "description" => "Jolly fellow", "credit_rating" => "Superb" }
376
- # customer.credit_rating # => nil
579
+ # Additionally, the class-level +table_name_prefix+ is prepended and the
580
+ # +table_name_suffix+ is appended. So if you have "myapp_" as a prefix,
581
+ # the table name guess for an Invoice class becomes "myapp_invoices".
582
+ # Invoice::Lineitem becomes "myapp_invoice_lineitems".
377
583
  #
378
- # customer.credit_rating = "Average"
379
- # customer.credit_rating # => "Average"
380
- def attr_protected(*attributes)
381
- write_inheritable_array("attr_protected", attributes)
584
+ # You can also overwrite this class method to allow for unguessable
585
+ # links, such as a Mouse class with a link to a "mice" table. Example:
586
+ #
587
+ # class Mouse < ActiveRecord::Base
588
+ # set_table_name "mice"
589
+ # end
590
+ def table_name
591
+ reset_table_name
382
592
  end
383
-
384
- # Returns an array of all the attributes that have been protected from mass-assigment.
385
- def protected_attributes # :nodoc:
386
- read_inheritable_attribute("attr_protected")
593
+
594
+ # Returns a quoted version of the table name, used to construct SQL statements.
595
+ def quoted_table_name
596
+ @quoted_table_name ||= connection.quote_table_name(table_name)
387
597
  end
388
598
 
389
- # If this macro is used, only those attributed named in it will be accessible for mass-assignment, such as
390
- # <tt>new(attributes)</tt> and <tt>attributes=(attributes)</tt>. This is the more conservative choice for mass-assignment
391
- # protection. If you'd rather start from an all-open default and restrict attributes as needed, have a look at
392
- # attr_protected.
393
- def attr_accessible(*attributes)
394
- write_inheritable_array("attr_accessible", attributes)
599
+ # Computes the table name, (re)sets it internally, and returns it.
600
+ def reset_table_name #:nodoc:
601
+ self.table_name = compute_table_name
395
602
  end
396
-
397
- # Returns an array of all the attributes that have been made accessible to mass-assigment.
398
- def accessible_attributes # :nodoc:
399
- read_inheritable_attribute("attr_accessible")
603
+
604
+ def full_table_name_prefix #:nodoc:
605
+ (parents.detect{ |p| p.respond_to?(:table_name_prefix) } || self).table_name_prefix
400
606
  end
401
607
 
402
- # Specifies that the attribute by the name of +attr_name+ should be serialized before saving to the database and unserialized
403
- # after loading from the database. The serialization is done through YAML. If +class_name+ is specified, the serialized
404
- # object must be of that class on retrival or +SerializationTypeMismatch+ will be raised.
405
- def serialize(attr_name, class_name = Object)
406
- write_inheritable_attribute("attr_serialized", serialized_attributes.update(attr_name.to_s => class_name))
608
+ # Defines the column name for use with single table inheritance. Use
609
+ # <tt>set_inheritance_column</tt> to set a different value.
610
+ def inheritance_column
611
+ @inheritance_column ||= "type".freeze
407
612
  end
408
-
409
- # Returns a hash of all the attributes that have been specified for serialization as keys and their class restriction as values.
410
- def serialized_attributes
411
- read_inheritable_attribute("attr_serialized") || { }
613
+
614
+ # Lazy-set the sequence name to the connection's default. This method
615
+ # is only ever called once since set_sequence_name overrides it.
616
+ def sequence_name #:nodoc:
617
+ reset_sequence_name
618
+ end
619
+
620
+ def reset_sequence_name #:nodoc:
621
+ default = connection.default_sequence_name(table_name, primary_key)
622
+ set_sequence_name(default)
623
+ default
412
624
  end
413
625
 
414
- # Guesses the table name (in forced lower-case) based on the name of the class in the inheritance hierarchy descending
415
- # directly from ActiveRecord. So if the hierarchy looks like: Reply < Message < ActiveRecord, then Message is used
416
- # to guess the table name from even when called on Reply. The guessing rules are as follows:
626
+ # Sets the table name. If the value is nil or false then the value returned by the given
627
+ # block is used.
417
628
  #
418
- # * Class name ends in "x", "ch" or "ss": "es" is appended, so a Search class becomes a searches table.
419
- # * Class name ends in "y" preceded by a consonant or "qu": The "y" is replaced with "ies", so a Category class becomes a categories table.
420
- # * Class name ends in "fe": The "fe" is replaced with "ves", so a Wife class becomes a wives table.
421
- # * Class name ends in "lf" or "rf": The "f" is replaced with "ves", so a Half class becomes a halves table.
422
- # * Class name ends in "person": The "person" is replaced with "people", so a Salesperson class becomes a salespeople table.
423
- # * Class name ends in "man": The "man" is replaced with "men", so a Spokesman class becomes a spokesmen table.
424
- # * Class name ends in "sis": The "i" is replaced with an "e", so a Basis class becomes a bases table.
425
- # * Class name ends in "tum" or "ium": The "um" is replaced with an "a", so a Datum class becomes a data table.
426
- # * Class name ends in "child": The "child" is replaced with "children", so a NodeChild class becomes a node_children table.
427
- # * Class name ends in an "s": No additional characters are added or removed.
428
- # * Class name doesn't end in "s": An "s" is appended, so a Comment class becomes a comments table.
429
- # * Class name with word compositions: Compositions are underscored, so CreditCard class becomes a credit_cards table.
629
+ # class Project < ActiveRecord::Base
630
+ # set_table_name "project"
631
+ # end
632
+ def set_table_name(value = nil, &block)
633
+ @quoted_table_name = nil
634
+ define_attr_method :table_name, value, &block
635
+ end
636
+ alias :table_name= :set_table_name
637
+
638
+ # Sets the name of the inheritance column to use to the given value,
639
+ # or (if the value # is nil or false) to the value returned by the
640
+ # given block.
641
+ #
642
+ # class Project < ActiveRecord::Base
643
+ # set_inheritance_column do
644
+ # original_inheritance_column + "_id"
645
+ # end
646
+ # end
647
+ def set_inheritance_column(value = nil, &block)
648
+ define_attr_method :inheritance_column, value, &block
649
+ end
650
+ alias :inheritance_column= :set_inheritance_column
651
+
652
+ # Sets the name of the sequence to use when generating ids to the given
653
+ # value, or (if the value is nil or false) to the value returned by the
654
+ # given block. This is required for Oracle and is useful for any
655
+ # database which relies on sequences for primary key generation.
430
656
  #
431
- # Additionally, the class-level table_name_prefix is prepended to the table_name and the table_name_suffix is appended.
432
- # So if you have "myapp_" as a prefix, the table name guess for an Account class becomes "myapp_accounts".
657
+ # If a sequence name is not explicitly set when using Oracle or Firebird,
658
+ # it will default to the commonly used pattern of: #{table_name}_seq
433
659
  #
434
- # You can also overwrite this class method to allow for unguessable links, such as a Mouse class with a link to a
435
- # "mice" table. Example:
660
+ # If a sequence name is not explicitly set when using PostgreSQL, it
661
+ # will discover the sequence corresponding to your primary key for you.
436
662
  #
437
- # class Mouse < ActiveRecord::Base
438
- # def self.table_name() "mice" end
663
+ # class Project < ActiveRecord::Base
664
+ # set_sequence_name "projectseq" # default would have been "project_seq"
439
665
  # end
440
- def table_name(class_name = nil)
441
- if class_name.nil?
442
- class_name = class_name_of_active_record_descendant(self)
443
- table_name_prefix + undecorated_table_name(class_name) + table_name_suffix
666
+ def set_sequence_name(value = nil, &block)
667
+ define_attr_method :sequence_name, value, &block
668
+ end
669
+ alias :sequence_name= :set_sequence_name
670
+
671
+ # Indicates whether the table associated with this class exists
672
+ def table_exists?
673
+ connection.table_exists?(table_name)
674
+ end
675
+
676
+ # Returns an array of column objects for the table associated with this class.
677
+ def columns
678
+ unless defined?(@columns) && @columns
679
+ @columns = connection.columns(table_name, "#{name} Columns")
680
+ @columns.each { |column| column.primary = column.name == primary_key }
681
+ end
682
+ @columns
683
+ end
684
+
685
+ # Returns a hash of column objects for the table associated with this class.
686
+ def columns_hash
687
+ @columns_hash ||= columns.inject({}) { |hash, column| hash[column.name] = column; hash }
688
+ end
689
+
690
+ # Returns an array of column names as strings.
691
+ def column_names
692
+ @column_names ||= columns.map { |column| column.name }
693
+ end
694
+
695
+ # Returns an array of column objects where the primary id, all columns ending in "_id" or "_count",
696
+ # and columns used for single table inheritance have been removed.
697
+ def content_columns
698
+ @content_columns ||= columns.reject { |c| c.primary || c.name =~ /(_id|_count)$/ || c.name == inheritance_column }
699
+ end
700
+
701
+ # Returns a hash of all the methods added to query each of the columns in the table with the name of the method as the key
702
+ # and true as the value. This makes it possible to do O(1) lookups in respond_to? to check if a given method for attribute
703
+ # is available.
704
+ def column_methods_hash #:nodoc:
705
+ @dynamic_methods_hash ||= column_names.inject(Hash.new(false)) do |methods, attr|
706
+ attr_name = attr.to_s
707
+ methods[attr.to_sym] = attr_name
708
+ methods["#{attr}=".to_sym] = attr_name
709
+ methods["#{attr}?".to_sym] = attr_name
710
+ methods["#{attr}_before_type_cast".to_sym] = attr_name
711
+ methods
712
+ end
713
+ end
714
+
715
+ # Resets all the cached information about columns, which will cause them
716
+ # to be reloaded on the next request.
717
+ #
718
+ # The most common usage pattern for this method is probably in a migration,
719
+ # when just after creating a table you want to populate it with some default
720
+ # values, eg:
721
+ #
722
+ # class CreateJobLevels < ActiveRecord::Migration
723
+ # def self.up
724
+ # create_table :job_levels do |t|
725
+ # t.integer :id
726
+ # t.string :name
727
+ #
728
+ # t.timestamps
729
+ # end
730
+ #
731
+ # JobLevel.reset_column_information
732
+ # %w{assistant executive manager director}.each do |type|
733
+ # JobLevel.create(:name => type)
734
+ # end
735
+ # end
736
+ #
737
+ # def self.down
738
+ # drop_table :job_levels
739
+ # end
740
+ # end
741
+ def reset_column_information
742
+ undefine_attribute_methods
743
+ @column_names = @columns = @columns_hash = @content_columns = @dynamic_methods_hash = @inheritance_column = nil
744
+ @arel_engine = @relation = @arel_table = nil
745
+ end
746
+
747
+ def reset_column_information_and_inheritable_attributes_for_all_subclasses#:nodoc:
748
+ descendants.each { |klass| klass.reset_inheritable_attributes; klass.reset_column_information }
749
+ end
750
+
751
+ def attribute_method?(attribute)
752
+ super || (table_exists? && column_names.include?(attribute.to_s.sub(/=$/, '')))
753
+ end
754
+
755
+ # Set the lookup ancestors for ActiveModel.
756
+ def lookup_ancestors #:nodoc:
757
+ klass = self
758
+ classes = [klass]
759
+ while klass != klass.base_class
760
+ classes << klass = klass.superclass
761
+ end
762
+ classes
763
+ rescue
764
+ # OPTIMIZE this rescue is to fix this test: ./test/cases/reflection_test.rb:56:in `test_human_name_for_column'
765
+ # Apparently the method base_class causes some trouble.
766
+ # It now works for sure.
767
+ [self]
768
+ end
769
+
770
+ # Set the i18n scope to overwrite ActiveModel.
771
+ def i18n_scope #:nodoc:
772
+ :activerecord
773
+ end
774
+
775
+ # True if this isn't a concrete subclass needing a STI type condition.
776
+ def descends_from_active_record?
777
+ if superclass.abstract_class?
778
+ superclass.descends_from_active_record?
444
779
  else
445
- table_name_prefix + undecorated_table_name(class_name) + table_name_suffix
780
+ superclass == Base || !columns_hash.include?(inheritance_column)
446
781
  end
447
782
  end
448
783
 
449
- # Defines the primary key field -- can be overridden in subclasses. Overwritting will negate any effect of the
450
- # primary_key_prefix_type setting, though.
451
- def primary_key
452
- case primary_key_prefix_type
453
- when :table_name
454
- Inflector.foreign_key(class_name_of_active_record_descendant(self), false)
455
- when :table_name_with_underscore
456
- Inflector.foreign_key(class_name_of_active_record_descendant(self))
457
- else
458
- "id"
784
+ def finder_needs_type_condition? #:nodoc:
785
+ # This is like this because benchmarking justifies the strange :false stuff
786
+ :true == (@finder_needs_type_condition ||= descends_from_active_record? ? :false : :true)
787
+ end
788
+
789
+ # Returns a string like 'Post id:integer, title:string, body:text'
790
+ def inspect
791
+ if self == Base
792
+ super
793
+ elsif abstract_class?
794
+ "#{super}(abstract)"
795
+ elsif table_exists?
796
+ attr_list = columns.map { |c| "#{c.name}: #{c.type}" } * ', '
797
+ "#{super}(#{attr_list})"
798
+ else
799
+ "#{super}(Table doesn't exist)"
459
800
  end
460
801
  end
461
802
 
462
- # Defines the column name for use with single table inheritance -- can be overridden in subclasses.
463
- def inheritance_column
464
- "type"
803
+ def quote_value(value, column = nil) #:nodoc:
804
+ connection.quote(value,column)
465
805
  end
466
806
 
467
- # Turns the +table_name+ back into a class name following the reverse rules of +table_name+.
468
- def class_name(table_name = table_name) # :nodoc:
469
- # remove any prefix and/or suffix from the table name
470
- class_name = Inflector.camelize(table_name[table_name_prefix.length..-(table_name_suffix.length + 1)])
471
- class_name = Inflector.singularize(class_name) if pluralize_table_names
472
- return class_name
807
+ # Used to sanitize objects before they're used in an SQL SELECT statement. Delegates to <tt>connection.quote</tt>.
808
+ def sanitize(object) #:nodoc:
809
+ connection.quote(object)
473
810
  end
474
811
 
475
- # Returns an array of column objects for the table associated with this class.
476
- def columns
477
- @columns ||= connection.columns(table_name, "#{name} Columns")
812
+ # Overwrite the default class equality method to provide support for association proxies.
813
+ def ===(object)
814
+ object.is_a?(self)
478
815
  end
479
-
480
- # Returns an array of column objects for the table associated with this class.
481
- def columns_hash
482
- @columns_hash ||= columns.inject({}) { |hash, column| hash[column.name] = column; hash }
816
+
817
+ # Returns the base AR subclass that this class descends from. If A
818
+ # extends AR::Base, A.base_class will return A. If B descends from A
819
+ # through some arbitrarily deep hierarchy, B.base_class will return A.
820
+ #
821
+ # If B < A and C < B and if A is an abstract_class then both B.base_class
822
+ # and C.base_class would return B as the answer since A is an abstract_class.
823
+ def base_class
824
+ class_of_active_record_descendant(self)
483
825
  end
484
826
 
485
- # Returns an array of columns objects where the primary id, all columns ending in "_id" or "_count",
486
- # and columns used for single table inheritance has been removed.
487
- def content_columns
488
- columns.reject { |c| c.name == primary_key || c.name =~ /(_id|_count)$/ || c.name == inheritance_column }
827
+ # Set this to true if this is an abstract class (see <tt>abstract_class?</tt>).
828
+ attr_accessor :abstract_class
829
+
830
+ # Returns whether this class is an abstract class or not.
831
+ def abstract_class?
832
+ defined?(@abstract_class) && @abstract_class == true
833
+ end
834
+
835
+ def respond_to?(method_id, include_private = false)
836
+ if match = DynamicFinderMatch.match(method_id)
837
+ return true if all_attributes_exists?(match.attribute_names)
838
+ elsif match = DynamicScopeMatch.match(method_id)
839
+ return true if all_attributes_exists?(match.attribute_names)
840
+ end
841
+
842
+ super
489
843
  end
490
844
 
491
- # Transforms attribute key names into a more humane format, such as "First name" instead of "first_name". Example:
492
- # Person.human_attribute_name("first_name") # => "First name"
493
- def human_attribute_name(attribute_key_name)
494
- attribute_key_name.gsub(/_/, " ").capitalize unless attribute_key_name.nil?
845
+ def sti_name
846
+ store_full_sti_class ? name : name.demodulize
495
847
  end
496
-
497
- def descents_from_active_record? # :nodoc:
498
- superclass == Base
848
+
849
+ def arel_table
850
+ @arel_table ||= Arel::Table.new(table_name, arel_engine)
499
851
  end
500
852
 
501
- # Used to sanitize objects before they're used in an SELECT SQL-statement.
502
- def sanitize(object) # :nodoc:
503
- return object if Fixnum === object
504
- object.to_s.gsub(/([;:])/, "").gsub('##', '\#\#').gsub(/'/, "''") # ' (for ruby-mode)
853
+ def arel_engine
854
+ @arel_engine ||= begin
855
+ if self == ActiveRecord::Base
856
+ Arel::Table.engine
857
+ else
858
+ connection_handler.connection_pools[name] ? Arel::Sql::Engine.new(self) : superclass.arel_engine
859
+ end
860
+ end
505
861
  end
506
862
 
507
- # Used to aggregate logging and benchmark, so you can measure and represent multiple statements in a single block.
508
- # Usage (hides all the SQL calls for the individual actions and calculates total runtime for them all):
863
+ # Returns a scope for this class without taking into account the default_scope.
509
864
  #
510
- # Project.benchmark("Creating project") do
511
- # project = Project.create("name" => "stuff")
512
- # project.create_manager("name" => "David")
513
- # project.milestones << Milestone.find_all
865
+ # class Post < ActiveRecord::Base
866
+ # default_scope :published => true
514
867
  # end
515
- def benchmark(title)
516
- logger.level = Logger::ERROR
517
- bm = Benchmark.measure { yield }
518
- logger.level = Logger::DEBUG
519
- logger.info "#{title} (#{sprintf("%f", bm.real)})"
868
+ #
869
+ # Post.all # Fires "SELECT * FROM posts WHERE published = true"
870
+ # Post.unscoped.all # Fires "SELECT * FROM posts"
871
+ #
872
+ # This method also accepts a block meaning that all queries inside the block will
873
+ # not use the default_scope:
874
+ #
875
+ # Post.unscoped {
876
+ # limit(10) # Fires "SELECT * FROM posts LIMIT 10"
877
+ # }
878
+ #
879
+ # It is recommended to use block form of unscoped because chaining unscoped with <tt>named_scope</tt>
880
+ # does not work. Assuming that <tt>published</tt> is a <tt>named_scope</tt> following two statements are same.
881
+ #
882
+ # Post.unscoped.published
883
+ # Post.published
884
+ def unscoped #:nodoc:
885
+ block_given? ? relation.scoping { yield } : relation
886
+ end
887
+
888
+ def scoped_methods #:nodoc:
889
+ key = :"#{self}_scoped_methods"
890
+ Thread.current[key] = Thread.current[key].presence || self.default_scoping.dup
520
891
  end
521
892
 
522
893
  private
523
- # Finder methods must instantiate through this method to work with the single-table inheritance model
524
- # that makes it possible to create objects of different types from the same table.
525
- def instantiate(record)
526
- object = record_with_type?(record) ? compute_type(record[inheritance_column]).allocate : allocate
527
- object.instance_variable_set("@attributes", record)
528
- return object
894
+
895
+ def relation #:nodoc:
896
+ @relation ||= Relation.new(self, arel_table)
897
+ finder_needs_type_condition? ? @relation.where(type_condition) : @relation
529
898
  end
530
-
531
- # Returns true if the +record+ has a single table inheritance column and is using it.
532
- def record_with_type?(record)
533
- record.include?(inheritance_column) && !record[inheritance_column].nil? &&
534
- !record[inheritance_column].empty?
899
+
900
+ # Finder methods must instantiate through this method to work with the
901
+ # single-table inheritance model that makes it possible to create
902
+ # objects of different types from the same table.
903
+ def instantiate(record)
904
+ object = find_sti_class(record[inheritance_column]).allocate
905
+
906
+ object.instance_variable_set(:@attributes, record)
907
+ object.instance_variable_set(:@attributes_cache, {})
908
+ object.instance_variable_set(:@new_record, false)
909
+ object.instance_variable_set(:@readonly, false)
910
+ object.instance_variable_set(:@destroyed, false)
911
+ object.instance_variable_set(:@marked_for_destruction, false)
912
+ object.instance_variable_set(:@previously_changed, {})
913
+ object.instance_variable_set(:@changed_attributes, {})
914
+
915
+ object.send(:_run_find_callbacks)
916
+ object.send(:_run_initialize_callbacks)
917
+
918
+ object
535
919
  end
536
-
537
- # Returns the name of the type of the record using the current module as a prefix. So descendents of
538
- # MyApp::Business::Account would be appear as "MyApp::Business::AccountSubclass".
539
- def type_name_with_module(type_name)
540
- self.name =~ /::/ ? self.name.scan(/(.*)::/).first.first + "::" + type_name : type_name
920
+
921
+ def find_sti_class(type_name)
922
+ if type_name.blank? || !columns_hash.include?(inheritance_column)
923
+ self
924
+ else
925
+ begin
926
+ if store_full_sti_class
927
+ ActiveSupport::Dependencies.constantize(type_name)
928
+ else
929
+ compute_type(type_name)
930
+ end
931
+ rescue NameError
932
+ raise SubclassNotFound,
933
+ "The single-table inheritance mechanism failed to locate the subclass: '#{type_name}'. " +
934
+ "This error is raised because the column '#{inheritance_column}' is reserved for storing the class in case of inheritance. " +
935
+ "Please rename this column if you didn't intend it to be used for storing the inheritance class " +
936
+ "or overwrite #{name}.inheritance_column to use another column for that information."
937
+ end
938
+ end
541
939
  end
542
940
 
543
- # Adds a sanitized version of +conditions+ to the +sql+ string. Note that it's the passed +sql+ string is changed.
544
- def add_conditions!(sql, conditions)
545
- sql << "WHERE #{sanitize_conditions(conditions)} " unless conditions.nil?
546
- sql << (conditions.nil? ? "WHERE " : " AND ") + type_condition unless descents_from_active_record?
941
+ def construct_finder_arel(options = {}, scope = nil)
942
+ relation = options.is_a?(Hash) ? unscoped.apply_finder_options(options) : unscoped.merge(options)
943
+ relation = scope.merge(relation) if scope
944
+ relation
547
945
  end
548
-
946
+
549
947
  def type_condition
550
- " (" + subclasses.inject("#{inheritance_column} = '#{Inflector.demodulize(name)}' ") do |condition, subclass|
551
- condition << "OR #{inheritance_column} = '#{Inflector.demodulize(subclass.name)}' "
552
- end + ") "
948
+ sti_column = arel_table[inheritance_column]
949
+ condition = sti_column.eq(sti_name)
950
+ descendants.each { |subclass| condition = condition.or(sti_column.eq(subclass.sti_name)) }
951
+
952
+ condition
553
953
  end
554
954
 
555
955
  # Guesses the table name, but does not decorate it with prefix and suffix information.
556
- def undecorated_table_name(class_name = class_name_of_active_record_descendant(self))
557
- table_name = Inflector.underscore(Inflector.demodulize(class_name))
558
- table_name = Inflector.pluralize(table_name) if pluralize_table_names
559
- return table_name
956
+ def undecorated_table_name(class_name = base_class.name)
957
+ table_name = class_name.to_s.demodulize.underscore
958
+ table_name = table_name.pluralize if pluralize_table_names
959
+ table_name
960
+ end
961
+
962
+ # Computes and returns a table name according to default conventions.
963
+ def compute_table_name
964
+ base = base_class
965
+ if self == base
966
+ # Nested classes are prefixed with singular parent table name.
967
+ if parent < ActiveRecord::Base && !parent.abstract_class?
968
+ contained = parent.table_name
969
+ contained = contained.singularize if parent.pluralize_table_names
970
+ contained << '_'
971
+ end
972
+ "#{full_table_name_prefix}#{contained}#{undecorated_table_name(name)}#{table_name_suffix}"
973
+ else
974
+ # STI subclasses always use their superclass' table.
975
+ base.table_name
976
+ end
560
977
  end
561
978
 
979
+ # Enables dynamic finders like <tt>User.find_by_user_name(user_name)</tt> and
980
+ # <tt>User.scoped_by_user_name(user_name). Refer to Dynamic attribute-based finders
981
+ # section at the top of this file for more detailed information.
982
+ #
983
+ # It's even possible to use all the additional parameters to +find+. For example, the
984
+ # full interface for +find_all_by_amount+ is actually <tt>find_all_by_amount(amount, options)</tt>.
985
+ #
986
+ # Each dynamic finder using <tt>scoped_by_*</tt> is also defined in the class after it
987
+ # is first invoked, so that future attempts to use it do not run through method_missing.
988
+ def method_missing(method_id, *arguments, &block)
989
+ if match = DynamicFinderMatch.match(method_id)
990
+ attribute_names = match.attribute_names
991
+ super unless all_attributes_exists?(attribute_names)
992
+ if match.finder?
993
+ options = arguments.extract_options!
994
+ relation = options.any? ? construct_finder_arel(options, current_scoped_methods) : scoped
995
+ relation.send :find_by_attributes, match, attribute_names, *arguments
996
+ elsif match.instantiator?
997
+ scoped.send :find_or_instantiator_by_attributes, match, attribute_names, *arguments, &block
998
+ end
999
+ elsif match = DynamicScopeMatch.match(method_id)
1000
+ attribute_names = match.attribute_names
1001
+ super unless all_attributes_exists?(attribute_names)
1002
+ if match.scope?
1003
+ self.class_eval <<-METHOD, __FILE__, __LINE__ + 1
1004
+ def self.#{method_id}(*args) # def self.scoped_by_user_name_and_password(*args)
1005
+ options = args.extract_options! # options = args.extract_options!
1006
+ attributes = construct_attributes_from_arguments( # attributes = construct_attributes_from_arguments(
1007
+ [:#{attribute_names.join(',:')}], args # [:user_name, :password], args
1008
+ ) # )
1009
+ #
1010
+ scoped(:conditions => attributes) # scoped(:conditions => attributes)
1011
+ end # end
1012
+ METHOD
1013
+ send(method_id, *arguments)
1014
+ end
1015
+ else
1016
+ super
1017
+ end
1018
+ end
1019
+
1020
+ def construct_attributes_from_arguments(attribute_names, arguments)
1021
+ attributes = {}
1022
+ attribute_names.each_with_index { |name, idx| attributes[name] = arguments[idx] }
1023
+ attributes
1024
+ end
1025
+
1026
+ # Similar in purpose to +expand_hash_conditions_for_aggregates+.
1027
+ def expand_attribute_names_for_aggregates(attribute_names)
1028
+ expanded_attribute_names = []
1029
+ attribute_names.each do |attribute_name|
1030
+ unless (aggregation = reflect_on_aggregation(attribute_name.to_sym)).nil?
1031
+ aggregate_mapping(aggregation).each do |field_attr, aggregate_attr|
1032
+ expanded_attribute_names << field_attr
1033
+ end
1034
+ else
1035
+ expanded_attribute_names << attribute_name
1036
+ end
1037
+ end
1038
+ expanded_attribute_names
1039
+ end
1040
+
1041
+ def all_attributes_exists?(attribute_names)
1042
+ attribute_names = expand_attribute_names_for_aggregates(attribute_names)
1043
+ attribute_names.all? { |name| column_methods_hash.include?(name.to_sym) }
1044
+ end
562
1045
 
563
1046
  protected
564
- def subclasses
565
- @@subclasses[self] ||= []
566
- @@subclasses[self] + extra = @@subclasses[self].inject([]) {|list, subclass| list + subclass.subclasses }
1047
+ # with_scope lets you apply options to inner block incrementally. It takes a hash and the keys must be
1048
+ # <tt>:find</tt> or <tt>:create</tt>. <tt>:find</tt> parameter is <tt>Relation</tt> while
1049
+ # <tt>:create</tt> parameters are an attributes hash.
1050
+ #
1051
+ # class Article < ActiveRecord::Base
1052
+ # def self.create_with_scope
1053
+ # with_scope(:find => where(:blog_id => 1), :create => { :blog_id => 1 }) do
1054
+ # find(1) # => SELECT * from articles WHERE blog_id = 1 AND id = 1
1055
+ # a = create(1)
1056
+ # a.blog_id # => 1
1057
+ # end
1058
+ # end
1059
+ # end
1060
+ #
1061
+ # In nested scopings, all previous parameters are overwritten by the innermost rule, with the exception of
1062
+ # <tt>where</tt>, <tt>includes</tt>, and <tt>joins</tt> operations in <tt>Relation</tt>, which are merged.
1063
+ #
1064
+ # <tt>joins</tt> operations are uniqued so multiple scopes can join in the same table without table aliasing
1065
+ # problems. If you need to join multiple tables, but still want one of the tables to be uniqued, use the
1066
+ # array of strings format for your joins.
1067
+ #
1068
+ # class Article < ActiveRecord::Base
1069
+ # def self.find_with_scope
1070
+ # with_scope(:find => where(:blog_id => 1).limit(1), :create => { :blog_id => 1 }) do
1071
+ # with_scope(:find => limit(10)) do
1072
+ # all # => SELECT * from articles WHERE blog_id = 1 LIMIT 10
1073
+ # end
1074
+ # with_scope(:find => where(:author_id => 3)) do
1075
+ # all # => SELECT * from articles WHERE blog_id = 1 AND author_id = 3 LIMIT 1
1076
+ # end
1077
+ # end
1078
+ # end
1079
+ # end
1080
+ #
1081
+ # You can ignore any previous scopings by using the <tt>with_exclusive_scope</tt> method.
1082
+ #
1083
+ # class Article < ActiveRecord::Base
1084
+ # def self.find_with_exclusive_scope
1085
+ # with_scope(:find => where(:blog_id => 1).limit(1)) do
1086
+ # with_exclusive_scope(:find => limit(10)) do
1087
+ # all # => SELECT * from articles LIMIT 10
1088
+ # end
1089
+ # end
1090
+ # end
1091
+ # end
1092
+ #
1093
+ # *Note*: the +:find+ scope also has effect on update and deletion methods, like +update_all+ and +delete_all+.
1094
+ def with_scope(method_scoping = {}, action = :merge, &block)
1095
+ method_scoping = method_scoping.method_scoping if method_scoping.respond_to?(:method_scoping)
1096
+
1097
+ if method_scoping.is_a?(Hash)
1098
+ # Dup first and second level of hash (method and params).
1099
+ method_scoping = method_scoping.inject({}) do |hash, (method, params)|
1100
+ hash[method] = (params == true) ? params : params.dup
1101
+ hash
1102
+ end
1103
+
1104
+ method_scoping.assert_valid_keys([ :find, :create ])
1105
+ relation = construct_finder_arel(method_scoping[:find] || {})
1106
+
1107
+ if current_scoped_methods && current_scoped_methods.create_with_value && method_scoping[:create]
1108
+ scope_for_create = if action == :merge
1109
+ current_scoped_methods.create_with_value.merge(method_scoping[:create])
1110
+ else
1111
+ method_scoping[:create]
1112
+ end
1113
+
1114
+ relation = relation.create_with(scope_for_create)
1115
+ else
1116
+ scope_for_create = method_scoping[:create]
1117
+ scope_for_create ||= current_scoped_methods.create_with_value if current_scoped_methods
1118
+ relation = relation.create_with(scope_for_create) if scope_for_create
1119
+ end
1120
+
1121
+ method_scoping = relation
1122
+ end
1123
+
1124
+ method_scoping = current_scoped_methods.merge(method_scoping) if current_scoped_methods && action == :merge
1125
+
1126
+ self.scoped_methods << method_scoping
1127
+ begin
1128
+ yield
1129
+ ensure
1130
+ self.scoped_methods.pop
1131
+ end
1132
+ end
1133
+
1134
+ # Works like with_scope, but discards any nested properties.
1135
+ def with_exclusive_scope(method_scoping = {}, &block)
1136
+ if method_scoping.values.any? { |e| e.is_a?(ActiveRecord::Relation) }
1137
+ raise ArgumentError, <<-MSG
1138
+ New finder API can not be used with_exclusive_scope. You can either call unscoped to get an anonymous scope not bound to the default_scope:
1139
+
1140
+ User.unscoped.where(:active => true)
1141
+
1142
+ Or call unscoped with a block:
1143
+
1144
+ User.unscoped do
1145
+ User.where(:active => true).all
1146
+ end
1147
+
1148
+ MSG
1149
+ end
1150
+ with_scope(method_scoping, :overwrite, &block)
567
1151
  end
568
-
569
- # Returns the class type of the record using the current module as a prefix. So descendents of
570
- # MyApp::Business::Account would be appear as MyApp::Business::AccountSubclass.
1152
+
1153
+ # Sets the default options for the model. The format of the
1154
+ # <tt>options</tt> argument is the same as in find.
1155
+ #
1156
+ # class Person < ActiveRecord::Base
1157
+ # default_scope order('last_name, first_name')
1158
+ # end
1159
+ #
1160
+ # <tt>default_scope</tt> is also applied while creating/building a record. It is not
1161
+ # applied while updating a record.
1162
+ #
1163
+ # class Article < ActiveRecord::Base
1164
+ # default_scope where(:published => true)
1165
+ # end
1166
+ #
1167
+ # Article.new.published # => true
1168
+ # Article.create.published # => true
1169
+ def default_scope(options = {})
1170
+ self.default_scoping << construct_finder_arel(options, default_scoping.pop)
1171
+ end
1172
+
1173
+ def current_scoped_methods #:nodoc:
1174
+ scoped_methods.last
1175
+ end
1176
+
1177
+ # Returns the class type of the record using the current module as a prefix. So descendants of
1178
+ # MyApp::Business::Account would appear as MyApp::Business::AccountSubclass.
571
1179
  def compute_type(type_name)
572
- type_name_with_module(type_name).split("::").inject(Object) do |final_type, part|
573
- final_type = final_type.const_get(part)
1180
+ if type_name.match(/^::/)
1181
+ # If the type is prefixed with a scope operator then we assume that
1182
+ # the type_name is an absolute reference.
1183
+ ActiveSupport::Dependencies.constantize(type_name)
1184
+ else
1185
+ # Build a list of candidates to search for
1186
+ candidates = []
1187
+ name.scan(/::|$/) { candidates.unshift "#{$`}::#{type_name}" }
1188
+ candidates << type_name
1189
+
1190
+ candidates.each do |candidate|
1191
+ begin
1192
+ constant = ActiveSupport::Dependencies.constantize(candidate)
1193
+ return constant if candidate == constant.to_s
1194
+ rescue NameError => e
1195
+ # We don't want to swallow NoMethodError < NameError errors
1196
+ raise e unless e.instance_of?(NameError)
1197
+ rescue ArgumentError
1198
+ end
1199
+ end
1200
+
1201
+ raise NameError, "uninitialized constant #{candidates.first}"
574
1202
  end
575
1203
  end
576
1204
 
577
- # Returns the name of the class descending directly from ActiveRecord in the inheritance hierarchy.
578
- def class_name_of_active_record_descendant(klass)
579
- if klass.superclass == Base
580
- return klass.name
1205
+ # Returns the class descending directly from ActiveRecord::Base or an
1206
+ # abstract class, if any, in the inheritance hierarchy.
1207
+ def class_of_active_record_descendant(klass)
1208
+ if klass.superclass == Base || klass.superclass.abstract_class?
1209
+ klass
581
1210
  elsif klass.superclass.nil?
582
1211
  raise ActiveRecordError, "#{name} doesn't belong in a hierarchy descending from ActiveRecord"
583
1212
  else
584
- class_name_of_active_record_descendant(klass.superclass)
1213
+ class_of_active_record_descendant(klass.superclass)
1214
+ end
1215
+ end
1216
+
1217
+ # Accepts an array, hash, or string of SQL conditions and sanitizes
1218
+ # them into a valid SQL fragment for a WHERE clause.
1219
+ # ["name='%s' and group_id='%s'", "foo'bar", 4] returns "name='foo''bar' and group_id='4'"
1220
+ # { :name => "foo'bar", :group_id => 4 } returns "name='foo''bar' and group_id='4'"
1221
+ # "name='foo''bar' and group_id='4'" returns "name='foo''bar' and group_id='4'"
1222
+ def sanitize_sql_for_conditions(condition, table_name = self.table_name)
1223
+ return nil if condition.blank?
1224
+
1225
+ case condition
1226
+ when Array; sanitize_sql_array(condition)
1227
+ when Hash; sanitize_sql_hash_for_conditions(condition, table_name)
1228
+ else condition
1229
+ end
1230
+ end
1231
+ alias_method :sanitize_sql, :sanitize_sql_for_conditions
1232
+
1233
+ # Accepts an array, hash, or string of SQL conditions and sanitizes
1234
+ # them into a valid SQL fragment for a SET clause.
1235
+ # { :name => nil, :group_id => 4 } returns "name = NULL , group_id='4'"
1236
+ def sanitize_sql_for_assignment(assignments)
1237
+ case assignments
1238
+ when Array; sanitize_sql_array(assignments)
1239
+ when Hash; sanitize_sql_hash_for_assignment(assignments)
1240
+ else assignments
1241
+ end
1242
+ end
1243
+
1244
+ def aggregate_mapping(reflection)
1245
+ mapping = reflection.options[:mapping] || [reflection.name, reflection.name]
1246
+ mapping.first.is_a?(Array) ? mapping : [mapping]
1247
+ end
1248
+
1249
+ # Accepts a hash of SQL conditions and replaces those attributes
1250
+ # that correspond to a +composed_of+ relationship with their expanded
1251
+ # aggregate attribute values.
1252
+ # Given:
1253
+ # class Person < ActiveRecord::Base
1254
+ # composed_of :address, :class_name => "Address",
1255
+ # :mapping => [%w(address_street street), %w(address_city city)]
1256
+ # end
1257
+ # Then:
1258
+ # { :address => Address.new("813 abc st.", "chicago") }
1259
+ # # => { :address_street => "813 abc st.", :address_city => "chicago" }
1260
+ def expand_hash_conditions_for_aggregates(attrs)
1261
+ expanded_attrs = {}
1262
+ attrs.each do |attr, value|
1263
+ unless (aggregation = reflect_on_aggregation(attr.to_sym)).nil?
1264
+ mapping = aggregate_mapping(aggregation)
1265
+ mapping.each do |field_attr, aggregate_attr|
1266
+ if mapping.size == 1 && !value.respond_to?(aggregate_attr)
1267
+ expanded_attrs[field_attr] = value
1268
+ else
1269
+ expanded_attrs[field_attr] = value.send(aggregate_attr)
1270
+ end
1271
+ end
1272
+ else
1273
+ expanded_attrs[attr] = value
1274
+ end
1275
+ end
1276
+ expanded_attrs
1277
+ end
1278
+
1279
+ # Sanitizes a hash of attribute/value pairs into SQL conditions for a WHERE clause.
1280
+ # { :name => "foo'bar", :group_id => 4 }
1281
+ # # => "name='foo''bar' and group_id= 4"
1282
+ # { :status => nil, :group_id => [1,2,3] }
1283
+ # # => "status IS NULL and group_id IN (1,2,3)"
1284
+ # { :age => 13..18 }
1285
+ # # => "age BETWEEN 13 AND 18"
1286
+ # { 'other_records.id' => 7 }
1287
+ # # => "`other_records`.`id` = 7"
1288
+ # { :other_records => { :id => 7 } }
1289
+ # # => "`other_records`.`id` = 7"
1290
+ # And for value objects on a composed_of relationship:
1291
+ # { :address => Address.new("123 abc st.", "chicago") }
1292
+ # # => "address_street='123 abc st.' and address_city='chicago'"
1293
+ def sanitize_sql_hash_for_conditions(attrs, default_table_name = self.table_name)
1294
+ attrs = expand_hash_conditions_for_aggregates(attrs)
1295
+
1296
+ table = Arel::Table.new(self.table_name, :engine => arel_engine, :as => default_table_name)
1297
+ builder = PredicateBuilder.new(arel_engine)
1298
+ builder.build_from_hash(attrs, table).map{ |b| b.to_sql }.join(' AND ')
1299
+ end
1300
+ alias_method :sanitize_sql_hash, :sanitize_sql_hash_for_conditions
1301
+
1302
+ # Sanitizes a hash of attribute/value pairs into SQL conditions for a SET clause.
1303
+ # { :status => nil, :group_id => 1 }
1304
+ # # => "status = NULL , group_id = 1"
1305
+ def sanitize_sql_hash_for_assignment(attrs)
1306
+ attrs.map do |attr, value|
1307
+ "#{connection.quote_column_name(attr)} = #{quote_bound_value(value)}"
1308
+ end.join(', ')
1309
+ end
1310
+
1311
+ # Accepts an array of conditions. The array has each value
1312
+ # sanitized and interpolated into the SQL statement.
1313
+ # ["name='%s' and group_id='%s'", "foo'bar", 4] returns "name='foo''bar' and group_id='4'"
1314
+ def sanitize_sql_array(ary)
1315
+ statement, *values = ary
1316
+ if values.first.is_a?(Hash) and statement =~ /:\w+/
1317
+ replace_named_bind_variables(statement, values.first)
1318
+ elsif statement.include?('?')
1319
+ replace_bind_variables(statement, values)
1320
+ elsif statement.blank?
1321
+ statement
1322
+ else
1323
+ statement % values.collect { |value| connection.quote_string(value.to_s) }
1324
+ end
1325
+ end
1326
+
1327
+ alias_method :sanitize_conditions, :sanitize_sql
1328
+
1329
+ def replace_bind_variables(statement, values) #:nodoc:
1330
+ raise_if_bind_arity_mismatch(statement, statement.count('?'), values.size)
1331
+ bound = values.dup
1332
+ c = connection
1333
+ statement.gsub('?') { quote_bound_value(bound.shift, c) }
1334
+ end
1335
+
1336
+ def replace_named_bind_variables(statement, bind_vars) #:nodoc:
1337
+ statement.gsub(/(:?):([a-zA-Z]\w*)/) do
1338
+ if $1 == ':' # skip postgresql casts
1339
+ $& # return the whole match
1340
+ elsif bind_vars.include?(match = $2.to_sym)
1341
+ quote_bound_value(bind_vars[match])
1342
+ else
1343
+ raise PreparedStatementInvalid, "missing value for :#{match} in #{statement}"
1344
+ end
1345
+ end
1346
+ end
1347
+
1348
+ def expand_range_bind_variables(bind_vars) #:nodoc:
1349
+ expanded = []
1350
+
1351
+ bind_vars.each do |var|
1352
+ next if var.is_a?(Hash)
1353
+
1354
+ if var.is_a?(Range)
1355
+ expanded << var.first
1356
+ expanded << var.last
1357
+ else
1358
+ expanded << var
1359
+ end
1360
+ end
1361
+
1362
+ expanded
1363
+ end
1364
+
1365
+ def quote_bound_value(value, c = connection) #:nodoc:
1366
+ if value.respond_to?(:map) && !value.acts_like?(:string)
1367
+ if value.respond_to?(:empty?) && value.empty?
1368
+ c.quote(nil)
1369
+ else
1370
+ value.map { |v| c.quote(v) }.join(',')
1371
+ end
1372
+ else
1373
+ c.quote(value)
585
1374
  end
586
1375
  end
587
1376
 
588
- # Accepts either a condition array or string. The string is returned untouched, but the array has each of
589
- # the condition values sanitized.
590
- def sanitize_conditions(conditions)
591
- if Array === conditions
592
- statement, values = conditions[0], conditions[1..-1]
593
- values.collect! { |value| sanitize(value) }
594
- conditions = statement % values
1377
+ def raise_if_bind_arity_mismatch(statement, expected, provided) #:nodoc:
1378
+ unless expected == provided
1379
+ raise PreparedStatementInvalid, "wrong number of bind variables (#{provided} for #{expected}) in: #{statement}"
595
1380
  end
596
-
597
- return conditions
1381
+ end
1382
+
1383
+ def encode_quoted_value(value) #:nodoc:
1384
+ quoted_value = connection.quote(value)
1385
+ quoted_value = "'#{quoted_value[1..-2].gsub(/\'/, "\\\\'")}'" if quoted_value.include?("\\\'") # (for ruby mode) "
1386
+ quoted_value
598
1387
  end
599
1388
  end
600
1389
 
601
1390
  public
602
1391
  # New objects can be instantiated as either empty (pass no construction parameter) or pre-set with
603
1392
  # attributes but not yet saved (pass a hash with key names matching the associated table column names).
604
- # In both instances, valid attribute keys are determined by the column names of the associated table --
1393
+ # In both instances, valid attribute keys are determined by the column names of the associated table --
605
1394
  # hence you can't have attributes that aren't part of the table columns.
606
1395
  def initialize(attributes = nil)
607
1396
  @attributes = attributes_from_column_definition
1397
+ @attributes_cache = {}
608
1398
  @new_record = true
1399
+ @readonly = false
1400
+ @destroyed = false
1401
+ @marked_for_destruction = false
1402
+ @previously_changed = {}
1403
+ @changed_attributes = {}
1404
+
609
1405
  ensure_proper_type
1406
+
1407
+ if scope = self.class.send(:current_scoped_methods)
1408
+ create_with = scope.scope_for_create
1409
+ create_with.each { |att,value| self.send("#{att}=", value) } if create_with
1410
+ end
610
1411
  self.attributes = attributes unless attributes.nil?
611
- yield self if block_given?
612
- end
613
-
614
- # Every Active Record class must use "id" as their primary ID. This getter overwrites the native
615
- # id method, which isn't being used in this context.
616
- def id
617
- read_attribute(self.class.primary_key)
618
- end
619
-
620
- # Sets the primary ID.
621
- def id=(value)
622
- write_attribute(self.class.primary_key, value)
623
- end
624
-
625
- # Returns true if this object hasn't been saved yet -- that is, a record for the object doesn't exist yet.
626
- def new_record?
627
- @new_record
628
- end
629
-
630
- # * No record exists: Creates a new record with values matching those of the object attributes.
631
- # * A record does exist: Updates the record with values matching those of the object attributes.
632
- def save
633
- create_or_update
634
- return true
635
- end
636
-
637
- # Deletes the record in the database and freezes this instance to reflect that no changes should
638
- # be made (since they can't be persisted).
639
- def destroy
640
- unless new_record?
641
- connection.delete(
642
- "DELETE FROM #{self.class.table_name} " +
643
- "WHERE #{self.class.primary_key} = '#{id}'",
644
- "#{self.class.name} Destroy"
645
- )
646
- end
647
-
648
- freeze
649
- end
650
-
651
- # Returns a clone of the record that hasn't been assigned an id yet and is treated as a new record.
652
- def clone
653
- attr = Hash.new
654
-
655
- self.attribute_names.each do |name|
656
- begin
657
- attr[name] = read_attribute(name).clone
658
- rescue TypeError
659
- attr[name] = read_attribute(name)
660
- end
1412
+
1413
+ result = yield self if block_given?
1414
+ _run_initialize_callbacks
1415
+ result
1416
+ end
1417
+
1418
+ # Cloned objects have no id assigned and are treated as new records. Note that this is a "shallow" clone
1419
+ # as it copies the object's attributes only, not its associations. The extent of a "deep" clone is
1420
+ # application specific and is therefore left to the application to implement according to its need.
1421
+ def initialize_copy(other)
1422
+ _run_after_initialize_callbacks if respond_to?(:_run_after_initialize_callbacks)
1423
+ cloned_attributes = other.clone_attributes(:read_attribute_before_type_cast)
1424
+ cloned_attributes.delete(self.class.primary_key)
1425
+
1426
+ @attributes = cloned_attributes
1427
+
1428
+ @changed_attributes = {}
1429
+ attributes_from_column_definition.each do |attr, orig_value|
1430
+ @changed_attributes[attr] = orig_value if field_changed?(attr, orig_value, @attributes[attr])
661
1431
  end
662
1432
 
663
- cloned_record = self.class.new(attr)
664
- cloned_record.instance_variable_set "@new_record", true
665
- cloned_record.id = nil
666
- cloned_record
1433
+ clear_aggregation_cache
1434
+ clear_association_cache
1435
+ @attributes_cache = {}
1436
+ @new_record = true
1437
+ ensure_proper_type
1438
+
1439
+ if scope = self.class.send(:current_scoped_methods)
1440
+ create_with = scope.scope_for_create
1441
+ create_with.each { |att,value| self.send("#{att}=", value) } if create_with
1442
+ end
1443
+ end
1444
+
1445
+ # Returns a String, which Action Pack uses for constructing an URL to this
1446
+ # object. The default implementation returns this record's id as a String,
1447
+ # or nil if this record's unsaved.
1448
+ #
1449
+ # For example, suppose that you have a User model, and that you have a
1450
+ # <tt>resources :users</tt> route. Normally, +user_path+ will
1451
+ # construct a path with the user object's 'id' in it:
1452
+ #
1453
+ # user = User.find_by_name('Phusion')
1454
+ # user_path(user) # => "/users/1"
1455
+ #
1456
+ # You can override +to_param+ in your model to make +user_path+ construct
1457
+ # a path using the user's name instead of the user's id:
1458
+ #
1459
+ # class User < ActiveRecord::Base
1460
+ # def to_param # overridden
1461
+ # name
1462
+ # end
1463
+ # end
1464
+ #
1465
+ # user = User.find_by_name('Phusion')
1466
+ # user_path(user) # => "/users/Phusion"
1467
+ def to_param
1468
+ # We can't use alias_method here, because method 'id' optimizes itself on the fly.
1469
+ id && id.to_s # Be sure to stringify the id for routes
667
1470
  end
668
-
669
- # Updates a single attribute and saves the record. This is especially useful for boolean flags on existing records.
670
- def update_attribute(name, value)
671
- self[name] = value
672
- save
1471
+
1472
+ # Returns a cache key that can be used to identify this record.
1473
+ #
1474
+ # ==== Examples
1475
+ #
1476
+ # Product.new.cache_key # => "products/new"
1477
+ # Product.find(5).cache_key # => "products/5" (updated_at not available)
1478
+ # Person.find(5).cache_key # => "people/5-20071224150000" (updated_at available)
1479
+ def cache_key
1480
+ case
1481
+ when new_record?
1482
+ "#{self.class.model_name.cache_key}/new"
1483
+ when timestamp = self[:updated_at]
1484
+ "#{self.class.model_name.cache_key}/#{id}-#{timestamp.to_s(:number)}"
1485
+ else
1486
+ "#{self.class.model_name.cache_key}/#{id}"
1487
+ end
1488
+ end
1489
+
1490
+ def quoted_id #:nodoc:
1491
+ quote_value(id, column_for_attribute(self.class.primary_key))
1492
+ end
1493
+
1494
+ # Returns true if the given attribute is in the attributes hash
1495
+ def has_attribute?(attr_name)
1496
+ @attributes.has_key?(attr_name.to_s)
1497
+ end
1498
+
1499
+ # Returns an array of names for the attributes available on this object sorted alphabetically.
1500
+ def attribute_names
1501
+ @attributes.keys.sort
673
1502
  end
674
1503
 
675
- # Returns the value of attribute identified by <tt>attr_name</tt> after it has been type cast (for example,
1504
+ # Returns the value of the attribute identified by <tt>attr_name</tt> after it has been typecast (for example,
676
1505
  # "2004-12-12" in a data column is cast to a date object, like Date.new(2004, 12, 12)).
677
1506
  # (Alias for the protected read_attribute method).
678
- def [](attr_name)
1507
+ def [](attr_name)
679
1508
  read_attribute(attr_name)
680
1509
  end
681
-
1510
+
682
1511
  # Updates the attribute identified by <tt>attr_name</tt> with the specified +value+.
683
1512
  # (Alias for the protected write_attribute method).
684
- def []= (attr_name, value)
1513
+ def []=(attr_name, value)
685
1514
  write_attribute(attr_name, value)
686
1515
  end
687
1516
 
688
1517
  # Allows you to set all the attributes at once by passing in a hash with keys
689
- # matching the attribute names (which again matches the column names). Sensitive attributes can be protected
690
- # from this form of mass-assignment by using the +attr_protected+ macro. Or you can alternatively
691
- # specify which attributes *can* be accessed in with the +attr_accessible+ macro. Then all the
1518
+ # matching the attribute names (which again matches the column names).
1519
+ #
1520
+ # If +guard_protected_attributes+ is true (the default), then sensitive
1521
+ # attributes can be protected from this form of mass-assignment by using
1522
+ # the +attr_protected+ macro. Or you can alternatively specify which
1523
+ # attributes *can* be accessed with the +attr_accessible+ macro. Then all the
692
1524
  # attributes not included in that won't be allowed to be mass-assigned.
693
- def attributes=(attributes)
694
- return if attributes.nil?
1525
+ #
1526
+ # class User < ActiveRecord::Base
1527
+ # attr_protected :is_admin
1528
+ # end
1529
+ #
1530
+ # user = User.new
1531
+ # user.attributes = { :username => 'Phusion', :is_admin => true }
1532
+ # user.username # => "Phusion"
1533
+ # user.is_admin? # => false
1534
+ #
1535
+ # user.send(:attributes=, { :username => 'Phusion', :is_admin => true }, false)
1536
+ # user.is_admin? # => true
1537
+ def attributes=(new_attributes, guard_protected_attributes = true)
1538
+ return unless new_attributes.is_a?(Hash)
1539
+ attributes = new_attributes.stringify_keys
695
1540
 
696
1541
  multi_parameter_attributes = []
697
- remove_attributes_protected_from_mass_assignment(attributes).each do |k, v|
698
- k.include?("(") ? multi_parameter_attributes << [ k, v ] : send(k + "=", v)
1542
+ attributes = sanitize_for_mass_assignment(attributes) if guard_protected_attributes
1543
+
1544
+ attributes.each do |k, v|
1545
+ if k.include?("(")
1546
+ multi_parameter_attributes << [ k, v ]
1547
+ else
1548
+ respond_to?(:"#{k}=") ? send(:"#{k}=", v) : raise(UnknownAttributeError, "unknown attribute: #{k}")
1549
+ end
699
1550
  end
1551
+
700
1552
  assign_multiparameter_attributes(multi_parameter_attributes)
701
1553
  end
702
1554
 
703
- # Returns true if the specified +attribute+ has been set by the user or by a database load and is neither
704
- # nil nor empty? (the latter only applies to objects that responds to empty?, most notably Strings).
705
- def attribute_present?(attribute)
706
- is_empty = read_attribute(attribute).respond_to?("empty?") ? read_attribute(attribute).empty? : false
707
- @attributes.include?(attribute) && !@attributes[attribute].nil? && !is_empty
1555
+ # Returns a hash of all the attributes with their names as keys and the values of the attributes as values.
1556
+ def attributes
1557
+ attrs = {}
1558
+ attribute_names.each { |name| attrs[name] = read_attribute(name) }
1559
+ attrs
708
1560
  end
709
1561
 
710
- # Returns an array of names for the attributes available on this object sorted alphabetically.
711
- def attribute_names
712
- @attributes.keys.sort
1562
+ # Returns an <tt>#inspect</tt>-like string for the value of the
1563
+ # attribute +attr_name+. String attributes are elided after 50
1564
+ # characters, and Date and Time attributes are returned in the
1565
+ # <tt>:db</tt> format. Other attributes return the value of
1566
+ # <tt>#inspect</tt> without modification.
1567
+ #
1568
+ # person = Person.create!(:name => "David Heinemeier Hansson " * 3)
1569
+ #
1570
+ # person.attribute_for_inspect(:name)
1571
+ # # => '"David Heinemeier Hansson David Heinemeier Hansson D..."'
1572
+ #
1573
+ # person.attribute_for_inspect(:created_at)
1574
+ # # => '"2009-01-12 04:48:57"'
1575
+ def attribute_for_inspect(attr_name)
1576
+ value = read_attribute(attr_name)
1577
+
1578
+ if value.is_a?(String) && value.length > 50
1579
+ "#{value[0..50]}...".inspect
1580
+ elsif value.is_a?(Date) || value.is_a?(Time)
1581
+ %("#{value.to_s(:db)}")
1582
+ else
1583
+ value.inspect
1584
+ end
1585
+ end
1586
+
1587
+ # Returns true if the specified +attribute+ has been set by the user or by a database load and is neither
1588
+ # nil nor empty? (the latter only applies to objects that respond to empty?, most notably Strings).
1589
+ def attribute_present?(attribute)
1590
+ value = read_attribute(attribute)
1591
+ !value.blank?
713
1592
  end
714
1593
 
715
1594
  # Returns the column object for the named attribute.
716
1595
  def column_for_attribute(name)
717
- self.class.columns_hash[name]
1596
+ self.class.columns_hash[name.to_s]
718
1597
  end
719
-
720
- # Returns true if the +comparison_object+ is of the same type and has the same id.
1598
+
1599
+ # Returns true if the +comparison_object+ is the same object, or is of the same type and has the same id.
721
1600
  def ==(comparison_object)
722
- comparison_object.instance_of?(self.class) && comparison_object.id == id
1601
+ comparison_object.equal?(self) ||
1602
+ (comparison_object.instance_of?(self.class) &&
1603
+ comparison_object.id == id && !comparison_object.new_record?)
723
1604
  end
724
1605
 
725
- # For checking respond_to? without searching the attributes (which is faster).
726
- alias_method :respond_to_without_attributes?, :respond_to?
1606
+ # Delegates to ==
1607
+ def eql?(comparison_object)
1608
+ self == (comparison_object)
1609
+ end
727
1610
 
728
- # A Person object with a name attribute can ask person.respond_to?("name"), person.respond_to?("name="), and
729
- # person.respond_to?("name?") which will all return true.
730
- def respond_to?(method)
731
- @@dynamic_methods ||= attribute_names + attribute_names.collect { |attr| attr + "=" } + attribute_names.collect { |attr| attr + "?" }
732
- @@dynamic_methods.include?(method.to_s) ? true : respond_to_without_attributes?(method)
1611
+ # Delegates to id in order to allow two records of the same type and id to work with something like:
1612
+ # [ Person.find(1), Person.find(2), Person.find(3) ] & [ Person.find(1), Person.find(4) ] # => [ Person.find(1) ]
1613
+ def hash
1614
+ id.hash
733
1615
  end
734
1616
 
735
- private
736
- def create_or_update
737
- if new_record? then create else update end
1617
+ # Freeze the attributes hash such that associations are still accessible, even on destroyed records.
1618
+ def freeze
1619
+ @attributes.freeze; self
738
1620
  end
739
1621
 
740
- # Updates the associated record with values matching those of the instant attributes.
741
- def update
742
- connection.update(
743
- "UPDATE #{self.class.table_name} " +
744
- "SET #{quoted_comma_pair_list(connection, attributes_with_quotes)} " +
745
- "WHERE #{self.class.primary_key} = '#{id}'",
746
- "#{self.class.name} Update"
747
- )
1622
+ # Returns +true+ if the attributes hash has been frozen.
1623
+ def frozen?
1624
+ @attributes.frozen?
748
1625
  end
749
1626
 
750
- # Creates a new record with values matching those of the instant attributes.
751
- def create
752
- self.id = connection.insert(
753
- "INSERT INTO #{self.class.table_name} " +
754
- "(#{quoted_column_names.join(', ')}) " +
755
- "VALUES(#{attributes_with_quotes.values.join(', ')})",
756
- "#{self.class.name} Create",
757
- self.class.primary_key, self.id
758
- )
759
-
760
- @new_record = false
1627
+ # Returns duplicated record with unfreezed attributes.
1628
+ def dup
1629
+ obj = super
1630
+ obj.instance_variable_set('@attributes', @attributes.dup)
1631
+ obj
761
1632
  end
762
1633
 
763
- # Sets the attribute used for single table inheritance to this class name if this is not the ActiveRecord descendant.
764
- # Considering the hierarchy Reply < Message < ActiveRecord, this makes it possible to do Reply.new without having to
765
- # set Reply[Reply.inheritance_column] = "Reply" yourself. No such attribute would be set for objects of the
766
- # Message class in that example.
767
- def ensure_proper_type
768
- unless self.class.descents_from_active_record?
769
- write_attribute(self.class.inheritance_column, Inflector.demodulize(self.class.name))
770
- end
771
- end
772
-
773
- # Allows access to the object attributes, which are held in the @attributes hash, as were
774
- # they first-class methods. So a Person class with a name attribute can use Person#name and
775
- # Person#name= and never directly use the attributes hash -- except for multiple assigns with
776
- # ActiveRecord#attributes=. A Milestone class can also ask Milestone#completed? to test that
777
- # the completed attribute is not nil or 0.
778
- #
779
- # It's also possible to instantiate related objects, so a Client class belonging to the clients
780
- # table with a master_id foreign key can instantiate master through Client#master.
781
- def method_missing(method_id, *arguments)
782
- method_name = method_id.id2name
783
-
784
- if method_name =~ read_method? && @attributes.include?($1)
785
- return read_attribute($1)
786
- elsif method_name =~ write_method?
787
- write_attribute($1, arguments[0])
788
- elsif method_name =~ query_method?
789
- return query_attribute($1)
790
- else
791
- super
792
- end
1634
+ # Returns +true+ if the record is read only. Records loaded through joins with piggy-back
1635
+ # attributes will be marked as read only since they cannot be saved.
1636
+ def readonly?
1637
+ @readonly
793
1638
  end
794
1639
 
795
- def read_method?() /^([a-zA-Z][-_\w]*)[^=?]*$/ end
796
- def write_method?() /^([a-zA-Z][-_\w]*)=.*$/ end
797
- def query_method?() /^([a-zA-Z][-_\w]*)\?$/ end
1640
+ # Marks this record as read only.
1641
+ def readonly!
1642
+ @readonly = true
1643
+ end
798
1644
 
799
- # Returns the value of attribute identified by <tt>attr_name</tt> after it has been type cast (for example,
800
- # "2004-12-12" in a data column is cast to a date object, like Date.new(2004, 12, 12)).
801
- def read_attribute(attr_name) #:doc:
802
- if column = column_for_attribute(attr_name)
803
- @attributes[attr_name] = unserializable_attribute?(attr_name, column) ?
804
- unserialize_attribute(attr_name) : column.type_cast(@attributes[attr_name])
1645
+ # Returns the contents of the record as a nicely formatted string.
1646
+ def inspect
1647
+ attributes_as_nice_string = self.class.column_names.collect { |name|
1648
+ if has_attribute?(name) || new_record?
1649
+ "#{name}: #{attribute_for_inspect(name)}"
1650
+ end
1651
+ }.compact.join(", ")
1652
+ "#<#{self.class} #{attributes_as_nice_string}>"
1653
+ end
1654
+
1655
+ protected
1656
+ def clone_attributes(reader_method = :read_attribute, attributes = {})
1657
+ attribute_names.each do |name|
1658
+ attributes[name] = clone_attribute_value(reader_method, name)
805
1659
  end
806
-
807
- @attributes[attr_name]
1660
+ attributes
808
1661
  end
809
1662
 
810
- # Returns true if the attribute is of a text column and marked for serialization.
811
- def unserializable_attribute?(attr_name, column)
812
- @attributes[attr_name] && column.send(:type) == :text && @attributes[attr_name].is_a?(String) && self.class.serialized_attributes[attr_name]
1663
+ def clone_attribute_value(reader_method, attribute_name)
1664
+ value = send(reader_method, attribute_name)
1665
+ value.duplicable? ? value.clone : value
1666
+ rescue TypeError, NoMethodError
1667
+ value
813
1668
  end
814
1669
 
815
- # Returns the unserialized object of the attribute.
816
- def unserialize_attribute(attr_name)
817
- unserialized_object = object_from_yaml(@attributes[attr_name])
1670
+ private
818
1671
 
819
- if unserialized_object.is_a?(self.class.serialized_attributes[attr_name])
820
- @attributes[attr_name] = unserialized_object
821
- else
822
- raise(
823
- SerializationTypeMismatch,
824
- "#{attr_name} was supposed to be a #{self.class.serialized_attributes[attr_name]}, " +
825
- "but was a #{unserialized_object.class.to_s}"
826
- )
827
- end
828
- end
829
-
830
- # Updates the attribute identified by <tt>attr_name</tt> with the specified +value+. Empty strings for fixnum and float
831
- # columns are turned into nil.
832
- def write_attribute(attr_name, value) #:doc:
833
- @attributes[attr_name] = empty_string_for_number_column?(attr_name, value) ? nil : value
834
- end
835
-
836
- def empty_string_for_number_column?(attr_name, value)
837
- column = column_for_attribute(attr_name)
838
- column && (column.klass == Fixnum || column.klass == Float) && value == ""
839
- end
840
-
841
- def query_attribute(attr_name)
842
- attribute = @attributes[attr_name]
843
- if attribute.kind_of?(Fixnum) && attribute == 0
844
- false
845
- elsif attribute.kind_of?(String) && attribute == "0"
846
- false
847
- elsif attribute.kind_of?(String) && attribute.empty?
848
- false
849
- elsif attribute.nil?
850
- false
851
- elsif attribute == false
852
- false
853
- elsif attribute == "f"
854
- false
855
- elsif attribute == "false"
856
- false
857
- else
858
- true
1672
+ # Sets the attribute used for single table inheritance to this class name if this is not the
1673
+ # ActiveRecord::Base descendant.
1674
+ # Considering the hierarchy Reply < Message < ActiveRecord::Base, this makes it possible to
1675
+ # do Reply.new without having to set <tt>Reply[Reply.inheritance_column] = "Reply"</tt> yourself.
1676
+ # No such attribute would be set for objects of the Message class in that example.
1677
+ def ensure_proper_type
1678
+ unless self.class.descends_from_active_record?
1679
+ write_attribute(self.class.inheritance_column, self.class.sti_name)
859
1680
  end
860
1681
  end
861
1682
 
862
- def remove_attributes_protected_from_mass_assignment(attributes)
863
- if self.class.accessible_attributes.nil? && self.class.protected_attributes.nil?
864
- attributes.reject { |key, value| key == self.class.primary_key }
865
- elsif self.class.protected_attributes.nil?
866
- attributes.reject { |key, value| !self.class.accessible_attributes.include?(key.intern) || key == self.class.primary_key }
867
- elsif self.class.accessible_attributes.nil?
868
- attributes.reject { |key, value| self.class.protected_attributes.include?(key.intern) || key == self.class.primary_key }
869
- end
1683
+ # The primary key and inheritance column can never be set by mass-assignment for security reasons.
1684
+ def self.attributes_protected_by_default
1685
+ default = [ primary_key, inheritance_column ]
1686
+ default << 'id' unless primary_key.eql? 'id'
1687
+ default
870
1688
  end
871
1689
 
872
- # Returns copy of the attributes hash where all the values have been safely quoted for use in
873
- # an SQL statement.
874
- def attributes_with_quotes
875
- columns_hash = self.class.columns_hash
876
- @attributes.inject({}) do |attrs_quoted, pair|
877
- attrs_quoted[pair.first] = quote(pair.last, columns_hash[pair.first])
878
- attrs_quoted
1690
+ # Returns a copy of the attributes hash where all the values have been safely quoted for use in
1691
+ # an Arel insert/update method.
1692
+ def arel_attributes_values(include_primary_key = true, include_readonly_attributes = true, attribute_names = @attributes.keys)
1693
+ attrs = {}
1694
+ attribute_names.each do |name|
1695
+ if (column = column_for_attribute(name)) && (include_primary_key || !column.primary)
1696
+
1697
+ if include_readonly_attributes || (!include_readonly_attributes && !self.class.readonly_attributes.include?(name))
1698
+ value = read_attribute(name)
1699
+
1700
+ if value && ((self.class.serialized_attributes.has_key?(name) && (value.acts_like?(:date) || value.acts_like?(:time))) || value.is_a?(Hash) || value.is_a?(Array))
1701
+ value = value.to_yaml
1702
+ end
1703
+ attrs[self.class.arel_table[name]] = value
1704
+ end
1705
+ end
879
1706
  end
1707
+ attrs
880
1708
  end
881
-
1709
+
882
1710
  # Quote strings appropriately for SQL statements.
883
- def quote(value, column = nil)
884
- connection.quote(value, column)
1711
+ def quote_value(value, column = nil)
1712
+ self.class.connection.quote(value, column)
885
1713
  end
886
1714
 
887
- # Initializes the attributes array with keys matching the columns from the linked table and
888
- # the values matching the corresponding default value of that column, so
889
- # that a new instance, or one populated from a passed-in Hash, still has all the attributes
890
- # that instances loaded from the database would.
891
- def attributes_from_column_definition
892
- connection.columns(self.class.table_name, "#{self.class.name} Columns").inject({}) do |attributes, column|
893
- attributes[column.name] = column.default unless column.name == self.class.primary_key
894
- attributes
895
- end
1715
+ # Interpolate custom SQL string in instance context.
1716
+ # Optional record argument is meant for custom insert_sql.
1717
+ def interpolate_sql(sql, record = nil)
1718
+ instance_eval("%@#{sql.gsub('@', '\@')}@", __FILE__, __LINE__)
896
1719
  end
897
1720
 
898
1721
  # Instantiates objects for all attribute classes that needs more than one constructor parameter. This is done
899
1722
  # by calling new on the column type or aggregation type (through composed_of) object with these parameters.
900
1723
  # So having the pairs written_on(1) = "2004", written_on(2) = "6", written_on(3) = "24", will instantiate
901
1724
  # written_on (a date type) with Date.new("2004", "6", "24"). You can also specify a typecast character in the
902
- # parenteses to have the parameters typecasted before they're used in the constructor. Use i for Fixnum, f for Float,
903
- # s for String, and a for Array. If all the values for a given attribute is empty, the attribute will be set to nil.
1725
+ # parentheses to have the parameters typecasted before they're used in the constructor. Use i for Fixnum,
1726
+ # f for Float, s for String, and a for Array. If all the values for a given attribute are empty, the
1727
+ # attribute will be set to nil.
904
1728
  def assign_multiparameter_attributes(pairs)
905
1729
  execute_callstack_for_multiparameter_attributes(
906
1730
  extract_callstack_for_multiparameter_attributes(pairs)
907
1731
  )
908
1732
  end
909
-
910
- # Includes an ugly hack for Time.local instead of Time.new because the latter is reserved by Time itself.
1733
+
1734
+ def instantiate_time_object(name, values)
1735
+ if self.class.send(:create_time_zone_conversion_attribute?, name, column_for_attribute(name))
1736
+ Time.zone.local(*values)
1737
+ else
1738
+ Time.time_with_datetime_fallback(@@default_timezone, *values)
1739
+ end
1740
+ end
1741
+
911
1742
  def execute_callstack_for_multiparameter_attributes(callstack)
912
- callstack.each do |name, values|
913
- klass = (self.class.reflect_on_aggregation(name) || column_for_attribute(name)).klass
914
- if values.empty?
915
- send(name + "=", nil)
916
- else
917
- send(name + "=", Time == klass ? klass.local(*values) : klass.new(*values))
1743
+ errors = []
1744
+ callstack.each do |name, values_with_empty_parameters|
1745
+ begin
1746
+ klass = (self.class.reflect_on_aggregation(name.to_sym) || column_for_attribute(name)).klass
1747
+ # in order to allow a date to be set without a year, we must keep the empty values.
1748
+ # Otherwise, we wouldn't be able to distinguish it from a date with an empty day.
1749
+ values = values_with_empty_parameters.reject { |v| v.nil? }
1750
+
1751
+ if values.empty?
1752
+ send(name + "=", nil)
1753
+ else
1754
+
1755
+ value = if Time == klass
1756
+ instantiate_time_object(name, values)
1757
+ elsif Date == klass
1758
+ begin
1759
+ values = values_with_empty_parameters.collect do |v| v.nil? ? 1 : v end
1760
+ Date.new(*values)
1761
+ rescue ArgumentError => ex # if Date.new raises an exception on an invalid date
1762
+ instantiate_time_object(name, values).to_date # we instantiate Time object and convert it back to a date thus using Time's logic in handling invalid dates
1763
+ end
1764
+ else
1765
+ klass.new(*values)
1766
+ end
1767
+
1768
+ send(name + "=", value)
1769
+ end
1770
+ rescue => ex
1771
+ errors << AttributeAssignmentError.new("error on assignment #{values.inspect} to #{name}", ex, name)
918
1772
  end
919
1773
  end
1774
+ unless errors.empty?
1775
+ raise MultiparameterAssignmentErrors.new(errors), "#{errors.size} error(s) on assignment of multiparameter attributes"
1776
+ end
920
1777
  end
921
-
1778
+
922
1779
  def extract_callstack_for_multiparameter_attributes(pairs)
923
1780
  attributes = { }
924
1781
 
@@ -927,59 +1784,84 @@ module ActiveRecord #:nodoc:
927
1784
  attribute_name = multiparameter_name.split("(").first
928
1785
  attributes[attribute_name] = [] unless attributes.include?(attribute_name)
929
1786
 
930
- unless value.empty?
931
- attributes[attribute_name] <<
932
- [find_parameter_position(multiparameter_name), type_cast_attribute_value(multiparameter_name, value)]
933
- end
1787
+ parameter_value = value.empty? ? nil : type_cast_attribute_value(multiparameter_name, value)
1788
+ attributes[attribute_name] << [ find_parameter_position(multiparameter_name), parameter_value ]
934
1789
  end
935
1790
 
936
1791
  attributes.each { |name, values| attributes[name] = values.sort_by{ |v| v.first }.collect { |v| v.last } }
937
1792
  end
938
-
1793
+
939
1794
  def type_cast_attribute_value(multiparameter_name, value)
940
- multiparameter_name =~ /\([0-9]*([a-z])\)/ ? value.send("to_" + $1) : value
1795
+ multiparameter_name =~ /\([0-9]*([if])\)/ ? value.send("to_" + $1) : value
941
1796
  end
942
-
1797
+
943
1798
  def find_parameter_position(multiparameter_name)
944
1799
  multiparameter_name.scan(/\(([0-9]*).*\)/).first.first
945
1800
  end
946
-
1801
+
947
1802
  # Returns a comma-separated pair list, like "key1 = val1, key2 = val2".
948
1803
  def comma_pair_list(hash)
949
- hash.inject([]) { |list, pair| list << "#{pair.first} = #{pair.last}" }.join(", ")
950
- end
951
-
952
- def quoted_column_names
953
- attributes_with_quotes.keys.collect { |column_name| connection.quote_column_name(column_name) }
1804
+ hash.map { |k,v| "#{k} = #{v}" }.join(", ")
954
1805
  end
955
1806
 
956
- def quote_columns(column_quoter, hash)
957
- hash.inject({}) {|list, pair|
958
- list[column_quoter.quote_column_name(pair.first)] = pair.last
959
- list
960
- }
1807
+ def quote_columns(quoter, hash)
1808
+ hash.inject({}) do |quoted, (name, value)|
1809
+ quoted[quoter.quote_column_name(name)] = value
1810
+ quoted
1811
+ end
961
1812
  end
962
1813
 
963
- def quoted_comma_pair_list(column_quoter, hash)
964
- comma_pair_list(quote_columns(column_quoter, hash))
1814
+ def quoted_comma_pair_list(quoter, hash)
1815
+ comma_pair_list(quote_columns(quoter, hash))
965
1816
  end
966
1817
 
967
- def object_from_yaml(string)
968
- return string unless String === string
969
- if has_yaml_encoding_header?(string)
970
- begin
971
- YAML::load(string)
972
- rescue Object
973
- # Apparently wasn't YAML anyway
974
- string
975
- end
1818
+ def convert_number_column_value(value)
1819
+ if value == false
1820
+ 0
1821
+ elsif value == true
1822
+ 1
1823
+ elsif value.is_a?(String) && value.blank?
1824
+ nil
976
1825
  else
977
- string
1826
+ value
978
1827
  end
979
1828
  end
980
1829
 
981
- def has_yaml_encoding_header?(string)
982
- string[0..3] == "--- "
1830
+ def object_from_yaml(string)
1831
+ return string unless string.is_a?(String) && string =~ /^---/
1832
+ YAML::load(string) rescue string
983
1833
  end
984
1834
  end
985
- end
1835
+
1836
+ Base.class_eval do
1837
+ include ActiveRecord::Persistence
1838
+ extend ActiveModel::Naming
1839
+ extend QueryCache::ClassMethods
1840
+ extend ActiveSupport::Benchmarkable
1841
+ extend ActiveSupport::DescendantsTracker
1842
+
1843
+ include ActiveModel::Conversion
1844
+ include Validations
1845
+ extend CounterCache
1846
+ include Locking::Optimistic, Locking::Pessimistic
1847
+ include AttributeMethods
1848
+ include AttributeMethods::Read, AttributeMethods::Write, AttributeMethods::BeforeTypeCast, AttributeMethods::Query
1849
+ include AttributeMethods::PrimaryKey
1850
+ include AttributeMethods::TimeZoneConversion
1851
+ include AttributeMethods::Dirty
1852
+ include ActiveModel::MassAssignmentSecurity
1853
+ include Callbacks, ActiveModel::Observing, Timestamp
1854
+ include Associations, AssociationPreload, NamedScope
1855
+
1856
+ # AutosaveAssociation needs to be included before Transactions, because we want
1857
+ # #save_with_autosave_associations to be wrapped inside a transaction.
1858
+ include AutosaveAssociation, NestedAttributes
1859
+ include Aggregations, Transactions, Reflection, Serialization
1860
+
1861
+ NilClass.add_whiner(self) if NilClass.respond_to?(:add_whiner)
1862
+ end
1863
+ end
1864
+
1865
+ # TODO: Remove this and make it work with LAZY flag
1866
+ require 'active_record/connection_adapters/abstract_adapter'
1867
+ ActiveSupport.run_load_hooks(:active_record, ActiveRecord::Base)