class ROM::Mapper::AttributeDSL

Mapper attribute DSL exposed by mapper subclasses

This class is private even though its methods are exposed by mappers. Typically it's not meant to be used directly.

TODO: break this madness down into smaller pieces

@api private

Attributes

attributes[R]
copy_keys[R]
options[R]
reject_keys[R]
steps[R]
symbolize_keys[R]

Public Class Methods

new(attributes, options) click to toggle source

@param [Array] attributes accumulator array @param [Hash] options

@api private

# File lib/rom/mapper/attribute_dsl.rb, line 23
def initialize(attributes, options)
  @attributes = attributes
  @options = options
  @copy_keys = options.fetch(:copy_keys)
  @symbolize_keys = options.fetch(:symbolize_keys)
  @prefix = options.fetch(:prefix)
  @prefix_separator = options.fetch(:prefix_separator)
  @reject_keys = options.fetch(:reject_keys)
  @steps = []
end

Public Instance Methods

attribute(name, options = EMPTY_HASH, &block) click to toggle source

Define a mapping attribute with its options and/or block

@example

dsl = AttributeDSL.new([])

dsl.attribute(:name)
dsl.attribute(:email, from: 'user_email')
dsl.attribute(:name) { 'John' }
dsl.attribute(:name) { |t| t.upcase }

@api public

# File lib/rom/mapper/attribute_dsl.rb, line 77
def attribute(name, options = EMPTY_HASH, &block)
  with_attr_options(name, options) do |attr_options|
    raise ArgumentError,
      "can't specify type and block at the same time" if options[:type] && block
    attr_options[:coercer] = block if block
    add_attribute(name, attr_options)
  end
end
combine(name, options, &block) click to toggle source

Define an embedded combined attribute that requires “combine” transformation

Typically this can be used to process results of eager-loading

@example

dsl = AttributeDSL.new([])

dsl.combine(:tags, user_id: :id) do
  model Tag

  attribute :name
end

@param [Symbol] name @param [Hash] options

@option options [Hash] :on The "join keys"
@option options [Symbol] :type The type, either :array (default) or :hash

@api public

# File lib/rom/mapper/attribute_dsl.rb, line 329
def combine(name, options, &block)
  dsl = new(options, &block)

  attr_opts = {
    type: options.fetch(:type, :array),
    keys: options.fetch(:on),
    combine: true,
    header: dsl.header
  }

  add_attribute(name, attr_opts)
end
embedded(name, options, &block) click to toggle source

Define an embedded attribute

Block exposes the attribute dsl too

@example

dsl = AttributeDSL.new([])

dsl.embedded :tags, type: :array do
  attribute :name
end

dsl.embedded :address, type: :hash do
  model Address
  attribute :name
end

@param [Symbol] name attribute

@param [Hash] options @option options [Symbol] :type Embedded type can be :hash or :array @option options [Symbol] :prefix Prefix that should be used for

its attributes

@api public

# File lib/rom/mapper/attribute_dsl.rb, line 128
def embedded(name, options, &block)
  with_attr_options(name) do |attr_options|
    mapper = options[:mapper]

    if mapper
      embedded_options = { type: :array }.update(options)
      attributes_from_mapper(
        mapper, name, embedded_options.update(attr_options)
      )
    else
      dsl = new(options, &block)
      attr_options.update(options)
      add_attribute(
        name, { header: dsl.header, type: :array }.update(attr_options)
      )
    end
  end
end
exclude(name) click to toggle source
# File lib/rom/mapper/attribute_dsl.rb, line 86
def exclude(name)
  attributes << [name, { exclude: true }]
end
fold(*args, &block) click to toggle source

Define an embedded hash attribute that requires “fold” transformation

Typically this is used in sql context to fold single joined field to the array of values.

@example

dsl = AttributeDSL.new([])

dsl.fold(tags: [:name])

@see AttributeDSL#embedded

@api public

# File lib/rom/mapper/attribute_dsl.rb, line 273
def fold(*args, &block)
  with_name_or_options(*args) do |name, *|
    fold_options = { type: :array, fold: true }
    dsl(name, fold_options, &block)
  end
end
group(*args, &block) click to toggle source

Define an embedded hash attribute that requires “grouping” transformation

Typically this is used in sql context when relation is a join.

@example

dsl = AttributeDSL.new([])

dsl.group(tags: [:name])

dsl.group(:tags) do
  model Tag
  attribute :name
end

@see AttributeDSL#embedded

@api public

# File lib/rom/mapper/attribute_dsl.rb, line 228
def group(*args, &block)
  ensure_mapper_configuration('group', args, block_given?)

  with_name_or_options(*args) do |name, options, mapper|
    group_options = { type: :array, group: true }.update(options)

    if mapper
      attributes_from_mapper(mapper, name, group_options)
    else
      dsl(name, group_options, &block)
    end
  end
end
header() click to toggle source

Generate a header from attribute definitions

@return [Header]

@api private

# File lib/rom/mapper/attribute_dsl.rb, line 347
def header
  Header.coerce(attributes, copy_keys: copy_keys, model: model, reject_keys: reject_keys)
end
prefix(value = Undefined) click to toggle source

Redefine the prefix for the following attributes

@example

dsl = AttributeDSL.new([])
dsl.attribute(:prefix, 'user')

@api public

# File lib/rom/mapper/attribute_dsl.rb, line 42
def prefix(value = Undefined)
  if value.equal?(Undefined)
    @prefix
  else
    @prefix = value
  end
end
prefix_separator(value = Undefined) click to toggle source

Redefine the prefix separator for the following attributes

@example

dsl = AttributeDSL.new([])
dsl.attribute(:prefix_separator, '.')

@api public

# File lib/rom/mapper/attribute_dsl.rb, line 58
def prefix_separator(value = Undefined)
  if value.equal?(Undefined)
    @prefix_separator
  else
    @prefix_separator = value
  end
end
step(options = EMPTY_HASH, &block) click to toggle source

Perform transformations sequentially

@example

dsl = AttributeDSL.new()

dsl.step do
  attribute :name
end

@api public

# File lib/rom/mapper/attribute_dsl.rb, line 100
def step(options = EMPTY_HASH, &block)
  steps << new(options, &block)
end
unfold(name, options = EMPTY_HASH) { || ... } click to toggle source

Define an embedded hash attribute that requires “unfold” transformation

Typically this is used in non-sql context to convert array of values (like in Cassandra 'SET' or 'LIST' types) to array of tuples.

Source values are assigned to the first key, the other keys being left blank.

@example

dsl = AttributeDSL.new([])

dsl.unfold(tags: [:name, :type], from: :tags_list)

dsl.unfold :tags, from: :tags_list do
  attribute :name, from: :tag_name
  attribute :type, from: :tag_type
end

@see AttributeDSL#embedded

@api public

# File lib/rom/mapper/attribute_dsl.rb, line 300
def unfold(name, options = EMPTY_HASH)
  with_attr_options(name, options) do |attr_options|
    old_name = attr_options.fetch(:from, name)
    dsl(old_name, type: :array, unfold: true) do
      attribute name, attr_options
      yield if block_given?
    end
  end
end
ungroup(*args, &block) click to toggle source

Define an embedded array attribute that requires “ungrouping” transformation

Typically this is used in non-sql context being prepared for import to sql.

@example

dsl = AttributeDSL.new([])
dsl.ungroup(tags: [:name])

@see AttributeDSL#embedded

@api public

# File lib/rom/mapper/attribute_dsl.rb, line 253
def ungroup(*args, &block)
  with_name_or_options(*args) do |name, options, *|
    ungroup_options = { type: :array, ungroup: true }.update(options)
    dsl(name, ungroup_options, &block)
  end
end
unwrap(*args, &block) click to toggle source

Define an embedded hash attribute that requires “unwrapping” transformation

Typically this is used in no-sql context to normalize data before inserting to sql gateway.

@example

dsl = AttributeDSL.new([])

dsl.unwrap(address: [:street, :zipcode, :city])

dsl.unwrap(:address) do
  attribute :street
  attribute :zipcode
  attribute :city
end

@see AttributeDSL#embedded

@api public

# File lib/rom/mapper/attribute_dsl.rb, line 199
def unwrap(*args, &block)
  with_name_or_options(*args) do |name, options, mapper|
    unwrap_options = { type: :hash, unwrap: true }.update(options)

    if mapper
      attributes_from_mapper(mapper, name, unwrap_options)
    else
      dsl(name, unwrap_options, &block)
    end
  end
end
wrap(*args, &block) click to toggle source

Define an embedded hash attribute that requires “wrapping” transformation

Typically this is used in sql context when relation is a join.

@example

dsl = AttributeDSL.new([])

dsl.wrap(address: [:street, :zipcode, :city])

dsl.wrap(:address) do
  model Address
  attribute :street
  attribute :zipcode
  attribute :city
end

@see AttributeDSL#embedded

@api public

# File lib/rom/mapper/attribute_dsl.rb, line 166
def wrap(*args, &block)
  ensure_mapper_configuration('wrap', args, block_given?)

  with_name_or_options(*args) do |name, options, mapper|
    wrap_options = { type: :hash, wrap: true }.update(options)

    if mapper
      attributes_from_mapper(mapper, name, wrap_options)
    else
      dsl(name, wrap_options, &block)
    end
  end
end

Private Instance Methods

add_attribute(name, options) click to toggle source

Add a new attribute and make sure it overrides previous definition

@api private

# File lib/rom/mapper/attribute_dsl.rb, line 447
def add_attribute(name, options)
  remove(name, name.to_s)
  attributes << [name, options]
end
attributes_from_block(name, options, &block) click to toggle source

Define attributes from a nested block

Used by embedded, wrap and group

@api private

# File lib/rom/mapper/attribute_dsl.rb, line 410
def attributes_from_block(name, options, &block)
  dsl = new(options, &block)
  header = dsl.header
  add_attribute(name, options.update(header: header))
  header.each { |attr| remove(attr.key) unless name == attr.key }
end
attributes_from_hash(hash, options) click to toggle source

Define attributes from the `name => attributes` hash syntax

Used by wrap and group

@api private

# File lib/rom/mapper/attribute_dsl.rb, line 422
def attributes_from_hash(hash, options)
  hash.each do |name, header|
    with_attr_options(name, options) do |attr_options|
      add_attribute(name, attr_options.update(header: header.zip))
      header.each { |attr| remove(attr) unless name == attr }
    end
  end
end
attributes_from_mapper(mapper, name, options) click to toggle source

Infer mapper header for an embedded attribute

@api private

# File lib/rom/mapper/attribute_dsl.rb, line 434
def attributes_from_mapper(mapper, name, options)
  if mapper.is_a?(Class)
    add_attribute(name, { header: mapper.header }.update(options))
  else
    raise(
      ArgumentError, ":mapper must be a class #{mapper.inspect}"
    )
  end
end
dsl(name_or_attrs, options, &block) click to toggle source

Create another instance of the dsl for nested definitions

This is used by embedded, wrap and group

@api private

# File lib/rom/mapper/attribute_dsl.rb, line 397
def dsl(name_or_attrs, options, &block)
  if block
    attributes_from_block(name_or_attrs, options, &block)
  else
    attributes_from_hash(name_or_attrs, options)
  end
end
ensure_mapper_configuration(method_name, args, block_present) click to toggle source

Ensure the mapping configuration isn't ambiguous

@api private

# File lib/rom/mapper/attribute_dsl.rb, line 466
def ensure_mapper_configuration(method_name, args, block_present)
  if args.first.is_a?(Hash) && block_present
    raise MapperMisconfiguredError,
          "Cannot configure `#{method_name}#` using both options and a block"
  end
  if args.first.is_a?(Hash) && args.first[:mapper]
    raise MapperMisconfiguredError,
          "Cannot configure `#{method_name}#` using both options and a mapper"
  end
end
new(options, &block) click to toggle source

Create a new dsl instance of potentially overidden options

Embedded, wrap and group can override top-level options like `prefix`

@api private

# File lib/rom/mapper/attribute_dsl.rb, line 457
def new(options, &block)
  dsl = self.class.new([], @options.merge(options))
  dsl.instance_exec(&block) unless block.nil?
  dsl
end
remove(*names) click to toggle source

Remove the attribute used somewhere else (in wrap, group, model etc.)

@api private

# File lib/rom/mapper/attribute_dsl.rb, line 356
def remove(*names)
  attributes.delete_if { |attr| names.include?(attr.first) }
end
with_attr_options(name, options = EMPTY_HASH) { |attr_options| ... } click to toggle source

Handle attribute options common for all definitions

@api private

# File lib/rom/mapper/attribute_dsl.rb, line 363
def with_attr_options(name, options = EMPTY_HASH)
  attr_options = options.dup

  if @prefix
    attr_options[:from] ||= "#{@prefix}#{@prefix_separator}#{name}"
    attr_options[:from] = attr_options[:from].to_sym if name.is_a? Symbol
  end

  if symbolize_keys
    attr_options.update(from: attr_options.fetch(:from) { name }.to_s)
  end

  yield(attr_options)
end
with_name_or_options(*args) { |name, options, options| ... } click to toggle source

Handle “name or options” syntax used by `wrap` and `group`

@api private

# File lib/rom/mapper/attribute_dsl.rb, line 381
def with_name_or_options(*args)
  name, options =
    if args.size > 1
      args
    else
      [args.first, {}]
    end

  yield(name, options, options[:mapper])
end