primedia-endeca 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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