Relacionamento ternário

59 views
Skip to first unread message

Andressa Leonel

unread,
Jan 26, 2009, 6:05:38 PM1/26/09
to rail...@googlegroups.com
Boa noite, pessoal.

Estou pesquisando sobre relacionamentos ternários no Rails, mas não tenho encontrado muita coisa.
Considerem o seguinte contexto:
-> Modelos: user, link e tag.

-> Um usuário pode cadastrar vários links e um link pode pertencer a vários usuários.
-> Um link possui várias tags associadas e uma tag pode pertencer a vários links.
-> É preciso recuperar quais tags um usuário já associou aos seus links.

Imagino ter uma tabela users, uma tabela links, uma tabela tags e uma tabela "join" links_tags_users (user_id, link_id, tag_id). Com esta tabela, eu consigo recuperar os links de cada usuário e suas tags associadas e tudo o mais que preciso. Como esta tabela não possui nenhum outro atributo sem ser os _ids, pensei que iria usar o HABTM, num relacionamento ternário. Porém, não estou muito certa após ler este post (o único que encontrei sobre este assunto): http://www.stonemind.net/blog/2008/03/12/exploring-ternary-associations-in-ruby-on-rails/

Será que alguém pode me ajudar a entender como implementar esse relacionamento/tabela? Li na documentação que o HABTM é para relacionar dois modelos... Alguma dica?

Obrigada,
Andressa


Rodrigo Toledo

unread,
Jan 26, 2009, 6:32:05 PM1/26/09
to rail...@googlegroups.com
Acho que o jeito de se chegar ao resultado mais próximo ao rails way
seria você ter
users
links
links_users
tags
links_tags

Então acho que usando

<< User
has_and_belongs_to_many :links
has_many :tags, :through => :links

<< Link
has_and_belongs_to_many :users
has_and_belongs_to_many :tags

<< Tag
has_and_belongs_to_many :links


Acredito que se fizer:

User.first.tags
Ele fará essa ligação


2009/1/26 Andressa Leonel <dessa...@gmail.com>:

Andressa Leonel

unread,
Jan 30, 2009, 11:36:09 AM1/30/09
to rail...@googlegroups.com
Oi, Rodrigo.

Infelizmente essa implementação não me atende.. Os links não serão duplicados no banco e podem pertencer a vários usuários. Porém eu preciso saber exatamente qual link cada usuário gravou. Neste primeiro caso, a tabela links_users resolve, mas não é só isso. Um usuário pode cadastrar várias tags (que também não serão duplicadas) e cada link pode possuir várias tags. Com a tabela links_tags eu consigo recuperar quais tags tal link possui, mas com essa modelagem não consigo recuperar quais tags determinado usuário salvou (se dois usuários salvarem o mesmo link, não consigo determinar quem salvou a tag x ou y).

Para tanto, a solução que eu encontrei foi ter uma tabela join tripla links_tags_users, e montar os relacionamentos assim:

user has_many :links, :through => :links_tags_users
user has_many :tags, :through => :links_tags_users
link has_many :users, :through => :links_tags_users
link has_many :tags, :through => :links_tags_users
tag has_many :users, :through => :links_tags_users
tag has_many :links, :through => :links_tags_users


links_tags_users belongs to :user
links_tags_users belongs to :link
links_tags_users belongs to :tag


Essa solução é baseada no exemplo do link que postei.. Não encontrei outra que me atendesse como preciso.

Obrigada pela ajuda,
Andressa

2009/1/26 Rodrigo Toledo <agc.r...@gmail.com>

Eduardo Resende

unread,
Jan 30, 2009, 4:02:25 PM1/30/09
to rail...@googlegroups.com
rodrigo ela precisa desse relacionamento ternario pq a tag de um user, contem links diferentes de uma tag com mesmo nome em outro user

exemplo, se eu tenho o link www.rails-br.com na tag "trabalho"
e a Andressa tem o mesmo link na tag "escola"

nos temos tags diferentes, mas se eu buscar as minhas tags, nao pode vir "escola" pois apesar de a tag estar no mesmo link que eu tenho, ela nao me pertence.

ela quer saber como verificar se um link pertence a um usuario especifico.

uma solução seria criar um metodo no model User:

def tem_o_link?(link)
        links_do_user = Ligacao.find_by_user(self.id).collect{|lig| lig.link_id}
        return links_do_user.includes?(link.id) # retorna falso ou verdadeiro
end

mas eh correto fazer um Find de um model dentro de um metodo de outro model??

Andressa Leonel

unread,
Jan 30, 2009, 4:10:35 PM1/30/09
to rail...@googlegroups.com
    def has_link? (link)
        LinksTagsUsers.(:first, :conditions => ["link_id = ? AND user_id = ?", link, self.id]).nil?
    end
Eu fiz o método assim! E como o Eduardo falou, a minha dúvida é se é politicamente correto fazer a pesquisa em um modelo num método em outro modelo.
E sobre o relacionamento complicado, eu queria discutir sobre o assunto, mas parece que o pessoal não se animou muito. :(

Obrigada,
Andressa

2009/1/30 Eduardo Resende <ed.re...@gmail.com>

Andressa Leonel

unread,
Jan 30, 2009, 4:12:56 PM1/30/09
to rail...@googlegroups.com
Ah, só esqueci de negar o nil no final..

2009/1/30 Andressa Leonel <dessa...@gmail.com>

Andressa Leonel

unread,
Jan 30, 2009, 4:21:51 PM1/30/09
to rail...@googlegroups.com
    def has_link? (link)
        ! LinksTagsUsers.find(:first, :conditions => ["link_id = ? AND user_id = ?", link, self.id]).nil?
    end

Tava faltando o find também, rs.

2009/1/30 Andressa Leonel <dessa...@gmail.com>

Cássio Marques

unread,
Jan 30, 2009, 4:23:39 PM1/30/09
to rail...@googlegroups.com
Na minha opinião usar find de um model dentro de outro é perfeitamente aceitável. Enquanto você não estiver quebrando o MVC (que nesse caso não ocorre já que estamos falando apenas de models) e não quebrar o encapsulamento de nada ou depender de detalhes de implementação das classes referenciadas, não há problema nenhum. Lógico que isso tem que ser feito com cuidado, caso contrário pode-se aumentar demais o acoplamento entre as classes. Uma boa forma de verificar isso é ver se está fácil de ser testado, Se ficar muito difícil, é porque a classe sendo testada está dependendo demais de outra(s).

2009/1/30 Andressa Leonel <dessa...@gmail.com>



--
Cássio Marques

Programador, nerd para caramba, vegan e skatista (não necessariamente nesta mesma ordem)

Blog: http://cassiomarques.wordpress.com

If you're writing code and you're not testing it, the code is wrong. I don't care if it does the right thing, and people need to understand this. If it works by accident, you're still wrong.
Bryan Liles - Ruby Hoedown 2008

Estou vendendo um monte de cds (hardcore, metal, pop, etc) http://spreadsheets.google.com/pub?key=pT61KxmNfc8zS0YX8e8JhOw

Cássio Marques

unread,
Jan 30, 2009, 4:25:12 PM1/30/09
to rail...@googlegroups.com
Andressa, nesse caso ficaria mais elegante se você colocasse esse find sobre a classe LinksTagsUsers dentro de um método dessa mesma classe.

Andressa Leonel

unread,
Jan 30, 2009, 4:51:59 PM1/30/09
to rail...@googlegroups.com
É que eu parti do princípio de que eu quero verificar se o usuário possui o link, logo devo chamar o método sobre o usuário. Chamando sobre o usuário eu passo apenas o link como parâmetro.
Se eu chamar sobre LinksTagsUsers, terei que passar o user_id e o link_id como parâmetros e acho que fica menos claro. A nao ser que fosse algo como LinksTagsUsers.has_user_and_his_link?(user_id, link_id)

Assim?

Obrigada,
Andressa

2009/1/30 Cássio Marques <cass...@gmail.com>

Cássio Marques

unread,
Jan 30, 2009, 4:56:51 PM1/30/09
to rail...@googlegroups.com
Sim, a idéia é exatamente essa que você mostrou no seu exemplo, mas você poderia abstrair isso ainda mais. Se voc tiver o método LinksTagsUsers.has_user_and_his_link?(user_id, link_id), pode criar também um outro em User:

def has_link?(link_id)
  LinksTagsUsers.has_users_and_his_link?(self.id, link_id)
end

Ai você fazer o que queria, pesquisar sempre sobre o usuário e esquecer que o outro método existe na classe LinksTagsUsers.



2009/1/30 Andressa Leonel <dessa...@gmail.com>

Andressa Leonel

unread,
Jan 30, 2009, 5:15:28 PM1/30/09
to rail...@googlegroups.com
Pois é, eu também não tinha certeza sobre fazer isso, pois assim seriam duas chamadas de função, sendo que uma delas seria desnecessária (eu ia empilhar uma função sem precisar)...

Essa foi a minha dúvida inicial: o preço que se paga por fazer essa chamada é muito alto? O que pesa é a minha falta de experiência sobre essas coisas práticas da programação, rs. Sei que devemos evitar empilhar funções demais de forma arbitrária, mas eu não sei qual é o reflexo real disso.

Enfim, não sei ao certo, se alguém puder ajudar com sua opinião acharia otimo!

Cássio Marques

unread,
Jan 30, 2009, 5:24:31 PM1/30/09
to rail...@googlegroups.com
Bom, eu penso assim:

Otimizar é algo que você deve fazer quando souber que precisa e souber exatamente onde otimizar. Se assim estiver funcionando bem e o código estiver organizado, não vejo porque mexer, pelo menos não agora. Se um dia isso vier a dar problemas por não escalar, você faz profiling e se necessário refatora de alguma forma.

O grande problema ai é que realmente fica melhor fazer a chamada sobre instâncias da classe User. A chamada adicional ao método da classe LinksTagsUsers acho que é irrisório, não fará diferença praticamente. O que pode vir a se tornam um problema um dia é que dependendo da lógica da sua app em alguns pontos podem haver duas consultas ao banco, uma pra buscar o usuário e depois outra pra verificar se ele tem o link... Por curiosidade, como é seu model User ?
2009/1/30 Andressa Leonel <dessa...@gmail.com>

Andressa Leonel

unread,
Jan 30, 2009, 5:49:50 PM1/30/09
to rail...@googlegroups.com
Ah, meu model user não tem quase nada demais. Tudo que o plugin restful_authentication gerou mais isso só:

 has_many :detalhes

 has_many :links, :through => :links_tags_users
 has_many :tags, :through => :links_tags_users

    # verifica se o usuario possui determinado link. ainda falta eu decidir como colocar este metodo =]

        def has_link? (link)
        ! LinksTagsUsers.find(:first, :conditions => ["link_id = ? AND user_id = ?", link, self.id]).nil?
    end

Obrigada pela sua opinião! Eu sempre me pego em dúvidas do tipo "o que custa mais?" e nesses casos é bom saber o ponto de vista de outras pessoas. =]

Att,
Andressa

Cássio Marques

unread,
Jan 30, 2009, 5:57:49 PM1/30/09
to rail...@googlegroups.com
A idéia é não usar a associação :links para não trazer tudo do banco?

Andressa Leonel

unread,
Feb 2, 2009, 10:38:47 AM2/2/09
to rail...@googlegroups.com
Olá, pessoal!
Achei uma solução para o problema do relacionamento! Muito mais simples, muito mais bonita, muito melhor que a anterior que eu havia postado! =p

Coloquei aqui:
http://dessaonrails.blogs.sapo.pt/7209.html

Obrigada a todos!
Andressa



2009/1/31 Andressa Leonel <dessa...@gmail.com>
Eu não tinha pensado em fazer isso, eu acho.. Mas mesmo assim se tivesse pensado não daria, pq o nome do modelo da minha tabela join estava escrito errado (estava links_tags_users, enquanto deveria ser links_tags_user pra funcionar direito).

Mas aí eu fiz as alterações e usei a associação :links como você mostrou. =]

De qualquer forma acho que vou deixar dessa maneira mesmo! Acho que o custo em retornar todos os links não seja muito grande nesse meu caso específico. Obrigada!

ps: Não respondi na lista pq não sabia se suas mensagens anteriores iriam junto. Se elas não fossem não teria muita lógica.. Então quando você responder esta já pode encaminhar pra lista, se der pra mandar tudo junto! rs


Obrigada,
Andressa

2009/1/30 Cássio Marques <cass...@gmail.com>
Você poderia usar a associação :links no seu método

class User < AR::Base

  def has_link?(link_id)
    self.links.detect { |link| link.id == link_id }
  end
end

O problema é que isso vai causar um select que traga todos os links do usuário.


2009/1/30 Andressa Leonel <dessa...@gmail.com>
huahus
Inacreditavelmente, não entendo nenhuma das suas perguntas. =p
Reply all
Reply to author
Forward
0 new messages