drawbridge 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +17 -0
- data/Gemfile +3 -0
- data/LICENSE.txt +22 -0
- data/README.md +52 -0
- data/Rakefile +10 -0
- data/drawbridge.gemspec +27 -0
- data/lib/drawbridge.rb +13 -0
- data/lib/drawbridge/adapter.rb +35 -0
- data/lib/drawbridge/config.rb +48 -0
- data/lib/drawbridge/debug.rb +18 -0
- data/lib/drawbridge/mapper.rb +214 -0
- data/lib/drawbridge/refinement_scrubber.rb +26 -0
- data/lib/drawbridge/request.rb +92 -0
- data/lib/drawbridge/result.rb +146 -0
- data/lib/drawbridge/transformer.rb +190 -0
- data/lib/drawbridge/version.rb +3 -0
- data/spec/debug_spec.rb +23 -0
- data/spec/helper.rb +18 -0
- data/spec/refinement_scrubber_spec.rb +68 -0
- data/spec/request_spec.rb +84 -0
- data/spec/result_spec.rb +388 -0
- data/spec/transformer_spec.rb +249 -0
- metadata +175 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2013 Michael Hoitomt
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,52 @@
|
|
1
|
+
# Drawbridge
|
2
|
+
|
3
|
+
[![Code Climate](https://codeclimate.com/repos/51d44e1c13d637371b0595e6/badges/f566eeb7ce85f64716ec/gpa.png)](https://codeclimate.com/repos/51d44e1c13d637371b0595e6/feed)
|
4
|
+
|
5
|
+
It draws the bridge between Endeca JSON bridge and IDG sites.
|
6
|
+
|
7
|
+
Sometimes it's up, sometimes it's down.
|
8
|
+
|
9
|
+
## Installation
|
10
|
+
|
11
|
+
Add this line to your application's Gemfile:
|
12
|
+
|
13
|
+
gem 'drawbridge'
|
14
|
+
|
15
|
+
And then execute:
|
16
|
+
|
17
|
+
$ bundle
|
18
|
+
|
19
|
+
Or install it yourself as:
|
20
|
+
|
21
|
+
$ gem install drawbridge
|
22
|
+
|
23
|
+
## Setup
|
24
|
+
|
25
|
+
To use:
|
26
|
+
|
27
|
+
```ruby
|
28
|
+
require 'drawbridge'
|
29
|
+
|
30
|
+
Drawbridge.setup do |config|
|
31
|
+
config.bridge_url = "http://example.com:8080"
|
32
|
+
config.bridge_path = "BridgeAPIService_apt64/servlet/bridgeapi.json"
|
33
|
+
# e.g. ENDECA_DEBUG=true rackup
|
34
|
+
config.endeca_debug = ENV.fetch('ENDECA_DEBUG') { false }
|
35
|
+
# optional, default is 5
|
36
|
+
config.timeout = 5
|
37
|
+
# optional, default is to change ' into ' before JSON is parsed
|
38
|
+
config.skip_single_quote_encoding = true
|
39
|
+
end
|
40
|
+
```
|
41
|
+
|
42
|
+
## Usage
|
43
|
+
|
44
|
+
[Endeca documentation here](https://github.com/primedia/idg/wiki/Endeca)
|
45
|
+
|
46
|
+
## Contributing
|
47
|
+
|
48
|
+
1. Fork it
|
49
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
50
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
51
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
52
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
data/drawbridge.gemspec
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'drawbridge/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |gem|
|
7
|
+
gem.name = "drawbridge"
|
8
|
+
gem.version = Drawbridge::VERSION
|
9
|
+
gem.authors = ["Michael Hoitomt"]
|
10
|
+
gem.email = ["mhoitomt@primedia.com"]
|
11
|
+
gem.description = %q{Papi's Wife}
|
12
|
+
gem.summary = %q{Papi's Replacement}
|
13
|
+
gem.homepage = ""
|
14
|
+
|
15
|
+
gem.files = `git ls-files`.split($/)
|
16
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
17
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
18
|
+
gem.require_paths = ["lib"]
|
19
|
+
|
20
|
+
gem.add_development_dependency('turn', '~> 0.8.3')
|
21
|
+
gem.add_development_dependency('mocha', '~> 0.13.0')
|
22
|
+
gem.add_development_dependency('rake')
|
23
|
+
|
24
|
+
gem.add_dependency('curb', '~> 0.8.4')
|
25
|
+
gem.add_dependency('oj', '~> 2.0.14')
|
26
|
+
gem.add_dependency('activesupport', '>= 3.2.9')
|
27
|
+
end
|
data/lib/drawbridge.rb
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
require 'active_support/core_ext/object/blank'
|
2
|
+
require 'drawbridge/version'
|
3
|
+
require 'drawbridge/config'
|
4
|
+
require 'drawbridge/debug'
|
5
|
+
require 'drawbridge/transformer'
|
6
|
+
require 'drawbridge/adapter'
|
7
|
+
require 'drawbridge/mapper'
|
8
|
+
require 'drawbridge/request'
|
9
|
+
require 'drawbridge/result'
|
10
|
+
require 'drawbridge/refinement_scrubber'
|
11
|
+
|
12
|
+
module Drawbridge
|
13
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module Drawbridge
|
2
|
+
class Adapter
|
3
|
+
include Drawbridge::Transformer
|
4
|
+
|
5
|
+
attr_accessor :mapper
|
6
|
+
|
7
|
+
def initialize(mapper)
|
8
|
+
@mapper = mapper
|
9
|
+
end
|
10
|
+
|
11
|
+
def search_path
|
12
|
+
"#{Drawbridge.config.bridge_url}/#{Drawbridge.config.bridge_path}"
|
13
|
+
end
|
14
|
+
|
15
|
+
def build_all_query
|
16
|
+
params = {
|
17
|
+
select: mapper.fields,
|
18
|
+
where: mapper.record_search,
|
19
|
+
record_filters: mapper.record_filters,
|
20
|
+
root_navigtion: mapper.navigation,
|
21
|
+
expand_refinements: mapper.dimensions,
|
22
|
+
geo_filter: mapper.geo_filters,
|
23
|
+
range_filter: mapper.range_filter,
|
24
|
+
rollup: mapper.rollup_key,
|
25
|
+
match_mode: mapper.mode,
|
26
|
+
aggregate_offset: mapper.aggregate_offset,
|
27
|
+
offset: mapper.offset,
|
28
|
+
limit: mapper.limit_param,
|
29
|
+
order: mapper.sort_key
|
30
|
+
}
|
31
|
+
transform(params)
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
module Drawbridge
|
2
|
+
class Config
|
3
|
+
attr_accessor :data
|
4
|
+
def initialize(data={})
|
5
|
+
@data = {}
|
6
|
+
update!(data)
|
7
|
+
end
|
8
|
+
|
9
|
+
def update!(data)
|
10
|
+
data.each do |key, value|
|
11
|
+
self[key.to_sym] = value
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def [](key)
|
16
|
+
@data[key.to_sym]
|
17
|
+
end
|
18
|
+
|
19
|
+
def []=(key, value)
|
20
|
+
@data[key.to_sym] = value
|
21
|
+
end
|
22
|
+
|
23
|
+
def keys
|
24
|
+
@data.keys
|
25
|
+
end
|
26
|
+
|
27
|
+
def to_hash
|
28
|
+
@data
|
29
|
+
end
|
30
|
+
|
31
|
+
def method_missing(sym, *args)
|
32
|
+
if sym.to_s =~ /(.+)=$/
|
33
|
+
self[$1] = args.first
|
34
|
+
else
|
35
|
+
self[sym]
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
class << self
|
41
|
+
attr_accessor :config, :data
|
42
|
+
|
43
|
+
def setup
|
44
|
+
self.config ||= Config.new
|
45
|
+
yield config
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
module Drawbridge
|
3
|
+
class Debug
|
4
|
+
|
5
|
+
def self.draw_line
|
6
|
+
@draw_line ||= "*"*80
|
7
|
+
end
|
8
|
+
|
9
|
+
# @params prepend_text: Endeca_Params
|
10
|
+
# @params debug_value: any object thaat can be inspected
|
11
|
+
def self.log(prepend_text, debug_value)
|
12
|
+
if Drawbridge.config.endeca_debug
|
13
|
+
puts "#{draw_line}\n#{prepend_text}: #{debug_value.inspect}\n#{draw_line}"
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,214 @@
|
|
1
|
+
module Drawbridge
|
2
|
+
class Mapper
|
3
|
+
|
4
|
+
|
5
|
+
# Class Level accessor - Makes pretty
|
6
|
+
class << self
|
7
|
+
def where(params)
|
8
|
+
self.new.where(params)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
def initialize
|
13
|
+
end
|
14
|
+
|
15
|
+
def where(params)
|
16
|
+
params.each do |k, v|
|
17
|
+
# regex should handle md5hash, and anything that doesn't have word characters
|
18
|
+
unless k[/\W|^\h+$/]
|
19
|
+
Mapper.send(:attr_accessor, k)
|
20
|
+
instance_variable_set("@#{k}", v)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
self
|
24
|
+
end
|
25
|
+
|
26
|
+
def order_by(sort_string)
|
27
|
+
@sort_string = sort_string
|
28
|
+
self
|
29
|
+
end
|
30
|
+
|
31
|
+
def sort_args
|
32
|
+
return [] unless @sort_string
|
33
|
+
sort_string = @sort_string.split(',').join('^+^')
|
34
|
+
sort_array = parse_sort(sort_string.gsub('-', '^'))
|
35
|
+
sort_array
|
36
|
+
end
|
37
|
+
|
38
|
+
def all
|
39
|
+
adapter = Drawbridge::Adapter.new(self)
|
40
|
+
query = adapter.build_all_query
|
41
|
+
result_params = Request.perform adapter.search_path, query
|
42
|
+
Result.new(result_params)
|
43
|
+
end
|
44
|
+
|
45
|
+
def first
|
46
|
+
results = all
|
47
|
+
results.records.empty? ? nil : results.records.first
|
48
|
+
end
|
49
|
+
|
50
|
+
def default_refinements
|
51
|
+
[]
|
52
|
+
end
|
53
|
+
|
54
|
+
# N
|
55
|
+
def navigation
|
56
|
+
(process_refinements(endeca_refinements) + default_refinements).flatten
|
57
|
+
end
|
58
|
+
|
59
|
+
# Ne - placeholder, might make more sense to hard-code in the adapter
|
60
|
+
def exposed_refinements
|
61
|
+
dimensions
|
62
|
+
end
|
63
|
+
|
64
|
+
# Ntt and Ntk
|
65
|
+
def record_search
|
66
|
+
record_key_values
|
67
|
+
end
|
68
|
+
|
69
|
+
# Ns
|
70
|
+
def sort_key
|
71
|
+
combined_sort
|
72
|
+
end
|
73
|
+
|
74
|
+
# Nr
|
75
|
+
def record_filters
|
76
|
+
[]
|
77
|
+
end
|
78
|
+
|
79
|
+
# Nf
|
80
|
+
def range_filter
|
81
|
+
[]
|
82
|
+
end
|
83
|
+
|
84
|
+
# Nf
|
85
|
+
def geo_filters
|
86
|
+
{}
|
87
|
+
end
|
88
|
+
|
89
|
+
# Nu
|
90
|
+
def rollup_key
|
91
|
+
""
|
92
|
+
end
|
93
|
+
|
94
|
+
# No
|
95
|
+
def offset
|
96
|
+
page_param.to_i * limit_param.to_i
|
97
|
+
end
|
98
|
+
|
99
|
+
# Nao
|
100
|
+
def aggregate_offset
|
101
|
+
page_param.to_i * limit_param.to_i
|
102
|
+
end
|
103
|
+
|
104
|
+
def page_param
|
105
|
+
@page ? @page.to_i - 1 : 0
|
106
|
+
end
|
107
|
+
|
108
|
+
def limit_param
|
109
|
+
@limit ||= 20
|
110
|
+
@limit.to_i
|
111
|
+
end
|
112
|
+
|
113
|
+
def radius_param
|
114
|
+
@radius ||= @rad
|
115
|
+
end
|
116
|
+
|
117
|
+
def combined_sort
|
118
|
+
(base_sort + sort_args).flatten.uniq
|
119
|
+
end
|
120
|
+
|
121
|
+
def base_sort
|
122
|
+
[]
|
123
|
+
end
|
124
|
+
|
125
|
+
def record_key_values
|
126
|
+
{}
|
127
|
+
end
|
128
|
+
|
129
|
+
def dimensions
|
130
|
+
[]
|
131
|
+
end
|
132
|
+
|
133
|
+
def dimensions=(dimension_list)
|
134
|
+
dimension_list
|
135
|
+
end
|
136
|
+
|
137
|
+
def fields
|
138
|
+
[]
|
139
|
+
end
|
140
|
+
|
141
|
+
def fields=(field_list)
|
142
|
+
field_list
|
143
|
+
end
|
144
|
+
|
145
|
+
def method_missing(meth)
|
146
|
+
nil
|
147
|
+
end
|
148
|
+
|
149
|
+
def name
|
150
|
+
""
|
151
|
+
end
|
152
|
+
|
153
|
+
def mode
|
154
|
+
[]
|
155
|
+
end
|
156
|
+
|
157
|
+
# converts refinements to int ids if they were base(36) encoded before
|
158
|
+
# @params - Array
|
159
|
+
# ["4j7"] => ['5875']
|
160
|
+
def process_refinements(refinements)
|
161
|
+
return [] if refinements.blank?
|
162
|
+
|
163
|
+
refinements.collect! do |ref|
|
164
|
+
ref.to_i(36).to_s
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
def parse_refinements(refinements)
|
169
|
+
refinements.collect! do |ref|
|
170
|
+
ref.to_s[/^\d+$/] ? ref.to_s : ref.to_i(36).to_s
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
protected
|
175
|
+
|
176
|
+
def refinements
|
177
|
+
@refinements ||= ""
|
178
|
+
end
|
179
|
+
|
180
|
+
def endeca_refinements
|
181
|
+
return [] if @refinements.nil?
|
182
|
+
split_out_dimension_codes(@refinements.dup) || []
|
183
|
+
end
|
184
|
+
|
185
|
+
def split_out_dimension_codes(refs)
|
186
|
+
nodes = refs.split('-').last
|
187
|
+
nodes = nodes.split(/\+|\s/) if nodes
|
188
|
+
nodes == ['miles'] ? nil : nodes
|
189
|
+
end
|
190
|
+
|
191
|
+
def parse_sort(sort)
|
192
|
+
return [] if sort.nil? || sort.empty?
|
193
|
+
[].tap do |a|
|
194
|
+
aray_sort = sort.split('^+^')
|
195
|
+
aray_sort.each do |s|
|
196
|
+
key, val = s.split('^')
|
197
|
+
a << {key => endeca_sort_value(val)}
|
198
|
+
end
|
199
|
+
end
|
200
|
+
end
|
201
|
+
|
202
|
+
def endeca_sort_value(value)
|
203
|
+
case value
|
204
|
+
when "asc"
|
205
|
+
return "0"
|
206
|
+
when "desc"
|
207
|
+
return "1"
|
208
|
+
else
|
209
|
+
return value
|
210
|
+
end
|
211
|
+
end
|
212
|
+
|
213
|
+
end
|
214
|
+
end
|