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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 1eab33595fb526ab1fd3bd9b5ce817efec4645d4
4
- data.tar.gz: 2d130461adf416f37c4a4bc4ae2244f663d09339
3
+ metadata.gz: b80b540534f9cbb0793eca389c041ac27eea153c
4
+ data.tar.gz: 1100a93307a68ab21ff168fad392c21a64191820
5
5
  SHA512:
6
- metadata.gz: f5f6c4e88f34f686f28b1954f9dce23aa692fdabeae8dae54ee99c1f27897f5545da557a0405f600622ba9b4a4f966d49111a531620485540f79860c38ec98f9
7
- data.tar.gz: 4f87a58f91f31f10a23636b452e1210b4a90945e0e7e65f68ce224a2f9347d22f0d9008eb51a09f7e3fcb5c09e5c32e4fcb78437988a434894bb478e9c27021f
6
+ metadata.gz: b80200b1e85cdd48327cecfc7fc684523a9190466f299f76f0a471290417696eca695e24e41b9b4564a7c9d4255551c45ddd6472f6771bcabd8a6865dfd7306a
7
+ data.tar.gz: 8939b512abcb68f664115143aef14d35b2b9a35dabce8605d5fea0f77fe5a1f813fd667b2b03c43de1f687635829837fbd910f2606a7d79b25c6c1425b9f3f19
data/.gitignore CHANGED
@@ -1,3 +1,4 @@
1
1
  Gemfile.lock
2
2
  config.yml
3
3
  geotrigger-0.0.1.gem
4
+ doc/*
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
+ ```
@@ -1,10 +1,11 @@
1
- class String
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
- alias_method :blank?, :empty?
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}" }
@@ -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'; class HTTPClient; def inspect; to_s; end; end
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, :params
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
- # http the specified method to the specified path with the given params.
33
- # json parse the response body or raise errors.
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
@@ -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
@@ -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
@@ -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.params = error['params']
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
@@ -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
@@ -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
@@ -1,3 +1,3 @@
1
1
  module Geotrigger
2
- VERSION = '0.0.4'
2
+ VERSION = '0.0.6'
3
3
  end
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
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-18 00:00:00.000000000 Z
11
+ date: 2014-02-27 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: httpclient