layer-api 0.3.1 → 0.4.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.
- checksums.yaml +4 -4
- data/MIGRATING.md +212 -0
- data/README.md +166 -43
- data/layer-api.gemspec +3 -3
- data/lib/layer/api.rb +16 -10
- data/lib/layer/errors.rb +43 -0
- data/lib/layer/http_client.rb +70 -0
- data/lib/layer/identity_token.rb +53 -0
- data/lib/layer/middleware/api_errors.rb +13 -0
- data/lib/layer/platform/client.rb +57 -0
- data/lib/layer/resource.rb +80 -0
- data/lib/layer/resource_proxy.rb +20 -0
- data/lib/layer/resources/announcement.rb +6 -0
- data/lib/layer/resources/block.rb +31 -0
- data/lib/layer/resources/conversation.rb +9 -0
- data/lib/layer/resources/message.rb +6 -0
- data/lib/layer/resources/user.rb +21 -0
- data/lib/layer/{api/version.rb → version.rb} +1 -1
- metadata +19 -15
- data/lib/layer/api/client.rb +0 -25
- data/lib/layer/api/client/announcements.rb +0 -9
- data/lib/layer/api/client/conversations.rb +0 -30
- data/lib/layer/api/client/identity_token.rb +0 -61
- data/lib/layer/api/client/users.rb +0 -18
- data/lib/layer/api/configuration.rb +0 -30
- data/lib/layer/api/connection.rb +0 -44
- data/lib/layer/api/error.rb +0 -39
- data/lib/layer/api/middleware/api_errors.rb +0 -15
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 68514306a1bf3af9a052d78f8b05a0320187b8e8
|
4
|
+
data.tar.gz: bf7c980017a973f4161d4506a028c9946b1d9493
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 2ceea987ca922d113bb8fb8d96e696f8d8487a731915c68b28d78e4b1a6982e9ec8ad739d4324324ab51060ca3b0f2302f923bc6f6674efc85f5a1deffbebe87
|
7
|
+
data.tar.gz: d1b427e746217ed81afc5ed239113cffa7226ecb8b545a59416336841c0939a976987c24e538f934de20e95e5ae527595ea3b9f665f769271166e581d68f7b52
|
data/MIGRATING.md
ADDED
@@ -0,0 +1,212 @@
|
|
1
|
+
# Migrating from version `0.3.x` to `0.4`
|
2
|
+
|
3
|
+
`0.4` introduces a pretty significant overhaul to the codebase, which isn't backwards compatible. There are a couple of reasons:
|
4
|
+
|
5
|
+
* This gem was originally built with the purpose of only wrapping the [Platform API](https://developer.layer.com/docs/platform). Since then, Layer have added REST & Websocket API's, Webhook support, as well as adding more functionality to the Platform API.
|
6
|
+
* While the existing code in `0.3.x` was fairly basic, it wasn't written in a way that is easily maintainable and extensible with all of Layer's API's
|
7
|
+
|
8
|
+
If you are starting from `0.4` upwards, you can discard all of this.
|
9
|
+
|
10
|
+
## Authentication
|
11
|
+
In `0.3.x` authentication & setup would be done using the following:
|
12
|
+
|
13
|
+
```ruby
|
14
|
+
layer = Layer::Api::Client.new(
|
15
|
+
api_token: "your_api_token",
|
16
|
+
app_id: "your_app_id"
|
17
|
+
)
|
18
|
+
```
|
19
|
+
|
20
|
+
In `0.4`, the following is required:
|
21
|
+
|
22
|
+
```ruby
|
23
|
+
layer = Layer::Platform::Client.new(api_token: "your_api_token", app_id: "your_app_id")
|
24
|
+
```
|
25
|
+
|
26
|
+
## Resources
|
27
|
+
In `0.3.x`, each function returned a Hash containing a parsed JSON representation of each resource.
|
28
|
+
|
29
|
+
`0.4` introduced `Resource` - A base class used to encapsulate each JSON response.
|
30
|
+
|
31
|
+
`Resource` can allow attributes to be accessed with dot notation. Alternatively, you can return a Hashed version of the resource using `Resource.attributes`. For example:
|
32
|
+
|
33
|
+
```ruby
|
34
|
+
|
35
|
+
conv = layer.conversations.find("conversation_id")
|
36
|
+
=> #<Layer::Resources::Conversation:0x007fea8b2be508 @attributes={...}>"
|
37
|
+
|
38
|
+
conv.url
|
39
|
+
=> "https://api.layer.com/apps/<APP_ID>/conversations/<CONV_ID>"
|
40
|
+
|
41
|
+
conv.attributes
|
42
|
+
=> {...}
|
43
|
+
```
|
44
|
+
|
45
|
+
## Retrieving conversations
|
46
|
+
In `0.3.x` a conversation could be retrieved with:
|
47
|
+
|
48
|
+
```ruby
|
49
|
+
layer.get_conversation("conversation_id")
|
50
|
+
```
|
51
|
+
|
52
|
+
In `0.4`, the same result can be achieved with:
|
53
|
+
|
54
|
+
```ruby
|
55
|
+
conv = layer.conversations.find("conversation_id")
|
56
|
+
conv.attributes
|
57
|
+
|
58
|
+
```
|
59
|
+
|
60
|
+
## Editing conversations
|
61
|
+
|
62
|
+
In `0.3.x`:
|
63
|
+
|
64
|
+
```ruby
|
65
|
+
operations = [
|
66
|
+
{operation: "add", property: "participants", value: "user1"},
|
67
|
+
{operation: "add", property: "participants", value: "user2"}
|
68
|
+
]
|
69
|
+
|
70
|
+
layer.edit_conversation("conversation_id", operations)
|
71
|
+
```
|
72
|
+
|
73
|
+
In `0.4`:
|
74
|
+
|
75
|
+
```ruby
|
76
|
+
conv = layer.conversations.find("conversation_id")
|
77
|
+
conv.update(operations)
|
78
|
+
```
|
79
|
+
|
80
|
+
## Sending messages
|
81
|
+
|
82
|
+
In `0.3.x`:
|
83
|
+
|
84
|
+
```ruby
|
85
|
+
message = {
|
86
|
+
sender: {
|
87
|
+
name: "t-bone"
|
88
|
+
},
|
89
|
+
parts: [
|
90
|
+
{
|
91
|
+
body: "Hello, World!",
|
92
|
+
mime_type: "text/plain"
|
93
|
+
},
|
94
|
+
{
|
95
|
+
body: "YW55IGNhcm5hbCBwbGVhc3VyZQ==",
|
96
|
+
mime_type: "image/jpeg",
|
97
|
+
encoding: "base64"
|
98
|
+
}
|
99
|
+
],
|
100
|
+
notification: {
|
101
|
+
text: "This is the alert text to include with the Push Notification.",
|
102
|
+
sound: "chime.aiff"
|
103
|
+
}
|
104
|
+
}
|
105
|
+
|
106
|
+
layer.send_message("conversation_id", message)
|
107
|
+
```
|
108
|
+
|
109
|
+
In `0.4`:
|
110
|
+
|
111
|
+
```ruby
|
112
|
+
conv = layer.conversations.find("conversation_id")
|
113
|
+
conv.messages.create(message)
|
114
|
+
```
|
115
|
+
|
116
|
+
## Sending announcements
|
117
|
+
|
118
|
+
In `0.3.x`:
|
119
|
+
|
120
|
+
```ruby
|
121
|
+
announcement = {
|
122
|
+
recipients: [ "1234", "5678" ],
|
123
|
+
sender: {
|
124
|
+
name: "The System"
|
125
|
+
},
|
126
|
+
parts: [
|
127
|
+
{
|
128
|
+
body: "Hello, World!",
|
129
|
+
mime_type: "text/plain"
|
130
|
+
},
|
131
|
+
{
|
132
|
+
body: "YW55IGNhcm5hbCBwbGVhc3VyZQ==",
|
133
|
+
mime_type: "image/jpeg",
|
134
|
+
encoding: "base64"
|
135
|
+
}
|
136
|
+
],
|
137
|
+
notification: {
|
138
|
+
text: "This is the alert text to include with the Push Notification.",
|
139
|
+
sound: "chime.aiff"
|
140
|
+
}
|
141
|
+
}
|
142
|
+
|
143
|
+
layer.send_announcement(announcement)
|
144
|
+
```
|
145
|
+
|
146
|
+
In `0.4`:
|
147
|
+
|
148
|
+
```ruby
|
149
|
+
layer.announcements.send(announcement)
|
150
|
+
```
|
151
|
+
|
152
|
+
## Retrieving a users block list
|
153
|
+
|
154
|
+
In `0.3.x`:
|
155
|
+
|
156
|
+
```ruby
|
157
|
+
layer.get_blocklist("user_id")
|
158
|
+
```
|
159
|
+
|
160
|
+
In `0.4`:
|
161
|
+
|
162
|
+
```ruby
|
163
|
+
user = layer.users.find("user_id")
|
164
|
+
user.blocks.list
|
165
|
+
```
|
166
|
+
|
167
|
+
## Adding a user to a block list
|
168
|
+
|
169
|
+
In `0.3.x`:
|
170
|
+
|
171
|
+
```ruby
|
172
|
+
layer.block_user("owner_id", "user_id")
|
173
|
+
```
|
174
|
+
|
175
|
+
In `0.4`:
|
176
|
+
|
177
|
+
```ruby
|
178
|
+
owner = layer.users.find("owner_id")
|
179
|
+
owner.blocks.create(user_id: "user_id")
|
180
|
+
```
|
181
|
+
|
182
|
+
## Remove a user from another users block list
|
183
|
+
|
184
|
+
In `0.3.x`:
|
185
|
+
|
186
|
+
```ruby
|
187
|
+
layer.unblock_user("owner_id", "user_id")
|
188
|
+
```
|
189
|
+
|
190
|
+
In `0.4`:
|
191
|
+
|
192
|
+
```ruby
|
193
|
+
block = layer.users.find("owner_id").blocks.find("user_id")
|
194
|
+
block.destroy
|
195
|
+
|
196
|
+
|
197
|
+
```
|
198
|
+
|
199
|
+
## Generating identity tokens
|
200
|
+
|
201
|
+
In `0.3.x`:
|
202
|
+
|
203
|
+
```ruby
|
204
|
+
layer.generate_identity_token(user_id: "1234", nonce: "your_random_nonce")
|
205
|
+
```
|
206
|
+
|
207
|
+
In `0.4.`:
|
208
|
+
|
209
|
+
```ruby
|
210
|
+
token = layer.generate_identity_token(user_id: "1234", nonce: "your_random_nonce")
|
211
|
+
token.to_s
|
212
|
+
```
|
data/README.md
CHANGED
@@ -1,11 +1,14 @@
|
|
1
|
-
# Layer
|
1
|
+
# Layer API Ruby Client
|
2
2
|
[](https://travis-ci.org/cakejelly/layer-api) [](http://badge.fury.io/rb/layer-api)
|
3
3
|
|
4
|
-
A very simple wrapper for
|
4
|
+
A very simple wrapper for Layer's Web API's
|
5
5
|
|
6
6
|
If you want to learn more, check out the [official documentation](https://developer.layer.com/docs/platform).
|
7
7
|
|
8
|
-
##
|
8
|
+
## Migrating from 0.3.x
|
9
|
+
Version 0.4 is not compatible with older versions. SEE [MIGRATING](MIGRATING.md) for details on how to migrate your code to the latest version.
|
10
|
+
|
11
|
+
## Installation ##
|
9
12
|
|
10
13
|
Add this line to your application's Gemfile:
|
11
14
|
|
@@ -21,31 +24,68 @@ Or install it yourself as:
|
|
21
24
|
|
22
25
|
$ gem install layer-api
|
23
26
|
|
27
|
+
|
24
28
|
## Usage
|
25
29
|
|
26
|
-
###
|
30
|
+
### Resources
|
31
|
+
All client methods return `Resource` objects or a collection of `Resource` objects. Every attribute from a resource can be accessed calling attribute methods:
|
32
|
+
|
33
|
+
```ruby
|
34
|
+
conversation = platform.conversations.find("fb2f3a48-523d-4449-a57f-c6651fc6612c")
|
35
|
+
#<Layer::Resources::Conversation:0x007fdb18b44bf0 @attributes={...}>
|
36
|
+
|
37
|
+
# Get the stripped uuid for any resource
|
38
|
+
conversation.uuid
|
39
|
+
# => "fb2f3a48-523d-4449-a57f-c6651fc6612c"
|
40
|
+
|
41
|
+
conversation.url
|
42
|
+
# => "https://api.layer.com/apps/<APP_ID>/conversations/fb2f3a48-523d-4449-a57f-c6651fc6612c"
|
43
|
+
|
44
|
+
# Retrieve all attributes
|
45
|
+
conversation.attributes
|
46
|
+
# => {"id" => "fb2f3a48-523d-4449-a57f-c6651fc6612c", "url" => "https://api.layer.com/apps/<APP_ID>/conversations/fb2f3a48-523d-4449-a57f-c6651fc6612c", ...}
|
47
|
+
```
|
48
|
+
|
49
|
+
### [Platform API](https://developer.layer.com/docs/platform)
|
50
|
+
See the official [Platform API docs](https://developer.layer.com/docs/platform) for additional info.
|
51
|
+
|
52
|
+
#### Authentication/setup
|
27
53
|
|
28
54
|
```ruby
|
29
|
-
|
30
|
-
|
31
|
-
app_id: "your_app_id"
|
32
|
-
)
|
55
|
+
platform = Layer::Platform::Client.new(api_token: "your_api_token", app_id: "your_app_id")
|
56
|
+
# => #<Layer::Platform::Client:0x007fdb19844f30 @api_token="...", @app_id="...">
|
33
57
|
```
|
34
|
-
If you have `ENV['LAYER_API_TOKEN']` and `ENV['LAYER_APP_ID']` environment variables setup, they will be used by default don't need to be included:
|
58
|
+
If you have `ENV['LAYER_API_TOKEN']` and `ENV['LAYER_APP_ID']` environment variables setup, they will be used by default and don't need to be included:
|
35
59
|
```ruby
|
36
|
-
|
60
|
+
platform = Layer::Platform::Client.new
|
61
|
+
# => #<Layer::Platform::Client:0x007fdb19844f30 @api_token="...", @app_id="...">
|
37
62
|
```
|
38
63
|
|
39
|
-
|
64
|
+
#### Retrieving Conversations ####
|
40
65
|
|
41
66
|
```ruby
|
42
|
-
|
67
|
+
user = platform.users.find("user_id")
|
68
|
+
convs = user.conversations.list
|
69
|
+
# => [#<Layer::Resources::Conversation>, #<Layer::Resources::Conversation>, ...]
|
70
|
+
|
43
71
|
```
|
44
72
|
|
45
|
-
|
73
|
+
#### Retrieving A Single Conversation ####
|
74
|
+
|
75
|
+
```ruby
|
76
|
+
# For a user
|
77
|
+
user = platform.users.find("user_id")
|
78
|
+
conv = user.conversations.find("conversation_id")
|
79
|
+
# => #<Layer::Resources::Conversation:0x007fdb18b44bf0 @attributes={...}>
|
80
|
+
|
81
|
+
# or alternatively
|
82
|
+
conv = platform.conversations.find("conversation_id")
|
83
|
+
# => #<Layer::Resources::Conversation:0x007fdb18b44bf0 @attributes={...}>
|
84
|
+
```
|
85
|
+
|
86
|
+
#### Creating Conversations ####
|
46
87
|
|
47
88
|
```ruby
|
48
|
-
# A sample conversation
|
49
89
|
conversation = {
|
50
90
|
participants: [
|
51
91
|
"1234",
|
@@ -57,24 +97,35 @@ conversation = {
|
|
57
97
|
}
|
58
98
|
}
|
59
99
|
|
60
|
-
|
100
|
+
platform.conversations.create(conversation)
|
101
|
+
# => #<Layer::Resources::Conversation:0x007fdb18b44bf0 @attributes={...}>
|
61
102
|
```
|
62
103
|
|
63
|
-
|
104
|
+
#### Editing Conversations ####
|
64
105
|
|
65
106
|
```ruby
|
66
|
-
|
107
|
+
conv = platform.conversations.find("conversation_id")
|
108
|
+
|
67
109
|
operations = [
|
68
|
-
{operation: "add", property: "participants", value: "user1"},
|
69
|
-
{operation: "add", property: "participants", value: "user2"}
|
110
|
+
{ operation: "add", property: "participants", value: "user1" },
|
111
|
+
{ operation: "add", property: "participants", value: "user2" }
|
70
112
|
]
|
71
113
|
|
72
|
-
|
114
|
+
conv.update(operations)
|
115
|
+
# => nil
|
73
116
|
```
|
117
|
+
#### Deleting Conversations ####
|
118
|
+
|
119
|
+
```ruby
|
120
|
+
conv = platform.conversations.find("conversation_id")
|
121
|
+
conv.destroy
|
122
|
+
# => nil
|
123
|
+
|
124
|
+
```
|
125
|
+
|
126
|
+
#### Sending Messages ####
|
74
127
|
|
75
|
-
### Sending messages
|
76
128
|
```ruby
|
77
|
-
# A sample message to send
|
78
129
|
message = {
|
79
130
|
sender: {
|
80
131
|
name: "t-bone"
|
@@ -96,14 +147,42 @@ message = {
|
|
96
147
|
}
|
97
148
|
}
|
98
149
|
|
99
|
-
|
150
|
+
conv = platform.conversations.find("conversation_id")
|
151
|
+
conv.messages.create(message)
|
152
|
+
# => #<Layer::Resources::Message:0x007fdb18b44bf0 @attributes={...}>
|
153
|
+
```
|
154
|
+
|
155
|
+
#### Retrieving Messages ####
|
156
|
+
|
157
|
+
```ruby
|
158
|
+
# From a specific user's perspective
|
159
|
+
conv = platform.users.find("user_id").conversations.find("conversation_id")
|
160
|
+
conv.messages.list
|
161
|
+
# => [#<Layer::Resources::Message>, #<Layer::Resources::Message>, ...]
|
162
|
+
|
163
|
+
# From the system's perspective
|
164
|
+
conv = platform.conversations.find("conversation_id")
|
165
|
+
conv.messages.list
|
166
|
+
# => [#<Layer::Resources::Message>, #<Layer::Resources::Message>, ...]
|
167
|
+
```
|
168
|
+
|
169
|
+
#### Retrieving A Single Message ####
|
100
170
|
|
171
|
+
```ruby
|
172
|
+
# From a specific user's perspective
|
173
|
+
user = platform.users.find("user_id")
|
174
|
+
messages = user.messages.find("message_id")
|
175
|
+
# => #<Layer::Resources::Message:0x007fdb18b44bf0 @attributes={...}>
|
176
|
+
|
177
|
+
# From the systems perspective
|
178
|
+
conv = platform.conversations.find("conversation_id")
|
179
|
+
messages = conv.messages.find("message_id")
|
180
|
+
# => #<Layer::Resources::Message:0x007fdb18b44bf0 @attributes={...}>
|
101
181
|
```
|
102
182
|
|
103
|
-
|
183
|
+
#### Sending Announcements ####
|
104
184
|
|
105
185
|
```ruby
|
106
|
-
# A sample announcement
|
107
186
|
announcement = {
|
108
187
|
recipients: [ "1234", "5678" ],
|
109
188
|
sender: {
|
@@ -113,11 +192,6 @@ announcement = {
|
|
113
192
|
{
|
114
193
|
body: "Hello, World!",
|
115
194
|
mime_type: "text/plain"
|
116
|
-
},
|
117
|
-
{
|
118
|
-
body: "YW55IGNhcm5hbCBwbGVhc3VyZQ==",
|
119
|
-
mime_type: "image/jpeg",
|
120
|
-
encoding: "base64"
|
121
195
|
}
|
122
196
|
],
|
123
197
|
notification: {
|
@@ -126,23 +200,68 @@ announcement = {
|
|
126
200
|
}
|
127
201
|
}
|
128
202
|
|
129
|
-
|
203
|
+
platform.announcements.create(announcement)
|
204
|
+
# => #<Layer::Resources::Announcement:0x007fdb18b44bf0 @attributes={...}>
|
130
205
|
```
|
131
206
|
|
132
|
-
|
207
|
+
#### Modifying A Users Block List ####
|
133
208
|
|
134
209
|
```ruby
|
135
|
-
|
136
|
-
|
210
|
+
user = platform.users.find("user_id")
|
211
|
+
|
212
|
+
operations = [
|
213
|
+
{ operation: "add", property: "blocks", value: "blockMe1" },
|
214
|
+
{ operation: "add", property: "blocks", value: "blockMe2" },
|
215
|
+
{ operation: "remove", property: "blocks", value: "unblockMe" }
|
216
|
+
]
|
217
|
+
|
218
|
+
user.update(operations)
|
219
|
+
# => nil
|
220
|
+
```
|
137
221
|
|
138
|
-
|
139
|
-
layer.block_user("owner_id", "user_id")
|
222
|
+
#### Retrieving A Users Block List
|
140
223
|
|
141
|
-
|
142
|
-
|
224
|
+
```ruby
|
225
|
+
user = platform.users.find("user_id")
|
226
|
+
|
227
|
+
blocks = user.blocks.list
|
228
|
+
# => [#<Layer::Resources::Block @attributes={...}>, [#<Layer::Resources::Block @attributes={...}>, ...]
|
143
229
|
```
|
144
230
|
|
145
|
-
|
231
|
+
#### Blocking Users
|
232
|
+
|
233
|
+
```ruby
|
234
|
+
# using params
|
235
|
+
owner = platform.users.find("owner")
|
236
|
+
owner.blocks.create(user_id: "blocked")
|
237
|
+
# => #<Layer::Resources::Block @attributes={...}>
|
238
|
+
|
239
|
+
# passing a User object
|
240
|
+
owner = platform.users.find("owner")
|
241
|
+
blocked = platform.users.find("blocked")
|
242
|
+
|
243
|
+
owner.blocks.create(blocked)
|
244
|
+
# => #<Layer::Resources::Block @attributes={...}>
|
245
|
+
```
|
246
|
+
|
247
|
+
#### Unblocking Users
|
248
|
+
|
249
|
+
```ruby
|
250
|
+
|
251
|
+
# using the blocked users id
|
252
|
+
owner = platform.users.find("owner")
|
253
|
+
owner.blocks.find("blocked_user").destroy
|
254
|
+
# => nil
|
255
|
+
|
256
|
+
# using a User object
|
257
|
+
owner = platform.users.find("owner")
|
258
|
+
blocked = platform.users.find("blocked")
|
259
|
+
|
260
|
+
owner.blocks.find(blocked).destroy
|
261
|
+
# => nil
|
262
|
+
```
|
263
|
+
|
264
|
+
#### Generating Identity Tokens ####
|
146
265
|
See: [the official authentication guide](https://developer.layer.com/docs/android/guides#authentication)
|
147
266
|
|
148
267
|
Make sure the following environment variables are set:
|
@@ -151,16 +270,20 @@ Make sure the following environment variables are set:
|
|
151
270
|
`ENV['LAYER_PRIVATE_KEY']`
|
152
271
|
|
153
272
|
```ruby
|
154
|
-
# Returns a valid signed identity token.
|
155
|
-
|
273
|
+
# Returns a valid signed identity token. #
|
274
|
+
token = platform.generate_identity_token(user_id: "1234", nonce: "your_random_nonce")
|
275
|
+
# => #<Layer::IdentityToken:0x007f89b4adb890
|
276
|
+
|
277
|
+
token.to_s
|
278
|
+
# => "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsInR.cCI6IkpXVCIsImN0eSI6ImxheWVyLWVpdDt2PTEiLCJraWQiOiJhNz.5YTE0MC02YzY3LTExZTUtYjM0Mi1jZGJmNDAwZTE5NDgifQ"
|
156
279
|
```
|
157
280
|
|
158
|
-
## Development
|
281
|
+
## Development ##
|
159
282
|
|
160
283
|
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake rspec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
161
284
|
|
162
285
|
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
163
286
|
|
164
|
-
## Contributing
|
287
|
+
## Contributing ##
|
165
288
|
|
166
289
|
Bug reports and pull requests are welcome on GitHub at https://github.com/cakejelly/layer-api.
|