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
Related
Rails newbie here. I am having trouble with ensuring the presence of at least one "has_many :through" relationship in my users model.
I have:
class User < ApplicationRecord
has_many :company_users, dependent: :destroy
has_many :companies, through: :company_users
#validates :company_users, presence: true
end
class Company < ApplicationRecord
has_many :company_users, dependent: :destroy
has_many :users, through: :company_users
end
class CompanyUser < ApplicationRecord
belongs_to :user
belongs_to :company
end
I got the commented validates line from the excellent answers at:
Validate that an object has one or more associated objects
Rails 3: validate presence of at least one has many through association item
However when I implement this line, then doing the following in rails console:
c = Company.create
c.users.create!(email: "test#example.com")
gives
`raise_validation_error': Validation failed: Company users can't be blank (ActiveRecord::RecordInvalid)
From my newbie perspective, it seems like the HMT entry isn't created until after the user is created, which creates a validation error preventing the user in the first place! It's probably a very simple error, but how do I get the above behaviour to work with that validation in place?
I have tried setting inverse_of in a couple place, without any success.
And before it's mentioned, I'm purposefully using HMT instead of HABTM because I have additional attributes on the company_users model that will be set.
One option would be to change the validation to
validates :companies, presence: true
and then create the user with
User.create(companies: [Company.create])
Edit:
Adding inverse_of to the CompanyUser model should also work. The validation would be left as validate :company_users, presence: true:
class CompanyUser
belongs_to :user, inverse_of: :company_users
belongs_to :company
end
A report_template has_many report_template_columns, which each have a
name and an index attribute.
class ReportTemplateColumn < ApplicationRecord
belongs_to :report_template
validates :name, presence: true
end
class ReportTemplate < ApplicationRecord
has_many :report_template_columns, -> { order(index: :asc) }, dependent: :destroy
accepts_nested_attributes_for :report_template_columns, allow_destroy: true
end
The report_template_columns need to be ordered by the index column. I'm applying this with a scope on the has_many association, however doing so causes the following error:
> ReportTemplate.create!(report_template_columns: [ReportTemplateColumn.new(name: 'id', index: '1')])
ActiveRecord::RecordInvalid: Validation failed: Report template columns report template must exist
from /usr/local/bundle/gems/activerecord-5.1.4/lib/active_record/validations.rb:78:in `raise_validation_error'
If I remove the scope the same command succeeds.
If I replace the order scope with where scope that command fails in the same way, so it seems to be the presence of the scope rather than the use of order specifically.
How can I apply a scope to the has_many without breaking the nested creation?
I believe you need the :inverse_of option added to the has_many association.
class ReportTemplate < ApplicationRecord
has_many :report_template_columns, -> { order(index: :asc) },
dependent: :destroy, inverse_of: :report_template
end
The api states that :inverse_of:
Specifies the name of the belongs_to association on the associated object that is the inverse of this has_many association. Does not work in combination with :through or :as options. See ActiveRecord::Associations::ClassMethods's overview on Bi-directional associations for more detail.
I also like how the cocoon gem words their reason for using it:
Rails 5 Note: since rails 5 a belongs_to relation is by default required. While this absolutely makes sense, this also means associations have to be declared more explicitly. When saving nested items, theoretically the parent is not yet saved on validation, so rails needs help to know the link between relations. There are two ways: either declare the belongs_to as optional: false, but the cleanest way is to specify the inverse_of: on the has_many. That is why we write: has_many :tasks, inverse_of: :project
Is there any reason you would use reject_if and do something like this?
class User < ActiveRecord::Base
has_many :posts
accepts_nested_attributes_for :posts, reject_if: proc do |attributes|
attributes['title'].blank?
end
end
Instead of using validation on the Post model?
class Post < ActiveRecord::Base
belongs_to :user
validates :title, presence: true
end
If you use validation, the creation of User will fail if there exists a Post which does not have a title.
If you use reject_if, the creation of User will succeed even if some, or all, of the Posts don't have titles. Only those posts without titles won't be saved.
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
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) }