aboutsummaryrefslogtreecommitdiffstats
path: root/activesupport/lib/active_support/configurable.rb
blob: 4fb8c7af3f808f75f07649661d440867434e123e (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
135
require 'active_support/concern'
require 'active_support/ordered_options'
require 'active_support/core_ext/array/extract_options'

module ActiveSupport
  # Configurable provides a <tt>config</tt> method to store and retrieve
  # configuration options as an <tt>OrderedHash</tt>.
  module Configurable
    extend ActiveSupport::Concern

    class Configuration < ActiveSupport::InheritableOptions
      def compile_methods!
        self.class.compile_methods!(keys)
      end

      # compiles reader methods so we don't have to go through method_missing
      def self.compile_methods!(keys)
        keys.reject { |m| method_defined?(m) }.each do |key|
          class_eval <<-RUBY, __FILE__, __LINE__ + 1
            def #{key}; _get(#{key.inspect}); end
          RUBY
        end
      end
    end

    module ClassMethods
      def config
        @_config ||= if respond_to?(:superclass) && superclass.respond_to?(:config)
          superclass.config.inheritable_copy
        else
          # create a new "anonymous" class that will host the compiled reader methods
          Class.new(Configuration).new
        end
      end

      def configure
        yield config
      end

      # Allows you to add shortcut so that you don't have to refer to attribute
      # through config. Also look at the example for config to contrast.
      # 
      # Defines both class and instance config accessors.
      #
      #   class User
      #     include ActiveSupport::Configurable
      #     config_accessor :allowed_access
      #   end
      #
      #   User.allowed_access # => nil 
      #   User.allowed_access = false
      #   User.allowed_access # => false   
      # 
      #   user = User.new
      #   user.allowed_access # => false
      #   user.allowed_access = true
      #   user.allowed_access # => true
      #
      #   User.allowed_access # => false  
      #
      # The attribute name must be a valid method name in Ruby.
      #
      #   class User
      #     include ActiveSupport::Configurable
      #     config_accessor :"1_Badname"
      #   end
      #   # => NameError: invalid config attribute name
      #
      # To opt out of the instance writer method, pass <tt>instance_writer: false</tt>.
      # To opt out of the instance reader method, pass <tt>instance_reader: false</tt>.
      #
      #   class User
      #     include ActiveSupport::Configurable
      #     config_accessor :allowed_access, instance_reader: false, instance_writer: false
      #   end
      #
      #   User.allowed_access = false
      #   User.allowed_access # => false
      #
      #   User.new.allowed_access = true # => NoMethodError
      #   User.new.allowed_access        # => NoMethodError
      #
      # Or pass <tt>instance_accessor: false</tt>, to opt out both instance methods.
      #
      #   class User
      #     include ActiveSupport::Configurable
      #     config_accessor :allowed_access, instance_accessor: false
      #   end
      #
      #   User.allowed_access = false
      #   User.allowed_access # => false
      #
      #   User.new.allowed_access = true # => NoMethodError
      #   User.new.allowed_access        # => NoMethodError  
      def config_accessor(*names)
        options = names.extract_options!

        names.each do |name|
          raise NameError.new('invalid config attribute name') unless name =~ /^[_A-Za-z]\w*$/

          reader, line = "def #{name}; config.#{name}; end", __LINE__
          writer, line = "def #{name}=(value); config.#{name} = value; end", __LINE__

          singleton_class.class_eval reader, __FILE__, line
          singleton_class.class_eval writer, __FILE__, line

          unless options[:instance_accessor] == false
            class_eval reader, __FILE__, line unless options[:instance_reader] == false
            class_eval writer, __FILE__, line unless options[:instance_writer] == false
          end
        end
      end
    end

    # Reads and writes attributes from a configuration <tt>OrderedHash</tt>.
    #
    #   require 'active_support/configurable'
    #
    #   class User
    #     include ActiveSupport::Configurable
    #   end
    #
    #   user = User.new
    #
    #   user.config.allowed_access = true
    #   user.config.level = 1
    #
    #   user.config.allowed_access # => true
    #   user.config.level          # => 1
    def config
      @_config ||= self.class.config.inheritable_copy
    end
  end
end