ansr_dpla 0.0.4

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 (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