18

Given the following classes:

class Candidate
  has_many :applications
  has_many :companies, :through => :job_offers
end

class JobOffer
  belongs_to :company
end

class Application
  belongs_to :candidate
  belongs_to :job_offer
end

enter image description here

How can I validate the previous statement (in the image) on Rails?

Adding the following validation on Application won't work when updating:

def validate_uniqueness_of_candidate_within_company
  errors.add(:job_offer_id, "...") if candidate.companies.include?(company)
end

Cause when trying to change the application to a different JobOffer of the same company candidate.companies will return that company.

I also tried doing something like this on Application:

validates_uniqueness_of :user_id, :scope => {:job_offer => :company_id}

But it didn't work either. Any ideas to solve this without having to use 10 lines of crappy code?

RRUZ
  • 134,889
  • 20
  • 356
  • 483
user1163964
  • 183
  • 1
  • 1
  • 5

4 Answers4

17

There are likely many acceptable approaches to solve your issue but I think the bottom line is that you're trying to enforce a uniqueness constraint on the table that doesn't (directly) have all the attributes (both company AND user). I'd de-normalize the company info into the application table (user_id, job_offer_id, company_id) and always set it in a before_save callback to match the job_offer's company. Then you should be able to use the scoped uniqueness validation:

class JobApplication < ActiveRecord::Base
  belongs_to :user
  belongs_to :job_offer
  belongs_to :hiring_company, :class_name=>"Company", :foreign_key=>"company_id"

  before_save :set_hiring_company

  def set_hiring_company
   self.hiring_company = job_offer.hiring_company
  end

  validates_uniqueness_of :user_id, :scope => :company_id
end
Matthias
  • 1,884
  • 2
  • 18
  • 35
miked
  • 4,489
  • 1
  • 17
  • 18
  • Your answer is correct. Though I don't want to de-normalize data just for a simple validation.. Thanks though! Any other ideas? – user1163964 Jan 23 '12 at 19:28
  • 1
    You could have different validations run :on=>:create than :on=>:update, but I still think you'll wind up circling back around to having the attribute in your join table. I'd be interested to see other solutions myself so I hope someone else chimes in. For me, this was the simplest solution (without 10 lines of yuck) ;-) Good luck. – miked Jan 23 '12 at 19:41
  • Looks like he easiest solution but from the eye of DB's is it really making sense? if we will add the parent's id field directly to the nested childs table what is the matter of using relational databases? and also, since you suggest adding before_save callback and this is creating a difference in between DB and logic in the project. – Baran Yeni Apr 06 '22 at 14:20
1

this is how I will structure the models:

class Candidate
  has_many :applicatons
  has_many :job_offers
  has_many :offering_companies, :through => :job_offers
end

class Application
  belongs_to :candidate
end

class JobOffer
  belongs_to :candidate
  belongs_to :company

  validates_uniqueness_of :candidate_id, :scope => :company_id
end

class Company
end

This should work! Btw. I will call the application something else to avoid the naming confusion( rails already has application.rb

ez.
  • 7,604
  • 5
  • 30
  • 29
  • Why does the JobOffer belong_to a candidate.. The job offer only belongs to the company.. I think you misunderstood the diagram from the picture. Thanks for your answer though! – user1163964 Jan 23 '12 at 01:54
0

For anyone finding this today. The answer is to check when there are pre-existing (prevents multiple) & skip when the application itself is switching.

def validate_uniqueness_of_candidate_within_company
  pre_existing = Application.includes(:candidate).includes(job_offer: :company).
    where(companies: { id: self.company.id }).
    where(candidates: { id: self.candidate.id })

  if pre_existing.present? && (!pre_existing.map(&:id).include? self.id)
    errors.add(:job_offer_id, "...")
  end
end
nitsujri
  • 1,448
  • 2
  • 16
  • 29
0

As the image points out, a candidate and a job offer will have many applications & a candidate can only apply to one job offer per company. So, validating uniqueness of candidate scoped to a job offer in application might do.

class Candidate
  has_many :applications
  has_many :job_offers, :through => :applications
end

class JobOffer
  belongs_to :company
  has_many :applications
  has_many :candidates, :through => :applications
end

class Application
  belongs_to :candidate
  belongs_to :job_offer

  validates_uniqueness_of :candidate_id, :scope => :job_offer_id
end

This will prevent an application to be associated to the same candidate for the same job offer. The candidate can apply to other job offers, right?

And as pointed out by @ez., renaming application to something appropriate is better.

Hope this helps.

prasvin
  • 3,009
  • 23
  • 28
  • This is not quite right. As said on the picture, a candidate cannot apply to 2 job offers of the same company.. This will only prevent the candidate to apply to the same offer twice.. – user1163964 Jan 23 '12 at 19:26