include_module
There are some general problems with mixins, and a lot of people complain about them:
Using mixins like this is akin to “cleaning” a messy room by dumping the clutter into six separate junk drawers and slamming them shut. Sure, it looks cleaner at the surface, but the junk drawers actually make it harder to identify and implement the decompositions and extractions necessary to clarify the domain model.
- Mixins considered harmful/1 (Python)
Injecting methods into a class namespace is a bad idea for a very simple reason: every time you use a mixin, you are actually polluting your class namespace and losing track of the origin of your methods.
- Mixins Considered Harmful (JavaScript)
- Mixins introduce implicit dependencies
- Mixins cause name clashes
- Mixins cause snowballing complexity
The include_module gem was created to help you to use mixing (aka module
in Ruby)
explicitly.
It is a simple library with just 90 LOC, with
zero dependencies, without any monkey patches and method overridings.
Usage
There are 2 common ways to use mixins in Ruby.
module UserMixin
def self.included(base)
base.extend(ClassMethods)
base.class_eval do
belongs_to :account
end
end
module ClassMethods
def top_user
User.order(rating: :desc).first
end
end
def name
"#{first_name} #{last_name}"
end
end
class User
include UserMixin
end
This is a standard way of using mixins in Ruby. The problem here is that when
you look at the User
you have no idea which methods the UserMixin
defines in the class.
module UserMixin
extend ActiveSupport::Concern
included do
belongs_to :account
end
class_methods do
def top_user
User.order(rating: :desc).first
end
end
def name
"#{first_name} #{last_name}"
end
end
class User
include UserMixin
end
By extending your module, you add .class_methods
which basically hides
ClassMethods
module inside, overrides .append_features
and .included
methods.
Additionally it keeps tracking how many times .included
was called to raise a custom exception about multiple "included" blocks.
But mostly ActiveSupport::Concern
has the same problem as standard Module.included
.
Why can't we define explicitly which methods we would like to use from a mixin?
include_module
module UserMixin
module ClassMethods
def top_user
User.order(rating: :desc).first
end
end
INCLUDED = proc do
belongs_to :account
end
def name
"#{first_name} #{last_name}"
end
end
class User
extend IncludeModule
extend_module UserMixin::ClassMethods, methods: [:top_user]
include_module UserMixin, included: true, methods: [:name]
end
Now when you look at the class you can see which methods "included_module" adds in it. And it's all done without any monkey patches or method overridings.
It is almost in-place replacement for ActiveSupport::Concern
. Here is a diff:
module UserMixin
- extend ActiveSupport::Concern
-
- included do
+ INCLUDED = proc do
belongs_to :account
end
...
class User
+ extend IncludeModule
+
- include_module UserMixin
+ extend_module UserMixin::ClassMethods, methods: [:top_user]
+ include_module UserMixin, included: true, methods: [:name]
end
You can also control which methods you would like to include, rename them while including in order to avoid name clashes, etc. Please see more examples below.
How Does It Work?
- Include no methods
When you add extend IncludeModule
, you add just 2 public methods called include_module
and extend_module
in
your class.
class User
extend IncludeModule
include_module UserMixin
end
By default when you call include_module
without any options, it won't load anything.
- Include all methods
If you want to include everything from your module, you can pass the following options:
class User
extend IncludeModule
include_module UserMixin, included: true, methods: :all
end
It will include
you module, extend
your ClassMethods
and class_eval
your
INCLUDED
proc without any changes.
- Include some methods
You can define explicitly which methods you want to include from you module:
module TestModule
def foo
:foo
end
def bar
:bar
end
end
class TestClass
extend IncludeModule
include_module TestModule, methods: [:foo]
end
In this case we have only TestClass#foo
method implemented.
It basically creates a new anonymous module which contains only specified methods and includes it as usual.
- Rename included method
class User
extend IncludeModule
include_module UserMixin, methods: [name: :full_name]
end
We can rename methods while including a mixin by creating a new anonymous module as in the previous example.
So User#full_name
method will be available instead of User#name
.
Installation
Install Ruby version >= 2.1.
Add this line to your application's Gemfile:
gem 'include_module'
And then execute:
$ bundle
Or install it yourself as:
$ gem install include_module
Development
Install development dependencies:
gem install bundler
bundle install
Run tests:
make test
To release a new version, update the version number in include_module.gemspec
,
and then run:
make release
Which will create a git tag for the version, push git commits and tags, and push
the .gem
file to rubygems.org.
What's New?
Please read the changelog.
Contributing
Bug reports and pull requests are welcome on GitHub at https://github.com/exAspArk/include_module.
To open a pull request:
- Fork it
- Create your feature branch (
git checkout -b my-new-feature
) - Commit your changes (
git commit -am 'Add some feature'
) - Push to the branch (
git push origin my-new-feature
) - Create a new Pull Request
License
The gem is available as open source under the terms of the MIT License.