surikat 0.2.2
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 +7 -0
- data/.gitignore +15 -0
- data/.idea/.rakeTasks +7 -0
- data/.idea/inspectionProfiles/Project_Default.xml +16 -0
- data/.idea/misc.xml +7 -0
- data/.idea/modules.xml +8 -0
- data/.idea/surikat.iml +50 -0
- data/.idea/vcs.xml +6 -0
- data/.idea/workspace.xml +744 -0
- data/.rspec +2 -0
- data/.travis.yml +5 -0
- data/Gemfile +6 -0
- data/LICENSE.txt +21 -0
- data/README.md +399 -0
- data/Rakefile +6 -0
- data/bin/console +11 -0
- data/bin/setup +8 -0
- data/exe/surikat +234 -0
- data/lib/surikat.rb +421 -0
- data/lib/surikat/base_model.rb +35 -0
- data/lib/surikat/base_queries.rb +10 -0
- data/lib/surikat/base_type.rb +3 -0
- data/lib/surikat/configurations.rb +22 -0
- data/lib/surikat/new_app.rb +108 -0
- data/lib/surikat/routes.rb +67 -0
- data/lib/surikat/scaffold.rb +503 -0
- data/lib/surikat/session.rb +35 -0
- data/lib/surikat/session_manager.rb +92 -0
- data/lib/surikat/templates/.rspec.tmpl +1 -0
- data/lib/surikat/templates/.standalone_migrations.tmpl +6 -0
- data/lib/surikat/templates/Gemfile.tmpl +31 -0
- data/lib/surikat/templates/Rakefile.tmpl +2 -0
- data/lib/surikat/templates/aaa_queries.rb.tmpl +124 -0
- data/lib/surikat/templates/aaa_spec.rb.tmpl +151 -0
- data/lib/surikat/templates/application.yml.tmpl +14 -0
- data/lib/surikat/templates/base_aaa_model.rb.tmpl +28 -0
- data/lib/surikat/templates/base_model.rb.tmpl +6 -0
- data/lib/surikat/templates/base_spec.rb.tmpl +148 -0
- data/lib/surikat/templates/config.ru.tmpl +61 -0
- data/lib/surikat/templates/console.tmpl +14 -0
- data/lib/surikat/templates/crud_queries.rb.tmpl +105 -0
- data/lib/surikat/templates/database.yml.tmpl +26 -0
- data/lib/surikat/templates/hello_queries.rb.tmpl +19 -0
- data/lib/surikat/templates/hello_spec.rb.tmpl +39 -0
- data/lib/surikat/templates/routes.yml.tmpl +15 -0
- data/lib/surikat/templates/spec_helper.rb.tmpl +11 -0
- data/lib/surikat/templates/test_helper.rb.tmpl +30 -0
- data/lib/surikat/types.rb +45 -0
- data/lib/surikat/version.rb +3 -0
- data/lib/surikat/yaml_configurator.rb +18 -0
- data/surikat.gemspec +47 -0
- metadata +199 -0
data/.rspec
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2018 Alex Deva
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,399 @@
|
|
1
|
+
# Surikat
|
2
|
+
|
3
|
+

|
4
|
+
|
5
|
+
## A backend web framework centred around GraphQL.
|
6
|
+
|
7
|
+
Many frontend apps require little more than a simple backend that does a few CRUD operations
|
8
|
+
and handles user authentication, authorisation and access (AAA).
|
9
|
+
|
10
|
+
For even the simplest backends, frontend developers must invest time and knowledge
|
11
|
+
into some unfamiliar framework, and then code the app -- often in a language that they're not very
|
12
|
+
comfortable in.
|
13
|
+
|
14
|
+
Surikat solves much of that.
|
15
|
+
|
16
|
+
With Surikat, you can have a backend app up and running in under a minute, that does CRUD
|
17
|
+
and AAA, with no code at all.
|
18
|
+
|
19
|
+
Sure, Rails has scaffolding -- but not for AAA, and not for GraphQL. Sure, there are gems for that -- but
|
20
|
+
they have significant learning curves.
|
21
|
+
|
22
|
+
Sure, Rails can make API-only apps -- but only REST API apps. [GraphQL](http://graphql.org), a standard created
|
23
|
+
by Facebook and made open-source since then, is far more efficient than REST. There's only
|
24
|
+
one endpoint, and you get exactly the data what you ask for -- nothing else.
|
25
|
+
|
26
|
+
Sure, Rails can also be taught GraphQL. But that's an add-on to everything else that Rails
|
27
|
+
does; by contrast, Surikat was built, from the ground up, around GraphQL.
|
28
|
+
|
29
|
+
Writing the backend for GraphQL queries, in most existing frameworks, can be tedious and complicated.
|
30
|
+
Surikat organises, simplifies and exemplifies all queries, and it even helps a lot with testing them.
|
31
|
+
You always know how to call a query, even without introspection; Surikat is intuitive, and will always have an example handy.
|
32
|
+
|
33
|
+
### Quick Start
|
34
|
+
|
35
|
+
```bash
|
36
|
+
$ gem install surikat
|
37
|
+
$ surikat new library
|
38
|
+
```
|
39
|
+
|
40
|
+
Once the Surikat app is created, follow the instructions; `cd` into the app directory,
|
41
|
+
run `rspec` for tests, `passenger start` to start a web server, `bin/console` to try
|
42
|
+
stuff out, etc.
|
43
|
+
|
44
|
+
Just type `surikat` to see what the command line tool can do.
|
45
|
+
|
46
|
+
### Slow Start
|
47
|
+
|
48
|
+
Surikat operates with four concepts: *Routes*, *Types*, *Queries* and *Models*.
|
49
|
+
|
50
|
+
#### Models
|
51
|
+
Surikat is not an MVC framework; it lacks the V and the C. But it does use models, and
|
52
|
+
in particular, the ActiveRecord library that Ruby on Rails was initially based on.
|
53
|
+
If you're familiar with modern MVC frameworks, then you'll feel right at home with
|
54
|
+
Surikat models.
|
55
|
+
|
56
|
+
#### Queries
|
57
|
+
With Surikat, Queries are simple Ruby code; you don't have to learn any complicated DSL
|
58
|
+
or try to adapt to someone else's idea of what a GraphQL query definition should look like.
|
59
|
+
|
60
|
+
Each model file has a companion queries file, but you can also write your own queries.
|
61
|
+
By using some simple conventions, and routes (see below), queries can easily be
|
62
|
+
represented as simple methods:
|
63
|
+
|
64
|
+
```ruby
|
65
|
+
class AuthorQueries < Surikat::BaseQueries
|
66
|
+
def get
|
67
|
+
Author.where(id: arguments['id'])
|
68
|
+
end
|
69
|
+
end
|
70
|
+
```
|
71
|
+
|
72
|
+
#### Routes
|
73
|
+
Models and Queries are the only components of Surikat which require a programming language
|
74
|
+
(Ruby). The other half are simple YAML files, which can be edited manually or
|
75
|
+
programmatically. Routes describe the links between GraphQL queries (or mutations), and
|
76
|
+
the queries method.
|
77
|
+
|
78
|
+
For example, the query method above might be routed thus:
|
79
|
+
|
80
|
+
```yaml
|
81
|
+
Author:
|
82
|
+
class: AuthorQueries
|
83
|
+
method: get
|
84
|
+
output_type: Author
|
85
|
+
arguments:
|
86
|
+
id: ID
|
87
|
+
```
|
88
|
+
|
89
|
+
#### Types
|
90
|
+
You'll notice in the route above that it mentions an output_type named `Author`. Just
|
91
|
+
like routes, types live also in YAML files, and they are used to describe the data
|
92
|
+
that goes in the app (input types), and the data that comes out (output types).
|
93
|
+
|
94
|
+
In the example above, the `Author` route calls the `get` method of the `AuthorQueries` class,
|
95
|
+
and it formats its return (an `Author` database record) to match a given type. Case in point:
|
96
|
+
|
97
|
+
```yaml
|
98
|
+
Author:
|
99
|
+
type: Output
|
100
|
+
fields:
|
101
|
+
name: String
|
102
|
+
created_at: String
|
103
|
+
updated_at: String
|
104
|
+
id: ID
|
105
|
+
books: "[Book]"
|
106
|
+
```
|
107
|
+
|
108
|
+
These are all the fields that the frontend would have access to; a `name` of the type `String`,
|
109
|
+
two timestamps which are also automatically cast as `String`, the record database id,
|
110
|
+
and an array of books (which are, in turn, rendered in accordance to their own `Book` output type).
|
111
|
+
|
112
|
+
### Examplifying Queries
|
113
|
+
|
114
|
+
Whenever you have a query, Surikat will tell you how it works, and it will even
|
115
|
+
give you a `curl` command line to test it with:
|
116
|
+
|
117
|
+
```bash
|
118
|
+
$ surikat exemplify AuthorQueries get
|
119
|
+
|
120
|
+
Query:
|
121
|
+
{
|
122
|
+
Author(id: 1) {
|
123
|
+
name
|
124
|
+
created_at
|
125
|
+
updated_at
|
126
|
+
id
|
127
|
+
books {
|
128
|
+
title
|
129
|
+
created_at
|
130
|
+
}
|
131
|
+
}
|
132
|
+
}
|
133
|
+
|
134
|
+
|
135
|
+
curl command:
|
136
|
+
curl 0:3000 -X POST -d 'query=%7B%0A++Author%28id%3A+1%29+%7B%0A++++name%0A++++created_at%0A++++updated_at%0A++++id%0A++++books+%7B%0A++++++title%0A++++++created_at%0A++++%7D%0A++%7D%0A%7D'
|
137
|
+
```
|
138
|
+
|
139
|
+
### Scaffolding
|
140
|
+
|
141
|
+
Surikat comes with a convenient scaffolding tool, which creates a model (with a database migration),
|
142
|
+
a set of queries for it (to Create, Retrieve, Update and Delete), as well as the necessary
|
143
|
+
types, routes and tests.
|
144
|
+
|
145
|
+
Example:
|
146
|
+
|
147
|
+
```bash
|
148
|
+
surikat generate model Book title:string
|
149
|
+
```
|
150
|
+
|
151
|
+
### Custom Data
|
152
|
+
|
153
|
+
Sometimes you need to supply for the frontend things that don't come directly from the database.
|
154
|
+
In fact, you can send anything you want; here are a few simple recipes:
|
155
|
+
|
156
|
+
1 To add an additional field to the ones already provided by the database, the easiest way
|
157
|
+
is to define a method in the model.
|
158
|
+
|
159
|
+
```ruby
|
160
|
+
class Person < Surikat::BaseModel
|
161
|
+
def favourite_number
|
162
|
+
rand(10)
|
163
|
+
end
|
164
|
+
end
|
165
|
+
```
|
166
|
+
|
167
|
+
Then, you can add `favourite_number` into the `Author` output type, and you're set.
|
168
|
+
|
169
|
+
2 If you need this field to have arguments:
|
170
|
+
|
171
|
+
```ruby
|
172
|
+
class Person < Surikat::BaseModel
|
173
|
+
def square(num)
|
174
|
+
num * num
|
175
|
+
end
|
176
|
+
end
|
177
|
+
```
|
178
|
+
|
179
|
+
And in the query:
|
180
|
+
|
181
|
+
```graphql
|
182
|
+
{
|
183
|
+
Person(id: 1) {
|
184
|
+
square(num: 5)
|
185
|
+
}
|
186
|
+
}
|
187
|
+
```
|
188
|
+
|
189
|
+
3 Returning custom types is also easy. If you have an output type that defines the fields
|
190
|
+
`favourite_food` and `favourite_drink`, all your query needs to do is to return a Ruby `Hash`
|
191
|
+
that has those two keys.
|
192
|
+
|
193
|
+
```ruby
|
194
|
+
class MyQueries < Surikat::BaseQueries
|
195
|
+
def favourite_stuff
|
196
|
+
{
|
197
|
+
favourite_food: 'air',
|
198
|
+
favourite_drink: 'water'
|
199
|
+
}
|
200
|
+
end
|
201
|
+
end
|
202
|
+
```
|
203
|
+
|
204
|
+
This works for arrays, too. You can return an array of such objects, and use them
|
205
|
+
in your output types using the brackets notation, for example `[FavouriteStuffType]`.
|
206
|
+
|
207
|
+
#### Errors
|
208
|
+
|
209
|
+
Application errors, type errors or model validation errors are return inside a field named `error`.
|
210
|
+
|
211
|
+
#### Arguments
|
212
|
+
|
213
|
+
In the queries, you always have access to the query arguments via the `arguments` helper:
|
214
|
+
|
215
|
+
```ruby
|
216
|
+
class AuthorQueries < Surikat::BaseQueries
|
217
|
+
def get
|
218
|
+
Author.where(id: arguments['id'])
|
219
|
+
end
|
220
|
+
end
|
221
|
+
```
|
222
|
+
|
223
|
+
### Session
|
224
|
+
|
225
|
+
Session management is easy with Surikat. Simply carry around an HTTP header named 'Surikat' with a value that's
|
226
|
+
as randomly unique as possible. You probably want to generate this value when your frontend app loads, then use it for all
|
227
|
+
Surikat queries. As long as you send the same Surikat header, you'll maintain a session.
|
228
|
+
|
229
|
+
With curl:
|
230
|
+
|
231
|
+
```bash
|
232
|
+
curl 0:3000 -X POST -d 'query=%7B%0AHello%0A%7D' -H 'Surikat: 1234'
|
233
|
+
```
|
234
|
+
|
235
|
+
In the queries, you always have access to the session object via the `session` helper:
|
236
|
+
|
237
|
+
```ruby
|
238
|
+
class AuthorQueries < Surikat::BaseQueries
|
239
|
+
def play_with_session
|
240
|
+
# store something in the session object
|
241
|
+
session[:something] = 'Something'
|
242
|
+
|
243
|
+
# retrieve something from the session object
|
244
|
+
{
|
245
|
+
name: session[:name]
|
246
|
+
}
|
247
|
+
end
|
248
|
+
end
|
249
|
+
```
|
250
|
+
|
251
|
+
#### Session Stores
|
252
|
+
|
253
|
+
The session store is configured in `config/application.yml` and it can either be a file, or Redis.
|
254
|
+
|
255
|
+
The file method is slower, and can it gets slower as the file (which lives in `tmp/`) gets bigger. Also,
|
256
|
+
needless to say, it doesn't work to scale up the app across several machines.
|
257
|
+
|
258
|
+
Redis is much preferred especially in production; remember to add the `redis` gem to Gemfile. To configure it,
|
259
|
+
use the `url` field in the same configuration file; that will be passed to the Redis initialisation method.
|
260
|
+
|
261
|
+
### Authentication, Authorisation and Access
|
262
|
+
|
263
|
+
Surikat comes with triple-A, but it's not enabled by default. Rather, the files must be generated:
|
264
|
+
|
265
|
+
```bash
|
266
|
+
surikat generate aaa
|
267
|
+
```
|
268
|
+
|
269
|
+
This will create a `User` model (plus migration), a class called `AAAQueries` and a suite of tests.
|
270
|
+
|
271
|
+
The model will, by default, have three columns: `email`, `hashed_password` and `roleids`.
|
272
|
+
|
273
|
+
To create a user, use the `password` accessor.
|
274
|
+
|
275
|
+
```ruby
|
276
|
+
$ bin/console
|
277
|
+
|
278
|
+
User.create email:'a@b.com', password:'abc'
|
279
|
+
```
|
280
|
+
|
281
|
+
Surikat will save a SHA256 digest for that password in the database.
|
282
|
+
|
283
|
+
To restrict a query to logged in users, add `permitted_roles: any` to its route.
|
284
|
+
|
285
|
+
To restrict a query to particular user roles (more about roles below), add for example `permitted_roles: admin,superadmin` to its route.
|
286
|
+
|
287
|
+
The AAA queries available to you are described in `app/queries/aaa_queries.rb`, including even query examples.
|
288
|
+
In short, they are:
|
289
|
+
|
290
|
+
* `Authenticate` - you pass the email and password, and you get a boolean value; if the authentication
|
291
|
+
succeeds, then a `user_id` will be stored in the session object, giving you access to the current user.
|
292
|
+
|
293
|
+
* `Logout` - self-explanatory.
|
294
|
+
|
295
|
+
* `CurrentUser` - returns the current user based on what's in `session[:user_id]`.
|
296
|
+
|
297
|
+
* `LoginAs` - allows a superadmin to login as another user (more about superadmins in the Roles section below).
|
298
|
+
During this time, the session will also contain `:superadmin_id`.
|
299
|
+
|
300
|
+
* `BackFrom LoginAs` - having logged in as someone else, return as the initial superadmin.
|
301
|
+
|
302
|
+
* `DemoOne`, `DemoTwo` and `DemoThree` - used by the rspec tests. If you delete them, please also delete the corresponding tests in `spec/aaa_spec.rb`.
|
303
|
+
|
304
|
+
#### Roles
|
305
|
+
|
306
|
+
Roles are simply identifiers stored, for a user, inside the `roleids` attribute, and comma-separated.
|
307
|
+
|
308
|
+
Before a query is executed, the contents of its `permitted_roles` field (from its route) is evaluated.
|
309
|
+
If it's `any` then a user of any role is allowed access. If it's a comma separated array of role identifiers,
|
310
|
+
then access will only be granted if there's an intersection between those roles and the current user's.
|
311
|
+
|
312
|
+
### Application Structure
|
313
|
+
A Surikat app has the following directory structure:
|
314
|
+
```bash
|
315
|
+
├── Gemfile
|
316
|
+
├── Rakefile
|
317
|
+
├── app
|
318
|
+
│ ├── models
|
319
|
+
│ └── queries
|
320
|
+
├── bin
|
321
|
+
│ └── console
|
322
|
+
├── config
|
323
|
+
│ ├── application.yml
|
324
|
+
│ ├── database.yml
|
325
|
+
│ ├── initializers
|
326
|
+
│ ├── routes.yml
|
327
|
+
│ └── types.yml
|
328
|
+
├── config.ru
|
329
|
+
├── db
|
330
|
+
│ ├── migrate
|
331
|
+
├── log
|
332
|
+
├── spec
|
333
|
+
└── tmp
|
334
|
+
```
|
335
|
+
|
336
|
+
* app - models and queries. That's where all the code you need to write will be. (Except for tests.)
|
337
|
+
* bin - just the console binary. Nothing to touch here.
|
338
|
+
* config - contains the database configuration, application configuration, and any initializers.
|
339
|
+
* db - migration files, database stuff.
|
340
|
+
* log - passenger logs
|
341
|
+
* spec - tests
|
342
|
+
* tmp - pid files, temporary stuff.
|
343
|
+
|
344
|
+
### Testing
|
345
|
+
|
346
|
+
All the scaffolds come with running tests; just run `rspec` or, if you'd rather see
|
347
|
+
some details, `rspec -f d`.
|
348
|
+
|
349
|
+
If you change the scaffolding, you need to change the tests, too.
|
350
|
+
|
351
|
+
*Note:* The intention was (and still is) to make autotests fully independent, so that they still test the scaffolded code
|
352
|
+
even after you change it. However, because of field arguments, that's not exactly trivial. Hopefully a later release will
|
353
|
+
come with a solution to this issue. Until then, you have to adapt the tests to your code changes "by hand".
|
354
|
+
|
355
|
+
### Web Server
|
356
|
+
|
357
|
+
Surikat uses (Phusion Passenger)[https://www.phusionpassenger.com/] as a web server. Simply type
|
358
|
+
|
359
|
+
```bash
|
360
|
+
passenger serve
|
361
|
+
```
|
362
|
+
|
363
|
+
to start a server on port 3000. Then you can use GraphiQL, curl or your actual frontend app to start
|
364
|
+
querying the backend.
|
365
|
+
|
366
|
+
#### A Note About Ransack
|
367
|
+
|
368
|
+
Surikat comes with Ransack, so that when you retrieve a collection of ActiveRecord objects, you can
|
369
|
+
already filter and sort them using [Ransack search matchers](https://github.com/activerecord-hackery/ransack#search-matchers).
|
370
|
+
|
371
|
+
Example query:
|
372
|
+
|
373
|
+
```graphql
|
374
|
+
{
|
375
|
+
Authors(q: "is_any_good_eq=false&id_lt=20 ") {
|
376
|
+
id
|
377
|
+
name
|
378
|
+
created_at
|
379
|
+
is_any_good
|
380
|
+
year_of_birth
|
381
|
+
}
|
382
|
+
}
|
383
|
+
```
|
384
|
+
|
385
|
+
## Development
|
386
|
+
|
387
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
388
|
+
|
389
|
+
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).
|
390
|
+
|
391
|
+
## Contributing
|
392
|
+
|
393
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/alxx/surikat.
|
394
|
+
|
395
|
+
## License
|
396
|
+
|
397
|
+
Author: Alex Deva (me@alxx.se)
|
398
|
+
|
399
|
+
The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
|
data/Rakefile
ADDED