scope uniqueness by ancestry? #72

Closed
JeanMertz opened this Issue Sep 8, 2011 · 8 comments

Projects

None yet

6 participants

@JeanMertz

In the old days I used to use parent_id as a column name instead of the ancestry gem. I also used this validation:

  validates :name,
    presence: true,
    uniqueness: { scope: [:website_id, :parent_id], case_sensitive: false },
    exclusion: { in: %w[login] },
    length: { within: 3..30 }

This way I could have the same name twice, just not for the same parent.

How would I go about doing this in ancestry? I tried using scope: [:website_id, :ancestry] but that doesn't work. Can I use some kind of Proc here?

@JeanMertz

I solved this by making a custom validation, in case anyone is interested (or has a better solution), here is my validator:

class PageUniquenessValidator < ActiveModel::EachValidator
  def validate_each(record, attribute, value)
     record.errors.add(attribute, :taken) if record.siblings.find_by_name_and_website_id(record.name, record.website_id)
  end
end
@JeanMertz JeanMertz closed this Sep 9, 2011
@Backoo
Backoo commented Nov 15, 2012

Is there a better solution for that? Maybe, this issue should be considered a bug.

@groe
groe commented May 21, 2013

Here is what I did:

class AncestryUniquenessValidator < ActiveModel::EachValidator
  def validate_each(record, attribute, value)
    scope_opts = options[:scope]
    scope_opts = [scope_opts] unless scope_opts.is_a?(Array)

    exclude_self = []
    exclude_self = ["id <> ?", record.id] if record.id

    scope = scope_opts.inject({}, ) do |s, attr|
      s.merge(
        { attr.to_sym => record.send(attr) }
      )
    end
    if record.siblings.where(exclude_self).where(scope.merge({attribute.to_sym => record.send(attribute)})).any?
      record.errors.add(attribute, options[:message] || :taken)
    end
  end
end

Use like this:

  validates :name, :ancestry_uniqueness => {:scope => :project_id}
@flauwekeul

I used @groe 's excellent AncestryUniquenessValidator but ran into a problem where the options hash was empty (in Rails 4 at least, but presumably in >= Rails 3). I solved it by inheriting from ActiveRecord::Validations::UniquenessValidator.

class AncestryUniquenessValidator < ActiveRecord::Validations::UniquenessValidator

  def validate_each(record, attribute, value)
    exclude_self = record.try(:id) ? ['id <> ?', record.id] : []
    scope_opts = [*options[:scope]]

    scope = scope_opts.inject({}) do |s, attr|
      s.merge({attr.to_sym => record.send(attr)})
    end

    if record.siblings.where(exclude_self).where(scope.merge({attribute.to_sym => record.send(attribute)})).any?
      record.errors.add(attribute, options[:message] || :taken)
    end
  end

end

I wrote some specs for it too :)

I guess a simpler AncestryUniquenessValidator could be made based on ActiveRecord::Validations::UniquenessValidator's (helper) methods. But this works too.

@nathanaelkane

Works great @flauwekeul, would be handy extracted out into a gem.

@flauwekeul

I'll do that!

@lilfaf
lilfaf commented Jan 10, 2014

@flauwekeul 👍 for the gem

@flauwekeul

I just pushed the first version of the gem ancestry_uniqueness to RubyGems. Please open an issue if you encounter any.

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