Skip to content

Commit f68ff97

Browse files
author
Nils Henning
committed
[TASK][REFACTOR] rework 04_forms_edit_new_create_update_delete guide
1 parent 462e62f commit f68ff97

File tree

1 file changed

+90
-91
lines changed

1 file changed

+90
-91
lines changed
Lines changed: 90 additions & 91 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,37 @@
11
# Essential Guide 4: Forms & Actions (Create, Update, Delete)
2+
23
Welcome to the forth part of the 10-step-guide of setting up a working Rails CRUD app with `matestack-ui-core`!
34

45
## Introduction
5-
In the [previous guide](guides/essential/03_index_show_transition.md), we added index and show pages for the **person** model. In this part, we will add the functionality to add new instances and edit and delete our database records using `matestack` forms.
6+
7+
In the [previous guide](guides/essential/03_index_show_transition.md), we added index and show pages for the **person** model. In this part, we will implement the create, update and delete part of our CRUD application. For this we will introduce the matestack `forms`.
68

79
In this guide, we will
8-
- add a **New** page and corresponding controller action to create new persons
9-
- add an **Edit** page and corresponding controller action to modify existing persons
10-
- add an action with a delete button to the "show" page and corresponding controller action to remove an existing person from the database
11-
- introduce the concept of `matestack` forms
12-
- introduce the concept of `matestack` actions
10+
- add a new page and action action to create new persons
11+
- add an edit page and action to modify existing persons
12+
- add an delete action to delete existing persons
13+
- introduce the concept of matestack forms
14+
- introduce the concept of matestack actions
15+
16+
action with a delete button to the "show" page and corresponding controller action to remove an existing person from the database
1317

1418
## Prerequisites
15-
We expect you to have successfully finished the [previous guide](guides/essential/03_index_show_transition.md) and no uncommited changes in your project.
19+
We expect you to have successfully finished the [previous guide](guides/essential/03_index_show_transition.md).
1620

1721
## Updating the person model
18-
Let's make sure all the persons in our database actually end up having a name and a a role by adding those lines to `app/models/person.rb`:
22+
23+
All persons should have a first name, last name and a role. Therefore we add validations to our person model for these three attributes in `person.rb`.
1924

2025
```ruby
2126
validates :first_name, presence: true
2227
validates :last_name, presence: true
2328
validates :role, presence: true
2429
```
2530

26-
This might not seem like a big change, but will help with validating form input later on!
31+
We only check for presence of this three attributes.
2732

2833
## Preparing routes & controller
34+
2935
In your `config/routes.rb`, change
3036

3137
```ruby
@@ -38,19 +44,15 @@ to
3844
resources :persons
3945
```
4046

41-
to allow further actions for the ressource.
47+
in order to let rails generate all CRUD routes for persons.
4248

4349
Then, in the `persons_controller.rb`, update the contents like this:
4450

4551
```ruby
4652
class PersonsController < ApplicationController
47-
before_action :set_person, only: [:show, :edit, :update, :destroy]
48-
4953
matestack_app Demo::App
5054

51-
def new
52-
render Demo::Pages::Persons::New
53-
end
55+
before_action :find_person, only: [:show, :edit, :update, :destroy]
5456

5557
def index
5658
render Demo::Pages::Persons::Index
@@ -60,43 +62,42 @@ class PersonsController < ApplicationController
6062
render Demo::Pages::Persons::Show
6163
end
6264

63-
def edit
64-
render Demo::Pages::Persons::Edit
65+
def new
66+
render Demo::Pages::Persons::New
6567
end
6668

67-
def update
68-
@person.update person_params
69-
70-
@person.save
71-
if @person.errors.any?
72-
render json: {errors: @person.errors}, status: :unprocessable_entity
69+
def create
70+
person = Person.create person_params
71+
if person.errors.empty?
72+
render json: { transition_to: person_path(person) }, status: :created
7373
else
74-
render json: { transition_to: person_path(id: @person.id) }, status: :ok
74+
render json: { errors: person.errors }, status: :unprocessable_entity
7575
end
7676
end
7777

78-
def create
79-
@person = Person.new(person_params)
80-
@person.save
78+
def edit
79+
render Demo::Pages::Persons::Edit
80+
end
8181

82-
if @person.errors.any?
83-
render json: {errors: @person.errors}, status: :unprocessable_entity
82+
def update
83+
if @person.update person_params
84+
render json: { transition_to: person_path(@person) }, status: :ok
8485
else
85-
render json: { transition_to: person_path(id: @person.id) }, status: :created
86+
render json: { errors: @person.errors }, status: :unprocessable_entity
8687
end
8788
end
8889

8990
def destroy
9091
if @person.destroy
9192
render json: { transition_to: persons_path }, status: :ok
9293
else
93-
render json: {errors: @person.errors}, status: :unprocessable_entity
94+
render json: { errors: @person.errors }, status: :unprocessable_entity
9495
end
9596
end
9697

9798
protected
9899

99-
def set_person
100+
def find_person
100101
@person = Person.find_by(id: params[:id])
101102
end
102103

@@ -111,23 +112,20 @@ class PersonsController < ApplicationController
111112
end
112113
```
113114

114-
What's going on there? We've added `render`-methods for two new `matestack` pages (**New** and **Edit**) and controller endpoints for **create**, **update** and **destroy** actions. Since we're interacting with the database directly now, we also need to sanitize the params and, to keep the code clean, have extracted some functionality into a `before_action`.
115+
What's going on there? We've added `render`-calls for two new matestack pages (new, edit) and controller actions for create, update and destroy. To keep the code clean, we have extracted some functionality into a `before_action` and added the `person_params` method to extract person relevant params from all params in rails safe params manner.
115116

116117
Nothing extraordinary, but definitely stepping things up a bit!
117118

118-
## Adding the **New** and **Edit** pages
119+
## Adding the new and edit pages with forms
120+
119121
Create a new page in `app/matestack/demo/pages/persons/new.rb` and add the following content:
120122

121123
```ruby
122124
class Demo::Pages::Persons::New < Matestack::Ui::Page
123125

124-
def prepare
125-
@person = Person.new
126-
end
127-
128126
def response
129-
transition path: :persons_path, text: 'Back to index page'
130-
heading size: 2, text: 'Create new person'
127+
transition path: :persons_path, text: 'All persons'
128+
heading size: 2, text: 'Create a new person'
131129
form new_person_form_config, :include do
132130
label text: 'First name'
133131
form_input key: :first_name, type: :text
@@ -136,7 +134,7 @@ class Demo::Pages::Persons::New < Matestack::Ui::Page
136134
form_input key: :last_name, type: :text
137135
br
138136
label text: 'Person role'
139-
form_select key: :role, type: :radio, options: Person.roles.keys
137+
form_radio key: :role, options: Person.roles.keys
140138
br
141139
form_submit do
142140
button text: 'Create person'
@@ -146,9 +144,9 @@ class Demo::Pages::Persons::New < Matestack::Ui::Page
146144

147145
def new_person_form_config
148146
{
149-
for: @person,
147+
for: Person.new,
150148
method: :post,
151-
path: :persons_path,
149+
path: persons_path,
152150
success: {
153151
transition: {
154152
follow_response: true
@@ -161,11 +159,21 @@ end
161159
```
162160

163161
What's going on here? Let's break it down:
164-
- in the `prepare` method, an empty person record is created so we can fill it later
165-
- within the `response` method, there's a link back to the **Show** page, a heading and the first `matestack` form, featuring two text input fields, radio select fields and a submit button
166-
- the `new_person_form_config` tells the form which data to use (the empty person, in this case), where to send the inputs upon submission and how to handle successful (and, potentially, unsuccessful) response messages from the controller!
167162

168-
Take a moment to familiarize yourself with everything going on in the file, and then go ahead and create another page in `app/matestack/demo/pages/persons/edit.rb`, featuring similar content:
163+
Within our response method we have at first a transition to the person index page. After that comes _h2_ tag as a headline stating 'Create a new person'. Nothing new so long. Afterwards comes an unfamiliar call of `form`.
164+
165+
Let's take a closer look. Like in Rails with `form_for` you can create a form in matestack with `form`. It takes a hash as first argument for configuration. In this case we defined `new_person_form_config` to return the config hash for our form. In the config hash you can set the http request method, a path, success and failure configs and a for key, which will be explained soon. This form gets submitted as a POST request to the `persons_path`.
166+
167+
The `for` key let's us define for what this form is. In this case we pass a empty model to the form component, which will therefore submitt the form inputs wrapped by the model name following the Rails behavior and conventions.
168+
169+
The 'success' key let's us define a behavior when the form was submitted successful, which means the server returned a status code of 2XX. In this case we tell the form that if successful it should follow the transition path we return in our controller action. So a page transition will happen to the detail page of our newly created model. We could also configure a failure behavior by specifying the `failure` key.
170+
171+
Inside our form component we have calls to normal html components like `label, br, button` which render the corresponding html tag and we have calls for form inputs and a form submit. Let's take a closer look at the `form_input` call. A form input at least requires a key and a type. The type can be any html input type possible. The key defines the input name as which it will get submitted. If the model specified by the `for` key in the form config responds to the key the input will be prefilled with the value the model returns. `form_radio, form_select, form_checkbox` helpers can take an array or hash in the `options` key. They render for example a radio button for each option in the array or hash. In case of an array the label and value are the same for each radio button, in case of a hash the keys are used as labels and the values as values.
172+
To enable the user to submit the form, we added a button to click. This button needs to be wrapped inside a `form_submit` call, which will take care of triggering the form submit if the contents inside the given block is clicked.
173+
174+
To learn more, check out the [complete API documentation](docs/components/form.md) for the `form` component.
175+
176+
Take a moment to familiarize yourself with everything going on and then go ahead and create another page in `app/matestack/demo/pages/persons/edit.rb`, featuring similar content:
169177

170178
```ruby
171179
class Demo::Pages::Persons::Edit < Matestack::Ui::Page
@@ -208,19 +216,39 @@ class Demo::Pages::Persons::Edit < Matestack::Ui::Page
208216
end
209217
```
210218

211-
Again, we're using a form within the `response` method and define its behaviour in the `person_edit_form_config`. Since this time, an existing person is looked up in the database, the form gets initialized with his/her data.
219+
Again, we're using a form within the `response` method and define its behaviour in the `person_edit_form_config`. Since this time, an existing person is looked up in the database, the form gets initialized with his/her data as described above.
220+
221+
## Updating the index page
212222

213-
## Updating the **Index** page
214223
Within the `response` block on the **Index** page (`app/matestack/demo/pages/persons/index.rb`), add the following line:
215224

216225
```ruby
217226
transition path: :new_person_path, text: 'Create new person'
218227
```
219228

220-
As you might have guessed, this takes us to the **New** page and you can add a new person to the database there!
229+
As you might have guessed, this takes us to the **New** page and you can create a new person there.
230+
231+
## Further introduction: Forms
232+
233+
During this article, you've got a general idea of how matestack forms handle data input. But since this is only an introductory guide, we can't cover all the possible use cases and functionality in here.
221234

222-
## Adding the **delete** button to the **Show** page
223-
Update your **Show** page in `app/matestack/demo/pages/persons/show.rb` to look like this:
235+
Let's do a quick recap: The `form` component can be used like other components we have seen before, but requires a hash as parameter for configuration. Within the hash, various configurations like HTTP method, submission path, payload and handling of success/failure responses can be set.
236+
237+
Beyond that, here's some suggestions of what you could try to add in the future:
238+
- uploading files
239+
- handling failure
240+
- different input types like email, password, textfield, range or dropdown
241+
- re-rendering parts of a page instead of doing a page transition
242+
- using other Ruby objects than ActiveRecord collections
243+
- fetching data from and sending data to third party APIs
244+
245+
To learn more, check out the [complete API documentation](docs/components/form.md) for the `form` component.
246+
247+
## Adding a delete button for persons
248+
249+
We want to add the ability to delete persons. For that we add a delete button to the persons show page, which will destroy the person model when it was clicked. To achieve this we will use matestacks `action` component.
250+
251+
Update your show page in `app/matestack/demo/pages/persons/show.rb` to look like this:
224252

225253
```ruby
226254
class Demo::Pages::Persons::Show < Matestack::Ui::Page
@@ -236,12 +264,9 @@ class Demo::Pages::Persons::Show < Matestack::Ui::Page
236264
end
237265

238266
def delete_person_config
239-
return {
267+
{
240268
method: :delete,
241-
path: :person_path,
242-
params: {
243-
id: @person.id
244-
},
269+
path: person_path(@person),
245270
success: {
246271
transition: {
247272
follow_response: true
@@ -256,63 +281,37 @@ class Demo::Pages::Persons::Show < Matestack::Ui::Page
256281
end
257282
```
258283

259-
This is the first time we encounter another powerful `matestack` component: The `action` component! In the example above, the delete button gets wrapped into an `action` that receives a `*_config*` parameter. The configuration then is specified below, sending the currently displayed person's `id` to the **delete** action in the `person controller`. Upon a positive response from our controller, we configure our `action` component to follow the redirect specified in the **delete** action.
260-
261-
## Further introduction: Forms
262-
During this article, you've got a general idea of how `matestack` forms handle data input. But since this is only an introductory guide, we can't cover all the possible use cases and functionality in here.
263-
264-
Let's do a quick recap: The `form` component can be used like the other components we have seen before, but requires a `*_config` parameter. Within the `*_config`, various parameters like HTTP method, submission path, payload and handling of success/error responses can be set.
265-
266-
Beyond that, here's some suggestions of what you could try to add in the future:
267-
- uploading files
268-
- handling failure
269-
- different input types like email, password, textfield, range or dropdown
270-
- re-rendering parts of a page instead of doing a page transition
271-
- using other Ruby objects than ActiveRecord collections
272-
- fetching data from and sending data to third party APIs instead of interacting with your database
273-
274-
To learn more, check out the [complete API documentation](docs/components/form.md) for the `form` component.
284+
As you see we wrap our delete button inside an `action` component. Like the `form` component, an `action` component also takes a hash as first parameter for configuration. An action component triggers a asynchronous request when someone clicks on the wrapped content. The request target and http method are again configured in the hash. In this case when the action component is clicked it will send an DELETE request to `/persons/:id`. For a successful request we configured our `action` component to follow the redirect specified by the delete action. With the `confirm` keyword we can configure that when the button is clicked a confirm dialog pops up and needs to be confirmed before the request is send.
275285

276286
## Further introduction: Actions
277287
As we've seen with the delete button, `matestack` actions are a convenient way to trigger HTTP requests without having to write a lot of code.
278288

279-
Let's do a quick recap: Similar to the `form`, an `action` component requires a `*_config` parameter and wraps other content, for example a button. This content is then clickable and triggers whatever HTTP request is specified in the configuration.
289+
Let's do a quick recap: Similar to the `form`, an `action` component requires a hash as parameter for configuration and wraps other content, for example a button. This content is then clickable and triggers whatever HTTP request is specified in the configuration.
280290

281291
Beyond that, here's some suggestions of what you could try to add in the future:
282292
- sending an advanced payload with the HTTP request
283-
- adding a confirmation window
284293
- re-rendering parts of a page on successful request
285294

286295
To learn more, check out the [complete API documentation](docs/components/action.md) for the `action` component.
287296

288297
## Local testing
298+
289299
Run `rails s` and head over to [localhost:3000](http://localhost:3000/) to test the changes! You should be able to create new persons as well as edit and delete existing ones!
290300

291301
## Saving the status quo
302+
292303
As usual, we want to commit the progress to Git. In the repo root, run
293304

294305
```sh
295306
git add . && git commit -m "Add edit/new matestack pages for person model (incl. create/update controller, routes), add delete button (incl. controller action & route) to person show page"
296307
```
297308

298-
## Deployment
299-
After you've finished all your changes and commited them to Git, run
300-
301-
```sh
302-
git push heroku master
303-
```
304-
305-
to deploy your latest changes (again, no migrations are needed as the database schema is still unchanged). Check the results via
306-
307-
```sh
308-
heroku open
309-
```
310-
311-
and celebrate your progress by creating some new persons, modifying existing ones and delete one or two!
312-
313309
## Recap & outlook
310+
314311
By now, we have already implemented the complete **CRUD** (**C**reate-**R**ead-**U**pdate-**D**elete) functionality around the person model. Neat!
315312

316-
But there's still six out of ten guides open - so what's left? In the upcoming chapters, we will dive deeper into some `matestack` concepts to further enhance both user experience and developer happiness!
313+
We got a brief introduction of the `form` and `action` components and know how to use them.
314+
315+
But there's still more guides coming - so what's left? In the upcoming chapters, we will dive deeper into some `matestack` concepts to further enhance both user experience and developer happiness!
317316

318317
Take a well deserved rest and make sure to come back to the next part of this series, introducing the powerful [`async` and `collection` components](/guides/essential/04_form_create_update_delete.md).

0 commit comments

Comments
 (0)