Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Ruby Module helper gem to implement traits behavior with easy

branch: master

Fetching latest commit…

Octocat-spinner-32-eaf2f5

Cannot retrieve the latest commit at this time

Octocat-spinner-32 lib
Octocat-spinner-32 spec
Octocat-spinner-32 .gitignore
Octocat-spinner-32 Gemfile
Octocat-spinner-32 LICENSE
Octocat-spinner-32 README.md
Octocat-spinner-32 Rakefile
Octocat-spinner-32 penetrator.gemspec
README.md

Penetrator

This gem aimed to help improving code reuse in ruby projects. Highly inspired by http://github.com/makandra/modularity gem but slightly modified for supporting conventional super inheritance methods chaining. Also much of code was shamelessly borrowed from ActiveSupport::Concern so I should say thanks that Ruby Hackers, who wrote it. All that what left to do for me - just to take the best from both worlds.

Installation

Add this line to your application's Gemfile:

gem 'penetrator'

And then execute:

$ bundle

Or install it yourself:

$ gem install penetrator

Usage

(Rails specific example)

config/application.rb

    config.autoload_paths += Rails.root.join( 'app', 'traits' )

File: app/controllers/traits/crudable_trait.rb

      module CrudableTrait
        extend Penetrator::Concern

        included do
         helper_method :resource, :resources # they are will be used in views
        end
        #
        # Implementation
        public

        def index
          respond_to do |format|
            format.html { render layout: take_layout }
            format.json { render json:   resources   }
            format.js
          end
        end

        def show
          respond_to do |format|
            format.html { render layout: take_layout }
            format.json { render json:   resource    }
            format.js
          end
        end

        # ... and so on ...

        private
          def take_layout
             # ...
          end

          def resource
            @_resource ||= resource_class.find(params[:id])
          end

          def resources
            @_resources ||= resource_class.order(default_order).all
          end
       end

File: app/controllers/accomodations_controller.rb

      class AccomodationsController < ApplicationController
        #
        # CrudableTrait assumes that this mehod exists
        private
        def resource_class
          Accomodation
        end

        behave_like "crudable"

        # Override public traits method
        def index
          if current_user.is_admin?
            # ...
          else
            super
          end
        end

        private

        # Override traits methods
        #
        def default_order
          "accomodations.name desc"
        end

        public

        # Override traits methods
        # with respecting call chaining
        #
        def kill_all_humans
          "Yes" or super
        end

      end

What makes this gem different from ActiveSupport::Concern ? Well, here you can parameterize your included modules-traits! (Extracted from spec/coerce_spec.rb )

File: app/traits/can_have_args_trait.rb

    module CanHaveArgsTrait
      extend Penetrator::Concern
      included do |*args|
        args.each do |method_name|
          define_method(method_name) do
            method_name.to_s + "-chunked!"
          end
        end
      end # included
    end # CanHaveArgs

File: app/models/my_model.rb

    class Victim
      behaves_like :CanHaveArgs, 'first', 'second'
    end

    obj = Victim.new

    obj.first   # => first-chunked!
    obj.second  # => second-chunked!

Also you can freely utilize ClassMethods internal module as you usually do with ActiveSupport::Concern

    module RichTrait
      extend Penetrator::Concern
      module ClassMethods
        def class_method
          # ... add what you want ...
        end
      end

      def instance_method
      end
    end

You can even extend arbitrary instance of any class with your trait:

    module HtmlSanitizerTrait
      extend Penetrator::Concern

      def cleanup
        # ...
      end
    end

    string_of_dirty_html = "Something <span>dirty</span> and even <marquee>fearing ugly</marquee>"
    string_of_dirty_html.behave_like 'html_sanitizer'

behave_like also accepts block which can be used in included section. I have no idea whom need that, but decided to make it possible.

    module HtmlSanitizerTrait
      included do |*args, block|
        args.each do |method_name|
          define_method(method_name) do
            method_name.to_s
          end
       end
       block.call if block
     end # included

    class VictimWithBlock
      behaves_like :HtmlSanitizerTrait, 'cleanup_processor' do
        class_variable_set(:@@foo, "I'm set")
        # ... something useful
      end
    end

Contributing

  1. Fork it
  2. Create your feature branch (git checkout -b my-new-feature)
  3. Commit your changes (git commit -am 'Added some feature')
  4. Push to the branch (git push origin my-new-feature)
  5. Create new Pull Request
Something went wrong with that request. Please try again.