class ROM::Changeset::Stateful
Stateful
changesets carry data and can transform it into a different structure compatible with a persistence backend
@abstract
Constants
- EMPTY_PIPE
Default no-op pipe
Public Class Methods
Build default pipe object
This can be overridden in a custom changeset subclass
@return [Pipe]
# File lib/rom/changeset/stateful.rb, line 89 def self.default_pipe(context) !pipes.empty? ? pipes.map { |p| p.bind(context) }.reduce(:>>) : EMPTY_PIPE end
Define a changeset mapping excluded from diffs
@see Changeset::Stateful.map
@see Changeset::Stateful#extend
@return [Array<Pipe>, Transproc::Function>]
@api public
# File lib/rom/changeset/stateful.rb, line 76 def self.extend(*, &block) if block map(use_for_diff: false, &block) else super end end
@api private
# File lib/rom/changeset/stateful.rb, line 94 def self.inherited(klass) return if klass == ROM::Changeset super klass.instance_variable_set(:@__pipes__, pipes.dup) end
Define a changeset mapping
Subsequent mapping definitions will be composed together and applied in the order they way defined
@example Transformation DSL
class NewUser < ROM::Changeset::Create map do unwrap :address, prefix: true end end
@example Using custom block
class NewUser < ROM::Changeset::Create map do |tuple| tuple.merge(created_at: Time.now) end end
@example Multiple mappings (executed in the order of definition)
class NewUser < ROM::Changeset::Create map do unwrap :address, prefix: true end map do |tuple| tuple.merge(created_at: Time.now) end end
@return [Array<Pipe>, Transproc::Function>]
@see github.com/solnic/transproc Transproc
@api public
# File lib/rom/changeset/stateful.rb, line 60 def self.map(**options, &block) if block.parameters.empty? pipes << Class.new(Pipe).define!(&block).new(**options) else pipes << Pipe.new(block, **options) end end
@api private
# File lib/rom/changeset/stateful.rb, line 102 def self.pipes @__pipes__ end
Public Instance Methods
Associate a changeset with another changeset or hash-like object
@example with another changeset
new_user = users.changeset(name: 'Jane') new_task = users.changeset(:tasks, title: 'A task') new_task.associate(new_user, :users)
@example with a hash-like object
user = users.users.by_pk(1).one new_task = users.changeset(:tasks, title: 'A task') new_task.associate(user, :users)
@param [#to_hash, Changeset] other Other changeset or hash-like object @param [Symbol] name The association identifier from schema
@api public
# File lib/rom/changeset/stateful.rb, line 225 def associate(other, name = Associated.infer_assoc_name(other)) Associated.new(self, associations: { name => other }) end
@api private
ROM::Changeset#command_compiler_options
# File lib/rom/changeset/stateful.rb, line 248 def command_compiler_options super.merge(result: result) end
Commit stateful changeset
@see Changeset#commit
@api public
# File lib/rom/changeset/stateful.rb, line 203 def commit command.call(self) end
Return changeset with data
@param [Hash] data
@return [Changeset]
@api public
# File lib/rom/changeset/stateful.rb, line 170 def data(data) with(__data__: data) end
Pipe
changeset's data using custom steps define on the pipe. You should use map
instead except updating timestamp fields. Calling changeset.extend builds a pipe that excludes certain steps for generating the diff. Currently the only place where it is used is update changesets with the `:touch` step, i.e. `changeset.extend(:touch).diff` will exclude `:updated_at` from the diff.
@return [Changeset]
@api public
# File lib/rom/changeset/stateful.rb, line 151 def extend(*steps, **options, &block) if block if !steps.empty? extend(*steps, **options).extend(**options, &block) else with(pipe: pipe.compose(Pipe.new(block).bind(self), **options)) end else with(pipe: steps.reduce(pipe.with(**options)) { |a, e| a.compose(pipe[e], **options) }) end end
Return string representation of the changeset
@return [String]
@api public
# File lib/rom/changeset/stateful.rb, line 243 def inspect %(#<#{self.class} relation=#{relation.name.inspect} data=#{__data__}>) end
Pipe
changeset's data using custom steps define on the pipe
@overload map(*steps)
Apply mapping using built-in transformations @example changeset.map(:add_timestamps) @param [Array<Symbol>] steps A list of mapping steps
@overload map(&block)
Apply mapping using a custom block @example changeset.map { |tuple| tuple.merge(created_at: Time.now) }
@overload map(*steps, &block)
Apply mapping using built-in transformations and a custom block @example changeset.map(:add_timestamps) { |tuple| tuple.merge(status: 'published') } @param [Array<Symbol>] steps A list of mapping steps
@return [Changeset]
@api public
# File lib/rom/changeset/stateful.rb, line 134 def map(*steps, &block) extend(*steps, for_diff: true, &block) end
Data transformation pipe
@return [Changeset::Pipe]
@api private
# File lib/rom/changeset/stateful.rb, line 257 def pipe @pipe ||= self.class.default_pipe(self) end
Return command result type
@return [Symbol]
@api private
# File lib/rom/changeset/stateful.rb, line 234 def result __data__.is_a?(Array) ? :many : :one end
Coerce changeset to an array
This will send the data through the pipe
@return [Array]
@api public
# File lib/rom/changeset/stateful.rb, line 193 def to_a result == :one ? [to_h] : __data__.map { |element| pipe.call(element) } end
Coerce changeset to a hash
This will send the data through the pipe
@return [Hash]
@api public
# File lib/rom/changeset/stateful.rb, line 181 def to_h pipe.call(__data__) end
Private Instance Methods
@api private
# File lib/rom/changeset/stateful.rb, line 269 def method_missing(meth, *args, &block) if __data__.respond_to?(meth) response = __data__.__send__(meth, *args, &block) if response.is_a?(__data__.class) with(__data__: response) else response end else super end end
@api private
# File lib/rom/changeset/stateful.rb, line 264 def respond_to_missing?(meth, include_private = false) super || __data__.respond_to?(meth) end