endeca_on_demand 1.0.1 → 1.1.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.
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