20200401のRailsに関する記事は17件です。

Swiper.jsが動作しなかった時の対処法[備忘録]

はじめに

バージョン確認の重要性

Swiper.jsはバージョンによってサポートしてくれるブラウザが違う。ver5.0以降から、IEは完全にサポート対象外になった。今回私はchromeで開発を行っているので最新版でいけると思いきや、挙動が意図したものにならなかったので、4.5.0系を使うことにした。

便利なオプションたち

オプションが豊富で有名なSwiper.js

ここでは、私が参考にした記事を載せておくだけに留めておく。

参考記事

実例12パターン】画像スライダーはSwiper使っておけば間違いない!実用的な使い方を紹介
https://haniwaman.com/swiper/
swiper.js使ってみたからそのオプションについて(v4.1.6)
https://reiwinn-web.net/2018/03/15/swiper-4-1-6/

実際のコード

swiper.js
$(function(){
  var mySwiper = new Swiper('.swiper-container', {
    slidesPerView: 3,
    slideToClickedSlide: true,
    pagination: {
      el: '.swiper-pagination',
      type: 'bullets',
      clickable: true
    },
    navigation: {
      nextEl: '.swiper-button-next',
      prevEl: '.swiper-button-prev'
    },
    breakpoints: {
      767: {
        slidesPerView: 1,
        spaceBetween: 0
      }
    }
  });
});


html.haml
        .swiper-container.p-2.rounded
          .swiper-wrapper.p-1
            - @posts.each do |post| 
              .events__content.col-sm-6.col-md-4.mb-3
                .swiper-slide
                  .card{id: post.id}
                    %label.m-1
                      - if post.image.present?
                        %img.card-img-top.img-fluid.rounded{src: "#{post.image}"}
                      - else
                        %img.card-img-top.img-fluid.rounded{src: "/assets/noimage.png"}
                      .card-body.event
                        %h5= link_to "#{post.title}", post_path(post.id), class: "event-title stretched-link text-decoration-none"
                        .event__name 
                          #{post.user.name} さん
                        .text-right
                          = l post.created_at, format: :long     
           .swiper-button-prev
           .swiper-button-next
           .swiper-pagination

これでprevボタンnextボタンを押しても、スライドされない。consoleをみてもエラーはない。ドラッグしながらスクロールすると、一応.swiper-containerに要素が入っていることはわかる。jsファイルは読み込まれているが、メソッドが実行されていない。

階層構造は崩すな

原因がわかった。先ほどのコードは、.swiper-wrapperと.swiper-slidesの間に、.events__content......というクラスが入っている。これが原因だった。Swiper.jsのswiper-container, wrapper, slidesのクラスたちは必ず親子孫の関係でなくてはならなかった。

修正後のコード

html.haml
.swiper-container.p-2.rounded
          .swiper-wrapper.p-1
            - @posts.each do |post| 
              .swiper-slide.events__content.col-sm-6.col-md-4.mb-3
                .card{id: post.id}
                  %label.m-1
                    - if post.image.present?
                      %img.card-img-top.img-fluid.rounded{src: "#{post.image}"}
                    - else
                      %img.card-img-top.img-fluid.rounded{src: "/assets/noimage.png"}
                    .card-body.event
                      %h5= link_to "#{post.title}", post_path(post.id), class: "event-title stretched-link text-decoration-none"
                      .event__name 
                        #{post.user.name} さん
                      .text-right
                        = l post.created_at, format: :long
                .card.col-auto
                  .text-left
                    - post.genre_list.each do |genre|
                      .badge.badge-primary{data:{role: "tagsinput"}}
                        = link_to "#{genre}", tag_path(genre), class: 'text-decoration-none text-white'
                - if user_signed_in?
                  .offset-8.col-auto.card
                    .text-center.likes
                      = render partial: '/posts/posts', locals: {post: post}
          - if @posts.count >= 4
            .swiper-button-prev
            .swiper-button-next
            .swiper-pagination

これで正常に動くようになった。

終わりに

私のように、こんなことで何時間も無駄にしないように、皆さんも気をつけてください。

参考記事

【Rails5】「Swiper」を使ってスライダー、カルーセルを作る方法
https://qiita.com/emincoring/items/18d07d0aec5d9836227c
[Rails]Swiperで画像スライド作成
https://qiita.com/yummy888/items/8528c7542f85ae7bbc55

サンプル付き!簡単にスライドを作れるライブラリSwiper.js超解説(各種ナビゲーションカスタマイズ編)
https://garigaricode.com/swiper_navigation/

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

railsのアセットパイプラインと本番環境について(未完成)

アセットパイプライン

基本

開発者が作成したJsやCSSを最終的に統合して、HTMLソースに落とし込めるようにする仕組み。本番環境でのみ行う。

流れ
高級言語のコンパイル(cofeeやSCSS)、開発環境ではここまでしか行わない。本番環境ではここから最後までの処理を行う。

アセットの結合(複数のjsファイルをapplication.jsに統合、cssも同様)

アセット最小化(コメントや改行を無くして圧縮する)

ダイジェストの付与(これをするとファイルにハッシュ値(暗号化された値?)をつけてpublic/assets下に反映される、イメージがつかなければ参考文献をみる)

HTMLでapplication.jsとapplication.cssが読み込まれる

マニフェストファイル(application.jsとapplication.cssのこと)

ここに記述することはどのJsやCSSを自身に統合するか
を記述したり、別ファイルに書かれた設定を適用させる。

探索パスを設定することでファイルの統合が簡単になる.
app/asset/とlib/assets/とvender/assets/*に探索パスが設定されている。それらの場所に探索パスが設定されるので、マニフェストファイルはその場所に統合するべき指定のファイルがないかを探す。

application.js
//= require slider

この意味はapp/asset/とlib/assets/とvender/assets/*にslider.jsがあるか探し、統合するということ

探索パスを配置する場所の設定を追加したい時はconfig/initializers/assetes.rbで記述してやる。

開発環境と本番環境

上記で示したように開発環境では高級言語のコンパイル(cofeeやSCSS)しか行わないため、jsやscssのファイルが別々に存在している。

なぜならば開発環境では開発しやすいコードやファイルの配置を行うべきだから、ファイルを連結して、改行やコメントを無くしたりするべきではない。

しかし本番環境では人間が理解しやすいコードや配置をする必要がなく、むしろスピードが求められるため、ファイルを連結、圧縮している。

参考文献は【Rails】デプロイに強くなるアセットパイプラインまとめ(アセットプリコンパイルで何が起きているか)

config/environments

ここには開発、テスト、本番のアセットパイプラインの設定をする。
設定とは開発環境ではアセットの連結や圧縮を行わないとか。

アセットプリコンパイル

本番環境で行われるファイルの連結、圧縮のこと。何度も書きますが本番環境でのみ行われる。

本番環境でのデプロイ

デプロイ時やらないといけないこと
*アセットパイプラインをどうするか
*データベースの設定

アセットパイプライン設定

アセットパイプライン設定のconfig/environmentsを行い、探索パスを増やしたければ
config/initializers/assetes.rbに記述。

アセットプリコンパイル(ファイル連結、圧縮)を実施するために以下のコマンドを実施。これで統合されたマニフェストファイルがハッシュ値を持った状態でpublic/assetsに保存されるようになる。

rails assets:precompile
データベースの設定

config/database.ymlのなかで各環境でのdb設定を記述できる。主にdatabase名、user名、そのパスワードを記述する。
user名とは本番環境でのSQLに関するユーザーの名前のことであるため、まず本番環境のSQLでログインとデータベース作成が可能なユーザーを作成し、パスワードも設定する。(database.ymlにはパスワードは環境変数として記述して、~/.bash_profileで環境変数定義をしてやる)

秘密情報の管理

本番環境にアプリを置くということは外部にアプリを置くということである。しかし外部には後悔したくない秘密情報をアプリに記述したい場合がある。その時はconfig/credential.yml.encに記述してやる。config/credential.yml.encは暗号化されている。この内容をアプリ内で使用する際はcinfig/master.keyによって復号され使用される。
また開発者が記述した秘密情報を新たにcredential.yml.encに追加したい場合も、master.keyによって暗号化されcredential.yml.encに反映される。

参考文献は[Rails]credentials.yml.encについてまとめる
Rails5.2から追加された credentials.yml.enc のキホン

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

railsのアセットパイプラインと本番環境のデプロイについて

アセットパイプライン

基本

開発者が作成したJsやCSSを最終的に統合して、HTMLソースに落とし込めるようにする仕組み。本番環境でのみ行う。

流れ
高級言語のコンパイル(cofeeやSCSS)、開発環境ではここまでしか行わない。本番環境ではここから最後までの処理を行う。

アセットの結合(複数のjsファイルをapplication.jsに統合、cssも同様)

アセット最小化(コメントや改行を無くして圧縮する)

ダイジェストの付与(これをするとファイルにハッシュ値(暗号化された値?)をつけてpublic/assets下に反映される、イメージがつかなければ参考文献をみる)

HTMLでapplication.jsとapplication.cssが読み込まれる

マニフェストファイル(application.jsとapplication.cssのこと)

ここに記述することはどのJsやCSSを自身に統合するか
を記述したり、別ファイルに書かれた設定を適用させる。

探索パスを設定することでファイルの統合が簡単になる.
app/asset/とlib/assets/とvender/assets/*に探索パスが設定されている。それらの場所に探索パスが設定されるので、マニフェストファイルはその場所に統合するべき指定のファイルがないかを探す。

application.js
//= require slider

この意味はapp/asset/とlib/assets/とvender/assets/*にslider.jsがあるか探し、統合するということ

探索パスを配置する場所の設定を追加したい時はconfig/initializers/assetes.rbで記述してやる。

開発環境と本番環境

上記で示したように開発環境では高級言語のコンパイル(cofeeやSCSS)しか行わないため、jsやscssのファイルが別々に存在している。

なぜならば開発環境では開発しやすいコードやファイルの配置を行うべきだから、ファイルを連結して、改行やコメントを無くしたりするべきではない。

しかし本番環境では人間が理解しやすいコードや配置をする必要がなく、むしろスピードが求められるため、ファイルを連結、圧縮している。

参考文献は【Rails】デプロイに強くなるアセットパイプラインまとめ(アセットプリコンパイルで何が起きているか)

config/environments

ここには開発、テスト、本番のアセットパイプラインの設定をする。
設定とは開発環境ではアセットの連結や圧縮を行わないとか。

アセットプリコンパイル

本番環境で行われるファイルの連結、圧縮のこと。何度も書きますが本番環境でのみ行われる。

本番環境でのデプロイ

デプロイ時やらないといけないこと
*アセットパイプラインをどうするか
*データベースの設定
*secret_key_baseの設定

アセットパイプライン設定

アセットパイプライン設定のconfig/environmentsを行い、探索パスを増やしたければ
config/initializers/assetes.rbに記述。

アセットプリコンパイル(ファイル連結、圧縮)を実施するために以下のコマンドを実施。これで統合されたマニフェストファイルがハッシュ値を持った状態でpublic/assetsに保存されるようになる。

rails assets:precompile
データベースの設定

config/database.ymlのなかで各環境でのdb設定を記述できる。主にdatabase名、user名、そのパスワードを記述する。
user名とは本番環境でのSQLに関するユーザーの名前のことであるため、まず本番環境のSQLでログインとデータベース作成が可能なユーザーを作成し、パスワードも設定する。(database.ymlにはパスワードは環境変数として記述して、~/.bash_profileで環境変数定義をしてやる)

secret_key_baseの設定

これは本番環境で作成される秘密鍵でcookies整合性確認に使用される。保存方法は環境変数としてSECRET_KEY_BASEに保存する方法とcredentials.yml.encに保存する方法がある。

補足:秘密情報の管理

本番環境にアプリを置くということは外部にアプリを置くということである。しかし外部には後悔したくない秘密情報をアプリに記述したい場合がある。その時はconfig/credential.yml.encに記述してやる。config/credential.yml.encは暗号化されている。この内容をアプリ内で使用する際はcinfig/master.keyによって復号され使用される。
また開発者が記述した秘密情報を新たにcredential.yml.encに追加したい場合も、master.keyによって暗号化されcredential.yml.encに反映される。

参考文献は[Rails]credentials.yml.encについてまとめる
Rails5.2から追加された credentials.yml.enc のキホン

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

ログイン機能つけてくよ。〜Railsチュートリアル8章〜

少しづつ難しくなってきました。今回はログイン機能についてまとめてみます。
足早に来てるけどこうして一個一個確実にアウトプットしてくよ。

この章ではログイン・ログアウト機能を実装していきます。
ログインの仕組みはブラウザがログインを保持し、ユーザーによってブラウザが閉じられたら状態を破棄すること。
ログインしたユーザーだけがアクセスできるページや扱える機能を制御する。こうした制限や制御の仕組みを認可モデルという。

セッション

HTTPは状態(state)がない(less)。つまりステートレスなプロトコルである。
HTTPによるリクエストの一つ一つはそれより前の情報を全く利用できません。
リクエストが終わると何もかも忘れてしまう健忘症的なプロトコルである。
つまりHTTP自体にはブラウザの別のページへ移動したときにユーザーのIDを保持しておく手段がない。
ユーザーログインが必要なApplicationではセッションと呼ばれる半永続的な接続をコンピュータ間に別途設定を行う。

cookies

Railsでセッションを実装する方法として最も一般的なのはcookiesを使う方法である。cookiesとはユーザーのブラウザに保存される小さなテキストデータである。cookiesはあるページから別のページに移動したときも破棄されないのでここにIDなどの情報を保存できる。Applicationはcookies内のデータをデータベースから取り出すことができる。

今回はsessionというRailsのメソッドを使って一時セッションを作成する。この一時セッションはブラウザを閉じると自動的に終了する。

セッションをRESTfulなResourcesとしてモデリングできると。他のRESTfulリソースと統一的に理解できる。

UserリソースとSessionリソースの違い

Userリソース...バックエンドでUserモデルを介してデータベース上の永続的データにアクセスする

Sessionリソース...cookiesを保存場所として使う。

Sessionsコントローラ

Sessionsコントローラの特定のRESTアクションにそれぞれ対応付けしていく。フォームはnewアクションで処理。
sessionsリソースの追加

  get   '/login',   to: 'sessions#new'
  post   '/login',   to: 'sessions#create'
  delete '/logout',  to: 'sessions#destroy

ログインフォーム

ログインフォームを整えていく。入力時に誤りがあった場合ログインページをもう一度表示しエラーメッセージを出力する。サインアップページではエラーメッセージに専用のパーシャルを使用したが今回はActive Recordからの自動生成ではないためフラッシュメッセージを使用する。

Sessionフォームにはモデルが存在しないため@userのようなインスタンス変数に相当するものもない。したがって新しいSessionフォームを作成する時はform_forヘルパーに追加の情報を独自に渡さなければならない。

 <%= 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 %>

ユーザーの検索と認証

ログイン機能を実装する際にはまず入力が無効な場合の処理を最初に行う。
SessionでのアクションはSessionコントローラへ定義する。
セッションの中にはemailとpasswordの情報が含まれている。

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

params[:session][:email]で、セッションの値を取り出し、メールアドレスには小文字しか入力できないため.downcaseで小文字に変換しています。
その後find_byメソッドで入力したemailをカラムに保存されたemailが一致した場合にユーザーに代入している。

if user && user.authenticate(params[:session][:password])

&&を使って、条件を2つ指定している。
・user: userの中身がデータベースの内容と一致しているかどうか。
・user.authenticate: userに代入されたレコードのパスワードがポストした値と一致しているか
パスワードが登録しているユーザー情報と一致していたらセッションにユーザーIDを登録している。

フラッシュメッセージの表示

ログインが失敗した場合にエラーメッセージを実装していく。まずは統合テストを構築する。

$ rails generate integration_test users_login

テストコードの流れ
1.ログイン用のパスを開く
2.新しいセッションのフォームが正しく表示されたことを確認する
3.わざと無効なparamsハッシュを使ってセッション用パスにPOSTする
4.新しいセッションのフォームが再度表示され、フラッシュメッセージが追加されることを確認する
5.別のページ (Homeページなど) にいったん移動する
6.移動先のページでフラッシュメッセージが表示されていないことを確認する

実際のテスト

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わざと無効なparamsハッシュを使ってセッション用パスにPOSTする
    assert_template 'sessions/new'
    assert_not flash.empty?   #4新しいセッションのフォームが再度表示され、フラッシュメッセージが追加されることを確認する
    get root_path    #5のページ (Homeページなど) にいったん移動する
    assert flash.empty?    #6フラッシュメッセージが表示されていないことを確認する
  end
end

ログイン失敗時の正しい処理

class SessionsController < ApplicationController

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

失敗するテストをパスさせるには、flashをflash.nowに置き換える。後者は、レンダリングが終わっているページで特別にフラッシュメッセージを表示することができます。flashのメッセージとは異なり、flash.nowのメッセージはその後リクエストが発生したときに消滅します

ログイン

実際にログイン中の状態での有効な値の送信をフォームで正しく扱える用にする。cookiesを使った一時セッションでユーザーをログインできるようにする。
セッションを実装するには通常様々なコントローラやビューでおびただしい数のメソッドを定義する必要がある。Rubyのモジュール機能を使うとそうしたメソッドを一箇所にパッケージ化できる。Sessionsコントローラを生成した時点でSessionヘルパーモジュールも自動生成されている。Applicationコントローラにこのモジュールを読み込ませればどのコントローラでも使える用になる。

class ApplicationController < ActionController::Base
  protect_from_forgery with: :exception
  include SessionsHelper
end

include...モジュールを呼ぶためのメソッド

log_inメソッド

railsで事前定義済みのSessionメソッドを使って単純なログインを行える用にする。

session[:user_id] = user.id

このコードを実行するとユーザーのブラウザ内の一時cookiesに暗号化済みのユーザーIDが自動で作成される。Sessionメソッドで作成した一時cookiesは一時的に暗号化されこのコードは保護される。攻撃者がたとえこの情報を盗み出すことができてもそれを使って本物のユーザーとしてログインすることはできない。

このセッションでlog_inというヘルパーメソッドを定義できたのでユーザーログインを行ってSessionのcreateアクションを完了し、ユーザーのプロフィールページにリダイレクトする準備ができる。

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

現在のユーザー

ユーザーIDを一時セッションの中に安全におけるようになったので今度はそのユーザーを別のページで取り出すことにする。そのためにはcurrent_userメソッドを定義して、SessionIDに対応するユーザー名をデータベースから取り出せるようにする。
なぜcurrent_userを定義するのか。

find_byメソッドを使うということは、使う度にデータベースアクセスが発生する。
データベースに保存されているデータが多いほど検索時間かかるし、無駄にアクセス増やしたくない。
そのためUser.find_byの実行結果をインスタンス変数に保存しておく方法がRailsの慣習である。
よって@current_userへの代入を行う。

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
end

レイアウトリンクを変更する。
レイアウトがユーザーがログインしているときとそうでないときとで変更できるようにする。まず統合テストを書いていく。

  # ユーザーがログインしていればtrue、その他ならfalseを返す
  def logged_in?
    !current_user.nil?
  end
end
 <% 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 %>

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

1.ログイン用のパスを開く
2.セッション用パスに有効な情報をpostする
3.ログイン用リンクが表示されなくなったことを確認する
4.ログアウト用リンクが表示されていることを確認する
5.プロフィール用リンクが表示されていることを確認する

上記のテストを行う際にはテストユーザーの作成が必要。
Railsではこのようなテスト用データをfixtureで作成できる。testデータベースでtestに必要なデータを読み込んでおくことができる。

  # 渡された文字列のハッシュ値を返す
  def User.digest(string)
    cost = ActiveModel::SecurePassword.min_cost ? BCrypt::Engine::MIN_COST :
                                                  BCrypt::Engine.cost
    BCrypt::Password.create(string, cost: cost)
  end

ユーザー登録中のログイン

 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

ログアウト

アプリケーションで扱う認証モデルでは、ユーザーが明示的にログアウトするまではログイン状態を保てなくてはならない。
SessionsコントローラのアクションはRESTfulルールに従っている。newでログインページを表示し、createでログインを完了するといった具合です。セッションを破棄するdestroyアクションも、引き続き同じ要領で作成できる。
ログアウトメソッドの定義

module SessionsHelper

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

Sessionを破棄する

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

log_outメソッドは、Sessionsコントローラのdestroyアクションでも同様に使っていく。

ユーザーログアウトのtest

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

テストでis_logged_in?ヘルパーメソッドを利用できるようにしてあったおかげで、有効な情報をセッション用パスにpostした直後にassert is_logged_in?で簡単にtestできる。

今日はここまで

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

Rails ウィザード形式導入について 2

はじめに

Rails ウィザード形式導入について 1 はこちらをクリック願います。
チーム開発でフリマサイトを開発致しました。
その際、ユーザーの新規登録画面でウィザード形式を導入致しましたので、内容を整理します。
もうすでにご存知の方、省略の仕方等ご存知でしたら、ご教授願います。

前提

  • ユーザー情報(User)については 以下 A と記述します。
  • 住所情報(Destination)については 以下 B と記述します。

Aの新規登録のnewアクションとビュー

  • app/controllers/users/registrations_controller.rbを見てみてください。
  • えらいことになっているかと思います・・・
  • なんだこれ・・・
app/controllers/users/registrations_controller.rb
# frozen_string_literal: true

class Users::RegistrationsController < Devise::RegistrationsController
  # before_action :configure_sign_up_params, only: [:create]
  # before_action :configure_account_update_params, only: [:update]

  # GET /resource/sign_up
  # def new
  #   super
  # end

  # POST /resource
  # def create
  #   super
  # end

  # GET /resource/edit
  # def edit
  #   super
  # end

  # PUT /resource
  # def update
  #   super
  # end

  # DELETE /resource
  # def destroy
  #   super
  # end

  # GET /resource/cancel
  # Forces the session data which is usually expired after sign
  # in to be expired now. This is useful if the user wants to
  # cancel oauth signing in/up in the middle of the process,
  # removing all OAuth session data.
  # def cancel
  #   super
  # end

  # protected

  # If you have extra params to permit, append them to the sanitizer.
  # def configure_sign_up_params
  #   devise_parameter_sanitizer.permit(:sign_up, keys: [:attribute])
  # end

  # If you have extra params to permit, append them to the sanitizer.
  # def configure_account_update_params
  #   devise_parameter_sanitizer.permit(:account_update, keys: [:attribute])
  # end

  # The path used after sign up.
  # def after_sign_up_path_for(resource)
  #   super(resource)
  # end

  # The path used after sign up for inactive accounts.
  # def after_inactive_sign_up_path_for(resource)
  #   super(resource)
  # end
end
  • コメントアウトしている箇所はすでにDevise::RegistrationsControllerで定義されているものです。
  • コメントアウト部分を外して上書きすることができます。(メソッドのオーバーライド)
  • superはスーパークラス(今回であればDevise)のメソッドを呼び出しています。

とりあえずコメントアウト部分(superクラスの呼び出し部分)を全部消してしまいます・・・

  • deviseとUserモデルが紐づくように設定してあるので、superで呼び出してもdeviseは同様の操作を行ってくれるようです。
  • 勉強中ですので、super呼び出し方法についてはお待ちください(泣)

newアクションを定義する

app/controllers/users/registrations_controller.rb
class Users::RegistrationsController < Devise::RegistrationsController

  def new
    @user = User.new
  end

end

そして、対応しているビューも編集する

  • 参考程度にみてください。このまま入力するとエラーが出るかも・・・です。
app/views/devise/registrations/new.html.haml
  .main
    .title
      .title__font
        A情報入力

    = form_for(@user, url: user_registration_path) do |f|
      .name-information
        .name-information__item
          .name-information__item__nicname
            = f.label :name,"ニックネーム"
        .name-information__name
          = f.text_field :name,size:26
      .email
        .email__information
          .email__information__address
            = f.label :email,"メールアドレス"
        .email__information
          = f.email_field :email, autofocus: true, autocomplete: "email",size:26

      .password
        .password__item
          .password__item__pass
            = f.label :password,"パスワード"
          .password__item__note
            - if @minimum_password_length
              (#{@minimum_password_length} 文字以上必要です)
        .password__input
        .password__description
          = f.password_field :password, autocomplete: "new-password", size:26

      .re-enter
        .re-enter__item
          .re-enter__item__pass
            = f.label :password_confirmation,"確認用パスワード "
        .re-enter__itempass
          = f.password_field :password_confirmation, autocomplete: "new-password",size:26
      .terms-of-service
        .terms-of-service__btm
          %input#submit_button1{:name => "submit", :type => "submit", :value => "次へ進む"}/


Aの新規登録のcreateアクションとビュー

  • 1ページ目で入力した情報のバリデーションチェックをします。
  • 1ページで入力した情報をsessionに保持させます。
  • 次のBの登録で使用するインスタンスを生成、当該ページへ遷移するようにします。

createアクションを定義する(追記してください)

app/controllers/users/registrations_controller.rb
class Users::RegistrationsController < Devise::RegistrationsController
  before_action :configure_sign_up_params, only: [:create]

# 省略

  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]
    @destination = @user.build_destination
    render :new_destination
  end



  protected

  def configure_sign_up_params
    devise_parameter_sanitizer.permit(:sign_up, keys: [:attribute])
  end

end
1ページ目で入力した情報のバリデーションチェック
  • Userモデルのインスタンスを生成する。
  • 1ページ目から送られてきたパラメータをインスタンス変数@userに代入する。
  • そのインスタンス変数に対してvalid?メソッドを適用する。
  • 送られてきたパラメータが指定されたバリデーションに違反しないかどうかチェックすることができる。
  • falseになった場合は、エラーメッセージとともにnewアクションへrenderする。
1ページで入力した情報をsessionに保持させる
  • A情報だけではなく、B情報までページ遷移した後に保存するという機能を達成するのが目的です。
  • そのために、sessionという機能を用いる。
  • sessionとは、ページが遷移しても情報が消えることが無いように、クライアント側で保持をさせておく機能のことをいうようです。
  • A情報のバリデーションチェック後、session["devise.regist_data"]に値を代入する。
  • この時、sessionにハッシュオブジェクトの形で情報を保持させるために、attributesメソッドを用いてデータを生成する。
  • また、paramsの中にはパスワードの情報は含まれているが、attributesメソッドでデータ整形をした際にパスワードの情報は含まれない。
  • そこで、パスワードを再度sessionに代入する。
B情報登録で使用するインスタンスを生成、B情報登録ページへ遷移する
  • 次ページで、ユーザーモデルに紐づくB情報を入力させるため、該当するインスタンスを生成しておく必要がある。
  • そのために、build_destinationで今回生成したインスタンス@userに紐づくDestinationモデルのインスタンスを生成する。
  • ここで生成したDestinationモデルのインスタンスは、@destinationというインスタンス変数に代入する。
  • そして、B情報を登録させるページを表示するnew_destinationアクションのビューへrenderする。

難しい!でも自作アプリには入れたいです!もうひと頑張り!

続きは次回!

さいごに

日々勉強中ですので、随時更新します。
皆様の復習にご活用頂けますと幸いです。

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

strftimeとは?

はじめに

新しい会社でインターンをはじめ、最初の方に回ってきた保守開発で、複数の日時を比べる機会がありました。
そこで、いろいろ調べてこのstrftimeを学んだので、アウトプットをかねて記事を書いていきます!(自分の初めてのqiita記事です)

strftimeとは?

rubyのstrftimeリファレンス
時刻を format 文字列に従って文字列に変換した結果を返す
と言っています。

そもそも日時ってどうやって取り出すの?

めっちゃいい記事を見つけたのでこちらを参照してください笑
RubyとRailsにおけるTime, Date, DateTime, TimeWithZoneの違い
Time.nowで現在の日時を取得できます。
irb(main):001:0> Time.now
=> 2020-04-01 18:42:47 +0900

今回は、Time.nowからstrftimeを用いて値を呼び出したい形に変える方法についてまとめます。

strftimeを使ってみよう!

まずは、rubyのstrftimeリファレンスにある表を眺めて概要を理解しましょう!
スクリーンショット 2020-04-01 18.54.09.png
この表からいくつか抜粋して、実際の開発や業務で役に立ちそうなものを、コードを用いて紹介していきます!

できること1 曜日の取得 %A,%a

irb(main):008:0> t = Time.now
=> 2020-04-01 18:59:14 +0900
irb(main):009:0> t.strftime("%A")
=> "Wednesday"
irb(main):010:0> t.strftime("%a")
=> "Wed"

これ以下はtに現在時刻が代入されているものとする

できること2 月の取得 %B,%b

irb(main):011:0> t.strftime("%B")
=> "April"
irb(main):012:0> t.strftime("%b")
=> "Apr"

できること3 日時と日付 %c

irb(main):013:0> t.strftime("%c")
=> "Wed Apr 1 18:59:14 2020"

できること4 日付の取得 %y/%m/%d

irb(main):016:0> t.strftime("%y/%m/%d")
=> "20/04/01"

irb(main):019:0> t.strftime("%Y/%m/%d")
=> "2020/04/01"

irb(main):018:0> t.strftime("%Y%m%d")
=> "20200401"

できること5 日付、それぞれの取得

irb(main):021:0> t.strftime("%Y")
=> "2020"

irb(main):022:0> t.strftime("%m")
=> "04"

irb(main):017:0> t.strftime("%d")
=> "01"

最後に

日時、時間、曜日の情報の取得に関して整理できたでしょうか?
また、万が一間違った情報がありましたら教えていただけると嬉しいです!
ご一読いただきありがとうございました!

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

AWSのRDSが日本語対応にならない場合の対処法

はじめに

 Ruby on Rails初心者です。今回はアプリケーションのデプロイ時に苦戦した箇所があったので
 勉強のために備忘録として残したいと思います。

前提

  Rails 5.2.4

問題

 ・AWSのRDSでMySQLインスタンスを作成
 (RDSのMySQLの文字コードは、初期設定は「latin」)
 ・その後「パラメータグループ」で日本語対応にするも本番環境で
  日本語対応されておらず、、

解決方法

 ・EC2にSSHで接続

ssh -i /Users/ユーザー名/.ssh/キーペア名.pem ec2-user@xx.xx.xx.xx

 ・EC2からRDSにアクセス(パスワード要求されます)

mysql -h エンドポイント -P Port -u ユーザ名 -p データベース名

 ・データベースの状態を確認

mysql> show variables like 'char%';

 ・以下のように表示される

+--------------------------+-------------------------------------------+
| Variable_name            | Value                                     |
+--------------------------+-------------------------------------------+
| character_set_client     | utf8mb4                                   |
| character_set_connection | utf8mb4                                   |
| character_set_database   | latin1                                   |
| character_set_filesystem | utf8mb4                                   |
| character_set_results    | utf8mb4                                   |
| character_set_server     | utf8mb4                                   |
| character_set_system     | utf8                                      |
| character_sets_dir       | /rdsdbbin/mysql-5.7.22.R5/share/charsets/ |
+--------------------------+-------------------------------------------+

 「character_set_database」が「 latin1 」 になっていたので、これを「utf8mb4」に直す
 
 ・データベースの文字コード修正

mysql> ALTER DATABASE データベース名 default character set utf8mb4;

 ・再度データベースの状態を確認

+--------------------------+-------------------------------------------+
| Variable_name            | Value                                     |
+--------------------------+-------------------------------------------+
| character_set_client     | utf8mb4                                   |
| character_set_connection | utf8mb4                                   |
| character_set_database   | utf8mb4                                |
| character_set_filesystem | utf8mb4                                   |
| character_set_results    | utf8mb4                                   |
| character_set_server     | utf8mb4                                   |
| character_set_system     | utf8                                      |
| character_sets_dir       | /rdsdbbin/mysql-5.7.22.R5/share/charsets/ |
+--------------------------+-------------------------------------------+

  これで日本語化に対応できた、、、と思ったができておらず(^^;)

 原因はテーブルをすでに作成していたため、テーブルの文字コードも変更しなければ
 ならなかった。

・テーブルの文字コード変更

mysql> ALTER TABLE テーブル名 CONVERT TO CHARACTER SET utf8md4

 自分で作成した全てのテーブルを変更し、問題解決した

参考にさせていただいた記事

https://qiita.com/kijitora-neko/items/aab58b4c1f684353e075

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

データの入ったテーブルに対して外部キーが設定されたカラムを追加する

やりたいこと

foosテーブルとbarsテーブルがあったとして
barsテーブルにfoo:referencesのカラムを追加したい。
ただし、システムは運用を始めておりbarsテーブルにはデータが入っている。

普通に

db/migrate/YYYYMMDDhhmmss_add_foo_to_bars.rb
class AddFooToBars < ActiveRecord::Migration[6.0]
  def change
    add_reference :bars, :foo, null: false, foreign_key: true
  end
end

db:migrateしようとすると、空のfoo_idカラムが発生してしまうため、エラーになってしまう。

解決策

まずは初期値を設定してカラムを追加する。
default値はfoosテーブルに存在するidを指定する。

db/migrate/YYYYMMDDhhmmss_add_foo_to_bars.rb
class AddFooToBars < ActiveRecord::Migration[6.0]
  def change
    add_reference :bars, :foo, null: false, foreign_key: true, default: 1
  end
end

次にDEFAULTの定義を残しておくと気持ち悪いので消す。

db/migrate/YYYYMMDDhhmmss_change_foo_to_bars.rb
class ChangeFooToBars < ActiveRecord::Migration[6.0]
  def change
    change_column_default(:bars, :foo_id, nil)
  end
end

これでデータの入ったテーブルに外部キーが設定されたカラムを追加できます。

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

Strong Parametersでネストする場合の注意点

params = {
  title: "タイトル",
  content: {
    header: "見出し",
    body: "内容"
  },
  author: "太郎"
}

みたいなパラメータをpermitしてあげるときこの順番通りに実装しようとすると

params.permit(:title, content: [:header, :body], :author)

となるが、これだとsyntax errorになる
理由はネストする場合はそのネストするパラメータを最後に持ってこないといけないため。
正解はこれ

params.permit(:title, :author, content: [:header, :body])
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ActiveModelSerializer + #Rails の Controller render で json /. json_api の adapter を無効化してデフォルト設定を使う ( disable json adapter and reset to default )

  • デフォルトのアダプタはattributes のようだ。これを指定すれば良い。
  • config でデフォルトが指定されており、一箇所の controller でだけ adapter を変えて戻したい場合などに
render json: some_instance, adapter: :attributes

Original by Github issue

https://github.com/YumaInaura/YumaInaura/issues/3058

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

Railsでlink_to deleteメソッドが動かない

rails5.2を使ってアプリを実装中にハマったエラーです。
なかなか解決策が見つからず困ったのでメモとして置いておきます。

状況

rootテーブルに投稿されたレコードを削除するため、実装したが削除されない。
リンクは表示されてクリックできるけどアクションが発火しない。加えてエラーも出ない。
ログを見るとGETで送られている。

<%= link_to '削除', "#{@root.id}", method: :delete %>

基本的な確認はしてみた。
route.rb、controllerの記述はちゃんとできてる。
rails routesを確認して、HTTPメソッドは来ている。htmlもdeleteになっている。

route.rb
  resources :roots

roots_controller.rb
  def destroy
    root= Root.find(params[:id])
    root.destroy
    redirect_to root_path
  end

試したこと

application.js
//= require rails-ujs
があるか確認。→ 記述はあったのでパス。

Turbolinksのバージョンを見る
→ Githubの投稿を読んで参考にしてみた。
https://gist.github.com/JunichiIto/791ee8b6c9eb4ac639d2141465f89fc4

・・・どうもうまくいかない。

解決方法

あれこれ修正を繰り返していたら「ActionController::InvalidAuthenticityToken」というエラーに当たる。
これがきっかけ。

■原因
RailsのCSRF対策が堅固なので外部からのアクセスをはじく設定がデフォルトになっていたため。
外部サイトからのAPIリクエストと認識されたようで、受け付けないらしい。
 ※RailsのCSRF対策についてはここでは割愛します。

■実装1
destroyアクションを実施したいコントローラーに追記する。
protect_from_forgery の記述を使います。(詳細は調べてください)

今回はRootsコントローラーからdestoryアクションを除外

1 class RootsController < ApplicationController
2  protect_from_forgery :except => [:destroy]
3
4  def index
///////以下省略////////

■実装2
link_toをbutton_toに修正。(なぜかlink_toだと動かなかった)

<%= link_to '削除', "#{@root.id}", method: :delete %>
 ↓
<%= button_to '削除', "#{@root.id}", method: :delete %>

■結果
無事削除とredirect_toが動きました。

最後に

今回はこの実装で投稿削除ができました。RailsのCSRF対策で見えにくいデフォルトの挙動などもあるので公式ガイドなどをチェックをされると良いと思います。
destroyアクションが効かない、deleteメソッドも来ている、コードもあっている、ときのエラーで本当に焦りましたが、エラーが解決の糸口でした。参考になれば幸いです。

参考

https://qiita.com/chobi9999/items/2b59fdaf3dd8f2ed9268
http://takayuki-inoue.hatenablog.com/entry/2018/01/12/105846
https://qiita.com/ayacai115/items/ec7a621ec73692065d7a
https://qiita.com/natu_kumo_/items/8ef3343fda6715ed1d1a

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

#Rails / ActiveRecord / 複数形の validates メソッドと 単数形の validate メソッドの違いは? 前者が標準のvalidatorで後者がカスタムのvalidator。

びっくりするぐらい分かりにくいが、きっと命名的に苦慮の末だったのだろう。

validates (標準)

おなじみのやつ。

image

validate (カスタム)

こちらは自分でModelにメソッドを作って、ちっちゃなカスタムバリデーターを作る。

Active Record Validations — Ruby on Rails Guides

image

Original by Github issue

https://github.com/YumaInaura/YumaInaura/issues/3057

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

Library not loaded: libssl.1.1.dylib (LoadError) 発生時の対処策

状況

macOS catalina で railsアプリの開発環境を整え終わって、
Windows10環境で開発してたWebアプリをクローンし、いざrails server実行だ!
と意気込んでいたら、見た事のないエラーに困惑…

解決はできたものの、いろいろと調べたら闇が深そうだったので、
自分用にメモを残しておきます。

ついでに同じエラーに悩む方の助けになれば幸いです!

エラー内容

zsh
dlopen(/Users/kirimaro/.rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/mysql2-0.5.3/lib/mysql2/mysql2.bundle, 9): Library not loaded: libssl.1.1.dylib (LoadError)
  Referenced from: /Users/kirimaro/.rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/mysql2-0.5.3/lib/mysql2/mysql2.bundle
  Reason: image not found - /Users/kirimaro/.rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/mysql2-0.5.3/lib/mysql2/mysql2.bundle

OpenSSL@1.1のインストール

macOS catalina はデフォルトのSSLがLibreSSL 2.8.3が設定されています。
試しにopenssl versionを実行すると、LibreSSL 2.8.3と出力されはずです。
上記エラーの原因はこいつです。

gem "mysql2"はOpenSSLと依存関係にあり、LibreSSLがデフォルトだとLibrary not loaded: libssl.1.1.dylibといったロードエラーが発生します。

これを解消するために、まずOpenSSL@1.1をインストールします。

尚、OpenSSL@1.1Homebrewでインストールを行いますが、
Homebrewのインストール方法については、他で調べれば記事が大量だと思うのでここでは割愛させていただきます!
※Homebrewの利用にはXcodeが必須となりますので、インストールしていない方は先にインストールしておきましょう。

既にHomebrewをインストール済みの方は、以下のコマンドを実行します。

zsh
% brew install openssl@1.1

インストールが完了したら、brew info opensslを実行します。

するとOpenSSL@1.1について以下のような情報が表示されます。

zsh
% brew info openssl
openssl@1.1: stable 1.1.1d (bottled) [keg-only]
Cryptography and SSL/TLS Toolkit
https://openssl.org/
/usr/local/Cellar/openssl@1.1/1.1.1d (7,983 files, 17.9MB)
  Poured from bottle on 2020-03-30 at 20:49:11
From: https://github.com/Homebrew/homebrew-core/blob/master/Formula/openssl@1.1.rb
==> Caveats
A CA file has been bootstrapped using certificates from the system
keychain. To add additional certificates, place .pem files in
  /usr/local/etc/openssl@1.1/certs

and run
  /usr/local/opt/openssl@1.1/bin/c_rehash

openssl@1.1 is keg-only, which means it was not symlinked into /usr/local,
because openssl/libressl is provided by macOS so don't link an incompatible version.

If you need to have openssl@1.1 first in your PATH run:
  echo 'export PATH="/usr/local/opt/openssl@1.1/bin:$PATH"' >> ~/.zshrc

For compilers to find openssl@1.1 you may need to set:
  export LDFLAGS="-L/usr/local/opt/openssl@1.1/lib"
  export CPPFLAGS="-I/usr/local/opt/openssl@1.1/include"

For pkg-config to find openssl@1.1 you may need to set:
  export PKG_CONFIG_PATH="/usr/local/opt

重要なのは、echo 'export PATH="/usr/local/opt/openssl@1.1/bin:$PATH"' >> ~/.zshrcで、
これをzshにコピペして実行する事で、デフォルトのSSLがOpenSSLへ変更されます。

実は、エラーの解消自体はOpenSSLをインストールしていれば、デフォルトに設定していなくても問題なく解決できます。

私自身まだそこまで詳しくないのですが、開発ツールなどの多くがまだまだOpenSSLへ依存している状況もあるようなので、
デフォルトに指定しておいたほうが無難なのかなと思います。

正しく切り替わったか確認したい場合は、zshを再起動したうえでopenssl versionを入力してみて下さい。
OpenSSL @1.1.1dといった感じで、デフォルトSSLが切り替わっている事が確認できると思います。

mysql2ビルド時に必要となるlib/includeのPathを環境変数に追加

~/.zshenvファイルをviなどのテキストエディタで開き、
brew info openssl実行時に表示される下記パスを追加します。

保存したら反映するために、zshを再起動します。

export LDFLAGS="-L/usr/local/opt/openssl@1.1/lib"
export CPPFLAGS="-I/usr/local/opt/openssl@1.1/include"

bundle configにビルドパスを追加

railsアプリのルートディレクトリへ移動し、zshから下記コマンドを実行します。

zsh
% bundle config build.mysql2 --with-ldflags=$LDFALGS --with-cppflags=$CPPFLAGS

これで、bundle installを実行した際、mysql2のインストール時にビルドオプションとして、opensslのlib/includeが読み込まれます。

Railsアプリからすでにインストール済みのmysql2をアンインストール

下記コマンドを実行し、インストール済みのmysql2を削除します。

zsh
% bundle exec gem uninstall mysql2

bundle install を実行

既にbundle configでビルドオプションを設定済みですので、bundle install若しくはbundleのみでインストール可能です。

zsh
% bundle install

これで、ロードエラーが解消され、正常にサーバーが起動されると思います。

デフォルトSSLをLibreSSLに戻したい時

デフォルトSSLをLibreSSLに戻したい場合は、~/.zshrcに追加された
export PATH="/usr/local/opt/openssl@1.1/bin:$PATH"を削除・保存し、zshを再起動すると、
LibreSSLがデフォルトSSLに戻ります。

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

Library not loaded: libssl.1.1.dylib (LoadError) 発生時の解決方法

状況

macOS catalina で railsアプリの開発環境を整え終わって、
Windows10環境で開発してたWebアプリをクローンし、いざrails server実行だ!
と意気込んでいたら、見た事のないエラーに困惑…

解決はできたものの、いろいろと調べたら闇が深そうだったので、
自分用にメモを残しておきます。

ついでに同じエラーに悩む方の助けになれば幸いです!

エラー内容

zsh
dlopen(/Users/kirimaro/.rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/mysql2-0.5.3/lib/mysql2/mysql2.bundle, 9): Library not loaded: libssl.1.1.dylib (LoadError)
  Referenced from: /Users/kirimaro/.rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/mysql2-0.5.3/lib/mysql2/mysql2.bundle
  Reason: image not found - /Users/kirimaro/.rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/mysql2-0.5.3/lib/mysql2/mysql2.bundle

OpenSSL@1.1のインストール

macOS catalina はデフォルトのSSLがLibreSSL 2.8.3が設定されています。
試しにopenssl versionを実行すると、LibreSSL 2.8.3と出力されはずです。
上記エラーの原因はこいつです。

gem "mysql2"はOpenSSLと依存関係にあり、LibreSSLがデフォルトだとLibrary not loaded: libssl.1.1.dylibといったロードエラーが発生します。

これを解消するために、まずOpenSSL@1.1をインストールします。

尚、OpenSSL@1.1Homebrewでインストールを行いますが、
Homebrewのインストール方法については、他で調べれば記事が大量だと思うのでここでは割愛させていただきます!
※Homebrewの利用にはXcodeが必須となりますので、インストールしていない方は先にインストールしておきましょう。

既にHomebrewをインストール済みの方は、以下のコマンドを実行します。

zsh
% brew install openssl@1.1

インストールが完了したら、brew info opensslを実行します。

するとOpenSSL@1.1について以下のような情報が表示されます。

zsh
% brew info openssl
openssl@1.1: stable 1.1.1d (bottled) [keg-only]
Cryptography and SSL/TLS Toolkit
https://openssl.org/
/usr/local/Cellar/openssl@1.1/1.1.1d (7,983 files, 17.9MB)
  Poured from bottle on 2020-03-30 at 20:49:11
From: https://github.com/Homebrew/homebrew-core/blob/master/Formula/openssl@1.1.rb
==> Caveats
A CA file has been bootstrapped using certificates from the system
keychain. To add additional certificates, place .pem files in
  /usr/local/etc/openssl@1.1/certs

and run
  /usr/local/opt/openssl@1.1/bin/c_rehash

openssl@1.1 is keg-only, which means it was not symlinked into /usr/local,
because openssl/libressl is provided by macOS so don't link an incompatible version.

If you need to have openssl@1.1 first in your PATH run:
  echo 'export PATH="/usr/local/opt/openssl@1.1/bin:$PATH"' >> ~/.zshrc

For compilers to find openssl@1.1 you may need to set:
  export LDFLAGS="-L/usr/local/opt/openssl@1.1/lib"
  export CPPFLAGS="-I/usr/local/opt/openssl@1.1/include"

For pkg-config to find openssl@1.1 you may need to set:
  export PKG_CONFIG_PATH="/usr/local/opt

重要なのは、echo 'export PATH="/usr/local/opt/openssl@1.1/bin:$PATH"' >> ~/.zshrcで、
これをzshにコピペして実行する事で、デフォルトのSSLがOpenSSLへ変更されます。

実は、エラーの解消自体はOpenSSLをインストールしていれば、デフォルトに設定していなくても問題なく解決できます。

私自身まだそこまで詳しくないのですが、開発ツールなどの多くがまだまだOpenSSLへ依存している状況もあるようなので、
デフォルトに指定しておいたほうが無難なのかなと思います。

正しく切り替わったか確認したい場合は、zshを再起動したうえでopenssl versionを入力してみて下さい。
OpenSSL @1.1.1dといった感じで、デフォルトSSLが切り替わっている事が確認できると思います。

mysql2ビルド時に必要となるlib/includeのPathを環境変数に追加

~/.zshenvファイルをviなどのテキストエディタで開き、
brew info openssl実行時に表示される下記パスを追加します。

保存したら反映するために、zshを再起動します。

export LDFLAGS="-L/usr/local/opt/openssl@1.1/lib"
export CPPFLAGS="-I/usr/local/opt/openssl@1.1/include"

bundle configにビルドパスを追加

railsアプリのルートディレクトリへ移動し、zshから下記コマンドを実行します。
※特定のアプリにのみビルドオプションを指定したい場合は、該当アプリのルートディレクトリにいる状態でローカルの方のコマンドを実行してください!

zsh
#グローバル(別のアプリでmysql2をインストールする場合も、ビルドオプションが適用される)
% bundle config build.mysql2 --with-ldflags=$LDFALGS --with-cppflags=$CPPFLAGS

#ローカル(現在ルートディレクトリとなっているアプリでのみ、mysql2インストール時にビルドオプションが渡される)
% bundle config --local build.mysql2 --with-ldflags=$LDFALGS --with-cppflags=$CPPFLAGS

これで、bundle installを実行した際、mysql2のインストール時にビルドオプションとして、opensslのlib/includeが読み込まれます。

Railsアプリからすでにインストール済みのmysql2をアンインストール

下記コマンドを実行し、インストール済みのmysql2を削除します。

zsh
% bundle exec gem uninstall mysql2

bundle install を実行

既にbundle configでビルドオプションを設定済みですので、bundle install若しくはbundleのみでインストール可能です。

zsh
% bundle install

これで、ロードエラーが解消され、正常にサーバーが起動されると思います。

デフォルトSSLをLibreSSLに戻したい時

デフォルトSSLをLibreSSLに戻したい場合は、~/.zshrcに追加された
export PATH="/usr/local/opt/openssl@1.1/bin:$PATH"を削除・保存し、zshを再起動すると、
LibreSSLがデフォルトSSLに戻ります。

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

【Rails】HerokuでAWS s3に画像をアップロードしようとしたら<Message>Access Denied</Message>

事前準備

Railsでcarrierwaveを使ってAWS S3に画像をアップロードする手順を画像付きで説明する

概要についてはこちらの記事がとても分かりやすかったので参考にさせていただきました。

ただしこのまま$ git pushすると、アクセスキーがアップロードされてしまうので注意です。
アクセスキーの隠し方については、heroku 環境変数とかgem 'dotenv-rails'$ heroku config:set ACCESS_KEY=aaaaaaとか調べると出てくると思います。

この記事を読むべき人

  1. https://myapp.herokuapp.com で画像をs3にアップロードしようとしてもうまくいかない
  2. $ heroku logsしたら<Message>Access Denied</Message>って言われる

この状況の人にはお役に立てるかもしれません。

手順

IAMのユーザーのARNを取得する

  1. 「AWSマネジメントコンソール」で「セキュリティ、ID、およびコンプライアンス /IAM」を選択
  2. 「IAMリソース」の「ユーザー: 2」を選択(数字は人それぞれ)
  3. 「ユーザー」を選択
  4. 「ユーザーの ARN」をコピーする

パブリックアクセスのブロックをオフにする

  1. 「AWSマネジメントコンソール」で「ストレージ /s3」を選択
  2. バケットを選択
  3. 「アクセス権限」タブを選択
  4. 「ブロックパブリックアクセス」タブを選択(デフォルトで選択されている)
  5. 全てのブロックをオフにする

バケットポリシーを使用する

  1. 「ブロックパブリックアクセス」から「バケットポリシー」タブに切り替える
  2. 「バケットポリシーエディター」に以下を適用して、保存する
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Principal": {
                "AWS": "arn:aws:iam::111111111111:user/IAMユーザー名"
            },
            "Action": [
                "s3:GetObject",
                "s3:PutObject",
                "s3:PutObjectAcl"
            ],
            "Resource": "arn:aws:s3:::バケット名/*"
        }
    ]
}
  • "arn:aws:iam::111111111111:user/IAMユーザー名"はコピーしてきたIAMユーザーのARN
  • "Resource": "arn:aws:s3:::バケット名/*"にバケット名を入力する

以上

これでいけるはず。

参考記事

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

deviseに新しいカラムを追加したけどviewsに表示されない

deviseで作ったusersテーブルに新しくnameカラムを追加、新規登録画面にnameを入れるフィールドを作ったが、なぜかviewsで表示されない問題が解決したので備忘録も兼ねて記事に致します。この記事で少しでも参考になれば幸いです。

結論から申し上げますと、i18nを使うためにはi18n化されたviewsファイルでなきゃ駄目なようです

①自分はdeviseのviewsファイルを作成する際に以下コマンドで作成しました。
rails g devise:views users

②そして日本語化もしたかったのでlocaleファイルも作成
rails g devise:i18n:locale ja

①のコマンドではviewsファイルがi18n化されておらず②と噛み合っていなかったことで、nameフィールドがviewsに表示されなかったと思われます。なので①のファイルを作り直しました。

rails g devise:i18n:views users

以後、無事にviewsに表示されてめでたしめでたし。

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

docker上でRailsアプリを動かす際に、localhostでアクセスするためのオプション

はじめに

学習環境を用意するにおいて、コンテナ上に開発環境が用意できることで、

・ホストPCを汚すことなく環境を構築できる
・異なるPC間で同じ環境を使いまわせる
・格好いいと感じる

といった理由からRailsアプリをコンテナ上で作って動かしてみましたが、ホストからの動作確認で引っかかったのでメモします。
※動かしたのはscaffoldレベルのアプリです

先にまとめ

先に問題と解決をまとめて書いておきます

  • 問題
    • コンテナ内で起動したRailsアプリにホストからアクセスできない
  • 解決方法
    • Railsアプリを立ち上げる際に、-b 0.0.0.0をオプションとしてつける

以下、やったこと

まとめにたどり着くまでの状況をつらつらと

準備

ポートフォワードの設定をしてコンテナを立ち上げる

(ホスト)$ docker container run -it -p 30000:3000 /bin/bash

コンテナ内でアプリを作る

(コンテナ)# rails new scaffold_sample
(コンテナ)# bin/rails db:create

データベースを作成無事できたことを確認した後にアプリケーションを起動

(コンテナ)# bin/rails server

上記手順で起動したアプリにアクセスできない

ホストからlocalhost:30000(30000はポートフォワードの設定)にアクセスしてもアプリケーションからの応答はなし
ホストのターミナルからcurlを実行してもエラー
コンテナからlocalhost宛にcurlを実行すると正常な(期待する)htmlが返却される

解決手段

アプリケーションの起動時に-b 0.0.0.0をオプションとしてつける

(コンテナ)# bin/rails server -b 0.0.0.0

するとホスト側のブラウザからアプリケーションにアクセスできるようになった
「localhost:30000」「127.0.0.1:30000」「0.0.0.0:30000」のどれでもlocalhostにアクセスできていた

オプションの値について

上記の通り、-b 0.0.0.0を指定することでによってホストからのアクセスは可能になったが
-b 127.0.0.1-b localhostではホストからアクセスできるようにはならなかった

個人的なメモとして

0.0.0.0や127.0.0.1の持つ意味や、コンテナからみた自分のIP、コンテナからみたホストのIP、ホストから見たコンテナのIPなどが、
ネットワークのオプションとともにどのように振る舞うのかを学ばなければならないと感じた

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