As a newbie to Rails and Ruby, I found the code generated by the rails generate scaffold
command initially very hard to understand. A lot of Googling later, I think I have a better understanding of what things are doing, and so I decided to write a post about it!
Running the generate scaffold command
If we were to create a to-do list app, we would need a way of storing our tasks in a database. With Rails, we can do this using something called a model - which acts like a wrapper aorund a database table.
In our case, we are going to need a table that stores tasks, and so we need to create a Task
model that stores the task's name, due date, and whether it has been completed or not.
We can call rails generate scaffold
with the name of our model, and any number of arguments in the shape fieldName:dataType
:
rails generate scaffold Task name:string done:boolean due:datetime
Note that you don't need to specify an ID - Rails will handle this one for you.
If you're not sure what data types to use for your fields, I found this StackOverflow answer on data types really useful.
One of the new files created by this command will be a new file in db/migrate
that will look something like this:
class CreateTasks < ActiveRecord::Migration[6.0]
def change
create_table :tasks do |t|
t.string :name
t.boolean :done
t.datetime :due
t.timestamps
end
end
end
To create a table of tasks in our database, we will need to run that file. We can do that with the following:
rails db:migrate
Now that your database can handle storing tasks you can start up your app:
rails s --binding=127.0.0.1
You will see that at http://localhost:3000/tasks
, Rails has generated a basic UI for you that lets you create, delete and edit tasks - all for free! (I was actually pretty amazed by this). I recommend having a little play around with it and creating a few tasks.
In the next section, we'll be exploring how all this UI has been generated for us.
How tasks routing works
If we take a look at config/routes.rb
we will see a new line has been added:
resources :tasks
This is a shorthand for 7 separate routes that allow you to view, modify and create tasks.
e.g. two of them would be:
get '/tasks' 'tasks#index'
post '/tasks' 'tasks#create'
Each line maps a URL to a controller and action, so if we make a GET call to /tasks
, we will execute the code inside of the index
action in the tasks
controller.
What's happening in the tasks controller?
The tasks
controller is another file that has been generated for us at app/controllers/tasks_controller.rb
. I'm going to break down some of what is happening.
before_action
Right up the top of the file, you'll see this:
before_action :set_task, only: [:show, :edit, :update, :destroy]
Here we're saying that the set_task
method (which is defined near the bottom of the file) should be called before the given list of actions (show, edit, update and destroy).
The colon (
:
) beforeset_task
indicates that it is a Symbol, which is like a reference to the method. If we didn't use the colon,before_action
would use the results of calling theset_task
method.
set_task
That set_task
method does the following:
def set_task
@task = Task.find(params[:id])
end
-
@task
- In Ruby, the@
defines a variable as an instance variable, which means it can be accessed outside of where it has been defined (even in the view file!) -
Task
- Refers to the Task class that lives inmodels/task.rb
. -
Task.find()
- we can callfind(id)
on the model, which will find the task that matches the ID we passed in -
params
- this object contains any parameters defined in the routes file. In our case one of our routes isget '/tasks/:id'
so if a user lands onlocalhost:3000/tasks/2
, then in our params object there will be an:id
with a value of2
.
In short, before we do certain things like viewing a specific task or deleting one, we will store in @task
the relevant task that we are looking for. This means each action that access it immediately without having to add in that extra line of code themselves.
Getting all tasks using the index action
Now the first action is our index action. This maps to the following route:
get '/tasks' 'tasks#index'
The action itself is very short:
def index
@tasks = Task.all
end
What we're doing here is storing a list of all the tasks in @tasks
.
And then with Rails magic, this action will map to the view file defined at tasks/index.html.erb
. This view file will have access to all the tasks, and can loop through them and render them all on the page:
<% @tasks.each do |task| %>
<tr>
<td><%= task.name %></td>
</tr>
<% end %>
Updating a task using the update action
The update action maps to the following route:
put '/tasks/:id' 'tasks#update'
def update
respond_to do |format|
if @task.update(task_params)
format.html { redirect_to @task, notice: 'Task was successfully updated.' }
format.json { render :show, status: :ok, location: @task }
else
format.html { render :edit }
format.json { render json: @task.errors, status: :unprocessable_entity }
end
end
end
Let's break this down!
-
respond_to
- a method available to us in controller classes, which will give access to aformat
method -
@task
- we got this from theset_task
method (described above) -
@task.update()
- we can callupdate()
on the specific task, and pass in new values -
task_params
- gives us the values in the body of the call that was made (described in more detail below) -
format.html
andformat.json
- we're defining what is returned, depending on if the request was an HTML or JSON request. -
if
andelse
- if@task.update
fails, we will enter theelse
block.
task_params
The other method defined at the bottom of the file is task_params
:
def task_params
params.require(:task).permit(:name, :done, :due)
end
When this method is called, we will require that a :task
symbol exists inside of the params object, and filter out any values from task that aren't the values for name, done and due.
What this means in practice is that if you called the endpoint with the data to create a new task, this would be accessible inside of params[:task]
. And if you passed in an extra field like "description", this would get filtered out by this method.
And that's it for this post! The other actions are fairly similar to the code I've gone through above, so hopefully shouldn't be too hard to understand.
Thanks for reading!
Top comments (0)