Skip to content

Instantly share code, notes, and snippets.

@diegodurs
Created June 25, 2012 17:30
Show Gist options
  • Save diegodurs/2990039 to your computer and use it in GitHub Desktop.
Save diegodurs/2990039 to your computer and use it in GitHub Desktop.
Ruby stateable lib for ActiveRecord class
# Author: Diego d'Ursel
# ---------------------
#
# This simple lib help you to add state to a model without implementing boring functions for each state
# just add macro like "add_state 1, 'mystate'" to the definition of your ActiveRecord model
# this lib require a "state" field in the model
#
# some example of usage:
# ---------------------
# add_state 0, 'inactive'
# add_state 1, 'active'
# add_state 3, 'deleted'
#
# instance.state_inactive? => true/false
# instance.state_active! => update_attribute(...)
# instance.state_active => 1
#
# instance.state => 3
# instance.state_to_s => 'deleted'
# Model.state_active.all
module Stateable
def self.included(receiver)
receiver.class_eval do |klass|
# init variable
@states = {}
class << self
attr_accessor :states
end
# instance method to get the name of the state
define_method :state_to_s do
self.class.states[self.state]
end
# class method to add a state
def self.add_state(num, str)
states[num] = str
# add my_state?
define_method "state_#{str}?" do
self.state == self.class.states.key(str)
end
# add modifier my_state!
define_method "state_#{str}!" do
self.update_attribute(:state, self.class.states.key(str))
end
# return the number behind the string
# this could be moved to a class method instead of instance
# define_method "state_#{str}" do
# states.key(str)
# end
# add scope state_mystate
self.scope "state_#{str}".to_sym, where("#{self.table_name}.state = ?", self.states.key(str))
end
end
end
end
require 'spec_helper'
require 'stateable'
describe Stateable do
before(:each) do
ModelA.add_state 1, :active
ModelA.add_state 2, :inactive
ModelB.add_state 1, :inactive
ModelB.add_state 2, :active
end
# test basic instance method
context 'instance model A with state 1 which should be active' do
before(:all) do
@instance = FactoryGirl.create(:model_a)
@instance.state = 1
end
subject{ @instance }
it{ should respond_to :state_active? }
it{ should respond_to :state_active! }
# it{ should respond_to :state_active }
its(:state_active?){ should be_true }
its(:state){ should == 1 }
it{ should respond_to :state_to_s }
its(:state_to_s){ should == :active }
context 'after calling inactive!' do
before(:each) do
@instance.state_inactive!
end
subject{ @instance }
its(:state){ should == 2}
end
end
# test the scopes
context 'class methods' do
subject{ ModelA }
it { should respond_to('state_active') }
it { should respond_to('state_inactive') }
its(:states){ should include({1 => :active})}
its(:states){ should include({2 => :inactive})}
end
# test that every model has his own states
context 'instance model B with state 1 which should be inactive' do
before(:all) do
@instance = FactoryGirl.create(:model_b)
@instance.state = 1
end
subject{ @instance }
its(:state_to_s){ should == :inactive }
end
end
@gregory
Copy link

gregory commented Jun 25, 2012

we can't push on your branch ... but i would do it like this =>

module Stateable
extend ActiveSupport::Concern
module ClassMethods
Proc.new do
states = {}

self.class.class_eval do |klass|
  define_method :add_state do |num, str|
    states.reverse_merge! Hash[num, str]

    self.scope :"with_state_#{str}", where("#{klass.table_name}.state = ?", states[str])

    define_method :"#{str}?" do
      self.state == states[str]
    end

    define_method :"#{str}!" do
      self.state = states[str]
    end

    define_method :"#{str}" do
      states[str]
    end
  end

  define_method :to_s do
    "#{states[self.state]}"
  end
end

end
end
end

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment