Статьи

Простая интернационализация для вашего Rails-приложения с BDD, часть IV

Удаление местоположения.

В первой части мы создали приложение, в котором перечислены местоположения на английском и испанском языках.

Во второй части мы добавили возможность создавать локации на английском и испанском языках.

В третьей части мы добавили возможность редактировать локации на английском и испанском языках.

В этой части мы удалим местоположение, показывая пути локализации. Помните, что мы используем Cucumber и BDD для запуска нашего приложения.

Почему бы нам не настроить четыре местоположения, а затем удалить третье местоположение?

Как бы вы написали сценарий для этого? Мы можем написать сценарий как:

@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 |

Это даст нам четыре местоположения, убедитесь, что мы находимся на английском сайте, удалите третье местоположение и оставим три местоположения. Также обратите внимание, что мы снова используем тег @wip, чтобы сосредоточиться на этом тесте.

Теперь давайте запустим 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/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.

view raw
gistfile1.sh
hosted with ❤ by GitHub

По крайней мере один из предыдущих шагов, которые мы написали, используется. Давайте реализуем определения шагов для неопределенных. Откройте файл features / step_definitions / location_step.rb и скопируйте в ожидающие шаги.

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

На первом этапе нам нужно, чтобы заводская девушка создала все локации. Этот шаг будет повторяться, сколько бы мы ни говорили, создавая местоположение на каждой итерации. Почему бы нам не назвать их «Местоположение 1», «Место 2» и так далее.

Каков был бы способ сделать это? Вот как я это сделал.

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

Вероятно, не самый шикарный, но вы можете изменить его после того, как он пройдет. Давайте запустим огурец и посмотрим, что получится.

$ 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.

view raw
gistfile1.sh
hosted with ❤ by GitHub

Мы создаем места, которые никому не нужны.

Время реализовать шаг удаления местоположения :

  • перейти на страницу индекса местоположений
  • найти третье место в списке
  • нажмите на ссылку, чтобы удалить ее

Как вы находите третье место в списке?

В Капибаре вы можете использовать обзор. Например, мы можем заглянуть в список, чтобы найти n-го ребенка.

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

Сначала мы идем на страницу индекса для определения местоположения. Так как мы ищем 3-е место, мы ищем его в списке. Как только ссылка найдена, clink_link

Давайте запустим огурец и посмотрим, что получится.

$ 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.

view raw
gistfile1.sh
hosted with ❤ by GitHub

Ссылка на уничтожение пока не существует. Откройте файл app / views / location / index.html.erb и добавьте ссылку уничтожения.

<%= link_to «Delete», location, confirm: ‘Are you sure?’, method: :delete %>

view raw
gistfile1.rb
hosted with ❤ by GitHub

Сохраните файл и запустите 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.

view raw
gistfile1.sh
hosted with ❤ by GitHub

Огурец жалуется на отсутствие удаляемого маршрута. Мы были здесь раньше с шоу и обновлением. Вот файл /config/route.rb

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

view raw
gistfile1.rb
hosted with ❤ by GitHub

Сохраните файл и запустите 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.

view raw
gistfile1.sh
hosted with ❤ by GitHub

Никаких действий для «уничтожить». Откройте файл /app/controllers/locations_controller.rb и добавьте этот метод.

# 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

view raw
gistfile1.rb
hosted with ❤ by GitHub

Сохраните файл и запустите 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.

view raw
gistfile1.sh
hosted with ❤ by GitHub

Теперь для реализации следующего шага. Если у нас было четыре местоположения, и мы удаляем одно, то у нас должно быть три (Math is FUN). Итак, количество мест должно равняться трем? Ответ в вопросе

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

Я чувствую, что это сработает. О, Огурец, пожалуйста, дайте нам знать, если мы сдадимся.

$ 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 didnt expect anything to pass. These scenarios passed:
(::) passed scenarios (::)
features/managing_locations.feature:50:in `| en | Delete |

view raw
gistfile1.sh
hosted with ❤ by GitHub

GREEN! Перерыв.

Боррар Ubicación

Настало время сделать то же самое на испанском. Помните, что делать? Бинго. Вы добавляете новую строку в таблицу примера.

@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 |

Как вы думаете, это будет зеленым? Посмотрим.

$ 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 didnt expect anything to pass. These scenarios passed:
(::) passed scenarios (::)
features/managing_locations.feature:50:in `| en | Delete |

view raw
gistfile1.sh
hosted with ❤ by GitHub

D’о! Мы еще даже не смотрели на локали. Нам нужно добавить перевод для destroy — на английском и испанском языках. Вы помните, где они и что должно произойти?

конфиг / локали / 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«

view raw
gistfile1.yml
hosted with ❤ by GitHub

confif / локали / 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«

view raw
gistfile1.yml
hosted with ❤ by GitHub

Что еще нам нужно сделать? Ты понял. Измените ссылку уничтожения, чтобы показать перевод.

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

view raw
gistfile1.rb
hosted with ❤ by GitHub

Все зеленые, пожалуйста.

$ 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 didnt 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 |

view raw
gistfile1.sh
hosted with ❤ by GitHub

Ладно, удалите тег wip и бросьте кости …

$ 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

view raw
gistfile1.sh
hosted with ❤ by GitHub

Отправим его! Ок, может и нет. Я уверен, что может произойти еще какой-нибудь рефакторинг и сушка. Взять, к примеру, маршруты.

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

view raw
gistfile1.txt
hosted with ❤ by GitHub

Запустите грабли в терминале, и вы должны увидеть что-то вроде этого.

$ 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

view raw
gistfile1.sh
hosted with ❤ by GitHub

Мы можем перефакторизовать расположение маршрутов одной линией.

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

view raw
gistfile1.rb
hosted with ❤ by GitHub

Теперь перезапускаем рейковые маршруты

$ 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

view raw
gistfile1.sh
hosted with ❤ by GitHub

Я знаю. Почему мы не сделали это в первую очередь? Две причины:

1) Мы просто писали код для прохождения тестов. Только позже мы поняли, что нам нужны они все.
2) Посмотрите, как много вы узнали о магии маршрутов. Часть тайны была раскрыта.

Теперь перезапустите огурец и убедитесь, что все тесты все еще проходят. Твой?

Я знаю, что во время этой серии мы проделали долгий путь. Это вывело тайну из интернационализации? Теперь пришло время сделать ваши приложения глобальными.

Ура,

Джон