Rails の seed.rb に冪等性を持たせる

rake db:seed を何回やっても大丈夫なようにするには auto increment の管理が必要になります。
本番環境で何度も実行することは、ほぼあり得ないし、開発環境であれば直接DBをわさわさしちゃえば間に合うので、まともに実装する必要はないんでしょうが、少し調べてみました。

自前で拡張

sql - Rails way to reset seed on id field - Stack Overflow
reset_pk_sequence というメソッドを追加していますが、面白いのが PostgreSQL の場合 reset_pk_sequence! というメソッドを呼ぶだけのようで、Rails自体に実装されています。
しかし、MySQLは考慮さえされておらず(MySQL用に拡張するのは簡単だと述べられていますが)DB格差が生まれています。

PostgreSQL実装

# active_record/connection_adapters/postgresql/schema_statements.rb
        def reset_pk_sequence!(table, pk = nil, sequence = nil) #:nodoc:
          (略)

            select_value(<<-end_sql, 'SCHEMA')
              SELECT setval('#{quoted_sequence}', (SELECT COALESCE(MAX(#{quote_column_name pk})+(SELECT increment_by FROM #{quoted_sequence}), (SELECT min_value FROM #{quoted_sequence})) FROM #{quote_table_name(table)}), false)
            end_sql

MySQLも考慮してみる

config/initializers 以下に格納。
こんな感じでしょうか。

module ActiveRecord
  class Base
    def self.reset_pk_sequence
      case ActiveRecord::Base.connection.adapter_name
      when "Mysql2"
        new_max = maximum(primary_key) || 0
        update_seq_sql = "alter table #{table_name} auto_increment = 1;"
        ActiveRecord::Base.connection.execute(update_seq_sql)
      when "SQLite"
        new_max = maximum(primary_key) || 0
        update_seq_sql = "update sqlite_sequence set seq = #{new_max} where name = '#{table_name}';"
        ActiveRecord::Base.connection.execute(update_seq_sql)
      when "PostgreSQL"
        ActiveRecord::Base.connection.reset_pk_sequence!(table_name)
      else
        raise "Task not implemented for this DB adapter"
      end
    end
  end
end

database_cleaner

ついでに test でお世話になっている DatabaseCleaner はどういう実装かというと、基本Truncateで処理しSQLiteの場合は sqlite_sequence を delete していました。

# database_cleaner-1.6.1/lib/database_cleaner/active_record/truncation.rb
    module SQLiteAdapter
      def delete_table(table_name)
        execute("DELETE FROM #{quote_table_name(table_name)};")
        if uses_sequence
          execute("DELETE FROM sqlite_sequence where name = '#{table_name}';")
        end
      end
      alias truncate_table delete_table

最近買ったスニーカーが軽すぎて最高

何年振りかにRunnig Shoes兼普段履きのスニーカを書いました。
もともとassicsのRunnig Shoesを1足持っていたのですが、そこそこ軽くて結構頑丈で良いのですが、どうも見た目がかっこ悪い。
もうちょっとかっこいいやつないかと探してみたら、最近は軽い、かっこいい、安いの3拍子揃ったやつが結構あるようで、色々迷いました。
最終的にはNIKEのFREE RN FLYKNIT 2017 を購入。

Nike メンズ

Nike メンズ


 
これが非常に良いです。
割と歩くのが好きで、1日5-10kmぐらい平気で歩くのですが、この靴にしてから足の疲労度が全然違います。もっと早く出会いたかった。
以下の記事でもあるように、スニーカー軽くておしゃれで色々捗るのでおすすめです。
 
シリコンバレーの大物たちが履くスニーカー24選 | BUSINESS INSIDER JAPAN

Mechanize の hook 処理について

RubyでScrapeする時はmechanizeを使って書いているのですが、ふと毎回入れていた sleep 処理を hook で対応できないかと調べてみたところ、以下の記事を見つけました。

Regulating / rate limiting ruby mechanize - Stack Overflow  
なるほど、 history_added 若干名前から想像つきにくいpropertyが存在するようです。

ソース眺めてみた

ググると他にもありそうなのでソースを眺めてみたところ、 gems/mechanize-2.7.5/lib/mechanize.rb にhook処理はまとめられていました。

content_encoding_hooks

  # A list of hooks to call before reading response header 'content-encoding'.
  #
  # The hook is called with the agent making the request, the URI of the
  # request, the response an IO containing the response body.

content-encoding を読み込む前に呼ばれるhook処理。
content-encoding の書き換えに利用できる。
Problems with text/csv Content-Encoding = UTF-8 in Ruby Mechanize - Stack Overflow

history_added

mechanizeは内部的にhistoryの管理を行っており、historyに追加する際に呼ばれるhook。

Callback which is invoked with the page that was added to history.

attr_accessor :history_added

post_connect_hooks

response取得後に呼ばれるhook処理。

  # A list of hooks to call after retrieving a response. Hooks are called with
  # the agent, the URI, the response, and the response body.

pre_connect_hooks

response取得前に呼ばれるhook処理。

  # A list of hooks to call before retrieving a response. Hooks are called
  # with the agent, the URI, the response, and the response body.

どれ使おうか

一見、history_added より post_connect_hooks の方が名前的にも良さそうなのですが、
redirect時の挙動が変わってきます。
1回redirectされるurlへのrequestだと、history_added は1度呼ばれますが、post_connect_hooks は2度呼ばれてしまいます。
redirect時もhookしたい場合は、post_connect_hooks、それ以外は history_added を使うのが良さそうです。