tawara's blog

雑記。個人の見解です。

ポリモーフィック関連を使うと、複数のモデルに属していることを1つの関連付けで表現できる

こんにちは、たわらです。

ポリモーフィック関連について整理した記事です。

モデルのコードはこんな感じ

ブログ記事を作成するサービスを考えます。記事をブロックを追加するかたちで作成します。記事は、Ariticleブロックを複数持っているということです。そして、記事ブロックには3種類あります。文章と画像です。

SentenceモデルとMediumモデルがあり、それらにAriticleblockモデルがポリモーフィック関連しているとします。

それぞれこんなふうになります。

Ariticleblockモデル

class ArticleBlock < ApplicationRecord
  belongs_to :article
  belongs_to :blockable, polymorphic: true, dependent: :destroy
  # ...
end

Sentenceモデル

class Sentence < ApplicationRecord
  has_one :article_block, as: :blockable, dependent: :destroy
  # ...
end

Mediumモデル

class Medium < ApplicationRecord
  has_one :article_block, as: :blockable, dependent: :destroy
  # ...
end

Articleblockモデルにpolymorphic: trueとあります。これでポリモーフィック関連ができます。blockableという存在しないテーブル名に属している、と読めます。

SentenceモデルとMediumモデルにはas: :blockableとあります。Articleblockモデルにblockableとして関連する、と読めます。

カラムの作成はこんな感じ

Ariticleブロックのカラムにはポリモーフィック関連のための準備が必要です。

class CreateArticleBlocks < ActiveRecord::Migration[5.2]
  def change
    create_table :article_blocks do |t|
      t.belongs_to :article
      t.belongs_to :blockable, polymorphic: true
      t.timestamps

      t.index :level
    end
  end
end

〇〇ableを作成する必要があるようです。

こうすると自動的にAriticleblockテーブルに、blockable_idとblockable_typeが追加されます。

この状態で、Sentence.newをすると自動的に、blockable_idに適当な数値が入り、blockable_typeに"Sentence"と入ります。

コンソールでの出力はこんな感じ

[1] pry(main)> ArticleBlock.first
  ArticleBlock Load (1.7ms)  SELECT  `article_blocks`.* FROM `article_blocks` ORDER BY `article_blocks`.`id` ASC LIMIT 1
=> #<ArticleBlock:0x00007ffa2e9d37f0
 id: 1,
 article_id: 1,
 blockable_type: "Sentence",
 blockable_id: 1,
 created_at: Wed, 15 Jul 2020 16:43:32 JST +09:00,
 updated_at: Wed, 15 Jul 2020 16:43:32 JST +09:00>
[2] pry(main)> ArticleBlock.first.blockable
  ArticleBlock Load (0.6ms)  SELECT  `article_blocks`.* FROM `article_blocks` ORDER BY `article_blocks`.`id` ASC LIMIT 1
  Sentence Load (1.2ms)  SELECT  `sentences`.* FROM `sentences` WHERE `sentences`.`id` = 1 LIMIT 1
=> #<Sentence:0x00007ffa2eaa9fd0 id: 1, body: "<p>およよよよ</p>", created_at: Wed, 15 Jul 2020 16:43:32 JST +09:00, updated_at: Wed, 15 Jul 2020 16:43:46 JST +09:00>

参考文献リスト

railsguides.jp

Railsのポリモーフィック関連とはなんなのか - Qiita

ポリモーフィック関連覚え書き - Qiita

hoge ll= foo はhogeがnilまたはfalseなら、fooをhogeに代入する

こんにちは、たわらです。

本記事は演算子||=について整理した記事です。

事例 nilだったら空文字入れたい

ある変数がnilだった場合に、空文字を代入したいことがありました。ない知恵を絞ってこんなコードを書きました。

if sentence.body.nil?
 sentence.body = ''
else
 sentence.body
end

なんだか冗長な感じを受けます。

自己代入を使おう

このコードは自己代入を使うとすっきりと書くことができます。

sentence.body ||= ''

これは、下記のコードを省略したものです。

sentence.body = sentence.body || ''

右辺に注目します。ll演算子は式全体の真偽値が確定した時点で式の評価を終わらせ、そのときの戻り値を返します。

つまり、sentence.bodyがfalseでもnilでもない真の値であれば、sentence.body = sentence.bodyとなります。一方で、sentence.bodyがfalseかnilの場合、sentence.body = ''となります。

Rubyのレファレンスには、

a ||= 1         # a が偽か未定義ならば1を代入。初期化時のイディオムの一種。

と説明されています。初期化時に使うことが多いようです。なるほど。

また、チュリー本には、

X ||=A というコードを見たら「変数Xがnilまたはfalseなら、AをXに代入」と頭の中で読み替えるようにしてください

とまで書いてあります(pp174)。

論理演算子は慣れないと、ぎょっと体が固まってしまいます。早く慣れたいものです。

参考文献

docs.ruby-lang.org

amzn.to

Enumを活用すれば整数値に名前をつけられる

こんにちは、たわらです。

Enumについて簡単に整理した記事です。

Enumって?

「列挙型」という意味だそうです。

Wikipediaによれば「列挙型(れっきょがた、enumerated typeあるいはenumeration type)とは、コンピュータプログラミング)において、プログラマが選んだ各々の識別子(列挙子)をそのまま有限集合として持つ抽象データ型である」

ざっくり説明すれば、一連の整数値に対して複数の変数名をつけること、と言えます。

こんなふうに使う

ユーザーのサービスに対する権限を定義する必要があるとします。

class AddColmunToUsers < ActiveRecord::Migration[5.2]
  def change
    add_column :users, :role, :integer, null: false, default: 0
  end
end

こんな感じで、Userクラスにroleカラムを追加します。データ型はintegerです。defaultを0にしておきます。

次にUserクラスに次のように記述します。

enum role: { general: 0, admin: 1 }

こうすることで、0と1にそれぞれgeneralとadminという名前を付与することができます。(言い方として、roleカラムにenum属性を宣言する、という言うのでしょうか。このあたりがわからないですね、、、)

便利メソッドが使えるから属性の確認が効率よくできる!

enum属性を付与することで、ユーザーがどの権限かを簡単に確認することができます。

たとえば、管理者権限のユーザーでしか許可したくないプログラムがあるとします。

enumを定義していない場合は、このように書くことになります。

実行したい処理 if @user.role == 1

このコードのいまいちなところは、1が何を示しているかわからないことになります。

このデメリットをenumは解消してくれます。enum属性を宣言すると便利メソッドが使えるようになるのです。

実行したい処理 if @user.admin?

このコードは自然言語に近いかたちで理解ができます。管理者か? とユーザーオブジェクトに聞いている感じですね。adminであればtrue、そうでなければfalseを返すメソッドが使えるようになります。

他にも破壊的なメソッドなどあるので都度確認しましょう。

参考文献

api.rubyonrails.org

qiita.com

namespace を使ってコントローラーをグループ化する

こんにちは、たわらです。

本記事は、namespaceを使ったときのディレクトリ構成の情報をまとめたものです。

名前空間をつくるメリット

名前空間を使うメリットはざっくりふたつあるみたいです。wikipediaによれば、

名前空間(なまえくうかん)はNamespaceの訳語で、名前の集合を分割することで衝突の可能性を低減しつつ参照を容易にする概念である。

https://ja.wikipedia.org/wiki/%E5%90%8D%E5%89%8D%E7%A9%BA%E9%96%93

とあります。

1 集合を分けることで同じ名前を使うことができる

たとえば管理者用のログイン機能を実装するときに、user_sessions_controllerという名前を使おうとします。ただし、既にサービスのログイン機能の実装の際に、その名前のコントローラー名を使用していると、アプリの挙動がおかしくなってしまう可能性があります。

2 参照が簡単になる

ディレクトリを見たときに、名前空間でファイルを分けておくと、人間も管理しやすいし、アプリケーションもファイルの参照が用意になります。たぶん。

名前空間をつくる

routes.rbにこのように記載します。

namespace :admin do
  resources :articles
end

そして、ディレクトリを下記のようにつくってあげます。名前空間名のところにはこの場合、adminが入ります。

─ app
 ├ controllers
 │└ 【名前空間名】
 │ └ 【コントローラ名】_controller.rb
 └ views
  └ 【名前空間名】
   └ 【コントローラ名】
    └ 【アクション名】.html.erb

コントローラーはこんな感じで記載します。

class 【名前空間名】::【コントローラ名】Controller < ApplicationController
  def 【アクション名】
  hoge
  end
end

管理者じゃないとログインできない、などのメソッドをつくり、before_actionメソッドなどを作るとよいですね。

最後に

ディレクトリの構造を俯瞰で眺めるって大事ですね。

読んでくださったかた、ありがとうございました。

参考文献

nemespaceの箇所

railsguides.jp

レンダリングの処理の流れとレイアウト宣言

こんちには、たわらです。

本記事では、railsレンダリングの処理の流れをまとめました。

ネームスペースを使用して、管理画面の画面表示を作成しているときに、こんがらがってしまったので、整理します。

ざっくりこんな処理の流れになってる

1 URLにアクセスがある

2 対応するコントローラーのアクションが作動する

3 アクションはrederメソッドで、何がしかのテンプレートを表示しようとする。

  ただ、アクション名と同じerb または slimフィアルがあれば、記載しなくてもレンダリングしてくれます。

  new.html.erbがあるとき、def new; endだけでもレンダリングしてくれる。すごい!

4 【コントローラーは同じ基本名を持つレイアウトをapp/views/layoutsに探しにいく】

ここの理解が欠如していましたね。。。

PhotosControllerクラスのアクションから出力するのであれば、app/views/layouts/photos.html.erbまたはapp/views/layouts/photos.builderを探します。

railsguides.jp

5 【なければapplication.htmlを使用する】

 rails newで作ったアプリだと、これを参照することになっているはずです。

6 application.htmlのなかの<%= yeild %>に、アクションに対応したテンプレートを挿入する

コントローラーごとのページレイアウトを変えるにはlayout宣言を利用する

たとえば、管理者用のページは既存のレイアウトとは変えたい場合があります。

なので、コントローラー別でページレイアウトを変えるには、コントローラーが探しにいくレイアウトを予め指定してあげればよい、というわけですね。

layout宣言を使用することで、デフォルトのレイアウト名ルールを上書きすることができます。例:

class ProductsController < ApplicationController
  layout "inventory"
  #...
end

この宣言によって、ProductsControllerからの出力で使用されるレイアウトはapp/views/layouts/inventory.html.erbになります。

railsguides.jp

最後に

基礎的な理解がスッキリすると見通しがたってよいですね。

読んでくださったかた、ありがとうございます。

控えめなJavascript(rails-ujs)のおかげで、DELETEなどのHTTPリクエストができるみたい

こんにちは、たわらです。

本記事は、控えめなJavascriptについての情報をまとめました。

ことの発端

最近、開発者ツールを使うことを覚えてたので、ログアウト機能を実装するときに、HTML構造を確認しました。

<a class="nav-link" rel="nofollow" data-method="delete" href="/admin/logout">ログアウト</a>

できてますね。

で、挙動の確認をしようと思って、開発者ツールの/admin/logoutをクリックすると、

No route matches [GET] "/admin/logout"

とエラーが出てきます。ルーティングもあってるし、

<%= link_to "ログアウト", admin_logout_path, class: "nav-link", method: :delete %>

deleteもちゃんと指定できてるのに、なんで??? となってしまいました。

で、ブラウザのログアウトボタンを押すと、きちんとログアウトができました。なんで???

aタグの基本はGETリクエス

で、講師の方に質問すると、「ざっくり説明すると、HTML上のルールとして、aタグはGETメソッドなんだよね」と教えてもらいました。だから、開発者ツールのリンクを踏むと、GETメソッドでHTTPリクエストが発行されるのですね。

ほえー、なるほど。じゃ、どうしてDELETEメソッドが使えるの?

講師「ブラウザ上のログアウトボタンを押したときには、rails-ujsというJavascriptが機能して、deleteやpostとしてリクエストができるようになっているよ」

なるほど。

マニフェストファイルのapplication.jsにあるこの行のことですね

//= require rails-ujs

控えめなJavascriptの登場

で、調べてみるとRailsガイドに、

Railsでは、JavaScriptをDOMに追加する際の手法を「UJS: Unobtrusive(控えめな)JavaScript」と呼んでいます。これは一般にフロントエンド開発者コミュニティでベストプラクティスであると見なされていますが、ここではもう少し違う角度から説明したいと思います。

と記載がありました。

この名称は、HTMLの中にJavaScriptを混入させないという意図に由来しています。JavaScriptを正しく分離できたので、今後の変更が楽になります。以後は、このdata-*属性をリンクタグに追加するだけでこの動作を簡単に追加できます。Railsでは、こうした「最小化」と「連結」によって、あらゆるJavaScriptを実行できます。

data-method="delete"とすると、よしなにやってくれるみたいです。

なので、マニフェストファイルに

//= require rails-ujs

を記述しないと、うまく作動してくれないようです。

もっと気になるかたは、この記事がわかりやすかったです。

link_to によって delete メソッドなどをaタグで実装できているように感じますが、実際にはrails-ujsが動的にformを組み立てて送信しているという動作になります。

ということをRailsの内部のコードに触れながら解説してくれます。

https://www.inodev.jp/entry/2019/12/03/234210

www.inodev.jp

最後に

rails-ujsによって、method: :delete などのオプション指定を鑑みて、HTTPリクエストを発行してくれるのですね。

読んでくださったかた、どうもありがとうございます。

参考文献

https://www.inodev.jp/entry/2019/12/03/234210

www.inodev.jp

railsguides.jp

パスワードリセット機能の処理の流れ

こんにちは、たわらです。

本記事は、パスワードリセット機能を実装したときの学びをまとめたものです。

認証管理Gem Sorceryのモジュールを使う

sorceryを使用しているアプリケーションであれば、Reset Passwordというモジュールを使うことができます。

github.com

このWikiに従って実装すれば、パスワードリセット機能を実装することができます。

また、開発環境では実際にメールを配信してしまうと何かと面倒なので、実際には発信してないにもかかわらず、発信したメールの挙動を確認できるletter_opener_webを使うとスムーズに開発ができます。

github.com

パスワードリセット機能の処理の流れ

パスワードリセット機能の処理の流れを理解しましょう。

1 【クライアント】パスワードリセットページにて、登録しているメールアドレスを打ち込む

2 『 サーバー 』パスワードリセット申請時に、一意のトークンを発行してユーザーテーブルのトークンカラムに保存しておく。入力されたメールアドレスに、パスワードリセットページへのリンクを含めた案内メールを発信。このとき、URLに一意のトークンを含める。

password_resetsコントロールのcreateアクションで、この処理を行う)

こんな感じのURLになる。

http://hogehoge/password_resets/zKsb7u8hysvZdqyqqKfiy/edit

zKsb7u8hysvZdqyqqKfiyの部分がトークンですね。

3 【クライアント】メールを開いて、URLをクリックする

4 『 サーバー 』送られてきたURLからから取得したトークンで、ユーザーテーブルのトークンカラムを検索する。

もし、取得したトークンと一致するトークンがDBになければ、ログイン画面に戻す

もし、取得したトークンと一致するトークンがDBにあれば、パスワード更新画面を表示する

5 【クライアント】パスワード更新画面で、パスワードを更新する

6 『 サーバー 』フォームから送られてきた新しいパスワードを取得してDBに保存する。

(この過程で、パスワードリセットトークンを削除する=nilを代入するので、ユーザークラスで、トークンのカラムにnilが入ってもいいように、

validates :reset_password_token, uniqueness: true, allow_nil: true

と記述する。しないと、エラーが出てしまう)

ざっとこんな感じになりました。

Action Mailerの基礎に目を通す

メーラークラスに必要なコードを記述すれば、メール内で使えるインスタンス変数や、メールの宛先、メールアドレスの題名などを決めることができます。

class UserMailer < ApplicationMailer
  default from: 'notifications@example.com'

  def welcome_email
    @user = params[:user]
    @url  = 'http://example.com/login'
    mail(to: @user.email, subject: '私の素敵なサイトへようこそ')
  end
end

こんな感じです。

他にも、さまざまなオプションがあるので、メール機能を実装するときには、必ず参照しようと思います。

railsguides.jp

最後に

読んでくださったかた、ありがとうございます。