Say we've got the following models:
class User < ActiveRecord::Base
has_many :widgets
accepts_nested_attributes_for :widgets, allow_destroy: true
end
class Widget < ActiveRecord::Base
belongs_to :user
validates :title, presence: true, uniqueness: { scope: [:user_id] }
end
When I save a user with nested widget attributes that contain a duplicate title I get a validation error as expected. What's a good way to avoid the validation error and silently eliminate the duplicate entries before saving?
Thanks.
You could just reject the nested attributes if they don't match certain criteria:
accepts_nested_attributes_for :widgets,
allow_destroy: true,
reject_if: lambda { |w| Widget.pluck(:title).include?(w.title) && Widget.pluck(:user_id).include?(w.user_id) }
Related
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.
I don't know why, but for some reason my nested association is not being respected by rails.
My student class has nested associations: addresses, tests
The addresses association works fine. The tests association, which is identical to the addresses association, does not work. While I can create and edit test instances successfully, they do not undergo the validation checks defined in their model, nor do they implement the ransack search features that I specified in their model.
Here's the student model:
class Student < ActiveRecord::Base
unloadable
validates :name, presence: true, length: {maximum: 50}
has_many :addresses, :dependent => :destroy
has_many :tests, :dependent => :destroy
accepts_nested_attributes_for :addresses,
:reject_if => :all_blank,
:allow_destroy => true
accepts_nested_attributes_for :tests,
:reject_if => :all_blank,
:allow_destroy => true
end
Here's the addresses model (which works):
class Address < ActiveRecord::Base
belongs_to :student
validates :address, presence: true, length: {maximum: 80}
def self.ransackable_attributes(auth_object = nil)
['county', 'zip', 'address']
end
end
And here's the (basically) identical tests model (which doesn't work):
class Test < ActiveRecord::Base
belongs_to :student
validates :name, length: {maximum: 40}
def self.ransackable_attributes(auth_object = nil)
['score', 'date']
end
end
It's completely ignoring the validation as well as the ransack function. While everything runs without error, I am able to input whatever I want, even if it's well over validated length, and it throws no error message.
I feel like I didn't create my model correctly or something, and Rails doesn't know it's there?
Test, I believe is a module that in the minitest framework. Avoid naming your models Test. As a matter of fact test is a reserved keyword in Rails. Try renaming your model's name and trying again.
My associated model gets autosaved. I would like this to happen only when those fields contains any values. Obviously reject_if is the way to go for this but I cannot get it to work. I've tried :new_record? together with passing in the Proc. But I suspect something else is wrong here as the reject_posts method below does not get called.
has_one :reservation, as: :reservable, autosave: false,
class_name: Booking::Reservation
accepts_nested_attributes_for :reservation, reject_if: :reject_posts
def reject_posts(attributed)
binding.pry
end
How would I go about to try to debug this?
How would I go about to try to debug this?
The first step is to make sure your flow is invoking the accepts_nested_attributes_for method.
The simple check is to determine if your request console log sends the parameters as...
parameters: {
"model" => {
"reservation_attributes" => {...}
}
}
If your params don't feature _attributes, your fields_for or flow are incorrect.
--
You should also make sure your class_name option passes a string:
has_one :reservation, as: :reservable, class_name: "Booking::Reservation"
I would personally do the following:
#app/models/model.rb
class Model < ActiveRecord::Base
has_one :reservation, as: :reservable, class_name: "Booking::Reservation", inverse_of: :model
accepts_nested_attributes_for :reservation, reject_if: :all_blank?
end
The "rejection" is not validation. It just prevents the associated object from being passed to the nested model...
You may also set a :reject_if proc to silently ignore any new record hashes if they fail to pass your criteria
I'd use validations on the other model (with inverse_of):
#app/models/reservation.rb
class Reservation < ActiveRecord::Base
belongs_to :model, inverse_of: :reservation
validates :x, :y, :z, presence: true
end
--
If you wanted to reject based on specific attributes being blank, you should use:
#app/models/model.rb
class Model < ActiveRecord::Base
has_one :reservation, as: :reservable, class_name: "Booking::Reservation"
accepts_nested_attributes_for :reservation, reject_if: proc { |a| a['name'].blank? || a['title'].blank? }
end
Given the relationship:
Library -> Books -> Category
Let's assume that category has some validations for instance:
validates :name, presence:true
If I run something like this:
Library.update(hash_with_all_attributes_from_library_to_category)
It will return true if all the library attributes are satisfied and ignore the category's validations.
why it happens? is there a better way to update all data except find one at time?
EDIT
I can't post the entire file but its a snippet
Class Question << ActiveRecord::Base
has_many :question_alternatives, dependent: :destroy, inverse_of: :question
# couple of validations
accepts_nested_attributes_for :question_alternatives, allow_destroy: true, reject_if: -> (attributes) {
attributes[:text].blank?
}
And I check for the other relationships that I have the accepts_nested_attributes_for and the inverse_of
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