activerecord 4.2.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 +1372 -0
- data/MIT-LICENSE +20 -0
- data/README.rdoc +218 -0
- data/examples/performance.rb +184 -0
- data/examples/simple.rb +14 -0
- data/lib/active_record.rb +173 -0
- data/lib/active_record/aggregations.rb +266 -0
- data/lib/active_record/association_relation.rb +22 -0
- data/lib/active_record/associations.rb +1724 -0
- data/lib/active_record/associations/alias_tracker.rb +87 -0
- data/lib/active_record/associations/association.rb +253 -0
- data/lib/active_record/associations/association_scope.rb +194 -0
- data/lib/active_record/associations/belongs_to_association.rb +111 -0
- data/lib/active_record/associations/belongs_to_polymorphic_association.rb +40 -0
- data/lib/active_record/associations/builder/association.rb +149 -0
- data/lib/active_record/associations/builder/belongs_to.rb +116 -0
- data/lib/active_record/associations/builder/collection_association.rb +91 -0
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +124 -0
- data/lib/active_record/associations/builder/has_many.rb +15 -0
- data/lib/active_record/associations/builder/has_one.rb +23 -0
- data/lib/active_record/associations/builder/singular_association.rb +38 -0
- data/lib/active_record/associations/collection_association.rb +634 -0
- data/lib/active_record/associations/collection_proxy.rb +1027 -0
- data/lib/active_record/associations/has_many_association.rb +184 -0
- data/lib/active_record/associations/has_many_through_association.rb +238 -0
- data/lib/active_record/associations/has_one_association.rb +105 -0
- data/lib/active_record/associations/has_one_through_association.rb +36 -0
- data/lib/active_record/associations/join_dependency.rb +282 -0
- data/lib/active_record/associations/join_dependency/join_association.rb +122 -0
- data/lib/active_record/associations/join_dependency/join_base.rb +22 -0
- data/lib/active_record/associations/join_dependency/join_part.rb +71 -0
- data/lib/active_record/associations/preloader.rb +203 -0
- data/lib/active_record/associations/preloader/association.rb +162 -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_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 +96 -0
- data/lib/active_record/associations/singular_association.rb +86 -0
- data/lib/active_record/associations/through_association.rb +96 -0
- data/lib/active_record/attribute.rb +149 -0
- data/lib/active_record/attribute_assignment.rb +212 -0
- data/lib/active_record/attribute_decorators.rb +66 -0
- data/lib/active_record/attribute_methods.rb +439 -0
- data/lib/active_record/attribute_methods/before_type_cast.rb +71 -0
- data/lib/active_record/attribute_methods/dirty.rb +181 -0
- data/lib/active_record/attribute_methods/primary_key.rb +128 -0
- data/lib/active_record/attribute_methods/query.rb +40 -0
- data/lib/active_record/attribute_methods/read.rb +103 -0
- data/lib/active_record/attribute_methods/serialization.rb +70 -0
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +65 -0
- data/lib/active_record/attribute_methods/write.rb +83 -0
- data/lib/active_record/attribute_set.rb +77 -0
- data/lib/active_record/attribute_set/builder.rb +86 -0
- data/lib/active_record/attributes.rb +139 -0
- data/lib/active_record/autosave_association.rb +439 -0
- data/lib/active_record/base.rb +317 -0
- data/lib/active_record/callbacks.rb +313 -0
- data/lib/active_record/coders/json.rb +13 -0
- data/lib/active_record/coders/yaml_column.rb +38 -0
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +659 -0
- data/lib/active_record/connection_adapters/abstract/database_limits.rb +67 -0
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +373 -0
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +95 -0
- data/lib/active_record/connection_adapters/abstract/quoting.rb +133 -0
- data/lib/active_record/connection_adapters/abstract/savepoints.rb +21 -0
- data/lib/active_record/connection_adapters/abstract/schema_creation.rb +125 -0
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +574 -0
- data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +50 -0
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +991 -0
- data/lib/active_record/connection_adapters/abstract/transaction.rb +219 -0
- data/lib/active_record/connection_adapters/abstract_adapter.rb +487 -0
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +883 -0
- data/lib/active_record/connection_adapters/column.rb +82 -0
- data/lib/active_record/connection_adapters/connection_specification.rb +275 -0
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +282 -0
- data/lib/active_record/connection_adapters/mysql_adapter.rb +491 -0
- data/lib/active_record/connection_adapters/postgresql/array_parser.rb +93 -0
- data/lib/active_record/connection_adapters/postgresql/column.rb +20 -0
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +232 -0
- data/lib/active_record/connection_adapters/postgresql/oid.rb +36 -0
- data/lib/active_record/connection_adapters/postgresql/oid/array.rb +99 -0
- data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +52 -0
- data/lib/active_record/connection_adapters/postgresql/oid/bit_varying.rb +13 -0
- data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +14 -0
- data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +46 -0
- data/lib/active_record/connection_adapters/postgresql/oid/date.rb +11 -0
- data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +27 -0
- data/lib/active_record/connection_adapters/postgresql/oid/decimal.rb +13 -0
- data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +17 -0
- data/lib/active_record/connection_adapters/postgresql/oid/float.rb +21 -0
- data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +59 -0
- data/lib/active_record/connection_adapters/postgresql/oid/inet.rb +13 -0
- data/lib/active_record/connection_adapters/postgresql/oid/infinity.rb +13 -0
- data/lib/active_record/connection_adapters/postgresql/oid/integer.rb +11 -0
- data/lib/active_record/connection_adapters/postgresql/oid/json.rb +35 -0
- data/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb +23 -0
- data/lib/active_record/connection_adapters/postgresql/oid/money.rb +43 -0
- data/lib/active_record/connection_adapters/postgresql/oid/point.rb +43 -0
- data/lib/active_record/connection_adapters/postgresql/oid/range.rb +79 -0
- data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +15 -0
- data/lib/active_record/connection_adapters/postgresql/oid/time.rb +11 -0
- data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +97 -0
- data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +21 -0
- data/lib/active_record/connection_adapters/postgresql/oid/vector.rb +26 -0
- data/lib/active_record/connection_adapters/postgresql/oid/xml.rb +28 -0
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +108 -0
- data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +30 -0
- data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +152 -0
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +588 -0
- data/lib/active_record/connection_adapters/postgresql/utils.rb +77 -0
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +754 -0
- data/lib/active_record/connection_adapters/schema_cache.rb +94 -0
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +628 -0
- data/lib/active_record/connection_adapters/statement_pool.rb +40 -0
- data/lib/active_record/connection_handling.rb +132 -0
- data/lib/active_record/core.rb +566 -0
- data/lib/active_record/counter_cache.rb +175 -0
- data/lib/active_record/dynamic_matchers.rb +140 -0
- data/lib/active_record/enum.rb +198 -0
- data/lib/active_record/errors.rb +252 -0
- 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 +56 -0
- data/lib/active_record/fixtures.rb +1007 -0
- data/lib/active_record/gem_version.rb +15 -0
- data/lib/active_record/inheritance.rb +247 -0
- data/lib/active_record/integration.rb +113 -0
- data/lib/active_record/locale/en.yml +47 -0
- data/lib/active_record/locking/optimistic.rb +204 -0
- data/lib/active_record/locking/pessimistic.rb +77 -0
- data/lib/active_record/log_subscriber.rb +75 -0
- data/lib/active_record/migration.rb +1051 -0
- data/lib/active_record/migration/command_recorder.rb +197 -0
- data/lib/active_record/migration/join_table.rb +15 -0
- data/lib/active_record/model_schema.rb +340 -0
- data/lib/active_record/nested_attributes.rb +548 -0
- data/lib/active_record/no_touching.rb +52 -0
- data/lib/active_record/null_relation.rb +81 -0
- data/lib/active_record/persistence.rb +532 -0
- data/lib/active_record/query_cache.rb +56 -0
- data/lib/active_record/querying.rb +68 -0
- data/lib/active_record/railtie.rb +162 -0
- data/lib/active_record/railties/console_sandbox.rb +5 -0
- data/lib/active_record/railties/controller_runtime.rb +50 -0
- data/lib/active_record/railties/databases.rake +391 -0
- data/lib/active_record/railties/jdbcmysql_error.rb +16 -0
- data/lib/active_record/readonly_attributes.rb +23 -0
- data/lib/active_record/reflection.rb +881 -0
- data/lib/active_record/relation.rb +681 -0
- data/lib/active_record/relation/batches.rb +138 -0
- data/lib/active_record/relation/calculations.rb +403 -0
- data/lib/active_record/relation/delegation.rb +140 -0
- data/lib/active_record/relation/finder_methods.rb +528 -0
- data/lib/active_record/relation/merger.rb +170 -0
- data/lib/active_record/relation/predicate_builder.rb +126 -0
- data/lib/active_record/relation/predicate_builder/array_handler.rb +47 -0
- data/lib/active_record/relation/predicate_builder/relation_handler.rb +13 -0
- data/lib/active_record/relation/query_methods.rb +1176 -0
- data/lib/active_record/relation/spawn_methods.rb +75 -0
- data/lib/active_record/result.rb +131 -0
- data/lib/active_record/runtime_registry.rb +22 -0
- data/lib/active_record/sanitization.rb +191 -0
- data/lib/active_record/schema.rb +64 -0
- data/lib/active_record/schema_dumper.rb +251 -0
- data/lib/active_record/schema_migration.rb +56 -0
- data/lib/active_record/scoping.rb +87 -0
- data/lib/active_record/scoping/default.rb +134 -0
- data/lib/active_record/scoping/named.rb +164 -0
- data/lib/active_record/serialization.rb +22 -0
- data/lib/active_record/serializers/xml_serializer.rb +193 -0
- data/lib/active_record/statement_cache.rb +111 -0
- data/lib/active_record/store.rb +205 -0
- data/lib/active_record/tasks/database_tasks.rb +296 -0
- data/lib/active_record/tasks/mysql_database_tasks.rb +145 -0
- data/lib/active_record/tasks/postgresql_database_tasks.rb +90 -0
- data/lib/active_record/tasks/sqlite_database_tasks.rb +55 -0
- data/lib/active_record/timestamp.rb +121 -0
- data/lib/active_record/transactions.rb +417 -0
- data/lib/active_record/translation.rb +22 -0
- data/lib/active_record/type.rb +23 -0
- data/lib/active_record/type/big_integer.rb +13 -0
- data/lib/active_record/type/binary.rb +50 -0
- data/lib/active_record/type/boolean.rb +30 -0
- data/lib/active_record/type/date.rb +46 -0
- data/lib/active_record/type/date_time.rb +43 -0
- data/lib/active_record/type/decimal.rb +40 -0
- data/lib/active_record/type/decimal_without_scale.rb +11 -0
- data/lib/active_record/type/decorator.rb +14 -0
- data/lib/active_record/type/float.rb +19 -0
- data/lib/active_record/type/hash_lookup_type_map.rb +17 -0
- data/lib/active_record/type/integer.rb +55 -0
- data/lib/active_record/type/mutable.rb +16 -0
- data/lib/active_record/type/numeric.rb +36 -0
- data/lib/active_record/type/serialized.rb +56 -0
- data/lib/active_record/type/string.rb +36 -0
- data/lib/active_record/type/text.rb +11 -0
- data/lib/active_record/type/time.rb +26 -0
- data/lib/active_record/type/time_value.rb +38 -0
- data/lib/active_record/type/type_map.rb +64 -0
- data/lib/active_record/type/unsigned_integer.rb +15 -0
- data/lib/active_record/type/value.rb +101 -0
- data/lib/active_record/validations.rb +90 -0
- data/lib/active_record/validations/associated.rb +51 -0
- data/lib/active_record/validations/presence.rb +67 -0
- data/lib/active_record/validations/uniqueness.rb +229 -0
- data/lib/active_record/version.rb +8 -0
- data/lib/rails/generators/active_record.rb +17 -0
- data/lib/rails/generators/active_record/migration.rb +18 -0
- data/lib/rails/generators/active_record/migration/migration_generator.rb +70 -0
- data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb +22 -0
- data/lib/rails/generators/active_record/migration/templates/migration.rb +45 -0
- data/lib/rails/generators/active_record/model/model_generator.rb +52 -0
- data/lib/rails/generators/active_record/model/templates/model.rb +10 -0
- data/lib/rails/generators/active_record/model/templates/module.rb +7 -0
- metadata +309 -0
@@ -0,0 +1,16 @@
|
|
1
|
+
#FIXME Remove if ArJdbcMysql will give.
|
2
|
+
module ArJdbcMySQL #:nodoc:
|
3
|
+
class Error < StandardError #:nodoc:
|
4
|
+
attr_accessor :error_number, :sql_state
|
5
|
+
|
6
|
+
def initialize msg
|
7
|
+
super
|
8
|
+
@error_number = nil
|
9
|
+
@sql_state = nil
|
10
|
+
end
|
11
|
+
|
12
|
+
# Mysql gem compatibility
|
13
|
+
alias_method :errno, :error_number
|
14
|
+
alias_method :error, :message
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module ActiveRecord
|
2
|
+
module ReadonlyAttributes
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
included do
|
6
|
+
class_attribute :_attr_readonly, instance_accessor: false
|
7
|
+
self._attr_readonly = []
|
8
|
+
end
|
9
|
+
|
10
|
+
module ClassMethods
|
11
|
+
# Attributes listed as readonly will be used to create a new record but update operations will
|
12
|
+
# ignore these fields.
|
13
|
+
def attr_readonly(*attributes)
|
14
|
+
self._attr_readonly = Set.new(attributes.map { |a| a.to_s }) + (self._attr_readonly || [])
|
15
|
+
end
|
16
|
+
|
17
|
+
# Returns an array of all the attributes that have been specified as readonly.
|
18
|
+
def readonly_attributes
|
19
|
+
self._attr_readonly
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,881 @@
|
|
1
|
+
require 'thread'
|
2
|
+
require 'active_support/core_ext/string/filters'
|
3
|
+
|
4
|
+
module ActiveRecord
|
5
|
+
# = Active Record Reflection
|
6
|
+
module Reflection # :nodoc:
|
7
|
+
extend ActiveSupport::Concern
|
8
|
+
|
9
|
+
included do
|
10
|
+
class_attribute :_reflections
|
11
|
+
class_attribute :aggregate_reflections
|
12
|
+
self._reflections = {}
|
13
|
+
self.aggregate_reflections = {}
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.create(macro, name, scope, options, ar)
|
17
|
+
klass = case macro
|
18
|
+
when :composed_of
|
19
|
+
AggregateReflection
|
20
|
+
when :has_many
|
21
|
+
HasManyReflection
|
22
|
+
when :has_one
|
23
|
+
HasOneReflection
|
24
|
+
when :belongs_to
|
25
|
+
BelongsToReflection
|
26
|
+
else
|
27
|
+
raise "Unsupported Macro: #{macro}"
|
28
|
+
end
|
29
|
+
|
30
|
+
reflection = klass.new(name, scope, options, ar)
|
31
|
+
options[:through] ? ThroughReflection.new(reflection) : reflection
|
32
|
+
end
|
33
|
+
|
34
|
+
def self.add_reflection(ar, name, reflection)
|
35
|
+
ar._reflections = ar._reflections.merge(name.to_s => reflection)
|
36
|
+
end
|
37
|
+
|
38
|
+
def self.add_aggregate_reflection(ar, name, reflection)
|
39
|
+
ar.aggregate_reflections = ar.aggregate_reflections.merge(name.to_s => reflection)
|
40
|
+
end
|
41
|
+
|
42
|
+
# \Reflection enables interrogating of Active Record classes and objects
|
43
|
+
# about their associations and aggregations. This information can,
|
44
|
+
# for example, be used in a form builder that takes an Active Record object
|
45
|
+
# and creates input fields for all of the attributes depending on their type
|
46
|
+
# and displays the associations to other objects.
|
47
|
+
#
|
48
|
+
# MacroReflection class has info for AggregateReflection and AssociationReflection
|
49
|
+
# classes.
|
50
|
+
module ClassMethods
|
51
|
+
# Returns an array of AggregateReflection objects for all the aggregations in the class.
|
52
|
+
def reflect_on_all_aggregations
|
53
|
+
aggregate_reflections.values
|
54
|
+
end
|
55
|
+
|
56
|
+
# Returns the AggregateReflection object for the named +aggregation+ (use the symbol).
|
57
|
+
#
|
58
|
+
# Account.reflect_on_aggregation(:balance) # => the balance AggregateReflection
|
59
|
+
#
|
60
|
+
def reflect_on_aggregation(aggregation)
|
61
|
+
aggregate_reflections[aggregation.to_s]
|
62
|
+
end
|
63
|
+
|
64
|
+
# Returns a Hash of name of the reflection as the key and a AssociationReflection as the value.
|
65
|
+
#
|
66
|
+
# Account.reflections # => {"balance" => AggregateReflection}
|
67
|
+
#
|
68
|
+
# @api public
|
69
|
+
def reflections
|
70
|
+
ref = {}
|
71
|
+
_reflections.each do |name, reflection|
|
72
|
+
parent_name, parent_reflection = reflection.parent_reflection
|
73
|
+
if parent_name
|
74
|
+
ref[parent_name] = parent_reflection
|
75
|
+
else
|
76
|
+
ref[name] = reflection
|
77
|
+
end
|
78
|
+
end
|
79
|
+
ref
|
80
|
+
end
|
81
|
+
|
82
|
+
# Returns an array of AssociationReflection objects for all the
|
83
|
+
# associations in the class. If you only want to reflect on a certain
|
84
|
+
# association type, pass in the symbol (<tt>:has_many</tt>, <tt>:has_one</tt>,
|
85
|
+
# <tt>:belongs_to</tt>) as the first parameter.
|
86
|
+
#
|
87
|
+
# Example:
|
88
|
+
#
|
89
|
+
# Account.reflect_on_all_associations # returns an array of all associations
|
90
|
+
# Account.reflect_on_all_associations(:has_many) # returns an array of all has_many associations
|
91
|
+
#
|
92
|
+
# @api public
|
93
|
+
def reflect_on_all_associations(macro = nil)
|
94
|
+
association_reflections = reflections.values
|
95
|
+
macro ? association_reflections.select { |reflection| reflection.macro == macro } : association_reflections
|
96
|
+
end
|
97
|
+
|
98
|
+
# Returns the AssociationReflection object for the +association+ (use the symbol).
|
99
|
+
#
|
100
|
+
# Account.reflect_on_association(:owner) # returns the owner AssociationReflection
|
101
|
+
# Invoice.reflect_on_association(:line_items).macro # returns :has_many
|
102
|
+
#
|
103
|
+
# @api public
|
104
|
+
def reflect_on_association(association)
|
105
|
+
reflections[association.to_s]
|
106
|
+
end
|
107
|
+
|
108
|
+
# @api private
|
109
|
+
def _reflect_on_association(association) #:nodoc:
|
110
|
+
_reflections[association.to_s]
|
111
|
+
end
|
112
|
+
|
113
|
+
# Returns an array of AssociationReflection objects for all associations which have <tt>:autosave</tt> enabled.
|
114
|
+
#
|
115
|
+
# @api public
|
116
|
+
def reflect_on_all_autosave_associations
|
117
|
+
reflections.values.select { |reflection| reflection.options[:autosave] }
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
# Holds all the methods that are shared between MacroReflection, AssociationReflection
|
122
|
+
# and ThroughReflection
|
123
|
+
class AbstractReflection # :nodoc:
|
124
|
+
def table_name
|
125
|
+
klass.table_name
|
126
|
+
end
|
127
|
+
|
128
|
+
# Returns a new, unsaved instance of the associated class. +attributes+ will
|
129
|
+
# be passed to the class's constructor.
|
130
|
+
def build_association(attributes, &block)
|
131
|
+
klass.new(attributes, &block)
|
132
|
+
end
|
133
|
+
|
134
|
+
def quoted_table_name
|
135
|
+
klass.quoted_table_name
|
136
|
+
end
|
137
|
+
|
138
|
+
def primary_key_type
|
139
|
+
klass.type_for_attribute(klass.primary_key)
|
140
|
+
end
|
141
|
+
|
142
|
+
# Returns the class name for the macro.
|
143
|
+
#
|
144
|
+
# <tt>composed_of :balance, class_name: 'Money'</tt> returns <tt>'Money'</tt>
|
145
|
+
# <tt>has_many :clients</tt> returns <tt>'Client'</tt>
|
146
|
+
def class_name
|
147
|
+
@class_name ||= (options[:class_name] || derive_class_name).to_s
|
148
|
+
end
|
149
|
+
|
150
|
+
JoinKeys = Struct.new(:key, :foreign_key) # :nodoc:
|
151
|
+
|
152
|
+
def join_keys(assoc_klass)
|
153
|
+
JoinKeys.new(foreign_key, active_record_primary_key)
|
154
|
+
end
|
155
|
+
|
156
|
+
def source_macro
|
157
|
+
ActiveSupport::Deprecation.warn(<<-MSG.squish)
|
158
|
+
ActiveRecord::Base.source_macro is deprecated and will be removed
|
159
|
+
without replacement.
|
160
|
+
MSG
|
161
|
+
|
162
|
+
macro
|
163
|
+
end
|
164
|
+
end
|
165
|
+
# Base class for AggregateReflection and AssociationReflection. Objects of
|
166
|
+
# AggregateReflection and AssociationReflection are returned by the Reflection::ClassMethods.
|
167
|
+
#
|
168
|
+
# MacroReflection
|
169
|
+
# AssociationReflection
|
170
|
+
# AggregateReflection
|
171
|
+
# HasManyReflection
|
172
|
+
# HasOneReflection
|
173
|
+
# BelongsToReflection
|
174
|
+
# ThroughReflection
|
175
|
+
class MacroReflection < AbstractReflection
|
176
|
+
# Returns the name of the macro.
|
177
|
+
#
|
178
|
+
# <tt>composed_of :balance, class_name: 'Money'</tt> returns <tt>:balance</tt>
|
179
|
+
# <tt>has_many :clients</tt> returns <tt>:clients</tt>
|
180
|
+
attr_reader :name
|
181
|
+
|
182
|
+
attr_reader :scope
|
183
|
+
|
184
|
+
# Returns the hash of options used for the macro.
|
185
|
+
#
|
186
|
+
# <tt>composed_of :balance, class_name: 'Money'</tt> returns <tt>{ class_name: "Money" }</tt>
|
187
|
+
# <tt>has_many :clients</tt> returns <tt>{}</tt>
|
188
|
+
attr_reader :options
|
189
|
+
|
190
|
+
attr_reader :active_record
|
191
|
+
|
192
|
+
attr_reader :plural_name # :nodoc:
|
193
|
+
|
194
|
+
def initialize(name, scope, options, active_record)
|
195
|
+
@name = name
|
196
|
+
@scope = scope
|
197
|
+
@options = options
|
198
|
+
@active_record = active_record
|
199
|
+
@klass = options[:class]
|
200
|
+
@plural_name = active_record.pluralize_table_names ?
|
201
|
+
name.to_s.pluralize : name.to_s
|
202
|
+
end
|
203
|
+
|
204
|
+
def autosave=(autosave)
|
205
|
+
@automatic_inverse_of = false
|
206
|
+
@options[:autosave] = autosave
|
207
|
+
_, parent_reflection = self.parent_reflection
|
208
|
+
if parent_reflection
|
209
|
+
parent_reflection.autosave = autosave
|
210
|
+
end
|
211
|
+
end
|
212
|
+
|
213
|
+
# Returns the class for the macro.
|
214
|
+
#
|
215
|
+
# <tt>composed_of :balance, class_name: 'Money'</tt> returns the Money class
|
216
|
+
# <tt>has_many :clients</tt> returns the Client class
|
217
|
+
def klass
|
218
|
+
@klass ||= compute_class(class_name)
|
219
|
+
end
|
220
|
+
|
221
|
+
def compute_class(name)
|
222
|
+
name.constantize
|
223
|
+
end
|
224
|
+
|
225
|
+
# Returns +true+ if +self+ and +other_aggregation+ have the same +name+ attribute, +active_record+ attribute,
|
226
|
+
# and +other_aggregation+ has an options hash assigned to it.
|
227
|
+
def ==(other_aggregation)
|
228
|
+
super ||
|
229
|
+
other_aggregation.kind_of?(self.class) &&
|
230
|
+
name == other_aggregation.name &&
|
231
|
+
!other_aggregation.options.nil? &&
|
232
|
+
active_record == other_aggregation.active_record
|
233
|
+
end
|
234
|
+
|
235
|
+
private
|
236
|
+
def derive_class_name
|
237
|
+
name.to_s.camelize
|
238
|
+
end
|
239
|
+
end
|
240
|
+
|
241
|
+
|
242
|
+
# Holds all the meta-data about an aggregation as it was specified in the
|
243
|
+
# Active Record class.
|
244
|
+
class AggregateReflection < MacroReflection #:nodoc:
|
245
|
+
def mapping
|
246
|
+
mapping = options[:mapping] || [name, name]
|
247
|
+
mapping.first.is_a?(Array) ? mapping : [mapping]
|
248
|
+
end
|
249
|
+
end
|
250
|
+
|
251
|
+
# Holds all the meta-data about an association as it was specified in the
|
252
|
+
# Active Record class.
|
253
|
+
class AssociationReflection < MacroReflection #:nodoc:
|
254
|
+
# Returns the target association's class.
|
255
|
+
#
|
256
|
+
# class Author < ActiveRecord::Base
|
257
|
+
# has_many :books
|
258
|
+
# end
|
259
|
+
#
|
260
|
+
# Author.reflect_on_association(:books).klass
|
261
|
+
# # => Book
|
262
|
+
#
|
263
|
+
# <b>Note:</b> Do not call +klass.new+ or +klass.create+ to instantiate
|
264
|
+
# a new association object. Use +build_association+ or +create_association+
|
265
|
+
# instead. This allows plugins to hook into association object creation.
|
266
|
+
def klass
|
267
|
+
@klass ||= compute_class(class_name)
|
268
|
+
end
|
269
|
+
|
270
|
+
def compute_class(name)
|
271
|
+
active_record.send(:compute_type, name)
|
272
|
+
end
|
273
|
+
|
274
|
+
attr_reader :type, :foreign_type
|
275
|
+
attr_accessor :parent_reflection # [:name, Reflection]
|
276
|
+
|
277
|
+
def initialize(name, scope, options, active_record)
|
278
|
+
super
|
279
|
+
@automatic_inverse_of = nil
|
280
|
+
@type = options[:as] && (options[:foreign_type] || "#{options[:as]}_type")
|
281
|
+
@foreign_type = options[:foreign_type] || "#{name}_type"
|
282
|
+
@constructable = calculate_constructable(macro, options)
|
283
|
+
@association_scope_cache = {}
|
284
|
+
@scope_lock = Mutex.new
|
285
|
+
end
|
286
|
+
|
287
|
+
def association_scope_cache(conn, owner)
|
288
|
+
key = conn.prepared_statements
|
289
|
+
if polymorphic?
|
290
|
+
key = [key, owner._read_attribute(@foreign_type)]
|
291
|
+
end
|
292
|
+
@association_scope_cache[key] ||= @scope_lock.synchronize {
|
293
|
+
@association_scope_cache[key] ||= yield
|
294
|
+
}
|
295
|
+
end
|
296
|
+
|
297
|
+
def constructable? # :nodoc:
|
298
|
+
@constructable
|
299
|
+
end
|
300
|
+
|
301
|
+
def join_table
|
302
|
+
@join_table ||= options[:join_table] || derive_join_table
|
303
|
+
end
|
304
|
+
|
305
|
+
def foreign_key
|
306
|
+
@foreign_key ||= options[:foreign_key] || derive_foreign_key
|
307
|
+
end
|
308
|
+
|
309
|
+
def association_foreign_key
|
310
|
+
@association_foreign_key ||= options[:association_foreign_key] || class_name.foreign_key
|
311
|
+
end
|
312
|
+
|
313
|
+
# klass option is necessary to support loading polymorphic associations
|
314
|
+
def association_primary_key(klass = nil)
|
315
|
+
options[:primary_key] || primary_key(klass || self.klass)
|
316
|
+
end
|
317
|
+
|
318
|
+
def active_record_primary_key
|
319
|
+
@active_record_primary_key ||= options[:primary_key] || primary_key(active_record)
|
320
|
+
end
|
321
|
+
|
322
|
+
def counter_cache_column
|
323
|
+
if options[:counter_cache] == true
|
324
|
+
"#{active_record.name.demodulize.underscore.pluralize}_count"
|
325
|
+
elsif options[:counter_cache]
|
326
|
+
options[:counter_cache].to_s
|
327
|
+
end
|
328
|
+
end
|
329
|
+
|
330
|
+
def check_validity!
|
331
|
+
check_validity_of_inverse!
|
332
|
+
end
|
333
|
+
|
334
|
+
def check_validity_of_inverse!
|
335
|
+
unless polymorphic?
|
336
|
+
if has_inverse? && inverse_of.nil?
|
337
|
+
raise InverseOfAssociationNotFoundError.new(self)
|
338
|
+
end
|
339
|
+
end
|
340
|
+
end
|
341
|
+
|
342
|
+
def check_preloadable!
|
343
|
+
return unless scope
|
344
|
+
|
345
|
+
if scope.arity > 0
|
346
|
+
ActiveSupport::Deprecation.warn(<<-MSG.squish)
|
347
|
+
The association scope '#{name}' is instance dependent (the scope
|
348
|
+
block takes an argument). Preloading happens before the individual
|
349
|
+
instances are created. This means that there is no instance being
|
350
|
+
passed to the association scope. This will most likely result in
|
351
|
+
broken or incorrect behavior. Joining, Preloading and eager loading
|
352
|
+
of these associations is deprecated and will be removed in the future.
|
353
|
+
MSG
|
354
|
+
end
|
355
|
+
end
|
356
|
+
alias :check_eager_loadable! :check_preloadable!
|
357
|
+
|
358
|
+
def join_id_for(owner) # :nodoc:
|
359
|
+
owner[active_record_primary_key]
|
360
|
+
end
|
361
|
+
|
362
|
+
def through_reflection
|
363
|
+
nil
|
364
|
+
end
|
365
|
+
|
366
|
+
def source_reflection
|
367
|
+
self
|
368
|
+
end
|
369
|
+
|
370
|
+
# A chain of reflections from this one back to the owner. For more see the explanation in
|
371
|
+
# ThroughReflection.
|
372
|
+
def chain
|
373
|
+
[self]
|
374
|
+
end
|
375
|
+
|
376
|
+
def nested?
|
377
|
+
false
|
378
|
+
end
|
379
|
+
|
380
|
+
# An array of arrays of scopes. Each item in the outside array corresponds to a reflection
|
381
|
+
# in the #chain.
|
382
|
+
def scope_chain
|
383
|
+
scope ? [[scope]] : [[]]
|
384
|
+
end
|
385
|
+
|
386
|
+
def has_inverse?
|
387
|
+
inverse_name
|
388
|
+
end
|
389
|
+
|
390
|
+
def inverse_of
|
391
|
+
return unless inverse_name
|
392
|
+
|
393
|
+
@inverse_of ||= klass._reflect_on_association inverse_name
|
394
|
+
end
|
395
|
+
|
396
|
+
def polymorphic_inverse_of(associated_class)
|
397
|
+
if has_inverse?
|
398
|
+
if inverse_relationship = associated_class._reflect_on_association(options[:inverse_of])
|
399
|
+
inverse_relationship
|
400
|
+
else
|
401
|
+
raise InverseOfAssociationNotFoundError.new(self, associated_class)
|
402
|
+
end
|
403
|
+
end
|
404
|
+
end
|
405
|
+
|
406
|
+
# Returns the macro type.
|
407
|
+
#
|
408
|
+
# <tt>has_many :clients</tt> returns <tt>:has_many</tt>
|
409
|
+
def macro; raise NotImplementedError; end
|
410
|
+
|
411
|
+
# Returns whether or not this association reflection is for a collection
|
412
|
+
# association. Returns +true+ if the +macro+ is either +has_many+ or
|
413
|
+
# +has_and_belongs_to_many+, +false+ otherwise.
|
414
|
+
def collection?
|
415
|
+
false
|
416
|
+
end
|
417
|
+
|
418
|
+
# Returns whether or not the association should be validated as part of
|
419
|
+
# the parent's validation.
|
420
|
+
#
|
421
|
+
# Unless you explicitly disable validation with
|
422
|
+
# <tt>validate: false</tt>, validation will take place when:
|
423
|
+
#
|
424
|
+
# * you explicitly enable validation; <tt>validate: true</tt>
|
425
|
+
# * you use autosave; <tt>autosave: true</tt>
|
426
|
+
# * the association is a +has_many+ association
|
427
|
+
def validate?
|
428
|
+
!options[:validate].nil? ? options[:validate] : (options[:autosave] == true || collection?)
|
429
|
+
end
|
430
|
+
|
431
|
+
# Returns +true+ if +self+ is a +belongs_to+ reflection.
|
432
|
+
def belongs_to?; false; end
|
433
|
+
|
434
|
+
# Returns +true+ if +self+ is a +has_one+ reflection.
|
435
|
+
def has_one?; false; end
|
436
|
+
|
437
|
+
def association_class
|
438
|
+
case macro
|
439
|
+
when :belongs_to
|
440
|
+
if polymorphic?
|
441
|
+
Associations::BelongsToPolymorphicAssociation
|
442
|
+
else
|
443
|
+
Associations::BelongsToAssociation
|
444
|
+
end
|
445
|
+
when :has_many
|
446
|
+
if options[:through]
|
447
|
+
Associations::HasManyThroughAssociation
|
448
|
+
else
|
449
|
+
Associations::HasManyAssociation
|
450
|
+
end
|
451
|
+
when :has_one
|
452
|
+
if options[:through]
|
453
|
+
Associations::HasOneThroughAssociation
|
454
|
+
else
|
455
|
+
Associations::HasOneAssociation
|
456
|
+
end
|
457
|
+
end
|
458
|
+
end
|
459
|
+
|
460
|
+
def polymorphic?
|
461
|
+
options[:polymorphic]
|
462
|
+
end
|
463
|
+
|
464
|
+
VALID_AUTOMATIC_INVERSE_MACROS = [:has_many, :has_one, :belongs_to]
|
465
|
+
INVALID_AUTOMATIC_INVERSE_OPTIONS = [:conditions, :through, :polymorphic, :foreign_key]
|
466
|
+
|
467
|
+
protected
|
468
|
+
|
469
|
+
def actual_source_reflection # FIXME: this is a horrible name
|
470
|
+
self
|
471
|
+
end
|
472
|
+
|
473
|
+
private
|
474
|
+
|
475
|
+
def calculate_constructable(macro, options)
|
476
|
+
case macro
|
477
|
+
when :belongs_to
|
478
|
+
!polymorphic?
|
479
|
+
when :has_one
|
480
|
+
!options[:through]
|
481
|
+
else
|
482
|
+
true
|
483
|
+
end
|
484
|
+
end
|
485
|
+
|
486
|
+
# Attempts to find the inverse association name automatically.
|
487
|
+
# If it cannot find a suitable inverse association name, it returns
|
488
|
+
# nil.
|
489
|
+
def inverse_name
|
490
|
+
options.fetch(:inverse_of) do
|
491
|
+
if @automatic_inverse_of == false
|
492
|
+
nil
|
493
|
+
else
|
494
|
+
@automatic_inverse_of ||= automatic_inverse_of
|
495
|
+
end
|
496
|
+
end
|
497
|
+
end
|
498
|
+
|
499
|
+
# returns either nil or the inverse association name that it finds.
|
500
|
+
def automatic_inverse_of
|
501
|
+
if can_find_inverse_of_automatically?(self)
|
502
|
+
inverse_name = ActiveSupport::Inflector.underscore(options[:as] || active_record.name).to_sym
|
503
|
+
|
504
|
+
begin
|
505
|
+
reflection = klass._reflect_on_association(inverse_name)
|
506
|
+
rescue NameError
|
507
|
+
# Give up: we couldn't compute the klass type so we won't be able
|
508
|
+
# to find any associations either.
|
509
|
+
reflection = false
|
510
|
+
end
|
511
|
+
|
512
|
+
if valid_inverse_reflection?(reflection)
|
513
|
+
return inverse_name
|
514
|
+
end
|
515
|
+
end
|
516
|
+
|
517
|
+
false
|
518
|
+
end
|
519
|
+
|
520
|
+
# Checks if the inverse reflection that is returned from the
|
521
|
+
# +automatic_inverse_of+ method is a valid reflection. We must
|
522
|
+
# make sure that the reflection's active_record name matches up
|
523
|
+
# with the current reflection's klass name.
|
524
|
+
#
|
525
|
+
# Note: klass will always be valid because when there's a NameError
|
526
|
+
# from calling +klass+, +reflection+ will already be set to false.
|
527
|
+
def valid_inverse_reflection?(reflection)
|
528
|
+
reflection &&
|
529
|
+
klass.name == reflection.active_record.name &&
|
530
|
+
can_find_inverse_of_automatically?(reflection)
|
531
|
+
end
|
532
|
+
|
533
|
+
# Checks to see if the reflection doesn't have any options that prevent
|
534
|
+
# us from being able to guess the inverse automatically. First, the
|
535
|
+
# <tt>inverse_of</tt> option cannot be set to false. Second, we must
|
536
|
+
# have <tt>has_many</tt>, <tt>has_one</tt>, <tt>belongs_to</tt> associations.
|
537
|
+
# Third, we must not have options such as <tt>:polymorphic</tt> or
|
538
|
+
# <tt>:foreign_key</tt> which prevent us from correctly guessing the
|
539
|
+
# inverse association.
|
540
|
+
#
|
541
|
+
# Anything with a scope can additionally ruin our attempt at finding an
|
542
|
+
# inverse, so we exclude reflections with scopes.
|
543
|
+
def can_find_inverse_of_automatically?(reflection)
|
544
|
+
reflection.options[:inverse_of] != false &&
|
545
|
+
VALID_AUTOMATIC_INVERSE_MACROS.include?(reflection.macro) &&
|
546
|
+
!INVALID_AUTOMATIC_INVERSE_OPTIONS.any? { |opt| reflection.options[opt] } &&
|
547
|
+
!reflection.scope
|
548
|
+
end
|
549
|
+
|
550
|
+
def derive_class_name
|
551
|
+
class_name = name.to_s
|
552
|
+
class_name = class_name.singularize if collection?
|
553
|
+
class_name.camelize
|
554
|
+
end
|
555
|
+
|
556
|
+
def derive_foreign_key
|
557
|
+
if belongs_to?
|
558
|
+
"#{name}_id"
|
559
|
+
elsif options[:as]
|
560
|
+
"#{options[:as]}_id"
|
561
|
+
else
|
562
|
+
active_record.name.foreign_key
|
563
|
+
end
|
564
|
+
end
|
565
|
+
|
566
|
+
def derive_join_table
|
567
|
+
ModelSchema.derive_join_table_name active_record.table_name, klass.table_name
|
568
|
+
end
|
569
|
+
|
570
|
+
def primary_key(klass)
|
571
|
+
klass.primary_key || raise(UnknownPrimaryKey.new(klass))
|
572
|
+
end
|
573
|
+
end
|
574
|
+
|
575
|
+
class HasManyReflection < AssociationReflection # :nodoc:
|
576
|
+
def initialize(name, scope, options, active_record)
|
577
|
+
super(name, scope, options, active_record)
|
578
|
+
end
|
579
|
+
|
580
|
+
def macro; :has_many; end
|
581
|
+
|
582
|
+
def collection?; true; end
|
583
|
+
end
|
584
|
+
|
585
|
+
class HasOneReflection < AssociationReflection # :nodoc:
|
586
|
+
def initialize(name, scope, options, active_record)
|
587
|
+
super(name, scope, options, active_record)
|
588
|
+
end
|
589
|
+
|
590
|
+
def macro; :has_one; end
|
591
|
+
|
592
|
+
def has_one?; true; end
|
593
|
+
end
|
594
|
+
|
595
|
+
class BelongsToReflection < AssociationReflection # :nodoc:
|
596
|
+
def initialize(name, scope, options, active_record)
|
597
|
+
super(name, scope, options, active_record)
|
598
|
+
end
|
599
|
+
|
600
|
+
def macro; :belongs_to; end
|
601
|
+
|
602
|
+
def belongs_to?; true; end
|
603
|
+
|
604
|
+
def join_keys(assoc_klass)
|
605
|
+
key = polymorphic? ? association_primary_key(assoc_klass) : association_primary_key
|
606
|
+
JoinKeys.new(key, foreign_key)
|
607
|
+
end
|
608
|
+
|
609
|
+
def join_id_for(owner) # :nodoc:
|
610
|
+
owner[foreign_key]
|
611
|
+
end
|
612
|
+
end
|
613
|
+
|
614
|
+
class HasAndBelongsToManyReflection < AssociationReflection # :nodoc:
|
615
|
+
def initialize(name, scope, options, active_record)
|
616
|
+
super
|
617
|
+
end
|
618
|
+
|
619
|
+
def macro; :has_and_belongs_to_many; end
|
620
|
+
|
621
|
+
def collection?
|
622
|
+
true
|
623
|
+
end
|
624
|
+
end
|
625
|
+
|
626
|
+
# Holds all the meta-data about a :through association as it was specified
|
627
|
+
# in the Active Record class.
|
628
|
+
class ThroughReflection < AbstractReflection #:nodoc:
|
629
|
+
attr_reader :delegate_reflection
|
630
|
+
delegate :foreign_key, :foreign_type, :association_foreign_key,
|
631
|
+
:active_record_primary_key, :type, :to => :source_reflection
|
632
|
+
|
633
|
+
def initialize(delegate_reflection)
|
634
|
+
@delegate_reflection = delegate_reflection
|
635
|
+
@klass = delegate_reflection.options[:class]
|
636
|
+
@source_reflection_name = delegate_reflection.options[:source]
|
637
|
+
end
|
638
|
+
|
639
|
+
def klass
|
640
|
+
@klass ||= delegate_reflection.compute_class(class_name)
|
641
|
+
end
|
642
|
+
|
643
|
+
# Returns the source of the through reflection. It checks both a singularized
|
644
|
+
# and pluralized form for <tt>:belongs_to</tt> or <tt>:has_many</tt>.
|
645
|
+
#
|
646
|
+
# class Post < ActiveRecord::Base
|
647
|
+
# has_many :taggings
|
648
|
+
# has_many :tags, through: :taggings
|
649
|
+
# end
|
650
|
+
#
|
651
|
+
# class Tagging < ActiveRecord::Base
|
652
|
+
# belongs_to :post
|
653
|
+
# belongs_to :tag
|
654
|
+
# end
|
655
|
+
#
|
656
|
+
# tags_reflection = Post.reflect_on_association(:tags)
|
657
|
+
# tags_reflection.source_reflection
|
658
|
+
# # => <ActiveRecord::Reflection::BelongsToReflection: @name=:tag, @active_record=Tagging, @plural_name="tags">
|
659
|
+
#
|
660
|
+
def source_reflection
|
661
|
+
through_reflection.klass._reflect_on_association(source_reflection_name)
|
662
|
+
end
|
663
|
+
|
664
|
+
# Returns the AssociationReflection object specified in the <tt>:through</tt> option
|
665
|
+
# of a HasManyThrough or HasOneThrough association.
|
666
|
+
#
|
667
|
+
# class Post < ActiveRecord::Base
|
668
|
+
# has_many :taggings
|
669
|
+
# has_many :tags, through: :taggings
|
670
|
+
# end
|
671
|
+
#
|
672
|
+
# tags_reflection = Post.reflect_on_association(:tags)
|
673
|
+
# tags_reflection.through_reflection
|
674
|
+
# # => <ActiveRecord::Reflection::HasManyReflection: @name=:taggings, @active_record=Post, @plural_name="taggings">
|
675
|
+
#
|
676
|
+
def through_reflection
|
677
|
+
active_record._reflect_on_association(options[:through])
|
678
|
+
end
|
679
|
+
|
680
|
+
# Returns an array of reflections which are involved in this association. Each item in the
|
681
|
+
# array corresponds to a table which will be part of the query for this association.
|
682
|
+
#
|
683
|
+
# The chain is built by recursively calling #chain on the source reflection and the through
|
684
|
+
# reflection. The base case for the recursion is a normal association, which just returns
|
685
|
+
# [self] as its #chain.
|
686
|
+
#
|
687
|
+
# class Post < ActiveRecord::Base
|
688
|
+
# has_many :taggings
|
689
|
+
# has_many :tags, through: :taggings
|
690
|
+
# end
|
691
|
+
#
|
692
|
+
# tags_reflection = Post.reflect_on_association(:tags)
|
693
|
+
# tags_reflection.chain
|
694
|
+
# # => [<ActiveRecord::Reflection::ThroughReflection: @delegate_reflection=#<ActiveRecord::Reflection::HasManyReflection: @name=:tags...>,
|
695
|
+
# <ActiveRecord::Reflection::HasManyReflection: @name=:taggings, @options={}, @active_record=Post>]
|
696
|
+
#
|
697
|
+
def chain
|
698
|
+
@chain ||= begin
|
699
|
+
a = source_reflection.chain
|
700
|
+
b = through_reflection.chain
|
701
|
+
chain = a + b
|
702
|
+
chain[0] = self # Use self so we don't lose the information from :source_type
|
703
|
+
chain
|
704
|
+
end
|
705
|
+
end
|
706
|
+
|
707
|
+
# Consider the following example:
|
708
|
+
#
|
709
|
+
# class Person
|
710
|
+
# has_many :articles
|
711
|
+
# has_many :comment_tags, through: :articles
|
712
|
+
# end
|
713
|
+
#
|
714
|
+
# class Article
|
715
|
+
# has_many :comments
|
716
|
+
# has_many :comment_tags, through: :comments, source: :tags
|
717
|
+
# end
|
718
|
+
#
|
719
|
+
# class Comment
|
720
|
+
# has_many :tags
|
721
|
+
# end
|
722
|
+
#
|
723
|
+
# There may be scopes on Person.comment_tags, Article.comment_tags and/or Comment.tags,
|
724
|
+
# but only Comment.tags will be represented in the #chain. So this method creates an array
|
725
|
+
# of scopes corresponding to the chain.
|
726
|
+
def scope_chain
|
727
|
+
@scope_chain ||= begin
|
728
|
+
scope_chain = source_reflection.scope_chain.map(&:dup)
|
729
|
+
|
730
|
+
# Add to it the scope from this reflection (if any)
|
731
|
+
scope_chain.first << scope if scope
|
732
|
+
|
733
|
+
through_scope_chain = through_reflection.scope_chain.map(&:dup)
|
734
|
+
|
735
|
+
if options[:source_type]
|
736
|
+
type = foreign_type
|
737
|
+
source_type = options[:source_type]
|
738
|
+
through_scope_chain.first << lambda { |object|
|
739
|
+
where(type => source_type)
|
740
|
+
}
|
741
|
+
end
|
742
|
+
|
743
|
+
# Recursively fill out the rest of the array from the through reflection
|
744
|
+
scope_chain + through_scope_chain
|
745
|
+
end
|
746
|
+
end
|
747
|
+
|
748
|
+
def join_keys(assoc_klass)
|
749
|
+
source_reflection.join_keys(assoc_klass)
|
750
|
+
end
|
751
|
+
|
752
|
+
# The macro used by the source association
|
753
|
+
def source_macro
|
754
|
+
ActiveSupport::Deprecation.warn(<<-MSG.squish)
|
755
|
+
ActiveRecord::Base.source_macro is deprecated and will be removed
|
756
|
+
without replacement.
|
757
|
+
MSG
|
758
|
+
|
759
|
+
source_reflection.source_macro
|
760
|
+
end
|
761
|
+
|
762
|
+
# A through association is nested if there would be more than one join table
|
763
|
+
def nested?
|
764
|
+
chain.length > 2
|
765
|
+
end
|
766
|
+
|
767
|
+
# We want to use the klass from this reflection, rather than just delegate straight to
|
768
|
+
# the source_reflection, because the source_reflection may be polymorphic. We still
|
769
|
+
# need to respect the source_reflection's :primary_key option, though.
|
770
|
+
def association_primary_key(klass = nil)
|
771
|
+
# Get the "actual" source reflection if the immediate source reflection has a
|
772
|
+
# source reflection itself
|
773
|
+
actual_source_reflection.options[:primary_key] || primary_key(klass || self.klass)
|
774
|
+
end
|
775
|
+
|
776
|
+
# Gets an array of possible <tt>:through</tt> source reflection names in both singular and plural form.
|
777
|
+
#
|
778
|
+
# class Post < ActiveRecord::Base
|
779
|
+
# has_many :taggings
|
780
|
+
# has_many :tags, through: :taggings
|
781
|
+
# end
|
782
|
+
#
|
783
|
+
# tags_reflection = Post.reflect_on_association(:tags)
|
784
|
+
# tags_reflection.source_reflection_names
|
785
|
+
# # => [:tag, :tags]
|
786
|
+
#
|
787
|
+
def source_reflection_names
|
788
|
+
options[:source] ? [options[:source]] : [name.to_s.singularize, name].uniq
|
789
|
+
end
|
790
|
+
|
791
|
+
def source_reflection_name # :nodoc:
|
792
|
+
return @source_reflection_name if @source_reflection_name
|
793
|
+
|
794
|
+
names = [name.to_s.singularize, name].collect { |n| n.to_sym }.uniq
|
795
|
+
names = names.find_all { |n|
|
796
|
+
through_reflection.klass._reflect_on_association(n)
|
797
|
+
}
|
798
|
+
|
799
|
+
if names.length > 1
|
800
|
+
example_options = options.dup
|
801
|
+
example_options[:source] = source_reflection_names.first
|
802
|
+
ActiveSupport::Deprecation.warn \
|
803
|
+
"Ambiguous source reflection for through association. Please " \
|
804
|
+
"specify a :source directive on your declaration like:\n" \
|
805
|
+
"\n" \
|
806
|
+
" class #{active_record.name} < ActiveRecord::Base\n" \
|
807
|
+
" #{macro} :#{name}, #{example_options}\n" \
|
808
|
+
" end"
|
809
|
+
end
|
810
|
+
|
811
|
+
@source_reflection_name = names.first
|
812
|
+
end
|
813
|
+
|
814
|
+
def source_options
|
815
|
+
source_reflection.options
|
816
|
+
end
|
817
|
+
|
818
|
+
def through_options
|
819
|
+
through_reflection.options
|
820
|
+
end
|
821
|
+
|
822
|
+
def join_id_for(owner) # :nodoc:
|
823
|
+
source_reflection.join_id_for(owner)
|
824
|
+
end
|
825
|
+
|
826
|
+
def check_validity!
|
827
|
+
if through_reflection.nil?
|
828
|
+
raise HasManyThroughAssociationNotFoundError.new(active_record.name, self)
|
829
|
+
end
|
830
|
+
|
831
|
+
if through_reflection.polymorphic?
|
832
|
+
if has_one?
|
833
|
+
raise HasOneAssociationPolymorphicThroughError.new(active_record.name, self)
|
834
|
+
else
|
835
|
+
raise HasManyThroughAssociationPolymorphicThroughError.new(active_record.name, self)
|
836
|
+
end
|
837
|
+
end
|
838
|
+
|
839
|
+
if source_reflection.nil?
|
840
|
+
raise HasManyThroughSourceAssociationNotFoundError.new(self)
|
841
|
+
end
|
842
|
+
|
843
|
+
if options[:source_type] && !source_reflection.polymorphic?
|
844
|
+
raise HasManyThroughAssociationPointlessSourceTypeError.new(active_record.name, self, source_reflection)
|
845
|
+
end
|
846
|
+
|
847
|
+
if source_reflection.polymorphic? && options[:source_type].nil?
|
848
|
+
raise HasManyThroughAssociationPolymorphicSourceError.new(active_record.name, self, source_reflection)
|
849
|
+
end
|
850
|
+
|
851
|
+
if has_one? && through_reflection.collection?
|
852
|
+
raise HasOneThroughCantAssociateThroughCollection.new(active_record.name, self, through_reflection)
|
853
|
+
end
|
854
|
+
|
855
|
+
check_validity_of_inverse!
|
856
|
+
end
|
857
|
+
|
858
|
+
protected
|
859
|
+
|
860
|
+
def actual_source_reflection # FIXME: this is a horrible name
|
861
|
+
source_reflection.send(:actual_source_reflection)
|
862
|
+
end
|
863
|
+
|
864
|
+
def primary_key(klass)
|
865
|
+
klass.primary_key || raise(UnknownPrimaryKey.new(klass))
|
866
|
+
end
|
867
|
+
|
868
|
+
private
|
869
|
+
def derive_class_name
|
870
|
+
# get the class_name of the belongs_to association of the through reflection
|
871
|
+
options[:source_type] || source_reflection.class_name
|
872
|
+
end
|
873
|
+
|
874
|
+
delegate_methods = AssociationReflection.public_instance_methods -
|
875
|
+
public_instance_methods
|
876
|
+
|
877
|
+
delegate(*delegate_methods, to: :delegate_reflection)
|
878
|
+
|
879
|
+
end
|
880
|
+
end
|
881
|
+
end
|