20210302のRailsに関する記事は29件です。

デバックツール pry-beybugの導入

はじめに

変数を確認したり、メソッドを呼び出す為の、デバックツールの導入方法のメモになります。

Gemfileに記入する

Gemfile 記入前

group :development do
  # Access an IRB console on exception pages or by using <%= console %> anywhere in the code.
  gem 'web-console'
  gem 'listen', '~> 3.0.5'
  # 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

Gemfile 記入後

group :development do
  # Access an IRB console on exception pages or by using <%= console %> anywhere in the code.
  gem 'web-console'
  gem 'listen', '~> 3.0.5'
  # 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'
 gem 'pry-byebug'
end

docker-compose buildを行う

docker-compose up -d

docker-compose psでコンテナを確認する

   Name                  Command               State           Ports
-----------------------------------------------------------------------------
rails_db_1    docker-entrypoint.sh mysqld      Up      3306/tcp, 33060/tcp
rails_web_1   bundle exec rails s -p 300 ...   Up      0.0.0.0:3000->3000/tcp

railsサーバーにアタッチを行う

docker attach rails_web_1

あとはデバックをしたい部分にbindng.pryを書くだけ。

デバッカーから抜けたい場合はexitかcontinueで抜ける。

アタッチを外したい場合はCtrlキーを押しながら、pキー、qキーの順に押す。

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

webpackとは?

webpackとは

  1. Webアプリケーションを作成する際に必要な、
    様々なJavaScriptをひとまとめに管理する為のツール
  2. モダンなWebアプリケーション開発では、
    様々なJavaScriptライブラリを用いる為、その依存関係を管理してくれる

以前取り上げた機械語と人間語とも関連しています。

webpackの公式ドキュメントこちらも参考として添付します。

webpackが行うこと

基本要素 読み 役割
Entry エントリー 依存関係を解決するために、
どのファイルを基準(エントリーポイント)とするかを決める
Output アウトプット エントリーポイントにされ、
webpackによってまとめられたファイルを、
どこへどのような名前で出力(アウトプット)するのか指定する
Loaders ローダー JavaScript以外のCSSやHTMLなどのファイルを
モジュールに変換する方法を読み込み(ロード)、指定した処理を行う
Plugins プラグイン 圧縮などの、ファイルをまとめる以外で
ローダーが実行できないタスクを導入し、拡張(プラグイン)する

webpackを用いることで、JavaScriptのライブラリとJavaScript以外の
さまざまな言語を、変換・圧縮した上で、好きな場所に配置することが可能

WebpackerというGem

Railsにもwebpackを導入してコマンドによる操作が可能だが、
設定ファイルの記述がやや難しくなっている
そのため、設定を簡易化してくれるWebpackerというGemを使用

Webpacker

webpackをRails仕様にし、専用の設定ファイルやヘルパーメソッドを用意してくれるGem

Railsバージョン6系以降からは、デフォルトでWebpackerが導入される

近年のフロントエンド技術の台頭により、
(主には、JavaScriptのライブラリが充実してきたこと)
Sprocketsからwebpackを利用する方針へ転換された

Webpackerの公式GitHub

Webpackerによって、
Sprocketsのアセットパイプラインと同じような静的ファイルのプリコンパイルに加え、
JavaScriptのパッケージが利用できるようになる

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

[Rails] 変数の中身が空か確認するメソッド

Railsで view model controller でも変数の中身が空か確認したいときに以下のメソッドを使います。

nilメソッド

nil?
オブジェクトがnilの場合のみ true を返し、それ以外は false を返します。空文字や配列の器、ハッシュの器だけでも全てfalseを返します。

emptyメソッド

empty?
String(文字列)Array(配列)Hash(連想配列) 定義されているメソッドです。空の文字列や空の配列の場合にtrueを返します。nilや真偽値オブジェクトに対して呼び出すと NoMethodError が発生するので、気をつけましょう。

blankメソッド

blank?
blankメソッドを使用したオブジェクトが空白の場合は true を返し、空白ではない場合は false を返すメソッドです。〜が存在しないとき の条件分岐でよく使います。

presentメソッド

present?
blankメソッドの逆(!blank?)で、オブジェクトが空白ではない場合に trueを返し、空白の場合は false を返します。 〜が存在するとき の条件分岐でよく使います。

if hoge.present?unless hoge.blank? は同じ意味になりますが、前者を使った方がわかりやすいです。

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

dockerでgemを追加したときにbundle installではなくbuildする

備忘録的な感じです

題名にある通りDockerでrails開発をしていてgemを追加した後

$ docker-compose run --rm (コンテナ名) bundle install

を試したらgemが更新されずエラーになる

$ docker-compose build

でエラーも出ず解決した。

色々調べていたらdockerfileの

Dokerfile
FROM ruby:2.6.6
RUN apt-get update -qq && apt-get install -y nodejs postgresql-client
RUN mkdir /rails-qanda
WORKDIR /rails-qanda
COPY Gemfile /rails-qanda/Gemfile
COPY Gemfile.lock /rails-qanda/Gemfile.lock
RUN bundle install
COPY . /rails-qanda

のCOPY Gemfile /rails-qanda/Gemfileが
[COPY] ローカルのファイルをコンテナへコピー
という意味らしいのでgemを追加したらここの部分やり直すためにbuildし直すということなのかな。

また理解が深まり次第追記予定

docker難しい、、

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

あとからカラム属性にインデックスを追加する

インデックス(索引)

インデックスとは検索やソートを高速化するためのデータ構造の事です。
むやみにインデックスを貼るのはデータベースリソースを余分に使うため、性能が向上するかどうかを見極めてから作成する必要があります。

モデル作成とadd_indexを同時に試みるも、

PG::UndefinedTable: ERROR:  relation "care_recipitents" does not exist

エラーを吐かれてしまったので先にモデルを作成しました。

migrationファイルを作成します。

rails g migration add_index_to_carerexipidents
class AddIndexToCarerexipidents < ActiveRecord::Migration[6.0]
  def change
    add_index :care_recipitents, [ :family_name_kana, :given_name_kana ]
  end
end

複合インデックスを作成しています。

rails db:migrate

schema.rb

 t.index ["family_name_kana", "given_name_kana"], name: "index_care_recipitents_on_family_name_kana_and_given_name_kana"

ちゃんと貼られています。

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

【ShouldaMatchers】複数カラムへの重複チェックで発生したエラーの原因と対応方法

事象

RspecでShouldaMatchersを使用した複数カラムの重複チェック実行時、以下のようなDBのエラーが発生しました。

 1) Like is expected to validate that :user_id is case-sensitively unique within the scope of :post_id
     Failure/Error: it { is_expected.to validate_uniqueness_of(:user_id).scoped_to(:post_id) }

     Shoulda::Matchers::ActiveRecord::ValidateUniquenessOfMatcher::ExistingRecordInvalid:
       validate_uniqueness_of works by matching a new record against an
       existing record. If there is no existing record, it will create one
       using the record you provide.

       While doing this, the following error was raised:

         Mysql2::Error: Field 'user_id' doesn't have a default value

       The best way to fix this is to provide the matcher with a record where
       any required attributes are filled in with valid values beforehand.
     # ./spec/models/like_spec.rb:8:in `block (2 levels) in <main>'
     # -e:1:in `<main>'
     # ------------------
     # --- Caused by: ---
     # Mysql2::Error:
     #   Field 'user_id' doesn't have a default value
     #   ./spec/models/like_spec.rb:8:in `block (2 levels) in <main>'

※投稿に対してのいいね機能のテストとして、UserPostを保持するLikeモデルを作成していました。

spec/models/like_spec.rb
RSpec.describe Like, type: :model do
  it { is_expected.to validate_presence_of(:user_id) }
  it { should belong_to(:user) }
  it { is_expected.to validate_uniqueness_of(:user_id).scoped_to(:post_id) } # エラー発生

  it { is_expected.to validate_presence_of(:post_id) }
  it { should belong_to(:post) }
end
app/models/like.rb
class Like < ApplicationRecord
  belongs_to :user
  belongs_to :post
  validates :user_id, presence: true, uniqueness: { scope: :post_id }
  validates :post_id, presence: true
end

原因

uniquenessに対してテストをするvalidate_uniqueness_ofは、インスタンスが存在しない場合は作成してテスト実行するため、その他のカラムのDBの制約によってはDBエラーが発生するようです。
今回の場合だと、user_idに対してnullを許容しないケースとなっていたため、nullのデータが作成されてしまい、エラーが発生しました。

対応

spec/models/like_spec.rbに、インスタンスを事前に作成するような記述を追加することで、テストが問題なく完了しました。
ShouldaMathcersを使用したモデルのテストではインスタンス等不要だと思っていましたが、作成が必要なケースがあることも認識しておく必要があります。

spec/models/like_spec.rb(修正後)
RSpec.describe Like, type: :model do
  subject { FactoryBot.build(:like) } # 追加
  it { is_expected.to validate_presence_of(:user_id) }
  it { should belong_to(:user) }
  it { is_expected.to validate_uniqueness_of(:user_id).scoped_to(:post_id) }

  it { is_expected.to validate_presence_of(:post_id) }
  it { should belong_to(:post) }
end

参考

モジュール:Shoulda :: Matchers :: ActiveRecord — YARD0.8.7.3によるドキュメント

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

railsのフォロー機能コードベタ貼りメモ

かなり自分用。アウトプットしないと忘れそうなので記録。分かりやすくはない。

db/migrate/relationships.rb
  def change
    create_table :relationships do |t|
      t.integer :follower_id
      t.integer :followed_id

      t.timestamps
    end
model/relationships.rb
    belongs_to :follower, class_name: "User"
    belongs_to :followed, class_name: "User"
model/user.rb
  #userテーブル→relationテーブル
  has_many :relationships, foreign_key: "follower_id", dependent: :destroy
  #relationテーブル→followedテーブル、followed_idを持って来る
  has_many :followings, through: :relationships, source: :followed

  #followerテーブル(本当はuser)→re_relationテーブル(本当relation)へfollowed_idを送る(参照)
  has_many :reverse_of_relationships, class_name: "Relationship", foreign_key: "followed_id", 
  dependent: :destroy
  #re_relationテーブル→userテーブルへfollower_idを持って来る
  has_many :followers, through: :reverse_of_relationships, source: :follower

  def follow(user_id)
    relationships.create(followed_id: user_id)
  end

  def unfollow(user_id)
    relationships.find_by(followed_id: user_id).destroy
  end

  def following?(user)
    followings.include?(user)
  end
routes.rb
  resources :users, only: [:show,:index,:edit,:update] do
    resources :relationships, only: [:create, :destroy] do
      member do
        get 'following' => 'relationships#followings', as: 'followings'
        get 'followers' => 'relationships#followers', as: 'followers'
      end
    end
  end
#menberよりcollectionのほうがいかも
relationships_controller.rb
  def create
    current_user.follow(params[:user_id])
    redirect_to request.referer
  end
  def destroy
    current_user.unfollow(params[:user_id])
    redirect_to request.referer
  end
view/users/どこか
    <% users.each do |user| %>
      <tr>
        <td>
          フォロー数:<%= user.followings.count %>
        </td>
        <td>
          フォロワー数:<%= user.followers.count %>
        </td>
      <td>
          <% unless user == current_user %>
            <% if current_user.following?(user) %>
              <%= link_to 'unfollow', user_relationship_path(user.id, current_user.id), method: :delete %>
            <% else %>
              <%= link_to 'follow', user_relationships_path(user.id), method: :post %>
            <% end %>
          <% end %>
        </td>
      </tr>
    <% end %>
  </tbody>

説明

1行目のrelationshipsはフォローした時にそのカラムを新規保存するときに使う。

relationship = relationships.new(followed_id: user_id)
relationship.save

イメージはこんな感じ
2行目のfollowingsは@user = User.find(params[:id])で特定のuser_idを抽としたときに@user.followings@userのフォロワー一覧が出せる。

1、2行目はusersテーブル→relationshipsテーブル→usersテーブルのフォローする人の一連の流れを表している
3、4行目はusersテーブル→relationshipsテーブル→usersテーブルのフォローされる人の一連の流れを表している

1行目はUserテーブルからrelationshipsテーブルに値を受け渡すための文。
例えばUser.find(1).relationshipsの記載だとUserid1なのでrelationshipsのfollower_idが1のrelationshipsのidが返される。

2行目がカラム→Viewにカラムを渡す時の文。
例えばUserのid1の人のフォロー『されてる』人を参照したいときはUser.followingsとやれば「follower_idが1」のfollowed_idを全部持ってきて!となり、User_id1の人のフォローされてる人をviewに持ってこれる

自分メモ

followers, through: :reverse_of_relationships, source: :followed
followersからreverse_of_relationshipsを通ってfollowedを持って来るという意味。sourceは持って来る側だから定義しているものの逆を書くイメージ。私は逆で認識してた。あとuserに対してfollowingsとやると沢山のfollowed_idが現れること。多対多ということを忘れず!!

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

【Railsチュートリアル】第11章 アカウントの有効化②

11.3 アカウントを有効化する

AccountActivationsコントローラのeditアクションを書いていく。アクションへのテストを書き、しっかりとテストできていることが確認できたら、AccountActivationsコントローラからUserモデルにコードを移していく作業(リファクタリング)にも取り掛かかる。

11.3.1 authenticated?メソッドの抽象化

sendメソッドを使うと共通化できる。

sendメソッド とは?
渡されたオブジェクトに「メッセージを送る」ことによって、呼び出すメソッドを動的に決めることができるメソッド。

>> user = User.first

>> user.activation_digest
=> "$2a$10$4e6TFzEJAVNyjLv8Q5u22ensMt28qEkx0roaZvtRcp6UZKRM6N9Ae"

>> user.send(:activation_digest)
=> "$2a$10$4e6TFzEJAVNyjLv8Q5u22ensMt28qEkx0roaZvtRcp6UZKRM6N9Ae"

>> user.send("activation_digest")
=> "$2a$10$4e6TFzEJAVNyjLv8Q5u22ensMt28qEkx0roaZvtRcp6UZKRM6N9Ae"
  # 文字列も渡すことができる

>> attribute = :activation
  # シンボルを代入

>> user.send("#{attribute}_digest")
=> "$2a$10$4e6TFzEJAVNyjLv8Q5u22ensMt28qEkx0roaZvtRcp6UZKRM6N9Ae"
  # 式展開を使って渡す

演習 1

コンソール内で新しいユーザーを作成してみてください。新しいユーザーの記憶トークンと有効化トークンはどのような値になっているでしょうか? また、各トークンに対応するダイジェストの値はどうなっているでしょうか?

>> user = User.create(name: "sample", email: "sample@email.com", password: "sample", password_confirmation: "sample")
   (4.2ms)  SELECT sqlite_version(*)
   (0.1ms)  begin transaction
  User Exists? (1.8ms)  SELECT 1 AS one FROM "users" WHERE LOWER("users"."email") = LOWER(?) LIMIT ?  [["email", "sample@email.com"], ["LIMIT", 1]]
  User Create (8.4ms)  INSERT INTO "users" ("name", "email", "created_at", "updated_at", "password_digest", "activation_digest") VALUES (?, ?, ?, ?, ?, ?)  [["name", "sample"], ["email", "sample@email.com"], ["created_at", "2021-03-02 04:11:18.445221"], ["updated_at", "2021-03-02 04:11:18.445221"], ["password_digest", "$2a$12$.NT19iae7r0WhFR2OcikiemF5WD3QvT0aA4/LJUSZ9UHYOt4XB0T."], ["activation_digest", "$2a$12$jKN4cC3pqjMUIKs4/EvhIOOza0GueGUAKzsGgaMLixzoGHv/Moo5W"]]
   (5.5ms)  commit transaction
=> #<User id: 102, name: "sample", email: "sample@email.com", created_at: "2021-03-02 04:11:18", updated_at: "2021-03-02 04:11:18", password_digest: [FILTERED], remember_digest: nil, admin: nil, activation_digest: "$2a$12$jKN4cC3pqjMUIKs4/EvhIOOza0GueGUAKzsGgaMLixz...", activated: false, activated_at: nil>

演習 2

リスト 11.26で抽象化したauthenticated?メソッドを使って、先ほどの各トークン/ダイジェストの組み合わせで認証が成功することを確認してみましょう。

>> user.remember_token
=> nil
>> user.remember_digest
=> nil
>> user.activation_token
=> "DQKQr9KfehJzT6PzsjrEdQ"
>> user.activation_digest
=> "$2a$12$jKN4cC3pqjMUIKs4/EvhIOOza0GueGUAKzsGgaMLixzoGHv/Moo5W"

11.3.2 editアクションで有効化

editアクションを書いていく。

  # GET /account_acrivations/:id/edit
    user = User.find_by(email: params[:email])
      # emailを元にuserを探して変数userに代入
    if user && !user.activated? && user.authenticated?      (:activation, params[:id])
      # 左: nilかどうかを確認
      # 中: userがactivatedされていないかを確認
      # 右: :activation, params[:id]の2つで認証する
      user.update_attribute(:activated,    true)
      user.update_attribute(:activated_at, Time.zone.now)
      log_in user
        # ログイン
      flash[:success] = "Account activated!"
      redirect_to user
        # プロフィールページにアクセス
    else
      flash[:danger] = "Invalid activation link"
      redirect_to root_url
    end
  end

演習 1

コンソールから、11.2.4で生成したメールに含まれているURLを調べてみてください。URL内のどこに有効化トークンが含まれているでしょうか?

----==_mimepart_603dc222a8f1c_14022ad7ea9149d8402a2
Content-Type: text/plain;
 charset=UTF-8
Content-Transfer-Encoding: 7bit

Hi test,

Welcome to the Sample App! Click on the link below to activate your account:

http://localhost:3000/account_activations/exKxGElXRHOQ6VyC0ia4MA/edit?email=test%40example.com

この部分がトークン: account_activations/exKxGElXRHOQ6VyC0ia4MA/

演習 2

先ほど見つけたURLをブラウザに貼り付けて、そのユーザーの認証に成功し、有効化できることを確認してみましょう。また、有効化ステータスがtrueになっていることをコンソールから確認してみてください。

>> user = User.find_by(name: "test")
  User Load (3.3ms)  SELECT "users".* FROM "users" WHERE "users"."name" = ? LIMIT ?  [["name", "test"], ["LIMIT", 1]]
=> #<User id: 103, name: "test", email: "test@example.com", created_at: "2021-03-02 04:42:10", updated_at: "2021-03-02 04:45:05", password_digest: [FILTERED], remember_digest: nil, admin: nil, activation_digest: "$2a$12$MmJY.TpGghnO.LdaF6GSPO8xQUWDIPzzl0L3ISc5FYU...", activated: true, activated_at: "2021-03-02 04:45:05">

>> user.activated
=> true

11.3.3 有効化のテストとリファクタリング

アカウント有効化の統合テストを追加する。

test/integration/users_signup_test.rb
test "valid signup information with account activation" do
  get signup_path
    # signup_pathにアクセスする
  assert_difference 'User.count', 1 do
    # ユーザー数が1つ増えているか確認。増えたユーザーの情報は以下。
    post users_path, params: { user: { name:  "Example User", email: "user@example.com", password: "password",  password_confirmation: "password" } }
  end
  assert_equal 1, ActionMailer::Base.deliveries.size
    # メールを1つ送っているか確認
  user = assigns(:user)
    # バリデーションされていない@userにアクセスする
  assert_not user.activated?
    #  userが有効化されていないか確認
  log_in_as(user)
    # 有効化していない状態でログインしてみる
  assert_not is_logged_in?
    # ログインできなかったらtrue
  get edit_account_activation_path("invalid token", email: user.email)
    # edit_account_activation_pathにトークンとメールアドレスを渡してアクセス
  assert_not is_logged_in?
    # ログインできなかったらtrue
  get edit_account_activation_path(user.activation_token, email: 'wrong')
    # トークンは正しいが、メールアドレスが無効の場合でアクセス
  assert_not is_logged_in?
    # ログインできなかったらtrue
  get edit_account_activation_path(user.activation_token, email: user.email)
    # トークンもメールアドレスも有効の場合でアクセス
  assert user.reload.activated?
    # ユーザーが更新されたらtrue
  follow_redirect!
    # リダイレクトされる
  assert_template 'users/show'
    # users/showを描画しているか確認
  assert is_logged_in?
    # ログインできていたらtrue
  end

演習 1

リスト 11.35にあるactivateメソッドはupdate_attributeを2回呼び出していますが、これは各行で1回ずつデータベースへ問い合わせしていることになります。リスト 11.39に記したテンプレートを使って、update_attributeの呼び出しを1回のupdate_columns呼び出しにまとめてみましょう。これでデータベースへの問い合わせが1回で済むようになります(注意!update_columnsは、モデルのコールバックやバリデーションが実行されない点がupdate_attributeと異なります)。また、変更後にテストを実行し、 green になることも確認してください。

app/models/user.rb
# アカウントを有効にする
def activate
  update_columns(activated: true, activated_at: Time.zone.now)
end

演習 2

現在は、/usersのユーザーindexページを開くとすべてのユーザーが表示され、/users/:idのようにIDを指定すると個別のユーザーを表示できます。しかし考えてみれば、有効でないユーザーは表示する意味がありません。そこで、リスト 11.40のテンプレートを使って、この動作を変更してみましょう9 。なお、ここで使っているActive Recordのwhereメソッドについては、13.3.3でもう少し詳しく説明します。

app/controllers/users_controller.rb
def index
  @users = User.where(activated: true).paginate(page: params[:page])
end

def show
  @user = User.find(params[:id])
  redirect_to root_url and return unless @user.activated?
end

11.4 本番環境でのメール送信

サンプルアプリケーションの設定を変更し、production環境で実際にメールを送信できるようにする。

演習 1

演習 2

$ heroku push時にPrecompiling assets failed.が出て、解決できなかったので後に回します。

エラーメモ

たぶんここで引っかかってるけど、調べてもよくわからない。

$ git push heroku
.
.
.
remote:        SyntaxError: /tmp/build_bb915e0f/config/environments/production.rb:84: syntax error, unexpected '\n', expecting =>
.
.
.
remote:  !
remote:  !     Precompiling assets failed.
remote:  !
remote:  !     Push rejected, failed to compile Ruby app.
remote: 
remote:  !     Push failed
.
.
.

さいごに

メール認証難しかったです!

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

Railsチュートリアル 第6版 振り返り 第5章

はじめに

このまとめ記事はRailsチュートリアルを1周終えた私が1周目で分からなかった所や記憶に残したい箇所のみをピックアップして記述しています。完全解説記事ではないので注意して下さい。

私と同じく2周目の方、たまに復習したいなと振り返りを行う方等におすすめです。

ナビゲーション(5.1.1)

この章ではレイアウトを作成していく。

その第一段階として、application.html.erbにHTML構造を追加して、レイアウトファイルの更新を行う。

app/views/layouts/application.html.erb
<!DOCTYPE html>
<html>
  <head>
    <title><%= full_title(yield(:title)) %></title>
    <%= csrf_meta_tags %>
    <%= csp_meta_tag %>
    <%= stylesheet_link_tag    'application', media: 'all',
                               'data-turbolinks-track': 'reload' %>
    <%= javascript_pack_tag 'application',
                               'data-turbolinks-track': 'reload' %>
    <!--[if lt IE 9]>
      <script src="//cdnjs.cloudflare.com/ajax/libs/html5shiv/r29/html5.min.js">
      </script>
    <![endif]-->
  </head>
  <body>
    <header class="navbar navbar-fixed-top navbar-inverse">
      <div class="container">
        <%= link_to "sample app", '#', id: "logo" %>
        <nav>
          <ul class="nav navbar-nav navbar-right">
            <li><%= link_to "Home",   '#' %></li>
            <li><%= link_to "Help",   '#' %></li>
            <li><%= link_to "Log in", '#' %></li>
          </ul>
        </nav>
      </div>
    </header>
    <div class="container">
      <%= yield %>
    </div>
  </body>
</html>

このコードの中から重要なコードについて順番に確認していく。

<!--[if lt IE 9]>
  <script src="//cdnjs.cloudflare.com/ajax/libs/html5shiv/r29/html5.min.js">
  </script>
<![endif]-->

このコードは、HTML5のサポートが不完全である、Microsoft Internet Explorer(IE)のバージョンが9より小さい場合に実行されるコード。
これによってバージョンの低いIEでもサポートされる様になる。

このコードはJavaScriptのコードであり、他のブラウザに影響を与えずにIEにのみ実行することが出来るため便利。

link to

<nav>
  <ul class="nav navbar-nav navbar-right">
    <li><%= link_to "Home",   '#' %></li>
    <li><%= link_to "Help",   '#' %></li>
    <li><%= link_to "Log in", '#' %></li>
  </ul>
</nav>

Railsヘルパーのlink toは、リンクを生成する。

第一引数にリンクテキスト、第二引数にURLを指定する。
なお第三引数としてオプションハッシュ(CSSのidなど)の指定も可能。

そしてlink toは、ブラウザからソースを確認するとRailsが埋め込みRubyを評価して、コードは以下の様に置き換わる。

<nav>
  <ul class="nav navbar-nav navbar-right">
    <li><a href="#">Home</a></li>
    <li><a href="#">Help</a></li>
    <li><a href="#">Log in</a></li>
  </ul>
</nav>

homeページのレイアウト

<div class="center jumbotron">
  <h1>Welcome to the Sample App</h1>

  <h2>
    This is the home page for the
    <a href="https://railstutorial.jp/">Ruby on Rails Tutorial</a>
    sample application.
  </h2>

  <%= link_to "Sign up now!", '#', class: "btn btn-lg btn-primary" %>
</div>

<%= link_to image_tag("rails.svg", alt: "Rails logo", width: "200px"),
                      "https://rubyonrails.org/" %>

homeページのHTMLも書き換え。

link toが2回出てくる。特に2回目のlink toに注目。

<%= link_to image_tag("rails.svg", alt: "Rails logo", width: "200px"),
                      "https://rubyonrails.org/" %>

このlink toにはimage_tagヘルパーが使用されている。

image tagは画像ファイルのパスとオプションハッシュを引数に指定できる。

このヘルパーのおかげで、Railsは該当の画像ファイルをアセットパイプラインを通してapp/assets/images/ディレクトリの中から探してくれる。

image_tagはブラウザから生成されたHTMLでは以下の様に表示される。

<img alt="Rails logo" width="200px" src="/assets/rails-<long string>.svg">

<long string>は、例えば画像ファイルを新しい画像に更新したときに、ブラウザ内に保存されたキャッシュに意図的にヒットさせないようにするための仕組み。

また、src属性にはimageディレクトリがない。これは、高速化のための仕組み。

Railsはassetsディレクトリ直下の画像をapp/assets/imagesディレクトリにある画像と紐付けている。

これにより、ブラウザから見るとすべてのファイルが同じディレクトリにあるように見えるようになる。

このようなフラットなディレクトリ構成を採っていると、ファイルをより高速にブラウザに渡すことができるようになる。

BootstrapとカスタムCSS(5.1.2)

ここまでで多くのHTML要素にCSSクラスを関連付けた。
このクラスはBootstrap特有のクラス。

Bootstrapの注目すべき点は、Bootstrapを使うことでアプリケーションをレスポンシブデザインにできるということ。

カスタムCSSルールとBootstrapを組み合わせて使用する。

ruby
gem 'bootstrap-sass', '3.4.1'

Gemfileに追加してインストール。

rails gコマンドを実行することでコントローラーごとに分けられたCSSファイルが自動的に生成されるが、これらのファイルを正しい順序で読み込ませるのは至難の技らしいので、すべてのCSSを1つにまとめる。

$ touch app/assets/stylesheets/custom.scss

上記のようにカスタムCSSの用のファイルを作成したら、@importでBootstrapを読み込む。

app/assets/stylesheets/custom.scss
@import "bootstrap-sprockets";
@import "bootstrap"; #Bootstrapのフレームワーク

この時点で結構良いデザインが出来上がってる。

すごいね〜。しかもこれでレスポンシブデザイン対応とか神すぎる。

んでこの後さまざまなスタイルを追加。

app/assets/stylesheets/custom.scss
/* universal 共通のデザイン*/

body {
  padding-top: 60px; 
}

section {
  overflow: auto;
}

textarea {
  resize: vertical;
}

.center {
  text-align: center;
}

.center h1 {
  margin-bottom: 10px;
}

/* typography 共通の文字設定など*/

h1, h2, h3, h4, h5, h6 {
  line-height: 1;
}

h1 {
  font-size: 3em;
  letter-spacing: -2px;
  margin-bottom: 30px;
  text-align: center;
}

h2 {
  font-size: 1.2em;
  letter-spacing: -1px;
  margin-bottom: 30px;
  text-align: center;
  font-weight: normal;
  color: #777;
}

p {
  font-size: 1.1em;
  line-height: 1.7em;
}

/* header ロゴ編集*/

#logo {
  float: left;
  margin-right: 10px;
  font-size: 1.7em;
  color: #fff;
  text-transform: uppercase;
  letter-spacing: -1px;
  padding-top: 9px;
  font-weight: bold;
}

#logo:hover {
  color: #fff;
  text-decoration: none;
}

パーシャル(5.1.3)

ここまでで大幅にレイアウトを変更することができたが、まだ少々散らかっているらしい。

例えば、IE特有の風変わりな文法のHTML shimだけで3行も占有しているし、HTMLヘッダーは論理的な単位として分けられるため、一箇所にまとめた方が便利。

Railsではパーシャル(partial)という機能でこの問題を解決することが出来る。

app/views/layouts/application.html.erb
<!DOCTYPE html>
<html>
  <head>
    <title><%= full_title(yield(:title)) %></title>
    <%= csrf_meta_tags %>
    <%= csp_meta_tag %>
    <%= stylesheet_link_tag    'application', media: 'all',
                               'data-turbolinks-track': 'reload' %>
    <%= javascript_pack_tag 'application',
                               'data-turbolinks-track': 'reload' %>
    <%= render 'layouts/shim' %> 
  </head>
  <body>
    <%= render 'layouts/header' %>
    <div class="container">
      <%= yield %>
    </div>
  </body>
</html>

renderと呼ばれるRailsヘルパー呼び出しだけを使って、HTML shimのスタイルシート行を置換している。

パーシャルは、ファイル名の先頭にアンダースコアをつけて命名するというルールがある。

このルールに従って、パーシャルを作成していく。
作成場所はapp/views/layouts/ディレクトリ内。

アセットパイプライン(5.2.1)

今まで多くの場面で出てきたこの用語。ようやく詳しく説明してくれます。

Rails開発者視点ではアセットパイプラインの

  • アセットディレクトリ

  • マニフェストファイル

  • プリプロセッサエンジン

以上の3つの主要な機能が理解の対象となるみたい。

1単語ずつ詳しく確認。

アセットディレクトリ

Railsのアセットパイプラインでは、静的ファイルを目的別に分類する、標準的な3つのディレクトリが使用されている。

  • app/assets: 現在のアプリケーション固有のアセット。

  • lib/assets : 自らの開発チームによって作成されたライブラリ用のアセット。

  • vendor/assets : サードパーティのアセット(デフォルトでは存在していない)

これらのディレクトリにはアセットクラス用のサブディレクトリがある。

例えばapp/assetsの場合は

$ ls app/assets/
config  images  stylesheets

この3つのディレクトリがある。

以前作ったカスタムcsscustom.scssapp/assets/stylesheetsに配置されたのは、サンプルアプリケーション固有のアセットだったから。

マニフェストファイル

アセットをアセットディレクトリで紹介した場所にそれぞれ配置すると、マニフェストファイルを使って、それらをどのように1つのファイルにまとめるのかをRailsに指示することができる。

実際にアセットをまとめる処理を行うのはSprocketsというgem。

また、マニフェストファイルはCSSとJavaScriptには適用されますが、画像ファイルには適用されない。

今回は具体例として、アプリケーションのCSS用マニフェストファイルを確認する。

app/assets/stylesheets/application.css
/*
 * This is a manifest file that'll be compiled into application.css, which will
 * include all the files listed below.
 *
 * Any CSS and SCSS file within this directory, lib/assets/stylesheets, or any
 * plugin's vendor/assets/stylesheets directory can be referenced here using a
 * relative path.
 *
 * You're free to add application-wide styles to this file and they'll appear at
 * the bottom of the compiled file so the styles you add here take precedence
 * over styles defined in any other CSS/SCSS files in this directory. Styles in
 * this file should be added after the last require_* statement.
 * It is generally better to create a new file per style scope.
 *
 *= require_tree .
 *= require_self
 */

ここで重要なのが最後の方の数行。

*= require_tree .
*= require_self
*/

この部分は、Sprocketsが適切なファイルを読み込むために使われる。

そして*= require_tree .は、app/assets/stylesheetsディレクトリ(サブディレクトリを含む)中のすべてのCSSファイルが、アプリケーションCSSに含まれるようにしている

次に*= require_selfは、CSSの読み込みシーケンスの中で、application.css自身もその対象に含めている。

プリプロセッサエンジン

必要なアセットをディレクトリに配置してまとめた後、Railsはさまざまなプリプロセッサエンジンを介してそれらを実行し、ブラウザに配信できるようにそれらをマニフェストファイルを用いて結合し、サイトテンプレート用に準備する。

Railsはどのプリプロセッサを使うのかを、ファイル名の拡張子を使って判断している。

  • Sass用の.scss

  • CoffeeScript用の.coffee

  • 埋め込みRuby(ERb)用の.erb

この3つが最も一般的な拡張子。

また、プリプロセッサエンジンは、繋げて実行する(chain)ことができる。

foobar.js.erb.coffee

上の拡張子の場合は、CoffeeScriptとERbの両方で実行される。
コードは、右から左へと実行されるため、この例ではCoffeeScriptが最初に実行される。

本番環境での効率性

アセットパイプラインの最大のメリットの1つは、本番のアプリケーションで効率的になるように最適化されたアセットも自動的に生成されること。

従来は、CSSとJavaScriptを整理するために、機能を個別のファイルに分割し、読みやすいフォーマットに整えていた。

この行為はプログラマにとっては便利な方法だが、、最小化されていないCSSファイルを多数に分割すると、ページの読み込み時間が著しく遅くなってしまう問題が起きる。

しかし、アセットパイプラインを使うと開発環境ではプログラマにとって読みやすいように整理しておき、本番環境ではAsset Pipelineを使ってファイルを最小化すればこの問題は解決できる。

具体的には、Asset Pipelineがすべてのスタイルシートを1つのCSSファイル(application.css)にまとめてくれた。

実は、それらのファイルすべてに対して 不要な空白やインデントを取り除く処理を行い、ファイルサイズを最小化してくれてもいたのだ。

結果として、開発環境と本番環境という、2つの異なった状況に対してAsset Pipelineはそれぞれ最高の環境を提供してくれる。

素晴らしい構文を備えたスタイルシート(5.2.2)

Sassは、スタイルシートを記述するための言語であり、CSSに比べて多くの点が強化されている。

SassはSCSSというフォーマットに対応している。
しかし、SCSSはCSSに新しい機能を追加しただけで、全く新しい構文を定義したようなものではない。

ネスト

.center {
  text-align: center;
}

.center h1 {
  margin-bottom: 10px;
}

通常のCSSでは、セレクタを指定しなければCSSを適用できないが、

.center {
  text-align: center;
  h1 {
    margin-bottom: 10px;
  }
}

このようにネスト構造にして書き換えることができる。

もう1つ属性を使った例も確認。

#logo {
  float: left;
  margin-right: 10px;
  font-size: 1.7em;
  color: #fff;
  text-transform: uppercase;
  letter-spacing: -1px;
  padding-top: 9px;
  font-weight: bold;
}

#logo:hover {
  color: #fff;
  text-decoration: none;
}

cssでは#logoを2回使用しなければいけないが、

#logo {
  float: left;
  margin-right: 10px;
  font-size: 1.7em;
  color: #fff;
  text-transform: uppercase;
  letter-spacing: -1px;
  padding-top: 9px;
  font-weight: bold;
  &:hover {
    color: #fff;
    text-decoration: none;
  }
}

Scssでは&アンパーサンドで表現ができる。

なお、Sassは、SCSSをCSSに変換する際に、&:hover#logo:hoverに置き換えてくれる。

変数

Sassでは、重複する箇所を変数を定義して書くことも可能。
(重複してなくても使用可能。)

$light-gray: #777;

この様に色を定義した値の変数は、

h1 {
  color: $light-gray;
}

のように書くことができる。

レイアウトのリンク(5.3)

サイトのレイアウトが仕上がったので今度はリンクを書き換えてみる。

RailsのERbテンプレートには素のHTMLを直接書くことができる。

<a href="/static_pages/about">About</a>

しかしこれはRails流ではない。

<%= link_to "About", about_path %>

こっちがRails流。

about_pathの様に名前付きルートを指定するのが一般的である。

RailsのルートURL(5.3.2)

では実際に名前付きルートをサンプルアプリケーションの静的ページで使うために、ルーティングを編集する。

今までrootメソッドを使用して、ルートURL "/" をコントローラーのアクションに紐付けてきた。

このルートURLのようなルーティングを定義することの効果は、ブラウザからアクセスしやすくすることだけではない。

生のURLではなく、名前付きルートを使ってURLを参照することができるようになるのだ。

例えばルートURLを定義すると、

root_path -> '/' #pathはルートURL以下の文字列を返す。
root_url  -> 'https://www.example.com/' #urlは完全なURLの文字列を返す。

このように、root_pathroot_urlメソッドでURLを参照することが出来る。

一般的な規約によると、基本的には_path、リダイレクトの場合のみ_url書式を使う。
(HTTPの標準として、リダイレクトのときに完全なURLが要求されるため)

名前付きルートを理解したところで、実際に他のルーティングも変更してみる。

get 'static_pages/help'

このコードを

get  '/help', to: 'static_pages#help'

こんな感じで書き換える。

これでGETリクエスト/helpに送信されたときにStaticPagesコントローラーhelpアクションを呼び出してくれるようになる。

(この時、/helpを書き替えてURLを変更することもできる。
/help/abcにすると/abcにURLを変更できる。)

またこれによって_path_urlといった名前付きルートも使用可能。

help_path -> '/help'
help_url  -> 'https://www.example.com/help'

さらに名前付きルートの名前も変更することが出来る。

  get  '/help', to: 'static_pages#help', as: 'helf'

as:オプションを使用すると変更可能。

名前変更したことによって、

helf_path -> '/help'
helf_url  -> 'https://www.example.com/help'

名前を変更して_path_urlといった名前付きルートも使用可能に。

では、次に全てのルーティングを変更!

変更したら今度はテストがエラーになる。

テストを書き換えてみる。

test/controllers/static_pages_controller_test.rb
require 'test_helper'

class StaticPagesControllerTest < ActionDispatch::IntegrationTest

  test "should get home" do
    get root_path
    assert_response :success
    assert_select "title", "Ruby on Rails Tutorial Sample App"
  end

  test "should get help" do
    get help_path
    assert_response :success
    assert_select "title", "Help | Ruby on Rails Tutorial Sample App"
  end

  test "should get about" do
    get about_path
    assert_response :success
    assert_select "title", "About | Ruby on Rails Tutorial Sample App"
  end

  test "should get contact" do
    get contact_path
    assert_response :success
    assert_select "title", "Contact | Ruby on Rails Tutorial Sample App"
  end
end

このように名前付きパスを使ってテストを書き換えよう。

リンクのテスト(5.3.4)

省略したがこの前の節では、レイアウト内のいくつかのリンクを埋めた。

これらが正しく動いているかを確認するテストを書いていく。

今回は、アプリケーションの動作を端から端まで(end-to-end)シミュレートしてテストすることができる統合テスト(Integration Test)を使う。

 rails generate integration_test site_layout #テストテンプレートを作成。

今回の目的は、アプリケーションのHTML構造を調べて、レイアウトの各リンクが正しく動くかどうかチェックすること。

①ルートURL(Homeページ)にGETリクエストを送る.

②正しいページテンプレートが描画されているかどうか確かめる.

③Home、Help、About、Contactの各ページへのリンクが正しく動くか確かめる.

Railsの統合テストでは、上のステップをコードに落とし込んでいくことになる。

まずは、assert_templateメソッドを使って、Homeページが正しいビューを描画しているかどうか確かめる。

test/integration/site_layout_test.rb
require 'test_helper'

class SiteLayoutTest < ActionDispatch::IntegrationTest

  test "layout links" do
    get root_path
    assert_template 'static_pages/home'
    assert_select "a[href=?]", root_path, count: 2
    assert_select "a[href=?]", help_path
    assert_select "a[href=?]", about_path
    assert_select "a[href=?]", contact_path
  end
end

こんな感じ。これもまた細かく分けて確認していく。

assert_select "a[href=?]", about_path

今回のケースでは、特定のリンクが存在するかどうかを、aタグhref属性をオプションで指定して調べている。

Railsは自動的に?の部分をabout_pathに置換している。

よってこのコードのは、

<a href="/about">...</a>

この様なHTMLがあるかどうかをテストしている。

次はこれ。

assert_select "a[href=?]", root_path, count: 2

homeページは、ルートURLへのリンクは2つある。(1つはロゴ、もう1つはナビゲーションバー)

このような時は、count: xでxに指定した個数、リンクがあるかをテストすることができる。

そして最後に結合テストの実行。

 rails test:integration

このコードでテストを起動することができる。

演習

今回の演習は結構大事。

Applicationヘルパーで使っているfull_titleヘルパーを、test環境でも使えるようにする問題。

test/test_helper.rb
class ActiveSupport::TestCase
  fixtures :all
  include ApplicationHelper #テスト環境にアプリケーションヘルパーを読み込み。
  .
  .
  .
end

まずはテスト環境にApplicationヘルパーを読み込む。

test/integration/site_layout_test.rb
 get contact_path
 assert_select "title", full_title("Contact")

そして先程の結合テストでfull_titleヘルパーを使ってみる。

しかし問題点として、full_titleヘルパーの定義自身にミスがあった場合このままでは発見できない。

極端な話、 Ruby on Rails Tutorial Sample AppRby on Rrails Tuutorial Sample Appeになっていてもこの結合テストはパスしてしまう。

そこでfull_titleヘルパーのテストを書く。

test/helpers/application_helper_test.rb
require 'test_helper'

class ApplicationHelperTest < ActionView::TestCase
  test "full title helper" do
    assert_equal full_title,  "Ruby on Rails Tutorial Sample App"
    assert_equal full_title("Help"), "Help | Ruby on Rails Tutorial Sample App"
  end
end

これで完璧。

ちなみにassert_equalではfull_title"..."""内の文字列と同じかを調べている。
   

assert_select

assert_selectには、色々な指定の仕方がある。

Code マッチするHTML
assert_select "div" <div>foobar</div>
assert_select "div", "foobar" <div>foobar</div>
assert_select "div.nav" <div class="nav">foobar</div>
assert_select "div#profile" <div id="profile">foobar</div>
assert_select "div[name=yo]" <div name="yo">hey</div>
assert_select "a[href=?]", '/', count: 1 <a href="/">foo</a>
assert_select "a[href=?]", '/', text: "foo" <a href="/">foo</a>

引用:Railsチュートリアル5.3.4表5.2

なるほどね〜。

用語一覧

  • レスポンシブデザイン

「レスポンシブWebデザイン」とは、PC、タブレット、スマートフォンなど、複数の異なる画面サイズをWebサイト表示の判断基準にし、ページのレイアウト・デザインを柔軟に調整することを指す。

現在はPCやスマートフォンなど、デバイス毎に各HTMLファイルを複数用意し最適化することが、一般的な制作方法。
引用: 必読!5分でわかるレスポンシブWebデザインまとめ | 株式会社LIG

  • タイポグラフィ typography

文字表現のデザイン処理のこという。情報伝達に適合したスペース、書体、大きさ、字間、字数、行間、行数等の選定だけでわなく、注目度、可読性、美しさなどへの配慮が重要である。
引用:Weblio辞書

  • シーケンス

ITの分野では、順番に並んだ一続きのデータや手順のことや、並んだ順番にデータや手順を取り扱う処理方式などのことを意味する場合が多い。派生語として、逐次的な、連続的な、といった様子を表す「シーケンシャル」(sequential)や、順序のあるデータや手順を取り扱う機器やソフトウェアを意味する「シーケンサー」(sequencer)もよく用いられる。
引用:IT用語辞典 e-Words

  • プリプロセッサ

コンパイラはプログラミング言語で記述されたソースコードを解釈してコンピュータが解釈・実行できるネイティブコードに変換するが、プリプロセッサはその下準備となるソースコードの追加や変換などの処理を担当する。プリプロセッサへの指示はプログラミング言語本体とは別に規定された特殊な記法(プリプロセッサディレクティブ)を用いて行われ、処理後のコードからは削除され残らないようになっている。
引用:IT用語辞典 e-Words

  • スタブ

テスト用に用意した、まだ完成していない機能の代わりとなる部品であり、テスト対象から呼び出される部品の代わりとなるもの。
引用:「分かりそう」で「分からない」でも「分かった」気になれるIT用語辞典

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

【個人学習振り返り】Ruby勉強 #2

■この記事の目的

自分の振り返り用としての投稿です。
Sierから卒業しWebエンジニアになるため、Rubyを1から習得中です。

■勉強に利用させていただいた動画

大変勉強になりました。投稿者様に感謝です。
本ページはこちらを参考に手でやった記録になります。

1.環境構築

自分はWindows環境で構築。

ruby・コマンドプロンプト・vscodeはあらかじめ入っていたため、
「SQLite」をインストールします。

ちなみに以下すべての記載で入力しているコマンドは、VScode経由での入力です。

1.1 SQlite

1.2 Railsのインストール

そもそも「gem」が何かが不明でしたが以下のページで説明されており、
rubyのパッケージ管理ツールのコマンドのようです。
linuxでいうところのyumのような存在でしょうか。

gem install rails -v "5.2.3"

1.3 パッケージ管理ツールのインストール

gem install bundler

Gemfileというファイルを作るためのコマンドを実行。
この時点でこのファイルの意義はまだ未理解です・・・

bundle init

このコマンドの後、Gemfileというファイルが実行フォルダの直下に作成されるので、
このファイルをテキストエディタで開き、以下を追記します。

gem "rails"

1.4.Railsのソースコードインストール

一つ上の手順でGemfileに"rails"という文字と定義したことで、
以下でrailsのソースコードがインストールできます。

bundle install --path vendor/bundle

2.Railsでアプリケーション作成

以下のコマンドでRailsのアプリケーションの基礎を作成します。

途中でオーバーライドするかと質問されたら、「A」を押して、オーバーライドします。

 rails new .

2.1Railsサーバー起動

rails s

http://localhost:3000/
Yay! You’re on Rails!
と表示されます。

ここまでのはまりポイント。すんなり、"rails s"ができた人はこの項目は飛ばしてください。

→rails s実行で返ってきた結果を抜粋。
サーバーが起動したとは思えないメッセージです。

rails\rails_tutorial> rails s
Usage:
rails new APP_PATH [options]...

同じ経験をされていた方の記事を発見し、こちらで対応してみました。
gitを入れていないことが原因のようです。
https://qiita.com/Leone/items/dc7f8ef2d5329d297e72

gitをインストールvscodeを閉じて開きなおして、再度以下のコマンドでアプリケーションの作成を行いました。

 rails new .

一回目のgitなしのアプリケーション作成と比べ、かなりファイルが増えました。

image.png

しかしまだ"rails s"コマンドは動きません。

bin/rails:3:in require_relative': cannot load such file -- C:/Users/****/Desktop/rails/rails_tutorial/config/boot (LoadError)
from bin/rails:3:in
'

これは調べてみたら、インストールしていたRubyのバージョンが原因で、boot.rbファイルが作られておりませんでした。
私の環境のRubyは以下のバージョンでした。

ruby 3.0.0p0 (2020-12-25 revision 95aff21468) [x64-mingw32]

以下のダウンロードページから、再度2.7台のRubyを入れなおしました。

https://rubyinstaller.org/downloads/
ruby 2.7.2p137 (2020-10-01 revision 5445e04352) [x64-mingw32]

2.2Railsサーバー停止

ターミナルをctrl+cで閉じるとサーバーが停止され、このページに接続できなくなります。

3.TOPページ作成

Railsはファイルの命名規則が非常に厳しく、
コントローラーのファイル名「top_controller.rb」とした場合、
クラス名は必ず「TopController」にしないとエラーとなります。

例)ファイル名:小文字アンダーバーで繋ぐ
  クラス名:ファイル名のアンダーバーを消す。
       「最初の文字」と「アンダーバーの後のアルファベット」を大文字にする。

3.1 ルーティング設定

ルーティング設定とは、URLをサーバーが受け取ったときにコントローラーどういう処理をさせるかという意味らしいです。
(コントローラーというのはMVCモデルの中のコントローラーを指します)

Railsのルーターは受け取ったURLを認識し、適切なコントローラ内アクションやRackアプリケーションに割り当てます。ルーターは、ビューでこれらのパスやURLを直接ハードコードすることを避けるためにパスやURLを生成することもできます。

これまでのディレクトリの中に、「config」フォルダがあり、その中のrouter.rbを開きます。
このファイルのdoとendの間に以下を追記します。

get '/', to: 'top#index'

これの意味としては。
1要素目:getはHTTPメソッドの「get」
2要素目:トップページということで「/」
3要素目:コントローラーとアクションを定義する。この書き方だとTOPコントローラーのindexアクションとなる。

3.2 コントローラー設定

これまでのディレクトリの中に、「app」→「controller」がありので、そこにファイルを作ります。
作るファイルは「top_controller.rb」とします。

内容は以下の通りにします。
この「ActionController:Base」を継承しているのはコントローラーのルールのようで、絶対に書かないといけないもののようです。

class TopController < ActionController::Base
    def index
    end
end

3.3 ビュー設定

これまでのディレクトリの中に、「app」→「views」がありので、そこにフォルダを作ります。
作成するフォルダ名は、コントローラーを「TopController」というクラスで定義したため、「top」となるようです。

topの下にファイルを作ります。
ファイル名は、先のコントローラー設定の中のdefで「index」の名前で定義したため、
index.html.erb
とします。
erbという拡張子を始めた見ましたが、railsでは拡張子を「html.erb」とするようです。

index.html.erbの中身自体は何でもよいので、動画を参考にこんな感じにしました。

<h1>Hello world</h1>

現状でき上ったものまとめ

config\routes.rb
Rails.application.routes.draw do
  # For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html
  get '/', to: 'top#index'
end

app\controllers\top_controller.rb

class TopController < ActionController::Base
    def index
    end
end

app\views\top\index.html.erb

<h1>Hello world</h1>


サーバーを起動してどう動くか見てみます。

rails s

http://localhost:3000/にアクセスすると、
Hello world
が表示されました。

ただ初回実行時のエラーにありました。

以下のエラーが発生しました。

raise LoadError, "Unable to autoload constant

コントローラーが読み込めないというエラーのようです。
しかしファイル名の違いなどは何度確認してもなく困惑しました。

改善した方法としては、
top_controller.rbにおいて「ActionController::Base」の箇所を
「ApplicationController」に変えたところ改善しました。
この二つの差異が今のところ不明です。

ただ試した限り、以下の動作後にいずれのパターンでも動作し、原因は不明のままです。
1.ApplicationControllerに変更
2.Hello world表示可能
3.rails停止
4.ActionController::Base:切り替え
5.rails起動
6.Hello world表示


4.DB接続

config\database.ymlが設定に使用するファイルとなるが、現時点での変更は不要です。

4.1 modelファイル作成

下記のコマンドを実行することでmodelが、app\models\user.rbとして作成する。
意味としては、 rails g model 以降が「DB名」 「列名:データ型」になる

rails g model User name:string age:integer

以下のようなファイルが作成されました。
invoke active_record
create db/migrate/20210302043737_create_users.rb
create app/models/user.rb
invoke test_unit
create test/models/user_test.rb
create test/fixtures/users.yml

4.2 DB作成

rails db:create

4.3 ユーザーテーブルの作成

4.1で作成した中のmigrateファイル内のデータを使ってテーブルが作成されます。
migrateファイルというのはDBの定義が書いてあるファイルと覚えればよいです。

rails db:migrate

4.4 データベースへの接続

今回sqlite3を用いているため、以下のコマンドで接続する。

sqlite3 db/development.sqlite3

中身を見るにはこのコマンド。

.schema users

ただ、「rails g model User name:string age:integer」というコマンドでつくったので、userが正しいと思っていたが、
db/migrate/20210302043737_create_users.rbファイル内のcreate文を見ると確かに、
「create_table "users」の記載があり、この辺りはまだ理解が及びません。。

4.5データの挿入

まずrailsのコンソールを以下のコマンドで立ち上げます。
rails環境でrubyを使用できる環境となる。

rails c

・insert文

User.create!(name:"Aさん",age:19)

User.create!(name:"Bさん",age:20)

・select文
このコマンドは内部的には「SELECT "users".* FROM "users" LIMIT ?」と同じになるようです。

User.all

・select結果の変数格納
この場合、id:1ののユーザーに対するwhere文となり、その結果が変数格納されます。
「SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT ? 」

user=User.find(1)

この状態で変数「user」にはid1のユーザーの情報が格納されます。
変数.nameとして名前を見ることや、変数.ageとして年齢を見ることができます。
またそれを指定して内容を更新することができます。

user.name="改名A"

・update文

現在、変数userの内容を更新していて、id1のユーザーの名前が"改名A"となっています。
ただこの時点では変数しか書き換えていないため、以下のコマンドでDBに反映させます。
内部的には「UPDATE "users" SET "name" = ?, "updated_at" = ? WHERE "users"."id" = ?」になっているようです。

user.save

・select+where文
条件付きのselect文には、下記のコマンドがあります。
User.where(条件)

ageを条件とする場合は、以下となります。

User.where("age>=19")

5 新しいページの追加

新しいページを追加します。
今回の目的はユーザーの情報をDBから取得し、画面に出力させます。

流れは、「ルーティングの追記」⇒「コントローラーの追加」⇒「ビュー追加」となります。

5.1ルーティングファイルの更新

例により最初にルーティングのファイルを書き換えます。
「config」フォルダのrouter.rbを開きます。

それぞれの意味として、
「/users/:id」は、users/1やusers/2として、変数で受け取れるようになります。
「to: 'users#show'」は、usersコントローラーのshowメソッドを追加しています。
「as:"user"」はルーティングに対して、名前を付ける機能です。後でユーザー一覧作成の際に使用します。

  get '/users/:id', to: 'users#show', as: "user"

5.2コントローラーファイルの追加

「app」→「controller」があるので、そこに新しいファイルを作ります。
作るファイルは「users_controller.rb」とします。
内容はTOPページと同じ要領で、
1)ファイル名の大文字&アンダーバー削除
2)メソッドはルーティングで追加した「show」を入れる。

ここから新しい要素で、 @user=user.find(params["id"])があります。
これは
user.find()は、モデルからデータを抜き出す処理
params["id"]は、ルーティングから受け取るID
最後の変数に結果を格納しています。これに「@」がついているのは、ビューにファイルを渡せるようにするため。

class UsersController < ActionController::Base
    def show
        @user=user.find(params["id"])
    end
end

5.3ビューファイルの追加

「app」→「views」がありので、そこに新しいフォルダを作ります。
作成するフォルダ名は、コントローラーを「UsersController」というクラスで定義したため、「users」となるようです。

「users」フォルダの配下に、show.html.erbを作成します。
(メソッド名+html.erb)

ファイルの内容は以下です。
この<%~~~ %>タグの中がRubyのコードを書く領域になります。

<%=と<%の違いは以下の通りです。
<%=:画面表示あり
<%:画面表示なし

if文を書く時には、画面表示しないrubyを使いたいので、そういうときは<%を使うように使い分けができる。

<h1>ユーザー詳細</h1>

<%= @user.name %> <br>
<%= @user.age %> 才

再び「rails s」でサーバーを起動させ、以下URLにアクセスします。
http://localhost:3000/users/1

画面上で以下の結果が出力されました。
ユーザー詳細
改名A
19 才

5.4ユーザー一覧を作成する。

同じ流れで、「ルーティングの追記」⇒「コントローラーの追加」⇒「ビュー追加」となります。
「config」フォルダのrouter.rbに以下を追記。

  get '/users', to: 'users#index'

「app」→「controller」の「users_controller.rb」に追加します。
User.allはすべてのユーザーを取得するコマンドでした。

    def index
        @users=User.all
    end

「users」フォルダの配下に、index.html.erbを作成します。
(メソッド名+html.erb)

@usersを受け取って、eachで回しています。
2行目のlink_toはhtmlでいうタグにあたる内容です。
user_path(id: user.id)は、
タグ内のhrefにあたり、id:user.idへのリンクを自動生成します。
(まだしっかり理解できていません...)

<h1>ユーザーすべて</h1>

<% @users.each do|user| %> 
    <%= link_to user.name,user_path(id: user.id) %> <br>
<% end %>

この状態でいかにアクセスすると
http://localhost:3000/users/

画面上以下が表示されました。

ユーザーすべて
改名A
Aさん
Bさん
Bさん

ソースコードは以下のようになっていました。

    <a href="/users/1">改名A</a> <br>
    <a href="/users/2">Aさん</a> <br>
    <a href="/users/3">Bさん</a> <br>
    <a href="/users/4">Bさん</a> <br>

5.5 追加機能で、「戻る」ボタンを作ってみます。

ユーザーの詳細画面からへのボタン追加のため、ルーティング設定を行います。
行う設定はas名前の追加です。

「config」フォルダのrouter.rbを以下に変更。

  get '/users', to: 'users#index'
  ⇒  get '/users', to: 'users#index', as: "users"

show.html.erb へ以下を追記。

link_to でタグ化し、asで定義したusersという名前を書くことで、その画面へ遷移することができます。

<%= link_to "戻る" ,users_path %>

■また次回に続きます。

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

【超かんたん】ransackを使って検索機能を実装しよう!

ransackを利用して検索機能を実装します。
今回も初心者向けにレシピ投稿アプリを例に作成していきます。

また、検索機能の実装にActive Hashの値も利用するのでActive Hashがわからない方は前回記事にしておりますので、そちらをご覧頂けたらと思います。
【超かんたん】Active Hashで投稿ページにプルダウンメニューを作成しよう!

完成イメージ

19c0a417740b22b00dc7a7fcfe4ad268.gif

それでは、実装していきましょう!

ransackとは

ransackはRails用の検索機能を実装するためのGem。公式ドキュメント

導入方法

Gemfileに下記を記述しbundle installします。

Gemfile
gem 'ransack'
ターミナル
bundle install

デモデータを投入しよう!

seed.rbにデモデータ生成する記述をしていきます。
category_idとtime_required_idはActive Hashの値と紐付いているカラムになります。

また、time_required_idのFakerがわからない方は前回記事にしておりますので、そちらをご覧頂けたらと思います。
【超かんたん】Fakerを使ってダミーデータを作成しよう!

db/seed.rb
5.times do |n|
  Recipe.create!(
    title: "すし・魚料理#{n}",
    text: "作り方",
    category_id: 1,
    time_required_id: Faker::Number.within(range: 2..6)
  )
end

#中略

5.times do |n|
  Recipe.create!(
    title: "お菓子・スイーツ#{n}",
    text: "作り方",
    category_id: 10,
    time_required_id: Faker::Number.within(range: 2..6)
  )
end
ターミナル
rails db:seed

デモデータの作成完了。
r.jpg

簡易的にindexページに表示します。
in.png

検索機能の実装

ルーティングの設定

まずは検索ページ(search)のルーティングと検索結果を表示するページ(result)のルーティングの設定をしましょう。

7つのアクション以外のルーティングを設定するので、recipesにネストさせます。
今回は、URLにidがつかないのでcollectionを利用します。

config/routes.rb
Rails.application.routes.draw do
  root to: 'home#index'
  devise_for :users
  resources :users
  resources :recipes do
    collection do
      get :search
      get :result
    end
  end
end

コントローラーの編集

以下の記述は公式ドキュメントを参考にしております。

app/controllers/recipes_controller.rb
class RecipesController < ApplicationController
  before_action :search_recipes, only: [:search, :result]

  def index
    @recipes = Recipe.all
  end

  #中略

  def search
  end

  def result
    @results = @q.result
  end

  private
  def search_recipes
    @q = Recipe.ransack(params[:q])
  end
end

params[:q]のキー「:q」でrecipesテーブルからレシピ情報を探し「@q」に格納します。
この@qに対して「.result」とすることで検索結果を取得します。

次にビューファイルを作成していきましょう。

ビューの作成

ビューファイルの作成

ターミナル
touch app/views/recipes/{search.html.erb,result.html.erb}

ビューファイルの編集

今回はCSSの説明は省きます。

検索フォームにはsearch_form_forというメソッドを使います。
form_withのransack版というイメージです。
form_withでは「text_field」ですが、search_form_forだと「search_field」になります。

また、「:カラム名_マッチャ」とすることで条件にあった検索を行います。
_cont」だと「入力された値が含まれている」という意味になり、「_eq」は「入力された値と等しい」という意味のマッチャになります。
その他のマッチャについては公式ドキュメントを参照してください。

collection_selectメソッドについては前回の記事Railsドキュメントを参照してください。

以下のように編集していきます。

app/views/recipes/search.html.erb
<div class="recipe-form">
  <h1 class="text-center">検索する</h1>
  <%= search_form_for @q, url: result_recipes_path do |f| %>
    <div class="form-group">
      <label class="text-secondary">料理名</label><br />
      <%= f.search_field :title_cont, class: "form-control"%>
    </div>
    <div class="form-group">
       <label class="text-secondary">カテゴリー</label><br />
       <%= f.collection_select(:category_id_eq, Category.where.not(id: 0), :id, :name, include_blank: '指定なし') %>
    </div>
    <div class="form-group">
       <label class="text-secondary">所要時間</label><br />
       <%= f.collection_select(:time_required_id_eq, TimeRequired.where.not(id: 0), :id, :name, include_blank: '指定なし') %>
    </div>
    <div class="form-group">
       <label class="text-secondary">フリーワード</label><br />
       <%= f.search_field :text_cont, class: "form-control"%>
    </div>
    <div class="actions">
      <%= f.submit '検索', class: "btn btn-primary" %>
    </div>
  <% end %>
</div>

それでは表示してみましょう。
せ.png

次に検索結果ページを作成していきましょう。
以下のように編集していきます。

app/views/recipes/result.html.erb
<div class="recipes-index text-center">
  <h1 class="result-index">検索結果</h1>
  <% if @results.length != 0 %>
    <% @results.each do |recipe| %>
      <div class="recipe">
        <div class="recipe-title">
          <%= recipe.title %>
        </div>
        <div class="recipe-content">
          カテゴリー: <span class="recipe-category"><%= recipe.category.name %></span>
          所要時間: <span class="recipe-time"><%= recipe.time_required.name %></span>
        </div>
      </div>
    <% end %>
  <% else %>
    該当するレシピはありません
  <% end %>
</div>

if文で該当する検索結果がない場合は「該当するレシピはありません」と表示させるようにしています。
それでは実際に検索してみましょう。

該当する検索結果がある場合

19c0a417740b22b00dc7a7fcfe4ad268.gif

該当する検索結果がない場合

c8e6ad3cfc012c474e3c28ddb7be9cfe.gif

以上で完成です。

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

【簡単】railsでbootstrapを用いたエラーメッセージを表示する

記事投稿の背景

オリジナルサービスの投稿フォームを作成していたところ、投稿内容が空白だった場合にエラー表示をさせる方法が分からなかったが、調べてみると簡単に実装できたので共有させていただきます。

環境

ruby 2.7.2
rails 6.1.2
bootstrap導入済み

実装

views/posts/new.html.erb
<%= form_for @post do |f| %>
  <div class="field">
    <h3>新規投稿</h3>    
     <p>投稿内容:</p>
     <%= f.text_area :body %>
  </div>
  <%= f.submit "投稿する" %>
<% end %>

上のような投稿フォーム上で、投稿内容が空だった場合エラーメッセージを表示させます。

models/post.rb
belongs_to :user
#ここに追記
validates :body, presence: true

validates :body, presence: trueとすることにより、空の投稿内容が認められないように設定します。

controllers/posts_controllers.rb
def create
    @post = Post.new(post_params)
    @post.user_id = current_user.id
    if @post.save
      redirect_to :action => "index"
    #ここに追記。エラー時に行われる処理
    else
      render action: :new
    end
  #終了
  end

投稿内容が保存されなかった場合(投稿内容が空だった場合)、postsのnewでアクションが行われるように設定します。

views/posts/new.html.erb
<%= form_for @post do |f| %>
  # ここに追記
  <% if @post.errors.any? %>
    <div class="alert alert-danger" role="alert">投稿内容を入力してください</div>
  <% end %>
  # 終了
  <div class="field">
    <h3>新規投稿</h3>    
     <p>投稿内容:</p>
     <%= f.text_area :body %>
  </div>
  <%= f.submit "投稿する" %>
<% end %>

フォーム内に、@post.errors.any?設置することにより、エラーの時のみ中身が発火するようにします。

<div class="alert alert-danger" role="alert">はbootstrapで用意されたアラートのデザインです。他の色もあるので、何か処理が成功したときのアラート等にも使えます。

以上で、エラー表示を簡単に実装することが出来ました!!!

参考記事

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

RailsチュートリアルでPostgreSQLを導入する

はじめに

RailsチュートリアルでPostgreSQLを導入する方法について説明されている記事が見当たらなかったので書いてみました。エンジニアの方など、間違いを見つけられましたら、私を含むRailsチュートリアルからステップアップしたいエンジニア志望者にとって大変ありがたいので指摘していただけると嬉しいです。

参考

Railsにpostgresqlを導入する
初心者のMac + PostgreSQL インストール
PostgreSQLの環境構築 (macOS/Postgres 11)
PostgreSQLの基本的なコマンド

PostgreSQLをインストールする

Homebrewがインストールされていることを前提とする。

ターミナル
$ brew install postgresql

最新のPostgreSQLがインストールできていることを確認してみよう。

ターミナル
$ postgres --version
>> postgres (PostgreSQL) 13.2

問題なさそう。

データベースの初期化

ターミナル
$ initdb /usr/local/var/postgres -E utf8
The files belonging to this database system will be owned by user "username".
This user must also own the server process.

The database cluster will be initialized with locale "ja_JP.UTF-8".
initdb: could not find suitable text search configuration for locale "ja_JP.UTF-8"
The default text search configuration will be set to "simple".

Data page checksums are disabled.

initdb: directory "/usr/local/var/postgres" exists but is not empty
If you want to create a new database system, either remove or empty
the directory "/usr/local/var/postgres" or run initdb
with an argument other than "/usr/local/var/postgres".

DBが無事に使えるかの確認

※筆者は不安なので確認していますが、今回の目的とは直接関係ありません

postgresサーバが無事に起動するかを確認してみる。いろいろ出てきた後に「server started」が表示されればOK。停止するには「pg_ctl stop」をターミナルに入力する。ただし、以降の操作は起動したまま行うこと。
(startを付けることでバックグラウンド起動してくれる。付けなくても起動してくれるけど起動中はそのターミナルは使えないから新しいウィンドウで操作する。)

ターミナル
#バックグラウンド起動
$ postgres -D /usr/local/var/postgres start

PostgreSQLが起動している状態で、psqlコマンドが使えるかを確認する。-lはデータベースを一覧表示してくれるオプションなので、データベースが3つぐらい出てきたら問題ない。

ターミナル
$ psql -l

環境変数にPATHを通す

アプリケーションがDBを探せるようにpostgresの場所を指定する。シェルがbashの人は、zshrcをbash_profileに変更して実行。

ターミナル
$ echo 'export PGDATA=/usr/local/var/postgres' >> ~/.zshrc
$ source ~/.zshrc

Railsで作るアプリケーションにPostgreSQLを導入する

ターミナル
$ cd (アプリの場所)
$ rails new sample_app(アプリ名) -d postgresql # データベースにPostgreSQLを指定
$ cd sample_app(アプリ名)
$ rails db:create #データベース構築
$ rails s

http://localhost:3000/ にアクセスしておなじみの画面が出ればOK

postgresサーバを停止してセッティングは終了

ターミナル
$ pg_ctl stop

RailsチュートリアルをPostgreSQLで進めるにあたっての注意点

6.1. ローカル環境

ローカルで確認しながら開発するときは、「Rails server」と同様にpostgresサーバも起動しておく。

6.2. Gemfile(3章)

3章のGemfileをインストールする際には、開発環境とテスト環境に「sqlite3」ではなく「pg」を記述する。

Gemfile
# 変更前
group :development, :test do
  gem 'sqlite3', '1.4.1'
  gem 'byebug',  '11.0.1', platforms: [:mri, :mingw, :x64_mingw]
end

# 変更後
group :development, :test do
  gem 'pg' #バージョンは本番環境と同じにすることを推奨
  gem 'byebug',  '11.0.1', platforms: [:mri, :mingw, :x64_mingw]
end

6.3. Userモデル(6章)

6章でUserモデルを作った後は、データベースを確認したくなると思うのでその方法を残しておく。

PostgreSQLを起動した状態で、アプリケーションがある場所まで移動する。

ターミナル
$ postgres -D /usr/local/var/postgres start
$ cd sample_app(アプリケーション名)

「rails console」を使ってデータの追加、削除はSQLiteと同じように可能。

ターミナル
$ rails console
irb> user = User.new(name: "EXAMPLE")
irb> user.save

また、「rails dbconsole」からデータベースの一覧を表示したり、実際のテーブルを確認することができる。

ターミナル
$ rails dbconsole
sample_app_development=> \l #データベース一覧を表示
sample_app_development=> \dt; #テーブル一覧を表示
sample_app_development=> \d users; #Userテーブルの構造を表示
sample_app_development=> select * from users; #Userテーブルのすべてのデータを表示

お疲れ様でした。

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

【まとめ】Railsアプリ AWS化に役立った記事たち

目的

第1回 RailsアプリAWS化計画を成功に導いてくれた有難き記事たちをまとめています。(自分のために)

大まかな流れに活躍してくれたで賞

初心者向け:AWS(EC2)にRailsのWebアプリをデプロイする方法 目次 (以下手順)

この流れにそって実行すれば、基本的にアプリをデプロイすることが可能です。
ただし、最新更新が2017年なのでバージョンによる違い、AWSのUIの違いなどが多数見受けられました。しかし、手順は簡潔にまとまっており、非常に参考になりました。

【画像付きで丁寧に解説】AWS(EC2)にRailsアプリをイチから上げる方法【その3〜サーバー設定とRailsアプリの配置編〜】
ゼロから行うのであれば、こちらの方が良いかも知れません。私は前者の方で始めたので、途中で参考記事を変えるのもややこしくなると考えたので移行しませんでした。

インストールの諸問題を解決をしてくれたで賞

【AWS EC2】Amazon Linux2にnginxをインストールする方法

手順に沿って進めていて、最初にぶつかる問題は、おそらくAmazon Linux AMIが無く、Amazon Linux2 AMIしかないことでしょう。この手順で2を選択し、Nignxをインストールしようとすると、エラーが発生します。Amazon Linux2にはnginxのyumが無いことが原因で、その解決方法を案内してくれます。

Rails6 開発時につまづきそうな webpacker, yarn 関係のエラーと解決方法

Rails6でwebpackerが標準になったことにより、Railsアプリの開発環境にyarnのインストールが必要になりました。

AmazonLinuxにyarnをインストールする
タイトルそのまんまです。

権限によって許可されていない操作(パーミッションエラー)を解決してくれたで賞

AmazonLinuxで新しいユーザを作成してec2-userを削除する

EC2にssh接続する際、デフォルトではec2-userになっています。セキュリティを高めるため、別ユーザーを作成してそちらを使用します。しかし、私の場合だと手順ではパーミッションエラーになってしまいます。そこでこちらのサイトが非常に参考になりました。なお、こちらでは最後にec2-userを削除しますが、手順には影響がないので大丈夫です。

Linuxの権限確認と変更(chmod)(超初心者向け)
そもそもパーミッションてなんやねん!って方は、この記事が参考になります。

EC2からRDSにMySQLでログインできない問題を解決してくれたで賞

EC2からMySQLでRDSに接続するが、「Access denied for user」を突き返される
私が書いた記事ですが、悪いのは100自分です。Access denied for userと返されたら、ユーザーネーム、パスワード、セキュリティグループを疑ってみましょう。

AWS RDSで設定を変更したらAccess denied (using password: YES)でmysql ログインできなくなった
RDSの設定を変更した時に見落としがちな部分。

メモリ不足を解決してくれたで賞

[Rails] CapistranoでEC2へデプロイ:EC2仮想メモリ不足トラブルシュート
EC2の無料枠で使えるt2.microのメモリサイズは1GiBとかなり少なめです。そのため、ある程度負荷のある処理をすると、メモリ不足で処理が完了しないエラーが発生します。
virtual memory exhausted: Cannot allocate memoryというエラーです。
手順でいうと、私の場合は$ bundle install --path vendor/bundleで発生しました。
調べてみるとbundle install系でこのエラーが発生するのは割と頻繁ぽい(?)。
この記事ではCapistranoを使っているときのようですが、手順での場合でも利用できます。

LinuxでRAMメモリのキャッシュやスワップをクリア・解放する方法
同時に、メモリの解放などもさらっておくと良いと思います。

DBの設定を環境変数化する手助けをしてくれたで賞

【画像付きで丁寧に解説】AWS(EC2)にRailsアプリをイチから上げる方法【その3〜サーバー設定とRailsアプリの配置編〜】
先程紹介したページの一部です。手順では、データベースのユーザー名やパスワードをdatabase.ymlに直接記述します。しかし、DBへアクセスする情報をdatabase.ymlへ直接記入するとセキュリティ的に問題があります。そこで、これらの秘密情報は環境変数へ移行します。

ロードバランサーとHTTPS化を手助けしてくれたで賞

【初心者向け】AWSのサービスを使ってWebサーバーをHTTPS化する
手順でのロードバランサーは以前のバージョンのものです。HTTPS化するのであれば、手順よりこちらにまるまる沿って行ってしまった方がやりやすいです。

デプロイ後のエラーの解決を手助けをしてくれた賞

[初学者] AWS デプロイ時のエラー解決に役立ちそうなコマンドや知識
バックグラウンドが黒でちょっと見にくいのですが、エラーに困ったらその糸口を見つけ出すことができると思います。

EC2のRuby/Rails環境構築中のwe're sorry, but something went wrongでハマった話
AWSにデプロイして、We're sorryが出たら、とりあえずログを確認してみろよってこと。
ログからエラーの糸口を見つけ出すことができる力があれば、これだけでOK。

デプロイしたアプリを更新する手助けをしてくれたで賞

【AWS】 EC2にデプロイしたRailsアプリを更新する方法
一度アプリをデプロイした後に更新することも往々にしてあると思います。そういう時はこちら。

本番環境でgit pullした時に起こったエラー【error: Your local changes to the following files would be overwritten by merge: composer.lock Please, commit your changes or stash them before you can merge. 】
上記の更新で、$ git pull origin masterを行うとたまにコンフリクトのエラーが発生します。原因は様々だと思いますが、もし発生した場合はこの方法を試してみてください。

Unicornに関する問題を解決してくれたで賞

unicorn使おうと思ったらいろいろ詰まった
基本更新などでgit pullをしたときなどに書き換えられることはないと思うのですが、私の場合コンフリクトが発生してstashで解決したあとに、unicornが起動できなくなりました。原因はGemfileのunicornが消えてなくなっていただけだったのですが。

AWSでだけPayjpが動作してくれない問題を解決してくれたで賞

環境変数の代わりに .env ファイルを使用する (dotenv)
私はアプリにPayjpを使った決済機能を導入していたのですが、ローカルやHerokuでは動作していたのに、AWSでだけ動作しない問題にぶち当たりました。私の場合は、この方法を導入するだけで解決しました。

Rails 本番環境(EC2)でPAY.JPのAPI keyを読み込めないエラー
こちらの様にすると解決するパターンもあると思います。

上記2つの違いとしては、Javascriptファイルから.envファイルを参照するのか、Rubyファイル(コントローラなど)から参照するのかだと思います。私の場合は、コントローラからは後者の記事のようにRails.application.credentials.payjp[:PAYJP_PRIVATE_KEY]とせずとも、ENV['鍵名']で読み取れたので、かならずしもそう記述しなければならないというよりは、.envファイルの設定の問題なのではないかと思います。代わりに、JSファイルから.envファイルを読み取れず、前者の記事通りに実装したところ上手くいったという次第です。

EC2ボリュームに関する問題を解決してくれたで賞

【AWS】EC2サーバーにボリュームをアタッチしたら起動しない!
EC2のボリュームは、アタッチ・デタッチができます。インスタンスが正常に起動できない場合などに、ボリュームをデタッチ、別のインスタンスにアタッチすることで中身を確認できたりします。アタッチする際に、ルートデバイスの指定があるのですが、これが何故か表示されているように記入しても上手くいかないケースがあります。

その他

セキュリティグループにマイIPを設定してssh接続できない
設定したIPアドレスと今現在自分が使っているIPアドレスに差異がないか確認します。
あなたが現在インターネットに接続しているグローバルIPアドレス確認

RailsアプリにFontAwesomeを使用している場合
この場合は、FontAwesomeをローカルでインストールしたときと同じ様にインストールします。Rails6の場合なので、5以前の場合は検証していません。
【Rails】Rails6でFontAwesomeを導入・表示させるための手順を初心者向けに解説

ClockWorkを使ったバッチ処理
EC2上でClockWorkの処理を実装する場合です。
clockworkを本番環境で動かす場合

いつでも強い味方

AWS ドキュメント

さいごに

インターネット上でこうやって探すだけで実装していけるなんて、ありがたいなあと改めて実感しました。もちろん、一筋縄ではありませんし、人によって解決できる方法も違うので、自分に当てはまる解決策を見つけるために費やした時間を振り返ると、まだまだ情報収集能力が足りないなあとひしひしと感じています。

公式ドキュメントはさらっとリンクを載せただけですが、何か実装にひっかかったらやはり公式ドキュメントがこころ強いですよね。

このまとめ記事もだれか1人にでもお役に立てれば嬉しいです。

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

[Rails]slickを用いて投稿された画像をランダムに取得してスライドショーを実装する

スライドショーの実装

題名通りjQueryのslickを用いて投稿された画像をランダムにスライドショーで実装する方法の紹介です。
初投稿なので誤字や脱字など至らない点はあると思いますが、よろしくお願いいたします。

完成形

前提

Rails 6.0.3.5
slickはjQueryのプラグインなのでjQueryの導入をお願いいたします。

slickの導入

slick公式サイト
CDNを利用して導入します。
application.html.erb内の<head>

apprication.erb
<link rel="stylesheet" type="text/css" href="//cdn.jsdelivr.net/npm/slick-carousel@1.8.1/slick/slick.css"/>
<link rel="stylesheet" type="text/css" href="//cdn.jsdelivr.net/npm/slick-carousel@1.8.1/slick/slick-theme.css"/>
<script type="text/javascript" src="//cdn.jsdelivr.net/npm/slick-carousel@1.8.1/slick/slick.min.js"></script>

を付け加えましょう。
導入に関してはこれだけです。

slick.jsの作成

javascript下にslick.jsファイルを作りましょう。
コードの記載は下の通りです。

slick.js
$(function() {
  $('.slider').slick();
});

slickの読み込み

slickを読み込むためにapplication.jsに

application.js
require('slick')

の記載を追加しましょう。

投稿を取得してスライドショーにしよう

スライドショーを実地するための記載をしましょう。
今回の場合は@postsに画像つきの投稿の全てを取得させています。

html.erb
<div class="slider">
  <% @posts.each do |post| %>
     <%= link_to image_tag(post.image), post_path(post.id), method: :get, class: :slick_image %>
   <% end %>
</div>

これで投稿を取得して画像をスライドショーで表示できるようになったと思います。

取得する投稿をランダムにしよう

↑の場合だと投稿全てを取得して表示してしまうので、
コントローラー内で新たに@randamsを定義してランダムな投稿を取得しましょう。

controller.rb
randams = Post.order("RAND()").limit(5)  //limit(5)で5件を取得させている。

@randamsにランダムな投稿5件を取得させています。

ビューファイルも書き換えましょう。

.html.erb
<div class="slider">
  <% @randams.each do |randam| %>
    <%= link_to image_tag(randam.image), post_path(randam.id), method: :get %>
  <% end %>
</div>

@randamsにランダムな投稿5件を取得させてeachで表示させています。

スライドを自動にしてみよう

現状のスライドだと寂しいので色々オプションを付け加えてみましょう。

slick.js
$(function() {
  $('.slider').slick({
      centerMode: true, //スライド画面に次のスライドが表示される
      centerPadding: '10%', //次のスライドの幅
      dots: true, //スライドの下にドットのナビゲーションを表示
      autoplay: true, //自動再生オン
      autoplaySpeed: 2000, //再生スピード
      infinite: true //スライドが終了したら最初に戻る
  });
});

他にもさまざまなオプションがあるので気になった方は公式リファレンスをチェック!

最後に

初投稿と言うこともあり、色々不十分な点もあると思いますが、拙い文章にお付き合いいただきありがとうございました。

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

【Rails開発】基本の基本コマンドチートシート

コラム: エラーメッセージを日本語にする

STEP①Githubのrails-i18nの日本語辞書リポジトリをDL
$ wget https://raw.githubusercontent.com/svenfuchs/rails-i18n/master/rails/locale/ja.yml -P config/locales

wget は引数に記載したURLからファイルをカレントディレクトリにDLするコマンド。(Command not foundと出た場合の対処法)

-P オプションでDLするディレクトリを指定します。
これで、翻訳ファイルconfig/locales/ja.yml が作成される。

STEP②config/initializers/locale.rbファイルを作成
vim config/initializers/locale.rb
STEP③以下を記述して保存(:wq コマンドで保存できる)
config/initializers/locale.rb
Rails.application.config.i18n.default_locale = :ja

モデルの雛形作り

$rails g model モデル名 属性名:データ型 name:string description:text

モデルが動作するには、データベースとモデルクラスの両方が必須。
データベースにテーブルを追加する為に、マイグレーションを実行する。
※Railsで用意されているマイグレーションという仕組みは、スキーマの歴史を進めるだけでなく、戻す機能も備えている。

$rails db:migrate

コントローラの雛形作り

$rails g controller tasks index show new edit

config.routes.rbから余計なものを削除し、resourcesにまとめる

config.routes.rb
 root to: 'tasks#index'
 resources :tasks

一覧画面(index)から新規登録ボタンを作ってリンクを飛ばす

app/views/〜/index.html.slim
= link_to '新規登録', new_task_path, class: 'btn btn-primary'

翻訳処理

config/locales/ja.yml
error:


model:
  task: タスク
attributes:
 id: ID
 name: 名称
 description: 詳しい説明
 created_at: 登録日時
 updated_at: 更新日時
ここら辺を任意で記載。

新規登録画面の為のアクションを実装

app/controllers/tasks_controller.rb
def new
 @task = Task.new 
end

※アクションからビューに受け渡しをしたいデータをインスタンス変数に入れるのが、アクションの基本役割の一つ。

新規登録画面のビューを作成

app/views/tasks/new.html.slim
form_withを使ってぽちぽち

登録アクションの実装

app/controllers/tasks_controller.rb
 def create
  task = Task.new(task_params)
  task.save!
  redirect_to tasks_url, notice: "タスク 「#{task.name}」を登録しました」"
 end

 private

 def task_params
   params.require(:task).permit(:name, :description)
 end
end

※なぜrenderでなく、redirect_toなのかは➡︎RenderとRedirectの違い

このままだとnoticeが表示されないので、application.htmlを直す

application.html.slim
 .container
 - if flash.notice.present?
  .alert.alert-success = flash.notice

一覧表示機能の実装

コントローラ

tasks_controller.rb
 def index
   @tasks = Task.all
 end

ビューの処理

index.html.slim
 =link_to '新規登録'

.mb-3 
table.table.table-hover
 thread.thread-default
   tr
     th= Task.human_attribute_name(:name)
     th= Task.human_attribute_name(:created_at)
 tbody
   - @tasks.each do |task|
     tr
       td= task.name
       td= task.created_at

mb-3はrem1の意味

詳細表示機能の実装

さっき書いた一覧にリンクをつける

index.html.slim
td = link_to task.name, task_path(task)

showアクションを実装

tasks_contoroller.rb
def show
 @task = Task.find(params[:id])
end

showのビュー実装

views/tasks/show.html.slim

編集機能の実装

・一覧と詳細画面に編集リンク追加
・newをコピーして、edit.htmlを作成
・editアクション実装

パーシャルでDRYに

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

テストコードについて(Ruby on Rails)

今回は、Ruby on Railsのテストコードについての記事を書きたいと思います。

バージョン

・Ruby 2.6.5
・Rails 6.0.0

テストコードとは?

・テストコードとは?
テストコードとは、アプリケーションの動作確認をする際にかくコードです。
アプリケーションを作成したら、新規登録やログインができるか手動で確認する
と思います。
これを、コードを書いて自動で実行できるのがテストコードです。

■なぜ、テストコードは必要?
①クオリティの担保ができる
もし、手動で挙動を確認するとデメリットが多い。
・人為的ミスが起きる
・仕様が変わった時に、もう一度やらなくてはいけない
・記録が残らない
こういったデメリットを回避できてクオリティを担保できる。

②仕様を見極めることができる
テストコードをかける人は、そのアプリケーションの仕様を理解している。

テストコードで大事なのは「何を確認したいのか意識する」

テストコードのパターンと種類

■パターン
・正常系
「ユーザーが開発者の意図する挙動を行った時の挙動」を確認するテストコード。
つまりは、「ログインできる時」など上手くいった時の確認。

・異常系
「ユーザーが開発者の意図しない操作を行った時の挙動」を確認するテストコード。
例えば、新規登録の際に「フォーム空欄だと登録できない」などを確認する。

※その他にも準正常系など様々な分類があります。

■種類
・単体テストコード
モデルやコントローラーなど機能ごとに問題がないかを確かめる。
モデルの場合バリデーションの確認などを行う。
例えば、「パスワードを英数混合」のバリデーションを設定していたらその挙動を
確認する。

Gemの導入

railsでテストコードを行うには、RspecのGemの導入が必要。

Gemfile

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]
  gem 'rspec-rails', '~> 4.0.0'
end

Gemfileのgroup :development, :test doの中に記述を行う。

% bundle install
 % rails g rspec:install

Gemのインストールを行った後に、rails gでrspecをインストールを行う。

create  .rspec
create  spec
create  spec/spec_helper.rb
create  spec/rails_helper.rb

すると上記のファイルが作成される。

.rspec

--format documentation

作成され.rspecファイルに上記を記述する。
これは、テストコードの結果をターミナル上に可視化するための記述。

これで、rspecの導入は完了したので、あとはファイルを作成してコードを書くだけです。

ちなみにテストファイルの生成の仕方は、

% rails g rspec:model user

ターミナルより上記のコマンドを実行するとファイルが生成でsきます。
これは、userモデルの単体テストコードのファイルを作成したものです。

ちなみにこれは、単体テストコードの場合で、次に結合テストの場合について。

結合テストの場合についての準備

結合テストの場合は、System Specという技術を使用します。
これは、CapybaraというGemを用い流のですが、このGemはデフォルトで
導入されています。

Gemfile

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

Gemfileを見ると導入されていることが確認できます。

% rails g rspec:system users

ターミナルよりファイルを作成。
これは、userの結合テストコードのファイルです。
つまり、「新規登録」や「ログイン」機能のテストを行うものです。

ここまでが、テストコードの説明と導入手順の流れです。

効率よくテストコードを書くために

テストコードを効率よく書くために、導入するGemが
FactoryBotとFakerです。

・FactoryBot
インスタンスをまとめることができるGemです。他のファイルであらかじめ各クラスのインスタンスに定める値を設定しておき、各テストコードで使用します。

RSpec.describe User, type: :model do
  describe 'ユーザー新規登録' do
    it 'nicknameが空では登録できない' do
      user = User.new(nickname: '', email: 'test@example', password: '000000', password_confirmation: '000000')
      user.valid?
      expect(user.errors.full_messages).to include("Nickname can't be blank")
    end
  end
end

これが、FactoryBotを使わずに書いたテストコードです。
テストを行うたびにインスタンスを生成しなくてはいけません。
別のファイルにあらかじめインスタンスを作成し、再度利用するのがFactoryBotの役目です。

Gemfile

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]
  gem 'rspec-rails', '~> 4.0.0'
  gem 'factory_bot_rails'
  gem 'faker'
end

Gemfileのgroup :development, :test doの中に記述します(Fakerも記述しています。)
記述を行ったら、

% bundle install

これで、導入完了です。
「factories」というフォルダができるので、その中にファイルを作成します。
factoriesというフォルダができていない場合は手動で作成してください。

spec/factories/users.rb

 FactoryBot.define do
  factory :user do
      nickname               {"test"}
      email                  {"test@test"}
      password               {'test1234'}
      password_confirmation  {password}
      profile                {"よろしくお願いします"}

このようにそれぞれのインスタンスをあらかじめ作成しておきます。

RSpec.describe User, type: :model do
  before do
    @user = FactoryBot.build(:user)
  end

  describe "新規登録" do
    context "登録できる時" do
      it "プロフィール写真以外の情報が入力されていれば登録できる" do
        expect(@user).to be_valid
      end

      it "プロフィール写真がなくても登録できる" do
        @user.image = nil
        expect(@user).to be_valid
      end
    end
    context "登録できない時" do
      it "emailがないと登録できない" do
        @user.email = nil
        @user.valid?
        expect(@user.errors.full_messages).to include("Email can't be blank")
      end

このように、beforeであらかじめセットアップしておくと、各項目で使えるようになります。
例えば、"登録できるとき"の"プロフィール写真以外の情報が入力されていれば登録できる"
はあらかじめ、@userの中にFactoryBotの情報が入っているため、これだけの記述で済みます。

・Faker
ランダムな値を生成するGemです。メールアドレス、人名、パスワードなど、さまざまな意図に応じたランダムな値を生成してくれます。

Fakerを使用すると、名前やemailなどをランダムに生成してくれます。
同じ情報が登録できないようなバリデーションを組んでいる時なんかに使います。
導入の仕方は、先ほどと同じで導入が済んだら、

spec/factories/users.rb

FactoryBot.define do
  factory :user do
      nickname               {Faker::Name}
      email                  {Faker::Internet.free_email}
      password               {'test1234'}
      password_confirmation  {password}
      profile                {"よろしくお願いします"}

  end
end

FactoryBotのファイルにFakerを使用したい部分に記述します。
今回だと、emailとnicknameを自動生成しました。

他にもいろんなものが生成できるので、興味ある方はこちらを参考してください。
Fakerの公式GitHub

ここまでが、テストコードに関しての説明です。
オリジナルアプリに、テストコードを書くことでより理解が深まりました。

初心者のため、記事に不備があるかもしれません。
その際は、コメントしていただければ幸いです。

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

【Ruby on Rails】デバッグツール(pry-rails)

エラーの時にはデバッグツールを使ってバグを発見したり、処理を止めて動作を確認したりします。
私はpry-railsを利用しているのでその使い方をまとめます。

pry-rails

Railsにおけるデバッグ用のGemです。
私の主な使い方はコントローラー内で記述し、その処理を止めることでどんなデータが渡っているかなどを調べるのに使っています。
テストコードを書く際などにもエラー文を調べるために利用したりします。

準備

Gemfileの一番下に

Gemfile
gem 'pry-rails'

と記述します。

ターミナルで該当のプロジェクトのディレクトリで以下のコマンドを実行します。

ターミナル
bundle install

以上でGemの導入とGemfileの更新ができました。

使い方

①送られているデータの確認方法

例えばcreateアクションでどのデータが送られているか確認したい場合、コントローラー内の処理を確認したいところにbinding.pryを記述します。

コントローラー
def create
    binding.pry
    Sample.create(sample_params)
end

記述できたらローカルのサーバーから実際にcreateアクションの動作を行ってみます。
(例えばformからの投稿など)

この状態でターミナルを見てみるとコンソールが起動していると思います。
下記のような入力街の状態です。

ターミナル
pry(#<SampleController>)>

ここにparamsと入力すると送られているデータが配列で確認できます。
うまく保存ができない時に試すと原因が分かることがあります。

②テストコードでの使い方

※テストコードの書き方の詳細は省きます。
以下のように異常系のテストコードを書く際にbinding.pryで処理を止め、エラーメッセージがどういうものか確認します。

context '新規登録がうまくいかない時' do
  it "名前(name)が空だと登録できない" do
    @user.name = ''
    @user.valid?
    binding.pry
  end
end

上記の記述をしてテストコードを実行するとターミナルでコンソールが起動するので

ターミナル
user.errors.full_messages

と入力するとエラーメッセージを出力してくれます。

以上です。

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

【Railsチュートリアル】第11章 アカウントの有効化①

はじめに

アカウントを有効化するステップを新規登録の途中に差し込み、本当にそのメールアドレスの持ち主なのかどうかを確認できるようにする。

11.1 AccountActivationsリソース

セッション機能(8.1)を使って、アカウントの有効化という作業を「リソース」としてモデル化する。

11.1.1 AccountActivationsコントローラ

resources :account_activations, only: [:edit]

URL「/account_activation/トークン/edit」にGETがリクエストされたらeditアクションを呼び出す。

演習 1

現時点でテストスイートを実行すると green になることを確認してみましょう。
確認のみなので省略。

演習 2

表 11.2の名前付きルートでは、_pathではなく_urlを使うように記してあります。なぜでしょうか? 考えてみましょう。ヒント: 私達はこれからメールで名前付きルートを使います。
メール本文のURLからアクセスするから。

11.1.2 AccountActivationのデータモデル

仮想的な属性を使ってハッシュ化した文字列をデータベースに保存するようにする。

演習 1

本項での変更を加えた後、テストスイートが green のままになっていることを確認してみましょう。

GREEN

演習 2

コンソールからUserクラスのインスタンスを生成し、そのオブジェクトからcreate_activation_digestメソッドを呼び出そうとすると(Privateメソッドなので)NoMethodErrorが発生することを確認してみましょう。また、そのUserオブジェクトからダイジェストの値も確認してみましょう。

>> user = User.new
   (4.5ms)  SELECT sqlite_version(*)
=> #<User id: nil, name: nil, email: nil, created_at: nil, updated_at: nil, password_digest: nil, remember_digest: nil, admin: nil, activation_digest: nil, activated: false, activated_at: nil>
>> user.create_activation_digest
Traceback (most recent call last):
        1: from (irb):2
NoMethodError (private method `create_activation_digest' called for #<User:0x00007f5eb1cd6088>)
Did you mean?  restore_activation_digest!

>> user.activation_digest
=> nil

演習 3

リスト 6.35で、メールアドレスの小文字化にはemail.downcase!という(代入せずに済む)メソッドがあることを知りました。このメソッドを使って、リスト 11.3のdowncase_emailメソッドを改良してみてください。また、うまく変更できれば、テストスイートは成功したままになっていることも確認してみてください。

app/models/user.rb
def downcase_email
  self.email.downcase!
end

GREEN

11.2 アカウント有効化のメール送信

Action Mailerライブラリを使ってUserのメイラーを追加する。

11.2.1 送信メールのテンプレート

Userメイラーの生成
$ rails generate mailer UserMailer account_activation password_reset

$ rails generate [メイラー名][アクション名][アクション名]

app/views/user_mailer/account_activation.text.erb
app/views/user_mailer/account_activation.html.erb
ブラウザと違って、メールボックスによってHTMLを描画できないものもあるので、textも用意している。

app/mailers/application_mailer.rb
class ApplicationMailer < ActionMailer::Base
  default from: 'from@example.com'
    # どこから送るか
  layout 'mailer'
    # デフォルトではどんなレイアウトを使うのか。
end

application_mailer.rbではメイラー全体の設定をする。

app/mailers/user_mailer.rb
class UserMailer < ApplicationMailer

  # Subject can be set in your I18n file at config/locales/en.yml
  # with the following lookup:
  #
  #   en.user_mailer.account_activation.subject
  #
  def account_activation
    @greeting = "Hi"
      # インスタンス変数を展開

    mail to: "to@example.org"
  end

  # Subject can be set in your I18n file at config/locales/en.yml
  # with the following lookup:
  #
  #   en.user_mailer.password_reset.subject
  #
  def password_reset
    @greeting = "Hi"
      # インスタンス変数を展開

    mail to: "to@example.org"
  end
end

user_mailer.rbではメイラーでは何をするのかを設定する。

演習 1

コンソールを開き、CGIモジュールのescapeメソッド(リスト 11.15)でメールアドレスの文字列をエスケープできることを確認してみましょう。このメソッドで"Don't panic!"をエスケープすると、どんな結果になりますか?

>> CGI.escape("Don't panic!")
=> "Don%27t+panic%21"

エスケープ:使えない文字列を使える文字列に変換する。

11.2.2 送信メールのプレビュー

メールのメッセージをその場でプレビューすることができるメールプレビューを設定する。

development.rb: 開発環境用 / test.rb: テスト環境用 / production.rb : 本番環境用

host = 'localhost:3000' # ローカル環境用
config.action_mailer.default_url_options = { host: host, protocol: 'http' }

cloud9を使っていないので、上記を選択。
host〜をコピペすると、自分が開発しているhost(ドメイン名)にGETリクエストが送れないためにエラーが起きます。

演習 1

Railsのプレビュー機能を使って、ブラウザから先ほどのメールを表示してみてください。「Date」の欄にはどんな内容が表示されているでしょうか?

アクセスした日時が表示される。

11.2.3 送信メールのテスト

メールプレビューのテストも作成して、プレビューをダブルチェックできるようする。

リスト 11.21: テストのドメインホストを設定する

test/mailers/user_mailer_test.rb
require 'test_helper'

class UserMailerTest < ActionMailer::TestCase

  test "account_activation" do
    user = users(:Michael)
      # users(:Michael)をuserに代入
    user.activation_token = User.new_token
      # トークン情報を生成してuser.activation_tokenに代入
    mail = UserMailer.account_activation(user)
      # UserMailer.account_activation(user)をmail変数に代入

    # ここから確認のテスト
    assert_equal "Account activation", mail.subject
      # mail.subject(件名)を確認
    assert_equal [user.email], mail.to
      # mail.to(送り先)を確認
    assert_equal ["noreply@example.com"], mail.from
      # mail.from(送り元)を確認
    assert_match user.name,               mail.body.encoded
      # mail.body(本文)にuser.name(名前)が入っているか確認
    assert_match user.activation_token,   mail.body.encoded
      # mail.body(本文)にuser.activation_token(トークン)が入っているか確認
    assert_match CGI.escape(user.email),  mail.body.encoded
      # mail.body(本文)にuser.emailの「@」が
      # CGI.escape(エスケープ処理)されているものが入っているか確認
  end
end

演習 1

この時点で、テストスイートが green になっていることを確認してみましょう。
動作確認のみなので省略。

演習 2

リスト 11.20で使ったCGI.escapeの部分を削除すると、テストが red に変わることを確認してみましょう。
動作確認のみなので省略。

11.2.4 ユーザーのcreateアクションを更新

ユーザー登録を行うcreateアクションにコードを追加し、メイラーをアプリケーションで実際に使えるようにする。
signup直後のログインの廃止(本人確認前にログインできないようにする)して、メールチェックしてもらえるように促す。

app/controllers/users_controller.rb
class UsersController < ApplicationController
  .
  .
  .
  def create
    @user = User.new(user_params)
      # app/models/user.rb
      # attr_accessor :remember_token, :activation_token
      # before_save   :downcase_email

      # def create_activation_digest
      #  self.activation_token  = User.new_token
      #   self.activation_digest = User.digest(activation_token)
      # end

    if @user.save

      # app/models/user.rb
      # before_create :create_activation_digest

      UserMailer.account_activation(@user).deliver_now
      flash[:info] = "Please check your email to activate your account."
      redirect_to root_url
    else
      render 'new'
    end
  end
  .
  .
  .
end

演習 1

新しいユーザーを登録したとき、リダイレクト先が適切なURLに変わったことを確認してみましょう。その後、Railsサーバーのログから送信メールの内容を確認してみてください。有効化トークンの値はどうなっていますか?

適切なURL: root_url

----==_mimepart_603da2dfbacd4_11ed2af75249ad6c294cd
Content-Type: text/plain;
 charset=UTF-8
Content-Transfer-Encoding: 7bit

Hi moutoon,

Welcome to the Sample App! Click on the link below to activate your account:

http://localhost:3000/account_activations/iXav_kpcTA4krSc-3LR3DA/edit?email=moutoonm342%40gamil.com

演習 2

コンソールを開き、データベース上にユーザーが作成されたことを確認してみましょう。また、このユーザーはデータベース上にはいますが、有効化のステータスがfalseのままになっていることを確認してください。

>> user = User.find_by(name: "moutoon")
   (0.1ms)  begin transaction
  User Load (5.8ms)  SELECT "users".* FROM "users" WHERE "users"."name" = ? LIMIT ?  [["name", "moutoon"], ["LIMIT", 1]]
=> #<User id: 101, name: "moutoon", email: "moutoonm342@gamil.com", created_at: "2021-03-02 02:28:47", updated_at: "2021-03-02 02:28:47", password_digest: [FILTERED], remember_digest: nil, admin: nil, activation_digest: "$2a$12$9y5elJ64pu1EC1COuJfdU.95jcOd0TdqpDyHz7W5m1M...", activated: false, activated_at: nil>

activated: falseになっている。

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

Railsポートフォリオ作成 #5 基本機能バックエンド開発

こんにちは:smiley:
今回は基本機能のバックエンド開発を行いました。
(前回記事(#4 herokuデプロイ))

私は、前職(ホテルの料飲部)における、コミュニケーションの課題を解決するアプリを作っているのですが、今回は、

基本機能のバックエンド開発を行いました

具体的には、「レストランで使うものの数を管理する機能」です。

しかし、肝心の数を管理するやり方が正直いまいちいいのが出てこなくて、一旦プルダウンの選択式しました。

ただこれはUXとしてはいまいちなのでは?
という気がするので、最終的には何かもっといい方法でできるといいんだけど、、、と思っています。

感じたこと

  • Railsでバックエンドやるのは少し慣れてきた。
    楽しみながらできるようになってきたし、基本部分は1日で終わらせることができました。
    少し成長を感じました。(やっぱり前回のAWSはキツかった、、、)

  • プログラミングはつくづく巨大な岩を小さなトンカチで少しずつ少しずつ削ってるみたいだってこと。
    やらないといけないこと、実装しないといけないこと、勉強しないといけないことがありすぎて、押しつぶされそうになって正直諦めそうになります、、、

    でもそれを一個一個少しずつ解決していくしかないわけで。
    水の呼吸で一刀両断とかできたらいいのに、、、
    もくもく会とか参加しながら自分をやらないといけない状況において、仕組みを使って解決していこうと思います。

最後に

モデルの単体テストまで書き終わったので、次はフロントエンドをやっていこうと思います。:fist:
フロントエンドは経験値が少ないので、復習しながら時間をかけてやっていくことになると思います。

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

【Nuxt Vue Rails】axiosのContent-Typeがappliation/jsonにならない

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

Ajaxでお気に入り機能を実装する

自己紹介

9月から独学でプログラミング学習を開始し、
11月からスクールを使って学習をしています。
現在はポートフォリオの作成し転職活動中です。
知識を定着させるために、学びをアウトプットしています。
また、これから学び始める方の参考になることを願っています。


開発環境

  • Ruby 3.0
  • Ruby on Rails 6.0.3.4
  • jQuery 3.5.1

Ajaxでお気に入り機能を実装する

前回、Railsでお気に入り機能を実装するという記事を投稿しました。
今回は、この記事の非同期化を行います。
同時にお気に入り数のカウントも行ってみましょう!

1.マイグレーションファイルを作成する

まずはターミナルで下記のコマンドを実行して下さい。
rails g migration AddLikersCountToMoives
そして、生成されたマイグレーションファイルを編集します。

class AddLikersCountToMovies < ActiveRecord::Migration[6.0]
  def change
    add_column :movies, :likers_count, :integer, default: 0
  end
end

これでlikees_countというメソッドが使用できるようになりました。

公式のGithubも合わせて参照下さい。

☆ポイント
ここでrails db:migrateを実行しますが、
その前に、もしお気に入りの登録を行っている場合は全て解除して下さい。
default: 0としているため、現在の状態が0になってしまいます。
このまま、お気に入り解除をすると-1のような表示になってしまいます。

2.ビューファイルを追加する

まずは、views/movie配下に_favorite.html.erbというビューファイルを新しく追加します。

<% if current_user.likes?(movie) %>
  お気に入り解除
<% else %>
  お気に入り登録
<% end %>
<%= movie.likers_count %>

これは以前に作成したビューファイルを切り出したものに、likers_countを追加したものになります。
それでは、以前に作成したビューファイルを編集しましょう

3.ビューファイルの編集をする

<%= link_to favorite_movie_path(movie), remote: true do %>
  <div id="favorite-movie">
    <%= render partial: "movies/favorite", locals: {movie: movie}
  </div>
<% end %>

まず、作成した_favorite.html.erbrender partial: "movies/favorite"で読み込んでいます。
locals: {movie: movie}favorite.html.erb内のmovieにビューで使っているmovieを渡しています。

renderについてはRailsガイドを参照ください。

link_toのオプションにremote: trueを指定しています。
これによりviews/movies/favorite.js.erbが呼び出されるようになります。
指定したid="favorite-moviefavorite.js.erbで使用します。

4.JavaScriptファイルを作成する。

views/movies配下にfavorite.js.erbを作成して下さい。
内部は下記の1行だけです。

$("#favorite-movie").html("<%= j(render partial: "movies/favorite", locals: {movie: @movie}) %>");

先程のidを指定し、内部のhtmlを_favorite.html.erbに書き換えています。
今回、jQueryを使用していますが、導入については、省略致します。


以上で機能の実装は終了です。
前回記事、Railsでお気に入り機能を実装すると合わせてご覧下さい。
また、至らない点があれば、お手数ですがご指摘下さい。


補足①

非同期でお気に入りを行った際に、お気に入り数のカウントが、遅れてしまう可能性があります。
そのような場合、controllers/movies_controller.rbに下記を追加して下さい。

  def favorite
    @movie = Movie.find(params[:id])
    current_user.toggle_like!(@movie)
    + @movie.reload    #この1行を追加

reloadでお気に入り処理後に再読み込みを行っています。

補足②

お気に入り処理をeachなど繰り返し処理内で行いたいこともあると思います。
その場合は、指定したid="favorite-movieを下記のように変更して下さい。

<%= @movies.each do |movie| %>
  <div id="favorite-movie-<%= movie.id %>">
    <%= render partial: "movie/favorite", locals: {movie: movie} %>
  </div>
<% end %>

views/movies/favorite.js.erb

$("#favorite-movie-<%= @movie.id %>").html("<%= j(render partial: "movies/favorite", locals: {movie: @movie}) %>");

id属性にid値を指定することで、繰り返し処理内でもお気に入りの処理を行うことができます。

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

Railsチュートリアル 12章まとめ

Railsチュートリアル 12章まとめ

この章でやること

  • よくあるパスワードを忘れた際にパスワードの再設定をできるようにする
  • パスワードリセットコントローラーを作成する
  • メールを送信できるようにする
  • パスワードを再設定できるようにする

12.1 PasswordResetsリソース

PasswordResetsリソースのモデリングから
必要なデータ(再設定用のダイジェストなど)をUserモデルに追加していく

PasswordResetsもリソースとして扱っていきたいので、まずは標準的なRESTfulなURLを用意。

12.1.1 PasswordResetsコントローラ

コントローラーを生成 newとeditのメソッドも追加

$ rails generate controller PasswordResets new edit --no-test-framework

no-test-framework はテストを生成しないオプション
今回は単体テストをせず統合テストでカバーするため

リスト 12.1: パスワード再設定用リソースを追加する

config/routes.rb
    Rails.application.routes.draw do
    root   'static_pages#home'
    get    '/help',    to: 'static_pages#help'
    get    '/about',   to: 'static_pages#about'
    get    '/contact', to: 'static_pages#contact'
    get    '/signup',  to: 'users#new'
    get    '/login',   to: 'sessions#new'
    post   '/login',   to: 'sessions#create'
    delete '/logout',  to: 'sessions#destroy'
    resources :users
    resources :account_activations, only: [:edit]
    resources :password_resets,     only: [:new, :create, :edit, :update] #対応するビューを作るため
    end

RESTfulのルーティング↓

HTTPリクエスト URL Action 名前付きルート
GET password_resets/new new new_password_reset_path
POST password_resets create password_resets_path
GET password_resets/トークン/edit edit edit_password_reset_url(token)
PATCH password_resets/トークン update password_reset_url(token)

パスワード再設定画面へのリンクを追加する

app/views/sessions/new.html.erb
    <% provide(:title, "Log in") %>
    <h1>Log in</h1>

    <div class="row">
    <div class="col-md-6 col-md-offset-3">
        <%= form_with(url: login_path, scope: :session, local: true) do |f| %>

        <%= f.label :email %>
        <%= f.email_field :email, class: 'form-control' %>

        <%= f.label :password %>
        <%= link_to "(forgot password)", new_password_reset_path %>  #パスワード忘れた時のリンク作成→createアクションへ
        <%= f.password_field :password, class: 'form-control' %>

        <%= f.label :remember_me, class: "checkbox inline" do %>
            <%= f.check_box :remember_me %>
            <span>Remember me on this computer</span>
        <% end %>

        <%= f.submit "Log in", class: "btn btn-primary" %>
        <% end %>

        <p>New user? <%= link_to "Sign up now!", signup_path %></p>
  </div>
</div>

12.1.2 新しいパスワードの設定

  • Userモデルに2つのカラムを追加していく

    ・reset_digest属性   ハッシュ化した認証トークンを保存
    ・reset_sent_at属性   再設定のリンクに有効期限をつけるためにリンク送信時間を記憶

migrationファイルを作成

$ rails generate migration add_reset_to_users reset_digest:string \ reset_sent_at:datetime
$ rails db:migrate

パスワード再設定用のメールアドレスを入力するビューpassword_resets/new.html.erb
sessions/new.html.erbを参考に作成

新しいパスワード再設定画面ビュー

app/views/password_resets/new.html.erb
    <% provide(:title, "Forgot password") %>
    <h1>Forgot password</h1>

    <div class="row">
    <div class="col-md-6 col-md-offset-3">
        <%= form_with(url: password_resets_path, scope: :password_reset,  #createアクションへpath
                        local: true) do |f| %>    
        <%= f.label :email %>
        <%= f.email_field :email, class: 'form-control' %>

        <%= f.submit "Submit", class: "btn btn-primary" %>
        <% end %>
    </div>
    </div>

12.1.3 createアクションでパスワード再設定

以下機能を持ったcreateアクションを定義する

  • フォームから送信を行った後、メールアドレスをキーとしてユーザーをデータベースから見つける
  • パスワード再設定用トークンと送信時のタイムスタンプでデータベースの属性を更新する
  • ルートURLにリダイレクトし、フラッシュメッセージをユーザーに表示
  • 送信が無効の場合は、ログイン同様にnewページを出力してflash.nowメッセージを表示

パスワード再設定用のcreateアクション

app/controllers/password_resets_controller.rb
    class PasswordResetsController < ApplicationController

    def create
        @user = User.find_by(email: params[:password_reset][:email].downcase) #formで送信された小文字化したemailから@userを取得
        if @user           #もしuserが存在すれば
        @user.create_reset_digest       #@userのcreate_reset..メソッドを発動→パスワード再設定の属性を設定
        @user.send_password_reset_email     #パスワード再設定のメールを送信する
        flash[:info] = "Email sent with password reset instructions"
        redirect_to root_url             #root_urlに飛ばす
        else
        flash.now[:danger] = "Email address not found"   #もしuserがなければ、メール見つからないとメッセージ表示
        render 'new'
        end
    end

Userモデルにパスワード再設定用メソッドを追加する

app/models/user.rb
    class User < ApplicationRecord
    attr_accessor :remember_token, :activation_token, :reset_token      #reset_token属性追加
    before_save   :downcase_email
    before_create :create_activation_digest
    .
    .
    .
    # アカウントを有効にする
    def activate
        update_attribute(:activated,    true)
        update_attribute(:activated_at, Time.zone.now)
    end

    # 有効化用のメールを送信する
    def send_activation_email
        UserMailer.account_activation(self).deliver_now
    end

    # パスワード再設定の属性を設定する
    def create_reset_digest
        self.reset_token = User.new_token                #トークンを作成しreset_token属性に代入
        update_attribute(:reset_digest,  User.digest(reset_token))  #Userモデルのreset_digestにresetトークンを入れてupdateする
        update_attribute(:reset_sent_at, Time.zone.now)  #Userモデルのreset_sent_atに現在時刻を入れてupdate
    end

    # パスワード再設定のメールを送信する
    def send_password_reset_email
        UserMailer.password_reset(self).deliver_now     #今すぐにメールを送信する
    end

    private

        # メールアドレスをすべて小文字にする
        def downcase_email
        self.email = email.downcase
        end

        # 有効化トークンとダイジェストを作成および代入する
        def create_activation_digest
        self.activation_token  = User.new_token
        self.activation_digest = User.digest(activation_token)
        end
    end

12.2 パスワード再設定のメール送信

・メールを送信できるようにする

12.2.1 パスワード再設定のメールとテンプレート

UserMailer.password_reset(self).deliver_now

このコードを実装するためにUserメイラーにpassword_resetメソッドを作成し、テキストメールのテンプレートを変えていく

パスワード再設定のリンクをメール送信する

app/mailers/user_mailer.rb
    class UserMailer < ApplicationMailer


    def password_reset(user)
        @user = user
        mail to: user.email, subject: "Password reset"
    end
    end

パスワード再設定のテンプレート(テキスト)

app/views/user_mailer/password_reset.text.erb
    To reset your password click the link below:

    <%= edit_password_reset_url(@user.reset_token, email: @user.email) %>

    This link will expire in two hours.

    If you did not request your password to be reset, please ignore this email and
    your password will stay as it is.

パスワード再設定のテンプレート(HTML)

app/views/user_mailer/password_reset.html.erb
    <h1>Password reset</h1>

    <p>To reset your password click the link below:</p>

    <%= link_to "Reset password", edit_password_reset_url(@user.reset_token,
                                                        email: @user.email) %>

    <p>This link will expire in two hours.</p>

    <p>
    If you did not request your password to be reset, please ignore this email and
    your password will stay as it is.
    </p>

パスワード再設定のプレビューメソッド(完成)

test/mailers/previews/user_mailer_preview.rb
    # Preview all emails at http://localhost:3000/rails/mailers/user_mailer
    class UserMailerPreview < ActionMailer::Preview

    # Preview this email at
    # http://localhost:3000/rails/mailers/user_mailer/account_activation
    def account_activation
        user = User.first
        user.activation_token = User.new_token
        UserMailer.account_activation(user)
    end

    # Preview this email at
    # http://localhost:3000/rails/mailers/user_mailer/password_reset
    def password_reset    #password_resetメソッド定義
        user = User.first
        user.reset_token = User.new_token
        UserMailer.password_reset(user)
    end
    end

これでhttp://localhost:3000/rails/mailers/user_mailer/password_resetのURL
(localhost:3000の部分はAWSではサーバー名に書き換える.....com/rails/mailers/...)

12.2.2 送信メールのテスト

メイラーメソッドのテストを書いていく

パスワード再設定用メイラーメソッドのテストを追加する

test/mailers/user_mailer_test.rb
    require 'test_helper'

    test "password_reset" do
        user = users(:michael)
        user.reset_token = User.new_token       #userにreset_tokenを代入する
        mail = UserMailer.password_reset(user)      #mail変数にメールを送る
        assert_equal "Password reset", mail.subject  #メールのタイトルは"Password reset"になっているか
        assert_equal [user.email], mail.to          #メールの宛先はuser.emailになっているか
        assert_equal ["noreply@example.com"], mail.from     #差出人はexample.comになっているか
        assert_match user.reset_token,        mail.body.encoded   #reset_tokenが入っているか
        assert_match CGI.escape(user.email),  mail.body.encoded   #userの.emailはescapeされているか@が%になっているか
    end
    end

12.3 パスワードを再設定する

次はPasswordResetsコントローラのeditアクションの実装

12.3.1 editアクションで再設定

先ほど定義したパスワード再設定の送信メールには、次のようなリンクが含まれている
https://example.com/password_resets/3BdBrXeQZSWqFIDRN8cxHA/edit?email=fu%40bar.com

このリンクを機能させるには、リンク先のパスワード再設定フォームを表示するビューを作る

注意点
メアドをキーにuserを検索するにはeditアクションとupdateアクションの両方でメアドが必要になる
フォームを一度送信してしまうと、メアドの情報は消えてしまう。
今回はこのメールアドレスを保持するため、隠しフィールドとしてページ内に保存する手法をとる
これにより、フォームから送信したときに、他の情報と一緒にメールアドレスが送信されるようになる

パスワード再設定のフォーム

app/views/password_resets/edit.html.erb
    <% provide(:title, 'Reset password') %>
    <h1>Reset password</h1>

    <div class="row">
    <div class="col-md-6 col-md-offset-3">
        <%= form_with(model: @user, url: password_reset_path(params[:id]),
                    local: true) do |f| %>
        <%= render 'shared/error_messages' %>

        <%= hidden_field_tag :email, @user.email %>   #hidden_field_tagを追加しメールも送るようにする

        <%= f.label :password %>                   #password入力フォーム
        <%= f.password_field :password, class: 'form-control' %>

        <%= f.label :password_confirmation, "Confirmation" %>       #確認用password入力フォーム
        <%= f.password_field :password_confirmation, class: 'form-control' %>

        <%= f.submit "Update password", class: "btn btn-primary" %>
        <% end %>
    </div>
    </div>

hidden_field_tag :email, @user.email はメールアドレスがparams[:email]に保存される
f.hidden_field :email, @user.email はparams[:user][:email]に保存される

次はeditアクションにparams[:email]が入った@userインスタンス変数を定義
authenticated?メソッドを使って認証する

パスワード再設定のeditアクション

app/controllers/password_resets_controller.rb
    class PasswordResetsController < ApplicationController
    before_action :get_user,   only: [:edit, :update]
    before_action :valid_user, only: [:edit, :update]#正しいユーザーだけ
    .
    private

        def get_user
        @user = User.find_by(email: params[:email])     #@userインスタンス変数=リンクで受け取ったメアドでuserを探す
        end

        # 正しいユーザーかどうか確認する
        def valid_user
        unless (@user && @user.activated? &&          #userが存在し、activated?がtrueかつ
                @user.authenticated?(:reset, params[:id]))      #reset_digest(テーブルのデータ)がリンクのURLと一致しているか
            redirect_to root_url
        end
        end
    end

抽象化したauthenticated?はsample_appで以下のように使われた

    authenticated?(:reset, params[:id])
    authenticated?(:remember, cookies[:remember_token])
    authenticated?(:activation, params[:id])

12.3.2 パスワードを更新する

AccountActivationsコントローラのeditアクションでは、ユーザーの有効化ステータスをfalseからtrueに変更したが、
今回の場合はフォームから新しいパスワードを送信するようになる。
したがって、フォームからの送信に対応するupdateアクションが必要
このupdateアクションでは、次の4つのケースを考慮する必要がある。

  1. パスワード再設定の有効期限が切れていないか
  2. 無効なパスワードであれば失敗させる(失敗した理由も表示する)
  3. 新しいパスワードが空文字列になっていないか(ユーザー情報の編集ではOKだった)
  4. 新しいパスワードが正しければ、更新する

(1)と(2)と(4)はこれまでの知識で対応できそうだが、(3)はどのように対応すれば良いのかあまり明確ではない

(1)「パスワード再設定の有効期限が切れていないか」については、editとupdateアクションに次のようなメソッドとbeforeフィルターを用意することで対応できそう

before_action :check_expiration, only: [:edit, :update]    # (1)への対応案

このcheck_expirationメソッドは、有効期限をチェックするPrivateメソッドとして定義

# 期限切れかどうかを確認する
def check_expiration
  if @user.password_reset_expired?#有効期限が切れていたら
    flash[:danger] = "Password reset has expired."
    redirect_to new_password_reset_url
  end
end

期限切れかどうかを確認するインスタンスメソッド「password_reset_expired?」を使っている。

beforeフィルターで保護したupdateアクションを使うことで、(2)「無効なパスワードであれば失敗させる(失敗した理由も表示する)」と(4)「新しいパスワードが正しければ、更新する」のケースに対応することができそう
例えば(2)については、更新が失敗したときにeditのビューが再描画され、パーシャルにエラーメッセージが表示されるようにすれば解決できる。
(4)については、更新が成功したときにパスワードを再設定し、あとはログインに成功したときと同様の処理を進めていけば問題なさそう。

ちょっと厄介なのが、パスワードが空文字だった場合の処理。
以前Userモデルを作っていたときに、パスワードが空でも良いallow_nilを実装をしたから
したがって、このケースについては明示的にキャッチするコードを追加する必要がある
これが考慮すべき点の(3)「 新しいパスワードが空文字列になっていないか(ユーザー情報の編集ではOKだった)」に当たる。
解決する方法として、今回は@userオブジェクトにエラーメッセージを追加する方法をとってみる

次のようにerrors.addを使ってエラーメッセージを追加する。

@user.errors.add(:password, :blank)

このように書くと、パスワードが空だった時に空の文字列に対するデフォルトのメッセージを表示してくれるようになる

以上の結果をまとめると、(1)のpassword_reset_expired?の実装を除き、すべてのケースに対応したupdateアクションが完成

app/controllers/password_resets_controller.rb
class PasswordResetsController < ApplicationController
  before_action :get_user,         only: [:edit, :update]
  before_action :valid_user,       only: [:edit, :update]
  before_action :check_expiration, only: [:edit, :update]    # (1)への対応

  def new
  end

  def create
    @user = User.find_by(email: params[:password_reset][:email].downcase)
    if @user
      @user.create_reset_digest
      @user.send_password_reset_email
      flash[:info] = "Email sent with password reset instructions"
      redirect_to root_url
    else
      flash.now[:danger] = "Email address not found"
      render 'new'
    end
  end

  def edit
  end

  def update
    if params[:user][:password].empty?                  # (3)への対応
      @user.errors.add(:password, :blank)
      render 'edit'
    elsif @user.update(user_params)                     # (4)への対応
      log_in @user
      flash[:success] = "Password has been reset."
      redirect_to @user
    else
      render 'edit'                                     # (2)への対応
    end
  end

  private

    def user_params
      params.require(:user).permit(:password, :password_confirmation)
    end

    # beforeフィルタ

    def get_user
      @user = User.find_by(email: params[:email])
    end

    # 有効なユーザーかどうか確認する
    def valid_user
      unless (@user && @user.activated? &&
              @user.authenticated?(:reset, params[:id]))
        redirect_to root_url
      end
    end

    # トークンが期限切れかどうか確認する
    def check_expiration
      if @user.password_reset_expired?
        flash[:danger] = "Password reset has expired."
        redirect_to new_password_reset_url
      end
    end
end

あとは、残しておいたpassword_reset_expired?の実装だけ

@user.password_reset_expired?

上のコードを動作させるために、password_reset_expired?メソッドをUserモデルで定義していく
このメソッドではパスワード再設定の期限を設定して、2時間以上パスワードが再設定されなかった場合は期限切れとする処理を行う

reset_sent_at < 2.hours.ago

上の<記号を「〜より少ない」と読んでしまうと、「パスワード再設定メール送信時から経過した時間が、2時間より少ない場合」となってしまうので、「少ない」ではなく「〜より早い時刻」と読む。
こうすると「パスワード再設定メールの送信時刻が、現在時刻より2時間以上前(早い)の場合」となり、 期待どおりの条件となる

app/models/user.rb
class User < ApplicationRecord

  # パスワード再設定の期限が切れている場合はtrueを返す
  def password_reset_expired?
    reset_sent_at < 2.hours.ago
  end

これでpassword_resets_controller.rbのcreateアクションが動く

12.3.3 パスワードの再設定をテストする

送信に成功した場合と失敗した場合の統合テストを作成する

まずは統合テストを作成

$ rails generate integration_test password_resets
      invoke  test_unit
      create    test/integration/password_resets_test.rb

パスワード再設定をテストする手順は、

  1. 最初に「forgot password」フォームを表示して無効なメールアドレスを送信
  2. 次はそのフォームで有効なメールアドレスを送信
  3. パスワード再設定用トークンが作成され、再設定用メールが送信される。
  4. メールのリンクを開いて無効な情報を送信
  5. そのリンクから有効な情報を送信して、それぞれが期待どおりに動作することを確認
test/integration/password_resets_test.rb
require 'test_helper'

class PasswordResetsTest < ActionDispatch::IntegrationTest

  def setup
    ActionMailer::Base.deliveries.clear
    @user = users(:michael)
  end

 test "password resets" do
    get new_password_reset_path#forgot passのリンク取得
    assert_template 'password_resets/new'#passリセット画面出るか
    assert_select 'input[name=?]', 'password_reset[email]'#input[name]がpassword_resetにあるか
    # メールアドレスが無効
    post password_resets_path, params: { password_reset: { email: "" } }
    assert_not flash.empty?#flashが空でないか
    assert_template 'password_resets/new'#newの画面を再描写するか
    # メールアドレスが有効
    post password_resets_path,
         params: { password_reset: { email: @user.email } }
    assert_not_equal @user.reset_digest, @user.reload.reset_digest#reset_digestカラムがreload後変化ないか
    assert_equal 1, ActionMailer::Base.deliveries.size #mailは1通だけ送ったか
    assert_not flash.empty?#flashが空でないか
    assert_redirected_to root_url#rootへリダイレクトしたか
    # パスワード再設定フォームのテスト
    user = assigns(:user)
    # メールアドレスが無効
    get edit_password_reset_path(user.reset_token, email: "")
    assert_redirected_to root_url
    # 無効なユーザー
    user.toggle!(:activated)#activatedカラムをtrueかfalseに反転(この場合false)
    get edit_password_reset_path(user.reset_token, email: user.email)
    assert_redirected_to root_url#失敗することを確認
    user.toggle!(:activated)#trueに戻す
    # メールアドレスが有効で、トークンが無効
    get edit_password_reset_path('wrong token', email: user.email)
    assert_redirected_to root_url
    # メールアドレスもトークンも有効
    get edit_password_reset_path(user.reset_token, email: user.email)
    assert_template 'password_resets/edit'#editページが描写されるか
    assert_select "input[name=email][type=hidden][value=?]", user.email#inputタグに正しい名前、type="hidden"、メールアドレスがあるかどうかを確認
    # 無効なパスワードとパスワード確認
    patch password_reset_path(user.reset_token),#createアクションへ送信
          params: { email: user.email,
                    user: { password:              "foobaz",
                            password_confirmation: "barquux" } }
    assert_select 'div#error_explanation'#errorのdivタグ出るか確認
    # パスワードが空
    patch password_reset_path(user.reset_token),
          params: { email: user.email,
                    user: { password:              "",
                            password_confirmation: "" } }
    assert_select 'div#error_explanation'#errorのdivタグ出るか確認
    # 有効なパスワードとパスワード確認
    patch password_reset_path(user.reset_token),
          params: { email: user.email,
                    user: { password:              "foobaz",
                            password_confirmation: "foobaz" } }
    assert is_logged_in?#ログインされたか
    assert_not flash.empty?#flashは空でないか
    assert_redirected_to user#@userページへ飛ぶか
  end
end

今回の新しい要素はinputタグぐらい

assert_select "input[name=email][type=hidden][value=?]", user.email

上のコードは、inputタグに正しい名前、type="hidden"、メールアドレスがあるかどうかを確認している

<input id="email" name="email" type="hidden" value="michael@example.com" />

テストコードは green になるはず

演習

1リスト 12.6にあるcreate_reset_digestメソッドはupdate_attributeを2回呼び出していますが、これは各行で1回ずつデータベースへ問い合わせしていることになります。リスト 12.20に記したテンプレートを使って、update_attributeの呼び出しを1回のupdate_columns呼び出しにまとめてみましょう(これでデータベースへの問い合わせが1回で済むようになります)。また、変更後にテストを実行し、 green になることも確認してください。ちなみにリスト 12.20にあるコードには、前章の演習(リスト 11.39)の解答も含まれています。

user.rb
  # パスワード再設定の属性を設定する
    def create_reset_digest
        self.reset_token = User.new_token#ランダムな文字列をいれる
        update_columns(reset_digest: User.digest(reset_token), reset_sent_at: Time.zone.now )
      end

2リスト 12.21のテンプレートを埋めて、期限切れのパスワード再設定で発生する分岐(リスト 12.16)を統合テストで網羅してみましょう(12.21 のコードにあるresponse.bodyは、そのページのHTML本文をすべて返すメソッドです)。期限切れをテストする方法はいくつかありますが、リスト 12.21でオススメした手法を使えば、レスポンスの本文に「expired」という語があるかどうかでチェックできます(なお、大文字と小文字は区別されません)。

password_reset_test.rb
test "expired token" do
    get new_password_reset_path#reset_pathを取得
    post password_resets_path,#mailを送る
         params: { password_reset: { email: @user.email } }

    @user = assigns(:user)
    @user.update_attribute(:reset_sent_at, 3.hours.ago)#メールを送った時間を3時間前にセット
    patch password_reset_path(@user.reset_token),#passwordの再設定をする
          params: { email: @user.email,
                    user: { password:              "foobar",
                            password_confirmation: "foobar" } }
    assert_response :redirect
    follow_redirect!
    assert_match /expired/i, response.body#Html bodyタグにexpiredがあるか確認
  end

3 2時間経ったらパスワードを再設定できなくする方針は、セキュリティ的に好ましいやり方でしょう。しかし、もっと良くする方法はまだあります。例えば、公共の(または共有された)コンピューターでパスワード再設定が行われた場合を考えてみてください。仮にログアウトして離席したとしても、2時間以内であれば、そのコンピューターの履歴からパスワード再設定フォームを表示させ、パスワードを更新してしまうことができてしまいます(しかもそのままログイン機構まで突破されてしまいます!)。この問題を解決するために、リスト 12.22のコードを追加し、パスワードの再設定に成功したらダイジェストをnilになるように変更してみましょう 。

app/controllers/password_resets_controller.rb
  def update
    if params[:user][:password].empty?
      @user.errors.add(:password, :blank)
      render 'edit'
    elsif @user.update(user_params)
      log_in @user
      @user.update_attribute(:reset_digest, nil)#追加するだけ
      flash[:success] = "Password has been reset."
      redirect_to @user
    else
      render 'edit'
    end
  end

4 リスト 12.18に1行追加し、1つ前の演習課題に対するテストを書いてみましょう。ヒント: リスト 9.25のassert_nilメソッドとリスト 11.33のuser.reloadメソッドを組み合わせて、reset_digest属性を直接テストしてみましょう。

test "password resets" do
....

    assert_nil user.reload.reset_digest   # 追加

12.4 本番環境でのメール送信(再掲)

これでパスワード再設定の実装も終わった。
あとはproduction環境でも動くようにするだけ

前章でやっているので、細かな設定はスキップ

以下スキップOK

本番環境からメール送信するために、「Mailgun」というHerokuアドオンを利用してアカウントを検証します
本チュートリアルでは、「starter tier」というサービスを使う

アプリケーションでMailgunアドオンを使うには、production環境のSMTPに情報を記入する
本番Webサイトのアドレスをhost変数に定義する必要がある。を自分のHerokuのURLに設定してください。その他の設定はこのまま使える

config/environments/production.rb
Rails.application.configure do
  .
  .
  .
  config.action_mailer.raise_delivery_errors = true
  config.action_mailer.delivery_method = :smtp
  host = '<your heroku app>.herokuapp.com'
  config.action_mailer.default_url_options = { host: host }
  ActionMailer::Base.smtp_settings = {
    :port           => ENV['MAILGUN_SMTP_PORT'],
    :address        => ENV['MAILGUN_SMTP_SERVER'],
    :user_name      => ENV['MAILGUN_SMTP_LOGIN'],
    :password       => ENV['MAILGUN_SMTP_PASSWORD'],
    :domain         => host,
    :authentication => :plain,
  }
  .
  .
  .
end

この時点で、Gitのトピックブランチをmasterにマージしておく


$ rails test
$ git add -A
$ git commit -m "Add password reset"
$ git checkout master
$ git merge password-reset
`

続いてリモートリポジトリにプッシュし、Herokuにデプロイします。

$ rails test
$ git push && git push heroku
$ heroku run rails db:migrate

MailgunのHerokuアドオンをまだ追加していなければ、次のコマンドを実行

$ heroku addons:create mailgun:starter

注: herokuコマンドのバージョンが古いとここで失敗するかも。その場合はHeroku Toolbeltを使って最新版に更新するか、次の古い文法のコマンドを試す

$ heroku addons:add mailgun:starter

メール設定にはMailgunアカウントのuser_nameとpassword設定を記入する行もありますが、そこには記入せず、必ず環境変数「ENV」に設定するよう注意。
本番運用するアプリケーションでは、暗号化されていないIDやパスワードのような重要なセキュリティ情報は「絶対に」ソースコードに直接書き込まない。
そのような情報は環境変数に記述し、そこからアプリケーションに読み込む。
今回の場合、そうした変数はMailgunアドオンが自動的に設定してくれる

最後に、受信メールの認証を行います。以下のコマンドを打つと、Mailgun ダッシュボードのURLが表示されるのでブラウザで開きます。

$ heroku addons:open mailgun

MailGun公式ドキュメントに従い、受信するメールアドレスを認証。
画面左側の「Sending」→「Domains」のリストにある「sandbox」で始まるサンドボックスドメインを選択。
画面右側の「Authorized Recipients」から受信メールアドレスを認証し、本番環境でのメール送信準備は完了。

Herokuへのデプロイが完了したら、ログインページの[forgot password]リンクをクリックして、production環境でパスワードの再設定を行ってみる。
フォームから送信すると、メールが送信されてくるはず。
記載されているリンクをクリックし、無効なパスワードと有効なパスワードをそれぞれ試してみましょう。

演習

production環境でユーザー登録を試してみましょう。ユーザー登録時に入力したメールアドレスにメールは届きましたか?
メールを受信できたら、実際にメールをクリックしてアカウントを有効化してみましょう。また、Heroku上のログを調べてみて、有効化に関するログがどうなっているのか調べてみてください。ヒント: ターミナルからheroku logsコマンドを実行してみましょう。
アカウントを有効化できたら、今度はパスワードの再設定を試してみましょう。正しくパスワードの再設定ができたでしょうか?

herokuエラー中なのでスキップ

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

railsアプリのデプロイエラーの原因が、gemのuglifierのバージョンに依るものだった話

railsアプリのデプロイがこけたので、その原因を追求してみた。

デプロイ時のエラーメッセージを読むと、以下の様なメッセージが頻出していた。

Caused by:
V8::Error: SyntaxError: Unexpected token: name (jsの変数名)
at js_error (<eval>:3623:12167)
at croak (<eval>:3623:22038)
at token_error (<eval>:3623:22175)
at unexpected (<eval>:3623:22263)
at semicolon (<eval>:3623:22781)
at simple_statement (<eval>:3623:25959)
at <eval>:3623:23747
at <eval>:3623:22954
at block_ (<eval>:3623:28083)
at ctor.body (<eval>:3623:27686)
at function_ (<eval>:3623:27782)
at <eval>:3623:24469
at <eval>:3623:22954
at block_ (<eval>:3623:28083)
at <eval>:3623:23857
at <eval>:3623:22954
at <eval>:3624:3759
at parse (<eval>:3624:3999)
at parse (<eval>:3958:22)
at uglifier (<eval>:4003:13)

下記の記事で、jsでletを使用している場合varに変更すると良い、というアドバイスがあったので、変更してみた結果、デプロイ成功。
どうやら、gemのuglifierのバージョンが足りず、ES6をサポートしていなかったため、letという新しい書き方を許容していなかったため、エラーが起きたらしい。
https://stackoverflow.com/questions/39221152/rails-5-heroku-deploy-error-execjsprogramerror-syntaxerror-unexpected-token

尚、ES6の書き方を許容するのはUglifier 3.2.0以降からだそう。

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

マジックナンバーとrailsにおける定数管理gem "config"

この記事の目的

開発中に「マジックナンバー」と言う聞き慣れない(恥ずかしながら)言葉を耳にし、その意味と問題点を調べたのでまとめました。また、Railsにおける「マジックナンバー」への対応策の一つを紹介したいと思います。

この記事で以下のことが分かると思います。

  • マジックナンバーとは何か
  • 開発におけるマジックナンバーの問題点
  • その解決策の一つであるrailsのgem "config"の扱い方

1. マジックナンバーとは

マジックナンバーとは、ハードコーディングされた数値のことです。

ハードコーディングとは、固有名詞・固有値をソースコード内に直接埋め込んだコードのことです。
ソースコードとは、開発者がコーディングしているファイルの内容のことです(index.html, stylesheet.cssなど大体そうかと)。

ハードコードの中でも数値のことを、プログラミングにおいて「マジックナンバー」と呼びます。

index.html.erb
# ハードコードの例
<li>
  <p>山田太郎<p>
  <p><span>23</span>歳です</p> # 23がマジックナンバー
</li>
<li>
  <p>田中花子</p>
  <p><span>73</span>歳です</p> # 73がマジックナンバー
</li>
:

上記はSNSにおけるユーザー一覧の例です。恐らく、こんな直接的な書き方はしないと思います。
一般にマジックナンバーはNGとされています(そもそもハードコードがNGなのかな...)。
その理由を次に説明します。

2. マジックナンバーが何故いけないのか

一般にマジックナンバーは、コーディングの際に避けるべきだ、と言われています。その主な理由は3つだと思われます。

  1. 数字だけ見ても何を意味しているか分からない[可読性が低い]
  2. 変更する場合は全てのマジックナンバーが利用されている場所を変更する必要がある [保守性が低い]
  3. セキュリティの安全性が下がる[脆弱性を生む]

3つの理由の説明はその他のサイトでも説明されていると思います。ここでは、少し具体的なマジックナンバーを使ってしまいそうな例を提示してみます(実体験)。

products_controller.rb
def show
  @product = Spree::Product.find(params[:id])
  @related_products = @product.related_products(4) # マジックナンバー
end

この4という数値が何を意味しているか、このコードを見ただけでは分かりません。
これは下の画像における表示個数の個数を表します。このように、実際の機能に対してコードの意味が伝わりにくくなってしまうのがマジックナンバーの良くない点です。
これ以降では、Railsにおけるマジックナンバーに関する対応策であるconfigと言うgemの扱いを簡単に紹介したいと思います。

[画像挿入]

3. Railsにおける定数管理のgem "config"

以下で具体的な使い方は説明しますが、このgemのいいところは以下の2つだと思います

  • 定数の意味をソースコード内で表現できる
  • 一箇所で定数を管理できる

これがリソースです。
rubyconfig/config

gem "config"は、定数管理を指定のYAMLファイルを用いて一箇所で行う為のgemです。場合によっては環境毎に(development, production, test)定数の値を変更することもできます。
ここでは、簡単な方法を一つ紹介し、先程の例を解決したいと思います。

4. gem "config"の使い方

ここでは最も簡単だと考えられる、一つのファイルで定数を管理する方法を示します。
その場合config/settings.ymlのみ書き込むことで定数管理を実現します。

(1) gem "config"の準備

Gemfile
gem "config"
Terminal
$ bundle install
$ rails g config:install
            create  config/initializers/config.rb
      create  config/settings.yml
      create  config/settings.local.yml
      create  config/settings
      create  config/settings/development.yml
      create  config/settings/production.yml
      create  config/settings/test.yml
      append  .gitignore

以下のようにファイルが追加されます(自動的に.gitignoreに追加されていました)。

(2) 定数を管理する

configでは、木構造のように定数を管理します。
今回は「商品の個数」の中で、「上限の数値」「テストの際の数値」といった定数を定義します。(このように記載することで、先程の例を解決します)

config/settings.yml
products_count:
  max_count: 4
  test_count: 10

このファイルにて定数の管理を、一括で扱います。また、定数への意味付けも可能になります
[可読性上がる]。

(3) - 1 定数を取り出す

定数は以下のように記載することで取り出すことができます。

Terminal
$ rails c
[1] pry(main)> Settings.products_count.max_count
=> 4
[2] pry(main)> Settings.products_count.test_count
=> 10

設定に応じてSetteings. ~ .~の~部分を置き換えることで、定数を取り出すことが可能になります。

(3) - 2 定数を取り出す(ファイル内で)

以上を参考に先程の例を、このように解決することができます。

products_controller.rb
def show
  @product = Spree::Product.find(params[:id])
  @related_products = @product.related_products(Settings.products_count[:max_count])
end

マジックナンバーを比べていた時に比べ、可読性が少しは上がります(恐らく)。

以上です。この記事が少しでも参考になるといいな、と思います。

余談

マジックナンバーには、エンジニアの方にコードレビューして頂いた際に出会いました。「実装要件を満たした」と思った自分に対して、予想のできない指摘が飛んできました。実務の世界では当たり前のことができていない、本当にまだまだなのだな、という実感を沸いた経験でした。

リソース

以下を参考にさせて頂きました

https://wa3.i-3-i.info/word12868.html

https://qiita.com/RyoheiHashimoto/items/38ec132bd2852238295e

https://it-biz.online/it-skills/hard-coding/

また、gem "config"に関しましてはその他多くの説明が存在しますので、詳細はそちらを参考にすると実装が捗るかと思います。

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

Rubyのincludeを理解したい①

書くこと

Rubyのincludeについて、メソッド探索について学んだ結果見えた事実
こちらの記事を参考にさせていただきました。ありがとうございました。

【ruby】 メソッド探索から見る、モジュール・特異メソッド・特異クラス

自分なりに、こちらの記事を設計図と車で喩え直してみました。

(こちらの記事は、2部構成になっているうちの、第一部です。)
次の記事はこちら→

クラスとインスタンス

本題は、Rubyのincludeについてですが、この記事では後に理解がしやすくなるようにクラスとインスタンス、それに伴う変数とメソッドについて掘り下げていきます。

Rubyには、クラスとインスタンスと言う概念があります。
車を作る時で考えてみましょう。

- クラスは、設計図です。

- インスタンスは、その設計図をもとに作られた実際の車です。

もう少し理解してみましょう。クラスとインスタンスには、それぞれメソッドなるものが存在します。

- クラスメソッドは、設計図全体に適応される共通オプションです。

手引書のルールみたいなもんですね

- インスタンスメソッドは、それぞれの車の特徴・動作などをそれぞれに付け加えます。

車は「走る」、あの車は「赤い」などと言ったことを表します。

example.rb
class Car   #クラス
  def color   #インスタンスメソッド
    @color = red   #@colorはインスタンス変数と呼ばれます。@colorには、redが代入されています。
  end
end

model_car = Car.new   #Carクラスのインスタンスを生成し、model_carに代入しています。
model_car.color   #Carクラスで定義されているcolorと言うインスタンスメソッドを、直前で生成したmodel_carインスタンスにかけています。

はい、ここまでは、多少Rubyについて勉強している方であれば、理解いただけたと思います。
わからない人も、ふーんと思いながら聞き進めてください。

本当に設計図と車の関係?

さて、ここから少しずつ、僕がなるほどなるほどと納得し直した部分に入っていきます。

まずは、先ほどのクラスとメソッドの関係性について
おさらいすると

クラスは、設計図です。

インスタンスは、その設計図をもとに作られた実際の車です。

と端的に書きました。

設計図と車の関係であれば
「設計図通りの色や大きさ、エンジンなどの特徴・部品で車が作られる」
つまり、設計図に書いてある内容を、それぞれの車が持つことになります。

しかし、クラスとインスタンスの関係は、少し違うようなのです。
どのように違うのでしょうか?

実は常に「参照」している

結論から述べると
特徴や動作を「持つ」のではなく、一回一回、設計図を「参照しながら」車は、動いているのです。

ちょっとイメージが湧きにくいかと思いますので、先ほどのコードをもとに考えてみましょう。

example.rb
class Car #Carクラスという設計図には、
  def color #colorでredを呼び出せるインスタンスメソッドが存在します。
    @color = red                           
  end
end

model_car = Car.new                        #1
model_car.color #ここで、colorを呼び出しています。 #2

ここからが、最も大切な部分ですので、注意して読んでみてください。

まず、ここで何が起きているか順を追って説明します。

  1. model_car変数に、Carクラスのインスタンスが代入されます
  2. model_carインスタンスに、colorインスタンスメソッドを呼び出します。その結果、model_carオブジェクトはインスタンス変数の@colorを持つことになります。

今の説明から、model_carと言う箱の中に、@colorがあるということをイメージできるでしょうか。
しかし、その箱の中には、colorというインスタンスメソッドは存在しません。

つまり、オブジェクトは@colorと言うインスタンス変数を持つことができるけど、colorというメソッドを持つことができない。という結論になります。

そうすると、クラスがインスタンスメソッドを持つことになり、インスタンスはクラスのメソッドを常に参照することになりそうです。

まとめると

オブジェクトは インスタンス変数 と クラスへの参照 を持つ
クラスは インスタンスメソッド を持つ

と言うことができます。

No Method Errorが起きてしまうわけ

もう少しだけ話を膨らませます。
もし、このクラス(設計図)に、言及がないメソッドが呼び出されたらどうでしょうか?

example.rb
class Car             #Carクラスには、wheelメソッドは存在しません。
  def color 
    @color = red                           
  end
end

model_car = Car.new                        
model_car.wheel        #wheelを参照しようとしています
#→No Method Error

このような状態で、No Method Errorが発生します。
このエラーが意味するところは

「そんな特徴は、設計図に書いてねーよ!」
ということです。次につながる話ですので、もう少し補足すると

「この設計図には書いてないから、この設計図の元になったものを追ってみよう。」
と判断させることもできます。

このことについては、次の記事で触れたいと思います。

最後に

最後まで読んでいただき、ありがとうございます。
ソースコード、記事の書き方について「もっとこうしたほうがいいよ!」というご意見、「そこどうなっているの?」というご質問など、お待ちしております。

参考文献

【ruby】 メソッド探索から見る、モジュール・特異メソッド・特異クラス

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

Railsチュートリアル11章まとめ

この章でやること

本当にそのメールアドレスの持ち主なのかどうかを確認できるようにする。
SMS認証的なやつ
(1)有効化トークンやダイジェストを関連付けておいた状態で、
(2)有効化トークンを含めたリンクをユーザーにメールで送信し、
(3)ユーザーがそのリンクをクリックすると有効化できるようにする

アカウントを有効化する基本的な手順は
1. ユーザーの初期状態は「有効化されていない」(unactivated)にしておく。
2. ユーザー登録が行われたときに、有効化トークンと、それに対応する有効化ダイジェストを生成する。
3. 有効化ダイジェストはデータベースに保存しておき、有効化トークンはメールアドレスと一緒に、ユーザーに送信する有効化用メールのリンクに仕込んでおく
4. ユーザーがメールのリンクをクリックしたら、アプリケーションはメールアドレスをキーにしてユーザーを探し、データベース内に保存しておいた有効化ダイジェストと比較することでトークンを認証する。
5. ユーザーを認証できたら、ユーザーのステータスを「有効化されていない」から「有効化済み」(activated)に変更する。

今回実装するアカウント有効化や次章でのパスワード再設定の仕組みはよく似た点が多いので、多くのアイデアを使い回すことができる
それぞれの仕組みの似ている点をまとめる

検索キー string digest authentication
email password password_digest authenticate(password)
id remember_token remember_digest authenticated?(:remember, token)
email activation_token activation_digest authenticated?(:activation, token)
email reset_token reset_digest authenticated?(:reset, token)

今時点意味分からなくてもOK

11.1 AccountActivationsリソース

セッション機能を使って、アカウントの有効化という作業を「リソース」としてモデル化する

なお、アカウント有効化もリソースとして扱うが、少し使い方が異なる。
例えば、有効化用のメールのリンクにアクセスして有効化のステータスを変更する場合、RESTのルールに従うと通常はPATCHリクエストとupdateアクションになる。しかし、有効化リンクはメールでユーザーに送られるので、ユーザーがこのリンクをクリックすれば、それはブラウザで普通にクリックしたときと同じであり、その場合ブラウザから発行されるのは(updateアクションで使うPATCHリクエストではなく)GETリクエストになってしまう。
このため、ユーザーからのGETリクエストを受けるために、(本来であればupdateのところを)editアクションに変更して使ってく

作業始める前にGitで新機能用のトピックブランチを作成

$ git checkout -b account-activation

11.1.1 AccountActivationsコントローラ

AccountActivationsリソースを作るために、まずはAccountActivationsコントローラを生成

$ rails generate controller AccountActivations

後ほど詳しく説明があるが、有効化のメールには次のURLを含めることで認証する
edit_account_activation_url(activation_token, ...)

つまり、editアクションへの名前付きルートが必要になるので、
ルーティングにアカウント有効化用のresources行を追加する

config/routes.rb
  resources :users
  resources :account_activations, only: [:edit] #リソースを追加
HTTPリクエスト URL Action 名前付きルート
GET /account_activation/トークン/edit edit edit_account_activation_url(token)

先にアカウント有効化用のデータモデルとメイラーを作成し、終わったらeditアクションを編集する

演習

現時点でテストスイートを実行すると green になることを確認してみましょう。
→パスする
表 11.2の名前付きルートでは、_pathではなく_urlを使うように記してあります。なぜでしょうか? 考えてみましょう。ヒント: 私達はこれからメールで名前付きルートを使います。
→pathは相対パス、urlは絶対パス メールからリンクしてもらうので、絶対パスを使う

11.1.2 AccountActivationのデータモデル

有効化のメールには一意の有効化トークンが必要になる。
そのため、「送信メールとデータベースに同じ文字列を置いておき、比較して認証する」というのが思いつくが、この方法では万が一データベースの内容が漏れたとき、危ない

なので仮想的な属性を使ってハッシュ化した文字列をデータベースに保存するようにして比べる
仮想的な属性は
user.activation_token
このようなメソッドでユーザーを認証していく
user.authenticated?(:activation, token)
(まだ使えない)

activated属性をusersテーブルに追加して論理値を取れるようにもする。↓
if user.activated? ...

本チュートリアルで使うことはないが、ユーザーを有効にしたときの日時も念のために記録

次のマイグレーションをコマンドラインで実行してのデータモデルを追加

$ rails generate migration add_activation_to_users \ activation_digest:string activated:boolean activated_at:datetime

次に、activated属性のデフォルトの論理値をfalseにしておく

db/migrate/[timestamp]_add_activation_to_users.rb
class AddActivationToUsers < ActiveRecord::Migration[6.0]
  def change
    add_column :users, :activation_digest, :string
    add_column :users, :activated, :boolean, default: false
    add_column :users, :activated_at, :datetime
  end
end
$ rails db:migrate

Activationトークンのコールバック

createでユーザーを新規登録する前に、有効化トークンや有効化ダイジェストは作成し、メールを送る必要がある
前もメールアドレスをデータベースに保存する前に、メールアドレスを全部小文字に変換するため、before_saveコールバックにdowncaseメソッドをバインドした。
before_saveコールバックを用意しておくと、オブジェクトが1保存される直前、2オブジェクトの作成時や3更新時にそのコールバックが呼び出されるようになる。
しかし今回は、オブジェクトが作成されたときだけコールバックを呼び出し、それ以外のときには呼び出したくない
そこでbefore_createコールバックが必要となる

before_create :create_activation_digest

上のコードはメソッド参照と呼ばれ、こうするとRailsはcreate_activation_digestというメソッドを探し、ユーザーを作成する前に実行するようになる
create_activation_digestメソッド自体はUserモデル内でしか使わないので、外部に公開する必要はない
なのでprivateキーワードを指定して、このメソッドをRuby流に隠蔽する

private

  def create_activation_digest
    # 有効化トークンとダイジェストを作成および代入する
  end

クラス内でprivateキーワードより下に記述したメソッドは自動的に非公開になる。
コンソールで確認すると..

$ rails console
>> User.first.create_activation_digest
NoMethodError: private method `create_activation_digest' called for #<User>

privete下のメソッドは呼び出せなくなる

今回のbefore_createコールバックを使う目的は、トークンとそれに対応するダイジェストを割り当てるため

self.activation_token  = User.new_token #User.new_tokenはランダムな文字列を作るメソッドとして定義ずみ
self.activation_digest = User.digest(activation_token)

このコードでは、記憶トークンや記憶ダイジェストのために作ったメソッドを使いまわしている
9章で作成したrememberメソッドと比べると...

# 永続セッションのためにユーザーをデータベースに記憶する
def remember
  self.remember_token = User.new_token
  update_attribute(:remember_digest, User.digest(remember_token))
end

主な違いは、後者のupdate_attributeの使い方にある。
記憶トークンやダイジェストは既にデータベースにいるユーザーのために作成されるのに対し、
before_createコールバックの方はユーザーが作成される前に呼び出される点が異なる。
このコールバックがあることで、User.newで新しいユーザーが定義されると、activation_token属性やactivation_digest属性が得られるようになる
後者のactivation_digest属性は既にデータベースのカラムとの関連付けができあがっているので、ユーザーが保存されるときに一緒に保存される。

実装のコードはこうなる
以前に実装したメールアドレスを小文字にするメソッドもメソッド参照に切り替えている点に注意

app/models/user.rb
class User < ApplicationRecord
  attr_accessor :remember_token, :activation_token  #追加
  before_save   :downcase_email  #メソッド参照
  before_create :create_activation_digest

  .
  private

    # メールアドレスをすべて小文字にする
    def downcase_email
      self.email = email.downcase
    end

    # 有効化トークンとダイジェストを作成および代入する
    def create_activation_digest
      self.activation_token  = User.new_token
      self.activation_digest = User.digest(activation_token)
    end
end

サンプルユーザーの生成とテスト

先に進む前に、サンプルデータとfixtureも更新し、テスト時のサンプルとユーザーを事前に有効化しておく
Time.zone.nowはRailsの組み込みヘルパーで、サーバーのタイムゾーンに応じたタイムスタンプを返す。

db/seeds.rb
# メインのサンプルユーザーを1人作成する
User.create!(name:  "Example User",
             email: "example@railstutorial.org",
             password:              "foobar",
             password_confirmation: "foobar",
             admin:     true,
             activated: true,#有効化しておく
             activated_at: Time.zone.now)

# 追加のユーザーをまとめて生成する
99.times do |n|
  name  = Faker::Name.name
  email = "example-#{n+1}@railstutorial.org"
  password = "password"
  User.create!(name:  name,
              email: email,
              password:              password,
              password_confirmation: password,
              activated: true,#有効化しておく
              activated_at: Time.zone.now)
end
test/fixtures/users.yml
michael:
  name: Michael Example
  email: michael@example.com
  password_digest: <%= User.digest('password') %>
  admin: true
  activated: true
  activated_at: <%= Time.zone.now %>

archer:
  name: Sterling Archer
  email: duchess@example.gov
  password_digest: <%= User.digest('password') %>
  activated: true
  activated_at: <%= Time.zone.now %>

lana:
  name: Lana Kane
  email: hands@example.gov
  password_digest: <%= User.digest('password') %>
  activated: true
  activated_at: <%= Time.zone.now %>

malory:
  name: Malory Archer
  email: boss@example.gov
  password_digest: <%= User.digest('password') %>
  activated: true
  activated_at: <%= Time.zone.now %>

<% 30.times do |n| %>
user_<%= n %>:
  name:  <%= "User #{n}" %>
  email: <%= "user-#{n}@example.com" %>
  password_digest: <%= User.digest('password') %>
  activated: true
  activated_at: <%= Time.zone.now %>
<% end %>

データベースを初期化して、サンプルデータを再度生成し直す

$ rails db:migrate:reset
$ rails db:seed

演習

本項での変更を加えた後、テストスイートが green のままになっていることを確認してみましょう。
コンソールからUserクラスのインスタンスを生成し、そのオブジェクトからcreate_activation_digestメソッドを呼び出そうとすると(Privateメソッドなので)NoMethodErrorが発生することを確認してみましょう。また、そのUserオブジェクトからダイジェストの値も確認してみましょう。

>> user = User.new
>> user.create_activation_digest
NoMethodError: private method `create_activation_digest' called for #<User:0x000000045483b0>
Did you mean?  restore_activation_digest!
        from (irb):2
>> user.digest
NoMethodError: undefined method `digest' for #<User:0x000000045483b0>
        from (irb):3
>> user.activation_digest
=> nil

アクセスできず、nilになる

リスト 6.35で、メールアドレスの小文字化にはemail.downcase!という(代入せずに済む)メソッドがあることを知りました。このメソッドを使って、リスト 11.3のdowncase_emailメソッドを改良してみてください。また、うまく変更できれば、テストスイートは成功したままになっていることも確認してみてください。

user.rb
  def downcase_email
    email.downcase!     #破壊的メソッドを使えばOK
  end

11.2 アカウント有効化のメール送信

データのモデル化が終わった

  • 今度は有効化メールの送信に必要なコードを追加していく。
  • Action Mailerライブラリを使ってUserのメイラーを追加する
  • メイラーはUsersコントローラのcreateアクションで有効化リンクをメール送信するために使う
  • メイラーの構成はコントローラのアクションとよく似ており、メールのテンプレートをビューと同じ要領で定義できる
  • メールのテンプレートの中に有効化トークンとメールアドレス(= 有効にするアカウントのアドレス)のリンクを含め、使っていく

11.2.1 送信メールのテンプレート

メイラーは、モデルやコントローラと同様にrails generateで生成

$ rails generate mailer UserMailer account_activation password_reset

これで今回必要となるaccount_activationメソッドと、次章で使うpassword_resetメソッドが生成された

さらに生成したメイラーごとに、ビューのテンプレートが2つずつ生成されている。
1つはテキストメール用のテンプレート、1つはHTMLメール用のテンプレートで以下のコードが自動で記載される(後で変更する)

app/views/user_mailer/account_activation.text.erb
UserMailer#account_activation

<%= @greeting %>, find me in app/views/user_mailer/account_activation.text.erb
app/views/user_mailer/account_activation.html.erb
<h1>UserMailer#account_activation</h1>

<p>
  <%= @greeting %>, find me in app/views/user_mailer/account_activation.html.erb
</p>
app/mailers/application_mailer.rb
class ApplicationMailer < ActionMailer::Base
  default from: "from@example.com"
  layout 'mailer'
end
app/mailers/user_mailer.rb
class UserMailer < ApplicationMailer

  # Subject can be set in your I18n file at config/locales/en.yml
  # with the following lookup:
  #
  #   en.user_mailer.account_activation.subject
  #
  def account_activation
    @greeting = "Hi"

    mail to: "to@example.org"
  end

  # Subject can be set in your I18n file at config/locales/en.yml
  # with the following lookup:
  #
  #   en.user_mailer.password_reset.subject
  #
  def password_reset
    @greeting = "Hi"

    mail to: "to@example.org"
  end
end

生成されたメイラーの動作を簡単に追ってみる
application_mailer.rbには、デフォルトのfromアドレスがある
さらにメールのフォーマットに対応するメイラーレイアウトも使われている

user_mailer.rbの各メソッドには宛先メールアドレスもある
Railsチュートリアルでは関係ないが、生成されるHTMLメイラーのレイアウトやテキストメイラーのレイアウトはapp/views/layoutsで確認可能。生成されたコードにはインスタンス変数@greetingも含まれてる

では最初に、生成されたテンプレートをカスタマイズして、実際に有効化メールで使えるようにしていく。
次に、ユーザーを含むインスタンス変数を作成してビューで使えるようにし、user.emailにメール送信
mailにsubjectキーを引数として渡しているが、この値は、メールの件名にあたる

app/mailers/application_mailer.rb
class ApplicationMailer < ActionMailer::Base
  default from: "noreply@example.com"#メアド変更
  layout 'mailer'
end
app/mailers/user_mailer.rb
class UserMailer < ApplicationMailer

  def account_activation(user)#userの引数を渡す
    @user = user
    mail to: user.email, subject: "Account activation"#タイトル付きのメールを送る
  end

  def password_reset#今は関係ない
    @greeting = "Hi"

    mail to: "to@example.org"
  end
end

このテストは現時点では red (account_activationに引数を与えたため)

これからレイアウトするテンプレートビューは、通常のビューと同様ERBで自由にカスタマイズ可能
ここでは挨拶文にユーザー名を含め、カスタムの有効化リンクを追加していく
この後、Railsサーバーでユーザーをメールアドレスで検索して有効化トークンを認証できるようにしたいので、リンクにはメールアドレスとトークンを両方含めておく必要がある

AccountActivationsリソースで有効化をモデル化したので、トークン自体は定義した名前付きルートの引数で使われる

edit_account_activation_url(@user.activation_token, ...)

例えば

edit_user_url(user)

上のメソッドは、絶対パスのuser_urlでurlを生成し、引数のユーザーの編集ページにアクセスする

https://www.example.com/account_activations/q5lt38hQDc_959PVoo6b7A/edit

上の「q5lt38hQDc_959PVoo6b7A」という部分はnew_tokenメソッドで生成されたもの。
URLで使えるようにBase64でエンコードされている。ちょうど/users/1/editの「1」のようなユーザーIDと同じ役割を果たす
このトークンは、AccountActivationsコントローラのeditアクションではparamsハッシュでparams[:id]として参照可能になる

クエリパラメータを使って、このURLにメールアドレスも組み込んでみる
※クエリパラメータとは、URLの末尾で疑問符「?」に続けてキーと値のペアを記述したもの

account_activations/q5lt38hQDc_959PVoo6b7A/edit?email=foo%40example.com

このとき、メールアドレスの「@」記号がURLでは「%40」となっている。これは「エスケープ」と呼ばれる手法で、通常URLでは扱えない文字を扱えるようにするために変換されている
※@はURLで使えない

Railsでクエリパラメータを設定するには、名前付きルートに対して次のようなハッシュを追加

edit_account_activation_url(@user.activation_token, email: @user.email)

このようにして名前付きルートでクエリパラメータを定義すると、Railsが特殊な文字を自動的にエスケープしてくれる
取り出し時もコントローラでparams[:email]からメールアドレスを取り出すときには、自動的にエスケープを解除してくれる

ここまでできれば、user_mailerで定義した@userインスタンス変数、editへの名前付きルート、ERBを組み合わせて、必要なリンクを作成できる。

アカウント有効化のHTMLテンプレートでは、正しいリンクを組み立てるためにlink_toメソッドを使われている。

app/views/user_mailer/account_activation.text.erb
Hi <%= @user.name %>,

Welcome to the Sample App! Click on the link below to activate your account:

<%= edit_account_activation_url(@user.activation_token, email: @user.email) %>
#リンクをセット(名前付きルートに引数を渡してURL生成)
app/views/user_mailer/account_activation.html.erb
<h1>Sample App</h1>

<p>Hi <%= @user.name %>,</p>

<p>
Welcome to the Sample App! Click on the link below to activate your account:
</p>

<%= link_to "Activate", edit_account_activation_url(@user.activation_token,
                                                    email: @user.email) %>
#リンクをセット(名前付きルートに引数を渡してURL生成)

演習

コンソールを開き、CGIモジュールのescapeメソッド(リスト 11.15)でメールアドレスの文字列をエスケープできることを確認してみましょう。このメソッドで"Don't panic!"をエスケープすると、どんな結果になりますか?

irb(main):005:0>  CGI.escape("Don't panic!")
=> "Don%27t+panic%21"

11.2.2 送信メールのプレビュー

メールプレビューを使って、今ほど定義したテンプレートの実際の表示を確認する
Railsでは、特殊なURLにアクセスするとメールのメッセージをその場でプレビューすることができる
利用するには、アプリケーションのdevelopment環境の設定に手を加える必要がある

config/environments/development.rb
Rails.application.configure do
  .
  .
  .
  config.action_mailer.raise_delivery_errors = false

  host = 'example.com' # ここをコピペすると失敗します。自分の環境のホストに変えてください。
  # クラウドIDEの場合は以下をお使いください
  config.action_mailer.default_url_options = { host: host, protocol: 'https' }
  # localhostで開発している場合は以下をお使いください
  # config.action_mailer.default_url_options = { host: host, protocol: 'http' }
  .
  .
  .
end

ホスト名 'example.com' の部分は、各自のdevelopment環境に合わせて変更
例えば、クラウドIDEでは,自分のブラウザのURLをhostへ代入する

host = '<hex string>.vfs.cloud9.us-east-2.amazonaws.com'     # クラウドIDE
config.action_mailer.default_url_options = { host: host, protocol: 'https' }

ローカル環境で開発している場合は、次のように変える

host = 'localhost:3000'                     # ローカル環境
config.action_mailer.default_url_options = { host: host, protocol: 'http' }

httpsが暗号化なしのhttpに変わっていることに注意

developmentサーバーを再起動して,Userメイラーのプレビューファイルの更新をする

test/mailers/previews/user_mailer_preview.rb 自動生成
# Preview all emails at http://localhost:3000/rails/mailers/user_mailer
class UserMailerPreview < ActionMailer::Preview

  # Preview this email at
  # http://localhost:3000/rails/mailers/user_mailer/account_activation
  def account_activation
    UserMailer.account_activation
  end

  # Preview this email at
  # http://localhost:3000/rails/mailers/user_mailer/password_reset
  def password_reset
    UserMailer.password_reset
  end
end

定義したaccount_activationの引数には有効なUserオブジェクトを渡す必要があるため、上のコードはこのままでは動かない。
これを回避するために、user変数が開発用データベースの最初のユーザーになるように定義し
UserMailer.account_activationの引数として渡す

このときuser.activation_tokenの値にも代入している点に注目
メールのテンプレートでは、アカウント有効化のトークンが必要なので、代入は省略不可。
なお、activation_tokenは仮の属性でしかないので、データベースのユーザーはこの値を実際には持っていない。

test/mailers/previews/user_mailer_preview.rb
# Preview all emails at http://localhost:3000/rails/mailers/user_mailer
class UserMailerPreview < ActionMailer::Preview

  # Preview this email at
  # http://localhost:3000/rails/mailers/user_mailer/account_activation
#このURLに接続すると、メールのプレビューがみれる
  def account_activation
    user = User.first
    user.activation_token = User.new_token
    UserMailer.account_activation(user)
  end

  # Preview this email at
  # http://localhost:3000/rails/mailers/user_mailer/password_reset
  def password_reset
    UserMailer.password_reset
  end
end

演習

Railsのプレビュー機能を使って、ブラウザから先ほどのメールを表示してみてください。「Date」の欄にはどんな内容が表示されているでしょうか?
→今日の日付が表示されている

11.2.3 送信メールのテスト

最後に、このメールプレビューのテストも作成

自動生成されたTest
```
test/mailers/user_mailer_test.rb
require 'test_helper'

class UserMailerTest < ActionMailer::TestCase

test "account_activation" do
mail = UserMailer.account_activation
assert_equal "Account activation", mail.subject
assert_equal ["to@example.org"], mail.to
assert_equal ["from@example.com"], mail.from
assert_match "Hi", mail.body.encoded#解説 Hiがメール本文にあるか
end

test "password_reset" do
mail = UserMailer.password_reset
assert_equal "Password reset", mail.subject
assert_equal ["to@example.org"], mail.to
assert_equal ["from@example.com"], mail.from
assert_match "Hi", mail.body.encoded #
end
end
```

assert_matchは正規表現で文字列をテストできる。

assert_match 'foo', 'foobar'      # true  #fooがある
assert_match 'baz', 'foobar'      # false  #bazない
assert_match /\w+/, 'foobar'      # true  #文字ある
assert_match /\w+/, '$#!*+@'      # false #文字ない

これから書くテストではassert_matchメソッドを使って名前、有効化トークン、エスケープ済みメールアドレスがメール本文に含まれているかどうかをテストする
最後にもう1つ小技を紹介

CGI.escape(user.email)

上のメソッドを使うと、テスト用のユーザーのメールアドレスをエスケープすることもできる
※@がなくてもテストできる

test/mailers/user_mailer_test.rb
require 'test_helper'

class UserMailerTest < ActionMailer::TestCase

  test "account_activation" do
    user = users(:michael)
    user.activation_token = User.new_token
    mail = UserMailer.account_activation(user)
    assert_equal "Account activation", mail.subject
    assert_equal [user.email], mail.to
    assert_equal ["noreply@example.com"], mail.from
    assert_match user.name,               mail.body.encoded
    assert_match user.activation_token,   mail.body.encoded
 #account_activation.html.erbでPタグが長いと失敗するので注意
    assert_match CGI.escape(user.email),  mail.body.encoded
  end
end

なお、このテストではまだ失敗する。

上記テストコードでは、fixtureユーザーに有効化トークンを追加している点に注目。
(user.activation_token = のところ)
追加しない場合は、空白になる。

なお、生成されたパスワード設定のテストも削除しているが、のちに戻す。

このテストをパスさせるには、テストファイル内のドメイン名を正しく設定する必要がある。

config/environments/test.rb
Rails.application.configure do
  .
  config.action_mailer.delivery_method = :test
  config.action_mailer.default_url_options = { host: 'example.com' }
#ここは変に変えなくてOK
  .
end

これでテストはパスする

演習

この時点で、テストスイートが green になっていることを確認してみましょう。
→パスしなかったので、テストのコメント注意記載

  assert_match user.activation_token,   mail.body.encoded
 #account_activation.html.erbでPタグが長いと失敗するので注意

日本語で「こちらのリンクをクリックしてください」とか長いとエラーになる

リスト 11.20で使ったCGI.escapeの部分を削除すると、テストが red に変わることを確認してみましょう。
→@マークが邪魔してエラーになる

11.2.4 ユーザーのcreateアクションを更新

あとはユーザー登録を行うcreateアクションに数行追加するだけで、メイラーをアプリケーションで実際に使うことができる

以下のコードでは、登録時のリダイレクトの挙動が変更されている点に注意。
変更前は、ユーザーのプロフィールページにリダイレクト=アカウント有効化を実装するうえでは無意味な動作だったので リダイレクト先をルートURLに変更

app/controllers/users_controller.rb
class UsersController < ApplicationController
  .
  def create
    @user = User.new(user_params)
    if @user.save
      UserMailer.account_activation(@user).deliver_now
      flash[:info] = "Please check your email to activate your account."
      redirect_to root_url
    else
      render 'new'
    end
  end
  .
end

createアクションの挙動を変えたので、テストが失敗する
失敗が発生するテストの行をひとまずコメントアウトする

test/integration/users_signup_test.rb
require 'test_helper'

class UsersSignupTest < ActionDispatch::IntegrationTest

  test "invalid signup information" do
    get signup_path
    assert_no_difference 'User.count' do
      post users_path, params: { user: { name:  "",
                                         email: "user@invalid",
                                         password:              "foo",
                                         password_confirmation: "bar" } }
    end
    assert_template 'users/new'
    assert_select 'div#error_explanation'
    assert_select 'div.field_with_errors'
  end

  test "valid signup information" do
    get signup_path
    assert_difference 'User.count', 1 do
      post users_path, params: { user: { name:  "Example User",
                                         email: "user@example.com",
                                         password:              "password",
                                         password_confirmation: "password" } }
    end
    follow_redirect!
    # assert_template 'users/show'
    # assert is_logged_in?
  end
end

この状態で実際に新規ユーザーとして登録してみると、リダイレクトされてメールが生成される。
ただし、実際にメールが生成されるわけではない

サーバーに以下のようなログが生成される

UserMailer#account_activation: processed outbound mail in 5.1ms
Delivered mail 5d606e97b7a44_28872b106582df988776a@ip-172-31-25-202.mail (3.2ms)
Date: Fri, 23 Aug 2019 22:54:15 +0000
From: noreply@example.com
To: michael@michaelhartl.com
Message-ID: <5d606e97b7a44_28872b106582df988776a@ip-172-31-25-202.mail>
Subject: Account activation
Mime-Version: 1.0
Content-Type: multipart/alternative;
 boundary="--==_mimepart_5d606e97b6f16_28872b106582df98876dd";
 charset=UTF-8
Content-Transfer-Encoding: 7bit


----==_mimepart_5d606e97b6f16_28872b106582df98876dd
Content-Type: text/plain;
 charset=UTF-8
Content-Transfer-Encoding: 7bit

Hi Michael Hartl,

Welcome to the Sample App! Click on the link below to activate your account:

https://0ebe1dc6d40e4a4bb06e0ca7fe138127.vfs.cloud9.us-east-2.
amazonaws.com/account_activations/zdqs6sF7BMiDfXBaC7-6vA/
edit?email=michael%40michaelhartl.com

----==_mimepart_5d606e97b6f16_28872b106582df98876dd
Content-Type: text/html;
 charset=UTF-8
Content-Transfer-Encoding: 7bit

<!DOCTYPE html>
<html>
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    <style>
      /* Email styles need to be inline */
    </style>
  </head>

  <body>
    <h1>Sample App</h1>

<p>Hi Michael Hartl,</p>

<p>
Welcome to the Sample App! Click on the link below to activate your account:
</p>

<a href="https://0ebe1dc6d40e4a4bb06e0ca7fe138127.vfs.cloud9.us-east-2.
amazonaws.com/account_activations/zdqs6sF7BMiDfXBaC7-6vA/
edit?email=michael%40michaelhartl.com">Activate</a>
  </body>
</html>

----==_mimepart_5d606e97b6f16_28872b106582df98876dd--

演習

新しいユーザーを登録したとき、リダイレクト先が適切なURLに変わったことを確認してみましょう。その後、Railsサーバーのログから送信メールの内容を確認してみてください。有効化トークンの値はどうなっていますか?

コンソールを開き、データベース上にユーザーが作成されたことを確認してみましょう。また、このユーザーはデータベース上にはいますが、有効化のステータスがfalseのままになっていることを確認してください。

>> user = User.find(101)
>> user.activated?
=> false

11.3 アカウントを有効化する

メールが生成できたら、今度はAccountActivationsコントローラのeditアクションを書いていく
アクションへのテストを書き、しっかりとテストできていることが確認できたら、AccountActivationsコントローラからUserモデルにコードを移していく作業(リファクタリング)にも取り掛かっていく

11.3.1 authenticated?メソッドの抽象化

有効化トークンとメールはそれぞれ
params[:id]
params[:email]で参照できる

以下のコードで
パスワードのモデルと記憶トークンで学んだことを元に、次のようなコードでユーザーを検索して認証することにする。

user = User.find_by(email: params[:email])
if user && user.authenticated?(:activation, params[:id])

ただし、このメソッドは記憶トークン用なので今は正常に動作しない

# トークンがダイジェストと一致したらtrueを返す
def authenticated?(remember_token)
  return false if remember_digest.nil?
  BCrypt::Password.new(remember_digest).is_password?(remember_token)
end

remember_digestはUserモデルの属性なので、モデル内では次のように書き換えることができる

self.remember_digest

今回は、上のコードのrememberの部分をどうにかして変数として扱いたい
つまり、次のコード例のように、状況に応じて呼び出すメソッドを切り替えたい

self.FOOBAR_digest

これから実装するauthenticated?メソッドでは、受け取ったパラメータに応じて呼び出すメソッドを切り替える手法を使う

この手法を「メタプログラミング」と呼ぶ
メタプログラミングを一言で言うと「プログラムでプログラムを作成する」こと

ここで重要なのは、sendメソッドの強力な機能
このメソッドは、渡されたオブジェクトに「メッセージを送る」ことによって、呼び出すメソッドを動的に決めることができる

$ rails console
>> a = [1, 2, 3]#aという配列を定義
>> a.length#aの配列の数
=> 3
>> a.send(:length) #aオブジェクトにsend(:length(シンボル))を送る
=> 3               #a.lengthと等価に
>> a.send("length")#aオブジェクトにsend("length"(文字列))を送る
=> 3              #a.lengthと等価に

このときsendを通して渡したシンボル:lengthや文字列"length"は、いずれもlengthメソッドと同じ結果になった。
つまり、どちらもオブジェクトにlengthメソッドを渡しているため、等価。

もう1つ例

>> user = User.first        #userに最初のUserインスタンスを代入
>> user.activation_digest   #userのactivation_digestカラムの値
=> "$2a$10$4e6TFzEJAVNyjLv8Q5u22ensMt28qEkx0roaZvtRcp6UZKRM6N9Ae"
>> user.send(:activation_digest)#userに:activation_digestシンボルを送信
=> "$2a$10$4e6TFzEJAVNyjLv8Q5u22ensMt28qEkx0roaZvtRcp6UZKRM6N9Ae"#同じ結果
>> user.send("activation_digest")
=> "$2a$10$4e6TFzEJAVNyjLv8Q5u22ensMt28qEkx0roaZvtRcp6UZKRM6N9Ae"
>> attribute = :activation
>> user.send("#{attribute}_digest")     #文字列の式展開で[変数_digest]ができた
=> "$2a$10$4e6TFzEJAVNyjLv8Q5u22ensMt28qEkx0roaZvtRcp6UZKRM6N9Ae"

最後の例では、シンボル:activationと等しいattribute変数を定義し、文字列の式展開(interpolation)を使って引数を正しく組み立ててから、sendに渡している。
文字列'activation'でも同じことができるが、Rubyではシンボルを使う方が一般的。

"#{attribute}_digest""activation_digest"

sendメソッドの動作原理がわかったので、この仕組みを利用してauthenticated?メソッドを書き換える

def authenticated?(remember_token)
  digest = self.send("remember_digest")
  return false if digest.nil?
  BCrypt::Password.new(digest).is_password?(remember_token)
end

上のコードの各引数を一般化し、文字列の式展開も利用すると、次のようなコードになる。

def authenticated?(attribute, token)
  digest = self.send("#{attribute}_digest")
  return false if digest.nil?
  BCrypt::Password.new(digest).is_password?(token)
end

他の認証でも使えるように、上では2番目の引数tokenの名前を変更して一般化している点に注意
また、このコードはモデル内にあるのでselfは省略可能
最終的にRubyらしく書かれたコードは、以下

def authenticated?(attribute, token)
  digest = send("#{attribute}_digest")
  return false if digest.nil?
  BCrypt::Password.new(digest).is_password?(token)
end

ここまでできれば、次のように呼び出すことでauthenticated?の振る舞いができる

user.authenticated?(:remember, remember_token)

以上の説明を実際のUserモデルに適用した、抽象化したauthenticated?メソッド

app/models/user.rb
  # トークンがダイジェストと一致したらtrueを返す
  def authenticated?(attribute, token)
    digest = send("#{attribute}_digest")
    return false if digest.nil?
    BCrypt::Password.new(digest).is_password?(token)
  end

この時点ではテストスイートは red

テストが失敗する理由は、current_userメソッドとnilダイジェストのテストの両方で、authenticated?が古いままで、引数も2つではなくまだ1つのままだから

これを解消するため、両者を更新して、新しい一般的なメソッドを使うようにする

app/helpers/sessions_helper.rb
module SessionsHelper
  .
  .
  .
  # 現在ログイン中のユーザーを返す(いる場合)
  def current_user
    if (user_id = session[:user_id])
      @current_user ||= User.find_by(id: user_id)
    elsif (user_id = cookies.signed[:user_id])
      user = User.find_by(id: user_id)
      if user && user.authenticated?(:remember, cookies[:remember_token])
        log_in user
        @current_user = user
      end
    end
  end
test/models/user_test.rb
require 'test_helper'

class UserTest < ActiveSupport::TestCase

  def setup
    @user = User.new(name: "Example User", email: "user@example.com",
                     password: "foobar", password_confirmation: "foobar")
  end
  .
  .
  .
  test "authenticated? should return false for a user with nil digest" do
    assert_not @user.authenticated?(:remember, '')
  end
end

上のような変更を加えると、テストは green に変わる

演習

コンソール内で新しいユーザーを作成してみてください。新しいユーザーの記憶トークンと有効化トークンはどのような値になっているでしょうか? また、各トークンに対応するダイジェストの値はどうなっているでしょうか?

irb(main):002:0> user=User.create(name:"kiyoma",email:"aaa@com.com",password:"123456",
=> #<User id: 101, name: "kiyoma", email: "aaa@com.com", created_at: "2021-03-01 ...
irb(main):004:0> user.remember_token
=> nil
irb(main):005:0> user.activation_token
=> "5daKKPOcrSWPBx5Dv_tY7Q"
irb(main):006:0> user.remember_digest
=> nil
irb(main):007:0> user.activation_digest
=> "$2a$12$vMNDNH2/c4FhHbxzit/7MOrSa4fvvSKvkUlPWpx/fDe1Cyny571Tq"

リスト 11.26で抽象化したauthenticated?メソッドを使って、先ほどの各トークン/ダイジェストの組み合わせで認証が成功することを確認してみましょう。

irb(main):008:0> user.remember_token = User.new_token
=> "p0dj7V57U5ukEawso3Kk1g"
irb(main):009:0> user.update_attribute(:remember_digest, User.digest(user.remember_tok
en))
=> true
irb(main):010:0> user.authenticated?(:remember,user.remember_token)
=> true

11.3.2 editアクションで有効化

authenticated?の準備ができたので、やっとeditアクションを書く準備ができた。
editアクションは、paramsハッシュで渡されたメールアドレスに対応するユーザーを認証
ユーザーが有効であることを確認する中核は、次の部分

if user && !user.activated? && user.authenticated?(:activation, params[:id])

!user.activated?という記述に注目。このコードは、既に有効になっているユーザーを誤って再度有効化しないために必要。
正当であろうとなかろうと、有効化が行われるとユーザーはログイン状態になる。
もしこのコードがなければ、攻撃者がユーザーの有効化リンクを後から盗みだしてクリックするだけで、本当のユーザーとしてログインできてしまう

上の論理値に基いてユーザーを認証するには、ユーザーを認証してからactivated_atタイムスタンプを更新する必要がある

user.update_attribute(:activated,    true)
user.update_attribute(:activated_at, Time.zone.now)

上のコードをeditアクションで使う

app/controllers/account_activations_controller.rb
class AccountActivationsController < ApplicationController

  def edit
    user = User.find_by(email: params[:email])
    if user && !user.activated? && user.authenticated?(:activation, params[:id])
      user.update_attribute(:activated,    true)
      user.update_attribute(:activated_at, Time.zone.now)
      log_in user
      flash[:success] = "Account activated!"
      redirect_to user
    else
      flash[:danger] = "Invalid activation link"
      redirect_to root_url
    end
  end
end

ユーザーの有効化が使われるためには、ユーザーが有効である場合にのみログインできるようにログイン方法を変更する必要がある。
つまりuser.activated?がtrueの場合にのみログインを許可し、そうでない場合はルートURLにリダイレクトしてwarningで警告を表示する

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])
      if user.activated?
        log_in user
        params[:session][:remember_me] == '1' ? remember(user) : forget(user)
        redirect_back_or user
      else
        message  = "Account not activated. "
        message += "Check your email for the activation link."
        flash[:warning] = message
        redirect_to root_url
      end
    else
      flash.now[:danger] = 'Invalid email/password combination'
      render 'new'
    end
  end

  def destroy
    log_out if logged_in?
    redirect_to root_url
  end
end

演習

コンソールから、11.2.4で生成したメールに含まれているURLを調べてみてください。URL内のどこに有効化トークンが含まれているでしょうか?
先ほど見つけたURLをブラウザに貼り付けて、そのユーザーの認証に成功し、有効化できることを確認してみましょう。また、有効化ステータスがtrueになっていることをコンソールから確認してみてください。

11.3.3 有効化のテストとリファクタリング

アカウント有効化の統合テストを追加していく

test/integration/users_signup_test.rb
require 'test_helper'

class UsersSignupTest < ActionDispatch::IntegrationTest

  def setup
    ActionMailer::Base.deliveries.clear#deliveriesを初期化 解説
  end

  test "invalid signup information" do
    get signup_path
    assert_no_difference 'User.count' do
      post users_path, params: { user: { name:  "",
                                         email: "user@invalid",
                                         password:              "foo",
                                         password_confirmation: "bar" } }
    end
    assert_template 'users/new'
    assert_select 'div#error_explanation'
    assert_select 'div.field_with_errors'
  end

  test "valid signup information with account activation" do
    get signup_path
    assert_difference 'User.count', 1 do
      post users_path, params: { user: { name:  "Example User",
                                         email: "user@example.com",
                                         password:              "password",
                                         password_confirmation: "password" } }
    end
    assert_equal 1, ActionMailer::Base.deliveries.size#解説
    user = assigns(:user)
    assert_not user.activated?
    # 有効化していない状態でログインしてみる
    log_in_as(user)
    assert_not is_logged_in?
    # 有効化トークンが不正な場合
    get edit_account_activation_path("invalid token", email: user.email)
    assert_not is_logged_in?
    # トークンは正しいがメールアドレスが無効な場合
    get edit_account_activation_path(user.activation_token, email: 'wrong')
    assert_not is_logged_in?
    # 有効化トークンが正しい場合
    get edit_account_activation_path(user.activation_token, email: user.email)
    assert user.reload.activated?
    follow_redirect!
    assert_template 'users/show'
    assert is_logged_in?
  end
end

リスト 11.33のコードは分量が多いが、本当に重要な部分は次の1行

assert_equal 1, ActionMailer::Base.deliveries.size

上のコードは、配信されたメッセージがきっかり1つであるかどうかを確認する。
配列deliveriesは変数なので、setupメソッドでこれを初期化しておかないと、並行して行われる他のテストでメールが配信されたときにエラーが発生してしまう

assignsメソッドは、対応するアクション内のインスタンス変数にアクセスできるようになる。
例えば、Usersコントローラのcreateアクションでは@userというインスタンス変数が定義されているが、テストでassigns(:user)と書くとこのインスタンス変数にアクセスできるようになる。

テストがパスする

テストができたので、ユーザー操作の一部をコントローラからモデルに移動するというリファクタリングを行う準備ができた。
ここでは特に、activateメソッドを作成し、ユーザーの有効化属性を更新し、send_activation_emailメソッドを作成して有効化メールを送信する

app/models/user.rb
class User < ApplicationRecord

  # アカウントを有効にする
  def activate
    update_attribute(:activated,    true)
    update_attribute(:activated_at, Time.zone.now)
  end

  # 有効化用のメールを送信する
  def send_activation_email
    UserMailer.account_activation(self).deliver_now #メールを送る
  end
app/controllers/users_controller.rb
  def create
    @user = User.new(user_params)
    if @user.save
      @user.send_activation_email#定義したメールを送るメソッド
      flash[:info] = "Please check your email to activate your account."
      redirect_to root_url
    else
      render 'new'
    end
  end
app/controllers/account_activations_controller.rb
class AccountActivationsController < ApplicationController

  def edit
    user = User.find_by(email: params[:email])
    if user && !user.activated? && user.authenticated?(:activation, params[:id])
      user.activate#定義したアカウントを有効にするメソッド
      log_in user
      flash[:success] = "Account activated!"
      redirect_to user
    else
      flash[:danger] = "Invalid activation link"
      redirect_to root_url
    end
  end
end

user.rbではuser.という記法を使っていない。Userモデルにはそのような変数はないので、これがあるとエラーになる

-user.update_attribute(:activated,    true)
-user.update_attribute(:activated_at, Time.zone.now)
+update_attribute(:activated,    true)
+update_attribute(:activated_at, Time.zone.now)

(userをselfに切り替えるという手もあるが、selfはモデル内では必須ではない)

テストもパスするのでリファクタリングOK

演習

リスト 11.35にあるactivateメソッドはupdate_attributeを2回呼び出していますが、これは各行で1回ずつデータベースへ問い合わせしていることになります。リスト 11.39に記したテンプレートを使って、update_attributeの呼び出しを1回のupdate_columns呼び出しにまとめてみましょう。これでデータベースへの問い合わせが1回で済むようになります(注意!update_columnsは、モデルのコールバックやバリデーションが実行されない点がupdate_attributeと異なります)。また、変更後にテストを実行し、 green になることも確認してください。

 # アカウントを有効にする
    def activate
        update_columns(activated: true, activated_at: Time.zone.now)
    end

現在は、/usersのユーザーindexページを開くとすべてのユーザーが表示され、/users/:idのようにIDを指定すると個別のユーザーを表示できます。しかし考えてみれば、有効でないユーザーは表示する意味がありません。そこで、リスト 11.40のテンプレートを使って、この動作を変更してみましょう9 。なお、ここで使っているActive Recordのwhereメソッドについては、13.3.3でもう少し詳しく説明します。

 def index
    @users = User.where(activated: true).paginate(page: params[:page])
  end

  def show
    @user = User.find(params[:id])
    redirect_to root_url and return unless @user.activated?
  end

ここまでの演習課題で変更したコードをテストするために、/users と /users/:id の両方に対する統合テストを作成してみましょう。

users.yml
non_activated:
 name: Non Activated
 email: non_activated@example.gov
 password_digest: <%= User.digest('password') %>
 activated: false
 activated_at: <%= Time.zone.now %>
users_controller_test.rb
 test "should not allow the not activated attribute" do
    log_in_as (@non_activated_user)                                             # 非有効化ユーザーでログイン
    assert_not @non_activated_user.activated?                                   # 有効化でないことを検証
    get users_path                                                              # /usersを取得
    assert_select "a[href=?]", user_path(@non_activated_user), count: 0         # 非有効化ユーザーが表示されていないことを確認
    get user_path(@non_activated_user)                                          # 非有効化ユーザーidのページを取得
    assert_redirected_to root_url                                               # ルートurlにリダイレクトされればtrue
  end

11.4 本番環境でのメール送信

ここまでの実装で、development環境におけるアカウント有効化の流れは完成した。次は、本番環境で実際にメールを送信できるようにしてみる
まずは無料のサービスを利用してメール送信の設定を行い、続いてアプリケーションの設定とデプロイを行う

本番環境からメール送信するために、「Mailgun」というHerokuアドオンを利用してアカウントを検証
本チュートリアルでは、「starter」というプランを使う

アプリケーションでMailgunアドオンを使うには、production環境のSMTPに情報を記入する必要がある
を自分のHerokuのURLに設定し、利用する

config/environments/production.rb
Rails.application.configure do
  .
  .
  .
  config.action_mailer.raise_delivery_errors = true
  config.action_mailer.delivery_method = :smtp
  host = '<your heroku app>.herokuapp.com'
  config.action_mailer.default_url_options = { host: host }
  ActionMailer::Base.smtp_settings = {
    :port           => ENV['MAILGUN_SMTP_PORT'],
    :address        => ENV['MAILGUN_SMTP_SERVER'],
    :user_name      => ENV['MAILGUN_SMTP_LOGIN'],
    :password       => ENV['MAILGUN_SMTP_PASSWORD'],
    :domain         => host,
    :authentication => :plain,
  }

この時点で、Gitのトピックブランチをmasterにマージ

$ rails test
$ git add -A
$ git commit -m "Add account activation"
$ git checkout master
$ git merge account-activation

続いてリモートリポジトリにプッシュし、Herokuにデプロイ

$ rails test
$ git push
$ git push heroku
$ heroku run rails db:migrate

MailgunのHerokuアドオンを追加するために、次のコマンドを実行

$ heroku addons:create mailgun:starter

注: herokuコマンドのバージョンが古いとここで失敗するかも。その場合は、Heroku Toolbeltを使って最新版に更新するか、次の古い文法のコマンドを試す

$ heroku addons:add mailgun:starter

Herokuの環境変数を表示したい場合は、次のコマンドを実行

$ heroku config:get MAILGUN_SMTP_LOGIN
$ heroku config:get MAILGUN_SMTP_PASSWORD

メール設定にはMailgunアカウントのuser_nameとpassword設定を記入する行もあるが、そこには記入せず、必ず環境変数「ENV」に設定するよう十分注意
本番運用するアプリケーションでは、暗号化されていないIDやパスワードのような重要なセキュリティ情報は「絶対に」ソースコードに直接書き込まない。そのような情報は環境変数に記述し、そこからアプリケーションに読み込む。
今回の場合、そうした変数はMailgunアドオンが自動的に設定してくれる

最後に、受信メールの認証を行う
以下のコマンドを打つと、Mailgun ダッシュボードのURLが表示されるのでブラウザで開く

$ heroku addons:open mailgun

MailGun公式ドキュメントに従い、受信するメールアドレスを認証していく。
画面左側の「Sending」→「Domains」のリストにある「sandbox」で始まるサンドボックスドメインを選択。
画面右側の「Authorized Recipients」から受信メールアドレスを認証し、本番環境でのメール送信準備は完了。

HerokuへのデプロイとMailgunのアドオンが完了したら、先ほど受信認証したメールアドレスを使って、production(本番)環境でユーザー登録を行ってみる。
受信したメールに記されているメールをクリックすると、期待通りアカウントの有効化に成功するはず

演習

実際に本番環境でユーザー登録をしてみましょう。ユーザー登録時に入力したメールアドレスにメールは届きましたか?
メールを受信できたら、実際にメールをクリックしてアカウントを有効化してみましょう。また、Heroku上のログを調べてみて、有効化に関するログがどうなっているのか調べてみてください。ヒント: ターミナルからheroku logsコマンドを実行してみましょう。

herokuに接続できないため、(jqueryエラー中)のためスキップ

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

【Rails】世界一簡単なcreated.at(updated.at)日本語表記

はじめに

railsではロンドン?の時刻表示がデフォになっていますが、簡単に日本語表記で表すことができます。

本記事では簡単にやる方法を簡潔に書きます。

開発環境

  • Ruby2.6.6

実際のコード

まずは、application.rbに以下を記述します。

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

次に、config/locateの中にja.ymlのファイルを作り、以下を記述します。

config/locate/ja.yml
ja:
  datetime:
    distance_in_words:

      less_than_x_minutes:
        one:  "1秒"
        other: "%{count}秒"
      x_minutes:
        one:   "1分"
        other: "%{count}分"
      about_x_hours:
        one:   "約1時間"
        other: "約%{count}時間"
      x_days:
        one:   "1日"
        other: "%{count}日"
      about_x_months:
        one:   "約1ヶ月"
        other: "約%{count}ヶ月"
      x_months:
        one:   "1ヶ月"
        other: "%{count}ヶ月"
      about_x_years:
        one:   "約1年"
        other: "約%{count}年"
      over_x_years:
        one:   "1年以上"
        other: "%{count}年以上"

あとは、表示させたいviewに追記部分を加えるだけ!

index.html.erb
<h1>sample</h1>
<h3>Tweet一覧</h3>
<%= link_to "新規投稿へ", tweets_new_path %>
<div class="tweets-container">
  <% @tweets.each do |t| %>
    <div class="tweet">
      <%= t.body %>
         #ここから
      <time datetime="<%= t.created_at %>">
      <%= time_ago_in_words(t.created_at) %>前
      </time>  
     #ここまで
    </div>
  <% end %>
</div>

最後に

世界一簡単にRailsでcreated.atやupdated.atをTwitterのように(〇分前、〇日前)日本語表記する方法についてご紹介しました。

簡単に実装可能なのでぜひ試してみてみてくださいね!

参考記事:https://qiita.com/bellx2/items/30906a7832ef4ff4c886

最後まで読んでいただき、ありがとうございました。

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

[Ruby on Rails] No template for interactive request

なんか色々と長い内容のエラーに出くわした。
備忘録と言うよりは打つ間違いに気をつけろ!ってことで
自身への反省をこめて書きます。

No template

なら、いつもの記述間違いか?ってなる所、
今回のエラーはいつもより長かった...

  

エラー内容↓

No template for interactive request

CatsController#show is missing a template for request formats: text/html
NOTE!


(インタラクティブリクエスト用のテンプレートはありません
CatsController#showにリクエスト形式のテンプレートがありません:text / html
注意!)
 

解決方法?

なのかは、分からないがshow.htmlのファイルを作った時にコンマが一個多かったorz
show..html.erb となっていた。
ただそれだけ。
 

まとめ

たまたま早期発見できたものの、今後templateエラーが出たら
コードとファイルの両方を細かくチェックするべきだと実感しました。

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