Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add options for uniqueness validations. Resolves #319 #333

Open
wants to merge 1 commit into
base: core
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,30 @@ Client.restore(id, :recursive => true)
client.restore(:recursive => true)
```

If you want to validate uniqueness but want to include deleted records:

``` ruby
class Client < ActiveRecord::Base
acts_as_paranoid

validates :name, uniqueness: { paranoia: :with_deleted }
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't custom paranoid gem.You can write:
validates :name, uniqueness: {conditions: ->{with_deleted}}


...
end
```

If you don't use the default_scope and want to validate uniqueness but want to exclude deleted records:

``` ruby
class Client < ActiveRecord::Base
acts_as_paranoid without_default_scope: true

validates :name, uniqueness: { paranoia: :without_deleted }
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't custom paranoid gem.You can write:
validates :name, uniqueness: {conditions: ->{with_deleted}}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't custom paranoid gem.You can write:
validates :name, uniqueness: {conditions: ->{with_deleted}}

Doesn't work for me

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doesn't work for me either in rails v4.2.10 using MySQL. Traced it a bit and I found that rails wasn't actually removing the scope via the unscope method because of the way the relation query is set up. By the time it hits here, the relation is wrapped and chained with an AND query. It doesn't get through this case statement to actually remove the column from the scope because the relation rel is a Arel::Nodes::And at that point.

I just ended up making my own validator which manually checked for uniqueness against the unscoped records to move forward.


...
end
```

For more information, please look at the tests.

#### About indexes:
Expand Down
9 changes: 7 additions & 2 deletions lib/paranoia.rb
Original file line number Diff line number Diff line change
Expand Up @@ -212,16 +212,17 @@ def self.acts_as_paranoid(options={})
alias_method :destroy_without_paranoia, :destroy

include Paranoia
class_attribute :paranoia_column, :paranoia_sentinel_value
class_attribute :paranoia_column, :paranoia_sentinel_value, :paranoid_by_default

self.paranoid_by_default = !options[:without_default_scope]
self.paranoia_column = (options[:column] || :deleted_at).to_s
self.paranoia_sentinel_value = options.fetch(:sentinel_value) { Paranoia.default_sentinel_value }
def self.paranoia_scope
where(paranoia_column => paranoia_sentinel_value)
end
class << self; alias_method :without_deleted, :paranoia_scope end

unless options[:without_default_scope]
if paranoid_by_default
default_scope { paranoia_scope }
end

Expand Down Expand Up @@ -266,6 +267,10 @@ module UniquenessParanoiaValidator
def build_relation(klass, table, attribute, value)
relation = super(klass, table, attribute, value)
return relation unless klass.respond_to?(:paranoia_column)

add_paranoia_scope = (klass.paranoid_by_default && options[:paranoia] != :with_deleted) || (!klass.paranoid_by_default && options[:paranoia] == :without_deleted)
return relation unless add_paranoia_scope

arel_paranoia_scope = klass.arel_table[klass.paranoia_column].eq(klass.paranoia_sentinel_value)
if ActiveRecord::VERSION::STRING >= "5.0"
relation.where(arel_paranoia_scope)
Expand Down
36 changes: 36 additions & 0 deletions test/paranoia_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,24 @@ def test_active_column_model_with_uniqueness_validation_still_works_on_non_delet
refute b.valid?
end

def test_uniqueness_with_deleted
a = ParanoidWithUniquenessWithDeleted.create!(name: "A")
b = ParanoidWithUniquenessWithDeleted.new(name: "A")
refute b.valid?
end

def test_uniqueness_without_default_scope_with_deleted
a = ParanoidWithoutDefaultScopeWithUniqueness.create!(name: "A")
b = ParanoidWithoutDefaultScopeWithUniqueness.new(name: "A")
refute b.valid?
end

def test_uniqueness_without_default_scope_without_deleted
a = ParanoidWithoutDefaultScopeWithUniquenessWithoutDeleted.create!(name: "A")
b = ParanoidWithoutDefaultScopeWithUniquenessWithoutDeleted.new(name: "A")
refute b.valid?
end

def test_sentinel_value_for_custom_sentinel_models
model = CustomSentinelModel.new
assert_equal 0, model.class.count
Expand Down Expand Up @@ -1105,6 +1123,24 @@ def paranoia_destroy_attributes
end
end

class ParanoidWithUniquenessWithDeleted < ActiveRecord::Base
self.table_name = 'featureful_models'
acts_as_paranoid
validates :name, uniqueness: { paranoia: :with_deleted }
end

class ParanoidWithoutDefaultScopeWithUniqueness < ActiveRecord::Base
self.table_name = 'featureful_models'
acts_as_paranoid without_default_scope: true
validates :name, uniqueness: true
end

class ParanoidWithoutDefaultScopeWithUniquenessWithoutDeleted < ActiveRecord::Base
self.table_name = 'featureful_models'
acts_as_paranoid
validates :name, uniqueness: { paranoia: :without_deleted }
end

class NonParanoidModel < ActiveRecord::Base
end

Expand Down