Created
May 4, 2012 05:49
-
-
Save mcmire/2592384 to your computer and use it in GitHub Desktop.
Stupid simple fixture replacement "library" (Ruby 1.9 only)
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
### spec/support/factories.rb ### | |
require_relative 'factory' | |
require 'faker' | |
module ExampleMethods | |
extend Factory::Mixin | |
# Defines: | |
# - build_publisher_user | |
# - create_publisher_user! | |
# - attributes_for_publisher_user | |
add_factory :publisher_user, 'User' do |f| | |
f.email = Faker::Internet.email | |
f.password = SecureRandom.hex(8) | |
f.first_name = Faker::Name.first_name | |
f.last_name = Faker::Name.last_name | |
end | |
alias_factory :user, :publisher_user | |
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
### spec/support/factory.rb ### | |
# Stupid simple fixture replacement "library" | |
require 'ostruct' | |
# Factory is just the namespace for all things factory-related. | |
# | |
module Factory | |
@factory_classes = {} | |
class << self | |
# Public: Define a factory which will manufacture objects of a given class. | |
# | |
# mod - A Module into which the factory methods will be placed. | |
# name - The Symbol name of the factory. | |
# model_class_name - A String name of a class. The manufactured objects | |
# will be of this class. | |
# opts - A Hash of options (default: {}): | |
# parent - The Symbol name of an existing factory; this | |
# factory will inherit attributes from its | |
# parent factory. | |
# | |
def add_factory(mod, name, model_class_name, opts={}, &block) | |
name = name.to_sym | |
@factory_classes[name] = factory_class = | |
_factory_class_for(model_class_name, opts[:parent]) | |
factory_class.defaults(&block) | |
mod.instance_eval do | |
define_method(:"build_#{name}") {|*args| factory_class.build(*args) } | |
define_method(:"create_#{name}!") {|*args| factory_class.create!(*args) } | |
define_method(:"attributes_for_#{name}") { factory_class.defaults } | |
end | |
factory_class | |
end | |
# Internal: Alias a factory by aliasing the methods for that factory. | |
# | |
# mod - A module in which the methods will be aliased. | |
# new_name - The Symbol name of a new factory. | |
# old_name - The Symbol name of an existing factory. | |
# | |
def alias_factory(mod, new_name, old_name) | |
mod.instance_eval do | |
alias_method :"build_#{new_name}", :"build_#{old_name}" | |
alias_method :"create_#{new_name}!", :"create_#{old_name}!" | |
alias_method :"attributes_for_#{new_name}", :"attributes_for_#{old_name}" | |
end | |
end | |
def _factory_class_for(model_class_name, parent_factory_name=nil) | |
parent_factory_class = parent_factory_name ? @factory_classes[parent_factory_name.to_sym] : Base | |
Class.new(parent_factory_class).tap do |klass| | |
klass.model_class_name = model_class_name | |
end | |
end | |
end | |
# Factory::Base represents a single factory. | |
# | |
class Base | |
@defaults = {} | |
class << self | |
# Get the name of the class this factory will use to manufacture objects. | |
attr_reader :model_class_name | |
# Set the name of the class this factory will use to manufacture objects. | |
attr_writer :model_class_name | |
# Make a new object. | |
# | |
# attrs - A Hash of attributes (default: {}). These will be merged with | |
# the set default attributes for this factory. | |
# | |
# Returns some Object. | |
# | |
def build(attrs={}) | |
_model_class.new(_assemble_attrs(attrs)) | |
end | |
# Make a new object and save it, if possible. | |
# | |
# attrs - A Hash of attributes (default: {}). These will be merged with | |
# the set default attributes for this factory. | |
# | |
# Returns some Object. | |
# | |
def create!(attrs={}) | |
model = build(attrs) | |
model.save! if model.respond_to?(:save!) | |
return model | |
end | |
# Set or get the default attributes for the factory. | |
# | |
# block - A block which is called with an instance of Hashie::Mash. | |
# The block is expected to return a Hash (for a set of | |
# attributes), which will be stored as the default attributes for | |
# this factory. (Optional) | |
# | |
# Returns a Hash. | |
# | |
def defaults(&block) | |
if block | |
if block.arity == 1 | |
# OpenStruct isn't a hash, so we can't do hashy stuff with it | |
attrs = Hashie::Mash.new | |
block.call(attrs) | |
@defaults = attrs | |
else | |
@defaults = block.call | |
end | |
else | |
@defaults | |
end | |
end | |
def _assemble_attrs(attrs) | |
@defaults.merge(attrs) | |
end | |
def _model_class | |
@_model_class ||= model_class_name.constantize | |
end | |
end | |
end | |
# This module should be mixed into your RSpec context, like so: | |
# | |
# RSpec.configure do |c| | |
# c.extend(Factory::Mixin) | |
# end | |
# | |
module Mixin | |
# Public: Define a factory which will manufacture objects of a given class. | |
# | |
# This will define three methods in the current context: | |
# | |
# build_NAME - Creates a new object from a class, populating the | |
# object with default attributes (and any additional | |
# attributes provided at runtime) | |
# create_NAME! - Creates a new object from a class and calls #save! | |
# on it (if that class has a #save! method) | |
# attributes_for_NAME - Return the default set of attributes defined in the | |
# factory. | |
# | |
# Arguments: | |
# | |
# name - The Symbol name of the factory. | |
# model_class_name - A String name of a class. The manufactured objects | |
# will be of this class. | |
# opts - A Hash of options (default: {}): | |
# parent - The Symbol name of an existing factory; this | |
# factory will inherit attributes from its | |
# parent factory. | |
# block - A block will will be called with a Hashie::Mash; this | |
# lets you define the default attributes for the factory. | |
# | |
# Example: | |
# | |
# add_factory :user, 'User' do |u| | |
# u.first_name = "Joe" | |
# u.last_name = "Blow" | |
# u.enabled = false | |
# end | |
# | |
# # within tests... | |
# build_user(...) | |
# create_user!(...) | |
# attributes_for_user | |
# | |
def add_factory(name, model_class_name, opts={}, &block) | |
Factory.add_factory(self, name, model_class_name, opts, &block) | |
end | |
# Internal: Alias a factory by aliasing the methods for that factory. | |
# | |
# new_name - The Symbol name of a new factory. | |
# old_name - The Symbol name of an existing factory. | |
# | |
# Example: | |
# | |
# add_factory :user, 'User', do |u| | |
# # ... | |
# end | |
# alias_factory :customer, :user | |
# | |
# # within tests... | |
# build_customer(...) | |
# create_customer(...) | |
# attributes_for_customer | |
# | |
def alias_factory(new_name, old_name) | |
Factory.alias_factory(self, new_name, old_name) | |
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
### spec/spec_helper.rb ### | |
require_relative 'support/factories' | |
RSpec.configure do |c| | |
c.include(ExampleMethods) | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Oh, I know, that's what I had originally. I was just following the TomDoc recommendation to split those out since they are two separate methods. But, I'm kind of divided on that, it might be too pedantic.