- 投稿日:2021-06-23T23:29:09+09:00
Railsチュートリアル 第7章 リスト7.23のテストでArgumentError: wrong number of arguments (given 2, expected 1)
はじめに Railsチュートリアル7章のリスト7.23のテストで ArgumentError: wrong number of arguments (given 2, expected 1) とエラーが出たので、解決した方法をまとめておきます。 自分の環境 - ryby: 3.0.1 - rails: 6.1.3.2 エラーが出た原因 ruby3.0以上を使っていると、キーワード引数の書き方が変わったため、書き方の問題でひっかかったぽいです。 エラーがでたコードとエラーメッセージ require 'test_helper' class UsersSignupTest < ActionDispatch::IntegrationTest test "invalid signup information" do get signup_path assert_no_difference 'User.count' do post users_path, params: { user: { name: "", email: "user@invalid", password: "foo", password_confirmation: "bar" } } end assert_template 'users/new' end end $ rails test Running via Spring preloader in process 4852 Started with run options --seed 46915 ERROR["test_invalid_signup_information", #<Minitest::Reporters::Suite:0x00007f81d2250d18 @name="UsersSignupTest">, 2.133824999909848] test_invalid_signup_information#UsersSignupTest (2.13s) Minitest::UnexpectedError: ArgumentError: wrong number of arguments (given 2, expected 1) test/integration/users_signup_test.rb:8:in `block (2 levels) in <class:UsersSignupTest>' test/integration/users_signup_test.rb:7:in `block in <class:UsersSignupTest>' 19/19: [=========================================] 100% Time: 00:00:02, Time: 00:00:02 Finished in 2.36285s 19 tests, 37 assertions, 0 failures, 1 errors, 0 skips 解決法 自分の場合はrubyのバージョンを2.7.3に変更しました。 どこかの記事で、8行目のpost users_path,の,を削除すればテストが通ったみたいなのですが、その方法では10章で詰むみたいです。 いったんはバージョン下げて対応しました。
- 投稿日:2021-06-23T23:24:15+09:00
配列.each do |変数|の使い方
配列 = [1,2,3・・・・] 配列.each do |変数| puts 変数 end 配列を定義してから、それひとつずつ実行していくイメージ
- 投稿日:2021-06-23T22:30:37+09:00
returnとputsの違い
return と putsの違い どちらも文を表示させる機能だが、メソッドを終了させるかどうかという点が異なる。 return ・メソッドから脱出できる ・戻り値を受け取るだけ(putsしないと表示されない) puts ・メソッドを終了させない returnを使うとdefから脱出できる def condition(money = "") return "お金がない" if money.empty? "お金持ち" end puts condition("") returnを使わないとdefから脱出できない def condition(money = "") "お金がない" if money.empty? "お金持ち" end puts condition("") 最後の文だけが機能するため、どんな値を入れても「お金持ち」しか表示されない selfの重要性 selfを用いた場合 class Menu attr_accessor :name attr_accessor :price end def info return "#{self.name} #{self.price}円" end selfを用いない場合 class Menu attr_accessor :name attr_accessor :price end def info return "#{name} #{price}円" end これらは出力値が同じだが、文法的にselfを使う意味はあるのか? 【結論】同名のローカル変数があるときに区別するためにselfを使用する 上記の例で言えば、複雑なプログラムを書くときにどの「name」か判別するためにselfを使用する
- 投稿日:2021-06-23T19:25:19+09:00
【転職】Web系エンジニアに憧れるSIerの下僕
まじで新卒で中小SIerに6年勤めてきましたがもうそろそろ限界です。手に職付けるってことと自分がマネジメント職向いてないなと思うことからWeb系エンジニアに転職しようと思いたちました。 ちなみに技術力はほぼ皆無でVB.NETちょこっとかけるくらい。SQLは普通くらい。 明日やろうは馬鹿野郎という言葉がありますが無視して2021/07/01から勉強始めます。 とりあえずまずはProgateから初めて2周くらいしたらRubyの参考書買おうかなと思ってます。 絶対転職するぞ!!
- 投稿日:2021-06-23T18:34:12+09:00
NoMethodErrorを解決した話 (Railsチュートリアル )
Qiita初投稿です。宜しくお願いします。 今回は、NoMethodErrorの解決にかなりの時間を費やしてしまったので、紹介していこうと思います。 エラー文 NoMethodError (undefined method `log_in' for ~~~~~) 原因 ApplicationコントローラにSessionヘルパーモジュールを読み込む際に、 helloメソッドに書き込んでしまっていたので、正常に動作していなかった。 正) application_controller.rb class ApplicationController < ActionController::Base include SessionsHelper end 誤) application_controller.rb class ApplicationController < ActionController::Base def hello include SessionsHelper end end 解決するまでの過程 エラーを見る限り、メソッドがないということなのでlog_inメソッドが定義されているかを確認した。 sessions_helper.rb module SessionsHelper def log_in(user) session[:user_id] = user.id end end log_inメソッドは定義されていることを確認してから、よく分からなくなってしまい、詰まってしまった。その後は、そもそもメソッドとは何か?をRuby on Rails ガイドで調べることに。 Railsのコントローラは、ApplicationControllerを継承したRubyのクラスであり、他の>クラスと同様のメソッドが使えます。アプリケーションがブラウザからのリクエストを受け取ると、ル>ーティングによってコントローラとアクションが指名され、Railsはそれに応じてコントローラのイ>ンスタンスを生成し、アクション名と同じ名前のメソッドを実行します。 振り返りつつ、チュートリアルに沿ってもう一度確認していると、何故かSessionヘルパーモジュールがhelloメソッドの中にあるのに気がついた。 感想 エラーを出すと、どうしても視野が狭くなってしまい、普段気付けることに気付きにくくなってしまう。今回のことを生かして、次からは、少し離れたところから見るつもりで、冷静に対処していきたい。
- 投稿日:2021-06-23T18:28:02+09:00
Ruby 問題14 特定の文字列を検知する include?メソッド
問題 以下の要件を満たすcheck_nameメソッドを実装して下さい。 名前を入力すると「登録が完了!」という文字列を出力すること 名前の中にピリオド(.)がある場合は、「 "記号は登録できません"」という文字列を出力すること 名前の中に空白(全角のみ)がある場合は、「 "全角空白は登録できません"」という文字列を出力すること def check_name(str) # 処理 end puts "登録したい名前を入力してください(例)SuzukiTaro" str = gets check_name(str) ヒント include?メソッド include?メソッドは、指定した値が配列や文字列内に含まれているかを判定するメソッドです。指定した値が含まれている場合はtrueを、含まれていない場合はfalseを返り値として返します。 公式リファレンス 解答 def check_name(str) if str.include?(".") puts "記号は登録できません" elsif str.include?(" ") puts "全角空白は登録できません" else puts "登録が完了しました" end end puts "登録したい名前を入力してください(例)SuzukiTaro" str = gets check_name(str) check_name(str) の引数に自身が入力した名前が渡されます。 ピリオド(.)や全角スペースがあった場合はエラー文を出し、正しく記入されていれば登録できましたと出るような記述をしたいのでifで条件分岐を記述します。 def check_name(str) if #条件式 puts "記号は登録できません" elsif #条件式 puts "全角空白は登録できません" else #条件式 puts "登録が完了しました" end end 条件が当てはまった時点で処理が終了するので先にエラーが出る場合の条件式を記述します。 ピリオド(.)がある場合 if str.include?(".") puts "記号は登録できません" 言葉で表すとピリオドがあった文章はtrueなので記号は登録できませんと返ってきます。 ピリオドがなかったらelseifの条件式へ移ります. 空白(全角のみ)がある場合 elsif str.include?(" ") puts "全角空白は登録できません" 全角空白があった文章はtrueなので全角空白は登録できませんと出力されます。 ピリオドも空白全角もなかったら全てを通り抜けて登録が完了しました返ってきます。 ちなみに半角であれば登録はできます。
- 投稿日:2021-06-23T18:26:18+09:00
[Ruby]paizaのスキルチェックD 文字の一致
文字の一致 (paizaランク D 相当)に挑戦しました。 今回は簡単だと思ったのですが、問題文を見落としていたため、少し苦戦しました。 ”入力値最終行の末尾に改行が1つ入ります。”これを見落としており、 chompの記載が抜けておりました。 正解したコードは以下の通りです。 a = gets.chomp b = gets.chomp if a==b puts"OK" else puts"NG" end 模範解答コードでは、if文ではなく、下記のようにコンパクトに記述していました。 puts a == b ? 'OK' : 'NG' 練習問題は終わりなので、実際にスキルチェック問題に挑戦したいと思います。
- 投稿日:2021-06-23T18:25:00+09:00
[Ruby on Rails] N+1問題の解決法(joinsメソッド+groupメソッド編)
はじめに 今回ポートフォリオ(以後PF)制作で、 日本各地の名所を投稿できるサイトを制作しました。 実際に製作したサイトと、コード(GitHub)は下記のURLからご覧ください。 ・サイトURL : https://japansiteinfo.com (今後予告なく公開停止する場合があります。ご了承ください。) ・GitHubのURL : https://github.com/yuta-pharmacy2359/dwc_JapanSiteInfo_app 今回は前回から引き続き「N+1問題」に関して、includesメソッドで解決できないものについて joinsメソッドおよびgroupメソッドを用いた解決法を紹介したいと思います。 本題 1. 前回のおさらい&今回扱う問題 前回の記事(https://qiita.com/yuta-pharmacy2359/items/cf30a20fbea9347c0b72) では、includesメソッドで「N+1問題」を解決できない以下の6機能のうち、 ・(ユーザー詳細画面における)1人のユーザーが獲得した総いいね数表示 ・(キーワード一覧画面における)1つのキーワードにおけるスポット評価の平均値表示 ・ランキング機能(ユーザー1人ごとの総獲得いいね数)における1人のユーザーの総いいね数表示 ・ランキング機能(ユーザー1人ごとの総獲得いいね数)における1人のユーザーの総スポット数表示 ・フォロー数、フォロワー数表示 ・(フォロー・フォロワー画面における)各ユーザーの最終更新日表示 最上段の「(ユーザー詳細画面における)1人のユーザーが獲得した総いいね数表示」について、joinsメソッドを用いた解決法を紹介しました。 こちらは「基本的にいいねを集計するスポットは当該ユーザーのもののみである(テーブル結合後にグループ分けしたりする必要がない)」ため、spotsテーブルとfavoritesテーブルを結合した後はcountメソッドでそのレコード数を集計すればOKでした。 一方、それ以外に関しては「一覧画面などでユーザー(またはスポット)全体から必要なデータを適宜抽出して表示する」必要があるため、単にテーブル結合後にcountメソッドなどを利用するだけでは解決することができません。 参考までに、「1人のユーザーが獲得した総いいね数表示」について、ユーザー詳細画面とランキング(その他一覧系)画面における違いを下図に示しました。 図の通り、ランキング画面では、user_idごとの獲得いいね数を集計する必要があるため、countメソッドの使用前にuser_idごとにグループ分けする必要があります。 (なお、図では比較のため「ランキング画面における1人のユーザーが獲得した総いいね数」を取り上げましたが、ランキング機能については「N+1問題」以外にも要説明事項がいくつかあるので、また別の記事で紹介します。) そこで今回は、「(キーワード一覧画面における)1つのキーワードにおけるスポット評価の平均値表示」を例に、joinsメソッドで複数のテーブルを結合した後にgroupメソッドでテーブル内のデータを仕分けて集計する方法を紹介します。 2. 機能概要と発生した問題点 当PFでは、投稿するスポットに任意のキーワードを付与することができる仕様となっています。 さらに、上図のように、スポット詳細画面に表示されるキーワードはそのキーワード詳細画面のリンクとなっており、そこで同じキーワードを持ったスポットの一覧を見ることができます。 また、キーワード一覧画面には、そのキーワードを持ったスポットの評価の平均値が表示される仕様になっています。 その機能をテーブル同士の繋がりで表したのが下図です。 (データの繋がりが見やすくなるよう、テーブルの内容は上図から変えています。ご了承ください。) 当PFでは、1つのスポットにつき複数のキーワードを付与することができる仕様(同時に、他のスポットに既出のキーワードを付与可能)であるため、spotsテーブルとkeywordsテーブルは「多対多の関係」となります。 そのため、それら2つのテーブルの間にkeyword_relationshipsテーブルという中間テーブルを設けています。 そしてキーワード詳細画面では、中間テーブルを介してkeyword_idが一致しているスポットが抽出され表示されます。 (下図の例では、キーワードid=1である「東京タワー」に紐づくスポットとして、id=1,6,9,12のスポットが抽出されます。) さらにキーワード一覧画面では、その抽出されたスポットの評価の平均値を算出し表示しています。 (下図の例では、(5 + 2 + 4 + 5) / 4 = 4がid=1のキーワードにおけるスポットの評価平均値となります。) 「N+1問題」を考慮しないときのindexのビューファイルおよびコントローラーファイルでの記述は以下の通りとなります。 view/keywords/index.html.erb <% @keywords.each do |keyword| %> <tr> (中略) <td> <%= keyword.spots.count %>スポット </td> <% if keyword.spots.average(:rate).present? %> <td><%= keyword.spots.average(:rate).round(2) %></td> <!-- 以下は評価を星マークで表示するためのJavaScriptの記述。詳細は別記事で紹介予定 --> <script> 評価を星マークで表示するためのJavaScriptの記述。詳細は別記事で紹介予定 if(!$("#star-rate-<%= keyword.id %> img").length) { $('#star-rate-<%= keyword.id %>').raty({ size: 36, starOff: '<%= asset_path('star-off.png') %>', starOn: '<%= asset_path('star-on.png') %>', starHalf: '<%= asset_path('star-half.png') %>', readOnly: true, score: <%= keyword.spots.average(:rate) %>, }); } </script> <% else %> <td>評価なし</td> <% end %> </tr> <% end %> controllers/keywords_controller.rb def index # Ransack(検索・ソート機能が利用できるgem)を利用している関係で、以下の記述となっています。 @q = Keyword.ransack(params[:q]) @q.sorts = 'updated_at desc' if @q.sorts.empty? @keywords = @q.result.page(params[:page]) end 一般的に平均値を表示したい場合は、以下のように記述します。 モデル名.average(:カラム名) また、その後ろのroundメソッドは、「表示する数値を小数第(引数)桁までに指定する」メソッドです。 今回の場合は引数が2ですので小数第2位まで表示することとなります。 そして、スポットの評価は「無評価(nil)」でも許容される設定にしており、もしキーワードに紐づくスポットが全て「無評価」であった場合は評価の平均値が存在しない(nil)ことになるため、その場合の分岐としてelse以下で「評価なし」を表示するようにしています。 この記述では、以下のようなアクセスが行われます。 Keyword Load (0.3ms) SELECT “keywords”.* FROM “keywords” ORDER BY “keywords”.”updated_at” DESC LIMIT ? OFFSET ? [[“LIMIT”,10],[“OFFSET”,0]] KeywordRelationship Load (0.2ms) SELECT "keyword_relationships".* FROM "keyword_relationships"."keyword_id" IN (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) [["keyword_id", 10], ["keyword_id", 9], ["keyword_id", 8], ["keyword_id", 7], ["keyword_id", 6], ["keyword_id", 5], ["keyword_id", 4], ["keyword_id", 3], ["keyword_id", 2], ["keyword_id", 1]] Spot Load (0.2ms) SELECT "spots".* FROM "spots" INNER JOIN "keyword_relationships" ON "spots"."id" = "keyword_relationships"."spot_id" WHERE "keyword_relationships"."keyword_id" = ? ORDER BY "spots"."id" DESC LIMIT ? [["keyword_id", 10], ["LIMIT", 1]] (0.2ms) SELECT COUNT(*) FROM "spots" INNER JOIN "keyword_relationships" ON "spots"."id" = "keyword_relationships"."spot_id" WHERE "keyword_relationships"."keyword_id" = ? [["keyword_id", 10]] (0.1ms) SELECT AVG("spots"."rate") FROM "spots" INNER JOIN "keyword_relationships" ON "spots"."id" = "keyword_relationships"."spot_id" WHERE "keyword_relationships"."keyword_id" = ? [["keyword_id", 10]] CACHE (0.0ms) SELECT AVG("spots"."rate") FROM "spots" INNER JOIN "keyword_relationships" ON "spots"."id" = "keyword_relationships"."spot_id" WHERE "keyword_relationships"."keyword_id" = ? [["keyword_id", 10]] CACHE (0.0ms) SELECT AVG("spots"."rate") FROM "spots" INNER JOIN "keyword_relationships" ON "spots"."id" = "keyword_relationships"."spot_id" WHERE "keyword_relationships"."keyword_id" = ? [["keyword_id", 10]] Spot Load (0.2ms) SELECT "spots".* FROM "spots" INNER JOIN "keyword_relationships" ON "spots"."id" = "keyword_relationships"."spot_id" WHERE "keyword_relationships"."keyword_id" = ? ORDER BY "spots"."id" DESC LIMIT ? [["keyword_id", 9], ["LIMIT", 1]] (0.2ms) SELECT COUNT(*) FROM "spots" INNER JOIN "keyword_relationships" ON "spots"."id" = "keyword_relationships"."spot_id" WHERE "keyword_relationships"."keyword_id" = ? [["keyword_id", 9]] (0.1ms) SELECT AVG("spots"."rate") FROM "spots" INNER JOIN "keyword_relationships" ON "spots"."id" = "keyword_relationships"."spot_id" WHERE "keyword_relationships"."keyword_id" = ? [["keyword_id", 9]] CACHE (0.0ms) SELECT AVG("spots"."rate") FROM "spots" INNER JOIN "keyword_relationships" ON "spots"."id" = "keyword_relationships"."spot_id" WHERE "keyword_relationships"."keyword_id" = ? [["keyword_id", 9]] CACHE (0.0ms) SELECT AVG("spots"."rate") FROM "spots" INNER JOIN "keyword_relationships" ON "spots"."id" = "keyword_relationships"."spot_id" WHERE "keyword_relationships"."keyword_id" = ? [["keyword_id", 9]] Spot Load (0.2ms) SELECT "spots".* FROM "spots" INNER JOIN "keyword_relationships" ON "spots"."id" = "keyword_relationships"."spot_id" WHERE "keyword_relationships"."keyword_id" = ? ORDER BY "spots"."id" DESC LIMIT ? [["keyword_id", 8], ["LIMIT", 1]] (0.2ms) SELECT COUNT(*) FROM "spots" INNER JOIN "keyword_relationships" ON "spots"."id" = "keyword_relationships"."spot_id" WHERE "keyword_relationships"."keyword_id" = ? [["keyword_id", 8]] (0.1ms) SELECT AVG("spots"."rate") FROM "spots" INNER JOIN "keyword_relationships" ON "spots"."id" = "keyword_relationships"."spot_id" WHERE "keyword_relationships"."keyword_id" = ? [["keyword_id", 8]] CACHE (0.0ms) SELECT AVG("spots"."rate") FROM "spots" INNER JOIN "keyword_relationships" ON "spots"."id" = "keyword_relationships"."spot_id" WHERE "keyword_relationships"."keyword_id" = ? [["keyword_id", 8]] CACHE (0.0ms) SELECT AVG("spots"."rate") FROM "spots" INNER JOIN "keyword_relationships" ON "spots"."id" = "keyword_relationships"."spot_id" WHERE "keyword_relationships"."keyword_id" = ? [["keyword_id", 8]] ・・・ (以下、keyword_id = 1まで上記の繰り返し) さすがに途中で省略させていただきました(笑) まず、キーワード一覧画面では「そのキーワード内の最新のスポットの画像」と「そのキーワードを持つスポット数」を表示しているため、keywordsテーブルと同時にspotsテーブルにもアクセスが行われることになります。 (「そのキーワードを持つスポット数」については「とある理由」でSpot Loadの真下の行にもう一回アクセスが行われた形跡がありますが、そちらも当記事の最後の方で取り上げていますのでご安心ください。) さらに、問題のスポット評価平均値表示の部分では一つのキーワードにつき3回ずつアクセスが行われています。その内訳ですが、 indexのviewファイルにおいて、 ・<% if keyword.spots.average(:rate).present? %> ・<%= keyword.spots.average(:rate).round(2) %> ・<%= keyword.spots.average(:rate) %> の部分でそれぞれアクセスが行われています。 さすがに表示内容に対してアクセス回数が多すぎるので、次項で一つずつ解消していきます。 3. 解決法(前半) まず、spotsテーブルを何度も読み込んでしまう部分に関しては、以前も紹介したincludesメソッドで解決することができます。 keywordのコントローラーファイルにおいて、 controllers/keywords_controller.rb def index @q = Keyword.ransack(params[:q]) @q.sorts = 'updated_at desc' if @q.sorts.empty? @keywords = @q.result.page(params[:page]).includes(:spots) end 最後の@keywordsの定義の部分で、末尾にincludes(:spots)を追加すればOKです。 (繰り返しにはなりますが、includesメソッドの引数はモデル名ではなく関連名です。不安な場合はkeywordのモデルファイル(models/keyword.rb)でhas_manyの部分を確認してみてください。) この状態で再度キーワード一覧画面にアクセスすると、以下のようにSQLが発行されていることがわかります。 Keyword Load (0.2ms) SELECT “keywords”.* FROM “keywords” ORDER BY “keywords”.”updated_at” DESC LIMIT ? OFFSET ? [[“LIMIT”,10],[“OFFSET”,0]] KeywordRelationship Load (0.2ms) SELECT "keyword_relationships".* FROM "keyword_relationships"."keyword_id" IN (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) [["keyword_id", 10], ["keyword_id", 9], ["keyword_id", 8], ["keyword_id", 7], ["keyword_id", 6], ["keyword_id", 5], ["keyword_id", 4], ["keyword_id", 3], ["keyword_id", 2], ["keyword_id", 1]] Spot Load (0.2ms) SELECT "spots".* FROM "spots" WHERE "spots"."id" IN (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) [["id", 12], ["id", 11], ["id", 10], ["id", 9], ["id", 8], ["id", 7], ["id", 6], ["id", 5], ["id", 4], ["id", 3], ["id", 2], ["id", 1]] (0.2ms) SELECT COUNT(*) FROM "spots" INNER JOIN "keyword_relationships" ON "spots"."id" = "keyword_relationships"."spot_id" WHERE "keyword_relationships"."keyword_id" = ? [["keyword_id", 10]] (0.1ms) SELECT AVG("spots"."rate") FROM "spots" INNER JOIN "keyword_relationships" ON "spots"."id" = "keyword_relationships"."spot_id" WHERE "keyword_relationships"."keyword_id" = ? [["keyword_id", 10]] CACHE (0.0ms) SELECT AVG("spots"."rate") FROM "spots" INNER JOIN "keyword_relationships" ON "spots"."id" = "keyword_relationships"."spot_id" WHERE "keyword_relationships"."keyword_id" = ? [["keyword_id", 10]] CACHE (0.0ms) SELECT AVG("spots"."rate") FROM "spots" INNER JOIN "keyword_relationships" ON "spots"."id" = "keyword_relationships"."spot_id" WHERE "keyword_relationships"."keyword_id" = ? [["keyword_id", 10]] (0.2ms) SELECT COUNT(*) FROM "spots" INNER JOIN "keyword_relationships" ON "spots"."id" = "keyword_relationships"."spot_id" WHERE "keyword_relationships"."keyword_id" = ? [["keyword_id", 9]] (0.1ms) SELECT AVG("spots"."rate") FROM "spots" INNER JOIN "keyword_relationships" ON "spots"."id" = "keyword_relationships"."spot_id" WHERE "keyword_relationships"."keyword_id" = ? [["keyword_id", 9]] CACHE (0.0ms) SELECT AVG("spots"."rate") FROM "spots" INNER JOIN "keyword_relationships" ON "spots"."id" = "keyword_relationships"."spot_id" WHERE "keyword_relationships"."keyword_id" = ? [["keyword_id", 9]] CACHE (0.0ms) SELECT AVG("spots"."rate") FROM "spots" INNER JOIN "keyword_relationships" ON "spots"."id" = "keyword_relationships"."spot_id" WHERE "keyword_relationships"."keyword_id" = ? [["keyword_id", 9]] (0.2ms) SELECT COUNT(*) FROM "spots" INNER JOIN "keyword_relationships" ON "spots"."id" = "keyword_relationships"."spot_id" WHERE "keyword_relationships"."keyword_id" = ? [["keyword_id", 8]] (0.1ms) SELECT AVG("spots"."rate") FROM "spots" INNER JOIN "keyword_relationships" ON "spots"."id" = "keyword_relationships"."spot_id" WHERE "keyword_relationships"."keyword_id" = ? [["keyword_id", 8]] CACHE (0.0ms) SELECT AVG("spots"."rate") FROM "spots" INNER JOIN "keyword_relationships" ON "spots"."id" = "keyword_relationships"."spot_id" WHERE "keyword_relationships"."keyword_id" = ? [["keyword_id", 8]] CACHE (0.0ms) SELECT AVG("spots"."rate") FROM "spots" INNER JOIN "keyword_relationships" ON "spots"."id" = "keyword_relationships"."spot_id" WHERE "keyword_relationships"."keyword_id" = ? [["keyword_id", 8]] ・・・ (以下、keyword_id = 1まで上記の繰り返し) includesメソッドを用いたことによって、3行目でspotsテーブルにアクセスしている部分の「N+1問題」が解消されていることがわかります。 しかし、4行目以降のスポット評価平均値計算の部分についてはまだ解消されていません。 これを解消するためには、冒頭でも述べたjoinメソッドおよびgroupメソッドの合わせ技が必要となります。 4. 解決法(後半) それではキーワード一覧画面における「N+1問題」を完全に解決するための記述法を紹介します。 まずはコントローラーファイルにおける記述です。 controllers/keywords_controller.rb def index @q = Keyword.ransack(params[:q]) @q.sorts = 'updated_at desc' if @q.sorts.empty? @keywords = @q.result.page(params[:page]).includes(:spots) @rate_avg = @keywords.joins(:spots).group("keywords.id").average(:rate) end これまでと違うのは一番下の@rate_avgの行です。 まずjoinsメソッドですが、「関連するテーブル同士を内部結合するメソッド」です。 (具体的な説明に関しては https://qiita.com/yuta-pharmacy2359/items/cf30a20fbea9347c0b72 の記事をご覧ください。) 今回はkeyword_relationshipsテーブルという中間テーブルの存在がありますが、基本的な考え方は同じです。 3つのテーブルで、カラムの内容が一致しているもの(spotsテーブルのidとkeyword_relationshipsテーブルのspot_id、keywordsテーブルのidとkeyword_relationshipsテーブルのkeyword_id)で対応させます。 各テーブルが上図のように対応するはずです。 さらにkeyword_relationshipsテーブルはspotsテーブルおよびkeywordsテーブルの仲介役に過ぎず最終的に必要なデータではないこと、またこのままではspotsテーブルおよびkeywordsテーブルの関係が見づらいので、以下のように書き換えます。 だいぶ両者の関係が見やすくなりました。 そして今回求めているのは「1つのキーワードにおけるスポット評価の平均値」なので、上図の赤色で囲った部分が最終的に必要な情報となります。 ここで、前回 (https://qiita.com/yuta-pharmacy2359/items/cf30a20fbea9347c0b72) 取り上げた「1人のユーザーが獲得した総いいね数表示」では、joinsメソッドで結合した時点で既に集計対象であるユーザーのidがユーザー詳細画面で表示されているユーザーに限定されており、あとはそのままcountメソッドでレコード数を集計すればOKでした。 一方今回は、上図の通り集計対象がキーワード一覧画面に表示する全てのキーワード(上図の例ではid=1~10)であり、集計前にキーワードのidごとにグループ分けする必要があります。 そんな時に活躍するのがgroupメソッドです。 groupメソッドは「指定したカラムのデータの種類(または条件式)ごとにデータをまとめるメソッド」です。 基本的な定義は以下の通りです。 モデル名.group(:カラム(または"条件式")) そしてこのgroupメソッドですが、あくまで「データを引数で指定した法則に従ってまとめるだけ」のメソッドであるため、それ単体で利用されることはほとんどなく、countメソッドやavgメソッドなど、集計系のメソッドと併用することが多いです。 それを踏まえると、改めてgroupメソッドの基本的な定義は以下の通りになります。 # avgメソッドを使用する場合 モデル名.group(:カラム(または"条件式")).avg(:カラム(または"条件式")) 続いてそれぞれの引数に関してですが、今回の場合は以下の2条件 ・キーワードのidごとに集計したい ・スポットの評価の平均値を求めたい と、spotsおよびkeywordsそれぞれのテーブルの関係性を考慮すると、 ・groupメソッドの引数: "keywords.id" ・avgメソッドの引数: :rate となります。イメージとしては下図のようになるかと思います。 ということで、先ほども載せましたが、この部分は @rate_avg = @keywords.joins(:spots).group("keywords.id").average(:rate) と記述することができます。 さらに留意していただきたいのは、groupメソッドと集計系のメソッドを併用した場合の返り値はハッシュの形であるということです。 今回の例の場合、上式の返り値は以下のようになります。 { 1=>0.4e1, 2=>0.433333e1, 3=>0.366666e1, 4=>0.4e1, 5=>0.3e1, 6=>0.45e1, 8=>0.1e1, 9=>0.1e1, 10=>0.5e1 } (ターミナル上ではこのように指数表記で表示されます。例えばキーワードid=1では、0.4e1は4と同値です。また、id=7のように値がnilであった場合は表示されません。) ということで、最後の課題はこのハッシュからどうやって必要な値を取り出すかということになります。 ここで活躍するのがfetchメソッドです。 fetchメソッドは、ハッシュから引数に指定したキーの値を取り出すメソッドです。 基本的な定義は以下の通りです。 ハッシュ.fetch(key) たとえば、今回の例では、key=1の場合は0.4e1(表示上は4.0)、key=2の場合は0.433333e1(表示上は4.33)が返ってくるというわけです。 一方、存在しないキー(今回の例では特にkey=7)を引数に取るとエラーとなりますので注意してください。 それを踏まえて、ビューファイルのほうを確認してみましょう。 view/keywords/index.html.erb <% @keywords.each do |keyword| %> <tr> (中略) <td> <%= keyword.spots.size %>スポット </td> <% if @rate_avg.has_key?(keyword.id) %> <td><%= @rate_avg.fetch(keyword.id).round(2) %></td> <script> if(!$("#star-rate-<%= keyword.id %> img").length) { $('#star-rate-<%= keyword.id %>').raty({ size: 36, starOff: '<%= asset_path('star-off.png') %>', starOn: '<%= asset_path('star-on.png') %>', starHalf: '<%= asset_path('star-half.png') %>', readOnly: true, score: <%= @rate_avg.fetch(keyword.id) %>, }); } </script> <% else %> <td>評価なし</td> <% end %> </tr> <% end %> まず、if文の部分ですが、先ほども述べた通り、fetchメソッドでは存在しないキーを引数に取ることができないため、まずは@rate_avgのハッシュの中に各キーワードのidがキーとして存在するかどうかをhas_key?メソッドで確認します。 存在する場合は、その直後の文でハッシュからfetchメソッドでそれぞれのキーワードidに対応するスポット評価平均値を取り出して表示します。ない場合はelse以下の文で「評価なし」を表示します。 あとは大方N+1問題を解決しない時の記述と同じですが、もう1点だけ注意すべき記述があります。 <%= keyword.spots.size %>スポットの部分です。 一見、「1つのキーワードに紐づくスポットの数」なので<%= keyword.spots.count %>スポットと記述したくなりますが、countメソッドの性質に落とし穴があります。 countメソッドは、キャッシュを利用しない関係上、表示するキーワードごとに毎回データベースにアクセスしてしまいます。 (参考: https://www.lanches.co.jp/blog/3199) そのため、countメソッドを利用した場合、ターミナルのログを確認すると、 (0.3ms) SELECT AVG(rate) AS average_rate, keywords.id AS keywords_id FROM "keywords" INNER JOIN "keyword_relationships" ON "keyword_relationships"."keyword_id" = "keywords"."id" INNER JOIN "spots" ON "spots"."id" = "keyword_relationships"."spot_id" GROUP BY keywords.id ORDER BY "keywords"."updated_at" DESK LIMIT ? OFFSET ? [["LIMIT", 10],["OFFSET", 0]] Keyword Load (0.2ms) SELECT “keywords”.* FROM “keywords” ORDER BY “keywords”.”updated_at” DESC LIMIT ? OFFSET ? [[“LIMIT”,10],[“OFFSET”,0]] KeywordRelationship Load (0.2ms) SELECT "keyword_relationships".* FROM "keyword_relationships"."keyword_id" IN (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) [["keyword_id", 10], ["keyword_id", 9], ["keyword_id", 8], ["keyword_id", 7], ["keyword_id", 6], ["keyword_id", 5], ["keyword_id", 4], ["keyword_id", 3], ["keyword_id", 2], ["keyword_id", 1]] Spot Load (0.2ms) SELECT "spots".* FROM "spots" WHERE "spots"."id" IN (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) [["id", 12], ["id", 11], ["id", 10], ["id", 9], ["id", 8], ["id", 7], ["id", 6], ["id", 5], ["id", 4], ["id", 3], ["id", 2], ["id", 1]] (0.2ms) SELECT COUNT(*) FROM "spots" INNER JOIN "keyword_relationships" ON "spots"."id" = "keyword_relationships"."spot_id" WHERE "keyword_relationships"."keyword_id" = ? [["keyword_id", 10]] (0.2ms) SELECT COUNT(*) FROM "spots" INNER JOIN "keyword_relationships" ON "spots"."id" = "keyword_relationships"."spot_id" WHERE "keyword_relationships"."keyword_id" = ? [["keyword_id", 9]] (0.2ms) SELECT COUNT(*) FROM "spots" INNER JOIN "keyword_relationships" ON "spots"."id" = "keyword_relationships"."spot_id" WHERE "keyword_relationships"."keyword_id" = ? [["keyword_id", 8]] (0.2ms) SELECT COUNT(*) FROM "spots" INNER JOIN "keyword_relationships" ON "spots"."id" = "keyword_relationships"."spot_id" WHERE "keyword_relationships"."keyword_id" = ? [["keyword_id", 7]] (0.2ms) SELECT COUNT(*) FROM "spots" INNER JOIN "keyword_relationships" ON "spots"."id" = "keyword_relationships"."spot_id" WHERE "keyword_relationships"."keyword_id" = ? [["keyword_id", 6]] (0.2ms) SELECT COUNT(*) FROM "spots" INNER JOIN "keyword_relationships" ON "spots"."id" = "keyword_relationships"."spot_id" WHERE "keyword_relationships"."keyword_id" = ? [["keyword_id", 5]] (0.2ms) SELECT COUNT(*) FROM "spots" INNER JOIN "keyword_relationships" ON "spots"."id" = "keyword_relationships"."spot_id" WHERE "keyword_relationships"."keyword_id" = ? [["keyword_id", 4]] (0.2ms) SELECT COUNT(*) FROM "spots" INNER JOIN "keyword_relationships" ON "spots"."id" = "keyword_relationships"."spot_id" WHERE "keyword_relationships"."keyword_id" = ? [["keyword_id", 3]] (0.2ms) SELECT COUNT(*) FROM "spots" INNER JOIN "keyword_relationships" ON "spots"."id" = "keyword_relationships"."spot_id" WHERE "keyword_relationships"."keyword_id" = ? [["keyword_id", 2]] (0.2ms) SELECT COUNT(*) FROM "spots" INNER JOIN "keyword_relationships" ON "spots"."id" = "keyword_relationships"."spot_id" WHERE "keyword_relationships"."keyword_id" = ? [["keyword_id", 1]] このように、各キーワードにおけるスポット数の集計で、1つのキーワードごとにいちいち1回ずつアクセスを行うという無駄が発生してしまいます。 前回紹介した「(ユーザー詳細画面における)1人のユーザーが獲得した総いいね数」のように、集計対象が単一である場合ならcountメソッドでも全く問題ありませんが、一覧画面のように集計対象が複数ある場合は、countメソッドではなくsizeメソッドを使用するようにしましょう。 そこまで対処したあとに再度キーワード一覧画面を表示すると、 (0.3ms) SELECT AVG(rate) AS average_rate, keywords.id AS keywords_id FROM "keywords" INNER JOIN "keyword_relationships" ON "keyword_relationships"."keyword_id" = "keywords"."id" INNER JOIN "spots" ON "spots"."id" = "keyword_relationships"."spot_id" GROUP BY keywords.id ORDER BY "keywords"."updated_at" DESK LIMIT ? OFFSET ? [["LIMIT", 10],["OFFSET", 0]] Keyword Load (0.3ms) SELECT “keywords”.* FROM “keywords” ORDER BY “keywords”.”updated_at” DESC LIMIT ? OFFSET ? [[“LIMIT”,10],[“OFFSET”,0]] KeywordRelationship Load (0.2ms) SELECT "keyword_relationships".* FROM "keyword_relationships"."keyword_id" IN (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) [["keyword_id", 10], ["keyword_id", 9], ["keyword_id", 8], ["keyword_id", 7], ["keyword_id", 6], ["keyword_id", 5], ["keyword_id", 4], ["keyword_id", 3], ["keyword_id", 2], ["keyword_id", 1]] Spot Load (0.2ms) SELECT "spots".* FROM "spots" WHERE "spots"."id" IN (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) [["id", 12], ["id", 11], ["id", 10], ["id", 9], ["id", 8], ["id", 7], ["id", 6], ["id", 5], ["id", 4], ["id", 3], ["id", 2], ["id", 1]] これでキーワード一覧における「N+1問題」を全て解消することができました。 終わりに 長くなりましたが、joinメソッドとgroupメソッドを併用した「N+1問題」の解決方法を紹介しました。 当記事を含め3記事にわたって「N+1問題の解決法」について取り上げましたが、これで大方解決できるかと思いますので、当問題でお悩みの方はぜひ試してみてください。
- 投稿日:2021-06-23T17:14:32+09:00
正規表現 基本まとめシート
正規表現を使う際、調べながら取り組みますが、毎回複数ページを行き来してしまうため、今後すぐ取り出せるようリストアップしました。 環境 Ruby scanメソッドの引数に正規表現を使用して検索が可能 gsubメソッドの第一引数に変換前、第二引数に変換後の文字列または正規表現を用いることで置換が可能 Rubular メタ文字 \d : 1個の半角数字 = [0-9] \w : 1個の半角英数字とアンダースコア = [a-zA-Z0-9_] \t : タブ文字 \n : 改行文字 \s : 空白文字全般 言語・環境によって含まれる文字が異なるので注意 Ruby : [ \t\r\n\f](半角スペース、タブ文字、復帰文字、改行文字、改ページ) \ : エスケープ文字 \\t : タブ文字ではなく、「\t」という文字列を意味する {n,m} : 直前の文字がn個以上、m個以下 {n} : 直前の文字がちょうどn文字 [ABC..] : AまたはBまたはCまたは...のいずれか1文字 ハイフンを対象にする際は、下の例のように文字の範囲の記号と扱われないよう、[]内の始めか終わりに記述 [a-z] : aまたはb...yまたはz [a-zA-Z0-9] : aまたはb...z、AまたはB...Z、0または1...9 = 半角英数字1文字 ? : 直前の文字が1文字、または無し . : 任意の1文字 * : 直前の文字が0文字以上 + : 直前の文字が1文字以上 *, +が貪欲なマッチと呼ばれ、条件に当てはまる限り、その全てを対象にする。 *?, +?は控えめなマッチと呼ばれ、最初にマッチした段階で、止まるようになる。 () : ()の中がキャプチャされる キャプチャする必要なしの場合は、(?: )のように、先頭に?:をつける (?:文字列)? : 文字列があり、またはなし [^A] : A以外の任意の1文字 [^AB] : AでもなくBでもない任意の1文字 [AB^] : AまたはBまたは^のいずれかの1文字となるので注意 ^~~ : 行頭 ^ + : 行頭からスペースが1文字以上続く ~~is $ : 行末 ^ +$ : 行頭から行末までスペースが1文字以上続く (AB|CD) : 文字列AB、またはCD 範囲を明確にするため( )、キャプチャが必要なければ(?: ) 参考 初心者歓迎!手と目で覚える正規表現入門・その1「さまざまな形式の電話番号を検索しよう」 99.99%正確な正規表現 初心者歓迎!手と目で覚える正規表現入門・その2「微妙な違いを許容しつつ置換しよう」 初心者歓迎!手と目で覚える正規表現入門・その3「空白文字を自由自在に操ろう」 現時点では、以前からお世話になっていた記事を元にまとめましたが、随時出会った正規表現を追加で更新していく予定です。
- 投稿日:2021-06-23T15:13:29+09:00
エラー解決 Did you mean?に惑わされないように
朝活の時、遷移できていたページに遷移できなくなった。 原因はGitHubでブランチを誤って削除してしまったためイチから。 上記のエラー文が出てきたがパスは絶対に間違っていないと確信があったので rails routesでパスの確認。 案の定パスが消えていたので今朝、朝活でご一緒にさせていただいている方から教えていただいた内容を 無い脳みその記憶を辿ること5分... 先ずコントローラーのにアクションを記述する必要がある。 ここで必要なアクションはnewアクションとcreateアクションなので以下のように記述。 class ItemsController < ApplicationController def index end def new end def create end 次にルーティングを設定する必要があるのでroutes.rbに以下のように記述。 resources :items, Only: [:new, :create] onlyアクションを使うことでnewアクション、createアクションのルーティングの設定完了。 ターミナルで再度、rails routesを実行すると new_item GET /items/new(.:format) items#new {:Only=>[:new, :create]} パスが復活!! なんとか手を動かしながら独力でエラー解決できた!! (朝活で教えて下さった方の説明が分かりやすかったのが1番)
- 投稿日:2021-06-23T12:15:16+09:00
【Rails】bundle installができない時の解決方法
超初心者向けです。 gemをインストールする際にbundle installを実行した時の、エラーの解決方法を紹介します。 エラー内容 ターミナル Fetching source index from https://rubygems.org/ Retrying fetcher due to error (2/4): Bundler::HTTPError Could not fetch specs from https://rubygems.org/ Retrying fetcher due to error (3/4): Bundler::HTTPError Could not fetch specs from https://rubygems.org/ bundle install を実行しところ、随分待たされた挙句に上記のエラーが発生。 試したこと gem update --system 私の場合、試してもエラーが発生。 大体は解決するそうですが、、、 解決方法 PCを再起動、もしくは、通信環境が良いところで再度チャレンジ。
- 投稿日:2021-06-23T09:59:55+09:00
テストコード データ型の注意点
単体テストコードの記述の注意点。 first_name_kana_full_width { 'タナカ' } last_name_full_width { '太朗' } last_name_kana_full_width { 'タロウ' } birthday { 1955_01_01 } 最初はこのような記述をしコミット、プッシュしていたがレビュワーの方から 生年月日(birthday)はデータ型なので値をクォーテーションで囲む必要があるとのことだった。 以下のように修正するとテストを実施する際に今まで問題なくできていた テストコードの部分でエラーがターミナルで発生... errors: Birthday can't be blank しかし付随するusers.rbの記述には以下のように記述しておりエラーの原因が分からなかった。 FactoryBot.define do factory :user do name { '田中tanaka' } email {Faker::Internet.free_email } password { '11111a' } password_confirmation { '11111a' } first_name_full_width { '田中' } first_name_kana_full_width { 'タナカ' } last_name_full_width { '太朗' } last_name_kana_full_width { 'タロウ' } birthday { "1955_01_01" } end end birthday記述しているのにと悩んでいたがどうやらクォーテーションで囲む際は アンダースコアではなくハイフンにしないといけないらしく FactoryBot.define do factory :user do name { '田中tanaka' } email {Faker::Internet.free_email } password { '11111a' } password_confirmation { '11111a' } first_name_full_width { '田中' } first_name_kana_full_width { 'タナカ' } last_name_full_width { '太朗' } last_name_kana_full_width { 'タロウ' } birthday { "1955-01-01" } end end こちらの記述にすることで無事、エラーなく単体テストコードの記述に成功。 最後になぜデータ型をクォーテーションで囲む必要があるのかというと 文字列として扱いたいからというのが私が調べた上での見解。
- 投稿日:2021-06-23T08:00:52+09:00
ransackで入力した値が、営業時間内か検索する。
はじめに 現在デートスポットのレビューサイトを作成中です。 検索した時間が、営業時間内に入っていれば表示する検索機能を実装しようとしたら、なかなか実装できませんでした。 その実装方法を記事にしてみようと思います。 modelの概要 create_table "date_spots", charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t| t.integer "genre_id" t.string "name" t.datetime "opening_time" t.datetime "closing_time" t.datetime "created_at", precision: 6, null: false t.datetime "updated_at", precision: 6, null: false end 始業時間(opening_time)と終業時間(closing_time)がデートスポットごとに決められています。 私が実装したい検索機能は、入力した時間が、openinng_timeとclosing_timeの間ならば検索できるというものです。 viewの概要 検索フォームのviewのコードになります。 <div> <%= search_form_for @date_spot_search_params, as: :date_spot_search do |f| %> <%= f.collection_select :opening_time_lteq, BusinessTime.all, :value_time, :time, {selected: @date_spot_search_params.opening_time_lteq.to_s, prompt: "来店時間"} %> <%= f.submit '検索' %> <% end %> </div> BusinessTimeAllはActiveHashで作成した時間の文字列になっています。 朝6:00から、次の日の深夜5時まで時間が登録されています。 一部分を抜粋して表示します。 {time: '00:00', value_time: '2000-01-02 00:00:00 UTC'}, {time: '00:30', value_time: '2000-01-02 00:30:00 UTC'}, {time: '01:00', value_time: '2000-01-02 01:00:00 UTC'}, {time: '02:00', value_time: '2000-01-02 02:00:00 UTC'}, {time: '03:00', value_time: '2000-01-02 03:00:00 UTC'}, {time: '04:00', value_time: '2000-01-02 04:00:00 UTC'} timeカラムはセレクトボックスに表示するために用意されたカラムで、value_timeは値として使用するために用意したカラムです。 value_timeで注目していただきたいのが、00:00時以上になると日付を変化させている点です。 日付を変化させることで、時系列を明確にしています。 このアプリケーションの営業時間カラムでは日付は表示しないので、日付は固定させています。 同じ画面にデートスポットモデル以外の検索機能も実装しているため、独自の検索キーを設定しています。 @date_spot_search_params = DateSpot.ransack(params[:date_spot_search], search_key: :date_spot_search) これを踏まえて、viewの3行目のopening_time_lteqという部分に注目していただきたいです。 <%= f.collection_select :opening_time_lteq, BusinessTime.all, :value_time, :time, {selected: @date_spot_search_params.opening_time_lteq.to_s, prompt: "来店時間"} %> これは、始業時間がtext_boxに入力した時間以下のデートスポットを全て検索するというマッチャになります。 例えば、8時から23時までの営業時間のデートスポットがあったとすると、text_boxに9時と入力すると、始業時間は8時なので検索されることになります。 始業時間以上のお店は検索できますが、閉店時間をすぎていても、このままだと検索されてしまします。 閉店時間以内に入力した時間が入っているかも同時に検索しなければなりません。 そこでコントローラで一工夫しました。 controller概要 application_controller include SessionsHelper include UsersHelper #デートスポットの名前検索の際に使用する def set_q_for_date_spot @date_spot_search_params = DateSpot.ransack(params[:date_spot_search], search_key: :date_spot_search) end #ユーザーの名前検索の際に使用する def set_q_for_user # 同時に1画面で同じパラメータを検索するため、違うパラメータを用意する。 @user_search_params = User.ransack(params[:q]) end date_spots_controller before_action :set_q_for_date_spot def index date_spot_search_params_decided = @date_spot_search_params.result.ransack(closing_time_gteq: @date_spot_search_params.opening_time_lteq) @date_spots = date_spot_search_params_decided.result end date_spots_controllerの方で解説したいと思います。 まず、3行目にあるdate_spot_search_params_decidedは、application_controllerで定義した変数@date_spot_search_paramsを使用しています。 この変数にはformから送られてきた、値が格納されています。 viewの部分で説明した通り、formで入力された値以下の始業時間のデートスポットが全て格納されています。 ここからがポイントです。 そこからさらに入力された値以上の閉店時間のデートスポットを検索します。 そのためのコードがこちらになります。 @date_spot_search_params.result.ransack(closing_time_gteq: @date_spot_search_params.opening_time_lteq) closing_time_gteqで対象の値以上の閉店時間を持つデートスポットを検索するようにします。 対象の時間は@date_spot_search_params.opening_time_lteqにformでviewで入力された値が入っています。 あとはdate_spot_params_decided.resultを行うだけで、求めていた結果が表示されました。 まとめ formに開始時間と終了時間を入力し、検索対象がその範囲に入っているかを検索する記事は多数発見しましたが、自分のように来店したい時間が営業時間の範囲に入っているか検索したい人の記事は少ないようでしたので、この記事を書きました。 まだまだ、エンジニアとしての日が浅く知識として不十分な部分がありますので、何か間違いがあるかもしれません。 間違いがあれば指摘していただけると幸いです。
- 投稿日:2021-06-23T06:43:00+09:00
ド忘れしていたRubyの基礎5選④
どうも、三町哲平です。 ド忘れしていたRubyの基礎5選の第4弾です。 今回は、コチラの問題を解く際を想定 Reverse Integer - LeetCode 解き方は、コチラを参照 Ruby Solution - LeetCode Discuss 1. 後置if # 普通のif文 if foo bar end # 後置ifを使ったif文 bar if foo 可読性の観点からも後置ifの多様には注意が必要です。 引用及び参考記事:【Ruby】乱用厳禁!?後置ifで書くとかえって読みづらくなるケース - Qiita 2. ** 四則演算 p 5**3 # => "125" べき乗されます。 ※上記の場合、5の3乗 引用及び参考記事:四則演算 - 数値と四則演算 - Ruby入門 3. ppライブラリ オブジェクトなどを見やすく出力するためのライブラリです。 pによるpretty-printされてない出力 #<PP:0x81a0d10 @stack=[], @genspace=#<Proc:0x81a0cc0>, @nest=[0], @newline="\n", @buf=#<PrettyPrint::Group:0x81a0c98 @group=0, @tail=0, @buf=[#<PrettyPrint::Gro up:0x81a0ba8 @group=1, @tail=0, @buf=[#<PrettyPrint::Text:0x81a0b30 @tail=2, @wi dth=1, @text="[">, #<PrettyPrint::Group:0x81a0a68 @group=2, @tail=1, @buf=[#<Pre ttyPrint::Text:0x81a09f0 @tail=1, @width=1, @text="1">], @singleline_width=1>, # <PrettyPrint::Text:0x81a0a7c @tail=0, @width=1, @text=",">, #<PrettyPrint::Break able:0x81a0a2c @group=2, @gensace=#<Proc:0x81a0cc0>, @newline="\n", @indent=1, @ tail=2, @sep=" ", @width=1>, #<PrettyPrint::Group:0x81a09c8 @group=2, @tail=1, @ buf=[#<PrettyPrint::Text:0x81a0950 @tail=1, @width=1, @text="2">], @singleline_w idth=1>, #<PrettyPrint::Text:0x81a0af4 @tail=0, @width=1, @text="]">], @singleli ne_width=6>], @singleline_width=6>, @sharing_detection=false> ppによるpretty-printされた出力 #<PP:0x40d0688 @buf= #<PrettyPrint::Group:0x40d064c @buf= [#<PrettyPrint::Group:0x40d05d4 @buf= [#<PrettyPrint::Text:0x40d0598 @tail=2, @text="[", @width=1>, #<PrettyPrint::Group:0x40d0534 @buf=[#<PrettyPrint::Text:0x40d04f8 @tail=1, @text="1", @width=1>], @group=2, @singleline_width=1, @tail=1>, #<PrettyPrint::Text:0x40d053e @tail=0, @text=",", @width=1>, #<PrettyPrint::Breakable:0x40d0516 @genspace=#<Proc:0x40d0656>, @group=2, @indent=1, @newline="\n", @sep=" ", @tail=2, @width=1>, #<PrettyPrint::Group:0x40d04e4 @buf=[#<PrettyPrint::Text:0x40d04a8 @tail=1, @text="2", @width=1>], @group=2, @singleline_width=1, @tail=1>, #<PrettyPrint::Text:0x40d057a @tail=0, @text="]", @width=1>], @group=1, @singleline_width=6, @tail=0>], @group=0, @singleline_width=6, @tail=0>, @genspace=#<Proc:0x40d0656>, @nest=[0], @newline="\n", @sharing_detection=false, @stack=[]> 引用及び参考記事:library pp (Ruby 3.0.0 リファレンスマニュアル) 引用及び参考記事:【ruby】p pp puts print 違い。 - Qiita 4. * 可変長引数 *をつければ引数を、配列に指定できます。 #複数の引数を配列にする def array(*a) pp a end > array(1,2) #=> [1, 2] > array(1, 2, 3) #=> [1, 2, 3] 引用及び参考記事:メソッドの引数にアスタリスク - Qiita 5. ** オプション引数 **をつければ引数を、ハッシュに指定できます。 #複数の引数をハッシュにする def array(**a) pp a end > array(b: 1, c: 2) #=> {:b=>1, :c=>2} > array(b: 1, c: 2, d: 3) #=> {:b=>1, :c=>2, :d=>3} 引用及び参考記事:メソッドの引数にアスタリスク - Qiita
- 投稿日:2021-06-23T00:26:05+09:00
reverse_markdown
マークダウン形式の変換に便利!「reverse_markdown」 参考Github reverse_markdown 実装経緯 (※今回ウィジウィグ・CSV出力については割愛させていただきます。) ウィジウィグ使用して保存されているHTML形式データを取り出してマークダウン形式に変換 それをCSV出力 上記を行う事があったため reverse_markdown(gem) を使用して実装してみた。(ありがたや...) 忘れないようにメモ。 結論 テスト的に下記のようなHTML文を変換すると... <h3>項目タイトル</h3> <p><a href="https://www.test.co.jp/">test</a></p> <p><img alt="【画像出典元】" height="165" src="https://www.test.com/"></p> <p> </p> <p> </p> <p> </p> <p><u>小見出し線</u></p> <p><strong>太字</strong></p> <p> </p> <p>改行<br /> 改行</p> <p><a href="http://www.yahoo.co.jp" rel="nofollow noopener" target="_blank"><img alt="test" loading="lazy" src="sakura.jpg" /></a></p> <p><a href="sakura.jpg" rel="noopener noreferrer" target="_blank"><img alt="test" src="test.jpg" style="max-width:100%;" /></a></p> こうなった(CSV出力データ) 1.項目タイトル ### 項目タイトル ## 項目タイトル [test](https://www.test.co.jp/)  <u>小見出し線</u> **太字** 改行 改行 [](http://www.test.co.jp) [](test.jpg) マークダウン形式を変換してくれるサイト(Qiita)にコピペしたらこんな感じ(下記) test 小見出し線 太字 改行 改行 わかったこと(箇条書き) brタグ、pタグは変換時に消える(gsubで対応すればなんとかなる) figureタグに囲まれている画像データ(img要素)は変換されない 上記には記載していないがuタグ(小見出し線)などもちゃんと出力できた。 その他は試してないので分からないので(ご自身でお試しください。) 使い方(基本機能のみ) ReverseMarkdown.convert 変数 #変数にはHTML要素を格納した変数 これだけ! 他にもオプションなどがある様でしたが、今回は基本的なものだけを使わせていただきました。 興味がある方ははじめに記載した参考Github「reverse_markdown」からご確認ください。
- 投稿日:2021-06-23T00:09:06+09:00
[Ruby] Arrayメソッドで使うfirstとlastとそれ以外について
Rubyには配列メソッドとしてfirstとlastというものが存在します。 [0,1,2].first => 0 [0,1,2].last => 1 それぞれ配列の要素の最初と最後を取得するメソッドです。 それ以外のやり方としてsecond,third,fourth,fifthがあります。 使い方は以下のようになります [1,2,3,4,5].second => 2 [1,2,3,4,5].third => 3 [1,2,3,4,5].fourth => 4 [1,2,3,4,5].fifth => 5 それぞれ配列の2,3,4,5番目から要素を取得します。
- 投稿日:2021-06-23T00:09:06+09:00
[Rails] Arrayメソッドで使うfirstとlastとそれ以外について
環境 ruby 2.6.6 Rails 6.0.3.6 本題 Rubyには配列メソッドとしてfirstとlastというものが存在します。 [0,1,2].first => 0 [0,1,2].last => 1 それぞれ配列の要素の最初と最後を取得するメソッドです。 それに加えてRailsには新たなメソッドsecond,third,fourth,fifthがあります。 ※これらはRailsのメソッドであり,Rubyの組込みメソッドではありません。 使い方は以下のようになります [1,2,3,4,5].second => 2 [1,2,3,4,5].third => 3 [1,2,3,4,5].fourth => 4 [1,2,3,4,5].fifth => 5 それぞれ配列の2,3,4,5番目から要素を取得します。