gradesfirst 0.2.1 → 0.3.0
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.
- data/.gitignore +1 -0
- data/Gemfile +1 -2
- data/Gemfile.lock +2 -6
- data/gradesfirst.gemspec +2 -2
- data/lib/gradesfirst.rb +1 -0
- data/lib/gradesfirst/cli.rb +7 -29
- data/lib/gradesfirst/cli_helper.rb +33 -0
- data/lib/gradesfirst/command.rb +7 -0
- data/lib/gradesfirst/commit_message_command.rb +1 -1
- data/lib/gradesfirst/task_add_command.rb +51 -0
- data/lib/gradesfirst/task_cli.rb +48 -0
- data/lib/gradesfirst/task_command.rb +38 -30
- data/lib/gradesfirst/task_delete_command.rb +64 -0
- data/lib/gradesfirst/task_list_command.rb +40 -0
- data/lib/gradesfirst/task_move_command.rb +64 -0
- data/lib/gradesfirst/task_toggle_command.rb +65 -0
- data/lib/http_magic.rb +2 -258
- data/lib/http_magic/api.rb +233 -0
- data/lib/http_magic/request.rb +93 -0
- data/lib/http_magic/uri.rb +74 -0
- data/lib/pivotal_tracker.rb +1 -1
- data/test/branch_command_test.rb +8 -21
- data/test/cli_test.rb +51 -34
- data/test/command_test.rb +5 -7
- data/test/commit_message_command_test.rb +37 -45
- data/test/fixtures/task.json +10 -0
- data/test/fixtures/task_added.txt +6 -0
- data/test/fixtures/task_deleted.txt +6 -0
- data/test/fixtures/task_moved.txt +6 -0
- data/test/fixtures/task_toggled.txt +6 -0
- data/test/fixtures/tasks.txt +2 -2
- data/test/http_magic/{get_test.rb → api/get_test.rb} +1 -1
- data/test/http_magic/api/post_test.rb +34 -0
- data/test/http_magic/request_test.rb +63 -0
- data/test/http_magic/uri_test.rb +28 -55
- data/test/support/pivotal_test_helper.rb +110 -0
- data/test/support/request_expectation.rb +33 -0
- data/test/task_add_command_test.rb +54 -0
- data/test/task_delete_command_test.rb +57 -0
- data/test/task_list_command_test.rb +18 -0
- data/test/task_move_command_test.rb +66 -0
- data/test/task_toggle_command_test.rb +58 -0
- data/test/test_helper.rb +35 -19
- metadata +29 -7
- data/test/pivotal_tracker_test.rb +0 -46
- data/test/task_command_test.rb +0 -52
@@ -0,0 +1,40 @@
|
|
1
|
+
require 'gradesfirst/task_command'
|
2
|
+
|
3
|
+
module GradesFirst
|
4
|
+
|
5
|
+
# Implementation of a Thor command for listing tasks related to a story.
|
6
|
+
class TaskListCommand < GradesFirst::TaskCommand
|
7
|
+
|
8
|
+
# Description of the gf task list Thor command that will be used in the
|
9
|
+
# command line help.
|
10
|
+
def self.description
|
11
|
+
'List the tasks related to a PivotalTracker story.'
|
12
|
+
end
|
13
|
+
|
14
|
+
# Performs the gf task list Thor command.
|
15
|
+
def execute
|
16
|
+
@story = current_story
|
17
|
+
if @story
|
18
|
+
@tasks = get_tasks(@story)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
# Generates the comand line output response. The output of the task command
|
23
|
+
# is a list of the tasks associated with the PivotalTracker story associated
|
24
|
+
# with the current branch.
|
25
|
+
def response
|
26
|
+
if @tasks.nil?
|
27
|
+
story_error_message
|
28
|
+
else
|
29
|
+
task_list_response(@story, @tasks)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
def story_error_message
|
36
|
+
'Tasks cannot be retrieved for this branch.'
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
require 'gradesfirst/task_command'
|
2
|
+
|
3
|
+
module GradesFirst
|
4
|
+
|
5
|
+
# Implementation of a Thor command for moving tasks on a PivotalTracker story
|
6
|
+
# from one priority position to another in the list.
|
7
|
+
class TaskMoveCommand < GradesFirst::TaskCommand
|
8
|
+
# Description of the "gf task move" Thor command that will be sued in the
|
9
|
+
# commandline help.
|
10
|
+
def self.description
|
11
|
+
'Move a task to a new position in the list for a PivotalTracker story.'
|
12
|
+
end
|
13
|
+
|
14
|
+
# Performs the gf task move Thor command.
|
15
|
+
def execute(from, to)
|
16
|
+
@story = current_story
|
17
|
+
if @story
|
18
|
+
task_id = get_task_id_by_position(@story, from)
|
19
|
+
if task_id
|
20
|
+
@success = task_move(@story, task_id, to)
|
21
|
+
else
|
22
|
+
@task_position_invalid = true
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
# Generates the command line output response. The output of the task move
|
28
|
+
# command is a completion status message which may be followed by the new
|
29
|
+
# list of tasks if the task was moved successfully.
|
30
|
+
def response
|
31
|
+
if @task_position_invalid
|
32
|
+
position_invalid_message
|
33
|
+
else
|
34
|
+
task_action_response(@story, @success)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
def position_invalid_message
|
41
|
+
'Task "from" position given does not exist.'
|
42
|
+
end
|
43
|
+
|
44
|
+
def story_error_message
|
45
|
+
'Tasks cannnot be moved for this branch.'
|
46
|
+
end
|
47
|
+
|
48
|
+
def task_error_message
|
49
|
+
'Moving the task failed.'
|
50
|
+
end
|
51
|
+
|
52
|
+
def task_move(story, task_id, to)
|
53
|
+
PivotalTracker.
|
54
|
+
projects[story['project_id']].
|
55
|
+
stories[story['id']].
|
56
|
+
tasks[task_id].
|
57
|
+
put(position: to.to_i)
|
58
|
+
end
|
59
|
+
|
60
|
+
def task_success_message
|
61
|
+
'Task was successfully moved.'
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
require 'gradesfirst/task_command'
|
2
|
+
|
3
|
+
module GradesFirst
|
4
|
+
|
5
|
+
# Implementation of a Thor command for toggling whether or not a task is
|
6
|
+
# complete.
|
7
|
+
class TaskToggleCommand < GradesFirst::TaskCommand
|
8
|
+
# Description of the "gf task toggle" Thor command that will be used in
|
9
|
+
# the commandline help.
|
10
|
+
def self.description
|
11
|
+
'Toggle completion status of a PivotalTracker story task.'
|
12
|
+
end
|
13
|
+
|
14
|
+
# Performs the gf task toggle POSITION Thor command.
|
15
|
+
def execute(position)
|
16
|
+
@story = current_story
|
17
|
+
if @story
|
18
|
+
task = get_task_by_position(@story, position)
|
19
|
+
if task
|
20
|
+
@success = task_toggle(@story, task)
|
21
|
+
else
|
22
|
+
@task_position_invalid = true
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
# Generates the command line output response. The output of the task toggle
|
28
|
+
# command is a completion status message which may be followed by the new
|
29
|
+
# list of tasks if the task was moved successfully.
|
30
|
+
def response
|
31
|
+
if @task_position_invalid
|
32
|
+
position_invalid_message
|
33
|
+
else
|
34
|
+
task_action_response(@story, @success)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
def position_invalid_message
|
41
|
+
'Task position given does not exist.'
|
42
|
+
end
|
43
|
+
|
44
|
+
def story_error_message
|
45
|
+
'Tasks cannot be toggled for this branch.'
|
46
|
+
end
|
47
|
+
|
48
|
+
def task_error_message
|
49
|
+
'Toggling of the task completion status failed.'
|
50
|
+
end
|
51
|
+
|
52
|
+
def task_success_message
|
53
|
+
'Task completion status was successfully toggled.'
|
54
|
+
end
|
55
|
+
|
56
|
+
def task_toggle(story, task)
|
57
|
+
PivotalTracker.
|
58
|
+
projects[story['project_id']].
|
59
|
+
stories[story['id']].
|
60
|
+
tasks[task['id']].
|
61
|
+
put(complete: !task['complete'])
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
end
|
data/lib/http_magic.rb
CHANGED
@@ -1,260 +1,4 @@
|
|
1
|
-
require '
|
2
|
-
require 'json'
|
1
|
+
require 'http_magic/api'
|
3
2
|
|
4
|
-
|
5
|
-
# configuration.
|
6
|
-
#
|
7
|
-
# Assuming an api where the url http://www.example.com/foo/99 responds with
|
8
|
-
# the following.
|
9
|
-
#
|
10
|
-
# Header:
|
11
|
-
#
|
12
|
-
# Content-Type: application/json
|
13
|
-
#
|
14
|
-
# Body:
|
15
|
-
#
|
16
|
-
# {
|
17
|
-
# "name": "Foo Bar"
|
18
|
-
# }
|
19
|
-
#
|
20
|
-
# == Example
|
21
|
-
#
|
22
|
-
# class ExampleApi < HttpMagic
|
23
|
-
# url 'www.example.com'
|
24
|
-
# end
|
25
|
-
#
|
26
|
-
# ExampleApi.foo[99].get['name']
|
27
|
-
# => "Foo Bar"
|
28
|
-
#
|
29
|
-
class HttpMagic < BasicObject
|
30
|
-
# Makes the new method private so that instances of this class and it's
|
31
|
-
# children can only be instantiated through the class method method_missing.
|
32
|
-
# This is needed to enforce the intended usage of HttpMagic where the class
|
33
|
-
# is configured to represent an http location. Once it is configured, the
|
34
|
-
# location is interacted with dynamically with chained methods that correspond
|
35
|
-
# with parts of URNs found at the location.
|
36
|
-
#
|
37
|
-
# == Example
|
38
|
-
#
|
39
|
-
# class ExampleApi < HttpMagic
|
40
|
-
# url 'www.example.com'
|
41
|
-
# namespace 'api/v1'
|
42
|
-
# headers({'X-AuthToken' => 'token'})
|
43
|
-
# end
|
44
|
-
#
|
45
|
-
# ExampleApi.new.uri
|
46
|
-
# => "http://www.example.com/api/v1/new"
|
47
|
-
#
|
48
|
-
# ExampleApi.foo[99].uri
|
49
|
-
# => "http://www.example.com/api/v1/foo/99"
|
50
|
-
class << self
|
51
|
-
private :new
|
52
|
-
end
|
53
|
-
|
54
|
-
# Sets or returns the request headers for an HttpMagic based class. The
|
55
|
-
# headers will be passed along to each resource request being made.
|
56
|
-
#
|
57
|
-
# == Example
|
58
|
-
#
|
59
|
-
# class ExampleApi < HttpMagic
|
60
|
-
# url 'www.example.com'
|
61
|
-
# namespace 'api/v1'
|
62
|
-
# headers({'X-AuthToken' => 'token'})
|
63
|
-
# end
|
64
|
-
def self.headers(value = :not_provided)
|
65
|
-
unless value == :not_provided
|
66
|
-
@headers = value
|
67
|
-
end
|
68
|
-
@headers
|
69
|
-
end
|
70
|
-
|
71
|
-
# Sets or returns the namespace for an HttpMagic based class. The namespace
|
72
|
-
# will be prefixed to the urn for each request made to the url.
|
73
|
-
#
|
74
|
-
# Assuming an api where each resource is namespaced with 'api/v1' and where
|
75
|
-
# the url http://www.example.com/api/v1/foo/99 responds with the following.
|
76
|
-
#
|
77
|
-
# Header:
|
78
|
-
#
|
79
|
-
# Content-Type: application/json
|
80
|
-
#
|
81
|
-
# Body:
|
82
|
-
#
|
83
|
-
# {
|
84
|
-
# "name": "Foo Bar"
|
85
|
-
# }
|
86
|
-
#
|
87
|
-
# == Example
|
88
|
-
#
|
89
|
-
# class ExampleApi < HttpMagic
|
90
|
-
# url 'www.example.com'
|
91
|
-
# namespace 'api/v1'
|
92
|
-
# end
|
93
|
-
#
|
94
|
-
# ExampleApi.foo[99].get['name']
|
95
|
-
# => "Foo Bar"
|
96
|
-
def self.namespace(value = :not_provided)
|
97
|
-
unless value == :not_provided
|
98
|
-
@namespace = value
|
99
|
-
end
|
100
|
-
@namespace
|
101
|
-
end
|
102
|
-
|
103
|
-
# Sets or returns the uniform resource locator for the HTTP resource.
|
104
|
-
#
|
105
|
-
# == Example
|
106
|
-
#
|
107
|
-
# class ExampleApi < HttpMagic
|
108
|
-
# url 'www.example.com'
|
109
|
-
# end
|
110
|
-
#
|
111
|
-
# ExampleApi.url
|
112
|
-
# => 'www.example.com'
|
113
|
-
def self.url(value = :not_provided)
|
114
|
-
unless value == :not_provided
|
115
|
-
@url = value
|
116
|
-
end
|
117
|
-
@url
|
118
|
-
end
|
119
|
-
|
120
|
-
# Class scoped method_missing that starts the magic of creating urns
|
121
|
-
# through meta programming by instantiating an instance of the class
|
122
|
-
# and delegating the first part of the urn to that instance. This method
|
123
|
-
# is an implementation of the Factory Method design pattern.
|
124
|
-
def self.method_missing(name, *args, &block)
|
125
|
-
new(@url, @namespace, @headers).__send__(name, *args)
|
126
|
-
end
|
127
|
-
|
128
|
-
def initialize(url, namespace, headers)
|
129
|
-
@url = url
|
130
|
-
@namespace = namespace
|
131
|
-
@headers = headers
|
132
|
-
|
133
|
-
@urn_parts = []
|
134
|
-
end
|
135
|
-
|
136
|
-
# Resource index reference intended to allow for the use of numbers in a urn
|
137
|
-
# such as the following 'foo/99' being referenced by ExampleApi.foo[99]. It
|
138
|
-
# can also be used to allow for HttpMagic methods to be specified for a urn
|
139
|
-
# such as 'foo/get' being referenced by ExampleApi.foo[:get]. Finally, it can
|
140
|
-
# be used for urn parts that are not valid Ruby methods such as 'foo/%5B%5D'
|
141
|
-
# being referenced by ExampleApi.foo["%5B%5D"].
|
142
|
-
#
|
143
|
-
# * part - a part of a urn such that 'foo' and 'bar' would be parts of the urn
|
144
|
-
# 'foo/bar'.
|
145
|
-
#
|
146
|
-
# Returns a reference to its instance so that urn parts can be chained
|
147
|
-
# together.
|
148
|
-
def [](part)
|
149
|
-
@urn_parts << part.to_s
|
150
|
-
self
|
151
|
-
end
|
152
|
-
|
153
|
-
# Gets a resource from the URI and returns it based on its content type. JSON
|
154
|
-
# content will be parsed and returned as equivalent Ruby objects. All other
|
155
|
-
# content types will be returned as text.
|
156
|
-
#
|
157
|
-
# Assuming an api where each resource is namespaced with 'api/v1' and where
|
158
|
-
# the url http://www.example.com/api/v1/foo/99 responds with the following.
|
159
|
-
#
|
160
|
-
# Header:
|
161
|
-
#
|
162
|
-
# Content-Type: application/json
|
163
|
-
#
|
164
|
-
# Body:
|
165
|
-
#
|
166
|
-
# {
|
167
|
-
# "name": "Foo Bar"
|
168
|
-
# }
|
169
|
-
#
|
170
|
-
# == Example
|
171
|
-
#
|
172
|
-
# class ExampleApi < HttpMagic
|
173
|
-
# url 'www.example.com'
|
174
|
-
# namespace 'api/v1'
|
175
|
-
# end
|
176
|
-
#
|
177
|
-
# ExampleApi.foo[99].get
|
178
|
-
# => { "name" => "Foo Bar" }
|
179
|
-
def get
|
180
|
-
http = ::Net::HTTP.new(url, 443)
|
181
|
-
http.use_ssl = true
|
182
|
-
|
183
|
-
response = http.request_get(urn, @headers || {})
|
184
|
-
if response && response.is_a?(::Net::HTTPSuccess)
|
185
|
-
if response.content_type == 'application/json'
|
186
|
-
::JSON.parse(response.body)
|
187
|
-
else
|
188
|
-
response.body
|
189
|
-
end
|
190
|
-
else
|
191
|
-
nil
|
192
|
-
end
|
193
|
-
end
|
194
|
-
|
195
|
-
# Uniform resource identifier for a specified request.
|
196
|
-
#
|
197
|
-
# == Example
|
198
|
-
#
|
199
|
-
# class ExampleApi < HttpMagic
|
200
|
-
# url 'www.example.com'
|
201
|
-
# namespace 'api/v1'
|
202
|
-
# end
|
203
|
-
#
|
204
|
-
# ExampleApi.foo[99].uri
|
205
|
-
# => "www.example.com/api/v1/foo/99"
|
206
|
-
def uri
|
207
|
-
"#{url}#{urn}"
|
208
|
-
end
|
209
|
-
|
210
|
-
# Uniform resource locator for all the resources.
|
211
|
-
#
|
212
|
-
# == Example
|
213
|
-
#
|
214
|
-
# class ExampleApi < HttpMagic
|
215
|
-
# url 'www.example.com'
|
216
|
-
# namespace 'api/v1'
|
217
|
-
# end
|
218
|
-
#
|
219
|
-
# ExampleApi.foo[99].url
|
220
|
-
# => "www.example.com"
|
221
|
-
def url
|
222
|
-
@url
|
223
|
-
end
|
224
|
-
|
225
|
-
# Uniform resource name for a resource.
|
226
|
-
#
|
227
|
-
# == Example
|
228
|
-
#
|
229
|
-
# class ExampleApi < HttpMagic
|
230
|
-
# url 'www.example.com'
|
231
|
-
# namespace 'api/v1'
|
232
|
-
# end
|
233
|
-
#
|
234
|
-
# ExampleApi.foo[99].urn
|
235
|
-
# => "api/v1/foo/99"
|
236
|
-
def urn
|
237
|
-
resource_name = [@namespace, @urn_parts].flatten.compact.join('/')
|
238
|
-
"/#{resource_name}"
|
239
|
-
end
|
240
|
-
|
241
|
-
# Instance scoped method_missing that accumulates the URN parts used in
|
242
|
-
# forming the URI. It returns a reference to the current instance so that
|
243
|
-
# method and [] based parts can be chained together to from the URN. In the
|
244
|
-
# case of ExampleApi.foo[99] the 'foo' method is accumlated as a URN part
|
245
|
-
# that will form the resulting URI.
|
246
|
-
#
|
247
|
-
# == Example
|
248
|
-
#
|
249
|
-
# class ExampleApi < HttpMagic
|
250
|
-
# url 'www.example.com'
|
251
|
-
# namespace 'api/v1'
|
252
|
-
# end
|
253
|
-
#
|
254
|
-
# ExampleApi.foo[99].uri
|
255
|
-
# => "www.example.com/api/v1/foo/99"
|
256
|
-
def method_missing(part, *args, &block)
|
257
|
-
@urn_parts << part.to_s
|
258
|
-
self
|
259
|
-
end
|
3
|
+
module HttpMagic
|
260
4
|
end
|
@@ -0,0 +1,233 @@
|
|
1
|
+
require 'json'
|
2
|
+
require 'http_magic/uri'
|
3
|
+
require 'http_magic/request'
|
4
|
+
|
5
|
+
module HttpMagic
|
6
|
+
# A magical class that interacts with HTTP resources with a minimal amount of
|
7
|
+
# configuration.
|
8
|
+
#
|
9
|
+
# Assuming an api where the url http://www.example.com/api/v1/foo/99 responds
|
10
|
+
# with the following.
|
11
|
+
#
|
12
|
+
# Header:
|
13
|
+
#
|
14
|
+
# Content-Type: application/json
|
15
|
+
#
|
16
|
+
# Body:
|
17
|
+
#
|
18
|
+
# {
|
19
|
+
# "name": "Foo Bar"
|
20
|
+
# }
|
21
|
+
#
|
22
|
+
# == Example
|
23
|
+
#
|
24
|
+
# class ExampleApi < HttpMagic::Api
|
25
|
+
# url 'www.example.com'
|
26
|
+
# namespace 'api/v1'
|
27
|
+
# headers({'X-AuthToken' => 'token'})
|
28
|
+
# end
|
29
|
+
#
|
30
|
+
# ExampleApi.foo[99].get['name']
|
31
|
+
# => "Foo Bar"
|
32
|
+
#
|
33
|
+
# ExampleApi.foo.create.post(name: 'New Foo')
|
34
|
+
# => { 'name' => 'New Foo' }
|
35
|
+
class Api < BasicObject
|
36
|
+
# Makes the new method private so that instances of this class and it's
|
37
|
+
# children can only be instantiated through the class method method_missing.
|
38
|
+
# This is needed to enforce the intended usage of HttpMagic where the class
|
39
|
+
# is configured to represent an http location. Once it is configured, the
|
40
|
+
# location is interacted with dynamically with chained methods that correspond
|
41
|
+
# with parts of URNs found at the location.
|
42
|
+
class << self
|
43
|
+
private :new
|
44
|
+
end
|
45
|
+
|
46
|
+
# Sets or returns the request headers for an HttpMagic based class. The
|
47
|
+
# headers will be passed along to each resource request being made.
|
48
|
+
#
|
49
|
+
# == Example
|
50
|
+
#
|
51
|
+
# class ExampleApi < HttpMagic:Api
|
52
|
+
# url 'www.example.com'
|
53
|
+
# headers({'X-AuthToken' => 'token'})
|
54
|
+
# end
|
55
|
+
def self.headers(value = :not_provided)
|
56
|
+
unless value == :not_provided
|
57
|
+
@headers = value
|
58
|
+
end
|
59
|
+
@headers
|
60
|
+
end
|
61
|
+
|
62
|
+
# Sets or returns the namespace for an HttpMagic based class. The namespace
|
63
|
+
# will be prefixed to the urn for each request made to the url.
|
64
|
+
#
|
65
|
+
# Assuming an api where each resource is namespaced with 'api/v1' and where
|
66
|
+
# the url http://www.example.com/api/v1/foo/99 responds with the following.
|
67
|
+
#
|
68
|
+
# Header:
|
69
|
+
#
|
70
|
+
# Content-Type: application/json
|
71
|
+
#
|
72
|
+
# Body:
|
73
|
+
#
|
74
|
+
# {
|
75
|
+
# "name": "Foo Bar"
|
76
|
+
# }
|
77
|
+
#
|
78
|
+
# == Example
|
79
|
+
#
|
80
|
+
# class ExampleApi < HttpMagic:Api
|
81
|
+
# url 'www.example.com'
|
82
|
+
# namespace 'api/v1'
|
83
|
+
# end
|
84
|
+
#
|
85
|
+
# ExampleApi.foo[99].get['name']
|
86
|
+
# => "Foo Bar"
|
87
|
+
#
|
88
|
+
def self.namespace(value = :not_provided)
|
89
|
+
unless value == :not_provided
|
90
|
+
@namespace = value
|
91
|
+
end
|
92
|
+
@namespace
|
93
|
+
end
|
94
|
+
|
95
|
+
# Sets or returns the uniform resource locator for the HTTP resource.
|
96
|
+
#
|
97
|
+
# == Example
|
98
|
+
#
|
99
|
+
# class ExampleApi < HttpMagic::Api
|
100
|
+
# url 'www.example.com'
|
101
|
+
# end
|
102
|
+
def self.url(value = :not_provided)
|
103
|
+
unless value == :not_provided
|
104
|
+
@url = value
|
105
|
+
end
|
106
|
+
@url
|
107
|
+
end
|
108
|
+
|
109
|
+
# Class scoped method_missing that starts the magic of creating urns
|
110
|
+
# through meta programming by instantiating an instance of the class
|
111
|
+
# and delegating the first part of the urn to that instance. This method
|
112
|
+
# is an implementation of the Factory Method design pattern.
|
113
|
+
def self.method_missing(name, *args, &block)
|
114
|
+
new(@url, @namespace, @headers).__send__(name, *args)
|
115
|
+
end
|
116
|
+
|
117
|
+
def initialize(url, namespace, headers)
|
118
|
+
@uri = Uri.new(url)
|
119
|
+
@uri.namespace = namespace
|
120
|
+
@headers = headers
|
121
|
+
end
|
122
|
+
|
123
|
+
# Resource index reference intended to allow for the use of numbers in a urn
|
124
|
+
# such as the following 'foo/99' being referenced by ExampleApi.foo[99]. It
|
125
|
+
# can also be used to allow for HttpMagic methods to be specified for a urn
|
126
|
+
# such as 'foo/get' being referenced by ExampleApi.foo[:get]. Finally, it can
|
127
|
+
# be used for urn parts that are not valid Ruby methods such as 'foo/%5B%5D'
|
128
|
+
# being referenced by ExampleApi.foo["%5B%5D"].
|
129
|
+
#
|
130
|
+
# * part - a part of a urn such that 'foo' and 'bar' would be parts of the urn
|
131
|
+
# 'foo/bar'.
|
132
|
+
#
|
133
|
+
# Returns a reference to its instance so that urn parts can be chained
|
134
|
+
# together.
|
135
|
+
def [](part)
|
136
|
+
@uri.parts << part.to_s
|
137
|
+
self
|
138
|
+
end
|
139
|
+
|
140
|
+
def delete
|
141
|
+
request = Request.new(@uri,
|
142
|
+
headers: @headers
|
143
|
+
)
|
144
|
+
request.delete
|
145
|
+
end
|
146
|
+
|
147
|
+
# Gets a resource from the URI and returns it based on its content type.
|
148
|
+
# JSON content will be parsed and returned as equivalent Ruby objects. All
|
149
|
+
# other content types will be returned as text.
|
150
|
+
#
|
151
|
+
# Assuming an api where each resource is namespaced with 'api/v1' and where
|
152
|
+
# the url http://www.example.com/api/v1/foo/99 responds with the following.
|
153
|
+
#
|
154
|
+
# Header:
|
155
|
+
#
|
156
|
+
# Content-Type: application/json
|
157
|
+
#
|
158
|
+
# Body:
|
159
|
+
#
|
160
|
+
# {
|
161
|
+
# "name": "Foo Bar"
|
162
|
+
# }
|
163
|
+
#
|
164
|
+
# == Example
|
165
|
+
#
|
166
|
+
# class ExampleApi < HttpMagic::Api
|
167
|
+
# url 'www.example.com'
|
168
|
+
# namespace 'api/v1'
|
169
|
+
# end
|
170
|
+
#
|
171
|
+
# ExampleApi.foo[99].get
|
172
|
+
# => { "name" => "Foo Bar" }
|
173
|
+
def get
|
174
|
+
request = Request.new(@uri,
|
175
|
+
headers: @headers
|
176
|
+
)
|
177
|
+
request.get
|
178
|
+
end
|
179
|
+
|
180
|
+
# POST's a resource from the URI and returns the result based on its content
|
181
|
+
# type. JSON content will be parsed and returned as equivalent Ruby objects.
|
182
|
+
# All other content types will be returned as text.
|
183
|
+
#
|
184
|
+
# Assuming an api where each resource is namespaced with 'api/v1' and where
|
185
|
+
# the url http://www.example.com/api/v1/foo/create responds with the
|
186
|
+
# following when a 'name' is sent with the request.
|
187
|
+
#
|
188
|
+
# Header:
|
189
|
+
#
|
190
|
+
# Content-Type: application/json
|
191
|
+
#
|
192
|
+
# Body:
|
193
|
+
#
|
194
|
+
# {
|
195
|
+
# "name": "New Foo"
|
196
|
+
# }
|
197
|
+
#
|
198
|
+
# == Example
|
199
|
+
#
|
200
|
+
# class ExampleApi < HttpMagic::Api
|
201
|
+
# url 'www.example.com'
|
202
|
+
# namespace 'api/v1'
|
203
|
+
# end
|
204
|
+
#
|
205
|
+
# ExampleApi.foo.create.post(name: 'New Foo')
|
206
|
+
# => { "name" => "New Foo" }
|
207
|
+
def post(data = {})
|
208
|
+
request = Request.new(@uri,
|
209
|
+
headers: @headers,
|
210
|
+
data: data,
|
211
|
+
)
|
212
|
+
request.post
|
213
|
+
end
|
214
|
+
|
215
|
+
def put(data = {})
|
216
|
+
request = Request.new(@uri,
|
217
|
+
headers: @headers,
|
218
|
+
data: data,
|
219
|
+
)
|
220
|
+
request.put
|
221
|
+
end
|
222
|
+
|
223
|
+
# Instance scoped method_missing that accumulates the URN parts used in
|
224
|
+
# forming the URI. It returns a reference to the current instance so that
|
225
|
+
# method and [] based parts can be chained together to from the URN. In the
|
226
|
+
# case of ExampleApi.foo[99] the 'foo' method is accumlated as a URN part
|
227
|
+
# that will form the resulting URI.
|
228
|
+
def method_missing(part, *args, &block)
|
229
|
+
@uri.parts << part.to_s
|
230
|
+
self
|
231
|
+
end
|
232
|
+
end
|
233
|
+
end
|