apicasso 0.4.6 → 0.4.7
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +17 -4
- data/app/controllers/apicasso/application_controller.rb +6 -1
- data/lib/apicasso/version.rb +1 -1
- data/spec/dummy/app/models/used_model.rb +1 -1
- data/spec/dummy/db/seeds.rb +1 -29
- data/spec/models/used_model_spec.rb +0 -9
- data/spec/requests/requests_spec.rb +67 -23
- metadata +3 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: cf4487870952607136a6a321524093ee1f0e0cd02935710110d4f5e94977b3be
|
4
|
+
data.tar.gz: 17633533e5faad631e27b8d0e3d6c169a86163ff5328da5d0d6cab61aabb88f7
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c036c58d2a4d9f1102cf47ca3d1125bbb5accbb6c69fcb583c82b7891c1801a6d68c55bfad3749e59f870c5608b48a00b1ead41b0c06c8dda60ba101785ea5d1
|
7
|
+
data.tar.gz: 22b512d38ff47b026bc0ea18c3173ce96c6d228e7623e5c2d61116d1450885a93bc19e348f97b0b3cc7f40af749d21c4487735d7996fd0d4bd6be2d991eb0f90
|
data/README.md
CHANGED
@@ -1,9 +1,22 @@
|
|
1
1
|
<img src="https://raw.githubusercontent.com/ErvalhouS/APIcasso/master/APIcasso.png" width="300" /> [![Gem Version](https://badge.fury.io/rb/apicasso.svg)](https://badge.fury.io/rb/apicasso) [![Docs Coverage](https://inch-ci.org/github/autoforce/APIcasso.svg?branch=master)](https://inch-ci.org/github/autoforce/APIcasso.svg?branch=master) [![Maintainability](https://api.codeclimate.com/v1/badges/b58bbd6b9a0376f7cfc8/maintainability)](https://codeclimate.com/github/autoforce/APIcasso/maintainability) [![codecov](https://codecov.io/gh/autoforce/APIcasso/branch/master/graph/badge.svg)](https://codecov.io/gh/autoforce/APIcasso) [![Build Status](https://travis-ci.org/autoforce/APIcasso.svg?branch=master)](https://travis-ci.org/autoforce/APIcasso)
|
2
2
|
|
3
|
-
|
3
|
+
Create APIs in a fast and dynamic way, without the need to develop everything from scratch. You just need to create your models and let **APIcasso** do the rest for you. It is the perfect candidate to make your project development go faster or for legacy Rails projects that do not have an API.
|
4
|
+
|
5
|
+
If you think it through, JSON API development can get boring and time consuming. Every time you use almost the same route structure, pointing to the same controller actions, with the same ordering, filtering and pagination features.
|
6
|
+
|
7
|
+
**APIcasso** is intended to be used to speed-up development, acting as a full-fledged CRUD JSON API into all your models. It is a route-based abstraction that lets you create, read, list, update or delete any `ActiveRecord` object in your application. This makes it possible to make CRUD-only applications just by creating functional Rails' models. Access to your application's resources is managed by a `.scope` JSON object per API key. It uses that permission scope to restrict and extend access.
|
8
|
+
|
9
|
+
You can make your own API with only 4 steps:
|
10
|
+
|
11
|
+
### Step 1
|
12
|
+
Create your models
|
13
|
+
### Step 2
|
14
|
+
Insert **APIcasso** engine into your routes
|
15
|
+
### Step 3
|
16
|
+
[Create an Apicasso::Key](https://github.com/autoforce/APIcasso#authorization)
|
17
|
+
### Step 4
|
18
|
+
Profit! :crown: Consume your REST API
|
4
19
|
|
5
|
-
**APIcasso** is intended to be used as a full-fledged CRUD JSON API or as a base controller to speed-up development.
|
6
|
-
It is a route-based resource abstraction using API key scoping. This makes it possible to make CRUD-only applications just by creating functional Rails' models. It is a perfect candidate for legacy Rails projects that do not have an API. Access to your application's resources is managed by a `.scope` JSON object per API key. It uses that permission scope to restrict and extend access.
|
7
20
|
|
8
21
|
# Installation
|
9
22
|
|
@@ -81,7 +94,7 @@ This way you enjoy all our object finder, authorization and authentication featu
|
|
81
94
|
|
82
95
|
> But exposing my models to the internet is permissive as hell! Haven't you thought about security?
|
83
96
|
|
84
|
-
_Sure!_ The **APIcasso** suite is exposing your application using authentication through `Authorization: Token` [HTTP header authentication](http://tools.ietf.org/html/draft-hammer-http-token-auth-01). The API key objects are manageable through the `Apicasso::Key` model, which gets setup at install. When a new key is created a `.token` is generated using an [Universally Unique Identifier(RFC 4122)](https://tools.ietf.org/html/rfc4122).
|
97
|
+
_Sure!_ The **APIcasso** suite is exposing your application using authentication through `Authorization: Token` [HTTP header authentication](http://tools.ietf.org/html/draft-hammer-http-token-auth-01). The API key objects are manageable through the `Apicasso::Key` model, which gets setup at install. When a new key is created a `.token` is generated using an [Universally Unique Identifier(RFC 4122)](https://tools.ietf.org/html/rfc4122). An authenticated request looks like this:
|
85
98
|
|
86
99
|
```
|
87
100
|
curl -X GET \
|
@@ -154,13 +154,18 @@ module Apicasso
|
|
154
154
|
# @TODO
|
155
155
|
# Remove this in favor of a more controllable aproach of CORS
|
156
156
|
def set_access_control_headers
|
157
|
-
response.headers['Access-Control-Allow-Origin'] =
|
157
|
+
response.headers['Access-Control-Allow-Origin'] = allow_origin
|
158
158
|
response.headers['Access-Control-Allow-Methods'] = 'POST, GET, PUT, PATCH, DELETE, OPTIONS'
|
159
159
|
response.headers['Access-Control-Allow-Credentials'] = 'true'
|
160
160
|
response.headers['Access-Control-Allow-Headers'] = 'Origin, Content-Type, Accept, Authorization, Token, Auth-Token, Email, X-User-Token, X-User-Email'
|
161
161
|
response.headers['Access-Control-Max-Age'] = '1728000'
|
162
162
|
end
|
163
163
|
|
164
|
+
# A method to allow origin customizing through method overriding
|
165
|
+
def allow_origin
|
166
|
+
request.headers['Referer'] || request.headers['Origin'] || '*'
|
167
|
+
end
|
168
|
+
|
164
169
|
# Checks if current request is a CORS preflight check
|
165
170
|
def preflight?
|
166
171
|
request.request_method == 'OPTIONS' &&
|
data/lib/apicasso/version.rb
CHANGED
data/spec/dummy/db/seeds.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
require 'faker'
|
2
2
|
|
3
|
-
|
3
|
+
30.times { UsedModel.create(
|
4
4
|
active: Faker::Boolean.boolean,
|
5
5
|
account_id: Faker::Number.number(1),
|
6
6
|
unit_id: Faker::Number.number(1),
|
@@ -25,32 +25,4 @@ require 'faker'
|
|
25
25
|
fuel: Faker::Number.number(1),
|
26
26
|
fuel_text: Faker::Vehicle.fuel_type,
|
27
27
|
shielded: Faker::Boolean.boolean
|
28
|
-
)}
|
29
|
-
|
30
|
-
1.times { UsedModel.create(
|
31
|
-
active: Faker::Boolean.boolean,
|
32
|
-
account_id: Faker::Number.number(1),
|
33
|
-
unit_id: Faker::Number.number(1),
|
34
|
-
slug: 'cr-v',
|
35
|
-
brand: Faker::Vehicle.make,
|
36
|
-
name: Faker::Vehicle.model,
|
37
|
-
model: Faker::Vehicle.model,
|
38
|
-
version: Faker::Number.decimal(1, 1),
|
39
|
-
model_year: Faker::Vehicle.year,
|
40
|
-
production_year: Faker::Vehicle.year,
|
41
|
-
kind: 'car',
|
42
|
-
new_vehicle: Faker::Boolean.boolean,
|
43
|
-
old_price: Faker::Number.decimal(4, 2).to_s,
|
44
|
-
price_value: Faker::Number.decimal(4, 2),
|
45
|
-
price: Faker::Number.decimal(4, 2).to_s,
|
46
|
-
category: Faker::Vehicle.car_type,
|
47
|
-
transmission: Faker::Vehicle.transmission,
|
48
|
-
km_value: Faker::Number.number(8),
|
49
|
-
km: Faker::Number.number(8).to_s,
|
50
|
-
plate: Faker::Number.number(4),
|
51
|
-
color: Faker::Vehicle.color,
|
52
|
-
doors: Faker::Number.number(1),
|
53
|
-
fuel: Faker::Number.number(1),
|
54
|
-
fuel_text: Faker::Vehicle.fuel_type,
|
55
|
-
shielded: Faker::Boolean.boolean
|
56
28
|
)}
|
@@ -7,15 +7,6 @@ RSpec.describe UsedModel, type: :model do
|
|
7
7
|
it 'is valid with valid attributes' do
|
8
8
|
expect(build :used_model).to be_valid
|
9
9
|
end
|
10
|
-
it 'is invalid with slug duplicate' do
|
11
|
-
used_model = build(:used_model)
|
12
|
-
used_model.slug = 'duplicate'
|
13
|
-
used_model.save
|
14
|
-
|
15
|
-
used_model_dup = build(:used_model)
|
16
|
-
used_model_dup.slug = 'duplicate'
|
17
|
-
expect { used_model_dup.save! }.to raise_exception ActiveRecord::RecordInvalid
|
18
|
-
end
|
19
10
|
it 'is invalid without valid slug attribute' do
|
20
11
|
used_model = build(:used_model)
|
21
12
|
used_model.slug = ''
|
@@ -5,6 +5,7 @@ require 'rails_helper'
|
|
5
5
|
RSpec.describe 'Used Model requests', type: :request do
|
6
6
|
token = Apicasso::Key.create(scope: { manage: { used_model: true } }).token
|
7
7
|
access_token = { 'AUTHORIZATION' => "Token token=#{token}" }
|
8
|
+
|
8
9
|
describe 'GET /api/v1/used_model' do
|
9
10
|
context 'with default pagination' do
|
10
11
|
before(:all) do
|
@@ -14,8 +15,12 @@ RSpec.describe 'Used Model requests', type: :request do
|
|
14
15
|
it 'returns status ok' do
|
15
16
|
expect(response).to have_http_status(:ok)
|
16
17
|
end
|
17
|
-
it 'returns all
|
18
|
-
|
18
|
+
it 'returns UsedModel records equal to WillPaginate.per_page or all UsedModels' do
|
19
|
+
if JSON.parse(response.body)['last_page'] == false
|
20
|
+
expect(JSON.parse(response.body)['entries'].size).to eq(WillPaginate.per_page)
|
21
|
+
else
|
22
|
+
expect(JSON.parse(response.body)['entries'].size).to eq(UsedModel.count)
|
23
|
+
end
|
19
24
|
end
|
20
25
|
end
|
21
26
|
|
@@ -33,22 +38,26 @@ RSpec.describe 'Used Model requests', type: :request do
|
|
33
38
|
end
|
34
39
|
|
35
40
|
context 'with pagination' do
|
41
|
+
per_page = (1..UsedModel.count+1).to_a.sample
|
42
|
+
page = (1..5).to_a.sample
|
43
|
+
|
36
44
|
before(:all) do
|
37
|
-
get '/api/v1/used_model', params: { per_page:
|
45
|
+
get '/api/v1/used_model', params: { per_page: per_page, page: page }, headers: access_token
|
38
46
|
end
|
39
47
|
|
40
48
|
it 'returns status ok' do
|
41
49
|
expect(response).to have_http_status(:ok)
|
42
50
|
end
|
43
51
|
|
44
|
-
it 'returns
|
45
|
-
expect(JSON.parse(response.body)['entries'].size).to be(
|
52
|
+
it 'returns size of records from UsedModel if not last page' do
|
53
|
+
expect(JSON.parse(response.body)['entries'].size).to be(per_page) if JSON.parse(response.body)['last_page'] == false
|
46
54
|
end
|
47
55
|
end
|
48
56
|
|
49
57
|
context 'by searching' do
|
58
|
+
brand_to_search = UsedModel.all.sample.brand
|
50
59
|
before(:all) do
|
51
|
-
get '/api/v1/used_model', params: { 'q[brand_matches]':
|
60
|
+
get '/api/v1/used_model', params: { 'q[brand_matches]': brand_to_search }, headers: access_token
|
52
61
|
end
|
53
62
|
|
54
63
|
it 'returns status ok' do
|
@@ -57,7 +66,7 @@ RSpec.describe 'Used Model requests', type: :request do
|
|
57
66
|
|
58
67
|
it 'returns all records with brand queried' do
|
59
68
|
JSON.parse(response.body)['entries'].each do |record|
|
60
|
-
expect(record['brand']).to eq(
|
69
|
+
expect(record['brand']).to eq(brand_to_search)
|
61
70
|
end
|
62
71
|
end
|
63
72
|
end
|
@@ -93,8 +102,9 @@ RSpec.describe 'Used Model requests', type: :request do
|
|
93
102
|
end
|
94
103
|
|
95
104
|
context 'with field selecting' do
|
105
|
+
field_select = UsedModel.column_names.sample
|
96
106
|
before(:all) do
|
97
|
-
get '/api/v1/used_model', params: { 'select':
|
107
|
+
get '/api/v1/used_model', params: { 'select': field_select }, headers: access_token
|
98
108
|
end
|
99
109
|
|
100
110
|
it 'returns status ok' do
|
@@ -103,7 +113,7 @@ RSpec.describe 'Used Model requests', type: :request do
|
|
103
113
|
|
104
114
|
it 'returns all records that have field queried' do
|
105
115
|
JSON.parse(response.body)['entries'].each do |record|
|
106
|
-
expect(record.keys).to include(
|
116
|
+
expect(record.keys).to include(field_select)
|
107
117
|
end
|
108
118
|
end
|
109
119
|
end
|
@@ -142,8 +152,9 @@ RSpec.describe 'Used Model requests', type: :request do
|
|
142
152
|
end
|
143
153
|
|
144
154
|
describe 'GET /api/v1/used_model/:id' do
|
155
|
+
id_to_get_id = UsedModel.all.sample.id.to_s
|
145
156
|
before(:all) do
|
146
|
-
get '/api/v1/used_model/
|
157
|
+
get '/api/v1/used_model/' + id_to_get_id, headers: access_token
|
147
158
|
end
|
148
159
|
|
149
160
|
it 'returns status ok' do
|
@@ -153,11 +164,16 @@ RSpec.describe 'Used Model requests', type: :request do
|
|
153
164
|
it 'returns a record with attributes' do
|
154
165
|
expect(JSON.parse(response.body).keys).to include('id', 'active', 'account_id', 'unit_id', 'brand', 'name', 'slug', 'model', 'version', 'model_year', 'production_year', 'kind', 'new_vehicle', 'old_price', 'price_value', 'price', 'category', 'transmission', 'km_value', 'km', 'plate', 'color', 'doors', 'fuel', 'fuel_text', 'note', 'chassis', 'shielded', 'featured', 'integrator', 'ordination', 'visits', 'bait_id', 'fipe_id', 'identifier', 'synced_at', 'deleted_at', 'created_at', 'updated_at')
|
155
166
|
end
|
167
|
+
|
168
|
+
it 'return matches with object searched' do
|
169
|
+
expect(UsedModel.find(id_to_get_id.to_i).attributes.to_json).to eq(response.body)
|
170
|
+
end
|
156
171
|
end
|
157
172
|
|
158
173
|
describe 'GET /api/v1/used_model/:slug' do
|
174
|
+
id_to_get_slug = UsedModel.all.sample.slug.to_s
|
159
175
|
before(:all) do
|
160
|
-
get '/api/v1/used_model/
|
176
|
+
get '/api/v1/used_model/' + id_to_get_slug, headers: access_token
|
161
177
|
end
|
162
178
|
|
163
179
|
it 'returns status ok' do
|
@@ -167,17 +183,32 @@ RSpec.describe 'Used Model requests', type: :request do
|
|
167
183
|
it 'returns a record with attributes' do
|
168
184
|
expect(JSON.parse(response.body).keys).to include('id', 'active', 'account_id', 'unit_id', 'brand', 'name', 'slug', 'model', 'version', 'model_year', 'production_year', 'kind', 'new_vehicle', 'old_price', 'price_value', 'price', 'category', 'transmission', 'km_value', 'km', 'plate', 'color', 'doors', 'fuel', 'fuel_text', 'note', 'chassis', 'shielded', 'featured', 'integrator', 'ordination', 'visits', 'bait_id', 'fipe_id', 'identifier', 'synced_at', 'deleted_at', 'created_at', 'updated_at')
|
169
185
|
end
|
186
|
+
|
187
|
+
it 'return matches with object searched' do
|
188
|
+
expect(UsedModel.friendly.find(id_to_get_slug).attributes.to_json).to eq(response.body)
|
189
|
+
end
|
170
190
|
end
|
171
191
|
|
172
192
|
describe 'POST /api/v1/used_model/' do
|
193
|
+
slug_to_post = Faker::Lorem.word
|
194
|
+
|
173
195
|
context 'with valid params' do
|
174
196
|
before(:all) do
|
175
|
-
|
197
|
+
@quantity = UsedModel.all.size
|
198
|
+
post '/api/v1/used_model/', params: { 'used_model': { 'name': 'test', 'account_id': 1, 'unit_id': 1, 'slug': slug_to_post, 'brand': 'BMW' }}, headers: access_token
|
176
199
|
end
|
177
200
|
|
178
201
|
it 'returns status created' do
|
179
202
|
expect(response).to have_http_status(:created)
|
180
203
|
end
|
204
|
+
|
205
|
+
it 'check records size into db' do
|
206
|
+
expect(UsedModel.all.size).to eq(@quantity + 1)
|
207
|
+
end
|
208
|
+
|
209
|
+
it 'find record into db' do
|
210
|
+
expect(UsedModel.find_by(slug: slug_to_post)).not_to eq nil
|
211
|
+
end
|
181
212
|
end
|
182
213
|
|
183
214
|
context 'with invalid params' do
|
@@ -189,9 +220,13 @@ RSpec.describe 'Used Model requests', type: :request do
|
|
189
220
|
end
|
190
221
|
|
191
222
|
describe 'PUT /api/v1/used_model/:id' do
|
223
|
+
id_to_put = UsedModel.all.sample.id.to_s
|
224
|
+
name_to_put = Faker::Lorem.word
|
225
|
+
slug_to_put = UsedModel.all.sample.slug
|
226
|
+
|
192
227
|
context 'with valid params' do
|
193
228
|
before(:all) do
|
194
|
-
patch '/api/v1/used_model/' +
|
229
|
+
patch '/api/v1/used_model/' + id_to_put, params: { 'used_model': { 'name': name_to_put }}, headers: access_token
|
195
230
|
end
|
196
231
|
|
197
232
|
it 'returns status ok' do
|
@@ -199,29 +234,38 @@ RSpec.describe 'Used Model requests', type: :request do
|
|
199
234
|
end
|
200
235
|
|
201
236
|
it 'updates requested record' do
|
202
|
-
expect(UsedModel.
|
237
|
+
expect(UsedModel.find(id_to_put).name).to eq(name_to_put)
|
203
238
|
end
|
204
239
|
end
|
205
240
|
|
206
241
|
context 'with invalid params' do
|
207
242
|
it 'return a error' do
|
208
|
-
patch '/api/v1/used_model/' +
|
243
|
+
patch '/api/v1/used_model/' + id_to_put, params: { 'used_model': { 'unit_id': nil }}, headers: access_token
|
209
244
|
expect(response).to have_http_status(:unprocessable_entity)
|
210
245
|
end
|
211
246
|
end
|
212
247
|
end
|
213
248
|
|
214
249
|
describe 'DELETE /api/v1/used_model/:id' do
|
215
|
-
|
216
|
-
delete '/api/v1/used_model/' + UsedModel.last.id.to_s, headers: access_token
|
217
|
-
end
|
250
|
+
id_to_del = UsedModel.all.sample.id.to_s
|
218
251
|
|
219
|
-
|
220
|
-
|
221
|
-
|
252
|
+
context 'with valid params' do
|
253
|
+
before(:all) do
|
254
|
+
@quantity = UsedModel.all.size
|
255
|
+
delete '/api/v1/used_model/' + id_to_del, headers: access_token
|
256
|
+
end
|
257
|
+
|
258
|
+
it 'returns status no content' do
|
259
|
+
expect(response).to have_http_status(:no_content)
|
260
|
+
end
|
222
261
|
|
223
|
-
|
224
|
-
|
262
|
+
it 'detete a UsedModel record' do
|
263
|
+
expect(UsedModel.all.size).to eq(@quantity - 1)
|
264
|
+
end
|
265
|
+
|
266
|
+
it 'check if record was deleted' do
|
267
|
+
expect{ UsedModel.find(id_to_del.to_i) }.to raise_exception(ActiveRecord::RecordNotFound)
|
268
|
+
end
|
225
269
|
end
|
226
270
|
end
|
227
271
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: apicasso
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.4.
|
4
|
+
version: 0.4.7
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Fernando Bellincanta
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2018-11-
|
11
|
+
date: 2018-11-12 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -307,7 +307,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
307
307
|
version: '0'
|
308
308
|
requirements: []
|
309
309
|
rubyforge_project:
|
310
|
-
rubygems_version: 2.7.
|
310
|
+
rubygems_version: 2.7.5
|
311
311
|
signing_key:
|
312
312
|
specification_version: 4
|
313
313
|
summary: An abstract API design as a mountable engine
|