20201010のRailsに関する記事は30件です。

【RSpec】ヘルパーメソッドが使われたviewファイルをテストしたらActionView::Template::Error:が出た。。

個人開発のアプリに通知機能を実装し、その後GitにpushしてCircleCIのテスト結果を確認したら突然大量のテストが失敗していて少し困ったので、その解決方法をここで共有させて頂きます。

エラー内容

Failure/Error: -if unchecked_notifications.any?

ActionView::Template::Error:
  undefined local variable or method `unchecked_notifications' for #<#<Class:0x0000555732134140>:0x00005557326f7f78>

通知が来ている場合と来ていない場合で表示するviewを変えるために、unchecked_notificationsという、ヘルパーメソッドを定義しました。しかし、RSpec実行時にはこのヘルパーメソッドが読み込まれておらずエラーが出てテストに失敗してしまったようです。

notifications_helper.rb
module NotificationsHelper
  def unchecked_notifications
    @notifications = current_user.passive_notifications.where(checked: false)
  end
end

解決策

解決策は簡単です。
テストを実行するファイルに、モジュールをインクルードするれば解決するはずです。↓例

post_spec.rb
require 'rails_helper'
#ヘルパーメソッドをインクルード
include NotificationsHelper

RSpec.describe '投稿機能', type: :system do
  #テスト処理
end

最後まで読んでいただきありがとうございます!

日々学習したことをアウトプットしてます!ご指摘などあればコメントいただけますと幸いです!

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

検索機能の実装

はじめに

個人アプリの作成にて、投稿した記事の内容を検索で表示出来るように検索機能の実装を行う。

ルーティングの設定

今回は:idを指定してページに遷移しないので、collectionを使用してルーティングを設定します。

routes.rb
resources :posts do
  get :search, on: :collection
  resource :likes, only: [:create, :destroy] -いいね機能実装
end

モデルの設定

LIKE句
LIKE句は、文字列の検索をすることができる。
whereメソッドと一緒に使う。

実行例 詳細
where('title LIKE(?)', "a%") aから始まるタイトル
where('title LIKE(?)', "%b") bで終わるタイトル
where('title LIKE(?)', "%c%") cが含まれるタイトル
where('title LIKE(?)', "d_") dで始まる2文字のタイトル
where('title LIKE(?)', "_e") eで終わる2文字のタイトル
app/models/post.rb
def self.search(search)
  return Post.all unless search
  Post.where('body LIKE(?)', "%#{search}%")
end

コントローラーにsearchアクションを定義

post_controller.rb
def search
  @posts = Post.search(params[:keyword])
end

viewの実装

renderメソッドを使って、部分テンプレートを行っているものとします。
{ post: post } の右側のpostはeachメソッド内の変数としてのpostpostのインスタンスを示しています。左側のpostは部分テンプレート内での変数の名前を表しています。

search.html.erb
<% @posts.each do |post| %>
   <%= render partial: "post", locals: { post: post } %>
<% end %>

おわりに

比較的簡単に検索機能の実装は完了しました。
他にも機能を実装し、出来ることを増やしていこうと思います。
最後まで見ていただきありがとうございます:grin:

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

Mixed Content(混在コンテンツ)エラーの解決方法

railsでアプリケーションを開発。本番環境(heroku)にデプロイしたがなんか見た目が違う...となりました。
解決はしましたが、新しい発見があったため記録として残しておきます!

問題発生の経緯

デプロイをしたがビューが崩れている

アセットファイルをコンパイルしてない!

rails assets:precompile RAILS_ENV=productionを実行

再度デプロイ。「これで大丈夫なはず!」

直ってませんでした......。

原因

結論から言うとコンパイルの問題ではなく外部ページがHTTPのページだったことが原因でした。(コンソールにちゃんとThis request has been blocked; the content must be served over HTTPS.と書かれていましたが確認しておらず反省です。)
httpsで読み込まれているのに対して、httpのものを読み込もうとするとエラーになるとは知らず新しい発見でした!

解決策

httpsであれば問題ないとのことだったのでhttpで読み込まれていたパスをhttpsに変えてみたが対応していませんでした...
今回はリセットCSSを外部から読み込んで使おうと思っていたため、外部から読み込むのではなくreset.cssとしてファイルを作成することで解決しました。

参考記事

https://qiita.com/Kouch/items/5089fb36694c6e467a3a
https://www.transnet.ne.jp/2016/02/28/rails%e5%88%9d%e5%ad%a6%e8%80%85%e3%81%8c%e3%81%a4%e3%81%be%e3%81%9a%e3%81%8dcolnr%e3%80%8c%e3%82%a2%e3%82%bb%e3%83%83%e3%83%88%e3%83%91%e3%82%a4%e3%83%97%e3%83%a9%e3%82%a4%e3%83%b3/

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

Rails datetime型からdate型のマイグレーションの書き方

「このカラムdatetimeじゃなくてdateでよさそう」

日付をあつかうからdatetimeにしてたけど、よくよく考えてみると「時間」は必要ないよねーとなったとき、dateに変換したくなりますよね。

今回のゴール

  • Bookモデルは発売日released_at(datetime)をもっている
  • マイグレーションをしてreleased_on(date)にしたい

環境

  • Rails 6.0.3
  • psql (PostgreSQL) 12.4

changeをつかったdatetime→dateの変換

def change
  rename_column :books, :released_at, :released_on
  change_column :books, :released_on, 'date USING CAST(released_on AS date)'
end

db:rollbackできますか?

これでよしよし、git commi..と、ちょっとまってください!

マイグレーションは巻き戻る(rollback)ことを考慮しておきましょう。

前述のマイグレーションをrollbackできるかを確かめてみましょう。

$ rails db:rollback

== ..... : reverting ========
rails aborted!
StandardError: An error has occurred, this and all later migrations canceled:


This migration uses change_column, which is not automatically reversible.
To make the migration reversible you can either:
1. Define #up and #down methods in place of the #change method.
2. Use the #reversible method to define reversible behavior.

なにがエラーになったのか

  • change_columnでロールバックしたとき、以前の型情報がわからないから元に戻せないから

'date USING CAST(released_on AS date)'で型を変換できたものの、「変換前の型の情報」が抜け落ちてしまったため、ロールバックに失敗してしまいました。

これを解決するためのひとつの方法としてup/downをつかった方法に書きかえてみましょう。

up/downをつかった方法

def up
  rename_column :books, :published_at, :release_on
  change_column :books, :release_on, 'date USING CAST(release_on AS date)'
end

def down
  rename_column :books, :release_on, :published_at
  change_column :books, :published_at, 'timestamp USING CAST(published_at AS timestamp)'
end

こうすることによって、

  • db:migrateではupdatetime -> dateが実行される
  • db:rollbackではdowndate -> datetimeが実行される

おまけ:PostgreSQLでdatetime USING CASTはエラーになる

change_column :books, :released_at, 'datetime USING CAST(released_at AS datetime)'
PG::UndefinedObject: ERROR:  type "datetime" does not exist
LINE 1: ...released_at" TYPE datetime USING CAST(published_at AS datetime)

Rails世界のdatetimeは、PostgreSQL世界ではtimestampになるという話でした :sweat_smile:

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

【Ruby】ハッシュテーブルの順番を逆にしたい

はじめに

たとえば、reputation = {very_bad: '最悪', bad: '悪い', good: '良い', very_good: '最高'}みたいなハッシュテーブルがあったとして、その順番を{very_good: '最高', good: '良い', bad: '悪い', very_bad: '最悪'}のように後ろから並べたい。

やり方

pry(main)> reputation.to_a.reverse.to_h 

 #=> {very_good: '最高', good: '良い', bad: '悪い', very_bad: '最悪'}

これだけです。

一応説明すると、reverseっていう配列の順番を逆にするメソッドを使いたいんだけどreputationはhashなので使えない。なので一度to_aでhashを配列に変換してreverseした後、そのままだと配列のままなのでto_hで元の戻す、ということです。

あとがき

「gemのenum_helpを使ってラジオボタンの表示は日本語表記に、内部ではint型で処理したい。評価が低い方から数字も低い方を割り当てたい、しかしラジオボタンの表示は評価が高い方から表示したい。」という場面で必要でした。
普通enum使うときってvalueが数字になるはずだから並べ替えやすいんですが、enum_help使うと上に書いたようなhashを渡すことになるので「どうやって並べ替えるんだ…」ってなりました。

記事にすることもないような気がしますが、僕のような初心者だとto_sto_iは使っても、to_aとかは使う機会が少なくて詰まることがあると思うので少しでも誰かの役に立てば。

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

【0からDockerに挑戦】③DockerとDocker-composeを使ってRuby on Rails・MYSQLの開発環境をつくる (part2)

DockerとDocker-composeを使ってRuby on Rails・MYSQLの開発環境をつくる

前回の続きになります。もし見てなければ先にそちらをみていただけたらと思います。

【前回の記事】
【0からDockerに挑戦】②DockerとDocker-composeを使ってRuby on Rails・MYSQLの開発環境をつくる (part1)

ちなみに前回までで下記手順②まで終了しています。なので今回は③からの手順になります。
 ①ローカルPC上に必要なディレクトリ・ファイルをつくる
 ②そのうちDockerfile、docker-compose.yml、Gemfileに必要な記述を書く
 ③docker-composeをビルド・実行させる
 ④コンテナ上でrails newを実行し必要なファイルを作成する。
 ⑤コンテナ上でデータを作成・起動する

それでは早速再開しましょう!!!


③docker-composeをビルド・実行させる

前回までで必要なファイルの記述が完了しましたので、さっそくターミナルでdocker-composeを実行しましょう。
まずはターミナルでDockerファイルのある場所(今回の場合は/Desktop/test-product)まで移動します。
そしたら下記コマンドを実行します。

ターミナル
user@user test-product % docker-compose up -d

そうすると2~3分程度で、さきほど用意したdocker-compose.ymlとDockerfileを基にコンテナが立ち上がります。もし記述に誤りがある場合は途中で処理が止まりますので、エラー文をみてファイルを修正しましょう。ちなみに「-d」はディタッチオプションといい、「-d」をつけることでバックグラウンドで実行することができます。

ちなみに下記のように、成功した後に「docker-compose ps」とターミナルで入力するとちゃんとコンテナができていることが確認できます。

ターミナル
user@user test-product % docker-compose ps

    Name                 Command             State           Ports         
---------------------------------------------------------------------------
test-product_db_1    docker-entrypoint.sh mysqld   Up      3306/tcp              
test-product_web_1   irb                           Up      0.0.0.0:3000->3000/tcp


④コンテナ上でrails newを実行し必要なファイルを作成する。

では出来上がったコンテナの中に入り、railsに必要なファイルを生成します。コンテナの中を操作するには下記のコマンドを入力します。

ターミナル
user@user test-product % docker-compose exec web bash
root@f206189dc19b:/test-product#

簡単に説明するとコマンドは、「exec(実行する)」、「web(コンテナの名前)」、「bash(場所はコンテナ内のシェル)」という意味です。つまりコンテナ内に入りbashを起動するということになります。「root@・・・・」と表示されていたらOKです。

そしてコンテナの中に入れたので「rails new」を実行し、必要なファイルを実行しましょう。

ターミナル
root@f206189dc19b:/test-product# rails new . --force --database=mysql --skip-bundle 

newのあとの「.」はカレントディレクトリを示します。「--force」はファイルを無理やり上書きすることを示します。rails newを実行するとGemfileも作らるのですが、今回ファイル自体はすでに作ってあるので--forceをつけることでGemfileを改めて作ります。(railsのバージョンは最初に指定したものになります)。「--database=mysql」はデータベースの指定です。そして最後の「--skip-bundle 」はコンテナ立ち上げ時にbundleをしたので、スキップを指定しました。これで必要なファイルは揃うのですが、1つ問題があります。それはdatabase.ymlの設定です。(おそらくファイルの生成には問題ないがbundle install時にエラーが起こる)。生成したファイルのconfig/database.ymlを開き、docker-compose.ymlの内容にあわせて下記のように編集しましょう。

ターミナル



(省略)
default: &default
  adapter: mysql2
  encoding: utf8
  pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
  username: root
  password: <%= ENV.fetch("DATABASE_PASSWORD")%>
  host: db


変更箇所はusernameとpasswordとhost名です。これで設定は問題ありません。

⑤コンテナ上でデータを作成・起動する

最後に「rails db:create」と「rails s」すれば終わりと行きたいところですが、このまま実行するとエラーがでます。理由はrails6.0から標準装備されたwebpackerがインストールされていないからです(rails5.0であればそのままでもエラー起きません)。なのでwebpackerをインストールしましょう。

ターミナル
root@f206189dc19b:/test-product# rails webpacker:install

問題なくインストールできるはずです。(もしエラーがおこるのであればDockerfileのyarnのインストールの指定の仕方を見直してください。前の記事に書いてあります)。これで準備ができたので「rails db:create」をしましょう成功するです。そして最後に下記のように[rails s」でサーバーを立ち上げましょう。ここでもしかしたらyarnに関するエラーがでるかもしれません。エラーが出た場合は「yarn upgrade」で解決できるはずです。

ターミナル
root@f206189dc19b:/test-product# rails s -b 0.0.0.0

ローカルの環境では自分のホスト内で完結していたため、特に指定なしで問題ありませんでした。しかしコンテナの場合、自分のPCからコンテナで立ち上げたサーバーへのアクセスは外部からのアクセス扱いになるので、指定が必要です。今回は0.0.0.0を指定して外部からのアクセスを許可しています。
【参照】
RailsアプリケーションをLAN内に公開する

これでブラウザにlocalhost:3000と記載すればrailsの初期画面が表示されるはずです。

まとめ

以上でdockerによる開発環境構築完了です。このあとはいつもどおりgitとrailsを用いいた開発になります。もし「バージョンを変えたい」や「環境系のエラー起きた」などがあっても、Dockerfileをいじり、新しくコンテナを立ち上げればすぐに対応できるでしょう(私も開発に取り組んでdokcerの凄さを実感しました)。dockerはまだまだ奥深く、勉強することがたくさんありそうですので、引き続き私もdockerの勉強がんばります。
しばらくあとになりますが、AWS環境(本番環境)でdockerを使ってみたり、dockerとCircleCIを用いてCICDパイプラインを作るという記事も書く予定です。

【前回までの記事】
【0からDockerに挑戦】①Dockerの概要と用語
【0からDockerに挑戦】②DockerとDocker-composeを使ってRuby on Rails・MYSQLの開発環境をつくる (part1)

参考になった教材

「米国AI開発者がゼロから教えるDocker講座」 
https://www.udemy.com/course/aidocker/
→初心者がDockerを学ぶならこれ!!!めちゃくちゃわかりやすい!!!linuxの基礎からDockerでの環境構築を網羅しています。
今回の環境構築もこの教材をベースにしています(もちろんrailsのバージョンとかdbなど自分なりに変えているので同じではないです)
もしDocker全然わかないという人はこの講座をぜひ買ってみてください。

「いまさらだけどDockerに入門したので分かりやすくまとめてみた」 
https://qiita.com/gold-kou/items/44860fbda1a34a001fc1
→すごく細かく網羅しているのでとてもわからなくなったらよく見ています。リファラル的によくみさせていただいてます。

「Docker/Kubernetes 実践コンテナ開発入門 --著 山田 明憲」 
→実践的な書籍、あまりにも初学者だと少しむずかしいかも。 → 基礎覚え得たあとに見返したら非常に良かったです!

「たった1日で基本が身に付く! Docker/Kubernetes超入門 --著」
→初学者が書籍で学びたい場合はこれがとっかかりやすい。

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

Ruby on Rails 変数、定数まとめ

Ruby on Rails 変数、定数まとめ

Railsにおいてよく使う変数、定数についてまとめました。

前提条件

  • 変数
    • 変更可能
  • 定数
    • 定数不可

変数

ローカル変数

  • メソッドやブロック内でのみ有効
  • 変数名
    • 小文字のスネークケース
      user_name = 'jon'

Railsでの活用

  • View
    • View でローカル変数を扱いたい場合は、Viewファイル内で定義する。 [users/show.html.erb]
<% hoge = 'huga' %>
<div><%= hoge %><div>
  • Controller
    • Viewに変数の値をわたす必要がなかったり、Contrller内で変数の値を共有する必要がない場合は、ローカル変数に定義する
def create
  article = Article.find( params[:id])
  return render_404 if article.blank?
end

インスタンス変数

  • 同じコントローラー内で使える変数
  • 変数名
    • 先頭に@を付ける

Rails での活用

  • Controller Action内で定義したインスタンス変数を View へわたす。
def show
  @user = User.find(params[:id])
end

[users/show.html.erb]

<div>ユーザーID:<%= @user.id %><div>

※ローカル変数をわたすことはできない

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

[users/show.html.erb]

<div>ユーザーID:<%= user.id %><div>

-> 参照不可
undefined local variable or method 'user'

グローバル変数

  • 全てのContrller使える
  • プログラムのどこからでも、変更、参照が可能
  • 変数名
    • 先頭に@を付ける

定数

  • クラス内で定義する

  • 定数名

    • 大文字、スネークケース

Rails での活用

  • [config/initializers/constants.rb]に共通の定数を定義する
    • 全ての Controller, View で参照することができる [config/initializers/constants.rb]
MAX_SIZE = 10
  • 各 Model で使用する定数を定義するには、model ファイルのクラス内に定義する。 [user.rb]
class User
  OFFICIAL_ID = 100
end

Controller や View で参照する場合は、User::OFFICIAL_IDと書く

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

[Rails5.2] Docker内のMysql 5.7の絵文字対応(文字コードをutf8mb4に変更する)

はじめに

Ruby on Railsでポートフォリオを作成しているRails初学者です。
今回はタイトルの通り、tableに絵文字情報を保存するためのmysqlをutf8mb4にした際の覚書です。

Youtube Data APIから動画タイトルを保存しようとしたらエラーが出た。

ActiveRecord::StatementInvalid in VideosController#refresh
Mysql2::Error: Incorrect string value: '\xF0\x9F\x94\xB5\xE8\x87...'

絵文字が入っているタイトルがいくつかあり、それが影響でエラーが出たようです。
mysql上のdbの文字コードを見るとuft8になっており、これを変更しなければいけない様子・・・
ひとまず色々なコードを参考にして以下の通りに実施しました。

環境

Docker for mac を使ってコンテナ内でRailsを開発しています。

ruby 2.4.5
mysql 5.7.31
Ruby on rails 5.0.7.2

流れ

(準備:dumpする)
1. my.cnfを変更する
2. mysqlをリスタートする
3. ActiveRecordにオプションを追加
4. database.ymlの変更
5. docker-compose.ymlの変更し、コンテナの再起動
6. db:migrate:resetを実行
7. dumpデータのrestore

準備:dumpする

そもそもこの作業を行うまでdumpという言葉を知らなかったのですが、dumpとはDBのテーブル内の情報をSQLの形で出力することだそうです。(ちなみに最後に行うrestoreは、dumpした情報をDBに投入すること)

今回データベース設定を変更し、各テーブルの文字コードをutf8mb4にするので、一旦データをファイルとして落としておいて、設定変更後に投入するという形をとりります。

dump/restoreを簡単に行うために以下のgemを使用します。

Gemfile
gem 'yaml_db'

今回使用するのはyaml_dbという、ダンプでyaml形式ファイルを出力してくれるgemです。
GitHub - yamldb/yaml_db

dumpするには以下のコマンドをすればOK

Terminal
bundle exec rails db:data:dump

コマンド実行後、db/data.ymlにファイルが出力されています。
こんな感じで、基本的にはテーブル名、カラム情報、レコードの順番で出力されます。

data.yml
videos:
  columns:
  - id
  - name
  - url
  - upload_at
  - created_at
  - updated_at
  records:
  - - 1
    - "【衝撃】ボーナス支給額をクイズで決める会社"
    - https://www.youtube.com/watch?v=42ofwfioMFM
    - 2020-10-09 09:00:00.000000000 Z
    - 2020-10-10 08:07:38.000000000 Z
    - 2020-10-10 08:07:38.000000000 Z
 :

以下で準備はOKです。

1. my.cnfを変更する

my.cnfファイルを以下のように設定します。
記載されている内容をきちんと理解したかったため、1行ずつ見ていきました。

my.cnf
[mysql]
default-character-set=utf8mb4 #文字コードの設定

[mysqld] #mysqld Mysqlサーバの設定
character-set-server = utf8mb4 #文字コードの設定
skip-character-set-client-handshake #client側で指定した文字コードを無視するためのもの
collation-server = utf8mb4_general_ci #ソート順の指定
init-connect = SET NAMES utf8mb4 #クライアントからserverへの送信に使用される文字セット指定

各項目について、細かく見ていきます。

[mysql]と[mysqld]
mysqldとは、MySQLサーバとも呼ばれる、mysqlでの各種操作を受け持っているメインプログラムのこと。
mysql側の操作は必ずこのmysqldよりされるため、ここに各種設定が必要。

skip-character-set-client-handshake
character-set-client-handshakeとは、クライアント側の文字コードをMySQL側に反映する行為。これをスキップすることで、uft8mb4への設定ができる。

collation-server = utf8mb4_general_ci
collationは、照合順序:ソート順のこと。_で区切られた各単語でそれぞれ設定。
詳細は以下を参照
【MySQL】照合順序とは?

init-connect = SET NAMES utf8mb4
クライアントからサーバへの送信に使用される文字コードの指定
詳細は以下を参照
10.1.4 接続文字セットおよび照合順序

2. mysqlをリスタートする

ひとまず、この設定を反映するためにmysqlをリスタートします。
Docker内のコンテナに入り以下のコマンドを実行します。

もともとの設定を一応確認しておきます

terminal
mysql>status;
:(中略)
Server characterset:    utf8
Db     characterset:    utf8
Client characterset:    utf8
Conn.  characterset:    utf8
:

utf8になっています。
というわけで、一旦リスタートして、もう一度みてみます。

terminal
mysql>service mysql restart
terminal
mysql>status
:(中略)
Server characterset:    utf8mb4
Db     characterset:    utf8mb4
Client characterset:    utf8mb4
Conn.  characterset:    utf8mb4
:

設定できました!

3. ActiveRecordにオプションを追加

次に、ActiveRecordのcreate_tableが実行される際に、utf8mb4を使って登録するよう設定していきます。

新しくconfig/initializers/utf8mb4.rbを作成し、以下のコードを記載。
config/initializersにファイルを投入すると、Railsが起動する前に初期設定として読み込まれるようになります。

config/initializers/utf8mb4.rb
#optionを設定するmodule
module Utf8mb4
  def create_table(table_name, options = {})
    table_options = options.merge(options: 'ENGINE=InnoDB ROW_FORMAT=DYNAMIC')
    super(table_name, table_options) do |td|
      yield td if block_given?
    end
  end
end

ActiveSupport.on_load :active_record do
  module ActiveRecord::ConnectionAdapters
    class AbstractMysqlAdapter
      #最初にmodule utf8mb4を実行し、その後既存のメソッドを実行(super)      
      prepend Utf8mb4
    end
  end
end

4. database.ymlの変更

以下の内容を追加ないし変更します

config/database.yml
  charset: utf8mb4
  encoding: utf8mb4
  collation: utf8mb4_general_ci

5. docker-compose.ymlの変更

Dockerの設定ファイルも以下のように変更します。

docker-compose.yml
:
  db:
    image: mysql:5.7
    command: mysqld --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci #utf8mb4をセット 
    container_name: コンテナ名
    volumes:
      - ./my.cnf:/etc/mysql/conf.d/my.cnf #my.cnfを読み込むよう設定
 :

この状態で、一度コンテナを再起動します。

6. db:migrate:resetを実行

ここまでのDBへの設定を反映させるため、DBをリセット→migrateします。

7. dumpデータのrestore

最後に、冒頭でdumpしたデータをもう一度投入すれば完了です。
以下のコマンドでrestoreできます。

terminal
rails db:data:load

以上で設定は完了です!

終わりに

色々と設定してきましたが、dumpなどの機能を知ることができたので、いい勉強になりました。

全面的に参考にさせていただいたサイト様
大変ありがとうございます・・・!
MySQLのencodingをutf8からutf8mb4に変更して寿司ビール問題に対応する
RailsのDBバックアップ(gem:yaml_db)
Rails5で絵文字を保存する utf8mb4 (docker)

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

DockerでのRailsアプリケーション構築

概要

Dockerの勉強会の際、作成したリポジトリがありそのまま埋めてるの勿体無いと思ったので記事にしました。
どなたかの参考になればいいなぁと思ってます。

https://github.com/yodev21/docker_tutorial_rails

作業手順

ファイル作成

下記ファイルを作成

Dockerfile
docker-compose.yml
Gemfile
Gemfile.lock
# イメージ名にRuby(Ver2.6.5)の実行環境のイメージを指定
FROM ruby:2.6.5

# パッケージのリストを更新しrailsの環境構築に必要なパッケージをインストール
RUN apt-get update -qq && apt-get install -y build-essential libpq-dev nodejs

# プロジェクト用のディレクトリを作成
RUN mkdir /myapp

# ワーキングディレクトリに設定
WORKDIR /myapp

# プロジェクトのディレクトリにコピー
COPY Gemfile /myapp/Gemfile
COPY Gemfile.lock /myapp/Gemfile.lock

# bundle install実行
RUN bundle install

# ビルドコンテキストの内容を全てmyappにコピー
COPY . /myapp
docker-compose.yml
version: '3'
services:
  db:
    # postgresのイメージを取得
    image: postgres
    environment:
      POSTGRES_USER: 'postgresql'
      POSTGRES_PASSWORD: 'postgresql-pass'
    restart: always
    volumes:
      - pgdatavol:/var/lib/postgresql/data
  web:
    # Dockerfileからイメージをビルドして使用
    build: .
    # コンテナ起動時に実行
    command: bundle exec rails s -p 3000 -b '0.0.0.0'
    # カレントディレクトリを/myappにバインドマウント
    volumes:
      - .:/myapp
    # 3000で公開して、コンテナの3000へ転送
    ports:
      - "3000:3000"
    # Webサービスを起動する前にdbサービスを起動
    depends_on:
      - db
# データ永続化のためにpgdatabolのvolumeを作成し、postgresqlのデータ領域をマウント
volumes:
  pgdatavol:
source 'https://rubygems.org'
gem 'rails', '5.2.4.2'
Gemfile.lock

railsアプリケーション作成

docker-compose run web rails new . --force --database=postgresql

railsプロジェクトに使用するデータベースの設定ファイルを修正

database.yml
default: &default
  adapter: postgresql
  encoding: unicode
  # -------- 追加 --------
  host: db
  username: postgresql
  password: postgresql-pass
  # -------- ここまで --------

デタッチモード(バックグラウンド)で起動

docker-compose up -d

bundle installが反映されない場合の対応

docker-compose build --no-cache

データベース作成コマンド

docker-compose run web rails db:create

Scaffoldにて簡易的なアプリケーション作成

docker-compose run web bin/rails g scaffold User name:string
docker-compose run web bin/rails db:migrate

http://localhost:3000/users

コンテナの停止

docker-compose stop

コンテナ削除

docker-compose down

コンテナ内に移動

docker-compose run web bash

Go言語を使用してみる

ディレクトリ移動

cd doc/golang/
FROM golang:1.9

RUN mkdir /echo
COPY main.go /echo
CMD ["go", "run", "/echo/main.go"]

イメージのビルド

docker image build -t example/echo:latest .

イメージの確認

docker image ls

コンテナの起動

docker container run -d -p 9000:8080 example/echo:latest

GETリクエストの確認

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

投稿者のみアクセスできるようにする記述

開発環境

Ruby 2.6.5
Rails 6.0.3.3

問題点

投稿者のみレビューの削除ができるように実装したいが、NoMethodErrorが出てしまう

原因

当初、条件式を以下のように記述をしておりました。

<% if current_user.id == @review.user_id %>

こちらは現在ログインしているユーザーと投稿者が同じ場合、条件式がTrueとなり処理を実行するという記述です。
しかし、こちらの記述で読み込むとエラーが発生しておりました。

確認したところ正しくは以下のような記述でした。

<% if user_signed_in? && current_user.id == @review.user_id %>

こちらの記述でログインの有無を確認することにより正常に処理を行えるようになりました。
初歩的な箇所ですが、改めて気付いたので、備忘録として投稿いたしました。

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

アソシエーション(多対多)!!

アソシエーションって何??:information_desk_person_tone2:

簡単に説明するとモデルを利用したテーブル同士の関連付けのことですね。
テーブル同士で関連付けておき、一方のモデルからもう一方のモデルに
アクセスできるようにするためということですね。

*今回は、「多対多」のアソシエーションの説明になります。

関連付けを行う理由

Railsでは、「関連付け(アソシエーション: association)」とは2つの
Active Recordモデル同士の繋がりのことですね。

2つモデルの間には関連付けを行なう必要がありますが、
その理由を知っていますか?

それはですね、関連付けを行う事でコードの共通操作をより
シンプルで簡単にできるからなんです。

例:アソシエーション(多対多)

今回は、usersテーブルとroomsテーブルで説明したいと思います。

まずは、テーブルごとに、他テーブルとの関係性を考えましょう。

・ユーザーがどのチャットルームに属しているかを管理する
・チャットルームにどのユーザーが存在するかを管理する

今回の下記の画像になります。

多対多.png

ここで注目すべきポイントは、usersテーブルとroomsテーブルの関係性が、
今回のDB設計では「多対多」という関係性が存在していますね。
「多対多」は、関連するテーブルのidをお互いが複数持っている関係性のことです。

今回の場合は、「一人のユーザーは複数のチャットルームに所属する」
「一つのチャットルームには複数ユーザーが所属する」という場合の関係性ですね。

しかしですね、このテーブルの関係性を示すために、外部キーへ複数の値を
保存できれば簡単なのですが、1つのカラムに対して複数の値を保存することはできません。

よく考えられる方法としては、関連するidが増えるごとにカラムを増やすという
パターンがあります。
例としてわかりやすいように下記に画像を載せておきますね。

無駄なカラム.png

画像を見てもらえるとわかるのですが、カラムを不安と
無駄なカラムが生じていますね。

このようなDB設計は、関連するレコードが増えるほどカラムが増えるため、
良くない設計とされているものですね。

これを解決するために使用するのが、中間テーブルです。

中間テーブル

では、中間テーブルって何って思われると思うので簡単に説明します。
中間テーブルとは、その名の通り2つのテーブルの中間にあるテーブルのことです。

今回で言うと、usersテーブルとroomsテーブルの間に中間テーブルを作成します。

中間テーブルは、「多対多」の関係にある2つのテーブルの間に挟まって、2つの組み合わせパターンだけをレコードとして保存することですね。

また、2つのモデルのみでは「多対多」のアソシエーションを
組むことはできません。
そのため、中間テーブルを利用して「多対多」の関係を定義します。

中間テーブル.png

まだこれだけの説明ではイメージがあまりできてないかもしれないので、
次はSNSに例えて説明します。
次の説明からはテーブル名を変えていきますので、先ほどのテーブル名とは異なります。わかりにくかったら申し訳ないです。。

タグ付き写真投稿アプリを例に中間テーブルの役割を確認しましょう

『Instagram』などの、一つの写真に複数のタグ付けができる写真投稿アプリをイメージしてみて下さい。
『Instagram』のようにタグ付け機能を実現するには3つのテーブルが
必要です。タグを保存するテーブル、写真を保存するテーブル、
そして「どの写真にどのタグが登録されているか」を保存するテーブルです。

このうち写真やタグを保存するテーブルの名前はtagsテーブル、photosテーブルで良いでしょう。「どの写真にどのタグが ~」のテーブルは、photos_tagsテーブルとします。関係する2つのテーブルをアンダーバーで繋いだ名前です。

それぞれのテーブルに保存されているレコードの関係性は以下のようになります。

instagram.png

上記の画像の見てもらうとわかるのですが、中間テーブルには、「どの写真とタグが関連づいているか」という情報が記録されています。一つのレコードには「photo_id × tag_id」の組み合わせが記録され、すべての写真とタグの組み合わせの数だけ、レコードが蓄積されていきます。例えば、10個の写真にそれぞれ、異なるタグが3つずつ付いている場合、これらの関係性を表すための中間テーブルには30個のレコードが生成されることになります。

ここまでが、中間テーブルを用いて情報をDBに保存することの説明です。ここからは、中間テーブルを用いてアソシエーションを設定する方法を確認します。

中間テーブルを経由して「多対多」のテーブルへアソシエーションを組むには、これまで使用してきたhas_manyメソッドに、throughオプションを記述する必要があります。
throughオプションの説明を次にさせて頂きます。

throughオプション

has_manyメソッドのthroughオプションは、モデルに多対多の関連を定義するときに利用します。
throughという名前のとおり、「〜を経由する」という意味です。
もっとわかりやすく参考画像を載せておきます。

参考.png

throughオプションを使用し、多対多のアソシエーションを定義する場合は、それぞれのモデルに以下のような記述をします。

models/photo.rb
class Photo < ApplicationRecord
  has_many :photos_tags
  has_many :tags, through: :photos_tags
end
models/tag.rb
class Tag < ApplicationRecord
  has_many :photos_tags
  has_many :photos, through: :photos_tags
end
models/photos_tag.rb
class PhotosTag < ApplicationRecord
  belongs_to :photo
  belongs_to :tag
end

多対多の関係にある2つのテーブルのモデルでは、has_manyメソッドによる「1対多」のアソシエーションを互いに定義するのと合わせて、
throughオプションによって経由する中間テーブルを指定します。

一方、中間テーブルのモデルでは、belongs_toメソッドで多対多の関係にある2つのテーブルを指定します。

以上が、「多対多」のアソシエーションを定義する方法です。

まとめ

・「多対多」のアソシエーションには中間テーブルを用いること
・「多対多」の関係にある2つのテーブルのモデルでは、has_manyメソッド
による「1対多」のアソシエーションを互いに定義するのと合わせて、
throughオプションを使用して経由する中間テーブルを指定すること

今回の説明は、長くなってしまって説明がわかりにくくなっていたら、
すいません。。
私なりに頑張ってまとめたので参考になれば嬉しいです。

以上。

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

RailsとVueの一連の流れについてのメモ書き

RailsとVueに関して今まで流れがわからなかったのですが、
色々いじっていたらようやく流れが理解できてきたので見返せるようにメモ書きをします。
初学者なので理解が間違っている可能性が非常に高いため参考程度に!
一般的な正解ではなく、あくまで私の理解と納得解です。

環境

MacOS Mojave
Ruby 2.6.4
Rails 6.0.3.3
Vue 2.6.12
Webpack 4.44.2
yarn 1.22.5
Docker 2.3.0.5
VScode
Vuetify
Vue-router
Vue-Store

ブラウザからroutes.rbまで

この記事では、以下のURLを使います。
http://localhost:3000/
http://localhost:3000/user

はじめに、WebブラウザからURLを入力します。
http://localhost:3000/

routes.rb
Rails.application.routes.draw do

  root to: 'home#index'
  get  "/user", to: 'home#index'
  # For details on the DSL available within this file, see https://guides.rubyonrails.org/routing.html
  end

ブラウザからRailsサーバへURLが送られます。
http://localhost:3000/のURLを指定しているため
routes.rbの"root to: 'home#index'"の読み込み先が
参照されます。

ルートからコントローラー

homeコントローラーのindexの関数が呼び出されます。

home_controller.rb
class HomeController < ApplicationController
  def index
  end
end

ちなみに正しいか分かりませんがroutes.rbも参照されるコントローラーも
毎回インスタンスが作成されている認識です。
だからルート間やコントローラ間で関連性がないのです。

本題に戻りますが、indexの関数が呼び出されます。
今回何も無いのでindexにマッチするVeiwファイルが参照されますが、
index関数内に@home = Home.allのようなモデルのインスタンスを作成する
プログラムがあった場合、モデルインスタンスが作成されて、
必要あればモデルがデータベースから情報を取得して@homeインスタンスに格納する
ステップがあるはずですが、今回は使用しません。流れとして覚えておきます。

コントローラーからVeiwファイル(erbファイル)

コントローラーのindex関数が対応するファイルを参照します。
veiw/home/index.html.erbを参照します。

ここでは以下の設定が投入されていますが、
これは、app/javascript/pack内のhello.vueファイルを参照することを意味します。

index.html.erb
<%= javascript_pack_tag 'hello_vue' %>
<%= stylesheet_pack_tag 'hello_vue' %>

Veiwファイル(erbファイル)からhello.vue(jsファイル)

Vueアプリケーションを使用するための根幹のファイルですね。
拡張子がVueの各ファイルはオブジェクトでまとまってこのファイルに
結合されるイメージです。

render: h => h(App)

で分かりますがapp.vueのテンプレートを
呼ぶことで app.vueの内容が表示されます。

このファイルはVueの各機能を使えるようにしてくれるものでして
このファイル自体にプロントの内容が記入されているわけではないです。
裏側で管理してくれる感じのファイルです。

    vuetify,
    router,
    store,

を定義してVueで使えるようにしています。

親コンポーネントファイルはAppとしています。
SPAを実現しているのはrouterで
データを格納するのがvuexで
UIのフレームワークがvuetifyです。

hello_vue.js
import Vue from 'vue';
import App from '../src/components/app/app';
import Vuetify from 'vuetify';
import router from '../src/router';
import store from "../src/vuex/index"
import 'vuetify/dist/vuetify.min.css';


Vue.use(Vuetify);
const vuetify = new Vuetify();

document.addEventListener('DOMContentLoaded', () => {
  const app = new Vue({
    vuetify,
    router,
    store,
    render: h => h(App)
  }).$mount()
  document.body.appendChild(app.$el)

  console.log(app)
})

次に見ていくのはAppファイルです。

hello.vue(jsファイル)からapp.vue(vueコンポーネント)

すごくシンプルにしていますが、
このファイルが親コンポーネントでこのファイルがブラウザに表示されます。
しかし

<router-view></router-view>

がいることで
hello_vueでroute機能を持たせているためrouteが待ったを掛けます。

app.vue
<template>
  <div>
    <router-view></router-view>
  </div>
</template>

app.vue(vueコンポーネント)からrouter.js(URLによってコンポーネントを決める)

route機能を持つとURL情報に従って指定のコンポーネントを返すようになります。

router.js
import Vue from 'vue';
import VueRouter from 'vue-router';
import Home from "../src/components/home/Home"
import User from "../src/components/user/User"



Vue.use(VueRouter);

export default new VueRouter({
    mode: 'history',
    routes: [{
        path: '/',
        component: Home
    }, {
        path: '/user',
        component: User
    }]
});

ブラウザでhttp://localhost:3000/
を打ちましたので

{
        path: '/',
        component: Home
    }

によりHomeのコンポーネントを返すように決まりました。
はじめのほうのroutes.rbにて

  root to: 'home#index'

のルート情報と連動しているのでURL情報をvueでも取得できています。

router.js(URLによってコンポーネントを決める)からHome.vue(子コンポーネント)

VueのルートにてURL情報から表示するコンポーネントを決めたので
ここでの対象コンポーネントであるHome.vueを見ていきます。

Home.vue
<template>
    <div>
        <p>Home</p>
        <p>{{ store}}</p>

    </div>
</template>


<script>
export default {
    computed: {
        store(){
            return this.$store.state.store
        }
    }
    }
</script>

templateは実際にブラウザに表示される部分でscriptはjsの処理の部分ですね。
template内の

<p>{{ store }}</p>

を表示したいのでscript処理内を見ていきます。

computed: {
    store(){
        return this.$store.state.store
    }
}

と書いてありますが、これは、Vuexを定義しているファイルの
state.storeのデータを持ってきてと言っていますのでvuexを定義しているファイルを見ていきます。

Home.vue(子コンポーネント)からindex.js(vuexを定義したファイル)

index.js
import Vuex from 'vuex';
import Vue from 'vue';

Vue.use(Vuex);


export default new Vuex.Store({
    state: {
        store: "Store"
    }
});

ファイル名が分かりにくくすいませんがvuexを定義したファイルです。

store:"Store"

"Store"をstoreで定義しているので、どのコンポーネントからでもstoreと書けば"Store"を呼び出せます。

index.js(vuexを定義したファイル)からHome.vue(子コンポーネント)

再びHome.vueへ戻ります。

<p>{{ store }}</p>

を表示したいのでscript処理内を見ていきます。

computed: {
    store(){
        return this.$store.state.store
    }
}

scriptでvuexで"Store"を持ってきて、

{{ store }}

に入れて表示するという流れです。

Home.vue(子コンポーネント)からapp.vue(vueコンポーネント)

RouteでURLのコンポーネント(Home)が決まったと思います。
それをapp.vueにあった、に代入します。

app.vue
<template>
  <div>
    <router-view></router-view>
  </div>
</template>

下の

<router-view></router-view>

Home.vue
<template>
    <div>
        <p>Home</p>
        <p>{{ store}}</p>

    </div>
</template>


<script>
export default {
    computed: {
        store(){
            return this.$store.state.store
        }
    }
    }
</script>

に代入されます。

app.vue(vueコンポーネント)からhello_vue.js

はじめの管理ファイルに戻ってきます。
app.vueファイルが全てオブジェクトになってhello_vue.jsに渡ってきます。

hello_vue.js
import Vue from 'vue';
import App from '../src/components/app/app';
import Vuetify from 'vuetify';
import router from '../src/router';
import store from "../src/vuex/index"
import 'vuetify/dist/vuetify.min.css';


Vue.use(Vuetify);
const vuetify = new Vuetify();

document.addEventListener('DOMContentLoaded', () => {
  const app = new Vue({
    vuetify,
    router,
    store,
    render: h => h(App)
  }).$mount()
  document.body.appendChild(app.$el)

  console.log(app)
})

以下のレンダー関数により

render: h => h(App)

全てのVueファイルがオブジェクトになって返されます。

hello_vue.jsからVeiwファイル(erbファイル)

やっとRails側に戻ってきます。
全てのVueファイルがindex.html.erbに結合されます。

index.html.erb
<%= javascript_pack_tag 'hello_vue' %>
<%= stylesheet_pack_tag 'hello_vue' %>

Veiwファイル(erbファイル)からコントローラー

ちょっと曖昧なのですが、最後にVeiwファイルをまとめてコントローラーがブラウザに返します。

スクリーンショット 2020-10-10 15.57.04.png

ブラウザにhttp://localhost:3000/を打ち込んだら
Home.vueに記載されているHomeとStoreが返されました。

同じように別のURLを打ったら?

別のURLで試してみます。
http://localhost:3000/user
上記のURLをブラウザで打ち込みます。

get  "/user", to: 'home#index'

が使用されます。
コントローラーは同じで、erbも同じですので割愛します。

routes.rb
Rails.application.routes.draw do

  root to: 'home#index'
  get  "/user", to: 'home#index'
  # For details on the DSL available within this file, see https://guides.rubyonrails.org/routing.html
  end
router.js
import Vue from 'vue';
import VueRouter from 'vue-router';
import Home from "../src/components/home/Home"
import User from "../src/components/user/User"



Vue.use(VueRouter);

export default new VueRouter({
    mode: 'history',
    routes: [{
        path: '/',
        component: Home
    }, {
        path: '/user',
        component: User
    }]
});

vue側のrouter.jsで以下のURLとコンポーネントが使用されます。
今回はUserコンポーネントです。

{
        path: '/user',
        component: User
    }

Userコンポーネントは以下のようにシンプルです。
あとは同じようにrouteがapp.vueに結合してくれてhello_vueで表示してくれます。
後は同じ手順です。

User.vue
<template>
    <div>
        <p>User</p>
    </div>
</template>

スクリーンショット 2020-10-10 16.04.02.png

Userが表示されました。

因みに、routerを使っているのになぜSPAにならないのか
(/と/user)で2回サーバにアクセスしに行っているのかですが、
ブラウザに直接URLを打ち込んでいるからです。

Home.vueやUser.vueで各コンポーネントに接続できるようにプログラムすれば
SPAを実現できます。

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

[Docker]dockerでRails5.2環境構築

前提

dockerとdocker-composeインストール済

1. fileを用意

Gemfile, Gemfile.lock, Dockerfile, Docker-compose.ymlを~/Desktop/product-register下におく。

2. Dockerfile

FROM ruby:2.5
RUN apt-get update && apt-get install -y \
      build-essential \
      libpg-dev \
      nodejs \
      postgresql-client \
      yarn
WORKDIR /product-register
COPY Gemfile Gemfile.lock /product-register/
RUN bundle install

3. Docker-compose.yml

version: '3'
services:
  web:
    build: .    // imageとってくる場合は image: イメージ名
    ports:
      - '3000:3000'
    volumes:
      - '.:/product-register'
    tty: true
    stdin_open: true

注意点: インデントやスペースがシビア

4. コンテナを作ってRailsアプリを作成

$ docker-compose up -d 
$ docker-compose exec web bash
:/product-register# rails new . --force -database=postgresql -skip-bundle // gemfileが更新されてしまう
$ docker-compose down // gemfileを更新するためにコンテナを一度落とす
$ docker-compose up --build -d  新しくdockerfileをbuildしたimageを使うために--buildをつける
$ docker-compose exec web bash //再度入る
:/product-register# rails s -b 0.0.0.0 // dbの設定がまだなのでエラーが出る

5. DBの設定

1. 作成されたアプリのconfig/database.ymlの修正

default: &default
  adapter: postgresql
  host: db                  //不思議だがこれでdbサービスを認識してくれる
  user: postgres       
  port: 5432
  password: <%= ENV.fetch("DATABASE_PASSWORD") %> 

2. docker-compose.yml ファイルのdb部分の追記

version: '3'

volumes:
  db-data:             // docker volumeを作っている
services:
  web:
    build: .
    ports:
      - '3000:3000'
    volumes:
      - '.:/app'
    environment:
      - 'DATABASE_PASSWORD=postgres'
    tty: true
    stdin_open: true
    depends_on:
      - db
    links:
      - db

  db:
    image: postgres
    volumes:
      - 'db-data:/var/lib/postgresql/data'    //上で作ったdockervolumeをマウントしてる
    environment:                      
      - 'POSTGRES_PASSWORD=postgres'   //最近必要になったらしい
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

部分一致検索機能の実装をRansuckを使用せず行う

部分一致検索機能の実装をRansuckを使用せず行ったのでまとめていく。
今回は名前、メールアドレスを入力するとそれに部分一致で該当するユーザーの情報を取得できるという簡単な機能を作る。

例 
名前フォームに「の」 メールアドレスフォームに「  」で検索
             ↓
野比 のび太 nobinobi@example.com
が出力するみたいな感じ。にしたい。

結論からということでまずはコード載せますね。

routes.rb
Rails.application.routes.draw do
  root 'users#index'
  resources :users
end
index.html
 <h1>ドラえもんキャラを部分一致検索してみよう</h1>
<%= form_with(url: 'users', local: true, method: :get) do |f| %>
  <span class="small">検索対象: 名前 メールアドレス</span>
    <div>
      <%= f.label :name, '名前' %>
      <%= f.text_field :name , value: @search_params[:name] %>
    </div>
    <div>
      <%= f.label :email, 'メールアドレス' %>
      <%= f.text_field :email, value: @search_params[:email] %>
    </div>
  <%= f.submit '検索', class: 'btn btn-default' %>
<%end%>

<h1>検索結果</h1>
  <% @users.each do |user| %>
    <%= user.name %>
    <%= user.email %>
  <% end %>
users_controller.rb
class UsersController < ApplicationController
  def index
    @search_params = search_params
    @users = User.search(search_params)
  end

  private

  def search_params
    params.permit( :name, :email  )
  end
end
user.rb
class User < ApplicationRecord
  scope :search, -> (search_params) do
    return if search_params.blank?
      name_like(search_params[:name] )
      .email_like(search_params[:email])
    end
  scope :name_like, -> (name){where("name LIKE ?" ,"%#{name}%")}
  scope :email_like, -> (email){where("email LIKE ?", "%#{email}%")}
end
schema.rb
ActiveRecord::Schema.define(version: 2020_10_10_011424) do

  create_table "users", force: :cascade do |t|
    t.string "name"
    t.string "email"
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
  end

end

seeds.rb
User.create!(name: "野比 のび太",email: "nobinobi@example.com")
User.create!(name: "剛田 武",email: "soulmate@example.com")
User.create!(name: "骨川 スネ夫",email: "kanemoti@example.com")
User.create!(name: "ドラえもん",email: "dora@example.com")
User.create!(name: "ドラミちゃん",email: "dorami@example.com")
User.create!(name: "源 しずか",email: "piano@example.com")

一個一個説明していきます。
まずviewの説明から

index.html
 <h1>ドラえもんキャラを部分一致検索してみよう</h1>
<%= form_with(url: 'users', local: true, method: :get) do |f| %>
  <span class="small">検索対象: 名前 メールアドレス</span>
    <div>
      <%= f.label :name, '名前' %>
      <%= f.text_field :name , value: @search_params[:name] %>
    </div>
    <div>
      <%= f.label :email, 'メールアドレス' %>
      <%= f.text_field :email, value: @search_params[:email] %>
    </div>
  <%= f.submit '検索', class: 'btn btn-default' %>
<%end%>

<h1>検索結果</h1>
  <% @users.each do |user| %>
    <%= user.name %>
    <%= user.email %>
  <% end %>

まずここなのですが

<%= form_with(scope: :search ,url: 'users', local: true, method: :get) do |f| %>

検索フォームはform_withを使用しています。form_tag と form_forとの違いは下記URLにわかりやすく説明されています。
https://qiita.com/hmmrjn/items/24f3b8eade206ace17e
(以前は関連するモデルがなかったときは form_tag 関連するモデルがある時は form_forを使用していましたがrails5.1で非推奨となっています。具体例として挙げるとUser情報を使いたいだけ(検索時)などはform_tag
User情報を登録したい→登録されるデータベースが存在している時はform_forを使用していた。)
rails5.1以降より推奨されているform_withはそこら辺の処理をうまい感じに切り分けて実行してくれます。またデフォルトで非同期通信のAjaxが実装されてます。今回はJSは使用しない実装なので、リモートフォームを無効にするため local: true と指定し、HTML形式でデータをmethod: get にて通信しています。

続いてコントローラーを見ていきましょう。

users_controller.rb
class UsersController < ApplicationController
  def index
    @search_params = search_params
    @users = User.search(search_params)
  end

  private

  def search_params
    params.permit( :name, :email  )
  end
end

ここでのみそは User.search(search_params) です。こちらのsearchメソッドなのですがUsermodelの方に部分一致検索機能をscopeしています。送られた値に対しsearchでnameとemailに部分一致処理をし返します。続いてmodelを参照してください。

user.rb
class User < ApplicationRecord
  scope :search, -> (search_params) do
    return if search_params.blank?
      name_like(search_params[:name] )
      .email_like(search_params[:email])
    end
  scope :name_like, -> (name){where("name LIKE ?" ,"%#{name}%")}
  scope :email_like, -> (email){where("email LIKE ?", "%#{email}%")}
end

searchスコープはこちらに飛んで一つ一つ部分処理をした後コントローラに返されます。
return if search_params.blank?
を入れておく事で仮に何も値がはいっていなくても処理をそこで止めてコントローラに返します。
検索対象が今回のように:name :emailと複数である場合はparamsを繰り返し処理とし一つ一つ検索を返すようにします。

今回は以上となります。
もしここは違うなどありましたどんどん指摘しください。

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

部分一致検索機能の実装をransuckを使用せず行う

部分一致検索機能の実装をransuckを使用せず行ったのでまとめていく。
今回は名前、メールアドレスを入力するとそれに部分一致で該当するユーザーの情報を取得できるという簡単な機能を作る。

例 
名前フォームに「の」 メールアドレスフォームに「  」で検索
             ↓
野比 のび太 nobinobi@example.com
が出力するみたいな感じ。にしたい。

結論からということでまずはコード載せますね。

routes.rb
Rails.application.routes.draw do
  root 'users#index'
  resources :users
end
index.html
 <h1>ドラえもんキャラを部分一致検索してみよう</h1>
<%= form_with(url: 'users', local: true, method: :get) do |f| %>
  <span class="small">検索対象: 名前 メールアドレス</span>
    <div>
      <%= f.label :name, '名前' %>
      <%= f.text_field :name , value: @search_params[:name] %>
    </div>
    <div>
      <%= f.label :email, 'メールアドレス' %>
      <%= f.text_field :email, value: @search_params[:email] %>
    </div>
  <%= f.submit '検索', class: 'btn btn-default' %>
<%end%>

<h1>検索結果</h1>
  <% @users.each do |user| %>
    <%= user.name %>
    <%= user.email %>
  <% end %>
users_controller.rb
class UsersController < ApplicationController
  def index
    @search_params = search_params
    @users = User.search(search_params)
  end

  private

  def search_params
    params.permit( :name, :email  )
  end
end
user.rb
class User < ApplicationRecord
  scope :search, -> (search_params) do
    return if search_params.blank?
      name_like(search_params[:name] )
      .email_like(search_params[:email])
    end
  scope :name_like, -> (name){where("name LIKE ?" ,"%#{name}%")}
  scope :email_like, -> (email){where("email LIKE ?", "%#{email}%")}
end
schema.rb
ActiveRecord::Schema.define(version: 2020_10_10_011424) do

  create_table "users", force: :cascade do |t|
    t.string "name"
    t.string "email"
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
  end

end

seeds.rb
User.create!(name: "野比 のび太",email: "nobinobi@example.com")
User.create!(name: "剛田 武",email: "soulmate@example.com")
User.create!(name: "骨川 スネ夫",email: "kanemoti@example.com")
User.create!(name: "ドラえもん",email: "dora@example.com")
User.create!(name: "ドラミちゃん",email: "dorami@example.com")
User.create!(name: "源 しずか",email: "piano@example.com")

一個一個説明していきます。
まずviewの説明から

index.html
 <h1>ドラえもんキャラを部分一致検索してみよう</h1>
<%= form_with(url: 'users', local: true, method: :get) do |f| %>
  <span class="small">検索対象: 名前 メールアドレス</span>
    <div>
      <%= f.label :name, '名前' %>
      <%= f.text_field :name , value: @search_params[:name] %>
    </div>
    <div>
      <%= f.label :email, 'メールアドレス' %>
      <%= f.text_field :email, value: @search_params[:email] %>
    </div>
  <%= f.submit '検索', class: 'btn btn-default' %>
<%end%>

<h1>検索結果</h1>
  <% @users.each do |user| %>
    <%= user.name %>
    <%= user.email %>
  <% end %>

まずここなのですが

<%= form_with(scope: :search ,url: 'users', local: true, method: :get) do |f| %>

検索フォームはform_withを使用しています。form_tag と form_forとの違いは下記URLにわかりやすく説明されています。
https://qiita.com/hmmrjn/items/24f3b8eade206ace17e
(以前は関連するモデルがなかったときは form_tag 関連するモデルがある時は form_forを使用していましたがrails5.1で非推奨となっています。具体例として挙げるとUser情報を使いたいだけ(検索時)などはform_tag
User情報を登録したい→登録されるデータベースが存在している時はform_forを使用していた。)
rails5.1以降より推奨されているform_withはそこら辺の処理をうまい感じに切り分けて実行してくれます。またデフォルトで非同期通信のAjaxが実装されてます。今回はJSは使用しない実装なので、リモートフォームを無効にするため local: true と指定し、HTML形式でデータをmethod: get にて通信しています。

続いてコントローラーを見ていきましょう。

users_controller.rb
class UsersController < ApplicationController
  def index
    @search_params = search_params
    @users = User.search(search_params)
  end

  private

  def search_params
    params.permit( :name, :email  )
  end
end

ここでのみそは User.search(search_params) です。こちらのsearchメソッドなのですがUsermodelの方に部分一致検索機能をscopeしています。送られた値に対しsearchでnameとemailに部分一致処理をし返します。続いてmodelを参照してください。

user.rb
class User < ApplicationRecord
  scope :search, -> (search_params) do
    return if search_params.blank?
      name_like(search_params[:name] )
      .email_like(search_params[:email])
    end
  scope :name_like, -> (name){where("name LIKE ?" ,"%#{name}%")}
  scope :email_like, -> (email){where("email LIKE ?", "%#{email}%")}
end

searchスコープはこちらに飛んで一つ一つ部分処理をした後コントローラに返されます。
return if search_params.blank?
を入れておく事で仮に何も値がはいっていなくても処理をそこで止めてコントローラに返します。
検索対象が今回のように:name :emailと複数である場合はparamsを繰り返し処理とし一つ一つ検索を返すようにします。

今回は以上となります。
もしここは違うなどありましたどんどん指摘しください。

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

Heroku+ActiveStorage+Amazon S3

1 AWSコンソール → サービス → ストレージ → S3でパケット作成

項目 入力・選択
パケット名 ex: my-rails-app-first-bucket
リージョン :flag_jp: 東京
アクセス権限 チェック欄全てoff
上記以外 デフォルト

2 アクセスキーを作る
下記参照
https://tech-blog.s-yoshiki.com/entry/135

3 必要なGemのインストール

gem "aws-sdk-s3", require: false #追記

4 S3へのアクセスキーを入力

$ EDITOR=vim rails credentials:edit

-vi/vim操作-
iキーで編集開始、escで編集終了 、 ZZで保存して終了
後から入力を確認したいときは

$ rails credentials:show

5 Herokuでの画像の保存先をAmazon S3に変更

config/storage.yml
amazon:
  # 以下3行はそのまま
  service: S3
  access_key_id: <%= Rails.application.credentials.dig(:aws, :access_key_id) %>
  secret_access_key: <%= Rails.application.credentials.dig(:aws, :secret_access_key) %>
  # 以下2行は変える
  region: ap-northeast-1 #東京
  bucket: my-rails-app-first-bucket #自分で作成したS3のバケットの名前
config/environmentas/production.rb
config.active_storage.service = :amazon #amazonに変更

6 最後にHerokuでやること

$ heroku buildpacks:add -i 1 https://github.com/heroku/heroku-buildpack-activestorage-preview
$ git add .
$ git commit -m "added s3 to production"
$ git push heroku master

Railsプロジェックトのマスターキー(config/master.keyの中身)をコピーします

$ heroku config:set RAILS_MASTER_KEY=マスターキーをここに貼り付け
$ heroku run rails db:migrate
$ heroku open
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Trailblazerでcreateを試してみる

はじめに

抽象レイヤーを提供してくれるGem,Trailblazerの存在を知り勉強がてらにCreateを試してみた。
Trailblazerの概要については他の記事でわかりやすく説明していただいてるので割愛とする。

こちらの記事がとてもわかりやすかったです。
TrailBlazer概要まとめてみた

環境

ruby '2.7.2'
'rails', '~> 6.0.3'
template engine: haml

Create

Trailblazerのディレクトリ構成

.
└── todo
    ├── cell
    │   ├── index.rb
    │   └── new.rb
    ├── contract
    │   └── form.rb
    ├── operation
    │   ├── create.rb
    │   └── index.rb
    └── view
        ├── form.haml
        ├── index.haml
        └── new.haml

Operation

module Todo::Operation
  class Create < Trailblazer::Operation
    class Present < Trailblazer::Operation
      step Model(Todo, :new)
      step Contract::Build( constant: Todo::Contract::Form )
    end

    step Subprocess(Present) # present classのstep呼び出し
    step Contract::Validate(key: :todo) # contractを使ってバリデーション
    step Contract::Persist() # モデルに保存する
  end
end

Contract

module Todo::Contract
  class Form < Reform::Form
    include Reform::Form::ActiveModel
    property :title
    property :description
    property :completed

    validates :title, presence: true
    validates :description, length: { minimum: 2 }

    # overrideもできる
    # def title
    #  super.capitalize
    # end
  end
end

Cell

module Todo::Cell
  class New < Trailblazer::Cell
    include ActionView::RecordIdentifier
    include ActionView::Helpers::FormOptionsHelper
    include SimpleForm::ActionViewExtensions::FormHelper
  end
end

View

= simple_form_for(model, html: {novalidate: true}) do |f|
  = f.input :title, placeholder: "Title"
  %br
  = f.input :description, as: :text, placeholder: "Description"
  %br
  = f.submit 'Submit'
  = link_to 'Back', todos_path

確認

trab-todo

今回試したプロジェクト

参考記事

TrailBlazer概要まとめてみた
trailblazer

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

JQueryでscrollイベントが発火しない問題を解決しました

概要

railsアプリを作っていて
ある時急にスクロールイベントトリガーを使った無限スクロールが動かなくなった時の原因と対処です

原因

cssプロパティの
overflow: auto;
こいつが原因でした。
どうやらoverflowでscroll(要素がはみでたらスクロールして対処)が指定されるとscrollイベントが起こらなくなる模様です。
autoを指定していたことで私の使っていたChromeではスクロールイベントが発火しなくなった模様です。

解決策

overflow: auto;またはoverflow: scroll;を消す

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

【Rails】deviseのviewやcontrollerの編集とカスタマイズ方法

はじめに

deviseのviewとcontrollerを編集、カスタマイズする方法

deviseを導入した後、見た目が味気なく英語ばかりのviewを整えたい。
deviseのcontrollerに編集を加えてカスタマイズする必要がある。
そんな時の方法を簡単に紹介します。

目次

1.前提
2.devise.rbの設定ファイルを変更
3.contorollerの生成・カスタマイズ
4.viewファイルの生成・編集
5.まとめ

開発環境

ruby 2.6.5
rails 6.0.0
devise 4.7.3

1.前提

devise導入済み
model生成済み
ルーティングは随所で設定

実装

それでは実装します。

2.devise.rbの設定ファイルを変更

config/initializers/devise.rbを開きます。config.scoped_viewsを有効にします。

config/initializers/devise.rb
  # ==> Scopes configuration
  # Turn scoped views on. Before rendering "sessions/new", it will first check for
  # "users/sessions/new". It's turned off by default because it's slower if you
  # are using only default views.
  config.scoped_views = true #←デフォルトはfalse#

Railsサーバを再起動します。
「gemを入れた時」などと同様で再起動が必要です。これを怠るとソースが反映されず「エラーは起きずないが、思い通りにならないという」原因を突き止めるのが難しい状況に陥ります。

3.controllerファイルの生成・編集

deviseのcontrollerを生成します。

$ rails g devise:controllers users

この時の公文は
 rails g devise:controllers モデル名
です。
今回はdeviseモデルをuserにしているので(大体userにしますが)その名前を使います。
app > controllers > users >
の下にファイルができます。
スクリーンショット 2020-10-10 11.27.44.png
例えば、ユーザー登録関連のcontrollerをカスタマイズしたければ、regisrations_controller.rbファイルを編集します。

ここで注意が必要なのが、ルーティングの設定です。
ルーティングで記述している

[コントローラー名]#[アクション名]
と実際の
[コントローラー]#[アクション]

が違うと、当たり前ですがうまく行きません。
なので、rails routes 等でしっかり確認する事をおすすめします。

4.viewファイルの生成・編集

deviseのviewファイルを生成・編集します。

$ rails g devise:views

app > views > devise >
devise直下にフォルダができます。

スクリーンショット 2020-10-10 11.04.57.png

例えば登録画面を編集したければ、regisrationsのディレクトリにあるファイルを編集します。

ここで注意するべきなのは、viewのファイル名です。
deviseではrenderやredirect_toで遷移先を指定しなければ、コントローラーの処理後メソッド名に遷移する様になってます。
なので、それぞれの名前を合わせる必要があります。

5.まとめ

contorollerの生成・カスタマイズとviewファイルの生成・編集を紹介しました。
deviseのcontrollerを使うと、デフォルトで持ってる機能を自動で使えると言うメリットがあります。
しかし、その反面に間違った記述をしてても、裏でdeviseが動いてくれるので、思い通りの挙動にならずエラーも出ないという状況にも陥ります。
その為にも、ルーティング、コントローラー、ビューがどうの様に辿ってきてるか注意する事をおすすめします。

最後に

私はプログラミング初学者ですが、自分と同じ様にエンジニアを目指す方々の助けになればと思い、記事を投稿しております。
それではまた次回お会いしましょう〜

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

【買付代行サービス個人開発 - No.010】会社の切替画面、会社の詳細画面、会社切替後は注文画面に行くようにする

Issue
PR

概要

会社の切替画面、会社の詳細画面、会社切替後は注文画面に行くようにする
Figmaに近づける

ToDoリスト

  • 会社の切替画面修正

  • 注文画面修正

  • 会社の詳細画面修正

ToDoリスト詳細

  • ロゴ追加
    app/assets/images/svg/sample.svg追加

  • 会社の切替画面修正

app/views/orgs/index.html.slim
= render(::Layout::NavbarComponent.new(org: @org, tab: false))
.py-10.px-8
  header
    .max-w-7xl.mx-auto.px-4.sm:px-6.lg:px-8
      h1.text-3xl.font-bold.leading-tight.text-gray-900
        | 所属会社一覧
  main
    .max-w-7xl.mx-auto.sm:px-6.lg:px-8
      .px-4.py-8.sm:px-0
        .border-4.border-dashed.border-gray-200.rounded-lg.h-96
          .bg-white.shadow.sm:rounded-md.my-16.m-auto(class="w-3/4")
            ul
              - @orgs.each do |org|
                / TODO:Sassで場合分けできるようにする
                - border_t = org == @orgs.first ? '' : 'border-t border-gray-200'
                li(class=border_t)
                  = link_to orders_ordering_org_sides_path, class: 'block hover:bg-gray-100 focus:outline-none focus:bg-gray-100 transition duration-150 ease-in-out' do
                    .flex.items-baseline.px-4.py-4.sm:px-6
                      .min-w-0.flex-1.flex.items-baseline
                        .min-w-0.flex-1.px-4.md:grid.md:grid-cols-3.md:gap-4
                          div.align-middle
                            .text-xl.font-medium.text-indigo-600.truncate.leading-10
                              = org.name
                          div
                            .text-xs.text-gray-500
                              | 会社タイプ
                            .text-lg.leading-5.font-medium.text-gray-900.truncate
                              / TODO: ENUM化する
                              | 買付会社
                          div
                            .text-xs.text-gray-500
                              | オーナー
                            .text-lg.leading-5.font-medium.text-gray-900.truncate
                              / TODO: Userモデルの紐付けをしたら、呼び出すようにすする
                              | 山田 太郎
                      div
                        i.fas.fa-sign-in-alt.fa-lg.text-gray-600
  • 注文画面修正
app/views/orders/ordering_org_sides/index.html.slim
.bg-gray-100
  = render(::Layout::NavbarComponent.new(org: @org, tab: true, active: :order_ordering_org_sides))
  / ...略
  • 会社の詳細画面修正
app/views/orgs/show.html.slim
= render(::Layout::NavbarComponent.new(org: @org, tab: true))
.bg-white.shadow.overflow-hidden.m-auto.mt-16.sm:rounded-lg(class="w-1/2")
  .px-4.py-5.border-b.border-gray-200.sm:px-6
    h3.text-lg.leading-6.font-medium.text-gray-900
      |  会社詳細
    p.mt-1.max-w-2xl.text-sm.leading-5.text-gray-500
      |  会社の詳細を説明します
  .px-4.py-5.sm:p-0
    dl
    .sm:grid.sm:grid-cols-3.sm:gap-4.sm:px-6.sm:py-5
      dt.text-sm.leading-5.font-medium.text-gray-500
        |  会社名
      dd.mt-1.text-sm.leading-5.text-gray-900.sm:mt-0.sm:col-span-2
        = @org.name
    .mt-8.sm:mt-0.sm:grid.sm:grid-cols-3.sm:gap-4.sm:border-t.sm:border-gray-200.sm:px-6.sm:py-5
      dt.text-sm.leading-5.font-medium.text-gray-500
        | 会社タイプ
      dd.mt-1.text-sm.leading-5.text-gray-900.sm:mt-0.sm:col-span-2
        / TODO:enum化する
        | 買付会社
    .mt-8.sm:mt-0.sm:grid.sm:grid-cols-3.sm:gap-4.sm:border-t.sm:border-gray-200.sm:px-6.sm:py-5
      dt.text-sm.leading-5.font-medium.text-gray-500
        | オーナー
      dd.mt-1.text-sm.leading-5.text-gray-900.sm:mt-0.sm:col-span-2
        | 山田太郎

動作確認

準備

bin/rails db:migrate:reset
bin/rails db:reset

受入基準

  • デザインがFigmaぽくなっている
  • 画面遷移が下図のようになっている。会社の切替後は注文一覧画面に行くようになっている 9ab474f7beae054adcf79949e0375703 # ドロップダウンのJSは後日実装予定
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

時刻表記にlメソッドを使う

経緯

店舗の運営時間を表示したくて調べていたところstrftimeというものを使うことで時刻の表記を指定できることがわかった。

view
 <%= @laundry.open_time.strftime('%-H:%M')%> 〜 <%= @laundry.close_time.strftime('%-H:%M')%> 

これで07:00 ~ 23:00といった表記ができるようになった。
しかしどうもstrftime('%H:%M')が2回繰り返されていることが気になり他の方法を調べていたところ、lメソッド(エルメソッド)を発見。上記の書き方からlメソッドでの書き方に変更をした過程を記録しておく。

lメソッドを使うまでの流れ

1 タイムゾーンを設定(strftimeを使用した時点で設定は済んでいたが今後見返せるように記述しておく)

lメソッドを使用する前にタイムゾーンを設定して日本時間で表示されるようにする必要がある(設定をしていない場合はUTC(世界協定時)になっており日本時間とは9時間の差がある)
config/application.rbに以下のように記述を行う。

config/application.rb
 config.time_zone = 'Tokyo'

2 デフォルトの言語をjaに変更、ymlファイルの作成

1と同じファイル内に以下のように記述をすることで言語を日本語に設定する。

config/application.rb
config.i18n.default_locale = :ja

ファイルはconfig/localesja.ymlという名前で作成する。(default_localeの後に続く国名(日本の場合はja)とconfig/localesの〇〇.yml)の〇〇を一致させることでファイルを読み込んでくれる)

3 ja.ymlにフォーマットを作成する

ja.yml
ja:
  time:
    formats:
      default: '%-H:%M'

default:の後の記述を変えることで好みの時間表記にすることができる。

4 ビューファイルに3で設定した時刻を表示
ここでlメソッドが登場する。lメソッドを使うことでymlファイルで指定したフォーマットを利用して時刻を表示させる。
使い方としては頭にlをつける。

view
<%= l @laundry.open_time %> 〜 <%= l @laundry.close_time %>

これでstrftimeをビューファイルに直書きすることなく好みを時刻表記を表示することができる。

view(strftime直書き)
 <%= @laundry.open_time.strftime('%-H:%M')%> 〜 <%= @laundry.close_time.strftime('%-H:%M')%> 

view(lメソッド使用)
<%= l @laundry.open_time %> 〜 <%= l @laundry.close_time %>

lメソッドを使いすっきりとした見た目に変更!!
ちなみにフォーマットを複数用意して使い分け流ことも可能(下記参考記事参照)

参考記事

https://qiita.com/jnchito/items/831654253fb8a958ec25

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

Failure/Error: require File.expand_path('../config/environment', __dir__)の対処法

何が起こったか

rspecコマンドでテストを実行したら,今までなんの問題もなく実行できていたのに突如タイトルのようなエラーが起きました
結果的にbundle updateのせいとわかりました.何気なくbundle updateをしていた身としては「何もしてないのにエラー出た」な気分でした

以下rspecコマンドを実行した時に出力された結果です

$ rspec spec/models/task_spec.rb 

 An error occurred while loading ./spec/models/task_spec.rb.
 Hint: Install the `did_you_mean` gem in order to provide suggestions for similarly named files.
 Failure/Error: require File.expand_path('../config/environment', __dir__)

 LoadError:
   cannot load such file -- public_suffix
 # ./config/application.rb:7:in `<top (required)>'
 # ./config/environment.rb:2:in `require_relative'
 # ./config/environment.rb:2:in `<top (required)>'
 # ./spec/rails_helper.rb:5:in `<top (required)>'
 # ./spec/models/task_spec.rb:1:in `<top (required)>'
 No examples found.

 Finished in 0.00005 seconds (files took 1.39 seconds to load)
 0 examples, 0 failures, 1 error occurred outside of examples

解決方法

先に解決方法を書いていきます
このエラーの問題はライブラリのバージョンです
cannot load such file -- public_suffixとあるようにpublic_suffixというライブラリがバージョンの影響なのか読み込めていません
私の場合はエラー時点でpublic_suffix (4.0.6)でした
エラーが出ていない前コミットを見るとpublic_suffix (4.0.5)となっていたので,Gemfile.lockからpublic_suffix (4.0.6)public_suffix (4.0.5)へと変更してbundle installを実行

もう一度rspecでテストを実行すると

$ rspec spec/models/task_spec.rb 

 An error occurred while loading ./spec/models/task_spec.rb.
 Hint: Install the `did_you_mean` gem in order to provide suggestions for similarly named files.
 Failure/Error: require 'rspec/rails'

 LoadError:
   cannot load such file -- minitest/assertions
 # ./spec/rails_helper.rb:8:in `<top (required)>'
 # ./spec/models/task_spec.rb:1:in `<top (required)>'
 No examples found.

 Finished in 0.00013 seconds (files took 2.54 seconds to load)
 0 examples, 0 failures, 1 error occurred outside of examples

と,またエラーが出ましたがcannot load such file -- minitest/assertionsとminitestのバージョンをpublic_suffixと同様に直してあげます
私の場合はminitest (5.14.2)minitest (5.14.1)にしました
またbundle installしてrspecを実行すると

$ rspec spec/models/task_spec.rb 

 Task
   ほげ2

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

通った!直った:joy:

原因・解決のためにやったこと

以下,何故このエラーが起こったのか,どんな検索をして解決しようとしたのか書いていきます

問題の本質はFailure/Error: require File.expand_path('../config/environment', __dir__)ではなく,cannot load such file -- public_suffixです
public_suffixというライブラリがあるのですが,それが読み込まれていないことが問題でした
ただGemfile.lockを見るとインストールされています.なのでバージョン違いか?という考えに行きついて解決できました

当初はエラーだと思って検索したのがFailure/Error: require File.expand_path('../config/environment', __dir__)でした
そのためタイトルにも盛り込んでいます.しかし,検索してもしても問題は解決できず,rspec側の問題では無いということしか分かりませんでした

rails newで初期プロジェクトから改めてrspecを導入してもエラーは消えなかったので,このことからもrspecの問題では無いことが明らかでした

6時間くらい格闘してFailure/Error: require File.expand_path('../config/environment', __dir__)は本質的な問題の副次作用だと気がついたのでLoadError: cannot load such file -- public_suffixがエラーの本質なのだと気がつきました
public_suffixがライブラリだと知らなかったのでそう言うエラーなのかと思ってたのも解決に時間がかかった理由です
あとはGemfile.lockをpublic_suffixで検索して,前コミットと見比べてバージョンが違うことに気が付いたので戻してあげたら直ったのが事の顛末です

ちなみにFailure/Error: require File.expand_path('../config/environment', __dir__)で検索しても解決には至らなかったので,同じエラーが出た人のためにこの記事を書きました

教訓

無闇なbundle updateは辞めましょう!
エラーの原因はライブラリのバージョン変化でしたのでいきなりエラーが出たらバージョンを疑いましょう
そもそも,bundle周りの挙動がわからず,なんとなくで使っていたのが一番のダメ要素でした
bundleで痛い目見たのでここの記事で勉強しました
https://qiita.com/lasershow/items/1a048d03ddaaba98171e

付録

public_suffixとminitestのバージョンを直して,rspecが動作するようになったGemfileとGemfile.lockです

Gemfile

ruby '2.6.5'
 gem 'bootstrap', '4.5.2'
 gem 'slim-rails', '3.2.0'
 gem 'html2slim', '0.2.0'
 gem 'enum_help', '0.0.17'

 gem 'rails', '~> 6.0.1'
 gem 'pg', '>= 0.18', '< 2.0'
 gem 'puma', '~> 4.1'
 gem 'sass-rails', '>= 6'
 gem 'webpacker', '~> 4.0'
 gem 'turbolinks', '~> 5'
 gem 'jbuilder', '~> 2.7'
 gem 'bootsnap', '>= 1.4.2'

 group :development, :test do
   gem 'byebug', platforms: [:mri, :mingw, :x64_mingw]
 end

 group :development do
   gem 'web-console', '>= 3.3.0'
   gem 'listen', '>= 3.0.5', '< 3.2'
   gem 'spring'
   gem 'spring-watcher-listen', '~> 2.0.0'
 end

 group :test do
   gem 'capybara', '>= 2.15'
   gem 'selenium-webdriver'
   gem 'webdrivers'
   gem 'rspec-rails', '< 4.0.0'
 end

 gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby]

Gemfile.lock

GEM
  remote: https://rubygems.org/
  specs:
    actioncable (6.0.3.4)
      actionpack (= 6.0.3.4)
      nio4r (~> 2.0)
      websocket-driver (>= 0.6.1)
    actionmailbox (6.0.3.4)
      actionpack (= 6.0.3.4)
      activejob (= 6.0.3.4)
      activerecord (= 6.0.3.4)
      activestorage (= 6.0.3.4)
      activesupport (= 6.0.3.4)
      mail (>= 2.7.1)
    actionmailer (6.0.3.4)
      actionpack (= 6.0.3.4)
      actionview (= 6.0.3.4)
      activejob (= 6.0.3.4)
      mail (~> 2.5, >= 2.5.4)
      rails-dom-testing (~> 2.0)
    actionpack (6.0.3.4)
      actionview (= 6.0.3.4)
      activesupport (= 6.0.3.4)
      rack (~> 2.0, >= 2.0.8)
      rack-test (>= 0.6.3)
      rails-dom-testing (~> 2.0)
      rails-html-sanitizer (~> 1.0, >= 1.2.0)
    actiontext (6.0.3.4)
      actionpack (= 6.0.3.4)
      activerecord (= 6.0.3.4)
      activestorage (= 6.0.3.4)
      activesupport (= 6.0.3.4)
      nokogiri (>= 1.8.5)
    actionview (6.0.3.4)
      activesupport (= 6.0.3.4)
      builder (~> 3.1)
      erubi (~> 1.4)
      rails-dom-testing (~> 2.0)
      rails-html-sanitizer (~> 1.1, >= 1.2.0)
    activejob (6.0.3.4)
      activesupport (= 6.0.3.4)
      globalid (>= 0.3.6)
    activemodel (6.0.3.4)
      activesupport (= 6.0.3.4)
    activerecord (6.0.3.4)
      activemodel (= 6.0.3.4)
      activesupport (= 6.0.3.4)
    activestorage (6.0.3.4)
      actionpack (= 6.0.3.4)
      activejob (= 6.0.3.4)
      activerecord (= 6.0.3.4)
      marcel (~> 0.3.1)
    activesupport (6.0.3.4)
      concurrent-ruby (~> 1.0, >= 1.0.2)
      i18n (>= 0.7, < 2)
      minitest (~> 5.1)
      tzinfo (~> 1.1)
      zeitwerk (~> 2.2, >= 2.2.2)
    addressable (2.7.0)
      public_suffix (>= 2.0.2, < 5.0)
    autoprefixer-rails (10.0.1.0)
      execjs
    bindex (0.8.1)
    bootsnap (1.4.8)
      msgpack (~> 1.0)
    bootstrap (4.5.2)
      autoprefixer-rails (>= 9.1.0)
      popper_js (>= 1.14.3, < 2)
      sassc-rails (>= 2.0.0)
    builder (3.2.4)
    byebug (11.1.3)
    capybara (3.33.0)
      addressable
      mini_mime (>= 0.1.3)
      nokogiri (~> 1.8)
      rack (>= 1.6.0)
      rack-test (>= 0.6.3)
      regexp_parser (~> 1.5)
      xpath (~> 3.2)
    childprocess (3.0.0)
    concurrent-ruby (1.1.7)
    crass (1.0.6)
    diff-lcs (1.4.4)
    enum_help (0.0.17)
      activesupport (>= 3.0.0)
    erubi (1.9.0)
    execjs (2.7.0)
    ffi (1.13.1)
    globalid (0.4.2)
      activesupport (>= 4.2.0)
    hpricot (0.8.6)
    html2slim (0.2.0)
      hpricot
    i18n (1.8.5)
      concurrent-ruby (~> 1.0)
    jbuilder (2.10.1)
      activesupport (>= 5.0.0)
    listen (3.1.5)
      rb-fsevent (~> 0.9, >= 0.9.4)
      rb-inotify (~> 0.9, >= 0.9.7)
      ruby_dep (~> 1.2)
    loofah (2.7.0)
      crass (~> 1.0.2)
      nokogiri (>= 1.5.9)
    mail (2.7.1)
      mini_mime (>= 0.1.1)
    marcel (0.3.3)
      mimemagic (~> 0.3.2)
    method_source (1.0.0)
    mimemagic (0.3.5)
    mini_mime (1.0.2)
    mini_portile2 (2.4.0)
    minitest (5.14.1)
    msgpack (1.3.3)
    nio4r (2.5.4)
    nokogiri (1.10.10)
      mini_portile2 (~> 2.4.0)
    pg (1.2.3)
    popper_js (1.16.0)
    public_suffix (4.0.5)
    puma (4.3.6)
      nio4r (~> 2.0)
    rack (2.2.3)
    rack-proxy (0.6.5)
      rack
    rack-test (1.1.0)
      rack (>= 1.0, < 3)
    rails (6.0.3.4)
      actioncable (= 6.0.3.4)
      actionmailbox (= 6.0.3.4)
      actionmailer (= 6.0.3.4)
      actionpack (= 6.0.3.4)
      actiontext (= 6.0.3.4)
      actionview (= 6.0.3.4)
      activejob (= 6.0.3.4)
      activemodel (= 6.0.3.4)
      activerecord (= 6.0.3.4)
      activestorage (= 6.0.3.4)
      activesupport (= 6.0.3.4)
      bundler (>= 1.3.0)
      railties (= 6.0.3.4)
      sprockets-rails (>= 2.0.0)
    rails-dom-testing (2.0.3)
      activesupport (>= 4.2.0)
      nokogiri (>= 1.6)
    rails-html-sanitizer (1.3.0)
      loofah (~> 2.3)
    railties (6.0.3.4)
      actionpack (= 6.0.3.4)
      activesupport (= 6.0.3.4)
      method_source
      rake (>= 0.8.7)
      thor (>= 0.20.3, < 2.0)
    rake (13.0.1)
    rb-fsevent (0.10.4)
    rb-inotify (0.10.1)
      ffi (~> 1.0)
    regexp_parser (1.8.1)
    rspec-core (3.9.3)
      rspec-support (~> 3.9.3)
    rspec-expectations (3.9.2)
      diff-lcs (>= 1.2.0, < 2.0)
      rspec-support (~> 3.9.0)
    rspec-mocks (3.9.1)
      diff-lcs (>= 1.2.0, < 2.0)
      rspec-support (~> 3.9.0)
    rspec-rails (3.9.1)
      actionpack (>= 3.0)
      activesupport (>= 3.0)
      railties (>= 3.0)
      rspec-core (~> 3.9.0)
      rspec-expectations (~> 3.9.0)
      rspec-mocks (~> 3.9.0)
      rspec-support (~> 3.9.0)
    rspec-support (3.9.3)
    ruby_dep (1.5.0)
    rubyzip (2.3.0)
    sass-rails (6.0.0)
      sassc-rails (~> 2.1, >= 2.1.1)
    sassc (2.4.0)
      ffi (~> 1.9)
    sassc-rails (2.1.2)
      railties (>= 4.0.0)
      sassc (>= 2.0)
      sprockets (> 3.0)
      sprockets-rails
      tilt
    selenium-webdriver (3.142.7)
      childprocess (>= 0.5, < 4.0)
      rubyzip (>= 1.2.2)
    slim (4.1.0)
      temple (>= 0.7.6, < 0.9)
      tilt (>= 2.0.6, < 2.1)
    slim-rails (3.2.0)
      actionpack (>= 3.1)
      railties (>= 3.1)
      slim (>= 3.0, < 5.0)
    spring (2.1.1)
    spring-watcher-listen (2.0.1)
      listen (>= 2.7, < 4.0)
      spring (>= 1.2, < 3.0)
    sprockets (4.0.2)
      concurrent-ruby (~> 1.0)
      rack (> 1, < 3)
    sprockets-rails (3.2.2)
      actionpack (>= 4.0)
      activesupport (>= 4.0)
      sprockets (>= 3.0.0)
    temple (0.8.2)
    thor (1.0.1)
    thread_safe (0.3.6)
    tilt (2.0.10)
    turbolinks (5.2.1)
      turbolinks-source (~> 5.2)
    turbolinks-source (5.2.0)
    tzinfo (1.2.7)
      thread_safe (~> 0.1)
    web-console (4.0.4)
      actionview (>= 6.0.0)
      activemodel (>= 6.0.0)
      bindex (>= 0.4.0)
      railties (>= 6.0.0)
    webdrivers (4.4.1)
      nokogiri (~> 1.6)
      rubyzip (>= 1.3.0)
      selenium-webdriver (>= 3.0, < 4.0)
    webpacker (4.3.0)
      activesupport (>= 4.2)
      rack-proxy (>= 0.6.1)
      railties (>= 4.2)
    websocket-driver (0.7.3)
      websocket-extensions (>= 0.1.0)
    websocket-extensions (0.1.5)
    xpath (3.2.0)
      nokogiri (~> 1.8)
    zeitwerk (2.4.0)

PLATFORMS
  ruby

DEPENDENCIES
  bootsnap (>= 1.4.2)
  bootstrap (= 4.5.2)
  byebug
  capybara (>= 2.15)
  enum_help (= 0.0.17)
  html2slim (= 0.2.0)
  jbuilder (~> 2.7)
  listen (>= 3.0.5, < 3.2)
  pg (>= 0.18, < 2.0)
  puma (~> 4.1)
  rails (~> 6.0.1)
  rspec-rails (< 4.0.0)
  sass-rails (>= 6)
  selenium-webdriver
  slim-rails (= 3.2.0)
  spring
  spring-watcher-listen (~> 2.0.0)
  turbolinks (~> 5)
  tzinfo-data
  web-console (>= 3.3.0)
  webdrivers
  webpacker (~> 4.0)

RUBY VERSION
   ruby 2.6.5p114

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

RSpecでFailure/Error: require File.expand_path('../config/environment', __dir__)が出たときの対処法

何が起こったか

rspecコマンドでテストを実行したら,今までなんの問題もなく実行できていたのに突如タイトルのようなエラーが起きました
結果的にbundle updateのせいとわかりました.何気なくbundle updateをしていた身としては「何もしてないのにエラー出た」な気分でした

以下rspecコマンドを実行した時に出力された結果です

$ rspec spec/models/task_spec.rb 

 An error occurred while loading ./spec/models/task_spec.rb.
 Hint: Install the `did_you_mean` gem in order to provide suggestions for similarly named files.
 Failure/Error: require File.expand_path('../config/environment', __dir__)

 LoadError:
   cannot load such file -- public_suffix
 # ./config/application.rb:7:in `<top (required)>'
 # ./config/environment.rb:2:in `require_relative'
 # ./config/environment.rb:2:in `<top (required)>'
 # ./spec/rails_helper.rb:5:in `<top (required)>'
 # ./spec/models/task_spec.rb:1:in `<top (required)>'
 No examples found.

 Finished in 0.00005 seconds (files took 1.39 seconds to load)
 0 examples, 0 failures, 1 error occurred outside of examples

解決方法

先に解決方法を書いていきます
このエラーの問題はライブラリのバージョンです
cannot load such file -- public_suffixとあるようにpublic_suffixというライブラリがバージョンの影響なのか読み込めていません
私の場合はエラー時点でpublic_suffix (4.0.6)でした
エラーが出ていない前コミットを見るとpublic_suffix (4.0.5)となっていたので,Gemfile.lockからpublic_suffix (4.0.6)public_suffix (4.0.5)へと変更してbundle installを実行

もう一度rspecでテストを実行すると

$ rspec spec/models/task_spec.rb 

 An error occurred while loading ./spec/models/task_spec.rb.
 Hint: Install the `did_you_mean` gem in order to provide suggestions for similarly named files.
 Failure/Error: require 'rspec/rails'

 LoadError:
   cannot load such file -- minitest/assertions
 # ./spec/rails_helper.rb:8:in `<top (required)>'
 # ./spec/models/task_spec.rb:1:in `<top (required)>'
 No examples found.

 Finished in 0.00013 seconds (files took 2.54 seconds to load)
 0 examples, 0 failures, 1 error occurred outside of examples

と,またエラーが出ましたがcannot load such file -- minitest/assertionsとminitestのバージョンをpublic_suffixと同様に直してあげます
私の場合はminitest (5.14.2)minitest (5.14.1)にしました
またbundle installしてrspecを実行すると

$ rspec spec/models/task_spec.rb 

 Task
   ほげ2

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

通った!直った:joy:

原因・解決のためにやったこと

以下,何故このエラーが起こったのか,どんな検索をして解決しようとしたのか書いていきます

問題の本質はFailure/Error: require File.expand_path('../config/environment', __dir__)ではなく,cannot load such file -- public_suffixです
public_suffixというライブラリがあるのですが,それが読み込まれていないことが問題でした
ただGemfile.lockを見るとインストールされています.なのでバージョン違いか?という考えに行きついて解決できました

当初はエラーだと思って検索したのがFailure/Error: require File.expand_path('../config/environment', __dir__)でした
そのためタイトルにも盛り込んでいます.しかし,検索してもしても問題は解決できず,rspec側の問題では無いということしか分かりませんでした

rails newで初期プロジェクトから改めてrspecを導入してもエラーは消えなかったので,このことからもrspecの問題では無いことが明らかでした

6時間くらい格闘してFailure/Error: require File.expand_path('../config/environment', __dir__)は本質的な問題の副次作用だと気がついたのでLoadError: cannot load such file -- public_suffixがエラーの本質なのだと気がつきました
public_suffixがライブラリだと知らなかったのでそう言うエラーなのかと思ってたのも解決に時間がかかった理由です
あとはGemfile.lockをpublic_suffixで検索して,前コミットと見比べてバージョンが違うことに気が付いたので戻してあげたら直ったのが事の顛末です

ちなみにFailure/Error: require File.expand_path('../config/environment', __dir__)で検索しても解決には至らなかったので,同じエラーが出た人のためにこの記事を書きました

教訓

無闇なbundle updateは辞めましょう!
エラーの原因はライブラリのバージョン変化でしたのでいきなりエラーが出たらバージョンを疑いましょう
そもそも,bundle周りの挙動がわからず,なんとなくで使っていたのが一番のダメ要素でした
bundleで痛い目見たのでここの記事で勉強しました
https://qiita.com/lasershow/items/1a048d03ddaaba98171e

付録

public_suffixとminitestのバージョンを直して,rspecが動作するようになったGemfileとGemfile.lockです

Gemfile

ruby '2.6.5'
 gem 'bootstrap', '4.5.2'
 gem 'slim-rails', '3.2.0'
 gem 'html2slim', '0.2.0'
 gem 'enum_help', '0.0.17'

 gem 'rails', '~> 6.0.1'
 gem 'pg', '>= 0.18', '< 2.0'
 gem 'puma', '~> 4.1'
 gem 'sass-rails', '>= 6'
 gem 'webpacker', '~> 4.0'
 gem 'turbolinks', '~> 5'
 gem 'jbuilder', '~> 2.7'
 gem 'bootsnap', '>= 1.4.2'

 group :development, :test do
   gem 'byebug', platforms: [:mri, :mingw, :x64_mingw]
 end

 group :development do
   gem 'web-console', '>= 3.3.0'
   gem 'listen', '>= 3.0.5', '< 3.2'
   gem 'spring'
   gem 'spring-watcher-listen', '~> 2.0.0'
 end

 group :test do
   gem 'capybara', '>= 2.15'
   gem 'selenium-webdriver'
   gem 'webdrivers'
   gem 'rspec-rails', '< 4.0.0'
 end

 gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby]

Gemfile.lock

GEM
  remote: https://rubygems.org/
  specs:
    actioncable (6.0.3.4)
      actionpack (= 6.0.3.4)
      nio4r (~> 2.0)
      websocket-driver (>= 0.6.1)
    actionmailbox (6.0.3.4)
      actionpack (= 6.0.3.4)
      activejob (= 6.0.3.4)
      activerecord (= 6.0.3.4)
      activestorage (= 6.0.3.4)
      activesupport (= 6.0.3.4)
      mail (>= 2.7.1)
    actionmailer (6.0.3.4)
      actionpack (= 6.0.3.4)
      actionview (= 6.0.3.4)
      activejob (= 6.0.3.4)
      mail (~> 2.5, >= 2.5.4)
      rails-dom-testing (~> 2.0)
    actionpack (6.0.3.4)
      actionview (= 6.0.3.4)
      activesupport (= 6.0.3.4)
      rack (~> 2.0, >= 2.0.8)
      rack-test (>= 0.6.3)
      rails-dom-testing (~> 2.0)
      rails-html-sanitizer (~> 1.0, >= 1.2.0)
    actiontext (6.0.3.4)
      actionpack (= 6.0.3.4)
      activerecord (= 6.0.3.4)
      activestorage (= 6.0.3.4)
      activesupport (= 6.0.3.4)
      nokogiri (>= 1.8.5)
    actionview (6.0.3.4)
      activesupport (= 6.0.3.4)
      builder (~> 3.1)
      erubi (~> 1.4)
      rails-dom-testing (~> 2.0)
      rails-html-sanitizer (~> 1.1, >= 1.2.0)
    activejob (6.0.3.4)
      activesupport (= 6.0.3.4)
      globalid (>= 0.3.6)
    activemodel (6.0.3.4)
      activesupport (= 6.0.3.4)
    activerecord (6.0.3.4)
      activemodel (= 6.0.3.4)
      activesupport (= 6.0.3.4)
    activestorage (6.0.3.4)
      actionpack (= 6.0.3.4)
      activejob (= 6.0.3.4)
      activerecord (= 6.0.3.4)
      marcel (~> 0.3.1)
    activesupport (6.0.3.4)
      concurrent-ruby (~> 1.0, >= 1.0.2)
      i18n (>= 0.7, < 2)
      minitest (~> 5.1)
      tzinfo (~> 1.1)
      zeitwerk (~> 2.2, >= 2.2.2)
    addressable (2.7.0)
      public_suffix (>= 2.0.2, < 5.0)
    autoprefixer-rails (10.0.1.0)
      execjs
    bindex (0.8.1)
    bootsnap (1.4.8)
      msgpack (~> 1.0)
    bootstrap (4.5.2)
      autoprefixer-rails (>= 9.1.0)
      popper_js (>= 1.14.3, < 2)
      sassc-rails (>= 2.0.0)
    builder (3.2.4)
    byebug (11.1.3)
    capybara (3.33.0)
      addressable
      mini_mime (>= 0.1.3)
      nokogiri (~> 1.8)
      rack (>= 1.6.0)
      rack-test (>= 0.6.3)
      regexp_parser (~> 1.5)
      xpath (~> 3.2)
    childprocess (3.0.0)
    concurrent-ruby (1.1.7)
    crass (1.0.6)
    diff-lcs (1.4.4)
    enum_help (0.0.17)
      activesupport (>= 3.0.0)
    erubi (1.9.0)
    execjs (2.7.0)
    ffi (1.13.1)
    globalid (0.4.2)
      activesupport (>= 4.2.0)
    hpricot (0.8.6)
    html2slim (0.2.0)
      hpricot
    i18n (1.8.5)
      concurrent-ruby (~> 1.0)
    jbuilder (2.10.1)
      activesupport (>= 5.0.0)
    listen (3.1.5)
      rb-fsevent (~> 0.9, >= 0.9.4)
      rb-inotify (~> 0.9, >= 0.9.7)
      ruby_dep (~> 1.2)
    loofah (2.7.0)
      crass (~> 1.0.2)
      nokogiri (>= 1.5.9)
    mail (2.7.1)
      mini_mime (>= 0.1.1)
    marcel (0.3.3)
      mimemagic (~> 0.3.2)
    method_source (1.0.0)
    mimemagic (0.3.5)
    mini_mime (1.0.2)
    mini_portile2 (2.4.0)
    minitest (5.14.1)
    msgpack (1.3.3)
    nio4r (2.5.4)
    nokogiri (1.10.10)
      mini_portile2 (~> 2.4.0)
    pg (1.2.3)
    popper_js (1.16.0)
    public_suffix (4.0.5)
    puma (4.3.6)
      nio4r (~> 2.0)
    rack (2.2.3)
    rack-proxy (0.6.5)
      rack
    rack-test (1.1.0)
      rack (>= 1.0, < 3)
    rails (6.0.3.4)
      actioncable (= 6.0.3.4)
      actionmailbox (= 6.0.3.4)
      actionmailer (= 6.0.3.4)
      actionpack (= 6.0.3.4)
      actiontext (= 6.0.3.4)
      actionview (= 6.0.3.4)
      activejob (= 6.0.3.4)
      activemodel (= 6.0.3.4)
      activerecord (= 6.0.3.4)
      activestorage (= 6.0.3.4)
      activesupport (= 6.0.3.4)
      bundler (>= 1.3.0)
      railties (= 6.0.3.4)
      sprockets-rails (>= 2.0.0)
    rails-dom-testing (2.0.3)
      activesupport (>= 4.2.0)
      nokogiri (>= 1.6)
    rails-html-sanitizer (1.3.0)
      loofah (~> 2.3)
    railties (6.0.3.4)
      actionpack (= 6.0.3.4)
      activesupport (= 6.0.3.4)
      method_source
      rake (>= 0.8.7)
      thor (>= 0.20.3, < 2.0)
    rake (13.0.1)
    rb-fsevent (0.10.4)
    rb-inotify (0.10.1)
      ffi (~> 1.0)
    regexp_parser (1.8.1)
    rspec-core (3.9.3)
      rspec-support (~> 3.9.3)
    rspec-expectations (3.9.2)
      diff-lcs (>= 1.2.0, < 2.0)
      rspec-support (~> 3.9.0)
    rspec-mocks (3.9.1)
      diff-lcs (>= 1.2.0, < 2.0)
      rspec-support (~> 3.9.0)
    rspec-rails (3.9.1)
      actionpack (>= 3.0)
      activesupport (>= 3.0)
      railties (>= 3.0)
      rspec-core (~> 3.9.0)
      rspec-expectations (~> 3.9.0)
      rspec-mocks (~> 3.9.0)
      rspec-support (~> 3.9.0)
    rspec-support (3.9.3)
    ruby_dep (1.5.0)
    rubyzip (2.3.0)
    sass-rails (6.0.0)
      sassc-rails (~> 2.1, >= 2.1.1)
    sassc (2.4.0)
      ffi (~> 1.9)
    sassc-rails (2.1.2)
      railties (>= 4.0.0)
      sassc (>= 2.0)
      sprockets (> 3.0)
      sprockets-rails
      tilt
    selenium-webdriver (3.142.7)
      childprocess (>= 0.5, < 4.0)
      rubyzip (>= 1.2.2)
    slim (4.1.0)
      temple (>= 0.7.6, < 0.9)
      tilt (>= 2.0.6, < 2.1)
    slim-rails (3.2.0)
      actionpack (>= 3.1)
      railties (>= 3.1)
      slim (>= 3.0, < 5.0)
    spring (2.1.1)
    spring-watcher-listen (2.0.1)
      listen (>= 2.7, < 4.0)
      spring (>= 1.2, < 3.0)
    sprockets (4.0.2)
      concurrent-ruby (~> 1.0)
      rack (> 1, < 3)
    sprockets-rails (3.2.2)
      actionpack (>= 4.0)
      activesupport (>= 4.0)
      sprockets (>= 3.0.0)
    temple (0.8.2)
    thor (1.0.1)
    thread_safe (0.3.6)
    tilt (2.0.10)
    turbolinks (5.2.1)
      turbolinks-source (~> 5.2)
    turbolinks-source (5.2.0)
    tzinfo (1.2.7)
      thread_safe (~> 0.1)
    web-console (4.0.4)
      actionview (>= 6.0.0)
      activemodel (>= 6.0.0)
      bindex (>= 0.4.0)
      railties (>= 6.0.0)
    webdrivers (4.4.1)
      nokogiri (~> 1.6)
      rubyzip (>= 1.3.0)
      selenium-webdriver (>= 3.0, < 4.0)
    webpacker (4.3.0)
      activesupport (>= 4.2)
      rack-proxy (>= 0.6.1)
      railties (>= 4.2)
    websocket-driver (0.7.3)
      websocket-extensions (>= 0.1.0)
    websocket-extensions (0.1.5)
    xpath (3.2.0)
      nokogiri (~> 1.8)
    zeitwerk (2.4.0)

PLATFORMS
  ruby

DEPENDENCIES
  bootsnap (>= 1.4.2)
  bootstrap (= 4.5.2)
  byebug
  capybara (>= 2.15)
  enum_help (= 0.0.17)
  html2slim (= 0.2.0)
  jbuilder (~> 2.7)
  listen (>= 3.0.5, < 3.2)
  pg (>= 0.18, < 2.0)
  puma (~> 4.1)
  rails (~> 6.0.1)
  rspec-rails (< 4.0.0)
  sass-rails (>= 6)
  selenium-webdriver
  slim-rails (= 3.2.0)
  spring
  spring-watcher-listen (~> 2.0.0)
  turbolinks (~> 5)
  tzinfo-data
  web-console (>= 3.3.0)
  webdrivers
  webpacker (~> 4.0)

RUBY VERSION
   ruby 2.6.5p114

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

DockerでRails5、postgresqlの環境を作り、pgadminも使えるようにする

mysqlしかdockerで使ったことがなくて、時間がかかってしまったので、後で見れるように書きました。
pgadminも便利なので入れました。
docoker-compose upしたあとrails sしなくてもいいようにdocker-compose.ymlにcommandで自動でやってくれるように書きました。

普段はコンテナ再作成しなくてもよいと思うのでdocker-compose start / stopでやる想定です。

Dockerfile
FROM ruby:2.6

RUN apt-get update -y && \
    apt-get install  -y nodejs

COPY Gemfile /Gemfile
COPY Gemfile.lock /Gemfile.lock
RUN gem install bundler
RUN bundle install
docker-compose.yml
version: "3"
services:
  db:
    image: postgres
    ports:
      - 5432:5432
    environment:
      POSTGRES_USER: root
      POSTGRES_PASSWORD: root
    volumes:
      - "./postgres-data:/var/lib/postgresql/data"
  pgadmin4:
    image: dpage/pgadmin4:4.2
    ports:
      - 80:80
    volumes:
      - ./docker/pgadmin4:/var/lib/pgadmin
    environment:
      PGADMIN_DEFAULT_EMAIL: root
      PGADMIN_DEFAULT_PASSWORD: root
    depends_on:
      - db
  web:
    build: .
    volumes:
      - ".:/app"
    ports:
      - "3000:3000"
    tty: true
    depends_on:
      - db
    working_dir: "/app"
    command: "rails s -b 0.0.0.0"

mysqlとdatabase.ymlが違っていて、ここらへんを書くところをdevelopとかに書いてしまっていてハマりました。ちゃんとdefaultに書かなくてはいけませんでした。

config/database.yml
default: &default
  adapter: postgresql
  encoding: unicode
  # For details on connection pooling, see Rails configuration guide
  # http://guides.rubyonrails.org/configuring.html#database-pooling
  pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
  # ここらへん
  username: root
  password: root
  host: db

参考サイト
pgadmin4/dockerの接続サーバー設定の永続化 - Qiita

rails5のDockerfileとdocker-compose.ymlを書く - Qiita

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

【Rails】ActiveStorageを用いた画像複数枚投稿のエラー

エラー【undefined method `to_model'】

フリマアプリの開発中、以下のようなエラーが出ました。
Can't resolve image into URL: undefined methodto_model' for #<ActiveStorage::Attached::Many:0x00007fb7ffa59fb0>
Did you mean? to_yaml

スクリーンショット 2020-10-10 11.25.10.png

現状

  • ActiveStorageというGemをを用いて画像保存を可能にしている。
  • 1つの商品につき複数の画像の投稿を可能にした。←今ここ

エラーの内容

to_modelというメソッドは定義されていないよーって言われてます。

以下のように、保存した画像を表示させたい時にエラーがでました。
スクリーンショット 2020-10-10 12.00.19.png

問題があったコード

<%= image_tag @item.images, class: 'buy-item-img' %>

解決したコード

<%= image_tag @item.images[0], class: 'buy-item-img' %>

一つの商品に複数枚画像があるため、どの画像を表示させるかを記述する必要があります。
そうじゃなかったらどの画像を表示するのか判断できないですもんね?

他の方の記事などを見てみると、
@item.images.urlと記述すれば解決することもあったそうです。
参考になれば、と思います!

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

[初心者でもできた]RailsアプリにDockerを導入する方法!

こんにちは。「Gitって何???」って状態から独学でプログラミングを勉強し始め、今はRails API × Nuxt SPA × Firebase AuthでTodoアプリを作成し終えたところです。

そこにDockerを導入したので今回はその導入方法をご紹介します。

はじめに

Dockerを導入したいと思うようになったきっかけの記事
Vue.jsとRails APIモードでストレスフリーに少人数開発できた話
自分でもDockerを導入できれば今後チーム開発で役立つと確信した。

とりあえずDocker試す

で、なにから始めればいいの?すでにアプリ完成してるんだけど、、、
既存のRailsアプリにDockerを導入できるのかな、、、

まず試したのがこの記事。
丁寧すぎるDocker-composeによるrails5 + MySQL on Dockerの環境構築(Docker for Mac)
10分くらいでできたのでDockerって意外と簡単かもって少し思えました。

では、実際にRailsアプリにDockerを導入していく。

Docker導入

まずはインストール
docker for mac

~$ docker -v
Docker version 19.03.12, build 48a66213fe
~$ docker-compose -v
docker-compose version 1.26.2, build eefe0d31

Dockerを導入したいRailsアプリに移動

~ $ cd backend

Dockerファイルを作成

以下を参考
既存のRailsアプリにDockerを導入する手順

~/backend $ vi Dockerfile
FROM ruby:2.7.1
RUN apt-get update && apt-get install -y nodejs --no-install-recommends && rm -rf /var/lib/apt/lists/*
RUN apt-get update && apt-get install -y postgresql-client --no-install-recommends && rm -rf /var/lib/apt/lists/*
RUN apt-get update -qq && apt-get install -y build-essential libpq-dev nodejs

WORKDIR /myproject

ADD Gemfile /myproject/Gemfile
ADD Gemfile.lock /myproject/Gemfile.lock

RUN gem install bundler
RUN bundle install

ADD . /myproject

?FROMでどのDockerイメージからコンテナを生成するか記述
使用できるコンテナイメージはdockerhubから確認します。
今回はrails2.7.1をインストールしました
(Dockerfileで指定するバージョンは、プロジェクトのRubyバージョンに合わせておく必要があります)

?RUNでコマンド実行
mysqlだとエラーが出すぎて嫌になったので今回はpostgresqlを使用

?WORKDIRで各種命令実行時のカレントディレクトリを指定

?ファイル / ディレクトリの追加

docker-compose.yml作成

~/backend $ vi docker-compose.yml
docker-compose.yml
#docker-composeのバージョン
version: '3'
services:
  db:
    image: postgres
    ports:
      - '5432:5432' # ホストからゲストへポートフォワード
    volumes:
      - postgresql-data:/var/lib/postgresql/data
    environment:
      - POSTGRES_PASSWORD=password
  web:
    build:
      context: .
      dockerfile: Dockerfile
    command: bundle exec rails s -p 3000 -b '0.0.0.0'
    tty: true
    stdin_open: true
    depends_on:
      - db # DB側のコンテナが出来上がってからwebを実行する
    ports:
      - "3000:3000" # ホストからゲストへポートフォワード
    volumes:
      - .:/myproject # ソースコード変更したらDocker側も即反映されるように
volumes:
  postgresql-data:
    driver: local

?僕はPostgreSQL(Docker)にRails(Docker)が接続できなくなったのでpostgresコンテナに環境変数POSTGRES_PASSWORDを設定して、その値をconfig/database.ymlのpasswordに定義しました。

参考
PostgreSQL(Docker)にRails(Docker)が接続できなくなったから調べてみた。(could not translate host name "db" to address: Name or service not known)

database.yml編集

database.yml
default: &default
  adapter: postgresql
  encoding: unicode
  host: db
  username: postgres
  password: password
  pool: 5

development:
  <<: *default
  database: myproject_development

test:
  <<: *default
  database: myproject_test

?アンカー: 名前をつける機能

&default

?エイリアス: 名前をつけたものを呼び出す

*default

?ハッシュのマージ: 一つにまとめる

<<: *default
database.yml
default: &default #ここで共通部分にアンカーで名前をつけておくと
  adapter: postgresql
  encoding: unicode
  host: db
  username: postgres
  password: password
  pool: 5

development:
  <<: *default #ここでdefaultとして呼び出し、スッキリ書ける
  database: myproject_development

test:
  <<: *default #ここでdefaultとして呼び出し、スッキリ書ける
  database: myproject_test

参考
【YAML】Railsのdatabase.ymlについてなんとなく分かった気になっていた記法・意味まとめ

build

サービスのビルドを実行します。
サービスとは「web」や「db」のことを指します。ymlファイルにimage:が書かれている場合はそのイメージ名がローカルになければ、リモートからプルしてきます。imageが書かれていない場合、buildに書かれているパスの(デフォルトは)Dockerfileを参考にしてイメージを構築します。

~/backend $ docker-compose build
~/backend $ docker-compose run web rake db:create db:migrate
~/backend $ docker-compose up

これで localhost:3000 にアクセスしたら確認できます!

僕は仕事終わりに試してはエラーがでて、、、を繰り返しなんだかんだ導入に一週間は使いました笑

最後に

今回、未経験からWeb系エンジニアに転職したく勉強しました。
Dockerのメリットなど知らないことが多いので、これを機にもっと勉強します。
1月からの入社を目指してがんばるぞ〜

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

with_optionの使い方

with_optionとは

同じオプションを使った記述が複数存在している場合、
それらを一つにまとめることができるもの。
記述量を少なくできることが利点。

使い方

例 app/models/item.rbファイルに、以下の同じオプションを使っているコードが複数あるとする。

validates :category_id, numericality: { other_than: 1 }
validates :days_to_send_id, numericality: { other_than: 1 }
validates :item_condition_id, numericality: { other_than: 1 }
validates :prace_id, numericality: { other_than: 1 }
validates :fee_id, numericality: { other_than: 1 }

この時、with_optionを使えば、以下のように記述量を少なく、みやすいコードにすることができる。

with_option numericality: { other_than:1} do
 validates :category_id
 validates :days_to_send
 validates :item_condition_id
 validates :prace_id
 validates :fee_id
end

以上になります。

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

Railsで本番環境(EC2, AmazonLinux)でPDF出力ができない

本番環境(EC2, AmazonLinux)でPDF出力ができない

wicked_pdfwkhtmltopdf-binaryというGemを使ってPDF出力機能を付けていますが、開発環境ではうまく行くのに本番環境では下記のエラーが発生。
Railsのバージョンは4.2です。

RuntimeError (Failed to execute:
["/var/www/~~~/shared/bundle/ruby/2.4.0/gems/wkhtmltopdf-binary-0.12.6.3/bin/wkhtmltopdf", "--encoding", "UTF-8", "--page-size", "A4", "file:////tmp/wicked_pdf20201007-11835-gppxfs.html", "/tmp/wicked_pdf_generated_file20201007-11835-j83wu8.pdf"]
Error: PDF could not be generated!
 Command Error: /var/www/~~~/shared/bundle/ruby/2.4.0/gems/wkhtmltopdf-binary-0.12.6.3/bin/wkhtmltopdf_centos_7_amd64: error while loading shared libraries: libpng15.so.15: cannot open shared object file: No such file or directory
):

libpng15.so.15というライブラリがない様ですが、同様のエラーをネットで調べて色々と試しましたが解決しませんでした。

Gemを変更し解決

2016年の記事ですが、下記が参考になりました。
https://qiita.com/s-mori/items/00aef46e6a10499f8254
https://qiita.com/yaboojp/items/526c9397070ca5d05256

wkhtmltopdf-binaryはAmazonLinuxに対応していない様で、
AmazonLinuxに対応しているwkhtmltopdf-binary-amlを使うことでうまくいきました。

修正前のGemfile

gem 'wicked_pdf'
gem 'wkhtmltopdf-binary-aml'

修正後のGemfile

gem 'wicked_pdf'
gem 'wkhtmltopdf-binary-aml', git: 'https://github.com/insphire/wkhtmltopdf-binary-aml'

修正前のwicked_pdf

config/initializers/wicked_pdf.rb
WickedPdf.config = {
  :exe_path => "#{Gem.loaded_specs['wkhtmltopdf-binary'].full_gem_path}/bin/wkhtmltopdf"
}

修正後のwicked_pdf

config/initializers/wicked_pdf.rb
WickedPdf.config = {
  :exe_path => "#{Gem.loaded_specs['wkhtmltopdf-binary-aml'].full_gem_path}/bin/wkhtmltopdf"
}

Bundlerのバージョンが変更されない様にして、bundle installしました。
$ bundle _1.16.1_ install

日本語表示に対応させる

本番環境にデプロイすると、日本語表示がされていませんでした。
なのでIPAフォントを本番サーバでインストールします。

cd /usr/share/fonts

$ yum install -y ipa-gothic-fonts ipa-mincho-fonts

フォントを変更したことでレイアウトが崩れてしまったので、CSSなどを整えて、領収書機能を完成させることができました。

開発環境でのエラー

AmazonLinux対応のGemに変更したことで、今度は開発環境でエラーが起こる様になってしまいました。

RuntimeError - PDF could not be generated!
 Command Error: /Users/~~~/vendor/bundle/ruby/2.4.0/bundler/gems/wkhtmltopdf-binary-aml-e5340ed88aa8/bin/wkhtmltopdf:15:in `exec': Bad CPU type in executable - /Users/~~~/vendor/bundle/ruby/2.4.0/bundler/gems/wkhtmltopdf-binary-aml-e5340ed88aa8/libexec/wkhtmltopdf-darwin-x86 (Errno::E086)

開発環境でも本番環境でもうまくやるには、wicked_pdf.rbif Rails.env.production?などを使ってかき分けたり、
Gemfileを以下の様にして環境別に切り替えればできます。

group :development do
 gem 'wkhtmltopdf-binary'
end
gem 'wkhtmltopdf-binary', group: :development
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Dockerと仮装サーバーとコンテナについて

Docker!仮想サーバー!コンテナ!について

Dockerとは

コンテナ化を用いてアプリケーションを開発・配置・実行するためのオープンソースソフトウェアのこと!

bit201712121505387423.jpg

仮想サーバーとは

1台のサーバーで仮想的に複数のサーバーを稼働させる仕組みのこと!

コンテナとは

他のユーザーから隔離された実行環境のこと。仮想サーバーに比べて起動時間が短く、同じ性能のハードウェアであれば、より多くのコンテナを同時に動かすことができる!

デプロイの種類について

カナリアリリースとは

一部のユーザーにのみ新機能の公開を行い、新しいバージョンにバグなどがないかを検証する手法のこと。サービスに不具合があっても全体に影響しませんよ!というもの。

ブルーグリーンデプロイメントとは

仮想サーバーを用い、2つの本番環境を用意し、それぞれバージョンを設定することができるデプロイの運用方法のこと。2つ用意するからどちらかに不具合が生じた場合どちらかにリクエストの方向を変える事ができる為、その間に不具合が生じた方をロールバックする事ができる!

イミュータブルデプロイメントとは

常に変更を行わない環境を構築することで、デプロイ時に新しい環境に切り替える手法のこと。古い環境を消去しますよというもの!

Docker公式 http://docs.docker.jp/

まとめ

Dockerについて概要を知るkとができましたね。おまけでクラウドの種類もよく使われるので載せておきます。

おまけ

SaaS

「Software as a Service」の略で、「サース」または「サーズ」と読む。
クライアント側に導入せずに、サービスを提供しているサーバーに直接アクセスをしてサービスを利用する状況を指す!
(例)
Microsoft Office 365などのオフィスソフト
GmailなどのWebメール
Dropboxなどのオンラインストレージ
サイボウズなどのグループウェア

PaaS

「Platform as a Service」の略で、「パース」と読む。
作成したアプリケーションなどを、ネットワーク上に公開するためのプラットフォームを提供するサービスのこと!
(例)
Heroku

IaaS

「Infrastructure as a Service 」の略で、「イアース」や「アイアース」と読む。
サービスを利用するユーザーが、仮想化をしたCPUやメモリ、ストレージなどをインターネット経由で提供するサービスのこと!
(例)
Microsoft Azure
Google Compute Engine

現場からは以上です!

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

[Ruby on rails] いいね機能の実装

はじめに

投稿アプリで他のユーザーがいいねを出来るようにいいね機能を実装。
usersテーブルとpostsテーブルとlikesテーブルがあるものとします。

アソシエーション

まずは、各テーブルの関係性を考え、アソシエーションを定義します。
ユーザー(1):いいね(多)
投稿(1):いいね(多)
1人1投稿に1回のいいねまでにしたいのでバリデーションもかけます。

like.rb
class Like < ApplicationRecord
  belongs_to :user
  belongs_to :post

  validates_uniqueness_of :post_id, scope: :user_id

end

投稿が削除されたされた場合いいねも削除。

post.rb
has_many :likes, dependent: :destroy
user.rb
has_many :likes, dependent: :destroy

def already_liked?(post)
  self.likes.exists?(post_id: post.id)
end

コントローラー実装

likes_controller.rb
class LikesController < ApplicationController

  def create
    @like = current_user.likes.create(post_id: params[:post_id])
    redirect_back(fallback_location: root_path)
  end

  def destroy
    @post = Post.find(params[:post_id])
    @like = current_user.likes.find_by(post_id: @post.id)
    @like.destroy
    redirect_back(fallback_location: root_path)
  end

end

ルーティングの設定

routes.rb
resources :posts do
  resource :likes, only: [:create, :destroy]
end

post_likes
DELETE /posts/:post_id/likes(.:format) likes#destroy
POST /posts/:post_id/likes(.:format) likes#create

viewの実装

~.html.erb
<% if current_user.already_liked?(post) %>
  <%= link_to post_likes_path(post), method: :delete do %>
    <i class="fas fa-heart"></i>
  <% end %>
<% else %>
  <%= link_to post_likes_path(post), method: :post do %>
    <i class="far fa-heart"></i>
  <% end %>
<% end %>
<%= post.likes.count %>    //いいねの数を表示

すでにcurrent_userがいいねしているか?
trueであればいいねを解除して、falseであればいいねをする。

最後に

いいねの実装方法は他にも非同期での実装などもあります。
まだまだ勉強中ですが、色々な技術を勉強し出来ることを増やしていきたいと思います。
最後まで読んでいただきありがとうございます:grin:

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