- 投稿日: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:22:55+09:00
M1 Mac も速くないことがある
これは何? 先日まで Mid 2015 の 15 inch MacBook Pro (Core i7 クアッド / 2.2 GHz) を使っていた。 先日 MacBook Pro 14 inch (M1 非Max) を手に入れたんだけど、あんまり速くないなと思うことがあったので、今日も楽しいマイクロベンチマーク。 計算内容 ruby で書くと短くていいね。 ruby N=10000 r=(1..N).max_by{ |x| ((N-x)**x/7) % 6074001001 } p r こういう内容。なんの意味もない。 出力は 8663 となれば正解。 これを、go, java, c++ with boost (clang, gcc), ruby, python3, node で試した。 以降、グラフで出てくる "m1", "rosetta", "amd64" の意味は下表のとおり。 記号 実行ハードウェア バイナリ m1 MacBook Pro 14 inch (M1 非Max) arm64 rosetta MacBook Pro 14 inch (M1 非Max) x86_64 amd64 MacBook Pro (Core i7, Mid 2015) x86_64 コンパイルするチーム go, java, c++ with boost (clang, g++-11)。 各コンパイラは下記の通り go version go1.17.5 darwin/arm64 openjdk 17.0.1 2021-10-19 LTS Apple clang version 13.0.0 (clang-1300.0.29.30) g++-11 (Homebrew GCC 11.2.0_3) 11.2.0 java と clang の rosetta はサボった。 結果は下記の通り。 time コマンドの real の値を出しているので棒が短いほど速い。 ちなみに、 real なのは並列実行を優遇するため。実際、 Java は user が real の 1.5倍ぐらいある。 結果は下図。 目盛りを見ると分かる通り、 go が速い。意外と clang が M1 を使いこなせてない感じ。 全体的にはまあそうだよねという結果だと思う。 コンパイルしないチーム 続いて、 ruby, python3, node。 各環境は下記の通り ruby 3.1.0p0 (2021-12-25 revision fb4df44d16) [arm64-darwin21] / for m1 ruby 3.0.3p157 (2021-11-24 revision 3fb7d2cadc) [x86_64-darwin21] / for rosetta ruby 3.1.0p0 (2021-12-25 revision fb4df44d16) [x86_64-darwin21] / for amd64 Python 3.9.10 (main, Jan 15 2022, 11:40:53) / for m1 Python 3.9.10 (main, Jan 15 2022, 11:48:04) / for rosetta Python 3.9.10 (main, Jan 15 2022, 11:48:04) / for amd64 node v17.3.0 / for m1 and rosetta node v17.3.1 / for amd64 なんか ruby と node のバージョンが合ってないけど気にしない。 結果は下図。 こちらはわりと思いがけなかった。 node はまあまあそうだよねという内容。m1 と amd64 の差はもうちょっとあってもいいかなと思うけど。 python3 は、三者ほぼ同タイム。 そして ruby は m1 が一番遅いという意外な展開。よく見てみると、m1 が遅いというより、amd64 が速すぎる。amd64 の中では go と並んでほぼ最速。m1 が遅いと書いたもののそれは ruby 内の比較の話。m1 内での比較だと Java と同等、clang より速い。node には負けるけど。 ruby や python で m1 がふるわないのは、おそらく、x86_64 バイナリは SSEとかをたっぷり使っていて、ARM64 バイナリは NEONとかを使いこなせてないんだろうと想像する。調べてないので想像するだけ。 まとめ M1 ネイティブでも rosetta 2 より遅いこともあるよ。 とはいえ。ほとんどの場合は M1 ネイティブは速いし、今遅いものもそのうち早くなるんじゃないかと思うよ(思うだけ)。
- 投稿日: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-01T21:48:18+09:00
Mysql2::Error: Duplicate entry 'xxx' for key 'PRIMARY' エラー解決方法
エラーの概要 Mysql2::Error: Duplicate entry '100018' for key 'PRIMARY'と表示され、レコードを追加することができず、、 エラーメッセージから主キーが重複しているということは理解できたが、該当の主キーとは何なのか、、 そんな中、無事解決できたので備忘録としてまとめておきます。 結論から、AUTO_INCREMENT属性を該当テーブルのidに設定していたため、レコード作成時にMySQLが自動的にid 番号を割り当てていたことによる重複が原因でした。 実装箇所をテストしていた際に、誤って操作したことによるものだと思われます、、反省。 前提 OS:MAC DB:MySQL TablePlusを使用してDBデータを確認しています。 1.AUTO_INCREMENTの値を確認する 取得する該当テーブルを指定してテーブルデータを確認してみました。 TablePlusの操作 左上のSQLをクリック コマンドを入力し、⌘ + Enterで実行 -- 構文 SHOW TABLE STATUS LIKE ('テーブル名'); -- 出力コマンド SHOW TABLE STATUS LIKE ('orders'); => 100017 出力結果からAUTO_INCREMENTの値が100017であることが確認できました。 2.最大値のレコードを取得 今回の場合、PRIMARYキーのidカラムの重複が原因のため、該当カラムの最大値レコード数を取得してみました。 SELECT MAX(id) FROM orders; => 100133 出力結果からテーブルに存在する1番最後のレコードのidが100133であることが確認できました。 本来なら、次に作成されるレコードのidには100134が付与されるのだが、どういうことかidに100018が付与されてしまい、「 重複してるー 」とエラーメッセージが表示されていたんですね、、 3.次回レコード作成時のAUTO_INCREMENTの値を確認 TablePlusで確認することができます。 TablePlusの操作 左上のitemsをクリックし、該当テーブルを選択する。 画面下にあるStructureをクリックする。 表示されたinfoをクリックし、1番下までスクロールすると確認できます。 ENGINE=InnoDB AUTO_INCREMENT=100018 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='xxx'; 確認したところ、100018と表示されており、本来は100134である必要があります。 4.AUTO_INCREMENTの値を変更 最後に、正しい値をALTER文で変更していきます。 ALTER TABLE orders AUTO_INCREMENT = 100134; 出力後 ENGINE=InnoDB AUTO_INCREMENT=100134 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='xxx'; 先ほど TablePlus で確認したAUTO_INCREMENTの値が反映されていることが分かります。 その結果、無事新規レコードを作成することに成功! 最後に 解決できたときの達成感って格別ですよね笑 SHOW TABLE STATUS で 取得できるデータ 【ストレージエンジンがInnoDBの場合】 項目 情報 Name テーブル名 Engine テーブルのストレージエンジン名 Version テーブルの .frm file. のバージョン番号 Row_format レコードの保存形式 Rows レコードの行数 Avg_row_length レコードの平均行長 Data_length クラスタ化されたインデックスに割り当てられるおおよその容量 (バイト単位) Max_data_length 未使用 Index_length クラスタ化されていないインデックスに割り当てられる領域の概算量 (バイト単位) Data_free 割り当てられているが、使用されていないバイト数 Auto_increment 次のAUTO_INCREMENT値 Create_time テーブル作成時刻 Update_time データファイルの最新更新時刻 Check_time 最後にチェックされた時刻 Collation テーブルのデフォルトの照合 Checksum ライブチェックサム値 Create_options パーティションテーブルの partitioned が表示(CREATE TABLE で使用される追加のオプション) Comment テーブルを作成時に使用されたコメント
- 投稿日: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:43+09:00
Rubyでコンソールにプログレスバーを作ってみた
できたもの プログラム print "\r" で、カーソルをコンソールの行頭に移動させて、一行分上書きできる。 "\e[32m#{str}\e[0m" で、任意の文字 str を緑色で表示させる。 sym = [ '\\', '|', '/', '-' ] def green(str) "\e[32m#{str}\e[0m" end 1.upto(100) do |i| print "\r" print " (#{sym[i % sym.length]}) [" print ('/' * (i / 10)).ljust(10, ' ') << ']' sleep 0.03 end 8.times do |i| print "\r" print ' (o) [ ' print green('ALL DONE'[0, i + 1].ljust(8, '.')) print ' ]' sleep 0.1 end 参考 https://stackoverflow.com/questions/1489183/how-can-i-use-ruby-to-colorize-the-text-output-to-a-terminal https://stackoverflow.com/questions/31090782/how-to-overwrite-the-current-console-line-after-gets-in-ruby https://ezgif.com/video-to-gif
- 投稿日: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: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-01T13:11:34+09:00
[py2rb] 関数の属性
はじめに 移植やってます。 ( from python 3.7 to ruby 2.7 ) 関数の属性 (Python) def make_func(x): def func1(): return '1' def func2(): return '2' func1.__doc__ = x func1.other = func2 return func1 my_func = make_func("func1") print(my_func()) print(my_func.__doc__) print(my_func.other()) # output 1 func1 2 関数の属性という説明が正しいか自信はないのですが、特殊属性の代表である__doc__ドキュメンテーション文字列の他、自作関数を属性として追加し、呼び出すことができます。 どうする (Ruby) make_func = lambda do |x = nil, *args, **kwargs| def func1() return '1' end def func2() return '2' end if x.nil? func1 elsif x == 'other' || x == :other func2 end end my_func = make_func puts my_func.call() puts my_func.call('other') # output 1 2 __doc__はPythonの文化なので使用しないと思いますが、必要があればdef __doc__の追加で対応できそうです。 メモ Python の 関数の属性 を学習した 百里を行く者は九十里を半ばとす
- 投稿日: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となるので、この選択肢は不正解です。 以上です。 何か間違いがございましたら、ご教示いただけますと幸いです。 【参考文献】