Validate whether foreign key exists if not nil - ruby-on-rails

I have a many-to-one relationship defined in Rails 4:
class Event < ActiveRecord::Base
belongs_to :user
end
How do I check, that the :user key exists if it is set?
The following correctly checks, that the key exists but does not allow for `nil? values:
validates :user, presence: true
The following allows any value, even non-existing IDs:
validates :user, presence: true, allow_nil: true
How do you do such kind of validation.

You can use the before_save callback to check that the user is valid if it is supplied:
class Event < ActiveRecord::Base
belongs_to :user
before_save: :validate_user unless: Proc.new { |event| event.user.nil? }
private
def validate_user
if User.where(id: user_id).empty?
errors[:user] << "must be valid"
end
end
end

You must keep the presence validation in Event and add in User model:
has_many :events, inverse_of: :user

Just faced this problem and here are the 2 solutions
1.
validates :user, presence: true, if: 'user_id.present?'
2.
. https://github.com/perfectline/validates_existence gem
can be used like this
validates : user, existence: { allow_nil: true, both: false }
both option is for the error messages: false will show 1 error(association only), true will show 2 errors (association and foreign key)

Related

Is there an easy way to remove blank objects from a has-many association?

I got the following models:
class Course < ApplicationRecord
has_many :external_ids, as: :identifiable, dependent: :destroy, validate: true
accepts_nested_attributes_for :external_ids
end
and this one:
class ExternalId < ApplicationRecord
belongs_to :identifiable, polymorphic: true
validates :name, presence: true, uniqueness: true
validates :identifiable, presence: true, on: :create
end
Because of a nested form in the course form-view it's possible that a blank object of an external id will be saved. I'd like to remove these blank objects. With "blank object" I mean an ExternalId which is a new record and has a blank name. Currently I do that as follows:
#course.attributes = course_params
## SNIP - workaround to fix validations
external_ids = []
#course.external_ids.each do |ext_id|
external_ids << ext_id unless(ext_id.new_record? && ext_id.name.blank?)
end
#course.external_ids = external_ids
## SNAP - workaround to fix validations
#course.save
But this is a lot of code for a very simple task. Is there any function for that? destroy_if doesn't exists for an association, only for arrays.
Thanks you!
You can use accepts_nested_attributes_for with such key
accepts_nested_attributes_for :external_ids, reject_if: proc { |attributes| attributes['name'].blank? }
From docs:
You may also set a :reject_if proc to silently ignore any new record hashes if they fail to pass your criteria

Make query with association and search by custom model method rails

I have this problem, i tried to get all Beauty Salons when the service has a promotion, but service has a custom method and return true or false, this is the structure of my code
class BeautySalon < ActiveRecord::Base
has_many :services
end
class Service < ActiveRecord::Base
belongs_to :beauty_salon
has_many :service_promotions
has_many :promotions, through: :service_promotions
def has_promotion?
## consult another tables and return true or false if found a promotion
end
end
iam tried to make the query like this
BeautySalon.all.includes(:services).select('services.*').select{|service| service.has_promotion?}
but rails return this error
NoMethodError (undefined method `has_promotion?' for #BeautySalon:0x0055a1119d1f40)
Any advice for this?
UPDATE
the method has_promotion do this
def has_promotion?
if promotions.exists?
if get_promotions(Date.today).exists?
return true
else
return false
end
end
return false
end
def get_promotions(date)
if promotions.exists?
promotions.where('start_date <= ? and end_date >= ?',date,date)
end
end
and another tables are there
class Promotion < ActiveRecord::Base
validates :discount, presence: true
validates :start_date, presence: true
validates :end_date, presence: true
has_many :service_promotions
has_many :services, through: :service_promotions
end
class ServicePromotion < ActiveRecord::Base
validates :service_id, presence:true
validates :promotion_id, presence:true
belongs_to :service
belongs_to :promotion
end
thanks for all the advises
BeautySalon.joins(:services).select { |beauty_salon| beauty_salon.services.any?(&:has_promotion?) }
This is an option. You are now calling has_promotion? on the wrong object (e.g. beauty salon instead of service) and the error is raised because the instance method is defined in Service.rb.
It would be better in my opinion to add a database column has_promotion (boolean) to your services table.
BeautySalon.joins(:services).where(has_promotion: true)
If has_promotion? simply retrieves data from another associated model (other_association, with db boolean column promotion) you can also do something like this:
BeautySalon.joins(services: other_association).where(other_association: { promotion: true })
Update:
BeautySalon.joins(services: :promotions).where("promotions.start_date <= :date AND promotions.end_date >= :date", date: Date.current )
This will return all beauty salons with running service promotions (today).

Validation in Child Model in Nested Attribute - Rails

This are my files:
word.rb
class Word < ActiveRecord::Base
has_many :word_answers, dependent: :destroy
accepts_nested_attributes_for :word_answers, allow_destroy: true,
reject_if: lambda {|attribute| attribute[:word_id].blank?}
end
word_answer.rb
class WordAnswer < ActiveRecord::Base
belongs_to :word
validates :content, uniqueness: true, presence: true
end
Is there any method that I can valiidate validates :content, uniqueness: true, presence: true of word_answer.rb in word.rb?
Rails 4.
I tried validates_associated :word_answers but it doesn't work.
I want to achieve is
(1) If I submit Word, it will give an error if there is no WordAnswer.
(2) If I submit Word, it will give an error if there is a BLANK WordAnswer.
(3) If a validation is WordAnswer is wrong, It will give an error if I submit Word.
I don't think if we can add validates :content, uniqueness: true, presence: true to Word method but you can achieve validation for WordAnswer model in Word Model by adding a before_save & before_update filters.
I guess the validations in WordAnswer model will work fine as you want them to when you use nested attributes in the form and it will validate fine.
In other case kindly explain what you actually want to achieve ?
Try the following
validate :detail
def detail
word_answers.each do |word_answer|
if word_answer.content.blank?
errors.add(:"word_answer.content", "must not be blank")
word_answer.errors.add(:content, "must not be blank")
end
end
end
It should work if your models are associated correctly.

Running custom validations with gem specific methods

I setup a custom validation that checks if a user has voted for an album before submitting a review. The validation works fine on the client side but when it comes to running my Rspec tests I seem to run into some problems.
The validation makes use of the Acts As Votable gem's voted_for? method. Unfortunately this is where things go bad. For my non-custom validations (that do work regularly btw) I get an error like this:
3) Review validations should ensure body has a length of at least 40
Failure/Error: it { should validate_length_of(:body).is_at_least(40) }
NoMethodError:
undefined method `voted_for' for nil:NilClass
What do I need to do in order for this method to be recognized?
Review Model
class Review < ActiveRecord::Base
belongs_to :album
belongs_to :owner, class_name: "User", foreign_key: :user_id
validates :body, presence: true, length: { minimum: 40 }
def album_vote
if !owner.voted_for?(album)
errors.add(:review, "requires an album vote before submitting")
end
end
end
Review Factory
FactoryGirl.define do
factory :review do
body { Faker::Lorem.paragraph(2) }
album
association :owner, factory: :user
end
end
You need to ensure that there actually is an owner. The error is simply because you are calling on nil and does not really have anything to do with ActsAsVotable.
class Review < ActiveRecord::Base
belongs_to :album
belongs_to :owner, class_name: "User", foreign_key: :user_id
validates :body, presence: true, length: { minimum: 40 }
validates :owner, presence: true
def album_vote
# consider raising an error if there is no owner.
if owner && !owner.voted_for(album)
errors.add(:review, "requires an album vote before submitting")
end
end
end
And then change your factory definition to create the owner:
FactoryGirl.define do
factory :user, aliases: [:owner] do
# ...
end
end
FactoryGirl.define do
factory :review do
body { Faker::Lorem.paragraph(2) }
album
owner
end
end

How can you check if all of a model's attributes have a value set?

I currently have the following method:
def complete?
attributes.delete_if{|k,v| k == 'id' or k == 'user_id'}.values.each do |value|
return false if value.blank?
end
true
end
Is there a better way of doing this? I just want to know if, with my instance, all the attributes have been set apart from id and user_id. There must be a better way of doing this.
I'm on rails 3 and ruby 1.9.3 (just so people don't give answers that will work with newer versions)
There is the validates method to do that:
class User < ActiveRecord::Base
validates :full_name, :username, :email, :address, presence: true
If you want to validate the presence of every attributes except few ones:
class User < ActiveRecord::Base
validates *(self.column_names - ['id', 'created_at', 'updated_at']), presence: true
Above exemple extended:
class User < ActiveRecord::Base
validates *self.validable_columns, presence: true
def self.validable_columns
excluded_columns = ['id', 'created_at', 'updated_at'] # columns to be excluded in the `validates`
self.column_names - excluded_columns
end

Resources