20191124のRubyに関する記事は22件です。

TataraというRubyで型を使えるライブラリを作ってみた

はじめに

この記事は最近僕が作っているRubyで型を使えるライブラリの紹介記事です。

対象読者としては、Rubyで簡単に型のようなものを使ってみたい人、またはC++でのRuby拡張を作ってみたい人を想定しています。

Tataraの紹介をしつつ、簡単なC++でのRuby拡張実装についても触れていきます。

作ったもの

TataraというRubyで簡単な型を使えるようにするRubyExtensionを作りました。RubyGemsにもリリースしており、v0.2.0が現在リリースされている最新バージョンになります。

v0.2.0まではRiceというRubyExtensionを簡単にC++で使用していました。しかし、GCへの管理追加が少しややこしかったり、RiceでC++のコードをRuby向けにラップしているため処理が遅くなったりするという問題がありました。そこで次期リリースではC++のみでの実装に書き直し、GCへの管理追加や処理速度向上を図っています。

ちなみに、次にリリースするv0.3.0は11月中にリリースする予定で実装を進めています。

サンプルコード

例えば、Tataraを使うことで以下のようなコードを実行できます。

require 'tatara'

@int_container = Tatara::IntVector.new

(1..10).each{|i| @int_container << i}
# Insert 1..10 to @int_container

@int_container.map{|i|
    puts i
    # 1..10 value is shown
}

@int_containerは整数値のみが保存できるVectorになります。浮動小数点数値なども代入できますが、整数値に暗黙的に変換されます。

@int_container = Tatara::IntVector.new
@int_container << 4.2
# => Push 4

ちなみに、文字列を保存しようとするとエラーになります。

@int_container = Tatara::IntVector.new
@int_container << "42"
# => Error!

このようにTataraでは簡単な型が使えるようにしてあります。

作ろうと思ったきっかけ

Ruby自体は非常に書きやすく、楽しくコーディングができる素晴らしい言語です。

ですが、元々静的型付け言語(C/C++など)を触ることが多かったため「Rubyにも型のようなものが欲しいなぁ……」と思うこともありました。

そんな折、RiceextppなどでRubyExtensionを作れそうだとわかり、「面白そうなので作ってみよう
!」となり作ってみました。

やったこと

開発初期のころはRiceでのRubyExtension作成についてドキュメントや記事などを漁りに漁っていました。で、Riceで実装できそうなことがわかったので少しづつ実装を進めていった感じですね。

最初はIntegerやFloatなどを実装し、その後VectorやMapなどを実装していきました。

v0.1.0をリリースした辺りでGCへの管理追加などがややこしいことが判明し、Riceを外す方法を模索し始めました。extppなどでのRubyExtension実装ついても調べたりしました。

結果として、Ruby自体の実装コードなどを読みつつ、以下の記事を参考にRiceを外していくことにしました。そのおかげでRubyの実装なども読むことができたので良い経験が積めたと思います。

Riceでの実装

Riceでは以下のようにC++で作成したクラスをラップしてRuby向けに使用できるようにできます。

#include <rice/Data_Type.hpp>
#include <rice/Constructor.hpp>

using namespace Rice;

class Integer {
    int value = 0;

    public:
        Integer();
        ~Integer();
        int assignment(const int var);
        int return_value();
};

Integer::Integer() {}

Integer::~Integer(){}

int Integer::assignment(const int var) {
    return this->value = var;
}

int Integer::return_value() {
    return this->value;
}

extern "C" {
    void Init_tatara() {
        Module rb_mTatara = define_module("Tatara");

        Data_Type<Integer> rbcInteger = define_class_under<Integer>(rb_mTatara, "Integer")
            .define_constructor(Constructor<Integer>())
            .define_method("value", &Integer::return_value)
            .define_method("value=", &Integer::assignment);
    }
}

RiceではC++で定義したクラスをData_TypeでRubyのクラスへとラップしてくれます。またモジュール名などはModule rb_mTatara = define_module("Tatara");のように作成することができます。

Module rb_mTatara = define_module("Tatara");

Data_Type<Integer> rbcInteger = define_class_under<Integer>(rb_mTatara, "Integer")

上記のコードではTataraモジュール内にIntegerクラスを作成しています。

.define_constructor(Constructor<Integer>())

またこのコードではC++のIntegerクラスのコンストラクタをnewとして利用しています。

あとは、以下のように必要なメソッドをdefine_methodでクラスに追加しています。

.define_method("value", &Integer::return_value)
.define_method("value=", &Integer::assignment);

define_methodはメソッド名と実行するメソッドを引数として渡すことで、C++側で定義したメソッドをRuby側で使用することができます。

これにより、以下のようなRubyのコードが実行できます。

@integer = Tatara::Integer.new
@integer.value = 42
puts @integer.value
# => 42

このようにRiceを使うことでC++で定義したクラスをRubyで使用することができます。

ちなみに、テンプレートなどを使う場合は以下のように型を指定する必要があります。

Data_Type<CppArray<int>> rb_cIntArray = define_class_under<CppArray<int>>(rb_mTatara, "IntArray")
    .define_constructor(Constructor<CppArray<int>>());

C++への実装書き直し

先ほどRiceで書いていたコードは以下のように書き直すことができます。

#include <ruby.h>

class Integer {
    int value = 0;

    public:
        Integer();
        ~Integer();
        int assignment(const int var);
        int return_value();
};

Integer::Integer() {}

Integer::~Integer(){}

int Integer::assignment(const int var) {
    return this->value = var;
}

int Integer::return_value() {
    return this->value;
}

static Integer *getInteger(VALUE self) {
    Integer *ptr;
    Data_Get_Struct(self, Integer, ptr);
    return ptr;
}

static void wrap_int_free(Integer *ptr) {
    ptr->~Integer();
    ruby_xfree(ptr);
}

static VALUE wrap_int_alloc(VALUE klass) {
    void *p = ruby_xmalloc(sizeof(Integer));
    p = new Integer;
    return Data_Wrap_Struct(klass, NULL, wrap_int_free, p);
}

static VALUE wrap_int_init(VALUE self) {
    Integer *p = getInteger(self);
    p = new Integer;
    return Qnil;
}


static VALUE wrap_int_return_value(VALUE self) {
    const int value = getInteger(self)->return_value();
    VALUE result = INT2NUM(value);
    return result;
}

static VALUE wrap_int_assignment(VALUE self, VALUE value) {
    const int v = NUM2INT(value);
    const int r = getInteger(self)->assignment(v);
    VALUE result = INT2NUM(r);
    return result;
}

extern "C" {
    void Init_tatara() {
        VALUE mTatara = rb_define_module("Tatara");

        VALUE rb_cInteger = rb_define_class_under(mTatara, "Integer", rb_cObject);

        rb_define_alloc_func(rb_cInteger, wrap_int_alloc);
        rb_define_private_method(rb_cInteger, "initialize", RUBY_METHOD_FUNC(wrap_int_init), 0);
        rb_define_method(rb_cInteger, "value", RUBY_METHOD_FUNC(wrap_int_return_value), 0);
        rb_define_method(rb_cInteger, "value=", RUBY_METHOD_FUNC(wrap_int_assignment), 1);
    }
}

C++のクラスをそのままRuby側で呼び出すことはできないので、以下のような関数を作成し、Ruby側から呼び出せるようにしています。

static Integer *getInteger(VALUE self) {
    Integer *ptr;
    Data_Get_Struct(self, Integer, ptr);
    return ptr;
}

static void wrap_int_free(Integer *ptr) {
    ptr->~Integer();
    ruby_xfree(ptr);
}

static VALUE wrap_int_alloc(VALUE klass) {
    void *p = ruby_xmalloc(sizeof(Integer));
    p = new Integer;
    return Data_Wrap_Struct(klass, NULL, wrap_int_free, p);
}

static VALUE wrap_int_init(VALUE self) {
    Integer *p = getInteger(self);
    p = new Integer;
    return Qnil;
}

モジュールはrb_define_moduleで作成でき、以下のようにTataraというモジュールを作成しています。

VALUE mTatara = rb_define_module("Tatara");

また、以下のコードではIntegerクラスをTataraモジュール内に作成し、その基底クラスにObjectクラスを使用しています。

VALUE rb_cInteger = rb_define_class_under(mTatara, "Integer", rb_cObject);

Rubyのソースコード内ではVALUE型を使うことでインスタンスの値などを取得することができています。

getIntegrではインスタンス自身からC++のクラスのポインタを取得し、そのポインタを使ってクラスで定義されているメソッドを実行しています。
例えば以下のようにreturn_valueメソッドを呼び出すことができます。

static VALUE wrap_int_return_value(VALUE self) {
    const int value = getInteger(self)->return_value();
    VALUE result = INT2NUM(value);
    return result;
}

またwrap_int_freeではRubyのGCでメモリが解放される際の処理を実装しています。

static void wrap_int_free(Integer *ptr) {
    ptr->~Integer();
    ruby_xfree(ptr);
}

wrap_int_allocはRubyのコードで新しくnewでインスタンスを作成した際のアロケーションを実装しています。

static VALUE wrap_int_alloc(VALUE klass) {
    void *p = ruby_xmalloc(sizeof(Integer));
    p = new Integer;
    return Data_Wrap_Struct(klass, NULL, wrap_int_free, p);
}

rb_define_alloc_funcでアロケーション時に実行する関数を指定できます。

rb_define_alloc_func(rb_cInteger, wrap_int_alloc);

wrap_int_initinitializeを実装しています。

static VALUE wrap_int_init(VALUE self) {
    Integer *p = getInteger(self);
    p = new Integer;
    return Qnil;
}

最後に、以下のようにC++で定義されたクラスのメソッドを実行するラップ関数を作成します。

static VALUE wrap_int_return_value(VALUE self) {
    const int value = getInteger(self)->return_value();
    VALUE result = INT2NUM(value);
    return result;
}

static VALUE wrap_int_assignment(VALUE self, VALUE value) {
    const int v = NUM2INT(value);
    const int r = getInteger(self)->assignment(v);
    VALUE result = INT2NUM(r);
    return result;
}

INT2NUM()NUM2INT()はCとRubyとの間でデータの変換を行う処理になります。この処理を間に挟むことでRuby内の変数の値などをC++側に渡すことができます。

あとは、作成したラップ関数を以下のようにメソッドとして追加します。

rb_define_method(rb_cInteger, "value", RUBY_METHOD_FUNC(wrap_int_return_value), 0);
rb_define_method(rb_cInteger, "value=", RUBY_METHOD_FUNC(wrap_int_assignment), 1);

rb_define_methodは第一引数にメソッドを追加するクラスのVALUEを渡します。第二引数にはメソッド名、第三引数には実行する関数を渡します。

RUBY_METHOD_FUNCでは渡している関数ポインタをRubyのメソッドとしてキャストしています。

第四引数は、そのメソッドが受け取る引数の数になります。0の場合は引数はなく、1などの場合はその数だけ引数を受け取ります。なお、-1の場合は可変長引数として受け取っているようです(RubyのArrayクラスの実装などで見る限り)。

このようにしてC++でRubyのクラスを作成することができます!

おわりに

今回作成したTataraの紹介をしつつ、C++でのRuby拡張の実装などについても触れてみました。普段使っているRubyの内部コードについても少し触れましたので、興味が湧いた方もおられるかもしれません。

ここから宣伝。

なお、東京近隣の方であればRuby Hacking Challenge HolidayというRubyの実装などをRubyコミッターから直々に聴けるというイベントがあります!

ちなみに、次回は「Ruby 2.7新機能紹介」や「Rails Girls Tokyo, More! 参加者の皆様と合同LT大会」を行うようです。

Ruby Hack Challenge Holiday #9 Ruby 2.7 + 年末LT大会

この記事を読んでRubyの内部に興味を持った方は是非参加してみるといいでしょう。

ちなみに、島根県浜田市ではRuby Hacking Challenge in Hamada.rbをHamada.rb内で開催しています。

僕のほうで内部のコードなど解説を行いますので、興味のある方は是非ご参加ください(なお、わかる範囲になりそうですが……)

Ruby Hacking Challenge in Hamada.rb

宣伝終わり

今後もゆっくりとTataraの実装を行いつつ、Ruby自体の実装への理解を深めていきたいと思います。

参考資料

Rubyソースコード完全解説

Rubyの拡張ライブラリの作り方

ko1/rubyhackchallenge

ruby/ruby

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

正規表現 アンカー

アンカーは幅0の文字列にマッチするメタ文字列です。 幅0とは文字と文字の間、文字列の先頭、文字列の末尾、 などを意味します。 ある特定の条件を満たす「位置」にマッチします。

^ 行頭にマッチします。行頭とは、文字列の先頭もしくは改行の次を 意味します。
$ 行末にマッチします。 行末とは文字列の末尾もしくは改行の手前を意味します。
\A 文字列の先頭にマッチします。
\Z 文字列の末尾にマッチします。 ただし文字列の最後の文字が改行ならばそれの手前にマッチします。
\z 文字列の末尾にマッチします。
\b 単語境界にマッチします。 単語を成す文字と単語を成さない文字の間にマッチします。 文字列の先頭の文字が単語成す文字であれば、文字列の先頭 の位置にマッチします。
\B 非単語境界にマッチします。 \bでマッチしない位置にマッチします。
# 文字列中の real にマッチする
/real/.match("surrealist") # => #< MatchData "real">
# 先頭に real とないとマッチしない
/\Areal/.match("surrealist") # => nil
# 単語境界がrealの前にないのでマッチしない
/\breal/.match("surrealist") # => nil
単語を成す文字、成さない文字の定義はエンコードによって 異なります。以下の例で「全角」括弧は EUC-JP では 単語を成す文字と見なされますが、UTF-8 では見なされません。 その結果、以下のような挙動をします。

# -- coding:utf-8 --
# デフォルトは UTF-8
/foo\b/.match("あいうfoo%") # => #< MatchData "foo">
/\bfoo\b/.match("あいうfoo%") # => nil
/\bfoo\b/e.match("(foo)".encode("EUC-JP")) # => nil
/\bfoo\b/.match("(foo)") # => #< MatchData "foo">
Unicode の規格では、単語を成す文字を Word というプロパティで 定義しています。

先読み、後読み(lookahead, lookbehind)
ある位置から続く文字列が ある部分式にマッチするならばその位置にマッチする という正規表現を書くことができます。

「ある位置から続く文字列(先読み、lookahead)/ある位置の手前までの文字列(後読み、lookbehind)」 と「マッチする(肯定、positive)/マッチしない(否定、negative)」 の組み合わせで4つのパターンがあります。

(?=pat) 肯定先読み(positive lookahead)
(?!pat) 否定先読み(negative lookahead)
(?<=pat) 肯定後読み(positive lookbehind)
(?<!pat) 否定後読み(negative lookbehind)
\K 後読みの別表記、このメタ文字列の手前までを後読みします。 つまり /pat1\Kpat2/ は /(?<=pat1)pat2/ と同様の意味となります。
# 以下の例では、後読みと先読みを使って < b> と
# に挟まれているという条件を正規表現中に記述しつつ
# 自体にはマッチさせていない。
/(?<=< b>)\w+(?=<\/b>)/.match("Fortune favours the < b>bold< /b>")
# => #< MatchData "bold">
# 以下は上の正規表現と同じものを表す
/< b>\K\w+(?=<\/b>)/.match("Fortune favours the < b>bold< /b>")
# => #< MatchData "bold">

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

正規表現 キャプチャ

丸括弧 ( ) によってキャプチャをすることができます。 括弧に囲まれた部分正規表現にマッチした 前からn番目の開き括弧によって囲まれた部分式にマッチした 文字列を後で参照することができます。

正規表現内では \1, \2, ... という記法で後方参照できます。 また、\k<1>, \k<2>, ... や \k'1', \k'2', ... という記法を使うこと もできます(10を越える数字を渡すことができます)。 また、Regexp#match で得られた MatchData からは MatchData#[ ]で取り出せます。

また、$1, $2, ... という特殊変数によって n 番目の括弧にマッチした 部分文字列を参照できます。これらの特殊変数はマッチ処理が終わったあとで しか使えないことに注意してください。

 # (..) に at がマッチしたのを \1 で参照し、マッチが成功している。
m = /csh [csh]\1 in/.match("The cat sat in the hat")
 # => #< MatchData "cat sat in" 1:"at">
 # Regexp#match でマッチしたテキストは MatchData#[] で参照できる
m[1] # => "at"
1,2,... ではなく、名前を付けることができます。 (?< name>pat)もしくは(?'name'pat)と記述します。キャプチャした文字列は MatchData#[ ] に Symbol を渡すことで参照できます。 これは名前付きキャプチャと呼ばれます。

m = /\$(?\d+).(?< cents>\d+)/.match("$3.67")
# => #< MatchData "$3.67" dollars:"3" cents:"67">
m[:dollars] # => "3"
m[:cents] # => "67"
名前付きキャプチャは正規表現内で \k、\k'name' という記法で参照できます。

/(?< vowel>[aeiou]).\k< vowel>.\k< vowel>/.match('ototomy')
# => #< MatchData "ototo" vowel:"o">
注: 名前付きキャプチャと数字によるキャプチャは併用できません。

リテラル正規表現内に名前付きキャプチャがあり、 =~ の左辺で用いた 場合には、その名前のローカル変数にキャプチャした文字列を代入します。

/\$(?< dollars>\d+).(?< cents>\d+)/ =~ "$3.67" # => 0
dollars # => "3"
cents # => "67"
注: ローカル変数への代入が行われるのは、左辺の正規表現リテラルが#{}による式展開を含んでいない場合に限られます。

数字による後方参照では、負の数による、 \k<-1>, \k<-2>, ... や \k'-1', \k'-2', ... という記法での 相対的な指定が可能です。-1 は後方参照が書かれた位置の1つ手前の 位置にあるキャプチャを表し、-2, -3, で2つ手前、3つ手前を表します。 これは非常に多くのキャプチャを持つような正規表現を記述するためや、 正規表現に別の正規表現を式展開で埋め込む場合などに便利です。

/(.)(.)\k<-2>\k<-1>/.match("xyzyz") # => #< MatchData "yzyz" 1:"y" 2:"z">

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

正規表現 繰り返し

以下のメタ文字列は繰り返しを表現します。 直前の部分式を何回繰り返すかを指定します。このような繰り返しを 表すメタ文字列を量指定子(quantifier)と呼びます。
 
* 0回以上
+ 1回以上
? 0回もしくは1回
{n} ちょうどn回(nは数字)
{n,} n回以上(nは数字)
{,m} m回以下(mは数字)
{n,m} n回以上m回以下(n,mは数字)
以下の例で、量指定子の基本的な使いかたを示しています。

 # 以下の正規表現は 最初に大文字が1文字以上(H)で、小文字が1文字以上(l)、
 # lが2文字(ll)の後ろにoが続く文字列にマッチします。
"Hello".match(/[[:upper:]]+[[:lower:]]+l{2}o/) # => #< MatchData "Hello">
これらは「欲張り(greedy)」にマッチします。 マッチが成功する、最長の文字列にマッチしようとします。 そのため、これらの量指定子は特に最大量指定子(greedy quantifier) と呼ばれます。

最小量指定子(reluctant quantifier)
一方、以下のメタ文字列(普通の繰り返しメタ文字列に ? を付加したもの) はマッチが成功する、最短の文字列にマッチします。そのため、これらの 量指定子は特に最小量指定子(reluctant quantifier)と呼ばれます。

*? 0回以上
+? 1回以上
?? 0回もしくは1回
{n,}? n回以上(nは数字)
{,m}? m回以下(mは数字)
{n,m}? n回以上m回以下(n,mは数字)
以下の例では、最小量指定子を使うことで、(\d+)がマッチする場所を変えています。

/^.(\d+)./.match("Copyright 2013.") # => #< MatchData "Copyright 2013." 1:"3">
/^.
?(\d+)./.match("Copyright 2013.") # => #< MatchData "Copyright 2013." 1:"2013">
また、ネストしていない括弧の対応を取るためにも使えます。

# ここでは < b> と < /b> の対応を取る
%r{< b>.< /b>}.match("< b>x< /b>y< b>z< /b>") # => #x< /b>y< b>z< /b>">
%r{< b>.
?< /b>}.match("< b>x< /b>y< b>z< /b>") # => #< MatchData "< b>x< /b>">
絶対最大量指定子(possessive quantifier)
以下のメタ文字列は、最大量指定子のように最長のマッチをしますが、 一度マッチすると、その後マッチに失敗してもバックトラックしません。 つまりマッチ済みの文字列を手放さずにマッチに失敗します。 これらの量指定子は絶対最大量指定子と呼ばれます。

*+ 0回以上
++ 1回以上
?+ 0回もしくは1回
アトミックグループを用いることで同じことができます。

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

文字クラス

文字クラス(character class) とは角括弧 [ と ] で囲まれ、1個以上の文字を列挙したもので、 いずれかの1文字にマッチします。

/W[aeiou]rd/
は Ward, Werd, Wird, Word, Wurd のいずれかにマッチします。

文字クラス内のハイフン(-)は文字の範囲を表すメタ文字です。 例えば [abcd] という文字クラスは [a-d] と表すことができます。 複数の範囲指定をすることもできます。例えば [abcdpqrs] は [a-dp-s]と 表すこともできます。

文字クラスの [ の直後の文字がキャレット(^)である場合、列挙「されていない」 文字にマッチするようになります(これは否定文字クラスと呼ばれます)。

[^a-d]
はabcd以外の1文字にマッチします。

文字クラス内に別の文字クラスを含めることができます。 [a-z[0-9]] は [a-z0-9]と同じ意味を持ちます。これだけではあまり意味が ありませんが、文字クラスは && という、共通部分を取る演算をサポートして いるため、これと組合せることで意味を持ちます。

/[a-z[0-9]]/.match("y") # => #< MatchData "y">
/[a-z[0-9]]/.match("[") # => nil
r = /[a-w&&[^c-g]e]/ # ([a-w] かつ ([^c-g] もしくは e)) つまり [abeh-w] と同じ
r.match("b") # => #< MatchData "b">
r.match("c") # => nil
r.match("e") # => #
r.match("g") # => nil
r.match("h") # => #< MatchData "h">
r.match("w") # => #< MatchData "w">
r.match("z") # => nil
文字クラスでは、否定(^)範囲(-)共通部分(&&)列挙(並べる)という 演算が可能ですが、これらは - > (列挙) > && > ^ という順の結合強度を持ちます。

文字クラス内の3つのメタ文字を通常の文字の意味で使用したい場合には、 \ によってエスケープ する必要があります。

文字クラスの略記法
良く使われる文字クラスには省略記法が存在します。

\w 単語構成文字 [a-zA-Z0-9_]
\W 非単語構成文字 [^a-zA-Z0-9_]
\s 空白文字 [ \t\r\n\f\v]
\S 非空白文字 [^ \t\r\n\f\v]
\d 10進数字 [0-9]
\D 非10進数字 [^0-9]
\h 16進数字 [0-9a-fA-F]
\H 非16進数字 [^0-9a-fA-F]
これらの「空白」「数字」などは ASCII の範囲の文字のみを対象としています。 いわゆる「全角アルファベット」「全角空白」「全角数字」などは ここの空白、数字、には含まれません。

/\w+/.match("ABCdef") # => nil
/\W+/.match("ABCdef") # => #< MatchData "ABCdef">
/\s+/.match(" ") # => nil
/\S+/.match(" ") # => #< MatchData " ">
これらは文字クラス内で演算することもできます。

r = /[\d&&[^47]]/ # 4, 7 以外の数字
r.match("3") # => #< MatchData "3">
r.match("7") # => nil
Unicode プロパティによる文字クラス指定
また、Unicodeのプロパティ(属性情報)による文字クラス指定も可能です。 以下の記法が使えます。

\p{property-name}
\p{^property-name} (否定)
\P{property-name} (否定)
サポートされているプロパティのリストは https://github.com/k-takata/Onigmo/blob/master/doc/UnicodeProps.txt を 参考にしてください。また、プロパティの意味は Unicode の仕様を参照してください。

/\p{Letter}+/.match(".|あaABc123") # => #< MatchData "あaABc">
POSIX 文字クラス
Unicodeプロパティと 似た機能を持つ記法として、POSIX 文字クラスと呼ばれるものがあります。 これらは上の省略記法とは異なり、文字クラスの中でしか用いることが できません。これらは [:クラス名:] という記法を持ちます。 また、[:^クラス名:]という記法でその否定を意味します。 以下の括弧では実際にどの文字にマッチするかが Unicode プロパティや Unicode コードポイントで示されています。

[:alnum:] 英数字 (Letter | Mark | Decimal_Number)
[:alpha:] 英字 (Letter | Mark)
[:ascii:] ASCIIに含まれる文字 (0000 - 007F)
[:blank:] スペースとタブ (Space_Separator | 0009)
[:cntrl:] 制御文字 (Control | Format | Unassigned | Private_Use | Surrogate)
[:digit:] 数字 (Decimal_Number)
[:graph:] 空白以外の表示可能な文字(つまり空白文字、制御文字、以外) ([[:^space:]] && ^Control && ^Unassigned && ^Surrogate)
[:lower:] 小文字 (Lowercase_Letter)
[:print:] 表示可能な文字(空白を含む) ([[:graph:]] | Space_Separator)
[:punct:] 句読点 (Connector_Punctuation | Dash_Punctuation | Close_Punctuation | Final_Punctuation | Initial_Punctuation | Other_Punctuation | Open_Punctuation)
[:space:] 空白、改行、復帰 (Space_Separator | Line_Separator | Paragraph_Separator | 0009 | 000A | 000B | 000C | 000D | 0085)
[:upper:] 大文字 (Uppercase_Letter)
[:xdigit:] 16進表記で使える文字 (0030 - 0039 | 0041 - 0046 | 0061 - 0066)
[:word:] 単語構成文字 (Letter | Mark | Decimal_Number | Connector_Punctuation)
これらの POSIX 文字クラスは \s といった省略記法と異なり、 ASCIIコード範囲外の空白などを考慮に入れます。

/[[:alnum:]]+/.match("abAB121") # => #< MatchData "abAB121">
#\u3000 は全角空白
/[[:graph:]]/.match("\u3000") # => nil
/[[:blank:]]/.match("\u3000") # => #
/[[:alnum:]&&[:^lower:]]/.match("aA") # => #< MatchData "A">
/[[:print:]&&[:^lower:]]/.match(" ") # => #< MatchData " ">
注: POSIX ではここで言う文字クラスのことを「ブラケット表現」と 呼び、[:xxx:] というのを文字クラス、と呼んでいます。よって、 POSIX文字クラス、というのは厳密には 「POSIXブラケット表現における文字クラス」と呼ぶべきものですが、 ここでは「POSIX文字クラス」と呼ぶことにします。

注: [:word:] と [:ascii:] は POSIX では定義されていません。 Ruby/Oniguruma/Onigmo独自のものです。

注: エンコーディングによってこれらの POSIX 文字クラスの挙動が 異なります。上に書いている「マッチする文字」は Unicode 系統の エンコーディングで使われるものです。 Unicode 系統以外のものは Onigmo の ドキュメントを参照してください。

オプション
文字クラスの挙動は オプション で変更することができます。 d, a, u の3つのオプションがあります。

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

正規表現 文字クラス

文字クラス(character class) とは角括弧 [ と ] で囲まれ、1個以上の文字を列挙したもので、 いずれかの1文字にマッチします。

/W[aeiou]rd/
は Ward, Werd, Wird, Word, Wurd のいずれかにマッチします。

文字クラス内のハイフン(-)は文字の範囲を表すメタ文字です。 例えば [abcd] という文字クラスは [a-d] と表すことができます。 複数の範囲指定をすることもできます。例えば [abcdpqrs] は [a-dp-s]と 表すこともできます。

文字クラスの [ の直後の文字がキャレット(^)である場合、列挙「されていない」 文字にマッチするようになります(これは否定文字クラスと呼ばれます)。

[^a-d]
はabcd以外の1文字にマッチします。

文字クラス内に別の文字クラスを含めることができます。 [a-z[0-9]] は [a-z0-9]と同じ意味を持ちます。これだけではあまり意味が ありませんが、文字クラスは && という、共通部分を取る演算をサポートして いるため、これと組合せることで意味を持ちます。

/[a-z[0-9]]/.match("y") # => #< MatchData "y">
/[a-z[0-9]]/.match("[") # => nil
r = /[a-w&&[^c-g]e]/ # ([a-w] かつ ([^c-g] もしくは e)) つまり [abeh-w] と同じ
r.match("b") # => #< MatchData "b">
r.match("c") # => nil
r.match("e") # => #
r.match("g") # => nil
r.match("h") # => #< MatchData "h">
r.match("w") # => #< MatchData "w">
r.match("z") # => nil
文字クラスでは、否定(^)範囲(-)共通部分(&&)列挙(並べる)という 演算が可能ですが、これらは - > (列挙) > && > ^ という順の結合強度を持ちます。

文字クラス内の3つのメタ文字を通常の文字の意味で使用したい場合には、 \ によってエスケープ する必要があります。

文字クラスの略記法
良く使われる文字クラスには省略記法が存在します。

\w 単語構成文字 [a-zA-Z0-9_]
\W 非単語構成文字 [^a-zA-Z0-9_]
\s 空白文字 [ \t\r\n\f\v]
\S 非空白文字 [^ \t\r\n\f\v]
\d 10進数字 [0-9]
\D 非10進数字 [^0-9]
\h 16進数字 [0-9a-fA-F]
\H 非16進数字 [^0-9a-fA-F]
これらの「空白」「数字」などは ASCII の範囲の文字のみを対象としています。 いわゆる「全角アルファベット」「全角空白」「全角数字」などは ここの空白、数字、には含まれません。

/\w+/.match("ABCdef") # => nil
/\W+/.match("ABCdef") # => #< MatchData "ABCdef">
/\s+/.match(" ") # => nil
/\S+/.match(" ") # => #< MatchData " ">
これらは文字クラス内で演算することもできます。

r = /[\d&&[^47]]/ # 4, 7 以外の数字
r.match("3") # => #< MatchData "3">
r.match("7") # => nil
Unicode プロパティによる文字クラス指定
また、Unicodeのプロパティ(属性情報)による文字クラス指定も可能です。 以下の記法が使えます。

\p{property-name}
\p{^property-name} (否定)
\P{property-name} (否定)
サポートされているプロパティのリストは https://github.com/k-takata/Onigmo/blob/master/doc/UnicodeProps.txt を 参考にしてください。また、プロパティの意味は Unicode の仕様を参照してください。

/\p{Letter}+/.match(".|あaABc123") # => #< MatchData "あaABc">
POSIX 文字クラス
Unicodeプロパティと 似た機能を持つ記法として、POSIX 文字クラスと呼ばれるものがあります。 これらは上の省略記法とは異なり、文字クラスの中でしか用いることが できません。これらは [:クラス名:] という記法を持ちます。 また、[:^クラス名:]という記法でその否定を意味します。 以下の括弧では実際にどの文字にマッチするかが Unicode プロパティや Unicode コードポイントで示されています。

[:alnum:] 英数字 (Letter | Mark | Decimal_Number)
[:alpha:] 英字 (Letter | Mark)
[:ascii:] ASCIIに含まれる文字 (0000 - 007F)
[:blank:] スペースとタブ (Space_Separator | 0009)
[:cntrl:] 制御文字 (Control | Format | Unassigned | Private_Use | Surrogate)
[:digit:] 数字 (Decimal_Number)
[:graph:] 空白以外の表示可能な文字(つまり空白文字、制御文字、以外) ([[:^space:]] && ^Control && ^Unassigned && ^Surrogate)
[:lower:] 小文字 (Lowercase_Letter)
[:print:] 表示可能な文字(空白を含む) ([[:graph:]] | Space_Separator)
[:punct:] 句読点 (Connector_Punctuation | Dash_Punctuation | Close_Punctuation | Final_Punctuation | Initial_Punctuation | Other_Punctuation | Open_Punctuation)
[:space:] 空白、改行、復帰 (Space_Separator | Line_Separator | Paragraph_Separator | 0009 | 000A | 000B | 000C | 000D | 0085)
[:upper:] 大文字 (Uppercase_Letter)
[:xdigit:] 16進表記で使える文字 (0030 - 0039 | 0041 - 0046 | 0061 - 0066)
[:word:] 単語構成文字 (Letter | Mark | Decimal_Number | Connector_Punctuation)
これらの POSIX 文字クラスは \s といった省略記法と異なり、 ASCIIコード範囲外の空白などを考慮に入れます。

/[[:alnum:]]+/.match("abAB121") # => #< MatchData "abAB121">
#\u3000 は全角空白
/[[:graph:]]/.match("\u3000") # => nil
/[[:blank:]]/.match("\u3000") # => #
/[[:alnum:]&&[:^lower:]]/.match("aA") # => #< MatchData "A">
/[[:print:]&&[:^lower:]]/.match(" ") # => #< MatchData " ">
注: POSIX ではここで言う文字クラスのことを「ブラケット表現」と 呼び、[:xxx:] というのを文字クラス、と呼んでいます。よって、 POSIX文字クラス、というのは厳密には 「POSIXブラケット表現における文字クラス」と呼ぶべきものですが、 ここでは「POSIX文字クラス」と呼ぶことにします。

注: [:word:] と [:ascii:] は POSIX では定義されていません。 Ruby/Oniguruma/Onigmo独自のものです。

注: エンコーディングによってこれらの POSIX 文字クラスの挙動が 異なります。上に書いている「マッチする文字」は Unicode 系統の エンコーディングで使われるものです。 Unicode 系統以外のものは Onigmo の ドキュメントを参照してください。

オプション
文字クラスの挙動は オプション で変更することができます。 d, a, u の3つのオプションがあります。

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

正規表現 文字

正規表現内では、「\」の後に文字列を置くことで、 ある特定の文字を表現することができます。 これは、改行のように Ruby の文法で特別な意味を持つ文字を埋め込む ためなどに用いられます。 文字列リテラルの記法とほぼ同様(リテラル/バックスラッシュ記法)で、 以下の記法が利用可能です。

\t 水平タブ horizontal tab (0x09)
\v 垂直タブ vertical tab (0x0B)
\n 改行 newline (0x0A)
\r 復帰 return (0x0D)
\b バックスペース back space (0x08)
\f 改ページ form feed (0x0C)
\a ベル bell (0x07)
\e エスケープ文字 escape (0x1B)
\nnn 符号化バイト値の8進数表現 (nnn の8進数3文字で表現)
\xHH 符号化バイト値の16進数表現 (HH の16進数2文字で表現)
\cx, \C-x 制御文字 (x は a から z までのいずれかの文字)
\M-x メタ (x|0x80)
\M-\C-x メタ制御文字
\uHHHH ユニコード文字 (HHHH の16進数4桁)
\u{HHHHHH HHHHHH ....} ユニコード文字列 (HHHHHH は16進数1桁から6桁まで指定可能)

\b は文字クラス内でのみ有効な表現です。文字クラスの外では 単語の区切りを表すメタ文字列と解釈されます。
「\s」は文字列では空白(0x20)を意味しますが、正規表現では タブなどを含む空白文字全般にマッチするメタ文字列です。

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

文字

正規表現内では、「\」の後に文字列を置くことで、 ある特定の文字を表現することができます。 これは、改行のように Ruby の文法で特別な意味を持つ文字を埋め込む ためなどに用いられます。 文字列リテラルの記法とほぼ同様(リテラル/バックスラッシュ記法)で、 以下の記法が利用可能です。

\t 水平タブ horizontal tab (0x09)
\v 垂直タブ vertical tab (0x0B)
\n 改行 newline (0x0A)
\r 復帰 return (0x0D)
\b バックスペース back space (0x08)
\f 改ページ form feed (0x0C)
\a ベル bell (0x07)
\e エスケープ文字 escape (0x1B)
\nnn 符号化バイト値の8進数表現 (nnn の8進数3文字で表現)
\xHH 符号化バイト値の16進数表現 (HH の16進数2文字で表現)
\cx, \C-x 制御文字 (x は a から z までのいずれかの文字)
\M-x メタ (x|0x80)
\M-\C-x メタ制御文字
\uHHHH ユニコード文字 (HHHH の16進数4桁)
\u{HHHHHH HHHHHH ....} ユニコード文字列 (HHHHHH は16進数1桁から6桁まで指定可能)
\b は文字クラス内でのみ有効な表現です。文字クラスの外では 単語の区切りを表すメタ文字列と解釈されます。

「\s」は文字列では空白(0x20)を意味しますが、正規表現では タブなどを含む空白文字全般にマッチするメタ文字列です。

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

正規表現 式展開

正規表現内では、#{式} という形式で式を評価した文字列を埋め込むことが できます。

place = "東京都"
/#{place}/.match("Go to 東京都") # => #< MatchData "東京都">
埋め込んだ文字列にメタ文字が含まれているならば、それは メタ文字として認識されます。

number = "(\d+)"
operator = "(\+|-|\*|/)"
/#{number}#{operator}#{number}/.match("43+291") # => #< MatchData "43+291" 1:"43" 2:"+" 3:"291">
埋め込む文字列をリテラルとして認識させたい場合は Regexp.quote を 使います。

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

式展開

正規表現内では、#{式} という形式で式を評価した文字列を埋め込むことが できます。

place = "東京都"
/#{place}/.match("Go to 東京都") # => #< MatchData "東京都">
埋め込んだ文字列にメタ文字が含まれているならば、それは メタ文字として認識されます。

number = "(\d+)"
operator = "(\+|-|\*|/)"
/#{number}#{operator}#{number}/.match("43+291") # => #< MatchData "43+291" 1:"43" 2:"+" 3:"291">
埋め込む文字列をリテラルとして認識させたい場合は Regexp.quote を 使います。

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

正規表現 メタ文字列とリテラル、メタ文字とエスケープ

正規表現の文法には、正規表現内で特別な働きをする文字列と、それ以外の その文字列そのものにマッチするような文字列があります。 前者をメタ文字列(meta string)、後者をリテラル(文字列)(literal string)と呼びます。

/京都|大阪|神戸/
という正規表現においては、「京都」「大阪」「神戸」がリテラルで、 2つの「|」がメタ文字列です。

以下の文字は「メタ文字」(meta character) と呼ばれる、正規表現内で特殊な働きをする文字です。

( ) [ ] { } . ? + * | \
これらの文字をリテラルのようにその文字としてマッチさせるためには、 バックスラッシュ「\」を前に付けます。「\」はバックスラッシュ1文字に マッチします。

メタ文字以外の文字も、メタ文字に続けて置くことで特別な働きをするよう になる場合があります。つまりメタ文字列を構成します。例えば

/[a-z]/
/\Axyz\Z/
という正規表現において "[a-z]", "\A", "\Z"はメタ文字列です。

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

メタ文字列とリテラル、メタ文字とエスケープ

正規表現の文法には、正規表現内で特別な働きをする文字列と、それ以外の その文字列そのものにマッチするような文字列があります。 前者をメタ文字列(meta string)、後者をリテラル(文字列)(literal string)と呼びます。

/京都|大阪|神戸/
という正規表現においては、「京都」「大阪」「神戸」がリテラルで、 2つの「|」がメタ文字列です。

以下の文字は「メタ文字」(meta character) と呼ばれる、正規表現内で特殊な働きをする文字です。

( ) [ ] { } . ? + * | \
これらの文字をリテラルのようにその文字としてマッチさせるためには、 バックスラッシュ「\」を前に付けます。「\」はバックスラッシュ1文字に マッチします。

メタ文字以外の文字も、メタ文字に続けて置くことで特別な働きをするよう になる場合があります。つまりメタ文字列を構成します。例えば

/[a-z]/
/\Axyz\Z/
という正規表現において "[a-z]", "\A", "\Z"はメタ文字列です。

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

【Rails】ログイン機能を実装する

TODOアプリにログイン機能を実装します。

【Rails】バリデーションを実装する
【Rails】パーシャルを利用する

Userモデルを作成

nameemailpassword_digestという属性を持つUserモデルを作成します。passwordではなく、必ずpassword_digestという属性を設定してください。

$ rails g model User name:string email:string password_digest:string
$ rails db:migrate

ハッシュ化したパスワードで認証ができるようにする

パスワードを平文で保存することは危険です。ハッシュ化したパスワードをDBに保存し、ユーザーのログイン時に入力されたパスワードをハッシュ化した値と比較するようにしましょう。

bcryptを追加

ハッシュ化を行うためのgemを追加します。

/Gemfile
source 'https://rubygems.org'

gem 'rails',   '5.1.6'
gem 'bcrypt',  '3.1.12'
.
.
.
$ bundle install

Userモデルにhas_secure_passwordを追加

password_digestという属性を持つモデルにhas_secure_passwordと書き込むことで、以下の3つが可能になります。

  1. セキュアにハッシュ化したパスワードを、データベース内のpassword_digestという属性に保存できるようになる。
  2. 仮想的な属性passwordpassword_confirmationが使えるようになる。また、存在性と値が一致するかどうかのバリデーションも追加される。
  3. authenticateメソッドが使えるようになる (引数の文字列がパスワードと一致するとUserオブジェクトを、間違っているとfalseを返すメソッド) 。
/app/models/user.rb
class User < ApplicationRecord
  has_secure_password
end

ログイン用のダミーデータを作成

password_digestという属性がある場合、ユーザー登録時にはpasswordpassword_confirmationの2つの属性を登録します。

$ rails c
irb(main):001:0> User.create(name: "sampleTarou", email: "tarou@example.com", password: "hogehoge", password_confirmation: "hogehoge")

セッション管理の基盤を作成

セッションを利用してユーザーを判別する仕組みを作ります。
Sessionsコントローラを生成するときに(密かに)自動生成されるセッション用ヘルパーモジュールを利用すると、sessionメソッドが使えるようになり、セッション管理の仕組みを簡単に実装することができます。

具体的には、ログイン時に
session[:user_id] = user.idでブラウザのcookieにハッシュ化したユーザーidを保存し、ページ遷移の度に
@current_user = User.find_by(id: session[:user_id])でidを元にログイン中のユーザーの情報を取得できるようになります。

Sessionsコントローラーを作成

newcreatedestroyアクションを持ったSessionsコントローラーを作成します。
viewが必要なのはログインページを表示するアクションであるnewアクションだけなので、余計なファイルを生成しないためにcreatedestroyアクションは手動で追加します。

$ rails g controller Sessions new
/app/controllers/sessions_controller.rb
class SessionsController < ApplicationController

  def new
  end

  def create
  end

  def destroy
  end
end

ルーティングを設定します。

/config/routes.rb
Rails.application.routes.draw do
  get    '/login',   to: 'sessions#new'
  post   '/login',   to: 'sessions#create'
  delete '/logout',  to: 'sessions#destroy'
.
.
end

Sessionヘルパーモジュールを読み込む

sessionメソッドを全てのページで使えるようにするために、ApplicationコントローラSessionヘルパーモジュールを読み込みます。

/app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
  protect_from_forgery with: :exception
  include SessionsHelper
end

ログインページを作成

セッションにはSessionモデルというものがなく、@taskのようなインスタンス変数に相当するものもありません。

本来form_for(@task)と書くだけで、「フォームのactionは/taskというURLへのPOSTである」と自動的に判定しますが、セッションの場合はリソースの名前とそれに対応するURLを具体的に指定する必要があります。

/app/views/sessions.new.html.erb
<h1>ログイン</h1>
<%= form_for(:session, url: login_path) do |f| %>
  <%= f.label :email %>
  <%= f.email_field :email %>
  <%= f.label :password %>
  <%= f.password_field :password %>
  <%= f.submit "Log in" %>
<% end %>

ログイン状態に応じてトップページの表示を切り分け

ログイン前はログイン画面へのリンク、ログイン後はタスク一覧とログアウトボタンが表示されるようにします。(logged_in?メソッドについては後述)

/app/views/tasks/index.html.erb
<h1>TODOアプリ</h1>
<% if logged_in? %>
  <%= render 'tasks/logged_in' %>
<% else %>
  <%= render 'tasks/not_logged_in' %>
<% end %>
/app/views/tasks/_logged_in.html.erb
<h2>タスク一覧</h2>
<table>
    <thead>
      <tr>
        <th>タスク名</th>
      </tr>
    </thead>
    <tbody>
      <% @tasks.each do |task| %>
        <tr>
          <td><%= task.title %></td>
          <td><%= link_to "編集", edit_task_path(task) %></td>
          <td><%= link_to "削除", task_path(task), method: :delete %></td>
        </tr>
      <% end %>
    </tbody>
</table>
<%= link_to "タスク追加", new_task_path %>
<%= link_to "ログアウト", logout_path, method: :delete %>
/app/views/tasks/_not_logged_in.html.erb
<p>タスクの管理ができるアプリです。まずはログインをしてください。</p>
<%= link_to "ログイン", login_path%>

セッション用ヘルパーメソッドを作成

log_inメソッド

ブラウザのcookieに、ハッシュ化したユーザーidを保存するメソッドです。

app/helpers/sessions_helper.rb
module SessionsHelper

 # 渡されたユーザーでログインする
  def log_in(user)
    session[:user_id] = user.id
  end
end

current_userメソッド

cookieに保存されたユーザーidを元に、ユーザーの情報を取得するメソッドです。

app/helpers/sessions_helper.rb
module SessionsHelper
.
.
 # 現在ログイン中のユーザーを返す (いる場合)
  def current_user
    if session[:user_id]
     #@current_user = @current_user || User.find_by(id: session[:user_id])と同じ意味
      @current_user ||= User.find_by(id: session[:user_id])
    end
  end
end

findではなくfind_byで検索をしているのは、ユーザーidが存在しなかった場合findを使うと例外が返されてしまうためです。find_byを使うとnilが返されます。

logged_in?メソッド

現在のユーザーがログインしているかどうかを判別するメソッドです。ログイン状況に応じて表示する画面を切り替えたりする処理が簡単に実装できるようになります。

app/helpers/sessions_helper.rb
module SessionsHelper
.
.
 # ユーザーがログインしていればtrue、その他ならfalseを返す
  def logged_in?
    !current_user.nil?
  end
end

log_outメソッド

ブラウザのcookieに保存されているユーザーidを削除するメソッドです。

app/helpers/sessions_helper.rb
module SessionsHelper
.
.
 # 現在のユーザーをログアウトする
  def log_out
    session.delete(:user_id)
    @current_user = nil
  end
end

ログイン、ログアウト処理を実装

createアクションを作成

ログインページから送信された情報を受け取り、ログイン処理を行うアクションです。

app/controllers/sessions_controller.rb
class SessionsController < ApplicationController
.
.
  def create
    user = User.find_by(email: params[:session][:email].downcase)
    if user && user.authenticate(params[:session][:password])
      log_in user
      redirect_to root_url
    else
      render 'new'
    end
  end
.
.

destroyアクションを作成

cookieに保存されたユーザーidを削除し、ログアウトを行うアクションです。

app/controllers/sessions_controller.rb
class SessionsController < ApplicationController
.
.
  def destroy
    log_out if logged_in?
    redirect_to root_url
  end
end

タスクをいじる前にログイン状況をチェック

タスクの表示、作成、編集、削除の前にログイン状況をチェックするように設定します。

logged_in_userアクションを作成

ログインしていないユーザーをログインページにリダイレクトするアクションを作成します。application_controllerに追加することで、全てのコントローラーで利用できるようになります。

/app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
.
.
  private
   # ログイン済みユーザーかどうか確認
    def logged_in_user
      unless logged_in?
        redirect_to login_url
      end
    end
end

before_actionを設定

コントローラーの冒頭にbefore_action :<privateアクション名>, only:[:アクション名]と書き込むことで、アクションが実行される前に、指定したprivateアクションを実行することができます。

/app/controllers/tasks_controller.rb
class TasksController < ApplicationController
  before_action :logged_in_user, only:[:create, :edit, :update, :destroy]
.
.
.

終わりに

予想以上に分量が多くなりました。今回実装した内容だと、ブラウザを閉じる度にセッションが切れてログアウトします。ブラウザを閉じてもセッションを維持する方法もあるので、またいつか他の記事でまとめたいと思います。

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

10進数でも2進数でも8進数でも逆から読んで同じになる回文数(Ruby)

回文とは

世の中ね、顔かお金かなのよ...
(よのなかねかお か おかねかなのよ)
のように上から読んでも下から読んでも同じ言葉を回文と言います。

10進数 2進数 8進数
0 0 0
1 1 1
2 10 2
3 11 3
4 100 4
5 101 5
6 110 6
7 111 7
8 1000 10
9 1001 11
10 1010 12

例えば、上の表では10進数で9が2進数、8進数共に回文数となっています。

今回は10以上の数で10進数、2進数、8進数共に回文となる最初の数を探してみます。

考え方

まず2進数で一番右が"0"の数を考えてみます。10101010とか。
回文でなければならないので、一番右が"0"だど一番左も"0"にならなければいけないのですが、そのような2進数は存在しません。(例えば、00101010は存在しない)
なので右も左も"1"から始まる2進数になるはずですが、これは必ず奇数になります。なので奇数だけ順番に探していけば良いことになります。

10進数の回文数

ある10進数が回文数となっているかどうかは、reverseメソッドを使って、

qiita.rb
num.to_s == num.to_s.reverse

を使えば分かります。

その10進数が2進数でも回文数になっているか

またある10進数の、その数の2進数が回文数になっているかどうかは、.to_sメソッドを使えば

qiita.rb
num.to_s(2) == num.to_s(2).reverse

で分かるようになります。
to.s()のカッコの中に数字を入れると、その進数に変換することが出来ます。

あとは10以上の10進数で2進数、8進数共に回文数になっている数をif文とwhileループで探すだけです!

qiita.rb
num = 11
border = "-------"
while true
  if num.to_s == num.to_s.reverse &&
     num.to_s(8) == num.to_s(8).reverse &&
     num.to_s(2) == num.to_s(2).reverse
    puts num
    puts num.to_s(2)
    puts num.to_s(8)
    puts "we found it!"
    break
  end
  puts num
  puts num.to_s(2)
  puts num.to_s(8)
  puts "not this one."
  puts border
  num += 2
end

計算してみると、

qiita.rb
585
1001001001
1111

になりました!

ちなみに

これに16進数でも回文数となるような数を探そうと思って、条件を付け足してプラグラムを走らせてみたのですが、何分待っても計算が終わりませんでした、、、

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

Railsのconfig.eager_loadの影響によるエラーについて

自身への注意喚起と同じところで躓いてしまった人がいたらすぐ解決できるように、メモします。

起こった問題

以下のようなcontrollerとviewがあります。(管理者一覧画面)

sysadmin_users_controller.rb
class SysadminUsersController < ApplicationController
 def index
  @sysadmin_users = SysadminUser.order(updated_at: :desc)
 end
end
index.html.slim
= "#{@sysadmin_users.count}件"
table
 thead
  tr
   th | 氏名
   th | メールアドレス
   th | 最終更新日
 tbody
   - @sysadmin_users.each do |sysadmin_user|
    td = sysadmin_user.name
    td = sysadmin_user.email
    td = l(sysadmin_user.updated_at, format: :short)

上記該当するURL(/sysadmin_users)にアクセスするとdevelopment環境では問題なかったのですが、staging環境では、index.html.slimのcountがno metthod errorになりました:fearful:

エラーの発生原因と解決方法

development環境とstaging環境によってエラーが発生するか違うため、環境によって異なる点を元にエラー原因を確かめようとしました。

1. データベースの違い

development環境とstaging環境で読み込んでいるデータベースが異なっているため、staging環境のデータベースをdevelopment環境にインポートし、同じテーブル状態にしました。

結果

エラー原因分からず、development環境はエラーが発生しませんでした。

2. configファイルの違い

環境ごとに異なるのが、app>config>environments配下にあるファイルです。
development.rbとstaging.rbで異なる箇所を探っていくと、怪しい箇所が・・・。

development.rb
 config.eager_load = false
staging.rb
 config.eager_load = true

development.rbをconfig.eager_load = trueに書き換えると・・・
staging環境と同じエラーが発生しました!

config.eager_loadって何?

@shakemurasanさんの記事が分かりやすいと思いますので、こちらを参照してみてください。
https://qiita.com/shakemurasan/items/305bd3d78d67b646bc06

config.eager_loadをtrueにすると、controllerやmodelなどのclass名がすべて事前に読み込まれます。(事前というのはおそらくrails serverが起動するタイミングです)
そのため、ページを読み込むたびにclassを読み込む必要はないので、ページ読み込みスピードは速くなります。
一方、config.eager_loadをfalseにすると、ページ読み込みスピードは遅くなりますが、rails serverの起動スピードは速くなります。

ちなみに、development環境においてconfig.eager_loadをfalseにしている理由としては、stagingとは異なり頻繁にソースコードが変更され、その度にrails serverを再起動しdevelopment環境でブラウザテストしているという背景があるためです。

なぜconfig.eager_loadの設定でエラーが発生したのか?

事前にnamespace(class名)をすべて読み込まれるとエラーが起こって、ページにアクセスしたタイミングで特定のclassを読み込むとエラーが発生しない・・・。
ん?もしや同じnamespace(class名)が存在しているのでは?
SysadminUsersControllerで全文検索すると、ありました!同じnamespace(class名)が!

users_controller.rb
class SysadminUsersController < ApplicationController
 def index
 end
end

衝撃的ことが2点。
1. users_contorllerなのに、class名がSysadminUsersController
2. このファイルは全く使用されていない・・・

ファイルを消したら、無事解決しました:ok_woman_tone1:

今後の対策

config.eager_loadについて理解するきっかけになったものの・・・。以下新たに肝に銘じておきます。
1. 環境ごとにエラーの有無が異なったら、app>config>environments配下にあるファイルを確認する。(まあこれはいいとして)
2. controller名とclass名を統一する
2. 使っていないファイル(controllerに限らず)は削除する(おそらくあとで使うかもしれないと残していたかもしれないが、エラーが発生する原因になり得る)

最後に

今回のエラーの原因は開発経験半年越えている人にとってはとても当たり前のことかもしれませんが、初めたてのメンバーからすると当たり前ではありません。チームで開発していると今初めたてのメンバーがいなくても今後現れるはずなので、しっかりドキュメントに記載し、ルール化、注意喚起したいと思います:raised_hand_tone2:

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

Rails 5 アプリのherokuへのデプロイ手順

環境

Rails 5.0.7.2
Ruby 2.6.3

herokuへのデプロイ手順

  1. heroku にアプリを作成(すでにherokuにログインしていると仮定)
    • $ heroku create herokuアプリ名
  2. 本番環境用にGemfile, database.ymlを変更
    • Gemfilm: Postgre SQLとRailsを連携させる
    • database.yml: DBをPostgreSQLに設定
  3. 下記を追加,実行し,本番環境へ反映
    • 本番環境上でアセットパイプラインを通るようにプリコンパイル処理を実行する
      • config/environments/production.rb config.assets.compile = false
    • 本番環境上でアセットパイプラインを自動で通るように設定を変更する
      • $ rake assets:precompile RAILS_ENV=production
      • 試したい場合は,以下コマンドを打ってエラーが出ないかを見てみる
        • $ RAILS_ENV=production bundle exec rake assets:precompile
  4. heroku にデプロイ
    • $ git push heroku master
  5. マイグレーション
    • $ heroku run rails db:migrate

詳細:本番環境用にGemfile, database.ymlを変更

group :production do
  gem 'pg', '0.21.0'
end
database.yml

production:
  adapter: postgresql
  encoding: unicode
  pool: 5
  database: message-board_production
  username: message-board
  password: <%= ENV['MESSAGE-BOARD_DATABASE_PASSWORD'] %>

まとめ

一番最初に(herokuへデプロイする前に)アセットパイプラインのための設定をしておきましょう.
私はこの順番をミスりました.

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

Ruby Net::SSHで多段ssh(ポートフォワード/トンネリング)をプログラマブルにやる

Goal

  • sshをプログラマブルにやる
  • 踏み台サーバ経由でsshする
  • 秘密鍵をメモリ上におき、ディスクにおかなくていいようにする
  • (おまけ)最後にspecinfra, itamaeをこの方式でつかえるようにする

Motivation

秘密鍵の管理がややこしい。案件が増えるにつれてどんどん増えていく。踏み台サーバの鍵と目的サーバの鍵が異なることもあり、サーバ構築時(ユーザ作成前)と構築後でつかう鍵が異なることもある。鍵を担当者に共有するのもややこしい。

少なくとも自分でコントロールできる案件については、構築に関係する鍵をプログラマブルに管理し、構築自体動的にやりたいと思うようになった。

Mechanism

key_data

ふつうコマンドラインのオプションでは-iなど、鍵のパスを指定するのが一般的だが、Net::SSHには key_data というオプションで、文字列で直接秘密鍵を指定する仕組みがある。 1 これをつかえば、秘密鍵をディスク上に保存することなくsshできる。

require 'net/ssh'

key_data = <<-EOS
-----BEGIN RSA PRIVATE KEY-----
MIIEowxxxxxxxx.....
-----END RSA PRIVATE KEY-----
EOS

Net::SSH.start('x.x.x.x', 'ec2-user',
  port: 22,
  key_data: key_data
) do |ssh|
  ssh.exec!("hostname")
end

# => "ip-y-y-y-y\n"

踏み台サーバごしに key_data をつかえれば今回の要件をクリアできるという寸法。

ProxyCommand

capistranoで踏み台経由のデプロイするときは、今までProxyCommandをつかっていた。capistranoは裏でNet::SSHをつかってるので、Net::SSH::Proxy::Commandをつかってプロキシできる。

今回の多段sshも、はじめこれと同じ要領でできるような気がしていたが、どうもできない。

net-ssh/command.rb at v5.2.0 · net-ssh/net-ssh · GitHub

Net::SSH::Proxy::Commandは引数に渡されたコマンドを、OSコマンドライン上で実行しているにすぎない(IO.popen)。つまり Net::SSH がもっているプログラマブルな鍵管理の仕組み(key_dataなど)を適用できない。しかしコマンドラインのsshでは秘密鍵を直接文字列で渡す仕組みはない。(コマンドライン実行されたものは履歴に残るので仮にできたとしてもやるべきではない)

require 'net/ssh'
require 'net/ssh/proxy/command'

key_data = <<-EOS
-----BEGIN RSA PRIVATE KEY-----
MIIEowxxxxxxxx.....
-----END RSA PRIVATE KEY-----
EOS

Net::SSH.start('x.x.x.x', 'ec2-user',
  port: 22,
  key_data: key_data,
  # ↓のコマンドに踏み台サーバログインのための鍵のパスを指定する必要がある
  # 踏み台へのアクセス時にはkey_dataに指定した鍵はつかわれない
  proxy: Net::SSH::Proxy::Command.new('ssh -W %h:%p metheglin@a.a.a.a -i ~/.ssh/id_rsa'),
) do |ssh|
  ssh.exec!("hostname")
end

# => "ip-y-y-y-y\n"

つまり、ProxyCommandつかうなら必ず秘密鍵がファイルとしてディスク上に配置されていなければいけない。今回の要件ではProxyCommandはつかえないことになる。

net-ssh-gateway

じつはわたしは、多段sshについて理解がとぼしく、軽く混乱状態にあった。ProxyCommandができないとわかると混乱きわまってあきらめかけた。
一度冷静になって、できないわけないと思い直し、歯を食いしばって調べ直した結果以下のgemを見つけた。最近歯医者に行ったからくいしばり力があがってたのかもしれない。

GitHub - net-ssh/net-ssh-gateway: A gateway class for tunneling connections via SSH over a forwarded port

READMEにはactiveに開発されてないとコメントがあるが、中身はすごいシンプルで、214行くらいしかない。2 このgemは、シンプルにローカルフォワード3をおこなっている。適当なポートがはらいだされて4、それがフォワードの窓口となる。プログラマはそのポートについて意識しなくてもいい。

Sample Code

net-ssh-gatewayをつかって、秘密鍵をディスクにおかない多段sshは以下のようにできる。

gem install net-ssh-gateway
require 'net/ssh/gateway'

key_data = <<-EOS
-----BEGIN RSA PRIVATE KEY-----
MIIEowxxxxxxxxxxxx...
-----END RSA PRIVATE KEY-----
EOS

gateway = Net::SSH::Gateway.new('x.x.x.x', 'ec2-user', key_data: key_data)
ssh = gateway.ssh('a.a.a.a', 'ec2-user', 
  key_data: key_data
)

ssh.exec!("hostname; pwd; id")
ssh.close
gateway.close(ssh.transport.port)

Net::SSH::Gateway.newした時点で、踏み台への接続が確立される。
その後 gateway.ssh() するともういっこ接続が確立される。

ここでは踏み台サーバとターゲットサーバで同じkey_dataをつかっているが、別の鍵にすることも可能。

specinfra

specinfraでこのgatewayごしのssh接続をどうつかうか?

specinfraではハックポイントが用意されていて、カスタムのssh接続をつかえるようになっていた。5
上記のコード gateway.ssh() でつくった接続 ssh を以下のように指定すればOK。

specinfra = Specinfra::Backend::Ssh.new(
  request_pty: true,
  disable_sudo: false,
  ssh: ssh,
  ssh_options: {
    user: 'ec2-user',
  }
)

specinfra.run_command("hostname; pwd; id")

# ssh.close
# gateway.close(ssh.transport.port)

itamae

itamaeはコマンドライン上の実行を主に想定してるっぽいので、大胆なハックができない。
以下のようにプログラマブルで実行用のRunnerをつくることでしのぐ。
上記specinfraのコードで作成した変数specinfraを指定してつかう。

recipe = File.expand_path("recipes/ping.rb", __dir__)
Itamae::InlineRunner.run(
  [recipe],
  specinfra: specinfra,
  node: {test: 1234},
)
module Itamae
  class InlineRunner < Itamae::Runner
    class << self
      def run(recipe_files, options)
        Itamae.logger.info "Starting Itamae... #{options[:dry_run] ? '(dry-run)' : ''}"

        backend = Backend::SshInline.new(options)
        runner = self.new(backend, options)
        runner.load_recipes(recipe_files)
        runner.run

        runner
      end
    end

    private
    def create_node
      return @options[:node] if @options[:node]
      super
    end
  end

  module Backend
    class SshInline < Base
      def initialize(options)
        @options = options
        @backend = options[:specinfra]
        @executed_commands = []
      end

      def disable_sudo?
        !@options[:sudo]
      end
    end
  end
end

Environment

% ruby -v
ruby 2.5.1p57 (2018-03-29 revision 63029) [x86_64-linux]
[2] pry(main)> Net::SSH::Version::STRING
=> "5.2.0"
[3] pry(main)> Net::SSH::Gateway::VERSION
=> "2.0.0"
[4] pry(main)> Specinfra::VERSION
=> "2.82.4"
[5] pry(main)> Itamae::VERSION
=> "1.10.6"
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Rails5 heroku にS3を使ってアップロードしたときに発生するエラー 403 Access denied の対処法

背景

S3にあるリソースはデフォルトでは外部からアクセスできないようになっている.
Heroku上で動いているアプリで画像をアップロードしても,403 errorではじかれて,表示できない.

やったこと

パブリックアクセス設定を一度すべてオフにする

Screen Shot 2019-11-24 at 4.20.26 PM.png

バケットポリシーを作成

アクセス権限→バケットポリシーを選択すると,編集画面になるので,以下の内容を書く.
Screen Shot 2019-11-24 at 4.25.42 PM.png

{
    "Version": "2012-10-17",
    "Id": "PublicRead",
    "Statement": [
        {
            "Sid": "ReadAccess",
            "Effect": "Allow",
            "Principal": {
                "AWS": "arn:aws:iam::<あなたのAWS12桁のID>:user/<IAMユーザー名>"
            },
            "Action": "*",
            "Resource": "arn:aws:s3:::<bucketの名前>/*"
        }
    ]
}

パブリックアクセス設定を変えて,新しいバケットポリシーをブロックさせる

念の為.ブロックさせるようにする.
Screen Shot 2019-11-24 at 4.15.00 PM.png

参考

https://qiita.com/rainbow___0/items/0ffeb29afc7865eb65b5
https://teratail.com/questions/170083

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

【RSpec3.9】苦手意識克服!一番シンプルな文法で簡単なテストを書いて理解してみよう

はじめに

これからRSpecを導入しようとされている方の中で、

  • テストに対する抵抗感をなくしたい
  • まずは簡単なテストの書き方を知りたい

という方向けに書きました。

RSpecの超基本文法とそれを使ったテストの例を以下4つの視点で1つずつ掲載しています。

  1. Model
  2. Controller
  3. View
  4. Routing

参考になれば幸いです。

【参考にさせて頂いた記事】
使えるRSpec入門・その1「RSpecの基本的な構文や便利な機能を理解する」 - Qiita
使えるRSpec入門・その2「使用頻度の高いマッチャを使いこなす」 - Qiita
使えるRSpec入門・その4「どんなブラウザ操作も自由自在!逆引きCapybara大辞典」 - Qiita
Ruby on Rails のテストフレームワーク RSpec 事始め - Qiita

この記事が役に立つ方

  • RSpecでテストを書いたことがない方

この記事のメリット

  • RSpecへの抵抗感がなくなる

環境

  • OS: macOS Catalina 10.15.1
  • シェル: zsh
  • Ruby: 2.6.5
  • Rails: 5.2.3
  • RSpec: 3.9.0

【事前準備】

今回はscaffoldを使ったアプリケーションを例にしますので、3ステップで事前準備をしておきましょう。

1. scaffoldでアプリケーションを作成しておく

$ rails g scaffold user name:string email:string

2. RSpecをインストールし、テスト用ファイルを作成する

Gemfile
group :development, :test do
  gem `rspec-rails`
end

$ bundle install

$ rails g rspec:install

3.テストの実行方法の把握

$ rspec

上記で全テストが走りますので、テストを書いたら走らせてみましょう。

次に基本文法を見ていきます。

基本文法

require 'rails_helper'

RSpec.describe テスト対象, テストの種類 do
  describe 'テスト対象' do
    it '期待する内容を言葉で書く' do
      期待する動作をコードで書く
    end
  end
end

※実際は他にcontextbeforeなども使いますが、本記事では超シンプルなテストを例にします。

基本文法だけ見てもよく分からないと思いますので、さっと目を通して以下具体例に移りましょう。

具体例

MVC+Routingそれぞれに対し、1つずつ超シンプルなテストを書いて試していきます。

それぞれ、動作するテストのみ記載していますので、自分でいじってみて失敗するかどうかも確認するほうが良いと思います。

1.Model

バリデーションが正しく動作するかをテストしたい

Userモデルに以下のようなバリデーションがかかっていて、それをテストしたいとします。

models/user.rb
class User < ApplicationRecord
  with_options presence: true do
    validates :name
    validates :email
  end
end

では、テストを書いてみましょう。
最初に以下コマンドを実行し、テストの雛形を作成しましょう。

$ rails g rspec:models user

spec/models/user_spec.rb
が出来ているはずなので、以下のように記述して下さい。

spec/models/user_spec.rb
require 'rails_helper'

RSpec.describe User, type: :model do
  describe 'Userモデルのバリデーションテスト' do
    it 'nameとemailに値があれば有効' do
      user = User.new(
       name: "name",
       email: "sample@example.com"
      )
      expect(user).to be_valid
    end
  end
end

【解説】

User, type: :modelでUser Modelのテストであることを明示。

userに適当な名前とメールアドレスを指定してUserモデルから新規インスタンスを作成し、それをbe_validというマッチャで有効かどうかを判定しています。


2.Controller

indexアクションで正常にページが表示されるかテストしたい

正常にページが表示されるかはHTTPレスポンスが200であると言い換えることが出来ます。

テストを書いてみましょう。
まず先程と同様、以下コマンドを実行します。

$ rails g rspec:request users

「あれ?request? controllerじゃないの?」と思いますよね。

実は、Rails5以降はcontroller_specが非推奨となっています。

controller_specでも書けるのですが、controller関連はrequest_specで記載するのが良いようです。
詳細は下記リンクより。

RSpec 3.5 がリリースされました!

では戻ります。

先程のコマンドで
spec/request/users_spec.rb
が出来ているので以下のように記述して下さい。

spec/request/users_spec.rb
require 'rails_helper'

RSpec.describe "Users", type: :request do
  describe "GET /users" do
    it "HTTPレスポンスが200になる" do
      get users_path
      expect(response).to have_http_status(200)
    end
  end
end

【解説】

"Users", type: :requestと指定することでUsersのrequest_specであることを明示。

getリクエストでusers_path(/users)を指定し、それに対するHTTPレスポンスがresponseに入ります。

そのresponseに対してhave_http_statusというマッチャで200と等しいかを確認しています。


3.View

<h1>タグ内が正しく表示されるかをテストする

app/views/users/index.html.erb
<h1>Users</h1>

index/html/erbにはこんなh1タグが記載されています。

このh1タグ内に表示されている内容が正しく表示されているかをテストしてみましょう。

まずはテスト用のファイルを以下のように作成します。

$ rails g rspec:view users index

spec/views/users/index.html.erb_spec.rb
が出来ているので、以下のように記述して下さい。

spec/views/users/index.html.erb_spec.rb
require 'rails_helper'

RSpec.describe "users/index", type: :view do
  describe 'index.html.erbのテスト' do
    it 'h1タグ内にUsersが表示されているかどうか' do
      visit users_path
      expect(page).to have_selector('h1', text: 'Users')
    end
  end
end

【解説】

users/indexviewのテストであることを最初に明示。

visitの後にusers_pathを指定することでindex.html.erbにアクセス。

expect(page)で今いるページを明示、have_selectorというマッチャで'h1'にUsersが表示されているかをテストしています。
...

※実は上記テストはうまくいきません。

騙したようで申し訳ありません。テストを実行したら、

NoMethodError: undefined method 'visit'

というエラーが出ませんでしたか?
実は自分もここで手間取りました。

ググるとかなり沢山記事が出てくるので、良くあるエラーなんだと思います。

原因はgemcapybaraがRSpecに読み込まれていないことです。

visitcapybaraの中で定義されているDSL(ドメイン指定言語)のため、読み込むために設定を追加しなければいけません。

spec/spec_helper.rb
を開いて以下のように2行追記して下さい。

spec/spec_helper.rb
...

require 'capybara/rspec' # ここに追記

RSpec.configure do |config|

config.include Capybara::DSL # ここに追記

...

end


この設定で読み込まれるCapybara::DSLで定義されているDSL一覧は以下で確認することができます。

もし他にNoMethodErrorが出たら参考にしてみるのも良いかもしれません。

capybara cheat sheet · GitHub

うまくいったら次に進みましょう。


4.Routing

ページのルーティングが正しいかをテストする

最後はルーティングです。

createアクションに対してルーティングがうまくいっているかテストをしてみましょう。

テスト用のファイルはジェネレータを使わなくても作成出来るので、
今回は、手入力でファイルを作成します。

spec/routing/users_routing_spec.rb
を新規作成し、以下のように記述して下さい。

spec/routing/users_routing_spec.rb
require 'rails_helper'

RSpec.describe "routes for Users", type: :routing do
  describe 'ルーティングのテスト' do
    it 'createアクションのルーティング' do
      expect(post("/users")).to route_to("users#create")
    end
  end
end

【解説】

Usersroutingについてのテストであることを明示。

/usersにPOSTしたら usersコントローラーのcreateアクションが動くことをテストしています。

おわりに

いかがでしたでしょうか?

超基本的な内容ではありますが、書くことに対しての苦手意識が少しで減ればいいなと思います。

私も書きながら調べていましたが、検索でヒットする内容には古い情報が多く、苦労しました。

今後も以下最新情報を参照しつつ勉強していこうと思います:point_up:
RSpec Rails 3-9 - RSpec Rails - RSpec - Relish

参考にさせて頂いたサイト(いつもありがとうございます)

使えるRSpec入門・その1「RSpecの基本的な構文や便利な機能を理解する」 - Qiita
使えるRSpec入門・その2「使用頻度の高いマッチャを使いこなす」 - Qiita

使えるRSpec入門・その4「どんなブラウザ操作も自由自在!逆引きCapybara大辞典」 - Qiita
Ruby on Rails のテストフレームワーク RSpec 事始め - Qiita
全国のSeleniumer必読 - Qiita
undefined method `visit’ when using RSpec and Capybara in rails
capybara cheat sheet · GitHub

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

RSpecに画像ファイルアップロードのテストを通す

FactoryBotとCarrierWaveを使ってRSpecに画像ファイルアップロードのテストを通す

備忘録

FactoryBotとCarrierWaveを使ってRSpecに画像ファイルアップロードのテストを通す方法

環境

ruby 2.6.3
rails 5.2.3
carrierwave
FactoryBot-rails

1.spec内にfixturesディレクトリを作成

2.model内のimageuplorderの記述があるか確認

3.FactoryBot内でpictureアップローダを呼び出す

factories/feed.rb
FactoryBot.define do
  factory :feed do
    title { 'タイトル' }
    content { '投稿内容'}
    picture { Rack::Test::UploadedFile.new(File.join(Rails.root, 'spec/fixtures/rspec_test.png')) }
    user
  end
end

参考にしたサイト

RSpecに画像ファイルアップロードのテストを通す

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

【初心者】ターミナルとお友達になってみる

ターミナルと仲良くしたい

プログラム初心者がターミナルと仲良くなるために、
ご挨拶(命令)をまとめてみました。

ターミナルでの基本命令

pwd

$ pwd

print working directoryの略。
現在のディレクトリの表示を行う為の命令

cd

$ cd

change directtryの略。
ディレクトリ間を移動したい時に使う命令。

ls

$ ls

list の略。
現在のディレクトリにいるファイルを一覧で表示してくれる。

exit

$ exit

サーバーとの接続を切断するときに使う。
パソコンの電源をポチっと落としてシャットダウンするイメージです。

clear

$ clear

ターミナルに表示されているテキストを綺麗さっぱりリセットをかける。
スッと消えると気持ちが良いです。

irb

$ irb

Interactive Ruby の略。
Rubyを使用する場合に使い、
主にRubyのコードの動作を確認する為に利用を行う。

rails g controller posts

$ rails g controller posts

Railsのコントローラーを新たに設置する命令。
これを行う事でmodelとviewの処理を繋ぐ事ができる。

rails g model post

$ rails g model post

Railsのモデルを新たに設置する命令。
これを行う事でデータベースへのアクセスへを処理を
行う箇所を設置する。

rails db:migrate

$ rails db:migrate

Railsでマイグレーションファイルを生成する命令

rails db:rollback

$ rails db:rollback

Railsでマイグレーションファイルをロールバックさせる命令

rails db:migrate:status

$ rails db:migrate:status

マイグレーションファイルがデータベースに反映されているかチェックする命令

2791EF70-5894-4051-8257-8A9D1BF25766.png
ターミナル内で

  • UP…適用されている
  • DOWN…適応がされていない状態の為、そのまま修正や削除が可能。

覚えておくとちょっと幸せになる豆知識

自動補完(頭文字を売ってtabキー

これは結構便利だなと思ったんですが、
例えば「test」というファイルを指定したい場合、
「t」まで打ってtabキーを押すと残りの文字を自動補完してくれます。(ターミナル頭良い!!)
ただし同じディレクトリ内に「test」「template」みたいに同じ頭文字が並んだ場「te」で
表示が止まってしまいます。
これは「同じ頭文字2つあるけどどっちやねん」ってターミナルが聞いてくれているので、
「tes」か「tem」まで打てば各々を表示してくれます。
短いファイル名だとあまり恩恵は少ないですが、長いファイル名の正確な綴りって
うろ覚えの場合が多いので助かっております。

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

case文で条件分岐

使い方

対象の値が条件に合うかどうか調べます。
合っていたら処理を実行します。

case 対象
when 条件1
  条件1
when 条件2
  処理2
...
else
  処理3
end

例1

fruits = banana

case fruits
when "apple"
  puts "It's red!"
when "banana"
  puts "It's yellow"
when "orange"
  puts "It's orange"
else
  puts "I don't know"
end

結果
It's yellow

例2

score = 65

case score
when 90..100
  puts "A+"
when 80..89
  puts "A"
when 70..79
  puts "B"
when 60..69
  puts "C"
else
  puts "F"
end

結果
C

if文との使い分け

条件式といえば if文 が真っ先に思いつきます。
上の例も if文 で書き換えることも可能です。

では、どう使い分けるか。。。

基本 → if

条件式が複数あり、対象がすべて同じ場合 → case



僕はこうしてます。
caseを使ったほうが可読性は上がります。

より良い意見あったらコメントお願いします。



ではまた!

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