ポリモーフィック関連を使うと、複数のモデルに属していることを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>
参考文献リスト
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)。
論理演算子は慣れないと、ぎょっと体が固まってしまいます。早く慣れたいものです。
参考文献
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を返すメソッドが使えるようになります。
他にも破壊的なメソッドなどあるので都度確認しましょう。
参考文献
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の箇所
レンダリングの処理の流れとレイアウト宣言
こんちには、たわらです。
本記事では、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
を探します。
5 【なければapplication.html
を使用する】
rails newで作ったアプリだと、これを参照することになっているはずです。
6 application.html
のなかの<%= yeild %>
に、アクションに対応したテンプレートを挿入する
コントローラーごとのページレイアウトを変えるにはlayout宣言を利用する
たとえば、管理者用のページは既存のレイアウトとは変えたい場合があります。
なので、コントローラー別でページレイアウトを変えるには、コントローラーが探しにいくレイアウトを予め指定してあげればよい、というわけですね。
layout
宣言を使用することで、デフォルトのレイアウト名ルールを上書きすることができます。例:
class ProductsController < ApplicationController layout "inventory" #... end
この宣言によって、
ProductsController
からの出力で使用されるレイアウトはapp/views/layouts/inventory.html.erb
になります。
最後に
基礎的な理解がスッキリすると見通しがたってよいですね。
読んでくださったかた、ありがとうございます。
控えめな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
最後に
rails-ujsによって、method: :delete
などのオプション指定を鑑みて、HTTPリクエストを発行してくれるのですね。
読んでくださったかた、どうもありがとうございます。
参考文献
パスワードリセット機能の処理の流れ
こんにちは、たわらです。
本記事は、パスワードリセット機能を実装したときの学びをまとめたものです。
認証管理Gem Sorceryのモジュールを使う
sorceryを使用しているアプリケーションであれば、Reset Passwordというモジュールを使うことができます。
このWikiに従って実装すれば、パスワードリセット機能を実装することができます。
また、開発環境では実際にメールを配信してしまうと何かと面倒なので、実際には発信してないにもかかわらず、発信したメールの挙動を確認できるletter_opener_web
を使うとスムーズに開発ができます。
パスワードリセット機能の処理の流れ
パスワードリセット機能の処理の流れを理解しましょう。
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
こんな感じです。
他にも、さまざまなオプションがあるので、メール機能を実装するときには、必ず参照しようと思います。
最後に
読んでくださったかた、ありがとうございます。