Skip to content

Repository files navigation

FunctionChain

Build Status Coverage Status Code Climate

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:
  ```ruby
  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

  1. Strings separated by a slash
PullChain.new(account, "user/name/upcase").call
  1. Use <<
chain = PullChain.new(account)
chain << :user << :name << :upcase
chain.call
  1. Use add
chain = PullChain.new(account)
chain.add(:user).add(:name).add(:upcase).call
  1. Use add_all
chain = PullChain.new(account)
chain.add_all(:user, :name, :upcase).call
  1. 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. ```ruby chain = PullChain.new(account) chain << lambda { || user } << lambda { || name } << lambda { |_| upcase } chain.call

if want to omit block parameter, recommend *Proc* use.

6. **Use *add* with block**
```ruby
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, message)
    puts "#{speaker} said '#{message}'"
  end
end
Solution
  1. Array, format is [Symbol, [*args]]
chain = PullChain.new(Foo.new) << [:say, ["Andres", "Hello"]]
chain.call # => Andres said 'Hello'
  1. String
chain = PullChain.new(Foo.new) << "say('John', 'Goodbye')"
chain.call # => John said 'Goodbye'
  1. Proc
chain = PullChain.new(Foo.new) << Proc.new { say('Julian', 'Nice to meet you') }
chain.call # => Julian said 'Nice to meet you'
  1. 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
  1. 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
  1. String
chain = PullChain.new([1,2,3,4,5])
chain << "inject(3) { |sum, n| sum + n }"
chain.call # => 18
  1. Proc
chain = PullChain.new([1,2,3,4,5])
chain << Proc.new { inject(3) { |sum, n| sum + n } }
chain.call # => 18
  1. 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, message)
    puts "#{speaker} said '#{message}'"
  end
end
foo = Foo.new(Bar.new(Baz.new))
Example: use result on chain
  1. 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'
  1. Array
    Can access result by Proc.
chain = PullChain.new(foo) << :bar << :baz
chain << [:say, Proc.new { next bar.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.bar.speaker, "Oh" }
chain << [:say, arg_reader]
chain.call # => Julian said 'Oh'

etc

  1. 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
  1. 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
  1. Note:use operator in chain
  • String type chain

    table = {name: %w(Bill Scott Paul)}
    PullChain.new(table, "[:name]").call # => [:name] NG
    PullChain.new(table, "self[:name]").call # => ["Bill", "Scott", "Paul"] OK
  • Array type chain

    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
  1. Some classes, such Fixnum and Bignum not supported
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

  1. Strings separated by a slash
chain = RelayChain.new(Decorator.new, "decorate1/decorate2")
chain.call("Hello")
  1. Use >> operator
chain = RelayChain.new(Decorator.new)
chain >> :decorate1 >> :decorate2
chain.call("Hello")
  1. Use Method object
chain = RelayChain.new
chain >> decorator.method(:decorate1) >> decorator.method(:decorate2)
chain.call("Hello")
  1. Use add
chain = RelayChain.new(Decorator.new)
chain.add(:decorate1).add(:decorate2).call("Hello")
  1. 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
  1. Array, format is [instance, Symbol or String of method]
# 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 ) } ]
  1. String, use registered instance
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
  1. 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
  1. 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.

About

FunctionChain is Ruby Gem, and objectifies of the method chain.

Resources

Stars

3 stars

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages