module NutritionCalculator::CachedOutputsWithRecalculation

Define an object that turns inputs into outputs. With Caching! And Logging!

@example

class Foo
  extend NutritionCalculator::CachedOutputsWithRecalculation

  def_input :bar

  def_input :spam, validate_with: ->(value) {
    value != 'ham'
  }

  def_output :baz do
    bar.expensive_operation
  end
end

x = Foo.new

x.baz
#=> Raises a RuntimeError because input bar was not set

x.spam = 'ham'
#=> Raises a
  NutritionCalculator::CachedOutputsWithRecalculation::InvalidInputError
  because the validation returned false.

a_thing = ExpensiveObject.new

x.bar = a_thing
x.baz
#=> result of `a_thing.expensive_operation`

# `a_thing.expensive_operation` will not be called again
x.baz
#=> cached result of `a_thing.expensive_operation`

# `a_thing.expensive_operation` will be called again since input is
# reassigned
x.bar = a_thing
x.baz
#=> result of `a_thing.expensive_operation`

Public Class Methods

extended(other) click to toggle source

@private

# File lib/nutrition_calculator/cached_outputs_with_recalculation.rb, line 52
def self.extended(other)
  other.include(InstanceMethods)
end

Public Instance Methods

def_input(name, validate_with: ->(_) { true } click to toggle source

Defines accessors for the named attribute

Assignment to the named accessor will cause all cached results to be recalculated the next time they are called.

@param name [Symbol]

# File lib/nutrition_calculator/cached_outputs_with_recalculation.rb, line 62
def def_input(name, validate_with: ->(_) { true })
  def_input_writer name, validator: validate_with
  def_input_reader name
end
def_output(name, &block) click to toggle source

Defines attribute reader methods for the specified calculation

The result of the block is cached as long as no inputs are re-assigned after the attribute reader is called. Additionaly, if ‘#logger` is set, the result of the calculation will be sent to the DEBUG log when the block is run.

@param name [Symbol] @param block [Proc] will be ‘instance_eval`ed in the Object as though it

were the body of a regular method
# File lib/nutrition_calculator/cached_outputs_with_recalculation.rb, line 77
def def_output(name, &block)
  define_method(name) do
    cache_and_debug(name, &block)
  end
end

Private Instance Methods

def_input_reader(name) click to toggle source
# File lib/nutrition_calculator/cached_outputs_with_recalculation.rb, line 93
def def_input_reader(name)
  define_method(name) do
    require_input name
    debug_value(name) {
      instance_variable_get("@#{name}")
    }
  end
end
def_input_writer(name, validator:) click to toggle source
# File lib/nutrition_calculator/cached_outputs_with_recalculation.rb, line 85
def def_input_writer(name, validator:)
  define_method("#{name}=") do |value|
    validate_input!(name, value, validator)
    recalculate!
    instance_variable_set("@#{name}", value)
  end
end