module SimonSays::Roleable::ClassMethods

Public Instance Methods

has_roles(*roles) click to toggle source

Provides a declarative method to introduce role based access controller through a give integer mask.

By default it'll use an attributed named role_mask. You can use the :as option to change the prefix for the _mask attribute. This will also alter the names of the dynamically generated methods.

Several methods are dynamically genreated when calling has_roles. The methods generated include a setter, a getter and a predicate method

@param [Array<Symbol, String>] roles array of role symbols or strings @param [Hash] opts options hash @param opts [Symbol] :as alternative prefix name instead of “role”

@example Detailed example:

class User < ActiveRecord::Base
  include SimonSays::Roleable

  has_roles :read, :write, :delete
end

class Editor < ActiveRecord::Base
  include SimonSays::Roleable

  has_roles :create, :update, :publish, as: :access
end

User.new.roles
=> []

User.new(roles: :read).roles
=> [:read]

User.new.tap { |u| u.roles = :write, :read }.roles
=> [:read, :write]

User.new(roles: [:read, :write]).has_roles? :read, :write
=> true

User.new(roles: :read).has_role? :read
=> true

Editor.new(access: %w[create update publish]).access
=> [:create, :update, :publish]

Editor.new(access: :publish).has_access? :create
=> false
# File lib/simon_says/roleable.rb, line 56
      def has_roles *roles
        options = roles.extract_options!

        name = (options[:as] || :roles).to_s
        singular = name.singularize
        const = name.upcase

        roles.map!(&:to_sym)

        class_eval <<-RUBY_EVAL, __FILE__, __LINE__
          #{const} = %i[#{roles * ' '}]

          def #{name}=(args)
            args = [args] unless Array === args

            args.compact!
            args.map!(&:to_sym)

            self[:#{name}_mask] = (args & #{const}).map { |i| 2 ** #{const}.index(i) }.sum
          end

          def #{name}
            #{const}.reject { |i| ((#{name}_mask || 0) & 2 ** #{const}.index(i)).zero? }
          end

          def has_#{name}?(*args)
            (#{name} & args).size > 0
          end

          def self.role_attribute_name
            :#{name}
          end
        RUBY_EVAL

        if name != singular
          class_eval <<-RUBY_EVAL
            alias has_#{singular}? has_#{name}?
          RUBY_EVAL
        end

        # Declare a scope for finding records with a given role set
        # TODO support an array roles (must match ALL)
        scope "with_#{name}", ->(role) {
          where("(#{name}_mask & ?) > 0", 2**roles.index(role.to_sym))
        }
      end