kegbot_api 0.0.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.
- checksums.yaml +7 -0
- data/.gitignore +24 -0
- data/.rspec +2 -0
- data/.travis.yml +9 -0
- data/.yardopts +1 -0
- data/CHANGELOG.md +18 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +178 -0
- data/Rakefile +1 -0
- data/kegbot_api.gemspec +30 -0
- data/lib/kegbot_api.rb +7 -0
- data/lib/kegbot_api/client.rb +39 -0
- data/lib/kegbot_api/client_helpers.rb +33 -0
- data/lib/kegbot_api/errors.rb +17 -0
- data/lib/kegbot_api/nouns/beverage.rb +23 -0
- data/lib/kegbot_api/nouns/drink.rb +32 -0
- data/lib/kegbot_api/nouns/image.rb +22 -0
- data/lib/kegbot_api/nouns/keg.rb +45 -0
- data/lib/kegbot_api/nouns/keg_size.rb +21 -0
- data/lib/kegbot_api/nouns/remote_rest_noun.rb +49 -0
- data/lib/kegbot_api/nouns/rest_noun.rb +36 -0
- data/lib/kegbot_api/nouns/rest_noun_attributes.rb +119 -0
- data/lib/kegbot_api/nouns/session.rb +32 -0
- data/lib/kegbot_api/nouns/tap.rb +31 -0
- data/lib/kegbot_api/rest_response.rb +76 -0
- data/lib/kegbot_api/version.rb +3 -0
- data/spec/beverage_spec.rb +44 -0
- data/spec/drink_spec.rb +88 -0
- data/spec/drinks.http +5416 -0
- data/spec/drinks_614.http +68 -0
- data/spec/drinks_999999.http +18 -0
- data/spec/example_1.rb +40 -0
- data/spec/example_2.rb +28 -0
- data/spec/examples_spec.rb +37 -0
- data/spec/image_spec.rb +33 -0
- data/spec/keg_spec.rb +131 -0
- data/spec/kegs.http +144 -0
- data/spec/kegs_4.http +46 -0
- data/spec/kegs_999999.http +18 -0
- data/spec/session_spec.rb +73 -0
- data/spec/sessions.http +601 -0
- data/spec/sessions_65.http +23 -0
- data/spec/sessions_999999.http +18 -0
- data/spec/spec_helper.rb +34 -0
- data/spec/tap_spec.rb +134 -0
- data/spec/taps.http +96 -0
- metadata +209 -0
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
require 'kegbot_api/nouns/rest_noun'
|
|
2
|
+
|
|
3
|
+
module KegbotApi
|
|
4
|
+
class Image < RestNoun
|
|
5
|
+
id :id
|
|
6
|
+
time :time
|
|
7
|
+
url :url
|
|
8
|
+
url :thumbnail_url
|
|
9
|
+
|
|
10
|
+
def to_s
|
|
11
|
+
"#<#{self.class.to_s}:#{"0x%x" % object_id} @id=#{self.id.inspect}>"
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def self.to_s
|
|
15
|
+
self.client ? "KegbotApi::Image<#{self.client.base_url}>" : super
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def self.inspect
|
|
19
|
+
to_s
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
require 'kegbot_api/nouns/remote_rest_noun'
|
|
2
|
+
|
|
3
|
+
module KegbotApi
|
|
4
|
+
class Keg < RemoteRestNoun
|
|
5
|
+
extend Forwardable
|
|
6
|
+
|
|
7
|
+
id :id
|
|
8
|
+
float :percent_full
|
|
9
|
+
float :volume_ml_remain, :alias => :volume_ml_remaining
|
|
10
|
+
float :spilled_ml
|
|
11
|
+
time :start_time
|
|
12
|
+
time :end_time
|
|
13
|
+
boolean :online
|
|
14
|
+
has_one :beverage, :attribute_name => :type, :alias => :type
|
|
15
|
+
has_one :size, :class_name => 'KegSize'
|
|
16
|
+
|
|
17
|
+
def_delegators :beverage, :name
|
|
18
|
+
def_delegators :size, :volume_ml
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def size_name
|
|
22
|
+
self.size.name
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def self.all
|
|
26
|
+
list(get("#{self.client.base_url}/kegs"))
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def self.find(id, *args)
|
|
30
|
+
one(get("#{self.client.base_url}/kegs/#{id}"))
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def to_s
|
|
34
|
+
"#<#{self.class.to_s}:#{"0x%x" % object_id} @id=#{self.id.inspect}>"
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def self.to_s
|
|
38
|
+
self.client ? "KegbotApi::Keg<#{self.client.base_url}>" : super
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def self.inspect
|
|
42
|
+
to_s
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
require 'kegbot_api/nouns/rest_noun'
|
|
2
|
+
|
|
3
|
+
module KegbotApi
|
|
4
|
+
class KegSize < RestNoun
|
|
5
|
+
id :id
|
|
6
|
+
string :name
|
|
7
|
+
float :volume_ml
|
|
8
|
+
|
|
9
|
+
def to_s
|
|
10
|
+
"#<#{self.class.to_s}:#{"0x%x" % object_id} @id=#{self.id.inspect}>"
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def self.to_s
|
|
14
|
+
self.client ? "KegbotApi::KegSize<#{self.client.base_url}>" : super
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def self.inspect
|
|
18
|
+
to_s
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
require 'active_support/concern'
|
|
2
|
+
require 'kegbot_api/nouns/rest_noun'
|
|
3
|
+
|
|
4
|
+
module KegbotApi
|
|
5
|
+
# Rest noun that supports remote access via {#all} and {#find}
|
|
6
|
+
class RemoteRestNoun < RestNoun
|
|
7
|
+
|
|
8
|
+
#
|
|
9
|
+
# Class Methods
|
|
10
|
+
#
|
|
11
|
+
|
|
12
|
+
class << self
|
|
13
|
+
def list(response)
|
|
14
|
+
if response.success?
|
|
15
|
+
response.objects.map { |hash| self.new hash }
|
|
16
|
+
else
|
|
17
|
+
raise *response.raise_arguments
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def one(response)
|
|
22
|
+
if response.success?
|
|
23
|
+
self.new response.object
|
|
24
|
+
else
|
|
25
|
+
raise *response.raise_arguments
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def first
|
|
30
|
+
all.first
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def get(url, options = {})
|
|
34
|
+
uri = URI(url)
|
|
35
|
+
|
|
36
|
+
Net::HTTP.start(uri.host, uri.port, :use_ssl => (uri.scheme == 'https')) do |http|
|
|
37
|
+
http_response = http.request_get uri.request_uri
|
|
38
|
+
|
|
39
|
+
case http_response
|
|
40
|
+
when Net::HTTPRedirection
|
|
41
|
+
raise NotImplementedError, "HTTP redirecting isn't supported"
|
|
42
|
+
else
|
|
43
|
+
RestResponse.new http_response
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
require 'net/https'
|
|
2
|
+
require 'kegbot_api/rest_response'
|
|
3
|
+
require 'kegbot_api/nouns/rest_noun_attributes'
|
|
4
|
+
|
|
5
|
+
module KegbotApi
|
|
6
|
+
# Base class of all REST nouns (types) in the Kegbot REST API.
|
|
7
|
+
class RestNoun
|
|
8
|
+
include RestNounAttributes
|
|
9
|
+
|
|
10
|
+
def initialize(attributes = {})
|
|
11
|
+
self.attributes = attributes
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
# Returns a {Client} client configured for this class
|
|
15
|
+
# @return [Client]
|
|
16
|
+
def client
|
|
17
|
+
self.class.client
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# Class Methods
|
|
21
|
+
|
|
22
|
+
class << self
|
|
23
|
+
# @return [Client]
|
|
24
|
+
attr_accessor :client
|
|
25
|
+
attr_accessor :noun
|
|
26
|
+
|
|
27
|
+
def client=(client)
|
|
28
|
+
@client = client
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def client
|
|
32
|
+
@client
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
require 'active_support/concern'
|
|
2
|
+
|
|
3
|
+
module KegbotApi
|
|
4
|
+
# Provides methods for specifying, storing, and retriving REST noun attributes and relationships.
|
|
5
|
+
# @see RestNoun
|
|
6
|
+
module RestNounAttributes
|
|
7
|
+
extend ActiveSupport::Concern
|
|
8
|
+
|
|
9
|
+
included do
|
|
10
|
+
attr_accessor :attributes
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
module ClassMethods
|
|
14
|
+
# Defines an id field for this REST noun
|
|
15
|
+
def id(name, *args)
|
|
16
|
+
define_accessor name
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# Defines a {String} field for this REST noun
|
|
20
|
+
def string(name, *args)
|
|
21
|
+
define_accessor name
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# Defines a {Float} field for this REST noun
|
|
25
|
+
def float(name, *args)
|
|
26
|
+
define_accessor name, *args
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# Defines a {Fixnum} field for this REST noun
|
|
30
|
+
def integer(name, *args)
|
|
31
|
+
define_accessor name
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Defines a 1-1 relationship between this Noun and another.
|
|
35
|
+
# @overload has_one(name, options = {})
|
|
36
|
+
def has_one(name, *args)
|
|
37
|
+
options = args.slice!(0) || {}
|
|
38
|
+
class_name = options[:class_name] || name.capitalize
|
|
39
|
+
attribute_name = options[:attribute_name] || name
|
|
40
|
+
key = attribute_name.to_s
|
|
41
|
+
aliases = options[:alias]
|
|
42
|
+
|
|
43
|
+
define_method(name) do
|
|
44
|
+
if self.attributes.include? key
|
|
45
|
+
self.client.noun_class(class_name).new self.attributes[key]
|
|
46
|
+
else
|
|
47
|
+
nil
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
alias_method aliases.to_sym, name.to_sym if aliases
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# Defines a boolean field for this REST noun, available at name?
|
|
54
|
+
# @overload boolean(name, options = {})
|
|
55
|
+
def boolean(name, *args)
|
|
56
|
+
options = args.slice!(0) || {}
|
|
57
|
+
attribute_name = options[:attribute_name] || name
|
|
58
|
+
key = attribute_name.to_s
|
|
59
|
+
|
|
60
|
+
define_method("#{name}?") do
|
|
61
|
+
self.attributes[key]
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# Defines a Time field for this REST noun.
|
|
66
|
+
# The time must be defined as a string in the REST JSON in ISO8601 format
|
|
67
|
+
def time(name, *args)
|
|
68
|
+
define_method(name) do
|
|
69
|
+
if self.attributes.include? name.to_s
|
|
70
|
+
DateTime.iso8601(self.attributes[name.to_s]).to_time
|
|
71
|
+
else
|
|
72
|
+
nil
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# Defines a NOUN attribute of a URL
|
|
79
|
+
# In the REST JSON, the URL my be relative or absolute. If relative, it will get appended to the client's base_url host.
|
|
80
|
+
# @overload url(name, options = {})
|
|
81
|
+
def url(name, *args)
|
|
82
|
+
options = args.slice!(0) || {}
|
|
83
|
+
attribute_name = options[:attribute_name] || name
|
|
84
|
+
key = attribute_name.to_s
|
|
85
|
+
|
|
86
|
+
define_method("#{name}") do
|
|
87
|
+
path = self.attributes[key]
|
|
88
|
+
|
|
89
|
+
if path
|
|
90
|
+
uri = URI(path)
|
|
91
|
+
|
|
92
|
+
if uri.absolute?
|
|
93
|
+
uri.to_s
|
|
94
|
+
else
|
|
95
|
+
# merge this URI with the client path
|
|
96
|
+
base_uri = URI(self.client.base_url)
|
|
97
|
+
|
|
98
|
+
klass = base_uri.scheme == 'http' ? URI::HTTP : URI::HTTPS
|
|
99
|
+
klass.build(:host => base_uri.host, :path => path).to_s
|
|
100
|
+
end
|
|
101
|
+
else
|
|
102
|
+
nil
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
# Defines an attribute accessor with the specified name, returning the value of {#attribute} value with key +name+
|
|
108
|
+
def define_accessor(name, *args)
|
|
109
|
+
options = args.slice!(0) || {}
|
|
110
|
+
aliases = options[:alias]
|
|
111
|
+
|
|
112
|
+
define_method(name) do
|
|
113
|
+
self.attributes[name.to_s]
|
|
114
|
+
end
|
|
115
|
+
alias_method aliases.to_sym, name.to_sym if aliases
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
end
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
require 'kegbot_api/nouns/remote_rest_noun'
|
|
2
|
+
|
|
3
|
+
module KegbotApi
|
|
4
|
+
class Session < RemoteRestNoun
|
|
5
|
+
id :id
|
|
6
|
+
string :name
|
|
7
|
+
time :start_time
|
|
8
|
+
time :end_time
|
|
9
|
+
boolean :active, :attribute_name => 'is_active'
|
|
10
|
+
float :volume_ml
|
|
11
|
+
|
|
12
|
+
def self.all
|
|
13
|
+
list(get("#{self.client.base_url}/sessions"))
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def self.find(id, *args)
|
|
17
|
+
one(get("#{self.client.base_url}/sessions/#{id}"))
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def to_s
|
|
21
|
+
"#<#{self.class.to_s}:#{"0x%x" % object_id} @id=#{self.id.inspect}>"
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def self.to_s
|
|
25
|
+
self.client ? "KegbotApi::Session<#{self.client.base_url}>" : super
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def self.inspect
|
|
29
|
+
to_s
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
require 'kegbot_api/nouns/remote_rest_noun'
|
|
2
|
+
require 'kegbot_api/nouns/keg'
|
|
3
|
+
|
|
4
|
+
module KegbotApi
|
|
5
|
+
class Tap < RemoteRestNoun
|
|
6
|
+
extend Forwardable
|
|
7
|
+
|
|
8
|
+
id :id
|
|
9
|
+
string :name
|
|
10
|
+
string :meter_name
|
|
11
|
+
float :ml_per_tick
|
|
12
|
+
has_one :keg, :class_name => 'Keg', :attribute_name => 'current_keg', :alias => 'current_keg'
|
|
13
|
+
def_delegators :keg, :online?
|
|
14
|
+
|
|
15
|
+
def self.all
|
|
16
|
+
list(get("#{self.client.base_url}/taps"))
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def to_s
|
|
20
|
+
"#<#{self.class.to_s}:#{"0x%x" % object_id} @id=#{self.id.inspect}>"
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def self.to_s
|
|
24
|
+
self.client ? "KegbotApi::Tap<#{self.client.base_url}>" : super
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def self.inspect
|
|
28
|
+
to_s
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
require 'json'
|
|
2
|
+
|
|
3
|
+
module KegbotApi
|
|
4
|
+
# Encapsulates a REST response ({Net::HTTPResponse})
|
|
5
|
+
#
|
|
6
|
+
# @visibility private
|
|
7
|
+
class RestResponse
|
|
8
|
+
# HTTP status code returned
|
|
9
|
+
attr_accessor :status_code
|
|
10
|
+
|
|
11
|
+
# result (as a string)
|
|
12
|
+
attr_accessor :body
|
|
13
|
+
|
|
14
|
+
# result (parsed into a {Hash})
|
|
15
|
+
attr_accessor :body_hash
|
|
16
|
+
|
|
17
|
+
# @overload initialize(http_response)
|
|
18
|
+
# @param http_reponse [Net::HTTPResponse]
|
|
19
|
+
def initialize(*args)
|
|
20
|
+
http_response = args.slice!(0)
|
|
21
|
+
|
|
22
|
+
self.status_code = http_response.code.to_i
|
|
23
|
+
self.body = http_response.read_body
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def success?
|
|
27
|
+
self.status_code == 200 && self.meta_result == 'ok'
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Raises an instance of {KegbotApi::Error} appropriate to the error code returned in the response
|
|
31
|
+
def raise_arguments
|
|
32
|
+
error_name = self.error_code
|
|
33
|
+
klass = KegbotApi.const_defined?(error_name) ? KegbotApi.const_get(error_name) : KegbotApi::Error
|
|
34
|
+
|
|
35
|
+
[klass, self.error_message]
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Parses the http result body into a {Hash}
|
|
39
|
+
def body_hash
|
|
40
|
+
@body_hash ||= JSON::load(self.body)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def metadata
|
|
44
|
+
hash_attribute_or_fail(self.body_hash, 'meta')
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def meta_result
|
|
48
|
+
hash_attribute_or_fail(self.metadata, 'result')
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def error
|
|
52
|
+
hash_attribute_or_fail(self.body_hash, 'error')
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def error_code
|
|
56
|
+
hash_attribute_or_fail(self.error, 'code')
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def error_message
|
|
60
|
+
hash_attribute_or_fail(self.error, 'message')
|
|
61
|
+
self.error['message']
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def objects
|
|
65
|
+
hash_attribute_or_fail(self.body_hash, 'objects')
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def object
|
|
69
|
+
hash_attribute_or_fail(self.body_hash, 'object')
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def hash_attribute_or_fail(hash, attribute)
|
|
73
|
+
(found = hash[attribute]) ? found : raise(InvalidInputError, "Server JSON response didn't include the expected '#{attribute}' field. #{hash.inspect}")
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|