1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
|
# This is the parent Association class which defines the variables
# used by all associations.
#
# The hierarchy is defined as follows:
# Association
# - SingularAssociation
# - BelongsToAssociation
# - HasOneAssociation
# - CollectionAssociation
# - HasManyAssociation
# - HasAndBelongsToManyAssociation
module ActiveRecord::Associations::Builder
class Association #:nodoc:
class << self
attr_accessor :extensions
end
self.extensions = []
VALID_OPTIONS = [:class_name, :class, :foreign_key, :validate]
attr_reader :name, :scope, :options
def self.build(model, name, scope, options, &block)
raise ArgumentError, "association names must be a Symbol" unless name.kind_of?(Symbol)
if scope.is_a?(Hash)
options = scope
scope = nil
end
builder = new(name, scope, options, &block)
reflection = builder.build(model)
builder.define_accessors model
builder.define_callbacks model, reflection
builder.define_extensions model
reflection
end
def self.create_builder(model, name, scope, options, &block)
raise ArgumentError, "association names must be a Symbol" unless name.kind_of?(Symbol)
if scope.is_a?(Hash)
options = scope
scope = nil
end
new(name, scope, options, &block)
end
def initialize(name, scope, options)
@name = name
@scope = scope
@options = options
validate_options
if scope && scope.arity == 0
@scope = proc { instance_exec(&scope) }
end
end
def build(model)
ActiveRecord::Reflection.create(macro, name, scope, options, model)
end
def macro
raise NotImplementedError
end
def valid_options
VALID_OPTIONS + Association.extensions.flat_map(&:valid_options)
end
def validate_options
options.assert_valid_keys(valid_options)
end
def define_extensions(model)
end
def define_callbacks(model, reflection)
add_before_destroy_callbacks(model, name) if options[:dependent]
Association.extensions.each do |extension|
extension.build model, reflection
end
end
# Defines the setter and getter methods for the association
# class Post < ActiveRecord::Base
# has_many :comments
# end
#
# Post.first.comments and Post.first.comments= methods are defined by this method...
def define_accessors(model)
mixin = model.generated_feature_methods
define_readers(mixin)
define_writers(mixin)
end
def define_readers(mixin)
mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1
def #{name}(*args)
association(:#{name}).reader(*args)
end
CODE
end
def define_writers(mixin)
mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1
def #{name}=(value)
association(:#{name}).writer(value)
end
CODE
end
def valid_dependent_options
raise NotImplementedError
end
private
def add_before_destroy_callbacks(model, name)
unless valid_dependent_options.include? options[:dependent]
raise ArgumentError, "The :dependent option must be one of #{valid_dependent_options}, but is :#{options[:dependent]}"
end
model.before_destroy lambda { |o| o.association(name).handle_dependency }
end
end
end
|