⽤ Bootstrap 做⼀個「有 Nav bar 」的 layout

首先寫測試來檢查畫面上是否有 Nav bar 的 CSS,這裡我們要撰寫 Feature spec,要搭配一個 gem Capybara。

安裝 & 配置 Capybara

group :development do
  ...
  gem 'spring-watcher-listen', '~> 2.0.0'
end

group :test do
  gem 'capybara', '~> 2.11.0' # <--
end

# Windows does not include zoneinfo files, so bundle the tzinfo-data gem
gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby]

bundle install

編輯 spec/rails_helper.rb 加入兩行 require:

# Add additional requires below this line. Rails is not loaded until this point!
Dir[Rails.root.join("spec/support/**/*.rb")].sort.each { |file| require file }
require 'capybara/rspec' # <--
require 'capybara/rails' # <--

# Requires supporting ruby files with custom matchers and macros, etc, in

撰寫測試檢查 Navbar

mkdir -p spec/features
touch spec/features/home_spec.rb
require "rails_helper"

RSpec.describe "Home" do
  scenario "has navbar element" do
    visit root_url

    expect(page).to have_css "nav.navbar"
  end
end

跑測試:

$ rspec spec/features/home_spec.rb
F

Failures:

  1) Home has navbar element
     Failure/Error: expect(page).to have_css "nav.navbar"
       expected to find css "nav.navbar" but there were no matches
     # ./spec/features/home_spec.rb:7:in `block (2 levels) in <top (required)>'

Finished in 0.19417 seconds (files took 1.8 seconds to load)
1 example, 1 failure

找不到有 navbar class 的 nav 元素。

修改 app/views/layouts/application.html.erb

<!DOCTYPE html>
<html>
  <head>
    <title>Classroom</title>
    <%= csrf_meta_tags %>

    <%= stylesheet_link_tag    'application', media: 'all', 'data-turbolinks-track': 'reload' %>
    <%= javascript_include_tag 'application', 'data-turbolinks-track': 'reload' %>
  </head>

  <body>
    <div class="container">
      <%= render "common/navbar" %>
      <div class="row">
        <%= yield %>
      </div>
    </div>
  </body>
</html>
rspec spec/features/home_spec.rb
F

Failures:

  1) Home has navbar element
     Failure/Error: <%= render "common/navbar" %>

     ActionView::Template::Error:
       Missing partial common/_navbar with {:locale=>[:en], :formats=>[:html], :variants=>[], :handlers=>[:raw, :erb, :html, :builder, :ruby, :coffee, :jbuilder]}. Searched in:
         * "/Users/ana/dev/classroom/app/views"
     # ./app/views/layouts/application.html.erb:13:in `_app_views_layouts_application_html_erb__3350428300626981296_70296580724620'
     # /Users/Mac/.gem/ruby/2.3.1/gems/rack-2.0.1/lib/rack/etag.rb:25:in `call'
     # /Users/Mac/.gem/ruby/2.3.1/gems/rack-2.0.1/lib/rack/conditional_get.rb:25:in `call'
     # /Users/Mac/.gem/ruby/2.3.1/gems/rack-2.0.1/lib/rack/head.rb:12:in `call'
     # /Users/Mac/.gem/ruby/2.3.1/gems/rack-2.0.1/lib/rack/session/abstract/id.rb:222:in `context'
     # /Users/Mac/.gem/ruby/2.3.1/gems/rack-2.0.1/lib/rack/session/abstract/id.rb:216:in `call'
     # /Users/Mac/.gem/ruby/2.3.1/gems/railties-5.0.0.1/lib/rails/rack/logger.rb:36:in `call_app'
     # /Users/Mac/.gem/ruby/2.3.1/gems/railties-5.0.0.1/lib/rails/rack/logger.rb:24:in `block in call'
     # /Users/Mac/.gem/ruby/2.3.1/gems/railties-5.0.0.1/lib/rails/rack/logger.rb:24:in `call'
     # /Users/Mac/.gem/ruby/2.3.1/gems/rack-2.0.1/lib/rack/method_override.rb:22:in `call'
     # /Users/Mac/.gem/ruby/2.3.1/gems/rack-2.0.1/lib/rack/runtime.rb:22:in `call'
     # /Users/Mac/.gem/ruby/2.3.1/gems/rack-2.0.1/lib/rack/sendfile.rb:111:in `call'
     # /Users/Mac/.gem/ruby/2.3.1/gems/railties-5.0.0.1/lib/rails/engine.rb:522:in `call'
     # /Users/Mac/.gem/ruby/2.3.1/gems/rack-2.0.1/lib/rack/urlmap.rb:68:in `block in call'
     # /Users/Mac/.gem/ruby/2.3.1/gems/rack-2.0.1/lib/rack/urlmap.rb:53:in `each'
     # /Users/Mac/.gem/ruby/2.3.1/gems/rack-2.0.1/lib/rack/urlmap.rb:53:in `call'
     # /Users/Mac/.gem/ruby/2.3.1/gems/rack-test-0.6.3/lib/rack/mock_session.rb:30:in `request'
     # /Users/Mac/.gem/ruby/2.3.1/gems/rack-test-0.6.3/lib/rack/test.rb:244:in `process_request'
     # /Users/Mac/.gem/ruby/2.3.1/gems/rack-test-0.6.3/lib/rack/test.rb:58:in `get'
     # /Users/Mac/.gem/ruby/2.3.1/gems/capybara-2.11.0/lib/capybara/rack_test/browser.rb:61:in `process'
     # /Users/Mac/.gem/ruby/2.3.1/gems/capybara-2.11.0/lib/capybara/rack_test/browser.rb:36:in `process_and_follow_redirects'
     # /Users/Mac/.gem/ruby/2.3.1/gems/capybara-2.11.0/lib/capybara/rack_test/browser.rb:22:in `visit'
     # /Users/Mac/.gem/ruby/2.3.1/gems/capybara-2.11.0/lib/capybara/rack_test/driver.rb:43:in `visit'
     # /Users/Mac/.gem/ruby/2.3.1/gems/capybara-2.11.0/lib/capybara/session.rb:246:in `visit'
     # /Users/Mac/.gem/ruby/2.3.1/gems/capybara-2.11.0/lib/capybara/dsl.rb:52:in `block (2 levels) in <module:DSL>'
     # ./spec/features/home_spec.rb:5:in `block (2 levels) in <top (required)>'
     # ------------------
     # --- Caused by: ---
     # ActionView::MissingTemplate:
     #   Missing partial common/_navbar with {:locale=>[:en], :formats=>[:html], :variants=>[], :handlers=>[:raw, :erb, :html, :builder, :ruby, :coffee, :jbuilder]}. Searched in:
     #     * "/Users/ana/dev/classroom/app/views"
     #   ./app/views/layouts/application.html.erb:13:in `_app_views_layouts_application_html_erb__3350428300626981296_70296580724620'

Finished in 0.16901 seconds (files took 1.79 seconds to load)
1 example, 1 failure

Failed examples:

rspec ./spec/features/home_spec.rb:4 # Home has navbar element

找不到 Partial。

新增 app/views/common/_navbar.html.erb Partial

mkdir -p app/views/common/
touch app/views/common/_navbar.html.erb
<nav class="navbar navbar-default" role="navigation">
  <div class="container-fluid">
    <!-- Brand and toggle get grouped for better mobile display -->
    <div class="navbar-header">
      <a class="navbar-brand" href="/">Classroom</a>
    </div>

    <!-- Collect the nav links, forms, and other content for toggling -->
    <div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
      <ul class="nav navbar-nav">
        <li class="active">
          <%= link_to("Courses", courses_path) %>
        </li>
      </ul>
      <ul class="nav navbar-nav navbar-right">
        <% if !current_user %>
          <li><%= link_to("Login", new_user_session_path) %></li>
        <% else %>
          <li class="dropdown">
            <a href="#" class="dropdown-toggle" data-toggle="dropdown">
              Hi!, <%= current_user.email %>
              <b class="caret"></b>
            </a>
            <ul class="dropdown-menu">
              <li>
                <%= link_to("Logout", destroy_user_session_path, method: :delete) %>
              </li>
            </ul>
          </li>
        <% end %>
      </ul>
    </div><!-- /.navbar-collapse -->
  </div><!-- /.container-fluid -->
</nav>

跑測試

$ rspec spec/features/home_spec.rb
F

Failures:

  1) Home has navbar element
     Failure/Error: <% if !current_user %>

     ActionView::Template::Error:
       undefined local variable or method `current_user' for #<#<Class:0x007fcbc4fe50c8>:0x007fcbc4fee920>
       Did you mean?  current_page?
     # ./app/views/common/_navbar.html.erb:16:in `_app_views_common__navbar_html_erb___2589854774641260496_70256626731280'
     # ./app/views/layouts/application.html.erb:13:in `_app_views_layouts_application_html_erb___3804411040484863998_70256581682180'
     # /Users/ana/.gem/ruby/2.3.1/gems/rack-2.0.1/lib/rack/etag.rb:25:in `call'
     # /Users/ana/.gem/ruby/2.3.1/gems/rack-2.0.1/lib/rack/conditional_get.rb:25:in `call'
     # /Users/ana/.gem/ruby/2.3.1/gems/rack-2.0.1/lib/rack/head.rb:12:in `call'
     # /Users/ana/.gem/ruby/2.3.1/gems/rack-2.0.1/lib/rack/session/abstract/id.rb:222:in `context'
     # /Users/ana/.gem/ruby/2.3.1/gems/rack-2.0.1/lib/rack/session/abstract/id.rb:216:in `call'
     # /Users/ana/.gem/ruby/2.3.1/gems/railties-5.0.0.1/lib/rails/rack/logger.rb:36:in `call_app'
     # /Users/ana/.gem/ruby/2.3.1/gems/railties-5.0.0.1/lib/rails/rack/logger.rb:24:in `block in call'
     # /Users/ana/.gem/ruby/2.3.1/gems/railties-5.0.0.1/lib/rails/rack/logger.rb:24:in `call'
     # /Users/ana/.gem/ruby/2.3.1/gems/rack-2.0.1/lib/rack/method_override.rb:22:in `call'
     # /Users/ana/.gem/ruby/2.3.1/gems/rack-2.0.1/lib/rack/runtime.rb:22:in `call'
     # /Users/ana/.gem/ruby/2.3.1/gems/rack-2.0.1/lib/rack/sendfile.rb:111:in `call'
     # /Users/ana/.gem/ruby/2.3.1/gems/railties-5.0.0.1/lib/rails/engine.rb:522:in `call'
     # /Users/ana/.gem/ruby/2.3.1/gems/rack-2.0.1/lib/rack/urlmap.rb:68:in `block in call'
     # /Users/ana/.gem/ruby/2.3.1/gems/rack-2.0.1/lib/rack/urlmap.rb:53:in `each'
     # /Users/ana/.gem/ruby/2.3.1/gems/rack-2.0.1/lib/rack/urlmap.rb:53:in `call'
     # /Users/ana/.gem/ruby/2.3.1/gems/rack-test-0.6.3/lib/rack/mock_session.rb:30:in `request'
     # /Users/ana/.gem/ruby/2.3.1/gems/rack-test-0.6.3/lib/rack/test.rb:244:in `process_request'
     # /Users/ana/.gem/ruby/2.3.1/gems/rack-test-0.6.3/lib/rack/test.rb:58:in `get'
     # /Users/ana/.gem/ruby/2.3.1/gems/capybara-2.11.0/lib/capybara/rack_test/browser.rb:61:in `process'
     # /Users/ana/.gem/ruby/2.3.1/gems/capybara-2.11.0/lib/capybara/rack_test/browser.rb:36:in `process_and_follow_redirects'
     # /Users/ana/.gem/ruby/2.3.1/gems/capybara-2.11.0/lib/capybara/rack_test/browser.rb:22:in `visit'
     # /Users/ana/.gem/ruby/2.3.1/gems/capybara-2.11.0/lib/capybara/rack_test/driver.rb:43:in `visit'
     # /Users/ana/.gem/ruby/2.3.1/gems/capybara-2.11.0/lib/capybara/session.rb:246:in `visit'
     # /Users/ana/.gem/ruby/2.3.1/gems/capybara-2.11.0/lib/capybara/dsl.rb:52:in `block (2 levels) in <module:DSL>'
     # ./spec/features/home_spec.rb:5:in `block (2 levels) in <top (required)>'
     # ------------------
     # --- Caused by: ---
     # NameError:
     #   undefined local variable or method `current_user' for #<#<Class:0x007fcbc4fe50c8>:0x007fcbc4fee920>
     #   Did you mean?  current_page?
     #   ./app/views/common/_navbar.html.erb:16:in `_app_views_common__navbar_html_erb___2589854774641260496_70256626731280'

Finished in 0.18746 seconds (files took 1.89 seconds to load)
1 example, 1 failure

Failed examples:

rspec ./spec/features/home_spec.rb:4 # Home has navbar element

找不到 current_usercurrent_user 是 devise gem 實現的一個方法,現在來安裝配置 devise。

安裝 devise

修改 Gemfile

# Use Capistrano for deployment
# gem 'capistrano-rails', group: :development

gem 'devise', '~> 4.2.0' # <--

然後執行 bundle install

接著配置 devise

  • rails g devise:install
  • rails g devise user
  • rake db:migrate

Controller 即 View 就有這些方法可以用:

before_action :authenticate_user!
user_signed_in?
current_user
user_session

測試就跑通了:

$ rspec spec/features/home_spec.rb
.

Finished in 0.20361 seconds (files took 1.99 seconds to load)
1 example, 0 failures

成品

成品會長這樣

results matching ""

    No results matching ""