activerecord 3.1.10 → 4.2.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of activerecord might be problematic. Click here for more details.
- checksums.yaml +6 -6
- data/CHANGELOG.md +1837 -338
- data/MIT-LICENSE +1 -1
- data/README.rdoc +39 -43
- data/examples/performance.rb +51 -20
- data/examples/simple.rb +4 -4
- data/lib/active_record/aggregations.rb +57 -43
- data/lib/active_record/association_relation.rb +35 -0
- data/lib/active_record/associations/alias_tracker.rb +47 -39
- data/lib/active_record/associations/association.rb +71 -85
- data/lib/active_record/associations/association_scope.rb +138 -89
- data/lib/active_record/associations/belongs_to_association.rb +65 -25
- data/lib/active_record/associations/belongs_to_polymorphic_association.rb +9 -3
- data/lib/active_record/associations/builder/association.rb +125 -29
- data/lib/active_record/associations/builder/belongs_to.rb +91 -60
- data/lib/active_record/associations/builder/collection_association.rb +69 -49
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +113 -42
- data/lib/active_record/associations/builder/has_many.rb +8 -64
- data/lib/active_record/associations/builder/has_one.rb +12 -52
- data/lib/active_record/associations/builder/singular_association.rb +22 -29
- data/lib/active_record/associations/collection_association.rb +294 -187
- data/lib/active_record/associations/collection_proxy.rb +961 -94
- data/lib/active_record/associations/foreign_association.rb +11 -0
- data/lib/active_record/associations/has_many_association.rb +118 -23
- data/lib/active_record/associations/has_many_through_association.rb +115 -45
- data/lib/active_record/associations/has_one_association.rb +57 -24
- data/lib/active_record/associations/has_one_through_association.rb +1 -1
- data/lib/active_record/associations/join_dependency/join_association.rb +76 -102
- data/lib/active_record/associations/join_dependency/join_base.rb +7 -9
- data/lib/active_record/associations/join_dependency/join_part.rb +30 -37
- data/lib/active_record/associations/join_dependency.rb +230 -156
- data/lib/active_record/associations/preloader/association.rb +96 -55
- data/lib/active_record/associations/preloader/collection_association.rb +3 -3
- data/lib/active_record/associations/preloader/has_many_through.rb +7 -3
- data/lib/active_record/associations/preloader/has_one.rb +1 -1
- data/lib/active_record/associations/preloader/singular_association.rb +3 -3
- data/lib/active_record/associations/preloader/through_association.rb +61 -32
- data/lib/active_record/associations/preloader.rb +113 -87
- data/lib/active_record/associations/singular_association.rb +29 -13
- data/lib/active_record/associations/through_association.rb +37 -19
- data/lib/active_record/associations.rb +505 -371
- data/lib/active_record/attribute.rb +163 -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/before_type_cast.rb +52 -7
- data/lib/active_record/attribute_methods/dirty.rb +141 -51
- data/lib/active_record/attribute_methods/primary_key.rb +87 -36
- data/lib/active_record/attribute_methods/query.rb +5 -4
- data/lib/active_record/attribute_methods/read.rb +74 -117
- data/lib/active_record/attribute_methods/serialization.rb +70 -0
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +49 -47
- data/lib/active_record/attribute_methods/write.rb +60 -21
- data/lib/active_record/attribute_methods.rb +409 -48
- data/lib/active_record/attribute_set/builder.rb +106 -0
- data/lib/active_record/attribute_set.rb +81 -0
- data/lib/active_record/attributes.rb +147 -0
- data/lib/active_record/autosave_association.rb +279 -232
- data/lib/active_record/base.rb +84 -1969
- data/lib/active_record/callbacks.rb +66 -28
- data/lib/active_record/coders/json.rb +13 -0
- data/lib/active_record/coders/yaml_column.rb +18 -21
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +422 -243
- data/lib/active_record/connection_adapters/abstract/database_limits.rb +9 -0
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +170 -194
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +32 -19
- data/lib/active_record/connection_adapters/abstract/quoting.rb +79 -57
- 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 +273 -170
- data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +50 -0
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +731 -254
- data/lib/active_record/connection_adapters/abstract/transaction.rb +215 -0
- data/lib/active_record/connection_adapters/abstract_adapter.rb +339 -95
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +946 -0
- data/lib/active_record/connection_adapters/column.rb +33 -221
- data/lib/active_record/connection_adapters/connection_specification.rb +275 -0
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +140 -602
- data/lib/active_record/connection_adapters/mysql_adapter.rb +254 -756
- 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/array.rb +100 -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 +15 -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 +36 -0
- data/lib/active_record/connection_adapters/postgresql/oid/decimal.rb +13 -0
- data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +19 -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 +19 -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 +109 -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/oid.rb +36 -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 +596 -0
- data/lib/active_record/connection_adapters/postgresql/utils.rb +77 -0
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +445 -902
- data/lib/active_record/connection_adapters/schema_cache.rb +94 -0
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +578 -25
- data/lib/active_record/connection_handling.rb +132 -0
- data/lib/active_record/core.rb +579 -0
- data/lib/active_record/counter_cache.rb +159 -102
- data/lib/active_record/dynamic_matchers.rb +140 -0
- data/lib/active_record/enum.rb +197 -0
- data/lib/active_record/errors.rb +102 -34
- 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 +318 -260
- 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/legacy_yaml_adapter.rb +30 -0
- data/lib/active_record/locale/en.yml +8 -1
- data/lib/active_record/locking/optimistic.rb +80 -52
- data/lib/active_record/locking/pessimistic.rb +27 -5
- data/lib/active_record/log_subscriber.rb +25 -18
- data/lib/active_record/migration/command_recorder.rb +130 -38
- data/lib/active_record/migration/join_table.rb +15 -0
- data/lib/active_record/migration.rb +532 -201
- data/lib/active_record/model_schema.rb +342 -0
- data/lib/active_record/nested_attributes.rb +229 -139
- data/lib/active_record/no_touching.rb +52 -0
- data/lib/active_record/null_relation.rb +81 -0
- data/lib/active_record/persistence.rb +304 -99
- data/lib/active_record/query_cache.rb +25 -43
- data/lib/active_record/querying.rb +68 -0
- data/lib/active_record/railtie.rb +86 -45
- data/lib/active_record/railties/console_sandbox.rb +3 -4
- data/lib/active_record/railties/controller_runtime.rb +7 -4
- data/lib/active_record/railties/databases.rake +198 -377
- data/lib/active_record/railties/jdbcmysql_error.rb +2 -2
- data/lib/active_record/readonly_attributes.rb +23 -0
- data/lib/active_record/reflection.rb +516 -165
- data/lib/active_record/relation/batches.rb +96 -45
- data/lib/active_record/relation/calculations.rb +221 -144
- data/lib/active_record/relation/delegation.rb +140 -0
- data/lib/active_record/relation/finder_methods.rb +362 -243
- data/lib/active_record/relation/merger.rb +193 -0
- data/lib/active_record/relation/predicate_builder/array_handler.rb +48 -0
- data/lib/active_record/relation/predicate_builder/relation_handler.rb +13 -0
- data/lib/active_record/relation/predicate_builder.rb +135 -41
- data/lib/active_record/relation/query_methods.rb +982 -155
- data/lib/active_record/relation/spawn_methods.rb +50 -110
- data/lib/active_record/relation.rb +371 -180
- data/lib/active_record/result.rb +109 -12
- data/lib/active_record/runtime_registry.rb +22 -0
- data/lib/active_record/sanitization.rb +191 -0
- data/lib/active_record/schema.rb +19 -13
- data/lib/active_record/schema_dumper.rb +111 -61
- data/lib/active_record/schema_migration.rb +53 -0
- data/lib/active_record/scoping/default.rb +135 -0
- data/lib/active_record/scoping/named.rb +164 -0
- data/lib/active_record/scoping.rb +87 -0
- data/lib/active_record/serialization.rb +7 -45
- data/lib/active_record/serializers/xml_serializer.rb +14 -65
- 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 +299 -0
- data/lib/active_record/tasks/mysql_database_tasks.rb +159 -0
- data/lib/active_record/tasks/postgresql_database_tasks.rb +101 -0
- data/lib/active_record/tasks/sqlite_database_tasks.rb +55 -0
- data/lib/active_record/timestamp.rb +35 -14
- data/lib/active_record/transactions.rb +141 -74
- data/lib/active_record/translation.rb +22 -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 +31 -0
- data/lib/active_record/type/date.rb +50 -0
- data/lib/active_record/type/date_time.rb +54 -0
- data/lib/active_record/type/decimal.rb +64 -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 +23 -0
- data/lib/active_record/type/integer.rb +59 -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 +62 -0
- data/lib/active_record/type/string.rb +40 -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 +110 -0
- data/lib/active_record/type.rb +23 -0
- data/lib/active_record/validations/associated.rb +27 -18
- data/lib/active_record/validations/presence.rb +67 -0
- data/lib/active_record/validations/uniqueness.rb +125 -66
- data/lib/active_record/validations.rb +37 -30
- data/lib/active_record/version.rb +5 -7
- data/lib/active_record.rb +80 -25
- data/lib/rails/generators/active_record/migration/migration_generator.rb +54 -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 +25 -11
- data/lib/rails/generators/active_record/migration.rb +11 -8
- data/lib/rails/generators/active_record/model/model_generator.rb +17 -4
- data/lib/rails/generators/active_record/model/templates/model.rb +5 -2
- data/lib/rails/generators/active_record/model/templates/module.rb +1 -1
- data/lib/rails/generators/active_record.rb +3 -11
- metadata +132 -53
- data/examples/associations.png +0 -0
- data/lib/active_record/associations/has_and_belongs_to_many_association.rb +0 -62
- data/lib/active_record/associations/join_helper.rb +0 -55
- data/lib/active_record/associations/preloader/has_and_belongs_to_many.rb +0 -60
- data/lib/active_record/connection_adapters/abstract/connection_specification.rb +0 -135
- data/lib/active_record/connection_adapters/sqlite_adapter.rb +0 -556
- data/lib/active_record/dynamic_finder_match.rb +0 -56
- data/lib/active_record/dynamic_scope_match.rb +0 -23
- data/lib/active_record/identity_map.rb +0 -163
- data/lib/active_record/named_scope.rb +0 -200
- data/lib/active_record/observer.rb +0 -121
- data/lib/active_record/session_store.rb +0 -358
- data/lib/active_record/test_case.rb +0 -69
- data/lib/rails/generators/active_record/model/templates/migration.rb +0 -17
- data/lib/rails/generators/active_record/observer/observer_generator.rb +0 -15
- data/lib/rails/generators/active_record/observer/templates/observer.rb +0 -4
- data/lib/rails/generators/active_record/session_migration/session_migration_generator.rb +0 -25
- data/lib/rails/generators/active_record/session_migration/templates/migration.rb +0 -16
@@ -1,122 +1,183 @@
|
|
1
1
|
module ActiveRecord
|
2
2
|
module Associations
|
3
3
|
class AssociationScope #:nodoc:
|
4
|
-
|
4
|
+
def self.scope(association, connection)
|
5
|
+
INSTANCE.scope association, connection
|
6
|
+
end
|
5
7
|
|
6
|
-
|
8
|
+
class BindSubstitution
|
9
|
+
def initialize(block)
|
10
|
+
@block = block
|
11
|
+
end
|
7
12
|
|
8
|
-
|
9
|
-
|
13
|
+
def bind_value(scope, column, value, alias_tracker)
|
14
|
+
substitute = alias_tracker.connection.substitute_at(column)
|
15
|
+
scope.bind_values += [[column, @block.call(value)]]
|
16
|
+
substitute
|
17
|
+
end
|
18
|
+
end
|
10
19
|
|
11
|
-
def
|
12
|
-
|
13
|
-
|
20
|
+
def self.create(&block)
|
21
|
+
block = block ? block : lambda { |val| val }
|
22
|
+
new BindSubstitution.new(block)
|
14
23
|
end
|
15
24
|
|
16
|
-
def
|
17
|
-
|
18
|
-
|
25
|
+
def initialize(bind_substitution)
|
26
|
+
@bind_substitution = bind_substitution
|
27
|
+
end
|
19
28
|
|
20
|
-
|
21
|
-
# association supports that option; this is enforced by the association builder.
|
22
|
-
scope = scope.apply_finder_options(options.slice(
|
23
|
-
:readonly, :include, :order, :limit, :joins, :group, :having, :offset))
|
29
|
+
INSTANCE = create
|
24
30
|
|
25
|
-
|
26
|
-
|
27
|
-
|
31
|
+
def scope(association, connection)
|
32
|
+
klass = association.klass
|
33
|
+
reflection = association.reflection
|
34
|
+
scope = klass.unscoped
|
35
|
+
owner = association.owner
|
36
|
+
alias_tracker = AliasTracker.empty connection
|
28
37
|
|
29
|
-
|
30
|
-
|
38
|
+
scope.extending! Array(reflection.options[:extend])
|
39
|
+
add_constraints(scope, owner, klass, reflection, alias_tracker)
|
40
|
+
end
|
41
|
+
|
42
|
+
def join_type
|
43
|
+
Arel::Nodes::InnerJoin
|
44
|
+
end
|
45
|
+
|
46
|
+
def self.get_bind_values(owner, chain)
|
47
|
+
binds = []
|
48
|
+
last_reflection = chain.last
|
49
|
+
|
50
|
+
binds << last_reflection.join_id_for(owner)
|
51
|
+
if last_reflection.type
|
52
|
+
binds << owner.class.base_class.name
|
31
53
|
end
|
32
54
|
|
33
|
-
|
55
|
+
chain.each_cons(2).each do |reflection, next_reflection|
|
56
|
+
if reflection.type
|
57
|
+
binds << next_reflection.klass.base_class.name
|
58
|
+
end
|
59
|
+
end
|
60
|
+
binds
|
34
61
|
end
|
35
62
|
|
36
63
|
private
|
37
64
|
|
38
|
-
def
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
65
|
+
def construct_tables(chain, klass, refl, alias_tracker)
|
66
|
+
chain.map do |reflection|
|
67
|
+
alias_tracker.aliased_table_for(
|
68
|
+
table_name_for(reflection, klass, refl),
|
69
|
+
table_alias_for(reflection, refl, reflection != refl)
|
70
|
+
)
|
43
71
|
end
|
72
|
+
end
|
44
73
|
|
45
|
-
|
74
|
+
def table_alias_for(reflection, refl, join = false)
|
75
|
+
name = "#{reflection.plural_name}_#{alias_suffix(refl)}"
|
76
|
+
name << "_join" if join
|
77
|
+
name
|
46
78
|
end
|
47
79
|
|
48
|
-
def
|
49
|
-
|
80
|
+
def join(table, constraint)
|
81
|
+
table.create_join(table, table.create_on(constraint), join_type)
|
82
|
+
end
|
50
83
|
|
51
|
-
|
52
|
-
|
84
|
+
def column_for(table_name, column_name, alias_tracker)
|
85
|
+
columns = alias_tracker.connection.schema_cache.columns_hash(table_name)
|
86
|
+
columns[column_name]
|
87
|
+
end
|
53
88
|
|
54
|
-
|
55
|
-
|
89
|
+
def bind_value(scope, column, value, alias_tracker)
|
90
|
+
@bind_substitution.bind_value scope, column, value, alias_tracker
|
91
|
+
end
|
56
92
|
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
))
|
93
|
+
def bind(scope, table_name, column_name, value, tracker)
|
94
|
+
column = column_for table_name, column_name, tracker
|
95
|
+
bind_value scope, column, value, tracker
|
96
|
+
end
|
62
97
|
|
63
|
-
|
64
|
-
|
98
|
+
def last_chain_scope(scope, table, reflection, owner, tracker, assoc_klass)
|
99
|
+
join_keys = reflection.join_keys(assoc_klass)
|
100
|
+
key = join_keys.key
|
101
|
+
foreign_key = join_keys.foreign_key
|
65
102
|
|
66
|
-
|
67
|
-
|
68
|
-
key = reflection.association_primary_key(klass)
|
69
|
-
else
|
70
|
-
key = reflection.association_primary_key
|
71
|
-
end
|
103
|
+
bind_val = bind scope, table.table_name, key.to_s, owner[foreign_key], tracker
|
104
|
+
scope = scope.where(table[key].eq(bind_val))
|
72
105
|
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
106
|
+
if reflection.type
|
107
|
+
value = owner.class.base_class.name
|
108
|
+
bind_val = bind scope, table.table_name, reflection.type, value, tracker
|
109
|
+
scope = scope.where(table[reflection.type].eq(bind_val))
|
110
|
+
else
|
111
|
+
scope
|
112
|
+
end
|
113
|
+
end
|
78
114
|
|
79
|
-
|
115
|
+
def next_chain_scope(scope, table, reflection, tracker, assoc_klass, foreign_table, next_reflection)
|
116
|
+
join_keys = reflection.join_keys(assoc_klass)
|
117
|
+
key = join_keys.key
|
118
|
+
foreign_key = join_keys.foreign_key
|
80
119
|
|
81
|
-
|
82
|
-
scope = scope.where(table[key].eq(owner[foreign_key]))
|
120
|
+
constraint = table[key].eq(foreign_table[foreign_key])
|
83
121
|
|
84
|
-
|
85
|
-
|
86
|
-
|
122
|
+
if reflection.type
|
123
|
+
value = next_reflection.klass.base_class.name
|
124
|
+
bind_val = bind scope, table.table_name, reflection.type, value, tracker
|
125
|
+
scope = scope.where(table[reflection.type].eq(bind_val))
|
126
|
+
end
|
87
127
|
|
88
|
-
|
89
|
-
|
90
|
-
condition = disambiguate_condition(table, condition)
|
91
|
-
end
|
128
|
+
scope = scope.joins(join(foreign_table, constraint))
|
129
|
+
end
|
92
130
|
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
constraint = table[key].eq(foreign_table[foreign_key])
|
131
|
+
def add_constraints(scope, owner, assoc_klass, refl, tracker)
|
132
|
+
chain = refl.chain
|
133
|
+
scope_chain = refl.scope_chain
|
97
134
|
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
135
|
+
tables = construct_tables(chain, assoc_klass, refl, tracker)
|
136
|
+
|
137
|
+
owner_reflection = chain.last
|
138
|
+
table = tables.last
|
139
|
+
scope = last_chain_scope(scope, table, owner_reflection, owner, tracker, assoc_klass)
|
140
|
+
|
141
|
+
chain.each_with_index do |reflection, i|
|
142
|
+
table, foreign_table = tables.shift, tables.first
|
143
|
+
|
144
|
+
unless reflection == chain.last
|
145
|
+
next_reflection = chain[i + 1]
|
146
|
+
scope = next_chain_scope(scope, table, reflection, tracker, assoc_klass, foreign_table, next_reflection)
|
147
|
+
end
|
148
|
+
|
149
|
+
is_first_chain = i == 0
|
150
|
+
klass = is_first_chain ? assoc_klass : reflection.klass
|
102
151
|
|
103
|
-
|
152
|
+
# Exclude the scope of the association itself, because that
|
153
|
+
# was already merged in the #scope method.
|
154
|
+
scope_chain[i].each do |scope_chain_item|
|
155
|
+
item = eval_scope(klass, scope_chain_item, owner)
|
104
156
|
|
105
|
-
|
106
|
-
scope
|
157
|
+
if scope_chain_item == refl.scope
|
158
|
+
scope.merge! item.except(:where, :includes, :bind)
|
107
159
|
end
|
160
|
+
|
161
|
+
if is_first_chain
|
162
|
+
scope.includes! item.includes_values
|
163
|
+
end
|
164
|
+
|
165
|
+
scope.unscope!(*item.unscope_values)
|
166
|
+
scope.where_values += item.where_values
|
167
|
+
scope.bind_values += item.bind_values
|
168
|
+
scope.order_values |= item.order_values
|
108
169
|
end
|
109
170
|
end
|
110
171
|
|
111
172
|
scope
|
112
173
|
end
|
113
174
|
|
114
|
-
def alias_suffix
|
115
|
-
|
175
|
+
def alias_suffix(refl)
|
176
|
+
refl.name
|
116
177
|
end
|
117
178
|
|
118
|
-
def table_name_for(reflection)
|
119
|
-
if reflection ==
|
179
|
+
def table_name_for(reflection, klass, refl)
|
180
|
+
if reflection == refl
|
120
181
|
# If this is a polymorphic belongs_to, we want to get the klass from the
|
121
182
|
# association because it depends on the polymorphic_type attribute of
|
122
183
|
# the owner
|
@@ -126,20 +187,8 @@ module ActiveRecord
|
|
126
187
|
end
|
127
188
|
end
|
128
189
|
|
129
|
-
def
|
130
|
-
|
131
|
-
Hash[
|
132
|
-
condition.map do |k, v|
|
133
|
-
if v.is_a?(Hash)
|
134
|
-
[k, v]
|
135
|
-
else
|
136
|
-
[table.table_alias || table.name, { k => v }]
|
137
|
-
end
|
138
|
-
end
|
139
|
-
]
|
140
|
-
else
|
141
|
-
condition
|
142
|
-
end
|
190
|
+
def eval_scope(klass, scope, owner)
|
191
|
+
klass.unscoped.instance_exec(owner, &scope)
|
143
192
|
end
|
144
193
|
end
|
145
194
|
end
|
@@ -1,78 +1,118 @@
|
|
1
1
|
module ActiveRecord
|
2
|
-
# = Active Record Belongs To
|
2
|
+
# = Active Record Belongs To Association
|
3
3
|
module Associations
|
4
4
|
class BelongsToAssociation < SingularAssociation #:nodoc:
|
5
|
-
def replace(record)
|
6
|
-
raise_on_type_mismatch(record) if record
|
7
5
|
|
8
|
-
|
9
|
-
|
10
|
-
|
6
|
+
def handle_dependency
|
7
|
+
target.send(options[:dependent]) if load_target
|
8
|
+
end
|
11
9
|
|
12
|
-
|
10
|
+
def replace(record)
|
11
|
+
if record
|
12
|
+
raise_on_type_mismatch!(record)
|
13
|
+
update_counters(record)
|
14
|
+
replace_keys(record)
|
15
|
+
set_inverse_instance(record)
|
16
|
+
@updated = true
|
17
|
+
else
|
18
|
+
decrement_counters
|
19
|
+
remove_keys
|
20
|
+
end
|
13
21
|
|
14
22
|
self.target = record
|
15
23
|
end
|
16
24
|
|
25
|
+
def reset
|
26
|
+
super
|
27
|
+
@updated = false
|
28
|
+
end
|
29
|
+
|
17
30
|
def updated?
|
18
31
|
@updated
|
19
32
|
end
|
20
33
|
|
34
|
+
def decrement_counters # :nodoc:
|
35
|
+
with_cache_name { |name| decrement_counter name }
|
36
|
+
end
|
37
|
+
|
38
|
+
def increment_counters # :nodoc:
|
39
|
+
with_cache_name { |name| increment_counter name }
|
40
|
+
end
|
41
|
+
|
21
42
|
private
|
22
43
|
|
23
44
|
def find_target?
|
24
45
|
!loaded? && foreign_key_present? && klass
|
25
46
|
end
|
26
47
|
|
27
|
-
def
|
48
|
+
def with_cache_name
|
28
49
|
counter_cache_name = reflection.counter_cache_column
|
50
|
+
return unless counter_cache_name && owner.persisted?
|
51
|
+
yield counter_cache_name
|
52
|
+
end
|
29
53
|
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
54
|
+
def update_counters(record)
|
55
|
+
with_cache_name do |name|
|
56
|
+
return unless different_target? record
|
57
|
+
record.class.increment_counter(name, record.id)
|
58
|
+
decrement_counter name
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def decrement_counter(counter_cache_name)
|
63
|
+
if foreign_key_present?
|
64
|
+
klass.decrement_counter(counter_cache_name, target_id)
|
65
|
+
end
|
66
|
+
end
|
34
67
|
|
35
|
-
|
36
|
-
|
68
|
+
def increment_counter(counter_cache_name)
|
69
|
+
if foreign_key_present?
|
70
|
+
klass.increment_counter(counter_cache_name, target_id)
|
71
|
+
if target && !stale_target? && counter_cache_available_in_memory?(counter_cache_name)
|
72
|
+
target.increment(counter_cache_name)
|
37
73
|
end
|
38
74
|
end
|
39
75
|
end
|
40
76
|
|
41
77
|
# Checks whether record is different to the current target, without loading it
|
42
78
|
def different_target?(record)
|
43
|
-
record.
|
44
|
-
record && record.id != owner[reflection.foreign_key]
|
79
|
+
record.id != owner._read_attribute(reflection.foreign_key)
|
45
80
|
end
|
46
81
|
|
47
82
|
def replace_keys(record)
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
83
|
+
owner[reflection.foreign_key] = record._read_attribute(reflection.association_primary_key(record.class))
|
84
|
+
end
|
85
|
+
|
86
|
+
def remove_keys
|
87
|
+
owner[reflection.foreign_key] = nil
|
53
88
|
end
|
54
89
|
|
55
90
|
def foreign_key_present?
|
56
|
-
owner
|
91
|
+
owner._read_attribute(reflection.foreign_key)
|
57
92
|
end
|
58
93
|
|
59
94
|
# NOTE - for now, we're only supporting inverse setting from belongs_to back onto
|
60
95
|
# has_one associations.
|
61
96
|
def invertible_for?(record)
|
62
97
|
inverse = inverse_reflection_for(record)
|
63
|
-
inverse && inverse.
|
98
|
+
inverse && inverse.has_one?
|
64
99
|
end
|
65
100
|
|
66
101
|
def target_id
|
67
102
|
if options[:primary_key]
|
68
103
|
owner.send(reflection.name).try(:id)
|
69
104
|
else
|
70
|
-
owner
|
105
|
+
owner._read_attribute(reflection.foreign_key)
|
71
106
|
end
|
72
107
|
end
|
73
108
|
|
74
109
|
def stale_state
|
75
|
-
owner
|
110
|
+
result = owner._read_attribute(reflection.foreign_key)
|
111
|
+
result && result.to_s
|
112
|
+
end
|
113
|
+
|
114
|
+
def counter_cache_available_in_memory?(counter_cache_name)
|
115
|
+
target.respond_to?(counter_cache_name)
|
76
116
|
end
|
77
117
|
end
|
78
118
|
end
|
@@ -11,7 +11,12 @@ module ActiveRecord
|
|
11
11
|
|
12
12
|
def replace_keys(record)
|
13
13
|
super
|
14
|
-
owner[reflection.foreign_type] = record
|
14
|
+
owner[reflection.foreign_type] = record.class.base_class.name
|
15
|
+
end
|
16
|
+
|
17
|
+
def remove_keys
|
18
|
+
super
|
19
|
+
owner[reflection.foreign_type] = nil
|
15
20
|
end
|
16
21
|
|
17
22
|
def different_target?(record)
|
@@ -22,12 +27,13 @@ module ActiveRecord
|
|
22
27
|
reflection.polymorphic_inverse_of(record.class)
|
23
28
|
end
|
24
29
|
|
25
|
-
def raise_on_type_mismatch(record)
|
30
|
+
def raise_on_type_mismatch!(record)
|
26
31
|
# A polymorphic association cannot have a type mismatch, by definition
|
27
32
|
end
|
28
33
|
|
29
34
|
def stale_state
|
30
|
-
|
35
|
+
foreign_key = super
|
36
|
+
foreign_key && [foreign_key.to_s, owner[reflection.foreign_type].to_s]
|
31
37
|
end
|
32
38
|
end
|
33
39
|
end
|
@@ -1,53 +1,149 @@
|
|
1
|
+
require 'active_support/core_ext/module/attribute_accessors'
|
2
|
+
|
3
|
+
# This is the parent Association class which defines the variables
|
4
|
+
# used by all associations.
|
5
|
+
#
|
6
|
+
# The hierarchy is defined as follows:
|
7
|
+
# Association
|
8
|
+
# - SingularAssociation
|
9
|
+
# - BelongsToAssociation
|
10
|
+
# - HasOneAssociation
|
11
|
+
# - CollectionAssociation
|
12
|
+
# - HasManyAssociation
|
13
|
+
|
1
14
|
module ActiveRecord::Associations::Builder
|
2
15
|
class Association #:nodoc:
|
3
|
-
|
4
|
-
|
16
|
+
class << self
|
17
|
+
attr_accessor :extensions
|
18
|
+
# TODO: This class accessor is needed to make activerecord-deprecated_finders work.
|
19
|
+
# We can move it to a constant in 5.0.
|
20
|
+
attr_accessor :valid_options
|
21
|
+
end
|
22
|
+
self.extensions = []
|
5
23
|
|
6
|
-
|
7
|
-
class_attribute :macro
|
24
|
+
self.valid_options = [:class_name, :anonymous_class, :foreign_key, :validate]
|
8
25
|
|
9
|
-
attr_reader :
|
26
|
+
attr_reader :name, :scope, :options
|
27
|
+
|
28
|
+
def self.build(model, name, scope, options, &block)
|
29
|
+
if model.dangerous_attribute_method?(name)
|
30
|
+
raise ArgumentError, "You tried to define an association named #{name} on the model #{model.name}, but " \
|
31
|
+
"this will conflict with a method #{name} already defined by Active Record. " \
|
32
|
+
"Please choose a different association name."
|
33
|
+
end
|
10
34
|
|
11
|
-
|
12
|
-
|
35
|
+
builder = create_builder model, name, scope, options, &block
|
36
|
+
reflection = builder.build(model)
|
37
|
+
define_accessors model, reflection
|
38
|
+
define_callbacks model, reflection
|
39
|
+
define_validations model, reflection
|
40
|
+
builder.define_extensions model
|
41
|
+
reflection
|
13
42
|
end
|
14
43
|
|
15
|
-
def
|
16
|
-
|
44
|
+
def self.create_builder(model, name, scope, options, &block)
|
45
|
+
raise ArgumentError, "association names must be a Symbol" unless name.kind_of?(Symbol)
|
46
|
+
|
47
|
+
new(model, name, scope, options, &block)
|
17
48
|
end
|
18
49
|
|
19
|
-
def
|
50
|
+
def initialize(model, name, scope, options)
|
51
|
+
# TODO: Move this to create_builder as soon we drop support to activerecord-deprecated_finders.
|
52
|
+
if scope.is_a?(Hash)
|
53
|
+
options = scope
|
54
|
+
scope = nil
|
55
|
+
end
|
56
|
+
|
57
|
+
# TODO: Remove this model argument as soon we drop support to activerecord-deprecated_finders.
|
58
|
+
@name = name
|
59
|
+
@scope = scope
|
60
|
+
@options = options
|
61
|
+
|
20
62
|
validate_options
|
21
|
-
|
22
|
-
|
23
|
-
|
63
|
+
|
64
|
+
if scope && scope.arity == 0
|
65
|
+
@scope = proc { instance_exec(&scope) }
|
66
|
+
end
|
24
67
|
end
|
25
68
|
|
26
|
-
|
69
|
+
def build(model)
|
70
|
+
ActiveRecord::Reflection.create(macro, name, scope, options, model)
|
71
|
+
end
|
27
72
|
|
28
|
-
|
29
|
-
|
73
|
+
def macro
|
74
|
+
raise NotImplementedError
|
75
|
+
end
|
76
|
+
|
77
|
+
def valid_options
|
78
|
+
Association.valid_options + Association.extensions.flat_map(&:valid_options)
|
79
|
+
end
|
80
|
+
|
81
|
+
def validate_options
|
82
|
+
options.assert_valid_keys(valid_options)
|
83
|
+
end
|
84
|
+
|
85
|
+
def define_extensions(model)
|
86
|
+
end
|
87
|
+
|
88
|
+
def self.define_callbacks(model, reflection)
|
89
|
+
if dependent = reflection.options[:dependent]
|
90
|
+
check_dependent_options(dependent)
|
91
|
+
add_destroy_callbacks(model, reflection)
|
30
92
|
end
|
31
93
|
|
32
|
-
|
33
|
-
|
34
|
-
define_writers
|
94
|
+
Association.extensions.each do |extension|
|
95
|
+
extension.build model, reflection
|
35
96
|
end
|
97
|
+
end
|
36
98
|
|
37
|
-
|
38
|
-
|
99
|
+
# Defines the setter and getter methods for the association
|
100
|
+
# class Post < ActiveRecord::Base
|
101
|
+
# has_many :comments
|
102
|
+
# end
|
103
|
+
#
|
104
|
+
# Post.first.comments and Post.first.comments= methods are defined by this method...
|
105
|
+
def self.define_accessors(model, reflection)
|
106
|
+
mixin = model.generated_association_methods
|
107
|
+
name = reflection.name
|
108
|
+
define_readers(mixin, name)
|
109
|
+
define_writers(mixin, name)
|
110
|
+
end
|
39
111
|
|
40
|
-
|
41
|
-
|
112
|
+
def self.define_readers(mixin, name)
|
113
|
+
mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1
|
114
|
+
def #{name}(*args)
|
115
|
+
association(:#{name}).reader(*args)
|
42
116
|
end
|
43
|
-
|
44
|
-
|
45
|
-
def define_writers
|
46
|
-
name = self.name
|
117
|
+
CODE
|
118
|
+
end
|
47
119
|
|
48
|
-
|
49
|
-
|
120
|
+
def self.define_writers(mixin, name)
|
121
|
+
mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1
|
122
|
+
def #{name}=(value)
|
123
|
+
association(:#{name}).writer(value)
|
50
124
|
end
|
125
|
+
CODE
|
126
|
+
end
|
127
|
+
|
128
|
+
def self.define_validations(model, reflection)
|
129
|
+
# noop
|
130
|
+
end
|
131
|
+
|
132
|
+
def self.valid_dependent_options
|
133
|
+
raise NotImplementedError
|
134
|
+
end
|
135
|
+
|
136
|
+
private
|
137
|
+
|
138
|
+
def self.check_dependent_options(dependent)
|
139
|
+
unless valid_dependent_options.include? dependent
|
140
|
+
raise ArgumentError, "The :dependent option must be one of #{valid_dependent_options}, but is :#{dependent}"
|
51
141
|
end
|
142
|
+
end
|
143
|
+
|
144
|
+
def self.add_destroy_callbacks(model, reflection)
|
145
|
+
name = reflection.name
|
146
|
+
model.before_destroy lambda { |o| o.association(name).handle_dependency }
|
147
|
+
end
|
52
148
|
end
|
53
149
|
end
|