Skip to content

Commit

Permalink
Support for configurable column names
Browse files Browse the repository at this point in the history
  • Loading branch information
cmw authored and troessner committed Feb 6, 2013
1 parent b2e64fe commit 1304a15
Show file tree
Hide file tree
Showing 3 changed files with 209 additions and 8 deletions.
14 changes: 14 additions & 0 deletions README.rdoc
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,20 @@ You can easily get a listing of all available states:
state :closed
end

=== Configuring a different column name with ActiveRecord

To use a different column than <tt>state</tt> to track it's value simply do this:

class Product < ActiveRecord::Base
include Transitions

state_machine :attribute_name => :different_column do

...

end
end

=== Documentation, Guides & Examples

- {Online API Documentation}[http://rdoc.info/github/troessner/transitions/master/Transitions]
Expand Down
47 changes: 39 additions & 8 deletions lib/active_model/transitions.rb
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,31 @@ module Transitions
extend ActiveSupport::Concern

included do
class ::Transitions::Machine
unless instance_methods.include?(:new_initialize) || instance_methods.include?(:new_update)
attr_reader :attribute_name
alias :old_initialize :initialize
alias :old_update :update

def new_initialize(*args, &block)
@attribute_name = :state
old_initialize(*args, &block)
end

def new_update(options = {}, &block)
@attribute_name = options[:attribute_name] if options.key?(:attribute_name)
old_update(options, &block)
end

alias :initialize :new_initialize
alias :update :new_update
else
puts "WARNING: Transitions::Machine#new_update or Transitions::Machine#new_initialize already defined. This can possibly break ActiveModel::Transitions."
end
end
include ::Transitions
after_initialize :set_initial_state
validates_presence_of :state
validate :state_presence
validate :state_inclusion
end

Expand All @@ -43,6 +65,10 @@ def reload(options = nil)

protected

def transitions_state_column_name
self.class.state_machine.attribute_name
end

def write_state(state)
prev_state = current_state
write_state_without_persistence(state)
Expand All @@ -55,22 +81,27 @@ def write_state(state)
def write_state_without_persistence(state)
ivar = self.class.get_state_machine.current_state_variable
instance_variable_set(ivar, state)
self.state = state.to_s
self[transitions_state_column_name] = state.to_s
end

def read_state
self.state && self.state.to_sym
self[transitions_state_column_name] && self[transitions_state_column_name].to_sym
end

def set_initial_state
self.state ||= self.class.get_state_machine.initial_state.to_s if self.respond_to?(:state=)
self[transitions_state_column_name] ||= self.class.get_state_machine.initial_state.to_s if self.respond_to?(transitions_state_column_name)
end

def state_presence
unless self[transitions_state_column_name].present?
self.errors.add(transitions_state_column_name, :presence)
end
end

def state_inclusion
unless self.class.get_state_machine.states.map{|s| s.name.to_s }.include?(self.state.to_s)
self.errors.add(:state, :inclusion, :value => self.state)
unless self.class.get_state_machine.states.map{|s| s.name.to_s }.include?(self[transitions_state_column_name].to_s)
self.errors.add(transitions_state_column_name, :inclusion, :value => self[transitions_state_column_name])
end
end
end
end

end
156 changes: 156 additions & 0 deletions test/active_record/test_active_record.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,17 @@ def self.up

set_up_db CreateTrafficLights

class CreateDifferentTrafficLights < ActiveRecord::Migration
def self.up
create_table(:different_traffic_lights) do |t|
t.string :different_state
t.string :name
end
end
end

set_up_db CreateDifferentTrafficLights

class TrafficLight < ActiveRecord::Base
include ActiveModel::Transitions

Expand Down Expand Up @@ -147,3 +158,148 @@ def setup
assert_equal "red", @light.reload.state
end
end

class TestNewActiveRecord < TestActiveRecord

def setup
set_up_db CreateTrafficLights
@light = TrafficLight.new
end

test "new active records defaults current state to the initial state" do
assert_equal :off, @light.current_state
end

end

class TestScopes < Test::Unit::TestCase
test "scope returns correct object" do
@light = TrafficLight.create!
assert_respond_to TrafficLight, :off
assert_equal TrafficLight.off.first, @light
assert TrafficLight.red.empty?
end

test "scopes exist" do
assert_respond_to TrafficLight, :off
assert_respond_to TrafficLight, :red
assert_respond_to TrafficLight, :green
assert_respond_to TrafficLight, :yellow
end

test 'scopes are only generated if we explicitly say so' do
assert_not_respond_to LightBulb, :off
assert_not_respond_to LightBulb, :on
end

test 'scope generation raises an exception if we try to overwrite an existing method' do
assert_raise(Transitions::InvalidMethodOverride) {
class Light < ActiveRecord::Base
include ActiveModel::Transitions

state_machine :auto_scopes => true do
state :new
state :broken
end
end
}
end
end

class DifferentTrafficLight < ActiveRecord::Base
include ActiveModel::Transitions

state_machine :attribute_name => :different_state, :auto_scopes => true do
state :off

state :red
state :green
state :yellow

event :red_on do
transitions :to => :red, :from => [:yellow]
end

event :green_on do
transitions :to => :green, :from => [:red]
end

event :yellow_on do
transitions :to => :yellow, :from => [:green]
end

event :reset do
transitions :to => :red, :from => [:off]
end
end
end

class TestActiveRecordWithDifferentColumnName < Test::Unit::TestCase
def setup
set_up_db CreateDifferentTrafficLights
@light = DifferentTrafficLight.create!
end

test "new record has the initial state set" do
@light = DifferentTrafficLight.new
assert_equal "off", @light.different_state
end

test "states initial state" do
assert @light.off?
assert_equal :off, @light.current_state
end

test "transition to a valid state" do
@light.reset
assert @light.red?
assert_equal :red, @light.current_state

@light.green_on
assert @light.green?
assert_equal :green, @light.current_state
end

test "transition does not persist state" do
@light.reset
assert_equal :red, @light.current_state
@light.reload
assert_equal "off", @light.different_state
end

test "transition does persists state" do
@light.reset!
assert_equal :red, @light.current_state
@light.reload
assert_equal "red", @light.different_state
end

test "transition to an invalid state" do
assert_raise(Transitions::InvalidTransition) { @light.yellow_on }
assert_equal :off, @light.current_state
end

test "transition with wrong state will not validate" do
for s in @light.class.state_machine.states
@light.different_state = s.name
assert @light.valid?
end
@light.different_state = "invalid_one"
assert_false @light.valid?
end

test "reloading model resets current state" do
@light.reset
assert @light.red?
@light.update_attribute(:different_state, 'green')
assert @light.reload.green?, "reloaded state should come from database, not instance variable"
end

test "calling non-bang event updates state attribute" do
@light.reset!
assert @light.red?
@light.green_on
assert_equal "green", @light.different_state
assert_equal "red", @light.reload.different_state
end
end

0 comments on commit 1304a15

Please # to comment.