SkillAgentSearch skills...

MooseX

MooseX - a new DSL for object creation in Ruby

Install / Use

/learn @peczenyj/MooseX
About this skill

Quality Score

0/100

Supported Platforms

Universal

README

MooseX

A postmodern object DSL for Ruby Build Status Gem Version Code Climate Dependency Status Coverage Status githalytics.com alpha

Introduction

This is another DSL for object creation, aspects, method delegation and much more. It is based on Perl Moose and Moo, two important modules who add a better way of Object Orientation development (and I enjoy A LOT). Using a declarative style, using Moose/Moo you can create attributes, methods, the entire constructor and much more. But I can't find something similar in Ruby world, so I decide port a small subset of Moose to create a powerfull DSL for object construction.

Want to help? Install the gem moosex and start to use. If you find some issue, please assign to me :)

Of course, there is few similar projects in ruby like

But the objetive of MooseX is different: this is a toolbox to create Classes based on DSL, with unique features like

  • method delegation and currying ( see 'handles')
  • lazy attributes
  • roles / abstract classes / interfaces
  • traits / monads
  • plugins
  • parameterized roles
  • composable type check
  • events

and much more.

This rubygem is based on this modules:

See also:

  • Reindeer, another Moose port to Ruby (still on 0.0.1 version)
  • Joose, a javascript port of Moose.
  • Perl 6, Perl 6 OO programming style.
  • Elk, Elk is an object system for Python inspired by Moose.

Why MooseX? Because the namespace MooseX/MooX is open to third-party projects/plugins/extensions. You can upgrade your Moo(se) class using other components if you want. And there is one gem called 'moose' :/

THIS MODULE IS EXPERIMENTAL YET! BE CAREFUL!

Talk is cheap. Show me the code!

require 'moosex'

class Point
  include MooseX

  has x: {
    is: :rw,      # read-write (mandatory)
    isa: Integer, # should be Integer
    default: 0,   # default value is 0 (constant)
  }

  has y: {
    is: :rw,
    isa: Integer,
    default: -> { 0 }, # you should specify a lambda
  }

  def clear! 
    self.x= 0     # to run with type-check you must
    self.y= 0     # use the setter instad @x=
  end

  def to_s
    "Point[x=#{self.x}, y=#{self.y}]"
  end 
end 

# now you have a generic constructor
p1  = Point.new                       # x and y will be 0
p2  = Point.new( x:  5 )              # y will be 0
p3  = Point.new( x:  5, y: 4)

Installation

Add this line to your application's Gemfile:

gem 'moosex'

And then execute:

$ bundle

Or install it yourself as:

$ gem install moosex

You need ruby 2.0.x or superior.

Description

MooseX is an extension of Ruby object system. The main goal of MooseX is to make Ruby Object Oriented programming easier, more consistent, and less tedious. With MooseX you can think more about what you want to do and less about the mechanics of OOP. It is a port of Moose/Moo from Perl to Ruby world.

Read more about Moose on http://moose.iinteractive.com/en/

Motivation

It is fun

Usage

You just need include the MooseX module in your class and start to describe the attributes with our DSL. This module will inject one smart constructor, acessor and other necessary methods.

Instead the normal way of add accessors, constructor, validation, etc

class Foo
  attr_accessor :bar, :baz, :bam

  def initialize(bar=0, baz=0, bam=0)
    unless [bar, baz, bam].all? {|x| x.is_a? Integer }
      raise "you should use only Integers to build Foo"
    end
    @bar = bar
    @baz = baz
    @bam = bam
  end
end

you can do this:

class Foo
  include MooseX

  has [:bar, :baz, :bam], {
    is: :rw,
    isa: Integer,
    default: 0
  }
  
  # you can declare inline too
  has :another, is: :rw, isa: Integer, default: ->{ Object.new }
end

DSL: the 'has' method

The key of the DSL is the 'has' method injected in your class. You should use this method do describe your class and define the behavior like this:

has :attribute_name, { hash of properties }

to describe one new attribute you shoud specify some properties inside a Hash. The only mandatory property is the ':is', to specify how we should create the acessors (if public or private).

The options for "has" are as follows:

is => ro|rw|rwp|private|lazy

Important, may be :ro, :rw, :rwp, :private or :lazy. If you not specify, we will consider :rw, with all acessors with public visibility (NEW).

"ro" specify a read-only attribute - generate only the reader method - you should specify the value in the constructor or using "default".

"rw" specify a read-write attribute - generate both reader and writter methods.

"rwp" specify a read-write private attribute. Similar to "rw" but the writter is a private method.

"private" will generate both reader and writter as private methods

"lazy" similar to "ro", but also sets "lazy" to true and "builder" to "build_#{attribute_name}".

isa => Class|lambda

You can specify an optional type check for the attribute. Accepts a lambda, and it must raise one exception if the type check fails. If you provides a Class or Module, we will call the 'is_a?' method in the new value againt the Class/Module. We call the type check routine on the constructor and in each call of the writter method.

You can specify your own kind of type validation.

    isa: ->(value) do
      unless value.respond_to? :to_sym
        raise "bar should respond to to_sym method!"
      end
    end,

Important: if you access the attribute instance name using @attribute_name= you loose the type check feature. You need always set/get the attribute value using the acessors generated by MooseX.

default => Constant|lambda

You can specify an optional default value to one attribute. If we don't specify in the constructor, we will initialize the attribute with this value. You also can specify one lambda to force object creation.

  default: 0,

or

  default: -> { MyObject.new },

required => true|false

if true, the constructor will raise error if this attribute was not present.

  required: true,

if this attribute has a default value, we will initialize with this value and no exception will be raised.

Optional.

coerce => method name|lambda

You can try to coerce the attribute value by a lambda/method before the type check phase. For example you can do

  coerce: ->(value) { value.to_i },

or just

  coerce: :to_i,

to force a convertion to integer. Or flatten one array, convert to symbol, etc. Optional.

handles => Array|Hash|Class|Module

One of the greatest features in MooseX: you can inject methods and delegate the method calling to the attribute. For example, instead do this:

  def some_method(a,b,c)
    @attribute.some_method(a,b,c)
  end

you simply specify one or more methods to inject.

  handles: [:some_method],

If you specify one Module or Class, we will handle all public instance methods defined in that Module or Class ( if Class, we will consider all methods except the methods declared in the superclass). The only limitation is BasicObject (forbidden).

If you need rename the method, you can specify a Hash:

  handles: {
    my_method_1: :method1,
    my_method_2: :method2,
  },

handles is similar to Forwardable module, the difference is the currying support and it is integrate with the has method/endpoint. If you want to use Forwardable, please use the reader method name instead the attribute name with @.

Optional.

Currying

It is possible curry constant values declaring a pair/hash and set one or more constant values / lambdas

  handles: {
    my_method_1: {
     method1: 1   
    }
  },

this will curry the constant 1 to the argument list. In other words:

obj.target.method1(1,2,3) # OR
obj.my_method_1(2,3)

are equivalent. You can curry as many arguments as you can.

  handles: {
    my_method_2: {
     method2: [1, ->{ 2 } ]   
    }
  },

will generate

obj.target.method1(1,2,3) # OR
obj.my_method_2(3)

are equivalent. if we find one lambda we will call on runtime.

Important: if you need do something more complex ( like manipulate the argument list, etc ) consider use the hook 'around'.

But how we can curry arrays?

Use Double arrays

  handles: {
    my_method_1: {
     method1: [ [1,2,3] ]  
    }
  },

this will curry the array [1,2,3] to the argument list. In other words:

obj.target.method1([1,2,3],2,3) # OR
obj.my_method_1(2,3)

are e

View on GitHub
GitHub Stars118
CategoryDevelopment
Updated1y ago
Forks5

Languages

Ruby

Security Score

80/100

Audited on Mar 1, 2025

No findings