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,111 @@
|
|
1
|
+
module ActiveRecord
|
2
|
+
# = Active Record Belongs To Association
|
3
|
+
module Associations
|
4
|
+
class BelongsToAssociation < SingularAssociation #:nodoc:
|
5
|
+
|
6
|
+
def handle_dependency
|
7
|
+
target.send(options[:dependent]) if load_target
|
8
|
+
end
|
9
|
+
|
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
|
21
|
+
|
22
|
+
self.target = record
|
23
|
+
end
|
24
|
+
|
25
|
+
def reset
|
26
|
+
super
|
27
|
+
@updated = false
|
28
|
+
end
|
29
|
+
|
30
|
+
def updated?
|
31
|
+
@updated
|
32
|
+
end
|
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
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
def find_target?
|
45
|
+
!loaded? && foreign_key_present? && klass
|
46
|
+
end
|
47
|
+
|
48
|
+
def with_cache_name
|
49
|
+
counter_cache_name = reflection.counter_cache_column
|
50
|
+
return unless counter_cache_name && owner.persisted?
|
51
|
+
yield counter_cache_name
|
52
|
+
end
|
53
|
+
|
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
|
67
|
+
|
68
|
+
def increment_counter(counter_cache_name)
|
69
|
+
if foreign_key_present?
|
70
|
+
klass.increment_counter(counter_cache_name, target_id)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
# Checks whether record is different to the current target, without loading it
|
75
|
+
def different_target?(record)
|
76
|
+
record.id != owner[reflection.foreign_key]
|
77
|
+
end
|
78
|
+
|
79
|
+
def replace_keys(record)
|
80
|
+
owner[reflection.foreign_key] = record[reflection.association_primary_key(record.class)]
|
81
|
+
end
|
82
|
+
|
83
|
+
def remove_keys
|
84
|
+
owner[reflection.foreign_key] = nil
|
85
|
+
end
|
86
|
+
|
87
|
+
def foreign_key_present?
|
88
|
+
owner[reflection.foreign_key]
|
89
|
+
end
|
90
|
+
|
91
|
+
# NOTE - for now, we're only supporting inverse setting from belongs_to back onto
|
92
|
+
# has_one associations.
|
93
|
+
def invertible_for?(record)
|
94
|
+
inverse = inverse_reflection_for(record)
|
95
|
+
inverse && inverse.has_one?
|
96
|
+
end
|
97
|
+
|
98
|
+
def target_id
|
99
|
+
if options[:primary_key]
|
100
|
+
owner.send(reflection.name).try(:id)
|
101
|
+
else
|
102
|
+
owner[reflection.foreign_key]
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
def stale_state
|
107
|
+
owner[reflection.foreign_key] && owner[reflection.foreign_key].to_s
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module ActiveRecord
|
2
|
+
# = Active Record Belongs To Polymorphic Association
|
3
|
+
module Associations
|
4
|
+
class BelongsToPolymorphicAssociation < BelongsToAssociation #:nodoc:
|
5
|
+
def klass
|
6
|
+
type = owner[reflection.foreign_type]
|
7
|
+
type.presence && type.constantize
|
8
|
+
end
|
9
|
+
|
10
|
+
private
|
11
|
+
|
12
|
+
def replace_keys(record)
|
13
|
+
super
|
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
|
20
|
+
end
|
21
|
+
|
22
|
+
def different_target?(record)
|
23
|
+
super || record.class != klass
|
24
|
+
end
|
25
|
+
|
26
|
+
def inverse_reflection_for(record)
|
27
|
+
reflection.polymorphic_inverse_of(record.class)
|
28
|
+
end
|
29
|
+
|
30
|
+
def raise_on_type_mismatch!(record)
|
31
|
+
# A polymorphic association cannot have a type mismatch, by definition
|
32
|
+
end
|
33
|
+
|
34
|
+
def stale_state
|
35
|
+
foreign_key = super
|
36
|
+
foreign_key && [foreign_key.to_s, owner[reflection.foreign_type].to_s]
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +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
|
+
|
14
|
+
module ActiveRecord::Associations::Builder
|
15
|
+
class Association #:nodoc:
|
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 = []
|
23
|
+
|
24
|
+
self.valid_options = [:class_name, :class, :foreign_key, :validate]
|
25
|
+
|
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
|
34
|
+
|
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
|
42
|
+
end
|
43
|
+
|
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)
|
48
|
+
end
|
49
|
+
|
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
|
+
|
62
|
+
validate_options
|
63
|
+
|
64
|
+
if scope && scope.arity == 0
|
65
|
+
@scope = proc { instance_exec(&scope) }
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def build(model)
|
70
|
+
ActiveRecord::Reflection.create(macro, name, scope, options, model)
|
71
|
+
end
|
72
|
+
|
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)
|
92
|
+
end
|
93
|
+
|
94
|
+
Association.extensions.each do |extension|
|
95
|
+
extension.build model, reflection
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
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
|
111
|
+
|
112
|
+
def self.define_readers(mixin, name)
|
113
|
+
mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1
|
114
|
+
def #{name}(*args)
|
115
|
+
association(:#{name}).reader(*args)
|
116
|
+
end
|
117
|
+
CODE
|
118
|
+
end
|
119
|
+
|
120
|
+
def self.define_writers(mixin, name)
|
121
|
+
mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1
|
122
|
+
def #{name}=(value)
|
123
|
+
association(:#{name}).writer(value)
|
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}"
|
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
|
148
|
+
end
|
149
|
+
end
|
@@ -0,0 +1,116 @@
|
|
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, :counter_cache]
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.valid_dependent_options
|
12
|
+
[:destroy, :delete]
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.define_callbacks(model, reflection)
|
16
|
+
super
|
17
|
+
add_counter_cache_callbacks(model, reflection) if reflection.options[:counter_cache]
|
18
|
+
add_touch_callbacks(model, reflection) if reflection.options[:touch]
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.define_accessors(mixin, reflection)
|
22
|
+
super
|
23
|
+
add_counter_cache_methods mixin
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def self.add_counter_cache_methods(mixin)
|
29
|
+
return if mixin.method_defined? :belongs_to_counter_cache_after_update
|
30
|
+
|
31
|
+
mixin.class_eval do
|
32
|
+
def belongs_to_counter_cache_after_update(reflection)
|
33
|
+
foreign_key = reflection.foreign_key
|
34
|
+
cache_column = reflection.counter_cache_column
|
35
|
+
|
36
|
+
if (@_after_create_counter_called ||= false)
|
37
|
+
@_after_create_counter_called = false
|
38
|
+
elsif attribute_changed?(foreign_key) && !new_record? && reflection.constructable?
|
39
|
+
model = reflection.klass
|
40
|
+
foreign_key_was = attribute_was foreign_key
|
41
|
+
foreign_key = attribute foreign_key
|
42
|
+
|
43
|
+
if foreign_key && model.respond_to?(:increment_counter)
|
44
|
+
model.increment_counter(cache_column, foreign_key)
|
45
|
+
end
|
46
|
+
if foreign_key_was && model.respond_to?(:decrement_counter)
|
47
|
+
model.decrement_counter(cache_column, foreign_key_was)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def self.add_counter_cache_callbacks(model, reflection)
|
55
|
+
cache_column = reflection.counter_cache_column
|
56
|
+
|
57
|
+
model.after_update lambda { |record|
|
58
|
+
record.belongs_to_counter_cache_after_update(reflection)
|
59
|
+
}
|
60
|
+
|
61
|
+
klass = reflection.class_name.safe_constantize
|
62
|
+
klass.attr_readonly cache_column if klass && klass.respond_to?(:attr_readonly)
|
63
|
+
end
|
64
|
+
|
65
|
+
def self.touch_record(o, foreign_key, name, touch) # :nodoc:
|
66
|
+
old_foreign_id = o.changed_attributes[foreign_key]
|
67
|
+
|
68
|
+
if old_foreign_id
|
69
|
+
association = o.association(name)
|
70
|
+
reflection = association.reflection
|
71
|
+
if reflection.polymorphic?
|
72
|
+
klass = o.public_send("#{reflection.foreign_type}_was").constantize
|
73
|
+
else
|
74
|
+
klass = association.klass
|
75
|
+
end
|
76
|
+
old_record = klass.find_by(klass.primary_key => old_foreign_id)
|
77
|
+
|
78
|
+
if old_record
|
79
|
+
if touch != true
|
80
|
+
old_record.touch touch
|
81
|
+
else
|
82
|
+
old_record.touch
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
record = o.send name
|
88
|
+
if record && record.persisted?
|
89
|
+
if touch != true
|
90
|
+
record.touch touch
|
91
|
+
else
|
92
|
+
record.touch
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
def self.add_touch_callbacks(model, reflection)
|
98
|
+
foreign_key = reflection.foreign_key
|
99
|
+
n = reflection.name
|
100
|
+
touch = reflection.options[:touch]
|
101
|
+
|
102
|
+
callback = lambda { |record|
|
103
|
+
BelongsTo.touch_record(record, foreign_key, n, touch)
|
104
|
+
}
|
105
|
+
|
106
|
+
model.after_save callback, if: :changed?
|
107
|
+
model.after_touch callback
|
108
|
+
model.after_destroy callback
|
109
|
+
end
|
110
|
+
|
111
|
+
def self.add_destroy_callbacks(model, reflection)
|
112
|
+
name = reflection.name
|
113
|
+
model.after_destroy lambda { |o| o.association(name).handle_dependency }
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
@@ -0,0 +1,91 @@
|
|
1
|
+
# This class is inherited by the has_many and has_many_and_belongs_to_many association classes
|
2
|
+
|
3
|
+
require 'active_record/associations'
|
4
|
+
|
5
|
+
module ActiveRecord::Associations::Builder
|
6
|
+
class CollectionAssociation < Association #:nodoc:
|
7
|
+
|
8
|
+
CALLBACKS = [:before_add, :after_add, :before_remove, :after_remove]
|
9
|
+
|
10
|
+
def valid_options
|
11
|
+
super + [:table_name, :before_add,
|
12
|
+
:after_add, :before_remove, :after_remove, :extend]
|
13
|
+
end
|
14
|
+
|
15
|
+
attr_reader :block_extension
|
16
|
+
|
17
|
+
def initialize(model, name, scope, options)
|
18
|
+
super
|
19
|
+
@mod = nil
|
20
|
+
if block_given?
|
21
|
+
@mod = Module.new(&Proc.new)
|
22
|
+
@scope = wrap_scope @scope, @mod
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.define_callbacks(model, reflection)
|
27
|
+
super
|
28
|
+
name = reflection.name
|
29
|
+
options = reflection.options
|
30
|
+
CALLBACKS.each { |callback_name|
|
31
|
+
define_callback(model, callback_name, name, options)
|
32
|
+
}
|
33
|
+
end
|
34
|
+
|
35
|
+
def define_extensions(model)
|
36
|
+
if @mod
|
37
|
+
extension_module_name = "#{model.name.demodulize}#{name.to_s.camelize}AssociationExtension"
|
38
|
+
model.parent.const_set(extension_module_name, @mod)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def self.define_callback(model, callback_name, name, options)
|
43
|
+
full_callback_name = "#{callback_name}_for_#{name}"
|
44
|
+
|
45
|
+
# TODO : why do i need method_defined? I think its because of the inheritance chain
|
46
|
+
model.class_attribute full_callback_name unless model.method_defined?(full_callback_name)
|
47
|
+
callbacks = Array(options[callback_name.to_sym]).map do |callback|
|
48
|
+
case callback
|
49
|
+
when Symbol
|
50
|
+
->(method, owner, record) { owner.send(callback, record) }
|
51
|
+
when Proc
|
52
|
+
->(method, owner, record) { callback.call(owner, record) }
|
53
|
+
else
|
54
|
+
->(method, owner, record) { callback.send(method, owner, record) }
|
55
|
+
end
|
56
|
+
end
|
57
|
+
model.send "#{full_callback_name}=", callbacks
|
58
|
+
end
|
59
|
+
|
60
|
+
# Defines the setter and getter methods for the collection_singular_ids.
|
61
|
+
def self.define_readers(mixin, name)
|
62
|
+
super
|
63
|
+
|
64
|
+
mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1
|
65
|
+
def #{name.to_s.singularize}_ids
|
66
|
+
association(:#{name}).ids_reader
|
67
|
+
end
|
68
|
+
CODE
|
69
|
+
end
|
70
|
+
|
71
|
+
def self.define_writers(mixin, name)
|
72
|
+
super
|
73
|
+
|
74
|
+
mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1
|
75
|
+
def #{name.to_s.singularize}_ids=(ids)
|
76
|
+
association(:#{name}).ids_writer(ids)
|
77
|
+
end
|
78
|
+
CODE
|
79
|
+
end
|
80
|
+
|
81
|
+
private
|
82
|
+
|
83
|
+
def wrap_scope(scope, mod)
|
84
|
+
if scope
|
85
|
+
proc { |owner| instance_exec(owner, &scope).extending(mod) }
|
86
|
+
else
|
87
|
+
proc { extending(mod) }
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|