rlocu 0.1.0 → 0.2.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.rspec +3 -0
- data/README.md +27 -26
- data/Rakefile +8 -7
- data/lib/rlocu.rb +21 -6
- data/lib/rlocu/geo_json.rb +22 -0
- data/lib/rlocu/menu.rb +113 -99
- data/lib/rlocu/query_builder.rb +55 -0
- data/lib/rlocu/utilities/lat_long_radius.rb +27 -0
- data/lib/rlocu/venue.rb +107 -24
- data/lib/rlocu/venue_query.rb +35 -0
- data/lib/rlocu/venue_search.rb +39 -21
- data/lib/rlocu/version.rb +1 -1
- data/rlocu.gemspec +13 -12
- data/spec/config_rlocu_spec_helper.rb +5 -0
- data/spec/geo_json_spec.rb +12 -0
- data/spec/menu/item_spec.rb +13 -0
- data/spec/menu/menu_item_spec.rb +13 -0
- data/spec/menu/option_group_spec.rb +10 -0
- data/spec/menu/option_spec.rb +9 -0
- data/spec/menu/section_spec.rb +9 -0
- data/spec/menu/section_text_spec.rb +9 -0
- data/spec/menu/subsection_spec.rb +9 -0
- data/spec/menu_spec.rb +9 -0
- data/spec/query/key_value_condition_spec.rb +37 -0
- data/spec/spec_helper.rb +99 -0
- data/spec/support/rspec_helpers.rb +53 -0
- data/spec/utilities/lat_long_radius_spec.rb +7 -0
- data/spec/venue_query_spec.rb +29 -0
- data/spec/venue_search_spec.rb +31 -0
- metadata +59 -25
- data/lib/rlocu/config.rb +0 -23
- data/lib/rlocu/venue_details.rb +0 -35
- data/lib/utilities.rb +0 -40
- data/test/test_truth.rb +0 -7
- data/test/test_utilities.rb +0 -21
- data/test/test_venue.rb +0 -15
- data/test/test_venue_details.rb +0 -11
- data/test/test_venue_search.rb +0 -26
@@ -0,0 +1,27 @@
|
|
1
|
+
module Rlocu
|
2
|
+
module Utilities
|
3
|
+
class LatLongRadius
|
4
|
+
attr_reader :lat, :long, :radius
|
5
|
+
def initialize(lat:, long:, radius: 5000)
|
6
|
+
if !lat.is_a?(Numeric) || !long.is_a?(Numeric) || !radius.is_a?(Numeric)
|
7
|
+
raise ArgumentError.new "Parameters must be numeric (floats)"
|
8
|
+
end
|
9
|
+
@lat = lat
|
10
|
+
@long = long
|
11
|
+
@radius = radius
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.bounds(lat_lng1, lat_lng2)
|
15
|
+
"#{@lat_lng1.to_s}|#{@lat_lng2.to_s}"
|
16
|
+
end
|
17
|
+
|
18
|
+
def to_s
|
19
|
+
"#{lat},#{long}"
|
20
|
+
end
|
21
|
+
|
22
|
+
def to_a
|
23
|
+
[lat, long, radius]
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
data/lib/rlocu/venue.rb
CHANGED
@@ -1,34 +1,117 @@
|
|
1
1
|
module Rlocu
|
2
2
|
class Venue
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
#
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
def initialize(
|
3
|
+
ExternalID = Struct.new(:id, :url, :mobile_url)
|
4
|
+
Category = Struct.new(:name, :str_id)
|
5
|
+
Contact = Struct.new(:phone, :fax, :email, :phones, :faxes, :emails, :business_owner)
|
6
|
+
# skipping Locu object for now
|
7
|
+
Delivery = Struct.new(:will_deliver, :hours, :minimum_order, :areas)
|
8
|
+
# skipping Extended object for now
|
9
|
+
Media = Struct.new(:cover_photo, :venue_photos, :menu_photos, :logos, :videos)
|
10
|
+
ATTRIBUTES = %i{locu_id name short_name description website_url menu_url menus menu_items open_hours external redirected_from categories location contact locu delivery extended media}
|
11
|
+
|
12
|
+
ATTRIBUTES.each { |prop| attr_accessor prop }
|
13
|
+
|
14
|
+
def initialize(venue)
|
15
|
+
@external = []
|
16
|
+
@categories = []
|
15
17
|
@menus = []
|
16
|
-
|
18
|
+
@not_supported_attributes = []
|
19
|
+
build_from_hash(venue)
|
20
|
+
end
|
21
|
+
|
22
|
+
def build_from_hash(venue)
|
23
|
+
venue.each do |k,v|
|
24
|
+
begin
|
25
|
+
self.send("#{k.to_s}=", v)
|
26
|
+
rescue NoMethodError
|
27
|
+
@not_supported_attributes << k
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def latitude
|
33
|
+
location.latitude
|
34
|
+
end
|
35
|
+
|
36
|
+
def longitude
|
37
|
+
location.longitude
|
17
38
|
end
|
18
|
-
|
19
|
-
def
|
20
|
-
|
39
|
+
|
40
|
+
def not_supported_attributes
|
41
|
+
@not_supported_attributes
|
42
|
+
end
|
43
|
+
|
44
|
+
def external=(externals_list)
|
45
|
+
@external = []
|
46
|
+
externals_list.each { |external_id| @external << ExternalID.new(id: external_id['id'], url: external_id['url'], mobile_url: external_id['mobile_url'])}
|
47
|
+
end
|
48
|
+
|
49
|
+
def categories=(categories_list)
|
50
|
+
@categories = []
|
51
|
+
categories_list.each do |category|
|
52
|
+
c = Category.new
|
53
|
+
category.each { |k,v| c.send("#{k.to_s}=", v) }
|
54
|
+
@categories << c
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def location=(location)
|
59
|
+
@location = Location.new(location)
|
21
60
|
end
|
22
61
|
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
62
|
+
def contact=(contact)
|
63
|
+
c = Contact.new
|
64
|
+
contact.each { |k,v| c.send("#{k.to_s}=", v) }
|
65
|
+
@location = c
|
66
|
+
end
|
67
|
+
|
68
|
+
def delivery=(delivery)
|
69
|
+
d = Delivery.new
|
70
|
+
delivery.each { |k,v| d.send("#{k.to_s}=", v) }
|
71
|
+
@location = l
|
72
|
+
end
|
73
|
+
|
74
|
+
def extended=(extended)
|
75
|
+
e = Extended.new
|
76
|
+
extended.each { |k,v| e.send("#{k.to_s}=", v) }
|
77
|
+
@extended = e
|
78
|
+
end
|
79
|
+
|
80
|
+
def media=(media)
|
81
|
+
m = Media.new
|
82
|
+
media.each { |k,v| m.send("#{k.to_s}=", v) }
|
83
|
+
@media = m
|
84
|
+
end
|
85
|
+
|
86
|
+
def menus=(menu_list)
|
87
|
+
@menus = []
|
88
|
+
menu_list.each do |menu|
|
89
|
+
@menus << Menu.new(menu)
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
class Location
|
94
|
+
attr_reader :address1, :address2, :address3, :locality, :region, :postal_code, :country, :geo
|
95
|
+
def initialize(location)
|
96
|
+
@address1 = location['address1']
|
97
|
+
@address2 = location['address2']
|
98
|
+
@address3 = location['address3']
|
99
|
+
@locality = location['locality']
|
100
|
+
@region = location['region']
|
101
|
+
@postal_code = location['postal_code']
|
102
|
+
@country = location['country']
|
103
|
+
@geo = GeoJSON.new(location['geo'])
|
104
|
+
end
|
105
|
+
|
106
|
+
def latitude
|
107
|
+
geo.latitude
|
108
|
+
end
|
109
|
+
|
110
|
+
def longitude
|
111
|
+
geo.longitude
|
30
112
|
end
|
31
113
|
end
|
32
114
|
|
115
|
+
# skipping menu_items since they can be accessed through the menus and only 15 are returned currently
|
33
116
|
end
|
34
|
-
end
|
117
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'rest-client'
|
2
|
+
require 'json'
|
3
|
+
|
4
|
+
module Rlocu
|
5
|
+
class VenueQuery
|
6
|
+
include Rlocu::QueryBuilder
|
7
|
+
VenueQueryError = Class.new(StandardError)
|
8
|
+
|
9
|
+
attr_reader :return_fields
|
10
|
+
|
11
|
+
def initialize(query_conditions:, return_fields:)
|
12
|
+
raise ArgumentError, 'Query Conditions Param must be an array of QueryConditions.' if !query_conditions.is_a?(Array) || !query_conditions.first.is_a?(QueryCondition)
|
13
|
+
|
14
|
+
raise ArgumentError, 'Return Fields Param must be an array of fields.' if !return_fields.is_a?(Array) || return_fields.empty?
|
15
|
+
@query_conditions = query_conditions
|
16
|
+
@return_fields = return_fields
|
17
|
+
end
|
18
|
+
|
19
|
+
def query_conditions
|
20
|
+
@query_conditions.map(&:to_h)
|
21
|
+
end
|
22
|
+
|
23
|
+
def form_data
|
24
|
+
{api_key: Rlocu.api_key, fields: return_fields, venue_queries: query_conditions }.to_json
|
25
|
+
end
|
26
|
+
|
27
|
+
def query
|
28
|
+
# TODO wrap this in a timeout
|
29
|
+
result = JSON.parse(RestClient.post(base_url, form_data))
|
30
|
+
status = result['status']
|
31
|
+
raise VenueQueryError.new("Query failed with status [#{status}] and http_status [#{result['http_status']}]") unless status == 'success'
|
32
|
+
result['venues'].each.reduce([]) { |accum, venue| accum << Rlocu::Venue.new(venue) }
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
data/lib/rlocu/venue_search.rb
CHANGED
@@ -1,27 +1,45 @@
|
|
1
|
-
require 'open-uri'
|
2
|
-
require 'JSON'
|
3
|
-
|
4
1
|
module Rlocu
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
def self.query(params, &block)
|
10
|
-
unsupported = params.keys.reduce([]) {|memo,p| memo << p unless PARAMETERS.include? p; memo }
|
11
|
-
raise ArgumentError.new "Unsupported parameter to Rlocu::VenuSearch (#{unsupported})" unless unsupported.empty?
|
12
|
-
response = {}
|
13
|
-
open(url(params)) {|request| response = JSON.parse(request.read)}
|
14
|
-
venues = []
|
15
|
-
venues = response['objects'].reduce(venues) {|arr, o| arr << Rlocu::Venue.new(o) ; arr }
|
16
|
-
venues.each { |venue| yield venue } if block_given?
|
17
|
-
return venues
|
2
|
+
class VenueSearch
|
3
|
+
def initialize(return_fields: [])
|
4
|
+
@return_fields = return_fields.empty? ? %w{locu_id name location description menus} : return_fields
|
5
|
+
@key_value_conditions = []
|
18
6
|
end
|
19
7
|
|
20
|
-
|
8
|
+
def with_menus
|
9
|
+
@key_value_conditions << QueryBuilder::KeyValueCondition.new(key: 'menus', value: true, condition: '$present')
|
10
|
+
self
|
11
|
+
end
|
12
|
+
|
13
|
+
def in_lat_long_radius(lat:, long:, radius:)
|
14
|
+
lat_long_radius = Utilities::LatLongRadius.new(lat: lat, long: long, radius: radius)
|
15
|
+
@key_value_conditions << QueryBuilder::KeyValueCondition.new(key: 'location', value: lat_long_radius, condition: '$in_lat_lng_radius')
|
16
|
+
self
|
17
|
+
end
|
18
|
+
|
19
|
+
def locu_id(locu_id)
|
20
|
+
@key_value_conditions << QueryBuilder::KeyValueCondition.new(key: 'locu_id', value: locu_id)
|
21
|
+
self
|
22
|
+
end
|
21
23
|
|
22
|
-
def
|
23
|
-
|
24
|
+
def name(name)
|
25
|
+
@key_value_conditions << QueryBuilder::KeyValueCondition.new(key: 'name', value: name)
|
26
|
+
self
|
24
27
|
end
|
25
|
-
|
28
|
+
|
29
|
+
def in_categories(categories)
|
30
|
+
raise ArgumentError unless categories.is_a? Array
|
31
|
+
@key_value_conditions << QueryBuilder::KeyValueCondition.new(key: 'categories', value: categories, condition: '$contains_any')
|
32
|
+
self
|
33
|
+
end
|
34
|
+
|
35
|
+
def search
|
36
|
+
conditions = [QueryBuilder::QueryCondition.new(key_value_conditions: key_value_conditions)]
|
37
|
+
venue_query = VenueQuery.new(query_conditions: conditions, return_fields: return_fields)
|
38
|
+
venue_query.query
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
|
43
|
+
attr_reader :return_fields, :key_value_conditions
|
26
44
|
end
|
27
|
-
end
|
45
|
+
end
|
data/lib/rlocu/version.rb
CHANGED
data/rlocu.gemspec
CHANGED
@@ -3,17 +3,18 @@ lib = File.expand_path('../lib', __FILE__)
|
|
3
3
|
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
4
|
require 'rlocu/version'
|
5
5
|
|
6
|
-
Gem::Specification.new do |
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "rlocu"
|
8
|
+
spec.version = Rlocu::VERSION
|
9
|
+
spec.authors = ["Stephen Philp"]
|
10
|
+
spec.email = ["stephen@stephenphilp.com"]
|
11
|
+
spec.description = %q{A simple ruby wrapper for the Locu API}
|
12
|
+
spec.summary = %q{Rlocu is a ruby wrapper for Locu's API. The Locu API gives you access to real-time local business data, from opening hours to price lists, such as restaurant menus.}
|
13
|
+
spec.homepage = "https://github.com/swelltrain/rlocu"
|
14
14
|
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
15
|
+
spec.files = `git ls-files`.split($/)
|
16
|
+
spec.executables = spec.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
17
|
+
spec.add_development_dependency('rspec')
|
18
|
+
spec.add_runtime_dependency('rest-client')
|
19
|
+
spec.require_paths = ["lib"]
|
19
20
|
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
include Rlocu
|
3
|
+
|
4
|
+
RSpec.describe GeoJSON, '#new' do
|
5
|
+
it "should raise ArgumentError if geo['type'] does not exist" do
|
6
|
+
expect{ GeoJSON.new({}) }.to raise_error(ArgumentError)
|
7
|
+
end
|
8
|
+
|
9
|
+
it "should raise ArgumentError if geo['coordinates'] does not exist" do
|
10
|
+
expect{ GeoJSON.new({'type' => 'Point'}) }.to raise_error(ArgumentError)
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
include Rlocu
|
2
|
+
|
3
|
+
RSpec.describe Menu::Item, '#new' do
|
4
|
+
it 'should set type, name, description, price and option groups' do
|
5
|
+
item = Menu::Item.new(item_hash)
|
6
|
+
|
7
|
+
expect(item.type).to eq(item_hash['type'])
|
8
|
+
expect(item.name).to eq(item_hash['name'])
|
9
|
+
expect(item.description).to eq(item_hash['description'])
|
10
|
+
expect(item.price).to eq(item_hash['price'])
|
11
|
+
expect(item.option_groups).not_to be_empty
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
include Rlocu
|
2
|
+
|
3
|
+
RSpec.describe Menu::MenuItem, '#new' do
|
4
|
+
it 'should set menu_name, section_name, subsection_name, section_text, type, currency_symbol and photos' do
|
5
|
+
menu_item = Menu::MenuItem.new(menu_menu_item_hash)
|
6
|
+
expect(menu_item.menu_name).to eq(menu_menu_item_hash['menu_name'])
|
7
|
+
expect(menu_item.section_name).to eq(menu_menu_item_hash['section_name'])
|
8
|
+
expect(menu_item.subsection_name).to eq(menu_menu_item_hash['subsection_name'])
|
9
|
+
expect(menu_item.section_text).to eq(menu_menu_item_hash['section_text'])
|
10
|
+
expect(menu_item.currency_symbol).to eq(menu_menu_item_hash['currency_symbol'])
|
11
|
+
expect(menu_item.photos).not_to be_empty
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,10 @@
|
|
1
|
+
include Rlocu
|
2
|
+
|
3
|
+
RSpec.describe Menu::OptionGroup, '#new' do
|
4
|
+
it 'should set the name, text and options' do
|
5
|
+
option_group = Menu::OptionGroup.new(menu_option_group)
|
6
|
+
expect(option_group.type).to eq(menu_option_group['type'])
|
7
|
+
expect(option_group.text).to eq(menu_option_group['text'])
|
8
|
+
expect(option_group.options).not_to be_empty
|
9
|
+
end
|
10
|
+
end
|
@@ -0,0 +1,9 @@
|
|
1
|
+
include Rlocu
|
2
|
+
|
3
|
+
RSpec.describe Menu::Section, '#new' do
|
4
|
+
it 'should set section name and subsections' do
|
5
|
+
menu_section = Menu::Section.new(menu_section_hash)
|
6
|
+
expect(menu_section.section_name).to eq(menu_section_hash['section_name'])
|
7
|
+
expect(menu_section.subsections).not_to be_empty
|
8
|
+
end
|
9
|
+
end
|
@@ -0,0 +1,9 @@
|
|
1
|
+
include Rlocu
|
2
|
+
|
3
|
+
RSpec.describe Menu::SectionText, '#new' do
|
4
|
+
it 'should set type and text' do
|
5
|
+
menu_section_text = Menu::SectionText.new(menu_section_text_hash)
|
6
|
+
expect(menu_section_text.type).to eq(menu_section_text_hash['type'])
|
7
|
+
expect(menu_section_text.text).to eq(menu_section_text_hash['text'])
|
8
|
+
end
|
9
|
+
end
|
@@ -0,0 +1,9 @@
|
|
1
|
+
include Rlocu
|
2
|
+
|
3
|
+
RSpec.describe Menu::Subsection, '#new' do
|
4
|
+
it 'should set subsection_name and contents' do
|
5
|
+
menu_subsection = Menu::Subsection.new(menu_subsection_hash)
|
6
|
+
expect(menu_subsection.subsection_name).to eq(menu_subsection_hash['subsection_name'])
|
7
|
+
expect(menu_subsection.contents).not_to be_empty
|
8
|
+
end
|
9
|
+
end
|
data/spec/menu_spec.rb
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
include Rlocu::QueryBuilder
|
2
|
+
include Rlocu::Utilities
|
3
|
+
|
4
|
+
RSpec.describe KeyValueCondition, '#new' do
|
5
|
+
context "when condition is not one of #{KeyValueCondition::ValidConditions}" do
|
6
|
+
it 'should raise ArgumentError' do
|
7
|
+
expect{ KeyValueCondition.new(key: 'spec', value: 'spec', condition: 'spec') }.to raise_error(ArgumentError)
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
context 'when condition is $in_lat_lng_radius' do
|
12
|
+
context 'when value is not a LatLongRadius' do
|
13
|
+
it 'should raise an ArgumentError' do
|
14
|
+
expect{ KeyValueCondition.new(key: 'location', value: 'spec', condition: '$in_lat_lng_radius') }.to raise_error(ArgumentError)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
RSpec.describe KeyValueCondition, '#to_h' do
|
21
|
+
let(:value) { LatLongRadius.new(lat: 1, long: 1) }
|
22
|
+
|
23
|
+
let!(:key_value_condition) {KeyValueCondition.new(key: 'location', value: value, condition: '$in_lat_lng_radius')}
|
24
|
+
|
25
|
+
context 'when condition is specified' do
|
26
|
+
context 'when value is a LatLongRadius' do
|
27
|
+
it 'should call to_a on the value' do
|
28
|
+
expect(value).to receive(:to_a)
|
29
|
+
key_value_condition.to_h
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
it 'should return a hash' do
|
35
|
+
expect(key_value_condition.to_h).to be_a_kind_of(Hash)
|
36
|
+
end
|
37
|
+
end
|