Accessing association in validates_with - ruby-on-rails

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

How to add conditions for json model association

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!

Why would one use reject_if if you can use validation?

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.

nested form associated model getting autosaved

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

Rails accepts_nested_attributes for: eliminating duplicates

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) }

Rails 3.0.10 before_validation callback not called for associated collection objects

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

Resources