dry-behaviour
is a ruby implementation of Elixir protocols.
For those who sees this concept for the first time, here is a quick example.
module ToString
include Dry::Protocol
defprotocol do
defmethod :dump, :this
defimpl target: User do
def dump(this)
"#{this.name} <#{this.email}>"
end
end
defimpl target: Post do
def dump(this)
"#{this.title} <#{this.date}>"
end
end
end
end
ToString.dump(User.last)
#⇒ Aleksei <am@mudasobwa.ru>
ToString.dump(Post.last)
#⇒ Allow Implicit Inheritance <2018-10-11>
Basically, this is another way to implement a polymorhism in ruby, isolated and allowing avoid monkeypatching at all.
In the last release we have significantly improved error handling, including, but not limited to:
- very descriptive, elixirish error messages that include possible causes, suggestions on how to resolve the issue, etc
- the whole stacktrace is carefully saved with
cause
- the error object does now store all the environment where the exception occured
- internal exceptions related to wrong implementation do now point to the proper lines in the client code (internal trace lines are removed).
Here is the example of the error message:
Protocols
implementation is very flexible. Besides direct implementation, it allows delegation of methods to the target object). Before this version we allowed the implicit delegation when the method was not implemented:
module Namer
include Dry::Protocol
defprotocol do
defmethod :email, :this
defimpl target: User do
end
end
end
Namer.email(User.last)
#⇒ am@mudasobwa.ru
There are problems with implicitness. Always. Sooner or later you’ll get into very hard to understand and debug issue, induced by some indirect initialization. Yeah, the warning message about implicit delegation was printed during class loading, but who does read these messages.
In the latest version we’ve deprecated implicit delegation with the intent to remove it completely in 1.0
and raise if the implementation is incomplete.
Instead we now allow implicit delegation to super
(parent protocol implementation) provided the protocol definition is allowing this.
module Namer
include Dry::Protocol
- defprotocol do
+ defprotocol implicit_inheritance: true do
defmethod :email
def email(this)
this.email
end
defimpl target: User do
- def email(this)
- super(this)
- end
end
end
end
Also, the most complete environment is stored within the exception to simplify debug if there are some issues:
Foo = Class.new
ex =
begin
Namer.email(Foo.new, true)
rescue => e
e
end
#⇒ Protocol “Namer” is not implemented for “Foo”.>
ex.details
#⇒ {:method=>:email,
# :receiver=>#<Foo:0x00560922447308>,
# :args=>[true],
# :self=>Namer,
# :message=>"Protocol “Namer” is not implemented for “Foo”."}
As one might see, this minor release is mostly focused on polishing error handling, documentation, “explicit-over-implicit” and cosmetics. It brings only one new feature (implicit inheritance inside protocol ancestors tree,) and even that was induced by the necessity to removedeprecate the implicit delegation.
I perfectly understand now why José is constantly repeating “documentaion is the first class citizen.” The code is now way more pleasantly to use. Enjoy!
For those who is curious about how the library is implemented under the hood, there is more technical introduction and, of course, the source is open.
Happy behaving!