activerecord 4.0.4 → 4.1.16
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 +4 -4
- data/CHANGELOG.md +1632 -1797
- data/MIT-LICENSE +1 -1
- data/README.rdoc +2 -2
- data/examples/performance.rb +30 -18
- data/examples/simple.rb +4 -4
- data/lib/active_record/aggregations.rb +2 -1
- data/lib/active_record/association_relation.rb +4 -0
- data/lib/active_record/associations/alias_tracker.rb +49 -29
- data/lib/active_record/associations/association.rb +9 -17
- data/lib/active_record/associations/association_scope.rb +59 -49
- data/lib/active_record/associations/belongs_to_association.rb +34 -25
- data/lib/active_record/associations/belongs_to_polymorphic_association.rb +6 -1
- data/lib/active_record/associations/builder/association.rb +84 -54
- data/lib/active_record/associations/builder/belongs_to.rb +90 -58
- data/lib/active_record/associations/builder/collection_association.rb +47 -45
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +119 -25
- data/lib/active_record/associations/builder/has_many.rb +3 -3
- data/lib/active_record/associations/builder/has_one.rb +5 -7
- data/lib/active_record/associations/builder/singular_association.rb +6 -7
- data/lib/active_record/associations/collection_association.rb +121 -111
- data/lib/active_record/associations/collection_proxy.rb +73 -18
- data/lib/active_record/associations/has_many_association.rb +14 -11
- data/lib/active_record/associations/has_many_through_association.rb +33 -6
- data/lib/active_record/associations/has_one_association.rb +1 -1
- data/lib/active_record/associations/join_dependency/join_association.rb +46 -104
- data/lib/active_record/associations/join_dependency/join_base.rb +6 -8
- data/lib/active_record/associations/join_dependency/join_part.rb +18 -37
- data/lib/active_record/associations/join_dependency.rb +208 -168
- data/lib/active_record/associations/preloader/association.rb +69 -27
- data/lib/active_record/associations/preloader/collection_association.rb +2 -2
- data/lib/active_record/associations/preloader/has_many_through.rb +1 -1
- data/lib/active_record/associations/preloader/singular_association.rb +3 -3
- data/lib/active_record/associations/preloader/through_association.rb +58 -26
- data/lib/active_record/associations/preloader.rb +63 -49
- data/lib/active_record/associations/singular_association.rb +6 -5
- data/lib/active_record/associations/through_association.rb +30 -9
- data/lib/active_record/associations.rb +116 -42
- data/lib/active_record/attribute_assignment.rb +6 -3
- data/lib/active_record/attribute_methods/before_type_cast.rb +2 -1
- data/lib/active_record/attribute_methods/dirty.rb +35 -26
- data/lib/active_record/attribute_methods/primary_key.rb +8 -1
- data/lib/active_record/attribute_methods/read.rb +56 -29
- data/lib/active_record/attribute_methods/serialization.rb +44 -12
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +13 -1
- data/lib/active_record/attribute_methods/write.rb +59 -26
- data/lib/active_record/attribute_methods.rb +82 -43
- data/lib/active_record/autosave_association.rb +209 -194
- data/lib/active_record/base.rb +6 -2
- data/lib/active_record/callbacks.rb +2 -2
- data/lib/active_record/coders/json.rb +13 -0
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +5 -10
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +14 -24
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +13 -13
- data/lib/active_record/connection_adapters/abstract/quoting.rb +6 -3
- data/lib/active_record/connection_adapters/abstract/savepoints.rb +21 -0
- data/lib/active_record/connection_adapters/abstract/schema_creation.rb +90 -0
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +9 -8
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +45 -70
- data/lib/active_record/connection_adapters/abstract/transaction.rb +1 -0
- data/lib/active_record/connection_adapters/abstract_adapter.rb +28 -96
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +74 -66
- data/lib/active_record/connection_adapters/column.rb +1 -35
- data/lib/active_record/connection_adapters/connection_specification.rb +231 -43
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +10 -5
- data/lib/active_record/connection_adapters/mysql_adapter.rb +24 -17
- data/lib/active_record/connection_adapters/postgresql/array_parser.rb +22 -15
- data/lib/active_record/connection_adapters/postgresql/cast.rb +12 -4
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +18 -44
- data/lib/active_record/connection_adapters/postgresql/oid.rb +38 -14
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +37 -12
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +20 -11
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +98 -52
- data/lib/active_record/connection_adapters/schema_cache.rb +8 -29
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +55 -60
- data/lib/active_record/connection_handling.rb +39 -5
- data/lib/active_record/core.rb +38 -54
- data/lib/active_record/counter_cache.rb +9 -10
- data/lib/active_record/dynamic_matchers.rb +6 -2
- data/lib/active_record/enum.rb +199 -0
- data/lib/active_record/errors.rb +22 -5
- data/lib/active_record/fixture_set/file.rb +2 -1
- data/lib/active_record/fixtures.rb +173 -76
- data/lib/active_record/gem_version.rb +15 -0
- data/lib/active_record/inheritance.rb +23 -9
- data/lib/active_record/integration.rb +54 -1
- data/lib/active_record/locking/optimistic.rb +7 -2
- data/lib/active_record/locking/pessimistic.rb +1 -1
- data/lib/active_record/log_subscriber.rb +6 -13
- data/lib/active_record/migration/command_recorder.rb +8 -2
- data/lib/active_record/migration.rb +91 -56
- data/lib/active_record/model_schema.rb +7 -14
- data/lib/active_record/nested_attributes.rb +25 -13
- data/lib/active_record/no_touching.rb +52 -0
- data/lib/active_record/null_relation.rb +26 -6
- data/lib/active_record/persistence.rb +23 -29
- data/lib/active_record/querying.rb +15 -12
- data/lib/active_record/railtie.rb +12 -61
- data/lib/active_record/railties/databases.rake +37 -56
- data/lib/active_record/readonly_attributes.rb +0 -6
- data/lib/active_record/reflection.rb +230 -79
- data/lib/active_record/relation/batches.rb +74 -24
- data/lib/active_record/relation/calculations.rb +52 -48
- data/lib/active_record/relation/delegation.rb +54 -39
- data/lib/active_record/relation/finder_methods.rb +210 -67
- data/lib/active_record/relation/merger.rb +15 -12
- data/lib/active_record/relation/predicate_builder/array_handler.rb +29 -0
- data/lib/active_record/relation/predicate_builder/relation_handler.rb +17 -0
- data/lib/active_record/relation/predicate_builder.rb +81 -40
- data/lib/active_record/relation/query_methods.rb +185 -108
- data/lib/active_record/relation/spawn_methods.rb +8 -5
- data/lib/active_record/relation.rb +79 -84
- data/lib/active_record/result.rb +45 -6
- data/lib/active_record/runtime_registry.rb +5 -0
- data/lib/active_record/sanitization.rb +4 -4
- data/lib/active_record/schema_dumper.rb +18 -6
- data/lib/active_record/schema_migration.rb +31 -18
- data/lib/active_record/scoping/default.rb +5 -18
- data/lib/active_record/scoping/named.rb +14 -29
- data/lib/active_record/scoping.rb +5 -0
- data/lib/active_record/store.rb +67 -18
- data/lib/active_record/tasks/database_tasks.rb +66 -26
- data/lib/active_record/tasks/mysql_database_tasks.rb +16 -10
- data/lib/active_record/tasks/postgresql_database_tasks.rb +1 -1
- data/lib/active_record/tasks/sqlite_database_tasks.rb +5 -1
- data/lib/active_record/timestamp.rb +6 -6
- data/lib/active_record/transactions.rb +10 -12
- data/lib/active_record/validations/presence.rb +1 -1
- data/lib/active_record/validations/uniqueness.rb +19 -9
- data/lib/active_record/version.rb +4 -7
- data/lib/active_record.rb +5 -7
- data/lib/rails/generators/active_record/migration/migration_generator.rb +4 -0
- data/lib/rails/generators/active_record/migration.rb +18 -0
- data/lib/rails/generators/active_record/model/model_generator.rb +4 -0
- data/lib/rails/generators/active_record.rb +2 -8
- metadata +18 -30
- data/lib/active_record/associations/has_and_belongs_to_many_association.rb +0 -65
- data/lib/active_record/associations/join_helper.rb +0 -45
- data/lib/active_record/associations/preloader/has_and_belongs_to_many.rb +0 -60
- data/lib/active_record/tasks/firebird_database_tasks.rb +0 -56
- data/lib/active_record/tasks/oracle_database_tasks.rb +0 -45
- data/lib/active_record/tasks/sqlserver_database_tasks.rb +0 -48
- data/lib/active_record/test_case.rb +0 -96
@@ -1,50 +1,72 @@
|
|
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
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.
|
4
20
|
attr_accessor :valid_options
|
5
21
|
end
|
22
|
+
self.extensions = []
|
23
|
+
|
24
|
+
self.valid_options = [:class_name, :anonymous_class, :foreign_key, :validate]
|
6
25
|
|
7
|
-
|
26
|
+
attr_reader :name, :scope, :options
|
8
27
|
|
9
|
-
|
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
|
+
builder.define_extensions model
|
40
|
+
reflection
|
13
41
|
end
|
14
42
|
|
15
|
-
def
|
43
|
+
def self.create_builder(model, name, scope, options, &block)
|
16
44
|
raise ArgumentError, "association names must be a Symbol" unless name.kind_of?(Symbol)
|
17
45
|
|
18
|
-
|
19
|
-
|
46
|
+
new(model, name, scope, options, &block)
|
47
|
+
end
|
20
48
|
|
49
|
+
def initialize(model, name, scope, options)
|
50
|
+
# TODO: Move this to create_builder as soon we drop support to activerecord-deprecated_finders.
|
21
51
|
if scope.is_a?(Hash)
|
22
|
-
|
23
|
-
|
24
|
-
else
|
25
|
-
@scope = scope
|
26
|
-
@options = options
|
52
|
+
options = scope
|
53
|
+
scope = nil
|
27
54
|
end
|
28
55
|
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
end
|
56
|
+
# TODO: Remove this model argument as soon we drop support to activerecord-deprecated_finders.
|
57
|
+
@name = name
|
58
|
+
@scope = scope
|
59
|
+
@options = options
|
34
60
|
|
35
|
-
|
36
|
-
@model.generated_feature_methods
|
37
|
-
end
|
61
|
+
validate_options
|
38
62
|
|
39
|
-
|
63
|
+
if scope && scope.arity == 0
|
64
|
+
@scope = proc { instance_exec(&scope) }
|
65
|
+
end
|
66
|
+
end
|
40
67
|
|
41
|
-
def build
|
42
|
-
|
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
|
68
|
+
def build(model)
|
69
|
+
ActiveRecord::Reflection.create(macro, name, scope, options, model)
|
48
70
|
end
|
49
71
|
|
50
72
|
def macro
|
@@ -52,19 +74,37 @@ module ActiveRecord::Associations::Builder
|
|
52
74
|
end
|
53
75
|
|
54
76
|
def valid_options
|
55
|
-
Association.valid_options
|
77
|
+
Association.valid_options + Association.extensions.flat_map(&:valid_options)
|
56
78
|
end
|
57
79
|
|
58
80
|
def validate_options
|
59
81
|
options.assert_valid_keys(valid_options)
|
60
82
|
end
|
61
83
|
|
62
|
-
def
|
63
|
-
define_readers
|
64
|
-
define_writers
|
84
|
+
def define_extensions(model)
|
65
85
|
end
|
66
86
|
|
67
|
-
def
|
87
|
+
def self.define_callbacks(model, reflection)
|
88
|
+
add_before_destroy_callbacks(model, reflection) if reflection.options[:dependent]
|
89
|
+
Association.extensions.each do |extension|
|
90
|
+
extension.build model, reflection
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
# Defines the setter and getter methods for the association
|
95
|
+
# class Post < ActiveRecord::Base
|
96
|
+
# has_many :comments
|
97
|
+
# end
|
98
|
+
#
|
99
|
+
# Post.first.comments and Post.first.comments= methods are defined by this method...
|
100
|
+
def self.define_accessors(model, reflection)
|
101
|
+
mixin = model.generated_association_methods
|
102
|
+
name = reflection.name
|
103
|
+
define_readers(mixin, name)
|
104
|
+
define_writers(mixin, name)
|
105
|
+
end
|
106
|
+
|
107
|
+
def self.define_readers(mixin, name)
|
68
108
|
mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1
|
69
109
|
def #{name}(*args)
|
70
110
|
association(:#{name}).reader(*args)
|
@@ -72,7 +112,7 @@ module ActiveRecord::Associations::Builder
|
|
72
112
|
CODE
|
73
113
|
end
|
74
114
|
|
75
|
-
def define_writers
|
115
|
+
def self.define_writers(mixin, name)
|
76
116
|
mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1
|
77
117
|
def #{name}=(value)
|
78
118
|
association(:#{name}).writer(value)
|
@@ -80,29 +120,19 @@ module ActiveRecord::Associations::Builder
|
|
80
120
|
CODE
|
81
121
|
end
|
82
122
|
|
83
|
-
def
|
84
|
-
|
85
|
-
|
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
|
123
|
+
def self.valid_dependent_options
|
124
|
+
raise NotImplementedError
|
125
|
+
end
|
94
126
|
|
95
|
-
|
96
|
-
def #{macro}_dependent_for_#{name}
|
97
|
-
association(:#{name}).handle_dependency
|
98
|
-
end
|
99
|
-
CODE
|
127
|
+
private
|
100
128
|
|
101
|
-
|
102
|
-
|
129
|
+
def self.add_before_destroy_callbacks(model, reflection)
|
130
|
+
unless valid_dependent_options.include? reflection.options[:dependent]
|
131
|
+
raise ArgumentError, "The :dependent option must be one of #{valid_dependent_options}, but is :#{reflection.options[:dependent]}"
|
132
|
+
end
|
103
133
|
|
104
|
-
|
105
|
-
|
134
|
+
name = reflection.name
|
135
|
+
model.before_destroy lambda { |o| o.association(name).handle_dependency }
|
106
136
|
end
|
107
137
|
end
|
108
138
|
end
|
@@ -8,99 +8,131 @@ module ActiveRecord::Associations::Builder
|
|
8
8
|
super + [:foreign_type, :polymorphic, :touch, :counter_cache]
|
9
9
|
end
|
10
10
|
|
11
|
-
def
|
12
|
-
|
11
|
+
def self.valid_dependent_options
|
12
|
+
[:destroy, :delete]
|
13
13
|
end
|
14
14
|
|
15
|
-
def
|
16
|
-
|
17
|
-
add_counter_cache_callbacks(reflection) if options[:counter_cache]
|
18
|
-
add_touch_callbacks(reflection) if options[:touch]
|
19
|
-
reflection
|
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]
|
20
19
|
end
|
21
20
|
|
22
|
-
def
|
23
|
-
|
24
|
-
|
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_create
|
25
30
|
|
26
|
-
mixin.class_eval
|
27
|
-
def
|
28
|
-
if record =
|
29
|
-
|
31
|
+
mixin.class_eval do
|
32
|
+
def belongs_to_counter_cache_after_create(reflection)
|
33
|
+
if record = send(reflection.name)
|
34
|
+
cache_column = reflection.counter_cache_column
|
35
|
+
record.class.increment_counter(cache_column, record.id)
|
30
36
|
@_after_create_counter_called = true
|
31
37
|
end
|
32
38
|
end
|
33
39
|
|
34
|
-
def
|
35
|
-
|
36
|
-
|
40
|
+
def belongs_to_counter_cache_before_destroy(reflection)
|
41
|
+
foreign_key = reflection.foreign_key.to_sym
|
42
|
+
unless destroyed_by_association && destroyed_by_association.foreign_key.to_sym == foreign_key
|
43
|
+
record = send reflection.name
|
37
44
|
if record && !self.destroyed?
|
38
|
-
|
45
|
+
cache_column = reflection.counter_cache_column
|
46
|
+
record.class.decrement_counter(cache_column, record.id)
|
39
47
|
end
|
40
48
|
end
|
41
49
|
end
|
42
50
|
|
43
|
-
def
|
51
|
+
def belongs_to_counter_cache_after_update(reflection)
|
52
|
+
foreign_key = reflection.foreign_key
|
53
|
+
cache_column = reflection.counter_cache_column
|
54
|
+
|
44
55
|
if (@_after_create_counter_called ||= false)
|
45
56
|
@_after_create_counter_called = false
|
46
|
-
elsif
|
47
|
-
model
|
48
|
-
foreign_key_was =
|
49
|
-
foreign_key
|
57
|
+
elsif attribute_changed?(foreign_key) && !new_record? && reflection.constructable?
|
58
|
+
model = reflection.klass
|
59
|
+
foreign_key_was = attribute_was foreign_key
|
60
|
+
foreign_key = attribute foreign_key
|
50
61
|
|
51
62
|
if foreign_key && model.respond_to?(:increment_counter)
|
52
|
-
model.increment_counter(
|
63
|
+
model.increment_counter(cache_column, foreign_key)
|
53
64
|
end
|
54
65
|
if foreign_key_was && model.respond_to?(:decrement_counter)
|
55
|
-
model.decrement_counter(
|
66
|
+
model.decrement_counter(cache_column, foreign_key_was)
|
56
67
|
end
|
57
68
|
end
|
58
69
|
end
|
59
|
-
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def self.add_counter_cache_callbacks(model, reflection)
|
74
|
+
cache_column = reflection.counter_cache_column
|
75
|
+
|
76
|
+
model.after_create lambda { |record|
|
77
|
+
record.belongs_to_counter_cache_after_create(reflection)
|
78
|
+
}
|
60
79
|
|
61
|
-
model.
|
62
|
-
|
63
|
-
|
80
|
+
model.before_destroy lambda { |record|
|
81
|
+
record.belongs_to_counter_cache_before_destroy(reflection)
|
82
|
+
}
|
83
|
+
|
84
|
+
model.after_update lambda { |record|
|
85
|
+
record.belongs_to_counter_cache_after_update(reflection)
|
86
|
+
}
|
64
87
|
|
65
88
|
klass = reflection.class_name.safe_constantize
|
66
89
|
klass.attr_readonly cache_column if klass && klass.respond_to?(:attr_readonly)
|
67
90
|
end
|
68
91
|
|
69
|
-
def
|
70
|
-
|
71
|
-
def belongs_to_touch_after_save_or_destroy_for_#{name}
|
72
|
-
foreign_key_field = #{reflection.foreign_key.inspect}
|
73
|
-
old_foreign_id = changed_attributes[foreign_key_field]
|
74
|
-
|
75
|
-
if old_foreign_id
|
76
|
-
association = association(:#{name})
|
77
|
-
reflection = association.reflection
|
78
|
-
if reflection.polymorphic?
|
79
|
-
klass = send("#{reflection.foreign_type}_was").constantize
|
80
|
-
else
|
81
|
-
klass = association.klass
|
82
|
-
end
|
83
|
-
old_record = klass.find_by(klass.primary_key => old_foreign_id)
|
92
|
+
def self.touch_record(o, foreign_key, name, touch) # :nodoc:
|
93
|
+
old_foreign_id = o.changed_attributes[foreign_key]
|
84
94
|
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
95
|
+
if old_foreign_id
|
96
|
+
association = o.association(name)
|
97
|
+
reflection = association.reflection
|
98
|
+
if reflection.polymorphic?
|
99
|
+
klass = o.public_send("#{reflection.foreign_type}_was").constantize
|
100
|
+
else
|
101
|
+
klass = association.klass
|
102
|
+
end
|
103
|
+
old_record = klass.find_by(klass.primary_key => old_foreign_id)
|
89
104
|
|
90
|
-
|
91
|
-
if
|
92
|
-
|
105
|
+
if old_record
|
106
|
+
if touch != true
|
107
|
+
old_record.touch touch
|
108
|
+
else
|
109
|
+
old_record.touch
|
93
110
|
end
|
94
111
|
end
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
112
|
+
end
|
113
|
+
|
114
|
+
record = o.send name
|
115
|
+
if record && record.persisted?
|
116
|
+
if touch != true
|
117
|
+
record.touch touch
|
118
|
+
else
|
119
|
+
record.touch
|
120
|
+
end
|
121
|
+
end
|
100
122
|
end
|
101
123
|
|
102
|
-
def
|
103
|
-
|
124
|
+
def self.add_touch_callbacks(model, reflection)
|
125
|
+
foreign_key = reflection.foreign_key
|
126
|
+
n = reflection.name
|
127
|
+
touch = reflection.options[:touch]
|
128
|
+
|
129
|
+
callback = lambda { |record|
|
130
|
+
BelongsTo.touch_record(record, foreign_key, n, touch)
|
131
|
+
}
|
132
|
+
|
133
|
+
model.after_save callback
|
134
|
+
model.after_touch callback
|
135
|
+
model.after_destroy callback
|
104
136
|
end
|
105
137
|
end
|
106
138
|
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# This class is inherited by the has_many and has_many_and_belongs_to_many association classes
|
2
|
+
|
1
3
|
require 'active_record/associations'
|
2
4
|
|
3
5
|
module ActiveRecord::Associations::Builder
|
@@ -6,67 +8,57 @@ module ActiveRecord::Associations::Builder
|
|
6
8
|
CALLBACKS = [:before_add, :after_add, :before_remove, :after_remove]
|
7
9
|
|
8
10
|
def valid_options
|
9
|
-
super + [:table_name, :
|
11
|
+
super + [:table_name, :before_add,
|
10
12
|
:after_add, :before_remove, :after_remove, :extend]
|
11
13
|
end
|
12
14
|
|
13
|
-
attr_reader :block_extension
|
14
|
-
|
15
|
-
def initialize(*args, &extension)
|
16
|
-
super(*args)
|
17
|
-
@block_extension = extension
|
18
|
-
end
|
15
|
+
attr_reader :block_extension
|
19
16
|
|
20
|
-
def
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
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
|
26
24
|
end
|
27
25
|
|
28
|
-
def
|
29
|
-
|
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
|
+
}
|
30
33
|
end
|
31
34
|
|
32
|
-
def
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
end
|
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)
|
37
39
|
end
|
38
40
|
end
|
39
41
|
|
40
|
-
def
|
41
|
-
|
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
|
42
|
+
def self.define_callback(model, callback_name, name, options)
|
43
|
+
full_callback_name = "#{callback_name}_for_#{name}"
|
48
44
|
|
49
|
-
|
50
|
-
|
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) }
|
51
53
|
else
|
52
|
-
|
54
|
+
->(method, owner, record) { callback.send(method, owner, record) }
|
53
55
|
end
|
54
56
|
end
|
57
|
+
model.send "#{full_callback_name}=", callbacks
|
55
58
|
end
|
56
59
|
|
57
|
-
|
58
|
-
|
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
|
60
|
+
# Defines the setter and getter methods for the collection_singular_ids.
|
61
|
+
def self.define_readers(mixin, name)
|
70
62
|
super
|
71
63
|
|
72
64
|
mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1
|
@@ -76,7 +68,7 @@ module ActiveRecord::Associations::Builder
|
|
76
68
|
CODE
|
77
69
|
end
|
78
70
|
|
79
|
-
def define_writers
|
71
|
+
def self.define_writers(mixin, name)
|
80
72
|
super
|
81
73
|
|
82
74
|
mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1
|
@@ -85,5 +77,15 @@ module ActiveRecord::Associations::Builder
|
|
85
77
|
end
|
86
78
|
CODE
|
87
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
|
88
90
|
end
|
89
91
|
end
|
@@ -1,39 +1,133 @@
|
|
1
1
|
module ActiveRecord::Associations::Builder
|
2
|
-
class HasAndBelongsToMany
|
3
|
-
|
4
|
-
:
|
5
|
-
|
2
|
+
class HasAndBelongsToMany # :nodoc:
|
3
|
+
class JoinTableResolver
|
4
|
+
KnownTable = Struct.new :join_table
|
5
|
+
|
6
|
+
class KnownClass
|
7
|
+
def initialize(lhs_class, rhs_class_name)
|
8
|
+
@lhs_class = lhs_class
|
9
|
+
@rhs_class_name = rhs_class_name
|
10
|
+
@join_table = nil
|
11
|
+
end
|
12
|
+
|
13
|
+
def join_table
|
14
|
+
@join_table ||= [@lhs_class.table_name, klass.table_name].sort.join("\0").gsub(/^(.*[._])(.+)\0\1(.+)/, '\1\2_\3').gsub("\0", "_")
|
15
|
+
end
|
6
16
|
|
7
|
-
|
8
|
-
|
17
|
+
private
|
18
|
+
|
19
|
+
def klass
|
20
|
+
@lhs_class.send(:compute_type, @rhs_class_name)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.build(lhs_class, name, options)
|
25
|
+
if options[:join_table]
|
26
|
+
KnownTable.new options[:join_table].to_s
|
27
|
+
else
|
28
|
+
class_name = options.fetch(:class_name) {
|
29
|
+
name.to_s.camelize.singularize
|
30
|
+
}
|
31
|
+
KnownClass.new lhs_class, class_name
|
32
|
+
end
|
33
|
+
end
|
9
34
|
end
|
10
35
|
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
36
|
+
attr_reader :lhs_model, :association_name, :options
|
37
|
+
|
38
|
+
def initialize(association_name, lhs_model, options)
|
39
|
+
@association_name = association_name
|
40
|
+
@lhs_model = lhs_model
|
41
|
+
@options = options
|
15
42
|
end
|
16
43
|
|
17
|
-
def
|
18
|
-
|
44
|
+
def through_model
|
45
|
+
habtm = JoinTableResolver.build lhs_model, association_name, options
|
19
46
|
|
20
|
-
|
21
|
-
|
22
|
-
|
47
|
+
join_model = Class.new(ActiveRecord::Base) {
|
48
|
+
class << self;
|
49
|
+
attr_accessor :class_resolver
|
50
|
+
attr_accessor :name
|
51
|
+
attr_accessor :table_name_resolver
|
52
|
+
attr_accessor :left_reflection
|
53
|
+
attr_accessor :right_reflection
|
23
54
|
end
|
55
|
+
|
56
|
+
def self.table_name
|
57
|
+
table_name_resolver.join_table
|
58
|
+
end
|
59
|
+
|
60
|
+
def self.compute_type(class_name)
|
61
|
+
class_resolver.compute_type class_name
|
62
|
+
end
|
63
|
+
|
64
|
+
def self.add_left_association(name, options)
|
65
|
+
belongs_to name, options
|
66
|
+
self.left_reflection = _reflect_on_association(name)
|
67
|
+
end
|
68
|
+
|
69
|
+
def self.add_right_association(name, options)
|
70
|
+
rhs_name = name.to_s.singularize.to_sym
|
71
|
+
belongs_to rhs_name, options
|
72
|
+
self.right_reflection = _reflect_on_association(rhs_name)
|
73
|
+
end
|
74
|
+
|
75
|
+
def hash
|
76
|
+
object_id.hash
|
77
|
+
end
|
78
|
+
|
79
|
+
def ==(other)
|
80
|
+
equal?(other)
|
81
|
+
end
|
82
|
+
alias :eql? :==
|
83
|
+
|
84
|
+
}
|
85
|
+
|
86
|
+
join_model.name = "HABTM_#{association_name.to_s.camelize}"
|
87
|
+
join_model.table_name_resolver = habtm
|
88
|
+
join_model.class_resolver = lhs_model
|
89
|
+
|
90
|
+
join_model.add_left_association :left_side, anonymous_class: lhs_model
|
91
|
+
join_model.add_right_association association_name, belongs_to_options(options)
|
92
|
+
join_model
|
93
|
+
end
|
94
|
+
|
95
|
+
def middle_reflection(join_model)
|
96
|
+
middle_name = [lhs_model.name.downcase.pluralize,
|
97
|
+
association_name].join('_').gsub(/::/, '_').to_sym
|
98
|
+
middle_options = middle_options join_model
|
99
|
+
hm_builder = HasMany.create_builder(lhs_model,
|
100
|
+
middle_name,
|
101
|
+
nil,
|
102
|
+
middle_options)
|
103
|
+
hm_builder.build lhs_model
|
104
|
+
end
|
105
|
+
|
106
|
+
private
|
107
|
+
|
108
|
+
def middle_options(join_model)
|
109
|
+
middle_options = {}
|
110
|
+
middle_options[:anonymous_class] = join_model
|
111
|
+
middle_options[:source] = join_model.left_reflection.name
|
112
|
+
if options.key? :foreign_key
|
113
|
+
middle_options[:foreign_key] = options[:foreign_key]
|
24
114
|
end
|
115
|
+
middle_options
|
25
116
|
end
|
26
117
|
|
27
|
-
def
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
118
|
+
def belongs_to_options(options)
|
119
|
+
rhs_options = {}
|
120
|
+
|
121
|
+
if options.key? :class_name
|
122
|
+
rhs_options[:foreign_key] = options[:class_name].foreign_key
|
123
|
+
rhs_options[:class_name] = options[:class_name]
|
124
|
+
end
|
125
|
+
|
126
|
+
if options.key? :association_foreign_key
|
127
|
+
rhs_options[:foreign_key] = options[:association_foreign_key]
|
128
|
+
end
|
129
|
+
|
130
|
+
rhs_options
|
37
131
|
end
|
38
132
|
end
|
39
133
|
end
|