class ROM::CommandCompiler

Builds commands for relations.

This class is used by repositories to automatically create commands for their relations. This is used both by `Repository#command` method and `commands` repository class macros.

@api private

Public Class Methods

registry() click to toggle source

@api private

# File lib/rom/command_compiler.rb, line 21
def self.registry
  Hash.new { |h, k| h[k] = {} }
end

Public Instance Methods

[](*args)
Alias for: call
call(*args) click to toggle source

Return a specific command type for a given adapter and relation AST

This class holds its own registry where all generated commands are being stored

CommandProxy is returned for complex command graphs as they expect root relation name to be present in the input, which we don't want to have in repositories. It might be worth looking into removing this requirement from rom core Command::Graph API.

@overload [](type, adapter, ast, plugins, meta)

@param type [Symbol] The type of command
@param adapter [Symbol] The adapter identifier
@param ast [Array] The AST representation of a relation
@param plugins [Array<Symbol>] A list of optional command plugins that should be used
@param meta [Hash] Meta data for a command

@return [Command, CommandProxy]

@api private

# File lib/rom/command_compiler.rb, line 89
def call(*args)
  cache.fetch_or_store(args.hash) do
    type, adapter, ast, plugins, plugins_options, meta = args

    compiler = with(
      id: type,
      adapter: adapter,
      plugins: Array(plugins),
      plugins_options: plugins_options,
      meta: meta
    )

    graph_opts = compiler.visit(ast)
    command = ROM::Commands::Graph.build(registry, graph_opts)

    if command.graph?
      CommandProxy.new(command)
    elsif command.lazy?
      command.unwrap
    else
      command
    end
  end
end
Also aliased as: []
type() click to toggle source

@api private

# File lib/rom/command_compiler.rb, line 116
def type
  @_type ||= Commands.const_get(Inflector.classify(id))[adapter]
rescue NameError
  nil
end
visit(ast, *args) click to toggle source

@api private

# File lib/rom/command_compiler.rb, line 123
def visit(ast, *args)
  name, node = ast
  __send__(:"visit_#{name}", node, *args)
end

Private Instance Methods

register_command(rel_name, type, rel_meta, parent_relation = nil) click to toggle source

Build a command object for a specific relation

The command will be prepared for handling associations if it's a combined relation. Additional plugins will be enabled if they are configured for this compiler.

@param [Symbol] rel_name A relation identifier from the container registry @param [Symbol] type The command type @param [Hash] rel_meta Meta information from relation AST @param [Symbol] parent_relation Optional parent relation identifier

@return [ROM::Command]

@api private

# File lib/rom/command_compiler.rb, line 190
def register_command(rel_name, type, rel_meta, parent_relation = nil)
  relation = relations[rel_name]

  type.create_class(rel_name, type) do |klass|
    klass.result(rel_meta.fetch(:combine_type, result))

    meta.each do |name, value|
      klass.public_send(name, value)
    end

    setup_associates(klass, relation, rel_meta, parent_relation) if rel_meta[:combine_type]

    plugins.each do |plugin|
      plugin_options = plugins_options.fetch(plugin) { EMPTY_HASH }
      klass.use(plugin, **plugin_options)
    end

    gateway = gateways[relation.gateway]

    notifications.trigger(
      'configuration.commands.class.before_build',
      command: klass, gateway: gateway, dataset: relation.dataset, adapter: adapter
    )

    klass.extend_for_relation(relation) if klass.restrictable

    registry[rel_name][type] = klass.build(relation)
  end
end
result() click to toggle source

Return default result type

@return [Symbol]

@api private

# File lib/rom/command_compiler.rb, line 225
def result
  meta.fetch(:result, :one)
end
setup_associates(klass, relation, _meta, parent_relation) click to toggle source

Sets up `associates` plugin for a given command class and relation

@param [Class] klass The command class @param [Relation] relation The relation for the command

@api private

# File lib/rom/command_compiler.rb, line 235
def setup_associates(klass, relation, _meta, parent_relation)
  assoc_name =
    if relation.associations.key?(parent_relation)
      parent_relation
    else
      singular_name = Inflector.singularize(parent_relation).to_sym
      singular_name if relation.associations.key?(singular_name)
    end

  if assoc_name
    klass.associates(assoc_name)
  else
    klass.associates(parent_relation)
  end
end
visit_attribute(*_args) click to toggle source

@api private

# File lib/rom/command_compiler.rb, line 172
def visit_attribute(*_args)
  nil
end
visit_relation(node, parent_relation = nil) click to toggle source

@api private

# File lib/rom/command_compiler.rb, line 131
def visit_relation(node, parent_relation = nil)
  name, header, meta = node
  other = header.map { |attr| visit(attr, name) }.compact

  if type
    register_command(name, type, meta, parent_relation)

    default_mapping =
      if meta[:combine_type] == :many
        name
      else
        { Inflector.singularize(name).to_sym => name }
      end

    mapping =
      if parent_relation
        associations = relations[parent_relation].associations

        assoc = associations[meta[:combine_name]]

        if assoc
          { assoc.key => assoc.target.name.to_sym }
        else
          default_mapping
        end
      else
        default_mapping
      end

    if !other.empty?
      [mapping, [type, other]]
    else
      [mapping, type]
    end
  else
    registry[name][id] = commands[name][id]
    [name, id]
  end
end