20200703のRailsに関する記事は19件です。

メッセージ送信の非同期化

フォームが送信されたら、イベントが発火するようにしよう

スクリーンショット 2020-07-03 22.12.39.png
この記述の解説をします。
$(**)には、formのクラス名を記述します。
.on(
**,にはイベント名を記述します。
e.preventDefaulでは、非同期通信を行う為にデフォルトのイベントを止めています。

イベントが発火したときにAjaxを使用して、messages#createが動くようしましょう

スクリーンショット 2020-07-03 22.17.55.png
この記述の中のthisは、イベントの発火元であるFormの情報が入っています。
$(this).attr('action');は、Form情報のパスを取得しています。

messagesコントローラーの#createアクションでメッセージを保存し、respond_toを使用してJSON形式のリクエストに対してのレスポンスを返せるようにしましょう

スクリーンショット 2020-07-03 22.21.45.png
if @message.save
リクエストで送られてきた情報を保存している
respond_to do |format|
format.json
json方式で返している

その他アウトプット

スクリーンショット 2020-07-03 22.25.20.png
クラス名MessageFieldにappend(html)でHTMLを追加している

$(".submit-btn").prop('disabled', false);

送信ボタンを一度押すとリロードしないと押せなくなるが
prop('disabled', false);を送信ボタンクラスに記述する事によりロードせずに投稿ができる様になる

非同期に失敗した場合の処理

スクリーンショット 2020-07-03 22.31.17.png
失敗した場合エラーをアラートで知らせてくれます。
Doneの後に使います

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

VueでRailsのactive_storageに保存した画像を表示する

Railsアプリ内でVueを使用し、SPAを実装しました。
その中で、VueからRailsのactive_storageを使おうと思いましたがそれっぽい方法を見つけられず、自分でとりあえず実装してみたので残します。
プログラミング初心者かつVue自体も初めてなので間違いもだいぶあると思います。見つけたら指摘していただいたら嬉しいです。

version
Rails 6.0.3.2
ruby 2.6.3
@vue/cli 4.4.1

active_storageに保存するまでは
https://qiita.com/ozin/items/5ec81a4b126b8ebf7a96
この記事でやりました。

このあとvue側に表示するまでを書きます。Vueでは単一ファイルコンポーネントで実装しています。
ライブラリのインストール方法は省略します。

モデル

diary.rb
has_one_attached :image

# 省略

コントローラ

diaries_controller.rb
  def index
    diaries = Diary.all
    diaries_array = []
    diaries.each do |d|
      hash = d.attributes  # インスタンスをハッシュにする
      if d.image.attached?
        # imageのパスを取得
        image = Rails.application.routes.url_helpers.rails_blob_path(d.image)
        hash["image"] = image  # ハッシュに新しいkeyと値を追加
      else
        hash["image"] = ''
      end
      diaries_array << hash  # 配列にハッシュを要素として追加
    end
    render json: diaries_array
  end

axiosでリクエストを送る

diareis.vue
// 省略

<script>
  export default {
    data: function() {
      return {
        diaries: ''
      }
    },
    mounted() {
      let that = this;
      axios.get('/APIなどのパス/'.then(function(response) {
        that.diaries = response.data;
      }
    }
  }
<script>

バインドして表示

diaries.vue
  <template>
    <div>
      <div v-for='diary in diaries' v-bind:key='diary.id' >
        <div v-if='diary.image'>
          <img v-bind:src="diary.image">
        </div>
      </div>
    </div>
  </template>

これで表示できました。
指摘などありましたらお願いします!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

完成したRailsアプリの立ち上げ方

Terminal
bundle install --path vendor/bundle

rails db:migrate

rails s
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Rails5でECサイトを作る① ~アプリ構成、各種gem準備、Model・Routing作成~

はじめに

先日、プログラミングスクールにてチーム実装の課題があり、ECサイトを作りました。
自分の担当したところ以外がどのようなコードで動いているのか、復習も兼ねて作ってみたいと思います。

何のお店のECサイトにするか迷いましたが、Googleフォトのアルバムにパンの写真がたくさん入っていたので、架空のパン屋さんということにします。

コードソース

https://github.com/Sn16799/bakeryFUMIZUKI

環境

Rails 5.2.4.2
Ruby 2.7.1
Centos7

アプリの概要

・ECサイト(ユーザがサイト内で買い物できる)
・ユーザサイトと管理者サイトを制作
・ユーザサイトでは会員登録してログイン後、カートに商品を入れ、注文手続きをする
・管理者サイトでは注文が入った商品の注文ステータス(入金待ち、製作中、……)と製作ステータス(着手不可、製作中、……)の変更、商品や会員の管理を行う

アプリ立ち上げ

$ rails new fumizuki # 7月なので店名(架空)は「ベーカリー文月」で。
$ cd fumizuki

Gemfileに以下を追加

# ログイン機能
gem 'devise'

# view装飾
gem 'bootstrap'
gem 'jquery-rails'

# 画像投稿
gem 'refile', require: "refile/rails", github: 'manfe/refile'
gem 'refile-mini_magick'

# 環境変数の管理
gem 'dotenv-rails', require: 'dotenv/rails-now'

# ページャ
gem 'kaminari','~> 1.1.1'
gem 'nokogiri', '1.10.9'

# デバッグ
gem 'pry-rails'

インストール出来たら

$ rails g devise:install

Model作成

DBの構成は下図の通りです。
管理者サイトもgemを使わず、自作したいと思います。
gemを使う場合は、active_adminが便利でした。
association.jpg
(地道な作業)

$ rails g devise admin email:string
$ rails g devise customer is_active:boolean first_name:string first_name_kana:string family_name:string family_name_kana:string post_code:string address:string tel:string email:string
$ rails g model address customer_id:integer post_code:string addressee:string address:string
$ rails g model cart_item product_id:integer customer_id:integer quantity:integer
$ rails g model genre name:string validity:boolean
$ rails g model order_item product_id:integer order_id:integer quantity:integer order_price:integer make_status:integer
$ rails g model order customer_id:integer addressee:string post_code:string send_to_address:string how_to_pay:boolean deliver_fee:integer order_status:integer
$ rails g model product genre_id:integer name:string introduction:text status:boolean image_id:string price:integer

# 一通り作ったらmigrate
$ rails db:migrate

Routing

Routingも仮のものを用意しておきます。

config/routes.rb
Rails.application.routes.draw do

#rootパス
root 'homes#top'

# 顧客用サイトのrouting
devise_for :customers, controllers: {
    registrations: 'customers/registrations',
    passwords: 'customers/passwords',
    sessions: 'customers/sessions'}

get 'homes/top' => 'homes#top', as: 'customer_top'
get 'homes/about' => 'homes#about', as: 'customer_about'
resources :customers, only: [:edit, :show, :update]
  get 'customers/:id/withdraw' => 'customers#withdraw', as: 'customer_withdraw'
  patch 'customers/:id/withdraw' => 'customers#withdraw_done', as: 'customer_withdraw_done'
  put "/customers/:id/withdraw" => "customers#withdraw_done", as: 'customers_withdraw_done'
resources :orders, only: [:new, :index, :create, :show]
  post 'orders/confirm' => 'orders#confirm', as: 'order_confirm'
  get 'orders/thanks' => 'orders#thanks', as: 'order_thanks'
resources :products, only: [:index, :show]
resources :order_items, only: [:index, :create, :new]
resources :addresses, only: [:index, :create, :edit, :update, :destroy]
resources :genres, only: [:show]

#カートアイテムを全て削除メソッドのために追加
resources :cart_items, only: [:index, :create, :update, :destroy] do
    collection do
        delete 'destroy_all'
    end
end

# 管理者用サイトのrouting
devise_scope :admins do
    devise_for :admins, controllers: {
        registrations: 'admins/registrations',
        passwords: 'admins/passwords',
        sessions: 'admins/sessions'
    }
end

namespace :admins do
    get 'homes/top' => 'homes#top', as:'top'
    resources :customers, only: [:index, :edit, :show, :update]
    resources :products, only: [:index, :create, :new, :edit, :show, :update]
    resources :orders, only: [:index, :create, :show, :update]
    resources :order_items, only: [:index, :create, :show, :update]
    resources :genres, only: [:index, :create, :edit, :update]
    get 'search' => 'searches#search', as: 'search'
end

end

後記

ひとまずModelとRoutingのみ作りました。アクションを書き込んでwebアプリらしい動きをするまで、まだまだ道のりは遠そうです。
それにしても、チーム実装の際に3人で作ったものを1人で作ろうとすると、作業量が尋常じゃないですね。でも、当時はかなり大変だと思っていた量も、今見るとそこまで高い壁でもない気がします。初めてこのアプリを作ったのは3か月前ですが、その間に私も成長したのかも知れません。

この後は、ControllerとViewを揃えたいと思います。次回へ続く!

参考

active_adminの使い方(本記事では使ってないけど)
Railsで最速で管理画面を作る!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Ruby on Rails 住所自動入力実装方法

はじめに

現在のバージョン:
macOS Catalina 10.15.3

Ruby on Railsで住所自動入力を実装してみました。
備忘録として記述します。もし参考になれば幸いです。

手順

(1)はじめに下記からjsファイル(jQueryプラグイン)をダウンロード。
jquery.jpostal.js

(2)ダウンロードしたjsファイルをapp/assets/javascriptsに配置。

(3)Gemfileに下記を追加後、ターミナルで$ bundle install

Gemfile
# RailsでjQueryを使えるようにするため
gem 'jquery-rails'
# 住所機能
gem 'jp_prefecture'

(4)Userモデルに下記カラムを追加後、ターミナルで$ rails db:migrate

ターミナル
rails generate migration AddColumnsToUsers postal_code:string prefecture_code:string address_city:string address_street:string

(5)Userモデルを編集する為、app/models/user.rbに下記追加

user.rb
  include JpPrefecture
  jp_prefecture :prefecture_code

  def prefecture_name
    JpPrefecture::Prefecture.find(code: prefecture_code).try(:name)
  end

  def prefecture_name=(prefecture_name)
    self.prefecture_code = JpPrefecture::Prefecture.find(name: prefecture_name).code
  end

(6)表示をさせるビューに下記追加(本記事はdeviseを導入して新規会員登録画面で実装)app/views/devise/registrations/new.html.erbに追加記述

new.html.erb
<%= f.label :郵便番号 %>
<%= f.text_field :postal_code, autocomplete: "postal_code", id: "customer_postal_code" %>

<%= f.label :都道府県 %>
<%= f.collection_select :prefecture_code, JpPrefecture::Prefecture.all,  :name, :name, autocomplete: "prefecture_code", id: "customer_prefecture_code" %>

<%= f.label :市区町村 %>
<%= f.text_field :address_city,  autocomplete: "address_city", id: "customer_address_city" %>

<%= f.label :町名番地 %>
<%= f.text_field :address_street,  autocomplete: "address_street", id: "customer_address_street" %>

(7)app/assets/javascripts/user.coffeeにjpostalメソッドを呼び出す為記述。

user.coffee
$ ->
  $("#user_postcode").jpostal({
    postcode : [ "#user_postcode" ],
    address  : {
                  "#user_prefecture_code" : "%3",
                  "#user_address_city" : "%4",
                  "#user_address_street" : "%5%6%7"
                }
  })
  # 入力項目フォーマット
    #   %3  都道府県
    #   %4  市区町村
    #   %5  町域
    #   %6  大口事業所の番地
    #   %7  大口事業所の名称

(8)app/controllers/users_controller.rbに下記追加記述

users_controller.rb
  private
  def user_params
    params.require(:user).permit(:postcode, :prefecture_code, :address_city, :address_street) #保存を許すカラム
  end

以上で郵便番号入力後、住所が自動で入力がされるかと思います。

補足

住所自動入力を設定した画面に遷移してリロードしないと郵便番号の自動入力がしない場合、リンクの記述に data: {"turbolinks" => false} を追加すると解消できました。

<%= link_to '会員登録', '/customers/sign_up',  data: {"turbolinks" => false} %>

初めて実装した場合約20分はかかりました。
ご参考になれば幸いです。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

エラーAn error occurred while installing mysql2 (0.5.3), and Bundler cannot continue. Make sure that `gem install mysql2 -v '0.5.3' --source 'https://rubygems.org/'` succeeds before bundling.の解決方法

何回も忘れるのでメモ。

原因

Gemfile内にmysql2を書いてない。

解決法

Gemfileに下記をかいて、bundle installする。

gem 'mysql2', '~>0.5.3'

なぐり書き

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ruby on rails にて 閲覧機能をつけたい

プログラミングを初めて2週間ほどです。

NoMethodError in Todolists#index
Showing /home/vagrant/work/sample_app/app/views/todolists/index.html.erb where line #2 raised:

undefined method `each' for nil:NilClass
Extracted source (around line #2):
1NoMethodError in Todolists#index
Showing /home/vagrant/work/sample_app/app/views/todolists/index.html.erb where line #2 raised:

undefined method `each' for nil:NilClass
Extracted source (around line #2
1 

投稿一覧


2 <% @lists.each do |list| %> ←ここがエラー

タイトル


4 <%= list.title %>
5 <% end %>

このようなエラーが出て解決方法がわかりません。 

余りにもわからないのでテキストをコピペしましたが未可決です。

どこを見直せば大丈夫ですか?

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[Rails]'devise'エラーメッセージの日本語化

application.rbを編集

application.rb
require_relative 'boot'

require 'rails/all'

# Require the gems listed in Gemfile, including any gems
# you've limited to :test, :development, or :production.
Bundler.require(*Rails.groups)

module Tsumiage
  class Application < Rails::Application
    config.i18n.default_locale = :ja  #記入コード
    # Settings in config/environments/* take precedence over those specified here.
    # Application configuration should go into files in config/initializers
    # -- all .rb files in that directory are automatically loaded.
  en
end

Gemのインストール

ターミナル.
gem 'devise-i18n'
gem 'devise-i18n-views'

インストール後忘れずにbundle install

devise.views.ja.ymlを作成

ターミナル.
rails g devise:views:locale ja

実行結果▼

ターミナル.
Running via Spring preloader in process 62024
      create  config/locales/devise.views.ja.yml            

config/locales/devise.views.ja.ymlが作成される。

最後に

rails sコマンドでローカル環境を再起動。
これで'devise'のエラー文を日本語に設定できました。

参考にして下さい!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

rails capybara使用時に出たエラー

今回の目的として、以下の参考記事のように、
rspecテスト実行時にsign_inメソッドを利用できるようになることであった。
https://qiita.com/jnchito/items/a8360e5e7a829d1e19b2

そのために、railsでcapybaraの設定を試みたところ以下のエラーが出た
それぞれのエラーに参考になったリンクを貼っておく。
また、大まかな設定は次の記事を参考にさせていただいた。
https://qiita.com/morrr/items/0e24251c049180218db4

undefined method `visit'

https://qiita.com/terufumi1122/items/aefd6c965e9e946efc3b
visitはcapybaraで使えるメソッドなので、設定したうえでないと上記のエラーになるらしい

Failure/Error: fill_in 'email', with: user.email 
Capybara::ElementNotFound:
Unable to find field "email" that is not disabled

https://qiita.com/pooooon/items/4fbc429d07e4b65ed928
私の場合、下記のように変更したところエラーがでなくなった。
fill_in 'user[email]', with: user.email
fill_in 'user[password]', with: 'password'
記事通り、'session[email]'ではないパターンもあるので、きちんとブラウザで確認した方がよいだろう。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

勤怠管理

- ruby 2.6.5
- rails 6.0.3
- devise
- rails_admin
- cancan
- rails_admin_import
- bootstrap
- slim-rails

group :test do
  - rspec
  - factory_bot_rails
end
参考サイト
サンプルサイト
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Rails 5.x】フリーフォントの導入方法

フリーフォントをRailsに導入する方法

自作のポートフォリオにフリーフォントを導入したいと思い、Qiita記事を参考に導入しました。
今更感はありますが・・・備忘録かつ、
初学者の参考になればと思い、記事を作成しました。

筆者の環境
・Ruby  2.5.3
・Rails 5.2.2

1 フリーフォントのファイルをダウンロード
2 フォントファイルを app/assets/fonts 配下に置く
(app名)/app/assets/fonts

hogehoge.ttf ( または hoge.otf ) のようなフォントファイルを、fonts ディレクトリ内に入れます。
自分の場合はfontsディレクトリがなかったのでassets配下にmkdirで作成しました。

3 SCSSファイルに記述

SCSSに、

custom.scss
@font-face {
  font-family: 'hoge'; # font-family名は適宜決定
  src: font-url('hogehoge.ttf') format('truetype');
  font-weight: normal;
  font-style: normal;
}

このように記述します。
ファイル内のどこに記述してもよいです。
そして、

custom.scss
body {  
  font-family: hoge;
}

先ほどのfont-family名を
bodyに記述し、全体に適用させます。

4 完成!

この手順でフリーフォントを適用できているかと思います。
意外に簡単でしたね。
反映されない場合は、適宜サーバーをrestartなどしてみてください!

5 参考記事

【初学者向け】Railsアプリケーションにカスタムフォントを追加する方法

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Rails】タイマー機能

はじめに

Railsでタイマー機能を実装したくて、調べてもなかなか欲しい情報が見つからず:expressionless:

友人から「javascriptで実装できるよ!」と教えてもらったら、あるわ♪あるわ♪情報が:sparkles:

無知過ぎる自分の戒め&【Rails タイマー】で検索した際、ヒットするよう記事に残します:bow_tone1:

どんな機能?

時間を指定し、時間になったらアラートがでるという機能になります。

例えば、3秒と入力し、3秒後にアラートがでる。

こんな感じです(Gyazoなのでタイミングによっては見れなくなります:bow_tone1:)
↓↓↓↓
https://gyazo.com/89773746aac0c2a1640d48e6a536190b

実装

view.html.haml
.timer__input
    %form{name: "timer"}
      %input{type: "text", value: ""}>/
      分
      %input{type: "text", value: ""}>/
      秒
      %br/
      %input{onclick: "cntStart()", type: "button", value: "スタート"}/
      %input{onclick: "cntStop()", type: "button", value: "ストップ"}/
javascript.js
//タイマー処理
var timer1;

//カウントダウン関数を1000ミリ秒ごとに呼び出す関数
function cntStart() {
  document.timer.elements[2].disabled=true;
  timer1 = setInterval("countDown()",1000);
}

// タイマー停止関数
function cntStop() {
  document.timer.elements[2].disabled=false;
  crearInterval(timer1);
}

// カウントダウン関数
function countDown() {
  var min=document.timer.elements[0].value;
  var sec=document.timer.elements[1].value;

  if((min=="")&&(sec=="")) {
    alert("時間を設定してください!");
    reSet();
  }
  else {
    if(min=="")min=0;
    min=parseInt(min);

    if(sec=="")sec=0;
    sec=parseInt(sec);

    tmWrite(min*60+sec-1);
  }
}

// 残り時間を書き出す関数
function tmWrite(int) {
  int=parseInt(int);

  if(int <= 0) {
    reSet();
    alert("時間です!");
  }
  else {
    //残り分すうはintを60で割って切り捨てる
    document.timer.elements[0].value=Math.floor(int/60);
    //残り秒数はintを60で割った余り
    document.timer.elements[1].value=int % 60;
  }
}

// フォームを初期状態に戻す(リセット)関数
function reSet() {
  document.timer.elements[0].value="0";
  document.timer.elements[1].value="0";
  document.timer.elements[2].disabled=false;
  clearInterval(timer1);
}

※上記はCSSはございません:bow_tone1:
(CSSを実装する前に記事にしてしまったためです:sweat_smile:
皆様のデザイン力で素敵なビューを作成してください:sparkles:

感想

javascriptは偉大なり:sun_with_face:
というのが正直な感想です 笑
まだまだ勉強中の身ではありますが、少しずつ知識をつけていきたいですね:relieved:

参考

JavaScript入門 カウントダウンタイマー

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

fullcalendarを使ってイベント管理できるものを作ろう。①準備

fullcalendarを使ってイベント管理できるものを作成しようと思っています。

これは、RubyonRailsの勉強を兼ねてのものです。何か間違っている、こう改善した方がいい、など指摘があれば教えていただけたらと思っています。
参考書は、「現場で使えるRuby on Rails 5速習実践ガイド」です。
さあ、頑張っていこう。

環境

OS:MacOS Catalina10.15.5
Ruby:2.6.3
Rails:5.2.4

アプリの作成

$ rails new Mark -d postgresql

モデルの作成

$ rails g model Event
Railsのモデルは、主に2つの要素から構成
・モデルに対応するRubyのクラス
・モデルに対応するデータベースのテーブル

クラス名とテーブル名には以下の命名規約がある。
・データベースのテーブル名は、モデルのクラス名を複数形にしたもの
・モデルのクラス名はキャメルケース、テーブル名はスネークケース

Eventモデルの属性を設定

属性の意味 属性名・カラム名 データ型
タイトル title string
始まり start datetime
終わり end datetime
終日 allday boolean
カレンダー色 color string

とりあえず、こんな感じで。

db/migrate/*******_create_events.rb
class CreateEvents < ActiveRecord::Migration[6.0]
  def change
    create_table :events do |t|
      t.string :title, null: false
      t.datetime :start, null: false
      t.datetime :end, null: false,
      t.boolean :allday, null: false, default: false
      t.string :color, null: false

      t.timestamps
    end
  end
end

$ rails db:migrate
マイグレーションをデータベースに適用。

コントローラーとビューの作成

$bin/rails g controller events index show new edit
コントローラ名は、モデルの複数形。その後ろに必要なアクション名を追記。

ルーティングの設定

config/routes.rb
Rails.application.routes.draw do
  root to: 'events#index'
  resources :events
end

fullcalendarの実装

Gemfileに追加。

gem 'fullcalendar-rails'
gem 'momentjs-rails'

$ bundle install

application.jsとapplication.cssに追加

assets/javascripts/application.js
//= require jquery
//= require moment
//= require fullcalendar
//= require_tree .
assets/stylesheets/application.css
*= require fullcalendar
*/

application.jsにコードを記述。

assets/javascripts/application.js
$(document).ready(function() {
    $('#calendar').fullCalendar({
        events: '/events.json'
    });
  });

viewに表示できるように追加。

app/view/events/index.html.erb
<div id="calendar"></div>

rails s で起動させてみる。
スクリーンショット 2020-07-03 14.24.37.png

お、出来ました。まだ、表示させただけなので、これから中身を作っていこう。。

参考

https://qiita.com/sasasoni/items/fb0bc1644ece888ae1d4
https://qiita.com/ShoutaWATANABE/items/3d0cddafadb4f275991e
https://fullcalendar.io/

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

herokuで常時SSL化(rails)

知ってれば何てことないのに、英語は読まなあかんわ、
情報は断片的だわってことで流れだけでもまとめました。

とりあえず、google翻訳は必須だね・・・。

前提:herokuで証明書を発行する

流れ

思い出しながらやっているので、実際のところ前後があるかもしれませんがご容赦ください

1、herokuのDynoを有料化してSSL証明書を発行できる状態にする
7ドル以上の有料プランしかSSLを発行できないです。
https://jp.heroku.com/pricing

2、herokuのsetting画面でSSL Certificatesを確認(画面の真ん中らへん

image.png

基本的には自動で作られるので、有料化したら作られるよう。
なければ、configure SSlボタンより作成

3、オリジナルドメイン追加

image.png

4、★重要!Point DNSを導入

herokuのメニュータブ「recoreces」 → Add ons から追加。
細かなところは、他の記事をご覧ください。

で、非常に重要なのが、
非常に重要!!!なのが、
ALIASと、CNAMEのDataには、上記3で表示されているDNS Targetの値をいれること!!
初期状態のheroku.app的なものだと、SSLが適応されません!

5、SSLの有効化を行う

$ heroku certs:auto:enableを実行
恐らく問題ないかと思いますが、エラーもろもろが出たら公式を日本語訳にして読んでね!
https://devcenter.heroku.com/articles/automated-certificate-management#setup

6、★重要!常時SSL化を実施

herokuでSSl化ができても、常時SSLはherokuじゃなく、
自分のrails アプリでやる必要があります!
SSL化 = SSL常時化ではないので、気を付けてください。

app/environments/puroduction.rb
の中に書いてある
# config.force_ssl = true
これのコメントアウトを解除。

以上です。誰かのお役に立てると幸いです

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【備忘録】Railsでgraphqlのmutationが増えていくとディレクトリがグちゃるので整理したい

 問題点

実用案件でmutationを黙々と増やしていくと、
api/app/graphql/mutations/に以下例のようにファイルが溢れてしまう。

(例)
create_user_mutation.rb
update_user_mutation.rb
delete_user_mutation.rb
create_news_mutation.rb
update_news_mutation.rb
delete_news_mutation.rb
create_item_mutation.rb
update_item_mutation.rb
delete_item_mutation.rb
create_catalog_mutation.rb
update_catalog_mutation.rb
delete_catalog_mutation.rb

Generateコマンド

自分はDockerを利用しているため以下のようになりますが、Dockerを利用しない場合は通常のRailsコマンドで大丈夫です。(Userを例に)

docker-compose exec 環境名 rails g graphql:mutation Users::CreateMutation
docker-compose exec 環境名 rails g graphql:mutation Users::UpdateMutation
docker-compose exec 環境名 rails g graphql:mutation Users::DeleteMutation

ファイルの確認

app/graphql/mutations/users/create_mutation.rb
mutation配下にusersのディレクトリが生成され、その配下にmutationのファイルが生成されるので確認。
/app/graphql/types/mutation_type.rb

field :users/create_mutation, mutation: Mutations::Users::CreateMutation

ファイルに上記のようなfieldが追加されるので、いい感じに修正する。

field :users_create_mutation, mutation: Mutations::Users::CreateMutation

ひとこと

わたしの調べ方が悪いのか、graphqの文献が少なく雑魚園児には辛み。ゴミのようなメモですみません。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

EC2、RDSを利用してRailsアプリをデプロイする [NGINX + puma + PostgreSQL + Rails 6]

目標:AWSのEC2、RDSを利用してRailsアプリをデプロイする

このような目的のため、とりあえずAWS上でアプリが動くことを目標にしています
そのためセキュリティ面で本来必要そうな手順(環境変数定義や、SSL対応)を排除して進行しております

備忘録的に記録を残しておりますので、
思い出したことや、不備あれば随時訂正いたします

気づき

  • 環境構築は学習コスト(時間)高い、けど仮想環境なら壊しても怒られないし、やってみようでなんとかなる

  • 作ったアプリが最終的にどのような環境で動くのか、開発にトップダウンの視点が加わる

  • AWSの無料範囲内で学べる、学習コスト(マネー)低すぎる

環境

  • AWS EC2 (amazon linux2)

  • AWS RDS (PostgreSQL 11.6)

  • NGINX: 1.12.2

  • puma: 3.12.1

  • Rails: 6.0.0

  • Ruby: 2.6.3

備忘録

前提

  • EC2インスタンスが稼働している
  • VPCはインターネット接続が可能(セキュリティグループ、サブネット、ルートテーブルの設定が適切)
  • EC2インスタンスとローカル環境はSSH接続が可能(鍵認証)
  • root権限をもつユーザーを作成しログインしていること
  • RDSでデータベースインスタンスを作成している(今回はPostgreSQL 11.6, 初期データベースsample_app_production)
  • EC2はGitHubと接続しており、/var/www/railsディレクトリにgit cloneでアプリを設置済み
  • master.keyを設定済み

参考:上記については以下のサイトを参考に進めました、感謝。

(DBの選定、設定の部分が異なっております)

【画像付きで丁寧に解説】AWS(EC2)にRailsアプリをイチから上げる方法【その1〜ネットワーク,RDS環境設定編〜】 - Qiita

パッケージのアップデート

まずは

sudo yum update

NGINXインストール

amazon-linux-extrasからインストール可能

sudo amazon-linux-extras install nginx1.12 -y

起動と自動起動設定

sudo systemctl start nginx && sudo systemctl enable nginx && systemctl status nginx

この時点でブラウザからEC2のElastic IPにアクセスすると
"Welcome to nginx on Amazon Linux!"というページが表示される(NGINXがポート: 80ポートで待ち受けている)

参考: NGINX関連コマンド

起動終了

$ sudo systemctl start nginx
$ systemctl status nginx
● nginx.service - The nginx HTTP and reverse proxy server
.
.
.
[sample_app@ip-10-0-0-44 rails]$ sudo systemctl stop nginx

自動起動

sudo systemctl enable nginx

PostgreSQL(クライアント)をインストール

sudo amazon-linux-extras install postgresql11

参考:クライアント以外もインストールする

sudo yum install postgresql-server postgresql-devel postgresql-contrib

参考:amazon-linux-extrasを使わない方法やアンインストールなど

EC2(Amazon Linux2)にPostgreSQLをインストールする | my opinion is my own

PostgreSQL install Amazon linux2
Amazon linux2にpostgresqlをインストールする手順 | 瀬戸内の雲のように

PostgreSQL install
PostgreSQL: Linux downloads (Red Hat family)

PostgreSQL uninstall
How To Completely Uninstall PostgreSQL | ObjectRocket

PostgreSQLに接続

PostgreSQLクライアントであるpsqlからRDSに作成したDBインスタンスに接続

psql --host=<DBインスタンスのエンドポイント> --port=5432 --dbname=sample_app_production  --username=sample_app
--port=5432 --dbname=sample_app_production  --username=sample_app

ユーザ sample_app のパスワード:
psql (11.5、サーバ 11.6)
SSL 接続 (プロトコル: TLSv1.2、暗号化方式: ECDHE-RSA-AES256-GCM-SHA384、ビット長: 256、圧縮: オフ)
"help" でヘルプを表示します。

sample_app_production=> 

RDSに接続可能なこと、初期データベースが存在することが確認できれば問題ないです

参考:AWS RDS公式

PostgreSQL データベースエンジンを実行する DB インスタンスへの接続 - Amazon Relational Database Service

psql を使用した PostgreSQL DB インスタンスへの接続

psql コマンドラインユーティリティのローカルインスタンスを使用して、PostgreSQL DB インスタンスに接続できます。PostgreSQL またはクライアントコンピュータにインストールされた psql クライアントのいずれかが必要です。psql を使用して PostgreSQL DB インスタンスに接続するには、ホスト情報とアクセス認証情報を指定する必要があります。

以下の形式のいずれかを使用して、Amazon RDS 上の PostgreSQL DB インスタンスに接続します。接続時にパスワードを求められます。バッチジョブまたはスクリプトには、--no-password オプションを使用します。

この DB インスタンスに初めて接続する場合、デフォルトのデータベース名 *postgres** を --dbname オプションに使用してみてください。*

Unix の場合、次の形式を使用します。

psql \
--host=<DB instance endpoint> \
--port=<port> \
--username=<master user name> \
--password \
--dbname=<database name> 

(中略)

たとえば、次のコマンドは、架空の認証情報を使用して、mypgdb という PostgreSQL DB インスタンス上の mypostgresql というデータベースに接続します。

psql --host=mypostgresql.c6c8mwvfdgv0.us-west-2.rds.amazonaws.com --port=5432 --username=awsuser --password --dbname=mypgdb 

参考:psql認証のtrust, ident, md5

RDSでDBインスタンスを作成したときの情報を使用します
接続認証にOSのユーザー名とDBのユーザー名の一致を求める仕組みがあるようですが
RDSとのhost-client接続ではユーザー名が違ってもパスワード認証で問題なかったです

PostgreSQL 認証に失敗しないための Ident、MD5、Trust 比較 - eTuts+ Server Tutorial

うまくいかない時は

タイムアウトしてしまい、認証に至らない(超重要)

セキュリティグループの設定を確認する
私はアウトバウンドの設定が必要でした(控えめに言って半日悩んだ)
EC2からRDS(MySQL)に接続できない。セキュリティグループの設定について・・・ - Qiita

クライアント(EC2)とホスト(RDS)のPostgreSQLバージョンが異なる

amazon-linux-extrasを使用せずにインストールすると
クライアント側が古いバージョンになってしまうことがありました
(RDS側は初期設定のPostgreSQL 11.6)
古いバージョンのアンインストールをして、新しいものをインストールしてください

警告: psql のメジャーバージョンは 9 ですが、サーバーのメジャーバージョンは 11 です。
         psql の機能の中で、動作しないものがあるかもしれません。

参考(メモ):以下はクライアントとしてpsqlを利用するだけなら関係ないかも

設定ファイルの場所

postgresql.conf

sudo vi /var/lib/pgsql/data/postgresql.conf

pg_hba.conf(認証関係)

sudo vi /var/lib/pgsql/data/pg_hba.conf

ユーザーpostgresのパスワードがわからない

途中自動生成されたユーザーpostgresのpasswordがわからず再設定しました
Postgresでパスワードを忘れた場合の対策 - Qiita

local(EC2)にDBを設置する場合のコマンドメモ

$ sudo postgresql-setup initdb
$ sudo systemctl enable postgresql.service
$ sudo systemctl start postgresql.service

PostgreSQLの設定をする

config/database.ymlを編集

default: &default
  adapter: postgresql
  encoding: unicode
  username: postgres
  password: password
  pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
  timeout: 5000

development:
  <<: *default
  host: db
  database: myapp_development

test:
  <<: *default
  host: db
  database: myapp_test

production:
  <<: *default 
  database: sample_app_production
  username: postgres # RDS DBインスタンスのユーザー名
  password: password # RDS DBインスタンスのパスワード
  host: xxxx.xxxx.ap-northeast-1.rds.amazonaws.com # RDS DBインスタンスのエンドポイント

本来ここに平文でユーザー名やパスワードを記録しないほうがよい

productionの<<: *defaultが抜けていてadapterが指定されていないよと半日怒られ続けました...
"adapterActiveRecord::AdapterNotSpecified: database configuration does not specify adapter"

hostを指定していればportの指定は不要でした

参考:database.ymlのPostgreSQL向け設定情報 ActiveRecord::ConnectionAdapters::PostgreSQLAdapter

NGINXの設定をする

dockerで環境構築したものが引き継がれたらいいのに...

pumaとsocket接続するようにしています

/etc/nginx/conf.d/sample_app.confを作成、編集

vi /etc/nginx/conf.d/sample_app.conf
error_log  /var/www/rails/sample_app/log/nginx.error.log;
access_log /var/www/rails/sample_app/log/nginx.access.log;

##ココ
upstream sample_app {
        server unix:///var/www/rails/sample_app/tmp/sockets/puma.sock;
}

server {
    listen 80;
    client_max_body_size 4G;
    server_name 54.238.15.249;

    keepalive_timeout 5;

    # Location of our static files
    root /var/www/rails/sample_app/public;

    location ~ ^/assets/ {
        root /var/www/rails/sample_app/public;
    }

    location / {
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header Host $http_host;
        proxy_redirect off;

        if (!-f $request_filename) {
            proxy_pass http://sample_app;
            break;
        }
    }

    error_page 500 502 503 504 /500.html;
    location = /500.html {
        root /var/www/rails/sample_app/public;
    }
}

NGINX再起動

sudo systemctl restart nginx

参考:NGINXコマンドほか

自動起動設定

sudo chkconfig nginx on

pumaの設定をする

アプリケーションサーバーとしてunicornは使用しません
Railsに組み込まれているpumaを利用してみます

NGINXとpumaはソケット接続するためconfig/puma.rbに以下を2点編集
(私はDockerで開発環境をNGINX + pumaに設定をした時点で設定済みでした)

# ポート: 3000をlistenしない
#port        ENV.fetch("PORT") { 3000 }

# socketの設定
bind "unix://#{Rails.root}/tmp/sockets/puma.sock"

参考:puma.rbの設定 puma/dsl.rb at master · puma/puma

参考:puma関連コマンド

production環境でpuma起動

bundle exec rails s -e production

puma, pumactlコマンドは私の環境では使えませんでした

bundle exec pumactl start

Pumaの起動におけるpumaコマンドとpumactlコマンドの違い - Qiita

pumaコマンドとpumactlコマンドの違い

このあたりちゃんと説明した日本語の情報がとても少なくて、リポジトリのREADMEを読んだり「what is deference between puma command and pumactl command」みたいなキーワードでがんばって調べたらRuby Journalになんかそれっぽいことが書いてあった。英語ができないととてもつらい。

As we can see that above operations can be tedious and error prone and definitely not fun to work with a big deployment scale. Introducing pumactl, this utility automates all of above tasks.
Digesting Pumactl

要するに、pumaコマンドで起動したりしてるといちいちオプション付けないといけないから大規模開発だとめっちゃつらいしやばいくらい闇だし、pumaコマンドでやってることはpumactlコマンドで自動化しような。ということを言っている。

なるほど、確かに。pumactlコマンドだと問答無用で設定ファイルが読まれるようになっているから、スレッドの数はどうするんだだの、ポート番号はどうするんだだの、ちゃんとコードで管理できる。

停止や再起動においてもpumactlコマンドで安全にできるらしい。

Available commands: halt, restart, phased-restart, start, stats, status, stop, reload-worker-directory

YarnとNodeをインストール、アップデート

Rails 6以降で必要
以降の過程でYarnが必要、nodeが古いと言われます

Yarn

# wgetインストール済みなら省略可能
$ yum -y install wget
$ wget https://dl.yarnpkg.com/rpm/yarn.repo -O /etc/yum.repos.d/yarn.repo
$ curl --silent --location https://rpm.nodesource.com/setup_6.x | bash -


$ sudo yum install yarn

$ yarn --version
1.22.4

Node

$ sudo npm install n -g
$ sudo n stable

$ node -v
v12.18.2

参考:capistrano・EC2・postgresql・rails6で自動デプロイ設定した際のエラー例 - Qiita

アプリを起動させる

マイグレーション、アセットプリコンパイル

# マイグレーション
bundle exec rake db:migrate RAILS_ENV=production

# プリコンパイル
bundle exec rake assets:precompile RAILS_ENV=production

Webサーバー、アプリケーションサーバー起動

# NGINX再起動
sudo service nginx restart

# puma起動
bundle exec rails s -e production

問題なければブラウザからElastic IPにアクセスすることで、
アプリのインターフェイスが表示されるはずです

"We're sorry, but something went wrong."

もうね、怒られるのなれましたよ

ブラウザにに上記メッセージが表示される場合ログを確認しましょう

cd log # アプリルート直下

tail -n 30 production.log

tail -n 30 nginx.error.log

私の場合production.logは空っぽで
nginx.error.logで以下のエラーが記録されていました

"connect() to unix:/home/var/www/rails/sample_app/tmp/sockets/puma.sock failed (2: No such file or directory) while connecting to upstream"

/etc/nginx/conf.d/sample_app.confのsocketパス指定が間違っておりました

その他既知の問題点

  • SSL非対応
    config/environments/production.rbconfig.force_ssl = trueconfig.force_ssl = false

  • 新規ユーザー登録のところでメール認証がうまく行かない

今後対応

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Ruby on Rails チュートリアル(第4版) 第4章

4.2 nanoエディタから離脱する際、Ctrl+XYだけでは離脱出来ず。Enterキーを押せば離脱出来た(ググった)。

4.2.2 演習

1.city変数に適当な市区町村を、prefecture変数に適当な都道府県を代入してください。

>> city = "横浜市"
=> "横浜市"
>> prefecture = "神奈川県"
=> "神奈川県"

2.先ほど作った変数と式展開を使って、「東京都 新宿区」のような住所の文字列を作ってみましょう。出力にはputsを使ってください。

>> puts "#{prefecture} #{city}"
神奈川県 横浜市
=> nil

3.上記の文字列の間にある半角スペースをタブに置き換えてみてください。(ヒント: 改行文字と同じで、タブも特殊文字です)
 tabキー押しても何も無いと思ったら、\tでtabらしい。

>> puts "#{prefecture}\t#{city}"
神奈川県        横浜市
=> nil

4.タブに置き換えた文字列を、ダブルクォートからシングルクォートに置き換えてみるとどうなるでしょうか?

>> puts '#{prefecture}\t#{city}'
#{prefecture}\t#{city}
=> nil

4.2.3 演習

1."racecar" の文字列の長さはいくつですか? lengthメソッドを使って調べてみてください。

>> "racecar".length
=> 7

2.reverseメソッドを使って、"racecar"の文字列を逆から読むとどうなるか調べてみてください。

>> "racecar".reverse
=> "racecar"

3.変数sに "racecar" を代入してください。その後、比較演算子 (==) を使って変数sとs.reverseの値が同じであるかどうか、調べてみてください。

>> s = "racecar"
=> "racecar"
>> s == s.reverse
=> true

4.リスト 4.9を実行すると、どんな結果になるでしょうか? 変数sに "onomatopoeia" という文字列を代入するとどうなるでしょうか? ヒント: 上矢印 (またはCtrl-Pコマンド) を使って以前に使ったコマンドを再利用すると一からコマンドを全部打ち込む必要がなくて便利ですよ。)

>> puts "It's a palindrome!" if s == s.reverse
It's a palindrome!
=> nil
>> s = "onomatopoeia"
=> "onomatopoeia"
>> puts "It's a palindrome!" if s == s.reverse
=> nil

4.2.4 演習

1.リスト 4.10のFILL_INの部分を適切なコードに置き換え、回文かどうかをチェックするメソッドを定義してみてください。ヒント: リスト 4.9の比較方法を参考にしてください。

>> def palindrome_tester(s)
>>   if s == s.reverse
>>     puts "It's a palindrome!"
>>   else
>>     puts "It's not a palindrome."
>>   end
>> end
=> :palindrome_tester

2.上で定義したメソッドを使って “racecar” と “onomatopoeia” が回文かどうかを確かめてみてください。1つ目は回文である、2つ目は回文でない、という結果になれば成功です。

>> palindrome_tester("racecar")
It's a palindrome!
=> nil
>> palindrome_tester("onomatopoeia")
It's not a palindrome.
=> nil

3.palindrome_tester("racecar")に対してnil?メソッドを呼び出し、戻り値がnilであるかどうかを確認してみてください (つまりnil?を呼び出した結果がtrueであることを確認してください)。このメソッドチェーンは、nil?メソッドがリスト 4.10の戻り値を受け取り、その結果を返しているという意味になります。

>> palindrome_tester("racecar").nil?
It's a palindrome!
=> true

4.3.1 演習

1.文字列「A man, a plan, a canal, Panama」を ", " で分割して配列にし、変数aに代入してみてください。

>> a="A man,a plan,a canal,Panama".split(',')
=> ["A man", "a plan", "a canal", "Panama"]

2.今度は、変数aの要素を連結した結果 (文字列) を、変数sに代入してみてください。

>> s = a.join
=> "A mana plana canalPanama"

3.変数sを半角スペースで分割した後、もう一度連結して文字列にしてください (ヒント: メソッドチェーンを使うと1行でもできます)。リスト 4.10で使った回文をチェックするメソッドを使って、(現状ではまだ) 変数sが回文ではないことを確認してください。downcaseメソッドを使って、s.downcaseは回文であることを確認してください。

>> s = s.split.join
=> "AmanaplanacanalPanama"
>> palindrome_tester(s)
It's not a palindrome.
=> nil
>> palindrome_tester(s.downcase)
It's a palindrome!
=> nil

4.aからzまでの範囲オブジェクトを作成し、7番目の要素を取り出してみてください。同様にして、後ろから7番目の要素を取り出してみてください。(ヒント: 範囲オブジェクトを配列に変換するのを忘れないでください)

>> a = ('a'..'z').to_a
=> ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z"]
>> a[6]
=> "g"
>> a[-7]
=> "t"

4.3.2 演習

1.範囲オブジェクト0..16を使って、各要素の2乗を出力してください。

>> (0..16).each { |i| puts i**2 }                                                                                 
0
1
4
9
16
25
36
49
64
81
100
121
144
169
196
225
256
=> 0..16

2.yeller (大声で叫ぶ) というメソッドを定義してください。このメソッドは、文字列の要素で構成された配列を受け取り、各要素を連結した後、大文字にして結果を返します。例えばyeller(['o', 'l', 'd'])と実行したとき、"OLD"という結果が返ってくれば成功です。ヒント: mapとupcaseとjoinメソッドを使ってみましょう。

>> def yeller(x='')
>>   x.map(&:upcase).join
>> end
=> :yeller
>> yeller(['o', 'l', 'd'])
=> "OLD"

3.random_subdomainというメソッドを定義してください。このメソッドはランダムな8文字を生成し、文字列として返します。ヒント: サブドメインを作るときに使ったRubyコードをメソッド化したものです。

>> def random_subdomain
>>   ('a'..'z').to_a.shuffle[0..7].join
>> end
=> :random_subdomain

4.リスト 4.12の「?」の部分を、それぞれ適切なメソッドに置き換えてみてください。ヒント:split、shuffle、joinメソッドを組み合わせると、メソッドに渡された文字列 (引数) をシャッフルさせることができます。

>> def string_shuffle(s)
>>   s.split('').shuffle.join
>> end
=> :string_shuffle
>> string_shuffle("foobar")
=> "robofa"

4.3.3 演習

だんだん難しくなってきた。

1.キーが'one'、'two'、'three'となっていて、それぞれの値が'uno'、'dos'、'tres'となっているハッシュを作ってみてください。その後、ハッシュの各要素をみて、それぞれのキーと値を"'#{key}'のスペイン語は'#{value}'"といった形で出力してみてください。

>> a = { one: "uno", two: "dos", three: "tres"}
=> {:one=>"uno", :two=>"dos", :three=>"tres"}
>> a.each do |key, value|
?>   puts "'#{key}'のスペイン語は'#{value}'"
>> end
'one'のスペイン語は'uno'
'two'のスペイン語は'dos'
'three'のスペイン語は'tres'
=> {:one=>"uno", :two=>"dos", :three=>"tres"}

2.person1、person2、person3という3つのハッシュを作成し、それぞれのハッシュに:firstと:lastキーを追加し、適当な値 (名前など) を入力してください。その後、次のようなparamsというハッシュのハッシュを作ってみてください。1.) キーparams[:father]の値にperson1を代入、2). キーparams[:mother]の値にperson2を代入、3). キーparams[:child]の値にperson3を代入。最後に、ハッシュのハッシュを調べていき、正しい値になっているか確かめてみてください。(例えばparams[:father][:first]がperson1[:first]と一致しているか確かめてみてください)

>> person1 = { first: "Taro", last: "Tanaka" }                                                                        
=> {:first=>"Taro", :last=>"Tanaka"}
>> person2 = { first: "Jiro", last: "Sato" }
=> {:first=>"Jiro", :last=>"Sato"}
>> person3 = { first: "Saburo", last: "Suzuki" }
=> {:first=>"Saburo", :last=>"Suzuki"}
>> params = {}
=> {}
>> params[:father] = person1
=> {:first=>"Taro", :last=>"Tanaka"}
>> params[:mother] = person2
=> {:first=>"Jiro", :last=>"Sato"}
>> params[:child] = person3
=> {:first=>"Saburo", :last=>"Suzuki"}
>> params[:father][:first] == person1[:first]
=> true

3.userというハッシュを定義してみてください。このハッシュは3つのキー:name、:email、:password_digestを持っていて、それぞれの値にあなたの名前、あなたのメールアドレス、そして16文字からなるランダムな文字列が代入されています。

>> user = { name: "muramako", email: "muramako@example.com", password_digest: ('a'..'z').to_a.shuffle[0..15].join } 
=> {:name=>"muramako", :email=>"muramako@example.com", :password_digest=>"xqsthfkjovdimeua"}

4.Ruby API (訳注: もしくはるりまサーチ) を使って、Hashクラスのmergeメソッドについて調べてみてください。次のコードを実行せずに、どのような結果が返ってくるか推測できますか? 推測できたら、実際にコードを実行して推測があっていたか確認してみましょう。
{ "a" => 100, "b" => 200 }.merge({ "b" => 300 })
 "b" => 200 が "b" => 300 に変わる

>> { "a" => 100, "b" => 200 }.merge({ "b" => 300 })
=> {"a"=>100, "b"=>300}

合ってた!

4.4.1 演習

1.1から10の範囲オブジェクトを生成するリテラルコンストラクタは何でしたか? (復習です)
1..10

2.今度はRangeクラスとnewメソッドを使って、1から10の範囲オブジェクトを作ってみてください。ヒント: newメソッドに2つの引数を渡す必要があります

>> a = Range.new(1..10)
Traceback (most recent call last):
        3: from (irb):85
        2: from (irb):85:in `new'
        1: from (irb):85:in `initialize'
ArgumentError (wrong number of arguments (given 1, expected 2..3))

自信満々に打ったら、エラーが出た。
Range.newの範囲の指定について調べたら,で引数を2つ指定すればいいらしい(そういう意味か!)。

>> a = Range.new(1,10)
=> 1..10

3.比較演算子==を使って、上記2つの課題で作ったそれぞれのオブジェクトが同じであることを確認してみてください。

>> a = Range.new(1,10)
=> 1..10
>> a == (1..10)
=> true

4.4.2 演習

1.Rangeクラスの継承階層を調べてみてください。同様にして、HashとSymbolクラスの継承階層も調べてみてください。

>> Range.class
=> Class
>> Range.class.superclass
=> Module
>> Range.class.superclass.superclass
=> Object
>> Range.class.superclass.superclass.superclass
=> BasicObject
>> Hash.class
=> Class
>> Hash.class.superclass
=> Module
>> Hash.class.superclass.superclass
=> Object
>> Hash.class.superclass.superclass.superclass
=> BasicObject
>> Symbol.class
=> Class
>> Symbol.class.superclass
=> Module
>> Symbol.class.superclass.superclass
=> Object
>> Symbol.class.superclass.superclass.superclass
=> BasicObject

2.リスト 4.15にあるself.reverseのselfを省略し、reverseと書いてもうまく動くことを確認してみてください。

>> class Word < String
>>   def palindrome?
>>     self == reverse
>>   end
>> end
=> :palindrome?
>> s = Word.new("level")
=> "level"
>> s.palindrome?
=> true

4.4.3 演習

1.palindrome?メソッドを使って、“racecar”が回文であり、“onomatopoeia”が回文でないことを確認してみてください。南インドの言葉「Malayalam」は回文でしょうか? ヒント: downcaseメソッドで小文字にすることを忘れないで。

>> class String
>>   def palindrome?
>>     self == reverse
>>   end
>> end
=> :palindrome?
>> "racecar".palindrome?
=> true
>> "onomatopoeia".palindrome?
=> false
>> "Malayalam".downcase.palindrome?
=> true

2.リスト 4.16を参考に、Stringクラスにshuffleメソッドを追加してみてください。ヒント: リスト 4.12も参考になります。

>> class String
>>   def shuffle
>>     self.split('').shuffle.join
>>   end
>> end
=> :shuffle
>> "foobar".shuffle
=> "roboaf"

3.リスト 4.16のコードにおいて、self.を削除してもうまく動くことを確認してください。

>> class String
>>   def shuffle
>>     split('').shuffle.join
>>   end
>> end
=> :shuffle
>> "foobar".shuffle
=> "rofaob"

4.4.4 演習

1.第2章で作ったToyアプリケーションのディレクトリでRailsコンソールを開き、User.newと実行することでuserオブジェクトが生成できることを確認してみましょう。

>> user = User.new
=> #<User id: nil, name: nil, email: nil, created_at: nil, updated_at: nil>

2.生成したuserオブジェクトのクラスの継承階層を調べてみてください。

>> user.class
=> User(id: integer, name: string, email: string, created_at: datetime, updated_at: datetime)
>> user.class.superclass
=> ApplicationRecord(abstract)
>> user.class.superclass.superclass
=> ActiveRecord::Base
>> user.class.superclass.superclass.superclass
=> Object
>> user.class.superclass.superclass.superclass.superclass
=> BasicObject

4.4.5 演習

1.Userクラスで定義されているname属性を修正して、first_name属性とlast_name属性に分割してみましょう。また、それらの属性を使って "Michael Hartl" といった文字列を返すfull_nameメソッドを定義してみてください。最後に、formatted_emailメソッドのnameの部分を、full_nameに置き換えてみましょう (元々の結果と同じになっていれば成功です)

/example_user.rb
class User
  attr_accessor :first_name, :last_name, :email

  def initialize(attributes = {})
    @first_name = attributes[:first_name]
    @last_name = attributes[:last_name]
    @email = attributes[:email]
  end

  def full_name
    "#{@last_name} #{@first_name}"
  end

  def formatted_email
    "#{full_name} <#{@email}>"
  end
end
>> user = User.new(first_name: "Hartl", last_name: "Michael", email: "mhartl@example.com")                           
=> #<User:0x00007f8148fbcf40 @first_name="Hartl", @last_name="Michael", @email="mhartl@example.com">
>> user.formatted_email
=> "Michael Hartl <mhartl@example.com>"

2."Hartl, Michael" といったフォーマット (苗字と名前がカンマ+半角スペースで区切られている文字列) で返すalphabetical_nameメソッドを定義してみましょう。

  def alphabetical_name
    "#{@first_name},#{@last_name}"
  end

3.full_name.splitとalphabetical_name.split(', ').reverseの結果を比較し、同じ結果になるかどうか確認してみましょう。

>> user.full_name.split == user.alphabetical_name.split(',').reverse
=> true

メモ

  • Rubyはシングルクォート文字列の中では式展開を行わない。
  • Railsでは自動的にヘルパーモジュールを読み込んでくれるので、include行をわざわざ書く必要がなく、メゾットが自動的にすべてのビューで利用できるようになっている。
  • initializeは、User.newを実行すると自動的に呼び出されるRubyの特殊なメソッド。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Rails こんなこともできるよ中間テーブル

どうもチャンクノです!!
今回は中間テーブルについて書いていきます。

多分基本的に中間テーブルを作成する場合、多対多の両テーブルのidを中間テーブルに持たせると思います。
https://qiita.com/gomasio1010/items/2cff1e11d2cc82fde464
この記事のような感じ。

でも中間テーブルって必ずしもidじゃなくていいみたいです。
自分が今担当している部分の中間テーブルは

 create_table :search_condition_areas do |t|
  t.string :area_code
  t.references :search_condition, foreign_key: true
  t.timestamps
 end

こんな感じになってます。

このままだと :area_codeがただのstring型のカラムになってしまうので、

has_many :search_condition_areas, foreign_key: :area_code, primary_key: :code
has_many :search_conditions, through: :search_condition_areas

モデル側でforeign_keyとprimary_keyを指定してあげてます。
これで基本的な中間テーブルを作成した時と同じ状態になりました。

さて、気になるのはこれをcreateする時ですね。

def search_condition_params
 params.require(:search_condition).permit(
  :hoge,  :huga, :foo, area_?: [] <= ここ
 )
end

ここの?に何が入るかわかりますか?
僕は脳死で area_codes: [] にしました!!!
え、違うの?そんな声が聞こえてきそうですねえ。うんうん。違うんです。
ここは普通に area_ids: [] です。
例えば @search_condition.area_ids とコンソールで叩けばidではなく

[83] pry(main)> @search_condition.area_ids
=> ["11101000000", "13101000000", "13103000000"]

こんな感じで @search_condition に紐づく中間テーブルが持っているarea_codeが返ってきます。

[99] pry(main)> area.search_condition_ids
   (1.8ms)  SELECT `search_conditions`.`id` FROM `search_conditions` 
INNER JOIN `search_condition_areas` ON `search_conditions`.`id` = `search_condition_areas`.`search_condition_id`
WHERE `search_condition_areas`.`area_code` = '13000000000'
=> [3]

逆も然り。これは普通にsearch_conditionのidが返ってきます。
〜ids <= この部分は collection_singular_ids このメソッドを使っています。
一対多の時使えるメソッドみたいですね。
https://railsdoc.com/association
ここに色々書かれています?‍♂️
https://www.rubydoc.info/gems/activerecord/ActiveRecord%2FAssociations%2FClassMethods:has_many
英語できる人はこっちの方がいいっぽいです。

これがあったので、もしかしてarea_codesじゃなくてarea_idsなんじゃね?って気付くことができました!!

で、ここで気を付けなければいけないのが

@search_condition.area_ids = @new_search_condition.area_ids
@search_condition.save

コントローラーの処理でこのような記述を使用する場合ですね。
どうなると思いますか?

@search_condition.area_ids = @new_search_condition.area_ids

これ @search_condition.save の処理が走る前に上記の部分が上書き保存されてしまいます。
どうやらautosaveが自動で走ってしまうみたいです。
accepts_nested_attributes_for を使っている場合必ずautosaveがtrueになってしまうのでどうしようもないっぽいです。

User.rb

has_one :search_condition
accepts_nested_attributes_for :search_condition <= 多分ここが原因

おそらくですが親テーブルに accepts_nested_attributes_for が使用されていたらautosave: falseは使えないんでしょうね。
なので、ここでは親テーブルが保存されてから子テーブルを上書き保存する処理を書くのがいいのかなーと思います。

if @search_condition.save
  @search_condition.area_ids = new_search_condition.area_ids
else
  render "new"
end

こんな感じ。
多分もっといいやり方があるんだろうなあ。

ごちゃごちゃと色々書いてきましたが、まとめると

・中間テーブルに持たせるものは両テーブルのidじゃなくても良い。
・でもその場合はidを持たせないテーブルにforeign_keyとprimary_keyの記述を忘れずに。
・idを持たせない場合でもコントローラーのパラメーターは必ず~idsにする。
・accepts_nested_attributes_forを使用している場合はautosaveに気を付ける。

こんなところですかね?
あんまり中間テーブルの実装には触れてこなかったのでお勉強になりました?
覚えることは山のようにあるなあ、、、。
では今回はこんな感じで!!
間違ってるところとかあったらコメントください!
それでは皆様良きプログラミングライフを?

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Rails Tutorial MEMO#8

Rails Tutorial第8章

基本的なログイン機構

本章では、ログインの基本的な仕組みを実装していく。

8.1 セッション

HTTPはそれより前のリクエストの情報を全く利用できない、Statelessなプロトコル。故にユーザーのIDを保持しておく手段がHTTPプロトコル内「には」全く無い。
ユーザーログインの必要なWebアプリケーションでは、セッション(Session)と呼ばれる半永続的な接続をコンピュータ間(ユーザーのパソコンのWebブラウザとRailsサーバーなど)に別途設定する。
Railsでセッションを実装する方法として最も一般的なのは、cookiesを使う方法。

8.1.1 Sessionsコントローラ

createアクションにPOSTリクエストを送信すると、実際にログインする。
destroyアクションにDELETEリクエストを送信するとログアウトする。
Sessionsコントローラを生成する
$rails generate controller Sessions new
routesにリソースを追加する。

config/routes.rb
Rails.application.routes.draw do
  root   'static_pages#home'
  get    '/help',    to: 'static_pages#help'
  get    '/about',   to: 'static_pages#about'
  get    '/contact', to: 'static_pages#contact'
  get    '/signup',  to: 'users#new'
  get    '/login',   to: 'sessions#new'      #新しいセッションのページ
  post   '/login',   to: 'sessions#create'   #新しいセッションの作成
  delete '/logout',  to: 'sessions#destroy'  #セッションの削除
  resources :users
end

Sessionsコントローラのテストで名前付きルートを使うようにする。

test/controllers/sessions_controller_test.rb
require 'test_helper'

class SessionsControllerTest < ActionDispatch::IntegrationTest

  test "should get new" do
    get login_path
    assert_response :success
  end
end

$rails routesコマンドで現状のルーティングを確認できる。

8.1.2 ログインフォーム

セッションフォームとユーザー登録フォームの最大の違いは、セッションにはSessionモデルというものがなく、そのため@userのようなインスタンス変数に相当するものもない点。したがって、新しいセッションフォームを作成するときには、form_forヘルパーに追加の情報を独自に渡さなければならない。
Railsでは以下のように書くだけで、「フォームのactionは/usersというURLへのPOSTである」と自動的に判定するが
form_for(@user)
↓セッションの場合は、リソースの名前とそれに対応するURLを具体的に指定する必要がある
form_for(:session, url: login_path)
ログインフォームのコード

app/views/sessions/new.html.erb
<% provide(:title, "Log in") %>
<h1>Log in</h1>

<div class="row">
  <div class="col-md-6 col-md-offset-3">
    <%= form_for(:session, url: login_path) do |f| %>

      <%= f.label :email %>
      <%= f.email_field :email, class: 'form-control' %>

      <%= f.label :password %>
      <%= f.password_field :password, class: 'form-control' %>

      <%= f.submit "Log in", class: "btn btn-primary" %>
    <% end %>

    <p>New user? <%= link_to "Sign up now!", signup_path %></p>
  </div>
</div>

8.1.3 ユーザーの検索と認証

ログインでセッションを作成する場合の作業順番
1,入力が無効な場合の処理
2,ログインが失敗した場合に表示されるエラーメッセージの配置
3,ログイン成功した場合に使う土台部分作成
今回はパスワードとメールアドレスの組み合わせが有効かどうかの判定
createアクションを実行するとnewビューが出力される

app/controllers/sessions_controller.rb
 class SessionsController < ApplicationController

  def new
  end

  def create
    render 'new'
  end

  def destroy
  end
end
cerateで最初に失敗したログインのデバッグ情報
---
session:
  email: 'user@example.com'
  password: 'foobar'
commit: Log in
action: create
controller: sessions

paramsは次のような入れ子ハッシュ(ハッシュの中にハッシュがある構造)になっている
{ session: { password: "foobar", email: "user@example.com" } }
createアクションの中では、ユーザーの認証に必要なあらゆる情報をparamsハッシュから簡単に取り出せる。

app/controllers/sessions_controller.rb
 class SessionsController < ApplicationController

  def new
  end

  def create
    user = User.find_by(email: params[:session][:email].downcase)
    if user && user.authenticate(params[:session][:password])
      # ユーザーログイン後にユーザー情報のページにリダイレクトする
    else
      # エラーメッセージを作成する
      render 'new'
    end
  end

  def destroy
  end
end

&&(論理積(and))は、取得したユーザーが有効かどうかを決定するために使う。

8.1.4 フラッシュメッセージを表示する

app/controllers/sessions_controller.rb
 class SessionsController < ApplicationController

  def new
  end

  def create
    user = User.find_by(email: params[:session][:email].downcase)
    if user && user.authenticate(params[:session][:password])
      # ユーザーログイン後にユーザー情報のページにリダイレクトする
    else
      flash[:danger] = 'Invalid email/password combination' # 本当は正しくない
      render 'new'
    end
  end

  def destroy
  end
end

上記のままでは一度表示されたフラッシュメッセージが消えずに残ってしまう

8.1.5 フラッシュのテスト

最初に統合テストを生成する
$ rails generate integration_test users_login
以下テストコード再現の流れ
1,ログイン用のパスを開く
2,新しいセッションのフォームが正しく表示されたことを確認する
3,わざと無効なparamsハッシュを使ってセッション用パスにPOSTする
4,新しいセッションフォームが再度表示され、フラッシュメッセージが追加されることを確認する
5,別のページに一旦移動する
6,移動先のページでフラッシュメッセージが表示されていないことを確認する

test/integration/users_login_test.rb
 require 'test_helper'

class UsersLoginTest < ActionDispatch::IntegrationTest

  test "login with invalid information" do
    get login_path                 (1)
    assert_template 'sessions/new' (2)
    post login_path, params: { session: { email: "", password: "" } }    (3)
    assert_template 'sessions/new' (4)新しいセッションフォームが再表示されることを確認
    assert_not flash.empty?        (4)フラッシュメッセージが追加されることを確認
    get root_path                  (5)
    assert flash.empty?            (6)
  end
end

$ rails test test/integration/users_login_test.rbこのようにrails testの引数にテストファイルを与えると、そのテストだけを実行することができる
テストをパスさせるにはcreateアクションのflashflash.nowに置き換える
flash.nowのメッセージはその後のリクエストが発生したときに消滅する
これによってテストもGREENになる

8.2 ログイン

ログイン中の状態での有効な値の送信をフォームで正しく扱えるようにする
セッションを実装するには様々なコントローラやビューで沢山のメソッドを定義する必要がある。そうしたメソッドを一箇所にパッケージ化できるRubyのモジュール機能を使う。
Sessionsコントローラを生成した時点で既にセッション用ヘルパーモジュールも自動生成されている。さらに、Railsのセッション用ヘルパーはビューにも自動的に読み込まれる。Railsの全コントローラの親クラスであるApplicationコントローラにこのモジュールを読み込ませれば、どのコントローラでも使えるようになる。

app/controllers/application_controller.rb
 class ApplicationController < ActionController::Base
  protect_from_forgery with: :exception
  include SessionsHelper
end

8.2.1 log_inメソッド

Railsで事前定義済みのsessionメソッドを使って、単純なログインを行えるようにする。このsessionメソッドはハッシュのように扱える。
seesion[:user_id] = user.id
上のコードを実行すると、ユーザーのブラウザ内の一時cookiesに暗号化済みのユーザーIDが自動で作成される。

app/helpers/sessions_helper.rb
 module SessionsHelper

  # 渡されたユーザーでログインする
  def log_in(user)
    session[:user_id] = user.id
  end
end

sessionメソッドで作成した一時cookiesは自動的に暗号化され、上記のコードは保護される。

app/controllers/sessions_controller.rb
 class SessionsController < ApplicationController

  def new
  end

  def create
    user = User.find_by(email: params[:session][:email].downcase)
    if user && user.authenticate(params[:session][:password])
      log_in user
      redirect_to user
    else
      flash.now[:danger] = 'Invalid email/password combination'
      render 'new'
    end
  end

  def destroy
  end
end

これでapp/views/sessions/new.html.erbで定義したログインフォームも正常に動作するようになった。

8.2.2 現在のユーザー

current_userメソッドを定義して、セッションIDに対応するユーザー名をデータベースから取り出せるようにする。しかし、findを使うと例外が発生してしまう。find_byメソッドを使うことで、IDが無効な場合(=ユーザーが存在しない場合)にもメソッドは例外を発生せず、nilを返す。
User.find_by(id: session[:user_id])

app/helpers/sessions_helper.rb
  def current_user
    if session[:user_id]
      @current_user ||= User.find_by(id: session[:user_id])
    end
  end
end
if @current_user.nil?
  @current_user = User.find_by(id: session[:user_id])
else
  @current_user
end

or演算子「||」を使えば上記の「メモ化」コードが次のように変換できる
@current_user = @current_user || User.find_by(id: session[:user_id])

「Ruby的に」正しいコードではないので以下のように変換させる
この書き換えは、 x = x + 1 が x += 1 になることと同じ
@current_user ||= User.find_by(id: session[:user_id])

8.2.3 レイアウトリンクを変更する

ユーザーがログインしている時とそうでない時でレイアウトを変更する。
論理値を返すlogged_in?メソッドを定義、ユーザーがログイン中の状態とは「sessionにユーザーidが存在している」こと、つまりcurrent_usernilではないという状態を指す。これをチェックするには!(否定演算子)を使っていく。

app/helpers/sessions_helper.rb
 module SessionsHelper

  # 渡されたユーザーでログインする
  def log_in(user)
    session[:user_id] = user.id
  end

  # 現在ログイン中のユーザーを返す (いる場合)
  def current_user
    if session[:user_id]
      @current_user ||= User.find_by(id: session[:user_id])
    end
  end

  # ユーザーがログインしていればtrue、その他ならfalseを返す
  def logged_in?
    !current_user.nil?
  end
end

ログイン中のユーザー用のレイアウトのリンクを変更する

app/views/layouts/_header.html.erb
 <header class="navbar navbar-fixed-top navbar-inverse">
  <div class="container">
    <%= link_to "sample app", root_path, id: "logo" %>
    <nav>
      <ul class="nav navbar-nav navbar-right">
        <li><%= link_to "Home", root_path %></li>
        <li><%= link_to "Help", help_path %></li>
        <% if logged_in? %>
          <li><%= link_to "Users", '#' %></li>
          <li class="dropdown">
            <a href="#" class="dropdown-toggle" data-toggle="dropdown">
              Account <b class="caret"></b>
            </a>
            <ul class="dropdown-menu">
              <li><%= link_to "Profile", current_user %></li>
              <li><%= link_to "Settings", '#' %></li>
              <li class="divider"></li>
              <li>
                <%= link_to "Log out", logout_path, method: :delete %>
              </li>
            </ul>
          </li>
        <% else %>
          <li><%= link_to "Log in", login_path %></li>
        <% end %>
      </ul>
    </nav>
  </div>
</header>

レイアウトに新しいリンクを追加したので、上記のコードにBootstrapのドロップダウンメニュー機能dropdownクラスやdropdown-menuなど使えるようになる。これらのドロップダウン機能を有効にするため、Railsのapplication.jsファイルを通して、Bootstrapに同梱されているJavaScriptライブラリとjQueryを読み込むようアセットパイプラインに指示する。

app/assets/javascripts/application.js
 //= require rails-ujs
//= require jquery
//= require bootstrap
//= require turbolinks
//= require_tree .

8.2.4 レイアウトの変更をテストする

統合テストを書いてこの動作をテストで表現し、今後の回帰バグの発生をキャッチで切るようにする。手順はtest/integration/users_login_test.rbを元に作成する。

テストの確認をするためにはテスト時に登録済みユーザーとしてログインしておく必要がある。Railsでは、このようなテスト用データをfixture(フィクスチャ)で作成できる。このfixtureを使って、テストに必要なデータをtestデータベースに読み込んでおくことができる。

現時点のテストでは、ユーザーは一人いれば問題なので有効な名前とメールアドレスを設定しておく。テスト中にそのユーザーとして自動ログインするために、そのユーザーの有効なパスワードも用意して、Sessionsコントローラのcreateアクションに送信されたパスワードと比較できるようにする必要がある。

app/models/user.rb
 class User < ApplicationRecord
  before_save { self.email = email.downcase }
  validates :name,  presence: true, length: { maximum: 50 }
  VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i
  validates :email, presence: true, length: { maximum: 255 },
                    format: { with: VALID_EMAIL_REGEX },
                    uniqueness: { case_sensitive: false }
  has_secure_password
  validates :password, presence: true, length: { minimum: 6 }

  # 渡された文字列のハッシュ値を返す
  def User.digest(string)
    cost = ActiveModel::SecurePassword.min_cost ? BCrypt::Engine::MIN_COST :
                                                  BCrypt::Engine.cost
    BCrypt::Password.create(string, cost: cost)
  end
end
test/fixtures/users.yml
 michael:
  name: Michael Example
  email: michael@example.com
  password_digest: <%= User.digest('password') %>
test/integration/users_login_test.rb
require 'test_helper'

class UsersLoginTest < ActionDispatch::IntegrationTest

  def setup
    @user = users(:michael) #fixtureのデータを参照
  end
  .
  .
  .
  test "login with valid information" do
    get login_path
    post login_path, params: { session: { email:    @user.email,
                                          password: 'password' } }
    assert_redirected_to @user #リダイレクト先が正しいかチェック
    follow_redirect!        #リダイレクト先に移動、ログイン用リンクが表示されなくなったことを確認
    assert_template 'users/show'
    assert_select "a[href=?]", login_path, count: 0
    assert_select "a[href=?]", logout_path
    assert_select "a[href=?]", user_path(@user)
  end
end

8.2.5 ユーザー登録時にログイン

登録の終わったユーザーがデフォルトでログインされている状態にする
Usersコントローラのcreateアクションにlog_inを追加する

app/controllers/users_controller.rb
class UsersController < ApplicationController

  def show
    @user = User.find(params[:id])
  end

  def new
    @user = User.new
  end

  def create
    @user = User.new(user_params)
    if @user.save
      log_in @user   #追加
      flash[:success] = "Welcome to the Sample App!"
      redirect_to @user
    else
      render 'new'
    end
  end

  private

    def user_params
      params.require(:user).permit(:name, :email, :password,
                                   :password_confirmation)
    end
end
test/integration/users_signup_test.rb
require 'test_helper'

class UsersSignupTest < ActionDispatch::IntegrationTest
  .
  .
  .
  test "valid signup information" do
    get signup_path
    assert_difference 'User.count', 1 do
      post users_path, params: { user: { name:  "Example User",
                                         email: "user@example.com",
                                         password:              "password",
                                         password_confirmation: "password" } }
    end
    follow_redirect!
    assert_template 'users/show'
    assert is_logged_in?  #追加
  end
end

8.3 ログアウト

ログアウト機能を追加する

app/helpers/sessions_helper.rb
module SessionsHelper

  # 渡されたユーザーでログインする
  def log_in(user)
    session[:user_id] = user.id
  end
  .
  .
  .
  # 現在のユーザーをログアウトする
  def log_out
    session.delete(:user_id)
    @current_user = nil
  end
end
app/controllers/sessions_controller.rb
class SessionsController < ApplicationController

  def new
  end

  def create
    user = User.find_by(email: params[:session][:email].downcase)
    if user && user.authenticate(params[:session][:password])
      log_in user
      redirect_to user
    else
      flash.now[:danger] = 'Invalid email/password combination'
      render 'new'
    end
  end

  def destroy
    log_out
    redirect_to root_url
  end
end

ユーザーログアウトのテスト

test/integration/users_login_test.rb
require 'test_helper'

class UsersLoginTest < ActionDispatch::IntegrationTest
  .
  .
  .
  test "login with valid information followed by logout" do
    get login_path
    post login_path, params: { session: { email:    @user.email,
                                          password: 'password' } }
    assert is_logged_in?            #ログイン出来ているか確認
    assert_redirected_to @user     #以降ログインテスト
    follow_redirect!
    assert_template 'users/show'
    assert_select "a[href=?]", login_path, count: 0
    assert_select "a[href=?]", logout_path
    assert_select "a[href=?]", user_path(@user)
    delete logout_path          #以降ログアウトテスト
    assert_not is_logged_in?
    assert_redirected_to root_url
    follow_redirect!
    assert_select "a[href=?]", login_path
    assert_select "a[href=?]", logout_path,      count: 0
    assert_select "a[href=?]", user_path(@user), count: 0
  end
end

まとめ

  • Railsのsessionメソッドを使うと、あるページから別のページに移動するときの状態を保持できる。一時的な状態の保存にはcookiesも使える
  • ログインフォームでは、ユーザーがログインするための新しいセッションが作成できる
  • flash.nowメソッドを使うと、描画済みのページにもフラッシュメッセージを表示できる
  • テスト駆動開発は、回帰バグを防ぐときに便利
  • sessionメソッドを使うと、ユーザーIDなどをブラウザに一時的に保存できる
  • ログインの状態に応じて、ページ内で表示するリンクを切り替えることができる
  • 統合テストでは、ルーティング、データベースの更新、レイアウトの変更が正しく行われているかを確認できる

あいも変わらずテストで理解が追いつかなかった。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む