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
|
# This is the parent Association class which defines certain class variables (valid_options) and
# instance variables (model, name, scope, options, reflection) which would be common across all the associations that we known today in Rails..
# Every association need to have the values of these variables set and they are used at multiple places
# The heirarchy is defined as follows:
# Association
# - SingularAssociation
# - BelongsTo
# - HasOne
# - CollectionAssociation
# - HasMany
# - HasAndBelongsToMany
#
# The HasMany :Through association is a special case of HasMany association with the :through option set for it
#
module ActiveRecord::Associations::Builder
class Association #:nodoc:
class << self
attr_accessor :valid_options
end
self.valid_options = [:class_name, :foreign_key, :validate]
attr_reader :model, :name, :scope, :options, :reflection
def self.build(*args, &block)
new(*args, &block).build
end
def initialize(model, name, scope, options)
raise ArgumentError, "association names must be a Symbol" unless name.kind_of?(Symbol)
@model = model
@name = name
if scope.is_a?(Hash)
@scope = nil
@options = scope
else
@scope = scope
@options = options
end
if @scope && @scope.arity == 0
prev_scope = @scope
@scope = proc { instance_exec(&prev_scope) }
end
end
def mixin
@model.generated_feature_methods
end
include Module.new { def build; end }
def build
validate_options
define_accessors
configure_dependency if options[:dependent]
@reflection = model.create_reflection(macro, name, scope, options, model)
super # provides an extension point
@reflection
end
def macro
raise NotImplementedError
end
def valid_options
Association.valid_options
end
def validate_options
options.assert_valid_keys(valid_options)
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
define_readers
define_writers
end
def define_readers
mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1
def #{name}(*args)
association(:#{name}).reader(*args)
end
CODE
end
def define_writers
mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1
def #{name}=(value)
association(:#{name}).writer(value)
end
CODE
end
def configure_dependency
unless valid_dependent_options.include? options[:dependent]
raise ArgumentError, "The :dependent option must be one of #{valid_dependent_options}, but is :#{options[:dependent]}"
end
if options[:dependent] == :restrict
ActiveSupport::Deprecation.warn(
"The :restrict option is deprecated. Please use :restrict_with_exception instead, which " \
"provides the same functionality."
)
end
mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1
def #{macro}_dependent_for_#{name}
association(:#{name}).handle_dependency
end
CODE
model.before_destroy "#{macro}_dependent_for_#{name}"
end
def valid_dependent_options
raise NotImplementedError
end
end
end
|