20200526のRubyに関する記事は18件です。

カレンダー作成問題(たのしい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 >という日付で値が渡されるようです。


何か改善点等あればお教えください。

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

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

Rubyの参照先を確認する

まずは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

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

Kinx 要素編 - 演算子オーバーライド

演算子オーバーライド

はじめに

「見た目は JavaScript、頭脳(中身)は Ruby、(安定感は AC/DC)」 でお届けしているスクリプト言語 Kinx。ライブラリ… ではないですが、ライブラリ作成で便利な機能。

今回は演算子オーバーライドです。

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 + r2r1 が破壊されるのは直感的ではないので、新しいオブジェクトを返すようにします。また、直接破壊的に操作する別のメソッドを用意してきます。ついでにオブジェクトのクローンをつくる 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 が働くようになります。

では、また。

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

オレオレRailsコーディング規約

あらすじ

吾輩は末端エンジニアである。名前はしょった。先日、急遽あるRailsアプリの引継ぎ業務に駆り出された。吾輩はここで始めて、アプリのコードを見た。しかもこのコードがまあ追いにくい。ブチギレ寸前である。

アンチパターン

というわけで本題。
先に、ここでいうアンチパターンというのは一般に流布されているそれとはまるで別物であるということは先にお伝えしておきたい。あくまで個人的なベストプラクティスであり、引き継ぐならこうなっていて欲しい(欲しかった)という願望である。

1. 1つのディレクトリ内に無駄にファイルが多い

今、ここに猫がいる。猫はかわいい。神は猫をこう定義した。

./cat.rb
class Cat
  def comment
    puts "かわいい"
  end
end

猫は猫耳と尻尾、そして肉球でできている。
神はかわいい猫をこう定義し直した。

./cat.rb
class Cat
  def initialize
    @parts  = [
      CatEar.new,
      CatTail.new,
      CatPad.new
    ]
  end

  def comment
    @parts.each(&:comment)
  end
end
./cat_ear.rb
class CatEar
  def comment
    puts "猫耳かわいい"
  end
end
./cat_tail.rb
class CatTail
  def comment
    puts "しっぽもかわいい"
  end
end
./cat_pad.rb
class 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.rb
class 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.rb
class Cat
  class Tail
    def comment
      puts "しっぽもかわいい"
    end
  end
end
./cat/pad.rb
class 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.rb
class 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.rb
class 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.rb
class Hoge < ApplicationRecord
  attribute :name, :string
end
views/hoge.html.erb
<%= Hoge.first.read_attribute(:name) %>

この実装は問題なく動くだろう。
ここで、nameカラムが null または空文字だった場合、 'Unknown' と返そう、となった。

models/hoge.rb
class 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.rb
class 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.rb
class 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文の後ろの中括弧はこうだ〜みたいなどっちでもいい議論は聞くけれど、こういう構造で書いた方がいいよ!とか、こうすれば管理がしやすいよ!みたいな議論の方が大事だと思うので、もっと盛んに議論してください。よろしくおねがいします。

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

renderとredirect_toの違い

render

次に指定するViewを指定している。
今持っている変数で新しいViewを構成する。

redirect_to

次に指定するメソッドを指定している。
ブラウザに対して指定したメソッドにHTTPリクエストを送るように司令する。
リクエストが二回発生してしまうことになる。

例:
=> GET home/show
<= redirect_to
=> Get home/index
<= home/index

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

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日

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

Qiitaはじめました。

概要

4月から新社会人になった駆け出しエンジニアです。
研修も全面リモートワークということもあり、何かはじめてみようと思い、自分がやってみた(やらなきゃいけない)ことをQiitaにアウトプットしていくことにしました。

初投稿は自分のモチベーションのために、技術的な内容ではなく、これからやってみたいこととか書いていければと思います。

これから何するの?

やっていきたいことは多々あるんですけど、なかでもバックエンドにフォーカスしていければと思います。(とか言いながら最近はVueに挑戦している)

と言うのも、就活をしていたときぐらいから何となくバックエンドの技術に興味があり、ちょっと調べているうちにやってみたいと思うようになったからで。

また、会社の配属先の部署で自動化やネットワークプログラマビリティなどのような高度な技術が必要とのことで、そこらへんに関連するバックエンドを中心に勉強していけたらと思っております。

具体的には?

具体的に取り組んでみたい内容を簡単に書き出してみました。

・Ruby on Rails(Javaの復習がてら)
・AWSのLambdaとかGreengrassとラズパイ
・Dockerでコンテナ型の仮想化を理解
・Kubernetes(ムズい)
などなど。。。

挙げだしたらキリがないです。。。
若干独学で出来るのか不安なところはありますが、会社に詳しい人がいると思うので、色々手を動かしながらやっていきたいです。

おい、資格の勉強はどうした?

資格て応用情報のことですよね。結論としては多分受けないです。
その代わり(?)といっては何ですが、今年から始まったCiscoのDevNet認定に今興味があり、色々調べています。

というのも、カリカリ勉強するよりパチパチコード書いてアウトプットしていったほうが圧倒的にスキルになるなと最近感じ始めたからで。

DevNet認定ではネットワークの基礎知識だけではなく、冒頭に申し上げたネットワークプログラマビリティや自動化が範囲としてあり、Python/Git/APIなどの幅広いモダンな開発知識が求められます。
個人的にIoT/IoEやDevOpsに興味があるので、ピッタリな内容かなと思いました。

お前、英語のこと忘れてるだろ

はい、最近気付きました。
チリツモを信じて毎朝Duoのチャプターひとつやるようにしてます。。。頑張ります。。。

最後に一言

これから長い長いエンジニア人生がはじまりますが、クリエイティブな思考を忘れないようにしたいものです。

ということで、これから触ってみた技術や知見をQiitaに書き留めていきたいと思います。
はじめはクオリティ低いと思いますが、よろしくお願いします。

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

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

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

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

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

データベースの削除&作成、テーブルに初期データが追加

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

Rails deviseのエラーメッセージ を日本語化するまでの道のり

Rails初学者です。今回はRailsのdeviseログイン機能でのエラーメッセージを日本語化したく、少々つまずいたので覚書きとしていきます。

前提

  • deviseのログイン機能が実装できている

gemインストール

devise日本語用のgemを追加

Gemfile
 gem 'devise-i18n'
 gem 'devise-i18n-views'
$ bundle install

deviseの日本語用ファイルを生成

$ 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.rb
module リポジトリ名
  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で起動し、エラー文を表示させてみる

 スクリーンショット 2020-05-26 17.37.50.png

日本語化されたものの、一部英語の文が、、
deviseのエラーとマッチする日本語の文をdeviseが取得できていないようでした

エラー文の追加

devise.views.ja.ymlの編集

$ rails g devise:views:locale jaで追加されたdevise.views.ja.ymlファイルにメッセージが保存されているので編集する
デフォルトでは以下の用に記述されている

config/locales/devise.views.ja.yml
ja:
  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.yml
ja:
  activerecord:
    attributes:
      user:
        current_password: "現在のパスワード"
        email: "メールアドレス"
        password: "パスワード"
        password_confirmation: "確認用パスワード"
        remember_me: "ログインを記憶"
    models:
      user: "ユーザー"

### ここから
    errors:
      models:
        user:
          attributes:
            email:
              blank: "を入力してください"
            password:
              blank: "を入力してください"
            name:
              blank: "を入力してください"
### ここまで

と編集し、エラー文を表示させてみると

スクリーンショット 2020-05-26 17.59.46.png

上手くできました

ついでにja.errors.messages.not_saved.oneなどを空欄one: ""にカスタマイズして、エラーカウント文を削除してみたりなど、、

スクリーンショット 2020-05-26 18.09.53.png

ざっとdeviseのエラー文の日本語化はこんな感じになりました

参考にした文献がこちら
【Rails5】Devise-i18nで日本語化する
Devise日本語化後の「translation missing」に対処する
github devise-i18n

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

Rails でアプリを作成する際の初期作業まとめ

はじめに

railsでアプリを作成するときに毎回のように設定していることがあるので、初期の設定をまとめておきます。
作業は以下の流れになります。

・アプリ作成
・データベース作成
・gem導入
・Haml導入
・テーブル作成
・コントローラ、ビュー作成
・jquery導入

アプリ作成

まずはターミナルからコマンド入力でアプリを作成します。

railsのバージョンは使い慣れているものを指定するのが良いかと思います。
(今回はバージョン5.2.3、データベースはmysplを使用)
アプリを保存するディレクトリで以下を実行します。(今回はアプリ名はsample)

ターミナル
$ rails _5.2.3_ new sample -d mysql

作成したアプリのディレクトリに移動しておきます。

ターミナル
$ cd sample

データベース作成

データベースを作成します。

ターミナル
$ rails db:create

developmentとtest用のデータベースが作成されるはずです。
(Sequel Pro等のアプリで確認してみると良いでしょう)

gem導入

開発に使いそうなgemを先に入れておきます。
以下の4つはとりあえず入れておいて良いかと思います。
Gemfileに以下を追記しましょう。

Gemfile
group :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 install

Haml導入

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

下の画像のような感じでマイグレーションファイルとモデルのファイルなんかが作成されるはずです。
6b09d101921453a7b9b21fad5bec9fe1.png

■ 続いてマイグレーションファイルを編集します。
作成するカラムの情報を記載します。
今回はカラム名をcontent, データ型はstring, null規制ありとしています。
マイグレーションファイルに追記してあげましょう!
(マイグレーションファイルはdb/migrateの中にあります)

xxxxxxxxxxxxxx_create_posts.rb
class 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

下の画像のようにテーブルが作成されればOKです。
f719a52b32f13a6df5d62f5569dd5d00.png

コントローラ、ビュー作成

■ まずはコントローラを作成します。
(これがないとアプリにならない…)
今回はpostsコントローラーを作成します。

ターミナル
$ rails g controller posts

コマンドを実行するといろいろファイルが作成されるかと思いますが、とりあえずposts_controller.rbだけ注目しておけばOKです。
(ここでpostsコントローラ用のビューフォルダも一緒に作られてます)
9be2237cae0ffacc53580744ec6464be.png

■ コントローラ編集
コントローラにアクションを記載しましょう。
(ファイルはapp/controllersの中)

posts_controller.rb
class PostsController < ApplicationController
  def index
  end
end

とりあえずindexアクションのみ記載しておきます。
今回はインスタンス変数等の中身の記述は割愛します。

■ ルーティング設定
続いてコントローラを使用するためにルーティングを設定しておきましょう。
(ファイルはconfigの中)

routes.rb
Rails.application.routes.draw do
  root "posts#index"
  resources :posts, only: :index
end

postsコントローラのindexアクションを使えるようにしています。
また、ルートパスも変更しておきましょう。(これがないとルートパスにアクセスした時にrailsが用意してあるデフォルトページが表示されてしまいます。)

■ ルーティング確認
ルーティングが正しく設定されているか確認してみましょう

ターミナル
$ rails routes

下の画像のようにpostsコントローラのindexアクションが設定されていればOKです。
e901ca940d1239356297f31fbf5a6ea5.png

■ ビューファイル作成
ルーティングを設定したらビューファイルを用意しておきましょう。
(これがないとリクエストを飛ばしてもファイルがないためエラーが出ます)

app/views/postsの中にindex.html.hamlを作成します。
中身はわかりやすいように何か書いておきましょう。

index.html.haml
%h1 インデックスページです

■ ブラウザで確認
ビューファイルを作成したらうまくいってるか実際にブラウザで確認してみましょう。
ローカルサーバーを立ち上げます。

ターミナル
$ rails s

ChromeなどのブラウザでURLに localhost:3000/ と入力してアクセスしてみましょう。
下の画像のように用意したビューファイルどおりに表示されていればOKです。

1f78058672dcf3d9d948b9cffe64c8be.png

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と表示が出ていれば正しく動作しています。
5d3352fcaeb81a05709680d955d6dccc.png

以上でアプリ作成時の一通りの作業は終了になります。
間違いあれば指摘いただけると助かります。

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

【初心者向け】Herokuでデプロイしたら code=H10 desc="App crashed" とか言われた時の解決法

環境

  • Ruby 2.5.1
  • Rails 5.2.3

エラー内容

Herokuで初めてデプロイした際に
「Application error」が解決できずに時間を取りましたので備忘録。

デプロイは「Verifying deploy... done.」
と成功しているようなのに、ブラウザが添付画像のようになって開かない...?なんで...?

Application error

エラー原因を特定したい

ターミナル
$ 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.pid

tmp/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の働きについてはまだまだ勉強していきます?

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

【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
end

while版

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

#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

https://github.com/YumaInaura/YumaInaura/issues/3169

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

【Nokogiri】RSSニュースをRubyで扱ってみよう!

こんにちは!
今回は、RSSのニュースをNokogiriで解析して、Rubyで扱うまでをまとめてみようと思います。

このまとめでは、Nokogiriというgemを使用して、RSSニュースをRubyで扱えるようにしていきます。
RSSニュースをRubyで扱う事が出来れば、自作キュレーションメディアを作れたりして面白いのでやってみましょう。

今回作るもの

以下のゲーム情報を配信しているRSSから、自動でニュースのタイトル部分を取ってきて配列に入れてみようと思います。
https://automaton-media.com/feed/

とりあえず今回は、簡単なもの作ってまとめる事にします。
(気が向いたら、キュレーションメディアをRailsで作る!みたいな大掛かりな記事を書こうかな)

XMLデータの簡単な説明

RSSニュースのデータを扱う前にXMLについて簡単に説明します。

ゲームRSSオリジナル説明用.png

XML宣言

一番先頭の<?xml version="1.0" 〜と書かれている部分は、このファイルがXMLファイルである事を表し、必ず先頭に記述する事になっています

channelの定義

XML宣言の次の<channel>から始まるブロックは、このRSSのチャンネル名を定義したものです。

imageの定義

channelのロゴなどが設定されてます。

Nokogiriをインストール

まずはNokogiriをインストールしてみましょう。

gem install nokogiri

Railsで使う場合は、Gemfileにnokogiriを追加して下さい。

gem 'nokogiri'

Gemfileに追加したらbundle installしましょう。

bundle install

プログラム作成

Nokogiriがインストール出来たところで、実際にプログラムを作って行きましょう。

使用ライブラリをrequireする

まず、nokogiri.rbというファイルを作って、先頭に以下の2行を追加して下さい。

nokogiri.rb
require 'open-uri' # 引数にURLを渡すとURLのデータを取得出来るopenメソッドを使いたいので読み込みます。
require 'nokogiri' # openメソッドで取って来たデータを、nokogiriで取り扱うため読み込みます。

openメソッドでニュース記事を読み込もう

nokogiri.rb
require 'open-uri'
require 'nokogiri'

url = 'https://automaton-media.com/feed/' # 今回読み込むニュースを設定します。

charset = nil # 読み込んだニュースが文字化けしないよう一度nilにして再設定します。
titles = open(url) do |file| # openメソッドでデータを取得し、ブロックに渡して操作します。
  charset = file.charset # charsetに読み込んだファイルのcharsetを設定します。
end

openメソッドで取って来たニュースをNokogiriで検索しよう

nokogiri.rb
require '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

以下のように配列にまとめたニュースタイトルが出力出来たでしょうか?

スクリーンショット 2020-05-24 4.39.09.png

よく使う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に通知してみたり色々出来ると思います。
自分用でまとめサイトを作ってみるというのも面白いかもしれませんね。

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

【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' #

end

gemを記述できたら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.rb
FactoryBot.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

end

factolesにモデルを先に作成して値を設定しておくことで、specファイルの中で特定のメソッドにより簡単にインスタンスを生成したり、DBに保存したりできるようになります。(1回1回テストを行うための値を入れて、データを作成しないで良くなる。)

user = FactoryBot.build(:user)

facutoryBotに対して、buildメソッドまたはcreateメソッドを使い、引数にfacutoriesフォルダ内で追加したモデルを指定することで~s.rbファイルで設定した値となる。

user = FactoryBot.build(:user,  email: "")

このように、モデルの後にカラム名の値を追加することにより、値を上書きできる。

2テストの記述を追加

specディレクトリにmodelsフォルダを生成。その中に各モデルのファイルを生成し実際のテストを行う。

user_spec.rb
require '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.rb
it "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.rb
require '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

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

【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' 

end

gemを記述できたら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.rb
FactoryBot.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

end

factolesにモデルを先に作成して値を設定しておくことで、specファイルの中で特定のメソッドにより簡単にインスタンスを生成したり、DBに保存したりできるようになります。(1回1回テストを行うための値を入れて、データを作成しないで良くなる。)

user = FactoryBot.build(:user)

facutoryBotに対して、buildメソッドまたはcreateメソッドを使い、引数にfacutoriesフォルダ内で追加したモデルを指定することで~s.rbファイルで設定した値となる。

user = FactoryBot.build(:user,  email: "")

このように、モデルの後にカラム名の値を追加することにより、値を上書きできる。

2テストの記述を追加

specディレクトリにmodelsフォルダを生成。その中に各モデルのファイルを生成し実際のテストを行う。

user_spec.rb
require '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.rb
it "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.rb
require '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

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