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

Ruby on Rails でnewコマンドにてアプリを作成するとbundler: failed to load command: spring (/Users/ユーザー名/アプリを作成するディレクトリ名/アプリ名/vendor/bundle/ruby/2.6.0/bin/spring)と出た話

こんばんは。Miyayanと申します。今回初めてQiitaにて記事をあげます。
ゆえに拙い部分もあるかと存じますが、どうぞよろしくお願いいたします。

目次

  • 環境
  • 参考記事
  • 概要
  • 仮説
  • 試したこと
  • 解決方法
  • まとめ

環境

OS : macOS Big Sur ver.11.1
Ruby : 2.6.5p114
Rails : 6.0.0
bundler : 2.1.4, default: 1.17.2
gem : 3.0.3

参考記事

【Rails6】rails new で bundler: failed to load command: spring が出た。問題はbundler2.1.4かRuby2.7にあるようだが.....

bundler: failed to load command: spring が出た。。

「gem update --system」と「gem update」の違い

概要

railsにてnewコマンドを用い、アプリケーションの作成を試みました。

rails _6.0.0_ new アプリ名 -d mysql

すると、下記文面が赤色で表示されるではないですか!

bundler: failed to load command: spring (/Users/ユーザー名/アプリを作成したいディレクトリ/アプリ名/vendor/bundle/ruby/2.6.0/bin/spring)

どうもエラーのようでエラーっぽくないと言いますか、正直プログラミング初学者の私からしたらイマイチ何が起きているのかもパッとしない状況・・・。
しかし赤文字ってだけで何かムズムズしてしまう。
どうにかしてこの警告のような文面が出ることなくアプリケーションを作成できないものだろうか・・・。

仮説

文面から見るにSpringというものがうまくいってないように感じます。
しかしながら、インストールされているGemを見ると、

Installing spring 2.1.1

と緑色で表示があり、Springは入っている。正直この警告は無視しても良さそうな感じがします。
参考記事を見てみると、バージョンが2.1.4となっているbundlerが悪さをしているのかと書かれております。

では、bundlerをいじってみましょうか。

試したこと

まずbundlerのバージョンを確かめてみます。

% gem list bundler

*** LOCAL GEMS ***

bundler (2.1.4, default: 1.17.2)

参考記事の方々と同じ2.1.4です。
これをアンインストールし、バージョンを再度確認。

% gem uninstall bundler
Successfully uninstalled bundler-2.1.4

% gem list bundler     

*** LOCAL GEMS ***

bundler (default: 1.17.2)

そしてさらに、最新バージョンのbundlerを入れてみます。

% gem install bundler

ここで再度bundlerのバージョンを確かめてみましょう。

% gem list bundler   

*** LOCAL GEMS ***

bundler (2.2.3, default: 1.17.2)

bundlerが2.2.3にアップグレードされました。
これでnewコマンドでアプリを作成してみましょう!!!

bundler: failed to load command: spring (/Users/ユーザー名/アプリを作成したいディレクトリ/アプリ名/vendor/bundle/ruby/2.6.0/bin/spring)

出るんかい!!!
どうもbundlerではなさそう。あるいは2.2.3でもSpringの警告文は出るのかもしれない。

gemそのもののバージョン・・・?

解決方法

こちらは私の場合、解決した方法です。あくまでご参考程度にしていただけると幸いです。

gemのバージョンを確認してみる。

% gem -v
3.0.3

gemのアップデートを試みる。

% gem update --system

再度gemのバージョンを確認する。

% gem -v             
3.2.3

アップデートが完了したようです。
流石にこれで解決するのかなーと半信半疑でしたが、ここで一旦bundlerのバージョンも確認してみます。

% gem list bundler

*** LOCAL GEMS ***

bundler (default: 2.2.3)

ん?ちょいと表記が違いますね。
先程は

bundler (2.2.3, default: 1.17.2)

こういう表示だったのが、gem自体のアップデートを行なったことによりbundlerのdefaultがカチッと最新バージョンになったようです。

ここでrails newコマンドで再度アプリを作成すると、
無事警告が出ることなく作成完了しました!!!

まとめ

結論、なぜ解決したのかパッとしませんが、
①bundlerのdefaultバージョンが1.17.2より現行最新である2.2.3へと切り替わったことで解決した。
②gemのバージョンが3.0.3から3.2.3になったことで解決した。

以上2パターンかなぁと感じております。

あくまで私の環境下での解決方法ではありましたが、同様に
bundler: failed to load command: spring
が出た方のご参考になれば幸いです。

ご覧いただきありがとうございました!

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

FactoryBot、外部キーバリデーションで弾かれたときの解決方法

はじめに

 以前から、FaxtoryBotを使って、Rspecのテストがエラーでなかなか進めませんでした。今回、解決出来たので紹介します。

↓以前の投稿
FactoryBotで外部キーの値はどうやって作り出すんだ〜(泣

設計

userテーブルはroomテーブルとアソシエーションしていて、ユーザー登録をする際に、room_idを入力する必要があるような状況です。

ユーザー登録のテストコード一部

user_spec.rb
RSpec.describe User, type: :model do
  describe 'ユーザー登録' do
    before do
      @user = FactoryBot.build(:user)
    end
    it '全ての項目が入力されていれば、登録できること' do
      expect(@user).to be_valid
    end
  end
end

@user = FactoryBot.build(:user)の部分でいつもエラーが出てしまっていた。

FactoryBotの一部

roomモデル

rooms.rb
FactoryBot.define do
  factory :room do
    Faker::Config.locale = :ja
    name { Faker::Name.first_name }
  end
end

userモデル

users.rb
FactoryBot.define do
  factory :user do
    Faker::Config.locale = :ja
    room { FactoryBot.create(:room) } #元々のエラーの原因はここ
    email { Faker::Internet.free_email }
    nickname { Faker::Name.last_name }
    password = Faker::Internet.password(min_length: 6)
    password { password }
    password_confirmation { password }
  end
end

Rspecのエラーだった原因

ずばり、この部分です。

room { FactoryBot.create(:room) }(修正後)
room_id { 1 }(修正前)

外部キーにしていたroom_idを無理やり、「1」という値を入れて、FacroryBotとして生成させようとしていました。しかし、これでは、バリデーションのエラーが出てしまっていました。エラーを解釈すると、「外部キーが見当たらない」みたいな感じです。

解決方法

  • 外部キーのカラムには
    FactoryBotの中に、FactoryBotを打ち込む!!
  • 細かい点ではあるが、
    _idは記述しない。

最後に

 これで、やっとテストコードを進めていくことができます。久しぶりに、がっつり知識が増えました!

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

Railsチュートリアル 14章 FactoryBotでRelationshipのテストデータの作成

はじめに

Railsチュートリアルをrspecに対応させながら取り組んでいたところ、14.2.3のリスト14.28のユーザのRelationshipテストデータをFactoryBotで作ろうとしたときにハマってしまったので、備忘録として投稿します。

環境

  • ruby 2.7.2
  • rails 6.0.3
  • factory_bot_rails 6.1.0
  • rspec-rails 4.0.1

やりたいこと

①下記ようにrelationshipのテストデータを作成するfixtureをFactoryBotで定義する。
②userのテストデータも同時にFactoryBotの定義内で作成する。

test/fixtures/relationships.yml(リスト14.28)
one:
  follower: michael
  followed: lana

two:
  follower: michael
  followed: malory

three:
  follower: lana
  followed: michael

four:
  follower: archer
  followed: michael

解決方法

試してみて、採用した解決方法を記載します。

その1 Relationshipのテストデータを作成するFactoryを定義(①のみ解決)

一番素直な方法です。follower_idとfollowed_idを持つRelationshipクラスのファクトリーを作成します。ただ、この方法でassociationをうまく設定することができなかったので、userのテストデータを別途作成しました。

spec/factories/relationships.rb
 factory :relationship, class: Relationship do
    follower_id { follower.id }
    followed_id { followed.id }
  end
spec/models/relationship_spec.rb
RSpec.describe Relationship, type: :model do
  let(:follower) { create(:user) }
  let(:followed) { create(:user) }
  let(:relationship) { create(:relationship, follower: follower, followed: followed) }

  it { expect(relationship).to be_valid }
end

その2 Relationshipを持つuserのテストデータを作成するFactoryBotを定義(①②の両方解決?)

2つめの方法は、Relationshipを持つuserのテストデータを作成する方法です。userのテストデータはすべてのテストでRelationshipを持っている必要はないので、traitで必要に応じて設定できるようにしています。この方法だと、userとrelationshipのテストデータをそれぞれ作成しなくても良くなりました。しかし、この方法はあくまでuserのテストデータを作成する方法で、relationshipを呼び出すにはuserを通す必要があるので、relationshipのテストには向いていなさそうです。

spec/factories/users.rb
factory :user do
  .
  .
  .
  trait :has_followed do
    after(:create) do |user|
      followed = create(:user)
      user.follow(followed) 
    end
  end
end
spec/models/relationship_spec.rb
RSpec.describe Relationship, type: :model do
  let(:user) { create(:user, :has_followed) }

  it { expect(user.actve_relationships).to be_valid }
end

終わりに

結局①②を完全に達成する解決方法を見つけられなかったので、relationshipのテストはその1の方法で、relationshipを持つuserのテストはその2で書くことにしました。

参考文献

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

【Ruby】便利なやつだよ 〜before_action編〜

コントローラー内の記述でかぶっているものについてまとめてくれる、そんな素晴らしいコマンド「before_action」編です。

class ItemsController < ApplicationController

  def index
    @items = Item.order('created_at DESC')
  end

  def new
    @item = Item.new
  end

  def create
    @item = Item.new(item_params)
    if @item.save
      redirect_to root_path
    else
      render :new
    end
  end

  def show
    @item = Item.find(params[:id])
  end

  def edit
    @item = Item.find(params[:id])
    redirect_to root_path unless current_user.id == @item.user_id
  end

  def update
    @item = Item.find(params[:id])
    @item.update(item_params)
    if @item.save
      redirect_to root_path
    else
      render :edit
    end
  end

end

アクション内でかぶっている記述がありますね。
上から順番に

show
edit
update


以上3つの「@item = Item.new(item_params)」という記述です。

これをbefore_actionを使ってまとめてみます。

class ItemsController < ApplicationController
before_action :find_item ⬅️❶まずこれを記述して

〜略〜

  def show
    @item = Item.find(params[:id])     ⬅️❷これを消してく
  end

  def edit
    @item = Item.find(params[:id])     ⬅️❷これを消してく
    redirect_to root_path unless current_user.id == @item.user_id
  end

  def update
    @item = Item.find(params[:id])      ⬅️❷これを消してく
    @item.update(item_params)
    if @item.save
      redirect_to root_path
    else
      render :edit
    end
  end

  private                     ⬅️❸privateメソッドにして


  def find_item                 ⬅️❹find_itemという名前のメソッドを定義して、中身はかぶっている記述をIN
    @item = Item.find(params[:id])
  end


そしてページ更新!

はい!エラーになりました!笑
定義したfind_itemメソッドは、かぶっていて記述を削ったアクションにだけ必要になっている。
つまりshoweditupdateの3つにだけ適用したい。
そんな時はonlyアクションでしたね。しぼっちゃいましょう。

class ItemsController < ApplicationController
before_action :find_item, only: [:show, :edit, :update] ⬅️❶ここに追加
〜略〜

  def show
  end

  def edit
    redirect_to root_path unless current_user.id == @item.user_id
  end

  def update
    @item.update(item_params)
    if @item.save
      redirect_to root_path
    else
      render :edit
    end
  end

  private

  def find_ite
    @item = Item.find(params[:id])
  end

だいぶスッキリしました。
ずっと同じファイルで記述しているとこういうことが起きやすいのかなと思いました。
コードが増えてきたら一度整理&確認してみることが大事ですね。
今日も良い経験になりました。

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

【Ruby】便利なやつだよ〜before_action編〜

コントローラー内の記述でかぶっているものについてまとめてくれる、そんな素晴らしいコマンド「before_action」編です。

class ItemsController < ApplicationController

  def index
    @items = Item.order('created_at DESC')
  end

  def new
    @item = Item.new
  end

  def create
    @item = Item.new(item_params)
    if @item.save
      redirect_to root_path
    else
      render :new
    end
  end

  def show
    @item = Item.find(params[:id])
  end

  def edit
    @item = Item.find(params[:id])
    redirect_to root_path unless current_user.id == @item.user_id
  end

  def update
    @item = Item.find(params[:id])
    @item.update(item_params)
    if @item.save
      redirect_to root_path
    else
      render :edit
    end
  end

end

アクション内でかぶっている記述がありますね。
上から順番に

show
edit
update


以上3つの「@item = Item.new(item_params)」という記述です。

これをbefore_actionを使ってまとめてみます。

class ItemsController < ApplicationController
before_action :find_item ⬅️❶まずこれを記述して

〜略〜

  def show
    @item = Item.find(params[:id])     ⬅️❷これを消してく
  end

  def edit
    @item = Item.find(params[:id])     ⬅️❷これを消してく
    redirect_to root_path unless current_user.id == @item.user_id
  end

  def update
    @item = Item.find(params[:id])      ⬅️❷これを消してく
    @item.update(item_params)
    if @item.save
      redirect_to root_path
    else
      render :edit
    end
  end

  private                     ⬅️❸privateメソッドにして


  def find_item                 ⬅️❹find_itemという名前のメソッドを定義して、中身はかぶっている記述をIN
    @item = Item.find(params[:id])
  end


そしてページ更新!

はい!エラーになりました!笑
定義したfind_itemメソッドは、かぶっていて記述を削ったアクションにだけ必要になっている。
つまりshoweditupdateの3つにだけ適用したい。
そんな時はonlyアクションでしたね。しぼっちゃいましょう。

class ItemsController < ApplicationController
before_action :find_item, only: [:show, :edit, :update] ⬅️❶ここに追加
〜略〜

  def show
  end

  def edit
    redirect_to root_path unless current_user.id == @item.user_id
  end

  def update
    @item.update(item_params)
    if @item.save
      redirect_to root_path
    else
      render :edit
    end
  end

  private

  def find_ite
    @item = Item.find(params[:id])
  end

だいぶスッキリしました。
ずっと同じファイルで記述しているとこういうことが起きやすいのかなと思いました。
コードが増えてきたら一度整理&確認してみることが大事ですね。
今日も良い経験になりました。

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

ファイル生成を抑えた最低限のRails

はじめに

新しくRailsでアプリを作ろうとした際に、余計なファイルが生成されてしまうのが気になったので、今回は自分に必要な最低限のファイル生成でrails newをする方法、設定を書きます。

Rails newのオプション

rails newにオプションをつけることで、指定した関連のファイルを生成しないでアプリを作ってくれます。
以下によく使いそうなオプションをいくつか上げていきます。中にはファイル生成以外の物も含まれています。

使用データベースの指定

rails new sample_app --database=mysql

--database=(任意のDB)で最初から指定したDBでアプリを作ってくれます。指定しない場合はsqliteになるはずです。勿論DBは後から変更することはできますが、少々面倒なので使用DBが決まっている場合は先に指定してしまいましょう。

minitestの除外

rails new sample_app --skip-test

rails標準搭載のminitestを生成しなくなります。Rspecを使用する際には確実に使うオプションです。skipオプションは他にもあります。

turbolinksの除外

rails new sample_app --skip-turbolinks

ページ遷移をAjaxに置き換え、JavaScriptやCSSのパースを省略して高速化させるturbolinksを取り込まなくなります。turbolinksはreadyが発火しないなどの問題もありますので、除外する方もいるみたいです。

.gitignoreの除外

rails new sample_app --skip-git

デフォルトのgitignoreを生成しなくなります。予めpushしないファイルを決めてgitignoreを用意している場合に使います。

APIモード

rails new sample_app --api

アプリをAPIとして作成する際の小さな構成で作ってくれます。MVCのVがなくなった感じです。デフォルトのgemもかなり削減されます。詳しくは最後に参考記事を載せますので、そちらをご参照ください。

Rails new後の設定

rails generateを使用することで簡単にコントローラーやモデルを作成できますが、同時に要らないhelperやtestが生成されてしまうことがあります。そういった場合はconfigのapplication.rbにgenerateコマンドの設定を記述します。

config/application.rb
module Sample
  class Application < Rails::Application
    config.generators do |g|
      g.stylesheets false #stylesheetsが自動生成されなくなる
      g.helper false #helperが自動生成されなくなる
      g.test_framework false #test及びfixtureが生成されなくなる
    end
  end
end

これはあくまで一例ですが、こういった設定をすることでファイル量をかなり減らすことができます。

終わりに

簡単なオプションを付け加えるだけでしたが、快適な開発を進める上でかなり役立ちました。

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

Rails 5 の API モードを触ってみる
rails generate で余分なファイルが生成されないようにする

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

クッキーとセッションの仕組み

概要

クッキーとセッションについて学習したので自分用メモとしてアウトプットしていきます。

はじめに

ショッピングサイトでログイン情報を入力しなくてもログインできたり、(自分が削除しない限り)カートに入れた商品がしばらくカートに入ったままになったりしますよね。これは、セッションクッキーの仕組みがあるため実現できています。

セッション(session)

  • Webサービスに情報を一時的に保持してくれる仕組み
  • アクセスの開始から終了までの一連の通信
  • Railsではsessionというオブジェクトにハッシュのような形で格納される

こちらの記事がイメージしやすく参考になりました!

クッキー(cookie)

  • ブラウザが持っているデータを保存できる領域
  • セッション情報(ID)を保存する場所
  • 有効期限がある

こちらの記事がイメージしやすく参考になりました!

CookieStore

  • Railsでセッションを利用する際のデフォルトの保存先
  • セッション情報は暗号化してブラウザのCookieに保存される
  • ハッシュ形式でセッションを保存する

まとめ

  • セッションとはアクセスのはじめから終わりまでの一連の通信
  • クッキーとはセッションなどユーザーのデータ(ID)を保存するブラウザの領域
  • SessionとはRailsでセッションを利用する際のオブジェクト
  • CookieStoreとはRailsでセッションを利用する際のデフォルトの保存先

参考文献

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

deviseを用いたユーザー認証機能の作成

アプリケーションに"devise"を読み込ませる

Gemfile
:
gem 'devise' #追加
terminal
$ bundle install

deviseコマンドでモデル、ビュー、コントローラを作成

terminal
$ rails g devise:install
$ rails g devise User name:string #モデルの作成
$ rails db:migrate
$ rails g devise:views users #ビューの作成
$ rails g devise:controllers users #コントローラの作成

rails g devise "model名"で作成したモデルには初期値ではnameカラムが存在しない為、上記のようにカラムを同時に作成するか、migrationファイルに記述してテーブルに反映させる。

・作成されたモデル

app/models/user.rb
class User < ApplicationRecord
  # Include default devise modules. Others available are:
  # :confirmable, :lockable, :timeoutable and :omniauthable
  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :validatable
end

デフォルトの機能

  • database_authenticatable(パスワードの正確性を検証)
  • registerable(ユーザー登録や編集、削除)
  • recoverable(パスワードをリセット)
  • rememberable(ログイン情報を保存)
  • validatable(emailのフォーマットなどのバリデーション)

ルーティングの変更

config/routes.rb
...編集
devise_for :users # devise機能の使用の際にURLにusersを含むことの宣言

devise_for :users, controllers: {
  sessions: 'users/sessions',
  registrations: 'users/registrations'
}

ビューページの編集

新規登録画面に名前の入力フォームを追加

app/views/users/registrations/new.html.erb
... +の部分を追加
+ <div class="field"> 
+  <%= f.label :name %><br />
+  <%= f.text_field :name, autofocus: true, autocomplete: "name" %>
+ </div>

<div class="field">
  <%= f.label :email %><br />
  <%= f.email_field :email, autofocus: true, autocomplete: "email" %>
</div>
:

ログイン画面の入力フォームをemailからnameに変更
これによりemailでのログイン認証へと変更させる

app/views/users/sessions/new.html.erb
...編集
:
 <div class="field">
   <%= f.label :email %><br />
   <%= f.email_field :email, autofocus: true, autocomplete: "email" %>
 </div><div class="field">
   <%= f.label :name %><br />
   <%= f.text_field :name, autofocus: true, autocomplete: "name" %>
 </div>
:

コントローラの編集

app/controllers/application_controller.rb
...追加
before_action :configure_permitted_parameters,if: :devise_controller? 

protected

def configure_permitted_parameters
  devise_parameter_sanitizer.permit(:sign_up,keys:[:name])
end

devise利用の機能(ユーザ登録、ログイン認証など)が使われる場合、その前にconfigure_permitted_parametersが実行

devise_parameter_sanitizer.permit(:sign_up,keys[:name])
= ユーザ登録(sign_up)の際のユーザ名(name)のデータ操作を許可

Strong Parametersと同様の機能
・private = 自分のコントローラ内のみで参照
・protected = 呼び出された他のコントローラからも参照可能

ログアウト機能の実装

app/views/layouts/application.html.erb
...+部分の追加
  <body>
+   <% if user_signed_in? %>
+     <%= link_to "ログアウト", destroy_user_session_path, method: :delete %>
+   <% else %>
+     <%= link_to "新規登録", new_user_registration_path %>
+     <%= link_to "ログイン", new_user_session_path %>
+   <% end %>
:

以上

記載内容に間違い等ございましたらご指摘頂けると幸いです。
ご連絡お待ちしております。

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

hidden_field_tagの使い方

はじめに

Railsアプリ作成時にhidden_field_tagを使ったので、その時の内容のメモ書きになります。
検索機能を使った時にパラメータが飛ばなかったのでhidden_field_tagを使用し対応した時の内容になります^^

使用方法

hidden_fieldhidden_field_tagは用途は似ているが使い方が違う。
hidden_fieldform_forform_withの中で使用するに対し、

hidden_field_tagは一つだけで使用でき単体でパラメーターを渡したいときに使用する。
form_for内でも使用はできます。

作業内容

このように検索窓のソースがあります。

app/views/texts/index.html.erb
<%= search_form_for @eq do |f| %>
      <div class="d-flex">
        <div class="form-group flex-grow-1">
          <%= f.search_field :title_cont, placeholder: "教材を探す", class: "form-control" %>
        </div>
        <div> 
        <%= f.submit "検索", class: "btn btn-primary ml-1" %>
        </div>
      </div>
    <% end %>

viewの表示はこのようにコントローラーで指示をしています。

app/controllers/texts_controller.rb
def index
    if params[:genre].nil? 
      @eq = Text.where(genre: ["Ruby on Rails", "Git","Basic","Ruby"]).ransack(params[:q])
    else
      @eq = Text.where(genre: params[:genre]).ransack(params[:q])
    end
      @texts = @eq.result.order(:id)
  end

そこで
indexアクションに記述してある
if params[:genre].nil?はパラメータが空ならtureで以下実行するという記述です。

一部ページのパスにgenre:を埋め込んでいます。

<%= link_to " Ruby/Rails教材", texts_path, class: "dropdown-item" %>
<%= link_to "動画教材", movies_path, class: "dropdown-item" %>
<%= link_to "PHP教材", texts_path(genre: "PHP"), class: "dropdown-item" %>
<%= link_to "プログラミング勉強会", movies_path(genre: "Live"), class: "dropdown-item" %>
<%= link_to "質問集", questions_path, class: "dropdown-item" %>
.
.
.
.

表示するページ事に[:genre]を付けて色分けしています。
なのでリンクからの表示なら問題は無いのです。

が、

今回は検索して表示が目的なので、

このままだと
キーワード検索してもgenreのparamsに値が無いのでtrueで表示されます。

app/views/texts/index.html.erb
<%= f.search_field :title_cont, placeholder: "教材を探す", class: "form-control" %>

↑今のままだと検索かけたとしてもユーザーが記入した文字列しか飛ばしていません。

ターミナル
           部分一致検索→"p"→検索
 Parameters: {"q"=>{"title_cont"=>"p"}, "commit"=>"検索"}
  User Load (0.3ms)  SELECT "users".* FROM "users" WHERE "users"."id" = $1 ORDER BY "users"."id" ASC LIMIT $2  [["id", 3], ["LIMIT", 1]]

From: /Users/ikedakeigo/Desktop/gyakuten_clone_group27/app/controllers/texts_controller.rb:4 TextsController#index:

     2: def index
     3:   binding.pry
 =>  4:   if params[:genre].nil? 
     5:     @eq = Text.where(genre: ["Ruby on Rails", "Git","Basic","Ruby"]).ransack(params[:q])
     6:   else
     7:     @eq = Text.where(genre: params[:genre]).ransack(params[:q])
     8:   end
     9:     @texts = @eq.result.order(:id)
    10: end

[1] pry(#<TextsController>)> params[:genre]
=> nil ⇦:genreに値が入っていない。

そこでhiddenfield_tagの出番です!
:genreを単体で送り込みましょう。

app/views/texts/index.html.erb
  <%= search_form_for @eq do |f| %>
      <div class="d-flex">
        <div class="form-group flex-grow-1">
          <%= f.search_field :title_cont, placeholder: "教材を探す", class: "form-control" %>
        </div>
        <div> 
        <%= hidden_field_tag(:genre, "Php")%> ⇦追加
        <%= f.submit "検索", class: "btn btn-primary ml-1" %>
        </div>
      </div>
    <% end %>

このように記述すると検索キーワードと:genreを飛ばすことができます!
が、
このままだと常に中身がPhpfalseが返され特定の物しか検索できない形になります。

そこで条件分岐を付けて上げて柔軟に対応するようにします。

app/views/texts/index.html.erb
  <%= search_form_for @eq do |f| %>
      <div class="d-flex">
        <div class="form-group flex-grow-1">
          <%= f.search_field :title_cont, placeholder: "教材を探す", class: "form-control" %>
        </div>
        <div>               
        <%= hidden_field_tag(:genre, params[:genre]) if params[:genre].present? %> ⇦追加
        <%= f.submit "検索", class: "btn btn-primary ml-1" %>
        </div>
      </div>
    <% end %>

present?メソッドは 「〜が存在するとき」の条件分岐

表示しているページによって:genreに値を付けているので、
それで判断させています。

これでページ事のキーワード検索ができました!^^

おわりに

コード書いていると極々普通なことかもしれませんが、
悩んでいたことが、上手くできると
「ん?いや、普通に考えたらわかるやん!」っておわりに気づくことがあります^^笑

なんでも諦めないことですね!^^

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

form_withのlocalとflashメッセージ

ちょっと困ったこと

Railsでよくあるflashメッセージを出そうと頑張っていたが、flash.nowだけ出せない。。。

例えば、以下のようにSessionControllerで、ユーザログインを行うようなコードを書く。

 def create
    @user = User.find_by(login_id: params[:session][:login_id])
    if @user && @user.authenticate(params[:session][:password])
      log_in(@user)
      redirect_to @user, success: 'ログインしました'
    else
      flash.now[:danger] = 'ログインに失敗しました。'
      render :new
    end
  end

ログイン成功したとき、すなわち、redirect_toした後の「ログインしました」は出るんだけど、ログイン失敗した時、すなわち「ログインに失敗しました」が全く表示されないという問題が発生しました。

問題点

結論から言うと、form_withに対してlocal: trueをつけていなかったからでした。
そのため、以下のようにlocal: trueをつけたらうまくいきました。

= form_with scope: :session, url: login_path, local: true do |f|
  = f.label :login_id, 'ログインID'
  = f.text_field :login_id, { class: 'form-control' }
  = f.label :password, 'パスワード'
  = f.password_field :password, { class: 'form-control' }
  = f.submit 'ログイン', class: 'btn btn-primary'

なぜlocal: trueが必要か

話がそれますが、form_withにはlocalオプションなるものがあることは知っていました。form_withを使うにあたって、色々調べながら実装していったわけで、参考にする情報は大体local: trueがついていました。

ただ、別にlocal: trueなくても、普通にログインセッション張れるし、つけなくてもいいんじゃね?ということに気がついたので、local: trueは外していました。しかしながら、今回こういう壁にぶち当たった以上、このlocal: trueって何ですか、というところを放置するのはよくないと思ったので、もう少し深堀してみます。

そもそもform_withにおけるlocalオプションって何?

とりあえずRailsドキュメントを見ましょう。https://railsdoc.com/page/form_with
すると「local:リモート送信の無効」と書いています。これだけでは何のことだかさっぱりです。

もう少し詳しく見たかったので、こちらをあたりました。https://techracho.bpsinc.jp/hachi8833/2020_03_09/39502#6
すると「local: trueを指定するとフォームのリモート + unobtrusive XHR送信が無効になります(デフォルトのフォームではリモート + unobtrusive XHRが有効になります)」という記載があります。

これだけ見るとはてなマークばかりですが「unobtrusive XHR送信」というのはAjax通信のことです。というわけで、form_withにおけるlocalオプションは「当該箇所のform_withはAjax通信を無効(true)にするか、有効(false)にするか」ということになります。

Ajax通信と今回のflashメッセージ問題考察

さて、ここからは完全に憶測になるので、誤った記載となると思います。参考にする場合はほどほどでお願いします。ちなみに、Ajaxについての詳しい記載は本筋とは少し違うので、省略します。詳細を知りたい方は、本記事最後の参考をどうぞ。

考察

元のコードでうまくいかなかった理由

  • form_withでlocalを指定しない場合、デフォルトでlocal: falseが設定される、すなわち、Ajax通信有効となっている。
    • 今回私が書いたコードもそうなっている。
  • これにより、ログインセッションを作成する処理は非同期通信で行われている。
  • 非同期通信のため、クライアント側からサーバ側にログインセッションを生成しに行ってもそれがうまくいったかどうかはサーバ側から、クライアント側に返ってこない
    • 例えば、HTTPステータス200とか404とかの情報が返ってこない。
  • 今回のケースで言うと「ログイン失敗した」という情報がサーバ側から返ってこないため「flash.now[:danger]」は実行されない。

ところで冒頭に書きましたが「local: trueがない状態で、サーバ側からログインセッション生成しに行った結果が返ってこないのに、なぜ「ログインしました」のメッセージが出て、ログイン後の画面に遷移するのか」という次の疑問が出てきます。

これは、renderメソッドとredirect_toメソッドの違い、というところにあるかと思っています。ざっくりいうと、renderはHTTPリクエストをサーバに送らず、redirect_toはHTTPリクエストをサーバに送ります、というところですね。

今回のケースではログイン処理後にredirect_toを実行している、すなわちサーバからリクエストを送っています。redirect_toメソッドはリクエストに応じたHTMLをクライアントに返すので、クライアント側で画面遷移が発生します。

local: trueでうまくいった理由

上記の裏返しなんですが、local: trueにして、Ajax通信を無効、すなわち同期通信をすることによって、サーバからログインできたかどうかの結果がちゃんと返ってきます。それを検知して、flashメッセージが出せるようになるわけです。

参考記事

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

resources & resource

resources

Ruby on Railsでルーティングを行う際に必ずと行っていいほど頻出であるresources
resourcesはモデルに対してアプリケーションにおける基本メソッド7つを自動的にルーティングしてくれます。

例えばUserモデルに対してresourcesを使ってルーティングを記載した場合

config/routes.rb
Rails.application.routes.draw do
  resources :users
end

$ rails routesでルーティングを表示させると以下のようになります。

$ rails routes
   Prefix Verb   URI Pattern               Controller#Action
    users GET    /users(.:format)                    users#index                                               
          POST   /users(.:format)                    users#create                                                     
 new_user GET    /users/new(.:format)                users#new                                                  
edit_user GET    /users/:id/edit(.:format)           users#edit                                                   
     user GET    /users/:id(.:format)                users#show                                                 
          PATCH  /users/:id(.:format)                users#update                                           
          DELETE /users/:id(.:format)              users#destroy

このようにして、たった1行記載するだけで、簡単にルーティイングをしてくれます。

resourcesが使われるのは基本的にモデルに複数のリソースがある時です。
例えば、Userモデルにはアプリケーションのユーザのモデルがたくさん登録されると思います。
他にも、ユーザの投稿モデルであるPostモデルも、アプリケーション内に複数の投稿が存在するので、resourcesを使用することができます。

resource

では、複数リソースに対してではなく単数のリソースにルーティングする場合はどうでしょうか?

単数のリソースとはつまり、アプリケーション内のページにおいて、ログインユーザだけが使用するリソースです。
例えば、Instagramの設定にあるプロフィールページは自分だけが使用できますよね?
他の人はあなたのプロフィール設定ページににアクセスすることはできません。(できたらアカウントが乗っ取られています笑)

そのような単一リソースに対してルーティングを設定する際はresourcesではなく、単数形resourceを使います。
例えば、プロフィール画面をルーティングするとしましょう。

config/routes.rb
Rails.application.routes.draw do
  resources :users
  # 追記
  resource :profile
end
$ rails routes
      Prefix Verb   URI Pattern               Controller#Action
 new_profile GET    /profile/new(.:format)             profiles#new
edit_profile GET    /profile/edit(.:format)            profiles#edit
     profile GET    /profile(.:format)                 profiles#show
             PATCH  /profile(.:format)                 profiles#update
             DELETE /profile(.:format)                 profiles#destroy
             POST   /profile(.:format)                 profiles#create

resourcesと何かが違いますね、、。

①indexアクションがない

なぜ?、と思うかもしれませんが、これはよくよく考えてみると簡単です。

上述したように、resourceを使用する場合、単一リソースが使用対象をなることを説明しました。
しかし、indexアクションはモデルのリソース全てを表示することを意図しています。
結果、単一リソースであるプロフィール画面を扱う際に複数を表示するindexアクションは不要となるわけです。

②:idがない
4つのアクション(edit、show、update、destroy)から:idという部分が消えています。

実はこれもindexアクションがない理由と同じになります。

resourcesにおいて4つのアクションに:idが必要だった理由は、複数あるリソースの中からどのリソースかを指定するためです。
例えば、自分の投稿を編集する場合、どのidの投稿かを指定しないと、どの投稿を編集していいのかがわかりません。投稿の詳細閲覧、投稿の更新、投稿の削除も同様に、どの投稿であるかを指定しないといけません。

しかし、プロフィール設定画面はどうでしょうか。
単一リソースであるプロフィールは、指定せずとも一つしかないのだからわかるという仕組みです。
ですので、idを指定する必要がそもそもないという理由で:idが消えています。

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

Rails チュートリアル 第8章 備忘録

第8章の備忘録です。

環境

Rails 6.0.3
Ruby 2.6.3

目次

1 セッションとcookies
2 フラッシュメッセージ表示
3 ヘルパーメソッドを作成して利用するまでの流れ
4 jqueryとbootstrapの導入方法
5 signupした後に再度ログインさせない

1 セッションとcookies

日頃通信する時に利用されているHTTPステートレスなプロトコルである。
そのステートレスとは、
ブラウザ側が、〇〇のページを表示してください(リクエスト)→サーバー側が、〇〇のページです!(レスポンス)のやりとりをトランザクションというのだが、一度そのやりとりを終えるとすっかり忘れてしまうのである。

つまり、会員サイトでログインした後に、この商品の購入ページを表示してくださいとリクエストを送ると、以前のログインしたやりとりを忘れてしまっているので、あなたは誰ですか?となってしまう。

それを防ぐために、ユーザーのIDを保持しておくための設定がセッションである。

そして、そのセッションを実装するために使われるのが、cookiesである。cookiesはブラウザに保存することができる小さなでキストデータである。ここにログインしたらユーザーのIDを保管、ログアウトしたらcookiesに保管したものを削除することで、ログインの機能を作っているのである。

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

第7章で実装した、新規登録時に表示されるフラッシュメッセージは、ActiveRecordオブジェクトに関連づけられていたものだが、セッション時にはActiveRecordを使用していないので、別の方法で作成する必要がある。

app/controllers/sessions_controller.rb
class SessionsController < ApplicationController
  def new
  end

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

実装の流れとしては、
表示したいタイミングで、flashメソッドを使用。application.html.erbでCSS含めレイアウトしてあるので、この1行で自動的に表示される。

また、flashメソッドだけだと、再レンダリングしても消えない小さなバグが発生するため、flashメッセージを表示した後に、リクエストが発生したら表示を消す機能を含んだ、flash.nowメソッドを使用する

3 ヘルパーメソッドを作成して利用するまでの流れ

ここまで来ると、各Controller毎に、ログインしているかどうかでの条件分岐が多発してくる。毎度毎度のコードを簡潔にメソッドとしてまとめておくと、コードが簡略化されるため、自身でメソッドを作成し使用するまでの流れをまとめる。

  1. application_controller.rbに、ヘルパーモジュールを読み込む(今回はsessions_helper.rb
app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
  include SessionsHelper
end
  1. 自作メソッドをhelperファイルに作成(今回はlog_inメソッド)
app/helpers/sessions_helper.rb
module SessionHelper
  def log_in(user)
    session[:user_id] = user.id
  end
end
  1. Controllerファイルで使用
appp/controllers/sessions_controller.rb
.
.
  def create
  .
  .
  log_in user
  .
  end
.

こうすることで、実際にメソッドを使用することができる。
今回はlog_inメソッドの中身も1行であるが、今後複数行のメソッドを作ることも想定し同じ動作を繰り返すのであれば、このようにすることで、メソッド名を設定することで可読性も上がり、DRYであることに繋がる。

4 jqueryとbootstrapの導入方法

Rails6から設定方法が変わった?かもしれないらしいので、今回はRails6での設定方法をまとめる。

  1. yarnでインストール
$ yarn add jquery@3.4.1 bootstrap@3.4.1
  1. WebpackにjQueryの設定を追加
config/webpack/environment.js
const { environment } = require('@rails/webpacker')

const webpack = require('webpack')
environment.plugins.prepend('Provide',
  new webpack.ProvidePlugin({
    $: 'jquery/src/jquery',
    jQuery: 'jquery/src/jquery'
  })
)

module.exports = environment
  1. 必要なJSファイルをrequire / import
app/javascript/packs/application.js
.
.
require("jquery")
import "bootstrap"

5 signupした後に再度ログインさせない

signupの機能を作った後に、login機能を作るとうっかり忘れがちになりそう?なのだが、実際にsignupすると、ログイン状態にならない。

実際の動きは、redirect_to @user/users/◯showビューが表示されているのだが、セッションにユーザーIDを保存していないので、セッションを用いて作成したcurrent_userlogged_inメソッドが使えない、もしくはfalseを返す。

こうならないためにも、signup時のcreateアクションでユーザーが保存されたら、log_in @userを忘れない。

わざわざ書くまでのことではないのかもしれないが、ユーザー登録自体がめんどくさいと思ってしまう自分だったら、仮に新規登録した後に、ログイン画面で再度入力しなければならなくなった場合、そっとそのアプリを閉じると思う。。笑

ちょっとしたユーザービリティも考えて一応メモ。

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

Ruby on Rails ✕ Docker 開発中のアプリにDocker導入(MySQL)

はじめに

はじめてのDocker導入だと、何から手を付けたら良いのかわからない、ということがよくありますが、まさに自分がそれでした...

本記事では開発途中・完成したRailsアプリケーション(DB: MySQL)にDockerを導入する方法・流れを解説していきます。
参考までに自分は6時間位かけてローカルに導入できました。その苦労を忘れないようにしっかりアウトプットしていこうと思います(笑)
間違っていたら指摘してくださると幸いです!

Dockerの導入

1)Dockerのインストール

まずMacにDockerのアプリをインストールしてください
それに関しては以下の記事がわかりやすいです!
DockerをMacにインストールする

インストールできたらターミナルで以下コマンドを実行しましょう!

ターミナル
~ % docker run -d -p 80:80 docker/getting-started

2)インストールの確認

ターミナル
~ % docker -v

以下のように出力されたらインストール成功です。
Docker version 20.10.0, build 7287ab3

ターミナル
~ % docker-compose -v

こちらも同じようにdocker-compose version 1.27.4, build 40524192と出力されたら成功です。

3)開発中・完成済みのアプリでDockerファイルの作成

次にDockerを導入するためにDockerファイルを作成し、設定を記述していきます。
テキストエディタでも可能ですが、自分の場合はターミナルから入力して方がエラーもなくスムーズだったため、そちらの方法を記載していきます。

①Dockerfileの作成

まずアプリのルートディレクトリにDockerfileを作成して、記述していきます。
ルートディレクトリというのは以下の図のようにアプリ直下のディレクトリを表します。

dock_app ----|-- app
            |-- bin
            |-- config
            |-- db
            ・・・・・・
       ・・・・・・
            |-- Gemfile
            |-- Gemfile.lock
            |-- package.json
            |-- Rakefile
            |-- README.md
            |-- Dockerfile  ←「Dockerfile]
            |-- docker-compose.yml  ←[docker-compose.yml]

手順としては以下の順に進んでいきます。

ターミナル
 ~ % cd Dockerを導入したいアプリのパス

 アプリ名 % vi Dockerfile
 #ファイルを開いたら「i」でインサートモードにして以下記述
FROM ruby:2.6.5 #アプリのRubyバージョン

RUN apt-get update -qq && \
    apt-get install -y build-essential \
                       libpq-dev \
                       nodejs

RUN mkdir /アプリ名
WORKDIR /アプリ名

ADD ./Gemfile /アプリ名/Gemfile
ADD ./Gemfile.lock /アプリ名/Gemfile.lock

RUN gem install bundler #bundlerをインストールしないとエラーが出る
RUN bundle install
ADD . /アプリ名

 #記述ができたら「escキー」を押して「:wq」で保存

②docker-compose.ymlファイルの作成

ターミナル
 アプリ名 % vi docker-compose.yml

 docker-compose.ymlに以下記述

 #「i」と入力しインサートモードで記述

version: '3'
services:
  db:
    image: mysql:5.7
    environment:
      MYSQL_ROOT_PASSWORD: 'password' # このままpasswordとしても問題なく動く
    ports:
      - "4306:3306" #DockerコンテナとSequelpro接続の為に必要な設定

  web:
    build: .
    command: bash -c "rm -f tmp/pids/server.pid && bundle exec rails s -p 3000 -b '0.0.0.0'"
    volumes:
      - .:/アプリ名
    ports:
      - "3000:3000"
    depends_on:
      - db
 #記述が終了したら「escキー」を押して「:wq」で保存

4)config/database.ymlファイルの編集

(テキストエディタ)
default: &default
  adapter: mysql2
  encoding: utf8
  pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
  username: root
  password: password # passwordの設定がなければ、このままで良い
  socket: /tmp/mysql.sock
  host: db

development:
  <<: *default
  database: アプリ名_development

追加されたのはpassword: passwordhost: dbの2つのみ。

5)docker-compose buildでコンテナ作成

ターミナル
アプリ名 ~ % docker-compose build

上記コマンドを実行してコンテナを作成しましょう。※時間がかかることがあります

以下のように出力されれば成功です。

ターミナル
Removing intermediate container dac250609513
 ---> f0ba8d685e44
Step 9/9 : ADD . /tumlog
 ---> f50cb7681119

Successfully built f50cb7681119
Successfully tagged tumlog_web:latest

ちなみに初心者の方だとコマンド実行中に赤字でdebconf: delaying package configuration, since apt-utils is not installedと表示されますが、自分の調べた範囲では特に気にするようなこともない?出力のようです。

6)コンテナ上でDB作成・migrationの実行

コンテナが作成できたらコンテナ上でDBを作成していきます。

ターミナル
アプリ名  % docker-compose run web bundle exec rake db:create

Rails6だとこのコマンドを実行すると

========================================
  Your Yarn packages are out of date!
  Please run `yarn install --check-files` to update.
========================================


To disable this check, please change `check_yarn_integrity`
to `false` in your webpacker config file (config/webpacker.yml).

このように出力されることがあります。
このように出力されたら、エラー分通りconfig/webpacker.ymlファイルの記述を以下のように編集しましょう。

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

 # check_yarn_integrity: true を falseに変えましょう → check_yarn_integrity: false

これでエラーは解決できると思うので、記述を変更したら、もう1度DB作成のコマンドを実行しましょう。

ターミナル
Created database 'アプリ名_development'
Created database 'アプリ名_test'

#上記のようにcreatingがcreated...doneとなったら成功

続いてmigrationを実行しましょう。

ターミナル
アプリ名  % docker-compose run web bundle exec rake db:migrate


== 20201222010929 Createテーブル名: migrating ====================================
-- create_table(:テーブル名)
   -> 0.0095s
== 20201222010929 Createテーブル名: migrated (0.0096s) ===========================

# 上記のようにrails db:migrateを実行したときのようにマイグレーションが実行されれば成功です

7)コンテナの起動

最後にコンテナを起動して無事、アプリがブラウザで表示されるか確認しましょう。

コンテナの起動コマンドを実行しましょう。

ターミナル
アプリ名 % docker-compose up 

# 起動していれば以下の様の出力が出る

db_1   | 2020-12-27T06:45:52.494912Z 0 [Note] Event Scheduler: Loaded 0 events
db_1   | 2020-12-27T06:45:52.497330Z 0 [Note] InnoDB: Buffer pool(s) load completed at 201227  6:45:52
db_1   | 2020-12-27T06:45:52.497669Z 0 [Note] mysqld: ready for connections.
db_1   | Version: '5.7.32'  socket: '/var/run/mysqld/mysqld.sock'  port: 3306  MySQL Community Server (GPL)
web_1  | => Booting Puma
web_1  | => Rails 6.0.3.4 application starting in development 
web_1  | => Run `rails server --help` for more startup options
web_1  | Puma starting in single mode...
web_1  | * Version 3.12.6 (ruby 2.6.5-p114), codename: Llamas in Pajamas
web_1  | * Min threads: 5, max threads: 5
web_1  | * Environment: development
web_1  | * Listening on tcp://0.0.0.0:3000
web_1  | Use Ctrl-C to stop

コマンドを実行し、サーバーが起動したらローカル環境にアクセスしてみましょう
http://localhost:3000/
無事に表示・挙動が確認できれば開発環境にDockerを導入できたということになります。

自分はこれだけで6時間くらいかかったので、ぜひ皆さんは本記事を活用して、さくさく進めていってください!!

本記文献

『さわって学ぶクラウドインフラ docker基礎からのコンテナ構築』
既存のrails6のアプリにMySQLでDockerを導入する。
Ruby on Rails 「途中まで作ったアプリにDockerを導入したい」に挑戦してみる(MySQL / Sequel Pro)

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

scopeの中にjoins等は書いて構わないと思う

主観もりもりです。賛否両論あると思いますが、いろんな意見もらえると嬉しいです。

背景

こんなコードを見かけた。

class Hoge
  scope :with_active_fuga, -> { merge(Fuga.active) }

これだとscope単体で動かない。joins(:fuga)等を付けてあげないといけない。
でも、このjoins(:fuga)等は,

  • joins(:fuga).with_active_fugaのように処理の中に付ける方がいいの?
  • それともscope外の呼び出し元にjoins(:fuga)等を付ける方がいいの?

と疑問に思ったので立ち止まって考えるに至った。

scopeに書いて構わないと判断した理由

  • joins等を外出しすると使う人にjoinの種類を委ねるからパフォーマンスや動作的にscope設計者の意図しない使われ方をされるリスクがある。
  • joins等を外出し前提で作ると、単純に動かないコードをそのまま残すことになってまずい。
  • joins(:fuga).joins(:fuga)のように、scopeの組み合わせで同じ内容が被ってもSQLに影響が出ない。
  • includes、preload、eager_loadと共存することができるので、同時にキャッシュも行いたい場合も問題ない。
  • 内部結合と外部結合どっちを使うか選びたいとかあるかもしれないけど、これはテーブルへの関連がない場合を含めるかどうかみたいな判断になって処理の役割が変わるから、それぞれのscopeを作成した方がよいと判断できる。

ちなみに

joins等が発生する絞り込みはscopeを使わないでクラスの中に処理を作るって人たちもいるから必ずしも「絞り込み = scope」とは限らない

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

RailsチュートリアルでRuby3.0.0をインストールしたらbundle updateとbundle installができなかったときの解決法

あいさつ

知っている人はこんにちは!知らない人ははじめまして!
独学でプログラミングを極める変態中学生のAtieです!
今回は約3日間格闘し続けたエラーの解決法がわかったのでここに記録を残します
これでやっとRailsチュートリアルを進めることができます

結論から言うと...

Rubyの最新版(3.0.0)をインストールしてしまって依存関係が解決できずに動かなかった!
ということのせいで動きませんでした
なのでRailsチュートリアルと同じバージョンのRuby2.6.3をインストールしてGlobalに2.6.3を設定することで動きました!

開発環境

Ruby 3.0.0
Rails 6.0.3.4
Gemfile.lock は削除済み
Gemfileの内容(名前は「Gemfile」ですがRubyのコードで記載されていてわかりやすいようにわざと.rbの拡張子をつけています本番環境では.rbはついていません以下Gemfileを表示するときは.rbを拡張子としてつけています)

Gemfile.rb
source 'https://rubygems.org'
git_source(:github) { |repo| "https://github.com/#{repo}.git" }

gem 'rails',      '6.0.3'
gem 'puma',       '4.3.6'
gem 'sass-rails', '5.1.0'
gem 'webpacker',  '4.0.7'
gem 'turbolinks', '5.2.0'
gem 'jbuilder',   '2.9.1'
gem 'bootsnap',   '1.4.5', require: false

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

group :development do
  gem 'web-console',           '4.0.1'
  gem 'listen',                '3.1.5'
  gem 'spring',                '2.1.0'
  gem 'spring-watcher-listen', '2.0.1'
end

group :test do
  gem 'capybara',           '3.28.0'
  gem 'selenium-webdriver', '3.142.4'
  gem 'webdrivers',         '4.1.2'
end

# Windows ではタイムゾーン情報用の tzinfo-data gem を含める必要があります
gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby]

エラーの内容

まずはbundle installを行うと以下のようなエラーが出てきました

$ bundle install
Fetching gem metadata from https://rubygems.org/............
Fetching gem metadata from https://rubygems.org/.
You have requested:
  listen = 3.1.5

The bundle currently has listen locked at 3.3.3.
Try running `bundle update listen`

If you are updating multiple gems in your Gemfile at once,
try passing them all to `bundle update`

エラーの内容は「バージョンがロックされているからbundle updateでアップデートしてね!」という内容です

$ bundle update
Fetching gem metadata from https://rubygems.org/............
Fetching gem metadata from https://rubygems.org/.
Resolving dependencies...
Bundler found conflicting requirements for the Ruby version:
  In Gemfile:
    Ruby armv7l-linux-eabihf

    capybara (= 3.28.0) armv7l-linux-eabihf was resolved to 3.28.0, which depends on
      Ruby (>= 2.4.0) armv7l-linux-eabihf

    listen (= 3.1.5) armv7l-linux-eabihf was resolved to 3.1.5, which depends on
      Ruby (>= 2.2.3, ~> 2.2)

    puma (= 4.3.6) armv7l-linux-eabihf was resolved to 4.3.6, which depends on
      Ruby (>= 2.2) armv7l-linux-eabihf

    selenium-webdriver (= 3.142.4) armv7l-linux-eabihf was resolved to 3.142.4, which depends on
      Ruby (>= 2.3) armv7l-linux-eabihf

内容は「競合が発生しているよ!」という内容です

ググってみると依存関係がどうのこうのだと...
あれ?ちょっと待てよRubyのバージョンってRailsチュートリアルのバージョンと一緒なのか?
という疑問が頭の中をよぎりました
そのときにぴーんと来ました!
なのでまずはRailsチュートリアルのRubyのバージョンを調べました
Railsチュートリアルの初期の状態のGemfileは以下のようになっていました

Gemfile.rb
source 'https://rubygems.org'
git_source(:github) { |repo| "https://github.com/#{repo}.git" }

ruby '2.6.3'

# Bundle edge Rails instead: gem 'rails', github: 'rails/rails'
gem 'rails', '~> 6.0.3'
# Use sqlite3 as the database for Active Record
gem 'sqlite3', '~> 1.4'
# Use Puma as the app server
gem 'puma', '~> 3.11'
# Use SCSS for stylesheets
gem 'sass-rails', '~> 5'
# Transpile app-like JavaScript. Read more: https://github.com/rails/webpacker
gem 'webpacker', '~> 4.0'
# Turbolinks makes navigating your web application faster.
# Read more: https://github.com/turbolinks/turbolinks
gem 'turbolinks', '~> 5'
# Build JSON APIs with ease. Read more: https://github.com/rails/jbuilder
gem 'jbuilder', '~> 2.7'
# Use Redis adapter to run Action Cable in production
# gem 'redis', '~> 4.0'
# Use Active Model has_secure_password
# gem 'bcrypt', '~> 3.1.7'

# Use Active Storage variant
# gem 'image_processing', '~> 1.2'

# Reduces boot times through caching; required in config/boot.rb
gem 'bootsnap', '>= 1.4.2', require: false

group :development, :test do
  # Call 'byebug' anywhere in the code to stop execution and get a
  # debugger console
  gem 'byebug', platforms: [:mri, :mingw, :x64_mingw]
end

group :development do
  # Access an interactive console on exception pages or by calling 'console'
  # anywhere in the code.
  gem 'web-console', '>= 3.3.0'
  gem 'listen', '>= 3.0.5', '< 3.2'
  # Spring speeds up development by keeping your application running in the
  # background. Read more: https://github.com/rails/spring
  gem 'spring'
  gem 'spring-watcher-listen', '~> 2.0.0'
end

group :test do
  # Adds support for Capybara system testing and selenium driver
  gem 'capybara', '>= 2.15'
  gem 'selenium-webdriver'
  # Easy installation and use of web drivers to run system tests with browsers
  gem 'webdrivers'
end

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

Railsチュートリアルのバージョンが2.6.3で自分の環境が3.0.0...
違いすぎるだろ!!それじゃ動くわけねぇよ!(3.0.0に至ってはメジャーアップデートでそこそこ大幅に改良されてたし... 詳しくはここへ)
そうですバージョンが大幅に違いました
あるあるです
特にLinuxを使っているとプログラムなどを動かそうとしてもライブラリなどのバージョンどうのこうのの依存関係のせいで動かないのがよくあります(自分が使っているFedoraなどは特にそう)

はぁはぁはぁ...疲れた...やっと動く...(フラグじゃないよ\(^o^)/)

ということで動きました!

最後に

今回はかなり解決に時間がかかったエラーでした
今回のことを経験することができそのおかげで一回りはプログラマーとして成長できたと思います
エラーの原因を探したり解決するのもプログラマーの必須スキルであったりします
そして今回は依存関係などのバージョンの大切さを非常に学ぶことができました
もしこの記事が皆さんのエラー解決に少しでも役に立てたら光栄です
最後まで読んでいただきありがとうございました
ではまた次回の記事で!
AtieのTwitter

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

FactoryBotでの外部参照キーの記述方法

はじめに

Railsでテストコードを記述していたところ、必要事項を全て埋めても、外部参照したカラムの記述に関するエラーが出たので、FactoryBotでの外部参照キーの記述方法についてまとめました。

前提条件

下記のER図のようなUserモデルとArticleモデルにおいて、Articleモデルのuser_idが外部参照している場合を考えます。また、下記のようにUserのFactoryBotは定義済みとします。
ER.png

user.rb
# UserのFactoryBot
FactoryBot.define do
  factory :user do
    nickname               {Faker::Name.name}
    email                  {Faker::Internet.email}
    password               {Faker::Internet.password}
    password_confirmation  {password}
    last_name              {"投稿"}
    first_name             {"太郎"}
  end
end

実行するテストコード

article_spec.rb
# Articleで実行するテストコード
require 'rails_helper'

describe Article do
  before do
    @article = FactoryBot.build(:article)
  end

  describe '記事新規投稿' do
    context '新規投稿がうまくいく時' do
      it '必要事項が全て存在すれば登録できる' do
        expect(@article).to be_valid
      end
    end
    context '新規投稿がうまくいかない時' do
      it 'titleが空だと登録できない' do
        @article.title = ""
        @article.valid?
        expect(@article.errors.full_messages).to include("Title can't be blank")
      end
    end
  end
end

上記のように、Articleモデルの単体テスト実行の場合を考えます。今回は、簡単のために、正常系として必要事項が全て記述されたとき、異常系としてtitleカラムが空の場合のみを考えます。

エラーになったコード

article.rb
# ArticleのFactoryBot
FactoryBot.define do
  factory :article do
    title            {"あいうえお"}
    prefecture_id    {Faker::Number.within(range: 1..10)}
    distance         {Faker::Number.within(range: 10..500)}
    content          {"あいうえおかきくけこ"}
    user_id          {1}
  end
end

最初は、上記のarticle.rbでArticleのFactoryBotを定義し、article_spec.rbのテストを実行しましたが、it '必要事項が全て存在すれば登録できる'の部分で'User must exist'のエラーが生じました。これにより、article_rbのuser_id {1} という記述だけでは、外部参照出来ていないことがわかります。

解決策1

article_spec.rb
# ArticleのFactoryBot
FactoryBot.define do
  factory :article do
    title            {"あいうえお"}
    prefecture_id    {Faker::Number.within(range: 2..48)}
    distance         {Faker::Number.within(range: 10..500)}
    content          {"あいうえおかきくけこ"}
    user             {FactoryBot.create(:user)}
  end
end

user_id {1} としていた部分をUserのFactoryBotで生成したインスタンスに変更しました。これにより、articlesテーブルで外部参照可能になり、無事article_spec.rbのテストコードが正しく実行されました。

解決策2

article_spec.rb
# ArticleのFactoryBot
FactoryBot.define do
  factory :article do
    title            {"あいうえお"}
    prefecture_id    {Faker::Number.within(range: 2..48)}
    distance         {Faker::Number.within(range: 10..500)}
    content          {"あいうえおかきくけこ"}
    association      :user
  end
end

次に、user_id {1} としていた部分をassociation :userという記述に変更しました。この記述でも、article_spec.rbのテストコードが正しく実行されました。

まとめ

FactoryBotで外部参照キーを記述する際は、user_id {1} などのように直接数字を記述するだけでは正しく外部参照できず、解決策①②のような記述が必要なことを学びました。

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

[Rails]ActionMailerでメール送信機能を作る(完全版)

はじめに

ある案件にお問い合わせメール送信機能の実装がありました。
自分的には、初めて自分で実装するので備忘録として、また、ひとつの記事だけではなかなか実装出来なかったので、この記事を見るだけで実装完了出来ると他の人にも役立つと思い書きます。

実装手順

1.使用するモデルの作成
# 任意のモデル名、必要カラムを設定する
$ rails g model inquiry name:string text:string

$ rails db:migrate
2.Action Mailerの作成

メール送信するために必要なApplicationMailerを継承するクラスは、generateコマンドから作成出来る。

$ rails g mailer inquiry
3.Gmailで送信するための設定

Railsからメール送信出来るようにするには、2つやることがあります。
①パスワードの二段階認証の設定
②アプリパスワードの設定
↑はこちらから設定出来ます。
設定出来たら、下記を記述してください。

config/enviroments/development.rb
# trueだと、メールが送信されない時にエラーメッセージが表示される
config.action_mailer.raise_delivery_errors = true

# メイラーのテンプレートでフラグメントキャッシュを有効にするべきかどうかを指定する
config.action_mailer.perform_caching = false

# 配信方法を指定する
config.action_mailer.delivery_method = :smtp
config.action_mailer.smtp_settings = {
 address:              'smtp.gmail.com',
 port:                  587,
 domain:               'gmail.com',
 user_name:            '<gmailのメールアドレス>',
 password:             'アプリパスワード',
 authentication:       'plain',
 enable_starttls_auto:  true
}
4.メールの宛先や件名を指定する
app/mailer/inquiry_mailer.rb
def send_mail(inquiry)
  @inquiry = inquiry
  mail(
    from: 'from@example.com', # 送信元メールアドレス
    to:   'to@example.com',   # 送信先メールアドレス
    subject: 'お問い合わせメール' # 件名
  )
end

※デフォルト値を設定する場合は、application_mailer.rbに記述する。
(この場合、app/mailer/inquiry_mailer.rbのfrom: 〇〇の記述は必要なし。)

app/mailer/application_mailer.rb
class ApplicationMailer < ActionMailer::Base
  default from: 'from@example.com'
  layout 'mailer'
end
5.メール本文のレイアウトを作成

メール本文のレイアウトを作成するためは命名規則にしたがってerbファイルを作成します。
(app/views/メイラー名_mailer/メイラークラスのメソッド名.text.erb)

app/views/inquiry_mailer/send_mail.text.erb
==================================
<%= @inquiry.name %> 様 から問い合わせがありました。
==================================

<お問い合わせ内容>
<%= @inquiry.text %>
6.メールをプレビューで確認

ActionMailer::Previewを継承したクラスは、generateコマンドで生成したInquiryMailerPreviewクラスが存在するので、そちらに下記を記述して確認する。

spec/mailers/previews/inquiry_preview.rb
# Preview all emails at http://localhost:3000/rails/mailers/inquiry
class InquiryPreview < ActionMailer::Preview
  def inquiry
      inquiry = Inquiry.new(name: "三原 蓮之介", text: "問い合わせメッセージ")

      InquiryMailer.send_mail(inquiry)
  end

  private
  def inquiry_params
    params.require(:inquiry).permit(:name, :text)
  end
end

確認方法は、サーバーを起動し、http://localhost:3000/rails/mailers/inquiry に接続。
※接続先はファイルの一番上に書いてあります。

7.実際にメールを送信する

コントローラーに送信する処理を記述する。
そうすれば、該当のアクションを実行された時にメールが送信されます。

app/controllers/inquiries_controller.rb
class InquiriesController < ApplicationController

〜中略〜

  def create
    @inquiry = Inquiry.new(inquiry_params)

    if @inquiry.save
      # こちらのコマンドを実行することでメールが送信される
      InquiryMailer.send_mail(@inquiry).deliver
      redirect_to inquiries_path, flash: {success: '送信が完了しました'}
    else
      flash.now[:alert] = '必須項目を入力、もしくは入力内容に間違いがあります'
      render :index
    end
  end

  private
  def inquiry_params
    params.require(:inquiry).permit(:name, :text)
  end
end

コンソールで確認してみても良いです○

$ rails c

irb(main):001:0> inquiry = Inquiry.new(name: "サンプル太郎", text: "問い合わせメール")
irb(main):002:0> InquiryMailer.send_mail(inquiry).deliver_now

終わりに

意外と簡単に実装出来ますね。
個人的にGmailの設定をするってことに気付くのに少し遅れて時間がかかりましたが、そこだけしっかり設定すればつまづくことも少ないと思います!
よく使う機能だと思うので、実装できるようにしておきましょう!!

参考

【Rails入門】Action Mailerのメール送信を初心者向けに基礎から解説

【Rails】メール送信設定 〜gmail利用〜

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

WebPacker 3.x -> 5.x

はじめに

※この文章はベータ版です。

既存のプロジェクトで Rails5.2 + WebPacker 3.5.5 -> Rails6.1 + WebPacker5.2.1
したので、手順etcをメモしておきます。

Rails5.2 -> Rails6.1

プロジェクトのrails & Webpacker gem を更新する

プロジェクトのルート・パスに移動後、gems.rb(Gemfile)を編集し、
バンドルされたgemを更新します。

% vim gems.rb
gem 'rails', '~> 6.1.0'
gem 'webpacker'
% bundle update

表示されるメッセージでgemの依存関係を確認し、依存関係に問題がなくなるようにgems.rb(Gemfile) を編集し、'bundle update' を実行します。
この作業をエラーが出なくなるまで繰り返します。

アップデートタスクを実行する

% bundle exec rails app:update

プロジェクトをGit etcでリポジトリでバージョン管理していない場合には
バックアップをとってから実行します。

実行により、いくつかのファイルのコンフリクトが指摘され、上書きするかどうか
訊いてきますが、全て、上書きします。

上書きされたファイルとリポジトリ上(or バックアップ)にある対応するファイルの
差分をチェックし、上書きされたファイルに必要な記述を復元します。

Webpacker 3.5.5 -> 5.2.1

% bundle exec rails webpacker:install

実行により、いくつかのファイルのコンフリクトが指摘され、上書きするかどうか
訊いてきますが、全て、上書きします。

上書きされたファイルとリポジトリ上(or バックアップ)にある対応するファイルの
差分をチェックし、上書きされたファイルに必要な記述を復元します。

Vue.js まわりのアップデート (オプション)

% bundle exec rails webpacker:install:vue

他のフレームワークを利用している場合には、対応した
タスクがあれば、それを実行します、対応したタスクがない
場合には自前で更新します。

babel-loader の設定変更 (オプション)

Vue.jsでHTMLテンプレートのコンパイルをブラウザでの実行に行う場合にはconfig/webpack/environment.js に次の設定を追加します。

// Vue.js フル版(Compiler入り)
environment.config.resolve.alias = { 'vue$': 'vue/dist/vue.esm.js' }

この設定がないときは、コンパイラなしの vue/dist/vue.runtime.esm.js
読み込まれます。
(Vue.jsでHTMLテンプレートの実行時コンパイルを行なっている場合、エラーに
なります。)

rails-erb-loaderのアップデート (オプション)

% bundle exec rails webpacker:install:erb

node_modulesの更新

% yarn install

表示されるメッセージで NPM の依存関係を確認し、必要な NPM があれば
yarn add でインストールしてから、再度、yarn install を実行します。
この手順をエラーが出なくなるまで繰り返します。

.babelrcの削除

Webpackerの3.xでは必要でしたが、Webpacker4.x 以降では、babel.cofig.js、
postcss.config.js、.postcssrc.yml に置き換わっているので、削除します。

バンドル

% bin/webpack

動作確認

% bundle exec rails s

rails-ujs -> @rails/ujs (オプション)

% yarn add @rails/ujs

Sprocketを利用している場合は、app/assets/javascripts/application.js
rails-ujs@rails/ujsに変更します。

//= require @rails/ujs

Webpackerを利用しているorする場合は、'app/javascript/packs/application.js'
に、次の2行を追加します。

import Rails from '@rails/ujs';
Rails.start();

Webpackerでの管理に新規に移行する場合には、erbにある javascript_include_tag
'application'
javascript_pack_tag 'application' に変更します。

WebPackerの 設定変更 (オプション)

SplitChunkの有効化 (オプション)

config/webpack/environment.js に次の設定を追加します。

environment.splitChunks()

erbにある javascriot_pack_tagjavascript_packs_with_chunks_tag
に変更します。

babel-loaderの設定変更

config/webpack/environment.js に次の設定を追加します。

environment.loaders.delete('nodeModules')

Webpacker4.x以降では、デフォルトでは、node_modulesディレクトリにあるファイルが
babel-loader経由でのトランパイルの対象になっています。

この追加により、WebPacker3と同様にWebpacker4以降でもbabel-loaderが'node_modules'
ディレクトリを無視する挙動になります。

Webpacker4以降でブラウザで動作確認を行なった際に、node_modules下にあるファイルで
エラーが発生している場合、この設定変更を試すことをオススメします。

node_modules ディレクトリにあるファイルがbabel-loader経由でのトランパイルの
対象になっていることにより、思わぬ変換が行われていることが原因である可能性が
あります。

例えば、node_modules ディレクトリにあるバンドル対象のファイル内に
module.exports =の記述がある場合、WebPacker4.x以降のデフォルト設定では、

Cannot assign to read only property 'exports' of object

エラーが発生することがあります。

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

signup後の画面で、profile,logoutを表示されない問題を解決した。

発生した問題

ブログの完成に向けて、ログインシステムを構築する過程でエラーが発生した。
http://localhost:3000/users/new
において、Signupを実行後、UserのProfile画面にリダイレクトはできただが、ページ上部にProfileとLogoutが表示されず、代わりにSignUpとLoginが表示されてしまう。
signup_error.gif

当初考えられた原因

 (1)app/helpers/sessions_helper.rbで定義し、layouts/application.html.erbに記述した”logged_in?”が有効でない。
 ・・・しかし、コードの記述自体に誤りは見られず、条件分岐もlogged_in?メソッド(※)も発火している。

logged_in?メソッドを格納しているapp/helpers/sessions_helper.rb
module SessionsHelper
  def current_user
    @current_user ||= User.find_by(id: session[:user_id])
  end
  def logged_in?
    current_user.present?
  end
end

 (2)そもそも、”rails g controller sessions new”実行時に、sessions_helper.rbが生成されなかった。
 ・・・これは「- CSS、JavaScript、Helperのファイルをrails g コマンドで自動生成しない設定を記述して、これらの余分なファイルができない」ようにするため、”config/application.rb”に下記の記述をしたことで、意図的に自動生成せず、代わりに自分でsessions_helper.rbファイルを作成した。

config/application.rb
require_relative 'boot'
require 'rails/all'
Bundler.require(*Rails.groups)
module HogehogeAppli
  class Application < Rails::Application
  #省略
    config.generators do |g|
      g.assets false
      g.helper false
    end
  end
end

 おそらく、上記(2)が原因で上手くlogged_in?が発火していないと予想していた。しかし、
http://localhost:3000/sessions/new
(Loginページ)でログインした場合は、正常にProfileとLogoutが表示されるため、なぜlogged_in?が発火しないのかが分からない。
 SignUp後に、ProfileとLogoutを表示させるためにはどうしたらよいのか、悩んでしまった。

解決法

 logged_in?メソッドは何の問題も発火しているため、問題点は違うところにあった。

 そもそもログイン状態とは、ユーザーのブラウザ内のcookiesに暗号化されたユーザーIDを付与することをログインといい、それが保持され続けていることをログイン状態という。(ブラウザとサーバ間で同一のユーザIDが突合し合えている状態)をいい、その仕組みはcreate時にsessionメソッドを使ってcookiesにユーザIDを持たせることでログイン状態が実現できる。
 
 Login(=ドメイン名/sessions/new)後には正常にProfileとLogoutを表示させられて、Signup(=ドメイン名/users/new)では表示されず代わりにSignupとLoginが表示されてしまう…。

 この違いについて焦点を当てることが解決につながると考え、users_controller.rb内のcreateアクションでログイン状態を作れていないことが判った。

app/controllers/users_controller.rb
def create
    @user = User.new(user_params)
    if @user.save
      #下記1行を追記し、ユーザーのブラウザ内のcookiesに暗号化されたユーザーIDが自動で生成されるようにした。
      session[:user_id] = @user.id
      redirect_to user_path(@user.id)
    else
      render :new
    end
  end

 結果として、別添画像のとおりSignup後の画面でProfileとLogoutを表示させることに成功した。
signup_correct.gif

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

Rubyで日付のデータを取得する方法

Dateクラスに対しクラスメソッドを使用

Rubyでは日付を扱うクラスとしてDateクラスが用意されており、これを使用することによりうるう年やひと月ごとの日数、曜日などの管理といった単なる数字のみでは正確に算出することが困難なデータを簡単に取得することが可能となる。
日付以外にも時間を管理するクラスや、日付と時間の両方を扱うクラスが存在する。

日付や時間に関する主なクラス

クラス名 管理可能なデータ   
Dateクラス   日付     
Timeクラス 時間
Datetimeクラス 日付と時刻の双方

Dateクラスを使う前準備(require)

Dateクラスを使用する際には、Rubyのコード上に"Date"ライブラリーを呼び込む必要がある。
その役割を果たすのがrequireメソッドであり、これにより外部ファイルを参照して、そこで定義されているクラスを取得することが可能となる。
※Railsのアプリケーションの場合はクラスはどのファイルにおいても参照できる為、require "~"の記述は不要

・具体的な使用方法

require "date"
#日付に関わるライブラリ"date"を取り込む
:
:

Dateクラスのインスタンス(オブジェクト)を取得する主な方法

  • todayメソッド(今日の日付を自動で算出してDateオブジェクトを作成)
require "date"
today = Date.today
#今日の日付を自動で算出してDateオブジェクトを作成
  • parseメソッド(引数で渡した文字列をDateオブジェクトに変換して作成)
require "date"
today_str = 2020-12-30
Date.parse(today_str)
#2020年12月30日のDateオブジェクトが作成される
  • newメソッドによる作成(引数で渡した整数を元にDateオプジェクトを作成)
require "date"
today = Date.new(2020, 12, 30) 
#2020年12月30日のDateオブジェクトが作成される

作成したDateオブジェクトから年や月などを取得する

上記の方法で作成したDateオブジェクトから特定の情報を取得する場合は、以下のようなコードを使用する。

require "date"
today = Date.today
today.year # 作成したDateオブジェクトから年を取得
today.mon # 作成したDateオブジェクトから月を取得
today.mday # 作成したDateオブジェクトから日を取得
today.wday # 作成したDateオブジェクトから曜日を数字として取得

記載内容に間違い等ございましたらご指摘頂けると嬉しいです。
ご連絡お待ちしております。

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

Rails5.2でMaterial Design for Bootstrapを使う

何をしたか

Railsの課題を実施しています。装飾に関しては、Material Design for Bootstrapを使用することになっていたのですが、WebpackerではないJS系のライブラリの導入が久しぶりで...。
だいぶ忘れていたので手順をメモします。

なお、実行環境は以下の通りです。

  • Rails 5.2.3
  • Ruby 2.6.4

Material Design for Bootstrapとは?

Material Design for Bootstrap(以下、MDB)は、Googleのマテリアルデザインに準拠した、Bootstrapのフレームワークです。

Githubの公式レポジトリに、色々便利なリンクがありました:relaxed:

導入

MDBは5.0系からは、jQueryを前提としたものではなくなっています。先にもあげたこちらのダウンロードリンクからは、さまざまなJSフレームワークに対応したものが入手可能です。

今回は、MDBの4.1.1を利用するので、jQueryの導入から始めます。

jQuery

Rails5.1からは、RailsにjQueryはデフォルトで入っていないので、別途導入する必要があります。

Gemfileに以下のように記載し、、、

Gemfile
gem 'jquery-rails'

bundle installします。

application.jsには以下のように追記しましょう。(require turbolinksは取ってしまっています)

assets/javascripts/application.js
//= require jquery  # 追記
//= require rails-ujs # 追記
//= require activestorage
//= require_tree .

MDB本体の導入

MDB本体はnpmでダウンロードします。gemもあるのですが、うまく動かない、とのことでカリキュラムではnpmが推奨されていました。サイトを見る限り、少し古そうですね。。。

$ npm install bootstrap-material-design@4.1.1

必要なファイル類の読み込み

すみません、ここからはお手本コードの写経であまり深く整理できていないのですが、MDBを動かすために必要なcssファイルを読み込みます。

assets/stylesheets/application.scss
@import 'bootstrap-material-design/dist/css/bootstrap-material-design';

ここまでで、自分の環境ではCSSと基本的なJSに関するMDBは動作しました。
おそらく、tooltipなど特定のものに関しては、依存するライブラリpopper.jsなども必要になるはずとは思います^^。
まだ、使用する場面はアプリ内にはないので、できたら追記しようかとは思いますが、ここまでで一旦記事はリリースしようと思います。:relaxed:

なにか過不足などありましたら、ご指摘いただければ嬉しいです。

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

日時の文字列を、Time.zone.parseしてRailsに認識させる方法

Time.zoneが一致しなくて検索できなかった問題

筆者がRailsのアプリを開発している時に、
日付を一致させる時にひと悶着あったので、備忘録として残しておきます。

今回使用する例

productsテーブル

id date (datetime型)
1 2020-11-01 15:00:00
2 2020-11-02 00:00:00

DBの時刻はUTCとします。

問題

下記の例がtrue or falseを返す理由が説明できるでしょうか?
RailsアプリのTime.zoneは次の状態だとします。

[*] pry(#<ProductsController>)> Time.zone
=> #<ActiveSupport::TimeZone:0x000055cc45b31408
 @name="Tokyo",
 @tzinfo=#<TZInfo::DataTimezone: Asia/Tokyo>,
 @utc_offset=nil>

例題

[1] pry(#<ProductsController>)> 
Product.find(1).date == "2020-11-01 15:00:00"
=> true

[2] pry(#<ProductsController>)> 
Product.find(1).date == "2020-11-02 00:00:00"
=> false

[3] pry(#<ProductsController>)> 
Product.find(1).date == Time.zone.parse("2020-11-01 15:00:00")
=> false

[4] pry(#<ProductsController>)> 
Product.find(1).date == Time.zone.parse("2020-11-02 00:00:00")
=> true

[5] pry(#<ProductsController>)> 
Product.find(2).date == "2020-11-01 15:00:00"
=> false

[6] pry(#<ProductsController>)> 
Product.find(2).date == "2020-11-02 00:00:00"
=> true

[7] pry(#<ProductsController>)> 
Product.find(1).date == Time.zone.parse("2020-11-01 15:00:00")
=> false

[8] pry(#<ProductsController>)> 
Product.find(1).date == Time.zone.parse("2020-11-02 00:00:00")
=> false

[9] pry(#<ProductsController>)> 
Product.find(1).date == Time.zone.parse("2020-11-02 09:00:00")
=> true


回答

[1] pry(#<ProductsController>)> 
Product.find(1).date == "2020-11-01 15:00:00"
=> true
# 文字列で検索した場合、DBのレコードと一致するものを検索します。
# つまり、2020-11-01 15:00:00という値が一致するのでtrueとなります。

[2] pry(#<ProductsController>)> 
Product.find(1).date == "2020-11-02 00:00:00"
=> false
# 純粋な文字列を比較してるので、
# 2020-11-01 15:00:00 == 2020-11-02 00:00:00はfalseとなります。

[3] pry(#<ProductsController>)> 
Product.find(1).date == Time.zone.parse("2020-11-01 15:00:00")
=> false
# 今回のRailsアプリのTime.zoneはTokyoでした。
# Time.zone.parseとは、文字通りアプリのタイムゾーンに合わせて変換することです。
# UTCとTokyoの時間は9時間ずれてる(Tokyoが遅れてる)
# DBにsaveする時、2020-11-02 00:00:00 ➡️ 2020-11-01 15:00:00に変換されます。
# RailsがDBからレコードを取り出す時、+9時間して返します。

[4] pry(#<ProductsController>)> 
Product.find(1).date == Time.zone.parse("2020-11-02 00:00:00")
=> true
# 上記で説明したとおり、Railsのタイムゾーンと同じ値を検索&parseしたので、trueを返します。

[5] pry(#<ProductsController>)> 
Product.find(2).date == "2020-11-01 15:00:00"
=> false
# 文字列で検索した場合、DBと一致する値を返すため

[6] pry(#<ProductsController>)> 
Product.find(2).date == "2020-11-02 00:00:00"
=> true
# 文字列で検索した場合、DBと一致する値を返すため

[7] pry(#<ProductsController>)> 
Product.find(1).date == Time.zone.parse("2020-11-01 15:00:00")
=> false
# parseした後はDBの値 - 9 されるためfalse
# parseした後は、2020-11-01 06:00:00になる

[8] pry(#<ProductsController>)> 
Product.find(1).date == Time.zone.parse("2020-11-02 00:00:00")
=> false 
# parseした後はDBの値 - 9 されるためfalse
# parseした後は、2020-11-01 15:00:00になる

[9] pry(#<ProductsController>)> 
Product.find(1).date == Time.zone.parse("2020-11-02 09:00:00")
=> true
# parseした後は、2020-11-02 00:00:00になるのでtrue

言いたかったこと

今回のRailsのTime.zoneはTokyoなので、
バックエンドのデータに-9時間してsaveしてる。

それに合わせて、Time.zoneをTokyoにparseしたら、
欲しかった情報を取得できました!

参考

ようやく:こうしきよめ
https://api.rubyonrails.org/classes/ActiveSupport/TimeZone.html

助けられたteratail
https://teratail.com/questions/148819

RubyとRailsにおけるTime, Date, DateTime, TimeWithZoneの違い
https://qiita.com/jnchito/items/cae89ee43c30f5d6fa2c

おまけ

ふと疑問に思ったので、
ransackのコード見たら、Time.zone.parseしてました。

def cast_to_time(val)
  if val.is_a?(Array)
    Time.zone.local(*val) rescue nil
  else
    unless val.acts_like?(:time)
      val = val.is_a?(String) ? Time.zone.parse(val) : val.to_time rescue val
    end
    val.in_time_zone rescue nil
  end
end
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[個人開発]経験を共有するWebアプリ作ってみた!

新しいwebアプリ開発しました。「Elder」という人生経験を募集、公開、評価できるサイトです。今回は作ったwebアプリの紹介、サービスのネタの考え方、herokuの無料プランでスリープにさせない方法など書いていきます。今回は前回みたいなヘマしないように頑張ります(^^♪

今回作ったWebアプリはこちら

紹介

Elder-top.png

↑トップページ
人生経験を共有するコミュニティサイトです。
人生経験の募集、エッセイ、一言だけ投稿、などができます。
現在技術系の記事ばかりですが、分野は問いません。

募集中のところはスライドショーになっていて動きます。一か所でも動くところがあると凝ったサイト感が出ます。
イメージカラーはを選びました。
使ってみた感想として、いろんな色とは合わないけどその代わりたくさん使っても不自然になりにくいと感じました。
普通、同じ色ばっかり使うとそれはそれで変な感じになるんですがね。

なぜつくったか

人生経験ってなかなか聞く機会も話す機会もなく、もっと共有したほうがいいと思ったから。

今回使った技術

gem
rails6
ruby2.7
postgresql
heroku free
Logo Garden
gem 'ridgepole'
gem 'slim-rails'
gem 'html2slim'
gem 'pry-rails'
gem "devise"
gem 'devise-i18n'
gem 'devise-i18n-views'
gem 'rails-i18n'
gem 'carrierwave'
gem "kaminari"
gem 'ransack'

Logo Gardenとはロゴ制作サイトです。地味に今回はじめてちゃんとしたロゴを作りました。
Elder.png

ちょい粗いですがこれが完全無料で作れるんですよ。すごいなー(感心)。もっと凝ったものも作れたんですけどシンプルが一番かなーと。
https://www.logogarden.com/

あと、bootstrapは使ってません。

サービスのネタの決め方(個人開発)

なんでサービスを作りたいのか。

  • サービスを届けたい人は本当に喜ぶ?
  • 本当にそのアプリで問題解決することができる?。
  • 有料でも使いたいか(無料で出すとしても)
  • お金を扱わない
  • 初期費用がいらない
  • 個人で管理できる
  • ほぼコンテンツがいらない or 自分でコンテンツが作れる
  • 幸福になりたいのだったら、人を喜ばすことを勉強したまえ。

こんなもんだと思います。まだ成功するかわからないのにこんなこと書くのはあれですが間違ったことも言ってないと思います。
一時的に当たるネタというのはこれのどれかが抜けていると思います。

あと、一応保険かけときます。※個人の意見です

herokuの無料プランで運用

herokuの無料プランで一番きついのは、30分ごとのスリープだと思います。
これがなかったら無料プランで全然いいという方も多いと思います。

僕がやったのはのはHeroku Schedulerです。
10分ごとに自動でサーバーになにかさせるというものです。

 heroku addons:create scheduler:standard
 heroku addons:open scheduler 

ブラウザが開いたらadd jobをクリックして
Run Commandを

$ curl サイトのURL

ScheduleはEvery 10 minutesにして保存。

これでスリープしなくなります。ただバグがあったりするらしいので注意してくださいねー。

参考:
https://casualdevelopers.com/tech-tips/how-to-prevent-idling-of-free-dyno-on-heroku/#HerokudynoHeroku_Scheduler

振り返り

よかったこと

  • 完全無料で作れた(人件費抜き)
  • ロゴも作れた
  • railsコードの再利用ができた

悪かったこと

  • 途中でなんかいもあきらめそうになった

個人でやるとモチベーション管理が一番むずいです...
なにあともあれ使ってくみてださいね(^^♪

https://e-elder.herokuapp.com/

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

人生経験を共有するコミュニティサイト作ってみた! & サービスの考え方

新しいwebアプリ開発しました。「Elder」という人生経験を募集、公開、評価できるサイトです。今回は作ったwebアプリの紹介、サービスのネタの考え方、herokuの無料プランでスリープにさせない方法など書いていきます。今回は前回みたいなヘマしないように頑張ります(^^♪

今回作ったWebアプリはこちら

紹介

Elder-top.png

↑トップページ
人生経験を共有するコミュニティサイトです。
人生経験の募集、エッセイ、一言だけ投稿、などができます。
現在技術系の記事ばかりですが、分野は問いません。

今回使った技術

gem
rails6
ruby2.7
postgresql
heroku free
Logo Garden
gem 'ridgepole'
gem 'slim-rails'
gem 'html2slim'
gem 'pry-rails'
gem "devise"
gem 'devise-i18n'
gem 'devise-i18n-views'
gem 'rails-i18n'
gem 'carrierwave'
gem "kaminari"
gem 'ransack'

Logo Gardenとはロゴ制作サイトです。地味に今回はじめてちゃんとしたロゴを作りました。
Elder.png

ちょい粗いですがこれが完全無料で作れるんですよ。すごいなー(感心)。もっと凝ったものも作れたんですけどシンプルが一番かなーと。
https://www.logogarden.com/

サービスのネタの決め方(個人開発)

なんでサービスを作りたいのか。

  • サービスを届けたい人は本当に喜ぶ?
  • 本当にそのアプリで問題解決することができる?。
  • 有料でも使いたいか(無料で出すとしても)
  • お金を扱わない
  • 初期費用がいらない
  • 個人で管理できる
  • ほぼコンテンツがいらない or 自分でコンテンツが作れる
  • 幸福になりたいのだったら、人を喜ばすことを勉強したまえ。

こんなもんだと思います。まだ成功するかわからないのにこんなこと書くのはあれですが間違ったことも言ってないと思います。
一時的に当たるネタというのはこれのどれかが抜けていると思います。

あと、一応保険かけときます。※個人の意見です

herokuの無料プランで運用

herokuの無料プランで一番きついのは、30分ごとのスリープだと思います。
これがなかったら無料プランで全然いいという方も多いと思います。

僕がやったのはのはHeroku Schedulerです。
10分ごとに自動でサーバーになにかさせるというものです。

 heroku addons:create scheduler:standard
 heroku addons:open scheduler 

ブラウザが開いたらadd jobをクリックして
Run Commandを

$ curl サイトのURL

ScheduleはEvery 10 minutesにして保存。

これでスリープしなくなります。ただバグがあったりするらしいので注意してくださいねー。

参考:
https://casualdevelopers.com/tech-tips/how-to-prevent-idling-of-free-dyno-on-heroku/#HerokudynoHeroku_Scheduler

振り返り

よかったこと

  • 完全無料で作れた(人件費抜き)
  • ロゴも作れた
  • railsコードの再利用ができた

悪かったこと

  • 途中でなんかいもあきらめそうになった

個人でやるとモチベーション管理が一番むずいです...
なにあともあれ使ってくみてださいね(^^♪

https://e-elder.herokuapp.com/

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

[個人開発]人生経験を共有するコミュニティサイト作ってみた!

新しいwebアプリ開発しました。「Elder」という人生経験を募集、公開、評価できるサイトです。今回は作ったwebアプリの紹介、サービスのネタの考え方、herokuの無料プランでスリープにさせない方法など書いていきます。今回は前回みたいなヘマしないように頑張ります(^^♪

今回作ったWebアプリはこちら

紹介

Elder-top.png

↑トップページ
人生経験を共有するコミュニティサイトです。
人生経験の募集、エッセイ、一言だけ投稿、などができます。
現在技術系の記事ばかりですが、分野は問いません。

募集中のところはスライドショーになっていて動きます。一か所でも動くところがあると凝ったサイト感が出ます。
イメージカラーはを選びました。
使ってみた感想として、いろんな色とは合わないけどその代わりたくさん使っても不自然になりにくいと感じました。
普通、同じ色ばっかり使うとそれはそれで変な感じになるんですがね。

なぜつくったか

人生経験ってなかなか聞く機会も話す機会もなく、もっと共有したほうがいいと思ったから。

今回使った技術

gem
rails6
ruby2.7
postgresql
heroku free
Logo Garden
gem 'ridgepole'
gem 'slim-rails'
gem 'html2slim'
gem 'pry-rails'
gem "devise"
gem 'devise-i18n'
gem 'devise-i18n-views'
gem 'rails-i18n'
gem 'carrierwave'
gem "kaminari"
gem 'ransack'

Logo Gardenとはロゴ制作サイトです。地味に今回はじめてちゃんとしたロゴを作りました。
Elder.png

ちょい粗いですがこれが完全無料で作れるんですよ。すごいなー(感心)。もっと凝ったものも作れたんですけどシンプルが一番かなーと。
https://www.logogarden.com/

あと、bootstrapは使ってません。

サービスのネタの決め方(個人開発)

なんでサービスを作りたいのか。

  • サービスを届けたい人は本当に喜ぶ?
  • 本当にそのアプリで問題解決することができる?。
  • 有料でも使いたいか(無料で出すとしても)
  • お金を扱わない
  • 初期費用がいらない
  • 個人で管理できる
  • ほぼコンテンツがいらない or 自分でコンテンツが作れる
  • 幸福になりたいのだったら、人を喜ばすことを勉強したまえ。

こんなもんだと思います。まだ成功するかわからないのにこんなこと書くのはあれですが間違ったことも言ってないと思います。
一時的に当たるネタというのはこれのどれかが抜けていると思います。

あと、一応保険かけときます。※個人の意見です

herokuの無料プランで運用

herokuの無料プランで一番きついのは、30分ごとのスリープだと思います。
これがなかったら無料プランで全然いいという方も多いと思います。

僕がやったのはのはHeroku Schedulerです。
10分ごとに自動でサーバーになにかさせるというものです。

 heroku addons:create scheduler:standard
 heroku addons:open scheduler 

ブラウザが開いたらadd jobをクリックして
Run Commandを

$ curl サイトのURL

ScheduleはEvery 10 minutesにして保存。

これでスリープしなくなります。ただバグがあったりするらしいので注意してくださいねー。

参考:
https://casualdevelopers.com/tech-tips/how-to-prevent-idling-of-free-dyno-on-heroku/#HerokudynoHeroku_Scheduler

振り返り

よかったこと

  • 完全無料で作れた(人件費抜き)
  • ロゴも作れた
  • railsコードの再利用ができた

悪かったこと

  • 途中でなんかいもあきらめそうになった

個人でやるとモチベーション管理が一番むずいです...
なにあともあれ使ってくみてださいね(^^♪

https://e-elder.herokuapp.com/

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

Rails開発の変遷2020

概要

  • webサイトの作成はRailsを使ってきた。
  • 今年はHTML、CSSを学び、UIを作るようになった。
  • vue/nuxtを学んでから、開発の仕方が変わった

Webpack

  • Rails本来のview作成はerb形式。slimで書くのが普通らしい
  • Rails 5.2からWebpackをサポートし、vueを使ってHTMLを作成できるようになった。知らんけど。
  • viewsにはjavascriptを参照する記述のみ。実際の中身はjavascriptディレクトリに作成する

やり方

  • Qiitaにわかりやすいチュートリアルがあるので、それを参考にするのが良い。
  • vue側はvue CLIを使ったほうが開発が捗る。vue CLIである程度完成させてから、railsのjavascriptにコピペするような方法で作成した。

欠点

  • 何も設定せずに開発すると、vueを更新した後にrails sを立ち上げなおす必要がある。
  • vue-routerを使っていると、ホットリロードした時にルーティングエラーがでてしまう。

サーバー側とフロント側を分離して開発

  • railsはサーバーサイドのみに特化し、フロントはvueで作成する。
  • サーバーとフロントの切り分け設計ができるようになった。(APIを意識する)
  • エラーを見つけやすくなり、開発効率アップした
  • DBはfirebaseに移行(それまではpostgreSQLを使っていた)

今年使い方を学んだgem

Rails

active admin

  • 管理者画面を作成できる
  • Railsではデータを作成する時に、コンソールやseedでデータ作成できるが、active adminを使えば画面上で編集できる
  • UIはhtmlテンプレートではない形式で作成されている。そのため、管理者画面にマップとか入れたいと思っても実際無理。
  • firebase storageを使うようになってから使わなくなった。

devise

  • Railsでログイン機能を作るにあたってのデファクトスタンダード
  • session云々やpassword暗号化を良しなに実装してくれる(便利過ぎてsession云々を忘れるくらい)
  • バックとフロントを切り分けて開発するようになってから、ヘルパーメソッドを使えなくなった。
  • firebase authを使うようになって不要になった。

active record

  • Railsで画像をアップしたりする時に使うgem
  • erbでviewを作成するなら、恩恵を存分に受けられる。(controllerだけでURLから画像を取得することもできる)
  • active recordは本来の使い方をすれば便利だろうと思う反面、本来の使い方以外がしにくい(どこで何がどう動いているのか把握しにくい)
  • 画像ファイルのアップロードなら、carrierwaveのほうがどこで何を処理しているのかがわかりやすい

miniMagic

  • Railsで画像を加工・編集するためのgem
  • 参考文献が多くないため、最低限の使い方しかわからない
  • 処理に時間がかかる。LINE Messangerで使おうとしたが、処理スピードが遅く、まともに使えなかった。
  • 画像編集はjavascriptで行い、firestorageに画像ファイルを保存するような設計にしたため、サーバー側で画像を作成してフロント側に送るようなことがなくなった。

今後について

  • RailsでAPIモードで開発するなら、Railsである必要性はないかな、、、
  • node.jsでのexpressかpythonでのfastAPIを検討中
  • とはいえ、rubyは好きな言語であるのでrubyとrailsを使うメリットがあるなら、使っていきたい。
  • railsの活用法があれば教えてください。d
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

パラメーターが配列の場合、ストパラのparams.permitに含める方法

パラメーターが配列の場合、ストパラのparams.permitに含める方法

状況

1つのusersテーブルに対して、同時に複数のレコードを作成するために下記のようなパラメーターを送りました。

 Parameters: {"users"=>[{"name"}=>{"satoshi"}, {"name"}=>{"yuta"}]}

受けるストパラは下記

private

def users_params
  params.require(:users).permit(
      :id,
      :name
  )
end

発生したエラー

requireした後にpermitメソッドを使うと、
permit?そんなメソッドは知らんよ」と言われてしまいました。

[1] pry(#<UsersController>)> params.require(:users).permit
NoMethodError: undefined method `permit'
for #<Array:0x00007f2024a3ee40>from (pry):62:in `users_params'

(binding.pryの結果を出力しています)

理由

params.require(:publisheds)はArrayクラスだから、permitメソッドが使えない。

[2] pry(#<UsersController>)> params.require(:users).class
=> Array
[3] pry(#<UsersController>)> params.require(:users).respond_to?("permit")
=> false

情報:配列番号を指定するとpermitが通る

[4] pry(#<UsersController>)> params.require(:users)[0].respond_to?("permit")
=> true

[5] pry(#<UsersController>)> params.require(:users)[0].permit(:name)
=> <ActionController::Parameters {"name"=>"satoshi"} permitted: true>

配列(Array)で来てるなら、1個ずつ取り出せばいいんじゃね?って思いました。

解決法

mapで回して、配列をばらしてpermitして返す

private

def users_params
  params.require(:users).map do |user|
    user.permit(
      :id,
      :name
    )
  end
end

mapが使えた理由

ブロックを評価して配列を戻り値として持つ=rubyの仕様は、最後に評価した値を暗黙的に返す(returnする)から。

eachが使えないのはなぜ?

配列を回して引数を取るけど、その中でpermitをしても値を返さないから?
permitメソッド自体が、値の変更(代入)ではなく、許可だから少し挙動が違う?

細かな違いが難しい。。。
rubyマスターの方がいらっしゃいましたら質問させてほしいです。

参考:るりまのリンク

eachの仕様
https://docs.ruby-lang.org/ja/latest/method/Array/i/each.html

mapの仕様
https://docs.ruby-lang.org/ja/latest/class/Array.html#I_COLLECT

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

scopeメソッドの主な機能

scopeメソッド、名前から機能を推察するのがむずかしいだけでなく出てくる場面もいろいろでややこしいので整理した。

1 scope(name, body, &block)

特定の条件を持ったオブジェクトをデータベースへ問い合わせるためのクラスメソッドを生成する。モデルで呼び出す。コントローラのアクションの記述をシンプルにしたいときに使える。

shirt.rb
class Shirt < ActiveRecord::Base
  scope :red, -> { where(color: 'red') }
end

↓同義

shirt.rb
class Shirt < ActiveRecord::Base
  def self.red
    where(color: 'red')
  end
end

第一引数にメソッド名を表すシンボル、第二引数に検索条件を示すブロック{ where.. }を引数として受けたラムダ式“->“が返すProcオブジェクトが渡されている。
->はlambdaに代替しても同じ。

2 scope(*args)

ルーティングに関する処理。リソースルーティングをカスタマイズしたいときに使える。routes.rbで呼び出す。いろいろな用法、記法があるので一部抜粋。

2-1 パスにプレフィックスをつける

routes.rb
scope path: "/admin" do
  resources :posts
end

/admin/postsからpostsコントローラへのルーティングが可能になる。

2-2 名前付きルーティングヘルパーにプレフィックスをつける

routes.rb
scope as: "secret" do
  resources :posts
end

secret_posts_path, new_secret_posts_pathなどのヘルパーが生成され、それぞれ/posts, /posts/newに割り当てられる。

2-3 ある名前空間のコントローラへのルーティング

routes.rb
scope module: "admin" do
  resources :posts
end

/postsからAdminという名前空間の中にあるコントローラ(Admin::PostsController)へのルーティングが可能になる。

3 フォームヘルパーform_withにおけるオプション “:scope”

inputフィールド名にプレフィックスをつける

new.html.erb
<%= form_with scope: :post, url: posts_path do |form| %>
  <%= form.text_field :title %>
<% end %>

# =>
<form action="/posts" method="post" data-remote="true">
  <input type="text" name="post[title]">
</form>

:scopeオプションで渡されるpostが、text.fieldに渡された属性名titleをネストした形のフィールド名になる。これによりコントローラでparams[:post][:title]にアクセスできるようになる。
:modelオプションでインスタンスを渡した場合、Railsがスコープを推察しプレフィックスをつける。
スコープ情報がない場合、フィールド名は“title”となり、コントローラでアクセスできるのはparams[:title]になる。

まとめ

  • 特定の条件を持ったオブジェクトをデータベースへ問い合わせるためのクラスメソッドを生成する
  • リソースルーティングをカスタマイズする
  • (オプション)inputフィールド名にプレフィックスをつける

参考文献
Ruby on Rails API
Railsガイド

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

FactoryBotで関連データを一緒に作成 自分用

after(:crate)を使いましょう

case

userがteacher_accountにアップデートした際、
user_performanceを関連データとして作成。

user_performanceはポリモーフィック関連を利用

code

FactoryBot.define do
  factory :user do
    sequence(:username) { |n| "Testuser#{n}" }
    sequence(:email)    { |n| "tester#{n}@example.com" }
    password            { 'password' }
    phone_number        { "0#{rand(0..9)}0#{rand(1_000_000..99_999_999)}" }
    confirmed_at        { Date.today }

    trait :teacher_account do
      teacher   { true }

      after(:create) do |user|
        create(:user_performance, performancable: user)
      end
    end
  end
end

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

RSpecにてプライベートメソッドのテスト方法 自分用

結論

sendで送る

calculate_average_scoreはプライベートメソッド

let!(:user) { create(:user) }
context 'calculate_average_score' do
  subject { user.send(:calculate_average_score) }
  it      { is_expected.to eq ** }
end
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む