Skip to content

Instantly share code, notes, and snippets.

@oleganza
Created December 5, 2008 14:14
Show Gist options
  • Save oleganza/32343 to your computer and use it in GitHub Desktop.
Save oleganza/32343 to your computer and use it in GitHub Desktop.
# SimpleFixtures is an alternative to dm-sweatshop
#
# Oleg Andreev <[email protected]>
# November 24, 2008
# License: WTFPL (http://sam.zoy.org/wtfpl/)
#
module SimpleFixtures
# Define a named fixture. Named fixtures other than :default are merged to :default one
#
# A.define_fixture(:a => 1)
# A.define_fixture {|overrides| {:a => 1}.merge(overrides) }
# A.define_fixture(:some_name, :a => 1)
# A.define_fixture(:some_name, proc {|overrides| {:a => 1} })
#
def define_fixture(name = nil, attrs = nil, &blk)
if !name.is_a?(Symbol) and !name.is_a?(String) # name and attrs could be nil, but blk != nil
attrs = name
name = :__default__
end
fixtures[name] = attrs || blk || {}
end
# DSSV = double space separated values.
# Example:
# name email account
# oleg Oleg [email protected] proc {Account.fixture!(:idbns)}
# antares Michael [email protected] proc {Account.fixture!(:nr)}
#
def dssv_fixtures(dssv)
splitted_lines = dssv.to_a.
map{|l| l.strip }. # strip spaces and CR/LF
select{|l| !l.empty? }. # remove blank lines
map{|l| l.split(/\s\s+/) } # split by two+ spaces
fields = splitted_lines.shift.map { |f| f.strip.to_sym }
splitted_lines.each do |arr|
arr.size != (fields.size + 1) and raise "Malformed DSSV fixture format (check double spaces!)"
# extract tag as Symbol
tag = arr.shift.to_sym
# add field names to values and convert to hash {:name=>"Name", :email=>"email", ...}
attrs = Hash[*(fields.zip(arr).flatten)]
# define named fixture
define_fixture(tag, attrs.inject({}) { |h, (k, v)|
h[k] = (v =~ %r{^proc\s?\{.*\}$}i ? eval(v) : P.autotype(v))
h
})
end
end
def fixture!(*args, &blk)
# special case: only name is given - return an unique fixture
return unique_fixture!(args.first) if args.size == 1 and args.first.is_a?(Symbol) || args.first.is_a?(String)
instance = fixture(*args, &blk)
instance.save or raise "Could not save #{instance}! Errors: #{instance.errors.to_a.inspect}"
instance
end
# A.fixture # default fixture
# A.fixture(:a => 1) # default fixture with overrides
# A.fixture(:black) # :black fixture
# A.fixture(:black, :a => 1) # :black fixture with overrides
#
def fixture(*args, &blk)
new(fixture_attributes(*args), &blk)
end
def fixture_attributes(*args)
P.fixture_attributes(self, *args)
end
def fast_clean!
@unique_fixtures = nil
super rescue nil
end
def unique_fixture!(name, overrides = {})
@unique_fixtures ||= Hash.new # this should be cleared in DM.fast_clean!
@unique_fixtures[name] ||= fixture!(name, overrides)
end
def fixtures
@fixtures ||= Hash.new({})
end
module P extend self
def autotype(str) # for DSSV
case str
when /^\:([\w\d_]+)$/: $1.to_sym
when /^\d[\d\_]+$/: str.to_i
when /^\d[\d\_\.]+$/: str.to_f
else str
end
end
def fixture_attributes(model, *args)
name = args.shift if args.first.is_a?(Symbol) || args.first.is_a?(String)
sc = model.superclass
base_fixtures = sc.respond_to?(:fixture_attributes) ? sc.fixture_attributes(name) : {}
execute_merge(model, base_fixtures, # first, use superclass fixture
execute_merge(model, model.fixtures[:__default__], # then override with local class default fixture
execute_merge(model, model.fixtures[name], # then override with named fixture
execute_merge(model, args.shift || {}, args.shift || {})))). # then override with local attributes
inject({}) do |h, (k,v)|
h[k] = (v.respond_to?(:call)) ? v.call : v if v
h
end
end
def execute_merge(model, proc_or_hash, overrides = {})
case proc_or_hash
when Proc: proc_or_hash.call(overrides)
when Hash: proc_or_hash.merge(overrides)
else
raise "Attributes must be Proc, DuckType(:call) or Hash (was #{proc_or_hash.inspect})"
end
end
end # P
module RandomFixtures
def random_sha1
require 'digest/sha1'
Digest::SHA1.hexdigest("#{rand(2**160)}")
end
def valid_email_fixture(prefix = "user", domain = "example.com")
"#{prefix}#{rand(2**64)}@#{domain}"
end
# Generates exponentially distributed time values
def random_fixture_datetime(range = 55, base = 1.5)
offset = (base**(rand * range))
offset > 2**30 && offset = 2**30
Time.now - offset
end
end
include RandomFixtures
module RSpec
# fixtures_spec.rb:
# extend SimpleFixtures::RSpec
# verify_default_fixtures!(
# :models => [ Person, Project ],
# :except => [ Merb::DataMapperSessionStore ]
# )
def verify_default_fixtures!(opts = {})
fixtured_models = opts[:models] || [ ]
exceptions = opts[:except] || opts [:exceptions] || [ ]
fixtured_models.each do |model|
describe model, "default fixture" do
before(:all) do
@fixture = model.fixture!
end
it { @fixture.should be_valid }
end
end
(DataMapper::Resource.descendants.to_a - fixtured_models - exceptions).each do |model|
describe model, "default fixture" do
it "should have valid default fixture" do
pending "add this class to a list of models with fixtures"
end
end
end
end # verify_default_fixtures
end # RSpec
end # SimpleFixtures
DataMapper::Model.send(:include, SimpleFixtures) if defined?(DataMapper)
require File.join(File.dirname(__FILE__), "spec_helper")
describe SimpleFixtures do
before(:each) do
@model = Class.new do
attr_accessor :attrs, :blk
def initialize(attrs, &blk)
@attrs = attrs
@blk = blk
end
def save
@saved = true
end
def saved?
@saved
end
end
@model.extend(SimpleFixtures)
end
describe "regular named fixtures" do
before(:each) do
model = @model
@model.define_fixture( :name => "Person", :friend => proc { model.fixture!(:with_block) } )
@model.define_fixture(:oleg, :name => "Oleg", :friend => proc { model.fixture!(:antares) } )
@model.define_fixture(:antares, proc {{:name => "MK"}})
@model.define_fixture(:with_block) do |overrides| # with block, but without a friend
overrides.merge(:with_block => :i_am_over_it, :friend => nil)
end
end
it "should have named fixtures" do
@model.fixtures[:__default__].should_not be_empty
@model.fixtures[:oleg].should_not be_empty
@model.fixtures[:antares].should respond_to(:call)
@model.fixtures[:with_block].should respond_to(:call)
end
it "should generate a named and default fixtures using Model#fixture and #fixture!" do
f = @model.fixture(&@some_block)
f.should_not be_saved
f.attrs[:friend].should be_saved
f.attrs[:name].should == "Person"
f.attrs[:friend].should be_kind_of(@model)
f.attrs[:friend].attrs[:name].should == "Person"
f.attrs[:friend].attrs[:with_block].should == :i_am_over_it
f.attrs[:friend].attrs[:friend].should == nil
end
describe "unique_fixture!(:name, overrides = {})" do
before(:each) do
@uf1 = @model.unique_fixture!(:oleg, {:xyz => 123})
@uf2 = @model.unique_fixture!(:oleg, {:qwe => 345})
end
it "should return equal unique fixtures" do
@uf1.object_id.should == @uf2.object_id
end
it "should set attributes provided first time" do
@uf2.attrs[:xyz].should == 123
@uf2.attrs[:qwe].should be_nil
end
end # unique_fixture!(:name, overrides)
describe "fixture!(:name)" do
before(:each) do
@uf1 = @model.fixture!(:oleg)
@uf2 = @model.fixture!(:oleg)
@f1 = @model.fixture!(:oleg, {:xyz => 123})
@f2 = @model.fixture!(:oleg, {:qwe => 345})
end
it "should return an unique fixture using fixture!(:name) with not overrides given" do
@uf1.object_id.should == @uf2.object_id
end
it "should return a not unique fixture using fixture!(:name) with overrides given" do
@f1.object_id.should_not == @f2.object_id
end
end # fixture!(:name)
end # regular
describe "with DSSV" do
before(:each) do
@model.dssv_fixtures(%{
name autotype country
spb St. Petersburg 5_000_000 proc { ("Rus" + "sia").to_sym }
paris Paris, Île-de-France :symbol proc {%{France}}
})
end
it "should generate named fixtures" do
spb = @model.fixture(:spb)
spb.attrs.should == {:name => "St. Petersburg", :autotype => 5_000_000, :country => :Russia }
paris = @model.fixture(:paris)
paris.attrs.should == {:name => "Paris, Île-de-France", :autotype => :symbol, :country => "France" }
end
describe "(malformed DSSV)" do
it do
lambda{ @model.dssv_fixtures(%{
a b c
tag1 a b c
tag2 a b b2 c
}) }.should raise_error
end
end
end # DSSV
describe "inherited fixtures" do
before(:each) do
@model2 = Class.new(@model){}
@model3 = Class.new(@model2){}
@model.define_fixture(:base => true, :prop => 1)
@model2.define_fixture(:child => true, :prop => 2)
@model3.define_fixture(:grandchild => true, :prop => 3)
@model.define_fixture(:named, :named => :base, :named_prop => 1)
@model2.define_fixture(:named, :named_prop => 2)
@model3.define_fixture(:named, :named_prop => 3)
end
it "should inherit default attributes" do
@model2.fixture_attributes.should == {
:base => true,
:child => true,
:prop => 2
}
@model3.fixture_attributes.should == {
:base => true,
:child => true,
:grandchild => true,
:prop => 3
}
end
it "should inherit named attributes" do
@model2.fixture_attributes(:named).should == {
:base => true,
:child => true,
:prop => 2,
:named => :base,
:named_prop => 2
}
@model3.fixture_attributes(:named).should == {
:base => true,
:child => true,
:grandchild => true,
:prop => 3,
:named => :base,
:named_prop => 3
}
end
end # inherited fixtures
end # SimpleFixtures
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment