2014-12-14 esaのストックとフローの絞り込みで(俺にとって)なにがうれしいのか


そもそもesaのストックとフローの絞り込みってなに?っていう人は以下のリンクからどうぞ。

release_note/2014/12/13/検索結果の絞り込み(Stock or Flow) - docs - esa.io

簡単に説明するとesaではカテゴリに日付が入っている記事はフロー、入っていない記事はストックとして扱いそれらを分けて検索できるようになった。

結論から

たまには長い話をすっとばして結論を書く。簡単に言ってしまうと

  • yy年mm月dd日にAの仕様を変更したよ
  • 現時点において最新の仕様はこうです

という2種類の情報を前者はフロー、後者はストックとして分けて検索できるようになった。大体においてほしい情報というのは後者のストックなので個人的には非常にうれしい。

そもそもストックとフローとは

記事自体にストックとフローという概念をもたせるのは実はあまり新しい話でもなく、「wiki ブログ ストック フロー」で検索するとそれなりに記事がでてくる。要はwiki=ストック、ブログ=フローということだ。もちろん、wikiのあるページにフローの概念をもたせることもブログのある記事にストックの概念をもたせることも可能だけど大枠としてそういうことにする。ブログもストックっていう人もいるけど、wikiとかブログとかはここではあまり重要ではないので置いておく。

僕の捉え方でいうと、時系列にそって情報の流れがあるものはフロー、累積されていくようなものはストックと考えている(これなんの説明にもなっていないのでは…)

議事録はフロー

esaでそれなりの量の議事録を書いてきたけど、これらはMTGの日付をカテゴリに入れているのでフローだ。これらは日々の仕様の変更の流れをそのまま現しているのでフローとして扱うのは妥当だと思う。

flow.png

こういった情報がフローだけだと困ることがある。議事録には変更の差分しか書かれていない。なので現時点であるべき仕様を議事録から読み解くのは大変だ。エンジニアにわかるようにいうとgitの各commitのdiffから現在のソースコードを想像しろというものだ。

そこでストック

つまり、フローである議事録とは別に今あるべき姿を累積する記事が必要になる。そこでesa上では日付の入っていない記事を作り、そこにあるべき姿を書いていく。重要なのはフローと違いストックはその記事をちゃんと更新する必要があるということだ。

今まではこういう風にフローとストックの記事を分けてもうまく検索することができずにREADME.mdにリンクを貼っておくということしかできなかった。なのでそもそも人の目につかず、結果更新もされないという感じだったけれど、検索しやすくなったことで情報が古いことが目につきやすくなるので自然と更新も行われやすくなるのではないかと思う(みんな更新してね!)

おまけ

そもそも仕様をesaに書くの?みたいな話ありそうだけど、実装の前にメンバーに仕様を共有するケースはそれなりにあるし、コードを読んでもWhyがわからないのでなぜこうしたのかみたいな部分はドキュメントに残したりもする。

それに今回は仕様の話を例にとったけど、例えばインフラまわりだと議事録でこういう構成にしたという話を残すのとは別に構成がかわったことによってデプロイ手順もかわったならそれはストックとして残したいという話もある。

2014-11-27 spotlights.jpが本日10時にリリースされました


みんなで贈るソーシャルギフト・プレゼント \SPOTLIGHTS/(スポットライト)

今日の10時より株式会社spice life(スパイスライフ)からSPOTLIGHTSがリリースされました!!

ここ2ヶ月弱の追い込みにプログラマとして開発に関わらせて頂いてます(今後も関わっていく予定です)。実は自分のブログで関わってます!って言えるサービスは初めてだと思うので、期間はまだ短いですがちょっと感慨深いものがあります。

結婚式や誕生日のお祝いや日頃の感謝などにご活用ください。

2014-09-21 RSpecでPower Assertをやるには


RubyKaigi 2014でpower assertの話を聞いてrspecでどうにかならんかちょっと考えてみました。まず結論だけ書くとrspecでpower assertを使いたければ以下の様に書けばOK。

require 'rspec'
require 'minitest'
require 'minitest-power_assert'

module Minitest
  module Assertions
    prepend  Minitest::PowerAssert::Assertions
  end
end

RSpec.configure do |config|
  config.expect_with :minitest
end

describe 'Test' do
  it 'test' do
    assert { 1.to_s.class == 1.to_i.class }
  end
end

これを

$ rspec --color -rpower_assert power_assert.rb

で実行するとこんな感じ。power_assertは事前にrequireした方が情報量がちょっと増える。

Failures:

  1) Test test
     Failure/Error: assert { 1.to_s.class == 1.to_i.class }
     Minitest::Assertion:

           assert { 1.to_s.class == 1.to_i.class }
                      |    |     |    |    |
                      |    |     |    |    Fixnum
                      |    |     |    1
                      |    |     false
                      |    String
                      "1"

letとsubject

powerassertの0.1.3だとdefinedmethodで定義されたメソッドの値が取れていないらしく、letやsubjectの値が表示されない。現時点でのmasterの0.1.4devだと修正されているとのことなのでちゃんと表示される。

describe 'Test' do
  let(:klass) { 1.to_s.class }
  it 'test' do
    assert { klass == 1.to_i.class }
  end
end

これを実行すると

Failures:

  1) Test test
     Failure/Error: assert { klass == 1.to_i.class }
     Minitest::Assertion:

           assert { klass == 1.to_i.class }
                    |     |    |    |
                    |     |    |    Fixnum
                    |     |    1
                    |     false
                    String

こんな感じ。subjectも大体おなじ。

expectとマッチャ

この方法はMinitestのadapterとminitest-power_assertを使うようにしているので無理。

ちなみに rspec で 手軽に power_assert 出力できるようにする の方法でexpectを使ってみると

require 'rspec/core'
require 'power_assert'

module RSpec
  module PowerAssert
    def power_assert(&block)
      ::PowerAssert.start(block) do |pa|
        begin
          pa.yield
        rescue RSpec::Expectations::ExpectationNotMetError => e
          e.message << "\nPowerAssert:\n#{pa.message_proc.call}"
          raise e
        end
      end
    end
  end
end

class RSpec::Core::ExampleGroup
  include RSpec::PowerAssert
end

describe 'Test' do
  it 'test' do
    power_assert {
      expect(1.to_s.class).to eq(1.to_i.class)
    }
  end
end

これは

       PowerAssert:
             expect(1.to_s.class).to eq(1.to_i.class)
             |        |    |      |  |    |    |
             |        |    |      |  |    |    Fixnum
             |        |    |      |  |    1
             |        |    |      |  #<RSpec::Matchers::BuiltIn::Eq:0x007f0850c91d78 @expected=Fixnum, @actual=String>
             |        |    |      nil
             |        |    String
             |        "1"
             #<RSpec::Expectations::ExpectationTarget:0x007f0850caa170 @target=String>

こうなってしまう。この場合、expectの@targetにStringという結果が入っているのでそれを取り出すようにして、eqの方も@expectedに期待するものがはいってるのでそれを取りだすようにすればいいのかなぁ。

もしくはexpectとeqの中の値さえわかれば良いといえば良いのでいっそ値をださなくてもいいのかも? 例えばこんな感じ。

       PowerAssert:
             expect(1.to_s.class).to eq(1.to_i.class)
                      |    |              |    |
                      |    |              |    Fixnum
                      |    |              1
                      |    |
                      |    |
                      |    String
                     "1"


別の例としてbe_falseyだとこんな感じ。

     Failure/Error: expect(nil.to_s.to_i).to be_falsey
       expected: falsey value
            got: 0
       PowerAssert:
             expect(nil.to_s.to_i).to be_falsey
             |          |    |     |  |
             |          |    |     |  #<RSpec::Matchers::BuiltIn::BeFalsey:0x007fa84b53bb00 @actual=0>
             |          |    |     nil
             |          |    0
             |          ""
             #<RSpec::Expectations::ExpectationTarget:0x007fa84b3e02b0 @target=0>

これに関していうとbefalseyにはfalseが欲しいという情報がない。befalseyを見れば求めてるものはわかるって話かもしれないけど… 更に言うとRSpec3でComposable Matcherが入ったりとか、以前からあるCustom Matcherとかがあったりして、それら全部対応するのは厳しいなーという感じ(そもそも対応できるのかもよくわからない…)

そもそもの話をするとそういったマッチャというか、たくさんあるassertion methodを使い分けしたくないからpower assertをつかうわけで別にマッチャとか使わなくていいのではという気持ちがある。

なのでexpectとかカスタムマッチャとかは(power assertを使う部分では)諦めてassertですませるのがよさそうかなと個人的には思う。

config.expect_with :minitest
config.expect_with :rspec

spechelper.rbにminitestもrspecも両方使うよう書けばpowerassertを使いたいところと、rspecを使いたいところで分けることができるのでどうしてもマッチャを使いたいところは素直にマッチャを書いてpower assertは諦めるしかない。

今あるテスト資産をそのまんまpower assert対応にはできないのが悲しいところではあるけれども、このassertを使う方法でもexpectation部分以外はrspecの機能そのまま使えるのでそこまで悪くはないかなと思う。

まだpower_assert gemの実装を理解できていないので、もしかしたらうまいことやれるかもしれないけどとりあえずここでギブアップ…

2014-09-05 Arelあれこれ


Model.arel_table を読みづらいと感じる

例えばこういうコード。

Post.joins(:comments).order(Comment.arel_table[:created_at].desc)

発行されるSQLはこんな感じ。

SELECT "posts".* FROM "posts" INNER JOIN "comments" ON "comments"."post_id" = "posts"."id"  ORDER BY "comments"."created_at" DESC

こちらの方が個人的には読みやすい。

Post.joins(:comments).order('comments.created_at desc')

問題もある

テーブル名を変えてしまったとき

仮にテーブル名がかわったら後者の場合はエラーになる。

SELECT "posts".* FROM "posts" INNER JOIN "new_comments" ON "new_comments"."post_id" = "posts"."id"  ORDER BY comments.created_at DESC
SQLite3::SQLException: no such column: comments.created_at: SELECT "posts".* FROM "posts" INNER JOIN "new_comments" ON "new_comments"."post_id" = "posts"."id"  ORDER BY comments.created_at D
ESC
ActiveRecord::StatementInvalid: SQLite3::SQLException: no such column: comments.created_at: SELECT "posts".* FROM "posts" INNER JOIN "new_comments" ON "new_comments"."post_id" = "posts"."id"
  ORDER BY comments.created_at DESC

前者はモデルの方でテーブル名を変更すれば動作する。

class Comment < ActiveRecord::Base
  self.table_name  :new_comments
end

scopeでmergeしたいとき

class Comment < ActiveRecord::Base
  scope :recent, -> { where('created_at > ?', 10.days.ago) }
end

Post.joins(:comments).merge(Comment.recent)

これだとCommentのcreated_atが解決できなくてコケる。

SELECT "posts".* FROM "posts" INNER JOIN "comments" ON "comments"."post_id" = "posts"."id" WHERE (created_at > '2014-08-26 09:16:45.106691')
SQLite3::SQLException: ambiguous column name: created_at: SELECT "posts".* FROM "posts" INNER JOIN "comments" ON "comments"."post_id" = "posts"."id" WHERE (created_at > '2014-08-26 09:16:45.
106691')
ActiveRecord::StatementInvalid: SQLite3::SQLException: ambiguous column name: created_at: SELECT "posts".* FROM "posts" INNER JOIN "comments" ON "comments"."post_id" = "posts"."id" WHERE (cr
eated_at > '2014-08-26 09:16:45.106691')

正しくはこう。

class Comment < ActiveRecord::Base
  scope :recent, -> { where(self.arel_table[:created_at].gt(10.days.ago)) }
end
SELECT "posts".* FROM "posts" INNER JOIN "comments" ON "comments"."post_id" = "posts"."id" WHERE ("comments"."created_at" > '2014-08-26 09:19:17.406299')

僕個人の結論

Model.arel_tableはできる限り隠したい(読みづらいから)。そしてmergeを使いたいケースではArelを使っておく。その変わりmergeが必要になるまでは無理にArelを使わない。

さっきのComment#recentの例でいうなら最初はwhere('created_at > ?', 10.days.ago)で書いておく。merged使いたい時になったらArelの方で書き直す。

テーブル名の変更のリスクは気にしない。そもそもあまりテーブル変更しないし…。既にあるDBの上にRailsアプリケーションを構築する場合はDBリファクタリングのことを考えてArel使いまくった方がいいのかもしれない。

2014-04-18 Vim(TagBar)でRSpecのctagsを扱う


rspec ctags

unite-outlineとかを使う人には不要なのかもしれないけど、あいにくuniteユーザではないのでctagsでなんとかできないか調べてみた。

まず、Funtooで入るctagsではrspecのタグは生成できないのでforkされたctagsを使う必要がある。

fishman/ctags

インストール場所はお好みで。個人的には手で入れる系のものは$HOME/localにインストールするのが好きなのでそこにインストールした。あとはTagBarの方でこのctagsを使うよう設定する。

let g:tagbar_ctags_bin="/home/ukstudio/local/bin/ctags"
let g:tagbar_type_ruby = {
    \ 'kinds' : [
        \ 'm:modules',
        \ 'c:classes',
        \ 'd:describes',
        \ 'C:contexts',
        \ 'f:methods',
        \ 'F:singleton methods'
    \ ]
\ }

これで上の画像のような感じでTagBarに表示されるようになるはず。