- 投稿日:2020-05-26T23:15:26+09:00
カレンダー作成問題(たのしいRuby 練習問題)
はじめに
『プロを目指す人のためのRuby入門』通称チェリー本を学習後、
インプットしたものを手を動かして実践してみたいなと思ったら、作者の記事を見つけました。
「アウトプットのネタに困ったらこれ!?Ruby初心者向けのプログラミング問題を集めてみた(全10問)」ここに載っている一問目
「たのしいRuby」に載っている、オーソドックスなカレンダー作成問題です。
DateクラスのAPIさえわかれば、あとは基礎的なプログラミング知識だけでコードが書けると思います。Date クラスを使って、今月の1日と月末の日付と曜日を求め、次のような形式でカレンダーを表示させてください
こんな感じにするのが目標
May 2020 Su Mo Tu We Th Fr Sa 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調べたところ色々な回答を見つけることができましたが、自分の回答はこんな感じになりました。
require 'date' def monthly_calendar(this_day = Date.today) #実行日の日付 # 実行月の1日を抜きだす first_day = Date.new(this_day.year,this_day.month,1) # 月の最初の週の日曜日(カレンダー上で左上)に該当する日を抜き出す start_day = first_day - first_day.strftime('%w').to_i # month year puts this_day.strftime('%B %Y').center(21) # weekdays puts "\sSu\sMo\sTu\sWe\sTh\sFr\sSa" # days while start_day.month <= first_day.month if start_day.month != first_day.month print "\s\s\s" elsif start_day.strftime('%u') == "6" print "\s" + start_day.strftime('%e') + "\n" else print "\s" + start_day.strftime('%e') end start_day += 1 end end puts monthly_calendar引数に年と月だけ渡してみてもその月のカレンダーを表示することができました。
puts monthly_calendar(Date.new(1995,8)) #任意の月で指定もできるAugust 1995 Su Mo Tu We Th Fr Sa 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< 1995-08-01 >という日付で値が渡されるようです。
何か改善点等あればお教えください。
- 投稿日:2020-05-26T22:51:44+09:00
Rubyがrbenvからではなく、Macにデフォルトでインストールされているバージョンが参照されていた
rbenvとMacのデフォルトでインストールされているRubyのバージョンの相違
作業を開始しようとしていつものように
bin/rails s
をしたら以下のようなメッセージが出てきました。$ bin/rails s Your Ruby version is 2.6.3, but your Gemfile specified 2.6.5システムで設定されているRubyのバージョンを確認するとrbenvで2.6.5をインストールしているはずなのに2.6.3になってしまっています。
$ ruby -v ruby 2.6.3p62 (2019-04-16 revision 67580) [universal.x86_64-darwin19]念のためrbenvで指定しているバージョンも確認しましたが、2.6.5で間違いはないです。
$ rbenv versions system * 2.6.5 (set by /Users/ユーザー名/desktop/ディレクトリ名/.ruby-version) 2.7.0Rubyの参照先を確認する
まずはrbenvのパスが通っているかを確認しますが特には問題なしでした。
$ cat ~/.bash_profile export PATH="$HOME/.rbenv/bin:$PATH" eval "$(rbenv init -)"
ruby
コマンドの参照先を確認すると以下の通り、 rbenvから参照されていませんでした。bundler
コマンドも同様でした。$ which ruby /usr/local/bin/ruby $ which bundler /usr/local/bin/bundlerパスが読み込まれる順番も確認しましたが、読み込み順に関しても問題はなかったです。
そこで、そもそもrbenvのインストール状況側に問題があるのではないかと推測しました。まずはrbenvのshimsを確認しました。
shimsはrbenvの実行可能なirb, gem, rake, rails, ruby
などのコマンドを管理するファイルです。$ ls -l ~/.rbenv/shims # 空でした。なんと中身が空だったため、rbenvで管理しているバージョンのRubyが参照されていなかたっということでした。
何かしらの拍子に削除されてしまったのでしょうか...別のバージョンのRubyをインストールする
当初は特に別のバージョンのRubyをインストールするつもりはなかったのでshimsに一通りのコマンドを追加するためrbenvの機能である
rehash
を実行。$ rbenv rehash # コマンドが追加されているかshimsを確認 $ ls -l ~/.rbenv/shims # 空のまま...
rehash
を実行してもコマンドが追加されませんでした。色々と調査をしましたが、原因解明には至らず。
この際なのでこの記事を執筆した2020年5月26日現在の安定版であるRuby2.6.6をインストールすることにしました。Ruby2.6.6をインストール
$ rbenv install 2.6.6 $ rbenv rehash # コマンドを追加する $ rbenv global 2.6.6 # システム全体で使用するバージョンを指定
ruby
コマンドの参照先を確認$ which ruby /Users/ユーザー名/.rbenv/shims/ruby $ which bundler /Users/kawafujimasashi/.rbenv/shims/bundler # 念のためbundlerも確認。問題なく追加されていました。RailsにインストールしたバージョンのRubyを適用する
# Railsアプリケーションの一番上の階層のディレクトリに移動 $ rbenv local 2.6.6 # 使用するRubyのバージョンを明記している.ruby-versionファイルを書き換えます $ bundle installそして
bin/rails s
を実行するとrbenvで指定したバージョンが参照され動いてくれました。参考にしたページ
公式のgithubのREADME
https://github.com/rbenv/rbenv#how-rbenv-hooks-into-your-shell
- 投稿日:2020-05-26T22:49:35+09:00
Kinx 要素編 - 演算子オーバーライド
演算子オーバーライド
はじめに
「見た目は JavaScript、頭脳(中身)は Ruby、(安定感は AC/DC)」 でお届けしているスクリプト言語 Kinx。ライブラリ… ではないですが、ライブラリ作成で便利な機能。
今回は演算子オーバーライドです。
- 参考
- 最初の動機 ... スクリプト言語 KINX(ご紹介)
- 個別記事へのリンクは全てここに集約してあります。
- リポジトリ ... https://github.com/Kray-G/kinx
- Pull Request 等お待ちしております。
Ruby の何でもオブジェクト方針は一貫した思想という意味で美しいとも思うのだが、
1+1
の意味を変えるのは百害あって一利無しと思います。できても良いとは思うけど。ただし、クラス・オブジェクトに対しての演算子オーバーライドは有益です。ということで、Kinx では クラス・オブジェクトに対してのみ演算子のオーバーライドを明示的にサポート します。
String.+
とかも定義して使えますが、というか標準ライブラリの中で既に使ってますが、オーバーライドしたときの動作保証は いたしません。標準ライブラリで使っている=標準ライブラリの動作が変わる、なので本当に保証できませんので悪しからず...。演算子オーバーライド
演算子オーバーライドとは
オブジェクトに対する演算子の挙動を上書きすること。演算子がクラスに属しているメソッドと考えれば「オーバーライド」となり、クラスに属さないと考えると「オーバーロード」となるイメージですが、ここでは Ruby っぽく演算子はクラス・オブジェクトへのメッセージでありクラスに属しているイメージで、そのクラス・メソッドを上書きする形を表現して「オーバーライド」で統一しておきます。
尚、C++ の演算子オーバーロードは演算子の多重定義です。クラス・メソッドではなく、同じ名前の関数(や演算子)でも、その引数の違いによって呼び出される関数が区別される機能のことです。
基本形
オーバーライド可能な演算子の種類は以下の通り。
==
,!=
,>
,>=
,<
,<=
,<=>
,<<
,>>
,+
,-
,*
,/
,%
,[]
,()
.例として、
+
演算子をオーバーライドしてみましょう。関数名を演算子名の+
とするだけです。他の演算子でも同じ。class Sample(value_) { @isSample = true; @value = value_; public +(rhs) { if (rhs.isSample) { return new Sample(value_ + rhs.value); } return new Sample(value_ + rhs); } }
rhs
として渡されるものは、適宜想定するコンテキストに合わせて場合分けして実装する必要があります。上記のように実装すると、以下のように使えます。var s1 = new Sample(10); var s2 = s1 + 100; s1 += 1100; System.println(s1.value); // => 1110 System.println(s2.value); // => 110
a += b
も内部的にはa = a + b
に展開されるので正しく動作します。尚、オブジェクトに対するメソッド呼び出しなので、以下のようにも書けます。
var s1 = new Sample(10); var s2 = s1.+(100); System.println(s2.value); // => 110基本的に、
[]
演算子と()
演算子以外の右辺値を取る演算子は、同様の動作をします。
[]
演算子
[]
はインデックス要素的なアクセスを許可します。ただし、インデックスには整数(Integer)かオブジェクト、配列しか使えません。実数(Double)は動作しますが引数には整数(Integer)で渡ってきます。文字列は使えません(プロパティ・アクセスと同じであり、無限ループする可能性があるため)。実際に、例えば
Range
には実装されており、以下のようなアクセスが可能です。System.println((2..10)[1]); // => 3 System.println(('b'..'z')[1]); // => 'c'ただし内部で toArray() されるので、イテレーションは最後まで行われた後に応答されます。具体的には以下のように実装されています。
Range(多少異なるがこんな感じ)class Range(start_, end_, excludeEnd_) { ... public [](rhs) { if (!@array) { @array = @toArray(); } return @array[rhs]; } }
[]
演算子もメソッド呼び出し風に書くと以下のようになります。System.println((2..10).[](1)); // => 3 System.println(('b'..'z').[](1)); // => 'c'
()
演算子
()
演算子はオブジェクトに直接作用します。C++ のファンクタ(operator()
を定義したクラス)みたいなものです。例えば以下のようにクラス・インスタンスを関数のように見立てて直接()
演算子を適用できます。class Functor { public ()(...a) { return System.println(a); } } var f = new Functor(); f(1, 2, 3, 4, 5, 6, 7); // => [1, 2, 3, 4, 5, 6, 7]メソッド呼び出し風に書くと以下と同じです。
var f = new Functor(); f.()(1, 2, 3, 4, 5, 6, 7); // => [1, 2, 3, 4, 5, 6, 7]サンプル
スタック
スタック操作を
<<
で行えるクラスStack
を作ってみましょう。<<
で Push します。>>
でポップさせたいですが、引数に左辺値を渡せないので、無理矢理ですが()
演算子で行きます。ちょっと中途半端ですが仕方ない。配列を Push すると末尾に全部追加するようにしておきます。class Stack { var stack_ = []; public <<(rhs) { if (rhs.isArray) { stack_ += rhs; } else { stack_.push(rhs); } } public ()() { return stack_.pop(); } public toString() { return stack_.toString(); } } var s = new Stack(); s << 1; s << 2; s << 3; s << 4; s << [5, 6, 7, 8, 9, 10]; System.println(s); var r = s(); System.println(s); System.println(r);実行してみましょう。
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10] [1, 2, 3, 4, 5, 6, 7, 8, 9] 10期待通りですね。
有理数クラス
別のサンプルとして四則演算のみをサポートした有理数クラスを作ってみましょう。符号処理は今回は省略します。基本形は以下の通り。
class Rational(n, d) { @isRational = true; }まず初期化です。Rational オブジェクトのコピーも作れるようにしておきます。また、有理数の演算では最大公約数を求める機会も多いのでそのための private メソッドを用意します。また、確認しやすいように
toString()
メソッドも用意しておきます。class Rational(n, d) { @isRational = true; private gcd(a, b) { if (a < b) { [a, b] = [b, a]; } var r; while ((r = a % b) != 0) { [a, b] = [b, r]; } return b; } private initialize() { if (d.isUndefined && n.isRatioal) { d = n.denominator; n = n.numerator; } var g = gcd(n, d); @numerator = Integer.parseInt(n / g); @denominator = Integer.parseInt(d / g); } public toString() { return "%{@numerator}/%{@denominator}"; } } var r = new Rational(5, 10); System.println("r = ", r); // => r = 1/2では、早速四則演算を定義していきます。
ここではまず
+
演算子の定義です。ただし、r1 + r2
でr1
が破壊されるのは直感的ではないので、新しいオブジェクトを返すようにします。また、直接破壊的に操作する別のメソッドを用意してきます。ついでにオブジェクトのクローンをつくるclone()
メソッドを作って活用しましょう。class Rational(n, d) { @isRational = true; private gcd(a, b) { if (a < b) { [a, b] = [b, a]; } var r; while ((r = a % b) != 0) { [a, b] = [b, r]; } return b; } private initialize() { if (d.isUndefined && n.isRational) { d = n.denominator; n = n.numerator; } var g = gcd(n, d); @numerator = Integer.parseInt(n / g); @denominator = Integer.parseInt(d / g); } public toString() { return "%{@numerator}/%{@denominator}"; } public clone() { return new Rational(this); } public add(rhs) { if (rhs.isInteger) { return this + new Rational(rhs, 1); } else if (rhs.isRational) { var n = @numerator * rhs.denominator + @denominator * rhs.numerator; var d = @denominator * rhs.denominator; var g = gcd(n, d); @numerator = Integer.parseInt(n / g); @denominator = Integer.parseInt(d / g); } else { throw RuntimeException("Unsupported type for rational calculation"); } return this; } public +(rhs) { return @clone().add(rhs); } } var r1 = new Rational(5, 10); var r2 = new Rational(2, 6); var r3 = r1 + r2; var r4 = r1 + 2; System.println("r1 = ", r1); System.println("r2 = ", r2); System.println("r1 + r2 = ", r3); System.println("r1 + 2 = ", r4);
rhs
が Integer の場合、こんなこと(this + new Rational(rhs, 1)
のことね)する必要はないのですが、こんなこともできます、という意味での単なる例です。新たに Rational オブジェクトを作って再度.+()
演算子が呼ばれて正しく計算されるというイメージです。結果は以下のように表示されます。
r1 = 1/2 r2 = 1/3 r1 + r2 = 5/6 r1 + 2 = 5/2では、四則演算全て定義してみましょう。先ほどの無駄っぽいところ(
this + new Rational(rhs, 1)
のことね)も今回は変えておきます。class Rational(n, d) { @isRational = true; private gcd(a, b) { if (a < b) { [a, b] = [b, a]; } var r; while ((r = a % b) != 0) { [a, b] = [b, r]; } return b; } private makeValue(n, d) { var g = gcd(n, d); @numerator = Integer.parseInt(n / g); @denominator = Integer.parseInt(d / g); return this; } private initialize() { if (d.isUndefined && n.isRational) { d = n.denominator; n = n.numerator; } makeValue(n, d); } public toString() { return "%{@numerator}/%{@denominator}"; } public clone() { return new Rational(this); } public add(rhs) { if (rhs.isInteger) { return makeValue(@numerator + @denominator * rhs, @denominator); } else if (rhs.isRational) { return makeValue(@numerator * rhs.denominator + @denominator * rhs.numerator, @denominator * rhs.denominator); } else { throw RuntimeException("Unsupported type for rational calculation"); } } public sub(rhs) { if (rhs.isInteger) { return makeValue(@numerator - @denominator * rhs, @denominator); } else if (rhs.isRational) { return makeValue(@numerator * rhs.denominator - @denominator * rhs.numerator, @denominator * rhs.denominator); } else { throw RuntimeException("Unsupported type for rational calculation"); } } public mul(rhs) { if (rhs.isInteger) { return makeValue(@numerator * rhs, @denominator); } else if (rhs.isRational) { return makeValue(@numerator * rhs.numerator, @denominator * rhs.denominator); } else { throw RuntimeException("Unsupported type for rational calculation"); } } public div(rhs) { if (rhs.isInteger) { return makeValue(@numerator, @denominator * rhs); } else if (rhs.isRational) { return makeValue(@numerator * rhs.denominator, @denominator * rhs.numerator); } else { throw RuntimeException("Unsupported type for rational calculation"); } } public +(rhs) { return @clone().add(rhs); } public -(rhs) { return @clone().sub(rhs); } public *(rhs) { return @clone().mul(rhs); } public /(rhs) { return @clone().div(rhs); } } var r1 = new Rational(5, 10); var r2 = new Rational(2, 6); var r3 = r1 + r2; var r4 = r1 - r2; var r5 = r1 * r2; var r6 = r1 / r2; System.println("r1 = ", r1); System.println("r2 = ", r2); System.println("r1 + r2 = ", r3); System.println("r1 - r2 = ", r4); System.println("r1 * r2 = ", r5); System.println("r1 / r2 = ", r6);結果。
r1 = 1/2 r2 = 1/3 r1 + r2 = 5/6 r1 - r2 = 1/6 r1 * r2 = 1/6 r1 / r2 = 3/2おわりに
上記有理数クラスに符号処理はありませんが、簡単なので省略します。もしかしたらどこかで正式に有理数クラスをサポートするかもしれません。その時は本気出して色々メソッドを定義してみます(以下が参考)。
ではまた次回。
clone()
についての補足
clone()
は通常、上記のようにnew 自分自身のクラス(this)
で定義することが多いですが、以下のようにすると新たに作ったオブジェクトが過去のオブジェクトへの参照を持ち続けてしまうので、新たに作成したオブジェクトが死なない限りその元オブジェクトも GC で解放されないといったことになり、リークする可能性があります。class A(arg_) { @isA = true; var a_; private initialize() { a_ = arg_.isA ? arg_.get() : 0; // arg_ = null が無いと参照を持ち続けてしまう } public get() { return a_; } public clone() { return new A(this); } /* ... */ }上記コメントのように初期化後に
arg_ = null
とすれば OK ですが、それ以外にも、arg_
とa_
を共用させる方法もあります(上記 Rational クラスはそれに近い方法)。例えば以下のような感じ。class A(a_) { @isA = true; private initialize() { a_ = a_.isA ? a_.get() : 0; } public get() { return a_; } public clone() { return new A(this); } /* ... */ }こうすることで、新たなオブジェクトから過去のオブジェクトへの参照が切れるので、しかるべき時にきちんと GC が働くようになります。
では、また。
- 投稿日:2020-05-26T22:39:53+09:00
オレオレRailsコーディング規約
あらすじ
吾輩は末端エンジニアである。名前はしょった。先日、急遽あるRailsアプリの引継ぎ業務に駆り出された。吾輩はここで始めて、アプリのコードを見た。しかもこのコードがまあ追いにくい。ブチギレ寸前である。
アンチパターン
というわけで本題。
先に、ここでいうアンチパターンというのは一般に流布されているそれとはまるで別物であるということは先にお伝えしておきたい。あくまで個人的なベストプラクティスであり、引き継ぐならこうなっていて欲しい(欲しかった)という願望である。1. 1つのディレクトリ内に無駄にファイルが多い
今、ここに猫がいる。猫はかわいい。神は猫をこう定義した。
./cat.rbclass Cat def comment puts "かわいい" end end猫は猫耳と尻尾、そして肉球でできている。
神はかわいい猫をこう定義し直した。./cat.rbclass Cat def initialize @parts = [ CatEar.new, CatTail.new, CatPad.new ] end def comment @parts.each(&:comment) end end./cat_ear.rbclass CatEar def comment puts "猫耳かわいい" end end./cat_tail.rbclass CatTail def comment puts "しっぽもかわいい" end end./cat_pad.rbclass CatPad def comment puts "肉球までかわいい" end endこれで猫を無限に愛することができる...猫はかわいい...猫は至高....................
...
......
うるせーーーーーーーーーーーーーーーーーーーーーーーーーー!!!!!!!!!!!!
猫を愛でる前にコードを片付けやがれ!!!!!!!!!というわけで茶番はさておき解説。
現在のディレクトリ構造を図示するとこうだ。./ ├ cat.rb ├ cat_ear.rb ├ cat_tail.rb └ cat_pad.rb猫クラスと猫のパーツが同居した状態で入れられている。
例えば、ここに犬が増えたらどうなるだろうか。./ ├ cat.rb ├ cat_ear.rb ├ cat_tail.rb ├ cat_pad.rb ├ dog.rb ├ dog_ear.rb ├ dog_tail.rb └ dog_pad.rbファイルの数がめっちゃ増えた...
さらにここに他の動物を加えていくと...まあ地獄が生まれるのは容易に想像できる。じゃあどうするのか。そう、パーツをCatクラス以下に作るのである。
./cat.rbclass Cat def initialize @parts = [ Ear.new, Tail.new, Pad.new ] end def comment @parts.each(&:comment) end end./cat/ear.rb# rails的にディレクトリ内に入ってるものはモジュールまたはクラスでネームスペースを切るのが一般的 class Cat class Ear def comment puts "猫耳かわいい" end end end./cat/tail.rbclass Cat class Tail def comment puts "しっぽもかわいい" end end end./cat/pad.rbclass Cat class Pad def comment puts "肉球までかわいい" end end endとなる。
ディレクトリ構造は./ ├ cat.rb └ cat ├ ear.rb ├ tail.rb └ pad.rbこれによって、それぞれのパーツはcatに関わるものとしてまとめられた。
カレントディレクトリから見ると、catの定義とcatに関わるものが入ってるのみなので、このアプリは猫に関するものだということが0秒でわかる。素晴らしい。と、少々無茶な説明をしたが、ちょっと具体的に。
今あなたはネットバンキングのサービスを作っている。このサービスでは以下の3つのモデルがある。
- 口座の情報
- 口座の入出金履歴
- 口座の振込履歴
あなたはこれをこういう構造で実装するかもしれない。
./ ├ account.rb ├ money_history.rb └ deposited_history.rbこれでも十分、客先には喜ばれ、上司には褒められるだろう。
ただ、ここでこれを./ ├ account.rb └ account ├ money_history.rb └ deposited_history.rbとしておくだけで、ベターになれる。きっとあなたのプロジェクトに後から入ってきたメンバーにも尊敬されるだろうし、末代まで感謝され続けるだろう。私ならこうして欲しい。こうして欲しかった。ただそれだけのことである。
2. オブジェクトのインターフェースが整っていない
rubyはオブジェクト指向言語である。オブジェクト指向は素晴らしい。オブジェクトから枝葉のように伸びるメソッドを叩くことで、やりたいことが完結できるからである。
例えるならこうだ。
ここにボルトがある。
オブジェクト指向でない場合、このボルトを締めるにはレンチが必要だ。
だがしかし、オブジェクト指向だと...
STARTボタンを押すだけレンチが勝手にボルトを締めてくれる。素晴らしい...素晴らしい......ではこれがアンチパターンになる場合をお見せしよう。
あなたは洗濯機を作った。次のような実装である。washing_machine.rbclass WashingMachine def wash locking do inject_water ventilate end end def dry locking do ventilate end end def locking puts "扉をロックします" yield puts "扉のロックを解除します" end def inject_water puts "注水します" end def ventilate puts "風を送ります" end end> WashingMachine.new.wash 扉をロックします 注水します 扉のロックを解除しますこの実装の問題は、以下のようなことが容易に起こり得ることにある。
> WashingMachine.new.inject_water 注水しますいや、扉閉まってないやん!!!
まあこれは初歩的な話なので、 今更かよ...って話だけど。
オブジェクト指向で大事なことはただ一つ、インターフェースを整えることだ。インターフェースが整ってないオブジェクトは洗練されていないリモコンと同じ。使い勝手は手続き型言語より悪いだろう。
オブジェクトのインターフェースの整える指針は以下
- メソッド名、必要な引数はわかりやすくする
- 必要なメソッド以外は隠す
先ほどの実装であれば、
washing_machine.rbclass WashingMachine def wash ... end def dry ... end private # これ以降は内部仕様なので隠す def locking ... end def inject_water ... end def ventilate ... end endこうしておけば先ほどのような操作ミスは起きないだろう。
余談だが、同じようなミスを実はRoRでもやってたりする。
ActiveRecord::Base#read_attribute(attr_name) というメソッドがある。
このメソッドは、「sqlが取ってきた[attr_name]の値をそのまま取得する」という挙動をし、任意のゲッターを作るときに利用価値がある。痒いところに手がとどくメソッドである。
ただこのメソッドは公開範囲がpublicなのが問題だ。例えば、以下のような実装ができる。models/hoge.rbclass Hoge < ApplicationRecord attribute :name, :string endviews/hoge.html.erb<%= Hoge.first.read_attribute(:name) %>この実装は問題なく動くだろう。
ここで、nameカラムがnull
または空文字だった場合、'Unknown'
と返そう、となった。models/hoge.rbclass Hoge < ApplicationRecord def name read_attribute(:name).presence || 'unknown' end endすると
views/hoge.html.erb
をレンダリングしたときにnameがnull
だった場合、出力される値はnilである。いや、viewではnameを表示してるはずなのに???なんで???ってなる。なった。は〜〜〜〜〜〜〜〜〜????????read_attributeメソッドは本来であればprotectedメソッドであるべきだ。なぜなら、クラスの外から使うにはあまりに抽象的すぎるからである。また、クラス外で使うとプロジェクト内検索では見落としやすくなるだろうし、変更にも弱い。マジで使うべきではない。
ちなみに、この話は実際に弊社の尊敬する先輩がやってたコードである。先輩のことは恨んでいない。RoRのことは恨んでいる。許さん。
3. ロジックが分散している
washing_machine.rbclass WashingMachine < Machine include DoorModule include ValveModule include AirValveModule def run locking do inject_water ventilate end end endこのクラスを見せられたときに、送風する部分を改修して欲しいと言われたら、あなたはどこを探すだろうか?
送風(ventilate)ということは、ventilateメソッドを探せばいいのだろう。ということはventilateメソッドを定義している場所を探せばいいのだな。と、ここであなたはventilateでプロジェクト内を検索するだろう。ここでの問題は、ventilateというメソッドがどこからきたのか、このクラスだけでは判断しにくいことだ。基本的にinclude、excludeに加えて、継承にも言えることだが、それらを経由してやってきたメソッドは往往にして所在が不明瞭になりやすい。
使わないべきとは言わないまでも、ある程度の制限を設けて、コードの可読性を損なわないように使って欲しい(願望例えば上記の場合、モジュールを使わない代わりに以下のようにするのをオススメしたい。
washing_machine.rbclass WashingMachine < Machine def initialize @door = Door.new @valve = Valve.new @air_valve = AirValve.new end def run @door.locking do @valve.inject_water @air_valve.ventilate end end # delegate :locking, to: :@door # などでメソッド定義しても良い。とにかくメソッドの定義がファイル内に明示されていることが重要 endこれだけで、
AirValve#ventilate
を見ればいいことは一目瞭然である。勝ち申した。本当はこれが一番書きたいところなんだけど、疲れたのでこの辺で。
おわりに
つらつらと好き勝手述べてきたが、結局言いたいのはもっとコードの可読性を上げてくれ、というところだ。
どの言語でも言えることだけど、キャメルがどうだ〜、if文の後ろの中括弧はこうだ〜みたいなどっちでもいい議論は聞くけれど、こういう構造で書いた方がいいよ!とか、こうすれば管理がしやすいよ!みたいな議論の方が大事だと思うので、もっと盛んに議論してください。よろしくおねがいします。
- 投稿日:2020-05-26T22:07:08+09:00
renderとredirect_toの違い
- 投稿日:2020-05-26T20:55:22+09:00
100日後に1人前になる新人エンジニア(6日目)
100日後に1人前になる新人エンジニア(6日目)
どうもこんばんは。今日のお題はテストです。
Railsってテストがいくつかあってそれらの特徴についてまとめてみたいと思います。今日取り上げるテストは2つです。
● Minitest
● RSpec
の2つです。私はRails Tutorial と プロを目指す人のためのRubyという書籍を読んでいたのですが、これらで取り上げられていたのはどちらもMinitestでした。しかしながら実際の私の業務ではRSpecを使用するため
双方の特徴を知りたいなと思い本日まとめたいと思います。Minitest
minitestはRubyと一緒にインストールしてありますので、特にセットアップなどは必要なく、またRuby on Railsにおいてもデフォルトで入っているテストフレームワークであります。とのこと。
実際requireでMinitestを記述するだけで使える様になります(簡単!)その他の特徴としては
● RSpecよりもロード時間が短い
● 必要最低限の機能を備えている(プラグインで追加する)
● Rubyの文法がわかれば習得が用意
● 検証メソッドは assert_equal A,B
● クラス名やメソッド名の重複に注意が必要
● 凝ったコードを書くのにはあまり向いていない(解析が難しい)
● RSpecよりは早い以上の様な特徴が挙げられるみたいです。
確かに勉強してみたけどすんなり理解できた感じはあったかも
syntaxがRubyと同じだからだったのかな...RSpec
RSpecはドメイン特化言語 (DSL)を使ったフレームワークです。
つまりテスト専用のプログラミング言語ってことです。
その他の特徴としては● DSLを覚える必要あり(これは上でも言っていますね。)
● デフォルトの機能が多い
● 検証メソッドはexpect(B).to eq A または B.must_equal A
● テストの命名が比較的自由
● 保守性が高い
● Minitestに比べると遅いまた1から勉強するのはちょっとつらい...笑
けど色々とメリットが存在するんですねどっちがいいの
これに答えはないと思いますが(ないんかい)
それぞれを比較してみると現場ではRSpecの方が多く使用されているみたいです。
RSpecの方が多機能で便利なのが現場で選ばれてる理由の一つなんですかね。なるほどねー。ってんじでした。
実務でこれを使ってるのでとりあえず勉強しなきゃってモチベーションより
なんでこのテストフレームワークを使ってるんだろうって考えて
そこから勉強した方が楽しんで勉強できるんじゃないかと思っています。ということでRSpecの勉強も初めていきたいと思います。
またRSpecの記事を書くかもしれません。今日のところは以上です。
1人前のエンジニアになるまであと94日
- 投稿日:2020-05-26T20:49:45+09:00
Qiitaはじめました。
概要
4月から新社会人になった駆け出しエンジニアです。
研修も全面リモートワークということもあり、何かはじめてみようと思い、自分がやってみた(やらなきゃいけない)ことをQiitaにアウトプットしていくことにしました。初投稿は自分のモチベーションのために、技術的な内容ではなく、これからやってみたいこととか書いていければと思います。
これから何するの?
やっていきたいことは多々あるんですけど、なかでもバックエンドにフォーカスしていければと思います。(とか言いながら最近はVueに挑戦している)
と言うのも、就活をしていたときぐらいから何となくバックエンドの技術に興味があり、ちょっと調べているうちにやってみたいと思うようになったからで。
また、会社の配属先の部署で自動化やネットワークプログラマビリティなどのような高度な技術が必要とのことで、そこらへんに関連するバックエンドを中心に勉強していけたらと思っております。
具体的には?
具体的に取り組んでみたい内容を簡単に書き出してみました。
・Ruby on Rails(Javaの復習がてら)
・AWSのLambdaとかGreengrassとラズパイ
・Dockerでコンテナ型の仮想化を理解
・Kubernetes(ムズい)
などなど。。。挙げだしたらキリがないです。。。
若干独学で出来るのか不安なところはありますが、会社に詳しい人がいると思うので、色々手を動かしながらやっていきたいです。おい、資格の勉強はどうした?
資格て応用情報のことですよね。結論としては多分受けないです。
その代わり(?)といっては何ですが、今年から始まったCiscoのDevNet認定に今興味があり、色々調べています。というのも、カリカリ勉強するよりパチパチコード書いてアウトプットしていったほうが圧倒的にスキルになるなと最近感じ始めたからで。
DevNet認定ではネットワークの基礎知識だけではなく、冒頭に申し上げたネットワークプログラマビリティや自動化が範囲としてあり、Python/Git/APIなどの幅広いモダンな開発知識が求められます。
個人的にIoT/IoEやDevOpsに興味があるので、ピッタリな内容かなと思いました。お前、英語のこと忘れてるだろ
はい、最近気付きました。
チリツモを信じて毎朝Duoのチャプターひとつやるようにしてます。。。頑張ります。。。最後に一言
これから長い長いエンジニア人生がはじまりますが、クリエイティブな思考を忘れないようにしたいものです。
ということで、これから触ってみた技術や知見をQiitaに書き留めていきたいと思います。
はじめはクオリティ低いと思いますが、よろしくお願いします。
- 投稿日:2020-05-26T20:23:18+09:00
Ruby : Nokogiriはバイナリモードで読んだhtmlの文字コードを自動で判別しにいく
はじめに
open-uriでhtmlを読み込み、Nokogiriでパースしてスクレイピングを行っていたのですが、その際Nokogiriのエンコーディングを
nil
に(もしくは省略)しても文字化けを起こすことなくパースができていました。
open-uriでhtmlを読むときはバイナリで読み込まれるので、バイナリで読み込まれたhtmlを、エンコーディングnil
にしてNokogiriでパースするとどうなるか、ということに焦点をしぼって検証してみました。HTML.parseのエンコーディングを「nil」にしてみる
Nokogiriでhtmlをパースするときのエンコーディングを
nil
としてみます。HTML.parseの第三引数にnil
を設定します。
今回読み込むのは以下のhtmlです。ファイルはShift_JISで書かれています。hello.html<html> <head> <title>こんにちわ</title> <meta charset="Shift_JIS"> </head> <body> </body> </html>バイナリモードでhtmlを読み込みます。openメソッドに
'rb'
オプションを追記することでバイナリでファイルを読むことができます。
検証のため、バイナリモードで読み込んだ時点での文字コードと、Nokogiriでパースしたあとの文字コードを表示してみます。sample.rbrequire 'nokogiri' html = open('hello.html', 'rb').read p html.encoding p Nokogiri::HTML.parse(html, nil, nil).encoding実行結果
sample.rbの結果$ ruby sample.rb #<Encoding:ASCII-8BIT> "Shift_JIS"結果からは、読み込んだhtml自体のエンコーディングはASCII-8BITですが、Nokogiriによってパースされたあとのエンコーディングは元ファイルと同じShift_JISとなっていることが確認できます。
ちなみにHTML.parse(html)
として引数を省略した場合でも、上記と同じ結果が得られます。Nokogiriはどこで文字コードを参照しているか
さきほどの検証結果を見るに、Nokogiriはどこかの文字コードを自分で参照しに行っています。どこを参照しているのか。
実は元のhtmlファイルのmeta要素を参照しに行っています。
<meta charset="Shift_JIS">
のcharsetを参照しているというわけです。試しにcharset部分をUTF-8に変えて、先ほどと同じように文字コードを出力してみます。
hello.html<html> <head> <title>こんにちわ</title> <meta charset="UTF-8"> </head> <body> </body> </html>実行結果
sample.rbの結果$ ruby sample.rb #<Encoding:ASCII-8BIT> "UTF-8"パース後の文字コードがUTF-8に変わっているのが確認できます。
ちなみにcharsetを無くしてみると、、、
hello.html<html> <head> <title>こんにちわ</title> <meta> </head> <body> </body> </html>sample.rbの結果$ ruby sample.rb #<Encoding:ASCII-8BIT> nilパース後の文字コードがnilとなってしまいました。
もちろんこの状態でタイトル等を表示すると文字化けを起こします。まとめ
- htmlをバイナリで読み込み、かつNokogiriのエンコーディングを
nil
にすると、Nokogiriは自分で文字コードを参照しに行く- Nokogiriが参照しに行くのは、バイナリで読み込んだhtmlのmeta要素のcharset
- charsetが書かれていないと、Nokogiriのエンコーディングは
nil
となってしまう
- 投稿日:2020-05-26T19:34:49+09:00
5秒で直す Library not loaded: /usr/local/opt/openssl/lib/libssl.1.0.0.dylib
結論
brew switch openssl 1.0.2s
で直る。バージョンは環境によって異なるので、
ls /usr/local/Cellar/openssl
で存在するバージョンを調べて
brew switch openssl バージョン名
とする。検証環境
- ruby 2.5.0p0
- Mac Book Pro (Catalina 10.15.4)
背景
pod install
時にこのエラーが出た。
rubyのopensslのバージョンが固定されていることが問題みたい。
他の記事を参考にbrew update && brew upgrade
してみたが直らず。
「rubyを再インストールする」という解決法もあるみたいだが、rubyの再インストールは面倒くさい。。。と思っていたら、スタックオーバーフローに書いてある方法で速攻で解決した。
https://stackoverflow.com/questions/59006602/dyld-library-not-loaded-usr-local-opt-openssl-lib-libssl-1-0-0-dylib
- 投稿日:2020-05-26T19:17:04+09:00
rails db:〇〇 まとめ
rails db:migrate
DBにmigrationファイルの実行
自動的に db:schema:dump も実行され、schema.rbも更新されるrails db:rollback
migrationファイルの実行前にDBを戻す
rails db:schema:load
schema.rbの内容を現在参照しているDBに適用
空のDBにロードする時に使うrails db:schema:dump
DBをschema.rbに反映
rails db:create
DBの作成
rails db:drop
DBの削除
rails db:reset
下記の省略
rails db:drop
rails db:create
rails db:schema:load
スキーマファイルだけを利用rails db:migrate:reset
下記の省略
rails db:drop
rails db:create
rails db:schema:load
マイグレーションファイルを直接利用rails db:seed
テーブルに初期データが追加
rails db:migrate:reset db:seed
データベースの削除&作成、テーブルに初期データが追加
- 投稿日:2020-05-26T18:33:22+09:00
Rails deviseのエラーメッセージ を日本語化するまでの道のり
Rails初学者です。今回はRailsのdeviseログイン機能でのエラーメッセージを日本語化したく、少々つまずいたので覚書きとしていきます。
前提
- deviseのログイン機能が実装できている
gemインストール
devise日本語用のgemを追加
Gemfilegem 'devise-i18n' gem 'devise-i18n-views'$ bundle installdeviseの日本語用ファイルを生成
$ rails g devise:views:locale ja create config/locales/devise.views.ja.yml日本語のエラーメッセージファイルが生成される
日本語用ファイルをdeviseから取得
config/application.rbを編集
'config.i18n.default_locale = :ja' を追加
デフォルトでは英語の:en
が設定されているので、日本語の:ja
に変更するconfig/application.rbmodule リポジトリ名 class Application < Rails::Application # Initialize configuration defaults for originally generated Rails version. config.load_defaults 5.2 ### config.i18n.default_locale = :ja ##### end end
Rails s
で起動し、エラー文を表示させてみる日本語化されたものの、一部英語の文が、、
deviseのエラーとマッチする日本語の文をdeviseが取得できていないようでしたエラー文の追加
devise.views.ja.ymlの編集
$ rails g devise:views:locale ja
で追加されたdevise.views.ja.yml
ファイルにメッセージが保存されているので編集する
デフォルトでは以下の用に記述されているconfig/locales/devise.views.ja.ymlja: activerecord: attributes: user: current_password: "現在のパスワード" email: "メールアドレス" password: "パスワード" password_confirmation: "確認用パスワード" remember_me: "ログインを記憶" models: user: "ユーザー" . . .先ほどのブラウザでのエラー文を見ると、
メールアドレス translation missing: ja.activerecord.errors.models.user.attributes.email.blank
のように出力されており、これをymlファイルで階層を作って編集する
config/locales/devise.views.ja.ymlja: activerecord: attributes: user: current_password: "現在のパスワード" email: "メールアドレス" password: "パスワード" password_confirmation: "確認用パスワード" remember_me: "ログインを記憶" models: user: "ユーザー" ### ここから errors: models: user: attributes: email: blank: "を入力してください" password: blank: "を入力してください" name: blank: "を入力してください" ### ここまでと編集し、エラー文を表示させてみると
上手くできました
ついでに
ja.errors.messages.not_saved.one
などを空欄one: ""
にカスタマイズして、エラーカウント文を削除してみたりなど、、ざっとdeviseのエラー文の日本語化はこんな感じになりました
参考にした文献がこちら
【Rails5】Devise-i18nで日本語化する
Devise日本語化後の「translation missing」に対処する
github devise-i18n
- 投稿日:2020-05-26T15:40:10+09:00
Rails でアプリを作成する際の初期作業まとめ
はじめに
railsでアプリを作成するときに毎回のように設定していることがあるので、初期の設定をまとめておきます。
作業は以下の流れになります。・アプリ作成
・データベース作成
・gem導入
・Haml導入
・テーブル作成
・コントローラ、ビュー作成
・jquery導入アプリ作成
まずはターミナルからコマンド入力でアプリを作成します。
railsのバージョンは使い慣れているものを指定するのが良いかと思います。
(今回はバージョン5.2.3、データベースはmysplを使用)
アプリを保存するディレクトリで以下を実行します。(今回はアプリ名はsample)ターミナル$ rails _5.2.3_ new sample -d mysql作成したアプリのディレクトリに移動しておきます。
ターミナル$ cd sampleデータベース作成
データベースを作成します。
ターミナル$ rails db:createdevelopmentとtest用のデータベースが作成されるはずです。
(Sequel Pro等のアプリで確認してみると良いでしょう)gem導入
開発に使いそうなgemを先に入れておきます。
以下の4つはとりあえず入れておいて良いかと思います。
Gemfileに以下を追記しましょう。Gemfilegroup :development, :test do gem 'pry-rails' end gem 'haml-rails' gem 'font-awesome-sass' gem 'jquery-rails'一応それぞれ説明しておくと
pry-rails:コントローラで処理を止めてparamsの中身を確認するため
haml:hamlを使うため(erbよりも記述が楽)
font-awesome-sass:font-awesomeのアイコンを使うため
jquery-rails:jqueryを使うため追記したらインストールしておく
ターミナル$ bundle installHaml導入
gemにhamlを追加していますが、アプリ作成時にもともと作られているerbファイルをhamlに直しておきます。
以下のコマンドを実行するだけです。ターミナル$ rails haml:erb2hamlコマンドを実行すると既存のerbファイルがhamlファイルに変換されます。
コマンド実行後、Would you like to delete the original .erb files?と聞かれるのでyと入力しておきましょう。
(もとのerbファイルを消して良いか?という質問です。良い→y, だめ→nで答えてあげましょう)テーブル作成
データベースにテーブルを作成します。
今回はpostsテーブルを作成することにします。
(実際にアプリを作成する際はDB設計をしてからになると思いますが、簡単に流れを確認するためと思ってください)■ まずはモデルを作成します。
ターミナル$ rails g model post下の画像のような感じでマイグレーションファイルとモデルのファイルなんかが作成されるはずです。
■ 続いてマイグレーションファイルを編集します。
作成するカラムの情報を記載します。
今回はカラム名をcontent, データ型はstring, null規制ありとしています。
マイグレーションファイルに追記してあげましょう!
(マイグレーションファイルはdb/migrateの中にあります)xxxxxxxxxxxxxx_create_posts.rbclass CreatePosts < ActiveRecord::Migration[5.2] def change create_table :posts do |t| t.string :content, null: false t.timestamps end end end■ マイグレーションを実行しましょう
マイグレーションファイルの記載が終わったらマイグレートします。ターミナル$ rails db:migrateコントローラ、ビュー作成
■ まずはコントローラを作成します。
(これがないとアプリにならない…)
今回はpostsコントローラーを作成します。ターミナル$ rails g controller postsコマンドを実行するといろいろファイルが作成されるかと思いますが、とりあえずposts_controller.rbだけ注目しておけばOKです。
(ここでpostsコントローラ用のビューフォルダも一緒に作られてます)
■ コントローラ編集
コントローラにアクションを記載しましょう。
(ファイルはapp/controllersの中)posts_controller.rbclass PostsController < ApplicationController def index end endとりあえずindexアクションのみ記載しておきます。
今回はインスタンス変数等の中身の記述は割愛します。■ ルーティング設定
続いてコントローラを使用するためにルーティングを設定しておきましょう。
(ファイルはconfigの中)routes.rbRails.application.routes.draw do root "posts#index" resources :posts, only: :index endpostsコントローラのindexアクションを使えるようにしています。
また、ルートパスも変更しておきましょう。(これがないとルートパスにアクセスした時にrailsが用意してあるデフォルトページが表示されてしまいます。)■ ルーティング確認
ルーティングが正しく設定されているか確認してみましょうターミナル$ rails routes下の画像のようにpostsコントローラのindexアクションが設定されていればOKです。
■ ビューファイル作成
ルーティングを設定したらビューファイルを用意しておきましょう。
(これがないとリクエストを飛ばしてもファイルがないためエラーが出ます)app/views/postsの中にindex.html.hamlを作成します。
中身はわかりやすいように何か書いておきましょう。index.html.haml%h1 インデックスページです
■ ブラウザで確認
ビューファイルを作成したらうまくいってるか実際にブラウザで確認してみましょう。
ローカルサーバーを立ち上げます。ターミナル$ rails sChromeなどのブラウザでURLに localhost:3000/ と入力してアクセスしてみましょう。
下の画像のように用意したビューファイルどおりに表示されていればOKです。jquery導入
jqueryの導入をしていきます。
application.jsにrequire jqueryを追記しましょう。application.js//= require jquery //= require rails-ujs //= require activestorage //= require_tree .※ require jqueryを追記する位置はrequire_treeよりも上にしないとエラーが出ます。
ーーー余談ーーー
ちなみにjqueryを使ってAjaxを手作業で追加する場合、turbolinksと干渉する恐れがあるので、私の場合はturbolinksを消しています。
Gemfileのturbolinksの記述をコメントアウト
application.htmlファイル中のturbolinksの記述を削除
application.jsファイル中のturbolinksの記述を削除
ーーーーーーーー最後にブラウザでjqueryが正しく動作するか確認しておきましょう。
なんでも良いのでjsファイルに以下を記述し、ブラウザのコンソールをみてみましょう!
コンソール画面はchromeの検証ツールを使用すれば確認できます。
(今回は確認用なのでapplication.jsに記述します)application.js$(function(){ console.log("OK") })下の画像のようにOKと表示が出ていれば正しく動作しています。
以上でアプリ作成時の一通りの作業は終了になります。
間違いあれば指摘いただけると助かります。
- 投稿日:2020-05-26T12:26:44+09:00
【初心者向け】Herokuでデプロイしたら code=H10 desc="App crashed" とか言われた時の解決法
環境
- Ruby 2.5.1
- Rails 5.2.3
エラー内容
Herokuで初めてデプロイした際に
「Application error」が解決できずに時間を取りましたので備忘録。デプロイは「Verifying deploy... done.」
と成功しているようなのに、ブラウザが添付画像のようになって開かない...?なんで...?エラー原因を特定したい
ターミナル$ heroku logs --tail言われた通り実行してみる。
すると大量のコードが...
ただ、最後に目立つエラーコードが。ターミナルheroku[router]: at=error code=H10 desc="App crashed" method=GET path="/" host=XXXX.herokuapp.com request_id=82c4af6d-f12b-4348-a654-1238f7b24e67 fwd="60.86.220.52" dyno= connect= service= status=503 bytes= protocol=https先人たちの知恵をお借りする
ググってみるとこちらの記事を見つけました。
https://qiita.com/Oakbow/items/1565922ddcdea0ce9ab5%E3%80%80ターミナル$ heroku restart一通り実践してみますが解決せず?
改めてエラーコードを見てみる
errorが赤くなるのでそこばかりに目がいってしまいましたが、
全部見るとずっと上の方に何かが書いてある!ターミナルapp[web.1]: bundler: failed to load command: puma (/app/vendor/bundle/ruby/2.5.1/bin/puma) app[web.1]: Errno::ENOENT: No such file or directory @ rb_sysopen - tmp/pids/server.pidtmp/pids/server.pidというファイルがないと怒られました。
そもそもなぜtmp/pids/server.pidが必要?
puma.rbを見てみたら「pidfile」というのが指定されている?
config/puma.rb# Specifies the `pidfile` that Puma will use. pidfile ENV.fetch("PIDFILE") { "tmp/pids/server.pid" }ちょっと調べてみるとRails6から追加された新機能とのことですが、
特に指定なく使えている人もいるので試しにコメントアウト。⇒ 再度デプロイを試みてみると無事成功!
結論
特にデプロイ時はエラーログが大量ですが思わぬところにヒントがありますので
一通り目を通した方が良いですね。pumaの働きについてはまだまだ勉強していきます?
- 投稿日:2020-05-26T12:12:31+09:00
【Ruby/Rails】クラスでユニークな値を設定する
rubocopでは、loop + break を推奨されます。
loop + break版
class SomeClass def set_state return true if state.present? self.state = loop do random_str = SecureRandom.urlsafe_base64 break random_str unless self.class.exists?(state: random_str) end end endwhile版
class SomeClass def set_state return true if state.present? begin self.state = SecureRandom.urlsafe_base64 end while self.class.exists?(state: state) end end
- 投稿日:2020-05-26T11:54:16+09:00
#Ruby の正規表現置換でマッチ結果に対してメソッド実行する ( 例: 小文字を大文字にする例 )
- カッコを使ってキャプチャする
- gsub + ブロック記法で マッチ結果に対して好きなメソッドを実行できる
"ab cd ef".gsub(/(ab|ef)/) { "#{$1.upcase}" } # => "AB cd EF"Bad Case
ブロックでないと、複数個のマッチ結果がある場合に、置換結果が全て同じものになってしまう
"ab cd ef".gsub(/(ab|ef)/, "#{$1.upcase}") # "EF cd EF"参考
How to change case of letters in string using RegEx in Ruby - Stack Overflow
Original by Github issue
- 投稿日:2020-05-26T04:03:40+09:00
【Nokogiri】RSSニュースをRubyで扱ってみよう!
こんにちは!
今回は、RSSのニュースをNokogiriで解析して、Rubyで扱うまでをまとめてみようと思います。このまとめでは、Nokogiriというgemを使用して、RSSニュースをRubyで扱えるようにしていきます。
RSSニュースをRubyで扱う事が出来れば、自作キュレーションメディアを作れたりして面白いのでやってみましょう。今回作るもの
以下のゲーム情報を配信しているRSSから、自動でニュースのタイトル部分を取ってきて配列に入れてみようと思います。
https://automaton-media.com/feed/とりあえず今回は、簡単なもの作ってまとめる事にします。
(気が向いたら、キュレーションメディアをRailsで作る!みたいな大掛かりな記事を書こうかな)XMLデータの簡単な説明
RSSニュースのデータを扱う前にXMLについて簡単に説明します。
XML宣言
一番先頭の
<?xml version="1.0" 〜
と書かれている部分は、このファイルがXMLファイルである事を表し、必ず先頭に記述する事になっていますchannelの定義
XML宣言の次の
<channel>
から始まるブロックは、このRSSのチャンネル名を定義したものです。imageの定義
channelのロゴなどが設定されてます。
Nokogiriをインストール
まずはNokogiriをインストールしてみましょう。
gem install nokogiriRailsで使う場合は、Gemfileにnokogiriを追加して下さい。
gem 'nokogiri'Gemfileに追加したらbundle installしましょう。
bundle installプログラム作成
Nokogiriがインストール出来たところで、実際にプログラムを作って行きましょう。
使用ライブラリをrequireする
まず、nokogiri.rbというファイルを作って、先頭に以下の2行を追加して下さい。
nokogiri.rbrequire 'open-uri' # 引数にURLを渡すとURLのデータを取得出来るopenメソッドを使いたいので読み込みます。 require 'nokogiri' # openメソッドで取って来たデータを、nokogiriで取り扱うため読み込みます。openメソッドでニュース記事を読み込もう
nokogiri.rbrequire 'open-uri' require 'nokogiri' url = 'https://automaton-media.com/feed/' # 今回読み込むニュースを設定します。 charset = nil # 読み込んだニュースが文字化けしないよう一度nilにして再設定します。 titles = open(url) do |file| # openメソッドでデータを取得し、ブロックに渡して操作します。 charset = file.charset # charsetに読み込んだファイルのcharsetを設定します。 endopenメソッドで取って来たニュースをNokogiriで検索しよう
nokogiri.rbrequire 'open-uri' require 'nokogiri' url = 'https://automaton-media.com/feed/' charset = nil titles = open(url) do |file| charset = file.charset doc = Nokogiri::XML(file) # openメソッドで取って来たファイルをNokogiriのオブジェクトにします。 channel = doc.at_xpath('//channel') # ファイル内のchannel部分を取得します。 title = channel.xpath('//title') # channel内にあるtitleを全て取得します。 title.map { |title| title.text } # titleのNodeSetからテキスト部分だけ配列に集めます。 end puts titles # タイトルを出力してみましょう。Nokogiriのメソッド説明
- at_xpath 指定したxpathに一致する最初のひとつの要素を返します。(要素をNodeと呼びます)
- xpath 指定したxpathに一致する全ての要素を返します。(要素をNodeSetと呼びます)
実行してみよう
作ったファイルを実行してみます。
ruby nokogiri.rb以下のように配列にまとめたニュースタイトルが出力出来たでしょうか?
よく使うNokogiriの検索メソッド
Nokogiriの検索メソッドは、抜き出したいニュースの要件に合わせてその都度メソッドをググれば良いのですが、よく使うメソッドを以下にまとめときます。
at
doc.at('//title') # 最初に検索ヒットしたNodeを返します。at_xpath
doc.at_xpath('//title') # xpathで検索して最初にヒットしたNodeを返します。xpath
doc.xpath('//title') # xpathでの検索にヒットするNodeSetを返します。at_css
doc.at_css('title') # cssで検索して最初にヒットしたNodeを返します。css
doc.css('title') # cssでの検索にヒットするNodeSetを返します。感想
今回、RSSニュースのタイトルをRubyで配列に集めましたが、ここまで出来れば後はDBに入れてみたり、SlackやLINEに通知してみたり色々出来ると思います。
自分用でまとめサイトを作ってみるというのも面白いかもしれませんね。
- 投稿日:2020-05-26T00:55:42+09:00
【Rialis】単体テストについて
最初に
Rspcを用いてモデルのバリデーションの単体テストを行った際の手順を備忘録として残します。
Rspecとは
RubyやRailsの代表的なテストツールのことで、クラスやメソッド単位でテストするために使用するためのgem。
環境
・Rails 5.0.7.2
・Ruby 2.5.1
・rspec-rails 3.9.0
・factory_bot_rails 5.1.1事前準備
1.rspecとFactory_bot gemの導入
#gemfile group :development, :test test do #開発環境とテスト環境で使用したいのでこの中に記述。 gem 'rspec_rails' gem 'factory_bot_gem' # endgemを記述できたらbundle install
2.rspecファイルの作成と動作確認
ターミナルで、
$ rails g rspec:installコマンド実行後下記のようにファイルが作成されればok。
~~~
create .rspec
create spec
create spec/spec_helper.rb
create spec/rails_helper.rb
~~~ファイルが作成されたのでターミナルで、
--require spec_helper --format documentationこの2文を.specファイルに追加してターミナルでrspec実行コマンドを行い動作確認。
$ bundle exec rspec#ターミナル No examples found. Finished in 0.00031 seconds (files took 0.19956 seconds to load) 0 examples, 0 failuresこのような記述がターミナルで出れば正常にRSpecが起動しているので、この後rspec/下のファイルにテストコードの記述を追加していく。
テスト手順
1.rspecディレクトリ直下にfactoriesフォルダを追加して、その中にusers.rbファイルを作成
users.rbFactoryBot.define do factory :user do nickname {"test"} email {"test@gmail.com"} password {"00000000"} family_name {"test"} first_name {"test"} family_name_kana {"test"} first_name_kana {"test"} birthday {"2000-01-01"} end endfactolesにモデルを先に作成して値を設定しておくことで、specファイルの中で特定のメソッドにより簡単にインスタンスを生成したり、DBに保存したりできるようになります。(1回1回テストを行うための値を入れて、データを作成しないで良くなる。)
user = FactoryBot.build(:user)facutoryBotに対して、buildメソッドまたはcreateメソッドを使い、引数にfacutoriesフォルダ内で追加したモデルを指定することで~s.rbファイルで設定した値となる。
user = FactoryBot.build(:user, email: "")このように、モデルの後にカラム名の値を追加することにより、値を上書きできる。
2テストの記述を追加
specディレクトリにmodelsフォルダを生成。その中に各モデルのファイルを生成し実際のテストを行う。
user_spec.rbrequire 'rails_helper' describe User do describe 'registrations#create' do it "nicknameが空欄の場合,登録できないこと" do user = FactoryBot.build(:user, nickname: "" ) user.valid? expect(user.errors[:nickname]).to include("を入力してください") #include("can't be blank")が正しい。日本語に変換されているため左のような形にしている。 end endこれで1つのテストのまとまりとなる。
1行目で、 spec/rails_helper.rb を読み込むよう設定。
2,3行目、describe直後のdo~endのまとまりが式を表す。ユーザーモデルのregistrations#createアクションのテスト式ということ。
it直後は動作するテストコードのまとまりを表し、itの後に続く""の中にはその説明を書きます。(exampleと呼ぶ)
次行でハッシュを生成して値をuser変数に代入。
ここでFactoryBotメソッドを使用。buildの引数にモデル名を指定して、nicknameカラムのみ値を上書き。
FactoryBotメソッドを使用すると下記は同じ値となる。usre_spec.rbit "nicknameが空欄の場合,登録できないこと" do user = User.new(nickname: "", email: "test@gmail.com", password: "00000000", family_name: "test", first_name: "test", famliy_name_kana: "test", first_name_kana: "test", birthday: "2020-01-01") #gem未使用 user = FactoryBot.build(:user, nickname: "" ) #FactoryBot使用時毎回値をハッシュを生成しないようにFactoryBotmを使用する。
値を代入したuser変数に.valid?メソッドを使用。
ActiveRecord::Baseを継承しているクラスのインスタンスを保存する際に「バリデーションにより保存ができないか?」を確認。
Userモデルには今回全てのカラムに対して、presence: trueがバリデーションされています。expect(X).to eq Yをしようして、xの部分に入れた式の値がYの部分の値と等しいかを確認。
ここではerrorsメソッドを利用。valid?メソッドの返り値はtrue/falseだが、valid?メソッドを利用したインスタンスvalid?メソッドを利用したインスタンスに対してerrorsメソッドを利用すると、バリデーションにより保存ができない状態である場合なぜできないのかを確認することができる。今回であれば、nicknameカラムに値が入っていないので、”cant be blank” と出る。
includeマッチャを使用して、引数にとった値がexpectの引数である配列に含まれているかをチェック。
今回の場合、「nicknameが空の場合はcan't be blankというエラーが出るということがわかっているため、include("can't be blank")のように書く。実際にその通りになればこちらのエクスペクテーションは通過し、
"nicknameが空欄の場合,登録できないこと"というテストがこれで完了。あとはひたすら単体テストの記述を追加して、1つ1つテストを行なっていく。
user_rspec.rbrequire 'rails_helper' describe User do describe 'registrations#create' do it "nicknameが空欄の場合,登録できないこと" do user = FactoryBot.build(:user, nickname: "" ) user.valid? expect(user.errors[:nickname]).to include("を入力してください") #include("can't be blank")が正しい。日本語に変換されているため右のような形にしている。 end it "emailが空欄の場合、登録できないこと" do user = FactoryBot.build(:user, email: "") user.valid? expect(user.errors[:email]).to include("を入力してください") end it "emailに「@」がない場合、登録できないこと" do user = FactoryBot.build(:user, email: "test.gmail.com") user.valid? expect(user.errors[:email]).to include("は不正な値です") end it "重複したメールアドレスの場合、無効である" do user1 = FactoryBot.create(:user) user2 = FactoryBot.build(:user) user2.valid? expect(user2.errors[:email]).to include("はすでに存在します") end it "passwordが空欄の場合、登録ができないこと" do user = FactoryBot.build(:user, password: "" ) user.valid? expect(user.errors[:password]).to include("を入力してください") end it "passwordが6文字以下の場合、登録ができないこと" do user = FactoryBot.build(:user, password: "00000") user.valid? expect(user.errors[:password]).to include("は6文字以上で入力してください") end it "family_nameが空欄の場合、登録できないこと" do user = FactoryBot.build(:user, family_name: "" ) user.valid? expect(user.errors[:family_name]).to include("を入力してください") end it "first_nameが空欄の場合、登録できないこと" do user = FactoryBot.build(:user, first_name: "" ) user.valid? expect(user.errors[:first_name]).to include("を入力してください") end it "family_name_kanaが空欄の場合、登録できないこと" do user = FactoryBot.build(:user, family_name_kana: "" ) user.valid? expect(user.errors[:family_name_kana]).to include("を入力してください") end it "first_name_kanaが空欄の場合、登録できないこと" do user = FactoryBot.build(:user, first_name_kana: "" ) user.valid? expect(user.errors[:first_name_kana]).to include("を入力してください") end it "birthdayが空欄の場合、登録できないこと" do user = FactoryBot.build(:user, birthday: "" ) user.valid? expect(user.errors[:birthday]).to include("を入力してください") end end end上記でuserモデルのバリデーションに対するテストが完成。
参考文献
・https://programming-beginner-zeroichi.jp/articles/61
・https://www.sejuku.net/blog/47847
・https://note.com/syojikishindoi/n/n5019bc64c254
- 投稿日:2020-05-26T00:55:42+09:00
【Ralis】単体テストについて
最初に
Rspcを用いてモデルのバリデーションの単体テストを行った際の手順を備忘録として残します。
Rspecとは
RubyやRailsの代表的なテストツールのことで、クラスやメソッド単位でテストするために使用するためのgem。
環境
・Rails 5.0.7.2
・Ruby 2.5.1
・rspec-rails 3.9.0
・factory_bot_rails 5.1.1事前準備
1.rspecとFactory_bot gemの導入
#gemfile group :development, :test test do #開発環境とテスト環境で使用したいのでこの中に記述。 gem 'rspec_rails' gem 'factory_bot_gem' endgemを記述できたらbundle install
2.rspecファイルの作成と動作確認
ターミナルで、
$ rails g rspec:installコマンド実行後下記のようにファイルが作成されればok。
~~~
create .rspec
create spec
create spec/spec_helper.rb
create spec/rails_helper.rb
~~~ファイルが作成されたのでターミナルで、
--require spec_helper --format documentationこの2文を.specファイルに追加してターミナルでrspec実行コマンドを行い動作確認。
$ bundle exec rspec#ターミナル No examples found. Finished in 0.00031 seconds (files took 0.19956 seconds to load) 0 examples, 0 failuresこのような記述がターミナルで出れば正常にRSpecが起動しているので、この後rspec/下のファイルにテストコードの記述を追加していく。
テスト手順
1.rspecディレクトリ直下にfactoriesフォルダを追加して、その中にusers.rbファイルを作成
users.rbFactoryBot.define do factory :user do nickname {"test"} email {"test@gmail.com"} password {"00000000"} family_name {"test"} first_name {"test"} family_name_kana {"test"} first_name_kana {"test"} birthday {"2000-01-01"} end endfactolesにモデルを先に作成して値を設定しておくことで、specファイルの中で特定のメソッドにより簡単にインスタンスを生成したり、DBに保存したりできるようになります。(1回1回テストを行うための値を入れて、データを作成しないで良くなる。)
user = FactoryBot.build(:user)facutoryBotに対して、buildメソッドまたはcreateメソッドを使い、引数にfacutoriesフォルダ内で追加したモデルを指定することで~s.rbファイルで設定した値となる。
user = FactoryBot.build(:user, email: "")このように、モデルの後にカラム名の値を追加することにより、値を上書きできる。
2テストの記述を追加
specディレクトリにmodelsフォルダを生成。その中に各モデルのファイルを生成し実際のテストを行う。
user_spec.rbrequire 'rails_helper' describe User do describe 'registrations#create' do it "nicknameが空欄の場合,登録できないこと" do user = FactoryBot.build(:user, nickname: "" ) user.valid? expect(user.errors[:nickname]).to include("を入力してください") #include("can't be blank")が正しい。日本語に変換されているため左のような形にしている。 end endこれで1つのテストのまとまりとなる。
1行目で、 spec/rails_helper.rb を読み込むよう設定。
2,3行目、describe直後のdo~endのまとまりが式を表す。ユーザーモデルのregistrations#createアクションのテスト式ということ。
it直後は動作するテストコードのまとまりを表し、itの後に続く""の中にはその説明を書きます。(exampleと呼ぶ)
次行でハッシュを生成して値をuser変数に代入。
ここでFactoryBotメソッドを使用。buildの引数にモデル名を指定して、nicknameカラムのみ値を上書き。
FactoryBotメソッドを使用すると下記は同じ値となる。usre_spec.rbit "nicknameが空欄の場合,登録できないこと" do user = User.new(nickname: "", email: "test@gmail.com", password: "00000000", family_name: "test", first_name: "test", famliy_name_kana: "test", first_name_kana: "test", birthday: "2020-01-01") #gem未使用 user = FactoryBot.build(:user, nickname: "" ) #FactoryBot使用時毎回値をハッシュを生成しないようにFactoryBotmを使用する。
値を代入したuser変数に.valid?メソッドを使用。
ActiveRecord::Baseを継承しているクラスのインスタンスを保存する際に「バリデーションにより保存ができないか?」を確認。
Userモデルには今回全てのカラムに対して、presence: trueがバリデーションされています。expect(X).to eq Yをしようして、xの部分に入れた式の値がYの部分の値と等しいかを確認。
ここではerrorsメソッドを利用。valid?メソッドの返り値はtrue/falseだが、valid?メソッドを利用したインスタンスvalid?メソッドを利用したインスタンスに対してerrorsメソッドを利用すると、バリデーションにより保存ができない状態である場合なぜできないのかを確認することができる。今回であれば、nicknameカラムに値が入っていないので、”cant be blank” と出る。
includeマッチャを使用して、引数にとった値がexpectの引数である配列に含まれているかをチェック。
今回の場合、「nicknameが空の場合はcan't be blankというエラーが出るということがわかっているため、include("can't be blank")のように書く。実際にその通りになればこちらのエクスペクテーションは通過し、
"nicknameが空欄の場合,登録できないこと"というテストがこれで完了。あとはひたすら単体テストの記述を追加して、1つ1つテストを行なっていく。
user_rspec.rbrequire 'rails_helper' describe User do describe 'registrations#create' do it "nicknameが空欄の場合,登録できないこと" do user = FactoryBot.build(:user, nickname: "" ) user.valid? expect(user.errors[:nickname]).to include("を入力してください") #include("can't be blank")が正しい。日本語に変換されているため右のような形にしている。 end it "emailが空欄の場合、登録できないこと" do user = FactoryBot.build(:user, email: "") user.valid? expect(user.errors[:email]).to include("を入力してください") end it "emailに「@」がない場合、登録できないこと" do user = FactoryBot.build(:user, email: "test.gmail.com") user.valid? expect(user.errors[:email]).to include("は不正な値です") end it "重複したメールアドレスの場合、無効である" do user1 = FactoryBot.create(:user) user2 = FactoryBot.build(:user) user2.valid? expect(user2.errors[:email]).to include("はすでに存在します") end it "passwordが空欄の場合、登録ができないこと" do user = FactoryBot.build(:user, password: "" ) user.valid? expect(user.errors[:password]).to include("を入力してください") end it "passwordが6文字以下の場合、登録ができないこと" do user = FactoryBot.build(:user, password: "00000") user.valid? expect(user.errors[:password]).to include("は6文字以上で入力してください") end it "family_nameが空欄の場合、登録できないこと" do user = FactoryBot.build(:user, family_name: "" ) user.valid? expect(user.errors[:family_name]).to include("を入力してください") end it "first_nameが空欄の場合、登録できないこと" do user = FactoryBot.build(:user, first_name: "" ) user.valid? expect(user.errors[:first_name]).to include("を入力してください") end it "family_name_kanaが空欄の場合、登録できないこと" do user = FactoryBot.build(:user, family_name_kana: "" ) user.valid? expect(user.errors[:family_name_kana]).to include("を入力してください") end it "first_name_kanaが空欄の場合、登録できないこと" do user = FactoryBot.build(:user, first_name_kana: "" ) user.valid? expect(user.errors[:first_name_kana]).to include("を入力してください") end it "birthdayが空欄の場合、登録できないこと" do user = FactoryBot.build(:user, birthday: "" ) user.valid? expect(user.errors[:birthday]).to include("を入力してください") end end end上記でuserモデルのバリデーションに対するテストが完成。
参考文献
・https://programming-beginner-zeroichi.jp/articles/61
・https://www.sejuku.net/blog/47847
・https://note.com/syojikishindoi/n/n5019bc64c254