ansr 0.0.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 (55) hide show
  1. data/.gitignore +2 -0
  2. data/Gemfile +3 -0
  3. data/Gemfile.lock +127 -0
  4. data/ansr.gemspec +23 -0
  5. data/ansr_dpla/README.md +59 -0
  6. data/ansr_dpla/ansr_dpla.gemspec +27 -0
  7. data/ansr_dpla/app/models/collection.rb +2 -0
  8. data/ansr_dpla/app/models/item.rb +2 -0
  9. data/ansr_dpla/fixtures/collection.json +1 -0
  10. data/ansr_dpla/fixtures/collection.jsonld +14 -0
  11. data/ansr_dpla/fixtures/collections.json +1 -0
  12. data/ansr_dpla/fixtures/collections.jsonld +65 -0
  13. data/ansr_dpla/fixtures/dpla.yml +2 -0
  14. data/ansr_dpla/fixtures/empty.jsonld +1 -0
  15. data/ansr_dpla/fixtures/item.json +1 -0
  16. data/ansr_dpla/fixtures/item.jsonld +272 -0
  17. data/ansr_dpla/fixtures/kittens.json +1 -0
  18. data/ansr_dpla/fixtures/kittens.jsonld +2477 -0
  19. data/ansr_dpla/fixtures/kittens_faceted.json +1 -0
  20. data/ansr_dpla/fixtures/kittens_faceted.jsonld +2693 -0
  21. data/ansr_dpla/lib/ansr_dpla.rb +6 -0
  22. data/ansr_dpla/lib/ansr_dpla/api.rb +78 -0
  23. data/ansr_dpla/lib/ansr_dpla/arel.rb +8 -0
  24. data/ansr_dpla/lib/ansr_dpla/arel/big_table.rb +104 -0
  25. data/ansr_dpla/lib/ansr_dpla/arel/connection.rb +81 -0
  26. data/ansr_dpla/lib/ansr_dpla/arel/query_builder.rb +131 -0
  27. data/ansr_dpla/lib/ansr_dpla/model.rb +7 -0
  28. data/ansr_dpla/lib/ansr_dpla/model/base.rb +17 -0
  29. data/ansr_dpla/lib/ansr_dpla/model/pseudo_associate.rb +14 -0
  30. data/ansr_dpla/lib/ansr_dpla/model/querying.rb +38 -0
  31. data/ansr_dpla/spec/adpla_test_api.rb +9 -0
  32. data/ansr_dpla/spec/lib/api_spec.rb +110 -0
  33. data/ansr_dpla/spec/lib/item_spec.rb +57 -0
  34. data/ansr_dpla/spec/lib/relation/facet_spec.rb +74 -0
  35. data/ansr_dpla/spec/lib/relation/select_spec.rb +52 -0
  36. data/ansr_dpla/spec/lib/relation/where_spec.rb +74 -0
  37. data/ansr_dpla/spec/lib/relation_spec.rb +305 -0
  38. data/ansr_dpla/spec/spec_helper.rb +36 -0
  39. data/ansr_dpla/test/debug.rb +14 -0
  40. data/ansr_dpla/test/system.rb +50 -0
  41. data/lib/ansr.rb +16 -0
  42. data/lib/ansr/.DS_Store +0 -0
  43. data/lib/ansr/arel.rb +5 -0
  44. data/lib/ansr/arel/big_table.rb +24 -0
  45. data/lib/ansr/base.rb +29 -0
  46. data/lib/ansr/configurable.rb +20 -0
  47. data/lib/ansr/model.rb +159 -0
  48. data/lib/ansr/model/connection.rb +103 -0
  49. data/lib/ansr/model/connection_handler.rb +20 -0
  50. data/lib/ansr/relation.rb +121 -0
  51. data/lib/ansr/relation/arel_methods.rb +14 -0
  52. data/lib/ansr/relation/query_methods.rb +156 -0
  53. data/lib/ansr/sanitization.rb +36 -0
  54. data/lib/ansr/version.rb +6 -0
  55. metadata +196 -0
@@ -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,50 @@
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
+ Item.config('config/dpla.yml')
9
+
10
+ # then you can find single items with known IDs
11
+ puts Item.find("7eb617e559007e1ad6d95bd30a30b16b")
12
+
13
+
14
+ # or you can search with ActiveRecord::Relations
15
+ opts = {q: 'kittens', facets: 'sourceResource.contributor'}
16
+ rel = Item.where(opts)
17
+ # this means the results are lazy-loaded, #load or #to_a will load them
18
+ rel.to_a
19
+ # you can also assemble the queries piecemeal with the Relation's decorator pattern
20
+ # the where decorator adds query fields
21
+ # the select decorator adds response fields
22
+ # the limit decorator adds a page size limit
23
+ # the offset decorator adds a non-zero starting point in the response set
24
+ # the filter decorator adds filter/facet fields and optionally values to query them on
25
+ rel = Item.where(q: 'kittens').limit(2).filter('sourceResource.contributor').select('sourceResource.title')
26
+ rel.to_a.each do |item|
27
+ puts "#{item["id"]} \"#{item['sourceResource.title']}\""
28
+ end
29
+ # the filter values for the query are available on the relation after it is loaded
30
+ rel.filters.each do |k,f|
31
+ puts "#{k} values"
32
+ f.items.each do |item|
33
+ puts "filter: \"#{item.value}\" : #{item.hits}"
34
+ end
35
+ end
36
+ # the loaded Relation has attributes describing the response set
37
+ rel.count # the size of the response
38
+ # the where decorator can be negated
39
+ rel = rel.where.not(q: 'cats')
40
+
41
+ rel.to_a.each do |item|
42
+ puts "#{item["id"]} \"#{item['sourceResource.title']}\" \"#{item['originalRecord']}\""
43
+ end
44
+
45
+ rel.filters.each do |k,f|
46
+ puts "#{k} values"
47
+ f.items.each do |item|
48
+ puts " \"#{item.value}\" : #{item.hits}"
49
+ end
50
+ end
@@ -0,0 +1,16 @@
1
+ require 'active_support'
2
+ module Ansr
3
+ extend ActiveSupport::Autoload
4
+ eager_autoload do
5
+ autoload :Configurable
6
+ autoload :Arel
7
+ autoload :Base
8
+ autoload :Model
9
+ autoload :Sanitization
10
+ autoload :Relation
11
+ autoload_under 'relation' do
12
+ autoload :ArelMethods
13
+ autoload :QueryMethods
14
+ end
15
+ end
16
+ end
Binary file
@@ -0,0 +1,5 @@
1
+ module Ansr
2
+ module Arel
3
+ require 'ansr/arel/big_table'
4
+ end
5
+ end
@@ -0,0 +1,24 @@
1
+ module Ansr
2
+ module Arel
3
+ class BigTable < ::Arel::Table
4
+ attr_reader :fields, :facets, :sorts
5
+
6
+ include Ansr::Configurable
7
+
8
+ attr_reader :klass
9
+ alias :model :klass
10
+
11
+ def initialize(klass, engine=nil)
12
+ super(klass.name, engine.nil? ? klass.engine : engine)
13
+ @klass = klass.model
14
+ @fields = []
15
+ @facets = []
16
+ @sorts = []
17
+ end
18
+
19
+ def view?
20
+ Ansr::Model::ViewProxy === model()
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,29 @@
1
+ module Ansr
2
+ class Base < ActiveRecord::Base
3
+ extend Ansr::Model::Methods
4
+ extend Ansr::Configurable
5
+ extend Ansr::QueryMethods
6
+ extend Ansr::ArelMethods
7
+ include Ansr::Sanitization
8
+
9
+ self.abstract_class = true
10
+
11
+ def initialize doc={}, options={}
12
+ super(filter_source_hash(doc), options)
13
+ @source_doc = doc
14
+ end
15
+
16
+ def filter_source_hash(doc)
17
+ fields = self.class.model().table().fields()
18
+ filtered = doc.select do |k,v|
19
+ fields.include? k.to_sym
20
+ end
21
+ filtered.with_indifferent_access
22
+ end
23
+
24
+ def [](key)
25
+ @source_doc[key]
26
+ end
27
+
28
+ end
29
+ end
@@ -0,0 +1,20 @@
1
+ module Ansr
2
+ module Configurable
3
+ def config(yaml=nil)
4
+ yaml ? @config ||= begin
5
+ y = begin
6
+ case yaml
7
+ when String
8
+ File.open(yaml) {|blob| YAML.load(blob)}
9
+ else
10
+ yaml
11
+ end
12
+ end
13
+ y
14
+ end : @config
15
+ end
16
+
17
+ alias_method :configure, :config
18
+
19
+ end
20
+ end
@@ -0,0 +1,159 @@
1
+ module Ansr
2
+ module Model
3
+ module Methods
4
+ def spawn
5
+ s = Ansr::Relation.new(model(), table())
6
+ s.references!(references())
7
+ end
8
+
9
+ def inherited(subclass)
10
+ super
11
+ # a hack for sanitize sql overrides to work, and some others where @klass used in place of klass()
12
+ subclass.instance_variable_set("@klass", subclass)
13
+ # a hack for the intermediate abstract model classes to work with table_name
14
+ subclass.instance_variable_set("@table_name", subclass.name)
15
+ end
16
+
17
+ def model
18
+ m = begin
19
+ instance_variable_get "@klass"
20
+ end
21
+ raise "#{name()}.model() -> nil" unless m
22
+ m
23
+ end
24
+
25
+ def references
26
+ []
27
+ end
28
+
29
+ def table
30
+ raise 'Implementing classes must provide a BigTable reader'
31
+ end
32
+
33
+ def table=(table)
34
+ raise 'Implementing classes must provide a BigTable writer'
35
+ end
36
+
37
+ def engine
38
+ model()
39
+ end
40
+
41
+ def model
42
+ @klass
43
+ end
44
+
45
+ def build_default_scope
46
+ Ansr::Relation.new(model(), table())
47
+ end
48
+
49
+ def view(*wheres)
50
+ return ViewProxy.new(model(), *wheres)
51
+ end
52
+ end
53
+
54
+ class ViewProxy
55
+ attr_accessor :model
56
+ def initialize(model, *wheres)
57
+ if ViewProxy === model
58
+ @model = model.model
59
+ self.constraints = Array(model.constraints)
60
+ self.projections = model.projections.dup
61
+ else
62
+ @model = model
63
+ end
64
+ if wheres[0]
65
+ self.constraints = self.constraints + wheres
66
+ end
67
+ end
68
+
69
+ def view_arel(engine, table)
70
+ arel = ::Arel::SelectManager.new(engine, table)
71
+ constraints.each {|c| arel.where(c) }
72
+ arel.project projections()
73
+ arel
74
+ end
75
+
76
+ def find_by_nosql(arel, bind_values)
77
+ filter_context = nil
78
+ if self.view?
79
+ filter_context = view_arel(arel.engine, arel.source.left)
80
+ arel = arel.intersect filter_context
81
+ end
82
+ model.find_by_nosql(arel, bind_values)
83
+ end
84
+
85
+ def expand_hash_conditions_for_aggregates(attrs)
86
+ # this is a protected method in the AR sanitization module
87
+ model.send(:expand_hash_conditions_for_aggregates, attrs)
88
+ end
89
+
90
+ def constraints
91
+ @constraints ||= []
92
+ end
93
+
94
+ def constraints=(values)
95
+ @constraints = Array === (values) ? values : Array(values)
96
+ end
97
+
98
+ def projections
99
+ @projections ||= []
100
+ end
101
+
102
+ def projections=(values)
103
+ @projections = Array === (values) ? values : Array(values)
104
+ end
105
+
106
+ def view?
107
+ (@constraints and @constraints.length > 0) or (@projections and @projections.length > 0)
108
+ end
109
+
110
+ def view(*wheres)
111
+ ViewProxy.new(self, wheres)
112
+ end
113
+
114
+ def self.===(other)
115
+ other.is_a? ViewProxy
116
+ end
117
+
118
+ def connection_handler=(handler)
119
+ @connection_handler = handler
120
+ end
121
+
122
+ def build_default_scope
123
+ model().all
124
+ end
125
+
126
+ # model delegations
127
+ def connection
128
+ model().connection
129
+ end
130
+
131
+ def name
132
+ model().name
133
+ end
134
+
135
+ def table
136
+ model().table
137
+ end
138
+
139
+ def arel_table
140
+ model().arel_table
141
+ end
142
+
143
+ def current_scope=(scope)
144
+ model().current_scope=(scope)
145
+ end
146
+
147
+ def current_scope
148
+ model().current_scope
149
+ end
150
+
151
+ def method_missing(method, *args, &block)
152
+ model().send(method, *args, &block)
153
+ end
154
+ end
155
+
156
+ require 'ansr/model/connection'
157
+ require 'ansr/model/connection_handler'
158
+ end
159
+ end
@@ -0,0 +1,103 @@
1
+ module Ansr
2
+ module Model
3
+ class Connection
4
+ def initialize(klass)
5
+ @table = klass.table
6
+ end
7
+
8
+ def primary_key(table_name)
9
+ 'id'
10
+ end
11
+
12
+ def limit(arel_node)
13
+ case arel_node
14
+ when ::Arel::SelectManager
15
+ arel_node.limit
16
+ when ::Arel::Nodes::SelectStatement
17
+ arel_node.limit && arel_node.limit.expr
18
+ else
19
+ nil
20
+ end
21
+ end
22
+
23
+ def offset(arel_node)
24
+ case arel_node
25
+ when ::Arel::SelectManager
26
+ arel_node.offset
27
+ when ::Arel::Nodes::SelectStatement
28
+ arel_node.offset && arel_node.offset.expr
29
+ else
30
+ nil
31
+ end
32
+ end
33
+
34
+ def constraints(arel_node)
35
+ case arel_node
36
+ when ::Arel::SelectManager
37
+ arel_node.constraints
38
+ when ::Arel::Nodes::SelectStatement
39
+ arel_node.cores.last.wheres
40
+ else
41
+ nil
42
+ end
43
+ end
44
+
45
+ def projections(arel_node)
46
+ case arel_node
47
+ when ::Arel::SelectManager
48
+ arel_node.projections
49
+ when ::Arel::Nodes::SelectStatement
50
+ arel_node.cores.last.projections
51
+ else
52
+ nil
53
+ end
54
+ end
55
+
56
+ def orders(arel_node)
57
+ case arel_node
58
+ when ::Arel::SelectManager
59
+ arel_node.orders
60
+ when ::Arel::Nodes::SelectStatement
61
+ arel_node.orders
62
+ else
63
+ nil
64
+ end
65
+ end
66
+
67
+ def schema_cache
68
+ ActiveRecord::ConnectionAdapters::SchemaCache.new(self)
69
+ end
70
+
71
+ def table_exists?(table_name)
72
+ true
73
+ end
74
+
75
+ # this is called by the BigTable impl
76
+ def columns(table_name, *rest)
77
+ @table.fields.map {|s| ::ActiveRecord::ConnectionAdapters::Column.new(s.to_s, nil, nil)}
78
+ end
79
+
80
+ def sanitize_limit(limit_value)
81
+ if limit_value.to_s.to_i >= 0
82
+ limit_value
83
+ else
84
+ Ansr::Relation::DEFAULT_PAGE_SIZE
85
+ end
86
+ end
87
+
88
+ def sanitize_filter_name(filter_value)
89
+ if filter_value.is_a? Array
90
+ return filter_value.collect {|x| sanitize_filter_name(x)}.compact
91
+ else
92
+ if @table.facets.include? filter_value.to_sym
93
+ return filter_value
94
+ else
95
+ raise "#{filter_value} is not a facetable field"
96
+ #Rails.logger.warn "Ignoring #{filter_value} (not a filterable field)" if Rails.logger
97
+ #return nil
98
+ end
99
+ end
100
+ end
101
+ end
102
+ end
103
+ end