surikat 0.2.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (52) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +15 -0
  3. data/.idea/.rakeTasks +7 -0
  4. data/.idea/inspectionProfiles/Project_Default.xml +16 -0
  5. data/.idea/misc.xml +7 -0
  6. data/.idea/modules.xml +8 -0
  7. data/.idea/surikat.iml +50 -0
  8. data/.idea/vcs.xml +6 -0
  9. data/.idea/workspace.xml +744 -0
  10. data/.rspec +2 -0
  11. data/.travis.yml +5 -0
  12. data/Gemfile +6 -0
  13. data/LICENSE.txt +21 -0
  14. data/README.md +399 -0
  15. data/Rakefile +6 -0
  16. data/bin/console +11 -0
  17. data/bin/setup +8 -0
  18. data/exe/surikat +234 -0
  19. data/lib/surikat.rb +421 -0
  20. data/lib/surikat/base_model.rb +35 -0
  21. data/lib/surikat/base_queries.rb +10 -0
  22. data/lib/surikat/base_type.rb +3 -0
  23. data/lib/surikat/configurations.rb +22 -0
  24. data/lib/surikat/new_app.rb +108 -0
  25. data/lib/surikat/routes.rb +67 -0
  26. data/lib/surikat/scaffold.rb +503 -0
  27. data/lib/surikat/session.rb +35 -0
  28. data/lib/surikat/session_manager.rb +92 -0
  29. data/lib/surikat/templates/.rspec.tmpl +1 -0
  30. data/lib/surikat/templates/.standalone_migrations.tmpl +6 -0
  31. data/lib/surikat/templates/Gemfile.tmpl +31 -0
  32. data/lib/surikat/templates/Rakefile.tmpl +2 -0
  33. data/lib/surikat/templates/aaa_queries.rb.tmpl +124 -0
  34. data/lib/surikat/templates/aaa_spec.rb.tmpl +151 -0
  35. data/lib/surikat/templates/application.yml.tmpl +14 -0
  36. data/lib/surikat/templates/base_aaa_model.rb.tmpl +28 -0
  37. data/lib/surikat/templates/base_model.rb.tmpl +6 -0
  38. data/lib/surikat/templates/base_spec.rb.tmpl +148 -0
  39. data/lib/surikat/templates/config.ru.tmpl +61 -0
  40. data/lib/surikat/templates/console.tmpl +14 -0
  41. data/lib/surikat/templates/crud_queries.rb.tmpl +105 -0
  42. data/lib/surikat/templates/database.yml.tmpl +26 -0
  43. data/lib/surikat/templates/hello_queries.rb.tmpl +19 -0
  44. data/lib/surikat/templates/hello_spec.rb.tmpl +39 -0
  45. data/lib/surikat/templates/routes.yml.tmpl +15 -0
  46. data/lib/surikat/templates/spec_helper.rb.tmpl +11 -0
  47. data/lib/surikat/templates/test_helper.rb.tmpl +30 -0
  48. data/lib/surikat/types.rb +45 -0
  49. data/lib/surikat/version.rb +3 -0
  50. data/lib/surikat/yaml_configurator.rb +18 -0
  51. data/surikat.gemspec +47 -0
  52. metadata +199 -0
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --format documentation
2
+ --color
@@ -0,0 +1,5 @@
1
+ sudo: false
2
+ language: ruby
3
+ rvm:
4
+ - 2.4.1
5
+ before_install: gem install bundler -v 1.15.4
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ source "https://rubygems.org"
2
+
3
+ git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }
4
+
5
+ # Specify your gem's dependencies in surikat.gemspec
6
+ gemspec
@@ -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.
@@ -0,0 +1,399 @@
1
+ # Surikat
2
+
3
+ ![Surikat](https://i.imgur.com/OlCUw38.png)
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).
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
@@ -0,0 +1,11 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "surikat"
5
+
6
+ # (If you use this, don't forget to add pry to your Gemfile!)
7
+ require "pry"
8
+ Pry.start
9
+
10
+ # require "irb"
11
+ # IRB.start(__FILE__)