ポケモンのデータをDBに突っ込んでみた

こんにちは!kossyです!



さて、今回はRailsとAngularの練習用に作ろうと模索しているポケモン図鑑APIの基になる、
ポケモンのマスタデータをDBにimportした時の備忘録をブログに残してみたいと思います。




環境
Rails 5.2.3
Ruby 2.5.1
MacOS Mojave





マスタデータ用意

今回使用したマスタデータはこちらのサイトから拝借しました。
ポケモンの種族値データシートの配布【第7世代】 - 理科系のトレーナー


私はメガシンカなんぞよく知らない世代なので、
メガシンカを図鑑から抜いたCSVデータを作成しました。
それがこちらです。
https://docs.google.com/spreadsheets/d/1Wc-bw_wq_t2IqfzoTfHE3_2cGFJtd3On5Y7IAh6EujY/edit#gid=1634117797

これをRubyの標準ライブラリであるcsvを用いて、DBにimportします。


実装

dbディレクトリ下にmaster_filesというディレクトリを用意し、先ほど用意したcsvファイルを置いておきます。


次に、db/seeds.rbに以下のコードを書きます。

require 'csv'

CSV.foreach("db/master_files/pokemon.csv", headers: true) do |csv|
  Pokemon.find_or_create_by! number: csv[0].to_i, name: csv[1], type_1: csv[2], type_2: csv[3], hp: csv[4].to_i, attack: csv[5].to_i, defense: csv[6].to_i, sp_attack: csv[7].to_i, sp_defense: csv[8].to_i, speed: csv[9].to_i, total: csv[10].to_i
end

csvライブラリは明示的にrequireしないと使用できないので、requireしています。

CSV.foreachで第一引数に指定したcsvファイルを一行ずつ読み込みます。headers: trueを指定すると、最初に読みこむ行をCSVのヘッダーと見なすようになります。
find_or_create_by! メソッドで、DBを検索して同一のレコードが無ければ保存、という処理をしています。
あとは値を一つずつそれぞれのカラムに保存するだけです。


もっと綺麗に書ける気はするが、、、

RubyのRangeオブジェクトを使って、数値が指定した範囲内にあるかを調べる

こんにちは!kossyです!




さて、今回はRubyでRangeオブジェクトを使って、ある数値がRangeオブジェクトの範囲内に含まれているかどうかを
チェックするやり方をブログに残してみたいと思います。






環境
Ruby 2.5.1






実装

先に実装を晒します。

# number は Pokemonクラスのカラム名です。

  def pokedex
    if (1..9) === number
      "00#{number}"
    elsif (11..99) === number
      "0#{number}"
    else
      number
    end
  end

irbで(1..9)の正体を調べます。

irb(main):001:0> (1..9)
=> 1..9
irb(main):002:0> (1..9).class
=> Range

どうやらRangeクラスのオブジェクトらしいです。
class Range (Ruby 2.6.0)

(0..9)でRangeクラスのオブジェクトを生成し、 === 演算子で numberが(0..9)の間に含まれているかをチェックしています。
チェックしたのち、numberの値が範囲(1..9)内であれば、numberの前に00を付与した文字列を返します。
numberの値が範囲(11..99)内であれば、numberの前に0を付与した文字列を返します。


ではコンソールで実行してみましょう。

[1] pry(main)> @pokemon = Pokemon.find 1
  Pokemon Load (0.7ms)  SELECT  `pokemons`.* FROM `pokemons` WHERE `pokemons`.`id` = 1 LIMIT 1
=> #<Pokemon:0x00007fae29fcc568
 id: 1,
 number: 1,
 name: "フシギダネ",
 type_1: "くさ",
 type_2: "どく",
 hp: 45,
 attack: 49,
 defense: 49,
 sp_attack: 65,
 sp_defense: 65,
 speed: 45,
 total: 318,
 created_at: Tue, 13 Aug 2019 13:02:29 UTC +00:00,
 updated_at: Tue, 13 Aug 2019 13:02:29 UTC +00:00>
[2] pry(main)> @pokemon.pokedex
=> "001"


期待通りの動作をしています。
ある数値が指定した範囲内にあるかを調べるにはRangeオブジェクトを用いれば良さそうです。

Vue.js 用語まとめ1

データ(UIの状態)
イベントリスナー
テンプレート記法(状態とDOMのマッピングの定義)
フィルタ
算出プロパティ
ディレクティブ
メソッド
ライフサイクルハック
イベントハンドリング

jQueryでのUI実装はイベントや要素が増えると複雑になりがち

Vue.jsはイベントと要素の間にUIの状態(state)が挟まる

jQueryのコーディングスタイル
・DOMツリー中心
・イベントによってDOMツリーをどのように変更するかを考える

Vue.jsのコーディングスタイル
・UIの状態を担うJavaScriptのオブジェクトを中心に捉える
・「そのUIの持つ状態は何か、JavaScriptのオブジェクトとしてどう表現できるか」
・「データバインディングによってUIの状態とDOMツリーをどうマッピングするか」
・「イベントによってどの状態に変更するか」
という3つの視点を切り替えながら、UIの構築を進めていくのがVue.jsのコーディングスタイル

グローバル変数Vue
コンストラク

マウント
既存のDOM要素をVue.jshが生成するDOM要素を置き換えること

rubyのextendについて

こんにちは!kossyです!


昨日は終日外出していたため、連続更新記録が途絶えてしまいました、、、
残念ですが、気持ちを切り替えて粛々と更新を続けます。



さて、今回はRubyのextendについてブログに残したいと思います。









モジュールとは


そもそも、モジュールとはなんでしょうか。
Rubyにおけるモジュールとは、


・継承を使わずにクラスにインスタンスメソッドを追加、上書きする
・複数のクラスに対して共通の特異メソッド(クラスメソッド)を追加する
・クラス名や定数名の衝突を防ぐために名前空間を作る
・関数的メソッドを定義する
・シングルトンオブジェクトのように扱って設定値などを保持する
出典: プロを目指す人のためのRuby入門 p.283 著者:伊藤淳一

以上のような用途で使用するものですが、ざっくりと理解するならば、
「メソッドをひとまとめにしておくもの」と言えばいいでしょうか。


定義の仕方は、

module モジュール名(先頭大文字)
  処理
end

これで定義できます。

モジュールにはいくつか特徴があり、
・モジュールからインスタンスの作成不可
・他のモジュール・クラスの継承不可
という特徴があります。


どこで役に立つのか

Rubyでは単一継承を採用しており、1つのクラスは1つのスーパークラスしか持てません。
しかし、複数のクラスにまたがって同じような機能が必要になることはあります。
この重複した機能をモジュールとしてまとめておくことで、擬似的な多重継承を実現できます。

map(&:id)とpluck(:id)はどちらが速いのか

こんにちは!kossyです!



さて今回は、rubyのメソッドであるmapと、Railsのメソッドであるpluckメソッドの速さの違いを
ブログに残してみたいと思います。




実行結果



結論はpluckの方が速いです。

```

[12] pry(main)> Member.all.map(&:id)
Member Load (1.4ms) SELECT `members`.* FROM `members`
=> [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40]
[13] pry(main)> Member.all.pluck(:id)
(0.5ms) SELECT `members`.`id` FROM `members`
=> [8, 7, 3, 2, 4, 11, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 12, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 13, 40, 14, 15, 16, 17, 18, 19, 9, 5, 6, 1, 10]

```

pluckで取得した値はソートされていませんが、sortメソッドを使用しても、pluckの方が速いです。


なぜか。


これは、mapの方はレシーバの数だけブロック内の処理を実行しているからだと思います。
つまり、pluckよりも計算する回数が多い。
対してpluckは、rubyのメソッドではなくActiveRecordのメソッドで、引数に指定したカラムのみのSELECT文に絞られているため処理が速いです。

ただ、場合によっては(pluckで取得する数が膨大な場合等)mapの方が処理速度が速いこともあるそうです。




参考
pluckよりもmapのほうが高速なケース - Qiita
mapとpluck | Railsの小技 | DoRuby
pluckとmap の違いを調査する - Qiita

Railsのアソシエーションに設定できるオプション「dependent: :nullify」

こんにちは!kossyです!



さて、今回は、Railsでアソシエーションを組むときに、オプションとして設定できる
「dependent: :nullify」の使い方をブログに残してみたいと思います。





定義は簡単

User has_many Posts

という関連があるとします。

その時のアソシエーションの定義としては、

has_many :posts

と書くと思います。

ここで、「Userのデータを削除してもUserが持っていたPostsの情報は残したい」

というときに使うのが、「dependent: :nullify」です。

has_many :posts, dependent: :nullify

こうすることによって、Userの削除時に、Userが持っていたPostsのuser_idがnullの状態でDBに残ります。



関連先のデータを残したいシチュエーションがあれば使えそうです。

Railsでビューからモデルにロジックを移す

こんにちは!kossyです!




さて、今回は、Railsでビューやコントローラに書きがちなロジックをモデルに移す方法について、
ブログに残してみたいと思います。





環境
Rails 5.2.3
Ruby 2.5.1
MacOS Mojave




値があったら表示するというロジックをモデルに移す

例えば、Userモデルがnameという属性を持っているとします。
で、view側でnameを表示する際に、値があれば表示するというコード

 - if user.name.present?
  = user.name

というコードが書いてあるとします。
これでもいい気がしますが、他の箇所でもnameを表示するように改修する際に、何度も同じロジックを書く羽目になります。

なので、モデルに移してみましょう。

app/models/user.rb

class User < ApplicationRecord

  def has_name?
     name.present?
  end

end

has_nameというインスタンスメソッドを定義しました。
「name」はUserモデルの属性で、メソッド内ではnameとするだけで参照できます。
present?メソッドでnameを持っているかどうか(bool値)を返しています。

これをビューで使いましょう。

- if user.has_name?
  = user.name

あまり変わってないような気もしますが、名前の有無で処理を分岐させることがある場合、このメソッドが使いまわせます。



今回は簡単な例でしたが、ビューやコントローラをダイエットさせるためにも、
なるべくモデルにロジックを集めるようにしましょう。




そうするとすぐモデルがデブってくるんですけどね、、、笑

月単位での処理に使えるgem 「month」

こんにちは!kossyです!




さて、今回は、年月を扱うクラスを定義してくれるgem「month」の使い方を
ブログに残してみたいと思います。




環境
Rails 5.2.3
Ruby 2.5.1
MacOS Mojave




導入

例によってgemをインストールするだけです。

./Gemfile

gem 'month'
$ bundle install

使い方

Monthクラスのインスタンスを生成してmethodsメソッドを実行してみました。

$ month = Month.new(2019, 8)
=> #<Month:0x00007ff1e3813cb0 @number=8, @year=2019>

$ month.methods

=>
:number,
:start_date,
:end_date,
:include?,
:name,
:year,
:february?,
:april?,
:march?,
:january?,
:may?,
:september?,
:july?,
:november?,
:december?,
:october?,
:august?,
:june?,
:dates,
:next_month,
:prev_month,
:length,
:next,
:between?,
:present?,
:presence,
:blank?,

よく使いそうなところだけ抜き出しました。
細かい挙動の説明は以下に譲ります。
qiita.com
github.com


1月かどうか?2月かどうか?等を返すのは面白いですね。
便利に使えそうです。

RspecでModelに定義したScopeのテストを書いてみる

こんにちは!kossyです!



さて、今回は、冗長なDB検索ロジックに名前をつけてひとまとめにできる機能であるscopeのテストを
Rspecで書いてみたいと思います。




scopeの定義

class Article < ApplicationRecord

  scope :open_to_the_public, -> { where(member_only: false) }

  scope :visible, -> do
    now = Time.now
    where('released_at <= ?', now).where('expired_at > ? OR expired_at IS NULL' , now)
  end

end

Articleというモデルに2つscopeを定義しています。



ModelsSpec

テストケースはこんな感じです。

require 'rails_helper'

RSpec.describe 'ArticleScope', type: :model do
  let!(:article_1) { create :article, member_only: true }
  let!(:article_2) { create :article, member_only: true }
  let!(:article_3) { create :article, expired_at: Date.today, member_only: false }

  describe ':open_to_the_public' do
    it 'member_onlyがfalseの記事が検索される' do
      expect(Article.open_to_the_public).to include(article_3)
    end

    it 'member_onlyがtrueの記事は検索されない' do
      expect(Article.open_to_the_public).to_not include(article_1, article_2)
    end
  end

  describe ':visible' do
    it '(released_at <= ?, now)かつ(expired_at > ? OR expired_at IS NULL , now)の記事が検索される' do
      expect(Article.visible).to include(article_1, article_2)
    end

    it '(released_at <= ?, now)かつ(expired_at > ? OR expired_at IS NULL , now)でない記事は検索されない' do
      expect(Article.visible).to_not include(article_3)
    end
  end
end

あらかじめ条件に合致するものとしないものをモックにしておき、
scopeを実行して想定通りのレコードを抽出してくるかをチェックしています。


こんなんでいいんじゃないでしょうか。(適当)

Railsのenumで定義したシンボルは真偽値を返したり破壊的変更ができる

こんにちは!kossyです!




かなり久々のブログの執筆です、、、
流石にアウトプットしなさすぎてまずいので、簡単なことでいいのでアウトプットをしてみます。




さて、今回は、Rails4系から使えるようになったenumのTips的なことを
ブログに残してみたいと思います。




環境
Rails 5.2.3
Ruby 2.6.3





真偽値を返す使い方

例えば、下記のようにモデルに定義したとします。

app/models/user.rb

enum role: [:admin, :normal]


このように定義すると、

@user = User.create role: :admin

@user.admin?
=> true

のように、シンボルで定義したものに?をつけると、roleの値がそのシンボルかどうかの真偽値を返すことができます。

モデル内のメソッドで使いたいときは、

def permit_admin
  if admin?
     処理
  end
end

みたいに使えます。



破壊的変更もできちゃう

次は破壊的変更です。

@user  = User.first

@user.role
=> "normal"

@user.admin!
=> true

@user.role
=> "admin"

ってな感じで使えます。




enumは配列型で定義するやり方だけでなく、ハッシュ型で定義するやり方もあるので、
コード規約に従って記述しましょう。。。