Last active
January 26, 2016 19:01
-
-
Save christhekeele/14b10671c1aeef4e3a74 to your computer and use it in GitHub Desktop.
Cool use of subclassing Module in Ruby.
This file contains 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
class Pipeline < Module | |
attr_accessor :transforms | |
def initialize(*transforms, &implementation) | |
@transforms = transforms | |
instance_eval &implementation if block_given? | |
end | |
def call(*args, &block) | |
transformers.reduce(block || default_block) do |pipeline, transformer| | |
-> *args { transformer.call(*args, &pipeline) } | |
end.call *args | |
end | |
class Before < self | |
# Use default behavior | |
end | |
class After < self | |
def apply(transformer) | |
-> *args, &block { | |
transformer.call *block.call(*args) | |
} | |
end | |
end | |
class Around < self | |
# Rely on implementation to yield | |
def apply(transformer) | |
transformer | |
end | |
end | |
class << self | |
def before(*args, &block) | |
Before.new(*args, &block) | |
end | |
def after(*args, &block) | |
After.new(*args, &block) | |
end | |
def around(*args, &block) | |
Around.new(*args, &block) | |
end | |
end | |
private | |
def transformers | |
transforms.reverse.map do |transform| | |
transformer_for transform | |
end.compact.map do |transformer| | |
apply transformer | |
end | |
end | |
def apply(transformer) | |
-> *args, &block { | |
block.call *transformer.call(*args) | |
} | |
end | |
def transformer_for(transform) | |
if transform.respond_to? :call | |
transform | |
elsif transform.respond_to? :to_sym | |
method transform.to_sym | |
end | |
end | |
def default_block | |
-> *args { | |
case args.length | |
when 0; nil | |
when 1; args.first; | |
else args; end | |
} | |
end | |
end |
This file contains 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
require 'pipeline' | |
#=> true | |
# Build an empty pipeline with `Pipeline.new`: | |
Pipeline.new | |
#=> #<Pipeline:0x007fcf3e0cee40> | |
# Call it with arguments and a block to evaluate the block with those arguments: | |
Pipeline.new.call(:foo, :bar) do |first, second| | |
puts second | |
end | |
#=> bar | |
# End result: a pretty clean and minimal callback chain DSL: | |
# Call it without a block just echo the result: | |
Pipeline.new.call #=> nil | |
Pipeline.new.call(:single_argument) #=> :single_argument | |
Pipeline.new.call(:multiple, :arguments) #=> [:multiple, :arguments] | |
# Build a pipeline with several transforms by adding methods: | |
p1 = Pipeline.new(:increment, :increment, :divide_by_2) do | |
def increment(num) | |
num + 1 | |
end | |
def divide_by_2(num) | |
num / 2 | |
end | |
end | |
# This runs in order: | |
p1.call(0) | |
#=> 1 # == (0 + 1 + 1) / 2 | |
# We can reverse the order: | |
p1 = Pipeline::After.new(:increment, :increment, :divide_by_2) do | |
def increment(num) | |
num + 1 | |
end | |
def divide_by_2(num) | |
num / 2 | |
end | |
end | |
# This runs in reverse order: | |
p1.call(0) | |
#=> 2 # == (0 / 2) + 1 + 1) | |
# Or we can use `around` to decide the order in each method by yielding: | |
p1 = Pipeline::Around.new(:increment, :increment, :divide_by_2) do | |
def increment(num) | |
yield(num + 1) | |
end | |
def divide_by_2(num) | |
yield(num) / 2 | |
end | |
end | |
# Transforms can also just be callables, not just method names | |
def palindromify(string) | |
yield string + string.reverse[1..-1] | |
end | |
Pipeline.new(-> string, &block { block.call string.upcase }, method(:palindromify) ).call("amanaplanac") | |
#=> "AMANAPLANACANALPANAMA" | |
# You can add transforms like you would any other method to a module: | |
module Duplicate | |
def duplicate(*things) | |
things * 2 | |
end | |
end | |
module SuperDuplicate | |
def duplicate(*things) | |
super * 10 | |
end | |
end | |
p2 = Pipeline.new(:duplicate) do | |
extend Duplicate | |
extend SuperDuplicate | |
end | |
p2.call(:thing) | |
#=> [:thing, :thing, :thing, :thing...] | |
p2.call(:thing).count | |
#=> 20 # 1 thing * 2 things * 10 things | |
# Pipelines are composable, since they're callable | |
p3 = Pipeline.new(:convert_to_string, p2) do | |
def convert_to_string(thing) | |
yield thing.to_s | |
end | |
end | |
p3.call(0) | |
#=> ["0", "0", "0", "0"...] | |
p3.call(0).size | |
#=> 20 # 1 "0" * 2 * 10 |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment