module Praxis::Mapper::Resources::TypedMethods

Constants

CREATE_LOADER_METHOD

Attributes

signatures[R]

Public Class Methods

_finalize!() click to toggle source
Calls superclass method
# File lib/praxis/mapper/resources/typed_methods.rb, line 25
def _finalize!
  if @signatures
    # Build the around callbacks for coercing the params for the methods with types defined
    # Also, this needs to be before, so that we hit the coercion code before any other around callback
    const_set(:MethodSignatures, Module.new)
    @signatures.each do |method, type|
      # Also add a constant pointing to the signature type inside Signatures (and substitute ! for Bang, as that's not allowed in a constant)
      # For class Methods, also substitute .self for Self
      # This helps with debugging, as we won't get anonymous struct classes, but we'll see these better names
      cleaned_name = method.to_s.gsub(/!/, '_bang').to_s.gsub(/^self./, 'self_')
      self::MethodSignatures.const_set(cleaned_name.camelize.to_sym, type)
      coerce_params_for method, type
    end
  end

  super
end
coerce_params_for(method, type) click to toggle source

Sets up a specific around callback to a given method, where it’d pass the loaded/coerced type from the input

# File lib/praxis/mapper/resources/typed_methods.rb, line 68
def coerce_params_for(method, type)
  raise "Argument type for #{method} could not be found. Did you define a `signature` stanza for it?" unless type

  if method.start_with?('self.')
    simple_name = method.to_s.gsub(/^self./, '').to_sym
    # Look for a Class method
    raise "Error building typed method signature: Method #{method} is not defined in class #{name}" unless methods.include?(simple_name)

    coerce_params_for_class(method(simple_name), type)
  else
    # Look for an instance method
    raise "Error building typed method signature: Method #{method} is not defined in class #{name}" unless method_defined?(method)

    coerce_params_for_instance(instance_method(method), type)
  end
end
coerce_params_for_class(method, type) click to toggle source
# File lib/praxis/mapper/resources/typed_methods.rb, line 97
def coerce_params_for_class(method, type)
  around_method_name = "_coerce_params_for_class_#{method.name}"
  # Define an instance method in the eigenclass
  singleton_class.instance_exec around_method_name: around_method_name,
                                orig_method: method,
                                type: type,
                                ctx: [to_s, method.name].freeze,
                                &CREATE_LOADER_METHOD

  # Set an around callback to call the defined method above (the callbacks need self. for class interceptors)
  class_method_name = "self.#{method.name}"
  around class_method_name.to_sym, around_method_name
end
coerce_params_for_instance(method, type) click to toggle source
# File lib/praxis/mapper/resources/typed_methods.rb, line 85
def coerce_params_for_instance(method, type)
  around_method_name = "_coerce_params_for_#{method.name}"
  instance_exec around_method_name: around_method_name,
                orig_method: method,
                type: type,
                ctx: [to_s, method.name].freeze,
                &CREATE_LOADER_METHOD

  # Set an around callback to call the defined method above
  around method.name, around_method_name
end
signature(method_name, same_as_type = nil, **opts, &block) click to toggle source

It can take options to be passed to the attribute’s block of the constructed struct It can take an already existing struct instead of a block/options

# File lib/praxis/mapper/resources/typed_methods.rb, line 45
def signature(method_name, same_as_type = nil, **opts, &block)
  method = method_name.to_sym
  @signatures ||= {}
  raise "signature definition for #{method_name}: need to pass either an existing type or a block, not both" if block_given? && same_as_type

  if block_given?
    type =
      Class.new(Attributor::Struct) do
        attributes(**opts) do
          instance_eval(&block)
        end
      end
    @signatures[method] = type
  elsif same_as_type
    raise "Options for signature definition for #{method_name} are not supported when passing an already existing type" unless opts.empty?

    @signatures[method] = same_as_type
  else
    @signatures[method]
  end
end