Rails Validates uniqueness of a column based on a relation - ruby-on-rails

I have a product table which belongs to market & has many variants, every variants has SKU column, i want to validates uniqueness of sku but for the same market. i've tried
validates :sku, uniqueness: { scope: :market_id }
but since variant doesn't have market_id column it doesn't work!!, how can i overcome this issue ?!

Although you can access variants from market,
if you set you relations with has many through, but
my opinion it's better to save market_id inside variants table and set index based two column
class Market < ApplicationRecord
has_many :products
has_many :variants , :through => :products
end
class Product < ApplicationRecord
belongs_to :market
has_many :variants
end
class Variant < ApplicationRecord
belongs_to :Product
end
You can generate migration to add market_id in variants table
with index based two column
here is the reference
and here is the code to generate index based two columnn(market_id and sku)
add_index(:variants, [:market_id, :sku], unique: true)

A variant belongs to a product in a certain market, therefore, in order to avoid an extra join (which is a costly operation) when retrieving a variant of a product in a market, and in order to implement this validation, the schema should be designed in a way that Variant should belong to both Market and Product.
If a Variant belongs both to Product and Market, the validation would be as follows
validates_uniqueness_of :sku, scope: [:market_id, :product_id]

Related

Find or create nested attributes in a polymorphic join table, in one transaction

Background
I want to save skills on different types of objects. Typically, a user can have a project or experience, and tag it with different skills etc. Ruby , python or Excel. These skills are global and used across the various possible objects that are "skillable".
The problem consists of three models:
Project - contains info about the project
Skill - contains the name of the skill
SkillObject - join table between skill and projects
(polymorphic)
Please look at this ER-diagram for a more detailed picture.
The problem
When I create or update a project, I also want the skills to be added in the same transaction, just by sending the names of the skills from the front-end.
I want a kind of find_or_create_by to avoid duplicate skills. At the same time, validations must ensure that two identical skills cannot be put on the same project (skill_object.rb) and that the skill name cannot be zero (skill.rb)
The wanted behavior is if the validation of skills is not successful, then either the project nor the skills are stored in the database. If you enter two different skill names and one is already in the database, the one that exists should be found and connected to the project, and the other created and so connected through the join table, if not validations fails.
project.rb
class Project < ApplicationRecord
include Skillable
[...]
end
skill_object.rb
class SkillObject < ApplicationRecord
belongs_to :skill, inverse_of: :skill_objects
belongs_to :skillable, polymorphic: true
delegate :delete_if_empty_skill_objects, to: :skill
after_destroy :delete_if_empty_skill_objects
# Avoiding duplicates of the same skill
validates :skill, uniqueness: { scope: :skillable }
end
skill.rb
class Skill < ApplicationRecord
has_many :skill_objects, inverse_of: :skill
has_many :projects, through: :skill_objects, source: :sectorable, source_type: 'Project'
has_many :experiences, through: :skill_objects, source: :sectorable, source_type: 'Experience'
validates :name, uniqueness: true, presence: true
def delete_if_empty_skill_objects
self.destroy if self.skill_objects.empty? and not self.is_global
end
end
I use a concern that is included on the different types of skillable objects:
concercns/skillable.rb
module Skillable
extend ActiveSupport::Concern
included do
attr_accessor :skills_attributes
after_save :set_skills
# Adding needed relations for skillable objects
has_many :skill_objects, -> { order(created_at: :asc) }, as: :skillable, dependent: :destroy
has_many :skills, through: :skill_objects
private
def set_skills
# THIS CODE IS THE ONE I AM STRUGGLING WITH
# Parsing out all the skill names
skill_names = skills_attributes.pluck(:name)
# Removing existing skills
self.skills = []
# Building new skills
skill_names.each do |name|
existing = Skill.find_by(name: name)
if existing
self.skills << existing
else
self.skills.new(name: name)
end
end
raise ActiveRecord::Rollback unless self.valid?
self.skills.each(&:save)
end
end
end
Does anyone know how I can write the set_skill function in skillable.rb to be able to save and update skills, validate the parent object and the skills, do a rollback if not validated and add the appropriate errors to the project object if it failes?

How to scope Rails model id which is dependent on belongs_to association?

I have next structure of models in my app:
class Company
has_many :employees
end
class Employee
belongs_to :company
end
Is there a way to make it possible for employees to have unique ids (default primary keys) depending on belongs_to Company association?
These should return different Employee models:
/companies/1/employees/1
/companies/2/employees/1
Thanks!
Try the sequenced gem, it does exactly what you're asking. There is one consideration to keep in mind though.
Your requirement deprives Employee's id field of uniqueness which it needs to be a primary key. Therefore you'd either need to have a composite key in Employee, namely [:company_id, :employee_id] or use the Employee's acts_as_sequenced field not as the primary key but more like a slug.
Just in case you care to explore the composite key approach, there is composite_primary_key gem which aims to support ActiveRecord associations on top of composite keys. I haven't tried it myself though.
According to its docs, your associations could look something like this:
class Company < ActiveRecord::Base
has_many :employees, :foreign_key => [:company_id, :employee_id]
end
class Employee < ActiveRecord::Base
self.primary_keys = :user_id, :employee_id
belongs_to :company
end
But its quite likely this is an overkill approach for your goal.

Caching for model in rails

product.rb
has_many :votes
vote.rb
belongs_to :product
Every time, i use sorting in my index controller:
index_controller.rb
def index
#products = Product.all.sort { |m| m.votes.count }
end
So, i think it would be good to cache votes count for each product (create additional column votesCount in products table)?
If yes, can i preform that using before_save and before_delete callbacks in vote.rb model?
Or what is the best practice method?
Give me some examples please.
I guess you are looking for counter_cache
The :counter_cache option can be used to make finding the number of belonging objects more efficient
Consider these models:
class Order < ActiveRecord::Base
belongs_to :customer, counter_cache: true
end
class Customer < ActiveRecord::Base
has_many :orders
end
With this declaration, Rails will keep the cache value up to date, and then return that value in response to the size method.
Although the :counter_cache option is specified on the model that includes the belongs_to declaration, the actual column must be added to the associated model. In the case above, you would need to add a column named orders_count to the Customer model

Rails: Address model being used twice, should it be separated into two tables?

I am making an ecommerce site, and I have Purchases which has_one :shipping_address and has_one :billing_address
In the past the way I've implemented this is to structure my models like so:
class Address < ActiveRecord::Base
belongs_to :billed_purchase, class_name: Purchase, foreign_key: "billed_purchase_id"
belongs_to :shipped_purchase, class_name: Purchase, foreign_key: "shipped_purchase_id"
belongs_to :state
end
class Purchase < ActiveRecord::Base
INCOMPLETE = 'Incomplete'
belongs_to :user
has_one :shipping_address, class: Address, foreign_key: "shipped_purchase_id"
has_one :billing_address, class: Address, foreign_key: "billed_purchase_id"
...
end
As you can see, I reuse the Address model and just mask it as something else by using different foreign keys.
This works completely find, but is there a cleaner way to do this? Should I be using concerns? I'm sure the behavior of these two models will always be 100% the same, so I'm not sure if splitting them up into two tables is the way to go. Thanks for your tips.
EDIT The original version of this was wrong. I have corrected it and added a note to the bottom.
You probably shouldn't split it into two models unless you have some other compelling reason to do so. One thing you might consider, though, is making the Address model polymorphic. Like this:
First: Remove the specific foreign keys from addresses and add polymorphic type and id columns in a migration:
remove_column :addresses, :shipping_purchase_id
remove_column :addresses, :billing_purchase_id
add_column :addresses, :addressable_type, :string
add_column :addresses, :addressable_id, :integer
add_column :addresses, :address_type, :string
add_index :addresses, [:addressable_type, :addressable_id]
add_index :addresses, :address_type
Second: Remove the associations from the Address model and add a polymorphic association instead:
class Address < ActiveRecord::Base
belongs_to :addressable, polymorphic: true
...
end
Third: Define associations to it from the Purchase model:
class Purchase < ActiveRecord::Base
has_one :billing_address, -> { where(address_type: "billing") }, as: :addressable, class_name: "Address"
has_one :shipping_address, -> { where(address_type: "shipping") }, as: :addressable, class_name: "Address"
end
Now you can work with them like this:
p = Purchase.new
p.build_billing_address(city: "Phoenix", state: "AZ")
p.build_shipping_address(city: "Indianapolis", state: "IN")
p.save!
...
p = Purchase.where(...)
p.billing_address
p.shipping_address
In your controllers and views this will work just like what you have now except that you access the Purchase for an Address by calling address.addressable instead of address.billed_purchase or address.shipped_purchase.
You can now add additional address joins to Purchase or to any other model just by defining the association with the :as option, so it is very flexible without model changes.
There are some disadvantages to polymorphic associations. Most importantly, you can't eager fetch from the Address side in the above setup:
Address.where(...).includes(:addressable) # <= This will fail with an error
But you can still do it from the Purchase side, which is almost certainly where you'd need it anyway.
You can read up on polymorphic associations here: Active Record Association Guide.
EDIT NOTE: In the original version of this, I neglected to add the address_type discriminator column. This is pernicious because it would seem like it is working, but you'd get the wrong address records back after the fact. When you use polymorphic associations, and you want to associate the model to another model in more than one way, you need a third "discriminator" column to keep track of which one is which. Sorry for the mixup!
In addtion to #gwcoffey 's answer.
Another option would be using Single Table Inhertinace which perhaps suits more for that case, because every address has a mostly similar format.

validating uniqueness of has_many association with a string "checksum"

I had an idea to validate the uniqueness of a has_many association: what if we generate a string based on the ids of the associated records?
For example:
class Exam
has_many :problems #problems are unique and can be in multiple exams
validate :checksum, uniqueness: true #string
before_validate :check
def check
checksum = problems.map {|p| p.id}.join
end
end
The edge case we want to solve is:
Given distinct problems 3x4, sqrt(4), 5+5, etc.., we don't want all of them to be in more than one exam.
Does anyone have thoughts on this approach? Is there a better way to validate uniqueness of has_many?
(P.S. I'm not sure if "checksum" is the right term.)
Base on the following:
You have an Exam model and a Problem model
Each Exam has_many Problems
Each problem must be unique per Exam
I think it makes more sense to place a uniqueness validation on an attribute of Problem but scope the validation to its Exam so that that multiple Exams can have the same Problems but each Exam has a unique set of Problems.
So for example if there was an attribute named value, we place the uniqueness validation on it and scope it to exam_id.
class Exam < ActiveRecord::Base
has_many :problems
end
class Problem < ActiveRecord::Base
belongs_to :exam
validates :value, :uniqueness => { :scope => :exam_id }
end

Resources