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

ラベル機能実装後、検索のテストをしていたらPG::AmbiguousColumn: ERROR

ラベルでの検索も実装し、そのSystem Spec中にタイトルのエラーが発生

System Spec中に……と書いてあるものの、テストを書き色んなパターンを試した結果、見落としていたエラーを発見した、というのが正しいです。

前提

タスク管理アプリを作成中。
タスク名、ステータス(未着手、着手、完了)での検索は実装済み。
今回、タスクにラベル(タグのようなもの)を付けられるようにし、そのラベルでもタスクを検索できるように実装。
その機能のテストを書いている時に起こったエラー。

環境

Rails 5.2.3
Ruby 2.6.5

テーブル構成

TaskテーブルとLabelテーブルがあって、多対多を実現するためのLabelingという中間テーブルがある。

schema.rb(一部略)
  create_table "labelings", force: :cascade do |t|
    t.bigint "task_id"
    t.bigint "label_id"
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
    t.index ["label_id"], name: "index_labelings_on_label_id"
    t.index ["task_id"], name: "index_labelings_on_task_id"
  end

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

  create_table "tasks", force: :cascade do |t|
    t.string "name", null: false
    t.text "content", null: false
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
    t.datetime "deadline", null: false
    t.integer "priority", default: 0, null: false
    t.integer "status", default: 0, null: false
    t.bigint "user_id"
    t.index ["name"], name: "index_tasks_on_name"
    t.index ["user_id"], name: "index_tasks_on_user_id"
  end

検索のロジック

モデルにscopeを書いて実装。

app/models/task.rb(一部略)
  scope :default_order, -> { order(created_at: :desc) } # 作成日を降順に並べるscopeの名前を変更し分かりやすく
  scope :sort_deadline, -> { order(deadline: :desc) }
  scope :search_with_name, -> (name) {
    return if name.blank?
    where('name LIKE ?', "%#{name}%")
  }
  scope :search_with_status, -> (status) {
    return if status.blank?
    where(status: status)
  }

  scope :search_with_label, -> (label) {
    return if label.blank?
    joins(:labels).where('labels.id = ?', label) # where(id: label)じゃだめ
  }
app/controllers/tasks_controller.rb
  def index
    @tasks = current_user.tasks

    @tasks = @tasks
    .search_with_name(params[:name])
    .search_with_status(params[:status])
    .search_with_label(params[:label])

    # 中略
  end

検索を実装してるviewのコード

app/views/tasks/index.html.slim
= form_with(method: :get, local: true, url: tasks_path) do |f|
  = f.label :name_search, t('.name_search'), value: params[:name]
  = f.search_field :name, placeholder: t('.name_search'), class: 'form-control'

  = f.label :status_search, t('.status_search'), value: params[:status]
  = f.select :status, Task.enum_options_for_select(:status), class: 'form_control', include_blank: true, selected: ''
  / = f.select :status, [Task.human_attribute_name('status.waiting'), Task.human_attribute_name('status.working'), Task.human_attribute_name('status.completed')], class: 'form-control', include_blank: true, selected: ''
  = f.label :label_search, t('.label_search'), value: params[:label]
  = f.select :label, Label.pluck(:name, :id), { include_blank: true }

  = f.hidden_field :search, value: true
  div.search_button = f.submit(t('.search'), class: 'btn btn-secondary')

実際のページはこんな感じ

bdee4b6507d645ce831fc2bddd61b40c.png

ラベルでの検索も問題なく実装できていた。ように見えていた。

System Specを書いていると……

ラベルでの検索機能のSystem Spec。ラベルのみでの検索は無事テストを書けたので、タスク名、ステータス、ラベルの3つ全てで検索をするテストを書いてみた。

spec/system/task_spec.rb
context 'タスク名、ステータス、ラベルの全てで検索した場合' do
  before do
    fill_in 'タスク名で検索', with: "TEST_TASK"
    select '未着手', from: :status
    select '勉強', from: :label
    click_button '検索'
  end

  example 'マッチしたタスクのみが表示される' do
    within ('tbody') do
      expect(page).to have_text "TEST_TASK", "未着手", "勉強"
    end
  end
end

FactoryBotで2つのデータを用意している。検索した結果、このタスクのみが表示されるはずだった。

スクリーンショット 2020-04-04 21.44.22.png

しかしここで問題発生。
テストが失敗してエラー時のスクリーンショットも見たことないような画面に……。

failures_r_spec_example_groups_tasks_nested_nested_4_nested_マッチしたタスクのみが表示される_200.png

コンソールのログを見てみると、

Failures:

  1) Tasks タスク一覧画面 タスクの検索機能 タスク名、ステータス、ラベルの全てで検索した場合 マッチしたタスクのみが表示される
     Failure/Error: - @tasks.each do |task|

     ActionView::Template::Error:
       PG::AmbiguousColumn: ERROR:  column reference "name" is ambiguous
       LINE 1: ...ngs"."label_id" WHERE "tasks"."user_id" = $1 AND (name LIKE ...
                                                                    ^
       : SELECT  "tasks".* FROM "tasks" INNER JOIN "labelings" ON "labelings"."task_id" = "tasks"."id" INNER JOIN "labels" ON "labels"."id" = "labelings"."label_id" WHERE "tasks"."user_id" = $1 AND (name LIKE '%TEST_TASK%') AND "tasks"."status" = $2 AND (labels.id = '1') ORDER BY "tasks"."created_at" DESC LIMIT $3 OFFSET $4

PG::AmbiguousColumn: ERRORで検索。

ActionView::Template::Error:PG::AmbiguousColumn: ERROR…でRSpecが失敗して困った。 - Qiita

Taskモデルに、TaskモデルとアソシエーションしているLabelモデルをjoinsで結合しています。
それによりorder("created_at desc")が、tasksテーブルにあるcreated_atを指しているのか、labelsテーブルにあるcreated_atを指しているのか、曖昧( = AmbiguousCoulumn )になっていることがエラーの原因でした。

これを見てもう一度コンソールのログを見直すと、column reference "name" is ambiguousと文句言われてる。そこで気付いた。

LabelテーブルにもTaskテーブルにもnameカラムがあることに。
その上、検索を実装してるscopeの中身を見ると、

app/models/task.rb
  scope :search_with_name, -> (name) {
    return if name.blank?
    where('name LIKE ?', "%#{name}%")
  }

と、'name LIKE?'だけで何のテーブルかを指定していないのが原因そうだ。

解決法

where('name LIKE ?', "%#{name}%")を、where('tasks.name LIKE ?', "%#{name}%")に。

何のnameかを指定してあげる。

app/models/task.rb
scope :search_with_name, -> (name) {
    return if name.blank?
    where('tasks.name LIKE ?', "%#{name}%")
  }

これで無事テストも通ったし、タスク名、ステータス、ラベル全てでの検索をしても問題なく動くようになった。

関連付けしたテーブルで同じカラム名がある場合は注意する!

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

[*Rails*] deviseの使い方(rails6版)

はじめに

Rails6での再再検証版です。
以下のRails5のときのものをRails6で実行し、一部変更になっている部分を反映したバージョンです。
[Rails] deviseの使い方(rails5版)

【前回】
rails : 5.0.0.1
ruby  : 2.3.1
devise: 4.2.0

↓

【今回】
rails : 6.0.2.2
ruby  : 2.7.1
devise: 4.7.1

deviseとは

ユーザー登録して、送られてきたメールのリンクをクリックして本登録して、ログインして、パスワード忘れたら再設定して、何回もログインミスったらアカウントロックして…などといった認証系アプリに必要な機能を簡単に追加できる便利なgemです。

1. gemのインストール

1. プロジェクトの作成

新しいプロジェクトを作ります。

$ rails new devise_rails6
$ cd devise_rails6/

2. Gemfileの編集とインストール

以下ファイルにdeviseomniauth-twitterを追加します。

source 'https://rubygems.org'

# (省略)...

# Devise
gem 'devise'
gem 'omniauth-twitter'

gemをインストール。

$ bundle install

2. deviseの設定

devise関連ファイルを追加。
すると以下のような英文が表示されます。1から4まで順番に見ていきます。

$ rails generate devise:install

Running via Spring preloader in process 30440
      create  config/initializers/devise.rb
      create  config/locales/devise.en.yml
===============================================================================

Some setup you must do manually if you haven't yet:

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

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

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

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

       root to: "home#index"

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

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

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

       rails g devise:views

===============================================================================

1. デフォルトURLの指定

英文の例に書いてあった config.action_mailer.default_url_options = { host: 'localhost', port: 3000 } を以下のファイルに追加しました。
config.action_mailer.default_url_options = { host: 'localhost:3000' } でもOKです。

config/environments/development.rb
Rails.application.configure do
  # Settings specified here will take precedence over those in config/application.rb.

  # (省略)...

  # mailer setting
  config.action_mailer.default_url_options = { host: 'localhost', port: 3000 }
  # config.action_mailer.default_url_options = { host: 'localhost:3000' }
end

2. root_urlの指定

1番で指定した http://localhost:3000/ にアクセスした際に表示されるページを指定します。
現状ページは1つも作っていないため、先に追加します。

Pagesコントローラーと、indexページとshowページを追加してみます。

$ rails g controller Pages index show

routes.rb に以下を指定します。

config/routes.rb
Rails.application.routes.draw do
  root 'pages#index'
  get 'pages/show'
  # For details on the DSL available within this file, see https://guides.rubyonrails.org/routing.html
end

3. flashメッセージの設定

ログインした時などに上の方に「ログインしました」みたいなメッセージが出るようにします。
以下のファイルの <body> タグのすぐ下に指定されたタグを挿入します。

app/views/layouts/application.html.erb
<!DOCTYPE html>
<html>
  <head>
    <title>DeviseRails6</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' %>
  </head>

  <body>
    <!-- ここに追加 -->
    <p class="notice"><%= notice %></p>
    <p class="alert"><%= alert %></p>

    <%= yield %>
  </body>
</html>

4. deviseのViewを生成

deviseの導入で追加されるViewは、以下のコマンドを実行しなければデザインを変更できないので、デザインをカスタマイズするためにも実行します。

$ rails g devise:views

すると以下の様なファイルが生成されます。

app/views/devise/shared/_error_messages.html.erb (エラーメッセージ用パーシャル)
app/views/devise/shared/_links.html.erb (リンク用パーシャル)
app/views/devise/confirmations/new.html.erb (認証メールの再送信画面)
app/views/devise/passwords/edit.html.erb (パスワード変更画面)
app/views/devise/passwords/new.html.erb (パスワードを忘れた際、メールを送る画面)
app/views/devise/registrations/edit.html.erb (ユーザー情報変更画面)
app/views/devise/registrations/new.html.erb (ユーザー登録画面)
app/views/devise/sessions/new.html.erb (ログイン画面)
app/views/devise/unlocks/new.html.erb (ロック解除メール再送信画面)
app/views/devise/mailer/confirmation_instructions.html.erb (メール用アカウント認証文)
app/views/devise/mailer/email_changed.html.erb (メール用メールアドレス変更完了文)
app/views/devise/mailer/password_change.html.erb (メール用パスワード変更完了文)
app/views/devise/mailer/reset_password_instructions.html.erb (メール用パスワードリセット文)
app/views/devise/mailer/unlock_instructions.html.erb (メール用ロック解除文)

3. Userモデルの設定

1. Userモデルの作成

以下を実行。

$ rails g devise User

マイグレーションファイルができます。
デフォルトではこんな感じになってます。

db/migrate/20200404040003_devise_create_users.rb
# frozen_string_literal: true

class DeviseCreateUsers < ActiveRecord::Migration[6.0]
  def change
    create_table :users do |t|
      ## Database authenticatable
      t.string :email,              null: false, default: ""
      t.string :encrypted_password, null: false, default: ""

      ## Recoverable
      t.string   :reset_password_token
      t.datetime :reset_password_sent_at

      ## Rememberable
      t.datetime :remember_created_at

      ## Trackable
      # t.integer  :sign_in_count, default: 0, null: false
      # t.datetime :current_sign_in_at
      # t.datetime :last_sign_in_at
      # t.string   :current_sign_in_ip
      # t.string   :last_sign_in_ip

      ## Confirmable
      # t.string   :confirmation_token
      # t.datetime :confirmed_at
      # t.datetime :confirmation_sent_at
      # t.string   :unconfirmed_email # Only if using reconfirmable

      ## Lockable
      # t.integer  :failed_attempts, default: 0, null: false # Only if lock strategy is :failed_attempts
      # t.string   :unlock_token # Only if unlock strategy is :email or :both
      # t.datetime :locked_at


      t.timestamps null: false
    end

    add_index :users, :email,                unique: true
    add_index :users, :reset_password_token, unique: true
    # add_index :users, :confirmation_token,   unique: true
    # add_index :users, :unlock_token,         unique: true
  end
end

Userモデルはこんな感じになっています。
デフォルトでは database_authenticatableregisterablerecoverablerememberablevalidatable が使えるようになっています。

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

2. deviseモジュール概要

各モジュールについて以下に紹介します。

機能 概要
database_authenticatable サインイン時にユーザーの正当性を検証するためにパスワードをハッシュ化してDBに登録します。認証方法としてはPOSTリクエストかHTTP Basic認証が使えます。
registerable 登録処理を通してユーザーをサインアップします。また、ユーザーに自身のアカウントを編集したり削除できるようにします。
recoverable パスワードをリセットし、それを通知します。
rememberable 保存されたcookieから、ユーザーを記憶するためのトークンを生成・削除します。
trackable サインイン回数や、サインイン時間、IPアドレスを記録します。
validatable Emailやパスワードのバリデーションを提供します。独自に定義したバリデーションを追加することもできます。
confirmable メールに記載されているURLをクリックして本登録を完了する、といったよくある登録方式を提供します。また、サインイン中にアカウントが認証済みかどうかを検証します。
lockable 一定回数サインインを失敗するとアカウントをロックします。ロック解除にはメールによる解除か、一定時間経つと解除するといった方法があります。
timeoutable 一定時間活動していないアカウントのセッションを破棄します。
omniauthable intridea/omniauthをサポートします。TwitterやFacebookなどの認証を追加したい場合はこれを使用します。

3. Userモデルの編集

今回はデフォルトではないものも触ってみたいと思うので全部入れてみます。
Twitter認証を使うのでTwitterを指定しています。

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

4. マイグレーションファイルの編集

上に合わせて使用モジュールに対応する部分のコメントアウトを外します。今回は全部使うので全部外します。
全部を使用しない場合は 3. で使うことにしたものに関連する項目だけコメントアウトを外してください。

db/migrate/20200404040003_devise_create_users.rb
# frozen_string_literal: true

class DeviseCreateUsers < ActiveRecord::Migration[6.0]
  def change
    create_table :users do |t|
      ## Database authenticatable
      t.string :email,              null: false, default: ""
      t.string :encrypted_password, null: false, default: ""

      ## Recoverable
      t.string   :reset_password_token
      t.datetime :reset_password_sent_at

      ## Rememberable
      t.datetime :remember_created_at

      ## Trackable
      t.integer  :sign_in_count, default: 0, null: false
      t.datetime :current_sign_in_at
      t.datetime :last_sign_in_at
      t.string   :current_sign_in_ip
      t.string   :last_sign_in_ip

      ## Confirmable
      t.string   :confirmation_token
      t.datetime :confirmed_at
      t.datetime :confirmation_sent_at
      t.string   :unconfirmed_email # Only if using reconfirmable

      ## Lockable
      t.integer  :failed_attempts, default: 0, null: false # Only if lock strategy is :failed_attempts
      t.string   :unlock_token # Only if unlock strategy is :email or :both
      t.datetime :locked_at


      t.timestamps null: false
    end

    add_index :users, :email,                unique: true
    add_index :users, :reset_password_token, unique: true
    add_index :users, :confirmation_token,   unique: true
    add_index :users, :unlock_token,         unique: true
  end
end

5. omniauth-twitter用カラムの追加

ついでに omniauth-twitter で使う provideruid 、それとTwitter認証の場合はアカウント名を保存しておきたいので username もUserテーブルに追加します。
その他、保存したい項目があったら arunagw / omniauth-twitter の認証時のハッシュ情報を参考に保存用カラムを追加してください。

$ rails g migration add_columns_to_users provider uid username

以下のようなマイグレーションファイルができます。

db/migrate/20200404042756_add_columns_to_users.rb
class AddColumnsToUsers < ActiveRecord::Migration[6.0]
  def change
    add_column :users, :provider, :string
    add_column :users, :uid, :string
    add_column :users, :username, :string
  end
end

ここまで出来たら以下を実行します。

$ rake db:migrate

4. viewの編集

以下のファイルを編集して、ページ上部にメニューが出るようにします。
user_signed_in? はdeviseのHelperメソッドです。
ログインしているかしてないかで上部のメニューの表示が変わるようになります。
current_user で現在サインインしているユーザーの情報を取得できます。

app/views/layouts/application.html.erb
<!DOCTYPE html>
<html>
  <head>
    <title>DeviseRails6</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' %>
  </head>

  <body>
    <header>
      <nav>
          <% if user_signed_in? %>
            <strong><%= link_to current_user.username, pages_show_path %></strong>
            <%= link_to 'プロフィール変更', edit_user_registration_path %>
            <%= link_to 'ログアウト', destroy_user_session_path, method: :delete %>
          <% else %>
            <%= link_to 'サインアップ', new_user_registration_path %>
            <%= link_to 'ログイン', new_user_session_path %>
          <% end %>
      </nav>
    </header>
    <!-- ここに追加 -->
    <p class="notice"><%= notice %></p>
    <p class="alert"><%= alert %></p>

    <%= yield %>
  </body>
</html>

以下の2ページも修正。
indexの方がトップページ、showの方がログインしているユーザー用のページになる予定です。

app/views/pages/index.html.erb
<h1>ようこそ</h1>
<p>トップページです。</p>
app/views/pages/show.html.erb
<h1>こんにちは、<%= current_user.username %>さん</h1>
<p>ユーザー用ページです。</p>

サーバーを立ち上げて、「サインアップ」を押下すると、 app/models/user.rbomniauthable を設定しているのに何の設定もしていないので現状ではエラーになります。

$ brew install yarn
$ rails webpacker:install
$ rails s

スクリーンショット 2020-04-04 13.39.59.png

スクリーンショット 2020-04-04 13.40.38.png

omniauthable を設定していない場合はサインアップページが表示されます。

Twitterで認証する

1. 設定

1. Twitter Developerの登録

Twitter Developerにアクセスし、 Create an app をクリックし(ツイッターにログインしてないとボタンが出ません)、情報を入力にします。
審査がいるようになってしまったので、私がやったときは何週間かかかった気がします。
私は今回、rails5版のこの記事を書いたときのものを使用しています。

スクリーンショット 2020-04-04 13.49.43.png

新規申請する場合は、申請時に Enable Sign in with Twitter にチェックを入れておいてください。

スクリーンショット 2020-04-04 13.52.47.png

2. 設定ファイルの編集

Keys and tokens タブを開き、 API keyAPI secret key を以下の該当箇所にコピーして貼り付けます。

スクリーンショット 2020-04-04 13.55.46.png

callback_urlhttp://127.0.0.1:3000/users/auth/twitter/callback にし、Twitter Developer側のコールバックの設定も変更する必要があるようです。
なので、以下の用に修正しました。

スクリーンショット 2020-04-04 14.16.43.png

config/initializers/devise.rb
Devise.setup do |config|
  # (省略)...

  config.omniauth :twitter, 'API Key', 'API Secret', callback_url: 'http://127.0.0.1:3000/users/auth/twitter/callback'
end

2. 動作確認

サーバーを立ち上げてサインアップページの下の方にあるSign in with Twitterというリンクをクリックします。

すると以下の様な画面が開くのでログインして 連携アプリを認証 をクリックします。

スクリーンショット 2020-04-04 14.16.18.png

こんな画面が出るはずです。

スクリーンショット 2020-04-04 14.20.44.png

3. コールバック用コントローラーの作成

Twitter認証後適切に画面が遷移するように以下を実行してコントローラーを作ります。

$ rails g controller omniauth_callbacks

作成したコントローラーの中身を以下のように修正します。
継承するのが Devise::OmniauthCallbacksController になっていることに注意です。
omniauth.auth という環境変数に認証に関する情報が入っています。
その情報を使ってユーザーが登録されているかを検証し、登録してる場合はログイン、登録されてない場合は登録用ページに遷移します。

app/controllers/omniauth_callbacks_controller.rb
class OmniauthCallbacksController < Devise::OmniauthCallbacksController
  def twitter
    @user = User.from_omniauth(request.env["omniauth.auth"].except("extra"))

    if @user.persisted?
        sign_in_and_redirect @user
    else
        session["devise.user_attributes"] = @user.attributes
        redirect_to new_user_registration_url
    end
  end
end

ちなみに omniauth.auth の中にはこんなものが入ってます。(一部の値は適当に変えてあります。)

{
  "provider"=>"twitter",
  "uid"=>"0123456789",
  "info"=>{
    "nickname"=>"manycicadas",
    "name"=>"芭蕉",
    "location"=>"関東",
    "image"=>"http://pbs.twimg.com/profile_images/483964583371997185/2ZqzhzKV_normal.png",
    "description"=>"JavaEE/Ruby(Rails)/HTML/CSS/JavaScript/Raspberry Pi などなどが好き。",
    "urls"=>{
      "Website"=>nil,
      "Twitter"=>"https://twitter.com/manycicadas"
    }
  },
  "credentials"=>{
    "token"=>"xxx",
    "secret"=>"xxxxxx"
  }
  (省略)...
}

Userモデルに self.from_omniauthself.new_with_session を作ります。
self.from_omniauth では uidprovider で検索してあったらそれを、無かったらレコードを作ります。
self.new_with_session については、もしこのメソッドを追加しておかなければ、Twitter認証後サインアップページで登録を行っても、認証情報として取ってきたuidやproviderなどが登録されません。それらが登録されないのでTwitterで認証しても登録されてないユーザーとして毎回サインアップページに飛ばされます。

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

  def self.from_omniauth(auth)
    find_or_create_by(provider: auth["provider"], uid: auth["uid"]) do |user|
      user.provider = auth["provider"]
      user.uid = auth["uid"]
      user.username = auth["info"]["nickname"]
    end
  end

  def self.new_with_session(params, session)
    if session["devise.user_attributes"]
      new(session["devise.user_attributes"]) do |user|
        user.attributes = params
      end
    else
      super
    end
  end
end

以下ファイルを編集して、コールバック用のコントローラーとしてさっき作ったコントローラーが呼ばれるようにします。これを書かないとdevise側のコントローラーが呼ばれます。

config/routes.rb
Rails.application.routes.draw do
  devise_for :users, controllers: { :omniauth_callbacks => "omniauth_callbacks" }

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

これでTwitter認証ができるようになりました。
初回、Twitter認証を行うと、サインアップページに飛ばされ、そこでメールアドレスやパスワードを入力して登録するとユーザー情報が登録されます。
今回は comfirmable 機能を入れているので、登録したら確認メッセージを送ったとのメッセージが出て、そのままログインすることはできません。
この機能を入れてなかった場合、登録すると即ログインします。

アカウント登録確認メールを送る

1. comfirmable概要

多くの登録系サイトで採用されている、登録すると仮登録状態になり、届いたメールのリンクをクリックするとログイン可能になるという仕組みを追加できるのがcomfirmableです。
今回はGmailのアカウントを使って実際にメールが届くように設定します。

2. ログを見る

log/deployments.log
Devise::Mailer#confirmation_instructions: processed outbound mail in 8.8ms
Delivered mail 5e881d3772753_89a53b9c70510@SampleMacBook-Pro.local.mail (22.3ms)
Date: Sat, 04 Apr 2020 14:37:59 +0900
From: please-change-me-at-config-initializers-devise@example.com
Reply-To: please-change-me-at-config-initializers-devise@example.com
To: sample@example.com
Message-ID: <5e881d3772753_89a53b9c70510@SampleMacBook-Pro.local.mail>
Subject: Confirmation instructions
Mime-Version: 1.0
Content-Type: text/html;
 charset=UTF-8
Content-Transfer-Encoding: 7bit

<p>Welcome sample@example.com!</p>

<p>You can confirm your account email through the link below:</p>

<p><a href="http://localhost:3000/users/confirmation?confirmation_token=1vus7S8UesPoay5yz2je">Confirm my account</a></p>

3. メールが実際に届くようにする

1. 設定ファイルの編集

今回はgmailを使うのでメールアドレスにはgmailを設定します。
デフォルトの設定があると思うので、それを変更してください。

config/initializers/devise.rb
Devise.setup do |config|
  # (省略)...
  config.mailer_sender = "メールアドレス"
end

gmailの場合はGメールアドレスとGメールパスワードの部分を自分のアカウントのものに変更します。

config/environments/development.rb
Rails.application.configure do
  # (省略)...

  # mailer setting
  config.action_mailer.default_url_options = { host: 'localhost', port: 3000 }
  # config.action_mailer.default_url_options = { host: 'localhost:3000' }
  config.action_mailer.raise_delivery_errors = true
  config.action_mailer.delivery_method = :smtp
  config.action_mailer.smtp_settings = {
    :address => 'smtp.gmail.com',
    :port => 587,
    :user_name => 'Gメールアドレス',
    :password => 'Gメールパスワード',
    :authentication => :plain,
    :enable_starttls_auto => true
  }
end

2. Gmailの設定

1. とりあえず試したい場合

デフォルトでは安全性の低いアプリケーションからGmailへのアクセスが制限されています。
よってその制限を解除することで動くようにできます。
ただ、安全性の低いアプリからのアクセスを許可するということはセキュリティ的にはいまいちなのであくまで動作確認ように利用することをおすすめします。
安全性の低いアプリがアカウントにアクセスするのを許可する の安全性の低いアプリを許可というリンクをクリックして、以下のように設定します。

スクリーンショット 2020-04-04 14.48.41.png

2. 2段階認証がオンの場合

2段階認証をオンにしている場合、1の設定はできないはずなのでこちらをやってください。
また、1のセキュリティ的にイマイチな方をやりたくない方もこの際2段階認証をオンにしてこの設定をやってください。

アカウントへのアクセスとセキュリティ設定の管理を開きます。
2段階認証がオフの場合は2段階認証プロセスから2段階認証の設定をしてください。(2 段階認証プロセスを有効にする)
2段階認証設定を行っている場合はアプリパスワードを開きます。

スクリーンショット 2020-04-04 15.00.16.png

アプリを選択その他 を選択します。

スクリーンショット 2020-04-04 15.00.51.png

適当に名前をつけます。

スクリーンショット 2020-04-04 15.01.50.png

パスワードが生成されます。

スクリーンショット 2020-04-04 15.04.21.png

生成されたパスワードを、1の設定の :password => "Gメールパスワード", のGメールのパスワードの部分に設定します。

3. 届いたメールを確認

これでアプリ側からサインアップすると、以下の様なメールが届くようになります。 Confirm my account をクリックするログイン画面からログインが可能になります。

スクリーンショット 2020-04-04 15.08.30.png

メールで送られる文章は以下のファイルを編集することで可能です。
app/views/devise/confirmation_instructions.html.erb

アカウントをロックする

1. lockable概要

アカウントの認証を一定回数間違うと、アカウントをロックするようにする機能です。

2. 設定

1. 設定ファイルの編集

以下ファイルを修正して、アカウントの認証を5回失敗します。

config/initializers/devise.rb
Devise.setup do |config|
    # (省略)...

    # lock sessings
    config.unlock_strategy = :email
    config.maximum_attempts = 4
end

するとアカウントがロックされてこのようなメールが届きます。

スクリーンショット 2020-04-04 15.14.29.png

メールの中身は app/views/devise/mailer/unlock_instructions.rb ファイルを修正すれば変わります。

2. 設定値について

  • lock_strategy(ロック方法)
属性 説明
:failed_attempts 失敗回数によってロック。
:none ロックしない。
  • unlock_strategy(ロック解除方法)
属性 説明
:time 指定時間でロックを解除する。
:email メールでロックを解除する。
:both :timeと:emailの両方。
:none 解除させない。

:none を指定した場合、ユーザーのレコードのlocked_atカラムをnilにアップデートしたらロックが解除できます。

  • unlock_in(ロック解除時間)

2時間で解除するならconfig.unlock_in = 2.hoursといった具合に指定。

  • unlock_keys

アカウントをロックまたは解除するときに使用するキーを定義するらしいです。
config.unlock_keys = [:username]という感じで指定できます。

  • maximum_attempts(失敗可能回数)

アカウントの認証を失敗して良い回数を指定します。
config.maximum_attempts = 4と指定した場合、4回目までは失敗しても大丈夫ですが、5回目を失敗した時点でアカウントがロックされます。

セッションをタイムアウトする

1. timeoutable概要

一定時間活動がない場合にセッションをタイムアウトさせるのがtimeoutableです。

2. 設定

以下ファイルを修正するとタイムアウトまでの時間を指定できます。
以下の場合だと3分後にセッションがタイムアウトします。
デフォルトは30分だそうです。

config/initializers/devise.rb
Devise.setup do |config|
    (省略)...
  # timeout setting
  config.timeout_in = 3.minutes
end

その他の設定

1. ログイン後のページを変更する

ログインすると、デフォルトでは root_url に飛ばされます。
これを app/views/pages/show.html.erb になるように修正します。
after_sign_in_path_for メソッドを追加します。ここにログイン後に遷移したいページを指定します。
あと sign_in_required も追加します。showページはログインしているユーザーだけにアクセスさせ、ログインしてない場合はログインページに遷移させます。

app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
  def after_sign_in_path_for(resource)
    pages_show_path
  end

  private
      def sign_in_required
          redirect_to new_user_session_url unless user_signed_in?
      end
end

Pagesコントローラーに before_action を追加します。

app/controllers/pages_controller.rb
class PagesController < ApplicationController
  before_action :sign_in_required, only: [:show]

  def index
  end

  def show
  end
end

これでログイン後showページに遷移するようになります。

2. サインアップする際に登録するパラメーターを増やす

ユーザーを登録するときにデフォルトではEmailとパスワードだけですが、
ユーザー名も登録させたい、などの場合があると思います。

まずはサインアップページにユーザー名を入力するエリアを追加します。
ユーザーのプロフィール変更ページ(views/devise/registrations/edit.html.erb)にも同様に追加しときます。

app/views/devise/registrations/new.html.erb
<h2>Sign up</h2>

<%= form_for(resource, as: resource_name, url: registration_path(resource_name)) do |f| %>
  # (省略)...

  <!-- ユーザー名を追加 -->
  <div class="field">
    <%= f.label :username %><br />
    <%= f.text_field :username %>
  </div>

  # (省略)...

<%= render "devise/shared/links" %>

次に ApplicationController に以下を追加します。
テンプレートを変えて、ユーザー名を入力するようにしただけでは実際に登録されないからです。
詳しくは strong_parameters について調べてください。
簡単に言えばよく分からんパラメーターは渡せないようになってるので渡せるようにします。

app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
  protect_from_forgery with: :exception
  before_action :configure_permitted_parameters, if: :devise_controller?

  # (省略)...

  protected
    def configure_permitted_parameters
      devise_parameer_sanitizer.permit(:sign_up, keys: [:username])
      devise_parameter_sanitizer.permit(:account_update, keys: [:username])
    end
end

最後に

作ったプロジェクト全体は以下です。
cigalecigales/devise_rails6

スクリーンショット 2020-04-04 15.42.43.png

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

MVCとは

MVC

Model(モデル)
View(ビュー)
Controller(コントローラー)
の頭文字です。
役割としては
Model(モデル)
ビジネスロジック(データ処理)を行う
View(ビュー)
表示処理を行う
Controller(コントローラー)
リクエストに応じて適切な処理を呼び出す

MVCの流れ

ルーティング=>コントローラー
リクエストに対応したルーティングが読まれ、それに対応したコントローラーのアクションが動きます。
コントローラー=>モデル
コントローラーで呼び出したアクションをモデルを通してデータベースから必要なデータを呼び出します。
コントローラー=>ビュー
呼び出したデータをビューに受け渡し、受け取ったデータをWebページに表示させます。

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

[Rails]本番環境でSeed-fuを使う

本記事投稿のいきさつ

railsでアプリを作成している中で、Seed_fu Gem を使用しました。
その際、開発環境では上手くテーブルにデータを格納することができたのですが、本番環境で苦戦したため備忘録として書きます。
ここでは、自分が苦戦したEC2内でのコマンドのみを書きます。

エラー(異なるディレクトリでコマンドを入力していたため)

自分が作成したアプリのディレクトリへ移動しコマンドを入力したところ

Mysql2::Error::ConnectionError: Can't connect to local MySQL server through socket '/tmp/mysql.sock' (2)

と、mysplに接続ができないというエラーが発生。
これはコマンドを入力するディレクトリが誤っていたため発生したエラーでした。
実は開発環境とは異なり、本番環境ではcurrentディレクトリ内のデータが本番環境で動いているフォルダ群になります。
そのためcurrentディレクトリへ移動した後、再度コマンドを入力します。

 cd current
 rails db:seed_fu

これで大丈夫かと思われましたが、またしてもエラーが。

エラー(コマンド誤り)

Could not load the 'listen' gem. Add `gem 'listen'` to the development group of your Gemfile

listen gem がないとのこと。
まさかと思いgemfileを確認したところちゃんと書かれていました。
調べたところ、seedでデータを投入する際は環境の指定をする必要があるようです。ということで以下を再度実行。

rails db:seed_fu RAILS_ENV=production

今度は成功しました。初めての経験で苦戦しましたが、とても勉強になりました。
ちなみに、test環境でも同じように以下を実行すれば大丈夫です。

rails db:seed_fu RAILS_ENV=test

おわり

最後まで見ていただきありがとうございました。

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

【rails】ユーザに紐づくデータをユーザ登録時に生成するための実装方法

新規ユーザ登録時に、ユーザに紐づくデータを生成するよう実装した。
具体的には、新規ユーザが登録されたときに、外部キーとしてuser_idを持つFolderモデルとTagモデルのデータが生成されるようにした。

Gemfile

gem 'devise'

devise導入

bundle Install
rails g devise:install
rails g devise User

DeviseControllerのカスタマイズ

※認証ユーザーのモデルはUser

rails g devise:controllers users

# rails g devise:controllers [scope]

app/controllers/users 以下に次のコントローラが作成される。

・confirmations_controller.rb
・omniauth_callbacks_controller.rb
・passwords_controller.rb
・registrations_controller.rb
・sessions_controller.rb
・unlocks_controller.rb

ルーティング設定

registration_controller.rbのルーティング設定を行う。

config/routes.rb
Rails.application.routes.draw do
  # devise_for :users

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

registration_controller設定

newアクションのコメントアウトを外す。
createアクションにデータを生成する処理を記述する。

app/controllers/users/registrations_controller.rb
# frozen_string_literal: true

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

  # GET /resource/sign_up
  def new
    super
  end

  # POST /resource
  def create
    super
    @default_folder = Folder.create( name: 'DefaultFolder', user_id: current_user.id )
    Tag.create( name: 'Temporary', user_id: current_user.id, folder_id: @default_folder.id )
  end


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

上記では、user_IDを外部キーとして持つFolderモデルとTagモデルのデータを生成している。
新規ユーザ登録により、登録したユーザーのuser_idを外部キーとして持つFolderモデルとTagモデルのデータが生成されるようになった。

参考文献

https://qiita.com/akasakas/items/138c29fa2cecd271cfe4

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

[Docker] webコンテナからdbコンテナへの接続方法

docker-compose.yml
:docker-compose.yml
version: '3'
services:
  db:
    container_name: local-mysql
    image: mysql:5.7
    volumes:
      - ./db/mysql_data:/var/lib/mysql
    environment:
      MYSQL_DATABASE: 'local-mysql-db'
      MYSQL_PASSWORD: password
      MYSQL_USER: root
    ports:
      - "3306:3306"
    networks:
      - app-net

  web:
    container_name: "local-application"
    build: .
    command: rails s -p 3000 -b '0.0.0.0'
    volumes:
      - .:/fishingshares
    ports:
      - "3000:3000"
    depends_on:
      - db
    links:
      - db
    networks:
      - app-net

networks:
  app-net:
    driver: bridge
$ mysql -h local-mysql -u root -D local-mysql-db -p

詳しく解説すると、
mysql -h [host名] -u [ユーザ名] -D [database名] -p
ということになる。

以上のコマンドをwebコンテナ内で実行すると、mysqlに接続できる。

以上

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

selfを使って、undefined methodエラーを解消

はじめに

Modelで定義したメソッドがControllerで呼び出せなくなったとき、
selfを使って2パターンの解決法を見つけたため、記録として残しておく。

今回は、helloという名前のメソッドをモデルで定義するという設定とする。

解決法1(特異メソッド方式)

post.rb
class Post
  def self.hello
    puts 'hello'
  end
end

メソッド名の前にself.をつけるやり方

ruby初学者なら何度も見たことのある形だ。

解決法2(特異クラス方式)

post.rb
class Post
  class << self
    def hello
      puts 'hello'
    end
  end
end

こちらでも、きちんとcontrollerで呼び出すことができた。
この場合だと、メソッド名の前にself.を書く必要もないし、class << self と end内であれば、
複数のメソッドを同じように定義できるのがメリットだ。

post.rb
class Post
  class << self
    def hello
      puts 'hello'
    end

    def goodmorning
      # 二つ定義しても self つけなくて良い
      puts 'Good morning'
    end
  end
end

そもそも

ここからは、特異メソッド方式や特異クラス方式、selfについてもう一歩踏み込んだところまで理解したい方々に読んで欲しい。

class << selfの仕組み

 特異メソッド

hello = 'hello'
def hello.say
  puts hello + 'world'
end
hello.say #=> hello world

another_hello = 'hello'
another_hello.say #=> NoMethodError: undefined method `say' for "hello":String

このように、特異メソッド(hello.say)とは、オブジェクト(hello)に直接固有のメソッドを定義したものだ。
なので、上記のように別のオブジェクト(another_hello)には使うことはできない。

これに加えて、もう一つ特異メソッドを定義する方法がある。

hello = 'hello'

class << hello
  def say_world
    puts "#{self}, world"
  end
end
hello.say_world #=> hello, world

これも << hello というところで helloオブジェクトの特異クラスを引き出しており、あくまでオブジェクトに対しての特異メソッドの定義となっている。

つまり、一番最初のこの部分、

class Post
  class << self

クラス定義のコンテキストでの self とは、Classクラスのインスタンス Post class だ。class Post とはクラスを定義するときの記法だが、Ruby の中での理解としては、Class クラスのオブジェクトを生成し Post というグローバルな定数へ代入している。

なので、以下の2つの記述は同じ意味だ。

class Post
  def hello
    puts 'hello'
  end
end
post = Post.new
post.hello #=> hello
Post = Class.new do
  def hello
    puts 'hello'
  end
end
post = Post.new
post.hello #=> hello

定数 Post に入っている Class クラスのオブジェクトへ特異メソッドを定義してみる。

Post = Class.new do
  def hello
    puts 'hello'
  end
end
def Post.bye
  puts 'good bye'
end
Post.new.hello #=> hello
Post.bye       #=> good bye

Post.bye という呼出しができた。これはクラスメソッドの呼出しと同じ。
つまり、クラスメソッド Post.bye というのは、Post に入っている Class クラスのオブジェクトへの特異メソッドの定義として読むことができる。

これにオブジェクトの特異クラスの引き出しの記法 << をあわせて考えると、一番冒頭のコードでやろうとしていることがわかってくる。

特異クラス

クラスメソッドの定義は特異メソッド形式と特異クラス形式があった。

特異メソッド形式で定義した def Post.bye を特異クラス形式へ書きかえてみる。

Post = Class.new do
  def hello
    puts 'hello'
  end
end
class << Post
  def bye
    puts 'good bye'
  end
end
Post.new.hello #=> hello
Post.bye       #=> good bye

さらに書き換えると下記にようになる

class Post
  def hello
    puts 'hello'
  end

  class << self
    def bye
      puts 'good bye'
    end
  end
end
Post.new.hello #=> hello
Post.bye       #=> good bye

クラスメソッドのための記法があるわけではなく、特異メソッドという仕組みを使って巧みにクラスメソッドが実現されていることがわかった。

おわりに

今まで何気なく使っていたself。
というかなんとなく理解できたようでできていなかった部分だった。

私が参考にした、というかこの記事はほぼ下記の記事のコピペなので、私の記事がわかりにくかったという方は、ぜひ下のURLの記事を参考にして欲しい。

参考記事

https://magazine.rubyist.net/articles/0046/0046-SingletonClassForBeginners.html

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

Active jobでの非同期処理

非同期処理とActive job

非同期処理とはAという作業と同時に別のBという作業を行うこと(マルチタスク、並列作業)。通常はAという作業の後にBという作業をするように一つ一つしか行えない非同期処理と同期処理の実装パターンと特徴。しかしrailsではActive jobを用いて必要なツールのインストールを行って実行する。ここではsidekiqの例をあげます。

準備、設定

sidekiqによる非同期処理を行うにはredisサーバーが起動していないといけないので、まずredisをマシーンにインストール。

redisのサーバーを起動

gemを使ってsidekiqをアプリにインストール

bundle exec sidekiqでsidekiqを起動

config/environmentsでその環境でsidekiqを使用することを記述

ジョブを作成、実行

ジョブとはメインの処理とは別に並行的にバックグラウンドで行わせる処理のこと。ジョブを実行するにはジョブを定義し、読み出さないといけない

rails g job ~でジョブを記述するためだけのファイルを作る

app/jobs/~job.rbにジョブの内容を記述

非同期処理を行いたいアクションの中でジョブの読み出しを行うことで非同期処理される

厳密にはジョブの読み出しをするとすぐに実行されるわけではない。予約をするイメージ。忙しくてできない時は開始されないし、処理できる状態になれば実行されるって感じ

参考文献

現場で使える Ruby on Rails 5速習実践ガイド
Railsガイド
Rails 4.2で導入されたActive Jobを使ってみよう

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

Rails5.2以降でのcredentials.yml.encの読み込み方

PAYJPのAPIが読み込めず、クレジットカードの登録ができない

PAYJPを使ってクレジット決済の導入をしたところ、
クレジットカードの登録をしようとすると下記のようなエラーがでました

スクリーンショット 2020-04-04 21.15.33.png

PAYJPの導入はQiita記事参照してください
RailsでPayjpを使った購入機能を実装する

PAYJPと連携するためのキーが読み込めていない

Rails5.2以降からはAPIのキーなど環境変数に設定するためのものは
credentilas.yml.encに記載するようになりました

詳しくはこちら
Rails5.2から追加された credentials.yml.enc のキホン

こちらを参照にcredentials.yml.encに記載したはず・・・
もしかしてちゃんと読み込めていなそう?

rails cで調べてみる

ターミナル
rails c
[1] pry(main)> Rails.application.credentials.PAYJP_PRIVATE_KEY
=> "sk_test_XXXXXXXXXXXXXXXXXXXX"

どうやらちゃんと読み込めるのでちゃんと環境変数は設定できていそう

credentials.yml.encを読み込む書き方が必要

どうやら他の参考記事の通りに

card_controller.rb
  def pay
    Payjp.api_key = ENV["PAYJP_PRIVATE_KEY"]
    中略

と書いてしまっていたのが原因。
ENV["PAYJP_PRIVATE_KEY"]で読み込んでいたのはcredentials.yml.encを使っていないRails.5.2以前の書き方で、credentials.yml.encを読み込むためには

card_controller.rb
  def pay
     Rails.application.credentials.dig(:payjp, :PAYJP_PRIVATE_KEY)
    中略

と書かないとcredentials.yml.encに記載した環境変数の読み込みができないということです。
記事によってRailsのバージョンが違うので安易にそのまま記載するのはいけないですね。。。。

参考

【Rails】payjpを使用した決済機能を実装する①〜クレジットカードの登録編〜

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

rails 通知機能

はじめに

今回は通知機能を作成しました。
こちらの記事を参考にさせていただいております。
https://qiita.com/nekojoker/items/80448944ec9aaae48d0a
https://qiita.com/yuto_1014/items/2db1dd4fcd7945b980f7

作るもの

コメント・いいね・フォローされた時に通知を作成して確認・削除できる機能を作成します。すでに上記の機能はあるものとして作成してます。どれか一つでも機能があればOK。参考に過去に投稿したコメント・フォロー機能についてリンクを載せておきます。
コメント機能:https://qiita.com/E6YOteYPzmFGfOD/items/ef776d34908872ea19f7
フォロー機能:https://qiita.com/E6YOteYPzmFGfOD/items/ec0492b509962c3b7ae4

ER図

参考に今回作成する通知機能のER図です。(今回関係ないタグ機能がありますが無視してください。)
スクリーンショット 2020-04-04 16.22.02.png

作成の流れ

1.Notificationモデルの作成
2.各モデルの関連づけ
3.通知メゾットの作成
4.通知機能をコントローラーへ埋め込む
5.通知一覧画面と通知削除作成

モデルの作成

rails g model Notification visitor:references visited:references micropost:references comment:references action:string checked:boolean
db/migrate/
class CreateNotifications < ActiveRecord::Migration[5.1]
  def change
    create_table :notifications do |t|
      t.references :visitor, foreign_key:{ to_table: :users }, null: false
      t.references :visited, foreign_key:{ to_table: :users }, null: false
      t.references :micropost, foregin_key: true
      t.references :comment, foregin_key: true
      t.string :action, null: false
      t.boolean :checked, null: false, default: false

      t.timestamps
    end
  end
end

vistor_id
→通知するユーザー(いいね、コメント、フォローする側)
visted_id
→通知をうけるユーザー
rails g model カラム名:references とすると自動で
・カラム名にidをつけた形でカラムを作成。
・インデックス付与。
foreign_key:true
・外部キー制約を付与
to_table: :user
・visitorとvisitedカラムの参照テーブルを指定してます。
nul: false
・空の状態で保存できなくする。

モデルの関連付け

app/models/user.rb
has_many :active_notifications, foreign_key:"visitor_id", class_name: "Notification", dependent: :destroy
has_many :passive_notifications, foreign_key:"visited_id", class_name: "Notification", dependent: :destroy
app/models/micropost.rb
has_many :notifications, dependent: :destroy
app/models/comment.rb
has_many :notifications, dependent: :destroy
app/models/notification.rb
class Notification < ApplicationRecord
  default_scope -> { order(created_at: :desc) }
  belongs_to :visitor, class_name: "User", optional: true
  belongs_to :visited, class_name: "User", optional: true
  belongs_to :micropost, optional: true
  belongs_to :comment, optional: true
  validates :visitor_id, presence: true
  validates :visited_id, presence: true
  ACTION_VALUES = ["like", "follow", "comment"]
  validates :action,  presence: true, inclusion: {in:ACTION_VALUES}
  validates :checked, inclusion: {in: [true,false]}
end

各オプション
foreign_key:"vistor_id", class_name: "Notification"
→関連づけるカラム名とテーブル名を指定します。
dependent: :destroy
→投稿が削除された際に関連付いている通知を削除。
default_scope -> { order(created_at: :desc) }
→通知が新しい順に並ぶ。
optional: true: nullの値を許可する。
inclusion
→保存できる値を制限しています。

通知メゾットの作成

いいね

app/models/micropost.rb
def create_notification_like!(current_user)
    temp = Notification.where(["visitor_id = ? and visited_id = ? and micropost_id = ? and action = ? ",
                                  current_user.id, user_id, id, 'like'])
    if temp.blank?
      notification = current_user.active_notifications.new(
        micropost_id: id,
        visited_id: user_id,
        action: 'like'
      )

      if notification.visitor_id == notification.visited_id
         notification.checked = true
      end
      notification.save if notification.valid?
    end
  end

始めに通知が作成済みでないか確認してます。(何度も作成しないように)登録済みの場合と自分の投稿に対するいいねの場合は通知を確認済みとして作成します。(checkedをtrueにする。)active_notificationsはuserモデルでhas_many関連付けを行った際に名付けた関連名です。

コメント

app/models/micropost.rb
def create_notification_comment!(current_user, comment_id)
    #同じ投稿にコメントしているユーザーに通知を送る。(current_userと投稿ユーザーのぞく)
    temp_ids = Comment.where(micropost_id: id).where.not("user_id=? or user_id=?", current_user.id,user_id).select(:user_id).distinct
    #取得したユーザー達へ通知を作成。(user_idのみ繰り返し取得)
    temp_ids.each do |temp_id|
      save_notification_comment!(current_user, comment_id, temp_id['user_id'])
    end
    #投稿者へ通知を作成
    save_notification_comment!(current_user, comment_id, user_id)
end

def save_notification_comment!(current_user, comment_id, visited_id)
    notification = current_user.active_notifications.new(
      micropost_id: id,
      comment_id: comment_id,
      visited_id: visited_id,
      action: 'comment'
    )
    if notification.visitor_id == notification.visited_id
      notification.checked = true
    end
    notification.save if notification.valid?
end

コメントの場合は同じ投稿に対してコメントしているユーザーにもコメントを送るようにします。マイクロポストの投稿者には必ず通知が1件作成されるようにします。
「where.(micropost_id: id)」
→同じ投稿にコメント
「where.not("user_id=? or user_id=?", current_user.id,user_id)」
→コメントしたユーザーと投稿者は除く。
distinct
→複数回コメントしている人も通知は一件のみにする
select(:user_id)
→user_idのみ取得しています。

取得したuser_idを下で定義している通知作成メゾットに渡して繰り返し通知を作成します。

フォロー

app/models/user.rb
def create_notification_follow!(current_user)
    #すでに通知が作成されているか確認
    temp = Notification.where(["visitor_id = ? and visited_id = ? and action = ? ",current_user.id, id, 'follow'])
    if temp.blank?
      notification = current_user.active_notifications.new(
        visited_id: id,
        action: 'follow'
      )
      notification.save if notification.valid?
    end
end

通知機能を各コントローラーへ埋め込み

各通知メゾットをコメント等のアクションを起こした際に起動するようにコントローラー内へ埋め込んでいきます。

コメント

app/controlloers/comments_controller.rb
def create
    @comment = current_user.comments.build(comment_params)
    @comment.micropost_id = params[:micropost_id]
    if @comment.save
      flash[:success] = 'コメントしました'
      #通知機能用
      @micropost=@comment.micropost
      @micropost.create_notification_comment!(current_user, @comment.id)
      #ここまで通知機能
      redirect_to @comment.micropost
    else
      comments_get
      render template: 'microposts/show'
end

フォロー

app/controllers/follow_controlloer.rb
def create
    @user =User.find(params[:follow_relationship][:following_id])
    current_user.follow(@user)
    #通知機能追加
    @user.create_notification_follow!(current_user)
    respond_to do |format|
      format.html {redirect_back(fallback_location: root_url)}
      format.js 
    end
end

いいね

app/controllers/like_controlloer.rb
def create
    @user = current_user
    @micropost = Micropost.find(params[:micropost_id])
    current_user.like(@micropost)
    #通知機能追加
    @micropost.create_notification_like!(current_user)
    respond_to do |format|
      format.html { redirect_back(fallback_location: root_url) }
      format.js
    end
end

通知一覧画面と通知削除作成

まずは通知ページ表示ようのコントローラ、ルーディングの設定です。

rails g controller notifications
config/routes.rb
resources :notifications, only: [:index, :destroy]
app/controllers/notifications_controlloer.rb
def index
    @notifications = current_user.passive_notifications
    #通知画面を開くとcheckedをtrueにして通知確認済にする
    @notifications.where(checked: false).each do |notification|
      notification.update_attributes(checked: true)
    end
end

def destroy
    @notifications =current_user.passive_notifications.destroy_all
    redirect_to notifications_path
end

続いて通知一覧画面の作成です。

app/views/notifications/index.html.erb
<h3 class="text-center">通知</h3>
    <%= link_to "通知削除", notification_path(@notifications), method: :delete ,class: "fas fa-trash" %>
    <% if @notifications.exists? %>
    <div class="notification-index">
        <%= render @notifications %>
    </div>
<% else %>
    <p>通知はありません</p>
<% end %>
app/views/notifications/_notification.html.erb
<div class="notification-view">
  <%= notification_form(notification) %><span class="moderate-font"><%= " (#{time_ago_in_words(notification.created_at)}前)" %></span>
  <br>
  <% if !@comment.nil? %>
    <p class="notification-comment"><%= @comment %></p>
  <% end %>
</div>

いいね、フォロー、コメントの通知によって表示する内容を変更するために、
ヘルパーメゾットを作成してviewで呼び出します。

ruby/app/helprs/notifications_helper.rb
module NotificationsHelper
  def notification_form(notification)
    #通知を送ってきたユーザーを取得
    @visitor = notification.visitor
    #コメントの内容を通知に表示する
    @comment = nil
    @visitor_comment = notification.comment_id
    # notification.actionがfollowかlikeかcommentかで処理を変える
    case notification.action
    when 'follow'
      #aタグで通知を作成したユーザーshowのリンクを作成
      tag.a(notification.visitor.name, href: user_path(@visitor)) + 'があなたをフォローしました'
    when 'like'
      tag.a(notification.visitor.name, href: user_path(@visitor)) + 'が' + tag.a('あなたの投稿', href: micropost_path(notification.micropost_id)) + 'にいいねしました'
    when 'comment' then
      #コメントの内容と投稿のタイトルを取得       
      @comment = Comment.find_by(id: @visitor_comment)
      @comment_content =@comment.content
      @micropost_title =@comment.micropost.title
      tag.a(@visitor.name, href: user_path(@visitor)) + 'が' + tag.a("#{@micropost_title}", href: micropost_path(notification.micropost_id)) + 'にコメントしました'
    end
end

以上となります。

だいぶ長くなりましたがここまでお読みいただきありがとうございました!!

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

【VScode】なぜか自動保存できねえ時の解決法、そしてパーミッション関連のことも【備忘録】

【VScode】なぜか自動保存できねえ時の解決法

設定でAfterdelayなどにしてファイルの自動保存機能をつけているVScodeで開発しているときに、
以下のようなエラーが出て、自動が保存ができないような事態の解決法を紹介します
エラー1.jpg

[アクセス権限が不十分です。[Sudo 権限で再試行]を選択してスーパーユーザーとして再試行してください。]
と出ております。
つまり、現在、Sudoユーザーとして、ファイルにアクセスできていない状態なので、
・スーパーユーザーにモードを変える
・ファイル、あるいはフォルダのアクセス権限(パーミッション)を変更する
方法があります。

今回は後者の手段を用いて、このエラーを解決します。

terminal
sudo chmod -R 777 ファイルのディレクトリ位置(Ex. /Users/kazuhitoNakayama/Desktop/index.html.erb)

この一行で、全てのユーザーに対してのファイルの実行権限を与えられるので、
上記エラーが解決されます。

解説

chmodとは

?ファイルやディレクトリのアクセス権限を変更する際に使うコマンドです
 (UNIX系のOSで使用できます。Ex.Macなど)

使い方は、chmod [オプション] [与える権限] [対象のファイル、ディレクトリのディレクトリ位置]

オプションは私は普段 「-R」を使ってますが、他にもたくさんあるので
「chmod オプション」とかでお調べください!

777とは

?先ほどchmod [オプション] [与える権限] [対象のファイル、ディレクトリのディレクトリ位置]
とお伝えしました。
ここでの[与える権限]について説明すると、アクセス権の記法には2種類あり、
・アルファベット
・数字
があります。

アルファベットでの記法

あるフォルダの中に入って「ls -l」をすることで、各ファイルの権限を参照できるので、みてみると、
アルファベットの羅列が見えます。

terminal
$ ls -l
drwxr-xr-x  2 user_name  group  68  8 30 15:53 css
-rw-r--r--  1 user_name  group   0  8 30 15:53 index.html

それぞれの文字は
r:(read)読み込み権限
w:(write)書き込み権限
x:(execute)実行権限
こんな風に対応しております。

例えば上の、drwxr-xr-xをいかに読むかというと
d/rwx/r-x/r-x 
↑こんな風に区切られます。

また、それぞれの区切りの中で、
ディレクトリなのかファイルなのか/所有者に対する権限/グループに対する権限/その他の者に対する権限/
という風に区切られており、それぞれの対象に対して、権限をr・w・xで与えます。
(1文字目に関してはdならば当該ファイルはディレクトリとわかります。-ならばファイルです。)

よって、
d/rwx/r-x/r-xは
ーディレクトリであり
自分に対して、読み込み、書き込み、実行の全ての権限が与えられており、
グループに対して、読み込み、実行権限が与えられており、
その他の者に対して、読み込み、実行権限が与えられています。

数字の記法

シンプルで、上記、r・w・xがそれぞれ数字に変わるだけですね!

r(読み込み権限):4
w(書き込み権限):2
x(実行権限):1

そして今回は3つの数字で権限対象、与える権限を表現します
例えば、731

1つ目の数字:所有者
2つ目の数字:グループ
3つ目の数字:その他の者
という風になっているので、

731は、
所有者は、7=4+2+1なので読み込み、書き込み、実行権限が与えられ、
グループは、3=2+1なので書き込み、実行権限が与えられ、
その他の者は、1なので実行権限が与えられます。

総括

冒頭の振り返りで行くと

terminal
sudo chmod -R 777 ファイルのディレクトリ位置(Ex. /Users/kazuhitoNakayama/Desktop/index.html.erb)

これによりindex.html.erbのファイルのパーミッションを、誰でも実行できるようにしたので、自分がファイルを変更してもパーミッションで怒られることがなくなったのです!

パーミッションは自分的に忘れやすい!
必覚!

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

【AWS】デプロイ関連で詰まった箇所

デプロイ

bundler: failed to load command: puma

解決法

参考記事

AssetsPrecompile関連エラー

Precompile = 1つのファイルにまとめる(圧縮する作業)

ターミナル
# アセッツプリコンパイル
$ bundle exec rake assets:precompile RAILS_ENV=production

解決法

yarnのインストール

ターミナル
$ curl -o- -L https://yarnpkg.com/install.sh | bash
$ source .bashrc # 反映
$ yarn -V # yarnインストール
config/environments/production.rb
config.assets.js_compressor = :uglifier

# 変更

config.assets.js_compressor = Uglifier.new(harmony: true)
config/environments/production.rb
config.public_file_server.enabled = ENV['RAILS_SERVE_STATIC_FILES'].present?

# 変更

config.public_file_server.enabled = true

capistrano関連エラー

SSHKit::Runner::ExecuteError

config/deploy/production.rb
set :ssh_options, {
  keys: %w(~/.ssh/id_rsa)
}

# 変更(例)

set :ssh_options, {
  keys: %w(/Users/hogehogehoge/.ssh/hogehoge_key_rsa)
}

# ①ssh接続の秘密鍵にはデフォルトでid_rsaを探しにいくため、使用する秘密鍵の名前に変更
# ②使用する秘密鍵の場所までのpathを通す

参考記事

Gem::LoadError : "ed25519 is not part of the bundle. Add it to your Gemfile."

Gemfile
gem 'ed25519'
gem 'bcrypt_pbkdf'
ターミナル
$ bundle

使ってるパソコンの環境によってエラーが出たり出なかったりするらしい。

bundle exec cap production deploy(git:checkで落ちる)

Gemfile
# ssh形式で接続
set :repo_url, "git@github.com:hogehogehoge.git"

# 変更

# https形式で接続
set :repo_url, "https://github.com/hoge/hogehogehoge.git"
set :git_http_username, "gitに登録しているusername"
set :git_http_password, "gitに登録した公開鍵パスワード"

bundle exec cap production deploy(deploy:symlink:linked_filesで落ちる)

master.key database.yml settings.ymlの配置を変更

/var/www/rails/アプリケーション/shared/config配下に移動

bundle exec cap production deploy(yarn:installで落ちる)

nodeのバージョンが古かったらしい

参考記事

bundle exec cap production deploy(nginx:restartで落ちる)

ターミナル
$ ps aux | grep nginx
→プロセスを確認してnginxが起動していないか確認
$ sudo nginx -t
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: [emerg] open() "/var/www/rails/アプリケーション名/log/nginx.error.log" failed (2: No such file or directory)
→nginx.conf(nginx設定ファイル)は問題ない
→/var/www/rails.......にnginx.error.logがあるか確認してくださいと言われるが、nginx.error.logは/var/www/rails/アプリケーション名/sheard/log/nginx.error.logに配置したいため/etc/nginx/sites-enabled/アプリケーション名を書き換える
ターミナル
[~]$ cd /etc/nginx/sites-enabled
$ sudo vim アプリケーション名
sites-enabled/アプリケーション名
error_log /var/www/rails/アプリケーション名/sheard/log/nginx.error.log
access_log /var/www/rails/アプリケーション名/sheard/log/nginx.access.log
ターミナル
$ sudo service nginx start
→ 起動確認

bundle exec cap production deploy(全て通ったのにブラウザで画面表示されない)

nginx.error.log
puma.error.log
production.log
を確認してエラーが出ているか確認。

nginx.error.log場所確認

ターミナル
/etc/nginx/sites-enabled/アプリケーション

vimで開いて中身を確認
自分の場合は/var/www/rails/アプリケーション名/shared/log/nginx.error.logに配置

注意))あくまで読み込むのは/etc/nginx/nginx.confであるためnginx.confinclude /etc/nginx/sites-enabled/*のようにincludeされているかを確認。

nginx関連で見るファイルは基本的にnginx.confとsites-enabled配下のファイルの2つ

puma.error.log場所確認

ローカル環境の

config/deploy.rb
set :puma_error_log の後にパスが記述

自分の場合は
/var/www/rails/アプリケーション名/shared/log/puma.error.logに配置

error.logは他の開発者とも共有しやすいようにshared(シェアード)配下に置くのが一般的なのでそのように設定しておくのがベター

production.logshared配下に配置

今回のエラーは/etc/nginx/sites-enabled配下にcapistranoでデプロイ以前に作成した.confファイルが残っておりそちらのファイルがnginx.confinclude
されており、間違った設定が適用されていたことが原因だった。

sites-enabled/.confを削除し、nginx.confに記載のincludeを削除することで解決。

AWSアーキテクチャ関連

複数サーバーにcapistranoでデプロイしたい時

元々あるWebサーバーからイメージの作成、EC2インスタンスを起動し、ローカルの

config/deploy/production.rb
role app, 'ユーザー名@パブリックIP'
role web, 'ユーザー名@パブリックIP'
role db, 'ユーザー名@パブリックIP'

を追加して

ターミナル
$ bundle exec cap production deploy

で、OK

SSL証明書発行(albに付与)

参考記事の7まで進めたら、最下部の新しい ACM 証明書をリクエストをクリック。

*.ドメイン名ドメイン名を記入し、次へ
検証方法はDNS
証明書が認証されるまで待って、リスナー画面へ戻り、発行された証明書を選択して保存

※ UdymyAWS:ゼロから実践するAmazon Web Services。手を動かしながらインフラの基礎を習得のセクション8の62.CloudFrontを設定して高速化しようが参考になる。

ローカルのconfig/deploy/templates/nginx_conf.erb
# bundle exec rails g capistrano:nginx_puma:configをした時に自動生成されるファイル
# デプロイすると、このファイルを元にEC2インスタンスの/etc/nginx/nginx.confや/etc/nginx/sites-enabled/アプリケーションに設定が反映される

location @puma_<%= fetch(:nginx_config_name) %> {
...

<% if fetch(:nginx_use_ssl) -%>
    proxy_set_header X-Forwarded-Proto https;
<% else -%>
    proxy_set_header X-Forwarded-Proto http;
<% end -%>

↓ 変更

proxy_set_header X-Forwarded-Proto https;

この作業をしないとPOSTリクエストがhttpsの時はできないので要注意
https://www.cotegg.com/blog/?p=1850

Git

プッシュ済みコミット取り消し、ローカルの状態をプッシュ

プッシュ済みコミット取り消し

ターミナル
$ git log
戻したいコミットの地点のIDをコピー
$ git reset --hard ID

ローカルの状態をプッシュ
リモートの方がコミットが進んでるためrejectするので

ターミナル
$ git push -f origin master
ローカルの状態を最新としてリモートを更新

チーム開発時の -fは要注意

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

初心者の気持ちがわかる!! Railsチュートリアル第1章(1.3)

初心者の気持ちがわかる!!Railsチュートリアル 1.3

Railsチュートリアル (1.3)

1.3 最初のアプリケーション

さっそくWebページに「hello,world!」を表示してみましょう。

と、その前に今回のRailsプロジェクトで使うためのenvironmentディレクトリを作成しましょう。(Cloud9 IDEを使用している人は元からあるので不要)

リスト1.2
$ cd                  # ホームディレクトリに移動する
$ mkdir environment     # 'environment' ディレクトリを作成する
$ cd environment/       # 'environment' ディレクトリに移動する

実際に使いながら学びたい人は

ドットインストールのUNIXコマンド入門 一般ユーザー編 (全24回)
をやってみよう。

cdやmkdirなどのunixコマンドは以下で確認。
スクリーンショット 2020-04-04 16.54.03.png

Q.UNIXコマンドとは?
A.
簡単に言うと、マウスで視覚的にフォルダを開いたり削除したりしているものをターミナルにコマンドを入力することで実行することが出来るもの。

次にリスト1.1でインストールしたRailsを使って最初のアプリケーションを作成します。

リスト1.3
$ cd ~/environment (cdのディレクトリに移動)
$ rails _5.1.6_ new hello_app (hello_appディレクトリを作成)

environmentディレクトリの中にhello_appというディレクトリが作成され、hello_appの中に大量のディレクトリができている。これはrails5.1.6を使うことで一気に生成されている。(時短、効率化に繋がっているということ?)
本来Webアプリケーションのディレクトリをどう構成するかは自由であるが、このように標準化されていることで、他の開発者が見ても、コードが読みやすくなっている。

1.3.1 Bundler

アプリケーションに必要なgem(アプリ作成に必要な便利グッツ?)をインストールします。
その際、リスト1.3で生成されたhello_appの中にあるgemfileを変更しインストールしていきます。それをbundlerを実行するといいます。(terminalにはbundle installと記載し実行する。)

実は既にrailsアプリケーションを新規作成すると自動でbundlerを実行し、既存のgemfile(リスト1.4)をインストールしている。

それをリスト1.5のように書き換え、bundlerを実行し、インストールする。

gemfileの場所
スクリーンショット 2020-04-04 17.50.26.png

リスト1.5以下のように書き換える
source 'https://rubygems.org'

gem 'rails',        '5.1.6'
gem 'puma',         '3.9.1'
gem 'sass-rails',   '5.0.6'
gem 'uglifier',     '3.2.0'
gem 'coffee-rails', '4.2.2'
gem 'jquery-rails', '4.3.1'
gem 'turbolinks',   '5.0.1'
gem 'jbuilder',     '2.6.4'

group :development, :test do
  gem 'sqlite3',      '1.3.13'
  gem 'byebug', '9.0.6', platform: :mri
end

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

# Windows環境ではtzinfo-dataというgemを含める必要があります
gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby]

ターミナルで以下をbundlerを実行

$ cd hello_app/  (hello_appディレクトリへ移動)
$ bundle install 

ここで以下のようなエラーが出た場合
スクリーンショット 2020-04-04 18.07.15.png

$ bundle update

を実行する。

1.3.2 rails server

本文の写真を見ながら、新しいターミナルを開き、サーバーを起動します。
(hello_appディレクトリに移動するのを忘れないように!)

リスト1.6
$ cd ~/environment/hello_app/
$ rails server

ここで私はpreviewボタンがないじゃないか〜!!と思いました。
スクリーンショット 2020-04-04 18.13.24.png
画面幅を広げると出てきますよ 笑
スクリーンショット 2020-04-04 18.16.03.png

演習

1.
$ ruby -v
ruby 2.6.3p62 (2019-04-16 revision 67580) [x86_64-linux]

ruby 2.6.3である。

2.
$ rails -v
Rails 5.1.6

rails 5.1.6である。
どちらも一致している。
スクリーンショット 2020-04-04 18.21.37.png

1.3.3 Model-View-Controller (MVC)

Railsアプリケーションの全体的な仕組みについてざっくり見ておきます。
後で、しっかり理解しまーす。

1.3.4 Hello,world!

hello,world!を表示させよう!
コントローラーアクションを追加することで表示させてみます。

まずコントローラーの確認

$ ls app/controllers/*_controller.rb
app/controllers/application_controller.rb(結果一つしかない)

app/controllers/の中にある~~controller.rbというファイルを表示して〜、とお願いしている。
今は一つしかありません。

それではapp/controllers/application_controller.rbを開いて記入してみる。
ファイルは以下の場所にあります。
スクリーンショット 2020-04-04 18.41.37.png

以下のように記載する。

リスト1.7(app/controllers/application_controller.rb)
class ApplicationController < ActionController::Base
  protect_from_forgery with: :exception

  def hello
    render html: "hello, world!"
  end
end

簡単に説明すると、defの右にある”hello"が呼び出されると、renderメソッドにより「hello,world!」をテキストとして表示してください。って感じです。
現状は書き方など深く理解しなくてもいいですよ〜
(defで定義した"hello"はなんでもいい"world"でもなんでもいい。定義したものを呼び出す時のために名前をつけている。)

それでは先ほどサーバーで呼び出したデフォルトページを表示するのではなく、定義したものを呼び出して「hello,world!」を表示してみます。

そのために、Railsのルーター (router) を編集します。

リスト1.9(config/routes.rb)
Rails.application.routes.draw do
  root 'application#hello'
end

上記の内容はrootURLにアクセスした場合、applicationコントローラーのhelloというアクションを呼び出し表示する。

以上で先ほどのdefaltページでリロードボタンを押すか[command+r]を押すとページが更新され、「hello,world!」が表示される。

演習

1.解答

1.(app/controllers/application_controller.rb)
class ApplicationController < ActionController::Base
  protect_from_forgery with: :exception

  def hello
    render html: "hola, mundo!"
  end
end

上記のようにアクション内容を書き換える

2.解答

2.(app/controllers/application_controller.rb)
class ApplicationController < ActionController::Base
  protect_from_forgery with: :exception

  def hello
    render html: "¡Hola, mundo!"
  end
end

3.解答

3.(app/controllers/application_controller.rb)
class ApplicationController < ActionController::Base
  protect_from_forgery with: :exception

  def hello
    render html: "goodbye, world!"
  end
end
3.(config/routes.rb)
Rails.application.routes.draw do
  root 'application#goodbye'
end

上記のように書き換える。

筆者コメント

普段文章を書かないせいか、理解不足のせいかなかなか時間がかかります。どこまで理解して、どこまで書こうか、線引きが曖昧なのが問題??
しかし、まとめるには今まで以上の理解が必要で、なんとなくわかった気になって進んでしまうのが防げるのでいいかなと思っています。
とりあえず出来るところまでやってみよう!!

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

railsで行うページネーション

やり方

kaminariをgemでインストール

ページ移行を使うviewのアクションの中で、ページネーションを実施

viewの方でページ移行を記述
・今どのページか
・他のページに移動するためのリンク
・全部でデータ数はどれだけかを示す

デザイン

bootstrapをパーシャルテンプレートを追加する。パーシャルテンプレートとは、renderのためのテンプレートで共通で、使用することでアプリ内でデザインを統一できる。

1ページの表示件数を変更する

perスコープを使用する

kaminariの設定

デフォルトで1ページの表示件数を変更したければ、config/initializers/kaminari_config.rbで設定をする

参考文献

現場で使える Ruby on Rails 5速習実践ガイド

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

素人がWebサービスを自分で作る備忘録(前準備編)1

この記事の目的

この記事は自分が初めてWebサービスを作るにあたっての備忘録的な意味合いが強いです。
その為、エンジニアリング経験者の方にとっては特に為になる内容ではないと思われますが、ズブの素人が初めてサービスを開発しようと思ってからの成長日記だと思って見てもらえると嬉しいです。

また、同じような未経験者の方がWebサービスを開発しようとしたとき、何かの手助けになれれば嬉しいと思います。

現在のレベル

・Progate学習済み(HTML&CSS、Javascript、React、Ruby、RubyonRails、Git、CommandLine、SQL)
・Railsチュートリアル 二周

個人でWebサービスを作る際にはまず、何のために作るのか決めそれに適した基礎知識を習得する必要が出てきます。
今回は、第一の目標として今まで学習してきた内容のアウトプット、第二の目標はモダンなIT企業へのジョブチェンジするためのポートフォリオ作成ということで行っていきます。
その為、Web業界で今現在多く使われているRailsを選択しました。
Rails学習のバイブルとして多くに人に利用されているRailsチュートリアルですが、これを内容を理解するためには学習のための学習が必要となってくる(Reactを除く)。その学習にはProgateがおすすめ!

Webサービスの企画

上記の学習を終えたところで実際に、Webサービスの開発に入っていきます
しかし、いきなりコードを書けと言われて書くことはできないはずです!なぜなら何を作ろうとか、どのような機能を持たせようとか、そういったところの話を先にやらないと作れるわけがないんです!

 1. 何のために作成するのか
 2. どのような物を作るのか
 3. どういった機能を盛り込むのか

このようなところから考えていきたいと思います。

1に関しては上でも書いた通り、知識のアウトプット、ポートフォリオ作成です。

2に関しては

 ・今まで学んだ知識 +αで作れる物(大きなことを言うと後で後悔しそうなので)
 ・未経験からの転職で有利になりそうな機能を実装すること
 ・自分が作りたい物(コロナとかで外に出れないので他の人と繋がれるサービスとか)

を条件とします。
 
3に関しては

 ・ユーザー管理機能
 ・投稿機能
 ・投稿一覧、投稿詳細機能
 ・画像ファイルアップロード機能
 ・ページネーション機能or無限スクロール機能
 ・DBテーブルのリレーション管理
 ・単体、統合テスト
+αの内容
 ・ReactによるJavascriptライブラリ
 
を取り入れていこうと思います。

次では具体的なサービスの形を考えていくことについて書いていこうと思います。

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

【Rails】enum_helpを用いてセレクトボックスを作成

はじめに

image.png
こんな感じのフォームを作ります。
観測した限りでは一番シンプルな書き方です。問題などございましたらご指摘をお願いします。

環境

ruby 2.5.3
Rails 5.2.4.2
enum_help (0.0.17)
rails-i18n (5.1.3)

作成法

enumの設定

models/user.rb
class User < ApplicationRecord
  validates :name, presence: true, length: { maximum: 30 }
  validates :description, length: { maximum: 300 }
  validates :privacy, presence: true

  enum privacy: { published: 0, closed: 1 }
end

booleanにしていないのは、今後限定公開などの機能を追加することを想定しているためです。
このようにenumを設定することで、データベースでは数値の値を文字列として擬似的に扱うことができます。
また、この値は文字列でもシンボルでも指定することができます。

user.privacy
=> "closed"
user.privacy = 0
=> 0
user.privacy
=> "published"
user.privacy = :closed
=> :closed
user.privacy
=> "closed"

i18nの設定

Gemfile
gem 'rails-i18n'
gem 'enum_help'

必要なgemを記述して、$ bundle installでインストールします。
そして、application.rbに以下の設定を追加します。

config/application.rb
module TestApp
  class Application < Rails::Application
    # 言語・タイムゾーンを日本に設定
    config.i18n.default_locale = :ja
    config.time_zone = 'Tokyo'
    config.active_record.default_timezone = :local
    # config/locales/配下の全てのrb, ymlファイルを読み込み対象とする
    config.i18n.load_path += Dir[Rails.root.join('config', 'locales', '**', '*.{rb,yml}').to_s]
  end
end

config/locales配下に日本語化用のファイルを用意します。(ファイルの場所や名前は自由)

config/locales/models/ja.yml
ja:
  activerecord:
    models:
      user: ユーザー
    attributes:
      id: ID
      created_at: 作成日時
      updated_at: 更新日時
      user:
        name: 名前
        description: 自己紹介
        privacy: 公開設定
  enums:
    user:
      privacy:
        published: 公開
        closed: 非公開

formの作成

form.html.slim
= form_with model: @user, local: true do |f|
  = f.label :name
  = f.text_field :name
  br
  = f.label :description
  = f.text_area :description
  br
  = f.label :privacy
  = f.select :privacy, User.privacies_i18n.invert
  br
  = f.submit

こんな感じです。ポイントはセレクトボックスの値です。
= f.select プロパティ名, 選択肢の配列とすればいいので、

  = f.select :privacy, [['公開', 0], ['非公開', 1]]

これでも可能ですが、なんか嫌です。モデルと関連付けたい。
そこで、gemの出番です。'enum_help'によってそれぞれ以下のようなインスタンスメソッドとクラスメソッドが使えるようになります。

user.privacy_i18n
=> "公開"
User.privacies_i18n
=> {"published"=>"公開", "closed"=>"非公開"}

これを利用して[[表示される選択肢, 渡される値], [表示される選択肢, 渡される値]]の形の配列に置き換えます。

User.privacies_i18n.map { |k, v| [v, k] }
=> [["公開", "published"], ["非公開", "closed"]]
User.privacies_i18n.invert.to_a
=> [["公開", "published"], ["非公開", "closed"]]

どちらも同じですが、後者の方が文字数が少なくシンプルかなと思います。

(追記)
これだけ書いておいて何ですが、配列に変更する必要はありませんでした。ハッシュのままでも行けました。
ということで、User.privacies_i18n.invertが最短です。
そして五年前の記事で既に言及していたので、ここに恥を晒しておきます。

リンク

Enums | Active Record クエリインターフェイス - Railsガイド
Rails 国際化 (i18n) API - Railsガイド

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

【Rails】Railsアプリでfont-awesome(5系)を使えるようにするには

Font-awesomeとは

Web上で無料で使えるアイコンを提供しているツール

Font-awesomeをアプリに読み込む

gemでインストールする

fontawesome4系では「gem 'font-awesome-rails'」でインストールできていたが、fontawesome5系には対応していないとのこと。よって最新版を使用するために次のgemをGemfileに追加しましょう。

gem 'font-awesome-sass'

gemを追加したらbundle install

$ bundle install

その後はapp/assets/stylesheets/application.scssにfont-awesomeを引き込んでおきましょう。
今回はscssを使う場合を想定しています。

@import 'font-awesome-sprockets';
@import 'font-awesome';

これで5系に対応しているfontawesomeがアプリ内で使えるようになる。

参考

font-awesome-sassについて/ピカワカ

gem 'font-awesome-rails'は最新版に対応してない... 代わりにfont-awesome-sassを使おう!

font-awesome-railsは5系をサポートしているのか?について

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

クラスとインスタンスで使うメソッドなど

ゲッター

あるクラスのインスタンスのインスタンス変数の値を返すだけのメソッドのことです。これを定義することで、インスタンスから自身の持つインスタンス変数の値を取得することができます。
【例】MovieクラスとTicketクラスがあるとします。

movie.rb
class Movie
  def initialize(title, fee, start_date, end_date)
〜省略〜
  end

  def title  #ここから
    return @title
  end

  def fee
    return @fee
  end   #ここまでがゲッターの定義です
end
ticket.rb
class Ticket
  def initialize(movie)
    @title = movie.title   #movieクラスのインスタンス.titleで参照できます
    @fee = movie.fee   #movieクラスのインスタンス.feeで参照できます
  end
end

インスタンスは自身のインスタンス変数titleやインスタンス変数feeの値を返します。つまり、movieクラスのインスタンス.titleなどとすることで、それぞれの値を取り出せるようになったのです。

require

あるファイルから他のファイルの記述内容を参照するためのメソッドです。
例えばhoge.rbとfuga.rbいうファイルがあった時、fuga.rbにrequire "./hoge"と書くことでfuga.rbにhoge.rbの記述が書かれていることにしてくれます。
なぜまとめて書かないかというと、クラスの中身が大きくなってきた時に不便で、細かく分けると管理しやすいからです。
【例】

xxx.rb
require './movie'
require './ticket'

Dateクラス

【例】

xxx.rb
require './movie'
require './ticket'
〜省略〜
oceans_eleven = Movie.new(
                          "Ocean's Eleven",
                          1800,
                          Date.new(2020, 4, 4),
                          Date.new(2020, 6, 4)
                )

Date.new()は日付を表現するためのクラスです。Dateクラスを使って日付を扱えば、うるう年など日付関連の計算が楽になります。Dateクラスを利用するには明示的にrequireを使って読み込む必要があります。
Dateクラスのインスタンスには、第一引数から順に年、月、日の順で数字で値を渡します。
Rubyの公式ドキュメント

セッター

インスタンス変数の値を更新するためのインスタンスメソッドです。
【例】セッターを使用せずに値を更新する場合

yyy.rb
  def initialize
    @movies = []
  end
xxx.rb
   @movies = 更新したい値

【例】セッターを使用し値を更新する場合

yyy.rb
  def initialize
    @movies = []
  end

  def movies = (movies)   #セッターを定義してます
     @movies = movies
  end
xxx.rb
   yyy.moveis = [A,B,C]

「=が一つの代入式」に見えるのは、メソッド名に=が含まれていたからです。Rubyはメソッド名であっても半角スペースを無視するため、このようなことができます。

self

例えば映画の選択肢が出て、どれかを選択できる状態にできるとします(今回は1つしか設定していませんが、)。メッセージを出すためにputsメソッドを、ユーザーが選択肢を選べるようにgetsメソッドを使います。

puts "どの映画を見ますか?"
    i = 0
    self.movies.each do |movie|
      puts "#{i} #{movie.title}: #{movie.fee}円"
      i += 1
    end
    gets

インスタンスメソッドの中でselfと書くと、そのインスタンスメソッドを使っているインスタンス自身を参照できます。
つまり、self.moviesの返り値は今回の場合xxx.rbにある[oceans_eleven]となります。その後、eachメソッドで一つ一つのmovieが参照され、それぞれのタイトルと金額が並んで表示される形になります。

yyy.rb
class Yyy
  def initialize
    @movies = []
  end

  def movies
    @movies
  end

  def movies=(movies)
    @movies = movies
  end

  def display_menu
    puts "どの映画を見ますか?"
    i = 0
    self.movies.each do |movie|
      puts "#{i} #{movie.title}: #{movie.fee}円"
      i += 1
    end
    gets
  end
end

attr_accessor

ゲッター、セッターを簡単に定義できるメソッドです。
【例】

class Dog
  attr_accessor :name, :type, :age
end

以下の定義と同様の意味合いです。
【例】

class Dog

  def name
    @name
  end

  def name=(name)
    @name = name
  end

  def type
    @type
  end

  def type=(type)
    @type = type
  end

  def age
    @age
  end

  def age=(age)
    @age = age
  end

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

railsでのcsv形式ファイルの出力、入力

CSVファイルとは

CSVファイルとはモデルのインスタンスの中身を外部へ出力したり、外部から入力するのに必要。具体的にいうと、出力は他のソフトウェアに出力するために使い、入力は他のソフトウェアからの大量データの一括などに使われる。

準備
config/application.rb
require 'csv'
#アプリ全体でcsvが使えるようにライブラリを取り入れる

インスタンスの中身をCSV出力する方法

今回の例ではtaskモデルの全インスタンスの中身をCSVファイルで出力する

models/task.rb
#この関数ではcsv_attributesという配列変数を定義
  def self.csv_attributes
    ["name", "description", "created_at", "updated_at"]
  end

#CSV形式でインスタンスの中身を出力できるようにする
  def self.generate_csv
    #ここでcsv形式の配列csvを定義する、csv形式の配列に代入していくと列ベクトルができる?
    CSV.generate(headers: true) do |csv|
      #まずこのcsvに上記で定義したcsv_attributesを1行目として代入
      csv << csv_attributes
      all.each do |task| #taskを一つ一つ取り出して
        #csvの次に行にtaskの値を代入していく
        csv << csv_attributes.map{ |attr| task.send(attr) }
      end
    end
  end


定義したgenerate_csvを読み出すためにcontrollerに記述する

tasks_controller.rb
  def index #名前付きURLは/tasks
    .
    .
    .

    respond_to do |format| #この意味はURLのformatによって、新たに変更を加えるよという意味
      format.html #format.htmlの場合は、つまりURLが/tasksの時はという意味。format.htmlの後に処理が書かれていないため何もせず画面遷移する
      format.csv { send_data @tasks.generate_csv, filename: "tasks-#{Time.zone.now.strftime('%Y%m%d%S')}.csv" }
    #format.csvの場合は、つまり/task.csvになっている場合はという意味。その場合は後述の処理をする。ここではsend dataをしているので、データをブラウザからダウンロードされるようにしている
    end
  end


では/task.csvでアクセスするにはどうしたらいいのか。以下のようなリンクならアクセス可能

= link_to 'エクスポート', tasks_path(format: :csv), class: 'btn btn-primary mb-5'

CSVデータを入力してデータ保存する方法

モデルファイルの中で受け取った値をデータとして保存する関数を定義する

models/task.rb
  def self.import(file)
    CSV.foreach(file.path, headers: true) do |row| #受け取ったCSVファイルを行ごとに取り出す、その時1行目headerには項目が書いてあるので、1行目は無視する
      task = new #Task.newと同価
      task.attributes = row.to_hash.slice(*csv_attributes) #taskの属性に順番にデータを格納していく。詳しくは調べてください
      task.save!
    end
  end


controllerの中で入力保存してリダイレクトするためのアクションを作る

tasks_controller.rb
  def import 
    current_user.tasks.import(params[:file])  #現在のuserのtaskにimportを発動させる
    redirect_to tasks_url, notice: "タスクを追加しました"
  end

ちなみにimportの読み出し方がcurrent_user.tasks.importの理解が意外と難しいのでメモ。モデルファイルの中でself.メソッド名で定義したメソッドはクラスメソッドとなるため、インスタンスではなくても使用できる。

現在taskモデルはuserモデルに従属しているのcurrent_user.tasksとすることで、今のユーザーのタスクモデル(クラス)のメソッドとして使用している。参考文献はRailsのmodelクラスのselfが曖昧だったので


ルーティング設定

routes.rb
  resources :tasks do
    post :import, on: :collection 
  end

c写真など大きなデータはURLにidとして挟み込めないので、collecionを使用することで、URLの引数として受け取れるようになる


入力画面設定

= form_tag import_tasks_path, multipart: true, class: 'mb-5' do
  = file_field_tag :file
  = submit_tag "インポート", class: 'btn btn-primary'

この入力でファイルを追加する時は拡張子が.csvになっていることを確認してからやりましょう。

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

【備忘録】カラムとプロパティの違いについて

はじめに

カラムプロパティって言葉でてきて混乱したのでめっちゃ簡単にまとめてみました~?

カラムとプロパティの違い?

カラム:データーベース側の呼び方

プロパティ:サーバーサイド側の呼び方

結論:呼び方が違うだけで大体一緒のこと

カラムちょっと詳しく?

カラム.png

画像(データーベース)の id や user_id 、 name などのこと。

プロパティちょっと詳しく?

例:form_for
<%= form_for @user do |form| %>
<%= form.label :name %> <%= form.text_field :name %>
<%= form.label :address %> <%= form.text_area :address %>
<%= form.submit %>
<% end %>

このようなフォームを作るときに出てくる

<%= form.text_field :name %>
<%= form.text_area :address %>

この部分の :name 、 :address のこと

さいごに

form.text_fieldの引数に、『カラム名を指定する』っていうのはあまり正しくなくて、『プロパティを指定する』っていうのが正しいみたいです~?

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

【HTTP/Rails】PATCHとPUTについて

はじめに~

Railsで作成したアプリで、投稿したものを更新するよ~ってときに使うHTTPがPATCHとPUT!

ページを表示するときはGET、投稿をするときはPOST、投稿とかを削除するときはDELETE、
あれ、なんで更新だけ2つも(PATCHとPUT)あるんだろ、、、
と気になってしまったので調べてみました〜?‍♀️?‍♂️

そもそもHTTPとは?

HTTP = Hyper Text Transfer Protocol(ハイパーテキスト転送プロトコル)、、、???

めっちゃざっくり言うと、
『クライアント(ユーザー)とサーバ間のやりとりを可能にする約束事!』

なんで約束事があるのかというと、たくさんのサイトが好き勝手にサーバーとやりとりする方法決めたら管理とかめっちゃ大変だから!

がんばってる.png

あとはHTTPとは?のおすすめ記事みてみてください~?

PATCHとPUTの違いとは?

PATCH:リソースの部分更新

PUT:リソースの完全な置き換え

もともとRails3まではPUTで更新を行っていたみたいです!
しかし一般的な更新は全てのリソース(投稿とかのデータ)を更新することはほとんどないのでRails4以降ではPATCHを使った更新が主流になりました?

最後に

PUTで更新できるならなんでもいいや~?じゃなく、
もっと効率的に更新するには??って頭のいい人が考えてPATCHが生まれたのかなと思うとなんかすげって思いました(小者感)

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

2つの条件の組み合わせで検索する

リレーションのできている異なる2つのテーブルのカラムを用いて検索出来るようにする。

viewで検索窓を実装

areaテーブルのarea_name(地域)とshopテーブルのname(お店の名前)の組み合わせで検索できるよう実装する。

~.html.erb
#フォームで:search_area, :search_shopを受け取れるようにする。

#shops_pathへ送り、 shops/indexページで検索結果を表示させた
<%= form_tag(shops_path, :method => "get") do %> 
 <%= text_field_tag :search_area,"",placeholder: "地域" %>
 <%= text_field_tag :search_shop,"",placeholder: "お店の名前" %>
 <%= submit_tag "検索", :name => nil %>
<% end %>

フォームで入力された情報をコントローラで受け取る

searchというメソッドにフォームで入力された:search_area:search_shopの2つの引数を渡す。

shops_controller.erb
def index
  @shops = Shop.search(params[:search_area],params[:search_shop])
end

現段階ではこのsearchというメソッドが定義されていない。
その為、このsearchメソッドをshopモデルで定義する必要がある。

検索するためのsearchメソッドをモデルに定義する

shop.rb
#引数にsearch_area と search_shopの2つを取るsearchメソッドを定義
def self.search(search_area,search_shop)
#どちらも空白の場合は全ての店舗情報を返す
  if search_area.blank? and search_shop.blank?
    Shop.all
#地域が無効な入力の時は店舗名だけで検索する
  elsif Area.find_by(area_name: search_area).nil?
    Shop.where("shops.name LIKE ?", "%#{search_shop}%")
  else
#上記以外の場合は地域と店舗名両方に合致するものを検索する
    search_area_id = Area.find_by(area_name: search_area).id 
#引数として受け取ったsearch_areaの地域名を地域idに変換する ⬆️
    Shop.where(area_id: search_area_id).where("shops.name LIKE ?", "%#{search_shop}%")
#shopテーブルのarea_idとnameが一致する店舗の一覧を取得する⬆️
  end
end

LIKE ?って何?→ https://qiita.com/seri1234/items/765423c2c46ca4114da0

これで先ほどコントローラに記述していたsearchメソッドが使えるようになりました。

おわり

joinsメソッドとかgemの ransackを使えばもっとsearchメソッドを綺麗にできそうです。

なるべくgemに頼りたくないなと考えつつ、いろいろ調べたもののどうにも上手くできずsearchメソッドの中身がかなり力技になってしまいました。

もっとスマートな記述ができると思うので後々更新したいと思います。

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

deviseを使ったユーザー管理機能の実装

deviseを使ったユーザー管理機能(ログイン・ログアウト)の実装手順

今回はトップページの作成も合わせて行います。
トップページを既に作成済みの方は、ログイン機能の実装のみお読みください。

作業の流れ

・トップページの作成

  1. homesコントローラ作成
  2. homesコントローラの編集
  3. トップページのビュー作成
  4. ルーティングの編集

・ログイン機能の実装

  1. gem 'devise' のインストール
  2. deviseの設定ファイル作成
  3. ログイン機能付きUserモデルの作成
  4. データベースに反映

トップページの作成

早速作業に入りましょう

1. homesコントローラの作成

今回は例として homesコントローラ を作成します。

ターミナル
$ rails g controller homes

*コマンドを実行すると以下のようなものが作成されます。

Running via Spring preloader in process ****
      create  app/controllers/homes_controller.rb
      invoke  erb
      create    app/views/homes
      invoke  test_unit
      create    test/controllers/homes_controller_test.rb
      invoke  helper
      create    app/helpers/homes_helper.rb
      invoke    test_unit
      invoke  assets
      invoke    coffee
      create      app/assets/javascripts/homes.coffee
      invoke    scss
      create      app/assets/stylesheets/homes.scss

2. homesコントローラの編集

続いて先ほど作成された、homes_controller.rbindexアクション を定義します。

app/controllers/homes_controller.rb
class HomesController < ApplicationController
    def index
    end
end

3. トップページのビュー作成

次に homesコントローラindexアクションが呼ばれた際に返すビューを用意します。
app/views内のhomesディレクトリにindex.html.erbを新規作成してください。
Image from Gyazo
後でちゃんとこのページが呼ばれているか確認するため、index.html.erbにテキストを入力しておきます。
Image from Gyazo

4. ルーティングの編集

トップページを表示するためのルーティングを設定します。

config/routes.rb
Rails.application.routes.draw do
    root to: "homes#index"
end

これで トップページの作成 は完了です。
最後にローカルサーバを起動して、ちゃんと表示されるか確認しておきましょう。
rails sを実行してからこちらをクリック➡️ http://localhost:3000/
白くて分かりづらいですが、以下のような画面が表示されればOKです。
Image from Gyazo

ログイン機能の実装

それでは ログイン機能の実装 に入ります。

1. gem 'devise' のインストール

まずは今回ログイン機能の実装に使用する'devise'というgemをインストールします。
Gemfileの最後の行にgem 'devise'を追記してください。

Gemfile
====== 省略 ======

gem 'devise'

続けてターミナルでgemのインストールを実行します。

ターミナル
$ bundle install

補足)gem listコマンドを実行し、きちんとgemがインストールされているか確認しておきましょう。

ターミナル
$ gem list

*** LOCAL GEMS ***

==== 省略 ====
debug_inspector (0.0.3)
devise (4.7.1)     ⬅️deviseがインストールされていればOK
did_you_mean (1.2.0)
diff-lcs (1.3)
domain_name (0.5.20190701)
==== 省略 ====

2. deviseの設定ファイル作成

次にdeviseを使用する際に必要な設定ファイルを作成します。
deviseに関するファイルなのでdevise専用コマンドを使用します。

ターミナル
$ rails g devise:install

Running via Spring preloader in process ****
      create  config/initializers/devise.rb
      create  config/locales/devise.en.yml

3. ログイン機能付きUserモデルの作成

ユーザー情報を登録するためのモデル、マイグレーションファイルなどを作成します。
ここでもdevise専用コマンドを使用します。

ターミナル
$ rails g devise user

Running via Spring preloader in process ****
      invoke  active_record
      create    db/migrate/20200404053603_devise_create_users.rb
      create    app/models/user.rb
      invoke    test_unit
      create      test/models/user_test.rb
      create      test/fixtures/users.yml
      insert    app/models/user.rb
       route  devise_for :users

※注意:userの前にmodelをつけるとmodelモデルが作成されてしまいます。

上記コマンドを実行すると、自動的に以下のようなルーティングが生成されます。

config/routes.rb
Rails.application.routes.draw do
    devise_for :users   ⬅️自動で生成
    root to: "homes#index"
end

4. データベースに反映

最後にユーザーの情報を登録できるようにするため、マイグレーションファイルをデータベースに反映します。

ターミナル
$ rails db:migrate

== 20200404053603 DeviseCreateUsers: migrating ================================
-- create_table(:users)
   -> 0.0122s
-- add_index(:users, :email, {:unique=>true})
   -> 0.0113s
-- add_index(:users, :reset_password_token, {:unique=>true})
   -> 0.0128s
== 20200404053603 DeviseCreateUsers: migrated (0.0366s) =======================

以上でログイン機能の実装は完了です。
早速確認してみましょう。

ローカルサーバを起動し、URLの末尾にusers/sign_upと入力して、新規登録画面を確認しましょう。以下のような画面が表示されます。
Image from Gyazo
続いて、URLの末尾をusers/sign_inに変更するか、sign_up画面のlog inのリンクをクリックして、ログイン画面を確認しましょう。
Image from Gyazo

画面が確認できればOKです。お疲れ様でした。
また、ここまでお読み頂きありがとうございました。

追加補足

補足1) 4. データベースに反映 を忘れていた場合、以下のようなPendingMigrationErrorというエラーが発生してしまいます。要は、データベースに反映されていないマイグレーションファイルが残ってるよ。という意味です。
Image from Gyazo

補足2)もし rails db:migrate を実行したはずなのに上記のエラー画面が表示される場合は、rails db:migarate:status コマンドを実行してマイグレーションの状況を確認しましょう。

ターミナル
$ rails db:migrate:status

database: sample_app_development

 Status   Migration ID    Migration Name
--------------------------------------------------
  down    20200404053603  Devise create users

補足3)上記のように Status がdownの場合はきちんとマイグレーションファイルの反映が行われていません。再度db:migrate を実行してください。Status が UP になればOKです。

補足4)上記のエラーとは別に、もし以下のようなNoMethodErrorというエラーが発生した場合は、ローカルサーバを再起動しましょう。
Image from Gyazo

追加実装

ログイン画面へのリンクをつくるのが面倒なので、ログインしていないとトップページにいけないようにしてしまいましょう。追記するのは以下の一点です。

app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
  before_action :authenticate_user!  ⬅️この一行を追記
end

補足)このままではログアウトができないので、ログアウトのリンクだけ作成しておきます。
index.html.erb を以下のように編集しましょう。

app/views/homes/index.html.erb
これは"homes#index"ページです。
<br>
ログアウトしますか?
<%= link_to "はい", destroy_user_session_path, method: :delete %>

ログイン後のビューは以下のようになります。
Image from Gyazo

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

ログアウト時に確認ダイアログを表示させたい…!(Rails)

はじめに

ログアウトボタンや、削除ボタンを押したとき、本当に実行するのか確認するアラートを表示させたいなって思うこと、ありますよね!
やり方を調べてみたら、めちゃめちゃ簡単だったので、ご紹介します。

今回やりたかったこと

Image from Gyazo

こんな感じで、ログアウトボタンを押した際に、確認のアラートを表示させたい…!

「でもどうせ、JavaScriptの知識が必要なんでしょ…?」

そう思っている方、多いと思います。私もそうでした。

安心してください。

JavaScriptの知識は必要ありません。

方法

早速やり方です。

アラートなしの場合

<%= link_to destroy_user_session_path, method: :delete do %>
  <i class="fas fa-sign-out-alt">ログアウト</i>
<% end %>

アラートなしのhtml.erbはこんな感じになってます。
ここに少し付け足すだけで、アラートが実装出来ちゃいます。

アラートありの場合

<%= link_to destroy_user_session_path, method: :delete, data: {confirm: "ログアウトしますか?"} do %>
  <i class="fas fa-sign-out-alt">ログアウト</i>
<% end %>

link_to の行に、
data: {confirm: "ログアウトしますか?"}
を追加しました。

これで終了です。

data: {confirm: ""} は、Railsのlink_toのオプションで、
"" の中に、アラートで表示させたいメッセージを入力する事ができます。

え、こんなに簡単なの…? って、私は知ったときに思いました(笑)

皆さんも是非お試しあれ!

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

Font-awesomeでcouldn't find file 'font-awesome' with type 'text/css'が出たときにやったこと

couldn't find file 'font-awesome' with type 'text/css'がエラーが発生

font-awesome便利ですよね〜

今回はfont-awesomeをインストール後に発生したエラーに対してやったことをまとめました。

font-awesomeをインストールしたとき、、、

gem 'font-awesome-rails'
$ bundle install
/*
 *= require font-awesome
 */


application.css内にも記載し、ページを開くと、、、

1.png

上記のようなエラーが発生
エラー文からfont-awesomeが読み込めていないと予想

application.css内の記述を確認する
割ととんでもない記述をしていたので、修正

2.png

*= require treeの下に記述をrequire_treeの上へ移動
4.png

また、同じエラー文、、、

3.png

その後、何度もエラー内容を調べるも解決の糸口は見えず

gem 'font-awesome-sass'のエラー対処は大量に出てくるのにな、、、

なんなら自分もsassでfont-awesome使っていたので、sassに切り替えようかとまで思ったり、、、


ある程度悩んだ後、もしやと思ってrails sを再起動してみる



すると、、、





起動した

なんでもっと早く再起動しておかなかったんだと

どうやらPCやターミナルをずっと動かしておくと、予期せぬエラーが発生することがあるらしい


人間と一緒で定期的に休ませてあげるのが大事だなと

さて、気を取り直して頑張ろう

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

RSpecの起動をスピードアップする files took n seconds to load が気になるあなたへ

dockerでrspec実行してたのですが、毎回アホみたいにファイルのロードが長かったのでなんとかしようと思いました。

Springを活用する

group :development do
  略
  gem "spring-commands-rspec"
$ bundle install
$ bundle exec spring binstub rspec
$ bin/rspec

springに bin/rspec を作ってもらい、それ経由でrspecを実行します。

できるファイル
bin/rspec

#!/usr/bin/env ruby
begin
  load File.expand_path('../spring', __FILE__)
rescue LoadError => e
  raise unless e.message.include?('spring')
end
require 'bundler/setup'
load Gem.bin_path('rspec-core', 'rspec')

一回目の実行は0から立ち上げるので普通の早さですが、二回目以降はspringがロードしてくれてるので早くなります。

僕の環境での恩恵はこんな感じでした。

before

Finished in 10.9 seconds (files took 1 minute 34.43 seconds to load)
37 examples, 0 failures

after

Finished in 6.71 seconds (files took 4.68 seconds to load)
37 examples, 0 failures

ファイルのロード時間が1分半 → 5秒弱に短縮されました。まじか。

補足

Springはapplication preloaderです。

Spring is a Rails application preloader. It speeds up development by keeping your application running in the background so you don't need to boot it every time you run a test, rake task or migration.

(https://github.com/rails/spring#spring より)

バックグラウンドで走らておくことで、スピードアップを図るものですね。
いまSpringが仕事中かどうかは、

$ bin/spring status

でわかります。
rspecだけでなく、railsやrakeコマンドも早くしてくれるので、活用するとハッピーになれるかもしれません。

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

railsでhaml変換した後にmissing a templateエラーが発生した際の対処方法

railsでhaml:erb2hamlを実行した後にindex.html.hamlのmissing a templateが発生したので解決方法をメモ。

環境

  • rails 5.2.4.2

haml-railsでrails haml:erb2hamlを実行

post_app % rails haml:erb2haml
--------------------------------------------------------------------------------
Generating HAML for app/views/posts/index.html.erb...
Generating HAML for app/views/posts/_nav.html.erb...
Generating HAML for app/views/posts/_main_top.html.erb...
Generating HAML for app/views/posts/_main_bottom.html.erb...
Generating HAML for app/views/posts/_sidebar.html.erb...
Generating HAML for app/views/layouts/application.html.erb...
Generating HAML for app/views/layouts/mailer.html.erb...
Generating HAML for app/views/layouts/mailer.text.erb...
--------------------------------------------------------------------------------
HAML generated for the following files:
    app/views/posts/index.html.erb
    app/views/posts/_nav.html.erb
    app/views/posts/_main_top.html.erb
    app/views/posts/_main_bottom.html.erb
    app/views/posts/_sidebar.html.erb
    app/views/layouts/application.html.erb
    app/views/layouts/mailer.html.erb
    app/views/layouts/mailer.text.erb
--------------------------------------------------------------------------------
Would you like to delete the original .erb files? (This is not recommended unless you are under version control.) (y/n)
y
Deleting original .erb files.
--------------------------------------------------------------------------------
Task complete!
No .erb files found. Task will now exit.

rails haml:erb2hamlは成功したにもかかわらずmissing a templateエラーが発生しました。

missing a template

ActionController::UnknownFormat (PostsController#index is missing a template
 for this request format and variant.

request.formats: ["text/html"]
request.variant: []

NOTE! For XHR/Ajax or API requests, this action would normally respond with
204 No Content: an empty white screen. Since you're loading it in a web
browser, we assume that you expected to actually render a template, not
nothing, so we're showing an error to be extra-clear. If you expect 204 No
Content, carry on. That's what you'll get from an XHR or API request. Give
it a shot.):

テンプレートがないと怒られたのでファイル名やパスを確認するも間違っているところはなさそう。
ここでindex.html.hamlのファイル名をindex.html.erbに変更してみるとエラーが消えました。
ちゃんとコントローラはviews/posts/index.html.erbを呼び出しているようです。

解決方法

rails s でローカルサーバを再起動するだけ!

rails s

いくらファイル名やパスを確認してもおかしなところは見あたらず、1時間くらいなんでやー!っとなっていたので同じ境遇の人に役に立てば幸いです。

参考記事

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

ActiveStorageで画像をアップロードする方法

Active Storage

Active Storageとは

Active Storageとはファイルをアップロードして[undefined]()
モデルに添付してくれるやつ。Active Storageをインストールして使用することで本番環境でクラウドストレージサービス(S3、GCSとか)にファイル(画像とか)をアップロードして、データベース上でActiveRecordモデル(アプリで使うモデル)のインスタンスに紐付けできるということ。

手順

Active Storageをgemを使ってインストール

するとmigrationファイルができる。これは2つのテーブル(モデル)を作るためにできた(active_storage_blobsとactive_storage_attachmentsという名前)。

一つ目のactive_storage_blobsは画像ファイルを管理するためのモデル。
二つ目のactive_storage_attachmentsはActiveRecordモデル(アプリで使うモデル)とactive_storage_blobsの関係性を表すためのモデル。つまりこの二つのモデルの間に存在し、二つのモデルに従属(belong_to)しているテーブル。

上記で説明をしてきたがActiveStorageで画像を紐づける際にこの二つのモデルを意識することは少ない?

95A30DAB-EF6C-4E57-96C6-2F8EE3981D4D_1_105_c.jpeg


db:migrateする

ActiveRecordモデル(appで使用するモデル)ファイルに写真を持つことを記述する

models/task.rb
has_one_attached :image
#一つのtaskに一つのimageがつく。
imageという名前は入力時のform fieldでのタグ名。
tasks/new.html.slim
= form_with model: task, local: true do |f|
.
.
.
 .form-group
    = f.label :image
    = f.file_field :image, class: 'form-control'
#imageというタグ名で値を送信している 

他にもモデルファイルで以下のように記述するとモデルは複数の画像を持つことができる。

models/message.rb
class Message < ApplicationRecord
  has_many_attached :images
end 

参考文献はRailsガイド【Rails 5.2】 Active Storageの使い方

設定

・画像ファイルを管理する場所を指定したい
そんな時はconfig/environmentsで各環境での管理場所を指定する。

development.rb
config.active_storage.service = :local
#開発環境ではファイルを保存する場所をlocalという設定にしている。

localがどういう設定になっているかを知りたい場合は

config/storage.yml
local:
  service: Disk
  root: <%= Rails.root.join("storage") %>
#localという設定はファイルの保存はアプリのstorogeディレクトリで行われる
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ActiveStorageについてまとめさせていただきました

Active Storage

Active Storageとは

Active Storageとはファイルをアップロードして[undefined]()
モデルに添付してくれるやつ。Active Storageをインストールして使用することで本番環境でクラウドストレージサービス(S3、GCSとか)にファイル(画像とか)をアップロードして、データベース上でActiveRecordモデル(アプリで使うモデル)のインスタンスに紐付けできるということ。

手順

Active Storageをgemを使ってインストール

するとmigrationファイルができる。これは2つのテーブル(モデル)を作るためにできた(active_storage_blobsとactive_storage_attachmentsという名前)。

一つ目のactive_storage_blobsは画像ファイルを管理するためのモデル。
二つ目のactive_storage_attachmentsはActiveRecordモデル(アプリで使うモデル)とactive_storage_blobsの関係性を表すためのモデル。つまりこの二つのモデルの間に存在し、二つのモデルに従属(belong_to)しているテーブル。

上記で説明をしてきたがActiveStorageで画像を紐づける際にこの二つのモデルを意識することは少ない?

95A30DAB-EF6C-4E57-96C6-2F8EE3981D4D_1_105_c.jpeg


db:migrateする

ActiveRecordモデル(appで使用するモデル)ファイルに写真を持つことを記述する

models/task.rb
has_one_attached :image
#一つのtaskに一つのimageがつく。
imageという名前は入力時のform fieldでのタグ名。
tasks/new.html.slim
= form_with model: task, local: true do |f|
.
.
.
 .form-group
    = f.label :image
    = f.file_field :image, class: 'form-control'
#imageというタグ名で値を送信している 

他にもモデルファイルで以下のように記述するとモデルは複数の画像を持つことができる。

models/message.rb
class Message < ApplicationRecord
  has_many_attached :images
end 

参考文献はRailsガイド【Rails 5.2】 Active Storageの使い方

設定

・画像ファイルを管理する場所を指定したい
そんな時はconfig/environmentsで各環境での管理場所を指定する。

development.rb
config.active_storage.service = :local
#開発環境ではファイルを保存する場所をlocalという設定にしている。

localがどういう設定になっているかを知りたい場合は

config/storage.yml
local:
  service: Disk
  root: <%= Rails.root.join("storage") %>
#localという設定はファイルの保存はアプリのstorogeディレクトリで行われる
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Rails】現場Railsメモ

執筆中

  • 復習
  • アウトプット前提のインプット

環境

Ruby 2.6.5
Rails 5.2.4.2
MySQL 5.7.28

Chapter1 RailsのためのRuby入門

オブジェクト

  • 言葉によって区別できるあらゆる「モノ」
  • 全てのオブジェクトはobject_idという固有番号をもち、固有の存在として認識される
  • 仕事は何らかの振る舞い(メソッド)をすること

クラス

  • オブジェクトの原型になるもの(設計図)

人間

太郎くん
花子さん
hogeくん
fugaくん

人間がクラスに該当。

インスタンス

  • クラスを元に作成された実態

人間

太郎くん
花子さん
hogeくん
fugaくん

人間未満(言葉不適切?)がインスタンスに該当。

変数

  • 何かのオブジェクトをさし示すことができる、ラベルのような存在

ローカル変数

  • プログラム中の一定の処理の範囲で使われ、その範囲が終わったら捨てられる

インスタンス変数

  • オブジェクトの内部に保持されてオブジェクトが存在する限り一緒に存在する

メソッド

  • オブジェクトの何らかの振る舞い
  • クラスメソッドとインスタンスメソッド

人間
- 名前
- 体
- 喜怒哀楽

太郎くん
- 1秒間にタイピングを10回打てる
花子さん
- 3桁の数字の15個の和を2秒で求められる
hogeくん
- 100mを9.58で走れる
fugaくん
- 1時間で料理を10品作れる

人間クラスのインスタンスである4人は歩く、食べる、笑うことができる。
名前、体、喜怒哀楽クラスメソッドに該当。

人間クラスのインスタンスである4人のそれぞれにしかできない
特殊能力がインスタンスメソッドに該当。

レシーバー

  • メソッドを呼ぶ元のオブジェクト

hoge = 'ほげ'
hoge.length
hoge.class

hoge.lengthhogeがレシーバーに該当。
.lengthStringクラス(前述の人間クラス)がもつメソッドに該当。

返り値(戻り値)

  • メソッドが呼ばれた時に帰ってくる値のこと

属性(Attribute)

  • オブジェクトの抱えるデータ
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む