rails_validates_nested_uniqueness 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/CHANGELOG.md +52 -0
- data/LICENSE +21 -0
- data/README.md +107 -0
- data/lib/validates_nested_uniqueness.rb +156 -0
- metadata +89 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 69a42a6cd773ea23d09f09a9168245ab05056af092cbeed9de15c765b67b7dd0
|
|
4
|
+
data.tar.gz: 81ff8b04934c18ae5f4a6c74ae8a90e56bedc5fc281d46880ee329d3a0a91949
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 6187525c8ca0575c4535d53dcd458f95f40779b6f9159199a86dc952b29a7a0c7a8e831efae3b6b18745eafb7c95faa3d407d50869c80a0555ffc2aefa795539
|
|
7
|
+
data.tar.gz: d5e11e33a59a39725ebb2fe3506d8a044e37c61a9be979d9c7e24921f239739b02b2d83278d300e743b628d22e622480480dae10d9218d916eeccaa4cb280acd
|
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [1.3.0] - 2025-11-13
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
- **New `:comparison` option** for custom value normalization logic
|
|
12
|
+
- Enhanced input validation with better error messages
|
|
13
|
+
- Required `:attribute` parameter validation
|
|
14
|
+
- Comprehensive edge case handling
|
|
15
|
+
- Extensive test coverage for edge cases
|
|
16
|
+
|
|
17
|
+
### Changed
|
|
18
|
+
- **Gem name changed to `rails_validates_nested_uniqueness`** for RubyGems publication
|
|
19
|
+
- Refactored validator logic into smaller, focused methods
|
|
20
|
+
- Improved code organization and maintainability
|
|
21
|
+
- Enhanced documentation with more examples
|
|
22
|
+
- Better nil and empty value handling
|
|
23
|
+
- More robust error handling throughout
|
|
24
|
+
|
|
25
|
+
### Fixed
|
|
26
|
+
- Safer method calls with proper existence checks
|
|
27
|
+
- Better handling of non-string values with case sensitivity
|
|
28
|
+
|
|
29
|
+
## [1.2.0] - 2024-11-13
|
|
30
|
+
### Added
|
|
31
|
+
- Updated minimal supported versions (Ruby >= 3.2.0, ActiveModel >= 7.2.0)
|
|
32
|
+
- Support for Rails 8.0 and 8.1
|
|
33
|
+
- Enhanced Ruby 3.4 support
|
|
34
|
+
|
|
35
|
+
### Changed
|
|
36
|
+
- Updated CI workflow to test with latest Rails versions
|
|
37
|
+
|
|
38
|
+
## [1.1.1] - 2024-11-13
|
|
39
|
+
### Fixed
|
|
40
|
+
- Added missing `require 'logger'` statement
|
|
41
|
+
|
|
42
|
+
## [1.1.0] - 2024-12-18
|
|
43
|
+
### Added
|
|
44
|
+
- Ruby 3.4 support
|
|
45
|
+
- Comprehensive test matrix for different Rails versions
|
|
46
|
+
|
|
47
|
+
## [1.0.0] - 2024-03-15
|
|
48
|
+
### Added
|
|
49
|
+
- Initial stable release
|
|
50
|
+
- Core nested uniqueness validation functionality
|
|
51
|
+
- Support for `:attribute`, `:scope`, `:case_sensitive`, `:message`, `:error_key` options
|
|
52
|
+
- Support for Rails 6.1+ and Ruby 2.7+
|
data/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2021-2025 Anton Maminov
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
# Validates Nested Uniqueness
|
|
2
|
+
|
|
3
|
+
[](https://github.com/mamantoha/validates_nested_uniqueness/actions/workflows/ruby.yml)
|
|
4
|
+
[](https://github.com/mamantoha/validates_nested_uniqueness/releases)
|
|
5
|
+
[](https://github.com/mamantoha/validates_nested_uniqueness/blob/main/LICENSE)
|
|
6
|
+
|
|
7
|
+
Validates whether associations are uniqueness when using `accepts_nested_attributes_for`.
|
|
8
|
+
|
|
9
|
+
Solves the original Rails issue [#20676](https://github.com/rails/rails/issues/20676).
|
|
10
|
+
|
|
11
|
+
This issue is very annoying and still open after years. And probably this will never be fixed.
|
|
12
|
+
|
|
13
|
+
This code is based on solutions proposed in the thread. Thanks everyone ❤️.
|
|
14
|
+
|
|
15
|
+
## Installation
|
|
16
|
+
|
|
17
|
+
`rails_validates_nested_uniqueness` works with Rails 7.2 onwards.
|
|
18
|
+
|
|
19
|
+
Add this to your Rails project's `Gemfile`:
|
|
20
|
+
|
|
21
|
+
```ruby
|
|
22
|
+
gem 'rails_validates_nested_uniqueness'
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Or install directly:
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
gem install rails_validates_nested_uniqueness
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Usage
|
|
32
|
+
|
|
33
|
+
Making sure that only one `city` of the `country` can be named "NY".
|
|
34
|
+
|
|
35
|
+
```ruby
|
|
36
|
+
class City < ActiveRecord::Base
|
|
37
|
+
belongs_to :country
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
class Country < ActiveRecord::Base
|
|
41
|
+
has_many :cities, dependent: :destroy
|
|
42
|
+
accepts_nested_attributes_for :cities, allow_destroy: true
|
|
43
|
+
|
|
44
|
+
validates :cities, nested_uniqueness: {
|
|
45
|
+
attribute: :name,
|
|
46
|
+
scope: [:country_id],
|
|
47
|
+
case_sensitive: false
|
|
48
|
+
}
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
country = Country.new(name: 'US', cities: [City.new(name: 'NY'), City.new(name: 'NY')])
|
|
52
|
+
country.save
|
|
53
|
+
# => false
|
|
54
|
+
|
|
55
|
+
country.errors
|
|
56
|
+
# => #<ActiveModel::Errors [#<ActiveModel::NestedError attribute=cities.name, type=taken, options={:value=>"NY", :message=>nil}>]>
|
|
57
|
+
|
|
58
|
+
country.errors.messages
|
|
59
|
+
# => {"cities.name"=>["has already been taken"]}
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## Advanced Usage
|
|
63
|
+
|
|
64
|
+
### Custom Comparison Logic
|
|
65
|
+
|
|
66
|
+
For more complex validation scenarios, you can provide custom comparison logic:
|
|
67
|
+
|
|
68
|
+
```ruby
|
|
69
|
+
validates :cities, nested_uniqueness: {
|
|
70
|
+
attribute: :name,
|
|
71
|
+
scope: [:country_id],
|
|
72
|
+
comparison: ->(value) { value.to_s.strip.downcase }
|
|
73
|
+
}
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
This is useful when you need to normalize values before comparison (e.g., trimming whitespace, handling special characters, etc.).
|
|
77
|
+
|
|
78
|
+
Configuration options:
|
|
79
|
+
|
|
80
|
+
- `:attribute` - (Required) Specify the attribute name of associated model to validate.
|
|
81
|
+
- `:scope` - One or more columns by which to limit the scope of the uniqueness constraint.
|
|
82
|
+
- `:case_sensitive` - Looks for an exact match. Ignored by non-text columns (`true` by default).
|
|
83
|
+
- `:message` - A custom error message (default is: "has already been taken").
|
|
84
|
+
- `:error_key` - A custom error key to use (default is: `:taken`).
|
|
85
|
+
- `:comparison` - A callable object (Proc/lambda) for custom value comparison logic.
|
|
86
|
+
|
|
87
|
+
## Sponsorship
|
|
88
|
+
|
|
89
|
+
This library is sponsored by [Faria Education Group](https://github.com/eduvo), where it was originally developed and utilized in a production project. It has been extracted and refined for open-source use.
|
|
90
|
+
|
|
91
|
+
## Contributing
|
|
92
|
+
|
|
93
|
+
1. Fork it (<https://github.com/mamantoha/validates_nested_uniqueness/fork>)
|
|
94
|
+
2. Create your feature branch (git checkout -b my-new-feature)
|
|
95
|
+
3. Commit your changes (git commit -am 'Add some feature')
|
|
96
|
+
4. Push to the branch (git push origin my-new-feature)
|
|
97
|
+
5. Create a new Pull Request
|
|
98
|
+
|
|
99
|
+
## Contributors
|
|
100
|
+
|
|
101
|
+
- [mamantoha](https://github.com/mamantoha) Anton Maminov - creator, maintainer
|
|
102
|
+
|
|
103
|
+
## License
|
|
104
|
+
|
|
105
|
+
Copyright: 2021-2025 Anton Maminov (anton.maminov@gmail.com)
|
|
106
|
+
|
|
107
|
+
This library is distributed under the MIT license. Please see the LICENSE file.
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'logger'
|
|
4
|
+
require 'active_model'
|
|
5
|
+
|
|
6
|
+
module ActiveRecord
|
|
7
|
+
module Validations
|
|
8
|
+
# ::nodoc
|
|
9
|
+
class NestedUniquenessValidator < ActiveModel::EachValidator
|
|
10
|
+
def initialize(options)
|
|
11
|
+
unless options[:attribute]
|
|
12
|
+
raise ArgumentError, ':attribute option is required. ' \
|
|
13
|
+
'Specify the attribute name to validate: `attribute: :name`'
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
unless Array(options[:scope]).all? { |scope| scope.respond_to?(:to_sym) }
|
|
17
|
+
raise ArgumentError, "#{options[:scope]} is not supported format for :scope option. " \
|
|
18
|
+
'Pass a symbol or an array of symbols instead: `scope: :user_id`'
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
if options[:comparison] && !options[:comparison].respond_to?(:call)
|
|
22
|
+
raise ArgumentError, ':comparison option must be a callable object (Proc or lambda)'
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
super
|
|
26
|
+
|
|
27
|
+
@attribute_name = options[:attribute]
|
|
28
|
+
@case_sensitive = options[:case_sensitive]
|
|
29
|
+
@scope = options[:scope] || []
|
|
30
|
+
@error_key = options[:error_key] || :taken
|
|
31
|
+
@message = options[:message] || nil
|
|
32
|
+
@comparison = options[:comparison]
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def validate_each(record, association_name, value)
|
|
36
|
+
return if value.blank? || !value.respond_to?(:reject)
|
|
37
|
+
|
|
38
|
+
track_values = Set.new
|
|
39
|
+
|
|
40
|
+
reflection = record.class.reflections[association_name.to_s]
|
|
41
|
+
return unless reflection
|
|
42
|
+
|
|
43
|
+
indexed_attribute = reflection.options[:index_errors] || ActiveRecord::Base.try(:index_nested_attribute_errors)
|
|
44
|
+
|
|
45
|
+
value.reject(&:marked_for_destruction?).select(&:changed_for_autosave?).each_with_index do |nested_value, index|
|
|
46
|
+
next unless nested_value.respond_to?(@attribute_name)
|
|
47
|
+
|
|
48
|
+
normalized_attribute = normalize_attribute(association_name, indexed_attribute:, index:)
|
|
49
|
+
|
|
50
|
+
track_value = build_track_value(nested_value)
|
|
51
|
+
|
|
52
|
+
if track_values.member?(track_value)
|
|
53
|
+
add_validation_error(record, nested_value, normalized_attribute)
|
|
54
|
+
else
|
|
55
|
+
track_values.add(track_value)
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
private
|
|
61
|
+
|
|
62
|
+
def build_track_value(nested_value)
|
|
63
|
+
track_value = @scope.each_with_object({}) do |scope_key, memo|
|
|
64
|
+
memo[scope_key] = nested_value.try(scope_key)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
attribute_value = nested_value.try(@attribute_name)
|
|
68
|
+
track_value[@attribute_name] = normalize_value(attribute_value)
|
|
69
|
+
track_value
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def normalize_value(value)
|
|
73
|
+
return value if value.nil?
|
|
74
|
+
return @comparison.call(value) if @comparison
|
|
75
|
+
return value if @case_sensitive != false
|
|
76
|
+
return value unless value.respond_to?(:downcase)
|
|
77
|
+
|
|
78
|
+
value.downcase
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def add_validation_error(record, nested_value, normalized_attribute)
|
|
82
|
+
inner_error = ActiveModel::Error.new(
|
|
83
|
+
nested_value,
|
|
84
|
+
@attribute_name,
|
|
85
|
+
@error_key,
|
|
86
|
+
value: nested_value[@attribute_name],
|
|
87
|
+
message: @message
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
error = ActiveModel::NestedError.new(record, inner_error, attribute: normalized_attribute)
|
|
91
|
+
record.errors.import(error)
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def normalize_attribute(association_name, indexed_attribute: false, index: nil)
|
|
95
|
+
if indexed_attribute
|
|
96
|
+
"#{association_name}[#{index}].#{@attribute_name}"
|
|
97
|
+
else
|
|
98
|
+
"#{association_name}.#{@attribute_name}"
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# :nodoc:
|
|
104
|
+
module ClassMethods
|
|
105
|
+
# Validates whether associations are uniqueness when using accepts_nested_attributes_for.
|
|
106
|
+
#
|
|
107
|
+
# This validator ensures that nested attributes maintain uniqueness constraints
|
|
108
|
+
# within the scope of their parent record and any additional specified scopes.
|
|
109
|
+
#
|
|
110
|
+
# @example Basic usage
|
|
111
|
+
# class Country < ActiveRecord::Base
|
|
112
|
+
# has_many :cities, dependent: :destroy
|
|
113
|
+
# accepts_nested_attributes_for :cities, allow_destroy: true
|
|
114
|
+
#
|
|
115
|
+
# validates :cities, nested_uniqueness: {
|
|
116
|
+
# attribute: :name,
|
|
117
|
+
# scope: [:country_id]
|
|
118
|
+
# }
|
|
119
|
+
# end
|
|
120
|
+
#
|
|
121
|
+
# @example With case-insensitive validation
|
|
122
|
+
# validates :cities, nested_uniqueness: {
|
|
123
|
+
# attribute: :name,
|
|
124
|
+
# scope: [:country_id],
|
|
125
|
+
# case_sensitive: false
|
|
126
|
+
# }
|
|
127
|
+
#
|
|
128
|
+
# @example With custom error message
|
|
129
|
+
# validates :cities, nested_uniqueness: {
|
|
130
|
+
# attribute: :name,
|
|
131
|
+
# scope: [:country_id],
|
|
132
|
+
# message: "must be unique within this country"
|
|
133
|
+
# }
|
|
134
|
+
#
|
|
135
|
+
# @example With custom comparison logic
|
|
136
|
+
# validates :cities, nested_uniqueness: {
|
|
137
|
+
# attribute: :name,
|
|
138
|
+
# scope: [:country_id],
|
|
139
|
+
# comparison: ->(value) { value.to_s.strip.downcase }
|
|
140
|
+
# }
|
|
141
|
+
#
|
|
142
|
+
# Configuration options:
|
|
143
|
+
# * <tt>:attribute</tt> - (Required) Specify the attribute name of associated model to validate.
|
|
144
|
+
# * <tt>:scope</tt> - One or more columns by which to limit the scope of
|
|
145
|
+
# the uniqueness constraint. Can be a symbol or array of symbols.
|
|
146
|
+
# * <tt>:case_sensitive</tt> - Looks for an exact match. Ignored by
|
|
147
|
+
# non-text columns (+true+ by default).
|
|
148
|
+
# * <tt>:message</tt> - A custom error message (default is: "has already been taken").
|
|
149
|
+
# * <tt>:error_key</tt> - A custom error key to use (default is: +:taken+).
|
|
150
|
+
# * <tt>:comparison</tt> - A callable object (Proc/lambda) for custom value comparison logic.
|
|
151
|
+
def validates_nested_uniqueness_of(*attr_names)
|
|
152
|
+
validates_with NestedUniquenessValidator, _merge_attributes(attr_names)
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: rails_validates_nested_uniqueness
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 1.3.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Anton Maminov
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: bin
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2025-11-13 00:00:00.000000000 Z
|
|
12
|
+
dependencies:
|
|
13
|
+
- !ruby/object:Gem::Dependency
|
|
14
|
+
name: activemodel
|
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
|
16
|
+
requirements:
|
|
17
|
+
- - ">="
|
|
18
|
+
- !ruby/object:Gem::Version
|
|
19
|
+
version: 7.2.0
|
|
20
|
+
type: :runtime
|
|
21
|
+
prerelease: false
|
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
+
requirements:
|
|
24
|
+
- - ">="
|
|
25
|
+
- !ruby/object:Gem::Version
|
|
26
|
+
version: 7.2.0
|
|
27
|
+
- !ruby/object:Gem::Dependency
|
|
28
|
+
name: activerecord
|
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
|
30
|
+
requirements:
|
|
31
|
+
- - ">="
|
|
32
|
+
- !ruby/object:Gem::Version
|
|
33
|
+
version: 7.2.0
|
|
34
|
+
type: :development
|
|
35
|
+
prerelease: false
|
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
37
|
+
requirements:
|
|
38
|
+
- - ">="
|
|
39
|
+
- !ruby/object:Gem::Version
|
|
40
|
+
version: 7.2.0
|
|
41
|
+
- !ruby/object:Gem::Dependency
|
|
42
|
+
name: rspec
|
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
|
44
|
+
requirements:
|
|
45
|
+
- - ">="
|
|
46
|
+
- !ruby/object:Gem::Version
|
|
47
|
+
version: 3.12.0
|
|
48
|
+
type: :development
|
|
49
|
+
prerelease: false
|
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
51
|
+
requirements:
|
|
52
|
+
- - ">="
|
|
53
|
+
- !ruby/object:Gem::Version
|
|
54
|
+
version: 3.12.0
|
|
55
|
+
description: Validates whether associations are uniqueness when using accepts_nested_attributes_for.
|
|
56
|
+
email: anton.maminov@gmail.com
|
|
57
|
+
executables: []
|
|
58
|
+
extensions: []
|
|
59
|
+
extra_rdoc_files: []
|
|
60
|
+
files:
|
|
61
|
+
- CHANGELOG.md
|
|
62
|
+
- LICENSE
|
|
63
|
+
- README.md
|
|
64
|
+
- lib/validates_nested_uniqueness.rb
|
|
65
|
+
homepage: https://github.com/mamantoha/validates_nested_uniqueness
|
|
66
|
+
licenses:
|
|
67
|
+
- MIT
|
|
68
|
+
metadata:
|
|
69
|
+
rubygems_mfa_required: 'true'
|
|
70
|
+
post_install_message:
|
|
71
|
+
rdoc_options: []
|
|
72
|
+
require_paths:
|
|
73
|
+
- lib
|
|
74
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
75
|
+
requirements:
|
|
76
|
+
- - ">="
|
|
77
|
+
- !ruby/object:Gem::Version
|
|
78
|
+
version: 3.2.0
|
|
79
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
80
|
+
requirements:
|
|
81
|
+
- - ">="
|
|
82
|
+
- !ruby/object:Gem::Version
|
|
83
|
+
version: '0'
|
|
84
|
+
requirements: []
|
|
85
|
+
rubygems_version: 3.5.9
|
|
86
|
+
signing_key:
|
|
87
|
+
specification_version: 4
|
|
88
|
+
summary: Rails library for validating nested uniqueness with accepts_nested_attributes_for.
|
|
89
|
+
test_files: []
|