rlocu 0.1.0 → 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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