module ViewModel::Callbacks

Callback hooks for viewmodel traversal contexts

Constants

ALWAYS

Placeholder for callbacks to invoked for all view types

DeserializeHookControl

Record changes made in the deserialization block so that they can be provided to the AfterDeserialize hook.

Public Class Methods

create(callbacks, view, context) click to toggle source
# File lib/view_model/callbacks.rb, line 82
def self.create(callbacks, view, context) # rubocop:disable Lint/NestedMethodDefinition
  self.new(callbacks, view, context)
end
wrap_deserialize(viewmodel, deserialize_context:) { |hook_control| ... } click to toggle source
# File lib/view_model/callbacks.rb, line 200
def self.wrap_deserialize(viewmodel, deserialize_context:)
  hook_control = DeserializeHookControl.new

  wrap_serialize(viewmodel, context: deserialize_context) do
    deserialize_context.run_callback(ViewModel::Callbacks::Hook::BeforeDeserialize,
                                     viewmodel)

    val = yield(hook_control)

    if hook_control.changes.nil?
      raise ViewModel::DeserializationError::Internal.new(
              'Internal error: changes not recorded for deserialization of viewmodel',
              viewmodel.blame_reference)
    end

    deserialize_context.run_callback(ViewModel::Callbacks::Hook::AfterDeserialize,
                                     viewmodel,
                                     changes: hook_control.changes)
    val
  end
end
wrap_serialize(viewmodel, context:) { || ... } click to toggle source
# File lib/view_model/callbacks.rb, line 187
def self.wrap_serialize(viewmodel, context:)
  context.run_callback(ViewModel::Callbacks::Hook::BeforeVisit, viewmodel)
  val = yield
  context.run_callback(ViewModel::Callbacks::Hook::AfterVisit, viewmodel)
  val
end

Public Instance Methods

add_callback(hook, view_name, &block) click to toggle source
# File lib/view_model/callbacks.rb, line 154
def add_callback(hook, view_name, &block)
  valid_hook!(hook)

  hook_callbacks = (class_callbacks[hook] ||= {})
  view_callbacks = (hook_callbacks[view_name.to_s] ||= [])
  view_callbacks << block
end
dsl_add_hook_name() click to toggle source
# File lib/view_model/callbacks.rb, line 89
def dsl_add_hook_name
  name.underscore
end
dsl_viewmodel_callback_method() click to toggle source
# File lib/view_model/callbacks.rb, line 93
def dsl_viewmodel_callback_method
  name.underscore.to_sym
end
each_callback(hook, view_name) { |c| ... } click to toggle source
# File lib/view_model/callbacks.rb, line 132
def each_callback(hook, view_name)
  valid_hook!(hook)
  return to_enum(__method__, hook, view_name) unless block_given?

  all_callbacks do |callbacks|
    if (hook_callbacks = callbacks[hook])
      hook_callbacks[view_name.to_s]&.each { |c| yield(c) }
      hook_callbacks[ALWAYS]&.each { |c| yield(c) }
    end
  end
end
ineligible(view) click to toggle source
# File lib/view_model/callbacks.rb, line 180
def ineligible(view)
  # ARVM synthetic views are considered part of their association and as such
  # are not visited by callbacks. Eligibility exclusion is intended to be
  # library-internal: subclasses should not attempt to extend this.
  view.is_a?(ViewModel) && view.class.synthetic
end
inherited(subclass) click to toggle source
Calls superclass method
# File lib/view_model/callbacks.rb, line 114
def inherited(subclass)
  subclass_callbacks = {}
  subclass.define_singleton_method(:class_callbacks) { subclass_callbacks }
  subclass.define_singleton_method(:all_callbacks) do |&block|
    return to_enum(__method__) unless block

    super(&block)
    block.call(subclass_callbacks)
  end
end
init(context_name, *other_params) click to toggle source
# File lib/view_model/callbacks.rb, line 60
    def init(context_name, *other_params)
      @context_name    = context_name
      @required_params = other_params
      @env_class = Value.new(:_callbacks, :view, context_name, *other_params) do
        include CallbackEnvContext
        delegate :model, to: :view

        unless context_name == :context
          alias_method :context, context_name
        end

        # If we have any other params, generate a combined positional/keyword
        # constructor wrapper
        if other_params.present?
          params = other_params.map { |x| "#{x}:" }.join(', ')
          args   = other_params.join(', ')
          instance_eval(<<-SRC, __FILE__, __LINE__ + 1)
            def create(callbacks, view, context, #{params})
              self.new(callbacks, view, context, #{args})
            end
          SRC
        else
          def self.create(callbacks, view, context) # rubocop:disable Lint/NestedMethodDefinition
            self.new(callbacks, view, context)
          end
        end
      end
    end
run_callback(hook, view, context, **args) click to toggle source
# File lib/view_model/callbacks.rb, line 169
def run_callback(hook, view, context, **args)
  return if ineligible(view)

  callback_env = hook.env_class.create(self, view, context, **args)

  view_name = view.class.view_name
  self.class.each_callback(hook, view_name) do |callback|
    callback_env.instance_exec(&callback)
  end
end
updates_view!() click to toggle source
# File lib/view_model/callbacks.rb, line 150
def updates_view!
  define_singleton_method(:updates_view?) { true }
end
updates_view?() click to toggle source
# File lib/view_model/callbacks.rb, line 144
def updates_view?
  false
end
valid_hook!(hook) click to toggle source
# File lib/view_model/callbacks.rb, line 162
def valid_hook!(hook)
  unless hook.is_a?(Hook)
    raise ArgumentError.new("Invalid hook: '#{hook}'")
  end
end