How to prevent creating the instance of the class with specific IDs - ruby-on-rails

I have 2 Models: Post, User. The User cannot like his post, so how can i prevent creating the instance of the Model Like (user_id: creator, post_id:created by the "creator") ?

You can validate that in your Like model:
class Like < ActiveRecord::Base
validates_presence_of :user_id, :post_id
validate :voter_not_author
private
def voter_not_author
if self.user_id == self.post.try(:user_id)
self.errors[:base] << "Author can't be the voter"
end
end
end

Another implementation I found...
#app/models/like.rb
class Like < ActiveRecord::Base
validates :user_id, exclusion: {in: ->(u) { [Post.find(u.post_id).user_id] }} #-> user_id cannot equal post.user_id
end
If you wanted to get rid of the db query, you'd have to associate the models and use inverse_of:
#app/models/user.rb
class User < ActiveRecord::Base
has_many :likes
end
#app/models/like.rb
class Like < ActiveRecord::Base
belongs_to :user
belongs_to :post, inverse_of: :likes
validates :user_id, exclusion: {in: ->(u) { u.post.user_id }}
end
#app/models/post.rb
class Post < ActiveRecord::Base
has_many :likes, inverse_of: :post
end

Related

Creating a record using has_many :through?

I have the following models:
class Department < ApplicationRecord
has_many :department_job_titles
has_many :job_titles, through: :department_job_titles
end
class JobTitle < ApplicationRecord
has_and_belongs_to_many :departments
end
class DepartmentJobTitle < ApplicationRecord
belongs_to :department
belongs_to :job_title
validates :department_id, uniqueness: { scope: :job_title_id }
end
This is erring w PG::UndefinedColumn: ERROR: column department_job_titles.title does not exist
LINE 1: ... "department_job_titles"."department_id" = $1 AND "departmen...
Department.first.department_job_titles.find_or_create_by(title: title)
DepartmentJobTitle has the following fields: id, department_id, job_title_id
What am I doing wrong here?
Try this:
job_title = JobTitle.find_or_create_by(title: title)
Department.first.job_titles << job_title unless job_title.in? Department.first.job_titles
Or that second line could be:
Department.first.job_titles = (Department.first.job_titles + [job_title]).uniq
Also:
class JobTitle < ApplicationRecord
has_many :department_job_titles
has_many :departments, through: :department_job_titles
end
... and ...
class DepartmentJobTitle < ApplicationRecord
belongs_to :department
belongs_to :job_title
validates :department, presence: true, uniqueness: { scope: :job_title }
validates :job_title, presence: true
end
... and think about what behaviour you want if someone destroys a JobTitle or Department -- either you want the DepartmentJobTitle destroyed also, or you want the destroy to be prevented, I expect.

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?

Validate relationship using associated table

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

AcriveRecord relationship validation between two or more belongs_to associations

I'm wondering if there is a cleaner way to validate multiple relationships in rails. I don't mean validating an association, rather ensuring relationship integrity between two or more belongs_to associations. Here is some code as an example:
class User < ActiveRecord::Base
has_many :products, inverse_of: :user
has_many :purchases, inverse_of: :user
end
class Purchase < ActiveRecord::Base
belongs_to :user, inverse_of: :purchases
has_many :products, inverse_of: :purchase
end
class Product < ActiveRecord::Base
belongs_to :user, inverse_of: :products
belongs_to :purchase, inverse_of: :products
validates :user, :purchase, presence: true
validate :purchase_user
private
def purchase_user
errors.add(:purchase, 'purchase user does not match user') if user != purchase.user
end
end
The purchase_user validation method here checks that the user is the same as purchase.user and adds an error if they are not.
I could change it to use :inclusion like so:
validates :purchase, inclusion: { in: proc { |record| record.user.purchases } }
But that seems even more inefficient, any suggestions?

Rails 3 - validate through few models

I have 4 models: SchoolClass, Timetable, Lesson, Reporting:
# class_code :string(255)
class SchoolClass < ActiveRecord::Base
has_many :timetables
end
class Timetable < ActiveRecord::Base
belongs_to :school_class
has_many :lessons
end
class Lesson < ActiveRecord::Base
belongs_to :timetable
has_one :reporting
end
# report_type :string(255)
class Reporting < ActiveRecord::Base
belongs_to :lesson
validates :report_type,
:presence => true,
:inclusion => { :in => %w(homework checkpoint)}
end
How can i validate that each SchoolClass can have only 1 Reporting with type "checkpoint"?
This gets super complicated because of the nesting associations.
I would start by using a custom validation method.
in the SchoolClass model:
validate :only_one_reporting_checkpoint
and then the method:
def only_one_reporting_checkpoint
timetables = self.timetables
reporting_checkpoint = nil
timetables.each do |t|
t.lessons.each do |l|
reporting_checkpoint = true if l.reporting.report_type == "checkpoint"
end
end
if reporting_checkpoint == true
errors.add(:reporting, "exception raised!")
end
end
There, I think that does it. If I understand your problem correctly.

Resources