おもしろwebサービス開発日記

Ruby や Rails を中心に、web技術について書いています

大阪Ruby会議02でConcernsについて発表してきました

先日開催された大阪Ruby会議02で、なんとなく使われがちな機能であるConcernsの使い方について話してきました。資料はこちら。

発表内容について

Concernsに関する説明は「関心事を分離するぞ!」のような抽象的なものが多く、

  • 何を関心事として分離するとよいのか
  • Concerns以外のロジックを分離する方法

を知らずに自分なりの解釈でConcernsを使うとかえってコードを読みづらくする形でmoduleが作られることになりがちです。この発表を通じて少しでも読みやすいRailsアプリケーションのコードが増えてほしいなと思います。

もし内容について感想や質問などを書きたい方がいたら、clean-rails.orgこの発表用のスレが立っているので書き込みお願いします!

このスライドだけだと分かりづらいところがありそうなので、どこかで再演するなり別途文章として公開したいところです*1

追記: 別途文章として公開しました! 我々はConcernsとどう向き合うか - おもしろwebサービス開発日記

f:id:willnet:20190915153710j:plain

所感

うちの子(1歳児)が僕の発表を見学に来ていたのですが眠くてぐずっていたようで参加者の方々にはご迷惑おかけしました*2><

大阪、昔1年ほど仕事の関係で住んでいたので当時よくいた場所をまわってみたいな、と思っていたのですが諸事情あってあまり外に出れなかったので、また次回の大阪Ruby会議で来れるようにしようと思います。主催者の皆さん、素晴らしい機会をありがとうございました!

*1:最近めっちゃ忙しくしているのであまり期待できません…><

*2:温かい言葉をくれた方々ありがとうございます

ajax_error_renderer 0.2.0をリリースした

個人的に便利に使っているajax_error_rendererなのだけど、フォームが長いときに「エラーメッセージがブラウザのスクリーン外に表示されてしまいユーザが気づけない」というケースがあったので、デフォルトでエラーメッセージのある場所までスクロールするように修正しました。どうぞご利用ください(\( ⁰⊖⁰)/)

参考: turbolinksとform_withを便利に使うためのgemを作った - おもしろwebサービス開発日記

Rails Developers Meetup 2019で、再び綺麗なテストコードの書き方について発表した

先日行われたRails Developers Meetup 2019で、Clean Test Code Revisedというタイトルで発表しました。スライドはこちら。

動画も上がっているようなので興味のある方はどうぞ*1

所感

ご存知のかたもいると思いますが、この発表は2017年5月に行われたRails Developers Meetup第一回目で発表した内容を更に一歩進めたものとなっています。

Rails Developers Meetup で綺麗なテストコードの書き方について発表した - おもしろwebサービス開発日記

当時僕の頭の中にあった「こういうケースのときはこう書く。なぜならこうだから」というものを点で出したのが前回の発表で、それらを「脳に負荷をかけない」という線でつなげてまとめたのが今回の発表になります。

テストコードをレビューしたときに「これなんか読みづらいな…」と思っても「なぜこれが読みづらいのか」が説明できないとスルーしてacceptしてしまいがちになります。現状、RSpecで書かれたテストコードの可読性について書かれた書籍やネット上の記事はほとんどなく、みんな試行錯誤で(多くの場合はいろいろ諦めつつ)頑張っているのではないでしょうか。なのでこの発表内容を活用して今一度テストコードの可読性について見直してみてもらえると嬉しいです。

今回の発表内容は、総論としてはみんな賛同してもらえたようなのですが各論(例: letよりなるべくlet!を使う)は違う意見を持っている人が結構いるみたいです。議論の場を作ってみたので「俺はこう書いている」みたいな意見があればぜひ教えてほしいです!

Clean Test Code Revisedの発表内容について - clean-rails.org

f:id:willnet:20190323111424j:plain

*1:僕は自分の動画は見てないです。恥ずかしいので><

株式会社ウィルネットは設立2周年を迎えました

昨日、2月21日は弊社の設立記念日でした*1

というわけで法人成りして2年経ちました。最初はフリーランスの延長のつもりだったのですが、会社という形態にしたことで意識が少しだけ変わってきた気がします。

これまではなんでも全部自分一人でやるというのが自然だったのですが、会社の予算を使って誰かに仕事を手伝ってもらう、という形態を徐々に受け入れられるようになってきました。空いた時間で少しずつ開発を進めているsavanna.ioも、いまはデザインに関しては本職にお願いするようになっています。

昔は、一つのスキルだけを伸ばすのではなくいろんな分野を学んでいくのがよい、と思ってデザイン含めいろいろ勉強してたのですが、現状では一つのスキルをガンガン伸ばして他は誰か得意な人にお願いする方が効率良いのでは?という気持ちになっています。

予算を使ってコーポレートサイトもちゃんとしたものを作りました。もともとペライチというサービスでそれっぽいものを作っていたのですが、問い合わせフォーム経由でスパムが毎日のように届くのでもう少し自分でコントロールできる環境が欲しくなったのでした*2

株式会社ウィルネット|Webサービス開発コンサルティング

なにか相談がある方は↑の問い合わせフォームからでもtwitterなどからでもよいのでお気軽に問い合わせください。

おかげさまで技術顧問業、savanna.ioの開発、執筆、登壇など相変わらず忙しくしています。直近だとRails Developers Meetup 2019に登壇予定です。テストの話をする予定なので興味ある人はどうぞ。

3期目も頑張ってやっていくのでよろしくおねがいします(\( ⁰⊖⁰)/)

*1:景気よくホールケーキを買おうかと思ったけど太るのでやめました

*2:まれに本当の問い合わせも来るので迷惑メール扱いにできないのがつらい

銀座Railsで「個人でつくるwebサービス」という話をした

昨日行われた銀座Railsで登壇させていただきました。

資料はこちら。

所感

複数の主張したいことを一つの発表に盛り込んでしまったので、ちょっとぼんやりした発表になってしまったかもなーという反省があります。

  • 個人でwebサービス作るのはいいぞ
  • 個人開発をモチベーションを落とさず継続する仕組み
  • Railsは個人or少人数で小規模なサービスを作るのに向いてるめっちゃべんりなライブラリなんですよ
  • turbolinks&stimulusはいいぞ
  • rubocopデフォルト設定でも全然普通に開発できるんですよ

それぞれのトピックごとにもっと深掘りして(もしくは角度を変えて)話せそうなので、どこか別の機会があれば喋ろうかなと思います。

次は3月のRails Developers Meetup 2019で登壇予定ですが、今回とは全く違った話になる予定です。乞うご期待。

turbolinksとform_withを便利に使うためのgemを作った

前提

rails標準のわりに使っている人の少ないturbolinksですが、僕は便利に使っています。turbolinksはご存知の通り、リンクを全部ajaxリクエストに置き換えてページ遷移を早くするライブラリです。

turbolinksが実現している「リクエストは全部ajaxにして、フルのページ遷移を避けたほうが速い」というロジックはformでも同じことが言えます。

Railsは5.1からform_withというviewヘルパーが増えました。これは既存のform_tagform_forを統合するヘルパーという位置づけですが、それ以外に大きな違いとしてデフォルトでdata-remote="true"がformタグに付与される、というのがあります。この振る舞いはformもturbolinksみたいにajaxにしようぜ!というDHHの思想の現れなのだろう、と推測します。実際、form_withのPRでDHHがそんなようなことをコメントしていますね。

Make remote: true the default. Full-page changes after submissions are rough. When using Turbolinks, a normal redirect will generate a Turbolinks.visit() call, and otherwise there's SJR. (We could consider having config.action_view.forms_remote_by_default that you could set to false, for people going old school).

turbolinksはajaxを利用したform submitの一部に対応しています。具体的にはturbolinks gemはredirect_toの挙動を差し替えており、form submitによるajaxリクエストをしたときのリダイレクト処理を非ajax時と同じ*1になるようにしています。書き換えたredirect_toメソッドは、もしget以外のajaxリクエストから呼び出された場合、302を返さずに、クライアントサイドでTurbolinks.visit()という画面遷移用のjs関数を実行するためのレスポンスを返します。この結果、ユーザとしては単にformをsubmitしてからredirect_toしたのと同じような画面遷移になります。

先程「turbolinksはajaxを利用したPOSTリクエストの一部に対応しています」と書きました。つまり対応していない部分があるということです。DHHが書いたコメントにもありますが、バリデーションエラーになった際の対応は、SJR(Server-generated JavaScript Responses)などを利用して各自でエラーメッセージを表示する処理を書かなければなりません。このあたりもうちょっとうまくできるとturbolinksもう少し普及するんじゃないのかな、という気がします*2

最初はSJR部分を普通に書いていたのですが、統一的なインタフェースがあったほうが良さそうだな、と思ったのでgemにしてみました。

willnet/ajax_error_renderer: a validation error renderer for ajax request

ajax_error_rendererとは

render_ajax_errorという、ajaxでエラーメッセージを表示するメソッドを提供する小さなgemです。次のようにAjaxErrorRendererをincludeすると使えるようになります。

class ApplicationController < ActionController::Base
  include AjaxErrorRenderer
end

ajaxでリクエストが来てエラーメッセージを表示する場面でrender_ajax_errorメソッドを使います

class UsersController < ApplicationController
  def create
    @user = User.new(params.require(:user).permit(:name))
    if @user.save
      redirect_to users_path, notice: 'You created a user successfully!'
    else
      render_ajax_error model: @user
    end
  end
end

すると#errorなDOMに動的にエラーメッセージが表示されるようになります

<%= form_with(model: @user) do |form| %>
  <div id="error">
    <%# ここにエラーメッセージが表示される %>
  </div>

  <div class="field">
    <%= form.label :name %>
    <%= form.text_field :name %>
  </div>

  <div class="actions">
    <%= form.submit %>
  </div>
<% end %>

基本的な使い方は以上です。簡単ですね。どうぞご利用ください。

*1:ユーザ体験的な意味で。実際の挙動は異なります

*2:とはいえ公式でこう!というように対応するのも難しい、というのもわかる

find_by_sqlしたあとにkaminariでページネーションするには

課題

Railsでページネーション機能を作るときにはkaminari を使うのが定番ですね。Active Recordのクエリメソッドに対してメソッドチェーン形式でpageやperを追加するだけで手軽にページネーションができます。

ただ、find_by_sqlを利用してActive Recordのオブジェクトを作成したときには、戻り値が配列になってしまうのでpageやperなどを後に追加することができません*1。どうしたらよいのでしょうか。

問題のあるやり方

ググるとKaminari.paginate_arrayを使った次のようなやり方がいくつか引っかかります。

Kaminari.paginate_array(array_from_find_by_sql).page(params[:page]).per(10)

Kaminari.paginate_arrayは読んで字のごとく、配列をkaminariで扱えるようにするためのメソッドです。これでとりあえず要件としては満たせますが、レコード件数が多くなってくるとパフォーマンスに問題が出てきます。このやり方だと、find_by_sqlが検索対象のActive Recordオブジェクトを毎回すべて生成する必要があるからです。例えば10000件のレコードが対象となるクエリだとしたら、毎回10000個のActive Recordオブジェクトが生成されるわけです、これはだいぶつらいですね…><。

とりあえずの解決方法

kaminariで生成したモデルオブジェクトは、ビューでpaginateメソッドへの引数になり、ページネーション用のhtmlになります。引数は必ずしもkaminariから作成されたクラスである必要はなく、必要なメソッドが定義されていれば問題ありません。なので必要なメソッドを返すラッパーを用意すればよい、と考えてしばらく↓のようなクラスを作り運用していました(もちろんfind_by_sqlの引数としてlimitやoffsetを使ったクエリを組み立てる必要がありますし、total_countは別のクエリで取得しておく必要があります)。

真の解決方法

しかし、そもそもKaminari.paginate_arrayに次のような形で引数を渡すと、無駄なActive Recordのオブジェクトを生成する必要なく、上記のWrapperForKaminariと同等のメソッドが提供されたオブジェクトが返る、ということに気づきました。

Kaminari.paginate_array(array_from_find_by_sql, limit: 10, offset: 0, total_count: 100)

WrapperForKaminariをgemにしようかな。と思っていたのですが不要でしたね。kaminariべんり。

*1:仮に追加できても、find_by_sqlで発行されるクエリをいじれないので無意味ですね…