module Heimdallr
  # Heimdallr is attached to your models by including the module and defining the
  # restrictions in your classes.
  #
  #     class Article < ActiveRecord::Base
  #       include Heimdallr::Model
  #
  #       restrict do |context|
  #         # ...
  #       end
  #     end
  #
  # {Heimdallr::Model} should be included prior to any other modules, as it may omit
  # some named scopes defined by those if it is not.
  #
  # @todo Improve description
  module Model
    extend ActiveSupport::Concern

    included do
      class_attribute :heimdallr_restrictions, :instance_writer => false
    end

    # Class methods for {Heimdallr::Model}. See also +ActiveSupport::Concern+.
    module ClassMethods

      # @overload restrict
      #   Define restrictions for a model with a DSL. See {Model} overview
      #   for DSL documentation.
      #
      #   @yield A passed block is executed in the context of a new {Evaluator}.
      #
      # @overload restrict(context, action=:view)
      #   Return a secure collection object for the current scope.
      #
      #   @param [Object] context security context
      #   @param [Symbol] action  kind of actions which will be performed
      #   @return [Proxy::Collection]
      def restrict(context=nil, options={}, &block)
        if block
          self.heimdallr_restrictions = Evaluator.new(self, block)
        else
          Proxy::Collection.new(context, restrictions(context).request_scope(:fetch, self), options)
        end
      end

      # Evaluate the restrictions for a given +context+ and +record+.
      #
      # @return [Evaluator]
      def restrictions(context, record=nil)
        heimdallr_restrictions.evaluate(context, record)
      end

      # @api private
      #
      # An internal attribute to store the list of user-defined name scopes.
      # It is required because ActiveRecord does not provide any introspection for
      # named scopes.
      attr_accessor :heimdallr_scopes

      # An interceptor for named scopes which adds them to {#heimdallr_scopes} list.
      def scope(name, *args)
        self.heimdallr_scopes ||= []
        self.heimdallr_scopes.push name

        super
      end

      # @api private
      #
      # An internal attribute to store the list of user-defined relation-like methods
      # which return ActiveRecord family objects and can be automatically restricted.
      attr_accessor :heimdallr_relations

      # A DSL method for defining relation-like methods.
      def heimdallr_relation(*methods)
        self.heimdallr_relations ||= []
        self.heimdallr_relations  += methods.map(&:to_sym)
      end

      # Builds the Proxy class that should be used to wrap this model
      def heimdallr_proxy_class
        unless @heimdallr_proxy_class
          record = self

          @heimdallr_proxy_class = Class.new(Proxy::Record) do
            define_singleton_method :model_name do
              record.model_name
            end
          end
        end

        @heimdallr_proxy_class
      end
    end

    # Return a secure proxy object for this record.
    #
    # @return [Record::Proxy]
    def restrict(context, options={})
      self.class.heimdallr_proxy_class.new(context, self, options)
    end

    # @api private
    #
    # An internal attribute to store the Heimdallr security validators for
    # the context in which this object is currently being saved.
    attr_accessor :heimdallr_validators

    # @api private
    #
    # An internal method to run Heimdallr security validators, when applicable.
    def heimdallr_validations
      validates_with Heimdallr::Validator
    end

    def self.included(klass)
      klass.class_eval do
        validate :heimdallr_validations
      end
    end
  end
end