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.
Files changed (60) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.rdoc +54 -0
  3. data/MIT-LICENSE +20 -0
  4. data/README.md +145 -0
  5. data/Rakefile +36 -0
  6. data/app/assets/javascripts/quickening/application.js +15 -0
  7. data/app/assets/stylesheets/quickening/application.css +13 -0
  8. data/app/controllers/quickening/application_controller.rb +4 -0
  9. data/app/helpers/quickening/application_helper.rb +4 -0
  10. data/app/views/layouts/quickening/application.html.erb +14 -0
  11. data/config/routes.rb +2 -0
  12. data/lib/quickening/engine.rb +16 -0
  13. data/lib/quickening/model.rb +178 -0
  14. data/lib/quickening/orm/active_record.rb +36 -0
  15. data/lib/quickening/version.rb +5 -0
  16. data/lib/quickening.rb +17 -0
  17. data/lib/tasks/quickening_tasks.rake +8 -0
  18. data/spec/dummy/README.rdoc +261 -0
  19. data/spec/dummy/Rakefile +7 -0
  20. data/spec/dummy/app/assets/javascripts/application.js +15 -0
  21. data/spec/dummy/app/assets/stylesheets/application.css +13 -0
  22. data/spec/dummy/app/controllers/application_controller.rb +3 -0
  23. data/spec/dummy/app/helpers/application_helper.rb +2 -0
  24. data/spec/dummy/app/models/admin.rb +3 -0
  25. data/spec/dummy/app/models/user.rb +6 -0
  26. data/spec/dummy/app/views/layouts/application.html.erb +14 -0
  27. data/spec/dummy/config/application.rb +65 -0
  28. data/spec/dummy/config/boot.rb +10 -0
  29. data/spec/dummy/config/database.yml +25 -0
  30. data/spec/dummy/config/environment.rb +5 -0
  31. data/spec/dummy/config/environments/development.rb +37 -0
  32. data/spec/dummy/config/environments/production.rb +67 -0
  33. data/spec/dummy/config/environments/test.rb +38 -0
  34. data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
  35. data/spec/dummy/config/initializers/inflections.rb +15 -0
  36. data/spec/dummy/config/initializers/mime_types.rb +5 -0
  37. data/spec/dummy/config/initializers/secret_token.rb +7 -0
  38. data/spec/dummy/config/initializers/session_store.rb +8 -0
  39. data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
  40. data/spec/dummy/config/locales/en.yml +5 -0
  41. data/spec/dummy/config/routes.rb +4 -0
  42. data/spec/dummy/config.ru +4 -0
  43. data/spec/dummy/db/development.sqlite3 +0 -0
  44. data/spec/dummy/db/migrate/20130302150624_create_users.rb +12 -0
  45. data/spec/dummy/db/migrate/20130302153106_create_admins.rb +12 -0
  46. data/spec/dummy/db/schema.rb +36 -0
  47. data/spec/dummy/db/test.sqlite3 +0 -0
  48. data/spec/dummy/log/development.log +404 -0
  49. data/spec/dummy/log/test.log +67550 -0
  50. data/spec/dummy/public/404.html +26 -0
  51. data/spec/dummy/public/422.html +26 -0
  52. data/spec/dummy/public/500.html +25 -0
  53. data/spec/dummy/public/favicon.ico +0 -0
  54. data/spec/dummy/script/rails +6 -0
  55. data/spec/factories/admin_factory.rb +13 -0
  56. data/spec/factories/user_factory.rb +13 -0
  57. data/spec/quickening_spec.rb +309 -0
  58. data/spec/spec_helper.rb +50 -0
  59. data/spec/support/shared_examples.rb +156 -0
  60. 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
@@ -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