読者です 読者をやめる 読者になる 読者になる

リア充爆発日記

You don't even know what ria-ju really is.

backbone.jsでrails3とかのhas_manyとかhas_oneとかを実装

追記1)以下の内容は、更新系でうまく動かない可能性があるので、ご了承ください。修正できたら直します。
追記2)hasOne側は登録・更新までの動作検証ができた。
追記3)hasMany側も動作検証できた。ので、いちおう、これで終わり。

ここんとこbackbone.jsにお熱なわけですが。
Rails3にbackbone.jsを導入する
backbone.jsでajaxのリクエスト先を別のHostにする方法
backbone.jsでfetch()でもsave()でもとにかくエラーハンドラが発火してしまう。
rails3 + backbone.jsでデータのやり取りをするために。

という経緯を辿って来まして、次にクライアントサイド(backbone.js側)にもリレーショナルの概念を導入しようと。
で、backbone-relationalというプラグインを導入することにした。
https://github.com/PaulUithol/Backbone-relational

若干チャレンジングかなぁ、と思ったけどSOでもまぁまぁヒットするし、やってみっか。ということで。

インストール

gemはないので、ギッハブからjsファイル持ってきて、asset piplineが反応する適当なところにおいてapplication.jsで陸ワイヤー

~snip~
//= require underscore
//= require backbone
//= require backbone-relational
~snip~

前提

  • backbone.jsはbackbone-on-rails
  • JSはCoffeeScript
  • イメージ的なものは以下な感じで。
    • User has_many tweet
    • User has_one profile
  • サーバ側にデータがイイカンジに入ってて/users/1をgetすると、関連したprofileやtweetも入れ子になったjsonがゲットできる
    • そんなjsonのイメージ。※手書きなので多少間違ってるかも。
{
  id: 1,
  name: "hoge",
  age: "10",
  profile: {
    id: "1"
    address: "hogehogefugafuga",
    tel: "00-0000-0000"
  },
  tweets: [
    {
      id: "1",
      tweet: "piyo",
    },
    {
      id: "2",
      tweet: "pafu"
    }
}
  • profileのリソースは/user/:id/profile
  • tweetのリソースは/user/:id/tweets,/user/:id/tweets/:tweet_id

Modelを作る

  • Userモデル
assets/javascripts/modes/user.js.coffee
class YourApp.Models.User extends Backbone.RelationalModel
  urlRoot: '/users'

  relations: [
    {
    type: Backbone.HasOne
    key: 'profile'
    relatedModel: YourApp.Models.Profile'
    reverseRelation:
      key: 'user'
      includeInJSON: false
    },
    {
    type: Backbone.HasMany
    key: 'tweets'
    relatedModel: 'YourApp.Models.Tweet'
    collectionType: 'YourApp.Models.Tweets'
    reverseRelation:
      key: 'user'
      includeInJSON: false
    }
  ]

YourApp.Models.User.setup()

ポイントは3つ。

  1. 2つ以上のrelationsを指定するとき、CoffeeScript的な書き方がわからず、うまくrelation単位で配列になってくれなくなったので{}で囲ったった。
  2. CoffeeScriptの場合は最後にsetup()をコールする。->https://github.com/PaulUithol/Backbone-relational/issues/91
  3. includeInJSONをfalseにしておかないと、逆サイドのモデルのJSONがフルで入れられちゃう(この場合、profileにはuser_idだけあればいいのに、userモデルを表すJSONががっちり入れられる)

配列の件は、うまい書き方あるのかね。

  • Profileモデル
assets/javascripts/modes/profile.js.coffee
class YourApp.Models.Profile extends Backbone.RelationalModel

  url: () ->
    '/users/' + this.get("user").first().id + '/profile'

YourApp.Models.Profile.setup()

ポイントは1つ。かな。

  1. url()をオーバライドするとき、this.get("user").first()でUserオブジェクトがゲットできるので、それでurlを作る。@.get("user")でもイケるけどなんだかよくわからないのでthisにした。hasOneなのにfirst()が必要なのが意味分かんない。俺が悪いのか、仕様が悪いのかもわからない。もう調べる気力もない。

なんか、もっとこう、例えばthis.userでUserオブジェクトがゲットできる書き方があるんじゃないか(つまり上記は正しくないやり方)っていう気がするんだけど。。。

assets/javascripts/modes/tweet.js.coffee
class YourApp.Models.Tweet extends Backbone.RelationalModel

YourApp.Models.Tweet.setup()

ポイントは1つ。

  1. 中身は何も書かなくてもいいみたい。とりあえず問題はない。けど無いとダメ。

これでModelづくりは終わり。

Collectionを作る

assets/javascripts/collections/tweets.js.coffee
class YourApp.Collections.Tweets extends Backbone.Collection
  url: () ->
    id = this.get("tweet").id if this.get("tweet")
    created = if id then ("/" + id) else ''
    '/users/' + this.user.id + '/tweets' + created
  model: YourApp.Models.Tweet

ポイントは・・・

  1. Modelとちがってsetupしなくていいみたい。
  2. 状況によってアクセスするURLが違うから、そこを調節する
  3. ここはthis.userでuserオブジェクトが取れるんだよなぁ。。

動作確認

Viewとかまだ書いてないので例によってSafarichromeのコンソールで確認。

user = new YourApp.Models.User({id: "1"})
 > User
user.fetch()
 > Object
profile = user.get("proflie")
 > Profile
proflie.url()
 "/users/1/profile"
profile.get("address")
 "hogehogefugafuga"

tweets = user.get("tweets")
 > Tweets
tweets.url()
 "/users/1/tweets
tweet = tweets.get(1)
 > Tweet
tweet.url()
 "/users/1/tweets/1"
tweet.get("tweet")
 "piyo"

user = new YourApp.Models.User()
 > User
user.set("name", "hoge")
 > User
user.save()
 > Object

tweet = new YourApp.Models.Tweet({user: user.id})
 > Tweet
tweet.set("tweet", "hogehoge;-)")
 > Tweet
tweet.url()
 "/users/2/tweets"
tweet.save()
 > Object
tweet.url()
 "/users/2/tweets/3"

もしかしたら最初のfetch()で以下のワーニングがでるかも。>>
'Relation=%o between instance=%o.%s and relatedModel=%o.%s already exists'