lib/chop/create.rb
require "active_support/core_ext/string/inflections"
require "active_support/core_ext/object/blank"
require "active_support/hash_with_indifferent_access"
require "active_support/core_ext/class/attribute"
module Chop
class Create < Struct.new(:klass, :table, :block)
def self.create! klass, table, &block
new(klass, table, block).create!
end
class_attribute :creation_strategies
self.creation_strategies = {}
def self.register_creation_strategy key, &block
creation_strategies[key] = block
end
register_creation_strategy nil do |klass, attributes|
klass.create! attributes
end
register_creation_strategy :factory_girl do |factory, attributes|
FactoryGirl.create factory, attributes
end
register_creation_strategy :factory_bot do |factory, attributes|
FactoryBot.create factory, attributes
end
attr_accessor :transformations, :deferred_attributes, :after_hooks
def initialize(*, &other_block)
super
self.transformations = []
self.deferred_attributes = HashWithIndifferentAccess.new
self.after_hooks = []
instance_eval &block if block.respond_to?(:call)
instance_eval &other_block if block_given?
end
def create! cucumber_table = table
cucumber_table.hashes.map do |attributes|
attributes = HashWithIndifferentAccess.new(attributes)
attributes = transformations.reduce(attributes) do |attrs, transformation|
transformation.call(attrs)
end
strategy, factory = klass.is_a?(Hash) ? klass.to_a.first : [nil, klass]
args = [factory, attributes]
record = creation_strategies[strategy].call(*args.compact)
after_hooks.each do |after_hook|
after_hook.call(record, attributes.merge(deferred_attributes))
end
record
end
end
def create &block
self.creation_strategies = Proc.new { block }
end
def transformation &block
transformations << block
end
def delete *keys
transformation do |attributes|
keys.reduce(attributes) do |attrs, key|
attributes.delete(key)
attributes
end
end
end
def copy mappings
transformation do |attributes|
mappings.each do |from, to|
attributes[to] = attributes[from]
end
attributes
end
end
def rename mappings
transformation do |attributes|
mappings.reduce(attributes) do |attrs, (old, new)|
attrs[new] = attrs.delete(old) if attrs.key?(old)
attrs
end
end
end
def field attribute, default: ""
if attribute.is_a?(Hash)
rename attribute
attribute = attribute.values.first
end
transformation do |attributes|
attributes.merge attribute => yield(attributes.fetch(attribute, default))
end
end
def default key, default_value = nil
field(key, default: nil) { |value| value || default_value || yield }
end
def underscore_keys
transformation do |attributes|
attributes.reduce(HashWithIndifferentAccess.new) do |hash, (key, value)|
hash.merge key.parameterize.underscore => value
end
end
end
def file *keys
options = extract_options!(keys)
path = options.fetch(:path, "features/support/fixtures")
upload = options.fetch(:upload, false)
handle_renames! keys
keys.each do |key|
field key do |file|
if file.present?
file_path = File.join(path, file)
if upload
Rack::Test::UploadedFile.new(file_path)
else
File.open(file_path)
end
end
end
end
end
def files *keys
options = extract_options!(keys)
path = options.fetch(:path, "features/support/fixtures")
upload = options.fetch(:upload, false)
delimiter = options.fetch(:delimiter, " ")
handle_renames! keys
keys.each do |key|
field key do |paths|
paths.split(delimiter).map do |file|
file_path = File.join(path, file)
if upload
Rack::Test::UploadedFile.new(file_path)
else
File.open(file_path)
end
end
end
end
end
def has_many key, klass=nil, delimiter: ", ", name_field: :name
klass ||= key.to_s.classify.constantize
field key do |names|
names.split(delimiter).map do |name|
klass.find_by!(name_field => name)
end
end
end
def has_one key, klass=nil, name_field: :name
klass ||= key.to_s.classify.constantize
field key do |name|
klass.find_by!(name_field => name) if name.present?
end
end
alias_method :belongs_to, :has_one
def after *keys, &block
defer *keys
after_hooks << block
end
def defer *keys
transformation do |attributes|
keys.each do |key|
self.deferred_attributes[key] = attributes.delete(key)
end
attributes
end
end
private
def extract_options! keys
if keys.last.is_a?(Hash) && keys.length > 1
keys.pop
else
{}
end
end
def handle_renames! keys
if keys.first.is_a?(Hash) && keys.length == 1
rename keys.first
keys.replace keys.first.values
end
end
end
end