Skip to content

Instantly share code, notes, and snippets.

@kaspth
Created December 18, 2022 17:48
Show Gist options
  • Save kaspth/402e8c71e33a963eb4b39df2bad2b4fe to your computer and use it in GitHub Desktop.
Save kaspth/402e8c71e33a963eb4b39df2bad2b4fe to your computer and use it in GitHub Desktop.
A half written-up implementation of progressively enhancing an options hash through method chaining on a context object.
class Method::Chain
def initialize(descriptor, **options)
@descriptor, @options = descriptor, options
end
def self.define(context, *methods, &block)
instance = new Definition.new(&block)
instance.apply context, *methods
instance
end
def apply(context, *methods)
context.const_set :ChainableMethods, Module.new {
source = methods.map do |name|
<<~RUBY
def #{name}(*arguments, **options, &block)
chain.handle(*arguments, **options, &block) do |final_options|
final_options.merge! options
super(*arguments, **final_options, &block)
end
end
RUBY
end
class_eval source.join("\n\n"), __FILE__, __LINE__ + 1
}
end
class Definition
DEFAULT_TERMINATOR = ->(_, arguments, options, _) { arguments.any? || options.any? }
def initialize(&)
@terminator, @values = DEFAULT_TERMINATOR, {}
instance_eval(&)
end
def terminator(value = nil, &block)
@terminator = value || block
end
def self.store(name, key: name, value: name)
values[name] = { key:, value: }
self.class.alias_method name, :exec
end
def finalize(options = nil)
@block.call options ? options.merge @options : @options
end
private
def exec(*arguments, **options, &block)
assign __callee__
if @terminator.call(__callee__, arguments, options, &block)
finalize options
else
self
end
end
def assign(name)
hash = values.fetch(name)
@options.store hash[:key], hash[:value]
end
end
end
Method::Chain.define self, :get do
store :json, key: :as
store :xhr, value: true
# terminator ->(_, arguments, options, &block) { arguments.any? || options.any? }
end
# Each get call kicks off a new chain, which terminates when an argument is passed
get.json root_url
get.json.xhr root_url
# The original `get` is none the wiser
def get(...)
request(:get, ...)
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment