Running custom validations with gem specific methods - ruby-on-rails

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

Related

How to write a test for validates_uniqueness_of with scope and presence: true RSpec Rails

Trying to write a test to verify uniqueness of :user_id with scope :board_id.
Model:
class Membership < ApplicationRecord
belongs_to :board
belongs_to :user
validates_uniqueness_of :user_id, scope: :board_id, presence: true
end
I try to test it like:
it { should validate_uniqueness_of(:user_id) }
it expect.to have_many(:membership).through(:board_id)
Thanks for help.
If using shoulda-matchers it can be done in two expectations (even though it's a single model line):
it { should validate_presence_of(:user_id) }
it { should validate_uniqueness_of(:user_id).scoped_to(:board_id) }

Prevent from raising ActiveRecord::RecordInvalid or adding twice on has_many association

I want to change has_many association behaviour
considering this basic data model
class Skill < ActiveRecord::Base
has_many :users, through: :skills_users
has_many :skills_users
end
class User < ActiveRecord::Base
has_many :skills, through: :skills_users, validate: true
has_many :skills_users
end
class SkillsUser < ActiveRecord::Base
belongs_to :user
belongs_to :skill
validates :user, :skill, presence: true
end
For adding a new skill we can easily do that :
john = User.create(name: 'John Doe')
tidy = Skill.create(name: 'Tidy')
john.skills << tidy
but if you do this twice we obtain a duplicate skill for this user
An possibility to prevent that is to check before adding
john.skills << tidy unless john.skills.include?(tidy)
But this is quite mean...
We can as well change ActiveRecord::Associations::CollectionProxy#<< behaviour like
module InvalidModelIgnoredSilently
def <<(*records)
super(records.to_a.keep_if { |r| !!include?(r) })
end
end
ActiveRecord::Associations::CollectionProxy.send :prepend, InvalidModelIgnoredSilently
to force CollectionProxy to ignore transparently adding duplicate records.
But I'm not happy with that.
We can add a validation on extra validation on SkillsUser
class SkillsUser < ActiveRecord::Base
belongs_to :user
belongs_to :skill
validates :user, :skill, presence: true
validates :user, uniqueness: { scope: :skill }
end
but in this case adding twice will raise up ActiveRecord::RecordInvalid and again we have to check before adding
or make a uglier hack on CollectionProxy
module InvalidModelIgnoredSilently
def <<(*records)
super(valid_records(records))
end
private
def valid_records(records)
records.with_object([]).each do |record, _valid_records|
begin
proxy_association.dup.concat(record)
_valid_records << record
rescue ActiveRecord::RecordInvalid
end
end
end
end
ActiveRecord::Associations::CollectionProxy.send :prepend, InvalidModelIgnoredSilently
But I'm still not happy with that.
To me the ideal and maybe missing methods on CollectionProxy are :
john.skills.push(tidy)
=> false
and
john.skills.push!(tidy)
=> ActiveRecord::RecordInvalid
Any idea how I can do that nicely?
-- EDIT --
A way I found to avoid throwing Exception is throwing an Exception!
class User < ActiveRecord::Base
has_many :skills, through: :skills_users, before_add: :check_presence
has_many :skills_users
private
def check_presence(skill)
raise ActiveRecord::Rollback if skills.include?(skill)
end
end
Isn't based on validations, neither a generic solution, but can help...
Perhaps i'm not understanding the problem but here is what I'd do:
Add a constraint on the DB level to make sure the data is clean, no matter how things are implemented
Make sure that skill is not added multiple times (on the client)
Can you show me the migration that created your SkillsUser table.
the better if you show me the indexes of SkillsUser table that you have.
i usually use has_and_belongs_to_many instead of has_many - through.
try to add this migration
$ rails g migration add_id_to_skills_users id:primary_key
# change the has_many - through TO has_and_belongs_to_many
no need for validations if you have double index "skills_users".
hope it helps you.

ActiveRecord models interdependencies and correlations

I have a bunch of AR models that depend one to each other and I'm not able to figure out how to make everything work together...
First of all a Pet and a Dog model:
class Pet < ActiveRecord::Base
belongs_to :animal, polymorphic: true, dependent: :destroy
belongs_to :facility
has_many :pet_pictures, dependent: :destroy
validates :name, :birth, :facility, :pet_pictures, presence: true
end
class Dog < ActiveRecord::Base
has_one :pet, as: :animal
has_many :mixtures, as: :blended, dependent: :destroy
validates :mixtures, presence: true
end
Then both PetPictures and Mixtures:
class PetPicture < ActiveRecord::Base
belongs_to :pet
validates :photo_file, presence: true
end
class Mixture < ActiveRecord::Base
belongs_to :blended, polymorphic: true
validates :breed, presence: true
validates :breed, uniqueness: { scope: [:blended_id, :blended_type] }
end
The problem is that this is too complex and I'm having huge problem coordinating all the dependencies, I also had to remove some validations (a pic MUST have a pet related to it, but it's so difficult that I ended up removing it). It appears to me that there's no correct order of creation to end up with a valid object.
For example, take this spec:
RSpec.describe PetPicture, type: :model do
let(:dog) do
FactoryGirl.build(:marley,
mixtures: [mixture_yorkshire, mixture_dachsund],
pet: FactoryGirl.build(
:dog,
pet_pictures: [FactoryGirl.build(:marleys_pic),
FactoryGirl.build(:second_marleys_pic)],
facility: lida))
end
context 'when creating' do
it 'should be valid' do
# dog.save && dog.pet.save
dog.pet.pictures.each do |picture|
expect(picture).to be_valid
end
end
end
end
This spec passes only after saving by hand (if I FactoryGirl.create nothing is created due to validation errors related to the order of creation) BUT, and I seriously see no reason for this behavior, picture.pet_id is NULL.
Can you help me debug this? Any suggestion or link on how to improve/refactor/cleanup this mess is very welcome - just keep in mind that I adopted this for a reason, so a Pet has many pictures and can be a Dog, a Cat or whatever, which have all a different set of attributes very specific to their class. Also, a Dog can be 50% dachsund and 50% yorkshire, to explain the mixtures relation.
Thanks in advance.
I think if you are trying to check for every validations, you should do it one at a time, don't try create a bunch of them. For example:
RSpec.describe PetPicture, type: :model do
let(:pet) { FactoryGirl.create(:pet) }
let(:picture) do
FactoryGirl.build(:marleys_pic, pet: pet)
end
context 'when creating' do
it 'should be valid' do
expect(picture).to be_valid
end
end
end
And if you want something really clean and clear, try shoulda-matchers. Every associations and validations could be tested by only 1 line of code.

Accessing the model instance in validation messages

I have Events to which each User can be invited once:
class Invitation < ActiveRecord::Base
belongs_to :event
belongs_to :user
validates_uniqueness_of :user_id,
scope: :event_id,
message: "has already been invited"
end
I would like to include the users' name in the error message, something to the effect of
validates_uniqueness_of :user_id,
scope: :event_id,
message: ->(error, values) { "#{user.firstname} has already been invited" }
However I cannot access the model instance from within the lambda. Is there a workaround to this?
You can do
validates_uniqueness_of :user,
scope: :event,
message: ->(error, values) { "#{values[:user].firstname} has already been invited" }
as per this Rails issue, however there was some regression in 3.2, so check if this works with your version.

Validate whether foreign key exists if not nil

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)

Resources