validation error through FactoryGirl - ruby-on-rails

I have problems on validating my data through FactoryGirl.
For team.rb, the custom validations are has_only_one_leader and belongs_to_same_department
class Team < ActiveRecord::Base
attr_accessible :name, :department, :active
has_many :memberships
has_many :users, through: :memberships
accepts_nested_attributes_for :memberships, :users
validates :department, inclusion: [nil, "Architectural", "Interior Design"]
validates_uniqueness_of :name, scope: :department, conditions: -> { where(active: true) }
validate :has_only_one_leader
validate :belongs_to_same_department
def has_only_one_leader
unless self.users.where!(designation: "Team Leader").size == 1
errors.add(:team, "needs to have exactly one leader")
end
end
def belongs_to_same_department
unless self.users.where!.not(department: self.department).size == 0
errors.add(:users, "should belong to the same department")
end
end
I'll also include membership.rb and user.rb (associations only) just for reference.
class Membership < ActiveRecord::Base
belongs_to :team
belongs_to :user
end
class User < ActiveRecord::Base
has_many :memberships
has_many :teams, through: :memberships
end
Here's my factory for team.rb
FactoryGirl.define do
factory :team do
sequence(:name) {|n| "Team #{n}" }
department "Architectural"
before(:create) do |team|
team.users << FactoryGirl.create(:user, designation: "Team Leader",
department: "Architectural")
team.users << FactoryGirl.create_list(:user, 5,
designation: "CAD Operator", department: "Architectural")
end
end
end
It seems that after I do FactoryGirl.create(:team) in the rails console, it seems that I got the error messages from my validations.
Two things I've noticed when I manually build a team, specifically adding members to the team with 1 leader and 5 members belonging to the same department:
team.users.where!(designation: "Team Leader").size returns 6 although there's only one leader, same goes for changing the designation to CAD Operator, returns 6 although there are only five non leaders in the team.
same goes for checking if the members are in the same department, team.users.where!.not(department: team.department).size returns 6, although all of them belong to the same department.
Are there any modifications to make in my model or in my factory?

Here are the things that I've discovered before getting the answer.
team.users.where(designation: "Team Leader") returns a User::ActiveRelation_AssociationRelation object
team.users returns a User::ActiveRecord_Associations_CollectionProxy object
CollectionProxy has no method where since the this method (in my opinion) is a query to the database (with exception if the team is already saved in the database, you can use where, but in this case, it's only for building)
Therefore, I used the select method accompanied with the count, to return the correct values like so. I'll use my example from the question to illustrate the answer.
team.users.select(:designation) {|user| user.designation == "Team Leader"}.count returns 1
team.users.select(:department) {|user| user.department != team.department}.count returns 0

Related

RSpec: Factory definition for associated object

I have the following three models: Product, Warehouse and Inventory
# app/models/product.rb
class Product < ApplicationRecord
has_many :inventories
has_many :warehouses, through: :inventories
end
# app/models/warehouse.rb
class Warehouse < ApplicationRecord
has_many :inventories
has_many :products, through: :inventories
end
# app/models/inventory.rb
class Inventory < ApplicationRecord
belongs_to :product
belongs_to :warehouse
end
I have this factory for Inventory:
FactoryBot.define do
factory :inventory do
product { nil }
warehouse { nil }
item_count { 1 }
low_item_threshold { 1 }
end
end
How can I use this factory for Inventory or what changes are needed in my other factories so that I can have a spec something like this?
RSpec.describe Inventory, type: :model do
it "has a valid factory" do
expect(FactoryBot.build(:inventory)).to be_valid
end
end
What you need is to change the :inventory factory definition, like this
FactoryBot.define do
factory :inventory do
product
warehouse
item_count { 1 }
low_item_threshold { 1 }
end
end
This will "tell" factory bot to instantiate the associated objects (https://github.com/thoughtbot/factory_bot/blob/master/GETTING_STARTED.md#associations)
But for this to work, you need to define warehouse and product factories.
You can use either the create or build methods passing the name if the factory as a symbol:
it "has a valid factory" do
expect(create(:inventory)).to be_valid
end
# OR
it "has a valid factory" do
expect(build(:inventory)).to be_valid
end
create will save the model while build will simply instantiate it. If you are having trouble getting your factories loaded, ensure they are in the right place.
you might change definition of the class Inventory to:
# app/models/inventory.rb
class Inventory < ApplicationRecord
belongs_to :product, optional: true
belongs_to :warehouse, optional: true
end
and you will get successful validation
inventory = FactoryBot.build(:inventory)
inventory.valid? #true
###############################################
Explanation:
to build valid Inventory object with current definition(like in question description) of the model its necessary to initialize associated objects also. So every time validation checks if warehouse and product attributes present.
But its possible to avoid such behaviour with associations attribute optional: true.
# app/models/inventory.rb
class Inventory < ApplicationRecord
belongs_to :product
belongs_to :warehouse
end
FactoryBot.define do
factory :inventory do
product { nil }
warehouse { nil }
item_count { 1 }
low_item_threshold { 1 }
end
end
inventory = FactoryBot.build(:inventory)
inventory.valid? #false
inventory.errors.full_messages # ["Product must exist", "Warehouse must exist"]
:required
When set to true, the association will also have its
presence validated. This will validate the association itself, not the
id. You can use :inverse_of to avoid an extra query during validation.
NOTE: required is set to true by default and is deprecated. If you
don’t want to have association presence validated, use optional: true.
https://apidock.com/rails/ActiveRecord/Associations/ClassMethods/belongs_to

Rails FactoryGirl for model that belongs_to 2 other models

I have 3 following models like this:
# model/timeline.rb
class Timeline
belongs_to :series
belongs_to :creator
end
def series_belongs_to_creator
if creator_id
creator = Creator.find_by id: creator_id
related_series = creator.series.find_by id: series_id
errors.add(:series_id, :not_found_series) unless related_series
end
end
# model/creator.rb
class Creator
has_many :timelines
has_many :series, through: :contents
end
# model/series.rb
class Series
has_many :timelines
has_many :creators, through: :contents
end
This is not many to many relation, timelines table has two fields creator_id and series_id beside another fields. creator_id and series_id must be entered when create Timeline and i have a method series_belongs_to_creator to validates series_id must belong to creator_id to create successful.
So how should I write factory for timeline model if using FactoryGirl. Im so confused about Unit test in Rails.
If you're using Rails 5, you have to keep in mind that belongs_to is no longer optional by default: https://blog.bigbinary.com/2016/02/15/rails-5-makes-belong-to-association-required-by-default.html
So creator_id will always need to be present unless you specify the relation is optional.
For the factories, you're going to end up with something like this (FactoryGirl was recently renamed to FactoryBot):
http://www.rubydoc.info/gems/factory_bot/file/GETTING_STARTED.md#Associations
FactoryBot.define do
factory :timeline do
creator
series
end
end
FactoryBot.define do
factory :creator do
...
end
end
FactoryBot.define do
factory :series do
...
end
end

Validate that belongs_to is in allowed values

I have this structure
class Organization
has_many :clients
end
class Client
belongs_to :organization
has_many :contacts
end
class Contact
belongs_to :client
belongs_to :organization
end
How can I make sure that when client is assigned to a contact he is a child of a specific organization and not allow a client from another organization to be assigned ?
While searching I did find a scope parameter can be added but that seems not to be evaluated when client_id is assigned.
Update
Here is an example from Rails Docs :
validates :name, uniqueness: { scope: :year,message: "should happen once per year" }
I'm looking for something like "if client is set it must be in Organization.clients"
class Contact
#...
validate :client_organization
def client_organization
unless client.nil?
unless organization == client.organization
errors.add(:organization, "can't be different for client.")
end
end
end
end

Factory Girl and nested attributes validation error

I have a model Company which accepts nested attributes for Recruiters model. I need to have validation in my Company model that at least one recruiter was created during Company creation.
class Company < ActiveRecord::Base
has_many :recruiters, dependent: :destroy, inverse_of: :company
accepts_nested_attributes_for :recruiters, reject_if: ->(attributes) { attributes[:name].blank? || attributes[:email].blank? }, allow_destroy: true
validate { check_recruiters_number } # this validates recruiters number
private
def recruiters_count_valid?
recruiters.reject(&:marked_for_destruction?).count >= RECRUITERS_COUNT_MIN
end
def check_recruiters_number
unless recruiters_count_valid?
errors.add(:base, :recruiters_too_short, count: RECRUITERS_COUNT_MIN)
end
end
end
Validation works as expected but after adding this validation I have a problem with FactoryGirl. My factory for company looks like this:
FactoryGirl.define do
factory :company do
association :industry
association :user
available_disclosures 15
city 'Berlin'
country 'Germany'
ignore do
recruiters_count 2
end
after(:build) do |company, evaluator|
FactoryGirl.create_list(:recruiter, evaluator.recruiters_count, company: company)
end
before(:create) do |company, evaluator|
FactoryGirl.create_list(:recruiter, evaluator.recruiters_count, company: company)
end
end
end
In tests, when I do
company = create(:company)
I get validation error:
ActiveRecord::RecordInvalid:
Validation failed: Company has to have at least one recruiter
When I first build company and then save it, the test passes:
company = build(:company)
company = save
Of course, I don't want to change all my tests this way to make them work. How can I set up my factory to create associated model during creation Company model?
Your validate { check_recruiters_number } is unreasonable. Remove it.
Why? You need to have a valid company id to save recruiters, but your validator prevent company to be valid because it has no recruiters. This is in contradiction.
This is an old question but I have similar problem and I solved it with the following code (rewritten to match your case):
FactoryGirl.define do
factory :company do
association :industry
association :user
available_disclosures 15
city 'Berlin'
country 'Germany'
ignore do
recruiters_count 2
end
after(:build) do |company, evaluator|
company.recruiters = FactoryGirl.build_list(:recruiter, evaluator.recruiters_count, company: company)
end
end
end

has_one through and polymorphic associations over multi-table inheritance

In the project i'm currently developing under rails 4.0.0beta1, i had the need for a user based authentication in which each user could be linked to an entity. I'm kinda new to rails and had some troubles doing so.
The model is as following:
class User < ActiveRecord::Base
end
class Agency < ActiveRecord::Base
end
class Client < ActiveRecord::Base
belongs_to :agency
end
What i need is for a user to be able to link to either an agency or a client but not both (those two are what i'll be calling entities). It can have no link at all and at most one link.
First thing i looked for was how to do Mutli-Table inheritance (MTI) in rails. But some things blocked me:
it was not available out of the box
MTI looked kinda hard to implement for a newbie such as me
the gems implementing the solutions seemed old and either too complexe or not complete
the gems would have probably broke under rails4 as they had not been updated for a while
So i looked for another solution and i found polymorphic associations.
I've be on this since yesterday and took some time to make it work even with the help of Rails polymorphic has_many :through and ActiveRecord, has_many :through, and Polymorphic Associations
I managed to make the examples from the question above work but it took a while and i finally have two problems:
How to transform the relations in user into a has_one association and be able to access "blindly" the linked entity ?
How to set a constraint so that no user can have more than one entity ?
Is there a better way to do what i want ?
Here's a fully working example:
The migration file:
class CreateUserEntities < ActiveRecord::Migration
def change
create_table :user_entities do |t|
t.integer :user_id
t.references :entity, polymorphic: true
t.timestamps
end
add_index :user_entities, [:user_id, :entity_id, :entity_type]
end
end
The models:
class User < ActiveRecord::Base
has_one :user_entity
has_one :client, through: :user_entity, source: :entity, source_type: 'Client'
has_one :agency, through: :user_entity, source: :entity, source_type: 'Agency'
def entity
self.user_entity.try(:entity)
end
def entity=(newEntity)
self.build_user_entity(entity: newEntity)
end
end
class UserEntity < ActiveRecord::Base
belongs_to :user
belongs_to :entity, polymorphic: true
validates_uniqueness_of :user
end
class Client < ActiveRecord::Base
has_many :user_entities, as: :entity
has_many :users, through: :user_entities
end
class Agency < ActiveRecord::Base
has_many :user_entities, as: :entity
has_many :users, through: :user_entities
end
As you can see i added a getter and a setter that i named "entity". That's because has_one :entity, through: :user_entity raises the following error:
ActiveRecord::HasManyThroughAssociationPolymorphicSourceError: Cannot have a has_many :through association 'User#entity' on the polymorphic object 'Entity#entity' without 'source_type'. Try adding 'source_type: "Entity"' to 'has_many :through' definition.
Finally, here are the tests i set up. I give them so that everyone understands know ho you can set and access data between those objects. i won't be detailing my FactoryGirl models but they're pretty obvious
require 'test_helper'
class UserEntityTest < ActiveSupport::TestCase
test "access entity from user" do
usr = FactoryGirl.create(:user_with_client)
assert_instance_of client, usr.user_entity.entity
assert_instance_of client, usr.entity
assert_instance_of client, usr.client
end
test "only right entity is set" do
usr = FactoryGirl.create(:user_with_client)
assert_instance_of client, usr.client
assert_nil usr.agency
end
test "add entity to user using the blind rails method" do
usr = FactoryGirl.create(:user)
client = FactoryGirl.create(:client)
usr.build_user_entity(entity: client)
usr.save!
result = UserEntity.where(user_id: usr.id)
assert_equal 1, result.size
assert_equal client.id, result.first.entity_id
end
test "add entity to user using setter" do
usr = FactoryGirl.create(:user)
client = FactoryGirl.create(:client)
usr.client = client
usr.save!
result = UserEntity.where(user_id: usr.id)
assert_equal 1, result.size
assert_equal client.id, result.first.entity_id
end
test "add entity to user using blind setter" do
usr = FactoryGirl.create(:user)
client = FactoryGirl.create(:client)
usr.entity = client
usr.save!
result = UserEntity.where(user_id: usr.id)
assert_equal 1, result.size
assert_equal client.id, result.first.entity_id
end
test "add user to entity" do
usr = FactoryGirl.create(:user)
client = FactoryGirl.create(:client)
client.users << usr
result = UserEntity.where(entity_id: client.id, entity_type: 'client')
assert_equal 1, result.size
assert_equal usr.id, result.first.user_id
end
test "only one entity by user" do
usr = FactoryGirl.create(:user)
client = FactoryGirl.create(:client)
agency = FactoryGirl.create(:agency)
usr.agency = agency
usr.client = client
usr.save!
result = UserEntity.where(user_id: usr.id)
assert_equal 1, result.size
assert_equal client.id, result.first.entity_id
end
test "user uniqueness" do
usr = FactoryGirl.create(:user)
client = FactoryGirl.create(:client)
agency = FactoryGirl.create(:agency)
UserEntity.create!(user: usr, entity: client)
assert_raise(ActiveRecord::RecordInvalid) {
UserEntity.create!(user: usr, entity: agency)
}
end
end
I Hope this can be of some help to someone. I decided to put the whole solution here cause it seems to me like a good one compared to MTI and i think it shouldn't take someone that much time to set something like that up.
The above answer was giving me some trouble. Use a column name instead of a model name when validating uniqueness. Change validates_uniqueness_of :user to validates_uniqueness_of :user_id.

Resources