Easy Internationalization for Your Rails App with BDD, Part IV
- Easy Internationalization for Your Rails App with BDD
- Easy Internationalization for Your Rails App with BDD, Part II
- Easy Internationalization for Your Rails App with BDD, Part III
- Easy Internationalization for Your Rails App with BDD, Part IV
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 wipUsing 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 hadend
Given /^I "([^"]*)" the (d+)rd location$/ do |arg1, arg2| pending # express the regexp above with the code you wish you hadend
Then /^I should see (d+) locations$/ do |arg1| pending # express the regexp above with the code you wish you hadend
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 hadend
Given /^I "([^"]*)" the (d+)rd location$/ do |arg1, arg2| pending # express the regexp above with the code you wish you hadend
Then /^I should see (d+) locations$/ do |arg1| pending # express the regexp above with the code you wish you hadend
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) }endProbably not the swankiest but you can re-factor later after it passes. Let’s run Cucumber and see what happens.
$ cucumber --profile wipUsing 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) endendFirst, 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 wipUsing 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 wipUsing 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' endend
Save the file and run Cucumber.
$ cucumber --profile wipUsing 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 endSave the file and run Cucumber.
$ cucumber --profile wipUsing 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_iendI feel that will work. Oh Cucumber, please let us know if we pass.
$ cucumber --profile wipUsing 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 wipUsing 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 wipUsing 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…
$ cucumberUsing 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' endendRun 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#newedit_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' endend
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#newedit_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