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
@@ -0,0 +1,135 @@
|
|
1
|
+
module ActiveRecord
|
2
|
+
module Associations
|
3
|
+
class AssociationScope #:nodoc:
|
4
|
+
include JoinHelper
|
5
|
+
|
6
|
+
attr_reader :association, :alias_tracker
|
7
|
+
|
8
|
+
delegate :klass, :owner, :reflection, :interpolate, :to => :association
|
9
|
+
delegate :chain, :scope_chain, :options, :source_options, :active_record, :to => :reflection
|
10
|
+
|
11
|
+
def initialize(association)
|
12
|
+
@association = association
|
13
|
+
@alias_tracker = AliasTracker.new klass.connection
|
14
|
+
end
|
15
|
+
|
16
|
+
def scope
|
17
|
+
scope = klass.unscoped
|
18
|
+
scope.extending! Array(options[:extend])
|
19
|
+
add_constraints(scope)
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def column_for(table_name, column_name)
|
25
|
+
columns = alias_tracker.connection.schema_cache.columns_hash(table_name)
|
26
|
+
columns[column_name]
|
27
|
+
end
|
28
|
+
|
29
|
+
def bind_value(scope, column, value)
|
30
|
+
substitute = alias_tracker.connection.substitute_at(
|
31
|
+
column, scope.bind_values.length)
|
32
|
+
scope.bind_values += [[column, value]]
|
33
|
+
substitute
|
34
|
+
end
|
35
|
+
|
36
|
+
def bind(scope, table_name, column_name, value)
|
37
|
+
column = column_for table_name, column_name
|
38
|
+
bind_value scope, column, value
|
39
|
+
end
|
40
|
+
|
41
|
+
def add_constraints(scope)
|
42
|
+
tables = construct_tables
|
43
|
+
|
44
|
+
chain.each_with_index do |reflection, i|
|
45
|
+
table, foreign_table = tables.shift, tables.first
|
46
|
+
|
47
|
+
if reflection.source_macro == :has_and_belongs_to_many
|
48
|
+
join_table = tables.shift
|
49
|
+
|
50
|
+
scope = scope.joins(join(
|
51
|
+
join_table,
|
52
|
+
table[reflection.association_primary_key].
|
53
|
+
eq(join_table[reflection.association_foreign_key])
|
54
|
+
))
|
55
|
+
|
56
|
+
table, foreign_table = join_table, tables.first
|
57
|
+
end
|
58
|
+
|
59
|
+
if reflection.source_macro == :belongs_to
|
60
|
+
if reflection.options[:polymorphic]
|
61
|
+
key = reflection.association_primary_key(self.klass)
|
62
|
+
else
|
63
|
+
key = reflection.association_primary_key
|
64
|
+
end
|
65
|
+
|
66
|
+
foreign_key = reflection.foreign_key
|
67
|
+
else
|
68
|
+
key = reflection.foreign_key
|
69
|
+
foreign_key = reflection.active_record_primary_key
|
70
|
+
end
|
71
|
+
|
72
|
+
if reflection == chain.last
|
73
|
+
bind_val = bind scope, table.table_name, key.to_s, owner[foreign_key]
|
74
|
+
scope = scope.where(table[key].eq(bind_val))
|
75
|
+
|
76
|
+
if reflection.type
|
77
|
+
value = owner.class.base_class.name
|
78
|
+
bind_val = bind scope, table.table_name, reflection.type.to_s, value
|
79
|
+
scope = scope.where(table[reflection.type].eq(bind_val))
|
80
|
+
end
|
81
|
+
else
|
82
|
+
constraint = table[key].eq(foreign_table[foreign_key])
|
83
|
+
|
84
|
+
if reflection.type
|
85
|
+
type = chain[i + 1].klass.base_class.name
|
86
|
+
constraint = constraint.and(table[reflection.type].eq(type))
|
87
|
+
end
|
88
|
+
|
89
|
+
scope = scope.joins(join(foreign_table, constraint))
|
90
|
+
end
|
91
|
+
|
92
|
+
# Exclude the scope of the association itself, because that
|
93
|
+
# was already merged in the #scope method.
|
94
|
+
scope_chain[i].each do |scope_chain_item|
|
95
|
+
klass = i == 0 ? self.klass : reflection.klass
|
96
|
+
item = eval_scope(klass, scope_chain_item)
|
97
|
+
|
98
|
+
if scope_chain_item == self.reflection.scope
|
99
|
+
scope.merge! item.except(:where, :includes)
|
100
|
+
end
|
101
|
+
|
102
|
+
scope.includes! item.includes_values
|
103
|
+
scope.where_values += item.where_values
|
104
|
+
scope.order_values |= item.order_values
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
scope
|
109
|
+
end
|
110
|
+
|
111
|
+
def alias_suffix
|
112
|
+
reflection.name
|
113
|
+
end
|
114
|
+
|
115
|
+
def table_name_for(reflection)
|
116
|
+
if reflection == self.reflection
|
117
|
+
# If this is a polymorphic belongs_to, we want to get the klass from the
|
118
|
+
# association because it depends on the polymorphic_type attribute of
|
119
|
+
# the owner
|
120
|
+
klass.table_name
|
121
|
+
else
|
122
|
+
reflection.table_name
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
def eval_scope(klass, scope)
|
127
|
+
if scope.is_a?(Relation)
|
128
|
+
scope
|
129
|
+
else
|
130
|
+
klass.unscoped.instance_exec(owner, &scope)
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
@@ -1,41 +1,27 @@
|
|
1
1
|
module ActiveRecord
|
2
|
-
# = Active Record Belongs To
|
2
|
+
# = Active Record Belongs To Association
|
3
3
|
module Associations
|
4
|
-
class BelongsToAssociation <
|
5
|
-
def create(attributes = {})
|
6
|
-
replace(@reflection.create_association(attributes))
|
7
|
-
end
|
4
|
+
class BelongsToAssociation < SingularAssociation #:nodoc:
|
8
5
|
|
9
|
-
def
|
10
|
-
|
6
|
+
def handle_dependency
|
7
|
+
target.send(options[:dependent]) if load_target
|
11
8
|
end
|
12
9
|
|
13
10
|
def replace(record)
|
14
|
-
|
15
|
-
|
16
|
-
if record.nil?
|
17
|
-
if counter_cache_name && !@owner.new_record?
|
18
|
-
@reflection.klass.decrement_counter(counter_cache_name, previous_record_id) if @owner[@reflection.primary_key_name]
|
19
|
-
end
|
11
|
+
raise_on_type_mismatch!(record) if record
|
20
12
|
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
if counter_cache_name && !@owner.new_record? && record.id != @owner[@reflection.primary_key_name]
|
26
|
-
@reflection.klass.increment_counter(counter_cache_name, record.id)
|
27
|
-
@reflection.klass.decrement_counter(counter_cache_name, @owner[@reflection.primary_key_name]) if @owner[@reflection.primary_key_name]
|
28
|
-
end
|
13
|
+
update_counters(record)
|
14
|
+
replace_keys(record)
|
15
|
+
set_inverse_instance(record)
|
29
16
|
|
30
|
-
|
31
|
-
@owner[@reflection.primary_key_name] = record_id(record) unless record.new_record?
|
32
|
-
@updated = true
|
33
|
-
end
|
17
|
+
@updated = true if record
|
34
18
|
|
35
|
-
|
19
|
+
self.target = record
|
20
|
+
end
|
36
21
|
|
37
|
-
|
38
|
-
|
22
|
+
def reset
|
23
|
+
super
|
24
|
+
@updated = false
|
39
25
|
end
|
40
26
|
|
41
27
|
def updated?
|
@@ -43,48 +29,63 @@ module ActiveRecord
|
|
43
29
|
end
|
44
30
|
|
45
31
|
private
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
32
|
+
|
33
|
+
def find_target?
|
34
|
+
!loaded? && foreign_key_present? && klass
|
35
|
+
end
|
36
|
+
|
37
|
+
def update_counters(record)
|
38
|
+
counter_cache_name = reflection.counter_cache_column
|
39
|
+
|
40
|
+
if counter_cache_name && owner.persisted? && different_target?(record)
|
41
|
+
if record
|
42
|
+
record.class.increment_counter(counter_cache_name, record.id)
|
43
|
+
end
|
44
|
+
|
45
|
+
if foreign_key_present?
|
46
|
+
klass.decrement_counter(counter_cache_name, target_id)
|
47
|
+
end
|
56
48
|
end
|
57
|
-
options[:conditions] = conditions
|
58
|
-
|
59
|
-
the_target = @reflection.klass.send(find_method,
|
60
|
-
@owner[@reflection.primary_key_name],
|
61
|
-
options
|
62
|
-
) if @owner[@reflection.primary_key_name]
|
63
|
-
set_inverse_instance(the_target, @owner)
|
64
|
-
the_target
|
65
49
|
end
|
66
50
|
|
67
|
-
|
68
|
-
|
51
|
+
# Checks whether record is different to the current target, without loading it
|
52
|
+
def different_target?(record)
|
53
|
+
if record.nil?
|
54
|
+
owner[reflection.foreign_key]
|
55
|
+
else
|
56
|
+
record.id != owner[reflection.foreign_key]
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def replace_keys(record)
|
61
|
+
if record
|
62
|
+
owner[reflection.foreign_key] = record[reflection.association_primary_key(record.class)]
|
63
|
+
else
|
64
|
+
owner[reflection.foreign_key] = nil
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def foreign_key_present?
|
69
|
+
owner[reflection.foreign_key]
|
69
70
|
end
|
70
71
|
|
71
72
|
# NOTE - for now, we're only supporting inverse setting from belongs_to back onto
|
72
73
|
# has_one associations.
|
73
|
-
def
|
74
|
-
|
74
|
+
def invertible_for?(record)
|
75
|
+
inverse = inverse_reflection_for(record)
|
76
|
+
inverse && inverse.macro == :has_one
|
75
77
|
end
|
76
78
|
|
77
|
-
def
|
78
|
-
|
79
|
+
def target_id
|
80
|
+
if options[:primary_key]
|
81
|
+
owner.send(reflection.name).try(:id)
|
82
|
+
else
|
83
|
+
owner[reflection.foreign_key]
|
84
|
+
end
|
79
85
|
end
|
80
86
|
|
81
|
-
def
|
82
|
-
|
83
|
-
previous_record = @owner.send(@reflection.name)
|
84
|
-
previous_record.nil? ? nil : previous_record.id
|
85
|
-
else
|
86
|
-
@owner[@reflection.primary_key_name]
|
87
|
-
end
|
87
|
+
def stale_state
|
88
|
+
owner[reflection.foreign_key] && owner[reflection.foreign_key].to_s
|
88
89
|
end
|
89
90
|
end
|
90
91
|
end
|
@@ -1,77 +1,34 @@
|
|
1
1
|
module ActiveRecord
|
2
2
|
# = Active Record Belongs To Polymorphic Association
|
3
3
|
module Associations
|
4
|
-
class BelongsToPolymorphicAssociation <
|
5
|
-
def
|
6
|
-
|
7
|
-
|
8
|
-
else
|
9
|
-
@target = (AssociationProxy === record ? record.target : record)
|
10
|
-
|
11
|
-
@owner[@reflection.primary_key_name] = record_id(record)
|
12
|
-
@owner[@reflection.options[:foreign_type]] = record.class.base_class.name.to_s
|
13
|
-
|
14
|
-
@updated = true
|
15
|
-
end
|
16
|
-
|
17
|
-
set_inverse_instance(record, @owner)
|
18
|
-
loaded
|
19
|
-
record
|
20
|
-
end
|
21
|
-
|
22
|
-
def updated?
|
23
|
-
@updated
|
4
|
+
class BelongsToPolymorphicAssociation < BelongsToAssociation #:nodoc:
|
5
|
+
def klass
|
6
|
+
type = owner[reflection.foreign_type]
|
7
|
+
type.presence && type.constantize
|
24
8
|
end
|
25
9
|
|
26
10
|
private
|
27
11
|
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
if @reflection.has_inverse?
|
32
|
-
inverse_association = @reflection.polymorphic_inverse_of(record.class)
|
33
|
-
inverse_association && inverse_association.macro == :has_one
|
34
|
-
else
|
35
|
-
false
|
36
|
-
end
|
37
|
-
end
|
38
|
-
|
39
|
-
def set_inverse_instance(record, instance)
|
40
|
-
return if record.nil? || !we_can_set_the_inverse_on_this?(record)
|
41
|
-
inverse_relationship = @reflection.polymorphic_inverse_of(record.class)
|
42
|
-
unless inverse_relationship.nil?
|
43
|
-
record.send(:"set_#{inverse_relationship.name}_target", instance)
|
44
|
-
end
|
12
|
+
def replace_keys(record)
|
13
|
+
super
|
14
|
+
owner[reflection.foreign_type] = record && record.class.base_class.name
|
45
15
|
end
|
46
16
|
|
47
|
-
def
|
48
|
-
|
49
|
-
|
50
|
-
target =
|
51
|
-
if @reflection.options[:conditions]
|
52
|
-
association_class.find(
|
53
|
-
@owner[@reflection.primary_key_name],
|
54
|
-
:select => @reflection.options[:select],
|
55
|
-
:conditions => conditions,
|
56
|
-
:include => @reflection.options[:include]
|
57
|
-
)
|
58
|
-
else
|
59
|
-
association_class.find(@owner[@reflection.primary_key_name], :select => @reflection.options[:select], :include => @reflection.options[:include])
|
60
|
-
end
|
61
|
-
set_inverse_instance(target, @owner)
|
62
|
-
target
|
17
|
+
def different_target?(record)
|
18
|
+
super || record.class != klass
|
63
19
|
end
|
64
20
|
|
65
|
-
def
|
66
|
-
|
21
|
+
def inverse_reflection_for(record)
|
22
|
+
reflection.polymorphic_inverse_of(record.class)
|
67
23
|
end
|
68
24
|
|
69
|
-
def
|
70
|
-
|
25
|
+
def raise_on_type_mismatch!(record)
|
26
|
+
# A polymorphic association cannot have a type mismatch, by definition
|
71
27
|
end
|
72
28
|
|
73
|
-
def
|
74
|
-
|
29
|
+
def stale_state
|
30
|
+
foreign_key = super
|
31
|
+
foreign_key && [foreign_key.to_s, owner[reflection.foreign_type].to_s]
|
75
32
|
end
|
76
33
|
end
|
77
34
|
end
|
@@ -0,0 +1,108 @@
|
|
1
|
+
module ActiveRecord::Associations::Builder
|
2
|
+
class Association #:nodoc:
|
3
|
+
class << self
|
4
|
+
attr_accessor :valid_options
|
5
|
+
end
|
6
|
+
|
7
|
+
self.valid_options = [:class_name, :foreign_key, :validate]
|
8
|
+
|
9
|
+
attr_reader :model, :name, :scope, :options, :reflection
|
10
|
+
|
11
|
+
def self.build(*args, &block)
|
12
|
+
new(*args, &block).build
|
13
|
+
end
|
14
|
+
|
15
|
+
def initialize(model, name, scope, options)
|
16
|
+
raise ArgumentError, "association names must be a Symbol" unless name.kind_of?(Symbol)
|
17
|
+
|
18
|
+
@model = model
|
19
|
+
@name = name
|
20
|
+
|
21
|
+
if scope.is_a?(Hash)
|
22
|
+
@scope = nil
|
23
|
+
@options = scope
|
24
|
+
else
|
25
|
+
@scope = scope
|
26
|
+
@options = options
|
27
|
+
end
|
28
|
+
|
29
|
+
if @scope && @scope.arity == 0
|
30
|
+
prev_scope = @scope
|
31
|
+
@scope = proc { instance_exec(&prev_scope) }
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def mixin
|
36
|
+
@model.generated_feature_methods
|
37
|
+
end
|
38
|
+
|
39
|
+
include Module.new { def build; end }
|
40
|
+
|
41
|
+
def build
|
42
|
+
validate_options
|
43
|
+
define_accessors
|
44
|
+
configure_dependency if options[:dependent]
|
45
|
+
@reflection = model.create_reflection(macro, name, scope, options, model)
|
46
|
+
super # provides an extension point
|
47
|
+
@reflection
|
48
|
+
end
|
49
|
+
|
50
|
+
def macro
|
51
|
+
raise NotImplementedError
|
52
|
+
end
|
53
|
+
|
54
|
+
def valid_options
|
55
|
+
Association.valid_options
|
56
|
+
end
|
57
|
+
|
58
|
+
def validate_options
|
59
|
+
options.assert_valid_keys(valid_options)
|
60
|
+
end
|
61
|
+
|
62
|
+
def define_accessors
|
63
|
+
define_readers
|
64
|
+
define_writers
|
65
|
+
end
|
66
|
+
|
67
|
+
def define_readers
|
68
|
+
mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1
|
69
|
+
def #{name}(*args)
|
70
|
+
association(:#{name}).reader(*args)
|
71
|
+
end
|
72
|
+
CODE
|
73
|
+
end
|
74
|
+
|
75
|
+
def define_writers
|
76
|
+
mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1
|
77
|
+
def #{name}=(value)
|
78
|
+
association(:#{name}).writer(value)
|
79
|
+
end
|
80
|
+
CODE
|
81
|
+
end
|
82
|
+
|
83
|
+
def configure_dependency
|
84
|
+
unless valid_dependent_options.include? options[:dependent]
|
85
|
+
raise ArgumentError, "The :dependent option must be one of #{valid_dependent_options}, but is :#{options[:dependent]}"
|
86
|
+
end
|
87
|
+
|
88
|
+
if options[:dependent] == :restrict
|
89
|
+
ActiveSupport::Deprecation.warn(
|
90
|
+
"The :restrict option is deprecated. Please use :restrict_with_exception instead, which " \
|
91
|
+
"provides the same functionality."
|
92
|
+
)
|
93
|
+
end
|
94
|
+
|
95
|
+
mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1
|
96
|
+
def #{macro}_dependent_for_#{name}
|
97
|
+
association(:#{name}).handle_dependency
|
98
|
+
end
|
99
|
+
CODE
|
100
|
+
|
101
|
+
model.before_destroy "#{macro}_dependent_for_#{name}"
|
102
|
+
end
|
103
|
+
|
104
|
+
def valid_dependent_options
|
105
|
+
raise NotImplementedError
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
@@ -0,0 +1,98 @@
|
|
1
|
+
module ActiveRecord::Associations::Builder
|
2
|
+
class BelongsTo < SingularAssociation #:nodoc:
|
3
|
+
def macro
|
4
|
+
:belongs_to
|
5
|
+
end
|
6
|
+
|
7
|
+
def valid_options
|
8
|
+
super + [:foreign_type, :polymorphic, :touch]
|
9
|
+
end
|
10
|
+
|
11
|
+
def constructable?
|
12
|
+
!options[:polymorphic]
|
13
|
+
end
|
14
|
+
|
15
|
+
def build
|
16
|
+
reflection = super
|
17
|
+
add_counter_cache_callbacks(reflection) if options[:counter_cache]
|
18
|
+
add_touch_callbacks(reflection) if options[:touch]
|
19
|
+
reflection
|
20
|
+
end
|
21
|
+
|
22
|
+
def add_counter_cache_callbacks(reflection)
|
23
|
+
cache_column = reflection.counter_cache_column
|
24
|
+
foreign_key = reflection.foreign_key
|
25
|
+
|
26
|
+
mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1
|
27
|
+
def belongs_to_counter_cache_after_create_for_#{name}
|
28
|
+
if record = #{name}
|
29
|
+
record.class.increment_counter(:#{cache_column}, record.id)
|
30
|
+
@_after_create_counter_called = true
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def belongs_to_counter_cache_before_destroy_for_#{name}
|
35
|
+
unless destroyed_by_association && destroyed_by_association.foreign_key.to_sym == #{foreign_key.to_sym.inspect}
|
36
|
+
record = #{name}
|
37
|
+
record.class.decrement_counter(:#{cache_column}, record.id) unless record.nil?
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def belongs_to_counter_cache_after_update_for_#{name}
|
42
|
+
if (@_after_create_counter_called ||= false)
|
43
|
+
@_after_create_counter_called = false
|
44
|
+
elsif self.#{foreign_key}_changed? && !new_record? && defined?(#{name.to_s.camelize})
|
45
|
+
model = #{name.to_s.camelize}
|
46
|
+
foreign_key_was = self.#{foreign_key}_was
|
47
|
+
foreign_key = self.#{foreign_key}
|
48
|
+
|
49
|
+
if foreign_key && model.respond_to?(:increment_counter)
|
50
|
+
model.increment_counter(:#{cache_column}, foreign_key)
|
51
|
+
end
|
52
|
+
if foreign_key_was && model.respond_to?(:decrement_counter)
|
53
|
+
model.decrement_counter(:#{cache_column}, foreign_key_was)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
CODE
|
58
|
+
|
59
|
+
model.after_create "belongs_to_counter_cache_after_create_for_#{name}"
|
60
|
+
model.before_destroy "belongs_to_counter_cache_before_destroy_for_#{name}"
|
61
|
+
model.after_update "belongs_to_counter_cache_after_update_for_#{name}"
|
62
|
+
|
63
|
+
klass = reflection.class_name.safe_constantize
|
64
|
+
klass.attr_readonly cache_column if klass && klass.respond_to?(:attr_readonly)
|
65
|
+
end
|
66
|
+
|
67
|
+
def add_touch_callbacks(reflection)
|
68
|
+
mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1
|
69
|
+
def belongs_to_touch_after_save_or_destroy_for_#{name}
|
70
|
+
foreign_key_field = #{reflection.foreign_key.inspect}
|
71
|
+
old_foreign_id = attribute_was(foreign_key_field)
|
72
|
+
|
73
|
+
if old_foreign_id
|
74
|
+
klass = association(#{name.inspect}).klass
|
75
|
+
old_record = klass.find_by(klass.primary_key => old_foreign_id)
|
76
|
+
|
77
|
+
if old_record
|
78
|
+
old_record.touch #{options[:touch].inspect if options[:touch] != true}
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
record = #{name}
|
83
|
+
unless record.nil? || record.new_record?
|
84
|
+
record.touch #{options[:touch].inspect if options[:touch] != true}
|
85
|
+
end
|
86
|
+
end
|
87
|
+
CODE
|
88
|
+
|
89
|
+
model.after_save "belongs_to_touch_after_save_or_destroy_for_#{name}"
|
90
|
+
model.after_touch "belongs_to_touch_after_save_or_destroy_for_#{name}"
|
91
|
+
model.after_destroy "belongs_to_touch_after_save_or_destroy_for_#{name}"
|
92
|
+
end
|
93
|
+
|
94
|
+
def valid_dependent_options
|
95
|
+
[:destroy, :delete]
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
require 'active_record/associations'
|
2
|
+
|
3
|
+
module ActiveRecord::Associations::Builder
|
4
|
+
class CollectionAssociation < Association #:nodoc:
|
5
|
+
|
6
|
+
CALLBACKS = [:before_add, :after_add, :before_remove, :after_remove]
|
7
|
+
|
8
|
+
def valid_options
|
9
|
+
super + [:table_name, :finder_sql, :counter_sql, :before_add,
|
10
|
+
:after_add, :before_remove, :after_remove, :extend]
|
11
|
+
end
|
12
|
+
|
13
|
+
attr_reader :block_extension, :extension_module
|
14
|
+
|
15
|
+
def initialize(*args, &extension)
|
16
|
+
super(*args)
|
17
|
+
@block_extension = extension
|
18
|
+
end
|
19
|
+
|
20
|
+
def build
|
21
|
+
show_deprecation_warnings
|
22
|
+
wrap_block_extension
|
23
|
+
reflection = super
|
24
|
+
CALLBACKS.each { |callback_name| define_callback(callback_name) }
|
25
|
+
reflection
|
26
|
+
end
|
27
|
+
|
28
|
+
def writable?
|
29
|
+
true
|
30
|
+
end
|
31
|
+
|
32
|
+
def show_deprecation_warnings
|
33
|
+
[:finder_sql, :counter_sql].each do |name|
|
34
|
+
if options.include? name
|
35
|
+
ActiveSupport::Deprecation.warn("The :#{name} association option is deprecated. Please find an alternative (such as using scopes).")
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def wrap_block_extension
|
41
|
+
if block_extension
|
42
|
+
@extension_module = mod = Module.new(&block_extension)
|
43
|
+
silence_warnings do
|
44
|
+
model.parent.const_set(extension_module_name, mod)
|
45
|
+
end
|
46
|
+
|
47
|
+
prev_scope = @scope
|
48
|
+
|
49
|
+
if prev_scope
|
50
|
+
@scope = proc { |owner| instance_exec(owner, &prev_scope).extending(mod) }
|
51
|
+
else
|
52
|
+
@scope = proc { extending(mod) }
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def extension_module_name
|
58
|
+
@extension_module_name ||= "#{model.name.demodulize}#{name.to_s.camelize}AssociationExtension"
|
59
|
+
end
|
60
|
+
|
61
|
+
def define_callback(callback_name)
|
62
|
+
full_callback_name = "#{callback_name}_for_#{name}"
|
63
|
+
|
64
|
+
# TODO : why do i need method_defined? I think its because of the inheritance chain
|
65
|
+
model.class_attribute full_callback_name.to_sym unless model.method_defined?(full_callback_name)
|
66
|
+
model.send("#{full_callback_name}=", Array(options[callback_name.to_sym]))
|
67
|
+
end
|
68
|
+
|
69
|
+
def define_readers
|
70
|
+
super
|
71
|
+
|
72
|
+
mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1
|
73
|
+
def #{name.to_s.singularize}_ids
|
74
|
+
association(:#{name}).ids_reader
|
75
|
+
end
|
76
|
+
CODE
|
77
|
+
end
|
78
|
+
|
79
|
+
def define_writers
|
80
|
+
super
|
81
|
+
|
82
|
+
mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1
|
83
|
+
def #{name.to_s.singularize}_ids=(ids)
|
84
|
+
association(:#{name}).ids_writer(ids)
|
85
|
+
end
|
86
|
+
CODE
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|