geotrigger 0.0.4 → 0.0.6
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/README.md +169 -0
- data/lib/ext/string.rb +6 -5
- data/lib/geotrigger.rb +31 -2
- data/lib/geotrigger/ago/session.rb +70 -2
- data/lib/geotrigger/application.rb +59 -0
- data/lib/geotrigger/device.rb +26 -0
- data/lib/geotrigger/model.rb +60 -0
- data/lib/geotrigger/session.rb +88 -2
- data/lib/geotrigger/tag.rb +37 -0
- data/lib/geotrigger/trigger.rb +36 -0
- data/lib/geotrigger/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b80b540534f9cbb0793eca389c041ac27eea153c
|
4
|
+
data.tar.gz: 1100a93307a68ab21ff168fad392c21a64191820
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b80200b1e85cdd48327cecfc7fc684523a9190466f299f76f0a471290417696eca695e24e41b9b4564a7c9d4255551c45ddd6472f6771bcabd8a6865dfd7306a
|
7
|
+
data.tar.gz: 8939b512abcb68f664115143aef14d35b2b9a35dabce8605d5fea0f77fe5a1f813fd667b2b03c43de1f687635829837fbd910f2606a7d79b25c6c1425b9f3f19
|
data/.gitignore
CHANGED
data/README.md
CHANGED
@@ -2,3 +2,172 @@ geotrigger-ruby
|
|
2
2
|
===============
|
3
3
|
|
4
4
|
a small ruby client for https://developers.arcgis.com/en/geotrigger-service/.
|
5
|
+
|
6
|
+
# Install
|
7
|
+
|
8
|
+
`gem install geotrigger`
|
9
|
+
|
10
|
+
# Usage
|
11
|
+
|
12
|
+
## Session
|
13
|
+
|
14
|
+
`Geotrigger::Session` is the main interface to the Geotrigger API. Once
|
15
|
+
created, it serves as the underlying support to all the Model subclasses.
|
16
|
+
Its main features are:
|
17
|
+
|
18
|
+
* wrapping communication with the Geotrigger API
|
19
|
+
* handling all `access_token` negotiation with ArcGIS Online
|
20
|
+
|
21
|
+
See also [API Doc](http://www.rubydoc.info/gems/geotrigger/Geotrigger/Session)
|
22
|
+
|
23
|
+
To create a `Session`, call `#new` with a optional Hash:
|
24
|
+
|
25
|
+
```ruby
|
26
|
+
# specify client_id and client_secret
|
27
|
+
#
|
28
|
+
session = Geotrigger::Session.new client_id: 'abcde', client_secret: '12345'
|
29
|
+
#=> <Geotrigger::Session ... >
|
30
|
+
|
31
|
+
# specify client_id and :device type
|
32
|
+
# (registers as a new device)
|
33
|
+
#
|
34
|
+
session = Geotrigger::Session.new client_id: 'abcde', type: :device
|
35
|
+
#=> <Geotrigger::Session ... >
|
36
|
+
|
37
|
+
# specify client_id, refresh_token, and :device type
|
38
|
+
# (gets access_tokens for an existing device)
|
39
|
+
#
|
40
|
+
session = Geotrigger::Session.new client_id: 'abcde', refresh_token: 'qwert', type: :device
|
41
|
+
#=> <Geotrigger::Session ... >
|
42
|
+
|
43
|
+
# reads config from ~/.geotrigger YAML
|
44
|
+
#
|
45
|
+
session = Geotrigger::Session.new
|
46
|
+
#=> <Geotrigger::Session ... >
|
47
|
+
|
48
|
+
# reads config from ~/.geotrigger YAML :dev key
|
49
|
+
#
|
50
|
+
session = Geotrigger::Session.new config: :dev
|
51
|
+
#=> <Geotrigger::Session ... >
|
52
|
+
```
|
53
|
+
|
54
|
+
You can then POST to any route in the API, without worring about managing
|
55
|
+
`access_token` lifecycles, with a normal Ruby `Hash` for both request
|
56
|
+
parameters and additional headers. The return value will be a normal Ruby
|
57
|
+
`Hash` parsed from the JSON response of the API. A `GeotriggerError` may be
|
58
|
+
raised if there was a problem with your request parameters or soemthing else.
|
59
|
+
|
60
|
+
```ruby
|
61
|
+
session.post 'trigger/list'
|
62
|
+
#=> { "triggers" => [] }
|
63
|
+
|
64
|
+
session.post 'device/update', deviceIds: ['abcd1234'], addTags: ['foo']
|
65
|
+
#=> { "devices" => [ { "deviceId" => "abcd1234", tags: ["foo", "device:abcd1234", ...] } ] }
|
66
|
+
|
67
|
+
begin
|
68
|
+
session.post 'device/update', bad_param: false
|
69
|
+
rescue Geotrigger::GeotriggerError => ge
|
70
|
+
|
71
|
+
ge.parameters
|
72
|
+
#=> {"bad_param"=>[{"type"=>"invalid", "message"=>"Not a valid parameter for this request."}]}
|
73
|
+
|
74
|
+
end
|
75
|
+
```
|
76
|
+
|
77
|
+
You can do all of what you need to through this interface, but there are some
|
78
|
+
more lightweight utility classes for those that prefer a more OO approach.
|
79
|
+
|
80
|
+
## Models
|
81
|
+
|
82
|
+
These classes provide a ORM-ish interface to the Geotrigger API objects
|
83
|
+
Application, Trigger, Tag, and Device.
|
84
|
+
|
85
|
+
### Application
|
86
|
+
|
87
|
+
Application objects offer top-level access to various other model objects,
|
88
|
+
as well as updating of [Application](https://developers.arcgis.com/geotrigger-service/api-reference/application/) specific settings.
|
89
|
+
|
90
|
+
See also [API Doc](http://www.rubydoc.info/gems/geotrigger/Geotrigger/Application)
|
91
|
+
|
92
|
+
```
|
93
|
+
a = Geotrigger::Application.new client_id: 'abcde', client_secret: '12345'
|
94
|
+
#=> <Geotrigger::Application ... >
|
95
|
+
|
96
|
+
d = a.devices(tags: ['foo']).first
|
97
|
+
#=> <Geotrigger::Device ... >
|
98
|
+
|
99
|
+
geojson = JSON.parse File.load 'something.geojson'
|
100
|
+
t = a.triggers(geo: {geojson: geosjon}).first
|
101
|
+
#=> <Geotrigger::Trigger ... >
|
102
|
+
|
103
|
+
tag = a.tags.first
|
104
|
+
#=> <Geotrigger::Tag ... >
|
105
|
+
```
|
106
|
+
|
107
|
+
### Trigger
|
108
|
+
|
109
|
+
Trigger objects offer access to all attributes of a [Trigger](https://developers.arcgis.com/geotrigger-service/api-reference/trigger/).
|
110
|
+
|
111
|
+
See also [API Doc](http://www.rubydoc.info/gems/geotrigger/Geotrigger/Trigger)
|
112
|
+
|
113
|
+
```ruby
|
114
|
+
trigger.add_tags 'foo'
|
115
|
+
trigger.save
|
116
|
+
|
117
|
+
trigger.remove_tags 'bar'
|
118
|
+
trigger.properties = { foo: 'bar', baz: true, bat: 123 }
|
119
|
+
trigger.save
|
120
|
+
```
|
121
|
+
|
122
|
+
### Device
|
123
|
+
|
124
|
+
Device objects offer access to all attributes of a [Device](https://developers.arcgis.com/geotrigger-service/api-reference/device/).
|
125
|
+
|
126
|
+
See also [API Doc](http://www.rubydoc.info/gems/geotrigger/Geotrigger/Device)
|
127
|
+
|
128
|
+
```ruby
|
129
|
+
device.add_tags 'foo'
|
130
|
+
device.save
|
131
|
+
|
132
|
+
device.remove_tags 'bar'
|
133
|
+
device.properties = { foo: 'bar', baz: true, bat: 123 }
|
134
|
+
device.save
|
135
|
+
|
136
|
+
device.session.post 'location/update', locations: [{
|
137
|
+
longitude: -122,
|
138
|
+
latitude: 45,
|
139
|
+
accuracy: 10,
|
140
|
+
timestamp: DateTime.now.iso8601,
|
141
|
+
trackingProfile: 'adaptive'
|
142
|
+
}]
|
143
|
+
```
|
144
|
+
|
145
|
+
### Tag
|
146
|
+
|
147
|
+
Tag objects offer access to all attributes of a [Tag](https://developers.arcgis.com/geotrigger-service/api-reference/tag/).
|
148
|
+
|
149
|
+
See also [API Doc](http://www.rubydoc.info/gems/geotrigger/Geotrigger/Tag)
|
150
|
+
|
151
|
+
```ruby
|
152
|
+
# create a new tag without applying it to any other objects
|
153
|
+
#
|
154
|
+
s = Geotrigger::Session.new
|
155
|
+
tag = Geotrigger::Tag.create s, name: 'foo', deviceTagging: false
|
156
|
+
#=> <Geotrigger::Tag ... >
|
157
|
+
```
|
158
|
+
|
159
|
+
Note that Tags are automatically created by the API, if needed, when added to a
|
160
|
+
Trigger or Device. This offers a way to create the Tag before applying it to
|
161
|
+
anything.
|
162
|
+
|
163
|
+
```ruby
|
164
|
+
a = Geotrigger::Application.new client_id: 'abcde', client_secret: '12345'
|
165
|
+
#=> <Geotrigger::Application ... >
|
166
|
+
|
167
|
+
tag = a.tags(tags: 'foo').first
|
168
|
+
#=> <Geotrigger::Tag ... >
|
169
|
+
|
170
|
+
tag.device_tagging = false
|
171
|
+
tag.trigger_list = false
|
172
|
+
tag.save
|
173
|
+
```
|
data/lib/ext/string.rb
CHANGED
@@ -1,10 +1,11 @@
|
|
1
|
-
|
1
|
+
# basic string extensions
|
2
|
+
#
|
3
|
+
# these are pretty much ripped from:
|
4
|
+
#
|
5
|
+
# https://github.com/rails/rails/blob/master/activesupport/lib/active_support/inflector/methods.rb
|
2
6
|
|
3
|
-
|
7
|
+
class String
|
4
8
|
|
5
|
-
# these are pretty much ripped from:
|
6
|
-
# https://github.com/rails/rails/blob/master/activesupport/lib/active_support/inflector/methods.rb
|
7
|
-
#
|
8
9
|
def camelcase
|
9
10
|
string = self.sub(/^(?:(?=\b|[A-Z_])|\w)/) { $&.downcase }
|
10
11
|
string.gsub!(/(?:_|(\/))([a-z\d]*)/i) { "#{$1}#{$2.capitalize}" }
|
data/lib/geotrigger.rb
CHANGED
@@ -1,12 +1,39 @@
|
|
1
|
+
# Geotrigger - A small Ruby client for the Esri Geotrigger Service.
|
2
|
+
# Copyright 2014 Esri; Nakamura, Kenichi (knakamura@esri.com)
|
3
|
+
#
|
4
|
+
# https://developers.arcgis.com/geotrigger-service/
|
5
|
+
# http://www.esri.com/
|
6
|
+
#
|
7
|
+
# [author] Kenichi Nakamura (knakamura@esri.com)
|
8
|
+
# [license] http://www.apache.org/licenses/LICENSE-2.0.txt
|
9
|
+
|
1
10
|
require 'forwardable'
|
2
|
-
require 'httpclient'
|
11
|
+
require 'httpclient'
|
3
12
|
require 'json'
|
4
13
|
|
14
|
+
# HTTPClient's normal #inspect is quite large, ungainly in irb sessions
|
15
|
+
#
|
16
|
+
if $0 == 'irb'
|
17
|
+
class HTTPClient
|
18
|
+
alias_method :real_inspect, :inspect
|
19
|
+
def inspect; self.to_s; end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
# The Geotrigger module is the main namespace for all things in this library.
|
24
|
+
#
|
5
25
|
module Geotrigger
|
26
|
+
|
27
|
+
# raised by AGOSession on error from ArcGIS Online API
|
28
|
+
#
|
6
29
|
class AGOError < StandardError; end
|
30
|
+
|
31
|
+
# raised by Session on error from Geotrigger API
|
32
|
+
#
|
7
33
|
class GeotriggerError < StandardError
|
8
|
-
attr_accessor :code, :headers, :message, :
|
34
|
+
attr_accessor :code, :headers, :message, :parameters
|
9
35
|
end
|
36
|
+
|
10
37
|
end
|
11
38
|
|
12
39
|
lib = File.expand_path '../..', __FILE__
|
@@ -21,3 +48,5 @@ require 'geotrigger/application'
|
|
21
48
|
require 'geotrigger/device'
|
22
49
|
require 'geotrigger/tag'
|
23
50
|
require 'geotrigger/trigger'
|
51
|
+
|
52
|
+
require 'geotrigger/version'
|
@@ -1,13 +1,46 @@
|
|
1
1
|
module Geotrigger
|
2
|
+
|
3
|
+
# namespace for interacting with the ArcGIS Online API.
|
4
|
+
#
|
2
5
|
module AGO
|
6
|
+
|
7
|
+
# AGO::Session is responsible for talking to the ArcGIS Online API.
|
8
|
+
#
|
9
|
+
# * retrieves a valid OAuth +access_token+, handling expiration
|
10
|
+
# * Application, via +client_credentials+
|
11
|
+
# * Device, via registration or +refresh_token+
|
12
|
+
# * registers a new Device given a +client_id+
|
13
|
+
#
|
14
|
+
# Generally, one interacts with only the +Session+, which retains an
|
15
|
+
# instance of this to deal with tokens.
|
16
|
+
#
|
3
17
|
class Session
|
18
|
+
|
4
19
|
extend ::Forwardable
|
5
20
|
def_delegators :@impl, :access_token, :access_token=, :ago_data, :device_data, :refresh_token
|
6
21
|
|
22
|
+
# Read the base URL for ArcGIS Online API from the environment,
|
23
|
+
# or use the default.
|
24
|
+
#
|
25
|
+
# If you have a different OAuth portal, you can:
|
26
|
+
#
|
27
|
+
# $ AGO_BASE_URL=http://example.com/path/ irb -rgeotrigger
|
28
|
+
#
|
29
|
+
# to have the client use that base URL.
|
30
|
+
#
|
7
31
|
AGO_BASE_URL = (ENV.key?('AGO_BASE_URL') ?
|
8
32
|
(ENV['AGO_BASE_URL'] + '%s') :
|
9
33
|
'https://www.arcgis.com/sharing/%s').freeze
|
10
34
|
|
35
|
+
# Determines underlying implementation type, and creates an HTTPClient
|
36
|
+
# instance.
|
37
|
+
#
|
38
|
+
# [opts] +Hash+ options for construction:
|
39
|
+
# [:client_id] +String+ OAuth client id
|
40
|
+
# [:client_secret] +String+ OAuth client secret
|
41
|
+
# [:refresh_token] +String+ OAuth refresh token
|
42
|
+
# [:type] +Symbol+ +:application+ (default) or +:device+
|
43
|
+
#
|
11
44
|
def initialize opts = {}
|
12
45
|
@hc = HTTPClient.new
|
13
46
|
@impl = case opts[:type] || :application
|
@@ -20,6 +53,8 @@ module Geotrigger
|
|
20
53
|
end
|
21
54
|
end
|
22
55
|
|
56
|
+
# Type of implementation as a symbol.
|
57
|
+
#
|
23
58
|
def type
|
24
59
|
case @impl
|
25
60
|
when Application
|
@@ -29,8 +64,12 @@ module Geotrigger
|
|
29
64
|
end
|
30
65
|
end
|
31
66
|
|
32
|
-
#
|
33
|
-
#
|
67
|
+
# HTTP the specified method to the specified path with the given params.
|
68
|
+
# JSON parse the response body or raise errors.
|
69
|
+
#
|
70
|
+
# [meth] +Symbol+ of the method to HTTP (:get,:post...)
|
71
|
+
# [path] +String+ path of the request ('/example/index.html')
|
72
|
+
# [params] +Hash+ parameters for the request
|
34
73
|
#
|
35
74
|
def hc meth, path, params
|
36
75
|
r = @hc.__send__ meth, AGO_BASE_URL % path, params.merge(f: 'json')
|
@@ -40,10 +79,17 @@ module Geotrigger
|
|
40
79
|
h
|
41
80
|
end
|
42
81
|
|
82
|
+
# Mixin for the AGO::Session underlying implementation.
|
83
|
+
#
|
43
84
|
module ExpirySet
|
44
85
|
|
86
|
+
# Number of seconds before expiration to refresh tokens.
|
87
|
+
#
|
45
88
|
TOKEN_EXPIRY_BUFFER = 10
|
46
89
|
|
90
|
+
# Sets a buffered +:expires_at+ for refreshing tokens. Generates this
|
91
|
+
# value from +Time.now+ and the supplied +expires_in+ value (seconds).
|
92
|
+
#
|
47
93
|
def wrap_token_retrieval &block
|
48
94
|
yield
|
49
95
|
expires_at = Time.now.to_i + @ago_data['expires_in']
|
@@ -53,6 +99,8 @@ module Geotrigger
|
|
53
99
|
|
54
100
|
end
|
55
101
|
|
102
|
+
# AGO::Session implementation for Applications
|
103
|
+
#
|
56
104
|
class Application
|
57
105
|
include ExpirySet
|
58
106
|
extend ::Forwardable
|
@@ -60,11 +108,15 @@ module Geotrigger
|
|
60
108
|
|
61
109
|
attr_reader :ago_data
|
62
110
|
|
111
|
+
# Accepts the abstract +AGO::Session+ and a +client_credentials+ Hash.
|
112
|
+
#
|
63
113
|
def initialize session, opts = {}
|
64
114
|
@session, @client_id, @client_secret =
|
65
115
|
session, opts[:client_id], opts[:client_secret]
|
66
116
|
end
|
67
117
|
|
118
|
+
# Returns a valid +access_token+. Gets a new one if +nil+ or expired.
|
119
|
+
#
|
68
120
|
def access_token
|
69
121
|
fetch_access_token if @ago_data.nil? or
|
70
122
|
(not @ago_data[:expires_at].nil? and
|
@@ -74,6 +126,8 @@ module Geotrigger
|
|
74
126
|
|
75
127
|
private
|
76
128
|
|
129
|
+
# Gets a new +access_token+.
|
130
|
+
#
|
77
131
|
def fetch_access_token
|
78
132
|
wrap_token_retrieval do
|
79
133
|
@ago_data = hc :post, 'oauth2/token',
|
@@ -85,6 +139,8 @@ module Geotrigger
|
|
85
139
|
|
86
140
|
end
|
87
141
|
|
142
|
+
# AGO::Session implementation for Devices
|
143
|
+
#
|
88
144
|
class Device
|
89
145
|
include ExpirySet
|
90
146
|
extend Forwardable
|
@@ -93,11 +149,17 @@ module Geotrigger
|
|
93
149
|
attr_accessor :refresh_token
|
94
150
|
attr_reader :ago_data
|
95
151
|
|
152
|
+
# Accepts the abstract +AGO::Session+ and a Hash with +:client_id+
|
153
|
+
# and +:refresh_token+ keys.
|
154
|
+
#
|
96
155
|
def initialize session, opts = {}
|
97
156
|
@session, @client_id, @refresh_token =
|
98
157
|
session, opts[:client_id], opts[:refresh_token]
|
99
158
|
end
|
100
159
|
|
160
|
+
# Returns a valid +access_token+. Registers a new Device with AGO if
|
161
|
+
# needed.
|
162
|
+
#
|
101
163
|
def access_token
|
102
164
|
if @ago_data.nil?
|
103
165
|
if @refresh_token.nil?
|
@@ -111,12 +173,16 @@ module Geotrigger
|
|
111
173
|
@ago_data['access_token']
|
112
174
|
end
|
113
175
|
|
176
|
+
# Fetches data from AGO about the device specified by this :access_token+.
|
177
|
+
#
|
114
178
|
def device_data
|
115
179
|
@device_data ||= hc(:get, 'portals/self', token: access_token)['deviceInfo']
|
116
180
|
end
|
117
181
|
|
118
182
|
private
|
119
183
|
|
184
|
+
# Registers as a new Device with AGO.
|
185
|
+
#
|
120
186
|
def register
|
121
187
|
wrap_token_retrieval do
|
122
188
|
data = hc :post, 'oauth2/registerDevice', client_id: @client_id, expiration: -1
|
@@ -129,6 +195,8 @@ module Geotrigger
|
|
129
195
|
end
|
130
196
|
end
|
131
197
|
|
198
|
+
# Gets a new +access_token+.
|
199
|
+
#
|
132
200
|
def refresh_access_token
|
133
201
|
wrap_token_retrieval do
|
134
202
|
@ago_data = hc :post, 'oauth2/token',
|
@@ -1,23 +1,82 @@
|
|
1
1
|
module Geotrigger
|
2
2
|
|
3
|
+
# Application objects offer top-level, ORM-ish access to various other model
|
4
|
+
# objects, as well as updating of application specific settings.
|
5
|
+
#
|
6
|
+
# a = Geotrigger::Application.new client_id: 'abcde', client_secret: '12345'
|
7
|
+
# #=> <Geotrigger::Application ...>
|
8
|
+
#
|
9
|
+
# # or
|
10
|
+
#
|
11
|
+
# s = Geotrigger::Session.new client_id: 'abcde', client_secret: '12345'
|
12
|
+
# a = Geotrigger::Application.new session: s
|
13
|
+
# #=> <Geotrigger::Application ...>
|
14
|
+
#
|
3
15
|
class Application < Model
|
4
16
|
|
17
|
+
# Return this application's default tag permissions as a +Hash+.
|
18
|
+
#
|
19
|
+
# a.permissions
|
20
|
+
# #=> {"deviceTagging"=>true, "deviceLocation"=>false, ... }
|
21
|
+
#
|
5
22
|
def permissions
|
6
23
|
post 'application/permissions'
|
7
24
|
end
|
8
25
|
|
26
|
+
# Update this application's default tag permissions with a +Hash+.
|
27
|
+
#
|
28
|
+
# a.permissions = { deviceTagging: false }
|
29
|
+
# #=> {:deviceTagging => false }
|
30
|
+
#
|
31
|
+
# a.permissions
|
32
|
+
# #=> {"deviceTagging"=>false, "deviceLocation"=>false, ... }
|
33
|
+
#
|
9
34
|
def permissions= perms
|
10
35
|
post 'application/permissions/update', perms
|
11
36
|
end
|
12
37
|
|
38
|
+
# Return an +Array+ of +Device+ model objects that belong to this
|
39
|
+
# application.
|
40
|
+
#
|
41
|
+
# [params] +Hash+ optional parameters to send with the request
|
42
|
+
#
|
43
|
+
# a.devices tags: 'foo'
|
44
|
+
# #=> [<Geotrigger::Device ...>, ...] # (devices that have the tag 'foo' on them)
|
45
|
+
#
|
46
|
+
# a.devices geo: { geojson: { type: "Feature", properties: nil, geometry: {
|
47
|
+
# type:"Polygon",coordinates:[[[-122.669085113593,45.4999973537201], ... ,[-122.669085113593,45.4999973537201]]]
|
48
|
+
# }}}
|
49
|
+
# #=> [<Geotrigger::Device ...>, ...] # (devices whose last location was inside the given polygon)
|
50
|
+
#
|
13
51
|
def devices params = {}
|
14
52
|
post_list 'devices', params
|
15
53
|
end
|
16
54
|
|
55
|
+
# Return an +Array+ of +Tag+ model objects that belong to this
|
56
|
+
# application.
|
57
|
+
#
|
58
|
+
# [params] +Hash+ optional parameters to send with the request
|
59
|
+
#
|
60
|
+
# a.tags
|
61
|
+
# #=> [<Geotrigger::Tag...>, ...]
|
62
|
+
#
|
17
63
|
def tags params = {}
|
18
64
|
post_list 'tags', params
|
19
65
|
end
|
20
66
|
|
67
|
+
# Return an +Array+ of +Trigger+ model objects that belong to this
|
68
|
+
# application.
|
69
|
+
#
|
70
|
+
# [params] +Hash+ optional parameters to send with the request
|
71
|
+
#
|
72
|
+
# a.triggers tags: 'foo'
|
73
|
+
# #=> [<Geotrigger::Trigger ...>, ...] # (triggers that have the tag 'foo' on them)
|
74
|
+
#
|
75
|
+
# a.triggers geo: { geojson: { type: "Feature", properties: nil, geometry: {
|
76
|
+
# type:"Polygon",coordinates:[[[-122.669085113593,45.4999973537201], ... ,[-122.669085113593,45.4999973537201]]]
|
77
|
+
# }}}
|
78
|
+
# #=> [<Geotrigger::Trigger ...>, ...] # (triggers whose condition polygon is inside the given polygon)
|
79
|
+
#
|
21
80
|
def triggers params = {}
|
22
81
|
post_list 'triggers', params
|
23
82
|
end
|
data/lib/geotrigger/device.rb
CHANGED
@@ -1,8 +1,23 @@
|
|
1
1
|
module Geotrigger
|
2
2
|
|
3
|
+
# +Device+ objects offer ORM-ish access to all attributes of a Device.
|
4
|
+
#
|
5
|
+
# device.add_tags 'foo'
|
6
|
+
# device.save
|
7
|
+
#
|
8
|
+
# device.remove_tags 'bar'
|
9
|
+
# device.properties = { foo: 'bar', baz: true, bat: 123 }
|
10
|
+
# device.save
|
11
|
+
#
|
3
12
|
class Device < Model
|
4
13
|
include Taggable
|
5
14
|
|
15
|
+
# Create a new +Device+ instance and load +@data+ from the API given a
|
16
|
+
# +Hash+ with options:
|
17
|
+
#
|
18
|
+
# [device_id] +String+ id of the device
|
19
|
+
# [tags] +Array+ name(s) of tag(s) to filter devices by
|
20
|
+
#
|
6
21
|
def initialize opts = {}
|
7
22
|
super opts
|
8
23
|
case session.type
|
@@ -17,10 +32,15 @@ module Geotrigger
|
|
17
32
|
end
|
18
33
|
end
|
19
34
|
|
35
|
+
# Return the +String+ of this device's default tag.
|
36
|
+
#
|
20
37
|
def default_tag
|
21
38
|
'device:%s' % deviceId
|
22
39
|
end
|
23
40
|
|
41
|
+
# POST the device's +@data+ to the API via 'device/update', and return
|
42
|
+
# the same object with the new +@data+ returned from API call.
|
43
|
+
#
|
24
44
|
def post_update
|
25
45
|
post_data = @data.dup
|
26
46
|
case @session.type
|
@@ -37,6 +57,12 @@ module Geotrigger
|
|
37
57
|
end
|
38
58
|
alias_method :save, :post_update
|
39
59
|
|
60
|
+
# Reads the data specific to this +Device+ from the API response and sets
|
61
|
+
# it in +@data+.
|
62
|
+
#
|
63
|
+
# [data] +Hash+ the API response
|
64
|
+
# [id] +String+ the id of the Device to pull out (first if nil)
|
65
|
+
#
|
40
66
|
def grok_self_from data, id = nil
|
41
67
|
if id == :first
|
42
68
|
@data = data['devices'].first
|
data/lib/geotrigger/model.rb
CHANGED
@@ -1,5 +1,11 @@
|
|
1
1
|
module Geotrigger
|
2
2
|
|
3
|
+
# Superclass for Geotrigger "objects" - +Application+, +Trigger+, +Device+,
|
4
|
+
# +Tag+.
|
5
|
+
#
|
6
|
+
# Contains the base logic for interacting with the Geotrigger API in an
|
7
|
+
# ORM-like fashion. Never instantiated directly by the user.
|
8
|
+
#
|
3
9
|
class Model
|
4
10
|
|
5
11
|
class StateError < StandardError; end
|
@@ -7,19 +13,39 @@ module Geotrigger
|
|
7
13
|
extend Forwardable
|
8
14
|
def_delegator :@session, :post
|
9
15
|
|
16
|
+
# The data behind the model object.
|
17
|
+
#
|
10
18
|
attr_accessor :data
|
19
|
+
|
20
|
+
# The +Session+ the model object uses to talk to the API.
|
21
|
+
#
|
11
22
|
attr_reader :session
|
12
23
|
|
24
|
+
# Create an instance of the subclassed model object from data retrieved
|
25
|
+
# from the API.
|
26
|
+
#
|
13
27
|
def self.from_api data, session
|
14
28
|
i = self.new session: session
|
15
29
|
i.data = data
|
16
30
|
return i
|
17
31
|
end
|
18
32
|
|
33
|
+
# Create an instance and from given options +Hash+.
|
34
|
+
#
|
35
|
+
# [:session] +Session+ underlying session to use when talking to the API
|
36
|
+
#
|
19
37
|
def initialize opts = {}
|
20
38
|
@session = opts[:session] || Session.new(opts)
|
21
39
|
end
|
22
40
|
|
41
|
+
# POST a request to this model's /list route, passing parameters. Returns
|
42
|
+
# a new instance of the model object with populated data via
|
43
|
+
# +Model.from_api+.
|
44
|
+
#
|
45
|
+
# [models] +String+ name of the model to request listed data for
|
46
|
+
# [params] +Hash+ parameters to send with the request
|
47
|
+
# [default_params] +Hash+ default parameters to merge +params+ into
|
48
|
+
#
|
23
49
|
def post_list models, params = {}, default_params = {}
|
24
50
|
model = models.sub /s$/, ''
|
25
51
|
params = default_params.merge params
|
@@ -28,6 +54,15 @@ module Geotrigger
|
|
28
54
|
end
|
29
55
|
end
|
30
56
|
|
57
|
+
# Allows snake_case accessor to top-level data values keyed by their
|
58
|
+
# camelCase counterparts. An attempt to be moar Rubyish.
|
59
|
+
#
|
60
|
+
# device.tracking_profile
|
61
|
+
# #=> 'adaptive'
|
62
|
+
#
|
63
|
+
# device.trackingProfile
|
64
|
+
# #=> 'adaptive'
|
65
|
+
#
|
31
66
|
def method_missing meth, *args
|
32
67
|
meth_s = meth.to_s
|
33
68
|
if meth_s =~ /=$/ and args.length == 1
|
@@ -47,6 +82,8 @@ module Geotrigger
|
|
47
82
|
end
|
48
83
|
end
|
49
84
|
|
85
|
+
# Compares underlying data for equality.
|
86
|
+
#
|
50
87
|
def == obj
|
51
88
|
if Model === obj
|
52
89
|
self.data == obj.data
|
@@ -55,22 +92,45 @@ module Geotrigger
|
|
55
92
|
end
|
56
93
|
end
|
57
94
|
|
95
|
+
# Mixin for +Trigger+ and +Device+ to add tag functionality.
|
96
|
+
#
|
58
97
|
module Taggable
|
59
98
|
|
99
|
+
# Returns this model's tags as an +Array+ of +Tag+ objects.
|
100
|
+
#
|
60
101
|
def tags params = {}
|
61
102
|
post_list 'tags', params, tags: @data['tags']
|
62
103
|
end
|
63
104
|
|
105
|
+
# Sets 'addTags' in this model's +@data+ for POSTing to the API via
|
106
|
+
# +Model#save+.
|
107
|
+
#
|
108
|
+
# device.add_tags 'foo', 'bar'
|
109
|
+
# device.save
|
110
|
+
#
|
64
111
|
def add_tags *names
|
65
112
|
@data['addTags'] = names.flatten
|
66
113
|
end
|
67
114
|
|
115
|
+
# Sets 'removeTags' in this model's +@data+ for POSTing to the API via
|
116
|
+
# +Model#save+.
|
117
|
+
#
|
118
|
+
# trigger.remove_tags 'foo', 'bar'
|
119
|
+
# trigger.save
|
120
|
+
#
|
68
121
|
def remove_tags *names
|
69
122
|
names = names.flatten
|
70
123
|
raise ArgumentError.new "default tag prohibited" if names.include? default_tag
|
71
124
|
@data['removeTags'] = names
|
72
125
|
end
|
73
126
|
|
127
|
+
|
128
|
+
# Sets 'setTags' in this model's +@data+ for POSTing to the API via
|
129
|
+
# +Model#save+.
|
130
|
+
#
|
131
|
+
# trigger.tags = ['foo', 'bar']
|
132
|
+
# trigger.save
|
133
|
+
#
|
74
134
|
def tags= *names
|
75
135
|
names = names.flatten
|
76
136
|
raise ArgumentError.new "default tag required" unless names.include? default_tag
|
data/lib/geotrigger/session.rb
CHANGED
@@ -1,9 +1,67 @@
|
|
1
|
+
# Geotrigger - A small Ruby client for the Esri Geotrigger Service.
|
2
|
+
# Copyright 2014 Esri; Nakamura, Kenichi (knakamura@esri.com)
|
3
|
+
#
|
4
|
+
# https://developers.arcgis.com/geotrigger-service/
|
5
|
+
# http://www.esri.com/
|
6
|
+
#
|
7
|
+
# [author] Kenichi Nakamura (knakamura@esri.com)
|
8
|
+
# [license] http://www.apache.org/licenses/LICENSE-2.0.txt
|
9
|
+
|
1
10
|
module Geotrigger
|
2
11
|
|
12
|
+
# +Session+ is the main interface to the Geotrigger API.
|
13
|
+
#
|
14
|
+
# Instances of it POST to the API and return the values using normal Ruby
|
15
|
+
# Hashes. Used by objects subclassed from +Model+.
|
16
|
+
#
|
17
|
+
# Example:
|
18
|
+
#
|
19
|
+
# session = Geotrigger::Session.new client_id: 'abcde', client_secret: '12345'
|
20
|
+
# session.post 'trigger/list'
|
21
|
+
# #=> { "triggers" => [ ... ] }
|
22
|
+
#
|
23
|
+
# device_session = Geotrigger::Session.new client_id: 'abcde', type: :device
|
24
|
+
# device_session.post 'device/update', addTags: ['foo']
|
25
|
+
# #=> { "devices" => [{ "deviceId" => '0987qwer', tags: ["device:0987qwer", "foo"], ... }] }
|
26
|
+
#
|
27
|
+
# device_session = Geotrigger::Session.new client_id: 'abcde', refresh_token: 'zxcvb', type: :device
|
28
|
+
# device_session.post 'device/list'
|
29
|
+
# #=> { "devices" => [{ "deviceId" => '1234zxcv', tags: ["device:1234zxcv"], ... }] }
|
30
|
+
#
|
31
|
+
# It can also read default values for the constructor options from
|
32
|
+
# +ENV['HOME']/.geotrigger+, which is YAML formatted like:
|
33
|
+
#
|
34
|
+
# :production:
|
35
|
+
# :client_id: abcde
|
36
|
+
# :client_secret: 12345
|
37
|
+
# :test:
|
38
|
+
# :client_id: qwert
|
39
|
+
# :client_secret: 67890
|
40
|
+
# :test_device:
|
41
|
+
# :client_id: qwert
|
42
|
+
# :refresh_token: 45678lmnop
|
43
|
+
# :type: :device
|
44
|
+
#
|
45
|
+
# It will load the first Hash it finds with value for key +:client_id+, or
|
46
|
+
# one specified with the +:config+ option like:
|
47
|
+
#
|
48
|
+
# # default to :production in the example
|
49
|
+
# s = Geotrigger::Session.new
|
50
|
+
#
|
51
|
+
# # specify the :test_device config
|
52
|
+
# s = Geotrigger::Session.new config: :test_device
|
53
|
+
#
|
3
54
|
class Session
|
4
55
|
extend Forwardable
|
5
56
|
def_delegator :@ago, :type
|
6
57
|
|
58
|
+
# Read the base URL for Geotrigger API from the environment, or use the
|
59
|
+
# default.
|
60
|
+
#
|
61
|
+
# $ GT_BASE_URL=http://example.com/path/ irb -rgeotrigger
|
62
|
+
#
|
63
|
+
# to have the client use that base URL.
|
64
|
+
#
|
7
65
|
BASE_URL = (ENV.key?('GT_BASE_URL') ?
|
8
66
|
(ENV['GT_BASE_URL'] + '%s') :
|
9
67
|
'https://geotrigger.arcgis.com/%s').freeze
|
@@ -12,6 +70,14 @@ module Geotrigger
|
|
12
70
|
|
13
71
|
attr_writer :access_token
|
14
72
|
|
73
|
+
# Creates a Geotrigger::Session instance. Valid key/values for +opts+ are:
|
74
|
+
#
|
75
|
+
# [:client_id] +String+ OAuth client id
|
76
|
+
# [:client_secret] +String+ OAuth client secret
|
77
|
+
# [:refresh_token] +String+ OAuth refresh token (used for +:device+ type)
|
78
|
+
# [:type] +Symbol+ +:application+ (default) or +:device+
|
79
|
+
# [:config] +Symbol+ key of options defaults in ~/.geotrigger
|
80
|
+
#
|
15
81
|
def initialize opts = {}
|
16
82
|
if opts[:config] or opts.empty?
|
17
83
|
if File.exist? USER_CONFIG
|
@@ -31,17 +97,31 @@ module Geotrigger
|
|
31
97
|
@hc = HTTPClient.new
|
32
98
|
end
|
33
99
|
|
100
|
+
# Returns a valid +access_token+. Gets a new one if +nil+ or expired.
|
101
|
+
#
|
34
102
|
def access_token
|
35
103
|
@access_token || @ago.access_token
|
36
104
|
end
|
37
105
|
|
106
|
+
# Returns default request headers optionally merged with specified others.
|
107
|
+
#
|
108
|
+
# [others] +Hash+ other headers to include
|
109
|
+
#
|
38
110
|
def headers others = {}
|
39
111
|
{
|
40
112
|
'Content-Type' => 'application/json',
|
41
|
-
'Authorization' => "Bearer #{access_token}"
|
113
|
+
'Authorization' => "Bearer #{access_token}",
|
114
|
+
'X-GT-Client-Name' => 'geotrigger-ruby',
|
115
|
+
'X-GT-Client-Version' => Geotrigger::VERSION
|
42
116
|
}.merge others
|
43
117
|
end
|
44
118
|
|
119
|
+
# POST an API request to the given path, with optional params and
|
120
|
+
# headers. Returns a normal Ruby +Hash+ of the response data.
|
121
|
+
#
|
122
|
+
# [params] +Hash+ parameters to include in the request (will be converted to JSON)
|
123
|
+
# [other_headers] +Hash+ headers to include in the request in addition to the defaults.
|
124
|
+
#
|
45
125
|
def post path, params = {}, other_headers = {}
|
46
126
|
r = @hc.post BASE_URL % path, params.to_json, headers(other_headers)
|
47
127
|
raise GeotriggerError.new r.body unless r.status == 200
|
@@ -50,20 +130,26 @@ module Geotrigger
|
|
50
130
|
h
|
51
131
|
end
|
52
132
|
|
133
|
+
# Creates and raises a +GeotriggerError+ from an API error response.
|
134
|
+
#
|
53
135
|
def raise_error error
|
54
136
|
ge = GeotriggerError.new error['message']
|
55
137
|
ge.code = error['code']
|
56
138
|
ge.headers = error['headers']
|
57
139
|
ge.message = error['message']
|
58
|
-
ge.
|
140
|
+
ge.parameters = error['parameters']
|
59
141
|
jj error
|
60
142
|
raise ge
|
61
143
|
end
|
62
144
|
|
145
|
+
# True if Session is for a Device.
|
146
|
+
#
|
63
147
|
def device?
|
64
148
|
type == :device
|
65
149
|
end
|
66
150
|
|
151
|
+
# True if Session is for an Application.
|
152
|
+
#
|
67
153
|
def application?
|
68
154
|
type == :application
|
69
155
|
end
|
data/lib/geotrigger/tag.rb
CHANGED
@@ -1,7 +1,18 @@
|
|
1
1
|
module Geotrigger
|
2
2
|
|
3
|
+
# +Tag+ objects offer ORM-ish access to all attributes of a Tag.
|
4
|
+
#
|
3
5
|
class Tag < Model
|
4
6
|
|
7
|
+
# Create a Tag with the given +Session+ and options. Note that Tags are
|
8
|
+
# automatically created by the API, if needed, when added to a Trigger
|
9
|
+
# or Device. This offers a way to create the Tag before applying it to
|
10
|
+
# anything.
|
11
|
+
#
|
12
|
+
# s = Geotrigger::Session.new
|
13
|
+
# tag = Geotrigger::Tag.create s, name: 'foo', deviceTagging: false
|
14
|
+
# #=> <Geotrigger::Tag ... >
|
15
|
+
#
|
5
16
|
def self.create session, opts
|
6
17
|
t = ::Geotrigger::Tag.new session: session
|
7
18
|
t.data = opts
|
@@ -9,6 +20,11 @@ module Geotrigger
|
|
9
20
|
t.post_create
|
10
21
|
end
|
11
22
|
|
23
|
+
# Create a new +Tag+ instance and load +@data+ from the API given a +Hash+
|
24
|
+
# with options:
|
25
|
+
#
|
26
|
+
# [name] +String+ name of the tag
|
27
|
+
#
|
12
28
|
def initialize opts = {}
|
13
29
|
super opts
|
14
30
|
if opts[:name] and @data.nil?
|
@@ -16,20 +32,35 @@ module Geotrigger
|
|
16
32
|
end
|
17
33
|
end
|
18
34
|
|
35
|
+
# Return an +Array+ of +Trigger+ objects in this Application that have this
|
36
|
+
# tag applied to them.
|
37
|
+
#
|
38
|
+
# [params] +Hash+ any additional parameters to include in the request (trigger/list)
|
39
|
+
#
|
19
40
|
def triggers params = {}
|
20
41
|
post_list 'triggers', params, tags: name
|
21
42
|
end
|
22
43
|
|
44
|
+
# Return an +Array+ of +Device+ objects in this Application that have this
|
45
|
+
# tag applied to them.
|
46
|
+
#
|
47
|
+
# [params] +Hash+ any additional parameters to include in the request (device/list)
|
48
|
+
#
|
23
49
|
def devices params = {}
|
24
50
|
post_list 'devices', params, tags: name
|
25
51
|
end
|
26
52
|
|
53
|
+
# Creates a tag by POSTing to tag/permissions/update with +@data+.
|
54
|
+
#
|
27
55
|
def post_create
|
28
56
|
post_data = @data.dup
|
29
57
|
grok_self_from post('tag/permissions/update', post_data), @data[:tags]
|
30
58
|
self
|
31
59
|
end
|
32
60
|
|
61
|
+
# POST the tag's +@data+ to the API via 'tag/permissions/update', and return
|
62
|
+
# the same object with the new +@data+ returned from API call.
|
63
|
+
#
|
33
64
|
def post_update
|
34
65
|
raise StateError.new 'device access_token prohibited' if @session.device?
|
35
66
|
post_data = @data.dup
|
@@ -39,6 +70,12 @@ module Geotrigger
|
|
39
70
|
end
|
40
71
|
alias_method :save, :post_update
|
41
72
|
|
73
|
+
# Reads the data specific to this +Tag+ from the API response and sets
|
74
|
+
# it in +@data+.
|
75
|
+
#
|
76
|
+
# [data] +Hash+ the API response
|
77
|
+
# [name] +String+ the name of the Tag to pull out
|
78
|
+
#
|
42
79
|
def grok_self_from data, name = nil
|
43
80
|
@data = data['tags'].select {|t| t['name'] == (name || @data['name'])}.first
|
44
81
|
end
|
data/lib/geotrigger/trigger.rb
CHANGED
@@ -1,16 +1,36 @@
|
|
1
1
|
module Geotrigger
|
2
2
|
|
3
|
+
# +Trigger+ objects offer ORM-ish access to all attributes of a Trigger.
|
4
|
+
#
|
5
|
+
# trigger.add_tags 'foo'
|
6
|
+
# trigger.save
|
7
|
+
#
|
8
|
+
# trigger.remove_tags 'bar'
|
9
|
+
# trigger.properties = { foo: 'bar', baz: true, bat: 123 }
|
10
|
+
# trigger.save
|
11
|
+
#
|
3
12
|
class Trigger < Model
|
4
13
|
include Taggable
|
5
14
|
|
6
15
|
CIRCLE_KEYS = %w[latitude longitude distance]
|
7
16
|
|
17
|
+
# Create a Trigger with the given +Session+ and options.
|
18
|
+
#
|
19
|
+
# s = Geotrigger::Session.new
|
20
|
+
# t = Geotrigger::Trigger.create s, condition: { ... }, action: { ... }, tags: ['foo']
|
21
|
+
# #=> <Geotrigger::Trigger ... >
|
22
|
+
#
|
8
23
|
def self.create session, opts
|
9
24
|
t = Trigger.new session: session
|
10
25
|
t.data = opts
|
11
26
|
t.post_create
|
12
27
|
end
|
13
28
|
|
29
|
+
# Create a new +Trigger+ instance and load +@data+ from the API given a
|
30
|
+
# +Hash+ with options:
|
31
|
+
#
|
32
|
+
# [tags] +Array+ name(s) of tag(s)
|
33
|
+
#
|
14
34
|
def initialize opts = {}
|
15
35
|
super opts
|
16
36
|
if opts[:trigger_id] and @data.nil?
|
@@ -18,16 +38,23 @@ module Geotrigger
|
|
18
38
|
end
|
19
39
|
end
|
20
40
|
|
41
|
+
# Return the +String+ of this trigger's default tag.
|
42
|
+
#
|
21
43
|
def default_tag
|
22
44
|
'trigger:%s' % triggerId
|
23
45
|
end
|
24
46
|
|
47
|
+
# Creates a trigger by POSTing to trigger/create with +@data+.
|
48
|
+
#
|
25
49
|
def post_create
|
26
50
|
post_data = @data.dup
|
27
51
|
@data = post 'trigger/create', post_data
|
28
52
|
self
|
29
53
|
end
|
30
54
|
|
55
|
+
# POST the trigger's +@data+ to the API via 'trigger/update', and return
|
56
|
+
# the same object with the new +@data+ returned from API call.
|
57
|
+
#
|
31
58
|
def post_update opts = {}
|
32
59
|
post_data = @data.dup
|
33
60
|
post_data['triggerIds'] = post_data.delete 'triggerId'
|
@@ -43,10 +70,19 @@ module Geotrigger
|
|
43
70
|
end
|
44
71
|
alias_method :save, :post_update
|
45
72
|
|
73
|
+
# Reads the data specific to this +Trigger+ from the API response and sets
|
74
|
+
# it in +@data+.
|
75
|
+
#
|
76
|
+
# [data] +Hash+ the API response
|
77
|
+
# [triggerId] +String+ the id of the trigger to pull out (first if nil)
|
78
|
+
#
|
46
79
|
def grok_self_from data, id = nil
|
47
80
|
@data = data['triggers'].select {|t| t['triggerId'] == (id || @data['triggerId'])}.first
|
48
81
|
end
|
49
82
|
|
83
|
+
# True if trigger is a "circle" type, meaning it has a point(longitude,latitude) and
|
84
|
+
# radius(distance) in its condition, rather than only a geojson or esrijson geometry.
|
85
|
+
#
|
50
86
|
def circle?
|
51
87
|
not CIRCLE_KEYS.map {|k| @data['condition']['geo'].keys.include? k}.select {|e| e}.empty?
|
52
88
|
end
|
data/lib/geotrigger/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: geotrigger
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.6
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Kenichi Nakamura
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2014-02-
|
11
|
+
date: 2014-02-27 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: httpclient
|