diff --git a/lib/pickle/api.rb b/lib/pickle/api.rb deleted file mode 100644 index de0e77c7..00000000 --- a/lib/pickle/api.rb +++ /dev/null @@ -1,84 +0,0 @@ -module Pickle - # pickle dsl is your gateway to making/finding/storing/retrieving models - # - # Terminology: - # make - creating a model in the db, probably using a factory/bluperint - # find - finding a model from the db - # store - making a memory of an existing model (in the pickle jar) - # retrieve - remembering a model (from the pickle jar) - # - # Example use, in a cucumber scenario: - # - # # create and store fred, and store his site for later reference - # fred = pickle.make_and_store :factory => 'website_owner', :label => 'fred' - # pickle.store fred.site, :label => "fred's site" - # - # # check what models pickle knows of - # pickle.known?(:label => 'fred') # => true - # pickle.known?('website_owner') # => true - # pickle.known?('site') # => true - # pickle.known?('site "fred"') # => false - # - # # these are all ways of retrieving the site - # pickle.retrieve 'the site' - # pickle.retrieve :label => "fred's site" - # pickle.retrieve '"fred's site"' - # pickle.retrieve 'last site' - # pickle.retrieve('fred').site - # - module Api - # makes a model using the ref and fields, and stores it - def make_and_store(ref, pickle = nil) - model = adapters.make(ref_from(ref), attributes_from(fields)) - jar.store model, ref - end - - # finds a model using the ref and fields, and stores it - def find_and_store(pickle_ref, fields = nil) - model = adapters.find_first(ref(pickle_ref), attributes(fields)) - jar.store model, ref - end - - # finds all models using a plural factory name and fields, and store them - def find_all_and_store(plural, fields = nil) - pickle_ref = ref(plural.singularize) - adapters.find_all(pickle_ref, attributes_from(fields)).each do |model| - jar.store model, pickle_ref - end - end - - # store a given model, with the optional ref - def store(model, pickle_ref = nil) - jar.store model, ref(pickle_ref) - end - - # retrieve a model that was stored previously, and re-find it - def retrieve(pickle_ref) - adapters.find jar.retrieve(ref(pickle_ref)) - end - - # retrieve an orignal model object that was stored previously - def retrieve_original(pickle_ref) - jar.retrieve ref(pickle_ref) - end - - # is a model known? - def known?(pickle_ref) - jar.include? ref(pickle_ref) - end - - # convert a string, hash, or ref into a Pickle::Ref, aware of config - def ref(pickle_ref) - case pickle_ref - when Pickle::Ref then pickle_ref - when Hash then Pickle::Ref.new(pickle_ref.merge(:config => config)) - else Pickle::Ref.new(pickle_ref, :config => config) - end - end - - # convert a string or hash into attributes, aware of models in the jar - def attributes(pickle_fields) - - end - end -end \ No newline at end of file diff --git a/lib/pickle/session/parser.rb b/lib/pickle/session/parser_old.rb similarity index 100% rename from lib/pickle/session/parser.rb rename to lib/pickle/session/parser_old.rb diff --git a/lib/pickle/session_old.rb b/lib/pickle/session_old.rb new file mode 100644 index 00000000..727a9364 --- /dev/null +++ b/lib/pickle/session_old.rb @@ -0,0 +1,205 @@ +module Pickle + class ModelNotKnownError < RuntimeError + attr_reader :name + + def initialize(name, message = nil) + @name = name + @message = message || "The model: '#{name}' is not known in this scenario. Use #create_model to create, or #find_model to find, and store a reference in this scenario." + end + + def to_s + @message + end + end + + module Session + class << self + def included(world_class) + proxy_to_pickle_parser(world_class) + end + + def extended(world_object) + proxy_to_pickle_parser(class << world_object; self; end) # metaclass is not 2.1 compatible + end + + protected + def proxy_to_pickle_parser(world_class) + world_class.class_eval do + unless methods.include?('method_missing_with_pickle_parser') + alias_method_chain :method_missing, :pickle_parser + alias_method_chain :respond_to?, :pickle_parser + end + end + end + end + + def create_model(pickle_ref, fields = nil) + factory, label = *parse_model(pickle_ref) + raise ArgumentError, "Can't create with an ordinal (e.g. 1st user)" if label.is_a?(Integer) + fields = fields.is_a?(Hash) ? parse_hash(fields) : parse_fields(fields) + record = pickle_config.factories[factory].create(fields) + store_model(factory, label, record) + record + end + + # if a column exists in the table which matches the singular factory name, this is used as the pickle ref + def create_models_from_table(plural_factory, table) + factory = plural_factory.singularize + table.hashes.map do |hash| + pickle_ref = factory + (hash[factory] ? " \"#{hash.delete(factory)}\"" : "") + create_model(pickle_ref, hash) + end + end + + def find_model(a_model_name, fields = nil) + factory, name = *parse_model(a_model_name) + + raise ArgumentError, "Can't find a model with an ordinal (e.g. 1st user)" if name.is_a?(Integer) + + model_class = pickle_config.factories[factory].klass + fields = fields.is_a?(Hash) ? parse_hash(fields) : parse_fields(fields) + conditions = convert_models_to_attributes(model_class, fields) + record = Pickle::Adapter.find_first_model(model_class, conditions) + + store_model(factory, name, record) if record + + record + end + + def find_model!(name, fields = nil) + find_model(name, fields) or raise ModelNotKnownError, name + end + + def find_models(factory, fields = nil) + factory = pickle_parser.canonical(factory) + + models_by_index(factory).clear + + model_class = pickle_config.factories[factory].klass + conditions = convert_models_to_attributes(model_class, parse_fields(fields)) + records = Pickle::Adapter.find_all_models(model_class, conditions) + + records.each {|record| store_model(factory, nil, record)} + end + + # if a column exists in the table which matches the singular factory name, this is used as the pickle ref + def find_models_from_table(plural_factory, table) + factory = plural_factory.singularize + table.hashes.map do |hash| + pickle_ref = factory + (hash[factory] ? " \"#{hash.delete(factory)}\"" : "") + find_model(pickle_ref, hash) + end + end + + # return the original model stored by create_model or find_model + def created_model(name) + factory, name_or_index = *parse_model(name) + + if name_or_index.blank? + models_by_index(factory).last + elsif name_or_index.is_a?(Integer) + models_by_index(factory)[name_or_index] + else + models_by_name(factory)[name_or_index] or raise ModelNotKnownError, name + end + end + + # predicate version which raises no errors + def created_model?(name) + (created_model(name) rescue nil) ? true : false + end + + # return a newly selected model + def model(name) + model = created_model(name) + return nil unless model + Pickle::Adapter.get_model(model.class, model.id) + end + + # predicate version which raises no errors + def model?(name) + (model(name) rescue nil) ? true : false + end + + # like model, but raise an error if it can't be found + def model!(name) + model(name) or raise ModelNotKnownError, name + end + + # like created_model, but raise an error if it can't be found + def created_model!(name) + created_model(name) or raise ModelNotKnownError, name + end + + # return all original models of specified type + def created_models(factory) + models_by_index(factory) + end + + # return all models of specified type (freshly selected from the database) + def models(factory) + created_models(factory).map do |model| + Pickle::Adapter.get_model(model.class, model.id) + end + end + + def respond_to_with_pickle_parser?(method, include_private = false) + respond_to_without_pickle_parser?(method, include_private) || pickle_parser.respond_to?(method, include_private) + end + + protected + def method_missing_with_pickle_parser(method, *args, &block) + if pickle_parser.respond_to?(method) + pickle_parser.send(method, *args, &block) + else + method_missing_without_pickle_parser(method, *args, &block) + end + end + + def pickle_parser=(parser) + parser.session = self + @pickle_parser = parser + end + + def pickle_parser + @pickle_parser or self.pickle_parser = Pickle.parser + end + + def pickle_config + pickle_parser.config + end + + def convert_models_to_attributes(ar_class, attrs) + attrs.each do |key, val| + if ((defined?(ActiveRecord::Base) && val.is_a?(ActiveRecord::Base)) || + (defined?(DataMapper::Model) && val.is_a?(DataMapper::Model))) && + Pickle::Adapter.column_names(ar_class).include?("#{key}_id") + attrs["#{key}_id"] = val.id + attrs["#{key}_type"] = val.class.base_class.name if ar_class.column_names.include?("#{key}_type") + attrs.delete(key) + end + end + end + + def models_by_name(factory) + @models_by_name ||= {} + @models_by_name[pickle_parser.canonical(factory)] ||= {} + end + + def models_by_index(factory) + @models_by_index ||= {} + @models_by_index[pickle_parser.canonical(factory)] ||= [] + end + + # if the factory name != the model name, store under both names + def store_model(factory, name, record) + store_record(record.class.name, name, record) unless pickle_parser.canonical(factory) == pickle_parser.canonical(record.class.name) + store_record(factory, name, record) + end + + def store_record(factory, name, record) + models_by_name(factory)[name] = record + models_by_index(factory) << record + end + end +end diff --git a/spec/pickle/api_spec.rb b/spec/pickle/api_spec.rb deleted file mode 100644 index ad3fc814..00000000 --- a/spec/pickle/api_spec.rb +++ /dev/null @@ -1,24 +0,0 @@ -require 'spec_helper' - -describe Pickle::Api do - include Pickle::Api - - describe "#ref" do - it " creates a new pickle ref using the hash, and my config" do - stub!(:config).and_return(mock) - Pickle::Ref.should_receive(:new).with({:foo => :bar, :config => config}).and_return(pickle_ref = mock) - ref({:foo => :bar}).should == pickle_ref - end - - it " creates a new pickle ref using the string, and my config" do - stub!(:config).and_return(mock) - Pickle::Ref.should_receive(:new).with('foo bar', {:config => config}).and_return(pickle_ref = mock) - ref('foo bar').should == pickle_ref - end - - it " just returns the pickle_ref" do - pickle_ref = Pickle::Ref.new('factory') - ref(pickle_ref).should == pickle_ref - end - end -end diff --git a/spec/pickle/session_spec.rb.old b/spec/pickle/session_spec.rb.old new file mode 100644 index 00000000..0c8a32c8 --- /dev/null +++ b/spec/pickle/session_spec.rb.old @@ -0,0 +1,434 @@ +require 'spec_helper' + +# TODO: remove this and push AR stuff into ORM adapter +module ActiveRecord + class Base + end +end + +module DataMapper + class Model + end +end + +describe Pickle::Session do + include Pickle::Session + + let :user_class do + mock("User class", :name => 'User') + end + + let :user do + mock("user", :class => user_class, :id => 1) + end + + let :user_factory do + Pickle::Adapter::Orm.new(user_class) + end + + before do + config.stub(:factories).and_return('user' => user_factory) + end + + describe "Pickle::Session proxy missing methods to parser", :shared => true do + it "should forward to pickle_parser it responds_to them" do + subject.pickle_parser.should_receive(:parse_model) + subject.parse_model + end + + it "should raise error if pickle_parser don't know about em" do + lambda { subject.parse_infinity }.should raise_error + end + end + + describe "including Pickle::Session" do + subject do + self + end + + it_should_behave_like "Pickle::Session proxy missing methods to parser" + end + + describe "extending Pickle::Session" do + subject do + returning Object.new do |object| + object.extend Pickle::Session + end + end + + it_should_behave_like "Pickle::Session proxy missing methods to parser" + end + + describe "after storing a single user", :shared => true do + it "created_models('user') should be array containing the original user" do + created_models('user').should == [user] + end + + describe "the original user should be retrievable with" do + it "created_model('the user')" do + created_model('the user').should == user + end + + it "created_model('1st user')" do + created_model('1st user').should == user + end + + it "created_model('last user')" do + created_model('last user').should == user + end + end + + describe "(found from db)" do + let :user_from_db do + returning user.dup do |from_db| + from_db.stub!(:id).and_return(100) + end + end + + before do + Pickle::Adapter.stub!(:get_model).with(user_class, 100).and_return(user_from_db) + end + + it "models('user') should be array containing user" do + models('user').should == [user_from_db] + end + + describe "user should be retrievable with" do + it "model('the user')" do + model('the user').should == user_from_db + end + + it "model('1st user')" do + model('1st user').should == user_from_db + end + + it "model('last user')" do + model('last user').should == user_from_db + end + + it "model!('last user')" do + model('last user').should == user_from_db + end + end + end + end + + describe "#create_model" do + before do + user_factory.stub!(:create).and_return(user) + end + + describe "('a user')" do + it "should call user_factory.create({})" do + user_factory.should_receive(:create).with({}) + create_model('a user') + end + + describe "after create," do + before { create_model('a user') } + + it_should_behave_like "after storing a single user" + end + end + + describe "('1 user', 'foo: \"bar\", baz: \"bing bong\"')" do + it "should call user_factory.create({'foo' => 'bar', 'baz' => 'bing bong'})" do + user_factory.should_receive(:create).with({'foo' => 'bar', 'baz' => 'bing bong'}) + create_model('1 user', 'foo: "bar", baz: "bing bong"') + end + + describe "after create," do + before { create_model('1 user', 'foo: "bar", baz: "bing bong"') } + + it_should_behave_like "after storing a single user" + end + end + + describe "('an user: \"fred\")" do + it "should call user_factory.create({})" do + user_factory.should_receive(:create).with({}) + create_model('an user: "fred"') + end + + describe "after create," do + before { create_model('an user: "fred"') } + + it_should_behave_like "after storing a single user" + + it "created_model('the user: \"fred\"') should retrieve the user" do + created_model('the user: "fred"').should == user + end + + it "created_model?('the user: \"shirl\"') should be false" do + created_model?('the user: "shirl"').should == false + end + + it "model?('the user: \"shirl\"') should be false" do + model?('the user: "shirl"').should == false + end + end + end + + describe "with hash" do + it "should call user_factory.create({'foo' => 'bar'})" do + user_factory.should_receive(:create).with({'foo' => 'bar'}) + create_model('a user', {'foo' => 'bar'}).should == user + end + + describe "after create," do + before { create_model('a user', {'foo' => 'bar'}) } + + it_should_behave_like "after storing a single user" + end + end + end + + describe '#find_model' do + before do + Pickle::Adapter.stub!(:find_first_model).with(user_class, anything).and_return(user) + end + + it "should call user_class.find :first, :conditions => {}" do + find_model('a user', 'hair: "pink"').should == user + end + + describe "after find," do + before { find_model('a user', 'hair: "pink"') } + + it_should_behave_like "after storing a single user" + end + + describe "with hash" do + it "should call user_class.find('user', {'foo' => 'bar'})" do + find_model('a user', {'foo' => 'bar'}) + end + + describe "after find," do + before { find_model('a user', {'foo' => 'bar'}) } + + it_should_behave_like "after storing a single user" + end + end + end + + describe "create and find using plural_factory and table" do + context "when given a table without a matching pickle ref column" do + let :table do + mock(:hashes => [{'name' => 'Fred'}, {'name' => 'Betty'}]) + end + + it "#create_models_from_table(, ) should call create_model for each of the table hashes with plain factory name and return the models" do + should_receive(:create_model).with("user", 'name' => "Fred").once.ordered.and_return(:fred) + should_receive(:create_model).with("user", 'name' => "Betty").once.ordered.and_return(:betty) + create_models_from_table("users", table).should == [:fred, :betty] + end + + it "#find_models_from_table(,
) should call find_model for each of the table hashes with plain factory name and return the models" do + should_receive(:find_model).with("user", 'name' => "Fred").once.ordered.and_return(:fred) + should_receive(:find_model).with("user", 'name' => "Betty").once.ordered.and_return(:betty) + find_models_from_table("users", table).should == [:fred, :betty] + end + end + + context "when given a table with a matching pickle ref column" do + let :table do + mock(:hashes => [{'user' => "fred", 'name' => 'Fred'}, {'user' => "betty", 'name' => 'Betty'}]) + end + + it "#create_models_from_table(,
) should call create_model for each of the table hashes with labelled pickle ref" do + should_receive(:create_model).with("user \"fred\"", 'name' => "Fred").once.ordered.and_return(:fred) + should_receive(:create_model).with("user \"betty\"", 'name' => "Betty").once.ordered.and_return(:betty) + create_models_from_table("users", table).should == [:fred, :betty] + end + + it "#find_models_from_table(,
) should call find_model for each of the table hashes with labelled pickle ref" do + should_receive(:find_model).with("user \"fred\"", 'name' => "Fred").once.ordered.and_return(:fred) + should_receive(:find_model).with("user \"betty\"", 'name' => "Betty").once.ordered.and_return(:betty) + find_models_from_table("users", table).should == [:fred, :betty] + end + end + end + + describe "#find_model!" do + it "should call find_model" do + should_receive(:find_model).with('name', 'fields').and_return(user) + find_model!('name', 'fields') + end + + it "should call raise error if find_model returns nil" do + should_receive(:find_model).with('name', 'fields').and_return(nil) + lambda { find_model!('name', 'fields') }.should raise_error(Pickle::ModelNotKnownError) + end + end + + describe "#find_models" do + before do + Pickle::Adapter.stub!(:find_all_models).with(user_class, anything).and_return([user]) + end + + it "should call User.find :all, :conditions => {'hair' => 'pink'}" do + find_models('user', 'hair: "pink"').should == [user] + end + + describe "after find," do + before { find_models('user', 'hair: "pink"') } + + it_should_behave_like "after storing a single user" + end + + it "should cope with spaces in the factory name (ie. it should make it canonical)" do + pickle_parser.stub!(:canonical).and_return('user') + pickle_parser.should_receive(:canonical).with('u ser').and_return('user') + find_models('u ser', 'hair: "pink"').should == [user] + end + end + + describe 'creating \'a super admin: "fred"\', then \'a user: "shirl"\', \'then 1 super_admin\' (super_admin is factory that returns users)' do + let(:fred) { mock("fred", :class => user_class, :id => 2) } + let(:shirl) { mock("shirl", :class => user_class, :id => 3) } + let(:noname) { mock("noname", :class => user_class, :is => 4) } + + let(:super_admin_factory) do + Pickle::Adapter::FactoryGirl.new(mock(:build_class => user_class, :factory_name => :super_admin)) + end + + before do + config.stub(:factories).and_return(user_factory.name => user_factory, super_admin_factory.name => super_admin_factory) + user_factory.stub(:create).and_return(shirl) + super_admin_factory.stub(:create).and_return(fred, noname) + end + + def do_create_users + create_model('a super admin: "fred"') + create_model('a user: "shirl"') + create_model('1 super_admin') + end + + it "should call Factory.create with <'super_admin'>, <'user'>, <'super_admin'>" do + super_admin_factory.should_receive(:create).with({}).twice + user_factory.should_receive(:create).with({}).once + do_create_users + end + + describe "after create," do + before do + do_create_users + end + + it "created_models('user') should == [fred, shirl, noname]" do + created_models('user').should == [fred, shirl, noname] + end + + it "created_models('super_admin') should == [fred, noname]" do + created_models('super_admin').should == [fred, noname] + end + + describe "#created_model" do + it "'that user' should be noname (the last user created - as super_admins are users)" do + created_model('that user').should == noname + end + + it "'the super admin' should be noname (the last super admin created)" do + created_model('that super admin').should == noname + end + + it "'the 1st super admin' should be fred" do + created_model('the 1st super admin').should == fred + end + + it "'the first user' should be fred" do + created_model('the first user').should == fred + end + + it "'the 2nd user' should be shirl" do + created_model('the 2nd user').should == shirl + end + + it "'the last user' should be noname" do + created_model('the last user').should == noname + end + + it "'the user: \"fred\" should be fred" do + created_model('the user: "fred"').should == fred + end + + it "'the user: \"shirl\" should be shirl" do + created_model('the user: "shirl"').should == shirl + end + end + end + end + + describe "when 'the user: \"me\"' exists and there is a mapping from 'I', 'myself' => 'user: \"me\"" do + before do + self.pickle_parser = Pickle::Parser.new(:config => Pickle::Config.new {|c| c.map 'I', 'myself', :to => 'user: "me"'}) + config.stub(:factories).and_return('user' => user_factory) + Pickle::Adapter.stub!(:get_model).with(user_class, anything).and_return(user) + user_factory.stub!(:create).and_return(user) + create_model('the user: "me"') + end + + it 'model("I") should return the user' do + model('I').should == user + end + + it 'model("myself") should return the user' do + model('myself').should == user + end + + it "#parser.parse_fields 'author: user \"JIM\"' should raise Error, as model deos not refer" do + lambda { pickle_parser.parse_fields('author: user "JIM"') }.should raise_error + end + + it "#parser.parse_fields 'author: the user' should return {\"author\" => }" do + pickle_parser.parse_fields('author: the user').should == {"author" => user} + end + + it "#parser.parse_fields 'author: myself' should return {\"author\" => }" do + pickle_parser.parse_fields('author: myself').should == {"author" => user} + end + + it "#parser.parse_fields 'author: the user, approver: I, rating: \"5\"' should return {'author' => , 'approver' => , 'rating' => '5'}" do + pickle_parser.parse_fields('author: the user, approver: I, rating: "5"').should == {'author' => user, 'approver' => user, 'rating' => '5'} + end + + it "#parser.parse_fields 'author: user: \"me\", approver: \"\"' should return {'author' => , 'approver' => \"\"}" do + pickle_parser.parse_fields('author: user: "me", approver: ""').should == {'author' => user, 'approver' => ""} + end + end + + describe "convert_models_to_attributes(ar_class, :user => )" do + before do + user.stub(:is_a?).with(ActiveRecord::Base).and_return(true) + end + + describe "(when ar_class has column 'user_id')" do + let :ar_class do + mock('ActiveRecord', :column_names => ['user_id'], :const_get => ActiveRecord::Base::PickleAdapter) + end + + it "should return {'user_id' => }" do + convert_models_to_attributes(ar_class, :user => user).should == {'user_id' => user.id} + end + end + + describe "(when ar_class has columns 'user_id', 'user_type')" do + let :ar_class do + mock('ActiveRecord', :column_names => ['user_id', 'user_type'], :const_get => ActiveRecord::Base::PickleAdapter) + end + + it "should return {'user_id' => , 'user_type' => }" do + user.class.should_receive(:base_class).and_return(mock('User base class', :name => 'UserBase')) + convert_models_to_attributes(ar_class, :user => user).should == {'user_id' => user.id, 'user_type' => 'UserBase'} + end + end + end + + it "#model!('unknown') should raise informative error message" do + lambda { model!('unknown') }.should raise_error(Pickle::ModelNotKnownError, "The model: 'unknown' is not known in this scenario. Use #create_model to create, or #find_model to find, and store a reference in this scenario.") + end + + it "#created_model!('unknown') should raise informative error message" do + lambda { created_model!('unknown') }.should raise_error(Pickle::ModelNotKnownError) + end +end