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