JLine is not loading in maven-scala-plugin with scala-console

For unknown reason maven-scala-plugin is not loading properly scala:console. After compile my app and run

mvn scala:console

It raises

Failed to created JLineReader: java.lang.NoClassDefFoundError: scala/tools/jline/console/completer/Completer
Falling back to SimpleReader.
Welcome to Scala version 2.9.1.final (Java HotSpot(TM) 64-Bit Server VM, Java 1.6.0_29).
Type in expressions to have them evaluated.
Type :help for more information.

scala>

Even though application context works, the collateral effect for this issue is you cannot neither get typed commands by up-arrow key nor get autocomple working by tab key.

I’ve fixed just including in my pom.xml the jline dependency

<dependency>
  <groupId>org.scala-lang</groupId>
  <artifactId>jline</artifactId>
  <version>2.9.0-1</version>
</dependency>

It’s not elegant but works. I’m looking for an environmental fix for this instead of a fix per project.

Indexing files with Scala and Elasticsearch

I was doing load testing in Elasticsearch., I’ve created a simple code in Scala to fetch files recursively and index them to Elasticsearch.

The code uses Java Mime Magic Library as a helper to get file description.

So let’s get started installing Elasticsearch

Installing and start Elasticsearch

curl -O http://cloud.github.com/downloads/elasticsearch/elasticsearch/elasticsearch-0.18.7.tar.gz
tar zxf elasticsearch-0.18.7.tar.gz
cd elasticsearch-0.18.7
bin/elasticsearch -f

Remove -f out if you don’t want start it in foreground.

Scala code

Our Scala code have 3 functions, one to list all files

def fetchFiles(path:String)(op:File => Unit){
  for (file <- new File(path).listFiles if !file.isHidden){
    op(file)
    if (file.isDirectory){
      fetchFiles(file.getAbsolutePath)(op)
    }
  }
}

A function to create the JSON.

def document(file:File) = {

  val json = jsonBuilder.startObject
  .field("name",file.getName)
  .field("parent",file.getParentFile.getAbsolutePath)
  .field("path",file.getAbsolutePath)
  .field("last_modified",new Date(file.lastModified))
  .field("size",file.length)
  .field("is_directory", file.isDirectory)

  if (file.isFile) {
    try{
      val m = Magic.getMagicMatch(file, true)
      json.field("description",m.getDescription)
      .field("extension",m.getExtension)
      .field("mimetype",m.getMimeType)
    }catch {
      case _ => json.field("description","unknown")
        .field("extension",file.getName.split("\\.").last.toLowerCase)
        .field("mimetype","application/octet-stream")
    }
  }
  json.endObject
}

Only files will be passed to Magic detection, there’s a treatment in case detector gets issue parsing the file. It’ll generate the final format to be indexed.

  {
      "name": "pragmatic-guide-to-git_p1_0.pdf",
      "parent": "/Users/shairon/Reference",
      "path": "/Users/shairon/Reference/pragmatic-guide-to-git_p1_0.pdf",
      "last_modified": "2010-11-26T18:55:43.000Z",
      "size": 1358963,
      "is_directory": false,
      "description": "PDF document",
      "extension": "pdf",
      "mimetype": "application/pdf"
  }

And finally the main

def main(args: Array[String]) = {
    val dir = new File(args(0))
    if (!dir.exists || dir.isFile || dir.isHidden) {
      printf("Directory not found %s\n",dir)
      System.exit(1)
    }

    val client = new TransportClient()
    client addTransportAddress(
      new InetSocketTransportAddress("0.0.0.0",9300)
    )
    fetchFiles( dir.getAbsolutePath){
      file => {
        printf("Indexing %s\n",file)
      client.prepareIndex("files", "file", DigestUtils.md5Hex(file.getAbsolutePath))
        .setSource(document(file))
        .execute.actionGet
      }
    }
    client.close
}

As you may notice, we’re running Elasticsearch and the program in the same machine(0.0.0.0), if you want to run Elasticsearch in other machine, change the ip/hostname at

client addTransportAddress(new InetSocketTransportAddress("ip/host-name-here",9300))

The index name and type is in the line

client.prepareIndex("files", "file", DigestUtils.md5Hex(file.getAbsolutePath))

it’s equivalent of a curl call

curl -XPUT 'http://0.0.0.0:9200/files/file/4cdb168a80e2adc397f44353b3223494' -d '...'

The only difference is port 9300, it’s used by Java Transport Client and 9200 is used straightforward by others clients.

Indexing

Indexing files is also simple. All we have to do is get this code put together so clone it https://github.com/shairontoledo/elasticsearch-filesystem-indexer

git clone git://github.com/shairontoledo/elasticsearch-filesystem-indexer.git
cd elasticsearch-filesystem-indexer

Install dependencies and compile it by maven

mvn install

Running

mvn exec:java -Dexec.mainClass=net.hashcode.fsindexer.Main -Dexec.args=/Users/me/directory/path

Set exec.args to a directory that you want to index.

Searching

After to index some files, you can search by

curl -XGET 'http://0.0.0.0:9200/files/file/_search?q=pdf&pretty=true'

You should see a response similar to

"took": 86,
"timed_out": false,
"_shards": {
    "total": 5,
    "successful": 5,
    "failed": 0
},
"hits": {
    "total": 767,
    "max_score": 0.34046465,
    "hits": [
        {
            "_index": "files",
            "_type": "file",
            "_id": "a277bda1f97f8ffa6885347b1c76b8d3",
            "_score": 0.34046465,
            "_source": {
                "name": "agile-web-development-with-rails_p1_0.pdf",
                "parent": "/Users/shairon/Reference",
                "path": "/Users/shairon/Reference/agile-web-development-with-rails_p1_0.pdf",
                "last_modified": "2011-11-03T09:59:09.000Z",
                "size": 6700177,
                "is_directory": false,
                "description": "PDF document",
                "extension": "pdf",
                "mimetype": "application/pdf"
            }
        },

      ...

Now you have data, you can improve your queries and get started with Elasticsearch in your Scala application.

A Placeholder for Ruby Splat

I was reading the code language/splat_spec.rb at master from rubyspec/rubyspec – GitHub and also this post
The Strange Ruby Splat.

I’ve written about asterix symbol(or star) in the post Asterisco (Portuguese), I’m complementing these posts introducing the (*), for unix users it’s similar to /dev/null for a variable. I’ve been used it in my codes, I don’t remember the reference about it.
It’s very simple, you can use it when you don’t want to assign a value to variable in a block or in a parallel assignment.

Parallel Assignment

A simple array

langs = ["java", "csharp", "ruby", "haskell" ]

Imagine that you want just first and third value of the array, in a regular coding you could do this way.

l1,l2,l3 = *langs

It will assign l1 to "java",l2 to "csharp" and l3 to "ruby". By default * will discard the rest of array. Ok so far, but remembering about our case, we don’t need second value so l2 won’t be used.

To troubleshoot that, use (*) to jump second position.

l1,(*),l3 = *langs

It can be applied for any position, another example, picking the first and the last position.

l1,(*),(*),l4 = *langs

In block

In block it’s similar, let’s create a class to illustrate that.

class Person
  attr_accessor :name, :email, :phone

  def initialize(name,email,phone)
    @name, @email, @phone = name, email, phone
  end

  def attributes
    yield @name, @email, @phone
  end
end

An instance of Person

person = Person.new("joe", "joe@domain.com", "555-5555")

As you may notice, Person#attributes will yield to block name, email and phone. We just want the name and phone.

person.attributes do |name, (*), phone|
  puts name, phone
end

So that’s all, it’s more interesting than useful, isn’t it? :)

Netbeans 7.1 + Ruby, JRuby and Ruby on Rails installation issues

If you are experiencing problems to install Netbeans 7.1 with Ruby, JRuby and Ruby on Rails plugin, there’s a workaround from Tom Enebo.

The installation should be by zip file for now. Read the post above to get more details.

Language detection using pdf documents

This is a set of test for detect tika extracted documents.. It uses Language Detection Library for Java (langdetect.jar). Add more files to /docs/{lang}, the test will load them automatically.

Current support

  • Portuguese
  • English
  • French
  • German
  • Dutch
  • Italian
  • Spanish
  • Korean
  • Simplified Chinese
  • Traditional Chinese
  • Japanese

Install

Install Language Detection Library for Java manually

mvn install:install-file -Dfile=lib/langdetect.jar -DgroupId=com.cybozu.labs.langdetect -DartifactId=langdetect -Dversion=1.0 -Dpackaging=jar

The others dependencies will be installed by Maven.

Two steps to add your language

Create a directory under docs/{langX}, add some document to it and then create a new test method.

@Test
public void LanguageX() throws Exception {
  genericTest("Language X", "lang-x");
}

Running

export JAVA_OPTS=-Djava.awt.headless=true
mvn test

Log Output

-------------------------------------------------------
 T E S T S
-------------------------------------------------------
Running TestSuite
[INFO ][LangMatcherTest] Setup
[INFO ][LangMatcherTest] Loading lang detector
[INFO ][LangMatcherTest] Loading tika
[INFO ][LangMatcherTest] Language Dutch(nl): loading
[INFO ][LangMatcherTest] Language Dutch(nl): 9 files
[INFO ][LangMatcherTest] Language Dutch(nl): docs/nl/bereikbaarkaart-nl_print.pdf
[INFO ][LangMatcherTest] Language Dutch(nl): docs/nl/de_bodem_onder_amsterdam.pdf
[INFO ][LangMatcherTest] Language Dutch(nl): docs/nl/Economische verkenningen MRA2011_tcm14-228419.pdf
[INFO ][LangMatcherTest] Language Dutch(nl): docs/nl/How To Reach Amsterdam RAI.pdf
[INFO ][LangMatcherTest] Language Dutch(nl): docs/nl/IBM_Amsterdam_HDK.pdf
[INFO ][LangMatcherTest] Language Dutch(nl): docs/nl/kwaliteitswijzer_291111_web_def.pdf
[INFO ][LangMatcherTest] Language Dutch(nl): docs/nl/mas0.pdf
[INFO ][LangMatcherTest] Language Dutch(nl): docs/nl/route-oracle-amsterdam1-157087-nl.pdf
[INFO ][LangMatcherTest] Language Dutch(nl): docs/nl/{00C1E905-406C-490B-8079-5B9DCA9927BA}_C_NED.pdf
[INFO ][LangMatcherTest] Language English(en): loading
[INFO ][LangMatcherTest] Language English(en): 8 files
[INFO ][LangMatcherTest] Language English(en): docs/en/188741.pdf
[INFO ][LangMatcherTest] Language English(en): docs/en/Article33.pdf
[INFO ][LangMatcherTest] Language English(en): docs/en/BOSTmap.pdf
[INFO ][LangMatcherTest] Language English(en): docs/en/ff-boston_tcm7-4572.pdf
[INFO ][LangMatcherTest] Language English(en): docs/en/file84471.pdf
[INFO ][LangMatcherTest] Language English(en): docs/en/InformingTheDebate_Final.pdf
[INFO ][LangMatcherTest] Language English(en): docs/en/preven_code_tcm3-4039.pdf
[INFO ][LangMatcherTest] Language English(en): docs/en/STATEOFBLACKBOSTON_000.pdf
[INFO ][LangMatcherTest] Language French(fr): loading

Dump files

When the test passes to the language you can get extracted text in dump directory in the root of the project.

dump/nl
dump/nl/bereikbaarkaart-nl_print.pdf.txt
dump/nl/de_bodem_onder_amsterdam.pdf.txt
dump/nl/Economische verkenningen MRA2011_tcm14-228419.pdf.txt
dump/nl/How To Reach Amsterdam RAI.pdf.txt
dump/nl/IBM_Amsterdam_HDK.pdf.txt
dump/nl/kwaliteitswijzer_291111_web_def.pdf.txt
dump/nl/mas0.pdf.txt
dump/nl/route-oracle-amsterdam1-157087-nl.pdf.txt
dump/nl/{00C1E905-406C-490B-8079-5B9DCA9927BA}_C_NED.pdf.txt
dump/en
dump/en/188741.pdf.txt
dump/en/Article33.pdf.txt
dump/en/BOSTmap.pdf.txt
dump/en/ff-boston_tcm7-4572.pdf.txt
dump/en/file84471.pdf.txt
dump/en/InformingTheDebate_Final.pdf.txt
dump/en/preven_code_tcm3-4039.pdf.txt
dump/en/STATEOFBLACKBOSTON_000.pdf.txt

Scala in a Mavenized Netbeans project

I like Netbeans development environment, with it I can handle Java, C++, Ruby and now Scala. I found Scala NetBeans Plugin, in my research about how to bring netbeans working with Scala I came across some outdated documentation, in some of those the plugin working only with Scala 2.8.x + Netbeans 6.9, issues with OSX Lion and so on.
Another problem(no problem for you but for me) is the Scala NetBeans Plugin is based on Apache Ant build project, I prefer to use Apache Maven instead. I’ve got some karma by this liking. To save your time, I’ve written some steps to put Java, Scala, Maven and Netbeans working together.

Scala 2.9.1

To install Scala just download it, uncompress it to a directory, in this example, I’m going to use /Application/CustomApps so my SCALA_HOME is

/Applications/CustomApps/scala-2.9.1.final

Download Netbeans 7.1

Download Netbeans 7.1

Install it, probably, the installer will place it to /Applications/NetBeans/NetBeans7.1.app, let’s bind SCALA_HOME to be accessible in Netbeans. Edit Netbeans start up script

/Applications/NetBeans/NetBeans\ 7.1.app/Contents/Resources/NetBeans/bin/netbeans

After commented lines export SCALA_HOME environment variable, this way

export SCALA_HOME=/Applications/CustomApps/scala-2.9.1.final

Ps. There was a way to bind environment variables by adding a .plist in ~/.MacOSX/environment.plist. it’s deprecated in OSX Lion then you can edit Netbeans start up script that will work for all OSX versions.

Scala NetBeans Plugin(nbscala)

Download Scala plugin for Netbeans 7.1 and Scala 2.9.x. Uncompress it anywhere, but don’t forget the path :)

Installing the plugin

Open Netbeans, menu Tools -> Plugins -> Downloaded, button Add Plugins, select *.nbm files of nbscala plugin. Say yes/agree/allow for every dialogs.

Maven vs Ant

The Netbeans Scala plugin uses ant as builder if you don’t care about using ant, this tutorial ends up here. If you want to Maven(yes! Maven!) go ahead in the next topic.

Maven based Scala project

Let’s create a new ‘mavenized’ project, File -> New Project -> Maven -> Java Application go there and place the project in where do you want to. There is the maven-scala-plugin we’re going to use it, it is not needed download anything, Maven takes care about that, do you need to add some lines to your pom.xml.

The Maven repository for Scala

<repositories>
  <repository>
      <id>scala-tools.org</id>
      <name>Scala-tools Maven2 Repository</name>
      <url>http://scala-tools.org/repo-releases</url>
    </repository>
</repositories>

The build entry

<build>
    <plugins>
      <plugin>
        <groupId>org.scala-tools</groupId>
        <artifactId>maven-scala-plugin</artifactId>
        <version>2.15.2</version>
        <executions>
          <execution>
            <phase>process-resources</phase>
            <goals>
              <goal>add-source</goal>
              <goal>compile</goal>
            </goals>
          </execution>
          <execution>
            <id>scala-test-compile</id>
            <phase>process-test-resources</phase>
            <goals>
              <goal>testCompile</goal>
            </goals>
          </execution>
        </executions>
        <configuration>
          <scalaVersion>${scala.version}</scalaVersion>
          <sendJavaToScalac>true</sendJavaToScalac>
          <args>
            <arg>-target:jvm-1.5</arg>
            <arg>-g:vars</arg>
            <arg>-deprecation</arg>
            <arg>-dependencyfile</arg>
            <arg>${project.build.directory}/.scala_dependencies</arg>
          </args>
        </configuration>
      </plugin>
    </plugins>
</build>

And scala-lang dependency

<dependency>
     <groupId>org.scala-lang</groupId>
     <artifactId>scala-library</artifactId>
     <version>2.9.0</version>
 </dependency>

Before run the project, you may create scala source directory src/main/scala and also the test src/test/scala.

A peace of scala code

Under src/main/scala create a directory mytest it will be our package. Now theMain.scala object in src/main/scala/mytest/Main.scala with content:

package mytest
import java.io.File

object Main {

  def main(args: Array[String]):Unit = {
    println("Hello this is my /tmp")
    for(file <- new File("/tmp").listFiles){
      println(file)
    }
  }
}

By default Netbeans continues point to App.java, you may change the main class in right-click in Project -> Properties -> Run in the Main Class type mytest.Main.

F6 and be happy.

ElasticSearch & Tika – the mapper-attachment plugin

ElasticSearch supports text extraction at indexing time, it’s called mapper-attachment plugin, basically it gets a base 64 encoded field, decoded it and so invokes Apache Tika to get its content. I had some troubles to get it running by current documentation then I’ve created a simple test project to get more sense about the mapping. I’m going to describe here.

Mapping

ElasticSearch uses JSON to indexing and searching, it also uses JSON in its configuration. In the ElasticSearch java client, you have a bunch of builders they facilitate a lot JSON creation in a DSL. In few words a mapping an index layout configuration, to the test I created the following structure:

String idxName = "test";
String idxType = "attachment";
XContentBuilder map = jsonBuilder().startObject()
        .startObject(idxType)
          .startObject("properties")
            .startObject("file")
              .field("type", "attachment")
              .startObject("fields")
                .startObject("title")
                  .field("store", "yes")
                .endObject()
                .startObject("file")
                  .field("term_vector","with_positions_offsets")
                  .field("store","yes")
                .endObject()
              .endObject()
            .endObject()
          .endObject()
     .endObject();

It just hold the mapping in the object map, you can to create the index and mapping it in the same time.

CreateIndexResponse resp = client.admin().indices().prepareCreate(idxName).setSettings(
            ImmutableSettings.settingsBuilder()
            .put("number_of_shards", 1)
            .put("index.numberOfReplicas", 1))
            .addMapping("attachment", map).execute().actionGet();

assertThat(resp.acknowledged(), equalTo(true));

As you may see above, CreateIndexResponse provides a method acknowledged to verify the answer that you got.

Indexing

The next step is to index a PDF document encoded in base64. I’m going to index a document with id 80.

String pdfPath = ClassLoader.getSystemResource("fn6742.pdf").getPath();
String data64 = org.elasticsearch.common.Base64.encodeFromFile(pdfPath);
XContentBuilder source = jsonBuilder().startObject()
        .field("file", data64).endObject();

IndexResponse idxResp = client.prepareIndex().setIndex(idxName).setType(idxType).setId("80")
        .setSource(source).setRefresh(true).execute().actionGet();

Searching

Now, you can perform search on it

QueryBuilder query = QueryBuilders.queryString("amplifier");

SearchRequestBuilder searchBuilder = client.prepareSearch().setQuery(query)
        .addField("title")
        .addHighlightedField("file");
SearchResponse search = searchBuilder.execute().actionGet();

Highlights

In the search result set you can get the hits and the highlights.

    assertThat(search.hits().totalHits(), equalTo(1L));
    assertThat(search.hits().hits().length, equalTo(1));
    assertThat(search.hits().getAt(0).highlightFields().get("file"), notNullValue());
    assertThat(search.hits().getAt(0).highlightFields().get("file").toString(), containsString("<em>Amplifier</em>"));

It is just a parsed search result for:

{
  "took" : 136,
  "timed_out" : false,
  "_shards" : {
    "total" : 3,
    "successful" : 3,
    "failed" : 0
  },
  "hits" : {
    "total" : 1,
    "max_score" : 0.005872132,
    "hits" : [ {
      "_index" : "test",
      "_type" : "attachment",
      "_id" : "80",
      "_score" : 0.005872132,
      "fields" : {
        "file.title" : "ISL99201"
      },
      "highlight" : {
        "file" : [ "\nMono "<em>Amplifier</em>"\nThe ISL99201 is a fully integrated high efficiency class-D \nmono "<em>amplifier</em>". It is designed" ]
      }
    } ]
  }
}

Very simple to get searching done in your system, files such as PDF and MS documents without previous steps to extract documents.

82 Barcodes for Ruby on Rails

I’ve just updated RGhost Barcode, I got last update from BWIPP Terry Burton code and I adapted it to RGhost Barcode v0.9.


RGhost and BWIPP have been created using postscript as core language, the integration is pretty easy to do, there are some advantages to using RGhost and RGhost Barcode(with BWIPP) to render barcodes, among them we can highlight:

  • All complexity is done in low-level, the code runs faster than using a pure-ruby code to do it.
  • It improves the backend performance using Ghostscript native libraries.
  • Many output formats are supported, such pdf, png, jpg, tif, ps, etc.
  • With RGhost templates and RGhost Barcode you can easily create pretty neat documents, using images, shapes, custom fonts and of course, your creativity.

New barcodes

Now it supports new 50 barcodes types, in a total of 82 barcodes, BWIPP supports more barcode formats than any other free or commercial software.
For most of types, only pass the barcode string then the method will render it properly.

The big list

Here is the list of supported barcodes

Installation

Use gem or bundler to install it, but make sure whether rghost version is >= 0.8 and rghost_barcode is >= 0.9.

gem install rghost
gem install rghost_barcode -v=0.9

A simple example using QRCode

require 'rghost'
require 'rghost_barcode'

RGhost::Document.new do |doc|
 doc.barcode_hibcqrcode "A123BJC5D6E71"
 doc.render :pdf, :filename => "my_nice_qrcode.pdf"
end

Need more Info

Barcode Writer in Pure PostScript
RGhost Barcode
RGhost
RGhost Rails

Enjoy it!

Objective-C Model Generator

Ok, ok, I know, the name sounds like a big project but it isn’t. I created a private RESTful API in Objective-C last year(until January 2011), there is a piece of code that I can share with you. I was thinking in create a model generator similar of Rails scaffold and adapt it to IPhone on Rails. The idea is simple, from a command line to create a .h and .m based on parameters, I pushed the code to the git repo objc-generators.

How use it

Clone it

git clone https://github.com/shairontoledo/objc-generators.git

There is a directory called script, it should be placed in your project root or you can generate the files and copy them to your project manually.

So generate the model.

scripts/generate model Person name:string code:float thenumber:integer active:bool created_at:date updated_at:date

Does it look familiar to you?

The output

A .h file

#import <Foundation/Foundation.h>

@interface Person : NSObject {
  NSString *name;
  float code;
  NSInteger thenumber;
  BOOL active;
  NSDate *created_at;
  NSDate *updated_at;
}
@property(retain, nonatomic) NSString *name;
@property float code;
@property NSInteger thenumber;
@property BOOL active;
@property(retain, nonatomic) NSDate *created_at;
@property(retain, nonatomic) NSDate *updated_at;
@end

and its .c

#import "Person.h"

@implementation Person
@synthesize name, code, thenumber, active, created_at, updated_at;
- (id)init {
    if ((self = [super init])) {
        // Initialization code here.
    }

    return self;
}

- (void)dealloc {
    // Clean-up code here.

    [super dealloc];
}

It’s simple, I hope I get a time to improve it. Maybe it’s useful for you.

Hooks em Ruby

Ruby é uma linguagem dinâmica e utiliza algumas caracteristicas interessantes para manipular objetos e classes no seu ciclo de vida em run-time, como por exemplo usar singleton methods em um objeto, realizar mixin, adicionar métodos em uma classe e outras coisas legais. Para capturar esse eventos que alteram o comportamento de classes, módulos e objetos, Ruby provê alguns Hooks.

O que seriam Hook propriamente dito(pergunta clássica no curso de metaprogramação)? Hooks são métodos com um nome específico para capturar um mudança no objeto. Esses hooks podem ser definidos por uma API ou mesmo pela a especificação da linguagem. Um exemplo de API que utilize hooks são os callbacks do ActiveRecord, como after_save, before_save, etc, esses métodos são invocados automaticamente após a chamada “save” do model como descrito na especificação.

Hooks da Linguagem Ruby

O intuito deste poste é falar sobre alguns hooks da linguagem Ruby, vamos iniciar capturando o evento de herança de uma classe, isso pode ser muito útil se sua super-classe precisa conhecer a subclass. Para esse hook funcionar temos que definir o método self.inherited na superclass. Exemplo

class A
  def self.inherited(subclass)
    puts "Eu #{self} sendo herdada por #{subclass}"
  end
end

class B < A
end

#=> "Eu A sendo herdada por B"

O conteúdo do método inherited é executado pelo interpretador no momento em que a sub classe é carregada na vm. Lembra do final public class A{} em java? Para quem nunca usou java o final public class é forma de não permitir que uma classe seja herdada por outra. Em ruby você pode simular esse comportamento lançando uma exceção dentro do self.inherited, veja:

class A
  def self.inherited(subclass)
    raise "A classe #{self} nao pode ser herdada!"
  end
end

class B < A
end 

#E=> RuntimeError: A classe A nao pode ser herdada!
 

self.included

O self.inherited é usado apenas para classes, se você precisar detectar mix-in entre módulos e classes, defina o método self.included dentro do módulo que será mixado. O exemplo abaixo demonstra como capturar o evento de mix-in(include):

module M
  def self.included(from)
    puts "#{self} foi mixado por #{from} "
  end
end

class A
  include M
end

Particularmente acho o self.included muito útil, com ele podemos adicionar funcionalidades durante o mix-in. Por exemplo, vamos criar um plugin chamado Printable, na verdade é só um módulo com um pouco de metaprogramação para detectar os accessor(setters e getters) e definir um novo método com o prefixo print_ para imprimir o retorno do método original, seria basicamente assim.

class Person
  attr_accessor :name, :code
end

p = Person.new
p.name ="Foobar"
p.print_name #ainda não existe

Vamos detectar os accessor, se um método tem dois métodos com o mesmo nome mas sendo um terminado com o operador de atribuição(=) então é um acessor.

Antes vamos criar a funcionalidade apenas para classe Person

class Person
  attr_accessor :name, :code
end

#pegando os métodos de instância
methods = Person.instance_methods false
#=> ["code", "name=", "code=", "name"]

#detectando quais deles são accessors
 acessors=methods.select{|m| methods.include? "#{m}=" }
#=> ["code", "name"]

#definindo os métodos na classe
acessors.each do |acessor|
  Person.send :define_method, "print_#{acessor}" do
    puts "Printing #{send(acessor)}..."
  end
end

#Testando
p = Person.new
p.name = "FFUUUU"
p.code = 44444
p.print_name  #=> Printing FFUUUU ...
p.print_code  #=> Printing 44444 ...

Perfeito! Agora vamos criar um módulo e utilizar o hook self.included, com isso, quando uma classe necessitar dessa funcionalidade é só fazer o include de Printable, veja

module Printable
  def self.included(klazz)
    methods = klazz.instance_methods false
    acessors=methods.select{|m| methods.include? "#{m}=" }
    acessors.each do |acessor|
      klazz.send :define_method, "print_#{acessor}" do
      puts "Printing #{send(acessor)}..."
    end
  end
end

Fazendo mix-in em Person e em OtherClass

class Person
  attr_accessor :name, :code
    include Printable
  end

class OtherClass
  attr_accessor :a, :b
  include Printable
end

Testando

p = Person.new
p.name = "FOO"
p.print_name

obj = OtherClass.new
obj.a = "Hey Everybody!"
obj.b = "It's working"
obj.print_a
obj.print_b

Output

Printing FOO...
Printing Hey Everybody!...
Printing It's working...

self.method_added

Se você leu esse post até aqui provavelmente você conhece o method_missing, aquele que captura a nome do método e os parâmetros caso o objeto não responda a esse método. Pois é, não falaremos do method_missing nesse post, vamos falar do primo dele, o self.method_addded. O self.method_added é um hook que captura o evento quando um método é adicionado ao objeto ou classe. Exemplo.

class Foo
  def self.method_added(method)
    puts "Adicionado o metodo #{method} na classe #{self}"
  end

  def testing
    puts "testing"
  end
end
 #=> "Adicionado o metodo testing na classe Foo"

Veja que na própria classe o evento é disparado. Em uma herança, o mesmo comportamento

class Foo
  def self.method_added(method)
    puts "Adicionado o metodo #{method} na classe #{self}"
  end
end

class Bar < Foo
  def other_method
    puts "say hello"
  end
end

#=> Adicionado o metodo other_method na classe Bar

Ou ainda, você pode capturar singleton methods com o mesmo hook.

class Foo
  def self.method_added(method)
    puts "Adicionado o metodo #{method} na classe #{self}"
  end
end

foo = Foo.new

def foo.new_method
  puts "hey I'm adding ..."
end

Object.const_missing

Sabemos que o method_missing captura o evento quando o método não existe, podemos ter uma funcionalidade semelhante para constantes, com o Object.const_missing. Sempre que uma constante não for encontrada na vm, o const_missing recebe o nome da constante. Esse método deve ser definido em Object, dado que constantes o escopo é global. Veja

UmaConstante #E=> NameError: uninitialized constant UmaConstante

def Object.const_missing(const)
  puts "Hey nao encontrei #{const} mas nao vou lançar error"
end 

UmaConstante
#=> Hey nao encontrei UmaConstante mas nao vou lanar error

Embora isso seja uma coisa legal, se você apenas quer capturar o evento, uma boa prática é fazer a sua lógica usando a constante que está faltando e depois usar o super para lançar a exceção via Kernel. Portando algo como

UmaConstante #E=> NameError: uninitialized constant UmaConstante

def Object.const_missing(const)
  puts "Hey nao encontrei #{const} "
  super
end 

UmaConstante

#=> Hey nao encontrei UmaConstante
#=> NameError: uninitialized constant UmaConstante
#=>	 from (irb):119:in `const_missing'
#=>	 from (irb):122
#=>	 from :0

Se seu é convencionado utilizando o nome do arquivo em snake case e o nome da classe em camel, você pode fazer um plugin para carregar seus arquivos automaticamente caso a classe não esteja visível no trecho do seu código. Exemplo, no mesmo diretório 3 arquivos

Arquivo person.rb

class Person
  attr_accessor :name, :state
end

Arquivo state.rb

class State
  attr_accessor :code
end

Arquivo principal main.rb

p = Person.new
p.state = State.new

Rode com

ruby main.rb

De cara o erro

#E=> main.rb:1: uninitialized constant Person (NameError)

Pois não colocamos o requires necessários para cada arquivo.
Vamos fazer um algoritmo para carregar esses arquivos automaticamente usando Kernel.autoload.
Então no main.rb fica com o conteúdo:

def Object.const_missing(const)
  #convertendo CamelCase to snake_case
  filename = const.to_s.gsub(/([A-Z])/, ' \1').split(' ').join('_').downcase
  #Associando a constante ao nome do arquivo
  autoload const, filename
  #binding a nova constante
  Object.const_get const
end  

p = Person.new
p.state = State.new

Se você rodar novamente o main.rb vai funcionar.

Conclusão

No dia-a-dia e em projetos pequenos não é comum capturar eventos no nível de interpretação mas se você esta depurando um sistema ou mesmo desenvolvendo uma API, Hooks podem ser essenciais.