rlocu 0.1.0 → 0.2.1

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.
@@ -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
@@ -1,34 +1,117 @@
1
1
  module Rlocu
2
2
  class Venue
3
- PROPERTIES = [:name, :locality, :street_address,
4
- :cuisines, :region, :long, :phone, :postal_code,
5
- :categories, :has_menu, :country, :lat, :id,
6
- :website_url, :resource_uri]
7
- # the Venue class encapsulates the venue_details also
8
- # keeping them separate for maintainability when they eventually change
9
- VENUE_DETAILS = [:open_hours, :facebook_url, :twitter_id,
10
- :similar_venues, :redirected_from]
11
- attr_reader :menus
12
- (PROPERTIES | VENUE_DETAILS).each {|prop| attr_accessor prop}
13
-
14
- def initialize(meta_venue)
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
- update(meta_venue)
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 update(meta_venue)
20
- meta_venue.each {|k,v| self.send("#{k.to_s}=",v) }
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
- # override some of the writers
24
- def menus=(meta_menus)
25
- # menus is an array of menu hashes
26
- # don't keep adding and duplicating if there are already menus
27
- @menus = []
28
- meta_menus.each do |h|
29
- @menus << Menu.new(h)
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
@@ -1,27 +1,45 @@
1
- require 'open-uri'
2
- require 'JSON'
3
-
4
1
  module Rlocu
5
- module VenueSearch
6
- PARAMETERS = [:name, :has_menu, :category, :cuisine, :website_url, :open_at, :street_address,
7
- :locality, :region, :postal_code, :country, :location, :radius, :bounds]
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
- private
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 self.url(params)
23
- "#{Rlocu.http_base}venue/search/?#{Rlocu.encode(params)}"
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
@@ -1,3 +1,3 @@
1
1
  module Rlocu
2
- VERSION = "0.1.0"
2
+ VERSION = "0.2.1"
3
3
  end
@@ -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 |gem|
7
- gem.name = "rlocu"
8
- gem.version = Rlocu::VERSION
9
- gem.authors = ["Stephen Philp"]
10
- gem.email = ["stephen@stephenphilp.com"]
11
- gem.description = %q{A simple ruby wrapper for the Locu API}
12
- gem.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
- gem.homepage = "https://github.com/swelltrain/rlocu"
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
- 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"]
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,5 @@
1
+ require 'yaml'
2
+
3
+ configs = YAML::load(File.open(File.expand_path('../../.config', __FILE__)))
4
+
5
+ Rlocu.configure { |rlocu| rlocu.api_key = configs['API_KEY'] }
@@ -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::Option, '#new' do
4
+ it 'should set the name and price' do
5
+ option = Menu::Option.new(menu_option)
6
+ expect(option.name).to eq(menu_option['name'])
7
+ expect(option.price).to eq(menu_option['price'])
8
+ end
9
+ 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
@@ -0,0 +1,9 @@
1
+ include Rlocu
2
+
3
+ RSpec.describe Menu, '#new' do
4
+ it 'should set menu name and sections' do
5
+ menu = Menu.new(menu_hash)
6
+ expect(menu.menu_name).to eq(menu_hash['menu_name'])
7
+ expect(menu.sections).not_to be_empty
8
+ end
9
+ end
@@ -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