module CucumberFactory::Factory

Constants

ATTRIBUTES_PATTERN
CLEAR_NAMED_RECORDS_STEP_DESCRIPTOR
CREATION_STEP_DESCRIPTOR
CREATION_STEP_DESCRIPTOR_WITH_TEXT_ATTRIBUTES
NAMED_CREATION_STEP_DESCRIPTOR

We cannot use vararg blocks in the descriptors in Ruby 1.8, as explained by Aslak: www.ruby-forum.com/topic/182927. We use different descriptors and cucumber priority to work around it.

NAMED_CREATION_STEP_DESCRIPTOR_WITH_TEXT_ATTRIBUTES
NAMED_RECORDS_VARIABLE
NAMED_RECORD_PATTERN
RECORD_PATTERN
RECORD_UPDATE_PATTERN
TEXT_ATTRIBUTES_PATTERN
TEXT_UPDATE_ATTR_PATTERN
UPDATE_ATTR_PATTERN
UPDATE_STEP_DESCRIPTOR
UPDATE_STEP_DESCRIPTOR_WITH_TEXT_ATTRIBUTES
VALUE_ARRAY
VALUE_DECIMAL
VALUE_FILE
VALUE_INTEGER
VALUE_LAST_RECORD
VALUE_SCALAR
VALUE_STRING

Public Class Methods

add_steps(main) click to toggle source
# File lib/cucumber_factory/factory.rb, line 75
def add_steps(main)
  add_step(main, CREATION_STEP_DESCRIPTOR)
  add_step(main, NAMED_CREATION_STEP_DESCRIPTOR)
  add_step(main, CREATION_STEP_DESCRIPTOR_WITH_TEXT_ATTRIBUTES)
  add_step(main, NAMED_CREATION_STEP_DESCRIPTOR_WITH_TEXT_ATTRIBUTES)
  add_step(main, CLEAR_NAMED_RECORDS_STEP_DESCRIPTOR)
  add_step(main, UPDATE_STEP_DESCRIPTOR)
  add_step(main, UPDATE_STEP_DESCRIPTOR_WITH_TEXT_ATTRIBUTES)
end

Private Class Methods

add_step(main, descriptor) click to toggle source
# File lib/cucumber_factory/factory.rb, line 87
def add_step(main, descriptor)
  main.instance_eval {
    kind = descriptor[:kind]
    object = send(kind, *[descriptor[:pattern]].compact, &descriptor[:block])
    # cucumber_factory steps get a low priority due to their generic syntax
    object.overridable(:priority => descriptor[:priority] ? -1 : -2) if kind != :Before
    object
  }
end
attribute_name_from_prose(prose) click to toggle source
# File lib/cucumber_factory/factory.rb, line 286
def attribute_name_from_prose(prose)
  prose.downcase.gsub(' ', '_').to_sym
end
attribute_value(world, model_class, transient_attributes, attribute, value) click to toggle source
# File lib/cucumber_factory/factory.rb, line 182
def attribute_value(world, model_class, transient_attributes, attribute, value)
  associated, association_class = resolve_association(attribute, model_class, transient_attributes)

  value = if matches_fully?(value, VALUE_ARRAY)
    array_values = unquote(value).scan(VALUE_SCALAR)
    array_values.map { |v| attribute_value(world, model_class, transient_attributes, attribute, v) }
  elsif associated
    resolve_associated_value(world, model_class, association_class, attribute, value)
  else
    resolve_scalar_value(world, model_class, attribute, value)
  end
  value
end
file_value_to_path(string) click to toggle source
# File lib/cucumber_factory/factory.rb, line 259
def file_value_to_path(string)
  # file paths are marked with a special keyword and enclosed with quotes.
  # Example: file:"/path/image.png"
  # This will extract the path (/path/image.png) from the text fragment above
  unquote string.sub(/\Afile:/, '')
end
full_regexp(partial_regexp) click to toggle source
# File lib/cucumber_factory/factory.rb, line 266
def full_regexp(partial_regexp)
  Regexp.new('\\A(?:' + partial_regexp.source + ')\\z', partial_regexp.options)
end
get_named_record(world, name) click to toggle source
# File lib/cucumber_factory/factory.rb, line 106
def get_named_record(world, name)
  named_records(world)[name].tap do |record|
    record.reload if record.respond_to?(:reload) and record.respond_to?(:new_record?) and not record.new_record?
  end
end
matches_fully?(string, partial_regexp) click to toggle source
# File lib/cucumber_factory/factory.rb, line 270
def matches_fully?(string, partial_regexp)
  string =~ full_regexp(partial_regexp)
end
named_records(world) click to toggle source
# File lib/cucumber_factory/factory.rb, line 101
def named_records(world)
  hash = world.instance_variable_get(NAMED_RECORDS_VARIABLE)
  hash || reset_named_records(world)
end
parse_attributes(world, model_class, raw_attributes, raw_boolean_attributes, raw_multiline_attributes = nil, transient_attributes = []) click to toggle source
# File lib/cucumber_factory/factory.rb, line 140
def parse_attributes(world, model_class, raw_attributes, raw_boolean_attributes, raw_multiline_attributes = nil, transient_attributes = [])
  attributes = {}
  if raw_attributes.try(:strip).present?
    raw_attribute_fragment_regex = /(?:the |and |with |but |,| )+(.*?) (#{VALUE_SCALAR}|#{VALUE_ARRAY}|#{VALUE_LAST_RECORD})/
    raw_attributes.scan(raw_attribute_fragment_regex).each do |fragment|
      attribute = attribute_name_from_prose(fragment[0])
      value = fragment[1]
      attributes[attribute] = attribute_value(world, model_class, transient_attributes, attribute, value)
    end
    unused_raw_attributes = raw_attributes.gsub(raw_attribute_fragment_regex, '')
    if unused_raw_attributes.present?
      raise ArgumentError, "Unable to parse attributes #{unused_raw_attributes.inspect}."
    end
  end
  if raw_boolean_attributes.try(:strip).present?
    raw_boolean_attributes.scan(/(?:which|who|that|is| )*(not )?(.+?)(?: and | but |,|$)+/).each do |fragment|
      flag = !fragment[0] # if the word 'not' didn't match above, this expression is true
      attribute = attribute_name_from_prose(fragment[1])
      attributes[attribute] = flag
    end
  end
  if raw_multiline_attributes.present?
    # DocString e.g. "first name: Jane\nlast name: Jenny\n"
    if raw_multiline_attributes.is_a?(String)
      raw_multiline_attributes.split("\n").each do |fragment|
        raw_attribute, value = fragment.split(': ', 2)
        attribute = attribute_name_from_prose(raw_attribute)
        value = "\"#{value}\"" unless matches_fully?(value, /#{VALUE_ARRAY}|#{VALUE_FILE}/)
        attributes[attribute] = attribute_value(world, model_class, transient_attributes, attribute, value)
      end
    # DataTable e.g. in raw [["first name", "Jane"], ["last name", "Jenny"]]
    else
      raw_multiline_attributes.raw.each do |raw_attribute, value|
        attribute = attribute_name_from_prose(raw_attribute)
        value = "\"#{value}\"" unless matches_fully?(value, /#{VALUE_ARRAY}|#{VALUE_FILE}/)
        attributes[attribute] = attribute_value(world, model_class, transient_attributes, attribute, value)
      end
    end
  end
  attributes
end
parse_creation(world, raw_model, raw_variant, raw_attributes, raw_boolean_attributes, raw_multiline_attributes = nil) click to toggle source
# File lib/cucumber_factory/factory.rb, line 122
def parse_creation(world, raw_model, raw_variant, raw_attributes, raw_boolean_attributes, raw_multiline_attributes = nil)
  build_strategy, transient_attributes = CucumberFactory::BuildStrategy.from_prose(raw_model, raw_variant)
  model_class = build_strategy.model_class
  attributes = parse_attributes(world, model_class, raw_attributes, raw_boolean_attributes, raw_multiline_attributes, transient_attributes)
  record = build_strategy.create_record(attributes)
  remember_record_names(world, record, attributes)
  record
end
parse_named_creation(world, double_quote_name, single_quote_name, raw_model, raw_variant, raw_attributes, raw_boolean_attributes, raw_multiline_attributes = nil) click to toggle source
# File lib/cucumber_factory/factory.rb, line 116
def parse_named_creation(world, double_quote_name, single_quote_name, raw_model, raw_variant, raw_attributes, raw_boolean_attributes, raw_multiline_attributes = nil)
  record = parse_creation(world, raw_model, raw_variant, raw_attributes, raw_boolean_attributes, raw_multiline_attributes)
  name = [double_quote_name, single_quote_name].compact.first
  set_named_record(world, name, record)
end
parse_update(world, raw_model, raw_name, raw_attributes, raw_boolean_attributes, raw_multiline_attributes = nil) click to toggle source
# File lib/cucumber_factory/factory.rb, line 131
def parse_update(world, raw_model, raw_name, raw_attributes, raw_boolean_attributes, raw_multiline_attributes = nil)
  model_class = CucumberFactory::BuildStrategy.parse_model_class(raw_model)
  attributes = parse_attributes(world, model_class, raw_attributes, raw_boolean_attributes, raw_multiline_attributes)
  record = resolve_associated_value(world, model_class, model_class, model_class, raw_name)
  CucumberFactory::UpdateStrategy.new(record).assign_attributes(attributes)
  remember_record_names(world, record, attributes)
  record
end
remember_record_names(world, record, attributes) click to toggle source
# File lib/cucumber_factory/factory.rb, line 290
def remember_record_names(world, record, attributes)
  rememberable_values = attributes.values.select { |v| v.is_a?(String) || v.is_a?(Integer) }
  for value in rememberable_values
    set_named_record(world, value.to_s, record)
  end
end
reset_named_records(world) click to toggle source
# File lib/cucumber_factory/factory.rb, line 97
def reset_named_records(world)
  world.instance_variable_set(NAMED_RECORDS_VARIABLE, {})
end
resolve_associated_value(world, model_class, association_class, attribute, value) click to toggle source
# File lib/cucumber_factory/factory.rb, line 220
def resolve_associated_value(world, model_class, association_class, attribute, value)
  if matches_fully?(value, VALUE_LAST_RECORD)
    raise(Error, "Cannot set last #{model_class}##{attribute} for polymorphic associations") unless association_class.present?

    CucumberFactory::Switcher.find_last(association_class) || raise(Error, "There is no last #{attribute}")
  elsif matches_fully?(value, VALUE_STRING)
    value = unquote(value)
    get_named_record(world, value) || transform_value(world, value)
  elsif matches_fully?(value, VALUE_INTEGER)
    value = value.to_s
    get_named_record(world, value) || transform_value(world, value)
  else
    raise Error, "Cannot set association #{model_class}##{attribute} to #{value}."
  end
end
resolve_association(attribute, model_class, transient_attributes) click to toggle source
# File lib/cucumber_factory/factory.rb, line 196
def resolve_association(attribute, model_class, transient_attributes)
  return unless model_class.respond_to?(:reflect_on_association)

  association = model_class.reflect_on_association(attribute)
  association_class = nil

  if association
    association_class = association.klass unless association.polymorphic?
    associated = true
  elsif transient_attributes.include?(attribute.to_sym)
    klass_name = attribute.to_s.camelize
    if Object.const_defined?(klass_name)
      association_class = klass_name.constantize
      associated = true
    elsif (factory_class = CucumberFactory::BuildStrategy.class_from_factory(attribute.to_s))
      association_class = factory_class
      associated = true
    end
  else
    associated = false
  end
  [associated, association_class]
end
resolve_scalar_value(world, model_class, attribute, value) click to toggle source
# File lib/cucumber_factory/factory.rb, line 236
def resolve_scalar_value(world, model_class, attribute, value)
  if matches_fully?(value, VALUE_STRING)
    value = unquote(value)
    value = transform_value(world, value)
  elsif matches_fully?(value, VALUE_INTEGER)
    value = value.to_i
  elsif matches_fully?(value, VALUE_DECIMAL)
    value = BigDecimal(value)
  elsif matches_fully?(value, VALUE_FILE)
    path = File.path("./#{file_value_to_path(value)}")
    value = File.new(path)
  else
    raise Error, "Cannot set attribute #{model_class}##{attribute} to #{value}."
  end
  value
end
set_named_record(world, name, record) click to toggle source
# File lib/cucumber_factory/factory.rb, line 112
def set_named_record(world, name, record)
  named_records(world)[name] = record
end
transform_value(world, value) click to toggle source
# File lib/cucumber_factory/factory.rb, line 274
def transform_value(world, value)
  # Transforms were a feature available in Cucumber 1 and 2.
  # They have been kind-of replaced by ParameterTypes, which don't work with generic steps
  # like CucumberFactory's.
  # https://app.cucumber.pro/projects/cucumber-ruby/documents/branch/master/features/docs/writing_support_code/parameter_types.feature
  if world.respond_to?(:Transform)
    world.Transform(value)
  else
    value
  end
end
unquote(string) click to toggle source
# File lib/cucumber_factory/factory.rb, line 253
def unquote(string)
  # This method removes quotes or brackets from the start and end from a string
  # Examples: 'single' => single, "double" => double, [1, 2, 3] => 1, 2, 3
  string[1, string.length - 2]
end