lib/rom/repository/changeset/associated.rb
require 'rom/initializer'
module ROM
class Changeset
# Associated changesets automatically set up FKs
#
# @api public
class Associated
extend Initializer
# @!attribute [r] left
# @return [Changeset::Create] Child changeset
param :left
# @!attribute [r] associations
# @return [Array] List of association identifiers from relation schema
option :associations
# Infer association name from an object with a schema
#
# This expects other to be an object with a schema that includes a primary key
# attribute with :source meta information. This makes it work with both structs
# and relations
#
# @see Stateful#associate
#
# @api private
def self.infer_assoc_name(other)
schema = other.class.schema
attrs = schema.is_a?(Hash) ? schema.values : schema
pk = attrs.detect { |attr| attr.meta[:primary_key] }
if pk
pk.meta[:source].relation
else
raise ArgumentError, "can't infer association name for #{other}"
end
end
# Commit changeset's composite command
#
# @example
# task_changeset = task_repo.
# changeset(title: 'Task One').
# associate(user, :user).
# commit
# # {:id => 1, :user_id => 1, title: 'Task One'}
#
# @return [Array<Hash>, Hash]
#
# @api public
def commit
command.call
end
# @api public
def associate(other, name = Associated.infer_assoc_name(other))
self.class.new(left, associations: associations.merge(name => other))
end
# Create a composed command
#
# @example using existing parent data
# user_changeset = user_repo.changeset(name: 'Jane')
# task_changeset = task_repo.changeset(title: 'Task One')
#
# user = user_repo.create(user_changeset)
# task = task_repo.create(task_changeset.associate(user, :user))
#
# @example saving both parent and child in one go
# user_changeset = user_repo.changeset(name: 'Jane')
# task_changeset = task_repo.changeset(title: 'Task One')
#
# task = task_repo.create(task_changeset.associate(user, :user))
#
# This works *only* with parent => child(ren) changeset hierarchy
#
# @return [ROM::Command::Composite]
#
# @api public
def command
associations.reduce(left.command.curry(left)) do |a, (assoc, other)|
case other
when Changeset
a >> other.command.with_association(assoc).curry(other)
when Associated
a >> other.command.with_association(assoc)
else
a.with_association(assoc, parent: other)
end
end
end
# @api private
def relation
left.relation
end
end
end
end