I need to ensure that when a product is created it has atleast one category.
I could do this with a custom validation class, but I was hoping there was a more standard way of doing it.
class Product < ActiveRecord::Base
has_many :product_categories
has_many :categories, :through => :product_categories #must have at least 1
end
class Category < ActiveRecord::Base
has_many :product_categories
has_many :products, :through => :product_categories
end
class ProductCategory < ActiveRecord::Base
belongs_to :product
belongs_to :category
end
There is a validation that will check the length of your association. Try this:
class Product < ActiveRecord::Base
has_many :product_categories
has_many :categories, :through => :product_categories
validates :categories, :length => { :minimum => 1 }
end
Ensures it has at least one category:
class Product < ActiveRecord::Base
has_many :product_categories
has_many :categories, :through => :product_categories
validates :categories, :presence => true
end
I find the error message using :presence is clearer than using length minimum 1 validation
Instead of wpgreenway's solution, I would suggest to use a hook method as before_save and use a has_and_belongs_to_many association.
class Product < ActiveRecord::Base
has_and_belongs_to_many :categories
before_save :ensure_that_a_product_belongs_to_one_category
def ensure_that_a_product_belongs_to_one_category
if self.category_ids < 1
errors.add(:base, "A product must belongs to one category at least")
return false
else
return true
end
end
class ProductsController < ApplicationController
def create
params[:category] ||= []
#product.category_ids = params[:category]
.....
end
end
And in your view, use can use for example options_from_collection_for_select
Related
The models I have:
Category:
class Category < ApplicationRecord
has_many :categorizations
has_many :providers, through: :categorizations
accepts_nested_attributes_for :categorizations
end
Provider:
class Provider < ApplicationRecord
has_many :categorizations
has_many :categories, through: :categorizations
accepts_nested_attributes_for :categorizations
end
Categorization:
class Categorization < ApplicationRecord
belongs_to :category
belongs_to :provider
has_many :games, dependent: :destroy
accepts_nested_attributes_for :games
end
Game:
class Game < ApplicationRecord
belongs_to :categorization
end
The games table has most_popular field that is boolean. I need that only one game for each provider could have that boolean field set to true. If I had to make that only one game could have a boolean field = true, I would do smth. like this:
class Game < ApplicationRecord
belongs_to :categorization
validate :only_one_most_popular_game
scope :most_popular, -> { where(most_popular: true) }
protected
def only_one_most_popular_game
return unless most_popular?
matches = Game.most_popular
if persisted?
matches = matches.where('id != ?', id)
end
if matches.exists?
errors.add(:most_popular, 'Can\'t have another most popular game.')
end
end
end
So, what is the best way to solve this issue? Thanks ahead.
You don't want to look at all games, you only want to look at the ones for that categorization.
def only_one_most_popular_game
return unless most_popular?
if categorization.games.most_popular.where('id != ?', id).first
errors.add(:most_popular, 'Can\'t have another most popular game.')
end
end
EDIT
You can also specify
class Provider < ApplicationRecord
has_many :games, through: :categories
(above requires Rails 3.1 or later)
Which will let you do...
if categorization.provider.games.most_popular.where('games.id != ?', id).first
I have the following models:
class Article < ActiveRecord::Base
has_many :comments, :as => :subject, :dependent => :destroy
has_many :deleted_comments, :as => :subject, :dependent => :destroy
end
class DeletedComment < ActiveRecord::Base
belongs_to :subject, :polymorphic => true
end
class Comment < ActiveRecord::Base
belongs_to :subject, :polymorphic => true
before_destroy :create_deleted_comment
def create_deleted_comment
DeletedComment.create!(....)
end
end
In my database, I have quite a few DeletedComment objects where the subject is nil. The DeletedComment (and Comment) model stores :article_id, and for the ones where the subject is nil, Article.find(deleted_comment.article_id) raises an ActiveRecord::RecordNotFound error.
Are there any cases where the :dependent => :destroy would destroy the parent record but leave the dependencies untouched?
Is it possible that in some cases when I delete an Article the deleted_comments are destroyed before comments? and when the comments are destroyed, deleted_comments are created and not destroyed (because ActiveRecord has already checked the dependent deleted_comment and tried to destroy any dependencies)?
According to official documentation:
Using polymorphic associations in combination with single table inheritance (STI) is a little tricky. In order for the associations to work as expected, ensure that you store the base model for the STI models in the type column of the polymorphic association. To continue with the asset example above, suppose there are guest posts and member posts that use the posts table for STI. In this case, there must be a type column in the posts table.
class Asset < ActiveRecord::Base
belongs_to :attachable, polymorphic: true
def attachable_type=(sType)
super(sType.to_s.classify.constantize.base_class.to_s)
end
end
class Post < ActiveRecord::Base
# because we store "Post" in attachable_type now dependent: :destroy will work
has_many :assets, as: :attachable, dependent: :destroy
end
class GuestPost < Post
end
class MemberPost < Post
end
I guess you could use examle and do something like:
class Article < ActiveRecord::Base
# for deletion only
has_many :abstract_comments, :as => :subject, :dependent => :destroy
# for 'manual' access/edition
has_many :comments, :as => :subject
has_many :deleted_comments, :as => :subject
end
class AbstractComment < ActiveRecord::Base
belongs_to :subject, :polymorphic => true
def attachable_type=(sType)
super(sType.to_s.classify.constantize.base_class.to_s)
end
end
class DeletedComment < AbstractComment
end
class Comment < AbstractComment
before_destroy :create_deleted_comment
def create_deleted_comment
DeletedComment.create!(....)
end
end
I have a fairly normal class structure, using a polymorphic association:
class Contact < ActiveRecord::Base
has_many :opportunities, :as => :has_opportunities, dependent: :destroy
end
class Company < ActiveRecord::Base
has_many :opportunities, :as => :has_opportunities, dependent: :destroy
end
class Opportunity < ActiveRecord::Base
belongs_to :has_opportunities, polymorphic: true
belongs_to :contact, foreign_key: 'has_opportunities_id', conditions: "opportunities.has_opportunities_type = 'Contact'"
belongs_to :company, foreign_key: 'has_opportunities_id', conditions: "opportunities.has_opportunities_type = 'Company'"
end
In Rails 4 using :conditions has been deprecated, but I can't figure out the "new" syntax required to allow access to the parent object from the child.
Edit: Yes, you can do opportunity.has_opportunities which will return you a Contact or a Company, but it is often "nicer" in code to use opportunity.contact or opportunity.company
Can't you simply set it up as a regular polymorphic association?
class Opportunity < ActiveRecord::Base
belongs_to :has_opportunities, polymorphic: true
end
I have some models like these:
class Alpha < ActiveRecord::Base
has_many :items
end
class Beta < ActiveRecord::Base
has_many :items
end
class Item < ActiveRecord::Base
belongs_to :alpha
belongs_to :beta
end
But i want Item model in each database record to belong either to an :alpha or to a :beta but NOT both. Any nice way to do it in Rails 3? or should I model it with AlphaItems and BetaItems instead?
You probably want to use a Polymorphic Association for this. More details - http://guides.rubyonrails.org/association_basics.html#polymorphic-associations
class Alpha < ActiveRecord::Base
has_many :items, :as => :itemable
end
class Beta < ActiveRecord::Base
has_many :items, :as => :itemable
end
class Item < ActiveRecord::Base
belongs_to :itemable, :polymorphic => true
end
I just created a counter_cache field and the controller looks like this.
#users = User.where(:sex => 2).order('received_likes_count')
The association in User.rb is
has_many :received_likes, :through => :attachments, :source => :likes, :dependent => :destroy
Problem is that counter_cache is declared in the belong_to of Like.rb and I don't know how to tell it that is for the has_many :through association.
belongs_to :user, :counter_cache => :received_likes
You have previous
class Product
has_and_belongs_to_many :categories
end
class Category
has_and_belongs_to_many :products
end
and migration
class CreateCategoriesProducts < ActiveRecord::Migration
def change
create_table :categories_products, id: false do |t|
t.references :category
t.references :product
end
add_index :categories_products, [:category_id, :product_id]
end
end
now change all to
class Product
has_many :categories_products, dependent: :destroy
has_many :categories, through: :categories_products
end
class Category
has_many :categories_products, dependent: :destroy
has_many :products, through: :categories_products
end
and new one
class CategoriesProduct < ActiveRecord::Base
# this model uses table "categories_products" as it is
# column products_count is in the table "categories"
belongs_to :category, counter_cache: :products_count
belongs_to :product
end
According to this post (from last month) and this post (from 2008), it doesn't seem to be possible. However, the latter post does have code for a workaround (copy/paste'd from that link for your convenience, credit goes to DEfusion in the second link)
class C < ActiveRecord::Base
belongs_to :B
after_create :increment_A_counter_cache
after_destroy :decrement_A_counter_cache
private
def increment_A_counter_cache
A.increment_counter( 'c_count', self.B.A.id )
end
def decrement_A_counter_cache
A.decrement_counter( 'c_count', self.B.A.id )
end
end
(This is for a scheme where C belongs_to B, B belongs_to A, A has_many C :through => B
This basically does the same thing:
after_save :cache_post_count_on_tags
def cache_post_count_on_tags
tags.each {|t| tag.update_attribute(:posts_count, t.posts.size)}
end
And you need a posts_count column on tags, or whatever associations you have.