geotrigger 0.0.4 → 0.0.6

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 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