activerecord 1.14.4 → 1.15.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 +400 -1
- data/README +2 -2
- data/RUNNING_UNIT_TESTS +21 -3
- data/Rakefile +55 -10
- data/lib/active_record.rb +10 -4
- data/lib/active_record/acts/list.rb +15 -4
- data/lib/active_record/acts/nested_set.rb +11 -12
- data/lib/active_record/acts/tree.rb +13 -14
- data/lib/active_record/aggregations.rb +46 -22
- data/lib/active_record/associations.rb +213 -162
- data/lib/active_record/associations/association_collection.rb +45 -15
- data/lib/active_record/associations/association_proxy.rb +32 -13
- data/lib/active_record/associations/has_and_belongs_to_many_association.rb +18 -18
- data/lib/active_record/associations/has_many_association.rb +37 -17
- data/lib/active_record/associations/has_many_through_association.rb +120 -30
- data/lib/active_record/associations/has_one_association.rb +1 -1
- data/lib/active_record/attribute_methods.rb +75 -0
- data/lib/active_record/base.rb +282 -203
- data/lib/active_record/calculations.rb +95 -54
- data/lib/active_record/callbacks.rb +13 -24
- data/lib/active_record/connection_adapters/abstract/connection_specification.rb +12 -1
- data/lib/active_record/connection_adapters/abstract/connection_specification.rb.rej +21 -0
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +30 -4
- data/lib/active_record/connection_adapters/abstract/quoting.rb +16 -9
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +121 -37
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +55 -23
- data/lib/active_record/connection_adapters/abstract_adapter.rb +8 -0
- data/lib/active_record/connection_adapters/db2_adapter.rb +1 -11
- data/lib/active_record/connection_adapters/firebird_adapter.rb +364 -50
- data/lib/active_record/connection_adapters/frontbase_adapter.rb +861 -0
- data/lib/active_record/connection_adapters/mysql_adapter.rb +86 -33
- data/lib/active_record/connection_adapters/openbase_adapter.rb +4 -3
- data/lib/active_record/connection_adapters/oracle_adapter.rb +151 -127
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +125 -48
- data/lib/active_record/connection_adapters/sqlite_adapter.rb +38 -10
- data/lib/active_record/connection_adapters/sqlserver_adapter.rb +183 -155
- data/lib/active_record/connection_adapters/sybase_adapter.rb +190 -212
- data/lib/active_record/deprecated_associations.rb +24 -10
- data/lib/active_record/deprecated_finders.rb +4 -1
- data/lib/active_record/fixtures.rb +37 -23
- data/lib/active_record/locking/optimistic.rb +106 -0
- data/lib/active_record/locking/pessimistic.rb +77 -0
- data/lib/active_record/migration.rb +8 -5
- data/lib/active_record/observer.rb +73 -34
- data/lib/active_record/reflection.rb +21 -7
- data/lib/active_record/schema_dumper.rb +33 -5
- data/lib/active_record/timestamp.rb +23 -34
- data/lib/active_record/transactions.rb +37 -30
- data/lib/active_record/validations.rb +46 -30
- data/lib/active_record/vendor/mysql.rb +20 -5
- data/lib/active_record/version.rb +2 -2
- data/lib/active_record/wrappings.rb +1 -2
- data/lib/active_record/xml_serialization.rb +308 -0
- data/test/aaa_create_tables_test.rb +5 -1
- data/test/abstract_unit.rb +18 -8
- data/test/{active_schema_mysql.rb → active_schema_test_mysql.rb} +2 -2
- data/test/adapter_test.rb +9 -7
- data/test/adapter_test_sqlserver.rb +81 -0
- data/test/aggregations_test.rb +29 -0
- data/test/{association_callbacks_test.rb → associations/callbacks_test.rb} +10 -8
- data/test/{associations_cascaded_eager_loading_test.rb → associations/cascaded_eager_loading_test.rb} +35 -3
- data/test/{associations_go_eager_test.rb → associations/eager_test.rb} +36 -2
- data/test/{associations_extensions_test.rb → associations/extension_test.rb} +5 -0
- data/test/{associations_join_model_test.rb → associations/join_model_test.rb} +118 -8
- data/test/associations_test.rb +339 -45
- data/test/attribute_methods_test.rb +49 -0
- data/test/base_test.rb +321 -67
- data/test/calculations_test.rb +48 -10
- data/test/callbacks_test.rb +13 -0
- data/test/connection_test_firebird.rb +8 -0
- data/test/connections/native_db2/connection.rb +18 -17
- data/test/connections/native_firebird/connection.rb +19 -17
- data/test/connections/native_frontbase/connection.rb +27 -0
- data/test/connections/native_mysql/connection.rb +18 -15
- data/test/connections/native_openbase/connection.rb +14 -15
- data/test/connections/native_oracle/connection.rb +16 -12
- data/test/connections/native_postgresql/connection.rb +16 -17
- data/test/connections/native_sqlite/connection.rb +3 -6
- data/test/connections/native_sqlite3/connection.rb +3 -6
- data/test/connections/native_sqlserver/connection.rb +16 -17
- data/test/connections/native_sqlserver_odbc/connection.rb +18 -19
- data/test/connections/native_sybase/connection.rb +16 -17
- data/test/datatype_test_postgresql.rb +52 -0
- data/test/defaults_test.rb +52 -10
- data/test/deprecated_associations_test.rb +151 -107
- data/test/deprecated_finder_test.rb +83 -66
- data/test/empty_date_time_test.rb +25 -0
- data/test/finder_test.rb +118 -11
- data/test/fixtures/accounts.yml +6 -1
- data/test/fixtures/author.rb +27 -4
- data/test/fixtures/categorizations.yml +8 -2
- data/test/fixtures/category.rb +1 -2
- data/test/fixtures/comments.yml +0 -6
- data/test/fixtures/companies.yml +6 -1
- data/test/fixtures/company.rb +23 -1
- data/test/fixtures/company_in_module.rb +8 -10
- data/test/fixtures/customer.rb +2 -2
- data/test/fixtures/customers.yml +9 -0
- data/test/fixtures/db_definitions/db2.drop.sql +1 -0
- data/test/fixtures/db_definitions/db2.sql +9 -0
- data/test/fixtures/db_definitions/firebird.drop.sql +3 -0
- data/test/fixtures/db_definitions/firebird.sql +13 -1
- data/test/fixtures/db_definitions/frontbase.drop.sql +31 -0
- data/test/fixtures/db_definitions/frontbase.sql +262 -0
- data/test/fixtures/db_definitions/frontbase2.drop.sql +1 -0
- data/test/fixtures/db_definitions/frontbase2.sql +4 -0
- data/test/fixtures/db_definitions/mysql.drop.sql +1 -0
- data/test/fixtures/db_definitions/mysql.sql +23 -14
- data/test/fixtures/db_definitions/openbase.sql +13 -1
- data/test/fixtures/db_definitions/oracle.drop.sql +2 -0
- data/test/fixtures/db_definitions/oracle.sql +29 -2
- data/test/fixtures/db_definitions/postgresql.drop.sql +3 -1
- data/test/fixtures/db_definitions/postgresql.sql +13 -3
- data/test/fixtures/db_definitions/schema.rb +29 -1
- data/test/fixtures/db_definitions/sqlite.drop.sql +1 -0
- data/test/fixtures/db_definitions/sqlite.sql +12 -3
- data/test/fixtures/db_definitions/sqlserver.drop.sql +3 -0
- data/test/fixtures/db_definitions/sqlserver.sql +35 -0
- data/test/fixtures/db_definitions/sybase.drop.sql +2 -0
- data/test/fixtures/db_definitions/sybase.sql +13 -4
- data/test/fixtures/developer.rb +12 -0
- data/test/fixtures/edge.rb +5 -0
- data/test/fixtures/edges.yml +6 -0
- data/test/fixtures/funny_jokes.yml +3 -7
- data/test/fixtures/migrations_with_decimal/1_give_me_big_numbers.rb +15 -0
- data/test/fixtures/migrations_with_missing_versions/1000_people_have_middle_names.rb +9 -0
- data/test/fixtures/migrations_with_missing_versions/1_people_have_last_names.rb +9 -0
- data/test/fixtures/migrations_with_missing_versions/3_we_need_reminders.rb +12 -0
- data/test/fixtures/migrations_with_missing_versions/4_innocent_jointable.rb +12 -0
- data/test/fixtures/mixin.rb +15 -0
- data/test/fixtures/mixins.yml +38 -0
- data/test/fixtures/post.rb +3 -2
- data/test/fixtures/project.rb +3 -1
- data/test/fixtures/topic.rb +6 -1
- data/test/fixtures/topics.yml +4 -4
- data/test/fixtures/vertex.rb +9 -0
- data/test/fixtures/vertices.yml +4 -0
- data/test/fixtures_test.rb +45 -0
- data/test/inheritance_test.rb +67 -6
- data/test/lifecycle_test.rb +40 -19
- data/test/locking_test.rb +170 -26
- data/test/method_scoping_test.rb +2 -2
- data/test/migration_test.rb +387 -110
- data/test/migration_test_firebird.rb +124 -0
- data/test/mixin_nested_set_test.rb +14 -2
- data/test/mixin_test.rb +56 -18
- data/test/modules_test.rb +8 -2
- data/test/multiple_db_test.rb +2 -2
- data/test/pk_test.rb +1 -0
- data/test/reflection_test.rb +8 -2
- data/test/schema_authorization_test_postgresql.rb +75 -0
- data/test/schema_dumper_test.rb +40 -4
- data/test/table_name_test_sqlserver.rb +23 -0
- data/test/threaded_connections_test.rb +19 -16
- data/test/transactions_test.rb +86 -72
- data/test/validations_test.rb +126 -56
- data/test/xml_serialization_test.rb +125 -0
- metadata +45 -11
- data/lib/active_record/locking.rb +0 -79
@@ -69,7 +69,7 @@ module ActiveRecord
|
|
69
69
|
when @reflection.options[:as]
|
70
70
|
@finder_sql =
|
71
71
|
"#{@reflection.klass.table_name}.#{@reflection.options[:as]}_id = #{@owner.quoted_id} AND " +
|
72
|
-
"#{@reflection.klass.table_name}.#{@reflection.options[:as]}_type = #{@owner.class.
|
72
|
+
"#{@reflection.klass.table_name}.#{@reflection.options[:as]}_type = #{@owner.class.quote_value(@owner.class.base_class.name.to_s)}"
|
73
73
|
else
|
74
74
|
@finder_sql = "#{@reflection.table_name}.#{@reflection.primary_key_name} = #{@owner.quoted_id}"
|
75
75
|
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
module ActiveRecord
|
2
|
+
module AttributeMethods #:nodoc:
|
3
|
+
DEFAULT_SUFFIXES = %w(= ? _before_type_cast)
|
4
|
+
|
5
|
+
def self.included(base)
|
6
|
+
base.extend ClassMethods
|
7
|
+
base.attribute_method_suffix *DEFAULT_SUFFIXES
|
8
|
+
end
|
9
|
+
|
10
|
+
# Declare and check for suffixed attribute methods.
|
11
|
+
module ClassMethods
|
12
|
+
# Declare a method available for all attributes with the given suffix.
|
13
|
+
# Uses method_missing and respond_to? to rewrite the method
|
14
|
+
# #{attr}#{suffix}(*args, &block)
|
15
|
+
# to
|
16
|
+
# attribute#{suffix}(#{attr}, *args, &block)
|
17
|
+
#
|
18
|
+
# An attribute#{suffix} instance method must exist and accept at least
|
19
|
+
# the attr argument.
|
20
|
+
#
|
21
|
+
# For example:
|
22
|
+
# class Person < ActiveRecord::Base
|
23
|
+
# attribute_method_suffix '_changed?'
|
24
|
+
#
|
25
|
+
# private
|
26
|
+
# def attribute_changed?(attr)
|
27
|
+
# ...
|
28
|
+
# end
|
29
|
+
# end
|
30
|
+
#
|
31
|
+
# person = Person.find(1)
|
32
|
+
# person.name_changed? # => false
|
33
|
+
# person.name = 'Hubert'
|
34
|
+
# person.name_changed? # => true
|
35
|
+
def attribute_method_suffix(*suffixes)
|
36
|
+
attribute_method_suffixes.concat suffixes
|
37
|
+
rebuild_attribute_method_regexp
|
38
|
+
end
|
39
|
+
|
40
|
+
# Returns MatchData if method_name is an attribute method.
|
41
|
+
def match_attribute_method?(method_name)
|
42
|
+
rebuild_attribute_method_regexp unless defined?(@@attribute_method_regexp) && @@attribute_method_regexp
|
43
|
+
@@attribute_method_regexp.match(method_name)
|
44
|
+
end
|
45
|
+
|
46
|
+
private
|
47
|
+
# Suffixes a, ?, c become regexp /(a|\?|c)$/
|
48
|
+
def rebuild_attribute_method_regexp
|
49
|
+
suffixes = attribute_method_suffixes.map { |s| Regexp.escape(s) }
|
50
|
+
@@attribute_method_regexp = /(#{suffixes.join('|')})$/.freeze
|
51
|
+
end
|
52
|
+
|
53
|
+
# Default to =, ?, _before_type_cast
|
54
|
+
def attribute_method_suffixes
|
55
|
+
@@attribute_method_suffixes ||= []
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
private
|
60
|
+
# Handle *? for method_missing.
|
61
|
+
def attribute?(attribute_name)
|
62
|
+
query_attribute(attribute_name)
|
63
|
+
end
|
64
|
+
|
65
|
+
# Handle *= for method_missing.
|
66
|
+
def attribute=(attribute_name, value)
|
67
|
+
write_attribute(attribute_name, value)
|
68
|
+
end
|
69
|
+
|
70
|
+
# Handle *_before_type_cast for method_missing.
|
71
|
+
def attribute_before_type_cast(attribute_name)
|
72
|
+
read_attribute_before_type_cast(attribute_name)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
data/lib/active_record/base.rb
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
require 'base64'
|
1
2
|
require 'yaml'
|
2
3
|
require 'set'
|
3
4
|
require 'active_record/deprecated_finders'
|
@@ -80,11 +81,12 @@ module ActiveRecord #:nodoc:
|
|
80
81
|
#
|
81
82
|
# == Conditions
|
82
83
|
#
|
83
|
-
# Conditions can either be specified as a string or
|
84
|
+
# Conditions can either be specified as a string, array, or hash representing the WHERE-part of an SQL statement.
|
84
85
|
# The array form is to be used when the condition input is tainted and requires sanitization. The string form can
|
85
|
-
# be used for statements that don't involve tainted data.
|
86
|
+
# be used for statements that don't involve tainted data. The hash form works much like the array form, except
|
87
|
+
# only equality is possible. Examples:
|
86
88
|
#
|
87
|
-
# User < ActiveRecord::Base
|
89
|
+
# class User < ActiveRecord::Base
|
88
90
|
# def self.authenticate_unsafely(user_name, password)
|
89
91
|
# find(:first, :conditions => "user_name = '#{user_name}' AND password = '#{password}'")
|
90
92
|
# end
|
@@ -92,12 +94,16 @@ module ActiveRecord #:nodoc:
|
|
92
94
|
# def self.authenticate_safely(user_name, password)
|
93
95
|
# find(:first, :conditions => [ "user_name = ? AND password = ?", user_name, password ])
|
94
96
|
# end
|
97
|
+
#
|
98
|
+
# def self.authenticate_safely_simply(user_name, password)
|
99
|
+
# find(:first, :conditions => { :user_name => user_name, :password => password })
|
100
|
+
# end
|
95
101
|
# end
|
96
102
|
#
|
97
103
|
# The <tt>authenticate_unsafely</tt> method inserts the parameters directly into the query and is thus susceptible to SQL-injection
|
98
|
-
# attacks if the <tt>user_name</tt> and +password+ parameters come directly from a HTTP request. The <tt>authenticate_safely</tt>
|
99
|
-
#
|
100
|
-
# an attacker can't escape the query and fake the login (or worse).
|
104
|
+
# attacks if the <tt>user_name</tt> and +password+ parameters come directly from a HTTP request. The <tt>authenticate_safely</tt> and
|
105
|
+
# <tt>authenticate_safely_simply</tt> both will sanitize the <tt>user_name</tt> and +password+ before inserting them in the query,
|
106
|
+
# which will ensure that an attacker can't escape the query and fake the login (or worse).
|
101
107
|
#
|
102
108
|
# When using multiple parameters in the conditions, it can easily become hard to read exactly what the fourth or fifth
|
103
109
|
# question mark is supposed to represent. In those cases, you can resort to named bind variables instead. That's done by replacing
|
@@ -108,6 +114,13 @@ module ActiveRecord #:nodoc:
|
|
108
114
|
# { :id => 3, :name => "37signals", :division => "First", :accounting_date => '2005-01-01' }
|
109
115
|
# ])
|
110
116
|
#
|
117
|
+
# Similarly, a simple hash without a statement will generate conditions based on equality with the SQL AND
|
118
|
+
# operator. For instance:
|
119
|
+
#
|
120
|
+
# Student.find(:all, :conditions => { :first_name => "Harvey", :status => 1 })
|
121
|
+
# Student.find(:all, :conditions => params[:student])
|
122
|
+
#
|
123
|
+
#
|
111
124
|
# == Overwriting default accessors
|
112
125
|
#
|
113
126
|
# All column values are automatically available through basic accessors on the Active Record object, but some times you
|
@@ -166,6 +179,12 @@ module ActiveRecord #:nodoc:
|
|
166
179
|
# # Now the 'Summer' tag does exist
|
167
180
|
# Tag.find_or_create_by_name("Summer") # equal to Tag.find_by_name("Summer")
|
168
181
|
#
|
182
|
+
# Use the <tt>find_or_initialize_by_</tt> finder if you want to return a new record without saving it first. Example:
|
183
|
+
#
|
184
|
+
# # No 'Winter' tag exists
|
185
|
+
# winter = Tag.find_or_initialize_by_name("Winter")
|
186
|
+
# winter.new_record? # true
|
187
|
+
#
|
169
188
|
# == Saving arrays, hashes, and other non-mappable objects in text columns
|
170
189
|
#
|
171
190
|
# 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+.
|
@@ -245,7 +264,7 @@ module ActiveRecord #:nodoc:
|
|
245
264
|
# on to any new database connections made and which can be retrieved on both a class and instance level by calling +logger+.
|
246
265
|
cattr_accessor :logger
|
247
266
|
|
248
|
-
include Reloadable::
|
267
|
+
include Reloadable::Deprecated
|
249
268
|
|
250
269
|
def self.inherited(child) #:nodoc:
|
251
270
|
@@subclasses[self] ||= []
|
@@ -256,7 +275,7 @@ module ActiveRecord #:nodoc:
|
|
256
275
|
def self.reset_subclasses #:nodoc:
|
257
276
|
nonreloadables = []
|
258
277
|
subclasses.each do |klass|
|
259
|
-
unless
|
278
|
+
unless Dependencies.autoloaded? klass
|
260
279
|
nonreloadables << klass
|
261
280
|
next
|
262
281
|
end
|
@@ -351,7 +370,11 @@ module ActiveRecord #:nodoc:
|
|
351
370
|
# to already defined associations. See eager loading under Associations.
|
352
371
|
# * <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
|
353
372
|
# include the joined columns.
|
373
|
+
# * <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
|
374
|
+
# of a database view).
|
354
375
|
# * <tt>:readonly</tt>: Mark the returned records read-only so they cannot be saved or updated.
|
376
|
+
# * <tt>:lock</tt>: An SQL fragment like "FOR UPDATE" or "LOCK IN SHARE MODE".
|
377
|
+
# :lock => true gives connection's default exclusive lock, usually "FOR UPDATE".
|
355
378
|
#
|
356
379
|
# Examples for find by id:
|
357
380
|
# Person.find(1) # returns the object for ID = 1
|
@@ -371,6 +394,17 @@ module ActiveRecord #:nodoc:
|
|
371
394
|
# Person.find(:all, :offset => 10, :limit => 10)
|
372
395
|
# Person.find(:all, :include => [ :account, :friends ])
|
373
396
|
# Person.find(:all, :group => "category")
|
397
|
+
#
|
398
|
+
# Example for find with a lock. Imagine two concurrent transactions:
|
399
|
+
# each will read person.visits == 2, add 1 to it, and save, resulting
|
400
|
+
# in two saves of person.visits = 3. By locking the row, the second
|
401
|
+
# transaction has to wait until the first is finished; we get the
|
402
|
+
# expected person.visits == 4.
|
403
|
+
# Person.transaction do
|
404
|
+
# person = Person.find(1, :lock => true)
|
405
|
+
# person.visits += 1
|
406
|
+
# person.save!
|
407
|
+
# end
|
374
408
|
def find(*args)
|
375
409
|
options = extract_options_from_args!(args)
|
376
410
|
validate_find_options(options)
|
@@ -391,10 +425,16 @@ module ActiveRecord #:nodoc:
|
|
391
425
|
end
|
392
426
|
|
393
427
|
# Returns true if the given +id+ represents the primary key of a record in the database, false otherwise.
|
428
|
+
# You can also pass a set of SQL conditions.
|
394
429
|
# Example:
|
395
430
|
# Person.exists?(5)
|
396
|
-
|
397
|
-
|
431
|
+
# Person.exists?('5')
|
432
|
+
# Person.exists?(:name => "David")
|
433
|
+
# Person.exists?(['name LIKE ?', "%#{query}%"])
|
434
|
+
def exists?(id_or_conditions)
|
435
|
+
!find(:first, :conditions => expand_id_conditions(id_or_conditions)).nil?
|
436
|
+
rescue ActiveRecord::ActiveRecordError
|
437
|
+
false
|
398
438
|
end
|
399
439
|
|
400
440
|
# Creates an object, instantly saves it as a record (if the validation permits it), and returns it. If the save
|
@@ -483,12 +523,12 @@ module ActiveRecord #:nodoc:
|
|
483
523
|
# for looping over a collection where each element require a number of aggregate values. Like the DiscussionBoard
|
484
524
|
# that needs to list both the number of posts and comments.
|
485
525
|
def increment_counter(counter_name, id)
|
486
|
-
update_all "#{counter_name} = #{counter_name} + 1", "#{primary_key} = #{
|
526
|
+
update_all "#{counter_name} = #{counter_name} + 1", "#{primary_key} = #{quote_value(id)}"
|
487
527
|
end
|
488
528
|
|
489
529
|
# Works like increment_counter, but decrements instead.
|
490
530
|
def decrement_counter(counter_name, id)
|
491
|
-
update_all "#{counter_name} = #{counter_name} - 1", "#{primary_key} = #{
|
531
|
+
update_all "#{counter_name} = #{counter_name} - 1", "#{primary_key} = #{quote_value(id)}"
|
492
532
|
end
|
493
533
|
|
494
534
|
|
@@ -548,21 +588,44 @@ module ActiveRecord #:nodoc:
|
|
548
588
|
# to guess the table name from even when called on Reply. The rules used to do the guess are handled by the Inflector class
|
549
589
|
# in Active Support, which knows almost all common English inflections (report a bug if your inflection isn't covered).
|
550
590
|
#
|
551
|
-
#
|
552
|
-
#
|
591
|
+
# Nested classes are given table names prefixed by the singular form of
|
592
|
+
# the parent's table name. Example:
|
593
|
+
# file class table_name
|
594
|
+
# invoice.rb Invoice invoices
|
595
|
+
# invoice/lineitem.rb Invoice::Lineitem invoice_lineitems
|
596
|
+
#
|
597
|
+
# Additionally, the class-level table_name_prefix is prepended and the
|
598
|
+
# table_name_suffix is appended. So if you have "myapp_" as a prefix,
|
599
|
+
# the table name guess for an Invoice class becomes "myapp_invoices".
|
600
|
+
# Invoice::Lineitem becomes "myapp_invoice_lineitems".
|
553
601
|
#
|
554
|
-
# You can also overwrite this class method to allow for unguessable
|
555
|
-
# "mice" table. Example:
|
602
|
+
# You can also overwrite this class method to allow for unguessable
|
603
|
+
# links, such as a Mouse class with a link to a "mice" table. Example:
|
556
604
|
#
|
557
605
|
# class Mouse < ActiveRecord::Base
|
558
|
-
#
|
606
|
+
# set_table_name "mice"
|
559
607
|
# end
|
560
608
|
def table_name
|
561
609
|
reset_table_name
|
562
610
|
end
|
563
611
|
|
564
612
|
def reset_table_name #:nodoc:
|
565
|
-
|
613
|
+
base = base_class
|
614
|
+
|
615
|
+
name =
|
616
|
+
# STI subclasses always use their superclass' table.
|
617
|
+
unless self == base
|
618
|
+
base.table_name
|
619
|
+
else
|
620
|
+
# Nested classes are prefixed with singular parent table name.
|
621
|
+
if parent < ActiveRecord::Base && !parent.abstract_class?
|
622
|
+
contained = parent.table_name
|
623
|
+
contained = contained.singularize if parent.pluralize_table_names
|
624
|
+
contained << '_'
|
625
|
+
end
|
626
|
+
name = "#{table_name_prefix}#{contained}#{undecorated_table_name(base.name)}#{table_name_suffix}"
|
627
|
+
end
|
628
|
+
|
566
629
|
set_table_name(name)
|
567
630
|
name
|
568
631
|
end
|
@@ -585,9 +648,10 @@ module ActiveRecord #:nodoc:
|
|
585
648
|
key
|
586
649
|
end
|
587
650
|
|
588
|
-
# Defines the column name for use with single table inheritance
|
651
|
+
# Defines the column name for use with single table inheritance
|
652
|
+
# -- can be set in subclasses like so: self.inheritance_column = "type_id"
|
589
653
|
def inheritance_column
|
590
|
-
"type"
|
654
|
+
@inheritance_column ||= "type".freeze
|
591
655
|
end
|
592
656
|
|
593
657
|
# Lazy-set the sequence name to the connection's default. This method
|
@@ -699,7 +763,7 @@ module ActiveRecord #:nodoc:
|
|
699
763
|
@columns
|
700
764
|
end
|
701
765
|
|
702
|
-
# Returns
|
766
|
+
# Returns a hash of column objects for the table associated with this class.
|
703
767
|
def columns_hash
|
704
768
|
@columns_hash ||= columns.inject({}) { |hash, column| hash[column.name] = column; hash }
|
705
769
|
end
|
@@ -737,7 +801,7 @@ module ActiveRecord #:nodoc:
|
|
737
801
|
# Resets all the cached information about columns, which will cause them to be reloaded on the next request.
|
738
802
|
def reset_column_information
|
739
803
|
read_methods.each { |name| undef_method(name) }
|
740
|
-
@column_names = @columns = @columns_hash = @content_columns = @dynamic_methods_hash = @read_methods = nil
|
804
|
+
@column_names = @columns = @columns_hash = @content_columns = @dynamic_methods_hash = @read_methods = @inheritance_column = nil
|
741
805
|
end
|
742
806
|
|
743
807
|
def reset_column_information_and_inheritable_attributes_for_all_subclasses#:nodoc:
|
@@ -755,9 +819,15 @@ module ActiveRecord #:nodoc:
|
|
755
819
|
superclass == Base || !columns_hash.include?(inheritance_column)
|
756
820
|
end
|
757
821
|
|
758
|
-
|
759
|
-
|
822
|
+
|
823
|
+
def quote_value(value, column = nil) #:nodoc:
|
824
|
+
connection.quote(value,column)
|
825
|
+
end
|
826
|
+
|
827
|
+
def quote(value, column = nil) #:nodoc:
|
828
|
+
connection.quote(value, column)
|
760
829
|
end
|
830
|
+
deprecate :quote => :quote_value
|
761
831
|
|
762
832
|
# Used to sanitize objects before they're used in an SELECT SQL-statement. Delegates to <tt>connection.quote</tt>.
|
763
833
|
def sanitize(object) #:nodoc:
|
@@ -837,7 +907,7 @@ module ActiveRecord #:nodoc:
|
|
837
907
|
method_scoping.assert_valid_keys([ :find, :create ])
|
838
908
|
|
839
909
|
if f = method_scoping[:find]
|
840
|
-
f.assert_valid_keys([ :conditions, :joins, :select, :include, :from, :offset, :limit, :readonly ])
|
910
|
+
f.assert_valid_keys([ :conditions, :joins, :select, :include, :from, :offset, :limit, :order, :readonly, :lock ])
|
841
911
|
f[:readonly] = true if !f[:joins].blank? && !f.has_key?(:readonly)
|
842
912
|
end
|
843
913
|
|
@@ -917,7 +987,7 @@ module ActiveRecord #:nodoc:
|
|
917
987
|
options.update(:limit => 1) unless options[:include]
|
918
988
|
find_every(options).first
|
919
989
|
end
|
920
|
-
|
990
|
+
|
921
991
|
def find_every(options)
|
922
992
|
records = scoped?(:find, :include) || options[:include] ?
|
923
993
|
find_with_associations(options) :
|
@@ -927,11 +997,11 @@ module ActiveRecord #:nodoc:
|
|
927
997
|
|
928
998
|
records
|
929
999
|
end
|
930
|
-
|
1000
|
+
|
931
1001
|
def find_from_ids(ids, options)
|
932
|
-
expects_array = ids.first.kind_of?(Array)
|
1002
|
+
expects_array = ids.first.kind_of?(Array)
|
933
1003
|
return ids.first if expects_array && ids.first.empty?
|
934
|
-
|
1004
|
+
|
935
1005
|
ids = ids.flatten.compact.uniq
|
936
1006
|
|
937
1007
|
case ids.size
|
@@ -947,9 +1017,12 @@ module ActiveRecord #:nodoc:
|
|
947
1017
|
|
948
1018
|
def find_one(id, options)
|
949
1019
|
conditions = " AND (#{sanitize_sql(options[:conditions])})" if options[:conditions]
|
950
|
-
options.update :conditions => "#{table_name}.#{primary_key} = #{
|
1020
|
+
options.update :conditions => "#{table_name}.#{primary_key} = #{quote_value(id,columns_hash[primary_key])}#{conditions}"
|
951
1021
|
|
952
|
-
|
1022
|
+
# Use find_every(options).first since the primary key condition
|
1023
|
+
# already ensures we have a single record. Using find_initial adds
|
1024
|
+
# a superfluous :limit => 1.
|
1025
|
+
if result = find_every(options).first
|
953
1026
|
result
|
954
1027
|
else
|
955
1028
|
raise RecordNotFound, "Couldn't find #{name} with ID=#{id}#{conditions}"
|
@@ -958,7 +1031,7 @@ module ActiveRecord #:nodoc:
|
|
958
1031
|
|
959
1032
|
def find_some(ids, options)
|
960
1033
|
conditions = " AND (#{sanitize_sql(options[:conditions])})" if options[:conditions]
|
961
|
-
ids_list = ids.map { |id|
|
1034
|
+
ids_list = ids.map { |id| quote_value(id,columns_hash[primary_key]) }.join(',')
|
962
1035
|
options.update :conditions => "#{table_name}.#{primary_key} IN (#{ids_list})#{conditions}"
|
963
1036
|
|
964
1037
|
result = find_every(options)
|
@@ -970,23 +1043,32 @@ module ActiveRecord #:nodoc:
|
|
970
1043
|
end
|
971
1044
|
end
|
972
1045
|
|
973
|
-
# Finder methods must instantiate through this method to work with the
|
974
|
-
# that makes it possible to create
|
1046
|
+
# Finder methods must instantiate through this method to work with the
|
1047
|
+
# single-table inheritance model that makes it possible to create
|
1048
|
+
# objects of different types from the same table.
|
975
1049
|
def instantiate(record)
|
976
|
-
object =
|
1050
|
+
object =
|
977
1051
|
if subclass_name = record[inheritance_column]
|
1052
|
+
# No type given.
|
978
1053
|
if subclass_name.empty?
|
979
1054
|
allocate
|
1055
|
+
|
980
1056
|
else
|
981
|
-
|
982
|
-
|
983
|
-
|
984
|
-
|
985
|
-
|
986
|
-
|
987
|
-
|
988
|
-
|
989
|
-
|
1057
|
+
# Ignore type if no column is present since it was probably
|
1058
|
+
# pulled in from a sloppy join.
|
1059
|
+
unless columns_hash.include?(inheritance_column)
|
1060
|
+
allocate
|
1061
|
+
|
1062
|
+
else
|
1063
|
+
begin
|
1064
|
+
compute_type(subclass_name).allocate
|
1065
|
+
rescue NameError
|
1066
|
+
raise SubclassNotFound,
|
1067
|
+
"The single-table inheritance mechanism failed to locate the subclass: '#{record[inheritance_column]}'. " +
|
1068
|
+
"This error is raised because the column '#{inheritance_column}' is reserved for storing the class in case of inheritance. " +
|
1069
|
+
"Please rename this column if you didn't intend it to be used for storing the inheritance class " +
|
1070
|
+
"or overwrite #{self.to_s}.inheritance_column to use another column for that information."
|
1071
|
+
end
|
990
1072
|
end
|
991
1073
|
end
|
992
1074
|
else
|
@@ -1012,19 +1094,20 @@ module ActiveRecord #:nodoc:
|
|
1012
1094
|
add_conditions!(sql, options[:conditions], scope)
|
1013
1095
|
|
1014
1096
|
sql << " GROUP BY #{options[:group]} " if options[:group]
|
1015
|
-
sql << " ORDER BY #{options[:order]} " if options[:order]
|
1016
1097
|
|
1098
|
+
add_order!(sql, options[:order], scope)
|
1017
1099
|
add_limit!(sql, options, scope)
|
1100
|
+
add_lock!(sql, options, scope)
|
1018
1101
|
|
1019
1102
|
sql
|
1020
1103
|
end
|
1021
1104
|
|
1022
1105
|
# Merges includes so that the result is a valid +include+
|
1023
1106
|
def merge_includes(first, second)
|
1024
|
-
safe_to_array(first) + safe_to_array(second)
|
1107
|
+
(safe_to_array(first) + safe_to_array(second)).uniq
|
1025
1108
|
end
|
1026
1109
|
|
1027
|
-
# Object#to_a is deprecated, though it does have the desired
|
1110
|
+
# Object#to_a is deprecated, though it does have the desired behavior
|
1028
1111
|
def safe_to_array(o)
|
1029
1112
|
case o
|
1030
1113
|
when NilClass
|
@@ -1036,16 +1119,32 @@ module ActiveRecord #:nodoc:
|
|
1036
1119
|
end
|
1037
1120
|
end
|
1038
1121
|
|
1122
|
+
def add_order!(sql, order, scope = :auto)
|
1123
|
+
scope = scope(:find) if :auto == scope
|
1124
|
+
scoped_order = scope[:order] if scope
|
1125
|
+
if order
|
1126
|
+
sql << " ORDER BY #{order}"
|
1127
|
+
sql << ", #{scoped_order}" if scoped_order
|
1128
|
+
else
|
1129
|
+
sql << " ORDER BY #{scoped_order}" if scoped_order
|
1130
|
+
end
|
1131
|
+
end
|
1132
|
+
|
1039
1133
|
# The optional scope argument is for the current :find scope.
|
1040
1134
|
def add_limit!(sql, options, scope = :auto)
|
1041
1135
|
scope = scope(:find) if :auto == scope
|
1042
|
-
if scope
|
1043
|
-
options[:limit] ||= scope[:limit]
|
1044
|
-
options[:offset] ||= scope[:offset]
|
1045
|
-
end
|
1136
|
+
options = options.reverse_merge(:limit => scope[:limit], :offset => scope[:offset]) if scope
|
1046
1137
|
connection.add_limit_offset!(sql, options)
|
1047
1138
|
end
|
1048
1139
|
|
1140
|
+
# The optional scope argument is for the current :find scope.
|
1141
|
+
# The :lock option has precedence over a scoped :lock.
|
1142
|
+
def add_lock!(sql, options, scope = :auto)
|
1143
|
+
scope = scope(:find) if :auto == scope
|
1144
|
+
options = options.reverse_merge(:lock => scope[:lock]) if scope
|
1145
|
+
connection.add_lock!(sql, options)
|
1146
|
+
end
|
1147
|
+
|
1049
1148
|
# The optional scope argument is for the current :find scope.
|
1050
1149
|
def add_joins!(sql, options, scope = :auto)
|
1051
1150
|
scope = scope(:find) if :auto == scope
|
@@ -1060,7 +1159,7 @@ module ActiveRecord #:nodoc:
|
|
1060
1159
|
segments = []
|
1061
1160
|
segments << sanitize_sql(scope[:conditions]) if scope && scope[:conditions]
|
1062
1161
|
segments << sanitize_sql(conditions) unless conditions.nil?
|
1063
|
-
segments << type_condition unless descends_from_active_record?
|
1162
|
+
segments << type_condition unless descends_from_active_record?
|
1064
1163
|
segments.compact!
|
1065
1164
|
sql << "WHERE (#{segments.join(") AND (")}) " unless segments.empty?
|
1066
1165
|
end
|
@@ -1088,43 +1187,48 @@ module ActiveRecord #:nodoc:
|
|
1088
1187
|
# It's even possible to use all the additional parameters to find. For example, the full interface for find_all_by_amount
|
1089
1188
|
# is actually find_all_by_amount(amount, options).
|
1090
1189
|
def method_missing(method_id, *arguments)
|
1091
|
-
if match =
|
1190
|
+
if match = /^find_(all_by|by)_([_a-zA-Z]\w*)$/.match(method_id.to_s)
|
1092
1191
|
finder, deprecated_finder = determine_finder(match), determine_deprecated_finder(match)
|
1093
1192
|
|
1094
1193
|
attribute_names = extract_attribute_names_from_match(match)
|
1095
1194
|
super unless all_attributes_exists?(attribute_names)
|
1096
1195
|
|
1097
|
-
|
1196
|
+
attributes = construct_attributes_from_arguments(attribute_names, arguments)
|
1098
1197
|
|
1099
1198
|
case extra_options = arguments[attribute_names.size]
|
1100
1199
|
when nil
|
1101
|
-
options = { :conditions =>
|
1200
|
+
options = { :conditions => attributes }
|
1102
1201
|
set_readonly_option!(options)
|
1103
|
-
send(finder, options)
|
1202
|
+
ActiveSupport::Deprecation.silence { send(finder, options) }
|
1104
1203
|
|
1105
1204
|
when Hash
|
1106
|
-
finder_options = extra_options.merge(:conditions =>
|
1205
|
+
finder_options = extra_options.merge(:conditions => attributes)
|
1107
1206
|
validate_find_options(finder_options)
|
1108
1207
|
set_readonly_option!(finder_options)
|
1109
1208
|
|
1110
1209
|
if extra_options[:conditions]
|
1111
1210
|
with_scope(:find => { :conditions => extra_options[:conditions] }) do
|
1112
|
-
send(finder, finder_options)
|
1211
|
+
ActiveSupport::Deprecation.silence { send(finder, finder_options) }
|
1113
1212
|
end
|
1114
1213
|
else
|
1115
|
-
send(finder, finder_options)
|
1214
|
+
ActiveSupport::Deprecation.silence { send(finder, finder_options) }
|
1116
1215
|
end
|
1117
1216
|
|
1118
1217
|
else
|
1119
|
-
|
1218
|
+
ActiveSupport::Deprecation.silence do
|
1219
|
+
send(deprecated_finder, sanitize_sql(attributes), *arguments[attribute_names.length..-1])
|
1220
|
+
end
|
1120
1221
|
end
|
1121
|
-
elsif match =
|
1222
|
+
elsif match = /^find_or_(initialize|create)_by_([_a-zA-Z]\w*)$/.match(method_id.to_s)
|
1223
|
+
instantiator = determine_instantiator(match)
|
1122
1224
|
attribute_names = extract_attribute_names_from_match(match)
|
1123
1225
|
super unless all_attributes_exists?(attribute_names)
|
1124
1226
|
|
1125
|
-
|
1227
|
+
attributes = construct_attributes_from_arguments(attribute_names, arguments)
|
1228
|
+
options = { :conditions => attributes }
|
1126
1229
|
set_readonly_option!(options)
|
1127
|
-
|
1230
|
+
|
1231
|
+
find_initial(options) || send(instantiator, attributes)
|
1128
1232
|
else
|
1129
1233
|
super
|
1130
1234
|
end
|
@@ -1138,16 +1242,14 @@ module ActiveRecord #:nodoc:
|
|
1138
1242
|
match.captures.first == 'all_by' ? :find_all : :find_first
|
1139
1243
|
end
|
1140
1244
|
|
1245
|
+
def determine_instantiator(match)
|
1246
|
+
match.captures.first == 'initialize' ? :new : :create
|
1247
|
+
end
|
1248
|
+
|
1141
1249
|
def extract_attribute_names_from_match(match)
|
1142
1250
|
match.captures.last.split('_and_')
|
1143
1251
|
end
|
1144
1252
|
|
1145
|
-
def construct_conditions_from_arguments(attribute_names, arguments)
|
1146
|
-
conditions = []
|
1147
|
-
attribute_names.each_with_index { |name, idx| conditions << "#{table_name}.#{connection.quote_column_name(name)} #{attribute_condition(arguments[idx])} " }
|
1148
|
-
[ conditions.join(" AND "), *arguments[0...attribute_names.length] ]
|
1149
|
-
end
|
1150
|
-
|
1151
1253
|
def construct_attributes_from_arguments(attribute_names, arguments)
|
1152
1254
|
attributes = {}
|
1153
1255
|
attribute_names.each_with_index { |name, idx| attributes[name] = arguments[idx] }
|
@@ -1166,6 +1268,15 @@ module ActiveRecord #:nodoc:
|
|
1166
1268
|
end
|
1167
1269
|
end
|
1168
1270
|
|
1271
|
+
# Interpret Array and Hash as conditions and anything else as an id.
|
1272
|
+
def expand_id_conditions(id_or_conditions)
|
1273
|
+
case id_or_conditions
|
1274
|
+
when Array, Hash then id_or_conditions
|
1275
|
+
else sanitize_sql(primary_key => id_or_conditions)
|
1276
|
+
end
|
1277
|
+
end
|
1278
|
+
|
1279
|
+
|
1169
1280
|
# Defines an "attribute" method (like #inheritance_column or
|
1170
1281
|
# #table_name). A new (class) method will be created with the
|
1171
1282
|
# given name. If a value is specified, the new method will
|
@@ -1241,9 +1352,9 @@ module ActiveRecord #:nodoc:
|
|
1241
1352
|
def compute_type(type_name)
|
1242
1353
|
modularized_name = type_name_with_module(type_name)
|
1243
1354
|
begin
|
1244
|
-
|
1245
|
-
rescue NameError
|
1246
|
-
|
1355
|
+
class_eval(modularized_name, __FILE__, __LINE__)
|
1356
|
+
rescue NameError
|
1357
|
+
class_eval(type_name, __FILE__, __LINE__)
|
1247
1358
|
end
|
1248
1359
|
end
|
1249
1360
|
|
@@ -1263,12 +1374,36 @@ module ActiveRecord #:nodoc:
|
|
1263
1374
|
klass.base_class.name
|
1264
1375
|
end
|
1265
1376
|
|
1266
|
-
# Accepts an array or string
|
1267
|
-
#
|
1377
|
+
# Accepts an array, hash, or string of sql conditions and sanitizes
|
1378
|
+
# them into a valid SQL fragment.
|
1268
1379
|
# ["name='%s' and group_id='%s'", "foo'bar", 4] returns "name='foo''bar' and group_id='4'"
|
1269
|
-
|
1270
|
-
|
1380
|
+
# { :name => "foo'bar", :group_id => 4 } returns "name='foo''bar' and group_id='4'"
|
1381
|
+
# "name='foo''bar' and group_id='4'" returns "name='foo''bar' and group_id='4'"
|
1382
|
+
def sanitize_sql(condition)
|
1383
|
+
case condition
|
1384
|
+
when Array; sanitize_sql_array(condition)
|
1385
|
+
when Hash; sanitize_sql_hash(condition)
|
1386
|
+
else condition
|
1387
|
+
end
|
1388
|
+
end
|
1271
1389
|
|
1390
|
+
# Sanitizes a hash of attribute/value pairs into SQL conditions.
|
1391
|
+
# { :name => "foo'bar", :group_id => 4 }
|
1392
|
+
# # => "name='foo''bar' and group_id= 4"
|
1393
|
+
# { :status => nil, :group_id => [1,2,3] }
|
1394
|
+
# # => "status IS NULL and group_id IN (1,2,3)"
|
1395
|
+
def sanitize_sql_hash(attrs)
|
1396
|
+
conditions = attrs.map do |attr, value|
|
1397
|
+
"#{table_name}.#{connection.quote_column_name(attr)} #{attribute_condition(value)}"
|
1398
|
+
end.join(' AND ')
|
1399
|
+
|
1400
|
+
replace_bind_variables(conditions, attrs.values)
|
1401
|
+
end
|
1402
|
+
|
1403
|
+
# Accepts an array of conditions. The array has each value
|
1404
|
+
# sanitized and interpolated into the sql statement.
|
1405
|
+
# ["name='%s' and group_id='%s'", "foo'bar", 4] returns "name='foo''bar' and group_id='4'"
|
1406
|
+
def sanitize_sql_array(ary)
|
1272
1407
|
statement, *values = ary
|
1273
1408
|
if values.first.is_a?(Hash) and statement =~ /:\w+/
|
1274
1409
|
replace_named_bind_variables(statement, values.first)
|
@@ -1299,8 +1434,12 @@ module ActiveRecord #:nodoc:
|
|
1299
1434
|
end
|
1300
1435
|
|
1301
1436
|
def quote_bound_value(value) #:nodoc:
|
1302
|
-
if
|
1303
|
-
value.
|
1437
|
+
if value.respond_to?(:map) && !value.is_a?(String)
|
1438
|
+
if value.respond_to?(:empty?) && value.empty?
|
1439
|
+
connection.quote(nil)
|
1440
|
+
else
|
1441
|
+
value.map { |v| connection.quote(v) }.join(',')
|
1442
|
+
end
|
1304
1443
|
else
|
1305
1444
|
connection.quote(value)
|
1306
1445
|
end
|
@@ -1317,12 +1456,12 @@ module ActiveRecord #:nodoc:
|
|
1317
1456
|
end
|
1318
1457
|
|
1319
1458
|
VALID_FIND_OPTIONS = [ :conditions, :include, :joins, :limit, :offset,
|
1320
|
-
:order, :select, :readonly, :group, :from
|
1321
|
-
|
1459
|
+
:order, :select, :readonly, :group, :from, :lock ]
|
1460
|
+
|
1322
1461
|
def validate_find_options(options) #:nodoc:
|
1323
1462
|
options.assert_valid_keys(VALID_FIND_OPTIONS)
|
1324
1463
|
end
|
1325
|
-
|
1464
|
+
|
1326
1465
|
def set_readonly_option!(options) #:nodoc:
|
1327
1466
|
# Inherit :readonly from finder scope if set. Otherwise,
|
1328
1467
|
# if :joins is not blank then :readonly defaults to true.
|
@@ -1365,14 +1504,17 @@ module ActiveRecord #:nodoc:
|
|
1365
1504
|
end
|
1366
1505
|
|
1367
1506
|
# Enables Active Record objects to be used as URL parameters in Action Pack automatically.
|
1368
|
-
|
1507
|
+
def to_param
|
1508
|
+
# We can't use alias_method here, because method 'id' optimizes itself on the fly.
|
1509
|
+
(id = self.id) ? id.to_s : nil # Be sure to stringify the id for routes
|
1510
|
+
end
|
1369
1511
|
|
1370
1512
|
def id_before_type_cast #:nodoc:
|
1371
1513
|
read_attribute_before_type_cast(self.class.primary_key)
|
1372
1514
|
end
|
1373
1515
|
|
1374
1516
|
def quoted_id #:nodoc:
|
1375
|
-
|
1517
|
+
quote_value(id, column_for_attribute(self.class.primary_key))
|
1376
1518
|
end
|
1377
1519
|
|
1378
1520
|
# Sets the primary ID.
|
@@ -1388,14 +1530,13 @@ module ActiveRecord #:nodoc:
|
|
1388
1530
|
# * No record exists: Creates a new record with values matching those of the object attributes.
|
1389
1531
|
# * A record does exist: Updates the record with values matching those of the object attributes.
|
1390
1532
|
def save
|
1391
|
-
raise ReadOnlyRecord if readonly?
|
1392
1533
|
create_or_update
|
1393
1534
|
end
|
1394
1535
|
|
1395
1536
|
# Attempts to save the record, but instead of just returning false if it couldn't happen, it raises a
|
1396
1537
|
# RecordNotSaved exception
|
1397
1538
|
def save!
|
1398
|
-
|
1539
|
+
create_or_update || raise(RecordNotSaved)
|
1399
1540
|
end
|
1400
1541
|
|
1401
1542
|
# Deletes the record in the database and freezes this instance to reflect that no changes should
|
@@ -1438,6 +1579,12 @@ module ActiveRecord #:nodoc:
|
|
1438
1579
|
self.attributes = attributes
|
1439
1580
|
save
|
1440
1581
|
end
|
1582
|
+
|
1583
|
+
# Updates an object just like Base.update_attributes but calls save! instead of save so an exception is raised if the record is invalid.
|
1584
|
+
def update_attributes!(attributes)
|
1585
|
+
self.attributes = attributes
|
1586
|
+
save!
|
1587
|
+
end
|
1441
1588
|
|
1442
1589
|
# Initializes the +attribute+ to zero if nil and adds one. Only makes sense for number-based attributes. Returns self.
|
1443
1590
|
def increment(attribute)
|
@@ -1475,10 +1622,13 @@ module ActiveRecord #:nodoc:
|
|
1475
1622
|
end
|
1476
1623
|
|
1477
1624
|
# Reloads the attributes of this object from the database.
|
1478
|
-
|
1625
|
+
# The optional options argument is passed to find when reloading so you
|
1626
|
+
# may do e.g. record.reload(:lock => true) to reload the same record with
|
1627
|
+
# an exclusive row lock.
|
1628
|
+
def reload(options = nil)
|
1479
1629
|
clear_aggregation_cache
|
1480
1630
|
clear_association_cache
|
1481
|
-
@attributes.update(self.class.find(self.id).instance_variable_get('@attributes'))
|
1631
|
+
@attributes.update(self.class.find(self.id, options).instance_variable_get('@attributes'))
|
1482
1632
|
self
|
1483
1633
|
end
|
1484
1634
|
|
@@ -1588,13 +1738,13 @@ module ActiveRecord #:nodoc:
|
|
1588
1738
|
# person.respond_to?("name?") which will all return true.
|
1589
1739
|
def respond_to?(method, include_priv = false)
|
1590
1740
|
if @attributes.nil?
|
1591
|
-
return super
|
1741
|
+
return super
|
1592
1742
|
elsif attr_name = self.class.column_methods_hash[method.to_sym]
|
1593
1743
|
return true if @attributes.include?(attr_name) || attr_name == self.class.primary_key
|
1594
1744
|
return false if self.class.read_methods.include?(attr_name)
|
1595
1745
|
elsif @attributes.include?(method_name = method.to_s)
|
1596
1746
|
return true
|
1597
|
-
elsif md =
|
1747
|
+
elsif md = self.class.match_attribute_method?(method.to_s)
|
1598
1748
|
return true if @attributes.include?(md.pre_match)
|
1599
1749
|
end
|
1600
1750
|
# super must be called at the end of the method, because the inherited respond_to?
|
@@ -1620,117 +1770,27 @@ module ActiveRecord #:nodoc:
|
|
1620
1770
|
@readonly = true
|
1621
1771
|
end
|
1622
1772
|
|
1623
|
-
# Builds an XML document to represent the model. Some configuration is
|
1624
|
-
# availble through +options+, however more complicated cases should use
|
1625
|
-
# Builder.
|
1626
|
-
#
|
1627
|
-
# By default the generated XML document will include the processing
|
1628
|
-
# instruction and all object's attributes. For example:
|
1629
|
-
#
|
1630
|
-
# <?xml version="1.0" encoding="UTF-8"?>
|
1631
|
-
# <topic>
|
1632
|
-
# <title>The First Topic</title>
|
1633
|
-
# <author-name>David</author-name>
|
1634
|
-
# <id type="integer">1</id>
|
1635
|
-
# <approved type="boolean">false</approved>
|
1636
|
-
# <replies-count type="integer">0</replies-count>
|
1637
|
-
# <bonus-time type="datetime">2000-01-01T08:28:00+12:00</bonus-time>
|
1638
|
-
# <written-on type="datetime">2003-07-16T09:28:00+1200</written-on>
|
1639
|
-
# <content>Have a nice day</content>
|
1640
|
-
# <author-email-address>david@loudthinking.com</author-email-address>
|
1641
|
-
# <parent-id></parent-id>
|
1642
|
-
# <last-read type="date">2004-04-15</last-read>
|
1643
|
-
# </topic>
|
1644
|
-
#
|
1645
|
-
# This behaviour can be controlled with :only, :except, and :skip_instruct
|
1646
|
-
# for instance:
|
1647
|
-
#
|
1648
|
-
# topic.to_xml(:skip_instruct => true, :except => [ :id, bonus_time, :written_on, replies_count ])
|
1649
|
-
#
|
1650
|
-
# <topic>
|
1651
|
-
# <title>The First Topic</title>
|
1652
|
-
# <author-name>David</author-name>
|
1653
|
-
# <approved type="boolean">false</approved>
|
1654
|
-
# <content>Have a nice day</content>
|
1655
|
-
# <author-email-address>david@loudthinking.com</author-email-address>
|
1656
|
-
# <parent-id></parent-id>
|
1657
|
-
# <last-read type="date">2004-04-15</last-read>
|
1658
|
-
# </topic>
|
1659
|
-
#
|
1660
|
-
# To include first level associations use :include
|
1661
|
-
#
|
1662
|
-
# firm.to_xml :include => [ :account, :clients ]
|
1663
|
-
#
|
1664
|
-
# <?xml version="1.0" encoding="UTF-8"?>
|
1665
|
-
# <firm>
|
1666
|
-
# <id type="integer">1</id>
|
1667
|
-
# <rating type="integer">1</rating>
|
1668
|
-
# <name>37signals</name>
|
1669
|
-
# <clients>
|
1670
|
-
# <client>
|
1671
|
-
# <rating type="integer">1</rating>
|
1672
|
-
# <name>Summit</name>
|
1673
|
-
# </client>
|
1674
|
-
# <client>
|
1675
|
-
# <rating type="integer">1</rating>
|
1676
|
-
# <name>Microsoft</name>
|
1677
|
-
# </client>
|
1678
|
-
# </clients>
|
1679
|
-
# <account>
|
1680
|
-
# <id type="integer">1</id>
|
1681
|
-
# <credit-limit type="integer">50</credit-limit>
|
1682
|
-
# </account>
|
1683
|
-
# </firm>
|
1684
|
-
def to_xml(options = {})
|
1685
|
-
options[:root] ||= self.class.to_s.underscore
|
1686
|
-
options[:except] = Array(options[:except]) << self.class.inheritance_column unless options[:only] # skip type column
|
1687
|
-
root_only_or_except = { :only => options[:only], :except => options[:except] }
|
1688
|
-
|
1689
|
-
attributes_for_xml = attributes(root_only_or_except)
|
1690
|
-
|
1691
|
-
if include_associations = options.delete(:include)
|
1692
|
-
include_has_options = include_associations.is_a?(Hash)
|
1693
|
-
|
1694
|
-
for association in include_has_options ? include_associations.keys : Array(include_associations)
|
1695
|
-
association_options = include_has_options ? include_associations[association] : root_only_or_except
|
1696
|
-
|
1697
|
-
case self.class.reflect_on_association(association).macro
|
1698
|
-
when :has_many, :has_and_belongs_to_many
|
1699
|
-
records = send(association).to_a
|
1700
|
-
unless records.empty?
|
1701
|
-
attributes_for_xml[association] = records.collect do |record|
|
1702
|
-
record.attributes(association_options)
|
1703
|
-
end
|
1704
|
-
end
|
1705
|
-
when :has_one, :belongs_to
|
1706
|
-
if record = send(association)
|
1707
|
-
attributes_for_xml[association] = record.attributes(association_options)
|
1708
|
-
end
|
1709
|
-
end
|
1710
|
-
end
|
1711
|
-
end
|
1712
|
-
|
1713
|
-
attributes_for_xml.to_xml(options)
|
1714
|
-
end
|
1715
1773
|
|
1716
1774
|
private
|
1717
1775
|
def create_or_update
|
1718
|
-
if
|
1776
|
+
raise ReadOnlyRecord if readonly?
|
1777
|
+
result = new_record? ? create : update
|
1778
|
+
result != false
|
1719
1779
|
end
|
1720
1780
|
|
1721
1781
|
# Updates the associated record with values matching those of the instance attributes.
|
1782
|
+
# Returns the number of affected rows.
|
1722
1783
|
def update
|
1723
1784
|
connection.update(
|
1724
1785
|
"UPDATE #{self.class.table_name} " +
|
1725
1786
|
"SET #{quoted_comma_pair_list(connection, attributes_with_quotes(false))} " +
|
1726
|
-
"WHERE #{self.class.primary_key} = #{
|
1787
|
+
"WHERE #{self.class.primary_key} = #{quote_value(id)}",
|
1727
1788
|
"#{self.class.name} Update"
|
1728
1789
|
)
|
1729
|
-
|
1730
|
-
return true
|
1731
1790
|
end
|
1732
1791
|
|
1733
|
-
# Creates a
|
1792
|
+
# Creates a record with values matching those of the instance attributes
|
1793
|
+
# and returns its id.
|
1734
1794
|
def create
|
1735
1795
|
if self.id.nil? && connection.prefetch_primary_key?(self.class.table_name)
|
1736
1796
|
self.id = connection.next_sequence_value(self.class.sequence_name)
|
@@ -1745,8 +1805,7 @@ module ActiveRecord #:nodoc:
|
|
1745
1805
|
)
|
1746
1806
|
|
1747
1807
|
@new_record = false
|
1748
|
-
|
1749
|
-
return true
|
1808
|
+
id
|
1750
1809
|
end
|
1751
1810
|
|
1752
1811
|
# Sets the attribute used for single table inheritance to this class name if this is not the ActiveRecord descendent.
|
@@ -1759,6 +1818,7 @@ module ActiveRecord #:nodoc:
|
|
1759
1818
|
end
|
1760
1819
|
end
|
1761
1820
|
|
1821
|
+
|
1762
1822
|
# Allows access to the object attributes, which are held in the @attributes hash, as were
|
1763
1823
|
# they first-class methods. So a Person class with a name attribute can use Person#name and
|
1764
1824
|
# Person#name= and never directly use the attributes hash -- except for multiple assigns with
|
@@ -1771,20 +1831,16 @@ module ActiveRecord #:nodoc:
|
|
1771
1831
|
method_name = method_id.to_s
|
1772
1832
|
if @attributes.include?(method_name) or
|
1773
1833
|
(md = /\?$/.match(method_name) and
|
1774
|
-
@attributes.include?(
|
1834
|
+
@attributes.include?(query_method_name = md.pre_match) and
|
1835
|
+
method_name = query_method_name)
|
1775
1836
|
define_read_methods if self.class.read_methods.empty? && self.class.generate_read_methods
|
1776
1837
|
md ? query_attribute(method_name) : read_attribute(method_name)
|
1777
1838
|
elsif self.class.primary_key.to_s == method_name
|
1778
1839
|
id
|
1779
|
-
elsif md =
|
1840
|
+
elsif md = self.class.match_attribute_method?(method_name)
|
1780
1841
|
attribute_name, method_type = md.pre_match, md.to_s
|
1781
1842
|
if @attributes.include?(attribute_name)
|
1782
|
-
|
1783
|
-
when '='
|
1784
|
-
write_attribute(attribute_name, args.first)
|
1785
|
-
when '_before_type_cast'
|
1786
|
-
read_attribute_before_type_cast(attribute_name)
|
1787
|
-
end
|
1843
|
+
__send__("attribute#{method_type}", attribute_name, *args, &block)
|
1788
1844
|
else
|
1789
1845
|
super
|
1790
1846
|
end
|
@@ -1821,9 +1877,16 @@ module ActiveRecord #:nodoc:
|
|
1821
1877
|
# ActiveRecord::Base.generate_read_methods is set to true.
|
1822
1878
|
def define_read_methods
|
1823
1879
|
self.class.columns_hash.each do |name, column|
|
1824
|
-
unless
|
1825
|
-
|
1826
|
-
|
1880
|
+
unless respond_to_without_attributes?(name)
|
1881
|
+
if self.class.serialized_attributes[name]
|
1882
|
+
define_read_method_for_serialized_attribute(name)
|
1883
|
+
else
|
1884
|
+
define_read_method(name.to_sym, name, column)
|
1885
|
+
end
|
1886
|
+
end
|
1887
|
+
|
1888
|
+
unless respond_to_without_attributes?("#{name}?")
|
1889
|
+
define_question_method(name)
|
1827
1890
|
end
|
1828
1891
|
end
|
1829
1892
|
end
|
@@ -1841,6 +1904,15 @@ module ActiveRecord #:nodoc:
|
|
1841
1904
|
evaluate_read_method attr_name, "def #{symbol}; #{access_code}; end"
|
1842
1905
|
end
|
1843
1906
|
|
1907
|
+
# Define read method for serialized attribute.
|
1908
|
+
def define_read_method_for_serialized_attribute(attr_name)
|
1909
|
+
unless attr_name.to_s == self.class.primary_key.to_s
|
1910
|
+
self.class.read_methods << attr_name
|
1911
|
+
end
|
1912
|
+
|
1913
|
+
evaluate_read_method attr_name, "def #{attr_name}; unserialize_attribute('#{attr_name}'); end"
|
1914
|
+
end
|
1915
|
+
|
1844
1916
|
# Define an attribute ? method.
|
1845
1917
|
def define_question_method(attr_name)
|
1846
1918
|
unless attr_name.to_s == self.class.primary_key.to_s
|
@@ -1857,7 +1929,7 @@ module ActiveRecord #:nodoc:
|
|
1857
1929
|
rescue SyntaxError => err
|
1858
1930
|
self.class.read_methods.delete(attr_name)
|
1859
1931
|
if logger
|
1860
|
-
logger.warn "Exception
|
1932
|
+
logger.warn "Exception occurred during reader method compilation."
|
1861
1933
|
logger.warn "Maybe #{attr_name} is not a valid Ruby identifier?"
|
1862
1934
|
logger.warn "#{err.message}"
|
1863
1935
|
end
|
@@ -1944,17 +2016,24 @@ module ActiveRecord #:nodoc:
|
|
1944
2016
|
def attributes_with_quotes(include_primary_key = true)
|
1945
2017
|
attributes.inject({}) do |quoted, (name, value)|
|
1946
2018
|
if column = column_for_attribute(name)
|
1947
|
-
quoted[name] =
|
2019
|
+
quoted[name] = quote_value(value, column) unless !include_primary_key && column.primary
|
1948
2020
|
end
|
1949
2021
|
quoted
|
1950
2022
|
end
|
1951
2023
|
end
|
1952
2024
|
|
1953
2025
|
# Quote strings appropriately for SQL statements.
|
1954
|
-
def
|
2026
|
+
def quote_value(value, column = nil)
|
1955
2027
|
self.class.connection.quote(value, column)
|
1956
2028
|
end
|
1957
2029
|
|
2030
|
+
# Deprecated, use quote_value
|
2031
|
+
def quote(value, column = nil)
|
2032
|
+
self.class.connection.quote(value, column)
|
2033
|
+
end
|
2034
|
+
deprecate :quote => :quote_value
|
2035
|
+
|
2036
|
+
|
1958
2037
|
# Interpolate custom sql string in instance context.
|
1959
2038
|
# Optional record argument is meant for custom insert_sql.
|
1960
2039
|
def interpolate_sql(sql, record = nil)
|
@@ -1993,7 +2072,7 @@ module ActiveRecord #:nodoc:
|
|
1993
2072
|
send(name + "=", nil)
|
1994
2073
|
else
|
1995
2074
|
begin
|
1996
|
-
send(name + "=", Time == klass ? klass.local(*values) : klass.new(*values))
|
2075
|
+
send(name + "=", Time == klass ? (@@default_timezone == :utc ? klass.utc(*values) : klass.local(*values)) : klass.new(*values))
|
1997
2076
|
rescue => ex
|
1998
2077
|
errors << AttributeAssignmentError.new("error on assignment #{values.inspect} to #{name}", ex, name)
|
1999
2078
|
end
|