Project

is-dsl

0.0
No release in over 3 years
Facade pattern realization for DSL development.
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
 Dependencies

Development

~> 13.3
~> 3.6
~> 3.13
~> 0.22.0
~> 0.9
 Project Readme
EN ru

is-dsl

GitHub License Gem Version Ruby Coverage

The IS::DSL module implements the Facade design pattern and is primarily intended for defining various kinds of DSL (Domain Specific Language).

How it works

The principle is simple: we define a module, execute extend IS::DSL within the module context or externally, and specify a set of constants and methods that it will delegate to other modules and/or singleton objects.

Why is this needed?

Primary use case: We have a set of logically related modules and classes, some of their methods are needed for interaction between them (and should thus be marked as public), but are not needed for the external library user. For this external user, we implement a "facade" — a module containing only what's necessary.

This becomes especially useful for DSL, when we want to provide (via include) a set of methods without prefixes and namespaces, while not exposing too much to reduce the likelihood of conflicts and unwanted overrides.

Delegated methods are defined via module_function, allowing their use both as DSL methods through include and as singleton methods of the module like Mod::my_method.

Shadow Module

Additionally, a shadow module is created, which is largely analogous to the main module and also serves as a "facade", but is intended for name shortening INSIDE the library. Using the attach_to method, it is assigned as a constant to some internal module or class.

Methods and constants delegated via encapsulate go to both the main module and the shadow, while those via shadow_encapsulate go only to the shadow.

Lazy Methods

In Ruby, singleton logic can be implemented in two ways — through singleton methods of modules and classes, or more traditionally (as in other languages) — through calling a single singleton method of the class like instance, which always returns the same class instance. Lazy encapsulation is designed for the second approach.

Example

module MyLib

  class Report

    # Some stuff

    class << self

      def all_reports
        # implementation
      end

    end

  end

  class ReportManager

    class << self

      def instance
        @instance ||= new
      end

      private :new

    end

    def get_report name
      MyLib::all_reports.find name
    end

    # Some stuff

  end

end

module ML
  extend IS::DSL

  encapsulate MyLib::Report, new: :create_report
  shadow_encapsulate MyLib::Report, :all_reports

  lazy_encapsulate :get_report do 
    MyLib::ReportManager.instance
  end

  attach_to MyLib::ReportManager
end

Here we get two methods: create_report directly calls MyLib::Report::new, while get_report calls MyLib::ReportManager.instance.get_report, with MyLib::ReportManager.instance being called only once on the first access to ML::get_report.

The attach_to call allows us inside ReportManager to use ML::create_report instead of MyLib::Report::new, and ML::all_reports instead of MyLib::Report::all_reports (in this example it doesn't give much benefit, but in a large library with a complex class structure it can be very useful).