module ViewModel::Callbacks
def self.create(callbacks, view, context) # rubocop:disable Lint/NestedMethodDefinition
def self.create(callbacks, view, context) # rubocop:disable Lint/NestedMethodDefinition self.new(callbacks, view, context) end
def self.wrap_deserialize(viewmodel, deserialize_context:)
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
def self.wrap_serialize(viewmodel, context:)
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
def add_callback(hook, view_name, &block)
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
def dsl_add_hook_name
def dsl_add_hook_name name.underscore end
def dsl_viewmodel_callback_method
def dsl_viewmodel_callback_method name.underscore.to_sym end
def each_callback(hook, view_name)
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
def ineligible(view)
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
def inherited(subclass)
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
def init(context_name, *other_params)
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
def run_callback(hook, view, context, **args)
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
def updates_view!
def updates_view! define_singleton_method(:updates_view?) { true } end
def updates_view?
def updates_view? false end
def valid_hook!(hook)
def valid_hook!(hook) unless hook.is_a?(Hook) raise ArgumentError.new("Invalid hook: '#{hook}'") end end