Matthew Berman
Matthew Berman

Reputation: 8631

How do I validate the uniqueness of a has_many :through join model?

I have users and issues joined by a votership model. Users can vote on issues. They can either vote up or down (which is recorded in the votership model). First, I want to be able to prevent users from casting multiple votes in one direction. Second, I want to allow users to cast the opposite vote. So, if they voted up, they should still be able to vote down which will replace the up vote. Users should never be able to vote on an issue twice. Here are my files:

class Issue < ActiveRecord::Base
  has_many :associations, :dependent => :destroy

  has_many :users, :through => :associations

  has_many :voterships, :dependent => :destroy
  has_many :users, :through => :voterships

  belongs_to :app

  STATUS = ['Open', 'Closed']

  validates :subject, :presence => true,
                      :length => { :maximum => 50 }
  validates :description, :presence => true,
                          :length => { :maximum => 200 }
  validates :type, :presence => true
  validates :status, :presence => true

  def cast_vote_up!(user_id, direction)
    voterships.create!(:issue_id => self.id, :user_id   => user_id,
                                             :direction => direction)
  end
end


class Votership < ActiveRecord::Base
  belongs_to :user
  belongs_to :issue
end

class VotershipsController < ApplicationController
  def create
    session[:return_to] = request.referrer
    @issue = Issue.find(params[:votership][:issue_id])
    @issue.cast_vote_up!(current_user.id, "up")
    redirect_to session[:return_to]
  end
end

class User < ActiveRecord::Base
  authenticates_with_sorcery!

  attr_accessible :email, :password, :password_confirmation

  validates_confirmation_of :password
  validates_presence_of :password, :on => :create
  validates_presence_of :email
  validates_uniqueness_of :email

  has_many :associations, :dependent => :destroy
  has_many :issues, :through => :associations

  has_many :voterships, :dependent => :destroy
  has_many :issues, :through => :voterships
end

Upvotes: 8

Views: 6135

Answers (2)

Kiry Meas
Kiry Meas

Reputation: 1242

Relationship models:

class Person
  has_many :accounts
  has_many :computers, through: :accounts
end

class Account
  belongs_to :person
  belongs_to :computer

  scope :administrators, -> { where(role: 'administrator') }
end

class Computer
  has_many :accounts
  has_many :people, through: :accounts
end

This is how it is called

person.accounts.administrators.map(&:computer)

We can do this better using ActiveRecord::SpawnMethods#merge!

person.computers.merge(Account.administrators)


Ref: https://coderwall.com/p/9xk6ra/rails-filter-using-join-model-on-has_many-through

Upvotes: 0

jefflunt
jefflunt

Reputation: 33954

You would put the uniqueness constraint on the Votership model. You don't need to put validations on the association itself.

class Votership < ActiveRecord::Base
  belongs_to :user
  belongs_to :issue

  validates :issue_id, :uniqueness => {:scope=>:user_id}
end

This means a user can only have a single vote on a given issue (up or down).

Upvotes: 12

Related Questions