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がゲットできる
{ id: 1, name: "hoge", age: "10", profile: { id: "1" address: "hogehogefugafuga", tel: "00-0000-0000" }, tweets: [ { id: "1", tweet: "piyo", }, { id: "2", tweet: "pafu" } }
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つ。
- 2つ以上のrelationsを指定するとき、CoffeeScript的な書き方がわからず、うまくrelation単位で配列になってくれなくなったので{}で囲ったった。
- CoffeeScriptの場合は最後にsetup()をコールする。->https://github.com/PaulUithol/Backbone-relational/issues/91
- 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つ。かな。
- url()をオーバライドするとき、this.get("user").first()でUserオブジェクトがゲットできるので、それでurlを作る。@.get("user")でもイケるけどなんだかよくわからないのでthisにした。hasOneなのにfirst()が必要なのが意味分かんない。俺が悪いのか、仕様が悪いのかもわからない。もう調べる気力もない。
なんか、もっとこう、例えばthis.userでUserオブジェクトがゲットできる書き方があるんじゃないか(つまり上記は正しくないやり方)っていう気がするんだけど。。。
- Tweetモデル
assets/javascripts/modes/tweet.js.coffee class YourApp.Models.Tweet extends Backbone.RelationalModel YourApp.Models.Tweet.setup()
ポイントは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
ポイントは・・・
- Modelとちがってsetupしなくていいみたい。
- 状況によってアクセスするURLが違うから、そこを調節する
- ここはthis.userでuserオブジェクトが取れるんだよなぁ。。
動作確認
Viewとかまだ書いてないので例によってSafariかchromeのコンソールで確認。
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()
> Objecttweet = 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'