ansr 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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