- 投稿日:2021-01-15T23:46:18+09:00
Active Storageで動画投稿
★Active Storageは画像、動画が投稿できる
Active Storageで動画の投稿している記事が少なかったので
carrier waveは使わず、こっちでチャレンジしました!!❶バリデーションに形式を記述
validates:videoの部分は現状presenseが適用されていない。。
video_type→携帯撮影をする程で作成しているためtypeで指定
video_size→10秒で20MB程の為、一応指定#app/models/post.rb class Post < ApplicationRecord #省略 with_options presence: true do validates :title validates :price, format: { with: /\A[-]?[0-9]+(\.[0-9]+)?\z/} validates_inclusion_of :price, in: 500..5000 validates :video end validate :video_type validate :video_size private def video_type if !video.blob.content_type.in?(%('video/quicktime video/quicktime')) errors.add(:video, '動画は携帯で撮影したmov形式でアップロードしてください') end end def video_size if video.blob.byte_size > 20.megabytes errors.add(:video, "動画を短く撮影し直してください(20MB以内)") end end end❷ビューファイルにvideoタグを記述
#app/views/posts/index.html.erb class PostsController < ApplicationController #省略 <video src=<%= rails_blob_path(post.video) %> type="video/mov", controls></video>★Active Storageは便利…
★ 今回の教科書はこちら
- 投稿日:2021-01-15T23:24:18+09:00
FizzBuzz問題の解説
FizzBuzz問題 解説
ステップ1
1〜100までの数字をターミナルに出力ステップ2
「3の倍数」のときは数字の代わりに文字列でFizz
「5の倍数」のときも同様にBuzzステップ3
3と5の倍数である「15の倍数」のときはFizzBuzzと出力
プログラムのひな形
雛形def fizz_buzz # 処理の中身 end fizz_buzz
1〜100までの数字をターミナルに出力するためのプログラム
ステップ1
1〜100までの数字をターミナルに出力
1〜100までの数字をターミナルに出力するdef fizz_buzz num = 1 # num = 1から始まる処理の中身 while num <= 100 do # 真偽値がtrueである限りは以下の処理が続く puts num # 出力される数値 num = num + 1 # 真偽値に +1 end # 処理の中身 end fizz_buzz
「3の倍数」のときは文字列でFizz、「5の倍数」のときはBuzzと出力する
ステップ2
「3の倍数」のときは数字の代わりに文字列でFizz
「5の倍数」のときも同様にBuzz
「3の倍数」のときは文字列でFizz、「5の倍数」のときはBuzzと出力def fizz_buzz num = 1 # num = 1から始まる処理の中身 while num <= 100 do # 真偽値がtrueである限りは以下の処理が続く if num % 3 == 0 # 3で割った余りが0の時 puts "Fizz" elsif num % 5 == 0 # 5で割った余りが0の時 puts "Buzz" else # それ以外のとき puts num # 上記の3の倍数・5の倍数に当たらない数値はそのまま出力 end num = num + 1 # 真偽値に +1 end # 処理の中身 end fizz_buzz「15の倍数(3と5の倍数)」の時についての条件式を追加する
ステップ3
3と5の倍数である「15の倍数」のときはFizzBuzzと出力
「15の倍数(3と5の倍数)」の時の条件式を追加def fizz_buzz num = 1 # num = 1から始まる処理の中身 while (num <= 100) do # 真偽値がtrueである限りは以下の処理が続く if num % 15 == 0 # 15の倍数のとき puts "FizzBuzz" elsif (num % 3) == 0 # 3で割った余りが0の時 puts "Fizz" elsif (num % 5) == 0 # 5で割った余りが0の時 puts "Buzz" else # それ以外のとき puts num #上記の3・5・15の倍数に当たらない数値はそのまま出力 end num = num + 1 # 真偽値に +1 end # 処理の中身 end fizz_buzz注意点
下に追加してしまうと上記の「3の倍数であるか」または「5の倍数であるか」という条件式が真になり、FizzまたはBuzzと表示されてしまう。以上の点からif文の最初に「15の倍数」のときはFizzBuzzと出力する条件を追加。
- 別解 「15の倍数であるとき」の条件式
- 上記で取り上げたのは
num % 15 == 0
num % 3 == 0 && num % 5 == 0
のように置き換える事も可能。
- 投稿日:2021-01-15T22:31:05+09:00
【Rails】seed.rbで初期データを作成する【Faker】【日本語化】
はじめに
- 筆者のスペック
- Rails学習歴:4ヶ月
- 条件
- Rails 6.0.3.2
- Ruby 2.6.6
そのため間違いなどがある可能性があるので鵜呑み厳禁!
gem「Faker」をinstall
- Gemfileに
gem 'faker
を追加- ターミナル上で
bundle install
を実行Gemfilegem 'faker'ターミナル$ bundle install「Faker」を日本語化する
- Rails全体を日本語化する OR Fakerのみを日本語化する
config/initializers/locale.rbRails.application.config.i18n.default_locale = :ja ##Rail全体を日本語化する場合 Faker::Config.locale = :ja ##Faker単体のみ日本語化する場合 ## どちらか片方のみでも設定すれば日本語化できます「Faker」の日本語訳をカスタマイズする
- Githubからja.ymlをダウンロードしてくる
- ja.ymlを
config/locales/faker
に配置するconfig.i18n.load_path
でconfig/locales以下のどの階層のディレクトリも読み込ませるようにする- ダウンロードしてきたja.ymlを自分好みにカスタマイズする
ターミナル## wget 【ダウンロードしたいファイルのURL】 -P 【ダウンロードしたファイルを格納したい場所】 $ wget https://raw.githubusercontent.com/faker-ruby/faker/master/lib/locales/ja.yml -P config/locales/fakerconfig/application.rbmodule Club class Application < Rails::Application ... config.i18n.load_path += Dir[Rails.root.join('config', 'locales', '**', '*.{rb,yml}').to_s] ... end endseed.rbでデモデータの投入
前提条件として
Userモデル
,Groupモデル
とその2つの中間テーブルGroupUserモデル
に初期データを投入していく。
1. 初期設定で作成するデモデータの数を決める。
2. テストデータでテストユーザが使用するデータを投入する。
3. ユーザーとグループを作成する。
4. ランダム性として1/5の確率でユーザーがグループに加入するようにする。
5.rails db:migrate:reset
でDBをリセットして、rails db:seed
で初期データを流し込む。db/seeds.rb# 初期設定 users_number = 20 groups_number = 20 # テストデータ User.create!(name: '佐藤 隆起', email: 'n1@example.com', password: 'password', description: '皆さんよろしくお願いします!!!') Group.create!(name: '水泳', description: '県優勝を目指して日々切磋琢磨しています。') GroupUser.create!(group_id: 1, user_id: 1) # ユーザー users_list = [] users_number.times do users_list << {name: Faker::Name.unique.name, email: Faker::Internet.unique.email, password: 'password', description: 'よろしく!'} end User.create!(users_list) # グループ groups_list = [] groups_number.times do groups_list << {name: Faker::Team.unique.sport, description: 'みんなぜひ入部してください!!'} end Group.create!(groups_list) # 1/5の確率で1つのグループに加入する group_users_list = [] User.all.ids.each do |user_id| Group.all.ids.each do |group_id| if rand(5) == 0 && (user_id != 1 && group_id != 1) group_users_list << { user_id: user_id, group_id: group_id} end end end GroupUser.create!(group_users_list)ターミナル$ rails db:migrate:reset $ rails db:seed配列を用いて複数のデータを一括で作成する
users_list
で今回作成するユーザを格納する。(今回は特に使用しないが後々便利になるかも)users_number.times
を用いて、指定の回数分ユーザを作成する。- 1回のブロック処理で
users_list
の中に1人のユーザの情報を格納していく。User.create!(users_list)
でusers_list
に格納されている全てのユーザを作成する。users_list = [] users_number.times do users_list << {name: Faker::Name.unique.name, email: Faker::Internet.unique.email, password: 'password', description: 'よろしく!'} end User.create!(users_list)参考するべき文献
create と create!の使い分けrand()を用いて不規則にデータを作成する
誰がどのグループに加入するかこちらで決めるのは面倒なので
rand()
を用いて簡易化します。
1.User.all.ids.each
、Group.all.ids.each
で全てのユーザ、グループのIDを取得する。
2.if rand(5) == 0
で1/5の確率で実行するようにする。(※rand(5)は0~4の数字をランダムに出力する。)
3.(user_id != 1 && group_id != 1)
は上でテストデータとして登録しているので重複しないように除外する。
4.GroupUser.create!(group_users_list)
でgroup_users_list
に格納されている全てのデータを作成する。group_users_list = [] User.all.ids.each do |user_id| Group.all.ids.each do |group_id| if rand(5) == 0 && (user_id != 1 && group_id != 1) group_users_list << { user_id: user_id, group_id: group_id} end end end GroupUser.create!(group_users_list)参考にすべき文献
【Ruby入門】randomを使いこなす!【数値、文字列、array、secure】
【Ruby eachの応用編】さまざまな使い方を網羅的に理解しよう
【Rails】アソシエーションの初期データを作ってみた
- 投稿日:2021-01-15T21:21:02+09:00
【初心者目線】RubyでFizzBuzz問題を"簡単に"解いてみた!
この記事では、FizzBuzz問題についてまとめてみました!
皆さんはプログラミングの学習を初めてから、だいぶ初期にこの問題を目にしたのではないでしょうか。
FizzBuzz問題は初心者にもそこまで難しくない問題で、プログラミングの効果的な学習材料となります。
できるだけ初心者向けでわかりやすいように頑張りますので、是非最後までお付き合いください!
FizzBuzz問題とは?
プログラミングで言うFizzBuzz問題とは、3で割ることのできる数字を入力したら"Fizz"、5で割ることのできる数字を入力したら"Buzz"、または両者で割ることのできる数字(すなわち15で割ることができる数字)を入力したら"FizzBuzz"と返事が来るプログラムを書く問題のことです。
Rubyの入門参考書などを買うとよく載っているので、皆さんの中には実際に解いたことがあると言う人も少なくないでしょう。
FizzBuzzの書き方・考え方
まずは完成形のコードをみてみましょう。
FizzBuzz.rbdef FizzBuzz(n) if n % 15 == 0 'FizzBuzz' elsif n % 3 == 0 'Fizz' elsif n % 5 == 0 'Buzz' else n.to_s end endまず、nを15で割った時にあまりが0、すなわち15で割ることができる数字を入力されたら'FizzBuzz'と返すように命令しています。
次に3で割れる数字を入力されたら'Fizz'、5で割ることができる数字を入力されたら'Buzz'と返すようにしています。
最後に、それ以外の数字が入力された場合、その数字を文字列に変換して返すように命令しています。
実際にこのプログラムを実行してみましょう。
まず、今書いたメソッドの下に、メソッドを使った命令を書いてみます。
FizzBuzz.rbdef FizzBuzz(n) if n % 15 == 0 'FizzBuzz' elsif n % 3 == 0 'Fizz' elsif n % 5 == 0 'Buzz' else n.to_s end end FizzBuzz(1) FizzBuzz(3) FizzBuzz(4) FizzBuzz(5) FizzBuzz(10) FizzBuzz(15)そして、ターミナルに以下の命令をします。
$ ruby lib/FizzBuzz.rbこれは、libと言うディレクトリ(フォルダ)の直下にあるFizzBuzz.rbと言うファイルを実行しろと言う命令です。
実際に実行してみると、下記の結果が出るはずです。
1 Fizz 4 Buzz 10 FizzBuzzこれと同じように結果が出れば成功となります!
まとめ
FizzBuzz問題は初心者の登竜門的なプログラミングの練習問題です。
3の倍数を入力したらFizz、5だとBuzz、両方の倍数だとFizzBuzzが出力されるようにプログラムを書きます。
どうだったでしょうか?わかりにくいところなどがあったら教えていただけると幸いです!
この記事のリライトの材料にもなりますし、僕自身の成長にも繋がるので是非お意見お待ちしております!
- 投稿日:2021-01-15T20:28:51+09:00
heroku rake db:migrateが失敗してエラーが出た時の対処
プログラミング初心者で勉強中の身ですが、ググってもなかなか情報が出なかったので報告します。
環境
Mac OS X
Ruby: 2.7.1
Rails: 6.0.3.4
heroku stack: heroku-20(エラーが出た時)
DB: MYSQL起こったこと
https://qiita.com/murakami-mm/items/9587e21fc0ed57c803d0
こちらの記事を参考にherokuへのデプロイを試みたところ、console$ heroku rake db:migrateを実行した時に
consoleMysql2::Error::ConnectionError: SSL connection error: error:1425F102:SSL routines:ssl_choose_client_version:unsupported protocol
とエラーが出た。
原因・対処
原因
自身の環境でこのエラーが起きた原因は二つ
➀heroku stackが heroku-20であった
➁config/database.ymlに必要な記述が無かった対処
➀heroku stackが heroku-20であった
→heroku-18を利用できるようにするconsoleheroku stack:set heroku-18 -a <app name>➁config/database.ymlに必要な記述が無かった
productionに以下のコードを追加config/database.ymlproduction: url: <%= ENV['DATABASE_URL'] %>理由
きちんとした理由は随時募集しています。。誰か教えてください。。
➀heroku stackが heroku-20であった
heroku-20に変わることで出てきた、書くべき必要な処理が書けていなかった?
→これまでのheroku-18にすることでとりあえず解決➁config/database.ymlに必要な記述が無かった
config/database.ymlにはこんな記述があるらしいです(自分は何故か消えていた)
config/database.yml# As with config/credentials.yml, you never want to store sensitive information, # like your database password, in your source code. If your source code is # ever seen by anyone, they now have access to your database. # # Instead, provide the password as a unix environment variable when you boot # the app. Read https://guides.rubyonrails.org/configuring.html#configuring-a-database # for a full rundown on how to provide these environment variables in a # production deployment. # # On Heroku and other platform providers, you may have a full connection URL # available as an environment variable. For example: # # DATABASE_URL="mysql2://myuser:mypass@localhost/somedatabase" # # You can use this database configuration with: # # production: # url: <%= ENV['DATABASE_URL'] %>特に今回は後半の記述が大切で
config/database.yml# On Heroku and other platform providers, you may have a full connection URL # available as an environment variable. For example: # # DATABASE_URL="mysql2://myuser:mypass@localhost/somedatabase" # # You can use this database configuration with: # # production: # url: <%= ENV['DATABASE_URL'] %>雑に和訳すると
「Herokuや他のプラットフォーム上で、あなたは環境変数の形で様々なURLをもらえるでしょう。こんなかんじで:
DATABASE_URL="mysql2://myuser:mypass@localhost/somedatabase"
あなたはこのデータ構成をこのように使えます
production:
url: <%= ENV['DATABASE_URL'] %>」要はこの記述が無かったことで、どこのデータベースを使えばいいか分からずにエラーが出ていた。
まとめ
何かおかしいことが起こり、ググっても分からないときは使っているもののバージョンを確認してみよう!
読んでいただきありがとうございました。
- 投稿日:2021-01-15T20:28:51+09:00
heroku rake db:migrateでエラーが出た時の対処
プログラミング初心者で勉強中の身ですが、ググってもなかなか情報が出なかったので報告します。
環境
Mac OS X
Ruby: 2.7.1
Rails: 6.0.3.4
heroku stack: heroku-20(エラーが出た時)
DB: MYSQL起こったこと
https://qiita.com/murakami-mm/items/9587e21fc0ed57c803d0
こちらの記事を参考にherokuへのデプロイを試みたところ、console$ heroku rake db:migrateを実行した時に
consoleMysql2::Error::ConnectionError: SSL connection error: error:1425F102:SSL routines:ssl_choose_client_version:unsupported protocol
とエラーが出た。
原因・対処
原因
自身の環境でこのエラーが起きた原因は二つ
➀heroku stackが heroku-20であった
➁config/database.ymlに必要な記述が無かった対処
➀heroku stackが heroku-20であった
→heroku-18を利用できるようにするconsoleheroku stack:set heroku-18 -a <app name>➁config/database.ymlに必要な記述が無かった
productionに以下のコードを追加config/database.ymlproduction: url: <%= ENV['DATABASE_URL'] %>理由
きちんとした理由は随時募集しています。。誰か教えてください。。
➀heroku stackが heroku-20であった
heroku-20に変わることで出てきた、書くべき必要な処理が書けていなかった?
→これまでのheroku-18にすることでとりあえず解決➁config/database.ymlに必要な記述が無かった
config/database.ymlにはこんな記述があるらしいです(自分は何故か消えていた)
config/database.yml# As with config/credentials.yml, you never want to store sensitive information, # like your database password, in your source code. If your source code is # ever seen by anyone, they now have access to your database. # # Instead, provide the password as a unix environment variable when you boot # the app. Read https://guides.rubyonrails.org/configuring.html#configuring-a-database # for a full rundown on how to provide these environment variables in a # production deployment. # # On Heroku and other platform providers, you may have a full connection URL # available as an environment variable. For example: # # DATABASE_URL="mysql2://myuser:mypass@localhost/somedatabase" # # You can use this database configuration with: # # production: # url: <%= ENV['DATABASE_URL'] %>特に今回は後半の記述が大切で
config/database.yml# On Heroku and other platform providers, you may have a full connection URL # available as an environment variable. For example: # # DATABASE_URL="mysql2://myuser:mypass@localhost/somedatabase" # # You can use this database configuration with: # # production: # url: <%= ENV['DATABASE_URL'] %>雑に和訳すると
「Herokuや他のプラットフォーム上で、あなたは環境変数の形で様々なURLをもらえるでしょう。こんなかんじで:
DATABASE_URL="mysql2://myuser:mypass@localhost/somedatabase"
あなたはこのデータ構成をこのように使えます
production:
url: <%= ENV['DATABASE_URL'] %>」要はこの記述が無かったことで、どこのデータベースを使えばいいか分からずにエラーが出ていた。
まとめ
何かおかしいことが起こり、ググっても分からないときは使っているもののバージョンを確認してみよう!
読んでいただきありがとうございました。
- 投稿日:2021-01-15T20:05:38+09:00
投稿を1段3つずつに分割する(each_sliceメソッド)
本記事の目的
- railsで投稿を1段3分割にするやり方の共有
→作成するにあたり、意外にやり方が見つからなかったため記事の対象
やり方
each_slice
メソッドを使う。each_sliceメソッドはブロックに渡す要素数を指定することができる。
例えば、、、
sample.rbnumbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] numbers.each_slice(2) do |number1, number2| puts " #{number1} : #{number2}" end↓↓↓↓↓↓↓↓↓↓↓
terminal% ruby sample.rb 1 : 2 3 : 4 5 : 6 7 : 8 9 : 10
もし、配列の要素数が割り切れないと、最後の段のみ要素数が減るsample.rbnumbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] numbers.each_slice(3) do |number1, number2, number3| puts " #{number1} : #{number2}: #{number3}" end↓↓↓↓↓↓↓↓↓↓↓
terminal% ruby sample.rb 1 : 2: 3 4 : 5: 6 7 : 8: 9 10 : :実践
上で示したeach_sliceメソッドの使い方でだいたい検討がつくと思うが、使い方は同じ。
コントローラーのアクション内データベースから全ての投稿情報を取得し、インスタンス変数に代入してから、そのインスタンス変数に対して、each_sliceメソッドを使う。
自分のアプリケーションでは、ユーザー詳細ページ内で、ログインしているユーザーが投稿した情報を全て表示している。
users_controller.rbdef show @records = @user.records.order("date DESC") # ログインしユーザーの投稿を、選択した日付の降順で並び替えて全て取得 end # @userには現在ログインしているユーザーの情報が代入されている。 # userモデルとrecord(記録)モデルを扱っているが、一対多の関係になっている。show.html.erb<div class="card-space"> <% @records.each_slice(3) do |record1,record2,record3| %> <div class="array-space"> <% if record1 != nil %> <div class="card"> <div class="date-wrapper"> <% time1 = record1.date %> <div class="selected-date"><%= record1.date.strftime("%Y/%m/%d(#{@wd[time1.wday]})") %></div> <div class="last-updated-date">最終更新日時:<%= record1.updated_at.to_s(:datetime_jp) %></div> </div> <div class="skip-time-space"> <label class="question-rabel">どのくらいサボった?</label> <div class="skip-time"> <%= "#{record1.time}分" %> </div> </div> <div class="text-space"> <label class="question-rabel">何してた?</label> <div class="what-skip"> <%= record1.skip %> </div> <label class="question-rabel">何すべきだった?</label> <div class="to-do"> <%= record1.to_do %> </div> </div> <div class="update-btns"> <%= link_to edit_record_path(record1.id), method: :get, class:"edit-link" do %> <i class="far fa-edit fa-2x"></i> <% end %> <%= link_to record_path(record1.id), method: :delete, class:"delete-link" do %> <i class="far fa-trash-alt fa-2x"></i> <% end %> </div> </div> <% end %> <% if record2 != nil %> <div class="card"> <div class="date-wrapper"> <% time2 = record2.date %> <div class="selected-date"><%= record2.date.strftime("%Y/%m/%d(#{@wd[time2.wday]})") %></div> <div class="last-updated-date">最終更新日時:<%= record2.updated_at.to_s(:datetime_jp) %></div> </div> <div class="skip-time-space"> <label class="question-rabel">どのくらいサボった?</label> <div class="skip-time"> <%= "#{record2.time}分" %> </div> </div> <div class="text-space"> <label class="question-rabel">何してた?</label> <div class="what-skip"> <%= record2.skip %> </div> <label class="question-rabel">何すべきだった?</label> <div class="to-do"> <%= record2.to_do %> </div> </div> <div class="update-btns"> <%= link_to edit_record_path(record2.id), method: :get, class:"edit-link" do %> <i class="far fa-edit fa-2x"></i> <% end %> <%= link_to record_path(record2.id), method: :delete, class:"delete-link" do %> <i class="far fa-trash-alt fa-2x"></i> <% end %> </div> </div> <% end %> <% if record3 != nil %> <div class="card"> <div class="date-wrapper"> <% time3 = record3.date %> <div class="selected-date"><%= record3.date.strftime("%Y/%m/%d(#{@wd[time3.wday]})") %></div> <div class="last-updated-date">最終更新日時:<%= record3.updated_at.to_s(:datetime_jp) %></div> </div> <div class="skip-time-space"> <label class="question-rabel">どのくらいサボった?</label> <div class="skip-time"> <%= "#{record3.time}分" %> </div> </div> <div class="text-space"> <label class="question-rabel">何してた?</label> <div class="what-skip"> <%= record3.skip %> </div> <label class="question-rabel">何すべきだった?</label> <div class="to-do"> <%= record3.to_do %> </div> </div> <div class="update-btns"> <%= link_to edit_record_path(record3.id), method: :get, class:"edit-link" do %> <i class="far fa-edit fa-2x"></i> <% end %> <%= link_to record_path(record3.id), method: :delete, class:"delete-link" do %> <i class="far fa-trash-alt fa-2x"></i> <% end %> </div> </div> <% end %> </div> <% end %> </div>実際には部分テンプレートを用いているので、一部記述を変えてある。
ここでポイントになるのは、各投稿を表示するコードを、
<% if record1 != nil %> <% end %> <% if record2 != nil %> <% end %> <% if record3 != nil %> <% end %>この様に、分割したブロック変数がnilでなければ表示するという囲いを作ることである。
先ほどの、each_sliceメソッドの紹介の例では、要素数がeach_sliceメソッドで指定した値で割り切れない時は、最後の段のみ要素数が減ったが、Railsでは上記の様に囲いがないと、ビューファイルをレンダリングする時にエラーが発生する。
参考記事
Qiita(@ota-yukiさん)「each_sliceメソッドの使い方」
https://qiita.com/ota-yuki/items/ad91ffa8e95108ba3ef7
(2020年12月頭頃に閲覧)Rubyリファレンスマニュアル「instance method Enumerable#each_slice」
https://docs.ruby-lang.org/ja/latest/method/Enumerable/i/each_slice.html
(2021年1月15日閲覧)
- 投稿日:2021-01-15T18:38:02+09:00
[Ruby] VSCodeのdebug実行でエラー「cannot load such file」が出た際の対処
原因
${file}
はフルパスに置き換えられることが原因でした。"${workspaceRoot}/${file}"
ではなく"${file}"
と記載すればOKでした。
@nodai2h_ITC さん、ご指摘ありがとうございました。実行環境
macOS (version: 11.1)
VSCode (version: 1.51.1)
Ruby (version: 2.7.2)対処法
launch.json内の
program
の指定を、"${workspaceRoot}/${file}"
ではなく"${file}"
に変更すればOKlaunch.json{ // Use IntelliSense to learn about possible attributes. // Hover to view descriptions of existing attributes. // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 "version": "0.2.0", "configurations": [ { "name": "Debug Local File", "type": "Ruby", "request": "launch", //"program": "${workspaceRoot}/${file}", "program": "${file}", "useBundler": true } ] }エラー内容
Uncaught exception: cannot load such file -- /Users/kzyonzw/works/ruby/Users/kzyonzw/works/ruby/main.rb /Users/kzyonzw/.rbenv/versions/2.7.2/bin/rdebug-ide:23:in `load' /Users/kzyonzw/.rbenv/versions/2.7.2/bin/rdebug-ide:23:in `<top (required)>' /Users/kzyonzw/.rbenv/versions/2.7.2/lib/ruby/gems/2.7.0/gems/bundler-2.2.5/lib/bundler/cli/exec.rb:63:in `load' /Users/kzyonzw/.rbenv/versions/2.7.2/lib/ruby/gems/2.7.0/gems/bundler-2.2.5/lib/bundler/cli/exec.rb:63:in `kernel_load' /Users/kzyonzw/.rbenv/versions/2.7.2/lib/ruby/gems/2.7.0/gems/bundler-2.2.5/lib/bundler/cli/exec.rb:28:in `run' /Users/kzyonzw/.rbenv/versions/2.7.2/lib/ruby/gems/2.7.0/gems/bundler-2.2.5/lib/bundler/cli.rb:494:in `exec' /Users/kzyonzw/.rbenv/versions/2.7.2/lib/ruby/gems/2.7.0/gems/bundler-2.2.5/lib/bundler/vendor/thor/lib/thor/command.rb:27:in `run' /Users/kzyonzw/.rbenv/versions/2.7.2/lib/ruby/gems/2.7.0/gems/bundler-2.2.5/lib/bundler/vendor/thor/lib/thor/invocation.rb:127:in `invoke_command' /Users/kzyonzw/.rbenv/versions/2.7.2/lib/ruby/gems/2.7.0/gems/bundler-2.2.5/lib/bundler/vendor/thor/lib/thor.rb:392:in `dispatch' /Users/kzyonzw/.rbenv/versions/2.7.2/lib/ruby/gems/2.7.0/gems/bundler-2.2.5/lib/bundler/cli.rb:30:in `dispatch' /Users/kzyonzw/.rbenv/versions/2.7.2/lib/ruby/gems/2.7.0/gems/bundler-2.2.5/lib/bundler/vendor/thor/lib/thor/base.rb:485:in `start' /Users/kzyonzw/.rbenv/versions/2.7.2/lib/ruby/gems/2.7.0/gems/bundler-2.2.5/lib/bundler/cli.rb:24:in `start' /Users/kzyonzw/.rbenv/versions/2.7.2/lib/ruby/gems/2.7.0/gems/bundler-2.2.5/exe/bundle:49:in `block in <top (required)>' /Users/kzyonzw/.rbenv/versions/2.7.2/lib/ruby/gems/2.7.0/gems/bundler-2.2.5/lib/bundler/friendly_errors.rb:130:in `with_friendly_errors' /Users/kzyonzw/.rbenv/versions/2.7.2/lib/ruby/gems/2.7.0/gems/bundler-2.2.5/exe/bundle:37:in `<top (required)>' /Users/kzyonzw/.rbenv/versions/2.7.2/bin/bundle:23:in `load' /Users/kzyonzw/.rbenv/versions/2.7.2/bin/bundle:23:in `<main>' bundler: failed to load command: rdebug-ide (/Users/kzyonzw/.rbenv/versions/2.7.2/bin/rdebug-ide) /Users/kzyonzw/.rbenv/versions/2.7.2/lib/ruby/gems/2.7.0/gems/ruby-debug-ide-0.7.2/lib/ruby-debug-ide.rb:106:in `debug_load': cannot load such file -- /Users/kzyonzw/works/ruby/Users/kzyonzw/works/ruby/main.rb (LoadError) from /Users/kzyonzw/.rbenv/versions/2.7.2/lib/ruby/gems/2.7.0/gems/ruby-debug-ide-0.7.2/lib/ruby-debug-ide.rb:106:in `debug_program' from /Users/kzyonzw/.rbenv/versions/2.7.2/lib/ruby/gems/2.7.0/gems/ruby-debug-ide-0.7.2/bin/rdebug-ide:193:in `<top (required)>' from /Users/kzyonzw/.rbenv/versions/2.7.2/bin/rdebug-ide:23:in `load' from /Users/kzyonzw/.rbenv/versions/2.7.2/bin/rdebug-ide:23:in `<top (required)>' from /Users/kzyonzw/.rbenv/versions/2.7.2/lib/ruby/gems/2.7.0/gems/bundler-2.2.5/lib/bundler/cli/exec.rb:63:in `load' from /Users/kzyonzw/.rbenv/versions/2.7.2/lib/ruby/gems/2.7.0/gems/bundler-2.2.5/lib/bundler/cli/exec.rb:63:in `kernel_load' from /Users/kzyonzw/.rbenv/versions/2.7.2/lib/ruby/gems/2.7.0/gems/bundler-2.2.5/lib/bundler/cli/exec.rb:28:in `run' from /Users/kzyonzw/.rbenv/versions/2.7.2/lib/ruby/gems/2.7.0/gems/bundler-2.2.5/lib/bundler/cli.rb:494:in `exec' from /Users/kzyonzw/.rbenv/versions/2.7.2/lib/ruby/gems/2.7.0/gems/bundler-2.2.5/lib/bundler/vendor/thor/lib/thor/command.rb:27:in `run' from /Users/kzyonzw/.rbenv/versions/2.7.2/lib/ruby/gems/2.7.0/gems/bundler-2.2.5/lib/bundler/vendor/thor/lib/thor/invocation.rb:127:in `invoke_command' from /Users/kzyonzw/.rbenv/versions/2.7.2/lib/ruby/gems/2.7.0/gems/bundler-2.2.5/lib/bundler/vendor/thor/lib/thor.rb:392:in `dispatch' from /Users/kzyonzw/.rbenv/versions/2.7.2/lib/ruby/gems/2.7.0/gems/bundler-2.2.5/lib/bundler/cli.rb:30:in `dispatch' from /Users/kzyonzw/.rbenv/versions/2.7.2/lib/ruby/gems/2.7.0/gems/bundler-2.2.5/lib/bundler/vendor/thor/lib/thor/base.rb:485:in `start' from /Users/kzyonzw/.rbenv/versions/2.7.2/lib/ruby/gems/2.7.0/gems/bundler-2.2.5/lib/bundler/cli.rb:24:in `start' from /Users/kzyonzw/.rbenv/versions/2.7.2/lib/ruby/gems/2.7.0/gems/bundler-2.2.5/exe/bundle:49:in `block in <top (required)>' from /Users/kzyonzw/.rbenv/versions/2.7.2/lib/ruby/gems/2.7.0/gems/bundler-2.2.5/lib/bundler/friendly_errors.rb:130:in `with_friendly_errors' from /Users/kzyonzw/.rbenv/versions/2.7.2/lib/ruby/gems/2.7.0/gems/bundler-2.2.5/exe/bundle:37:in `<top (required)>' from /Users/kzyonzw/.rbenv/versions/2.7.2/bin/bundle:23:in `load' from /Users/kzyonzw/.rbenv/versions/2.7.2/bin/bundle:23:in `<main>'
- 投稿日:2021-01-15T18:16:43+09:00
Jenkins+CapistranoでUser credentialをデプロイ先へ渡す
Capistranoで実行元の環境変数をデプロイ先へ送るにはどうすれば良いか?
direnv
でAccess token
をセットして使うなどはAWS CLI
の常套手段だったりします。
また環境変数にUser credentialを保持させるのはわりとある手段です。Credentialには
username
,password
,access token
, ... と沢山あります。
これをデプロイ先でも使いたい場合のお話です。Jenkins User Handbookを読む
Jenkins User Handbook にドンピシャに書いてあったりします。
これで
pipeline
-stage
-steps
の中で参照できます。このセクションにはもうひとつのやり方が記述されています。
JenkinsにCredential情報を持たせることができますよね。(GUIで操作するヤツです)
アレを参照できるんです。Usernames and passwords - Using a Jenkinsfile
ただちょっとトリッキーなので解説。
hoge_credential
として登録しますJenkinsfile
に使うように指示を追加します。- 参照する時
- user:
USER_CREDENTIAL_USR
- password:
USER_CREDENTIAL_PSW
Jenkinsfile
のサンプルJenkinsfilepipeline { environment { USER_CREDENTIAL = credentials('$hoge_credential') } }こうなります。
Capistranoから環境変数をリモートへ送る
Jenkinsfile
の中で参照できましたのでCapistrano
からもENV
として参照できます。先程の
USER_CREDENTIAL
で書くと、deploy.rbnamespace :deploy do tasks :sample-task do on roles(:app) do with hoge_user: ENV['USER_CREDENTIAL_USR'] do with hoge_passwd: ENV['USER_CREDENTIAL_PSW'] do capture(:echo, '--user=$HOGE_USER', '--password=$HOGE_PASSWD') end end end end endとなります。
- 投稿日:2021-01-15T18:06:56+09:00
RSpec コントローラーテストの書き方
前提
・deviseが導入されています
・今回書くテストの内容について
・ユーザーがログインしていれば、正しく表示されることapp/controllers/posts_controller.rbclass PostsController < ApplicationController before_action: authenticate_user! def index @posts = Post.all end endファイルの作成・役割について
①spec配下にrequests, factoriesフォルダを作成
・spec/requests/posts_request.rbを作成
→テストの内容を記述するファイル・spec/factories/user.rb
・spec/factories/post.rbを作成
→userとpostに関するダミーデータを作成するファイル②FactoryBotを使えるようにするためにspec配下にsupportフォルダとfactory_bot.rbファイルを作成
spec/support/factroy_bot.rbRSpec.configure do |config| config.include FactoryBot::Syntax::Method #FactoryBotのため config.include Devise::Test::IntegrationHelper, type: :request #deviseのsign_inメソッドが使えるようになる end次にrails_helper.rbの編集
spec/rails_helper.rbrequire 'spec_helper' ... require 'rspec/rails' require 'support/factroy_bot' #←この記述によってFactroyBotを使用できるダミーデータの作成
spec/factories/user.rbFactoryBot.define do factory :user do email { Faker::Internet.email } name { "testuser1" } password { "password" } password_confirmation { "password" } end endspec/factories/post.rbFactroyBot.defind do factory :post do body { Faker::Lorem.charactors(number: 15) } #15文字のダミー文を作成 end endテストコードの作成
spec/requests/post_spec.rbrequire 'rails_helper' RSpec.describe 'post_controllerのテスト', type: :request do let(:user) { create(:user) } let(:post) { create(:post) } describe 'ログイン済みの場合' do before do sign_in user get posts_path end it '投稿一覧ページへのリクエストは200 okとなること' do expect(response.status).to eq 200 end it 'ページタイトルが正しく表示されていること' do expect(response.body).to include('投稿一覧') end end describe "ログインしていない場合" do before do get posts_path end it 'リクエストが401となること' do expect(response.status).to eq 401 end it 'タイトルが表示されない' do expect(response.body).to_not include("投稿一覧") end end endあとはRSpecを動かしてみるのみ!
$ rspec spec/requests最後に
初心者中の初心者の記事なので、十中八九ミスがあると思います。
気づかれた方がおられたら、教えていただけるととてもありがたいです!
!
- 投稿日:2021-01-15T16:02:04+09:00
プロを目指す人のためのRuby入門 配列や繰り返し処理4章
イントロダクション
今回プロを目指す人のためのRuby入門1の続きを書いていこうと思います!
引き続き、自分用アウトプットとなっておりますので、暖かく見ていただけると幸いです!この章で学ぶこと
配列
ブロック
範囲
様々な繰り返し処理
繰り返し処理用の制御構造配列
配列とは複数のデータをまとめて格納できるオブジェクト。
#空の配列 [] #3つの要素が格納された配列 [要素1,要素2,要素3]配列のクラスはArrayクラス
配列はどんなデータ型でも格納できる、それぞれが違ったデータ型だろうと、、、
また配列の中に配列を入れたりも可能a = [1, 2, 3, 4, 5] a.size #=> 5 a.length #=> 5sizeとlegthのメソッドを使うと配列の長さを取得できる。
要素の変更、追加、削除
配列[添字] = 新しい値 a = [1,2,3] a[1] = 20 a #=> [1,20,3] #元の大きさよりも大きい添字をすると間の値がnilになる a[4] = 50 a #=> [1,20,3,nil,50] #<<を使うと最後の配列の最後に要素を追加できる a << 60 a #=> [1,20,3,nil,50,60] #配列ないの要素を削除したい場合は、delete_atを使う a.delete_at(1) a #=> [1,3,nil,50,60] #存在しない添字を指定するとnilが変える a.delete(100) #=> nil a #=> [1,3,nil,50,60]配列を使った多重代入
a, b = [1, 2] a #=> 1 b #=> 2 c, d = [10] c #=> 10 d #=> nil e,f = [100, 200, 300] e #=> 100 f #=> 200
divmodメソッド
・・・割り算の章とあまりを配列として返す14.divmod(3) #=> [4, 2] #戻り値を配列のまま受け取る quo_rem = 14.divmod(3) "商=#{quo_rem[0]},余り=#{quo_rem[1]}" #=> "商=4, 余り=2"ブロック
ブロックはメソッドの引数として渡すことができる処理のかたまり。ブロック内で記述した処理は必要に応じてメソッドから呼び出される。
rubyの繰り返し処理
numbers = [1,2,3,4] sum = 0 numbers.each do |n| sum += n end sum #=> 10ここではnumbers.eachメソッドが繰り返し処理をしている
eachメソッド
・・・配列の要素を最初から最後まで順番にとりだすことそして取り出した中でそれぞれの要素をどう扱うかはブロックに記述
ここのコードでいうとdoからendまでがブロックです!
そして|n|
がブロック引数になる配列の要素を削除する条件を自由に指定
deleteメソッド
・・・要素の値と一致したものを削除a = [1,2,3,1,2,3] a.delete(2) a #=> [1,3,1,3]'delete_ifメソッド`・・・条件にあった要素削除
基本的には配列から要素を1つずつ取り出すという点では、eachメソッドと同じですが、そこからdelete_ifは、ブロックの戻り値チェックし、真であればその要素から配列を削除します。よってブロック内のコードは、削除したい条件を記入する
a = [1,2,3,1,2,3] #配列から値が奇数の要素の削除 a.delete_if do |n| n.odd? end a #=> [2,2]ブロック引数とブロック内の変数
ブロック処理が行われる際の要素一つ一つに対しての引数
よって引数名は自由に決められる。また使わない場合は、ブロック引数は、省略可能
numbers = [1,2,3,4] sum = 0 #|n|がブロック引数 numbers.each do |n| sum_value = n.even? ? n * 10 : n sum += sum_value sum #=> 1 3 6 10の順に表示(エラーにならない) end sum_value #=> NameError: ...上記のようにブロック内で定義した変数は、参照できない
しかし変数sumのようにブロックの外部で作成されたローカル変数は、ブロックの内部でも外部でも参照可能注意) ブロック引数をブロックの外にある変数と同じにすると、ブロック引数が優先される。シャドーイングという
do ... endと{}
ブロックは改行を入れなくもrubyの文法上、動作する
numbers = [1,2,3,4] numbers.each do |n| sum += n end sum #=> 10 numbers.each { |n| sum += n }しかし、可読性が低い
またdo ... end以外にも{}でかこんでブロックを作れる
もちろん改行もできるおすすめの使い分け
・改行を含める場合は、do...end
・1行でコンパクトに書きたいときは{}ブロックを使う配列メソッド
使用頻度が高い配列メソッド
・map/collect
・select/find_all/reject
・find/detect
・inject/reducemap/collect
各要素に対してブロックを評価した結果を新しい配列にして返す。(collectはエイリアスメソッド)
numbers = [1,2,3,4,5] new_numbers = [] numbers.each { |n| new_numbers << n * 10 } new_numbers #=> [10,20,30,40.50] #mapメソッドを使うと new_numbers = numbers.map { |n| n * 10 } new_numbers #=> [10,20,30,40.50]このようにmapメソッドを使うとブロックの戻り値が配列の要素として新しい配列が作成される
mapメソッドは、配列に対して、新しい配列を作るselect/find_all/reject
select・・・各要素に対してブロックを評価し、その戻り値が真の要素を集めた新しい配列を返すメソッド(find_allをselectのエイリアスメソッド)
reject・・・selectの逆。偽の要素を集めた新しい配列を返すnumbers = [1,2,3,4,5] even_numbers = numbers.select { |n| n.even? } non_multiples_of_three = numbers.reject {|n| n % 3 === 0 } even_numbers #=> [2,4,6] non_multiples_of_three #=> [1,2,4,5]find/detect
ブロックが戻り値が真になった最初の要素を返す(detectはエイリアスメソッド)
numbers = [1,2,3,4,5] even_number = numbers.find{ |n| n.even? } even_number #=> 2inject/reduce
たたみ込み演算を行う(reduceはエイリアスメソッド)
numbers = [1,2,3,4] sum = numbers.inject(0){ |result, n| result + n } sum #=> 10このコードではinjectメソッドの第一引数には、足していく合計値、第二引数にはそれぞれの要素が入ってくる
1 result = 0 n = 1 + 0
2 result = 1 n = 2 + 1
3 result = 3 n = 3 + 3
4 result = 6 n = 4 + 6
で10が戻り値になる&とシンボルを使って完結に
['ruby', 'java7, 'perl'].map { |s| s.upcase } #=> ["RUBY", "JAVA", "PERL"] #上記のコードを簡潔にすると ['ruby', 'java7, 'perl'].map($:upcase) #=> ["RUBY", "JAVA", "PERL"]mapメソッドやselectメソッドにブロックを渡す代わりに&:メソッド名という引数を渡している
下記の条件が揃ったときに使える
・ブロック引数が1つだけ
・ブロックな中で呼び出すメソッドがない
・ブロックの中では、ブロック引数に足してメソッドを1回呼び出す以外の処理がない注意) 慣れないうちは使わなくてもいい
範囲(Range)
rubyには1から5まで、aからeまでのように値の範囲を表すオブジェクトがある。
これを範囲オブジェクトという#最初の値..最後の値(最後の値を含む) 1..5 a..e #最初の値...最後の値(最後の値を含まない) 1...5 a...e (1...5).class #=> Range範囲オブジェクトはRangeクラス
配列や文字列の一部を抜き出す
a = [1,2,3,4,5] a[1...3] #=> [2,3,4] a = "abcde" a[1...3] "bcd"n以上n以下、n以上n未満を判定
#不等号をつか場合 def liquid?(temperature) 0 <= temperature && temperature > 100 end liquid?(-1) #=> false liquid?(0) #=> true liquid?(99) #=> true liquid?(100) #=> false #範囲オブジェクトを使う場合 def liquid?(temperature) (0...100).include?(temperature) end liquid?(-1) #=> false liquid?(0) #=> true liquid?(99) #=> true liquid?(100) #=> falsecase文を使う
範囲オブジェクトはcase文と組み合わせることもできる
def charge(age) case age when 0...5 0 when 6...12 300 when 13...18 600 else 1000 end charge(3) #=> 0 charge(12) #=> 300 charge(16) #=> 600 charge(25) #=> 1000値が連続する配列を作成
to_aメソッド
・・・値が連続する配列を作成(1..5).to_a #=> [1.2.3.4.5] ('a'...'e').to_a #=> ["a","b","c","d"] [*1...5] #=> [1,2,3,4,5][]のなかに*と範囲オブジェクトを書いても同じように配列を作れる
繰り返し処理
numbers = (1..4).to_a sum = 0 numbers.each {|n| sum += n } sum #=> 10 sum2 = 0 (1..4).each{ |n| sum2 += n } sum2 #=> 10 numbers2=[] (1..10).step(2){ |n| numbers2 << n } numbers #=> [1,3,5,7,9]
stepメソッド
・・・値を増やす感覚を指定できる配列について詳しく
a = [1,2,3,4,5] a[1,3] #=> [2,3,4] a.value_at(0,2,4) #=> 1,3,5 a[a.size -1] #=> 5 a[-1] #=> 5 a[-2] #=> 4 a[-2, 2] #=> [4,5] a.last #=> 5 a.last(2) #=> [4,5] a.first #=> 1 a.first(2) #=> [1,2]様々な要素の変更方法
a = [1,2,3] a[-3] = -10 #=> [-10,2,3] #2つ目から3要素分を100で置き換える b = [1,2,3,4,5] b[1,3] = 100 b #=> [1,100,5] #pushは>>と同じ挙動のメソッド c = [] c.push(1) #=> [1] c.push(2,3) #=> [1,2,3] d = [1,2,3,1,2,3] d.delete(2) #=> 2 d #=> [1,3,1,3] d.delete(5) #=> nil d #=> [1,3,1,3]注意)指定可能位置よりも小さくなった場合や大きくなった場合は、エラーになる
配列の連結
2つの配列を連結したい場合は、concatメソッドか+演算子を使う
違いは元の配列を変更するかどうかという点a = [1] b = [2,3] a.concat(b) #=> [1,2,3] a #=> [1,2,3]変更される b #=> [2,3]変更されない c = [1] d = [2,3] c + b #=> [1,2,3] c #=> [1]変更されない d #=> [2,3]変更されない+演算子を使うことをお勧めする
配列の和集合、差集合、積集合
rubyの配列は、
|,-,&
を使って和集合、差集合、積集合を求められるいずれも元の配列は変更しません
a = [1,2,3] b = [3,4,5] a | b #=> [1,2,3,4,5] a - b #=> [1,2] a && b #=> [3]多重代入で残りの全要素を入れとして受け取る
#このように残りの300は通常切り捨てられる a, b = 100,200,300 a #=> 100 b #=> 200 #*を使うと残りの値は配列として取得できる c, *d = 100,200,300 c #=> 100 d #=> [200,300]一つの配列を複数の配列の引数として展開
配列の引数に配列を渡す時、展開して1つの配列にしたい時は、引数を
*引数名
とするa = [1] b = [2,3] a.push(b) #=> [1,[2,3]]となってしまう a.push(*b) #=> [1,2,3]個数に制限のない引数のことを可変長引数という。自分で定義するメソッドで可変長引数を使う場合は引数名の前に*をつける
==で等しい配列かどうか判断する
[1,2,3] == [1,2,3] #=> true [1,2,3,4] == [1,2,3] #=> false%記法で配列で文字列の配列を簡潔に作る
#w!で文字列配列 %w!apple melon orange! #=> ["apple","melon","orange"]また文字列の空白を入れたい場合は、/(バックスラッシュ)でエスケープする
囲うのは、!!ではなく()でも問題ない
\nや\tを含め痛い場合はwを大文字(W)にする文字列を配列に変換する
chartsメソッド
・・・1文字1文字を配列の要素にする"Ruby".charts #=> ['R','u','b','y']
splitメソッド
・・・引数で渡した区切り文字で文字列を配列に分割するメソッド"ruby,java,perl,php".split(',') #=> ["ruby","java","perl","php"]
配列に初期値を設定する
#a = []と同じ a = Array.new a = Array.new(5) a #=> [nil,nil,nil,nil] a = Array.new(5,0) a #=> [0,0,0,0,0] a = Array.new(10){|n| n % 3 + 1} a #=> [1,2,3,1,2,3,1,2,3,1]ミュータブルとイミュータブル
ミュータブル・・・変更可能(破壊的)
イミュータブル・・・変更不可(非破壊的)rubyのイミュータブルなクラス
・数値
・シンボル
・ture/false(TrueClassとFlaseClass)
・nilブロックについて詳しく
添字付きの繰り返し処理
each_with_index
・・・ブロック引数の第二引数に添字を渡してくれる(要素が今配列の中で何番目かわかる)fruits = ['apple','orange','melon'] fruits.each_with_index{|fruit,i| puts "#{i}: #{fruit}"} #=> 0: apple # 1: orange # 2: melonその他の配列メソッドで使う時は.with_indexメソッド使う
fruits.map.with_index{|fruit, i| "#{i}: #{fruit}"} #=> ["0: apple", "1: orange", "2: melon"]map以外のメソッドでも使える!!
添字を0以外の文字から始めたい場合は、with_index(始めたい添字)
注意)eact_with_indexには引数は渡せないため、使いたい時は、each.with_index()で使う配列がブロック引数に渡される場合
配列の中に配列が入っている場合、配列処理の中のブロック引数に配列が入ってくる!
dimensions = [ [10,20] [30,40] [50,60] ] areas = [] #ここのブロック引数(dimension)には配列が入ってくる dimensions.each do |dimension| lengh = dimension[0] width = dimension[1] area << length * width end area #=> [200,1200,3000] #引数を二つ受け取れるようにするとコードがシンプルになる areas2 = [] dimensions.each do |length, width| areas2 << length * width end areas2 #=> [200,1200,3000]注意)指定したブロック引数の数が配列の要素より多い場合は、はみ出しているブロック引数は、nilになる
#with_indexを使う場合 dimensions = [ [10,20] [30,40] [50,60] ] dimensions.each_width_index do |dimension, i| #配列をそのまま取り出すようにする end #また()で受け取っても良い dimensions.each_width_index do |(length, width), i| #これだと配列の要素と添字を一気に受け取れる endブロックローカル変数
ブロック引数を;で区切り、続けて変数を宣言すると、ブロックのみで有効な独立したローカル変数を宣言できる
余り使う機会はないと思われる。numbers = [1,2,3,4] sum = 0 numbers.each do |n; sum| sum = 10 sum += n p sum end #=> 11 # 12 # 13 # 14 sum #=> 0様々な繰り返し処理
timeメソッド
配列を使わずに、単純にn回処理を繰り返したい、という場合はIntegerクラスのtimesメソッドを使う
sum = 0 #処理を5回繰り返すnには0,1,2,3,4の順に入る 5.times {|n| sum += n} sum #=> 10ブロック引数が必要ない場合は省略可能!
uptoメソッドとdowntoメソッド
nからmまで数値を1つずつ増やしながら何かの処理をしたい場合はuptoを減らしながら行いたい場合はdowntoを使う
a = [] 10.upto(14) {|n| a << n} a #=> [10,11,12,13,14] b = [] 14.downto(10){|n| b << n} b #=> [14,13,12,11,10]stepメソッド
1,3,5,7のようにnからmまで数値をx個ずつ増やしながら何か処理をしたい場合は、Numericクラスのstepメソッドを使う
a = [] 1.step(10, 2){|n| a << n} a #=> [1,3,5,7,9] b = [] 10.step(1,-2){|n| b << n} b #0> [10,8,6,4,2]while文とuntil文
繰り返し処理ようのメソッド
while文は条件が真である間、処理を繰り返す
unless文は条件が偽である間、処理を繰り返すa = [] #配列が5より小さい間は、1を配列に追加 while a.size < 5 a << 1 end a #=>[1,1,1,1,1] b = [1,2,3,4,5] #配列の長さが3より小さくない場合、配列の最後から要素を消していく until a.size <= 3 a.delete_at(-1) end a #=> [1,2,3]for文
繰り返し処理メソッド
numbers = [1,2,3,4] sum = 0 for n in numbers sum += n end sum #=> 10 sum = 0 for n in numbers do sum += n end sum #=> 10見ての通りeachの挙動とほとんど同じ
ちがいは、for文の中で宣言したローカル変数もfor文の外でも使えるnumbers = [1,2,3,4] sum = 0 for n in numbers sum_value = n.even ? ? n * 10 * n sum += sum._value end n #=> 4 sum_value #=> 40loopメソッド
ループ処理を行うときに使用
loop do #無限ループ用の処理 end無限ループからの脱出はbreakを使う
numbers = [1,2,3,4,5] loop do #sampleメソッドでランダムに要素を1つ取得する n = numbers.sample puts n break if n === 5 end #=> 3 # 2 # 5 5が出たから処理終了do...endを使うのでブロック処理となる。そのためローカル変数を参照できない
whileはdo...end(ブロック)を使わないから参照できる繰り返し処理用の制御構造
繰り返し処理の動きを制御するためのキーワード
・break
・next
・redobreak
繰り返し処理からの脱出
#shuffleメソッドで配列の要素をランダムに並び替える numbers = [1,2,3,4,5].shuffle #5が出たら処理終了 numbers.each do |n| puts n break if n === 5 end while i < numbers.size n = numbers[i] puts n break if n == 5 i += 1 end #=> 2 # 4 # 5breakに引数を渡すと、while文やfor文の戻り値になる
ない場合戻り値はnilになるret = while true break 123 end ret #=> 123breakは繰り返し処理が重なっていた場合一番内側の繰り返し処理しか脱出できない
throwとcatchをつかった大域脱出
breakは繰り返し処理が重なっていた場合一番内側の繰り返し処理しか脱出できない
と言いましたが、一番外側のループまで脱出したい場合は、throwとcatchメソッドを使う
catch タグ do #繰り返し処理など throw endfruits = ['apple', 'melon', 'orange'] numbers = [1,2,3] catch :done do fruits.shuffle.each.do |fruit| numbers.shuffle.each do |n| puts "#{fruit}, #{n}" if fruit == "orange" && n == 3 #全ての繰り返し処理を脱出する throw :done end end end end #melon, 2 #orage, 3 ここで終了 #またthrowの第二引数(タグ名の後)に戻り値を指定できる ret = catch :done do throw :done ,123 end ret #=> 123 #throwで何も第二引数を指定しなければnil繰り返し処理で使うbreakとreturnの違い
主な違い
break・・・繰り返し処理からの脱出
return・・・メソッドからの脱出
処理の中に配列処理があった場合、
breakは、配列処理のみからの脱出
returnだとメソッド全体から脱出してしまうまたreturnはメソッドからの脱出なので、配列処理の中で使用するとエラーになる
next
繰り返し処理を途中でちゅ出し、次の繰り返し処理に進める
numbers = [1,2,3,4,5] numbers.each do |n| next if n.even? puts n end #=> 1 # 3 # 5一番内側のループだけが中断の対象になるeachだけではなく他の繰り返しの中でも使えることは、breakと同じ
redo
繰り返し処理をやり直しする
foods = ["ピーマン","トマト","セロリ"] foods.each do |food| print "#{food}は好きですか? => " answer = ["はい","いいえ"].sample puts answer count += 1 #はいと答えなければまたいいえと答えた回数が2回までの間は、もう一度聞き直す redo unless answer != "はい" && count < 2 endずっといいえが出る場合無限ループになるので注意して使う!
やり直しの制限を設けておくことが最適である。その他でてきたメソッド
rjust
・・・右寄せするメソッド第一引数には桁数、第二引数は空白の文字を何にするか指定できる
hex
・・・16進数を10進数に変換するメソッド
scan
・・・一気に文字列を3つの16進数に分割できる 戻り値がそのまま配列になる
Set(クラス)
・・・本格的に集合演算を使うときに利用する
shuffle
・・・配列の要素をランダムに並び替える
sample
・・・配列からランダムの要素を取得
- 投稿日:2021-01-15T13:57:58+09:00
【Ruby】今日の曜日を表示するプログラムを作りたい!
はじめに
プログラミングにおける思考力を高めるため、プログラムを書く練習をしています。今回は今日の曜日を出力するプログラムを「date」クラスを用いて記述しました。
Dateクラス
以下の記述でDateクラスが使用できます。
require "date"Dateクラスを用いて今日の曜日を取得する場合は
Date.today.wdayと記述します。wdayは曜日を0(日曜日)~6(土曜日)の整数で取得することができるDateクラスに用意されているメソッドです。
上記を用いて、今日の曜日を文章で出力します。
以下の記述のようにしてみました。require "date" day = Date.today.wday days = ["日","月","火","水","木","金","土"] puts "今日は#{days[day]}曜日だ"配列daysを定義することで、日曜日(0)~土曜日(6)まで文字列を格納しました。
- 投稿日:2021-01-15T13:23:50+09:00
rubyの正規表現で「^」や「$」を使おうとするとエラーが出る話
前提
Rubyのバージョンは6.0.0
railsのバージョンは6.0.3.4
上記の環境でエラーは発生しました。エラー内容
The provided regular expression is using multiline anchors (^ or $), which may present a security risk. Did you mean to use \A and \z, or forgot to add the :multiline => true option? (ArgumentError)
どうやら「^」や「$」はセキュリティ上のリスクがあるということでエラーが出ているようです。
解決した方法
エラー文にしたがって ^→\A, $→\z のように変更すると解決しました!
エラーに関しての詳細
調べてみたところRailsの4.0以降のバージョンでは上記エラーが出るようになったようです。
日々セキュリティが向上しているんですね!「^」や「$」がリスクがある理由としては
「^」→行頭
「$」→行末
であるため、複数行を渡された時に予期せぬ値の許容が起きてしまうから。そのため
「/A」→文字列の先頭
「/z]→文字列の末尾
に変更すると各文字列ごとに読み込むため、複数行渡されても大丈夫なので「/A」「/z」を使ってくださいね!
という指示が出てきた、というエラーでした。ちなみに「:multiline => true」オプションを使えば「^」や「$」も使えるようなので、複数行を渡すことが前提であればこのオプションを使う解決方法の方がいいのかもしれませんね。
- 投稿日:2021-01-15T12:46:31+09:00
単体テストコードの実装
私自身の備忘録として簡単にテストコードの実装をここに残しておきます。
前提条件として、今回はユーザーの新規登録の単体テストコードを作成します。
テストする内容はnickname、email、password、password_confirmationの4つをテストします。gemの準備
まずテストするためのgemをgemfileに記述
※記述する箇所はgroup :development, :test doの中に記述すること
ここに記述するとテストする時にだけ使えるようになるためgemfilegroup :development, :test do gem 'rspec-rails' #この4行を記述、テストするためのgem gem 'factory_bot_rails' #この4行を記述、テンプレート的なやつを作るgem gem 'faker' #この4行を記述、テキトーな文字列などを入れてくれるgem gem 'pry-rails' #この4行を記述、binding.pryで処理を止めれるようにするgem # Call 'byebug' anywhere in the code to stop execution and get a debugger console gem 'byebug', platforms: [:mri, :mingw, :x64_mingw] end上記の記述をしたら以下をターミナルで実行
ターミナル% bundle install % rails g rspec:install
これらのファイルが作成されたら完璧
ターミナルcreate .rspec create spec create spec/spec_helper.rb create spec/rails_helper.rb
.rspecのファイルに以下を記述
.rspec--require spec_helper #デフォルトで入ってる --format documentation #この行を追加この記述は、テストコードの結果をターミナル上に可視化するための記述です。
モデルの準備
以下のコマンドでモデルの作成をします(今回はuserモデルを作成)
ターミナル% rails g rspec:model user
これらが作成されたらおk
ターミナルcreate spec/models/user_spec.rb invoke factory_bot create spec/factories/users.rb
テスト内容の洗い出し
spec/models/user_spec.rbrequire 'rails_helper' RSpec.describe User, type: :model do describe "ユーザー新規登録" do it "nicknameが空では登録できない" do # nicknameが空では登録できないテストコードを記述します end it "emailが空では登録できない" do # emailが空では登録できないテストコードを記述します end it "passwordが空では登録できない" do # passwordが空では登録できないテストコードを記述します end it "password_confirmationが空では登録できない" do # password_confirmationが空では登録できないテストコードを記述します end end endかる〜く解説知ってたらスルーしてよし
①describe "行いたいテストの内容" do〜end
"行いたいテストの内容"のなかに内容を記述、今回はユーザー管理機能のテストを記述②it '条件'do〜end
'条件'の部分にテストしたい条件を記述ちゃんとできてるか確認するためにコマンドを実行
ターミナル% bundle exec rspec spec/models/user_spec.rb
これがターミナルに出力されたらおk
FactoryBotとFakerを使う
まずspecのディレクトリの中にfactoriesと言うディレクトリを作り、その中にuser.rbを作成する
作成したusers.rbに以下を記述
spec/factories/users.rb(Fakerを使わない場合)FactoryBot.define do factory :user do nickname {"test"} email {"test@example"} password {"000000"} password_confirmation {password} end endspec/factories/users.rb(Fakerを使う場合)FactoryBot.define do factory :user do nickname {Faker::Name.initials(number: 2)} email {Faker::Internet.free_email} password {Faker::Internet.password(min_length: 6)} password_confirmation {password} end endこれでfactorybotの準備完了!
①user_spec.rbのファイルに戻ってRSpec.describe User, type: :model doの下に以下の3行を追加
②it do〜endの処理の中に実際行う処理をそれぞれ記述するspec/models/user_spec.rbrequire 'rails_helper' RSpec.describe User, type: :model do #⬇︎この3行を追加 before do @user = FactoryBot.build(:user) end #⬆︎この3行を追加 describe "ユーザー新規登録" do it "nicknameが空では登録できない" do @user.nickname = "" @user.valid? expect(@user.errors.full_messages).to include("Nickname can't be blank") end it "emailが空では登録できない" do #⬇︎実際に行う処理 @user.email = "" @user.valid? expect(@user.errors.full_messages).to include("Email can't be blank") #⬆︎実際に行う処理 end it "passwordが空では登録できない" do @user.password = "" @user.valid? expect(@user.errors.full_messages).to include("Password can't be blank") end it "password_confirmationが空では登録できない" do @user.password_confirmation = "" @user.valid? expect(@user.errors.full_messages).to include("Password confirmation doesn't match Password") end end end実行しましょう!
ターミナル% bundle exec rspec spec/models/user_spec.rb
これらが表示されたらおk
もしエラーが出てしまうならエラー箇所にbinding.pryを記述し実行しましょう!spec/models/user_spec.rbit "nicknameが空では登録できない" do @user.nickname = "" @user.valid? binding.pry #ここで処理が止まる expect(@user.errors.full_messages).to include("Nickname can't be blank") end※処理を止めてエラーの内容を確認する手順は以下の通りです
ターミナルpry(#<RSpec::ExampleGroups::User>)> @user.valid? #エラーが出ているかの確認 pry(#<RSpec::ExampleGroups::User>)> @user.errors.full_messages #エラーメッセージの確認 pry(#<RSpec::ExampleGroups::User>)> exit #railsコンソールから脱出
- 投稿日:2021-01-15T12:41:33+09:00
ActiveSupportのArray#sumは七変化する
Railsエンジニアなら必ずお世話になるActiveSupportはRubyデフォルトの
Array#sum
を拡張する処理が含まれている。
とあるライブラリ開発でActiveSupportの実装を追いかけた時に見つけた#sum
の挙動をまとめます。
[1,2,3].sum
おそらくは1番標準的な使い方。
[5, 15, 10].sum => 3ちなみにこの使い方はActiveSupportを使わなくても使用できる。
昔はRuby標準のArray#sum
よりActiveSupportのArray#sum
のほうが高速に動作したがある時期を持って本家に取り込まれたそうで今はどっちも同じ早さという話
['foo', 'bar'].sum
ActiveSupport無しだとエラーになる↓
pry(main)> ['foo', 'bar'].sum TypeError: String can't be coerced into Integer from (pry):1:in `+'ところがActiveSupportありだとこうなる
pry(main)> ['foo', 'bar'].sum => "foobar"唐突に
+
がArray.join
に化けた。
そうは言ってもRubyで文字列を + すると連結できるのでこの挙動自体が理解できないわけではない。
[[1,2,3], [4,5]].sum
先の例同様にActiveSupport無しだとエラーになる
pry(main)> [[1,2,3], [4,5]].sum TypeError: Array can't be coerced into Integer from (pry):2:in `+'じゃあActiveSupportありだとどうなるのか?
こうなりますpry(main)> [[1,2,3], [4,5]].sum => [1, 2, 3, 4, 5]もしかしたら
Array.flatten.sum
と同じ挙動になると予想された人もいたのではないでしょうか?
残念なことに結果はsum
されません。代わりにflatten
されます。なんで...?おまけ
ArrayじゃなくHashでsumしたらどうなるだろうか?
pry(main)> {a: 10, b: 20}.sum => [:a, 10, :b, 20]!?!?!?
ソースコードを見てると以下の記述を発見# We can't use Refinements here because Refinements with Module which will be prepended # doesn't work well https://bugs.ruby-lang.org/issues/13446なるほどぉ...
- 投稿日:2021-01-15T11:54:29+09:00
expected `Product.count` to have changed by -1, but was changed by 0
※CRUD操作の内、destroyアクションにしか言及しません。
request specでdestroyアクションのテストを行っていて、
userに関連づいたproductを削除してもテストコード内でcountが−1されず、0のままになっていて長い間困っていた。web上で探してみてもいい感じの記事が見つからなかったので今後同じようなエラーに悩まされた人がいた場合に備えて私の解決策を残しておく。
これがエラー文expected `Product.count` to have changed by -1, but was changed by 0改善前のソースコード
describe 'DELETE #destroy' do context 'ログインしているユーザー' do ??let(:product_params) do { product: { title: "削除用", language: "PHP", detail: "ウオウオの実", period: "8年", user: user } } end subject { delete product_path(product_params) } it '投稿数が一つ減っている' do sign_in_as(user) expect { subject }.to change(Product, :count).by(-1) endだが、これだと上記のようなエラーが出てしまう。流れの解説としては、まずlet(:product_params)~内で削除するproductの準備をする。
そしてsubject(繰り返しを避けるために共通化しておく)内でproduct_paramsの削除を行い、
expectでProductの数が1つ減っているかの確認をする。ここからが改善策
describe 'DELETE #destroy' do context 'ログインしているユーザー' do it '投稿数が一つ減っている' do ?? product_params = user.products.create(title: "削除用",language: "PHP",detail: "ウオウオの実",period: "8年",user: user) sign_in_as(user) expect{ product_params.destroy }.to change{ Product.count }.by(-1) end最初のコードと違う点は、
product_params = user.products.create(title: "削除用",language: "PHP",detail: "ウオウオの実",period: "8年",user: user)ここでproduct_paramsを作成している点だ。最初のコードではproductの情報をcreateしていないのに削除(destroty)していたので、それではcountが0のままなのも今となっては理解ができる。
では、productを作成する書き方として、以下のようなコードはどうだろうか?post products_path, params: product_params理由はよくわからないが、これでは動かなかった。
今後RSpecでControllerのテストの中で削除するためなど、一時的にデータを保存しておきたい場合はpostで送るのではなく,createを使っていこうと思う。
- 投稿日:2021-01-15T11:31:51+09:00
ActiveStorageを導入した時のimageカラムのテストコード
初心者の私がactivestorageを使ったときのテストコードで1時間ほど詰まったのでその備忘録です
まず前提として、必要なactivestorageやfactorybot、specのインストールは済ませているものとします。
済ませていなければ以下のgemをgemfileに記述しbundle installしましょう
その後RSpecのインストールをしましょうrails g rspec:installgemfilegroup :development, :test do gem 'rspec-rails' #この3行を記述、テストをするためのgem gem 'factory_bot_rails' #この3行を記述、テスト用のテンプレートを作成するためのgem gem 'pry-rails' #この3行を記述、bindind.pryを実行するためのgem # Call 'byebug' anywhere in the code to stop execution and get a debugger console gem 'byebug', platforms: [:mri, :mingw, :x64_mingw] endターミナル% bundle install % rails g rspec:install
まずpublicのディレクトリ下にimagesと言うファイルを作成する
そのimagesの中に適当な画像のデータを保存する(今回はa.pngと言う名前の画像を用意します)今このような状態
public/images/a.pnga.pngは適当な画像のデータです
次にそのデータを参照したいのでfactoriesディレクトリの中にあるテンプレートの中にimageカラムを作成します今回私は「item」と言うモデルをテストするのでractories/items.rbになります
ractories/items.rb(モデル名.rb)FactoryBot.define do factory :item do name {"名前"} price {500} text {"テスト"} judgement_id {1} category_id {1} prefecture_id {2} day_id {1} status_id {1} user #userとのアソシエーション after(:build) do |message| message.image.attach(io: File.open('public/images/a.png'), filename: 'a.png') end end endゆるく解説
・judgement_idやcategory_idなどの「_id」の項目はactivehashを使って導入したので無視して大丈夫です。
・userと言う記述はuserモデルとのアソシエーションを意味してます、これを記述することでuser_idを入力しなくてすみます。
今回の注目していただきたいのはuserの下にある記述です
ractories/items.rb(モデル名.rb)after(:build) do |message| message.image.attach(io: File.open('public/images/a.png'), filename: 'a.png') endこの記述により実際に存在する写真を参照するようになります。
ダメな例
ractories/items.rb(モデル名.rb)FactoryBot.define do factory :item do name {"名前"} price {500} text {"テスト"} judgement_id {1} category_id {1} prefecture_id {2} day_id {1} status_id {1} image {"a.png"} #ダメな例です user end endこうしてしまうと、文字列しか登録できていないため、エラーになりますちなみにエラー内容は以下になります。
エラーを確かめたい場合はbinding.pryを記述して処理を止めましょう!ターミナル[1] pry(#<RSpec::ExampleGroups::Item>)> @item.valid? ActiveSupport::MessageVerifier::InvalidSignature: ActiveSupport::MessageVerifier::InvalidSignature同じエラーに出会した方の参考になれば幸いです。
- 投稿日:2021-01-15T09:45:16+09:00
ステートレスとかステートフルとかのお話
ステートレスとは
ステートレス = 「状態を保持しない」
HTTPでは、HTTPリクエスト・HTTPレスポンスのこの1往復のやりとりで一つの処理として完結する。
よって複数の処理に関連を持たせることができない。では、ステートフルとは
ステートフル = 「状態を保持する]
要するに、状態を保持して次の処理でも前の処理で行われた内容を反映させることをいう。ステートフルのデメリット
ステートフルにはデメリットがある。例えば、複数のクライアントが同時にWEBサーバーへリクエストを行った場合に、WEBサーバーはその内容を保持する必要がある。すると、WEBサーバー1つに対して、複数クライアントの情報を保持する必要があるため、応答に多くの時間を必要としてしまう。
- 投稿日:2021-01-15T09:45:16+09:00
【HTTP】ステートレスとかステートフルとかのお話
ステートレスとは
ステートレス = 「状態を保持しない」
HTTPでは、HTTPリクエスト・HTTPレスポンスのこの1往復のやりとりで一つの処理として完結する。
よって複数の処理に関連を持たせることができない。では、ステートフルとは
ステートフル = 「状態を保持する]
要するに、状態を保持して次の処理でも前の処理で行われた内容を反映させることをいう。ステートフルのデメリット
ステートフルにはデメリットがある。例えば、複数のクライアントが同時にWEBサーバーへリクエストを行った場合に、WEBサーバーはその内容を保持する必要がある。すると、WEBサーバー1つに対して、複数クライアントの情報を保持する必要があるため、応答に多くの時間を必要としてしまう。
- 投稿日:2021-01-15T09:26:35+09:00
HTTPレスポンスに含まれるステータスコード
ステータスコードとは
WEBブラウザからHTMLファイルや画像などのデータをWEBサーバーに要求した際に、WEBサーバーはHTTPレスポンスとして応答する。HTTPレスポンスには、その要求(HTTPリクエスト)に対する処理の結果が含まれている。これが、ステータスコード。このステータスコードは、100番から、500番台の3桁の数字となっている。私たちが普段WEBサイトを正常に閲覧できている時は、
200番
のステータスコードがHTTPレスポンスとして返ってきている。HTTPレスポンスに含まれる代表的なレスポンスコード
ステータスコード 内容 100 continue リクエスト継続中 200 OK リクエストが正常に受理された 301 Moved Paramanetly リクエストされたコンテンツが移動した 302 Found リクエストされたコンテンツが一時的に移動 304 Not Modified リクエストされたコンテンツが未更新であること。WEBブラウザに一時保存されたコンテンツが表示 400 Bad Request リクエストが不正 404 Not Found リクエストされたコンテンツが未検出 500 Internal Server Error リクエスト処理中にサーバー内部でエラーが発生したこと 503 Service Unavailable アクセス集中やメンテナンスなどの理由で一時的に処理不可
- 投稿日:2021-01-15T09:26:35+09:00
【HTTP】HTTPレスポンスに含まれるステータスコード
ステータスコードとは
WEBブラウザからHTMLファイルや画像などのデータをWEBサーバーに要求した際に、WEBサーバーはHTTPレスポンスとして応答する。HTTPレスポンスには、その要求(HTTPリクエスト)に対する処理の結果が含まれている。これが、ステータスコード。このステータスコードは、100番から、500番台の3桁の数字となっている。私たちが普段WEBサイトを正常に閲覧できている時は、
200番
のステータスコードがHTTPレスポンスとして返ってきている。HTTPレスポンスに含まれる代表的なレスポンスコード
ステータスコード 内容 100 continue リクエスト継続中 200 OK リクエストが正常に受理された 301 Moved Paramanetly リクエストされたコンテンツが移動した 302 Found リクエストされたコンテンツが一時的に移動 304 Not Modified リクエストされたコンテンツが未更新であること。WEBブラウザに一時保存されたコンテンツが表示 400 Bad Request リクエストが不正 404 Not Found リクエストされたコンテンツが未検出 500 Internal Server Error リクエスト処理中にサーバー内部でエラーが発生したこと 503 Service Unavailable アクセス集中やメンテナンスなどの理由で一時的に処理不可
- 投稿日:2021-01-15T09:05:26+09:00
【Rails】スコープでfind_byを使用して、条件に一致するレコードがないとnilではなくレコード全件が返される理由
なにこれ
タイトルの通り、筆者が業務でRailsを書いている時に、scope内でfind_byを使用しました。
そこで、条件に一致しなかった場合にnil
ではなく該当テーブルのレコード全件が返される事象に遭遇したので、解決策と原因を備忘録として残しておきます。注意書き
ドキュメントに答えは書いてあります。
Active Record クエリインターフェイス
検索しても同様の記事がヒットしなかった。ドキュメントに書いてあるからかな?結論(解決策)
1.scopeではなくクラスメソッドを使用する。
そうすることで、条件に一致するものがなかった場合、レコード全件ではなく
nil
が返ります。product.rb# bad scope :find_by, ->(product_id) { find_by(product_id: product_id) } #=> ActiveRecord::Relation # good def self.find_by(product_id) find_by(product_id: product_id) end #=> nil2.find_by!メソッドに変更して例外を投げる(一番下の余談を参照)
原因
なぜクラスメソッドだとnilが返るのか?
それを説明するために、スコープとクラスメソッドの違いを確認します。
そもそもスコープとは具体的になんぞや
Railsガイド 14 スコープ
公式から引用します。スコープを設定することで、関連オブジェクトやモデルへのメソッド呼び出しとして参照される、よく使用されるクエリを指定することができます。スコープでは、where、joins、includesなど、これまでに登場したすべてのメソッドを使用できます。どのスコープメソッドも、常にActiveRecord::Relationオブジェクトを返します。このオブジェクトに対して、別のスコープを含む他のメソッド呼び出しを行なうこともできます。
伝えたいことを要約すると、スコープは常に
ActiveRecord::Relation
オブジェクトを返します。
検索結果がnil
だった場合でも、ActiveRecord::Relation
オブジェクトを返します。絶対にです。絶対に!!めっちゃ大事なことなので2回言いました。じゃあActiveRecord::Relationってなんだよ
ActiveRecord::Relation
について簡単にまとまってる良い記事があったので引用させていただきます。
ActiveRecord::Relationとは一体なんなのか上記の記事から要約します。
Product
なるモデルがあったとするとProduct.all
やProduct.where(name: "hoge")
などで返ってくるものがActiveRecord::Relation
のインスタンスです。ざっくり言うと、
ActiveRecord::Relation
は複数のレコード(インスタンス?)を持ったやつです。
where.class
をすると、Product::ActiveRecord_Relation
が返されてるのが確認できます。
find_by.class
をすると、Product
クラス自身を返します。[31] pry(main)> Product.where(id: 1).class => Product::ActiveRecord_Relation [32] pry(main)> Product.find_by(id: 1).class Product Load (2.1ms) SELECT `products`.* FROM `products` WHERE `products`.`id` = 1 LIMIT 1 => Product(id: integer, name: string)クラスメソッドとスコープの違い
下記の記事が非常に分かりやすかったので引用させてください。
ActiveRecordのscopeでnilを返すと…
クラスメソッドはスコープと全く動きをします。
(スコープはインスタンスメソッドではありません。筆者はここを勘違いしてました。)下記2つが行う動作はほぼ同じです。違うのは
nil
やUser.none
の場合返り値だけ。user.rbclass User < ActiveRecord::Base scope :hoge, -> (fuga) { find_by(fuga: fuga) } end class User < ActiveRecord::Base def self.hoge(fuga) find_by(fuga: fuga) end end結局なんでスコープだとダメなの?
スコープだとレコード全件返す理由は、常に
ActiveRecord::Relation
オブジェクトを返すからです。
常にActiveRecord::Relation
を返すので、nilだった場合は自動的に全てのレコードを返します。クラスメソッドを使用すると、メソッド本来の動きをするので
nil
が返されます。
これが答えです。茶番劇
スコープくん「よっしゃ、DBにアクセスしてレコードを絞り込むで〜」
⬇️SQL実行
スコープくん「は!?一致するもんなくて
nil
なんやが。。。」スコープくん「でも、ワイは絶対
ActiveRecord::Relation
オブジェクトを返すって決められてるからなあ。。。」スコープくん「うーん、
nil
を返したら規約違反だし、しょうがないからレコード全件返すべ!」みたいなことが繰り広げられてると思ってます。笑
(スコープで実行したのと同じSQLを実行したら、nilの時は0件で表示されます。)感想
ActiveRecord::Relation
について全く知らなかったので、初めて今回の事象に遭遇した時に「Railsのバグじゃね!?」と思ってました。Railsを疑ってすみません。バリバリ仕様でした。
find_by
でレコードが1件も一致しないことがレアケースだと思いますが、こういった事象が起こることを頭の片隅のギリギリにでも置いてもらえたら幸いです。別解:where + take
find_by
は、そもそも内部の動き的にはwhere(wheres).limit(1)
をしています。
where
で絞り込んだ後にtake
メソッドを呼び出すことで、find_by
を再現できます。[32] pry(main)> Product.where(id: 1).take.class Product Load (2.1ms) SELECT `products`.* FROM `products` WHERE `products`.`id` = 1 LIMIT 1 => Product(id: integer, name: string)状況によっては、
where + take
の方が適切な場面がありそう(nil
を返して欲しい時とか)ここでややこしいのが、
where + take
してもスコープで1件もヒットしない場合は、同じく該当テーブルのレコード全件が返されます。
nil
が返される + スコープはActiveRecord::Relation
を返すっていう仕様のため。余談にもありますが、
take!
メソッドは、ActiveRecord::RecordNotFound
例外を投げるため、find_by!
を代用できそうです。余談(find_by!のススメ)
ここまで長々と書いてきましたが、
find_by
で一致するものが無い可能性がある時は、find_by!
メソッドにしてActiveRecord::RecordNotFound
例外を投げた方がエラーハンドリングできるし処理的に良さそう?
(そもそも、find_by
で一致しないことが異常事態な気がするから)# product.rb scope :hoge, ->(product_id) { find_by!(product_id: product_id) } # product_controller.rb def fuga product = Product.hoge(params[:product_id]) render json: product, status: :ok rescue ActiveRecord::RecordNotFound => e render json: e, status: :not_found end
- 投稿日:2021-01-15T02:34:47+09:00
管理画面生成gem Administrate の紹介と導入
Railsで管理画面を作るとき、どうしてますでしょうか。
ちゃんと作りたいなら自作のがよいと思いますが、個人開発など、そこまで工数割けないよ!という時のために、
管理画面生成gem Administrate を紹介したいと思います。Administrate とは
管理画面を自動生成するRailsライブラリ。
技術者以外のユーザーに、アプリケーション内の任意のレコードの作成、編集、検索、削除をできるクリーンなインターフェースを提供します。(公式より)文面を見ても分からないと思うので、公式のデモを触るのが早いです。
公式サイト(英語): https://administrate-prototype.herokuapp.com/
公式デモ: https://administrate-prototype.herokuapp.com/admin
公式Github (thoughtbot/administrate): https://github.com/thoughtbot/administrate※注意
version 1.0 以前なので、APIに大きな変更が加えられる可能性があります。
updateする前にリリースノートを確認した方がよさそうです。Administrate is still pre-1.0, and there may be occasional breaking changes to the API.
導入
Gemfileに追記
# Gemfile gem "administrate"bundle install 後、インストラーを実行する
# デフォルト設定での install (デフォルトのnamespace: admin) $ rails generate administrate:install # 任意のnamespaceを使いたいとき (namespace: supervisor) $ rails generate administrate:install --namespace=supervisor
config/routes.rb
に/admin
のルートが追加され、app/controllers/admin/application_controller.rb
が生成されます。
また、ActiveRecordリソースごとに Dashboard と Controller が生成されます。
- app/controllers/admin/foos_controller.rb
- app/dashboards/foo_dashboard.rb
※namespaceを指定した際は、ルートもディレクトリ名も変わります
# --namespace=supervisor の場合 http://localhost:3000/supervisor app/controllers/supervisor/application_controller.rb終わりに
これで導入が完了し、一通りの機能(作成、編集、検索、削除)が使えるようになりました。
Administrateは、使いやすいように色々とカスタマイズ出来るので、次回いくつか紹介したいと思います。