telvue-rsolr 2.2.2

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,59 @@
1
+ module RSolr
2
+ class Document
3
+ CHILD_DOCUMENT_KEY = '_childDocuments_'.freeze
4
+
5
+ # "attrs" is a hash for setting the "doc" xml attributes
6
+ # "fields" is an array of Field objects
7
+ attr_accessor :attrs, :fields
8
+
9
+ # "doc_hash" must be a Hash/Mash object
10
+ # If a value in the "doc_hash" is an array,
11
+ # a field object is created for each value...
12
+ def initialize(doc_hash = {})
13
+ @fields = []
14
+ doc_hash.each_pair do |field, values|
15
+ add_field(field, values)
16
+ end
17
+ @attrs={}
18
+ end
19
+
20
+ # returns an array of fields that match the "name" arg
21
+ def fields_by_name(name)
22
+ @fields.select{|f|f.name==name}
23
+ end
24
+
25
+ # returns the *first* field that matches the "name" arg
26
+ def field_by_name(name)
27
+ @fields.detect{|f|f.name==name}
28
+ end
29
+
30
+ #
31
+ # Add a field value to the document. Options map directly to
32
+ # XML attributes in the Solr <field> node.
33
+ # See http://wiki.apache.org/solr/UpdateXmlMessages#head-8315b8028923d028950ff750a57ee22cbf7977c6
34
+ #
35
+ # === Example:
36
+ #
37
+ # document.add_field('title', 'A Title', :boost => 2.0)
38
+ #
39
+ def add_field(name, values, options = {})
40
+ RSolr::Array.wrap(values).each do |v|
41
+ field_attrs = { name: name }
42
+ field_attrs[:type] = DocumentField if name.to_s == CHILD_DOCUMENT_KEY
43
+
44
+ @fields << RSolr::Field.instance(options.merge(field_attrs), v)
45
+ end
46
+ end
47
+
48
+ def as_json
49
+ @fields.group_by(&:name).each_with_object({}) do |(field, values), result|
50
+ v = values.map(&:as_json)
51
+ if v.length > 1 && v.first.is_a?(Hash) && v.first.key?(:value)
52
+ v = v.first.merge(value: v.map { |single| single[:value] })
53
+ end
54
+ v = v.first if v.length == 1 && field.to_s != CHILD_DOCUMENT_KEY
55
+ result[field] = v
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,136 @@
1
+ module RSolr::Error
2
+
3
+ module SolrContext
4
+
5
+ attr_accessor :request, :response
6
+
7
+ def to_s
8
+ m = "#{super.to_s}"
9
+ if response
10
+ m << " - #{response[:status]} #{Http::STATUS_CODES[response[:status].to_i]}"
11
+ details = parse_solr_error_response response[:body]
12
+ m << "\nError: #{details}\n" if details
13
+ end
14
+ p = "\nURI: #{request[:uri].to_s}"
15
+ p << "\nRequest Headers: #{request[:headers].inspect}" if request[:headers]
16
+ p << "\nRequest Data: #{request[:data].inspect}" if request[:data]
17
+ p << "\n"
18
+ p << "\nBacktrace: " + self.backtrace[0..10].join("\n")
19
+ m << p
20
+ m
21
+ end
22
+
23
+ protected
24
+
25
+ def parse_solr_error_response body
26
+ begin
27
+ if body =~ /<pre>/
28
+ info = body.scan(/<pre>(.*)<\/pre>/mi)[0]
29
+ elsif body =~ /'msg'=>/
30
+ info = body.scan(/'msg'=>(.*)/)[0]
31
+ end
32
+ info = info.join if info.respond_to? :join
33
+ info ||= body # body might not contain <pre> or msg elements
34
+
35
+ partial = info.to_s.split("\n")[0..10]
36
+ partial.join("\n").gsub("&gt;", ">").gsub("&lt;", "<")
37
+ rescue
38
+ nil
39
+ end
40
+ end
41
+
42
+
43
+ end
44
+
45
+ class ConnectionRefused < ::Errno::ECONNREFUSED
46
+ end
47
+
48
+ class Http < RuntimeError
49
+
50
+ include SolrContext
51
+
52
+ # ripped right from ActionPack
53
+ # Defines the standard HTTP status codes, by integer, with their
54
+ # corresponding default message texts.
55
+ # Source: http://www.iana.org/assignments/http-status-codes
56
+ STATUS_CODES = {
57
+ 100 => "Continue",
58
+ 101 => "Switching Protocols",
59
+ 102 => "Processing",
60
+
61
+ 200 => "OK",
62
+ 201 => "Created",
63
+ 202 => "Accepted",
64
+ 203 => "Non-Authoritative Information",
65
+ 204 => "No Content",
66
+ 205 => "Reset Content",
67
+ 206 => "Partial Content",
68
+ 207 => "Multi-Status",
69
+ 226 => "IM Used",
70
+
71
+ 300 => "Multiple Choices",
72
+ 301 => "Moved Permanently",
73
+ 302 => "Found",
74
+ 303 => "See Other",
75
+ 304 => "Not Modified",
76
+ 305 => "Use Proxy",
77
+ 307 => "Temporary Redirect",
78
+
79
+ 400 => "Bad Request",
80
+ 401 => "Unauthorized",
81
+ 402 => "Payment Required",
82
+ 403 => "Forbidden",
83
+ 404 => "Not Found",
84
+ 405 => "Method Not Allowed",
85
+ 406 => "Not Acceptable",
86
+ 407 => "Proxy Authentication Required",
87
+ 408 => "Request Timeout",
88
+ 409 => "Conflict",
89
+ 410 => "Gone",
90
+ 411 => "Length Required",
91
+ 412 => "Precondition Failed",
92
+ 413 => "Request Entity Too Large",
93
+ 414 => "Request-URI Too Long",
94
+ 415 => "Unsupported Media Type",
95
+ 416 => "Requested Range Not Satisfiable",
96
+ 417 => "Expectation Failed",
97
+ 422 => "Unprocessable Entity",
98
+ 423 => "Locked",
99
+ 424 => "Failed Dependency",
100
+ 426 => "Upgrade Required",
101
+
102
+ 500 => "Internal Server Error",
103
+ 501 => "Not Implemented",
104
+ 502 => "Bad Gateway",
105
+ 503 => "Service Unavailable",
106
+ 504 => "Gateway Timeout",
107
+ 505 => "HTTP Version Not Supported",
108
+ 507 => "Insufficient Storage",
109
+ 510 => "Not Extended"
110
+ }
111
+
112
+ def initialize request, response
113
+ @request, @response = request, response
114
+ end
115
+
116
+ end
117
+
118
+ # Thrown if the :wt is :ruby
119
+ # but the body wasn't succesfully parsed/evaluated
120
+ class InvalidResponse < Http
121
+
122
+ end
123
+
124
+ # Thrown if the :wt is :ruby
125
+ # but the body wasn't succesfully parsed/evaluated
126
+ class InvalidJsonResponse < InvalidResponse
127
+
128
+ end
129
+
130
+ # Thrown if the :wt is :ruby
131
+ # but the body wasn't succesfully parsed/evaluated
132
+ class InvalidRubyResponse < InvalidResponse
133
+
134
+ end
135
+
136
+ end
@@ -0,0 +1,87 @@
1
+ module RSolr
2
+ class Field
3
+ def self.instance(attrs, value)
4
+ attrs = attrs.dup
5
+ field_type = attrs.delete(:type) { value.class.name }
6
+
7
+ klass = if field_type.is_a? String
8
+ class_for_field(field_type)
9
+ elsif field_type.is_a? Class
10
+ field_type
11
+ else
12
+ self
13
+ end
14
+
15
+ klass.new(attrs, value)
16
+ end
17
+
18
+ def self.class_for_field(field_type)
19
+ potential_class_name = field_type + 'Field'.freeze
20
+ search_scope = Module.nesting[1]
21
+ search_scope.const_defined?(potential_class_name, false) ? search_scope.const_get(potential_class_name) : self
22
+ end
23
+ private_class_method :class_for_field
24
+
25
+ # "attrs" is a hash for setting the "doc" xml attributes
26
+ # "value" is the text value for the node
27
+ attr_accessor :attrs, :source_value
28
+
29
+ # "attrs" must be a hash
30
+ # "value" should be something that responds to #_to_s
31
+ def initialize(attrs, source_value)
32
+ @attrs = attrs
33
+ @source_value = source_value
34
+ end
35
+
36
+ # the value of the "name" attribute
37
+ def name
38
+ attrs[:name]
39
+ end
40
+
41
+ def value
42
+ source_value
43
+ end
44
+
45
+ def as_json
46
+ if attrs[:update]
47
+ { attrs[:update] => value }
48
+ elsif attrs.any? { |k, _| k != :name }
49
+ hash = attrs.dup
50
+ hash.delete(:name)
51
+ hash.merge(value: value)
52
+ else
53
+ value
54
+ end
55
+ end
56
+ end
57
+
58
+ class DateField < Field
59
+ def value
60
+ Time.utc(source_value.year, source_value.mon, source_value.mday).iso8601
61
+ end
62
+ end
63
+
64
+ class TimeField < Field
65
+ def value
66
+ source_value.getutc.strftime('%FT%TZ')
67
+ end
68
+ end
69
+
70
+ class DateTimeField < Field
71
+ def value
72
+ source_value.to_time.getutc.iso8601
73
+ end
74
+ end
75
+
76
+ class DocumentField < Field
77
+ def value
78
+ return RSolr::Document.new(source_value) if source_value.respond_to? :each_pair
79
+
80
+ super
81
+ end
82
+
83
+ def as_json
84
+ value.as_json
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,5 @@
1
+ module RSolr
2
+ class Generator
3
+
4
+ end
5
+ end
@@ -0,0 +1,60 @@
1
+ require 'json'
2
+
3
+ module RSolr::JSON
4
+ class Generator < RSolr::Generator
5
+ CONTENT_TYPE = 'application/json'.freeze
6
+
7
+ def content_type
8
+ CONTENT_TYPE
9
+ end
10
+
11
+ def add data, add_attrs = {}
12
+ add_attrs ||= {}
13
+ data = RSolr::Array.wrap(data)
14
+
15
+ if add_attrs.empty? && data.none? { |doc| doc.is_a?(RSolr::Document) && !doc.attrs.empty? }
16
+ data.map do |doc|
17
+ doc = RSolr::Document.new(doc) if doc.respond_to?(:each_pair)
18
+ yield doc if block_given?
19
+ doc.as_json
20
+ end.to_json
21
+ else
22
+ i = 0
23
+ data.each_with_object({}) do |doc, hash|
24
+ doc = RSolr::Document.new(doc) if doc.respond_to?(:each_pair)
25
+ yield doc if block_given?
26
+ hash["add__UNIQUE_RSOLR_SUFFIX_#{i += 1}"] = add_attrs.merge(doc.attrs).merge(doc: doc.as_json)
27
+ end.to_json.gsub(/__UNIQUE_RSOLR_SUFFIX_\d+/, '')
28
+ end
29
+ end
30
+
31
+ # generates a commit message
32
+ def commit(opts = {})
33
+ opts ||= {}
34
+ { commit: opts }.to_json
35
+ end
36
+
37
+ # generates a optimize message
38
+ def optimize(opts = {})
39
+ opts ||= {}
40
+ { optimize: opts }.to_json
41
+ end
42
+
43
+ # generates a rollback message
44
+ def rollback
45
+ { rollback: {} }.to_json
46
+ end
47
+
48
+ # generates a delete message
49
+ # "ids" can be a single value or array of values
50
+ def delete_by_id(ids)
51
+ { delete: ids }.to_json
52
+ end
53
+
54
+ # generates a delete message
55
+ # "queries" can be a single value or an array of values
56
+ def delete_by_query(queries)
57
+ { delete: { query: queries } }.to_json
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,95 @@
1
+ module RSolr::Response
2
+
3
+ def self.included(base)
4
+ unless base < Hash
5
+ raise ArgumentError, "RSolr::Response expects to included only in (sub)classes of Hash; got included in '#{base}' instead."
6
+ end
7
+ base.send(:attr_reader, :request, :response)
8
+ end
9
+
10
+ def initialize_rsolr_response(request, response, result)
11
+ @request = request
12
+ @response = response
13
+ self.merge!(result)
14
+ if self["response"] && self["response"]["docs"].is_a?(::Array)
15
+ docs = PaginatedDocSet.new(self["response"]["docs"])
16
+ docs.per_page = request[:params]["rows"]
17
+ docs.page_start = request[:params]["start"]
18
+ docs.page_total = self["response"]["numFound"].to_s.to_i
19
+ self["response"]["docs"] = docs
20
+ end
21
+ end
22
+
23
+ def with_indifferent_access
24
+ if defined?(::RSolr::HashWithIndifferentAccessWithResponse)
25
+ ::RSolr::HashWithIndifferentAccessWithResponse.new(request, response, self)
26
+ else
27
+ if defined?(ActiveSupport::HashWithIndifferentAccess)
28
+ RSolr.const_set("HashWithIndifferentAccessWithResponse", Class.new(ActiveSupport::HashWithIndifferentAccess))
29
+ RSolr::HashWithIndifferentAccessWithResponse.class_eval <<-eos
30
+ include RSolr::Response
31
+ def initialize(request, response, result)
32
+ super()
33
+ initialize_rsolr_response(request, response, result)
34
+ end
35
+ eos
36
+ ::RSolr::HashWithIndifferentAccessWithResponse.new(request, response, self)
37
+ else
38
+ raise RuntimeError, "HashWithIndifferentAccess is not currently defined"
39
+ end
40
+ end
41
+ end
42
+
43
+ # A response module which gets mixed into the solr ["response"]["docs"] array.
44
+ class PaginatedDocSet < ::Array
45
+
46
+ attr_accessor :page_start, :per_page, :page_total
47
+ if not (Object.const_defined?("RUBY_ENGINE") and Object::RUBY_ENGINE=='rbx')
48
+ alias_method(:start,:page_start)
49
+ alias_method(:start=,:page_start=)
50
+ alias_method(:total,:page_total)
51
+ alias_method(:total=,:page_total=)
52
+ end
53
+
54
+ # Returns the current page calculated from 'rows' and 'start'
55
+ def current_page
56
+ return 1 if start < 1
57
+ per_page_normalized = per_page < 1 ? 1 : per_page
58
+ @current_page ||= (start / per_page_normalized).ceil + 1
59
+ end
60
+
61
+ # Calcuates the total pages from 'numFound' and 'rows'
62
+ def total_pages
63
+ @total_pages ||= per_page > 0 ? (total / per_page.to_f).ceil : 1
64
+ end
65
+
66
+ # returns the previous page number or 1
67
+ def previous_page
68
+ @previous_page ||= (current_page > 1) ? current_page - 1 : 1
69
+ end
70
+
71
+ # returns the next page number or the last
72
+ def next_page
73
+ @next_page ||= (current_page == total_pages) ? total_pages : current_page+1
74
+ end
75
+
76
+ def has_next?
77
+ current_page < total_pages
78
+ end
79
+
80
+ def has_previous?
81
+ current_page > 1
82
+ end
83
+
84
+ end
85
+
86
+ end
87
+
88
+ class RSolr::HashWithResponse < Hash
89
+ include RSolr::Response
90
+
91
+ def initialize(request, response, result)
92
+ super()
93
+ initialize_rsolr_response(request, response, result || {})
94
+ end
95
+ end