20200522のRailsに関する記事は15件です。

【Rails】ウィザード形式での実装

最初に

ユーザー新規登録の際にdevise認証機能を使用して、ユーザーの情報(ユーザーモデル)と、ユーザーの連絡先の情報(addressモデル)を登録できるよにすることが目的であり、1ページでまとめて(fileds_forを使用しない)ではなくウィザード形式を用いて2ページに分割しての実装が難しかったため、備忘録として残します。

ウィザード形式とは

画面が切り替わりながら操作が進んでいくことで、具体的にはこんな感じ。
https://gyazo.com/c0b7e9d98fb66ad7da724a95a77e4438
ユーザー情報(メールアドレスなど)の登録が完了した際に次のページでユーザー情報に付随する住所などの情報をページを切り替えて表示して登録させている。これをウィザード形式と呼ぶ。らしい。

実装の流れ

ユーザー情報を入力させ、その情報を一旦sessionに保持させる。
次に住所の情報を入力させ、入力が完了したらsessionに保持したユーザー情報と住所の情報を各テーブルに保存させる。
ページが切り替わる際にバリデーションをかけられるため、各ページごとにテーブルを分ける必要があります。
例えばユーザー情報登録画面にaddressモデルの住所を登録する項目(カラム)があった場合、userモデルではpermitされていないため、登録が進まないことになってしまう。1ページに1モデル対応させるようにする。

手順

1.deviseのコントローラーをカスタムできるようにする。
deviseのデフォルトの状態だとコントローラを作成していなくてもdevise/registrationsコントローラーが起動して、view/regstrations/new.html.hamlにルーティングが敷かれている。(ルーティングはdevise_for: users)
これだとカスタムができないため、対応するコントローラーを作成する。

$  rails g devise:controllers users

これでusers/registrations_contoroller.rbが作成される。
https://gyazo.com/a8052ca6b6cc4bd1e19ff4e2465cff1b

2.deviseのルーティングを変更
deviseのルーティングを指定する。
スクリーンショット 2020-05-22 1.00.10.png

この記述でdeviseのルーティングの指定が完了。
devise/registrationsコントローラー → users/registrationsコントローラー
にルーティングを変更することができた。
スクリーンショット 2020-05-22 1.13.26.png

3.app/controllers/application_controller.rbの修正
deviseのpermitはapplication_controller.rbで行う。

application_controller.rb
before_action :configure_permitted_parameters, if: :devise_controller?

  protected

  def configure_permitted_parameters
    devise_parameter_sanitizer.permit(:sign_up, keys: [:nickname, :family_name, :first_name, :family_name_kana, :first_name_kana, :birthday]) 
  end
 before_action :configure_permitted_parameters, if: :devise_controller?

この記述でdevise_controllerが動いた際に、後に記述したconfigure_permitted_parametersメソッドが始動する様にしている。

def configure_permitted_parameters
    devise_parameter_sanitizer.permit(:sign_up, keys: [:nickname, :family_name, :first_name, :family_name_kana, :first_name_kana, :birthday]) 
  end

この記述により、Strong Parametersで追加したいカラムを追加。
デフォルトで:email、:password は許可されているので記載しなくてOK。

4.コントローラーの記述の変更
スクリーンショット 2020-05-22 1.35.15.png

先ほど作ったコントローラー。
クラス名意外消してOK。
あとはいつも通り、コントローラーに記載をしていく。

users/registrations_controller.rb
def new
    @user = User.new
  end

空のハッシュを生成して、

users/registrations_controller.rb
def new
    @user = User.new
  end

 def create
    @user = User.new(sign_up_params)
    unless @user.valid?
      flash.now[:alert] = @user.errors.full_messages
      render :new and return
    end
    session["devise.regist_data"] = {user: @user.attributes}
    session["devise.regist_data"][:user]["password"] = params[:user][:password]
    @address = @user.addresses.build
    render :new_address
  end

createアクションで実際にsessionsに一度保持して、address情報を登録するページに飛ぶように記載している。
最初これ何やってるか意味がわからなかったので3つに分けます。

・1ページ目(ユーザー情報)で入力した情報のバリデーションのチェック
・1ページ目で入力した情報をsessionに保持させること
・次ページの住所情報登録で使用するインスタンスを生成、次ページへ遷移すること

users/registrations_controller.rb
 @user = User.new(sign_up_params)
    unless @user.valid?
      flash.now[:alert] = @user.errors.full_messages
      render :new and return
    end

Userモデルのインスタンスを生成し、ユーザー情報登録画面から送られてきたパラメータをインスタンス変数@userに代入し、引数としてapplication_controller.rbで修正したsign_up_paramsを引数とする。
configure_permitted_parametersメソッドなのにsign_up_paramsで引っ張ってこれている。これは謎です。
もしかしたらdeviseが勝手に動いてくれているのかも。
インスタンス変数@userに対してvalid?メソッドを適用することで送られてきたパラメータが指定されたバリデーションに引っかかっているかどうかをチェック。falseになった場合は、エラーメッセージとともにnewアクションへrenderさせるような記述となっています。

users/registrations_controller.rb
 @user = User.new(sign_up_params)
    unless @user.valid?
      flash.now[:alert] = @user.errors.full_messages
      render :new and return
    end
   session["devise.regist_data"] = {user: @user.attributes}
    session["devise.regist_data"][:user]["password"] = params[:user][:password]

end以下の2文でユーザー情報登録画面での情報をsessionに保持させています。
ウィザード形式上、次ページの入力が完了した段階で各モデルのデータをDBに反映させたいです。なのでここでsessionに保持させる必要があります。この2行ずば抜けて意味不明だったので細かく書きます。。(笑)
session["devise.regist_data"]に値を代入します。この時、sessionにハッシュの形で情報を保持させるために、attributesメソッドを用いてデータを整形しています。
ここで注意したいのがパスワードはattributesメソッドでは追加できないということ。
paramsから取得して別で追加してあげます。パスワードを再度sessionに代入する必要があり、session["devise.regist_data"][:user]["password"]に再代入している。らしい。全然わからん。(泣)

users/registrations_controller.rb
def new
    @user = User.new
  end

 def create
    @user = User.new(sign_up_params)
    unless @user.valid?
      flash.now[:alert] = @user.errors.full_messages
      render :new and return
    end
    session["devise.regist_data"] = {user: @user.attributes}
    session["devise.regist_data"][:user]["password"] = params[:user][:password]
    @address = @user.addresses.build
    render :new_address
  end

最後の2行で、次ページでユーザー情報に紐ずく住所情報を入力させるため、addresses.buildで生成したインスタンス@userに紐づくAddressモデルのインスタンスを生成して@addressに代入。
住所情報を登録させるページを表示するためnew_addressアクションのビューへrenderしています。

5.new_addressへのルーティング指定と対応するビューを移動。

config/routes.rb
Rails.application.routes.draw do
  devise_for :users, controllers: {
    registrations: 'users/registrations'
  }
  devise_scope :user do
    get 'addresses', to: 'users/registrations#new_address'
    post 'addresses', to: 'users/registrations#create_address'
  end
end

devise_scope :user do~endでルーティングを指定。これで#new_addressと#create_addressが使用できるようになる。
対応するnew_address.html.hamlファイルは、
app/views/devise/registrations/下に移動。これでusers/registrationsのアクションでビューが表示される。

6.create_addressアクションでユーザー情報とプロフィール情報をテーブルに保存。

registrations_controller.rb
def create_address
    @user = User.new(session["devise.regist_data"]["user"])
    @address = Address.new(address_params)
    unless @address.valid?
      flash.now[:alert] = @address.errors.full_messages
      render :new_address and return
    end
    @user.addresses.build(@address.attributes)
    @user.save
    session["devise.regist_data"]["user"].clear
    sign_in(:user, @user)
    redirect_to root_path
end

private
  def address_params
    params.require(:address).permit(:post_code, :prefectures, :city, :block, :building, :phone_number)  
  end 

さきほどsessionにuserオブジェクトのデータを保存したので、create_addressアクションでもuserオブジェクトを生成して@userに代入。2行目でprivateメソッド下の引数で address_paramsメソッドを引数としてAddressモデルを生成、@addressに代入。
あとは先ほどと同じで、addressオブジェクトのバリデーションを確認して、通ったら保存。
addressesテーブルに外部キーを設定していると、バリデーションに引っかかるので、モデルでoptional: trueを追加。

address.rb
class Address < ApplicationRecord
  belongs_to :user, optional: true
end

optional: trueは外部キーにnullが入ることを許可してくれるオプション。これでバリデーションに引っ掛からなくなる。
その後はuserモデルに紐付いたaddressモデルという記述を追加。
@userをsaveして、sessionをclearメソッドを使用して削除。
まだ登録できただけなので、sign_inメソッドでサインイン。

あとはcreate_address.html.hamlが動くかを確認して、正常に動けばウィザード形式の実装完了です!

参考文献

https://satoryu.hatenablog.com/entry/antipattern_in_designing_models_for_wizard
https://note.com/vixer93/n/nac92cc4c0983

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

【Rails】ウィザード形式でのユーザー新規登録機能実装

最初に

ユーザー新規登録の際にdevise認証機能を使用して、ユーザーの情報(ユーザーモデル)と、ユーザーの連絡先の情報(addressモデル)を登録できるよにすることが目的であり、1ページでまとめて(fileds_forを使用しない)ではなくウィザード形式を用いて2ページに分割しての実装が難しかったため、備忘録として残します。

ウィザード形式とは

画面が切り替わりながら操作が進んでいくことで、具体的にはこんな感じ。
https://gyazo.com/c0b7e9d98fb66ad7da724a95a77e4438
ユーザー情報(メールアドレスなど)の登録が完了した際に次のページでユーザー情報に付随する住所などの情報をページを切り替えて表示して登録させている。これをウィザード形式と呼ぶ。らしい。

実装の流れ

ユーザー情報を入力させ、その情報を一旦sessionに保持させる。
次に住所の情報を入力させ、入力が完了したらsessionに保持したユーザー情報と住所の情報を各テーブルに保存させる。
ページが切り替わる際にバリデーションをかけられるため、各ページごとにテーブルを分ける必要があります。
例えばユーザー情報登録画面にaddressモデルの住所を登録する項目(カラム)があった場合、userモデルではpermitされていないため、登録が進まないことになってしまう。1ページに1モデル対応させるようにする。

手順

1.deviseのコントローラーをカスタムできるようにする。
deviseのデフォルトの状態だとコントローラを作成していなくてもdevise/registrationsコントローラーが起動して、view/regstrations/new.html.hamlにルーティングが敷かれている。(ルーティングはdevise_for: users)
これだとカスタムができないため、対応するコントローラーを作成する。

$  rails g devise:controllers users

これでusers/registrations_contoroller.rbが作成される。
https://gyazo.com/a8052ca6b6cc4bd1e19ff4e2465cff1b

2.deviseのルーティングを変更
deviseのルーティングを指定する。
スクリーンショット 2020-05-22 1.00.10.png

この記述でdeviseのルーティングの指定が完了。
devise/registrationsコントローラー → users/registrationsコントローラー
にルーティングを変更することができた。
スクリーンショット 2020-05-22 1.13.26.png

3.app/controllers/application_controller.rbの修正
deviseのpermitはapplication_controller.rbで行う。

application_controller.rb
before_action :configure_permitted_parameters, if: :devise_controller?

  protected

  def configure_permitted_parameters
    devise_parameter_sanitizer.permit(:sign_up, keys: [:nickname, :family_name, :first_name, :family_name_kana, :first_name_kana, :birthday]) 
  end
 before_action :configure_permitted_parameters, if: :devise_controller?

この記述でdevise_controllerが動いた際に、後に記述したconfigure_permitted_parametersメソッドが始動する様にしている。

def configure_permitted_parameters
    devise_parameter_sanitizer.permit(:sign_up, keys: [:nickname, :family_name, :first_name, :family_name_kana, :first_name_kana, :birthday]) 
  end

この記述により、Strong Parametersで追加したいカラムを追加。
デフォルトで:email、:password は許可されているので記載しなくてOK。

4.コントローラーの記述の変更
スクリーンショット 2020-05-22 1.35.15.png

先ほど作ったコントローラー。
クラス名意外消してOK。
あとはいつも通り、コントローラーに記載をしていく。

users/registrations_controller.rb
def new
    @user = User.new
  end

空のハッシュを生成して、

users/registrations_controller.rb
def new
    @user = User.new
  end

 def create
    @user = User.new(sign_up_params)
    unless @user.valid?
      flash.now[:alert] = @user.errors.full_messages
      render :new and return
    end
    session["devise.regist_data"] = {user: @user.attributes}
    session["devise.regist_data"][:user]["password"] = params[:user][:password]
    @address = @user.addresses.build
    render :new_address
  end

createアクションで実際にsessionsに一度保持して、address情報を登録するページに飛ぶように記載している。
最初これ何やってるか意味がわからなかったので3つに分けます。

・1ページ目(ユーザー情報)で入力した情報のバリデーションのチェック
・1ページ目で入力した情報をsessionに保持させること
・次ページの住所情報登録で使用するインスタンスを生成、次ページへ遷移すること

users/registrations_controller.rb
 @user = User.new(sign_up_params)
    unless @user.valid?
      flash.now[:alert] = @user.errors.full_messages
      render :new and return
    end

Userモデルのインスタンスを生成し、ユーザー情報登録画面から送られてきたパラメータをインスタンス変数@userに代入し、引数としてapplication_controller.rbで修正したsign_up_paramsを引数とする。
configure_permitted_parametersメソッドなのにsign_up_paramsで引っ張ってこれている。これは謎です。
もしかしたらdeviseが勝手に動いてくれているのかも。
インスタンス変数@userに対してvalid?メソッドを適用することで送られてきたパラメータが指定されたバリデーションに引っかかっているかどうかをチェック。falseになった場合は、エラーメッセージとともにnewアクションへrenderさせるような記述となっています。

users/registrations_controller.rb
 @user = User.new(sign_up_params)
    unless @user.valid?
      flash.now[:alert] = @user.errors.full_messages
      render :new and return
    end
   session["devise.regist_data"] = {user: @user.attributes}
    session["devise.regist_data"][:user]["password"] = params[:user][:password]

end以下の2文でユーザー情報登録画面での情報をsessionに保持させています。
ウィザード形式上、次ページの入力が完了した段階で各モデルのデータをDBに反映させたいです。なのでここでsessionに保持させる必要があります。この2行ずば抜けて意味不明だったので細かく書きます。。(笑)
session["devise.regist_data"]に値を代入します。この時、sessionにハッシュの形で情報を保持させるために、attributesメソッドを用いてデータを整形しています。
ここで注意したいのがパスワードはattributesメソッドでは追加できないということ。
paramsから取得して別で追加してあげます。パスワードを再度sessionに代入する必要があり、session["devise.regist_data"][:user]["password"]に再代入している。らしい。全然わからん。(泣)

users/registrations_controller.rb
def new
    @user = User.new
  end

 def create
    @user = User.new(sign_up_params)
    unless @user.valid?
      flash.now[:alert] = @user.errors.full_messages
      render :new and return
    end
    session["devise.regist_data"] = {user: @user.attributes}
    session["devise.regist_data"][:user]["password"] = params[:user][:password]
    @address = @user.addresses.build
    render :new_address
  end

最後の2行で、次ページでユーザー情報に紐ずく住所情報を入力させるため、addresses.buildで生成したインスタンス@userに紐づくAddressモデルのインスタンスを生成して@addressに代入。
住所情報を登録させるページを表示するためnew_addressアクションのビューへrenderしています。

5.new_addressへのルーティング指定と対応するビューを移動。

config/routes.rb
Rails.application.routes.draw do
  devise_for :users, controllers: {
    registrations: 'users/registrations'
  }
  devise_scope :user do
    get 'addresses', to: 'users/registrations#new_address'
    post 'addresses', to: 'users/registrations#create_address'
  end
end

devise_scope :user do~endでルーティングを指定。これで#new_addressと#create_addressが使用できるようになる。
対応するnew_address.html.hamlファイルは、
app/views/devise/registrations/下に移動。これでusers/registrationsのアクションでビューが表示される。

6.create_addressアクションでユーザー情報とプロフィール情報をテーブルに保存。

registrations_controller.rb
def create_address
    @user = User.new(session["devise.regist_data"]["user"])
    @address = Address.new(address_params)
    unless @address.valid?
      flash.now[:alert] = @address.errors.full_messages
      render :new_address and return
    end
    @user.addresses.build(@address.attributes)
    @user.save
    session["devise.regist_data"]["user"].clear
    sign_in(:user, @user)
    redirect_to root_path
end

private
  def address_params
    params.require(:address).permit(:post_code, :prefectures, :city, :block, :building, :phone_number)  
  end 

さきほどsessionにuserオブジェクトのデータを保存したので、create_addressアクションでもuserオブジェクトを生成して@userに代入。2行目でprivateメソッド下の引数で address_paramsメソッドを引数としてAddressモデルを生成、@addressに代入。
あとは先ほどと同じで、addressオブジェクトのバリデーションを確認して、通ったら保存。
addressesテーブルに外部キーを設定していると、バリデーションに引っかかるので、モデルでoptional: trueを追加。

address.rb
class Address < ApplicationRecord
  belongs_to :user, optional: true
end

optional: trueは外部キーにnullが入ることを許可してくれるオプション。これでバリデーションに引っ掛からなくなる。
その後はuserモデルに紐付いたaddressモデルという記述を追加。
@userをsaveして、sessionをclearメソッドを使用して削除。
まだ登録できただけなので、sign_inメソッドでサインイン。

あとはcreate_address.html.hamlが動くかを確認して、正常に動けばウィザード形式の実装完了です!

参考文献

https://satoryu.hatenablog.com/entry/antipattern_in_designing_models_for_wizard
https://note.com/vixer93/n/nac92cc4c0983

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

rspecでGraphQLのresolverをテストする

はじめに

graphqlのテストってみなさんどう書いてますか?
queryを定義してテストもできますが、少し面倒だど感じている今日この頃。
そこで、queryとかmutationに定義されているresolverをrspecでテストする方法について説明します。

mutationの例

例えば、以下のようなmutationを定義しているとしましょう。

mutation.rb
module Mutations
  class CreateUser < BaseMutation
    argument :id, ID, required: true
    field :user, ObjectTypes::UserType, null: false

    def resolve(id: nil)
      user = ::User.create!(id: id)
      {
        user: user
       }
    end
  end
end

単純ですが、idをもらってuserを作成するmutationです。
今回のテスト対象はこのresolveになります。

rspceを書く

ではさっそくrspecを書きたいと思います。

create_user_rspec.rb
require 'rails_helper'

RSpec.describe CreateUser, type: :request do
  describe 'resolver' do
    it 'userが作成されていること' do
      mutation = CreateUser.new(field: nil, object: nil, context:{})
      mutation.resolve(id: [作成するuser_id])
      expect(..).to eq ..
    end

まず、mutationのクラスであるCreateUserクラスのインスタンスを作成します。
引数のfieldとobjectは基本的にnullで良いと思います。
contextについては、場合によってはcurrent_userを入れることもできます。その場合は context:{current_user: User.first}となります。

contextを直接入れられるのが便利ですよね。
そして、作成したmutationのresolveメソッドを読んであげればテスト上で定義したresolve内の処理が実行されます。
これだと、graphqlのテストがかなり楽にかけます!

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

例外のまとめ 自分用

記事まとめ

https://speakerdeck.com/jnchito/number-rubykansai-2018-01-13?slide=28

例外の基本

パターン1
def some_method

  1 / 0
rescue ZeroDivisionError => e
  puts e.class
  puts e.message
  puts e.backtrace
end


パターン2
def some_method
  begin
    1 /0
  rescue ZeroDivisionError => e
    puts e.class
    puts e.message
    puts e.backtrace
  end
end

使っていい場合

仲間を道ずれにしたくない場合など

users.each do |user|
 beginn
  send_mail_to(user)
 rescue => e
   puts e.backtrace
 end
end

例外をかくポイント

  • ログを残す
    • backtraceを
  • 通知する

    • slackなどに
  • 例外の対象範囲は狭くする

  • 例外もテストする

例外の対応方針

  • 業務エラーとシステムエラーに切り分ける

    • 業務エラー
    • ユーザーミス
    • 権限エラー
    • システムエラー
    • バグ(シンタックなど)
    • ネットワーク、DBエラー
  • 業務エラー
    =>がモデルで対応
    => 返り血を検証する(saveなども true falseが返ってきている)

  • システムエラー

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

エラー対応方法

  • ログをみる観点
    • エラーは自分が書いたところからのエラーの確認
    • ライブラリーのエラーの確認
    • ユーザーはどんな操作をしたのかの確認

参考

https://qiita.com/jnchito/items/056325421b7e36f02335

長くて飽きてしまった

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

iframeタグを用いてyoutube動画を埋め込む方法(haml)

はじめに

Railsアプリケーションを作成する際に、youtubeの埋め込み方法を調べたのでまとめます。
今回はhamlを用いたマークアップで、youtubeを表示させることができます。

前提環境

今回作成したアプリケーションの環境は下記の通りです。

  • ruby 2.5.1
  • Rails 5.2.4.3

完成見本

スクリーンショット 2020-05-21 0.41.16.png

無事埋め込む事ができるとこのような画面になります。
それでは早速実装していきましょう!

hamlを導入する

念のため、hamlの導入方法から説明していきます!
gemを導入するだけなので簡単です!

Gemfileの1番下に下記のように記述します。
※ Gemfileに記述したらbundle installを忘れないように!!

Gemfile
gem 'haml-rails'
ターミナル
%  bundle install

これでhamlを導入することができました!

しかし、既存のファイルには haml が適用されていないので、下記のように記述して haml に変更します。

ターミナル
%  rails haml:erb2haml

実行して、下のような記述が出てきたときは、" y " と入力してEnterを押してください。

ターミナル
Would you like to delete the original .erb files? (This is not recommended unless you are under version control.) (y/n)

これで既存のファイルにもhamlを適用させることができました!!

Youtube動画のURLをコピーする

それではいよいよyoutubeの埋め込みの方に入っていきます。
今回は、こちらのyoutubeを例にして実装していきます。

まず、表示させたいyoutube動画の、HTML埋め込みタグをコピーしてください。

コピーは、下記の手順でできます。

スクリーンショット 2020-05-20 21.29.27.png

スクリーンショット 2020-05-20 21.38.46.png

スクリーンショット 2020-05-20 21.39.26.png

ビューファイルに動画を埋め込む

では、コピーができたら、ビューファイルの方をいじっていきます。
今回使用するのはiframeタグと呼ばれるものです。

iframeタグをhamlで記述すると下のような書き方になります。

%iframe{ この中にyoutubeの情報を記述していく }

まずyoutube動画を表示させたいビューファイルを開き、先ほどコピーしたものを1度そのままペーストします。

〇〇〇.html.haml
<iframe width="560" height="315" src="https://www.youtube.com/embed/_ZRp7KYXM1A" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>

ペーストしたら、実際にhamlの書き方に直していきます。

タグの中の width 〜 allowfullscreen の部分を、%iframe {}{} の中にコピペしてください。

〇〇〇.html.haml
<iframe width="560" height="315" src="https://www.youtube.com/embed/_ZRp7KYXM1A" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
⬇️ ⬇️ ⬇️
%iframe{ width="560" height="315" src="https://www.youtube.com/embed/_ZRp7KYXM1A" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen }

このままだとエラーが出てしまうので、さらに修正を加えていきます。
修正をする箇所はいくつかあります。細かい変更なので例を見てもらった方がわかりやすいかと思います。

〇〇〇.html.haml
%iframe{ width="560" height="315" src="https://www.youtube.com/embed/_ZRp7KYXM1A" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen }
⬇️ ⬇️ ⬇️
%iframe{ width: "560", height: "315", src: "https://www.youtube.com/embed/_ZRp7KYXM1A", frameborder: "0", allow: "accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture", allow: "fullscreen" }

一応、修正箇所を書き出すと、

  1. = から : に変更
  2. 属性の後に , を追加
  3. allowfullscreenallow: "fullscreen" に変更

このような変更を行っています。

最初のHTMLタグの記述と、修正後の記述を比べてみるとこのような違いになっています。

元の記述(htmlバージョン)
<iframe width="560" height="315" src="https://www.youtube.com/embed/_ZRp7KYXM1A" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
直した記述(hamlバージョン)
%iframe{width: "560", height: "315", src: "https://www.youtube.com/embed/_ZRp7KYXM1A", frameborder: "0", allow: "accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture", allow: "fullscreen" }

以上の操作で、無事youtubeの動画を表示させることができました!

ちなみに、パラメータ(widthheightなど)の値を変えることで表示される大きさを変更したりすることも可能です!

パラメータは、元から記述されているもの以外にも、

  • 動画の開始時間や終了時間を決めるパラメータ
  • 動画終了後の関連動画を非表示にするパラメータ

などいくつか存在しています。

パラメータについては、今後時間がある時に追記して行けたらと思います!!

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

RSpecでActiveRecord::StatementInvalid: Could not find tableが出た

出たエラー

ActiveRecord::StatementInvalid: Could not find table

テストを実行したときにDBのテーブルを見つけられない

やってみたこと

rails db:migrate:reset RAILS_ENV=test

でテストDBをリセットして反映

rails dbconsole -e test

sqlite> .table

でテーブルができたか確認。作成された。

多くの人はここまでで解決と思うが、もう一度テストを走らせると、

ActiveRecord::StatementInvalid: Could not find table

同じエラー。

そして、またテーブルを確認してみると、テーブルが消えてた。

これを踏まえてやってみたこと

ActiveRecord::Migration.maintain_test_schema!を外す

rails_helper.rbで

rails_helper.rb
#コメントアウトするコード
ActiveRecord::Migration.maintain_test_schema!

これをコメントアウトした。

そしたら通った。が、まだテーブルは作られず、そのテーブルを使うテストは全て落ちた。

ActiveRecord::Migration.maintain_test_schema!とは

rspecを実行する前にschema.rbをみて、testのDBを自動でマイグレーションしてくれるためのコード。

これを外すことでテストが実行されるということは、schemaに問題ある可能性ありだと思ったので確認。

Could not dump table "テーブル名" because of following StandardError

schemaファイルにこのように書かれていた。

テーブルをschemaに反映できていない。

migrationファイルを確認

すると、

migration_name.rb
  def up
    change_column :comments, :discovery_id, :reference, null: true
  end

としていた箇所があった。

型にreferenceなんてないが、なぜかこうしてしまっていた。

:referenceを:integerに変えて、rails db:migrate:reset
を行うと、schemaに記述された。

再度走らせる

schemaにしっかり記述されてから、もう一度マイグレーションをリセット

rails db:migrate:reset RAILS_ENV=test

そして、ActiveRecord::Migration.maintain_test_schema!のコメントを外す

rails_helper.rb
#コメントアウトを外す
ActiveRecord::Migration.maintain_test_schema!

そしてテストを走らせる

bin/rspec

全テスト通った。

まとめ

テスト実行 → テーブルが見つからない → テスト環境でリセットしてマイグレーション → テーブルできた → またテスト実行 → なくなって見つからない → 実行直前のスキーマ読まないようにする → テストは動く → スキーマに見つからないテーブルが記述されてなかった → 適切にマイグレーションする → テスト走る

こんな流れだった。

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

【チーム開発】マイグレーションの更新作業

概要

チーム開発において、DBの修正が発生することもあり!!
安易に更新してしまうとサーバーがおかしくなってしまうのでは???
チームに迷惑かけちゃう:confounded:
と、私も更新することが怖かったのですが、以下の手順を踏んだら問題なくできました:relaxed:

実施方法

$rake db:drop
 #DBの情報を全て削除!!登録したユーザー情報なども削除されます!
$rake db:create
 #DBの更新
$rake db:migrate
 #マイグレート
$rake db:migrate:status
 #きちんとマイグレートできているかを確認

ちなみに、、、
カテゴリーなどをテキストエディタに作成していたら、下記のコードもマイグレートした後に行なってください!!

$rake db:seed

これでDBの更新OK:laughing:
この手法は、登録したユーザー情報なども合わせて削除されるので、また登録する必要があります!

参考文献

https://qiita.com/parsetree/items/e9b08c6b11f762b949de

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

【Rails】hidden_fieldでのエラー対処法

環境

Mac OS
Rails5.2

状況

Railsでチャット機能を作成中で、
下記の機能を搭載したいと考えておりました。

①form_withヘルパーを用いて、チャットでメッセージを送りたい
②どのチャットルームのメッセージなのかを判断できる様、
【chat_room_id】のデータをhidden_fieldを用いて、form_withのパラメータと一緒に渡したい。

上手くいかなかった例

chat_rooms/show.html.erb
 <%= form_with(model: @chat_message ,url:chat_room_path,local:true,method: :post) do |f| %>
    <%= f.label :message %>
    <%= f.text_field :message, :class => "form-control myform", :placeholder => "メッセージを入力して下さい" %>
   <%= f.hidden_field :chat_room_id, :value => @chat_room.id %>

    <div class="text-center">
      <%= f.submit "チャットを送る", :class => "btn btn-primary",method: :post %>¥
      </div>
    <% end %>

上手くいった(chat_room_idを渡せた)例

chat_rooms/show.html.erb
 <%= form_with(model: @chat_message ,url:chat_room_path,local:true,method: :post) do |f| %>
      <%= f.label :message %>
     <%= f.text_field :message, :class => "form-control myform", :placeholder => "メッセージを入力して下さい" %>
   <%= f.hidden_field :chat_room_id, :value => "@chat_room.id" %>

      <div class="text-center">
        <%= f.submit "チャットを送る", :class => "btn btn-primary",method: :post %>

      </div>
    <% end %>

<%= f.hidden_field :chat_room_id, :value => "@chat_room.id" %>
と、"" をつけて記載することで@chat_room.idをもったデータをコントローラに送ることができました。

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

Visual Studio Codespaces で Ruby on Rails

最近 Rails を始めた VSCoder ですが、以下の問題点がありました。
※VSCoder: Visual Studio Code 愛用者

  • 環境構築が面倒

そのため、AWS の Cloud9 を利用していたのですが、今度は以下の問題が

  • ブラウザ上のエディタは使いづらく違和感がある
  • VSCode が使いたい

そこで今ホットな Visual Studio Codespaces に手を出してみて、人生変わりました。

Visual Studio Codespaces とは?

一言で言うと、クラウド上の開発環境です。
公式サイトから引用すると、以下の特徴が挙げられます。

  • Git リポジトリ、拡張機能、および組み込みのコマンドライン インターフェイスを備えたブラウザーベースのエディターである
  • どのデバイスからでもアプリケーションを編集、実行、デバッグできる

また、私にとって最大の特徴は、

  • デスクトップアプリの VSCode でも開発できる

ということです。もちろん拡張機能も追加できます。

Codespace を作成する

  1. Azure のアカウントを作成します。
  2. Visual Studio Codespaces のサイトから、Azure アカウントで Sign in します。
  3. Create Codespace で作成します。Codespace Name だけ指定すれば、あとはデフォルトで十分だと思います。既存のリポジトリがあれば、Git Repository で指定すると自動的に Clone してくれます。(Rails + Space で Railspace という名前にしてみました)

image.png

ブラウザ上に VSCode が出ます!!!
image.png

ターミナルも使えます。vsonline というユーザで workspace が作られています。
image.png

Python はデフォルトでいろいろ入っています。
image.png

デスクトップアプリの VSCode で開く

  1. Visual Studio Code のインストール
  2. ウィンドウ左側のツールバーの Extensions で Visual Studio Codespaces をインストール image.png
  3. Remote Explorer のアイコンができるのでそこから先ほど作成したのと同じアカウントに Sign in します。 image.png
  4. 先ほど作成した Railspace という Codespace が出てくるので、コンセントのマークをクリックして connect します。 image.png

これでいつもの VScode です!!
image.png

Ruby on Rails の環境構築

Cloud9 はデフォルトで Ruby や Rails の環境がありますが、Visual Studio Codespaces では残念ながらありません...。まあどうせ、 Cloud9 でもバージョン変えたりするので。
以下はすべて、Codespace に接続した デスクトップアプリの VSCode のターミナルで行います。
Codespaces は Linux 環境を使用しているので、基本的には普通の Linux への環境構築と同じです。

rbenv のインストール

Ruby 公式サイトでもおすすめしているこちらの README を参考にインストールしていきます。rbenv によって Ruby の複数のバージョンを管理できます。

まずはリポジトリをクローンしてパスに追加し、セットアップします。

$ git clone https://github.com/rbenv/rbenv.git ~/.rbenv
$ echo 'export PATH="$HOME/.rbenv/bin:$PATH"' >> ~/.bashrc
$ echo 'export PATH="$HOME/.rbenv/shims:$PATH"' >> ~/.bashrc
$ ~/.rbenv/bin/rbenv init

その後、ターミナルを再起動する必要があるので、 + ボタンで新しいターミナルを開いてください
以下のコマンドを入力して確認できますが、rbenv install が not found になっています。

$ curl -fsSL https://github.com/rbenv/rbenv-installer/raw/master/bin/rbenv-doctor | bash

image.png

指示されたリンクの通り、以下で解決します。

$ mkdir -p "$(rbenv root)"/plugins
$ git clone https://github.com/rbenv/ruby-build.git "$(rbenv root)"/plugins/ruby-build

もう一度確認すると、今度はうまくいっているはずです。

$ curl -fsSL https://github.com/rbenv/rbenv-installer/raw/master/bin/rbenv-doctor | bash

image.png

これで rbenv は完了です。私はここが山場でした。

Ruby と Rails のインストール

主要な Ruby のバージョンを確認できます。すべて見たい場合は、$ rbenv install --list-all で可能です。

$ rbenv install --list

あとは $ rbenv install [バージョンナンバー] で好きなバージョンをインストールできます。

$ rbenv install 2.7.1

インストール後、以下のコマンドでどのバージョンの Ruby を使うか指定します。

$ rbenv global 2.7.1

無事に Ruby がインストールできました!
image.png

続いて、Rails をインストールします。

$ gem install rails

Rails をインストールできました!
image.png

アプリの作成とローカルホストへのアクセス

あとはまるでローカル環境であるかのように開発ができます。
ローカルホストも使えます

$ rails new SampleApp

で Rails アプリを作成後、

$ rails s

でサーバを立ち上げます。
ブラウザで http://localhost:3000/ にアクセスすると...
image.png

以上です。
Rails に関しては始めて2カ月なので至らない点があるかもしれないです。
修正やコメントお待ちしてます。

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

railsを使ったajaxの非同期通信概要

非同期通信って何?

結論、ネットにつなぐことなく更新できることです。
具体例としてTwitterのいいね!機能があげられます。
試しにネット環境を遮断していいね!を押してみてください。
いいね!が普通に押せます。
これが非同期通信です。
この実装ができるようになるとページをいちいちリロードせずに
機能を反映することができ、
ユーザーの視点から見るとものすごく便利な機能です。

非同期通信をするために何を使うのか

結論、AjaxとJSONです。1個ずつ説明していきます。

Ajax

非同期通信を英語で言うと"Asynchronous JavaScript + XML"です。
略してAjax!これだけです。
Ajaxと言われたり、そのコードを見かけたら非同期通信のことだと思ってください。

JSON

結論、サーバからのレスポンスで返す時の形式になるものです。
これだとよくわかりませんので具体例を示しながら説明します。
まず、以下のようなコードがあったとします。

{fruits: "orange", vegetable: "onion"}

これはrubyの時にも学んだキーとバリューの組み合わせです。
特に何にも設定していなければサーバーからキーとバリューのレスポンスを受け取る時
HTML形式で受け取っています。

キーとバリューって何だっけ?キーはカテゴリーでバリューはカテゴリーの中にある具体例のようなものでした。そしてこのキーとバリューをセットで扱うためにハッシュを用いるのでした。

HTML形式で受け取るとネット通信が始まり、そこでデータが更新されていくようなイメージです。
ではこれをJSON形式で受け取るとどうなるのか?
ネット通信を始めることなく一部だけページを書き換えるデータを受け取ることができます。
つまり、これが非同期通信です。
JSON形式でデータを受け取ることによって非同期通信が可能になると理解しておきましょう。

非同期通信の実装概要

以下、手順です
①JavaScriptでリクエストを送り、レスポンスをJSON形式に変更する
②JSON形式でレスポンスしてもらうようにコントローラーのアクションにその旨を追記する。
③レスポンスするためのJSON形式のビューを追加する
あと細かい作業もあるのですがこの概要だけ抑えておくと、
実装のタイミングでスムーズに進められると思います。
また具体的な手順は後日、記述しようと思います。

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

rails CSV.open とCSV.generateの使う場面の違い CSV

CSV.open とCSV.generateの使う場面の違い

  • csvの使い方を調べると、CSV.open が多い
  • 会社の実装を見てみるとCSV.generateが多い気がする

なぜ場面によって違うのか?

csvの出力先を【ディレクトリ配下に置く】のか。【export】するのか(railsで)によって異なるのかな。

## CSV.open
### これはopenの第一引数の指定先(output.csv)に出力する事を想定している
### 実行 すると同じディレクトリ配下に ls csv_ruby.rb output.csvみたいになる

csv_ruby.rb # ファイル名

lists = [["1","2","3"],["4","5","6"]]

CSV.open("./output.csv", "w") do |row|
  lists.each do
    row << list 
  end
end

## CSV.generate


lists = [["1","2","3"],["4","5","6"]]

csvs = CSV.generate do |row|
  lists.each do
    row << list 
  end
end

## これは出力結果を なにかしらの変数に入れて中身を確認する必要がある

## 最終的にsenddataの引数に渡してダウンロードする

def index
 format.csv do
   sendata(csvs, type: :csv, failename: "ファイル名")
 end
end

まとめ

こんな感じで、最終的な期待結果に応じて、csvの作り方が異なる

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

【Rails】CarrierWaveとrmagickでプロフィール画像を設定できるようにする

CarrierWaveで画像のアップロード機能を実装する

今回はユーザーテーブルにプロフィール画像を設定できるようにします。

1. モデルに画像アップロード用のカラムを設定する

userモデルに画像をアップロードするためのimageカラムを追加します。

$ rails g migration add_image_column_to_users image:string

マイグレーションファイルの内容を適用します。

$ rails db:migration

もしすでにデータベース内にimageカラムを持っていないユーザーデータが存在しているなら、データベースの中身を一度リセットする必要があります。

$ rake db:migrate:reset

2. Gemfileにcarrierwaveを追加してbundle installを実行

Gemfile
gem 'carrierwave'
$ bundle install

サーバーの再起動も忘れないように、このタイミングで。(基本いつでも大丈夫)

rails s

3. rails g uploader imageで画像のアップローダークラスを作成

$ rails g uploader image

これで画像をアップロードできるようになります。

4. アップローダを実装したいクラスに以下のコードを追加

/app/models/user.rb
mount_uploader :image, ImageUploader

5. Strong Parameterにimageカラムを追加

/app/controllers/users_controller.rb
def user_params
  params.require(:user).permit(:name, :email, :image)
end

これで画像をアップロードする準備が整いました、あとはviewを記述するだけです。

6. 画像の表示・投稿するためのviewを記述

画像表示するためのview

/app/views/users/show.html.erb
<% if @user.image? %>                                                              
  <%= image_tag @user.image.url %>
<% end %>

画像を投稿するためのview

/app/views/users/new.html.erb
<%= f.label :image %>
<%= f.file_field :image %>

これで画像の表示・投稿機能が完成しました!
アップロードした画像はpublicフォルダ内に格納されていました。しかしこのままではアップロードしたときの画像名・大きさでフォルダ内に保存されるので、画像の加工を行うgemのrmagickを利用します。

rmagicで画像の加工を行う

rmagickのgemをインストールするにはimageMagickがインストールされていないといけないそうです。

imageMagickがインストールされているかは以下のコマンドで確認できます。

$ convert -version

もしインストールされていなかったらimageMagickをインストールしてください。Macの場合はbrewを使えばインストールできるそうです。

$ brew install imagemagick@6

1. Gemfileにrmagickを追加してbundle installを実行します

Gemfile
gem 'rmagick'                                                                      
$ bundle install

サーバーの再起動も忘れないでね

2. image_uploader.rbで設定を記述する

画像をどのような形式に加工するかは、/app/uploaders/image_uploader.rbで設定できます。

デフォルトでは以下のようになっています、このファイルを編集していきます。

/app/uploaders/image_uploader.rb
class ImageUploader < CarrierWave::Uploader::Base
  # Include RMagick or MiniMagick support:
  # include CarrierWave::RMagick
  # include CarrierWave::MiniMagick

  # Choose what kind of storage to use for this uploader:
  storage :file
  # storage :fog

  # Override the directory where uploaded files will be stored.
  # This is a sensible default for uploaders that are meant to be mounted:
  def store_dir
    "uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}"
  end

  # Provide a default URL as a default if there hasn't been a file uploaded:
  # def default_url(*args)
  #   # For Rails 3.1+ asset pipeline compatibility:
  #   # ActionController::Base.helpers.asset_path("fallback/" + [version_name, "default.png"].compact.join('_'))
  #
  #   "/images/fallback/" + [version_name, "default.png"].compact.join('_')
  # end

  # Process files as they are uploaded:
  # process scale: [200, 300]
  #
  # def scale(width, height)
  #   # do something
  # end

  # Create different versions of your uploaded files:
  # version :thumb do
  #   process resize_to_fit: [50, 50]
  # end

  # Add a white list of extensions which are allowed to be uploaded.
  # For images you might use something like this:
  # def extension_whitelist
  #   %w(jpg jpeg gif png)
  # end

  # Override the filename of the uploaded files:
  # Avoid using model.id or version_name here, see uploader/store.rb for details.
  # def filename
  #   "something.jpg" if original_filename
  # end
end

変更を加えたファイルはこのようになりました。

/app/uploaders/image_uploader.rb
class ImageUploader < CarrierWave::Uploader::Base  

  # リサイズしたり画像形式を変更するのに必要
  include CarrierWave::RMagick

  # 画像の上限を640x480にする
  process :resize_to_limit => [640, 480]

  # 保存形式をJPGにする
  process :convert => 'jpg'

  # サムネイルを生成する設定
  version :thumb do
    process :resize_to_limit => [300, 300]
  end

  version :thumb100 do
    process :resize_to_limit => [100, 100]
  end

  version :thumb30 do
    process :resize_to_limit => [30, 30]
  end

  # jpg,jpeg,gif,pngしか受け付けない
  def extension_white_list
    %w(jpg jpeg gif png)
  end

  # 拡張子が同じでないとGIFをJPGとかにコンバートできないので、ファイル名を変更
  def filename
    super.chomp(File.extname(super)) + '.jpg' if original_filename.present?
  end

  # ファイル名を日付にするとタイミングのせいでサムネイル名がずれる
  #ファイル名はランダムで一意になる
  def filename
    "#{secure_token}.#{file.extension}" if original_filename.present?
  end

  protected
  def secure_token
    var = :"@#{mounted_as}_secure_token"
    model.instance_variable_get(var) or model.instance_variable_set(var, SecureRandom.uuid)
  end

end

これで完了です!

参考

https://pg-happy.jp/carrierwave-rmagic-uploader.html
https://nyoken.com/rails-carrierwave

image_uploader.rbについて
http://www.workabroad.jp/tech/1118
https://qiita.com/tackey/items/ba68ca8489500b7cb739

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

controller を間違えて生成してしまったら

rails tutorial 3章 「ほぼ静的なページの作成」 にてcontrollerを生成する際にcontroller名を間違えて生成してしまいました。
その時の対処法です。


controller Static_Pages_controller.rb
アクション home help


controller Static_controller.rb
アクション Pages home help

原因

controllerを生成する際にコマンドラインで
rails g controller Static Pages home help
としてしまった為、Pages がアクションとして認識されてしまったんですね、、、

対処

rails destroy controller Static Pages home help
これで間違えて生成したcontrollerは削除できました。

再度
rails g controller StaticPages home help
で正しいcontrollerを生成

ちなみに
rails g controller Static_Pages home help
でも同じStatic_Pagesコントローラーが生成されます。

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

Rails 6のdevelopment環境でhttpsを使う

はじめに

この記事はRails6をしばらく使用し、ハマった部分、忘れたくないことを将来の自分のためにメモとして残し、公開することで同じようなことをRails6で実現しようとしている方のお役に立てたら、うれしいなと思い書いています。

この記事を読んで「助かったぜ!」、「それは間違ってるぜ!」や「もっとこうした方がいいぜ!」
がありましたら、お知らせ頂ければと思います。

実現したこと

  • Rails 6.0.3.1のdevelopment環境でwebpack-dev-serverpumaの起動をforemanで同時に起動しhttpsを使う

なんでdevelopment環境でhttpsを使う必要があるの?

development環境でrails sを実行してRailsアプリ(Server)を起動するとデフォルトではhttpで起動することになります。
ところが、どこかのサービスが提供しているAPIを叩く際、エンドポイントURLをhttpsで指定しなければならない場合がほとんどなのでdevelopmentでAPIを叩いて連携し、目的の情報を正しく取得、加工、表示、できているのかを確認できません。

なんでwebpack-dev-serverを使うの?

Rails6ではデフォルトでJavaScriptの管理がWebpacker環境となりますのでrails sだけだと、ページが表示されるまでに時間がかかり不便なのでwebpack-dev-serverを利用することでファイルが更新されたら勝手にコンパイルしてくれるようにし、開発効率を上げるためです。

なんでforemanを使うの?

Railsアプリを起動する度にrails swebpack-dev-serverのコマンドを打つ必要があり、これまた面倒です。
そこで登場するのがforemanです。

要するに

  • ムダなことに時間をとられたくない
  • 人生(命)を削られたくない
  • コンピュータを自由自在に操るのが生き甲斐なのに、Rails6のデフォルトだとコンピュータに操られてるみたいで屈辱的なので絶対に許せない

ってことです

SSL証明書どうするの問題の解決

Railsの開発環境でhttpsを使う
https://qiita.com/itkrt2y/items/2837a45061b9a3b711b7
という記事を参考にさせて頂きました。@itkrt2y さん、ありがとうございます!

ただし、Rails6では1箇所だけエラーを引き起こす箇所がありました。
エラー内容
NameError: uninitialized constant #<Class:#<Puma::DSL:0x00007f9070276ea8>>::Rails

該当箇所(1行目のところが原因です)

config/puma.rb
if Rails.env.development?

なので下記のように修正することでエラーを回避しました。

config/puma.rb
if ENV['RAILS_ENV']=='development'

これでrails sを実行します。
すると、config/certs/配下にlocalhost.keylocalhost.certが生成されます。

webpack-dev-serverのhttpsを有効化

developmentのdev_serverのhttpsをtrueに変更。下記コードの一番最後の行のところです。

config/webpacker.yml
evelopment:
  <<: *default
  compile: true

  # Verifies that correct packages and versions are installed by inspecting package.json, yarn.lock, and node_modules
  check_yarn_integrity: true

  # Reference: https://webpack.js.org/configuration/dev-server/
  dev_server:
    https: true

foremanを使ってラクになる

foreman で アプリケーションを動かす。
https://qiita.com/7kaji/items/6a59977d2ad85604e7fd
という記事を参考にさせて頂きました。 @7kajis さん、ありがとうございます!

インストール

$ gem install foreman

設定

下記の設定ではportを443にしていますが、必要に応じて8443などに変更してください。

Procfile
rails: rails s -b 'ssl://0.0.0.0:443?key=config/certs/localhost.key&cert=config/certs/localhost.cert'
webpack: bin/webpack-dev-server --https

起動!

下記のコマンドを実行し、Railsアプリを起動します。 https://localhost/ にアクセスし、ページが表示されれば成功です。

foreman start

はぁ〜ラクチン。幸せだなぁ。

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