问题
I have my model setup as below. Everything works fine except blank part records are allowed even if all part and chapter fields are blank.
class Book < ActiveRecord::Base
has_many :parts, inverse_of: :book
accepts_nested_attributes_for :parts, reject_if: :all_blank
end
class Part < ActiveRecord::Base
belongs_to :book, inverse_of: :parts
has_many :chapters, inverse_of: :part
accepts_nested_attributes_for :chapters, reject_if: :all_blank
end
class Chapter < ActiveRecord::Base
belongs_to :part, inverse_of: :chapters
end
Spelunking the code, :all_blank
gets replaced with proc { |attributes| attributes.all? { |key, value| key == '_destroy' || value.blank? } }
. So, I use that instead of :all_blank
and add in some debugging. Looks like what is happening is the part's chapters attribute is responding to blank?
with false
because it is an instantiated hash object, even though all it contains is another hash that only contains blank values:
chapters_attributes: !ruby/hash:ActionController::Parameters
'0': !ruby/hash:ActionController::Parameters
title: ''
text: ''
Is it just not meant to work this way?
I've found a workaround:
accepts_nested_attributes_for :parts, reject_if: proc { |attributes|
attributes.all? do |key, value|
key == '_destroy' || value.blank? ||
(value.is_a?(Hash) && value.all? { |key2, value2| value2.all? { |key3, value3| key3 == '_destroy' || value3.blank? } })
end
}
But I was hoping I was missing a better way to handle this.
Update 1: I tried redefining blank?
for Hash
but that causes probs.
class Hash
def blank?
:empty? || all? { |k,v| v.blank? }
end
end
Update 2: This makes :all_blank
work as I was expecting it to, but it is ugly and not well-tested.
module ActiveRecord::NestedAttributes::ClassMethods
REJECT_ALL_BLANK_PROC = proc { |attributes| attributes.all? { |k, v| k == '_destroy' || v.valueless? } }
end
class Object
alias_method :valueless?, :blank?
end
class Hash
def valueless?
blank? || all? { |k, v| v.valueless? }
end
end
Update 3: Doh! Update 1 had a typo in it. This version does seem to work.
class Hash
def blank?
empty? || all? { |k,v| v.blank? }
end
end
Does this have too much potential for unintended consequences to be a viable option? If this is a good option, where in my app should this code live?
回答1:
When using :all_blank
with accepts_nested_attributes_for
, it will check each individual attribute to see if it is blank.
# From the api documentation
REJECT_ALL_BLANK_PROC = proc do |attributes|
attributes.all? { |key, value| key == "_destroy" || value.blank? }
end
http://api.rubyonrails.org/classes/ActiveRecord/NestedAttributes/ClassMethods.html
The attribute of the nested association will be a hash that contains the attributes of the association. The check to see if the attribute is blank will return false
because the hash is not empty - it contains a key for each attribute of the association. This behavior will cause the reject_if: :all_blank
to return false
because of the nested association.
To work around this, you can add your own method to application_record.rb like so:
# Add an instance method to application_record.rb / active_record.rb
def all_blank?(attributes)
attributes.all? do |key, value|
key == '_destroy' || value.blank? ||
value.is_a?(Hash) && all_blank?(value)
end
end
# Then modify your model book.rb to call that method
accepts_nested_attributes_for :parts, reject_if: :all_blank?
回答2:
This is still an issue in Rails 4.2.4, so I figured I would share what I've learned. To promote getting a fix in Rails, see this issue and this pull request.
I based my fix on that pull request. In your case, it would look something like this (just to be clear, the three dots are just to skip over other code):
class Book < ActiveRecord::Base
...
accepts_nested_attributes_for :parts,
reject_if: proc { |attributes| deep_blank?(attributes) }
...
def self.deep_blank?(hash)
hash.each do |key, value|
next if key == '_destroy'
any_blank = value.is_a?(Hash) ? deep_blank?(value) : value.blank?
return false unless any_blank
end
true
end
end
来源:https://stackoverflow.com/questions/19415205/how-does-reject-if-all-blank-for-accepts-nested-attributes-for-work-when-worki