Created
September 15, 2011 16:09
-
-
Save the-architect/1219672 to your computer and use it in GitHub Desktop.
Automatically create scopes for all available state_machine states in Rails 3.1
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class Contact < ActiveRecord::Base | |
state_machine :state, :initial => :imported do | |
state :imported | |
state :contacted | |
state :verified | |
state :prospect | |
state :deleted | |
end | |
include StateMachineScopes | |
state_machine_scopes :state | |
end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
module StateMachineScopes | |
class MethodAlreadyDefinedError < StandardError; end | |
class StateMachineNotFoundError < StandardError; end | |
class NoStateMachineNotFoundError < StandardError; end | |
module ClassMethods | |
def all_states(machine = :state) | |
machine = machine.to_sym | |
if state_machines.key?(machine) | |
state_machines[machine.to_sym].states.keys.compact.sort | |
else | |
raise StateMachineNotFoundError.new(%Q{"#{self.name}" does not seem to have a state machine called "#{machine}".}) | |
end | |
end | |
def state_machine_scopes(*which) | |
options = which.last.is_a?(Hash) ? which.pop : {} | |
prefix = options.delete(:prefix) | |
if which.any? | |
which.each do |machine_name| | |
define_scopes_for(machine_name, prefix) | |
end | |
else | |
state_machines.keys.each do |machine_name| | |
define_scopes_for(machine_name, prefix) | |
end | |
end | |
end | |
private | |
def define_scopes_for(machine_name, prefix) | |
state_machines[machine_name.to_sym].states.keys.compact.each do |state| | |
scope_name = [prefix, machine_name, state].compact.join('_') | |
debugger if scope_name.to_s == 'state' | |
if self.respond_to? scope_name | |
raise MethodAlreadyDefinedError.new("Already defined method called \"#{scope_name}\"") | |
end | |
self.class_eval do | |
scope :"#{scope_name}", where(:state => state) | |
end | |
end | |
end | |
end | |
def self.included(klass) | |
if klass.respond_to?(:state_machines) | |
klass.extend(ClassMethods) | |
else | |
raise NoStateMachineNotFoundError.new(%Q{"#{klass.name}" does not seem to have any state machines.}) | |
end | |
end | |
end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
require 'spec_helper' | |
describe StateMachineScopes do | |
class TestBase | |
class << self | |
def where(*args); end | |
def scope(name, *args) | |
scopes << name | |
end | |
def scopes | |
@scopes ||= [] | |
@scopes | |
end | |
end | |
end | |
before :each do | |
class ClassWithStateMachine < TestBase | |
state_machine do | |
state :initialized | |
state :ready | |
end | |
end | |
class ClassWithNamedStateMachine < TestBase | |
state_machine :mystatus do | |
state :initialized | |
state :ready | |
end | |
end | |
class ClassWithMultipleStateMachine < TestBase | |
state_machine :hello do | |
state :world | |
state :endofworld | |
end | |
state_machine :mystatus do | |
state :initialized | |
state :ready | |
end | |
end | |
end | |
after :each do | |
Object.send(:remove_const, :ClassWithStateMachine) | |
Object.send(:remove_const, :ClassWithNamedStateMachine) | |
Object.send(:remove_const, :ClassWithMultipleStateMachine) | |
end | |
context "inclusion" do | |
it "throws error if no state machines are found" do | |
expect do | |
Class.new.send :include, StateMachineScopes | |
end.to raise_error(StateMachineScopes::NoStateMachineNotFoundError) | |
end | |
class ClassWithStateMachineMethod | |
class << self | |
def state_machines; end | |
end | |
end | |
it "should have state_machine_scope method" do | |
ClassWithStateMachineMethod.send :include, StateMachineScopes | |
ClassWithStateMachineMethod.should respond_to(:state_machine_scopes) | |
end | |
end | |
context "build state machine scopes" do | |
context "all states" do | |
it "finds all states for default state machine name" do | |
ClassWithStateMachine.send :include, StateMachineScopes | |
ClassWithStateMachine.all_states.should eql [:initialized, :ready] | |
end | |
it "finds all states for specific state machine name" do | |
ClassWithMultipleStateMachine.send :include, StateMachineScopes | |
ClassWithMultipleStateMachine.all_states(:hello).should eql [:endofworld, :world] | |
end | |
it "throws error if state machine is not defined" do | |
ClassWithMultipleStateMachine.send :include, StateMachineScopes | |
expect do | |
ClassWithMultipleStateMachine.all_states(:dostuff) | |
end.to raise_error(StateMachineScopes::StateMachineNotFoundError) | |
end | |
end | |
context "without arguments" do | |
it "for default state machine name" do | |
ClassWithStateMachine.send :include, StateMachineScopes | |
ClassWithStateMachine.state_machine_scopes | |
ClassWithStateMachine.scopes.should eql [:state_initialized, :state_ready] | |
end | |
it "for named state machine" do | |
ClassWithNamedStateMachine.send :include, StateMachineScopes | |
ClassWithNamedStateMachine.state_machine_scopes | |
ClassWithNamedStateMachine.scopes.should eql [:mystatus_initialized, :mystatus_ready] | |
end | |
end | |
context "with arguments" do | |
it "only specified state machines" do | |
ClassWithMultipleStateMachine.send :include, StateMachineScopes | |
ClassWithMultipleStateMachine.state_machine_scopes(:hello) | |
ClassWithMultipleStateMachine.scopes.should eql [:hello_world, :hello_endofworld] | |
end | |
it "adds prefix" do | |
ClassWithMultipleStateMachine.send :include, StateMachineScopes | |
ClassWithMultipleStateMachine.state_machine_scopes(:prefix => :test) | |
ClassWithMultipleStateMachine.scopes.should eql [:test_hello_world, :test_hello_endofworld, :test_mystatus_initialized, :test_mystatus_ready] | |
end | |
it "ignored other options" do | |
ClassWithMultipleStateMachine.send :include, StateMachineScopes | |
ClassWithMultipleStateMachine.state_machine_scopes(:foo => :bar) | |
ClassWithMultipleStateMachine.scopes.should eql [:hello_world, :hello_endofworld, :mystatus_initialized, :mystatus_ready] | |
end | |
it "adds prefix and creates only for specified state machines" do | |
ClassWithMultipleStateMachine.send :include, StateMachineScopes | |
ClassWithMultipleStateMachine.state_machine_scopes(:hello, :prefix => 'test') | |
ClassWithMultipleStateMachine.scopes.should eql [:test_hello_world, :test_hello_endofworld] | |
end | |
end | |
end | |
end |
So great! Thanks for sharing. Looks like on line 41:
#state_machine_scopes.rb
scope :"#{scope_name}", where(:state => state)
Should be:
scope :"#{scope_name}", where(machine_name => state)
(My machine uses :status)
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
glad you found use for it :)