This entry is part 4 of 4 in the series I18n for Your Rails App

Deleting a Location.

In the first part, we built an application that lists locations in English and Spanish.

In the second part, we added the ability to create locations in English and Spanish.

In the third part, we added the ability to edit locations in English and Spanish.

In this part we will delete a location, showing localization concerns along the way. Remember, we are using Cucumber and BDD to drive out our application.

Why don’t we set up four locations and then delete the third location?

How would you write the scenario to do that? We can write the scenario like:

@wip
Scenario Outline: Delete a location
Given there are 4 locations
And I am on the <language> site
And I "<action>" the 3rd location
Then I should see 3 locations

Examples:
| language | action |
| en | Delete |

That will give us the four locations, make sure we are on the English site, delete the third location, and have three locations left. Also, notice we’re using the @wip tag again so we can focus on this test.

Now let’s run Cucumber and see what happens.

 cucumber --profile wip
Using the wip profile...
Feature: Manage locations
  In order to manage locations
  As a user
  I want to create and edit my locations.

  @wip
  Scenario Outline: Delete a location # features/managing_locations.feature:42
    Given there are 4 locations # features/managing_locations.feature:43
    And I am on the <language> site # features/step_definitions/location_steps.rb:5
    And I "<action>" the 3rd location # features/managing_locations.feature:45
    Then I should see 3 locations # features/managing_locations.feature:46

    Examples:
      | language | action |
      | en | Delete |

1 scenario (1 undefined)
4 steps (1 skipped, 3 undefined)
0m1.299s

You can implement step definitions for undefined steps with these snippets:

Given /^there are (d+) locations$/ do |arg1|
  pending # express the regexp above with the code you wish you had
end

Given /^I "([^"]*)" the (d+)rd location$/ do |arg1, arg2|
  pending # express the regexp above with the code you wish you had
end

Then /^I should see (d+) locations$/ do |arg1|
  pending # express the regexp above with the code you wish you had
end


The --wip switch was used, so the failures were expected. All is good.

At least one of the prevoius steps we wrote is being used. Let’s implement step definitions for the undefined ones. Open up the features/step_definitions/location_step.rb file and copy in the pending steps.

Given /^there are (d+) locations$/ do |arg1|
pending # express the regexp above with the code you wish you had
end

Given /^I "([^"]*)" the (d+)rd location$/ do |arg1, arg2|
pending # express the regexp above with the code you wish you had
end

Then /^I should see (d+) locations$/ do |arg1|
pending # express the regexp above with the code you wish you had
end

In the first step we need to get factory girl to create all of the locations. The step will loop through however many times we tell it, creating a location on each iteration. Why don’t we name them “Location 1″, “Location 2″, and so on.

What would be a way to do that? Here’s how I did it.

Given /^there are (d+) locations$/ do |number|
number.to_i.times { |x| Factory(:location, :name => x+1) }
end

Probably not the swankiest but you can re-factor later after it passes. Let’s run Cucumber and see what happens.

$ cucumber --profile wip
Using the wip profile...
Feature: Manage locations
  In order to manage locations
  As a user
  I want to create and edit my locations.

  @wip
  Scenario Outline: Delete a location # features/managing_locations.feature:42
    Given there are 4 locations # features/step_definitions/location_steps.rb:40
    And I am on the <language> site # features/step_definitions/location_steps.rb:5
    And I "<action>" the 3rd location # features/step_definitions/location_steps.rb:44
    Then I should see 3 locations # features/step_definitions/location_steps.rb:48

    Examples:
      | language | action |
      | en | Delete |
      TODO (Cucumber::Pending)
      ./features/step_definitions/location_steps.rb:45:in `/^I "([^"]*)" the (d+)rd location$/'
features/managing_locations.feature:45:in `And I "<action>" the 3rd location'

1 scenario (1 pending)
4 steps (1 skipped, 1 pending, 2 passed)
0m1.410s

The --wip switch was used, so the failures were expected. All is good.

We’re creating locations like nobody’s business.

Time to implement delete location step:

  • go to the locations index page
  • find the third location in the list
  • click some link to delete it

How do you find the third location in the list?

In Capybara you can use scoping. For example we can look within a list to find an nth child.

Given /^I (.*) the (d+)rd location$/ do |action, pos|
visit(locations_path)
within("ul li:nth-child(#{pos.to_i})") do
click_link(action)
end
end

First, we go to the index page for locations. Since we are looking for the 3rd location, we look within the list for it. Once the link is found, clink_link deletes the location.

Let’s run Cucumber and see what happens.

$ cucumber --profile wip
Using the wip profile...
Feature: Manage locations
  In order to manage locations
  As a user
  I want to create and edit my locations.

  @wip
  Scenario Outline: Delete a location # features/managing_locations.feature:42
    Given there are 4 locations # features/step_definitions/location_steps.rb:33
    And I am on the <language> site # features/step_definitions/location_steps.rb:5
    And I "<action>" the 3rd location # features/step_definitions/location_steps.rb:37
    Then I should see 3 locations # features/step_definitions/location_steps.rb:44

    Examples:
      | language | action |
      | en | Delete |
      no link with title, id or text '"Delete"' found (Capybara::ElementNotFound)
      (eval):2:in `click_link'
./features/step_definitions/location_steps.rb:40:in `block (2 levels) in <top (required)>'
      ./features/step_definitions/location_steps.rb:39:in `/^I (.*) the (d+)rd location$/'
features/managing_locations.feature:45:in `And I "<action>" the 3rd location'

Failing Scenarios:
cucumber -p wip features/managing_locations.feature:42 # Scenario: Delete a location

1 scenario (1 failed)
4 steps (1 failed, 1 skipped, 2 passed)
0m2.200s

The --wip switch was used, so the failures were expected. All is good.

The destroy link does not exist, yet. Open up the app/views/locations/index.html.erb file and add the destroy link.

<%= link_to "Delete", location, confirm: 'Are you sure?', method: :delete %>

Save the file and run Cucumber.

$ cucumber --profile wip
Using the wip profile...
Feature: Manage locations
  In order to manage locations
  As a user
  I want to create and edit my locations.

  @wip
  Scenario Outline: Delete a location # features/managing_locations.feature:42
    Given there are 4 locations # features/step_definitions/location_steps.rb:33
    And I am on the <language> site # features/step_definitions/location_steps.rb:5
    And I <action> the 3rd location # features/step_definitions/location_steps.rb:37
    Then I should see 3 locations # features/step_definitions/location_steps.rb:44

    Examples:
      | language | action |
      | en | Delete |
      No route matches [DELETE] "/en/locations/3" (ActionController::RoutingError)
      (eval):2:in `click_link'
./features/step_definitions/location_steps.rb:40:in `block (2 levels) in <top (required)>'
      ./features/step_definitions/location_steps.rb:39:in `/^I (.*) the (d+)rd location$/'
features/managing_locations.feature:45:in `And I <action> the 3rd location'

Failing Scenarios:
cucumber -p wip features/managing_locations.feature:42 # Scenario: Delete a location

1 scenario (1 failed)
4 steps (1 failed, 1 skipped, 2 passed)
0m2.194s

The --wip switch was used, so the failures were expected. All is good.

Cucumber complains about the lack of a delete route. We’ve been here before with show and update. Here’s the /config/route.rb file

International::Application.routes.draw do
  scope '(:locale)' do
    match 'locations/' => 'locations#index', :as => :locations, :via => [:get]
    match 'locations/new' => 'locations#new', :as => :new_location
    match "locations" => 'locations#create', :as => :locations, :via => [:post]
    match "locations/(:id)" => 'locations#show', :as => :location, :via => [:get]
    match 'locations/:id/edit' => 'locations#edit', :as => :edit_location
    match "locations/(:id)" => 'locations#update', :as => :location, :via => [:put]
    match "locations/(:id)" => 'locations#destroy', :as => :location, :via => [:delete]
    root :to => 'locations#index'
  end
end

Save the file and run Cucumber.

$ cucumber --profile wip
Using the wip profile...
Feature: Manage locations
  In order to manage locations
  As a user
  I want to create and edit my locations.

  @wip
  Scenario Outline: Delete a location # features/managing_locations.feature:42
    Given there are 4 locations # features/step_definitions/location_steps.rb:33
    And I am on the <language> site # features/step_definitions/location_steps.rb:5
    And I <action> the 3rd location # features/step_definitions/location_steps.rb:37
    Then I should see 3 locations # features/step_definitions/location_steps.rb:44

    Examples:
      | language | action |
      | en | Delete |
      The action 'destroy' could not be found for LocationsController (AbstractController::ActionNotFound)
      (eval):2:in `click_link'
./features/step_definitions/location_steps.rb:40:in `block (2 levels) in <top (required)>'
      ./features/step_definitions/location_steps.rb:39:in `/^I (.*) the (d+)rd location$/'
features/managing_locations.feature:45:in `And I <action> the 3rd location'

Failing Scenarios:
cucumber -p wip features/managing_locations.feature:42 # Scenario: Delete a location

1 scenario (1 failed)
4 steps (1 failed, 1 skipped, 2 passed)
0m2.465s

The --wip switch was used, so the failures were expected. All is good.

No action for ‘destroy’. Open up the /app/controllers/locations_controller.rb file and add that method.

  # DELETE /locations/1
  # DELETE /locations/1.json
  def destroy
    @location = Location.find(params[:id])
    @location.destroy
      respond_to do |format|
      format.html { redirect_to locations_url }
      format.json { head :ok }
    end
  end

Save the file and run Cucumber.

$ cucumber --profile wip
Using the wip profile...
Feature: Manage locations
  In order to manage locations
  As a user
  I want to create and edit my locations.

  @wip
  Scenario Outline: Delete a location # features/managing_locations.feature:42
    Given there are 4 locations # features/step_definitions/location_steps.rb:33
    And I am on the <language> site # features/step_definitions/location_steps.rb:5
    And I <action> the 3rd location # features/step_definitions/location_steps.rb:37
    Then I should see 3 locations # features/step_definitions/location_steps.rb:44

    Examples:
      | language | action |
      | en | Delete |
      TODO (Cucumber::Pending)
      ./features/step_definitions/location_steps.rb:45:in `/^I should see (d+) locations$/'
features/managing_locations.feature:46:in `Then I should see 3 locations'

1 scenario (1 pending)
4 steps (1 pending, 3 passed)
0m2.315s

The --wip switch was used, so the failures were expected. All is good.

Now to implement the next step. If we had four locations and we delete one, then we should have three (Math is FUN). So, Location count should equal three? The answer is in the question

Then /^I should see (d+) locations$/ do |number|
Location.count.should == number.to_i
end

I feel that will work. Oh Cucumber, please let us know if we pass.

$ cucumber --profile wip
Using the wip profile...
Feature: Manage locations
  In order to manage locations
  As a user
  I want to create and edit my locations.

  @wip
  Scenario Outline: Delete a location # features/managing_locations.feature:42
    Given there are 4 locations # features/step_definitions/location_steps.rb:33
    And I am on the <language> site # features/step_definitions/location_steps.rb:5
    And I <action> the 3rd location # features/step_definitions/location_steps.rb:37
    Then I should see 3 locations # features/step_definitions/location_steps.rb:44

    Examples:
      | language | action |
      | en | Delete |

1 scenario (1 passed)
4 steps (4 passed)
0m2.295s

The --wip switch was used, so I didn't expect anything to pass. These scenarios passed:
(::) passed scenarios (::)

features/managing_locations.feature:50:in `| en | Delete |'

GREEN! Break time.

Borrar a Ubicación

It is time to do the same thing in Spanish. Remember what to do? Bingo. You add a new line to example’s table.

@wip
Scenario Outline: Delete a location
Given there are 4 locations
And I am on the <language> site
And I <action> the 3rd location
Then I should see 3 locations

Examples:
| language | action |
| en | Delete |
| es | Borrar |

Do you think this will be green? Let’s see.

$ cucumber --profile wip
Using the wip profile...
Feature: Manage locations
  In order to manage locations
  As a user
  I want to create and edit my locations.

  @wip
  Scenario Outline: Delete a location # features/managing_locations.feature:42
    Given there are 4 locations # features/step_definitions/location_steps.rb:33
    And I am on the <language> site # features/step_definitions/location_steps.rb:5
    And I <action> the 3rd location # features/step_definitions/location_steps.rb:37
    Then I should see 3 locations # features/step_definitions/location_steps.rb:44

    Examples:
      | language | action |
      | en | Delete |
      | es | Borrar |
      no link with title, id or text 'Borrar' found (Capybara::ElementNotFound)
      (eval):2:in `click_link'
./features/step_definitions/location_steps.rb:40:in `block (2 levels) in <top (required)>'
      ./features/step_definitions/location_steps.rb:39:in `/^I (.*) the (d+)rd location$/'
features/managing_locations.feature:45:in `And I <action> the 3rd location'

Failing Scenarios:
cucumber -p wip features/managing_locations.feature:42 # Scenario: Delete a location

2 scenarios (1 failed, 1 passed)
8 steps (1 failed, 1 skipped, 6 passed)
0m2.436s

The --wip switch was used, so I didn't expect anything to pass. These scenarios passed:
(::) passed scenarios (::)

features/managing_locations.feature:50:in `| en | Delete |'

D’oh! We haven’t even looked at the locales yet. We need to add the translation for destroy – both in English and Spanish. Do you remember where those are and what needs to happen?

config/locales/en.yml

# Sample localization file for English. Add more files in this directory for other locales.
# See https://github.com/svenfuchs/rails-i18n/tree/master/rails%2Flocale for starting points.

en:
    helpers:
        submit:
            create: "Create %{model}"
            update: "Update %{model}"
    
    locations:
        index:
            title_html: "Locations"
            destroy_html: "Delete"
        form:
            name_html: "Name"

confif/locales/es.yml

# Sample localization file for English. Add more files in this directory for other locales.
# See https://github.com/svenfuchs/rails-i18n/tree/master/rails%2Flocale for starting points.

es:
    helpers:
        submit:
            create: "crear %{model}"
            update: "Actualizar %{model}"

    locations:
        index:
            title_html: "Locaciones"
            destroy_html: "Borrar"
        form:
            name_html: "Nombre"

What else do we need to do? You got it. Change the destroy link to show the translation.

<%= link_to t('.destroy_html'), location, confirm: 'Are you sure?', method: :delete %>

All green please.

$ cucumber --profile wip
Using the wip profile...
Feature: Manage locations
  In order to manage locations
  As a user
  I want to create and edit my locations.

  @wip
  Scenario Outline: Delete a location # features/managing_locations.feature:42
    Given there are 4 locations # features/step_definitions/location_steps.rb:33
    And I am on the <language> site # features/step_definitions/location_steps.rb:5
    And I <action> the 3rd location # features/step_definitions/location_steps.rb:37
    Then I should see 3 locations # features/step_definitions/location_steps.rb:44

    Examples:
      | language | action |
      | en | Delete |
      | es | Borrar |

2 scenarios (2 passed)
8 steps (8 passed)
0m2.288s

The --wip switch was used, so I didn't expect anything to pass. These scenarios passed:
(::) passed scenarios (::)

features/managing_locations.feature:50:in `| en | Delete |'

features/managing_locations.feature:51:in `| es | Borrar |'

Ok, remove the wip tag and roll the dice…

$ cucumber
Using the default profile...
Feature: Manage locations
  In order to manage locations
  As a user
  I want to create and edit my locations.

  Scenario Outline: List locations # features/managing_locations.feature:6
    Given there is a location named "<location>" # features/step_definitions/location_steps.rb:1
    And I am on the <language> site # features/step_definitions/location_steps.rb:5
    When I am on the locations page # features/step_definitions/location_steps.rb:9
    Then I should see "<title>" # features/step_definitions/location_steps.rb:13
    And I should see "<location>" # features/step_definitions/location_steps.rb:13

    Examples:
      | location | language | title |
      | location 1 | en | Locations |
      | location 2 | es | Locaciones |

  Scenario Outline: : Create a new location # features/managing_locations.feature:18
    Given I am on the <language> site # features/step_definitions/location_steps.rb:5
    And I am on new location page # features/step_definitions/location_steps.rb:21
    And I fill in "<name>" with "<location>" # features/step_definitions/location_steps.rb:25
    And press "<button>" # features/step_definitions/location_steps.rb:29
    Then I should see "<location>" # features/step_definitions/location_steps.rb:13

    Examples:
      | language | name | location | button |
      | en | Name | location 1 | Create |
      | es | Nombre | location 1 | crear |

  Scenario Outline: Edit a location # features/managing_locations.feature:30
    Given I am on the <language> site # features/step_definitions/location_steps.rb:5
    And there is a location named "<location>" # features/step_definitions/location_steps.rb:1
    When I "<action>" the location "<field>" to "<new_name>" # features/step_definitions/location_steps.rb:33
    Then I should see "<new_name>" # features/step_definitions/location_steps.rb:13

    Examples:
      | language | location | action | field | new_name |
      | en | location 1 | Update | Name | location has changed |
      | es | location 1 | Actualizar | Nombre | location has changed |

  Scenario Outline: Delete a location # features/managing_locations.feature:42
    Given there are 4 locations # features/step_definitions/location_steps.rb:40
    And I am on the <language> site # features/step_definitions/location_steps.rb:5
    And I <action> the 3rd location # features/step_definitions/location_steps.rb:44
    Then I should see 3 locations # features/step_definitions/location_steps.rb:51

    Examples:
      | language | action |
      | en | Delete |
      | es | Borrar |

8 scenarios (8 passed)
36 steps (36 passed)
0m2.658s

Ship it! Ok, maybe not. I’m sure some more re-factoring and DRYing up could go on. Take the routes for example.

International::Application.routes.draw do
  scope '(:locale)' do
    match 'locations/' => 'locations#index', :as => :locations, :via => [:get]
    match "locations" => 'locations#create', :via => [:post]
    match 'locations/new' => 'locations#new', :as => :new_location
    match 'locations/:id/edit' => 'locations#edit', :as => :edit_location
    match "locations/(:id)" => 'locations#show', :as => :location, :via => [:get]
    match "locations/(:id)" => 'locations#update', :via => [:put]
    match "locations/(:id)" => 'locations#destroy', :via => [:delete]
    root :to => 'locations#index'
  end
end

Run rake routes in the terminal and you should see something like this.

$ rake routes
    locations GET (/:locale)/locations(.:format) locations#index
              POST (/:locale)/locations(.:format) locations#create
 new_location (/:locale)/locations/new(.:format) locations#new
edit_location (/:locale)/locations/:id/edit(.:format) locations#edit
     location GET (/:locale)/locations(/:id)(.:format) locations#show
              PUT (/:locale)/locations(/:id)(.:format) locations#update
              DELETE (/:locale)/locations(/:id)(.:format) locations#destroy
         root /(:locale)(.:format) locations#index

We can re-factor the up the location routes with one line.

International::Application.routes.draw do
  scope '(:locale)' do
    resources :locations
    root :to => 'locations#index'
  end
end

Now rerun rake routes

$ rake routes
    locations GET (/:locale)/locations(.:format) locations#index
              POST (/:locale)/locations(.:format) locations#create
 new_location GET (/:locale)/locations/new(.:format) locations#new
edit_location GET (/:locale)/locations/:id/edit(.:format) locations#edit
     location GET (/:locale)/locations/:id(.:format) locations#show
              PUT (/:locale)/locations/:id(.:format) locations#update
              DELETE (/:locale)/locations/:id(.:format) locations#destroy
         root /(:locale)(.:format) locations#index

I know. Why didn’t we do that in the first place? Two reasons:

1) We were just writing code to make the tests pass. It wasn’t until later that we knew we needed them all.
2) Look at how much you’ve learned about the magic of routes. Some of the mystery has been revealed.

Now, rerun cucumber and make sure all the tests still pass. Did yours?

I know during this series we did some stuff the long way. Did it take the mystery out of internationalization? Now it’s time to take your applications global.

 

Cheers,

John

Series Navigation<< Easy Internationalization for Your Rails App with BDD, Part III