activerecord 1.0.0 → 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of activerecord might be problematic. Click here for more details.
- data/CHANGELOG +102 -1
- data/dev-utils/eval_debugger.rb +12 -7
- data/lib/active_record.rb +2 -0
- data/lib/active_record/aggregations.rb +1 -1
- data/lib/active_record/associations.rb +74 -53
- data/lib/active_record/associations.rb.orig +555 -0
- data/lib/active_record/associations/association_collection.rb +74 -15
- data/lib/active_record/associations/has_and_belongs_to_many_association.rb +86 -25
- data/lib/active_record/associations/has_many_association.rb +48 -50
- data/lib/active_record/base.rb +56 -24
- data/lib/active_record/connection_adapters/abstract_adapter.rb +46 -3
- data/lib/active_record/connection_adapters/mysql_adapter.rb +15 -15
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +128 -135
- data/lib/active_record/connection_adapters/sqlite_adapter.rb +76 -78
- data/lib/active_record/deprecated_associations.rb +1 -1
- data/lib/active_record/fixtures.rb +137 -54
- data/lib/active_record/observer.rb +1 -1
- data/lib/active_record/support/inflector.rb +8 -0
- data/lib/active_record/transactions.rb +31 -14
- data/rakefile +13 -5
- data/test/abstract_unit.rb +7 -1
- data/test/associations_test.rb +99 -27
- data/test/base_test.rb +15 -1
- data/test/connections/native_sqlite/connection.rb +24 -14
- data/test/deprecated_associations_test.rb +3 -4
- data/test/deprecated_associations_test.rb.orig +334 -0
- data/test/fixtures/bad_fixtures/attr_with_numeric_first_char +1 -0
- data/test/fixtures/bad_fixtures/attr_with_spaces +1 -0
- data/test/fixtures/bad_fixtures/blank_line +3 -0
- data/test/fixtures/bad_fixtures/duplicate_attributes +3 -0
- data/test/fixtures/bad_fixtures/missing_value +1 -0
- data/test/fixtures/company_in_module.rb +15 -1
- data/test/fixtures/db_definitions/mysql.sql +2 -1
- data/test/fixtures/db_definitions/postgresql.sql +2 -1
- data/test/fixtures/db_definitions/sqlite.sql +2 -1
- data/test/fixtures/developers_projects/david_action_controller +2 -1
- data/test/fixtures/developers_projects/david_active_record +2 -1
- data/test/fixtures/fixture_database.sqlite +0 -0
- data/test/fixtures/fixture_database_2.sqlite +0 -0
- data/test/fixtures/project.rb +2 -1
- data/test/fixtures/projects/action_controller +1 -1
- data/test/fixtures/topics/second +1 -1
- data/test/fixtures_test.rb +63 -4
- data/test/inflector_test.rb +17 -0
- data/test/modules_test.rb +8 -0
- data/test/transactions_test.rb +16 -4
- metadata +10 -2
@@ -3,7 +3,7 @@ module ActiveRecord
|
|
3
3
|
class AssociationCollection #:nodoc:
|
4
4
|
alias_method :proxy_respond_to?, :respond_to?
|
5
5
|
instance_methods.each { |m| undef_method m unless m =~ /(^__|^nil\?|^proxy_respond_to\?)/ }
|
6
|
-
|
6
|
+
|
7
7
|
def initialize(owner, association_name, association_class_name, association_class_primary_key_name, options)
|
8
8
|
@owner = owner
|
9
9
|
@options = options
|
@@ -13,45 +13,99 @@ module ActiveRecord
|
|
13
13
|
end
|
14
14
|
|
15
15
|
def method_missing(symbol, *args, &block)
|
16
|
-
|
17
|
-
@
|
16
|
+
load_collection
|
17
|
+
@collection.send(symbol, *args, &block)
|
18
18
|
end
|
19
19
|
|
20
20
|
def to_ary
|
21
|
-
|
22
|
-
@
|
21
|
+
load_collection
|
22
|
+
@collection.to_ary
|
23
23
|
end
|
24
|
-
|
24
|
+
|
25
25
|
def respond_to?(symbol)
|
26
26
|
proxy_respond_to?(symbol) || [].respond_to?(symbol)
|
27
27
|
end
|
28
|
+
|
29
|
+
def loaded?
|
30
|
+
!@collection.nil?
|
31
|
+
end
|
28
32
|
|
29
33
|
def reload
|
30
|
-
@
|
34
|
+
@collection = nil
|
31
35
|
end
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
+
|
37
|
+
# Add +records+ to this association. Returns +self+ so method calls may be chained.
|
38
|
+
# Since << flattens its argument list and inserts each record, +push+ and +concat+ behave identically.
|
39
|
+
def <<(*records)
|
40
|
+
flatten_deeper(records).each do |record|
|
41
|
+
raise_on_type_mismatch(record)
|
42
|
+
insert_record(record)
|
43
|
+
@collection << record if loaded?
|
44
|
+
end
|
45
|
+
self
|
46
|
+
end
|
47
|
+
|
48
|
+
alias_method :push, :<<
|
49
|
+
alias_method :concat, :<<
|
50
|
+
|
51
|
+
# Remove +records+ from this association. Does not destroy +records+.
|
52
|
+
def delete(*records)
|
53
|
+
records = flatten_deeper(records)
|
54
|
+
records.each { |record| raise_on_type_mismatch(record) }
|
55
|
+
delete_records(records)
|
56
|
+
records.each { |record| @collection.delete(record) } if loaded?
|
36
57
|
end
|
37
58
|
|
38
59
|
def destroy_all
|
39
|
-
|
40
|
-
@
|
41
|
-
@collection_array = []
|
60
|
+
each { |record| record.destroy }
|
61
|
+
@collection = []
|
42
62
|
end
|
43
63
|
|
44
64
|
def size
|
45
|
-
|
65
|
+
if loaded? then @collection.size else count_records end
|
46
66
|
end
|
47
67
|
|
48
68
|
def empty?
|
49
69
|
size == 0
|
50
70
|
end
|
51
71
|
|
72
|
+
def uniq(collection = self)
|
73
|
+
collection.inject([]) { |uniq_records, record| uniq_records << record unless uniq_records.include?(record); uniq_records }
|
74
|
+
end
|
75
|
+
|
52
76
|
alias_method :length, :size
|
77
|
+
|
78
|
+
protected
|
79
|
+
def loaded?
|
80
|
+
not @collection.nil?
|
81
|
+
end
|
82
|
+
|
83
|
+
def quoted_record_ids(records)
|
84
|
+
records.map { |record| "'#{@association_class.send(:sanitize, record.id)}'" }.join(',')
|
85
|
+
end
|
86
|
+
|
87
|
+
def interpolate_sql_options!(options, *keys)
|
88
|
+
keys.each { |key| options[key] &&= interpolate_sql(options[key]) }
|
89
|
+
end
|
90
|
+
|
91
|
+
def interpolate_sql(sql, record = nil)
|
92
|
+
@owner.send(:interpolate_sql, sql, record)
|
93
|
+
end
|
53
94
|
|
54
95
|
private
|
96
|
+
def load_collection
|
97
|
+
begin
|
98
|
+
@collection = find_all_records unless loaded?
|
99
|
+
rescue ActiveRecord::RecordNotFound
|
100
|
+
@collection = []
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
def raise_on_type_mismatch(record)
|
105
|
+
raise ActiveRecord::AssociationTypeMismatch, "#{@association_class} expected, got #{record.class}" unless record.is_a?(@association_class)
|
106
|
+
end
|
107
|
+
|
108
|
+
|
55
109
|
def load_collection_to_array
|
56
110
|
return unless @collection_array.nil?
|
57
111
|
begin
|
@@ -65,6 +119,11 @@ module ActiveRecord
|
|
65
119
|
records = [records] unless records.is_a?(Array) || records.is_a?(ActiveRecord::Associations::AssociationCollection)
|
66
120
|
records.dup
|
67
121
|
end
|
122
|
+
|
123
|
+
# Array#flatten has problems with rescursive arrays. Going one level deeper solves the majority of the problems.
|
124
|
+
def flatten_deeper(array)
|
125
|
+
array.collect { |element| element.respond_to?(:flatten) ? element.flatten : element }.flatten
|
126
|
+
end
|
68
127
|
end
|
69
128
|
end
|
70
129
|
end
|
@@ -1,45 +1,106 @@
|
|
1
1
|
module ActiveRecord
|
2
2
|
module Associations
|
3
|
-
class
|
3
|
+
class HasAndBelongsToManyAssociation < AssociationCollection #:nodoc:
|
4
4
|
def initialize(owner, association_name, association_class_name, association_class_primary_key_name, join_table, options)
|
5
5
|
super(owner, association_name, association_class_name, association_class_primary_key_name, options)
|
6
|
-
|
7
|
-
@association_foreign_key = options[:association_foreign_key] || association_class_name.downcase + "_id"
|
6
|
+
|
7
|
+
@association_foreign_key = options[:association_foreign_key] || Inflector.underscore(Inflector.demodulize(association_class_name.downcase)) + "_id"
|
8
8
|
association_table_name = options[:table_name] || @association_class.table_name(association_class_name)
|
9
9
|
@join_table = join_table
|
10
10
|
@order = options[:order] || "t.#{@owner.class.primary_key}"
|
11
11
|
|
12
|
+
interpolate_sql_options!(options, :finder_sql, :delete_sql)
|
12
13
|
@finder_sql = options[:finder_sql] ||
|
13
|
-
"SELECT t.* FROM #{association_table_name} t, #{@join_table} j " +
|
14
|
+
"SELECT t.*, j.* FROM #{association_table_name} t, #{@join_table} j " +
|
14
15
|
"WHERE t.#{@owner.class.primary_key} = j.#{@association_foreign_key} AND " +
|
15
|
-
"j.#{association_class_primary_key_name} = '#{@owner.id}'
|
16
|
+
"j.#{association_class_primary_key_name} = '#{@owner.id}' " +
|
17
|
+
(options[:conditions] ? " AND " + options[:conditions] : "") + " " +
|
18
|
+
"ORDER BY #{@order}"
|
16
19
|
end
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
@
|
20
|
+
|
21
|
+
# Removes all records from this association. Returns +self+ so method calls may be chained.
|
22
|
+
def clear
|
23
|
+
return self if size == 0 # forces load_collection if hasn't happened already
|
24
|
+
|
25
|
+
if sql = @options[:delete_sql]
|
26
|
+
each { |record| @owner.connection.execute(sql) }
|
27
|
+
elsif @options[:conditions]
|
28
|
+
sql =
|
29
|
+
"DELETE FROM #{@join_table} WHERE #{@association_class_primary_key_name} = '#{@owner.id}' " +
|
30
|
+
"AND #{@association_foreign_key} IN (#{collect { |record| record.id }.join(", ")})"
|
31
|
+
@owner.connection.execute(sql)
|
32
|
+
else
|
33
|
+
sql = "DELETE FROM #{@join_table} WHERE #{@association_class_primary_key_name} = '#{@owner.id}'"
|
34
|
+
@owner.connection.execute(sql)
|
35
|
+
end
|
36
|
+
|
37
|
+
@collection = []
|
38
|
+
self
|
25
39
|
end
|
26
|
-
|
27
|
-
def
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
40
|
+
|
41
|
+
def find(association_id = nil, &block)
|
42
|
+
if block_given? || @options[:finder_sql]
|
43
|
+
load_collection
|
44
|
+
@collection.find(&block)
|
45
|
+
else
|
46
|
+
if loaded?
|
47
|
+
find_all { |record| record.id == association_id.to_i }.first
|
48
|
+
else
|
49
|
+
find_all_records(@finder_sql.sub(/ORDER BY/, "AND j.#{@association_foreign_key} = '#{association_id}' ORDER BY")).first
|
50
|
+
end
|
51
|
+
end
|
33
52
|
end
|
34
|
-
|
53
|
+
|
54
|
+
def push_with_attributes(record, join_attributes = {})
|
55
|
+
raise_on_type_mismatch(record)
|
56
|
+
insert_record_with_join_attributes(record, join_attributes)
|
57
|
+
join_attributes.each { |key, value| record.send(:write_attribute, key, value) }
|
58
|
+
@collection << record if loaded?
|
59
|
+
self
|
60
|
+
end
|
61
|
+
|
62
|
+
alias :concat_with_attributes :push_with_attributes
|
63
|
+
|
64
|
+
def size
|
65
|
+
@options[:uniq] ? count_records : super
|
66
|
+
end
|
67
|
+
|
35
68
|
protected
|
36
|
-
def find_all_records
|
37
|
-
@association_class.find_by_sql(
|
69
|
+
def find_all_records(sql = @finder_sql)
|
70
|
+
records = @association_class.find_by_sql(sql)
|
71
|
+
@options[:uniq] ? uniq(records) : records
|
38
72
|
end
|
39
73
|
|
40
74
|
def count_records
|
41
|
-
|
42
|
-
@
|
75
|
+
load_collection
|
76
|
+
@collection.size
|
77
|
+
end
|
78
|
+
|
79
|
+
def insert_record(record)
|
80
|
+
if @options[:insert_sql]
|
81
|
+
@owner.connection.execute(interpolate_sql(@options[:insert_sql], record))
|
82
|
+
else
|
83
|
+
sql = "INSERT INTO #{@join_table} (#{@association_class_primary_key_name}, #{@association_foreign_key}) VALUES ('#{@owner.id}','#{record.id}')"
|
84
|
+
@owner.connection.execute(sql)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
def insert_record_with_join_attributes(record, join_attributes)
|
89
|
+
attributes = { @association_class_primary_key_name => @owner.id, @association_foreign_key => record.id }.update(join_attributes)
|
90
|
+
sql =
|
91
|
+
"INSERT INTO #{@join_table} (#{@owner.send(:quoted_column_names, attributes).join(', ')}) " +
|
92
|
+
"VALUES (#{attributes.values.collect { |value| @owner.send(:quote, value) }.join(', ')})"
|
93
|
+
@owner.connection.execute(sql)
|
94
|
+
end
|
95
|
+
|
96
|
+
def delete_records(records)
|
97
|
+
if sql = @options[:delete_sql]
|
98
|
+
records.each { |record| @owner.connection.execute(sql) }
|
99
|
+
else
|
100
|
+
ids = quoted_record_ids(records)
|
101
|
+
sql = "DELETE FROM #{@join_table} WHERE #{@association_class_primary_key_name} = '#{@owner.id}' AND #{@association_foreign_key} IN (#{ids})"
|
102
|
+
@owner.connection.execute(sql)
|
103
|
+
end
|
43
104
|
end
|
44
105
|
end
|
45
106
|
end
|
@@ -3,76 +3,65 @@ module ActiveRecord
|
|
3
3
|
class HasManyAssociation < AssociationCollection #:nodoc:
|
4
4
|
def initialize(owner, association_name, association_class_name, association_class_primary_key_name, options)
|
5
5
|
super(owner, association_name, association_class_name, association_class_primary_key_name, options)
|
6
|
-
@conditions = options[:conditions]
|
7
|
-
|
6
|
+
@conditions = @association_class.send(:sanitize_conditions, options[:conditions])
|
7
|
+
|
8
8
|
if options[:finder_sql]
|
9
|
-
@
|
10
|
-
@
|
9
|
+
@finder_sql = interpolate_sql(options[:finder_sql])
|
10
|
+
@counter_sql = @finder_sql.gsub(/SELECT (.*) FROM/i, "SELECT COUNT(*) FROM")
|
11
11
|
else
|
12
|
-
@
|
13
|
-
@
|
14
|
-
end
|
15
|
-
end
|
16
|
-
|
17
|
-
def <<(record)
|
18
|
-
raise ActiveRecord::AssociationTypeMismatch unless @association_class === record
|
19
|
-
record.send(@association_class_primary_key_name + "=", @owner.id)
|
20
|
-
record.save(false)
|
21
|
-
@collection_array << record unless @collection_array.nil?
|
22
|
-
end
|
23
|
-
|
24
|
-
def delete(records)
|
25
|
-
duplicated_records_array(records).each do |record|
|
26
|
-
next if record.send(@association_class_primary_key_name) != @owner.id
|
27
|
-
record.send(@association_class_primary_key_name + "=", nil)
|
28
|
-
record.save(false)
|
29
|
-
@collection_array.delete(record) unless @collection_array.nil?
|
12
|
+
@finder_sql = "#{@association_class_primary_key_name} = '#{@owner.id}' #{@conditions ? " AND " + interpolate_sql(@conditions) : ""}"
|
13
|
+
@counter_sql = "#{@association_class_primary_key_name} = '#{@owner.id}'#{@conditions ? " AND " + interpolate_sql(@conditions) : ""}"
|
30
14
|
end
|
31
15
|
end
|
32
|
-
|
16
|
+
|
33
17
|
def create(attributes = {})
|
34
|
-
#
|
35
|
-
record =
|
36
|
-
record.send(@association_class_primary_key_name + "=", @owner.id)
|
18
|
+
# Can't use Base.create since the foreign key may be a protected attribute.
|
19
|
+
record = build(attributes)
|
37
20
|
record.save
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
return record
|
21
|
+
@collection << record if loaded?
|
22
|
+
record
|
42
23
|
end
|
43
24
|
|
44
25
|
def build(attributes = {})
|
45
|
-
|
46
|
-
|
47
|
-
|
26
|
+
record = @association_class.new(attributes)
|
27
|
+
record[@association_class_primary_key_name] = @owner.id
|
28
|
+
record
|
48
29
|
end
|
49
|
-
|
30
|
+
|
50
31
|
def find_all(runtime_conditions = nil, orderings = nil, limit = nil, joins = nil, &block)
|
51
32
|
if block_given? || @options[:finder_sql]
|
52
|
-
|
53
|
-
@
|
33
|
+
load_collection
|
34
|
+
@collection.find_all(&block)
|
54
35
|
else
|
55
36
|
@association_class.find_all(
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
37
|
+
"#{@association_class_primary_key_name} = '#{@owner.id}' " +
|
38
|
+
"#{@conditions ? " AND " + @conditions : ""} #{runtime_conditions ? " AND " + @association_class.send(:sanitize_conditions, runtime_conditions) : ""}",
|
39
|
+
orderings,
|
40
|
+
limit,
|
41
|
+
joins
|
42
|
+
)
|
62
43
|
end
|
63
44
|
end
|
64
45
|
|
65
46
|
def find(association_id = nil, &block)
|
66
47
|
if block_given? || @options[:finder_sql]
|
67
|
-
|
68
|
-
|
48
|
+
load_collection
|
49
|
+
@collection.find(&block)
|
69
50
|
else
|
70
|
-
@association_class.find_on_conditions(
|
71
|
-
|
72
|
-
|
51
|
+
@association_class.find_on_conditions(association_id,
|
52
|
+
"#{@association_class_primary_key_name} = '#{@owner.id}' #{@conditions ? " AND " + @conditions : ""}"
|
53
|
+
)
|
73
54
|
end
|
74
55
|
end
|
75
|
-
|
56
|
+
|
57
|
+
# Removes all records from this association. Returns +self+ so
|
58
|
+
# method calls may be chained.
|
59
|
+
def clear
|
60
|
+
@association_class.update_all("#{@association_class_primary_key_name} = NULL", "#{@association_class_primary_key_name} = '#{@owner.id}'")
|
61
|
+
@collection = []
|
62
|
+
self
|
63
|
+
end
|
64
|
+
|
76
65
|
protected
|
77
66
|
def find_all_records
|
78
67
|
if @options[:finder_sql]
|
@@ -97,8 +86,17 @@ module ActiveRecord
|
|
97
86
|
end
|
98
87
|
|
99
88
|
def cached_counter_attribute_name
|
100
|
-
@association_name
|
89
|
+
"#{@association_name}_count"
|
90
|
+
end
|
91
|
+
|
92
|
+
def insert_record(record)
|
93
|
+
record.update_attribute(@association_class_primary_key_name, @owner.id)
|
94
|
+
end
|
95
|
+
|
96
|
+
def delete_records(records)
|
97
|
+
ids = quoted_record_ids(records)
|
98
|
+
@association_class.update_all("#{@association_class_primary_key_name} = NULL", "#{@association_class_primary_key_name} = '#{@owner.id}' AND #{@association_class.primary_key} IN (#{ids})")
|
101
99
|
end
|
102
100
|
end
|
103
101
|
end
|
104
|
-
end
|
102
|
+
end
|
data/lib/active_record/base.rb
CHANGED
@@ -111,7 +111,7 @@ module ActiveRecord #:nodoc:
|
|
111
111
|
# end
|
112
112
|
#
|
113
113
|
# user = User.create("preferences" => %w( one two three ))
|
114
|
-
# User.find(user.id).preferences
|
114
|
+
# User.find(user.id).preferences # raises SerializationTypeMismatch
|
115
115
|
#
|
116
116
|
# == Single table inheritance
|
117
117
|
#
|
@@ -220,7 +220,7 @@ module ActiveRecord #:nodoc:
|
|
220
220
|
# Person.find([7, 17]) # returns an array for objects with IDs in (7, 17)
|
221
221
|
# +RecordNotFound+ is raised if no record can be found.
|
222
222
|
def find(*ids)
|
223
|
-
ids =
|
223
|
+
ids = ids.flatten.compact.uniq
|
224
224
|
|
225
225
|
if ids.length > 1
|
226
226
|
ids_list = ids.map{ |id| "'#{sanitize(id)}'" }.join(", ")
|
@@ -234,7 +234,7 @@ module ActiveRecord #:nodoc:
|
|
234
234
|
elsif ids.length == 1
|
235
235
|
id = ids.first
|
236
236
|
sql = "SELECT * FROM #{table_name} WHERE #{primary_key} = '#{sanitize(id)}'"
|
237
|
-
sql << " AND #{type_condition}" unless
|
237
|
+
sql << " AND #{type_condition}" unless descends_from_active_record?
|
238
238
|
|
239
239
|
if record = connection.select_one(sql, "#{name} Find")
|
240
240
|
instantiate(record)
|
@@ -275,7 +275,7 @@ module ActiveRecord #:nodoc:
|
|
275
275
|
def find_by_sql(sql)
|
276
276
|
connection.select_all(sql, "#{name} Load").inject([]) { |objects, record| objects << instantiate(record) }
|
277
277
|
end
|
278
|
-
|
278
|
+
|
279
279
|
# Returns the object for the first record responding to the conditions in +conditions+,
|
280
280
|
# such as "group = 'master'". If more than one record is returned from the query, it's the first that'll
|
281
281
|
# be used to create the object. In such cases, it might be beneficial to also specify
|
@@ -461,7 +461,7 @@ module ActiveRecord #:nodoc:
|
|
461
461
|
|
462
462
|
# Defines the column name for use with single table inheritance -- can be overridden in subclasses.
|
463
463
|
def inheritance_column
|
464
|
-
|
464
|
+
"type"
|
465
465
|
end
|
466
466
|
|
467
467
|
# Turns the +table_name+ back into a class name following the reverse rules of +table_name+.
|
@@ -485,7 +485,19 @@ module ActiveRecord #:nodoc:
|
|
485
485
|
# Returns an array of columns objects where the primary id, all columns ending in "_id" or "_count",
|
486
486
|
# and columns used for single table inheritance has been removed.
|
487
487
|
def content_columns
|
488
|
-
columns.reject { |c| c.name == primary_key || c.name =~ /(_id|_count)$/ || c.name == inheritance_column }
|
488
|
+
@content_columns ||= columns.reject { |c| c.name == primary_key || c.name =~ /(_id|_count)$/ || c.name == inheritance_column }
|
489
|
+
end
|
490
|
+
|
491
|
+
# 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
|
492
|
+
# and true as the value. This makes it possible to do O(1) lookups in respond_to? to check if a given method for attribute
|
493
|
+
# is available.
|
494
|
+
def column_methods_hash
|
495
|
+
@dynamic_methods_hash ||= columns_hash.keys.inject(Hash.new(false)) do |methods, attr|
|
496
|
+
methods[attr.to_sym] = true
|
497
|
+
methods["#{attr}=".to_sym] = true
|
498
|
+
methods["#{attr}?".to_sym] = true
|
499
|
+
methods
|
500
|
+
end
|
489
501
|
end
|
490
502
|
|
491
503
|
# Transforms attribute key names into a more humane format, such as "First name" instead of "first_name". Example:
|
@@ -494,7 +506,7 @@ module ActiveRecord #:nodoc:
|
|
494
506
|
attribute_key_name.gsub(/_/, " ").capitalize unless attribute_key_name.nil?
|
495
507
|
end
|
496
508
|
|
497
|
-
def
|
509
|
+
def descends_from_active_record? # :nodoc:
|
498
510
|
superclass == Base
|
499
511
|
end
|
500
512
|
|
@@ -513,10 +525,12 @@ module ActiveRecord #:nodoc:
|
|
513
525
|
# project.milestones << Milestone.find_all
|
514
526
|
# end
|
515
527
|
def benchmark(title)
|
528
|
+
result = nil
|
516
529
|
logger.level = Logger::ERROR
|
517
|
-
bm = Benchmark.measure { yield }
|
530
|
+
bm = Benchmark.measure { result = yield }
|
518
531
|
logger.level = Logger::DEBUG
|
519
532
|
logger.info "#{title} (#{sprintf("%f", bm.real)})"
|
533
|
+
return result
|
520
534
|
end
|
521
535
|
|
522
536
|
private
|
@@ -531,7 +545,7 @@ module ActiveRecord #:nodoc:
|
|
531
545
|
# Returns true if the +record+ has a single table inheritance column and is using it.
|
532
546
|
def record_with_type?(record)
|
533
547
|
record.include?(inheritance_column) && !record[inheritance_column].nil? &&
|
534
|
-
|
548
|
+
!record[inheritance_column].empty?
|
535
549
|
end
|
536
550
|
|
537
551
|
# Returns the name of the type of the record using the current module as a prefix. So descendents of
|
@@ -543,12 +557,12 @@ module ActiveRecord #:nodoc:
|
|
543
557
|
# Adds a sanitized version of +conditions+ to the +sql+ string. Note that it's the passed +sql+ string is changed.
|
544
558
|
def add_conditions!(sql, conditions)
|
545
559
|
sql << "WHERE #{sanitize_conditions(conditions)} " unless conditions.nil?
|
546
|
-
sql << (conditions.nil? ? "WHERE " : " AND ") + type_condition unless
|
560
|
+
sql << (conditions.nil? ? "WHERE " : " AND ") + type_condition unless descends_from_active_record?
|
547
561
|
end
|
548
562
|
|
549
563
|
def type_condition
|
550
564
|
" (" + subclasses.inject("#{inheritance_column} = '#{Inflector.demodulize(name)}' ") do |condition, subclass|
|
551
|
-
condition << "OR #{inheritance_column} = '#{Inflector.demodulize(subclass.name)}'
|
565
|
+
condition << "OR #{inheritance_column} = '#{Inflector.demodulize(subclass.name)}'"
|
552
566
|
end + ") "
|
553
567
|
end
|
554
568
|
|
@@ -614,7 +628,7 @@ module ActiveRecord #:nodoc:
|
|
614
628
|
# Every Active Record class must use "id" as their primary ID. This getter overwrites the native
|
615
629
|
# id method, which isn't being used in this context.
|
616
630
|
def id
|
617
|
-
read_attribute(self.class.primary_key)
|
631
|
+
read_attribute(self.class.primary_key) unless new_record?
|
618
632
|
end
|
619
633
|
|
620
634
|
# Sets the primary ID.
|
@@ -716,20 +730,30 @@ module ActiveRecord #:nodoc:
|
|
716
730
|
def column_for_attribute(name)
|
717
731
|
self.class.columns_hash[name]
|
718
732
|
end
|
719
|
-
|
733
|
+
|
720
734
|
# Returns true if the +comparison_object+ is of the same type and has the same id.
|
721
735
|
def ==(comparison_object)
|
722
736
|
comparison_object.instance_of?(self.class) && comparison_object.id == id
|
723
737
|
end
|
724
738
|
|
739
|
+
# Delegates to ==
|
740
|
+
def eql?(comparison_object)
|
741
|
+
self == (comparison_object)
|
742
|
+
end
|
743
|
+
|
744
|
+
# Delegates to id in order to allow two records of the same type and id to work with something like:
|
745
|
+
# [ Person.find(1), Person.find(2), Person.find(3) ] & [ Person.find(1), Person.find(4) ] # => [ Person.find(1) ]
|
746
|
+
def hash
|
747
|
+
id
|
748
|
+
end
|
749
|
+
|
725
750
|
# For checking respond_to? without searching the attributes (which is faster).
|
726
751
|
alias_method :respond_to_without_attributes?, :respond_to?
|
727
752
|
|
728
753
|
# A Person object with a name attribute can ask person.respond_to?("name"), person.respond_to?("name="), and
|
729
754
|
# person.respond_to?("name?") which will all return true.
|
730
755
|
def respond_to?(method)
|
731
|
-
|
732
|
-
@@dynamic_methods.include?(method.to_s) ? true : respond_to_without_attributes?(method)
|
756
|
+
self.class.column_methods_hash[method.to_sym] || respond_to_without_attributes?(method)
|
733
757
|
end
|
734
758
|
|
735
759
|
private
|
@@ -754,7 +778,7 @@ module ActiveRecord #:nodoc:
|
|
754
778
|
"(#{quoted_column_names.join(', ')}) " +
|
755
779
|
"VALUES(#{attributes_with_quotes.values.join(', ')})",
|
756
780
|
"#{self.class.name} Create",
|
757
|
-
|
781
|
+
self.class.primary_key, self.id
|
758
782
|
)
|
759
783
|
|
760
784
|
@new_record = false
|
@@ -765,9 +789,9 @@ module ActiveRecord #:nodoc:
|
|
765
789
|
# set Reply[Reply.inheritance_column] = "Reply" yourself. No such attribute would be set for objects of the
|
766
790
|
# Message class in that example.
|
767
791
|
def ensure_proper_type
|
768
|
-
unless self.class.
|
769
|
-
|
770
|
-
|
792
|
+
unless self.class.descends_from_active_record?
|
793
|
+
write_attribute(self.class.inheritance_column, Inflector.demodulize(self.class.name))
|
794
|
+
end
|
771
795
|
end
|
772
796
|
|
773
797
|
# Allows access to the object attributes, which are held in the @attributes hash, as were
|
@@ -781,11 +805,13 @@ module ActiveRecord #:nodoc:
|
|
781
805
|
def method_missing(method_id, *arguments)
|
782
806
|
method_name = method_id.id2name
|
783
807
|
|
808
|
+
|
809
|
+
|
784
810
|
if method_name =~ read_method? && @attributes.include?($1)
|
785
811
|
return read_attribute($1)
|
786
|
-
elsif method_name =~ write_method?
|
812
|
+
elsif method_name =~ write_method? && @attributes.include?($1)
|
787
813
|
write_attribute($1, arguments[0])
|
788
|
-
elsif method_name =~ query_method?
|
814
|
+
elsif method_name =~ query_method? && @attributes.include?($1)
|
789
815
|
return query_attribute($1)
|
790
816
|
else
|
791
817
|
super
|
@@ -884,6 +910,12 @@ module ActiveRecord #:nodoc:
|
|
884
910
|
connection.quote(value, column)
|
885
911
|
end
|
886
912
|
|
913
|
+
# Interpolate custom sql string in instance context.
|
914
|
+
# Optional record argument is meant for custom insert_sql.
|
915
|
+
def interpolate_sql(sql, record = nil)
|
916
|
+
instance_eval("%(#{sql})")
|
917
|
+
end
|
918
|
+
|
887
919
|
# Initializes the attributes array with keys matching the columns from the linked table and
|
888
920
|
# the values matching the corresponding default value of that column, so
|
889
921
|
# that a new instance, or one populated from a passed-in Hash, still has all the attributes
|
@@ -949,8 +981,8 @@ module ActiveRecord #:nodoc:
|
|
949
981
|
hash.inject([]) { |list, pair| list << "#{pair.first} = #{pair.last}" }.join(", ")
|
950
982
|
end
|
951
983
|
|
952
|
-
def quoted_column_names
|
953
|
-
|
984
|
+
def quoted_column_names(attributes = attributes_with_quotes)
|
985
|
+
attributes.keys.collect { |column_name| connection.quote_column_name(column_name) }
|
954
986
|
end
|
955
987
|
|
956
988
|
def quote_columns(column_quoter, hash)
|
@@ -982,4 +1014,4 @@ module ActiveRecord #:nodoc:
|
|
982
1014
|
string[0..3] == "--- "
|
983
1015
|
end
|
984
1016
|
end
|
985
|
-
end
|
1017
|
+
end
|