【rails】ActionTextとポリモーフィック関連付け

前回書いてから1ヶ月経ってしまった。もちろんtechcampは終了(修了?)している。
以前記事を書いたときからそこまで状況は変わっていない。せっせとアプリ実装を進めている。

年末年始、今年は実家に帰ることもできなかったのでひたすらテストコードを書いていた。誇張ではなく本当に12/31も1/1も朝から晩までコード書いたりコード書いたりコード書いたりしていた。悪くない年越しだったと思う。

そこまで状況は変わっていないといいつつ、できることや知識は増えたと思う。前に行っていた「HerokuにあげたアプリのDBがいじれない問題」も解決したし、とりあえず開発環境のみだけどdockerに乗せた。

それをtravisCIと連携して自動でテストを流すところまで来たのはいいのだが、ローカルだとうまくいく結合テストがうまく行かない。selenium chromeのコンテナは立てているのだけれど、多分うまく連携できていないのか、結合テストで画面操作が必要なところだけことごとく失敗する。
ここ数日何時間も調べていろいろ試しているのだけどどうにもならず、とりあえず今日は距離を置くことにした。
シンプルに、dockerやdocker composeやCIツールの知識がまだまだ足りていない。

自分のアプリにActionTextを導入してみる

Udemyの講座でその存在を知ったrails6の新機能、ActionText
railsguides.jp

レシピやブログが保存できるアプリ実装をしているので、この機能は親和性があるなと思って導入してみた。導入自体は簡単。ただ、ガイドにもある通り事前にActive Strageをセットアップしておく必要はある。
Active Storage の概要 - Railsガイド

ActionTextをインストールしたあと、リッチテキストのフィールドを追加したいモデルで以下記述をする。

class Blog < ApplicationRecord
 has_rich_text :content
end

あとはview画面のフォーム内で参照すればOK。

# 中略
<div class="new-blog-form">
 <%= form_with model: [@recipe, @blog], local: true do |f| %>
  <div class="label">title</div>
  <%= f.text_field :title %>
  <div class="label">本文</div>
  <%= f.rich_text_area :content %>
  <%= f.submit '追加' %>
 <% end %>
</div>

こんな感じで簡単にリッチテキスト形式のフォームが作れる。すごい。
f:id:yuki_526:20210102155718p:plain

一番いいなと思ったのは、やっぱり画像をそのままドラッグ&ドロップで文中に埋め込むことができる点。例えば料理を作った記録を残したいときとか、手順と一緒に調理過程の画像を載せる、ということがリッチテキストなら簡単にできる(画像のサイズをどうするのか?という問題はあると思うので、本格的なサービスとかで使うにはもっとしっかり揉んでからという気はするけど、個人で記録を残す分には便利)。

加えて、今自分が作成しているアプリではレシピを保存するrecipe modelとブログ記事を保存するblog modelの両方でActionTextを使っており、当然同じテーブルに2種類のcontentがごっちゃになって保存されている。
これって一体全体どんな仕組みなんだ?と気になったので、Udemyの講座をもう一度見てみたり、ガイドを読んだり、ER図を書きながら自分でもじっくりDBのテーブルやカラムたちを観察してみた。

Active Storageのモデルについて

事前準備としてセットアップしたActive Strageは、もともとは「簡単に画像をアップロードできる機能」くらいにしか思っていなかった。ガイドの言い方をすると、

ファイルをActive Recordオブジェクトにアタッチする機能を提供します。
Active Storage の概要 - Railsガイド

ということらしい。使い方としては、Active Storageをインストールしたあとにファイルをもたせたいモデルに対して

class User < ApplicationRecord
 has_one_attached :picture
 # has_many_attached もある
end

と記述すればいい。

このActive Storaggeは導入された段階でDBに以下2つのテーブルが作られる。

  • active_storage_blobs
  • active_storage_attachments

このうち、実際のファイルに対応しているのはactive_storage_blobs(blobはBinary Large Object、画像などのバイナリデータを格納するデータ型)であり、active_storage_attachmentsは紐付けられたモデルとactive_storage_blobsの中間テーブルのような役割を果たしている。

以下はschema.rb内のactive_storage_attachmentsに関する記述。

create_table "active_storage_attachments", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8", force: :cascade do |t|
    t.string "name", null: false
    t.string "record_type", null: false
    t.bigint "record_id", null: false
    t.bigint "blob_id", null: false
    t.datetime "created_at", null: false
    t.index ["blob_id"], name: "index_active_storage_attachments_on_blob_id"
    t.index ["record_type", "record_id", "name", "blob_id"], name: "index_active_storage_attachments_uniqueness", unique: true
  end

active_storage_blobsが「blob_id」という名前の外部キーとして保持されている。
一方、「record_id」というのがモデル側と紐づくための外部キーだが、同時に「record_type」には紐づくモデルのクラス名が入り、実際はこの2つのキーでレコードを一意のものにしている(と思う)。

こちらはマイグレーションファイルの記述。

create_table :active_storage_attachments do |t|
      t.string     :name,     null: false
      t.references :record,   null: false, polymorphic: true, index: false
      t.references :blob,     null: false

      t.datetime :created_at, null: false

      t.index [ :record_type, :record_id, :name, :blob_id ], name: "index_active_storage_attachments_uniqueness", unique: true
      t.foreign_key :active_storage_blobs, column: :blob_id
end

「record_id」には「polymorphic: true」のオプションがついている。これが、ポリモーフィック関連付け。ガイドにも以下記載がある。

active_storage_attachmentsは、使うモデルのクラス名を保存するポリモーフィックjoinテーブルです。モデルのクラス名を変更した場合は、このテーブルに対してマイグレーションを実行して背後のrecord_typeをモデルの新しいクラス名に更新する必要があります。
Active Storage の概要 - Railsガイド

ポリモーフィック関連付けとは

railsガイドはものすごくとっつきにくい時もあるが、ポリモーフィック関連付けに関してはガイドを読むのが一番わかり易い。

ポリモーフィック関連付けを使うと、ある1つのモデルが他の複数のモデルに属していることを、1つの関連付けだけで表現できます。たとえば、写真(picture)モデルがあり、このモデルを従業員(employee)モデルと製品(product)モデルの両方に従属させたいとします。この場合は以下のように宣言します。

class Picture < ApplicationRecord
  belongs_to :imageable, polymorphic: true
end

class Employee < ApplicationRecord
  has_many :pictures, as: :imageable
end

class Product < ApplicationRecord
  has_many :pictures, as: :imageable
end

ポリモーフィックなbelongs_toは、他のあらゆるモデルから利用できる、(デザインパターンで言うところの)インターフェイスを設定する宣言とみなすこともできます。@employee.picturesとすると、写真のコレクションをEmployeeモデルのインスタンスから取得できます。

同様に、@product.picturesとすれば写真のコレクションをProductモデルのインスタンスから取得できます。
Active Record の関連付け - Railsガイド

というわけなので、Active Storageを導入すれば関連付けられるモデルは一つとは限らない。先程の通り、active_storage_attachmentsが中間テーブル的な役割をし、モデル名とそのidを保持しつつ、一方でそれに結びつくファイルのid(=blob_id)も保持している。ファイル保存先のテーブルは一つだが、このような仕組みで各レコードが一意になっている。


ActionTextとActive Storageの関係性

なんでこんなにActive Storageを紐解いたかと言うと、コンソールでActionTextクラスを見てみると以下のような記述がある。

[1] pry(main)> show-source ActionText::RichText

From: /usr/local/bundle/gems/actiontext-6.0.3.4/app/models/action_text/rich_text.rb:4
Class name: ActionText::RichText
Number of monkeypatches: 5. Use the `-a` option to display all available monkeypatches
Number of lines: 19

class RichText < ActiveRecord::Base
 # 中略
  belongs_to :record, polymorphic: true, touch: true
  has_many_attached :embeds
  # 以下略

つまり、ActionTextもポリモーフィック関連付けがされており、またActive Storageも紐付いている。そしてテーブル間でそれぞれ保持している情報も微妙に変わってくる。

ActionTextをインストールすると作られるテーブルは「action_text_rich_texts」だ。
マイグレーションファイルは以下の通り。

create_table :action_text_rich_texts do |t|
      t.string     :name, null: false
      t.text       :body, size: :long
      t.references :record, null: false, polymorphic: true, index: false

      t.timestamps

      t.index [ :record_type, :record_id, :name ], name: "index_action_text_rich_texts_uniqueness", unique: true
end

「body」カラムにはリッチテキストフィールドに入力された文字の内容(添付ファイル以外)が保存される。
そして、先程の「active_storage_attachments」テーブルと同じく「record_id」カラムと「record_type」カラムを持つので、外部キーとして紐付いたモデル名とそのidも保持することができる。

その一方で、今回ActionTextに従属的に紐付けられたactive_storage_attachmentsテーブルにおいて、「record_id」カラムと「record_type」カラムの役割は以下のようになる。

  • 「record_id」カラム:保存されるidは「action_text_rich_texts」のidになる。
  • 「record_type」カラム:保存されるモデル名は「ActionText::RichText」になる。

引き続きactive_storage_attachmentsテーブルは「blob_id」を外部キーとして持つので、リッチテキストフィールド内に添付されたファイルと結びつくことができる。


まとめ

個人の勝手な印象ではあるが、Active Storageだけで運用していたときは各モデルと直接結びついていたactive_storage_attachmentsテーブルが、Action Textに内包される(?)ことでaction_text_rich_textモデルとしか結びつくことができず、action_text_rich_textモデルとactive_storage_blobsモデルをつなげるだけの存在になってしまっているのが面白い。

また、ここらへんの詳しい解説を以下講座(参照欄)ではしてくださっているのだが、最初に見たときは全然意味がわからなかった。
自分でActionTextを導入し、テーブルなどいろいろ触れてみることでようやく各テーブルの関係性がわかってきた。

いろんな知識を上からじゃんじゃん入れていくのも大事だが、こうやって着実にわかることを増やしてくのも重要だし、「よくわからなかったものが理解できた」という状態って精神衛生上ものすごく良い体験だと思う。


(おまけ)ER図

空間把握能力なさすぎてER図書くのがしんどいししんどい思いして書いた割にはヘタクソ

f:id:yuki_526:20210102201843p:plain

作成中アプリの一部を切り出しました。



Udemyのすてきな講座でActionTextをかじってみた【rails】

先日、techcampの同じチームの方にUdemyでブラックフライデーセールが行われていることを教えてもらい、初めてUdemyで講座を買って利用してみた。
Online Courses - Learn Anything, On Your Schedule | Udemy
1万とか2万とかする講座が1200円とか言われたら買ってしまう。
ちなみにtechcampはオリジナルアプリをやらないといけないのだが、この1週間はおまけカリキュラムみたいなのをやってお茶をにごしていた。

はじめてのUdemy

私も名前くらいは知っていた(うめでぃかと思っていた。汗)
何よりも素晴らしいのは、動画の倍速再生ができること。等倍だとどうしても眠くなってしまう。寝てしまう。でも2倍にしておけば、一瞬気を抜いたらもう置いていかれる緊張感がある。
人気講師の方は、内容はもちろん大変良いんだろうけど倍速にした時の声や話し方の聞きやすさも重要な要素として支持されているのだと思う。
調子に乗って6講座も買ってしまった…

こちらはご飯食べるときとか休憩の時に見ている。とってもわかりやすい。www.udemy.com


こちらはもう完全な趣味。実は1年前くらいにほんとにちょっとだけディープラーニングをかじったことがある。(というかひとなめしたくらい)
実はKaggleのアカウント持ってる。
やりたいんだけど趣味やってる場合じゃないなって気する…

www.udemy.com


【はむ式】ハンズオンで学ぶRuby on Rails 6 < Action Textを支えるデータベースアソシエーション編 >

私の記念すべき初めての修了講座@Udemyはこちら。

www.udemy.com

railsの講座で、0からやる系じゃなくて一歩進んだようなやつないかな?と探していたところ発見。
とってもよかった。
本当は、せっかくこの講座で学んだことを色々整理してアウトプットをしたいんだけど、なにせ今この講座についてああだこうだ語れるほどベース知識がない。この状態で内容を語ろうとするのはもったいないというか、時期尚早感が否めないので、とりあえずドしろうと感想を書き残しておきたい。
おそらく今後2周目とかすると思うので、そのときはこの講座にふさわしい、もうちょっと実のあるアウトプットができるようになっていたい。

①ActionTextで簡単にリッチテキスト形式の編集画面が実装できる。

これの実装自体は本当に難しいことはなく、とっても手軽だったので驚いた。もちろん背後の仕組みを理解したり、バリデーションの設定とか色々別問題はあるのだが、導入自体はかんたん。すごい。使ってみたくなってしまう。
Trixエディタという、オープンソースWYSIWYGエディタを使っているらしい。
trix-editor.org

②dockerやvimに初めて触れた

名前は知っているけどいまいちどういうものか未だにピンとこないdocker。
エディタなんだろうなということくらいしか知らなかったvim
自分だけの独りよがり勉強ではなかなか触れる機会がないだろうものたちに触れて、実際に使ってみることができた。
受講の必須条件がdockerを使える環境であること、だったけど1ミリも使ったことなかったので大急ぎで以下参考にさせていただきちょっとかじった。
datawokagaku.com
概念や、その有用性をしっかり理解するのは今の自分には難しい。
でもこの講座をスムーズに受講できたのは間違いなくdockerのおかげなのはわかる。だいたい何事もまず環境構築でつまづくもんね。
それが、講師の方が用意してくださったソースコードをgitからもってきてdocker-compose upしたらもう完了していた…(もっと細かい手順が色々あるけど、イメージそんな感じ)
ほんとうにすごい。

vimは、ほんとにしろうとの感想で恐縮だけどターミナルでコード書けるってすごい。
本講座はずっとターミナル上でファイル操作はもちろんソースコードの編集とかも行っていくので、ターミナルに全然慣れていない自分にはとても勉強になった。
GitさえGit Desktopに頼りがちだったし…
今回はなるべく動画と同じくターミナルだけでやっていこうと思って色々調べながらやったので、少しは慣れたのではないかと思う。どうしてもようわからん状態になったときはGit・dockerのデスクトップ版やVScodeの力は借りたけど。

③モデルの相関やバリデーションのところが面白かった

ActionTextの導入はあっさり完了するけど、その後の各モデルの話やバリデーションの設定の仕方のところがとても面白かった。ただここが本当にしろうと感イッパイというか、自分は「お〜すげえ〜そうなっているのか〜おもしれ〜」くらいの平べったい感想しかだせない。
おそらくここらへんがこの講座の肝というか、本当に面白いところだと思っていて、その面白さは体感できたけど、「あれがああ言う仕組みだからああなってこういうふうになるんだな」みたいな知見ありそうな感想が言えない。はがゆい。
今の激浅知識で知ったかぶりを語るのはもったいないし失礼。
でも本当に面白かったし、バリデーションの記述とか自体はそんなに複雑じゃなくて、やっぱりやり方を知ってるかどうかってことが重要だなと思った。

④倍速でも聞きやすく、ずっと画面を映してくださっているので自分のやっていることが迷子にならずに済んだ

とっても聞きやすい声で、話し方やトーンもとっても良かった。なんか癖のある話し方の方とかだと、そっちに気がいってしまい集中できないし…
あとは終始講師の方が実行している画面を映してくださるのがとてもよかった。
個人的な感想だけど、実行画面と解説のパワポとかがごちゃまぜでくるのはどうしても集中が途切れてしまうし、「あれ?なんか画面に映してないところでやって当然の準備みたいなのやった??同じ画面じゃないんですけど」みたいなことが発生することがある。
流れをずっと映し続けてくださったほうが、自分がどこでしくじったとかも見つけやすい。

最終的な感想:受講して本当によかった。

なんか、ブラックフライデーだからってあんな安いお値段で買ってしまって本当に良かったんだろうか?と申し訳なくなるくらい良い講座にであえてよかった。ボリュームもちょうどよかったし、railsの基礎の基礎を勉強して、次何をやってみようか?ってウロウロしていた自分にはとてもありがたい講座だった。
1度とおして観たし、今度からご飯食べてるときとかに流すだけ流して復習するのも全然ありだ。
そんでもうちょっと自分が色々知識身につけて語るに足る人間になったら、今度はちゃんと”学んだ内容をアウトプット”したい。

あーもう楽しくてしょうがない。


30日目の状況(+34日目に最終課題完了するところまで)

30日。

techcampが始まってちょうど30日がたった。
ということはRubyRailsの勉強を始めてしてちょうど30日か。
30日一心不乱に勉強してしっかり身につけました!と、胸を張って言えるほどの知識量を得たのかはわからない。一心不乱にやったのかと聞かれるとそうだったときとそうじゃなかったときもあるし…

でも教材が後半になってきて、ほとんど自分で手を動かしていくフェーズになってからものすごく楽しい。その前も楽しかったけどやっぱりせっせとコード書いてあれやこれやとやってるのは楽しい。

別に楽しさを得るために勉強を始めたわけではないが、自分のモチベーションや集中力やその他諸々勉強に必要そうな気持ちはすべて楽しいとか明るいとか嬉しいとかポジティブ感情によって強く影響されるっていうのは改めてわかったので、楽しいに越したことはない。
そう、楽しい。
これがやりたかった。ずっと勉強でもいい。
そういうわけにも行かないんだけど…

現状の自分見つめ直す系記事。

切りが良いので30日目の勉強の状況や気持ちを書き残しておく。
というか、正直言うと、最終課題は機能ごとにコードレビューを受けないといけないのだが、昨日依頼をだした分のフィードバックがまだ返ってこない。

Gitを使った開発疑似体験で機能ごとに一人でブランチ切って実装進めていて、コードレビューOKでないとマージできない。
今次の機能を実装しようとして新たにmasterからブランチ切ると今コードレビュー依頼出している分と激しくコンフリクトが起きてしまう…
ということで二の足を踏んでいる。

一昨日まではレビュー依頼して30分くらいで返ってきてたのになあ…せっかくやる気あったのに…
コンフリクト覚悟でやるか、逆にディレクトリごとコピーして追加したやつだけ置き換えするとか、手はあるかもしれないが、どうもそれらをする気が起きない。

だからこれを書き残す時間にする。今の自分見つめる系記事。

(ちなみに同じチームの方は私とおんなじくらいのタイミングでレビュー依頼出しまくったそうだが普通に20分くらいで返ってきているらしい。私が出したコードはそんなに時間かかるほどやべーやつなんだろうか?笑)

tech campに関わる取り組み

勉強時間で言えば、シンプルに人生で今一番勉強していると言ってもいいくらいしている。逆に今までがしていなかったのか?
決められた時間割が10:00~20:00。それ以外でも8時とか9時くらいからは何らかのtechcampの勉強とかしている。20時以降は何もしていないが。
週末も近所のスタバに7時半くらいにMacをかついでいそいそと通い、AirPods(風のパチもんイヤホン)で思い切りイキり倒している。ただし真剣に勉強に取り組んでいるかは別(ANNとか聴いてる)

教材は進み具合で基礎→応用→発展→最終課題とフェーズ分けされていて、各フェーズをいつまでにやりましょうというスケジュールが決まっている。もちろん巻きでやってもOK。自分はとりあえず巻ける分は巻いとこうという感じで予定よりは2週間早く最終課題フェーズまで来た。
ただしもっと高速で進んでる方もいるので、スピード感でいうとそこまで早い感じでも無いかもしれない。

基礎〜発展までは主に何かしらの小さいwebアプリを作りながらrubyrailsを勉強していく。途中からはほぼrailsかな。rubyは基本をおさえただけって感じ。それが主軸の教材という感じで、それ以外にも副菜というか読み物コンテンツ的な感じでセキュリティとか、SQLとか、開発体制とかもさらっとかじれるようにはなっている。
さらっとだから自分でちゃんと勉強はしないといけない。

ただ、スクールがカリキュラムとしてそのあたりも含んでいることを示している以上、それも「勉強しました」っていうことにはなるので、足りないと思うなら自分でどうにかしないといけない。当たり前だけど、やっぱりコードが書けるだけじゃ仕事はできない…そりゃそうだ。

最終課題完了しました。

ここまで書いたところで、どうにも自分の出したレビュー依頼がロストしている?か、無視されている?か、とりあえず何らかの理由で依頼を出してないことになっている気がして(ちゃんとまじで送信ボタン押したのは確認した)、しれっともう一度出してみたら普通に20分くらいで返ってきた。
なので、そのまま作業を継続して、その4日後、34日目に最終課題の機能実装は完了しました。とりあえず嬉しい。
ちょっと前まではチームでメルカリクローンを作ろうみたいな課題だったらしい?ですが、今は個人実装になっている。メルカリクローンと言えるほどの機能も無いフリマ的アプリ。

とはいえ、実際のところフロントの実装はほぼ全ページHTMLとCSSがすでにいい感じに組まれていて、各機能(商品出品とか購入とか情報編集とか)サーバーサイドの処理を今までカリキュラムで作り倒してきたものを今一度作ろうという感じでした。
うん。作るのに1周間はかかっているのでチョロかったとかそういうなめたことはまだまだとても言えないような知識レベルだけど、これを転職活動のポートフォリオにいれたらあかんというのはわかる。そこはこれから考えよう。

よかったところ

最終課題の取り組みでよかったところ。

①最初から最後まで楽しい気持ちでできた。

自分にとってはすごく重要なことなのでいちばんに書きました

②やりたいことを実現するために調査する力

こういう処理を書きたい、こういうふうに表示させたい、それらのやり方がわからない。でもきっと便利なメソッドとかrailsのしくみとかなにかあるはず。探そう。
最終課題とか、おそらく今後作るであろうものたちもそうだけど、だいたいの取り組みはこれに尽きるのじゃないかと思う。今だったら、教材で探すのもそうだしネットでググるのも同じ。今までやってきたこと全部全て頭にいれるのは難しいし、今後も新しいことはどんどん増えていく世界だから、自分の知識が100%になることは永遠にない。だから、必要な方法を見つけるとか、新しい情報をキャッチアップ?的なことをしていく力が大事なんじゃないかと思えた。
ちなみに「調査する力(どや)」とかいってさも身についたみたいな書き方しているが、まだ全然。もっと継続が必要。でもそれに気づけたからまずはOKっしょ

③安定したメンタル

エラーが出ても慌てない。動じない。悲しまない。怒らない。
エラーを解決してすぐ次のエラーが出てもはらわた煮えくり返さない。
正直、そこまであの赤いエラー画面としょっちゅう出会ったという感じではなかった。どちらかといえば、粛々と処理は動き、画面も遷移するんだけど、期待通りの動きをしていない、なんかおかしいぞ、みたいな方が多かった。
そんなときも、落ち込まない。いろいろ調べたり、ああだこうだ試したり、そういうのを暗い気持ちや怒りの気持ちでやらない。
マイナス感情で溢れてしまうとますます注意力が散漫になってドツボにはまる。
今回はそういう気持ちのマネジメントというか、気持ちの持っていき方がうまく行った1週間だった。
①とそんなかわらないか。

④スケジュールより早めに終了できた

スケジュールだと最終課題提出は12/18締切なので、ある程度余裕を持ってここまで来られた。逆に言うとこのあとの時間をどうするか?は結構自分に委ねられている感はある。期間が70日なので、およそあと半分。

⑤なるべくシンプルで読みやすく書けた

そもそもそんな複雑なことをしていないというのもあるし、rubyrailsが便利なメソッドとか色々あるのも相まっての結果ではあるが、今見ても割と見やすいコードが書けた。
なりふり構わず希望の処理結果を得るために書くような無茶苦茶なコードもないし、インデントとかコメントもちゃんとやったつもり。
当たり前のことなんだけど、今後もちゃんと意識してやる。

よくなかったところ

①公式ドキュメントと友だちになれなかった

顔見知りくらいにはなれた???
うーん。。。
教材でもそう言われているので、とりあえず調べ物するときはまず公式ドキュメントにあたるようにした。前よりは多少読みやすくというか、とっつきやすくというか、慣れてきた気がしなくもないが、それだけを見てハイじゃあこれですねコードを書きましょう。というふうになるのはまだまだ難しい。
とにかく1発目は公式にあたるということを今後も徹底して、慣れていくしかない。

②首をめっちゃ痛めた

今も痛いから書いておくけどめっちゃ痛い

③「同じ処理の記述をしているところが複数箇所ある」→「じゃあ切り出そう!」という考えがない

何回かコードレビューで指摘をいただいた。
切り出したほうが、可読性?とか、保守性?のためにはいいんだろう(専門用語ぽくていいね。)やったほうがいい。
そもそもそれを思いつくというか、そういう発想がまったくない。どうやったらそういう発想がある人になれるんだろう?
これも「慣れるしかない」と書けばそれまでなんだろうけど、すべての課題を「慣れ」という解決策に頼っても仕方がない。自発的にどういうことができるか…うーん。
ある程度いろんな機能の処理を書きすすめたら、おんなじようなところがないかチェックするようにしたらいいのかなあ。

④herokuにデプロイしたアプリのDBの中身見る方法がよくわからない

一回調べたんだけどちょっとよくわからなかった。もっかい調べよう。


よくなかったところはよかったとこより少なめにしておこう。


今後

次やることとしては、今作ったフリマのアプリに追加実装をするか、新しくオリジナルアプリを作るかという道があって、オリジナルの方だと、メンターに質問もできないしコードレビューも受け付けてもらえないらしい。
質問は正直ほぼ使っていないのでいいけど、レビューもしてもらえないのか。まあ、個々で違うの作るから大変か。
とりあえずtechcampの感想は全部終わって色々振り返ってから書くとして、明日から何をするか?

たぶんオリジナルアプリをやるんだろう。
でもその前にボーナスカリキュラム(笑)というか、追加実装とかで参考にしてくださいみたいな教材がいくつかあるのでとりあえずそれをこなしつつオリジナルのブツの方を考えないといけない。
むしろここからが本番、いまスタートラインにようやく立ちかけているのではないか。

きっと今後のほうがバンバンエラーにぶつかるだろうからバンバン残そう。



deviseのユーザー情報編集でcurrent_user.updateという記述にハマる

今わかっていることをまとめておく。

この件は、教材の流れには直接関係ないし、エラーが出て詰まったわけでもない。
結果だけ先に書くと、結局何が正しいのかもまだちゃんとわかっていない。
でもすごく気になって、ここ数日ずっと考えたり調べたりしていたので
とりあえず今のところ理解したことを書いておく。
未来の自分がこの疑問をスッキリ解決してくれるといいのだけど…

deviseでユーザー情報更新機能を実装する

deviseのユーザー管理機能実装の教材で、ユーザー情報更新機能を作りましょうとなったとき。自分の頭の中ではなんとなく

①DBから今ログインしているユーザー情報を持ってくる
 (Users#newアクションで)
②それを表示させたユーザー情報編集画面を作る
 (views/users/edit.html./erb)
③DBから今ログインしているユーザー情報を持ってきて、
 編集画面のフォームから流れてきた情報を上書きしてupdateし、トップページにリダイレクトする。
 (Usesr#updateアクションで)

という流れを想像した。そこで、
とりあえず③のupdateアクションに関して以下のように記載。
(※後でわかったんですが以下の記述は.valid?の使い方が変でした。汗
今回はここかメインの話じゃないので詳細は記載しません)

  def update
    user = User.find_by(params[:id])
    user.update(user_params)
    if current_user.valid?
       redirect_to root_path
    else
      render :edit
    end

#中略

  private
  def user_params
    params.require(:user).permit(:name, :email)
  end


editのビューファイルは内容記述済の用意されていたものを
教材に言われるがままディレクトリにポンと入れただけなので、
この話は一旦おいておく。

上記のコードは一応想定通りの動きはしてくれた。
(教材で実装中のアプリのためスクショは載せられないが…)

ただ、教材の記載としては以下のような感じだった。
(そのまま載せられないので一部変えた。)

def update
    if current_user.update(user_params)
      redirect_to root_path
    else
      render :edit
    end
  end

  private

  def user_params
    params.require(:user).permit(:name, :email)
  end


「current_user.update」??

最初にこの記載を見たとき、全然ピンとこなかった。
「current_userって今ログインしているユーザーの情報が入っていて、
それにupdateをかける…???」っていう状態。
なんかたぶん、自分の中でcurrent_userの機能ってログインした瞬間とかにユーザー情報が入って、ログアウトしたらそれが消えるみたいな、
セッションみたいなもの?を想像していたからかもしれない。
current_userっていう変数に、ユーザー情報を格納している的なイメージだった。

current_userって一体何なのか?

まずそもそもそれを全然理解していなかった。
以下参考にさせていただきました。
tech.mof-mof.co.jp

def current_#{mapping}
 @current_#{mapping} ||= warden.authenticate(scope: :#{mapping})
end

current_userはdeviseのヘルパーメソッドで、Userモデルのインスタンスが戻り値になるらしい。
インスタンスだったのか。
上記がこのメソッドの中身だが、認証してDBにアクセスしてデータを持ってきている?みたいな動きなんだろうか。


それであれば、自分がやりたかったことが

今のユーザー情報をDBから持ってくる
   ↓
フォームの送信してきた内容で上書きする
   ↓
その内容をDBに保存する

なので、教材の見本通りにcurrent_user.updateはやっていることは同じだし、こっちのほうが短い&ひと目でわかりやすいコードだと思うのだが…

気になる記事

"current_user.update"でググると一番上に出てくるこちら。
diary.shu-cream.net
この件で調べ始めて一番最初に出会った記事。

フォームで変更するオブジェクトが、フォーム以外の場所でも表示されている場合、フォームで変更するオブジェクトと表示するオブジェクトは分けておかないと意図していない挙動になるから注意しましょう。

とのこと。なるほど。
一応自分で、views/users/edit.html./erbにcurrent_userを表示させるようにして
更新後のリダイレクト先を編集ページのままにしたりしていろいろ試したが、
current_userの中身を不正な状態にはできていなさそうで再現はできなかった。

それと、そもそもこの「current_user.update」というソースコード自体が
あまり検索に引っかからないような感じがして、
実はもっといい感じの書き方があるのだろうか、とかそもそもdeviseのユーザー編集ってそんなに話題にならないのかなとか(パスワードなしで編集する、ための記事はたくさんあった)いろんなことを考えてしまった。


でもこれのせいで教材の進みもだいぶ鈍足になったし復習する時間もほしいのにこれの検索ばっか何時間もしてるしでここ数日とにかく効率が悪かった。
まだrubyrails初めて3週間とかだし、いろんなことに興味があったり疑問を持つのも大事だろうけど
まずはしっかり教材進めて慣れていくためにもある程度割り切ってガンガン進めなきゃってすごく思う。
でも気になると延々調べてしまう。今みたいなベース知識が無の状態であちこち調べ回っても納得できる答えに出会えないのは理解しているんだけど。


性分だなあ。
それがいい時に働くときもあるだろうけど、今は変えたほうがいい性分だと思う。

ファイル名ミスのエラーに気が付きにくい【rails】

エラーまとめの方に書こうと思ったけど、
意外と長くなったので独立させておくことにした。
ykpg.hatenadiary.com

同じミスと同じ解決方法を2回たどったのでメモっておく。


ファイル名誤りで発生するエラー。
系統で言えば①スペルミスの一部ではあるが、
コードではなくファイル名でこれをやると余計ややこしい。
とくにrailsでルーティングしてコントローラー生成して
自分でビューファイルを作成する時に間違えてしまう。
拡張子。.hrml.erbになってた。2回とも。

f:id:yuki_526:20201104092309p:plain
そしてこういうものすごくよくわからないエラーが出る。
missing a templateってなに。


大体コードのどこかが記述おかしいとかであれば、
エラーは詳細に出してくれるし行数とかも書いてあるしで原因を見つけやすい。
そういう感じの具体的なエラーじゃないときは、
なんか各機能のつながりがおかしいのかなとか
サーバーがおかしいのかなとか色々想像をしつつ、
とりあえずサーバー再起動してみたり
コードの中の「コードとしての記述(文法?構文っていうのか?)はおかしくないが
その表記(中身)は今適切じゃない」みたいな部分を探す。
引っ張ってきているurlがちょっと違うとか、
テーブルから名前持ってきたいのにメアド持ってきてるとか
後々ページ遷移してから影響が出ておかしくなってることもあるので
そういうのをコード舐め回すように見て探す。


それでもおかしいところ見つからないなあってなって初めて
ディレクトリがいい感じになっているかどうか
ファイルが変なところに作成されてないかとかみて
その時にやっとへんなファイル名に気づいたりする。


そこにたどり着くまで冷静沈着な試合運びができていたらいいのだが、
コードをねぶり回すように見てもエラーが見つからず
それを関係ありそうな全ファイルでやっているとだんだんイライラしたり追い込まれたり
「こんなに見てるのに間違いないじゃん!!なんでエラー出るんだよ!!」
「うわーーん見つからないよー」
なんて情緒が不安定になったりしちゃうこともある。
その分たった1文字のミスを見つけたときはものすごく拍子抜けするし、
なんでもっと気をつけてファイル名記載しなかったんだろう?って後悔する。


とくにちょっと慣れてきた動作のほうが心が油断するので
こういうの起きやすいと思う。
でものちのちのエラーの苦しみを考えたら、
ちゃんと1つ1つ真剣にやらなきゃだめだ。


ちなみに最近になってコントローラーとビューはコマンドで同時作成できると知ったので
hrmlにはもう遭遇しない…といいな。

今日もがんばろう



KeyError: Factory not registered:のエラーが出た件【FactoryBot】

FactoryBotとは。

https://github.com/thoughtbot/factory_bot_rails/blob/master/README.md

テストコードを書く時に、予め各クラスのインスタンスの値を設定しておいて
テストコードから呼び出せる仕組み。
railsのユーザー登録の設定だったら、FactoryBotのGemをインストールした上で

spec/factories/users.rb

FactoryBot.define do
  factory :user do
    name         {"testname"}
    email        {"test@test.com"}
    password     {"test1234"}
  end
end

こんな感じで設定しておけば、テストコードでそのレコードを呼び出せる。

FactoryBot.build(:user)

Newじゃなくてbuildを使う。

よくわからないエラーが現れる【KeyError】


今回は上記のとおりFactoryBotの値を設定してとりあえずコンソールで呼び出してみた。

[1] pry(main)> FactoryBot.build(:user)

そしたら謎のエラーが…

KeyError: Factory not registered: "user"
from /Users/home/.rbenv/versions/2.6.5/lib/ruby/gems/2.6.0/gems/activesupport-6.0.3.2/lib/active_support/hash_with_indifferent_access.rb:191:in `fetch'
Caused by KeyError: key not found: "user"
from /Users/home/.rbenv/versions/2.6.5/lib/ruby/gems/2.6.0/gems/activesupport-6.0.3.2/lib/active_support/hash_with_indifferent_access.rb:191:in `fetch'

エラーメッセージを読んでみてもよくわからない。KeyErrorって初めて見たし…
まずは各ファイル内にいつものタイポや記述ミスが無いか目を凝らして確認したが、
合っていそうな感じ。
ということは私の人為的ミスではなく、手の及んでいない裏方でなにかあるのかも?と思い
コンソールの再起動をしたりしてみたけれどうまく行かず。
見たことあるエラーだったらなんとなくあたりをつけて探れるけど、
どうにもこうにも行かないのでエラーメッセージでググってみた。

解決:spring stop


以下参考にさせていただきました。
masawada.hatenablog.jpqiita.com

なんじゃspringって??

Spring is a Rails application preloader. It speeds up development by keeping your application running in the background so you don't need to boot it every time you run a test, rake task or migration.
引用元:https://github.com/rails/spring


アプリを常にバックグラウンドで走らせておいて、
いちいち起動しなくて済むから速いぜみたいな感じのやつかな。
結局このエラーが出たときも、なあーんにも考えず教材用アプリをクローンしてきただけなので
springが入ってるかどうかとかも知らなかった。
入ってた。

一旦コンソールを抜け出して、以下コマンドを試す。

% spring stop
Spring stopped.

止まった。動いてたのも知らなかったやつ止めた。
この状態でコンソールをもう一度起動する。

% rails c
Running via Spring preloader in process 32415
Loading development environment (Rails 6.0.3.2)
[1] pry(main)>

あれ??springも再起動しちゃってない??
(後で調べたらstopしたspringはrailsコマンドを使うと再起動するらしい)

まいいや。もう一度FactoryBotをbuildする。

[1] pry(main)> FactoryBot.build(:user)
   (14.6ms)  SET NAMES utf8,  @@SESSION.sql_mode = CONCAT(CONCAT(@@sql_mode, ',STRICT_ALL_TABLES'), ',NO_AUTO_VALUE_ON_ZERO'),  @@SESSION.sql_auto_is_null = 0, @@SESSION.wait_timeout = 2147483
=> #<User id: nil, email: "test@test.com", created_at: nil, updated_at: nil, name: "testname">

うわーんできた。めっちゃ安心した。ドツボにはまるかと思った。
springのせいでrailsのコマンドとかが動かないってたまにあるみたい。

自分のコードが間違っているならまだ解決方法はあるとして、
こういうふうに全く予見できないエラーとか起きると初見だとほんとに詰む。
とはいえスーパー初心者の自分が遭遇するようなエラーって
だいたいいろんな人が経験しているはずで
今まで同じエラーで苦しんだ方々の解決法が調べれば出てくるはずなので
調べる力も大事、かついろんなエラーに遭遇しておくのも大事。

そしてこんな感じで蓄積しておくのも大事。


いつでも起こりそうな普遍的なエラーの原因を並べておく【随時追記】【自分戒め用】

今はrubyを勉強しているので主にrubyの話になるが、
どの言語でも同じようなことはありうるというか
結局元をたどるとパーソナリティのせいというか
ズボラな面が顔を見せがちなエラーを並べておいて
たまに見返してウッってなって
同じ過ちを繰り返さないように心を正したい。

①シンプルにスペルをミスる

焦ってタイピングして同じ文字が並んじゃってたり
(userがuseeerになっているとかしゃれにならんレベルの)、
ふつうの英単語とかでさえミスっていたりする
(requireがrequeireとかになってて気が付かなくて1時間位ハマった)。
これでも今までの仕事では英語を使うこともあったので、
目とかは英語になれてるはず。(目とか?)
なのにコード上になるとしょっちゅうスペルをミスっている。なんでだろう。

今まで勉強してきたはずの英語の記憶が
コーディングの土俵に立つとすべてリセットされるのだろうか?
これまで培ってきたものをまっさらにして取り組もうみたいな気持ちが
なんだか空回りして悪い方にいっちゃっているのだろうか?
いずれにせよケアレスミスに間違いないので慎重に書くようにする。

②メソッド閉じ忘れ

rubyはメソッドを定義するとき
def メソッド名〜 で始まり 〜end で終わる。

def greeting
  puts "Hello"
end

よくendを忘れる。
よくこのエラー画面と目が合う。

syntax error, unexpected end-of-input, expecting end


言い訳じゃないけど昔ちょっとかじったJavaのメソッドは{}で囲んでた。

public static void greeting(){
    System.out.println("Hello");
  }

テキストエディタってとってもかしこいので、波括弧のはじまり{ を打つと
勝手に }もくっつけてくれる。
中身だけ書いておけばいい。
defはdefって書いても勝手にendはくっつかない。
メソッドの終わりにeを打つとendが出て来て、控えめだけど気が利くみたいな顔で
インデントを一歩前にさっと動いてくれるのはありがたい。けど、
そもそもendを書く習慣が身についてない。
多分Javaの }を書く習慣も身についてなかったんだろうな。(;は必死で書く)


【追記】
VScodeでendを補完してくれる拡張機能見つけた。
endwise - Visual Studio Marketplace
ないほうが、ちゃんとendを書く癖が付きそうな気もするけど、
今後もexpecting endとよく出会いそうな気もするのでとりあえず有効化しておく。
知識としてendをつけるっていうのはわかっているからいいや。
それ以外のエラーとも死ぬほどやり合うことになるのと思うので
エディタに頼って潰せるものは潰しておく。


③:のつけ忘れ

これはただ単純にシンボルの使い所をちゃんと理解できていない。
hoge: :hogeとか並ぶとすごい違和感感じている時点で理解できてないんだろう。
ハッシュと最初に接したときくらいはまだ理解できていた(はず)。
文字列っぽく扱えるけど数値だから処理速い的なことらしく、
そのへんのフレキシブルな感じがrubyすげえ!って思った覚えがある。
Javaで最後の方にちらっとenumを使ったときの感動に似ている。
え?めっちゃ日本語なのに””で囲まなくていいの?っていう)

それがrailsやっててもシンボルっぽいのがバンバン出てきて
それがシンボルなのか何なのかもよくわからなくなってきてしまっている。
今書いているコードはモデル名なのか?コントローラーなのか?
テーブルの名前なのか?カラム名なのか?アクションなのか?メソッドなのか?
そこらへんがいつも迷子。

まずはちゃんとシンボルを押さえるところからやるか。