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,9 +1,7 @@
|
|
1
|
-
require 'active_support/core_ext/array/wrap'
|
2
1
|
require 'active_support/core_ext/enumerable'
|
3
|
-
require 'active_support/core_ext/module/delegation'
|
4
|
-
require 'active_support/core_ext/object/blank'
|
5
2
|
require 'active_support/core_ext/string/conversions'
|
6
3
|
require 'active_support/core_ext/module/remove_method'
|
4
|
+
require 'active_record/errors'
|
7
5
|
|
8
6
|
module ActiveRecord
|
9
7
|
class InverseOfAssociationNotFoundError < ActiveRecordError #:nodoc:
|
@@ -18,15 +16,27 @@ module ActiveRecord
|
|
18
16
|
end
|
19
17
|
end
|
20
18
|
|
21
|
-
class
|
19
|
+
class HasManyThroughAssociationPolymorphicSourceError < ActiveRecordError #:nodoc:
|
22
20
|
def initialize(owner_class_name, reflection, source_reflection)
|
23
|
-
super("Cannot have a has_many :through association '#{owner_class_name}##{reflection.name}' on the polymorphic object '#{source_reflection.class_name}##{source_reflection.name}'.")
|
21
|
+
super("Cannot have a has_many :through association '#{owner_class_name}##{reflection.name}' on the polymorphic object '#{source_reflection.class_name}##{source_reflection.name}' without 'source_type'. Try adding 'source_type: \"#{reflection.name.to_s.classify}\"' to 'has_many :through' definition.")
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
class HasManyThroughAssociationPolymorphicThroughError < ActiveRecordError #:nodoc:
|
26
|
+
def initialize(owner_class_name, reflection)
|
27
|
+
super("Cannot have a has_many :through association '#{owner_class_name}##{reflection.name}' which goes through the polymorphic association '#{owner_class_name}##{reflection.through_reflection.name}'.")
|
24
28
|
end
|
25
29
|
end
|
26
30
|
|
27
31
|
class HasManyThroughAssociationPointlessSourceTypeError < ActiveRecordError #:nodoc:
|
28
32
|
def initialize(owner_class_name, reflection, source_reflection)
|
29
|
-
super("Cannot have a has_many :through association '#{owner_class_name}##{reflection.name}' with a :source_type option if the '#{reflection.through_reflection.class_name}##{source_reflection.name}' is not polymorphic.
|
33
|
+
super("Cannot have a has_many :through association '#{owner_class_name}##{reflection.name}' with a :source_type option if the '#{reflection.through_reflection.class_name}##{source_reflection.name}' is not polymorphic. Try removing :source_type on your association.")
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
class HasOneThroughCantAssociateThroughCollection < ActiveRecordError #:nodoc:
|
38
|
+
def initialize(owner_class_name, reflection, through_reflection)
|
39
|
+
super("Cannot have a has_one :through association '#{owner_class_name}##{reflection.name}' where the :through association '#{owner_class_name}##{through_reflection.name}' is a collection. Specify a has_one or belongs_to association in the :through option instead.")
|
30
40
|
end
|
31
41
|
end
|
32
42
|
|
@@ -35,15 +45,7 @@ module ActiveRecord
|
|
35
45
|
through_reflection = reflection.through_reflection
|
36
46
|
source_reflection_names = reflection.source_reflection_names
|
37
47
|
source_associations = reflection.through_reflection.klass.reflect_on_all_associations.collect { |a| a.name.inspect }
|
38
|
-
super("Could not find the source association(s) #{source_reflection_names.collect{ |a| a.inspect }.to_sentence(:two_words_connector => ' or ', :last_word_connector => ', or ', :locale => :en)} in model #{through_reflection.klass}.
|
39
|
-
end
|
40
|
-
end
|
41
|
-
|
42
|
-
class HasManyThroughSourceAssociationMacroError < ActiveRecordError #:nodoc:
|
43
|
-
def initialize(reflection)
|
44
|
-
through_reflection = reflection.through_reflection
|
45
|
-
source_reflection = reflection.source_reflection
|
46
|
-
super("Invalid source reflection macro :#{source_reflection.macro}#{" :through" if source_reflection.options[:through]} for has_many #{reflection.name.inspect}, :through => #{through_reflection.name.inspect}. Use :source to specify the source reflection.")
|
48
|
+
super("Could not find the source association(s) #{source_reflection_names.collect{ |a| a.inspect }.to_sentence(:two_words_connector => ' or ', :last_word_connector => ', or ', :locale => :en)} in model #{through_reflection.klass}. Try 'has_many #{reflection.name.inspect}, :through => #{through_reflection.name.inspect}, :source => <name>'. Is it one of #{source_associations.to_sentence(:two_words_connector => ' or ', :last_word_connector => ', or ', :locale => :en)}?")
|
47
49
|
end
|
48
50
|
end
|
49
51
|
|
@@ -65,9 +67,9 @@ module ActiveRecord
|
|
65
67
|
end
|
66
68
|
end
|
67
69
|
|
68
|
-
class
|
69
|
-
def initialize(reflection)
|
70
|
-
super("
|
70
|
+
class HasManyThroughNestedAssociationsAreReadonly < ActiveRecordError #:nodoc:
|
71
|
+
def initialize(owner, reflection)
|
72
|
+
super("Cannot modify association '#{owner.class.name}##{reflection.name}' because it goes through more than one other association.")
|
71
73
|
end
|
72
74
|
end
|
73
75
|
|
@@ -85,7 +87,7 @@ module ActiveRecord
|
|
85
87
|
|
86
88
|
class ReadOnlyAssociation < ActiveRecordError #:nodoc:
|
87
89
|
def initialize(reflection)
|
88
|
-
super("Can not add to a has_many :through association.
|
90
|
+
super("Can not add to a has_many :through association. Try adding to #{reflection.through_reflection.name.inspect}.")
|
89
91
|
end
|
90
92
|
end
|
91
93
|
|
@@ -93,47 +95,81 @@ module ActiveRecord
|
|
93
95
|
# (has_many, has_one) when there is at least 1 child associated instance.
|
94
96
|
# ex: if @project.tasks.size > 0, DeleteRestrictionError will be raised when trying to destroy @project
|
95
97
|
class DeleteRestrictionError < ActiveRecordError #:nodoc:
|
96
|
-
def initialize(
|
97
|
-
super("Cannot delete record because of dependent #{
|
98
|
+
def initialize(name)
|
99
|
+
super("Cannot delete record because of dependent #{name}")
|
98
100
|
end
|
99
101
|
end
|
100
102
|
|
101
103
|
# See ActiveRecord::Associations::ClassMethods for documentation.
|
102
104
|
module Associations # :nodoc:
|
105
|
+
extend ActiveSupport::Autoload
|
103
106
|
extend ActiveSupport::Concern
|
104
107
|
|
105
108
|
# These classes will be loaded when associations are created.
|
106
109
|
# So there is no need to eager load them.
|
107
|
-
autoload :
|
108
|
-
autoload :
|
109
|
-
autoload :
|
110
|
+
autoload :Association, 'active_record/associations/association'
|
111
|
+
autoload :SingularAssociation, 'active_record/associations/singular_association'
|
112
|
+
autoload :CollectionAssociation, 'active_record/associations/collection_association'
|
113
|
+
autoload :CollectionProxy, 'active_record/associations/collection_proxy'
|
114
|
+
|
115
|
+
autoload :BelongsToAssociation, 'active_record/associations/belongs_to_association'
|
110
116
|
autoload :BelongsToPolymorphicAssociation, 'active_record/associations/belongs_to_polymorphic_association'
|
111
|
-
autoload :HasAndBelongsToManyAssociation,
|
112
|
-
autoload :HasManyAssociation,
|
113
|
-
autoload :HasManyThroughAssociation,
|
114
|
-
autoload :HasOneAssociation,
|
115
|
-
autoload :HasOneThroughAssociation,
|
117
|
+
autoload :HasAndBelongsToManyAssociation, 'active_record/associations/has_and_belongs_to_many_association'
|
118
|
+
autoload :HasManyAssociation, 'active_record/associations/has_many_association'
|
119
|
+
autoload :HasManyThroughAssociation, 'active_record/associations/has_many_through_association'
|
120
|
+
autoload :HasOneAssociation, 'active_record/associations/has_one_association'
|
121
|
+
autoload :HasOneThroughAssociation, 'active_record/associations/has_one_through_association'
|
122
|
+
autoload :ThroughAssociation, 'active_record/associations/through_association'
|
123
|
+
|
124
|
+
module Builder #:nodoc:
|
125
|
+
autoload :Association, 'active_record/associations/builder/association'
|
126
|
+
autoload :SingularAssociation, 'active_record/associations/builder/singular_association'
|
127
|
+
autoload :CollectionAssociation, 'active_record/associations/builder/collection_association'
|
128
|
+
|
129
|
+
autoload :BelongsTo, 'active_record/associations/builder/belongs_to'
|
130
|
+
autoload :HasOne, 'active_record/associations/builder/has_one'
|
131
|
+
autoload :HasMany, 'active_record/associations/builder/has_many'
|
132
|
+
autoload :HasAndBelongsToMany, 'active_record/associations/builder/has_and_belongs_to_many'
|
133
|
+
end
|
134
|
+
|
135
|
+
eager_autoload do
|
136
|
+
autoload :Preloader, 'active_record/associations/preloader'
|
137
|
+
autoload :JoinDependency, 'active_record/associations/join_dependency'
|
138
|
+
autoload :AssociationScope, 'active_record/associations/association_scope'
|
139
|
+
autoload :AliasTracker, 'active_record/associations/alias_tracker'
|
140
|
+
autoload :JoinHelper, 'active_record/associations/join_helper'
|
141
|
+
end
|
116
142
|
|
117
143
|
# Clears out the association cache.
|
118
144
|
def clear_association_cache #:nodoc:
|
119
|
-
|
120
|
-
|
121
|
-
|
145
|
+
@association_cache.clear if persisted?
|
146
|
+
end
|
147
|
+
|
148
|
+
# :nodoc:
|
149
|
+
attr_reader :association_cache
|
150
|
+
|
151
|
+
# Returns the association instance for the given name, instantiating it if it doesn't already exist
|
152
|
+
def association(name) #:nodoc:
|
153
|
+
association = association_instance_get(name)
|
154
|
+
|
155
|
+
if association.nil?
|
156
|
+
reflection = self.class.reflect_on_association(name)
|
157
|
+
association = reflection.association_class.new(self, reflection)
|
158
|
+
association_instance_set(name, association)
|
159
|
+
end
|
160
|
+
|
161
|
+
association
|
122
162
|
end
|
123
163
|
|
124
164
|
private
|
125
165
|
# Returns the specified association instance if it responds to :loaded?, nil otherwise.
|
126
166
|
def association_instance_get(name)
|
127
|
-
|
128
|
-
if instance_variable_defined?(ivar)
|
129
|
-
association = instance_variable_get(ivar)
|
130
|
-
association if association.respond_to?(:loaded?)
|
131
|
-
end
|
167
|
+
@association_cache[name.to_sym]
|
132
168
|
end
|
133
169
|
|
134
170
|
# Set the specified association instance.
|
135
171
|
def association_instance_set(name, association)
|
136
|
-
|
172
|
+
@association_cache[name] = association
|
137
173
|
end
|
138
174
|
|
139
175
|
# Associations are a set of macro-like class methods for tying objects together through
|
@@ -155,10 +191,10 @@ module ActiveRecord
|
|
155
191
|
# * <tt>Project#portfolio, Project#portfolio=(portfolio), Project#portfolio.nil?</tt>
|
156
192
|
# * <tt>Project#project_manager, Project#project_manager=(project_manager), Project#project_manager.nil?,</tt>
|
157
193
|
# * <tt>Project#milestones.empty?, Project#milestones.size, Project#milestones, Project#milestones<<(milestone),</tt>
|
158
|
-
# <tt>Project#milestones.delete(milestone), Project#milestones.
|
194
|
+
# <tt>Project#milestones.delete(milestone), Project#milestones.destroy(milestone), Project#milestones.find(milestone_id),</tt>
|
159
195
|
# <tt>Project#milestones.build, Project#milestones.create</tt>
|
160
196
|
# * <tt>Project#categories.empty?, Project#categories.size, Project#categories, Project#categories<<(category1),</tt>
|
161
|
-
# <tt>Project#categories.delete(category1)</tt>
|
197
|
+
# <tt>Project#categories.delete(category1), Project#categories.destroy(category1)</tt>
|
162
198
|
#
|
163
199
|
# === A word of warning
|
164
200
|
#
|
@@ -177,7 +213,7 @@ module ActiveRecord
|
|
177
213
|
# other=(other) | X | X | X
|
178
214
|
# build_other(attributes={}) | X | | X
|
179
215
|
# create_other(attributes={}) | X | | X
|
180
|
-
#
|
216
|
+
# create_other!(attributes={}) | X | | X
|
181
217
|
#
|
182
218
|
# ===Collection associations (one-to-many / many-to-many)
|
183
219
|
# | | | has_many
|
@@ -196,18 +232,39 @@ module ActiveRecord
|
|
196
232
|
# others.size | X | X | X
|
197
233
|
# others.length | X | X | X
|
198
234
|
# others.count | X | X | X
|
199
|
-
# others.sum(args
|
235
|
+
# others.sum(*args) | X | X | X
|
200
236
|
# others.empty? | X | X | X
|
201
237
|
# others.clear | X | X | X
|
202
238
|
# others.delete(other,other,...) | X | X | X
|
203
|
-
# others.delete_all | X | X |
|
239
|
+
# others.delete_all | X | X | X
|
240
|
+
# others.destroy(other,other,...) | X | X | X
|
204
241
|
# others.destroy_all | X | X | X
|
205
242
|
# others.find(*args) | X | X | X
|
206
|
-
# others.find_first | X | |
|
207
243
|
# others.exists? | X | X | X
|
244
|
+
# others.distinct | X | X | X
|
208
245
|
# others.uniq | X | X | X
|
209
246
|
# others.reset | X | X | X
|
210
247
|
#
|
248
|
+
# === Overriding generated methods
|
249
|
+
#
|
250
|
+
# Association methods are generated in a module that is included into the model class,
|
251
|
+
# which allows you to easily override with your own methods and call the original
|
252
|
+
# generated method with +super+. For example:
|
253
|
+
#
|
254
|
+
# class Car < ActiveRecord::Base
|
255
|
+
# belongs_to :owner
|
256
|
+
# belongs_to :old_owner
|
257
|
+
# def owner=(new_owner)
|
258
|
+
# self.old_owner = self.owner
|
259
|
+
# super
|
260
|
+
# end
|
261
|
+
# end
|
262
|
+
#
|
263
|
+
# If your model class is <tt>Project</tt>, the module is
|
264
|
+
# named <tt>Project::GeneratedFeatureMethods</tt>. The GeneratedFeatureMethods module is
|
265
|
+
# included in the model class immediately after the (anonymous) generated attributes methods
|
266
|
+
# module, meaning an association will override the methods for an attribute with the same name.
|
267
|
+
#
|
211
268
|
# == Cardinality and associations
|
212
269
|
#
|
213
270
|
# Active Record associations can be used to describe one-to-one, one-to-many and many-to-many
|
@@ -250,11 +307,11 @@ module ActiveRecord
|
|
250
307
|
# end
|
251
308
|
# class Programmer < ActiveRecord::Base
|
252
309
|
# has_many :assignments
|
253
|
-
# has_many :projects, :
|
310
|
+
# has_many :projects, through: :assignments
|
254
311
|
# end
|
255
312
|
# class Project < ActiveRecord::Base
|
256
313
|
# has_many :assignments
|
257
|
-
# has_many :programmers, :
|
314
|
+
# has_many :programmers, through: :assignments
|
258
315
|
# end
|
259
316
|
#
|
260
317
|
# For the second way, use +has_and_belongs_to_many+ in both models. This requires a join table
|
@@ -317,35 +374,61 @@ module ActiveRecord
|
|
317
374
|
# === One-to-one associations
|
318
375
|
#
|
319
376
|
# * Assigning an object to a +has_one+ association automatically saves that object and
|
320
|
-
#
|
321
|
-
#
|
322
|
-
# * If either of these saves fail (due to one of the objects being invalid)
|
323
|
-
#
|
377
|
+
# the object being replaced (if there is one), in order to update their foreign
|
378
|
+
# keys - except if the parent object is unsaved (<tt>new_record? == true</tt>).
|
379
|
+
# * If either of these saves fail (due to one of the objects being invalid), an
|
380
|
+
# <tt>ActiveRecord::RecordNotSaved</tt> exception is raised and the assignment is
|
381
|
+
# cancelled.
|
324
382
|
# * If you wish to assign an object to a +has_one+ association without saving it,
|
325
|
-
#
|
383
|
+
# use the <tt>build_association</tt> method (documented below). The object being
|
384
|
+
# replaced will still be saved to update its foreign key.
|
326
385
|
# * Assigning an object to a +belongs_to+ association does not save the object, since
|
327
|
-
#
|
386
|
+
# the foreign key field belongs on the parent. It does not save the parent either.
|
328
387
|
#
|
329
388
|
# === Collections
|
330
389
|
#
|
331
390
|
# * Adding an object to a collection (+has_many+ or +has_and_belongs_to_many+) automatically
|
332
|
-
#
|
333
|
-
#
|
391
|
+
# saves that object, except if the parent object (the owner of the collection) is not yet
|
392
|
+
# stored in the database.
|
334
393
|
# * If saving any of the objects being added to a collection (via <tt>push</tt> or similar)
|
335
|
-
#
|
394
|
+
# fails, then <tt>push</tt> returns +false+.
|
395
|
+
# * If saving fails while replacing the collection (via <tt>association=</tt>), an
|
396
|
+
# <tt>ActiveRecord::RecordNotSaved</tt> exception is raised and the assignment is
|
397
|
+
# cancelled.
|
336
398
|
# * You can add an object to a collection without automatically saving it by using the
|
337
|
-
#
|
399
|
+
# <tt>collection.build</tt> method (documented below).
|
338
400
|
# * All unsaved (<tt>new_record? == true</tt>) members of the collection are automatically
|
339
|
-
#
|
401
|
+
# saved when the parent is saved.
|
340
402
|
#
|
341
|
-
#
|
403
|
+
# == Customizing the query
|
404
|
+
#
|
405
|
+
# Associations are built from <tt>Relation</tt>s, and you can use the <tt>Relation</tt> syntax
|
406
|
+
# to customize them. For example, to add a condition:
|
407
|
+
#
|
408
|
+
# class Blog < ActiveRecord::Base
|
409
|
+
# has_many :published_posts, -> { where published: true }, class_name: 'Post'
|
410
|
+
# end
|
411
|
+
#
|
412
|
+
# Inside the <tt>-> { ... }</tt> block you can use all of the usual <tt>Relation</tt> methods.
|
413
|
+
#
|
414
|
+
# === Accessing the owner object
|
415
|
+
#
|
416
|
+
# Sometimes it is useful to have access to the owner object when building the query. The owner
|
417
|
+
# is passed as a parameter to the block. For example, the following association would find all
|
418
|
+
# events that occur on the user's birthday:
|
419
|
+
#
|
420
|
+
# class User < ActiveRecord::Base
|
421
|
+
# has_many :birthday_events, ->(user) { where starts_on: user.birthday }, class_name: 'Event'
|
422
|
+
# end
|
423
|
+
#
|
424
|
+
# == Association callbacks
|
342
425
|
#
|
343
426
|
# Similar to the normal callbacks that hook into the life cycle of an Active Record object,
|
344
427
|
# you can also define callbacks that get triggered when you add an object to or remove an
|
345
428
|
# object from an association collection.
|
346
429
|
#
|
347
430
|
# class Project
|
348
|
-
# has_and_belongs_to_many :developers, :
|
431
|
+
# has_and_belongs_to_many :developers, after_add: :evaluate_velocity
|
349
432
|
#
|
350
433
|
# def evaluate_velocity(developer)
|
351
434
|
# ...
|
@@ -356,7 +439,7 @@ module ActiveRecord
|
|
356
439
|
#
|
357
440
|
# class Project
|
358
441
|
# has_and_belongs_to_many :developers,
|
359
|
-
# :
|
442
|
+
# after_add: [:evaluate_velocity, Proc.new { |p, d| p.shipping_date = Time.now}]
|
360
443
|
# end
|
361
444
|
#
|
362
445
|
# Possible callbacks are: +before_add+, +after_add+, +before_remove+ and +after_remove+.
|
@@ -365,7 +448,7 @@ module ActiveRecord
|
|
365
448
|
# added to the collection. Same with the +before_remove+ callbacks; if an exception is
|
366
449
|
# thrown the object doesn't get removed.
|
367
450
|
#
|
368
|
-
#
|
451
|
+
# == Association extensions
|
369
452
|
#
|
370
453
|
# The proxy objects that control the access to associations can be extended through anonymous
|
371
454
|
# modules. This is especially beneficial for adding new finders, creators, and other
|
@@ -375,12 +458,12 @@ module ActiveRecord
|
|
375
458
|
# has_many :people do
|
376
459
|
# def find_or_create_by_name(name)
|
377
460
|
# first_name, last_name = name.split(" ", 2)
|
378
|
-
#
|
461
|
+
# find_or_create_by(first_name: first_name, last_name: last_name)
|
379
462
|
# end
|
380
463
|
# end
|
381
464
|
# end
|
382
465
|
#
|
383
|
-
# person = Account.
|
466
|
+
# person = Account.first.people.find_or_create_by_name("David Heinemeier Hansson")
|
384
467
|
# person.first_name # => "David"
|
385
468
|
# person.last_name # => "Heinemeier Hansson"
|
386
469
|
#
|
@@ -390,45 +473,43 @@ module ActiveRecord
|
|
390
473
|
# module FindOrCreateByNameExtension
|
391
474
|
# def find_or_create_by_name(name)
|
392
475
|
# first_name, last_name = name.split(" ", 2)
|
393
|
-
#
|
476
|
+
# find_or_create_by(first_name: first_name, last_name: last_name)
|
394
477
|
# end
|
395
478
|
# end
|
396
479
|
#
|
397
480
|
# class Account < ActiveRecord::Base
|
398
|
-
# has_many :people,
|
481
|
+
# has_many :people, -> { extending FindOrCreateByNameExtension }
|
399
482
|
# end
|
400
483
|
#
|
401
484
|
# class Company < ActiveRecord::Base
|
402
|
-
# has_many :people,
|
485
|
+
# has_many :people, -> { extending FindOrCreateByNameExtension }
|
403
486
|
# end
|
404
487
|
#
|
405
|
-
#
|
406
|
-
#
|
407
|
-
#
|
408
|
-
# in the array supercede those earlier in the array.
|
488
|
+
# Some extensions can only be made to work with knowledge of the association's internals.
|
489
|
+
# Extensions can access relevant state using the following methods (where +items+ is the
|
490
|
+
# name of the association):
|
409
491
|
#
|
410
|
-
#
|
411
|
-
#
|
412
|
-
#
|
413
|
-
#
|
414
|
-
# Some extensions can only be made to work with knowledge of the association proxy's internals.
|
415
|
-
# Extensions can access relevant state using accessors on the association proxy:
|
416
|
-
#
|
417
|
-
# * +proxy_owner+ - Returns the object the association is part of.
|
418
|
-
# * +proxy_reflection+ - Returns the reflection object that describes the association.
|
419
|
-
# * +proxy_target+ - Returns the associated object for +belongs_to+ and +has_one+, or
|
492
|
+
# * <tt>record.association(:items).owner</tt> - Returns the object the association is part of.
|
493
|
+
# * <tt>record.association(:items).reflection</tt> - Returns the reflection object that describes the association.
|
494
|
+
# * <tt>record.association(:items).target</tt> - Returns the associated object for +belongs_to+ and +has_one+, or
|
420
495
|
# the collection of associated objects for +has_many+ and +has_and_belongs_to_many+.
|
421
496
|
#
|
422
|
-
#
|
497
|
+
# However, inside the actual extension code, you will not have access to the <tt>record</tt> as
|
498
|
+
# above. In this case, you can access <tt>proxy_association</tt>. For example,
|
499
|
+
# <tt>record.association(:items)</tt> and <tt>record.items.proxy_association</tt> will return
|
500
|
+
# the same object, allowing you to make calls like <tt>proxy_association.owner</tt> inside
|
501
|
+
# association extensions.
|
502
|
+
#
|
503
|
+
# == Association Join Models
|
423
504
|
#
|
424
505
|
# Has Many associations can be configured with the <tt>:through</tt> option to use an
|
425
|
-
# explicit join model to retrieve the data.
|
426
|
-
# +has_and_belongs_to_many+ association.
|
427
|
-
# callbacks, and extra attributes on the join model.
|
506
|
+
# explicit join model to retrieve the data. This operates similarly to a
|
507
|
+
# +has_and_belongs_to_many+ association. The advantage is that you're able to add validations,
|
508
|
+
# callbacks, and extra attributes on the join model. Consider the following schema:
|
428
509
|
#
|
429
510
|
# class Author < ActiveRecord::Base
|
430
511
|
# has_many :authorships
|
431
|
-
# has_many :books, :
|
512
|
+
# has_many :books, through: :authorships
|
432
513
|
# end
|
433
514
|
#
|
434
515
|
# class Authorship < ActiveRecord::Base
|
@@ -436,7 +517,7 @@ module ActiveRecord
|
|
436
517
|
# belongs_to :book
|
437
518
|
# end
|
438
519
|
#
|
439
|
-
# @author = Author.
|
520
|
+
# @author = Author.first
|
440
521
|
# @author.authorships.collect { |a| a.book } # selects all books that the author's authorships belong to
|
441
522
|
# @author.books # selects all books by using the Authorship join model
|
442
523
|
#
|
@@ -444,7 +525,7 @@ module ActiveRecord
|
|
444
525
|
#
|
445
526
|
# class Firm < ActiveRecord::Base
|
446
527
|
# has_many :clients
|
447
|
-
# has_many :invoices, :
|
528
|
+
# has_many :invoices, through: :clients
|
448
529
|
# end
|
449
530
|
#
|
450
531
|
# class Client < ActiveRecord::Base
|
@@ -456,7 +537,7 @@ module ActiveRecord
|
|
456
537
|
# belongs_to :client
|
457
538
|
# end
|
458
539
|
#
|
459
|
-
# @firm = Firm.
|
540
|
+
# @firm = Firm.first
|
460
541
|
# @firm.clients.collect { |c| c.invoices }.flatten # select all invoices for all clients of the firm
|
461
542
|
# @firm.invoices # selects all invoices by going through the Client join model
|
462
543
|
#
|
@@ -464,7 +545,7 @@ module ActiveRecord
|
|
464
545
|
#
|
465
546
|
# class Group < ActiveRecord::Base
|
466
547
|
# has_many :users
|
467
|
-
# has_many :avatars, :
|
548
|
+
# has_many :avatars, through: :users
|
468
549
|
# end
|
469
550
|
#
|
470
551
|
# class User < ActiveRecord::Base
|
@@ -477,34 +558,93 @@ module ActiveRecord
|
|
477
558
|
# end
|
478
559
|
#
|
479
560
|
# @group = Group.first
|
480
|
-
# @group.users.collect { |u| u.avatar }.
|
561
|
+
# @group.users.collect { |u| u.avatar }.compact # select all avatars for all users in the group
|
481
562
|
# @group.avatars # selects all avatars by going through the User join model.
|
482
563
|
#
|
483
564
|
# An important caveat with going through +has_one+ or +has_many+ associations on the
|
484
|
-
# join model is that these associations are *read-only*.
|
565
|
+
# join model is that these associations are *read-only*. For example, the following
|
485
566
|
# would not work following the previous example:
|
486
567
|
#
|
487
568
|
# @group.avatars << Avatar.new # this would work if User belonged_to Avatar rather than the other way around
|
488
569
|
# @group.avatars.delete(@group.avatars.last) # so would this
|
489
570
|
#
|
490
|
-
#
|
571
|
+
# If you are using a +belongs_to+ on the join model, it is a good idea to set the
|
572
|
+
# <tt>:inverse_of</tt> option on the +belongs_to+, which will mean that the following example
|
573
|
+
# works correctly (where <tt>tags</tt> is a +has_many+ <tt>:through</tt> association):
|
574
|
+
#
|
575
|
+
# @post = Post.first
|
576
|
+
# @tag = @post.tags.build name: "ruby"
|
577
|
+
# @tag.save
|
578
|
+
#
|
579
|
+
# The last line ought to save the through record (a <tt>Taggable</tt>). This will only work if the
|
580
|
+
# <tt>:inverse_of</tt> is set:
|
581
|
+
#
|
582
|
+
# class Taggable < ActiveRecord::Base
|
583
|
+
# belongs_to :post
|
584
|
+
# belongs_to :tag, inverse_of: :taggings
|
585
|
+
# end
|
586
|
+
#
|
587
|
+
# == Nested Associations
|
588
|
+
#
|
589
|
+
# You can actually specify *any* association with the <tt>:through</tt> option, including an
|
590
|
+
# association which has a <tt>:through</tt> option itself. For example:
|
591
|
+
#
|
592
|
+
# class Author < ActiveRecord::Base
|
593
|
+
# has_many :posts
|
594
|
+
# has_many :comments, through: :posts
|
595
|
+
# has_many :commenters, through: :comments
|
596
|
+
# end
|
597
|
+
#
|
598
|
+
# class Post < ActiveRecord::Base
|
599
|
+
# has_many :comments
|
600
|
+
# end
|
601
|
+
#
|
602
|
+
# class Comment < ActiveRecord::Base
|
603
|
+
# belongs_to :commenter
|
604
|
+
# end
|
605
|
+
#
|
606
|
+
# @author = Author.first
|
607
|
+
# @author.commenters # => People who commented on posts written by the author
|
608
|
+
#
|
609
|
+
# An equivalent way of setting up this association this would be:
|
610
|
+
#
|
611
|
+
# class Author < ActiveRecord::Base
|
612
|
+
# has_many :posts
|
613
|
+
# has_many :commenters, through: :posts
|
614
|
+
# end
|
615
|
+
#
|
616
|
+
# class Post < ActiveRecord::Base
|
617
|
+
# has_many :comments
|
618
|
+
# has_many :commenters, through: :comments
|
619
|
+
# end
|
620
|
+
#
|
621
|
+
# class Comment < ActiveRecord::Base
|
622
|
+
# belongs_to :commenter
|
623
|
+
# end
|
624
|
+
#
|
625
|
+
# When using nested association, you will not be able to modify the association because there
|
626
|
+
# is not enough information to know what modification to make. For example, if you tried to
|
627
|
+
# add a <tt>Commenter</tt> in the example above, there would be no way to tell how to set up the
|
628
|
+
# intermediate <tt>Post</tt> and <tt>Comment</tt> objects.
|
629
|
+
#
|
630
|
+
# == Polymorphic Associations
|
491
631
|
#
|
492
632
|
# Polymorphic associations on models are not restricted on what types of models they
|
493
|
-
# can be associated with.
|
633
|
+
# can be associated with. Rather, they specify an interface that a +has_many+ association
|
494
634
|
# must adhere to.
|
495
635
|
#
|
496
636
|
# class Asset < ActiveRecord::Base
|
497
|
-
# belongs_to :attachable, :
|
637
|
+
# belongs_to :attachable, polymorphic: true
|
498
638
|
# end
|
499
639
|
#
|
500
640
|
# class Post < ActiveRecord::Base
|
501
|
-
# has_many :assets, :
|
641
|
+
# has_many :assets, as: :attachable # The :as option specifies the polymorphic interface to use.
|
502
642
|
# end
|
503
643
|
#
|
504
644
|
# @asset.attachable = @post
|
505
645
|
#
|
506
646
|
# This works by using a type column in addition to a foreign key to specify the associated
|
507
|
-
# record.
|
647
|
+
# record. In the Asset example, you'd need an +attachable_id+ integer column and an
|
508
648
|
# +attachable_type+ string column.
|
509
649
|
#
|
510
650
|
# Using polymorphic associations in combination with single table inheritance (STI) is
|
@@ -515,7 +655,7 @@ module ActiveRecord
|
|
515
655
|
# column in the posts table.
|
516
656
|
#
|
517
657
|
# class Asset < ActiveRecord::Base
|
518
|
-
# belongs_to :attachable, :
|
658
|
+
# belongs_to :attachable, polymorphic: true
|
519
659
|
#
|
520
660
|
# def attachable_type=(sType)
|
521
661
|
# super(sType.to_s.classify.constantize.base_class.to_s)
|
@@ -523,8 +663,8 @@ module ActiveRecord
|
|
523
663
|
# end
|
524
664
|
#
|
525
665
|
# class Post < ActiveRecord::Base
|
526
|
-
# # because we store "Post" in attachable_type now :
|
527
|
-
# has_many :assets, :
|
666
|
+
# # because we store "Post" in attachable_type now dependent: :destroy will work
|
667
|
+
# has_many :assets, as: :attachable, dependent: :destroy
|
528
668
|
# end
|
529
669
|
#
|
530
670
|
# class GuestPost < Post
|
@@ -560,7 +700,7 @@ module ActiveRecord
|
|
560
700
|
#
|
561
701
|
# Consider the following loop using the class above:
|
562
702
|
#
|
563
|
-
#
|
703
|
+
# Post.all.each do |post|
|
564
704
|
# puts "Post: " + post.title
|
565
705
|
# puts "Written by: " + post.author.name
|
566
706
|
# puts "Last comment on: " + post.comments.first.created_on
|
@@ -569,7 +709,7 @@ module ActiveRecord
|
|
569
709
|
# To iterate over these one hundred posts, we'll generate 201 database queries. Let's
|
570
710
|
# first just optimize it for retrieving the author:
|
571
711
|
#
|
572
|
-
#
|
712
|
+
# Post.includes(:author).each do |post|
|
573
713
|
#
|
574
714
|
# This references the name of the +belongs_to+ association that also used the <tt>:author</tt>
|
575
715
|
# symbol. After loading the posts, find will collect the +author_id+ from each one and load
|
@@ -578,7 +718,7 @@ module ActiveRecord
|
|
578
718
|
#
|
579
719
|
# We can improve upon the situation further by referencing both associations in the finder with:
|
580
720
|
#
|
581
|
-
#
|
721
|
+
# Post.includes(:author, :comments).each do |post|
|
582
722
|
#
|
583
723
|
# This will load all comments with a single query. This reduces the total number of queries
|
584
724
|
# to 3. More generally the number of queries will be 1 plus the number of associations
|
@@ -586,7 +726,7 @@ module ActiveRecord
|
|
586
726
|
#
|
587
727
|
# To include a deep hierarchy of associations, use a hash:
|
588
728
|
#
|
589
|
-
#
|
729
|
+
# Post.includes(:author, {comments: {author: :gravatar}}).each do |post|
|
590
730
|
#
|
591
731
|
# That'll grab not only all the comments but all their authors and gravatar pictures.
|
592
732
|
# You can mix and match symbols, arrays and hashes in any combination to describe the
|
@@ -602,7 +742,7 @@ module ActiveRecord
|
|
602
742
|
# other than the main one. If this is the case Active Record falls back to the previously
|
603
743
|
# used LEFT OUTER JOIN based strategy. For example
|
604
744
|
#
|
605
|
-
# Post.
|
745
|
+
# Post.includes([:author, :comments]).where(['comments.approved = ?', true])
|
606
746
|
#
|
607
747
|
# This will result in a single SQL query with joins along the lines of:
|
608
748
|
# <tt>LEFT OUTER JOIN comments ON comments.post_id = posts.id</tt> and
|
@@ -611,16 +751,16 @@ module ActiveRecord
|
|
611
751
|
# In the above example posts with no approved comments are not returned at all, because
|
612
752
|
# the conditions apply to the SQL statement as a whole and not just to the association.
|
613
753
|
# You must disambiguate column references for this fallback to happen, for example
|
614
|
-
# <tt
|
754
|
+
# <tt>order: "author.name DESC"</tt> will work but <tt>order: "name DESC"</tt> will not.
|
615
755
|
#
|
616
756
|
# If you do want eager load only some members of an association it is usually more natural
|
617
|
-
# to
|
757
|
+
# to include an association which has conditions defined on it:
|
618
758
|
#
|
619
759
|
# class Post < ActiveRecord::Base
|
620
|
-
# has_many :approved_comments,
|
760
|
+
# has_many :approved_comments, -> { where approved: true }, class_name: 'Comment'
|
621
761
|
# end
|
622
762
|
#
|
623
|
-
# Post.
|
763
|
+
# Post.includes(:approved_comments)
|
624
764
|
#
|
625
765
|
# This will load posts and eager load the +approved_comments+ association, which contains
|
626
766
|
# only those comments that have been approved.
|
@@ -629,23 +769,20 @@ module ActiveRecord
|
|
629
769
|
# returning all the associated objects:
|
630
770
|
#
|
631
771
|
# class Picture < ActiveRecord::Base
|
632
|
-
# has_many :most_recent_comments,
|
772
|
+
# has_many :most_recent_comments, -> { order('id DESC').limit(10) }, class_name: 'Comment'
|
633
773
|
# end
|
634
774
|
#
|
635
|
-
# Picture.
|
636
|
-
#
|
637
|
-
# When eager loaded, conditions are interpolated in the context of the model class, not
|
638
|
-
# the model instance. Conditions are lazily interpolated before the actual model exists.
|
775
|
+
# Picture.includes(:most_recent_comments).first.most_recent_comments # => returns all associated comments.
|
639
776
|
#
|
640
777
|
# Eager loading is supported with polymorphic associations.
|
641
778
|
#
|
642
779
|
# class Address < ActiveRecord::Base
|
643
|
-
# belongs_to :addressable, :
|
780
|
+
# belongs_to :addressable, polymorphic: true
|
644
781
|
# end
|
645
782
|
#
|
646
783
|
# A call that tries to eager load the addressable model
|
647
784
|
#
|
648
|
-
# Address.
|
785
|
+
# Address.includes(:addressable)
|
649
786
|
#
|
650
787
|
# This will execute one query to load the addresses and load the addressables with one
|
651
788
|
# query per addressable type.
|
@@ -659,47 +796,47 @@ module ActiveRecord
|
|
659
796
|
# == Table Aliasing
|
660
797
|
#
|
661
798
|
# Active Record uses table aliasing in the case that a table is referenced multiple times
|
662
|
-
# in a join.
|
799
|
+
# in a join. If a table is referenced only once, the standard table name is used. The
|
663
800
|
# second time, the table is aliased as <tt>#{reflection_name}_#{parent_table_name}</tt>.
|
664
801
|
# Indexes are appended for any more successive uses of the table name.
|
665
802
|
#
|
666
|
-
# Post.
|
803
|
+
# Post.joins(:comments)
|
667
804
|
# # => SELECT ... FROM posts INNER JOIN comments ON ...
|
668
|
-
# Post.
|
805
|
+
# Post.joins(:special_comments) # STI
|
669
806
|
# # => SELECT ... FROM posts INNER JOIN comments ON ... AND comments.type = 'SpecialComment'
|
670
|
-
# Post.
|
807
|
+
# Post.joins(:comments, :special_comments) # special_comments is the reflection name, posts is the parent table name
|
671
808
|
# # => SELECT ... FROM posts INNER JOIN comments ON ... INNER JOIN comments special_comments_posts
|
672
809
|
#
|
673
810
|
# Acts as tree example:
|
674
811
|
#
|
675
|
-
# TreeMixin.
|
812
|
+
# TreeMixin.joins(:children)
|
676
813
|
# # => SELECT ... FROM mixins INNER JOIN mixins childrens_mixins ...
|
677
|
-
# TreeMixin.
|
814
|
+
# TreeMixin.joins(children: :parent)
|
678
815
|
# # => SELECT ... FROM mixins INNER JOIN mixins childrens_mixins ...
|
679
816
|
# INNER JOIN parents_mixins ...
|
680
|
-
# TreeMixin.
|
817
|
+
# TreeMixin.joins(children: {parent: :children})
|
681
818
|
# # => SELECT ... FROM mixins INNER JOIN mixins childrens_mixins ...
|
682
819
|
# INNER JOIN parents_mixins ...
|
683
820
|
# INNER JOIN mixins childrens_mixins_2
|
684
821
|
#
|
685
822
|
# Has and Belongs to Many join tables use the same idea, but add a <tt>_join</tt> suffix:
|
686
823
|
#
|
687
|
-
# Post.
|
824
|
+
# Post.joins(:categories)
|
688
825
|
# # => SELECT ... FROM posts INNER JOIN categories_posts ... INNER JOIN categories ...
|
689
|
-
# Post.
|
826
|
+
# Post.joins(categories: :posts)
|
690
827
|
# # => SELECT ... FROM posts INNER JOIN categories_posts ... INNER JOIN categories ...
|
691
828
|
# INNER JOIN categories_posts posts_categories_join INNER JOIN posts posts_categories
|
692
|
-
# Post.
|
829
|
+
# Post.joins(categories: {posts: :categories})
|
693
830
|
# # => SELECT ... FROM posts INNER JOIN categories_posts ... INNER JOIN categories ...
|
694
831
|
# INNER JOIN categories_posts posts_categories_join INNER JOIN posts posts_categories
|
695
832
|
# INNER JOIN categories_posts categories_posts_join INNER JOIN categories categories_posts_2
|
696
833
|
#
|
697
|
-
# If you wish to specify your own custom joins using
|
834
|
+
# If you wish to specify your own custom joins using <tt>joins</tt> method, those table
|
698
835
|
# names will take precedence over the eager associations:
|
699
836
|
#
|
700
|
-
# Post.
|
837
|
+
# Post.joins(:comments).joins("inner join comments ...")
|
701
838
|
# # => SELECT ... FROM posts INNER JOIN comments_posts ON ... INNER JOIN comments ...
|
702
|
-
# Post.
|
839
|
+
# Post.joins(:comments, :special_comments).joins("inner join comments ...")
|
703
840
|
# # => SELECT ... FROM posts INNER JOIN comments comments_posts ON ...
|
704
841
|
# INNER JOIN comments special_comments_posts ...
|
705
842
|
# INNER JOIN comments ...
|
@@ -714,8 +851,8 @@ module ActiveRecord
|
|
714
851
|
# module MyApplication
|
715
852
|
# module Business
|
716
853
|
# class Firm < ActiveRecord::Base
|
717
|
-
#
|
718
|
-
#
|
854
|
+
# has_many :clients
|
855
|
+
# end
|
719
856
|
#
|
720
857
|
# class Client < ActiveRecord::Base; end
|
721
858
|
# end
|
@@ -733,7 +870,7 @@ module ActiveRecord
|
|
733
870
|
#
|
734
871
|
# module Billing
|
735
872
|
# class Account < ActiveRecord::Base
|
736
|
-
# belongs_to :firm, :
|
873
|
+
# belongs_to :firm, class_name: "MyApplication::Business::Firm"
|
737
874
|
# end
|
738
875
|
# end
|
739
876
|
# end
|
@@ -741,7 +878,7 @@ module ActiveRecord
|
|
741
878
|
# == Bi-directional associations
|
742
879
|
#
|
743
880
|
# When you specify an association there is usually an association on the associated model
|
744
|
-
# that specifies the same relationship in reverse.
|
881
|
+
# that specifies the same relationship in reverse. For example, with the following models:
|
745
882
|
#
|
746
883
|
# class Dungeon < ActiveRecord::Base
|
747
884
|
# has_many :traps
|
@@ -756,11 +893,11 @@ module ActiveRecord
|
|
756
893
|
# belongs_to :dungeon
|
757
894
|
# end
|
758
895
|
#
|
759
|
-
# The +traps+ association on +Dungeon+ and the
|
896
|
+
# The +traps+ association on +Dungeon+ and the +dungeon+ association on +Trap+ are
|
760
897
|
# the inverse of each other and the inverse of the +dungeon+ association on +EvilWizard+
|
761
|
-
# is the +evil_wizard+ association on +Dungeon+ (and vice-versa).
|
898
|
+
# is the +evil_wizard+ association on +Dungeon+ (and vice-versa). By default,
|
762
899
|
# Active Record doesn't know anything about these inverse relationships and so no object
|
763
|
-
# loading
|
900
|
+
# loading optimization is possible. For example:
|
764
901
|
#
|
765
902
|
# d = Dungeon.first
|
766
903
|
# t = d.traps.first
|
@@ -770,21 +907,21 @@ module ActiveRecord
|
|
770
907
|
#
|
771
908
|
# The +Dungeon+ instances +d+ and <tt>t.dungeon</tt> in the above example refer to
|
772
909
|
# the same object data from the database, but are actually different in-memory copies
|
773
|
-
# of that data.
|
774
|
-
# Active Record about inverse relationships and it will optimise object loading.
|
910
|
+
# of that data. Specifying the <tt>:inverse_of</tt> option on associations lets you tell
|
911
|
+
# Active Record about inverse relationships and it will optimise object loading. For
|
775
912
|
# example, if we changed our model definitions to:
|
776
913
|
#
|
777
914
|
# class Dungeon < ActiveRecord::Base
|
778
|
-
# has_many :traps, :
|
779
|
-
# has_one :evil_wizard, :
|
915
|
+
# has_many :traps, inverse_of: :dungeon
|
916
|
+
# has_one :evil_wizard, inverse_of: :dungeon
|
780
917
|
# end
|
781
918
|
#
|
782
919
|
# class Trap < ActiveRecord::Base
|
783
|
-
# belongs_to :dungeon, :
|
920
|
+
# belongs_to :dungeon, inverse_of: :traps
|
784
921
|
# end
|
785
922
|
#
|
786
923
|
# class EvilWizard < ActiveRecord::Base
|
787
|
-
# belongs_to :dungeon, :
|
924
|
+
# belongs_to :dungeon, inverse_of: :evil_wizard
|
788
925
|
# end
|
789
926
|
#
|
790
927
|
# Then, from our code snippet above, +d+ and <tt>t.dungeon</tt> are actually the same
|
@@ -796,6 +933,79 @@ module ActiveRecord
|
|
796
933
|
# * does not work with <tt>:polymorphic</tt> associations.
|
797
934
|
# * for +belongs_to+ associations +has_many+ inverse associations are ignored.
|
798
935
|
#
|
936
|
+
# == Deleting from associations
|
937
|
+
#
|
938
|
+
# === Dependent associations
|
939
|
+
#
|
940
|
+
# +has_many+, +has_one+ and +belongs_to+ associations support the <tt>:dependent</tt> option.
|
941
|
+
# This allows you to specify that associated records should be deleted when the owner is
|
942
|
+
# deleted.
|
943
|
+
#
|
944
|
+
# For example:
|
945
|
+
#
|
946
|
+
# class Author
|
947
|
+
# has_many :posts, dependent: :destroy
|
948
|
+
# end
|
949
|
+
# Author.find(1).destroy # => Will destroy all of the author's posts, too
|
950
|
+
#
|
951
|
+
# The <tt>:dependent</tt> option can have different values which specify how the deletion
|
952
|
+
# is done. For more information, see the documentation for this option on the different
|
953
|
+
# specific association types. When no option is given, the behavior is to do nothing
|
954
|
+
# with the associated records when destroying a record.
|
955
|
+
#
|
956
|
+
# Note that <tt>:dependent</tt> is implemented using Rails' callback
|
957
|
+
# system, which works by processing callbacks in order. Therefore, other
|
958
|
+
# callbacks declared either before or after the <tt>:dependent</tt> option
|
959
|
+
# can affect what it does.
|
960
|
+
#
|
961
|
+
# === Delete or destroy?
|
962
|
+
#
|
963
|
+
# +has_many+ and +has_and_belongs_to_many+ associations have the methods <tt>destroy</tt>,
|
964
|
+
# <tt>delete</tt>, <tt>destroy_all</tt> and <tt>delete_all</tt>.
|
965
|
+
#
|
966
|
+
# For +has_and_belongs_to_many+, <tt>delete</tt> and <tt>destroy</tt> are the same: they
|
967
|
+
# cause the records in the join table to be removed.
|
968
|
+
#
|
969
|
+
# For +has_many+, <tt>destroy</tt> and <tt>destroy_all</tt> will always call the <tt>destroy</tt> method of the
|
970
|
+
# record(s) being removed so that callbacks are run. However <tt>delete</tt> and <tt>delete_all</tt> will either
|
971
|
+
# do the deletion according to the strategy specified by the <tt>:dependent</tt> option, or
|
972
|
+
# if no <tt>:dependent</tt> option is given, then it will follow the default strategy.
|
973
|
+
# The default strategy is <tt>:nullify</tt> (set the foreign keys to <tt>nil</tt>), except for
|
974
|
+
# +has_many+ <tt>:through</tt>, where the default strategy is <tt>delete_all</tt> (delete
|
975
|
+
# the join records, without running their callbacks).
|
976
|
+
#
|
977
|
+
# There is also a <tt>clear</tt> method which is the same as <tt>delete_all</tt>, except that
|
978
|
+
# it returns the association rather than the records which have been deleted.
|
979
|
+
#
|
980
|
+
# === What gets deleted?
|
981
|
+
#
|
982
|
+
# There is a potential pitfall here: +has_and_belongs_to_many+ and +has_many+ <tt>:through</tt>
|
983
|
+
# associations have records in join tables, as well as the associated records. So when we
|
984
|
+
# call one of these deletion methods, what exactly should be deleted?
|
985
|
+
#
|
986
|
+
# The answer is that it is assumed that deletion on an association is about removing the
|
987
|
+
# <i>link</i> between the owner and the associated object(s), rather than necessarily the
|
988
|
+
# associated objects themselves. So with +has_and_belongs_to_many+ and +has_many+
|
989
|
+
# <tt>:through</tt>, the join records will be deleted, but the associated records won't.
|
990
|
+
#
|
991
|
+
# This makes sense if you think about it: if you were to call <tt>post.tags.delete(Tag.find_by(name: 'food'))</tt>
|
992
|
+
# you would want the 'food' tag to be unlinked from the post, rather than for the tag itself
|
993
|
+
# to be removed from the database.
|
994
|
+
#
|
995
|
+
# However, there are examples where this strategy doesn't make sense. For example, suppose
|
996
|
+
# a person has many projects, and each project has many tasks. If we deleted one of a person's
|
997
|
+
# tasks, we would probably not want the project to be deleted. In this scenario, the delete method
|
998
|
+
# won't actually work: it can only be used if the association on the join model is a
|
999
|
+
# +belongs_to+. In other situations you are expected to perform operations directly on
|
1000
|
+
# either the associated records or the <tt>:through</tt> association.
|
1001
|
+
#
|
1002
|
+
# With a regular +has_many+ there is no distinction between the "associated records"
|
1003
|
+
# and the "link", so there is only one choice for what gets deleted.
|
1004
|
+
#
|
1005
|
+
# With +has_and_belongs_to_many+ and +has_many+ <tt>:through</tt>, if you want to delete the
|
1006
|
+
# associated records themselves, you can always do something along the lines of
|
1007
|
+
# <tt>person.tasks.each(&:destroy)</tt>.
|
1008
|
+
#
|
799
1009
|
# == Type safety with <tt>ActiveRecord::AssociationTypeMismatch</tt>
|
800
1010
|
#
|
801
1011
|
# If you attempt to assign an object to an association that doesn't match the inferred
|
@@ -815,11 +1025,21 @@ module ActiveRecord
|
|
815
1025
|
# [collection<<(object, ...)]
|
816
1026
|
# Adds one or more objects to the collection by setting their foreign keys to the collection's primary key.
|
817
1027
|
# Note that this operation instantly fires update sql without waiting for the save or update call on the
|
818
|
-
# parent object.
|
1028
|
+
# parent object, unless the parent object is a new record.
|
819
1029
|
# [collection.delete(object, ...)]
|
820
1030
|
# Removes one or more objects from the collection by setting their foreign keys to +NULL+.
|
821
|
-
# Objects will be in addition destroyed if they're associated with <tt
|
822
|
-
# and deleted if they're associated with <tt
|
1031
|
+
# Objects will be in addition destroyed if they're associated with <tt>dependent: :destroy</tt>,
|
1032
|
+
# and deleted if they're associated with <tt>dependent: :delete_all</tt>.
|
1033
|
+
#
|
1034
|
+
# If the <tt>:through</tt> option is used, then the join records are deleted (rather than
|
1035
|
+
# nullified) by default, but you can specify <tt>dependent: :destroy</tt> or
|
1036
|
+
# <tt>dependent: :nullify</tt> to override this.
|
1037
|
+
# [collection.destroy(object, ...)]
|
1038
|
+
# Removes one or more objects from the collection by running <tt>destroy</tt> on
|
1039
|
+
# each record, regardless of any dependent option, ensuring callbacks are run.
|
1040
|
+
#
|
1041
|
+
# If the <tt>:through</tt> option is used, then the join records are destroyed
|
1042
|
+
# instead, not the objects themselves.
|
823
1043
|
# [collection=objects]
|
824
1044
|
# Replaces the collections content by deleting and adding objects as appropriate. If the <tt>:through</tt>
|
825
1045
|
# option is true callbacks in the join models are triggered except destroy callbacks, since deletion is
|
@@ -831,8 +1051,8 @@ module ActiveRecord
|
|
831
1051
|
# method loads the models and calls <tt>collection=</tt>. See above.
|
832
1052
|
# [collection.clear]
|
833
1053
|
# Removes every object from the collection. This destroys the associated objects if they
|
834
|
-
# are associated with <tt
|
835
|
-
# database if <tt
|
1054
|
+
# are associated with <tt>dependent: :destroy</tt>, deletes them directly from the
|
1055
|
+
# database if <tt>dependent: :delete_all</tt>, otherwise sets their foreign keys to +NULL+.
|
836
1056
|
# If the <tt>:through</tt> option is true no destroy callbacks are invoked on the join models.
|
837
1057
|
# Join models are directly deleted.
|
838
1058
|
# [collection.empty?]
|
@@ -853,6 +1073,9 @@ module ActiveRecord
|
|
853
1073
|
# with +attributes+, linked to this object through a foreign key, and that has already
|
854
1074
|
# been saved (if it passed the validation). *Note*: This only works if the base model
|
855
1075
|
# already exists in the DB, not if it is a new (unsaved) record!
|
1076
|
+
# [collection.create!(attributes = {})]
|
1077
|
+
# Does the same as <tt>collection.create</tt>, but raises <tt>ActiveRecord::RecordInvalid</tt>
|
1078
|
+
# if the record is invalid.
|
856
1079
|
#
|
857
1080
|
# (*Note*: +collection+ is replaced with the symbol passed as the first argument, so
|
858
1081
|
# <tt>has_many :clients</tt> would add among others <tt>clients.empty?</tt>.)
|
@@ -860,36 +1083,29 @@ module ActiveRecord
|
|
860
1083
|
# === Example
|
861
1084
|
#
|
862
1085
|
# Example: A Firm class declares <tt>has_many :clients</tt>, which will add:
|
863
|
-
# * <tt>Firm#clients</tt> (similar to <tt>
|
1086
|
+
# * <tt>Firm#clients</tt> (similar to <tt>Client.where(firm_id: id)</tt>)
|
864
1087
|
# * <tt>Firm#clients<<</tt>
|
865
1088
|
# * <tt>Firm#clients.delete</tt>
|
1089
|
+
# * <tt>Firm#clients.destroy</tt>
|
866
1090
|
# * <tt>Firm#clients=</tt>
|
867
1091
|
# * <tt>Firm#client_ids</tt>
|
868
1092
|
# * <tt>Firm#client_ids=</tt>
|
869
1093
|
# * <tt>Firm#clients.clear</tt>
|
870
1094
|
# * <tt>Firm#clients.empty?</tt> (similar to <tt>firm.clients.size == 0</tt>)
|
871
1095
|
# * <tt>Firm#clients.size</tt> (similar to <tt>Client.count "firm_id = #{id}"</tt>)
|
872
|
-
# * <tt>Firm#clients.find</tt> (similar to <tt>Client.
|
873
|
-
# * <tt>Firm#clients.exists?(:
|
1096
|
+
# * <tt>Firm#clients.find</tt> (similar to <tt>Client.where(firm_id: id).find(id)</tt>)
|
1097
|
+
# * <tt>Firm#clients.exists?(name: 'ACME')</tt> (similar to <tt>Client.exists?(name: 'ACME', firm_id: firm.id)</tt>)
|
874
1098
|
# * <tt>Firm#clients.build</tt> (similar to <tt>Client.new("firm_id" => id)</tt>)
|
875
1099
|
# * <tt>Firm#clients.create</tt> (similar to <tt>c = Client.new("firm_id" => id); c.save; c</tt>)
|
1100
|
+
# * <tt>Firm#clients.create!</tt> (similar to <tt>c = Client.new("firm_id" => id); c.save!</tt>)
|
876
1101
|
# The declaration can also include an options hash to specialize the behavior of the association.
|
877
1102
|
#
|
878
|
-
# ===
|
1103
|
+
# === Options
|
879
1104
|
# [:class_name]
|
880
1105
|
# Specify the class name of the association. Use it only if that name can't be inferred
|
881
1106
|
# from the association name. So <tt>has_many :products</tt> will by default be linked
|
882
1107
|
# to the Product class, but if the real class name is SpecialProduct, you'll have to
|
883
1108
|
# specify it with this option.
|
884
|
-
# [:conditions]
|
885
|
-
# Specify the conditions that the associated objects must meet in order to be included as a +WHERE+
|
886
|
-
# SQL fragment, such as <tt>price > 5 AND name LIKE 'B%'</tt>. Record creations from
|
887
|
-
# the association are scoped if a hash is used.
|
888
|
-
# <tt>has_many :posts, :conditions => {:published => true}</tt> will create published
|
889
|
-
# posts with <tt>@blog.posts.create</tt> or <tt>@blog.posts.build</tt>.
|
890
|
-
# [:order]
|
891
|
-
# Specify the order in which the associated objects are returned as an <tt>ORDER BY</tt> SQL fragment,
|
892
|
-
# such as <tt>last_name, first_name DESC</tt>.
|
893
1109
|
# [:foreign_key]
|
894
1110
|
# Specify the foreign key used for the association. By default this is guessed to be the name
|
895
1111
|
# of this class in lower-case and "_id" suffixed. So a Person class that makes a +has_many+
|
@@ -897,68 +1113,60 @@ module ActiveRecord
|
|
897
1113
|
# [:primary_key]
|
898
1114
|
# Specify the method that returns the primary key used for the association. By default this is +id+.
|
899
1115
|
# [:dependent]
|
900
|
-
#
|
901
|
-
#
|
902
|
-
#
|
903
|
-
#
|
904
|
-
#
|
1116
|
+
# Controls what happens to the associated objects when
|
1117
|
+
# their owner is destroyed. Note that these are implemented as
|
1118
|
+
# callbacks, and Rails executes callbacks in order. Therefore, other
|
1119
|
+
# similar callbacks may affect the :dependent behavior, and the
|
1120
|
+
# :dependent behavior may affect other callbacks.
|
905
1121
|
#
|
906
|
-
# *
|
1122
|
+
# * <tt>:destroy</tt> causes all the associated objects to also be destroyed.
|
1123
|
+
# * <tt>:delete_all</tt> causes all the associated objects to be deleted directly from the database (so callbacks will not be executed).
|
1124
|
+
# * <tt>:nullify</tt> causes the foreign keys to be set to +NULL+. Callbacks are not executed.
|
1125
|
+
# * <tt>:restrict_with_exception</tt> causes an exception to be raised if there are any associated records.
|
1126
|
+
# * <tt>:restrict_with_error</tt> causes an error to be added to the owner if there are any associated objects.
|
907
1127
|
#
|
908
|
-
#
|
909
|
-
#
|
910
|
-
#
|
911
|
-
#
|
912
|
-
#
|
913
|
-
#
|
914
|
-
# specified but not <tt>:counter_sql</tt>, <tt>:counter_sql</tt> will be generated by
|
915
|
-
# replacing <tt>SELECT ... FROM</tt> with <tt>SELECT COUNT(*) FROM</tt>.
|
916
|
-
# [:extend]
|
917
|
-
# Specify a named module for extending the proxy. See "Association extensions".
|
918
|
-
# [:include]
|
919
|
-
# Specify second-order associations that should be eager loaded when the collection is loaded.
|
920
|
-
# [:group]
|
921
|
-
# An attribute name by which the result should be grouped. Uses the <tt>GROUP BY</tt> SQL-clause.
|
922
|
-
# [:having]
|
923
|
-
# Combined with +:group+ this can be used to filter the records that a <tt>GROUP BY</tt>
|
924
|
-
# returns. Uses the <tt>HAVING</tt> SQL-clause.
|
925
|
-
# [:limit]
|
926
|
-
# An integer determining the limit on the number of rows that should be returned.
|
927
|
-
# [:offset]
|
928
|
-
# An integer determining the offset from where the rows should be fetched. So at 5,
|
929
|
-
# it would skip the first 4 rows.
|
930
|
-
# [:select]
|
931
|
-
# By default, this is <tt>*</tt> as in <tt>SELECT * FROM</tt>, but can be changed if
|
932
|
-
# you, for example, want to do a join but not include the joined columns. Do not forget
|
933
|
-
# to include the primary and foreign keys, otherwise it will raise an error.
|
1128
|
+
# If using with the <tt>:through</tt> option, the association on the join model must be
|
1129
|
+
# a +belongs_to+, and the records which get deleted are the join records, rather than
|
1130
|
+
# the associated records.
|
1131
|
+
# [:counter_cache]
|
1132
|
+
# This option can be used to configure a custom named <tt>:counter_cache.</tt> You only need this option,
|
1133
|
+
# when you customized the name of your <tt>:counter_cache</tt> on the <tt>belongs_to</tt> association.
|
934
1134
|
# [:as]
|
935
1135
|
# Specifies a polymorphic interface (See <tt>belongs_to</tt>).
|
936
1136
|
# [:through]
|
937
|
-
# Specifies
|
938
|
-
#
|
939
|
-
#
|
940
|
-
#
|
941
|
-
#
|
942
|
-
#
|
943
|
-
#
|
1137
|
+
# Specifies an association through which to perform the query. This can be any other type
|
1138
|
+
# of association, including other <tt>:through</tt> associations. Options for <tt>:class_name</tt>,
|
1139
|
+
# <tt>:primary_key</tt> and <tt>:foreign_key</tt> are ignored, as the association uses the
|
1140
|
+
# source reflection.
|
1141
|
+
#
|
1142
|
+
# If the association on the join model is a +belongs_to+, the collection can be modified
|
1143
|
+
# and the records on the <tt>:through</tt> model will be automatically created and removed
|
1144
|
+
# as appropriate. Otherwise, the collection is read-only, so you should manipulate the
|
1145
|
+
# <tt>:through</tt> association directly.
|
1146
|
+
#
|
1147
|
+
# If you are going to modify the association (rather than just read from it), then it is
|
1148
|
+
# a good idea to set the <tt>:inverse_of</tt> option on the source association on the
|
1149
|
+
# join model. This allows associated records to be built which will automatically create
|
1150
|
+
# the appropriate join model records when they are saved. (See the 'Association Join Models'
|
1151
|
+
# section above.)
|
944
1152
|
# [:source]
|
945
1153
|
# Specifies the source association name used by <tt>has_many :through</tt> queries.
|
946
1154
|
# Only use it if the name cannot be inferred from the association.
|
947
|
-
# <tt>has_many :subscribers, :
|
1155
|
+
# <tt>has_many :subscribers, through: :subscriptions</tt> will look for either <tt>:subscribers</tt> or
|
948
1156
|
# <tt>:subscriber</tt> on Subscription, unless a <tt>:source</tt> is given.
|
949
1157
|
# [:source_type]
|
950
1158
|
# Specifies type of the source association used by <tt>has_many :through</tt> queries where the source
|
951
1159
|
# association is a polymorphic +belongs_to+.
|
952
|
-
# [:uniq]
|
953
|
-
# If true, duplicates will be omitted from the collection. Useful in conjunction with <tt>:through</tt>.
|
954
|
-
# [:readonly]
|
955
|
-
# If true, all the associated objects are readonly through the association.
|
956
1160
|
# [:validate]
|
957
1161
|
# If +false+, don't validate the associated objects when saving the parent object. true by default.
|
958
1162
|
# [:autosave]
|
959
1163
|
# If true, always save the associated objects or destroy them if marked for destruction,
|
960
1164
|
# when saving the parent object. If false, never save or destroy the associated objects.
|
961
|
-
# By default, only save associated objects that are new records.
|
1165
|
+
# By default, only save associated objects that are new records. This option is implemented as a
|
1166
|
+
# before_save callback. Because callbacks are run in the order they are defined, associated objects
|
1167
|
+
# may need to be explicitly saved in any user-defined before_save callbacks.
|
1168
|
+
#
|
1169
|
+
# Note that <tt>accepts_nested_attributes_for</tt> sets <tt>:autosave</tt> to <tt>true</tt>.
|
962
1170
|
# [:inverse_of]
|
963
1171
|
# Specifies the name of the <tt>belongs_to</tt> association on the associated object
|
964
1172
|
# that is the inverse of this <tt>has_many</tt> association. Does not work in combination
|
@@ -966,29 +1174,16 @@ module ActiveRecord
|
|
966
1174
|
# See ActiveRecord::Associations::ClassMethods's overview on Bi-directional associations for more detail.
|
967
1175
|
#
|
968
1176
|
# Option examples:
|
969
|
-
# has_many :comments,
|
970
|
-
# has_many :comments,
|
971
|
-
# has_many :people,
|
972
|
-
# has_many :tracks,
|
973
|
-
# has_many :comments, :
|
974
|
-
# has_many :tags, :
|
975
|
-
# has_many :reports,
|
976
|
-
# has_many :subscribers, :
|
977
|
-
|
978
|
-
|
979
|
-
# 'FROM people p, post_subscriptions ps ' +
|
980
|
-
# 'WHERE ps.post_id = #{id} AND ps.person_id = p.id ' +
|
981
|
-
# 'ORDER BY p.first_name'
|
982
|
-
def has_many(association_id, options = {}, &extension)
|
983
|
-
reflection = create_has_many_reflection(association_id, options, &extension)
|
984
|
-
configure_dependency_for_has_many(reflection)
|
985
|
-
add_association_callbacks(reflection.name, reflection.options)
|
986
|
-
|
987
|
-
if options[:through]
|
988
|
-
collection_accessor_methods(reflection, HasManyThroughAssociation)
|
989
|
-
else
|
990
|
-
collection_accessor_methods(reflection, HasManyAssociation)
|
991
|
-
end
|
1177
|
+
# has_many :comments, -> { order "posted_on" }
|
1178
|
+
# has_many :comments, -> { includes :author }
|
1179
|
+
# has_many :people, -> { where("deleted = 0").order("name") }, class_name: "Person"
|
1180
|
+
# has_many :tracks, -> { order "position" }, dependent: :destroy
|
1181
|
+
# has_many :comments, dependent: :nullify
|
1182
|
+
# has_many :tags, as: :taggable
|
1183
|
+
# has_many :reports, -> { readonly }
|
1184
|
+
# has_many :subscribers, through: :subscriptions, source: :user
|
1185
|
+
def has_many(name, scope = nil, options = {}, &extension)
|
1186
|
+
Builder::HasMany.build(self, name, scope, options, &extension)
|
992
1187
|
end
|
993
1188
|
|
994
1189
|
# Specifies a one-to-one association with another class. This method should only be used
|
@@ -1006,12 +1201,14 @@ module ActiveRecord
|
|
1006
1201
|
# [build_association(attributes = {})]
|
1007
1202
|
# Returns a new object of the associated type that has been instantiated
|
1008
1203
|
# with +attributes+ and linked to this object through a foreign key, but has not
|
1009
|
-
# yet been saved.
|
1010
|
-
# It will NOT work if the association is +nil+.
|
1204
|
+
# yet been saved.
|
1011
1205
|
# [create_association(attributes = {})]
|
1012
1206
|
# Returns a new object of the associated type that has been instantiated
|
1013
1207
|
# with +attributes+, linked to this object through a foreign key, and that
|
1014
1208
|
# has already been saved (if it passed the validation).
|
1209
|
+
# [create_association!(attributes = {})]
|
1210
|
+
# Does the same as <tt>create_association</tt>, but raises <tt>ActiveRecord::RecordInvalid</tt>
|
1211
|
+
# if the record is invalid.
|
1015
1212
|
#
|
1016
1213
|
# (+association+ is replaced with the symbol passed as the first argument, so
|
1017
1214
|
# <tt>has_one :manager</tt> would add among others <tt>manager.nil?</tt>.)
|
@@ -1019,10 +1216,11 @@ module ActiveRecord
|
|
1019
1216
|
# === Example
|
1020
1217
|
#
|
1021
1218
|
# An Account class declares <tt>has_one :beneficiary</tt>, which will add:
|
1022
|
-
# * <tt>Account#beneficiary</tt> (similar to <tt>Beneficiary.
|
1219
|
+
# * <tt>Account#beneficiary</tt> (similar to <tt>Beneficiary.where(account_id: id).first</tt>)
|
1023
1220
|
# * <tt>Account#beneficiary=(beneficiary)</tt> (similar to <tt>beneficiary.account_id = account.id; beneficiary.save</tt>)
|
1024
1221
|
# * <tt>Account#build_beneficiary</tt> (similar to <tt>Beneficiary.new("account_id" => id)</tt>)
|
1025
1222
|
# * <tt>Account#create_beneficiary</tt> (similar to <tt>b = Beneficiary.new("account_id" => id); b.save; b</tt>)
|
1223
|
+
# * <tt>Account#create_beneficiary!</tt> (similar to <tt>b = Beneficiary.new("account_id" => id); b.save!; b</tt>)
|
1026
1224
|
#
|
1027
1225
|
# === Options
|
1028
1226
|
#
|
@@ -1033,81 +1231,62 @@ module ActiveRecord
|
|
1033
1231
|
# Specify the class name of the association. Use it only if that name can't be inferred
|
1034
1232
|
# from the association name. So <tt>has_one :manager</tt> will by default be linked to the Manager class, but
|
1035
1233
|
# if the real class name is Person, you'll have to specify it with this option.
|
1036
|
-
# [:conditions]
|
1037
|
-
# Specify the conditions that the associated object must meet in order to be included as a +WHERE+
|
1038
|
-
# SQL fragment, such as <tt>rank = 5</tt>. Record creation from the association is scoped if a hash
|
1039
|
-
# is used. <tt>has_one :account, :conditions => {:enabled => true}</tt> will create
|
1040
|
-
# an enabled account with <tt>@company.create_account</tt> or <tt>@company.build_account</tt>.
|
1041
|
-
# [:order]
|
1042
|
-
# Specify the order in which the associated objects are returned as an <tt>ORDER BY</tt> SQL fragment,
|
1043
|
-
# such as <tt>last_name, first_name DESC</tt>.
|
1044
1234
|
# [:dependent]
|
1045
|
-
#
|
1046
|
-
#
|
1047
|
-
#
|
1048
|
-
#
|
1235
|
+
# Controls what happens to the associated object when
|
1236
|
+
# its owner is destroyed:
|
1237
|
+
#
|
1238
|
+
# * <tt>:destroy</tt> causes the associated object to also be destroyed
|
1239
|
+
# * <tt>:delete</tt> causes the associated object to be deleted directly from the database (so callbacks will not execute)
|
1240
|
+
# * <tt>:nullify</tt> causes the foreign key to be set to +NULL+. Callbacks are not executed.
|
1241
|
+
# * <tt>:restrict_with_exception</tt> causes an exception to be raised if there is an associated record
|
1242
|
+
# * <tt>:restrict_with_error</tt> causes an error to be added to the owner if there is an associated object
|
1049
1243
|
# [:foreign_key]
|
1050
1244
|
# Specify the foreign key used for the association. By default this is guessed to be the name
|
1051
1245
|
# of this class in lower-case and "_id" suffixed. So a Person class that makes a +has_one+ association
|
1052
1246
|
# will use "person_id" as the default <tt>:foreign_key</tt>.
|
1053
1247
|
# [:primary_key]
|
1054
1248
|
# Specify the method that returns the primary key used for the association. By default this is +id+.
|
1055
|
-
# [:include]
|
1056
|
-
# Specify second-order associations that should be eager loaded when this object is loaded.
|
1057
1249
|
# [:as]
|
1058
1250
|
# Specifies a polymorphic interface (See <tt>belongs_to</tt>).
|
1059
|
-
# [:select]
|
1060
|
-
# By default, this is <tt>*</tt> as in <tt>SELECT * FROM</tt>, but can be changed if, for example,
|
1061
|
-
# you want to do a join but not include the joined columns. Do not forget to include the
|
1062
|
-
# primary and foreign keys, otherwise it will raise an error.
|
1063
1251
|
# [:through]
|
1064
|
-
# Specifies a Join Model through which to perform the query.
|
1065
|
-
# and <tt>:foreign_key</tt> are ignored, as the association uses the
|
1066
|
-
# can only use a <tt>:through</tt> query through a <tt>has_one</tt>
|
1067
|
-
# association on the join model.
|
1252
|
+
# Specifies a Join Model through which to perform the query. Options for <tt>:class_name</tt>,
|
1253
|
+
# <tt>:primary_key</tt>, and <tt>:foreign_key</tt> are ignored, as the association uses the
|
1254
|
+
# source reflection. You can only use a <tt>:through</tt> query through a <tt>has_one</tt>
|
1255
|
+
# or <tt>belongs_to</tt> association on the join model.
|
1068
1256
|
# [:source]
|
1069
1257
|
# Specifies the source association name used by <tt>has_one :through</tt> queries.
|
1070
1258
|
# Only use it if the name cannot be inferred from the association.
|
1071
|
-
# <tt>has_one :favorite, :
|
1259
|
+
# <tt>has_one :favorite, through: :favorites</tt> will look for a
|
1072
1260
|
# <tt>:favorite</tt> on Favorite, unless a <tt>:source</tt> is given.
|
1073
1261
|
# [:source_type]
|
1074
1262
|
# Specifies type of the source association used by <tt>has_one :through</tt> queries where the source
|
1075
1263
|
# association is a polymorphic +belongs_to+.
|
1076
|
-
# [:readonly]
|
1077
|
-
# If true, the associated object is readonly through the association.
|
1078
1264
|
# [:validate]
|
1079
1265
|
# If +false+, don't validate the associated object when saving the parent object. +false+ by default.
|
1080
1266
|
# [:autosave]
|
1081
1267
|
# If true, always save the associated object or destroy it if marked for destruction,
|
1082
1268
|
# when saving the parent object. If false, never save or destroy the associated object.
|
1083
1269
|
# By default, only save the associated object if it's a new record.
|
1270
|
+
#
|
1271
|
+
# Note that <tt>accepts_nested_attributes_for</tt> sets <tt>:autosave</tt> to <tt>true</tt>.
|
1084
1272
|
# [:inverse_of]
|
1085
1273
|
# Specifies the name of the <tt>belongs_to</tt> association on the associated object
|
1086
|
-
# that is the inverse of this <tt>has_one</tt> association.
|
1274
|
+
# that is the inverse of this <tt>has_one</tt> association. Does not work in combination
|
1087
1275
|
# with <tt>:through</tt> or <tt>:as</tt> options.
|
1088
1276
|
# See ActiveRecord::Associations::ClassMethods's overview on Bi-directional associations for more detail.
|
1089
1277
|
#
|
1090
1278
|
# Option examples:
|
1091
|
-
# has_one :credit_card, :
|
1092
|
-
# has_one :credit_card, :
|
1279
|
+
# has_one :credit_card, dependent: :destroy # destroys the associated credit card
|
1280
|
+
# has_one :credit_card, dependent: :nullify # updates the associated records foreign
|
1093
1281
|
# # key value to NULL rather than destroying it
|
1094
|
-
# has_one :last_comment,
|
1095
|
-
# has_one :project_manager,
|
1096
|
-
# has_one :attachment, :
|
1097
|
-
# has_one :boss, :
|
1098
|
-
# has_one :club, :
|
1099
|
-
# has_one :primary_address,
|
1100
|
-
def has_one(
|
1101
|
-
|
1102
|
-
reflection = create_has_one_through_reflection(association_id, options)
|
1103
|
-
association_accessor_methods(reflection, ActiveRecord::Associations::HasOneThroughAssociation)
|
1104
|
-
else
|
1105
|
-
reflection = create_has_one_reflection(association_id, options)
|
1106
|
-
association_accessor_methods(reflection, HasOneAssociation)
|
1107
|
-
association_constructor_method(:build, reflection, HasOneAssociation)
|
1108
|
-
association_constructor_method(:create, reflection, HasOneAssociation)
|
1109
|
-
configure_dependency_for_has_one(reflection)
|
1110
|
-
end
|
1282
|
+
# has_one :last_comment, -> { order 'posted_on' }, class_name: "Comment"
|
1283
|
+
# has_one :project_manager, -> { where role: 'project_manager' }, class_name: "Person"
|
1284
|
+
# has_one :attachment, as: :attachable
|
1285
|
+
# has_one :boss, readonly: :true
|
1286
|
+
# has_one :club, through: :membership
|
1287
|
+
# has_one :primary_address, -> { where primary: true }, through: :addressables, source: :addressable
|
1288
|
+
def has_one(name, scope = nil, options = {})
|
1289
|
+
Builder::HasOne.build(self, name, scope, options)
|
1111
1290
|
end
|
1112
1291
|
|
1113
1292
|
# Specifies a one-to-one association with another class. This method should only be used
|
@@ -1129,6 +1308,9 @@ module ActiveRecord
|
|
1129
1308
|
# Returns a new object of the associated type that has been instantiated
|
1130
1309
|
# with +attributes+, linked to this object through a foreign key, and that
|
1131
1310
|
# has already been saved (if it passed the validation).
|
1311
|
+
# [create_association!(attributes = {})]
|
1312
|
+
# Does the same as <tt>create_association</tt>, but raises <tt>ActiveRecord::RecordInvalid</tt>
|
1313
|
+
# if the record is invalid.
|
1132
1314
|
#
|
1133
1315
|
# (+association+ is replaced with the symbol passed as the first argument, so
|
1134
1316
|
# <tt>belongs_to :author</tt> would add among others <tt>author.nil?</tt>.)
|
@@ -1140,27 +1322,26 @@ module ActiveRecord
|
|
1140
1322
|
# * <tt>Post#author=(author)</tt> (similar to <tt>post.author_id = author.id</tt>)
|
1141
1323
|
# * <tt>Post#build_author</tt> (similar to <tt>post.author = Author.new</tt>)
|
1142
1324
|
# * <tt>Post#create_author</tt> (similar to <tt>post.author = Author.new; post.author.save; post.author</tt>)
|
1325
|
+
# * <tt>Post#create_author!</tt> (similar to <tt>post.author = Author.new; post.author.save!; post.author</tt>)
|
1143
1326
|
# The declaration can also include an options hash to specialize the behavior of the association.
|
1144
1327
|
#
|
1145
1328
|
# === Options
|
1146
1329
|
#
|
1147
1330
|
# [:class_name]
|
1148
1331
|
# Specify the class name of the association. Use it only if that name can't be inferred
|
1149
|
-
# from the association name. So <tt>
|
1332
|
+
# from the association name. So <tt>belongs_to :author</tt> will by default be linked to the Author class, but
|
1150
1333
|
# if the real class name is Person, you'll have to specify it with this option.
|
1151
|
-
# [:conditions]
|
1152
|
-
# Specify the conditions that the associated object must meet in order to be included as a +WHERE+
|
1153
|
-
# SQL fragment, such as <tt>authorized = 1</tt>.
|
1154
|
-
# [:select]
|
1155
|
-
# By default, this is <tt>*</tt> as in <tt>SELECT * FROM</tt>, but can be changed
|
1156
|
-
# if, for example, you want to do a join but not include the joined columns. Do not
|
1157
|
-
# forget to include the primary and foreign keys, otherwise it will raise an error.
|
1158
1334
|
# [:foreign_key]
|
1159
1335
|
# Specify the foreign key used for the association. By default this is guessed to be the name
|
1160
1336
|
# of the association with an "_id" suffix. So a class that defines a <tt>belongs_to :person</tt>
|
1161
1337
|
# association will use "person_id" as the default <tt>:foreign_key</tt>. Similarly,
|
1162
|
-
# <tt>belongs_to :favorite_person, :
|
1338
|
+
# <tt>belongs_to :favorite_person, class_name: "Person"</tt> will use a foreign key
|
1163
1339
|
# of "favorite_person_id".
|
1340
|
+
# [:foreign_type]
|
1341
|
+
# Specify the column used to store the associated object's type, if this is a polymorphic
|
1342
|
+
# association. By default this is guessed to be the name of the association with a "_type"
|
1343
|
+
# suffix. So a class that defines a <tt>belongs_to :taggable, polymorphic: true</tt>
|
1344
|
+
# association will use "taggable_type" as the default <tt>:foreign_type</tt>.
|
1164
1345
|
# [:primary_key]
|
1165
1346
|
# Specify the method that returns the primary key of associated object used for the association.
|
1166
1347
|
# By default this is id.
|
@@ -1175,19 +1356,17 @@ module ActiveRecord
|
|
1175
1356
|
# and +decrement_counter+. The counter cache is incremented when an object of this
|
1176
1357
|
# class is created and decremented when it's destroyed. This requires that a column
|
1177
1358
|
# named <tt>#{table_name}_count</tt> (such as +comments_count+ for a belonging Comment class)
|
1178
|
-
# is used on the associate class (such as a Post class)
|
1359
|
+
# is used on the associate class (such as a Post class) - that is the migration for
|
1360
|
+
# <tt>#{table_name}_count</tt> is created on the associate class (such that Post.comments_count will
|
1361
|
+
# return the count cached, see note below). You can also specify a custom counter
|
1179
1362
|
# cache column by providing a column name instead of a +true+/+false+ value to this
|
1180
|
-
# option (e.g., <tt
|
1363
|
+
# option (e.g., <tt>counter_cache: :my_custom_counter</tt>.)
|
1181
1364
|
# Note: Specifying a counter cache will add it to that model's list of readonly attributes
|
1182
1365
|
# using +attr_readonly+.
|
1183
|
-
# [:include]
|
1184
|
-
# Specify second-order associations that should be eager loaded when this object is loaded.
|
1185
1366
|
# [:polymorphic]
|
1186
1367
|
# Specify this association is a polymorphic association by passing +true+.
|
1187
1368
|
# Note: If you've enabled the counter cache, then you may want to add the counter cache attribute
|
1188
1369
|
# to the +attr_readonly+ list in the associated classes (e.g. <tt>class Post; attr_readonly :comments_count; end</tt>).
|
1189
|
-
# [:readonly]
|
1190
|
-
# If true, the associated object is readonly through the association.
|
1191
1370
|
# [:validate]
|
1192
1371
|
# If +false+, don't validate the associated objects when saving the parent object. +false+ by default.
|
1193
1372
|
# [:autosave]
|
@@ -1195,77 +1374,62 @@ module ActiveRecord
|
|
1195
1374
|
# saving the parent object.
|
1196
1375
|
# If false, never save or destroy the associated object.
|
1197
1376
|
# By default, only save the associated object if it's a new record.
|
1377
|
+
#
|
1378
|
+
# Note that <tt>accepts_nested_attributes_for</tt> sets <tt>:autosave</tt> to <tt>true</tt>.
|
1198
1379
|
# [:touch]
|
1199
1380
|
# If true, the associated object will be touched (the updated_at/on attributes set to now)
|
1200
1381
|
# when this record is either saved or destroyed. If you specify a symbol, that attribute
|
1201
|
-
# will be updated with the current time
|
1382
|
+
# will be updated with the current time in addition to the updated_at/on attribute.
|
1202
1383
|
# [:inverse_of]
|
1203
1384
|
# Specifies the name of the <tt>has_one</tt> or <tt>has_many</tt> association on the associated
|
1204
|
-
# object that is the inverse of this <tt>belongs_to</tt> association.
|
1385
|
+
# object that is the inverse of this <tt>belongs_to</tt> association. Does not work in
|
1205
1386
|
# combination with the <tt>:polymorphic</tt> options.
|
1206
1387
|
# See ActiveRecord::Associations::ClassMethods's overview on Bi-directional associations for more detail.
|
1207
1388
|
#
|
1208
1389
|
# Option examples:
|
1209
|
-
# belongs_to :firm, :
|
1210
|
-
# belongs_to :person, :
|
1211
|
-
# belongs_to :author, :
|
1212
|
-
# belongs_to :valid_coupon,
|
1213
|
-
#
|
1214
|
-
# belongs_to :attachable, :
|
1215
|
-
# belongs_to :project, :
|
1216
|
-
# belongs_to :post, :
|
1217
|
-
# belongs_to :company, :
|
1218
|
-
# belongs_to :company, :
|
1219
|
-
def belongs_to(
|
1220
|
-
|
1221
|
-
|
1222
|
-
if reflection.options[:polymorphic]
|
1223
|
-
association_accessor_methods(reflection, BelongsToPolymorphicAssociation)
|
1224
|
-
else
|
1225
|
-
association_accessor_methods(reflection, BelongsToAssociation)
|
1226
|
-
association_constructor_method(:build, reflection, BelongsToAssociation)
|
1227
|
-
association_constructor_method(:create, reflection, BelongsToAssociation)
|
1228
|
-
end
|
1229
|
-
|
1230
|
-
add_counter_cache_callbacks(reflection) if options[:counter_cache]
|
1231
|
-
add_touch_callbacks(reflection, options[:touch]) if options[:touch]
|
1232
|
-
|
1233
|
-
configure_dependency_for_belongs_to(reflection)
|
1390
|
+
# belongs_to :firm, foreign_key: "client_of"
|
1391
|
+
# belongs_to :person, primary_key: "name", foreign_key: "person_name"
|
1392
|
+
# belongs_to :author, class_name: "Person", foreign_key: "author_id"
|
1393
|
+
# belongs_to :valid_coupon, ->(o) { where "discounts > #{o.payments_count}" },
|
1394
|
+
# class_name: "Coupon", foreign_key: "coupon_id"
|
1395
|
+
# belongs_to :attachable, polymorphic: true
|
1396
|
+
# belongs_to :project, readonly: true
|
1397
|
+
# belongs_to :post, counter_cache: true
|
1398
|
+
# belongs_to :company, touch: true
|
1399
|
+
# belongs_to :company, touch: :employees_last_updated_at
|
1400
|
+
def belongs_to(name, scope = nil, options = {})
|
1401
|
+
Builder::BelongsTo.build(self, name, scope, options)
|
1234
1402
|
end
|
1235
1403
|
|
1236
1404
|
# Specifies a many-to-many relationship with another class. This associates two classes via an
|
1237
|
-
# intermediate join table.
|
1405
|
+
# intermediate join table. Unless the join table is explicitly specified as an option, it is
|
1238
1406
|
# guessed using the lexical order of the class names. So a join between Developer and Project
|
1239
|
-
# will give the default join table name of "developers_projects" because "D"
|
1240
|
-
# Note that this precedence is calculated using the <tt><</tt> operator for String.
|
1407
|
+
# will give the default join table name of "developers_projects" because "D" precedes "P" alphabetically.
|
1408
|
+
# Note that this precedence is calculated using the <tt><</tt> operator for String. This
|
1241
1409
|
# means that if the strings are of different lengths, and the strings are equal when compared
|
1242
1410
|
# up to the shortest length, then the longer string is considered of higher
|
1243
|
-
# lexical precedence than the shorter one.
|
1411
|
+
# lexical precedence than the shorter one. For example, one would expect the tables "paper_boxes" and "papers"
|
1244
1412
|
# to generate a join table name of "papers_paper_boxes" because of the length of the name "paper_boxes",
|
1245
|
-
# but it in fact generates a join table name of "paper_boxes_papers".
|
1413
|
+
# but it in fact generates a join table name of "paper_boxes_papers". Be aware of this caveat, and use the
|
1246
1414
|
# custom <tt>:join_table</tt> option if you need to.
|
1415
|
+
# If your tables share a common prefix, it will only appear once at the beginning. For example,
|
1416
|
+
# the tables "catalog_categories" and "catalog_products" generate a join table name of "catalog_categories_products".
|
1247
1417
|
#
|
1248
1418
|
# The join table should not have a primary key or a model associated with it. You must manually generate the
|
1249
1419
|
# join table with a migration such as this:
|
1250
1420
|
#
|
1251
1421
|
# class CreateDevelopersProjectsJoinTable < ActiveRecord::Migration
|
1252
|
-
# def
|
1253
|
-
# create_table :developers_projects, :
|
1422
|
+
# def change
|
1423
|
+
# create_table :developers_projects, id: false do |t|
|
1254
1424
|
# t.integer :developer_id
|
1255
1425
|
# t.integer :project_id
|
1256
1426
|
# end
|
1257
1427
|
# end
|
1258
|
-
#
|
1259
|
-
# def self.down
|
1260
|
-
# drop_table :developers_projects
|
1261
|
-
# end
|
1262
1428
|
# end
|
1263
1429
|
#
|
1264
|
-
#
|
1265
|
-
#
|
1266
|
-
#
|
1267
|
-
# to the additional attributes). It's strongly recommended that you upgrade any
|
1268
|
-
# associations with attributes to a real join model (see introduction).
|
1430
|
+
# It's also a good idea to add indexes to each of those columns to speed up the joins process.
|
1431
|
+
# However, in MySQL it is advised to add a compound index for both of the columns as MySQL only
|
1432
|
+
# uses one index per table during the lookup.
|
1269
1433
|
#
|
1270
1434
|
# Adds the following methods for retrieval and query:
|
1271
1435
|
#
|
@@ -1276,10 +1440,13 @@ module ActiveRecord
|
|
1276
1440
|
# Adds one or more objects to the collection by creating associations in the join table
|
1277
1441
|
# (<tt>collection.push</tt> and <tt>collection.concat</tt> are aliases to this method).
|
1278
1442
|
# Note that this operation instantly fires update sql without waiting for the save or update call on the
|
1279
|
-
# parent object.
|
1443
|
+
# parent object, unless the parent object is a new record.
|
1280
1444
|
# [collection.delete(object, ...)]
|
1281
1445
|
# Removes one or more objects from the collection by removing their associations from the join table.
|
1282
1446
|
# This does not destroy the objects.
|
1447
|
+
# [collection.destroy(object, ...)]
|
1448
|
+
# Removes one or more objects from the collection by running destroy on each association in the join table, overriding any dependent option.
|
1449
|
+
# This does not destroy the objects.
|
1283
1450
|
# [collection=objects]
|
1284
1451
|
# Replaces the collection's content by deleting and adding objects as appropriate.
|
1285
1452
|
# [collection_singular_ids]
|
@@ -1316,6 +1483,7 @@ module ActiveRecord
|
|
1316
1483
|
# * <tt>Developer#projects</tt>
|
1317
1484
|
# * <tt>Developer#projects<<</tt>
|
1318
1485
|
# * <tt>Developer#projects.delete</tt>
|
1486
|
+
# * <tt>Developer#projects.destroy</tt>
|
1319
1487
|
# * <tt>Developer#projects=</tt>
|
1320
1488
|
# * <tt>Developer#project_ids</tt>
|
1321
1489
|
# * <tt>Developer#project_ids=</tt>
|
@@ -1324,8 +1492,8 @@ module ActiveRecord
|
|
1324
1492
|
# * <tt>Developer#projects.size</tt>
|
1325
1493
|
# * <tt>Developer#projects.find(id)</tt>
|
1326
1494
|
# * <tt>Developer#projects.exists?(...)</tt>
|
1327
|
-
# * <tt>Developer#projects.build</tt> (similar to <tt>Project.new("
|
1328
|
-
# * <tt>Developer#projects.create</tt> (similar to <tt>c = Project.new("
|
1495
|
+
# * <tt>Developer#projects.build</tt> (similar to <tt>Project.new("developer_id" => id)</tt>)
|
1496
|
+
# * <tt>Developer#projects.create</tt> (similar to <tt>c = Project.new("developer_id" => id); c.save; c</tt>)
|
1329
1497
|
# The declaration may include an options hash to specialize the behavior of the association.
|
1330
1498
|
#
|
1331
1499
|
# === Options
|
@@ -1348,47 +1516,6 @@ module ActiveRecord
|
|
1348
1516
|
# By default this is guessed to be the name of the associated class in lower-case and "_id" suffixed.
|
1349
1517
|
# So if a Person class makes a +has_and_belongs_to_many+ association to Project,
|
1350
1518
|
# the association will use "project_id" as the default <tt>:association_foreign_key</tt>.
|
1351
|
-
# [:conditions]
|
1352
|
-
# Specify the conditions that the associated object must meet in order to be included as a +WHERE+
|
1353
|
-
# SQL fragment, such as <tt>authorized = 1</tt>. Record creations from the association are
|
1354
|
-
# scoped if a hash is used.
|
1355
|
-
# <tt>has_many :posts, :conditions => {:published => true}</tt> will create published posts with <tt>@blog.posts.create</tt>
|
1356
|
-
# or <tt>@blog.posts.build</tt>.
|
1357
|
-
# [:order]
|
1358
|
-
# Specify the order in which the associated objects are returned as an <tt>ORDER BY</tt> SQL fragment,
|
1359
|
-
# such as <tt>last_name, first_name DESC</tt>
|
1360
|
-
# [:uniq]
|
1361
|
-
# If true, duplicate associated objects will be ignored by accessors and query methods.
|
1362
|
-
# [:finder_sql]
|
1363
|
-
# Overwrite the default generated SQL statement used to fetch the association with a manual statement
|
1364
|
-
# [:counter_sql]
|
1365
|
-
# Specify a complete SQL statement to fetch the size of the association. If <tt>:finder_sql</tt> is
|
1366
|
-
# specified but not <tt>:counter_sql</tt>, <tt>:counter_sql</tt> will be generated by
|
1367
|
-
# replacing <tt>SELECT ... FROM</tt> with <tt>SELECT COUNT(*) FROM</tt>.
|
1368
|
-
# [:delete_sql]
|
1369
|
-
# Overwrite the default generated SQL statement used to remove links between the associated
|
1370
|
-
# classes with a manual statement.
|
1371
|
-
# [:insert_sql]
|
1372
|
-
# Overwrite the default generated SQL statement used to add links between the associated classes
|
1373
|
-
# with a manual statement.
|
1374
|
-
# [:extend]
|
1375
|
-
# Anonymous module for extending the proxy, see "Association extensions".
|
1376
|
-
# [:include]
|
1377
|
-
# Specify second-order associations that should be eager loaded when the collection is loaded.
|
1378
|
-
# [:group]
|
1379
|
-
# An attribute name by which the result should be grouped. Uses the <tt>GROUP BY</tt> SQL-clause.
|
1380
|
-
# [:having]
|
1381
|
-
# Combined with +:group+ this can be used to filter the records that a <tt>GROUP BY</tt> returns.
|
1382
|
-
# Uses the <tt>HAVING</tt> SQL-clause.
|
1383
|
-
# [:limit]
|
1384
|
-
# An integer determining the limit on the number of rows that should be returned.
|
1385
|
-
# [:offset]
|
1386
|
-
# An integer determining the offset from where the rows should be fetched. So at 5,
|
1387
|
-
# it would skip the first 4 rows.
|
1388
|
-
# [:select]
|
1389
|
-
# By default, this is <tt>*</tt> as in <tt>SELECT * FROM</tt>, but can be changed if, for example,
|
1390
|
-
# you want to do a join but not include the joined columns. Do not forget to include the primary
|
1391
|
-
# and foreign keys, otherwise it will raise an error.
|
1392
1519
|
# [:readonly]
|
1393
1520
|
# If true, all the associated objects are readonly through the association.
|
1394
1521
|
# [:validate]
|
@@ -1399,856 +1526,17 @@ module ActiveRecord
|
|
1399
1526
|
# If false, never save or destroy the associated objects.
|
1400
1527
|
# By default, only save associated objects that are new records.
|
1401
1528
|
#
|
1529
|
+
# Note that <tt>accepts_nested_attributes_for</tt> sets <tt>:autosave</tt> to <tt>true</tt>.
|
1530
|
+
#
|
1402
1531
|
# Option examples:
|
1403
1532
|
# has_and_belongs_to_many :projects
|
1404
|
-
# has_and_belongs_to_many :projects,
|
1405
|
-
# has_and_belongs_to_many :nations, :
|
1406
|
-
# has_and_belongs_to_many :categories, :
|
1407
|
-
# has_and_belongs_to_many :categories,
|
1408
|
-
|
1409
|
-
|
1410
|
-
def has_and_belongs_to_many(association_id, options = {}, &extension)
|
1411
|
-
reflection = create_has_and_belongs_to_many_reflection(association_id, options, &extension)
|
1412
|
-
collection_accessor_methods(reflection, HasAndBelongsToManyAssociation)
|
1413
|
-
|
1414
|
-
# Don't use a before_destroy callback since users' before_destroy
|
1415
|
-
# callbacks will be executed after the association is wiped out.
|
1416
|
-
include Module.new {
|
1417
|
-
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
1418
|
-
def destroy # def destroy
|
1419
|
-
super # super
|
1420
|
-
#{reflection.name}.clear # posts.clear
|
1421
|
-
end # end
|
1422
|
-
RUBY
|
1423
|
-
}
|
1424
|
-
|
1425
|
-
add_association_callbacks(reflection.name, options)
|
1533
|
+
# has_and_belongs_to_many :projects, -> { includes :milestones, :manager }
|
1534
|
+
# has_and_belongs_to_many :nations, class_name: "Country"
|
1535
|
+
# has_and_belongs_to_many :categories, join_table: "prods_cats"
|
1536
|
+
# has_and_belongs_to_many :categories, -> { readonly }
|
1537
|
+
def has_and_belongs_to_many(name, scope = nil, options = {}, &extension)
|
1538
|
+
Builder::HasAndBelongsToMany.build(self, name, scope, options, &extension)
|
1426
1539
|
end
|
1427
|
-
|
1428
|
-
private
|
1429
|
-
# Generates a join table name from two provided table names.
|
1430
|
-
# The names in the join table names end up in lexicographic order.
|
1431
|
-
#
|
1432
|
-
# join_table_name("members", "clubs") # => "clubs_members"
|
1433
|
-
# join_table_name("members", "special_clubs") # => "members_special_clubs"
|
1434
|
-
def join_table_name(first_table_name, second_table_name)
|
1435
|
-
if first_table_name < second_table_name
|
1436
|
-
join_table = "#{first_table_name}_#{second_table_name}"
|
1437
|
-
else
|
1438
|
-
join_table = "#{second_table_name}_#{first_table_name}"
|
1439
|
-
end
|
1440
|
-
|
1441
|
-
table_name_prefix + join_table + table_name_suffix
|
1442
|
-
end
|
1443
|
-
|
1444
|
-
def association_accessor_methods(reflection, association_proxy_class)
|
1445
|
-
redefine_method(reflection.name) do |*params|
|
1446
|
-
force_reload = params.first unless params.empty?
|
1447
|
-
association = association_instance_get(reflection.name)
|
1448
|
-
|
1449
|
-
if association.nil? || force_reload
|
1450
|
-
association = association_proxy_class.new(self, reflection)
|
1451
|
-
retval = force_reload ? reflection.klass.uncached { association.reload } : association.reload
|
1452
|
-
if retval.nil? and association_proxy_class == BelongsToAssociation
|
1453
|
-
association_instance_set(reflection.name, nil)
|
1454
|
-
return nil
|
1455
|
-
end
|
1456
|
-
association_instance_set(reflection.name, association)
|
1457
|
-
end
|
1458
|
-
|
1459
|
-
association.target.nil? ? nil : association
|
1460
|
-
end
|
1461
|
-
|
1462
|
-
redefine_method("loaded_#{reflection.name}?") do
|
1463
|
-
association = association_instance_get(reflection.name)
|
1464
|
-
association && association.loaded?
|
1465
|
-
end
|
1466
|
-
|
1467
|
-
redefine_method("#{reflection.name}=") do |new_value|
|
1468
|
-
association = association_instance_get(reflection.name)
|
1469
|
-
|
1470
|
-
if association.nil? || association.target != new_value
|
1471
|
-
association = association_proxy_class.new(self, reflection)
|
1472
|
-
end
|
1473
|
-
|
1474
|
-
association.replace(new_value)
|
1475
|
-
association_instance_set(reflection.name, new_value.nil? ? nil : association)
|
1476
|
-
end
|
1477
|
-
|
1478
|
-
redefine_method("set_#{reflection.name}_target") do |target|
|
1479
|
-
return if target.nil? and association_proxy_class == BelongsToAssociation
|
1480
|
-
association = association_proxy_class.new(self, reflection)
|
1481
|
-
association.target = target
|
1482
|
-
association_instance_set(reflection.name, association)
|
1483
|
-
end
|
1484
|
-
end
|
1485
|
-
|
1486
|
-
def collection_reader_method(reflection, association_proxy_class)
|
1487
|
-
redefine_method(reflection.name) do |*params|
|
1488
|
-
force_reload = params.first unless params.empty?
|
1489
|
-
association = association_instance_get(reflection.name)
|
1490
|
-
|
1491
|
-
unless association
|
1492
|
-
association = association_proxy_class.new(self, reflection)
|
1493
|
-
association_instance_set(reflection.name, association)
|
1494
|
-
end
|
1495
|
-
|
1496
|
-
reflection.klass.uncached { association.reload } if force_reload
|
1497
|
-
|
1498
|
-
association
|
1499
|
-
end
|
1500
|
-
|
1501
|
-
redefine_method("#{reflection.name.to_s.singularize}_ids") do
|
1502
|
-
if send(reflection.name).loaded? || reflection.options[:finder_sql]
|
1503
|
-
send(reflection.name).map { |r| r.id }
|
1504
|
-
else
|
1505
|
-
if reflection.through_reflection && reflection.source_reflection.belongs_to?
|
1506
|
-
through = reflection.through_reflection
|
1507
|
-
primary_key = reflection.source_reflection.primary_key_name
|
1508
|
-
send(through.name).select("DISTINCT #{through.quoted_table_name}.#{primary_key}").map! { |r| r.send(primary_key) }
|
1509
|
-
else
|
1510
|
-
send(reflection.name).select("#{reflection.quoted_table_name}.#{reflection.klass.primary_key}").except(:includes).map! { |r| r.id }
|
1511
|
-
end
|
1512
|
-
end
|
1513
|
-
end
|
1514
|
-
|
1515
|
-
end
|
1516
|
-
|
1517
|
-
def collection_accessor_methods(reflection, association_proxy_class, writer = true)
|
1518
|
-
collection_reader_method(reflection, association_proxy_class)
|
1519
|
-
|
1520
|
-
if writer
|
1521
|
-
redefine_method("#{reflection.name}=") do |new_value|
|
1522
|
-
# Loads proxy class instance (defined in collection_reader_method) if not already loaded
|
1523
|
-
association = send(reflection.name)
|
1524
|
-
association.replace(new_value)
|
1525
|
-
association
|
1526
|
-
end
|
1527
|
-
|
1528
|
-
redefine_method("#{reflection.name.to_s.singularize}_ids=") do |new_value|
|
1529
|
-
pk_column = reflection.primary_key_column
|
1530
|
-
ids = (new_value || []).reject { |nid| nid.blank? }
|
1531
|
-
ids.map!{ |i| pk_column.type_cast(i) }
|
1532
|
-
send("#{reflection.name}=", reflection.klass.find(ids).index_by{ |r| r.id }.values_at(*ids))
|
1533
|
-
end
|
1534
|
-
end
|
1535
|
-
end
|
1536
|
-
|
1537
|
-
def association_constructor_method(constructor, reflection, association_proxy_class)
|
1538
|
-
redefine_method("#{constructor}_#{reflection.name}") do |*params|
|
1539
|
-
attributees = params.first unless params.empty?
|
1540
|
-
replace_existing = params[1].nil? ? true : params[1]
|
1541
|
-
association = association_instance_get(reflection.name)
|
1542
|
-
|
1543
|
-
unless association
|
1544
|
-
association = association_proxy_class.new(self, reflection)
|
1545
|
-
association_instance_set(reflection.name, association)
|
1546
|
-
end
|
1547
|
-
|
1548
|
-
if association_proxy_class == HasOneAssociation
|
1549
|
-
association.send(constructor, attributees, replace_existing)
|
1550
|
-
else
|
1551
|
-
association.send(constructor, attributees)
|
1552
|
-
end
|
1553
|
-
end
|
1554
|
-
end
|
1555
|
-
|
1556
|
-
def add_counter_cache_callbacks(reflection)
|
1557
|
-
cache_column = reflection.counter_cache_column
|
1558
|
-
|
1559
|
-
method_name = "belongs_to_counter_cache_after_create_for_#{reflection.name}".to_sym
|
1560
|
-
define_method(method_name) do
|
1561
|
-
association = send(reflection.name)
|
1562
|
-
association.class.increment_counter(cache_column, association.id) unless association.nil?
|
1563
|
-
end
|
1564
|
-
after_create(method_name)
|
1565
|
-
|
1566
|
-
method_name = "belongs_to_counter_cache_before_destroy_for_#{reflection.name}".to_sym
|
1567
|
-
define_method(method_name) do
|
1568
|
-
association = send(reflection.name)
|
1569
|
-
association.class.decrement_counter(cache_column, association.id) unless association.nil?
|
1570
|
-
end
|
1571
|
-
before_destroy(method_name)
|
1572
|
-
|
1573
|
-
module_eval(
|
1574
|
-
"#{reflection.class_name}.send(:attr_readonly,\"#{cache_column}\".intern) if defined?(#{reflection.class_name}) && #{reflection.class_name}.respond_to?(:attr_readonly)", __FILE__, __LINE__
|
1575
|
-
)
|
1576
|
-
end
|
1577
|
-
|
1578
|
-
def add_touch_callbacks(reflection, touch_attribute)
|
1579
|
-
method_name = :"belongs_to_touch_after_save_or_destroy_for_#{reflection.name}"
|
1580
|
-
redefine_method(method_name) do
|
1581
|
-
association = send(reflection.name)
|
1582
|
-
|
1583
|
-
if touch_attribute == true
|
1584
|
-
association.touch unless association.nil?
|
1585
|
-
else
|
1586
|
-
association.touch(touch_attribute) unless association.nil?
|
1587
|
-
end
|
1588
|
-
end
|
1589
|
-
after_save(method_name)
|
1590
|
-
after_touch(method_name)
|
1591
|
-
after_destroy(method_name)
|
1592
|
-
end
|
1593
|
-
|
1594
|
-
# Creates before_destroy callback methods that nullify, delete or destroy
|
1595
|
-
# has_many associated objects, according to the defined :dependent rule.
|
1596
|
-
# If the association is marked as :dependent => :restrict, create a callback
|
1597
|
-
# that prevents deleting entirely.
|
1598
|
-
#
|
1599
|
-
# See HasManyAssociation#delete_records. Dependent associations
|
1600
|
-
# delete children, otherwise foreign key is set to NULL.
|
1601
|
-
# See HasManyAssociation#delete_records. Dependent associations
|
1602
|
-
# delete children if the option is set to :destroy or :delete_all, set the
|
1603
|
-
# foreign key to NULL if the option is set to :nullify, and do not touch the
|
1604
|
-
# child records if the option is set to :restrict.
|
1605
|
-
#
|
1606
|
-
# The +extra_conditions+ parameter, which is not used within the main
|
1607
|
-
# Active Record codebase, is meant to allow plugins to define extra
|
1608
|
-
# finder conditions.
|
1609
|
-
def configure_dependency_for_has_many(reflection, extra_conditions = nil)
|
1610
|
-
if reflection.options.include?(:dependent)
|
1611
|
-
case reflection.options[:dependent]
|
1612
|
-
when :destroy
|
1613
|
-
method_name = "has_many_dependent_destroy_for_#{reflection.name}".to_sym
|
1614
|
-
define_method(method_name) do
|
1615
|
-
send(reflection.name).each do |o|
|
1616
|
-
# No point in executing the counter update since we're going to destroy the parent anyway
|
1617
|
-
counter_method = ('belongs_to_counter_cache_before_destroy_for_' + self.class.name.downcase).to_sym
|
1618
|
-
if(o.respond_to? counter_method) then
|
1619
|
-
class << o
|
1620
|
-
self
|
1621
|
-
end.send(:define_method, counter_method, Proc.new {})
|
1622
|
-
end
|
1623
|
-
o.destroy
|
1624
|
-
end
|
1625
|
-
end
|
1626
|
-
before_destroy method_name
|
1627
|
-
when :delete_all
|
1628
|
-
before_destroy do |record|
|
1629
|
-
self.class.send(:delete_all_has_many_dependencies,
|
1630
|
-
record,
|
1631
|
-
reflection.name,
|
1632
|
-
reflection.klass,
|
1633
|
-
reflection.dependent_conditions(record, self.class, extra_conditions))
|
1634
|
-
end
|
1635
|
-
when :nullify
|
1636
|
-
before_destroy do |record|
|
1637
|
-
self.class.send(:nullify_has_many_dependencies,
|
1638
|
-
record,
|
1639
|
-
reflection.name,
|
1640
|
-
reflection.klass,
|
1641
|
-
reflection.primary_key_name,
|
1642
|
-
reflection.dependent_conditions(record, self.class, extra_conditions))
|
1643
|
-
end
|
1644
|
-
when :restrict
|
1645
|
-
method_name = "has_many_dependent_restrict_for_#{reflection.name}".to_sym
|
1646
|
-
define_method(method_name) do
|
1647
|
-
unless send(reflection.name).empty?
|
1648
|
-
raise DeleteRestrictionError.new(reflection)
|
1649
|
-
end
|
1650
|
-
end
|
1651
|
-
before_destroy method_name
|
1652
|
-
else
|
1653
|
-
raise ArgumentError, "The :dependent option expects either :destroy, :delete_all, :nullify or :restrict (#{reflection.options[:dependent].inspect})"
|
1654
|
-
end
|
1655
|
-
end
|
1656
|
-
end
|
1657
|
-
|
1658
|
-
# Creates before_destroy callback methods that nullify, delete or destroy
|
1659
|
-
# has_one associated objects, according to the defined :dependent rule.
|
1660
|
-
# If the association is marked as :dependent => :restrict, create a callback
|
1661
|
-
# that prevents deleting entirely.
|
1662
|
-
def configure_dependency_for_has_one(reflection)
|
1663
|
-
if reflection.options.include?(:dependent)
|
1664
|
-
name = reflection.options[:dependent]
|
1665
|
-
method_name = :"has_one_dependent_#{name}_for_#{reflection.name}"
|
1666
|
-
|
1667
|
-
case name
|
1668
|
-
when :destroy, :delete
|
1669
|
-
class_eval <<-eoruby, __FILE__, __LINE__ + 1
|
1670
|
-
def #{method_name}
|
1671
|
-
association = #{reflection.name}
|
1672
|
-
association.#{name} if association
|
1673
|
-
end
|
1674
|
-
eoruby
|
1675
|
-
when :nullify
|
1676
|
-
class_eval <<-eoruby, __FILE__, __LINE__ + 1
|
1677
|
-
def #{method_name}
|
1678
|
-
association = #{reflection.name}
|
1679
|
-
association.update_attribute(#{reflection.primary_key_name.inspect}, nil) if association
|
1680
|
-
end
|
1681
|
-
eoruby
|
1682
|
-
when :restrict
|
1683
|
-
method_name = "has_one_dependent_restrict_for_#{reflection.name}".to_sym
|
1684
|
-
define_method(method_name) do
|
1685
|
-
unless send(reflection.name).nil?
|
1686
|
-
raise DeleteRestrictionError.new(reflection)
|
1687
|
-
end
|
1688
|
-
end
|
1689
|
-
before_destroy method_name
|
1690
|
-
else
|
1691
|
-
raise ArgumentError, "The :dependent option expects either :destroy, :delete, :nullify or :restrict (#{reflection.options[:dependent].inspect})"
|
1692
|
-
end
|
1693
|
-
|
1694
|
-
before_destroy method_name
|
1695
|
-
end
|
1696
|
-
end
|
1697
|
-
|
1698
|
-
def configure_dependency_for_belongs_to(reflection)
|
1699
|
-
if reflection.options.include?(:dependent)
|
1700
|
-
name = reflection.options[:dependent]
|
1701
|
-
|
1702
|
-
unless [:destroy, :delete].include?(name)
|
1703
|
-
raise ArgumentError, "The :dependent option expects either :destroy or :delete (#{reflection.options[:dependent].inspect})"
|
1704
|
-
end
|
1705
|
-
|
1706
|
-
method_name = :"belongs_to_dependent_#{name}_for_#{reflection.name}"
|
1707
|
-
class_eval <<-eoruby, __FILE__, __LINE__ + 1
|
1708
|
-
def #{method_name}
|
1709
|
-
association = #{reflection.name}
|
1710
|
-
association.#{name} if association
|
1711
|
-
end
|
1712
|
-
eoruby
|
1713
|
-
after_destroy method_name
|
1714
|
-
end
|
1715
|
-
end
|
1716
|
-
|
1717
|
-
def delete_all_has_many_dependencies(record, reflection_name, association_class, dependent_conditions)
|
1718
|
-
association_class.delete_all(dependent_conditions)
|
1719
|
-
end
|
1720
|
-
|
1721
|
-
def nullify_has_many_dependencies(record, reflection_name, association_class, primary_key_name, dependent_conditions)
|
1722
|
-
association_class.update_all("#{primary_key_name} = NULL", dependent_conditions)
|
1723
|
-
end
|
1724
|
-
|
1725
|
-
mattr_accessor :valid_keys_for_has_many_association
|
1726
|
-
@@valid_keys_for_has_many_association = [
|
1727
|
-
:class_name, :table_name, :foreign_key, :primary_key,
|
1728
|
-
:dependent,
|
1729
|
-
:select, :conditions, :include, :order, :group, :having, :limit, :offset,
|
1730
|
-
:as, :through, :source, :source_type,
|
1731
|
-
:uniq,
|
1732
|
-
:finder_sql, :counter_sql,
|
1733
|
-
:before_add, :after_add, :before_remove, :after_remove,
|
1734
|
-
:extend, :readonly,
|
1735
|
-
:validate, :inverse_of
|
1736
|
-
]
|
1737
|
-
|
1738
|
-
def create_has_many_reflection(association_id, options, &extension)
|
1739
|
-
options.assert_valid_keys(valid_keys_for_has_many_association)
|
1740
|
-
options[:extend] = create_extension_modules(association_id, extension, options[:extend])
|
1741
|
-
|
1742
|
-
create_reflection(:has_many, association_id, options, self)
|
1743
|
-
end
|
1744
|
-
|
1745
|
-
mattr_accessor :valid_keys_for_has_one_association
|
1746
|
-
@@valid_keys_for_has_one_association = [
|
1747
|
-
:class_name, :foreign_key, :remote, :select, :conditions, :order,
|
1748
|
-
:include, :dependent, :counter_cache, :extend, :as, :readonly,
|
1749
|
-
:validate, :primary_key, :inverse_of
|
1750
|
-
]
|
1751
|
-
|
1752
|
-
def create_has_one_reflection(association_id, options)
|
1753
|
-
options.assert_valid_keys(valid_keys_for_has_one_association)
|
1754
|
-
create_reflection(:has_one, association_id, options, self)
|
1755
|
-
end
|
1756
|
-
|
1757
|
-
def create_has_one_through_reflection(association_id, options)
|
1758
|
-
options.assert_valid_keys(
|
1759
|
-
:class_name, :foreign_key, :remote, :select, :conditions, :order, :include, :dependent, :counter_cache, :extend, :as, :through, :source, :source_type, :validate
|
1760
|
-
)
|
1761
|
-
create_reflection(:has_one, association_id, options, self)
|
1762
|
-
end
|
1763
|
-
|
1764
|
-
mattr_accessor :valid_keys_for_belongs_to_association
|
1765
|
-
@@valid_keys_for_belongs_to_association = [
|
1766
|
-
:class_name, :primary_key, :foreign_key, :foreign_type, :remote, :select, :conditions,
|
1767
|
-
:include, :dependent, :counter_cache, :extend, :polymorphic, :readonly,
|
1768
|
-
:validate, :touch, :inverse_of
|
1769
|
-
]
|
1770
|
-
|
1771
|
-
def create_belongs_to_reflection(association_id, options)
|
1772
|
-
options.assert_valid_keys(valid_keys_for_belongs_to_association)
|
1773
|
-
reflection = create_reflection(:belongs_to, association_id, options, self)
|
1774
|
-
|
1775
|
-
if options[:polymorphic]
|
1776
|
-
reflection.options[:foreign_type] ||= reflection.class_name.underscore + "_type"
|
1777
|
-
end
|
1778
|
-
|
1779
|
-
reflection
|
1780
|
-
end
|
1781
|
-
|
1782
|
-
mattr_accessor :valid_keys_for_has_and_belongs_to_many_association
|
1783
|
-
@@valid_keys_for_has_and_belongs_to_many_association = [
|
1784
|
-
:class_name, :table_name, :join_table, :foreign_key, :association_foreign_key,
|
1785
|
-
:select, :conditions, :include, :order, :group, :having, :limit, :offset,
|
1786
|
-
:uniq,
|
1787
|
-
:finder_sql, :counter_sql, :delete_sql, :insert_sql,
|
1788
|
-
:before_add, :after_add, :before_remove, :after_remove,
|
1789
|
-
:extend, :readonly,
|
1790
|
-
:validate
|
1791
|
-
]
|
1792
|
-
|
1793
|
-
def create_has_and_belongs_to_many_reflection(association_id, options, &extension)
|
1794
|
-
options.assert_valid_keys(valid_keys_for_has_and_belongs_to_many_association)
|
1795
|
-
options[:extend] = create_extension_modules(association_id, extension, options[:extend])
|
1796
|
-
|
1797
|
-
reflection = create_reflection(:has_and_belongs_to_many, association_id, options, self)
|
1798
|
-
|
1799
|
-
if reflection.association_foreign_key == reflection.primary_key_name
|
1800
|
-
raise HasAndBelongsToManyAssociationForeignKeyNeeded.new(reflection)
|
1801
|
-
end
|
1802
|
-
|
1803
|
-
reflection.options[:join_table] ||= join_table_name(undecorated_table_name(self.to_s), undecorated_table_name(reflection.class_name))
|
1804
|
-
if connection.supports_primary_key? && (connection.primary_key(reflection.options[:join_table]) rescue false)
|
1805
|
-
raise HasAndBelongsToManyAssociationWithPrimaryKeyError.new(reflection)
|
1806
|
-
end
|
1807
|
-
|
1808
|
-
reflection
|
1809
|
-
end
|
1810
|
-
|
1811
|
-
def add_association_callbacks(association_name, options)
|
1812
|
-
callbacks = %w(before_add after_add before_remove after_remove)
|
1813
|
-
callbacks.each do |callback_name|
|
1814
|
-
full_callback_name = "#{callback_name}_for_#{association_name}"
|
1815
|
-
defined_callbacks = options[callback_name.to_sym]
|
1816
|
-
if options.has_key?(callback_name.to_sym)
|
1817
|
-
class_inheritable_reader full_callback_name.to_sym
|
1818
|
-
write_inheritable_attribute(full_callback_name.to_sym, [defined_callbacks].flatten)
|
1819
|
-
else
|
1820
|
-
write_inheritable_attribute(full_callback_name.to_sym, [])
|
1821
|
-
end
|
1822
|
-
end
|
1823
|
-
end
|
1824
|
-
|
1825
|
-
def create_extension_modules(association_id, block_extension, extensions)
|
1826
|
-
if block_extension
|
1827
|
-
extension_module_name = "#{self.to_s.demodulize}#{association_id.to_s.camelize}AssociationExtension"
|
1828
|
-
|
1829
|
-
silence_warnings do
|
1830
|
-
self.parent.const_set(extension_module_name, Module.new(&block_extension))
|
1831
|
-
end
|
1832
|
-
Array.wrap(extensions).push("#{self.parent}::#{extension_module_name}".constantize)
|
1833
|
-
else
|
1834
|
-
Array.wrap(extensions)
|
1835
|
-
end
|
1836
|
-
end
|
1837
|
-
|
1838
|
-
class JoinDependency # :nodoc:
|
1839
|
-
attr_reader :joins, :reflections, :table_aliases
|
1840
|
-
|
1841
|
-
def initialize(base, associations, joins)
|
1842
|
-
@joins = [JoinBase.new(base, joins)]
|
1843
|
-
@associations = associations
|
1844
|
-
@reflections = []
|
1845
|
-
@base_records_hash = {}
|
1846
|
-
@base_records_in_order = []
|
1847
|
-
@table_aliases = Hash.new { |aliases, table| aliases[table] = 0 }
|
1848
|
-
@table_aliases[base.table_name] = 1
|
1849
|
-
build(associations)
|
1850
|
-
end
|
1851
|
-
|
1852
|
-
def graft(*associations)
|
1853
|
-
associations.each do |association|
|
1854
|
-
join_associations.detect {|a| association == a} ||
|
1855
|
-
build(association.reflection.name, association.find_parent_in(self) || join_base, association.join_class)
|
1856
|
-
end
|
1857
|
-
self
|
1858
|
-
end
|
1859
|
-
|
1860
|
-
def join_associations
|
1861
|
-
@joins[1..-1].to_a
|
1862
|
-
end
|
1863
|
-
|
1864
|
-
def join_base
|
1865
|
-
@joins[0]
|
1866
|
-
end
|
1867
|
-
|
1868
|
-
def count_aliases_from_table_joins(name)
|
1869
|
-
# quoted_name should be downcased as some database adapters (Oracle) return quoted name in uppercase
|
1870
|
-
quoted_name = join_base.active_record.connection.quote_table_name(name.downcase).downcase
|
1871
|
-
join_sql = join_base.table_joins.to_s.downcase
|
1872
|
-
join_sql.blank? ? 0 :
|
1873
|
-
# Table names
|
1874
|
-
join_sql.scan(/join(?:\s+\w+)?\s+#{quoted_name}\son/).size +
|
1875
|
-
# Table aliases
|
1876
|
-
join_sql.scan(/join(?:\s+\w+)?\s+\S+\s+#{quoted_name}\son/).size
|
1877
|
-
end
|
1878
|
-
|
1879
|
-
def instantiate(rows)
|
1880
|
-
rows.each_with_index do |row, i|
|
1881
|
-
primary_id = join_base.record_id(row)
|
1882
|
-
unless @base_records_hash[primary_id]
|
1883
|
-
@base_records_in_order << (@base_records_hash[primary_id] = join_base.instantiate(row))
|
1884
|
-
end
|
1885
|
-
construct(@base_records_hash[primary_id], @associations, join_associations.dup, row)
|
1886
|
-
end
|
1887
|
-
remove_duplicate_results!(join_base.active_record, @base_records_in_order, @associations)
|
1888
|
-
return @base_records_in_order
|
1889
|
-
end
|
1890
|
-
|
1891
|
-
def remove_duplicate_results!(base, records, associations)
|
1892
|
-
case associations
|
1893
|
-
when Symbol, String
|
1894
|
-
reflection = base.reflections[associations]
|
1895
|
-
remove_uniq_by_reflection(reflection, records)
|
1896
|
-
when Array
|
1897
|
-
associations.each do |association|
|
1898
|
-
remove_duplicate_results!(base, records, association)
|
1899
|
-
end
|
1900
|
-
when Hash
|
1901
|
-
associations.keys.each do |name|
|
1902
|
-
reflection = base.reflections[name]
|
1903
|
-
remove_uniq_by_reflection(reflection, records)
|
1904
|
-
|
1905
|
-
parent_records = []
|
1906
|
-
records.each do |record|
|
1907
|
-
if descendant = record.send(reflection.name)
|
1908
|
-
if reflection.collection?
|
1909
|
-
parent_records.concat descendant.target.uniq
|
1910
|
-
else
|
1911
|
-
parent_records << descendant
|
1912
|
-
end
|
1913
|
-
end
|
1914
|
-
end
|
1915
|
-
|
1916
|
-
remove_duplicate_results!(reflection.klass, parent_records, associations[name]) unless parent_records.empty?
|
1917
|
-
end
|
1918
|
-
end
|
1919
|
-
end
|
1920
|
-
|
1921
|
-
protected
|
1922
|
-
|
1923
|
-
def build(associations, parent = nil, join_class = Arel::InnerJoin)
|
1924
|
-
parent ||= @joins.last
|
1925
|
-
case associations
|
1926
|
-
when Symbol, String
|
1927
|
-
reflection = parent.reflections[associations.to_s.intern] or
|
1928
|
-
raise ConfigurationError, "Association named '#{ associations }' was not found; perhaps you misspelled it?"
|
1929
|
-
@reflections << reflection
|
1930
|
-
@joins << build_join_association(reflection, parent).with_join_class(join_class)
|
1931
|
-
when Array
|
1932
|
-
associations.each do |association|
|
1933
|
-
build(association, parent, join_class)
|
1934
|
-
end
|
1935
|
-
when Hash
|
1936
|
-
associations.keys.sort{|a,b|a.to_s<=>b.to_s}.each do |name|
|
1937
|
-
build(name, parent, join_class)
|
1938
|
-
build(associations[name], nil, join_class)
|
1939
|
-
end
|
1940
|
-
else
|
1941
|
-
raise ConfigurationError, associations.inspect
|
1942
|
-
end
|
1943
|
-
end
|
1944
|
-
|
1945
|
-
def remove_uniq_by_reflection(reflection, records)
|
1946
|
-
if reflection && reflection.collection?
|
1947
|
-
records.each { |record| record.send(reflection.name).target.uniq! }
|
1948
|
-
end
|
1949
|
-
end
|
1950
|
-
|
1951
|
-
def build_join_association(reflection, parent)
|
1952
|
-
JoinAssociation.new(reflection, self, parent)
|
1953
|
-
end
|
1954
|
-
|
1955
|
-
def construct(parent, associations, joins, row)
|
1956
|
-
case associations
|
1957
|
-
when Symbol, String
|
1958
|
-
join = joins.detect{|j| j.reflection.name.to_s == associations.to_s && j.parent_table_name == parent.class.table_name }
|
1959
|
-
raise(ConfigurationError, "No such association") if join.nil?
|
1960
|
-
|
1961
|
-
joins.delete(join)
|
1962
|
-
construct_association(parent, join, row)
|
1963
|
-
when Array
|
1964
|
-
associations.each do |association|
|
1965
|
-
construct(parent, association, joins, row)
|
1966
|
-
end
|
1967
|
-
when Hash
|
1968
|
-
associations.keys.sort{|a,b|a.to_s<=>b.to_s}.each do |name|
|
1969
|
-
join = joins.detect{|j| j.reflection.name.to_s == name.to_s && j.parent_table_name == parent.class.table_name }
|
1970
|
-
raise(ConfigurationError, "No such association") if join.nil?
|
1971
|
-
|
1972
|
-
association = construct_association(parent, join, row)
|
1973
|
-
joins.delete(join)
|
1974
|
-
construct(association, associations[name], joins, row) if association
|
1975
|
-
end
|
1976
|
-
else
|
1977
|
-
raise ConfigurationError, associations.inspect
|
1978
|
-
end
|
1979
|
-
end
|
1980
|
-
|
1981
|
-
def construct_association(record, join, row)
|
1982
|
-
case join.reflection.macro
|
1983
|
-
when :has_many, :has_and_belongs_to_many
|
1984
|
-
collection = record.send(join.reflection.name)
|
1985
|
-
collection.loaded
|
1986
|
-
|
1987
|
-
return nil if record.id.to_s != join.parent.record_id(row).to_s or row[join.aliased_primary_key].nil?
|
1988
|
-
association = join.instantiate(row)
|
1989
|
-
collection.target.push(association)
|
1990
|
-
collection.__send__(:set_inverse_instance, association, record)
|
1991
|
-
when :has_one
|
1992
|
-
return if record.id.to_s != join.parent.record_id(row).to_s
|
1993
|
-
return if record.instance_variable_defined?("@#{join.reflection.name}")
|
1994
|
-
association = join.instantiate(row) unless row[join.aliased_primary_key].nil?
|
1995
|
-
set_target_and_inverse(join, association, record)
|
1996
|
-
when :belongs_to
|
1997
|
-
return if record.id.to_s != join.parent.record_id(row).to_s or row[join.aliased_primary_key].nil?
|
1998
|
-
association = join.instantiate(row)
|
1999
|
-
set_target_and_inverse(join, association, record)
|
2000
|
-
else
|
2001
|
-
raise ConfigurationError, "unknown macro: #{join.reflection.macro}"
|
2002
|
-
end
|
2003
|
-
return association
|
2004
|
-
end
|
2005
|
-
|
2006
|
-
def set_target_and_inverse(join, association, record)
|
2007
|
-
association_proxy = record.send("set_#{join.reflection.name}_target", association)
|
2008
|
-
association_proxy.__send__(:set_inverse_instance, association, record)
|
2009
|
-
end
|
2010
|
-
|
2011
|
-
class JoinBase # :nodoc:
|
2012
|
-
attr_reader :active_record, :table_joins
|
2013
|
-
delegate :table_name, :column_names, :primary_key, :reflections, :sanitize_sql, :arel_engine, :to => :active_record
|
2014
|
-
|
2015
|
-
def initialize(active_record, joins = nil)
|
2016
|
-
@active_record = active_record
|
2017
|
-
@cached_record = {}
|
2018
|
-
@table_joins = joins
|
2019
|
-
end
|
2020
|
-
|
2021
|
-
def ==(other)
|
2022
|
-
other.class == self.class &&
|
2023
|
-
other.active_record == active_record &&
|
2024
|
-
other.table_joins == table_joins
|
2025
|
-
end
|
2026
|
-
|
2027
|
-
def aliased_prefix
|
2028
|
-
"t0"
|
2029
|
-
end
|
2030
|
-
|
2031
|
-
def aliased_primary_key
|
2032
|
-
"#{aliased_prefix}_r0"
|
2033
|
-
end
|
2034
|
-
|
2035
|
-
def aliased_table_name
|
2036
|
-
active_record.table_name
|
2037
|
-
end
|
2038
|
-
|
2039
|
-
def column_names_with_alias
|
2040
|
-
unless defined?(@column_names_with_alias)
|
2041
|
-
@column_names_with_alias = []
|
2042
|
-
|
2043
|
-
([primary_key] + (column_names - [primary_key])).each_with_index do |column_name, i|
|
2044
|
-
@column_names_with_alias << [column_name, "#{aliased_prefix}_r#{i}"]
|
2045
|
-
end
|
2046
|
-
end
|
2047
|
-
|
2048
|
-
@column_names_with_alias
|
2049
|
-
end
|
2050
|
-
|
2051
|
-
def extract_record(row)
|
2052
|
-
column_names_with_alias.inject({}){|record, (cn, an)| record[cn] = row[an]; record}
|
2053
|
-
end
|
2054
|
-
|
2055
|
-
def record_id(row)
|
2056
|
-
row[aliased_primary_key]
|
2057
|
-
end
|
2058
|
-
|
2059
|
-
def instantiate(row)
|
2060
|
-
@cached_record[record_id(row)] ||= active_record.send(:instantiate, extract_record(row))
|
2061
|
-
end
|
2062
|
-
end
|
2063
|
-
|
2064
|
-
class JoinAssociation < JoinBase # :nodoc:
|
2065
|
-
attr_reader :reflection, :parent, :aliased_table_name, :aliased_prefix, :aliased_join_table_name, :parent_table_name, :join_class
|
2066
|
-
delegate :options, :klass, :through_reflection, :source_reflection, :to => :reflection
|
2067
|
-
|
2068
|
-
def initialize(reflection, join_dependency, parent = nil)
|
2069
|
-
reflection.check_validity!
|
2070
|
-
if reflection.options[:polymorphic]
|
2071
|
-
raise EagerLoadPolymorphicError.new(reflection)
|
2072
|
-
end
|
2073
|
-
|
2074
|
-
super(reflection.klass)
|
2075
|
-
@join_dependency = join_dependency
|
2076
|
-
@parent = parent
|
2077
|
-
@reflection = reflection
|
2078
|
-
@aliased_prefix = "t#{ join_dependency.joins.size }"
|
2079
|
-
@parent_table_name = parent.active_record.table_name
|
2080
|
-
@aliased_table_name = aliased_table_name_for(table_name)
|
2081
|
-
@join = nil
|
2082
|
-
@join_class = Arel::InnerJoin
|
2083
|
-
|
2084
|
-
if reflection.macro == :has_and_belongs_to_many
|
2085
|
-
@aliased_join_table_name = aliased_table_name_for(reflection.options[:join_table], "_join")
|
2086
|
-
end
|
2087
|
-
|
2088
|
-
if [:has_many, :has_one].include?(reflection.macro) && reflection.options[:through]
|
2089
|
-
@aliased_join_table_name = aliased_table_name_for(reflection.through_reflection.klass.table_name, "_join")
|
2090
|
-
end
|
2091
|
-
end
|
2092
|
-
|
2093
|
-
def ==(other)
|
2094
|
-
other.class == self.class &&
|
2095
|
-
other.reflection == reflection &&
|
2096
|
-
other.parent == parent
|
2097
|
-
end
|
2098
|
-
|
2099
|
-
def find_parent_in(other_join_dependency)
|
2100
|
-
other_join_dependency.joins.detect do |join|
|
2101
|
-
self.parent == join
|
2102
|
-
end
|
2103
|
-
end
|
2104
|
-
|
2105
|
-
def with_join_class(join_class)
|
2106
|
-
@join_class = join_class
|
2107
|
-
self
|
2108
|
-
end
|
2109
|
-
|
2110
|
-
def association_join
|
2111
|
-
return @join if @join
|
2112
|
-
|
2113
|
-
aliased_table = Arel::Table.new(table_name, :as => @aliased_table_name, :engine => arel_engine)
|
2114
|
-
parent_table = Arel::Table.new(parent.table_name, :as => parent.aliased_table_name, :engine => arel_engine)
|
2115
|
-
|
2116
|
-
@join = case reflection.macro
|
2117
|
-
when :has_and_belongs_to_many
|
2118
|
-
join_table = Arel::Table.new(options[:join_table], :as => aliased_join_table_name, :engine => arel_engine)
|
2119
|
-
fk = options[:foreign_key] || reflection.active_record.to_s.foreign_key
|
2120
|
-
klass_fk = options[:association_foreign_key] || klass.to_s.foreign_key
|
2121
|
-
|
2122
|
-
[
|
2123
|
-
join_table[fk].eq(parent_table[reflection.active_record.primary_key]),
|
2124
|
-
aliased_table[klass.primary_key].eq(join_table[klass_fk])
|
2125
|
-
]
|
2126
|
-
when :has_many, :has_one
|
2127
|
-
if reflection.options[:through]
|
2128
|
-
join_table = Arel::Table.new(through_reflection.klass.table_name, :as => aliased_join_table_name, :engine => arel_engine)
|
2129
|
-
jt_foreign_key = jt_as_extra = jt_source_extra = jt_sti_extra = nil
|
2130
|
-
first_key = second_key = as_extra = nil
|
2131
|
-
|
2132
|
-
if through_reflection.options[:as] # has_many :through against a polymorphic join
|
2133
|
-
jt_foreign_key = through_reflection.options[:as].to_s + '_id'
|
2134
|
-
jt_as_extra = join_table[through_reflection.options[:as].to_s + '_type'].eq(parent.active_record.base_class.name)
|
2135
|
-
else
|
2136
|
-
jt_foreign_key = through_reflection.primary_key_name
|
2137
|
-
end
|
2138
|
-
|
2139
|
-
case source_reflection.macro
|
2140
|
-
when :has_many
|
2141
|
-
if source_reflection.options[:as]
|
2142
|
-
first_key = "#{source_reflection.options[:as]}_id"
|
2143
|
-
second_key = options[:foreign_key] || primary_key
|
2144
|
-
as_extra = aliased_table["#{source_reflection.options[:as]}_type"].eq(source_reflection.active_record.base_class.name)
|
2145
|
-
else
|
2146
|
-
first_key = through_reflection.klass.base_class.to_s.foreign_key
|
2147
|
-
second_key = options[:foreign_key] || primary_key
|
2148
|
-
end
|
2149
|
-
|
2150
|
-
unless through_reflection.klass.descends_from_active_record?
|
2151
|
-
jt_sti_extra = join_table[through_reflection.active_record.inheritance_column].eq(through_reflection.klass.sti_name)
|
2152
|
-
end
|
2153
|
-
when :belongs_to
|
2154
|
-
first_key = primary_key
|
2155
|
-
if reflection.options[:source_type]
|
2156
|
-
second_key = source_reflection.association_foreign_key
|
2157
|
-
jt_source_extra = join_table[reflection.source_reflection.options[:foreign_type]].eq(reflection.options[:source_type])
|
2158
|
-
else
|
2159
|
-
second_key = source_reflection.primary_key_name
|
2160
|
-
end
|
2161
|
-
end
|
2162
|
-
|
2163
|
-
[
|
2164
|
-
[parent_table[parent.primary_key].eq(join_table[jt_foreign_key]), jt_as_extra, jt_source_extra, jt_sti_extra].reject{|x| x.blank? },
|
2165
|
-
aliased_table[first_key].eq(join_table[second_key])
|
2166
|
-
]
|
2167
|
-
elsif reflection.options[:as]
|
2168
|
-
id_rel = aliased_table["#{reflection.options[:as]}_id"].eq(parent_table[parent.primary_key])
|
2169
|
-
type_rel = aliased_table["#{reflection.options[:as]}_type"].eq(parent.active_record.base_class.name)
|
2170
|
-
[id_rel, type_rel]
|
2171
|
-
else
|
2172
|
-
foreign_key = options[:foreign_key] || reflection.active_record.name.foreign_key
|
2173
|
-
[aliased_table[foreign_key].eq(parent_table[reflection.options[:primary_key] || parent.primary_key])]
|
2174
|
-
end
|
2175
|
-
when :belongs_to
|
2176
|
-
[aliased_table[options[:primary_key] || reflection.klass.primary_key].eq(parent_table[options[:foreign_key] || reflection.primary_key_name])]
|
2177
|
-
end
|
2178
|
-
|
2179
|
-
unless klass.descends_from_active_record?
|
2180
|
-
sti_column = aliased_table[klass.inheritance_column]
|
2181
|
-
sti_condition = sti_column.eq(klass.sti_name)
|
2182
|
-
klass.descendants.each {|subclass| sti_condition = sti_condition.or(sti_column.eq(subclass.sti_name)) }
|
2183
|
-
|
2184
|
-
@join << sti_condition
|
2185
|
-
end
|
2186
|
-
|
2187
|
-
[through_reflection, reflection].each do |ref|
|
2188
|
-
if ref && ref.options[:conditions]
|
2189
|
-
@join << interpolate_sql(sanitize_sql(ref.options[:conditions], aliased_table_name))
|
2190
|
-
end
|
2191
|
-
end
|
2192
|
-
|
2193
|
-
@join
|
2194
|
-
end
|
2195
|
-
|
2196
|
-
def relation
|
2197
|
-
aliased = Arel::Table.new(table_name, :as => @aliased_table_name, :engine => arel_engine)
|
2198
|
-
|
2199
|
-
if reflection.macro == :has_and_belongs_to_many
|
2200
|
-
[Arel::Table.new(options[:join_table], :as => aliased_join_table_name, :engine => arel_engine), aliased]
|
2201
|
-
elsif reflection.options[:through]
|
2202
|
-
[Arel::Table.new(through_reflection.klass.table_name, :as => aliased_join_table_name, :engine => arel_engine), aliased]
|
2203
|
-
else
|
2204
|
-
aliased
|
2205
|
-
end
|
2206
|
-
end
|
2207
|
-
|
2208
|
-
def join_relation(joining_relation, join = nil)
|
2209
|
-
joining_relation.joins(self.with_join_class(Arel::OuterJoin))
|
2210
|
-
end
|
2211
|
-
|
2212
|
-
protected
|
2213
|
-
|
2214
|
-
def aliased_table_name_for(name, suffix = nil)
|
2215
|
-
if @join_dependency.table_aliases[name].zero?
|
2216
|
-
@join_dependency.table_aliases[name] = @join_dependency.count_aliases_from_table_joins(name)
|
2217
|
-
end
|
2218
|
-
|
2219
|
-
if !@join_dependency.table_aliases[name].zero? # We need an alias
|
2220
|
-
name = active_record.connection.table_alias_for "#{pluralize(reflection.name)}_#{parent_table_name}#{suffix}"
|
2221
|
-
@join_dependency.table_aliases[name] += 1
|
2222
|
-
if @join_dependency.table_aliases[name] == 1 # First time we've seen this name
|
2223
|
-
# Also need to count the aliases from the table_aliases to avoid incorrect count
|
2224
|
-
@join_dependency.table_aliases[name] += @join_dependency.count_aliases_from_table_joins(name)
|
2225
|
-
end
|
2226
|
-
table_index = @join_dependency.table_aliases[name]
|
2227
|
-
name = name[0..active_record.connection.table_alias_length-3] + "_#{table_index}" if table_index > 1
|
2228
|
-
else
|
2229
|
-
@join_dependency.table_aliases[name] += 1
|
2230
|
-
end
|
2231
|
-
|
2232
|
-
name
|
2233
|
-
end
|
2234
|
-
|
2235
|
-
def pluralize(table_name)
|
2236
|
-
ActiveRecord::Base.pluralize_table_names ? table_name.to_s.pluralize : table_name
|
2237
|
-
end
|
2238
|
-
|
2239
|
-
def table_alias_for(table_name, table_alias)
|
2240
|
-
"#{table_name} #{table_alias if table_name != table_alias}".strip
|
2241
|
-
end
|
2242
|
-
|
2243
|
-
def table_name_and_alias
|
2244
|
-
table_alias_for table_name, @aliased_table_name
|
2245
|
-
end
|
2246
|
-
|
2247
|
-
def interpolate_sql(sql)
|
2248
|
-
instance_eval("%@#{sql.gsub('@', '\@')}@", __FILE__, __LINE__)
|
2249
|
-
end
|
2250
|
-
end
|
2251
|
-
end
|
2252
1540
|
end
|
2253
1541
|
end
|
2254
1542
|
end
|