endeca 1.3.7
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/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
|