20200528のRubyに関する記事は21件です。

[Rails]N+1は悪!発生したらとりあえず解消せよ!!という考えは危険

N+1

それは諸悪の根源!パフォーマンスの敵!!
見つけたらすぐに撃退すべき悪しき存在です!!!

と思われている方が多いと思います。

実際その通りで、ネットで調べてもN+1を解消するノウハウが溢れています。

基本的にはノウハウ通りに修正すれば良いのですが、まれにN+1は解消しない方が良いパターンもあるので具体例を交えて紹介します。

この記事で話したいこと

この記事で話したいことは、なぜN+1は直すべきなのか?ということです。
N+1はあまりに有名すぎて直すノウハウはたくさんありますが、なぜ直すべきなのかが忘れられている感があります。
1つ言っておきたいのは、N+1を直すのはクエリー発行数を減らしたいからではないです。パフォーマンスを改善したいからです!
言い換えるとクエリー数が減ってもはフォーマンスが改善しないのであればN+1を直す必要はないのです。

N+1とは

最初に典型的なN+1を復習しておきましょう。

下記のモデルを使って説明します。

  • ユーザー(user)は複数の記事(articles)を持っている
  • 記事には複数の写真(images)を添付することができる
class User
  has_many :articles
end

class Article
  belongs_to :user
  has_many :images
end

class Image
  belongs_to :article
end

このモデルを使って特定ユーザーの記事の一覧を取得するAPIを考えてみます。
レスポンスは下記の通り

{
  articles: [
    id: 1
    body: "hogehoge"
    images: [
      {
        id: 1
        alt: "alt"
        src: "https://example.com/hoge1.img"
      }
    ]
  ]
}

これをみんな大好き(?)Jbuilderを使うと下記のような実装になると思います。

class ArticlesController
  def index
    # 更新日の降順に10件取得(using kaminari)
    @articles = Articles.where(user_id: params[:user_id]).order(updated_at: :desc).page(params[:page]).per(10)
  end
end

# articles/index.json.jbuilder
json.articles do
  json.array!(@articles) do |article|
    json.id article.id
    json.body article.body
    json.images do
      json.array!(article.images) do |image|
        json.id image.id
        json.alt image.alt
        json.src image.src
    end
  end
end

上記を実行するとどうなるでしょうか?
下記のようにimagesテーブルを取得するSQLがarticlesの数だけ発行されます。

-- articles取得は1クエリー
SELECT `articles` FROM `articles` WHERE `articles`.`user_id` = 1 ORDER BY `articles`.`updated_at` DESC LIMIT 10

-- articlesの数だけimages取得クエリーが発行される
SELECT `images`.* FROM `images` WHERE `images`.`article_id` = 1
SELECT `images`.* FROM `images` WHERE `images`.`article_id` = 2
SELECT `images`.* FROM `images` WHERE `images`.`article_id` = 3
SELECT `images`.* FROM `images` WHERE `images`.`article_id` = 4
SELECT `images`.* FROM `images` WHERE `images`.`article_id` = 5
...
SELECT `images`.* FROM `images` WHERE `images`.`article_id` = 10

これを解消するための機能がRailsには備わっています。
それがpreload, eager_load, includesですね。
これらの細かい使い方はこの記事では省略しますが、今回はpreloadを使ってN+1を解消します。

class ArticlesController
  def index
    # preload(:images)追加
    @articles = Articles.where(user_id: params[:user_id]).preload(:images).order(updated_at: :desc, id: :desc).page(params[:page]).per(10)
  end
end
-- articles取得は1クエリー
SELECT `articles` FROM `articles` WHERE `articles`.`user_id` = 1 ORDER BY `articles`.`updated_at` DESC LIMIT 10

-- images取得も1クエリー
SELECT `images`.* FROM `images` WHERE `images`.`article_id` in (1, 2, 3, 4, 5, 6, 7, 8, 9, 10)

クエリーは発行するコストが高いのでクエリー数を減らすことでパフォーマンスを大幅に改善することができます。
今回の例でも11クエリーが2クエリーと大幅に減っています。

N+1撃退完了!!

N+1を解消させない方が良いパターン

上記の例で無事にN+1が解消しましたが、もしimageを大量に持っているユーザーがたくさんいるとしたらこの対応は本当に良かったのでしょうか?
極端な例になってしまいますが、1つのarticlesあたり平均1,000枚のimageが紐づいている場合を考えてみましょう。

この場合、上記のレスポンスのままだと1,000 * 10 = 10,000のimageを返すことになってしまい、レスポンスが巨大になりすぎてパフォーマンスが劣化します。
そこで大抵の場合は記事一覧ではimageの一部(先頭5件だけとか)を返却する仕様に変更しようとかなるわけです。

では仕様変更してみましょう。
コントローラーはそのままでjbuilderの箇所を変更してみました。

# articles/index.json.jbuilder
json.articles do
  json.array!(@articles) do |article|
    json.id article.id
    json.body article.body
    json.images do
      # 最初の5件だけ取得
      json.array!(article.images.first(5)) do |image|
        json.id image.id
        json.alt image.alt
        json.src image.src
    end
  end
end

これで実行すると無事レスポンスのimageはarticleごとに5件までになります。
めでたしめでたし...とはなりません!!!!

どこが問題でしょうか?
クエリーを見ても1つ前の例と同じ2クエリーしか発行されていません。

-- articles取得は1クエリー
SELECT `articles` FROM `articles` WHERE `articles`.`user_id` = 1 ORDER BY `articles`.`updated_at` DESC LIMIT 10

-- images取得も1クエリー
SELECT `images`.* FROM `images` WHERE `images`.`article_id` in (1, 2, 3, 4, 5, 6, 7, 8, 9, 10)

問題点は分かりましたか?
今回の例では前回N+1を解消したimagesのクエリーが問題になります。
今回の前提条件で1つのarticlesに1,000枚のimageが紐づいているとしています。
ということは、このimagesのクエリーでは10,000枚のimageオブジェクトが取得されていることになります。
ActiveRecordは便利な反面、オブジェクトのサイズがとても大きいです。そのオブジェクトを10,000個生成したらかなりのメモリを消費します。
しかも今回は先頭の5枚しか使いません。

ではpreloadを外してN+1解消前の状態に戻すとどうなるでしょうか?

-- articles取得は1クエリー
SELECT `articles` FROM `articles` WHERE `articles`.`user_id` = 1 ORDER BY `articles`.`updated_at` DESC LIMIT 10

-- articlesの数だけimages取得クエリーが発行される
SELECT `images`.* FROM `images` WHERE `images`.`article_id` = 1 limit 5
SELECT `images`.* FROM `images` WHERE `images`.`article_id` = 2 limit 5
SELECT `images`.* FROM `images` WHERE `images`.`article_id` = 3 limit 5
SELECT `images`.* FROM `images` WHERE `images`.`article_id` = 4 limit 5
SELECT `images`.* FROM `images` WHERE `images`.`article_id` = 5 limit 5
...
SELECT `images`.* FROM `images` WHERE `images`.`article_id` = 10 limit 5

imageのクエリーはarticlesの数だけ発行されるように戻ってしまいますが、それぞれ5件しか取得しないのでActiveRecordオブジェクトの生成数は10,000個から50個に大幅に減少します。

実行環境の性能に左右されるので絶対とは言えませんが、N+1を解消するよりメモリーを節約した方がパフォーマンスが上がることはよくあります。
(※実際にどちらの対応が適切かは実行環境と同等の環境でパフォーマンス検証しないと分かりません)
もしN+1は悪!絶対に直さないといけないと言う考えがあると、上記のようにimagesのクエリーが発行されているのをみるとpreloadをつけてしまい、メモリ使用量を大幅に上げてパフォーマンス劣化を招いてしまうかもしれません。

最後に

N+1はとても有名で目につきやすく、しかもRailsだとpreloadなどを付けるだけでさくっと解消させることができるため、深く考えずにとりあえず解消させるということがよくあると思います。
ただ今回の例のようにhas_manyを先読みしておく場合はどれくらいの件数が見込まれるかを考えてから実装するようにしましょう。
件数を考慮せず全部取得してしまうとメモリー不足を引き起こしてパフォーマンス劣化や最悪の場合にはサーバーをフリーズさせてしまう可能性があります。

あと、最初にも少し書きましたがN+1を直す目的をきちんと認識しましょう。
N+1を直した時にクエリーが減ったことを確認して対応完了にすることはありませんか?
N+1を解消するのは発行クエリーを減らすためではなくパフォーマンスを改善させるためです。
ただクエリーが減ったことだけを確認するのではなく、きちんとパフォーマンスが改善していることを確認するようにしましょう。

パフォーマンスを改善させるという観点で考えるとN+1の他にもメモリー利用量やループなど処理回数の削減など様々な観点があります。
N+1はあくまでパフォーマンス改善の1つの手であるということを忘れず、他の観点も頭に入れておくようにしましょう。

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

[Rails]strftimeあれこれ

前置き

現在Railsでマッチングアプリ作成中ですが、strftimeをよく使うので、すぐ見れるよう書き方をまとめておきます。

strftimeって?

日時データを好きなようにビューに表示出来るメソッドです。

やってみよう

今回はcreated_atでやっていきます。

デフォルトだとこうですね。後ろの+0900は使わなさそうですね。
2020-05-22 16:11:01 +0900

created_at.strftime("%Y-%-m-%-d %-H:%M")

# 表示 2020-5-28 20:25

こんな風に文字列変換出来ます。
適当に記号並べているわけではありません。笑

下記に書式を一部添付しておきます。
もっと知りたい方はこちらにあります。
引用元pikawaka

書式 返り値 説明
%Y 2019 西暦を4桁の数で取得する※西暦1年は「0001」
%m 01 月を必ず2桁の数字で取得する(01-12)
%-m 1 月を1桁か2桁の数字で取得する(1-12)
%d 01 日付を必ず2桁で取得する(01, 02 ... )
%-d 1 日付を1桁か2桁で取得する(1, 2 ... 10, 11 ...)
%H 12 24時間制の時刻を必ず2桁で取得する(00-23)
%-H 12 24時間制の時刻を1桁か2桁で取得する(0-23)
%I 12 12時間制の時刻を1桁か2桁で取得する(1-12)
%M 00 分を取得する(00-59)
%S 00 秒を取得する(00-60)※60はうるう秒

ありがとうございました!

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

Railsアプリで初めて自作ヘルパーを使ってリファクタリングした話

はじめに

今までRuby on Railsでアプリケーションを作成していて
ふと気になったことがありました。

コントローラー作成時に毎回ついてくるコイツ
〇〇_helper.rb
このファイル何に使うんだろ...。
ヘルパーって書いてあるからヘルパーメソッドに関係しているのかな...??

この時、私は
"わざわざ同時に作成される物だからきっと便利なファイルに違いない"
と、興味の向くままにQiitaで解説記事などを調べてみました。

結論

自作のヘルパーメソッドを記述するファイルのようです。
(自作ヘルパー == カスタムヘルパー と言うらしい)
ヘルパーメソッドは今日の今日までGemに用意されているモノしか使用したことがなかったので
これは便利そうだ、と直感で思いました。

さっそく使ってみた

他の方のQiita記事を参考にさせていただき
今回は個人開発したアプリのコードリファクタリングに使用してみました。
コントローラー内のリファクタリング内容を本記事の内容にします。

環境

Rails 5.2.3
Ruby 2.5.1

使い方

まずはリファクタリングをするコントローラーを見ていきましょう。

users_controller.rb
class UsersController < ApplicationController

~省略~

  private
  def set_user
    @user = User.find(params[:id])
  end

  def check_user
    redirect_to new_user_registration_path, alert: 'ログインまたは新規登録をお願いします' unless current_user.id == @user.id
  end
end

check_userというメソッド内のunless以下がイケてないので
ヘルパーメソッドを作成してリファクタリングします。


helpers/users_helper.rb
module UsersHelper

  def current_user?(user)
    current_user.id == user.id
  end

end

current_user?というヘルパーメソッドを作成しました。
このメソッド名および内容はけっこう定番のようです。


helpersというディレクトリ直下に
application_helper.rbというファイルもありますが
これは複数のコントローラーやビューに適用させるヘルパーメソッドを管理するファイルのようです。
※今回は未使用


さっそく作成したヘルパーメソッドをコントローラー内に適用させましょう。

users_controller.rb
class UsersController < ApplicationController
  include UsersHelper

~省略~

  private
  def set_user
    @user = User.find(params[:id])
  end

  def check_user
    redirect_to new_user_registration_path, alert: 'ログインまたは新規登録をお願いします' if !current_user?(@user)
  end
end

説明

コントローラー内でhelperファイルの自作ヘルパーを使う際は
2行目のようにincludeで読み込み必要があるそうです。
※筆者はコントローラー名とヘルパーファイル名が対応してるから勝手にやってくれるんやろうな
と、Rails特有の処理が入ると勝手に思い込んでいましたが記述が必要なようです。


リファクタリング内容ですが
set_userで定義された@userを引数に渡して
先ほど作成したcurrent_user?メソッドを使用しました。

また、unlessという記述もif !~という記述に変更しました。
これで少しはコードがスッキリしました。




同じような内容でposts_controllerもリファクタリングします。

posts_controller.rb
class PostsController < ApplicationController


  def edit
    redirect_to new_user_registration_path, alert: 'ログインをお願いします' unless current_user.id == @a_post.user_id
  end

  def destroy
    redirect_to new_user_registration_path, alert: 'ログインをお願いします' unless current_user.id == @a_post.user_id
    if @a_post.destroy
      redirect_to root_path, notice: '投稿を削除しました'
    else
      render :show, notice: '投稿が削除できませんでした'
    end
  end


  private

  def set_post
    @a_post = Post.find(params[:id])
  end
end

こちらもunless以下の記述を修正していきます。

まずはヘルパーメソッドを作成。

helpers/posts_helper.rb
module PostsHelper

  def current_user_post?(a_post)
    current_user.id == a_post.user_id
  end

end

これをコントローラーに適用していきます。

posts_controller.rb
class PostsController < ApplicationController
  include PostsHelper

  def edit
    redirect_to new_user_registration_path, alert: 'ログインをお願いします' if !current_user_post?(@a_post)
  end

  def destroy
    redirect_to new_user_registration_path, alert: 'ログインをお願いします' if !current_user_post?(@a_post)
    if @a_post.destroy
      redirect_to root_path, notice: '投稿を削除しました'
    else
      render :show, notice: '投稿が削除できませんでした'
    end
  end

  private

  def set_post
    @a_post = Post.find(params[:id])
  end
end

こちらもヘルパーを読み込んでリファクタリングしたら完了です。

最後に

今回はコントローラーに絞って記事を書きましたが自作ヘルパーはもちろんビューにも使用できます。
また、ビューに使用する場合はコントローラーと違って、読み込みの記述が必要ないみたいですね。
私もこのあとビューファイルのコードをリファクタリングしようと思います。

どこか不備がありましたらコメントにてご指摘いただけると幸いです。

参考記事

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

【Ruby】複数の条件下で、配列から特定の値を取り出す方法【select・each】

できるようになること

複数の条件を用いて、配列から特定の値を見つけることができます。

例)1~10までの数字のうち、5以下かつ2で割れるものを導き出しなさい。
みたいな問題が解けるようになります。

方法

Array#eachとArray#selectを使います。

numbers = (1..10).to_a
rule = [["<", 5],["%", 2]]

rule.each do |b|
    numbers.select! do |a|
        if b[0] == "<" 
            a <= b[1]
        else
            a % b[1] == 0
        end
    end
end

p numbers # [2,4]

つまりどう言うことか

条件(今回はrule)をeachで回して、その中に判別したいもの(今回はnumber)を入れる必要があります。かつて、逆にして困っており、最近この方法に気づいたのでまとめさせていただきました。

何か同じ悩みで引っかかっていた人の解決になれば嬉しいです。

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

個人的に頻繁に使ったRSpec集

RSpecを書いているうちに、「これとよく似た比較書いたけどどのプロダクトのどのSpecだっけ?」って探して時間を取られた事があったので、ここに記事としてまとめておきます。未来の自分と他の開発者の方に役立つ事を願って。

エラーログが出力されている事を確認。

it 'エラーログが出力される事' do
    expect(Rails.logger).to receive(:error).with(/code: 400/)
    # 以下、テストしたい処理を書く
end

ポイントとしては

  • expectをitの一番最初に書く。こうしないとログの出力メソッドがreceiveによって捕捉されません。
  • withの中に、含まれていてほしいメッセージを書く。そのまま書くと完全一致にならないとパスしないので、スラッシュで囲むことで、特定のメッセージを含んでいた場合のケースを書くことができます。いわゆる正規表現。

参考にしたサイト

エラーログが出力されていない事を確認。

it 'エラーログが出力されない事' do
    expect(Rails.logger).to receive(:error).exactly(0).times
    # 以下、テストしたい処理を書く
end

先程の例の応用で、「正常に処理が完了するケースの場合はエラーログが出ないはずだ。」も調べたかった。
receiveメソッドにつなぐ形で回数を指定できるので、.exactly(0).timesでメソッドが呼ばれていない事を確かめました。
参考にしたサイト

よくRubyやRailsを書く人からしてみれば「当たり前じゃねぇか!」って言われるかもしれませんが、私がここ最近初めて本格的にRailsを使った開発にアサインされて、戸惑いながらも開発していた時に得たものなので、優しい目で見ていただけますと幸いでございます。
使えそうなものが出てきたら追記していこうと思います。

ありがとうございました。

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

Ruby 自作クラスの配列をソートする

Ruby 自作クラスの配列をソートする

100日後に1人前になる新人エンジニア(8日目)です

あれ自作したクラスの入った配列ってどうやってソートするんだっけ...
って今日ちょっと詰まってしまったのでメモとして残しておきます。

自作のクラス

user.rb
class User
  attr_accessor :name, :age, :height

  def initialize(name, age, height)
    @name = name
    @age = age
    @height = height
  end
end

user1 = User.new("foo", 15, 190)
user2 = User.new("bar", 80, 150)
user3 = User.new("baz", 28, 168)
users = [user1, user2, user3]

こんな感じで自作のユーザークラスを作りそれらをusersという配列に入れる
このusersの並べ替えについて今日は考える

とりあえずソートしてみる

ruby.rb
users.sort
#エラーが発生
#`sort': comparison of User with User failed (ArgumentError)

上のように怒られます。
Userを比較することに失敗しました。
オブジェクトそのままの比較はできないですね。

ということでオブジェクトの属性で比較します。
そのためにはsortメソッドではなくsort_byを使います
年齢で比較する

ruby.rb
users = users.sort_by{|user|user.age}  #昇順
p users.map{|user|"#{user.name},#{user.age},#{user.height}"}
#出力["foo,15,190", "baz,28,168", "bar,80,150"]

users = users.sort_by{|user|user.age}.reverse  #降順
#出力["bar,80,150", "baz,28,168", "foo,15,190"]

こんな感じで自作クラスの属性ごとにソートすることができました。
降順にしたいときはreverseメソッドを使えば簡潔になります。
またsort_byメソッドももっと簡単にかけるので、

ruby.rb
users = users.sort_by(&:height)

この一行でusersの配列に入ったuserを身長昇順に並び替えられます。
Rubyてすごいよね。

本日はここまでです。
みてくださった方ありがとうございました。

1人前のエンジニアになるまであと93日

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

【Rails初心者必見】Ruby on Railsでversion指定をしてWebアプリを作成する方法

開発環境

ruby 2.6.4

Rails 6.0.2.2

やりたいこと

Rails初心者にとって開発環境を整えるのは難しいですよね
環境構築で挫折してほしくないんです!

そこで今回は環境設定の一部ではありますが、Railsのバージョン指定して新規プロジェクトを作成していきます

versionを指定して新規プロジェクトを作成する

ステップ1. 特定のRailsのバージョンをインストール

$ gem install rails -v 6.0.2.2

ステップ2. インストールしたRailsを確認

$ gem list rails

ステップ3. インストールしたRailsのバージョンを指定して新規プロジェクトを作成

$ rails _6.0.2.2_ new twinz_blog
# twinz_blogの箇所にはアプリ名を入れてください

すごく簡単にできましたね!

参考文献

【Rails初心者必見】Railsのversionを指定して新規プロジェクトを作成する方法
https://www.twinzlabo.com/rails_version_project/

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

【Rails初心者必見】Ruby on Railsでversion指定をしてWebアプリを新規作成する方法

開発環境

ruby 2.6.4

Rails 6.0.2.2

やりたいこと

Rails初心者にとって開発環境を整えるのは難しいですよね
環境構築で挫折してほしくないんです!

そこで今回は環境設定の一部ではありますが、Railsのバージョン指定して新規プロジェクトを作成していきます

versionを指定して新規プロジェクトを作成する

ステップ1. 特定のRailsのバージョンをインストール

$ gem install rails -v 6.0.2.2

ステップ2. インストールしたRailsを確認

$ gem list rails

ステップ3. インストールしたRailsのバージョンを指定して新規プロジェクトを作成

$ rails _6.0.2.2_ new twinz_blog
# twinz_blogの箇所にはアプリ名を入れてください

すごく簡単にできましたね!

参考文献

【Rails初心者必見】Railsのversionを指定して新規プロジェクトを作成する方法
https://www.twinzlabo.com/rails_version_project/

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

Ruby と Python で解く AtCoder ARC080 D シミュレーション

はじめに

AtCoder Problems の Recommendation を利用して、過去の問題を解いています。
AtCoder さん、AtCoder Problems さん、ありがとうございます。

今回のお題

AtCoder Regular Contest D - Grid Coloring
Difficulty: 855

今回のテーマ、シミュレーション

内容は難しいところはないと思いますが、実装に手間がかかりそうな問題です。

1 2 2 3 3
4 4 4 4 3
5 5 5 5 5

与えられた数値で上から順に左右に塗りつぶせばOKです。

Ruby Array

ruby.rb
h, w = gets.split.map(&:to_i)
_ = gets.to_i
a = gets.split.map(&:to_i)
m = Array.new(h){Array.new(w, 0)}
y, x, lr = 0, 0, 1
a.each_with_index do |n, v|
  n.times do
    m[y][x] = v + 1
    x += lr
    if x == w
      y += 1
      x = w - 1
      lr = -1
    elsif x < 0
      y += 1
      x = 0
      lr = 1
    end
  end
end
h.times do |i|
  puts m[i].join(' ')
end
join.rb
  puts m[i].join(' ')

  puts m[i] * ' '

最近知ったのですが、joinは上記の様に書くこともできます。

Ruby Class

ruby.rb
class MASS
  def initialize(h, w)
    @h = h
    @w = w
    @m = Array.new(@h){Array.new(@w, 0)}
    @x = 0
    @y = 0
    @LR = 1
  end
  def draw(v, n)
    n.times do
      @m[@y][@x] = v + 1
      @x += @LR
      if @x == @w
        @y += 1
        @x = @w - 1
        @LR = -1
      elsif @x < 0
        @y += 1
        @x = 0
        @LR = 1
      end
    end
  end
  def out
    @h.times do |i|
      puts @m[i].join(' ')
    end
  end
end

h, w = gets.split.map(&:to_i)
_ = gets.to_i
a = gets.split.map(&:to_i)
m = MASS.new(h, w)
a.each_with_index do |v, i|
  m.draw(i, v)
end
m.out

今回の問題では、あまり有効ではないのですが、学習を兼ねてclassを使用してみました。
コード長が倍近くになりましたが、実行時間は変わらないようです。

Python

python.py
from sys import stdin

def main():
    input = stdin.readline
    h, w = map(int, input().split())
    _ = int(input())
    a = list(map(int, input().split()))
    m = [[0] * w for _ in range(h)]
    x, y, LR, v = 0, 0, 1, 0
    for n in a:
        v += 1
        for _ in range(n):
            m[y][x] = str(v)
            x += LR
            if x == w:
                y += 1
                x = w - 1
                LR = -1
            elif x < 0:
                y += 1
                x = 0
                LR = 1
    for i in range(h):
        print(" ".join(m[i]))
main()

Python のコード長が長いのは、半角スペースもカウントしているからでしょうか。

Ruby Array Ruby Class Python
コード長 (Byte) 392 631 603
実行時間 (ms) 17 18 25
メモリ (KB) 2428 3836 3828

まとめ

  • ARC 080 D を解いた
  • Ruby に詳しくなった
  • Python に詳しくなった
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Rubyでリスキーダイスを作ってみた

rubyの練習でハンターハンターに登場するリスキーダイスを作ってみた。
1から20の数字があり20が大凶です。

def risky_daice
  array = []
  puts '何回振ろうかな'
  stop = gets.to_i
  puts 'ダイスを振るよ'
  while true
    deme = rand(1..20)
    num = array.push deme
    if deme == 20
      puts deme
      puts 'あーあ出ちゃった'
      p "#{num.count}回目で大凶がでた"
      break
    elsif num.count == stop
      puts deme
      puts 'そこでやめ'
      p "#{num.count}回振れたよ。また挑戦してね"
      break
    else
      puts deme
    end
  end
end
risky_daice
-> % ruby risky_daice.rb

何回振ろうかな
20
ダイスを振るよ
16
12
4
1
12
8
18
19
15
2
16
5
3
5
5
3
20
あーあ出ちゃった
"17回目で大凶がでた"

本番の前に振るといいことあるかも?

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

rails link_to

railsのlink_to

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

インスタンスメソッドとクラスメソッドの違い

Rubyに関して学習中に表題の部分で混乱してしまったので、
整理するためにもまとめて見ました。

※誤っている点がありましたら、ご指摘いただけると幸いです。。。

インスタンスメソッドとクラスメソッドの違い

class Example
  def index
  end
end


  ①example = Example.new 
  example.index  //インスタンスから呼び出す →○

  ②Example.index  //クラスから直接呼び出せない →こちらを使用する際はメソッドにself.をつける(self.index)

  index  //もちろんこれではエラー表示

要するに、基本メソッドを呼ぶ際は、インスタンスを作成してそこからよび起こす(①)。
→インスタンスメソッド

②のようにインスタンスの作成をせずともメソッドを呼び起こす方法。
→クラスメソッド

といったようなイメージ。

クラスメソッドの使用のメリットとしては、他インスタンスの情報を含まないメソッドとして使用できる点があるようです(具体例はわからなかったので、わかり次第まとめます。)
まずはクラスと関係性はあるが、他インスタンスの情報を含まない独立したメソッドという認識でOKそう。

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

インスタンスメゾットとクラスメゾットの違い

Rubyに関して学習中に表題の部分で混乱してしまったので、
整理するためにもまとめて見ました。

※誤っている点がありましたら、ご指摘いただけると幸いです。。。

インスタンスメゾットとクラスメゾットの違い

class Example
  def index
  end
end


  ①example = Example.new 
  example.index  //インスタンスから呼び出す →○

  ②Example.index  //クラスから直接呼び出せない →こちらを使用する際はメゾットにself.をつける(self.index)

  index  //もちろんこれではエラー表示

要するに、基本メゾットを呼ぶ際は、インスタンスを作成してそこからよび起こす(①)。
→インスタンスメゾット

②のようにインスタンスの作成をせずともメゾットを呼び起こす方法。
→クラスメゾット

といったようなイメージ。

クラスメゾットの使用のメリットとしては、他インスタンスの情報を含まないメゾットとして使用できる点があるようです(具体例はわからなかったので、わかり次第まとめます。)
まずはクラスと関係性はあるが、他インスタンスの情報を含まない独立したメゾットという認識でOKそう。

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

データの並び替え降順、昇順/Railsでまとめてみた

投稿したデータや記事の並び替えを変える際に、descが降順?降順?で毎回ググって調べていたのでまとめました。

board.rb
class Board < ApplicationRecord
  scope :desc, -> { order(created_at: :desc) } #作成の降順
  scope :asc, -> { order(id: :asc) } #idの昇順 デフォルトでasc(昇順)で表記されるので明示的に書くことは少ない
end
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

なぜファイル末尾に改行を入れるのか

Rubyのプログラムを書いていてふと思ったのでメモ。

なぜ最終行に改行を入れるのですか?

POSIX上、テキストファイルは「行」の集合体で、そして「行」は「0文字以上の文字+改行」からなっている、とのことです(Qiita)。
Rubyのコードでも、(よほど変なことをしない限り)それに従わない必要性のある理由もないことでしょう。C言語においても、改行で終わらないコードは正しく動くことが保証されません。

  • そう決まっている
  • 改行がなかったらエラーが起きるかもしれない

ということみたいですね。

VSCodeでファイル保存時に自動で改行うを入れる設定

  1. ツールバー > Code > Preferences > Settings で設定を開く
  2. 「insert newline」で検索
  3. 「Files: Insert Final Newline」の項目にチェックを入れる
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

test

api test

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

マイグレーションファイルがダブっちまった

近況報告

 絶賛就活中でございます。最近やっているのは習慣化ですね。最近,英語と筋トレを習慣化させることができました。いきなり英語は飽きると思ったので,Gacktの母音発音のyoutubeをみて簡単な文を音読しています。考えが改まったことがひとつ,Google翻訳ってあるじゃないですか。あれ,全然翻訳してくれないからゴミだと昔は思っていたんですけど,実際は自身の発音がゴミだったってことがわかりましたw 筋トレは腹筋スクワットメインの運動を自重かつ強度をあげて20分だけ行なっています。腹筋が生まれて初めて横以外に割れそうな勢いです。そしてここ数日で始めたのは朝型転換です。ここ二ヶ月日付が変わったらアマゾンなプライムでビデオ見まくっていたので,生活習慣の改善として行なっています。

今回のお題

 マイグレーションファイルがダブってる
 始めたての初心者がいつの間にかハマるやつですね!・・・はい,やらかしました。
 ⇨解決策は片方を消す。以上。

マイグレーションファイル

●マイグレーションファイルは噛み砕くとテーブルの設計図です。
 rails db:migrateの際にテーブルを作成更新するときに利用します。一度利用されたマイグレーションファイルはテーブルを再度更新,生成しない限り用いることはありません。実際削除しても問題ありません。

●本当に削除していいの?
 そこに書かれている情報を二度と使わないのであれば消してもいいよ。
 開発においてテーブルの変更はなんども行われます。そのときにマイグレーションファイルがないと以前はあったとしてもパソコンがテーブルを読み取れなくてエラーを起こします。また,開発環境で用いたテーブルは本番環境でも使えるわけではなく,本番環境のDBを設置して再度テーブルを組み立てることが多いので,本番の段階でマイグレーションファイルがないのは一大事なわけです。長くなりましたが結論はもう使わなくてもほっとけです。

●ダブった場合
 マイグレーションファイルがダブっている場合も消したらテーブルが作成できなくなると心配になります(自分はなった)。ただ、この場合は同じ設計書が二枚あるだけなので,一枚になっても変わりませんし,マイグレーションファイルの数だけテーブルができるわけではないので安心して片方消してください。

最後に

内定が欲しい! エンジニアライフを早く堪能したい!

 

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

hamlでクラス名やid名に変数を使う

アプリ開発中にクラス名にインデックスをつけたいときがあり、書き方が分からずつまづいたので、同じような方がおられましたら参考にしてください。

基本

まず、基本的なクラス名id名の書き方は「.」「#」始まりで書き始めます。

#クラス名
.item_name
%p.item_name

#ID名
#item_name
%p#item_name

変数を使った書き方

そして今回わたしがつまづいたのはeach文の中で変数名を使った書き方です。
下記の書き方で適用されました!

-@items.each_with_index do |item, i|
  %div{class: "#{item.name}"}
    %p{class: "item_name_#{i}"}

クラス名を{}で囲うと、適用されます!
each文ではない場合でも同じ書き方で適用されます

%div{class: @item.name}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Rails 6.0で"Uniqueness validator will no longer enforce case sensitive comparison in Rails 6.1."という警告が出たときの対処法

はじめに

MySQLを使っている既存のRailsアプリケーションをRails 6.0にアップデートすると、次のような警告が出ることがあります。

DEPRECATION WARNING: Uniqueness validator will no longer enforce case sensitive comparison in Rails 6.1. To continue case sensitive comparison on the :name attribute in User model, pass `case_sensitive: true` option explicitly to the uniqueness validator.

(翻訳)
非推奨の警告: UniquenessバリデータはRails 6.1で「強制的に大文字小文字を区別する比較」をしなくなります。Userモデルの:name属性について引き続き「大文字小文字を区別する比較」を使い続けたい場合は、uniquenessバリデータに対して明示的にcase_sensitive: trueオプションを指定してください。

警告が出るのは次のようにuniquenessバリデータを使っている部分です。

class User < ApplicationRecord
  validates :name, uniqueness: true
end

とりあえず、こんなふうにcase_sensitiveオプションを付けると警告は出なくなります。

class User < ApplicationRecord
  # こうすれば警告は出なくなる、が!!!
  validates :name, uniqueness: { case_sensitive: true }
end

しかし、深く考えずにオプションを付けるのはあまりよくありません。
というわけで、この記事ではこの警告に対する対処方法を詳しく説明していきます。

Rails 5.2以前の仕様(と問題)

前提としてこの問題はMySQLを使っている場合に発生します。PostgreSQLを使っている場合は通常問題になりません。

詳しい話は省略しますが、MySQLにはcollationという概念があります。
デフォルトではutf8mb4_unicode_ciというようなcollationになっており、この場合はデータベースに保存された文字列の大文字小文字を区別しません。

つまり、"jnchito"という名前を検索するのに、WHERE name = 'jnchito'というSQLを発行しても、WHERE name = 'JNCHITO'というSQLを発行してもどちらもヒットします。

しかし、Rails 5.2以前のuniquenessバリデータはデフォルトで親切にも大文字小文字を区別する比較をしてくれます。

なので、DBに"jnchito"がすでに保存されている場合は、次のように振る舞います。

# 小文字のjnchitoはすでに登録済みなのでNG
user.name = 'jnchito'
user.valid? #=> false

# 大文字のjnchitoはすでに未登録なのでOK
user.name = 'JNCHITO'
user.valid? #=> true

# 背後では以下のようなSQLが発行されている(BINARYが付く)
# SELECT 1 AS one FROM `users` WHERE `users`.`name` = BINARY 'JNCHITO' LIMIT 1

一見これはありがたい仕様のように見えますが、次のような思わぬデメリットがあります。

  • DB上のユニーク制約に一致しないため、バリデーションの結果が100%信用できない
  • DB上のINDEXが効率良く使えないため、DBの負荷が大きくなる

実際、先ほど挙げたコードは以下のような矛盾した振る舞いをします。
(DB側にユニーク制約が付けられていた場合)

# 大文字の"JNCHITO"なら検証エラーなしだから保存できそうだ
user.name = 'JNCHITO'
user.valid? #=> true

# 保存実行・・・あれっ、DBのユニーク制約違反に引っかかって例外が発生しちゃった!!
user.save
#=> ActiveRecord::RecordNotUnique:
#     Mysql2::Error: Duplicate entry 'JNCHITO' for key 'users.index_users_on_name'

RailsでMySQLを使っているとこのような問題がたびたび発生していたようです。
(僕は普段PostgreSQLを使っているので気づいていませんでしたが)

(Rails 6.0ではなく)Rails 6.1で導入される仕様

この問題を回避するため、Rails 6.1のuniquenessバリデータはデフォルトで大文字小文字を区別しなくなります。
というか、厳密には「Rails側では素直にSQLを発行して、大文字小文字の区別はDB側の設定に任せる」という仕様になります。

これにより、DB側の機能をフル活用できるようになるため、上で挙げていた、

  • DB上のユニーク制約に一致しないため、バリデーションの結果が100%信用できない
  • DB上のINDEXが効率良く使えないため、DBの負荷が大きくなる

といった問題が発生しなくなります。

たとえば、DBに"jnchito"がすでに保存されている場合、Rails 6.1ではおそらく次のような振る舞いになるはずです。

# jnchitoはすでに登録済みなのでNG(大文字小文字を区別しない)
user.name = 'jnchito'
user.valid? #=> false

# JNCHITOはすでに登録済みなのでNG(大文字小文字を区別しない)
user.name = 'JNCHITO'
user.valid? #=> false

# 背後では以下のようなSQLが発行されるはず(BINARYが付かない)
# SELECT 1 AS one FROM `users` WHERE `users`.`name` = 'JNCHITO' LIMIT 1

Rails 6.0は6.1の仕様変更に向けて、開発者にコードやDB設定の見直しを促す

しかし、Rails 6.1の仕様変更は「思わぬデメリット」を避けられるのと引き換えに、「大文字小文字の区別をしなくなる」という振る舞いの変化を招いてしまいます。

そこで、Rails 6.0ではRails 5.2以前の振る舞いを保ちつつ、「Rails 6.1は振る舞いが変わるよ!今のうちにどうしたいか決めて!」と、開発者に変更を促します。それが冒頭に紹介した警告です。

大文字小文字を区別する場合

Rails 5.2時代と同様に大文字小文字を区別したい場合は、明示的にcase_sensitive: trueのオプションを付ければ警告は消えます。

ただし、DB側のcollationに変更がなければ、

  • DB上のユニーク制約に一致しないため、バリデーションの結果が100%信用できない
  • DB上のINDEXが効率良く使えないため、DBの負荷が大きくなる

という問題を抱えたままになってしまいます。

class User < ApplicationRecord
  # 警告は出なくなるが、DB側のcollationを変えなければ「思わぬデメリット」は残ったまま
  validates :name, uniqueness: { case_sensitive: true }
end

こうした問題を解消したい場合は、Rails側のコードを修正するのではなく、DB側のcollationをutf8mb4_binのような「大文字小文字を区別するcollation」に変更する必要があります。(collationを変更する手順はここでは割愛します)

DB側のcollationが大文字小文字を区別するようになっていれば、Railsのuniquenessバリデータの振る舞いとミスマッチがなくなるので警告は出なくなります。(case_sensitiveオプションを指定する必要はありません)

class User < ApplicationRecord
  # DB側のcollationを変えればcase_sensitiveオプションは不要。警告も「思わぬデメリット」も発生しない
  validates :name, uniqueness: true
end

大文字小文字を区別しない場合

大文字小文字を区別しなくていい場合は明示的にcase_sensitive: falseを指定します。
こうすればDB側のcollationもRailsのuniquenessバリデータも大文字小文字を区別しなくなるので、ミスマッチが解消され、警告も表示されなくなります。

ただし、この場合はアプリケーションの挙動が変わってしまうので、ユーザーに混乱を招いたりしないか、よく検討する必要があります。

class User < ApplicationRecord
  # 警告は出なくなる。「思わぬデメリット」もなくなる。が、Rails 5.2と挙動が変わる
  validates :name, uniqueness: { case_sensitive: false }
end

また、Rails 6.1にアプリケーションをアップグレードしたあとはcase_sensitive: falseのオプションを外しても問題ありません。(デフォルトで大文字小文字を区別しなくなるため)

class User < ApplicationRecord
  # Rails 6.1ではcase_sensitiveをなくしてしまってもOK
  validates :name, uniqueness: true
end

参考:Rails 5.2〜6.1の振る舞いまとめ

この話は「MySQL側のcollation」と「uniquenessバリデータのcase_sensitiveオプション」と「Railsのバージョン」の組み合わせによって話がいろいろと変わってきます。

それぞれの組み合わせで何が起きるか、以下の表にまとめておきます。

Screen Shot 2020-05-28 at 11.14.41.png

最終的には上の表の「DB側とRailsのミスマッチ?」欄が"NO"になる組み合わせが実現できれば理想的な状態、となります。

参考文献

謝辞

この件についてはRailsコミッタのkamipoさんにTwitter上で質問して丁寧に回答していただきました(参考)。kamipoさん、どうもありがとうございました!

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

Rails テーブルにcommentを追加するマイグレーションファイル

マイグレーションファイル

class AddCommmentsToStaffEmail < ActiveRecord::Migration[6.0]
  def change
    change_column_comment(:staff_emails, :employee_id, '業者ID') 
  end
end

change_column_comment

change_column_commentにテーブル名、カラム名、コメント内容 を記載する

change_column_comment(:table_name, :column_name, 'contents') 

環境

  • activerecord (6.0)

実行手順

マイグレーションファイル作成

コマンド

cmd.
$bundel exec rails g migration AddCommentsToTable

マイグレーションの実行

コマンド

cmd.
$bundel exec rake db:migrate

備考

  • 参考リンク

http://hotoolong.hatenablog.com/entry/2018/06/20/083106

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

Ruby カラオケマシン問題 解いてみた(解答例あり)

はじめに

『プロを目指す人のためのRuby入門』通称チェリー本を学習後のプログラミング初心者です。
インプットしたものを手を動かして実践してみたいなと思ったら、作者の記事を見つけました。
「アウトプットのネタに困ったらこれ!?Ruby初心者向けのプログラミング問題を集めてみた(全10問)」

この二つ目の問題を解いてみました。

一問目:カレンダー作成問題(たのしいRuby 練習問題)

問題

詳しくは実際の問題文をみてください。
問題知ってる人は、解答例へジャンプ

<前提>

カラオケにはキーを変える機能があります。
+1するとキーが1つ上がります。
-1するとキーが1つ下がります。
たとえば「ドレミファソ」というメロディのキーを2つ上げると「レミファ#ソラ」になります。
image.png
「ドレミファソ」のようにカタカナだとプログラムで扱いづらいので、英語の読み方、つまりアルファベットに置き換えましょう。
ド レ ミ ファ ソ → C D E F G
レ ミ ファ# ソ ラ → D E F# G A

<本題>
かえるのうたのメロディは以下のような文字列で表現されます。
これを(+6)や(-11)など指定大きさ分キーを変えます。

"C D E F |E D C   |E F G A |G F E   |C   C   |C   C   |CCDDEEFF|E D C   "

実行例はこんな感じになります。

melody = "C D E F |E D C   |E F G A |G F E   |C   C   |C   C   |CCDDEEFF|E D C   "
karaoke = KaraokeMachine.new(melody)
karaoke.transpose(2)
# => "D E F# G |F# E D   |F# G A B |A G F#   |D   D   |D   D   |DDEEF#F#GG|F# E D   "
karaoke.transpose(-1)
# => "B C# D# E |D# C# B   |D# E F# G# |F# E D#   |B   B   |B   B   |BBC#C#D#D#EE|D# C# B   "
# 1オクターブ(12音)以上変えることもできる
karaoke.transpose(14)
# => "D E F# G |F# E D   |F# G A B |A G F#   |D   D   |D   D   |DDEEF#F#GG|F# E D   "

解答例

こんな感じになりました。

class KaraokeMachine

  def initialize(melody)
    @melody = melody
  end

  def transpose(amount)
    scales = ["C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"]
    @melody.split(/(\w#?)/).map{
            |m| /(\w#?)/.match?(m) ?
            scales[(scales.find_index(m) + amount) % scales.length] : m 
           }.join
  end
end

当初下記のコードで完成したと思ってたのですが、

class KaraokeMachine

  def initialize(melody)
    @melody = melody
  end

  def transpose(amount)
    scales = ["C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"]
    @melody.split("").map{
                |m| m == " " || m == "|" ?
                 m : scales[(scales.find_index(m) + amount) % scales.length]
               }.join
  end
end

これだと下記のようなメロディが来たときにアルファベット#が分離してしまうことに気付き正規表現を用いて修正しました。

melody = "F# G# A# B |A# G# F#   |A# B C# D# |C# B A#   |F#   F#   |F#   F#   |F#F#G#G#A#A#BB|A# G# F#   "

解説

自分の備忘録としても書いておきます。

この問題での音階はシャープも入れると「ドド#レレ#ミファファ#ソソ#ララ#シ」の繰り返しなので,

  scales = ["C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"]`

このように一つの配列にしました。

melody =で受け取った文字列を、音階それ以外に区切り、配列にします。

melody = "C D E F |E D C   |E F G A |G F E   |C   C   |C   C   |CCDDEEFF|E D C   "
melody.split(/(\w#?)/)
=> ["", "C", " ", "D", " ", "E", " ", "F", " |", "E", " ", "D", " ", "C", "   |", "E", " ", "F", " ", "G", " ", "A", " |", "G", " ", "F", " ", "E", "   |", "C", "   ", "C", "   |", "C", "   ", "C", "   |", "C", "", "C", "", "D", "", "D", "", "E", "", "E", "", "F", "", "F", "|", "E", " ", "D", " ", "C", "   "]

配列の各要素をmapメソッドのブロック内で条件の真偽値を求めぞれぞれの値を、新しい配列に追加していきます。
1. 音階なら指定された分キーを変更する。
2. それ以外ならそのままの要素を追加。
を次のコードで実行していきます。

@melody.split(/(\w#?)/).map{
          |m|  /(\w#?)/.match?(m) ?
          scales[(scales.find_index(m) + amount) % scales.length] : m 
         }.join

音階indexと指定されたキーの増減数を足した数をscaleslengthで割ると、キーを変更した先の対応する音階indexが求められます。
ですので、scales[ ]の値に代入しindexを指定すればキー変更をした音階がわかります。

最後にこの音階それ以外をもう一度joinして文字列に直せば完成です。

さいごに

若干強引気味になってしまったところもあるかなと思います。
何か間違いやご意見あれば教えてください。

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