Data Ember: Tutorial Komprehensif untuk Pustaka data bara

Diterbitkan: 2022-03-11

Ember Data (alias ember-data atau ember.data) adalah perpustakaan untuk mengelola data model dengan kuat dalam aplikasi Ember.js. Pengembang Ember Data menyatakan bahwa itu dirancang untuk menjadi agnostik terhadap mekanisme persistensi yang mendasarinya, sehingga berfungsi dengan baik dengan JSON API melalui HTTP seperti halnya dengan streaming WebSockets atau penyimpanan IndexedDB lokal. Ini menyediakan banyak fasilitas yang akan Anda temukan di pemetaan relasional objek sisi server (ORM) seperti ActiveRecord, tetapi dirancang khusus untuk lingkungan unik JavaScript di browser.

Meskipun Ember Data mungkin memerlukan beberapa waktu untuk di-grok, setelah Anda melakukannya, kemungkinan besar Anda akan menganggapnya layak untuk investasi. Ini pada akhirnya akan membuat pengembangan, peningkatan, dan pemeliharaan sistem Anda menjadi lebih mudah.

Saat API direpresentasikan menggunakan model, adaptor, dan serializer Data Ember, setiap asosiasi hanya menjadi nama bidang. Ini merangkum detail internal setiap asosiasi, sehingga mengisolasi sisa kode Anda dari perubahan pada asosiasi itu sendiri. Sisa kode Anda tidak akan peduli, misalnya, jika asosiasi tertentu adalah polimorfik atau merupakan hasil dari peta banyak asosiasi.

Selain itu, basis kode Anda sebagian besar terisolasi dari perubahan backend, bahkan jika itu signifikan, karena semua yang diharapkan basis kode Anda adalah bidang dan fungsi pada model, bukan representasi model JSON atau XML atau YAML.

Dalam tutorial ini, kami akan memperkenalkan fitur paling menonjol dari Data Ember dan mendemonstrasikan bagaimana hal itu membantu meminimalkan churn kode, melalui fokus pada contoh dunia nyata.

Sebuah lampiran juga disediakan yang membahas sejumlah topik dan contoh Data Ember yang lebih canggih.

Catatan: Artikel ini mengasumsikan beberapa keakraban dasar dengan Ember.js. Jika Anda tidak terbiasa dengan Ember.js, lihat tutorial Ember.js kami yang populer untuk pengenalan. Kami juga memiliki panduan JavaScript lengkap yang tersedia dalam bahasa Rusia, Portugis, dan Spanyol.

Proposisi Nilai Data Ember

Contoh bagaimana perpustakaan Ember Data dapat membantu mengakomodasi kebutuhan pelanggan.

Mari kita mulai dengan mempertimbangkan contoh sederhana.

Katakanlah kita memiliki basis kode yang berfungsi untuk sistem blog dasar. Sistem berisi Posting dan Tag, yang memiliki hubungan banyak ke banyak satu sama lain.

Semuanya baik-baik saja sampai kami mendapatkan persyaratan untuk mendukung Halaman. Persyaratan juga menyatakan bahwa, karena mungkin untuk menandai Halaman di WordPress, kita juga harus bisa melakukannya.

Jadi sekarang, Tag tidak lagi hanya berlaku untuk Postingan, tetapi juga berlaku untuk Halaman. Akibatnya, hubungan sederhana kami antara Tag dan Postingan tidak lagi memadai. Sebagai gantinya, kita membutuhkan hubungan polimorfik satu sisi banyak ke banyak, seperti berikut:

  • Setiap Posting adalah Taggable dan memiliki banyak Tag
  • Setiap Halaman adalah Taggable dan memiliki banyak Tag
  • Setiap Tag memiliki banyak Taggable polimorfik

Transisi ke kumpulan asosiasi baru yang lebih kompleks ini kemungkinan akan memiliki konsekuensi yang signifikan di seluruh kode kita, menghasilkan banyak churn. Karena kami tidak tahu cara membuat serial asosiasi polimorfik ke JSON, kami mungkin hanya akan membuat lebih banyak titik akhir API seperti GET /posts/:id/tags dan GET /pages/:id/tags . Dan kemudian, kita akan membuang semua fungsi parser JSON yang ada dan menulis yang baru untuk sumber daya baru yang ditambahkan. Ugh. Membosankan dan menyakitkan.

Sekarang mari kita pertimbangkan bagaimana kita akan mendekati ini menggunakan Data Ember.

Di Data Ember, mengakomodasi kumpulan asosiasi yang dimodifikasi ini hanya akan melibatkan perpindahan dari:

 App.Post = DS.Model.extend({ tags: DS.hasMany('tag', {async: true}) }); App.Tag = DS.Model.extend({ post: DS.belongsTo('post', {async: true}) });

ke:

 App.PostType = DS.Model.extend({ tags: DS.hasMany('tag', {async: true}) }); App.Post = App.PostType.extend({ }); App.Page = App.PostType.extend({ }); App.Tag = DS.Model.extend({ posts: DS.hasMany('postType', {polymorphic: true, async: true}) });

Hasil churn di sisa kode kami akan minimal dan kami dapat menggunakan kembali sebagian besar template kami. Perhatikan khususnya bahwa nama asosiasi tags pada Postingan tetap tidak berubah. Selain itu, sisa basis kode kami hanya bergantung pada keberadaan asosiasi tags , dan tidak menyadari detailnya.

Primer Data Ember

Sebelum masuk ke contoh dunia nyata, mari kita tinjau beberapa dasar Ember Data.

Rute dan Model

Di Ember.js, router bertanggung jawab untuk menampilkan template, memuat data, dan mengatur status aplikasi. Router mencocokkan URL saat ini dengan rute yang telah Anda tetapkan, jadi Route bertanggung jawab untuk menentukan model yang akan ditampilkan oleh template (Ember mengharapkan model ini menjadi subkelas dari Ember.Object ):

 App.ItemsRoute = Ember.Route.extend({ model: function(){ // GET /items // Retrieves all items. return this.modelFor('orders.show').get('items'); } });

Data Ember menyediakan DS.Model yang merupakan subkelas dari Ember.Object dan menambahkan kemampuan seperti menyimpan atau memperbarui satu atau beberapa catatan untuk kenyamanan.

Untuk membuat Model baru, kita membuat subkelas DS.Model (misalnya, App.User = DS.Model.extend({}) ).

Ember Data mengharapkan struktur JSON yang terdefinisi dengan baik dan intuitif dari server dan membuat serial catatan yang baru dibuat ke JSON terstruktur yang sama.

Ember Data juga menyediakan rangkaian kelas array seperti DS.RecordArray untuk bekerja dengan Model. Ini memiliki tanggung jawab seperti menangani hubungan satu-ke-banyak atau banyak-ke-banyak, menangani pengambilan data asinkron, dan sebagainya.

Atribut Model

Atribut model dasar didefinisikan menggunakan DS.attr ; misalnya:

 App.User = DS.Model.extend({ firstName: DS.attr('string'), lastName: DS.attr('string') });

Hanya bidang yang dibuat oleh DS.attr yang akan disertakan dalam muatan yang diteruskan ke server untuk membuat atau memperbarui catatan.

DS.attr menerima empat tipe data: string , number , boolean dan date .

RESTSerializer

Secara default:

  • Data Ember menggunakan RESTSerializer untuk membuat objek dari respons API (deserialisasi) dan untuk menghasilkan JSON untuk permintaan API (serialisasi).
  • RESTSerializer mengharapkan bidang yang dibuat oleh DS.belongsTo memiliki bidang bernama user yang disertakan dalam respons JSON dari server. Bidang itu berisi id dari catatan yang direferensikan.
  • RESTSerializer menambahkan bidang user ke muatan yang diteruskan ke API dengan id dari pesanan terkait.

Sebuah tanggapan mungkin, misalnya, terlihat seperti ini:

 GET http://api.example.com/orders?ids[]=19&ids[]=28 { "orders": [ { "id": "19", "createdAt": "1401492647008", "user": "1" }, { "id": "28", "createdAt": "1401492647008", "user": "1" } ] }

Dan permintaan HTTP yang dibuat oleh RESTSerializer untuk menyimpan pesanan mungkin terlihat seperti ini:

 POST http://api.example.com/orders { "order": { "createdAt": "1401492647008", "user": "1" } }

Hubungan satu-ke-satu

Katakanlah, misalnya, bahwa setiap Pengguna memiliki Profil unik. Kami dapat merepresentasikan hubungan ini dalam Data Ember menggunakan DS.belongsTo pada Pengguna dan Profil:

 App.User = DS.Model.extend({ profile: DS.belongsTo('profile', {async: true}) }); App.Profile = DS.Model.extend({ user: DS.belongsTo('user', {async: true}) });

Kami kemudian bisa mendapatkan asosiasi dengan user.get('profile') atau mengaturnya dengan user.set('profile', aProfile) .

RESTSerializer mengharapkan ID model terkait disediakan untuk setiap model; misalnya:

 GET /users { "users": [ { "id": "14", "profile": "1" /* ID of profile associated with this user */ } ] } GET /profiles { "profiles": [ { "id": "1", "user": "14" /* ID of user associated with this profile */ } ] }

Demikian pula, ini menyertakan ID model terkait dalam payload permintaan:

 POST /profiles { "profile": { "user": "17" /* ID of user associated with this profile */ } }

Hubungan satu-ke-banyak dan Banyak-ke-satu

Katakanlah kita memiliki model di mana Post memiliki banyak Komentar. Dalam Data Ember, kita dapat merepresentasikan hubungan ini dengan DS.hasMany('comment', {async: true}) pada Post dan DS.belongsTo('post', {async: true}) pada Comment:

 App.Post = DS.Model.extend({ content: DS.attr('string'), comments: DS.hasMany('comment', {async: true}) }); App.Comment = DS.Model.extend({ message: DS.attr('string'), post: DS.belongsTo('post', {async: true}) });

Kami kemudian bisa mendapatkan item terkait dengan post.get('comments', {async: true}) dan menambahkan asosiasi baru dengan post.get('comments').then(function(comments){ return comments.pushObject(aComment);}) .

Server kemudian akan merespons dengan serangkaian ID untuk komentar terkait pada Postingan:

 GET /posts { "posts": [ { "id": "12", "content": "", "comments": ["56", "58"] } ] }

… dan dengan ID untuk setiap Komentar:

 GET /comments?ids[]=56&ids[]=58 { "comments": [ { "id": "56", "message": "", "post": "12" }, { "id": "58", "message": "", "post": "12" } ] }

RESTSerializer menambahkan id Posting terkait ke Komentar:

 POST /comments { "comment": { "message": "", "post": "12" /* ID of post associated with this comment */ } }

Perhatikan bahwa, secara default, RESTSerializer, tidak akan menambahkan ID terkait DS.hasMany ke objek yang diserialisasikan, karena asosiasi tersebut ditentukan di sisi "banyak" (yaitu, yang memiliki asosiasi DS.belongsTo ). Jadi, dalam contoh kita, meskipun Post memiliki banyak komentar, ID tersebut tidak akan ditambahkan ke objek Post:

 POST /posts { "post": { "content": "" /* no associated post IDs added here */ } }

Untuk "memaksa" DS.hasMany ID menjadi serial juga, Anda dapat menggunakan Embedded Records Mixin.

Hubungan Banyak-ke-banyak

Katakan bahwa dalam model kami, seorang Penulis mungkin memiliki beberapa Postingan dan sebuah Postingan mungkin memiliki beberapa Penulis.

Untuk merepresentasikan hubungan ini dalam Data Ember, kita dapat menggunakan DS.hasMany('author', {async: true}) pada Post dan DS.hasMany('post', {async: true}) pada Author:

 App.Author = DS.Model.extend({ name: DS.attr('string'), posts: DS.hasMany('post', {async: true}) }); App.Post = DS.Model.extend({ content: DS.attr('string'), authors: DS.hasMany('author', {async: true}) });

Kita kemudian bisa mendapatkan item terkait dengan author.get('posts') dan menambahkan asosiasi baru dengan author.get('posts').then(function(posts){ return posts.pushObject(aPost);}) .

Server kemudian akan merespon dengan array ID untuk objek yang sesuai; misalnya:

 GET /authors { "authors": [ { "id": "1", "name": "", "posts": ["12"] /* IDs of posts associated with this author */ } ] } GET /posts { "posts": [ { "id": "12", "content": "", "authors": ["1"] /* IDs of authors associated with this post */ } ] }

Karena ini adalah hubungan banyak ke banyak, RESTSerializer menambahkan larik ID objek terkait; misalnya:

 POST /posts { "post": { "content": "", "authors": ["1", "4"] /* IDs of authors associated with this post */ } }

Contoh Dunia Nyata: Meningkatkan Sistem Pemesanan yang Ada

Dalam sistem pemesanan kami yang ada, setiap Pengguna memiliki banyak Pesanan dan setiap Pesanan memiliki banyak Barang. Sistem kami memiliki beberapa Penyedia (yaitu, vendor) dari siapa produk dapat dipesan, tetapi setiap pesanan hanya dapat berisi item dari satu penyedia.

Persyaratan baru #1: Aktifkan satu pesanan untuk menyertakan item dari beberapa penyedia.

Dalam sistem yang ada, ada hubungan satu-ke-banyak antara Penyedia dan Pesanan. Namun, begitu kami memperluas pesanan untuk menyertakan item dari beberapa penyedia, hubungan sederhana ini tidak lagi memadai.

Khususnya, jika penyedia dikaitkan dengan seluruh pesanan, dalam sistem yang disempurnakan, pesanan itu mungkin juga mencakup item yang dipesan dari penyedia lain. Oleh karena itu, perlu ada cara untuk menunjukkan bagian mana dari setiap pesanan yang relevan untuk setiap penyedia. Selain itu, ketika penyedia mengakses pesanan mereka, mereka seharusnya hanya memiliki visibilitas ke item yang dipesan dari mereka, bukan item lain yang mungkin dipesan pelanggan dari penyedia lain.

Salah satu pendekatannya adalah dengan memperkenalkan dua asosiasi banyak-ke-banyak yang baru; satu antara Pesanan dan Barang dan satu lagi antara Pesanan dan Penyedia.

Namun, untuk membuatnya lebih sederhana, kami memperkenalkan konstruksi baru ke dalam model data yang kami sebut sebagai "Pesanan Penyedia".

Merancang Hubungan

Model data yang disempurnakan perlu mengakomodasi asosiasi berikut:

  • Hubungan satu-ke-banyak antara Pengguna dan Pesanan (setiap Pengguna dapat dikaitkan dengan 0 hingga n Pesanan) dan hubungan Satu-ke-banyak antara Pengguna dan Penyedia (setiap Pengguna dapat dikaitkan dengan 0 hingga n Penyedia)

     App.User = DS.Model.extend({ firstName: DS.attr('string'), lastName: DS.attr('string'), isAdmin: DS.attr('boolean'), orders: DS.hasMany('order', {async: true}), providers: DS.hasMany('provider', {async: true}) });
  • Hubungan satu-ke-banyak antara Pesanan dan ProviderOrders (setiap Pesanan terdiri dari 1 hingga n ProviderOrders):

     App.Order = DS.Model.extend({ createdAt: DS.attr('date'), user: DS.belongsTo('user', {async: true}), providerOrders: DS.hasMany('providerOrders', {async: true}) });
  • Hubungan satu-ke-banyak antara Penyedia dan ProviderOrders (setiap Provider dapat dikaitkan dengan 0 hingga n ProviderOrders):

     App.Provider = DS.Model.extend({ link: DS.attr('string'), admin: DS.belongsTo('user', {async: true}), orders: DS.belongsTo('providerOrder', {async: true}), items: DS.hasMany('items', {async: true}) });
  • Hubungan satu-ke-banyak antara ProviderOrders dan Items (setiap ProviderOrder terdiri dari 1 hingga n item):

     App.ProviderOrder = DS.Model.extend({ // One of ['processed', 'in_delivery', 'delivered'] status: DS.attr('string'), provider: DS.belongsTo('provider', {async: true}), order: DS.belongsTo('order', {async: true}), items: DS.hasMany('item', {async: true}) }); App.Item = DS.Model.extend({ name: DS.attr('string'), price: DS.attr('number'), order: DS.belongsTo('order', {async: true}), provider: DS.belongsTo('provider', {async: true}) });

Dan jangan lupa definisi Rute kami:

 App.OrdersRoute = Ember.Route.extend({ model: function(){ // GET /orders // Retrieves all orders. return this.store.find('order'); } });

Sekarang setiap ProviderOrder memiliki satu Provider, yang merupakan tujuan utama kami. Item dipindahkan dari Order ke ProviderOrder dan asumsinya adalah bahwa semua item dalam satu ProviderOrder milik satu Provider.

Meminimalkan Kode Churn

Sayangnya, ada beberapa perubahan yang melanggar di sini. Jadi mari kita lihat bagaimana Ember Data dapat membantu kita meminimalkan churn kode yang dihasilkan di basis kode kita.

Sebelumnya, kami mendorong item dengan items.pushObject(item) . Sekarang kita harus terlebih dahulu menemukan ProviderOrder yang sesuai dan mendorong Item ke sana:

 order.get('providerOrders').then(function(providerOrders){ return providerOrders.findBy('id', item.get('provider.id') ) .get('items').then(functions(items){ return items.pushObject(item); }); });

Karena ini banyak churn, dan lebih banyak pekerjaan Order daripada controller, lebih baik jika kita memindahkan kode ini ke Order#pushItem :

 App.Order = DS.Model.extend({ createdAt: DS.attr('date'), user: DS.belongsTo('user', {async: true}), providerOrders: DS.hasMany('providerOrders', {async: true}), /** returns a promise */ pushItem: function(item){ return this.get('providerOrders').then(function(providerOrders){ return providerOrders.findBy('id', item.get('provider.id') ) .get('items').then(functions(items){ return items.pushObject(item); }); }); } });

Sekarang kita dapat menambahkan item langsung pada pesanan seperti order.pushItem(item) .

Dan untuk daftar Item dari setiap Pesanan:

 App.Order = DS.Model.extend({ /* ... */ /** returns a promise */ items: function(){ return this.get('restaurantOrders').then(function(restaurantOrders){ var arrayOfPromisesContainingItems = restaurantOrders.mapBy('items') return arrayOfPromisesContainingItems.then(function(items){ return items.reduce(function flattenByReduce(memo, index, element){ return memo.pushObject(element); }, Ember.A([])); }); }); }.property('[email protected]') /* ... */ }); App.ItemsRoute = Ember.Route.extend({ model: function(){ // Multiple GET /items with ids[] query parameter. // Returns a promise. return this.modelFor('orders.show').get('items'); } });

Hubungan Polimorfik

Sekarang mari kita perkenalkan permintaan peningkatan tambahan ke sistem kami yang semakin memperumit masalah:

Persyaratan baru #2: Mendukung berbagai jenis Penyedia.

Untuk contoh sederhana kami, katakanlah dua jenis Penyedia ("Toko" dan "Toko Buku") didefinisikan:

 App.Shop = App.Provider.extend({ status: DS.attr('string') }); App.BookStore = App.Provider.extend({ name: DS.attr('string') });

Di sinilah dukungan Ember Data untuk hubungan polimorfik akan berguna. Data Ember mendukung hubungan polimorfik satu-ke-satu, satu-ke-banyak, dan banyak-ke-banyak. Ini dilakukan hanya dengan menambahkan atribut polymorphic: true ke spesifikasi asosiasi. Sebagai contoh:

 App.Provider = DS.Model.extend({ providerOrders: DS.hasMany('providerOrder', {async: true}) }); App.ProviderOrder = DS.Model.extend({ provider: DS.belongsTo('provider', {polymorphic: true, async: true}) });

Bendera polymorphic di atas menunjukkan bahwa ada berbagai jenis Penyedia yang dapat dikaitkan dengan Pesanan Penyedia (dalam kasus kami, baik Toko atau Toko Buku).

Ketika suatu relasi bersifat polimorfik, respons server harus menunjukkan ID dan tipe objek yang dikembalikan ( RESTSerializer melakukan ini secara default); misalnya:

 GET /providerOrders { "providerOrders": [{ "status": "in_delivery", "provider": 1, "providerType": "shop" }] }

Memenuhi Persyaratan Baru

Kami membutuhkan Penyedia dan Item polimorfik untuk memenuhi persyaratan. Karena ProviderOrder menghubungkan Penyedia dengan Item, kita dapat mengubah asosiasinya menjadi asosiasi polimorfik:

 App.ProviderOrder = DS.Model.extend({ provider: DS.belongsTo('provider', {polymorphic: true, async: true}), items: DS.hasMany('item', {polymorphic: true, async: true}) });

Ada masalah yang tersisa, meskipun: Penyedia memiliki asosiasi non-polimorfik ke Item tetapi Item adalah tipe abstrak. Oleh karena itu, kami memiliki dua opsi untuk mengatasi hal ini:

  1. Mengharuskan semua penyedia untuk dikaitkan dengan jenis barang yang sama (yaitu, menyatakan jenis Barang tertentu untuk dikaitkan dengan Penyedia)
  2. Deklarasikan asosiasi items pada Penyedia sebagai polimorfik

Dalam kasus kami, kami harus menggunakan opsi #2 dan mendeklarasikan asosiasi items pada Penyedia sebagai polimorfik:

 App.Provider = DS.Model.extend({ /* ... */ items: DS.hasMany('items', {polymorphic: true, async: true}) });

Perhatikan bahwa ini tidak memperkenalkan churn kode apa pun ; semua asosiasi hanya bekerja seperti yang mereka lakukan sebelum perubahan ini. Kekuatan Data Ember yang terbaik!

Bisakah Ember Data benar-benar memodelkan semua data saya?

Tentu saja ada pengecualian, tetapi saya menganggap konvensi ActiveRecord sebagai cara standar dan fleksibel untuk menyusun dan memodelkan data, jadi izinkan saya menunjukkan kepada Anda bagaimana konvensi ActiveRecord memetakan ke Data Ember:

has_many :pengguna melalui: :kepemilikan atau Mewakili Model Menengah

Ini akan berkonsultasi dengan model pivot yang disebut Kepemilikan untuk menemukan Pengguna terkait. Jika model pivot pada dasarnya adalah tabel pivot , Anda dapat menghindari pembuatan model perantara di Ember Data dan mewakili hubungan dengan DS.hasMany di kedua sisi.

Namun jika Anda memerlukan hubungan pivot di dalam front-end Anda, siapkan model Kepemilikan yang menyertakan DS.belongsTo('user', {async: true}) dan DS.belongsTo('provider', {async: true}) , dan kemudian menambahkan properti pada Pengguna dan Penyedia yang memetakan melalui asosiasi menggunakan Kepemilikan; misalnya:

 App.User = DS.Model.extend({ // Omitted ownerships: DS.hasMany('ownership'), /** returns a promise */ providers: function(){ return this.get('ownerships').then(function(ownerships){ return ownerships.mapBy('provider'); }); }.property('[email protected]') }); App.Ownership = DS.Model.extend({ // One of ['approved', 'pending'] status: DS.attr('string'), user: DS.belongsTo('user', {async: true}), provider: DS.belongsTo('provider', {async: true}) }); App.Provider = DS.Model.extend({ // Omitted ownerships: DS.hasMany('ownership', {async: true}), /** returns a promise */ users: function(){ return this.get('ownerships').then(function(ownerships){ return ownerships.mapBy('user'); }); }.property('[email protected]') });

has_many :pemetaan, sebagai: dapat ditemukan

Dalam objek ActiveRecord kami, kami memiliki hubungan polimorfik yang khas:

 class User < ActiveRecord::Base has_many :mappings, as: locatable has_many :locations, through: :mappings end class Mapping < ActiveRecord::Base belongs_to :locatable, polymorphic: true belongs_to :location end class Location < ActiveRecord::Base has_many :mappings has_many :users, through: :mappings, source: :locatable, source_type: 'User' has_many :providers, through: :mappings, source: :locatable, source_type: 'Provider' def locatables users + providers end end

Ini adalah hubungan banyak (polimorfik) ke banyak (non-polimorfik normal). Dalam Data Ember kita dapat mengekspresikannya dengan DS.hasMany('locatable', {polymorphic: true, async: true}) dan DS.hasMany('location', {async: true}) statis :

 App.Locatable = DS.Model.extend({ locations: DS.hasMany('location', {async: true}) }); App.User = App.Locatable.extend({ userName: DS.attr('string') }); App.Location = DS.Model.extend({ locatables: DS.hasMany('locatable', {polymorphic: true, async: true}) });

Untuk Locatables, seperti Pengguna, server harus mengembalikan ID untuk lokasi terkait:

 GET /users { "users": [ { "id": "1", "userName": "Pooyan", "locations": ["1"] } ] }

Untuk Lokasi, server harus mengembalikan ID dan tipe Locatable dalam larik objek:

 GET /locations { "locations": [ { "id": "1", "locatables": [ {"id": "1", "type": "user"}, {"id": "2", "type": "provider"} ] } ] }

Selain itu, Anda dapat merepresentasikan hubungan menurut jenisnya dengan hubungan banyak-ke-banyak statis:

 App.User = App.Locatable.extend({ userName: DS.attr('string'), locations: DS.hasMany('location', {async: true}) }); App.Provider = App.Locatable.extend({ link: DS.attr('string'), locations: DS.hasMany('location, {async: true} }); App.Location = DS.Model.extend({ users: DS.hasMany('user', {async: true}), providers: DS.hasMnay('provider', {async: true}) });

Dan bagaimana dengan data waktu nyata?

Data Ember memiliki push , pushPayload , dan update . Anda selalu dapat secara manual mendorong catatan baru/yang diperbarui ke cache lokal Data Ember (disebut toko) dan itu akan menangani sisanya.

 App.ApplicationRoute = Ember.Route.extend({ activate: function(){ socket.on('recordUpdated', function(response){ var type = response.type; var payload = response.payload; this.store.pushPayload(type, payload); }); } });

Saya pribadi hanya menggunakan soket untuk acara dengan muatan yang sangat kecil. Peristiwa khas adalah 'recordUpdated' dengan muatan {"type": "shop", "id": "14"} dan kemudian di ApplicationRoute saya akan memeriksa apakah catatan itu ada di cache lokal (toko) dan jika itu saya hanya akan mengambilnya kembali.

 App.ApplicationRoute = Ember.Route.extend({ activate: function(){ socket.on('recordUpdated', function(response){ var type = response.type; var id = response.id; if( this.store.hasRecordForId(type, id) ){ this.store.find(type, id); } }); } });

Dengan cara ini kami dapat mengirim rekaman acara yang diperbarui ke semua klien tanpa biaya tambahan yang tidak dapat diterima.

Pada dasarnya ada dua pendekatan dalam Data Ember untuk menangani data waktu nyata:

  1. Tulis Adaptor untuk saluran komunikasi waktu nyata Anda dan gunakan sebagai pengganti RESTAdapter.
  2. Dorong rekaman ke toko utama kapan pun tersedia.

Kelemahan dengan opsi pertama adalah agak mirip dengan menciptakan kembali roda. Untuk opsi kedua, kita perlu mengakses toko utama, yang tersedia di semua rute sebagai route#store .

Bungkus

Dalam artikel ini, kami telah memperkenalkan Anda pada konstruksi dan paradigma utama Ember Data, yang menunjukkan nilai yang dapat diberikannya kepada Anda sebagai pengembang. Ember Data menyediakan alur kerja pengembangan yang lebih fleksibel dan ramping, meminimalkan churn kode sebagai respons terhadap apa yang seharusnya menjadi perubahan berdampak tinggi.

Investasi di muka (kurva waktu dan pembelajaran) yang Anda buat dalam menggunakan Data Ember untuk proyek Anda pasti akan terbukti bermanfaat karena sistem Anda pasti berkembang dan perlu diperpanjang, dimodifikasi, dan ditingkatkan.


LAMPIRAN: Topik Data Ember Tingkat Lanjut

Lampiran ini memperkenalkan sejumlah topik Data Ember yang lebih canggih termasuk:

  • Desain Modular Ember
  • Sideloading
  • Tautan
  • Serializer dan Adaptor Model Aktif
  • Campuran Rekaman Tertanam
  • Pengubah Asosiasi (Async, Inverse, dan Polymorphic)
  • Parameter 'id' dalam GET Requests

Desain modular

Ember Data memiliki desain modular di bawah tenda. Komponen utama meliputi:

  1. Adapters bertanggung jawab untuk menangani komunikasi, saat ini hanya REST melalui HTTP.
  2. Serializers mengelola pembuatan model dari JSON atau sebaliknya.
  3. Store cache yang dibuat catatan.
  4. Container merekatkan semua ini bersama-sama.

Manfaat dari desain ini antara lain:

  1. Deserialisasi dan penyimpanan data bekerja secara independen dari saluran komunikasi yang digunakan dan sumber daya yang diminta.
  2. Konfigurasi yang berfungsi, seperti ActiveModelSerializer atau EmbeddedRecordsMixin, disediakan secara langsung.
  3. Sumber data (misalnya, Penyimpanan Lokal, implementasi CouchDB, dll.) dapat ditukar masuk dan keluar dengan mengubah adaptor.
  4. Meskipun banyak konvensi tentang konfigurasi, dimungkinkan untuk mengonfigurasi semuanya dan membagikan konfigurasi/implementasi Anda dengan komunitas.

Sideloading

Ember Data mendukung “sideloading” data; yaitu, menunjukkan data tambahan yang harus diambil (bersama dengan data primer yang diminta) untuk membantu mengkonsolidasikan beberapa permintaan HTTP terkait.

Kasus penggunaan yang umum adalah mengesampingkan model terkait. Misalnya, setiap Toko memiliki banyak bahan makanan, jadi kami dapat memasukkan semua bahan makanan terkait di /shops response:

 GET /shops { "shops": [ { "id": "14", "groceries": ["98", "99", "112"] } ] }

Saat asosiasi bahan makanan diakses, Data Ember akan mengeluarkan:

 GET /groceries?ids[]=98&ids[]=99&ids[]=112 { "groceries": [ { "id": "98", "provider": "14", "type": "shop" }, { "id": "99", "provider": "14", "type": "shop" }, { "id": "112", "provider": "14", "type": "shop" } ] }

Namun, jika kami mengembalikan Groceries terkait di /shops endpoint, Ember Data tidak perlu mengeluarkan permintaan lain:

 GET /shops { "shops": [ { "id": "14", "groceries": ["98", "99", "112"] } ], "groceries": [ { "id": "98", "provider": "14", "type": "shop" }, { "id": "99", "provider": "14", "type": "shop" }, { "id": "112", "provider": "14", "type": "shop" } ] }

Tautan

Data Ember menerima tautan sebagai pengganti ID asosiasi. Saat asosiasi yang ditentukan sebagai tautan diakses, Ember Data akan mengeluarkan permintaan GET ke tautan itu untuk mendapatkan catatan terkait.

Misalnya, kami dapat mengembalikan tautan untuk asosiasi bahan makanan:

 GET /shops { "shops": [ { "id": "14", "links": { "groceries": "/shops/14/groceries" } } ] }

Dan Ember Data kemudian akan mengeluarkan permintaan ke /shops/14/groceries

 GET /shops/14/groceries { "groceries": [ { "id": "98", "provider": "14", "type": "shop" }, { "id": "99", "provider": "14", "type": "shop" }, { "id": "112", "provider": "14", "type": "shop" } ] }

Ingatlah bahwa Anda masih perlu merepresentasikan asosiasi dalam data; tautan hanya menyarankan permintaan HTTP baru dan tidak akan memengaruhi asosiasi.

Serializer dan Adaptor Model Aktif

Diperdebatkan, ActiveModelSerializer dan ActiveModelAdapter lebih banyak digunakan dalam praktik daripada RESTSerializer dan RESTAdapter . Khususnya, ketika backend menggunakan Ruby on Rails dan permata ActiveModel::Serializers , opsi terbaik adalah menggunakan ActiveModelSerializer dan ActiveModelAdapter , karena keduanya mendukung ActiveModel::Serializers di luar kotak.

Untungnya, perbedaan penggunaan antara ActiveModelSerializer / ActiveModelAdapter dan RESTSerializer / RESTAdapter cukup terbatas; yaitu:

  1. ActiveModelSerializer akan menggunakan nama bidang snake_case sementara RESTSerializer membutuhkan nama bidang camelCased.
  2. ActiveModelAdapter mengeluarkan permintaan ke metode API snake_case sementara RESTSerializer mengeluarkan permintaan ke metode API camelCased.
  3. ActiveModelSerializer mengharapkan nama bidang terkait asosiasi diakhiri dengan _id atau _ids sementara RESTSerializer mengharapkan nama bidang terkait asosiasi sama dengan bidang asosiasi.

Terlepas dari pilihan Adaptor dan Serializer, model Ember Data akan sama persis. Hanya representasi JSON dan titik akhir API yang akan berbeda.

Ambil ProviderOrder terakhir kami sebagai contoh:

 App.ApplicationSerializer = DS.ActiveModelSerializer.extend({ }); App.ProviderOrder = DS.Model.extend({ // One of ['processed', 'in_delivery', 'delivered'] status: DS.attr('string'), provider: DS.belongsTo('provider', {polymorphic: true, async: true}), order: DS.belongsTo('order', {async: true}), items: DS.hasMany('item', {polymorphic: true, async: true}) });

Dengan Serializer dan Adaptor Model Aktif, server harus mengharapkan:

 Post /provider_orders { "provider_order": [ "status": "", "provider": {"id": "13", "type": "shop"} "order_id": "68", ] }

… dan harus merespons dengan:

 GET /provider_orders { "provider_orders": [ "id": "1", "status": "", "provider": {"id": "13", "type": "shop"} "order_id": "68", "items": [ {"id": "57", "type": "grocery"}, {"id": "89", "type": "grocery"} ] ] }

Campuran Rekaman Tertanam

DS.EmbeddedRecordsMixin adalah ekstensi untuk DS.ActiveModelSerializer yang memungkinkan konfigurasi bagaimana asosiasi menjadi serial atau deserialized. Meskipun belum lengkap (terutama yang berkaitan dengan asosiasi polimorfik), ini tetap menarik.

Kamu bisa memilih:

  1. Tidak untuk membuat serial atau deserialize asosiasi.
  2. Untuk membuat serial atau deserialize asosiasi dengan id atau id.
  3. Untuk membuat serial atau deserialize asosiasi dengan model yang disematkan.

Ini sangat berguna dalam hubungan satu-ke-banyak di mana, secara default, DS.hasMany terkait ID tidak ditambahkan ke objek yang serial. Ambil keranjang belanja yang memiliki banyak barang sebagai contoh. Dalam contoh ini, Keranjang sedang dibuat saat Item diketahui. Namun, saat Anda menyimpan Keranjang, Data Ember tidak akan secara otomatis memasukkan ID Item terkait pada payload permintaan.

Namun, menggunakan DS.EmbeddedRecordsMixin , dimungkinkan untuk memberi tahu Ember Data untuk membuat serial ID item pada Keranjang sebagai berikut:

 App.CartSerializer = DS.ActiveModelSerializer .extend(DS.EmbeddedRecordsMixin) .extend{ attrs: { // thanks EmbeddedRecordsMixin! items: {serialize: 'ids', deserialize: 'ids'} } }); App.Cart = DS.Model.extend({ items: DS.hasMany('item', {async: true}) }); App.Item = DS.Model.extend({ cart: DS.belongsTo('item', {async: true}) });

Seperti yang ditunjukkan dalam contoh di atas, EmbeddedRecordsMixin memungkinkan spesifikasi eksplisit dari asosiasi mana yang akan dibuat serial dan/atau deserialize melalui objek attrs . Nilai yang valid untuk serialize dan deserialize adalah: - 'no' : jangan sertakan asosiasi dalam data serial/deserialisasi - 'id' atau 'ids' : sertakan ID terkait dalam data serial/deserialisasi - 'records ': sertakan properti aktual (yaitu, merekam nilai bidang) sebagai larik dalam data serial/deserialisasi

Pengubah Asosiasi (Async, Inverse, dan Polymorphic)

Pengubah asosiasi berikut didukung: polymorphic , inverse , dan async

Pengubah Polimorfik

Dalam asosiasi polimorfik, satu atau kedua sisi asosiasi mewakili kelas objek, bukan objek tertentu.

Ingat contoh blog kami sebelumnya di mana kami perlu mendukung kemampuan untuk menandai pos dan halaman. Untuk mendukung ini, kami telah sampai pada model berikut:

  • Setiap Posting adalah Taggable dan memiliki banyak Tag
  • Setiap Halaman adalah Taggable dan memiliki banyak Tag
  • Setiap Tag memiliki banyak Taggable polimorfik

Mengikuti model itu, pengubah polymorphic dapat digunakan untuk menyatakan bahwa Tag terkait dengan semua jenis "Taggable" (yang dapat berupa Postingan atau Halaman), sebagai berikut:

 // A Taggable is something that can be tagged (ie, that has tags) App.Taggable = DS.Model.extend({ tags: DS.hasMany('tag') }); // A Page is a type of Taggable App.Page = App.Taggable.extend({}); // A Post is a type of Taggable App.Post = App.Taggable.extend App.Tag = DS.Model.extend({ // the "other side" of this association (ie, the 'taggable') is polymorphic taggable: DS.belongsTo('taggable', {polymorphic: true}) });

Pengubah Terbalik

Biasanya asosiasi bersifat dua arah. Misalnya, "Posting memiliki banyak Komentar" akan menjadi satu arah asosiasi, sedangkan "Komentar milik sebuah Postingan" akan menjadi arah lain (yaitu, "terbalik") dari asosiasi itu.

Dalam kasus di mana tidak ada ambiguitas dalam asosiasi, hanya satu arah yang perlu ditentukan karena Ember Data dapat menyimpulkan bagian kebalikan dari asosiasi.

However, in cases where objects in your model have multiple associations with one another, Ember Data cannot derive the inverse of each association automatically and it therefore needs to be specified using the invers modifier.

Consider, for example, a case where:

  • Each Page may have many Users as Collaborators
  • Each Page may have many Users as Maintainers
  • Each User may have many Favorite Pages
  • Each User may have many Followed Pages

This would need to be specified as follows in our model:

 App.User = DS.Model.extend({ favoritePages: DS.hasMany('page', {inverse: 'favoritors}), followedPages: DS.hasMany('page', {inverse: 'followers'}), collaboratePages: DS.hasMany('page', {inverse: 'collaborators'}), maintainedPages: DS.hasMany('page', {inverse: 'maintainers'}) }); App.Page = DS.Model.extend({ favoritors: DS.hasMany('user', {inverse: 'favoritePages'}), followers: DS.hasMany('user', {inverse: 'followedPages'}), collaborators: DS.hasMany('user', {inverse: 'collaboratedPages}), maintainers: DS.hasMany('user', {inverse: 'maintainedPages'}) });

Async Modifier

When data needs to be retrieved based on relevant associations, that associated data may or may not already have been loaded. If not, a synchronous association will throw an error since the associated data has not been loaded.

{async: true} indicates that the request for the associated data should be handled asynchronously. The request therefore immediately returns a promise and the supplied callback is invoked once the associated data has been retrieved and is available.

When async is false , getting associated objects would be done as follows:

 post.get('comments').pushObject(comment);

When async is true , getting associated objects would be done as follows (note the callback function specified):

 post.get('comments').then(function(comments){ comments.pushObject(comment); })

Note well: The current default value of async is false , but in Ember Data 1.0 the default will be true .

'ids' Parameter in GET Requests

By default, Ember Data expects that resources aren't nested. Take Posts which have many Comments as an example. In a typical interpretation of REST, API endpoints might look like these:

 GET /users GET /users/:id/posts GET /users/:id/posts/:id/comments

However, Ember Data expects API endpoints to be flat, and not nested; misalnya:

 GET /users with ?ids[]=4 as query parameters. GET /posts with ?ids[]=18&ids[]=27 as query parameters. GET /comments with ?ids[]=74&ids[]=78&ids[]=114&ids[]=117 as query parameters.

In the above example, ids is the name of the array, [] indicates that this query parameter is an array, and = indicates that a new ID is being pushed onto the array.

Now Ember Data can avoid downloading resources it already has or defer downloading them to very last moment.

Also, to white list an array parameter using the StrongParameters gem, you can declare it as params.require(:shop).permit(item_ids: []) .