Usando o Observer no Rails

Este artigo seria apenas uma dica de como saber o que foi alterado em um objeto, mas para ficar mais fácil e principalmente para quem está dando os primeiros passos em rails resolvi fazer uma pequena introdução sobre o assunto.

O Observer é um objeto que se responsabiliza por "escutar" os eventos que ocorrem com outro objeto. Ou seja, qualquer alteração feito no objeto observado será analizado e poderá executar uma ação. Isto é muito útil para gravar logs, enviar emails e outros.

Vamos criar um projetinho para entender:

1
2
3
4
rails observers
cd observers
script/generate scaffold User name:string phone:string age:integer
rake db:migrate

Acima criamos um projeto e geramos uma entidade User e seus atributos, e migramos o banco. Vamos criar agora a entidade de log e o observer:

1
2
3
4
5
6
7
8
9
10
11

script/generate model Log table:string action:string field:string \ 
old_value:string new_value:string comments:text
      exists  app/models/
      ...
script/generate observer user
      exists  app/models/
      exists  test/unit/
      create  app/models/user_observer.rb
      create  test/unit/user_observer_test.rb
rake db:migrate

Feito isto precisamos "ativar" o observer, e para isto basta adicionar no config/enviroment.rb

1
2
3
4
5
6

  # config/enviroment.rb
  ...
  # Activate observers that should always be running
  # config.active_record.observers = :cacher, :garbage_collector
  config.active_record.observers = :user_observer

Agora podemos definir em qual momento ele vai gerar o log, antes ou depois de persistir o objeto:

Como tudo no rails os nomes já definem o que cada um vai fazer. Vamos gerar um log quando criar ou excluir um usuário. Para isto, vamos abrir o arquivo user_observer.rb

1
2
3
4
5
6
7
8
9
10
# app/models/user_observer.rb
class UserObserver < ActiveRecord::Observer
  def after_create(user)
    Log.create(:table => "user", :action => "create", :comments => "New user added!")
  end
  
  def after_destroy(user)
    Log.create(:table => "user", :action => "destroy", :comments => "User removed!")
  end
end

Vamos acessar http://localhost:3000/users e criar um user e veja o log gerado:

1
2
3
4
5
6
INSERT INTO "users" ("name", "updated_at", "phone", "age", "created_at")
VALUES('First User', '2008-08-22 12:07:34', '111-1111', 20, '2008-08-22 12:07:34')

INSERT INTO "logs" ("new_value", "updated_at", "comments", "old_value", "action", "field",
"table", "created_at") VALUES(NULL, '2008-08-22 12:07:34', 'New user added!', NULL, 
'create', NULL, 'user', '2008-08-22 12:07:34')

Simples? Muito. Agora vamos pedir para ele observar o que foi alterado em um objeto:

1
2
3
4
5
6
7
8
9
# app/models/user_observer.rb
  ...
  def before_update(user)
    user.changes.each do |key, values|
      Log.create(:table => "user", :action => "update", :field => key, :old_value => values.first,
        :new_value => values.last,  :comments => "User updated!")
    end
  end
  ...

O log gerado:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
INSERT INTO "logs" ("new_value", "updated_at", "comments",
"old_value", "action", "field", "table", "created_at") VALUES('2008-08-22 12:29:00', 
'2008-08-22 12:29:00', 'User updated!', '2008-08-22 12:07:34', 'update', 'updated_at', 'user',
'2008-08-22 12:29:00')

INSERT INTO "logs" ("new_value", "updated_at", "comments", "old_value", "action", "field",
"table", "created_at") VALUES('222-2222', '2008-08-22 12:29:00', 'User updated!', '111-1111', 
'update', 'phone', 'user', '2008-08-22 12:29:00')

INSERT INTO "logs" ("new_value", "updated_at", "comments", "old_value", "action", "field", 
"table", "created_at") VALUES(26, '2008-08-22 12:29:00', 'User updated!', 20, 'update',
'age', 'user', '2008-08-22 12:29:00')

UPDATE "users" SET "phone" = '222-2222', "age" = 26, "updated_at" = '2008-08-22 12:29:00'
WHERE "id" = 1

Aqui temos apenas uma introdução do que podemos ter com o Observer. Bons estudos.

Comentários
Ótimo post Ozéias! Sem dúvidas isso vai servir para muita gente. Simples e direto! Só instigou ainda mais a imaginação para quem ainda tem alguns jobs usando o Rails 1.2.3 ;-) Abraços
Complica a parada quando vc precisa saber qual usuário efetuou a ação. Seria uma boa já publicar um artigo implementando isso.
@bruno boa dica, vou preparar algo.
Deeeeeeesiiign Paaaaaatern Véééééi!!! É sempre bom aprender um em qualquer linguagem. =D
Bruno Azisaka, para isso você precisaria passar a sessao com o usuário do controller para o modelo como argumento. Agora caso você queira quebrar literalmente com a arquitetura MVC pode tentar usar isso: http://m.onkey.org/2007/10/17/how-to-access-session-cookies-params-request-in-model

Leave a Reply