Thursday, June 16, 2011

Factory Girl steps for Cucumber with MongoMapper: what about associations?

Update:

I tried to propagate the fix to Plucky itself, in vain. So far, it's not fixed. Use this patch, if you experience problems migrating your project from ActiveRecord to MongoMapper or MongoMapper problems not properly working with queries, even though the article is about FactoryGirl associations (which might've been changed in FactoryGirl itself by now).

I also came across hidden issues with MongoMapper, such as a method not working, but the test is designed in a way to pass. If it's my decision, I will try MongoId next time.

-------------------------

If you have an association (an Article belonging to a User) and you write something like that in Cucumber:

Given the following article exists:
| title | user |
| My article | email: writer1@example.com |

then the Factory Girl steps will handle this association for you, creating both an Article and a User for your test if they are of the ActiveRecord class. But this will throw an exception with MongoMapper.

Digging into the Factory Girl steps code explains why. Factory Girl steps use User.find(:first, :conditions => attributes) but syntax such as find(:first...) doesn't exist for MongoMapper.

MongoMapper uses Plucky gem for all the find methods. I wrote the following monkey patch to account for the missing attributes:

/config/initializers/plucky_ext.rb
----------------------------------

Plucky::Query.class_eval do
alias_method :find_old, :find
def find_new(*args)
options = args.extract_options!
first = args.shift
args.push(options)

case first
when :first
first(*args)
when :last
last(*args)
when :all
all(*args)
else
args.unshift(first) unless first.nil?
find_old(*args)
end
end
alias_method :find, :find_new
end
Now the Factory Girl steps work with associations

Saturday, June 11, 2011

MongoMapper with rspec/shoulda hack

As far as I know, MongoMapper is currently not able to handle association tests for rspec/shoulda, such as most common belongs_to, has_many, has_one. I think they are working on it, but it's not there yet.

I wrote a hack to work with MongoMapper, so far it's able to handle my simple belongs_to association, and my rspec test passes all right when it has the association and fails otherwise. As I progress through more associations, I will keep posting if it needs improvements.

In the config/initializers folder create a file mongo_mapper_ext.rb and place the following code into it:

module MongoMapperExt
module Plugins
module Associations
module ClassMethods
def reflect_on_association(association)
associations[association].extend AssociationMethods if associations[association]
end
end
module AssociationMethods
def macro
@macro = derive_macro
end
def primary_key_name
@primary_key_name ||= options[:foreign_key] || derive_primary_key_name
end
def belongs_to?
@macro == :belongs_to
end

private
def derive_primary_key_name
if belongs_to?
"#{name}_id"
elsif options[:as]
"#{options[:as]}_id"
else
klass.foreign_key
end
end

def derive_macro
case self.class.name
when "MongoMapper::Plugins::Associations::BelongsToAssociation"
:belongs_to
when "MongoMapper::Plugins::Associations::ManyAssociation"
:has_many
when "MongoMapper::Plugins::Associations::OneAssociation"
:has_one
else
"unknown association"
end
end
end
end
end
end

MongoMapper::Document.send(:include, MongoMapperExt::Plugins::Associations)


Basically, it adds some methods from the ActiveRecord reflections which are missing in MongoMapper. Why not, if MongoMapper classes already contain suitable options to work with.