endeca 1.3.7
Sign up to get free protection for your applications and to get access to all the features.
- data/History.txt +4 -0
- data/Manifest.txt +37 -0
- data/README.rdoc +60 -0
- data/Rakefile +52 -0
- data/endeca.gemspec +20 -0
- data/example/benchmark.rb +13 -0
- data/example/listing.rb +33 -0
- data/lib/class_to_proc.rb +5 -0
- data/lib/core_ext.rb +106 -0
- data/lib/endeca.rb +76 -0
- data/lib/endeca/benchmarking.rb +38 -0
- data/lib/endeca/breadcrumb.rb +42 -0
- data/lib/endeca/breadcrumbs.rb +13 -0
- data/lib/endeca/dimension.rb +38 -0
- data/lib/endeca/document.rb +146 -0
- data/lib/endeca/document_collection.rb +112 -0
- data/lib/endeca/logging.rb +9 -0
- data/lib/endeca/map.rb +191 -0
- data/lib/endeca/readers.rb +93 -0
- data/lib/endeca/refinement.rb +42 -0
- data/lib/endeca/refinement_dimension.rb +32 -0
- data/lib/endeca/request.rb +88 -0
- data/lib/endeca/transformer.rb +43 -0
- data/spec/core_ext_spec.rb +134 -0
- data/spec/endeca/benchmarking_spec.rb +33 -0
- data/spec/endeca/breadcrumb_spec.rb +90 -0
- data/spec/endeca/dimension_spec.rb +91 -0
- data/spec/endeca/document_collection_spec.rb +158 -0
- data/spec/endeca/document_spec.rb +378 -0
- data/spec/endeca/map_spec.rb +122 -0
- data/spec/endeca/readers_spec.rb +118 -0
- data/spec/endeca/refinement_dimension_spec.rb +74 -0
- data/spec/endeca/refinement_spec.rb +72 -0
- data/spec/endeca/request_spec.rb +107 -0
- data/spec/endeca/transformer_spec.rb +50 -0
- data/spec/endeca_spec.rb +37 -0
- data/spec/rcov.opts +5 -0
- data/spec/spec.opts +3 -0
- data/spec/spec_helper.rb +1 -0
- metadata +97 -0
@@ -0,0 +1,93 @@
|
|
1
|
+
module Endeca
|
2
|
+
class ReaderError < ::StandardError; end
|
3
|
+
|
4
|
+
module Readers
|
5
|
+
module ClassMethods
|
6
|
+
def add_reader(name, &block)
|
7
|
+
meta = (class << self; self; end)
|
8
|
+
meta.instance_eval do
|
9
|
+
define_method(name) { |*attrs| reader(*attrs, &block) }
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
# Maps key/value pairs from the data structure used to initialize a
|
14
|
+
# Endeca object. Allows attribute renaming. Use a block to perform data
|
15
|
+
# injunction on the value as it is set.
|
16
|
+
#
|
17
|
+
# ==== Examples
|
18
|
+
#
|
19
|
+
# # Specify basic attributes
|
20
|
+
# reader :title
|
21
|
+
#
|
22
|
+
# # Attribute renaming
|
23
|
+
# reader :long_desc => :description
|
24
|
+
#
|
25
|
+
# # Data injunction
|
26
|
+
# reader(:title => :upcased_title) { |title| title.upcase }
|
27
|
+
def reader(*attrs,&block)
|
28
|
+
hash = {}
|
29
|
+
block ||= lambda {|x| x}
|
30
|
+
|
31
|
+
hash.update(attrs.pop) if Hash === attrs.last
|
32
|
+
|
33
|
+
attrs.each{ |attr| hash[attr] = attr }
|
34
|
+
|
35
|
+
hash.each do |variable, method|
|
36
|
+
reader_names << method if respond_to?(:reader_names)
|
37
|
+
define_method(method) do
|
38
|
+
begin
|
39
|
+
block.call(attributes[variable.to_s])
|
40
|
+
rescue StandardError => e
|
41
|
+
raise Endeca::ReaderError, e.message
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
# Typecasts attributes as integers.
|
48
|
+
#
|
49
|
+
# ==== Examples
|
50
|
+
# integer_reader :id, :rating
|
51
|
+
def integer_reader(*attrs)
|
52
|
+
reader(*attrs) { |value| value.blank? ? 0 : Integer(value) }
|
53
|
+
end
|
54
|
+
|
55
|
+
# Typecasts attributes as BigDecimal
|
56
|
+
#
|
57
|
+
# ==== Examples
|
58
|
+
# decimal_reader :price
|
59
|
+
def decimal_reader(*attrs)
|
60
|
+
require 'bigdecimal' unless defined?(BigDecimal)
|
61
|
+
reader(*attrs) { |value| BigDecimal(value.to_s) }
|
62
|
+
end
|
63
|
+
|
64
|
+
# Typecasts attributes as floats
|
65
|
+
#
|
66
|
+
# ==== Examples
|
67
|
+
# float_reader :latitude, :longitude
|
68
|
+
def float_reader(*attrs)
|
69
|
+
reader(*attrs) { |value| Float(value) if value }
|
70
|
+
end
|
71
|
+
|
72
|
+
# Typecasts attributes as a Perly boolean ("0" == false, "1" == true")
|
73
|
+
#
|
74
|
+
# ==== Examples
|
75
|
+
# boolean_reader :price
|
76
|
+
def boolean_reader(*attrs)
|
77
|
+
reader(*attrs) { |value| value == "1" ? true : false }
|
78
|
+
end
|
79
|
+
|
80
|
+
end
|
81
|
+
|
82
|
+
module InstanceMethods
|
83
|
+
def method_missing(method_id)
|
84
|
+
raise NoMethodError, "undefined method '#{method_id}' for #{self.inspect}. Do you need to add a reader for it?"
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
def self.included(receiver)
|
89
|
+
receiver.extend ClassMethods
|
90
|
+
receiver.send :include, InstanceMethods
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
module Endeca
|
2
|
+
class Refinement
|
3
|
+
include Readers
|
4
|
+
extend ClassToProc
|
5
|
+
|
6
|
+
reader 'DimensionName' => :name,
|
7
|
+
'ExpansionLink' => :expansion_link,
|
8
|
+
'ContractionLink' => :contraction_link
|
9
|
+
|
10
|
+
integer_reader 'DimensionID' => :id
|
11
|
+
|
12
|
+
reader('DimensionValues' => :dimension_values) do |values|
|
13
|
+
values.map(&Dimension) if values
|
14
|
+
end
|
15
|
+
|
16
|
+
reader('Dimensions' => :dimensions) do |values|
|
17
|
+
values.map(&RefinementDimension) if values
|
18
|
+
end
|
19
|
+
|
20
|
+
attr_reader :raw
|
21
|
+
def initialize(raw={})
|
22
|
+
@raw = raw
|
23
|
+
end
|
24
|
+
|
25
|
+
def ==(other)
|
26
|
+
id == other.id
|
27
|
+
end
|
28
|
+
|
29
|
+
def inspect
|
30
|
+
"#<#{self.class}=0x#{self.object_id.to_s(16)} id=#{id} name=#{name.inspect}>"
|
31
|
+
end
|
32
|
+
|
33
|
+
def attributes
|
34
|
+
(@raw['Dimensions'] || []).first || {}
|
35
|
+
end
|
36
|
+
|
37
|
+
def to_endeca_params
|
38
|
+
expansion_link || contraction_link
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module Endeca
|
2
|
+
class RefinementDimension
|
3
|
+
include Comparable
|
4
|
+
include Readers
|
5
|
+
extend ClassToProc
|
6
|
+
|
7
|
+
reader \
|
8
|
+
"DimensionName" => :name,
|
9
|
+
"ExpansionLink" => :to_endeca_params
|
10
|
+
|
11
|
+
integer_reader \
|
12
|
+
"DimensionID" => :id
|
13
|
+
|
14
|
+
attr_reader :raw
|
15
|
+
def initialize(raw={})
|
16
|
+
@raw=raw
|
17
|
+
end
|
18
|
+
alias_method :attributes, :raw
|
19
|
+
|
20
|
+
def inspect
|
21
|
+
"#<#{self.class}=0x#{self.object_id.to_s(16)} id=#{id} name=#{name.inspect}>"
|
22
|
+
end
|
23
|
+
|
24
|
+
def ==(other)
|
25
|
+
id == other.id
|
26
|
+
end
|
27
|
+
|
28
|
+
def <=>(other)
|
29
|
+
name <=> other.name
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,88 @@
|
|
1
|
+
require 'uri'
|
2
|
+
|
3
|
+
module Endeca
|
4
|
+
class RequestError < ::StandardError; end
|
5
|
+
|
6
|
+
class Request
|
7
|
+
def self.perform(path, query=nil)
|
8
|
+
raise RequestError, "Must provide a path" unless path
|
9
|
+
new(path, query).perform
|
10
|
+
end
|
11
|
+
|
12
|
+
def initialize(path, query=nil)
|
13
|
+
@path = path.strip
|
14
|
+
@query = query
|
15
|
+
end
|
16
|
+
|
17
|
+
def perform
|
18
|
+
raise RequestError, endeca_error[:message] if endeca_error?
|
19
|
+
Endeca.increase_metric(:request_count, 1)
|
20
|
+
return response
|
21
|
+
end
|
22
|
+
|
23
|
+
def response
|
24
|
+
@response ||= handle_response(get_response)
|
25
|
+
end
|
26
|
+
|
27
|
+
def uri
|
28
|
+
return @uri if @uri
|
29
|
+
|
30
|
+
@uri = URI.parse(@path)
|
31
|
+
@uri.query = query_string unless !@query || @query.include?("/_/")
|
32
|
+
@uri = URI.parse("#{@path}#{@query}") if @query && @query.include?("/_/")
|
33
|
+
@uri
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
def endeca_error
|
39
|
+
method_response = response["methodResponse"]
|
40
|
+
fault = method_response && method_response["fault"]
|
41
|
+
values = fault && fault["value"]
|
42
|
+
return nil unless values
|
43
|
+
{
|
44
|
+
:code => values["faultCode"].to_i,
|
45
|
+
:message => values["faultString"]
|
46
|
+
}
|
47
|
+
end
|
48
|
+
|
49
|
+
def endeca_error?
|
50
|
+
!endeca_error.nil?
|
51
|
+
end
|
52
|
+
|
53
|
+
def get_response #:nodoc:
|
54
|
+
Endeca.log "ENDECA ADAPTER REQUEST"
|
55
|
+
Endeca.log " parameters => " + @query.inspect
|
56
|
+
Endeca.log " uri => " + uri.to_s
|
57
|
+
Endeca.bm(:request_time, "#{@path} #{@query.inspect}") do
|
58
|
+
Endeca.timer.timeout(Endeca.timeout) do
|
59
|
+
@response = Net::HTTP.get_response(uri)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
return @response
|
64
|
+
end
|
65
|
+
|
66
|
+
# Raises exception Net::XXX (http error code) if an http error occured
|
67
|
+
def handle_response(response) #:nodoc:
|
68
|
+
case response
|
69
|
+
when Net::HTTPSuccess
|
70
|
+
Endeca.bm :parse_time do
|
71
|
+
@json = JSON.parse(response.body)
|
72
|
+
end
|
73
|
+
else
|
74
|
+
response.error! # raises exception corresponding to http error Net::XXX
|
75
|
+
end
|
76
|
+
|
77
|
+
rescue => e
|
78
|
+
raise RequestError, e.message
|
79
|
+
end
|
80
|
+
|
81
|
+
def query_string
|
82
|
+
query_string_parts = [@uri.query, @query.to_endeca_params]
|
83
|
+
query_string_parts.reject!{ |s| s.nil? || s.empty? }
|
84
|
+
|
85
|
+
query_string_parts.empty? ? nil : query_string_parts.join('&')
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
module Endeca
|
2
|
+
module Transformer
|
3
|
+
# Requires existence of mappings accessor (Hash)
|
4
|
+
#
|
5
|
+
# ==== Examples
|
6
|
+
# # Standard map call that returns an Endeca::Map object
|
7
|
+
# map(:old_name => :new_name)
|
8
|
+
#
|
9
|
+
# # Allows to to create a map object to perform other functionality such
|
10
|
+
# # as transformations.
|
11
|
+
# map(:new_name)
|
12
|
+
def map(mapping = {})
|
13
|
+
mapping = {mapping => mapping} if Symbol === mapping
|
14
|
+
mapping = mapping.symbolize_keys
|
15
|
+
|
16
|
+
if mapping.length > 1
|
17
|
+
raise ArgumentError, "map only accepts one key=>value pair"
|
18
|
+
end
|
19
|
+
|
20
|
+
mapping.each do |key, transformed_key|
|
21
|
+
transformed_key = key unless transformed_key
|
22
|
+
return mappings[key] = Map.new(key, transformed_key)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
# Use the mappings hash to replace domain level query query_options with
|
27
|
+
# their Endeca equivalent.
|
28
|
+
def transform_query_options(query_options)
|
29
|
+
# {"apartments" => true}
|
30
|
+
query = query_options.symbolize_keys
|
31
|
+
# {:apartments => true}
|
32
|
+
query.each do |key, _|
|
33
|
+
if mapping = mappings[key]
|
34
|
+
new_options = mapping.perform(query)
|
35
|
+
query.delete(key)
|
36
|
+
query.update(new_options)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
query
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,134 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), %w[spec_helper])
|
2
|
+
|
3
|
+
describe Array do
|
4
|
+
describe "#to_endeca_params" do
|
5
|
+
it "should join the elements with ampersand" do
|
6
|
+
["foo=1","bar=3"].to_endeca_params.should == "foo=1&bar=3"
|
7
|
+
end
|
8
|
+
|
9
|
+
it "should escape all elements" do
|
10
|
+
['|=|','||=||'].to_endeca_params.should == '%7C=%7C&%7C%7C=%7C%7C'
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
describe Benchmark do
|
16
|
+
describe ".realtime" do
|
17
|
+
it "should check the time twice" do
|
18
|
+
Time.should_receive(:now).exactly(2).times
|
19
|
+
Benchmark.realtime(){}
|
20
|
+
end
|
21
|
+
|
22
|
+
it "should return the time difference as a float" do
|
23
|
+
Benchmark.realtime(){}.should be_a_kind_of(Float)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
describe ".ms" do
|
28
|
+
it "should be 1000 times the realtime value" do
|
29
|
+
Benchmark.stub!(:realtime).and_return(1)
|
30
|
+
Benchmark.ms.should == 1000
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
describe Hash do
|
36
|
+
describe "#to_endeca_params" do
|
37
|
+
it "should join a key-value pair with equals" do
|
38
|
+
{:foo => :bar}.to_endeca_params.should == 'foo=bar'
|
39
|
+
end
|
40
|
+
|
41
|
+
it "should join two key-value pairs with ampersand" do
|
42
|
+
result = {:foo => :bar, :bizz => :bazz}.to_endeca_params
|
43
|
+
(result == 'foo=bar&bizz=bazz' || result == 'bizz=bazz&foo=bar').should be_true
|
44
|
+
end
|
45
|
+
|
46
|
+
it "should use brackets to indicate a nested hash" do
|
47
|
+
{:foo => {:foo => :bar}}.to_endeca_params.should == 'foo[foo]=bar'
|
48
|
+
end
|
49
|
+
|
50
|
+
it "should escape all elements" do
|
51
|
+
{'|' => '||'}.to_endeca_params.should == '%7C=%7C%7C'
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
describe NilClass do
|
57
|
+
describe "to_endeca_params" do
|
58
|
+
it "should return the empty string" do
|
59
|
+
nil.to_endeca_params.should == ''
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
describe String do
|
65
|
+
describe "#to_endeca_params" do
|
66
|
+
it "should URI escape the contents" do
|
67
|
+
'|'.to_endeca_params.should == '%7C'
|
68
|
+
end
|
69
|
+
|
70
|
+
it "should URI escape a colon" do
|
71
|
+
':'.to_endeca_params.should == "%3A"
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
describe "#inherited_accessor" do
|
77
|
+
|
78
|
+
class SomeParent
|
79
|
+
inherited_accessor :a_hash, {:m => "my_value"}
|
80
|
+
inherited_accessor :a_string, "fibby foo"
|
81
|
+
inherited_accessor :a_array, ["atlanta"]
|
82
|
+
end
|
83
|
+
|
84
|
+
class A < SomeParent; end
|
85
|
+
class B < SomeParent; end
|
86
|
+
|
87
|
+
before do
|
88
|
+
A.a_hash[:a] = "a_value"
|
89
|
+
A.a_string = "a_string"
|
90
|
+
|
91
|
+
B.a_hash[:a] = "b_value"
|
92
|
+
B.a_string = "b_string"
|
93
|
+
end
|
94
|
+
|
95
|
+
it "should retrieve default value" do
|
96
|
+
SomeParent.a_hash[:m].should == "my_value"
|
97
|
+
SomeParent.a_string.should == "fibby foo"
|
98
|
+
end
|
99
|
+
|
100
|
+
it "should retrieve A hash value correctly" do
|
101
|
+
A.a_hash[:a].should == "a_value"
|
102
|
+
end
|
103
|
+
|
104
|
+
it "should retrieve A string value correctly" do
|
105
|
+
A.a_string.should == "a_string"
|
106
|
+
end
|
107
|
+
|
108
|
+
it "should retrieve A array value correctly" do
|
109
|
+
original = A.a_array
|
110
|
+
|
111
|
+
A.a_array << "jacksonville"
|
112
|
+
A.a_array.should == ["atlanta", "jacksonville"]
|
113
|
+
|
114
|
+
A.a_array = original
|
115
|
+
end
|
116
|
+
|
117
|
+
it "should retrieve B hash value correctly" do
|
118
|
+
B.a_hash[:a].should == "b_value"
|
119
|
+
end
|
120
|
+
|
121
|
+
it "should retrieve B string value correctly" do
|
122
|
+
B.a_string.should == "b_string"
|
123
|
+
end
|
124
|
+
|
125
|
+
it "should retrieve B array value correctly" do
|
126
|
+
original = B.a_array
|
127
|
+
|
128
|
+
B.a_array << "new york"
|
129
|
+
B.a_array.should == ["atlanta", "new york"]
|
130
|
+
|
131
|
+
B.a_array = original
|
132
|
+
end
|
133
|
+
end
|
134
|
+
# EOF
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), %w[.. spec_helper])
|
2
|
+
|
3
|
+
describe Endeca::Benchmarking do
|
4
|
+
class Helper
|
5
|
+
extend Endeca::Benchmarking
|
6
|
+
end
|
7
|
+
|
8
|
+
describe "#benchmark" do
|
9
|
+
before do
|
10
|
+
@logger = mock('Logger')
|
11
|
+
|
12
|
+
Endeca.stub!(:logger).and_return(@logger)
|
13
|
+
Endeca.stub!(:debug => true, :benchmark => true)
|
14
|
+
|
15
|
+
Benchmark.stub!(:ms => 1)
|
16
|
+
end
|
17
|
+
|
18
|
+
it "should log the title and the time to the Endeca logger" do
|
19
|
+
@logger.should_receive(:debug).with("metric: 1.0ms")
|
20
|
+
Endeca.bm(:metric){ 1 }
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
describe "#add_bm_detail" do
|
25
|
+
it "should add info to the current thread" do
|
26
|
+
Endeca.stub!(:analyze?).and_return(true)
|
27
|
+
|
28
|
+
Endeca.send(:add_bm_detail, :metric, 1.1, 'query query')
|
29
|
+
|
30
|
+
Thread.current[:endeca]["metric_detail"][0].should == {:detail => "query query", :time => 1.1}
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|