Seiichi Yonezawa

“How creativity is helped by failure”

開発日記 #10

今日巷ではハロウィンでした。例えばGitHubでもグラフの色がハロウィン仕様になったり、この時期は世間で騒がれていることと関わらずに、何か小ネタのようなものを披露できたらと思い、また今年のハロウィンを過ごしてしまいました。いえ、厳密に言うとこの時期に向けて何かをするよりも、常に動いているせいか、今日も通常通りだったのが正しいです。

さて、そこまで言うからには今日は何をしていたのかというと、昨日から書き始めたクイズゲームを進めていました。もともとは練習がてら、いい題材はないかと考えていたら、かつて誰かにクイズを作るのは簡単にできるとか、そんなことを吹聴した記憶がパッと現れて、無性に書きたくなってしまったわけです。私自身も自分が何をしたいのかはよくわかりませんね。

それで、最近Railsに関しては理想を貫けるようになりつつあった気がしていて、Rubyそのものの自信もかなりついてきたような気分になっていたのですが、Railsに対してはあくまでRailsに対してのみであって、Rubyに関してはやっぱりまだまだでした。RSpecとMinitestを何度か行き来しているのですが、最近はMinitestに楽しみを見出すのがだんだん辛く思えてきました。RSpecよりもレスポンスが早く感じるのはいいのですが、肝心のテストがRailsに比べるとやっぱり便利なアクセサが使えなかったりして心苦しい。そんな状況で、今日はいくつかテストを書いていました。せっかくのハロウィンなのに、すごく地味な作業でした。それでもテストを通しきると妙な達成感がありますね。だからこそこうしてブログも書けるのでしょう。

いつものごとくとりとめのない感じで終わりそうですが、今回書くクイズはあくまで処理そのものよりも、もう少し先の領域で挑戦したいことがあるのです。それについてはおいおい書いていく予定ですが、それはいつになるでしょうか。乞うご期待?

開発日記 #9

昨日はしばらく眠っていたRaspberry Piを掘り出して、一日中インストールに費やしていました。その傍らでRailsも書いていたのですが、前回の「GhostからJSONを取り込む」機能に続いて、「既に登録してある投稿用のモデルを変換する」機能を追加しました。といっても、STIなのでデータベースのtypeを書き換えるだけです。フォームオブジェクトやリダイレクトが若干手こずりましたが、結果的には上手くいきました。片手間で進めていただけあって、特にもう書くことがなくなってしまいました。現状はビューのテストがほとんど手付かずなので、その辺りを直していこうと思っています。

開発日記 #8

今日とある案件にて、某サイトの「フォーム入力支援スクリプト」なるものに取り組んでいたのですが、言い換えるとほぼXSSな感じで大丈夫なのかなーと思いつつ、ブラウザバーにjavascript:というプレフィックスを加えるとURLがJavaScriptとして実行されることを知ったそんな今日この頃。

スクレイピングとして、HTMLで記述されたサイトの情報をかいつまんで収集するようなものを作ったりしたことはあったのですが、今回も基本はそのページに存在しているDOMに依存していて、やることはそんなに変わりません。ActiveRecordから値を拾って、対応するタグに代入してやるだけです。簡単です。

しかし、何を血迷ったのか、JavaScript絡みのテストを書き慣れていないせいか、TrueとかFalseのboolean型を文字列で出力する仕様にしたんですね。trueとかfalseとか書けばいいところをわざわざ'true'とか'false'にしたわけです。前置きが長くなりましたが、以下のコードをごらんください。

document.querySelector('input[type=checkbox]').checked = 'true'
// チェックされる
document.querySelector('input[type=checkbox]').checked
// true

document.querySelector('input[type=checkbox]').checked = 'false'
// チェックされる
document.querySelector('input[type=checkbox]').checked
// true

なんと、文字列で'false'を代入してもチェックボックスがチェックされた状態になってしまうわけです。始めはハッピーパスな感じでテストを書いていたので、原因の特定に小一時間悩みました。

document.querySelector('input[type=checkbox]').checked = false
// チェック外れる
document.querySelector('input[type=checkbox]').checked
// false

document.querySelector('input[type=checkbox]').checked = ''
// チェック外れる
document.querySelector('input[type=checkbox]').checked
// false

JavaScriptの世界では、はっきりfalseとか空白とかを指定しないと意図した動作になってくれませんでした。if文で分けてtrueとかをはっきり代入しなおせば良かったんですが、何をトチ狂ってしまったのか、StackOverflowで文字列をbool型に変換するみたいな文言で検索して同じように施工しました。

Boolean('true')
// true
Boolean('false')
// true
Boolean('')
// false
Boolean()
// false

document.querySelector('input[type=checkbox]').checked = Boolean('false')
// true

なんというか、先ほど例にあげたコードのままなのですが、てっきりBoolean()ならRegexpみたいに文字列を都合よく解釈してくれると思っていました。new Boolean()の書き方でも結果は変わりません。やっぱりこれも原因を突き止めるまで無駄に時間を費やしてしまいました。Rubyでも'' || trueとか0 || trueはtrueが返ってこないのですが、正直JavaScriptには驚かされました。

それでも私はJavaScriptが好きです。これからも精進を続けます。

開発日記 #7

今日はついにGhostで編集していた投稿を、このサイトに引っ張ってくることに成功しました。また、いくつか投稿を見直していましたが、最初はエディターを作ると意気込んでいましたね。エディターを作るはずが、Railsの挙動を見直しているうちに、結局また遠のいてしまいました。エディター自体はやはり実用レベルにしようと思うと、結構な労力が必要です。しばらくはGhostで投稿して、投稿をインポートし、このサイトで編集して、それから公開というような形にしていく予定です。他のサイトもその形式でならやりやすいでしょうか。

投稿のインポート機能に関してはまた別途作り方をメモに残していきたいなと思っていますが、明日からはチャットでbotと連携できるようにして、最終的にはTwitterなどの外部サイトとも連携していくようにしたいと思っています。ある程度こうして形に残せるようになってくると、またいろいろとやりたいことが湧き上がってくるのですが、今はまだこの調子で地道に進めていきたいですね。

開発日記 #6

先週から引き続き今週も同じようにRailsの開発を進めていました。しかし、進めていてどうもおかしい部分があって、ソートしているのにソートできなかったり、WillPaginateのページ分割が1ページ目に全て表示され、2ページ目以降が空になってしまったりと、なかなか不可解な現象が発生してしまいました。

原因としてはRails Style Guideにあるfind-eachというメソッドを使っていたことが原因でした。この説明だとviewで使えるかどうかには言及していなかったので、eachでイテレートするよりも効率的なのではと勘違いしてしまいました。この原因を探るのにほぼ作り直しのような状況から始めてしまったのですが、他のサイトでもこれから置き換えていく予定だったので残念に思えてしまいました。

また、この投稿はまだサイトの方に移行しきれていないので、現在はGhost利用しています。それで、本来は昨日の夜にこの投稿をする予定だったのですが、残念ながらパスワードを紛失してしまいました。Railsであればコンソールからmodelを直接上書きすることが可能なのですが、ghostの場合はその方法がわかりません。でもダメ元で送ってみたパスワードリセットのメールがなぜか届いていたので、こうしてログインすることができたわけです。それにしてもメールの設定はしていなかった気がするし、アドオンも特に使っていなかったのに不思議な感じです。

開発日記 #5

現在Railsで構築しているサイトはこのサイトを含めてそれぞれ専用のテーブルがあって、それぞれが独立したサイトとして運用しています。とはいえ、まだPVはおろか、投稿そのものもほぼない状況なので、まだ本格的な運用とは程遠い状況ですけどね。

しかし、せっかくそれぞれが静的なサイトではなく、データベースの下に投稿を表示する機能があるのだから、似たような開発を別々にするよりも、メインとスレーブの二種類に分けることができないかと考えました。そこで、登場するのがRabbitMQのような通信手段です。それからモノリシックからマイクロサービスに対する移行も視野に現れてくるでしょう。

もちろん機能そのものは残しておきつつ、補助的な機能をサポートしたいわけです。例えば投稿そのもののモデルは共通しているのだから、下書きを送信して、それぞれの管理下で公開するようなものが望ましいです。やはり現状はそれぞれのサイトに管理ページを設けようとしていたのですが、ただでさえややこしい実装を繰り返すのは無駄なようにも思えます。

現状はAPI通信のやり方は抑えているので、それぞれのサイトに簡易的な投稿機能を設ける予定ですが、最終的にはこれをひとつに絞り込みたいですね。

開発日記 #4

今日はRails以外のことをやろうと思ったのですが、ついつい手を出して気がついたらもう夜になってしまいました。今日はPaperclipを導入しようとしたのですが、このライブラリのデザインはすでにあるモデルに対してファイル名や種類などのカラムを追加します。

例ではUserというモデルに#avatarというメソッドを追加する一対一の関係なのですが、今回はひとつのモデルで複数のモデルに対応できるように、Polymorphicなモデルとして管理することにしました。時間はかかってしまいましたが、結果的にはうまくいきました。

Paperclipの特徴としては、標準でImagemagickをサポートしている他に、ファイルが存在しない場合のデフォルト画像を指定することができます。

<!-- 引数に画像のサイズを指定できる -->
<% if @user.avatar.present? %>
  <%= image_tag(@user.avatar(:medium)) %>
<% else %>
  <%= image_tag('medium/missing.png') %>
<% end %>

つまり、このような記述をしなくても@user.avatar(:medium)だけで画像が登録されていない時も常にURLを返してくれます。ただし、Polymorphicなクラス設計だった場合、UserImageというクラスを持ち、Image#avatarを持っていた場合はどうなるでしょうか?

@user.image.avatar(:medium) 
#=> NoMethodError: undefined method `avatar' for nil:NilClass

そうなるとImageが登録されていない場合は当然nilが返るので、結局上のようなif文を書かざるを得なくなってしまいます。
もちろん余分に四行追加していれば今日はこのように悩む必要もなかったのかもしれませんが、この問題の解決策は至って単純です。

class User < ActiveRecord::Base
  after_initialize :set_image

  private

  def set_image
    build_image
  end
end

こうすればUserを作成したり、更新しようとする時にImageが自動的に追加されるので、後のバリデーションに失敗さえしなければ先ほどのコードを使うことができます。

seeds.rbやコントローラでbuildする方法を先に試していたのですが、なぜか最後にRSpecで失敗していました。その結果この方法を思いついたのですが、今となっては無駄な記述を減らせたのでよかったです。

開発日記 #3

三連休最終日。結局3日間Railsを書き続けていました。今日はずっと先延ばしにしていたデプロイをすべてのサイトでようやく完了させることができました。私自身にとって、非常に達成感にあふれる出来事だったのですが、残念ながら外向きにアピールできるほどのものはありません。強いて言うなら、こうしてブログに記録として残しているくらい。

今日技術的な内容としては、今まであやふやだったstubが出てきたことと、Let's encryptでSSLの証明書をサーバーに導入することが以前試した時よりもずいぶんと簡単になっていたことでしょうか。これらに関しては後日残しておければ改めてメモとして保存しておこうかなと思っています。

また、今日は久しぶりにCSSのデザインもしました。いつもはBootstrapの上に直接使っているので、新しくCSSを書くことがめっきりなくなってしましました。そこで今日はあえてBootstrapのCSSのコアとなる部分のみを使うことで、全くゼロから始める必要もなく、デザインに集中できて楽しかったです。

今週は今日デプロイしたサイトをもう少し使いやすいように改良していく予定です。

開発日記 #2

Railsサーバーをデプロイし始めておよそ3ヶ月経過しました。ただ、当初計画していたほど開発を進められていません。夏は開発そっちのけで遊んでばかりいたので、最近はバリバリ開発の気力がみなぎっている気がします。そしてこの土日はRailsの開発に注力するついでに、このサイトをもう一度デプロイしなおすことにしました。

今回デプロイしたサイトは、以前からあったコンセプトそのものから変えました。ただ単に投稿できるだけではなく、それ以外の用途でも使っていけるように、ノートやタスク等のモデルを追加しました。そして、今までは個別に投稿やノート等のモデルをひとつひとつ用意していたのに対して、今回からはSTI(単一テーブル継承)を適用した設計にしてみました。

もともとサブドメインで運用していたこのサイトも、これからはベアドメインとして、Railsで運用していくことにしました。まだAPI経由でのアクセスまではできないのですが、HTTPSを設定し直してから実装してみようかなと思います。

このサイト以外でもRailsで運用をしながら、ぼちぼちとやりたいことを始めていきたいと思っています。

開発日記 #1

またしてもブログの更新が滞ってしまいました。公開した当初は出来に満足していたはずなのに、次第に更新が遠のいてしまったのはログインするまでが長かったからなのかもしれません。Lingrは接続障害が起こっていた頃も使い続けていたので、使い勝手が変われば更新できるはず。そうして導き出した答えは、ずっと作ろうと思っていたエディタをもう一度作ってみたいという思いでした。

エディタを作ることの難しさのひとつはエディタそのもののプログラムもそうですが、サーバーとの通信が占めているのではないだろうかと思っています。そして、一番厄介なのはログインの方法です。仮にエディタが作れたとしても、サーバーと正しい手段で認証できなければずっとアクセスできないか、あるいは誰もがアクセス仕放題になってしまうでしょう。

幸い今年に入って本格的にRailsを勉強し始めたこともあって、RailsにはDeviseという認証用のgemがあることを知ったのですが、このgemはブラウザの認証が前提です。認証に使われるのは当然メールとパスワードの組み合わせなので、API経由で使うことが前提だと、httpsであってもパスワードをリクエストに含めなければならないので、なんとなく不安に思えます。

それから、RailsのTokenという項目を見つけてようやくヘッダ情報をもとにログインできる方法を見つけました。ただ、これを使ってどう認証するか悩んでOAuthのようにtokenとsecretのペアにしようと思ったらうまくいきませんでした。それにOAuthに似せた実装よりもDoorkeeperというgemを利用すればよかったのかもしれません。

もっと良い方法があるはずと思案していたら、先ほどのヘッダはtokenのみで通信することを前提にしたならログインできました。ただこのままでは16進数のトークンは総当たり攻撃で突破されてしまうのではないかと不安です。そこで対抗策として不正なトークンでログインを試みた場合は応答を遅らせることにしました。ただ、これも結局非同期にリクエストを送信されたらサーバーにだけ無駄に負荷がかかりそうな気もします。WikipediaにCryptographic nonceという項目を見つけたのですが、自分で実装しようと思うと複雑になりそうなので今回はやめました。

このブログも早くAPI経由で更新できるようにしたいですね。