activerecord 1.14.4 → 1.15.0
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of activerecord might be problematic. Click here for more details.
- data/CHANGELOG +400 -1
- data/README +2 -2
- data/RUNNING_UNIT_TESTS +21 -3
- data/Rakefile +55 -10
- data/lib/active_record.rb +10 -4
- data/lib/active_record/acts/list.rb +15 -4
- data/lib/active_record/acts/nested_set.rb +11 -12
- data/lib/active_record/acts/tree.rb +13 -14
- data/lib/active_record/aggregations.rb +46 -22
- data/lib/active_record/associations.rb +213 -162
- data/lib/active_record/associations/association_collection.rb +45 -15
- data/lib/active_record/associations/association_proxy.rb +32 -13
- data/lib/active_record/associations/has_and_belongs_to_many_association.rb +18 -18
- data/lib/active_record/associations/has_many_association.rb +37 -17
- data/lib/active_record/associations/has_many_through_association.rb +120 -30
- data/lib/active_record/associations/has_one_association.rb +1 -1
- data/lib/active_record/attribute_methods.rb +75 -0
- data/lib/active_record/base.rb +282 -203
- data/lib/active_record/calculations.rb +95 -54
- data/lib/active_record/callbacks.rb +13 -24
- data/lib/active_record/connection_adapters/abstract/connection_specification.rb +12 -1
- data/lib/active_record/connection_adapters/abstract/connection_specification.rb.rej +21 -0
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +30 -4
- data/lib/active_record/connection_adapters/abstract/quoting.rb +16 -9
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +121 -37
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +55 -23
- data/lib/active_record/connection_adapters/abstract_adapter.rb +8 -0
- data/lib/active_record/connection_adapters/db2_adapter.rb +1 -11
- data/lib/active_record/connection_adapters/firebird_adapter.rb +364 -50
- data/lib/active_record/connection_adapters/frontbase_adapter.rb +861 -0
- data/lib/active_record/connection_adapters/mysql_adapter.rb +86 -33
- data/lib/active_record/connection_adapters/openbase_adapter.rb +4 -3
- data/lib/active_record/connection_adapters/oracle_adapter.rb +151 -127
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +125 -48
- data/lib/active_record/connection_adapters/sqlite_adapter.rb +38 -10
- data/lib/active_record/connection_adapters/sqlserver_adapter.rb +183 -155
- data/lib/active_record/connection_adapters/sybase_adapter.rb +190 -212
- data/lib/active_record/deprecated_associations.rb +24 -10
- data/lib/active_record/deprecated_finders.rb +4 -1
- data/lib/active_record/fixtures.rb +37 -23
- data/lib/active_record/locking/optimistic.rb +106 -0
- data/lib/active_record/locking/pessimistic.rb +77 -0
- data/lib/active_record/migration.rb +8 -5
- data/lib/active_record/observer.rb +73 -34
- data/lib/active_record/reflection.rb +21 -7
- data/lib/active_record/schema_dumper.rb +33 -5
- data/lib/active_record/timestamp.rb +23 -34
- data/lib/active_record/transactions.rb +37 -30
- data/lib/active_record/validations.rb +46 -30
- data/lib/active_record/vendor/mysql.rb +20 -5
- data/lib/active_record/version.rb +2 -2
- data/lib/active_record/wrappings.rb +1 -2
- data/lib/active_record/xml_serialization.rb +308 -0
- data/test/aaa_create_tables_test.rb +5 -1
- data/test/abstract_unit.rb +18 -8
- data/test/{active_schema_mysql.rb → active_schema_test_mysql.rb} +2 -2
- data/test/adapter_test.rb +9 -7
- data/test/adapter_test_sqlserver.rb +81 -0
- data/test/aggregations_test.rb +29 -0
- data/test/{association_callbacks_test.rb → associations/callbacks_test.rb} +10 -8
- data/test/{associations_cascaded_eager_loading_test.rb → associations/cascaded_eager_loading_test.rb} +35 -3
- data/test/{associations_go_eager_test.rb → associations/eager_test.rb} +36 -2
- data/test/{associations_extensions_test.rb → associations/extension_test.rb} +5 -0
- data/test/{associations_join_model_test.rb → associations/join_model_test.rb} +118 -8
- data/test/associations_test.rb +339 -45
- data/test/attribute_methods_test.rb +49 -0
- data/test/base_test.rb +321 -67
- data/test/calculations_test.rb +48 -10
- data/test/callbacks_test.rb +13 -0
- data/test/connection_test_firebird.rb +8 -0
- data/test/connections/native_db2/connection.rb +18 -17
- data/test/connections/native_firebird/connection.rb +19 -17
- data/test/connections/native_frontbase/connection.rb +27 -0
- data/test/connections/native_mysql/connection.rb +18 -15
- data/test/connections/native_openbase/connection.rb +14 -15
- data/test/connections/native_oracle/connection.rb +16 -12
- data/test/connections/native_postgresql/connection.rb +16 -17
- data/test/connections/native_sqlite/connection.rb +3 -6
- data/test/connections/native_sqlite3/connection.rb +3 -6
- data/test/connections/native_sqlserver/connection.rb +16 -17
- data/test/connections/native_sqlserver_odbc/connection.rb +18 -19
- data/test/connections/native_sybase/connection.rb +16 -17
- data/test/datatype_test_postgresql.rb +52 -0
- data/test/defaults_test.rb +52 -10
- data/test/deprecated_associations_test.rb +151 -107
- data/test/deprecated_finder_test.rb +83 -66
- data/test/empty_date_time_test.rb +25 -0
- data/test/finder_test.rb +118 -11
- data/test/fixtures/accounts.yml +6 -1
- data/test/fixtures/author.rb +27 -4
- data/test/fixtures/categorizations.yml +8 -2
- data/test/fixtures/category.rb +1 -2
- data/test/fixtures/comments.yml +0 -6
- data/test/fixtures/companies.yml +6 -1
- data/test/fixtures/company.rb +23 -1
- data/test/fixtures/company_in_module.rb +8 -10
- data/test/fixtures/customer.rb +2 -2
- data/test/fixtures/customers.yml +9 -0
- data/test/fixtures/db_definitions/db2.drop.sql +1 -0
- data/test/fixtures/db_definitions/db2.sql +9 -0
- data/test/fixtures/db_definitions/firebird.drop.sql +3 -0
- data/test/fixtures/db_definitions/firebird.sql +13 -1
- data/test/fixtures/db_definitions/frontbase.drop.sql +31 -0
- data/test/fixtures/db_definitions/frontbase.sql +262 -0
- data/test/fixtures/db_definitions/frontbase2.drop.sql +1 -0
- data/test/fixtures/db_definitions/frontbase2.sql +4 -0
- data/test/fixtures/db_definitions/mysql.drop.sql +1 -0
- data/test/fixtures/db_definitions/mysql.sql +23 -14
- data/test/fixtures/db_definitions/openbase.sql +13 -1
- data/test/fixtures/db_definitions/oracle.drop.sql +2 -0
- data/test/fixtures/db_definitions/oracle.sql +29 -2
- data/test/fixtures/db_definitions/postgresql.drop.sql +3 -1
- data/test/fixtures/db_definitions/postgresql.sql +13 -3
- data/test/fixtures/db_definitions/schema.rb +29 -1
- data/test/fixtures/db_definitions/sqlite.drop.sql +1 -0
- data/test/fixtures/db_definitions/sqlite.sql +12 -3
- data/test/fixtures/db_definitions/sqlserver.drop.sql +3 -0
- data/test/fixtures/db_definitions/sqlserver.sql +35 -0
- data/test/fixtures/db_definitions/sybase.drop.sql +2 -0
- data/test/fixtures/db_definitions/sybase.sql +13 -4
- data/test/fixtures/developer.rb +12 -0
- data/test/fixtures/edge.rb +5 -0
- data/test/fixtures/edges.yml +6 -0
- data/test/fixtures/funny_jokes.yml +3 -7
- data/test/fixtures/migrations_with_decimal/1_give_me_big_numbers.rb +15 -0
- data/test/fixtures/migrations_with_missing_versions/1000_people_have_middle_names.rb +9 -0
- data/test/fixtures/migrations_with_missing_versions/1_people_have_last_names.rb +9 -0
- data/test/fixtures/migrations_with_missing_versions/3_we_need_reminders.rb +12 -0
- data/test/fixtures/migrations_with_missing_versions/4_innocent_jointable.rb +12 -0
- data/test/fixtures/mixin.rb +15 -0
- data/test/fixtures/mixins.yml +38 -0
- data/test/fixtures/post.rb +3 -2
- data/test/fixtures/project.rb +3 -1
- data/test/fixtures/topic.rb +6 -1
- data/test/fixtures/topics.yml +4 -4
- data/test/fixtures/vertex.rb +9 -0
- data/test/fixtures/vertices.yml +4 -0
- data/test/fixtures_test.rb +45 -0
- data/test/inheritance_test.rb +67 -6
- data/test/lifecycle_test.rb +40 -19
- data/test/locking_test.rb +170 -26
- data/test/method_scoping_test.rb +2 -2
- data/test/migration_test.rb +387 -110
- data/test/migration_test_firebird.rb +124 -0
- data/test/mixin_nested_set_test.rb +14 -2
- data/test/mixin_test.rb +56 -18
- data/test/modules_test.rb +8 -2
- data/test/multiple_db_test.rb +2 -2
- data/test/pk_test.rb +1 -0
- data/test/reflection_test.rb +8 -2
- data/test/schema_authorization_test_postgresql.rb +75 -0
- data/test/schema_dumper_test.rb +40 -4
- data/test/table_name_test_sqlserver.rb +23 -0
- data/test/threaded_connections_test.rb +19 -16
- data/test/transactions_test.rb +86 -72
- data/test/validations_test.rb +126 -56
- data/test/xml_serialization_test.rb +125 -0
- metadata +45 -11
- data/lib/active_record/locking.rb +0 -79
@@ -9,7 +9,7 @@ module ActiveRecord
|
|
9
9
|
end
|
10
10
|
|
11
11
|
def reset
|
12
|
-
|
12
|
+
reset_target!
|
13
13
|
@loaded = false
|
14
14
|
end
|
15
15
|
|
@@ -28,7 +28,7 @@ module ActiveRecord
|
|
28
28
|
callback(:after_add, record)
|
29
29
|
end
|
30
30
|
end
|
31
|
-
|
31
|
+
|
32
32
|
result && self
|
33
33
|
end
|
34
34
|
|
@@ -39,7 +39,12 @@ module ActiveRecord
|
|
39
39
|
def delete_all
|
40
40
|
load_target
|
41
41
|
delete(@target)
|
42
|
-
|
42
|
+
reset_target!
|
43
|
+
end
|
44
|
+
|
45
|
+
# Calculate sum using SQL, not Enumerable
|
46
|
+
def sum(*args, &block)
|
47
|
+
calculate(:sum, *args, &block)
|
43
48
|
end
|
44
49
|
|
45
50
|
# Remove +records+ from this association. Does not destroy +records+.
|
@@ -77,9 +82,9 @@ module ActiveRecord
|
|
77
82
|
each { |record| record.destroy }
|
78
83
|
end
|
79
84
|
|
80
|
-
|
85
|
+
reset_target!
|
81
86
|
end
|
82
|
-
|
87
|
+
|
83
88
|
def create(attributes = {})
|
84
89
|
# Can't use Base.create since the foreign key may be a protected attribute.
|
85
90
|
if attributes.is_a?(Array)
|
@@ -95,21 +100,35 @@ module ActiveRecord
|
|
95
100
|
# calling collection.size if it has. If it's more likely than not that the collection does have a size larger than zero
|
96
101
|
# and you need to fetch that collection afterwards, it'll take one less SELECT query if you use length.
|
97
102
|
def size
|
98
|
-
if loaded?
|
103
|
+
if loaded? && !@reflection.options[:uniq]
|
104
|
+
@target.size
|
105
|
+
elsif !loaded? && !@reflection.options[:uniq] && @target.is_a?(Array)
|
106
|
+
unsaved_records = Array(@target.detect { |r| r.new_record? })
|
107
|
+
unsaved_records.size + count_records
|
108
|
+
else
|
109
|
+
count_records
|
110
|
+
end
|
99
111
|
end
|
100
|
-
|
112
|
+
|
101
113
|
# Returns the size of the collection by loading it and calling size on the array. If you want to use this method to check
|
102
114
|
# whether the collection is empty, use collection.length.zero? instead of collection.empty?
|
103
115
|
def length
|
104
116
|
load_target.size
|
105
117
|
end
|
106
|
-
|
118
|
+
|
107
119
|
def empty?
|
108
120
|
size.zero?
|
109
121
|
end
|
110
|
-
|
122
|
+
|
111
123
|
def uniq(collection = self)
|
112
|
-
|
124
|
+
seen = Set.new
|
125
|
+
collection.inject([]) do |kept, record|
|
126
|
+
unless seen.include?(record.id)
|
127
|
+
kept << record
|
128
|
+
seen << record.id
|
129
|
+
end
|
130
|
+
kept
|
131
|
+
end
|
113
132
|
end
|
114
133
|
|
115
134
|
# Replace this collection with +other_array+
|
@@ -127,12 +146,23 @@ module ActiveRecord
|
|
127
146
|
end
|
128
147
|
end
|
129
148
|
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
array.collect { |element| element.respond_to?(:flatten) ? element.flatten : element }.flatten
|
149
|
+
protected
|
150
|
+
def reset_target!
|
151
|
+
@target = Array.new
|
134
152
|
end
|
135
|
-
|
153
|
+
|
154
|
+
def find_target
|
155
|
+
records =
|
156
|
+
if @reflection.options[:finder_sql]
|
157
|
+
@reflection.klass.find_by_sql(@finder_sql)
|
158
|
+
else
|
159
|
+
find(:all)
|
160
|
+
end
|
161
|
+
|
162
|
+
@reflection.options[:uniq] ? uniq(records) : records
|
163
|
+
end
|
164
|
+
|
165
|
+
private
|
136
166
|
def callback(method, record)
|
137
167
|
callbacks_for(method).each do |callback|
|
138
168
|
case callback
|
@@ -4,14 +4,27 @@ module ActiveRecord
|
|
4
4
|
attr_reader :reflection
|
5
5
|
alias_method :proxy_respond_to?, :respond_to?
|
6
6
|
alias_method :proxy_extend, :extend
|
7
|
-
|
7
|
+
delegate :to_param, :to => :proxy_target
|
8
|
+
instance_methods.each { |m| undef_method m unless m =~ /(^__|^nil\?$|^send$|proxy_)/ }
|
8
9
|
|
9
10
|
def initialize(owner, reflection)
|
10
11
|
@owner, @reflection = owner, reflection
|
11
|
-
|
12
|
+
Array(reflection.options[:extend]).each { |ext| proxy_extend(ext) }
|
12
13
|
reset
|
13
14
|
end
|
14
15
|
|
16
|
+
def proxy_owner
|
17
|
+
@owner
|
18
|
+
end
|
19
|
+
|
20
|
+
def proxy_reflection
|
21
|
+
@reflection
|
22
|
+
end
|
23
|
+
|
24
|
+
def proxy_target
|
25
|
+
@target
|
26
|
+
end
|
27
|
+
|
15
28
|
def respond_to?(symbol, include_priv = false)
|
16
29
|
proxy_respond_to?(symbol, include_priv) || (load_target && @target.respond_to?(symbol, include_priv))
|
17
30
|
end
|
@@ -28,7 +41,7 @@ module ActiveRecord
|
|
28
41
|
end
|
29
42
|
|
30
43
|
def conditions
|
31
|
-
@conditions ||=
|
44
|
+
@conditions ||= interpolate_sql(sanitize_sql(@reflection.options[:conditions])) if @reflection.options[:conditions]
|
32
45
|
end
|
33
46
|
alias :sql_conditions :conditions
|
34
47
|
|
@@ -106,21 +119,22 @@ module ActiveRecord
|
|
106
119
|
|
107
120
|
private
|
108
121
|
def method_missing(method, *args, &block)
|
109
|
-
load_target
|
110
|
-
|
122
|
+
if load_target
|
123
|
+
@target.send(method, *args, &block)
|
124
|
+
end
|
111
125
|
end
|
112
126
|
|
113
127
|
def load_target
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
reset
|
119
|
-
end
|
128
|
+
return nil unless defined?(@loaded)
|
129
|
+
|
130
|
+
if !loaded? and (!@owner.new_record? || foreign_key_present)
|
131
|
+
@target = find_target
|
120
132
|
end
|
121
133
|
|
122
|
-
loaded
|
123
|
-
target
|
134
|
+
@loaded = true
|
135
|
+
@target
|
136
|
+
rescue ActiveRecord::RecordNotFound
|
137
|
+
reset
|
124
138
|
end
|
125
139
|
|
126
140
|
# Can be overwritten by associations that might have the foreign key available for an association without
|
@@ -134,6 +148,11 @@ module ActiveRecord
|
|
134
148
|
raise ActiveRecord::AssociationTypeMismatch, "#{@reflection.class_name} expected, got #{record.class}"
|
135
149
|
end
|
136
150
|
end
|
151
|
+
|
152
|
+
# Array#flatten has problems with recursive arrays. Going one level deeper solves the majority of the problems.
|
153
|
+
def flatten_deeper(array)
|
154
|
+
array.collect { |element| element.respond_to?(:flatten) ? element.flatten : element }.flatten
|
155
|
+
end
|
137
156
|
end
|
138
157
|
end
|
139
158
|
end
|
@@ -13,6 +13,17 @@ module ActiveRecord
|
|
13
13
|
record
|
14
14
|
end
|
15
15
|
|
16
|
+
def create(attributes = {})
|
17
|
+
# Can't use Base.create since the foreign key may be a protected attribute.
|
18
|
+
if attributes.is_a?(Array)
|
19
|
+
attributes.collect { |attr| create(attr) }
|
20
|
+
else
|
21
|
+
record = build(attributes)
|
22
|
+
insert_record(record) unless @owner.new_record?
|
23
|
+
record
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
16
27
|
def find_first
|
17
28
|
load_target.first
|
18
29
|
end
|
@@ -56,7 +67,9 @@ module ActiveRecord
|
|
56
67
|
@reflection.klass.find(*args)
|
57
68
|
end
|
58
69
|
end
|
59
|
-
|
70
|
+
|
71
|
+
# Deprecated as of Rails 1.2. If your associations require attributes
|
72
|
+
# you should be using has_many :through
|
60
73
|
def push_with_attributes(record, join_attributes = {})
|
61
74
|
raise_on_type_mismatch(record)
|
62
75
|
join_attributes.each { |key, value| record[key.to_s] = value }
|
@@ -68,13 +81,10 @@ module ActiveRecord
|
|
68
81
|
|
69
82
|
self
|
70
83
|
end
|
71
|
-
|
84
|
+
deprecate :push_with_attributes => "consider using has_many :through instead"
|
85
|
+
|
72
86
|
alias :concat_with_attributes :push_with_attributes
|
73
87
|
|
74
|
-
def size
|
75
|
-
@reflection.options[:uniq] ? count_records : super
|
76
|
-
end
|
77
|
-
|
78
88
|
protected
|
79
89
|
def method_missing(method, *args, &block)
|
80
90
|
if @target.respond_to?(method) || (!@reflection.klass.respond_to?(method) && Class.respond_to?(method))
|
@@ -85,17 +95,7 @@ module ActiveRecord
|
|
85
95
|
end
|
86
96
|
end
|
87
97
|
end
|
88
|
-
|
89
|
-
def find_target
|
90
|
-
if @reflection.options[:finder_sql]
|
91
|
-
records = @reflection.klass.find_by_sql(@finder_sql)
|
92
|
-
else
|
93
|
-
records = find(:all)
|
94
|
-
end
|
95
|
-
|
96
|
-
@reflection.options[:uniq] ? uniq(records) : records
|
97
|
-
end
|
98
|
-
|
98
|
+
|
99
99
|
def count_records
|
100
100
|
load_target.size
|
101
101
|
end
|
@@ -118,7 +118,7 @@ module ActiveRecord
|
|
118
118
|
attributes[column.name] = record.quoted_id
|
119
119
|
else
|
120
120
|
if record.attributes.has_key?(column.name)
|
121
|
-
value = @owner.send(:
|
121
|
+
value = @owner.send(:quote_value, record[column.name], column)
|
122
122
|
attributes[column.name] = value unless value.nil?
|
123
123
|
end
|
124
124
|
end
|
@@ -10,10 +10,12 @@ module ActiveRecord
|
|
10
10
|
if attributes.is_a?(Array)
|
11
11
|
attributes.collect { |attr| build(attr) }
|
12
12
|
else
|
13
|
-
load_target
|
14
13
|
record = @reflection.klass.new(attributes)
|
15
14
|
set_belongs_to_association_for(record)
|
15
|
+
|
16
|
+
@target ||= [] unless loaded?
|
16
17
|
@target << record
|
18
|
+
|
17
19
|
record
|
18
20
|
end
|
19
21
|
end
|
@@ -29,22 +31,28 @@ module ActiveRecord
|
|
29
31
|
@reflection.klass.find_all(conditions, orderings, limit, joins)
|
30
32
|
end
|
31
33
|
end
|
34
|
+
deprecate :find_all => "use find(:all, ...) instead"
|
32
35
|
|
33
36
|
# DEPRECATED. Find the first associated record. All arguments are optional.
|
34
37
|
def find_first(conditions = nil, orderings = nil)
|
35
38
|
find_all(conditions, orderings, 1).first
|
36
39
|
end
|
40
|
+
deprecate :find_first => "use find(:first, ...) instead"
|
37
41
|
|
38
42
|
# Count the number of associated records. All arguments are optional.
|
39
|
-
def count(
|
43
|
+
def count(*args)
|
40
44
|
if @reflection.options[:counter_sql]
|
41
45
|
@reflection.klass.count_by_sql(@counter_sql)
|
42
46
|
elsif @reflection.options[:finder_sql]
|
43
47
|
@reflection.klass.count_by_sql(@finder_sql)
|
44
48
|
else
|
45
|
-
|
46
|
-
|
47
|
-
|
49
|
+
column_name, options = @reflection.klass.send(:construct_count_options_from_legacy_args, *args)
|
50
|
+
options[:conditions] = options[:conditions].nil? ?
|
51
|
+
@finder_sql :
|
52
|
+
@finder_sql + " AND (#{sanitize_sql(options[:conditions])})"
|
53
|
+
options[:include] = @reflection.options[:include]
|
54
|
+
|
55
|
+
@reflection.klass.count(column_name, options)
|
48
56
|
end
|
49
57
|
end
|
50
58
|
|
@@ -83,33 +91,45 @@ module ActiveRecord
|
|
83
91
|
@reflection.klass.find(*args)
|
84
92
|
end
|
85
93
|
end
|
86
|
-
|
94
|
+
|
87
95
|
protected
|
88
96
|
def method_missing(method, *args, &block)
|
89
97
|
if @target.respond_to?(method) || (!@reflection.klass.respond_to?(method) && Class.respond_to?(method))
|
90
98
|
super
|
91
99
|
else
|
100
|
+
create_scoping = {}
|
101
|
+
set_belongs_to_association_for(create_scoping)
|
102
|
+
|
92
103
|
@reflection.klass.with_scope(
|
104
|
+
:create => create_scoping,
|
93
105
|
:find => {
|
94
106
|
:conditions => @finder_sql,
|
95
107
|
:joins => @join_sql,
|
96
108
|
:readonly => false
|
97
|
-
},
|
98
|
-
:create => {
|
99
|
-
@reflection.primary_key_name => @owner.id
|
100
109
|
}
|
101
110
|
) do
|
102
111
|
@reflection.klass.send(method, *args, &block)
|
103
112
|
end
|
104
113
|
end
|
105
114
|
end
|
106
|
-
|
107
|
-
def
|
108
|
-
if
|
109
|
-
|
110
|
-
|
111
|
-
|
115
|
+
|
116
|
+
def load_target
|
117
|
+
if !@owner.new_record? || foreign_key_present
|
118
|
+
begin
|
119
|
+
if !loaded?
|
120
|
+
if @target.is_a?(Array) && @target.any?
|
121
|
+
@target = (find_target + @target).uniq
|
122
|
+
else
|
123
|
+
@target = find_target
|
124
|
+
end
|
125
|
+
end
|
126
|
+
rescue ActiveRecord::RecordNotFound
|
127
|
+
reset
|
128
|
+
end
|
112
129
|
end
|
130
|
+
|
131
|
+
loaded if target
|
132
|
+
target
|
113
133
|
end
|
114
134
|
|
115
135
|
def count_records
|
@@ -118,7 +138,7 @@ module ActiveRecord
|
|
118
138
|
elsif @reflection.options[:counter_sql]
|
119
139
|
@reflection.klass.count_by_sql(@counter_sql)
|
120
140
|
else
|
121
|
-
@reflection.klass.count(@counter_sql)
|
141
|
+
@reflection.klass.count(:conditions => @counter_sql)
|
122
142
|
end
|
123
143
|
|
124
144
|
@target = [] and loaded if count == 0
|
@@ -167,7 +187,7 @@ module ActiveRecord
|
|
167
187
|
when @reflection.options[:as]
|
168
188
|
@finder_sql =
|
169
189
|
"#{@reflection.klass.table_name}.#{@reflection.options[:as]}_id = #{@owner.quoted_id} AND " +
|
170
|
-
"#{@reflection.klass.table_name}.#{@reflection.options[:as]}_type = #{@owner.class.
|
190
|
+
"#{@reflection.klass.table_name}.#{@reflection.options[:as]}_type = #{@owner.class.quote_value(@owner.class.base_class.name.to_s)}"
|
171
191
|
@finder_sql << " AND (#{conditions})" if conditions
|
172
192
|
|
173
193
|
else
|
@@ -8,7 +8,6 @@ module ActiveRecord
|
|
8
8
|
construct_sql
|
9
9
|
end
|
10
10
|
|
11
|
-
|
12
11
|
def find(*args)
|
13
12
|
options = Base.send(:extract_options_from_args!, args)
|
14
13
|
|
@@ -23,12 +22,12 @@ module ActiveRecord
|
|
23
22
|
elsif @reflection.options[:order]
|
24
23
|
options[:order] = @reflection.options[:order]
|
25
24
|
end
|
26
|
-
|
25
|
+
|
27
26
|
options[:select] = construct_select(options[:select])
|
28
27
|
options[:from] ||= construct_from
|
29
28
|
options[:joins] = construct_joins(options[:joins])
|
30
29
|
options[:include] = @reflection.source_reflection.options[:include] if options[:include].nil?
|
31
|
-
|
30
|
+
|
32
31
|
merge_options_from_reflection!(options)
|
33
32
|
|
34
33
|
# Pass through args exactly as we received them.
|
@@ -41,6 +40,68 @@ module ActiveRecord
|
|
41
40
|
@loaded = false
|
42
41
|
end
|
43
42
|
|
43
|
+
# Adds records to the association. The source record and its associates
|
44
|
+
# must have ids in order to create records associating them, so this
|
45
|
+
# will raise ActiveRecord::HasManyThroughCantAssociateNewRecords if
|
46
|
+
# either is a new record. Calls create! so you can rescue errors.
|
47
|
+
#
|
48
|
+
# The :before_add and :after_add callbacks are not yet supported.
|
49
|
+
def <<(*records)
|
50
|
+
return if records.empty?
|
51
|
+
through = @reflection.through_reflection
|
52
|
+
raise ActiveRecord::HasManyThroughCantAssociateNewRecords.new(@owner, through) if @owner.new_record?
|
53
|
+
|
54
|
+
load_target
|
55
|
+
|
56
|
+
klass = through.klass
|
57
|
+
klass.transaction do
|
58
|
+
flatten_deeper(records).each do |associate|
|
59
|
+
raise_on_type_mismatch(associate)
|
60
|
+
raise ActiveRecord::HasManyThroughCantAssociateNewRecords.new(@owner, through) unless associate.respond_to?(:new_record?) && !associate.new_record?
|
61
|
+
|
62
|
+
@owner.send(@reflection.through_reflection.name).proxy_target << klass.with_scope(:create => construct_join_attributes(associate)) { klass.create! }
|
63
|
+
@target << associate
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
self
|
68
|
+
end
|
69
|
+
|
70
|
+
[:push, :concat].each { |method| alias_method method, :<< }
|
71
|
+
|
72
|
+
# Remove +records+ from this association. Does not destroy +records+.
|
73
|
+
def delete(*records)
|
74
|
+
records = flatten_deeper(records)
|
75
|
+
records.each { |associate| raise_on_type_mismatch(associate) }
|
76
|
+
records.reject! { |associate| @target.delete(associate) if associate.new_record? }
|
77
|
+
return if records.empty?
|
78
|
+
|
79
|
+
@delete_join_finder ||= "find_all_by_#{@reflection.source_reflection.association_foreign_key}"
|
80
|
+
through = @reflection.through_reflection
|
81
|
+
through.klass.transaction do
|
82
|
+
records.each do |associate|
|
83
|
+
joins = @owner.send(through.name).send(@delete_join_finder, associate.id)
|
84
|
+
@owner.send(through.name).delete(joins)
|
85
|
+
@target.delete(associate)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
def build(attrs = nil)
|
91
|
+
raise ActiveRecord::HasManyThroughCantAssociateNewRecords.new(@owner, @reflection.through_reflection)
|
92
|
+
end
|
93
|
+
|
94
|
+
def create!(attrs = nil)
|
95
|
+
@reflection.klass.transaction do
|
96
|
+
self << @reflection.klass.with_scope(:create => attrs) { @reflection.klass.create! }
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
# Calculate sum using SQL, not Enumerable
|
101
|
+
def sum(*args, &block)
|
102
|
+
calculate(:sum, *args, &block)
|
103
|
+
end
|
104
|
+
|
44
105
|
protected
|
45
106
|
def method_missing(method, *args, &block)
|
46
107
|
if @target.respond_to?(method) || (!@reflection.klass.respond_to?(method) && Class.respond_to?(method))
|
@@ -49,41 +110,68 @@ module ActiveRecord
|
|
49
110
|
@reflection.klass.with_scope(construct_scope) { @reflection.klass.send(method, *args, &block) }
|
50
111
|
end
|
51
112
|
end
|
52
|
-
|
113
|
+
|
53
114
|
def find_target
|
54
|
-
@reflection.klass.find(:all,
|
115
|
+
records = @reflection.klass.find(:all,
|
55
116
|
:select => construct_select,
|
56
117
|
:conditions => construct_conditions,
|
57
118
|
:from => construct_from,
|
58
119
|
:joins => construct_joins,
|
59
|
-
:order => @reflection.options[:order],
|
120
|
+
:order => @reflection.options[:order],
|
60
121
|
:limit => @reflection.options[:limit],
|
61
122
|
:group => @reflection.options[:group],
|
62
123
|
:include => @reflection.options[:include] || @reflection.source_reflection.options[:include]
|
63
124
|
)
|
125
|
+
|
126
|
+
@reflection.options[:uniq] ? records.to_set.to_a : records
|
64
127
|
end
|
65
128
|
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
129
|
+
# Construct attributes for associate pointing to owner.
|
130
|
+
def construct_owner_attributes(reflection)
|
131
|
+
if as = reflection.options[:as]
|
132
|
+
{ "#{as}_id" => @owner.id,
|
133
|
+
"#{as}_type" => @owner.class.base_class.name.to_s }
|
70
134
|
else
|
71
|
-
|
135
|
+
{ reflection.primary_key_name => @owner.id }
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
# Construct attributes for :through pointing to owner and associate.
|
140
|
+
def construct_join_attributes(associate)
|
141
|
+
construct_owner_attributes(@reflection.through_reflection).merge(@reflection.source_reflection.association_foreign_key => associate.id)
|
142
|
+
end
|
143
|
+
|
144
|
+
# Associate attributes pointing to owner, quoted.
|
145
|
+
def construct_quoted_owner_attributes(reflection)
|
146
|
+
if as = reflection.options[:as]
|
147
|
+
{ "#{as}_id" => @owner.quoted_id,
|
148
|
+
"#{as}_type" => reflection.klass.quote_value(
|
149
|
+
@owner.class.base_class.name.to_s,
|
150
|
+
reflection.klass.columns_hash["#{as}_type"]) }
|
151
|
+
else
|
152
|
+
{ reflection.primary_key_name => @owner.quoted_id }
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
# Build SQL conditions from attributes, qualified by table name.
|
157
|
+
def construct_conditions
|
158
|
+
table_name = @reflection.through_reflection.table_name
|
159
|
+
conditions = construct_quoted_owner_attributes(@reflection.through_reflection).map do |attr, value|
|
160
|
+
"#{table_name}.#{attr} = #{value}"
|
72
161
|
end
|
73
|
-
conditions <<
|
74
|
-
|
75
|
-
return conditions
|
162
|
+
conditions << sql_conditions if sql_conditions
|
163
|
+
"(" + conditions.join(') AND (') + ")"
|
76
164
|
end
|
77
165
|
|
78
166
|
def construct_from
|
79
167
|
@reflection.table_name
|
80
168
|
end
|
81
|
-
|
169
|
+
|
82
170
|
def construct_select(custom_select = nil)
|
83
|
-
selected = custom_select || @reflection.options[:select] || "#{@reflection.table_name}.*"
|
171
|
+
selected = custom_select || @reflection.options[:select] || "#{@reflection.table_name}.*"
|
84
172
|
end
|
85
|
-
|
86
|
-
def construct_joins(custom_joins = nil)
|
173
|
+
|
174
|
+
def construct_joins(custom_joins = nil)
|
87
175
|
polymorphic_join = nil
|
88
176
|
if @reflection.through_reflection.options[:as] || @reflection.source_reflection.macro == :belongs_to
|
89
177
|
reflection_primary_key = @reflection.klass.primary_key
|
@@ -94,7 +182,7 @@ module ActiveRecord
|
|
94
182
|
if @reflection.source_reflection.options[:as]
|
95
183
|
polymorphic_join = "AND %s.%s = %s" % [
|
96
184
|
@reflection.table_name, "#{@reflection.source_reflection.options[:as]}_type",
|
97
|
-
@owner.class.
|
185
|
+
@owner.class.quote_value(@reflection.through_reflection.klass.name)
|
98
186
|
]
|
99
187
|
end
|
100
188
|
end
|
@@ -106,14 +194,15 @@ module ActiveRecord
|
|
106
194
|
polymorphic_join
|
107
195
|
]
|
108
196
|
end
|
109
|
-
|
197
|
+
|
110
198
|
def construct_scope
|
111
|
-
{
|
112
|
-
:find => { :from
|
113
|
-
|
114
|
-
|
199
|
+
{ :create => construct_owner_attributes(@reflection),
|
200
|
+
:find => { :from => construct_from,
|
201
|
+
:conditions => construct_conditions,
|
202
|
+
:joins => construct_joins,
|
203
|
+
:select => construct_select } }
|
115
204
|
end
|
116
|
-
|
205
|
+
|
117
206
|
def construct_sql
|
118
207
|
case
|
119
208
|
when @reflection.options[:finder_sql]
|
@@ -133,14 +222,15 @@ module ActiveRecord
|
|
133
222
|
@counter_sql = @finder_sql
|
134
223
|
end
|
135
224
|
end
|
136
|
-
|
225
|
+
|
137
226
|
def conditions
|
138
227
|
@conditions ||= [
|
139
|
-
(interpolate_sql(@reflection.
|
140
|
-
(interpolate_sql(@reflection.active_record.send(:sanitize_sql, @reflection.through_reflection.options[:conditions])) if @reflection.through_reflection.options[:conditions])
|
141
|
-
|
228
|
+
(interpolate_sql(@reflection.klass.send(:sanitize_sql, @reflection.options[:conditions])) if @reflection.options[:conditions]),
|
229
|
+
(interpolate_sql(@reflection.active_record.send(:sanitize_sql, @reflection.through_reflection.options[:conditions])) if @reflection.through_reflection.options[:conditions]),
|
230
|
+
("#{@reflection.through_reflection.table_name}.#{@reflection.through_reflection.klass.inheritance_column} = #{@reflection.klass.quote_value(@reflection.through_reflection.klass.name.demodulize)}" unless @reflection.through_reflection.klass.descends_from_active_record?)
|
231
|
+
].compact.collect { |condition| "(#{condition})" }.join(' AND ') unless (!@reflection.options[:conditions] && !@reflection.through_reflection.options[:conditions] && @reflection.through_reflection.klass.descends_from_active_record?)
|
142
232
|
end
|
143
|
-
|
233
|
+
|
144
234
|
alias_method :sql_conditions, :conditions
|
145
235
|
end
|
146
236
|
end
|