Simplifying BDD on has_many associations
My first Ruby on Rails post on this blog..
– another advantage of Fat Models Thin Controllers.
Consider
class Box < ActiveRecord::Base
has_many :marbles
end
class BoxesController < ActionController::Base
def show
@box = Box.find(params[:box_id])
@box.marbles.find(params[:id)])
end
end
To test this using rspec, you write something like
describe BoxesController, "handling GET /boxes/1/marbles/2"
def do_get
get :show, :box_id => 1, :id => 2
end
it "should get the right marble" do
@marble = mock_model(Marble)
@marbles = mock_model(Array)
@marbles.should_receive(:find).with("2").and_return(@marble)
@box = mock_model(Box, :marbles => @marbles)
Box.should_receive(:find).with("1").and_return(@box)
do_get
end
end
I think the @marbles part in the controller specs is misplaced and ugly. This behavior should be pushed into the model specs. So lets refactor by combining the collection (@marbles) and find method into find_marble:
class Box < ActiveRecord::Base
has_many :marbles
def find_marble(id)
marbles.find(id)
end
end
class BoxesController < ActionController::Base
def show
@box = Box.find(params[:box_id])
@box.find_marble(params[:id)]
end
end
This makes the controller specs:
describe BoxesController, "handling GET /boxes/1/marbles/2"
def do_get
get :show, :box_id => 1, :id => 2
end
it "should get the right marble" do
@marble = mock_model(Marble)
@box = mock_model(Box)
@box.should_receive(:find_marble).with("2").and_return(@marble)
Box.should_receive(:find).with("1").and_return(@box)
do_get
end
end
and model specs code:
describe Box do
it "should find_marble" do
@marble = mock_model(Marble)
@marbles = mock_model(Array)
@marbles.should_receive(:find).with("1").and_return(@marble)
@box = mock_model(Box, :marbles => @marbles)
@box.find_marble("1").should == @marble
end
end
I feel that this is much cleaner. What do you think?