activerecord 1.11.1 → 1.12.1
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 +198 -0
- data/lib/active_record.rb +19 -14
- data/lib/active_record/acts/list.rb +8 -6
- data/lib/active_record/acts/tree.rb +33 -10
- data/lib/active_record/aggregations.rb +1 -7
- data/lib/active_record/associations.rb +151 -82
- data/lib/active_record/associations/association_collection.rb +25 -0
- data/lib/active_record/associations/association_proxy.rb +9 -8
- data/lib/active_record/associations/belongs_to_association.rb +19 -5
- data/lib/active_record/associations/has_and_belongs_to_many_association.rb +44 -69
- data/lib/active_record/associations/has_many_association.rb +6 -14
- data/lib/active_record/associations/has_one_association.rb +5 -3
- data/lib/active_record/base.rb +344 -130
- data/lib/active_record/callbacks.rb +2 -2
- data/lib/active_record/connection_adapters/abstract/connection_specification.rb +128 -0
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +104 -0
- data/lib/active_record/connection_adapters/abstract/quoting.rb +51 -0
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +249 -0
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +245 -0
- data/lib/active_record/connection_adapters/abstract_adapter.rb +29 -464
- data/lib/active_record/connection_adapters/db2_adapter.rb +40 -10
- data/lib/active_record/connection_adapters/mysql_adapter.rb +131 -60
- data/lib/active_record/connection_adapters/oci_adapter.rb +106 -26
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +211 -62
- data/lib/active_record/connection_adapters/sqlite_adapter.rb +193 -44
- data/lib/active_record/connection_adapters/sqlserver_adapter.rb +24 -15
- data/lib/active_record/fixtures.rb +47 -24
- data/lib/active_record/migration.rb +34 -5
- data/lib/active_record/observer.rb +32 -2
- data/lib/active_record/query_cache.rb +12 -11
- data/lib/active_record/schema.rb +58 -0
- data/lib/active_record/schema_dumper.rb +84 -0
- data/lib/active_record/transactions.rb +1 -3
- data/lib/active_record/validations.rb +40 -26
- data/lib/active_record/vendor/mysql.rb +6 -0
- data/lib/active_record/version.rb +9 -0
- data/rakefile +5 -16
- data/test/abstract_unit.rb +6 -11
- data/test/adapter_test.rb +58 -0
- data/test/ar_schema_test.rb +33 -0
- data/test/association_callbacks_test.rb +14 -0
- data/test/associations_go_eager_test.rb +56 -14
- data/test/associations_test.rb +245 -25
- data/test/base_test.rb +205 -34
- data/test/binary_test.rb +25 -42
- data/test/callbacks_test.rb +75 -0
- data/test/conditions_scoping_test.rb +136 -0
- data/test/connections/native_mysql/connection.rb +0 -4
- data/test/connections/native_sqlite3/in_memory_connection.rb +17 -0
- data/test/copy_table_sqlite.rb +64 -0
- data/test/deprecated_associations_test.rb +7 -6
- data/test/deprecated_finder_test.rb +3 -3
- data/test/finder_test.rb +33 -3
- data/test/fixtures/accounts.yml +5 -0
- data/test/fixtures/categories_ordered.yml +7 -0
- data/test/fixtures/category.rb +11 -1
- data/test/fixtures/comment.rb +22 -2
- data/test/fixtures/comments.yml +6 -0
- data/test/fixtures/companies.yml +15 -0
- data/test/fixtures/company.rb +24 -1
- data/test/fixtures/db_definitions/db2.drop.sql +5 -1
- data/test/fixtures/db_definitions/db2.sql +15 -1
- data/test/fixtures/db_definitions/mysql.drop.sql +2 -0
- data/test/fixtures/db_definitions/mysql.sql +17 -2
- data/test/fixtures/db_definitions/oci.drop.sql +37 -5
- data/test/fixtures/db_definitions/oci.sql +47 -4
- data/test/fixtures/db_definitions/oci2.drop.sql +1 -1
- data/test/fixtures/db_definitions/oci2.sql +2 -2
- data/test/fixtures/db_definitions/postgresql.drop.sql +4 -0
- data/test/fixtures/db_definitions/postgresql.sql +33 -4
- data/test/fixtures/db_definitions/sqlite.drop.sql +2 -0
- data/test/fixtures/db_definitions/sqlite.sql +16 -2
- data/test/fixtures/db_definitions/sqlserver.drop.sql +2 -0
- data/test/fixtures/db_definitions/sqlserver.sql +16 -2
- data/test/fixtures/developer.rb +1 -1
- data/test/fixtures/flowers.jpg +0 -0
- data/test/fixtures/keyboard.rb +3 -0
- data/test/fixtures/mixins.yml +11 -1
- data/test/fixtures/order.rb +4 -0
- data/test/fixtures/post.rb +4 -0
- data/test/fixtures/posts.yml +7 -0
- data/test/fixtures/project.rb +1 -0
- data/test/fixtures/subject.rb +4 -0
- data/test/fixtures/subscriber.rb +2 -4
- data/test/fixtures/topics.yml +2 -2
- data/test/fixtures_test.rb +79 -7
- data/test/inheritance_test.rb +2 -2
- data/test/lifecycle_test.rb +14 -6
- data/test/migration_test.rb +164 -6
- data/test/mixin_test.rb +78 -2
- data/test/pk_test.rb +25 -1
- data/test/readonly_test.rb +31 -0
- data/test/reflection_test.rb +4 -1
- data/test/schema_dumper_test.rb +19 -0
- data/test/schema_test_postgresql.rb +3 -2
- data/test/synonym_test_oci.rb +17 -0
- data/test/threaded_connections_test.rb +2 -1
- data/test/transactions_test.rb +109 -10
- data/test/validations_test.rb +70 -42
- metadata +25 -5
- data/test/fixtures/associations.png +0 -0
- data/test/thread_safety_test.rb +0 -36
@@ -33,6 +33,12 @@ module ActiveRecord
|
|
33
33
|
|
34
34
|
alias_method :push, :<<
|
35
35
|
alias_method :concat, :<<
|
36
|
+
|
37
|
+
# Remove all records from this association
|
38
|
+
def delete_all
|
39
|
+
delete(@target)
|
40
|
+
@target = []
|
41
|
+
end
|
36
42
|
|
37
43
|
# Remove +records+ from this association. Does not destroy +records+.
|
38
44
|
def delete(*records)
|
@@ -50,6 +56,17 @@ module ActiveRecord
|
|
50
56
|
end
|
51
57
|
end
|
52
58
|
end
|
59
|
+
|
60
|
+
# Removes all records from this association. Returns +self+ so method calls may be chained.
|
61
|
+
def clear
|
62
|
+
return self if empty? # forces load_target if hasn't happened already
|
63
|
+
if @options[:exclusively_dependent]
|
64
|
+
destroy_all
|
65
|
+
else
|
66
|
+
delete_all
|
67
|
+
end
|
68
|
+
self
|
69
|
+
end
|
53
70
|
|
54
71
|
def destroy_all
|
55
72
|
@owner.transaction do
|
@@ -107,6 +124,14 @@ module ActiveRecord
|
|
107
124
|
end
|
108
125
|
|
109
126
|
private
|
127
|
+
def method_missing(method, *args, &block)
|
128
|
+
if @target.respond_to?(method) or (not @association_class.respond_to?(method) and Class.respond_to?(method))
|
129
|
+
super
|
130
|
+
else
|
131
|
+
@association_class.constrain(:conditions => @finder_sql, :joins => @join_sql) { @association_class.send(method, *args, &block) }
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
110
135
|
def raise_on_type_mismatch(record)
|
111
136
|
raise ActiveRecord::AssociationTypeMismatch, "#{@association_class} expected, got #{record.class}" unless record.is_a?(@association_class)
|
112
137
|
end
|
@@ -8,7 +8,7 @@ module ActiveRecord
|
|
8
8
|
@owner = owner
|
9
9
|
@options = options
|
10
10
|
@association_name = association_name
|
11
|
-
@association_class = eval(association_class_name)
|
11
|
+
@association_class = eval(association_class_name, nil, __FILE__, __LINE__)
|
12
12
|
@association_class_primary_key_name = association_class_primary_key_name
|
13
13
|
|
14
14
|
reset
|
@@ -19,11 +19,6 @@ module ActiveRecord
|
|
19
19
|
load_target
|
20
20
|
end
|
21
21
|
|
22
|
-
def method_missing(symbol, *args, &block)
|
23
|
-
load_target
|
24
|
-
@target.send(symbol, *args, &block)
|
25
|
-
end
|
26
|
-
|
27
22
|
def respond_to?(symbol, include_priv = false)
|
28
23
|
proxy_respond_to?(symbol, include_priv) || (load_target && @target.respond_to?(symbol, include_priv))
|
29
24
|
end
|
@@ -44,7 +39,7 @@ module ActiveRecord
|
|
44
39
|
@target = t
|
45
40
|
@loaded = true
|
46
41
|
end
|
47
|
-
|
42
|
+
|
48
43
|
protected
|
49
44
|
def dependent?
|
50
45
|
@options[:dependent] || false
|
@@ -69,8 +64,14 @@ module ActiveRecord
|
|
69
64
|
def extract_options_from_args!(args)
|
70
65
|
@owner.send(:extract_options_from_args!, args)
|
71
66
|
end
|
72
|
-
|
67
|
+
|
73
68
|
private
|
69
|
+
|
70
|
+
def method_missing(method, *args, &block)
|
71
|
+
load_target
|
72
|
+
@target.send(method, *args, &block)
|
73
|
+
end
|
74
|
+
|
74
75
|
def load_target
|
75
76
|
if !@owner.new_record? || foreign_key_present
|
76
77
|
begin
|
@@ -1,14 +1,20 @@
|
|
1
1
|
module ActiveRecord
|
2
2
|
module Associations
|
3
3
|
class BelongsToAssociation < AssociationProxy #:nodoc:
|
4
|
+
|
5
|
+
def initialize(owner, association_name, association_class_name, association_class_primary_key_name, options)
|
6
|
+
super
|
7
|
+
construct_sql
|
8
|
+
end
|
9
|
+
|
4
10
|
def reset
|
5
11
|
@target = nil
|
6
12
|
@loaded = false
|
7
13
|
end
|
8
14
|
|
9
15
|
def create(attributes = {})
|
10
|
-
record =
|
11
|
-
record
|
16
|
+
record = @association_class.create(attributes)
|
17
|
+
replace(record, true)
|
12
18
|
record
|
13
19
|
end
|
14
20
|
|
@@ -24,13 +30,21 @@ module ActiveRecord
|
|
24
30
|
else
|
25
31
|
raise_on_type_mismatch(obj) unless obj.nil?
|
26
32
|
|
27
|
-
@target = obj
|
33
|
+
@target = (AssociationProxy === obj ? obj.target : obj)
|
28
34
|
@owner[@association_class_primary_key_name] = obj.id unless obj.new_record?
|
35
|
+
@updated = true
|
29
36
|
end
|
30
37
|
@loaded = true
|
31
38
|
|
32
39
|
return (@target.nil? ? nil : self)
|
33
40
|
end
|
41
|
+
|
42
|
+
def updated?
|
43
|
+
@updated
|
44
|
+
end
|
45
|
+
|
46
|
+
protected
|
47
|
+
|
34
48
|
|
35
49
|
private
|
36
50
|
def find_target
|
@@ -48,9 +62,9 @@ module ActiveRecord
|
|
48
62
|
def target_obsolete?
|
49
63
|
@owner[@association_class_primary_key_name] != @target.id
|
50
64
|
end
|
51
|
-
|
65
|
+
|
52
66
|
def construct_sql
|
53
|
-
|
67
|
+
@finder_sql = "#{@association_class.table_name}.#{@association_class.primary_key} = #{@owner.id}"
|
54
68
|
end
|
55
69
|
end
|
56
70
|
end
|
@@ -4,7 +4,7 @@ module ActiveRecord
|
|
4
4
|
def initialize(owner, association_name, association_class_name, association_class_primary_key_name, options)
|
5
5
|
super
|
6
6
|
|
7
|
-
@association_foreign_key = options[:association_foreign_key] ||
|
7
|
+
@association_foreign_key = options[:association_foreign_key] || association_class_name.foreign_key
|
8
8
|
@association_table_name = options[:table_name] || @association_class.table_name
|
9
9
|
@join_table = options[:join_table]
|
10
10
|
@order = options[:order]
|
@@ -19,65 +19,44 @@ module ActiveRecord
|
|
19
19
|
record
|
20
20
|
end
|
21
21
|
|
22
|
-
# Removes all records from this association. Returns +self+ so method calls may be chained.
|
23
|
-
def clear
|
24
|
-
return self if size == 0 # forces load_target if hasn't happened already
|
25
|
-
|
26
|
-
if sql = @options[:delete_sql]
|
27
|
-
each { |record| @owner.connection.execute(sql) }
|
28
|
-
elsif @options[:conditions]
|
29
|
-
sql =
|
30
|
-
"DELETE FROM #{@join_table} WHERE #{@association_class_primary_key_name} = #{@owner.quoted_id} " +
|
31
|
-
"AND #{@association_foreign_key} IN (#{collect { |record| record.id }.join(", ")})"
|
32
|
-
@owner.connection.execute(sql)
|
33
|
-
else
|
34
|
-
sql = "DELETE FROM #{@join_table} WHERE #{@association_class_primary_key_name} = #{@owner.quoted_id}"
|
35
|
-
@owner.connection.execute(sql)
|
36
|
-
end
|
37
|
-
|
38
|
-
@target = []
|
39
|
-
self
|
40
|
-
end
|
41
|
-
|
42
22
|
def find_first
|
43
23
|
load_target.first
|
44
24
|
end
|
45
|
-
|
25
|
+
|
46
26
|
def find(*args)
|
47
|
-
|
48
|
-
expects_array = args.first.kind_of?(Array)
|
49
|
-
|
50
|
-
ids = args.flatten.compact.uniq
|
51
|
-
|
52
|
-
# If no block is given, raise RecordNotFound.
|
53
|
-
if ids.empty?
|
54
|
-
raise RecordNotFound, "Couldn't find #{@association_class.name} without an ID"
|
27
|
+
options = Base.send(:extract_options_from_args!, args)
|
55
28
|
|
56
29
|
# If using a custom finder_sql, scan the entire collection.
|
57
|
-
|
30
|
+
if @options[:finder_sql]
|
31
|
+
expects_array = args.first.kind_of?(Array)
|
32
|
+
ids = args.flatten.compact.uniq
|
33
|
+
|
58
34
|
if ids.size == 1
|
59
|
-
id = ids.first
|
35
|
+
id = ids.first.to_i
|
60
36
|
record = load_target.detect { |record| id == record.id }
|
61
|
-
expects_array
|
37
|
+
expects_array ? [record] : record
|
62
38
|
else
|
63
39
|
load_target.select { |record| ids.include?(record.id) }
|
64
40
|
end
|
65
|
-
|
66
|
-
# Otherwise, construct a query.
|
67
41
|
else
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
42
|
+
conditions = "#{@finder_sql}"
|
43
|
+
if sanitized_conditions = sanitize_sql(options[:conditions])
|
44
|
+
conditions << " AND (#{sanitized_conditions})"
|
45
|
+
end
|
46
|
+
options[:conditions] = conditions
|
47
|
+
options[:joins] = @join_sql
|
48
|
+
|
49
|
+
if options[:order] && @options[:order]
|
50
|
+
options[:order] = "#{options[:order]}, #{@options[:order]}"
|
51
|
+
elsif @options[:order]
|
52
|
+
options[:order] = @options[:order]
|
78
53
|
end
|
54
|
+
|
55
|
+
# Pass through args exactly as we received them.
|
56
|
+
args << options
|
57
|
+
@association_class.find(*args)
|
79
58
|
end
|
80
|
-
end
|
59
|
+
end
|
81
60
|
|
82
61
|
def push_with_attributes(record, join_attributes = {})
|
83
62
|
raise_on_type_mismatch(record)
|
@@ -96,11 +75,16 @@ module ActiveRecord
|
|
96
75
|
end
|
97
76
|
|
98
77
|
protected
|
99
|
-
def find_target
|
100
|
-
|
78
|
+
def find_target
|
79
|
+
if @options[:finder_sql]
|
80
|
+
records = @association_class.find_by_sql(@finder_sql)
|
81
|
+
else
|
82
|
+
records = find(:all)
|
83
|
+
end
|
84
|
+
|
101
85
|
@options[:uniq] ? uniq(records) : records
|
102
86
|
end
|
103
|
-
|
87
|
+
|
104
88
|
def count_records
|
105
89
|
load_target.size
|
106
90
|
end
|
@@ -122,15 +106,17 @@ module ActiveRecord
|
|
122
106
|
when @association_foreign_key
|
123
107
|
attributes[column.name] = record.quoted_id
|
124
108
|
else
|
125
|
-
|
126
|
-
|
109
|
+
if record.attributes.has_key?(column.name)
|
110
|
+
value = @owner.send(:quote, record[column.name], column)
|
111
|
+
attributes[column.name] = value unless value.nil?
|
112
|
+
end
|
127
113
|
end
|
128
114
|
attributes
|
129
115
|
end
|
130
116
|
|
131
117
|
sql =
|
132
118
|
"INSERT INTO #{@join_table} (#{@owner.send(:quoted_column_names, attributes).join(', ')}) " +
|
133
|
-
"VALUES (#{attributes.values.
|
119
|
+
"VALUES (#{attributes.values.join(', ')})"
|
134
120
|
|
135
121
|
@owner.connection.execute(sql)
|
136
122
|
end
|
@@ -150,28 +136,17 @@ module ActiveRecord
|
|
150
136
|
|
151
137
|
def construct_sql
|
152
138
|
interpolate_sql_options!(@options, :finder_sql)
|
153
|
-
|
139
|
+
|
154
140
|
if @options[:finder_sql]
|
155
141
|
@finder_sql = @options[:finder_sql]
|
156
142
|
else
|
157
|
-
@finder_sql =
|
158
|
-
|
159
|
-
"WHERE t.#{@association_class.primary_key} = j.#{@association_foreign_key} AND " +
|
160
|
-
"j.#{@association_class_primary_key_name} = #{@owner.quoted_id} "
|
161
|
-
|
162
|
-
@finder_sql << " AND #{interpolate_sql(@options[:conditions])}" if @options[:conditions]
|
163
|
-
|
164
|
-
unless @association_class.descends_from_active_record?
|
165
|
-
type_condition = @association_class.send(:subclasses).inject("t.#{@association_class.inheritance_column} = '#{@association_class.name.demodulize}' ") do |condition, subclass|
|
166
|
-
condition << "OR t.#{@association_class.inheritance_column} = '#{subclass.name.demodulize}' "
|
167
|
-
end
|
168
|
-
|
169
|
-
@finder_sql << " AND (#{type_condition})"
|
170
|
-
end
|
171
|
-
|
172
|
-
@finder_sql << " ORDER BY #{@order}" if @order
|
143
|
+
@finder_sql = "#{@join_table}.#{@association_class_primary_key_name} = #{@owner.quoted_id} "
|
144
|
+
@finder_sql << " AND (#{interpolate_sql(@options[:conditions])})" if @options[:conditions]
|
173
145
|
end
|
146
|
+
|
147
|
+
@join_sql = "LEFT JOIN #{@join_table} ON #{@association_class.table_name}.#{@association_class.primary_key} = #{@join_table}.#{@association_foreign_key}"
|
174
148
|
end
|
149
|
+
|
175
150
|
end
|
176
151
|
end
|
177
152
|
end
|
@@ -26,7 +26,7 @@ module ActiveRecord
|
|
26
26
|
records = @association_class.find_by_sql(@finder_sql)
|
27
27
|
else
|
28
28
|
sql = @finder_sql
|
29
|
-
sql += " AND #{sanitize_sql(runtime_conditions)}" if runtime_conditions
|
29
|
+
sql += " AND (#{sanitize_sql(runtime_conditions)})" if runtime_conditions
|
30
30
|
orderings ||= @options[:order]
|
31
31
|
records = @association_class.find_all(sql, orderings, limit, joins)
|
32
32
|
end
|
@@ -45,7 +45,7 @@ module ActiveRecord
|
|
45
45
|
@association_class.count_by_sql(@finder_sql)
|
46
46
|
else
|
47
47
|
sql = @finder_sql
|
48
|
-
sql += " AND #{sanitize_sql(runtime_conditions)}" if runtime_conditions
|
48
|
+
sql += " AND (#{sanitize_sql(runtime_conditions)})" if runtime_conditions
|
49
49
|
@association_class.count(sql)
|
50
50
|
end
|
51
51
|
end
|
@@ -68,7 +68,7 @@ module ActiveRecord
|
|
68
68
|
else
|
69
69
|
conditions = "#{@finder_sql}"
|
70
70
|
if sanitized_conditions = sanitize_sql(options[:conditions])
|
71
|
-
conditions << " AND #{sanitized_conditions}"
|
71
|
+
conditions << " AND (#{sanitized_conditions})"
|
72
72
|
end
|
73
73
|
options[:conditions] = conditions
|
74
74
|
|
@@ -83,15 +83,7 @@ module ActiveRecord
|
|
83
83
|
@association_class.find(*args)
|
84
84
|
end
|
85
85
|
end
|
86
|
-
|
87
|
-
# Removes all records from this association. Returns +self+ so
|
88
|
-
# method calls may be chained.
|
89
|
-
def clear
|
90
|
-
@association_class.update_all("#{@association_class_primary_key_name} = NULL", "#{@association_class_primary_key_name} = #{@owner.quoted_id}")
|
91
|
-
@target = []
|
92
|
-
self
|
93
|
-
end
|
94
|
-
|
86
|
+
|
95
87
|
protected
|
96
88
|
def find_target
|
97
89
|
find_all
|
@@ -145,7 +137,7 @@ module ActiveRecord
|
|
145
137
|
@finder_sql = interpolate_sql(@options[:finder_sql])
|
146
138
|
else
|
147
139
|
@finder_sql = "#{@association_class.table_name}.#{@association_class_primary_key_name} = #{@owner.quoted_id}"
|
148
|
-
@finder_sql << " AND #{interpolate_sql(@conditions)}" if @conditions
|
140
|
+
@finder_sql << " AND (#{interpolate_sql(@conditions)})" if @conditions
|
149
141
|
end
|
150
142
|
|
151
143
|
if @options[:counter_sql]
|
@@ -155,7 +147,7 @@ module ActiveRecord
|
|
155
147
|
@counter_sql = interpolate_sql(@options[:counter_sql])
|
156
148
|
else
|
157
149
|
@counter_sql = "#{@association_class.table_name}.#{@association_class_primary_key_name} = #{@owner.quoted_id}"
|
158
|
-
@counter_sql << " AND #{interpolate_sql(@conditions)}" if @conditions
|
150
|
+
@counter_sql << " AND (#{interpolate_sql(@conditions)})" if @conditions
|
159
151
|
end
|
160
152
|
end
|
161
153
|
end
|
@@ -29,7 +29,7 @@ module ActiveRecord
|
|
29
29
|
def replace(obj, dont_save = false)
|
30
30
|
load_target
|
31
31
|
unless @target.nil?
|
32
|
-
if dependent? && !dont_save
|
32
|
+
if dependent? && !dont_save && @target != obj
|
33
33
|
@target.destroy unless @target.new_record?
|
34
34
|
@owner.clear_association_cache
|
35
35
|
else
|
@@ -44,7 +44,7 @@ module ActiveRecord
|
|
44
44
|
raise_on_type_mismatch(obj)
|
45
45
|
|
46
46
|
obj[@association_class_primary_key_name] = @owner.id unless @owner.new_record?
|
47
|
-
@target = obj
|
47
|
+
@target = (AssociationProxy === obj ? obj.target : obj)
|
48
48
|
end
|
49
49
|
|
50
50
|
@loaded = true
|
@@ -54,7 +54,7 @@ module ActiveRecord
|
|
54
54
|
return (obj.nil? ? nil : self)
|
55
55
|
end
|
56
56
|
end
|
57
|
-
|
57
|
+
|
58
58
|
private
|
59
59
|
def find_target
|
60
60
|
@association_class.find(:first, :conditions => @finder_sql, :order => @options[:order])
|
@@ -66,6 +66,8 @@ module ActiveRecord
|
|
66
66
|
|
67
67
|
def construct_sql
|
68
68
|
@finder_sql = "#{@association_class.table_name}.#{@association_class_primary_key_name} = #{@owner.quoted_id}#{@options[:conditions] ? " AND " + @options[:conditions] : ""}"
|
69
|
+
@finder_sql << " AND (#{sanitize_sql(@options[:conditions])})" if @options[:conditions]
|
70
|
+
@finder_sql
|
69
71
|
end
|
70
72
|
end
|
71
73
|
end
|
data/lib/active_record/base.rb
CHANGED
@@ -28,7 +28,9 @@ module ActiveRecord #:nodoc:
|
|
28
28
|
end
|
29
29
|
class ConfigurationError < StandardError #:nodoc:
|
30
30
|
end
|
31
|
-
|
31
|
+
class ReadOnlyRecord < StandardError #:nodoc:
|
32
|
+
end
|
33
|
+
|
32
34
|
class AttributeAssignmentError < ActiveRecordError #:nodoc:
|
33
35
|
attr_reader :exception, :attribute
|
34
36
|
def initialize(message, exception, attribute)
|
@@ -45,7 +47,7 @@ module ActiveRecord #:nodoc:
|
|
45
47
|
end
|
46
48
|
end
|
47
49
|
|
48
|
-
# Active Record objects
|
50
|
+
# Active Record objects don't specify their attributes directly, but rather infer them from the table definition with
|
49
51
|
# which they're linked. Adding, removing, and changing attributes and their type is done directly in the database. Any change
|
50
52
|
# is instantly reflected in the Active Record objects. The mapping that binds a given Active Record class to a certain
|
51
53
|
# database table will happen automatically in most common cases, but can be overwritten for the uncommon ones.
|
@@ -54,7 +56,7 @@ module ActiveRecord #:nodoc:
|
|
54
56
|
#
|
55
57
|
# == Creation
|
56
58
|
#
|
57
|
-
# Active Records
|
59
|
+
# Active Records accept constructor parameters either in a hash or as a block. The hash method is especially useful when
|
58
60
|
# you're receiving the data from somewhere else, like a HTTP request. It works like this:
|
59
61
|
#
|
60
62
|
# user = User.new(:name => "David", :occupation => "Code Artist")
|
@@ -77,7 +79,7 @@ module ActiveRecord #:nodoc:
|
|
77
79
|
#
|
78
80
|
# Conditions can either be specified as a string or an array representing the WHERE-part of an SQL statement.
|
79
81
|
# The array form is to be used when the condition input is tainted and requires sanitization. The string form can
|
80
|
-
# be used for statements that
|
82
|
+
# be used for statements that don't involve tainted data. Examples:
|
81
83
|
#
|
82
84
|
# User < ActiveRecord::Base
|
83
85
|
# def self.authenticate_unsafely(user_name, password)
|
@@ -125,14 +127,14 @@ module ActiveRecord #:nodoc:
|
|
125
127
|
# You can alternatively use self[:attribute]=(value) and self[:attribute] instead of write_attribute(:attribute, vaule) and
|
126
128
|
# read_attribute(:attribute) as a shorter form.
|
127
129
|
#
|
128
|
-
# == Accessing attributes before they have been
|
130
|
+
# == Accessing attributes before they have been typecasted
|
129
131
|
#
|
130
|
-
#
|
132
|
+
# Sometimes you want to be able to read the raw attribute data without having the column-determined typecast run its course first.
|
131
133
|
# That can be done by using the <attribute>_before_type_cast accessors that all attributes have. For example, if your Account model
|
132
134
|
# has a balance attribute, you can call account.balance_before_type_cast or account.id_before_type_cast.
|
133
135
|
#
|
134
136
|
# This is especially useful in validation situations where the user might supply a string for an integer field and you want to display
|
135
|
-
# the original string back in an error message. Accessing the attribute normally would
|
137
|
+
# the original string back in an error message. Accessing the attribute normally would typecast the string to 0, which isn't what you
|
136
138
|
# want.
|
137
139
|
#
|
138
140
|
# == Dynamic attribute-based finders
|
@@ -155,7 +157,7 @@ module ActiveRecord #:nodoc:
|
|
155
157
|
# == Saving arrays, hashes, and other non-mappable objects in text columns
|
156
158
|
#
|
157
159
|
# 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+.
|
158
|
-
# This makes it possible to store arrays, hashes, and other non-
|
160
|
+
# This makes it possible to store arrays, hashes, and other non-mappable objects without doing any additional work. Example:
|
159
161
|
#
|
160
162
|
# class User < ActiveRecord::Base
|
161
163
|
# serialize :preferences
|
@@ -164,7 +166,7 @@ module ActiveRecord #:nodoc:
|
|
164
166
|
# user = User.create(:preferences) => { "background" => "black", "display" => large })
|
165
167
|
# User.find(user.id).preferences # => { "background" => "black", "display" => large }
|
166
168
|
#
|
167
|
-
# You can also specify
|
169
|
+
# You can also specify a class option as the second parameter that'll raise an exception if a serialized object is retrieved as a
|
168
170
|
# descendent of a class not in the hierarchy. Example:
|
169
171
|
#
|
170
172
|
# class User < ActiveRecord::Base
|
@@ -208,7 +210,7 @@ module ActiveRecord #:nodoc:
|
|
208
210
|
# * +ActiveRecordError+ -- generic error class and superclass of all other errors raised by Active Record
|
209
211
|
# * +AdapterNotSpecified+ -- the configuration hash used in <tt>establish_connection</tt> didn't include a
|
210
212
|
# <tt>:adapter</tt> key.
|
211
|
-
# * +
|
213
|
+
# * +AdapterNotFound+ -- the <tt>:adapter</tt> key used in <tt>establish_connection</tt> specified an non-existent adapter
|
212
214
|
# (or a bad spelling of an existing one).
|
213
215
|
# * +AssociationTypeMismatch+ -- the object assigned to the association wasn't of the type specified in the association definition.
|
214
216
|
# * +SerializationTypeMismatch+ -- the object serialized wasn't of the class specified as the second parameter.
|
@@ -222,12 +224,11 @@ module ActiveRecord #:nodoc:
|
|
222
224
|
# objects that should be inspected to determine which attributes triggered the errors.
|
223
225
|
# * +AttributeAssignmentError+ -- an error occurred while doing a mass assignment through the +attributes=+ method.
|
224
226
|
# You can inspect the +attribute+ property of the exception object to determine which attribute triggered the error.
|
227
|
+
#
|
225
228
|
# *Note*: The attributes listed are class-level attributes (accessible from both the class and instance level).
|
226
229
|
# So it's possible to assign a logger to the class through Base.logger= which will then be used by all
|
227
230
|
# instances in the current object space.
|
228
231
|
class Base
|
229
|
-
include ClassInheritableAttributes
|
230
|
-
|
231
232
|
# Accepts a logger conforming to the interface of Log4r or the default Ruby 1.8+ Logger class, which is then passed
|
232
233
|
# on to any new database connections made and which can be retrieved on both a class and instance level by calling +logger+.
|
233
234
|
cattr_accessor :logger
|
@@ -236,7 +237,11 @@ module ActiveRecord #:nodoc:
|
|
236
237
|
# also be used to "borrow" the connection to do database work unrelated
|
237
238
|
# to any of the specific Active Records.
|
238
239
|
def self.connection
|
239
|
-
|
240
|
+
if allow_concurrency
|
241
|
+
retrieve_connection
|
242
|
+
else
|
243
|
+
@connection ||= retrieve_connection
|
244
|
+
end
|
240
245
|
end
|
241
246
|
|
242
247
|
# Returns the connection currently associated with the class. This can
|
@@ -251,6 +256,28 @@ module ActiveRecord #:nodoc:
|
|
251
256
|
@@subclasses[self] << child
|
252
257
|
super
|
253
258
|
end
|
259
|
+
|
260
|
+
# Allow all subclasses of AR::Base to be reloaded in dev mode, unless they
|
261
|
+
# explicitly decline the honor. USE WITH CAUTION. Only AR subclasses kept
|
262
|
+
# in the framework should use the flag, so #reset_subclasses and so forth
|
263
|
+
# leave it alone.
|
264
|
+
def self.reloadable? #:nodoc:
|
265
|
+
true
|
266
|
+
end
|
267
|
+
|
268
|
+
def self.reset_subclasses
|
269
|
+
nonreloadables = []
|
270
|
+
subclasses.each do |klass|
|
271
|
+
unless klass.reloadable?
|
272
|
+
nonreloadables << klass
|
273
|
+
next
|
274
|
+
end
|
275
|
+
klass.instance_variables.each { |var| klass.send(:remove_instance_variable, var) }
|
276
|
+
klass.instance_methods(false).each { |m| klass.send :undef_method, m }
|
277
|
+
end
|
278
|
+
@@subclasses = {}
|
279
|
+
nonreloadables.each { |klass| (@@subclasses[klass.superclass] ||= []) << klass }
|
280
|
+
end
|
254
281
|
|
255
282
|
@@subclasses = {}
|
256
283
|
|
@@ -275,14 +302,14 @@ module ActiveRecord #:nodoc:
|
|
275
302
|
cattr_accessor :table_name_suffix
|
276
303
|
@@table_name_suffix = ""
|
277
304
|
|
278
|
-
#
|
279
|
-
# If true,
|
305
|
+
# Indicates whether or not table names should be the pluralized versions of the corresponding class names.
|
306
|
+
# If true, the default table name for a +Product+ class will be +products+. If false, it would just be +product+.
|
280
307
|
# See table_name for the full rules on table/class naming. This is true, by default.
|
281
308
|
cattr_accessor :pluralize_table_names
|
282
309
|
@@pluralize_table_names = true
|
283
310
|
|
284
311
|
# Determines whether or not to use ANSI codes to colorize the logging statements committed by the connection adapter. These colors
|
285
|
-
#
|
312
|
+
# make it much easier to overview things during debugging (when used through a reader like +tail+ and on a black background), but
|
286
313
|
# may complicate matters if you use software like syslog. This is true, by default.
|
287
314
|
cattr_accessor :colorize_logging
|
288
315
|
@@colorize_logging = true
|
@@ -294,11 +321,27 @@ module ActiveRecord #:nodoc:
|
|
294
321
|
|
295
322
|
# Determines whether or not to use a connection for each thread, or a single shared connection for all threads.
|
296
323
|
# Defaults to true; Railties' WEBrick server sets this to false.
|
297
|
-
cattr_accessor :
|
298
|
-
@@
|
324
|
+
cattr_accessor :allow_concurrency
|
325
|
+
@@allow_concurrency = true
|
326
|
+
|
327
|
+
# Determines whether to speed up access by generating optimized reader
|
328
|
+
# methods to avoid expensive calls to method_missing when accessing
|
329
|
+
# attributes by name. You might want to set this to false in development
|
330
|
+
# mode, because the methods would be regenerated on each request.
|
331
|
+
cattr_accessor :generate_read_methods
|
332
|
+
@@generate_read_methods = true
|
333
|
+
|
334
|
+
# Specifies the format to use when dumping the database schema with Rails'
|
335
|
+
# Rakefile. If :sql, the schema is dumped as (potentially database-
|
336
|
+
# specific) SQL statements. If :ruby, the schema is dumped as an
|
337
|
+
# ActiveRecord::Schema file which can be loaded into any database that
|
338
|
+
# supports migrations. Use :ruby if you want to have different database
|
339
|
+
# adapters for, e.g., your development and test environments.
|
340
|
+
cattr_accessor :schema_format
|
341
|
+
@@schema_format = :sql
|
299
342
|
|
300
343
|
class << self # Class methods
|
301
|
-
# Find operates with three different
|
344
|
+
# Find operates with three different retrieval approaches:
|
302
345
|
#
|
303
346
|
# * Find by id: This can either be a specific id (1), a list of ids (1, 5, 6), or an array of ids ([5, 6, 10]).
|
304
347
|
# If no record can be found for all of the listed ids, then RecordNotFound will be raised.
|
@@ -306,15 +349,20 @@ module ActiveRecord #:nodoc:
|
|
306
349
|
# conditions or merely an order. If no record can matched, nil is returned.
|
307
350
|
# * Find all: This will return all the records matched by the options used. If no records are found, an empty array is returned.
|
308
351
|
#
|
309
|
-
# All approaches
|
352
|
+
# All approaches accept an option hash as their last parameter. The options are:
|
310
353
|
#
|
311
354
|
# * <tt>:conditions</tt>: An SQL fragment like "administrator = 1" or [ "user_name = ?", username ]. See conditions in the intro.
|
312
355
|
# * <tt>:order</tt>: An SQL fragment like "created_at DESC, name".
|
313
356
|
# * <tt>:limit</tt>: An integer determining the limit on the number of rows that should be returned.
|
314
357
|
# * <tt>:offset</tt>: An integer determining the offset from where the rows should be fetched. So at 5, it would skip the first 4 rows.
|
315
358
|
# * <tt>:joins</tt>: An SQL fragment for additional joins like "LEFT JOIN comments ON comments.post_id = id". (Rarely needed).
|
359
|
+
# The records will be returned read-only since they will have attributes that do not correspond to the table's columns.
|
360
|
+
# Use <tt>find_by_sql</tt> to circumvent this limitation.
|
316
361
|
# * <tt>:include</tt>: Names associations that should be loaded alongside using LEFT OUTER JOINs. The symbols named refer
|
317
362
|
# to already defined associations. See eager loading under Associations.
|
363
|
+
# * <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
|
364
|
+
# include the joined columns.
|
365
|
+
# * <tt>:readonly</tt>: Mark the returned records read-only so they cannot be saved or updated.
|
318
366
|
#
|
319
367
|
# Examples for find by id:
|
320
368
|
# Person.find(1) # returns the object for ID = 1
|
@@ -336,15 +384,21 @@ module ActiveRecord #:nodoc:
|
|
336
384
|
def find(*args)
|
337
385
|
options = extract_options_from_args!(args)
|
338
386
|
|
387
|
+
# :joins implies :readonly => true
|
388
|
+
options[:readonly] = true if options[:joins]
|
389
|
+
|
339
390
|
case args.first
|
340
391
|
when :first
|
341
392
|
find(:all, options.merge(options[:include] ? { } : { :limit => 1 })).first
|
342
393
|
when :all
|
343
|
-
options[:include] ? find_with_associations(options) : find_by_sql(construct_finder_sql(options))
|
394
|
+
records = options[:include] ? find_with_associations(options) : find_by_sql(construct_finder_sql(options))
|
395
|
+
records.each { |record| record.readonly! } if options[:readonly]
|
396
|
+
records
|
344
397
|
else
|
345
398
|
return args.first if args.first.kind_of?(Array) && args.first.empty?
|
346
399
|
expects_array = args.first.kind_of?(Array)
|
347
|
-
|
400
|
+
|
401
|
+
conditions = " AND (#{sanitize_sql(options[:conditions])})" if options[:conditions]
|
348
402
|
|
349
403
|
ids = args.flatten.compact.uniq
|
350
404
|
case ids.size
|
@@ -384,7 +438,7 @@ module ActiveRecord #:nodoc:
|
|
384
438
|
end
|
385
439
|
|
386
440
|
# Creates an object, instantly saves it as a record (if the validation permits it), and returns it. If the save
|
387
|
-
#
|
441
|
+
# fails under validations, the unsaved object is still returned.
|
388
442
|
def create(attributes = nil)
|
389
443
|
if attributes.is_a?(Array)
|
390
444
|
attributes.collect { |attr| create(attr) }
|
@@ -396,7 +450,7 @@ module ActiveRecord #:nodoc:
|
|
396
450
|
end
|
397
451
|
|
398
452
|
# Finds the record from the passed +id+, instantly saves it with the passed +attributes+ (if the validation permits it),
|
399
|
-
# and returns it. If the save
|
453
|
+
# and returns it. If the save fails under validations, the unsaved object is still returned.
|
400
454
|
def update(id, attributes)
|
401
455
|
if id.is_a?(Array)
|
402
456
|
idx = -1
|
@@ -420,7 +474,7 @@ module ActiveRecord #:nodoc:
|
|
420
474
|
id.is_a?(Array) ? id.each { |id| destroy(id) } : find(id).destroy
|
421
475
|
end
|
422
476
|
|
423
|
-
# Updates all records with the SET-part of an SQL update statement in +updates+ and returns an integer with the number of rows
|
477
|
+
# Updates all records with the SET-part of an SQL update statement in +updates+ and returns an integer with the number of rows updated.
|
424
478
|
# A subset of the records can be selected by specifying +conditions+. Example:
|
425
479
|
# Billing.update_all "category = 'authorized', approved = 1", "author = 'David'"
|
426
480
|
def update_all(updates, conditions = nil)
|
@@ -429,14 +483,14 @@ module ActiveRecord #:nodoc:
|
|
429
483
|
connection.update(sql, "#{name} Update")
|
430
484
|
end
|
431
485
|
|
432
|
-
# Destroys the objects for all the records that
|
486
|
+
# Destroys the objects for all the records that match the +condition+ by instantiating each object and calling
|
433
487
|
# the destroy method. Example:
|
434
488
|
# Person.destroy_all "last_login < '2004-04-04'"
|
435
489
|
def destroy_all(conditions = nil)
|
436
490
|
find(:all, :conditions => conditions).each { |object| object.destroy }
|
437
491
|
end
|
438
492
|
|
439
|
-
# Deletes all the records that
|
493
|
+
# Deletes all the records that match the +condition+ without instantiating the objects first (and hence not
|
440
494
|
# calling the destroy method). Example:
|
441
495
|
# Post.destroy_all "person_id = 5 AND (category = 'Something' OR category = 'Else')"
|
442
496
|
def delete_all(conditions = nil)
|
@@ -445,7 +499,7 @@ module ActiveRecord #:nodoc:
|
|
445
499
|
connection.delete(sql, "#{name} Delete all")
|
446
500
|
end
|
447
501
|
|
448
|
-
# Returns the number of records that
|
502
|
+
# Returns the number of records that meet the +conditions+. Zero is returned if no records match. Example:
|
449
503
|
# Product.count "sales > 1"
|
450
504
|
def count(conditions = nil, joins = nil)
|
451
505
|
sql = "SELECT COUNT(*) FROM #{table_name} "
|
@@ -455,21 +509,15 @@ module ActiveRecord #:nodoc:
|
|
455
509
|
end
|
456
510
|
|
457
511
|
# Returns the result of an SQL statement that should only include a COUNT(*) in the SELECT part.
|
458
|
-
# Product.
|
512
|
+
# Product.count_by_sql "SELECT COUNT(*) FROM sales s, customers c WHERE s.customer_id = c.id"
|
459
513
|
def count_by_sql(sql)
|
460
514
|
sql = sanitize_conditions(sql)
|
461
|
-
|
462
|
-
|
463
|
-
if !rows.nil? and count = rows.values.first
|
464
|
-
count.to_i
|
465
|
-
else
|
466
|
-
0
|
467
|
-
end
|
515
|
+
connection.select_value(sql, "#{name} Count").to_i
|
468
516
|
end
|
469
517
|
|
470
518
|
# Increments the specified counter by one. So <tt>DiscussionBoard.increment_counter("post_count",
|
471
519
|
# discussion_board_id)</tt> would increment the "post_count" counter on the board responding to discussion_board_id.
|
472
|
-
# This is used for caching aggregate values, so that they
|
520
|
+
# This is used for caching aggregate values, so that they don't need to be computed every time. Especially important
|
473
521
|
# for looping over a collection where each element require a number of aggregate values. Like the DiscussionBoard
|
474
522
|
# that needs to list both the number of posts and comments.
|
475
523
|
def increment_counter(counter_name, id)
|
@@ -483,7 +531,7 @@ module ActiveRecord #:nodoc:
|
|
483
531
|
|
484
532
|
# Attributes named in this macro are protected from mass-assignment, such as <tt>new(attributes)</tt> and
|
485
533
|
# <tt>attributes=(attributes)</tt>. Their assignment will simply be ignored. Instead, you can use the direct writer
|
486
|
-
# methods to do assignment. This is meant to protect sensitive attributes
|
534
|
+
# methods to do assignment. This is meant to protect sensitive attributes from being overwritten by URL/form hackers. Example:
|
487
535
|
#
|
488
536
|
# class Customer < ActiveRecord::Base
|
489
537
|
# attr_protected :credit_rating
|
@@ -497,7 +545,7 @@ module ActiveRecord #:nodoc:
|
|
497
545
|
# customer.credit_rating = "Average"
|
498
546
|
# customer.credit_rating # => "Average"
|
499
547
|
def attr_protected(*attributes)
|
500
|
-
write_inheritable_array("attr_protected", attributes)
|
548
|
+
write_inheritable_array("attr_protected", attributes - (protected_attributes || []))
|
501
549
|
end
|
502
550
|
|
503
551
|
# Returns an array of all the attributes that have been protected from mass-assignment.
|
@@ -505,12 +553,12 @@ module ActiveRecord #:nodoc:
|
|
505
553
|
read_inheritable_attribute("attr_protected")
|
506
554
|
end
|
507
555
|
|
508
|
-
# If this macro is used, only those
|
556
|
+
# If this macro is used, only those attributes named in it will be accessible for mass-assignment, such as
|
509
557
|
# <tt>new(attributes)</tt> and <tt>attributes=(attributes)</tt>. This is the more conservative choice for mass-assignment
|
510
558
|
# protection. If you'd rather start from an all-open default and restrict attributes as needed, have a look at
|
511
559
|
# attr_protected.
|
512
560
|
def attr_accessible(*attributes)
|
513
|
-
write_inheritable_array("attr_accessible", attributes)
|
561
|
+
write_inheritable_array("attr_accessible", attributes - (accessible_attributes || []))
|
514
562
|
end
|
515
563
|
|
516
564
|
# Returns an array of all the attributes that have been made accessible to mass-assignment.
|
@@ -545,20 +593,31 @@ module ActiveRecord #:nodoc:
|
|
545
593
|
# set_table_name "mice"
|
546
594
|
# end
|
547
595
|
def table_name
|
548
|
-
|
596
|
+
reset_table_name
|
597
|
+
end
|
598
|
+
|
599
|
+
def reset_table_name
|
600
|
+
name = "#{table_name_prefix}#{undecorated_table_name(class_name_of_active_record_descendant(self))}#{table_name_suffix}"
|
601
|
+
set_table_name name
|
602
|
+
name
|
549
603
|
end
|
550
604
|
|
551
605
|
# Defines the primary key field -- can be overridden in subclasses. Overwriting will negate any effect of the
|
552
606
|
# primary_key_prefix_type setting, though.
|
553
607
|
def primary_key
|
608
|
+
reset_primary_key
|
609
|
+
end
|
610
|
+
|
611
|
+
def reset_primary_key
|
612
|
+
key = 'id'
|
554
613
|
case primary_key_prefix_type
|
555
614
|
when :table_name
|
556
|
-
Inflector.foreign_key(class_name_of_active_record_descendant(self), false)
|
615
|
+
key = Inflector.foreign_key(class_name_of_active_record_descendant(self), false)
|
557
616
|
when :table_name_with_underscore
|
558
|
-
Inflector.foreign_key(class_name_of_active_record_descendant(self))
|
559
|
-
else
|
560
|
-
"id"
|
617
|
+
key = Inflector.foreign_key(class_name_of_active_record_descendant(self))
|
561
618
|
end
|
619
|
+
set_primary_key(key)
|
620
|
+
key
|
562
621
|
end
|
563
622
|
|
564
623
|
# Defines the column name for use with single table inheritance -- can be overridden in subclasses.
|
@@ -566,6 +625,11 @@ module ActiveRecord #:nodoc:
|
|
566
625
|
"type"
|
567
626
|
end
|
568
627
|
|
628
|
+
# Default sequence_name. Use set_sequence_name to override.
|
629
|
+
def sequence_name
|
630
|
+
connection.default_sequence_name(table_name, primary_key)
|
631
|
+
end
|
632
|
+
|
569
633
|
# Sets the table name to use to the given value, or (if the value
|
570
634
|
# is nil or false) to the value returned by the given block.
|
571
635
|
#
|
@@ -609,6 +673,25 @@ module ActiveRecord #:nodoc:
|
|
609
673
|
end
|
610
674
|
alias :inheritance_column= :set_inheritance_column
|
611
675
|
|
676
|
+
# Sets the name of the sequence to use when generating ids to the given
|
677
|
+
# value, or (if the value is nil or false) to the value returned by the
|
678
|
+
# given block. This is required for Oracle and is useful for any
|
679
|
+
# database which relies on sequences for primary key generation.
|
680
|
+
#
|
681
|
+
# Setting the sequence name when using other dbs will have no effect.
|
682
|
+
# If a sequence name is not explicitly set when using Oracle, it will
|
683
|
+
# default to the commonly used pattern of: #{table_name}_seq
|
684
|
+
#
|
685
|
+
# Example:
|
686
|
+
#
|
687
|
+
# class Project < ActiveRecord::Base
|
688
|
+
# set_sequence_name "projectseq" # default would have been "project_seq"
|
689
|
+
# end
|
690
|
+
def set_sequence_name( value=nil, &block )
|
691
|
+
define_attr_method :sequence_name, value, &block
|
692
|
+
end
|
693
|
+
alias :sequence_name= :set_sequence_name
|
694
|
+
|
612
695
|
# Turns the +table_name+ back into a class name following the reverse rules of +table_name+.
|
613
696
|
def class_name(table_name = table_name) # :nodoc:
|
614
697
|
# remove any prefix and/or suffix from the table name
|
@@ -619,7 +702,11 @@ module ActiveRecord #:nodoc:
|
|
619
702
|
|
620
703
|
# Returns an array of column objects for the table associated with this class.
|
621
704
|
def columns
|
622
|
-
@columns
|
705
|
+
unless @columns
|
706
|
+
@columns = connection.columns(table_name, "#{name} Columns")
|
707
|
+
@columns.each {|column| column.primary = column.name == primary_key}
|
708
|
+
end
|
709
|
+
@columns
|
623
710
|
end
|
624
711
|
|
625
712
|
# Returns an array of column objects for the table associated with this class.
|
@@ -631,10 +718,10 @@ module ActiveRecord #:nodoc:
|
|
631
718
|
@column_names ||= columns.map { |column| column.name }
|
632
719
|
end
|
633
720
|
|
634
|
-
# Returns an array of
|
635
|
-
# and columns used for single table inheritance
|
721
|
+
# Returns an array of column objects where the primary id, all columns ending in "_id" or "_count",
|
722
|
+
# and columns used for single table inheritance have been removed.
|
636
723
|
def content_columns
|
637
|
-
@content_columns ||= columns.reject { |c| c.
|
724
|
+
@content_columns ||= columns.reject { |c| c.primary || c.name =~ /(_id|_count)$/ || c.name == inheritance_column }
|
638
725
|
end
|
639
726
|
|
640
727
|
# Returns a hash of all the methods added to query each of the columns in the table with the name of the method as the key
|
@@ -650,9 +737,16 @@ module ActiveRecord #:nodoc:
|
|
650
737
|
end
|
651
738
|
end
|
652
739
|
|
653
|
-
|
740
|
+
|
741
|
+
# Contains the names of the generated reader methods.
|
742
|
+
def read_methods
|
743
|
+
@read_methods ||= {}
|
744
|
+
end
|
745
|
+
|
746
|
+
# Resets all the cached information about columns, which will cause them to be reloaded on the next request.
|
654
747
|
def reset_column_information
|
655
|
-
|
748
|
+
read_methods.each_key {|name| undef_method(name)}
|
749
|
+
@column_names = @columns = @columns_hash = @content_columns = @dynamic_methods_hash = @read_methods = nil
|
656
750
|
end
|
657
751
|
|
658
752
|
def reset_column_information_and_inheritable_attributes_for_all_subclasses#:nodoc:
|
@@ -679,19 +773,28 @@ module ActiveRecord #:nodoc:
|
|
679
773
|
connection.quote(object)
|
680
774
|
end
|
681
775
|
|
682
|
-
# Log and benchmark multiple statements in a single block.
|
683
|
-
# Usage (hides all the SQL calls for the individual actions and calculates total runtime for them all):
|
776
|
+
# Log and benchmark multiple statements in a single block. Example:
|
684
777
|
#
|
685
778
|
# Project.benchmark("Creating project") do
|
686
779
|
# project = Project.create("name" => "stuff")
|
687
780
|
# project.create_manager("name" => "David")
|
688
781
|
# project.milestones << Milestone.find(:all)
|
689
782
|
# end
|
690
|
-
|
691
|
-
|
692
|
-
|
693
|
-
|
694
|
-
|
783
|
+
#
|
784
|
+
# The benchmark is only recorded if the current level of the logger matches the <tt>log_level</tt>, which makes it
|
785
|
+
# easy to include benchmarking statements in production software that will remain inexpensive because the benchmark
|
786
|
+
# will only be conducted if the log level is low enough.
|
787
|
+
#
|
788
|
+
# The logging of the multiple statements is turned off unless <tt>use_silence</tt> is set to false.
|
789
|
+
def benchmark(title, log_level = Logger::DEBUG, use_silence = true)
|
790
|
+
if logger && logger.level == log_level
|
791
|
+
result = nil
|
792
|
+
seconds = Benchmark.realtime { result = use_silence ? silence { yield } : yield }
|
793
|
+
logger.add(log_level, "#{title} (#{'%.5f' % seconds})")
|
794
|
+
result
|
795
|
+
else
|
796
|
+
yield
|
797
|
+
end
|
695
798
|
end
|
696
799
|
|
697
800
|
# Silences the logger for the duration of the block.
|
@@ -701,48 +804,77 @@ module ActiveRecord #:nodoc:
|
|
701
804
|
ensure
|
702
805
|
logger.level = old_logger_level if logger
|
703
806
|
end
|
807
|
+
|
808
|
+
# Add constraints to all queries to the same model in the given block.
|
809
|
+
# Currently supported constraints are <tt>:conditions</tt> and <tt>:joins</tt>
|
810
|
+
#
|
811
|
+
# Article.constrain(:conditions => "blog_id = 1") do
|
812
|
+
# Article.find(1) # => SELECT * from articles WHERE blog_id = 1 AND id = 1
|
813
|
+
# end
|
814
|
+
def constrain(options = {}, &block)
|
815
|
+
begin
|
816
|
+
self.scope_constraints = options
|
817
|
+
block.call if block_given?
|
818
|
+
ensure
|
819
|
+
self.scope_constraints = nil
|
820
|
+
end
|
821
|
+
end
|
704
822
|
|
705
823
|
# Overwrite the default class equality method to provide support for association proxies.
|
706
824
|
def ===(object)
|
707
825
|
object.is_a?(self)
|
826
|
+
end
|
827
|
+
|
828
|
+
# Deprecated
|
829
|
+
def threaded_connections
|
830
|
+
allow_concurrency
|
708
831
|
end
|
709
832
|
|
833
|
+
# Deprecated
|
834
|
+
def threaded_connections=(value)
|
835
|
+
self.allow_concurrency = value
|
836
|
+
end
|
837
|
+
|
838
|
+
|
710
839
|
private
|
711
840
|
# Finder methods must instantiate through this method to work with the single-table inheritance model
|
712
841
|
# that makes it possible to create objects of different types from the same table.
|
713
842
|
def instantiate(record)
|
714
|
-
|
715
|
-
|
716
|
-
|
717
|
-
|
718
|
-
|
719
|
-
|
720
|
-
|
721
|
-
|
722
|
-
|
723
|
-
|
724
|
-
|
725
|
-
|
726
|
-
|
727
|
-
|
843
|
+
object =
|
844
|
+
if subclass_name = record[inheritance_column]
|
845
|
+
if subclass_name.empty?
|
846
|
+
allocate
|
847
|
+
else
|
848
|
+
require_association_class(subclass_name)
|
849
|
+
begin
|
850
|
+
compute_type(subclass_name).allocate
|
851
|
+
rescue NameError
|
852
|
+
raise SubclassNotFound,
|
853
|
+
"The single-table inheritance mechanism failed to locate the subclass: '#{record[inheritance_column]}'. " +
|
854
|
+
"This error is raised because the column '#{inheritance_column}' is reserved for storing the class in case of inheritance. " +
|
855
|
+
"Please rename this column if you didn't intend it to be used for storing the inheritance class " +
|
856
|
+
"or overwrite #{self.to_s}.inheritance_column to use another column for that information."
|
857
|
+
end
|
858
|
+
end
|
859
|
+
else
|
860
|
+
allocate
|
728
861
|
end
|
729
|
-
end
|
730
862
|
|
731
863
|
object.instance_variable_set("@attributes", record)
|
732
864
|
object
|
733
865
|
end
|
734
866
|
|
735
867
|
# Returns the name of the type of the record using the current module as a prefix. So descendents of
|
736
|
-
# MyApp::Business::Account would
|
868
|
+
# MyApp::Business::Account would appear as "MyApp::Business::AccountSubclass".
|
737
869
|
def type_name_with_module(type_name)
|
738
870
|
self.name =~ /::/ ? self.name.scan(/(.*)::/).first.first + "::" + type_name : type_name
|
739
871
|
end
|
740
872
|
|
741
873
|
def construct_finder_sql(options)
|
742
|
-
sql = "SELECT * FROM #{table_name} "
|
743
|
-
sql
|
874
|
+
sql = "SELECT #{options[:select] || '*'} FROM #{table_name} "
|
875
|
+
add_joins!(sql, options)
|
744
876
|
add_conditions!(sql, options[:conditions])
|
745
|
-
sql << "ORDER BY #{options[:order]} " if options[:order]
|
877
|
+
sql << " ORDER BY #{options[:order]} " if options[:order]
|
746
878
|
add_limit!(sql, options)
|
747
879
|
sql
|
748
880
|
end
|
@@ -750,11 +882,19 @@ module ActiveRecord #:nodoc:
|
|
750
882
|
def add_limit!(sql, options)
|
751
883
|
connection.add_limit_offset!(sql, options)
|
752
884
|
end
|
753
|
-
|
754
|
-
|
755
|
-
|
756
|
-
sql << "
|
757
|
-
|
885
|
+
|
886
|
+
def add_joins!(sql, options)
|
887
|
+
join = scope_constraints[:joins] || options[:joins]
|
888
|
+
sql << " #{join} " if join
|
889
|
+
end
|
890
|
+
|
891
|
+
# Adds a sanitized version of +conditions+ to the +sql+ string. Note that the passed-in +sql+ string is changed.
|
892
|
+
def add_conditions!(sql, conditions)
|
893
|
+
condition_segments = [scope_constraints[:conditions]]
|
894
|
+
condition_segments << sanitize_sql(conditions) unless conditions.nil?
|
895
|
+
condition_segments << type_condition unless descends_from_active_record?
|
896
|
+
condition_segments.compact!
|
897
|
+
sql << "WHERE (#{condition_segments.join(") AND (")}) " unless condition_segments.empty?
|
758
898
|
end
|
759
899
|
|
760
900
|
def type_condition
|
@@ -787,7 +927,7 @@ module ActiveRecord #:nodoc:
|
|
787
927
|
attributes.each { |attr_name| super unless column_methods_hash.include?(attr_name.to_sym) }
|
788
928
|
|
789
929
|
attr_index = -1
|
790
|
-
conditions = attributes.collect { |attr_name| attr_index += 1; "#{attr_name} #{attribute_condition(arguments[attr_index])} " }.join(" AND ")
|
930
|
+
conditions = attributes.collect { |attr_name| attr_index += 1; "#{table_name}.#{attr_name} #{attribute_condition(arguments[attr_index])} " }.join(" AND ")
|
791
931
|
|
792
932
|
if arguments[attributes.length].is_a?(Hash)
|
793
933
|
find(finder, { :conditions => [conditions, *arguments[0...attributes.length]] }.update(arguments[attributes.length]))
|
@@ -828,9 +968,14 @@ module ActiveRecord #:nodoc:
|
|
828
968
|
# end
|
829
969
|
def define_attr_method(name, value=nil, &block)
|
830
970
|
sing = class << self; self; end
|
831
|
-
|
832
|
-
|
833
|
-
|
971
|
+
sing.send :alias_method, "original_#{name}", name
|
972
|
+
if value
|
973
|
+
# use eval instead of a block to work around a memory leak in dev
|
974
|
+
# mode in fcgi
|
975
|
+
sing.class_eval "def #{name}; #{value.to_s.inspect}; end"
|
976
|
+
else
|
977
|
+
sing.send :define_method, name, &block
|
978
|
+
end
|
834
979
|
end
|
835
980
|
|
836
981
|
protected
|
@@ -838,9 +983,31 @@ module ActiveRecord #:nodoc:
|
|
838
983
|
@@subclasses[self] ||= []
|
839
984
|
@@subclasses[self] + extra = @@subclasses[self].inject([]) {|list, subclass| list + subclass.subclasses }
|
840
985
|
end
|
986
|
+
|
987
|
+
def scope_constraints
|
988
|
+
if allow_concurrency
|
989
|
+
Thread.current[:constraints] ||= {}
|
990
|
+
Thread.current[:constraints][self] ||= {}
|
991
|
+
else
|
992
|
+
@scope_constraints ||= {}
|
993
|
+
end
|
994
|
+
end
|
995
|
+
# backwards compatibility
|
996
|
+
alias_method :scope_constrains, :scope_constraints
|
841
997
|
|
998
|
+
def scope_constraints=(value)
|
999
|
+
if allow_concurrency
|
1000
|
+
Thread.current[:constraints] ||= {}
|
1001
|
+
Thread.current[:constraints][self] = value
|
1002
|
+
else
|
1003
|
+
@scope_constraints = value
|
1004
|
+
end
|
1005
|
+
end
|
1006
|
+
# backwards compatibility
|
1007
|
+
alias_method :scope_constrains=, :scope_constraints=
|
1008
|
+
|
842
1009
|
# Returns the class type of the record using the current module as a prefix. So descendents of
|
843
|
-
# MyApp::Business::Account would
|
1010
|
+
# MyApp::Business::Account would appear as MyApp::Business::AccountSubclass.
|
844
1011
|
def compute_type(type_name)
|
845
1012
|
type_name_with_module(type_name).split("::").inject(Object) do |final_type, part|
|
846
1013
|
final_type.const_get(part)
|
@@ -909,7 +1076,13 @@ module ActiveRecord #:nodoc:
|
|
909
1076
|
end
|
910
1077
|
|
911
1078
|
def extract_options_from_args!(args)
|
912
|
-
|
1079
|
+
options = args.last.is_a?(Hash) ? args.pop : {}
|
1080
|
+
validate_find_options(options)
|
1081
|
+
options
|
1082
|
+
end
|
1083
|
+
|
1084
|
+
def validate_find_options(options)
|
1085
|
+
options.assert_valid_keys [:conditions, :include, :joins, :limit, :offset, :order, :select, :readonly]
|
913
1086
|
end
|
914
1087
|
|
915
1088
|
def encode_quoted_value(value)
|
@@ -935,7 +1108,10 @@ module ActiveRecord #:nodoc:
|
|
935
1108
|
# Every Active Record class must use "id" as their primary ID. This getter overwrites the native
|
936
1109
|
# id method, which isn't being used in this context.
|
937
1110
|
def id
|
938
|
-
|
1111
|
+
attr_name = self.class.primary_key
|
1112
|
+
column = column_for_attribute(attr_name)
|
1113
|
+
define_read_method(:id, attr_name, column) if self.class.generate_read_methods
|
1114
|
+
(value = @attributes[attr_name]) && column.type_cast(value)
|
939
1115
|
end
|
940
1116
|
|
941
1117
|
# Enables Active Record objects to be used as URL parameters in Action Pack automatically.
|
@@ -962,6 +1138,7 @@ module ActiveRecord #:nodoc:
|
|
962
1138
|
# * No record exists: Creates a new record with values matching those of the object attributes.
|
963
1139
|
# * A record does exist: Updates the record with values matching those of the object attributes.
|
964
1140
|
def save
|
1141
|
+
raise ActiveRecord::ReadOnlyRecord if readonly?
|
965
1142
|
create_or_update
|
966
1143
|
end
|
967
1144
|
|
@@ -991,11 +1168,11 @@ module ActiveRecord #:nodoc:
|
|
991
1168
|
# Note: This method is overwritten by the Validation module that'll make sure that updates made with this method
|
992
1169
|
# doesn't get subjected to validation checks. Hence, attributes can be updated even if the full object isn't valid.
|
993
1170
|
def update_attribute(name, value)
|
994
|
-
|
1171
|
+
send(name.to_s + '=', value)
|
995
1172
|
save
|
996
1173
|
end
|
997
1174
|
|
998
|
-
# Updates all the attributes
|
1175
|
+
# Updates all the attributes from the passed-in Hash and saves the record. If the object is invalid, the saving will
|
999
1176
|
# fail and false will be returned.
|
1000
1177
|
def update_attributes(attributes)
|
1001
1178
|
self.attributes = attributes
|
@@ -1028,7 +1205,7 @@ module ActiveRecord #:nodoc:
|
|
1028
1205
|
|
1029
1206
|
# Turns an +attribute+ that's currently true into false and vice versa. Returns self.
|
1030
1207
|
def toggle(attribute)
|
1031
|
-
self[attribute] =
|
1208
|
+
self[attribute] = !send("#{attribute}?")
|
1032
1209
|
self
|
1033
1210
|
end
|
1034
1211
|
|
@@ -1044,17 +1221,17 @@ module ActiveRecord #:nodoc:
|
|
1044
1221
|
self
|
1045
1222
|
end
|
1046
1223
|
|
1047
|
-
# Returns the value of attribute identified by <tt>attr_name</tt> after it has been
|
1224
|
+
# Returns the value of the attribute identified by <tt>attr_name</tt> after it has been typecast (for example,
|
1048
1225
|
# "2004-12-12" in a data column is cast to a date object, like Date.new(2004, 12, 12)).
|
1049
1226
|
# (Alias for the protected read_attribute method).
|
1050
1227
|
def [](attr_name)
|
1051
|
-
read_attribute(attr_name
|
1228
|
+
read_attribute(attr_name)
|
1052
1229
|
end
|
1053
1230
|
|
1054
1231
|
# Updates the attribute identified by <tt>attr_name</tt> with the specified +value+.
|
1055
1232
|
# (Alias for the protected write_attribute method).
|
1056
1233
|
def []=(attr_name, value)
|
1057
|
-
write_attribute(attr_name
|
1234
|
+
write_attribute(attr_name, value)
|
1058
1235
|
end
|
1059
1236
|
|
1060
1237
|
# Allows you to set all the attributes at once by passing in a hash with keys
|
@@ -1084,7 +1261,7 @@ module ActiveRecord #:nodoc:
|
|
1084
1261
|
end
|
1085
1262
|
|
1086
1263
|
# Returns true if the specified +attribute+ has been set by the user or by a database load and is neither
|
1087
|
-
# nil nor empty? (the latter only applies to objects that
|
1264
|
+
# nil nor empty? (the latter only applies to objects that respond to empty?, most notably Strings).
|
1088
1265
|
def attribute_present?(attribute)
|
1089
1266
|
value = read_attribute(attribute)
|
1090
1267
|
!value.blank? or value == 0
|
@@ -1102,7 +1279,10 @@ module ActiveRecord #:nodoc:
|
|
1102
1279
|
|
1103
1280
|
# Returns true if the +comparison_object+ is the same object, or is of the same type and has the same id.
|
1104
1281
|
def ==(comparison_object)
|
1105
|
-
comparison_object.equal?(self)
|
1282
|
+
comparison_object.equal?(self) ||
|
1283
|
+
(comparison_object.instance_of?(self.class) &&
|
1284
|
+
comparison_object.id == id &&
|
1285
|
+
!comparison_object.new_record?)
|
1106
1286
|
end
|
1107
1287
|
|
1108
1288
|
# Delegates to ==
|
@@ -1127,20 +1307,27 @@ module ActiveRecord #:nodoc:
|
|
1127
1307
|
|
1128
1308
|
# Just freeze the attributes hash, such that associations are still accessible even on destroyed records.
|
1129
1309
|
def freeze
|
1130
|
-
@attributes.freeze
|
1310
|
+
@attributes.freeze; self
|
1131
1311
|
end
|
1132
1312
|
|
1133
1313
|
def frozen?
|
1134
1314
|
@attributes.frozen?
|
1135
1315
|
end
|
1136
1316
|
|
1317
|
+
def readonly?
|
1318
|
+
@readonly == true
|
1319
|
+
end
|
1320
|
+
|
1321
|
+
def readonly!
|
1322
|
+
@readonly = true
|
1323
|
+
end
|
1324
|
+
|
1137
1325
|
private
|
1138
1326
|
def create_or_update
|
1139
1327
|
if new_record? then create else update end
|
1140
|
-
true
|
1141
1328
|
end
|
1142
1329
|
|
1143
|
-
# Updates the associated record with values matching those of the
|
1330
|
+
# Updates the associated record with values matching those of the instance attributes.
|
1144
1331
|
def update
|
1145
1332
|
connection.update(
|
1146
1333
|
"UPDATE #{self.class.table_name} " +
|
@@ -1150,20 +1337,20 @@ module ActiveRecord #:nodoc:
|
|
1150
1337
|
)
|
1151
1338
|
end
|
1152
1339
|
|
1153
|
-
# Creates a new record with values matching those of the
|
1340
|
+
# Creates a new record with values matching those of the instance attributes.
|
1154
1341
|
def create
|
1155
1342
|
self.id = connection.insert(
|
1156
1343
|
"INSERT INTO #{self.class.table_name} " +
|
1157
1344
|
"(#{quoted_column_names.join(', ')}) " +
|
1158
1345
|
"VALUES(#{attributes_with_quotes.values.join(', ')})",
|
1159
1346
|
"#{self.class.name} Create",
|
1160
|
-
self.class.primary_key, self.id
|
1347
|
+
self.class.primary_key, self.id, self.class.sequence_name
|
1161
1348
|
)
|
1162
1349
|
|
1163
1350
|
@new_record = false
|
1164
1351
|
end
|
1165
1352
|
|
1166
|
-
# Sets the attribute used for single table inheritance to this class name if this is not the ActiveRecord
|
1353
|
+
# Sets the attribute used for single table inheritance to this class name if this is not the ActiveRecord descendent.
|
1167
1354
|
# Considering the hierarchy Reply < Message < ActiveRecord, this makes it possible to do Reply.new without having to
|
1168
1355
|
# set Reply[Reply.inheritance_column] = "Reply" yourself. No such attribute would be set for objects of the
|
1169
1356
|
# Message class in that example.
|
@@ -1184,7 +1371,10 @@ module ActiveRecord #:nodoc:
|
|
1184
1371
|
def method_missing(method_id, *args, &block)
|
1185
1372
|
method_name = method_id.to_s
|
1186
1373
|
if @attributes.include?(method_name)
|
1374
|
+
define_read_methods if self.class.read_methods.empty? && self.class.generate_read_methods
|
1187
1375
|
read_attribute(method_name)
|
1376
|
+
elsif self.class.primary_key.to_s == method_name
|
1377
|
+
id
|
1188
1378
|
elsif md = /(=|\?|_before_type_cast)$/.match(method_name)
|
1189
1379
|
attribute_name, method_type = md.pre_match, md.to_s
|
1190
1380
|
if @attributes.include?(attribute_name)
|
@@ -1204,9 +1394,10 @@ module ActiveRecord #:nodoc:
|
|
1204
1394
|
end
|
1205
1395
|
end
|
1206
1396
|
|
1207
|
-
# Returns the value of attribute identified by <tt>attr_name</tt> after it has been
|
1397
|
+
# Returns the value of the attribute identified by <tt>attr_name</tt> after it has been typecast (for example,
|
1208
1398
|
# "2004-12-12" in a data column is cast to a date object, like Date.new(2004, 12, 12)).
|
1209
1399
|
def read_attribute(attr_name)
|
1400
|
+
attr_name = attr_name.to_s
|
1210
1401
|
if !(value = @attributes[attr_name]).nil?
|
1211
1402
|
if column = column_for_attribute(attr_name)
|
1212
1403
|
if unserializable_attribute?(attr_name, column)
|
@@ -1226,11 +1417,36 @@ module ActiveRecord #:nodoc:
|
|
1226
1417
|
@attributes[attr_name]
|
1227
1418
|
end
|
1228
1419
|
|
1420
|
+
# Called on first read access to any given column and generates reader
|
1421
|
+
# methods for all columns in the columns_hash if
|
1422
|
+
# ActiveRecord::Base.generate_read_methods is set to true.
|
1423
|
+
def define_read_methods
|
1424
|
+
self.class.columns_hash.each do |name, column|
|
1425
|
+
unless column.primary || self.class.serialized_attributes[name] || respond_to_without_attributes?(name)
|
1426
|
+
define_read_method(name.to_sym, name, column)
|
1427
|
+
end
|
1428
|
+
end
|
1429
|
+
end
|
1430
|
+
|
1431
|
+
# Define a column type specific reader method.
|
1432
|
+
def define_read_method(symbol, attr_name, column)
|
1433
|
+
cast_code = column.type_cast_code('v')
|
1434
|
+
access_code = cast_code ? "(v=@attributes['#{attr_name}']) && #{cast_code}" : "@attributes['#{attr_name}']"
|
1435
|
+
body = access_code
|
1436
|
+
|
1437
|
+
# The following 3 lines behave exactly like method_missing if the
|
1438
|
+
# attribute isn't present.
|
1439
|
+
unless symbol == :id
|
1440
|
+
body = body.insert(0, "raise NoMethodError, 'missing attribute: #{attr_name}', caller unless @attributes.has_key?('#{attr_name}'); ")
|
1441
|
+
end
|
1442
|
+
self.class.class_eval("def #{symbol}; #{body} end")
|
1443
|
+
|
1444
|
+
self.class.read_methods[attr_name] = true unless symbol == :id
|
1445
|
+
end
|
1446
|
+
|
1229
1447
|
# Returns true if the attribute is of a text column and marked for serialization.
|
1230
1448
|
def unserializable_attribute?(attr_name, column)
|
1231
|
-
|
1232
|
-
[:text, :string].include?(column.send(:type)) && value.is_a?(String) && self.class.serialized_attributes[attr_name]
|
1233
|
-
end
|
1449
|
+
column.text? && self.class.serialized_attributes[attr_name]
|
1234
1450
|
end
|
1235
1451
|
|
1236
1452
|
# Returns the unserialized object of the attribute.
|
@@ -1248,12 +1464,21 @@ module ActiveRecord #:nodoc:
|
|
1248
1464
|
# Updates the attribute identified by <tt>attr_name</tt> with the specified +value+. Empty strings for fixnum and float
|
1249
1465
|
# columns are turned into nil.
|
1250
1466
|
def write_attribute(attr_name, value)
|
1251
|
-
|
1467
|
+
attr_name = attr_name.to_s
|
1468
|
+
if (column = column_for_attribute(attr_name)) && column.number?
|
1469
|
+
@attributes[attr_name] = convert_number_column_value(value)
|
1470
|
+
else
|
1471
|
+
@attributes[attr_name] = value
|
1472
|
+
end
|
1252
1473
|
end
|
1253
1474
|
|
1254
|
-
def
|
1255
|
-
|
1256
|
-
|
1475
|
+
def convert_number_column_value(value)
|
1476
|
+
case value
|
1477
|
+
when FalseClass: 0
|
1478
|
+
when TrueClass: 1
|
1479
|
+
when '': nil
|
1480
|
+
else value
|
1481
|
+
end
|
1257
1482
|
end
|
1258
1483
|
|
1259
1484
|
def query_attribute(attr_name)
|
@@ -1289,7 +1514,9 @@ module ActiveRecord #:nodoc:
|
|
1289
1514
|
|
1290
1515
|
# The primary key and inheritance column can never be set by mass-assignment for security reasons.
|
1291
1516
|
def attributes_protected_by_default
|
1292
|
-
[ self.class.primary_key, self.class.inheritance_column ]
|
1517
|
+
default = [ self.class.primary_key, self.class.inheritance_column ]
|
1518
|
+
default << 'id' unless self.class.primary_key.eql? 'id'
|
1519
|
+
default
|
1293
1520
|
end
|
1294
1521
|
|
1295
1522
|
# Returns copy of the attributes hash where all the values have been safely quoted for use in
|
@@ -1297,7 +1524,7 @@ module ActiveRecord #:nodoc:
|
|
1297
1524
|
def attributes_with_quotes(include_primary_key = true)
|
1298
1525
|
attributes.inject({}) do |quoted, (name, value)|
|
1299
1526
|
if column = column_for_attribute(name)
|
1300
|
-
quoted[name] = quote(value, column) unless !include_primary_key &&
|
1527
|
+
quoted[name] = quote(value, column) unless !include_primary_key && column.primary
|
1301
1528
|
end
|
1302
1529
|
quoted
|
1303
1530
|
end
|
@@ -1311,7 +1538,7 @@ module ActiveRecord #:nodoc:
|
|
1311
1538
|
# Interpolate custom sql string in instance context.
|
1312
1539
|
# Optional record argument is meant for custom insert_sql.
|
1313
1540
|
def interpolate_sql(sql, record = nil)
|
1314
|
-
instance_eval("
|
1541
|
+
instance_eval("%@#{sql.gsub('@', '\@')}@")
|
1315
1542
|
end
|
1316
1543
|
|
1317
1544
|
# Initializes the attributes array with keys matching the columns from the linked table and
|
@@ -1319,7 +1546,7 @@ module ActiveRecord #:nodoc:
|
|
1319
1546
|
# that a new instance, or one populated from a passed-in Hash, still has all the attributes
|
1320
1547
|
# that instances loaded from the database would.
|
1321
1548
|
def attributes_from_column_definition
|
1322
|
-
|
1549
|
+
self.class.columns.inject({}) do |attributes, column|
|
1323
1550
|
attributes[column.name] = column.default unless column.name == self.class.primary_key
|
1324
1551
|
attributes
|
1325
1552
|
end
|
@@ -1406,20 +1633,7 @@ module ActiveRecord #:nodoc:
|
|
1406
1633
|
|
1407
1634
|
def object_from_yaml(string)
|
1408
1635
|
return string unless string.is_a?(String)
|
1409
|
-
|
1410
|
-
begin
|
1411
|
-
YAML::load(string)
|
1412
|
-
rescue Object
|
1413
|
-
# Apparently wasn't YAML anyway
|
1414
|
-
string
|
1415
|
-
end
|
1416
|
-
else
|
1417
|
-
string
|
1418
|
-
end
|
1419
|
-
end
|
1420
|
-
|
1421
|
-
def has_yaml_encoding_header?(string)
|
1422
|
-
string[0..3] == "--- "
|
1636
|
+
YAML::load(string) rescue string
|
1423
1637
|
end
|
1424
1638
|
|
1425
1639
|
def clone_attributes(reader_method = :read_attribute, attributes = {})
|