aboutsummaryrefslogblamecommitdiffstats
path: root/activemodel/lib/active_model/configuration.rb
blob: 1757c12ebf417d83c7b8662b719e689e43d61d44 (plain) (tree)





































































































































                                                                                    
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__
          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
          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__
            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