20210115のRubyに関する記事は23件です。

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は便利…

今回の教科書はこちら

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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のように置き換える事も可能。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Rails】seed.rbで初期データを作成する【Faker】【日本語化】

はじめに

  • 筆者のスペック
    • Rails学習歴:4ヶ月
  • 条件
    • Rails 6.0.3.2
    • Ruby 2.6.6

そのため間違いなどがある可能性があるので鵜呑み厳禁!

gem「Faker」をinstall

  1. Gemfileにgem 'fakerを追加
  2. ターミナル上でbundle installを実行
Gemfile
gem 'faker'
ターミナル
$ bundle install

「Faker」を日本語化する

  • Rails全体を日本語化する OR Fakerのみを日本語化する
config/initializers/locale.rb
Rails.application.config.i18n.default_locale = :ja ##Rail全体を日本語化する場合
Faker::Config.locale = :ja ##Faker単体のみ日本語化する場合
## どちらか片方のみでも設定すれば日本語化できます

参考文献
FakerのGithub(英語)
Fakerの使用例(英語)

「Faker」の日本語訳をカスタマイズする

  1. Githubからja.ymlをダウンロードしてくる
  2. ja.ymlをconfig/locales/fakerに配置する
  3. config.i18n.load_pathでconfig/locales以下のどの階層のディレクトリも読み込ませるようにする
  4. ダウンロードしてきたja.ymlを自分好みにカスタマイズする
ターミナル
## wget 【ダウンロードしたいファイルのURL】 -P 【ダウンロードしたファイルを格納したい場所】
$ wget https://raw.githubusercontent.com/faker-ruby/faker/master/lib/locales/ja.yml -P config/locales/faker
config/application.rb
module Club
  class Application < Rails::Application
    ...
    config.i18n.load_path += Dir[Rails.root.join('config', 'locales', '**', '*.{rb,yml}').to_s]
    ...
  end
end

seed.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

配列を用いて複数のデータを一括で作成する

  1. users_listで今回作成するユーザを格納する。(今回は特に使用しないが後々便利になるかも)
  2. users_number.timesを用いて、指定の回数分ユーザを作成する。
  3. 1回のブロック処理でusers_listの中に1人のユーザの情報を格納していく。
  4. 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.eachGroup.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】アソシエーションの初期データを作ってみた

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【初心者目線】RubyでFizzBuzz問題を"簡単に"解いてみた!

この記事では、FizzBuzz問題についてまとめてみました!

皆さんはプログラミングの学習を初めてから、だいぶ初期にこの問題を目にしたのではないでしょうか。

FizzBuzz問題は初心者にもそこまで難しくない問題で、プログラミングの効果的な学習材料となります。

できるだけ初心者向けでわかりやすいように頑張りますので、是非最後までお付き合いください!

FizzBuzz問題とは?

プログラミングで言うFizzBuzz問題とは、3で割ることのできる数字を入力したら"Fizz"、5で割ることのできる数字を入力したら"Buzz"、または両者で割ることのできる数字(すなわち15で割ることができる数字)を入力したら"FizzBuzz"と返事が来るプログラムを書く問題のことです。

Rubyの入門参考書などを買うとよく載っているので、皆さんの中には実際に解いたことがあると言う人も少なくないでしょう。

FizzBuzzの書き方・考え方

まずは完成形のコードをみてみましょう。

FizzBuzz.rb
def 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.rb
def 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が出力されるようにプログラムを書きます。

どうだったでしょうか?わかりにくいところなどがあったら教えていただけると幸いです!

この記事のリライトの材料にもなりますし、僕自身の成長にも繋がるので是非お意見お待ちしております!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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

を実行した時に

console
Mysql2::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を利用できるようにする

console
heroku stack:set heroku-18 -a <app name>

➁config/database.ymlに必要な記述が無かった
productionに以下のコードを追加

config/database.yml
production:
  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'] %>」

要はこの記述が無かったことで、どこのデータベースを使えばいいか分からずにエラーが出ていた。

まとめ

何かおかしいことが起こり、ググっても分からないときは使っているもののバージョンを確認してみよう!

読んでいただきありがとうございました。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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

を実行した時に

console
Mysql2::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を利用できるようにする

console
heroku stack:set heroku-18 -a <app name>

➁config/database.ymlに必要な記述が無かった
productionに以下のコードを追加

config/database.yml
production:
  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'] %>」

要はこの記述が無かったことで、どこのデータベースを使えばいいか分からずにエラーが出ていた。

まとめ

何かおかしいことが起こり、ググっても分からないときは使っているもののバージョンを確認してみよう!

読んでいただきありがとうございました。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

投稿を1段3つずつに分割する(each_sliceメソッド)

本記事の目的

  • railsで投稿を1段3分割にするやり方の共有
    →作成するにあたり、意外にやり方が見つからなかったため

記事の対象

  • railsで投稿を以下画像の様に1段3分割にしたい方 eachslice.png

やり方

each_sliceメソッドを使う。

each_sliceメソッドはブロックに渡す要素数を指定することができる。

例えば、、、

sample.rb
numbers = [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.rb
numbers = [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.rb
def 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で続きを読む

[Ruby] VSCodeのdebug実行でエラー「cannot load such file」が出た際の対処

原因

${file}はフルパスに置き換えられることが原因でした。"${workspaceRoot}/${file}"ではなく"${file}"と記載すればOKでした。
@nodai2h_ITC さん、ご指摘ありがとうございました。:bow::bow::bow:

実行環境

macOS (version: 11.1)
VSCode (version: 1.51.1)
Ruby (version: 2.7.2)

対処法

launch.json内のprogramの指定を、"${workspaceRoot}/${file}"ではなく"${file}"に変更すればOK

launch.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>'
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Jenkins+CapistranoでUser credentialをデプロイ先へ渡す

Capistranoで実行元の環境変数をデプロイ先へ送るにはどうすれば良いか?

direnvAccess 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

ただちょっとトリッキーなので解説。

  1. hoge_credentialとして登録します
  2. Jenkinsfileに使うように指示を追加します。
  3. 参照する時
    • user: USER_CREDENTIAL_USR
    • password: USER_CREDENTIAL_PSW

Jenkinsfileのサンプル

Jenkinsfile
pipeline {
    environment {
        USER_CREDENTIAL = credentials('$hoge_credential')
    }
}

こうなります。

Capistranoから環境変数をリモートへ送る

Jenkinsfileの中で参照できましたのでCapistranoからもENVとして参照できます。

先程のUSER_CREDENTIALで書くと、

deploy.rb
namespace :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

となります。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

RSpec コントローラーテストの書き方

前提

・deviseが導入されています
・今回書くテストの内容について
  ・ユーザーがログインしていれば、正しく表示されること

app/controllers/posts_controller.rb
  class 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.rb
RSpec.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.rb
require 'spec_helper'
...
require 'rspec/rails'
require 'support/factroy_bot'  #←この記述によってFactroyBotを使用できる

ダミーデータの作成

spec/factories/user.rb
FactoryBot.define do
  factory :user do 
    email { Faker::Internet.email }
    name { "testuser1" }
    password { "password" }
    password_confirmation { "password" }
  end 
end
spec/factories/post.rb
FactroyBot.defind do
  factory :post do
    body { Faker::Lorem.charactors(number: 15) } #15文字のダミー文を作成
  end
end

テストコードの作成

spec/requests/post_spec.rb
require '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

最後に

初心者中の初心者の記事なので、十中八九ミスがあると思います。
気づかれた方がおられたら、教えていただけるととてもありがたいです!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

プロを目指す人のためのRuby入門 配列や繰り返し処理4章

イントロダクション

今回プロを目指す人のためのRuby入門1の続きを書いていこうと思います!
引き続き、自分用アウトプットとなっておりますので、暖かく見ていただけると幸いです!

この章で学ぶこと

配列
ブロック
範囲
様々な繰り返し処理
繰り返し処理用の制御構造

配列

配列とは複数のデータをまとめて格納できるオブジェクト。

#空の配列
[]

#3つの要素が格納された配列
[要素1,要素2,要素3]

配列のクラスはArrayクラス

配列はどんなデータ型でも格納できる、それぞれが違ったデータ型だろうと、、、
また配列の中に配列を入れたりも可能

a = [1, 2, 3, 4, 5]
a.size #=> 5
a.length #=> 5

sizeと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/reduce

map/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 #=> 2

inject/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) #=> false

case文を使う

範囲オブジェクトは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 #=> 40

loopメソッド

ループ処理を行うときに使用

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
・redo

break

繰り返し処理からの脱出

#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
# 5

breakに引数を渡すと、while文やfor文の戻り値になる 
ない場合戻り値はnilになる

ret = 
  while true
    break 123
  end
ret #=> 123

breakは繰り返し処理が重なっていた場合一番内側の繰り返し処理しか脱出できない

throwとcatchをつかった大域脱出

breakは繰り返し処理が重なっていた場合一番内側の繰り返し処理しか脱出できない

と言いましたが、一番外側のループまで脱出したい場合は、throwとcatchメソッドを使う

catch タグ do 
  #繰り返し処理など
  throw
end
fruits = ['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・・・配列からランダムの要素を取得

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【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)まで文字列を格納しました。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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」オプションを使えば「^」や「$」も使えるようなので、複数行を渡すことが前提であればこのオプションを使う解決方法の方がいいのかもしれませんね。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

単体テストコードの実装

私自身の備忘録として簡単にテストコードの実装をここに残しておきます。

前提条件として、今回はユーザーの新規登録の単体テストコードを作成します。
テストする内容はnickname、email、password、password_confirmationの4つをテストします。

gemの準備

まずテストするためのgemをgemfileに記述
※記述する箇所はgroup :development, :test doの中に記述すること
ここに記述するとテストする時にだけ使えるようになるため

gemfile
group :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.rb
require '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 

スクリーンショット 2021-01-15 12.23.43.png

これがターミナルに出力されたらおk

FactoryBotとFakerを使う

まずspecのディレクトリの中にfactoriesと言うディレクトリを作り、その中にuser.rbを作成する
スクリーンショット 2021-01-15 12.09.06.png

作成したusers.rbに以下を記述

spec/factories/users.rb(Fakerを使わない場合)
FactoryBot.define do
  factory :user do
    nickname              {"test"}
    email                 {"test@example"}
    password              {"000000"}
    password_confirmation {password}
  end
end
spec/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.rb
require '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 

スクリーンショット 2021-01-15 12.23.43.png

これらが表示されたらおk
もしエラーが出てしまうならエラー箇所にbinding.pryを記述し実行しましょう!

spec/models/user_spec.rb
it "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コンソールから脱出
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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

なるほどぉ...

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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を使っていこうと思う。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ActiveStorageを導入した時のimageカラムのテストコード

初心者の私がactivestorageを使ったときのテストコードで1時間ほど詰まったのでその備忘録です

まず前提として、必要なactivestorageやfactorybot、specのインストールは済ませているものとします。

済ませていなければ以下のgemをgemfileに記述しbundle installしましょう
その後RSpecのインストールをしましょうrails g rspec:install

gemfile
group :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.png

a.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

同じエラーに出会した方の参考になれば幸いです。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ステートレスとかステートフルとかのお話

ステートレスとは

ステートレス = 「状態を保持しない」
HTTPでは、HTTPリクエスト・HTTPレスポンスのこの1往復のやりとりで一つの処理として完結する。
よって複数の処理に関連を持たせることができない。

では、ステートフルとは

ステートフル = 「状態を保持する]
要するに、状態を保持して次の処理でも前の処理で行われた内容を反映させることをいう。

ステートフルのデメリット

ステートフルにはデメリットがある。例えば、複数のクライアントが同時にWEBサーバーへリクエストを行った場合に、WEBサーバーはその内容を保持する必要がある。すると、WEBサーバー1つに対して、複数クライアントの情報を保持する必要があるため、応答に多くの時間を必要としてしまう。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【HTTP】ステートレスとかステートフルとかのお話

ステートレスとは

ステートレス = 「状態を保持しない」
HTTPでは、HTTPリクエスト・HTTPレスポンスのこの1往復のやりとりで一つの処理として完結する。
よって複数の処理に関連を持たせることができない。

では、ステートフルとは

ステートフル = 「状態を保持する]
要するに、状態を保持して次の処理でも前の処理で行われた内容を反映させることをいう。

ステートフルのデメリット

ステートフルにはデメリットがある。例えば、複数のクライアントが同時にWEBサーバーへリクエストを行った場合に、WEBサーバーはその内容を保持する必要がある。すると、WEBサーバー1つに対して、複数クライアントの情報を保持する必要があるため、応答に多くの時間を必要としてしまう。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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 アクセス集中やメンテナンスなどの理由で一時的に処理不可
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【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 アクセス集中やメンテナンスなどの理由で一時的に処理不可
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【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 #=> nil

2.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つが行う動作はほぼ同じです。違うのはnilUser.noneの場合返り値だけ。

user.rb
class 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
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

管理画面生成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.

image.png

導入

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は、使いやすいように色々とカスタマイズ出来るので、次回いくつか紹介したいと思います。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む