Last active
July 12, 2018 05:32
-
-
Save inopinatus/f2996da8577f903668f25bdad476f97c to your computer and use it in GitHub Desktop.
Solve unique validation problem within nested simultaneous destruction and creation
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class UniqueCollectedValidator < ActiveRecord::Validations::UniquenessValidator | |
def initialize(options) | |
unless options[:within].present? | |
raise ArgumentError, ":within option is required to determine the unique scope's collection." | |
end | |
unless Array(options[:within]).all? { |within| within.respond_to?(:to_sym) } | |
raise ArgumentError, "#{options[:within]} is not a supported format for the :within option. " \ | |
"Pass a symbol or an array of symbols instead: `within: :profiles`" | |
end | |
super | |
end | |
# Adds an additional constraint on the database query to exclude any siblings | |
# marked for destruction. | |
def scope_relation(record, relation) | |
relation = super | |
Array(options[:scope]).each do |scope_item| | |
scope_value = record.association(scope_item).reader | |
inverse_association = options[:within] | |
scope_collection = scope_value.association(inverse_association).reader | |
dead_siblings = scope_collection.grep(:marked_for_destruction?.to_proc, &:id_in_database) | |
if dead_siblings.present? | |
finder_class = find_finder_class_for(record) | |
relation = relation.where.not(finder_class.primary_key => dead_siblings) | |
end | |
end | |
relation | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Example use
Now an update with nested attributes can simultaneously destroy and create a profile with the same email address.
Caveat: depends on Rails internal API. Tested with Rails 5.2 only.