endeca_on_demand 1.0.1 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (35) hide show
  1. data/endeca_on_demand.gemspec +4 -0
  2. data/lib/endeca_on_demand/client.rb +24 -0
  3. data/lib/endeca_on_demand/collection.rb +41 -0
  4. data/lib/endeca_on_demand/pp.rb +48 -0
  5. data/lib/endeca_on_demand/proxy.rb +58 -8
  6. data/lib/endeca_on_demand/query.rb +187 -0
  7. data/lib/endeca_on_demand/response/applied_filters/keyword_redirect.rb +42 -0
  8. data/lib/endeca_on_demand/response/applied_filters/search_report/search.rb +42 -0
  9. data/lib/endeca_on_demand/response/applied_filters/search_report.rb +50 -0
  10. data/lib/endeca_on_demand/response/applied_filters/selected_dimension_value_id.rb +32 -0
  11. data/lib/endeca_on_demand/response/applied_filters.rb +39 -0
  12. data/lib/endeca_on_demand/response/breadcrumb/bread.rb +42 -0
  13. data/lib/endeca_on_demand/response/breadcrumb.rb +35 -0
  14. data/lib/endeca_on_demand/response/business_rules_result/business_rule.rb +54 -0
  15. data/lib/endeca_on_demand/response/business_rules_result.rb +31 -0
  16. data/lib/endeca_on_demand/response/dimension/dimension_value.rb +50 -0
  17. data/lib/endeca_on_demand/response/dimension.rb +50 -0
  18. data/lib/endeca_on_demand/response/property.rb +36 -0
  19. data/lib/endeca_on_demand/response/records_set/record.rb +53 -0
  20. data/lib/endeca_on_demand/response/records_set.rb +50 -0
  21. data/lib/endeca_on_demand/response.rb +62 -0
  22. data/lib/endeca_on_demand/version.rb +1 -1
  23. data/lib/endeca_on_demand.rb +251 -243
  24. metadata +69 -17
  25. data/lib/endeca_on_demand/bread_crumb.rb +0 -12
  26. data/lib/endeca_on_demand/business_rules_result/property.rb +0 -14
  27. data/lib/endeca_on_demand/business_rules_result.rb +0 -33
  28. data/lib/endeca_on_demand/dimension/dimension_value.rb +0 -14
  29. data/lib/endeca_on_demand/dimension.rb +0 -24
  30. data/lib/endeca_on_demand/keyword_redirect.rb +0 -12
  31. data/lib/endeca_on_demand/record_set/record.rb +0 -14
  32. data/lib/endeca_on_demand/record_set.rb +0 -24
  33. data/lib/endeca_on_demand/search_report/search.rb +0 -14
  34. data/lib/endeca_on_demand/search_report.rb +0 -21
  35. data/lib/endeca_on_demand/selected_dimension_value_id.rb +0 -12
@@ -20,6 +20,10 @@ Gem::Specification.new do |s|
20
20
  s.require_paths = ["lib"]
21
21
 
22
22
  s.add_development_dependency 'rspec'
23
+ s.add_development_dependency 'pry'
23
24
 
24
25
  s.add_dependency 'nokogiri'
26
+ s.add_dependency 'builder'
27
+ s.add_dependency 'activesupport', '~> 3.1'
28
+ s.add_dependency 'i18n'
25
29
  end
@@ -0,0 +1,24 @@
1
+ class EndecaOnDemand::Client
2
+
3
+ include EndecaOnDemand::PP
4
+
5
+ def inspect_attributes; [ :api, :default_options ]; end
6
+
7
+ ## fields ##
8
+
9
+ attr_reader :api, :default_options, :query
10
+
11
+ def initialize(api, default_options = {})
12
+ @api, @default_options = api, default_options.dup.with_indifferent_access
13
+ end
14
+
15
+ ## associations ##
16
+
17
+ def query(options = {})
18
+ @query = nil if options.present?
19
+ @query ||= EndecaOnDemand::Query.new(self, options)
20
+ end
21
+
22
+ ##
23
+
24
+ end
@@ -0,0 +1,41 @@
1
+ class EndecaOnDemand::Collection < EndecaOnDemand::Proxy
2
+
3
+ attr_reader :klass, :target
4
+
5
+ def initialize(klass, target, mapping = nil)
6
+ @klass, @target = klass, target
7
+ @target = target.map { |object| @klass.new(mapping, object) } if mapping.present?
8
+ extend klass.collection_class if klass.respond_to?(:collection_class)
9
+ end
10
+
11
+ ## override proxy ##
12
+
13
+ def class
14
+ EndecaOnDemand::Collection
15
+ end
16
+
17
+ def inspect
18
+ target.to_a.inspect
19
+ end
20
+
21
+ ##
22
+
23
+ def where(conditions = {})
24
+ target.select do |object|
25
+ conditions.all? do |key,value|
26
+ value.is_a?(Regexp) ? object.send(key) =~ value : object.send(key) == value
27
+ end
28
+ end
29
+ end
30
+
31
+ protected
32
+
33
+ def wrap_collection(collection)
34
+ EndecaOnDemand::Collection.new(klass, collection)
35
+ end
36
+
37
+ def method_missing(name, *args, &block)
38
+ target.send(name, *args, &block)
39
+ end
40
+
41
+ end
@@ -0,0 +1,48 @@
1
+ module EndecaOnDemand::PP
2
+
3
+ def inspect # :nodoc:
4
+ return super if not respond_to?(:inspect_attributes) or inspect_attributes.blank?
5
+ attributes = inspect_attributes.reject { |x|
6
+ begin
7
+ attribute = send x
8
+ attribute.blank?
9
+ rescue NoMethodError
10
+ true
11
+ end
12
+ }.map { |attribute|
13
+ "#{attribute.to_s.sub(/_\w+/, 's')}=#{send(attribute).inspect}"
14
+ }.join ' '
15
+ "#<#{self.class.name}:#{sprintf("0x%x", object_id)} #{attributes}>"
16
+ end
17
+
18
+ def pretty_print pp # :nodoc:
19
+ return super if not respond_to?(:inspect_attributes) or inspect_attributes.blank?
20
+ nice_name = self.class.name
21
+ pp.group(2, "#(#{nice_name}:#{sprintf("0x%x", object_id)} {", '})') do
22
+
23
+ pp.breakable
24
+ attrs = inspect_attributes.map { |t|
25
+ [t, send(t)] if respond_to?(t)
26
+ }.compact.find_all { |x|
27
+ x.last.present?
28
+ }
29
+
30
+ pp.seplist(attrs) do |v|
31
+ if v.last.class == EndecaOnDemand::Collection
32
+ pp.group(2, "#{v.first} = [", "]") do
33
+ pp.breakable
34
+ pp.seplist(v.last) do |item|
35
+ pp.pp item
36
+ end
37
+ end
38
+ else
39
+ pp.text "#{v.first} = "
40
+ pp.pp v.last
41
+ end
42
+ end
43
+ pp.breakable
44
+
45
+ end
46
+ end
47
+
48
+ end
@@ -1,11 +1,61 @@
1
- class EndecaOnDemand
2
- class Proxy
3
-
4
- def method_missing(method, *args, &block)
5
- unless self.instance_variables.include?(:"@#{method}")
6
- "#{method} is unavailable."
1
+ class EndecaOnDemand::Proxy
2
+
3
+ # We undefine most methods to get them sent through to the target.
4
+ instance_methods.each do |method|
5
+ undef_method(method) unless
6
+ method =~ /(^__|^send$|^object_id$|^extend$|^respond_to\?$|^tap$|^inspect$|^pretty_)/
7
+ end
8
+
9
+ attr_accessor :xml
10
+
11
+ # def inspect
12
+ # respond_to?(:inspection) ?
13
+ # "#<#{self.class.name} #{inspection.kind_of?(Hash) ? inspection.inspect : (inspection * ', ')}>" :
14
+ # super
15
+ # end
16
+
17
+ def inspect # :nodoc:
18
+ return super if not respond_to?(:inspect_attributes) or inspect_attributes.blank?
19
+ attributes = inspect_attributes.reject { |x|
20
+ begin
21
+ attribute = send x
22
+ attribute.blank?
23
+ rescue NoMethodError
24
+ true
7
25
  end
8
- end
9
-
26
+ }.map { |attribute|
27
+ "#{attribute.to_s.sub(/_\w+/, 's')}=#{send(attribute).inspect}"
28
+ }.join ' '
29
+ "#<#{self.class.name}:#{sprintf("0x%x", object_id)} #{attributes}>"
30
+ end
31
+
32
+ # def pretty_inspect
33
+ # respond_to?(:inspection) ?
34
+ # "#(#{self.class.name} {\n #{inspection.pretty_inspect}\n})" :
35
+ # super
36
+ # end
37
+
38
+ # def inspect
39
+ # inspection = []
40
+ # inspection.concat(options.dup.delete_if { |k,v| v.blank? }.sort_by(&:first).map { |k,v| "#{k}: #{v.inspect}" })
41
+ # "#<#{self.class.name} #{inspection * ', '}>"
42
+ # end
43
+
44
+ protected
45
+
46
+ # Default behavior of method missing should be to delegate all calls
47
+ # to the target of the proxy. This can be overridden in special cases.
48
+ #
49
+ # @param [ String, Symbol ] name The name of the method.
50
+ # @param [ Array ] *args The arguments passed to the method.
51
+ def method_missing(name, *args, &block)
52
+ xml.send(name, *args, &block)
10
53
  end
54
+
55
+ # def method_missing(method, *args, &block)
56
+ # unless self.instance_variables.include?(:"@#{method}")
57
+ # "#{method} is unavailable."
58
+ # end
59
+ # end
60
+
11
61
  end
@@ -0,0 +1,187 @@
1
+ class EndecaOnDemand::Query
2
+
3
+ include EndecaOnDemand::PP
4
+
5
+ def inspect_attributes; [ :uri, :xml, :errors, :options ]; end
6
+
7
+ ## fields ##
8
+
9
+ attr_reader :body, :client, :errors, :http, :options, :response, :uri, :xml
10
+
11
+ def initialize(client, options = {})
12
+ @client, @options = client, options.dup.with_indifferent_access
13
+
14
+ process_options!
15
+ end
16
+
17
+ ## associations ##
18
+
19
+ def body
20
+ @body ||= to_xml
21
+ end
22
+
23
+ def http
24
+ @http ||= Net::HTTP.new(uri.host, uri.port)
25
+ end
26
+
27
+ def response
28
+ @response ||= EndecaOnDemand::Response.new(self, http.post(uri.path, body, 'Content-type' => 'application/xml'))
29
+ end
30
+
31
+ def uri
32
+ @uri ||= URI.parse(client.api)
33
+ end
34
+
35
+ def xml
36
+ @xml ||= Nokogiri::XML(body) { |config| config.strict.noblanks }
37
+ end
38
+
39
+ ##
40
+
41
+ ## xml builder ##
42
+
43
+ def to_xml
44
+ Builder::XmlMarkup.new(indent: 2).tag!(:Query) do |xml|
45
+
46
+ Flags(xml)
47
+ KeywordSearch(xml)
48
+ NavigationQuery(xml)
49
+ CategoryNavigationQuery(xml)
50
+ Sorting(xml)
51
+ Paging(xml)
52
+ AdvancedParameters(xml)
53
+
54
+ end
55
+ # @query.Query do
56
+ # @query << @body.target!
57
+ # end
58
+
59
+ # begin
60
+ # # ask Domino what this is?
61
+ # # @request, @raw_response = @http.post(@uri.path, @query.target!, 'Content-type' => 'application/xml')
62
+
63
+ # @response = Nokogiri::XML(fetch_response.body)
64
+
65
+ # build_records
66
+ # build_breadcrumbs
67
+ # build_dimensions
68
+ # build_business_rules
69
+ # build_search_reports
70
+ # build_selected_dimension_value_ids
71
+ # build_keyword_redirect
72
+ # rescue Timeout::Error, Errno::EINVAL, Errno::ECONNRESET, Errno::ECONNREFUSED, EOFError, Net::HTTPBadResponse, Net::HTTPHeaderSyntaxError, Net::ProtocolError => error
73
+ # @error = error
74
+ # end
75
+ end
76
+
77
+ ### data ###
78
+
79
+ def flags
80
+ @flags ||= options[:flags] = (options[:flags] || {}).inject({}.with_indifferent_access) do |hash,(key,value)|
81
+ hash.tap do
82
+ hash[key.to_s.underscore] = value
83
+ end
84
+ end
85
+ end
86
+
87
+ def searches
88
+ @searches ||= options[:searches]
89
+ end
90
+
91
+ def dimensions
92
+ @dimensions ||= [*options[:dimensions]]
93
+ end
94
+
95
+ def category
96
+ @category ||= options[:category]
97
+ end
98
+
99
+ def sorts
100
+ @sorts ||= options[:sorts]
101
+ end
102
+
103
+ def paging
104
+ @paging ||= options[:paging] = (options[:paging] || {}).tap do |paging|
105
+ if paging.has_key?(:page) and paging.has_key?(:per_page)
106
+ paging[:offset] = (paging[:page].to_i * paging[:per_page].to_i rescue 0)
107
+ end
108
+ end
109
+ end
110
+
111
+ def advanced_parameters
112
+ @advanced_parameters ||= options[:advanced] = (options[:advanced] || {}).inject({}.with_indifferent_access) do |hash,(key,value)|
113
+ hash.tap do
114
+ hash[key.to_s.underscore] = value
115
+ end
116
+ end
117
+ end
118
+
119
+ ###
120
+
121
+ protected
122
+
123
+ def Flags(xml)
124
+ return if flags.blank?
125
+ flags.each do |flag,value|
126
+ xml.tag!(flag.to_s.camelcase, value)
127
+ end
128
+ end
129
+
130
+ def KeywordSearch(xml)
131
+ return if searches.blank?
132
+ xml.tag!(:Searches) do
133
+ searches.each do |key,term|
134
+ xml.tag!(:Search) do
135
+ xml.tag!('search-key', key)
136
+ xml.tag!('search-term', term)
137
+ end
138
+ end
139
+ end
140
+ end
141
+
142
+ def NavigationQuery(xml)
143
+ return if dimensions.blank?
144
+ xml.tag!(:SelectedDimensionValueIds) do
145
+ dimensions.each do |dimension|
146
+ xml.tag!(:DimensionValueId, dimension)
147
+ end
148
+ end
149
+ end
150
+
151
+ def CategoryNavigationQuery(xml)
152
+ return if category.blank?
153
+ xml.tag!(:Category) do
154
+ xml.tag!(:CategoryId, category)
155
+ end
156
+ end
157
+
158
+ def Sorting(xml)
159
+ return if sorts.blank?
160
+ xml.tag!(:Sorts) do
161
+ sorts.each do |key,direction|
162
+ xml.tag!(:Sort) do
163
+ xml.tag!('sort-key', key)
164
+ xml.tag!('sort-direction', direction.to_s.capitalize)
165
+ end
166
+ end
167
+ end
168
+ end
169
+
170
+ def Paging(xml)
171
+ return if paging.blank?
172
+ xml.tag!(:RecordOffset, paging[:offset]) if paging.has_key?(:offset)
173
+ xml.tag!(:RecordsPerPage, paging[:per_page]) if paging.has_key?(:per_page)
174
+ end
175
+
176
+ def AdvancedParameters(xml)
177
+
178
+ end
179
+
180
+ def process_options!
181
+ default_options = client.default_options[:query] || {}
182
+ @options = default_options.dup.merge(options)
183
+ end
184
+
185
+ ##
186
+
187
+ end
@@ -0,0 +1,42 @@
1
+ class EndecaOnDemand::Response::AppliedFilters::KeywordRedirect < EndecaOnDemand::Proxy
2
+
3
+ include EndecaOnDemand::PP
4
+
5
+ def inspect_attributes; [ :options ]; end
6
+
7
+ ## fields ##
8
+
9
+ attr_reader :applied_filters
10
+
11
+ def initialize(applied_filters, xml)
12
+ @applied_filters, @xml = applied_filters, xml
13
+ end
14
+
15
+ ## override proxy ##
16
+
17
+ def class
18
+ EndecaOnDemand::Response::AppliedFilters::KeywordRedirect
19
+ end
20
+
21
+ ##
22
+
23
+ ## data ##
24
+
25
+ def options
26
+ @options ||= xml.children.inject({}.with_indifferent_access) do |hash,child|
27
+ hash.tap do
28
+ hash[child.name] = child.content
29
+ end
30
+ end
31
+ end
32
+
33
+ ##
34
+
35
+ protected
36
+
37
+ def method_missing(method, *args, &block)
38
+ return options[method] if @options.present? and options.has_key?(method)
39
+ super(method, *args, &block)
40
+ end
41
+
42
+ end
@@ -0,0 +1,42 @@
1
+ class EndecaOnDemand::Response::AppliedFilters::SearchReport::Search < EndecaOnDemand::Proxy
2
+
3
+ include EndecaOnDemand::PP
4
+
5
+ def inspect_attributes; [ :options ]; end
6
+
7
+ ## fields ##
8
+
9
+ attr_reader :search_report
10
+
11
+ def initialize(search_report, xml)
12
+ @search_report, @xml = search_report, xml
13
+ end
14
+
15
+ ## override proxy ##
16
+
17
+ def class
18
+ EndecaOnDemand::Response::AppliedFilters::SearchReport::Search
19
+ end
20
+
21
+ ##
22
+
23
+ ## data ##
24
+
25
+ def options
26
+ @options ||= xml.children.inject({}.with_indifferent_access) do |hash,child|
27
+ hash.tap do
28
+ hash[child.name] = child.content
29
+ end
30
+ end
31
+ end
32
+
33
+ ##
34
+
35
+ protected
36
+
37
+ def method_missing(method, *args, &block)
38
+ return options[method] if @options.present? and options.has_key?(method)
39
+ super(method, *args, &block)
40
+ end
41
+
42
+ end
@@ -0,0 +1,50 @@
1
+ class EndecaOnDemand::Response::AppliedFilters::SearchReport < EndecaOnDemand::Proxy
2
+
3
+ include EndecaOnDemand::PP
4
+
5
+ def inspect_attributes; [ :options, :search ]; end
6
+
7
+ ## fields ##
8
+
9
+ attr_reader :applied_filters, :search
10
+
11
+ def initialize(applied_filters, xml)
12
+ @applied_filters, @xml = applied_filters, xml
13
+ end
14
+
15
+ ## override proxy ##
16
+
17
+ def class
18
+ EndecaOnDemand::Response::AppliedFilters::SearchReport
19
+ end
20
+
21
+ ##
22
+
23
+ ## associations ##
24
+
25
+ def search
26
+ @search ||= EndecaOnDemand::Response::AppliedFilters::SearchReport::Search.new(self, xml.children.css('Search'))
27
+ end
28
+
29
+ ##
30
+
31
+ ## data ##
32
+
33
+ def options
34
+ @options ||= xml.xpath('child::node()[not(local-name() = "Search")]').inject({}.with_indifferent_access) do |hash,child|
35
+ hash.tap do
36
+ hash[child.name] = child.content
37
+ end
38
+ end
39
+ end
40
+
41
+ ##
42
+
43
+ protected
44
+
45
+ def method_missing(method, *args, &block)
46
+ return options[method] if @options.present? and options.has_key?(method)
47
+ super(method, *args, &block)
48
+ end
49
+
50
+ end
@@ -0,0 +1,32 @@
1
+ class EndecaOnDemand::Response::AppliedFilters::SelectedDimensionValueId < EndecaOnDemand::Proxy
2
+
3
+ include EndecaOnDemand::PP
4
+
5
+ def inspect_attributes; [ :value ]; end
6
+
7
+ ## fields ##
8
+
9
+ attr_reader :applied_filters
10
+
11
+ def initialize(applied_filters, xml)
12
+ @applied_filters, @xml = applied_filters, xml
13
+ end
14
+
15
+ ## override proxy ##
16
+
17
+ def class
18
+ EndecaOnDemand::Response::AppliedFilters::SelectedDimensionValueId
19
+ end
20
+
21
+ ##
22
+
23
+ ## data ##
24
+
25
+ def to_s
26
+ xml.content
27
+ end
28
+ alias :value :to_s
29
+
30
+ ##
31
+
32
+ end
@@ -0,0 +1,39 @@
1
+ class EndecaOnDemand::Response::AppliedFilters < EndecaOnDemand::Proxy
2
+
3
+ include EndecaOnDemand::PP
4
+
5
+ def inspect_attributes; [ :keyword_redirects, :search_reports, :selected_dimension_value_ids ]; end
6
+
7
+ ## fields ##
8
+
9
+ attr_reader :response, :search_reports
10
+
11
+ def initialize(response, xml)
12
+ @response, @xml = response, xml
13
+ end
14
+
15
+ ## override proxy ##
16
+
17
+ def class
18
+ EndecaOnDemand::Response::AppliedFilters
19
+ end
20
+
21
+ ##
22
+
23
+ ## associations ##
24
+
25
+ def search_reports
26
+ @search_reports ||= EndecaOnDemand::Collection.new(EndecaOnDemand::Response::AppliedFilters::SearchReport, xml.children.css('SearchReports > SearchReport'), self)
27
+ end
28
+
29
+ def selected_dimension_value_ids
30
+ @selected_dimension_value_ids ||= EndecaOnDemand::Collection.new(EndecaOnDemand::Response::AppliedFilters::SelectedDimensionValueId, xml.children.css('SelectedDimensionValueIds > DimensionValueId'), self)
31
+ end
32
+
33
+ def keyword_redirects
34
+ @keyword_redirects ||= EndecaOnDemand::Collection.new(EndecaOnDemand::Response::AppliedFilters::KeywordRedirect, xml.children.css('KeywordRedirects'), self)
35
+ end
36
+
37
+ ##
38
+
39
+ end
@@ -0,0 +1,42 @@
1
+ class EndecaOnDemand::Response::Breadcrumb::Bread < EndecaOnDemand::Proxy
2
+
3
+ include EndecaOnDemand::PP
4
+
5
+ def inspect_attributes; [ :options ]; end
6
+
7
+ ## fields ##
8
+
9
+ attr_reader :breadcrumb
10
+
11
+ def initialize(breadcrumb, xml)
12
+ @breadcrumb, @xml = breadcrumb, xml
13
+ end
14
+
15
+ ## override proxy ##
16
+
17
+ def class
18
+ EndecaOnDemand::Response::Breadcrumb::Bread
19
+ end
20
+
21
+ ##
22
+
23
+ ## data ##
24
+
25
+ def options
26
+ xml.children.inject({}.with_indifferent_access) do |hash,child|
27
+ hash.tap do
28
+ hash[child.name] = child.content
29
+ end
30
+ end
31
+ end
32
+
33
+ ##
34
+
35
+ protected
36
+
37
+ def method_missing(method, *args, &block)
38
+ return options[method] if options.has_key?(method)
39
+ super(method, *args, &block)
40
+ end
41
+
42
+ end
@@ -0,0 +1,35 @@
1
+ class EndecaOnDemand::Response::Breadcrumb < EndecaOnDemand::Proxy
2
+
3
+ include EndecaOnDemand::PP
4
+
5
+ def inspect_attributes; [ :breads ]; end
6
+
7
+ ## fields ##
8
+
9
+ attr_reader :response
10
+
11
+ def initialize(response, xml)
12
+ @response, @xml = response, xml
13
+ end
14
+
15
+ ## override proxy ##
16
+
17
+ def class
18
+ EndecaOnDemand::Response::Breadcrumb
19
+ end
20
+
21
+ # def inspection
22
+ # [ "breads: #{breads.inspect}" ]
23
+ # end
24
+
25
+ ##
26
+
27
+ ## associations ##
28
+
29
+ def breads
30
+ @breads ||= EndecaOnDemand::Collection.new(EndecaOnDemand::Response::Breadcrumb::Bread, xml.children.css('Bread'), self)
31
+ end
32
+
33
+ ##
34
+
35
+ end