activerecord 2.1.2 → 2.2.2
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 +32 -6
- data/README +0 -0
- data/Rakefile +4 -5
- data/lib/active_record.rb +11 -10
- data/lib/active_record/aggregations.rb +110 -38
- data/lib/active_record/association_preload.rb +104 -15
- data/lib/active_record/associations.rb +427 -212
- data/lib/active_record/associations/association_collection.rb +101 -16
- data/lib/active_record/associations/association_proxy.rb +65 -13
- data/lib/active_record/associations/belongs_to_association.rb +2 -2
- data/lib/active_record/associations/belongs_to_polymorphic_association.rb +0 -0
- data/lib/active_record/associations/has_and_belongs_to_many_association.rb +13 -3
- data/lib/active_record/associations/has_many_association.rb +28 -28
- data/lib/active_record/associations/has_many_through_association.rb +21 -19
- data/lib/active_record/associations/has_one_association.rb +24 -7
- data/lib/active_record/associations/has_one_through_association.rb +3 -4
- data/lib/active_record/attribute_methods.rb +13 -5
- data/lib/active_record/base.rb +435 -212
- data/lib/active_record/calculations.rb +12 -5
- data/lib/active_record/callbacks.rb +28 -9
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +355 -0
- data/lib/active_record/connection_adapters/abstract/connection_specification.rb +42 -215
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +30 -5
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +2 -1
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +48 -7
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +10 -4
- data/lib/active_record/connection_adapters/abstract_adapter.rb +67 -26
- data/lib/active_record/connection_adapters/mysql_adapter.rb +71 -45
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +155 -84
- data/lib/active_record/dirty.rb +25 -7
- data/lib/active_record/dynamic_finder_match.rb +41 -0
- data/lib/active_record/fixtures.rb +10 -9
- data/lib/active_record/i18n_interpolation_deprecation.rb +26 -0
- data/lib/active_record/locale/en.yml +54 -0
- data/lib/active_record/migration.rb +47 -10
- data/lib/active_record/named_scope.rb +29 -16
- data/lib/active_record/reflection.rb +118 -54
- data/lib/active_record/schema_dumper.rb +13 -7
- data/lib/active_record/test_case.rb +18 -5
- data/lib/active_record/transactions.rb +89 -34
- data/lib/active_record/validations.rb +270 -180
- data/lib/active_record/version.rb +1 -1
- data/test/cases/active_schema_test_mysql.rb +5 -0
- data/test/cases/adapter_test.rb +6 -0
- data/test/cases/aggregations_test.rb +39 -0
- data/test/cases/associations/belongs_to_associations_test.rb +10 -0
- data/test/cases/associations/eager_load_nested_include_test.rb +30 -12
- data/test/cases/associations/eager_test.rb +54 -5
- data/test/cases/associations/has_and_belongs_to_many_associations_test.rb +77 -10
- data/test/cases/associations/has_many_associations_test.rb +74 -7
- data/test/cases/associations/has_many_through_associations_test.rb +50 -3
- data/test/cases/associations/has_one_associations_test.rb +17 -0
- data/test/cases/associations/has_one_through_associations_test.rb +49 -1
- data/test/cases/associations_test.rb +0 -0
- data/test/cases/attribute_methods_test.rb +59 -4
- data/test/cases/base_test.rb +93 -21
- data/test/cases/binary_test.rb +1 -5
- data/test/cases/calculations_test.rb +5 -0
- data/test/cases/callbacks_observers_test.rb +38 -0
- data/test/cases/connection_test_mysql.rb +1 -1
- data/test/cases/defaults_test.rb +32 -1
- data/test/cases/deprecated_finder_test.rb +0 -0
- data/test/cases/dirty_test.rb +13 -0
- data/test/cases/finder_test.rb +162 -12
- data/test/cases/fixtures_test.rb +32 -3
- data/test/cases/helper.rb +15 -0
- data/test/cases/i18n_test.rb +41 -0
- data/test/cases/inheritance_test.rb +2 -2
- data/test/cases/lifecycle_test.rb +0 -0
- data/test/cases/locking_test.rb +4 -9
- data/test/cases/method_scoping_test.rb +109 -2
- data/test/cases/migration_test.rb +43 -8
- data/test/cases/multiple_db_test.rb +25 -0
- data/test/cases/named_scope_test.rb +74 -0
- data/test/cases/pooled_connections_test.rb +103 -0
- data/test/cases/readonly_test.rb +0 -0
- data/test/cases/reflection_test.rb +11 -3
- data/test/cases/reload_models_test.rb +20 -0
- data/test/cases/sanitize_test.rb +25 -0
- data/test/cases/schema_authorization_test_postgresql.rb +2 -2
- data/test/cases/transactions_test.rb +62 -12
- data/test/cases/unconnected_test.rb +0 -0
- data/test/cases/validations_i18n_test.rb +921 -0
- data/test/cases/validations_test.rb +44 -33
- data/test/connections/native_mysql/connection.rb +1 -3
- data/test/fixtures/companies.yml +1 -0
- data/test/fixtures/customers.yml +10 -1
- data/test/fixtures/fixture_database.sqlite3 +0 -0
- data/test/fixtures/fixture_database_2.sqlite3 +0 -0
- data/test/fixtures/organizations.yml +5 -0
- data/test/migrations/broken/100_migration_that_raises_exception.rb +10 -0
- data/test/models/author.rb +3 -0
- data/test/models/category.rb +3 -0
- data/test/models/club.rb +6 -0
- data/test/models/company.rb +25 -1
- data/test/models/customer.rb +19 -1
- data/test/models/member.rb +2 -0
- data/test/models/member_detail.rb +4 -0
- data/test/models/organization.rb +4 -0
- data/test/models/parrot.rb +1 -0
- data/test/models/post.rb +3 -0
- data/test/models/reply.rb +0 -0
- data/test/models/topic.rb +3 -0
- data/test/schema/schema.rb +12 -1
- metadata +22 -10
- data/lib/active_record/vendor/mysql.rb +0 -1214
- data/test/cases/adapter_test_sqlserver.rb +0 -95
- data/test/cases/table_name_test_sqlserver.rb +0 -23
- data/test/cases/threaded_connections_test.rb +0 -48
- data/test/schema/sqlserver_specific_schema.rb +0 -5
File without changes
|
@@ -37,7 +37,7 @@ module ActiveRecord
|
|
37
37
|
attributes = columns.inject({}) do |attrs, column|
|
38
38
|
case column.name.to_s
|
39
39
|
when @reflection.primary_key_name.to_s
|
40
|
-
attrs[column.name] =
|
40
|
+
attrs[column.name] = owner_quoted_id
|
41
41
|
when @reflection.association_foreign_key.to_s
|
42
42
|
attrs[column.name] = record.quoted_id
|
43
43
|
else
|
@@ -64,7 +64,7 @@ module ActiveRecord
|
|
64
64
|
records.each { |record| @owner.connection.delete(interpolate_sql(sql, record)) }
|
65
65
|
else
|
66
66
|
ids = quoted_record_ids(records)
|
67
|
-
sql = "DELETE FROM #{@owner.connection.quote_table_name @reflection.options[:join_table]} WHERE #{@reflection.primary_key_name} = #{
|
67
|
+
sql = "DELETE FROM #{@owner.connection.quote_table_name @reflection.options[:join_table]} WHERE #{@reflection.primary_key_name} = #{owner_quoted_id} AND #{@reflection.association_foreign_key} IN (#{ids})"
|
68
68
|
@owner.connection.delete(sql)
|
69
69
|
end
|
70
70
|
end
|
@@ -73,11 +73,21 @@ module ActiveRecord
|
|
73
73
|
if @reflection.options[:finder_sql]
|
74
74
|
@finder_sql = interpolate_sql(@reflection.options[:finder_sql])
|
75
75
|
else
|
76
|
-
@finder_sql = "#{@owner.connection.quote_table_name @reflection.options[:join_table]}.#{@reflection.primary_key_name} = #{
|
76
|
+
@finder_sql = "#{@owner.connection.quote_table_name @reflection.options[:join_table]}.#{@reflection.primary_key_name} = #{owner_quoted_id} "
|
77
77
|
@finder_sql << " AND (#{conditions})" if conditions
|
78
78
|
end
|
79
79
|
|
80
80
|
@join_sql = "INNER JOIN #{@owner.connection.quote_table_name @reflection.options[:join_table]} ON #{@reflection.quoted_table_name}.#{@reflection.klass.primary_key} = #{@owner.connection.quote_table_name @reflection.options[:join_table]}.#{@reflection.association_foreign_key}"
|
81
|
+
|
82
|
+
if @reflection.options[:counter_sql]
|
83
|
+
@counter_sql = interpolate_sql(@reflection.options[:counter_sql])
|
84
|
+
elsif @reflection.options[:finder_sql]
|
85
|
+
# replace the SELECT clause with COUNT(*), preserving any hints within /* ... */
|
86
|
+
@reflection.options[:counter_sql] = @reflection.options[:finder_sql].sub(/SELECT (\/\*.*?\*\/ )?(.*)\bFROM\b/im) { "SELECT #{$1}COUNT(*) FROM" }
|
87
|
+
@counter_sql = interpolate_sql(@reflection.options[:counter_sql])
|
88
|
+
else
|
89
|
+
@counter_sql = @finder_sql
|
90
|
+
end
|
81
91
|
end
|
82
92
|
|
83
93
|
def construct_scope
|
@@ -1,33 +1,32 @@
|
|
1
1
|
module ActiveRecord
|
2
2
|
module Associations
|
3
|
+
# This is the proxy that handles a has many association.
|
4
|
+
#
|
5
|
+
# If the association has a <tt>:through</tt> option further specialization
|
6
|
+
# is provided by its child HasManyThroughAssociation.
|
3
7
|
class HasManyAssociation < AssociationCollection #:nodoc:
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
elsif @reflection.options[:finder_sql]
|
9
|
-
@reflection.klass.count_by_sql(@finder_sql)
|
10
|
-
else
|
11
|
-
column_name, options = @reflection.klass.send(:construct_count_options_from_args, *args)
|
12
|
-
options[:conditions] = options[:conditions].blank? ?
|
13
|
-
@finder_sql :
|
14
|
-
@finder_sql + " AND (#{sanitize_sql(options[:conditions])})"
|
15
|
-
options[:include] ||= @reflection.options[:include]
|
16
|
-
|
17
|
-
value = @reflection.klass.count(column_name, options)
|
18
|
-
|
19
|
-
limit = @reflection.options[:limit]
|
20
|
-
offset = @reflection.options[:offset]
|
21
|
-
|
22
|
-
if limit || offset
|
23
|
-
[ [value - offset.to_i, 0].max, limit.to_i ].min
|
8
|
+
protected
|
9
|
+
def owner_quoted_id
|
10
|
+
if @reflection.options[:primary_key]
|
11
|
+
quote_value(@owner.send(@reflection.options[:primary_key]))
|
24
12
|
else
|
25
|
-
|
13
|
+
@owner.quoted_id
|
26
14
|
end
|
27
15
|
end
|
28
|
-
end
|
29
16
|
|
30
|
-
|
17
|
+
# Returns the number of records in this collection.
|
18
|
+
#
|
19
|
+
# If the association has a counter cache it gets that value. Otherwise
|
20
|
+
# it will attempt to do a count via SQL, bounded to <tt>:limit</tt> if
|
21
|
+
# there's one. Some configuration options like :group make it impossible
|
22
|
+
# to do a SQL count, in those cases the array count will be used.
|
23
|
+
#
|
24
|
+
# That does not depend on whether the collection has already been loaded
|
25
|
+
# or not. The +size+ method is the one that takes the loaded flag into
|
26
|
+
# account and delegates to +count_records+ if needed.
|
27
|
+
#
|
28
|
+
# If the collection is empty the target is set to an empty array and
|
29
|
+
# the loaded flag is set to true as well.
|
31
30
|
def count_records
|
32
31
|
count = if has_cached_counter?
|
33
32
|
@owner.send(:read_attribute, cached_counter_attribute_name)
|
@@ -62,17 +61,18 @@ module ActiveRecord
|
|
62
61
|
record.save
|
63
62
|
end
|
64
63
|
|
64
|
+
# Deletes the records according to the <tt>:dependent</tt> option.
|
65
65
|
def delete_records(records)
|
66
66
|
case @reflection.options[:dependent]
|
67
67
|
when :destroy
|
68
|
-
records.each
|
68
|
+
records.each { |r| r.destroy }
|
69
69
|
when :delete_all
|
70
|
-
@reflection.klass.delete(records.map
|
70
|
+
@reflection.klass.delete(records.map { |record| record.id })
|
71
71
|
else
|
72
72
|
ids = quoted_record_ids(records)
|
73
73
|
@reflection.klass.update_all(
|
74
74
|
"#{@reflection.primary_key_name} = NULL",
|
75
|
-
"#{@reflection.primary_key_name} = #{
|
75
|
+
"#{@reflection.primary_key_name} = #{owner_quoted_id} AND #{@reflection.klass.primary_key} IN (#{ids})"
|
76
76
|
)
|
77
77
|
end
|
78
78
|
end
|
@@ -88,12 +88,12 @@ module ActiveRecord
|
|
88
88
|
|
89
89
|
when @reflection.options[:as]
|
90
90
|
@finder_sql =
|
91
|
-
"#{@reflection.quoted_table_name}.#{@reflection.options[:as]}_id = #{
|
91
|
+
"#{@reflection.quoted_table_name}.#{@reflection.options[:as]}_id = #{owner_quoted_id} AND " +
|
92
92
|
"#{@reflection.quoted_table_name}.#{@reflection.options[:as]}_type = #{@owner.class.quote_value(@owner.class.base_class.name.to_s)}"
|
93
93
|
@finder_sql << " AND (#{conditions})" if conditions
|
94
94
|
|
95
95
|
else
|
96
|
-
@finder_sql = "#{@reflection.quoted_table_name}.#{@reflection.primary_key_name} = #{
|
96
|
+
@finder_sql = "#{@reflection.quoted_table_name}.#{@reflection.primary_key_name} = #{owner_quoted_id}"
|
97
97
|
@finder_sql << " AND (#{conditions})" if conditions
|
98
98
|
end
|
99
99
|
|
@@ -9,15 +9,15 @@ module ActiveRecord
|
|
9
9
|
alias_method :new, :build
|
10
10
|
|
11
11
|
def create!(attrs = nil)
|
12
|
-
|
13
|
-
self << (object = attrs ? @reflection.klass.send(:with_scope, :create => attrs) { @reflection.
|
12
|
+
transaction do
|
13
|
+
self << (object = attrs ? @reflection.klass.send(:with_scope, :create => attrs) { @reflection.create_association! } : @reflection.create_association!)
|
14
14
|
object
|
15
15
|
end
|
16
16
|
end
|
17
17
|
|
18
18
|
def create(attrs = nil)
|
19
|
-
|
20
|
-
self << (object = attrs ? @reflection.klass.send(:with_scope, :create => attrs) { @reflection.
|
19
|
+
transaction do
|
20
|
+
self << (object = attrs ? @reflection.klass.send(:with_scope, :create => attrs) { @reflection.create_association } : @reflection.create_association)
|
21
21
|
object
|
22
22
|
end
|
23
23
|
end
|
@@ -31,17 +31,15 @@ module ActiveRecord
|
|
31
31
|
return count
|
32
32
|
end
|
33
33
|
|
34
|
-
def count(*args)
|
35
|
-
column_name, options = @reflection.klass.send(:construct_count_options_from_args, *args)
|
36
|
-
if @reflection.options[:uniq]
|
37
|
-
# This is needed because 'SELECT count(DISTINCT *)..' is not valid SQL statement.
|
38
|
-
column_name = "#{@reflection.quoted_table_name}.#{@reflection.klass.primary_key}" if column_name == :all
|
39
|
-
options.merge!(:distinct => true)
|
40
|
-
end
|
41
|
-
@reflection.klass.send(:with_scope, construct_scope) { @reflection.klass.count(column_name, options) }
|
42
|
-
end
|
43
|
-
|
44
34
|
protected
|
35
|
+
def target_reflection_has_associated_record?
|
36
|
+
if @reflection.through_reflection.macro == :belongs_to && @owner[@reflection.through_reflection.primary_key_name].blank?
|
37
|
+
false
|
38
|
+
else
|
39
|
+
true
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
45
43
|
def construct_find_options!(options)
|
46
44
|
options[:select] = construct_select(options[:select])
|
47
45
|
options[:from] ||= construct_from
|
@@ -57,8 +55,9 @@ module ActiveRecord
|
|
57
55
|
return false unless record.save
|
58
56
|
end
|
59
57
|
end
|
60
|
-
|
61
|
-
|
58
|
+
through_reflection = @reflection.through_reflection
|
59
|
+
klass = through_reflection.klass
|
60
|
+
@owner.send(@reflection.through_reflection.name).proxy_target << klass.send(:with_scope, :create => construct_join_attributes(record)) { through_reflection.create_association! }
|
62
61
|
end
|
63
62
|
|
64
63
|
# TODO - add dependent option support
|
@@ -70,6 +69,7 @@ module ActiveRecord
|
|
70
69
|
end
|
71
70
|
|
72
71
|
def find_target
|
72
|
+
return [] unless target_reflection_has_associated_record?
|
73
73
|
@reflection.klass.find(:all,
|
74
74
|
:select => construct_select,
|
75
75
|
:conditions => construct_conditions,
|
@@ -107,12 +107,14 @@ module ActiveRecord
|
|
107
107
|
# Associate attributes pointing to owner, quoted.
|
108
108
|
def construct_quoted_owner_attributes(reflection)
|
109
109
|
if as = reflection.options[:as]
|
110
|
-
{ "#{as}_id" =>
|
110
|
+
{ "#{as}_id" => owner_quoted_id,
|
111
111
|
"#{as}_type" => reflection.klass.quote_value(
|
112
112
|
@owner.class.base_class.name.to_s,
|
113
113
|
reflection.klass.columns_hash["#{as}_type"]) }
|
114
|
+
elsif reflection.macro == :belongs_to
|
115
|
+
{ reflection.klass.primary_key => @owner[reflection.primary_key_name] }
|
114
116
|
else
|
115
|
-
{ reflection.primary_key_name =>
|
117
|
+
{ reflection.primary_key_name => owner_quoted_id }
|
116
118
|
end
|
117
119
|
end
|
118
120
|
|
@@ -183,7 +185,7 @@ module ActiveRecord
|
|
183
185
|
when @reflection.options[:finder_sql]
|
184
186
|
@finder_sql = interpolate_sql(@reflection.options[:finder_sql])
|
185
187
|
|
186
|
-
@finder_sql = "#{@reflection.quoted_table_name}.#{@reflection.primary_key_name} = #{
|
188
|
+
@finder_sql = "#{@reflection.quoted_table_name}.#{@reflection.primary_key_name} = #{owner_quoted_id}"
|
187
189
|
@finder_sql << " AND (#{conditions})" if conditions
|
188
190
|
else
|
189
191
|
@finder_sql = construct_conditions
|
@@ -7,15 +7,21 @@ module ActiveRecord
|
|
7
7
|
end
|
8
8
|
|
9
9
|
def create(attrs = {}, replace_existing = true)
|
10
|
-
new_record(replace_existing)
|
10
|
+
new_record(replace_existing) do |reflection|
|
11
|
+
reflection.create_association(attrs)
|
12
|
+
end
|
11
13
|
end
|
12
14
|
|
13
15
|
def create!(attrs = {}, replace_existing = true)
|
14
|
-
new_record(replace_existing)
|
16
|
+
new_record(replace_existing) do |reflection|
|
17
|
+
reflection.create_association!(attrs)
|
18
|
+
end
|
15
19
|
end
|
16
20
|
|
17
21
|
def build(attrs = {}, replace_existing = true)
|
18
|
-
new_record(replace_existing)
|
22
|
+
new_record(replace_existing) do |reflection|
|
23
|
+
reflection.build_association(attrs)
|
24
|
+
end
|
19
25
|
end
|
20
26
|
|
21
27
|
def replace(obj, dont_save = false)
|
@@ -47,7 +53,16 @@ module ActiveRecord
|
|
47
53
|
return (obj.nil? ? nil : self)
|
48
54
|
end
|
49
55
|
end
|
50
|
-
|
56
|
+
|
57
|
+
protected
|
58
|
+
def owner_quoted_id
|
59
|
+
if @reflection.options[:primary_key]
|
60
|
+
@owner.class.quote_value(@owner.send(@reflection.options[:primary_key]))
|
61
|
+
else
|
62
|
+
@owner.quoted_id
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
51
66
|
private
|
52
67
|
def find_target
|
53
68
|
@reflection.klass.find(:first,
|
@@ -63,10 +78,10 @@ module ActiveRecord
|
|
63
78
|
case
|
64
79
|
when @reflection.options[:as]
|
65
80
|
@finder_sql =
|
66
|
-
"#{@reflection.quoted_table_name}.#{@reflection.options[:as]}_id = #{
|
81
|
+
"#{@reflection.quoted_table_name}.#{@reflection.options[:as]}_id = #{owner_quoted_id} AND " +
|
67
82
|
"#{@reflection.quoted_table_name}.#{@reflection.options[:as]}_type = #{@owner.class.quote_value(@owner.class.base_class.name.to_s)}"
|
68
83
|
else
|
69
|
-
@finder_sql = "#{@reflection.quoted_table_name}.#{@reflection.primary_key_name} = #{
|
84
|
+
@finder_sql = "#{@reflection.quoted_table_name}.#{@reflection.primary_key_name} = #{owner_quoted_id}"
|
70
85
|
end
|
71
86
|
@finder_sql << " AND (#{conditions})" if conditions
|
72
87
|
end
|
@@ -82,7 +97,9 @@ module ActiveRecord
|
|
82
97
|
# instance. Otherwise, if the target has not previously been loaded
|
83
98
|
# elsewhere, the instance we create will get orphaned.
|
84
99
|
load_target if replace_existing
|
85
|
-
record = @reflection.klass.send(:with_scope, :create => construct_scope[:create])
|
100
|
+
record = @reflection.klass.send(:with_scope, :create => construct_scope[:create]) do
|
101
|
+
yield @reflection
|
102
|
+
end
|
86
103
|
|
87
104
|
if replace_existing
|
88
105
|
replace(record, true)
|
@@ -8,11 +8,10 @@ module ActiveRecord
|
|
8
8
|
current_object = @owner.send(@reflection.through_reflection.name)
|
9
9
|
|
10
10
|
if current_object
|
11
|
-
|
12
|
-
|
11
|
+
current_object.update_attributes(construct_join_attributes(new_value))
|
12
|
+
else
|
13
|
+
@owner.send(@reflection.through_reflection.name, klass.send(:create, construct_join_attributes(new_value)))
|
13
14
|
end
|
14
|
-
|
15
|
-
@owner.send(@reflection.through_reflection.name, klass.send(:create, construct_join_attributes(new_value)))
|
16
15
|
end
|
17
16
|
|
18
17
|
private
|
@@ -10,7 +10,7 @@ module ActiveRecord
|
|
10
10
|
base.attribute_types_cached_by_default = ATTRIBUTE_TYPES_CACHED_BY_DEFAULT
|
11
11
|
base.cattr_accessor :time_zone_aware_attributes, :instance_writer => false
|
12
12
|
base.time_zone_aware_attributes = false
|
13
|
-
base.
|
13
|
+
base.class_inheritable_accessor :skip_time_zone_conversion_for_attributes, :instance_writer => false
|
14
14
|
base.skip_time_zone_conversion_for_attributes = []
|
15
15
|
end
|
16
16
|
|
@@ -214,7 +214,7 @@ module ActiveRecord
|
|
214
214
|
if logger
|
215
215
|
logger.warn "Exception occurred during reader method compilation."
|
216
216
|
logger.warn "Maybe #{attr_name} is not a valid Ruby identifier?"
|
217
|
-
logger.warn
|
217
|
+
logger.warn err.message
|
218
218
|
end
|
219
219
|
end
|
220
220
|
end
|
@@ -232,6 +232,10 @@ module ActiveRecord
|
|
232
232
|
def method_missing(method_id, *args, &block)
|
233
233
|
method_name = method_id.to_s
|
234
234
|
|
235
|
+
if self.class.private_method_defined?(method_name)
|
236
|
+
raise NoMethodError.new("Attempt to call private method", method_name, args)
|
237
|
+
end
|
238
|
+
|
235
239
|
# If we haven't generated any methods yet, generate them, then
|
236
240
|
# see if we've created the method we're looking for.
|
237
241
|
if !self.class.generated_methods?
|
@@ -330,14 +334,18 @@ module ActiveRecord
|
|
330
334
|
end
|
331
335
|
end
|
332
336
|
|
333
|
-
# A Person object with a name attribute can ask <tt>person.respond_to?(
|
334
|
-
# <tt>person.respond_to?(
|
337
|
+
# A Person object with a name attribute can ask <tt>person.respond_to?(:name)</tt>,
|
338
|
+
# <tt>person.respond_to?(:name=)</tt>, and <tt>person.respond_to?(:name?)</tt>
|
335
339
|
# which will all return +true+.
|
336
340
|
alias :respond_to_without_attributes? :respond_to?
|
337
|
-
def respond_to?(method,
|
341
|
+
def respond_to?(method, include_private_methods = false)
|
338
342
|
method_name = method.to_s
|
339
343
|
if super
|
340
344
|
return true
|
345
|
+
elsif !include_private_methods && super(method, true)
|
346
|
+
# If we're here than we haven't found among non-private methods
|
347
|
+
# but found among all methods. Which means that given method is private.
|
348
|
+
return false
|
341
349
|
elsif !self.class.generated_methods?
|
342
350
|
self.class.define_attribute_methods
|
343
351
|
if self.class.generated_methods.include?(method_name)
|
data/lib/active_record/base.rb
CHANGED
@@ -6,7 +6,7 @@ module ActiveRecord #:nodoc:
|
|
6
6
|
class ActiveRecordError < StandardError
|
7
7
|
end
|
8
8
|
|
9
|
-
# Raised when the single-table inheritance mechanism
|
9
|
+
# Raised when the single-table inheritance mechanism fails to locate the subclass
|
10
10
|
# (for example due to improper usage of column that +inheritance_column+ points to).
|
11
11
|
class SubclassNotFound < ActiveRecordError #:nodoc:
|
12
12
|
end
|
@@ -83,8 +83,33 @@ module ActiveRecord #:nodoc:
|
|
83
83
|
class ReadOnlyRecord < ActiveRecordError
|
84
84
|
end
|
85
85
|
|
86
|
-
#
|
87
|
-
#
|
86
|
+
# ActiveRecord::Transactions::ClassMethods.transaction uses this exception
|
87
|
+
# to distinguish a deliberate rollback from other exceptional situations.
|
88
|
+
# Normally, raising an exception will cause the +transaction+ method to rollback
|
89
|
+
# the database transaction *and* pass on the exception. But if you raise an
|
90
|
+
# ActiveRecord::Rollback exception, then the database transaction will be rolled back,
|
91
|
+
# without passing on the exception.
|
92
|
+
#
|
93
|
+
# For example, you could do this in your controller to rollback a transaction:
|
94
|
+
#
|
95
|
+
# class BooksController < ActionController::Base
|
96
|
+
# def create
|
97
|
+
# Book.transaction do
|
98
|
+
# book = Book.new(params[:book])
|
99
|
+
# book.save!
|
100
|
+
# if today_is_friday?
|
101
|
+
# # The system must fail on Friday so that our support department
|
102
|
+
# # won't be out of job. We silently rollback this transaction
|
103
|
+
# # without telling the user.
|
104
|
+
# raise ActiveRecord::Rollback, "Call tech support!"
|
105
|
+
# end
|
106
|
+
# end
|
107
|
+
# # ActiveRecord::Rollback is the only exception that won't be passed on
|
108
|
+
# # by ActiveRecord::Base.transaction, so this line will still be reached
|
109
|
+
# # even on Friday.
|
110
|
+
# redirect_to root_url
|
111
|
+
# end
|
112
|
+
# end
|
88
113
|
class Rollback < ActiveRecordError
|
89
114
|
end
|
90
115
|
|
@@ -97,7 +122,11 @@ module ActiveRecord #:nodoc:
|
|
97
122
|
class MissingAttributeError < NoMethodError
|
98
123
|
end
|
99
124
|
|
100
|
-
# Raised when
|
125
|
+
# Raised when unknown attributes are supplied via mass assignment.
|
126
|
+
class UnknownAttributeError < NoMethodError
|
127
|
+
end
|
128
|
+
|
129
|
+
# Raised when an error occurred while doing a mass assignment to an attribute through the
|
101
130
|
# <tt>attributes=</tt> method. The exception has an +attribute+ property that is the name of the
|
102
131
|
# offending attribute.
|
103
132
|
class AttributeAssignmentError < ActiveRecordError
|
@@ -245,7 +274,7 @@ module ActiveRecord #:nodoc:
|
|
245
274
|
# == Dynamic attribute-based finders
|
246
275
|
#
|
247
276
|
# Dynamic attribute-based finders are a cleaner way of getting (and/or creating) objects by simple queries without turning to SQL. They work by
|
248
|
-
# appending the name of an attribute to <tt>find_by_</tt> or <tt>find_all_by_</tt>, so you get finders like <tt>Person.find_by_user_name</tt>,
|
277
|
+
# appending the name of an attribute to <tt>find_by_</tt>, <tt>find_last_by_</tt>, or <tt>find_all_by_</tt>, so you get finders like <tt>Person.find_by_user_name</tt>,
|
249
278
|
# <tt>Person.find_all_by_last_name</tt>, and <tt>Payment.find_by_transaction_id</tt>. So instead of writing
|
250
279
|
# <tt>Person.find(:first, :conditions => ["user_name = ?", user_name])</tt>, you just do <tt>Person.find_by_user_name(user_name)</tt>.
|
251
280
|
# 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>.
|
@@ -258,6 +287,7 @@ module ActiveRecord #:nodoc:
|
|
258
287
|
# It's even possible to use all the additional parameters to find. For example, the full interface for <tt>Payment.find_all_by_amount</tt>
|
259
288
|
# is actually <tt>Payment.find_all_by_amount(amount, options)</tt>. And the full interface to <tt>Person.find_by_user_name</tt> is
|
260
289
|
# actually <tt>Person.find_by_user_name(user_name, options)</tt>. So you could call <tt>Payment.find_all_by_amount(50, :order => "created_on")</tt>.
|
290
|
+
# Also you may call <tt>Payment.find_last_by_amount(amount, options)</tt> returning the last record matching that amount and options.
|
261
291
|
#
|
262
292
|
# The same dynamic finder style can be used to create the object if it doesn't already exist. This dynamic finder is called with
|
263
293
|
# <tt>find_or_create_by_</tt> and will return the object if it already exists and otherwise creates it, then returns it. Protected attributes won't be set unless they are given in a block. For example:
|
@@ -271,7 +301,7 @@ module ActiveRecord #:nodoc:
|
|
271
301
|
# # Now 'Bob' exist and is an 'admin'
|
272
302
|
# User.find_or_create_by_name('Bob', :age => 40) { |u| u.admin = true }
|
273
303
|
#
|
274
|
-
# Use the <tt>find_or_initialize_by_</tt> finder if you want to return a new record without saving it first. Protected attributes won't be
|
304
|
+
# Use the <tt>find_or_initialize_by_</tt> finder if you want to return a new record without saving it first. Protected attributes won't be set unless they are given in a block. For example:
|
275
305
|
#
|
276
306
|
# # No 'Winter' tag exists
|
277
307
|
# winter = Tag.find_or_initialize_by_name("Winter")
|
@@ -385,6 +415,31 @@ module ActiveRecord #:nodoc:
|
|
385
415
|
|
386
416
|
@@subclasses = {}
|
387
417
|
|
418
|
+
# Contains the database configuration - as is typically stored in config/database.yml -
|
419
|
+
# as a Hash.
|
420
|
+
#
|
421
|
+
# For example, the following database.yml...
|
422
|
+
#
|
423
|
+
# development:
|
424
|
+
# adapter: sqlite3
|
425
|
+
# database: db/development.sqlite3
|
426
|
+
#
|
427
|
+
# production:
|
428
|
+
# adapter: sqlite3
|
429
|
+
# database: db/production.sqlite3
|
430
|
+
#
|
431
|
+
# ...would result in ActiveRecord::Base.configurations to look like this:
|
432
|
+
#
|
433
|
+
# {
|
434
|
+
# 'development' => {
|
435
|
+
# 'adapter' => 'sqlite3',
|
436
|
+
# 'database' => 'db/development.sqlite3'
|
437
|
+
# },
|
438
|
+
# 'production' => {
|
439
|
+
# 'adapter' => 'sqlite3',
|
440
|
+
# 'database' => 'db/production.sqlite3'
|
441
|
+
# }
|
442
|
+
# }
|
388
443
|
cattr_accessor :configurations, :instance_writer => false
|
389
444
|
@@configurations = {}
|
390
445
|
|
@@ -423,13 +478,6 @@ module ActiveRecord #:nodoc:
|
|
423
478
|
cattr_accessor :default_timezone, :instance_writer => false
|
424
479
|
@@default_timezone = :local
|
425
480
|
|
426
|
-
# Determines whether to use a connection for each thread, or a single shared connection for all threads.
|
427
|
-
# Defaults to false. If you're writing a threaded application, set to true
|
428
|
-
# and periodically call verify_active_connections! to clear out connections
|
429
|
-
# assigned to stale threads.
|
430
|
-
cattr_accessor :allow_concurrency, :instance_writer => false
|
431
|
-
@@allow_concurrency = false
|
432
|
-
|
433
481
|
# Specifies the format to use when dumping the database schema with Rails'
|
434
482
|
# Rakefile. If :sql, the schema is dumped as (potentially database-
|
435
483
|
# specific) SQL statements. If :ruby, the schema is dumped as an
|
@@ -464,9 +512,9 @@ module ActiveRecord #:nodoc:
|
|
464
512
|
#
|
465
513
|
# All approaches accept an options hash as their last parameter.
|
466
514
|
#
|
467
|
-
# ====
|
515
|
+
# ==== Parameters
|
468
516
|
#
|
469
|
-
# * <tt>:conditions</tt> - An SQL fragment like "administrator = 1"
|
517
|
+
# * <tt>:conditions</tt> - An SQL fragment like "administrator = 1", <tt>[ "user_name = ?", username ]</tt>, or <tt>["user_name = :user_name", { :user_name => user_name }]</tt>. See conditions in the intro.
|
470
518
|
# * <tt>:order</tt> - An SQL fragment like "created_at DESC, name".
|
471
519
|
# * <tt>:group</tt> - An attribute name by which the result should be grouped. Uses the <tt>GROUP BY</tt> SQL-clause.
|
472
520
|
# * <tt>:limit</tt> - An integer determining the limit on the number of rows that should be returned.
|
@@ -478,7 +526,7 @@ module ActiveRecord #:nodoc:
|
|
478
526
|
# * <tt>:include</tt> - Names associations that should be loaded alongside. The symbols named refer
|
479
527
|
# to already defined associations. See eager loading under Associations.
|
480
528
|
# * <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
|
481
|
-
# include the joined columns.
|
529
|
+
# include the joined columns. Takes a string with the SELECT SQL fragment (e.g. "id, name").
|
482
530
|
# * <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
|
483
531
|
# of a database view).
|
484
532
|
# * <tt>:readonly</tt> - Mark the returned records read-only so they cannot be saved or updated.
|
@@ -503,6 +551,7 @@ module ActiveRecord #:nodoc:
|
|
503
551
|
# # find first
|
504
552
|
# Person.find(:first) # returns the first object fetched by SELECT * FROM people
|
505
553
|
# Person.find(:first, :conditions => [ "user_name = ?", user_name])
|
554
|
+
# Person.find(:first, :conditions => [ "user_name = :u", { :u => user_name }])
|
506
555
|
# Person.find(:first, :order => "created_on DESC", :offset => 5)
|
507
556
|
#
|
508
557
|
# # find last
|
@@ -562,8 +611,8 @@ module ActiveRecord #:nodoc:
|
|
562
611
|
|
563
612
|
# Executes a custom SQL query against your database and returns all the results. The results will
|
564
613
|
# be returned as an array with columns requested encapsulated as attributes of the model you call
|
565
|
-
# this method from. If you call
|
566
|
-
# object with the attributes you specified in the SQL query.
|
614
|
+
# this method from. If you call <tt>Product.find_by_sql</tt> then the results will be returned in
|
615
|
+
# a Product object with the attributes you specified in the SQL query.
|
567
616
|
#
|
568
617
|
# If you call a complicated SQL query which spans multiple tables the columns specified by the
|
569
618
|
# SELECT will be attributes of the model, whether or not they are columns of the corresponding
|
@@ -572,7 +621,7 @@ module ActiveRecord #:nodoc:
|
|
572
621
|
# The +sql+ parameter is a full SQL query as a string. It will be called as is, there will be
|
573
622
|
# no database agnostic conversions performed. This should be a last resort because using, for example,
|
574
623
|
# MySQL specific terms will lock you to using that particular database engine or require you to
|
575
|
-
# change your call if you switch engines
|
624
|
+
# change your call if you switch engines.
|
576
625
|
#
|
577
626
|
# ==== Examples
|
578
627
|
# # A simple SQL query spanning multiple tables
|
@@ -649,7 +698,7 @@ module ActiveRecord #:nodoc:
|
|
649
698
|
# Updates an object (or multiple objects) and saves it to the database, if validations pass.
|
650
699
|
# The resulting object is returned whether the object was saved successfully to the database or not.
|
651
700
|
#
|
652
|
-
# ====
|
701
|
+
# ==== Parameters
|
653
702
|
#
|
654
703
|
# * +id+ - This should be the id or an array of ids to be updated.
|
655
704
|
# * +attributes+ - This should be a Hash of attributes to be set on the object, or an array of Hashes.
|
@@ -677,9 +726,10 @@ module ActiveRecord #:nodoc:
|
|
677
726
|
# is executed on the database which means that no callbacks are fired off running this. This is an efficient method
|
678
727
|
# of deleting records that don't need cleaning up after or other actions to be taken.
|
679
728
|
#
|
680
|
-
# Objects are _not_ instantiated with this method
|
729
|
+
# Objects are _not_ instantiated with this method, and so +:dependent+ rules
|
730
|
+
# defined on associations are not honered.
|
681
731
|
#
|
682
|
-
# ====
|
732
|
+
# ==== Parameters
|
683
733
|
#
|
684
734
|
# * +id+ - Can be either an Integer or an Array of Integers.
|
685
735
|
#
|
@@ -702,7 +752,7 @@ module ActiveRecord #:nodoc:
|
|
702
752
|
# This essentially finds the object (or multiple objects) with the given id, creates a new object
|
703
753
|
# from the attributes, and then calls destroy on it.
|
704
754
|
#
|
705
|
-
# ====
|
755
|
+
# ==== Parameters
|
706
756
|
#
|
707
757
|
# * +id+ - Can be either an Integer or an Array of Integers.
|
708
758
|
#
|
@@ -723,14 +773,15 @@ module ActiveRecord #:nodoc:
|
|
723
773
|
end
|
724
774
|
|
725
775
|
# Updates all records with details given if they match a set of conditions supplied, limits and order can
|
726
|
-
# also be supplied.
|
776
|
+
# also be supplied. This method constructs a single SQL UPDATE statement and sends it straight to the
|
777
|
+
# database. It does not instantiate the involved models and it does not trigger Active Record callbacks.
|
727
778
|
#
|
728
|
-
# ====
|
779
|
+
# ==== Parameters
|
729
780
|
#
|
730
|
-
# * +updates+ - A
|
731
|
-
#
|
732
|
-
#
|
733
|
-
# * +options+ - Additional options are <tt>:limit</tt> and
|
781
|
+
# * +updates+ - A string of column and value pairs that will be set on any records that match conditions.
|
782
|
+
# What goes into the SET clause.
|
783
|
+
# * +conditions+ - An SQL fragment like "administrator = 1" or [ "user_name = ?", username ]. See conditions in the intro for more info.
|
784
|
+
# * +options+ - Additional options are <tt>:limit</tt> and <tt>:order</tt>, see the examples for usage.
|
734
785
|
#
|
735
786
|
# ==== Examples
|
736
787
|
#
|
@@ -745,46 +796,66 @@ module ActiveRecord #:nodoc:
|
|
745
796
|
# :order => 'created_at', :limit => 5 )
|
746
797
|
def update_all(updates, conditions = nil, options = {})
|
747
798
|
sql = "UPDATE #{quoted_table_name} SET #{sanitize_sql_for_assignment(updates)} "
|
799
|
+
|
748
800
|
scope = scope(:find)
|
749
|
-
|
750
|
-
|
751
|
-
|
801
|
+
|
802
|
+
select_sql = ""
|
803
|
+
add_conditions!(select_sql, conditions, scope)
|
804
|
+
|
805
|
+
if options.has_key?(:limit) || (scope && scope[:limit])
|
806
|
+
# Only take order from scope if limit is also provided by scope, this
|
807
|
+
# is useful for updating a has_many association with a limit.
|
808
|
+
add_order!(select_sql, options[:order], scope)
|
809
|
+
|
810
|
+
add_limit!(select_sql, options, scope)
|
811
|
+
sql.concat(connection.limited_update_conditions(select_sql, quoted_table_name, connection.quote_column_name(primary_key)))
|
812
|
+
else
|
813
|
+
add_order!(select_sql, options[:order], nil)
|
814
|
+
sql.concat(select_sql)
|
815
|
+
end
|
816
|
+
|
752
817
|
connection.update(sql, "#{name} Update")
|
753
818
|
end
|
754
819
|
|
755
|
-
# Destroys the records matching +conditions+ by instantiating each record and calling
|
756
|
-
# This means at least 2*N database queries to destroy N records, so avoid destroy_all if you are deleting
|
820
|
+
# Destroys the records matching +conditions+ by instantiating each record and calling their +destroy+ method.
|
821
|
+
# This means at least 2*N database queries to destroy N records, so avoid +destroy_all+ if you are deleting
|
757
822
|
# many records. If you want to simply delete records without worrying about dependent associations or
|
758
823
|
# callbacks, use the much faster +delete_all+ method instead.
|
759
824
|
#
|
760
|
-
# ====
|
825
|
+
# ==== Parameters
|
761
826
|
#
|
762
827
|
# * +conditions+ - Conditions are specified the same way as with +find+ method.
|
763
828
|
#
|
764
829
|
# ==== Example
|
765
830
|
#
|
766
|
-
# Person.destroy_all
|
831
|
+
# Person.destroy_all("last_login < '2004-04-04'")
|
767
832
|
#
|
768
833
|
# This loads and destroys each person one by one, including its dependent associations and before_ and
|
769
834
|
# after_destroy callbacks.
|
835
|
+
#
|
836
|
+
# +conditions+ can be anything that +find+ also accepts:
|
837
|
+
#
|
838
|
+
# Person.destroy_all(:last_login => 6.hours.ago)
|
770
839
|
def destroy_all(conditions = nil)
|
771
840
|
find(:all, :conditions => conditions).each { |object| object.destroy }
|
772
841
|
end
|
773
842
|
|
774
843
|
# Deletes the records matching +conditions+ without instantiating the records first, and hence not
|
775
|
-
# calling the destroy method
|
776
|
-
# than destroy_all
|
844
|
+
# calling the +destroy+ method nor invoking callbacks. This is a single SQL DELETE statement that
|
845
|
+
# goes straight to the database, much more efficient than +destroy_all+. Be careful with relations
|
846
|
+
# though, in particular <tt>:dependent</tt> rules defined on associations are not honored.
|
777
847
|
#
|
778
|
-
# ====
|
848
|
+
# ==== Parameters
|
779
849
|
#
|
780
850
|
# * +conditions+ - Conditions are specified the same way as with +find+ method.
|
781
851
|
#
|
782
852
|
# ==== Example
|
783
853
|
#
|
784
|
-
# Post.delete_all
|
854
|
+
# Post.delete_all("person_id = 5 AND (category = 'Something' OR category = 'Else')")
|
855
|
+
# Post.delete_all(["person_id = ? AND (category = ? OR category = ?)", 5, 'Something', 'Else'])
|
785
856
|
#
|
786
|
-
#
|
787
|
-
# associations or call your before_ or after_destroy callbacks, use the +destroy_all+ method instead.
|
857
|
+
# Both calls delete the affected posts all at once with a single DELETE statement. If you need to destroy dependent
|
858
|
+
# associations or call your <tt>before_*</tt> or +after_destroy+ callbacks, use the +destroy_all+ method instead.
|
788
859
|
def delete_all(conditions = nil)
|
789
860
|
sql = "DELETE FROM #{quoted_table_name} "
|
790
861
|
add_conditions!(sql, conditions, scope(:find))
|
@@ -795,7 +866,7 @@ module ActiveRecord #:nodoc:
|
|
795
866
|
# The use of this method should be restricted to complicated SQL queries that can't be executed
|
796
867
|
# using the ActiveRecord::Calculations class methods. Look into those before using this.
|
797
868
|
#
|
798
|
-
# ====
|
869
|
+
# ==== Parameters
|
799
870
|
#
|
800
871
|
# * +sql+ - An SQL statement which should return a count query from the database, see the example below.
|
801
872
|
#
|
@@ -813,7 +884,7 @@ module ActiveRecord #:nodoc:
|
|
813
884
|
# with the given ID, altering the given hash of counters by the amount
|
814
885
|
# given by the corresponding value:
|
815
886
|
#
|
816
|
-
# ====
|
887
|
+
# ==== Parameters
|
817
888
|
#
|
818
889
|
# * +id+ - The id of the object you wish to update a counter on.
|
819
890
|
# * +counters+ - An Array of Hashes containing the names of the fields
|
@@ -843,7 +914,7 @@ module ActiveRecord #:nodoc:
|
|
843
914
|
# For example, a DiscussionBoard may cache post_count and comment_count otherwise every time the board is
|
844
915
|
# shown it would have to run an SQL query to find how many posts and comments there are.
|
845
916
|
#
|
846
|
-
# ====
|
917
|
+
# ==== Parameters
|
847
918
|
#
|
848
919
|
# * +counter_name+ - The name of the field that should be incremented.
|
849
920
|
# * +id+ - The id of the object that should be incremented.
|
@@ -860,7 +931,7 @@ module ActiveRecord #:nodoc:
|
|
860
931
|
#
|
861
932
|
# This works the same as increment_counter but reduces the column value by 1 instead of increasing it.
|
862
933
|
#
|
863
|
-
# ====
|
934
|
+
# ==== Parameters
|
864
935
|
#
|
865
936
|
# * +counter_name+ - The name of the field that should be decremented.
|
866
937
|
# * +id+ - The id of the object that should be decremented.
|
@@ -899,12 +970,12 @@ module ActiveRecord #:nodoc:
|
|
899
970
|
# To start from an all-closed default and enable attributes as needed,
|
900
971
|
# have a look at +attr_accessible+.
|
901
972
|
def attr_protected(*attributes)
|
902
|
-
write_inheritable_attribute(
|
973
|
+
write_inheritable_attribute(:attr_protected, Set.new(attributes.map(&:to_s)) + (protected_attributes || []))
|
903
974
|
end
|
904
975
|
|
905
976
|
# Returns an array of all the attributes that have been protected from mass-assignment.
|
906
977
|
def protected_attributes # :nodoc:
|
907
|
-
read_inheritable_attribute(
|
978
|
+
read_inheritable_attribute(:attr_protected)
|
908
979
|
end
|
909
980
|
|
910
981
|
# Specifies a white list of model attributes that can be set via
|
@@ -932,22 +1003,22 @@ module ActiveRecord #:nodoc:
|
|
932
1003
|
# customer.credit_rating = "Average"
|
933
1004
|
# customer.credit_rating # => "Average"
|
934
1005
|
def attr_accessible(*attributes)
|
935
|
-
write_inheritable_attribute(
|
1006
|
+
write_inheritable_attribute(:attr_accessible, Set.new(attributes.map(&:to_s)) + (accessible_attributes || []))
|
936
1007
|
end
|
937
1008
|
|
938
1009
|
# Returns an array of all the attributes that have been made accessible to mass-assignment.
|
939
1010
|
def accessible_attributes # :nodoc:
|
940
|
-
read_inheritable_attribute(
|
1011
|
+
read_inheritable_attribute(:attr_accessible)
|
941
1012
|
end
|
942
1013
|
|
943
1014
|
# Attributes listed as readonly can be set for a new record, but will be ignored in database updates afterwards.
|
944
1015
|
def attr_readonly(*attributes)
|
945
|
-
write_inheritable_attribute(
|
1016
|
+
write_inheritable_attribute(:attr_readonly, Set.new(attributes.map(&:to_s)) + (readonly_attributes || []))
|
946
1017
|
end
|
947
1018
|
|
948
1019
|
# Returns an array of all the attributes that have been specified as readonly.
|
949
1020
|
def readonly_attributes
|
950
|
-
read_inheritable_attribute(
|
1021
|
+
read_inheritable_attribute(:attr_readonly)
|
951
1022
|
end
|
952
1023
|
|
953
1024
|
# If you have an attribute that needs to be saved to the database as an object, and retrieved as the same object,
|
@@ -955,7 +1026,7 @@ module ActiveRecord #:nodoc:
|
|
955
1026
|
# The serialization is done through YAML. If +class_name+ is specified, the serialized object must be of that
|
956
1027
|
# class on retrieval or SerializationTypeMismatch will be raised.
|
957
1028
|
#
|
958
|
-
# ====
|
1029
|
+
# ==== Parameters
|
959
1030
|
#
|
960
1031
|
# * +attr_name+ - The field name that should be serialized.
|
961
1032
|
# * +class_name+ - Optional, class name that the object type should be equal to.
|
@@ -971,7 +1042,7 @@ module ActiveRecord #:nodoc:
|
|
971
1042
|
|
972
1043
|
# Returns a hash of all the attributes that have been specified for serialization as keys and their class restriction as values.
|
973
1044
|
def serialized_attributes
|
974
|
-
read_inheritable_attribute(
|
1045
|
+
read_inheritable_attribute(:attr_serialized) or write_inheritable_attribute(:attr_serialized, {})
|
975
1046
|
end
|
976
1047
|
|
977
1048
|
|
@@ -1182,7 +1253,32 @@ module ActiveRecord #:nodoc:
|
|
1182
1253
|
end
|
1183
1254
|
end
|
1184
1255
|
|
1185
|
-
# Resets all the cached information about columns, which will cause them
|
1256
|
+
# Resets all the cached information about columns, which will cause them
|
1257
|
+
# to be reloaded on the next request.
|
1258
|
+
#
|
1259
|
+
# The most common usage pattern for this method is probably in a migration,
|
1260
|
+
# when just after creating a table you want to populate it with some default
|
1261
|
+
# values, eg:
|
1262
|
+
#
|
1263
|
+
# class CreateJobLevels < ActiveRecord::Migration
|
1264
|
+
# def self.up
|
1265
|
+
# create_table :job_levels do |t|
|
1266
|
+
# t.integer :id
|
1267
|
+
# t.string :name
|
1268
|
+
#
|
1269
|
+
# t.timestamps
|
1270
|
+
# end
|
1271
|
+
#
|
1272
|
+
# JobLevel.reset_column_information
|
1273
|
+
# %w{assistant executive manager director}.each do |type|
|
1274
|
+
# JobLevel.create(:name => type)
|
1275
|
+
# end
|
1276
|
+
# end
|
1277
|
+
#
|
1278
|
+
# def self.down
|
1279
|
+
# drop_table :job_levels
|
1280
|
+
# end
|
1281
|
+
# end
|
1186
1282
|
def reset_column_information
|
1187
1283
|
generated_methods.each { |name| undef_method(name) }
|
1188
1284
|
@column_names = @columns = @columns_hash = @content_columns = @dynamic_methods_hash = @generated_methods = @inheritance_column = nil
|
@@ -1192,11 +1288,46 @@ module ActiveRecord #:nodoc:
|
|
1192
1288
|
subclasses.each { |klass| klass.reset_inheritable_attributes; klass.reset_column_information }
|
1193
1289
|
end
|
1194
1290
|
|
1291
|
+
def self_and_descendents_from_active_record#nodoc:
|
1292
|
+
klass = self
|
1293
|
+
classes = [klass]
|
1294
|
+
while klass != klass.base_class
|
1295
|
+
classes << klass = klass.superclass
|
1296
|
+
end
|
1297
|
+
classes
|
1298
|
+
rescue
|
1299
|
+
# OPTIMIZE this rescue is to fix this test: ./test/cases/reflection_test.rb:56:in `test_human_name_for_column'
|
1300
|
+
# Appearantly the method base_class causes some trouble.
|
1301
|
+
# It now works for sure.
|
1302
|
+
[self]
|
1303
|
+
end
|
1304
|
+
|
1195
1305
|
# Transforms attribute key names into a more humane format, such as "First name" instead of "first_name". Example:
|
1196
1306
|
# Person.human_attribute_name("first_name") # => "First name"
|
1197
|
-
#
|
1198
|
-
|
1199
|
-
|
1307
|
+
# This used to be depricated in favor of humanize, but is now preferred, because it automatically uses the I18n
|
1308
|
+
# module now.
|
1309
|
+
# Specify +options+ with additional translating options.
|
1310
|
+
def human_attribute_name(attribute_key_name, options = {})
|
1311
|
+
defaults = self_and_descendents_from_active_record.map do |klass|
|
1312
|
+
:"#{klass.name.underscore}.#{attribute_key_name}"
|
1313
|
+
end
|
1314
|
+
defaults << options[:default] if options[:default]
|
1315
|
+
defaults.flatten!
|
1316
|
+
defaults << attribute_key_name.humanize
|
1317
|
+
options[:count] ||= 1
|
1318
|
+
I18n.translate(defaults.shift, options.merge(:default => defaults, :scope => [:activerecord, :attributes]))
|
1319
|
+
end
|
1320
|
+
|
1321
|
+
# Transform the modelname into a more humane format, using I18n.
|
1322
|
+
# Defaults to the basic humanize method.
|
1323
|
+
# Default scope of the translation is activerecord.models
|
1324
|
+
# Specify +options+ with additional translating options.
|
1325
|
+
def human_name(options = {})
|
1326
|
+
defaults = self_and_descendents_from_active_record.map do |klass|
|
1327
|
+
:"#{klass.name.underscore}"
|
1328
|
+
end
|
1329
|
+
defaults << self.name.humanize
|
1330
|
+
I18n.translate(defaults.shift, {:scope => [:activerecord, :models], :count => 1, :default => defaults}.merge(options))
|
1200
1331
|
end
|
1201
1332
|
|
1202
1333
|
# True if this isn't a concrete subclass needing a STI type condition.
|
@@ -1254,7 +1385,7 @@ module ActiveRecord #:nodoc:
|
|
1254
1385
|
if logger && logger.level <= log_level
|
1255
1386
|
result = nil
|
1256
1387
|
seconds = Benchmark.realtime { result = use_silence ? silence { yield } : yield }
|
1257
|
-
logger.add(log_level, "#{title} (#{'%.
|
1388
|
+
logger.add(log_level, "#{title} (#{'%.1f' % (seconds * 1000)}ms)")
|
1258
1389
|
result
|
1259
1390
|
else
|
1260
1391
|
yield
|
@@ -1291,8 +1422,8 @@ module ActiveRecord #:nodoc:
|
|
1291
1422
|
end
|
1292
1423
|
|
1293
1424
|
def respond_to?(method_id, include_private = false)
|
1294
|
-
if match =
|
1295
|
-
return true if all_attributes_exists?(
|
1425
|
+
if match = DynamicFinderMatch.match(method_id)
|
1426
|
+
return true if all_attributes_exists?(match.attribute_names)
|
1296
1427
|
end
|
1297
1428
|
super
|
1298
1429
|
end
|
@@ -1301,6 +1432,20 @@ module ActiveRecord #:nodoc:
|
|
1301
1432
|
store_full_sti_class ? name : name.demodulize
|
1302
1433
|
end
|
1303
1434
|
|
1435
|
+
# Merges conditions so that the result is a valid +condition+
|
1436
|
+
def merge_conditions(*conditions)
|
1437
|
+
segments = []
|
1438
|
+
|
1439
|
+
conditions.each do |condition|
|
1440
|
+
unless condition.blank?
|
1441
|
+
sql = sanitize_sql(condition)
|
1442
|
+
segments << sql unless sql.blank?
|
1443
|
+
end
|
1444
|
+
end
|
1445
|
+
|
1446
|
+
"(#{segments.join(') AND (')})" unless segments.empty?
|
1447
|
+
end
|
1448
|
+
|
1304
1449
|
private
|
1305
1450
|
def find_initial(options)
|
1306
1451
|
options.update(:limit => 1)
|
@@ -1467,12 +1612,20 @@ module ActiveRecord #:nodoc:
|
|
1467
1612
|
end
|
1468
1613
|
end
|
1469
1614
|
|
1615
|
+
def default_select(qualified)
|
1616
|
+
if qualified
|
1617
|
+
quoted_table_name + '.*'
|
1618
|
+
else
|
1619
|
+
'*'
|
1620
|
+
end
|
1621
|
+
end
|
1622
|
+
|
1470
1623
|
def construct_finder_sql(options)
|
1471
1624
|
scope = scope(:find)
|
1472
|
-
sql = "SELECT #{options[:select] || (scope && scope[:select]) || (
|
1625
|
+
sql = "SELECT #{options[:select] || (scope && scope[:select]) || default_select(options[:joins] || (scope && scope[:joins]))} "
|
1473
1626
|
sql << "FROM #{(scope && scope[:from]) || options[:from] || quoted_table_name} "
|
1474
1627
|
|
1475
|
-
add_joins!(sql, options, scope)
|
1628
|
+
add_joins!(sql, options[:joins], scope)
|
1476
1629
|
add_conditions!(sql, options[:conditions], scope)
|
1477
1630
|
|
1478
1631
|
add_group!(sql, options[:group], scope)
|
@@ -1488,18 +1641,20 @@ module ActiveRecord #:nodoc:
|
|
1488
1641
|
(safe_to_array(first) + safe_to_array(second)).uniq
|
1489
1642
|
end
|
1490
1643
|
|
1491
|
-
|
1492
|
-
|
1493
|
-
|
1494
|
-
|
1495
|
-
|
1496
|
-
|
1497
|
-
|
1498
|
-
|
1644
|
+
def merge_joins(*joins)
|
1645
|
+
if joins.any?{|j| j.is_a?(String) || array_of_strings?(j) }
|
1646
|
+
joins = joins.collect do |join|
|
1647
|
+
join = [join] if join.is_a?(String)
|
1648
|
+
unless array_of_strings?(join)
|
1649
|
+
join_dependency = ActiveRecord::Associations::ClassMethods::InnerJoinDependency.new(self, join, nil)
|
1650
|
+
join = join_dependency.join_associations.collect { |assoc| assoc.association_join }
|
1651
|
+
end
|
1652
|
+
join
|
1499
1653
|
end
|
1654
|
+
joins.flatten.uniq
|
1655
|
+
else
|
1656
|
+
joins.collect{|j| safe_to_array(j)}.flatten.uniq
|
1500
1657
|
end
|
1501
|
-
|
1502
|
-
"(#{segments.join(') AND (')})" unless segments.empty?
|
1503
1658
|
end
|
1504
1659
|
|
1505
1660
|
# Object#to_a is deprecated, though it does have the desired behavior
|
@@ -1514,6 +1669,10 @@ module ActiveRecord #:nodoc:
|
|
1514
1669
|
end
|
1515
1670
|
end
|
1516
1671
|
|
1672
|
+
def array_of_strings?(o)
|
1673
|
+
o.is_a?(Array) && o.all?{|obj| obj.is_a?(String)}
|
1674
|
+
end
|
1675
|
+
|
1517
1676
|
def add_order!(sql, order, scope = :auto)
|
1518
1677
|
scope = scope(:find) if :auto == scope
|
1519
1678
|
scoped_order = scope[:order] if scope
|
@@ -1557,16 +1716,19 @@ module ActiveRecord #:nodoc:
|
|
1557
1716
|
end
|
1558
1717
|
|
1559
1718
|
# The optional scope argument is for the current <tt>:find</tt> scope.
|
1560
|
-
def add_joins!(sql,
|
1719
|
+
def add_joins!(sql, joins, scope = :auto)
|
1561
1720
|
scope = scope(:find) if :auto == scope
|
1562
|
-
|
1563
|
-
|
1564
|
-
|
1565
|
-
|
1566
|
-
sql <<
|
1721
|
+
merged_joins = scope && scope[:joins] && joins ? merge_joins(scope[:joins], joins) : (joins || scope && scope[:joins])
|
1722
|
+
case merged_joins
|
1723
|
+
when Symbol, Hash, Array
|
1724
|
+
if array_of_strings?(merged_joins)
|
1725
|
+
sql << merged_joins.join(' ') + " "
|
1567
1726
|
else
|
1568
|
-
|
1727
|
+
join_dependency = ActiveRecord::Associations::ClassMethods::InnerJoinDependency.new(self, merged_joins, nil)
|
1728
|
+
sql << " #{join_dependency.join_associations.collect { |assoc| assoc.association_join }.join} "
|
1569
1729
|
end
|
1730
|
+
when String
|
1731
|
+
sql << " #{merged_joins} "
|
1570
1732
|
end
|
1571
1733
|
end
|
1572
1734
|
|
@@ -1610,89 +1772,68 @@ module ActiveRecord #:nodoc:
|
|
1610
1772
|
#
|
1611
1773
|
# Each dynamic finder or initializer/creator is also defined in the class after it is first invoked, so that future
|
1612
1774
|
# attempts to use it do not run through method_missing.
|
1613
|
-
def method_missing(method_id, *arguments)
|
1614
|
-
if match =
|
1615
|
-
|
1616
|
-
|
1617
|
-
attribute_names = extract_attribute_names_from_match(match)
|
1775
|
+
def method_missing(method_id, *arguments, &block)
|
1776
|
+
if match = DynamicFinderMatch.match(method_id)
|
1777
|
+
attribute_names = match.attribute_names
|
1618
1778
|
super unless all_attributes_exists?(attribute_names)
|
1619
|
-
|
1620
|
-
|
1621
|
-
|
1622
|
-
|
1623
|
-
|
1624
|
-
|
1625
|
-
|
1626
|
-
|
1627
|
-
|
1628
|
-
|
1629
|
-
|
1630
|
-
|
1779
|
+
if match.finder?
|
1780
|
+
finder = match.finder
|
1781
|
+
bang = match.bang?
|
1782
|
+
self.class_eval %{
|
1783
|
+
def self.#{method_id}(*args)
|
1784
|
+
options = args.extract_options!
|
1785
|
+
attributes = construct_attributes_from_arguments([:#{attribute_names.join(',:')}], args)
|
1786
|
+
finder_options = { :conditions => attributes }
|
1787
|
+
validate_find_options(options)
|
1788
|
+
set_readonly_option!(options)
|
1789
|
+
|
1790
|
+
#{'result = ' if bang}if options[:conditions]
|
1791
|
+
with_scope(:find => finder_options) do
|
1792
|
+
find(:#{finder}, options)
|
1793
|
+
end
|
1794
|
+
else
|
1795
|
+
find(:#{finder}, options.merge(finder_options))
|
1631
1796
|
end
|
1632
|
-
|
1633
|
-
ActiveSupport::Deprecation.silence { send(:#{finder}, options.merge(finder_options)) }
|
1634
|
-
end
|
1635
|
-
end
|
1636
|
-
}, __FILE__, __LINE__
|
1637
|
-
send(method_id, *arguments)
|
1638
|
-
elsif match = matches_dynamic_finder_with_initialize_or_create?(method_id)
|
1639
|
-
instantiator = determine_instantiator(match)
|
1640
|
-
attribute_names = extract_attribute_names_from_match(match)
|
1641
|
-
super unless all_attributes_exists?(attribute_names)
|
1642
|
-
|
1643
|
-
self.class_eval %{
|
1644
|
-
def self.#{method_id}(*args)
|
1645
|
-
guard_protected_attributes = false
|
1646
|
-
|
1647
|
-
if args[0].is_a?(Hash)
|
1648
|
-
guard_protected_attributes = true
|
1649
|
-
attributes = args[0].with_indifferent_access
|
1650
|
-
find_attributes = attributes.slice(*[:#{attribute_names.join(',:')}])
|
1651
|
-
else
|
1652
|
-
find_attributes = attributes = construct_attributes_from_arguments([:#{attribute_names.join(',:')}], args)
|
1797
|
+
#{'result || raise(RecordNotFound)' if bang}
|
1653
1798
|
end
|
1799
|
+
}, __FILE__, __LINE__
|
1800
|
+
send(method_id, *arguments)
|
1801
|
+
elsif match.instantiator?
|
1802
|
+
instantiator = match.instantiator
|
1803
|
+
self.class_eval %{
|
1804
|
+
def self.#{method_id}(*args)
|
1805
|
+
guard_protected_attributes = false
|
1806
|
+
|
1807
|
+
if args[0].is_a?(Hash)
|
1808
|
+
guard_protected_attributes = true
|
1809
|
+
attributes = args[0].with_indifferent_access
|
1810
|
+
find_attributes = attributes.slice(*[:#{attribute_names.join(',:')}])
|
1811
|
+
else
|
1812
|
+
find_attributes = attributes = construct_attributes_from_arguments([:#{attribute_names.join(',:')}], args)
|
1813
|
+
end
|
1654
1814
|
|
1655
|
-
|
1656
|
-
|
1815
|
+
options = { :conditions => find_attributes }
|
1816
|
+
set_readonly_option!(options)
|
1657
1817
|
|
1658
|
-
|
1818
|
+
record = find(:first, options)
|
1659
1819
|
|
1660
|
-
|
1661
|
-
|
1662
|
-
|
1663
|
-
|
1664
|
-
|
1665
|
-
|
1666
|
-
|
1820
|
+
if record.nil?
|
1821
|
+
record = self.new { |r| r.send(:attributes=, attributes, guard_protected_attributes) }
|
1822
|
+
#{'yield(record) if block_given?'}
|
1823
|
+
#{'record.save' if instantiator == :create}
|
1824
|
+
record
|
1825
|
+
else
|
1826
|
+
record
|
1827
|
+
end
|
1667
1828
|
end
|
1668
|
-
|
1669
|
-
|
1670
|
-
|
1829
|
+
}, __FILE__, __LINE__
|
1830
|
+
send(method_id, *arguments, &block)
|
1831
|
+
end
|
1671
1832
|
else
|
1672
1833
|
super
|
1673
1834
|
end
|
1674
1835
|
end
|
1675
1836
|
|
1676
|
-
def matches_dynamic_finder?(method_id)
|
1677
|
-
/^find_(all_by|by)_([_a-zA-Z]\w*)$/.match(method_id.to_s)
|
1678
|
-
end
|
1679
|
-
|
1680
|
-
def matches_dynamic_finder_with_initialize_or_create?(method_id)
|
1681
|
-
/^find_or_(initialize|create)_by_([_a-zA-Z]\w*)$/.match(method_id.to_s)
|
1682
|
-
end
|
1683
|
-
|
1684
|
-
def determine_finder(match)
|
1685
|
-
match.captures.first == 'all_by' ? :find_every : :find_initial
|
1686
|
-
end
|
1687
|
-
|
1688
|
-
def determine_instantiator(match)
|
1689
|
-
match.captures.first == 'initialize' ? :new : :create
|
1690
|
-
end
|
1691
|
-
|
1692
|
-
def extract_attribute_names_from_match(match)
|
1693
|
-
match.captures.last.split('_and_')
|
1694
|
-
end
|
1695
|
-
|
1696
1837
|
def construct_attributes_from_arguments(attribute_names, arguments)
|
1697
1838
|
attributes = {}
|
1698
1839
|
attribute_names.each_with_index { |name, idx| attributes[name] = arguments[idx] }
|
@@ -1809,6 +1950,9 @@ module ActiveRecord #:nodoc:
|
|
1809
1950
|
# end
|
1810
1951
|
# end
|
1811
1952
|
# end
|
1953
|
+
#
|
1954
|
+
# *Note*: the +:find+ scope also has effect on update and deletion methods,
|
1955
|
+
# like +update_all+ and +delete_all+.
|
1812
1956
|
def with_scope(method_scoping = {}, action = :merge, &block)
|
1813
1957
|
method_scoping = method_scoping.method_scoping if method_scoping.respond_to?(:method_scoping)
|
1814
1958
|
|
@@ -1837,6 +1981,8 @@ module ActiveRecord #:nodoc:
|
|
1837
1981
|
hash[method][key] = merge_conditions(params[key], hash[method][key])
|
1838
1982
|
elsif key == :include && merge
|
1839
1983
|
hash[method][key] = merge_includes(hash[method][key], params[key]).uniq
|
1984
|
+
elsif key == :joins && merge
|
1985
|
+
hash[method][key] = merge_joins(params[key], hash[method][key])
|
1840
1986
|
else
|
1841
1987
|
hash[method][key] = hash[method][key] || params[key]
|
1842
1988
|
end
|
@@ -1884,20 +2030,8 @@ module ActiveRecord #:nodoc:
|
|
1884
2030
|
end
|
1885
2031
|
end
|
1886
2032
|
|
1887
|
-
def
|
1888
|
-
|
1889
|
-
scoped_methods[self] ||= []
|
1890
|
-
end
|
1891
|
-
|
1892
|
-
def single_threaded_scoped_methods #:nodoc:
|
1893
|
-
@scoped_methods ||= []
|
1894
|
-
end
|
1895
|
-
|
1896
|
-
# pick up the correct scoped_methods version from @@allow_concurrency
|
1897
|
-
if @@allow_concurrency
|
1898
|
-
alias_method :scoped_methods, :thread_safe_scoped_methods
|
1899
|
-
else
|
1900
|
-
alias_method :scoped_methods, :single_threaded_scoped_methods
|
2033
|
+
def scoped_methods #:nodoc:
|
2034
|
+
Thread.current[:"#{self}_scoped_methods"] ||= []
|
1901
2035
|
end
|
1902
2036
|
|
1903
2037
|
def current_scoped_methods #:nodoc:
|
@@ -1908,10 +2042,12 @@ module ActiveRecord #:nodoc:
|
|
1908
2042
|
# MyApp::Business::Account would appear as MyApp::Business::AccountSubclass.
|
1909
2043
|
def compute_type(type_name)
|
1910
2044
|
modularized_name = type_name_with_module(type_name)
|
1911
|
-
|
1912
|
-
|
1913
|
-
|
1914
|
-
|
2045
|
+
silence_warnings do
|
2046
|
+
begin
|
2047
|
+
class_eval(modularized_name, __FILE__, __LINE__)
|
2048
|
+
rescue NameError
|
2049
|
+
class_eval(type_name, __FILE__, __LINE__)
|
2050
|
+
end
|
1915
2051
|
end
|
1916
2052
|
end
|
1917
2053
|
|
@@ -2002,24 +2138,28 @@ module ActiveRecord #:nodoc:
|
|
2002
2138
|
# # => "age BETWEEN 13 AND 18"
|
2003
2139
|
# { 'other_records.id' => 7 }
|
2004
2140
|
# # => "`other_records`.`id` = 7"
|
2141
|
+
# { :other_records => { :id => 7 } }
|
2142
|
+
# # => "`other_records`.`id` = 7"
|
2005
2143
|
# And for value objects on a composed_of relationship:
|
2006
2144
|
# { :address => Address.new("123 abc st.", "chicago") }
|
2007
2145
|
# # => "address_street='123 abc st.' and address_city='chicago'"
|
2008
|
-
def sanitize_sql_hash_for_conditions(attrs)
|
2146
|
+
def sanitize_sql_hash_for_conditions(attrs, table_name = quoted_table_name)
|
2009
2147
|
attrs = expand_hash_conditions_for_aggregates(attrs)
|
2010
2148
|
|
2011
2149
|
conditions = attrs.map do |attr, value|
|
2012
|
-
|
2150
|
+
unless value.is_a?(Hash)
|
2151
|
+
attr = attr.to_s
|
2152
|
+
|
2153
|
+
# Extract table name from qualified attribute names.
|
2154
|
+
if attr.include?('.')
|
2155
|
+
table_name, attr = attr.split('.', 2)
|
2156
|
+
table_name = connection.quote_table_name(table_name)
|
2157
|
+
end
|
2013
2158
|
|
2014
|
-
|
2015
|
-
if attr.include?('.')
|
2016
|
-
table_name, attr = attr.split('.', 2)
|
2017
|
-
table_name = connection.quote_table_name(table_name)
|
2159
|
+
"#{table_name}.#{connection.quote_column_name(attr)} #{attribute_condition(value)}"
|
2018
2160
|
else
|
2019
|
-
|
2161
|
+
sanitize_sql_hash_for_conditions(value, connection.quote_table_name(attr.to_s))
|
2020
2162
|
end
|
2021
|
-
|
2022
|
-
"#{table_name}.#{connection.quote_column_name(attr)} #{attribute_condition(value)}"
|
2023
2163
|
end.join(' AND ')
|
2024
2164
|
|
2025
2165
|
replace_bind_variables(conditions, expand_range_bind_variables(attrs.values))
|
@@ -2073,6 +2213,8 @@ module ActiveRecord #:nodoc:
|
|
2073
2213
|
expanded = []
|
2074
2214
|
|
2075
2215
|
bind_vars.each do |var|
|
2216
|
+
next if var.is_a?(Hash)
|
2217
|
+
|
2076
2218
|
if var.is_a?(Range)
|
2077
2219
|
expanded << var.first
|
2078
2220
|
expanded << var.last
|
@@ -2085,7 +2227,7 @@ module ActiveRecord #:nodoc:
|
|
2085
2227
|
end
|
2086
2228
|
|
2087
2229
|
def quote_bound_value(value) #:nodoc:
|
2088
|
-
if value.respond_to?(:map) && !value.
|
2230
|
+
if value.respond_to?(:map) && !value.acts_like?(:string)
|
2089
2231
|
if value.respond_to?(:empty?) && value.empty?
|
2090
2232
|
connection.quote(nil)
|
2091
2233
|
else
|
@@ -2157,7 +2299,28 @@ module ActiveRecord #:nodoc:
|
|
2157
2299
|
|
2158
2300
|
end
|
2159
2301
|
|
2160
|
-
#
|
2302
|
+
# Returns a String, which Action Pack uses for constructing an URL to this
|
2303
|
+
# object. The default implementation returns this record's id as a String,
|
2304
|
+
# or nil if this record's unsaved.
|
2305
|
+
#
|
2306
|
+
# For example, suppose that you have a Users model, and that you have a
|
2307
|
+
# <tt>map.resources :users</tt> route. Normally, +users_path+ will
|
2308
|
+
# construct an URI with the user object's 'id' in it:
|
2309
|
+
#
|
2310
|
+
# user = User.find_by_name('Phusion')
|
2311
|
+
# user_path(path) # => "/users/1"
|
2312
|
+
#
|
2313
|
+
# You can override +to_param+ in your model to make +users_path+ construct
|
2314
|
+
# an URI using the user's name instead of the user's id:
|
2315
|
+
#
|
2316
|
+
# class User < ActiveRecord::Base
|
2317
|
+
# def to_param # overridden
|
2318
|
+
# name
|
2319
|
+
# end
|
2320
|
+
# end
|
2321
|
+
#
|
2322
|
+
# user = User.find_by_name('Phusion')
|
2323
|
+
# user_path(path) # => "/users/Phusion"
|
2161
2324
|
def to_param
|
2162
2325
|
# We can't use alias_method here, because method 'id' optimizes itself on the fly.
|
2163
2326
|
(id = self.id) ? id.to_s : nil # Be sure to stringify the id for routes
|
@@ -2173,11 +2336,11 @@ module ActiveRecord #:nodoc:
|
|
2173
2336
|
def cache_key
|
2174
2337
|
case
|
2175
2338
|
when new_record?
|
2176
|
-
"#{self.class.
|
2177
|
-
when self[:updated_at]
|
2178
|
-
"#{self.class.
|
2339
|
+
"#{self.class.model_name.cache_key}/new"
|
2340
|
+
when timestamp = self[:updated_at]
|
2341
|
+
"#{self.class.model_name.cache_key}/#{id}-#{timestamp.to_s(:number)}"
|
2179
2342
|
else
|
2180
|
-
"#{self.class.
|
2343
|
+
"#{self.class.model_name.cache_key}/#{id}"
|
2181
2344
|
end
|
2182
2345
|
end
|
2183
2346
|
|
@@ -2199,32 +2362,66 @@ module ActiveRecord #:nodoc:
|
|
2199
2362
|
defined?(@new_record) && @new_record
|
2200
2363
|
end
|
2201
2364
|
|
2202
|
-
#
|
2203
|
-
#
|
2365
|
+
# :call-seq:
|
2366
|
+
# save(perform_validation = true)
|
2367
|
+
#
|
2368
|
+
# Saves the model.
|
2204
2369
|
#
|
2205
|
-
#
|
2206
|
-
#
|
2207
|
-
#
|
2208
|
-
#
|
2209
|
-
#
|
2370
|
+
# If the model is new a record gets created in the database, otherwise
|
2371
|
+
# the existing record gets updated.
|
2372
|
+
#
|
2373
|
+
# If +perform_validation+ is true validations run. If any of them fail
|
2374
|
+
# the action is cancelled and +save+ returns +false+. If the flag is
|
2375
|
+
# false validations are bypassed altogether. See
|
2376
|
+
# ActiveRecord::Validations for more information.
|
2377
|
+
#
|
2378
|
+
# There's a series of callbacks associated with +save+. If any of the
|
2379
|
+
# <tt>before_*</tt> callbacks return +false+ the action is cancelled and
|
2380
|
+
# +save+ returns +false+. See ActiveRecord::Callbacks for further
|
2381
|
+
# details.
|
2210
2382
|
def save
|
2211
2383
|
create_or_update
|
2212
2384
|
end
|
2213
2385
|
|
2214
|
-
#
|
2215
|
-
#
|
2386
|
+
# Saves the model.
|
2387
|
+
#
|
2388
|
+
# If the model is new a record gets created in the database, otherwise
|
2389
|
+
# the existing record gets updated.
|
2390
|
+
#
|
2391
|
+
# With <tt>save!</tt> validations always run. If any of them fail
|
2392
|
+
# ActiveRecord::RecordInvalid gets raised. See ActiveRecord::Validations
|
2393
|
+
# for more information.
|
2394
|
+
#
|
2395
|
+
# There's a series of callbacks associated with <tt>save!</tt>. If any of
|
2396
|
+
# the <tt>before_*</tt> callbacks return +false+ the action is cancelled
|
2397
|
+
# and <tt>save!</tt> raises ActiveRecord::RecordNotSaved. See
|
2398
|
+
# ActiveRecord::Callbacks for further details.
|
2216
2399
|
def save!
|
2217
2400
|
create_or_update || raise(RecordNotSaved)
|
2218
2401
|
end
|
2219
2402
|
|
2403
|
+
# Deletes the record in the database and freezes this instance to reflect that no changes should
|
2404
|
+
# be made (since they can't be persisted).
|
2405
|
+
#
|
2406
|
+
# Unlike #destroy, this method doesn't run any +before_delete+ and +after_delete+
|
2407
|
+
# callbacks, nor will it enforce any association +:dependent+ rules.
|
2408
|
+
#
|
2409
|
+
# In addition to deleting this record, any defined +before_delete+ and +after_delete+
|
2410
|
+
# callbacks are run, and +:dependent+ rules defined on associations are run.
|
2411
|
+
def delete
|
2412
|
+
self.class.delete(id) unless new_record?
|
2413
|
+
freeze
|
2414
|
+
end
|
2415
|
+
|
2220
2416
|
# Deletes the record in the database and freezes this instance to reflect that no changes should
|
2221
2417
|
# be made (since they can't be persisted).
|
2222
2418
|
def destroy
|
2223
2419
|
unless new_record?
|
2224
|
-
connection.delete
|
2225
|
-
DELETE FROM #{self.class.quoted_table_name}
|
2226
|
-
WHERE #{connection.quote_column_name(self.class.primary_key)} = #{quoted_id}
|
2227
|
-
|
2420
|
+
connection.delete(
|
2421
|
+
"DELETE FROM #{self.class.quoted_table_name} " +
|
2422
|
+
"WHERE #{connection.quote_column_name(self.class.primary_key)} = #{quoted_id}",
|
2423
|
+
"#{self.class.name} Destroy"
|
2424
|
+
)
|
2228
2425
|
end
|
2229
2426
|
|
2230
2427
|
freeze
|
@@ -2258,12 +2455,12 @@ module ActiveRecord #:nodoc:
|
|
2258
2455
|
end
|
2259
2456
|
end
|
2260
2457
|
|
2261
|
-
# Updates a single attribute and saves the record
|
2262
|
-
#
|
2263
|
-
#
|
2458
|
+
# Updates a single attribute and saves the record without going through the normal validation procedure.
|
2459
|
+
# This is especially useful for boolean flags on existing records. The regular +update_attribute+ method
|
2460
|
+
# in Base is replaced with this when the validations module is mixed in, which it is by default.
|
2264
2461
|
def update_attribute(name, value)
|
2265
2462
|
send(name.to_s + '=', value)
|
2266
|
-
save
|
2463
|
+
save(false)
|
2267
2464
|
end
|
2268
2465
|
|
2269
2466
|
# Updates all the attributes from the passed-in Hash and saves the record. If the object is invalid, the saving will
|
@@ -2356,10 +2553,25 @@ module ActiveRecord #:nodoc:
|
|
2356
2553
|
end
|
2357
2554
|
|
2358
2555
|
# Allows you to set all the attributes at once by passing in a hash with keys
|
2359
|
-
# matching the attribute names (which again matches the column names).
|
2360
|
-
#
|
2361
|
-
#
|
2556
|
+
# matching the attribute names (which again matches the column names).
|
2557
|
+
#
|
2558
|
+
# If +guard_protected_attributes+ is true (the default), then sensitive
|
2559
|
+
# attributes can be protected from this form of mass-assignment by using
|
2560
|
+
# the +attr_protected+ macro. Or you can alternatively specify which
|
2561
|
+
# attributes *can* be accessed with the +attr_accessible+ macro. Then all the
|
2362
2562
|
# attributes not included in that won't be allowed to be mass-assigned.
|
2563
|
+
#
|
2564
|
+
# class User < ActiveRecord::Base
|
2565
|
+
# attr_protected :is_admin
|
2566
|
+
# end
|
2567
|
+
#
|
2568
|
+
# user = User.new
|
2569
|
+
# user.attributes = { :username => 'Phusion', :is_admin => true }
|
2570
|
+
# user.username # => "Phusion"
|
2571
|
+
# user.is_admin? # => false
|
2572
|
+
#
|
2573
|
+
# user.send(:attributes=, { :username => 'Phusion', :is_admin => true }, false)
|
2574
|
+
# user.is_admin? # => true
|
2363
2575
|
def attributes=(new_attributes, guard_protected_attributes = true)
|
2364
2576
|
return if new_attributes.nil?
|
2365
2577
|
attributes = new_attributes.dup
|
@@ -2369,7 +2581,11 @@ module ActiveRecord #:nodoc:
|
|
2369
2581
|
attributes = remove_attributes_protected_from_mass_assignment(attributes) if guard_protected_attributes
|
2370
2582
|
|
2371
2583
|
attributes.each do |k, v|
|
2372
|
-
k.include?("(")
|
2584
|
+
if k.include?("(")
|
2585
|
+
multi_parameter_attributes << [ k, v ]
|
2586
|
+
else
|
2587
|
+
respond_to?(:"#{k}=") ? send(:"#{k}=", v) : raise(UnknownAttributeError, "unknown attribute: #{k}")
|
2588
|
+
end
|
2373
2589
|
end
|
2374
2590
|
|
2375
2591
|
assign_multiparameter_attributes(multi_parameter_attributes)
|
@@ -2532,11 +2748,14 @@ module ActiveRecord #:nodoc:
|
|
2532
2748
|
end
|
2533
2749
|
|
2534
2750
|
def convert_number_column_value(value)
|
2535
|
-
|
2536
|
-
|
2537
|
-
|
2538
|
-
|
2539
|
-
|
2751
|
+
if value == false
|
2752
|
+
0
|
2753
|
+
elsif value == true
|
2754
|
+
1
|
2755
|
+
elsif value.is_a?(String) && value.blank?
|
2756
|
+
nil
|
2757
|
+
else
|
2758
|
+
value
|
2540
2759
|
end
|
2541
2760
|
end
|
2542
2761
|
|
@@ -2555,7 +2774,7 @@ module ActiveRecord #:nodoc:
|
|
2555
2774
|
removed_attributes = attributes.keys - safe_attributes.keys
|
2556
2775
|
|
2557
2776
|
if removed_attributes.any?
|
2558
|
-
|
2777
|
+
log_protected_attribute_removal(removed_attributes)
|
2559
2778
|
end
|
2560
2779
|
|
2561
2780
|
safe_attributes
|
@@ -2570,6 +2789,10 @@ module ActiveRecord #:nodoc:
|
|
2570
2789
|
end
|
2571
2790
|
end
|
2572
2791
|
|
2792
|
+
def log_protected_attribute_removal(*attributes)
|
2793
|
+
logger.debug "WARNING: Can't mass-assign these protected attributes: #{attributes.join(', ')}"
|
2794
|
+
end
|
2795
|
+
|
2573
2796
|
# The primary key and inheritance column can never be set by mass-assignment for security reasons.
|
2574
2797
|
def attributes_protected_by_default
|
2575
2798
|
default = [ self.class.primary_key, self.class.inheritance_column ]
|
@@ -2723,7 +2946,7 @@ module ActiveRecord #:nodoc:
|
|
2723
2946
|
end
|
2724
2947
|
|
2725
2948
|
def object_from_yaml(string)
|
2726
|
-
return string unless string.is_a?(String)
|
2949
|
+
return string unless string.is_a?(String) && string =~ /^---/
|
2727
2950
|
YAML::load(string) rescue string
|
2728
2951
|
end
|
2729
2952
|
|