activerecord 3.0.0 → 4.0.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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +2102 -0
- data/MIT-LICENSE +20 -0
- data/README.rdoc +35 -44
- data/examples/performance.rb +110 -100
- data/lib/active_record/aggregations.rb +59 -75
- data/lib/active_record/associations/alias_tracker.rb +76 -0
- data/lib/active_record/associations/association.rb +248 -0
- data/lib/active_record/associations/association_scope.rb +135 -0
- data/lib/active_record/associations/belongs_to_association.rb +60 -59
- data/lib/active_record/associations/belongs_to_polymorphic_association.rb +16 -59
- data/lib/active_record/associations/builder/association.rb +108 -0
- data/lib/active_record/associations/builder/belongs_to.rb +98 -0
- data/lib/active_record/associations/builder/collection_association.rb +89 -0
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +39 -0
- data/lib/active_record/associations/builder/has_many.rb +15 -0
- data/lib/active_record/associations/builder/has_one.rb +25 -0
- data/lib/active_record/associations/builder/singular_association.rb +32 -0
- data/lib/active_record/associations/collection_association.rb +608 -0
- data/lib/active_record/associations/collection_proxy.rb +986 -0
- data/lib/active_record/associations/has_and_belongs_to_many_association.rb +40 -112
- data/lib/active_record/associations/has_many_association.rb +83 -76
- data/lib/active_record/associations/has_many_through_association.rb +147 -66
- data/lib/active_record/associations/has_one_association.rb +67 -108
- data/lib/active_record/associations/has_one_through_association.rb +21 -25
- data/lib/active_record/associations/join_dependency/join_association.rb +174 -0
- data/lib/active_record/associations/join_dependency/join_base.rb +24 -0
- data/lib/active_record/associations/join_dependency/join_part.rb +78 -0
- data/lib/active_record/associations/join_dependency.rb +235 -0
- data/lib/active_record/associations/join_helper.rb +45 -0
- data/lib/active_record/associations/preloader/association.rb +121 -0
- data/lib/active_record/associations/preloader/belongs_to.rb +17 -0
- data/lib/active_record/associations/preloader/collection_association.rb +24 -0
- data/lib/active_record/associations/preloader/has_and_belongs_to_many.rb +60 -0
- data/lib/active_record/associations/preloader/has_many.rb +17 -0
- data/lib/active_record/associations/preloader/has_many_through.rb +19 -0
- data/lib/active_record/associations/preloader/has_one.rb +23 -0
- data/lib/active_record/associations/preloader/has_one_through.rb +9 -0
- data/lib/active_record/associations/preloader/singular_association.rb +21 -0
- data/lib/active_record/associations/preloader/through_association.rb +63 -0
- data/lib/active_record/associations/preloader.rb +178 -0
- data/lib/active_record/associations/singular_association.rb +64 -0
- data/lib/active_record/associations/through_association.rb +87 -0
- data/lib/active_record/associations.rb +512 -1224
- data/lib/active_record/attribute_assignment.rb +201 -0
- data/lib/active_record/attribute_methods/before_type_cast.rb +49 -12
- data/lib/active_record/attribute_methods/dirty.rb +51 -28
- data/lib/active_record/attribute_methods/primary_key.rb +94 -22
- data/lib/active_record/attribute_methods/query.rb +5 -4
- data/lib/active_record/attribute_methods/read.rb +63 -72
- data/lib/active_record/attribute_methods/serialization.rb +162 -0
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +39 -41
- data/lib/active_record/attribute_methods/write.rb +39 -13
- data/lib/active_record/attribute_methods.rb +362 -29
- data/lib/active_record/autosave_association.rb +132 -75
- data/lib/active_record/base.rb +83 -1627
- data/lib/active_record/callbacks.rb +69 -47
- data/lib/active_record/coders/yaml_column.rb +38 -0
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +411 -138
- data/lib/active_record/connection_adapters/abstract/database_limits.rb +21 -11
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +234 -173
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +36 -22
- data/lib/active_record/connection_adapters/abstract/quoting.rb +82 -25
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +176 -414
- data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +70 -0
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +562 -232
- data/lib/active_record/connection_adapters/abstract/transaction.rb +203 -0
- data/lib/active_record/connection_adapters/abstract_adapter.rb +281 -53
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +782 -0
- data/lib/active_record/connection_adapters/column.rb +318 -0
- data/lib/active_record/connection_adapters/connection_specification.rb +96 -0
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +273 -0
- data/lib/active_record/connection_adapters/mysql_adapter.rb +365 -450
- data/lib/active_record/connection_adapters/postgresql/array_parser.rb +97 -0
- data/lib/active_record/connection_adapters/postgresql/cast.rb +152 -0
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +242 -0
- data/lib/active_record/connection_adapters/postgresql/oid.rb +366 -0
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +171 -0
- data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +30 -0
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +489 -0
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +672 -752
- data/lib/active_record/connection_adapters/schema_cache.rb +129 -0
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +588 -17
- data/lib/active_record/connection_adapters/statement_pool.rb +40 -0
- data/lib/active_record/connection_handling.rb +98 -0
- data/lib/active_record/core.rb +463 -0
- data/lib/active_record/counter_cache.rb +108 -101
- data/lib/active_record/dynamic_matchers.rb +131 -0
- data/lib/active_record/errors.rb +54 -13
- data/lib/active_record/explain.rb +38 -0
- data/lib/active_record/explain_registry.rb +30 -0
- data/lib/active_record/explain_subscriber.rb +29 -0
- data/lib/active_record/fixture_set/file.rb +55 -0
- data/lib/active_record/fixtures.rb +703 -785
- data/lib/active_record/inheritance.rb +200 -0
- data/lib/active_record/integration.rb +60 -0
- data/lib/active_record/locale/en.yml +8 -1
- data/lib/active_record/locking/optimistic.rb +69 -60
- data/lib/active_record/locking/pessimistic.rb +34 -12
- data/lib/active_record/log_subscriber.rb +40 -6
- data/lib/active_record/migration/command_recorder.rb +164 -0
- data/lib/active_record/migration/join_table.rb +15 -0
- data/lib/active_record/migration.rb +614 -216
- data/lib/active_record/model_schema.rb +345 -0
- data/lib/active_record/nested_attributes.rb +248 -119
- data/lib/active_record/null_relation.rb +65 -0
- data/lib/active_record/persistence.rb +275 -57
- data/lib/active_record/query_cache.rb +29 -9
- data/lib/active_record/querying.rb +62 -0
- data/lib/active_record/railtie.rb +135 -21
- data/lib/active_record/railties/console_sandbox.rb +5 -0
- data/lib/active_record/railties/controller_runtime.rb +17 -5
- data/lib/active_record/railties/databases.rake +249 -359
- data/lib/active_record/railties/jdbcmysql_error.rb +16 -0
- data/lib/active_record/readonly_attributes.rb +30 -0
- data/lib/active_record/reflection.rb +283 -103
- data/lib/active_record/relation/batches.rb +38 -34
- data/lib/active_record/relation/calculations.rb +252 -139
- data/lib/active_record/relation/delegation.rb +125 -0
- data/lib/active_record/relation/finder_methods.rb +182 -188
- data/lib/active_record/relation/merger.rb +161 -0
- data/lib/active_record/relation/predicate_builder.rb +86 -21
- data/lib/active_record/relation/query_methods.rb +917 -134
- data/lib/active_record/relation/spawn_methods.rb +53 -92
- data/lib/active_record/relation.rb +405 -143
- data/lib/active_record/result.rb +67 -0
- data/lib/active_record/runtime_registry.rb +17 -0
- data/lib/active_record/sanitization.rb +168 -0
- data/lib/active_record/schema.rb +20 -14
- data/lib/active_record/schema_dumper.rb +55 -46
- data/lib/active_record/schema_migration.rb +39 -0
- data/lib/active_record/scoping/default.rb +146 -0
- data/lib/active_record/scoping/named.rb +175 -0
- data/lib/active_record/scoping.rb +82 -0
- data/lib/active_record/serialization.rb +8 -46
- data/lib/active_record/serializers/xml_serializer.rb +21 -68
- data/lib/active_record/statement_cache.rb +26 -0
- data/lib/active_record/store.rb +156 -0
- data/lib/active_record/tasks/database_tasks.rb +203 -0
- data/lib/active_record/tasks/firebird_database_tasks.rb +56 -0
- data/lib/active_record/tasks/mysql_database_tasks.rb +143 -0
- data/lib/active_record/tasks/oracle_database_tasks.rb +45 -0
- data/lib/active_record/tasks/postgresql_database_tasks.rb +90 -0
- data/lib/active_record/tasks/sqlite_database_tasks.rb +51 -0
- data/lib/active_record/tasks/sqlserver_database_tasks.rb +48 -0
- data/lib/active_record/test_case.rb +57 -28
- data/lib/active_record/timestamp.rb +49 -18
- data/lib/active_record/transactions.rb +106 -63
- data/lib/active_record/translation.rb +22 -0
- data/lib/active_record/validations/associated.rb +25 -24
- data/lib/active_record/validations/presence.rb +65 -0
- data/lib/active_record/validations/uniqueness.rb +123 -83
- data/lib/active_record/validations.rb +29 -29
- data/lib/active_record/version.rb +7 -5
- data/lib/active_record.rb +83 -34
- data/lib/rails/generators/active_record/migration/migration_generator.rb +46 -9
- data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb +19 -0
- data/lib/rails/generators/active_record/migration/templates/migration.rb +30 -8
- data/lib/rails/generators/active_record/model/model_generator.rb +15 -5
- data/lib/rails/generators/active_record/model/templates/model.rb +7 -2
- data/lib/rails/generators/active_record/model/templates/module.rb +3 -1
- data/lib/rails/generators/active_record.rb +4 -8
- metadata +163 -121
- data/CHANGELOG +0 -6023
- data/examples/associations.png +0 -0
- data/lib/active_record/association_preload.rb +0 -403
- data/lib/active_record/associations/association_collection.rb +0 -562
- data/lib/active_record/associations/association_proxy.rb +0 -295
- data/lib/active_record/associations/through_association_scope.rb +0 -154
- data/lib/active_record/connection_adapters/abstract/connection_specification.rb +0 -113
- data/lib/active_record/connection_adapters/sqlite_adapter.rb +0 -401
- data/lib/active_record/dynamic_finder_match.rb +0 -53
- data/lib/active_record/dynamic_scope_match.rb +0 -32
- data/lib/active_record/named_scope.rb +0 -138
- data/lib/active_record/observer.rb +0 -140
- data/lib/active_record/session_store.rb +0 -340
- data/lib/rails/generators/active_record/model/templates/migration.rb +0 -16
- data/lib/rails/generators/active_record/observer/observer_generator.rb +0 -15
- data/lib/rails/generators/active_record/observer/templates/observer.rb +0 -2
- data/lib/rails/generators/active_record/session_migration/session_migration_generator.rb +0 -24
- data/lib/rails/generators/active_record/session_migration/templates/migration.rb +0 -16
@@ -1,142 +1,101 @@
|
|
1
|
+
|
1
2
|
module ActiveRecord
|
2
3
|
# = Active Record Belongs To Has One Association
|
3
4
|
module Associations
|
4
|
-
class HasOneAssociation <
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
5
|
+
class HasOneAssociation < SingularAssociation #:nodoc:
|
6
|
+
|
7
|
+
def handle_dependency
|
8
|
+
case options[:dependent]
|
9
|
+
when :restrict, :restrict_with_exception
|
10
|
+
raise ActiveRecord::DeleteRestrictionError.new(reflection.name) if load_target
|
11
|
+
|
12
|
+
when :restrict_with_error
|
13
|
+
if load_target
|
14
|
+
record = klass.human_attribute_name(reflection.name).downcase
|
15
|
+
owner.errors.add(:base, :"restrict_dependent_destroy.one", record: record)
|
16
|
+
false
|
17
|
+
end
|
9
18
|
|
10
|
-
|
11
|
-
|
12
|
-
attrs = merge_with_conditions(attrs)
|
13
|
-
reflection.create_association(attrs)
|
19
|
+
else
|
20
|
+
delete
|
14
21
|
end
|
15
22
|
end
|
16
23
|
|
17
|
-
def
|
18
|
-
|
19
|
-
|
20
|
-
reflection.create_association!(attrs)
|
21
|
-
end
|
22
|
-
end
|
24
|
+
def replace(record, save = true)
|
25
|
+
raise_on_type_mismatch!(record) if record
|
26
|
+
load_target
|
23
27
|
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
end
|
29
|
-
end
|
28
|
+
return self.target if !(target || record)
|
29
|
+
if (target != record) || record.changed?
|
30
|
+
transaction_if(save) do
|
31
|
+
remove_target!(options[:dependent]) if target && !target.destroyed?
|
30
32
|
|
31
|
-
|
32
|
-
|
33
|
+
if record
|
34
|
+
set_owner_attributes(record)
|
35
|
+
set_inverse_instance(record)
|
33
36
|
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
@owner.clear_association_cache
|
40
|
-
when :destroy
|
41
|
-
@target.destroy unless @target.new_record?
|
42
|
-
@owner.clear_association_cache
|
43
|
-
when :nullify
|
44
|
-
@target[@reflection.primary_key_name] = nil
|
45
|
-
@target.save unless @owner.new_record? || @target.new_record?
|
37
|
+
if owner.persisted? && save && !record.save
|
38
|
+
nullify_owner_attributes(record)
|
39
|
+
set_owner_attributes(target) if target
|
40
|
+
raise RecordNotSaved, "Failed to save the new associated #{reflection.name}."
|
41
|
+
end
|
46
42
|
end
|
47
|
-
else
|
48
|
-
@target[@reflection.primary_key_name] = nil
|
49
|
-
@target.save unless @owner.new_record? || @target.new_record?
|
50
43
|
end
|
51
44
|
end
|
52
45
|
|
53
|
-
|
54
|
-
@target = nil
|
55
|
-
else
|
56
|
-
raise_on_type_mismatch(obj)
|
57
|
-
set_belongs_to_association_for(obj)
|
58
|
-
@target = (AssociationProxy === obj ? obj.target : obj)
|
59
|
-
end
|
60
|
-
|
61
|
-
set_inverse_instance(obj, @owner)
|
62
|
-
@loaded = true
|
63
|
-
|
64
|
-
unless @owner.new_record? or obj.nil? or dont_save
|
65
|
-
return (obj.save ? self : false)
|
66
|
-
else
|
67
|
-
return (obj.nil? ? nil : self)
|
68
|
-
end
|
46
|
+
self.target = record
|
69
47
|
end
|
70
48
|
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
49
|
+
def delete(method = options[:dependent])
|
50
|
+
if load_target
|
51
|
+
case method
|
52
|
+
when :delete
|
53
|
+
target.delete
|
54
|
+
when :destroy
|
55
|
+
target.destroy
|
56
|
+
when :nullify
|
57
|
+
target.update_columns(reflection.foreign_key => nil)
|
77
58
|
end
|
78
59
|
end
|
60
|
+
end
|
79
61
|
|
80
62
|
private
|
81
|
-
def find_target
|
82
|
-
options = @reflection.options.dup
|
83
|
-
(options.keys - [:select, :order, :include, :readonly]).each do |key|
|
84
|
-
options.delete key
|
85
|
-
end
|
86
|
-
options[:conditions] = @finder_sql
|
87
63
|
|
88
|
-
|
89
|
-
|
90
|
-
|
64
|
+
# The reason that the save param for replace is false, if for create (not just build),
|
65
|
+
# is because the setting of the foreign keys is actually handled by the scoping when
|
66
|
+
# the record is instantiated, and so they are set straight away and do not need to be
|
67
|
+
# updated within replace.
|
68
|
+
def set_new_record(record)
|
69
|
+
replace(record, false)
|
91
70
|
end
|
92
71
|
|
93
|
-
def
|
94
|
-
case
|
95
|
-
when
|
96
|
-
|
97
|
-
|
98
|
-
|
72
|
+
def remove_target!(method)
|
73
|
+
case method
|
74
|
+
when :delete
|
75
|
+
target.delete
|
76
|
+
when :destroy
|
77
|
+
target.destroy
|
99
78
|
else
|
100
|
-
|
79
|
+
nullify_owner_attributes(target)
|
80
|
+
|
81
|
+
if target.persisted? && owner.persisted? && !target.save
|
82
|
+
set_owner_attributes(target)
|
83
|
+
raise RecordNotSaved, "Failed to remove the existing associated #{reflection.name}. " +
|
84
|
+
"The record failed to save after its foreign key was set to nil."
|
85
|
+
end
|
101
86
|
end
|
102
|
-
@finder_sql << " AND (#{conditions})" if conditions
|
103
87
|
end
|
104
88
|
|
105
|
-
def
|
106
|
-
|
107
|
-
set_belongs_to_association_for(create_scoping)
|
108
|
-
{ :create => create_scoping }
|
89
|
+
def nullify_owner_attributes(record)
|
90
|
+
record[reflection.foreign_key] = nil
|
109
91
|
end
|
110
92
|
|
111
|
-
def
|
112
|
-
|
113
|
-
|
114
|
-
# elsewhere, the instance we create will get orphaned.
|
115
|
-
load_target if replace_existing
|
116
|
-
record = @reflection.klass.send(:with_scope, :create => construct_scope[:create]) do
|
117
|
-
yield @reflection
|
118
|
-
end
|
119
|
-
|
120
|
-
if replace_existing
|
121
|
-
replace(record, true)
|
93
|
+
def transaction_if(value)
|
94
|
+
if value
|
95
|
+
reflection.klass.transaction { yield }
|
122
96
|
else
|
123
|
-
|
124
|
-
self.target = record
|
125
|
-
set_inverse_instance(record, @owner)
|
97
|
+
yield
|
126
98
|
end
|
127
|
-
|
128
|
-
record
|
129
|
-
end
|
130
|
-
|
131
|
-
def we_can_set_the_inverse_on_this?(record)
|
132
|
-
inverse = @reflection.inverse_of
|
133
|
-
return !inverse.nil?
|
134
|
-
end
|
135
|
-
|
136
|
-
def merge_with_conditions(attrs={})
|
137
|
-
attrs ||= {}
|
138
|
-
attrs.update(@reflection.options[:conditions]) if @reflection.options[:conditions].is_a?(Hash)
|
139
|
-
attrs
|
140
99
|
end
|
141
100
|
end
|
142
101
|
end
|
@@ -1,40 +1,36 @@
|
|
1
|
-
require "active_record/associations/through_association_scope"
|
2
|
-
|
3
1
|
module ActiveRecord
|
4
2
|
# = Active Record Has One Through Association
|
5
3
|
module Associations
|
6
|
-
class HasOneThroughAssociation < HasOneAssociation
|
7
|
-
include
|
4
|
+
class HasOneThroughAssociation < HasOneAssociation #:nodoc:
|
5
|
+
include ThroughAssociation
|
8
6
|
|
9
|
-
def replace(
|
10
|
-
create_through_record(
|
11
|
-
|
7
|
+
def replace(record)
|
8
|
+
create_through_record(record)
|
9
|
+
self.target = record
|
12
10
|
end
|
13
11
|
|
14
12
|
private
|
15
13
|
|
16
|
-
|
17
|
-
|
14
|
+
def create_through_record(record)
|
15
|
+
ensure_not_nested
|
16
|
+
|
17
|
+
through_proxy = owner.association(through_reflection.name)
|
18
|
+
through_record = through_proxy.send(:load_target)
|
18
19
|
|
19
|
-
|
20
|
+
if through_record && !record
|
21
|
+
through_record.destroy
|
22
|
+
elsif record
|
23
|
+
attributes = construct_join_attributes(record)
|
20
24
|
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
else
|
29
|
-
@owner.send(@reflection.through_reflection.name, klass.create(construct_join_attributes(new_value)))
|
25
|
+
if through_record
|
26
|
+
through_record.update(attributes)
|
27
|
+
elsif owner.new_record?
|
28
|
+
through_proxy.build(attributes)
|
29
|
+
else
|
30
|
+
through_proxy.create(attributes)
|
31
|
+
end
|
30
32
|
end
|
31
33
|
end
|
32
|
-
end
|
33
|
-
|
34
|
-
private
|
35
|
-
def find_target
|
36
|
-
with_scope(construct_scope) { @reflection.klass.find(:first) }
|
37
|
-
end
|
38
34
|
end
|
39
35
|
end
|
40
36
|
end
|
@@ -0,0 +1,174 @@
|
|
1
|
+
module ActiveRecord
|
2
|
+
module Associations
|
3
|
+
class JoinDependency # :nodoc:
|
4
|
+
class JoinAssociation < JoinPart # :nodoc:
|
5
|
+
include JoinHelper
|
6
|
+
|
7
|
+
# The reflection of the association represented
|
8
|
+
attr_reader :reflection
|
9
|
+
|
10
|
+
# The JoinDependency object which this JoinAssociation exists within. This is mainly
|
11
|
+
# relevant for generating aliases which do not conflict with other joins which are
|
12
|
+
# part of the query.
|
13
|
+
attr_reader :join_dependency
|
14
|
+
|
15
|
+
# A JoinBase instance representing the active record we are joining onto.
|
16
|
+
# (So in Author.has_many :posts, the Author would be that base record.)
|
17
|
+
attr_reader :parent
|
18
|
+
|
19
|
+
# What type of join will be generated, either Arel::InnerJoin (default) or Arel::OuterJoin
|
20
|
+
attr_accessor :join_type
|
21
|
+
|
22
|
+
# These implement abstract methods from the superclass
|
23
|
+
attr_reader :aliased_prefix
|
24
|
+
|
25
|
+
attr_reader :tables
|
26
|
+
|
27
|
+
delegate :options, :through_reflection, :source_reflection, :chain, :to => :reflection
|
28
|
+
delegate :table, :table_name, :to => :parent, :prefix => :parent
|
29
|
+
delegate :alias_tracker, :to => :join_dependency
|
30
|
+
|
31
|
+
alias :alias_suffix :parent_table_name
|
32
|
+
|
33
|
+
def initialize(reflection, join_dependency, parent = nil)
|
34
|
+
reflection.check_validity!
|
35
|
+
|
36
|
+
if reflection.options[:polymorphic]
|
37
|
+
raise EagerLoadPolymorphicError.new(reflection)
|
38
|
+
end
|
39
|
+
|
40
|
+
super(reflection.klass)
|
41
|
+
|
42
|
+
@reflection = reflection
|
43
|
+
@join_dependency = join_dependency
|
44
|
+
@parent = parent
|
45
|
+
@join_type = Arel::InnerJoin
|
46
|
+
@aliased_prefix = "t#{ join_dependency.join_parts.size }"
|
47
|
+
@tables = construct_tables.reverse
|
48
|
+
end
|
49
|
+
|
50
|
+
def ==(other)
|
51
|
+
other.class == self.class &&
|
52
|
+
other.reflection == reflection &&
|
53
|
+
other.parent == parent
|
54
|
+
end
|
55
|
+
|
56
|
+
def find_parent_in(other_join_dependency)
|
57
|
+
other_join_dependency.join_parts.detect do |join_part|
|
58
|
+
case parent
|
59
|
+
when JoinBase
|
60
|
+
parent.base_klass == join_part.base_klass
|
61
|
+
else
|
62
|
+
parent == join_part
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def join_to(manager)
|
68
|
+
tables = @tables.dup
|
69
|
+
foreign_table = parent_table
|
70
|
+
foreign_klass = parent.base_klass
|
71
|
+
|
72
|
+
# The chain starts with the target table, but we want to end with it here (makes
|
73
|
+
# more sense in this context), so we reverse
|
74
|
+
chain.reverse.each_with_index do |reflection, i|
|
75
|
+
table = tables.shift
|
76
|
+
|
77
|
+
case reflection.source_macro
|
78
|
+
when :belongs_to
|
79
|
+
key = reflection.association_primary_key
|
80
|
+
foreign_key = reflection.foreign_key
|
81
|
+
when :has_and_belongs_to_many
|
82
|
+
# Join the join table first...
|
83
|
+
manager.from(join(
|
84
|
+
table,
|
85
|
+
table[reflection.foreign_key].
|
86
|
+
eq(foreign_table[reflection.active_record_primary_key])
|
87
|
+
))
|
88
|
+
|
89
|
+
foreign_table, table = table, tables.shift
|
90
|
+
|
91
|
+
key = reflection.association_primary_key
|
92
|
+
foreign_key = reflection.association_foreign_key
|
93
|
+
else
|
94
|
+
key = reflection.foreign_key
|
95
|
+
foreign_key = reflection.active_record_primary_key
|
96
|
+
end
|
97
|
+
|
98
|
+
constraint = build_constraint(reflection, table, key, foreign_table, foreign_key)
|
99
|
+
|
100
|
+
scope_chain_items = scope_chain[i]
|
101
|
+
|
102
|
+
if reflection.type
|
103
|
+
scope_chain_items += [
|
104
|
+
ActiveRecord::Relation.new(reflection.klass, table)
|
105
|
+
.where(reflection.type => foreign_klass.base_class.name)
|
106
|
+
]
|
107
|
+
end
|
108
|
+
|
109
|
+
scope_chain_items.each do |item|
|
110
|
+
unless item.is_a?(Relation)
|
111
|
+
item = ActiveRecord::Relation.new(reflection.klass, table).instance_exec(self, &item)
|
112
|
+
end
|
113
|
+
|
114
|
+
constraint = constraint.and(item.arel.constraints) unless item.arel.constraints.empty?
|
115
|
+
end
|
116
|
+
|
117
|
+
manager.from(join(table, constraint))
|
118
|
+
|
119
|
+
# The current table in this iteration becomes the foreign table in the next
|
120
|
+
foreign_table, foreign_klass = table, reflection.klass
|
121
|
+
end
|
122
|
+
|
123
|
+
manager
|
124
|
+
end
|
125
|
+
|
126
|
+
# Builds equality condition.
|
127
|
+
#
|
128
|
+
# Example:
|
129
|
+
#
|
130
|
+
# class Physician < ActiveRecord::Base
|
131
|
+
# has_many :appointments
|
132
|
+
# end
|
133
|
+
#
|
134
|
+
# If I execute `Physician.joins(:appointments).to_a` then
|
135
|
+
# reflection #=> #<ActiveRecord::Reflection::AssociationReflection @macro=:has_many ...>
|
136
|
+
# table #=> #<Arel::Table @name="appointments" ...>
|
137
|
+
# key #=> physician_id
|
138
|
+
# foreign_table #=> #<Arel::Table @name="physicians" ...>
|
139
|
+
# foreign_key #=> id
|
140
|
+
#
|
141
|
+
def build_constraint(reflection, table, key, foreign_table, foreign_key)
|
142
|
+
constraint = table[key].eq(foreign_table[foreign_key])
|
143
|
+
|
144
|
+
if reflection.klass.finder_needs_type_condition?
|
145
|
+
constraint = table.create_and([
|
146
|
+
constraint,
|
147
|
+
reflection.klass.send(:type_condition, table)
|
148
|
+
])
|
149
|
+
end
|
150
|
+
|
151
|
+
constraint
|
152
|
+
end
|
153
|
+
|
154
|
+
def join_relation(joining_relation)
|
155
|
+
self.join_type = Arel::OuterJoin
|
156
|
+
joining_relation.joins(self)
|
157
|
+
end
|
158
|
+
|
159
|
+
def table
|
160
|
+
tables.last
|
161
|
+
end
|
162
|
+
|
163
|
+
def aliased_table_name
|
164
|
+
table.table_alias || table.name
|
165
|
+
end
|
166
|
+
|
167
|
+
def scope_chain
|
168
|
+
@scope_chain ||= reflection.scope_chain.reverse
|
169
|
+
end
|
170
|
+
|
171
|
+
end
|
172
|
+
end
|
173
|
+
end
|
174
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module ActiveRecord
|
2
|
+
module Associations
|
3
|
+
class JoinDependency # :nodoc:
|
4
|
+
class JoinBase < JoinPart # :nodoc:
|
5
|
+
def ==(other)
|
6
|
+
other.class == self.class &&
|
7
|
+
other.base_klass == base_klass
|
8
|
+
end
|
9
|
+
|
10
|
+
def aliased_prefix
|
11
|
+
"t0"
|
12
|
+
end
|
13
|
+
|
14
|
+
def table
|
15
|
+
Arel::Table.new(table_name, arel_engine)
|
16
|
+
end
|
17
|
+
|
18
|
+
def aliased_table_name
|
19
|
+
base_klass.table_name
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
module ActiveRecord
|
2
|
+
module Associations
|
3
|
+
class JoinDependency # :nodoc:
|
4
|
+
# A JoinPart represents a part of a JoinDependency. It is inherited
|
5
|
+
# by JoinBase and JoinAssociation. A JoinBase represents the Active Record which
|
6
|
+
# everything else is being joined onto. A JoinAssociation represents an association which
|
7
|
+
# is joining to the base. A JoinAssociation may result in more than one actual join
|
8
|
+
# operations (for example a has_and_belongs_to_many JoinAssociation would result in
|
9
|
+
# two; one for the join table and one for the target table).
|
10
|
+
class JoinPart # :nodoc:
|
11
|
+
# The Active Record class which this join part is associated 'about'; for a JoinBase
|
12
|
+
# this is the actual base model, for a JoinAssociation this is the target model of the
|
13
|
+
# association.
|
14
|
+
attr_reader :base_klass
|
15
|
+
|
16
|
+
delegate :table_name, :column_names, :primary_key, :reflections, :arel_engine, :to => :base_klass
|
17
|
+
|
18
|
+
def initialize(base_klass)
|
19
|
+
@base_klass = base_klass
|
20
|
+
@cached_record = {}
|
21
|
+
@column_names_with_alias = nil
|
22
|
+
end
|
23
|
+
|
24
|
+
def aliased_table
|
25
|
+
Arel::Nodes::TableAlias.new table, aliased_table_name
|
26
|
+
end
|
27
|
+
|
28
|
+
def ==(other)
|
29
|
+
raise NotImplementedError
|
30
|
+
end
|
31
|
+
|
32
|
+
# An Arel::Table for the active_record
|
33
|
+
def table
|
34
|
+
raise NotImplementedError
|
35
|
+
end
|
36
|
+
|
37
|
+
# The prefix to be used when aliasing columns in the active_record's table
|
38
|
+
def aliased_prefix
|
39
|
+
raise NotImplementedError
|
40
|
+
end
|
41
|
+
|
42
|
+
# The alias for the active_record's table
|
43
|
+
def aliased_table_name
|
44
|
+
raise NotImplementedError
|
45
|
+
end
|
46
|
+
|
47
|
+
# The alias for the primary key of the active_record's table
|
48
|
+
def aliased_primary_key
|
49
|
+
"#{aliased_prefix}_r0"
|
50
|
+
end
|
51
|
+
|
52
|
+
# An array of [column_name, alias] pairs for the table
|
53
|
+
def column_names_with_alias
|
54
|
+
unless @column_names_with_alias
|
55
|
+
@column_names_with_alias = []
|
56
|
+
|
57
|
+
([primary_key] + (column_names - [primary_key])).compact.each_with_index do |column_name, i|
|
58
|
+
@column_names_with_alias << [column_name, "#{aliased_prefix}_r#{i}"]
|
59
|
+
end
|
60
|
+
end
|
61
|
+
@column_names_with_alias
|
62
|
+
end
|
63
|
+
|
64
|
+
def extract_record(row)
|
65
|
+
Hash[column_names_with_alias.map{|cn, an| [cn, row[an]]}]
|
66
|
+
end
|
67
|
+
|
68
|
+
def record_id(row)
|
69
|
+
row[aliased_primary_key]
|
70
|
+
end
|
71
|
+
|
72
|
+
def instantiate(row)
|
73
|
+
@cached_record[record_id(row)] ||= base_klass.instantiate(extract_record(row))
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|