quickening 0.1.1
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.
- checksums.yaml +7 -0
- data/CHANGELOG.rdoc +54 -0
- data/MIT-LICENSE +20 -0
- data/README.md +145 -0
- data/Rakefile +36 -0
- data/app/assets/javascripts/quickening/application.js +15 -0
- data/app/assets/stylesheets/quickening/application.css +13 -0
- data/app/controllers/quickening/application_controller.rb +4 -0
- data/app/helpers/quickening/application_helper.rb +4 -0
- data/app/views/layouts/quickening/application.html.erb +14 -0
- data/config/routes.rb +2 -0
- data/lib/quickening/engine.rb +16 -0
- data/lib/quickening/model.rb +178 -0
- data/lib/quickening/orm/active_record.rb +36 -0
- data/lib/quickening/version.rb +5 -0
- data/lib/quickening.rb +17 -0
- data/lib/tasks/quickening_tasks.rake +8 -0
- data/spec/dummy/README.rdoc +261 -0
- data/spec/dummy/Rakefile +7 -0
- data/spec/dummy/app/assets/javascripts/application.js +15 -0
- data/spec/dummy/app/assets/stylesheets/application.css +13 -0
- data/spec/dummy/app/controllers/application_controller.rb +3 -0
- data/spec/dummy/app/helpers/application_helper.rb +2 -0
- data/spec/dummy/app/models/admin.rb +3 -0
- data/spec/dummy/app/models/user.rb +6 -0
- data/spec/dummy/app/views/layouts/application.html.erb +14 -0
- data/spec/dummy/config/application.rb +65 -0
- data/spec/dummy/config/boot.rb +10 -0
- data/spec/dummy/config/database.yml +25 -0
- data/spec/dummy/config/environment.rb +5 -0
- data/spec/dummy/config/environments/development.rb +37 -0
- data/spec/dummy/config/environments/production.rb +67 -0
- data/spec/dummy/config/environments/test.rb +38 -0
- data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
- data/spec/dummy/config/initializers/inflections.rb +15 -0
- data/spec/dummy/config/initializers/mime_types.rb +5 -0
- data/spec/dummy/config/initializers/secret_token.rb +7 -0
- data/spec/dummy/config/initializers/session_store.rb +8 -0
- data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
- data/spec/dummy/config/locales/en.yml +5 -0
- data/spec/dummy/config/routes.rb +4 -0
- data/spec/dummy/config.ru +4 -0
- data/spec/dummy/db/development.sqlite3 +0 -0
- data/spec/dummy/db/migrate/20130302150624_create_users.rb +12 -0
- data/spec/dummy/db/migrate/20130302153106_create_admins.rb +12 -0
- data/spec/dummy/db/schema.rb +36 -0
- data/spec/dummy/db/test.sqlite3 +0 -0
- data/spec/dummy/log/development.log +404 -0
- data/spec/dummy/log/test.log +67550 -0
- data/spec/dummy/public/404.html +26 -0
- data/spec/dummy/public/422.html +26 -0
- data/spec/dummy/public/500.html +25 -0
- data/spec/dummy/public/favicon.ico +0 -0
- data/spec/dummy/script/rails +6 -0
- data/spec/factories/admin_factory.rb +13 -0
- data/spec/factories/user_factory.rb +13 -0
- data/spec/quickening_spec.rb +309 -0
- data/spec/spec_helper.rb +50 -0
- data/spec/support/shared_examples.rb +156 -0
- metadata +301 -0
@@ -0,0 +1,26 @@
|
|
1
|
+
<!DOCTYPE html>
|
2
|
+
<html>
|
3
|
+
<head>
|
4
|
+
<title>The page you were looking for doesn't exist (404)</title>
|
5
|
+
<style type="text/css">
|
6
|
+
body { background-color: #fff; color: #666; text-align: center; font-family: arial, sans-serif; }
|
7
|
+
div.dialog {
|
8
|
+
width: 25em;
|
9
|
+
padding: 0 4em;
|
10
|
+
margin: 4em auto 0 auto;
|
11
|
+
border: 1px solid #ccc;
|
12
|
+
border-right-color: #999;
|
13
|
+
border-bottom-color: #999;
|
14
|
+
}
|
15
|
+
h1 { font-size: 100%; color: #f00; line-height: 1.5em; }
|
16
|
+
</style>
|
17
|
+
</head>
|
18
|
+
|
19
|
+
<body>
|
20
|
+
<!-- This file lives in public/404.html -->
|
21
|
+
<div class="dialog">
|
22
|
+
<h1>The page you were looking for doesn't exist.</h1>
|
23
|
+
<p>You may have mistyped the address or the page may have moved.</p>
|
24
|
+
</div>
|
25
|
+
</body>
|
26
|
+
</html>
|
@@ -0,0 +1,26 @@
|
|
1
|
+
<!DOCTYPE html>
|
2
|
+
<html>
|
3
|
+
<head>
|
4
|
+
<title>The change you wanted was rejected (422)</title>
|
5
|
+
<style type="text/css">
|
6
|
+
body { background-color: #fff; color: #666; text-align: center; font-family: arial, sans-serif; }
|
7
|
+
div.dialog {
|
8
|
+
width: 25em;
|
9
|
+
padding: 0 4em;
|
10
|
+
margin: 4em auto 0 auto;
|
11
|
+
border: 1px solid #ccc;
|
12
|
+
border-right-color: #999;
|
13
|
+
border-bottom-color: #999;
|
14
|
+
}
|
15
|
+
h1 { font-size: 100%; color: #f00; line-height: 1.5em; }
|
16
|
+
</style>
|
17
|
+
</head>
|
18
|
+
|
19
|
+
<body>
|
20
|
+
<!-- This file lives in public/422.html -->
|
21
|
+
<div class="dialog">
|
22
|
+
<h1>The change you wanted was rejected.</h1>
|
23
|
+
<p>Maybe you tried to change something you didn't have access to.</p>
|
24
|
+
</div>
|
25
|
+
</body>
|
26
|
+
</html>
|
@@ -0,0 +1,25 @@
|
|
1
|
+
<!DOCTYPE html>
|
2
|
+
<html>
|
3
|
+
<head>
|
4
|
+
<title>We're sorry, but something went wrong (500)</title>
|
5
|
+
<style type="text/css">
|
6
|
+
body { background-color: #fff; color: #666; text-align: center; font-family: arial, sans-serif; }
|
7
|
+
div.dialog {
|
8
|
+
width: 25em;
|
9
|
+
padding: 0 4em;
|
10
|
+
margin: 4em auto 0 auto;
|
11
|
+
border: 1px solid #ccc;
|
12
|
+
border-right-color: #999;
|
13
|
+
border-bottom-color: #999;
|
14
|
+
}
|
15
|
+
h1 { font-size: 100%; color: #f00; line-height: 1.5em; }
|
16
|
+
</style>
|
17
|
+
</head>
|
18
|
+
|
19
|
+
<body>
|
20
|
+
<!-- This file lives in public/500.html -->
|
21
|
+
<div class="dialog">
|
22
|
+
<h1>We're sorry, but something went wrong.</h1>
|
23
|
+
</div>
|
24
|
+
</body>
|
25
|
+
</html>
|
File without changes
|
@@ -0,0 +1,6 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# This command will automatically be run when you run "rails" with Rails 3 gems installed from the root of your application.
|
3
|
+
|
4
|
+
APP_PATH = File.expand_path('../../config/application', __FILE__)
|
5
|
+
require File.expand_path('../../config/boot', __FILE__)
|
6
|
+
require 'rails/commands'
|
@@ -0,0 +1,13 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
FactoryGirl.define do
|
4
|
+
|
5
|
+
factory :admin do
|
6
|
+
|
7
|
+
email { Faker::Internet.email }
|
8
|
+
first_name { Faker::Name.first_name }
|
9
|
+
middle_name { Faker::Name.first_name }
|
10
|
+
last_name { Faker::Name.last_name }
|
11
|
+
birthdate '1985-01-29'
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
FactoryGirl.define do
|
4
|
+
|
5
|
+
factory :user do
|
6
|
+
|
7
|
+
email { Faker::Internet.email }
|
8
|
+
first_name { Faker::Name.first_name }
|
9
|
+
middle_name { Faker::Name.first_name }
|
10
|
+
last_name { Faker::Name.last_name }
|
11
|
+
birthdate '1985-01-29'
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,309 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
|
5
|
+
describe Quickening do
|
6
|
+
subject(:mod) { described_class }
|
7
|
+
it { should be_a Module }
|
8
|
+
|
9
|
+
describe Quickening::VERSION do
|
10
|
+
subject(:konstant) { Quickening::VERSION }
|
11
|
+
|
12
|
+
it { should be_a String }
|
13
|
+
it { should =~ /^(\d+\.){2,}\d+$/ }
|
14
|
+
it { should be > '0.0.0' }
|
15
|
+
end
|
16
|
+
|
17
|
+
describe Quickening::Model do
|
18
|
+
subject(:mod) { described_class }
|
19
|
+
it { should be_a Module }
|
20
|
+
end
|
21
|
+
|
22
|
+
describe Quickening::ORM::ActiveRecord do
|
23
|
+
subject(:mod) { described_class }
|
24
|
+
it { should be_a Module }
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
describe 'Dummy App' do
|
29
|
+
|
30
|
+
[Admin].each do |k|
|
31
|
+
|
32
|
+
describe k do
|
33
|
+
let(:klass) { k }
|
34
|
+
subject { klass }
|
35
|
+
|
36
|
+
its(:superclass) { should be ActiveRecord::Base }
|
37
|
+
|
38
|
+
it { should respond_to :quickening }
|
39
|
+
it { should_not include Quickening::Model }
|
40
|
+
it { should_not respond_to :duplicate_matchers }
|
41
|
+
it { should_not respond_to :duplicate_matchers= }
|
42
|
+
it { should_not respond_to :duplicate }
|
43
|
+
it { should_not respond_to :find_duplicates_for }
|
44
|
+
|
45
|
+
it 'does not make available the instance methods' do
|
46
|
+
[:duplicates, :_duplicate_conditions].each do |method|
|
47
|
+
klass.instance_methods.should_not include method
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
describe 'Instance thereof' do
|
52
|
+
subject(:user) { klass.new }
|
53
|
+
|
54
|
+
it { should be_a klass }
|
55
|
+
it { should be_a ActiveRecord::Base }
|
56
|
+
it { should_not be_a Quickening::Model }
|
57
|
+
|
58
|
+
it { should_not respond_to :duplicate_matchers }
|
59
|
+
it { should_not respond_to :duplicate_matchers= }
|
60
|
+
it { should_not respond_to :duplicates }
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
context 'when quickening is called in the class' do
|
66
|
+
let(:klass) { User }
|
67
|
+
|
68
|
+
describe 'the enabled class' do
|
69
|
+
subject { klass }
|
70
|
+
|
71
|
+
it { should respond_to :quickening }
|
72
|
+
it { should include Quickening::Model }
|
73
|
+
it { should respond_to :duplicate_matchers }
|
74
|
+
it { should respond_to :duplicate_matchers= }
|
75
|
+
it { should respond_to :duplicate }
|
76
|
+
its(:duplicate) { should respond_to :originals }
|
77
|
+
its(:duplicate) { should respond_to :copies }
|
78
|
+
it { should respond_to :find_duplicates_for }
|
79
|
+
|
80
|
+
it 'makes available the instance methods' do
|
81
|
+
[:duplicates, :_duplicate_conditions].each do |method|
|
82
|
+
klass.instance_methods.should include method
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
its(:duplicate_matchers) { should eq [:first_name, :last_name] }
|
87
|
+
|
88
|
+
describe 'Instance thereof' do
|
89
|
+
subject(:user) { klass.new }
|
90
|
+
|
91
|
+
it { should be_a klass }
|
92
|
+
it { should be_a ActiveRecord::Base }
|
93
|
+
it { should be_a Quickening::Model }
|
94
|
+
|
95
|
+
it { should respond_to :duplicate_matchers }
|
96
|
+
it { should_not respond_to :duplicate_matchers= }
|
97
|
+
it { should respond_to :duplicates }
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
let(:klass) { described_class }
|
103
|
+
let(:null_relation) { klass.limit(0) }
|
104
|
+
let(:mock_relation) { mock('Relation') }
|
105
|
+
let(:returned) {}
|
106
|
+
|
107
|
+
context 'when there are no duplicates' do
|
108
|
+
before(:all) do
|
109
|
+
User.delete_all
|
110
|
+
@users = 10.times.map { FactoryGirl.create(:user) }
|
111
|
+
end
|
112
|
+
|
113
|
+
after(:all) { User.delete_all }
|
114
|
+
|
115
|
+
describe User do
|
116
|
+
let(:user) { @users[0] }
|
117
|
+
subject { returned }
|
118
|
+
|
119
|
+
describe '.duplicate' do
|
120
|
+
include_context 'Proper .duplicate results'
|
121
|
+
it_behaves_like 'Empty relational collections'
|
122
|
+
|
123
|
+
describe '.originals' do
|
124
|
+
include_context 'Proper .originals results'
|
125
|
+
it_behaves_like 'Empty relational collections'
|
126
|
+
end
|
127
|
+
|
128
|
+
describe '.copies' do
|
129
|
+
include_context 'Proper .copies results'
|
130
|
+
it_behaves_like 'Empty relational collections'
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
describe '.find_duplicates_for' do
|
135
|
+
include_context 'Proper .find_duplicates_for results'
|
136
|
+
it_behaves_like 'Empty relational collections'
|
137
|
+
end
|
138
|
+
|
139
|
+
describe '#duplicates' do
|
140
|
+
include_context 'Proper #duplicates results'
|
141
|
+
it_behaves_like 'Empty relational collections'
|
142
|
+
end
|
143
|
+
|
144
|
+
describe '#_duplicate_conditions' do
|
145
|
+
include_context 'Proper #_duplicate_conditions results'
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
context 'when half are duplicates of the other half' do
|
151
|
+
before(:all) do
|
152
|
+
User.delete_all
|
153
|
+
@users1 = 5.times.map { FactoryGirl.create(:user) }
|
154
|
+
@users2 = @users1.map { |u| FactoryGirl.create(:user, first_name: u.first_name,
|
155
|
+
last_name: u.last_name) }
|
156
|
+
@users = @users1 + @users2
|
157
|
+
end
|
158
|
+
after(:all) { User.delete_all }
|
159
|
+
|
160
|
+
describe User do
|
161
|
+
let(:user) { @users[0] }
|
162
|
+
subject { returned }
|
163
|
+
|
164
|
+
describe '.duplicate' do
|
165
|
+
include_context 'Proper .duplicate results'
|
166
|
+
it_behaves_like 'Empty relational collections'
|
167
|
+
|
168
|
+
context 'but this time, with force: true' do
|
169
|
+
let(:returned) { User.duplicate(force: true) }
|
170
|
+
it_behaves_like 'Non-empty relational collections'
|
171
|
+
it { should have(10).records }
|
172
|
+
end
|
173
|
+
|
174
|
+
describe '.originals' do
|
175
|
+
include_context 'Proper .originals results'
|
176
|
+
it_behaves_like 'Non-empty relational collections'
|
177
|
+
it { should have(5).records }
|
178
|
+
|
179
|
+
it 'is composed of records for which exactly one counterpart exists outside of this set' do
|
180
|
+
returned.each do |record|
|
181
|
+
dupe, * = dupes = record.duplicates
|
182
|
+
expect(dupes).to have(1).record
|
183
|
+
expect(dupe).not_to be_in returned
|
184
|
+
expect(dupe).to be_in User.duplicate.copies
|
185
|
+
end
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
189
|
+
describe '.copies' do
|
190
|
+
include_context 'Proper .copies results'
|
191
|
+
it_behaves_like 'Non-empty relational collections'
|
192
|
+
it { should have(5).records }
|
193
|
+
|
194
|
+
it 'is composed of records for which exactly one counterpart exists outside of this set' do
|
195
|
+
returned.each do |record|
|
196
|
+
dupe, * = dupes = record.duplicates
|
197
|
+
expect(dupes).to have(1).record
|
198
|
+
expect(dupe).not_to be_in returned
|
199
|
+
expect(dupe).to be_in User.duplicate.originals
|
200
|
+
end
|
201
|
+
end
|
202
|
+
end
|
203
|
+
end
|
204
|
+
|
205
|
+
describe '.find_duplicates_for' do
|
206
|
+
include_context 'Proper .find_duplicates_for results'
|
207
|
+
it_behaves_like 'Non-empty relational collections'
|
208
|
+
|
209
|
+
it { should have(1).record }
|
210
|
+
it { should_not include user }
|
211
|
+
end
|
212
|
+
|
213
|
+
describe '#duplicates' do
|
214
|
+
include_context 'Proper #duplicates results'
|
215
|
+
it_behaves_like 'Non-empty relational collections'
|
216
|
+
|
217
|
+
it { should have(1).record }
|
218
|
+
it { should_not include user }
|
219
|
+
its('first.duplicates') { should have(1).record }
|
220
|
+
its('first.duplicates.first') { should == user }
|
221
|
+
end
|
222
|
+
|
223
|
+
describe '#_duplicate_conditions' do
|
224
|
+
include_context 'Proper #_duplicate_conditions results'
|
225
|
+
end
|
226
|
+
end
|
227
|
+
end
|
228
|
+
|
229
|
+
context 'when there are 10 items, all a duplicate of one another' do
|
230
|
+
before(:all) do
|
231
|
+
User.delete_all
|
232
|
+
@users = FactoryGirl.create_list(:user, 10, first_name: 'John',
|
233
|
+
last_name: 'Malkovich')
|
234
|
+
end
|
235
|
+
|
236
|
+
after(:all) { User.delete_all }
|
237
|
+
|
238
|
+
describe User do
|
239
|
+
let(:user) { @users[0] }
|
240
|
+
subject { returned }
|
241
|
+
|
242
|
+
describe '.duplicate' do
|
243
|
+
include_context 'Proper .duplicate results'
|
244
|
+
it_behaves_like 'Empty relational collections'
|
245
|
+
|
246
|
+
context 'but this time, with force: true' do
|
247
|
+
let(:returned) { User.duplicate(force: true) }
|
248
|
+
it_behaves_like 'Non-empty relational collections'
|
249
|
+
it { should have(10).records }
|
250
|
+
end
|
251
|
+
|
252
|
+
describe '.originals' do
|
253
|
+
include_context 'Proper .originals results'
|
254
|
+
it_behaves_like 'Non-empty relational collections'
|
255
|
+
|
256
|
+
it 'is composed of one record for which exactly nine counterparts exist outside of this set' do
|
257
|
+
returned.each do |record|
|
258
|
+
dupe, * = dupes = record.duplicates
|
259
|
+
expect(dupes).to have(9).records
|
260
|
+
expect(dupe).not_to be_in returned
|
261
|
+
expect(dupe).to be_in User.duplicate.copies
|
262
|
+
end
|
263
|
+
end
|
264
|
+
end
|
265
|
+
|
266
|
+
describe '.copies' do
|
267
|
+
include_context 'Proper .copies results'
|
268
|
+
it_behaves_like 'Non-empty relational collections'
|
269
|
+
it { should have(9).records }
|
270
|
+
|
271
|
+
it 'is composed of records for which exactly one counterpart exists outside of this set' do
|
272
|
+
returned.each do |record|
|
273
|
+
dupe, * = dupes = record.duplicates
|
274
|
+
expect(dupes).to have(9).records
|
275
|
+
expect(record).not_to be_in User.duplicate.originals
|
276
|
+
end
|
277
|
+
end
|
278
|
+
end
|
279
|
+
end
|
280
|
+
|
281
|
+
describe '.find_duplicates_for' do
|
282
|
+
include_context 'Proper .find_duplicates_for results'
|
283
|
+
it_behaves_like 'Non-empty relational collections'
|
284
|
+
|
285
|
+
it { should have(9).records }
|
286
|
+
it { should_not include user }
|
287
|
+
end
|
288
|
+
|
289
|
+
describe '#duplicates' do
|
290
|
+
include_context 'Proper #duplicates results'
|
291
|
+
it_behaves_like 'Non-empty relational collections'
|
292
|
+
|
293
|
+
it { should have(9).records }
|
294
|
+
it { should_not include user }
|
295
|
+
|
296
|
+
it 'contains records, each of which refer this record as one of its duplicates' do
|
297
|
+
returned.each do |record|
|
298
|
+
expect(record.duplicates).to have(9).records
|
299
|
+
expect(record.duplicates).to include user
|
300
|
+
end
|
301
|
+
end
|
302
|
+
end
|
303
|
+
|
304
|
+
describe '#_duplicate_conditions' do
|
305
|
+
include_context 'Proper #_duplicate_conditions results'
|
306
|
+
end
|
307
|
+
end
|
308
|
+
end
|
309
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,50 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
def setup_environment
|
4
|
+
ENV["RAILS_ENV"] ||= 'test'
|
5
|
+
|
6
|
+
require File.expand_path("../dummy/config/environment", __FILE__)
|
7
|
+
require 'rspec/rails'
|
8
|
+
require 'rspec/autorun'
|
9
|
+
require 'faker'
|
10
|
+
require 'factory_girl_rails'
|
11
|
+
|
12
|
+
# Rails.backtrace_cleaner.remove_silencers!
|
13
|
+
|
14
|
+
Dir[File.expand_path('../support/**/*.rb', __FILE__)].each { |f| require f }
|
15
|
+
|
16
|
+
RSpec.configure do |config|
|
17
|
+
config.treat_symbols_as_metadata_keys_with_true_values = true
|
18
|
+
config.run_all_when_everything_filtered = true
|
19
|
+
config.filter_run :focus
|
20
|
+
config.mock_with :rspec
|
21
|
+
config.use_transactional_fixtures = true
|
22
|
+
config.infer_base_class_for_anonymous_controllers = true
|
23
|
+
config.order = "random"
|
24
|
+
|
25
|
+
config.fixture_path = "#{::Rails.root}/spec/fixtures"
|
26
|
+
config.include FactoryGirl::Syntax::Methods
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def each_run
|
31
|
+
|
32
|
+
end
|
33
|
+
|
34
|
+
if(begin; require 'spork'; rescue LoadError; nil end).nil? || !Spork.using_spork?
|
35
|
+
# require 'simplecov'
|
36
|
+
setup_environment
|
37
|
+
each_run
|
38
|
+
else
|
39
|
+
# require 'spork/ext/ruby-debug'
|
40
|
+
Spork.prefork do
|
41
|
+
ENV['DRB'] = 'true'
|
42
|
+
setup_environment
|
43
|
+
# ActiveSupport::Dependencies.clear
|
44
|
+
end
|
45
|
+
|
46
|
+
Spork.each_run do
|
47
|
+
# require 'simplecov'
|
48
|
+
each_run
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,156 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
shared_examples 'Relational collections' do
|
4
|
+
specify { expect { returned }.not_to raise_error }
|
5
|
+
it { should be_a ActiveRecord::Relation }
|
6
|
+
end
|
7
|
+
|
8
|
+
shared_examples 'Empty relational collections' do
|
9
|
+
include_examples 'Relational collections'
|
10
|
+
it { should be_empty }
|
11
|
+
end
|
12
|
+
|
13
|
+
shared_examples 'Non-empty relational collections' do
|
14
|
+
include_examples 'Relational collections'
|
15
|
+
it { should_not be_empty }
|
16
|
+
end
|
17
|
+
|
18
|
+
shared_context 'Proper .duplicate results' do
|
19
|
+
let(:returned) { User.duplicate }
|
20
|
+
|
21
|
+
it 'only returns unique rows' do
|
22
|
+
expect(returned.to_sql).to match(/ DISTINCT/)
|
23
|
+
end
|
24
|
+
|
25
|
+
it 'returns the results in an ascending order by id' do
|
26
|
+
klass.stub_chain(:select, :uniq, :joins, :where) { mock_relation.as_null_object }
|
27
|
+
|
28
|
+
mock_relation.should_receive(:order).with(/id/) { null_relation }
|
29
|
+
returned
|
30
|
+
end
|
31
|
+
|
32
|
+
it 'limits the result size to 0 by default' do
|
33
|
+
klass.stub_chain(:select, :uniq, :joins, :where, :order) { mock_relation.as_null_object }
|
34
|
+
mock_relation.should_receive(:limit).with(0)
|
35
|
+
returned
|
36
|
+
end
|
37
|
+
|
38
|
+
context 'with force: true' do
|
39
|
+
subject(:returned) { klass.duplicate(force: true) }
|
40
|
+
|
41
|
+
it 'only returns unique rows' do
|
42
|
+
expect(returned.to_sql).to match(/ DISTINCT/)
|
43
|
+
end
|
44
|
+
|
45
|
+
it 'returns the results in an ascending order by id' do
|
46
|
+
klass.stub_chain(:select, :uniq, :joins, :where) { mock_relation.as_null_object }
|
47
|
+
mock_relation.should_receive(:order).with(/id/) { null_relation }
|
48
|
+
returned
|
49
|
+
end
|
50
|
+
|
51
|
+
it 'does not limit the result size to 0' do
|
52
|
+
klass.stub_chain(:select, :uniq, :joins, :where, :order) { mock_relation.as_null_object }
|
53
|
+
mock_relation.should_receive(:limit).with(nil)
|
54
|
+
returned
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
shared_examples 'Proper .originals results' do
|
60
|
+
subject(:returned) { klass.duplicate.originals }
|
61
|
+
|
62
|
+
it 'removes the default limit of 0' do
|
63
|
+
ActiveRecord::Relation.any_instance.should_receive(:except).with(:limit) { null_relation }
|
64
|
+
returned
|
65
|
+
end
|
66
|
+
|
67
|
+
it 'restricts the results to those which are the earliest created records' do
|
68
|
+
ActiveRecord::Relation.any_instance.should_receive(:except).with(:limit) { mock_relation.as_null_object }
|
69
|
+
mock_relation.stub(:where) do |arg|
|
70
|
+
sql = arg
|
71
|
+
expect(sql).to match(/HAVING.* MIN.*=/)
|
72
|
+
end
|
73
|
+
returned
|
74
|
+
end
|
75
|
+
|
76
|
+
it 'has no element for which even older duplicates can be found' do
|
77
|
+
returned.each do |original|
|
78
|
+
original.duplicates.each do |dupe|
|
79
|
+
dupe.id.should be > original.id
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
shared_context 'Proper .copies results' do
|
86
|
+
subject(:returned) { klass.duplicate.copies }
|
87
|
+
|
88
|
+
it 'removes the default limit of 0' do
|
89
|
+
ActiveRecord::Relation.any_instance.should_receive(:except).with(:limit) { null_relation }
|
90
|
+
returned
|
91
|
+
end
|
92
|
+
|
93
|
+
it 'restricts the results to those which are not the earliest created records' do
|
94
|
+
ActiveRecord::Relation.any_instance.should_receive(:except).with(:limit) { mock_relation.as_null_object }
|
95
|
+
# This tests things at too low a level; breaks everytime a tiny thing changes.
|
96
|
+
# mock_relation.stub(:where) do |arg|
|
97
|
+
# sql = arg
|
98
|
+
# expect(sql).to match(/ < /)
|
99
|
+
# expect(sql).not_to match(/ > /)
|
100
|
+
# end
|
101
|
+
returned
|
102
|
+
end
|
103
|
+
|
104
|
+
it 'has no element for which a older duplicates canNOT be found' do
|
105
|
+
expect(returned).to satisfy do |copies|
|
106
|
+
(copies & klass.duplicate.originals).empty?
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
shared_context 'Proper .find_duplicates_for results' do
|
112
|
+
subject(:returned) { klass.find_duplicates_for(user) }
|
113
|
+
|
114
|
+
it 'utilizes the instance-level conditions helper' do
|
115
|
+
user.should_receive(:_duplicate_conditions) { :helper_return }
|
116
|
+
klass.should_receive(:where).with(:helper_return) { null_relation.as_null_object }
|
117
|
+
returned
|
118
|
+
end
|
119
|
+
|
120
|
+
it 'makes sure not to return the original item among results' do
|
121
|
+
klass.stub_chain(:where, :where) do |arg|
|
122
|
+
sql = arg
|
123
|
+
expect(sql).to match %r{ != }
|
124
|
+
end
|
125
|
+
returned
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
shared_context 'Proper #duplicates results' do
|
130
|
+
subject(:returned) { user.duplicates }
|
131
|
+
|
132
|
+
it 'calls the proxy class method' do
|
133
|
+
klass.should_receive(:find_duplicates_for).with(user)
|
134
|
+
returned
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
shared_context 'Proper #_duplicate_conditions results' do
|
139
|
+
subject(:returned) { user._duplicate_conditions }
|
140
|
+
|
141
|
+
it 'calls the class-level list of matcher attributes' do
|
142
|
+
user.should_receive(:duplicate_matchers).and_call_original
|
143
|
+
klass.should_receive(:duplicate_matchers) { [] }
|
144
|
+
returned
|
145
|
+
end
|
146
|
+
|
147
|
+
it 'iterates through matcher attributes to construct a key-val composition' do
|
148
|
+
Hash.should_receive(:[]).with(instance_of Array)
|
149
|
+
returned
|
150
|
+
end
|
151
|
+
|
152
|
+
it 'returns a hash composed of keys from the class-level list' do
|
153
|
+
expect(returned).to be_a Hash
|
154
|
+
expect(returned.keys).to eq klass.duplicate_matchers
|
155
|
+
end
|
156
|
+
end
|