猫の魔法

主にruby系の技術メモを記載

今日の学び 2017/10/17

だいぶブログを休んでいたが、やっと生活が落ち着いて来たので学んだ事があったら書き留めて行こうと思う。 空白期間にあった話は、気が向けば今度記事にしようと思う。

とりあえず平日はrubyvim関連で個人的にメモって置いた方が良さそうなことを記載して行こうと思う。基本的にコード書く時間を優先するので、毎日更新はしないかもだけど。。。。

※気になる所あったらツッコミ歓迎です。

正規表現の比較

比較だけしたいなら===で比較する。正規表現側が左辺値になるので注意

str = 'hoge'

/hoge/ === str 
=>true
/wan/ === str 
=>false

mapでハッシュのキー値を StringからSymbolに

ymlファイルからデータ読み込むと、キー値が文字列のハッシュで読み込まれるので、それをsymbolに変えたいと思った事がきっかけ。簡単にやるならmapするのがいい。

sample = {"hoge"=>"neko"}
sample.map {|key, value|  [key.to_sim, value] }.to_h
=>{:hoge=>"neko"}

上の説明

mapはレシーバーをブロック内で展開し、その評価結果を1要素として配列を作って返す。それをto_hするとハッシュに戻る仕組み。

gsubの謎

Codewarsでよく分からない回答があったので、自分なりに調べてみた。

問題はカード番号のマスキングのように下4桁以外の文字なり数字を#でマスキングするという物で、 自分は以下のような回答で提出した(色々褒められた物ではないが。。。)

def maskify(cc)
  cc.to_s[0, cc.length-4] = "#"*(cc.length-4) if cc.length > 4
  cc
end

実際に他の人の回答を見てみると、以下の回答に支持が集まっていた。

def maskify(cc)
  cc.gsub(/.(?=....)/, '#')
end

確かにシンプルですごいと思ったのだが、
どうしてこれで条件を満たすのかパット見分からなかったので調べてみた。

調査

まずgusbだが、 これは正規表現で一致した文字列を第二引数の文字に変換した文字列を返すという物である。

問題は正規表現の方だが、 .は任意の1文字の文字列、?=は肯定先読みという物で、正規表現の一致した部分の前の部分を一致として返す物になる。

この説明だとよく分からないので、肯定先読みの具体例を出すと、hogehの場合、ogehが(?=....)と一致するので、hが一致した部分として返される事になる。
hogehogehogeの場合はhoge(?=....)と一致するのでhogehogeの最後のeが一致した部分として返される。
ちなみにhogeの場合は(?=....)と一致するが、直前の文字はないのでrubyでは空が返される。

.(?=…)と.*(?=…)の違い

今回は一致した文字の前の文字列も欲しいので、.*(?=....)という風にするのが、一致させるという意味では正しいと自分は思っていた。
しかし、gsub(/.*(?=....)/,'#')だとhogehogehogeの場合、hogehogeが一致している事になり、これが#と置換されてしまう。
本来であればhogehogeを########と置換して欲しいので、このやり方は上手く行かない。

対して、gsub(/.(?=...)/,'#')の場合、hogehogehogeは
1回目:hがogehと一致する為一致
2回目:oがgehoと一致する為一致
3回目:gがehogと一致する為一致
・・・
8回目:eがhogeと一致する為一致
9回目:hはその後ろに任意の4文字がないので一致しない

という風に動き、1〜8回目それぞれのタイミングで文字が#と置換される事が実際に試して見て分かった。
つまりgsubは一致した項目を最後まで次々と処理して第二引数の物に置き換えて行くことになり、今回の処理が上手く動くことになるようだ。

ここまで分かって、あの問題を解く人達は素直にすごいと思った。

プログラミング能力を鍛える手段

今日は毛色を変えて、自分が使っているプログラミング能力を鍛える方法を紹介して見る

自分の場合、普段プログラムを組まないので、出来るだけ組む時間を取りたいと思っている。
だからと言って、何かアプリケーションを作るとなると、環境設定や設計に時間が取られ、 組む前に貴重な時間を食いつぶしてしまう事も珍しくない。

そこで、自分はコーディングの練習用として、codewarsというサービスを使っている。

www.codewars.com

codewarsはオンラインで出来るプログラミング学習用SNSで、言語を問わず色々な課題が公開されていて、無料で課題を試す事が出来る。

プログラミングの課題は、KATAというプログラム課題郡の中から適当な課題を選び、設問者が用意したテストをすべて通るようにプログラムを組む形で行っていく事になる。
組んだ後は他の人の回答も見れる為、他の人がどういう風に問題を解くかというのを見れるという意味でも為になる物となっている。
他に、KUMITEという、よりよいコードを勝負し合うという物があるが、自分はKATAの方しか使っていない。

基本英語だが、いい問題も多いのでとりあえずgoogle翻訳掛けながらでもやる価値はある。

自分の場合、あまり難易度が高い物だとこれもそもそも問題を理解するのに時間が掛かるので、
喋れるように練習するという意味ではまずは難易度が低い物を何個も試してみている。

興味のある方は是非。

[個人的メモ]Webを支える技術(その1)

公私共々忙し過ぎて、久々の更新。
少なくとも週1更新位のペースは維持したいが、どうなるやら…

少し前に「Webを支える技術」を買って読んだ。

普段何気なく使っているHTTPやHTMLと言ったWebの技術を、
特定のプログラム言語ではなく、その技術にフォーカスして学べるという意味ではとてもいい本だった。
忘れないように読んだ内容を記録しておく。

Webを支える技術 -HTTP、URI、HTML、そしてREST (WEB+DB PRESS plus)

Webを支える技術 -HTTP、URI、HTML、そしてREST (WEB+DB PRESS plus)

2.6章 SOAPとRESTの歴史

Web系の話は大学時代に少し学んだ程度だったので、何故現在RESTが主流になったのかが記載されていたのはとても参考になった。
SOAPがRESTの対立項だったというのは知らなかった。

7.11章 べき等性と安全性

すぐ分からなくなる「べき等」という言葉。
「ある操作を何回行っても結果が同じこと」という説明がとてもしっくり来た。

また、メソッド毎にべき等と安全についての記載があるのは良かった。
下記の区分分けはとても分かり易かった。

  • べき等で安全:GET,HEAD
  • べき等で安全でない:PUT,DELETE
  • べき等でもなく安全でもない:POST

7.12章 PUTがべき等でなくなる例

PUTでべき等でなくなる例と、PUTでべき等である例が記載されていて、なるほどと思った。

PUT /pen/price HTTP/1.1
Host: hoge.jp
Content-Type: text/plain;

100

上記でペンの値段が+100円されるのであれば、もう一度同じリクエストを投げるとペンの値段がさらに+100円される事になる。
これは、結果が変わってしまう事になるので、べき等ではない。

逆に、上記でペンの値段が100円になるのであれば、もう一度同じリクエストを投げてもペンの値段は100円のままになる。
この場合、結果が変わらない為、べき等になる。

つまり、べき等かどうかはHTTPリクエストのそれのみで決まるのではなく、HTTPリクエストを受けた側がどう解釈するかが重要になる。

今日はここまで

再々考:classとそれが読み込んでいるmoduleの一覧を表示する方法(bugfix)

nekomaho.hatenablog.jp

前に上記の記事を書いたが、改めて見直して見た所、新メソッドと旧メソッドの比較が全く意味の無い物となっている事が分かった。

※記事のアップ時間が夜中の2時な事を差し引いてもちょっと酷いと思う。

コードを再掲するので、モノ好きな方は何が間違っているのか見て見て欲しい。
ちなみに出力は間違っていない。
答えは下に掲載している。

def display_class_modules_old(class_name)
  print "class:#{class_name},modules:#{class_name.included_modules}","\n" if class_name.respond_to?(:included_modules)
  display_class_modules(class_name.superclass) if class_name.respond_to?(:superclass)
end


def display_class_modules(class_name)
  class_name.ancestors.each do |klass|
     puts "class:#{klass},modules:#{klass.included_modules}" if klass.is_a?(Class)
  end
end

puts 'same' if display_class_modules(1.class) == display_class_modules(1.class)


#=> class:Integer,modules:[Comparable, Kernel]
#=> class:Numeric,modules:[Comparable, Kernel]
#=> class:Object,modules:[Kernel]
#=> class:BasicObject,modules:[]
#=> class:Integer,modules:[Comparable, Kernel]
#=> class:Numeric,modules:[Comparable, Kernel]
#=> class:Object,modules:[Kernel]
#=> class:BasicObject,modules:[]
#=> same












間違い

間違いは以下の3つ。

1: 同じかどうかの比較をしている物がおかしい

puts 'same' if display_class_modules(1.class) == display_class_modules(1.class)

上記、ifの左辺値と右辺値がスペルミスで同じメソッドを比較している。それは同じ結果になるだろう。

2: oldがちゃんとoldのメソッドを呼んでいない

仮に1を直すと、display_class_modulesとdisplay_class_modules_oldは異なる値となる。 何故なら、これもタイプミスだがdisplay_class_modules_oldが再帰的に呼んでいるメソッドがdisplay_class_modulesだからだ。
※こう言うときはaliasを使うのが正しいのでしょう。。。

3: そもそも比較出来ていない

仮に1と2を直したとしても、1で行っているようなメソッド同時を==で比較する方法では正しく比較が出来ない。
なぜなら、この2つのメソッドは返している値が異なるからだ。
新しい方はancestorsの戻りが返るが、古い方はputsしているだけなのでnilが返る。
本来なら、何かしらの戻りを比較するか、putsで出している内容を比較する必要がある。

1〜3を統合して、直したのが以下のロジックになる。

def display_class_modules_old(class_name)
  calls ||= []
  calls = [{class: class_name, modules: class_name.included_modules}] if class_name.respond_to?(:included_modules)
  calls << display_class_modules_old(class_name.superclass) if class_name.respond_to?(:superclass)
  calls.flatten
end

def display_class_modules(class_name)
  calls ||= []
  class_name.ancestors.each do |klass|
     calls << {class: klass,modules: klass.included_modules} if klass.is_a?(Class)
  end
  calls
end

old=display_class_modules_old(1.class)
new=display_class_modules(1.class)

old == new ? puts("same") : puts("different")

#=> same

これで本当に正しいのか。。。何か気がついた方居ましたら教えて下さい。

ちなみに自分は両方のメソッドの中身を書き換えたが、
恐らくメソッドの中身を直接書き換える事なく比較する方法もある。
これは時間あるときにチャレンジしてみたい。

railsアプリケーションを作った

しばらくぶりの更新。 仕事の忙しさとアプリケーション作成に時間を取られて、随分ご無沙汰になってしまった。

今後は今まで通り、こっちに割く時間も取ろうと思う。

とりあえず作っていたrailsアプリケーションがある程度公開出来る物になったので、このブログにも貼っておこうと思う。

GitHub - nekomaho/task-controller

task-controllerは簡易なタスク管理を行う用と思って作ったもの。

本当はクリティカルパスを自動的に求めてくれるような物を作ろうとしたが、 時間(一週間で取れる時間がいい時で8h程度だったので、とりあえずアウトプットを出した感じ)と技術力の点で一旦タスクを登録して関連付け(これも条件付きで、やりたいことは出来ていない)だけ出来るような物を作成した

公開した物については、まだアルファ版な上、やり残しも大量にあるのでちょっとずつ消化していけたらいいなと思っている。

再考:classとそれが読み込んでいるmoduleの一覧を表示する方法

※この記事の最後のプログラムはバグがあります。正しい物は下記の物を参照して下さい。
再々考:classとそれが読み込んでいるmoduleの一覧を表示する方法(bugfix) - 猫の魔法

今日、Module#ancestorsという インスタンスメソッドを目にしたのだが、このメソッドを使えば、このブログの一番最初に書いたプログラムがもっと簡単に組める事に気がついた。

nekomaho.hatenablog.jp

def display_class_modules(class_name)
  class_name.ancestors.each do |klass| 
     puts "class:#{klass},modules:#{klass.included_modules}" if klass.is_a?(Class)
  end
end

古いやつと比較してみるとバッチリ結果が一緒になった。

def display_class_modules_old(class_name)
  print "class:#{class_name},modules:#{class_name.included_modules}","\n" if class_name.respond_to?(:included_modules)
  display_class_modules(class_name.superclass) if class_name.respond_to?(:superclass)
end


def display_class_modules(class_name)
  class_name.ancestors.each do |klass|
     puts "class:#{klass},modules:#{klass.included_modules}" if klass.is_a?(Class)
  end
end

puts 'same' if display_class_modules(1.class) == display_class_modules(1.class)


#=> class:Integer,modules:[Comparable, Kernel]
#=> class:Numeric,modules:[Comparable, Kernel]
#=> class:Object,modules:[Kernel]
#=> class:BasicObject,modules:[]
#=> class:Integer,modules:[Comparable, Kernel]
#=> class:Numeric,modules:[Comparable, Kernel]
#=> class:Object,modules:[Kernel]
#=> class:BasicObject,modules:[]
#=> same

少しずつrubyにも慣れて来たかな。もっとプログラムを書く時間が欲しい。

is_a?についてのメモ

Highlineのソースを読んでいたら以下の部分でちょっと考えてしまった。

      if template_or_question.is_a? Question
        template_or_question
      else
        Question.new(template_or_question, answer_type, &details)
      end

悩んだのはtemplate_or_question.is_a? Questionの部分で、名前だけ見るとis_a?ってto_aと同じ意味(つまり配列かどうかを返す)のかと思って読んでいたが、そうすると後ろのQuestionが意味不明。調べてみたらis_a?はObjectのメソッドで、引数のクラスかどうかを返す物だった。

※リファレンスにもあるが、is_a?は対象が「引数 をインクルードしたクラスかそのサブクラス のインスタンスである場合」もtrueになるので、厳密に確認したいならinstanse_of?を使うのが正解。

名前から意味を推測して読むとこう言う落とし穴があるので注意せねば。。。

ちなみに上のソースと同じ文脈で配列かどうかを見るのは、

[1,2,3].is_a? Array

となるみたい。

deviseのメールアドレスバリデーション

最近deviseを使う事があったのだが、メールアドレスのバリデーションがどうなっているのか気になっったので調べてみた。

devise/devise_test.rb at master · plataformatec/devise · GitHub

メールアドレスのバリデーションテストは上記で行っている。

で、実際の処理はこっちで、

devise/devise.rb at master · plataformatec/devise · GitHub

モジュール変数としてemail_regexpを定義していて、ここにマッチしないメールアドレスを弾いている。

弾いているのだが、正規表現を見ると以下のように随分あっさりしたチェックになっている。

mattr_accessor :email_regexp
@@email_regexp = /\A[^@\s]+@[^@\s]+\z/

このチェックだと「ほげ@ほげ」でもチェックをすり抜ける。

どういう経緯なのか調べてみると前はもう少し厳しく見ていたようだ。

ただ、テストケースにexample@ttを追加したら通らないという話になり、

Devise::email_regexp rejects unusual but valid email · Issue #3997 · plataformatec/devise · GitHub

結果的に複雑な正規表現をするくらいなら、いっそ簡単にしようという話に纏まったみたい。

updated email_regexp and added test cases by kimgb · Pull Request #4001 · plataformatec/devise · GitHub

なのでメールアドレスを厳密に見たい各位は、deviseを使う際はバリデーションを変更する必要がある旨を留意する必要がある。

※ここからは検証が必要。 恐らくだが、email_regexpを書き換えればバリデーションを変えられるはず。何処で書き換えればいいかはちょっと時間見つけて調査してみようかな。

has_many throughの苦悩

railsのhas_many throughの使い方が分からなかったという話。

モデルの関連付けでhas_manyは分かりやすい。1対多のモデルは1側にhas_manyをつければいい。

問題はhas_many throughでこれが良く分からない。多対多の時につけるというが多対多の場合、リレーション用のテーブルがあるとは限らない。必ずリレーション用のテーブルが必要なのか?引く側も引かれる側も両方につけるの?そもそも多対多のモデル自体を一対多にした方がいいのでは?学生の時にrailsを勉強した時は恥ずかしながらそこがまったく腑に落ちず理解が全然進まなかった。

月日は流れ、今になってもう一度railsを学んでみると、そもそもDBを主軸に考えるから良く分からなくなることに気がついた。今のrailsガイドは非常に分かりやすい。

Active Record の関連付け (アソシエーション) | Rails ガイド

このガイドを読んで分かったのはhas_many throughは多対多を表すというよりは、「このモデルとの関連付けはこのモデルを通して行うよ」という宣言という事だ。

なので、別にモデル関の関連が多対多でなくてもthroughを使うことで直接キーがないモデルを操作することが出来る。 よって「has_many throughを使う時は必ずリレーション用のテーブルが必要なのか?」という問はYesという事になる。

また以下のような正規化されていない多対多のモデルについてはhas_manyで結び付けられるような気がするのだが、そこら辺はネットで情報を見つける事ができなかった。 これについてはどっかで実験してみようと思う(そもそも正規化しろという話だが、既存システムの載せ替え等の場合、こう言うまずい作りが多々ある気がする)

f:id:nekomaho:20170314003934p:plain

モデル≠DBのテーブルで無い事にもっと早く気がつけば良かったという話でした。