guise

Build Status Code Climate

An alternative to storing role resources in the database.

guise uses a has_many association to store type information instead of using has_many :through or has_and_belongs_to_many. The has_many association stores the role or type information as a string representing the class name.

If effect, guise enables 'multi-table-inheritance'.

Installation

Add this line to your application's Gemfile:

gem 'guise'

And then execute:

$ bundle

Or install it yourself as:

$ gem install guise

Usage

Create a table to store your type information:

rails generate model user_role user:references title:string
rake db:migrate

Then add has_guises to your model. This will setup the has_many association for you. It requires the name of the association and name of the column that the sublcass name will be stored in.

class User < ActiveRecord::Base
  has_guises :DeskWorker, :MailForwarder, :association => :user_roles, :attribute => :title
end

This adds the following methods to the User class:

  • :desk_workers and :mail_forwarders model scopes.
  • :has_guise? that checks if a user is a particular type.
  • :desk_worker?, :mail_forwarder that proxy to :has_guise?.
  • :has_guises? that checks if a user has records for all the types supplied.
  • :has_any_guises? that checks if a user has records for any of the types supplied.

To configure the other end of the association, add guise_for:

class UserRole < ActiveRecord::Base
  guise_for :User
end

This method does the following:

  • Sets up belongs_to association and accepts the standard options.
  • Validates the column storing the name of the guise in the list supplied is unique to the resource it belongs to and is one of the provided names.

To add a class for each guise, call :guise_of in a subclass:

class DeskWorker < User
  guise_of :User
end

This adds the following to the DeskWorker class:

class DeskWorker < User
  default_scope -> { joins(:user_roles).where(user_roles: { title: 'DeskWorker'}) }

  after_initialize do
    self.guises.build(title: 'DeskWorker')
  end

  after_create do
    self.guises.create(title: 'DeskWorker')
  end
end

To scope the association class to a guise, use scoped_guise_for. The name of the class must be the guise it represents combined with the name of the parent class.

class DeskWorkerUserRole < UserRole
  scoped_guise_for :User
end

This sets up the class as follows:

class DeskWorkerUserRole < UserRole
  default_scope -> { where(title: 'DeskWorker') }

  after_initialize do
    self.title = 'DeskWorker'
  end

  before_create do
    self.title = 'DeskWorker'
  end
end

Customization

If the association doesn't standard association assumptions, you can pass in the options for has_many into has_guises. The same applies to guise_for with the addition that you can specify not to validate attributes.

class Person < ActiveRecord::Base
  has_guises :Admin, :Engineer,
             :association => :positions,
             :attribute => :rank,
             :foreign_key => :employee_id,
             :class_name => :JobTitle
end

class JobTitle < ActiveRecord::Base
  guise_for :Person,
            :foreign_key => :employee_id,
            :validate => false # skip setting up validations
end