Created
November 2, 2020 12:06
-
-
Save estum/11d22b5fa3ab8eead6a4ffcdd237f6bd to your computer and use it in GitHub Desktop.
Yieldable v2
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# frozen_string_literal: true | |
# = Yieldable | |
# | |
# This is meta-mixin, which makes your module, class or it's instance to respond | |
# to method +to_proc+, for example, to easily build instances during iteration: | |
# | |
# ["1", "2", "3"].map(&SomeObject) | |
# | |
# It is 2 different ways to use this module: +extend+ or +prepend+ it to your class. | |
# | |
# == <tt>extend Yieldable</tt> | |
# | |
# class SomeObject | |
# extend Yieldable # => singleton_class.to_proc = SomeObject.method(:call).to_proc | |
# end | |
# | |
# This way defines an attribute reader +to_proc+ in a singleton class. | |
# It sets a proc only once for a class, so it will not generate a new +Proc+ object | |
# every time, like if you would use just a <tt>method(:call).to_proc</tt>. | |
# By default it uses the +call+ class method as a source method for a proc. You | |
# can change the source method by using brackets: | |
# | |
# class SomeObject | |
# extend Yieldable[:new] # => singleton_class.to_proc = SomeObject.method(:new).to_proc | |
# end | |
# | |
# It's better to ensure that a source method is defined _before_ extending a class with +Yieldable+. | |
# In the other way it will add the +singletone_method_added+ hook and wait. After a source method | |
# will be added, the hook method will be removed. | |
# | |
# You can avoid to removing the hook with an optional <tt>once: false</tt> passed into the brackets: | |
# | |
# extend Yieldable[:new, once: false] | |
# | |
# == <tt>prepend Yieldable</tt> | |
# | |
# This way defines a +to_proc+ instance method once for a class, but a proc will be generated once for | |
# each object. You can also use a brackets like within an +extend+ way. | |
module Yieldable | |
# Creates configured mixin. | |
# | |
# @param method_name [Symbol] A source method's name | |
# @param once [Boolean] Will be a proc defined only once? (by default: +true+) | |
# @return [Module] the configured mixin | |
def self.[](method_name = :call, once: true) | |
Mixin.new(method_name, once: once) | |
end | |
class Mixin < Module | |
attr_reader :method_name | |
def initialize(method_name = :call, once: true) | |
@method_name = method_name | |
@once = once | |
@proc = nil | |
super() | |
end | |
def method_added(base, id) | |
set_proc(base) | |
cleanup(base) if @once # self-remove | |
end | |
def extend_object(base) | |
unless base.singleton_class.method_defined?(:to_proc) | |
base.singleton_class.attr_reader(:to_proc) | |
end | |
if base.singleton_class.method_defined?(@method_name) | |
set_proc(base) | |
end | |
if wait_method_definition? | |
mixin = self | |
base.define_method :singleton_method_added do |id| | |
mixin.method_added(self, id) if id == mixin.method_name | |
super(id) | |
end | |
end | |
super | |
end | |
def prepend_features(base) | |
base.class_eval <<~RUBY, __FILE__, __LINE__ + 1 | |
def to_proc | |
@to_proc ||= method(:#{@method_name}).to_proc | |
end | |
RUBY | |
super | |
end | |
private | |
def wait_method_definition? | |
@proc.nil? || !@once | |
end | |
def set_proc(base) | |
@proc = find_method_on(base).to_proc | |
base.instance_variable_set(:@to_proc, @proc) | |
base | |
end | |
def cleanup(base) | |
base.singleton_class.remove_method(:singleton_method_added) | |
end | |
def find_method_on(base) | |
if base.respond_to?(@method_name) | |
base.method(@method_name) | |
else | |
base.singleton_method(@method_name) | |
end | |
end | |
end | |
CallMixin = Mixin.new(:call) | |
private_constant :Mixin | |
private_constant :CallMixin | |
class << self | |
private | |
def extend_object(base) | |
CallMixin.extend_object(base) | |
end | |
def prepend_features(base) | |
CallMixin.prepend_features(base) | |
end | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Yieldable
This is meta-mixin, which makes your module, class or it's instance to respond
to method
to_proc
, for example, to easily build instances during iteration:It is 2 different ways to use this module:
extend
orprepend
it to your class.extend Yieldable
This way defines an attribute reader
to_proc
in a singleton class.It sets a proc only once for a class, so it will not generate a new
Proc
objectevery time, like if you would use just a
method(:call).to_proc
.By default it uses the
call
class method as a source method for a proc. Youcan change the source method by using brackets:
It's better to ensure that a source method is defined before extending a class with +Yieldable+.
In the other way it will add the
singletone_method_added
hook and wait. After a source methodwill be added, the hook method will be removed.
You can avoid to removing the hook with an optional
once: false
passed into the brackets:prepend Yieldable
This way defines a
to_proc
instance method once for a class, but a proc will be generated once foreach object. You can also use a brackets like within an +extend+ way.