• Stars
    star
    138
  • Rank 264,508 (Top 6 %)
  • Language
    Ruby
  • License
    MIT License
  • Created over 10 years ago
  • Updated almost 3 years ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

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

SuperModule Β  SuperModule 1.4.1

Gem Version Coverage Status Code Climate

(Note: despite the advanced version number, the idea of super_module is highly experimental and relies heavily on meta-programming, so unless you really need it, always prefer using pure Ruby modules when sufficient)

SuperModule 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.

Calling Ruby's Module#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.

Ruby offers one workaround in the form of implementing the hook method Module.included(base) following a certain boilerplate code idiom. Unfortunately, it hinders code maintainability and productivity with extra unnecessary complexity, especially in production-environment projects employing many mixins (e.g. modeling business domain models with composable object traits).

Another workaround is ActiveSupport::Concern, a Rails library that attempts to ease some of the boilerplate pain by offering a DSL layer on top of Module.included(base). 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.

But do not fear, SuperModule 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.

Used in my other project: Glimmer DSL for SWT (JRuby Desktop Development GUI Framework)

Works in both Ruby and JRuby.

Introductory Comparison

To introduce SuperModule, here is a comparison of three different approaches for writing a UserIdentifiable module, which includes ActiveModel::Model module as an in-memory alternative to ActiveRecord::Base superclass.

1) self.included(base)

module UserIdentifiable
  include ActiveModel::Model

  def self.included(base)
    base.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

This is a lot to think about and process for simply wanting inclusion of class method definitions (like most_active_user) and class method invocations (like belongs_to and validates). 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 in a polyglot stack, such as Groupon's Rails/JVM/Node.js stack and SoundCloud's JRuby/Scala/Clojure stack.

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

A 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.

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

By 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 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.

P.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.

Instructions

1) Install and require gem

Using Bundler

Add the following to Gemfile:

gem 'super_module', '1.4.1'

And run the following command:

bundle

Afterwards, SuperModule will automatically get required in the application (e.g. a Rails application) and be ready for use.

Using RubyGem Directly

Run the following command:

gem install super_module

(add --no-ri --no-rdoc if you wish to skip downloading documentation for a faster install)

Add the following at the top of your Ruby file:

require 'super_module'

2) Simply include SuperModule at the top of your module definition before anything else.

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

Note: Even if you are including another super module in your new super module, you must include SuperModule at the top of your module definition before anything else.

3) Mix newly defined module into a class or another super module

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

4) Start using by invoking class methods or instance methods

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?

Usage Notes

  • 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

Glossary and Definitions

  • SuperModule: name of the library and Ruby module that provides functionality via mixin
  • Super module: any Ruby module that mixes in SuperModule
  • Singleton class: also known as the metaclass or eigenclass, 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)
  • 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 via def self.method_name(...) or class << self enclosing def method_name(...)
  • 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

IRB Example

Create a ruby file called super_module_irb_example.rb with the following content:

require 'rubygems' # to be backwards compatible with Ruby 1.8.7
require 'super_module'

module RequiresAttributes
 include SuperModule

 def self.requires(*attributes)
   attributes.each {|attribute| required_attributes << attribute}
 end

 def self.required_attributes
   @required_attributes ||= []
 end

 def requirements_satisfied?
   !!self.class.required_attributes.reduce(true) { |result, required_attribute| result && send(required_attribute) }
 end
end

class MediaAuthorization
 include RequiresAttributes
 attr_accessor :user_id, :credit_card_id
 requires :user_id, :credit_card_id
end

Open irb (Interactive Ruby) and paste the following code snippets in. You should get the output denoted by the rockets (=>).

require './super_module_irb_example.rb'

=> true

MediaAuthorization.required_attributes

=> [:user_id, :credit_card_id]

media_authorization = MediaAuthorization.new # resulting object print-out varies

=> #MediaAuthorization:0x832b36be1

media_authorization.requirements_satisfied?

=> false

media_authorization.user_id = 387

=> 387

media_authorization.requirements_satisfied?

=> false

media_authorization.credit_card_id = 37

=> 37

media_authorization.requirements_satisfied?

=> true

Overriding self.included(base)

With 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.

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).

In order for a super module 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 modules.

Example:

module V1::SummarizedActiveModel
  include SuperModule

  super_module_included do |klass|
    if klass.name.split(/::/).last.start_with?('Fake')
      klass.extend(FakeClassMethods1)
    end
  end

  module FakeClassMethods1
    def fake_summary
      'This is a fake summary.'
    end
  end

  class << self
    def self.validates(attribute, options = {})
      validations << [attribute, options]
    end

    def self.validations
      @validations ||= []
    end

    def summary
      validations.flatten.map(&:to_s).join("/")
    end
  end
end

module V1::ExtraSummarizedActiveModel
  include SuperModule

  include ::V1::SummarizedActiveModel

  super_module_included do |klass|
    if klass.name.split(/::/).last.start_with?('Fake')
      klass.extend(FakeClassMethods2)
    end
  end

  module FakeClassMethods2
    def fake_extra
      'This is fake extra.'
    end
  end

  class << self
    def extra
      "This is extra."
    end
  end
end

class V1::SummarizedActiveRecord
  include ::V1::SummarizedActiveModel
end

class V1::FakeSummarizedActiveRecord
  include ::V1::SummarizedActiveModel
end

class V1::ExtraSummarizedActiveRecord
  include ::V1::ExtraSummarizedActiveModel
end

class V1::FakeExtraSummarizedActiveRecord
  include ::V1::ExtraSummarizedActiveModel
end

V1::SummarizedActiveRecord.validates 'foo', {:presence => true}
V1::SummarizedActiveRecord.validates 'bar', {:presence => true}
puts V1::SummarizedActiveRecord.summary
# prints 'foo/{:presence=>true}/bar/{:presence=>true}'

V1::FakeSummarizedActiveRecord.validates 'foo', {:presence => true}
V1::FakeSummarizedActiveRecord.validates 'bar', {:presence => true}
puts V1::FakeSummarizedActiveRecord.summary
# prints 'foo/{:presence=>true}/bar/{:presence=>true}'
puts V1::FakeSummarizedActiveRecord.fake_summary
# prints 'This is a fake summary.'

V1::ExtraSummarizedActiveRecord.validates 'foo', {:presence => true}
V1::ExtraSummarizedActiveRecord.validates 'bar', {:presence => true}
puts V1::ExtraSummarizedActiveRecord.summary
# prints 'foo/{:presence=>true}/bar/{:presence=>true}'
puts V1::ExtraSummarizedActiveRecord.extra
# prints 'This is extra.'

V1::FakeExtraSummarizedActiveRecord.validates 'foo', {:presence => true}
V1::FakeExtraSummarizedActiveRecord.validates 'bar', {:presence => true}
puts V1::FakeExtraSummarizedActiveRecord.summary
# prints 'foo/{:presence=>true}/bar/{:presence=>true}'
puts V1::FakeExtraSummarizedActiveRecord.fake_summary
# prints 'This is a fake summary.'
puts V1::FakeExtraSummarizedActiveRecord.extra
# prints 'This is extra.'
puts V1::FakeExtraSummarizedActiveRecord.fake_extra
# prints 'This is fake extra.'

Limitations

SuperModule 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.

Change Log

CHANGELOG.md

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.

Articles, Publications, and Blog Posts

TODO

None

Copyright

Copyright (c) 2014-2020 Andy Maleh. See LICENSE.txt for further details.

More Repositories

1

glimmer-dsl-libui

Glimmer DSL for LibUI - Prerequisite-Free Ruby Desktop Development Cross-Platform Native GUI Library - The Quickest Way From Zero To GUI - If You Liked Shoes, You'll Love Glimmer! - No need to pre-install any prerequisites. Just install the gem and have platform-independent GUI that just works on Mac, Windows, and Linux.
Ruby
497
star
2

glimmer

DSL Framework consisting of a DSL Engine and a Data-Binding Library used in Glimmer DSL for SWT (JRuby Desktop Development GUI Framework), Glimmer DSL for Opal (Pure Ruby Web GUI), Glimmer DSL for LibUI (Prerequisite-Free Ruby Desktop Development GUI Library), Glimmer DSL for Tk (Ruby Tk Desktop Development GUI Library), Glimmer DSL for GTK (Ruby-GNOME Desktop Development GUI Library), Glimmer DSL for XML (& HTML), and Glimmer DSL for CSS
Ruby
395
star
3

how-to-build-desktop-applications-in-ruby

Code Exercises for RubyConf 2023 Workshop: How To Build Desktop Applications in Ruby (Andy Maleh)
Ruby
178
star
4

glimmer-dsl-swt

Glimmer DSL for SWT (JRuby Desktop Development Cross-Platform Native GUI Framework) - The Quickest Way From Zero To GUI - If You Liked Shoes, You'll Love Glimmer!
Ruby
107
star
5

glimmer-dsl-web

Glimmer DSL for Web (Ruby-in-the-Browser Web Frontend Framework). The "Rails" of Frontend Frameworks!!!
Ruby
90
star
6

puts_debuggerer

Ruby library for improved puts debugging, automatically displaying bonus useful information such as source line number and source code.
Ruby
86
star
7

strategic

Strategic - Painless Strategy Pattern in Ruby and Rails
Ruby
47
star
8

ultra_light_wizard

No time to manage a wizard state machine, session variables, or complicated controllers? Use ultra light wizard!! A RESTful session-less validation-friendly simple multi-step form approach in Rails.
Ruby
41
star
9

glimmer-cs-gladiator

Gladiator (Glimmer Editor) - Glimmer Custom Shell
Ruby
31
star
10

glimmer-dsl-tk

Glimmer DSL for Tk (Ruby Tk Desktop Development GUI Library)
Ruby
30
star
11

perfect-shape

Perfect Shape is a collection of geometric algorithms that are mostly useful for GUI manipulation like checking containment of a point in popular geometric shapes such as rectangle, square, arc, circle, polygon, and paths containing lines, quadratic bΓ©zier curves, and cubic bezier curves. Also, some general math algorithms like IEEE-754 Remainder.
Ruby
29
star
12

glimmer-dsl-opal

Glimmer DSL for Opal (Pure-Ruby Web GUI and Auto-Webifier of Desktop Apps)
Ruby
26
star
13

abstract_feature_branch

abstract_feature_branch is a Ruby gem that provides a variation on the Branch by Abstraction Pattern by Paul Hammant and the Feature Toggles Pattern by Martin Fowler (aka Feature Flags) to enable Continuous Integration and Trunk-Based Development.
Ruby
23
star
14

glimmer-dsl-gtk

Glimmer DSL for GTK - Ruby-GNOME Desktop Development GUI Library
Ruby
21
star
15

glimmer-dsl-wx

Glimmer DSL for WX - Ruby Desktop Development GUI Library for the wxWidgets GUI toolkit and wxruby3 binding
Ruby
13
star
16

are-we-there-yet

Are We There Yet? Small Project Tracking Desktop App for Windows and Mac. Built with Glimmer (Ruby Desktop Development GUI Library)
Ruby
13
star
17

glimmer-dsl-css

Glimmer DSL for CSS (Cascading Style Sheets)
Ruby
10
star
18

glimmer_wordle

Glimmer Wordle - Play Wordle Endlessly with No Limit!
Ruby
10
star
19

connector

A minimalist open-source multi-engine web browser built in Ruby with Glimmer DSL for SWT
Ruby
10
star
20

opal-async

Non-blocking tasks and enumerators for Opal.
Ruby
9
star
21

glimmer-libui-cc-graphs_and_charts

Graphs and Charts (Glimmer DSL for LibUI Custom Controls)
Ruby
9
star
22

yasl

Yet Another Serialization Library - A pure Ruby auto-serialization library that works across different Ruby implementations like Opal and JRuby as an automatic alternative to YAML/Marshal. Unlike Marshal, it does not raise errors for unserializable objects, thus provides a highly productive friction-free auto-serialization experience.
Ruby
9
star
23

rake-tui

Rake Text-based User Interface
Ruby
8
star
24

rails_best_practices

Rails Best Practices
7
star
25

dcr

Draw Color Repeat - A Young Child Programming Language for Drawing and Coloring with Repetition
Ruby
7
star
26

glimmer-dsl-xml

Glimmer DSL for XML (& HTML)
Ruby
6
star
27

array_include_methods

Array#include_all?, Array#include_any?, Array#include_array?, Array#array_index, Array#array_diff_indices, Array#array_intersection_indices, Array#counts, and Array#duplicates operations missing from basic Ruby Array API
Ruby
6
star
28

bundler-download

Bundler plugin for auto-downloading specified extra files after gem install
Ruby
5
star
29

contact_manager

Contact Manager is a Glimmer DSL for SWT (JRuby Desktop Development GUI Framework) sample leveraging SQLite DB via ActiveRecord.
Ruby
4
star
30

MathBowling

Bowling game that tests math skills.
Ruby
4
star
31

coffee_queue_meteor

CoffeeQueue implemented in Meteor
CSS
4
star
32

glimmer-cs-calculator

Calculator - Glimmer Custom Shell
Ruby
3
star
33

baseball_cards

Opal Ruby on Rails 7 Advanced Example
Ruby
3
star
34

glimmer_klondike_solitaire

Glimmer Klondike Solitaire - Glimmer External Sample
Ruby
3
star
35

glimmer_tetris

Glimmer Tetris
Ruby
3
star
36

rails-gui

Rails GUI (e.g. display routes in a table, run rails commands visually, etc...)
Ruby
3
star
37

glimmer_snake

Classic Snake game (popularized by Nokia phones)
Ruby
3
star
38

rvm-tui

Ruby enVironment Manager - Text-based User Interface
Ruby
2
star
39

glimmer-cw-browser-chromium

Chromium Browser - Glimmer Custom Widget
Ruby
2
star
40

pure-struct

Pure Ruby Re-Implementation of Struct
Ruby
2
star
41

glimmer-dsl-jfx

Glimmer DSL for JFX (JRuby JavaFX Desktop Development GUI Library)
Ruby
2
star
42

easily_typable

Ruby module that facilitates English-like type checking in an inheritance hierarchy via "type_name?" methods
Ruby
1
star
43

glimmer_metronome

Glimmer Metronome supports different beat counts, click sounds, and tempos, including tap-based tempo calculation.
Ruby
1
star
44

glimmer-cs-timer

Glimmer Timer App and Custom Shell/Widget
Ruby
1
star
45

pivotal-jobs

Pivotal Tracker background jobs
JavaScript
1
star
46

dehumanize

Inverse of ActiveSupport::Inflector#humanize
Ruby
1
star
47

desktopify

Desktopify Your Web Apps! With Glimmer (Ruby Desktop Development GUI Framework)!
Ruby
1
star
48

github-repo-code-search

GitHub Repo Code Search via Solr
Ruby
1
star
49

ruby-bash

User-Friendly Versions of Bash Commands Built in Ruby
Ruby
1
star
50

conference_room_booker

Cycle.js example of a conference room booking app demonstrating advanced features
Ruby
1
star
51

coffee_queue

Coffee Ordering and Fulfillment Application built with Ruby on Rails, Batman.js, and Pusher
Ruby
1
star
52

glimmer-cw-cdatetime-nebula

Nebula CDateTime Widget - Glimmer Custom Widget
Ruby
1
star
53

glimmer-dsl-swing

Glimmer DSL for Swing (JRuby Swing Desktop Development GUI Library) - Enables development of desktop applications using Java Swing and Java 2D, including vector graphics and AWT geometry.
Ruby
1
star
54

sample-glimmer-dsl-opal-rails-app

Sample Glimmer DSL for Opal Rails App
Ruby
1
star
55

glimmer-cw-video

Video custom widget for Glimmer DSL for SWT
Ruby
1
star
56

glimmer-dsl-fx

Glimmer DSL for FX (FOX Toolkit Ruby Desktop Development GUI Library)
Ruby
1
star
57

sample-glimmer-dsl-web-rails7-app

Sample Glimmer DSL for Web Rails 7 Application
Ruby
1
star
58

garderie_rainbow_daily_agenda

Garderie Rainbow Daily Agenda
Ruby
1
star
59

color_the_circles

Color The Circles game built in Ruby with Glimmer DSL for LibUI
Ruby
1
star