20200522のRubyに関する記事は14件です。

Kinx ライブラリ - CSV Parser

CSV

はじめに

「見た目は JavaScript、頭脳(中身)は Ruby、(安定感は AC/DC)」 でお届けしているスクリプト言語 Kinx。言語はライブラリが命。ということでライブラリの使い方編。

今回は CSV パーサーです。TSV もあります。

使い方

using CSV

CSV ライブラリは標準組み込みではないため、using ディレクティブを使用して明示的に読み込む。

using CSV;

全てを一度にパース

ファイルを読み込んで全体をパースするには、CSV.parse() を使う。

using CSV;
var r = CSV.parse("filename.csv");

データの形式は以下の通り。全ての行オブジェクトの配列として返す。行オブジェクトには data フィールドがあり、そこに配列として各要素の文字列が設定される。ただし、コメント行(先頭が #)の場合、data フィールドは空となり comment フィールドにコメント文字列が入る(# も含む)。

[{
    "data": ["aaa", "bbb", "ccc"]
}, ..., {
    "data": {},
    "comment": "# comment"
}]

1行ずつコールバック

行数が多い場合、全体を一気にパースすると大量にメモリを消費する可能性がある。そこで、以下のように 1 行ずつコールバックさせることもできる。コールバックの引数は 1 行分の行オブジェクトとなる。

using CSV;
CSV.parse("filename.csv", &(row) => {
    System.println(row);  // like `{ data: ["aaa", "bbb", "ccc"] }`
});

尚、この場合 CSV.parse() は値を返さない(null を返す)。

文字列をパース

文字列をパースする場合、CSV.parseString() を使う。コールバック版も同様。

using CSV;
var r = CSV.parseString(csvString);
CSV.parseString(csvString, &(row) => {
    ...
});

CSV 仕様

解釈する CSV の仕様は以下の通り。

  • 先頭文字が # の場合、コメント行と認識。
  • ダブルクォートは無くても良い。
  • ,"、改行を含めたい場合はダブルクォートが必要。
  • ダブルクォート内では以下の仕様。
    • , は直接記載可能。
    • """ と重ねて記載。
    • 改行はそのまま改行することで表現する。
  • 最終行の末尾に改行コードは無くても良い。

サンプル

サンプルとして以下をパース。

# comment in CSV.
abcde,efgh,non-quoted string
"abcde","efgh","quoted string"
"abcde",efgh,"quoted & non-quoted string in the same row"
"abcde",efgh,"can use , or "" in csv"
"abcde",efgh,"can use newlines

in the row"
# another comment in CSV.
abcde,efgh,not necessary \n the end of csv

結果はこうなる。

[{
    "data": {},
    "comment": "# comment in CSV."
}, {
    "data": ["abcde", "efgh", "non-quoted string"]
}, {
    "data": ["abcde", "efgh", "quoted string"]
}, {
    "data": ["abcde", "efgh", "quoted & non-quoted string in the same row"]
}, {
    "data": ["abcde", "efgh", "can use , or \" in csv"]
}, {
    "data": ["abcde", "efgh", "can use newlines\n\nin the row"]
}, {
    "data": {},
    "comment": "# another comment in CSV."
}, {
    "data": ["abcde", "efgh", "not necessary \\n the end of csv"]
}]

TSV

TSV とは CSV のカンマ区切りがタブ区切りになったもの。

using CSV は一緒。使うときに CSVTSV にするだけで使える。

using CSV;
var r = TSV.parse(tsvFilename);
TSV.parseString(tsvString, &(row) => {
    ...
});

パーサー

パーサー自体を Kinx で書いています。

パーサーの形態は1文字先読みの再帰下降パーサーですが、そもそも CSV の仕様に再帰的な要素がないので再帰しません。言うなればただの下降パーサーです。

コードは GitHub にあります。全体で100行ちょっとしかないのですぐ理解できるかと思います。

内部ライブラリ用の特殊キーワードについて

ちなみに、_class_function というキーワードが使われていますが、一般では使いません。意味は classfunction と同様です。

何が違うかというと、_ 付きのほうは例外発生時にスタックトレースに情報を残さないといった動作をします(また、-d オプションで VM コードを表示しません)。これは内部ライブラリの場合、スタックトレースに情報を残すと混乱する可能性があるためです。例えば、例外の起点が内部ライブラリの行番号を示していると、そこに問題があると勘違いする可能性があるためで、例外の起点はあくまでユーザー・コードを示すようにしています。VM コード出力でもユーザー・コードの邪魔になると考えられるので表示しないといった配慮がなされる形です。

このストラテジーに従うと逆にライブラリ内部にバグがあった場合は調査困難になりますが、通常ケースでパラメーター異常による例外、といったケースのほうが一般的と考えられるので、この仕組みを入れてあります。尚、内部ライブラリ自体のデバッグをしたい場合は _classclass に、_functionfunction に変更すればできます。

おわりに

CSV パーサーは何か既存のライブラリを使おうかと思って探したんですが、イマイチ統合するのが手間取りそうだったのと、実は簡単なんじゃないかと思ってスクラッチで書いてみました。何か問題があればお知らせください。

ではまた、次回。

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

シーザー暗号を噛み砕く

近況報告

 最近は就活(企業にひたすらESを送るだけの作業)ばっかで空いている時間にテックキャンプのドリルをしこしこ解いています。githubの草は生やし続ける。今日受けた企業さん,無事通っているといいな。

本題 シーザー暗号

 辞書順に3文字分シフトして暗号文を作る暗号
  ex. abc ⇨ cde

 シーザーというのは高校世界史を履修していたら知らない人はいないあのシーザー,カエサルさんのことを指します。ガリア戦記とか,「ブルートゥスお前もか」とかかなりの人が認知しているのではないのでしょうか。

 昔はパソコンもケータイもないアナログの時代,かろうじてエジプト文明がパピルスとか発明していたので文字を記録する文明はあったでしょう。ただ,文書の輸送手段は人の足です。となれば機密文書が道中で奪われてそれが原因で亡国の危機に晒されることは度々あったでしょう。

 カエサルは重要な情報の漏洩を防ぐためにこのような暗号を用いました。ちなみに甥のアウグストゥスも用いていたとか。なお,現代におけるシーザー暗号はセキュリティの観点からは非常に脆弱であることは認識しておきましょう。

パソコン上でのおおまかな方法

1.文字列を文字コードに変える
2.文字コードに任意の数を足す(引く)
3.文字コードを文字列に変更する

サンプル シーザー暗号の生成解読メソッド

前後させる数の大きさは任意です。

def cipherAction
  result = [] ←配列の箱用意
  puts "暗号化したい文字は?"
  x = gets.to_s ←str型で定義
  x.chars.each do |i| ←文字列を1文字単位で分解
    code = (i.ord + 3) ←ordは文字コードに変換するメソッド
    result << code.chr ←継承
  end
    puts result.join("") ←1文字単位で格納されているのを文字列に整える
end

def deciperAction
  result = []
  puts "元に戻したい文字は?"
  y = gets.to_s
  y.chars.each do |j|
    code = (j.ord - 3)
    result << code.chr
  end
  puts result.join("")
end

puts "Select Action"
puts "1:暗号化"
puts "2:元に戻す"
input = gets.to_i
if input == 1
  cipherAction
elsif input == 2
  deciperAction
else
  puts "不正な入力です"
  exit
end

おわりに

安直ではありますが,暗号化された分は何文字前後しているか分からないので解読は手間(まあパソコン使えばサクッとできますが)ですので日常レベルではまあつかえますね。

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

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

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

早くもエンジニアとして働いて2日が過ぎました。

こちらは昨日の記事ですよかったらどうぞ
100日後に1人前になる新人エンジニア(1日目)

少しずつ業務に慣れてチームに貢献していきたい!!という気持ちと、
自分は何にもわかってないんだなという気持ちを持ちつつ
2日目の記事を書いていきます。

今日やったこと

・フォームの改善
・viewの改善 の2つでした

issueに対してコードを書いてGitHubにプルリクエストを送る
という流れでした。

今日の最も苦戦した部分はGit(もちろん他にも苦戦はしたけど)

そもそも1人で開発していた時はほとんどGitを使っていなかったので、
チームとして働き始めて覚えていかねばと思っています。

ちなみにgitを作った人はLinuxを作った人と一緒なんだね
天才かよ。

ということで今日はGitについてフォーカス

Gitあれこれ

gitはバージョンを管理するシステムだよ。
これによってバージョンを行き来したり修正したりできるよ。

基本的なGitコマンド

git init            #リポジトリを初期化
git status          #リポジトリの状態を確認 addされていないファイルを確認できるよ
git add (ファイル指定) #ステージ領域にファイルを追加
git commit          #リポジトリの歴史として記録
git log             #コミットログを確認。いつ誰がコミットやマージをしたのか
git log -p          #コミットで行われた差分を確認
git diff            #addされていないファイルとコミットされているものの差分を表示

とても簡単だけどこんな感じgit commitは -mのオプションで一行のコメント
オプションなしでは長いコメントが可能だよ。

Branch

枝分かれとか支店とかそうゆう意味がありますよね。
作業を分岐させることでそれぞれ独立した作業を行えるってことですね。

git branch            #ブランチの一覧と現在のブランチを表示
git checkout -b hoge  #hogeブランチを作成してhogeブランチに切り替える
git checkout master   #masterブランチに切り替える

ブランチに関してはまた今度しっかりまとめたいと思います。

GitHubプルリクエスト

実際に書いたコードを他人にレビューしてもらう機能
これはGitの機能ではなくGitHubの提供している機能

この機能ができたからこそSocial codingつまり閉ざされた世界ではなく
真の意味で開かれた状態でコードを共有することができるようになった!!
って本に書いてありました

gitコマンドを参考にして
git add → git commit → git push origin branch名
でプッシュをした後にGitHubでプルリクエストをかけばOK!
ここはコードとかではなくGitHubの使い方なので省略。

今日のさいごに

できないことしかないので
できるようになった喜びも多いはず。
楽しみながら成長を。
ちなみに土日もちゃんと更新していきます。

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

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

rspecでGraphQLのresolverをテストする

はじめに

graphqlのテストってみなさんどう書いてますか?
queryを定義してテストもできますが、少し面倒だど感じている今日この頃。
そこで、queryとかmutationに定義されているresolverをrspecでテストする方法について説明します。

mutationの例

例えば、以下のようなmutationを定義しているとしましょう。

mutation.rb
module Mutations
  class CreateUser < BaseMutation
    argument :id, ID, required: true
    field :user, ObjectTypes::UserType, null: false

    def resolve(id: nil)
      user = ::User.create!(id: id)
      {
        user: user
       }
    end
  end
end

単純ですが、idをもらってuserを作成するmutationです。
今回のテスト対象はこのresolveになります。

rspceを書く

ではさっそくrspecを書きたいと思います。

create_user_rspec.rb
require 'rails_helper'

RSpec.describe CreateUser, type: :request do
  describe 'resolver' do
    it 'userが作成されていること' do
      mutation = CreateUser.new(field: nil, object: nil, context:{})
      mutation.resolve(id: [作成するuser_id])
      expect(..).to eq ..
    end

まず、mutationのクラスであるCreateUserクラスのインスタンスを作成します。
引数のfieldとobjectは基本的にnullで良いと思います。
contextについては、場合によってはcurrent_userを入れることもできます。その場合は context:{current_user: User.first}となります。

contextを直接入れられるのが便利ですよね。
そして、作成したmutationのresolveメソッドを読んであげればテスト上で定義したresolve内の処理が実行されます。
これだと、graphqlのテストがかなり楽にかけます!

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

【Ruby on rails】heroku コマンド実行時にbash: heroku: command not foundと表示される。【rails tutorial】

エラー内容

herokuでのマイグレーションを実施するために以下コマンドを実行。

heroku run rails db:migrate

すると

bash: heroku: command not found

herokuコマンドが使えない・・・。

解決方法

調べたところ、herokuを再インストールすれば直るとのこと。

以下コマンドで再インストール。

source <(curl -sL https://cdn.learnenough.com/heroku_install)

herokuコマンドでバージョン確認をしてみる。

heroku -v
heroku/7.39.3 linux-x64 node-v12.16.2

コマンドが通りました!

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

iframeタグを用いてyoutube動画を埋め込む方法(haml)

はじめに

Railsアプリケーションを作成する際に、youtubeの埋め込み方法を調べたのでまとめます。
今回はhamlを用いたマークアップで、youtubeを表示させることができます。

前提環境

今回作成したアプリケーションの環境は下記の通りです。

  • ruby 2.5.1
  • Rails 5.2.4.3

完成見本

スクリーンショット 2020-05-21 0.41.16.png

無事埋め込む事ができるとこのような画面になります。
それでは早速実装していきましょう!

hamlを導入する

念のため、hamlの導入方法から説明していきます!
gemを導入するだけなので簡単です!

Gemfileの1番下に下記のように記述します。
※ Gemfileに記述したらbundle installを忘れないように!!

Gemfile
gem 'haml-rails'
ターミナル
%  bundle install

これでhamlを導入することができました!

しかし、既存のファイルには haml が適用されていないので、下記のように記述して haml に変更します。

ターミナル
%  rails haml:erb2haml

実行して、下のような記述が出てきたときは、" y " と入力してEnterを押してください。

ターミナル
Would you like to delete the original .erb files? (This is not recommended unless you are under version control.) (y/n)

これで既存のファイルにもhamlを適用させることができました!!

Youtube動画のURLをコピーする

それではいよいよyoutubeの埋め込みの方に入っていきます。
今回は、こちらのyoutubeを例にして実装していきます。

まず、表示させたいyoutube動画の、HTML埋め込みタグをコピーしてください。

コピーは、下記の手順でできます。

スクリーンショット 2020-05-20 21.29.27.png

スクリーンショット 2020-05-20 21.38.46.png

スクリーンショット 2020-05-20 21.39.26.png

ビューファイルに動画を埋め込む

では、コピーができたら、ビューファイルの方をいじっていきます。
今回使用するのはiframeタグと呼ばれるものです。

iframeタグをhamlで記述すると下のような書き方になります。

%iframe{ この中にyoutubeの情報を記述していく }

まずyoutube動画を表示させたいビューファイルを開き、先ほどコピーしたものを1度そのままペーストします。

〇〇〇.html.haml
<iframe width="560" height="315" src="https://www.youtube.com/embed/_ZRp7KYXM1A" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>

ペーストしたら、実際にhamlの書き方に直していきます。

タグの中の width 〜 allowfullscreen の部分を、%iframe {}{} の中にコピペしてください。

〇〇〇.html.haml
<iframe width="560" height="315" src="https://www.youtube.com/embed/_ZRp7KYXM1A" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
⬇️ ⬇️ ⬇️
%iframe{ width="560" height="315" src="https://www.youtube.com/embed/_ZRp7KYXM1A" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen }

このままだとエラーが出てしまうので、さらに修正を加えていきます。
修正をする箇所はいくつかあります。細かい変更なので例を見てもらった方がわかりやすいかと思います。

〇〇〇.html.haml
%iframe{ width="560" height="315" src="https://www.youtube.com/embed/_ZRp7KYXM1A" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen }
⬇️ ⬇️ ⬇️
%iframe{ width: "560", height: "315", src: "https://www.youtube.com/embed/_ZRp7KYXM1A", frameborder: "0", allow: "accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture", allow: "fullscreen" }

一応、修正箇所を書き出すと、

  1. = から : に変更
  2. 属性の後に , を追加
  3. allowfullscreenallow: "fullscreen" に変更

このような変更を行っています。

最初のHTMLタグの記述と、修正後の記述を比べてみるとこのような違いになっています。

元の記述(htmlバージョン)
<iframe width="560" height="315" src="https://www.youtube.com/embed/_ZRp7KYXM1A" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
直した記述(hamlバージョン)
%iframe{width: "560", height: "315", src: "https://www.youtube.com/embed/_ZRp7KYXM1A", frameborder: "0", allow: "accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture", allow: "fullscreen" }

以上の操作で、無事youtubeの動画を表示させることができました!

ちなみに、パラメータ(widthheightなど)の値を変えることで表示される大きさを変更したりすることも可能です!

パラメータは、元から記述されているもの以外にも、

  • 動画の開始時間や終了時間を決めるパラメータ
  • 動画終了後の関連動画を非表示にするパラメータ

などいくつか存在しています。

パラメータについては、今後時間がある時に追記して行けたらと思います!!

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

[Rails] RSpecでテストをしてみる

はじめに

今まで難しそうと思って避けてきたテスト。
そろそろサービスを本格的に動かそうと思ったためしっかりとテストというものを理解するための第一歩として学んだことをこちらの記事にかきます。
いわゆる備忘録ですね。
自分自身が初心者なので初心者の視点で書いていきます。
もっとこういう書き方や手法があるなどあればコメントいただけてら嬉しいです!

RSpecの導入

おきまりの導入をしていきます。
まあ、ここは特に何も考えずに

Gemfile
group :development, :test do
  ...省略...
  gem 'rspec-rails'
end

group :development do
  gem 'web-console' #もともと記載されている可能性があります。
end


Gemfileを編集したら、おきまりのbundle installします。

RSpecの基本設定

まずはRSpec用の設定ファイルを作成する必要があります。
ターミナルで

$ rails g rspec:install

すると以下のファイルが生成されます。

      create  .rspec
      create  spec
      create  spec/spec_helper.rb
      create  spec/rails_helper.rb

生成されたファイルのうち.rspecファイルに下記を追記します。

.rspec
--format documentation

上記を追記するとテスト結果を見やすく出力してくれます。
後ほど出てくるcontextとitを段階的に書いてくれるものだと考えればいいと思います。
まあ、なくても問題ないものだと思います。
ちなみに上記以外のオプションを追加するとテキスト出力や、並行表示とかもできるので持っときあめたい方は調べてみてください。

とりあえずここまでで導入は完了です。
ここからテストコードを書いていく作業です。

テストを実行してみる

RSpecはターミナルでコマンドを打つことにより実行が可能です。
まだ、テストコードは何も書いていませんが、実際に動くかどうか確かめるため下記コードをターミナルに打ち込みます。

ターミナル
$ bundle exec rspec

上記がテストを実行する際のコマンドです。
結果下記のような表示になればオッケーです。

ターミナル
No examples found.


Finished in 0.00031 seconds (files took 0.19956 seconds to load)
0 examples, 0 failures

ここからモデルやコントローラー、ルート、ビューなどのテスト用のファイルを作成し、テストコードを書いていきます。

単体テスト(モデル)

単体テストであるモデルのバリデーションに関するテストコードを書いて実行することを目標とします。

事前に知っておくべきこと

テストコードはspecファイルを作成し記述していきます。
一つのファイルを作って書いていくのではなく、わかりやすいようにモデルに関するテスト用ファイルであればspec/models/以下に、コントローラーに関するテスト用ファイルであればspec/controllers/以下にspecファイルを配置します。

specファイルの命名規則
specファイルは対応するクラス名spec.rbという名前になります。
今回はお問い合わせフォーム(contactモデル / contact.rb)のバリデーションに関してのテストを行うため、その場合の名前は「contact
spec.rb」になります。

テストコードの基本

テストコードを書くファイルに関してわかったところで、実際に書くまえに超超超基本なテストコードを書いておきます。
「1 + 1が2になることを確かめる」という簡単なテストコード

sample_spec.rb
describe "hogehoge" do
  it "1 + 1は2になること" do
    expect(1 + 1).to eq 2
  end
end

語彙力は無視して自分的解説をします。

sample_spec.rb
describe "hogehoge" do        #describe(説明するという意)でこのテストのグループを作ります。
  it "1 + 1は2になること" do     #it "~~~" do の ~~~部分にこのコードがなんのテス等をしているか書きます
    expect(1 + 1).to eq 2     #この部分が実際にテストが成功するかどうかチェックされる式となります。
  end
end

上記の expect(1 + 1).to eq 2 の部分の式のことをエクスペクテーションと言います。
● expect(X).to eq Y 
こんな感じでxの部分に入れた式の値がYの部分の値と等しければ、テストが成功します。
また、eqの部分を、マッチャと言います。

マッチャとは、エクスペクテーションの中で、テストが成功する条件を示します。
例えば上記のようにeqは「等しければ」という意味になります。
他にもinclude(含んでいれば)、valid(バリデーションされれば)など複数のマッチャが存在します。
たくさんあるので書きたいテストに合わせて利用してください。

実際にモデルのバリデーションのテストを行う。

今回使用するcontactモデル(お問い合わせ機能)は下記のようになっております。

contact.rb
class Contact < ApplicationRecord

#送信者の名前
  validates :name, presence: true 

#お問い合わせに対して返信先のメアド
  validates :email, presence: true, length: {maximum:255},
                                    format: {with: /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i}

#お問い合わせの件名
  validates :title, presence: true

#お問い合わせ内容
  validates :message, presence: true
end

上記のバリデーションに対してテストコードを書いていきます。

spec/models/contact_spec.rbファイルを作成する。

作成したcontact_spec.rbを下記のように編集します。

contact_spec.rb
require 'rails_helper'
  RSpec.describe Contact, type: :model do
    it "nameがない場合は登録できないこと" do
  end
end

1行目のrequire 'rails_helper'は、rails_helper.rb内の記述を読み込むことで共通の設定を有効にしています。
この1行目の記述は、全てのspecファイルに毎度書き込みます。

書いたところで一度、bundle exec rspecというコマンドをターミナルで実行します。
現在はまだテストとして評価される式を書いていないので、テストは無条件でパスします。
下記のようになればオッケーです。

ターミナル
```:ターミナル
Contact
nameがない場合は登録できないこと

Finished in 0.23784 seconds (files took 4.71 seconds to load)
1 example, 0 failures
```
 

nameが空の場合登録できないことを確かめるテストコードを書く

先ほど書いたテストコードに下記を追記します。

contact_spec.rb
RSpec.describe Contact, type: :model do
  it "nameがない場合は登録できないこと" do
    contact = Contact.new(name: "", email: "kkk@gmail.com", title: "返品について", message: "注文番号000000000の商品を返品したいです。")
    contact.valid?
    expect(contact.errors[:name]).to include("を入力してください")
  end
end

[3行目]でテストしたいプロパティを持ったcontactクラスのインスタンスを新規作成します。
「nameが空である場合登録できないこと」を確かめるテストコードを作成したいのでnameの値を空にし、それ以外は適当な値をセットした状態でcontactクラスのインスタンスを作成しています。

[6行目]で作成したインスタンスがバリデーションによって保存ができない状態かチェックします。

[7行目]でチェックした結果インスタンスが持つエラー文が期待したものであるか確かめます。
contact.errorsに対してハッシュのバリューの取り出し方でカラム名を指定すると、そのカラムが原因のエラー文が入った配列を取り出すことができます。こちらに対して、includeというマッチャを利用してエクスペクテーションを作っています。
今回はバリデーションのエラー文で"を入力してください"が出力されるため、その内容を記述しております。

上記の流れは下記のコンソールでの検証と同じ意味を持ちます。

ターミナル
#コンソールを立ち上げる
$ rails c
#nameの値が空であるcontactクラスのインスタンスを作成する
>contact = Contact.new(name: "", email: "kkk@gmail.com", title: "返品について", message: "注文番号000000000の商品を返品したいです。")
#valid?メソッドを利用する
>contact.valid?
#errorsメソッドを利用する
>contact.errors
=> #<ActiveModel::Errors:0x007ffa6ce07ef0
 @base=
  #<Contact:0x007ffa6d3430b8
#中略
 @messages={:name=>["を入力してください"]}>

ここでRSpecのコマンドを入力しテストの実行をします。

ターミナル
$ bundle exec rspec

下記のような結果になれば成功です。

ターミナル
Contact
  nameがない場合は登録できないこと

Finished in 0.07346 seconds (files took 2.31 seconds to load)
1 example, 0 failures

もしエラーが表示される場合はエラーメッセージがターミナルに表示されるのでよく読めば解決できることが多いです。
また、上記のようにコンソールで一度一連の流れとして確認してみると少し理解が深まる気がします。

[効率的にするため]factory_botの導入

先ほどはテストコードを一つ書きましたが、name以外のmailのテストを書く際は下記のようになります。

contact_spec.rb
RSpec.describe Contact, type: :model do
  it "nameがない場合は登録できないこと" do
    contact = Contact.new(name: "", email: "kkk@gmail.com", title: "返品について", message: "注文番号000000000の商品を返品したいです。")
    contact.valid?
    expect(contact.errors[:name]).to include("を入力してください")
  end

  it "emailがない場合は登録できないこと" do
    contact = Contact.new(name: "takashi", email: "", title: "返品について", message: "注文番号000000000の商品を返品したいです。")     
    contact.valid?
    expect(contact.errors[:email]).to include("を入力してください")
  end
end

単純にemailの部分をからにしているのですが、インスタンスを毎回作成するとコード量が増えてしまいます。
これを改善するのがfactory_botです。

factory_botとは

簡単にダミーのインスタンスを作成することができるGemです。
他のファイルで予め各クラスのインスタンスに定めるプロパティを設定しておき、specファイルからメソッドを利用してその通りのインスタンスを作成します。

factory_botを利用すると下記のような記述となります。

contact_spec.rb
RSpec.describe Contact, type: :model do
  it "nameがない場合は登録できないこと" do
    contact = build(:contact, name: "")
    contact.valid?
    expect(contact.errors[:name]).to include("を入力してください")
  end

  it "emailがない場合は登録できないこと" do
    contact = build(:contact, email: "")      
    contact.valid?
    expect(contact.errors[:email]).to include("を入力してください")
  end
end

あらかじめインスタンスを別ファイルで作成しそれを呼び出しています。

導入手順

gem 'factory_bot_rails'をrspec-railsと同じ環境に追加

Gemfile
group :development, :test do
  #省略
  gem 'rspec-rails'
  gem 'factory_bot_rails'
end

その後、bundle installし、
specディレクトリ直下に「factories」というディレクトリを作成します。
その中に、作成したインスタンスの複数形のファイル名でRubyのファイルを作成します。今回の場合は、contacts.rbです。

作成したcontacts.rbファイルを以下のように編集します。

contacts.rb
FactoryBot.define do
  factory :contact do
    name {"takashi"}
    email {"kkk@gmail.com"}
    title {"返品について"}
    message {"注文番号000000000の商品を返品したいです。"}
  end
end

するとインスタンス作成の際下記のような記述をすることで、設定したテンプレートの内容でインスタンスの作成ができます。

#factory_botを利用しない場合
contact = Contact.new(name: "", email: "kkk@gmail.com", title: "返品について", message: "注文番号000000000の商品を返品したいです。")
#factory_botを利用する場合
contact = FactoryBot.build(:contact)

だいぶスッキリました。
また、spec/rails_helper.rbを以下のように編集することでレシーバーであるクラスのFactoryBotという記述を省略することができます。

rails_helper.rb
RSpec.configure do |config|
  #下記の記述を追加
  config.include FactoryBot::Syntax::Methods

  #省略
end

上記踏まえて追加でテストコードを書いていきます。

contact_spec.rb
require 'rails_helper'

RSpec.describe Contact, type: :model do
  it "nameがない場合は登録できないこと" do
    contact = build(:contact, name: "")
    contact.valid?
    expect(contact.errors[:name]).to include("を入力してください")
  end

  it "emailがない場合は登録できないこと" do
    contact = build(:contact, email: "")      
    contact.valid?
    expect(contact.errors[:email]).to include("を入力してください")
  end

  it "@マークのあとのドメイン" do
    contact = build(:contact, email: "example@eee")
    contact.valid?
    expect(contact.errors[:email]).to include("は不正な値です")
  end

  it "titleがない場合は登録できないこと" do
    contact = build(:contact, title: "")      
    contact.valid?
    expect(contact.errors[:title]).to include("を入力してください")
  end

  it "messageがない場合は登録できないこと" do
    contact = build(:contact, message: "")      
    contact.valid?
    expect(contact.errors[:message]).to include("を入力してください")
  end
end


こちらでとりあえず完成形です。

終わりに

今回はcontactモデルのバリデーションに関してテストを実行しましたが、WEBアプリの仕様によって様々な書き方があります。
テストという世界を少しでも理解していただくことに役立てば幸いです。
コントローラーのテストの仕方はまた違ったやり方になるっぽいのでそのうちアップできればなと思います。

自分も少しテストを理解してきた中で、次のアプリケーションを開発するときはテストコードを書いてから実装してみたりしたいなと考えるようになりました。
記事の内容に関して修正や訂正点があればコメントいただけると幸いです。

長々とお読みいただきありがとうございました!

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

RSpecでActiveRecord::StatementInvalid: Could not find tableが出た

出たエラー

ActiveRecord::StatementInvalid: Could not find table

テストを実行したときにDBのテーブルを見つけられない

やってみたこと

rails db:migrate:reset RAILS_ENV=test

でテストDBをリセットして反映

rails dbconsole -e test

sqlite> .table

でテーブルができたか確認。作成された。

多くの人はここまでで解決と思うが、もう一度テストを走らせると、

ActiveRecord::StatementInvalid: Could not find table

同じエラー。

そして、またテーブルを確認してみると、テーブルが消えてた。

これを踏まえてやってみたこと

ActiveRecord::Migration.maintain_test_schema!を外す

rails_helper.rbで

rails_helper.rb
#コメントアウトするコード
ActiveRecord::Migration.maintain_test_schema!

これをコメントアウトした。

そしたら通った。が、まだテーブルは作られず、そのテーブルを使うテストは全て落ちた。

ActiveRecord::Migration.maintain_test_schema!とは

rspecを実行する前にschema.rbをみて、testのDBを自動でマイグレーションしてくれるためのコード。

これを外すことでテストが実行されるということは、schemaに問題ある可能性ありだと思ったので確認。

Could not dump table "テーブル名" because of following StandardError

schemaファイルにこのように書かれていた。

テーブルをschemaに反映できていない。

migrationファイルを確認

すると、

migration_name.rb
  def up
    change_column :comments, :discovery_id, :reference, null: true
  end

としていた箇所があった。

型にreferenceなんてないが、なぜかこうしてしまっていた。

:referenceを:integerに変えて、rails db:migrate:reset
を行うと、schemaに記述された。

再度走らせる

schemaにしっかり記述されてから、もう一度マイグレーションをリセット

rails db:migrate:reset RAILS_ENV=test

そして、ActiveRecord::Migration.maintain_test_schema!のコメントを外す

rails_helper.rb
#コメントアウトを外す
ActiveRecord::Migration.maintain_test_schema!

そしてテストを走らせる

bin/rspec

全テスト通った。

まとめ

テスト実行 → テーブルが見つからない → テスト環境でリセットしてマイグレーション → テーブルできた → またテスト実行 → なくなって見つからない → 実行直前のスキーマ読まないようにする → テストは動く → スキーマに見つからないテーブルが記述されてなかった → 適切にマイグレーションする → テスト走る

こんな流れだった。

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

【チーム開発】マイグレーションの更新作業

概要

チーム開発において、DBの修正が発生することもあり!!
安易に更新してしまうとサーバーがおかしくなってしまうのでは???
チームに迷惑かけちゃう:confounded:
と、私も更新することが怖かったのですが、以下の手順を踏んだら問題なくできました:relaxed:

実施方法

$rake db:drop
 #DBの情報を全て削除!!登録したユーザー情報なども削除されます!
$rake db:create
 #DBの更新
$rake db:migrate
 #マイグレート
$rake db:migrate:status
 #きちんとマイグレートできているかを確認

ちなみに、、、
カテゴリーなどをテキストエディタに作成していたら、下記のコードもマイグレートした後に行なってください!!

$rake db:seed

これでDBの更新OK:laughing:
この手法は、登録したユーザー情報なども合わせて削除されるので、また登録する必要があります!

参考文献

https://qiita.com/parsetree/items/e9b08c6b11f762b949de

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

Visual Studio Codespaces で Ruby on Rails

最近 Rails を始めた VSCoder ですが、以下の問題点がありました。
※VSCoder: Visual Studio Code 愛用者

  • 環境構築が面倒

そのため、AWS の Cloud9 を利用していたのですが、今度は以下の問題が

  • ブラウザ上のエディタは使いづらく違和感がある
  • VSCode が使いたい

そこで今ホットな Visual Studio Codespaces に手を出してみて、人生変わりました。

Visual Studio Codespaces とは?

一言で言うと、クラウド上の開発環境です。
公式サイトから引用すると、以下の特徴が挙げられます。

  • Git リポジトリ、拡張機能、および組み込みのコマンドライン インターフェイスを備えたブラウザーベースのエディターである
  • どのデバイスからでもアプリケーションを編集、実行、デバッグできる

また、私にとって最大の特徴は、

  • デスクトップアプリの VSCode でも開発できる

ということです。もちろん拡張機能も追加できます。

Codespace を作成する

  1. Azure のアカウントを作成します。
  2. Visual Studio Codespaces のサイトから、Azure アカウントで Sign in します。
  3. Create Codespace で作成します。Codespace Name だけ指定すれば、あとはデフォルトで十分だと思います。既存のリポジトリがあれば、Git Repository で指定すると自動的に Clone してくれます。(Rails + Space で Railspace という名前にしてみました)

image.png

ブラウザ上に VSCode が出ます!!!
image.png

ターミナルも使えます。vsonline というユーザで workspace が作られています。
image.png

Python はデフォルトでいろいろ入っています。
image.png

デスクトップアプリの VSCode で開く

  1. Visual Studio Code のインストール
  2. ウィンドウ左側のツールバーの Extensions で Visual Studio Codespaces をインストール image.png
  3. Remote Explorer のアイコンができるのでそこから先ほど作成したのと同じアカウントに Sign in します。 image.png
  4. 先ほど作成した Railspace という Codespace が出てくるので、コンセントのマークをクリックして connect します。 image.png

これでいつもの VScode です!!
image.png

Ruby on Rails の環境構築

Cloud9 はデフォルトで Ruby や Rails の環境がありますが、Visual Studio Codespaces では残念ながらありません...。まあどうせ、 Cloud9 でもバージョン変えたりするので。
以下はすべて、Codespace に接続した デスクトップアプリの VSCode のターミナルで行います。
Codespaces は Linux 環境を使用しているので、基本的には普通の Linux への環境構築と同じです。

rbenv のインストール

Ruby 公式サイトでもおすすめしているこちらの README を参考にインストールしていきます。rbenv によって Ruby の複数のバージョンを管理できます。

まずはリポジトリをクローンしてパスに追加し、セットアップします。

$ git clone https://github.com/rbenv/rbenv.git ~/.rbenv
$ echo 'export PATH="$HOME/.rbenv/bin:$PATH"' >> ~/.bashrc
$ echo 'export PATH="$HOME/.rbenv/shims:$PATH"' >> ~/.bashrc
$ ~/.rbenv/bin/rbenv init

その後、ターミナルを再起動する必要があるので、 + ボタンで新しいターミナルを開いてください
以下のコマンドを入力して確認できますが、rbenv install が not found になっています。

$ curl -fsSL https://github.com/rbenv/rbenv-installer/raw/master/bin/rbenv-doctor | bash

image.png

指示されたリンクの通り、以下で解決します。

$ mkdir -p "$(rbenv root)"/plugins
$ git clone https://github.com/rbenv/ruby-build.git "$(rbenv root)"/plugins/ruby-build

もう一度確認すると、今度はうまくいっているはずです。

$ curl -fsSL https://github.com/rbenv/rbenv-installer/raw/master/bin/rbenv-doctor | bash

image.png

これで rbenv は完了です。私はここが山場でした。

Ruby と Rails のインストール

主要な Ruby のバージョンを確認できます。すべて見たい場合は、$ rbenv install --list-all で可能です。

$ rbenv install --list

あとは $ rbenv install [バージョンナンバー] で好きなバージョンをインストールできます。

$ rbenv install 2.7.1

インストール後、以下のコマンドでどのバージョンの Ruby を使うか指定します。

$ rbenv global 2.7.1

無事に Ruby がインストールできました!
image.png

続いて、Rails をインストールします。

$ gem install rails

Rails をインストールできました!
image.png

アプリの作成とローカルホストへのアクセス

あとはまるでローカル環境であるかのように開発ができます。
ローカルホストも使えます

$ rails new SampleApp

で Rails アプリを作成後、

$ rails s

でサーバを立ち上げます。
ブラウザで http://localhost:3000/ にアクセスすると...
image.png

以上です。
Rails に関しては始めて2カ月なので至らない点があるかもしれないです。
修正やコメントお待ちしてます。

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

Rubocopの実行が遅い時に見直すべきポイント

Rubocopの実行で遅くて困っている方は除外ディレクトリに.git/**/*を真っ先に含めましょう。爆速になります。
バージョンは 0.83.0です。

AllCops:
  Exclude:
    - vendor/bundle/**/*
    - bin/*
    - db/**/*
    - config/**/*
    - lib/*
    - Gemfile
    - public/**/*
    - tmp/**/*
    - log/**/*
    - .git/**/*

私の環境では2分近くかかっていたのが6秒で終わるようになりました。これで頻繁に実行できます。
役に立てば幸いです。

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

ログイン機能

1.Gemをインストールしてサーバーを再起動しよう
 Gemfileに以下のコードを追記
 追記する場所は、Gemfileの最後の行にgem 'devise'
 その後ターミナルでbundle install
ローカルサーバーを再起動しましょう
rails s

2.コマンドを利用してdeviseの設定ファイルを作成しよう
deviseを使用するためには、Gemのインストールに加えてdevise専用のコマンドを利用して設定ファイルを作成

ターミナルでrails g devise:install

config/initializers/devise.rb
config/locales/devise.en.ymlが作成される。

3.コマンドを利用してUsersモデルを作成しよう
 deviseを利用する際にはアカウントを作成するためのUserモデルを新しく作成します。作成には通常のモデルの作成方法ではなく、deviseのモデル作成用コマンドでUserモデルを作成

 ターミナルでrails g devise user

 app/models/user.rb
 db/migrate/20XXXXXXXXXXXX_devise_create_users.rb
 test/fixtures/users.yml
 test/models/user_test.rbが作成される
 routes.rbにdevise_for :usersが自動で追記される

 先ほどのrails g devise userコマンドで作成されたマイグレーションファイルを実行します
 ターミナルでrails db:migrate
 これで、usersテーブルが作成される
 ルーティングが変更されたので、サーバーを再起動してお
 rails s

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

html.erbでナンバリング(番号をふる)をする方法

HTMLの表示側で例えば、

  • 特徴1
  • 特徴1、特徴2
  • 特徴1、特徴2、特徴3…

のように内容によって、数値が変わるように番号をつけるときに使うコード。
(とくに利用が多いのはこういうときだった)

with_index(1) を利用する

こちらを利用することで、 ナンバリングスタート時の値を()内の値からスタートすることができ、
+1 と書く必要がなくなる。

<% ....map.with_index(1) do |a, i| %>


<p><%= i %></p>

each.with_index

こっちだと、 0 スタートになってしまうので、 +1 と書く必要が発生する。

<% ....map.each_with_index do |a, i| %>

<p><%= i + 1 %></p>
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

10⁹⁹⁴²⁰⁶⁷ は 8 桁である

この記事は下記の記事の続編ともいうべきものです。
桁数は Math.log10(x).floor + 1 でいいのか - Qiita

さて,$10^{9942067}$ は 10 進で何桁の数だろうか1

Ruby なら簡単さ。

irb(main):001:0> (10 ** 9942067).to_s.length
(irb):1: warning: in a**b, b may be too big
=> 8

え? は,8 桁ぁ? ど,どういうこと?

しかも何か変な警告が出てるし。つか,この警告がヒントか。要するにデカすぎてちゃんと計算できてないってこと? でもエラーじゃないしな。

いっそ .to_s.length を取って実行してみるか。
いや待てよ,もし巨大整数が表示されようとしたらターミナルがしばらく操作不能になりそうだしな。
えーい,やっちまえ。

irb(main):001:0> 10 ** 9942067
(irb):1: warning: in a**b, b may be too big
=> Infinity

へ? Float::INFINITY ってこと?
整数の整数乗なのに「浮動小数点数の正の無限大」になると?

あの,もしかして答えの「8」って,Infinity の文字数ですかい?

irb(main):002:0> Float::INFINITY.to_s
=> "Infinity"

やっぱし。

ちなみに,指数が 1 小さい $10^{9942066}$ は 10 ** 9942066 で計算できる。
このあたり,Ruby のバージョンにもよるそう。この記事は Ruby 2.7.1 に基づいている。

** は,結果が巨大すぎる整数になりそうなとき,計算を放棄して警告を出したうえで Float::INFINITY を返す,という仕様だそうです。
なので,実際的なプログラムで本当にそんな事態が起こりそうなときは,計算結果を finite? で確認したうえで使う必要がありそう。

なお,$10^{9942067}$ を本当に計算したければ 10 ** 9942066 * 10 で計算できる。


  1. もちろん計算するまでもなく 9942068 桁である。 

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