Essential Guide 4: Forms & Actions (Create, Update, Delete)
Demo: Matestack Demo Github Repo: Matestack Demo Application
Welcome to the fourth part of our tutorial about building a web application with matestack.
Introduction
In the previous guide, 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
.
In this guide, we will
add a new page and action action to create new persons
add an edit page and action to modify existing persons
add an delete action to delete existing persons
introduce the concept of matestack forms
introduce the concept of matestack actions
Prerequisites
We expect you to have successfully finished the previous guide.
Updating the person model
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
.
validates :first_name, presence: true
validates :last_name, presence: true
validates :role, presence: true
We only check for presence of this three attributes.
Preparing routes & controller
In your config/routes.rb
, change
resources :persons, only: [:index, :show]
to
resources :persons
in order to let rails generate all CRUD routes for persons.
Then, in the persons_controller.rb
, update the contents like this:
class PersonsController < ApplicationController
matestack_app Demo::App
before_action :find_person, only: [:show, :edit, :update, :destroy]
def index
render Demo::Pages::Persons::Index
end
def show
render Demo::Pages::Persons::Show
end
def new
render Demo::Pages::Persons::New
end
def create
person = Person.create person_params
if person.errors.empty?
render json: { transition_to: person_path(person) }, status: :created
else
render json: { errors: person.errors }, status: :unprocessable_entity
end
end
def edit
render Demo::Pages::Persons::Edit
end
def update
if @person.update person_params
render json: { transition_to: person_path(@person) }, status: :ok
else
render json: { errors: @person.errors }, status: :unprocessable_entity
end
end
def destroy
if @person.destroy
render json: { transition_to: persons_path }, status: :ok
else
render json: { errors: @person.errors }, status: :unprocessable_entity
end
end
protected
def find_person
@person = Person.find_by(id: params[:id])
end
def person_params
params.require(:person).permit(
:first_name,
:last_name,
:role
)
end
end
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.
Nothing extraordinary, but definitely stepping things up a bit!
Adding the new and edit pages with forms
Create a new page in app/matestack/demo/pages/persons/new.rb
and add the following content:
class Demo::Pages::Persons::New < Matestack::Ui::Page
def response
transition path: persons_path, text: 'All persons'
heading size: 2, text: 'Create a new person'
form new_person_form_config do
label text: 'First name'
form_input key: :first_name, type: :text
br
label text: 'Last name'
form_input key: :last_name, type: :text
br
label text: 'Person role'
form_radio key: :role, options: Person.roles.keys
br
form_submit do
button text: 'Create person'
end
end
end
def new_person_form_config
{
for: Person.new,
method: :post,
path: persons_path,
success: {
transition: {
follow_response: true
}
}
}
end
end
What's going on here? Let's break it down:
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
.
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
.
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 submit the form inputs wrapped by the model name following the Rails behavior and conventions.
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.
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. 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.
To learn more, check out the complete API documentation for the form
component.
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:
class Demo::Pages::Persons::Edit < Matestack::Ui::Page
def response
transition path: :person_path, params: { id: @person.id }, text: 'Back to detail page'
heading size: 2, text: "Edit Person: #{@person.first_name} #{@person.last_name}"
form person_edit_form_config do
label text: 'First name'
form_input key: :first_name, type: :text
br
label text: 'Last name'
form_input key: :last_name, type: :text
br
label text: 'Person role'
form_select key: :role, type: :radio, options: Person.roles.keys
br
form_submit do
button text: 'Save changes'
end
end
end
def person_edit_form_config
{
for: @person,
method: :patch,
path: :person_path,
params: {
id: @person.id
},
success: {
transition: {
follow_response: true
}
}
}
end
end
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.
Updating the index page
Within the response
block on the Index page (app/matestack/demo/pages/persons/index.rb
), add the following line:
transition path: :new_person_path, text: 'Create new person'
As you might have guessed, this takes us to the New page and you can create a new person there.
Further introduction: Forms
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.
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.
Beyond that, here's some suggestions of what you could try to add in the future:
uploading files
handling failure
different input types like email, password, textfield, range or dropdown
re-rendering parts of a page instead of doing a page transition
using other Ruby objects than ActiveRecord collections
fetching data from and sending data to third party APIs
To learn more, check out the complete API documentation for the form
component.
Adding a delete button for persons
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.
Update your show page in app/matestack/demo/pages/persons/show.rb
to look like this:
class Demo::Pages::Persons::Show < Matestack::Ui::Page
def response
transition path: persons_path, text: 'All persons'
heading size: 2, text: "Name: #{@person.first_name} #{@person.last_name}"
paragraph text: "Role: #{@person.role}"
transition path: :edit_person_path, params: { id: @person.id }, text: 'Edit'
action delete_person_config do
button text: 'Delete person'
end
end
def delete_person_config
{
method: :delete,
path: person_path(@person),
success: {
transition: {
follow_response: true
}
},
confirm: {
text: 'Do you really want to delete this person?'
}
}
end
end
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.
Further introduction: Actions
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.
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.
Beyond that, here's some suggestions of what you could try to add in the future:
sending an advanced payload with the HTTP request
re-rendering parts of a page on successful request
To learn more, check out the complete API documentation for the action
component.
Local testing
Run rails s
and head over to localhost:3000 to test the changes! You should be able to create new persons as well as edit and delete existing ones!
Saving the status quo
As usual, we want to commit the progress to Git. In the repo root, run
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"
Recap & outlook
By now, we have already implemented the complete CRUD (Create-Read-Update-Delete) functionality around the person model. Neat!
We got a brief introduction of the form
and action
components and know how to use them.
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!
Take a well deserved rest and make sure to come back to the next part of this series, introducing the powerful toggle component
.
Last updated
Was this helpful?