association.rb 3.7 KB
Newer Older
1 2
require 'active_support/core_ext/module/attribute_accessors'

V
Vijay Dev 已提交
3 4 5 6
# This is the parent Association class which defines the variables
# used by all associations.
#
# The hierarchy is defined as follows:
7
#  Association
8
#    - SingularAssociation
V
Vijay Dev 已提交
9 10
#      - BelongsToAssociation
#      - HasOneAssociation
11
#    - CollectionAssociation
V
Vijay Dev 已提交
12
#      - HasManyAssociation
13

14 15
module ActiveRecord::Associations::Builder
  class Association #:nodoc:
16
    class << self
17
      attr_accessor :extensions
18
    end
19
    self.extensions = []
20

21 22 23 24
    # TODO: This class accessor is needed to make activerecord-deprecated_finders work.
    # We can move it to a constant in 5.0.
    cattr_accessor :valid_options, instance_accessor: false
    self.valid_options = [:class_name, :class, :foreign_key, :validate]
25

26
    def self.build(model, name, scope, options, &block)
27
      extension = define_extensions model, name, &block
28
      reflection = create_reflection model, name, scope, options, extension
29
      define_accessors model, reflection
30
      define_callbacks model, reflection
31
      reflection
32
    end
33

34
    def self.create_reflection(model, name, scope, options, extension = nil)
35 36 37 38 39 40 41
      raise ArgumentError, "association names must be a Symbol" unless name.kind_of?(Symbol)

      if scope.is_a?(Hash)
        options = scope
        scope   = nil
      end

42
      validate_options(options)
J
Jon Leighton 已提交
43

44
      scope = build_scope(scope, extension)
45

46
      ActiveRecord::Reflection.create(macro, name, scope, options, model)
47 48 49 50 51
    end

    def self.build_scope(scope, extension)
      new_scope = scope

52
      if scope && scope.arity == 0
53 54 55 56 57
        new_scope = proc { instance_exec(&scope) }
      end

      if extension
        new_scope = wrap_scope new_scope, extension
J
Jon Leighton 已提交
58
      end
59 60 61 62 63 64

      new_scope
    end

    def self.wrap_scope(scope, extension)
      scope
65 66
    end

67
    def self.macro
68 69 70
      raise NotImplementedError
    end

71 72
    def self.build_valid_options(options)
      self.valid_options + Association.extensions.flat_map(&:valid_options)
73 74
    end

75
    def self.validate_options(options)
76
      options.assert_valid_keys(build_valid_options(options))
J
Jon Leighton 已提交
77
    end
78

79
    def self.define_extensions(model, name)
80 81
    end

82 83
    def self.define_callbacks(model, reflection)
      add_before_destroy_callbacks(model, reflection) if reflection.options[:dependent]
84
      Association.extensions.each do |extension|
85
        extension.build model, reflection
86 87 88
      end
    end

89 90 91 92
    # Defines the setter and getter methods for the association
    # class Post < ActiveRecord::Base
    #   has_many :comments
    # end
93
    #
94
    # Post.first.comments and Post.first.comments= methods are defined by this method...
95
    def self.define_accessors(model, reflection)
96
      mixin = model.generated_association_methods
97
      name = reflection.name
98 99
      define_readers(mixin, name)
      define_writers(mixin, name)
J
Jon Leighton 已提交
100
    end
101

102
    def self.define_readers(mixin, name)
103 104 105 106 107
      mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1
        def #{name}(*args)
          association(:#{name}).reader(*args)
        end
      CODE
J
Jon Leighton 已提交
108
    end
109

110
    def self.define_writers(mixin, name)
111 112 113 114 115
      mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1
        def #{name}=(value)
          association(:#{name}).writer(value)
        end
      CODE
J
Jon Leighton 已提交
116
    end
117

118
    def self.valid_dependent_options
119 120 121
      raise NotImplementedError
    end

122 123 124
    def self.add_before_destroy_callbacks(model, reflection)
      unless valid_dependent_options.include? reflection.options[:dependent]
        raise ArgumentError, "The :dependent option must be one of #{valid_dependent_options}, but is :#{reflection.options[:dependent]}"
125
      end
126

127
      name = reflection.name
128
      model.before_destroy lambda { |o| o.association(name).handle_dependency }
J
Jon Leighton 已提交
129
    end
130
  end
131
end