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
Public Class Methods
@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
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
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
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
# File lib/rom/mapper/attribute_dsl.rb, line 86 def exclude(name) attributes << [name, { exclude: true }] end
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])
@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
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
@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
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
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
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
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
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
@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
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])
@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
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
@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
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
@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 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
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
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
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
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 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
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 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
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
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