In previous part of this tutorial we’ve created static pages
In this post we’ll learn:
- How to style our app with Bootstrap
- Showing list of all tasks
- Adding tasks
- Deleting Tasks
- Completing Tasks
Styling our app with Bootstrap
Our app is little bit boring, just html, no css. Let’s do something about that we will use Bootstrap to take care of basic design
Get started with Bootstrap
Bootstrap is a framework for easier developing websites. To use it we have to install bootstrap-sass gem
Gemfile
$ gem 'bootstrap-sass'
$ bundle install
Styling Navigation with Boostrap
Let’s make our navigation prettier. Go to the app/views/layouts and add this code right after body.
app/views/layouts/application.html.slim
header.navbar
.navbar-inner
.container
nav
ul.nav.navbar-nav
li = link_to "Home", root_path
li = link_to "Help", help_path
li = link_to "About", about_path
To work we need to import bootstrap from our extended css file. Create file main.css.scss in app/assets/stylesheets directory.
What is scss
Scss is improved CSS, it allow us to use variables, and removes some repetitions in our commands. If you want to know more about Sass I recommend you pragmatic guide to sass book
Add this code to main.css.scss
app/assets/stylesheets/main.css.scss
@import "bootstrap";
So we have bootstrap imported,let’s look at the home page:
So we’ve applied some styling let’s move on showing list of tasks
Showing list of all tasks
To manipulate tasks we need to create a new controller:
$ rails generate controller Tasks
That’s fine, but we need to record tasks do database. Writing all sql code would be tedious, fortunately there is Active Record to help.
What is Active Record
Active Record is Object-Relation-Mapping Software, that means that maps row in database to Ruby object. You don’t have to write sql, you can use Ruby. If you want to know more about Active Record you can try this site
Creating Models
We need to create models to add records to database.
$ rails generate model Task name:string
Our model isn’t already in database, we need to move our migration. Go to db/migrate folder. You should see migration create_tasks.rb
$ rake db:migrate
And move it for test purposes.
$ rake db:test:prepare
Let’s go to rails console and create some tasks.
$ rails console
You saw that we’ve created task with name Pay a bill. That’s fine it works in console, but to view it in browser we need to add some code to our controllers and views.
First we’ll generate list of all tasks. We want to generate lists of tasks on home page, so we’ll use static_pages_controller, otherwise we would use tasks route with tasks controller. Go to app/controllers/static_pages_controller.rb and add this code.
app/controllers/static_pages_controller.rb
def home
@tasks = Task.all
This code will check all instances of object Task and save it into variable @tasks
We want to show all tasks on home page, open up app/views/static_pages/home.html.slim and add following code:
app/views/static_pages/home.html.slim
ul#tasks
- @tasks.each do |task|
li = task.name
In this code we connect view with static_pages controller and with each method we loop through all the tasks and add their names to unordered list.
Go to checkout change in browser. You should see list with our recently created tasks.
Adding Tasks
It works, with some problems. Creating task from rails console would be really boring. We need to create task form to do it for us.
First we write up some tests. Create new file tasks.feature
features/tasks.feature
Feature: Task Manipulation
In order to use my todo list app I want to add, remove and complete my tasks.
Scenario: Adding task
When I add a new task
Then I should see task added message
Now write some step definitions.
features/step_definitions/tasks_steps.rb
When /^I add a new task$/ do
visit root_path
fill_in 'task_name', :with => 'Pay a bill'
expect { click_button "Add task" }.to change(Task, :count).by(1)
end
We used capybara methods to fill in form for task. fill_in method fills text field task_name and click button with value Add task, then it count number of tasks and if one more tasks has been it will pass.
Try it now. Our tests should fail.
We need to add routes for tasks. To save the time we use resources to generate all the routes for tasks.
config/routes.rb
resources :tasks
Let’s try to create our form.
app/views/static_pages/home.html.slim
= form_for :task, url: tasks_path do |f|
= f.label :name
= f.text_field :name
= f.submit "Add task", class: "btn btn-large btn-primary"
Maybe you wonder what form_for method does. Take look at this at the browser.
If you look at it with Firebug, Chrome Developer Tools or something like that, you should see that form_for method will create html code as you see in the picture.
Form is created but it doesn’t work because we didn’t add controller.
app/controllers/tasks_controller.rb
def create
@task = Task.new(task_params)
@task.save
redirect_to root_path
Maybe you wonder what means task_params. Due to security issues Rails developers add some safer code to create new objects.
Add this code to bottom of the tasks_controller
app/controllers/tasks_controller.rb
private
def task_params
params.require(:task).permit(:name)
end
We’ve completed our test let’s move on second. We need to add Task added Message. To display success or error messages with controller we will use flash hash.
Let’s write test first.
features/step_definitions/tasks_steps.rb
Then /^I should see task added message$/ do
expect(page).to have_content "Task added"
end
Our test should fail. To work we need to add flash notice to our controller and add it to layout.
app/controllers/tasks_controller.rb
flash[:notice] = "Task added"
application.html.slim
- if notice
= notice
- if alert
= alert
Try to run test now you should see passing tests. If you go to web browser you should see task added message at the top of the page
Removing Tasks
If we’ll complete our tasks we should remove them from tasks list. First we’ll write some tests
features/tasks.feature
When I delete task
Then I should see task deleted message
features/step_definitions/tasks_steps.rb
When /^I delete task$/ do
visit root_path
expect { click_button "Delete task" }.to change(Task, :count).by(-1)
end
Then /^I should see task deleted message$/ do
expect(page).to have_content("Task deleted")
end
Our tests should fail. We need to add some code to our views and controllers.
def destroy
@task = Task.find(params[:id])
@task.destroy
redirect_to root_path
flash[:alert] = "Task deleted"
This code will find Task by id and saves it to variable and then it will use Active Record destroy method to remove it.
Add this code to home.html.slim
app/views/static_pages/home.html.slim
= link_to 'Delete task', task, method: :delete
If you test it right now. You should see passing tests.
Completing Tasks
We want to complete tasks and display it as completed. To do it we need to add boolean completed to our tasks table.
rails generate migration AddCompletedToTask
We need to edit our migration manually. Open up db/migrate/add_completed_to_task.rb and add some content:
db/migrate/add_completed_to_task.rb
def change
add_column :tasks, :completed, :boolean
end
We need to add new route.
config/routes.rb
match 'tasks/complete' => 'tasks#complete' :via => post
And view and controller
app/views/static_pages/home.html.slim
= form_tag("/tasks/complete", :method => "post") do
ul#tasks
- @tasks.each do |task|
- if task.completed == true
li[style="color:grey;"]
= check_box_tag "tasks_checkbox[]",task.id
strike
= task.name
= link_to 'Delete task', task, method: :delete
- else
li
= check_box_tag "tasks_checkbox[]",task.id
= task.name
= link_to 'Delete task', task, method: :delete
= submit_tag("Complete Task", class: "btn btn-large btn-primary")
= form_for :task, url: tasks_path do |f|
= f.label :name
= f.text_field :name
= f.submit "Add task", class: "btn btn-large btn-primary"
We checkout if task has attribute completed set to true and if it does we use strike and little css styling.
app/controllers/tasks_controller.rb
def complete
params[:tasks_checkbox].each do |check|
task_id = check
t = Task.find_by_id(task_id)
t.update_attribute(:completed, true)
flash[:notice] = "Task completed"
end
redirect_to root_path
end
What’s next
In next tutorial we’ll look at user authentication.