association.rb 3.4 KB
Newer Older
1 2
module ActiveRecord::Associations::Builder
  class Association #:nodoc:
3 4 5
    class << self
      attr_accessor :valid_options
    end
6

7
    self.valid_options = [:class_name, :foreign_key, :validate]
8

9
    attr_reader :model, :name, :scope, :options, :reflection
10

11 12
    def self.build(*args, &block)
      new(*args, &block).build
13 14
    end

15 16 17 18
    def initialize(model, name, scope, options)
      @model   = model
      @name    = name

J
Jon Leighton 已提交
19
      if scope.is_a?(Hash)
20 21
        @scope   = nil
        @options = scope
J
Jon Leighton 已提交
22 23 24
      else
        @scope   = scope
        @options = options
25
      end
J
Jon Leighton 已提交
26 27 28 29 30

      if @scope && @scope.arity == 0
        prev_scope = @scope
        @scope = proc { instance_exec(&prev_scope) }
      end
31 32 33 34
    end

    def mixin
      @model.generated_feature_methods
35 36
    end

37 38
    include Module.new { def build; end }

39 40 41
    def build
      validate_options
      define_accessors
42 43 44 45 46 47 48 49 50 51 52
      @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
53 54 55 56 57
    end

    private

      def validate_options
58
        options.assert_valid_keys(valid_options)
59 60 61 62 63 64 65 66 67
      end

      def define_accessors
        define_readers
        define_writers
      end

      def define_readers
        name = self.name
J
Josh Susser 已提交
68
        mixin.redefine_method(name) do |*params|
69 70 71 72 73 74
          association(name).reader(*params)
        end
      end

      def define_writers
        name = self.name
J
Josh Susser 已提交
75
        mixin.redefine_method("#{name}=") do |value|
76 77 78
          association(name).writer(value)
        end
      end
79

80 81 82 83
      def check_valid_dependent!(dependent, valid_options)
        unless valid_options.include?(dependent)
          valid_options_message = valid_options.map(&:inspect).to_sentence(
            words_connector: ', ', two_words_connector: ' or ', last_word_connector: ' or ')
84

85 86 87
          raise ArgumentError, "The :dependent option expects either " \
            "#{valid_options_message} (#{dependent.inspect})"
        end
88 89
      end

90 91 92 93 94 95
      def dependent_restrict_raises?
        ActiveRecord::Base.dependent_restrict_raises == true
      end

      def dependent_restrict_deprecation_warning
        if dependent_restrict_raises?
96 97 98
          msg = "In the next release, `:dependent => :restrict` will not raise a `DeleteRestrictionError`. "\
                "Instead, it will add an error on the model. To fix this warning, make sure your code " \
                "isn't relying on a `DeleteRestrictionError` and then add " \
99 100 101 102
                "`config.active_record.dependent_restrict_raises = false` to your application config."
          ActiveSupport::Deprecation.warn msg
        end
      end
103 104 105 106

      def define_restrict_dependency_method
        name = self.name
        mixin.redefine_method(dependency_method_name) do
107 108
          has_one_macro = association(name).reflection.macro == :has_one
          if has_one_macro ? !send(name).nil? : send(name).exists?
109 110 111
            if dependent_restrict_raises?
              raise ActiveRecord::DeleteRestrictionError.new(name)
            else
112
              key  = has_one_macro ? "one" : "many"
113 114
              errors.add(:base, :"restrict_dependent_destroy.#{key}",
                         :record => self.class.human_attribute_name(name).downcase)
115 116 117 118 119
              return false
            end
          end
        end
      end
120
  end
121
end