Skip to content

Instantly share code, notes, and snippets.

@dchandekstark
Last active August 29, 2015 14:01
Show Gist options
  • Save dchandekstark/f969ad21bf518c7cd3c5 to your computer and use it in GitHub Desktop.
Save dchandekstark/f969ad21bf518c7cd3c5 to your computer and use it in GitHub Desktop.
UniquenessValidator for ActiveFedora

Notes

  • Modeled after ActiveRecord::Validations::UniquenessValidator
  • Only supports uniqueness for attributes, not associations.
  • Because indexing can vary, only supports one attribute at a time. Defaults to the :symbol indexer (*_ssim). Index field can be declared explicitly with :index_field option, or implicitly with :index_type and :data_type options.
  • Designed for single-valued attributes, i.e., :multiple => false. Should work for multi-valued attributes having single value; may work (perhaps not exactly as desired) for attributes having multiple values.
  • Does not support scoping or extra conditions.

Instructions

  • Save uniqueness_validator.rb in app/validators/.
  • If you want the validates_uniqueness_of convenience method, include the Validations mixin in your model.

Example

See the spec test module.

class UniquenessValidator < ActiveModel::EachValidator
def initialize(options = {})
if options[:attributes].length > 1
raise ArgumentError, "UniquenessValidator accepts only a single attribute"
end
super
@index_field = options[:index_field]
# :index_type and :data_type options are ignored if :index_field is present
unless @index_field
@index_type = options[:index_type] || :symbol
@data_type = options[:data_type]
end
end
def validate_each(record, attribute, value)
conditions = {index_field(attribute) => value}
conditions.merge!("-id" => record.id) if record.persisted?
# Should be able to use record.class.exists?(conditions)
# but ActiveFedora method only supports PIDs, not hash conditions
# https://github.com/projecthydra/active_fedora/issues/427
if record.class.where(conditions).first
record.errors.add attribute, "has already been taken"
end
end
private
def index_field(attribute)
return @index_field if @index_field
args = [attribute, @index_type]
args << {type: @data_type} if @data_type
ActiveFedora::SolrService.solr_name(*args)
end
end
module Validations
extend ActiveSupport::Concern
module ClassMethods
def validates_uniqueness_of *attr_names
options = attr_names.extract_options!
options.merge!(attributes: attr_names)
validates_with UniquenessValidator, options
end
end
end
require 'spec_helper'
describe Validations do
before(:all) do
class Validatable < ActiveFedora::Base
include DulHydra::Validations
has_metadata name: 'descMetadata', type: ActiveFedora::QualifiedDublinCoreDatastream
has_attributes :title, datastream: 'descMetadata', multiple: false
end
@obj = Validatable.new(pid: "foobar:1", title: "I am Validatable")
end
describe "validating uniqueness" do
let(:taken) { double("another record") }
before(:all) { Validatable.validates_uniqueness_of :title, index_type: :stored_sortable }
after(:all) { Validatable.clear_validators! }
context "on a new record" do
context "when the value is not taken" do
before { allow(Validatable).to receive(:where).with("title_ssi" => "I am Validatable") { [] } }
it "should be valid" do
expect(@obj).to be_valid
end
end
context "when the value is taken" do
before { allow(Validatable).to receive(:where).with("title_ssi" => "I am Validatable") { [taken] } }
it "should not be valid" do
expect(@obj).not_to be_valid
end
end
end
context "on a persisted record" do
before { allow(@obj).to receive(:persisted?) { true } }
context "when the value is not taken" do
before do
allow(Validatable).to receive(:where).with("title_ssi" => "I am Validatable", "-id" => "foobar:1") { [] }
end
it "should be valid" do
expect(@obj).to be_valid
end
end
context "when the value is taken by another record" do
before do
allow(Validatable).to receive(:where).with("title_ssi" => "I am Validatable", "-id" => "foobar:1") { [taken] }
end
it "should not be valid" do
expect(@obj).not_to be_valid
end
end
end
end
end
@cjcolvar
Copy link

You should use the predefined :taken error message instead of hard-coding the error string. We did this based upon the activerecord uniqueness validator:
https://github.com/avalonmediasystem/avalon/blob/develop/app/validators/uniqueness_validator.rb#L28
https://github.com/rails/rails/blob/master/activerecord/lib/active_record/validations/uniqueness.rb#L25-L28

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