Shoulda test macro for acts_as_state_machine

In this post I will talk about shoulda macro for state_machine to facilitate TDD. So lets get familiar with these at first (want to skip the intro! ok, just jump to code ;)

Acts_as_state_machine:

A really powerful plugin to incorporate state machine in rails application. In scrumpad we are using this plugin and it made the implementation such a fun that everyone here just love to take this work 8). You can take a look to see how this plugin makes everything for you in a blink of eye.

Shoulda:

"The Shoulda gem makes it easy to write elegant, understandable, and maintainable Ruby tests"-Thoughtbot Community. Yes, shoulda really makes your test codes speak. You will be able to define your every test cases exactly in the way that you would probably state to someone.

TDD:

Firstly programming without test code, impossible!! And, if your application is written in any dynamic language(e.g: ruby, php) then TDD is the must. In TDD practice at first you will define and write test codes for all your scenarios and definitely all these codes will fail. Then you will implement functionalities and make this test codes passed one by one.

Get bored?? Dont worry! 

Here is the cake with a cherry on top 8D

For example you have a Post model with three states - draft, published and archived. And you want your post model to move in following way:

ActionTransitions
post.publish:draft => :published
post.archive:draft => :archived or
:published => :archieved

For now, lets work with above simplified transitions. So, lets move ahead to right the test codes first. Check following style... 

class PostTest << ActionController::TestCase
    should_acts_as_state_machine(:initial => :draft) do |config|
        config[:states] = [:draft, :published, :archived]
        config[:events] = {
            :publish => {:draft => :published},
            :archive => {:draft => :archived, :published => :archived}
        }
    end
end

Yes, you are done with your test code. Now write your Post model and let it be acting as a state machine. The test code will assist you to make sure that your Post model have right number of states. And it moves between the states for right transitions. But before that.... do a little favor... copy the following code to the test_helper.rb

ActiveSupport::TestCase.class_eval do
    class << self
        def should_acts_as_state_machine(options)
            options.reverse_merge!( :class => self.name.gsub('Test','').constantize )
            expected_config = {:initial => options[:initial]}
            yield expected_config
            model_attributes = options[:class].inheritable_attributes
            context 'State machine' do
                should 'set initial' do
                    assert_equal(model_attributes[:initial_state], options[:initial], 'Initial state doesnt match')
                end
                should 'have identical states' do
                    assert_equal(expected_config[:states].size, model_attributes[:states].size, "Invalid number of states")
                    expected_config[:states].each do |state|
                        assert(model_attributes[:states].include?(state), "'#{state}' state not found")
                    end
                end

                should 'have identical transitions' do
                    assert_equal(expected_config[:events].size, model_attributes[:transition_table].size, 'Invalide numbers of events found')
                end

                should 'have events with appropriate transitions' do
                    expected_config[:events].each_key do |event|
                        expected_events = expected_config[:events][event]
                        actual_events  = model_attributes[:transition_table][event]
                        assert_equal(expected_events.size, actual_events.size, "<#{event}> has different number of transitions")
                        expected_events.each_pair do |expected_from, expected_to|
                            detected = actual_events.detect{|transition| transition.from == expected_from and transition.to == expected_to }
                            assert_not_nil(detected, "From states are different for <#{event}> event")
                        end
                    end
                end
            end
        end
    end
end

This is the should_acts_as_state_machine method that you called in your test code.
Just another note. This method assumes that you named your test class by postfixed the "Test" word to your model class name( PostTest for Post model). However if you are not doing in this way, just pass your class name (e,g. MyPost) like following

should_acts_as_state_machine(:initial => :draft, :class => MyPost) do |config|
        # set your configurations
end
So, what are you waiting for? Just give it a try and let me know your feedback :)

Comments

  1. Like this post and I suggest you commit it to the shoulda project if possible. If thats a problem, you can create a plugin and commit to github so that more people gets to know about this.

    ReplyDelete
  2. Thanks a lot vaia. I thought about create a plugin but then thought to make some more similar helpers and put all in a plugin. Anyway now i think its better to start with it. Thank you again.

    ReplyDelete
  3. Your post has been linked at www.DrinkRails.com

    ReplyDelete

Post a Comment

Popular posts from this blog

Ajax form submission with tiny MCE editor

Samsung BADA, Starting with sample application

Bada tutorial: Application basics