menjadi milik melalui asosiasi

141

Mengingat asosiasi berikut ini, saya perlu merujuk Questionbahwa a Choicedilampirkan melalui Choicemodel. Saya telah berusaha menggunakan belongs_to :question, through: :answeruntuk melakukan tindakan ini.

class User
  has_many :questions
  has_many :choices
end

class Question
  belongs_to :user
  has_many :answers
  has_one :choice, :through => :answer
end

class Answer
  belongs_to :question
end

class Choice
  belongs_to :user
  belongs_to :answer
  belongs_to :question, :through => :answer

  validates_uniqueness_of :answer_id, :scope => [ :question_id, :user_id ]
end

Saya mendapatkan

NameError konstanta tidak diinisialisasi User::Choice

ketika saya coba lakukan current_user.choices

Ini berfungsi dengan baik, jika saya tidak memasukkan

belongs_to :question, :through => :answer

Tetapi saya ingin menggunakannya karena saya ingin dapat melakukan itu validates_uniqueness_of

Saya mungkin menghadap sesuatu yang sederhana. Bantuan apa pun akan dihargai.

vinhboy
sumber
1
Mungkin ada baiknya mengubah jawaban yang diterima ke delegasi?
23inhouse

Jawaban:

60

Sebuah belongs_toasosiasi tidak dapat memiliki :throughpilihan. Anda lebih baik melakukan caching question_idon Choicedan menambahkan indeks unik ke tabel (terutama karena validates_uniqueness_ofrentan terhadap kondisi balapan).

Jika Anda paranoid, tambahkan validasi khusus ke Choiceyang mengonfirmasi bahwa jawabannya question_idcocok, tetapi sepertinya pengguna akhir tidak boleh diberi kesempatan untuk mengirimkan data yang akan membuat ketidakcocokan seperti ini.

stephencelis
sumber
Terima kasih Stephen, saya benar-benar tidak ingin bergaul langsung dengan question_id, tapi saya rasa ini cara termudah. Pikiran asli saya adalah, karena "jawaban" adalah milik "pertanyaan", saya selalu dapat melalui "jawaban" untuk sampai ke "pertanyaan". Tetapi apakah Anda pikir itu tidak mudah untuk dilakukan, atau apakah Anda pikir itu hanya skema yang buruk?
vinhboy
Jika Anda menginginkan batasan / validasi unik, bidang yang dicakup harus ada dalam tabel yang sama. Ingat, ada kondisi lomba.
stephencelis
1
>> sepertinya pengguna akhir tidak boleh diberi kesempatan untuk mengirimkan data yang akan membuat ketidakcocokan seperti ini. - Anda tidak pernah dapat menjamin bahwa pengguna "tidak memiliki kesempatan untuk melakukan sesuatu" kecuali Anda melakukan pemeriksaan sisi server eksplisit untuk itu.
Konstantin
376

Anda juga dapat mendelegasikan:

class Company < ActiveRecord::Base
  has_many :employees
  has_many :dogs, :through => :employees
end

class Employee < ActiveRescord::Base
  belongs_to :company
  has_many :dogs
end

class Dog < ActiveRecord::Base
  belongs_to :employee

  delegate :company, :to => :employee, :allow_nil => true
end
Renra
sumber
27
+1, ini adalah cara terbersih untuk melakukan ini. (setidaknya itu bisa saya pikirkan)
Orlando
9
Apakah ada cara untuk melakukan ini dengan BERGABUNG sehingga tidak menggunakan banyak pertanyaan?
Tallboy
1
Saya ingin tahu sendiri. Semua yang saya coba melepaskan 3 pilihan. Anda dapat menentukan lambda "-> {joins: something}" di sebuah asosiasi. Bergabung dipecat tetapi selanjutnya pilih lagi pula. Saya tidak bisa memperbaiki ini.
Renra
2
@Tallboy Beberapa kueri pemilihan yang diindeks dengan sempurna pada kunci primer hampir selalu lebih baik daripada permintaan JOIN tunggal. Bergabung membuat database bekerja keras.
Ryan McGeary
1
Apa yang boleh dilakukan allow_nil? Bukankah seharusnya seorang Karyawan selalu memiliki perusahaan?
aaron-coding
115

Cukup gunakan has_onealih-alih belongs_todalam :through, seperti ini:

class Choice
  belongs_to :user
  belongs_to :answer
  has_one :question, :through => :answer
end

Tidak terkait, tetapi saya akan ragu untuk menggunakan validates_uniqueness_of daripada menggunakan batasan unik yang tepat dalam database Anda. Ketika Anda melakukan ini di ruby ​​Anda memiliki kondisi balapan.

mrm
sumber
38
Peringatan besar dengan solusi ini. Setiap kali Anda menyimpan Pilihan, itu akan selalu menyimpan Pertanyaan kecuali jika autosave: falsediatur.
Chris Nicola
@ ChrisNicola bisa tolong jelaskan apa yang Anda maksud, saya tidak mengerti apa yang Anda maksud.
aks
Apa yang saya maksudkan di mana? Jika Anda bermaksud kendala unik yang tepat, maksud saya menambahkan indeks UNIK ke kolom / bidang yang harus unik dalam database.
Chris Nicola
4

Pendekatan saya adalah membuat atribut virtual alih-alih menambahkan kolom basis data.

class Choice
  belongs_to :user
  belongs_to :answer

  # ------- Helpers -------
  def question
    answer.question
  end

  # extra sugar
  def question_id
    answer.question_id
  end
end

Pendekatan ini cukup sederhana, tetapi disertai dengan pengorbanan. Itu membutuhkan Rails untuk memuat answerdari db, dan kemudian question. Ini dapat dioptimalkan nanti dengan bersemangat memuat asosiasi yang Anda butuhkan (yaitu c = Choice.first(include: {answer: :question})), namun, jika optimasi ini diperlukan, maka jawaban stephencelis mungkin merupakan keputusan kinerja yang lebih baik.

Ada waktu dan tempat untuk pilihan tertentu, dan saya pikir pilihan ini lebih baik ketika membuat prototipe. Saya tidak akan menggunakannya untuk kode produksi kecuali saya tahu itu untuk kasus penggunaan yang jarang.

Eric Hu
sumber
1

Sepertinya yang Anda inginkan adalah Pengguna yang memiliki banyak Pertanyaan.
Pertanyaan memiliki banyak Jawaban, salah satunya adalah Pilihan Pengguna.

Apakah ini yang Anda cari?

Saya akan memodelkan sesuatu seperti itu di bawah ini:

class User
  has_many :questions
end

class Question
  belongs_to :user
  has_many   :answers
  has_one    :choice, :class_name => "Answer"

  validates_inclusion_of :choice, :in => lambda { answers }
end

class Answer
  belongs_to :question
end
Adam Tanner
sumber
1

Jadi Anda tidak dapat memiliki perilaku yang Anda inginkan tetapi Anda dapat melakukan sesuatu yang terasa seperti itu. Anda ingin dapat melakukannyaChoice.first.question

apa yang telah saya lakukan di masa lalu adalah sesuatu seperti ini

class Choice
  belongs_to :user
  belongs_to :answer
  validates_uniqueness_of :answer_id, :scope => [ :question_id, :user_id ]
  ...
  def question
    answer.question
  end
end

dengan cara ini Anda sekarang dapat mengajukan pertanyaan pada Pilihan

MZaragoza
sumber
-1

The has_many :choicesmenciptakan sebuah asosiasi yang bernama choices, tidak choice. Coba gunakan current_user.choicessebagai gantinya.

Lihat dokumentasi ActiveRecord :: Associations untuk informasi tentang has_manysihir.

Michael Melanson
sumber
1
Terima kasih atas bantuan Anda Michael, bagaimanapun, itu salah ketik di pihak saya. Saya sudah melakukan current_user.choices. Kesalahan ini ada hubungannya dengan saya yang ingin memberikan hak milik kepada pengguna dan pertanyaan.
vinhboy