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.name
If used the PullChain (detail description is here)
chain = PullChain.new(account) << :user << :name chain.call
RelayChain 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").call
Use <<
chain = PullChain.new(account) chain << :user << :name << :upcase chain.call
Use *add*
chain = PullChain.new(account) chain.add(:user).add(:name).add(:upcase).call
Use *add_all*
chain = PullChain.new(account) chain.add_all(:user, :name, :upcase).call
Use *Proc*
chain = PullChain.new(account) chain << Proc.new { user } << Proc.new { name } << Proc.new { upcase } chain.call
If you use lambda then can't omit block parameter. block parameter is previous chain result.
chain = PullChain.new(account) chain << lambda { |account| account.user } << lambda { |user| user.name } << lambda { |name| name.upcase } chain.call
lambda evaluate by previous chain result, so can call results method direct.
chain = PullChain.new(account) chain << lambda { |_| user } << lambda { |_| name } << lambda { |_| upcase } chain.call
if want to omit block parameter, recommend Proc use.
Use add with block
PullChain.new(account).add { user }.add { name }.add { upcase }.call
Can exist nil value on the way, like a following case
user.name = nil
chain.call # => nil
Insert
insert, insert_all is insert method to chain.
chain = PullChain.new(account, :user, :upcase)
chain.insert(1, :name).call
chain = PullChain.new(account, :user)
chain.insert_all(1, :name, :upcase).call
insert with block
chain = PullChain.new(account, :user, :upcase)
chain.insert(1) { name }.call
Delete
delete_at is delete method from chain.
chain = PullChain.new(account, :user, :name, :upcase)
chain.delete_at(2)
chain.call # => Louis
Clear
clear is delete all method from chain.
chain = PullChain.new(account, :user, :name, :upcase)
chain.clear
chain.call # => #<struct Account user=#<struct User name="Louis">>
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.new) << "say('John', 'Goodbye')" chain.call # => John said 'Goodbye'
Proc
chain = PullChain.new(Foo.new) << Proc.new { say('Julian', 'Nice to meet you') } chain.call # => Julian said 'Nice to meet you'
add with block
chain = PullChain.new(Foo.new).add { say('Narciso', 'How do you do?') } chain.call # => Narciso said 'How do you do?'
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 # => 18
String
chain = PullChain.new([1,2,3,4,5]) chain << "inject(3) { |sum, n| sum + n }" chain.call # => 18
Proc
chain = PullChain.new([1,2,3,4,5]) chain << Proc.new { inject(3) { |sum, n| sum + n } } chain.call # => 18
add with block
chain = PullChain.new([1,2,3,4,5]) chain.add { 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/DC
Use return_nil_at_error=, 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 # => nil
Note: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
chain = RelayChain.new(Decorator.new) chain.add(:decorate1).add(:decorate2).call("Hello")
Use add_all
chain = RelayChain.new(Decorator.new) chain.add_all(:decorate1, :decorate2).call("Hello")
Insert
insert, insert_all is insert function to chain.
chain = RelayChain.new(Decorator.new, :decorate2)
chain.insert(0, :decorate1)
chain.call("Hello") # => { ( Hello ) }
chain = RelayChain.new(Decorator.new)
chain.insert_all(0, :decorate1, :decorate2)
chain.call("Hello") # => { ( Hello ) }
Delete
delete_at is delete function from chain.
chain = RelayChain.new(Decorator.new, :decorate1, :decorate2)
chain.delete_at(0)
chain.call("Hello") # => { Hello }
Clear
clear is delete all function from chain.
chain = RelayChain.new(Decorator.new, :decorate1, :decorate2)
chain.clear
chain.call("Hello") # => nil
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 Palmer
Add lambda or Proc to between these methods.
lambda's format is following.# parameter: chain is chain object. # parameter: *args is previous functions output. # *args_of_next_func is next functions input. lambda {|chain, *args| chain.call( *args_of_next_func ) }.
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
can write as follows by add with block.
chain = RelayChain.new(Decorator.new) chain.add(:decorate).add { |chain, value| chain.call(value, "Jerry") }.add(: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 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.