ansr_dpla 0.0.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (44) hide show
  1. checksums.yaml +15 -0
  2. data/Gemfile +3 -0
  3. data/Gemfile.lock +137 -0
  4. data/README.md +59 -0
  5. data/ansr_dpla.gemspec +27 -0
  6. data/app/models/collection.rb +2 -0
  7. data/app/models/item.rb +2 -0
  8. data/fixtures/collection.json +1 -0
  9. data/fixtures/collection.jsonld +14 -0
  10. data/fixtures/collections.json +1 -0
  11. data/fixtures/collections.jsonld +65 -0
  12. data/fixtures/dpla.yml +2 -0
  13. data/fixtures/empty.jsonld +1 -0
  14. data/fixtures/item.json +1 -0
  15. data/fixtures/item.jsonld +272 -0
  16. data/fixtures/kittens.json +1 -0
  17. data/fixtures/kittens.jsonld +2477 -0
  18. data/fixtures/kittens_faceted.json +1 -0
  19. data/fixtures/kittens_faceted.jsonld +2693 -0
  20. data/lib/ansr_dpla.rb +9 -0
  21. data/lib/ansr_dpla/api.rb +78 -0
  22. data/lib/ansr_dpla/arel.rb +7 -0
  23. data/lib/ansr_dpla/arel/big_table.rb +108 -0
  24. data/lib/ansr_dpla/arel/visitors.rb +6 -0
  25. data/lib/ansr_dpla/arel/visitors/query_builder.rb +188 -0
  26. data/lib/ansr_dpla/arel/visitors/to_no_sql.rb +9 -0
  27. data/lib/ansr_dpla/connection_adapters/no_sql_adapter.rb +58 -0
  28. data/lib/ansr_dpla/model.rb +7 -0
  29. data/lib/ansr_dpla/model/base.rb +24 -0
  30. data/lib/ansr_dpla/model/pseudo_associate.rb +14 -0
  31. data/lib/ansr_dpla/model/querying.rb +34 -0
  32. data/lib/ansr_dpla/relation.rb +60 -0
  33. data/lib/ansr_dpla/request.rb +5 -0
  34. data/spec/adpla_test_api.rb +9 -0
  35. data/spec/lib/api_spec.rb +113 -0
  36. data/spec/lib/item_spec.rb +57 -0
  37. data/spec/lib/relation/facet_spec.rb +82 -0
  38. data/spec/lib/relation/select_spec.rb +54 -0
  39. data/spec/lib/relation/where_spec.rb +74 -0
  40. data/spec/lib/relation_spec.rb +307 -0
  41. data/spec/spec_helper.rb +36 -0
  42. data/test/debug.rb +14 -0
  43. data/test/system.rb +52 -0
  44. metadata +236 -0
@@ -0,0 +1,74 @@
1
+ require 'spec_helper'
2
+
3
+ describe Ansr::Relation do
4
+ before do
5
+ @kittens = read_fixture('kittens.jsonld')
6
+ @faceted = read_fixture('kittens_faceted.jsonld')
7
+ @empty = read_fixture('empty.jsonld')
8
+ @mock_api = double('api')
9
+ Item.config{ |x| x[:api_key] = :foo}
10
+ Item.engine.api= @mock_api
11
+ end
12
+ describe '#where' do
13
+ it "do a single field, single value where" do
14
+ test = Ansr::Relation.new(Item, Item.table).where(:q=>'kittens')
15
+ @mock_api.should_receive(:items).with(:q => 'kittens').and_return('')
16
+ test.load
17
+ end
18
+ it "do a single field, multiple value where" do
19
+ test = Ansr::Relation.new(Item, Item.table).where(:q=>['kittens', 'cats'])
20
+ @mock_api.should_receive(:items).with(:q => ['kittens','cats']).and_return('')
21
+ test.load
22
+ end
23
+ it "do merge single field, multiple value wheres" do
24
+ test = Ansr::Relation.new(Item, Item.table).where(:q=>'kittens').where(:q=>'cats')
25
+ @mock_api.should_receive(:items).with(:q => ['kittens','cats']).and_return('')
26
+ test.load
27
+ end
28
+ it "do a multiple field, single value where" do
29
+ test = Ansr::Relation.new(Item, Item.table).where(:q=>'kittens',:foo=>'bears')
30
+ @mock_api.should_receive(:items).with(:q => 'kittens', :foo=>'bears').and_return('')
31
+ test.load
32
+ end
33
+ it "should keep scope distinct from spawned Relations" do
34
+ test = Ansr::Relation.new(Item, Item.table).where(:q=>'kittens')
35
+ test.where(:q=>'cats')
36
+ @mock_api.should_receive(:items).with(:q => 'kittens').and_return('')
37
+ test.load
38
+ end
39
+ describe '#not' do
40
+ it 'should exclude a specified map of field values' do
41
+ test = Ansr::Relation.new(Item, Item.table)
42
+ test = test.where(:foo =>'kittens')
43
+ test = test.where.not(:foo => 'cats')
44
+ @mock_api.should_receive(:items).with(:foo => ['kittens', 'NOT cats']).and_return('')
45
+ test.load
46
+ end
47
+
48
+ pending 'should exclude a value from the default query' do
49
+ test = Ansr::Relation.new(Item, Item.table)
50
+ test = test.where('kittens')
51
+ test = test.where.not('cats')
52
+ @mock_api.should_receive(:items).with(:q => ['kittens', 'NOT cats']).and_return('')
53
+ test.load
54
+ end
55
+
56
+ pending 'should exclude a specified field' do
57
+ test = Ansr::Relation.new(Item, Item.table)
58
+ test = test.where(:foo => 'kittens')
59
+ test = test.where.not('cats')
60
+ @mock_api.should_receive(:items).with(:foo => 'kittens', :q => 'NOT cats').and_return('')
61
+ test.load
62
+ end
63
+ end
64
+ describe '#or' do
65
+ it 'should union a specified map of field values' do
66
+ test = Ansr::Relation.new(Item, Item.table)
67
+ test = test.where(:foo =>'kittens')
68
+ test = test.where.or(:foo => 'cats')
69
+ @mock_api.should_receive(:items).with(:foo => ['kittens', 'OR cats']).and_return('')
70
+ test.load
71
+ end
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,307 @@
1
+ require 'spec_helper'
2
+
3
+ describe Ansr::Dpla::Relation do
4
+ before do
5
+ @kittens = read_fixture('kittens.jsonld')
6
+ @faceted = read_fixture('kittens_faceted.jsonld')
7
+ @empty = read_fixture('empty.jsonld')
8
+ @mock_api = double('api')
9
+ Item.config {|x| x[:api_key] = :foo }
10
+ Item.engine.api= @mock_api
11
+ end
12
+
13
+ subject { Ansr::Dpla::Relation.new(Item, Item.table) }
14
+
15
+ describe "#initialize" do
16
+ it "should identify the correct resource for the model" do
17
+ class Foo
18
+ def self.table
19
+ nil
20
+ end
21
+ end
22
+
23
+ test = Ansr::Dpla::Relation.new(Foo, Foo.table)
24
+ expect(test.resource).to eql(:foos)
25
+ end
26
+ end
27
+
28
+ describe ".load" do
29
+ it "should fetch the appropriate REST resource" do
30
+ test = subject.where(:q=>'kittens')
31
+ @mock_api.should_receive(:items).with(:q => 'kittens').and_return('')
32
+ test.load
33
+ end
34
+
35
+ describe "Relation attributes" do
36
+ it "should set attributes correctly from a response" do
37
+ test = subject.where(:q=>'kittens')
38
+ @mock_api.stub(:items).and_return(@kittens)
39
+ test.load
40
+ expect(test.count).to eql(144)
41
+ expect(test.offset_value).to eql(0)
42
+ expect(test.limit_value).to eql(10)
43
+ end
44
+
45
+ it "should set attributes correctly for an empty response" do
46
+ test = subject.where(:q=>'kittens')
47
+ @mock_api.stub(:items).and_return(@empty)
48
+ test.load
49
+ expect(test.count).to eql(0)
50
+ expect(test.offset_value).to eql(0)
51
+ expect(test.limit_value).to eql(10)
52
+ end
53
+ end
54
+ end
55
+
56
+ describe '#all' do
57
+ it 'should fail because we don\'t have a test yet'
58
+ end
59
+
60
+ describe '#none' do
61
+ it 'should fail because we don\'t have a test yet'
62
+ end
63
+
64
+ describe '.order' do
65
+ describe 'with symbol parms' do
66
+ it "should add sort clause to query" do
67
+ test = subject.where(:q=>'kittens').order(:foo)
68
+ expect(test).to be_a(Ansr::Relation)
69
+ @mock_api.should_receive(:items).with(:q => 'kittens',:sort_by=>:foo).and_return('')
70
+ test.load
71
+ end
72
+
73
+ it "should add multiple sort clauses to query" do
74
+ test = subject.where(:q=>'kittens').order(:foo).order(:bar)
75
+ expect(test).to be_a(Ansr::Relation)
76
+ @mock_api.should_receive(:items).with(:q => 'kittens',:sort_by=>[:foo,:bar]).and_return('')
77
+ test.load
78
+ end
79
+
80
+ it "should sort in descending order if necessary" do
81
+ test = subject.where(:q=>'kittens').order(:foo => :desc)
82
+ expect(test).to be_a(Ansr::Relation)
83
+ @mock_api.should_receive(:items).with(:q => 'kittens',:sort_by=>:foo, :sort_order=>:desc).and_return('')
84
+ test.load
85
+ end
86
+ end
87
+ describe 'with String parms' do
88
+ it "should add sort clause to query" do
89
+ test = subject.where(q:'kittens').order("foo")
90
+ expect(test).to be_a(Ansr::Relation)
91
+ @mock_api.should_receive(:items).with(:q => 'kittens',:sort_by=>:foo).and_return('')
92
+ test.load
93
+ end
94
+
95
+ it "should sort in descending order if necessary" do
96
+ test = subject.where(q:'kittens').order("foo DESC")
97
+ expect(test).to be_a(Ansr::Relation)
98
+ @mock_api.should_receive(:items).with(:q => 'kittens',:sort_by=>:foo, :sort_order=>:desc).and_return('')
99
+ test.load
100
+ end
101
+ end
102
+ end
103
+
104
+ describe '#reorder' do
105
+ it "should replace existing order" do
106
+ test = subject.where(q:'kittens').order("foo DESC")
107
+ test = test.reorder("foo ASC")
108
+ expect(test).to be_a(Ansr::Relation)
109
+ @mock_api.should_receive(:items).with(:q => 'kittens',:sort_by=>:foo).and_return('')
110
+ test.load
111
+ end
112
+ end
113
+
114
+ describe '#reverse_order' do
115
+ it "should replace existing DESC order" do
116
+ test = subject.where(q:'kittens').order("foo DESC")
117
+ test = test.reverse_order
118
+ expect(test).to be_a(Ansr::Relation)
119
+ @mock_api.should_receive(:items).with(:q => 'kittens',:sort_by=>:foo).and_return('')
120
+ test.load
121
+ end
122
+
123
+ it "should replace existing ASC order" do
124
+ test = subject.where(q:'kittens').order("foo ASC")
125
+ test = test.reverse_order
126
+ expect(test).to be_a(Ansr::Relation)
127
+ @mock_api.should_receive(:items).with(:q => 'kittens',:sort_by=>:foo, :sort_order=>:desc).and_return('')
128
+ test.load
129
+ end
130
+ end
131
+
132
+ describe '#unscope' do
133
+ it 'should remove clauses only from spawned Relation' do
134
+ test = subject.where(q:'kittens').order("foo DESC")
135
+ test2 = test.unscope(:order)
136
+ expect(test2).to be_a(Ansr::Relation)
137
+ @mock_api.should_receive(:items).with(:q => 'kittens').and_return('')
138
+ test2.load
139
+ expect(test.order_values.empty?).to be_false
140
+ end
141
+ # ActiveRecord::QueryMethods.VALID_UNSCOPING_VALUES =>
142
+ # Set.new([:where, :select, :group, :order, :lock, :limit, :offset, :joins, :includes, :from, :readonly, :having])
143
+ it 'should reject bad scope keys' do
144
+ test = subject.where(q:'kittens').order("foo DESC")
145
+ expect { test.unscope(:foo) }.to raise_error
146
+ end
147
+ end
148
+
149
+ describe '#select' do
150
+ describe 'with a block given' do
151
+ it "should build an array" do
152
+ test = subject.where(q:'kittens')
153
+ @mock_api.should_receive(:items).with(:q => 'kittens').and_return(@kittens)
154
+ actual = test.select {|d| true}
155
+ expect(actual).to be_a(Array)
156
+ expect(actual.length).to eql(test.limit_value)
157
+ actual = test.select {|d| false}
158
+ expect(actual).to be_a(Array)
159
+ expect(actual.length).to eql(0)
160
+ end
161
+ end
162
+ describe 'with a String or Symbol key given' do
163
+ it 'should change the requested document fields' do
164
+ test = subject.where(q:'kittens')
165
+ @mock_api.should_receive(:items).with(:q => 'kittens', :fields=>:name).and_return('')
166
+ test = test.select('name')
167
+ test.load
168
+ end
169
+ end
170
+ describe 'with a list of keys' do
171
+ it "should add all the requested document fields" do
172
+ test = subject.where(q:'kittens')
173
+ @mock_api.should_receive(:items).with(:q => 'kittens', :fields=>[:name,:foo]).and_return('')
174
+ test = test.select(['name','foo'])
175
+ test.load
176
+ end
177
+ it "should add all the requested document fields and proxy them" do
178
+ test = subject.where(q:'kittens')
179
+ @mock_api.should_receive(:items).with(:q => 'kittens', :fields=>:object).and_return(@kittens)
180
+ test = test.select('object AS my_object')
181
+ test.load
182
+ expect(test.to_a.first['object']).to be_nil
183
+ expect(test.to_a.first['my_object']).to eql('http://ark.digitalcommonwealth.org/ark:/50959/6682xj30d/thumbnail')
184
+ end
185
+ end
186
+ end
187
+
188
+ describe '#limit' do
189
+ it "should add page_size to the query params" do
190
+ test = subject.where(q:'kittens')
191
+ @mock_api.should_receive(:items).with(:q => 'kittens', :page_size=>17).and_return('')
192
+ test = test.limit(17)
193
+ test.load
194
+ end
195
+ it "should raise an error if limit > 500" do
196
+ test = subject.where(q:'kittens')
197
+ test = test.limit(500)
198
+ expect {test.load }.to raise_error
199
+ end
200
+ end
201
+
202
+ describe '#offset' do
203
+ it "should add page to the query params when page_size is defaulted" do
204
+ test = subject.where(q:'kittens')
205
+ @mock_api.should_receive(:items).with(:q => 'kittens', :page=>3).and_return('')
206
+ test = test.offset(20)
207
+ test.load
208
+ end
209
+ it 'should raise an error if an offset is requested that is not a multiple of the page size' do
210
+ test = subject.where(q:'kittens').limit(12)
211
+ expect{test.offset(17)}.to raise_error(/^Bad/)
212
+ test.offset(24)
213
+ end
214
+ end
215
+
216
+ describe '#to_a' do
217
+ it 'should load the records and return them' do
218
+ end
219
+ end
220
+
221
+ describe '#loaded?' do
222
+ it 'should be false before and true after' do
223
+ test = subject.where(q:'kittens')
224
+ @mock_api.stub(:items).with(:q => 'kittens').and_return('')
225
+ expect(test.loaded?).to be_false
226
+ test.load
227
+ expect(test.loaded?).to be_true
228
+ end
229
+ end
230
+
231
+
232
+ describe '#any?'
233
+
234
+
235
+ describe '#blank?' do
236
+ it 'should load the records via to_a'
237
+
238
+ end
239
+
240
+ describe '#count' do
241
+ end
242
+
243
+ describe '#empty?' do
244
+ it 'should not make another call if records are loaded' do
245
+ test = subject.where(q:'kittens')
246
+ @mock_api.should_receive(:items).with(:q => 'kittens').once.and_return(@empty)
247
+ test.load
248
+ expect(test.loaded?).to be_true
249
+ expect(test.empty?).to be_true
250
+ end
251
+
252
+ end
253
+
254
+ describe "#spawn" do
255
+ it "should create new, independent Relations from Query methods"
256
+
257
+ end
258
+
259
+ describe 'RDBMS-specific methods' do
260
+ describe '#references' do
261
+ it 'should not do anything at all'
262
+ end
263
+ describe '#joins' do
264
+ it 'should not do anything at all'
265
+ end
266
+ describe '#distinct and #uniq' do
267
+ it 'should not do anything at all'
268
+ end
269
+ describe '#preload' do
270
+ it 'should not do anything at all'
271
+ end
272
+ describe '#includes' do
273
+ it 'should not do anything at all'
274
+ end
275
+ describe '#having' do
276
+ it 'should not do anything at all'
277
+ end
278
+ end
279
+
280
+ describe 'read-write methods' do
281
+ describe '#readonly' do
282
+ it "should blissfully ignore no-args or true"
283
+
284
+ it "should refuse values of false"
285
+
286
+ end
287
+ describe '#create_with' do
288
+ it 'should not do anything at all'
289
+ end
290
+ describe '#insert' do
291
+ it 'should not do anything at all'
292
+ end
293
+ describe '#delete' do
294
+ it 'should not do anything at all'
295
+ end
296
+ describe '#delete_all' do
297
+ it 'should not do anything at all'
298
+ end
299
+ describe '#destroy' do
300
+ it 'should not do anything at all'
301
+ end
302
+ describe '#destroy_all' do
303
+ it 'should not do anything at all'
304
+ end
305
+ end
306
+
307
+ end
@@ -0,0 +1,36 @@
1
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', '..', 'lib'))
2
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
3
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'app/models'))
4
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
5
+
6
+ require 'rspec/autorun'
7
+ require 'loggable'
8
+ require 'ansr'
9
+ require 'ansr_dpla'
10
+ require 'adpla_test_api'
11
+ require 'blacklight'
12
+ require 'item'
13
+ require 'collection'
14
+
15
+ RSpec.configure do |config|
16
+
17
+ end
18
+
19
+ def fixture_path(path)
20
+ File.join(File.dirname(__FILE__), '..', 'fixtures', path)
21
+ end
22
+
23
+ def fixture path, &block
24
+ if block_given?
25
+ open(fixture_path(path)) &block
26
+ else
27
+ open(fixture_path(path))
28
+ end
29
+ end
30
+
31
+ def read_fixture(path)
32
+ _f = fixture(path)
33
+ _f.read
34
+ ensure
35
+ _f and _f.close
36
+ end
@@ -0,0 +1,14 @@
1
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'app/models'))
2
+ require 'rails'
3
+ require 'adpla'
4
+ require 'item'
5
+ class Logger
6
+ def info(msg)
7
+ puts msg
8
+ end
9
+ alias :warn :info
10
+ alias :error :info
11
+ alias :debug :info
12
+ end
13
+
14
+ puts Item.table.inspect
@@ -0,0 +1,52 @@
1
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'app/models'))
2
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', '..', 'lib'))
3
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
4
+ require 'ansr_dpla'
5
+ require 'item'
6
+
7
+ # you config the model with a hash including an API key for dp.la/v2, or the path to a YAML file
8
+ open('config/dpla.yml') do |blob|
9
+ Item.config {|x| x.merge! YAML.load(blob)}
10
+ end
11
+
12
+ # then you can find single items with known IDs
13
+ puts Item.find("7eb617e559007e1ad6d95bd30a30b16b")
14
+
15
+
16
+ # or you can search with ActiveRecord::Relations
17
+ opts = {q: 'kittens', facets: 'sourceResource.contributor'}
18
+ rel = Item.where(opts)
19
+ # this means the results are lazy-loaded, #load or #to_a will load them
20
+ rel.to_a
21
+ # you can also assemble the queries piecemeal with the Relation's decorator pattern
22
+ # the where decorator adds query fields
23
+ # the select decorator adds response fields
24
+ # the limit decorator adds a page size limit
25
+ # the offset decorator adds a non-zero starting point in the response set
26
+ # the filter decorator adds filter/facet fields and optionally values to query them on
27
+ rel = Item.where(q: 'kittens').limit(2).facet('sourceResource.contributor').select('sourceResource.title')
28
+ rel.to_a.each do |item|
29
+ puts "#{item["id"]} \"#{item['sourceResource.title']}\""
30
+ end
31
+ # the filter values for the query are available on the relation after it is loaded
32
+ rel.filters.each do |k,f|
33
+ puts "#{k} values"
34
+ f.items.each do |item|
35
+ puts "filter: \"#{item.value}\" : #{item.hits}"
36
+ end
37
+ end
38
+ # the loaded Relation has attributes describing the response set
39
+ rel.count # the size of the response
40
+ # the where decorator can be negated
41
+ rel = rel.where.not(q: 'cats')
42
+
43
+ rel.to_a.each do |item|
44
+ puts "#{item["id"]} \"#{item['sourceResource.title']}\" \"#{item['originalRecord']}\""
45
+ end
46
+
47
+ rel.filters.each do |k,f|
48
+ puts "#{k} values"
49
+ f.items.each do |item|
50
+ puts " \"#{item.value}\" : #{item.hits}"
51
+ end
52
+ end