aboutsummaryrefslogtreecommitdiffstats
path: root/activemodel/lib/active_model/configuration.rb
blob: ba5a6a2075ad69108a5f503c747ce8e8e1469264 (plain) (blame)
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
133
134
require 'active_support/concern'
require 'active_support/core_ext/class/attribute'
require 'active_support/core_ext/class/attribute_accessors'

module ActiveModel
  # This API is for Rails' internal use and is not currently considered 'public', so
  # it may change in the future without warning.
  #
  # It creates configuration attributes that can be inherited from a module down
  # to a class that includes the module. E.g.
  #
  #   module MyModel
  #     extend ActiveModel::Configuration
  #     config_attribute :awesome
  #     self.awesome = true
  #   end
  #
  #   class Post
  #     include MyModel
  #   end
  #
  #   Post.awesome # => true
  #
  #   Post.awesome = false
  #   Post.awesome    # => false
  #   MyModel.awesome # => true
  #
  # We assume that the module will have a ClassMethods submodule containing methods
  # to be transferred to the including class' singleton class.
  #
  # Config options can also be defined directly on a class:
  #
  #   class Post
  #     extend ActiveModel::Configuration
  #     config_attribute :awesome
  #   end
  #
  # So this allows us to define a module that doesn't care about whether it is being
  # included in a class or a module:
  #
  #   module Awesomeness
  #     extend ActiveSupport::Concern
  #
  #     included do
  #       extend ActiveModel::Configuration
  #       config_attribute :awesome
  #       self.awesome = true
  #     end
  #   end
  #
  #   class Post
  #     include Awesomeness
  #   end
  #
  #   module AwesomeModel
  #     include Awesomeness
  #   end
  module Configuration #:nodoc:
    def config_attribute(name, options = {})
      klass = self.is_a?(Class) ? ClassAttribute : ModuleAttribute
      klass.new(self, name, options).define
    end

    class Attribute
      attr_reader :host, :name, :options

      def initialize(host, name, options)
        @host, @name, @options = host, name, options
      end

      def instance_writer?
        options.fetch(:instance_writer, false)
      end
    end

    class ClassAttribute < Attribute
      def define
        if options[:global]
          host.cattr_accessor name, :instance_writer => instance_writer?
        else
          host.class_attribute name, :instance_writer => instance_writer?
        end
      end
    end

    class ModuleAttribute < Attribute
      def class_methods
        @class_methods ||= begin
          if host.const_defined?(:ClassMethods, false)
            host.const_get(:ClassMethods)
          else
            host.const_set(:ClassMethods, Module.new)
          end
        end
      end

      def define
        host.singleton_class.class_eval <<-CODE, __FILE__, __LINE__ + 1
          attr_accessor :#{name}
          def #{name}?; !!#{name}; end
        CODE

        name, host = self.name, self.host

        class_methods.class_eval do
          define_method(name) { host.send(name) }
          define_method("#{name}?") { !!send(name) }
        end

        host.class_eval <<-CODE, __FILE__, __LINE__ + 1
          def #{name};  defined?(@#{name}) ? @#{name} : self.class.#{name}; end
          def #{name}?; !!#{name}; end
        CODE

        if options[:global]
          class_methods.class_eval do
            define_method("#{name}=") { |val| host.send("#{name}=", val) }
          end
        else
          class_methods.class_eval <<-CODE, __FILE__, __LINE__ + 1
            def #{name}=(val)
              singleton_class.class_eval do
                remove_possible_method(:#{name})
                define_method(:#{name}) { val }
              end
            end
          CODE
        end

        host.send(:attr_writer, name) if instance_writer?
      end
    end
  end
end