primedia-endeca 0.9.0

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,93 @@
1
+ require 'endeca/document'
2
+ module Endeca
3
+ # Endeca DocumentCollections wrap a collection of Endeca Documents to provide
4
+ # access to metadata returned by the Endeca query. They behave like a simple
5
+ # Array in most cases (e.g. iteration) but also provide access to
6
+ # +refinements+.
7
+ #
8
+ # ==Attribute Readers
9
+ #
10
+ # DocumentCollection provides attribute readers for collection metadata in
11
+ # an interface that is compatible with WillPaginate::Collection for use in
12
+ # views.
13
+ #
14
+ # == Method Delegation
15
+ #
16
+ # DocumentCollections delegate array-like behavior to their embedded document
17
+ # collection, (+documents+). In most cases a DocumentCollection can be used
18
+ # as if it were an array of Document objects. (Array delegation pattern
19
+ # borrowed from Rake::FileList)
20
+ class DocumentCollection
21
+ extend ClassToProc
22
+ extend Readers
23
+
24
+ attr_reader :raw
25
+ def initialize(raw, document_klass=Document)
26
+ @raw = raw
27
+ @document_klass = document_klass
28
+ end
29
+
30
+ def attributes
31
+ @raw['MetaInfo'] || {}
32
+ end
33
+
34
+ reader \
35
+ 'NextPageLink' => :next_page_params
36
+
37
+ integer_reader \
38
+ 'NumberofPages' => :total_pages,
39
+ 'NumberofRecordsperPage' => :per_page,
40
+ 'PageNumber' => :current_page,
41
+ 'TotalNumberofMatchingRecords' => :total_entries
42
+
43
+ # WillPaginate +offset+ correspondes to Endeca StartingRecordNumber - 1
44
+ reader('StartingRecordNumber' => :offset) {|i| Integer(i) - 1}
45
+
46
+ # The previous page number.
47
+ # Returns nil if there is no previous page.
48
+ # Borrowed from WillPaginate for compatibility.
49
+ def previous_page
50
+ current_page > 1 ? (current_page - 1) : nil
51
+ end
52
+
53
+ # The next page number.
54
+ # Returns nil if there is no next page.
55
+ # Borrowed from WillPaginate for compatibility
56
+ def next_page
57
+ current_page < total_pages ? (current_page + 1) : nil
58
+ end
59
+
60
+ # The internal collection of Document objects. Array methods are delegated here.
61
+ def documents
62
+ @documents ||= (@raw['Records'] || []).map(&@document_klass)
63
+ end
64
+
65
+ # The collection of Refinement objects for the collection.
66
+ def refinements
67
+ @refinements ||= (@raw['Refinements'] || []).map(&Refinement)
68
+ end
69
+
70
+ # List of array methods (that are not in +Object+) that need to be
71
+ # delegated to +documents+.
72
+ ARRAY_METHODS = (Array.instance_methods - Object.instance_methods).map { |n| n.to_s }
73
+
74
+ # List of additional methods that must be delegated to +documents+.
75
+ MUST_DEFINE = %w[to_a to_ary inspect]
76
+
77
+ (ARRAY_METHODS + MUST_DEFINE).uniq.each do |method|
78
+ class_eval <<-RUBY, __FILE__, __LINE__ + 1
79
+ def #{method}(*args, &block) # def each(*args, &block)
80
+ documents.send(:#{method}, *args, &block) # documents.send(:each, *args, &block)
81
+ end # end
82
+ RUBY
83
+ end
84
+
85
+ # Lie about our class. Borrowed from Rake::FileList
86
+ # Note: Does not work for case equality (<tt>===</tt>)
87
+ def is_a?(klass)
88
+ klass == Array || super(klass)
89
+ end
90
+ alias kind_of? is_a?
91
+
92
+ end
93
+ end
data/lib/endeca/map.rb ADDED
@@ -0,0 +1,119 @@
1
+ module Endeca
2
+ class Map
3
+ def initialize(old_key=nil, new_key=nil)
4
+ @old_key = old_key
5
+ @new_key = new_key
6
+ boolean
7
+ end
8
+
9
+ # Convert true and false into their Endeca equivalents
10
+ def boolean
11
+ @boolean = true
12
+ add_transformation { |value| value == true ? 1 : value }
13
+ add_transformation { |value| value == false ? 0 : value }
14
+ self
15
+ end
16
+
17
+ def boolean?; @boolean end
18
+
19
+ # Mapping actually resides in another key, value pair. Uses Endeca default
20
+ # join characters (can be overridden by specifying +with+ and/or +join+).
21
+ #
22
+ # Example:
23
+ # map(:city => :propertycity).in(:ntk => ntt)
24
+ #
25
+ # Document.all(:city => 'Atlanta') =>
26
+ # ?ntk=propercity&ntt=>Atlanta
27
+ def into(hash)
28
+ @into = hash
29
+ @with ||= ':'
30
+ @join ||= '|'
31
+ self
32
+ end
33
+
34
+ # When mapping multiple key/value pairs to a single parameter value (via
35
+ # +into+), use this character to join a key with a value.
36
+ def with(character)
37
+ @with = character
38
+ self
39
+ end
40
+
41
+ # When mapping multiple key/value pairs to one or two parameter values (via
42
+ # +into+), use this character to join each pair.
43
+ def join(character)
44
+ @join = character
45
+ self
46
+ end
47
+
48
+ # Code block to execute on the original data
49
+ def transform(&block)
50
+ add_transformation block
51
+ self
52
+ end
53
+
54
+ # Perform the mapping as defined for the current_query
55
+ def perform(current_query)
56
+ @current_query = current_query
57
+
58
+ perform_transformation
59
+ perform_map
60
+ perform_into
61
+ perform_join
62
+
63
+ return @new_query
64
+ end
65
+
66
+ # Mapping object is equal to other mapping object if their attributes
67
+ # are equal
68
+ def ==(other)
69
+ equality_elements == other.equality_elements
70
+ end
71
+
72
+ private
73
+
74
+ def transformations
75
+ @transformations ||= []
76
+ end
77
+
78
+ def add_transformation(transformation = nil, &block)
79
+ transformations.push transformation if transformation
80
+ transformations.push block if block_given?
81
+ end
82
+
83
+ def perform_transformation
84
+ current_value = @current_query[@old_key]
85
+ transformations.each do |transformation|
86
+ current_value = transformation.call(current_value)
87
+ end
88
+ @current_value = current_value
89
+ end
90
+
91
+ def perform_map
92
+ @new_query = {@new_key => @current_value}
93
+ end
94
+
95
+ def perform_into
96
+ return unless @into
97
+ old_key, old_value = Array(@new_query).flatten
98
+ new_key, new_value = Array(@into).flatten
99
+ if new_value
100
+ @new_query = {new_key => old_key, new_value => old_value}
101
+ else
102
+ @new_query = {new_key => [old_key, old_value].compact.join(@with)}
103
+ end
104
+ end
105
+
106
+ def perform_join
107
+ return unless @join
108
+ @new_query.each do |key, value|
109
+ @new_query[key] = [@current_query[key], value].compact.join(@join)
110
+ end
111
+ end
112
+
113
+ protected
114
+
115
+ def equality_elements # :nodoc:
116
+ [@old_key, @new_key, @join, @with, @join]
117
+ end
118
+ end
119
+ end
@@ -0,0 +1,95 @@
1
+ module Endeca
2
+ class ReaderError < ::StandardError; end
3
+
4
+ module Readers
5
+ def add_reader(name, &block)
6
+ meta = (class << self; self; end)
7
+ meta.instance_eval do
8
+ define_method(name) { |*attrs| reader(*attrs, &block) }
9
+ end
10
+ end
11
+
12
+ # Maps key/value pairs from the data structure used to initialize a
13
+ # Endeca object. Allows attribute renaming. Use a block to perform data
14
+ # injunction on the value as it is set.
15
+ #
16
+ # ==== Examples
17
+ #
18
+ # # Specify basic attributes
19
+ # reader :title
20
+ #
21
+ # # Attribute renaming
22
+ # reader :long_desc => :description
23
+ #
24
+ # # Data injunction
25
+ # reader(:title => :upcased_title) { |title| title.upcase }
26
+ def reader(*attrs,&block)
27
+ hash = {}
28
+ block ||= lambda {|x| x}
29
+
30
+ hash.update(attrs.pop) if Hash === attrs.last
31
+
32
+ attrs.each{ |attr| hash[attr] = attr }
33
+
34
+ hash.each do |variable, method|
35
+ define_method(method) do
36
+ begin
37
+ block.call(attributes[variable.to_s])
38
+ rescue StandardError => e
39
+ raise Endeca::ReaderError, e.message
40
+ end
41
+ end
42
+ end
43
+ end
44
+
45
+ # Typecasts attributes as integers.
46
+ #
47
+ # ==== Examples
48
+ # integer_reader :id, :rating
49
+ def integer_reader(*attrs)
50
+ reader(*attrs) { |value| Integer(value) }
51
+ end
52
+
53
+ # Typecasts attributes as BigDecimal
54
+ #
55
+ # ==== Examples
56
+ # decimal_reader :price
57
+ def decimal_reader(*attrs)
58
+ require 'bigdecimal' unless defined?(BigDecimal)
59
+ reader(*attrs) { |value| BigDecimal(value.to_s) }
60
+ end
61
+
62
+ # Typecasts attributes as floats
63
+ #
64
+ # ==== Examples
65
+ # float_reader :latitude, :longitude
66
+ def float_reader(*attrs)
67
+ reader(*attrs) { |value| Float(value) }
68
+ end
69
+
70
+ # Typecasts attributes as a Perly boolean ("0" == false, "1" == true")
71
+ #
72
+ # ==== Examples
73
+ # boolean_reader :price
74
+ def boolean_reader(*attrs)
75
+ reader(*attrs) { |value| value == "1" ? true : false }
76
+ end
77
+
78
+ def dim_reader(*attrs, &block)
79
+ hash = {}
80
+ block ||= lambda {|x| x}
81
+
82
+ hash.update(attrs.pop) if Hash === attrs.last
83
+
84
+ attrs.each{|attr| hash[attr] = attr}
85
+
86
+ hash.each do |variable, method|
87
+ define_method method do
88
+ dim = dimensions[variable.to_s]
89
+ name = dim && dim.name
90
+ block.call(name)
91
+ end
92
+ end
93
+ end
94
+ end
95
+ end
@@ -0,0 +1,29 @@
1
+ module Endeca
2
+ class Refinement
3
+ extend ClassToProc
4
+ extend Readers
5
+
6
+ attr_reader :raw
7
+ def initialize(raw={})
8
+ @raw = raw
9
+ end
10
+
11
+ def ==(other)
12
+ id == other.id
13
+ end
14
+
15
+ def inspect
16
+ "#<#{self.class}=0x#{self.object_id.to_s(16)} id=#{id} name=#{name.inspect}>"
17
+ end
18
+
19
+ def attributes
20
+ (@raw['Dimensions'] || []).first || {}
21
+ end
22
+
23
+ reader \
24
+ 'DimensionName' => :name,
25
+ 'ExpansionLink' => :to_params
26
+
27
+ integer_reader 'DimensionID' => :id
28
+ end
29
+ end
@@ -0,0 +1,58 @@
1
+ require 'uri'
2
+ module Endeca
3
+ class RequestError < ::StandardError; end
4
+
5
+ class Request
6
+
7
+ def self.perform(path, query=nil)
8
+ new(path, query).perform
9
+ end
10
+
11
+ def initialize(path, query=nil)
12
+ @path = path.strip
13
+ @query = query
14
+ end
15
+
16
+ def perform
17
+ handle_response(get_response)
18
+ end
19
+
20
+ def uri
21
+ return @uri if @uri
22
+
23
+ @uri = URI.parse(@path)
24
+ @uri.query = query_string
25
+ @uri
26
+ end
27
+
28
+ private
29
+
30
+ def get_response #:nodoc:
31
+ http = Net::HTTP.new(uri.host, uri.port)
32
+ request = Net::HTTP::Get.new(uri.request_uri)
33
+
34
+ Endeca.logger.debug "ENDECA REQUEST: uri=#{uri}" if Endeca.debug
35
+ http.request(request)
36
+ end
37
+
38
+ # Raises exception Net::XXX (http error code) if an http error occured
39
+ def handle_response(response) #:nodoc:
40
+ case response
41
+ when Net::HTTPSuccess
42
+ JSON.parse(response.body)
43
+ else
44
+ response.error! # raises exception corresponding to http error Net::XXX
45
+ end
46
+
47
+ rescue => e
48
+ raise RequestError, e.message
49
+ end
50
+
51
+ def query_string
52
+ query_string_parts = [@uri.query, @query.to_params]
53
+ query_string_parts.reject!{ |s| s.nil? || s.empty? }
54
+
55
+ query_string_parts.empty? ? nil : query_string_parts.join('&')
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,40 @@
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
+
15
+ if mapping.length > 1
16
+ raise ArgumentError, "map only accepts one key=>value pair"
17
+ end
18
+
19
+ mapping.each do |key, transformed_key|
20
+ transformed_key = key unless transformed_key
21
+ return mappings[key] = Map.new(key, transformed_key)
22
+ end
23
+ end
24
+
25
+ # Use the mappings hash to replace domain level query query_options with
26
+ # their Endeca equivalent.
27
+ def transform_query_options(query_options)
28
+ query = query_options.dup
29
+ query.each do |key, value|
30
+ if mappings[key]
31
+ new_options = mappings[key].perform(query_options)
32
+ query_options.delete(key)
33
+ query_options.update(new_options)
34
+ end
35
+ end
36
+ query_options
37
+ end
38
+
39
+ end
40
+ end
@@ -0,0 +1,52 @@
1
+ require File.join(File.dirname(__FILE__), %w[spec_helper])
2
+
3
+ describe Array do
4
+ describe "#to_params" do
5
+ it "should join the elements with ampersand" do
6
+ ["foo=1","bar=3"].to_params.should == "foo=1&bar=3"
7
+ end
8
+
9
+ it "should escape all elements" do
10
+ ['|=|','||=||'].to_params.should == '%7C=%7C&%7C%7C=%7C%7C'
11
+ end
12
+ end
13
+ end
14
+
15
+ describe Hash do
16
+ describe "#to_params" do
17
+ it "should join a key-value pair with equals" do
18
+ {:foo => :bar}.to_params.should == 'foo=bar'
19
+ end
20
+
21
+ it "should join two key-value pairs with ampersand" do
22
+ result = {:foo => :bar, :bizz => :bazz}.to_params
23
+ (result == 'foo=bar&bizz=bazz' || result == 'bizz=bazz&foo=bar').should be_true
24
+ end
25
+
26
+ it "should use brackets to indicate a nested hash" do
27
+ {:foo => {:foo => :bar}}.to_params.should == 'foo[foo]=bar'
28
+ end
29
+
30
+ it "should escape all elements" do
31
+ {'|' => '||'}.to_params.should == '%7C=%7C%7C'
32
+ end
33
+ end
34
+ end
35
+
36
+ describe NilClass do
37
+ describe "to_params" do
38
+ it "should return the empty string" do
39
+ nil.to_params.should == ''
40
+ end
41
+ end
42
+ end
43
+
44
+ describe String do
45
+ describe "#to_params" do
46
+ it "should URI escape the contents" do
47
+ '|'.to_params.should == '%7C'
48
+ end
49
+ end
50
+ end
51
+
52
+ # EOF