I've created a custom validator in order to validate some attributes in my has_many association.
My class:
class User < ApplicationRecord
has_many :addresses
accepts_nested_attributes_for :addresses, allow_destroy: true
validates_with UniquenessMemoryValidator,
attributes: [:name],
collection: :addresses,
message: 'My custom message'
end
UniquenessMemoryValidator:
class UniquenessMemoryValidator < ActiveModel::Validator
def validate(record)
attrs, message = options.values_at(:attributes, :message)
collection = record[options[:collection]]
puts "collection #{collection}" # it's nil
end
end
The problem is that when I try to access my association (in this case: addresses) it prints nil.
So my question is: How can I access my "nested" array in validator?
PS: I can access any other attribute of my "record", less the association.
You can try with send if want to do it dynamically because it's usual method call:
collection = record.send(options[:collection])
Also, you can access to nested array only in accepts_nested_attributes_for validation with reject_if.
In reject_if you can pass block or method.
accepts_nested_attributes_for :addresses, reject_if: { |attrs| ... }
#OR
accepts_nested_attributes_for :addresses, reject_if: :my_method
def my_method
#some logic that returns true for invalid record
end
Related
Im trying to include an association but only ones based on its attribute's value
Joo Model:
def as_json(options = {})
super(include: [:foo, (:bar).where('bar.accepted = ?', true)])
end
undefined method `where' for :bar:Symbol
Doing super(include: [:foo, :bar]), I have no control of what I want. How to accomplish what Im trying to do? Only include where bar.accepted == true
I was looking at this to see if it was possible. Im using Rails 5 API.
Edited to show associations:
Joo:
has_many :bars, dependent: :destroy
belongs_to :foo
Foo:
has_many :bars, dependent: :destroy
Bar:
belongs_to :foo
belongs_to :joo
As per the doc I see there isn't way to include associations conditionally. I think you can make a conditional association on model Joo and call it in as_json
Class Joo < ApplicationRecord
belongs_to :foo
has_many :bars, dependent: :destroy
has_many :accepted_bars, -> { where accepted: true }, class_name: "Bar"
end
Then you call it like
#joo = Joo.all.reverse
#joo.as_json(include: [:foo, :accepted_bars])
Note: Not tested!
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) }
I've got an object called a Parent that has_many Child objects:
has_many :children
accepts_nested_attributes_for :children, :allow_destroy => true
Child includes a Module that specifies a :before_validation callback:
def self.included base
base.class_eval do
before_validation :my_callback
end
end
protected
def my_callback
logger.debug "see me!"
end
I've noticed that when creating a Parent and nesting attributes for children, the :before_validation callback is not being invoked for each Child. Is this the intended behavior? I've tried doing a before_save callback instead and it appears to work fine.
This is on Rails 3.0.10.
Thanks!
You should use validates_associated:
class Parent < ActiveRecord::Base
has_many :children
accepts_nested_attributes_for :children, :allow_destroy => true
validates_associated :children
end