Validate relationship using associated table - ruby-on-rails

I have the following models:
class Property < ActiveRecord::Base
belongs_to :property_type
has_many :variant_properties
has_many :variants, through: :variant_properties
end
class PropertyType < ActiveRecord::Base
has_many :properties
end
class Variant < ActiveRecord::Base
has_many :variant_properties
has_many :properties, through: :variant_properties
end
class VariantProperty < ActiveRecord::Base
belongs_to :property
belongs_to :variant
validates_uniqueness_of :property, scope: :property_type
end
What I am trying to validate is that two Properties for the same Variant should never belong to the same Property_Type.
Is there any way to perform this validation in a Rails way?
EDIT:
Finally solved using a custom validator, as suggested by #qaisar-nadeem. A redundant column would be also ok, but I would consider it an optimization more than a solution.
class Property < ActiveRecord::Base
(...)
validate :property_type_uniqueness
private
def property_type_uniqueness
unless property_type_unique?
msg = 'You cannot have multiple property variants with same property type'
errors.add(:property_id, msg)
end
end
def property_type_unique?
VariantProperty
.where(variant: variant)
.select { |vp| vp.property.property_type == property.property_type }
.empty?
end
end

Validates scope cannot access joined table so you will need to have custom validation.
So there are two options.
Option 1 : Use a custom validator and have SQL check if there is any Property Variant with same Property Type.
Custom Validations guide can be found on http://guides.rubyonrails.org/active_record_validations.html#custom-validators
Option 2 : Add a redundant column property_type_id in variant_properties model and then add validates_uniqueness_of :property, scope: :property_type as you already have done
UPDATE
Here is the custom validator
class VariantProperty < ActiveRecord::Base
belongs_to :property
belongs_to :variant
validate :check_property_type_uniqueness
def check_property_type_uniqueness
errors.add(:property_id, "You cannot have multiple property variants with same property type") if VariantProperty.joins(:property).where(:property_id=>self.property_id,:variant_id=>self.variant_id,:properties=>{:property_type_id=>self.property.property_type_id}).count > 0
end
end

your should add relation with PropertyType to VariantProperty
class VariantProperty < ActiveRecord::Base
belongs_to :property
belongs_to :variant
has_one :property_type, through: :property
validates :property_type, uniqueness: { scope: :variant_id }
end

Related

double belongs_to with validation on rails

I have the following models
class School
has_many :classrooms
has_many :communications
end
class Classroom
belongs_to :school
end
class Communication
belongs_to :school
end
At the moment I can have a school_id in communication, however due to business logic I realized that I might have to index the communication also with a classroom, making the models to be like this:
class School
has_many :classrooms
has_many :communications
end
class Classroom
belongs_to :school
has_many :communications
end
class Communication
belongs_to :school
belongs_to :classroom, optional: true
end
What I want is that a communication should always belong to a school, but if it belongs to a classroom i want to make sure that the classroom also belongs to the same school
How can I write a validation for this case?
For this case I ended up creating a custom validator:
class ClassroomValidator < ActiveModel::Validator
def validate(record)
if record.classroom.school.id != record.school.id
record.errors.add :classroom, message: "Invalid relation between classroom and school"
end
end
end
class Communication < ApplicationRecord
belongs_to :school
belongs_to :classroom, optional: true
validates_with ClassroomValidator, if: -> { self.classroom != nil }
end

An equivalent to first_or_create on a nested attribute in Rails 4

I'm using a has many through pattern with these 3 models
class User < ActiveRecord::Base
has_many :user_topics
has_many :topics, through: :user_topics
end
class Topic < ActiveRecord::Base
validates_presence_of :name
validates :name, :uniqueness => true
end
class UserTopic < ActiveRecord::Base
belongs_to :user
belongs_to :topic
accepts_nested_attributes_for :topic
end
At the moment a new topic model is trying to be created every time a new user_topic is created. I'd like to create a new topic model only if the topic name doesn't already exist, otherwise if it does, use the existing topic_id.
So something like:
class UserTopic < ActiveRecord::Base
belongs_to :user
belongs_to :topic
accepts_nested_attributes_for :topic, :first_or_create(:name)
end
Is it possible to do something similar to this?

Uniqueness field using Scope of association

I want to insure that an items name is unique within an organization. So I've used the "validates_uniqueness_of :name, scope: [:organization]" within Item. This unfortunetely didn't work.
Error (edited):
> 1) Item
> Failure/Error: #item_1 = create(:item, :item_category => #item_cat_1)
>
> NoMethodError:
> undefined method `organization_id' for #<Item:0x00000002565840>
models:
class Item < ActiveRecord::Base
belongs_to :item_category
has_one :organization, through: :item_category
validates_uniqueness_of :name, scope: [:organization]
end
class ItemCategory < ActiveRecord::Base
has_many :items
belongs_to :organization
end
class Organization < ActiveRecord::Base
has_many :item_categories
has_many :items, :through => item_categories
end
In theory, as I did above may I use the item's, item_category association (belongs_to :item_category) for the organization_id?
If the above isn't possible. I guess I could have an organization_id in the item and the item_category. But then how could we validate that a item.organization_id be always equal to an item_category.organization_id (its association)
It is okay to not include the organization_id inside an item?
Yes, not include because the column organization_id will be redundant.
For complex validation, we usually use customized one to validate, here is my example, you may correct it:
class Item < ActiveRecord::Base
belongs_to :item_category
has_one :organization, through: :item_category
# validates_uniqueness_of :name, scope: [:organization]
validate :check_uniqueness_of_name
def check_uniqueness_of_name
if Organization.includes(item_categories: :items).where.not(items: {id: self.id}).where(items: {name: self.name}).count > 0
errors.add(:name, 'name was duplidated')
end
end
end

Rails has_one per scope

I have three models as follows:
class User < ActiveRecord::Base
...
has_many :feeds
...
end
class Project < ActiceRecord::Base
...
has_many :feeds
has_many :users, through: :feeds
...
end
class Feed < ActiveRecord::Base
...
belongs_to :user
belongs_to :project
...
end
I want to model the situation where a user can have a maximum of one feed per project. I know that I can do this check in a custom validator within the Feed class, but is there a way to model this using only ActiveRecord associations?
You can do that on Feed.rb:
validates :user_id, :uniqueness => {:scope => :project_id}

Rails 3 - Custom Validation

I'm somewhat confused by my options for custom validations in Rails 3, and i'm hoping that someone can point me in the direction of a resource that can help with my current issue.
I currently have 3 models, vehicle, trim and model_year. They look as follows:
class Vehicle < ActiveRecord::Base
attr_accessible :make_id, :model_id, :trim_id, :model_year_id
belongs_to :trim
belongs_to :model_year
end
class ModelYear < ActiveRecord::Base
attr_accessible :value
has_many :model_year_trims
has_many :trims, :through => :model_year_trims
end
class Trim < ActiveRecord::Base
attr_accessible :value, :model_id
has_many :vehicles
has_many :model_year_trims
has_many :model_years, :through => :model_year_trims
end
My query is this - when I am creating a vehicle, how can I ensure that the model_year that is selected is valid for the trim (and vice versa)?
you can use custom validation method, as described here:
class Vehicle < ActiveRecord::Base
validate :model_year_valid_for_trim
def model_year_valid_for_trim
if #some validation code for model year and trim
errors.add(:model_years, "some error")
end
end
end
You can use the ActiveModel::Validator class like so:
class VehicleValidator < ActiveModel::Validator
def validate(record)
return true if # custom model_year and trip logic
record.errors[:base] << # error message
end
end
class Vehicle < ActiveRecord::Base
attr_accessible :make_id, :model_id, :trim_id, :model_year_id
belongs_to :trim
belongs_to :model_year
include ActiveModel::Validations
validates_with VehicleValidator
end

Resources