{"id":15527217,"url":"https://github.com/andyobtiva/super_module","last_synced_at":"2025-11-11T11:39:03.163Z","repository":{"id":15432614,"uuid":"18165198","full_name":"AndyObtiva/super_module","owner":"AndyObtiva","description":"SuperModule allows defining class methods and method invocations the same way a super class does without using def included(base). This also succeeds ActiveSupport::Concern by offering lighter syntax","archived":false,"fork":false,"pushed_at":"2024-07-31T16:19:02.000Z","size":187,"stargazers_count":141,"open_issues_count":0,"forks_count":4,"subscribers_count":4,"default_branch":"master","last_synced_at":"2025-04-05T05:10:04.312Z","etag":null,"topics":["extension","mixin","mixins","rails","ruby","supermodule"],"latest_commit_sha":null,"homepage":"","language":"Ruby","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/AndyObtiva.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2014-03-27T05:16:44.000Z","updated_at":"2025-01-31T15:46:07.000Z","dependencies_parsed_at":"2024-07-31T20:15:36.742Z","dependency_job_id":null,"html_url":"https://github.com/AndyObtiva/super_module","commit_stats":{"total_commits":133,"total_committers":4,"mean_commits":33.25,"dds":0.09774436090225569,"last_synced_commit":"2152357b6520b080a0816ee035d155df83390815"},"previous_names":[],"tags_count":11,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/AndyObtiva%2Fsuper_module","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/AndyObtiva%2Fsuper_module/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/AndyObtiva%2Fsuper_module/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/AndyObtiva%2Fsuper_module/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/AndyObtiva","download_url":"https://codeload.github.com/AndyObtiva/super_module/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247289429,"owners_count":20914464,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["extension","mixin","mixins","rails","ruby","supermodule"],"created_at":"2024-10-02T11:05:06.495Z","updated_at":"2025-11-11T11:39:03.134Z","avatar_url":"https://github.com/AndyObtiva.png","language":"Ruby","readme":"# \u003cimg src=\"https://raw.githubusercontent.com/AndyObtiva/super_module/master/SuperModule.jpg\" alt=\"SuperModule\" align=\"left\" height=\"50\" /\u003e \u0026nbsp; SuperModule 1.4.2\n[![Gem Version](https://badge.fury.io/rb/super_module.svg)](http://badge.fury.io/rb/super_module)\n[![Coverage Status](https://coveralls.io/repos/AndyObtiva/super_module/badge.svg?branch=master)](https://coveralls.io/r/AndyObtiva/super_module?branch=master)\n[![Code Climate](https://codeclimate.com/github/AndyObtiva/super_module.svg)](https://codeclimate.com/github/AndyObtiva/super_module)\n\n(Note: super_module includes features that involve heavy use of meta-programming, so unless you really need it, prefer using pure Ruby modules when sufficient)\n\n[SuperModule](https://rubygems.org/gems/super_module) provides a simpler and more intuitive solution than `ActiveRecord::Concern` that enables developers to continue to use Ruby modules as first-class citizens with mixin inheritance even when wanting to inherit singleton-class methods and invocations.\n\nCalling [Ruby](https://www.ruby-lang.org/en/)'s [`Module#include`](http://ruby-doc.org/core-2.2.1/Module.html#method-i-include) to mix in a module does not bring in class methods by default. This can come as quite the surprise when attempting to include class methods via a module.\n\nRuby offers one workaround in the form of implementing the hook method [`Module.included(base)`](http://ruby-doc.org/core-2.2.1/Module.html#method-i-included) [following a certain boilerplate code idiom](http://www.railstips.org/blog/archives/2009/05/15/include-vs-extend-in-ruby/). Unfortunately, it hinders code maintainability and productivity with extra unnecessary complexity, especially in production-environment projects employing many [mixins](http://en.wikipedia.org/wiki/Mixin) (e.g. modeling business domain models with composable object [traits](http://en.wikipedia.org/wiki/Trait_(computer_programming))).\n\nAnother workaround is [`ActiveSupport::Concern`](http://api.rubyonrails.org/classes/ActiveSupport/Concern.html), a Rails library that attempts to ease some of the boilerplate pain by offering a [DSL](http://www.infoq.com/news/2007/06/dsl-or-not) layer on top of [`Module.included(base)`](http://ruby-doc.org/core-2.2.1/Module.html#method-i-included). Unfortunately, while it helps improve readability a bit, it adds even more boilerplate idiom cruft, thus feeling no more than putting a band-aid on the problem.\n\nBut do not fear, [SuperModule](https://rubygems.org/gems/super_module) comes to the rescue! By declaring your module as a SuperModule, it will simply behave as one would expect and automatically include class methods along with instance methods, without any further work needed.\n\nUsed in my other project: [Glimmer DSL for SWT](https://github.com/AndyObtiva/glimmer-dsl-swt) (JRuby Desktop Development GUI Framework)\n\nWorks in both [Ruby](https://www.ruby-lang.org) and [JRuby](https://www.jruby.org).\n\n## Introductory Comparison\n\nTo introduce [SuperModule](https://rubygems.org/gems/super_module), here is a comparison of three different approaches for writing a\n`UserIdentifiable` module, which includes ActiveModel::Model module as an in-memory alternative to `ActiveRecord::Base` superclass.\n\n#### 1) [self.included(base)](http://ruby-doc.org/core-2.2.1/Module.html#method-i-included)\n\n```ruby\nmodule UserIdentifiable\n  include ActiveModel::Model\n\n  def self.included(base)\n    base.extend(ClassMethods)\n    base.class_eval do\n      belongs_to :user\n      validates :user_id, presence: true\n    end\n  end\n\n  module ClassMethods\n    def most_active_user\n      User.find_by_id(select('count(id) as head_count, user_id').group('user_id').order('count(id) desc').first.user_id)\n    end\n  end\n\n  def slug\n    \"#{self.class.name}_#{user_id}\"\n  end\nend\n```\n\nThis is a lot to think about and process for simply wanting inclusion of class method definitions (like \u003ccode\u003emost_active_user\u003c/code\u003e) and class method invocations (like \u003ccode\u003ebelongs_to\u003c/code\u003e and \u003ccode\u003evalidates\u003c/code\u003e). The unnecessary complexity gets in the way of problem-solving; slows down productivity with repetitive boiler-plate code; and breaks expectations set in other similar object-oriented languages, discouraging companies from including [Ruby](https://www.ruby-lang.org/en/) in a polyglot stack, such as [Groupon](http://www.groupon.com)'s [Rails/JVM/Node.js](https://engineering.groupon.com/2013/misc/i-tier-dismantling-the-monoliths/) stack and [SoundCloud](http://www.soundcloud.com)'s [JRuby/Scala/Clojure stack](https://developers.soundcloud.com/blog/building-products-at-soundcloud-part-3-microservices-in-scala-and-finagle).\n\n#### 2) [ActiveSupport::Concern](http://api.rubyonrails.org/classes/ActiveSupport/Concern.html)\n\n```ruby\nmodule UserIdentifiable\n  extend ActiveSupport::Concern\n  include ActiveModel::Model\n\n  included do\n    belongs_to :user\n    validates :user_id, presence: true\n  end\n\n  module ClassMethods\n    def most_active_user\n      User.find_by_id(select('count(id) as head_count, user_id').group('user_id').order('count(id) desc').first.user_id)\n    end\n  end\n\n  def slug\n    \"#{self.class.name}_#{user_id}\"\n  end\nend\n```\n\nA step forward that addresses the boiler-plate repetitive code concern, but is otherwise no more than putting a band-aid on the problem. To explain more, developer problem solving and creativity flow is still disrupted by having to think about the lower-level mechanism of running code on inclusion (using `included`) and structuring class methods in an extra sub-module (`ClassMethods`) instead of simply declaring class methods like they normally would in Ruby and staying focused on the task at hand.\n\n#### 3) [SuperModule](https://github.com/AndyObtiva/super_module)\n\n```ruby\nmodule UserIdentifiable\n  include SuperModule\n  include ActiveModel::Model\n\n  belongs_to :user\n  validates :user_id, presence: true\n\n  def self.most_active_user\n    User.find_by_id(select('count(id) as head_count, user_id').group('user_id').order('count(id) desc').first.user_id)\n  end\n\n  def slug\n    \"#{self.class.name}_#{user_id}\"\n  end\nend\n```\nBy including `SuperModule` (following Ruby's basic convention of relying on a module), developers can directly add class method invocations and definitions inside the module's body, and [`SuperModule`](https://github.com/AndyObtiva/super_module) takes care of automatically mixing them into classes that include the module.\n\nAs a result, [SuperModule](https://rubygems.org/gems/super_module) collapses the difference between extending a super class and including a [super module](#glossary-and-definitions), thus encouraging developers to write simpler code while making better Object-Oriented Design decisions.\n\nIn other words, [SuperModule](https://rubygems.org/gems/super_module) furthers Ruby's goal of making programmers happy.\n\nP.S. this library intentionally avoids bad techniques like \"eval\" of entire module body since they do not maintain Module mixin inheritance support. SuperModule supports full Ruby module mixin inheritance as it does not change it, yet only adds automation for singleton-class method inheritance on top of it (via surgical class_eval instead of full eval). SuperModule in fact encourages developers to continue to rely on basic Ruby code like `include SuperModule`.\n\n## Instructions\n\n#### 1) Install and require gem\n\n\u003cb\u003eUsing [Bundler](http://bundler.io/)\u003c/b\u003e\n\nAdd the following to Gemfile: \u003cpre\u003egem 'super_module', '1.4.2'\u003c/pre\u003e\n\nAnd run the following command: \u003cpre\u003ebundle\u003c/pre\u003e\n\nAfterwards, [SuperModule](https://rubygems.org/gems/super_module) will automatically get required in the application (e.g. a Rails application) and be ready for use.\n\n\u003cb\u003eUsing [RubyGem](https://rubygems.org/gems/super_module) Directly\u003c/b\u003e\n\nRun the following command: \u003cpre\u003egem install super_module\u003c/pre\u003e\n\n(add \u003ccode\u003e--no-ri --no-rdoc\u003c/code\u003e if you wish to skip downloading documentation for a faster install)\n\nAdd the following at the top of your [Ruby](https://www.ruby-lang.org/en/) file: \u003cpre\u003erequire 'super_module'\u003c/pre\u003e\n\n#### 2) Simply include SuperModule at the top of your module definition before anything else.\n\n```ruby\nmodule UserIdentifiable\n  include SuperModule\n  include ActiveModel::Model\n\n  belongs_to :user\n  validates :user_id, presence: true\n\n  def self.most_active_user\n    User.find_by_id(select('count(id) as head_count, user_id').group('user_id').order('count(id) desc').first.user_id)\n  end\n\n  def slug\n    \"#{self.class.name}_#{user_id}\"\n  end\nend\n```\n\nNote: Even if you are including another [super module](#glossary-and-definitions) in your new [super module](#glossary-and-definitions), you must `include SuperModule` at the top of your module definition before anything else.\n\n#### 3) Mix newly defined module into a class or another [super module](#glossary-and-definitions)\n\n```ruby\nclass ClubParticipation \u003c ActiveRecord::Base\n  include UserIdentifiable\nend\nclass CourseEnrollment \u003c ActiveRecord::Base\n  include UserIdentifiable\nend\nmodule Accountable\n  include SuperModule\n  include UserIdentifiable\nend\nclass Activity \u003c ActiveRecord::Base\n  include Accountable\nend\n```\n\n#### 4) Start using by invoking class methods or instance methods\n\n```ruby\nCourseEnrollment.most_active_user\nClubParticipation.most_active_user\nActivity.last.slug\nClubParticipation.create(club_id: club.id, user_id: user.id).slug\nCourseEnrollment.new(course_id: course.id).valid?\n```\n\n## Usage Notes\n\n * SuperModule must always be included at the top of a module's body at [code-time](#glossary-and-definitions)\n * SuperModule inclusion can be optionally followed by other basic or [super module](#glossary-and-definitions) inclusions\n * A [super module](#glossary-and-definitions) can only be included in a class or another [super module](#glossary-and-definitions)\n\n## Glossary and Definitions\n\n* SuperModule: name of the library and Ruby module that provides functionality via mixin\n* Super module: any Ruby module that mixes in SuperModule\n* Singleton class: also known as the [metaclass](https://rubymonk.com/learning/books/4-ruby-primer-ascent/chapters/39-ruby-s-object-model/lessons/131-singleton-methods-and-metaclasses) or [eigenclass](http://eigenjoy.com/2008/05/29/railsconf08-meta-programming-ruby-for-fun-and-profit/), it is the object-instance-associated class copy available to every object in Ruby (e.g. every `Object.new` instance has a singleton class that is a copy of the `Object` class, which can house instance-specific behavior if needed)\n* Singleton method: an instance method defined on an object's singleton class. Often used to refer to a class or module method defined on the [Ruby class object or module object singleton class](http://ruby-doc.com/docs/ProgrammingRuby/html/classes.html) via `def self.method_name(...)` or `class \u003c\u003c self` enclosing `def method_name(...)`\n* Class method invocation: Inherited Ruby class or module method invoked in the body of a class or module (e.g. \u003ccode\u003evalidates :username, presence: true\u003c/code\u003e)\n* Code-time: Time of writing code in a Ruby file as opposed to Run-time\n* Run-time: Time of executing Ruby code\n\n## IRB Example\n\nCreate a ruby file called super_module_irb_example.rb with the following content:\n\n```ruby\nrequire 'rubygems' # to be backwards compatible with Ruby 1.8.7\nrequire 'super_module'\n\nmodule RequiresAttributes\n include SuperModule\n\n def self.requires(*attributes)\n   attributes.each {|attribute| required_attributes \u003c\u003c attribute}\n end\n\n def self.required_attributes\n   @required_attributes ||= []\n end\n\n def requirements_satisfied?\n   !!self.class.required_attributes.reduce(true) { |result, required_attribute| result \u0026\u0026 send(required_attribute) }\n end\nend\n\nclass MediaAuthorization\n include RequiresAttributes\n attr_accessor :user_id, :credit_card_id\n requires :user_id, :credit_card_id\nend\n```\n\nOpen `irb` ([Interactive Ruby](https://www.ruby-lang.org/en/documentation/quickstart/)) and paste the following code snippets in. You should get the output denoted by the rockets (`=\u003e`).\n\n```ruby\nrequire './super_module_irb_example.rb'\n```\n=\u003e true\n\n```ruby\nMediaAuthorization.required_attributes\n```\n=\u003e [:user_id, :credit_card_id]\n\n```ruby\nmedia_authorization = MediaAuthorization.new # resulting object print-out varies\n```\n=\u003e #\u003cMediaAuthorization:0x832b36be1\u003e\n\n```ruby\nmedia_authorization.requirements_satisfied?\n```\n=\u003e false\n\n```ruby\nmedia_authorization.user_id = 387\n```\n=\u003e 387\n\n```ruby\nmedia_authorization.requirements_satisfied?\n```\n=\u003e false\n\n```ruby\nmedia_authorization.credit_card_id = 37\n```\n=\u003e 37\n\n```ruby\nmedia_authorization.requirements_satisfied?\n```\n=\u003e true\n\n## Overriding `self.included(base)`\n\nWith `SuperModule`, hooking into `self.included(base)` is no longer needed for most cases. Still, there rare exceptions where that might be needed to execute some meta-programmatic logic. Fortunately, `SuperModule` offers a mechanism to do so.\n\n`SuperModule` relies on `self.included(base)`, so modules mixing it in must refrain from implementing `self.included(base)` directly (`SuperModule` will automatically prevent that by providing instructions should one attempt to do so).\n\nIn order for a [super module](#glossary-and-definitions) to hook into `self.included(base)` and add extra logic, it must do so via `super_module_included {|base| ... }` instead, which safely appends that logic to the work of `SuperModule` as well as other nested [super module](#glossary-and-definitions)s.\n\nExample:\n\n```ruby\nmodule V1::SummarizedActiveModel\n  include SuperModule\n\n  super_module_included do |klass|\n    if klass.name.split(/::/).last.start_with?('Fake')\n      klass.extend(FakeClassMethods1)\n    end\n  end\n\n  module FakeClassMethods1\n    def fake_summary\n      'This is a fake summary.'\n    end\n  end\n\n  class \u003c\u003c self\n    def self.validates(attribute, options = {})\n      validations \u003c\u003c [attribute, options]\n    end\n\n    def self.validations\n      @validations ||= []\n    end\n\n    def summary\n      validations.flatten.map(\u0026:to_s).join(\"/\")\n    end\n  end\nend\n\nmodule V1::ExtraSummarizedActiveModel\n  include SuperModule\n\n  include ::V1::SummarizedActiveModel\n\n  super_module_included do |klass|\n    if klass.name.split(/::/).last.start_with?('Fake')\n      klass.extend(FakeClassMethods2)\n    end\n  end\n\n  module FakeClassMethods2\n    def fake_extra\n      'This is fake extra.'\n    end\n  end\n\n  class \u003c\u003c self\n    def extra\n      \"This is extra.\"\n    end\n  end\nend\n\nclass V1::SummarizedActiveRecord\n  include ::V1::SummarizedActiveModel\nend\n\nclass V1::FakeSummarizedActiveRecord\n  include ::V1::SummarizedActiveModel\nend\n\nclass V1::ExtraSummarizedActiveRecord\n  include ::V1::ExtraSummarizedActiveModel\nend\n\nclass V1::FakeExtraSummarizedActiveRecord\n  include ::V1::ExtraSummarizedActiveModel\nend\n\nV1::SummarizedActiveRecord.validates 'foo', {:presence =\u003e true}\nV1::SummarizedActiveRecord.validates 'bar', {:presence =\u003e true}\nputs V1::SummarizedActiveRecord.summary\n# prints 'foo/{:presence=\u003etrue}/bar/{:presence=\u003etrue}'\n\nV1::FakeSummarizedActiveRecord.validates 'foo', {:presence =\u003e true}\nV1::FakeSummarizedActiveRecord.validates 'bar', {:presence =\u003e true}\nputs V1::FakeSummarizedActiveRecord.summary\n# prints 'foo/{:presence=\u003etrue}/bar/{:presence=\u003etrue}'\nputs V1::FakeSummarizedActiveRecord.fake_summary\n# prints 'This is a fake summary.'\n\nV1::ExtraSummarizedActiveRecord.validates 'foo', {:presence =\u003e true}\nV1::ExtraSummarizedActiveRecord.validates 'bar', {:presence =\u003e true}\nputs V1::ExtraSummarizedActiveRecord.summary\n# prints 'foo/{:presence=\u003etrue}/bar/{:presence=\u003etrue}'\nputs V1::ExtraSummarizedActiveRecord.extra\n# prints 'This is extra.'\n\nV1::FakeExtraSummarizedActiveRecord.validates 'foo', {:presence =\u003e true}\nV1::FakeExtraSummarizedActiveRecord.validates 'bar', {:presence =\u003e true}\nputs V1::FakeExtraSummarizedActiveRecord.summary\n# prints 'foo/{:presence=\u003etrue}/bar/{:presence=\u003etrue}'\nputs V1::FakeExtraSummarizedActiveRecord.fake_summary\n# prints 'This is a fake summary.'\nputs V1::FakeExtraSummarizedActiveRecord.extra\n# prints 'This is extra.'\nputs V1::FakeExtraSummarizedActiveRecord.fake_extra\n# prints 'This is fake extra.'\n```\n\n## Limitations\n\n[SuperModule](https://rubygems.org/gems/super_module) by definition has been designed to be used only in the initial code declaration of a module, not later mixing or re-opening of a module.\n\n## Change Log\n\n[CHANGELOG.md](https://github.com/AndyObtiva/super_module/blob/master/CHANGELOG.md)\n\n## Feedback and Contribution\n\n[SuperModule](https://rubygems.org/gems/super_module) 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:\nhttps://github.com/AndyObtiva/super_module\n\nThe library is quite novel 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](https://github.com/AndyObtiva/super_module#fork-destination-box) in order to [make contributions via Pull Requests](https://github.com/AndyObtiva/super_module/pulls).\n\n## Articles, Publications, and Blog Posts\n * 2015-04-05 - [Ruby Weekly](http://rubyweekly.com): [Issue 240](http://rubyweekly.com/issues/240)\n * 2014-03-27 - [Code Painter](http://andymaleh.blogspot.com) Blog Post: [Ruby SuperModule Comes To The Rescue!!](http://andymaleh.blogspot.ca/2014/03/ruby-supermodule-comes-to-rescue.html)\n\n## TODO\n\nNone\n\n## Copyright\n\nCopyright (c) 2014-2024 Andy Maleh. See [LICENSE.txt](LICENSE.txt) for further details.\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fandyobtiva%2Fsuper_module","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fandyobtiva%2Fsuper_module","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fandyobtiva%2Fsuper_module/lists"}