Origin
Origin provides ease-to-use macros for delegating methods from a class to another object. It behaves like delegate
Crystal's stdlib macro, but with type declaration support and simpler declaration.
This kind of macro is really interesting when, for example, using the Decorator Pattern.
It supports:
- types
- arguments
- blocks
Installation
dependencies:
origin:
github: pyrsmk/origin
version: ~> 0.1.0
Usage
Basics
Origin has two macros: wire
and autowire
. They work in a similar way by relying on the definition of an @origin
instance variable with the object you want to wire on.
See the following very simple example using wire
:
require "origin"
class Animal
def head
1
end
def ears
2
end
def legs
4
end
end
class Human
wire head, to: head
wire ears, to: ears
def initialize(@origin : Animal); end
def legs
2
end
def arms
2
end
end
human = Human.new(Animal.new)
# Prints `2`
puts human.legs
# Prints `2`
puts human.ears
# Prints `1`
puts human.head
That is, we don't need to re-implement head
and ears
since we don't want to change their behavior.
Internally, the code wire head, to: head
compiles to:
def head(*args, **options)
@origin.head(*args, **options)
end
def head(*args, **options)
@origin.head(*args, **options) do |*yield_args|
yield *yield_args
end
end
That means wire
can handle arguments and blocks as well.
Wire a method to another name
wire
is also useful when you want to plug to a method with another name. Let's use the same example with an Animal
and a Human
:
class Animal
def tail
"I'm a tail!"
end
end
class Human
wire tailbone, to: tail
def initialize(@origin : Animal); end
end
# Prints `"I'm a tail!"`
puts Human.new(Animal.new).tailbone
Return types
At this point, you may think: But what about return type definition?. Here's how we can declare types:
class Animal
def head : Int32; end
def ears : Int32; end
def eyes : Int32; end
def nose : Bool; end
def tailbone : String; end
end
class Human
wire head : Int32, to: head
wire ears : Int32, to: ears
wire eyes : Int32, to: eyes
wire nose : Bool, to: nose
wire tailbone : String, to: tail
def initialize(@origin : Animal); end
end
Setters and special method names
There's a special syntax we had not talk about yet. If you want to wire setters or some weird method names like <<
or []?
you would see that the compiler's complaining. Hence, for these cases, you need to use a symbol.
Just note that if you want to have a return type for special method names, you'll need to explicitly define it with the return_type
option (but you won't be able to use this trick with autowire
).
Here's a more concrete example:
class StringCollection
@items = [] of String
def <<(item : String)
@items << item
end
def []=(index : Int32, item : String)
@items[index] = item
end
def [](index : Int32) : String
@items[index]
end
def []?(index : Int32) : String?
@items[index]?
end
end
class SymbolToStringCollection
wire :[], return_type: String, to: :[]
wire :[]?, return_type: String?, to: :[]?
def initialize(@origin : StringCollection); end
def <<(item : Symbol)
@origin << item.to_s
end
def []=(index : Int32, item : Symbol)
@origin[index] = item.to_s
end
end
collection = SymbolToStringCollection.new(StringCollection.new)
collection << :foo
collection[0] = :bar
# Prints `"bar"`
puts collection[0]?
# Prints `nil`
puts collection[1]?
Auto-wiring
After we approached how wire
works, we can talk about autowire
. It wraps wire
behavior and simplify the declaration for methods you don't need to rename:
class Animal
def head : Int32; end
def ears : Int32; end
def eyes : Int32; end
def nose : Bool; end
end
class Human
autowire head : Int32,
ears : Int32,
eyes : Int32,
node : Bool
def initialize(@origin : Animal); end
end
Contributing
Before contributing don't hesitate to ask if your idea of a new feature could be accepted before developing it.
- Fork the project (https://github.com/pyrsmk/origin/fork)
- Initialize the repository (
make init
) - Create your feature branch (
git checkout -b feature/my-new-feature
) - Write your code and exhaustive tests (
make test
) - Push and create a new Pull Request
- Wait for the maintainers to approve or request changes
- Merge your work 🎉️