paranoia を使った論理削除でコールバックを実行しない

paranoia を使った論理削除では、destroy したとき deleted_at が設定されるだけでなく、コールバックも実行される。

そのため

class User < ActiveRecord::Base
  acts_as_paranoid

  has_many :items, dependent: :destroy
end

上記の User で destroy を実行すると items が削除される。

論理削除でコールバックが実行されるのが今回都合が悪かったので、paranoiaソースコードを見ながら、destroy を上書きして対処してみた。

class User < ActiveRecord::Base
  acts_as_paranoid

  has_many :items, dependent: :destroy

  def destroy
    # paranoia で定義されているメソッドを呼び出して、
    # deleted_at に時間をセット
    result = touch_paranoia_column(true)
    result ? self : false
  end
end

その場しのぎ感が強いので、他の方法を考えたほうがいいかも。dependent オプションを外すとか。とりあえずメモしておく。

Apartment を使った Rails アプリを Heroku にデプロイできない

マルチテナント用の gem である apartment を使っている Rails アプリを、 Heroku に push すると下記のエラーが発生した。

Preparing app for Rails asset pipeline
       Running: rake assets:precompile
       rake aborted!
       Gem::LoadError: Specified 'sqlite3' for database adapter, but the gem is not loaded. Add `gem 'sqlite3'` to your Gemfile (and ensure its version is at the minimum required by ActiveRecord).
       /tmp/build_4f21ff1d-af6a-46cb-ba2e-935e4be9cdd8/vendor/bundle/ruby/2.0.0/gems/activerecord-4.1.0/lib/active_record/connection_adapters/connection_specification.rb:190:in `rescue in spec'
       /tmp/build_4f21ff1d-af6a-46cb-ba2e-935e4be9cdd8/vendor/bundle/ruby/2.0.0/gems/activerecord-4.1.0/lib/active_record/connection_adapters/connection_specification.rb:187:in `spec'
       /tmp/build_4f21ff1d-af6a-46cb-ba2e-935e4be9cdd8/vendor/bundle/ruby/2.0.0/gems/activerecord-4.1.0/lib/active_record/connection_handling.rb:50:in `establish_connection'
       /tmp/build_4f21ff1d-af6a-46cb-ba2e-935e4be9cdd8/vendor/bundle/ruby/2.0.0/gems/apartment-0.24.3/lib/apartment/adapters/abstract_adapter.rb:82:in `block in process_excluded_models'
       /tmp/build_4f21ff1d-af6a-46cb-ba2e-935e4be9cdd8/vendor/bundle/ruby/2.0.0/gems/apartment-0.24.3/lib/apartment/adapters/abstract_adapter.rb:81:in `each'
       /tmp/build_4f21ff1d-af6a-46cb-ba2e-935e4be9cdd8/vendor/bundle/ruby/2.0.0/gems/apartment-0.24.3/lib/apartment/adapters/abstract_adapter.rb:81:in `process_excluded_models'
       /tmp/build_4f21ff1d-af6a-46cb-ba2e-935e4be9cdd8/vendor/bundle/ruby/2.0.0/gems/apartment-0.24.3/lib/apartment/database.rb:18:in `init'
       /tmp/build_4f21ff1d-af6a-46cb-ba2e-935e4be9cdd8/vendor/bundle/ruby/2.0.0/gems/apartment-0.24.3/lib/apartment/railtie.rb:31:in `block in <class:Railtie>'
       /tmp/build_4f21ff1d-af6a-46cb-ba2e-935e4be9cdd8/vendor/bundle/ruby/2.0.0/gems/activesupport-4.1.0/lib/active_support/callbacks.rb:438:in `instance_exec'
       /tmp/build_4f21ff1d-af6a-46cb-ba2e-935e4be9cdd8/vendor/bundle/ruby/2.0.0/gems/activesupport-4.1.0/lib/active_support/callbacks.rb:438:in `block in make_lambda'
       /tmp/build_4f21ff1d-af6a-46cb-ba2e-935e4be9cdd8/vendor/bundle/ruby/2.0.0/gems/activesupport-4.1.0/lib/active_support/callbacks.rb:184:in `call'
       /tmp/build_4f21ff1d-af6a-46cb-ba2e-935e4be9cdd8/vendor/bundle/ruby/2.0.0/gems/activesupport-4.1.0/lib/active_support/callbacks.rb:184:in `block in simple'
       /tmp/build_4f21ff1d-af6a-46cb-ba2e-935e4be9cdd8/vendor/bundle/ruby/2.0.0/gems/activesupport-4.1.0/lib/active_support/callbacks.rb:185:in `call'
       /tmp/build_4f21ff1d-af6a-46cb-ba2e-935e4be9cdd8/vendor/bundle/ruby/2.0.0/gems/activesupport-4.1.0/lib/active_support/callbacks.rb:185:in `block in simple'
       /tmp/build_4f21ff1d-af6a-46cb-ba2e-935e4be9cdd8/vendor/bundle/ruby/2.0.0/gems/activesupport-4.1.0/lib/active_support/callbacks.rb:86:in `call'
       /tmp/build_4f21ff1d-af6a-46cb-ba2e-935e4be9cdd8/vendor/bundle/ruby/2.0.0/gems/activesupport-4.1.0/lib/active_support/callbacks.rb:86:in `run_callbacks'
       /tmp/build_4f21ff1d-af6a-46cb-ba2e-935e4be9cdd8/vendor/bundle/ruby/2.0.0/gems/actionpack-4.1.0/lib/action_dispatch/middleware/reloader.rb:83:in `prepare!'
       /tmp/build_4f21ff1d-af6a-46cb-ba2e-935e4be9cdd8/vendor/bundle/ruby/2.0.0/gems/actionpack-4.1.0/lib/action_dispatch/middleware/reloader.rb:55:in `prepare!'
       /tmp/build_4f21ff1d-af6a-46cb-ba2e-935e4be9cdd8/vendor/bundle/ruby/2.0.0/gems/railties-4.1.0/lib/rails/application/finisher.rb:52:in `block in <module:Finisher>'
       /tmp/build_4f21ff1d-af6a-46cb-ba2e-935e4be9cdd8/vendor/bundle/ruby/2.0.0/gems/railties-4.1.0/lib/rails/initializable.rb:30:in `instance_exec'
       /tmp/build_4f21ff1d-af6a-46cb-ba2e-935e4be9cdd8/vendor/bundle/ruby/2.0.0/gems/railties-4.1.0/lib/rails/initializable.rb:30:in `run'
       /tmp/build_4f21ff1d-af6a-46cb-ba2e-935e4be9cdd8/vendor/bundle/ruby/2.0.0/gems/railties-4.1.0/lib/rails/initializable.rb:55:in `block in run_initializers'
       /tmp/build_4f21ff1d-af6a-46cb-ba2e-935e4be9cdd8/vendor/bundle/ruby/2.0.0/gems/railties-4.1.0/lib/rails/initializable.rb:54:in `run_initializers'
       /tmp/build_4f21ff1d-af6a-46cb-ba2e-935e4be9cdd8/vendor/bundle/ruby/2.0.0/gems/railties-4.1.0/lib/rails/application.rb:288:in `initialize!'
       /tmp/build_4f21ff1d-af6a-46cb-ba2e-935e4be9cdd8/config/environment.rb:5:in `<top (required)>'
       /tmp/build_4f21ff1d-af6a-46cb-ba2e-935e4be9cdd8/vendor/bundle/ruby/2.0.0/gems/activesupport-4.1.0/lib/active_support/dependencies.rb:247:in `require'
       /tmp/build_4f21ff1d-af6a-46cb-ba2e-935e4be9cdd8/vendor/bundle/ruby/2.0.0/gems/activesupport-4.1.0/lib/active_support/dependencies.rb:247:in `block in require'
       /tmp/build_4f21ff1d-af6a-46cb-ba2e-935e4be9cdd8/vendor/bundle/ruby/2.0.0/gems/activesupport-4.1.0/lib/active_support/dependencies.rb:232:in `load_dependency'
       /tmp/build_4f21ff1d-af6a-46cb-ba2e-935e4be9cdd8/vendor/bundle/ruby/2.0.0/gems/activesupport-4.1.0/lib/active_support/dependencies.rb:247:in `require'
       /tmp/build_4f21ff1d-af6a-46cb-ba2e-935e4be9cdd8/vendor/bundle/ruby/2.0.0/gems/railties-4.1.0/lib/rails/application.rb:264:in `require_environment!'
       /tmp/build_4f21ff1d-af6a-46cb-ba2e-935e4be9cdd8/vendor/bundle/ruby/2.0.0/gems/railties-4.1.0/lib/rails/application.rb:367:in `block in run_tasks_blocks'
       /tmp/build_4f21ff1d-af6a-46cb-ba2e-935e4be9cdd8/vendor/bundle/ruby/2.0.0/gems/sprockets-rails-2.1.3/lib/sprockets/rails/task.rb:55:in `block (2 levels) in define'
       Tasks: TOP => environment
       (See full trace by running task with --trace)
 !
 !     Precompiling assets failed.
 !

 !     Push rejected, failed to compile Ruby app

Rails 初期化時に apartment の初期化を行っており、 database.yml に書かれている production 環境のアダプタを読み込もうとしてエラーが発生していた。 SQLite のアダプタを読み込もうとしたのが原因みたい。

Heroku に push したとき、 Heroku Postgres を使うように database.yml が自動で書き換わるんだけど、 Rails 初期化のタイミングではまだ書き換わっていなかったようだ。

database.yml で

production:
  adapter: postgresql
  host: HerokuPostgresホスト名
  port: 5432
  username: ユーザー名
  password: パスワード
  database: データベース名

という風に、SQLite ではなく PostgreSQL のアダプタを使うように書いておけば回避できた。

サーティワン

今日でついに 31 歳になってしまった。 31、サーティワン。旨そうな響き。 そういえば先週、もうすぐ 31 になるからって、 サーティワンのアイスクリームを食べたな。 その日は日差しが強くて暑かったので、実に旨かった。

今までアラサーなんて言ってたけど、31 歳にもなればアラサーとは言えないかも。 もはやアラサーじゃなくてサーだな。 何言ってるのか良くわからないだろうけど、 自分も何言ってるのかわかってないから、まぁいい。

さて、だいたい一年のうち半分が過ぎたわけだけど、 正月に立てた目標「アプリやサービスをリリースしまくる」の進捗は思わしくない。 今年に入ってまだ何もリリースしてないし。 RubyMotion でアプリを作り直したくらいだけど、こいつはノーカウント。

今プライベートで関わっている Rails のプロジェクトがあるので、 それを秋までにはリリースして、 あと Swift でアプリを1~2個作りたいところ。 Web サービスとアプリ、どちらもデザインがネックなんだよな。 コーディングはさくさく進むんだけど、いつもデザインでペースダウン。 いい加減そろそろ本気でデザインの勉強もしたほうがよさそうだ。

最近、やりたいことが多くて時間が足りない。 時間の使い方も見直さなければいけないな。

最後にアサマシエイトを貼っておく。

http://www.amazon.co.jp/registry/wishlist/13B3447YOJQNZ

Rails Assets を使って Rails プロジェクトに AngularJS をインストール

Rails で AngularJS を使おうと思っていたけど、

  • ファイルをダウンロードして vendor/assets に直接配置するのは管理が面倒
  • AngularJS をラップした gem は AngularJS 本体がバージョンアップしたとき対応するまでタイムラグがある
    • そもそも angular-gem や angular-rails や angularjs-rails-resource など乱立してるし
  • Bower で AngularJS を管理するのがいいかも
    • Bower を動かすために Node が必須なので、Heroku デプロイ時に AngularJS をインストールできない
    • カスタム Buildpack を作れば可能だけど、アプリの本質ではないので労力をかけたくない。

という紆余曲折を経て、『Rails Assets』を使ってみることにした。

Rails Assets は、Bower のパッケージを gem にコンバートし、ホストしているサービス。 Gemfile に

source "https://rails-assets.org"

を追加し、

gem "rails-assets-<Bower パッケージ名>"

という形式で指定することで、Bundler で JavaScript ライブラリをインストールできる。

Rails で AngularJS を使いたい場合、

source "https://rails-assets.org"
gem "rails-assets-angular"
gem "rails-assets-angular-route"

と書いて bundle install でインストール。 このとき依存するライブラリもインストールしてくれる。

あとは app/assets/javascript/application.js に

//= require angular
//= require angular-route

と書けば、Asset Pipeline に AngularJS が組み込まれる。

Rails Assets は gem のインストールなので Bower 不要。Node も不要。 カスタム Buildpack を使わなくても Heroku で動かせる。

Subversion でリベースしたい

仕事では大人の事情で Subversion を使わざるをえないんだけど、 Subversion でも Github Flow みたいに、ブランチ切って作業するように努めてはいる。

今回、他の作業が入って、ブランチをだ〜いぶ放置していたら、その間に trunk に結構修正が入ってしまった。 trunk の修正内容をブランチにマージすると、最終的にブランチを trunk にマージするとき衝突しまくった過去があるので、リベースしたい。

Git なら rebase で一発だけど、Subversion には rebase コマンドはない。 仕方ないので、他の方法をとることにした。

具体的には、リベースしたい feature ブランチがあるとき下記の手順をおこなった。

  1. trunk から最新リビジョンを起点に新しいブランチ rabase_feature を作成
  2. feature ブランチを rebase_feature ブランチにマージする
  3. feature ブランチを削除
  4. rebase_feature ブランチを feature にリネーム

これでリベースっぽくなった。 ただ、旧 feature ブランチの履歴を引き継げないから完璧ではない…。 もっと上手いやり方はあるのかも。

Subversion から Git に移行するのが個人的には一番いい。 Visual Studio に Git バンドルされないかな。 そうなれば大人の事情もクリアできそうなんだけど。

パーフェクト Ruby on Rails

技術評論社のパーフェクトシリーズに、 ついにRuby on Railsが出たので購入。

お目当ては、9章『より実践的なモデルの使い方』。

自分の場合、Railsは趣味プログラミングで使うことがほとんどなので、 つい動かすことを優先にコードを書きがち。 コントローラーが太らないよう心がけてはいるので、 ビジネスロジックをモデルに実装していったら、 気が付けばファットモデルの出来上がり。 悲しいけど、よく遭遇するパターンだったりする。

そこでモデルのダイエットに取り組むのだけど、

  • 複雑なバリデーションとコールバックをクラスに切り出す
  • データベースに依存しないモデルを切り出す
  • 値オブジェクトを切り出す
  • 共通の関心ごとを cencerns に切り出す
  • 複数のモデルにまたがった処理をサービスクラス化

といった、9章で紹介されている手法は、概ね自分がやっている手法と同じでホッとした。 バリデーションやコールバックをクラスにするまではやってないけど。

文章が読みやすかったので、週末一気に読み終えた。 扱う範囲が、Railsアプリ開発とテストだけに留まらず、インフラ運用とRailsの拡張までと幅広く、 盛りだくさんという言葉がふさわしい本だった。 Rails 初心者には難しい。中級者向けなのは間違いない。

パーフェクト Ruby on Rails

パーフェクト Ruby on Rails

鼎泰豊

博多阪急にオープン時から出店しているの『鼎泰豊』。 本店は世界の人気レストラン10店に選ばれたらしい。 その情報を知って、行きたかったんだけど、ランチといえど敷居が高くて…。 予算オーバーではあるけど、今回意を決して行ってみた。

いろんな種類の小龍包を食べるか、それともランチセットにするか注文ギリギリまで迷って、 土壇場で海老入りチャーハンセット(1620円)を注文。空腹が決め手だった。

f:id:griefworker:20140607124118j:plain

オーソドックスな肉餡の小龍包が 4 個。 刻み生姜と酢醤油でいただく。 薄皮を箸でつつくと、閉じ込めらていたスープがあふれ出し、 それが酢醤油と混ざって、旨みにほのかな酸味が合わさって美味だった。

f:id:griefworker:20140607124548j:plain

これまたオーソドックスな炒飯。 海老を生かす、あっさりとした味付けだった。 大ぶりの海老の、プリっとした食感が印象的。

今回は空腹だったのでランチセットにしたけど、もっといろんな種類の小龍包を食べてみたかった。 ホタテ、かに味噌、ウニなど、絶対旨いに違いない。

関連ランキング:飲茶・点心 | 博多駅祇園駅