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!
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.
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.
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.
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.
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 in models/task.rb
.Task.find()
- we can call find(id)
on the model, which will find the task that matches the ID we passed inparams
- this object contains any parameters defined in the routes file. In our case one of our routes is get '/tasks/:id'
so if a user lands on localhost:3000/tasks/2
, then in our params object there will be an :id
with a value of 2
.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.
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 %>
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 a format
method@task
- we got this from the set_task
method (described above)@task.update()
- we can call update()
on the specific task, and pass in new valuestask_params
- gives us the values in the body of the call that was made (described in more detail below)format.html
and format.json
- we're defining what is returned, depending on if the request was an HTML or JSON request.if
and else
- if @task.update
fails, we will enter the else
block.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!