0.0
A long-lived project that still receives updates
Adds an `evaluate` refinement method to Proc and Object instances. The goal of this gem is to allow the evaluation of variables, procs, and lambdas with the same level of flexibility. The `evaluate` method has been added to the Object class to return the evaluated value of the variable. The `evaluate` method is overridden on the Proc class to allow parameters to be passed to lambdas in the same flexible way as procs. This takes into consideration, required/optional/remaining parameters, and required/optional/remaining keyword parameters. For information on Refinements, see: - https://ruby-doc.org/core-2.0.0/doc/syntax/refinements_rdoc.html - https://ruby-doc.org/core-2.1.0/doc/syntax/refinements_rdoc.html - https://ruby-doc.org/core-2.2.0/doc/syntax/refinements_rdoc.html - https://ruby-doc.org/core-2.3.0/doc/syntax/refinements_rdoc.html - https://ruby-doc.org/core-2.4.0/doc/syntax/refinements_rdoc.html - http://yehudakatz.com/2010/11/30/ruby-2-0-refinements-in-practice/
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
 Dependencies

Development

>= 13
 Project Readme

ProcEvaluate

This ruby gem adds an evaluate method to Proc and Object instances through the use of Refinements.

The goal of this gem is to allow evaluation of variables, procs, and lambdas with the same level of flexibility.

Let's say you want to provide an API or DSL where you want the developer to return a value, such as a string, but you want to give the developer
as much flexibility as possible, so that they can either return a string directly, or return a string from a proc or lambda. \

Your API or DSL may even want to pass in some helpful data to the proc or lambda, which the developer can use to derive their return value.

But you don't want to write code that handles the annoying differences between procs and lambdas:

proc = proc {|a, b, c| [a, b, c] }
proc.call() # [nil,  nil, nil]

lambda = ->(a, b, c) { [a, b, c] }
lambda.call() # ArgumentError: wrong number of arguments (given 0, expected 3)

The evaluate method has been added to the Object class to return the evaluated value of the variable. The evaluate method is overridden on the Proc class to allow parameters to be passed to lambdas in the same flexible way as procs. This takes into consideration, required/optional/remaining parameters, and required/optional/remaining keyword parameters.

Usecase Examples

Your gem or library or dsl requires a value from the developer. You want to allow the developer the greatest possible flexibility with the value they provide.

A contrived usecase of allowing a developer to define an OpenIdConnect endpoint at config time vs request time:

# define endpoint at config time
OpenIdConnectClient.config do |c|
  c.authorize_endpoint 'https://iknowthis.com/at/config/time'
end

# define endpoint at request time
OpenIdConnectClient.config do |c|
  c.authorize_endpoint ->(request) { OpenIdConnectProvider.find_by(name: request.params['provider_name']) }
end

Within your code, you can call evaluate on the value passed to authorize_endpoint. E.g: authorize_endpoint_value.evaluate(request)

  • In the case where authorize_endpoint is passed a string, the string is returned
  • In the case where authorize_endpoint is passed a Lamda, the lambda is called, and an OpenIdConnectProvider is returned

By using the evaluate method within your gem/library/dsl, you do not need to be concerned with whether the developer provides a value, proc, or lambda.
Likewise, the developer has greater flexibility when using you gem/library/dsl as they have greater options for deriving and returning the required value.

Compatibility

This gem makes use of keyword parameters and refinements, so it is only compatible with Ruby version 2.0 and above.

As of 2023-11-02 the test are run on every push and pull request against the currently supported Ruby versions: 2.7, 3.0, 3.1, 3.2. However, as no breaking changes have been introduced, this gem is still compatible with Ruby 2.0+.

This gem makes use of keyword parameters and refinements, so it is only compatible with Ruby version 2.0 and above.

Usage

In your Gemfile add gem 'proc_evaluate'.

In your codebase add require 'proc_evaluate'.

The refinement methods in the gem can be used by including using ProcEvaluate in the file, class definition, or module definition in which you wish to use the refinement.

Class and Module usage

The refinement methods can be activated for use within a specific Class or Module.

class ProcEvaluateClassExamples
  using ProcEvaluate

  def example1
    a = ->(a) { a }
    a.evaluate('hello', 'world')
  end

  def example2
    a = ->(a, b, c, d, e, f) { [a, b, c, d, e, f] }
    a.evaluate(1, 2, 3, 4)
  end

  def example3
    a = ->(a) { a }
    a.evaluate('Im a proc!!!', 'world')
  end

  def example4
    a = 'im a value!!!'
    a.evaluate('hello', 'world')
  end
end

e = ProcEvaluateClassExamples.new
e.example1 # "hello"
e.example2 # [1, 2, 3, 4, nil, nil, nil]
e.example3 # "Im a proc!!!"
e.example4 # "im a value!!!"

module ProcEvaluateModuleExamples
  using ProcEvaluate
  extend self

  def example1
    a = ->(a) { a }
    a.evaluate('hello', 'world')
  end

  def example2
    a = ->(a, b, c, d, e, f) { [a, b, c, d, e, f] }
    a.evaluate(1, 2, 3, 4)
  end

  def example3
    a = ->(a) { a }
    a.evaluate('Im a proc!!!', 'world')
  end

  def example4
    a = 'im a value!!!'
    a.evaluate('hello', 'world')
  end
end

ProcEvaluateModuleExamples.example1 # "hello"
ProcEvaluateModuleExamples.example2 # [1, 2, 3, 4, nil, nil, nil]
ProcEvaluateModuleExamples.example3 # "Im a proc!!!"
ProcEvaluateModuleExamples.example4 # "im a value!!!"

Another example showing a different pattern of usage:

class Example
  using ProcEvaluate

  def initialize(value)
    @value = value
  end

  def evaluate_value(*args, **options)
    @value.evaluate(*args, **options)
  end
end

# Example 1: Evaluating a plain value
e1 = Example.new('Hello')
e1.evaluate_value # "Hello"
e1.evaluate_value('World')  # "Hello"
e1.evaluate_value(hello: 'World') # "Hello"

# Example 2: Evaluating a lambda
lambda = ->(req, opt = nil, *rest, keyreq:, keyopt: nil, **options) {
  [req, opt, rest, keyreq, keyopt, options]
}

e2 = Example.new(lambda)
begin
  e2.evaluate_value # ArgumentError: missing keywords: keyreq
rescue => e
  puts e.message
end

e2.evaluate_value(1, keyreq: true) # [1, nil, [], true, nil, {}]
e2.evaluate_value(1, keyreq: true) # [1, nil, [], true, nil, {}]
e2.evaluate_value(1, :optional, keyreq: true) # [1, :optional, [], true, nil, {}]
e2.evaluate_value(1, keyreq: true, keyopt: :optional) # [1, nil, [], true, :optional, {}]
e2.evaluate_value(1, :optional, 'another', 2, keyreq: true, keyopt: :optional, my_key: 'Hello World') # [1, :optional, ["another", 2], true, :optional, {:my_key=>"Hello World"}]

Top Level usage

Please note that the below example will not work if copied and pasted into an irb or pry console. The using statement can only be used at the top level, or within a class or module definition. To test the example, place the code into a file and run with the command ruby example.rb.

# example.rb

using ProcEvaluate # activate the refinements for the current file

proc = proc {|a, b, c| [a, b, c] }
proc.evaluate() # [nil,  nil, nil]

lambda = ->(a, b, c) { [a, b, c] }
lambda.evaluate() # [nil,  nil, nil]

var1 = 1
var1.evaluate('hello', 'world') # 1

var2 = ->(a) { a }
var2.evaluate('hello', 'world') # "hello"

Further Reading

For information on Refinements, see: