Unix and Me

About unix, web programing and me

Bot IRC para Salvar Mensagens

Usando dos dois últimos posts nós vamos fazer agora com que nosso bot de irc armazene as mensagens.

Primeiro um Gemfile:

source 'http://rubygems.org'

gem 'cinch'
gem 'sqlite3'
gem 'activerecord', :require => 'active_record'

Seguindo o post sobre ActiveRecord sem Rails nos vamos criar os models, as migrations e o nosso arquivo boot.rb.

boot.rb:

require 'rubygems'
require 'bundler'

Bundler.require :default

ActiveRecord::Base.establish_connection(
  :adapter => 'sqlite3',
  :database => 'development.sqlite3'
)

ActiveRecord::Migrator.migrate('./migrations')
require './quote'

quote.rb

class Quote < ActiveRecord::Base
end

migrations/01_create_quotes.rb

class CreateQuote < ActiveRecord::Migration
  def change
    create_table :quotes do |t|
      t.string :added_nick
      t.string :quote
      t.timestamps
    end
  end
end

Agora seguindo o post sobre Bot IRC usando Ruby vamos criar o bot.rb:

require './boot'
require 'cinch'

DEFAULT_CHANNEL = '#gurupi'

bot = Cinch::Bot.new do
  configure do |c|
    c.server = 'irc.freenode.net'
    c.channels = [DEFAULT_CHANNEL]
  end

  on :message, /^!add (.+)/ do |m, msg|
    Quote.create :added_nick => m.user.nick, :quote => msg
    m.reply 'Quote adicionado com sucesso.'
  end

  on :message, /^!quote (.+)/ do |m, nick|
    ch = Channel DEFAULT_CHANNEL
    quotes = Quote.where('quote like ?', "%#{nick}%").to_a
    ch.send quotes[rand(quotes.count)].quote
  end

  on :message, '!count' do |m|
    m.reply "Quote: #{Quote.count}"
  end
end

bot.start

Agora é só iniciar o bot.rb para que ele entre no canal.

Como de costume o código do post está em https://github.com/dmitrynix/bot-irc-and-active-record.

ActiveRecord sem Rails

Uma boa ferramenta do Rails é o ActiveRecord, sem dúvida ela pode ajudar e muito, seja para o rails, para sinatra ou apenas para algum script simples.

Neste post nós vamos tentar usar o ActiveRecord apenas com um script bem simples de Ruby.

Como de costume um Gemfile:

source 'http://rubygems.org'

gem 'sqlite3'
gem 'activerecord', :require => 'active_record'

Vamos criar nosso model de mensagem no arquivo message.rb:

class Message < ActiveRecord::Base
end

Nosso model precisa da base de dados, então vamos escrever uma migration, para isso coloque o seguinte conteúdo no arquivo migrations/01_create_messages.rb:

class CreateMessages < ActiveRecord::Migration
  def change
    create_table :messages do |t|
      t.string :text
      t.timestamps
    end
  end
end

Agora vamos criar um arquivo boot.rb que vai se encarregar de criar a conexão com a base de dados, de fazer todas as migrações e também de carregar todos os models:

require 'rubygems'
require 'bundler'

Bundler.require :default

ActiveRecord::Base.establish_connection(
  :adapter => 'sqlite3',
  :database => 'development.sqlite3'
)

ActiveRecord::Migrator.migrate('./migrations')

require './message'

Nos podemos testar isso tudo de várias formas, uma delas é usando o irb como "console":

% irb -r ./boot.rb
==  CreateMessages: migrating
=================================================
-- create_table(:messages)
   -> 0.0014s
==  CreateMessages: migrated (0.0016s)
========================================

1.9.3p0 :001 > Message.create :text => 'Teste 01'
 => #<Message id: 1, text: "Teste 01", created_at: "2012-02-19
23:09:33", updated_at: "2012-02-19 23:09:33">
1.9.3p0 :002 > Message.create :text => 'Outro Teste'
 => #<Message id: 2, text: "Outro Teste", created_at: "2012-02-19
23:09:41", updated_at: "2012-02-19 23:09:41">
1.9.3p0 :003 > Message.count
 => 2
1.9.3p0 :004 > Message.find(1).text
 => "Teste 01"
1.9.3p0 :005 >

Note acima que logo que o arquivo boot.rb foi carregado foi feita uma migração da base de dados, isso é exatamente o que esperamos que aconteça.

Agora vamos remover alguma coluna para testar novamente as migrations, crie o arquivo migrations/02_remove_messages_updated_at.rb:

class RemoveMessagesUpdatedAt < ActiveRecord::Migration
  def change
    remove_column :messages, :updated_at
  end
end

Mais uma vez no console:

% irb -r ./boot.rb
==  RemoveMessagesUpdatedAt: migrating
========================================
-- remove_column(:messages, :updated_at)
   -> 0.0224s
==  RemoveMessagesUpdatedAt: migrated (0.0225s)
===============================

1.9.3p0 :001 > Message.first
 => #<Message id: 1, text: "Teste 01", created_at: "2012-02-19
23:09:33">
1.9.3p0 :002 > Message.first.attributes
 => {"id"=>1, "text"=>"Teste 01", "created_at"=>2012-02-19 23:09:33
-0300}

O código está disponível no github em: https://github.com/dmitrynix/ruby-active-record.

Bot IRC usando Ruby

Voltei a usar IRC depois de um tempo e como antes eu já ouvi falar muito de bots em irc resolvi testar por mim mesmo.

Após pesquisar um pouco achei o cinch:

The IRC Bot Building Framework

Ou seja: "Um framework de criação de bot IRC".

Mãos a obra

Primeiro instale a gem 'cinch' ou como eu prefiro cria um arquivo Gemfile:

source 'http://rubygems.org'

gem 'cinch'

No nosso primeiro exemplo, vamo fazer o bot responder bom dia para todos que derem bom dia:

require 'rubygems'
require 'bundler/setup'

bot = Cinch::Bot.new do
  configure do |c|
    c.server = 'irc.freenode.net'
    c.channels = ['#gurupi']
  end

  on :message, /bom dia/i do |m|
    m.reply "Bom dia, #{m.user.nick}"
  end
end

bot.start

Pronto nosso bot já está ativo, basta usar.

Note que estou usando expressão regular, ou seja, bom dia, Bom dia e Bom dia!, vão funcionar sem problemas ;).

Estou tendo mais ideias para posts sobre IRC, fiquem ligados ;)

E por falar em ligados entrem na irc.freenode.net na sala ##gurupi (com dois #).

Ordenar Array em Javascript

Recentemente tive um problema ao ordenar array em javascript, na verdade o array era ordenado, porém não da "forma padrão" desta forma quando eu rodava no firefox (entenda: teste de integração com capybara) e com o webkit (entenda: teste de integração usando o capybara-webkit) retornava valores diferentes.

Vou usar o nodejs como console.

Vamos ao primeiro exemplo:

> var nomes = ['Bob', 'Jonh', 'Alice'];
undefined
> nomes.sort();
[ 'Alice', 'Bob', 'Jonh' ]

Ordenação normal, mas agora com números:

> var numeros = [ 20, 2010, 300 ];
undefined
> numeros.sort();
[ 20, 2010, 300 ]

Ops… erro. Tenta ordenar como string ao invés de número.

Para "remediar" podemos passar uma função para a função sort. Essa função espera um retorno do tipo -1, 0 ou 1:

  • -1 (ou menor que zero) se o primeiro argumento for menor que o segundo;
  • 0 se os dois forem iguais;
  • 1 (ou maior que zero) se o segundo argumento for maior que o primeiro;

Uma solução bem verbosa é esta:

> numeros.sort(function(x, y){ return((x > y) ? -1 : ( x > y ? 1 : 0) ) });
[ 20, 300, 2010 ]

Uma solução "menor" é esta:

> numeros.sort(function(x, y) { return( x - y); });

Basicamente na subtração nos podemos ter os seguintes valores:

  • quando o x é menor que o y retorna um número negativo;
  • quando x é igual a y retorna zero;
  • e quando x é maior que y retorna um número positivo;

Meu caso era um pouco mais complicado, pois eu queria ordenar um array semelhante a este:

> var arr = [[, 'ponto'] , [2, 'ponto e virgua'], [1, 'barra']];

Ou seja: um array de array para ordenar pelo primeiro elemento (podendo ainda este ser vazio).

A solução:

> arr.sort(function(x, y){ if(!x[0]) { return(1) } else { return (x[0] - y[0]) } });
[ [ 1, 'barra' ],
  [ 2, 'ponto e virgua' ],
  [ , 'ponto' ] ]

Sim o "vazio" fica por último, essa é a regra.

Fonte: Sorting a JavaScript array using array.sort()

Eu Sou Um Fracasso

Eu devia este post a uns 2-3 meses, mas como aconteceu muito neste tempo e deixei outras jogadas ao relento esse é um bom momento para voltar a escrever isto.

Comecei a ser um fracasso na empresa que queria "ver crescer". Até criei um post com a lista do que se evitar o fracasso, mas não deu, o que posso citar como erro é:

Supus que TODOS os "homens de tecnologia" pensassem semelhante a mim.

Só isto, sem apontar o dedo para ninguém. Quem me conhece sabe que não conseguiria listar defeitos, não assim citando alguém para todos.

Meu segundo fracasso neste período foi tentando andar sozinho.

Trabalhei, não terminei o projeto (ainda está em andamento, mas depois atualizo aqui) e ainda por cima sem receber o pagamento dos meus trabalhos (para os curiosos é cerca de 1-2 meses de atraso).

Não sei me impor (estou aprendendo x), não sei negociar, não sei dizer não.

O que resta? Resta um imenso #EpicFail para mim e só.

Estava procurando um motivo de este post não ser um fracasso, e encontrei, o que me alegra:

Saia da teoria e pratique mais.

É extremamente fácil abrir uma empresa, muito fácil ser freela, mas eu falhei nestas duas áreas, o motivo é simples: sei muito bem a teoria, mas na prática.

Para não dizer que eu sou um completo fracasso eu estou tentando uma outra forma de "viver".

Ordem das Migrations?

Nota: este post está sobre a tag git, pois estou simulando um "conflito" usando git e rails

% rails g model teams name:string
% rails g model tasks name:string
% rails g model user name:string

Para ficar melhor para ver mudei as migrations para:

check_migration_order % ls -1 db/migrate
20100105101918_create_tasks.rb
20110105101832_create_teams.rb
20120105102143_create_users.rb

O processo normal seria roda uma migration aqui, porém eu "apaguei" a 2011 e rodei as migrations:

check_migration_order % rake db:migrate
== CreateTasks: migrating ====================================================
-- create_table(:tasks)
-> 0.0016s
== CreateTasks: migrated (0.0017s) ===========================================

== CreateUsers: migrating ====================================================
-- create_table(:users)
-> 0.0015s
== CreateUsers: migrated (0.0016s) ===========================================

Agora eu adicionei a migration 2011 justamente para simular, por exemplo, como se alguém tivesse mandado a migration posteriormente via git.

Rodando o rake db:migrate:

check_migration_order % rake db:migrate
== CreateTeams: migrating ====================================================
-- create_table(:teams)
-> 0.0016s
== CreateTeams: migrated (0.0018s) ===========================================

Ou seja, o ActiveRecord não faz as migrations em ordem e descarta as que já passou (como muitos pensam E como era até certas versões), ele verifica as que não foram feitas e, claro, dando preferência para a ordem no nome.

Sinatra e Capybara

Seguindo nosso projeto anterior sobre Sinatra Hello Sinatra vamos agora usar o Capybara para fazer os "testes de aceitação".

Adicionar a gem

O nosso projeto é baseado no post Hello Sinatra ;), agora vamos pegar o projeto e adicionar a gem do capybara:

% git clone git://github.com/dmitrynix/sinatra-demo-app-post.git

Adicionar a gem no Gemfile:

source 'http://rubygems.org'

gem 'sinatra'

group :development, :test do
  gem 'rspec'
  gem 'rack-test', :require => 'rack/test'
  gem 'capybara'
end

E executar o bundle install.

Primeiro teste

Nosso exemplo será de um formulário simples que a pessoa vai escrever uma mensagem e ela aparecerá após ela enviar para o servidor.

Como já temos um projeto basta adicionar ao spec_helper.rb o conteúdo:

require 'capybara/rspec'

Esse require, claro, deve ser depois do require das rubygems ou depois do require do bundler.

E dizer ao capybara qual a nossa app rack:

Capybara.app = DemoApp::Application

Agora vamos ao nosso primeiro spec:

require 'spec_helper'

feature 'the message process' do
  it 'expose message' do
    visit '/'

    fill_in 'Message', :with => 'Hi!'

    click_button 'Message!'

    page.should have_content 'Sua mensagem foi "Hi!"'
  end
end

Na sequencia

  • Visite a url '/' da aplicação;
  • Preencha o input do label correspondente com o nome "Message";
  • Click no butão "Message!";
  • No final deste processo deve ter a nossa mensagem.

Certamente ele irá falhar:

% rspec spec/demo_app_spec.rb
F

Failures:

  1) the message process expose message
     Failure/Error: within("#message") do
     ArgumentError:
       rack-test requires a rack application, but none was given
     # (eval):2:in `find'
     # ./spec/demo_app_spec.rb:5:in `block (2 levels) in <top (required)>'

Finished in 0.22032 seconds
1 example, 1 failure

Failed examples:

rspec ./spec/demo_app_spec.rb:4 # the message process expose message

Agora vamos modificar nossa aplicação para o spec:

module DemoApp
  class Application < Sinatra::Base
    get '/' do
<<END
<form action="/" method="post">
  <label for="message">Message</label>
  <input type="text" name="message" id="message">
  <button>Message!</button>
</form>
END
    end

    post '/' do
      "Sua mensagem foi \"#{params[:message]}\""
    end
  end
end

E ele irá passar:

% rspec spec/demo_app_spec.rb
.

Finished in 0.36823 seconds
1 example, 0 failures

Alguns preferem escrever os specs dentro de spec/integration, mas eu já vi também em spec/acceptance.

Como de costume o projeto está no github: sinatra-and-capybara-demo

MongoDB e Ruby

ORM no Rails

No Rails eu uso o Mongoid como ORM.

Sua configuração é simples:

  • Adicionar a gem mongoid;
  • Rodar bundle install e;
  • Criar o arquivo de configuração config/mongoid.yml ou rodar o comando rails g mongoid:config

Os models ficariam semelhante a isso:

class Customer
  include Mongoid::Document
  include Mongoid::Timestamps

  field :name, :type => String
  field :active, :type => Boolean, :default => false
  field :priority, :type => Integer, :default => 1
end

Praticamente não há diferença quando substitui o ActiveRecord.

Uma outra configuração seria tirar o "require all" do arquivo config/application.rb e usar:

require 'action_controller/railtie'
require 'action_mailer/railtie'
require 'active_resource/railtie'
require 'rails/test_unit/railtie'

E mais em baixo deste arquivo:

config.generators do |g|
  g.orm :mongoid
end

Pronto, Rails configurado!

Rails, Mongoid e Heroku

Quando usando git eu prefiro não colocar o mongoid.yml ou database.yml ou qualquer outro de configuração de base de dados, então em um projeto que eu tenho no heroku eu apenas criei o arquivo config/initializers/heroku.rb:

if ENV['MONGOHQ_URL']
  Mongoid.configure do |config|
    uri = URI.parse(ENV['MONGOHQ_URL'])
    connection = Mongo::Connection.from_uri(ENV['MONGOHQ_URL'])
    db = connection.db(uri.path.gsub(/^\//, ''))
    config.master = db
  end
end

Sinatra

No sinatra só precisa colocar em algum ponto da app a linha:

Mongoid.load!('/path/to/mongoid.yml')

E, claro, fazer o require dos models.

Carrierwave

O Carrierwave pode usar o mesmo banco do mongoid para armazenas os arquivos.

É necessário antes adicionar a gem carrierwave-mongoid para o projeto:

# Gemfile
gem 'carrierwave-mongoid', :require => 'carrierwave/mongoid'

E o arquivo de configuração, config/initializers/carrierwave.rb:

CarrierWave.configure do |config|
  config.storage = :grid_fs
  config.grid_fs_connection = Mongoid.database
end

Mas claro, também dá para usar outro banco:

CarrierWave.configure do |config|
  config.grid_fs_database = 'my_mongo_database'
  config.grid_fs_host = 'mongo.example.com'
end

No sinatra é semelhante, configurar Gemfile e "executar" o código ruby antes de carregar a aplicação (ou no config).

Grid

 mongo_con = Mongo::Connection.new(
   gridfs_conf['host'],
   (gridfs_conf['port'] || 27017)
 ).db(gridfs_conf['database'])

 key = params[:arquivo]

 Mongo::GridFileSystem.new(mongo_con).open(key, 'r') do |file|
   file.read
 end

No exemplo acima as últimas linha iram fazer a "query" no banco de dados e retornar um objeto arquivo, o read vai mostrar o conteúdo do arquivo.

Backup+Restore do MySQL

Isto é algo que eu sempre esqueço como se faz (sério).

Backup

Se for na máquina local:

% mysqldump -u usuario -p banco_de_dados > nome_do_arquivo.sql
Enter password:

Se não usar a senha, basta remover a opção -p.

E se quiser por a senha na linha de comando:

% mysqldump -u usuario --password='minha_senha' banco > arquivo.sql

Backup Remoto

Como você notou o backup é gerado na saída padrão e feito um redirecionamento para arquivo.sql.

% ssh usuario@site-remoto.com 'mysqldump -u usuario --password="minha_senha" banco' | gzip > arquivo.sql.gz

No exemplo acima nós acessamos o servidor remoto via ssh e executamos o backup com compactação (com o comando gzip), mas a "saída padrão" é na nossa máquina e com ela jogamos no arquivo arquivo.sql.gz.

Mas atenção: se o banco de dados for grande você vai usar muita banda.

Para bancos pequenos eu faço o comando acima, mas em banco de dados grandes entro no servidor via ssh, faço o backup compactado e coloco para download via http ou ftp.

Restauação (Restore)

O comando para restauração é o mesmo de acesso a "shell" do mysql, com o arquivo_de_backup.sql em mãos:

% mysql -u usuario --password='senha' banco < arquivo.sql

Se você ver o erro:

ERROR 1049 (42000): Unknown database 'vclientes_production'

É por que, como a tradução sugere, não existe a base de dados. Neste caso, é preciso entrar no mysql e executar o comando para criar a base de dados, com o create database banco;

% mysql -u root
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 19
Server version: 5.1.49-1ubuntu8.1 (Ubuntu)

Copyright (c) 2000, 2010, Oracle and/or its affiliates. All rights reserved.
This software comes with ABSOLUTELY NO WARRANTY. This is free software,
and you are welcome to modify and redistribute it under the GPL v2 license

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

mysql> create database banco;
Query OK, 1 row affected (0.00 sec)

mysql>

Ou se preferir, basta executar direto no shell:

% echo 'create database banco' | mysql -u usuario --password='senha'

Se o arquivo estiver compactado via gzip, basta descompactar e mandar para o mysql:

% cat arquivo.sql.gz | gunzip | mysql -u usuario banco

Restauração Remota para Local Direta

% ssh usario@host-remoto.com 'mysqldump -u usuario --password="senha" banco | gzip' | gunzip | mysql -u usuario banco

Na linha acima o backup é feito e compactado no servidor remoto, e na nossa máquina ele é descompactado e usado para restauração.

Orientação a Objetos com Ruby

Como você leu no primeiro post fazendo uma Introdução a Ruby, Ruby é uma linguagem orientada a objetos, significa?

Significa que usando Ruby pode-se "mapear" os objetos do mundo real para os seus programas.

Classes e Objetos

As classes constroem objetos com o método new:

% irb
ruby-1.8.7-p352 :001 > String.new("teste")
=> "teste" 

O método new é mapeado para o método initialize da classe:

class Animal
  def initialize(nome)
    @nome = nome
  end
end

Para chamar usando o irb:

% irb -r ./animal.rb
ruby-1.8.7-p352 :001 > animal = Animal.new('nome')
=> #<Animal:0x7f679fa1e0d8 @nome="nome">
ruby-1.8.7-p352 :002 >

Aproveitando vamos fazer perguntas ao objeto:

ruby-1.8.7-p352 :002 > animal.class
=> Animal
ruby-1.8.7-p352 :003 > animal.instance_variables
=> ["@nome"]

Variáveis

Ao contrário de linguagens estáticas, em Ruby não há relação entre a classe e as variáveis do objeto. As variáveis de instância só existem quando lhes é dado um valor, baseado nisso você pode ter 2 objetos de mesma classe com variáveis de instância diferentes.

Observe o código abaixo:

class Animal
  def initialize(nome)
    @nome = nome
    @um_gato = 'sim' if nome == 'gato'
  end
end

E

% irb -r ./animal.rb
ruby-1.8.7-p352 :001 > gato = Animal.new('gato')
=> #<Animal:0x7f1d386e87d0 @um_gato="sim", @nome="gato">
ruby-1.8.7-p352 :002 > gato.instance_variables
=> ["@um_gato", "@nome"]
ruby-1.8.7-p352 :003 > macaco = Animal.new('macaco')
=> #<Animal:0x7f1d386d79f8 @nome="macaco">
ruby-1.8.7-p352 :004 > macaco.instance_variables
=> ["@nome"]

Métodos

Assim como as classes tem seus métodos os objetos também tem, para ver a lista de métodos de um objeto:

ruby-1.8.7-p352 :006 > gato.methods
=> ["inspect", "tap", "clone", "public_methods", "__send__", "object_id", "instance_variable_defined?", "equal?", "freeze", "extend", "send", "methods", "hash", "dup", "to_enum", "instance_variables", "eql?", "instance_eval", "id", "singleton_methods", "taint", "enum_for", "frozen?", "instance_variable_get", "instance_of?", "display", "to_a", "method", "type", "instance_exec", "protected_methods", "==", "===", "instance_variable_set", "kind_of?", "respond_to?", "to_s", "class", "__id__", "tainted?", "=~", "private_methods", "untaint", "nil?", "is_a?"]

E para filtrar:

ruby-1.8.7-p352 :002 > gato.methods.grep /ins/
=> ["inspect", "instance_variable_defined?", "instance_variables", "instance_eval", "instance_variable_get", "instance_of?", "instance_exec", "instance_variable_set"] 

Se você abrir o interpretador Ruby, verá que o Ruby armazena as variáveis de instância no objeto e um "link" para a classe, ou seja, as variáveis de instância no objeto e os métodos na classe.

class Animal
  def initialize(nome)
    @nome = nome
    @um_gato = 'sim' if nome == 'gato'
  end

  def qual_o_nome
    @nome
  end
end

Como você viu o método qual_o_nome é de instância, ou seja, só existe quando um objeto é criado, para exemplificar vamos filtrar os métodos de instância dos de classe:

ruby-1.8.7-p352 :007 > gato.methods.grep /qual/
=> ["equal?", "qual_o_nome"]
ruby-1.8.7-p352 :008 > Animal.methods.grep /qual/
=> ["equal?"]

Para criar o método de classe usa-se o self na definição do método:

class Animal
  def initialize(nome)
    @nome = nome
  end

  def qual_o_nome
    @ja_foi_mostrado_o_nome = 'sim'
    @nome
  end

  def self.qual_o_nome
    "Eu sou uma classe e ainda nao tenho nome"
  end
end

Verificando os métodos:

% irb -r ./animal
ruby-1.8.7-p352 :001 > Animal.qual_o_nome
=> "Eu sou uma classe e ainda nao tenho nome"
ruby-1.8.7-p352 :002 >

Um pouco sobre herança

Quando um método é chamado na instância de um objeto, o interpretador procura pelo método na sua classe e "sobe" na hierarquia até encontrar o método.

Por exemplo, agora temos estas 3 classes:

class Animal
  def initialize(nome)
    @nome = nome
    @tipo = 'animal'
  end

  def meu_tipo
    @tipo
  end
end

class Macaco < Animal
  def initialize(nome)
    @nome = nome
    @tipo = 'macaco'
  end
end

class Gato < Animal
  def initialize(nome)
    @nome = nome
    @tipo = 'gato'
  end
end

Testando:

% irb -r ./animal.rb
ruby-1.8.7-p352 :001 > animal = Animal.new('tipo generico de animal')
=> #<Animal:0x7f11ce5d93a0 @tipo="animal", @nome="tipo generico de animal"> 
ruby-1.8.7-p352 :002 > animal.meu_tipo
=> "animal"
ruby-1.8.7-p352 :003 > macaco = Macaco.new('nome do macaco')
=> #<Macaco:0x7f11ce5c7768 @tipo="macaco", @nome="nome do macaco">
ruby-1.8.7-p352 :004 > macaco.meu_tipo
=> "macaco"
ruby-1.8.7-p352 :005 > gato = Gato.new('um gato')
=> #<Gato:0x7f11ce5b6d00 @tipo="gato", @nome="um gato">
ruby-1.8.7-p352 :004 > gato.meu_tipo
=> "gato"

Ao inicializar uma instância de cada um dos tipos você verá que cada um vai responder pelo método meu_tipo, porém somente a classe Animal define o método meu_tipo as outras duas classes herdam este método.