I have 2 models as below,
Updated based on suggestions
class User < ActiveRecord::Base
has_many :company_users, dependent: :destroy, inverse_of: :user
accepts_nested_attributes_for :company_users, allow_destroy: true
has_many :companies, through: :company_users
has_many :roles, through: :company_users
end
and
class CompanyUser < ActiveRecord::Base
belongs_to :company
belongs_to :role
belongs_to :user, inverse_of: :company_users
validates :user, uniqueness: {scope: [:company, :role]}
end
I find the uniqueness validation is working only on the update request. On create request validation is not functioning and it simply bypasses it.
I want to enable the same validation to reject if a user has same company & role assigned more than once.
If you want a ensure the uniqueness of user on unique pair of :company and :role, then you can try following. By default, the validations run for both create and update. You don't need :on => [ :create, :update ]. So it should be just:
validates :user, uniqueness: {scope: [:company, :role]}
Solved this issue with the below validation,
class User < ActiveRecord::Base
has_many :company_users, dependent: :destroy, inverse_of: :user
accepts_nested_attributes_for :company_users, allow_destroy: true
has_many :companies, through: :company_users
has_many :roles, through: :company_users
validate :company_users, :uniqueness_of_company_users
end
private
def uniqueness_of_company_users
errors.add(:company_users, 'error in role creation') if company_users.map{|x| "#{x.company_id} #{x.role_id}"}.uniq.size != company_users.size
end
This is the additional validation required to solve the issue
Thanks Rich Peck for this https://railscoding.wordpress.com/2015/04/27/uniqueness-gotcha/
From the docs
The :on option takes one of the values :create or :update
--
A validation is only run on create or update anyway, right?
find doesn't manipulate the db, destroy gets rid of the record & new just invokes a new instance of the object. You have literally zero other reasons to validate.
So, really, you should have:
validates :user, uniqueness: {scope: [:company_id, :role_id]}
This will look up against the values in company_id and role_id, which is probably going to be more efficient than calling the company and role objects themselves.
I could be wrong, but I really think if you used the above, it should work.
--
You may also wish to clean up your models:
class User < ActiveRecord::Base
has_many :company_users, dependent: :destroy, inverse_of: :user
accepts_nested_attributes_for :company_users, allow_destroy: true
has_many :companies, through: :company_users
has_many :roles, through: :company_users
end
class CompanyUser < ActiveRecord::Base
belongs_to :company
belongs_to :role
belongs_to :user, inverse_of: :company_users
validates :user, uniqueness: {scope: [:company_id, :role_id]}
end
Related
I'm making a rails marketplace app for uni where Users can be matched with specific products based on their request.
Users can list products that have specific categories.
Users can also list Requests where they can specify what products they're looking and their categories.
The aim is to match the request to a particular product based on the matching categories
Here are my models
class Product < ApplicationRecord
belongs_to :user
has_many_attached :images, dependent: :destroy
has_many :product_categories
has_many :categories, through: :product_categories
validates :user_id, presence: true
end
class Category < ApplicationRecord
has_many :product_categories
has_many :products, through: :product_categories
validates :name, presence: true, length: { minimum: 3, maximum: 25}
validates_uniqueness_of :name
end
class ProductCategory < ApplicationRecord
belongs_to :product
belongs_to :category
end
class Request < ApplicationRecord
belongs_to :user
has_many_attached :images, dependent: :destroy
has_many :request_categories
has_many :categories, through: :request_categories
validates :user_id, presence: true
end
class RequestCategory < ApplicationRecord
belongs_to :request
belongs_to :category
end
I was thinking of creating a new model called Match to bring together the product and categories or is it easier to match it in the request?
In my mind, your new Match class would essentially be a join table for a has_many :through association. Assuming that you're implementing an asynchronous worker (e.g. Sidekiq / ActiveJob) to go through and make "matches", you'll want to connect matches to a particular Request, and likely store some meta-data (has the user seen the Match yet? Have they rejected it?)
So, I'd probably generate a Match class like this:
rails g model Match seen_at:datetime deleted_at:datetime request:references product:references
And set up the associations as follows:
class Match < ApplicationRecord
belongs_to :request
belongs_to :product
end
class Request < ApplicationRecord
belongs_to :user
has_many_attached :images, dependent: :destroy
has_many :request_categories
has_many :categories, through: :request_categories
has_many :matches
has_many :products, through: :matches
validates :user_id, presence: true
end
class Product < ApplicationRecord
belongs_to :user
has_many_attached :images, dependent: :destroy
has_many :product_categories
has_many :categories, through: :product_categories
has_many :matches
has_many :requests, through: :matches
validates :user_id, presence: true
end
Also, you'll likely want to add the Request has_many :through to your Category model (I think you forgot that one):
class Category < ApplicationRecord
has_many :product_categories
has_many :products, through: :product_categories
has_many :request_categories
has_many :requests, through: :request_categories
validates :name, presence: true, length: { minimum: 3, maximum: 25}
validates_uniqueness_of :name
end
The big part of the job is working out how to have your app periodically look for matches - you may want to start with the Active Job Basics documentation.
In my application I have a simple relationship where a user that has many events:
class User < ApplicationRecord
has_many :events, foreign_key: 'created_by', dependent: :destroy
accepts_nested_attributes_for :events
end
class Event < ApplicationRecord
belongs_to :user, foreign_key: 'created_by', inverse_of: :events
validates :created_by, presence: true
end
When I am trying to create a user alongside with some events I am getting a validation error "Companies.created by can't be blank".
My params hash looks like that:
{"user"=>{"events_attributes"=>[{"name"=>"disney show"}]}}
When I remove validates :created_by, presence: true everything works as expected. Any help would be appreciated!
Try to specify explicitly bi-directional associations in your User model using inverse_of: :
class User < ApplicationRecord
has_many :events, foreign_key: 'created_by', inverse_of: :user, dependent: :destroy
accepts_nested_attributes_for :events
end
And your Event record will be created with present foreign key pointing to User.
How can add a cascade of deletes that will remove Profile, TodoList, and TodoItem rows for any User removed.
User Model:
class User < ActiveRecord::Base
has_one :profile
has_many :todo_lists
has_many :todo_items, through: :todo_lists, source: :todo_items
validates :username, presence: true
end
Profile Model:
class Profile < ActiveRecord::Base
belongs_to :user
validates :first_name, presence: true
validates :last_name, presence: true
validates :gender, inclusion: %w(male female)
validate :first_and_last
validate :male_Sue
def first_and_last
if (first_name.nil? and last_name.nil?)
errors.add(:base, "Specify a first or a last.")
end
end
def male_Sue
if (first_name == "Sue" and gender == "male")
errors.add(:base, "we are prevent male by name Sue.")
end
end
end
TodoList Model:
class TodoList < ActiveRecord::Base
belongs_to :user
has_many :todo_items, dependent: :destroy
default_scope { order :list_due_date }
end
TodoItem Model:
class TodoItem < ActiveRecord::Base
belongs_to :todo_list
default_scope {order :due_date }
end
Thanks, Michael.
I guess adding dependent: :destroy will do.
#user.rb
class User < ActiveRecord::Base
has_one :profile, dependent: :destroy
has_many :todo_lists, dependent: :destroy
has_many :todo_items, through: :todo_lists, source: :todo_items, dependent: :destroy
validates :username, presence: true
end
From the docs:
has_many, has_one and belongs_to associations support the :dependent option. This allows you to specify that associated records should be deleted when the owner is deleted
By using dependent: :destroy on your association in the User class, anytime you destroy a User, all associated objects to that instance gets destroyed as well.
You can check this documentation for more information.
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?
I have Board model. Each Board can be Source for other Board. This relationship is saved in Feed table.
class Board < ActiveRecord::Base
belongs_to :user
has_many :links, dependent: :destroy
has_many :feeds, dependent: :destroy
has_many :sources, through: :feeds
attr_accessible :description, :name, :user_id
validates :name, presence: true
end
class Feed < ActiveRecord::Base
belongs_to :board
belongs_to :source, class_name: "Board"
attr_accessible :board_id, :source_id
end
When I destroy Board record, then it destroys corresponding feeds. But how to do this the same but for source ?
Why don't you use :dependent => :destroy too?
Read Deleting from associations: has_many, has_one and belongs_to associations support the :dependent option
If that doesn't work you might also be able to use a ActiveRecord Callbacks:
after_destroy do |record|
other = BoardsSources.find_by_board_id_and_source_id(record.board_id, record.source_id)
other.destroy if other
end