[RoR] Relacionamento NxN com Ruby on Rails

21 set 2011(há 13 anos)
DevelopmentRuby on Rails
#self.up#self.down#rake#migration#migrate#has_many#has_and_belongs_to_many#capitalize#belongs_to

[RoR] Relacionamento NxN

introdução

Dando continuidade ao ultimo post, hoje veremos como configurar relacionamento NxN em Rails, nosso relacionamento será: Usuário, como autor possui vários posts e post pertence a um autor.

1 - Vamos começar adicionando um campo author_id ao model Post pois é ele que será relacionado com o model User (author), ou seja, um author tem varios post, bem sem mais delongas, "let's go nessa"... vamos gerar uma migration para adicionar o campo author_id

rails g migration add_author_id_to_post author_id:integer

Bem vamos abrir o arquivo 20110914163321_add_author_id_to_post.rb para verificar se está tudo cerinho, vejam que no arquivo no método up existe a linha: add_column :posts, :author_id, :integer, que faz referência a Post, porém em nenhum momento eu disse que ele teria um referencia de Post, bem isso acontece por causa de uma das convenções de rails no comando ...migration add_author_id_to_post... o to_post do final indica que ele deve referenciar Post

class AddAuthorIdToPost < ActiveRecord::Migration
  def self.up
    add_column :posts, :author_id, :integer
  end
    def self.down
    remove_column :posts, :author_id
  end
end

Vamos rodar um "rake db:migrate" pois é assim que queremos que fique essa migration:

rake db:migrate

Depois do "rake db:migrate" precisamos fazer as configurações dos models User, que será o "Author", e Post, bem no model User adicionaremos:

has_many :posts

Isso identifica que um usuário terá vários posts, o model Post adicionaremos:

belongs_to :author, :class_name => "User", :foreign_key => "author_id"

Que indica que um post pertence a um autor que é referenciada na classe User e author_id como chave estrangeira, aproveitando que estamos no post e também adicionaremos validações para autor deste post, bem no model já existiam algumas validações para atributo em branco adicionaremos o de autor associado:

validates_presence_of :title, :body, :author
validates_associated :author

vamos adicionar um método para retornar o nome completo:

def full_name
  "#{first_name} #{last_name}".capitalize
end

Bem agora é hora inserir o combobox de user(author) no formulário de post, vou inserir antes do campo title:

<%= f.label :author_id, "Author" %>
<%= f.collection_select :author_id, @authors, :id, :full_name, :prompt => "Selecione o autor" %>

Vejam que não declarei o @authors em canto nenhum, poderia passar nas actions new, create, edit, update, porém rails tem uma coisa bacana chamada before_filter que podemos dizer que um metodo será carregado em algumas actions pré-definidas, vamos ver com vai ficar o post_controller:

# adicione esta linha no inicio
before_filter :load_authors, :only => [:new,:create, :edit, :update]
# e esse método no fim
protected
  def load_authors
    @authors = User.all
  end

Depois disso vamos criar um scaffold de categoria e rodar um "rake db:migrate":

rails g scaffold category name:string
rake db:migrate

Como em todo banco relacional quando há um relacionamento NxN é necessário criar tabela auxiliar para isso é só rodar uma "migration"

rails g migration create_categories_posts

abra o arquivo que foi criado e deixe-o como o code abaixo:

class CreateCategoriesPosts < ActiveRecord::Migration
def self.up
  create_table "categories_posts", :id => false, :force => true do |t|
      t.integer "post_id", :null => false
      t.integer "category_id", :null => false
    end
  end
    def self.down
    drop_table "categories_posts"
  end
end

e rode um

rake db:migrate

no model category adicione:

validates_presence_of :name

e no model post adicione:

has_and_belongs_to_many :categories

e o inverso model categoty:

has_and_belongs_to_many :posts

Lá em cima usamos um before_filter com o metodo load_authors vou renomea-lo para load_resources, pois ele ira carregar também as categorias, então no post_controller mude para

# adicione esta linha no inicio
before_filter :load_resources, : only => [:new,:create, :edit, :update]
# e esse método no fim
protected
  def load_resources
    @categories = Category.all
    @authors = User.all
  end

Depois de fazer as alterações acima é necessário colocar o campo categoria no form post:

<% @categories.each do |c| %> <%= check_box_tag "post[category_ids][]", c.id, @post.categories.include?(c) %> <%= c.name%> <% end %>

Se você tentar testar se tudo der certo não irar apercer nenhum checkbox mas é só acessar http://0.0.0.0:3000/categories e adicionar algumas categorias.

Conclusão

Para finalização deste post vamos deixar a receita de bolo de como fazer o relacionamento NxN.

1º has_and_belongs_to_many nos dois modelos

- para o rails criar os métodos de relacionamento ex: para pegar todas as categorias de um post é só chamar o método Post.first.categories

2º criar migração manualmente

3º ajustar migração sem chave primári

4º seguir a ordem alfabética para nome das tabelas

Como sempre ta aqui o link para download do projeto até então, bem "Isso é tudo pessoal" em breve voltarei com a continuação