RoR - Validates_existence_of with nil foreign key not working - ruby-on-rails

I am using the validates_existence_of gem. It works well except when I want to allow my foreign key to be nil.
Here are my models for User and Project. A project belongs_to a user and a contributor (a contributor is also a user), but the contributor can be nil.
Here is my user model:
class User < ActiveRecord::Base
attr_accessible :first_name, :last_name
has_many :projects, :dependent => :destroy
has_many :user_trimester_statuses, :dependent => :destroy
end
And here is my project model:
class Project < ActiveRecord::Base
attr_accessible :added, :amount, :contributor_id, :label, :ref, :trimester_id, :user_id
belongs_to :user
belongs_to :contributor, :class_name => 'User'
belongs_to :trimester
validates :user, :existence => { :both => false }
validates :trimester, :existence => { :both => false }
validates :contributor, :existence => { :allow_nil => true, :both => false }
end
When I try to add a new project, I have an error if the user_id or trimester_id field is blank or invalid. But for the contributor_id field, there is no error thrown if the field is invalid. It goes through either way (valid, invalid, or nil).
What am I doing wrong?
I am using ruby 2.0.0p0 and rails 3.2.13.

It looks like there is an open bug about this in the project.
https://github.com/perfectline/validates_existence/issues/15
You may have to write a simple custom validator for this case until this gets fixed. (or dig in and see if you can fix the issue yourself)
UPDATE:
I just cloned the project and wrote a test to see what the issue was. It seems that when you add the allow_nil, the existence validator doesn't get called at all.
I'm not sure why that is, but in the meantime, you can work around the bug in an easy way, by using a proc.
instead of
validates :contributor, :existence => { :allow_nil => true, :both => false }
this would get the job done
validates_existence_of :contributor, :unless => Proc.new { |obj| obj.contributor_id.blank? }
I was able to prove that in my test case.
(I went with the 'validates_existence_of' method, instead of 'validates', because I thought it was cleaner in this case)

Related

How to validate_presence_of with condition in a nested attributes?

I have model Question, Answer and AnswerDetail.
Answer:
class Answer < ActiveRecord::Base
has_many :answer_details, :dependent => :destroy
accepts_nested_attributes_for :answer_details, :allow_destroy => true
validates_associated :answer_details
end
AnswerDetail:
class AnswerDetail < ActiveRecord::Base
belongs_to :answer
belongs_to :question
validates_presence_of :answer_field, :if => lambda {isrequired == true}, :message => "This is required field"
end
The isrequired field is from the model Question.
Question:
class Question < ActiveRecord::Base
has_one :answer_detail
end
The AnswerDetal model has the question_id and answer_id field on it. I want to filter the answer_field if the isrequire field from Question model is equal to true? How I will do this? How to access the has_one association's attribute inside model?
I've done this before, although I've not got the code handy right now:
Nested
From what I remember, you can actually put the validation in the nested model:
#app/models/question.rb
class Question < ActiveRecord::Base
validates :answer_field, presence: true, if: lambda {isrequired == true}
end
I highly recommend using the new validates syntax
--
inverse_of
I'm sure I had to use inverse_of somewhere in the code I had (it's locked in a private GitHub repo sorry).
inverse_of basically includes the associated model in your current model. Much like how you've found the effectiveness of self.question.isrequired:
#app/models/answer_detail.rb
class AnswerDetail < ActiveRecord::Base
belongs_to :question, inverse_of: :answer_detail
validates :answer_field, presence: true, if: lambda { question.isrequired == true }
end
#app/models/question.rb
class Question < ActiveRecord::Base
has_one :answer_detail, inverse_of: :question
end
However, good news for you:
This is not the answer, this is how I would troubleshoot
Inspect in:
validates_presence_of :answer_field, :if => lambda {isrequired == true}, :message => "This is required field"
What variables you have available, you can do this with pry-rails gem. Like this:
validates_presence_of :answer_field, :if => lambda {binding.pry; isrequired == true}, :message => "This is required field"
When trying to save a question you will get a interactive console(in the server terminal window) where you can print values like self or do this:
validates_presence_of :answer_field, :if => lambda {|record| binding.pry; isrequired == true}, :message => "This is required field"
And print record.
Then you can try finding where Question is stored with: self.question or record.question.
I tried using self.question.isrequired and it is working. But maybe you have other nice suggestion to do the error validation. Thank you.

Rails 3.1.0 conditional validations on nested attributes

I have a structure where an Item may belong to a Claim, and if it does, I want another of its fields to be required as well. These are the relevant code snippets:
class Claim
has_many :items
accepts_nested_attributes_for :items
validates_associated :items
end
class Item
belongs_to :claim
validates :amount_paid, :presence => {:if => :claim}
end
And this works in almost every case. When I edit an existing Claim and try to enter blanks in the amount_paid field, I get the errors I want. And the Claim should exist when it hits this validation, because a previous iteration, which also worked, had the equivalent of
validates :claim_id, :presence => {:unless => :new_claim?}
...
def new_claim?
claim.new_record? # would have thrown an error if claim was nil
end
But when I create a new Claim with blank amount_paid fields on its Items, the validations pass, which they shouldn't.
To no avail, I have also tried
validates :amount_paid, :presence => {:if => :claim_exists?}
...
def claim_exists?
!!claim
end
Any other ideas?
What I've done is possibly a bit of a hack, but it seems to work:
class Item
...
validates :amount_paid, :presence => {:if => :claimed?}
...
def claimed?
!!claim || caller.any? { |m| m =~ /claims_controller/ }
end
end
So if the claim exists, or if this is being called from ClaimsController at any point in the stack trace, the validation will run.
I'd still welcome input from anyone who has a better idea.
I believe the problem can be fixed by adding an :inverse_of option to the associations:
class Claim
has_many :items, :inverse_of => :claim
end
class Item
belongs_to :claim, :inverse_of => :items
end
(It's been a while since I came across this, though, so if you're having the same problem as I was, do a bit of experimentation.)

How can I validate a parent model, which uses accepts_nested_attributes_for

I have a polymorphic association (contact_details) in my Company model and I want to validate the parent model. Note: I am using accepts_nested_attributes_for in my parent model.
The basic rule:
the company must have at least one phone (phone is the kind of
contact_detail)
The problem:
accepts_nested_attributes_for call destroy for child objects AFTER
validation of the parent object
so the user are able to delete a phone. Of course, later, when the user will try to edit a company without a phone, he/she will get an error (The company must have at least one phone).
Company (Parent) model:
class Company < ActiveRecord::Base
PHONES_NUMBER_MIN = 1
attr_accessible :name, :contact_details_attributes, ...
has_many :contact_details, :as => :contactable, :dependent => :destroy
validate do |company|
check_phones_number
end
accepts_nested_attributes_for :contact_details, :allow_destroy => true, :reject_if => :all_blank
private
def phones_number_valid?
kind = ContactDetail::Kind.phone
phones = contact_details.select { |cd| cd.kind_id == kind.id }
phones.size >= PHONES_NUMBER_MIN
end
def check_phones_number
unless phones_number_valid?
errors.add(:base, :phones_too_short, :count => PHONES_NUMBER_MIN)
end
end
...
end
ContactDetail (Child) model:
class ContactDetail < ActiveRecord::Base
attr_accessible :kind_id, :kind_value_source
belongs_to :contactable, :polymorphic => true
belongs_to :kind
validates :kind_value_source, :presence => true, :length => {:maximum => 255}
...
end
Note: I simplified the original version, so objective was clear to you. Here is the gist with the code.
By using the reject_if option I am able to forbid the deletion of all the phones. It is probably the best option by now. But I want to hear your opinions.
I also found this question and tried to apply the answer, but it didn't helped a lot. The same problem, as I described above. I've drawn a flowchart so you can see the trace, as I see it.
How can I validate the parent model in such a case?
I would be grateful for any help.
From the question you referenced, you can get rid of the reject_if and modify the line in phones_number_valid?:
phones = contact_details.select { |cd| cd.kind_id == kind.id && !cd.marked_for_destruction? }

Rails Conditional Validation

So I have two models here:
class Screen < ActiveRecord::Base
belongs_to :user
validates :screen_size, :numericality =>{:less_than_or_equal_to =>100,:greater_than_or_equal_to => 0},:if => "user.access==1"
class User < ActiveRecord::Base
has_many :screens
attr_accessible :access
But this code doesn't work, cause no matter what value the user.access is, it will still execute the validation. What am I doing wrong here?
Thank you
change:
:if => "user.access==1"
with:
:if => lambda { |screen| screen.user.try(:access) ==1 }
Because:
you need to pass a function to evaluate condition on the fly
if your screen doesn't have any user, a mere screen.user.access would throw an exception
You passed in a string to the :if executable proc/function parameter. When this is a string, it tries to find a function with that name. What you actually want is an anonymous function here using a lambda.
class Screen < ActiveRecord::Base
belongs_to :user
validates :screen_size, :numericality => {:less_than_or_equal_to =>100, :greater_than_or_equal_to => 0}, :if => lambda {|s| s.user.access == 1 }
end
class User < ActiveRecord::Base
has_many :screens
attr_accessible :access
end

Polymorphic model question in rails - does it maintain the relationship for you?

Let's say you have a SiteUpdate and a Comment model, and you want to make Comment polymorphic. You make comment hold a "commentable_id" and "commentable_type"...
Here's our comment model:
class Comment < ActiveRecord::Base
belongs_to :commentable, :polymorphic => true
validates_presence_of :content
validates_presence_of :commentable
end
Here is our SiteUpdate:
class SiteUpdate < ActiveRecord::Base
belongs_to :author, :class_name => 'User', :foreign_key => 'author_id'
has_many :comments, :as => :commentable
validates_presence_of :subject
validates_length_of :subject, :maximum => 80
validates_presence_of :intro
validates_length_of :intro, :maximum => 200
validates_presence_of :text
validates_presence_of :author
scope :sorted, order("site_updates.created_at desc")
end
Does Rails link the commentable to the site_update instance, or do I have to do that manually?
#site_update.comments << Factory.build(:comment, :commentable_id => nil)
#site_update.save
This fails -> it complains that the comment.commentable_id should not be blank (I set this validation in the Comment model).
So do I do this manually, or do I set this up incorrectly?
Or do I just not validate it at all?
I'm making an assumption that your #site_update object is a new object. If so...
There's a somewhat annoying thing about rails associations. You can't really add them before the record is saved.
What's happening is, you have a new site update object without an ID. You build a new comment object for that site update, so it sets the commentable_type to "SiteUpdate", however, there's no ID yet, so it sets the commentable_id to nil. You save, and it bubbles out to save associated objects, but it doesn't set the comment commentable_id to the SiteUpdate ID, because it doesn't exist.
So if you change it around to be :
#site_update.save
#site_update.comments << Factory.build(:comment, :commentable_id => nil)
#site_update.comments.map { |c| c.save }
it should work.
If it's not a new record...it should work as is.

Resources