プレーンなクラスにActiveRecordのアソシエーションを生やすfake_associationsをつくった


最近、Railsで開発していて、モデルは必要だが、テーブルは必要ではなくなるということがありました。
つまり、ActtiveModelなどのオブジェクトを、例えば具体的には以下のように、Railsのモデルのように他のモデルとの関連付け(アソシエーション)を維持して、利用したいということがありました。

class Tweet < ActiveRecord; end

class User

  attr_accessor :id

  has_many :tweets
end

もちろん一つ一つ必要なアソシエーションを実装することもできたのですが、同じようなメソッドを定義するのも良くないことは自明です。
そこで、includeするだけである程度のアソシエーションを実現できるものにしました。

tsuwatch/fake_associations

実装としては、Railsの命名規則やアソシエーション部分に書かれている情報を頼りにモデルを生成し、ActiveRecordのメソッドを呼び出していくということをしています。
ActiveRecordの内部APIを利用することも考えたのですが、あまりActiveRecordの内部実装に依存し過ぎるのは辛くなるのではないだろうかと考えてこのような方針で実装をしました。

使い方

現在サポートしているのは、has_manyとある程度のオプションのみです。
以下のようなコードで動作することは確認しています。
今後さらに他のアソシエーションのサポートを充実させていきたいと考えています。

class User
  include FakeAssociations

  # Need
  attr_accessor :id

  has_many :tweets

  has_many :friendships, foreign_key: 'follower_id'
  has_many :followed_user, through: :friendships, source: :followed

  has_many :reverse_friendships, foreign_key: 'followed_id', class_name: 'Friendship'
  has_many :followers, through: :reverse_friendships, source: :follower

  has_many :favorite_tweets, through: :favorites, source: :tweet
end

class Friendship < ActiveRecord::Base
  #  Table name: friendships
  #
  #  id          :integer  not null, primary key
  #  follower_id :integer
  #  followed_id :integer
end

class Tweet < ActiveRecord::Base
  #  Table name: tweets
  #
  #  id          :integer  not null, primary key
  #  user_id     :integer
end

class Favorite < ActiveRecord::Base
  #  Table name: favorites
  #
  #  id          :integer  not null, primary key
  #  user_id     :integer
  #  tweet_id    :integer
end

おわり

プレーンなクラスに見せかけの関係(fake associations)を定義できるgemを作り、説明しました。Railsの規則のおかげで、探り探り関連するモデルを引っ張りながら実装を進めていくことが出来ました。主にActiveModelなクラスで利用することになると思います。
現時点で、自分の要件を満たしているのでリリースしましたが、今後他のアソシエーションもサポートしていければと考えています。