Behaves is a gem that helps you define behaviors between classes. Say goodbye to runtime error when defining behaviors.
Behaves is especially useful for dealing with adapter patterns by making sure that all of your adapters define the required behaviors. See usage below for more examples.
Detailed explanations in the sections below.
Add this line to your application's Gemfile:
This is how you define behaviors with
First, define required methods on the
Behavior Object with the
implements method, which take a list of methods.
class Animal extend Behaves implements :speak, :eat end
Then, you can turn any object (the
Behaving Object) to behave like the
Behavior Object by using the
behaves_like method, which takes a
class Dog extend Behaves behaves_like Animal end
Voilà, that's all it takes to define behaviors! Now if
Dog does not implement
eat, your code will then throw error on file load, instead of at runtime.
- NotImplementedError: Expected `Dog` to behave like `Animal`, but `speak, eat` are not implemented.
This is in stark contrast to defining behaviors with inheritance. Let's take a look.
# Inheritance - potential runtime error. class Animal def speak raise NotImplementedError, "Animals need to be able to speak!" end def eat raise NotImplementedError, "Animals need to be able to eat!" end end class Dog < Animal def speak "woof" end end
It is unclear that
Doghas a certain set of behaviors to adhere to.
Dogdoes not implement
#eat? Inheritance-based behaviors have no guarantee that
Dogadheres to a certain set of behaviors, which means you can run into runtime errors like this.
corgi = Dog.new corgi.eat # => NotImplementedError, "Animals need to be able to eat!"
- Another problem is you have now defined
Animal#eat, two stub methods of which they do nothing but raise an undesirable
The power of
Behaves does not stop here either.
Behaves allow you to define multiple behavior for a single behaving object. This is not possible with inheritance.
class Predator extend Behaves implements :hunt end class Prey extend Behaves implements :run, :hide end class Shark extend Behaves # Shark is both a `Predator` and a `Prey` behaves_like Predator behaves_like Prey end
When someone decides to use
behaves to define behaviors, they in turn lose the ability to utilize some other aspect of inheritance, one of it being inheriting methods.
Behaves now ship with a feature called
inject_behaviors for that need!
class Dad extend Behaves implements :speak, :eat inject_behaviors do def traits; "Dad's traits!"; end end end class Child extend Behaves behaves_like Dad def speak; "BABA"; end def eat; "NOM NOM"; end end # Child.new.traits #=> "Dad's traits!"
This extends to more than just method implementation too, you can do anything you want! That's because the code inside
inject_behaviors run in the context of the
Behaving Object, also
injected_behaviors refers to the
Do note that if you use this extensively, you might be better off using inheritance, since this will create more
Method objects than inheritance.
Private behaviors can be defined like so:
class Interface extend Behaves implements :foo implements :bar, private: true end class Implementor extend Behaves behaves_like Interface def foo 123 end private def bar 456 end end
If you do not want to type
extend Behaves every time, you can monkey patch
Object class, like so:
I found that the current idiom to achieve
behaviors in Ruby is through inheritence, and then subsequently defining 'required' methods, which does nothing except raising a
NotImplementedError. This approach is fragile, as it does not guarantee behaviors, runs the risk of runtime errors, and has an opaque implementation.
Thus with this comes the birth of
Also referring to the article by José Valim, I really liked the idea of being able to use Mock as a noun. However, while the idea sounds good, you've now introduced a new problem in your codebase -- your Mock and your original Object might deviate from their implementation later on. Not a good design if it breaks. Elixir has
@callback built in to keep them in sync.
Behaves is inspired by that.
After checking out the repo, run
bin/setup to install dependencies. Then, run
rake spec to run the tests. You can also run
bin/console for an interactive prompt that will allow you to experiment.
To install this gem onto your local machine, run
bundle exec rake install. To release a new version, update the version number in
version.rb, and then run
bundle exec rake release, which will create a git tag for the version, push git commits and tags, and push the
.gem file to rubygems.org.
Bug reports and pull requests are welcome on GitHub at https://github.com/edisonywh/behaves. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the Contributor Covenant code of conduct.
The gem is available as open source under the terms of the MIT License.