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

ビューファイルを編集してカラムのデータを表示の仕方

テーブルをposts、カラムをtextとした場合

app/views/posts/index.html.erb
<% @posts.each do |post| %>
  <%= post.text %>
<% end %>

となる
これをするとtextのカラムの指定をしてtextのデータだけ持って来てくれる
これをやらないと複数のデータを一度に表示しようとしたためにエラーが出る

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

ActiveRecord クラスとは、データをコントローラーで取得

テーブルから情報を取得するために必要なメソッドを兼ね備えたクラス
| メソッド | 用途 |
|:-----|:-----|
| all | テーブルの全てのデータを取得する |
| find | テーブルのレコードの内、ある1つのデータを取得する |
| new | クラスのインスタンス(レコード)を生成する |
| save | クラスのインスタンス(レコード)を保存する |
などがある
データをコントローラーで取得、ビューファイルでテーブルの単一データ表示の場合、、、テーブル名がposts(モデルはpost)として

app/controllers/posts_controller.rb
def index
   @post = Post.find(1)  # 1番目のレコードを@postに代入
end

となる
全てのデータを取得できるようにするには

app/controllers/posts_controller.rb
def index
   @posts = Post.all  # 全てのレコードを@postsに代入
end

となる
module名は、頭文字大文字、単語の境界に「アンダーバー」無しで、ファイル名は、頭文字小文字、単語の境界に「アンダーバー」有りという命名規則があるためこの命名規則に従っていないと、適切に読み込めない
名称       例
コントローラ名 posts
コントローラクラス名 PostsController
ファイル名 posts_controller.rb

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

【Rails】細かいあれこれ

細かいあれこれ

1.リンク付きでFont-awesomeを実装したい!

html.erb
<%= link_to(content_tag(:i, '', class: 'Font-awesomeの名前'), 遷移先のパス名(もし引数を渡したかったらここにモデル名を入れる)) %>

Ex)
<%= link_to(content_tag(:i, '', class: 'fas fa-pen'), edit_list_path(list)) %>

content_tagはHTMLを生成するメソッド。

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

【Rails devise】Deviseの実装周りのあれこれ

Deviseの実装周りのあれこれ

1.Gemのインストール

command
gem 'devise'
command
bundle install

2.deviseファイルの生成

command
rails g devise:install

3.deviseのビューファイルの生成

command
rails g devise:views

4.deviseのモデルの生成

command
rails g devise User(ここは任意です!Adminとかでもおけです)

5.deviseのテーブルの生成

command
rails db:migrate

参考:
https://github.com/heartcombo/devise

以上!

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

マイグレーションファイル、カラムの「型」とは

マイグレーションファイルは、テーブルの設計図・仕様書。db/migrate/20XXXXXXXXXXXX_create_XXXXX.rbのマイグレーションファイルを編集する。
create_table :posts do |t|
t.timestamps
の間にt.カラムの型 :カラムを必要なものを入れる
t.text :tite
t.string :text
t.text :image
など、、、
カラムの「型」は
|:-----|:-----|:-----|
| 型 | どのような型か | 用途 |
| integer | 数字 | 金額、回数など |
| string | 文字(短文) | ユーザー名、メールアドレスなど |
| text | 文字(長文) | 投稿文など |
| boolean | 真か偽か | はい・いいえの選択肢など |
| datetime | 日付と時刻 | 作成日時、更新日時など |
を参考にして記述する。
このままではデータベースにテーブルを作成はまだできてないので、ターミナルでrails db:migrateを実行する。Sequel Proでテーブルを作成できていればテーブルの完成

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

モデルとは、モデルの作成

モデルは、Railsの中でデータベースへのアクセスをはじめとする情報のやりとりに関する処理を担当している。簡単な解釈で書くとコントローラー=>モデル=>データベース、データベース=>モデル=>コントローラーと受け渡しになってるが、コントローラーは日本語、モデルは通訳、データベースは英語みたいに考えるとわかりやすいかも
モデルの作成はターミナルでrails g model モデル名で作られる。モデルを作るとVs codeの中にdb/migrate/ディレクトリに20XXXXXXXXXXXX_create_XXXXX.rbが作られる。

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

【Rails】最短でRailsにFont-awesomeを導入する

最短でRailsにFont-awesomeを導入する

1.Gemのインストール

gemfile
gem 'font-awesome-sass', '~> 5.12.0'
command
bundle install

2.scssでFont-awesomeを読み込む

application.scss
@import "font-awesome-sprockets";
@import "font-awesome";

5秒ですね!

参考:
https://github.com/FortAwesome/font-awesome-sass

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

【Rails binstubs】Rails sできない時

Rails sできないとき

この記事では
・Rails sできない
・なにやらエラーにbinstubsという単語がある

という方のエラー解決に役立てればと思います!

1.binとやらを消す

command
rm -rf ./bin

2.binディレクトリを刷新!

command
rake app:update:bin   #railsのバージョンが5.2以上の方はこちら

rake rails:update:bin   #railsのバージョンがそれ未満の方はこちら

参考:
https://stackoverflow.com/questions/7546869/problem-running-rails-s

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

SEしてるけど実はあんまりコード書いたことないんだよねって人に捧ぐ、Rails on Dockerハンズオン vol.9 - Sign in -

はじめに

第9回目です。
前回はサインアップ機能を作ってみましたね。今回はサインインやサインアウト機能を作ってまいります。
今後、サインインしていないと使えない機能や見れないページなども作っていきますので、アプリがユーザーのサインイン状況を知ることができてサインインしているユーザーを特定できるようにしましょう!

セッション

RailsはRESTfulなWebアプリケーションフレームワークなのでステートレスなアプリです。
しかし、サインイン機能を実装するとなるとそのユーザーがサインイン状態であることをアプリが知る必要がありますし、アプリはそのユーザーがどのユーザーなのかを特定する必要があります。
このために使われるのがセッション(Session)です。

Railsではセッションを管理する便利なsessionメソッドが用意されています。
今回はこのsessionメソッドを使ってサインイン状態を管理し、マイページ機能、つまりサインインしているユーザーのユーザー詳細ページに遷移する機能を作っていきましょう。
サインインしていない場合はユーザーを特定できないので、マイページには遷移させずトップページにリダイレクトをかけるようにもしていきます。

このように、セッションでサインイン状態を管理できるようになれば、その状態によるページや動作の出しわけやサインインユーザーに合わせたコンテンツの出しわけが可能になります。

今回はセッションを一つのリソースと捉えて実装していきます。
つまり、サインインしたときにsessions#createでセッションを作成したり、サインアウトしたときにSessions#destroyでセッションを削除したりさせます。

サインインページを作ろう

セッションはSessionsコントローラーで管理してきます。
アクションとしては、

  • new: サインインページに遷移
  • create: セッション作成(サインイン処理)
  • destroy: セッション削除(サインアウト処理)

が必要になってきます。
今回はrails gコマンドに頼らずにディレクトリやファイルを作成していきますね。

ルーティングを作成する

まずはルーティングを作成しましょう。

config/routes.rb
Rails.application.routes.draw do
  ...
  get     '/sign_in',   to: 'sessions#new',     as: :sign_in
  post    '/sign_in',   to: 'sessions#create',  as: :create_session
  delete  '/sign_out',  to: 'sessions#destroy', as: :sign_out
end

この辺りはもう慣れてきましたよね。

コントローラーを作成する

お次はコントローラーを作ります。

# touch app/controllers/sessions_controller.rb
app/controllers/sessions_controller.rb
class SessionsController < ApplicationController
  def new
    @user = User.new
  end 

  def create
  end

  def destroy
  end
end

とりあえずルーティングと対応したアクションを記述しておきます。
newアクション、つまりサインインページではまたUserモデルを扱います(ユーザーのサインイン情報を入力してもらうので)。
そのため、newアクションはUsersコントローラーと同じように@user = User.newを記述してます。

サインインページを作成する

さて、newアクションがレンダリングするnew.html.erbをコーディングしてきましょう。

まずは、Sessionsコントローラーのビューファイルを格納するディレクトリを作成し、ビューファイルを作りましょう。

# mkdir app/views/sessions
# touch app/views/sessions/new.html.erb

さて、ビューファイルをコーディングしてみます。

app/views/sessions/new.html.erb
<div class="container">
  <h1 class="my-5">Sign in</h1>

  <%= form_with model: @user, url: create_session_path, local: true do |form| %>
    <div class="form-group">
      <%= form.label :email %>
      <%= form.text_field :email, class: "form-control" %>
    </div>
    <div class="form-group">
      <%= form.label :password %>
      <%= form.password_field :password, class: "form-control" %>
    </div>
    <div class="form-check">
      <%= check_box_tag :visible_password, :visible, false, class: "form-check-input" %>
      <%= label_tag :visible_password, "パスワードを表示する" %>
    </div>

    <div class="form-group mt-5">
      <%= form.submit "Sign in", class: "btn btn-primary form-control" %>
    </div>
  <% end %>
  <p class="text-center">登録がまだの方は<%= link_to "こちら", sign_up_path %></p>
</div>

<%= javascript_pack_tag 'visible_password' %>

基本的には前回のサインアップページと同じですね。今回はサインインなのでemailpasswordを入力項目に指定しました。
また、passwordはサインアップページと同様、チェックボックスで表示非表示を変更できるようにしています。

『Sign in』ボタンの下にまだサインアップが終わっていないユーザー向けにサインアップページへのリンクを追加しています。
<%= link_to "こちら", sign_up_path %>だけで適切なaタグを作成してくれるのはやはり便利ですね。

一度http://localhost:3000/sign_inにアクセスしておきましょう。以下のようなページが表示されたら、ここまでのコーディングは成功です!
image.png

サインインページへのリンクを作る

サインインページの形が出来上がってきたので、ここでサインインページへのリンクを以下のページにつけていこうと思います。

  • ヘッダーの「Sign in」リンク
  • サインアップページの下部に「登録済みの方はこちら」リンクを設置

ヘッダーの「Sign in」リンク

ヘッダーの「Sign in」リンクはまだ遷移先を指定できていませんでしたね。
やっとサインインページができあがってきたので遷移先としてsign_in_pathを指定しておきます。

app/views/layouts/application.html.erb
...
<li class="nav-item"><%= link_to "Sign in", sign_in_path, class: "nav-link" %></li>
...

コーディングが終わったら、一度トップページに遷移してヘッダーの「Sign in」リンクを選択してみましょう。
サインインページに遷移できたら成功です!

サインアップページの下部に「登録済みの方はこちら」リンクを設置

サインインページで「登録がまだの方はこちら」リンクを設置したように、サインアップページにも「登録済みの方はこちら」リンクを設置してみましょう。

app/views/users/new.html.erb
...
    <div class="form-group mt-5">
      <%= form.submit "Sign up!", class: "form-control btn btn-primary" %>
    </div>
  <% end %>
  <p class="text-center">登録済みの方は<%= link_to "こちら", sign_in_path %></p>
...

こちらもサインアップページで「こちら」をクリックしてサインインページに遷移できれば成功です!

サインイン処理を作成する

ここまででサインインページの形ができあがりました。
次はサインイン処理(create)をつくっていきます!

サインイン処理ではサインインページで入力されたemailpasswordからユーザーを検索します。
ユーザーがヒットすればそのユーザーのユーザー詳細ページ(users#show)、ユーザーが存在しない、またはパスワードが誤っているような場合はエラーメッセージが表示されるようにしましょう。

少しおさらいですが、モデルを検索する場合はfind_byメソッドを使うことで指定した属性に対して検索をすることができました。
さらに、has_secure_passwordをもつモデルはauthenticateメソッドを使ってパスワード認証ができることも思い出しましょう。
この2つを組み合わせることでユーザーの検索し認証することができそうですね。

では、createアクションを記述していきます。

app/controllers/sessions_controller.rb
...
def create
  @user = User.new(email: params[:user][:email])
  user = User.find_by(email: @user.email.downcase)
  if user && user.authenticate(params[:user][:password])
    flash[:success] = "サインインしました。"
    redirect_to user
  else
    flash.now[:danger] = "#{User.human_attribute_name(:email)}または#{User.human_attribute_name(:password)}をもう一度確認してください。"
    render :new
  end
end
...

少し複雑に見えるかもしれません。1行ずつ解説していきますね。

@user = User.new(email: params[:user][:email])

まず、@userに新しくUserモデルを代入しています。属性値はemailparams[:user][:email]を持っています。
paramsはフォームからのリクエストの値を取得するメソッドで、フォームのinputタグでname="user[email]"と定義されている値は[:user][:email]から取得することができます。
@userparams[:user][:email]の属性値をもつUserモデルを代入しているのは、この処理がエラー(ユーザーが見つからない or パスワード認証が通らない)の場合に今入力したemailをデフォルトでフォームに入力された状態でサインインページを再度表示するためです。
これがないと入力したemailをキープする方法がなくてユーザーは毎回メールアドレスを再入力する手間になってしまいます。(パスワードは特性上、キープしないように作っています)

user = User.find_by(email: @user.email.downcase)

先ほど@userに設定したemailを使ってUserを検索しています。
Userモデルを作成するときにモデル側でemailを小文字化してからDBに保存するようにコーディングしたことを覚えているでしょうか?
今DBには必ず全て小文字のメールアドレスが保存されているので、find_byemailを検索する時もdowncaseで検索文字列を小文字化して検索しています。
find_byは検索対象が存在していた場合はモデルオブジェクトを返却し、存在しない場合はnilを返却するメソッドであることも改めて意識しましょう。

if user && user.authenticate(params[:user][:password])
  # trueの処理
else
  # falseの処理
end

次に条件分岐を設けています。&&は「アンド条件」、「かつ」を意味していますので、

  • user
  • user.authenticate(params[:user][:password])

の両方を満たした場合はtrue、どちらか一方でも満たさない場合はfalseの処理に分岐します。

まずuserの条件式をみてみます。
これはusernilfalseでないかどうかを検証しています。
先ほどfind_byは検索結果があればモデルオブジェクト、なければnilを返却するといいました。
なのでこのuserの条件はfind_byの結果そのemailをもつユーザーがそもそも存在しない場合falseの処理を実行するようにするための条件ということになります。

次にuser.authenticate(params[:user][:password])の条件式をみてみます。
これは順番的にuserの条件式を満たした場合に検証される条件式です。なのでuserにはUserモデルのモデルオブジェクトが入っています。
authenticateメソッドはhas_secure_passwordの便利機能の一つで、平文のpasswordを与えるとハッシュ化してDBに格納しているpassword_digestと照会し、同一であればモデルオブジェクトを、そうでなければfalseを返却してくれるものでした。

以上より、このif文の条件式の条件分岐は以下の通りになります。

入力したemailのユーザーが パスワードが 実行する処理
いない - falseの処理
いる 誤っている falseの処理
いる 正しい trueの処理

やりたいことに合致してますね。
ではtrueの処理、falseの処理をそれぞれみてみましょう。

flash[:success] = "サインインしました。"
redirect_to user

trueの処理はとても単純です。
検索し認証したユーザーのユーザー詳細ページにリダイレクトさせているだけです。flashでサインインメッセージも定義してますね。

flash.now[:danger] = "#{User.human_attribute_name(:email)}または#{User.human_attribute_name(:password)}をもう一度確認してください。"
render :new

falseの処理も最終的にはrender :newで再度サインインページを表示させていることがわかります。
その前にflash.nowメソッドを使ってエラー文を用意しています。
前回のUsersコントローラーのcreateアクションでもflashメソッドを使ってサインアップ成功時のメッセージをユーザー詳細ページに一時的に表示させることをしました。
flash.nowはそれのrender時に利用する版と思ってください。keydangerにしているのは前回と同じでBootstrapの命名規則に則っています。

flash.nowのメッセージの中身が若干複雑ですね。
まず、Rubyでは文字列の中に変数を入れる場合#{変数}と記述することができます。
"aaa" + 変数 + "bbb"のような書き方もできますが、"aaa#{変数}bbb"の方がよりスマートな気がしますよね。
その変数はUser.human_attribute_name(属性名)となっています。
human_attribute_nameメソッドは引数の属性のi18nで定義した文字列を返してくれます。ちょうどform.labelと同じような感じですね。
なので今回の場合、User.human_attribute_name(:email)は『メールアドレス』、User.human_attribute_name(:password)は『パスワード』が変数の結果として当て込まれます。

ここまででサインイン処理(createアクション)をみてきました。
最後にflash.nowのメッセージを表示するコードが今のsessions/new.html.erbにはないので、前回のusers/new.html.erbに倣って記述します。

app/views/sessions/new.html.erb
...
<h1 class="my-5">Sign in</h1>

<% flash.each do |msg_type, msg| %>
  <div class="alert alert-<%= msg_type %>"><%= msg %></div>
<% end %>

<%= form_with model: @user, url: create_session_path, local: true do |form| %>
...

動作確認

では、サインイン処理を動作確認してみましょう。
前回DBをリセットしてますので、もう一度John SmithさんをDBに登録しておきます。

> User.create(name: "John Smith", email: "john@sample.com", password: "password")

http://localhost:3000/sign_inにアクセスして色々とチェックしてみましょう!

エラー系

emailpasswordを入力していない

image.png
何も入力していない、またはどちらかだけでも入力していない場合はfind_byauthenticateのいずれかが必ず失敗するのでエラーメッセージが表示されていますね。

入力したemailのユーザーがいない

test@test.comみたいな適当なemailを入力して確認。
image.png
エラーメッセージが表示されているし、emailのテキストフィールドに入力してたtest@test.comが残っているのも確認できましたね。

emailpasswordの組み合わせが異なっている

john@sample.comと適当なパスワードjohn1234を入力して確認してみましょう。
image.png
こちらもエラーメッセージが表示されていますね。emailjohn@sample.comも残っています。

正常系

期待動作はjohn@sample.com&passwordでユーザー詳細ページに遷移することです。
やってみましょう!
image.png
ちゃんと期待通りの動作になりましたね。

セッション管理する

ここまででパスワード認証のロジックができあがりましたね。

ただ今のままでは、「認証」自体はできましたが「認証済み」という状態を管理することはできてません。
「認証済み」という状態を管理するためにセッション管理をする機能を作っていきます。

ちなみにRailsのsessionメソッドでは、半永続的で暗号化された一時Cookieが払い出されます。
これは仮にこのCookieが盗まれたとしても、それを使ってサインインを乗っ取ることはできないようになっています。
その代わりと言ってはなんですが、一度ブラウザを閉じると自動的に消える仕組みになっています。

SessionsHelperを作成する

セッション管理するためにsign_inメソッドやsign_outメソッドなどを自前で実装していきます。
これらはコントローラーの元になるapplication_controller.rbにコーディングしていきたいところですが、それではapplication_controller.rbに多くのメソッドを記述する必要がでてくるため可読性が確保できなくなる恐れがあります。

今回は、新たにSessionsHelperを作成し、その中でsign_inメソッド、sign_outメソッドを定義します。
このSessionsHelperをApplicationControllerが読み込んでコントローラー内でメソッドを利用できるようにすることで、可読性を損なうことなく機能を実装していきましょう。

まずSessionsHelperファイルを作成します。

# touch app/helpers/sessions_helper.rb
app/helpers/sessions_helper.rb
module SessionsHelper
end

ヘルパーはmodule [helper_name]で定義します。
最後に、ヘルパーをApplicationControllerから読み取るように定義しましょう。

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

SessionsHelperでsign_inメソッドを作成する

次に、SessionsHelperを編集して、sign_inメソッドを作成していきます。
Railsではsessionメソッドが用意されており、簡単にセッションを管理することができます。

app/helpers/sessions_helper.rb
module SessionsHelper

  def sign_in(user)
    session[:user_id] = user.id
  end

end

これだけで、sign_inメソッドを使ってuser.idをセッションに記録することができます。

サインインページでサインインが成功した場合にsign_inメソッドを呼び出してセッションを記録するようにしてみます。

app/controllers/sessions_controller.rb
...
def create
  ...
  if user && user.authenticate(params[:user][:password])
    flash[:success] = "サインインしました。"
    sign_in user
    redirect_to user
  else
  ...
end
...

redirect_toの前にsign_in userを追加しただけです。
SessionsControllerはApplicationControllerを継承しているのでApplicationControllerで読み込んでいるSessionsHelperを利用することができます。

current_userメソッドを作成する

ここまででサインインしたときにセッション情報としてサインインユーザーのidをCookieに格納できるようになりました。
今度はこれを使って、サインイン中のユーザー情報をアプリが特定するためのcurrent_userメソッドを作成しましょう。
このcurrent_userメソッドを使うことで、例えば<%= current_user.name %>でサインイン中のユーザーの名前を表示できるようにしたりします。

今、セッションにuser_idのキーに対してuser.idを格納しています。これはsession[:user_id]で取り出すことができます。
このことからUser.find(session[:user_id])またはUser.find_by(id: session[:user_id])でサインイン中のユーザーを特定できることを思いつきますね。
ただ、findメソッドはNotFoundの場合に例外が発生します。find_byメソッドの場合はNotFoundでもnilを返却するだけなのでif文などで簡単にエラー時の処理を実装できるのですが、例外の場合はちょっとクセがありますね。
今回はfind_byメソッドで実装するようにしましょう。

では、SessionsHelperに定義します。

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

少しつまずきそうなコードですね。ちょっと紐解きます。

まずわかりやすいところから、一番後ろにif session[:user_id]と記入しています。
これはこれよりも前の文章が実行される条件式になっています。session[:user_id]が存在する、つまりサインイン済みの状態であればこれよりも前の文章が実行されるというわけです。
current_userメソッドではこの1行以外にコードはないので、session[:user_id]が存在しない場合は何も実行されず、nilが返却されるようになります。

次に見慣れない||=をみてみてましょう。
これは見慣れないと思いますが、少しプログラミングをかじったことがある人であれば何かの言語で+=のような表現を見たことがあるのではないでしょうか?
Rubyでも同じような挙動をとりますが、a += ba = a + bと同義になりますね。
||はORを表す演算子ですので、a ||= ba = a || bと同義になります。
current_userは上で少しお話したように、<%= current_user.name %><%= current_user.email %>のように使われるケースを考えています。
これが同じビューファイルで2回呼び出された場合、User.find_by(id: session[:user_id])とだけコーディングしていた場合は2回ともDBから情報を取得しなくてはならなくなります。
そこで@current_user ||= User.find_by(id: session[:user_id])としておくことで、一度目に<%= current_user.name %>時に@current_userがセットされるため二度目の<%= current_user.email %>の場合はDBにアクセスすることなく前回の検索時のモデルオブジェクトを再利用することができるようになります。
DBの負荷軽減やDBアクセスの回数削減によるレスポンス向上を目的として、こういった記述にしてみました。

まとめると、current_userメソッドは呼び出されたときに以下の動作をします。

サインイン ユーザー 処理
- nil
NotFound nil
Found サインイン済みユーザーのモデルオブジェクト

書き方を変えると以下と同じですね。

def current_user
  if session[:user_id]
    if @current_user
      @current_user
    else
      User.find_by(id: session[:user_id])
    end
  end
end

KStep取れそうですね。笑
上の書き方の方がシンプルで可読性高いですよね。

サインイン状態を確認するsigned_in?メソッドを作る

どんどんいきましょう。
次にサインイン状態をtruefalseで返却するsigned_in?メソッドを作ってみます。
このメソッドによってユーザーのサインイン状態に合わせた処理を簡単に実装することができます。

先ほどのcurrent_userメソッドはユーザーがサインインしている場合はそのユーザーのモデルオブジェクト、サインインしていない場合はnilを返却するメソッドでした。
そしてRailsにはnilの場合trueを、そうでない場合falseを返却するnil?メソッドが用意されています。
さらに!truefalseが入れ替わる否定演算子です。

これらを組み合わせればsigned_in?メソッドが作れそうですね。

app/helpers/sessions_helper.rb
module SessionsHelper
  ...
  def signed_in?
    !current_user.nil?
  end
  ...
end

サインアウトするsign_outメソッドを作成する

最後にサインアウトするメソッドとしてsign_outメソッドを作ってみましょう!

app/helpers/sessions_helper.rb
module SessionsHelper
  ...
  def sign_out
    session.delete(:user_id)
    @current_user = nil
  end
  ...
end

なんとなーくわかるとおもいますが、session.delete(:user_id)でCookieに保存していたsession[:user_id]を削除してます。
さらにサインイン中は@current_userにサインインしているユーザーのモデルオブジェクトが格納されているのでこれもnilに初期化しています。

さてさて、ここまででサインインに関して最低限必要な全てのメソッドを作成できました。
サインインの状態に合わせてヘッダーを出しわけしてみましょう!

ヘッダーを更新する

ヘッダーを以下の条件で出しわけしてみます。

  • 未サインイン
    • Home: トップページに遷移する
    • Sign in: サインインページに遷移する
  • サインイン済
    • Profile: サインインしたユーザーのユーザー詳細ページに遷移する
    • Sign out: サインアウトしてトップページに遷移する

まずは、ヘッダーをsigned_in?メソッドを使って出しわけしてみます。

app/views/layouts/application.html.erb
...
<ul class="navbar-nav">
  <% if signed_in? %>
    <%# サインイン済みの場合のリンク %>
    <li class="nav-item"><%= link_to "Profile", current_user, class: "nav-link" %></li>
    <li class="nav-item"><%= link_to "Sign out", sign_out_path, method: :delete, class: "nav-link" %></li>
  <% else %>
    <%# 未サインインの場合のリンク %>
    <li class="nav-item"><%= link_to "Home", root_path, class: "nav-link" %></li>
    <li class="nav-item"><%= link_to "Sign in", sign_in_path, class: "nav-link" %></li>
  <% end %>
</ul>
...

今までのヘッダーは未サインインの場合のリンクになっていたので、signed_in?falseの場合の方に記述してます。

サインイン済みの場合は新たに「Profile」と「Sign out」のリンクを作っています。

<li class="nav-item"><%= link_to "Profile", current_user, class: "nav-link" %></li>

「Profile」リンクの方は今までとほぼほぼ変わらない書き方ですね。少し違う点としてはroot_pathsign_in_pathのように遷移先としてpathを指定するのではなくcurrent_userというモデルオブジェクトを指定している点です。
link_toメソッドでは、遷移先としてモデルオブジェクトが指定された場合、そのモデルオブジェクトの参照パスに読み替えてくれます。つまり、user_path(current_path)と同義になります。

<li class="nav-item"><%= link_to "Sign out", sign_out_path, method: :delete, class: "nav-link" %></li>

「Sign out」リンクの方はmethod: :deleteの箇所が今までとは異なります。
通常link_toメソッドはGETメソッドでリンク先にリクエストしますが、method: [method]で指定することで別のメソッドでリクエストすることができます。
sign_out_pathroutes.rbdeleteメソッドでルーティングするように定義していましたので、この形でlink_toのHTTPメソッドを指定してあげないとルーティングされなくなってしまいます。

そういえば、sign_out_pathでルーティングされるsessions#destroyについて、コーディングをしていませんでした。
セッションを削除するsign_outメソッドを実行し、トップページにリダイレクトするようにコーディングしましょう。

app/controllers/sessions_controller.rb
...
def destroy
  sign_out
  redirect_to root_path
end
...

ここまでで、サインイン機能、サインアウト機能、ヘッダーの出しわけについてコーディングが完了しました。
ここで一度動作確認をしていきましょう。

サインイン機能、サインアウト機能、ヘッダー出しわけの動作確認

サインインしていない場合

まずサインインしていない状態でhttp://localhost:3000にアクセスしてみましょう。
すでにサインインしてしまっている場合は、ブラウザを閉じるか、「Sign out」リンクを押すかしてサインアウトしましょう。

この状態ではヘッダーは「Home」リンクと「Sign in」リンクが表示されています。
image.png

「Home」リンクを選択するとトップページへ、「Sign in」リンクを選択するとサインインページに遷移することも確認できますね。

サインインしている場合

では、サインインページからjohn@sample.comでサインインしてみましょう。
すると、john@sample.comのユーザー詳細ページに遷移し、ヘッダーも「Profile」リンクと「Sign out」リンクに変わっていることが確認できます。
image.png

また、「Profile」リンクを選択することでjohn@sample.comのユーザーのユーザー詳細ページに遷移できることも確認できますね。

サインアウトを試してみる

最後にサインアウトが正しく動作するか確認してみましょう。
サインインしている状態で、「Sign out」リンクをクリックしてみてください。

トップページに遷移して、ヘッダーが未サインイン状態の場合のヘッダーに戻っていることが確認できるはずです。
image.png

これで、想定どおりに動作していることが確認できましたね。

サインアップした時もサインイン状態になるようにする

今のままではサインアップした後にサインインをしないといけなくなり面倒です。
サインアップ時(users#create)もサインイン状態になるように更新します。

app/controllers/users_controller.rb
...
def create
  @user = User.new(user_params)
  if @user.save
    sign_in @user
    flash[:success] = "サインアップありがとう!"
    redirect_to @user
  else
    render :new
  end
end
...

if @user.saveの下にsign_in @userを追加しました。
これでDB保存が成功した場合に、そのユーザーでサインイン状態になるようになりました。

では、新たにユーザーをサインアップページで作成して、サインイン状態でプロフィールページに遷移することを確認してみましょう。
image.png
プロフィールページに遷移してますし、ヘッダーのリンクからサインイン後の状態になっていることがわかりますね。

サインイン状態のときに遷移できないページを作る

また、サインインしたあとにサインアップページやサインインページに遷移することは必要ありませんね。(むしろ禁止したい。)
さらに、サインイン後はトップページにもアクセスする必要はなく、ルートパスにアクセスしようとした場合、プロフィールページ(サインインユーザーのユーザー詳細ページ)に遷移させるようにしたいかもしれません。

これらを実装してみましょう。

ApplicationControllerにサインインしている場合、強制的にプロフィールページに遷移させるメソッドを作成します。

app/controllers/application_controller.rb
...
def redirect_to_profile_if_signed_in
  redirect_to current_user if signed_in?
end
...

これを、トップページ(static_pages#home)、サインアップページ(users#new, users#create)、サインインページ(sessions#new, sessions#create)にルーティングされた直後(アクションが処理される前)に実行されるようにします。
これはbefore_actionメソッドを使えば容易です。

app/controllers/static_pages_controller.rb
class StaticPagesController < ApplicationController
  before_action :redirect_to_profile_if_signed_in

  def home
  end
end
app/controllers/users_controller.rb
class UsersController < ApplicationController
  before_action :redirect_to_profile_if_signed_in, only: [:new, :create]
  ...
end
app/controllers/sessions_controller.rb
class SessionsController < ApplicationController
  before_action :redirect_to_profile_if_signed_in, only: [:new, :create]
  ...
end

StaticPagesControllerはhomeアクションしかなかったので、コントローラー全体にbefore_actionを適用しました。
UsersControllerとSessionsControllerはonlyオプションをつけて対象のアクションの実行前にのみredirect_to_profile_if_signed_inメソッドが呼び出されるようにしました。

サインイン状態にして、「トップページ」「サインアップページ」「サインインページ」にそれぞれダイレクトアクセス(URL直打ち)してみてください。
全てプロフィールページにリダイレクトされます。

また、念のためサインアウトした場合は「トップページ」「サインアップページ」「サインインページ」にそれぞれアクセスできることも確認しておきましょう!

確認できましたね?
では、今日はここまでにしておきましょう!

後片付け

いつものように、次回に向けてデータを消します。

$ docker-compose down
$ docker-compose run --rm web rails db:migrate:reset

DBコンテナが立ち上がった状態だと思うのでdownさせます。

$ docker-compose down

まとめ

今回はセッションを利用してユーザーのサインイン機能、サインアウト機能を作ってみました。
さらにヘッダーの出しわけや、特定のページアクセス時にサインインしている状態だとプロフィールページにリダイレクトされる機能を作ってみました。
すでにお気づきとは思いますが、今回作成したredirect_to_profile_if_signed_inメソッドと逆のことをすればサインインしていないと遷移できないページを作り出すことも可能ですし、条件をsigned_in?ではないものにすれば、特定の条件のユーザー(例えばadminフラグを持っているユーザーとか)しかアクセスできないページを作り出すことも可能です。

それにしても、色々と動作確認することも増えてきましたね...
このまま毎回手で目で確認していくのもしんどそうです。

次回はこれを解決するためにTDD/BDD、そしてテスト自動化に取り組んでみます!

では、次回も乞うご期待!ここまでお読みいただきありがとうございました!

Reference

Links

Vol.1 - Introduction -
Vol.2 - Hello, Rails on Docker -
Vol.3 - Scaffold, RESTful, MVC -
Vol.4 - Static pages -
Vol.5 - Model and CRUD -
Vol.6 - Model validation -
Vol.7 - Secure password -
Vol.8 - Sign up -
・ Vol.9 - Sign in - ?この記事

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

【Rails Devise】ログインユーザーのみ、内容を見れるようにするには

ログインユーザーのみ、内容を見れるようにするには

Devise使用時に、ヘッダーやログアウトボタンなどの内容をログインユーザーのみに表示させたい場合などに、
ご参考にして頂ければと思います!

めちゃめちゃ簡単です!

html.erb
<% if user_signed_in? %>
   <%# この間に書かれた内容はログインユーザーのみ見れる %>
<% else %>
   <%# この間に書かれた内容はログインしていないユーザーのみ見れる %>
<% end %>

こんな使い方!

html.erb
<% if user_signed_in? %>
   <li><%= link_to 'LOGOUT', destroy_user_session_path, method: :delete %></li>
<% else %>
   <%= link_to "ログイン", new_user_session_path, class: 'post' %>
   <%= link_to "新規登録", new_user_registration_path, class: 'post' %>
<% end %>

これでログインユーザーだけがログアウトできて、ログインしていないユーザーだけが新規登録やログインができます!

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

画像一覧表示で指定の枠内でスクロールさせる方法

現在スクールにてプログラミングを学習中
目標は4週間の間でフリマアプリを完成させる

開発環境
rails 5.2.4.1
ruby 2.5.1

初学者向けに記述してます
アウトプット練習の為でもあります。

実装したいこと
画像を指定の範囲内でスクロールさせる。

完成イメージ
https://i.gyazo.com/55340c88a724b2e46eee6ed3254fbef6.mp4
写真は動物ですがあくまでテストイメージです。
予めご了承ください。

実装にあたりhamlの記述は終えているものとします。
書きにはSCSSの記述のみ載せます。

まずはスクロールさせたいブロックの枠のクラスに下記を記述(?部分)

&__lists {
  width: 800px;
  padding: 26px 0;
  margin: 0 auto;
  display: flex;
?overflow-x: scroll;

次にスクロースさせたい子要素の.scssに記述を追記

.item_list {
?min-width: 250px;
  height: 245px;
  color: #000000;
  background-color: #ffffff;
  display: inline-block;
  position: relative;
  margin: 0 10px;

widthにmin-を追加する記述で最低の枠の大きさを固定でき
指定した大きさの枠内でクスロールしてくれる。

これでスクロール完了です。

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

Railsでゲームを作成してバックエンドを学ぶ① アクセス(通信)する

はじめに

これは、普段Web系やAndroidなどフロントエンド中心に開発を行っており、びっくりするほどバックエンドのことが分からなかった筆者が、バックエンドの知識を付ける目的で、Railsで◯×ゲーム(三目並べ)を作成した時の記録である。
いくつかの段階に分けて取り組んだので、記事も分けることにする。

①アクセス(通信)する

今回はWebサーバにアクセスして画面を表示させられるようにするところまで行う。

実現したいこと

Webサーバにアクセスして、ブラウザに画面を表示させる。
ここではとりあえず、Hello, Worldなどの何かしら簡単なオブジェクトが表示されているようにする。

実現するうえでの不明点

  • Webサーバへのアクセスの仕方が分からない(忘れた)
    →手順としてはrails serverでサーバを立ち上げてからブラウザでアクセスするだけ
     Railsチュートリアルの1.3に全部書いてある。

開発手順

ほぼ全てRailsチュートリアルの1.3に則って進めた。

Railsサーバの起動

Railsアプリケーションのディレクトリでrails serverする(rails sというエイリアスあり)。
自分は別マシンにssh接続して開発しているため、-bオプションでホストを指定している。
$ rails s -b <ssh先のホスト>
rails serverコマンドを打つと、PumaというWebサーバが起動する( https://railsguides.jp/command_line.html#rails-server )。

$ rails s
=> Booting Puma
=> Rails 5.0.7.2 application starting in development on http://localhost:3000
=> Run `rails server -h` for more startup options
Puma starting in single mode...
* Version 3.12.1 (ruby 2.6.2-p47), codename: Llamas in Pajamas
* Min threads: 5, max threads: 5
* Environment: development
* Listening on tcp://localhost:3000
Use Ctrl-C to stop

画面の表示

上記でサーバを起動したので、ブラウザからアクセスする。
デフォルトはlocalhost:3000で、指定したホスト(-bオプションで指定できる)やポート番号(-pオプション)に適宜書き換える。
アクセスするとデフォルトのRailsページが表示される。Hello, Worldより遥かに豪華

画面に別の物を表示させる

上記で接続した画面に"Hello, World!"を表示して原始的にすることにする。

アクション追加

コントローラに「文字列を描画する」というアクションを追加する。
app/controllers/application_controller.rbhelloアクションを追加した。

  def hello
    render html: "Hello, World!"
  end

アクションを使う

上記で作成したhelloアクションを使うようにして、実際に画面に表示されるようにする。
config/routes.rbroot 'application#hello''<コントローラ名>#<アクション名>')を追加する。
これで晴れて"Hello, World!"が画面に表示されるようになった。
スクリーンショット 2019-07-24 10.17.33.png

エラーや詰まった箇所

Gem::LoadError

rails serverを起動してブラウザでアクセスすると、Gem::LoadErrorというエラーが発生した。
sqlite3というgemの最新バージョン(1.4.x)がActiveRecordに対応していないことによるエラーらしい。
sqlite3のバージョンを指定してインストールしなおすことで解消した。

参考: sqlite3のgemでGem::LoadErrorが出てしまう

ActiveRecord::ConnectionNotEstablished

上記のgemのエラーを解消したらこのエラーが発生した。
アプリがActiveRecordを使わない設定で作られていたのが原因らしい。
config/application.rbrequire 'active_record/railtie'を追加すると解消した。

参考: Railsで ActiveRecord::ConnectionNotEstablished: No connection pool with 'primary' found. がでたら確認すること。

補足

Railsアプリケーションの作成

rails newする。自動的にbundle installもされる。

Gitの導入

git initする。
git addgit commitをするとローカルにmasterブランチができるので、あとはいつもGitを使っている感じでブランチを切ったりできる。

まとめ

  • Webサーバにアクセスして、自分で指定した文字列を画面に表示させた。
  • Railsが頑張っているおかげでWebサーバを容易に利用できる。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

RSpecはここから始めよう

Everyday Rails - RSpecによるRailsテスト入門を読みました。
まずはここから始めようと思ったことをまとめます。

導入方法はまた別記事で。

用語

spec:テストファイル
example:spec内に書かれているテスト1つ1つ

specの種類

モデルスペック、コントローラースペック、フィーチャースペック、システムスペック、レスポンススペックの5種類があります。特に重要なのは以下の3つです。

モデルスペック:バリデーションや、メソッドの単体テスト。

システムスペック:統合テスト。ユーザーの動作を再現する。手動テストを自動でやる感じ。
昔はフィーチャースペックが使われていたけど、今はこっちが推奨されてるみたいです。
実行に時間がかかります。

レスポンススペック:主に外部APIとの通信をするテストで使われます。

specファイルの生成

モデルスペック

$ bin/rails g rspec:model user

システムスペックは手動で作成

$ mkdir spec/system
$ touch spec/system/users_spec.rb

request spec

bin/rails g rspec:request user

基本文法

まずit '<テスト名>' do ~ endのブロックの中にデータのセットアップや検証したい項目を書きます。

it 'can have many notes' do
  #検証したい内容が入る
end

次にexpect(<検証したい項目>).to <どうなっていて欲しいか>を書きます。英語の文法と同じ語順なのでとても読みやすくて気に入っています。

it 'can have many notes' do
  project = ... #データのセットアップなど
  expect(project).to be_valid
end

以下のコマンドでテストを実行します。

$ bin/rspec
.
.
Finished in 3.12 seconds (files took 0.93752 seconds to load)
1 examples, 0 failures

テストデータの生成

各テスト内でUser.newなどで生成しても全然オッケーなのですが、めんどくさいので、FactoryBotというgemを使います。

$ bin/rails g factory_bot:model user
spec/factories/users.rb
FactoryBot.define do
  factory :user do
    name { 'Sample Tarou' }
    email { 'test@example.com' }
    password { 'mypassword1234' }
  end
end

あとはテスト内にFactoryBot.create(:user)と書くだけで、上記のユーザーのインスタンスを生成することができます。

spec/models/user.rb
it 'is valid with a name, email, and password' do
  user = FactoryBot.create(:user)
  expect(user).to be_valid
end

共通のテストデータ

letを利用することで、1つのファイル内で共通で使うテストデータを作成することができます。

spec/factories/users.rb
let(:user) { FactoryBot.create(:user) }

it 'is valid with a name, email, and password' do
  expect(user).to be_valid
end

it 'is Sample Tarou' do
  expect(user).to eq 'Sample Tarou'
end

上の例ではletを使いuserという変数の中にファクトリに定義したユーザーのインスタンスを格納しました。

exampleをグループ分けする

describecontextを使って、exampleをグループ分けすることで、可読性をあげることができます。

describe 'search message for a term' do
  context 'when a match is found' do
    it 'returns notes that match the search term' do
      .
      .
    end
  end

  context 'when no match is found' do
    it 'returns an empty collenction when no result are found' do
      .
      .
    end

    it 'returns an empty collenction when serach conditions are invalid' do
      .
      .
    end
  end
end

システムスペックとcapybara

システムスペックを書く上でcapybaraというgemが必要です。
インストールすると、capybaraのDSL(独自の言語)が使えるようになります。
以下のvisitclick_linkfill_inclick_buttonなどがDSLです。非常に直感的で気に入っています。

specs/systems/projects_spec.rb
it 'signs in' do
  visit root_path
  click_link 'Sign In'
  fill_in 'Email', with: user.email
  fill_in 'Password', with: user.password
  click_button 'Log in'
  expect(page).to have_content 'ようこそ Sample Tarou 様'
end

shoulda-matchers

shoulda-matchersというgemを使うと、macher(be_validとか)を拡張することができ、特にモデルスペックのバリデーションのチェックで力を発揮します。

it 'is invalid without a first name' do
  user = FactoryBot.build(:user, first_name: nil)
  user.valid?
  expect(user.errors[:first_name]).to include("can't be blank")
end

このバリデーションチェックのテストが

it 'is invalid without a first name' do
  it { is_expected.to validate_presence_of :first_name }
end

このように1行で検証できるようになります。

デフォルトで使えるマッチャーについては以下の記事を参照
使えるRSpec入門・その2「使用頻度の高いマッチャを使いこなす」

メソッドを切り出す

複数箇所で共通する処理は、メソッドとして切り出すことができる。
ファイルの一番下に書くことが多い。

def go_to_project(name)
  visit root_path
  click_link name
end

複数のファイルで共通する処理はspecs/supportディレクトリにファイルを作る。

spec/support/login_support.rb
module LoginSupport
  def sign_in_as(user)
    visit root_path
    click_link "Sign in"
    fill_in "Email", with: user.email
    fill_in "Password", with: user.password
    click_button "Log in"
  end
end

#以下のように記述することで、各ファイルでincludeする必要がなくなる
RSpec.configure do |config|
  config.include LoginSupport
end

明示的に読み込みたい場合にはinclude LoginSupportのように記述する。

終わりに

他にもたくさんTIPS的なものがありますが、全部いきなり全部意識するのは無理なので、まずはこの記事にまとめたテクニックでどんどんテストを書いていきます。

参考

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

Docker Compose でホストとクライアントの両方にて Rails を起動したいそこのあなた!

なにこれ?

あんまりいないとは思うけど、Rails で Docker Compose を使用しているときにホスト側とクライアント側で Rails server を起動したいときはありませんか?自分の場合だと、System Test がどうしても Docker 環境のみで動作させる方法が見つけ出せなかったので、自分の場合 Rails を Ubuntu 環境でシステムテストを走らせて、それ以外は Docker 内のコンテナで動作させたいと思ったのでこういう環境が必要になった。そんなことをしようとしていると困るのが、(Rails のルート)/tmp/ に作られるファイルがホスト側とクライアント側で競合して、同時に起動できないという問題だった。そんなときに読んで解決できる糸口となったのが「DockerでVolumeをマウントするとき一部を除外する方法」だったので、自分もココにその軌跡をのこしておくことにする。

なにするの?

要は Rails 同時に起動できない原因は tmp フォルダが存在するからである。こいつさえなければ、ポートの競合とかはともかく、起動できるのだ。要はお互いが疎な関係になればいい。つまり、クライアントとホストの tmp フォルダが同期しなければいい。以下のコードは tmp フォルダだけはホストと動悸しないようにしてある。

version: '3'

volumes:
  rails_tmp_data:

services:
  as:
    build:
      context: ./
    ports:
      - 3333:3000
    volumes:
      - ./:/usr/src/app/
      - rails_tmp_data:/usr/src/app/tmp/
    command: 
      /bin/sh -c "yarn install --check-files && bundle install && rails db:create && rails db:migrate && rm -f /usr/src/app/tmp/pids/server.pid && bundle exec rails s -p 3000 -b '0.0.0.0' --early-hints"

なお上記のコードは、要点だけに絞っている。そのため、コピペでは動かないのは、予めご了承いただきたい。

これでおしまい

ポートの設定を端折ってしまった。そこらへんは、ココでは記事にしない。途中で記事を書くのがめんどくさくなったからね。ググるかなにかしてください。そんじゃねー。

タイトルについて

スタパ斉藤マジリスペクト、というわけでなく、冗談抜きでこんなタイトルしか思いつかなかった。

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

rails でお問合せフォームを作り、自分のメールアドレスに送信させる方法

railsで簡単にお問合せフォームを作り自分のメールアドレスに送信させる

railsに標準搭載のaction mailerを使用して簡単にお問合せフォームを実装させる。

まずはじめに、お問合せ内容を保存するためのInquiryモデルを作ります。

Inquiryモデルは、nameカラムと、messageカラムを持ちます。

$ rails g model inquiry name:string message:string
$ rails db:migrate

次に、action mailer を作成していきます

$ rails g mailer inquiry

このinquiryの名前に特に意味はないので、好きな名前でやってもらって大丈夫です。

実行されると、このようにいくつかのファイルが作成されます

Running via Spring preloader in process 32746
      create  app/mailers/inquiry_mailer.rb
      invoke  erb
      create    app/views/inquiry_mailer
      invoke  test_unit
      create    test/mailers/inquiry_mailer_test.rb
      create    test/mailers/previews/inquiry_mailer_preview.rb

次に、メールの送信機能を実装するため、app/mailers/inquiry_mailer.rbに追記をしていきます。

inquiry_mailer.rb
class InquiryMailer < ApplicationMailer
# -----追記------
  def send_mail(inquiry)
    @inquiry = inquiry
    mail(
      from: 'system@example.com',
      to:   'manager@example.com',
      subject: 'お問い合わせ通知'
    )
  end
# -----追記ここまで----
end

こちらで使用できるメソッドはこちらになります。

オプション できること
from 送信元メールアドレス
subject メールの件名
to メールの送信先アドレス
cc ccのメールアドレス
bcc bccのメールアドレス

メール本文のレイアウトを作成する

メール本文のレイアウトを作成するためには、命名規則に従ってファイルを作成します。

.app/views/メイラー名_mailer/メイラークラスのメソッド名.text.erb

今回の場合は
app/views/inquiry_mailer/send_mail.text.erb
を作成しましょう。

そして中に

send_mail.text.erb
<%= @inquiry.name %> 様 から問い合わせがありました。
・お問い合わせ内容
<%= @inquiry.message %>

を追記しましょう。

メールをプレビューで確認する機能

次に、実際にお問合せフォームを送る前に自分のお問合せ内容を確認するプレビュー機能を作成していきます。
action mailerに元から搭載されている機能なので簡単にできます。

まず

test/mailers/previews/inquiry_mailer_preview.rb
# Preview all emails at http://localhost:3000/rails/mailers/inquiry_mailer
class InquiryMailerPreview < ActionMailer::Preview
# ----追記-----
  def inquiry
     inquiry = Inquiry.new(name: "侍 太郎", message: "問い合わせメッセージ")

     InquiryMailer.send_mail(inquiry)
   end
#----追記ここまで----
end

そしてここでターミナルで

$rails s

をし、サーバーを起動させます。
そして
http://ホスト名:3000/rails/mailers/inquiry_mailer
にアクセスしてみてください。

大体の方は
http://localhost:3000/rails/mailers/inquiry_mailer
ですね。
すると


Inquiry Mailer
・inquiry

と表示されますので
inquiryをクリックしましょう。

すると
Screenshot from Gyazo

と言う画面が出てきます。
今回はformatをHTMLに変えているので皆様の画面より少し文字が大きめに書かれていますが問題ないです。

Gmailでメールを実際に送信してみましょう

まずメールサーバーを設定します。

config/environments/development.rb

config.action_mailer.delivery_method = :smtp
config.action_mailer.smtp_settings = {
 address:              'smtp.gmail.com',
 port:                  587,
 domain:               'gmail.com',
 user_name:            '<gmailのメールアドレス>',
 password:             '<gmailのパスワード>',
 authentication:       'plain',
 enable_starttls_auto:  true
}

をファイル内のどこでもいいので追記してください。(※一番上のclass名~一番下のendの間でお願いします)

メールを送信するために、自分のメールアドレスとパスワードを書き加えます。

config/environments/development.rb

config.action_mailer.delivery_method = :smtp
config.action_mailer.smtp_settings = {
 address:              'smtp.gmail.com',
 port:                  587,
 domain:               'gmail.com',
#-----変更点---------------------------
 user_name:            'aaaaa@gmail.com',
 password:             'aaaaaaaaaa',
#----変更点ここまで-----------------------
 authentication:       'plain',
 enable_starttls_auto:  true
}

そしてこちらのファイルにも自分のメールアドレスを書き加えます

inquiry_mailer.rb
class InquiryMailer < ApplicationMailer
  def send_mail(inquiry)
    @inquiry = inquiry
    mail(
      from: 'system@example.com',
#----------変更点--------------
      to:   'aaaaa@gmail.com',
#----------変更点ここまで---------
      subject: 'お問い合わせ通知'
    )
  end
end

ここまできたら、あとは確認のため、rails コンソールからメール送信を実行します。

$ ご自身のファイルディレクトリにいく
$ rails c
irb(main):001:0> inquiry = Inquiry.new(name: "侍 太郎", message: "問い合わせメッセージ")
irb(main):002:0> InquiryMailer.send_mail(inquiry).deliver_now

すると、自分のメールアドレスにメールが送信されたかと思います。

ですが、セキュリティの関係上警告メールが出る場合が多くあります。

これは憶測ですが、コードにそのままアドレスやパスワードを貼っているのが原因なので、この部分を環境変数に変えて、隠す方法を試してみてください。

そちらに関連する記事はこちらです

以上で終了です。

皆様できましたでしょうか?

参考にした記事
https://www.sejuku.net/blog/48739

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

image画像を一覧表示させる

画像の一覧表示の実装について実装

現在、プログラミングスクールにてフリマアプリを5名で実装中
少し手間取ったホーム画面の一覧表示の実装手順を載せます。
フロント実装は完了済みとします。

開発環境
rails 5.2.4.1
ruby 2.5.1

マークアップはhamlで行いました。

※アウトプットの為記述
※初学者向けに記述します

完成イメージ

home-index.png

※載せている写真は動物ですがあくまでテスト画像として載せているだけです。
 予めご了承ください。

まずはコントローラの記述から
今回は一覧の表示をしたいので対象のapp/controllers/home_controller.rbを編集

home_controller.rb

 class HomeController < ApplicationController
   def index
     @images = Image.all
     @items = Item.all
   end
 end

今回はフリマのitemテーブルとimageテーブルのデータを取得できるように一旦記述
これでアイテムテーブルとimegeテーブルのデータをホーム画面に持ってきます。
roteはすでにできている為、今回は追記はありませんでしたが、一応載せております

routes.rb

 Rails.application.routes.draw do
   devise_for :users
   root "home#index"
   resources :items, only: [:new, :create, :show, :edit, :destroy]
   resources :sendings, only: [:new, :create]
   resources :users, only: [:edit]
   resources :cards, only: [:new, :create, :index, :destroy]
   resources :orders, only: [:index, :new, :create] do  
     collection do
       get 'index', to: 'orders#index'
       post 'pay', to: 'orders#pay'
       get 'done', to: 'orders#done'
     end
   end
 end

次にhamlに追記


  .main__item__category
    %h2.main__item__category__title
      ピックアップカテゴリー
    .main__item__category__item_box
      = link_to "#", class: "main__item__category__item_box__title" do
        新規投稿商品
      %ul.main__item__brand__item_box__lists
        %li
       ?- @items.each do |item| 
       ?  = link_to item_path(id: item.id) ,class: 
"main__item__brand__item_box__lists--list, item_list" do
            - ft_image = item.images.first
            = image_tag ft_image.photo.url, class: 
"item_list__picture"
            .item_list__body
              %h3.item_list__body__name
                = item.name
              %ul
                %li.item_list__body__price 
                  = item.price 
                  = "円"
                %li.item_list__body__likes
                  = icon 'fas', 'star'
                  0
              %p (税込)     

今回はitemテーブルに紐づいているimageの最初のデータを取得したかった為、上記の?のように追記いたしました。
この記述でデータを取得し表示まではできました。
あとはスクロールで画像がうまくスクロールするようにいたしました。
長くなりますのでスクロールは別の記事に載せたいと思います。

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

【やってみた】未経験エンジニアのPay.jp導入(Ruby on Rails)

まずはじめに

Pay.jpとは
シンプルなAPIと豊富なライブラリで簡単にクレジットカード決済を導入できます。
ApplePayに対応していたり定期課金を組むことができます。
今回は開発環境での実装をイメージしているので後ほど出てくる各鍵はテストキーを使用しています。

導入

Pay.jpの登録
まずはアカウントを取得をします。
登録完了しログインすると下記のようになります。

pay.jp root_view.png

gemのインストール

Gemfile
gem 'payjp'
```:fist_tone1::point_up:
**インストール**

bundle install
```

環境変数の設定

まず、プロジェクト直下に.envファイルを新たに作成し、以下のように編集しましょう。

PAYJP_PUBLIC_KEY='自身のアカウントのテスト公開鍵'
PAYJP_SECRET_KEY='自身のアカウントのテスト秘密鍵'

各キーはPay.jpマイページのAPIより確認ができます。
Pay.jp api_view.png

そしてこの.envファイルをGithubにあげないよう .gitignoreに下記追記をします。

.gitignore
/.env

controller

支払い機能を管理するコントローラーを作成しpayアクションを定義します。
私の場合はPurchasesControllerを作成し記載しております。

app/controllers/purchases_controller.rb
  def index
  end

  def pay
    Payjp.api_key = ENV["PAYJP_SECRET_KEY"]
    Payjp::Charge.create(
      amount: params[:amount],
      card: params['payjp-token'],
      currency: 'jpy'
    )
  end

Charge.createで売り上げ作成処理をしているのかと思います。
amountは売り上げ金額(ここではviewから取得したparams[:amount]を売上高とします。)、
cardはクレジットカードのトークンを使っての決済を表し、
currencyは 'jpy'とすることで日本円での決済としています。

また、indexに「カードで支払う」ボタンを設置します。

ルーティング

payアクションのルートを追記します。

route.rb
resources :purchases
post 'purchases/pay' => 'purchases#pay'

view

ここまできたらあとはビューに支払い用のモーダルを設置する処理をするだけですね。

app/views/purchases/index.html.haml
= form_with local: true, url: purchases_pay_path do |form|
  = form.number_field :amount
  %script.payjp-button{"data-key" => "自身のアカウントのテスト公開鍵", src: "https://checkout.pay.jp", type: "text/javascript"}

なんとこれだけでindex.html.hamlに表示される「カードで支払う」ボタンを押すだけで下記のようなモーダルが出てくるんです。
Pay.jp modalview.png

忘れずに支払い完了後のviewも用意します。

app/views/purchases/pay.html.haml
支払いが完了しました

= link_to 'Topへ', root_path

ここまで完了したら実際に試してみましょう!
index.html.hamlに記載した「カードで支払う」ボタンを押して、モーダルを開き必要な情報を入力します。
カード番号はあらかじめPay.jpで用意しているテスト用の共通カード番号'4242 4242 4242 4242'、
有効期限は現在から未来の日付、CVC番号は3桁の適当な数字、名前も適当な名前を入力して「カードで支払う」ボタンをクリック。

すると下記のようにpay.html.hamlの画面になれば完了です。
※CSSを当ててないのでシンプルですがご了承ください。

pay.jp paied_view.png

実際にPay.jpにアクセスし売り上げの画面を見て、支払い済みとなっていれば問題ないです。

さいごに

今回は開発環境についてですが、本番環境の際はもう少し考慮する点はあるかと思います。
が、皆さんならやってくれると信じてます。

ここから発展して定期課金などはまたの機会でご紹介できればなんて思ってます。

ありがとうございました。

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

Railsチュートリアルメモ - 第8章

メモの目次記事はこちら

Railsチュートリアル8章へのリンク

第7章からだいぶ日にちが空いてしまった。。。
気を取り直して最後まで完走していく

第8章 基本的なログイン機構

8.1 セッション

cookieを利用したセッションの実現方法についての章

ポイント

  • ユーザー登録と違い、名前付きルートではなく、個別指定でルーティングを指定する
  • flash変数の設定後の画面表示をredirect_toではなく、renderにするとflashメッセージが消えないが、flash.nowにすると次のリクエストでメッセージが消える

8.2 ログイン

ポイント

  • Railsで事前定義済みのsessionメソッドが利用できる
    • session[:user_id] = user.id
  • User.find(session[:user_id])を使うと、userが存在しないときに例外が発生するので、User.find_by(id: session[:user_id])を使用する
  • helperにログイン用のメソッド、ログイン情報取得用のメソッドを追加し、どこからでも呼び出せるようにする
  • 余計なDBアクセスを避けるため、||=を使用して、非ログインのときのみDBアクセスが発生するようにする
  • test/fixtures/の中のymlに定義することで、都度DBに登録しなくても、DB登録ユーザーのようにユーザー情報のテストを行うことができる
  • ヘルパーメソッドはテストから呼び出せない

8.3 ログアウト

ポイント

ログアウトでやることは①sessionから:user_idを消す②@current_userインスタンス変数を消す(nilで上書く)の2つ

  def log_out
    session.delete(:user_id)
    @current_user = nil
  end

8.4 最後に

割愛

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

ActiveRecord以外で型キャストを使う

ActiveRecordのカラムのように、"0" を false に変換したり、"2020/02/26 12:30" をTimeオブジェクトに変換したりする方法です。フォームから受け取った値をActiveRecord以外のクラスで処理するときなどに使えます。

環境: Rails 5.1, 5.2, 6.0

ActiveModel::Type::Valueのサブクラスをnewしてcastメソッドを呼び出します。true/falseにしたければ、ActiveModel::Type::Boolean です。[false, 0, "0", "f", "F", "false", "FALSE", "off", "OFF"] はfalse、それ以外はtrueになります。

ただし、nil と "" は nil になります。また、Rails 5.2.4 と 6.0.0 以降では、[:"0", :f, :F, :false, :FALSE, :off, :OFF] も falseになるよう修正が入っています。

attr_reader :draft

def draft=(val)
  @draft = ActiveModel::Type::Boolean.new.cast(val)
end

日付時刻型(Timeクラス)にしたいときは、ActiveModel::Type::DateTime を使います。Time.parse(val) とすると書式が不正なときに例外が発生しますが、Railsの型キャストではnilになります。

attr_reader :published_at

def published_at=(val)
  @published_at = ActiveModel::Type::DateTime.new.cast(val)
end

ほかにどんな型が使えるのかは、Railsのソースの activemodel/lib/active_model/type 下を参照してください。

https://github.com/rails/rails/tree/master/activemodel/lib/active_model/type

関連記事: ActiveModel::Attributesを使う

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

Ruby on rails個人的備忘録

はじめに

私がRuby on Railsについてまとめたいと思い書いた備忘録です。
読む価値はあまりありません。

Ruby on Railsでアプリ作ってみたいと思い手を出しました。
が、流れが段々掴めなくなってきたので基礎から私なりに整理して書いていきます。

色々端折って書いてます。

Ruby on Rails

そもそもRuby on Railsとはなんやねんってところから始めます。

Ruby on Rails(以下Rails)とはWebアプリケーションを作成する際の骨組みを作ってくれるものです(フレームワークというらしいです)。
RailsではMVCアーキテクチャという設計方法でプログラムを書いていきます。こんな感じです。
Mとはモデル(Model)といいDBのデータを扱うオブジェクトです。Vはビュー(View)といいHTMLなどをテンプレートとしてブラウザに返すものです。Cはコントローラ(Controller)といいモデルからのデータを受け取ってビューに渡すものです。以下に図を示します。ナンバリングは気にしないで下さい。
image.001.jpeg.001.jpeg
モデルの必要性として、DBに不正な値が紛れ込まないかなどをチェックすることができアプリケーションのセキュリティ性を高めることが出来ます。
コントローラはデータに対して各処理を施したり、ビューが返す適切なテンプレート(画面の表示方法みたいなもの?)を選択したりします。これらはコントローラ内のアクションというもので行います。
このように、Webアプリケーション内で細かくロールを決めることで作業効率を上げることができ、またプロジェクト全体の可読性も高くなります。

まずは基本から

ここから先は、私が学んだRailsに関する知識とかについて綴っていきます。プログラムを書くことは少ないのでご了承下さい。

どんなものを作るか

何作るか考えてないのにものが作れるわけありません。適当に考えてみます。なんか表示させたいだけとかでもいいです。大事なのは仕様を決めることです。どのコントローラとアクション、モデル、ビューで仕様を実現するか考えることです。使わなくてもいいものもありますし、複数使うこともあるかも知れません。ここでは、こう考えます。考え方については私自身手探りで行ってますので正しくないかも知れません。
まず、画面に何を出すか決めます。今回はおなじみの"Hello World!"を表示することにします。トップページに表示するので、コントローラ名はTopにします。アクションはindexとします。Hello World!を表示するだけなので、DBは使わなそうですね。今回はモデルは使いません。
ビューは使います。Hello World!をどんな文体で表示させようかなとか、中央に配置させようかなとか考えます。

ルーティングの設定

ルーティング(routing)とはWebブラウザに送られてきたリクエスト(GETとかPOSTとか)をどのコントローラのどのアクションに対して任せるかという関連付けを行うところです。これをしないとWebのトップページすら作れません(多分)。
ここではrootというメソッドを使います。rootでどのコントローラのどのアクションで行うかを設定します。
アプリのトップページを表示するようなアクションはこのように書きます。

routes.rb
Rails.application.routes.draw do
  root "top#index"
end

#の左側でコントローラ名を、右側でアクション名をしています。
コントローラもアクションも定義していないので、次はこれを書きます。

コントローラ作成/アクション定義

Ruby on Railsではコントローラを1から書かなくても、コマンドを打つことである程度の部分までコントローラを作ってくれます。モデルも作ってくれます。ターミナルでコマンドを打ち込みコントローラtopを作成します。コントローラはtop_controller.rbというスネークケースで保存されます。クラス名はTopControllerのようにキャメルケースで書かれます。
このファイルはapp/controllers/に置かれます。

top_controller.rb
class TopController < ApplicationController
  def index
    render action: "index"
  end
end

renderメソッド(Railsには様々なメソッド(関数)があります。いきなり出てきて「知らねー」という気持ちになりますが堪えましょう)はHTMLを生成するメソッドです。action: "index"は「indexアクション用のテンプレートを用いて下さい」という意味になります。
indexというアクションはtop_controller.rbに書かれたアクションですね。これに対するテンプレートを作成しましょう。

ビューの作成

私が読んでいる参考書ではERBというライブラリを用いてテンプレートを作成するERBテンプレートを用いていたのでそちらを使用していきます。ERBテンプレートは適当な名前では使えません。同じコントローラ、同じアクションに対応させるようにテンプレートを配置させる必要があります。app/views/top(コントローラ)/にindex(アクション名).html.erbという名前で配置します。

index.html.erb
<% @title = "Hello World!"%>
<h1><%= @title %></h1>

ここまで書いたらコンソールでrails sコマンドを実行しサーバを立ち上げ結果をみます。

流れ

①ルーティングでどのコントローラにどのアクションにやってもらうか決めます。
②コントローラとアクションを定義します。(得たデータの加工などはここで行います)
③必要ならばモデルも定義します。(モデルについては分かり次第追記)
④ビューします。

最後に

現在の私の記事は他の方がみても何の価値もない備忘録ですが、将来的には誰が読んでも分かるような記事に仕上げていきたいと思っています。精進していきます。

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

heroku run rails db:migrateをしたときにundefined method `assets'が出る【Did you mean? asset_host】

APIを作成してHerokuでデプロイしようとしていました。そこで以下のコマンドを打つと

$ heroku run rails db:migrate

以下のようなエラーが出ました。

NoMethodError: undefined method `assets' for #<Rails::Application::Configuration:0x000055586e3a0090>
Did you mean?  asset_host

このエラーを解決するのに結構時間がかかったので、共有したいと思います。

Railsバージョン 6.0.2.1
Rubyバージョン 5.2.1

解決手順①

まずは

config/application.rb
require "sprockets/railtie"

上記がコメントアウトされていると思うので、外してください。もしなければ追加してください。

そして、追加できたら

$ git push origin master

masterブランチにpushし

$ git push heroku master

デプロイします。

すると以下のようなエラーが。

Sprockets::Railtie::ManifestNeededError: Expected to find a manifest file in `app/assets/config/manifest.js`
remote:        But did not, please create this file and use it to link any assets that need
remote:        to be rendered by your app:
remote:        
remote:        Example:
remote:          //= link_tree ../images
remote:          //= link_directory ../javascripts .js
remote:          //= link_directory ../stylesheets .css
remote:        and restart your server
remote:        /tmp/build_e14dcaf905c9154df3f8551de0e5899c/vendor/bundle/ruby/2.5.0/gems/sprockets-rails-3.2.1/lib/sprockets/railtie.rb:105:in `block in <class:Railtie>'
remote:        /tmp/build_e14dcaf905c9154df3f8551de0e5899c/vendor/bundle/ruby/2.5.0/gems/railties-6.0.2.1/lib/rails/initializable.rb:32:in `instance_exec'
remote:        /tmp/build_e14dcaf905c9154df3f8551de0e5899c/vendor/bundle/ruby/2.5.0/gems/railties-6.0.2.1/lib/rails/initializable.rb:32:in `run'
remote:        /tmp/build_e14dcaf905c9154df3f8551de0e5899c/vendor/bundle/ruby/2.5.0/gems/railties-6.0.2.1/lib/rails/initializable.rb:61:in `block in run_initializers'
remote:        /tmp/build_e14dcaf905c9154df3f8551de0e5899c/vendor/bundle/ruby/2.5.0/gems/railties-6.0.2.1/lib/rails/initializable.rb:60:in `run_initializers'
remote:        /tmp/build_e14dcaf905c9154df3f8551de0e5899c/vendor/bundle/ruby/2.5.0/gems/railties-6.0.2.1/lib/rails/application.rb:363:in `initialize!'
remote:        /tmp/build_e14dcaf905c9154df3f8551de0e5899c/config/environment.rb:5:in `<top (required)>'
remote:        /tmp/build_e14dcaf905c9154df3f8551de0e5899c/vendor/bundle/ruby/2.5.0/gems/bootsnap-1.4.6/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:23:in `require'
remote:        /tmp/build_e14dcaf905c9154df3f8551de0e5899c/vendor/bundle/ruby/2.5.0/gems/bootsnap-1.4.6/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:23:in `block in require_with_bootsnap_lfi'
remote:        /tmp/build_e14dcaf905c9154df3f8551de0e5899c/vendor/bundle/ruby/2.5.0/gems/bootsnap-1.4.6/lib/bootsnap/load_path_cache/loaded_features_index.rb:92:in `register'
remote:        /tmp/build_e14dcaf905c9154df3f8551de0e5899c/vendor/bundle/ruby/2.5.0/gems/bootsnap-1.4.6/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:22:in `require_with_bootsnap_lfi'
remote:        /tmp/build_e14dcaf905c9154df3f8551de0e5899c/vendor/bundle/ruby/2.5.0/gems/bootsnap-1.4.6/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:31:in `require'
remote:        /tmp/build_e14dcaf905c9154df3f8551de0e5899c/vendor/bundle/ruby/2.5.0/gems/zeitwerk-2.2.2/lib/zeitwerk/kernel.rb:23:in `require'
remote:        /tmp/build_e14dcaf905c9154df3f8551de0e5899c/vendor/bundle/ruby/2.5.0/gems/activesupport-6.0.2.1/lib/active_support/dependencies.rb:325:in `block in require'
remote:        /tmp/build_e14dcaf905c9154df3f8551de0e5899c/vendor/bundle/ruby/2.5.0/gems/activesupport-6.0.2.1/lib/active_support/dependencies.rb:291:in `load_dependency'
remote:        /tmp/build_e14dcaf905c9154df3f8551de0e5899c/vendor/bundle/ruby/2.5.0/gems/activesupport-6.0.2.1/lib/active_support/dependencies.rb:325:in `require'
remote:        /tmp/build_e14dcaf905c9154df3f8551de0e5899c/vendor/bundle/ruby/2.5.0/gems/railties-6.0.2.1/lib/rails/application.rb:339:in `require_environment!'
remote:        /tmp/build_e14dcaf905c9154df3f8551de0e5899c/vendor/bundle/ruby/2.5.0/gems/railties-6.0.2.1/lib/rails/application.rb:515:in `block in run_tasks_blocks'
remote:        /tmp/build_e14dcaf905c9154df3f8551de0e5899c/vendor/bundle/ruby/2.5.0/gems/sprockets-rails-3.2.1/lib/sprockets/rails/task.rb:62:in `block (2 levels) in define'
remote:        /tmp/build_e14dcaf905c9154df3f8551de0e5899c/vendor/bundle/ruby/2.5.0/gems/rake-13.0.1/exe/rake:27:in `<top (required)>'
remote:        Tasks: TOP => environment
remote:        (See full trace by running task with --trace)
remote: 
remote:  !
remote:  !     Precompiling assets failed.
remote:  !

このエラーのここ注目してください。

Sprockets::Railtie::ManifestNeededError: Expected to find a manifest file in `app/assets/config/manifest.js`

app/assets/config/manifest.jsを期待してたんだけどなあって言われてるんですね。

なので、作っちゃいます。

解決手順②

スクリーンショット 2020-02-26 14.32.44.png

こんな感じで作ってください。ファイルの中身は空でOKです。

そしてまた

$ git push origin master

masterブランチにpushし

$ git push heroku master

デプロイします。

https://xxxxxxxxx.herokuapp.com/ deployed to Heroku

デプロイ成功です。

そして

$ heroku run rails db:migrate

migrateしても

D, [2020-02-26T04:10:06.961840 #4] DEBUG -- :    (62.5ms)  CREATE TABLE "schema_migrations" ("version" character varying NOT NULL PRIMARY KEY)
D, [2020-02-26T04:10:06.984953 #4] DEBUG -- :    (13.8ms)  CREATE TABLE "ar_internal_metadata" ("key" character varying NOT NULL PRIMARY KEY, "value" character varying, "created_at" timestamp(6) NOT NULL, "updated_at" timestamp(6) NOT NULL)
D, [2020-02-26T04:10:06.987824 #4] DEBUG -- :    (1.3ms)  SELECT pg_try_advisory_lock(1636886831838516615)
D, [2020-02-26T04:10:07.016634 #4] DEBUG -- :    (2.0ms)  SELECT "schema_migrations"."version" FROM "schema_migrations" ORDER BY "schema_migrations"."version" ASC
I, [2020-02-26T04:10:07.018343 #4]  INFO -- : Migrating to CreateUsers (20200225063934)
== 20200225063934 CreateUsers: migrating ======================================
-- create_table(:users)
D, [2020-02-26T04:10:07.023146 #4] DEBUG -- :    (1.1ms)  BEGIN
D, [2020-02-26T04:10:07.045683 #4] DEBUG -- :    (21.9ms)  CREATE TABLE "users" ("id" bigserial primary key, "name" character varying, "age" integer, "created_at" timestamp(6) NOT NULL, "updated_at" timestamp(6) NOT NULL)
   -> 0.0241s
== 20200225063934 CreateUsers: migrated (0.0242s) =============================

D, [2020-02-26T04:10:07.058256 #4] DEBUG -- :   primary::SchemaMigration Create (2.2ms)  INSERT INTO "schema_migrations" ("version") VALUES ($1) RETURNING "version"  [["version", "20200225063934"]]
D, [2020-02-26T04:10:07.061377 #4] DEBUG -- :    (2.8ms)  COMMIT
D, [2020-02-26T04:10:07.074206 #4] DEBUG -- :   ActiveRecord::InternalMetadata Load (1.4ms)  SELECT "ar_internal_metadata".* FROM "ar_internal_metadata" WHERE "ar_internal_metadata"."key" = $1 LIMIT $2  [["key", "environment"], ["LIMIT", 1]]
D, [2020-02-26T04:10:07.089249 #4] DEBUG -- :    (1.2ms)  BEGIN
D, [2020-02-26T04:10:07.091895 #4] DEBUG -- :   ActiveRecord::InternalMetadata Create (2.2ms)  INSERT INTO "ar_internal_metadata" ("key", "value", "created_at", "updated_at") VALUES ($1, $2, $3, $4) RETURNING "key"  [["key", "environment"], ["value", "production"], ["created_at", "2020-02-26 04:10:07.086635"], ["updated_at", "2020-02-26 04:10:07.086635"]]
D, [2020-02-26T04:10:07.094787 #4] DEBUG -- :    (2.6ms)  COMMIT
D, [2020-02-26T04:10:07.096357 #4] DEBUG -- :    (1.4ms)  SELECT pg_advisory_unlock(1636886831838516615)

エラーを吐くことなく、無事migrateすることができました。

$ heroku open

をしてローカルの内容がきちんと確認できれば大成功です。

今回はAPI特化でRailsアプリを作成していたので、出くわしたことのないエラーが多発して大変でした。

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

Railsでdeviseを導入する

チラ裏レベルのなぐり書きを世に放って申し訳ない。

方法

$ vim Gemfile => gem "devise" を追加
$ bundle install
$ bundle exec rails g devise:install

以下を設定する。
===============================================================================
Some setup you must do manually if you haven't yet:

  1. Ensure you have defined default url options in your environments files. Here
     is an example of default_url_options appropriate for a development environment
     in config/environments/development.rb:

       config.action_mailer.default_url_options = { host: 'localhost', port: 3000 }

     In production, :host should be set to the actual host of your application.

  2. Ensure you have defined root_url to *something* in your config/routes.rb.
     For example:

       root to: "home#index"

  3. Ensure you have flash messages in app/views/layouts/application.html.erb.
     For example:

       <p class="notice"><%= notice %></p>
       <p class="alert"><%= alert %></p>

  4. You can copy Devise views (for customization) to your app by running:

       rails g devise:views
===============================================================================

設定後、

$ bundle exec rails g devise:views
$ bundle exec rails g devise Account

# 必要ならここでmigrationファイルにフィールドを加える

$ bundle exec rails db:migrate
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Railsでdeviseを導入する方法

チラ裏レベルのなぐり書きを世に放って申し訳ない。

方法

$ vim Gemfile
$ bundle install
$ bundle exec rails devise:install

以下を設定する。
===============================================================================
Some setup you must do manually if you haven't yet:

  1. Ensure you have defined default url options in your environments files. Here
     is an example of default_url_options appropriate for a development environment
     in config/environments/development.rb:

       config.action_mailer.default_url_options = { host: 'localhost', port: 3000 }

     In production, :host should be set to the actual host of your application.

  2. Ensure you have defined root_url to *something* in your config/routes.rb.
     For example:

       root to: "home#index"

  3. Ensure you have flash messages in app/views/layouts/application.html.erb.
     For example:

       <p class="notice"><%= notice %></p>
       <p class="alert"><%= alert %></p>

  4. You can copy Devise views (for customization) to your app by running:

       rails g devise:views
===============================================================================

設定後、

$ bundle exec rails g devise:views
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

each文とrender のcollectionオプション[備忘録]

はじめに

掲示板アプリを作成しているのだが、
いいね機能実装時、

https://qiita.com/hayabusa3703/items/2b916e652a1dc85bb6e3

を参考に(ほぼ写して)、コードを書いていた。

機能自体の実装は成功したが、ビューでの表示が思うようにいかない。

各投稿一つ一つに、全ての投稿のイイねボタンがついてしまった。

これに対処するにあたって、
今までおざなりにしていた、renderとそのオプションについての理解がかなり深まったので、備忘録として残す。

バージョンなど

ruby 2.5.1
rails 5.2.4.1
bootstrap 4.4.1
haml-rails 2.0.1
jquery-rails 4.3.5

コード

posts_controller.rb
def index
    @posts = Post.all.includes(:user).order('created_at DESC')
  end
index.html.haml
.events__wrapper.row
        /# each文
      - @posts.each do |post|
        .events__content.col-sm-6.col-md-3.mb-3
          .card{id: post.id}
            %label.m-1
              - if post.image
                %img.card-img-top{src: "#{post.image}"}
              - else
                %img.card-img-top{src: "/public/noimage.jpeg"}
              .card-body.event
                %h5= link_to "#{post.title}", post_path(post.id), class: "event-title stretched-link text-decoration-none"
                .event__name 
                  #{post.user.name} さん
                .text-right
                  = l post.created_at, format: :long
                  = render partial: '/posts/posts', collection: @posts, as: :post  

each文の中に、renderとオプションでcollectionを使っている形になっている。

この書き方だと、先述の通り、一つのpostにイイねボタンがいくつもついてしまう。

原因を調べていると発見した、とてもrenderとオプションについてまとめてあるqiitaの記事があるので共有しておきます。

https://qiita.com/takeru56/items/299850d0f054ce107e21

この記事を読んで、collectionオプションは変数に自動でeachメソッドを使ってくれていることに気付き、renderの記述を修正。

index.html.haml
= render partial: '/posts/posts', locals: {post: post}
/# これにより、元々あったeach文の中の変数postを部分テンプレートに渡せる。

まとめ

イイね機能の実装を通じて、
collection, locals, object, as, などのオプションの理解が深まった。

エラーの数だけ成長がある!!!

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

Herokuでデプロイしたアプリで投稿画像の保存先をS3に設定してみた。

はじめに

前回記事でHerokuにデプロイしたアプリですが、投稿した画像が時間が経つと次のように表示されなくなります。
スクリーンショット 2020-02-25 21.43.43.png

原因

git上で管理されている画像は表示されますが、ブラウザ上で投稿した画像については保存されないため、一定時間で表示されなくなってしまいます。

これは、実装した画像アップローダーは、開発環境で動かす分には問題はありませんが、本番環境には適していません。app/uploaders/image_uploader.rbstorage :fileというコードによって、ローカルのファイルシステムに画像を保存するようになるためです。本番環境でも投稿した画像を保存するためには、ファイルシステムではなくクラウドストレージサービスに画像を保存する必要があります。

対処法

今回AWS/S3のクラウドストレージサービスを利用します。なにぶん初心者のためエラーにぶつかる事が多く、スマートな処理ではないですがじぶんの備忘録として同じようなエラーに苦労される方の助けになればと思います。

※注意事項として、試行錯誤しながらやっと解決できた感じなので、決してスマートな解決方法ではないと思います。その点はご了承ください。そしてこうしたらスマートに解決できるよ!というご意見がございましたら是非コメントいただければ幸いです!

前提条件

・Railsアプリ作成済
・CarrierWaveとMiniMagickで画像投稿機能は実装済
・AWSアカウント登録済
・AmazonS3FullAccess権限を与えたIAMユーザーを作成済

以下実装の流れ

1.fogのインストール

本番環境でクラウドストレージに保存するために、fog gemを使います。

Gemfile.
gem 'bootstrap', '~> 4.4.1'
gem 'jquery-rails'
gem 'devise', '~> 4.6.1'
gem 'carrierwave', '~> 1.0'
gem "mini_magick"
gem 'fog'      <= 追加
$ bundle install path --vendor/bundle

2.S3で保存先を準備

AWSでバケットの作成します。
スクリーンショット 2020-02-02 12.51.01.png

オリジナルのバケット名を入力し、アクセス許可をパブリックに設定します。

スクリーンショット 2020-02-26 12.01.27.png

3.設定

Herokuでの画像の保存先をAmazon S3に保存できるように設定します。
基本的な話ですが、アクセスキーIDとシークレットアクセスキーは直接入力厳禁です。これをするととんでもないことになります。ですので、必ず環境変数を設定します。私は身をもって体験いたしました。

config/initializers/carrierwave.rb
 〈中略〉 以下を追加
require 'carrierwave/storage/abstract'
require 'carrierwave/storage/file'
require 'carrierwave/storage/fog'

if Rails.env.production?
  CarrierWave.configure do |config|
    config.fog_credentials = {
      :provider              => 'AWS',
      :region                => ENV['S3_REGION'],     
      :aws_access_key_id     => ENV['S3_ACCESS_KEY'],
      :aws_secret_access_key => ENV['S3_SECRET_KEY']
    }
    config.fog_directory     =  ENV['S3_BUCKET']
  end
end
app/uploaders/image_uploader.rb
class ImageUploader < CarrierWave::Uploader::Base
  # Include RMagick or MiniMagick support:
  # include CarrierWave::RMagick
  include CarrierWave::MiniMagick

 # 以下を追加
 if Rails.env.production?
    storage :fog
  else
    storage :file
  end

4.herokuへ環境変数登録

herokuに環境変数を登録(IAMでCSVダウンロードした中身通りに値を入力)

$ heroku config:set S3_ACCESS_KEY="Accessキーを入力"
Setting S3_ACCESS_KEY and restarting ⬢ [アプリ名]... done, v15
S3_ACCESS_KEY: Accessキー

$ heroku config:set S3_SECRET_KEY="Secretキーを入力"
Setting S3_SECRET_KEY and restarting ⬢ [アプリ名]... done, v16
S3_SECRET_KEY: Secretキー

$ heroku config:set S3_BUCKET="Bucket名を入力"
Setting S3_BUCKET and restarting ⬢ [アプリ名]... done, v17
S3_BUCKET: Bucket名

$ heroku config:set S3_REGION="Region名を入力"
Setting S3_REGION and restarting ⬢ [アプリ名]... done, v18
S3_REGION: Region名

以上でherokuへ再度pushして'heroku open'してみると……

スクリーンショット 2020-02-02 14.43.49.png
上手くいっていないようです。ログを確認してみます。

$ heroku logs -t
2020-02-02T05:42:54.905758+00:00 app[web.1]: from /app/vendor/bundle/ruby/
2.6.0/gems/bootsnap-1.4.5/lib/bootsnap/load_path_cache/core_ext/kernel_require.
rb:22:in `block in require_with_bootsnap_lfi'
・
・
・
2020-02-02T05:43:15.180513+00:00 heroku[router]: at=error code=H10 
desc="App crashed" method=GET path="/" host=photo-ogiri.herokuapp.com 
request_id=ba2d6298-7c81-40a0-843d-02b9e2a700ef fwd="126.233.30.95" 
dyno= connect= service= status=503 bytes= protocol=https

アプリがクラッシュしたとの内容のみで修正が必要な箇所がわからない。

$ heroku run rails c
・
・
・
in `block in load_missing_constant': uninitialized constant CarrierWave::Storage::Fog (NameError)

見つけました。carrierwaveの部分でエラーが出ているようです。

5.エラー対処

確認すると carrierwave.rb が config/initializers 配下になく、 vendor/bundle 配下にしかなかったため、 config/initializers 配下に新たに carrierwave.rb を作成し、再度、heroku pushすると別のエラーが発生しました。

$ git push heroku master
・
remote:        rake aborted!
remote:        NameError: uninitialized constant CarrierWave::Storage::Fog
・
・
remote:  !
remote:  !     Precompiling assets failed.
remote:  !
remote:  !     Push rejected, failed to compile Ruby app.
・

まずは開発環境でPrecompileするために、以下を実行し成功することを確認しました。

$ RAILS_ENV=development bundle exec rails assets:precompile
・
・
yarn install v1.21.1
info No lockfile found.
[1/4] ?  Resolving packages...
[2/4] ?  Fetching packages...
[3/4] ?  Linking dependencies...
[4/4] ?  Building fresh packages...
success Saved lockfile.
✨  Done in 0.11s.

続いて本番環境でPrecompileできるか試してみました。

$ RAILS_ENV=production bin/rails assets:precompile
rails aborted!
NameError: uninitialized constant CarrierWave::Uploader

carrierwaveの設定に修正が必要な様子。調べてみるとRailsガイドではRails5.2以降の変更点として次の記載がありましたので、carrierwave.rbを修正しました。

config/credentials.yml.encファイルが追加され、productionアプリケーションの秘密情報(secret)をここに保存できるようになりました。これによって、外部サービスのあらゆる認証credentialを、config/master.keyファイルまたはRAILS_MASTER_KEY環境変数にあるキーで暗号化した形で直接リポジトリに保存できます。

carrierwave.rb
require 'carrierwave/storage/fog'

  CarrierWave.configure do |config|
    if Rails.env.production?
    config.fog_provider = 'fog/aws'
    config.fog_credentials = {
      provider:              'AWS',
      aws_access_key_id:     Rails.application.credentials.aws[:access_key_id],
      aws_secret_access_key: Rails.application.credentials.aws[:secret_access_key],
      region:                'ap-northeast-1'
    }
    config.fog_directory  = 'S3バケット名'
    config.fog_public     = true
    config.fog_attributes = { cache_control: "public, max-age=#{365.days.to_i}" }
    end
  end

再度、本番環境でPrecompileできるか試してみました。

$ RAILS_ENV=production bundle exec rails assets:precompile
rails aborted!
ArgumentError: Missing `secret_key_base` for 'production' environment, set this string with `rails credentials:edit`

失敗しましたがエラーは変わりました。secret_key_baseあたりを確認します。

$ EDITOR=vim rails credentials:edit
aws:
   access_key_id: ~~~~~~~~~~~~~~~~~~~~~~~
   secret_access_key: ~~~~~~~~~~~~~~~~~~~
# Used as the base secret for all MessageVerifiers in Rails, including the one protecting cookies.
   secret_key_base: ~~~~~~~~~~~~~~~~~~~~~~~~~

値もきちんと入っていますが、rails cで1つずつキーを確認するとsecret_key_baseのみがnillでした。

$ bundle exec rails c
>Rails.application.credentials.secret_key_base
=> nil
>Rails.application.credentials.aws[:access_key_id]
=> ~~~~~~~~~~~~~~~~~
>Rails.application.credentials.aws[:secret_access_key]
=> ~~~~~~~~~~~~~~~~~

ここでかなり苦戦しましたが、結果インデントの位置がおかしかったため生じていたエラーでした。修正後のcredentials.yml.encファイルがこちら。secret_key_base:のインデントを左に寄せただけです。恐らく修正前のコードだとaws:配下のコードとみなされてしまって、secret_key_base:の値が読み取られなかったことが原因です。

$ EDITOR=vim rails credentials:edit
aws:
   access_key_id: ~~~~~~~~~~~~~~~~~~~~~~~
   secret_access_key: ~~~~~~~~~~~~~~~~~~~
# Used as the base secret for all MessageVerifiers in Rails, including the one protecting cookies.
secret_key_base: ~~~~~~~~~~~~~~~~~~~~~~~~~

無事プリコンパイル処理もとおりました。

$ RAILS_ENV=production bundle exec rails assets:precompile
yarn install v1.21.1
[1/4] ?  Resolving packages...
success Nothing to install.
success Saved lockfile.
✨  Done in 0.15s.

あとはherokuにmaster.keyをセットして完了です。master.key は git 管理していないので、heroku にはデプロイされていません。そのため、最初この状態でheroku pushするとNoMethodError: undefined method '[]' for nil:NilClassと怒られました。

$ heroku config:set RAILS_MASTER_KEY=`cat config/master.key`
$ git push heroku master

無事heroku上のアプリに画像が表示されるようになり、S3のバケットに画像が保存されていることも確認できました。

スクリーンショット 2020-02-26 12.17.52.png

参考記事

https://railsguides.jp/asset_pipeline.html
https://github.com/carrierwaveuploader/carrierwave#using-amazon-s3
https://railsguides.jp/5_2_release_notes.html#credential%E7%AE%A1%E7%90%86
https://workabroad.jp/posts/2166

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

[Rails]個人アプリ開発の際の便利なgem

初めての個人アプリ開発の際に導入すると便利なgemを紹介します。(個人的なメモ用)(どれもとても有名なものです。)

この記事ではmacOS Catalina バージョン 10.15.3にインストールした Rails5.2.4.1を使っています。

Better Errors(Railsのエラーを見やすくしてくれます。)

https://github.com/BetterErrors/better_errors

Bullet(N+1問題を自動で発見してくれます。)

https://github.com/flyerhzm/bullet

robocop(コードが規約に沿って書かれているか指摘してくれます。)

https://github.com/rubocop-hq/rubocop

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

RailsでjQueryを導入する方法+確認方法

jQuery導入について

・久しぶりにjQueryを導入しようとして苦戦したので調べました。

導入手順

環境
Rails 5.2.3
Ruby 2.5.1

1.gem 'jquery-rails'をインストール

(Gemfile)
# 全ての環境で適用したい為、最下部に記述
gem 'jquery-rails'

忘れずにbundle installしましょう!

2.application.jsを編集

require jqueryを追加

application.js
//= require jquery

※注意点
記述の順番によってはjQueryが動作しないので要注意!

application.js
//= require jquery
//= require jquery_ujs
//= require turbolinks
//= require_tree . #require_treeより上にjquery、jquery_ujsを書くこと
//= require activestorage

基本的にはここまで実装できれば、あとはhtmlファイルでイベントを用意して、jsファイルで
イベントに対して内容を記載すれば動きます。

3.turbolinksを使う場合

application.html.erb等htmlファイルのhead内に以下の記述があるか確認。
またdocumentの記載方法についても注意が必要。

application.html.erb
 <%= javascript_include_tag 'application', 'data-turbolinks-track': 'reload' %>
products.js
 //初回読み込み、リロード、ページ切り替えで動く。
$(document).on('turbolinks:load', function() { });

//初回読み込み、ページ切り替えで動く。リロードは動かない
$(document).on('turbolinks:render', function() { });

//ページ遷移前に行いたい処理用。ページ切り替えでもリロードでも動かない
$(document).on('turbolinks:request-start', function() { });

4.導入確認方法

①console.logでチェック

ブラウザに表示できているか確認。

application.js
//= require jquery
//= require jquery_ujs
//= require turbolinks
//= require_tree . 
//= require activestorage

console.log('hoge') # 追加

スクリーンショット 2020-02-25 14.50.22.png

②仮でコントローラー・ビューを作成

tests_controller.rb
class TestsController < ApplicationController

  def index
  end

end
index.html
<h1>Ajax#index</h1>
<p id="hoge">赤色になるよ</p>

<script>
  $("#hoge").css("color","red")
</script>

スクリーンショット 2020-02-25 15.07.47.png
文字の色が変わればjQueryは導入できています。

5.それでも導入できない場合

coffeeファイルの削除

今回苦戦した原因がこのcoffeeファイルでした。
「rails g controller」で任意のコントローラーを作成した際に、自動で「コントローラー名.coffee」という名前で生成されます。
jsファイルと同様の名前でcoffeeファイルがあると、同じ名前で作ったjsファイルがあっても、coffeeファイルが優先的にRailsから呼び出されるため、jsファイルが呼び出されないという状態になります。
coffeeファイルは使用しないならコントローラー作成時に忘れずに削除しましょう!


今回はturbolinksを初めて使用することもあり、記述誤りばかりを疑っていました。
coffeeファイルについても理解が足りてなかったです:sweat_smile:
今後学習していく中で追加事項があれば随時更新していきます!

参考:
https://qiita.com/s-yank/items/cf7cadbb6c6996d67cf7
https://qiita.com/kumagi/items/289ccadf344f32613304
coffeeファイルについて

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

Railsの静的コード解析をGitHub Actionsでやる

これはなにか

GitHub に push すると GitHub Actions がブンブン回って、静的コード解析をしてくれるやつ。ついでにキャッシュされるので、毎回 bundle install がまわることがないやつ。はやい、やすい、うまい ... かどうかはわからないが、要はオレによし、オマエによし。

ワークフロー

name: Static Check 

on: [push]

jobs:
  RuboCop:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/setup-ruby@v1
      with:
        ruby-version: '2.7'
    - uses: actions/checkout@v2
    - uses: actions/cache@preview
      with:
        path: ./web/vendor/bundle
        key: ${{ runner.os }}-gem-${{ hashFiles('**/Gemfile.lock') }}
        restore-keys: |
          ${{ runner.os }}-gem-
    - name: Run RuboCop
      run: |
        cd ./web/
        bundle install --jobs 4 --retry 3 --path vendor/bundle
        bundle exec rubocop -a
  BrakeMan:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/setup-ruby@v1
      with:
        ruby-version: '2.7'
    - uses: actions/checkout@v2
    - uses: actions/cache@preview
      with:
        path: ./web/vendor/bundle
        key: ${{ runner.os }}-gem-${{ hashFiles('**/Gemfile.lock') }}
        restore-keys: |
          ${{ runner.os }}-gem-
    - name: Run BrakeMan
      run: |
        cd ./web/
        bundle install --jobs 4 --retry 3 --path vendor/bundle
        bundle exec rubocop -a
  RubyCritic:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/setup-ruby@v1
      with:
        ruby-version: '2.7'
    - uses: actions/checkout@v2
    - uses: actions/cache@preview
      with:
        path: ./web/vendor/bundle
        key: ${{ runner.os }}-gem-${{ hashFiles('**/Gemfile.lock') }}
        restore-keys: |
          ${{ runner.os }}-gem-
    - name: Run BrakeMan
      run: |
        cd ./web/
        bundle install --jobs 4 --retry 3 --path vendor/bundle
        bundle exec rubycritic

おわりに

ほんとは Docker 化しているのだから、Docker 上で動かせば良いのだろうけど Docker イメージの置き場所とか考えると、めんどくさくなったのでこうした。次は GitHub Actions で Docker image をロードして CI するやつでもつくろうかな。

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

ビューファイルの作成

ビューファイルはアプリケーションの見た目を定義するファイル。ビューファイルはapp/views/コントローラー名ディレクトリに、二本指クリックで新しいファイルを選択し、アクション名.html.erbで作成される。
アクション名は7つのアクション
ちなみにhamlでのファイル名はアクション名.hamlでOK

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

7つのアクションとは

7つのアクションとはindex、new、create、show、edit、update、destroyのこと。
アクションにより処理が分かれる。
indexはトップページを表示
newは新規投稿ページを表示
createはデータの新規投稿
showは個別詳細ページを表示
editは投稿編集ページを表示
updateはデータの編集
destroyは投稿のデータの削除
これらを作ったVS Codeのcontroller.rbに記述していく

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