dedupe 0.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.document +11 -0
- data/.gitignore +15 -0
- data/.rspec +2 -0
- data/.rvmrc +5 -0
- data/Gemfile +15 -0
- data/Gemfile.lock +62 -0
- data/LICENSE +20 -0
- data/README.rdoc +85 -0
- data/Rakefile +45 -0
- data/VERSION +1 -0
- data/lib/dedupe.rb +30 -0
- data/lib/dedupe/errors.rb +6 -0
- data/lib/dedupe/orm/active_record.rb +31 -0
- data/lib/dedupe/orm/mongoid.rb +31 -0
- data/lib/dedupe/validations.rb +18 -0
- data/spec/dedupe_spec.rb +148 -0
- data/spec/spec_helper.rb +17 -0
- data/spec/support/active_record.rb +35 -0
- data/spec/support/mongoid.rb +40 -0
- metadata +239 -0
data/.document
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
# .document is used by rdoc and yard to know how to generate documentation
|
2
|
+
# for example, it can be used to control how rdoc gets built when you do `gem install foo`
|
3
|
+
|
4
|
+
README.rdoc
|
5
|
+
lib/**/*.rb
|
6
|
+
bin/*
|
7
|
+
|
8
|
+
# Files below this - are treated as 'extra files', and aren't parsed for ruby code
|
9
|
+
-
|
10
|
+
features/**/*.feature
|
11
|
+
LICENSE
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.rvmrc
ADDED
data/Gemfile
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
source "http://rubygems.org"
|
2
|
+
|
3
|
+
gem "activesupport", "~> 3.0.0"
|
4
|
+
gem "activemodel", "~> 3.0.0"
|
5
|
+
|
6
|
+
group :development do
|
7
|
+
gem "rspec", ">= 2.0.0.beta.19"
|
8
|
+
gem "bundler", "~> 1.0.0"
|
9
|
+
gem "jeweler", "~> 1.5.0.pre3"
|
10
|
+
gem "rcov", ">= 0"
|
11
|
+
gem "mongoid", "2.0.0.beta.19"
|
12
|
+
gem "bson_ext", "1.0.9"
|
13
|
+
gem "activerecord", "~> 3.0.0"
|
14
|
+
gem "sqlite3-ruby", "~> 1.3.1"
|
15
|
+
end
|
data/Gemfile.lock
ADDED
@@ -0,0 +1,62 @@
|
|
1
|
+
GEM
|
2
|
+
remote: http://rubygems.org/
|
3
|
+
specs:
|
4
|
+
activemodel (3.0.0)
|
5
|
+
activesupport (= 3.0.0)
|
6
|
+
builder (~> 2.1.2)
|
7
|
+
i18n (~> 0.4.1)
|
8
|
+
activerecord (3.0.0)
|
9
|
+
activemodel (= 3.0.0)
|
10
|
+
activesupport (= 3.0.0)
|
11
|
+
arel (~> 1.0.0)
|
12
|
+
tzinfo (~> 0.3.23)
|
13
|
+
activesupport (3.0.0)
|
14
|
+
arel (1.0.1)
|
15
|
+
activesupport (~> 3.0.0)
|
16
|
+
bson (1.0.9)
|
17
|
+
bson_ext (1.0.9)
|
18
|
+
builder (2.1.2)
|
19
|
+
diff-lcs (1.1.2)
|
20
|
+
git (1.2.5)
|
21
|
+
i18n (0.4.1)
|
22
|
+
jeweler (1.5.0.pre3)
|
23
|
+
bundler (~> 1.0.0)
|
24
|
+
git (>= 1.2.5)
|
25
|
+
rake
|
26
|
+
mongo (1.0.9)
|
27
|
+
bson (>= 1.0.5)
|
28
|
+
mongoid (2.0.0.beta.19)
|
29
|
+
activemodel (~> 3.0)
|
30
|
+
mongo (= 1.0.9)
|
31
|
+
tzinfo (~> 0.3.22)
|
32
|
+
will_paginate (~> 3.0.pre)
|
33
|
+
rake (0.8.7)
|
34
|
+
rcov (0.9.9)
|
35
|
+
rspec (2.0.0.beta.22)
|
36
|
+
rspec-core (= 2.0.0.beta.22)
|
37
|
+
rspec-expectations (= 2.0.0.beta.22)
|
38
|
+
rspec-mocks (= 2.0.0.beta.22)
|
39
|
+
rspec-core (2.0.0.beta.22)
|
40
|
+
rspec-expectations (2.0.0.beta.22)
|
41
|
+
diff-lcs (>= 1.1.2)
|
42
|
+
rspec-mocks (2.0.0.beta.22)
|
43
|
+
rspec-core (= 2.0.0.beta.22)
|
44
|
+
rspec-expectations (= 2.0.0.beta.22)
|
45
|
+
sqlite3-ruby (1.3.1)
|
46
|
+
tzinfo (0.3.23)
|
47
|
+
will_paginate (3.0.pre2)
|
48
|
+
|
49
|
+
PLATFORMS
|
50
|
+
ruby
|
51
|
+
|
52
|
+
DEPENDENCIES
|
53
|
+
activemodel (~> 3.0.0)
|
54
|
+
activerecord (~> 3.0.0)
|
55
|
+
activesupport (~> 3.0.0)
|
56
|
+
bson_ext (= 1.0.9)
|
57
|
+
bundler (~> 1.0.0)
|
58
|
+
jeweler (~> 1.5.0.pre3)
|
59
|
+
mongoid (= 2.0.0.beta.19)
|
60
|
+
rcov
|
61
|
+
rspec (>= 2.0.0.beta.19)
|
62
|
+
sqlite3-ruby (~> 1.3.1)
|
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2010 Brent Hargrave
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.rdoc
ADDED
@@ -0,0 +1,85 @@
|
|
1
|
+
= dedupe
|
2
|
+
|
3
|
+
|
4
|
+
== Description
|
5
|
+
|
6
|
+
Dedupe provides Mongoid and ActiveRecord 3 with a uniqueness validation that protects your datastore from duplicate records, defined by your domain.
|
7
|
+
|
8
|
+
The following model defines a uniqueness constraint:
|
9
|
+
|
10
|
+
class Model
|
11
|
+
include Mongoid::Document
|
12
|
+
|
13
|
+
field :foo
|
14
|
+
field :bar
|
15
|
+
field :baz
|
16
|
+
|
17
|
+
validates_uniqueness :using => :with_matching_foo_and_bar
|
18
|
+
scope :with_matching_foo_and_bar, lambda { |instance|
|
19
|
+
where :foo => instance.foo, :bar => instance.bar
|
20
|
+
# our hypothetical domain requires that we disregard :baz
|
21
|
+
}
|
22
|
+
|
23
|
+
end
|
24
|
+
|
25
|
+
Any attempt to save a record with the same "foo" and "bar" values will fail.
|
26
|
+
|
27
|
+
> existing = Model.create! :foo => "a", :bar => "b", :baz => "c"
|
28
|
+
=> #<Model _id: 123abc, foo: "a", bar: "b", baz: "c">
|
29
|
+
|
30
|
+
> duplicate = Model.new :foo => "a", :bar => "b", :baz => "z"
|
31
|
+
=> #<Model _id: 456def, foo: "a", bar: "b", baz: "z">
|
32
|
+
|
33
|
+
> duplicate.valid?
|
34
|
+
=> false
|
35
|
+
|
36
|
+
> duplicate.errors.full_messages
|
37
|
+
=> ["Duplicates existing record"]
|
38
|
+
|
39
|
+
> duplicate.save!
|
40
|
+
Mongoid::Errors::Validations: Validation failed...
|
41
|
+
|
42
|
+
The gem also provides a few other methods, possibly useful for cleaning up datastores already corrupted with duplicate records.
|
43
|
+
|
44
|
+
> duplicate.duplicate?
|
45
|
+
=> true
|
46
|
+
|
47
|
+
> duplicate.duplicates.to_a
|
48
|
+
=> [#<Model _id: 123abc, foo: "a", bar: "b", baz: "c">]
|
49
|
+
|
50
|
+
> existing.duplicates.to_a
|
51
|
+
=> []
|
52
|
+
|
53
|
+
|
54
|
+
== Installation & Usage
|
55
|
+
|
56
|
+
Add the gem to your Gemfile:
|
57
|
+
gem 'dedupe'
|
58
|
+
|
59
|
+
Add a scope or "finder" method to your model that a) accepts an instance of your model and b) returns a relation/criterion that would select all duplicate instances in your datastore. In the above example, this method is:
|
60
|
+
scope :with_matching_foo_and_bar, lambda { |instance|
|
61
|
+
where :foo => instance.foo, :baz => instance.baz
|
62
|
+
}
|
63
|
+
|
64
|
+
But you could also define:
|
65
|
+
def with_matching_foo_and_bar(instance)
|
66
|
+
where :foo => instance.foo, :baz => instance.baz
|
67
|
+
end
|
68
|
+
|
69
|
+
Then, mix the Dedupe module into your model using the "validates_uniqueness" macro, passing it the name of your scope/finder:
|
70
|
+
validates_uniqueness :using => :with_matching_foo_and_bar
|
71
|
+
|
72
|
+
|
73
|
+
== Note on Patches/Pull Requests
|
74
|
+
|
75
|
+
* Fork the project.
|
76
|
+
* Make your feature addition or bug fix.
|
77
|
+
* Add tests for it. This is important so I don't break it in a
|
78
|
+
future version unintentionally.
|
79
|
+
* Commit, do not mess with rakefile, version, or history.
|
80
|
+
(if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
|
81
|
+
* Send me a pull request. Bonus points for topic branches.
|
82
|
+
|
83
|
+
== Copyright
|
84
|
+
|
85
|
+
Copyright (c) 2010 Brent Hargrave. See LICENSE for details.
|
data/Rakefile
ADDED
@@ -0,0 +1,45 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'bundler'
|
3
|
+
begin
|
4
|
+
Bundler.setup(:default, :development)
|
5
|
+
rescue Bundler::BundlerError => e
|
6
|
+
$stderr.puts e.message
|
7
|
+
$stderr.puts "Run `bundle install` to install missing gems"
|
8
|
+
exit e.status_code
|
9
|
+
end
|
10
|
+
require 'rake'
|
11
|
+
|
12
|
+
require 'jeweler'
|
13
|
+
Jeweler::Tasks.new do |gem|
|
14
|
+
# gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
|
15
|
+
gem.name = "dedupe"
|
16
|
+
gem.summary = %Q{ActiveModel validation against duplicating records.}
|
17
|
+
gem.description = %Q{ActiveModel validation against duplicating records.}
|
18
|
+
gem.email = "brent.hargrave@gmail.com"
|
19
|
+
gem.homepage = "http://github.com/jamescallmebrent/dedupe"
|
20
|
+
gem.authors = ["Brent Hargrave"]
|
21
|
+
end
|
22
|
+
Jeweler::RubygemsDotOrgTasks.new
|
23
|
+
|
24
|
+
require 'rspec/core'
|
25
|
+
require 'rspec/core/rake_task'
|
26
|
+
RSpec::Core::RakeTask.new(:spec) do |spec|
|
27
|
+
spec.pattern = FileList['spec/**/*_spec.rb']
|
28
|
+
end
|
29
|
+
|
30
|
+
RSpec::Core::RakeTask.new(:rcov) do |spec|
|
31
|
+
spec.pattern = 'spec/**/*_spec.rb'
|
32
|
+
spec.rcov = true
|
33
|
+
end
|
34
|
+
|
35
|
+
task :default => :spec
|
36
|
+
|
37
|
+
require 'rake/rdoctask'
|
38
|
+
Rake::RDocTask.new do |rdoc|
|
39
|
+
version = File.exist?('VERSION') ? File.read('VERSION') : ""
|
40
|
+
|
41
|
+
rdoc.rdoc_dir = 'rdoc'
|
42
|
+
rdoc.title = "dedupe #{version}"
|
43
|
+
rdoc.rdoc_files.include('README*')
|
44
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
45
|
+
end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.0.0
|
data/lib/dedupe.rb
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'dedupe/errors'
|
2
|
+
require 'dedupe/validations'
|
3
|
+
require 'dedupe/orm/mongoid'
|
4
|
+
require 'dedupe/orm/active_record'
|
5
|
+
|
6
|
+
module Dedupe
|
7
|
+
extend ActiveSupport::Concern
|
8
|
+
|
9
|
+
included do
|
10
|
+
cattr_accessor :dedupe_scope_name
|
11
|
+
end
|
12
|
+
|
13
|
+
module ClassMethods
|
14
|
+
|
15
|
+
def validates_uniqueness(options={})
|
16
|
+
self.dedupe_scope_name = options.delete :using
|
17
|
+
if defined?(Mongoid::Document) && self.new.is_a?(Mongoid::Document)
|
18
|
+
self.send :include, Orm::Mongoid
|
19
|
+
elsif defined?(ActiveRecord::Base) && self.new.is_a?(ActiveRecord::Base)
|
20
|
+
self.send :include, Orm::ActiveRecord
|
21
|
+
end
|
22
|
+
self.send :include, Validations
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
|
29
|
+
Mongoid::Document.send :include, Dedupe if defined? Mongoid::Document
|
30
|
+
ActiveRecord::Base.send :include, Dedupe if defined? ActiveRecord::Base
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module Dedupe
|
2
|
+
module Orm
|
3
|
+
module ActiveRecord
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
|
6
|
+
included do
|
7
|
+
scope :excluding, lambda { |instance|
|
8
|
+
where('id <> ?', instance.id.to_i)
|
9
|
+
}
|
10
|
+
end
|
11
|
+
|
12
|
+
module InstanceMethods
|
13
|
+
include Errors
|
14
|
+
|
15
|
+
def duplicates
|
16
|
+
klass, scope_name = self.class, self.class.dedupe_scope_name
|
17
|
+
raise MissingScope unless klass.respond_to? scope_name
|
18
|
+
result_set = klass.send scope_name, self
|
19
|
+
raise InvalidScope unless result_set.is_a? ::ActiveRecord::Relation
|
20
|
+
result_set.excluding self
|
21
|
+
end
|
22
|
+
|
23
|
+
def duplicate?
|
24
|
+
self.duplicates.present?
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module Dedupe
|
2
|
+
module Orm
|
3
|
+
module Mongoid
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
|
6
|
+
included do
|
7
|
+
scope :excluding, lambda { |instance|
|
8
|
+
excludes(:id => instance.id)
|
9
|
+
}
|
10
|
+
end
|
11
|
+
|
12
|
+
module InstanceMethods
|
13
|
+
include Errors
|
14
|
+
|
15
|
+
def duplicates
|
16
|
+
klass, scope_name = self.class, self.class.dedupe_scope_name
|
17
|
+
raise MissingScope unless klass.respond_to? scope_name
|
18
|
+
result_set = klass.send scope_name, self
|
19
|
+
raise InvalidScope unless result_set.is_a? ::Mongoid::Criteria
|
20
|
+
result_set.excluding self
|
21
|
+
end
|
22
|
+
|
23
|
+
def duplicate?
|
24
|
+
self.duplicates.present?
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module Dedupe
|
2
|
+
module Validations
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
included do
|
6
|
+
validates_with RecordUniquenessValidator
|
7
|
+
end
|
8
|
+
|
9
|
+
class RecordUniquenessValidator < ActiveModel::Validator
|
10
|
+
def validate(record)
|
11
|
+
if record.duplicate?
|
12
|
+
record.errors.add :base, "Duplicates existing record"
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
18
|
+
end
|
data/spec/dedupe_spec.rb
ADDED
@@ -0,0 +1,148 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
2
|
+
|
3
|
+
shared_examples_for "any Dedupe helper method" do
|
4
|
+
it "should raise error if validates_uniqueness omitted" do
|
5
|
+
instance ||= @macroless_model.new
|
6
|
+
lambda do
|
7
|
+
instance.send(@method_name)
|
8
|
+
end.should raise_error(NoMethodError)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
shared_examples_for "any Dedupe instance method" do
|
12
|
+
it "should raise error if scope omitted" do
|
13
|
+
instance = @scopeless_model.new
|
14
|
+
lambda do
|
15
|
+
instance.send(@method_name)
|
16
|
+
end.should raise_error(Dedupe::Errors::MissingScope)
|
17
|
+
end
|
18
|
+
it "should raise error if scope returns a non-criterion" do
|
19
|
+
instance = @invalid_scope_model.new
|
20
|
+
lambda do
|
21
|
+
instance.send(@method_name)
|
22
|
+
end.should raise_error(Dedupe::Errors::InvalidScope)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
shared_examples_for "any supported ORM" do
|
27
|
+
context "finding" do
|
28
|
+
context "all but a provided instance (excluding)" do
|
29
|
+
before do
|
30
|
+
@existing = @model.create!(:foo => 'a', :bar => 'b')
|
31
|
+
@others = []
|
32
|
+
@others << @model.create!(:foo => 'd', :bar => 'e')
|
33
|
+
@others << @model.create!(:foo => 'g', :bar => 'g')
|
34
|
+
@found = @model.excluding(@existing).to_a
|
35
|
+
end
|
36
|
+
it "should not include that instance" do
|
37
|
+
@found.should_not include(@existing)
|
38
|
+
end
|
39
|
+
it "should include all other instances" do
|
40
|
+
@others.all? { |instance| @found.include?(instance) }.should be_true
|
41
|
+
end
|
42
|
+
it "should raise error if validates_uniqueness omitted" do
|
43
|
+
lambda do
|
44
|
+
@macroless_model.excluding
|
45
|
+
end.should raise_error(NoMethodError)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
context 'duplicates of a provided instance (dev-defined scope/finder)' do
|
49
|
+
before do
|
50
|
+
@existing = @model.create! :foo => 'foo', :bar => 'bar'
|
51
|
+
@unique_bar = @model.create! :foo => 'foo', :bar => 'unique'
|
52
|
+
@unique_foo = @model.create! :foo => 'unique', :bar => 'bar'
|
53
|
+
@duplicate = @model.new :foo => 'foo', :bar => 'bar'
|
54
|
+
@found = @model.with_attributes_of(@duplicate).to_a
|
55
|
+
end
|
56
|
+
it 'should exclude instances with only *some* duplicate attributes' do
|
57
|
+
@found.should_not include(@unique_foo)
|
58
|
+
@found.should_not include(@unique_bar)
|
59
|
+
end
|
60
|
+
it 'should include instances with duplicate attributes' do
|
61
|
+
@found.should include(@existing)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
context "instance" do
|
66
|
+
context "duplicates" do
|
67
|
+
before do
|
68
|
+
@method_name = "duplicates"
|
69
|
+
@existing = @model.create! :foo => 'a', :bar => 'b'
|
70
|
+
@other = @model.create! :foo => 'c', :bar => 'd'
|
71
|
+
@proposed = @model.new :foo => 'a', :bar => 'b'
|
72
|
+
end
|
73
|
+
it "should include its duplicate" do
|
74
|
+
@proposed.duplicates.should include(@existing)
|
75
|
+
end
|
76
|
+
it "should exclude non-duplicates" do
|
77
|
+
@proposed.duplicates.should_not include(@other)
|
78
|
+
end
|
79
|
+
it "should exclude itself" do
|
80
|
+
@existing.duplicates.should_not include(@existing)
|
81
|
+
end
|
82
|
+
it_should_behave_like "any Dedupe helper method"
|
83
|
+
it_should_behave_like "any Dedupe instance method"
|
84
|
+
end
|
85
|
+
context "duplicate?" do
|
86
|
+
before do
|
87
|
+
@method_name = "duplicate?"
|
88
|
+
@existing = @model.create! :foo => 'a', :bar => 'b'
|
89
|
+
@duplicate = @model.new :foo => 'a', :bar => 'b'
|
90
|
+
@non_duplicate = @model.new :foo => 'c', :bar => 'd'
|
91
|
+
end
|
92
|
+
it "duplicate? should be true if it has duplicates" do
|
93
|
+
@duplicate.should be_duplicate
|
94
|
+
end
|
95
|
+
it "duplicate? should be false if it lacks duplicates" do
|
96
|
+
@non_duplicate.should_not be_duplicate
|
97
|
+
end
|
98
|
+
# shared behavior?
|
99
|
+
it_should_behave_like "any Dedupe helper method"
|
100
|
+
it_should_behave_like "any Dedupe instance method"
|
101
|
+
end
|
102
|
+
context "validation (valid?)" do
|
103
|
+
before do
|
104
|
+
@method_name = "valid?"
|
105
|
+
@existing = @model.create! :foo => 'a', :bar => 'b'
|
106
|
+
@duplicate = @model.new :foo => 'a', :bar => 'b'
|
107
|
+
@non_duplicate = @model.new :foo => 'c', :bar => 'd'
|
108
|
+
end
|
109
|
+
it "with unique attributes should be true" do
|
110
|
+
@non_duplicate.should be_valid
|
111
|
+
end
|
112
|
+
context "with duplicate attributes" do
|
113
|
+
it "should be false" do
|
114
|
+
@duplicate.should_not be_valid
|
115
|
+
end
|
116
|
+
it "should add an error" do
|
117
|
+
@duplicate.valid? # call valid
|
118
|
+
@duplicate.errors[:base].should include "Duplicates existing record"
|
119
|
+
end
|
120
|
+
end
|
121
|
+
it_should_behave_like "any Dedupe instance method"
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
describe "Mongoid model" do
|
127
|
+
before do
|
128
|
+
@macroless_model = MongoidMacrolessModel
|
129
|
+
@scopeless_model = MongoidScopelessModel
|
130
|
+
@invalid_scope_model = MongoidInvalidScopeModel
|
131
|
+
@model = MongoidModel
|
132
|
+
end
|
133
|
+
it_should_behave_like "any supported ORM"
|
134
|
+
end
|
135
|
+
|
136
|
+
describe "ActiveRecord model" do
|
137
|
+
before do
|
138
|
+
@macroless_model = ActiveRecordMacrolessModel
|
139
|
+
@macroless_model.destroy_all
|
140
|
+
@scopeless_model = ActiveRecordScopelessModel
|
141
|
+
@scopeless_model.destroy_all
|
142
|
+
@invalid_scope_model = ActiveRecordInvalidScopeModel
|
143
|
+
@invalid_scope_model.destroy_all
|
144
|
+
@model = ActiveRecordModel
|
145
|
+
@model.destroy_all
|
146
|
+
end
|
147
|
+
it_should_behave_like "any supported ORM"
|
148
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'bundler'
|
2
|
+
begin
|
3
|
+
Bundler.setup(:default, :development)
|
4
|
+
rescue Bundler::BundlerError => e
|
5
|
+
$stderr.puts e.message
|
6
|
+
$stderr.puts "Run `bundle install` to install missing gems"
|
7
|
+
exit e.status_code
|
8
|
+
end
|
9
|
+
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
10
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
11
|
+
|
12
|
+
require "mongoid"
|
13
|
+
require "active_record"
|
14
|
+
require "dedupe"
|
15
|
+
require "rspec"
|
16
|
+
|
17
|
+
Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each { |file| require file }
|
@@ -0,0 +1,35 @@
|
|
1
|
+
ActiveRecord::Base.establish_connection :adapter => 'sqlite3',
|
2
|
+
:database => ':memory:'
|
3
|
+
ActiveRecord::Migration.verbose = false
|
4
|
+
tables = %w{ active_record_macroless_models active_record_scopeless_models active_record_invalid_scope_models active_record_models }
|
5
|
+
ActiveRecord::Schema.define(:version => 1) do
|
6
|
+
tables.each do |table_name|
|
7
|
+
create_table table_name.intern, :force => true do |t|
|
8
|
+
t.datetime :created_at
|
9
|
+
t.datetime :updated_at
|
10
|
+
t.string :foo
|
11
|
+
t.string :bar
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
# a "vanilla" ActiveRecord model
|
17
|
+
class ActiveRecordMacrolessModel < ActiveRecord::Base
|
18
|
+
end
|
19
|
+
# ActiveRecord model with macro included, but required scope "forgotten"
|
20
|
+
class ActiveRecordScopelessModel < ActiveRecordMacrolessModel
|
21
|
+
validates_uniqueness :using => :with_attributes_of
|
22
|
+
end
|
23
|
+
# ActiveRecord model with gem improperly implemented
|
24
|
+
# returns an instance of Array instead of Criteria
|
25
|
+
class ActiveRecordInvalidScopeModel < ActiveRecordScopelessModel
|
26
|
+
def self.with_attributes_of(instance)
|
27
|
+
where(:foo => instance.foo, :bar => instance.bar).to_a
|
28
|
+
end
|
29
|
+
end
|
30
|
+
# ActiveRecord model with gem properly implemented
|
31
|
+
class ActiveRecordModel < ActiveRecordScopelessModel
|
32
|
+
def self.with_attributes_of(instance)
|
33
|
+
where(:foo => instance.foo, :bar => instance.bar)
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
Mongoid.configure do |config|
|
2
|
+
name = "mongoid_test"
|
3
|
+
host = "localhost"
|
4
|
+
config.master = Mongo::Connection.new.db(name)
|
5
|
+
config.logger = nil
|
6
|
+
end
|
7
|
+
|
8
|
+
RSpec.configure do |config|
|
9
|
+
config.before do
|
10
|
+
Mongoid.master.collections.select {|c| c.name !~ /system/ }.each(&:drop)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
# a "vanilla" Mongoid model
|
15
|
+
class MongoidMacrolessModel
|
16
|
+
include Mongoid::Document
|
17
|
+
field :foo
|
18
|
+
field :bar
|
19
|
+
field :baz
|
20
|
+
end
|
21
|
+
|
22
|
+
# a Mongoid model with macro included, but required scope "forgotten"
|
23
|
+
class MongoidScopelessModel < MongoidMacrolessModel
|
24
|
+
validates_uniqueness :using => :with_attributes_of
|
25
|
+
end
|
26
|
+
|
27
|
+
# a Mongoid model with gem improperly implemented
|
28
|
+
# returns an instance of Array instead of Criteria
|
29
|
+
class MongoidInvalidScopeModel < MongoidScopelessModel
|
30
|
+
def self.with_attributes_of(instance)
|
31
|
+
where(:foo => instance.foo, :bar => instance.bar).to_a
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
# a Mongoid model with gem properly implemented
|
36
|
+
class MongoidModel < MongoidScopelessModel
|
37
|
+
def self.with_attributes_of(instance)
|
38
|
+
where(:foo => instance.foo, :bar => instance.bar)
|
39
|
+
end
|
40
|
+
end
|
metadata
ADDED
@@ -0,0 +1,239 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: dedupe
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease: false
|
5
|
+
segments:
|
6
|
+
- 0
|
7
|
+
- 0
|
8
|
+
- 0
|
9
|
+
version: 0.0.0
|
10
|
+
platform: ruby
|
11
|
+
authors:
|
12
|
+
- Brent Hargrave
|
13
|
+
autorequire:
|
14
|
+
bindir: bin
|
15
|
+
cert_chain: []
|
16
|
+
|
17
|
+
date: 2010-10-02 00:00:00 -04:00
|
18
|
+
default_executable:
|
19
|
+
dependencies:
|
20
|
+
- !ruby/object:Gem::Dependency
|
21
|
+
name: activesupport
|
22
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
23
|
+
none: false
|
24
|
+
requirements:
|
25
|
+
- - ~>
|
26
|
+
- !ruby/object:Gem::Version
|
27
|
+
segments:
|
28
|
+
- 3
|
29
|
+
- 0
|
30
|
+
- 0
|
31
|
+
version: 3.0.0
|
32
|
+
type: :runtime
|
33
|
+
prerelease: false
|
34
|
+
version_requirements: *id001
|
35
|
+
- !ruby/object:Gem::Dependency
|
36
|
+
name: activemodel
|
37
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
38
|
+
none: false
|
39
|
+
requirements:
|
40
|
+
- - ~>
|
41
|
+
- !ruby/object:Gem::Version
|
42
|
+
segments:
|
43
|
+
- 3
|
44
|
+
- 0
|
45
|
+
- 0
|
46
|
+
version: 3.0.0
|
47
|
+
type: :runtime
|
48
|
+
prerelease: false
|
49
|
+
version_requirements: *id002
|
50
|
+
- !ruby/object:Gem::Dependency
|
51
|
+
name: rspec
|
52
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
53
|
+
none: false
|
54
|
+
requirements:
|
55
|
+
- - ">="
|
56
|
+
- !ruby/object:Gem::Version
|
57
|
+
segments:
|
58
|
+
- 2
|
59
|
+
- 0
|
60
|
+
- 0
|
61
|
+
- beta
|
62
|
+
- 19
|
63
|
+
version: 2.0.0.beta.19
|
64
|
+
type: :development
|
65
|
+
prerelease: false
|
66
|
+
version_requirements: *id003
|
67
|
+
- !ruby/object:Gem::Dependency
|
68
|
+
name: bundler
|
69
|
+
requirement: &id004 !ruby/object:Gem::Requirement
|
70
|
+
none: false
|
71
|
+
requirements:
|
72
|
+
- - ~>
|
73
|
+
- !ruby/object:Gem::Version
|
74
|
+
segments:
|
75
|
+
- 1
|
76
|
+
- 0
|
77
|
+
- 0
|
78
|
+
version: 1.0.0
|
79
|
+
type: :development
|
80
|
+
prerelease: false
|
81
|
+
version_requirements: *id004
|
82
|
+
- !ruby/object:Gem::Dependency
|
83
|
+
name: jeweler
|
84
|
+
requirement: &id005 !ruby/object:Gem::Requirement
|
85
|
+
none: false
|
86
|
+
requirements:
|
87
|
+
- - ~>
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
segments:
|
90
|
+
- 1
|
91
|
+
- 5
|
92
|
+
- 0
|
93
|
+
- pre3
|
94
|
+
version: 1.5.0.pre3
|
95
|
+
type: :development
|
96
|
+
prerelease: false
|
97
|
+
version_requirements: *id005
|
98
|
+
- !ruby/object:Gem::Dependency
|
99
|
+
name: rcov
|
100
|
+
requirement: &id006 !ruby/object:Gem::Requirement
|
101
|
+
none: false
|
102
|
+
requirements:
|
103
|
+
- - ">="
|
104
|
+
- !ruby/object:Gem::Version
|
105
|
+
segments:
|
106
|
+
- 0
|
107
|
+
version: "0"
|
108
|
+
type: :development
|
109
|
+
prerelease: false
|
110
|
+
version_requirements: *id006
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: mongoid
|
113
|
+
requirement: &id007 !ruby/object:Gem::Requirement
|
114
|
+
none: false
|
115
|
+
requirements:
|
116
|
+
- - "="
|
117
|
+
- !ruby/object:Gem::Version
|
118
|
+
segments:
|
119
|
+
- 2
|
120
|
+
- 0
|
121
|
+
- 0
|
122
|
+
- beta
|
123
|
+
- 19
|
124
|
+
version: 2.0.0.beta.19
|
125
|
+
type: :development
|
126
|
+
prerelease: false
|
127
|
+
version_requirements: *id007
|
128
|
+
- !ruby/object:Gem::Dependency
|
129
|
+
name: bson_ext
|
130
|
+
requirement: &id008 !ruby/object:Gem::Requirement
|
131
|
+
none: false
|
132
|
+
requirements:
|
133
|
+
- - "="
|
134
|
+
- !ruby/object:Gem::Version
|
135
|
+
segments:
|
136
|
+
- 1
|
137
|
+
- 0
|
138
|
+
- 9
|
139
|
+
version: 1.0.9
|
140
|
+
type: :development
|
141
|
+
prerelease: false
|
142
|
+
version_requirements: *id008
|
143
|
+
- !ruby/object:Gem::Dependency
|
144
|
+
name: activerecord
|
145
|
+
requirement: &id009 !ruby/object:Gem::Requirement
|
146
|
+
none: false
|
147
|
+
requirements:
|
148
|
+
- - ~>
|
149
|
+
- !ruby/object:Gem::Version
|
150
|
+
segments:
|
151
|
+
- 3
|
152
|
+
- 0
|
153
|
+
- 0
|
154
|
+
version: 3.0.0
|
155
|
+
type: :development
|
156
|
+
prerelease: false
|
157
|
+
version_requirements: *id009
|
158
|
+
- !ruby/object:Gem::Dependency
|
159
|
+
name: sqlite3-ruby
|
160
|
+
requirement: &id010 !ruby/object:Gem::Requirement
|
161
|
+
none: false
|
162
|
+
requirements:
|
163
|
+
- - ~>
|
164
|
+
- !ruby/object:Gem::Version
|
165
|
+
segments:
|
166
|
+
- 1
|
167
|
+
- 3
|
168
|
+
- 1
|
169
|
+
version: 1.3.1
|
170
|
+
type: :development
|
171
|
+
prerelease: false
|
172
|
+
version_requirements: *id010
|
173
|
+
description: ActiveModel validation against duplicating records.
|
174
|
+
email: brent.hargrave@gmail.com
|
175
|
+
executables: []
|
176
|
+
|
177
|
+
extensions: []
|
178
|
+
|
179
|
+
extra_rdoc_files:
|
180
|
+
- LICENSE
|
181
|
+
- README.rdoc
|
182
|
+
files:
|
183
|
+
- .document
|
184
|
+
- .gitignore
|
185
|
+
- .rspec
|
186
|
+
- .rvmrc
|
187
|
+
- Gemfile
|
188
|
+
- Gemfile.lock
|
189
|
+
- LICENSE
|
190
|
+
- README.rdoc
|
191
|
+
- Rakefile
|
192
|
+
- VERSION
|
193
|
+
- lib/dedupe.rb
|
194
|
+
- lib/dedupe/errors.rb
|
195
|
+
- lib/dedupe/orm/active_record.rb
|
196
|
+
- lib/dedupe/orm/mongoid.rb
|
197
|
+
- lib/dedupe/validations.rb
|
198
|
+
- spec/dedupe_spec.rb
|
199
|
+
- spec/spec_helper.rb
|
200
|
+
- spec/support/active_record.rb
|
201
|
+
- spec/support/mongoid.rb
|
202
|
+
has_rdoc: true
|
203
|
+
homepage: http://github.com/jamescallmebrent/dedupe
|
204
|
+
licenses: []
|
205
|
+
|
206
|
+
post_install_message:
|
207
|
+
rdoc_options: []
|
208
|
+
|
209
|
+
require_paths:
|
210
|
+
- lib
|
211
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
212
|
+
none: false
|
213
|
+
requirements:
|
214
|
+
- - ">="
|
215
|
+
- !ruby/object:Gem::Version
|
216
|
+
hash: -1388205667040375508
|
217
|
+
segments:
|
218
|
+
- 0
|
219
|
+
version: "0"
|
220
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
221
|
+
none: false
|
222
|
+
requirements:
|
223
|
+
- - ">="
|
224
|
+
- !ruby/object:Gem::Version
|
225
|
+
segments:
|
226
|
+
- 0
|
227
|
+
version: "0"
|
228
|
+
requirements: []
|
229
|
+
|
230
|
+
rubyforge_project:
|
231
|
+
rubygems_version: 1.3.7
|
232
|
+
signing_key:
|
233
|
+
specification_version: 3
|
234
|
+
summary: ActiveModel validation against duplicating records.
|
235
|
+
test_files:
|
236
|
+
- spec/dedupe_spec.rb
|
237
|
+
- spec/spec_helper.rb
|
238
|
+
- spec/support/active_record.rb
|
239
|
+
- spec/support/mongoid.rb
|