Skip to content

Commit

Permalink
adds Pickle::Session::Api, Pickle::Dsl, and modules for adapters, con…
Browse files Browse the repository at this point in the history
…version and default config
  • Loading branch information
ianwhite committed Aug 22, 2010
1 parent e2d3b7c commit 1a83ba5
Show file tree
Hide file tree
Showing 14 changed files with 239 additions and 643 deletions.
10 changes: 10 additions & 0 deletions lib/pickle/default_config.rb
@@ -0,0 +1,10 @@
module Pickle
# include this module to get a config attribute that defaults to Pickle.config if it is not set otherwise
module DefaultConfig
attr_writer :config

def config
@config ||= Pickle.config
end
end
end
44 changes: 44 additions & 0 deletions lib/pickle/dsl.rb
@@ -0,0 +1,44 @@
module Pickle
# included into cucumber scenarios via World(Pickle::Scenario)
#
# for more fine-grained access to the pickle session, see Pickle::Session::Api
module Dsl
include MakeMatcher

# retrieve the model with the given pickle_ref from the pickle session, and re-find it from the database
# @raise Pickle::UnknownModelError
# @return Object the stored model
def model(pickle_ref)
pickle.retrieve_and_reload(pickle_ref)
end

# does the model with given pickle_ref exist in the pickle session?
# @return falsy if pickle_ref not know, truthy otherwise
def model?(pickle_ref)
pickle.known?(pickle_ref)
end

# make a model using the pickle_ref, and optional fields, and store it in the pickle session under its pickle_ref
# @return Object the newly made model
def make(pickle_ref, fields = nil)
pickle.make_and_store(pickle_ref, fields)
end

# find a model using the given pickle_ref and optional fields, and store it in the pickle session under its pickle_ref
# @return Object the found object
def find(pickle_ref, fields = nil)
pickle.find_and_store(pickle_ref, fields)
end

# find all models using the given plural factory name, and optional fields, and store them in the pickle session using the factory name
# @return Array array of found objects
def find_all(plural, fields = nil)
pickle.find_all_and_store(plural, fields)
end

# the pickle session, @see Pickle::Session::Api
def pickle
@pickle ||= Pickle::Session.new
end
end
end
23 changes: 21 additions & 2 deletions lib/pickle/ref.rb
@@ -1,3 +1,6 @@
require 'pickle/parser/matchers'
require 'pickle/parser/canonical'

module Pickle
class InvalidPickleRefError < RuntimeError
end
Expand All @@ -23,7 +26,15 @@ def initialize(*args)
args.any? ? parse_string(args.first) : parse_hash(options)
validate!
end


def to_s
"#{factory}#{" index:#{index}" if index}#{" \"#{label}\"" if label}"
end

def inspect
"#<Pickle::Ref '#{to_s}'>"
end

protected
def validate!
raise InvalidPickleRefError, "#{inspect} requires a factory or label" if factory.blank? && label.blank?
Expand All @@ -40,6 +51,7 @@ def parse_hash(orig)

def parse_string(orig)
str = orig.dup
apply_mappings!(str)
@index = parse_index!(str)
@factory = parse_factory!(str)
@label = parse_label!(str)
Expand All @@ -63,7 +75,8 @@ def parse_factory!(string)
# parse the label, removing it if found
# @return the label or nil
def parse_label!(string)
remove_from_and_return_1st_capture!(string, /^(?: |: )?(#{match_quoted})/).try(:gsub, '"', '')
label = remove_from_and_return_1st_capture!(string, /^(?: |: )?(#{match_quoted})/)
label && label.gsub('"', '')
end

def remove_from_and_return_1st_capture!(string, regexp)
Expand All @@ -80,5 +93,11 @@ def index_word_to_i(word)
else word.gsub(/\D/,'').to_i - 1
end
end

def apply_mappings!(string)
config && config.mappings.each do |mapping|
string.sub! /^#{mapping.search}$/, mapping.replacement
end
end
end
end
209 changes: 14 additions & 195 deletions lib/pickle/session.rb
@@ -1,205 +1,24 @@
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

# a Pickle::Session holds everything together, may be included, or created as a standalone object with Pickle::Session.new
# (actually a Pickle::Session::Object)
#
# @see Pickle::Session::Api for a list of the main api methods
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
include DefaultConfig
include Adapters
include Api

# predicate version which raises no errors
def created_model?(name)
(created_model(name) rescue nil) ? true : false
def jar
@jar ||= Pickle::Jar.new
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
class Object
include Pickle::Session
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
def self.new
Pickle::Session::Object.new
end
end
end
end

0 comments on commit 1a83ba5

Please sign in to comment.