require 'dm-core'
require 'active_support/core_ext/class/attribute'
require 'active_support/concern'
require 'active_model'

if defined?(::ActiveModel::MassAssignmentSecurity)
  module ActiveModel
    module MassAssignmentSecurity
      # Provides a patched version of the Sanitizer used in Rails to handle property
      # and relationship objects as keys. There is no way to inject a custom sanitizer
      # without reimplementing the permission sets.
      Sanitizer.send(Sanitizer.is_a?(Module) ? :module_eval : :class_eval) do
        # Returns all attributes not denied by the authorizer.
        #
        # @param [Hash{Symbol,String,::DataMapper::Property,::DataMapper::Relationship=>Object}] attributes
        #   Names and values of attributes to sanitize.
        # @return [Hash]
        #   Sanitized hash of attributes.
        def sanitize(attributes, authorizer = nil)
          sanitized_attributes = attributes.reject do |key, value|
            key_name = key.name rescue key
            authorizer ? authorizer.deny?(key_name) : deny?(key_name)
          end
          debug_protected_attribute_removal(attributes, sanitized_attributes)
          sanitized_attributes
        end
      end
    end
  end

  module DataMapper
    # Include this module into a DataMapper model to enable ActiveModel's mass
    # assignment security.
    #
    # To use second parameter of {#attributes=} make sure to include this module
    # last.
    module MassAssignmentSecurity
      extend ::ActiveSupport::Concern

      include ::ActiveModel::MassAssignmentSecurity

      module ClassMethods
        extend ::ActiveModel::MassAssignmentSecurity::ClassMethods

        def logger
          @logger ||= ::DataMapper.logger
        end
      end

      # Sanitizes the specified +attributes+ according to the defined mass-assignment
      # security rules and calls +super+ with the result.
      #
      # Use either +attr_accessible+ to specify which attributes are allowed to be
      # assigned via {#attributes=}, or +attr_protected+ to specify which attributes
      # are *not* allowed to be assigned via {#attributes=}.
      #
      # +attr_accessible+ and +attr_protected+ are mutually exclusive.
      #
      # @param [Hash{Symbol,String,::DataMapper::Property,::DataMapper::Relationship=>Object}] attributes
      #   Names and values of attributes to sanitize.
      # @param [Boolean] guard_protected_attributes
      #   Determines whether mass-security rules are applied (when +true+) or not.
      # @return [Hash]
      #   Sanitized hash of attributes.
      # @api public
      #
      # @example [Usage]
      #   class User
      #     include DataMapper::Resource
      #     include DataMapper::MassAssignmentSecurity
      #
      #     property :name, String
      #     property :is_admin, Boolean
      #
      #     # Only allow name to be set via #attributes=
      #     attr_accessible :name
      #   end
      #
      #   user = User.new
      #   user.attributes = { :username => 'Phusion', :is_admin => true }
      #   user.username  # => "Phusion"
      #   user.is_admin  # => false
      #
      #   user.send(:attributes=, { :username => 'Phusion', :is_admin => true }, false)
      #   user.is_admin  # => true
      def attributes=(attributes, guard_protected_attributes = true)
        attributes = sanitize_for_mass_assignment(attributes) if guard_protected_attributes
        super(attributes)
      end
    end
  end
end
