Artigos com a tag ‘ActiveRecord’

Rails 2.3: Active Record

12 de fevereiro de 2009  |  Rails 2.3  |  1 Comentário  | 

O lançamento do Ruby on Rails 2.3 está muito próximo e eu ainda não comentei sobre algumas novidades por aqui. A idéia desta série não é apenas mostrar as novas funcionalidades, mas explicar e dar exemplos de como elas funcionam na prática, mas alguns dos novos recursos são muito simples e não exigem muitos detalhes. Vejamos alguns relacionados ao Active Record.

Os callbacks do Active Record receberam uma nova implementação que permite o uso combinado das opções :if e :unless no mesmo callback. Você também pode usar um array para definir múltiplas condições:

before_save :update_credit_rating,
            :if => :active,
            :unless => [:admin, :cash_only]

O método find também ganhou uma nova opção :having para filtrar registros agrupados:

Developer.find(:all,
               :group => "salary",
               :having => "sum(salary) >  10000",
               :select => "salary")

Outra funcionalidade importante que temos de volta é o uso de um hash :conditions em relacionamentos has_many. Isto já funcionava no Rails 2.1, parou de funcionar no Rails 2.2 e está de volta no Rails 2.3.

has_many :orders, :conditions => {:status => 'confirmed'}

Todos os exemplos dados aqui funcionarão somente no Ruby on Rails 2.3 ou superior. Você pode encontrar mais detalhes sobre esta e outras novidades acompanhando a série Rails 2.3.

Rails 2.3: Nested Transactions

5 de fevereiro de 2009  |  Rails 2.3  |  4 Comentários  | 

Outro recurso que também foi muito solicitado e estará disponível com a versão 2.3 do Rails é uma melhoria nas nested transactions (transações aninhadas).

Atualmente no Rails já podemos aninhar transações, ou em um português mais simples podemos colocar uma transação dentro de outra. Veja um exemplo:

User.transaction do
  User.create(:username => 'Kotori')
  User.transaction do
    User.create(:username => 'Nemu')
    raise ActiveRecord::Rollback
  end
end

User.find(:all) # => empty

Resumindo o exemplo acima: Criei uma transação e inclui um novo usuário no banco, então criei uma segunda transação dentro da primeira e inclui um segundo usuário, e no fim disparei uma exceção que faz com que a primeira transação e todas as suas filhas sejam desfeitas como você pode ver no resultado do método find.

A novidade é a opção :requires_new. Quando usada em uma sub-transação, caso alguma coisa dê errado somente as alterações feitas dai em diante é que serão desfeitas. Veja um novo exemplo, agora com este recurso habilitado:

User.transaction do
  User.create(:username => 'Kotori')
  User.transaction(:requires_new => true) do
    User.create(:username => 'Nemu')
    raise ActiveRecord::Rollback
  end
end

User.find(:all) # => Returns only Kotori

Como você pode ver no retorno do método find, ao realizar o rollback somente a inclusão do segundo usuário foi desfeita, já que estou usando a opção :requires_new habilitada na segunda transação.

Nem todo banco de dados tem suporte real a nested transactions. Na verdade, até o momento somente o MS-SQL tem esta funcionalidade. Mas não se preocupe se você estiver usando outro banco de dados, o Active Record consegue emular nested transactions usando savepoints.

ATENÇÃO: Caso você esteja usando o MySQL, então não faça operações DDL em nested transactions que estiverem emulando savepoints. Ou seja, não tente executar algo como ‘CREATE TABLE’ nestes blocos. Isto acontece porque o MySQL automaticamente libera todos os savepoints após executar uma operação DDL. Então quando a transação terminar e tentar liberar os savepoints criados, ocorrerá um erro no banco já que todos os savepoints foram liberados antes da hora. Para entender melhor, veja o exemplo abaixo:

# INICIO
Model.connection.transaction do
  # CRIA O SAVEPOINT active_record_1
  Model.connection.transaction(:requires_new => true) do
    # active_record_1 é automaticamente liberado
    Model.connection.create_table(...)
  end # LIBERA o savepoint active_record_1

  # ^^^^ BOOM! database error!
end

Apesar deste pequeno detalhe, em todos os outros casos este recurso tem funcionado perfeitamente.


Todos os exemplos dados aqui funcionarão somente no Ruby on Rails 2.3 ou superior. Você pode encontrar mais detalhes sobre esta e outras novidades acompanhando a série Rails 2.3.

Rails 2.3: Nested Attributes

4 de fevereiro de 2009  |  Rails 2.3  |  7 Comentários  | 

Durante muito tempo a funcionalidade mais requisitada ao core team do Rails era a simplificação do gerenciamento de múltiplos modelos em apenas um formulário. Eu mesmo cheguei a comentar sobre uma nova opção chamada :accessible que facilitaria atribuições em massa em objetos ActiveRecord (aqui e aqui).

Infelizmente este recurso foi incluído ao Rails cedo demais, já que ele só dava suporte a nested models (é como chamamos os modelos que estão “acoplados” a um outro modelo, como quando usamos belongs_to ou has_many) durante a criação dos objetos e por isto ele foi removido afim de ser aprimorado.

No Rails 2.3 esta funcionalidade volta a existir, mas de uma maneira diferente. A primeira coisa que devemos fazer é informar ao modelo que ele se beneficiará deste recurso incluindo uma chamada ao método accept_nested_attributes_for, como no exemplo:

class Project < ActiveRecord::Base
  has_many :tasks

  accept_nested_attributes_for :tasks, :allow_destroy => true
end

Como visto acima estou “ligando” a atribuição em massa para o modelo Task via o modelo Project. Isto também vale para qualquer tipo de relacionamento, como belongs_to, has_one, has_many e has_and_belongs_to_many.

Uma vez feito isto, agora eu posso criar, editar e apagar tarefas (tasks) através do objeto Project:

# Adicionando uma nova tarefa ao projeto
@project.task_attributes = { 'new_1' => { :name => 'Task 1' } }
@project.task #=> [ <#Task: name: 'Task 1'> ]
@project.task.clear

# Adicionando duas tarefas ao projeto
@project.task_attributes =
  { 'new_1' => { :name => 'Task 1' }, 'new_2' => { :name => 'Task 2' } }
@project.save
@project.task #=> [ <#Task: name: 'Task 1'>, <#Task: name: 'Task 2'> ]

# Alterando a primeira tarefa (assumindo o id == 1)
@project.task_attributes = { '1' => { :name => 'My Task' } }
@project.save

# Alterando a segunda tarefa (id == 2) e incluindo uma nova
@project.task_attributes = {
  '2' => { :name => 'My Second Task' },
  'new_1' => { :name => 'Task 3' } }
@project.save

# Apaga o último registro (id == 3)
@project.task_attributes = { '3' => { '_delete' => '1' } }
@project.save

Talvez neste momento você esteja se questionando sobre estes formatos estranhos, como ao apagar um registro. Sim, estes hashs são meio confusos mesmo, mas eles não foram criados para serem usados desta maneira. O uso prático deste novo recurso está na criação de formulários:

<% form_for @project do |project_form| %>
  <div>
    <%= project_form.label :name, 'Project name:' %>
    <%= project_form.text_field :name %>
  </div>

  <!-- PRESTE ATENÇÃO AQUI -->
  <% project_form.fields_for :tasks do |task_form| %>
      <p>
        <div>
          <%= task_form.label :name, 'Task:' %>
          <%= task_form.text_field :name %>
        </div>

        <% unless task_form.object.new_record? %>
          <div>
            <%= task_form.label :_delete, 'Remove:' %>
            <%= task_form.check_box :_delete %>
          </div>
        <% end %>
      </p>
    <% end %>
  <% end %>

  <%= project_form.submit %>
<% end %>

Ao definir project_form.fields_for :tasks estamos dizendo que aquele trecho do formulário deve usar o recurso de atribuição em massa para criar, editar ou apagar uma tarefa (task) já existente.

Caso uma das validações da classe Task não passe (imagine que esta tenha um validates_presence_of :name e que eu deixei o campo name em branco), a mensagem correspondente a esta validação será copiada para a classe pai, no caso para a classe Project e estará acessível através do método error_messages_for dela.

Este novo sistema também conta com o recurso de transações. Isto significa que ao realizar uma série de operações de uma só vez, se uma der errado, nenhuma delas será efetivada. Lembre-se apenas que como toda transação no Rails, embora no banco de dados nada aconteça, na instancia do seu objeto ele ainda continuará com as alterações marcadas.

O mais importante em tudo isto é que o código em seus controllers continuarão exatamente da mesma forma como já é hoje. Nenhuma alteração é necessária. Seguindo os exemplos acima, veja como ficaria meu controller:

class ProjectController < ApplicationController

  def create
    @project = Project.new(params[:project])
    if @project.save
      redirect_to(project_path(@project))
    else
      render(:action => :new)
    end
  end

  def update
    @project = Project.find(params[:id])
    @project.update_attributes(params[:project]) ?
      redirect_to(project_path(@project)) : render(:action => :edit)
  end

end

Nada mudou correto?

Eloy Duran, o programador responsável por este novo recurso, tem um projeto no GitHub mostrando mais detalhes sobre o seu funcionamento. Recomendo que você dê uma olhada neste formulário em especial, onde ele mostra como incluir múltiplas tarefas no mesmo projeto.


Todos os exemplos dados aqui funcionarão somente no Ruby on Rails 2.3 ou superior. Você pode encontrar mais detalhes sobre esta e outras novidades acompanhando a série Rails 2.3.

Edge Rails: Escopos Dinâmicos

27 de janeiro de 2009  |  Rails 2.3  |  2 Comentários  | 

Finders dinâmicos não são mais novidades no Ruby on Rails há muito tempo, qualquer um que já tenha trabalhado em um simples projeto Rails, já fez algo assim:

User.find_by_first_name_and_age('Pedro', 20)

Sabemos que o método acima não existe, mas graças aos finders dinâmicos podemos usá-lo para recuperar todos os usuários que tenham como primeiro nome ‘Pedro’ e 20 anos de idade.

Outro recurso, um pouco mais novo, que também tem se mostrado muito útil são os named scopes. Com este recurso podemos definir um escopo como base para uma pesquisa. Por exemplo:

class Article < ActiveRecord::Base
  named_scope :published, :conditions => { :published => true }
end

Article.published.all :limit => 5

No exemplo acima estou usando o escopo published que ajustará minha pesquisa para que apenas artigos publicados sejam devolvidos, mas acrescentando o método all também posso limitar a quantidade de artigos retornados em cinco. Este tipo de flexibilidade não é possível em finders dinâmicos.

Para resolver esta deficiência foi adicionado um novo recurso ao Rails chamado dynamic scopes (escopos dinâmicos). Imagine esta funcionalidade como uma mistura dos dois conceitos que acabamos de ver.

Agora você poderá criar escopos de forma dinâmica, da mesma forma como já fazíamos através do find_by. Desta forma:

User.scoped_by_first_name_and_age 'Pedro', 20

Mas com a vantagem de poder usar toda a flexibilidade de um escopo:

User.scoped_by_first_name_and_age('Pedro', 20).all(:limit => 5)

Da mesma forma como fazíamos em escopos comuns, também podemos preceder um escopo com outro escopo:

User.scoped_by_first_name('Pedro').scoped_by_age(20)

Enfim, mais um grande ganho à nossa produtividade.


Todos os exemplos dados aqui funcionarão somente no Ruby on Rails 2.3/3.0 ou superior. Você pode encontrar mais detalhes sobre esta e outras novidades acompanhando a série Edge Rails.

Edge Rails: Definindo o separador de palavras em mensagens de erro

8 de janeiro de 2009  |  Rails 2.3  |  3 Comentários  | 

Este novo recurso acrescentado ao Rails dificilmente será usado por nós, mas vale a pela curiosidade. Foi acrescentado ao ActiveRecord um novo parâmetro de configuração para internacionalização que permite definir o separador de palavras em mensagens de erro.

Como?

No inglês e no português não faz o menor sentido, mas pegue o seguinte arquivo de internacionalização criado para um projeto em japonês:

ja-JP:
  activerecord:
    models:
      user: ユーザー
    attributes:
      user:
        name: 名前
    errors:
      messages:
        blank: を入力してください。

Quando o atributo name do objeto User estiver em branco a seguinte mensagem será devolvida pelo AR:

名前 を入力してください。

Talvez você não tenha percebido mas existe um espaço em branco entre o nome do atributo e a mensagem de erro, o que não está certo no japonês, onde as palavras normalmente não são separadas por espaços.

Com este novo recurso um Railer japonês pode configurar em seu arquivo:

ja-JP:
  activerecord:
    errors:
      format:
        # Sets the separator between the words
        separator: ""

E ter seguinte mensagem de erro sem espaços separando as palavras:

名前を入力してください。

Curioso ou não?


Todos os exemplos dados aqui funcionarão somente no Ruby on Rails 2.3/3.0 ou superior. Você pode encontrar mais detalhes sobre esta e outras novidades acompanhando a série Edge Rails.

Edge Rails: Escopo padrão

16 de dezembro de 2008  |  Rails 2.3  |  Nenhum comentário  | 

Finalmente a série de artigos cobrindo todas as novas funcionalidades do Rails 2.2 terminou, e de agora em diante somente novas adições serão cobertas. Não sabemos ao certo se a próxima versão do Rails será a 2.3 ou 3.0, mas aqui chamaremos apenas de Edge Rails.

Outra novidade neste blog foi a adição do Google Connect. Vocês podem encontrar na primeira página uma caixa para se cadastrar no menu lateral. Ainda não inclui todas funcionalidades, até o momento é possível apenas se associar ao blog, mas futuramente serão permitidos comentários via esta ferramenta, além de outros tipos de colaboração. A idéia no momento é apenas testar ferramenta.

Vamos ao que realmente interessa. No Rails 2.1 tivemos a introdução dos named_scope, que se tornaram ferramentas poderosas em nossas mãos. Mas na próxima versão do Rails teremos também um default_scope.

Ele funciona exatamente como um named_scope, mas como o próprio nome diz é um escopo padrão que deve ser usado em todas as pesquisas. Vejamos alguns exemplos:

class Person < ActiveRecord::Base
  default_scope :order => 'last_name, first_name'
end

No exemplo acima estou definindo o escopo padrão para o meu objeto Person. Veja o resultado de uma simples pesquisa por todos os registros no banco de dados:

Person.all
# => Person.find(:all, :order => 'last_name, first_name')

Isto também vale para minhas associações:

class Company < ActiveRecord::Base
  has_many :people
end

Company.find(1).people
# => Person.find(:all, :order => 'last_name, first_name', :conditions => { :company_id => 1 })

Caso seu modelo possua named_scopes, eles também incluirão as condições impostas pelo default_scope, à menos que você sobrecarregue a condição existente com alguma outra (informando uma outra ordem no exemplo acima).

Para executar uma pesquisa desconsiderando o default_scope, você deve fazer uso do método with_exclusive_scope:

Person.with_exclusive_scope { find(:all) }
# => Person.find(:all)

Todos os exemplos dados aqui funcionarão somente no Ruby on Rails 2.3/3.0 ou superior. Você pode encontrar mais detalhes sobre esta e outras novidades acompanhando a série Edge Rails.

Rails 2.2: Tornando atributos do ActiveRecord privados

14 de novembro de 2008  |  Rails 2.2  |  1 Comentário  | 

No Rails 2.2 você poderá definir atributos do ActiveRecord como private. Como estes atributos são criados via metaprogramação, até agora isto era impossível.

Para entender como isto funcionará, vamos tornar o atributo name da classe User privado:

class User < ActiveRecord::Base

  private
  def name
    "I'm private"
  end

end

Agora ao tentar recuperar o valor do atributo name:

user = User.first
# => #<User id: 1, name: "teste", created_at: "2008-09-26 21:55:23", updated_at: "2008-09-26 21:55:23">

user.name
# => NoMethodError: undefined method `NoMethodError' for #<User:0x234df08>
#    from /Users/carlosbrando/Projects/sandbox/edge/vendor/rails/activerecord/lib/active_record/attribute_methods.rb:260:in `method_missing'
#    from /Users/carlosbrando/Projects/sandbox/edge/vendor/rails/activerecord/lib/active_record/attribute_methods.rb:236:in `method_missing'
#    from (irb):3

Veja que uma exceção NoMethodError foi disparada ao executar o método que agora é privado. Por outro lado eu posso alterar o nome do usuário, já que o método name= ainda é público.

user.name = "Carlos"
# => "Carlos"

Todos os exemplos dados aqui funcionarão somente no Ruby on Rails 2.2 ou superior. Você pode encontrar mais detalhes sobre esta e outras novidades do Rails 2.2 no e-book “Ruby on Rails – O que há de novo?“.

Rails 2.2: Novo método de instância Model#delete

7 de novembro de 2008  |  Rails 2.2  |  Nenhum comentário  | 

Para tornar o ActiveRecord mais consistente foi adicionado o método de instância Model#delete. Ele é similar ao método de classe com o mesmo nome. O método delete, diferente do método destroy, apaga o registro do banco de dados sem disparar callbacks, como o before_destroy e o after_destroy.

Este método também não aplicará nenhuma das regras impostas na associação através de cláusulas como :dependent.

client = Client.find(1)
client.delete

Todos os exemplos dados aqui funcionarão somente no Ruby on Rails 2.2 ou superior. Você pode encontrar mais detalhes sobre esta e outras novidades do Rails 2.2 no e-book “Ruby on Rails – O que há de novo?“.

Rails 2.2: alias_attribute funcionando com Dirty Objects

30 de outubro de 2008  |  Rails 2.2  |  10 Comentários  | 

Para entender esta alteração, vamos precisar analisar o mesmo código sendo executado em uma versão anterior do Rails e depois no Rails 2.2. Vamos pegar um modelo como exemplo:

class Comment < ActiveRecord::Base
  alias_attribute :text, :body
end

Note que estou usando o método alias_attribute para criar um alias para o atributo body com o nome de text. Na teoria este método deveria replicar todos os métodos de leitura, escrita, pesquisa e qualquer outro que envolva o atributo body. Mas vejamos um exemplo sendo executado no Rails 2.1 ou anterior:

c = Comment.first
# => #<Comment id: 1, body: "my comment">

c.body
# => "my comment"

c.text
# => "my comment"

c.body = "a new message"
# => "a new message"

c.body_changed?
# => true

c.text_changed?
# => NoMethodError: undefined method `text_changed?' ...

Ao executar o método text_changed? temos um erro, porque o alias_attribute não estava replicando os métodos de rastreamento, mas isto já foi corrigido. Veja o mesmo código executado agora em um projeto Rails 2.2:

c = Comment.first
# => #<Comment id: 1, body: "my comment">

c.body
# => "my comment"

c.text
# => "my comment"

c.body = "a new message"
# => "a new message"

c.body_changed?
# => true

c.text_changed?
# => true

c.text_change
# => ["my comment", "a new message"]

Todos os exemplos dados aqui funcionarão somente no Ruby on Rails 2.2 ou superior. Você pode encontrar mais detalhes sobre esta e outras novidades do Rails 2.2 no e-book “Ruby on Rails – O que há de novo?“.

Rails 2.2: Atualizando uma associação através de sua foreign key

29 de outubro de 2008  |  Rails 2.2  |  7 Comentários  | 

Não sei dizer se isto é um bug ou não, mas na minha opinião isto representa um problema. Veja o código abaixo, onde tento alterar a conta de um usuário usando sua foreign key em um projeto Rails 2.1 ou anterior:

class User < ActiveRecord::Base
  belongs_to :account
end

user = User.first
# => #<User id: 1, login: "admin", account_id: 1>

user.account
# => #<Account id: 1, name: "My Account">

user.account_id = 2
# => 2

user.account
# => #<Account id: 1, name: "My Account">

Note que estou alterando a conta do usuário, mas a associação não foi atualizada. Mesmo depois de salvar o objeto user, se ele não for recarregado, a associação continuará mostrando a conta errada.

No Rails 2.2 este problema foi corrigido, veja:

class Comment < ActiveRecord::Base
  belongs_to :post
end

comment = Comment.first
# => #<Comment id: 1>

>> comment.post
# => #<Post id: 1>

>> comment.post_id = 2
# => 2

>> comment.post
# => #<Post id: 2>

Veja que ao alterar o post por meio de sua foreign key, automaticamente a associação foi atualizada.


Todos os exemplos dados aqui funcionarão somente no Ruby on Rails 2.2 ou superior. Você pode encontrar mais detalhes sobre esta e outras novidades do Rails 2.2 no e-book “Ruby on Rails – O que há de novo?“.