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
@api private
# File lib/rom/command_compiler.rb, line 21 def self.registry Hash.new { |h, k| h[k] = {} } end
Public Instance Methods
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
@api private
# File lib/rom/command_compiler.rb, line 116 def type @_type ||= Commands.const_get(Inflector.classify(id))[adapter] rescue NameError nil end
@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
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
Return default result type
@return [Symbol]
@api private
# File lib/rom/command_compiler.rb, line 225 def result meta.fetch(:result, :one) end
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
@api private
# File lib/rom/command_compiler.rb, line 172 def visit_attribute(*_args) nil end
@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