- 投稿日:2022-02-01T23:58:04+09:00
グループ招待機能の実装方法
※ 初めて書いた記事ですので、間違い等あればご指摘いただけますと幸いです。 概要 通知機能を利用して、チーム機能に招待機能を実装する。 前提 ユーザー機能を実装済み チーム機能を実装済み 通知機能を実装済み 参考記事 通知機能はこちらの記事を参考させていただきました。 【Rails】通知機能を誰でも実装できるように解説する【いいね、コメント、フォロー】 通知モデルの概要 以下、テーブル情報の例です。 visitor_id visited_id post_id comment_id action checked 1 2 nil nil follow false 1 2 3 nil like false 1 2 nil nil comment false visitor_id:通知を送ったユーザーのID visited_id:通知を送られた(受け取る)ユーザーのID post_id:いいねされた投稿 comment_id:投稿へのコメントID action:通知の種類(ここではfollow,like,comment) checked:通知を確認したかどうか この記事の招待機能の概要 チームに加入できるリンクが付いている通知を送る。 visitor_id visited_id team_id action checked 1 2 1 invitaion false 上記のようなテーブル情報を持つ通知を生成し、通知一覧に表示させる。 通知に記載されているリンクを踏むと招待されたチームに加入する。 このような流れでチームへの招待を実装します。 実装の手順 ルーティングを設定。(config/routes.rb) 通知生成用のメソッドを作成。(app/models) コントローラに処理を記述。(app/controllers) フォームを記述。(app/views) 本記事ではこの順番で実装を進めていきます。 実装 ルーティングの設定。 config/routes.rb resources :teams, only: [:index, :show, :new, :create, :edit, :update] do get :join post :invitation end 招待通知を生成するためのアクションinvitation。 チームに加入する処理を行うアクションjoin。 上記の2つのアクションのルーティングを記述します。 通知生成用のメソッドを作成。 app/models/team.rb def team_invitation_notification(current_user, visited_id, team_id) # すでに招待用の通知が送られているか検索。 temp = Notification.where(visitor_id: current_user.id, visited_id: visited_id, team_id: team_id) # 上記で検索した通知がない場合のみ、通知レコードを作成。 if temp.blank? notification = current_user.active_notifications.new( visited_id: visited_id, team_id: team_id, action: "invitation", ) # エラーがなければ、通知レコードを保存。 notification.save if notification.valid? end end end コントローラに処理を記述。 招待通知生成の処理を行うinvitationアクションを記述。 app/controllers/teams_controller.rb def invitation @team = Team.find(params[:id]) # この後 view に設置するフォームの値を参照する。 @user = User.find_by(id: params[:user_id]) notification = Notification.where(visited_id: @user.id, team_id: @team.id, action: "invitation") unless notification.exists? # それぞれの仮引数を置き換えて、team.rb に記述したメソッドを呼び出す。 @team.team_invitation_notification(current_user, @user.id, @team.id) # 遷移する前のURLを取得し、リダイレクトさせる。 redirect_to request.referer, notice: "招待を送りました。" else redirect_to request.referer, alert: "すでに招待しています。" end end チーム加入処理を行うjoinアクションを記述。 app/controllers/teams_controller.rb def join @team = Team.find(params[:id]) # @team.users に、current_user のレコードが含まれていなければ以下の処理を行う。 unless @team.users.include?(current_user) # @team.users に、current_user のレコードを追加する。 @team.users << current_user # 招待通知を検索して削除。 notification = Notification.find_by(visited_id: current_user.id, team_id: @team.id, action: "invitation") notification.destroy end redirect_to team_path(@team), notice: "チームに参加しました。" end フォームを記述。 app/views/teams/show.html.erb # @other_users は、チームに所属していないユーザーの値を取得している。 <% if @other_users.exists? %> <%= form_with model: @user, url: invitation_team_path(@team), method: :post, local: true do |f| %> <%= f.collection_select :user_id, @other_users, :id, :nickname %> <%= f.submit "招待する" %> <% end %> <% end %> 実際に表示される通知を記述。 actionカラムの値によって表示する内容を変えるようにしたいので、case文を使って表示を分岐させます。 app/views/notifications/_notification.html.erb <% case notification.action %> <% when "invitaion" %> <%= link_to "visitor.name", user_path(visitor) %> さんからチームへの招待が届きました。 # このリンクをクリックすると、チーム加入処理を行う join アクションが働く。 <%= link_to "参加する", team_join_path(notification.team_id) %> 以下の記述は、<% if notification.action == "invitation" %>と同義です。 <% case notification.action %> <% when "invitaion" %> 通知機能のように、分岐が多い場合にはcase文を用いる方が良いと思います。 実装の工程は以上です。 お疲れ様でした。 初学者が考えた程度のものですので、足りないところが多々あるかと存じますが、どなたかのお役に立てば幸いです。
- 投稿日:2022-02-01T23:26:50+09:00
strftimeメソッドの使い方
rubyシルバーを勉強していくなかで、strftimeメソッドについてわからなかったので、解説していこうとおもいます。 strftimeメソッドとは strftimeメソッドとは、Timeクラスのインスタンスメソッドなんです。 Timeクラスとは名前からも分かる通り、時刻を表すクラスです。 このメソッドをもし使わなかったら、どう時刻や日付を表現するのでしょうか(あくまで一例です) date = DateTime.now => Tue, 01 Feb 2022 22:36:33 +0900 でも、フロント画面にこう表示されたら、ちょっとイケてないですよね。 そこで登場するのが、strftimeメソッドなんですね。 実際に使ってみましょう date = Datetime.now date.strftime('%Y年%m月%d日') => "2022年02月01日" date.strftime('%Y年%m月%d日%H時%M分') => "2022年02月01日22時52分" 他にも使えるフォーマット文字列があるので、こちらの公式ドキュメントを参考にしてみてください 実際に問題を解いてみましょう 選択肢➀ %xは、%Dと同じなんです。じゃあ、%Dはどんな結果を出すかというと、%m/%d/%yを表現します。 %mは01~12で表し、日にちを表します。%dは、01~31で日付を表します。%yは西暦の下二桁を表します。今回の問題でいえば、15です。 これが正解になります。 選択肢➁ %m/%d/%Yを見ていきましょう。%mは、日にちですね。01~12を表します。%dは01~31を表します。ここまでは正解なのですが、%Yがアウトです。 %Yは西暦を表すんです。今回の問題でいえば、2015を表します。 そのため、この問題では不正解です。 選択肢➂ %mは、日にちですね。01~12を表します。%Dは「/月/日/年」を表現するので、この時点で、この選択肢は不正解ですね。%yは、西暦の下二桁を表します。今回の問題でいえば、15です。 選択肢➃ %Mは、分を表すので、この時点で不正解になります。 以上より、この問題の選択肢は➀が正解です 以上です。何か間違いがございましたら、ご教示いただけますと幸いです。 【参考文献】
- 投稿日:2022-02-01T22:19:54+09:00
<初心者向け>DB設計参考
初めて1からアプリを作成するにあたって、DB(データベース)設計のやり方がまったくわからなかったので、個人的にわかりやすく? まとめてみました! 「わかりづらかった・ここは違う・こうした方がいい」という部分があったら是非ご教授お願いしたいです!! 1:まずはエンティティを洗い出す。 (Entity=実体 狭い範囲での情報の集合体で、集合体を一言で表した共通の名前) 真似て作るのであればまずは実際にアプリを触ってみる。 オリジナルだと完全に自分で考えるしかない、、? ポイント = データが登録されるときに注目する! 新規ユーザー登録 → エンティティ=「ユーザー管理に関する情報」 写真の投稿 → エンティティ=「写真の投稿に関する情報」 コメントができる → エンティティ=「コメントに関する情報」 全てデータがDBに登録されているから画面に新たに入力した情報が表示される。 このように複数でてくるエンティティを、似たもの同士で大きくグループ分けしたものがテーブルになり、そのテーブル名を考えて複数形にする。 例:usersテーブル、picturesテーブル、commentsテーブル ーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーー 2:テーブル同士の関係性を考える DB設計にはテーブル同士の関係性が重要みたい。 どういう関係性が必要かというと、 ・1つのデータに対して1つなのか? ・1つのデータに対して複数なのか? ・複数のデータに対して複数なのか? という関係性。他にもあるのかな? とりあえずusersテーブルとpicturesテーブルで考えると、 usersテーブルとpicturesの関係は、 「1人のユーザーは、複数の写真を投稿することができる」 「1つの写真は、1人のユーザーが投稿したもの=1人のユーザーのもの(データ)」 つまり、この2つの関係性は、 usersテーブルのアソシエーション(関係) 「1対多 = user has many pictures」 = 「has_many: pictures」 picturesテーブルのアソシエーション(関係) 「1対多 = pictures belongs to user」 = 「belongs_to: user」 最後に書いた関係性をREADMEに記述していく。 これはのちにマイグレーションファイルに記述する。 このようにして各テーブルの関係性を明らかにしていく。 ーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーー 3:モデリングする モデリングは、エンティティや関連付け、テーブルのカラムなどをわかりやすく関係図で表したもの。 テーブルを四角く複数書いて、関係性があるテーブルを線で結んで、必要と思われる情報を記述していく。 今回は記述は省略しますが、これがあると結構わかりやすいですね。 4:制約を考える 制約とは、データを扱う際に制限をかけること。この時点でしっかりと考えておかないと後に変更するのがめんどくさい。以下は一部の紹介です。 ・「NOT NULL制約」 カラムに空の値(NULL)が入らないように制限する。空白のままじゃだめってこと。 README,マイグレーションファイルに記述します。 例:t.(カラムの型,integerなど), null: false ・「一意性制約」 テーブル内で重複するデータを禁止する制約。 メールアドレスとか被っちゃだめだよってこと。 README、マイグレーションファイルに記述します。 unique: true ・「外部キー制約」 1対多、または1対1の親側になる関係にある、多になる側に必要になる外部キー。 すごく簡単に言うと、データを引っ張ってきたいテーブルのidと情報がなければいけないという制約。 README、マイグレーションファイルに記述します。 t.references:(カラム名), foreign_key: true ーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーー 5:ER図を作成する ここまで全部考え出せたらER図を作成する。 ーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーー すごくざっくりしたものになりますが以上になります。 もっと技術がついたら具体的にさらにわかりやすさを追求して書いてみたいと思っています!
- 投稿日:2022-02-01T22:14:34+09:00
掲示板機能作成に苦戦した
はじめに 今回は、ポートフォリオ作成中に苦戦した、掲示板機能について復習します。 view <h1>掲示板</h1> <div class = "container"> <div class="row"> <%= will_paginate %> <% @microposts.each do |micropost| %> <div class="col-md-4"> <div class="card box"> <div class="card-body text-center"> ↓このコードで苦戦しました <h4 class="card-title"><%= link_to micropost.user.name, user_path(micropost.user) %></h4> <p class="card-text"><%= micropost.content%></p> <%=time_ago_in_words(micropost.created_at)%>前 </div> </div> </div> <%end%> </div> </div> microposts_controller.rb def index @user = User.find_by(params[:id]) @microposts = Micropost.paginate(page: params[:page]) end ポイント <h4 class="card-title"><%= link_to micropost.user.name, user_path(micropost.user) %></h4> 掲示版にどのユーザーがどの投稿をしたかを表示しようとしましたが、全く表示できなかった。 原因 非常に初歩的なミスでした。 マイクロポストコントローラーにユーザーのIDを取得する @user = User.find_by(params[:id]) を記述忘れしていました。 今後このようなミスがないように、コントローラーの記述をしっかりと確認していく癖をつけていきたいです。
- 投稿日:2022-02-01T14:34:53+09:00
【Rails】新規投稿時のリダイレクト先を条件分岐・分類(備忘録)
みなさん、こんにちは! 筆者は大学生限定のプログラミングスクール「GeekSalon」でメンターをしています! 興味のある方や話だけでも聞いてみてい方はぜひのぞいてみてください? さっそく今回の本題に入っていきます! 今回は投稿の分類に応じて、新規投稿後のリダイレクト先を変更していきます。 私はこの記事の「カテゴリー分けその2」の方法を参考に投稿を分類するということをおこなったのですが、新規投稿完了後にリダイレクトするページがどのカテゴリを選んでも同じViewページに飛んでしまうため、カテゴリに応じてリダイレクト先のViewページも変更したいと思いそれを実装しました。今回はその方法残しておきます。 なお、投稿の分類は実装済みという前提で話を進めていくため、投稿の分類を実装していない場合は先ほどの記事を参考に実装したのち、こちらの記事をお読みください!。 実装環境 ruby 2.7.4p191 (2021-07-07 revision a21a3b7d23) [x64-mingw32] Rails 6.1.4.1 実装① ~該当コントローラーのCreateアクションの記述を変更~ 今回は参考とした記事に依拠して、dogとcatとhadakadebanezumiという3つの分類を行うことを想定して実装していきます。 やるべきことは以下の1つです。 Tweets_controller.rb def create tweet = Tweet.new(tweet_params) if tweet.save #記述個所↓ if tweet.category = "dog" redirect_to :action => "dog" elsif tweet.category = "cat" redirect_to :action => "cat" elsif tweet.category = "hadakadebanezumi" redirect_to :action => "hadakadebanezumi" else end #記述個所↑ else redirect_to :action => "new" end end これだけです! しかし、この条件分岐はもっと簡略化して記述することもできます。(以下参照) Tweets_controller.rb def create tweet = Tweet.new(tweet_params) if tweet.save #記述個所↓ case tweet.category when "dog" redirect_to :action => "dog" when "cat" redirect_to :action => "cat" when "hadakadebanezumi" redirect_to :action => "hadakadebanezumi" #記述個所↑ else redirect_to :action => "new" end end これで終わりです! モデル名・カラム名・カラムに収納されている値は作成するプロダクトに応じて変更してください! 以上で、新規投稿で選択したカテゴリに応じて、リダイレクト先のViewページを変更することができました。
- 投稿日:2022-02-01T13:33:37+09:00
【Rails6】Ruby on Railsの環境構築手順
本稿では、MacでRailsを使った開発を行うための環境構築手順を解説しています。 環境 macOS Monterey バージョン12.1 Railsの環境構築手順 1.Homebrewのインストール 2.rbenvのインストール 3.Rubyのインストール 4.Bundlerのインストール 5.yarnのインストール 6.Railsのインストール 1.Homebrewのインストール 以下のリンク先からHomebrewの公式サイトにアクセスします。 Homebrew 「インストール」の見出しの下にあるコマンドをターミナルで実行するとHomebrewのインストールが開始されます。 インストール実行後、以下のコマンドで無事にHomebrewがインストールされたことを確認します。 % brew -v Homebrew 3.3.13 Homebrew/homebrew-core (git revision 418b6985490; last commit 2022-02-01) Homebrew/homebrew-cask (git revision 6721eaff8f; last commit 2022-02-01) 2.rbenvのインストール 次に、Rubyのバージョン管理ツールであるrbenvをインストールします。 % brew install rbenv インストールしたrbenvのバージョンを確認します。 % rbenv --version rbenv 1.2.0 次に以下のコマンドでrbenvにPATHを通します。 (使用中のシェルがbashの場合は、zshrcをbashrcに置き換えてください。) echo 'export PATH="$HOME/.rbenv/bin:$PATH"' >> ~/.zshrc echo 'if which rbenv > /dev/null; then eval "$(rbenv init -)"; fi' >> ~/.zshrc source ~/.zshrc 3.Rubyのインストール インストールできるRubyのバージョンを確認します。 % rbenv install -l 2.6.9 2.7.5 3.0.3 3.1.0 jruby-9.3.3.0 mruby-3.0.0 rbx-5.0 truffleruby-22.0.0.2 truffleruby+graalvm-22.0.0.2 Only latest stable releases for each Ruby implementation are shown. Use 'rbenv install --list-all / -L' to show all local versions. 2022/2/1現在の最新安定版の3.1.0をインストール。 % rbenv install 3.1.0 使用するRubyのバージョンをグローバルに設定。 % rbenv global 3.1.0 設定したバージョンを確認。 % ruby -v ruby 3.1.0p0 (2021-12-25 revision fb4df44d16) [x86_64-darwin21] 4.Bundlerのインストール 次に、Bundlerをインストールします。 Bundlerとはgemを管理するためのツールです。 複数のgemの依存関係を保ちながらパッケージの種類やバージョンを管理します。 % gem install bundler インストールされたBandlerのバージョンを確認。 % bundler -v Bundler version 2.3.6 5.yarnのインストール 次に、yarnをインストールします。 yarnはJavaScriptのパッケージマネージャです。 JavaScriptのパッケージマネージャとしてはyarnと互換性のあるnpmもありますが、Rails6からWebpackerが標準になったことにより、yarnが必須となりました。 % brew install yarn インストールされたyarnのバージョンを確認。 % yarn -v 1.22.17 6.Railsのインストール 最後に、Railsをインストールします。 ここではバージョン6.1.3.1を指定しています。 (最新のRailsのバージョンは「railsの全バージョン履歴」から確認できます。) % gem install rails -v 6.1.3.1 インストールされたRailsのバージョンを確認。 % rails -v Rails 6.1.3.1 以上でRails6の環境構築は終了です!
- 投稿日:2022-02-01T13:26:48+09:00
細かいつまずいたことをメモしておく(1月編)
はじめに 先月は特にメモするような話題がなかったので2か月ぶりのメモ記事です。 案外自分でみることが多く、役立っているこのシリーズですが、他の人に見られることはほぼないと思ってはいます(調べればわかることをメモしてるだけです) 今月から案件のサポートとして参加することになりすこしずつわからないこともでてくるようになりました。 問題 1. VPN接続している際に(在宅)WSL2でgit cloneができない 以下の記事の通りに対処することで解決 https://zenn.dev/mallowlabs/articles/ssh-on-wsl2-with-vpn 前も同じようなところで詰まっていました。.bashrcなどに書いて毎度実行するほうが良いかもしれないです。 2. SageMakerのドメインが削除できない ユーザープロファイルを削除したのにドメイン削除ボタンが押せない。 コンテナイメージをアタッチしたエラーがずっと表示されておりそれが原因かもしれない。 以下のコマンドでCLIから削除したところ削除できた $ aws sagemaker delete-domain --domain-id [ドメインID] --region [リージョン(ap-northeast-1など)] 3. Access denied for user 'root'がでてしまう MySQLで接続できないエラーが発生 まさかのwebpackerコンテナからrails db:createしていた。 また、database.ymlのusernameをrootにしていなかったのもあった 4. Railsで複数選択可能なチェックボックスの作成 以下の記事を参考に作成した 【Rails入門説明書】checkboxについて解説 Railsで複数check_boxを配列で送って、カラムに登録、viewで表示して、checkedもつける方法 checkboxでmultipleをつけると配列で値を受け取れるが、params.requireで値を取るとnilになってしまうところで苦戦した。 結果以下の箇所を修正することでできるようにした。 test.html.erb <div class='d-inline text-nowrap ml-1'> <label class='fw-normal'> <%= f.check_box :color, {multiple: true}, '青色', nil %> 青色 </label> </div> <div class='d-inline text-nowrap ml-1'> <label class='fw-normal'> <%= f.check_box :color, {multiple: true}, '黄色', nil %> 黄色 </label> </div> test.controller def notebook_params notebook_params = params.require(:notebook).require(:size, :width) notebook_params[:color] = params.require(:notebook).permit({:color => []})[:color] notebook.rb(model) serialize :color, Array before_save do self.color.split(',').map{ |m| m.delete('[]"\\')} if attribute_present?("color") end これで配列にして保存ができる。パラメータの取り方が特殊になるらしい 5. ./bin/webpack-dev-server: Permission deniedが時々起きる (chmod・chownが効かない) なぜかファイルが権限で読み込めなくなる現象が時々おきる。一回治ると以降起きないが、Gitからダウンロードしたときにコンテナをあげると起きたり、起きなかったり。 Dockerfileでchmod +x ./bin/webpacker-dev-serverをしても同じエラーがでてしまいchown, chmodが効いていない これはローカルにある./bin/webpacker-dev-serverで上書きされてしまうために起きていて、そのファイルはなぜか読み込める権限がなかった。 これはWSL2のフォルダをデスクトップにダウンロードしてからWSL2に再度戻したことにより権限が変わっていたことが原因でした。Gitに上げる前に権限を変更してあげないとCloneした人も同じ現象が起きてしまうので注意が必要 おわりに 本当は来月から1年出張しにくはずでしたが、コロナで完全リモートになりました。 来月も引き続きAWSとWeb技術頑張ります。 参考 WSL2 内の Ubuntu 20.04 で VPN をつないだときだけ ssh できない 【保存版】Docker上に構築したRails6でVue.jsを表示する方法(エラー対処法&Vuetifyの使い方) 【Rails入門説明書】checkboxについて解説 Railsで複数check_boxを配列で送って、カラムに登録、viewで表示して、checkedもつける方法
- 投稿日:2022-02-01T13:24:17+09:00
[Ruby, Rails] リファクタリングに役立つTips集 (初心者向け)
Ruby, Ruby on Railsを実務で使用する中で、レビューで指摘いただいたことや自分で調べた便利メソッドなどをリファクタリングの際のTips集としてまとめたいと思います。 メソッドの返り値にはreturnを使用しない 別の言語を使用する際にはreturnを使用して返り値を表現することが多いと思いますが、Rubyではreturnを明示的に書くことは少ないです。 def total(count) price = 100 sum = price * count sum # ここでreturnは不要 end 返り値を配列として返したい場合はmapを使用する numbersという数字の配列から5以上の数字だけを取り出して result_numbers という配列に入れたい場合、eachを用いると次のように書くことができます。 def method result_numbers = [] numbers.each do |number| result_numbers << sample if number >= 5 end end eachの代わりにmapを使えば result_numbers = [] と書かなくても以下のように書くことができます。 mapはブロックで評価した値を配列で返してくれます。 def method result_numbers = numbers.map do |number| number if number >= 5 end end ぼっち演算子(&.)を用いてレシーバーがnilの場合のハンドリングをする オブジェクトがnilの場合のハンドリングをしたいときに下記のように書くことができます。 if article name = article.author end ここでオブジェクト&.メソッド というようにメソッドの前に &. をつけることでオブジェクトがnilの場合でもエラーが出なくなり、オブジェクトがnilの時のハンドリングを省略して書けます。 name = article&.author # articleがnilの場合はnameにnilを代入。 定数は基本的にfreezeさせて使用する 定数にfreezeメソッドを使用しない場合、以下のように破壊的な変更が可能になってしまいます。 MESSAGE = "hello" p MESSAGE.upcase! #=> HELLO 意図しない書き換えを防ぐために、freezeメソッドを使用してイミュータブルなオブジェクトに変換しましょう。 そうすると破壊的な変更が行われようとした際に以下のようにエラーが出ます。 MESSAGE = "hello".freeze p MESSAGE.upcase! #=> can't modify frozen String: "hello" (FrozenError) ヒアドキュメントを用いて複数行の文字列を定義する 複数行の文字列を定義する場合には下記のように書くことがあると思います。 text = "content1\ncontent2" この程度の長さなら上記の定義でも問題はなさそうですが、例えばjsonのレスポンスを定義したい場合などにこの書き方をすると見づらくなってしまうと思います。そこでヒアドキュメントを使用すると以下のように定義できます。 text = <<-TEXT content1 content2 TEXT 今回は以上です。どんどん追記していこうと思いますので、参考になったという方はLGTM, ストックをしていただけると嬉しいです!
- 投稿日:2022-02-01T01:47:57+09:00
Rails のポリモーフィック関連でカラムの値とは異なるモデルを使用する
背景 あまりないかもしれませんが、例えばメインの Rails アプリで運用されてる DB にサブの Rails アプリからもアクセスしたいような場合に同じテーブルを違うネームスペースのクラスから扱いたい場合があります。 Rails のポリモーフィック関連はレコードの文字列をそのままクラス名として使うため、全く同じクラス名であれば問題ないですが別にネームスペースが切られていると面倒なことになります。 構成 具体的には以下のような場合です(最小限の部分のみ記載しています)。 あるメニューのリストのうちメニューそのもの、見出し部分、内容部分をそれぞれモデルにしています。 イメージ的には↓のような感じです。 またバージョンは ruby 3.0.1、rails 6.1.4.1 です。 schema.rb create_table "menus", charset: "utf8mb4", force: :cascade do |t| t.string "menu_type", null: false t.integer "menu_id", null: false end create_table "menu_items", charset: "utf8mb4", force: :cascade do |t| t.string "label", null: false t.string "image" end create_table "menu_sections", charset: "utf8mb4", force: :cascade do |t| t.string "headline", null: false end db # menus テーブル +-----------------+--------------+------+-----+---------+----------------+ | Field | Type | Null | Key | Default | Extra | +-----------------+--------------+------+-----+---------+----------------+ | id | bigint(20) | NO | PRI | NULL | auto_increment | | menu_type | varchar(255) | NO | MUL | NULL | | | menu_id | int(11) | NO | | NULL | | +-----------------+--------------+------+-----+---------+----------------+ # menu_sections テーブル +------------+--------------+------+-----+---------+----------------+ | Field | Type | Null | Key | Default | Extra | +------------+--------------+------+-----+---------+----------------+ | id | bigint(20) | NO | PRI | NULL | auto_increment | | headline | varchar(255) | NO | | NULL | | +------------+--------------+------+-----+---------+----------------+ # menu_items テーブル +-----------------+--------------+------+-----+---------+----------------+ | Field | Type | Null | Key | Default | Extra | +-----------------+--------------+------+-----+---------+----------------+ | id | bigint(20) | NO | PRI | NULL | auto_increment | | label | varchar(255) | NO | | NULL | | | item_type | tinyint(4) | NO | | NULL | | +-----------------+--------------+------+-----+---------+----------------+ 上記のテーブルを扱うモデルがそれぞれ以下です。 メインシステムのモデル module Main class Menu < ApplicationRecord belongs_to :content, polymorphic: true, foreign_type: :menu_type, foreign_key: :menu_id end end module Main class Menu class Item < ApplicationRecord has_one :menu, as: :content, class_name: 'Main::Menu', foreign_type: :menu_type, foreign_key: :menu_id end end end module Main class Menu class Section < ApplicationRecord has_one :menu, as: :content, class_name: 'Main::Menu', foreign_type: :menu_type, foreign_key: :menu_id end end end これを以下のようなサブシステムのモデルから扱いたいと思います。 サブシステムのモデル module Sub class Menu < ApplicationRecord end end module Sub class Menu class Section < ApplicationRecord end end end module Sub class Menu class Item < ApplicationRecord end end end そして以下のようなレコードが入っているとします。 mysql mysql> select * from main_menus; +-----+---------------------+---------+ | id | menu_type | menu_id | +-----+---------------------+---------- | 1 | Main::Menu::Section | 1 | | 2 | Main::Menu::Item | 1 | +-----+---------------------+---------+ この menu_type のレコードの値を読み替えるにはどうすれば良いでしょうか? 結論 結論から言うと rails 5 の attributes API を使って実現することが出来ます。 以下のように SubMenuType という型を新しく定義して追加します。 config/initializers/sub_menu_type.rb module Sub class SubMenuType < ActiveRecord::Type::String def cast(value) value&.sub(/Main::/, 'Sub::') end end end ActiveRecord::Type.register(:sub_menu_type, Sub::SubMenuType) これを Sub::Menu モデルで扱う menu_type の型として attribute メソッドを使って定義します。 attribute API に関してはすでに色々記事が出ていますのでそちらをご参照ください。 https://techracho.bpsinc.jp/hachi8833/2020_12_23/48422 https://qiita.com/hamajyotan/items/0c1281d0156f89dcbe98 また Section や Item モデルのほうは has_one アソシエーションにおいて unscope と where で Menu モデルのレコードの WHERE 句をそれぞれ指定します。 サブシステムのモデル module Sub class Menu < ApplicationRecord attribute :menu_type, :sub_menu_type belongs_to :content, polymorphic: true, foreign_type: :menu_type, foreign_key: :menu_id end end module Sub class Menu class Section < ApplicationRecord has_one :menu, lambda { |_obj| unscope(where: :menu_type).where(menu_type: 'Main::Menu::Section') }, class_name: 'Sub::Menu', as: :section, foreign_type: :menu_type, foreign_key: :menu_id, inverse_of: :content, dependent: :destroy end end end module Sub class Menu class Item < ApplicationRecord has_one :menu, lambda { |_obj| unscope(where: :menu_type).where(menu_type: 'Main::Menu::Item') }, class_name: 'Sub::Menu', as: :item, foreign_type: :menu_type, foreign_key: :menu_id, inverse_of: :content, dependent: :destroy end end end これでレコードの値がそのままでも Sub::Menu インスタンスから紐づく Sub::Menu::Section や Sub::Menu::Item インスタンスを呼べたり、Sub::Menu.includes(:content) のように includes(動作的には preload)を使うことも出来ます。 おまけ コードリーディング 以下のようにサブシステムの方でメインシステムと同じように belongs_to の記述でそのまま実行してみます。 module Sub class Menu < ApplicationRecord belongs_to :content, polymorphic: true, foreign_type: :menu_type, foreign_key: :menu_id end end すると以下のようなエラーログになりました。これを追っていってみます。 activesupport (6.1.4) lib/active_support/inflector/methods.rb:288:in `const_get' activesupport (6.1.4) lib/active_support/inflector/methods.rb:288:in `block in constantize' activesupport (6.1.4) lib/active_support/inflector/methods.rb:284:in `each' activesupport (6.1.4) lib/active_support/inflector/methods.rb:284:in `inject' activesupport (6.1.4) lib/active_support/inflector/methods.rb:284:in `constantize' activesupport (6.1.4) lib/active_support/dependencies/zeitwerk_integration.rb:19:in `constantize' activerecord (6.1.4) lib/active_record/inheritance.rb:199:in `polymorphic_class_for' activerecord (6.1.4) lib/active_record/associations/belongs_to_polymorphic_association.rb:9:in `klass' ... これを少しずつ追っていきます。 ActiveSupport::Inflector#constantize エラーの直接の原因は ActiveSupport::Inflector#constantize 内の const_get で起きています。 lib/active_support/inflector/methods.rb def constantize(camel_cased_word) if camel_cased_word.blank? || !camel_cased_word.include?("::") Object.const_get(camel_cased_word) else names = camel_cased_word.split("::") # Trigger a built-in NameError exception including the ill-formed constant in the message. Object.const_get(camel_cased_word) if names.empty? # Remove the first blank element in case of '::ClassName' notation. names.shift if names.size > 1 && names.first.empty? names.inject(Object) do |constant, name| if constant == Object constant.const_get(name) else candidate = constant.const_get(name) # <= ココ next candidate if constant.const_defined?(name, false) next candidate unless Object.const_defined?(name) ... end end end end 288行目の constant.const_get(name) でエラーになっているので、この name の元となっている引数、つまり constantize の呼び出し元を見てみます。 lib/active_record/inheritance.rb def polymorphic_class_for(name) if store_full_class_name ActiveSupport::Dependencies.constantize(name) # <= ココ else compute_type(name) end end polymorphic_class_for の呼び出しは以下です。 lib/active_record/associations/belongs_to_polymorphic_association.rb def klass type = owner[reflection.foreign_type] type.presence && owner.class.polymorphic_class_for(type) # <= ココ end これは名前からわかるようにポリモーフィック関連用の belongs_to を扱うためのクラスです。この klass メソッドでいい感じのクラス名を返せると目的が達成できそうです。そこでカラム情報が定義されている owner オブジェクトをなんとかしようという発想にいたります。 そして、実はこれ以降を辿っていってもオプションや設定でポリモーフィック関連のクエリを変更出来るところはありません。もし gem のオーバーライドで対応するのであればこの辺を上書きするところでした。 終わりに ランチェスターではサーバーサイドエンジニアを募集しています!まずはざっくばらんにお話ししてみませんか? 下記からご応募お待ちしております。 ▼ https://herp.careers/v1/lanchester/Bucw0mXRKogc ▼ https://www.wantedly.com/companies/lanchester ▼採用動画について:https://moovy.jp/job/651
- 投稿日:2022-02-01T01:38:49+09:00
injectメソッド
rubyシルバーを勉強していくなかで、injectメソッドについてわかったなかったので、解説していこうとおもいます。 injectメソッドとは injectメソッドとは、たたみこみ演算を行うメソッドです。 詳細は、公式ドキュメントを参照してください。 言葉で説明するのは、むずかしいのですが、具体例で見てみると分かるとおもいます。 #合計を求める 1から6までをそれぞれ足していく。 p [1, 2, 3, 4, 5, 6].inject {|int, num| int + num } =>21 #1から6までをそれぞれ掛けていく p [1, 2, 3, 4, 5, 6].inject {|int, num| int * num } =>720 また、初期値を入れることもできます。先ほどのコードでいえば、 p [2, 3, 4, 5].inject(0){|result, item| result + item } => 14 p [2, 3, 4, 5].inject(10){|result, item| result + item } => 24 もうちょい分かりやすく書きたい p [1, 2, 3, 4, 5, 6].inject(:+) p [1, 2, 3, 4, 5, 6].inject(:*) 上の書き方で、書いても同じような結果がでてきます。 シンボルで書くとスッキリしますね! 実際に問題を解いてみましょう 選択肢➀ この選択肢は、今回の記事を読んでいけば分かるとおもいます。 aとbの変数に格納されている配列の要素を、順次足していってるんです。 そうすると、この選択肢は、30以上にはならないので、この選択肢は不正解です。 選択肢➁ 「|」は集合の和演算を表しています。+までの左辺は、[-1, 2, 3, 4, 5, 6]に対して、順次「-」が適用されていき、それに絶対値を当てます。 「&」は共通した値を取得します。+以降は、[4, 5]に順次「+」が適用されていきます この計算結果は、30になるのでこの選択肢が正解になります。 選択肢➂ ここまで読んだ方でしたら、特別解説はいらないとおもいます。**3は3乗の意味ですね。この選択肢は不正解です。 選択肢➃ ||と&&の使い方は、以下の記事を参考にしてみてください。 succメソッドは、次の整数をとるメソッドです。 たとえば、 1.succ => 2 こういうふうに、次の整数をとれるメソッドです。 この選択肢では、29となるので、この選択肢は不正解です。 以上です。 何か間違いがございましたら、ご教示いただけますと幸いです。 【参考文献】