next_on_rails 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (53) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +591 -0
  4. data/Rakefile +32 -0
  5. data/app/controllers/next_on_rails/application_controller.rb +21 -0
  6. data/app/controllers/next_on_rails/devise_token_auth_override/concerns/current_resource_serializer.rb +15 -0
  7. data/app/controllers/next_on_rails/devise_token_auth_override/confirmations_controller.rb +4 -0
  8. data/app/controllers/next_on_rails/devise_token_auth_override/omniauth_callbacks_controller.rb +7 -0
  9. data/app/controllers/next_on_rails/devise_token_auth_override/passwords_controller.rb +23 -0
  10. data/app/controllers/next_on_rails/devise_token_auth_override/registrations_controller.rb +33 -0
  11. data/app/controllers/next_on_rails/devise_token_auth_override/sessions_controller.rb +27 -0
  12. data/app/controllers/next_on_rails/devise_token_auth_override/token_validations_controller.rb +13 -0
  13. data/app/controllers/next_on_rails/devise_token_auth_override/unlocks_controller.rb +23 -0
  14. data/app/controllers/next_on_rails/merger_controller.rb +36 -0
  15. data/app/serializers/next_on_rails/active_model_errors_serializer.rb +45 -0
  16. data/app/serializers/next_on_rails/errors_serializer.rb +16 -0
  17. data/config/routes.rb +12 -0
  18. data/lib/generators/nor/backend/backend_generator.rb +129 -0
  19. data/lib/generators/nor/backend/templates/Procfile +2 -0
  20. data/lib/generators/nor/backend/templates/application_controller.rb +2 -0
  21. data/lib/generators/nor/backend/templates/cors.rb +23 -0
  22. data/lib/generators/nor/backend/templates/current_user_serializer.rb.tt +8 -0
  23. data/lib/generators/nor/backend/templates/user.rb.tt +8 -0
  24. data/lib/generators/nor/backend/templates/user_migration.rb.tt +53 -0
  25. data/lib/generators/nor/backend/templates/user_serializer.rb.tt +11 -0
  26. data/lib/generators/nor/frontend/frontend_generator.rb +60 -0
  27. data/lib/generators/nor/frontend/templates/frontend/components/flash.js +29 -0
  28. data/lib/generators/nor/frontend/templates/frontend/components/inputs.js +69 -0
  29. data/lib/generators/nor/frontend/templates/frontend/components/login-form.js +23 -0
  30. data/lib/generators/nor/frontend/templates/frontend/components/registration-form.js +49 -0
  31. data/lib/generators/nor/frontend/templates/frontend/next-on-rails.config.js +3 -0
  32. data/lib/generators/nor/frontend/templates/frontend/next.config.js +20 -0
  33. data/lib/generators/nor/frontend/templates/frontend/pages/_app.js +3 -0
  34. data/lib/generators/nor/frontend/templates/frontend/pages/index.js +57 -0
  35. data/lib/generators/nor/frontend/templates/frontend/server.js +31 -0
  36. data/lib/generators/nor/frontend/templates/frontend/stylesheets/application.scss +2 -0
  37. data/lib/generators/nor/install/USAGE +30 -0
  38. data/lib/generators/nor/install/install_generator.rb +8 -0
  39. data/lib/generators/nor/scaffold/USAGE +10 -0
  40. data/lib/generators/nor/scaffold/scaffold_generator.rb +53 -0
  41. data/lib/generators/nor/scaffold/templates/controller.rb.tt +40 -0
  42. data/lib/generators/nor/scaffold/templates/frontend/components/details.js.tt +20 -0
  43. data/lib/generators/nor/scaffold/templates/frontend/components/form.js.tt +17 -0
  44. data/lib/generators/nor/scaffold/templates/frontend/pages/crud.js.tt +123 -0
  45. data/lib/generators/nor/scaffold/templates/frontend/pages/edit.js.tt +55 -0
  46. data/lib/generators/nor/scaffold/templates/frontend/pages/index.js.tt +88 -0
  47. data/lib/generators/nor/scaffold/templates/frontend/pages/new.js.tt +51 -0
  48. data/lib/generators/nor/scaffold/templates/frontend/pages/show.js.tt +43 -0
  49. data/lib/next_on_rails.rb +12 -0
  50. data/lib/next_on_rails/engine.rb +5 -0
  51. data/lib/next_on_rails/version.rb +3 -0
  52. data/lib/tasks/next_on_rails_tasks.rake +4 -0
  53. metadata +235 -0
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 20645c6f6a59e3294c584a02c801b53efd4711b66b5b28e431bde613e6209e24
4
+ data.tar.gz: f42e5e7d240bbf70d949b7e99b4ee3e761f7ff16a17ad4fc76bb10c3928a7bf3
5
+ SHA512:
6
+ metadata.gz: 91001cd6fa66e07fb965caaf96e232ff678d20df93b2d19ab418ba000d7d36c5f91bea06d1a7745e69d347df31722d3ec059a9406c883f6cbe5475e404c5890b
7
+ data.tar.gz: 671d41b98f9ed825ed29b81e639b4c6909ff87af9fdd4c241e06afdf95c20914f990f4e1fb22b760ec39b0efa48afb40a659a0ad42ae74a92dbd5d52e94f3370
@@ -0,0 +1,20 @@
1
+ Copyright 2019 Enrico
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,591 @@
1
+ # Next On Rails
2
+
3
+ Next On Rails (aka NOR) is a Ruby gem and a NPM package that help developers
4
+ connecting [Rails](https://rubyonrails.org/) --api backend app to
5
+ [Next.js](https://nextjs.org/) frontend app.
6
+
7
+ The Ruby gem setup Rails api app to serve json in the [jsonapi
8
+ format](https://jsonapi.org/) using the awesome Netflix [fast
9
+ jsonapi](https://github.com/Netflix/fast_jsonapi) gem and configure
10
+ [Devise](https://github.com/plataformatec/devise) for you with [Devise Token
11
+ Auth](https://github.com/lynndylanhurley/devise_token_auth/tree/master/lib/generators/devise_token_auth)
12
+ for authentication.
13
+
14
+ The NPM package provides a set of [React
15
+ hooks](https://reactjs.org/docs/hooks-intro.html) and utility to interact with
16
+ backend. Basically it allows developers to:
17
+
18
+ - fetch data with [Next.js
19
+ `getInitialProps`](https://github.com/zeit/next.js/#fetching-data-and-component-lifecycle)
20
+ async function in an easy way
21
+ - handle Rails resources as React states easily with hooks
22
+ ([CRUD](https://en.wikipedia.org/wiki/Create,_read,_update_and_delete))
23
+
24
+ The NPM package also provides a `requester` object that performs HTTP requests
25
+ to Rails backend taking care of Devise Token Auth authentication token
26
+ management.
27
+
28
+ ## Installation
29
+
30
+ Create a fresh Rails --api app:
31
+
32
+ rails new YOUR-APP --api
33
+
34
+ Add in the Gemfile Next On Rails
35
+
36
+ gem 'next_on_rails'
37
+ bundle
38
+
39
+ Install
40
+
41
+ rails g nor:install
42
+
43
+ This generator setup your backend and generate a new frontend app in Next.js
44
+ inside the `./frontend` directory.
45
+
46
+ **What's appened?**
47
+
48
+ 1. Generation of the `User` model for authentication with Devise
49
+ 2. Add `UserSerializer` in `app/serializers/user_serializer.rb` and `CurrentUserSerializer` in `app/serializers/current_user_serializer.rb`
50
+ 3. Add Devise config file in `config/initializers/devise.rb`
51
+ 4. Add Devise Token Auth config file in `config/initializers/devise_token_auth.rb`
52
+ 5. Add CORS configuration in `config/initializers/cors.rb`
53
+ 6. Add Devise routes in `config/routes.rb`
54
+ 7. Install a new `ApplicationController` that extends from `NextOnRails::ApplicationController`
55
+ 8. Add [Foreman gem](https://github.com/ddollar/foreman) gem and `Procfile`
56
+ 9. Setup [`letter_opener` gem](https://github.com/ryanb/letter_opener) for development environment
57
+ 10. Generate a Next.js frontend app in `./frontend` directory
58
+
59
+ Now you are ready to use Next On Rails! Let's generate something:
60
+
61
+ rails g nor:scaffold post title body:text public:boolean
62
+ rails db:migrate
63
+
64
+ And run your backend and fronend with `foreman start`. Rails backend runs on
65
+ port 3000, while Next.js frontend runs on port 3001 (with
66
+ [express.js](https://github.com/expressjs/express))
67
+
68
+ Now visit [http://localhost:3001](http://localhost:3001). You should see the
69
+ homepage with login and registration form. You can also visit the post CRUD
70
+ pages just generated at
71
+ [http://localhost:3001/posts](http://localhost:3001/posts).
72
+
73
+ ## The Frontend App
74
+
75
+ The frontend app is a normal Next.js app with some enhancements:
76
+
77
+ - Configured to use [SASS](https://sass-lang.com/) (see `./frontend/next.config.js`)
78
+ - [Bootstrap](https://getbootstrap.com/) ready
79
+ - The `App` React component is not the default `import App from 'next/app'` but `import App from 'next-on-rails/app'` (some High Order Components have been applied)
80
+ - In the `./frontend/components` there are some usefull React components:
81
+ - `<Flash/>` (for display flash messages)
82
+ - `<LoginForm/>`
83
+ - `<RegistrationForm/>`
84
+ - `<Input/>`, `<CheckBox/>`, `<Select/>`, `<TextArea/>` forms
85
+
86
+ # next-on-rails NPM package
87
+
88
+ Let's say that we have the resources _posts_, in our backend we have the model
89
+ `./app/models/post.rb` and the controller
90
+ `./app/controllers/posts_controller.rb` with the usual crud actions _index_,
91
+ _show_, _create_, _update_ and _destroy_. Now lets see how we can handle this
92
+ resources from the Next.js frontend app using the next-on-rails NPM package.
93
+
94
+ First of all we need to create a `/posts` page with the list of the posts. So
95
+ we add the file `./frontend/pages/posts/index.js`:
96
+
97
+ const PostsIndex = props => {
98
+ return (
99
+ <div>
100
+ <h1>Posts</h1>
101
+ <!-- content here -->
102
+ </div>
103
+ )
104
+ }
105
+
106
+ export default PostsIndex
107
+
108
+ We have just created a simple React functional component. This component will
109
+ render the entire `/posts` HTML page. Now it would be nice if the component
110
+ was initialized with a property `posts`, an array with the posts to display in
111
+ this page. We need to request the posts to the Rails backend at
112
+ `http://localhost:3000/posts.json` (standard
113
+ [restful](https://en.wikipedia.org/wiki/Representational_state_transfer)
114
+ route).
115
+
116
+ ### getInitialResources
117
+
118
+ Next On Rails can help us with the function `getInitialResources` that returns
119
+ an async function that returns the object
120
+ `{ <resourcesName>: arrayOfResources }`
121
+ that is the `getInitialProps` function that we need!! So we can change our
122
+ component:
123
+
124
+ import { getInitialResources } from 'next-on-rails/resources'
125
+
126
+ const PostsIndex = props => {
127
+ return (
128
+ <div>
129
+ <h1>Posts</h1>
130
+ <ul>
131
+ {props.posts.map(post => <li key={post.id}>{post.title}</li>)}
132
+ </ul>
133
+ </div>
134
+ )
135
+ }
136
+
137
+ PostsIndex.getInitialProps = getInitialResources('posts')
138
+
139
+ export default PostsIndex
140
+
141
+ Now the React component will be initialized with the property `posts` that is
142
+ the array with all posts returned by the Rails backend `posts#index` endpoint.
143
+
144
+ So `getInitialResources` call the index action of the controller associated at
145
+ the resources passed as first parameter (`'posts'` in this example). It has
146
+ also an optional second parameter: the request parameters used to make the
147
+ request to the backend.
148
+
149
+ ### getInitialResource
150
+
151
+ We can do the same for the single post page. We need to create a `/posts/:id`
152
+ page. So we add the file `./frontend/pages/posts/show.js`. In this case we
153
+ have to add the route to _express_ (in `./frontend/server.js` file) cause the
154
+ route `/posts/:id` is [dynamic](https://github.com/zeit/next.js#with-link):
155
+
156
+ server.get('/posts/:id(\\d+)', (req, res) => {
157
+ app.render(req, res, '/posts/show', { id: req.params.id })
158
+ })
159
+
160
+ The React component for this page will be:
161
+
162
+ import { getInitialResource } from 'next-on-rails/resources'
163
+
164
+ const PostsShow = props => {
165
+ return (
166
+ <div>
167
+ <h1>Post #{props.post.id}</h1>
168
+ <h2>{props.post.title}</h2>
169
+ <p>
170
+ {props.post.body}
171
+ </p>
172
+ </div>
173
+ )
174
+ }
175
+
176
+ PostsShow.getInitialProps = getInitialResource('post')
177
+
178
+ export default PostsShow
179
+
180
+ In this case we used the function `getInitialResource` that is similar to
181
+ `getInitialResources` but it calls the endpoint `posts#show` passing the
182
+ parameter `id` retrieved from the current url. The property returned is no
183
+ more an array, but a js object with the post (
184
+ `{post: {id: 1, title: ..., body: ..., ...}}`).
185
+
186
+ **NB:** first, we have said that the Rails backend responds in jsonapi format,
187
+ but the post object is not in jsonapi format. Next On Rails normalize the
188
+ jsonapi format internally for us. So in our frontend, we have the data ready
189
+ to use!
190
+
191
+ ### composeGetInitialResources
192
+
193
+ This function is like `getInitialResources` or `getInitialResource` but take a
194
+ list of resource names and combine the property to pass to the React
195
+ component. For example if we want to prefetch a post and a list of authors (in
196
+ an edit page we can edit the post author for example) we can use this function:
197
+
198
+ PostsEdit.getInitialProps = composeGetInitialResources('post', 'authors')
199
+
200
+ For this function the plurality of names are very important: singular names
201
+ will be request the `show` action, plural names the `index` action. With this
202
+ function is not possible to pass HTTP extra params. If you need to pass extra
203
+ HTTP params you should call `getInitialResources` or `getInitialResource` and
204
+ compose yourself the results.
205
+
206
+ `composeGetInitialResources` __performs a single HTTP request__.
207
+
208
+ ### useResources
209
+
210
+ Now let's say we want update a post. We need to create a `/posts/:id/edit`
211
+ page. So we add the file `./frontend/pages/posts/edit.js` and add the route to _express_.
212
+
213
+ The React component for this page will be:
214
+
215
+ import { getInitialResource, useResources, useResourceForm } from 'next-on-rails/resources'
216
+ import { Input, TextArea, CheckBox, HiddenIdField } from '../inputs'
217
+
218
+ const PostsEdit = props => {
219
+ const [, post, { update }] = useResources('posts', [], props.post)
220
+ const [submit, getError] = useResourceForm(update)
221
+
222
+ return (
223
+ <div>
224
+ <h1>Edit Post #{post.id}</h1>
225
+ <form onSubmit={submit}>
226
+ <HiddenIdField id={post.id} />
227
+ <Input name="title" value={post.title} error={getError('title')} />
228
+ <TextArea name="body" value={post.body} error={getError('body')} />
229
+ <CheckBox name="public" value={post.public} error={getError('public')} />
230
+ <button type="submit" className="btn btn-primary">
231
+ Save
232
+ </button>
233
+ </form>
234
+ </div>
235
+ )
236
+ }
237
+
238
+ PostsEdit.getInitialProps = getInitialResource('post')
239
+
240
+ export default PostsEdit
241
+
242
+ Ok, we have a lot to say about this component. Don't worry!
243
+ Let's start from the React hook `useResources`.
244
+
245
+ The idea is that we have a state consisting of 2 elements. An array with a
246
+ collection of resources and an object with a single resource. For example the
247
+ array with all posts and an object with the current post. This 2 elements
248
+ should be bound together so if I change the resource also the element with
249
+ the same id in the resources array should change accordingly, and vice versa.
250
+
251
+ This hook returns an array with four elements:
252
+ `[arrayOfResources, currentResourceObject, { actions }, dispatch]`.
253
+ The first two elements are the state, the array of resources (first one) and a
254
+ single resource (second one). The third is an object with the five functions
255
+ to perform the CRUD operations: `index`, `show`, `create`, `update` and
256
+ `destroy`. The fourth is the dispatch function returned by the
257
+ [`useReducer`](https://reactjs.org/docs/hooks-reference.html#usereducer) React
258
+ hook that is used internally by the `useResources` hook.
259
+
260
+ The third element is an object with five function, let's see one by one:
261
+
262
+ - `function index(params)` has as its argument HTTP params and performs a
263
+ request to the `resources_controller#index` action of Rails backend. For
264
+ example you can call `index({ page: 2 })` to get array of posts.
265
+ Returns a promise that will be resolved with the array of resources or
266
+ rejected with the errors.
267
+
268
+ - `function show(id, params)` has as its first argument the ID of a resource
269
+ and as its second argument HTTP params. Performs a request to the
270
+ `resources_controller#show` action of Rails backend. Returns a promise that
271
+ will be resolved with the object of the resource with the corresponding ID
272
+ or rejected with the errors.
273
+
274
+ - `function create(params)` has as its argument the params to create a new
275
+ resource. For example to create a new post you can call
276
+ `create({ title: 'Spiderman is dead', body: '...', public: true })`.
277
+ Performs a request to the `resources_controller#create` action of Rails
278
+ backend. Returns a promise that will be resolved with the object just
279
+ created or rejected with the errors.
280
+
281
+ - `function update(id, params)` has as its first argument the ID of the
282
+ resource to update and as its second argument params to update. For example
283
+ to update a post you can call
284
+ `update(1, { title: 'Spiderman is alive', body: '...', public: true })`.
285
+ Performs a request to the `resources_controller#update` action of Rails
286
+ backend. Returns a promise that will be resolved with the object just
287
+ updated or rejected with the errors.
288
+
289
+ - `function destroy(id)` has as its argument the ID of the resource to delete.
290
+ Performs a request to the `resources_controller#destroy` action of Rails
291
+ backend. Returns a promise that will be resolved with no arguments or
292
+ rejected with the errors.
293
+
294
+ So now in our previous example we can understand the line
295
+
296
+ const [, post, { update }] = useResources('posts', [], props.post)
297
+
298
+ We want a state with a post (initialized with the property `post`) and a
299
+ function to update it! For this component we don't need posts array and any
300
+ other CRUD functions.
301
+
302
+ ### useResourceForm
303
+
304
+ Now let's talk about the other very useful hook that NOR makes us available:
305
+ `useResourceForm`. This hook take a CRUD function which we talked about
306
+ earlier as first argument, a success callback as second argument and an error
307
+ callback as third argument.
308
+
309
+ It returns an array with two functions: `[submit, getError]`. The first one is
310
+ a function that is ready to pass as value of `onSubmit` form tag attribute. It
311
+ collects all form data with
312
+ [`FormData`](https://developer.mozilla.org/en-US/docs/Web/API/FormData) web
313
+ API object and then call the CRUD function (passed as first parameter) with
314
+ this form data. The resource ID can be passed as hidden field tag.
315
+
316
+ The second function `getError` takes a resource field as string, like the post
317
+ attribute `'title'`, and returns a validation error message for this attribute
318
+ if there is one. The errors are modelled as a React state. So at the beginnig
319
+ there are no errors and the function will return always `null`. After a failed
320
+ submit the function can return the validation error message. So if we call
321
+ `getError('title')` we get `null` if there is no validation errors or for
322
+ example _"Title can't be blank"_ if there is.
323
+
324
+ So we can now understand the line in the previous example
325
+
326
+ const [submit, getError] = useResourceForm(update)
327
+
328
+ We get the `submit` function to update a post and the `getError` function to
329
+ display the validation error messages.
330
+
331
+ ### useCurrentUser
332
+
333
+ Always in our React components we can use this hook:
334
+
335
+
336
+ import { useCurrentUser } from 'next-on-rails/current-user'
337
+
338
+ const HelloUser = () => {
339
+ const { currentUser } = useCurrentUser()
340
+
341
+ return (
342
+ <p>{ currentUser ? `Hello ${currentUser.username}` : 'You are not logged' }</p>
343
+ )
344
+ }
345
+
346
+ export default HelloUser
347
+
348
+ This hook return an object with two key. `currentUser` is the object with the
349
+ current user logged or `null`. `setCurrentUser` is a function to update the
350
+ current user.
351
+
352
+ const { currentUser, setCurrentUser } = useCurrentUser()
353
+
354
+ ### useFlash
355
+
356
+ We can use flash messages also in our Next.js frontend app. Here the previous example with flash messages:
357
+
358
+ import { getInitialResource, useResources, useResourceForm } from 'next-on-rails/resources'
359
+ import { useFlash } from 'next-on-rails/utils'
360
+ import { Input, TextArea, CheckBox, HiddenIdField } from '../inputs'
361
+ import Flash from '../flash'
362
+
363
+ const PostsEdit = props => {
364
+ const { setFlash } = useFlash()
365
+ const [, post, { update }] = useResources('posts', [], props.post)
366
+ const [submit, getError] = useResourceForm(update, () => {
367
+ setFlash('notice', 'Post updated successfully')
368
+ })
369
+
370
+ return (
371
+ <div>
372
+ <Flash />
373
+ <h1>Edit Post #{post.id}</h1>
374
+ <form onSubmit={submit}>
375
+ <HiddenIdField id={post.id} />
376
+ <Input name="title" value={post.title} error={getError('title')} />
377
+ <TextArea name="body" value={post.body} error={getError('body')} />
378
+ <CheckBox name="public" value={post.public} error={getError('public')} />
379
+ <button type="submit" className="btn btn-primary">
380
+ Save
381
+ </button>
382
+ </form>
383
+ </div>
384
+ )
385
+ }
386
+
387
+ PostsEdit.getInitialProps = getInitialResource('post')
388
+
389
+ export default PostsEdit
390
+
391
+
392
+ We can use the hook `useFlash` from which we can get a function to set a flash
393
+ message and then we can display the component `Flash` that will show the
394
+ message. You can find the `Flash` component in the file
395
+ `./frontend/components/flash.js` where you can customize it as you want.
396
+
397
+ ### useLoading
398
+
399
+ `useLoading` is another utility hook to know when the frontend is loading data from backend. Is quite simple:
400
+
401
+ import { useLoading } from 'next-on-rails/utils'
402
+
403
+ const Loader = props => {
404
+ const loading = useLoading()
405
+
406
+ if (loading) {
407
+ return <img src='loader.gif' alt="loading" />
408
+ } else {
409
+ return null
410
+ }
411
+ }
412
+
413
+ ### Requester
414
+
415
+ To perform HTTP request to backend you should use the `requester`.
416
+
417
+ import requester from 'next-on-rails/requester'
418
+
419
+ requester.get('/posts')
420
+ .then(function (response) {
421
+ // handle success
422
+ console.log(response)
423
+ })
424
+ .catch(function (error) {
425
+ // handle error
426
+ console.log(error);
427
+ })
428
+
429
+ It is a wrapper over [Axios](https://github.com/axios/axios) and it works in
430
+ the same way. You can also access to axios object with `requester.axios`.
431
+
432
+ So why the `requester`? Because is handle for you the [Devise Token Auth
433
+ authentication
434
+ token](https://devise-token-auth.gitbook.io/devise-token-auth/conceptual),
435
+ adding the token in the request header and change it at every request based on
436
+ the previous response. The requester save the token on cookies.
437
+
438
+ It also normalize the jsonapi response. For example if reponse json is
439
+
440
+ {
441
+ "data": [
442
+ {
443
+ "id": "1",
444
+ "type": "post",
445
+ "attributes": {
446
+ "id": 1,
447
+ "title": "My first blog post",
448
+ "body": "Amazing",
449
+ "public": true
450
+ }
451
+ },
452
+ {
453
+ "id": "2",
454
+ "type": "post",
455
+ "attributes": {
456
+ "id": 2,
457
+ "title": "My second blog post",
458
+ "body": "Wow",
459
+ "public": false
460
+ }
461
+ }
462
+ ]
463
+ }
464
+
465
+ it will be normalized in:
466
+
467
+ [
468
+ {
469
+ "id": 1,
470
+ "title": "My first blog post",
471
+ "body": "Amazing",
472
+ "public": true
473
+ },
474
+ {
475
+ "id": 2,
476
+ "title": "My second blog post",
477
+ "body": "Wow",
478
+ "public": false
479
+ }
480
+ ]
481
+
482
+ ### Config
483
+
484
+ NOR try to follow the Rails conventions, but you can customize something
485
+ editing the configuration file `./frontend/next-on-rails.config.js`. The default config is this:
486
+
487
+ {
488
+ baseURL: 'http://localhost:3000',
489
+ flashTimeout: 5000,
490
+ devisePaths: {
491
+ validateToken: '/auth/validate_token',
492
+ signIn: '/auth/sign_in',
493
+ signOut: '/auth/sign_out',
494
+ signUp: '/auth',
495
+ passwordReset: '/auth/password',
496
+ passwordChange: '/auth/password',
497
+ unlock: '/auth/unlock'
498
+ },
499
+ deviseFormInputNames: {
500
+ email: 'email',
501
+ password: 'password',
502
+ passwordConfirmation: 'password_confirmation'
503
+ }
504
+ }
505
+
506
+ You can customize the resource names and actions:
507
+
508
+ resources: {
509
+ posts: {
510
+ routes: {
511
+ index: { method: 'get', url: '/all_posts.json' }
512
+ },
513
+ name: {
514
+ singular: 'post',
515
+ plural: 'posts'
516
+ }
517
+ }
518
+ }
519
+
520
+ ## Add custom action
521
+
522
+ Until now we have always talk about the standard CRUD action. But how to do if
523
+ we need manage a custom controller action?
524
+
525
+ We can add in our post controller a new action:
526
+
527
+ def like
528
+ @post = Post.find(params[:id])
529
+ @post.increment!(:like_counter)
530
+ render json: PostSerializer.new(@post)
531
+ end
532
+
533
+ And in `routes.rb`
534
+
535
+ resources :posts do
536
+ member do
537
+ patch :like
538
+ end
539
+ end
540
+
541
+ Our frontend page component should be:
542
+
543
+ import { getInitialResource, useResources } from 'next-on-rails/resources'
544
+ import requester from 'next-on-rails/requester'
545
+
546
+ const PostsShow = props => {
547
+ const [, post, , dispatch] = useResources('posts', [], props.post)
548
+
549
+ const like = (event) => {
550
+ requester.patch(`/posts/${post.id}/like`)
551
+ .then(response => {
552
+ dispatch({ type: 'set', resource: response.data })
553
+ })
554
+ .catch(console.log)
555
+ }
556
+
557
+ return (
558
+ <div className="container pt-4">
559
+ <h1 className="mb-5">Post #{post.id}</h1>
560
+
561
+ <p>{post.body}</p>
562
+
563
+ <div>
564
+ <button onClick={like}>Like</button>
565
+ {post.like_counter} people like it
566
+ </div>
567
+ </div>
568
+ )
569
+ }
570
+
571
+ PostsShow.getInitialProps = getInitialResource('post')
572
+
573
+ export default PostsShow
574
+
575
+ We have create a custom function `like` that performs the `posts#like` action
576
+ and on success we replace the current post state object with the new post
577
+ object returned from the HTTP request. In this way the `like_counter` will be
578
+ updated and React re-render the incremented counter.
579
+
580
+ ## License
581
+
582
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
583
+
584
+ ## About Uqido
585
+
586
+ [![uqido](https://i.imgur.com/FAo2W7w.png)](http://uqido.com)
587
+
588
+ Next On Rails is maintained and funded by [Uqido](https://uqido.com).
589
+ The names and logos for Uqido are trademarks of Uqido s.r.l.
590
+
591
+ The [Uqido team](https://www.uqido.com/en/about-us/).