next_on_rails 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/MIT-LICENSE +20 -0
- data/README.md +591 -0
- data/Rakefile +32 -0
- data/app/controllers/next_on_rails/application_controller.rb +21 -0
- data/app/controllers/next_on_rails/devise_token_auth_override/concerns/current_resource_serializer.rb +15 -0
- data/app/controllers/next_on_rails/devise_token_auth_override/confirmations_controller.rb +4 -0
- data/app/controllers/next_on_rails/devise_token_auth_override/omniauth_callbacks_controller.rb +7 -0
- data/app/controllers/next_on_rails/devise_token_auth_override/passwords_controller.rb +23 -0
- data/app/controllers/next_on_rails/devise_token_auth_override/registrations_controller.rb +33 -0
- data/app/controllers/next_on_rails/devise_token_auth_override/sessions_controller.rb +27 -0
- data/app/controllers/next_on_rails/devise_token_auth_override/token_validations_controller.rb +13 -0
- data/app/controllers/next_on_rails/devise_token_auth_override/unlocks_controller.rb +23 -0
- data/app/controllers/next_on_rails/merger_controller.rb +36 -0
- data/app/serializers/next_on_rails/active_model_errors_serializer.rb +45 -0
- data/app/serializers/next_on_rails/errors_serializer.rb +16 -0
- data/config/routes.rb +12 -0
- data/lib/generators/nor/backend/backend_generator.rb +129 -0
- data/lib/generators/nor/backend/templates/Procfile +2 -0
- data/lib/generators/nor/backend/templates/application_controller.rb +2 -0
- data/lib/generators/nor/backend/templates/cors.rb +23 -0
- data/lib/generators/nor/backend/templates/current_user_serializer.rb.tt +8 -0
- data/lib/generators/nor/backend/templates/user.rb.tt +8 -0
- data/lib/generators/nor/backend/templates/user_migration.rb.tt +53 -0
- data/lib/generators/nor/backend/templates/user_serializer.rb.tt +11 -0
- data/lib/generators/nor/frontend/frontend_generator.rb +60 -0
- data/lib/generators/nor/frontend/templates/frontend/components/flash.js +29 -0
- data/lib/generators/nor/frontend/templates/frontend/components/inputs.js +69 -0
- data/lib/generators/nor/frontend/templates/frontend/components/login-form.js +23 -0
- data/lib/generators/nor/frontend/templates/frontend/components/registration-form.js +49 -0
- data/lib/generators/nor/frontend/templates/frontend/next-on-rails.config.js +3 -0
- data/lib/generators/nor/frontend/templates/frontend/next.config.js +20 -0
- data/lib/generators/nor/frontend/templates/frontend/pages/_app.js +3 -0
- data/lib/generators/nor/frontend/templates/frontend/pages/index.js +57 -0
- data/lib/generators/nor/frontend/templates/frontend/server.js +31 -0
- data/lib/generators/nor/frontend/templates/frontend/stylesheets/application.scss +2 -0
- data/lib/generators/nor/install/USAGE +30 -0
- data/lib/generators/nor/install/install_generator.rb +8 -0
- data/lib/generators/nor/scaffold/USAGE +10 -0
- data/lib/generators/nor/scaffold/scaffold_generator.rb +53 -0
- data/lib/generators/nor/scaffold/templates/controller.rb.tt +40 -0
- data/lib/generators/nor/scaffold/templates/frontend/components/details.js.tt +20 -0
- data/lib/generators/nor/scaffold/templates/frontend/components/form.js.tt +17 -0
- data/lib/generators/nor/scaffold/templates/frontend/pages/crud.js.tt +123 -0
- data/lib/generators/nor/scaffold/templates/frontend/pages/edit.js.tt +55 -0
- data/lib/generators/nor/scaffold/templates/frontend/pages/index.js.tt +88 -0
- data/lib/generators/nor/scaffold/templates/frontend/pages/new.js.tt +51 -0
- data/lib/generators/nor/scaffold/templates/frontend/pages/show.js.tt +43 -0
- data/lib/next_on_rails.rb +12 -0
- data/lib/next_on_rails/engine.rb +5 -0
- data/lib/next_on_rails/version.rb +3 -0
- data/lib/tasks/next_on_rails_tasks.rake +4 -0
- metadata +235 -0
checksums.yaml
ADDED
@@ -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
|
data/MIT-LICENSE
ADDED
@@ -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.
|
data/README.md
ADDED
@@ -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/).
|