20210308のRailsに関する記事は22件です。

【Rails】モデルのアソシエーションの書き方

環境

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

困ったこと

モデルを作成したときにモデル名をアッパーキャメルケースで記述したとき、アソシエーションを書くときはどういう形式(アッパーキャメル?、キャメルケース?、スネークケース?)で書くのか迷いが生じた。
実際、書き方が違うだけでエラーが出て2時間も解決に時間を要したことから注意したいところ。

実例

作成したモデルは以下の通り。
Acitive Hashのクラスも含んでます。

class OrderDetail < ApplicationRecord
  belongs_to :order

  extend ActiveHash::Associations::ActiveRecordExtensions
  belongs_to :prefecture
  belongs_to :visit_time
  belongs_to :visit_day

  # バリデーションはフォームオブジェクトクラスに記述
end
class VisitDay < ActiveHash::Base
  self.data = [
    { id: 0, name: '--' },
    { id: 1, name: '本日' },
    { id: 2, name: '明日' },
    { id: 3, name: 'あさって' }
  ]

  include ActiveHash::Associations
  has_many :order_details
end
class VisitTime < ActiveHash::Base
  self.data = [
    { id: 0, name: '--' },
    { id: 1, name: '7時〜8時' },
    { id: 2, name: '8時〜9時' },
    { id: 3, name: '9時〜10時' },
    { id: 4, name: '10時〜11時' },
    { id: 5, name: '11時〜12時' },
    { id: 6, name: '13時〜14時' },
    { id: 7, name: '14時〜15時' },
    { id: 8, name: '15時〜16時' },
    { id: 9, name: '16時〜17時' },
    { id: 10, name: '17時〜18時' },
    { id: 11, name: '18時〜19時' },
    { id: 12, name: '19時〜20時' },
    { id: 13, name: '20時〜21時' }
  ]

  include ActiveHash::Associations
  has_many :order_details
end

見ていただければ分かる通り、3つのモデル(クラス)は全てアッパーキャメルケースで定義しています。
こういう場合、アソシエーションでモデル名を記述するときはスネークケースで記述すること!
初心者の自分はアソシエーションを書くときになんとなくキャメルケースで書いてしまい、それがエラーの原因となっていました。

皆様はどうぞお気をつけください。

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

【Stripe Rails】決済モーダル内のemailにJavaScriptで値を設定する

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

【Ruby on Rails】フォロー・フォロワー機能の実装

はじめに

Railsで作成中のアプリにフォロー・フォロワー機能を実装しました。
理解できていなかったことも多かったので、理解を深めるために投稿します。
解説が長すぎるので、全てリンクにしています。
参考にされる際は必要なところだけご覧いただいても構いません。

前提条件と作成物

前提条件
  • rails 5.2.4
  • devise導入済み
  • userテーブルは作成済み←Userに対しフォロー機能を設定します
作成物
  • フォロー機能(follow,unfollow)
  • フォロー・フォロワー一覧ページ

流れ

  1. Relationshipモデルとカラムを作成
  2. アソシエーションを記述
  3. Userモデルに
    「フォロー機能」「フォロー解除機能」「フォローしているかどうかメソッド」を記述
  4. Relationshipコントローラを作成・編集、ルーティング編集
  5. viewページを作成(フォローボタン)
  6. viewページを作成(フォロー・フォロワー一覧)

1. Relationshipモデルを作成

ターミナル
$ rails g model Relationship follower_id:integer followed_id:integer
# follower_id:フォローするユーザーのid, followed_id:フォローされるユーザーのid
$ rails db:migrate
# マイグレーション実行

まずはRelationshipモデルとカラムを作ります。
今回はRelationshipで作りましたが、他のモデルの場合は置き換えてください。

カラム データ型 備考
relationship_id integer 記述不要、自動作成される
follower_id integer フォローするユーザーのid
followed_id integer フォローされるユーザーのid

【解説】なぜRelationshipモデルのカラムはuser_idではないのか?(クリック)
Relationshipモデルは中間テーブルの役割を担っています。
中間テーブルとは、多 対 多のリレーションを構築するとき、
2つのテーブルを関連づけるために各テーブルの外部キー(FK)をもつテーブルのことです。
今回のフォロー・フォロワー機能は、
・1ユーザーはたくさんのユーザーにフォローできる(1:N)
・1ユーザーはたくさんのユーザーにフォローされる(1:N)
つまり多 対 多のリレーション状態を作る機能です。
通常であれば、参照するテーブルのid(この場合はuser_id)を中間テーブルのカラムに当てることが望ましいですが、
今回の場合は参照されるのは両方ともuserテーブルとなり、重複してしまいます。
そのため、最終的に参照するのはuser_idですが、
分かりやすいように別のカラム名で定義をしてあげる必要があります。
今回は、
follower_id : フォローするユーザーのid
followed_id : フォローされるユーザーのid
としてカラムを設定しました。

中間テーブル?についてはこちら↓
Active Record の関連付け

2. アソシエーションを記述

app/models/relationship.rb
class Relationship < ApplicationRecord
  belongs_to :follower, class_name: "User"
  belongs_to :followed, class_name: "User"
  # class_name: "User"を定義することでUserテーブルのレコードを参照する
end

Relationshipモデルにアソシエーションを記述します。
belongs_to :follower の後にclass_name: "User"を忘れないようにします。


【解説】なぜbelongs_to :follower(followed)なのか?class_nameはなぜ必要?(クリック)
belongs_to :follower, class_name: "User"
を元に見ていきます。
belongs_to = 従属 の意味です。


belongs_to :followerでfollowerテーブルのfollower_idを探しにいきます。
ただ、今回はfollowerテーブルは作成していないのと、
参照してもらいたいのはfollower_idに格納されている値と同じuser_idです。
(user_idはフォロー・フォロワー関係にありますが、
relationshipテーブルの中で重複するため定義できません。
そのため、このような現象が起こります。)
このままではエラーになってしまうため、
class_name: "User"でUserテーブルを参照するように定義します。


これを行うことで、例えば
「relationship_idが(1)の時follower_idに格納されている値と
同じuser_idをUserテーブルから探して」

というように関連づけることができます。(あくまで私の捉え方です)
followed_idに関しても同じなので、同様に定義します。

app/models/user.rb(フォロー機能部分のみ抜粋)
class User < ApplicationRecord
  has_many :reverse_of_relationships, class_name: "Relationship", foreign_key: "followed_id", dependent: :destroy
  has_many :followers, through: :reverse_of_relationships, source: :follower
  # 被フォロー関係を通じて参照→followed_idをフォローしている人

  has_many :relationships, class_name: "Relationship", foreign_key: "follower_id", dependent: :destroy
  # 【class_name: "Relationship"】は省略可能
  has_many :followings, through: :relationships, source: :followed
  # 与フォロー関係を通じて参照→follower_idをフォローしている人
end

UserモデルにもRelationshipとのアソシエーションを記述します。
くどいようですが、user_idを中間テーブルで直接定義していないため、
Relationshipテーブルを参照した時にuser_idが参照できるよう、定義します。
また、今回はフォロー・フォロワー一覧ページも作成するため、2、4行目も記述します。
(フォロー・フォロワー数をカウントする時も使えます。)


【解説】has_many 1行目と3行目の意味は?(クリック)
has_many :reverse_of_relationships, class_name: "Relationship"
先程のRelationshipモデルと同じ意味です。
見分けやすいようにreverse_of_relationshipsと定義しました。
このままだと、reverse_of_relationship_idを探してしまいます。
それを防ぐために
foreign_key: "followed_id"でどのカラムを参照して欲しいのかを定義します。
(foreign_keyは外部キーの意味です)



has_many :relationships, class_name: "Relationship"
の場合、class_nameは不要ですが、見た目統一のためにコードを残しています。


【解説】has_many 2行目と4行目の意味は?(クリック)
2行目のhas_many :followers, through: :reverse_of_relationships, source: :follower
を基準に見ていきます。


1行目で定義したのは、「どのカラムを参照するか」でした。
2行目と4行目では、参照したカラムを元に、関連するuser_idも参照できるように記述します。
1行目で定義したreverse_of_relationshipsモデルをthroughしてfollower(_id)というカラムを参照(source)してね、と記述しています。


これを記述することで、@user.followersという記述がコントローラーで使えるようになります。
回りくどい説明になってしまいますが、2行目の場合は、
followed_idにあたるユーザーをフォローしているユーザー(つまりfollower_idにあたるユーザー)を参照することができます。
これはフォロー・フォロワー一覧ページを作成する時に使います。

3. Userモデルにメソッドを記述

app/models/user.rb(フォロー機能部分のみ抜粋)
class User < ApplicationRecord
  def follow(user_id)
    relationships.create(followed_id: user_id)
  end
  def unfollow(user_id)
    relationships.find_by(followed_id: user_id).destroy
  end
  def following?(user)
    followings.include?(user)
  end
end

4. Relationshipコントローラを作成・編集

まずはrelationshipsコントローラを作成します。

ターミナル
$ rails g controller relationships

作成したコントローラにフォロー機能を作成・保存・削除するアクションと、
フォロー・フォロワー一覧を表示するアクションを記述していきます。

app/controllers/relationships_controller.rb
class RelationshipsController < ApplicationController
# ——————フォロー機能を作成・保存・削除する————————————
  def create
    current_user.follow(params[:user_id])
    redirect_to request.referer
  end

  def destroy
    current_user.unfollow(params[:user_id])
    redirect_to request.referer  
  end
#————————フォロー・フォロワー一覧を表示する-————————————
  def followings
    user = User.find(params[:user_id])
    @users = user.followings
  end

  def followers
    user = User.find(params[:user_id])
    @users = user.followers
end


【解説】フォローを作成・保存・削除する(クリック)
current_user.follow(params[:user_id])
current_user.unfollow(params[:user_id])では、先程userモデルで定義したfollow、unfollowメソッドを使っています。
relationshipsとusersはネストの関係のため、userモデルからメソッドの呼び出しも可能です。


モデルに記述する理由はこちらが参考になりました↓
Railsのモデルに書いたメソッドってどうやってコントローラで使うの?


【解説】フォロー・フォロワー一覧を表示する(クリック)
@users = user.followings
@users = user.followersでは、
先程userモデルで定義したアソシエーションを利用します。
これによりuser = User.find(params[:user_id])で取得したユーザーのidが、
フォローしている もしくは フォローされている ユーザーのid一覧を
取得することができます。

ルーティングも記述します。

config/routes.rb(関連部分のみ)
resources :users, only: [:index, :show, :edit, :update] do
# ——————————————— ここから ———————————————
  resource :relationships, only: [:create, :destroy]
  get 'followings' => 'relationships#followings', as: 'followings'
  get 'followers' => 'relationships#followers', as: 'followers'
# ——————————— ここまでネストさせる ———————————
end

resources :usersの最後にdoを記述するのを忘れないようにします。
relationships は中間テーブルなので、usersモデルにネストさせます。
followingsとfollowersは一覧ページ用に定義したアクションです。

5. viewページを作成(フォローボタン)

view/users/_info.html.erb(フォローボタン部分のみ記述)
<% if current_user.following?(user) %>
  <%= link_to "Unfollow", user_relationships_path(user.id), method: :delete %>
<% else %>
  <%= link_to "Follow", user_relationships_path(user.id), method: :post %>
<% end %>

今回はuserページの部分テンプレート内に埋め込みましたが、
view/relationships/_follow_button.html.erbなど、
ボタン部分だけ記述したviewを作成してもOKだと思います。(そちらの方が見やすいです)


【解説】<% if current_user.following?(user) %>(クリック)
if current_user.following?(user)では、
userモデルで定義したfollowing?メソッドを使っています。
これにより、「今ログインしているユーザーは今参照しているユーザーをフォロしているか?」が条件となります。
trueの場合は、フォローしている状態なので、フォロー解除(unfollow)できるような表示を、
falseの場合は、フォローしていない状態なので、フォローする(follow)できるように表示します。

6. viewページを作成(フォロー・フォロワー一覧)

view/relationships/followers.html.erbもしくはview/relationships/followings.html.erb
<h2>Follower Users</h2>または<h2>Follow Users</h2>
<% if @users.exists? %>
  <thead>
    <tr>
      <th>name</th>
      <th></th>
      <th></th>
    </tr>
  </thead>
  <tbody>
    <% users.each do |user| %>
      <tr>
        <td><%= user.name %></td>
        <td>フォロー数: <%= user.followings.count %></td>
        <td>フォロワー数: <%= user.followers.count %></td>
      </tr>
    <% end %>
  </tbody>
</table>
<% else %>
  <p>ユーザーはいません</p>
<% end %>

フォロー・フォロワー一覧ともにほとんど同じ記述です。
タイトルを<h2>Follower Users</h2>なのか、
<h2>Follow Users</h2>なのか選択します。
<% if @users.exists? %>trueの場合は、ユーザー一覧を表示させます。
すでにユーザー一覧(index)を作成していれば、部分テンプレート化して埋め込みでOKです。
例:<%= render '/users/index', users: @users %>などです。
作成していない場合は、上記のような形で表記できます。
tableタグだけで整えていますが、bootstrapなどを使えばより綺麗に整えられると思います。

これでフォロー・フォロワー機能の実装は完了です!

おわりに

今回は、フォロー・フォロワー機能の実装を、手順と内容についてまとめました。
1度の実装だけでは理解が難しく、何度もパターンを試して、
アウトプットすることでまた少し理解が深まったような気がします。
フォロー・フォロワー機能の実装方法はたくさんあると思うので、
よりスマートで効率の良いコードを書けるように精進していきたいです^^

内容が盛りだくさんのため、抜け漏れがあったら申し訳ありません。
また、理解が乏しい箇所・用語の使い方が間違っている点も多々あるかと思います。コメント欄でご指摘いただければ幸いです。

こちらも参考になりました↓ありがとうございました。
https://qiita.com/mitsumitsu1128/items/e41e2ff37f143db81897

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

Railsポートフォリオで詰まった箇所まとめ(その都度追加)

Railsポートフォリオ作成中、時間を取られた箇所まとめ。精神的に来たもの一覧でもある。ほぼ自分への戒め用。もう少し早く書き始めればよかった・・・

【ページ内リンク】

0.環境
Q1.フォルダ全消しした後、再度同じものを作り直したときに途中でエラーの嵐になる
Q2.テーブルにDateTimeが追加できない!

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

Q1.フォルダ全消しした後、再度同じものを作り直したときに途中でエラーの嵐になる

A.前に作ったデータベース消してなかった。そりゃ同名のDB作ろうとしたらエラー出ますよね。

【解決策】

mysql> drop database 消したいDB名;

・消したいDB名の確認方法はmysql> show database;

ページ内リンクへ戻る

Q2.テーブルにDateTimeが追加できない!

A.諦めてdateクラスとtimeクラスに分けた・・・(後に、DateTimeって大文字DTを使ってたからだと判明。これは酷い。)

【経緯】
・日付と時刻を入れたかったので、それら二つとも代入できるDateTimeクラスって便利じゃん!ということで$ rails g model テーブル名 カラム名:DateTimeをやってみたが$ rails db:migrateでエラー。
・エラー文読んでみたらDateTimeがダメみたい
TimeでもDateでも出来なかった
・色々考えた結果、$ rails g model テーブル名 カラム名:date ~としたら出来た
・普通に$ rails g model テーブル名 カラム名:datetimeとしたら出来るのでは・・・?
・出来た。

【解決策】

$ rails g model テーブル名 カラム名:datetime

ページ内リンクへ戻る

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

interactor gem についてまとめてみた (2/2)

Interactor のまとめ記事第2弾です。

全2部作となっていて、
1. Interactor 概論
2. ActiveInteractor Gem で Interactor を使いやすく(今回)
という構成でお届けしています。
今回は、 activeinteractor の wiki をベースに、 activeinteractor 特有の機能について説明します。

前回のおさらい

  • ビジネスロジックをカプセル化するためのオブジェクトとして Interactor というものがある
  • それぞれの Interactor は、自身が所持する Context を変更する形で外部に影響を及ぼす
  • フックを使って、 Interactor の実行前後に実施する処理をまとめることができる
  • Interactor を使うことで、動作が明快になったり、コントローラの肥大化を防いだりすることができる
  • Interactor には、普通の Interactor と、それを束ねる Organizer という2種類がある
  • Interactor が行う動作がきちんと1つに制限されていれば、テストを書くのは簡単

ActiveInteractor

概要

Interactor Gem を改良した Gem として、今回は activeinteractor1 を紹介します。
基本的な使い方は同じですが、以下の点を中心に書き方に違いが見られます。

  • Interactor は ActiveInteractor::Base を、 Context は ActiveInteractor::Context::Base を継承する
  • .call ではなく .perform
  • バリデーションが使える
  • コールバックやヘルパーメソッドが便利

Interactor と Organizer

Interactor や Organizer はクラスの継承で表現されます。

# interactor gem
class AuthenticateUser
  include Interactor

  def call
    # ...
  end
end

# activeinteractor gem
class AuthenticateUser < ActiveInteractor::Base
  def perform
    # ...
  end
end
# interactor gem
class PlaceOrder
  include Interactor::Organizer
  organize CreateOrder, ChargeCard, SendThankYou
end

# activeinteractor gem
class PlaceOrder < ActiveInteractor::Organizer::Base
  organize :create_order, :charge_card, :send_thank_you
end

organize メソッドはブロックを引数にとることができ、 Interactor を organize するかどうか決める条件やコールバックを指定することができます。

class PlaceOrder < ActiveInteractor::Organizer::Base
  organize do
    add :create_order, if :user_registered?
    add :charge_card, if: -> { context.order }
    add :send_thank_you, if: -> { context.order }
  end

  private

  def user_registered?
    context.user&.registered?
  end
end
class PlaceOrder < ActiveInteractor::Organizer::Base
  organize do
    add :create_order, before: -> { puts context.order }, after: :print_order_id
    add :charge_card
    add :send_thank_you
  end

  private

  def print_order_id
    puts context.order.id
  end
end

一方の Interactor の Context が他方の Interactor の実行に依存しないとき、Organizer で .perform_in_parallel メソッドを使うことで、 Interactor を並行して動作させることができます。

class CreateNewUser < ActiveInteractor::Base
  def perform
    context.user = User.create(
      first_name: context.first_name,
      last_name: context.last_name
    )
  end
end

class LogNewUserCreation < ActiveInteractor::Base
  def perform
    context.log = Log.create(
      event: 'new user created',
      first_name: context.first_name,
      last_name: context.last_name
    )
  end
end

class CreateUser < ActiveInteractor::Organizer::Base
  perform_in_parallel
  organize :create_new_user, :log_new_user_creation
end

CreateUser.perform(first_name: 'Aaron', last_name: 'Allen')
#=> <#CreateUser::Context first_name='Aaron' last_name='Allen' user=>#<User ...> log=<#Log ...>>

Context

Context と Interactor の紐付け

Context は ActiveInteractor::Context::Base を継承します。それぞれの Interactor は、自身に紐づく Context を以下の順番で探索します。

  1. InteractorName::Context
  2. InteractorNameContext

該当するクラスが存在しない場合は、InteractorName::Context < ActiveInteractor::Context::Base が自動的に作成されます。

class MyInteractor < ActiveInteractor::Base; end
class MyInteractor::Context < ActiveInteractor::Context::Base; end

MyInteractor.context_class
#=> MyInteractor::Context
class MyInteractorContext < ActiveInteractor::Context::Base; end
class MyInteractor < ActiveInteractor::Base; end

MyInteractor.context_class
#=> MyInteractorContext
class MyInteractor < ActiveInteractor::Base; end

MyInteractor.context_class
#=> MyInteractor::Context

.contextualize_with メソッドを使って、紐付ける Context を明示的に指定することもできます。

class MyGenericContext < ActiveInteractor::Context::Base; end

class MyInteractor
  contextualize_with :my_generic_context
end

MyInteractor.context_class
#=> MyGenericContext

明示的にプロパティを指定する

それぞれの Context は、 Interactor が動作した後に自身にどんなプロパティが指定されているべきか明示的に定義することができます。.attributes メソッドを使うことで、 Context にどのようなプロパティが割り当てられているか確認することができます。

class MyInteractorContext < ActiveInteractor::Context::Base
  attributes :first_name, :last_name, :email, :user
end

class MyInteractor < ActiveInteractor::Base; end

result = MyInteractor.perform(
  first_name: 'Aaron',
  last_name: 'Allen',
  email: 'hello@aaronmallen.me',
  occupation: 'Software Dude'
)
#=> <#MyInteractor::Context first_name='Aaron' last_name='Allen' email='hello@aaronmallen.me' occupation='Software Dude'>

result.attributes
#=> { first_name: 'Aaron', last_name: 'Allen', email: 'hello@aaronmallen.me' }

result.occupation
#=> 'Software Dude'

バリデーション

ActiveModel::Validations で定義されているバリデーション用のメソッドは、 Context でも使用することができます。 Interactor 内でも context_ という接頭辞をつけることでバリデーションメソッドを使うことができますが、 Context クラスの内部でバリデーションを完結させることが推奨されます。
バリデーションを行うタイミングは、以下の2通りに指定できます。

  • :calling : #perform の前にバリデーションを行う
  • :called : #perform の後にバリデーションを行う
class MyInteractorContext < ActiveInteractor::Context::Base
  attributes :first_name, :last_name, :email, :user

  # #perform の前でのみバリデーションされる
  validates :first_name, presence: true, on: :calling

  # #perform の前後でバリデーションされる
  validates :email, presence: true, format: { with: URI::MailTo::EMAIL_REGEXP }

  # #perform の後でのみバリデーションされる
  validates :user, presence: true, on: :called
  validate :user_is_a_user, on: :called

  private

  def user_is_a_user
    return if user.is_a?(User)

    errors.add(:user, :invalid)
  end
end

class MyInteractor < ActiveInteractor::Base
  def perform
    context.user = User.create_with(
      first_name: context.first_name,
      last_name: context.last_name
    ).find_or_create_by(email: context.email)
  end
end

result = MyInteractor.perform(last_name: 'Allen')
#=> <#MyInteractor::Context last_name='Allen>

result.failure? #=> true
result.valid?   #=> false
result.errors[:first_name] #=> ['can not be blank']

result = MyInterator.perform(first_name: 'Aaron', email: 'hello@aaronmallen.me')
#=> <#MyInteractor::Context first_name='Aaron' email='hello@aaronmallen.me' user=<#User ...>>

result.success?      #=> true
result.valid?        #=> true
result.errors.empty? #=> true

コールバック

バリデーション前後のコールバック

.before_context_validation でバリデーション前に実行されるコールバックを、 .after_context_validation でバリデーション後に実行されるコールバックを指定することができます。

class MyInteractorContext < ActiveInteractor::Context::Base
  attributes :first_name, :last_name, :email
  validates :last_name, presence: true
end

class MyInteractor < ActiveInteractor::Base
  before_context_validation { context.last_name ||= 'Unknown' }
end

result = MyInteractor.perform(first_name: 'Aaron', email: 'hello@aaronmallen.me')
result.valid?
#=> true

result.last_name
#=> 'Unknown'
class MyInteractorContext < ActiveInteractor::Context::Base
  attributes :first_name, :last_name, :email
  validates :email, presence: true, format: { with: URI::MailTo::EMAIL_REGEXP }
end

class MyInteractor < ActiveInteractor::Base
  after_context_validation { context.email&.downcase! }
end

result = MyInteractor.perform(first_name: 'Aaron', last_name: 'Allen', email: 'HELLO@AARONMALLEN.ME')
result.valid? #=> true
result.email  #=> 'hello@aaronmallen.me'

#perform 前後のコールバック

#perform 前後に実行されるコールバックは、 .before_perform .after_perform .around_perform で指定することができます。

class MyInteractor < ActiveInteractor::Base
  before_perform :print_start

  def perform
    puts 'Performing'
  end

  private

  def print_start
    puts 'Start'
  end
end

MyInteractor.perform
# Start
# Performing
#=> <#MyInteractor::Context...>
class MyInteractor < ActiveInteractor::Base
  around_perform :track_time

  def perform
    sleep(1)
  end

  private

  def track_time
    context.start_time = Time.now.utc
    yield
    context.end_time = Time.now.utc
  end
end

result = MyInteractor.perform
result.start_time #=> 2019-01-01 00:00:00 UTC
result.end_time #=> 2019-01-01 00:00:01 UTC
class MyInteractor < ActiveInteractor::Base
  after_perform :print_done

  def perform
    puts 'Performing'
  end

  private

  def print_done
    puts 'Done'
  end
end

MyInteractor.perform
# Performing
# Done
#=> <#MyInteractor::Context...>

ロールバック前後のコールバック

同様に、 .before_rollback .after_rollback .around_rollback で指定することができます。具体例は省略します。

Organizer のコールバック

Organizer が organize するそれぞれの Interactor について、それぞれの実行前後に実行するコールバックを .before_each_perform .after_each_perform .around_each_perform で指定することができます。.before_each_perform のみ具体例を見てみます。

class MyInteractor1 < ActiveInteractor::Base
  def perform
    puts 'MyInteractor1'
  end
end

class MyInteractor2 < ActiveInteractor::Base
  def perform
    puts 'MyInteractor2'
  end
end

class MyOrganizer < ActiveInteractor::Organizer::Base
  before_each_perform :print_start

  organized MyInteractor1, MyInteractor2

  private

  def print_start
    puts "Start"
  end
end

MyOrganizer.perform
# Start
# MyInteractor1
# Start
# MyInteractor2
#=> <MyOrganizer::Context...>

Rails で便利なコマンド

rails g

rails g を使ってファイルを作成することができます。

# ActiveInteractor の導入
rails generate active_interactor:install

# 各種ファイルの生成
rails generate interactor NAME [context_attributes] [options]
rails generate interactor:organizer NAME [interactor interactor] [options]
rails generate interactor:context NAME [attribute attribute] [options]

ジェネレーターの設定を以下のように行うことができます。

# config/application.rb
module MyApplication
  class Application < Rails::Application
    config.generators.interactors :active_interactor,
      dir: 'my_directory', # ファイルを生成するディレクトリを指定
      generate_context: false # Context のクラスを自動生成するかどうかを指定
    end
  end
end

ActiveRecord のモデルを Context として使用する

ActiveRecord モデルを Context として使用するには、 モデル内で acts_as_context を使用した後、 Interactor で contextualize_with メソッドを使用します。

# app/models/user
class User < ApplicationRecord
  acts_as_context
end

# app/interactors/create_user
class CreateUser < ApplicationInteractor
  contextualize_with :user

  def perform
    context.email&.downcase!
    context.save
  end
end

CreateUser.perform(email: 'HELLO@AARONMALLEN.ME')
#=> <#User id=1 email='hello@aaronmallen.me'>

  1. Copyright (c) 2019 Aaron Allen, Released under the MIT license: https://github.com/aaronmallen/activeinteractor/blob/main/LICENSE 

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

RSpecにて引数が必要なsubjectの使い方

はじめに

RSpecで、同じメソッドに対してのテストを複数回書くシーンでは、subjectを使うことでDRYなコードを書くことができます。
ここでは引数が必要なメソッドのテストをsubjectを用いて書きます。

実装

ユーザー名(user_name)、自己紹介(pr)カラムがあるユーザーを、フリーワードで検索するuser_search(free_word)メソッドをテストするとします。普通に書くと以下のように繰り返し同じ処理が出てきます。

spec/models/user_spec.rb
describe "#user_search" do
  context "when 'user_name1' is given" do
    it "return 1 result" do
      expect(Job.user_search("user_name1").count).to eq 1
    end
  end

  context "when 'pr1' is given" do
    it "return 1 result" do
      expect(Job.user_search("pr1").count).to eq 1
    end
  end
end

これは以下のようにsubjectでまとめて処理を書くことができます。

spec/models/user_spec.rb
describe "#user_search" do
  subject { User.user_search(field).count }

  context "when user_name1 is given" do
    let(:field) { "user_name1" }
    is_expected.to eq 1
  end

  context "when pr1 is given" do
    let(:field) { "pr1" }
    is_expected.to eq 1
  end
end

引数はletで定義することができます。

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

tiktokでogp相当の情報をAPIで取得する(ruby)

tiktokのスクレイピングをしてogp情報をとってくる処理を書いていたのだがいつのまにか下記のようにstatus code1000で弾かれるようになってしまっていた。

[25] pry(main)> html = open(movie_url) do |f| f.read; end
=> "{\"statusCode\":10000,\"verifyConfig\":{\"code\":10000,\"type\":\"verify\",\"subtype\":\"slide\",\"fp\":\"verify_5f101e29a0ad54278347a24437cfcb93\",\"region\":\"sg\",\"detail\":\"2SdJGObfW85-lX4dEGohkmh8YbPmZYiXwIh1pqrEUuohsJqr4t3XjgXHtkBHWheNJbj4MB*CVwF1ayKFmhUFcJY2yAgBxyatF0XXzv0*xk*DmEz7Og6oE0nS1mFuZXdkJE*Z05uOnCgTkh4d4yy7xfv6EiiQJGBxdHDxp21vXCZjphcNMaY6YoCrq-5wRulWVle0lP3RJyqJQUFbtIP8pH0LICH8SRGn9a6mItvTEiEwiHCfJHBxhTUf1kFCYkGm1gBpVE1s45qbaNPeVELCCU0732nlU1VLR7Jm16yTIne64NABwJSaXFAl19NrjsfHrV-Cxq*d4kuVUBERzDIIncNGgQ3m6MLdwGLw*H4xfl94O1LId2V5zlOltVL1gYtral9VUYNMskUBWclUpWt2zrUfpbVzuUg.\"}}"

どうしようかなと思ったが、調べてみたらTikTokのAPIでogp相当の情報をとってこれるようだったので、apiを通して取得してくるように修正した。ドキュメントもあるので別に何も大変なことはない。

https://www.tiktok.com/oembed?url=https://www.tiktok.com/@scout2015/video/6718335390845095173

このような形で情報を取得したいtiktokのURLをurlパラメータに渡してhttps://www.tiktok.com/oembed を叩くだけでいい。そうすると下記のようなレスポンスが返ってくる。

{
  "version": "1.0",
  "type": "video",
  "title": "Scramble up ur name & I’ll try to guess it?❤️ #foryoupage #petsoftiktok #aesthetic",
  "author_url": "https://www.tiktok.com/@scout2015",
  "author_name": "Scout & Suki",
  "width": "100%",
  "height": "100%",
  "html": "<blockquote class=\"tiktok-embed\" cite=\"https://www.tiktok.com/@scout2015/video/6718335390845095173\" data-video-id=\"6718335390845095173\" style=\"max-width: 605px;min-width: 325px;\" > <section> <a target=\"_blank\" title=\"@scout2015\" href=\"https://www.tiktok.com/@scout2015\">@scout2015</a> <p>Scramble up ur name & I’ll try to guess it?❤️ <a title=\"foryoupage\" target=\"_blank\" href=\"https://www.tiktok.com/tag/foryoupage\">#foryoupage</a> <a title=\"petsoftiktok\" target=\"_blank\" href=\"https://www.tiktok.com/tag/petsoftiktok\">#petsoftiktok</a> <a title=\"aesthetic\" target=\"_blank\" href=\"https://www.tiktok.com/tag/aesthetic\">#aesthetic</a></p> <a target=\"_blank\" title=\"♬ original sound - ???????\" href=\"https://www.tiktok.com/music/original-sound-6689804660171082501\">♬ original sound - ???????</a> </section> </blockquote> <script async src=\"https://www.tiktok.com/embed.js\"></script>",
  "thumbnail_width": 720,
  "thumbnail_height": 1280,
  "thumbnail_url": "https://p16.muscdn.com/obj/tos-maliva-p-0068/06kv6rfcesljdjr45ukb0000d844090v0200010605",
  "provider_url": "https://www.tiktok.com",
  "provider_name": "TikTok"
}

コード(ruby)

uri = URI.parse("https://www.tiktok.com/oembed?url=#{params[:tiktok_url]}")
response = Net::HTTP.get_response(uri)
res_json = JSON.parse(response.body)
html_doc = res_json["html"]
title = res_json["title"]
ogp_image = res_json["thumbnail_url"]
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Ruby2.3.4+Rails5.0.4からRuby3.0.0+Rails6.1.3にバージョンアップした話

はじめに

Heroku-16 Stackで動いているアプリケーションがあり、こちらが2021/6にEOLを迎える。
それに伴いHeroku Stackを上げるとRubyのバージョンも上げないといけなかった。
せっかくだったらこの機会に最新にしようと思い、バージョンを上げたのでその時の注意事項をメモ書き程度に残しておく。

Gemfile

まず元々のGemfile

Gemfile
# 一部抜粋
gem 'rails', github: 'rails/rails', branch: "5-0-stable"
gem 'mysql2', '>= 0.3.18', '< 0.5'
gem 'puma', '~> 3.0'
gem 'rspec-rails', '~> 3.5'
gem 'factory_girl_rails'

Rubyのバージョン指定はしておらず、Herokuのデフォルトのバージョンになっていた。
今回はRubyとRailsのバージョンを上げたいので以下を設定してみた。

Gemfile
ruby "3.0.0"
gem 'rails', '~> 6.1', '>= 6.1.3'

これで環境を作り直そうとした結果以下のエラー

Bundler could not find compatible versions for gem "bundler":
  In Gemfile:
    bundler-audit was resolved to 0.6.0, which depends on
      bundler (~> 1.2)
    license_finder was resolved to 3.0.2, which depends on
      bundler
    rails (~> 6.1, >= 6.1.3) was resolved to 6.1.3, which depends on
      bundler (>= 1.15.0)
  Current Bundler version:
    bundler (2.2.3)
This Gemfile requires a different version of Bundler.
Perhaps you need to update Bundler by running `gem install bundler`?
Could not find gem 'bundler (~> 1.2)', which is required by gem 'bundler-audit',
in any of the sources.

bundler関連の問題でうまく環境が作れないようだった。
どうやらbundlerが1.15.3で作ろうとしているのが問題のようだった。
Gemfile.lockを消した状態でbundlerのバージョンを上げてbundle installした。
bundlerは2.2.11になった。

Gemfile.lock
# 一部抜粋
+ RUBY VERSION
+   ruby 3.0.0p0
BUNDLED WITH
-   1.15.3
+   2.2.11

rexml

gem installすると以下のエラー

LoadError - cannot load such file -- rexml/document

こちらの記事を参考にしました。

Gemfile
gem 'rexml'

factory_girl

DEPRECATION WARNING: The factory_girl gem is deprecated. Please upgrade to factory_bot. See https://github.com/thoughtbot/factory_bot/blob/v4.9.0/UPGRADE_FROM_FACTORY_GIRL.md for further instructions. (called from <top (required)> at /usr/src/app/config/application.rb:17)
porter-web-dev | /usr/local/bundle/gems/activesupport-6.1.3/lib/active_support/dependencies.rb:332:in `require': cannot load such file -- rexml/document (LoadError)

factory_girlは非推奨なのでfactory_botにバージョンアップする。

Gemfile
-  gem 'factory_girl_rails'
+  gem 'factory_bot'

rspecファイル内にてFactoryGirlをFactoryBotに置換。
更新が用意してくれている置換コマンドで割といい感じに置換できた。

mysql2

手元の環境はDockerで作成しているがmysql-clientがないと言われたのでdefault-mysql-clientに変更

Package mysql-client is not available, but is referred to by another package.
This may mean that the package is missing, has been obsoleted, or
is only available from another source
Dockerfile
- RUN apt-get install -y mysql-client
+ RUN apt-get install -y default-mysql-client

環境を立ち上げてみたがDBアクセスで以下エラー

Puma caught this error: Error loading the 'mysql2' Active Record adapter. Missing a gem it depends on? can't activate mysql2 (~> 0.5), already activated mysql2-0.4.10. Make sure all dependencies are added to Gemfile. (LoadError)

mysql2のバージョンも上げる。

Gemfile
- gem 'mysql2', '>= 0.3.18', '< 0.5'
+ gem 'mysql2', '~> 0.5.3'

puma

ローカル環境が立ち上がらない。
こちらの記事を参考にしました。

config/initializers/new_framework_defaults.rb
- ActiveSupport.halt_callback_chains_on_return_false = false
+ #ActiveSupport.halt_callback_chains_on_return_false = false

ローカル環境を立ち上げると証明書関連のエラー
こちらの記事を参考にpumaのバージョンも上げる。

Gemfile
- gem 'puma', '~> 3.0'
+ gem 'puma', '~> 5.2', '>= 5.2.1'

Rspec

ローカル環境も立ち上がりある程度動くようになったのでテストを通してみたところ全部失敗した。

Failure/Error:
         raise WrongScopeError,
               "`#{name}` is not available from within an example (e.g. an " \
               "`it` block) or from constructs that run in the scope of an " \
               "example (e.g. `before`, `let`, etc). It is only available " \
               "on an example group (e.g. a `describe` or `context` block)."
         `name` is not available from within an example (e.g. an `it` block) or from constructs that run in the scope of an example (e.g. `before`, `let`, etc). It is only available on an example group (e.g. a `describe` or `context` block).

こちらの記事を参考にしました。

spec/factories/hoge.rb
- FactoryGirl.define do
+ FactoryBot.define do
  factory :hoge do
-    name 'テスト'
+    name {'テスト'}
  end
end
spec/spec_helper.rb
  config.before(:all) do
    FactoryBot.reload
  end

最終的に

以下のようなGemfileになった。

Gemfile
# 一部抜粋
ruby "3.0.0"
gem 'rails', '~> 6.1', '>= 6.1.3'
gem 'mysql2', '~> 0.5.3'
gem 'puma', '~> 5.2', '>= 5.2.1'
gem 'rexml'
gem 'rspec-rails', '~> 4.0', '>= 4.0.2'
gem 'factory_bot'

各バージョンの後方互換を調べる

ローカル環境が起動しひと通りアプリケーションが動くことを確認。
RSpecが全て通ることを確認。
この時点で8割方バージョンアップ完了だったが、念の為各バージョンで後方互換切られているところを中心に調べていくことにした。
Ruby2.3から3.0という記事はなかったので1バージョンずつ調べていった。

ruby2.4
特に問題なさそう。

ruby2.5
後方互換の話は特になし。

ruby2.6
範囲オブジェクトに影響あり。
git grep -i range
git grep "\.\."
あたりで範囲オブジェクトを使っている箇所を調べていった。

ruby2.7
こちらも範囲オブジェクト関連。

ruby3.0.0
1つずつ見てgrepしてみたが影響のありそうなものはそもそも使っていなかったので問題ないと判断した。

rails6.1.3
https://qiita.com/ryohashimoto/items/622c3bcfb3336cb9317e
https://railsguides.jp/upgrading_ruby_on_rails.html
cookiesの話が気になるがrails4との比較をしているのでrails5からのアップデートでは問題なしと判断した。

Heroku

以上の調査と修正によりバージョンアップ問題なしと判断し本番をHeroku-20 Stackに上げてデプロイ。
しかし本番でエラーが発生した。

ActiveRecord::ConnectionNotEstablished: SSL connection error: error:1425F102:SSL routines:ssl_choose_client_version:unsupported protocol

こちらの記事と同様の事象のようだった。
なんとか解消したかったが原因不明のため、記事と同様にHeroku-18 Stackにしたら問題なく動いた。

まとめ

今回所要時間15時間程度でバージョンアップすることができた。
ここまで早くできたのは以下の要因によるものと思われる。

  • 元のバージョンがそこまで低くなかった。
  • テストが書いてあった。
  • 該当アプリケーションがシンプルな作りで機能がそこまで多くなく、検証すべき項目が少なかった。

せっかくバージョンを上げたので新バージョンで使えるようになった機能とかを使っていきたい。
まだ全然調べられていないがRails6からは標準でバルクインサートできるようになったらしい。
activerecord-importとかgemを後入れしなくても良くなったのは嬉しい。
時間ができたら触ってみることにする。

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

Rails いいね機能の非同期通信化(Ajax)

はじめに

こんにちは!閲覧ありがとうございます。

今回は【いいね機能の非同期通信化(Ajax)】を学習した為、
復習の意味も込めてアウトプットとしてまとめていこうと思います。

未来の僕が見返した時に、
また、プログラミング初学者が見てくれた時に、
有益な情報であるよう僕なりに書いていきます。

※誤字、脱字、知識の誤りなど見つけた方はご教授いただけると幸いです。

目次

0:はじめに
1:開発環境
2:前提条件
3:処理の流れ
4:実装
5:さいごに
  

開発環境

・ruby: 2.6.3
・rails: 5.2.4.5
・OS: macOS Catalina ver10.15.7
・Cloud9

前提条件

下記3つはすでに実装済で話を進めます。
1:User(ユーザー)
2:book(投稿)
3:favorite(いいね)
※本を投稿するWebアプリを例にしている為、bookモデルを作成しています。
(postで作っている方が多いかも)

・device導入

※ここではCRUD機能基礎やアソシエーション、いいね機能そのものについての説明は割愛します。

実装の流れ

Ajaxの概要についてはこちらの記事がとても参考になりました。
初心者目線でAjaxの説明


簡単に言い換えるなら、
webブラウザからのリクエストに対し、JavaScriptを使って、
画面を遷移せずに(リロードすることなく)指定されたリクエストを行う。




ユーザーがいいねボタンを押す

(ページは変わらずに)
画面のいいねボタンのところだけ変更される(いいねが増える)


非同期通信 処理の流れ

実際の処理の流れは下記のようになっています。

1:ユーザーがいいねをクリックする(送信する)
  (Webブラウザ)
     ↓
2:ルーティングでどのコントローラのどのアクションを呼び出すかを定める
  (route.rb)
     ↓
3:アクションを実行する[いいねの保存や削除]
  (favorites.controller.rb)
     ↓
4:コントローラ名/アクション名.js.erbファイル内の処理を実行する[部分的にページを更新]
  (views/favorites/create.js.erb,
   views/favorites/destroy.js.erb)
     ↓
5:レスポンスを返す
  (Webブラウザ)


実装

ここからは順を追って実装方法をまとめていきます。

■jQueryの読み込み

下記、該当箇所に追記

Gemfile.
gem 'jquery-rails'

bundle install 忘れずに!

app/assets/javascripts/application.js
//= require jquery
//= require rails-ujs


■いいねボタンにAjaxの処理を適用させる

books/index.html.erb
<% @books.each do |book| %>

 <td>
  <%= link_to book.title, book_path(book.id) %>
 </td>
 <td>
  <%= book.body %>
 </td>


 <td>
  <% if book.favorited_by?(current_user) %>
   <%= link_to book_favorites_path(book), method: :delete, remote: true do %>  #remote: trueを追加
     ♥<%= book.favorites.count %>いいね
    <% end %>
  <% else %>
    <%= link_to book_favorites_path(book), method: :post, remote: true do %>  #remote: trueを追加
     ♡<%= book.favorites.count %>いいね
    <% end %>
  <% end %>
 </td>
<% end %>



link_to内にremote; true を追加しています。
これにより、HTMLリクエストではなく、JavaScriptのリクエストがfavoritesコントローラに送られます。

HTMLリクエストの場合
→データベースにいいねを保存or削除し、画面にリダイレクトする(ページを読み込む)
JavaScriptリクエストの場合
→データベースにいいねを保存or削除し、JavaScriptを使っていいね部分のみ更新する(ページ読み込みなし)

■いいね保存、削除のリダイレクト先を消す。

favorites.controller.rb
class FavoritesController < ApplicationController

  before_action :authenticate_user!

  def create
    @book = Book.find(params[:book_id])
    favorite = current_user.favorites.new(book_id: @book.id)
    favorite.save
    redirect_back(fallback_location: root_path)  #ここを削除!
  end

  def destroy
    @book = Book.find(params[:book_id])
    favorite = current_user.favorites.find_by(book_id: @book.id)
    favorite.destroy
    redirect_back(fallback_location: root_path)  #ここを削除!
  end
end

リダイレクト先を削除したことにより、
リダイレクト先がない、かつJavaScriptリクエストという状況になり、

createアクション実行後は、create.js.erbファイルを、
destroyアクション実行後はdestroy.js.erbファイルを探すようになります。

■更新したい箇所を部分テンプレート化する

/favorites/_favorites.html.erb
<% if book.favorited_by?(current_user) %>
  <%= link_to book_favorites_path(book), method: :delete, remote: true do %><%= book.favorites.count %>いいね
  <% end %>
<% else %>
  <%= link_to book_favorites_path(book), method: :post, remote: true do %><%= book.favorites.count %>いいね
  <% end %>
<% end %>

少しややこしいですが、「2:いいねボタンにAjaxの処理を適用させる」のコードから、
更新したい部分だけを切り取って部分テンプレートにしています。(いいねボタンといいね数)

■部分テンプレートを読み込みます。

book/index/html.erb
<% @books.each do |book| %>
 <tr>
  <td>
   #本のタイトル、本詳細ページ(show)へのリンク
   <%= link_to book.title, book_path(book.id) %>
  </td>
  <td>
   #本の内容
   <%= book.body %>
  </td>
  #部分テンプレートにした箇所(部分的に更新したい箇所)
  <td id="favorite_buttons_<%= book.id %>">
   <%= render "favorites/favorite", book: book %>
  </td>
 </tr>
<% end %>



point1
  eachメソッド内でのrender処理なので、
  インスタンス変数は存在しません。bookをbookに渡しています。(book: book)

point2
  この箇所の更新を指定するために、変更したい箇所にidで名前をつけます。
  id="favorite_buttons_<%= book.id %>"

■js.erbファイルの編集

favorites/create.js.erb
$('#favorite_buttons_<%= @book.id %>').html("<%= j(render "favorites/favorite", book: @book) %>");
favorites/destroy.js.erb
$('#favorite_buttons_<%= @book.id %>').html("<%= j(render "favorites/favorite", book: @book) %>");

指定したセレクタ(idの部分)のみHTMLをrenderして部分的に更新します。

これで完成です。

さいごに

実装の流れを簡単にまとめるとこのようになります。

1:link_to文remote: trueを追加
2:create,destroyアクションのリダイレクト先を削除
3:部分テンプレートの作成
4:部分テンプレートの読み込み
5:js.erbファイルにて部分テンプレート箇所のみ更新処理


個人的な感想
 render内で読み込む部分テンプレートにどのような変数を渡すのかを間違えないように意識することが大事だと思いました。
 今までクラス名やid名の指定で-(ハイフン)を使っていましたが、_(アンダーバー)と見間違えて、js.erbファイル内でのセレクタの指定がうまくできず、半日無駄にしてしまいました。今後、-(ハイフン)の使用は禁止しようと思います。


以上です。
拙い知識でわかりづらい文章だと思いますが、最後まで閲覧ありがとうございました。
少しでも誰かの役に立っていたら幸いです。

この記事について誤っている部分や改善点等あればコメントしていだけると助かります。

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

Rails newまでまとめてみた

アウトプット兼ねてまとめてみました。
*アドバイス訂正などありましたらご指摘お願いいたします。

ステップ1

mkdir xxx

でフォルダー作ってそこに移動

ステップ2

gem install rails 
↓
rbenv exec gem install bundler

rbenv execはrbenvでインストールしたrubyを使ってbindlerを入れるらしい。

ステップ3

rails new プロジェクト名 -d mysql

作成したプロジェクトに移動

bundle install --path vendor/bundle
二回目以降はbundle installのみでOK

ステップ4

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

環境構築 OSがMojave以前の場合

1. Command Line Toolsの導入

1-1. Command Line Toolsをインストール

ターミナルに入力

$ xcode-select --install

「インストール」をクリック。
「同意する」をクリック。

2. Homebrewの導入

2-1. Homebrewをインストール

コマンドを順番に1つずつ実行。

$ cd  #ホームディレクトリに移動
$ pwd #ホームディレクトリにいるかどうか確認
$ /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install.sh)" # コマンドを実行

PCのパスワードを入力する。
「Press RETURN to continue or other key to abort」
が表示されたら、エンターキーをおす。

2-2. Homebrewがインストールされているか確認

上のコマンドだけ実行する。
バージョン情報が表示されれば無事にインストールされている。

$ brew -v
Homebrew 2.5.1 # 数値は異なる場合があります

2-3. Homebrewをアップデート

$ brew update

2-4. Homebrewの権限を変更

$ sudo chown -R `whoami`:admin /usr/local/bin

再度パスワードを求められた場合は、先ほどと同じように入力。

3. Rubyをインストール

Webアプリケーションの開発においては専用のRubyをインストールする必要があります。

3-1. rbenv と ruby-buildをインストール

$ brew install rbenv ruby-build

3-2. rbenvをどこからも使用できるようにする

$ echo 'eval "$(rbenv init -)"' >> ~/.bash_profile

3-3. bash_profileの変更を反映

$ source ~/.bash_profile

3-4. readlineをinstall

ターミナルのirb上で日本語入力を可能にする設定

$ brew install readline

3-5. readlineをどこからも使用できるようにする

$ brew link readline --force

3-6. rbenvを利用してRubyをインストール

$ RUBY_CONFIGURE_OPTS="--with-readline-dir=$(brew --prefix readline)"
$ rbenv install 2.6.

2.6.5と書いてあるのは今回インストールするRubyのバージョンです。

3-7. 利用するRubyのバージョンを指定

$ rbenv global 2.6.5

デフォルトでPCに入っていたRubyから、先ほどインストールしたRubyを使用するように切り替えることができました。

3-8. rbenvを読み込んで変更を反映

$ rbenv rehash

3-9. Rubyのバージョンを確認

以下のコマンドで最終確認。

$ ruby -v

4. MySQLを用意

4-1. MySQLのインストール

$ brew install mysql@5.6

4-2. MySQLの自動起動設定

MySQLは本来であればPC再起動のたびに起動し直す必要がありますが、それは面倒であるため、自動で起動するようにしておく。

$ mkdir ~/Library/LaunchAgents
$ ln -sfv /usr/local/opt/mysql\@5.6/*.plist ~/Library/LaunchAgents
$ launchctl load ~/Library/LaunchAgents/homebrew.mxcl.mysql\@5.6.plist

4-3.mysqlコマンドをどこからでも実行できるようにする

# mysqlのコマンドを実行できるようにする
$ echo 'export PATH="/usr/local/opt/mysql@5.6/bin:$PATH"' >> ~/.bash_profile
$ source ~/.bash_profile
# mysqlのコマンドが打てるか確認する
$ which mysql
# 以下のように表示されれば成功
/usr/local/opt/mysql@5.6/bin/mysql

4-4. mysqlを起動を確認

# mysqlの状態を確認するコマンドです
$ mysql.server status

# 以下のように表示されれば成功
 SUCCESS! MySQL running

5. Railsの導入

5-1. bundlerをインストール

Rubyの拡張機能(gem)を管理するためのbundler(バンドラー)をインストールする。

$ gem install bundler --version='2.1.4'

5-2. Railsをインストール

$ gem install rails --version='6.0.0'

5-3. rbenvを再読み込み

$ rbenv rehash

5-4. Railsが導入できたか確認

% rails -v
Rails 6.0.0   #「Rails」のあとに続く数字は変わる可能性があります

6. Node.jsの導入

6-1.Node.jsのインストール

$ brew install node@14

Node.jsへのパスを設定

$ echo 'export PATH="/usr/local/opt/node@14/bin:$PATH"' >> ~/.bash_profile
$ source ~/.bash_profile

6-2. Node.jsが導入できたか確認

$ node -v
v14.15.3 # 数値は異なる場合があります

7. yarnの導入

7-1. yarnをインストール

$ brew install yarn

7-2. yarnが導入できたか確認

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

[Rails]Rspecでログインユーザーを作成する方法

はじめに

  • 自分が初めてRspec(リクエストスペック)を実装した時につまづいた。同じ人のために、記事録として作成しています。

課題

  • 200を期待しているが、302が返る
it "リクエストが成功する" do
  subject
  expect(response).to have_http_status(:ok) #=> found(302)
  #=>ok(200)を期待しているが、 found(302)が返る
end

原因

  • ログインユーザーではない場合、リダイレクトするbefore_actionを設定している

解決策

  • ユーザーをログインさせる

実装

①設定
以下のファイルにコードを追加する

rails_helper.rb
RSpec.configure do |config|  
  #以下を追加
  # リクエストスペックで Devise のテストヘルパーを使用できるようにする
  config.include Devise::Test::ControllerHelpers, type: :controller
  config.include Devise::Test::IntegrationHelpers, type: :request
  # 以上を追加
end

②ユーザーをログインさせる
1.before do ~ endでログインユーザーを作成(テストデータはusers.rbで作成)
2.sign_in @userでユーザーをログインさせる

xxx_request_spec.rb
RSpec.describe "xxx", type: :request do
  # ログインさせるユーザーを作成
 + before do
 +   @user = create(:user)
 + end

  # 略

  it "リクエストが成功する" do
    # 先ほど作成したユーザーをログインさせる
  + sign_in @user
    subject
    expect(response).to have_http_status(:ok)
  end
end

before

it "リクエストが成功する" do
  subject
  expect(response).to have_http_status(:ok) #=> 302
  #=>ok(200)を期待しているが、 found(302)が返る
end

after

it "リクエストが成功する" do
+ sign_in @user
  subject
  expect(response).to have_http_status(:ok) #=> 200
  #=>ログインしている為、before_actionにかからなくなり、ok(200)が返る
end

今回のまとめ

  • テストを実行するとリダイレクトされてしまう(200 => 302)
  • 原因はbefore_actionで未ログインユーザーはリダイレクトするようになっている為。 →ユーザーをログインさせる必要がある
  • ユーザーをログインさせるには、sign_inを使用できるようにする必要がある
  • sign_inを使用するには、rails_helper.rbに設定を記述する必要がある
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Rails】slickを使ってスライド形式で画像を表示しよう!

slickを利用して複数の画像をスライド形式で表示できる機能を実装します。
今回もレシピアプリを例に作成していきます。

完成イメージ

ビュー.gif

※今回は画像投稿機能が実装されている前提で話を進めていきます。
画像投稿機能については前回記事にしておりますのでそちらを参照してください。

slickとは

slickとはjQueryベースの、スライダーを作成するためのプラグインです。

slickの導入

まずはslickの公式サイトにアクセスし、「get it now」を選択します。
ebd52a29b1c18acc6c10543898b1b455.png

するとページ下部に遷移するので、そこに記載されているCDNのコードをコピーします。
81335cceee2857e8a79481215da93641.png

コピーしたコードをapplication.html.erbに貼り付けます。

app/views/layouts/application.html.erb
<!DOCTYPE html>
<html>
  <head>
    <title>Recipe</title>
    <%= csrf_meta_tags %>
    <%= csp_meta_tag %>

    <%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %>
    <%= stylesheet_pack_tag 'application', 'data-turbolinks-track': 'reload' %>

    <link rel="stylesheet" type="text/css" href="//cdn.jsdelivr.net/npm/slick-carousel@1.8.1/slick/slick.css"/>  #追加

    <%= javascript_pack_tag 'application', 'data-turbolinks-track': 'reload' %>

    <script type="text/javascript" src="//cdn.jsdelivr.net/npm/slick-carousel@1.8.1/slick/slick.min.js"></script>  #追加
  </head>
#以下略

jQueryの導入

Bootstrapを導入している方は、すでにjQueryが導入されているので飛ばしてください。
Bootstrap導入についてはこちら

それではjQueryを導入していきましょう。

ターミナル
yarn add jquery

webpackの設定をします。

config/webpack/environment.js
const { environment } = require('@rails/webpacker')

//ここから
const webpack = require('webpack')
environment.plugins.append(
  'Provide',
  new webpack.ProvidePlugin({
    $: 'jquery/src/jquery',
    jQuery: 'jquery/src/jquery'
  })
)
//ここまでを追加

module.exports = environment

jQueryを読み込ませます。

app/javascript/packs/application.js
//中略

require("@rails/ujs").start()
//require("turbolinks").start()
require("@rails/activestorage").start()
require("channels")
require("./preview")
require('jquery')  //追記

//以下略

以上でslick、jQueryの導入完了です。

スライドの実装

ビューファイルの編集

まずは、表示させたい画像をビューに表示させます。

app/views/recipes/show.html.erb
<div class="text-center">
  <div class="card recipe-card ">

  #画像の表示
    <% @recipe.images.each do |image| %>
       <%= image_tag image, class: "card-img-top show-img" %>
    <% end %>

    <div class="card-body">
      <div class="recipe-name">
        <%= @recipe.title %>
      </div>
      <div class="recipe-content">
        カテゴリー: <span class="recipe-category"><%= @recipe.category.name %></span>
        所要時間: <span class="recipe-time"><%= @recipe.time_required.name %></span>
      </div>
      <hr>
      <p class="card-text">
        <div class="recipe-title">作り方</div>
        <div class="recipe-text d-flex justify-content-start">
          <%= safe_join(@recipe.text.split("\n"),tag(:br)) %>
        </div>
      </p>
    </div>
  </div>
</div>

このままだと、以下のようにただ画像を3枚縦に並べただけになります。
db432536e85ee910b2a4192c410ab35c.jpg

slick.jsの作成

slickを使用するためのJSファイルを作成します。

touch app/javascript/packs/slick.js

作成したslick.jsに以下のコードを貼り付けます。

app/javascript/packs/slick.js
$(function() {
  $('.slider').slick();
});

slick.jsを読み込ませます。

app/javascript/packs/application.js
//中略

require("@rails/ujs").start()
//require("turbolinks").start()
require("@rails/activestorage").start()
require("channels")
require("./preview")
require('jquery')  
require("./slick") //追記

//以下略

そして、ビューファイルを以下のようにsliderのdiv要素でimage_tagを囲うよう再編集します。

app/views/recipes/show.html.erb
<div class="text-center">
  <div class="card recipe-card ">

    #sliderで囲う
    <div class="slider">
        <% @recipe.images.each do |image| %>
          <%= image_tag image, class: "card-img-top show-img" %>
        <% end %>
    </div>

    <div class="card-body">
      <div class="recipe-name">
        <%= @recipe.title %>
      </div>

#以下略

すると以下のようにスライドが実装できます。
cdfa40caa09410f46978b6da9961f222.gif

slick.jsの編集

このままでは、上下のボタンが不格好なので表示させないようslick.jsを編集します。
ボタンの代わりに自動でスライドを再生させる記述をします。

app/javascript/packs/slick.js
$(function() {
  $('.slider').slick({
      arrows: false,  //ボタン非表示
      autoplay: true, //自動再生
      autoplaySpeed: 4000, //再生スピード
  });
});

以上で完成です。
ビュー.gif

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

AWSへデプロイ後にユーザー情報が更新できない現象への対処法

AWSへデプロイ後に、user.saveでユーザー情報が保存できない現象についての対処法

とあるプログラミング学習サイトを利用してRailsを学び、作成したアプリケーションをサーバーへデプロイしたところまでは良かったのですが、その後作成したユーザーのアイコンを変更できない現象に遭遇しました。手探りでの対処で苦戦しましたので、同じことを繰り返さないためにも投稿に残したいと思います。

現象が発生した元のコード

app/controllers/users_controller.rb
  def update
    @user = User.find_by(id: params[:id])
    @user.name = params[:name]
    @user.user_id = params[:user_id]
    @user.email = params[:email]
    if params[:image]
      @user.image_name = "#{@user.id}.jpg"
      image = params[:image]
      File.binwrite("public/user_images/#{@user.image_name}", image.read)
    end
    if params[:password] != nil
      @user.password = params[:password]
    end
    if @user.save
      flash[:notice] = "ユーザー情報を編集しました"
      redirect_to("/users/#{@user.user_id}")
    else
      render("users/#{@user.user_id}/edit")
    end
  end

このコードでローカルではうまくいっていたのですが、サーバーへデプロイ後はユーザー画像のみ保存されて、MySQLのデータは更新されませんでした。

試したこと

エラーが吐き出されていないか確認しましたが、log/production.logにはエラーらしきものはなく、mysql.logを確認しようとしましたがどこにあるのかわからず、できませんでした。

このためはじめはrails consoleで状況を確認しようとしましたが、RDSのデータベースには接続されていないのか、users = User.allでユーザー情報を取得しようとしても、users.count = 0の状態でした。

そこで直接MySQLへログインして、テーブルのデータをSQLで更新してみたところ、こちらはできましたので、MySQLを使用して更新する方法を取ることにしました。

現象が改善した後のコード

app/controllers/users_controller.rb
  def update
    id = @current_user.id
    updated = 0
    update_name_sql = "update users set name = '#{params[:name]}' where id =#{id};"
    updated = ActiveRecord::Base.connection.execute(update_name_sql)
    update_email_sql = "update users set email = '#{params[:email]}' where id =#{id};"
    updated = ActiveRecord::Base.connection.execute(update_email_sql)
    if params[:image]
      update_image_name_sql = "update users set image_name = '#{id}.jpg' where id =#{id};"
      updated = ActiveRecord::Base.connection.execute(update_image_name_sql)
      image = params[:image]
      File.binwrite("public/user_images/#{id}.jpg", image.read)
    end
    if params[:password]
hashed_password = BCrypt::Password.create(params[:password])
      update_password_sql = "update users set encrypted_password = '#{hashed_password}' where id =#{@current_user.id};"
      ActiveRecord::Base.connection.execute(update_password_sql)
    end
    if updated =! nil
      flash[:notice] = "ユーザー情報を編集しました"
      redirect_to("/users/#{@user.id}")
    else
      flash[:notice] = "データベースへの保存に失敗しました"
      render("users/edit")
    end
  end

SQLを直に作成して、ActiveRecord::Base.connection.execute(SQL)でMySQLのデータベースを直接更新する方法へ変えました。
(パスワードに関してもトラブルがありましたので、直接暗号化してから保存するコードに書き換えました)

まとめ

プログラミング学習サイトの良いところは、手軽に言語の学習に手をつけられるところですね。しかし現実には実用レベルに達するためにいくつもの壁を越えなければいけないものなのだと実感しています。ローカルでは動いていたけれど、サーバー環境ではうまくいかないことが普通にあるのだとわかりました。これに懲りずにポートフォリオ作りに励もうと思います!

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

カラムから外部キーを削除する

アプリを作成している中で外部キーを削除する必要が出てきたので今後のためにも記事を残します。

/schema.rb

 create_table "behavior_histories", force: :cascade do |t|
    t.bigint "care_recipitent_id"
    t.date "behavior_history_date", null: false
    t.text "action_record", null: false
    t.time "behavior_time", null: false
    t.datetime "created_at", precision: 6, null: false
    t.datetime "updated_at", precision: 6, null: false
    t.bigint "family_id"
    t.index ["family_id"], name: "index_behavior_histories_on_family_id"
    t.index ["care_recipitent_id"], name: "index_behavior_histories_on_care_recipitent_id"

この中の外部キーであるcare_recipitent_idを削除します。

migrationファイルの作成

rails g migration remove_foreign_key_to_behavior_histories

外部キーの削除

class RemoveForeignKeyToBehaviorHistories < ActiveRecord::Migration[6.0]
  def change
    remove_foreign_key :behavior_histories, :care_recipitents
    remove_reference :behavior_histories, :care_recipitent, index: true
  end
end

rails:db:mirateで削除されていることを無事確認。
※remove_foreign_keyとremove_referenceの順序を変えるとうまくいかないため注意してください。

参考:references型のカラムを後から追加・削除

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

[Rails]Arelは簡単or難関?使い方大全

はじめに

あるプロジェクトの開発中、「Arel」というSQL文的なものが出てきました。
初めて見た僕は、「なにこれSQL文?」と疑問でした。調べてみると、Railsの簡単にSQLを使えるという機能の一つということがわかりました。
そこで、今後も使う可能性がありそうだったので、記事にまとめようと思った次第です。。
備忘録的にまとめてますが、誰かの為になれたらと思います。

Arelとは

Arelとは,「⁠Relational Algebra」または「Active Relation」の略で、
その名前の通り、ActiveRecordから派生した、
「⁠関係代数」をRubyのオブジェクトで取り扱うライブラリのことです。

公式ドキュメントによると、、
1.複雑なSQLクエリを簡単に生成可能
2.色んなRDBMSに対応可能
3.フレームワーク(特にORM)のフレームワークとして開発された

つまり、Arelを使うことで、DBの互換性やSQL文字列の生成などに時間を取られることなく、
大事な設計やモデリングに注力してO/Rマッピング処理を実装することができるようになっています。

基本的な操作方法一覧

arel_table

モデル名.arel_tableとすることで指定したモデルのテーブルを検索する準備が出来る。
オプションとして、カラム名を追加したり、条件を追加して検索できる。

Staff.where( Staff.arel_table[:age] )

Arel.sql(〇〇〇〇)

Arel.sql(〇〇〇〇)とすることでSQL文を生成できる。
※〇〇にはSQL文を入れる。

Arel.sql('〇〇〇〇')

Arel::Nodes

Arel::Nodes#build_quoted

文字列などの値をラップして、Arel::Nodes::NamedFunctionなどの引数に渡せるようにします。

Arel::Nodes.build_quoted(" ")

Arel::Nodes::NamedFunction

任意のSQL関数の呼び出しを表現するために使う。
第1引数に関数名、第2引数に関数に渡す引数(配列)、第3引数にエイリアス名(任意)を渡す。

staffs_table = Staff.arel_table
date_format = Arel::Nodes.build_quoted("%Y-%m-%d")

Staff.select(
  Arel::Nodes::NamedFunction.new(
    "DATE_FORMAT", # 日時を指定のフォーマットに整形してくれる関数
    [staffs_table[:created_at], date_format], # created_atを指定したフォーマットに変換
    "registration_date"
  )
)

Arel::Nodes::SqlLiteral

生のSQL文字列を生成出来る。
SQLの構文中に必要な文字列をラップするのに使われる。

  • utf8_general_ci
    • UTF-8文字コードで半角や全角、濁点を区別し、英語の大文字小文字を区別しない指定。
Arel::Nodes::SqlLiteral.new("utf8_general_ci")

Arel::Nodes::InfixOperation

「infix operation」は二項演算という意。
2つの値・変数の計算を行うことができます。
第1引数に演算子、第2引数に演算子の左側の値、第3引数に演算子の右側の値が入る。

Staff.where(
  Arel::Nodes::InfixOperation.new(
    "COLLATE", # 検索条件で照合順序を指定
    Staff.arel_table[:name], # 演算子の左側が、staffsテーブルのnameカラム
    Arel::Nodes::SqlLiteral.new("utf8_general_ci") # 演算子の右側が、utf8_genera_ci
  )
  .matches(Arel::Nodes.build_quoted("さとう%")) # nameが「さとう」から始まるStaffを取得
)

Arel::Nodes::OuterJoin

外部結合(OUTER JOIN)する際に使用する。
JOIN句の第2引数に Arel::Nodes::OuterJoin を指定し、
最後にjoin_sources を呼び出すことで結合できる。

comments_table = Comment.arel_table
staffs_table = Staff.arel_table

Comment.joins(
  comments_table
    .join(staffs_table, Arel::Nodes::OuterJoin)
    .on(
      comments_table[:staff_id].eq(staffs_table[:id])
      .and(staffs_table[:is_active].eq(true))
    )
    .join_sources
)

※commentsテーブルのstaff_idとStaffテーブルのidが等しい、
かつstaffsテーブルの is_activeがtrueのものという条件。

Arel::Nodes::Descending, Arel::Nodes::Ascending

Arel::Nodes::Descendingは ORDER 句の DESC (降順)の部分を担当。
Arel::Nodes::Ascendingは ASC (昇順)です。引数には、ORDER BY の右側の値が入ります。

Staff.order(
  Arel::Nodes::Descending.new(
    Staff.arel_table[:staff_type]) # staff_typeの降順
  )
)

Arel::Nodes::Case

SQL の CASE 構文を表現することができます。

  • gt
    • 左項の値が右項の値より大きいときに真になる。(「>」と同意)
staffs_table = Staff.arel_table
new_cond = staffs_table[:created_at].gt(Time.zone.now - 3.days)

Staff.order(
  Arel::Nodes::Ascending.new(
    Arel::Nodes::Case.new
      .when(new_cond).then(1)
      .else(9)
  )
)

Staffが作成されて3日以内であれば 1、そうでなければ 9 という値とする。
3日以内に作成されたStaffを昇順で並び替えている。

Arel::Nodes::Grouping

括弧()で囲む。四則計算等で括弧を使いたい場合に使う。

Arel::Nodes::Grouping( object )

Arel::Nodes::As

AS による別名定義の文 〇 AS × を作る。

Staff.select( Staff.arel_table[:id].as('no') ) 

Tips集

使えるTipsをご紹介。

IS NOT NULL

Staff.where(
  Staff.arel_table[:age].not_eq(20)
)

AS

Staff.where(
  Staff.arel_table[:name].as('admin')
)

admin = Staff.arel_table.alias('admin')
Comment.joins(admin).select(admin[:name])

サブクエリ

FROM句へのサブクエリ

new_table = Arel::Table.new(nil)
sql = new_table.from(
    Staff.where(created_at: start_at...end_at).to_sql
  )
  .project(Arel.sql('*'))
  .to_sql

JOIN句へのサブクエリ

sub_query = Staff.where(created_at: start_at...end_at).to_sql
Comment.joins("INNER JOIN (#{sub_query})")

SUM

Staff.where(created_at: start_at...end_at)
  .select(Staff.arel_table[:name].sum().as('admin'))

Where

staffs = Staff.arel_table
staffs.arel_table.project(Arel.sql('*')).where(staffs[:id].eq(1)).to_sql

order by

staffs = Staff.arel_table
staffs.project(Arel.sql('*')).order(staffs[:id].asc).to_sql

集計関数とgroup by

staffs = Staff.arel_table
staffs.project(staffs[:id].count, staffs[:name]).group(staffs[:age]).to_sql

COUNTで利用するDISTINCT

Staff.select(Staff.arel_table[:id].count())
  .select(Staff.arel_table[:id].count('distinct'))

終わりに

Arelでは色んな使い方があるのだなと調べて驚きました。
使いこなすには実践あるのみ!ですね。。
しかし、使うことに難を示す人たちもいるようで、賛否両論ではありますが、
チーム開発において、誰かが使って自分が理解出来ない。。ってことが起きると開発にブレーキをかけてしまうので、一応理解しているだけ得だと思いました!

参考

Arel::Nodes を使って Arel で複雑な SQL文を作っちゃおう

Arel::Nodesを最低限読めるようになりたい

RailsのArelのTips

RailsのArelを調査してみた

Arelで色んなSQLを組み立ててみる

ActiveRecordのarel_tableから作れる条件式まとめ

RailsでArel、早見表

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

Basic認証の導入

本日は、Basic認証の導入手順を復習のため記事に
残したいと思います。

バージョン

・Ruby 2.6.5
・Rails 6.0.0
・macOS Big Sur

Basic認証とは

Basic認証とは、HTTP通信の規格に備え付けられている、ユーザー認証の仕組みのことです。
簡単に説明すると、作成したアプリケーションにユーザー名とパスワードを記述して入力した人だけが
アプリケーションを使えるよと言うものです。

Basic認証導入手順

まずは、ユーザーとパスワードを設定しましょう。
設定には、authenticate_or_request_with_http_basicメソッドを使用します。
Basic認証によるログインの要求は、すべてのコントローラーで行いたいです。

app/controllers/application_controller.rb

class ApplicationController < ActionController::Base
  before_action :basic_auth


  private

  def basic_auth
    authenticate_or_request_with_http_basic do |username, password|
      username == ユーザー && password == パスワード
    end
  end
end

Basic認証の処理をapplication_controller.rbのprivate以下にメソッドとして定義し、before_actionで呼び出しましょう。
これで、設定できました。

Basic認証コードを改良

設定できましたが、このままではGit Hubに更新した際にそのまま載ってしまうので
セキュリティ的に良くないため、環境変数を利用する実装に切り替えます。
basic_authメソッド内で直接記述しているユーザー名とパスワードを、開発環境の環境変数に格納します。

これは使用しているMacのOSによってやり方は変わってきます。
今回は、OSがCatalina以降のやり方となります。

1.コマンドの実行

% vim ~/.zshrc

2.「iキー」を押して、インサートモードに移行します。
ターミナルの左下に「INSERT」と表示されるのを確認。

3.zshの内部に、以下の記述を追加しましょう。

export BASIC_AUTH_USER=ユーザー
export BASIC_AUTH_PASSWORD=パスワード

既にzsh内に記述がある場合は、その記述の下に「追加」します。既存の記述を削除してしまうと、パソコンが正常に動作しなくなる危険性があります。

4.記述を追加したら「escキー」を押して、 「:wq」と入力しましょう。
入力後、「Enterキー」を押して終了します。

5.最後に、「sourceコマンド」を実行します。

% source ~/.zshrc

これで、環境変数の設定は完了です。
出てきたいくつかの用語について説明させていただきます。

・zsh(ズィーシェル)
「zsh」はログインシェルと呼ばれるもので、プログラムを実行する時に、ユーザーの要求に一番最初に対応する役割を担います。隠しファイルなので、特別な設定なしではFinderなどには表示されていません。
環境変数を記載する場所は、設定ファイルである「.zshrc」の中です。
OSがCatalina以降であれば「zsh」、Mojave以前であれば「bash」が自動で適用されます。

・vim(ヴィム)
vim」とは、サーバー上で使用できるテキストエディタです。vimコマンドを用いることで、指定したファイルの編集をターミナルから行うことが可能です。
以下が使用例になります。

・source(ソース)コマンド
「sourceコマンド」とは、シェルに記述された内容を実行する役割を担います。zshファイルに記述された内容を実行します。

つまり、.zshrcというファイルは隠しファイルで直接記述できないので、vimコマンド
で編集を行い、sourceコマンド実行を行うということですね。
使っているMacのOSによって、「zsh」か「bash」か変わって来るので、設定する前に確認
が必要です。

ここまでできたら再度、application_controller.rbファイルを編集しましょう。

application_controller.rb

class ApplicationController < ActionController::Base
  before_action :basic_auth


  private

  def basic_auth
    authenticate_or_request_with_http_basic do |username, password|
      username == ENV["BASIC_AUTH_USER"] && password == ENV["BASIC_AUTH_PASSWORD"]  # 環境変数を読み込む記述に変更
    end
  end
end

これで、環境変数を使って、Basic認証を行えるユーザー名とパスワードを定義できました。

本番環境での環境変数の設定

続いて、Herokuの本番環境のアプリケーションにも設定を行います。
下記のコマンドを入力します。

% heroku config:set BASIC_AUTH_USER=ユーザー
% heroku config:set BASIC_AUTH_PASSWORD=パスワード

設定できたかは、下記のコマンドで確認できます。

% heroku config

=== stormy-journey-22625 Config Vars
BASIC_AUTH_PASSWORD:      パスワード
BASIC_AUTH_USER:          ユーザー

変更したコードをコミットしHerokuにデプロイします。

% git add .
% git commit -m "Basic認証を導入"
% git push heroku master

これで、すべて設定は完了です。
使っている環境によってやり方が違う場合もあるので
ご了承ください。

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

Rails 超簡単管理者権限

全知全能管理者権限をつけたいそこのあなた!
簡単な方法で実装できる管理者権限を記事にしてみましたっ!^^

前提

devise を使ってユーザー周りを作成していること
簡単な投稿機能をつけているサイトであること

管理者権限を付与する

まずusersテーブルにユーザーが管理者かどうかを判定するためのカラム(adminカラム)を追加します!
管理者ならtrueをそうでなければfalseを返したいので、データ型はboolean型にします!

$ rails g migration: AddAdminToUser admin:boolean

ここでマイグレーションファイルがうまく作成されているかを確認しましょう!
管理者権限は基本的に特定の人にしか付与しないので、作成されたマイグレーションファイルに default: false を追加します。

db/〇〇_add_admin_to_user.rb
class AddAdminToUser < ActiveRecord::Migration[6.0]
  def change
    add_column :users, :admin, :boolean, default: false  #default: falseの追加をお忘れなく!!!
  end
end
$ rails db:migrate

管理者権限を持たせたいユーザーの初期レコードをseedファイルで定義する
※このサイトではユーザー名を格納するカラムをnameカラムとしています。schemaで自分のものに適したカラム名を加えてください、!

db/seeds.rb
User.create!(name:  "kazuya",
             email: "kazuya@mail.com",
             password:  "ochibi",
             password_confirmation: "ochibi",
             admin: true)
$ rails db:seed

※複数の人に管理者権限を付与したい場合のseedファイルの書き方

db/seeds.rb
User.create!(
  [
    {
      name:  "kazuya",
      email: "kazuya@mail.com",
      password:  "ochibi",
      password_confirmation: "ochibi",
      admin: true
    },
    {
      name:  "kubochiro",
      email: "kubo@mail.com",
      password:  "dodeka",
      password_confirmation: "dodeka",
      admin: true
    }
  ]
)
$ rails db:seed

※管理者のデータを間違えて入れてもた!って人向け

$ rails c
irb(main):001:0> User.all

これで消し去りたい管理者のidを確認する
今回管理者権限を剥奪したいkazuyaのidは3でした!!!!!!

irb(main):002:0> User.find(3).destroy
irb(main):003:0> exit

〜管理者権限を使ってやりたいこと色々〜

管理者権限でよく実装されるものを書き連ねます。
こんな権限が欲しいというのが他にありましたらぜひ依頼くださいまし (^_^)

その1: 不適切な投稿を削除する

どうしてもサイトの趣旨に合わない投稿をされてしまうことってありますよね、、、
これは管理者権限を付与されている人だけが、どんな投稿でも削除できるようにするとことで解決できます!

<% if current_user.admin? %>これが一番のミソです!!ここでadminの値がtrueかどうかを判定しています(つまりログインしているユーザーが管理者かどうか)

posts/index.html.erb
<% @posts.each do |t| %>
  #ここから
    <% if user_signed_in? %>
      <% if current_user.id == t.user_id %>
        <%= link_to "編集する", edit_post_path(t.id) %>
        <%= link_to "削除する", post_path(t.id), method: :delete %>
      <% elsif current_user.admin? %>
        <%= link_to "管理者用編集", edit_post_path(t.id) %>
        <%= link_to "管理者用削除", post_path(t.id), method: :delete %>
      <% end %>
    <% end %>
  #ここまで
<% end %>

ざっくり解説すると、、この条件分岐では、ユーザーは自分の投稿の編集削除はできるが、他のユーザーの投稿をいじることはできない。しかし管理者(adminがtrue)だと、全ての投稿を編集削除することが可能。また管理者でログインしているとき、その管理者自身の投稿の編集削除は「編集する」「削除する」と表示されるが、その管理者以外の投稿は「管理者用編集」「管理者用削除」と表示される。
、、、こういう風に条件分岐を駆使して理想状態を考えて作るのは楽しいですよ〜〜 (^O^)

その2: 不適切管理者だけの秘密のページ❤️

実際にどれくらいの人が自分の作ったサイトに新規登録してくれているのか、気になっちゃいますよね?

ここでは管理者だけが覗けるページ、今回は例として、登録者情報、登録者数をこっそり閲覧するユーザー一覧ページを作成しようと思います!!

他にも良いやり方はあると思いますが、今回は簡単のため条件分岐だけで実装しまふ。。

usersコントローラー

ユーザーの全情報を取得するために・・・

users_controller.rb
class UsersController < ApplicationController
  def index
    @users = User.all
  end
end

一覧ページへのルート

urlは適宜変えてください m(_ _)m
なるべくマイページ(show)を作ってる人がエラー出にくくなるようにこのurlにしました

routes.rb
get 'users' =>'users#index' 

一覧ページのビュー

もし管理者権限を付与されていない人がこのページのURLにたどり着いても、中身を表示させないためにif分で制限をかけています。
今回は、登録者情報(メアド、ユーザー名)とアカウントの合計数を表示しています。

users/index.html.erb
 <% if user_signed_in? %>
   <% if current_user.admin? %>
     <% sum = 0 %>
       <% @users.each do |u| %>
         <p>メアド:<%= u.email %></p>
         <p>名前:<%= u.name %>さん</p>
       <% sum += 1 %>
       <% end %>
     <p>登録者合計:<%= sum %></p>
    <% end %>
  <% end %>

秘密のページに飛ぶための秘密のリンク

秘密のページ(ユーザー一覧ページ)に飛ぶための秘密のリンクを任意のビューページに表示します!
今回は例として posts/index.html.erb に表示させています!

posts/index.html.erb
 <% if user_signed_in? %>
   <% if current_user.admin? %>
     <%= link_to "秘密のページへ", users_path %>
   <% end %>
 <% end %>

いかがでしたでしょうか、、???

これ以外にも管理者権限でやりたいことなどあれば依頼してください?‍♂️
その3その4、、を追記していきます、、!!

忘れないで!

管理者権限を付与する際に、seedファイルに初期レコードを定義しているので、デプロイする際には以下のコマンドを忘れないようにしましょう!!

$ heroku run rake db:seed

最後に

ここまで読んでくださってありがとうございました (°▽°)

やっていることは簡単ですが、railsの勉強を始めたばかりの人にとっては、seedファイルをいじったり、コンソールをいじったり、条件分岐を駆使したり、、といい勉強になったのではないかなと思います ( ̄∇ ̄)

修正補足依頼、アドバイス等ございましたら、どしどしお願い致しやーーーーーーーーーーーーーーーーーーーーす !!!

バイチャ〜〜?

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

routes.rbのルーティングを他ファイルに分割した話

はじめに

本稿は、railsのroutes.rbに記述されたルーティングが数百行にも渡り、コードの可読性が欠けてしまっている場合において、自身がどのように見やすさを確保したのかを記載したものになります。

ルーティングを分割する

0. 編集前のconfig/routes.rb

※記載されている内容は例として設定しているだけなので、それぞれのルーティング名に関連性はありません。

config/routes.rb
Rails.application.routes.draw do
  namespace :api, format: 'json' do
    namespace :v1 do
      namespace :libraies do
        resources :libraries
      end

      namespace :books do
        resources :books
      end
    end

    namespace :v2 do
      namespace :libraries do
        resources :libraries do
          collection do
            get :borrowed_books
          end
        end

        resources :librarians do
        end
      end
    end
  end
end

こちらのroutes.rbをファイル分割していきます。

1. config配下に"routes"ディレクトリを作成する

スクリーンショット 2021-03-07 21.43.56.png

2. config/routes.rbを編集

config/routes.rb
Rails.application.routes.draw do
  def draw(routes_name)
    instance_eval(File.read(Rails.root.join("config/routes/#{routes_name}.rb")))
  end

  Rails.application.routes.draw do
   # ここに"routes_name"を定義します。
  end
end
  • "routes_name"には1. config配下に"routes"ディレクトリを作成するで作成したroutesディレクトリ内に作成したファイル名を指定します。

※まだconfig/routes配下にファイルを作成していないので、次で作成していきます。

分割先のファイルを用意

今回サンプルで作成したroutes.rbでは

  • api v1
  • api v2

この粒度で分割していきたいと思います。

そこでapi_v1.rbapi_v2.rbという名前のファイルを作成します。

touch config/routes/api_v1.rb
touch config/routes/api_v2.rb

3. routes.rbに作成したファイル名を指定する

config/routes.rb
Rails.application.routes.draw do
  def draw(routes_name)
    instance_eval(File.read(Rails.root.join("config/routes/#{routes_name}.rb")))
  end

  Rails.application.routes.draw do
    draw :api_v1
    draw :api_v2
  end
end

4. api_v1.rbを編集

config/routes/api_v1.rb
namespace :api, format: 'json' do
  namespace :v1 do
    namespace :libraies do
      resources :libraries
    end

    namespace :books do
      resources :books
    end
  end
end

5. api_v2を編集

config/routes/api_v2.rb
namespace :api, format: 'json' do
  namespace :v2 do
    namespace :libraries do
      resources :libraries do
        collection do
          get :borrowed_books
        end
      end

      resources :librarians do
      end
    end
  end
end

終わりに

これでかなり見やすくなりました!

分割するタイミングはプロダクトによって様々だと思いますが、コードの可読性を上げるためにも管理ができる範囲でコードをどんどん外に出していくことは重要だと思います。

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

【個人開発】ファラオが情報共有し現代社会を生き抜くアプリ作りました

はじめに

こんにちはファラオ
世は情報社会、多くの人々がスマートフォンを持ち歩きTwitterなどで常に情報交換していますね
しかし、世で使われているSNSは現代の言語で書かれており、ファラオが使うのには少し不便なのではないでしょうか
今回はそんな情報収集に苦労しているファラオ達に使っていただけるSNSを開設しました

サービス概要

hieroglitter
ファラオ達がヒエログリフで情報を共有出来るアプリです
普段お使いの言語を入力すると、ヒエログリフに変換されて投稿されます
これで世界中のファラオと情報を共有出来ますね
※現在は日本語とアルファベットのみに対応しています

使い方

こちらがトップページです
image.png
ファラオのプライバシーを守るためファラオログイン機能によって平民はフィルタリングしています
安心してお使いください
ファラオの皆さんならどこからファラオログイン出来るか一目瞭然ですね?
こちらがアプリのメインページです
image.png
ここから好きなように投稿してください
Image from Gyazo
お気に入りの投稿を見つけたら猫ボタンを押すと好きなだけあなたの好意を伝えられます
Image from Gyazo

使用技術について

自作gemについて

アルファベットを対応するヒエログリフに変換して出力するメソッドを提供する単純なgemです
Unicodeはヒエログリフに対応しているので、A-Zまでそれぞれの音が対応するヒエログリフの文字コードをもとに文字列を変換しています
アルファベット以外はそのままの文字を出力します
※デプロイ後にノリで作成したのでせっかくなのでノリで使いました

おわりに

クソアプリ作ってみたかったので心が満たされました
自分の好きな感じのアプリが作れたかなと思います
就活用に作ってるポートフォリオそっちのけで作ったので、そっちが全然進んでませんが僕は元気です
よかったら遊んでみてくださいね
hieroglitter
ちなみにファラオファラオ言ってたらアプリの名前誰も覚えてくれなくてファラオSNSって呼ばれてます
このアプリはweb1week-202102に参加するために作りました。だら?FlutterとReactとLaravelと風来のシレン様、良い機会を下さりありがとうございました

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

Railsチュートリアル6.0に沿って、AWS Cloud9上にRailsの開発環境作ってみた

Railsチュートリアル6.0を実施するにあたって、記載があった手順でAWS Cloud9でクラウド統合開発環境を作ってみました。
Cloud上とPC上で説明が混ざっていたので、こちらにまとめてすっきりさせたい意図で本記事を作成しました。(あと、記事の画面が若干古かったため)

なお、アカウントは既に作成済みで、使用機材はwindowsです。(クラウドなので、差異はない気がしています)

以下が参照した手順
https://railstutorial.jp/chapters/beginning?version=6.0#sec-development_environment

手順1:AWSのCloud9にアクセスする

ルートユーザーでログインし、AWSマネジメントコンソールにアクセス。

上の検索窓から「Cloud9」を入力し、Cloud9に遷移します。
キャプチャ.JPG

手順2:「Create environment」を押下

Cloud9の画面で「Create environment」を押下する。

手順3:Name environmentを設定

Name environmentの「Name」と「Description」に適当な値を設定します。
自分の場合は
Name:rails-tutorial
Description:rails-tutorial
と値を設定しました。

値設定後「Next step」を押下します。
キャプチャ1.JPG

手順4:各種設定

ここでの設定ですが、
Platform:Ubuntu Server 18.04 LTS
を設定して、他はデフォルトのままで「Next step」を押下します。
キャプチャ2.JPG

キャプチャ3.JPG

手順5:Review

設定した内容を確認し、「Create environment」を押下。

キャプチャ4.JPG

これでCloud9で開発環境の構築は完了です。
キャプチャ6.JPG

補足:インデントのデフォルトを変えておく

Rubyはインデントに2つのスペースを使うの通例らしいので、エディタのインデント設定をデフォルトから2に変更します。

手順としては、Cloud 9の右上歯車マークを押下し、Project Settingを開きます。
その後、CodeEditer(Ace)のsofttabを2に設定すれば完了です。

キャプチャ7.JPG

Railsインストール

下記コマンドをCloud 9のターミナルで走らせます。

まず、Rubyドキュメントをインストールしないよう.gemrcファイルを設定します。

$ echo "gem: --no-document" >> ~/.gemrc

バージョンを指定してRailsをインストールします。(Railsチュートリアルではv 6.0.3をインストールしているのでそれに倣います)

$ gem install rails -v 6.0.3

下記コマンドを入力して、Railsのバージョンが返ってくれば、Railsのインストールは完了です。

$ rails -v

Yarnインストール

もう一つだけ、javascriptのパッケージマネージャーの「Yarn」をインストールしていきます。

$ source <(curl -sL https://cdn.learnenough.com/yarn_install)

もし警告メッセージが出た場合は、メッセージ通り下記を入力すればOKです。

$ yarn install --check-files

こちらもバージョンを確認して、表示されたらOKです。

$ yarn -v

これでRailsチュートリアルで実施されていたクラウド統合開発環境構築はいったん完了です。

Gitやherokuなどセットアップはまた別の機会に。

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

Railsチュートリアル6.0に沿って、AWSのCloud9上でRailsの開発環境作ってみた

Railsチュートリアル6.0を実施するにあたって、記載があった手順でAWS Cloud9でクラウド統合開発環境を作ってみました。
Cloud上とPC上で説明が混ざっていたので、こちらにまとめてすっきりさせたい意図で本記事を作成しました。(あと、記事の画面が若干古かったため)

なお、アカウントは既に作成済みで、使用機材はwindowsです。(クラウドなので、差異はない気がしています)

以下が参照した手順
https://railstutorial.jp/chapters/beginning?version=6.0#sec-development_environment

手順1:AWSのCloud9にアクセスする

ルートユーザーでログインし、AWSマネジメントコンソールにアクセス。

上の検索窓から「Cloud9」を入力し、Cloud9に遷移します。
キャプチャ.JPG

手順2:「Create environment」を押下

Cloud9の画面で「Create environment」を押下する。

手順3:Name environmentを設定

Name environmentの「Name」と「Description」に適当な値を設定します。
自分の場合は
Name:rails-tutorial
Description:rails-tutorial
と値を設定しました。

値設定後「Next step」を押下します。
キャプチャ1.JPG

手順4:各種設定

ここでの設定ですが、
Platform:Ubuntu Server 18.04 LTS
を設定して、他はデフォルトのままで「Next step」を押下します。
キャプチャ2.JPG

キャプチャ3.JPG

手順5:Review

設定した内容を確認し、「Create environment」を押下。

キャプチャ4.JPG

これでCloud9で開発環境の構築は完了です。
キャプチャ6.JPG

補足:インデントのデフォルトを変えておく

Rubyはインデントに2つのスペースを使うの通例らしいので、エディタのインデント設定をデフォルトから2に変更します。

手順としては、Cloud 9の右上歯車マークを押下し、Project Settingを開きます。
その後、CodeEditer(Ace)のsofttabを2に設定すれば完了です。

キャプチャ7.JPG

Railsインストール

下記コマンドをCloud 9のターミナルで走らせます。

まず、Rubyドキュメントをインストールしないよう.gemrcファイルを設定します。

$ echo "gem: --no-document" >> ~/.gemrc

バージョンを指定してRailsをインストールします。(Railsチュートリアルではv 6.0.3をインストールしているのでそれに倣います)

$ gem install rails -v 6.0.3

下記コマンドを入力して、Railsのバージョンが返ってくれば、Railsのインストールは完了です。

$ rails -v

Yarnインストール

もう一つだけ、javascriptのパッケージマネージャーの「Yarn」をインストールしていきます。

$ source <(curl -sL https://cdn.learnenough.com/yarn_install)

もし警告メッセージが出た場合は、メッセージ通り下記を入力すればOKです。

$ yarn install --check-files

こちらもバージョンを確認して、表示されたらOKです。

$ yarn -v

これでRailsチュートリアルで実施されていたクラウド統合開発環境構築はいったん完了です。

Gitやherokuなどセットアップはまた別の機会に。

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