ansr_blacklight 0.0.3

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.
@@ -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