Skip to content

Instantly share code, notes, and snippets.

@inopinatus
Last active July 12, 2018 05:32
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save inopinatus/f2996da8577f903668f25bdad476f97c to your computer and use it in GitHub Desktop.
Save inopinatus/f2996da8577f903668f25bdad476f97c to your computer and use it in GitHub Desktop.
Solve unique validation problem within nested simultaneous destruction and creation
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
@inopinatus
Copy link
Author

inopinatus commented Jun 21, 2018

Example use

class Account < ApplicationRecord
  has_many :profiles
  accepts_nested_attributes_for :profiles

class Profile < ApplicationRecord
  belongs_to :account
  validates :email, unique_collected: { scope: :account, within: :profiles }

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment