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

【Rails】credentials.yml を使って管理画面を作る(その2)

■ はじめに

皆さん、こんにちは。
桜も開花して、春の訪れを見た目でも感じられるようになってきましたね。

春の訪れを感じると共に、
年が開けてから随分と時間が経ってしまったのだなと、
やや焦りを感じてしまう自分がいるのですが、皆さんはどうでしょうか?

今回は前回の「【Rails】credentials.yml を使って管理画面を作る(その1)」の続きになります。(前回⇒ https://qiita.com/tsuchiya-yu2/items/ab77a59abb9aef73f187 )

今回は前回以上に作業がありますので、焦らずゆっくり進めていきましょう!

(Qiitaの表示ではコードが見にくくなりそうな箇所に関しては、GitHubのURLを用意しておりますので、適宜ご活用くださいね。)

■ 本記事での試み

前回のその1では、「管理画面は管理者のみアクセス可能で、一般ユーザーがアクセスを試みるとトップページヘリダイレクトするようにする」を目的に実装を進めてきました。

そのため他の細部の部分、特に Post 機能においては、ログインできてしまえば誰でも他者の Post を編集、更新、削除ができてしまう状態にあります。本記事ではまずそうした Post 機能の不備を手直しし、投稿者自身もしくは管理者のみが Post を編集、更新、削除できるようにします。

その作業が完了した後、管理者がパートナーにアクセス権を付与する機能の実装と、パートナーのアクセス可能な範囲の設定(今回は Post の編集・更新・削除権限の付与)を行っていこうと思います。

箇条書きにすると以下の流れになります。

  • Post 機能の手直し
  • 管理者からアクセス権を付与する機能の実装
  • Partner 機能に関するビューファイルの整備
  • Partner が許された範囲での活動ができるまでの諸整備
  • サーバーの起動と実装した部分の確認
  • 本記事の最後に

[DEMO]
下の画像では、左側が管理者として管理画面にアクセスした場合、右側はパートナーとして管理者画面にアクセスした場合になります。管理者にはパートナーの新規登録から削除、ユーザーの削除機能を実装します。パートナーは新規のパートナー登録から削除、ユーザーの削除はできませんが、全ての Post の編集・更新・削除することが可能です。(パートナーに可能な機能は管理者も可能です。)
スクリーンショット 001.png


■ 開発環境・本記事終了時のコード(GitHub)

  • OS:macOS Big Sur 11.2.3
  • Ruby:3.0.0
  • Ruby on Rails:6.1.3
  • ローカル環境DB:Mysql
  • テキストエディタ:Visual Studio Code


■ Post 機能の手直し

それでは、前回実装した Post 機能の手直しから始めましょう。

まずはトップページ(posts#index)の'Edit'、'Destroy'のリンクを、投稿者自身の場合のみ表示させるように直します。

app/views/posts/index.html.erb
<% @posts.each do |post| %>
  <tr>
    <td><%= post.text %></td>
    <td><%= post.user.email %></td>
    <td><%= link_to 'Show', post %></td>
    <% if current_user.id == post.user_id %>   <%# 追加 %>
      <td><%= link_to 'Edit', edit_post_path(post) %></td>
      <td><%= link_to 'Destroy', post, method: :delete, data: { confirm: 'Are you sure?' } %></td>
    <% end %>   <%# 追加 %>
  </tr>
<% end %>

'Edit'、'Destroy'のリンク部分を「 if current_user.id == post.user_id 」「 end 」で囲うことで、ログインしているユーザーのIDと Post に登録されている user_id が一致しているかの分岐を行っています。(一致していれば(trueならば)、'Edit'、'Destroy'を表示させ、そうでなければ非表示にします。)

次に管理画面の投稿一覧の部分を修正して、管理者が Post の'Edit'と'Destroy'にアクセスできるようにします。具体的にはを以下のように td 行を追加します。

app/views/users/index.html.erb
<table>==== 投稿一覧 ====
  <thead>
    <tr>
      <th>投稿ID</th>
      <th>投稿内容</th>
      <th>投稿者ID</th>
      <th>投稿者メールアドレス</th>
      <th colspan="3"></th>
    </tr>
  </thead>

  <tbody>
    <% @posts.each do |post| %>
      <tr>
        <td><%= post.id %></td>
        <td><%= post.text %></td>
        <td><%= post.user_id %></td>
        <td><%= post.user.email %></td>
        <td><%= link_to 'Edit', edit_post_path(post) %>   <%# 追加 %>
        <td><%= link_to 'Destroy', post_path(post), method: :delete, data: { confirm: 'この投稿を本当に削除しますか?' } %></td>
      </tr>
    <% end %>
  </tbody>
</table>

続いてコントローラーも修正しましょう。

app/controllers/posts_controller.rb
class PostsController < ApplicationController
  before_action :set_post, only: %i[ show edit update destroy ]
  before_action :access_post, only: %i[ edit update destroy ]
  # before_action :access_post ※showは誰でも閲覧可能のため除外

  # 省略 #
  def update
    # 管理者も更新できるようにaccess_post_paramsに変更
    if @post.update(access_post_params)
      if current_user.email == Rails.application.credentials[:access_user]
        # updateに成功:管理者であった場合、users#index に遷移
        flash[:message] = "変更が反映されました"
        redirect_to users_path
      else
        # updateに成功:管理者でない場合、posts#show に遷移
        respond_to do |format|
          # 省略 #
        end
      end
    else # updateに失敗:管理者でも投稿者でも posts#show に遷移
      respond_to do |format|
        # 省略 #
      end
    end
  end


  def destroy
    @post.destroy
    if current_user.email == Rails.application.credentials[:access_user]
      # destroy実行後:管理者であった場合、users#index に遷移
      flash[:message] = "変更が反映されました"
      redirect_to users_path
    else
      # destroy実行後:管理者でない場合、posts#index に遷移
      respond_to do |format|
        # 省略 #
      end
    end
  end

  private
    # 省略 #
    def post_params
      params.require(:post).permit(:text).merge(user_id: current_user.id)
    end

    # posts#edit,#update,#destroy の際に
    # 投稿者自身もしくは管理者でない場合は posts#index に遷移
    def access_post
      unless current_user.id == @post.user_id || current_user.email == Rails.application.credentials[:access_user]
        flash[:message] = "アクセスをブロックしました"
        redirect_to root_path
      end
    end

    # posts#update の際に使用
    def access_post_params
      params.require(:post).permit(:text).merge(user_id: @post.user_id)
    end
end

新たに before_action、access_post メソッド、access_post_params メソッド、Rails.application.credentials[:access_user] を利用した分岐などを加えております。

簡易的な説明になりますが、「 before_action :access_post 」で①ログインしているユーザーIDと Post に登録されているuser_idが一致しなかった場合、②ログインしているユーザーが管理者ではなかった場合、3つのアクション(#edit, #update, #destroy)においてはトップページ(posts#index)にリダイレクトさせるようにしています。

「 access_post 」メソッドは、update を実行する際、Post の user_id が投稿者のIDから管理者のIDへと上書きされてしまうのを防ぐようにしています。post_params では投稿者自身が更新を試みるときには問題がないのですが、管理者が更新を試みた際に、Post の user_id の部分が投稿者IDから管理者IDに書き換えられてしまいます。そのため、投稿された Post の user_id はそのままに中身(:text)のみ変更できるように「 params.require(:post).permit(:text).merge(user_id: @post.user_id) 」という形にしております。

先程の before_action :access_post のおかげで、#edit, #update, #destroy アクションを試みる前段階で、投稿者自身か管理者以外はトップページ(posts#index)に遷移させるのを実装できていますが、#update, #destroy アクションが成功した際に管理者は管理画面に、投稿者自身がアクションに成功した場合はトップページ(posts#index)に遷移するようにしたいですよね。

それを実現するために、#update, #destroyのアクション内に「 if current_user.email == Rails.application.credentials[:access_user] 」を使って、それぞれのアクションが成功した後に、ログイン中のユーザーが管理者であるかによって、遷移先を分岐させています。

以上で前回実装した部分の手直しは完了です。お疲れさまでした。
次の項目から管理者がアクセス権を付与する部分に触れていきますので、引き続き頑張りましょう!


■ 管理者からアクセス権を付与する機能の実装(モデル・コントローラーまで)

それでは管理者から他のユーザーに管理画面へのアクセス権を付与する機能の実装に取り掛かっていきましょう!大まかな流れとしては以下のようになります。

  • Partner モデルの作成
  • マイグレーションファイルの編集
  • モデルの関係性の記載
  • ルーティングの追記と User コントローラーの修正
  • Partner コントローラーの作成

● Patner モデルの作成

まずはターミナルで以下のコマンドを入力して Partner モデルを作成します。

ターミナル
$ rails g model Partner

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

先程のコマンドで作成されたマイグレーションファイル(20..._create_partners.rb)を以下のように編集しましょう。

今回は付与するユーザーIDを、一意性制約及び外部キー制約を設けた上でDBに保存させます。(Partner モデルにおいても一意性制約を後ほど設けます。)

grant には付与したユーザーのメールアドレスが保存されるようにしますが、Partner モデルにおいて管理者用に設定したメールアドレスでログインしているユーザーによる付与なのかを検証させます。

db/migrate/20..._create_partners.rb
class CreatePartners < ActiveRecord::Migration[6.1]
  def change
    create_table :partners do |t|
      t.references :user,   null: false, index: { unique: true }, foreign_key: true
      t.text       :grant,  null: false
      t.timestamps
    end
  end
end

編集が済んだらターミナルで rails db:migrate を実行します。

ターミナル
$ rails db:migrate

● モデルの関係性の記載

それでは次に、 User モデル と Partner モデルの関係性をそれぞれに記載しましょう。Partner は User に付与される関係性にあるため、Partner 側は belongs_to :user、User 側は has_one :partner にしました。

app/models/user.rb
class User < ApplicationRecord
  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :validatable
  has_many :posts, dependent: :destroy
  has_one :partner, dependent: :destroy   # 追加
end
app/models/partner.rb
class Partner < ApplicationRecord
  belongs_to :user
  validates :user_id, uniqueness: true
  validates :grant,   inclusion: { in: [Rails.application.credentials[:access_user]] }
end

validates :user_id, uniquenss: true でモデル側にも一意性制約を設けて、モデルとDB両方で一意性制約を設けている形になります。なぜモデルとDBの両方に一意性制約を設ける必要があるのかについては、「Ruby on Rails チュートリアル 第6章 ユーザーのモデルを作成する - 6.2.5 一意性を検証する -」に詳しく記載されているので、そちらを参照してください。

validates :grant, .... ですが、inclusion のバリデーションを使って、Rails.application.credentials[:access_user]で設定した管理者用のメールアドレスが grant で送られているかを検証しています。inclusion のバリデーションについては Rails ガイドを参照してください。

● ルーティングの追記と User コントローラーの修正

続いて、Partner を登録から削除までを行えるようにルーティングを設定していきましょう。今回は管理画面に Partner の一覧を表示させたいと思うので、resources :partners は、#new, #edit, #create, #update, #destroy のアクションに絞ります。

config/routes.rb
Rails.application.routes.draw do
  root to:    "posts#index"
  devise_for  :users
  resources   :posts
  resources   :users, only: [:index, :destroy]
  resources   :partners, only: [:new, :edit, :create, :update, :destroy] # 追加
end

そして、User コントローラーの index で Partner の一覧を表示させるために、以下の1文を追加します。

app/controllers/users_controller.rb
class UsersController < ApplicationController
  before_action :access_user
  layout "access_layout"

  def index
    @users = User.all
    @posts = Post.includes(:user)
    @partners = Partner.includes(:user) # 追加
  end

  ### 以下略 ####
end

● Partner コントローラーの作成

では次に、Partner コントローラーの作成とその中身を記述していきましょう!
まずはターミナルで以下のコマンドを入力して Partner コントローラーと、それに付随するファイルを作成します。

ターミナル
$ rails g controller partners

先程のコマンドで作成された partners_controller.rb を、以下のように記載します。

app/controllers/partners_controller.rb
class PartnersController < ApplicationController
  layout "access_layout"
  before_action :set_partner, only: %i[ edit update destroy ]
  before_action :access_partner, only: %i[ new edit create update destroy]

  def new
    @partner = Partner.new
  end

  def edit
  end

  def create
    @partner = Partner.new(partner_params)
    if @partner.save
      flash[:message] = "パートナーを登録しました"
      redirect_to users_path
    else
      render :new
    end
  end

  def update
    if @partner.update(partner_params)
      flash[:message] = "変更が反映されました"
      redirect_to users_path
    else
      flash[:message] = "変更が反映されませんでした"
      redirect_to edit_partner_path
    end
  end

  def destroy
    @partner.destroy
    flash[:message] = "パートナー関係を解消しました"
    redirect_to users_path
  end

  private

    def set_partner
      @partner = Partner.find(params[:id])
    end

    def partner_params
      params.require(:partner).permit(:user_id).merge(grant: current_user.email)
    end

    # 管理者でない場合はトップページ(posts#index)に遷移
    def access_partner 
      unless current_user.email == Rails.application.credentials[:access_user]
        flash[:message] = "アクセスをブロックしました"
        redirect_to root_path
      end
    end
end

ここでまず注目して欲しいのは、partner_params のコードです。merge(grant: current_user.email) と記載することにより、ログインしているユーザーのメールアドレスをgrant カラムに保存するようにしています。先程の Partner モデルの記載を思い出して欲しいのですが、

app/models/partner.rb
### 省略 ###
  validates :user_id, uniqueness: true
  validates :grant,   inclusion: { in: [Rails.application.credentials[:access_user]] }
### 省略 ###

上記の記述により、以下のケースにおいて、バリデーション機能が働いてDBへの保存を阻止します。

  • user_id カラムに既に同じ値が登録されていた場合
  • grant (current_user.email)が管理者用メールアドレスと一致しない場合

次に、access_partner メソッドで、ログインしているユーザーのメールアドレスと管理者用に設定したメールアドレスが一致しない場合、トップページ(posts#index)にリダイレクトさせています。また、access_partner メソッドを before_action に設定し、全てのアクションに適応することで、Partner に関しては管理者のみアクセスできる制約を設けています。


■ Partner 機能に関連するビューファイルの整備

続いて、Partner 機能に関連するビューファイルを整えましょう。app/views/partners フォルダに、手動で以下の3ファイルを作成及び記載します。

app/views/partners/new.html.erb
<h1>New Partner</h1>

<%= render 'form', partner: @partner %>

<%= link_to 'Back', users_path %>
app/views/partners/edit.html.erb
<h1>Editing Partner</h1>

<%= render 'form', partner: @partner %>

<%= link_to 'Back', users_path %>
app/views/partners/_form.html.erb
<%= form_with(model: partner) do |form| %>
  <% if partner.errors.any? %>
    <div id="error_explanation">
      <h2>
        <%= pluralize(partner.errors.count, "error") %>
        prohibited this partner from being saved:
      </h2>

      <ul>
        <% partner.errors.each do |error| %>
          <li><%= error.full_message %></li>
        <% end %>
      </ul>
    </div>
  <% end %>

  <div class="field">
    <%= form.label :user_id %>
    <%= form.number_field :user_id %>
  </div>

  <div class="actions">
    <%= form.submit %>
  </div>
<% end %>

_form.html.erb の <%= form.number_field :user_id %> という箇所で、管理者がアクセス権を付与するユーザーIDを入力するフォームになっています。

次にパートナー一覧を管理画面(users#index)で表示できるように、app/views/users/index.html.erb 内に以下を加筆します。

app/views/users/index.html.erb
<h1>管理画面</h1>

<%# ↓追加↓ %>

<% if current_user.email == Rails.application.credentials[:access_user] %>
  <p>
    <%= link_to 'New_Partner', new_partner_path %>
  </p>
<% end %>

<table>==== パートナー一覧 ====
  <thead>
    <tr>
      <th>パートナーID</th>
      <th>ユーザーID</th>
      <th>メールアドレス</th>
      <th colspan="3"></th>
    </tr>
  </thead>

  <tbody>
    <% @partners.each do |partner| %>
      <tr>
        <td><%= partner.id %></td>
        <td><%= partner.user_id %></td>
        <td><%= partner.user.email %></td>
        <% if current_user.email == Rails.application.credentials[:access_user] %>
          <td><%= link_to 'Edit', edit_partner_path(partner) %></td>
          <td><%= link_to 'Destroy', partner_path(partner), method: :delete, data: { confirm: 'パートナー関係を解消しますか?' } %></td>
        <% end %>
      </tr>
    <% end %>
  </tbody>
</table>

<br>

<%# ↑追加↑ %>

<%# 以下略 %>

これで Partner の一覧表示から削除(index ~ destroy)まで整いましたが、まだ不十分です。現状だとアクセス権を付与されたユーザーが管理画面にアクセスすることもできなければ、Post 内容を編集・更新・削除することもできません。次の項目でそれらを整備していきましょう!


■ Partner が許された範囲での活動ができるまでの諸整備

まずは 以下の2つを実装するために User コントローラーを編集しましょう。

  • 管理画面(users#index)にパートナーがアクセスできるようにする。
  • User の削除は管理者のみ可能なように制約を設ける。
app/controllers/users_controller.rb
class UsersController < ApplicationController
  before_action :access_user, only: %i[ index ]
  before_action :action_permit, only: %i[ destroy ]
  layout "access_layout"

  # 省略 #
  private

    def access_user
      unless current_user.email == Rails.application.credentials[:access_user] || !current_user.partner.nil?
        flash[:message] = "アクセスをブロックしました"
        redirect_to root_path
      end
    end

    def action_permit
      unless current_user.email == Rails.application.credentials[:access_user]
        flash[:message] = "権限が与えられていません"
        redirect_to users_path
      end
    end
end

(上記の全容コード:https://github.com/tsuchiya-yu2/access_test2/blob/master/app/controllers/users_controller.rb

次に、パートナーに全ての Post に対して編集・更新・削除ができるように、Post コントローラを編集して権限を付与しましょう。

app/controllers/posts_controller.rb
class PostsController < ApplicationController
  before_action :set_post, only: %i[ show edit update destroy ]
  before_action :access_post, only: %i[ edit update destroy ]

  # 省略 #

  def update
    if @post.update(access_post_params)
      if current_user.email == Rails.application.credentials[:access_user] || !current_user.partner.nil?
        # 省略 #
      else
        # 省略 #
      end
    else
     # 省略 #
    end
  end

  def destroy
    @post.destroy
    if current_user.email == Rails.application.credentials[:access_user] || !current_user.partner.nil?
      # 省略 #
    else
      # 省略 #
    end
  end

  private
    # 省略 #
    def access_post
      if current_user.id == @post.user_id
      elsif current_user.email == Rails.application.credentials[:access_user] || !current_user.partner.nil?
      else
        flash[:message] = "アクセスをブロックしました"
        redirect_to root_path
      end
    end
    # 省略 #
end

(上記の全容コード:https://github.com/tsuchiya-yu2/access_test2/blob/master/app/controllers/posts_controller.rb

続いて、パートナーがログインした際の header に、'管理画面'のリンクが表示されるように app/views/lyouts にある application.html.erb と access_layout.html.erb を編集します。

app/views/layouts/application.html.erb
<% if user_signed_in? %>
  <%= link_to 'ログアウト', 省略 %>
  <% if current_user.email == Rails.application.credentials[:access_user] || !current_user.partner.nil? %>
    ||| <%= link_to '管理画面', users_path %>
  <% end %>
<% else %>
  <%# 省略 %>
<% end %>

(上記の全容コード:https://github.com/tsuchiya-yu2/access_test2/blob/master/app/views/layouts/application.html.erb
(下記の全容コード:https://github.com/tsuchiya-yu2/access_test2/blob/master/app/views/layouts/access_layout.html.erb

app/views/layouts/access_layout.html.erb
<% if user_signed_in? %>
  <%= link_to 'ログアウト', 省略 %>
  <% if current_user.email == Rails.application.credentials[:access_user] %>
    ||| <%= link_to '管理画面', users_path %>
  <% end %>
<% else %>
  <%# 省略 %>
<% end %>

最後に、ユーザーを 'Destroy' できるリンクが、管理者のみに表示されるように制約を設けます。

app/views/users/index.html.erb
<h1>管理画面</h1>

<%# 省略 %>
<table>==== ユーザー一覧 ====
  <thead>
    <tr>
      <th>ユーザーID</th>
      <th>メールアドレス</th>
      <th colspan="3"></th>
    </tr>
  </thead>

  <tbody>
    <% @users.each do |user| %>
      <tr>
        <td><%= user.id %></td>
        <td><%= user.email %></td>
        <% ##### ↓追加↓ ##### %>
        <% if current_user.email == Rails.application.credentials[:access_user] %>
          <td><%= link_to 'Destroy', user_path(user), method: :delete, data: { confirm: 'ユーザーを本当に削除しますか?' } %></td>
        <% end %>
        <% ##### ↑追加↑ ##### %>
      </tr>
    <% end %>
  </tbody>
</table>

<br>
<%# 省略 %>
<%= link_to '一般トップページへ', root_path %>

(上記の全容コード:https://github.com/tsuchiya-yu2/access_test2/blob/master/app/views/users/index.html.erb


■ サーバーの起動と実装した部分の確認

作業お疲れさまでした。
これで管理者がパートナーにアクセス権を付与する機能の実装と、パートナーのアクセス権の設定(今回は Post の編集・更新・削除権限の付与)を実装できました。実際に rails s コマンドを実行してサーバーを立ち上げて確認してみてください。

ターミナル
$ rails s
  • 管理者に設定したメールアドレスで管理画面へアクセスした場合と、パートナー登録を受けたユーザーで管理画面へアクセスした場合で、管理画面の違いは確認できたでしょうか?
  • パートナー状態の管理画面において、Post の変更・更新・削除ができましたか?

[DEMO]
下の画像は、左が管理者として管理画面にアクセスした場合、右がパートナーとして管理画面にアクセスした場合になります。
スクリーンショット 001.png


■ 本記事の最後に

本記事は以上になります。お疲れさまでした。
credentials.yml を使っての管理画面の実装は終了になります。

UIの改良や Post に楽観的ロックを導入したりと、いろいろと手を付けたい所はありますが、当初の目的は果たせたと思います。

個人的な振り返りになりますが、本実装の良い点はcredentials を利用しているので、管理者が誰であるのかがコード上(GitHub含む)では分からないところだと思います。

また、devise のメール認証などを実装し、パートナー登録を終えた後に、管理者IDを削除して管理者ID不在にしておくことで、セキュリティを向上させられる余地があるところも良い点と考えます。

(仮に他者が管理者用のメールアドレスを特定し、そのメールアドレスでアカウントを作成しようとしても、アプリケーションから送信される認証メールを開くためには、別途手間やセキュリティを潜らなければならなくなります。仮にgmailであれば、gmailを開くパスワードを入手し得ない限りは、本アプリケーションに管理者として成り代わってアクセスすることはできません。)

本記事で作成したアプリケーションのコードは、上記のGitHub上からも閲覧できますので適宜ご活用ください。

最後に
ここまで読んでいただき、ありがとうございました!!


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

?preloadとeager_loadの使い分けについて

preloadとeager_loadをどのようにして使い分けるかのメモです。

※議論の余地がかなりあるという前提です

:white_check_mark: 前提条件:全てのレコードを取得するような場合

  • 条件を指定する(whereなどを使う)ような場合は、eager_loadを使う。(テーブル結合していなければ、そもそも関連先の情報がわからないためpreloadは使えない)
  • 条件を指定しない場合は、preload、each_loadの使い分けはケースバイケース。所得したいデータのカラムが少ないならeach_loadを使って一つのクエリで済ませるべきだし、多いならpreload使った方がいいかも。基本的には、レスポンスタイムはpreload使った方が早い。

:white_check_mark: 前提条件:全てのレコードを取得する+関連先のデータが1対1であるような場合

  • 1対1であるかつ条件を指定しない場合、eager_loadを使うことで結合してからまとめて取得する方が効率が良い場合が多い。 (1対1になるようなデータを持ってくる場合は、抽象的ですが関連度がかなり強いので結合していった方が効率が多い場合が多いという解釈。company_userとcompany_user_profile的な!)
  • 1対1であるかつ条件を指定する(whereなどを使う)ような場合は当然eager_loadが当てはまる。

:white_check_mark: 前提条件:全てのレコードを取得する+関連先のデータが1対多であるような場合

  • 1対多であるかつ条件を指定しない場合、includesの挙動通りpreloadを使う
  • 1対多であるかつ条件を指定する(whereなどを使う)ような場合、結合しなければいけないのでeager_loadを使うしかない

:door: 注意点

  • ここまでの考えであっているのなら、一対一のときはeager_loadを使う場合が良いのかもしれないが結合してもデータが重複するような場合がある。その時は、preloadを使うのも検討。なぜなら、eager_loadにはdistinctが備わっていないために指定したデータ数以上のデータを取ってきてしまう可能性があるため。
  • preloadを使う時に、最初のクエリのデータ取得量が多すぎるとin句内の膨大なデータに対してSQL自体の設定値やメモリサイズの設定値を考慮しなければいけない
  • includesはeager_loadとpreloadを判断するのに時間がかかる、また状況を考えたクエリでのデータ取得操作が悪いのであまり使うべきでない。

参考記事
- ActiveRecordのincludes, preload, eager_load の個人的な使い分け
- Rails: JOINすべきかどうか、それが問題だ — #includesの振舞いを理解する(翻訳)
- なぜ、SQLは重たくなるのか?──『SQLパフォーマンス詳解』の翻訳者が教える原因と対策

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

Rooで取り込んだExcelの日時のデータがうまく反映されない

こんにちは

エラーが出た背景

rooを使ってexcelの日時のデータを取り込むよう開発を進めました。
ある程度できあがって確認しようとすると、下記のようなエラーが出ました。
おそらく, in_time_zoneメソッドがinteger型に対応できていないと
下記に
- エラーメッセージが出た部分
- 対応するメソッド(controller)
- エラーが発生したファイル
を載せています。

Screen Shot 2021-03-22 at 1.02.11 PM.png

app/controller/ir/meeting_logs_controller.rb
  def time_from_excel_date_time(excel_date, excel_time)
    date = excel_date.in_time_zone

    if excel_time.is_a?(String)
      time = excel_time.to_time
      hour = time.hour
      minute = time.min
    elsif excel_time.is_a?(Integer)
      hour = (excel_time / 3600)
      minute = (excel_time % 3600 / 60)
    end

    date + hour.hour + minute.minute
  end

普段取り込むexcelファイル(該当箇所のみ)

id 日付 開始時間
12/9 1:00
12/8 1:00
11/27 1:00
11/26 1:00

今回エラーが発生したexcelファイル(該当箇所のみ)

id 日付 開始時間
12月9日 1:00
12月8日 1:00
11月27日 1:00
11月26日 1:00

原因

上の部分を見ていただければわかる通り、日付の区切られている部分が/月日かの違いによるものでした。
この部分が違うと、
/だとそのまま(String型)、
月日だと数字(Integer型)
が変数として取り込まれるので、String型にしか対応していなかったメソッドでエラーが発生してしまいました。

改善策

そのためcontrollerを書き換えました。

app/controller/ir/meeting_logs_controller.rb
  def time_from_excel_date_time(excel_date, excel_time)
  EXCEL_EPOCH = 2209078800
  TICKS_PER_DAY = 60 * 60 * 24
    if excel_date.is_a?(String) || excel_date.is_a?(Date)
      date = excel_date.in_time_zone
    elsif excel_date.is_a?(Integer)
      # 日付が漢字の場合、integer型で受け取る => 1899/12/31 からどれくらいの日数がたったかを表している
      # Unixのゼロタイム(基準)が "1970/1/1" に設定されている為、下記ステップで日付を求める
      # 1. TICKS_PER_DAY をかけて、1899/12/31 からどれくらいの秒数がたったかを計算
      # 2. 1から EXCEL_EPOCH 秒をを引き、1970/1/1 からどれくらいの秒数がたったかを計算

      unix_timestamp = excel_date * TICKS_PER_DAY - EXCEL_EPOCH
      date = Time.at(unix_timestamp).to_datetime - 8.hours - 1.day
    end

    if excel_time.is_a?(String)
      time = excel_time.to_time
      hour = time.hour
      minute = time.min
    elsif excel_time.is_a?(Integer)
      hour = (excel_time / 3600)
      minute = (excel_time % 3600 / 60)
    end

    date + hour.hour + minute.minute
  end

excelから取り込んだ日付の変数が入っているexcel_dateに対して、if文でString型とInteger型に分けて処理を走らせることにしました。
また、unix_timestampの部分が複雑なので下記に説明をしたいと思います。

unixとexcel

このコントローラーの記述は主にunixとexcelの基準の時間を合わせるために書きました。
コメントアウトでも書いてありますが、
excelは基準が1899/12/31なので帰ってくる変数は、1899/12/31から何日たっているかを表します。
しかし、Rubyに限らずunixを基準としているプログラミング言語は1970/1/1(ゼロタイム)を基準に作動します。
なので、excelの変数をunixの基準に合わせるために

unix_timestamp = excel_date * TICKS_PER_DAY - EXCEL_EPOCH

の記述をしています。

最後に

今回僕の初めての投稿なので至らない点があると思いますが、
僕なりに一生懸命頑張ったつもりです。笑
もし、何かアドバイスなどありましたらぜひ教えていただきたいです!

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

Rails gem bcryptのメソッドが使えない

はじめに

初めまして。私はプログラミングを勉強してまだ間もない駆け出しのエンジニアです。Qiitaへの投稿も含めまだまだ勉強中です。そしてこれがQiitaへの初投稿となりますのでお手柔らかにお願いします。
主に初心者の方向け、また自分用のメモとしてこれから発信していきます。

開発環境

Rails 6.1.3 ローカル環境でProgateのTweet_Appを作成中

概要

今回はタイトルにもある通り、Gemfileへbcryptを追加したにも関わらず、has_secure_passwordやauthenticateといったメソッドが使えない!と言う状況に出くわしたのでそちらを共有していきます。

まず、
Gemfileに gem 'bcrypt' を追加。
その後ターミナルで bundle install を実行。

パスワードのハッシュ化を実装させていこうとhas_secure_passwordメソッドを記述

models/user.rb
class User < ApplicationRecord  
    has_secure_password

確認のため作成中のサイトを見たところ、そのメソッドは知らないよとのエラーが。
使えるはずのメソッドが使えずかなり焦った。

原因は、bcryptのバージョン?・・・いやバージョンは指定せず記述したので最新のものが導入されているはず。
じゃあ一体なんでだろうと小一時間悩んだ挙句ようやく原因を突き止めた。

rails s の再起動

サーバーを立ち上げ直すことでエラーは解消された。
長いこと悩んだ問題の原因はなんとも簡単なことだった。
以前、テーブルのcreated_atカラムを日本時間にしたいなと思い立ち、Qiitaなどを参考にしたときにGemfileの編集をしたことがあった。その時参考にした記事に「サーバーを再起動させて変更完了です。」の一文があったことを思い出した。

まとめ

自分と同じようなことで悩んでいた方がもしいたら、無駄な時間を過ごすことがなくなればいいなと思います。
rails s の再起動はProgateでGemfileのレッスンで書かれてなかったぞ・・・
それとも正規の解決法が他にあるのか?まあ解決できればそれでいいか!

またネタがあったら投稿したいと思います。

以上

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

Rails Gemfileでbcrypt追加後にメソッドが使えない

はじめに

初めまして。私はプログラミングを勉強してまだ間もない駆け出しのエンジニアです。Qiitaへの投稿も含めまだまだ勉強中です。そしてこれがQiitaへの初投稿となりますのでお手柔らかにお願いします。
主に初心者の方向け、また自分用のメモとしてこれから発信していきます。

開発環境

Rails 6.1.3 ローカル環境でProgateのTweet_Appを作成中

概要

今回はタイトルにもある通り、Gemfileへbcryptを追加したにも関わらず、has_secure_passwordやauthenticateといったメソッドが使えない!と言う状況に出くわしたのでそちらを共有していきます。

Gemfileに gem 'bcrypt' を追加。
その後ターミナルで bundle install を実行。

さあ、学んだパスワードのハッシュ化を実装させていこうとhas_secure_passwordメソッドを記述

models/user.rb
class User < ApplicationRecord  
    has_secure_password

確認のため作成中のサイトを見たところ、そのメソッドは知らないよとのエラーが。
使えるはずのメソッドが使えずかなり焦った。

原因は、bcryptのバージョン?・・・いやバージョンは指定せず記述したので最新のものが導入されているはず。
じゃあ一体なんでだろうと小一時間悩んだ挙句ようやく原因を突き止めた。

rails s の再起動

長いこと悩んだ問題の原因はなんとも簡単なことだった。
以前、テーブルのcreated_atカラムを日本時間にしたいなと思い立ち、Qiitaなどを参考にしたときにGemfileの編集をしたことがあった。その時参考にした記事に「サーバーを再起動させて変更完了です。」の一文があったことを思い出した。

まとめ

自分と同じようなことで悩んでいた方がもしいたら、無駄な時間を過ごすことがなくなればいいなと思います。
rails s の再起動はProgateでGemfileのレッスンで書かれてなかったぞ・・・
それとも正規の解決法が他にあるのか?まあ解決できればそれでいいか!

またネタがあったら投稿したいと思います。

以上

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

mimemagicエラー

gemを入れてbundle install を行ったところ、以下のエラーが出ました。

Your bundle is locked to mimemagic (0.3.5), but that version could not be found
in any of the sources listed in your Gemfile. If you haven't changed sources,
that means the author of mimemagic (0.3.5) has removed it. You'll need to update
your bundle to a version other than mimemagic (0.3.5) that hasn't been removed
in order to install.

んん、よく分からん。。とりあえずGemfile.lockで関係してそうな内容を消して再度bundle install をしたら直りました。

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

Twitter API 登録の仕方(2021年3月17日)

【欲しいもの】
bot を作るために必要なもの。
1. Access Token
2. Access Token Secret
3. Consumer Key
4. Consumer Secret
これらを以下で取得していく。

準備編

1

ここにアクセスする

2

Apply(右上)

3

Apply for a developer account

4

目的を選ぶ(今回はbotを作りたいのでhobbyist > making a bot)

5

get start

6

名前とかemailとか入力する

7

何に使うのか入力する
利用規約に同意する
色々進めて、、、

8

submit application
メールが送られたっぽい

9

メールにある[Confirm your email]をクリック
メールアドレス、パスワードを打って認証完了する。

10

「今あなたのレビューをしているよ!!」的なメールが届く。
他の人の記事を見ているとレビュー完了まで数時間かかるそう。

###11 メールがかえってくる。
今レビューしていますが、もう少し用途について聞かせてください的な内容。
これは、7.で英文と全く同じのでもOK

12

30程度待っていると、twitterからメールが来る
登録申請がapproveされたメールがきました。

13

送られてきたリンクにアクセスして、アプリ名を決める。
Get keyを押す。

14

【api key】
【API secret key】
【Bearer token】
を保存しておく。

15

作ったアプリの中からAccess token & secretをgenerateする。
そこに、Access token、Access token secretがあるからコピーしてどこかに記録しておく。

16

これで最初に述べたAccess Token、Access Token Secret、Consumer Key、Consumer Secretの4つがゲットできた。

ここからは本格的にTwitter bot を作っていく。

https://qiita.com/ryoya-s/items/bc8a0e39716bb2844f0b
の記事を参考にした。

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

User.allって良くないの?って話

こんにちは。たにーです。

今回は、チーム開発で少し議論した

「アクション内でのでのallは良くない?」についてです。

items_controller.rb
 @items_all = Item.all

結論

結論から言うと、悪いことはないが
場面によっては使い方を考えなくてはいけないということです。

カリキュラムで作成したアプリでは、
レコード数がmax10個ぐらいで試していたこともあり、取り出すデータ量が少ないため問題なかった。

もし、その数が100個、1000個、1万個あった場合だと、
情報量が多すぎて、処理速度が遅くなり、ページのロードが遅いなどが起きるかもしれません。

なので、その処理速度をより早くする書き方をご紹介します。

状況について

  • railsでwebアプリケーションを開発中。
  • 販売している商品数をviewに表示させたい。
    例:( 商品一覧(全:〇〇件) )
  • viewとcontrollerには下記のように書いていた。
index.html.erb
  <div class="container">
  <div class="row">
    <div class="col-sm-12 px-sm-0">
      <h2>商品一覧(<%= @items_all.count %>件)</h2>
    </div>
  </div>
  
items_controller.erb
  class Public::ItemsController < ApplicationController
  def index
    @items_all = Item.all
  end

実際にターミナルを見て確認します。

まずは、そのままでページを開いてみます。
そうすると、、、、、

(item.allの場合)
 Started GET "/" for 106.180.147.162 at 2021-03-25 11:43:45 +0000
 Cannot render console from 106.180.147.162! Allowed networks: 127.0.0.1, ::1, 127.0.0.0/127.255.255.255
 Processing by Public::HomesController#top as HTML
   Rendering public/items/index.html.erb within layouts/application
   (0.2ms)  SELECT COUNT(*) FROM "items"
  ↳ app/views/public/items/index.html.erb:4
  Item Load (0.1ms)  SELECT  "items".* FROM "items" LIMIT ? OFFSET ?  [["LIMIT", 8], ["OFFSET", 0]]
  ↳ app/views/public/items/index.html.erb:8

見て欲しいところはここです。

(item.allの場合)
  Item Load (0.3ms)  SELECT  "items".* FROM "items" ORDER BY "items"."created_at" DESC LIMIT ?  [["LIMIT", 4]]

Item Loadの(0.3ms)、と書いてあります。

この数値が低ければ低いほど応答速度が速い(タイムラグが少ない)と言われています。

もしかしたら、不要なデータを取得してきているから
0.3なのか?書き方でより少なくできるのでは?と気になったところでチームメンバーで解決策を探しました。

実際に調べて試しました

selectで試した

items_controller.erb
  def index
    @items_all = Item.select(:id)
  end
(Item.selectの場合)
Started GET "/items" for 106.180.147.162 at 2021-03-25 12:13:24 +0000
Cannot render console from 106.180.147.162! Allowed networks: 127.0.0.1, ::1, 127.0.0.0/127.255.255.255
Processing by Public::ItemsController#index as HTML
  Rendering public/items/index.html.erb within layouts/application
   (0.1ms)  SELECT COUNT("items"."id") FROM "items"
  ↳ app/views/public/items/index.html.erb:4
  Item Load (0.1ms)  SELECT  "items".* FROM "items" LIMIT ? OFFSET ?  [["LIMIT", 8], ["OFFSET", 0]]
  ↳ app/views/public/items/index.html.erb:8

2箇所あるますが、それでも0.2msで早くなっているのがわかります。

countで試した

items_controller.erb
class Public::ItemsController < ApplicationController
  def index
    @items_all = Item.count
  end
(Item.count)
Started GET "/items" for 106.180.147.162 at 2021-03-25 12:22:09 +0000
Cannot render console from 106.180.147.162! Allowed networks: 127.0.0.1, ::1, 127.0.0.0/127.255.255.255
Processing by Public::ItemsController#index as HTML
   (0.1ms)  SELECT COUNT(*) FROM "items"
  ↳ app/controllers/public/items_controller.rb:4
  Rendering public/items/index.html.erb within layouts/application
  Item Load (0.2ms)  SELECT  "items".* FROM "items" LIMIT ? OFFSET ?  [["LIMIT", 8], ["OFFSET", 0]]

こちらは,0.3msであまり変わっていない、、、

結果としては、

メソッド 応答速度
all 0.3ms
select 0.2ms
count 0.3ms

selectで特定のカラムを指定する「select」で件数を取得する方法が今の段階では良さそうです!!
もし、違った方法、間違っているなどあれば教えていただけますと幸いです。

以上、たにーでした。

参考文献

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

allメソッドって良くないの?って話

こんにちは。たにーです。

今回は、チーム開発で少し議論した

「アクション内でのでのallは良くない?」についてです。

items_controller.rb
 @items_all = Item.all

結論

結論から言うと、悪いことはないが
場面によっては使い方を考えなくてはいけないということです。

カリキュラムで作成したアプリでは、
レコード数がmax10個ぐらいで試していたこともあり、取り出すデータ量が少ないため問題なかった。

もし、その数が100個、1000個、1万個あった場合だと、
情報量が多すぎて、処理速度が遅くなり、ページのロードが遅いなどが起きるかもしれません。

なので、その処理速度をより早くする書き方をご紹介します。

状況について

  • railsでwebアプリケーションを開発中。
  • 販売している商品数をviewに表示させたい。
    例:( 商品一覧(全:〇〇件) )
  • viewとcontrollerには下記のように書いていた。
index.html.erb
    <div class="col-sm-12 px-sm-0">
      <h2>商品一覧(<%= @items_all.count %>件)</h2>
    </div>
items_controller.erb
  class Public::ItemsController < ApplicationController
  def index
    @items_all = Item.all
  end

実際にターミナルを見て確認します。

まずは、そのままでページを開いてみます。
そうすると、、、、、

(item.allの場合)
 Started GET "/" for 106.180.147.162 at 2021-03-25 11:43:45 +0000
 Cannot render console from 106.180.147.162! Allowed networks: 127.0.0.1, ::1, 127.0.0.0/127.255.255.255
 Processing by Public::HomesController#top as HTML
   Rendering public/items/index.html.erb within layouts/application
   (0.2ms)  SELECT COUNT(*) FROM "items"
  ↳ app/views/public/items/index.html.erb:4

見て欲しいところはここです。

(item.allの場合)
   (0.2ms)  SELECT COUNT(*) FROM "items"
  ↳ app/views/public/items/index.html.erb:4

(0.2ms)、と書いてあります。

この数値が低ければ低いほど応答速度が速い(タイムラグが少ない)と言われています。

もしかしたら、不要なデータを取得してきているから
0.2なのか?書き方でより少なくできるのでは?と気になったところでチームメンバーで解決策を探しました。

実際に調べて試しました

selectで試した

items_controller.erb
  def index
    @items_all = Item.select(:id)
  end
(Item.selectの場合)
Started GET "/items" for 106.180.147.162 at 2021-03-25 12:13:24 +0000
Cannot render console from 106.180.147.162! Allowed networks: 127.0.0.1, ::1, 127.0.0.0/127.255.255.255
Processing by Public::ItemsController#index as HTML
  Rendering public/items/index.html.erb within layouts/application
   (0.1ms)  SELECT COUNT("items"."id") FROM "items"
  ↳ app/views/public/items/index.html.erb:4

それでも0.2msで早くなっているのがわかります。

countで試した

items_controller.erb
class Public::ItemsController < ApplicationController
  def index
    @items_all = Item.count
  end
(Item.count)
Started GET "/items" for 106.180.147.162 at 2021-03-25 12:22:09 +0000
Cannot render console from 106.180.147.162! Allowed networks: 127.0.0.1, ::1, 127.0.0.0/127.255.255.255
Processing by Public::ItemsController#index as HTML
   (0.1ms)  SELECT COUNT(*) FROM "items"

こちらも、0.1msと早くなってるのかな?

結果としては、

メソッド 応答速度
all 0.2ms
select 0.1ms
count 0.1ms

もし、違った方法、間違っているなどあれば教えていただけますと幸いです。

以上、たにーでした。

参考文献

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

【Rails6】flatpickrカレンダーの導入で気をつける事

自作ポートフォリオを作成している時に、bootstrapのカレンダーが使いづらかった為、flatpickrを導入する事にしました。その時に躓いたポイントがあったので、シェアします。

開発環境

  • macOS Catalina 10.15.7
  • Ruby 2.7.2
  • Rails 6.1.1

すでに何かしらのアプリを作っている前提で進めていきます。

まずはこちらの記事をベースにflatpickrを導入

気をつけないといけなかった点

上記の記事で、

<!-- ***** 以下を追加 ***** -->
<%= form_with model: @calendar do |form| %>
  <input id ="calendar_form" type="text" name="calendar[date]" readonly="readonly" >
  <%= form.submit "送信" %>
<% end %>
<!-- ***** 以上を追加 ***** -->

という部分があるのですが、

僕のアプリはこのようなフォーム設計になっています。

view
<%= f.label :visit_date %>
<input id="calendar_form" type="text" name="visit_history[visit_date]" placeholder="日付を設定" class="form-control" readonly="readonly">

ここで name="コントローラー名[バリュー名]という形にし、下記に記載しているコントローラーに合わせてあげる必要があります。

visit_histories_controller.rb
class VisitHistoriesController < ApplicationController
  before_action :set_visit_history, only: [:show, :edit, :update, :destroy]

  # GET /visit_historys
  # GET /visit_historys.json
  def index
    @visit_histories = VisitHistory.all
  end

  # GET /visit_historys/1
  # GET /visit_historys/1.json
  def show
    @visit_history
  end

  # GET /visit_historys/new
  def new
    @visit_history = VisitHistory.new(visit_history_params)
    @visit_history.build_medical_treatment_history
  end

  # GET /visit_historys/1/edit
  def edit
  end

  # POST /visit_historys
  # POST /visit_historys.json
  def create
    @visit_history = current_user.visit_histories.new(visit_history_params)
    if @visit_history.save
      redirect_to client_path(@visit_history.client_id), notice: "VisitHistory was successfully created."
    else
      render :new
    end
  end

  # PATCH/PUT /visit_historys/1
  # PATCH/PUT /visit_historys/1.json
  def update
    if @visit_history.update(visit_history_params)
      redirect_to @visit_history, notice: "VisitHistory was successfully updated."
    else
      render :edit
    end
  end

  # DELETE /visit_historys/1
  # DELETE /visit_historys/1.json
  def destroy
    @visit_history.destroy
    redirect_to client_path(@visit_history.client_id), notice: "VisitHistory was successfully destroyed."
  end

  private

  # Use callbacks to share common setup or constraints between actions.
  def set_visit_history
    @visit_history = VisitHistory.find(params[:id])
  end

  # Only allow a list of trusted parameters through.
  def visit_history_params
    params.require(:visit_history).permit(:visit_date, :fee, :client_id, medical_treatment_history_attributes: [:user_id, :subjective, :objective, :assessment, :plan, :memo])
  end
end

ここで、さきほどの name="コントローラー名[バリュー名]が合っていないと、
ストロングパラメーターのpermitの部分で弾かれてしまい、@visit_hisrotyの中のvisit_dateの値が nilになってしまいます。

失敗例

あまり良く理解して出来ていなかったため、はじめは name="calendar[date]name="calendar[visit_date]で試していました。

この場合、先程の通りストロングパラメーターで弾かれて、 visit_dateの値が nilになります。

binding.pryでパラメーターを確認してみました。
visit_dateの値が nilになっています。

ターミナル
[1] pry(#<VisitHistoriesController>)> params
=> #<ActionController::Parameters {"authenticity_token"=>"ADamDRCkllatE4r75IMx5YaVQSkqML1tkzc6zhm9uFtqgsQ6uG0JbKWpe4PC8AAryLjP3587W5233WLlp_dqDQ", "visit_history"=>#<ActionController::Parameters {"client_id"=>"1", "fee"=>"5000", "medical_treatment_history_attributes"=>{"user_id"=>"1", "subjective"=>"", "objective"=>"", "assessment"=>"", "plan"=>"", "memo"=>""}} permitted: false>, "calendar"=>#<ActionController::Parameters {"visit_date"=>"2021-03-25"} permitted: false>, "commit"=>"登録", "controller"=>"visit_histories", "action"=>"create"} permitted: false>
[2] pry(#<VisitHistoriesController>)> @visit_history
=> #<VisitHistory:0x00007fd57424f710
 id: 20,
 visit_date: nil,
 fee: 5000,
 client_id: 1,
 user_id: 1,
 created_at: Thu, 25 Mar 2021 17:49:26.938352000 JST +09:00,
 updated_at: Thu, 25 Mar 2021 17:49:26.938352000 JST +09:00>

成功例

visit_dateの中に値がしっかりと入っています。

ターミナル
[1] pry(#<VisitHistoriesController>)> params
=> #<ActionController::Parameters {"authenticity_token"=>"QXCgqasqGOeA9w-vtKOevhC8Bz9e-WHQ8yWr1nYd9skrxMKeA-OH3YhN_teS0K9wXpGJyevyhyDXz_P9yFcknw", "visit_history"=>#<ActionController::Parameters {"client_id"=>"1", "visit_date"=>"2021-03-28", "fee"=>"9000", "medical_treatment_history_attributes"=>{"user_id"=>"1", "subjective"=>"", "objective"=>"", "assessment"=>"", "plan"=>"", "memo"=>""}} permitted: false>, "commit"=>"登録", "controller"=>"visit_histories", "action"=>"create"} permitted: false>
[2] pry(#<VisitHistoriesController>)> @visit_history
=> #<VisitHistory:0x00007fc42c874c48
 id: 23,
 visit_date: Sun, 28 Mar 2021,
 fee: 9000,
 client_id: 1,
 user_id: 1,
 created_at: Thu, 25 Mar 2021 19:17:45.940796000 JST +09:00,
 updated_at: Thu, 25 Mar 2021 19:17:45.940796000 JST +09:00>

まとめ

Rails6でflatpickrを導入する際の記事がほとんど英語のものしか無かったので、今回記事としてまとめてみました。見ていただいた方の参考になれば幸いです。

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

【Rails&heroku】MySQLで出来てた「あいまい検索」がPostgreSQLで出来ない場合にやること

AWSでコード完成、いざherokuへデプロイ!・・・したけどエラー。
調べてみると、あいまい検索の部分がおかしいらしい。

【ページ内リンク】

0.環境
1.ソースコード
2.そもそもあいまい検索部分が原因だと分かった経緯
3.解決策

0.環境

・AWS
・heroku/7.48.0 linux-x64 node-v12.16.2
・Rails 5.2.4.5
・ruby 2.6.3p62 (2019-04-16 revision 67580) [x86_64-linux]
・MySQL 5.7.31
・PostgreSQL 9.2.24

1.ソースコード

Example.rb
@teams = Team.where("#{key} like ?", "%#{value}%")

・いたって普通のあいまい検索。keyがキー、valueが値。
【例:key = "id"、`value = "(検索用に入力した値)"】
・MySQLだと上手くいったのに、PostgreSQLだとエラーが生じた。

※ちなみに、下記に2通り完全一致検索方法を書いたが、方法1はどちらでも作動し、方法2PostgreSQLでは作動しなかった。

Example2.rb
@teams = Team.where("#{key} = #{value}")   #方法1
@teams = Team.where("#{key} like ?", "#{value}")   #方法2

たぶん「like」が悪そう。

ページ内リンクへ戻る

2.そもそもあいまい検索部分が原因だと分かった経緯

$ heroku logs -t

heroku logs --tailの略。これを打てば、どこでエラーが出たか一発で分かる。

ページ内リンクへ戻る

3.解決策

調べてみると、keyvalueの型が違うのが原因らしい。

Example.rb
@teams = Team.where("cast(#{key} as text) like ?", "%#{value}%")

PostgreSQLなら、これで良い。cast、RubyやRailsというよりSQL寄り?
でもMySQLだとこれはエラーとなった。絶対何か他にいい方法あるよなぁ。

ページ内リンクへ戻る

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

N + 1問題を解決する

はじめに仕込んでいたgem bulletが警告ログを発したので解決してみます。
スクリーンショット 2021-03-25 19.32.01.png

N + 1問題とは?

データベースへのアクセス回数が必要以上に多くなってしまう現象の事。モデル間のアソシエーションで発生します。

gem bulletとは?

N + 1問題が発生してる箇所を警告で教えてくれるgemです。

現状

CareRecipitent Load (0.4ms)  SELECT "care_recipitents".* FROM "care_recipitents"
  ↳ app/views/caregiver/tops/index.html.erb:1
  Caregiver Load (0.5ms)  SELECT "caregivers".* FROM "caregivers" WHERE "caregivers"."id" = $1 LIMIT $2  [["id", 1], ["LIMIT", 1]]
  ↳ app/views/caregiver/tops/index.html.erb:37
  CACHE Caregiver Load (0.0ms)  SELECT "caregivers".* FROM "caregivers" WHERE "caregivers"."id" = $1 LIMIT $2  [["id", 1], ["LIMIT", 1]]
  ↳ app/views/caregiver/tops/index.html.erb:37
  CACHE Caregiver Load (0.0ms)  SELECT "caregivers".* FROM "caregivers" WHERE "caregivers"."id" = $1 LIMIT $2  [["id", 1], ["LIMIT", 1]]
  ↳ app/views/caregiver/tops/index.html.erb:37
  Rendered caregiver/tops/index.html.erb within layouts/caregiver (Duration: 16.2ms | Allocations: 6553)
  CACHE Caregiver Load (0.1ms)  SELECT "caregivers".* FROM "caregivers" WHERE "caregivers"."id" = $1 LIMIT $2  [["id", 1], ["LIMIT", 1]]

はじめに親モデルに検索がかけられ、その後に子モデルに4回クエリが発行されていることがわかります。

解決策

子モデル.includes(:親モデル)

  def index
    #@caregiver = Caregiver.find(params[:staff_member_id])
  -  @care_recipitents = CareRecipitent.all
  +  @care_recipitents = CareRecipitent.includes(:caregiver)
  end

コントローラーのindexアクションを書き換えます。

CareRecipitent Load (1.2ms)  SELECT "care_recipitents".* FROM "care_recipitents"
  ↳ app/views/caregiver/tops/index.html.erb:1
  Caregiver Load (0.6ms)  SELECT "caregivers".* FROM "caregivers" WHERE "caregivers"."id" = $1  [["id", 1]]
  ↳ app/views/caregiver/tops/index.html.erb:1
  Rendered caregiver/tops/index.html.erb within layouts/caregiver (Duration: 75.3ms | Allocations: 19989)
  Caregiver Load (0.3ms)  SELECT "caregivers".* FROM "caregivers" WHERE "caregivers"."id" = $1 LIMIT $2  [["id", 1], ["LIMIT", 1]]

クエリの回数が減り、無事ログを消えました。

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

repec 備忘録

導入方法

Gemfile
group :development, :test do
  gem 'rspec-rails' #追加
  gem 'capybara' #追加
#元あるやつはコメントアウトする
end
ターミナル
$ bundle install
$ rails generate rspec:install
spec/spec_helper.rb
# See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
require 'capybara/rspec' #追記
RSpec.configure do |config|
  config.before(:each, type: :system) do #追記
    driven_by :rack_test #追記
  end #追記
.
.
.
config.include FactoryBot::Syntax::Methods 
#これを記入することでFactoryBot.create(:cake)⇒create(:cake)にできる
config.include CartItemTestHelper #(helperはここに追加)

初期設定

モデル、コントローラー作成時、関係するrspecのファイルを作成

config/application.rb
config.generators do |g|
  g.test_framework :rspec,
end

テスト実行画面をいい感じにする

.rspec
--require spec_helper
--color
--format d

spec/support/配下のファイルを読み込む
(コメントアウト外す)

/spec/rails_helper.rb
Dir[Rails.root.join('spec/support/**/*.rb')].each { |f| require f }

ファイル作成方法

ファクトリーボット

ターミナル
$ rails g factory_bot:model user

テストファイル

ターミナル
$ rails g rspec:system users
$ rails g rspec:model user

実行方法

ターミナル
#どちらかで起動可能
$ rspec
$ bundle exec rspec
#特定の箇所のみ
$ rspec spec/system/users_spec.rb
$ rspec spec/system/users_spec.rb:2

ファクトリーボット

表記方法
一意制約をもたせている場合は、email部のように表記する

FactoryBot.define do 
    factory :user do 
        name {"test"}
        sequence(:email) { |n| "test#{n}@example.com"}
        password {"password"}
    end 
end 

モデルのテスト

require 'rails_helper'

RSpec.describe CartItem, "モデルに関するテスト", type: :model do
  before do
    FactoryBot.create(:cake)
    FactoryBot.create(:candy)
    FactoryBot.create(:customer)
  end

  describe "実際に保存してみる" do
    it "有効な物は保存可能か?" do
      cart_item = CartItem.new(item_id: 1,customer_id: 1,amount: 2)
      expect(cart_item).to be_valid
    end

    context "空白のバリデーションチェック" do
      it "個数が空白" do
        cart_item = CartItem.new(item_id: 1,customer_id: 1,amount: "")
        expect(cart_item).to be_invalid
        expect(cart_item.errors[:amount]).to include("is not a number")
      end
      it "item_idが空白" do
        expect(FactoryBot.build(:cart_item, item_id: "")).to be_invalid
               #上記方法でもok
      end
    end
  end
end

フォームの入力

fill_in " idやname名 ", with: 入れたい内容
fill_in " idやname名 ", with: 入れたい内容
click_button " idやname名 "

fill_in "customer_email", with: customer.email
fill_in "customer_password", with: customer.password
click_button "commit"

helperの追加

helperの作成

spec/support/cart_item_test_helper.rb
module CartItemTestHelper
  def item_in_cart(item, amount)
    visit item_path(item)
    fill_in "cart_item_amount", with: amount
    click_button "commit"
  end
end

spec_helperに追記

spec/spec_helper.rb
# See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
require 'capybara/rspec' 
RSpec.configure do |config|
  config.before(:each, type: :system) do 
    driven_by :rack_test 
  end 
.
.
.
config.include FactoryBot::Syntax::Methods 
config.include CartItemTestHelper #追記
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Rails6 Herokuにデプロイしようとしたらエラーが多発した話

前提

オリジナルアプリケーションを開発した後、herokuにデプロイした際にでたエラーを備忘録としてまとめておこうと思います。

結論

  • assetのファイルの場所が違った
  • bundlerのバージョンを合わせた
  • platformを変えた

1つ目

% git heroku push master

実行すると、、、

.
.
.
# 省略
Caused by:
Sass::SyntaxError: File to import not found or unreadable: tagsinput.
.
.
.
remote:  !
remote:  !     Precompiling assets failed.
remote:  !
.
.
.

と表示された。
tagsinput.jsが読み込まれていないとあるので、みてみるとrequire('../tagsinput')をjavastcript/packs/application.jsに記述していなかったのが原因だったっぽい。

2つ目

1つ目のエラーが解消したので成功すると思い、

% git heroku push master

実行すると、、、

.
.         # 省略
.

Bundler Output: Fetching gem metadata from https://rubygems.org/............
Your bundle is locked to mimemagic (0.3.5), but that version could not be found 
in any of the sources listed in your Gemfile. If you haven't changed sources, 
that means the author of mimemagic (0.3.5) has removed it. You'll need to update
your bundle to a version other than mimemagic (0.3.5) that hasn't been removed
in order to install.

remote:  !
remote:  !     Failed to install gems via Bundler.
remote:  !
.
.
.

調べてみたところ、ローカルのbundlerとherokuのbundlerを合わせないといけないらしい、、、
ということでもう一度Terminalを眺めてみると、

Installing bundler 2.2.11

と書いてある
一方ローカルの方でみてみると

% bundler -v
Bundler version 2.1.4

違っていたので、ローカルの方を合わせることにした。

手順としては、
1. 今のローカルにあるbundlerをアンインストール。
2. Herokuに合わせたbundlerのversionをインストール
3. Gemfile.lockを一度削除。
4. Gemfile.lockを再度生成。
5. HerokuへPush
とういことでまず既存のbundlerをアンインストールするため

% gem uninstall bundler

実行し、アンインストールができたら
バージョンを合わせるため、

% gem install bundler --version '2.2.11'

を実行。
インストールできたことを確認したら、Gemfile.lockを削除するため下記を実行。

% rm gemfile.lock

できたら、もう一度Gemfile.lockを生成するためバージョンを指定して下記を実行

% bundle _2.2.11_ install --without production

これでこのエラー解消できた!、、、

3つ目

と思いきやherokuにpushするとさらに追撃が、、、

.
.      # 省略
.

Bundler Output: Your bundle only supports platforms ["x86_64-darwin-20"] but your local platform      
is x86_64-linux. Add the current platform to the lockfile with `bundle lock
--add-platform x86_64-linux` and try again.

remote:  !
remote:  !     Failed to install gems via Bundler.
remote:  !
.
.
.

エラー文に記載された通り、

% bundle lock --add-platform x86_64-linux

実行し、コミットしてプッシュする。
再度

% git push heroku master

実行すると、やっとできた!

少しでも参考になったら幸いです。

参考

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

【jQuery Raty】ブラウザバックすると星が倍になる不具合

はじめまして、プログラミングを初めて3ヶ月の初学者です。
Ratyを利用した星評価を実装中に
ブラウザバックで星評価のある画面に戻ると
5つの星が10個になるという不具合にぶつかりました。

いくら検索しても解決せず、最終的にメンターと試行錯誤して解決したので
今日は同じ目に逢ってる方と、いつかの自分の為に記録します。

ちなみに初めて書くのでお手柔らかに…

目次

  • 開発環境
  • 不具合
  • 何故なるのか?
  • 結論 ←ココが解決策
  • 解説
  • 試した方法

開発環境

  • Cloud9
  • ruby '2.6.3'
  • Rails 5.2.4.5
  • jquery

不具合

本来5つ星での評価をしたいのに、
ブラウザバックで評価の存在するページに戻ると
星の数が倍になる
スクリーンショット 2021-03-25 16.16.20.png

正しい表示はこう
スクリーンショット 2021-03-25 16.16.37.png

何故なるのか?

不具合時のコードを見ると記載がこうなります。

show.html.erb
<div id="star-rate-<%= @review.id %>"></div>

 <script>
  $('#star-rate-<%= @review.id %>').raty({
    size: 36,
    starOff:  '<%= asset_path('star-off.png') %>',
    starOn : '<%= asset_path('star-on.png') %>',
    starHalf: '<%= asset_path('star-half.png') %>',
    readOnly: true,
    score: <%= @review.rate %>,
  });
 </script>

これだと、ページを表示する度に星5つを読み込むことになるとか…
順序で見てみると

  1. 星5つが読み込まれたページを表示
  2. ページ移行
  3. ブラウザバック
  4. 星5つが読み込まれたページ に 再度星5つ読み込む(合計10個になる)

となるのだとか…

つまり星10個の時点で他のベー時に移行し、再度ブラウザバックすると星が15個になってしまうわけです。

結論

ページを表示する際に、一度真っさらにしてしまえばいい。
先ほどのコードに1文追加するだけです

show.html.erb
 <script>
 $('#star-rate-<%= @review.id %>').empty(); #これを追加
  $('#star-rate-<%= @review.id %>').raty({
    size: 36,
    starOff:  '<%= asset_path('star-off.png') %>',

解説

これについては私もメンターの言葉を鵜呑みにしていたので、調べてみました。
【jQuery】要素を削除する.remove()と.empty()の違い
こちらの記事によると…

empty();
こちらは指定した要素の中にある子要素を削除します。

とのことでした。

メンターの言葉を借りると

星部分を一度削除→再度読み込む。

となるそうです。

試した方法

ついでなので、調べて試してダメだった方法も載せておきます。

.remove();
これだと親要素(星5つを表示するブロック)ごと削除されてしまうらしく
星5つを表示すらされませんでした。


周りでも数人同じことで困っていたので、結構よくあるのだと思います。
この記事が役に立ちます様に。

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

【Rails jQuery Raty】ブラウザバックすると星が増える不具合

はじめまして、プログラミングを初めて3ヶ月の初学者です。
Ratyを利用した星評価を実装中に
ブラウザバックで星評価のある画面に戻ると
星5個が星10個になるという不具合にぶつかりました。

いくら検索しても解決せず、最終的にメンターと試行錯誤して解決したので
今日は同じ目に逢ってる方と、いつかの自分の為に記録します。

ちなみに初めて書くのでお手柔らかに…

目次

  • 開発環境
  • 不具合
  • 何故なるのか?
  • 結論 ←ココが解決策
  • 解説
  • 試した方法

開発環境

  • Cloud9
  • ruby '2.6.3'
  • Rails 5.2.4.5
  • jquery

不具合

本来5つ星での評価をしたいのに、
ブラウザバックで評価の存在するページに戻ると
星の数が倍になる
スクリーンショット 2021-03-25 16.16.20.png

正しい表示はこう
スクリーンショット 2021-03-25 16.16.37.png

何故なるのか?

不具合時のコードを見ると記載がこうなります。

show.html.erb
<div id="star-rate-<%= @review.id %>"></div>

 <script>
  $('#star-rate-<%= @review.id %>').raty({
    size: 36,
    starOff:  '<%= asset_path('star-off.png') %>',
    starOn : '<%= asset_path('star-on.png') %>',
    starHalf: '<%= asset_path('star-half.png') %>',
    readOnly: true,
    score: <%= @review.rate %>,
  });
 </script>

これだと、ページを表示する度に星5つを読み込むことになるとか…
順序で見てみると

  1. 星5つが読み込まれたページを表示
  2. ページ移行
  3. ブラウザバック
  4. 星5つが読み込まれたページ に 再度星5つ読み込む(合計10個になる)

となるのだとか…

つまり星10個の時点で他のベー時に移行し、再度ブラウザバックすると星が15個になってしまうわけです。

結論

ページを表示する際に、一度真っさらにしてしまえばいい。
先ほどのコードに1文追加するだけです

show.html.erb
 <script>
 $('#star-rate-<%= @review.id %>').empty(); #これを追加
  $('#star-rate-<%= @review.id %>').raty({
    size: 36,
    starOff:  '<%= asset_path('star-off.png') %>',

解説

これについては私もメンターの言葉を鵜呑みにしていたので、調べてみました。
【jQuery】要素を削除する.remove()と.empty()の違い
こちらの記事によると…

empty();
こちらは指定した要素の中にある子要素を削除します。

とのことでした。

メンターの言葉を借りると

星部分を一度削除→再度読み込む。

となるそうです。

試した方法

ついでなので、調べて試してダメだった方法も載せておきます。

.remove();
これだと親要素(星5つを表示するブロック)ごと削除されてしまうらしく
星5つを表示すらされませんでした。


周りでも数人同じことで困っていたので、結構よくあるのだと思います。
この記事が役に立ちます様に。

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

【初学者向け】uninitialized constantエラー

uninitialized constantエラーが起きる原因

uninitialized constant errorを直訳すると「初期化されていない定数のエラー」という意味:expressionless:
Railsでは「定数やclassが定義されていない」ことを意味:thinking:
uninitialized constantは名前が間違っているという意味のNameErrorということ:rolling_eyes:
つまり、ファイル名の記述間違いなどで、呼びたいクラス名を記述出来ていない場合などでエラーが発生します。

実際に出たエラー

NameError in Users::RegistrationsController#create
uninitialized constant User::GenderId

ここから考えたこと

う〜ん:thinking:User::GenderIdでエラーが発生してるみたいだから、GenderIdに関わる内容を確認してみたほうがよさそうだな。Railsでは「定数やclassが定義されていない」ことを意味する。。。ということはclass名やモデル名で定義しないといけないところをGenderIdで定義しているのでは。。。:thinking:おっ!!

原因

user.rb
(省略)
  extend ActiveHash::Associations::ActiveRecordExtensions
  belongs_to :gender_id
(省略)

アソシエーションを間違えていたみたいです。。。
アソシエーションはテーブル同士で関連付けておき、一方のモデルからもう一方のモデルにアクセスできるようへするためのものなので、
今回の場合だと「GenderIdにアクセスしようとしたけどそんなものないですよ」と伝えてくれていたみたいです。 ⬅︎ という認識であってます??

user.rb
(省略)
  extend ActiveHash::Associations::ActiveRecordExtensions
  belongs_to :gender
(省略)

上記のように編集したことで解決しました!!(Genderは定義されているので)
完全な凡ミスでした。。。

参考

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

RailsのGPL混入問題についてまとめ(mimemagic)

!!New!!mimemagicがMITに戻った(3/26追記)

概要

RailsのGPL混入問題についてまとめました。間違いがあればご指摘ください。(2021/3/25現在)
https://github.com/rails/rails/issues/41750

ここには3つの問題がある。

  • Railsが依存しているmimemagicのライセンスがMITからGPL2.0になった
    • もともとGPLライセンスのものが混入していたのにMITになってしまっていた
  • これにより、Railsのbuildができなくなった
    • Railsが依存しているmimemagic0.3.5が削除されたことが原因
  • Railsの依存モジュールにGPLライセンスのものが混入することとなった
    • mimemagicを0.3.6以降にすればbuildはできるが、GPL問題は解決しない

mimemagicのライセンスがMITからGPL2.0になった

mimemagicはMITからGPLに変わった。
mimemagicのissues #97によると、

shared-mime-infoはGPLライセンス。
mimemagicの以下のファイルがshared-mime-info依存している。
https://github.com/minad/mimemagic/blob/master/script/freedesktop.org.xml

これまでmimemagicはMITだったが、shared-mime-infoがGPLなので、mimemagicもGPLにすべきであり修正された。
変更内容:https://github.com/minad/mimemagic/commit/c0f7b6b21a192629839db87612794d08f9ff7e88

Railsがbuildできない問題

前述のGPL問題修正により、Railsのbuildができない問題が起きた。
https://github.com/minad/mimemagic/issues/98

bundle installすると以下のエラーが発生する。

Installing dependencies using bundler 2.2.1
Running: bundle install --jobs=4 --retry=4

Your bundle is locked to mimemagic (0.3.5), but that version could not be found
in any of the sources listed in your Gemfile. If you haven't changed sources,
that means the author of mimemagic (0.3.5) has removed it. You'll need to update
your bundle to a version other than mimemagic (0.3.5) that hasn't been removed
in order to install.

Railsが依存しているmiemagicは0.3.5。これがbut that version could not be foundと言われる。削除されたみたい。

Railsの依存モジュールにGPLライセンス混入

0.3.6以上にすればbuildはできるようになるみたいだが、0.3.6も0.4.0もGPLなのでライセンス問題は解決されない。

GPLのソフトウェアをサーバサイドで使う場合の著作権表示について

GPLのソフトウェアをサーバサイドで使う場合の著作権表示について

GPLのライブラリをサーバサイドで使う場合、ソースコード公開義務はないという解釈が一般的です。

「サーバサイドで使えばプログラムの頒布じゃないからソースコード公開しなくていいじゃん!」という解釈のもと、ウェブサービスではGPLものが結構使われています。

どういうことかというと、ウェブサービスはプログラムの出力結果の頒布であって、プログラムの頒布ではないのでソースコードの公開義務はないという解釈です。
GCCでコンパイルされたバイナリを頒布してもソース公開義務がないのと同じ解釈です。

という解釈があるようだが、どうなんだろうか。

リンク

mimemagicがMITに戻った(3/26追記)

mimemagicの修正PR:Externalise source data #3

this PR removes it from the gem, and instead requires the user to provide one themselves.
Otherwise an environment variable will need to be set to point it in the right direction.

mimemagicに含まれるshared-mime-infoがGPL2.0だっため、これを取り除いたことによって、MITになった。
しかし、それを自分でインストールしないといけなくなった。

Place the file freedesktop.org.xml in an appropriate location, and then set the environment variable FREEDESKTOP_MIME_TYPES_PATH to that path.

How to fix

shared-mime-infoのインストール

shared-mime-info => MIME typesのデータベース。これがGPL2.0。
http://ftp.riken.jp/Linux/cern/centos/7/updates/x86_64/repoview/shared-mime-info.html

Linux

Linuxならshared-mime-infoはインストールされてるみたい。CentOS7ではインストールされていた。

$ yum list installed | grep shared-mime-info
shared-mime-info.x86_64         1.8-5.el7                 @centos-base

macOS

shared-mime-infoをインストールする。

$ brew install shared-mime-info
$ bundle update mimemagic

もしmimemagicのupgradeだけで動かない人は、nokogiriとmarcelもupgradeするといいかも。
参照:https://github.com/rails/rails/issues/41757#issuecomment-806938727

$ bundle update nokogiri marcel mimemagic

mimemagicを0.3.9にupgrade

$ bundle update mimemagic
$ git diff
diff --git a/Gemfile.lock b/Gemfile.lock
index cef0127..4a0ef93 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -133,7 +133,9 @@ GEM
     marcel (0.3.3)
       mimemagic (~> 0.3.2)
     method_source (1.0.0)
-    mimemagic (0.3.5)
+    mimemagic (0.3.9)
+      nokogiri (~> 1)
+      rake
     mini_mime (1.0.2)
     mini_portile2 (2.4.0)
     minitest (5.14.3)

shared-mime-infoのPATHを環境変数にセット

PATHは以下のあたりにある。参考:possible_paths

  • /usr/local/share/mime/packages/freedesktop.org.xml
  • /opt/homebrew/share/mime/packages/freedesktop.org.xml
  • /usr/share/mime/packages/freedesktop.org.xml

CentOS7では以下にあった。

$ ls -l /usr/share/mime/packages/freedesktop.org.xml
-rw-r--r-- 1 root root 2196823 Apr  1  2020 /usr/share/mime/packages/freedesktop.org.xml

Rails起動時にこれを環境変数 FREEDESKTOP_MIME_TYPES_PATH としてセットする。

$ FREEDESKTOP_MIME_TYPES_PATH=/usr/share/mime/packages/freedesktop.org.xml \
    bundle exec pumactl start

一応これで起動したし、問題はなさそうに見える。

これでよいのかrailsのIssueで聞いてみてる。(3/26 12:00現在)
https://github.com/rails/rails/issues/41757#issuecomment-807898051

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

RailsのGPL混入問題についてまとめ

!!New!!mimemagicがMITに戻った(3/26追記)

概要

RailsのGPL混入問題についてまとめました。間違いがあればご指摘ください。(2021/3/25現在)
https://github.com/rails/rails/issues/41750

ここには3つの問題がある。

  • Railsが依存しているmimemagicのライセンスがMITからGPL2.0になった
    • もともとGPLライセンスのものが混入していたのにMITになってしまっていた
  • これにより、Railsのbuildができなくなった
    • Railsが依存しているmimemagic0.3.5が削除されたことが原因
  • Railsの依存モジュールにGPLライセンスのものが混入することとなった
    • mimemagicを0.3.6以降にすればbuildはできるが、GPL問題は解決しない

mimemagicのライセンスがMITからGPL2.0になった

mimemagicはMITからGPLに変わった。
mimemagicのissues #97によると、

shared-mime-infoはGPLライセンス。
mimemagicの以下のファイルがshared-mime-info依存している。
https://github.com/minad/mimemagic/blob/master/script/freedesktop.org.xml

これまでmimemagicはMITだったが、shared-mime-infoがGPLなので、mimemagicもGPLにすべきであり修正された。
変更内容:https://github.com/minad/mimemagic/commit/c0f7b6b21a192629839db87612794d08f9ff7e88

Railsがbuildできない問題

前述のGPL問題修正により、Railsのbuildができない問題が起きた。
https://github.com/minad/mimemagic/issues/98

bundle installすると以下のエラーが発生する。

Installing dependencies using bundler 2.2.1
Running: bundle install --jobs=4 --retry=4

Your bundle is locked to mimemagic (0.3.5), but that version could not be found
in any of the sources listed in your Gemfile. If you haven't changed sources,
that means the author of mimemagic (0.3.5) has removed it. You'll need to update
your bundle to a version other than mimemagic (0.3.5) that hasn't been removed
in order to install.

Railsが依存しているmiemagicは0.3.5。これがbut that version could not be foundと言われる。削除されたみたい。

Railsの依存モジュールにGPLライセンス混入

0.3.6以上にすればbuildはできるようになるみたいだが、0.3.6も0.4.0もGPLなのでライセンス問題は解決されない。

GPLのソフトウェアをサーバサイドで使う場合の著作権表示について

GPLのソフトウェアをサーバサイドで使う場合の著作権表示について

GPLのライブラリをサーバサイドで使う場合、ソースコード公開義務はないという解釈が一般的です。

「サーバサイドで使えばプログラムの頒布じゃないからソースコード公開しなくていいじゃん!」という解釈のもと、ウェブサービスではGPLものが結構使われています。

どういうことかというと、ウェブサービスはプログラムの出力結果の頒布であって、プログラムの頒布ではないのでソースコードの公開義務はないという解釈です。
GCCでコンパイルされたバイナリを頒布してもソース公開義務がないのと同じ解釈です。

という解釈があるようだが、どうなんだろうか。

リンク

mimemagicがMITに戻った(3/26追記)

mimemagicの修正PR:Externalise source data #3

this PR removes it from the gem, and instead requires the user to provide one themselves.
Otherwise an environment variable will need to be set to point it in the right direction.

mimemagicに含まれるshared-mime-infoがGPL2.0だっため、これを取り除いたことによって、MITになった。
しかし、それを自分でインストールしないといけなくなった。

Place the file freedesktop.org.xml in an appropriate location, and then set the environment variable FREEDESKTOP_MIME_TYPES_PATH to that path.

How to fix

shared-mime-infoのインストール

shared-mime-info => MIME typesのデータベース。これがGPL2.0。
http://ftp.riken.jp/Linux/cern/centos/7/updates/x86_64/repoview/shared-mime-info.html

Linux

Linuxならshared-mime-infoはインストールされてるみたい。CentOS7ではインストールされていた。

$ yum list installed | grep shared-mime-info
shared-mime-info.x86_64         1.8-5.el7                 @centos-base

macOS

shared-mime-infoをインストールする。

$ brew install shared-mime-info
$ bundle update mimemagic

もしmimemagicのupgradeだけで動かない人は、nokogiriとmarcelもupgradeするといいかも。
参照:https://github.com/rails/rails/issues/41757#issuecomment-806938727

$ bundle update nokogiri marcel mimemagic

mimemagicを0.3.9にupgrade

$ bundle update mimemagic
$ git diff
diff --git a/Gemfile.lock b/Gemfile.lock
index cef0127..4a0ef93 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -133,7 +133,9 @@ GEM
     marcel (0.3.3)
       mimemagic (~> 0.3.2)
     method_source (1.0.0)
-    mimemagic (0.3.5)
+    mimemagic (0.3.9)
+      nokogiri (~> 1)
+      rake
     mini_mime (1.0.2)
     mini_portile2 (2.4.0)
     minitest (5.14.3)

shared-mime-infoのPATHを環境変数にセット

PATHは以下のあたりにある。参考:possible_paths

  • /usr/local/share/mime/packages/freedesktop.org.xml
  • /opt/homebrew/share/mime/packages/freedesktop.org.xml
  • /usr/share/mime/packages/freedesktop.org.xml

CentOS7では以下にあった。

$ ls -l /usr/share/mime/packages/freedesktop.org.xml
-rw-r--r-- 1 root root 2196823 Apr  1  2020 /usr/share/mime/packages/freedesktop.org.xml

Rails起動時にこれを環境変数 FREEDESKTOP_MIME_TYPES_PATH としてセットする。

$ FREEDESKTOP_MIME_TYPES_PATH=/usr/share/mime/packages/freedesktop.org.xml \
    bundle exec pumactl start

一応これで起動したし、問題はなさそうに見える。

これでよいのかrailsのIssueで聞いてみてる。(3/26 12:00現在)
https://github.com/rails/rails/issues/41757#issuecomment-807898051

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

【Rails】ユーザー編集時だけパスワードのバリデーションを無効にする(devise)

環境

macOS: Big Sur Ver11.2.2
Rails: 6.0.0
Ruby: 2.6.5

この記事の目標

devise のデフォルトのユーザー編集画面ではパスワードの変更ができるが、現在のパスワードを入力しないとユーザー情報が編集できないようになっている。

そこで、
①現在のパスワードを入力しなくてもユーザー情報ができるようにする
②ユーザー編集のときだけモデルに設定しているパスワードのバリデーションを解除し、パスワード変更欄が空欄のままでもユーザー編集ができるようにする

の2点を実装することを目標とします。

現在のパスワードを入力不要にする

これについては多くの記事が出ています。以下が参考になります。

参考①
参考②
GitHub公式のwiki

ユーザー編集時だけパスワードのバリデーションを解除する

まず、Userモデルのバリデーションは以下のとおり

app/models/user.rb
class User < ApplicationRecord
  has_many :menus

  extend ActiveHash::Associations::ActiveRecordExtensions
  belongs_to :prefecture
  belongs_to :category

  has_one_attached :image

  # Include default devise modules. Others available are:
  # :confirmable, :lockable, :timeoutable, :trackable and :omniauthable
  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :validatable

  # パスワードは半角英数混合で8文字
  validates :password, format: { with: /\A(?=.*?[a-z])(?=.*?\d)[a-z\d]{8}\z/i }
  with_options presence: true do
    validates :shop_name
    validates :address
    validates :business_hours
    validates :holiday
    validates :image
    validates :phone_number, format: { with: /\A0[1-9]\d{0,3}[-(]\d{1,4}[-)]\d{4}\z/ }
    with_options numericality: { other_than: 0, message: 'を選択してください' } do
      validates :category_id
      validates :prefecture_id
    end
  end
end

真ん中あたりにパスワードカラムのバリデーションに関する記述があり、正規表現を使っているため自動的に presence: true の存在性のバリデーションが効いています。
これは新規登録時には必須なのですが、編集時にパスワードを変えずに他の項目だけ変更したい場合にバリデーションエラーのもとになってしまいます。

解決策(オプションを記述する)

上記のパスワードカラムに対するバリデーションに on: :create オプションを記述します。
これにより新規登録時のみバリデーションがかかるようになります!
参考:Railsガイド「onオプション」

app/models/user.rb
class User < ApplicationRecord
()

  # on: :createオプションを追記
  validates :password, format: { with: /\A(?=.*?[a-z])(?=.*?\d)[a-z\d]{8}\z/i }, on: :create
  with_options presence: true do
    validates :shop_name
    validates :address
    validates :business_hours
    validates :holiday
    validates :image
    validates :phone_number, format: { with: /\A0[1-9]\d{0,3}[-(]\d{1,4}[-)]\d{4}\z/ }
    with_options numericality: { other_than: 0, message: 'を選択してください' } do
      validates :category_id
      validates :prefecture_id
    end
  end
end

ただし、これではユーザー編集時にパスワードに関するバリデーション(今回は半角英数混合で8文字)がまったく効かなくなってしまい、「abc」とかでもパスワード設定できてしまいます。
そこで、今回はバリデーションの記述を2行に分け、allow_blank: true オプションを追記します。

ということで、最終的なモデルのコードはこんな感じ。

app/models/user.rb
class User < ApplicationRecord
  has_many :menus

  extend ActiveHash::Associations::ActiveRecordExtensions
  belongs_to :prefecture
  belongs_to :category

  has_one_attached :image

  # Include default devise modules. Others available are:
  # :confirmable, :lockable, :timeoutable, :trackable and :omniauthable
  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :validatable

  # パスワードに関するバリデーションをcreateとupdateで別々に記述しました
  validates :password, format: { with: /\A(?=.*?[a-z])(?=.*?\d)[a-z\d]{8}\z/i }, on: :create
  validates :password, format: { with: /\A(?=.*?[a-z])(?=.*?\d)[a-z\d]{8}\z/i, message: 'は半角英数混合で8文字です' }, allow_blank: true, on: :update
  with_options presence: true do
    validates :shop_name
    validates :address
    validates :business_hours
    validates :holiday
    validates :image
    validates :phone_number, format: { with: /\A0[1-9]\d{0,3}[-(]\d{1,4}[-)]\d{4}\z/ }
    with_options numericality: { other_than: 0, message: 'を選択してください' } do
      validates :category_id
      validates :prefecture_id
    end
  end

  def update_without_current_password(params)
    if params[:password].blank? && params[:password_confirmation].blank?
      params.delete(:password)
      params.delete(:password_confirmation)
    end
    update(params)
  end

end

これでユーザー編集時にパスワードの変更をしない場合は空欄のまま変更ができ、パスワードの変更をする場合は正規表現を適用することができました!

以上!

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

Active Storageで画像投稿とツイート機能を実装しようとしたらエラーになってしまいました

RailsでActive Storageを使って画像も投稿できるツイッターのようなものを作ろうとしていたのですが、それをしている途中でRouting Errorになってしまいました。

Routing Error
No route matches [POST] "/posts/new"

今回はその解決法と、投稿が表示される仕組みから今回どうしてエラーが解決できたのかを自分なりにまとめていきたいと思います。

エラーになってしまったcontrollerがあるのでそちらを見ていきたいと思います。
いかがそのcontrollerの部分のコードです。

class PostsController < ApplicationController
  before_action :authenticate_user!, only: [:new, :update, :create, :edit, :update, :destroy]
  before_action :find_post, only: [:edit, :update, :show, :destroy]

    def index
        @posts = Post.all
        @like = Like.new
      end

      def new
        @like = Like.new
      end

    def show

    end


    def create
      @post = current_user
      @post = Post.create(post_params)
      if @post.save
        redirect_to root_path,notice:'投稿に成功しました'
        else
          redirect_to new_post_path,notice:'投稿に失敗しました'
        end
      end

      def edit
      end

      def update
        @post.update(post_params)
      end

      def destroy
        if @post.destroy
          redirect_to root_path,alert: '投稿を削除しました'
        else
          redirect_to root_path
        end
      end

      private

        def post_params
          params.require(:post).permit(:content, images: []).merge(user_id: current_user.id)
        end

        def find_post
          @post = Post.find(params[:id])
        end

        def force_redirect_unless_my_post
          return redirect_to root_path,alert:'権限がありません'if @post.user != current_user
        end
end

@likeなどの物が入っていますが、それはいま高評価機能を作っているからそうなっています。
そして今回の原因なのですが、newメソッドの中にpostのnewメソッドというオブジェクトという今回でいうと投稿を作る機能を入れておく器を作るコードが書いていないからです。
当たり前ですがRailsは
RoutingControllermodelviewの順番で動いていきます。
ですが、仮にRoutingまで行けたとしても、そこから先のControllerで指定されたActionを用意できないとRouting errorになってしまいます。
ですので、正しいコードはこちらになります

正しいコード

class PostsController < ApplicationController
  before_action :authenticate_user!, only: [:new, :update, :create, :edit, :update, :destroy]
  before_action :find_post, only: [:edit, :update, :show, :destroy]

    def index
        @posts = Post.all
        @like = Like.new
      end

      def new
        @post = Post.new
        @like = Like.new
      end

    def show

    end


    def create
      @post = current_user
      @post = Post.create(post_params)
      if @post.save
        redirect_to root_path,notice:'投稿に成功しました'
        else
          redirect_to new_post_path,notice:'投稿に失敗しました'
        end
      end

      def edit
      end

      def update
        @post.update(post_params)
      end

      def destroy
        if @post.destroy
          redirect_to root_path,alert: '投稿を削除しました'
        else
          redirect_to root_path
        end
      end

      private

        def post_params
          params.require(:post).permit(:content, images: []).merge(user_id: current_user.id)
        end

        def find_post
          @post = Post.find(params[:id])
        end

        def force_redirect_unless_my_post
          return redirect_to root_path,alert:'権限がありません'if @post.user != current_user
        end
end

ここの
newメソッドの中に注目してください

      def new
        @post = Post.new
        @like = Like.new
      end

@post = Post.newが加わっているので、投稿(post)のオブジェクトが作られています。
これにより、Routingで指定したアクションが用意されているのでエラーが解決できるわけです。
皆さん是非参考にしてみてください

最後に

今回terateilさんで質問をして回答をしていただきました。
今回質問を回答していただいたので、その方のURLをはらせていただきます。
この場でお礼申し上げます
https://teratail.com/users/maisumakun#reply

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

Rails→Git→Heroku→サイト公開 〜手順〜

概要

Railsで作成したプロジェクトをデプロイしてサイト公開するまでの手順の解説
今回は公開の手順なので導入方法については明記していません。参考欄をご参照ください。
メモなので細かい事は書いてません。すいません。

目次

手順① プロジェクトの準備
手順② Gitとの連携
手順③ herokuとの連携

環境

version etc
PC 13-inch, m1, 2020 MacBookPro
OS 11.2.3 Big Sur
Rails 6.1.3
git 2.24.3 Apple Git-128
heroku 7.51.0 node-v12.21.0
bundler 2.1.4

手順① プロジェクトの準備

Railsで新規プロジェクトを作成

% rails new sample_app(← 任意の名前)

終わると↓こんな感じ↓でケーキとクラッカーで祝福してくれる(笑)

├─ websocket-extensions@0.1.4
└─ ws@6.2.1
✨  Done in 9.05s.
Webpacker successfully installed ? ?

移動して確認してみると色々作ってくれてる。すごい!ハイテク!

% cd sample_app

~/sample_app master*
% ls
Gemfile     app         config.ru   node_modules       storage      yarn.lock
Gemfile.lock  babel.config.js  db       package.json       test
README.md    bin         lib       postcss.config.js    tmp
Rakefile     config       log       public           vendor

続いてRailsサーバー起動! ちゃんと起動できるとしたみたいになる↓

~/sample_app master*
% rails s
=> Booting Puma
=> Rails 6.1.3 application starting in development 
=> Run `bin/rails server --help` for more startup options
Puma starting in single mode...
* Puma version: 5.2.2 (ruby 2.7.2-p137) ("Fettisdagsbulle")
*  Min threads: 5
*  Max threads: 5
*  Environment: development
*          PID: 52125
* Listening on http://127.0.0.1:3000
* Listening on http://[::1]:3000 # localhost:3000 で開いてみ!って言われてる
Use Ctrl-C to stop # わかってると思うけどサーバーを止める時は Ctrl + C を押せよって言われてる

言われた通りブラウザで localhost:3000 を開くとなんか祝福されてる

手順② Gitとの連携

github アカウントの作成

下記リンクよりアカウントを作成してください。

リポジトリ作成

リポジトリをプライベートで新規作成してリポジトリができたらターミナルで下記を入力

% git init
% git add .
% git commit -m "first commit"
% git remote add origin https://github.com/アカウント名/リポジトリ名.git
% git push -u origin master

Gitに更新をかけてみてリポジトリの内容が追加されていればOKです。

ついでによく使いそうなコマンドのエイリアスを作成しておきます。

.zshrc
alias gs='git status' # 今いるブランチや追加などがあるか表示してくれる
alias ga='git add .' # バージョン管理下に置く
alias gc='git commit -m '$1 # コミットする 第1引数に文字列を送れるようにしてる。(関数にして色々やる方が便利)
alias gp='git push' # プッシュする
alias gb='git branch' # ブランチ一覧と自分のいるブランチを表示

これでGitとの連携はOKですね!色々といじってみてください

手順③ Herokuとの連携

色々と準備

railsの基本データベースはSQlite3になっていると思うのですがherokuはPostgreSQLらしいのでGemfileを書き換えます。
Gemfileは作成プロジェクト内に入ってます。(左の -数値 は大体この位の行に書いてあるっていうのを明記しています。)

////////// 下記2行を削除 ///////////
-8  # Use sqlite3 as the database for Active Recor
-9  gem 'sqlite3', '~> 1.4'
/////////////////////////////////

-29 group :development, :test do
-30   gem 'sqlite3', '1.4.2' # ← この一文を追加 ///////////
-31   # Call 'byebug' anywhere in the code to stop execution and get a debugger console
-32   gem 'byebug', platforms: [:mri, :mingw, :x64_mingw]
-33 end

/////////// まるまる追加[追加する行はどこでも大丈夫です] ///////////
-54 group :production do
-55   gem 'pg', '1.1.4'
-56 end
/////////////////////////////////

できたらこれをGemfile.lockに反映させるので下記を実行。じゃないとherokuにデプロイした時にエラー吐きます。(自分は結構忘れがちになるのでエラーだいぶ吐かれました)

% bundle install --without production

実行完了したらこんなんでました。

Bundle complete! 18 Gemfile dependencies, 75 gems now installed.
Gems in the group production were not installed.
Use `bundle info [gemname]` to see where a bundled gem is installed.

変更内容をgitにも反映させておきましょう!(やり方は手順②を参考に)ここでエイリアスためしてみてね。

まちにまったheroku

% heroku --version

でバージョン表示されない方はherokuをインストールしてくださいな。
ちゃんとバージョンが表示されるよになったら heroku にログインします。(アカウントがない方は作成してね。無料ね。)
ログイン方法は好きなほうで大丈夫です。

% heroku login # ブラウザが立ち上がってログイン画面が出るってよ
% heorku login --interactive # ターミナルのままログイン情報打ち込めるってよ

ログイン完了するとターミナルに下記画面が表示されます。

Logging in... done
Logged in as 自分のアカウントのメールアドレス

そしたらherokuに新規アプリケーション(プロジェクト)を作成しまする。

% heroku create

完了するとこんなんでました。適当にアプリ名を考えて作成してくれます。

Creating app... done, ⬢ [適当な数値と文字列]
https://[適当な数値と文字列].herokuapp.com/ | https://git.heroku.com/[適当な数値と文字列].git

herokuapp.com/のURLを打ち込むともうサイトができてますね!
そうですあとはここにGitを反映させるだけですね!
それでは魔法の呪文を打ち込みます。

% git push heroku master

そうするとターミナルにババババババと出てきて色々してくれます。
初回のみRailsをインストールするので時間がちょっとかかるかもしれないですね。
完了すると、、、

reemote:        https://[適当な数値と文字列].herokuapp.com/ deployed to Heroku
remote: 
remote: Verifying deploy... done.
To https://git.heroku.com/[適当な数値と文字列].git
 * [new branch]      master -> master

と完了したみたいなのでherokuapp.com/のURLにアクセスします。

ヽ(゚◇゚ )ノ!!!!

みんなが祝福してくれたあの微笑ましい画像が出ると思ってました。。
ってことで元のプロジェクトの方で適当に書いていきます。

application_controller.rb
class ApplicationController < ActionController::Base
    def hello
        render html:"hello, ruby on rails"
    end
end
routes.rb
Rails.application.routes.draw do
  root 'application#hello'
end

これでgitとherokuにまたaddしてcommitしてpushします。
完了したらまた herokuapp.com にアクセスしてみます。

hello, ruby on rails

と表示されていればOKですね!
無事サイト公開までいきました!でめたし!でめたし!

応用編

git push で heroku を更新や 独自ドメインの設定方法など記載しました。
Rails→Git→Heroku→サイト公開 〜応用〜

参考

Ruby on Rails チュートリアル
【初心者向け】railsアプリをherokuを使って確実にデプロイする方法【決定版】

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

Rails→Git→Heroku→サイト公開 〜応用〜

前記事

Railsプロジェクトの作成からGitにpushからherokuにデプロイの手順の解説してます。

Rails → Git → Heroku → サイト公開  〜手順〜

目次

応用① git push で heroku も反映
応用② 独自ドメイン使いたくね?

応用① git push で heroku も反映

gitにpushしたらherokuも自動的に変わって欲しいと思ったのでherokuのdeploy画面にいきます。

選択すると↓画面みたくなるので repo name のところに自分のgitのリポジトリ名を入力

そうすると下の方に自分のリポジトリ名が出てくるのでconnectionみたいなやつを押す

無事連携できると↓画面みたくなるので Enable Automatic Deploys をクリック

確認のためコード編集

application_controller.rb
class ApplicationController < ActionController::Base
    def hello
        render html:"hello, ruby on rails / git and heroku"
    end
end

編集したら git の方だけで add して commit して push!!
これ一連でやってくれるコマンドあるんで気になる方は調べてみてね。

ちょっと待機・・・(git pushはすぐ終わるけど heroku へのデプロイはちょっと時間かかる。20秒位?)

そんでherokuapp.comのURLを確認

 
hello, ruby on rails / git and heroku

 

って表示されたらOKです!無事gitのpushだけでサイトに反映できました。
「gitにpushしただけで反映されたら困るよー」って方は
解除するなり、別ブランチにデプロイしてmasterにpullするなりなんなりしてくださいな。
はい!次!

応用② 独自ドメイン使いたくね?

って事なんですけど、無料じゃできないみたいです。

なのでここからは有料でもいいよって方だけみてください。
って言っても $7/month のプランに切り替えるだけです。
切り替えは Resources から Change Dyno Type を Free から Hobby に変更すれば良いだけです。
詳しい DynoType については公式で → Heroku Dynos

肝心のドメインは お名前ドットコム でドメインを取得して反映させます。
ドメインの取得はこちらを参考に → ドメイン取得方法

好きなドメインを取得したらherokuに登録します。今回はサンプルで test-sample.com で書いていきます。
なので test-sample.com のところを自分で取得したドメイン名に置き換えてくださいな。

% heroku domains:add test-sample.com

ちゃんと登録できると

Configure your app's DNS provider to point to the DNS Target [ランダムな数値とアルファベット].herokudns.com.
    For help, see https://devcenter.heroku.com/articles/custom-domains

The domain test-sample.com has been enqueued for addition
Run heroku domains:wait 'test-sample.com' to wait for completion
Adding test-sample.com to ⬢ [自分のherokuのAPP名]... done

DNS Targetは後ほど使います。
次にheorkuのResouces画面からAdd-onsで 「PointDNS」を検索して追加します。
そうすると↓の画像みたいにると思うので

そのPointDNSを選択します。
domain name ってところに取得したドメインを入力

色々出てくるので + Add record をクリック

ここで諸々記入していきます。

Record type : CNAMEに変更します。
Name : www
Hostname : DNS Targetを入力

これでAdd recordすればOKです。

ここで Type NS っていうのが4つくらいあるかと思うんですがこれを
お名前ドットコムのほうで使用します。

この画面に移動すると思うので

ホスト名 : www
Type : CNAME に変更
VALUE : DNS Targetを入力

追加で追加します。
追加されたら設定を忘れずにおこないましょう。
「DNSレコード設定用ネームサーバー変更確認」はチェックを入れずに確認画面に進み設定します。

一応ネームサーバも設定します。

自分のドメインを選択し、その他のネームサーバーを使うを選択
ネームサーバ1、2、3、4 に
先程のPointDNSの4つの Type NS のやつのDataだけをコピペしていきます。
末尾が com. となっていたら . は消してください。
全て入力したら確認を押します。

これで終了です。

ちょっと待機・・・(反映はすぐにはされません。30分くらいは待ったかな?)
気長に待ちましょう。

WWW.自分のドメイン名 でherokuと同じページが出たら無事独自ドメインで反映されています。

おわりに

結構反映されないと不安ですが気長に待ちましょう。
ちょっと大変ですが、レンタルサーバー借りて色々やるよりかなりコストカットできると思います。
反映されればsslも簡単に設定できます。

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

seedで10人ユーザーを作って1人5つの記事を持たせるユーザーを作成する

開発環境

環境
ruby:2.6.4
rails:6.0.3
userにはusersモデル、username,email,password,password_confirmation(確認用)を持たせ、
投稿にはarticlesモデル、title,textカラムを持たせています。
画像のアップロードはactivestorageを使用してavatarとしています。
gem 'Faker'を使用しています。

題名の通り早速作っていきます

アカウント=10人
1人のアカウント=5つのarticle(記事)
Faker::Name.nameはランダムに名前を作成してくれるfakerの機能、
Faker::Internet.emailはランダムにemailを作成してくれる機能、
Faker::Hacker.say_something_smartはランダムで文字を作成してくれる機能だと思います。
これに関してはあまり理解しておらずとりあえず文字を作成してくれたので使用しています
指定の文字を入れたい場合は
titel: "任意の文字"
text: "任意の文字"
とすれば良いと思います。
avatarには/assets/imagesに保存してある画像を登録するようにしてあります。

seed.rb
puts 'users ...'
10.times do
  user = User.create!(
    username: Faker::Name.name,
    email: Faker::Internet.email,
    password: 'foobar',
    password_confirmation: 'foobar',
  )
    user.avatar.attach(io: File.open('app/assets/images/cat.jpg'),filename: 'cat.jpg')
end

puts 'articles ...'
5.times do |n|
  User.all.each do |user|
    user.articles.create!(
      title: Faker::Hacker.say_something_smart,
      text: Faker::Hacker.say_something_smart
    )
  end
end
user.avatar.attach(io: File.open('app/assets/images/cat.jpg'),filename: 'cat.jpg')

上のavatarを登録するコードですが必ず()の外で書くようにしてください。
()の中で書くと誤字のエラーがでます。僕はこのエラーに5時間ほどハマりました。
それにactivestorageの場合あまり情報が少ないので本当にこの記述で正解なのかもわかりませんがとりあえず成功できているので良いと思います。
これで内容がよければ

rails db:seed

と打てば完了になります。

最後にいつでもログインできるアカウントを作る

migrate:resetしたときにいちいちアカウントを作り直すのもめんどくさいのでseedにいつでもログインできるようにメール認証もいらないアカウントを作ります。
人気俳優のお二人に参加してもらいます。
user1.skip_confirmation!
これはメール認証はスキップするコードみたいです。
しかしこのまま実行するとアカウント作成は成功したもののメール認識のスキップはできていませんでした。
そこで最後に
user1.save!と保存し、実行してみると見事成功していました。
今回は画像を保存するのは面倒だったのでコードは書きませんでしたが上と同じようにすることでできるかと思います。

seed.rb
user1 = User.create!(
  username: "横浜流星",
  email: "任意のアドレス",
  password: '123456',
  password_confirmation: '123456',
)
user1.skip_confirmation!
user1.save!

user2 = User.create!(
  username: "成田凌",
  email: "任意のアドレス",
  password: 'abcdef',
  password_confirmation: 'abcdef',
)
user2.skip_confirmation!
user2.save!

rails初学者が書いた記事なので間違いがあるかもしれません
もし間違いがある場合報告してもらえますと助かります。

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

ローカルでのエラーAccess denied for user 'root'@'localhost' (using password: NO)が出たとき。

・エラー:Access denied for user 'root'@'localhost' (using password: NO)

・問題は『PWがないので開かない。』ということです。

・解決方法
 ・root ユーザーのPW設定を無くして、rails sで動くようにする。
 ・mysqlで設定したパスワードをdatabase.ymlに書く。

・手順
 ・mysqlの起動 
  sudo service mysql start

 ・mysqlにログイン 
  mysql -u root -p

 ・使用するデータベースを選択する。 
  mysql> use mysql;

・パスワードを空にする。
  mysql> update user set authentication_string='' where user='root';
 
 ・変更が成功したらmysqlを閉じる。
  mysql> exit

・変更を反映させるため、,mysqlをストップ。
  sudo service mysql stop

・再び起動。
  sudo service mysql start

・パスワードなしで、ログインできることを確認。
  mysql -u root

・rails s 立ち上げる(rails s をしていた場合は一度閉じてからもう一度立ち上げる)。

以上、お疲れ様で〜〜〜す。
 
  

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

README 画像挿入の仕方

README 画像挿入

Qiita記事では多くのREADMEについての画像の挿入の手順を解説している記事があります。
今回私は、上記記事でのやり方とは違った、googleの拡張機能を使用した画像作成・READMEに画像挿入までの手順を解説します!!

というのも、閲覧記事で参考になったものは多くありましたが、googleの拡張機能を使用した画像挿入についての記事は、調べたところでは見当たらなかったので、
自身で挿入から、解決まで行いました。

ご参考までにしていただけたら幸いです。

なぜ他記事を参考にしなかったか

今回、自身で作成したポートフォリオでは、トップページだけでもかなり下にスクロールして全体像が見えるという、課題がありました。
通常のスクリーンショットでは、一画面に収まりせんでした。
gif画像でもgyazoを使用して全体を撮影することも実行しましたが、
撮影秒数が決められたなか全てを撮影するのは困難且、見にくい画像となってしまいました。

そこでgoogle拡張機能の「Awesome Screenshot」を使用し、全体を撮影できるようにしました。

Awesome Screenshot

googleの検索で「Awesome Screenshot 拡張機能」と検索していただいたらでききます。
インストールを行ったら順位完了です!

Awesome Screenshotではフルページ画像がとる事ができ、非常に使い勝手が便利な拡張機能となっています。

画像撮影手順

インストールしたAwesome Screenshotを撮影したいHPでクリックします。

1.クリック後上部バーで「レコード」「キャプチャ」の選択蘭があるので、「キャプチャ」をクリック

2.「キャプチャ」をクリックしたら、「フルページ」をクリック

3.「フルページ」をクリック後、自動でページを撮影できます

4.撮影した画像をダウンロードして完了

非常に簡単な手順でフルページの撮影ができるのでおすすめです!!

READMEに画像情報記載

ダウンロードした画像を、自身の開発中コードのディレクトリに挿入します。
※画像専用のディレクトリを作成しておくことをおすすめします

私の場合、ダウンロードした画像を
public/imagesディレクトリに格納しています。

後は、格納した画像をREADMEに記述するだけで画像挿入ができます。

<img src="public/images/画像名">

まとめ

非常に簡単に且フルページでの画像挿入ができるので、スクリーンショットで何枚も画像をとる手間を省けます!!

今回掲載した内容は他のqiita記事には調べた限りなかった為、
共有させていただきます!!

issueを利用した画像挿入の仕方もあるので、その際は他の記事にも乗っているのでそちら参考にしていただけたらと思います!!

宜しくお願いします。

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

Ruby on Railsの日付操作まとめ

現在日付/時刻

pry(main)> Time.current
=> Thu, 25 Mar 2021 11:36:35 JST +09:00
pry(main)> 0.days.ago
=> Thu, 25 Mar 2021 12:16:53 JST +09:00

nowよりcurrentを使うほうが良いらしい。
https://qiita.com/kodai_0122/items/111457104f83f1fb2259

特定の日付

pry(main)> Time.new(2021, 3, 25, 11, 22, 33, 00)
=> 2021-03-25 11:22:33 +0000

●日前/●日後

[9] pry(main)> Time.current.yesterday
=> Wed, 24 Mar 2021 11:47:08 JST +09:00
[10] pry(main)> Time.current.tomorrow
=> Fri, 26 Mar 2021 11:47:16 JST +09:00
[11] pry(main)> Time.current.ago(3.days)
=> Mon, 22 Mar 2021 11:47:28 JST +09:00
[12] pry(main)> Time.current.since(3.days)
=> Sun, 28 Mar 2021 11:47:43 JST +09:00
[13] pry(main)>

●ヶ月前/●ヶ月後

[13] pry(main)> Time.current.last_month
=> Thu, 25 Feb 2021 11:50:33 JST +09:00
[14] pry(main)> Time.current.next_month
=> Sun, 25 Apr 2021 11:50:41 JST +09:00
[15] pry(main)> Time.current.ago(3.month)
=> Fri, 25 Dec 2020 11:50:52 JST +09:00
[16] pry(main)> Time.current.since(3.month)
=> Fri, 25 Jun 2021 11:50:59 JST +09:00
[17] pry(main)>

●年前/●年後

[17] pry(main)> Time.current.last_year
=> Wed, 25 Mar 2020 11:52:07 JST +09:00
[18] pry(main)> Time.current.next_year
=> Fri, 25 Mar 2022 11:52:12 JST +09:00
[19] pry(main)> Time.current.ago(3.year)
=> Sun, 25 Mar 2018 11:52:26 JST +09:00
[20] pry(main)> Time.current.since(3.year)
=> Mon, 25 Mar 2024 11:52:34 JST +09:00

0:00/23:59

[23] pry(main)> Time.current.beginning_of_day
=> Thu, 25 Mar 2021 00:00:00 JST +09:00
[24] pry(main)> Time.current.end_of_day
=> Thu, 25 Mar 2021 23:59:59 JST +09:00

月初/月末

[25] pry(main)> Time.current.beginning_of_month
=> Mon, 01 Mar 2021 00:00:00 JST +09:00
[26] pry(main)> Time.current.end_of_month
=> Wed, 31 Mar 2021 23:59:59 JST +09:00

年始/年末

[27] pry(main)> Time.current.beginning_of_year
=> Fri, 01 Jan 2021 00:00:00 JST +09:00
[28] pry(main)> Time.current.end_of_year
=> Fri, 31 Dec 2021 23:59:59 JST +09:00

週明け/週末

[29] pry(main)> Time.current.beginning_of_week
=> Mon, 22 Mar 2021 00:00:00 JST +09:00
[30] pry(main)> Time.current.end_of_week
=> Sun, 28 Mar 2021 23:59:59 JST +09:00

先週/来週(何故か時間は月曜0:00になるよう・・・)

[31] pry(main)> Time.current.last_week
=> Mon, 15 Mar 2021 00:00:00 JST +09:00
[32] pry(main)> Time.current.next_week
=> Mon, 29 Mar 2021 00:00:00 JST +09:00

●曜日

[38] pry(main)> Time.current.beginning_of_week(:wednesday)
=> Wed, 24 Mar 2021 00:00:00 JST +09:00
[39] pry(main)> Time.current.last_week(:wednesday)
=> Wed, 17 Mar 2021 00:00:00 JST +09:00
[40] pry(main)> Time.current.next_week(:wednesday)
=> Wed, 31 Mar 2021 00:00:00 JST +09:00

フォーマット

[41] pry(main)> Time.current.strftime("%Y-%m-%d %T")
=> "2021-03-25 12:09:26"

範囲
範囲をwhere句に渡すとbetweenで検索してくれるらしい

[50] pry(main)> Time.current.all_day
=> Thu, 25 Mar 2021 00:00:00 JST +09:00..Thu, 25 Mar 2021 23:59:59 JST +09:00
[51] pry(main)> Time.current.all_month
=> Mon, 01 Mar 2021 00:00:00 JST +09:00..Wed, 31 Mar 2021 23:59:59 JST +09:00
[52] pry(main)> Time.current.all_year
=> Fri, 01 Jan 2021 00:00:00 JST +09:00..Fri, 31 Dec 2021 23:59:59 JST +09:00
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【AWS】EC2へのデプロイ時にmimemagicのバージョンエラーが出た場合の対処

$ bundle install
The dependency tzinfo-data (>= 0) will be unused by any of the platforms Bundler is installing for. Bundler is installing for ruby but the dependency is only for x86-mingw32, x86-mswin32, x64-mingw32, java. To add those platforms to the bundle, run bundle lock --add-platform x86-mingw32 x86-mswin32 x64-mingw32 java.
Fetching gem metadata from https://rubygems.org/.........
Your bundle is locked to mimemagic (0.3.5), but that version could not be found
in any of the sources listed in your Gemfile. If you haven't changed sources,
that means the author of mimemagic (0.3.5) has removed it. You'll need to update
your bundle to a version other than mimemagic (0.3.5) that hasn't been removed
in order to install.

$ rake secret
Could not find rake-13.0.3 in any of the sources
Run bundle install to install missing gems.

bundle update mimemagic

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

RSpec 多対多関係 モデルテスト(例.Tagモデル)

はじめに

rspecのTagのmodelテストをする際、中間テーブル(post_tag)を介したやり方に苦戦したため、
factory_botを用いて、多対多関係 (has_many through) のテスト作成方法をご説明致します。

テーブル

posttagの間にpost_tagテーブルがある状態です。

スクリーンショット 2021-02-27 11.08.49.png

到達点

以下の2点を達成する
・中間テーブルを介したmodelテストのFactoryBotを理解する
・中間テーブルを介したmodelテストの記述方法を理解する

流れ

① 各モデルのvalidatesを確認
② FactoryBotの記述
③ modelテストの記述

① 各モデルのvalidatesを確認

app/models/post.rb
class Post < ApplicationRecord
  has_many :post_tags, dependent: :destroy
  has_many :tags, through: :post_tags

  validates :title,
            presence: true,
            length: { maximum: 60 }
  validates :body,
            presence: true,
            length: { maximum: 2000 }
end
app/models/post.rb
class Tag < ApplicationRecord
  has_many :post_tags, dependent: :destroy
  has_many :posts, through: :post_tags

  validates :name, presence: true, length: { maximum: 50 }
end
app/models/post.rb
class PostTag < ApplicationRecord
  belongs_to :post
  belongs_to :tag

  validates :post_id, presence: true
  validates :tag_id, presence: true

② FactoryBotの記述

app/spec/factories/post.rb
FactoryBot.define do
  factory :post do
    sequence(:title) { |n| "title-#{n}" }
    sequence(:body) { |n| "body-#{n}" }

    after(:create) do |post|
      create_list(:post_tag, 1, post: post, tag: create(:tag))
    end
  end
end

after(:create)を使用することで、post生成後に、tagとpost_tagが生成されます。

app/spec/factories/tag.rb
FactoryBot.define do
  factory :tag do
    sequence(:name) { |n| "tag-#{n}" }
  end
end

sequenceでユニークnameを生成できます。

app/spec/factories/post_tag.rb
FactoryBot.define do
  factory :post_tag do
    association :post
    association :tag
  end
end

association :post association :tagとすることで、
post_tagのmodelテストにおいて
let(:post_tag) { create(:post_tag) }と記述するだけで
postとtagも生成できます。

ただし、associationはhas_many側(今回の場合,post,tag)では記述せず、
belong_to側でのみ使用しましょう。

③ modelテストの記述

app/spec/requests/post.rb
RSpec.describe Post, type: :model do
  let(:post) { create(:post) }

  it "タイトル、本文、user_idがある場合、有効であること" do
    expect(post).to be_valid
  end

  it "user_idがない場合、無効であること" do
    post.user_id = nil
    expect(post).to be_invalid
  end

  describe "タイトル" do
    it "タイトルがない場合、無効であること" do
      post.title = nil
      expect(post).to be_invalid
      expect(post.errors[:title]).to include("を入力してください")
    end

    context "タイトルが60文字以下の場合" do
      it "有効であること" do
        post.title = "1" * 60
        expect(post).to be_valid
      end
    end

    context "タイトルが61文字以上の場合" do
      it "無効であること" do
        post.title = "1" * 61
        expect(post).to be_invalid
      end
    end
  end

  describe "本文" do
    it "本文がない場合、無効であること" do
      post.body = nil
      expect(post).to be_invalid
      expect(post.errors[:body]).to include("を入力してください")
    end

    context "本文が2000文字以下の場合" do
      it "有効であること" do
        post.body = "1" * 2000
        expect(post).to be_valid
      end
    end

    context "本文が2001文字以上の場合" do
      it "無効であること" do
        post.body = "1" * 2001
        expect(post).to be_invalid
      end
    end
  end
end

app/spec/requests/tag.rb
RSpec.describe Tag, type: :model do
  let(:tag) { create(:tag) }

  describe "name" do
    it "タグ名がある場合、有効であること" do
      expect(tag).to be_valid
    end

    it "タグ名がない場合、無効であること" do
      tag.name = nil
      expect(tag).to be_invalid
      expect(tag.errors[:name]).to include("を入力してください")
    end

    context "タグ名が50文字以下の場合" do
      it "有効であること" do
        tag.name = "1" * 50
        expect(tag).to be_valid
      end
    end

    context "タグ名が51文字以上の場合" do
      it "無効であること" do
        tag.name = "1" * 51
        expect(tag).to be_invalid
        expect(tag.errors[:name]).to include("は50文字以内で入力してください")
      end
    end
  end
end

app/spec/requests/post_tag.rb
RSpec.describe PostTag, type: :model do
  let(:post_tag) { create(:post_tag) }

  it "post_idとtag_idがある場合、有効であること" do
    expect(post_tag).to be_valid
  end

  it "post_idがない場合、無効であること" do
    post_tag.post_id = nil
    expect(post_tag).to be_invalid
  end

  it "tag_idがない場合、無効であること" do
    post_tag.tag_id = nil
    expect(post_tag).to be_invalid
  end
end

associationによってlet(:post_tag) { create(:post_tag) }が一文で済みました。
なお、itやcontext内の文章は、英語だとスペルミス等が出る可能性があるため、基本日本語にしております。

間違い等がありましたらご指摘の方よろしくお願いします。

参考記事

FactoryBot(FactoryGirl)チートシート
factory_girl で最低限知っておきたい4つの使い方
FactoryBot(旧FactoryGirl)で関連データを同時に生成する方法いろいろ

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

バリデーションの書き方(Rails)

バリデーションとは

バリデーションは、正しいデータだけをデータベースに保存するために制約をかける事です。
modelに書くことでデータベースに保存する前に受け取った情報を正しいのか判定させます。

空でないこと

validates :name, presence: true

空であること

validates :name, absence: true

一意性であること(重複していないこと)

validates :name, uniqueness: true

文字数制限

validates :name, length: { minimum: 2 }  # 2文字以上
validates :name, length: { maximum: 50 } # 50文字以下
validates :name, length: { in: 2..50 }   # 2文字以上50文字以下
validates :name, length: { is: 6 }       # 6文字のみ

Boolian型

validates :publish, inclusion: { in: [true, false] }

参考

Active Record バリデーション Railsガイド v.6.1

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