Relacja many-to-many through z dodatkową kolumną

Witajcie.
Męczę się nad jednym problemem. Dla uproszczenia przyjmę inne nazwy modeli niż w docelowym projekcie.

Mam modele:

class User < ActiveRecord::Base
  has_many :posts, through: :user_posts
  has_many :user_posts
end

class Post < ActiveRecord::Base
  has_many :users, through: :user_posts
  has_many :user_posts
end

class UserPost < ActiveRecord::Base
  belongs_to :user
  belongs_to :post
end

Model UserPost ma jeszcze pole role_id. Chodzi o to, że np. user1 może być autorem posta, a user2 recenzentem. Role userów zależą więc od relacji, dla innych postów te role mogą rozkładać się inaczej. Jak teraz w najefektywniejszy sposób “dobrać się” do informacji, kim dany user jest dla konkretnego postu?

Próbowałem też używać gemu rolify który umożliwiał coś takiego, ale trzymał wszystkie relacje w jednej tablicy - w docelowym projekcie model User będzie miał relacje z innymi modelami, w których też te role trzeba będzie uwzględnić - nie wydaje mi się to zbytnio wydajne. Jaki będzie najlepszy sposób na rozwiązanie tego?

Jeżeli w Twoim modelu jest tylko jeden recenzent i tylko jeden autor, a obaj mają odwzorowanie w tabeli User, to wydaje mi się, że możesz spróbować jakoś tak:

Utwórz na początek “z ręki” dwa pliki:
/models/author.rb

class Author < ActiveRecord::Base
  self.table_name = 'users' 
  has_many :posts, foreign_key: :author_id, class_name: "Post"
end

/models/critic.rb

class Critic < ActiveRecord::Base
  self.table_name = 'users'
  has_many :posts, foreign_key: :critic_id, class_name: "Post" 
end
  1. z wiersza poleceń: rails g Post author:references critic:references title:string note:text

co da: /db/migrate/xxxxxxxxcreate_post.rb

class CreatePosts < ActiveRecord::Migration
  def change
    create_table :posts do |t|
      t.references :author, index: true
      t.references :critic, index: true
      t.string :title
      t.text :note

    end
    add_foreign_key :posts, :users, column: :author_id, primary_key: "id"
    add_foreign_key :posts, :users, column: :critic_id, primary_key: "id"

  end
end
  1. w pliku /models/post.rb

    class Post < ActiveRecord::Base
    belongs_to :author
    belongs_to :critic
    end

…Jakąś tabele “User” zakładam, że oczywiście już masz.

Od tej pory możesz już używać “klasycznych” konwencji
np:
Users = [ {id: 1, name: “Kowalski”}, {id: 2, name: “Nowak”} ]
Posts = [
{id: 1, author_id: 1, critic_id: 1, title: “abrakadabra”},
{id: 2, author_id: 1, critic_id: 2, title: “abrakadabra 2”} ]

p = Post.find_by(id: 2) 
p.title           # => "abrakadabra 2"
p.author.name     # => "Kowalski"
p.critic.name     # => "Nowak"

c = Critic.find_by(id: 1)
c.posts       # => lista postów gdzie User(id: 1) występuje jako critic

Dziękuję za odpowiedź:)

Właśnie problem jest nieco głębszy… Chodzi o to że taki Post może mieć wielu autorów, wielu recenzentów, i może jeszcze innych ról… Taka relacja wszystko do wszystkiego;)

Idąc dalej, w docelowym projekcie, tak jak wspomniałem w pierwszym poście, będą kolejne relacje między modelem User , a dajmy na to, Food. I znowu to samo - User może mieć wiele foods, Food może mieć wiele users, niektórzy userzy w tej relacji będą kucharzami, inni degustatorami itp… Takich relacji z modelem User będzie ok 3-4. Dlatego kończą mi się pomysły…

I would solve it like this

post.rb

class Post < ActiveRecord::Base
  has_many :authors, through: :posts_authors, class_name: 'User'
  has_many :post_authors -> { authors }, class_name: 'PostUser'

  has_many :referents, through: :posts_referents, class_name: 'User'
  has_many :post_referents -> { referents }, class_name: 'PostUser'
end

post_user.rb

class PostUser < ActiveRecord::Base
  enum role: [:author, :referent]
end

But of cause if you have a bunch of role for every relation. Then you can define role list in a separate module and then just use it like this

class PostUser < ActiveRecord::Base
  enum role: POST_USER_ROLES
end
class Post < ActiveRecord::Base
  POST_USER_ROLES.each do |role|
   has_many role.to_multiple.to_sym, through: :"posts_#{role.to_multiple}", class_name: 'User'
   has_many :"posts_#{role.to_multiple}" -> { public_send(role.to_multiple.to_sym) }, class_name: 'PostUser'
  end
end

example of to_multiple method

def to_multiple
  to_s.pluralize
end
1 Like