gsolr_ext 0.12.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.
data/lib/gsolr_ext.rb ADDED
@@ -0,0 +1,42 @@
1
+ # add this directory to the load path if it hasn't already been added
2
+
3
+ $:.unshift(File.dirname(__FILE__)) unless
4
+ $:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
5
+
6
+ require File.join(File.dirname(__FILE__), 'mash') unless defined?(Mash)
7
+
8
+ unless Hash.respond_to?(:to_mash)
9
+ class Hash
10
+ def to_mash
11
+ Mash.new(self)
12
+ end
13
+ end
14
+ end
15
+
16
+ require 'gsolr'
17
+ require 'gsolr_ext'
18
+
19
+ module GSolr
20
+ module Ext
21
+ autoload :Client, 'gsolr_ext/client.rb'
22
+ autoload :Doc, 'gsolr_ext/doc.rb'
23
+ autoload :Request, 'gsolr_ext/request.rb'
24
+ autoload :Response, 'gsolr_ext/response.rb'
25
+ autoload :Model, 'gsolr_ext/model.rb'
26
+
27
+ def self.version
28
+ @version ||= File.read(File.join(File.dirname(__FILE__), '..', 'VERSION'))
29
+ end
30
+
31
+ # modify the GSolr::Client (provides #find and #luke methods)
32
+ GSolr::Client.class_eval do
33
+ include GSolr::Ext::Client
34
+ end
35
+
36
+ # this is for backward compatibility: GSolr::Ext.connect
37
+ # recommended way is to just use GSolr.connect
38
+ def self.connect(*args, &blk)
39
+ GSolr.connect(*args, &blk)
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,65 @@
1
+ module GSolr
2
+ module Ext
3
+ module Client
4
+ # TWO modes of arguments:
5
+ #
6
+ # <request-handler-path>, <solr-params-hash>
7
+ # OR
8
+ # <solr-params-hash>
9
+ #
10
+ # The default request-handler-path is /select
11
+ #
12
+ # If a hash is used for solr params, all of the normal GSolr::Ext::Request
13
+ # mappings are available (everything else gets passed to solr).
14
+ # Returns a new GSolr::Ext::Response::Base object.
15
+ def find *args
16
+ # remove the handler arg - the first, if it is a string OR set default
17
+ path = args.first.is_a?(String) ? args.shift : '/select'
18
+ # remove the params - the first, if it is a Hash OR set default
19
+ params = args.first.kind_of?(Hash) ? args.shift : {}
20
+ # send path, map params and send the rest of the args along
21
+ response = self.request path, GSolr::Ext::Request.map(params), *args
22
+ GSolr::Ext::Response::Base.new(response, path, params)
23
+ end
24
+
25
+ # TWO modes of arguments:
26
+ #
27
+ # <request-handler-path>, <solr-params-hash>
28
+ # OR
29
+ # <solr-params-hash>
30
+ #
31
+ # The default request-handler-path is /admin/luke
32
+ # The default params are numTerms=0
33
+ #
34
+ # Returns a new Mash object.
35
+ def luke *args
36
+ path = args.first.is_a?(String) ? args.shift : '/admin/luke'
37
+ params = args.pop || {}
38
+ params['numTerms'] ||= 0
39
+ self.request(path, params).to_mash
40
+ end
41
+
42
+ # sends request to /admin/ping
43
+ def ping *args
44
+ path = args.first.is_a?(String) ? args.shift : '/admin/ping'
45
+ params = args.pop || {}
46
+ self.request(path, params).to_mash
47
+ end
48
+
49
+ # Ping the server and make sure it is alright
50
+ # solr.ping?
51
+ #
52
+ # It returns true if the server pings and the status is OK
53
+ # It returns false otherwise -- which probably cannot happen
54
+ # Or raises an exception if there is a failure to connect or
55
+ # the ping service is not activated in the solr server
56
+ #
57
+ # The default configuration point of the PingRequestHandler
58
+ # in the solr server of '/admin/ping' is assumed.
59
+ #
60
+ def ping?
61
+ ping['status'] == 'OK'
62
+ end
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,44 @@
1
+ module GSolr::Ext::Doc
2
+
3
+ # for easy access to the solr id (route helpers etc..)
4
+ def id
5
+ self['id']
6
+ end
7
+
8
+ # Helper method to check if value/multi-values exist for a given key.
9
+ # The value can be a string, or a RegExp
10
+ # Multiple "values" can be given; only one needs to match.
11
+ #
12
+ # Example:
13
+ # doc.has?(:location_facet)
14
+ # doc.has?(:location_facet, 'Clemons')
15
+ # doc.has?(:id, 'h009', /^u/i)
16
+ def has?(k, *values)
17
+ return if self[k].nil?
18
+ return true if self.key?(k) and values.empty?
19
+ target = self[k]
20
+ if target.is_a?(Array)
21
+ values.each do |val|
22
+ return target.any?{|tv| val.is_a?(Regexp) ? (tv =~ val) : (tv==val)}
23
+ end
24
+ else
25
+ return values.any? {|val| val.is_a?(Regexp) ? (target =~ val) : (target == val)}
26
+ end
27
+ end
28
+
29
+ # helper
30
+ # key is the name of the field
31
+ # opts is a hash with the following valid keys:
32
+ # - :sep - a string used for joining multivalued field values
33
+ # - :default - a value to return when the key doesn't exist
34
+ # if :sep is nil and the field is a multivalued field, the array is returned
35
+ def get key, opts={:sep=>', ', :default=>nil}
36
+ if self.key? key
37
+ val = self[key]
38
+ (val.is_a?(Array) and opts[:sep]) ? val.join(opts[:sep]) : val
39
+ else
40
+ opts[:default]
41
+ end
42
+ end
43
+
44
+ end
@@ -0,0 +1,111 @@
1
+ # include this module into a plain ruby class:
2
+ # class Book
3
+ # include GSolr::Ext::Model
4
+ # connection = GSolr::Ext.connect
5
+ # default_params = {:phrase_filters=>'type:book'}
6
+ # end
7
+ #
8
+ # Then:
9
+ # number_10 = Book.find_by_id(10)
10
+ #
11
+ module GSolr::Ext::Model
12
+
13
+ # ripped from MongoMapper!
14
+ module Pluggable
15
+
16
+ def plugins
17
+ @plugins ||= []
18
+ end
19
+
20
+ def plugin(mod)
21
+ extend mod::ClassMethods if mod.const_defined?(:ClassMethods)
22
+ include mod::InstanceMethods if mod.const_defined?(:InstanceMethods)
23
+ mod.configure(self) if mod.respond_to?(:configure)
24
+ plugins << mod
25
+ end
26
+
27
+ end
28
+
29
+ # Class level methods for altering object instances
30
+ module Callbacks
31
+
32
+ # method that only accepts a block
33
+ # The block is executed when an object is created via #new -> SolrDoc.new
34
+ # The blocks scope is the instance of the object.
35
+ def after_initialize(&blk)
36
+ hooks << blk
37
+ end
38
+
39
+ # Removes the current set of after_initialize blocks.
40
+ # You would use this if you wanted to open a class back up,
41
+ # but clear out the previously defined blocks.
42
+ def clear_after_initialize_blocks!
43
+ @hooks = []
44
+ end
45
+
46
+ # creates the @hooks container ("hooks" are blocks or procs).
47
+ # returns an array
48
+ def hooks
49
+ @hooks ||= []
50
+ end
51
+
52
+ end
53
+
54
+ #
55
+ # Findable is a module that gets mixed into the SolrDocument *class* object.
56
+ # These methods will be available through the class: SolrDocument.find
57
+ #
58
+ module Findable
59
+
60
+ attr_accessor :connection
61
+
62
+ def connection
63
+ @connection ||= GSolr::Ext.connect
64
+ end
65
+
66
+ # this method decorates the connection find method
67
+ # and then creates new instance of the class that uses this module.
68
+ def find *args, &block
69
+ response = connection.find(*args)
70
+ response.docs.map {|doc|
71
+ d = self.new doc, response
72
+ yield d if block_given?
73
+ d
74
+ }
75
+ end
76
+
77
+ end
78
+
79
+ # Called by Ruby Module API
80
+ # extends this *class* object
81
+ def self.included(base)
82
+ base.extend Pluggable
83
+ base.extend Callbacks
84
+ base.extend Findable
85
+ base.send :include, GSolr::Ext::Doc
86
+ end
87
+
88
+ attr_reader :solr_response
89
+
90
+ # The original object passed in to the #new method
91
+ attr :_source
92
+
93
+ # Constructor **for the class that is getting this module included**
94
+ # source_doc should be a hash or something similar
95
+ # calls each of after_initialize blocks
96
+ def initialize(source_doc={}, solr_response=nil)
97
+ @_source = source_doc.to_mash
98
+ @solr_response = solr_response
99
+ self.class.hooks.each do |h|
100
+ instance_eval &h
101
+ end
102
+ end
103
+
104
+ # the wrapper method to the @_source object.
105
+ # If a method is missing, it gets sent to @_source
106
+ # with all of the original params and block
107
+ def method_missing(m, *args, &b)
108
+ @_source.send(m, *args, &b)
109
+ end
110
+
111
+ end
@@ -0,0 +1,110 @@
1
+ module GSolr::Ext::Request
2
+
3
+ module Params
4
+
5
+ def map input_params
6
+ input = input_params.dup
7
+
8
+ output = {}
9
+
10
+ if input[:per_page]
11
+ output[:rows] = input.delete(:per_page).to_i
12
+ end
13
+
14
+ if page = input.delete(:page)
15
+ raise ':per_page must be set when using :page' unless output[:rows]
16
+ page = page.to_s.to_i-1
17
+ page = page < 1 ? 0 : page
18
+ output[:start] = page * output[:rows]
19
+ end
20
+
21
+ # remove the input :q params
22
+ output[:q] = input.delete :q
23
+ output[:fq] = input.delete(:fq) if input[:fq]
24
+
25
+ if queries = input.delete(:queries)
26
+ output[:q] = append_to_param output[:q], build_query(queries, false)
27
+ end
28
+ if phrases = input.delete(:phrases)
29
+ output[:q] = append_to_param output[:q], build_query(phrases, true)
30
+ end
31
+ if filters = input.delete(:filters)
32
+ output[:fq] = append_to_param output[:fq], build_query(filters), false
33
+ end
34
+ if phrase_filters = input.delete(:phrase_filters)
35
+ output[:fq] = append_to_param output[:fq], build_query(phrase_filters, true), false
36
+ end
37
+ if facets = input.delete(:facets)
38
+ output[:facet] = true
39
+ output['facet.field'] = append_to_param output['facet.field'], build_query(facets.values), false
40
+ end
41
+ output.merge input
42
+ end
43
+
44
+ end
45
+
46
+ module QueryHelpers
47
+
48
+ # Wraps a string around double quotes
49
+ def quote(value)
50
+ %("#{value}")
51
+ end
52
+
53
+ # builds a solr range query from a Range object
54
+ def build_range(r)
55
+ "[#{r.min} TO #{r.max}]"
56
+ end
57
+
58
+ # builds a solr query fragment
59
+ # if "quote_string" is true, the values will be quoted.
60
+ # if "value" is a string/symbol, the #to_s method is called
61
+ # if the "value" is an array, each item in the array is
62
+ # send to build_query (recursive)
63
+ # if the "value" is a Hash, a fielded query is built
64
+ # where the keys are used as the field names and
65
+ # the values are either processed as a Range or
66
+ # passed back into build_query (recursive)
67
+ def build_query(value, quote_string=false)
68
+ case value
69
+ when String,Symbol,Numeric
70
+ quote_string ? quote(value.to_s) : value.to_s
71
+ when Array
72
+ value.collect do |v|
73
+ build_query(v, quote_string)
74
+ end.flatten
75
+ when Range
76
+ build_range(value)
77
+ when Hash
78
+ return value.collect do |(k,v)|
79
+ if v.is_a?(Range)
80
+ "#{k}:#{build_range(v)}"
81
+ # If the value is an array, we want the same param, multiple times (not a query join)
82
+ elsif v.is_a?(Array)
83
+ v.collect do |vv|
84
+ "#{k}:#{build_query(vv, quote_string)}"
85
+ end
86
+ else
87
+ "#{k}:#{build_query(v, quote_string)}"
88
+ end
89
+ end.flatten
90
+ end
91
+ end
92
+
93
+ # creates an array where the "existing_value" param is first
94
+ # and the "new_value" is the last.
95
+ # All empty/nil items are removed.
96
+ # the return result is either the result of the
97
+ # array being joined on a space, or the array itself.
98
+ # "auto_join" should be true or false.
99
+ def append_to_param(existing_value, new_value, auto_join=true)
100
+ values = [existing_value, new_value]
101
+ values.delete_if{|v|v.nil?}
102
+ auto_join ? values.join(' ') : values.flatten
103
+ end
104
+
105
+ end
106
+
107
+ extend QueryHelpers
108
+ extend Params
109
+
110
+ end
@@ -0,0 +1,65 @@
1
+ module GSolr::Ext::Response
2
+
3
+ autoload :Docs, 'gsolr_ext/response/docs'
4
+ autoload :Facets, 'gsolr_ext/response/facets'
5
+ autoload :Spelling, 'gsolr_ext/response/spelling'
6
+
7
+ class Base < Mash
8
+
9
+ attr :original_hash
10
+ attr_reader :request_path, :request_params
11
+
12
+ def initialize hash, handler, request_params
13
+ super hash
14
+ @original_hash = hash
15
+ @request_path, @request_params = request_path, request_params
16
+ extend Response
17
+ extend Docs
18
+ extend Facets
19
+ extend Spelling
20
+ end
21
+
22
+ def header
23
+ self['responseHeader']
24
+ end
25
+
26
+ def rows
27
+ params[:rows].to_i
28
+ end
29
+
30
+ def params
31
+ (header and header['params']) ? header['params'] : request_params
32
+ end
33
+
34
+ def ok?
35
+ (header and header['status']) ? header['status'] == 0 : nil
36
+ end
37
+
38
+ def method_missing *args, &blk
39
+ self.original_hash.send *args, &blk
40
+ end
41
+
42
+ end
43
+
44
+ module Response
45
+
46
+ def response
47
+ self[:response]
48
+ end
49
+
50
+ # short cut to response['numFound']
51
+ def total
52
+ response[:numFound].to_s.to_i
53
+ end
54
+
55
+ def total
56
+ response[:numFound].to_s.to_i
57
+ end
58
+
59
+ def start
60
+ response[:start].to_s.to_i
61
+ end
62
+
63
+ end
64
+
65
+ end
@@ -0,0 +1,56 @@
1
+ module GSolr::Ext::Response::Docs
2
+
3
+ module Pageable
4
+
5
+ attr_accessor :start, :per_page, :total
6
+
7
+ # Returns the current page calculated from 'rows' and 'start'
8
+ # WillPaginate hook
9
+ def current_page
10
+ return 1 if start < 1
11
+ per_page_normalized = per_page < 1 ? 1 : per_page
12
+ @current_page ||= (start / per_page_normalized).ceil + 1
13
+ end
14
+
15
+ # Calcuates the total pages from 'numFound' and 'rows'
16
+ # WillPaginate hook
17
+ def total_pages
18
+ @total_pages ||= per_page > 0 ? (total / per_page.to_f).ceil : 1
19
+ end
20
+
21
+ # returns the previous page number or 1
22
+ # WillPaginate hook
23
+ def previous_page
24
+ @previous_page ||= (current_page > 1) ? current_page - 1 : 1
25
+ end
26
+
27
+ # returns the next page number or the last
28
+ # WillPaginate hook
29
+ def next_page
30
+ @next_page ||= (current_page == total_pages) ? total_pages : current_page+1
31
+ end
32
+
33
+ def has_next?
34
+ current_page < total_pages
35
+ end
36
+
37
+ def has_previous?
38
+ current_page > 1
39
+ end
40
+
41
+ end
42
+
43
+ def self.extended(base)
44
+ d = base['response']['docs']
45
+ d.each{|doc| doc.extend GSolr::Ext::Doc }
46
+ d.extend Pageable
47
+ d.per_page = base.rows
48
+ d.start = base.start
49
+ d.total = base.total
50
+ end
51
+
52
+ def docs
53
+ response['docs']
54
+ end
55
+
56
+ end