lib/rom/sql/plugin/associates.rb in rom-sql-1.1.2 vs lib/rom/sql/plugin/associates.rb in rom-sql-1.2.0
- old
+ new
@@ -18,10 +18,38 @@
relation: command.relation,
name: assoc_name }
end
end
+ class AssociateOptions
+ attr_reader :name, :assoc, :opts
+
+ def initialize(name, relation, opts)
+ @name = name
+ @opts = { assoc: name, keys: opts[:key] }
+
+ relation.associations.try(name) do |assoc|
+ @assoc = assoc
+ @opts.update(assoc: assoc, keys: assoc.join_keys(relation.__registry__))
+ end
+
+ @opts.update(parent: opts[:parent]) if opts[:parent]
+ end
+
+ def after?
+ assoc.is_a?(Association::ManyToMany)
+ end
+
+ def ensure_valid(command)
+ raise MissingJoinKeysError.new(command, name) unless opts[:keys]
+ end
+
+ def to_hash
+ { associate: opts }
+ end
+ end
+
# @api private
def self.included(klass)
klass.class_eval do
extend ClassMethods
include InstanceMethods
@@ -33,20 +61,76 @@
option :configured_associations, reader: true, optional: true, default: proc { [] }
end
super
end
+ module ClassMethods
+ # @see ROM::Command::ClassInterface.build
+ #
+ # @api public
+ def build(relation, options = EMPTY_HASH)
+ command = super
+
+ configured_assocs = command.configured_associations
+
+ associate_options = command.associations.map { |(name, opts)|
+ next if configured_assocs.include?(name)
+ AssociateOptions.new(name, relation, opts)
+ }.compact
+
+ associate_options.each { |opts| opts.ensure_valid(self) }
+
+ before_hooks = associate_options.reject(&:after?).map(&:to_hash)
+ after_hooks = associate_options.select(&:after?).map(&:to_hash)
+
+ command.
+ with_opts(configured_associations: configured_assocs + associate_options.map(&:name)).
+ before(*before_hooks).
+ after(*after_hooks)
+ end
+
+ # Set command to associate tuples with a parent tuple using provided keys
+ #
+ # @example
+ # class CreateTask < ROM::Commands::Create[:sql]
+ # relation :tasks
+ # associates :user, key: [:user_id, :id]
+ # end
+ #
+ # create_user = rom.command(:user).create.with(name: 'Jane')
+ #
+ # create_tasks = rom.command(:tasks).create
+ # .with [{ title: 'One' }, { title: 'Two' } ]
+ #
+ # command = create_user >> create_tasks
+ # command.call
+ #
+ # @param [Symbol] name The name of associated table
+ # @param [Hash] options The options
+ # @option options [Array] :key The association keys
+ #
+ # @api public
+ def associates(name, options = EMPTY_HASH)
+ if associations.key?(name)
+ raise ArgumentError,
+ "#{name} association is already defined for #{self.class}"
+ end
+
+ associations(associations.merge(name => options))
+ end
+ end
+
module InstanceMethods
# Set fk on tuples from parent tuple
#
# @param [Array<Hash>, Hash] tuples The input tuple(s)
# @param [Hash] parent The parent tuple with its pk already set
#
# @return [Array<Hash>]
#
# @api public
- def associate(tuples, parent, assoc:, keys:)
+ def associate(tuples, curried_parent = nil, assoc:, keys:, parent: curried_parent)
result_type = result
output_tuples =
case assoc
when Symbol
@@ -88,98 +172,12 @@
self.class.build(
relation, options.merge(associations: associations.merge(name => opts))
)
end
- def associations_configured?
- if configured_associations.empty?
- false
- else
- configured_associations.all? { |name| associations.key?(name) }
- end
- end
-
# @api private
def __registry__
relation.__registry__
- end
- end
-
- module ClassMethods
- # @see ROM::Command::ClassInterface.build
- #
- # @api public
- def build(relation, options = EMPTY_HASH)
- command = super
-
- if command.associations_configured?
- return command
- end
-
- associations = command.associations
- assoc_names = []
-
- before_hooks = associations.each_with_object([]) do |(name, opts), acc|
- relation.associations.try(name) do |assoc|
- unless assoc.is_a?(Association::ManyToMany)
- acc << { associate: { assoc: assoc, keys: assoc.join_keys(relation.__registry__) } }
- else
- true
- end
- end or acc << { associate: { assoc: name, keys: opts[:key] } }
-
- assoc_names << name
- end
-
- after_hooks = associations.each_with_object([]) do |(name, opts), acc|
- next unless relation.associations.key?(name)
-
- assoc = relation.associations[name]
-
- if assoc.is_a?(Association::ManyToMany)
- acc << { associate: { assoc: assoc, keys: assoc.join_keys(relation.__registry__) } }
- assoc_names << name
- end
- end
-
- [*before_hooks, *after_hooks].
- map { |hook| hook[:associate] }.
- each { |conf| raise MissingJoinKeysError.new(self, conf[:assoc]) unless conf[:keys] }
-
- command.
- with_opts(configured_associations: assoc_names).
- before(*before_hooks).
- after(*after_hooks)
- end
-
- # Set command to associate tuples with a parent tuple using provided keys
- #
- # @example
- # class CreateTask < ROM::Commands::Create[:sql]
- # relation :tasks
- # associates :user, key: [:user_id, :id]
- # end
- #
- # create_user = rom.command(:user).create.with(name: 'Jane')
- #
- # create_tasks = rom.command(:tasks).create
- # .with [{ title: 'One' }, { title: 'Two' } ]
- #
- # command = create_user >> create_tasks
- # command.call
- #
- # @param [Symbol] name The name of associated table
- # @param [Hash] options The options
- # @option options [Array] :key The association keys
- #
- # @api public
- def associates(name, options = EMPTY_HASH)
- if associations.key?(name)
- raise ArgumentError,
- "#{name} association is already defined for #{self.class}"
- end
-
- associations(associations.merge(name => options))
end
end
end
end
end