ansr 0.0.1 → 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (83) hide show
  1. checksums.yaml +15 -0
  2. data/README.md +35 -0
  3. data/ansr.gemspec +2 -1
  4. data/ansr_blacklight/Gemfile +3 -0
  5. data/ansr_blacklight/README.md +37 -0
  6. data/ansr_blacklight/ansr_blacklight.gemspec +28 -0
  7. data/ansr_blacklight/lib/ansr_blacklight.rb +57 -0
  8. data/ansr_blacklight/lib/ansr_blacklight/arel.rb +6 -0
  9. data/ansr_blacklight/lib/ansr_blacklight/arel/big_table.rb +57 -0
  10. data/ansr_blacklight/lib/ansr_blacklight/arel/visitors.rb +6 -0
  11. data/ansr_blacklight/lib/ansr_blacklight/arel/visitors/query_builder.rb +217 -0
  12. data/ansr_blacklight/lib/ansr_blacklight/arel/visitors/to_no_sql.rb +14 -0
  13. data/ansr_blacklight/lib/ansr_blacklight/base.rb +21 -0
  14. data/ansr_blacklight/lib/ansr_blacklight/connection_adapters/no_sql_adapter.rb +38 -0
  15. data/ansr_blacklight/lib/ansr_blacklight/model/querying.rb +29 -0
  16. data/ansr_blacklight/lib/ansr_blacklight/relation.rb +50 -0
  17. data/ansr_blacklight/lib/ansr_blacklight/relation/solr_projection_methods.rb +55 -0
  18. data/ansr_blacklight/lib/ansr_blacklight/request_builders.rb +141 -0
  19. data/ansr_blacklight/lib/ansr_blacklight/solr.rb +4 -0
  20. data/ansr_blacklight/lib/ansr_blacklight/solr/request.rb +46 -0
  21. data/ansr_blacklight/lib/ansr_blacklight/solr/response.rb +94 -0
  22. data/ansr_blacklight/lib/ansr_blacklight/solr/response/group.rb +32 -0
  23. data/ansr_blacklight/lib/ansr_blacklight/solr/response/group_response.rb +50 -0
  24. data/ansr_blacklight/lib/ansr_blacklight/solr/response/more_like_this.rb +14 -0
  25. data/ansr_blacklight/lib/ansr_blacklight/solr/response/pagination_methods.rb +35 -0
  26. data/ansr_blacklight/lib/ansr_blacklight/solr/response/spelling.rb +92 -0
  27. data/ansr_blacklight/spec/fixtures/config.yml +0 -0
  28. data/ansr_blacklight/spec/lib/loaded_relation_spec.rb +223 -0
  29. data/ansr_blacklight/spec/lib/queryable_relation_spec.rb +133 -0
  30. data/ansr_blacklight/spec/lib/relation/faceting_spec.rb +475 -0
  31. data/ansr_blacklight/spec/lib/relation/grouping_spec.rb +159 -0
  32. data/ansr_blacklight/spec/spec_helper.rb +72 -0
  33. data/ansr_dpla/Gemfile +3 -0
  34. data/ansr_dpla/Gemfile.lock +138 -0
  35. data/ansr_dpla/README.md +2 -2
  36. data/ansr_dpla/ansr_dpla.gemspec +2 -2
  37. data/ansr_dpla/lib/ansr_dpla.rb +3 -0
  38. data/ansr_dpla/lib/ansr_dpla/api.rb +3 -3
  39. data/ansr_dpla/lib/ansr_dpla/arel.rb +1 -2
  40. data/ansr_dpla/lib/ansr_dpla/arel/big_table.rb +4 -0
  41. data/ansr_dpla/lib/ansr_dpla/arel/visitors.rb +6 -0
  42. data/ansr_dpla/lib/ansr_dpla/arel/visitors/query_builder.rb +188 -0
  43. data/ansr_dpla/lib/ansr_dpla/arel/visitors/to_no_sql.rb +9 -0
  44. data/ansr_dpla/lib/ansr_dpla/connection_adapters/no_sql_adapter.rb +58 -0
  45. data/ansr_dpla/lib/ansr_dpla/model/base.rb +7 -0
  46. data/ansr_dpla/lib/ansr_dpla/model/querying.rb +6 -10
  47. data/ansr_dpla/lib/ansr_dpla/relation.rb +61 -0
  48. data/ansr_dpla/lib/ansr_dpla/request.rb +5 -0
  49. data/ansr_dpla/spec/lib/api_spec.rb +8 -5
  50. data/ansr_dpla/spec/lib/item_spec.rb +2 -2
  51. data/ansr_dpla/spec/lib/relation/facet_spec.rb +27 -19
  52. data/ansr_dpla/spec/lib/relation/select_spec.rb +10 -8
  53. data/ansr_dpla/spec/lib/relation/where_spec.rb +1 -1
  54. data/ansr_dpla/spec/lib/relation_spec.rb +31 -29
  55. data/ansr_dpla/test/system.rb +4 -2
  56. data/lib/ansr.rb +7 -0
  57. data/lib/ansr/arel.rb +3 -0
  58. data/lib/ansr/arel/big_table.rb +43 -3
  59. data/lib/ansr/arel/configured_field.rb +19 -0
  60. data/lib/ansr/arel/nodes.rb +41 -0
  61. data/lib/ansr/arel/visitors.rb +7 -0
  62. data/lib/ansr/arel/visitors/context.rb +13 -0
  63. data/lib/ansr/arel/visitors/query_builder.rb +47 -0
  64. data/lib/ansr/arel/visitors/to_no_sql.rb +41 -0
  65. data/lib/ansr/base.rb +29 -1
  66. data/lib/ansr/configurable.rb +6 -12
  67. data/lib/ansr/connection_adapters.rb +5 -0
  68. data/lib/ansr/connection_adapters/no_sql_adapter.rb +80 -0
  69. data/lib/ansr/dummy_associations.rb +105 -0
  70. data/lib/ansr/facets.rb +103 -0
  71. data/lib/ansr/model.rb +17 -107
  72. data/lib/ansr/model/connection_handler.rb +6 -0
  73. data/lib/ansr/relation.rb +40 -23
  74. data/lib/ansr/relation/group.rb +31 -0
  75. data/lib/ansr/relation/predicate_builder.rb +106 -0
  76. data/lib/ansr/relation/query_methods.rb +192 -45
  77. data/lib/ansr/sanitization.rb +5 -18
  78. data/lib/ansr/utils.rb +89 -0
  79. data/lib/ansr/version.rb +1 -1
  80. metadata +73 -25
  81. data/ansr_dpla/lib/ansr_dpla/arel/connection.rb +0 -81
  82. data/ansr_dpla/lib/ansr_dpla/arel/query_builder.rb +0 -131
  83. data/lib/ansr/model/connection.rb +0 -103
@@ -0,0 +1,14 @@
1
+ module Ansr::Blacklight::Arel::Visitors
2
+ class ToNoSql < Ansr::Arel::Visitors::ToNoSql
3
+
4
+ def initialize(table)
5
+ super(table)
6
+ end
7
+
8
+ def query_builder()
9
+ Ansr::Blacklight::Arel::Visitors::QueryBuilder.new(table)
10
+ end
11
+
12
+ end
13
+
14
+ end
@@ -0,0 +1,21 @@
1
+ module Ansr::Blacklight
2
+ class Base < Ansr::Base
3
+ include Ansr::Blacklight::Model::Querying
4
+
5
+ self.abstract_class = true
6
+
7
+ def self.solr_search_params_logic
8
+ @solr_search_params_logic || []
9
+ end
10
+
11
+ def self.solr_search_params_logic=(vals)
12
+ @solr_search_params_logic=vals
13
+ end
14
+
15
+ def self.build_default_scope
16
+ rel = super
17
+ solr_search_params_logic.each {|method| rel = self.send(method, rel)}
18
+ rel
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,38 @@
1
+ module Ansr::Blacklight::ConnectionAdapters
2
+ class NoSqlAdapter < Ansr::ConnectionAdapters::NoSqlAdapter
3
+
4
+ def self.connection_for(klass)
5
+ Ansr::Blacklight.solr
6
+ end
7
+
8
+ def initialize(klass, logger = nil, pool = nil) #:nodoc:
9
+ super(klass, klass.solr, logger, pool)
10
+ # the RSolr class has one query method, with the name of the selector the first parm?
11
+ @method = :send_and_receive
12
+ @visitor = Ansr::Blacklight::Arel::Visitors::ToNoSql.new(@table)
13
+ end
14
+
15
+ # RSolr
16
+ def raw_connection
17
+ @connection
18
+ end
19
+
20
+ def adapter_name
21
+ 'Solr'
22
+ end
23
+
24
+ def to_sql(*args)
25
+ to_nosql(*args)
26
+ end
27
+
28
+ def execute(query, name='ANSR-SOLR')
29
+ query = query.dup
30
+ # TODO: execution context to assign :post to params[:method]
31
+ params = {params: query, method: :get}
32
+ params[:data] = params.delete(:params) if params[:method] == :post
33
+ raw_response = eval(@connection.send(@method, query.path, params))
34
+ Ansr::Blacklight::Solr::Response.new(raw_response, raw_response['params'])
35
+ end
36
+
37
+ end
38
+ end
@@ -0,0 +1,29 @@
1
+ module Ansr::Blacklight::Model
2
+ module Querying
3
+ extend ActiveSupport::Concern
4
+
5
+ module ClassMethods
6
+
7
+ def solr
8
+ Ansr::Blacklight.solr
9
+ end
10
+
11
+ def build_default_scope
12
+ rel = Ansr::Blacklight::Relation.new(model(), table())
13
+ rel
14
+ end
15
+
16
+ def unique_key
17
+ table().unique_key
18
+ end
19
+
20
+ def default_connection_handler
21
+ @default_connection_handler ||= Ansr::Model::ConnectionHandler.new(Ansr::Blacklight::ConnectionAdapters::NoSqlAdapter)
22
+ end
23
+
24
+ def references
25
+ []
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,50 @@
1
+ module Ansr::Blacklight
2
+ class Relation < Ansr::Relation
3
+ include Ansr::Blacklight::SolrProjectionMethods
4
+ delegate :blacklight_config, to: :model
5
+
6
+ # some compatibility aliases that should go away to properly genericize
7
+ alias :facets :filters
8
+
9
+ delegate :docs, to: :response
10
+ delegate :params, to: :response
11
+
12
+ # overrides for query response handling
13
+ def docs_from(response)
14
+ response.docs
15
+ end
16
+
17
+ def filters_from(response)
18
+ response.facets
19
+ end
20
+
21
+ def count
22
+ response.total
23
+ end
24
+
25
+ # overrides for weird Blacklight expectations
26
+ def max_pages
27
+ if Kaminari.config.respond_to? :max_pages
28
+ nil
29
+ else
30
+ super
31
+ end
32
+ end
33
+
34
+ def limit_value
35
+ (super || default_limit_value) + 1
36
+ end
37
+
38
+ def build_arel
39
+ arel = super
40
+ solr_props = {}
41
+ solr_props[:defType] = defType_value if defType_value
42
+ solr_props[:wt] = wt_value if wt_value
43
+ unless solr_props.empty?
44
+ prop_node = Ansr::Arel::Nodes::ProjectionTraits.new arel.grouping(arel.projections), solr_props
45
+ arel.projections = [prop_node]
46
+ end
47
+ arel
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,55 @@
1
+ module Ansr::Blacklight
2
+ module SolrProjectionMethods
3
+ def defType_value
4
+ @values[:defType]
5
+ end
6
+
7
+ def defType_value=(value)
8
+ raise ImmutableRelation if @loaded
9
+ @values[:defType] = value
10
+ end
11
+
12
+ def defType(value)
13
+ spawn.defType!(value)
14
+ end
15
+
16
+ def defType!(value)
17
+ self.defType_value= value
18
+ self
19
+ end
20
+
21
+ def defType_unscoping
22
+ end
23
+
24
+ def wt_value
25
+ @values[:wt]
26
+ end
27
+
28
+ def wt_value=(value)
29
+ raise ImmutableRelation if @loaded
30
+ @values[:wt] = value
31
+ end
32
+
33
+ def wt(value)
34
+ spawn.wt!(value)
35
+ end
36
+
37
+ def wt!(value)
38
+ self.wt_value= (value)
39
+ self
40
+ end
41
+
42
+ def wt_unscoping
43
+ end
44
+
45
+ # omitHeader
46
+
47
+ # timeAllowed
48
+
49
+ # debug (true, :timing, :query, :results)
50
+
51
+ # explainOther
52
+
53
+ # debug.explain.structured
54
+ end
55
+ end
@@ -0,0 +1,141 @@
1
+ module Ansr::Blacklight
2
+ ##
3
+ # This module contains methods that transform user parameters into parameters that are sent
4
+ # as a request to Solr when RequestBuilders#solr_search_params is called.
5
+ #
6
+ module RequestBuilders
7
+ extend ActiveSupport::Concern
8
+
9
+ def local_field_params(facet_field)
10
+ cf = table[facet_field]
11
+ if (cf.is_a? Ansr::Arel::ConfiguredField)
12
+ return cf.config.fetch(:local, {})
13
+ else
14
+ return {}
15
+ end
16
+ end
17
+ # A helper method used for generating solr LocalParams, put quotes
18
+ # around the term unless it's a bare-word. Escape internal quotes
19
+ # if needed.
20
+ def solr_param_quote(val, options = {})
21
+ options[:quote] ||= '"'
22
+ unless val =~ /^[a-zA-Z0-9$_\-\^]+$/
23
+ val = options[:quote] +
24
+ # Yes, we need crazy escaping here, to deal with regexp esc too!
25
+ val.gsub("'", "\\\\\'").gsub('"', "\\\\\"") +
26
+ options[:quote]
27
+ end
28
+ return val
29
+ end
30
+
31
+ ##
32
+ # Take the user-entered query, and put it in the solr params,
33
+ # including config's "search field" params for current search field.
34
+ # also include setting spellcheck.q.
35
+ def add_query_to_solr(field_key, value, opts={})
36
+ ###
37
+ # Merge in search field configured values, if present, over-writing general
38
+ # defaults
39
+ ###
40
+
41
+ if (::Arel::Nodes::As === field_key)
42
+ solr_request[:qt] = field_key.right.to_s
43
+ field_key = field_key.left
44
+ end
45
+
46
+ search_field = table[field_key]
47
+ ##
48
+ # Create Solr 'q' including the user-entered q, prefixed by any
49
+ # solr LocalParams in config, using solr LocalParams syntax.
50
+ # http://wiki.apache.org/solr/LocalParams
51
+ ##
52
+ if (Ansr::Arel::ConfiguredField === search_field && !search_field.config.empty?)
53
+ local_params = search_field.config.fetch(:local,{}).merge(opts).collect do |key, val|
54
+ key.to_s + "=" + solr_param_quote(val, :quote => "'")
55
+ end.join(" ")
56
+ solr_request[:q] = local_params.empty? ? value : "{!#{local_params}}#{value}"
57
+ search_field.config.fetch(:query,{}).each do |k,v|
58
+ solr_request[k] = v
59
+ end
60
+ else
61
+ solr_request[:q] = value if value
62
+ end
63
+
64
+ ##
65
+ # Set Solr spellcheck.q to be original user-entered query, without
66
+ # our local params, otherwise it'll try and spellcheck the local
67
+ # params! Unless spellcheck.q has already been set by someone,
68
+ # respect that.
69
+ #
70
+ # TODO: Change calling code to expect this as a symbol instead of
71
+ # a string, for consistency? :'spellcheck.q' is a symbol. Right now
72
+ # rspec tests for a string, and can't tell if other code may
73
+ # insist on a string.
74
+ solr_request["spellcheck.q"] = value unless solr_request["spellcheck.q"]
75
+ end
76
+
77
+ ##
78
+ # Add any existing facet limits, stored in app-level HTTP query
79
+ # as :f, to solr as appropriate :fq query.
80
+ def add_filter_fq_to_solr(solr_request, user_params)
81
+
82
+ # convert a String value into an Array
83
+ if solr_request[:fq].is_a? String
84
+ solr_request[:fq] = [solr_request[:fq]]
85
+ end
86
+
87
+ # :fq, map from :f.
88
+ if ( user_params[:f])
89
+ f_request_params = user_params[:f]
90
+
91
+ f_request_params.each_pair do |facet_field, value_list|
92
+ opts = local_field_params(facet_field).merge(user_params.fetch(:opts,{}))
93
+ Array(value_list).each do |value|
94
+ solr_request.append_filter_query filter_value_to_fq_string(facet_field, value, user_params[:opts])
95
+ end
96
+ end
97
+ end
98
+ end
99
+
100
+ def with_ex_local_param(ex, value)
101
+ if ex
102
+ "{!ex=#{ex}}#{value}"
103
+ else
104
+ value
105
+ end
106
+ end
107
+
108
+ private
109
+
110
+ ##
111
+ # Convert a filter/value pair into a solr fq parameter
112
+ def filter_value_to_fq_string(facet_key, value, facet_opts=nil)
113
+ facet_field = table[facet_key]
114
+ facet_config = (Ansr::Arel::ConfiguredField === facet_field)
115
+ facet_default = (::Arel.star == facet_key)
116
+ local_params = local_field_params(facet_key)
117
+ local_params.merge!(facet_opts) if facet_opts
118
+ local_params = local_params.collect {|k,v| "#{k.to_s}=#{v.to_s}"}
119
+
120
+ prefix = ""
121
+ prefix = "{!#{local_params.join(" ")}}" unless local_params.empty?
122
+
123
+ fq = case
124
+ when facet_default
125
+ ""
126
+ when (facet_config and facet_field.date),
127
+ (value.is_a?(TrueClass) or value.is_a?(FalseClass) or value == 'true' or value == 'false'),
128
+ (value.is_a?(Integer) or (value.to_i.to_s == value if value.respond_to? :to_i)),
129
+ (value.is_a?(Float) or (value.to_f.to_s == value if value.respond_to? :to_f))
130
+ (value.is_a?(DateTime) or value.is_a?(Date) or value.is_a?(Time))
131
+ "#{prefix}#{facet_field.name}:#{value}"
132
+ when value.is_a?(Range)
133
+ "#{prefix}#{facet_field.name}:[#{value.first} TO #{value.last}]"
134
+ else
135
+ "{!raw f=#{facet_field.name}#{(" " + local_params.join(" ")) unless local_params.empty?}}#{value}"
136
+ end
137
+
138
+
139
+ end
140
+ end
141
+ end
@@ -0,0 +1,4 @@
1
+ module Ansr::Blacklight::Solr
2
+ require 'ansr_blacklight/solr/request'
3
+ require 'ansr_blacklight/solr/response'
4
+ end
@@ -0,0 +1,46 @@
1
+ module Ansr::Blacklight::Solr
2
+ class Request < ::HashWithIndifferentAccess
3
+ attr_accessor :path
4
+
5
+ SINGULAR_KEYS = %W{ facet fl q qt rows start spellcheck spellcheck.q sort
6
+ per_page wt hl group defType}
7
+ ARRAY_KEYS = %W{facet.field facet.query facet.pivot fq hl.fl }
8
+
9
+ def initialize(constructor = {})
10
+ if constructor.is_a?(Hash)
11
+ super()
12
+ update(constructor)
13
+ else
14
+ super(constructor)
15
+ end
16
+ ARRAY_KEYS.each do |key|
17
+ self[key] ||= []
18
+ end
19
+ end
20
+
21
+ def append_filter_query(query)
22
+ self['fq'] << query
23
+ end
24
+
25
+ def append_facet_fields(values)
26
+ (self['facet.field'] += Array(values)).uniq!
27
+ self['facet'] = true unless values.blank?
28
+ end
29
+
30
+ def append_facet_query(values)
31
+ self['facet.query'] += Array(values)
32
+ end
33
+
34
+ def append_facet_pivot(query)
35
+ self['facet.pivot'] << query
36
+ end
37
+
38
+ def append_highlight_field(query)
39
+ self['hl.fl'] << query
40
+ end
41
+
42
+ def to_hash
43
+ reject {|key, value| ARRAY_KEYS.include?(key) && value.blank?}
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,94 @@
1
+ ## copied directly from Blacklight::SolrResponse
2
+ class Ansr::Blacklight::Solr::Response < HashWithIndifferentAccess
3
+
4
+ require 'ansr_blacklight/solr/response/pagination_methods'
5
+
6
+ autoload :Spelling, 'ansr_blacklight/solr/response/spelling'
7
+ autoload :MoreLikeThis, 'ansr_blacklight/solr/response/more_like_this'
8
+ autoload :GroupResponse, 'ansr_blacklight/solr/response/group_response'
9
+ autoload :Group, 'ansr_blacklight/solr/response/group'
10
+
11
+ include PaginationMethods
12
+
13
+ attr_reader :request_params
14
+ def initialize(data, request_params)
15
+ super(data)
16
+ @request_params = request_params
17
+ extend Spelling
18
+ extend Ansr::Facets
19
+ extend Response
20
+ extend MoreLikeThis
21
+ end
22
+
23
+ def header
24
+ self['responseHeader']
25
+ end
26
+
27
+ def update(other_hash)
28
+ other_hash.each_pair { |key, value| self[key] = value }
29
+ self
30
+ end
31
+
32
+ def params
33
+ (header and header['params']) ? header['params'] : request_params
34
+ end
35
+
36
+ def rows
37
+ params[:rows].to_i
38
+ end
39
+
40
+ def docs
41
+ @docs ||= begin
42
+ response['docs'] || []
43
+ end
44
+ end
45
+
46
+ def spelling
47
+ self['spelling']
48
+ end
49
+
50
+ def grouped(model)
51
+ @groups ||= self["grouped"].map do |field, group|
52
+ # grouped responses can either be grouped by:
53
+ # - field, where this key is the field name, and there will be a list
54
+ # of documents grouped by field value, or:
55
+ # - function, where the key is the function, and the documents will be
56
+ # further grouped by function value, or:
57
+ # - query, where the key is the query, and the matching documents will be
58
+ # in the doclist on THIS object
59
+ if group["groups"] # field or function
60
+ GroupResponse.new field, model, group, self
61
+ else # query
62
+ Group.new({field => field}, model, group, self)
63
+ end
64
+ end
65
+ end
66
+
67
+ def group key
68
+ grouped.select { |x| x.key == key }.first
69
+ end
70
+
71
+ def grouped?
72
+ self.has_key? "grouped"
73
+ end
74
+
75
+ module Response
76
+ def response
77
+ self[:response] || {}
78
+ end
79
+
80
+ # short cut to response['numFound']
81
+ def total
82
+ response[:numFound].to_s.to_i
83
+ end
84
+
85
+ def start
86
+ response[:start].to_s.to_i
87
+ end
88
+
89
+ def empty?
90
+ total == 0
91
+ end
92
+
93
+ end
94
+ end