I'm facing a difficult to associate things on Rails. In this case, I have a model for locals, and a model to identifier a user token ( that came from another APi). A place has a rating ( a score from 0 to 5 ) associated with it, and every user has many ratings but just one for each place. Trying to do this I create a new model that has ratings attribute and I want to associate a rating id to just one place id.
# Description of User Identifier Class
class UserIdentifier < ApplicationRecord
has_many :favorite_locals, dependent: :destroy
has_many :user_rate, dependent: :destroy
validates :identifier, presence: true
validates_numericality_of :identifier
validates_uniqueness_of :identifier
def self.find_favorites(params)
UserIdentifier.find(params).favorite_locals
end
end
# Model of Users Rates
class UserRate < ApplicationRecord
belongs_to :user_identifier
validates :rating, numericality: true
validates_numericality_of :rating, less_than_or_equal_to: 5
validates_numericality_of :rating, greater_than_or_equal_to: 0
validates :user_identifier, presence: true
end
First, you have a typo in the has_many :user_rate, dependent: :destroy. The association should be named user_rates, so the correct code for UserIdentifier model is:
class UserIdentifier < ApplicationRecord
has_many :favorite_locals, dependent: :destroy
has_many :user_rates, dependent: :destroy
# ...
end
Second, it's not clear how the "place" entity is named in your project. If it's FavoriteLocal then this is the code you need:
class UserRate < ApplicationRecord
belongs_to :user_identifier
belongs_to :favorite_local
# ...
end
If this is another model just define the belongs to association right below belongs_to :user_identifier. I hope you got the idea.
Related
Basically, I have three models: a profileable Profile model, a Staff model and a Client model.
If Profile model is created for Staff, the employee has to be at least 18 years ago. (I use a validates_date helper method to check age, see below)
If Profile model is created for Client, then there is no age restriction.
How to achieve this?
Profile.rb:
class Profile < ActiveRecord::Base
belongs_to :profileable, polymorphic: true
validates_date :date_of_birth, :before => 16.years.ago
end
Staff.rb:
class Staff < ActiveRecord::Base
has_one :profile, as: :profileable, :dependent => :destroy
end
Client.rb:
class Client < ActiveRecord::Base
has_one :profile, as: :profileable, :dependent => :destroy
end
Try using conditional validation
class Profile < ActiveRecord::Base
STAFF = 'Staff'.freeze
belongs_to :profileable, polymorphic: true
validates_date :date_of_birth, before: 18.years.ago, if: staff?
def staff?
profileable_type == STAFF
end
end
I have the following models
class User < ActiveRecord::Base
has_many :games, dependent: :destroy
validates :games, length: { maximum: 3 }
end
class Game < ActiveRecord::Base
belongs_to :user
validates :user, presence: true
validates_associated :user
end
I want to make sure that no user has more than 3 games, but the validation does not seem to work.
Why?
You can make a custom validation method. As is shown in the Rails guide:
custom validation method
class User < ActiveRecord::Base
has_many :games, dependent: :destroy
validate :has_three_games_or_less
private:
def has_three_games_or_less
if games.count > 3.
errors.add_to_base("can't have more than 3 games")
end
end
end
According to this documentation, it should be achievable using the code you have.
If it doesn't work, you could use inverse_of to store the associated objects in memory (hence making them available to your validator):
#app/models/user.rb
class User < ActiveRecord::Base
has_many :games, inverse_of: :user
validates :games, length: { maximum: 3 }
end
#app/models/game.rb
class Game < ActiveRecord::Base
belongs_to :user, inverse_of: :games
end
You could alternatively set up a custom method to do it for you:
#app/models/user.rb
class User < ActiveRecord::Base
has_many :games, inverse_of: :user
validate :max_games
private
def max_games
errors.add(:base, "You cannot have more than 3 games on this account.") unless games.size <= 3
end
end
I have two model product and variant.
Product.rb => model.
class Product < ActiveRecord::Base
has_many :variants, dependent: :destroy
end
variant.rb => model
class Variant < ActiveRecord::Base
belongs_to :product
validates :product_id, presence: true
end
I want to validate product_id validation when admin create record from variant form, not when admin create record from product model using has_many relationship.
Thanks in advance
I have 3 models. Rom::Favorite, Rom::Card, User. I am having an issue creating the User has_many rom_cards through rom_favorites
Here are my relevant parts of my models
Rom::Card
class Rom::Card < ActiveRecord::Base
has_many :rom_favorites, class_name: "Rom::Favorite", foreign_key: "rom_card_id", dependent: :destroy
self.table_name = "rom_cards"
end
User
class User < ActiveRecord::Base
# Setup accessible (or protected) attributes for your model
attr_accessible :email, :password, :password_confirmation, :remember_me, :role
has_many :rom_favorites, class_name: "Rom::Favorite", dependent: :destroy
has_many :rom_cards, class_name: "Rom::Card", through: :rom_favorites, class_name: "Rom::Favorite"
end
Rom::Favorite
class Rom::Favorite < ActiveRecord::Base
attr_accessible :rom_card_id, :user_id
belongs_to :user
belongs_to :rom_card, class_name: "Rom::Card"
validates :user, presence: true
validates :rom_card, presence: true
validates :rom_card_id, :uniqueness => {:scope => :user_id}
self.table_name = "rom_favorites"
end
All of the utility methods that come along with associations work except
a = User.find(1)
a.rom_cards
The call a.rom_cards returns an empty array and it seems to run this SQL query
SELECT "rom_favorites".* FROM "rom_favorites" INNER JOIN "rom_favorites" "rom_favorites_rom_cards_join" ON "rom_favorites"."id" = "rom_favorites_rom_cards_join"."rom_card_id" WHERE "rom_favorites_rom_cards_join"."user_id" = 1
I am not strong in SQL, but I think this seems correct.
I know a.rom_cards should return 2 cards because a.rom_favorites returns 2 favorites, and in those favorites are card_id's that exist.
The call that should allow rom_cards is the following
has_many :rom_cards, class_name: "Rom::Card", through: :rom_favorites, class_name: "Rom::Favorite"
I feel like the issue has something to do with the fact that it is trying to find the Users cards through favorites and it is looking for card_id (because I specified the class Rom::Card) instead of rom_card_id. But I could be wrong, not exactly sure.
You are duplicating the key class_name in the association hash. There is no need to write class_name: "Rom::Favorite" because by using through: :rom_favorites it will use the configuration options of has_many :rom_favorites.
Try with:
has_many :rom_cards, class_name: "Rom::Card", through: :rom_favorites
My full code can be seen at https://github.com/andyw8/simpleform_examples
I have a join model ProductCategory with the following validations:
validates :product, presence: true
validates :category, presence: true
My Product model has the following associations:
has_many :product_categories
has_many :categories, through: :product_categories
When I try to create a new product with a category, the call to #product.save! in the controller fails with:
Validation failed: Product categories is invalid
When I remove the validations, everything works and the join models are saved correctly.
I'm using strong_parameters but I don't think that should be related to this issue.
This is a "racing condition" in the callback chain.
When you create a product it doesn't have any id before it is saved, therefore there is no product in the scope of ProductCategory.
Product.new(name: "modern times", category_ids:[1, 2]) #=> #<Product id: nil >
At that stage of validation (before saving), ProductCatgory cannot assign any id to it's foreign key product_id.
That's the reason you have association validations : so that the validation happens in the scope of the whole transaction
UPDATE: As said in the comment you still can't ensure presence of a product/category. There's many ways around depending on why you want do this (e.g direct access to ProductCategory through some form)
You can create a flag to have validates :product, presence: true, if: :direct_access?
or if you can only update them: validates :product, presence: true, on: "update"
create your product first (in the products_controller) and add the categories after
... But indeed these are all compromises or workarounds from the simple #product.create(params)
Specifying inverse_of on your joining models has been documented to fix this issue:
https://github.com/rails/rails/issues/6161#issuecomment-6330795
https://github.com/rails/rails/pull/7661#issuecomment-8614206
Simplified Example:
class Product < ActiveRecord::Base
has_many :product_categories, :inverse_of => :product
has_many :categories, through: :product_categories
end
class Category < ActiveRecord::Base
has_many :product_categories, inverse_of: :category
has_many :products, through: :product_categories
end
class ProductCategory < ActiveRecord::Base
belongs_to :product
belongs_to :category
validates :product, presence: true
validates :category, presence: true
end
Product.new(:categories => [Category.new]).valid? # complains that the ProductCategory is invalid without inverse_of specified
Adapted from: https://github.com/rails/rails/issues/8269#issuecomment-12032536
Pretty sure you just need to define your relationships better. I still might have missed some, but hopefully you get the idea.
class Product < ActiveRecord::Base
include ActiveModel::ForbiddenAttributesProtection
validates :name, presence: true
validates :description, presence: true
validates :color_scheme, presence: true
belongs_to :color_scheme
has_many :product_categories, inverse_of: :product
has_many :categories, through: :product_categories
end
class ProductCategory < ActiveRecord::Base
belongs_to :product
belongs_to :category
validates_associated :product
validates_associated :category
# TODO work out why this causes ProductsController#create to fail
# validates :product, presence: true
# validates :category, presence: true
end
class Category < ActiveRecord::Base
has_many :product_categories, inverse_of: :category
has_many :products, through: :product_categories
end