Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
adds Pickle::Jar that has responsibility for storing and retrieving m…
…odels using PickleRefs
- Loading branch information
Showing
2 changed files
with
250 additions
and
0 deletions.
There are no files selected for viewing
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,123 @@ | ||
module Pickle | ||
# handles storing and retrieveing models using pickle_refs to do so. This is not the smae as creating/finding models | ||
# | ||
# For cucumber use, it's better to use the methods with the same names found in in Pickle::Scenario | ||
# | ||
# Examples: (FYI - the pickle.create_and_store methods do all of this automatically) | ||
# | ||
# # store a user | ||
# jar.store User.make | ||
# | ||
# # retrieve with: | ||
# jar.retrieve Pickle::Ref.new('the user') | ||
# jar.retrieve Pickle::Ref.new('user') | ||
# jar.retrieve Pickle::Ref.new('the first user') | ||
# | ||
# # store another user, this time labelled | ||
# jar.store User.make, :label => "fred" | ||
# | ||
# # retrieve with: | ||
# jar.retrieve Pickle::Ref.new('the user "fred"') | ||
# jar.retrieve Pickle::Ref.new('"fred"') | ||
# | ||
# # store a user made with the admin blueprint | ||
# jar.store User.make(:admin), :factory => "admin_user" | ||
# | ||
# # retrieve with: | ||
# jar.retrieve Pickle::Ref.new('the admin_user') | ||
# jar.retrieve Pickle::Ref.new('the user') | ||
# | ||
# # retrieve all 1st, 2nd and 3rd - in different ways | ||
# jar.retrieve Pickle::Ref.new('the first user') | ||
# jar.retrieve Pickle::Ref.new('fred') | ||
# jar.retrieve Pickle::Ref.new('admin_user') | ||
# | ||
# # what if it's not in the jar? | ||
# jar.retrieve Pickle::Ref.new('the 4th user') | ||
# # => Pickle::Jar::UnknownModelError | ||
# | ||
# jar.include? Pickle::Ref.new('4th user') | ||
# # => false | ||
# | ||
# jar.include? Pickle::Ref.new('user') | ||
# # => true | ||
class Jar | ||
include Parser::Canonical | ||
|
||
# store the given model in the pickle jar. | ||
# | ||
# By default it will be stored under its class name (canononicalized), can also be given a label | ||
# If given a factory, it will also be stored under that name (canononicalized) | ||
# | ||
# @examples | ||
# store object | ||
# store object, :label => "fred" | ||
# store object, :factory => "admin_user" | ||
# store object, :label => "fred", :label => "admin_user" | ||
# | ||
# @return Object the stored object | ||
def store(model, opts = {}) | ||
store_by_factory_and_label(model, model.class.name, opts[:label]) | ||
unless canonical(opts[:factory]) == canonical(model.class.name) | ||
store_by_factory_and_label(model, opts[:factory], opts[:label]) | ||
end | ||
model | ||
end | ||
|
||
# retreive the model matching the pickle_ref | ||
# @raise Pickle::Jar::UnknownModelError raised if the pickle_ref does not refer to a model in this jar | ||
# @raise Pickle::Jar::AmbiguousLabel raised if a label by its own refers to more than one model | ||
def retrieve(ref) | ||
begin | ||
if ref.factory && ref.label | ||
labeled_models(ref.label)[ref.factory] | ||
elsif ref.label | ||
retrieve_by_label ref.label | ||
elsif ref.index | ||
factory_models(ref.factory)[ref.index] | ||
else | ||
factory_models(ref.factory).last | ||
end | ||
end or raise UnknownModelError, '#{ref} is not known' | ||
end | ||
|
||
# Does the given pickle_ref refer to a model in this jar? | ||
# @return boolean | ||
def include?(ref) | ||
retrieve(ref) | ||
rescue UnknownModelError | ||
false | ||
end | ||
|
||
private | ||
def store_by_factory_and_label(model, factory, label) | ||
factory = canonical(factory) | ||
factory_models(factory) << model | ||
labeled_models(label)[factory] = model if label.present? | ||
end | ||
|
||
def retrieve_by_label(label) | ||
models = labeled_models(label).values.uniq | ||
raise AmbiguiousLabelError, "#{label.inspect} refers to #{models.length} models. Specify factory to narrow it down." if models.length > 1 | ||
models[0] | ||
end | ||
|
||
# Hash of arrays, each key is a factory name, each value is an ordered array of models | ||
def factory_models(factory) | ||
@factory_models ||= {} | ||
@factory_models[factory] ||= [] | ||
end | ||
|
||
# Hash of hashes, each key is a label, each value is a hash of factory name => model | ||
def labeled_models(label) | ||
@labeled_models ||= {} | ||
@labeled_models[label] ||= {} | ||
end | ||
|
||
class AmbiguiousLabelError < RuntimeError | ||
end | ||
|
||
class UnknownModelError < RuntimeError | ||
end | ||
end | ||
end |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,127 @@ | ||
require 'spec_helper' | ||
|
||
describe Pickle::Jar do | ||
let(:model_class) { Class.new.tap {|c| c.stub!(:name).and_return('Module::ModelClass') } } | ||
let(:model) { model_class.new } | ||
|
||
shared_examples_for "after storing a model" do | ||
specify "can be retrieved with Pickle::Ref.new(<model class name>)" do | ||
subject.retrieve(Pickle::Ref.new('Module::ModelClass')).should == model | ||
end | ||
|
||
specify "can be retrieved with Pickle::Ref.new('underscored_class_name')" do | ||
subject.retrieve(Pickle::Ref.new('module_model_class')).should == model | ||
end | ||
|
||
specify "can be retrieve with Pickle::Ref.new('last underscored_class_name')" do | ||
subject.retrieve(Pickle::Ref.new("last module_model_class")).should == model | ||
end | ||
end | ||
|
||
describe "after storing a model," do | ||
before { subject.store(model) } | ||
|
||
describe "the model" do | ||
it_should_behave_like "after storing a model" | ||
end | ||
end | ||
|
||
shared_examples_for "after storing a model with an optional factory" do | ||
specify "can be retrieved with Pickle::Ref.new('<factory>')" do | ||
subject.retrieve(Pickle::Ref.new("#{factory}")).should == model | ||
end | ||
|
||
specify "can be retrieve with Pickle::Ref.new('last <factory>')" do | ||
subject.retrieve(Pickle::Ref.new("last #{factory}")).should == model | ||
end | ||
end | ||
|
||
describe "after storing a model with an optional factory," do | ||
let(:factory) { 'factory' } | ||
before { subject.store(model, :factory => factory) } | ||
|
||
describe "the model" do | ||
it_should_behave_like "after storing a model" | ||
it_should_behave_like "after storing a model with an optional factory" | ||
end | ||
end | ||
|
||
shared_examples_for "after storing a model with an optional label" do | ||
specify "can be retrieved with Pickle::Ref.new('<model class name> \"<label>\"')" do | ||
subject.retrieve(Pickle::Ref.new("Module::ModelClass \"#{label}\"")).should == model | ||
end | ||
|
||
specify "can be retrieved with Pickle::Ref.new('\"<label>\"')" do | ||
subject.retrieve(Pickle::Ref.new("\"#{label}\"")).should == model | ||
end | ||
end | ||
|
||
describe "after storing a model with an optional label," do | ||
let(:label) { 'label' } | ||
before { subject.store(model, :label => label) } | ||
|
||
describe "the model" do | ||
it_should_behave_like "after storing a model" | ||
it_should_behave_like "after storing a model with an optional label" | ||
end | ||
end | ||
|
||
describe "after storing two different models under the same label" do | ||
before do | ||
subject.store(model, :label => "fred", :factory => "factory") | ||
subject.store(model_class.new, :label => "fred", :factory => "factory2") | ||
end | ||
|
||
specify "a model can be retrieved by factory and label" do | ||
subject.retrieve(Pickle::Ref.new('factory "fred"')).should == model | ||
end | ||
|
||
specify "retrieving by label alone raises Pickle::Jar::AmbiguiousLabelError" do | ||
lambda { subject.retrieve(Pickle::Ref.new('"fred"')) }.should raise_error(Pickle::Jar::AmbiguiousLabelError) | ||
end | ||
end | ||
|
||
describe "after storing 3 models," do | ||
let(:earliest_model) { model_class.new } | ||
let(:middle_model) { model_class.new } | ||
|
||
before do | ||
subject.store(earliest_model) | ||
subject.store(middle_model) | ||
subject.store(model) | ||
end | ||
|
||
describe "the latest stored model" do | ||
it_should_behave_like "after storing a model" | ||
|
||
specify "can be retrieved with '3rd' in the pickle ref" do | ||
subject.retrieve(Pickle::Ref.new("2nd module_model_class")).should == middle_model | ||
end | ||
end | ||
|
||
specify "the earliest stored model can be retrieved with '2nd' in the pickle ref" do | ||
subject.retrieve(Pickle::Ref.new("2nd module_model_class")).should == middle_model | ||
end | ||
|
||
specify "the earliest stored model can be retrieved with '1st' in the pickle ref" do | ||
subject.retrieve(Pickle::Ref.new("1st module_model_class")).should == earliest_model | ||
end | ||
|
||
specify "the earliest stored model can be retrieved with 'first' in the pickle ref" do | ||
subject.retrieve(Pickle::Ref.new("first module_model_class")).should == earliest_model | ||
end | ||
|
||
specify "attempting to retrieve the 4th model raises UnknownModelError" do | ||
lambda { subject.retrieve(Pickle::Ref.new("4th module_model_class")) }.should raise_error(Pickle::Jar::UnknownModelError) | ||
end | ||
|
||
specify "asking if jar #contains? the 4th model returns false" do | ||
subject.include?(Pickle::Ref.new("4th module_model_class")).should == false | ||
end | ||
|
||
it { should include(Pickle::Ref.new("1st module_model_class")) } | ||
it { should include(Pickle::Ref.new("2nd module_model_class")) } | ||
it { should include(Pickle::Ref.new("3rd module_model_class")) } | ||
it { should_not include(Pickle::Ref.new("25th module_model_class")) } | ||
end | ||
end |