プレーンなRubyオブジェクトでhas_manyとbelongs_toを使う

最近、Railsで開発していると、ActiveModelを使ってプレーンなオブジェクトを拡張してモデルとして利用する事が増えてきた。
そういうクラスを定義していると、しばしばActiveRecordなオブジェクトに対する参照が欲しくなる事がある。
で、単純にこういう定義をしたりする。

```ruby
class Comment
  include ActiveModel::Model

  attr_accessor :body, :user_id

  def user
    @user ||= User.find_by(id: user_id)
  end

  def user=(user)
    self.user_id = user.id
    @user = user
  end
end
```

とは言え、タイプチェック入れたり、同じような定義がいくつも入ると邪魔である。こういうものは宣言的に済ませてソースコードには、もっとドメインのコードを表現したい。
で、ふと思い付いてActiveModelを利用したオブジェクトから直接ActiveRecordの`has_many`とか`belongs_to`を呼べないものかと試してみた。

[joker1007/activemodel-associations](https://github.com/joker1007/activemodel-associations "joker1007/activemodel-associations")

Railsのソースを読んで夜にせっせと内職することで、何とか形にできた。
しかし、ActiveRecordの内部APIをオーバーライドしまくってるので、元のAPIがゴリっと変わると即死ぬという危険を秘めている。
もっと単純なDSL定義しても十分だったかもしれない……。
まあ、おかげでActiveRecordのリレーション回りがどうやって定義されてるか大体分かった気がするので、良かったか。

実装の方針としては、内部で使ってるメソッドをいくつかクラスに定義してActiveRecordをごまかしつつ、どうにもならん所だけモジュールをprependして処理奪って独自の関連定義クラスに差し替えている。
`has_many`に関してはかなり限定的な動作だけをサポートしていて、使い所が無かったり実装が辛そうなオプションは無効化してある。
しかし、以前にQiitaに投稿したモジュールからメソッドを一つだけ借用するというテクニックを本当に使う機会があるとは思わなかった。

cf. [Ruby - UnboundMethodを使って他のモジュールからメソッドを借りる - Qiita](http://qiita.com/joker1007/items/9334348e45dd3f59830f "Ruby - UnboundMethodを使って他のモジュールからメソッドを借りる - Qiita")

ActiveRecordは流石に良く出来ていて、関連を定義するために必要なメソッドはそんなに多くなかった。
Associationクラスをもうちょっとしっかり実装すれば、勝手に独自の関連タイプを定義できそうな気がする。

とりあえず手元のユースケースは満たしているのだが、Railsがアップデートされるとすぐ壊れそうなので、プレーンなRubyオブジェクトからActiveRecordへの関連を定義する機能をRails本体に追加して欲しい所。

## 使い方

### belongs\_to

```ruby
class User < ActiveRecord::Base; end

class Comment
  include ActiveModel::Model         # need ActiveModel::Model
  include ActiveModel::Associations  # include this

  attr_accessor :body, :user_id # belongs_to association need foreign_key attribute

  belongs_to :user

  # need hash like accessor, used internal Rails
  def [](attr)
    self.send(attr)
  end

  # need hash like accessor, used internal Rails
  def []=(attr, value)
    self.send("#{attr}=", value)
  end
end

user = User.create(name: "joker1007")
comment = Comment.new(user_id: user.id)
comment.user # => <User {name: "joker1007"}>
```

### Polymorphic belongs\_to

```ruby
class User < ActiveRecord::Base; end

class Comment
  include ActiveModel::Model         # need ActiveModel::Model
  include ActiveModel::Associations  # include this

  attr_accessor :body, :commenter_id, :commenter_type

  belongs_to :commenter, polymorphic: true

  # need hash like accessor, used internal Rails
  def [](attr)
    self.send(attr)
  end

  # need hash like accessor, used internal Rails
  def []=(attr, value)
    self.send("#{attr}=", value)
  end
end

user = User.create(name: "joker1007")
comment = Comment.new(commenter_id: user.id, commenter_type: "User")
comment.commenter # => <User {name: "joker1007"}>
```

### has\_many

```ruby
class User < ActiveRecord::Base; end

class Group
  include ActiveModel::Model
  include ActiveModel::Associations

  attr_accessor :name
  attr_reader :user_ids

  has_many :users

  def [](attr)
    self.send(attr)
  end

  def []=(attr, value)
    self.send("#{attr}=", value)
  end
end

user = User.create(name: "joker1007")
group = Group.new(user_ids: [user.id])
group.users # => ActiveRecord::Relation (SELECT * from users WHERE id IN (#{user.id}))
group.users.find_by(name: "none") # => []
```