Grayscale profile picture

Patrique Ouimet

Senior Product Engineer

Ruby on Rails Exploration Part 1

Sun, Nov 25, 2018 2:32 PM

Introduction

This is an overview of things I've learnt while working on my side project anonforum in Ruby on Rails.

Today's topics:

  • Testing
  • Routing
  • Controllers
  • Views (helpers)

Testing! Getting Started

This is everyone's favourite part of development right?!?! Thankfully testing in Rails is very straight forward.

To run tests you simply run:

rails test {file/directory}

Testing: Simple Route Test

I started with a basic test to determine if the index route returned successfully. To do this I started by generating a controller and test:

rails g controller post --no-assets --no-helper
create  app/controllers/post_controller.rb
      invoke  erb
      create    app/views/post
      invoke  test_unit
      create    test/controllers/post_controller_test.rb

test/controllers/post_controller_test.rb

require 'test_helper'
class PostControllerTest < ActionDispatch::IntegrationTest
  test "can see threads" do
    get '/'
    assert_response :success
  end
end

Which failed because the route and controller was not setup, so I updated my routes and controller files then added an index view file app/views/post/index.html.erb.

config/routes.rb

Rails.application.routes.draw do
  get '/' => 'post#index'
end

app/controllers/post_controller.rb

class PostController < ApplicationController
  def index
    render 'index'
  end
end

Now we run the test:

rails test test/controllers/post_controller_test.rb
Run options: --seed 59367

# Running:

.

Finished in 0.795857s, 1.2565 runs/s, 1.2565 assertions/s.
1 runs, 1 assertions, 0 failures, 0 errors, 0 skips

Green!

Now let's make sure posts are being passed to the view.

test/controllers/post_controller_test.rb

require 'test_helper'
class PostControllerTest < ActionDispatch::IntegrationTest
  test "can see threads" do
    get '/'
    assert_response :success
    assert_not_nil assigns(:posts)
  end
end

This will fail as all we're currently doing is rendering the view, so let's make all the post avaiable to the view

app/controllers/post_controller.rb

class PostController < ApplicationController
  def index
    @posts = Post.all
    render 'index'
  end
end

And let's add some content to our index view:

app/views/post/index.html.erb

<ul>
  <% @posts.each do |post| %>
    <li>
      <h5><a href="#"><%= post.title %></a></h5>
      <p><%= post.body %></p>
    </li>
  <% end %>
</ul>

Re-run the test:

rails test test/controllers/post_controller_test.rb
Run options: --seed 10337

# Running:

.

Finished in 0.756118s, 1.3225 runs/s, 2.6451 assertions/s.
1 runs, 2 assertions, 0 failures, 0 errors, 0 skips

Green again!

Now we'd like to be able to create new posts (threads), so let's add a test for seeing the new post (thread) form

test/controllers/post_controller_test.rb

require 'test_helper'
class PostControllerTest < ActionDispatch::IntegrationTest
  test "can see threads" do
    get '/'
    assert_response :success
    assert_not_nil assigns(:posts)
  end

  test "can see new thread form" do
    get '/thread/new'
    assert_response :success
  end
end

This fails as we have yet to define a route for it and a controller method for it and there is no view file, so let's update those now

config/routes.rb

Rails.application.routes.draw do
  get '/' => 'post#index'
  get '/thread/new' => 'post#new'
end

app/controllers/post_controller.rb

class PostController < ApplicationController
  def index
    render 'index'
  end
  def new
    render 'new'
  end
end

Create app/views/post/new.html.erb and add the following

<%= form_tag("/thread") do %>
  <input name="title" type="text" />
  <textarea name="body"></textarea>
  <button type="submit">Submit</button>
<% end %>

I'm sure you noticed the method above form_tag("/thread") do, this generates some content for us we don't want to be bothered with like CSRF tokens, encoding/charset, route action, and http method. Just ignore the /thread part for now we'll adding that in later. The output of the above will look like:

<form action="/thread" accept-charset="UTF-8" method="post">
  <input name="utf8" type="hidden" value="&#x2713;">
  <input type="hidden" name="authenticity_token" value="Z3Gor/8T/h0WkynAH/IufRxRlvEBpUc4QN0dIEbnGDQjCCPBLCOI2qXgKyGXQAnGujj14zSeZBiJsi+PaP9jGA==">
  <input name="title" type="text">
  <textarea name="body"></textarea>
  <button type="submit">Submit</button>
</form>

Re-run the test:

rails test test/controllers/post_controller_test.rb
Run options: --seed 58678

# Running:

..

Finished in 0.761473s, 2.6265 runs/s, 3.9397 assertions/s.
2 runs, 3 assertions, 0 failures, 0 errors, 0 skips

Green again!

Now that the form is setup we need to make sure the route that creates post (threads) works, here's the setup for the test

test/controllers/post_controller_test.rb

require 'test_helper'
class PostControllerTest < ActionDispatch::IntegrationTest
  test "can see threads" do
    get '/'
    assert_response :success
    assert_not_nil assigns(:posts)
  end

  test "can see new thread form" do
    get '/thread/new'
    assert_response :success
  end

  test "can create a thread" do
    get "/thread/new"
    assert_response :success
    post "/thread",
      params: { title: "My New Thread", body: "The body of my new thread" }
    assert_response :redirect
    follow_redirect!
    assert_response :success
    assert_select "a", "My New Thread"
  end
end

This test is a little more involved so we'll take it line by line:

  • test "can create a thread" do

    • naming the test
  • get "/thread/new"

    • go to the new thread (post) form page
  • post "/thread", params: { title: "My New Thread", body: "The body of my new thread" }

    • Do a POST action against the route /thread with the parameters { title: "My New Thread", body: "The body of my new thread" }
  • assert_response :redirect

    • Assert the response is a redirect (302)
  • follow_redirect!

    • Follow the redirect to make new assertions on the redirected page (which will be / in our case)
  • assert_response :success

    • Assert the page redirected to gives us a successful response (200)
  • assert_select "a", "My New Thread"

    • Assert an element on the page of a (anchor tag) has the content My New Thread

All this just to check if a post (thread) was created successfully!

Now we know this will fail as we've setup none of the above, so let's update all the necessary files now

config/routes.rb

Rails.application.routes.draw do
  get '/' => 'post#index'
  get '/thread/new' => 'post#new'
  post '/thread' => 'post#create'
end

app/controllers/post_controller.rb

class PostController < ApplicationController
  def index
    render 'index'
  end
  def new
    render 'new'
  end

  def create
    @post = Post.new(title: params[:title], body: params[:body])
    if @post.save
      redirect_to action: 'index' and return
    else
      render 'new'
    end
  end
end

Now let's try the tests again:

rails test test/controllers/post_controller_test.rb
Run options: --seed 42830

# Running:

...

Finished in 0.775124s, 3.8703 runs/s, 9.0308 assertions/s.
3 runs, 7 assertions, 0 failures, 0 errors, 0 skips

Conclusion

There you have it! I can now create threads (posts) in my application! I ran into a few bumps while trying to figure this out, the documentation isn't very straight forward to navigate but once I found what I was looking for it worked almost flawlessly. Also, there's a few things that were left out like validation error output and the styling of the mark up. If you'd like to see more have a look here https://github.com/patoui/anon-forum.

Feel free to share! Thanks!

Gist