Skip to content

Commit

Permalink
Merge pull request #220 from jrochkind/fix_nested_model_validation
Browse files Browse the repository at this point in the history
Keep nested model validation working in Rails post 7.1 (undefined method `custom_validation_context?')
  • Loading branch information
jrochkind authored Jan 8, 2024
2 parents a4024dc + 5ecce44 commit 244e4ed
Show file tree
Hide file tree
Showing 3 changed files with 33 additions and 6 deletions.
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Fixed

*
* Keep nested model validation working in Rails post-7.1, by no longer trying to re-use ActiveRecord::Validations::AssociatedValidator, instead supplying our own custom code. https://github.com/jrochkind/attr_json/pull/220

*

Expand Down
10 changes: 5 additions & 5 deletions lib/attr_json/model.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

require 'attr_json/type/model'
require 'attr_json/model/cocoon_compat'
require 'attr_json/model/nested_model_validator'

require 'attr_json/serialization_coder_from_type'

Expand Down Expand Up @@ -207,8 +208,8 @@ def attribute_types
# @option options [String,Symbol] :store_key (nil) Serialize to JSON using
# given store_key, rather than name as would be usual.
#
# @option options [Boolean] :validate (true) Create an ActiveRecord::Validations::AssociatedValidator so
# validation errors on the attributes post up to self.
# @option options [Boolean] :validate (true) Mak validation errors on the attributes post up
# to self, using something similar to an ActiveRecord::Validations::AssociatedValidator
def attr_json(name, type, **options)
options.assert_valid_keys(*(AttributeDefinition::VALID_OPTIONS - [:container_attribute] + [:validate]))

Expand All @@ -220,9 +221,8 @@ def attr_json(name, type, **options)

# By default, automatically validate nested models
if type.kind_of?(AttrJson::Type::Model) && options[:validate] != false
# Yes. we're passing an ActiveRecord::Validations validator, but
# it works fine for ActiveModel. If this changes in the future, tests will catch.
self.validates_with ActiveRecord::Validations::AssociatedValidator, attributes: [name.to_sym]
# Post validations up with something based on ActiveRecord::Validations::AssociatedValidator
self.validates_with ::AttrJson::Model::NestedModelValidator, attributes: [name.to_sym]
end

_attr_jsons_module.module_eval do
Expand Down
27 changes: 27 additions & 0 deletions lib/attr_json/model/nested_model_validator.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
module AttrJson
module Model
# Used to validate an attribute in an AttrJson::Model whose values are other models, when
# you want validation errors on the nested models to post up.
#
# This is based on ActiveRecord's own ActiveRecord::Validations::AssociatedValidator, and actually forked
# from it at https://github.com/rails/rails/blob/e37adfed4eff3b43350ec87222a922e9c72d9c1b/activerecord/lib/active_record/validations/associated.rb
#
# We used to simply use an ActiveRecord::Validations::AssociatedValidator, but as of https://github.com/jrochkind/attr_json/pull/220 (e1e798142d)
# it got ActiveRecord-specific functionality that no longer worked with our use case.
#
# No problem, the implementation is simple, we can provide it here, based on the last version that did work.
class NestedModelValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
if Array(value).reject { |r| valid_object?(r) }.any?
record.errors.add(attribute, :invalid, **options.merge(value: value))
end
end

private
def valid_object?(record)
#(record.respond_to?(:marked_for_destruction?) && record.marked_for_destruction?) || record.valid?
record.valid?
end
end
end
end

0 comments on commit 244e4ed

Please sign in to comment.