Press "Enter" to skip to content

Exercises for Programmers (in Rails): Say Hello

I picked up “Exercises for Programmers” by Brian P. Hogan a while back as a way to test my skills and keep them sharp. Earlier today it occurred to me that since I’m in the process of teaching myself Ruby on Rails, it might be fun to implement these exercises in a Rails app.

Exercise 1: Say Hello

The problem description for exercise 1 says to “create a program that prompts for your name and prints a greeting using your name”. Since I’m using Rails, it’s obvious to me that I want to create form with an text box to receive the name input, and redirect to a page that displays the greeting.

First I created the app.

rails new exercises_for_programmers_rails

Next I created a controller with ‘new’ and ‘show’ views to receive the input and produce the output respectively.

rails g controller say_hello new show

This automatically inserted the following routes into the routes file.

# app/config.routes.rb
Rails.application.routes.draw do
  get 'say_hello/new'
  get 'say_hello/show'
end

Next I updated the auto-generated view file /app/views/say_hello/new.html.erb to include a form with a text field for receiving the ‘name’ input

<%# app/views/say_hello/new.html.erb %>
<h1>Saying Hello</h1>

<p>What is your name?</p>

<%= form_for(:form) do |f| %>
  <%= f.text_field :name %>
  <%= f.submit %>
<% end %>

Then I added a create action to app/controllers/say_hello_controller.rb to capture the input, set it to an instance variable, and redirect to the show view.

# app/controllers/say_hello_controller.rb
  ...
  def create
    @name = params[:form][:name]
    redirect_to say_hello_show_path
  end
  ...
<%# app/views/say_hello/show.html.erb %>
<p>Hello <%= @name %>, It's nice to meet you!</p> <%= link_to 'Ask Name', say_hello_new_path %>

After this, I booted the rails app up running rails s tested it out:

After clicking ‘Save Form’, I was met with the following error:

The issue here was that in app/views/say_hello/new.html.erb, the form_for helper doesn’t specify a url to post to, so it defaults to the the path that points to the current page. Fixing this was a two step process:

First, I added a new route to app/config/routes.rb to associate a post request with the create action on the SayHelloController:

# app/config/routes.rb
Rails.application.routes.draw do
  ...
  post 'say_hello' => 'say_hello#create'
end

Second, I updated form_for helper in app/views/say_hello/new.html.erb to point to the new route:

<%# app/views/say_hello/new.html.erb %>
<h1>Saying Hello</h1>

<p>What is your name?</p>

<%= form_for(:form, url: say_hello_path) do |f| %>
  <%= f.text_field :name %>
  <%= f.submit %>
<% end %>

Testing it again fixes the routing error:

But in the resulting page, the name isn’t displayed:

That’s when it occurs to me that every time the browser makes a request to a Rails app, and the rails router maps that request to a controller action, a new instance of the controller is created. So all state set by a previous request (including the instance variable @name set by the create method) disappears.

Typically Rails applications use ActiveRecord to persist state to a database which is queried for each requests. That seems like overkill for such a simple exercise, so instead I create a singleton object to cache the name in the controllers create  action, and retrieve it in the show action.  Below shows the updated and complete source for the SayHelloController.

# app/controllers/say_hello_controller.rb
class SayHelloController < ApplicationController
  def new
  end

  def show
    # @name = SayHello.get_instance.name
  end

  def create
    @name = params[:form][:name]
    # SayHello.get_instance.name = @name
    redirect_to say_hello_show_path
  end
end

class SayHello
  @@instance = SayHello.new

  attr_accessor :name
  def self.get_instance
    @@instance
  end
end

Testing it again:

yields success:

You can find all the source code in this exercise here.

Thanks for reading!

References

Hogan, Brian P., Exercises for Programmers