FunctionChain
Description
FunctionChain objectifies of the method chain.
Chain object can following.
- Call later.
- Add method to chain
- Insert method to chain.
- Delete method from chain.
Installation
gem install function_chain
PullChain & RelayChain
PullChain & RelayChain is FunctionChain module's classes.
PullChain & RelayChain will support call chain type, each different.
PullChain to support the call chain type such as the following:
account.user.nameIf used the PullChain (detail description is here)
chain = PullChain.new(account) << :user << :name chain.callRelayChain to support the call chain type such as the following:
filter3(filter2(filter1(value)))If used the RelayChain (detail description is here)
chain = RelayChain.new >> :filter1 >> :filter2 >> :filter3 chain.call("XXX")
Usage and documentation
The following is necessary call to use the PullChain or RelayChain.
require "function_chain"
include FunctionChain
Note: This document omit the above code from now on.
PullChain
PullChain is object as represent method call chain.
Can inner object's method call of object.
Example
Account = Struct.new(:user)
User = Struct.new(:name)
account = Account.new(User.new("Louis"))
chain = PullChain.new(account, :user, :name, :upcase)
chain.call # => LOUIS
Similar
Strings separated by a slash
PullChain.new(account, "user/name/upcase").callUse << operator
chain = PullChain.new(account) chain << :user << :name << :upcase chain.callUse add method
chain.add(:user).add(:name).add(:upcase).callUse add_all method
chain.add_all(:user, :name, :upcase).call
Can exist nil value on the way, like a following case
user.name = nil
chain.call # => nil
Insert, Delete, Clear
insert, insert_all method is insert_all method to chain.
delete_at method is delete method from chain.
clear method is delete all method from chain.
Require arguments on method
Following example's method is require two arguments.
What should do in this case?
class Foo
def say(speaker, )
puts "#{speaker} said '#{}'"
end
end
Solution
Array, format is [Symbol, [*Args]]
chain = PullChain.new(Foo.new) << [:say, ["Andres", "Hello"]] chain.call # => Andres said 'Hello'String
chain = PullChain.new(foo) << "say('John', 'Goodbye')" chain.call # => John said 'Goodbye'
Require block on method
Following example's method is require Block.
What should do in this case?
[1,2,3,4,5].inject(3) { |sum, n| sum + n } # => 18
Solution
Array, format is [Symbol, [*Args, Proc]].
chain = PullChain.new([1,2,3,4,5]) chain << [:inject, [3, lambda { |sum, n| sum + n }]] chain.call # => 18String
chain = PullChain.new([1,2,3,4,5]) chain << "inject(3) { |sum, n| sum + n }" chain.call # => 18
Use result on chain
Like a following example, can use result on chain.
Foo = Struct.new(:bar)
Bar = Struct.new(:baz) {
def speaker
"Julian"
end
}
class Baz
def say(speaker, )
puts "#{speaker} said '#{}'"
end
end
foo = Foo.new(Bar.new(Baz.new))
Example: use result on chain
String
Can use bar instance in backward!chain = PullChain.new(foo) << "bar/baz/say(bar.speaker, 'Good!')" chain.call # => Julian said 'Good!'Furthermore, can use variable name assigned.
@b is bar instance alias.chain = PullChain.new(foo) << "@b = bar/baz/say(b.speaker, 'Cool')" chain.call # => Julian said 'Cool'Array
Can access result by Proc.chain = PullChain.new(foo) << :bar << :baz chain << [:say, Proc.new { next .speaker, "Oh" }] chain.call # => Julian said 'Oh'Case of use a lambda, can use result access object explicit.
chain = PullChain.new(foo) << :bar << :baz arg_reader = lambda { |accessor| next accessor..speaker, "Oh" } chain << [:say, arg_reader] chain.call # => Julian said 'Oh'
etc
How to use slash in strings separated by a slash
Like following, please escaped by backslash.chain = PullChain.new("AC") << "concat '\\/DC'" chain.call # => AC/DCUse return_nil_at_error= method, then can ignore error
chain = PullChain.new("Test") << :xxx begin chain.call # => undefined method `xxx' rescue end chain.return_nil_at_error = true chain.call # => nilNote:use operator in chain
- String type chain
ruby table = {name: %w(Bill Scott Paul)} PullChain.new(table, "[:name]").call # => [:name] NG PullChain.new(table, "self[:name]").call # => ["Bill", "Scott", "Paul"] OK
- String type chain
- Array type chain
ruby PullChain.new(table, [:[], [:name]]).call # OK
Following is also the same.
<< operator of String
PullChain.new("Led", "<< ' Zeppelin'").call # NG syntax error PullChain.new("Led", "self << ' Zeppelin'").call # => "Led Zeppelin"[] operator of Array
PullChain.new(%w(Donald Walter), "[1]").call # NG => [1] PullChain.new(%w(Donald Walter), "self[1]").call # OK => Walter
- Some classes, such Fixnum and Bignum not supported
ruby PullChain.new(999999999999999, "self % 2").call # NG
RelayChain
RelayChain is object like a connect to function's input from function's output.
(methods well as can connect Proc.)
Example
class Decorator
def decorate1(value)
"( #{value} )"
end
def decorate2(value)
"{ #{value} }"
end
end
chain = RelayChain.new(Decorator.new, :decorate1, :decorate2)
chain.call("Hello") # => { ( Hello ) }
Similar
Strings separated by a slash
chain = RelayChain.new(Decorator.new, "decorate1/decorate2") chain.call("Hello")Use >> operator
chain = RelayChain.new(Decorator.new) chain >> :decorate1 >> :decorate2 chain.call("Hello")Use Method object
chain = RelayChain.new chain >> decorator.method(:decorate1) >> decorator.method(:decorate2) chain.call("Hello")Use add method
chain.add(:decorate1).add(:decorate2).call("Hello")Use add_all method
chain.add_all(:decorate1, :decorate2).call("Hello")
Insert, Delete, Clear
insert, insert_all method is insert function to chain.
delete_at method is delete function from chain.
clear method is delete all function from chain.
How to connect method of differed instance
Example, following two class.
How to connect method of these class?
class Decorator
def decorate1(value)
"( #{value} )"
end
def decorate2(value)
"{ #{value} }"
end
end
class Decorator2
def decorate(value)
"[ #{value} ]"
end
end
Solution
- Array, format is [instance, Symbol or String of method] ```ruby # Symbol ver. chain = RelayChain.new(Decorator.new) chain >> :decorate1 >> :decorate2 >> [Decorator2.new, :decorate] chain.call("Hello") # => [ { ( Hello ) } ]
# String ver. chain = RelayChain.new(Decorator.new) chain >> :decorate1 >> :decorate2 >> [Decorator2.new, "decorate"] chain.call("Hello") # => [ { ( Hello ) } ]
2. **String, use registered instance**
```ruby
chain = RelayChain.new(Decorator.new)
# register name and instance
chain.add_receiver("d2", Decorator2.new)
# use registered instance
chain >> "/decorate1/decorate2/d2.decorate"
chain.call("Hello") # => [ { ( Hello ) } ]
# add_receiver_table method is register name and instance at once.
chain.add_receiver_table({"x" => X.new, "y" => Y.new})
Case of method's output and method's input mismatch
Following example, decorate output is 1, and union input is 2.
How to do connect these methods?
class Decorator
def decorate(value)
"#{value} And"
end
def union(value1, value2)
"#{value1} #{value2}"
end
end
Solution
Define connect method.
class Decorator def connect(value) return value, "Palmer" end end chain = RelayChain.new(Decorator.new) chain >> :decorate >> :connect >> :union chain.call("Emerson, Lake") # => Emerson, Lake And PalmerAdd lambda or Proc to between these methods.
lambda's format is following.# parameter: chain is chain object. # parameter: \*args is previous functions output. lambda {|chain, *args| chain.call(next functions arguments) }.can call next function by chain object.
chain = RelayChain.new(Decorator.new) arg_adder = lambda { |chain, value| chain.call(value, "Jerry") } chain >> :decorate >> arg_adder >> :union chain.call("Tom") # => Tom And Jerry
Appendix
Chain stop by means of lambda.
class Decorator
def decorate1(value)
"( #{value} )"
end
def decorate2(value)
"{ #{value} }"
end
end
def create_stopper(&stop_condition)
lambda do |chain, value|
# if stop conditions are met then return value
if stop_condition.call(value)
value
else
chain.call(value)
end
end
end
chain = RelayChain.new(Decorator.new, :decorate1, :decorate2)
# insert_all conditional chain stopper
chain.insert(1, create_stopper { |value| value =~ /\d/ })
chain.call("Van Halen 1984") # => ( Van Halen 1984 ) not enclosed to {}
chain.call("Van Halen Jump") # => { ( Van Halen Jump ) } enclosed to {}
License
Released under the MIT License.