![]() |
ActiveSupport::Concern to accomplish that goal?Well, worry no more! SuperModule comes to the rescue!
SuperModule allows defining class methods and method invocations the same way a super class does without using
self.included(base).This succeeds
ActiveSupport::Concern by offering lighter syntax and simpler module dependency support.Introductory Comparison
To introduce SuperModule, here is a comparison of three different approaches for writing a UserIdentifiable module.
1) self.included(base)
- module UserIdentifiable
- include ActiveModel::Model
- def self.included(base_klass)
- base_klass.extend(ClassMethods)
- base.class_eval do
- belongs_to :user
- validates :user_id, presence: true
- end
- end
- module ClassMethods
- def most_active_user
- User.find_by_id(select('count(id) as head_count, user_id').group('user_id').order('count(id) desc').first.user_id)
- end
- end
- def slug
- "#{self.class.name}_#{user_id}"
- end
- end
2) ActiveSupport::Concern
- module UserIdentifiable
- extend ActiveSupport::Concern
- include ActiveModel::Model
- included do
- belongs_to :user
- validates :user_id, presence: true
- end
- module ClassMethods
- def most_active_user
- User.find_by_id(select('count(id) as head_count, user_id').group('user_id').order('count(id) desc').first.user_id)
- end
- end
- def slug
- "#{self.class.name}_#{user_id}"
- end
- end
3) SuperModule
- module UserIdentifiable
- include SuperModule
- include ActiveModel::Model
- belongs_to :user
- validates :user_id, presence: true
- def self.most_active_user
- User.find_by_id(select('count(id) as head_count, user_id').group('user_id').order('count(id) desc').first.user_id)
- end
- def slug
- "#{self.class.name}_#{user_id}"
- end
- end
SuperModule takes care of automatically mixing them into classes that include the module.As a result, SuperModule collapses the difference between extending a super class and including a super module, thus encouraging developers to write simpler code while making better Object-Oriented Design decisions.
In other words, SuperModule furthers Ruby's goal of making programmers happy.
Instructions
1) Install and require gem
Using Bundler
Add the following to Gemfile:
- gem 'super_module', '1.0.0'
- bundle
Using RubyGem Directly
Run the following command:
- gem install super_module
Add the following at the top of your Ruby file:
- require 'super_module'
- module UserIdentifiable
- include SuperModule
- include ActiveModel::Model
- belongs_to :user
- validates :user_id, presence: true
- def self.most_active_user
- User.find_by_id(select('count(id) as head_count, user_id').group('user_id').order('count(id) desc').first.user_id)
- end
- def slug
- "#{self.class.name}_#{user_id}"
- end
- end
- class ClubParticipation < ActiveRecord::Base
- include UserIdentifiable
- end
- class CourseEnrollment < ActiveRecord::Base
- include UserIdentifiable
- end
- module Accountable
- include SuperModule
- include UserIdentifiable
- end
- class Activity < ActiveRecord::Base
- include Accountable
- end
- CourseEnrollment.most_active_user
- ClubParticipation.most_active_user
- Activity.last.slug
- ClubParticipation.create(club_id: club.id, user_id: user.id).slug
- CourseEnrollment.new(course_id: course.id).valid?
- SuperModule: name of the library and Ruby module that provides functionality via mixin
- Super module: any Ruby module that mixes in SuperModule
- Class method definition: Ruby class or module method declared with
self.method_nameorclass << self - Class method invocation: Inherited Ruby class or module method invoked in the body of a class or module (e.g. validates :username, presence: true)
- Code-time: Time of writing code in a Ruby file as opposed to Run-time
- Run-time: Time of executing Ruby code
- SuperModule must always be included at the top of a module's body at code-time
- SuperModule inclusion can be optionally followed by other basic or super module inclusions
- A super module can only be included in a class or another super module
- SuperModule adds zero cost to instantiation of including classes and invocation of included methods (both class and instance)
How Does It Work?
Here is the general algorithm from the implementation:
- def included(base)
- __invoke_super_module_class_method_calls(base)
- __define_super_module_class_methods(base)
- end
- 1) Invoke super module class method calls on the including base class.
For example, suppose we have a super module called Locatable:
- module Locatable
- include SuperModule
- validates :x_coordinate, numericality: true
- validates :y_coordinate, numericality: true
- def move(x, y)
- self.x_coordinate += x
- self.y_coordinate += y
- end
- end
- class Vehicle < ActiveRecord::Base
- include Locatable
- # … more code follows
- end
This first step guarantees invocation of the two
Locatable validates method calls on the Vehicle object class.
It does so by relying on method_missing(method_name, *args, &block) to record every class method call that happens in the super module class body, and later replaying those calls on the including base class during self.included(base) by using Ruby's send(method_name, *args, &block) method introspection.
2) Defines super module class methods on the including base class
For example, suppose we have a super module called Addressable:
- module Addressable
- include SuperModule
- include Locatable
- validates :city, presence: true, length: { maximum: 255 }
- validates :state, presence: true, length: { is: 2 }
- def self.merge_duplicates
- # 1. Look through all Addressable instances in the database
- # 2. Identify duplicates
- # 3. Merge duplicate addressables
- end
- end
- class Contact < ActiveRecord::Base
- include Addressable
- # … more code follows
- end
The second step ensures that merge_duplicates is included in Contact as a class method, allowing the call Contact.merge_duplicates
It does so by recording every class method defined using the self.singleton_method_added(method_name) added hook, and then later replaying these class definitions on the including base class during invocation of self.included(base).
In order to avoid interference with existing class method definitions, there is an exception list for what not to record, such as included, method_missing, singleton_method_added, and any other "__" prefixed class method defined in SuperModule (e.g. __super_module_class_method_calls).
Limitations and Caveats
- SuperModule has been designed to be used only in the code definition of a module, not to be mixed in at run-time.
- Initial Ruby runtime load of a class or module mixing in SuperModule will incur a very marginal performance hit (in the order of nano-to-milliseconds). However, class usage (instantiation and method invocation) will not incur any performance hit, running as fast as any other Ruby class.
- Given SuperModule relies on self.included(base) in its implementation, if an including super module (or a super module including another super module) must hook into self.included(base)for meta-programming cases that require it, such as conditional include statements or method definitions, it would have to alias self.included(base) and then invoke the aliased version in every super module that needs it like in this example:
- module AdminIdentifiable
- include SuperModule
- include UserIdentifiable
- class << self
- alias included_super_module included
- def included(base)
- included_super_module(base)
- # do some extra work
- # like conditional inclusion of other modules
- # or conditional definition of methods
- end
- end
In the future, SuperModule could perhaps provide robust built-in facilities for allowing super modules to easily hook into self.included(base) without interfering with SuperModule behavior.
Given SuperModule relies on method_missing(method_name, args, &block) inside
class << self, a super module including it that needs to do some additional method_missing (method_missing(method_name, args, &block) meta-programming must not only alias it, but also be mindful of implications on SuperModule behavior.
Feedback and Contribution
SuperModule is written in a very clean and maintainable test-first approach, so you are welcome to read through the code on GitHub for more in-depth details:
https://github.com/AndyObtiva/super_module
The library is quite new and can use all the feedback and help it can get. So, please do not hesitate to add comments if you have any, and please fork the project on GitHub in order to make contributions via Pull Requests.
Written by Andy Maleh
If you found this post interesting, follow and support us.
Suggest for you:

No comments:
Post a Comment