Ember Data: Ember-data Library için Kapsamlı Bir Eğitim

Yayınlanan: 2022-03-11

Ember Data (aka ember-data veya ember.data), Ember.js uygulamalarında model verilerini güçlü bir şekilde yönetmek için bir kitaplıktır. Ember Data geliştiricileri, altta yatan kalıcılık mekanizmasına karşı agnostik olacak şekilde tasarlandığını belirtir, bu nedenle HTTP üzerinden JSON API'leri ile akışlı WebSockets veya yerel IndexedDB depolama ile olduğu kadar iyi çalışır. ActiveRecord gibi sunucu tarafı nesne ilişkisel eşlemelerinde (ORM'ler) bulacağınız birçok olanağı sağlar, ancak tarayıcıdaki benzersiz JavaScript ortamı için özel olarak tasarlanmıştır.

Ember Data'nın anlaşılması biraz zaman alabilir, ancak bunu yaptıktan sonra, büyük olasılıkla yatırıma değdiğini göreceksiniz. Sonuç olarak, sisteminizin geliştirilmesini, iyileştirilmesini ve bakımını çok daha kolay hale getirecektir.

Bir API, Ember Data modelleri, bağdaştırıcılar ve serileştiriciler kullanılarak temsil edildiğinde, her ilişkilendirme yalnızca bir alan adı olur. Bu, her bir ilişkinin dahili ayrıntılarını kapsar, böylece kodunuzun geri kalanını ilişkilendirmelerin kendisinde yapılan değişikliklerden yalıtır. Örneğin, belirli bir ilişkilendirmenin polimorfik olup olmadığı veya birçok ilişkilendirme haritasının sonucu olup olmadığı, kodunuzun geri kalanının umurunda olmayacaktır.

Ayrıca, kod tabanınız, önemli olsalar bile, arka uç değişikliklerinden büyük ölçüde yalıtılmıştır, çünkü kod tabanınızın beklediği tek şey, modelin bir JSON veya XML veya YAML temsili değil, modeller üzerindeki alanlar ve işlevlerdir.

Bu eğitimde, Ember Data'nın en göze çarpan özelliklerini tanıtacağız ve gerçek dünyadan bir örneğe odaklanarak kod karmaşasını en aza indirmeye nasıl yardımcı olduğunu göstereceğiz.

Bir dizi daha gelişmiş Ember Data konusunu ve örneğini tartışan bir ek de sağlanmıştır.

Not: Bu makalede, Ember.js ile bazı temel aşinalıkların olduğu varsayılmaktadır. Ember.js'ye aşina değilseniz, giriş için popüler Ember.js eğitimimize göz atın. Ayrıca Rusça, Portekizce ve İspanyolca dillerinde sunulan eksiksiz bir JavaScript kılavuzumuz da var.

Ember Veri Değer Önerisi

Ember Data kitaplığının müşteri ihtiyaçlarını karşılamaya nasıl yardımcı olabileceğine dair bir örnek.

Basit bir örnekle başlayalım.

Diyelim ki temel bir blog sistemi için çalışan bir kod tabanımız var. Sistem, birbirleriyle çoktan çoğa ilişkisi olan Gönderiler ve Etiketler içerir.

Sayfaları desteklemek için bir gereklilik alana kadar her şey yolunda. Gereklilik ayrıca, WordPress'te bir Sayfayı etiketlemek mümkün olduğundan, bizim de bunu yapabilmemiz gerektiğini belirtir.

Artık Etiketler yalnızca Gönderiler için geçerli olmayacak, Sayfalar için de geçerli olabilir. Sonuç olarak, Etiketler ve Gönderiler arasındaki basit ilişkilendirmemiz artık yeterli olmayacak. Bunun yerine, aşağıdaki gibi çoktan çoğa tek taraflı polimorfik bir ilişkiye ihtiyacımız olacak:

  • Her Gönderi bir Etiketlenebilirdir ve birçok Etiketi vardır
  • Her Sayfa bir Etiketlenebilirdir ve birçok Etiketi vardır
  • Her Etiketin birçok polimorfik Etiketlenebiliri vardır

Bu yeni, daha karmaşık ilişkilendirme kümesine geçişin, kodumuz boyunca önemli sonuçları olması muhtemeldir ve bu da birçok çalkantıya neden olur. JSON ile bir polimorfik ilişkilendirmeyi nasıl seri hale getireceğimize dair hiçbir fikrimiz olmadığı için, muhtemelen GET /posts/:id/tags ve GET /pages/:id/tags gibi daha fazla API bitiş noktası oluşturacağız. Ardından mevcut JSON ayrıştırıcı fonksiyonlarımızın hepsini atacağız ve eklenen yeni kaynaklar için yenilerini yazacağız. Ah. Sıkıcı ve acı verici.

Şimdi, Ember Data'yı kullanarak buna nasıl yaklaşacağımızı düşünelim.

Ember Data'da, bu değiştirilmiş ilişkilendirme setini barındırmak, basitçe aşağıdakilerden hareket etmeyi içerir:

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

ile:

 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}) });

Kodumuzun geri kalanında ortaya çıkan karışıklık minimum olacak ve şablonlarımızın çoğunu yeniden kullanabilecektik. Özellikle, tags ilişkilendirme adının değişmediğini unutmayın. Ek olarak, kod tabanımızın geri kalanı yalnızca tags ilişkisinin varlığına dayanır ve ayrıntılarından habersizdir.

Bir Ember Veri Astarı

Gerçek dünyadan bir örneğe dalmadan önce, bazı Ember Data temellerini gözden geçirelim.

Rotalar ve Modeller

Ember.js'de yönlendirici, şablonları görüntülemekten, verileri yüklemekten ve uygulama durumunu ayarlamaktan sorumludur. Yönlendirici geçerli URL'yi tanımladığınız rotalarla eşleştirir, bu nedenle bir şablonun görüntüleyeceği modeli belirtmekten bir Rota sorumludur (Ember, bu modelin Ember.Object alt sınıfı olmasını bekler):

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

Ember Data, DS.Model bir alt sınıfı olan Ember.Object ve kolaylık sağlamak için tek bir kaydı veya birden çok kaydı kaydetme veya güncelleme gibi yetenekler ekler.

Yeni bir Model oluşturmak için, DS.Model bir alt sınıfını yaratırız (örneğin, App.User = DS.Model.extend({}) ).

Ember Data, sunucudan iyi tanımlanmış, sezgisel bir JSON yapısı bekler ve yeni oluşturulan kayıtları aynı yapılandırılmış JSON'a serileştirir.

Ember Data, Modellerle çalışmak için DS.RecordArray gibi bir dizi sınıfları paketi de sağlar. Bunların birden çoğa veya çoktan çoğa ilişkileri işleme, verilerin eşzamansız olarak alınmasını işleme vb. gibi sorumlulukları vardır.

Model Özellikleri

Temel model öznitelikleri DS.attr kullanılarak tanımlanır; Örneğin:

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

Yalnızca DS.attr tarafından oluşturulan alanlar, kayıt oluşturmak veya güncellemek için sunucuya iletilen yüke dahil edilecektir.

DS.attr dört veri türünü kabul eder: string , number , boolean ve date .

RESTSerializer

Varsayılan olarak:

  • Ember Data, API yanıtlarından nesneler oluşturmak (seri hale getirme) ve API istekleri için JSON oluşturmak (seri hale getirme) için RESTSerializer kullanır.
  • RESTSerializer , DS.belongsTo tarafından oluşturulan alanların, sunucudan gelen JSON yanıtında user adlı bir alana sahip olmasını bekler. Bu alan, başvurulan kaydın kimliğini içerir.
  • RESTSerializer , ilişkili siparişin kimliğiyle API'ye iletilen yüke bir user alanı ekler.

Örneğin, bir yanıt şöyle görünebilir:

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

Ve bir siparişi kaydetmek için RESTSerializer tarafından oluşturulan bir HTTP isteği şöyle görünebilir:

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

Birebir İlişkiler

Örneğin, her Kullanıcının benzersiz bir Profili olduğunu varsayalım. Bu ilişkiyi Ember DS.belongsTo hem Kullanıcıda hem de Profilde DS.belongsTo kullanarak temsil edebiliriz:

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

Daha sonra user.get('profile') ile ilişkilendirmeyi alabilir veya user.set('profile', aProfile) ile ayarlayabiliriz.

RESTSerializer , her model için ilgili modelin kimliğinin sağlanmasını bekler; Örneğin:

 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 */ } ] }

Benzer şekilde, bir istek yükünde ilişkili modelin kimliğini içerir:

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

Bire Çok ve Çoka Bir İlişkiler

Bir Gönderinin birçok Yorumu olduğu bir modelimiz olduğunu varsayalım. Ember Data'da, bu ilişkiyi DS.hasMany('comment', {async: true}) ve DS.belongsTo('post', {async: true}) ile temsil edebiliriz:

 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}) });

Daha sonra post.get('comments', {async: true}) ile ilişkili öğeleri alabilir ve post.get('comments').then(function(comments){ return comments.pushObject(aComment);}) ile yeni bir ilişkilendirme ekleyebiliriz. post.get('comments').then(function(comments){ return comments.pushObject(aComment);}) .

Sunucu daha sonra bir Gönderideki ilgili yorumlar için bir dizi kimlikle yanıt verecektir:

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

… ve her Yorum için bir kimlikle:

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

RESTSerializer , ilişkili Gönderinin kimliğini Yoruma ekler:

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

Varsayılan olarak, RESTSerializer'ın serileştirdiği nesnelere DS.hasMany ilişkili kimlikleri eklemeyeceğini unutmayın, çünkü bu ilişkilendirmeler "çok" tarafında belirtilir (yani, DS.belongsTo ilişkilendirmesine sahip olanlar). Bu nedenle, örneğimizde, bir Gönderinin birçok yorumu olmasına rağmen, bu kimlikler Post nesnesine eklenmez :

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

DS.hasMany ID'lerini de serileştirilmeye "zorlamak" için Embedded Records Mixin'i kullanabilirsiniz.

Çoktan çoğa ilişkiler

Modelimizde bir Yazarın birden fazla Gönderisi olabileceğini ve bir Gönderinin birden fazla Yazarı olabileceğini söyleyin.

Bu ilişkiyi Ember Data'da temsil etmek için, DS.hasMany('author', {async: true}) ve DS.hasMany('post', {async: true}) :

 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}) });

Ardından, author.get('posts') ile ilişkili öğeleri alabilir ve author.get('posts').then(function(posts){ return posts.pushObject(aPost);}) ile yeni bir ilişkilendirme ekleyebiliriz.

Sunucu daha sonra karşılık gelen nesneler için bir dizi kimlikle yanıt verecektir; Örneğin:

 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 */ } ] }

Bu çoktan çoğa bir ilişki olduğundan, RESTSerializer ilişkili nesnelerin bir dizi ID'sini ekler; Örneğin:

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

Gerçek Bir Dünya Örneği: Mevcut Bir Sipariş Sistemini Geliştirme

Mevcut sipariş sistemimizde, her Kullanıcının birçok Siparişi ve her Siparişin birçok Öğesi vardır. Sistemimizde, ürünlerin sipariş edilebileceği birden fazla Sağlayıcı (yani satıcılar) vardır, ancak her sipariş yalnızca tek bir sağlayıcıdan öğeler içerebilir.

Yeni gereksinim #1: Birden çok sağlayıcıdan öğeleri dahil etmek için tek bir siparişi etkinleştirin.

Mevcut sistemde Sağlayıcılar ve Siparişler arasında bire çok ilişki vardır. Ancak, bir siparişi birden fazla sağlayıcıdan öğeleri içerecek şekilde genişlettiğimizde, bu basit ilişki artık yeterli olmayacaktır.

Spesifik olarak, bir sağlayıcı bir siparişin tamamıyla ilişkiliyse, gelişmiş sistemde bu sipariş, diğer sağlayıcılardan sipariş edilen öğeleri de çok iyi içerebilir. Bu nedenle, her siparişin hangi bölümünün her sağlayıcıyla ilgili olduğunu belirtmenin bir yolu olmalıdır. Ayrıca, bir sağlayıcı siparişlerine eriştiğinde, müşterinin diğer sağlayıcılardan sipariş etmiş olabileceği diğer öğeleri değil, yalnızca kendilerinden sipariş edilen öğeleri görmelidir.

Bir yaklaşım, iki yeni çoktan çoğa ilişkilendirmeyi tanıtmak olabilir; biri Sipariş ile Ürün arasında ve diğeri Sipariş ile Sağlayıcı arasında.

Ancak, işleri daha basit tutmak için, veri modeline "SağlayıcıSiparişi" olarak adlandırdığımız yeni bir yapı ekledik.

İlişkiler Taslağı

Gelişmiş veri modelinin aşağıdaki ilişkilendirmeleri barındırması gerekecektir:

  • Kullanıcılar ve Siparişler arasında bire çok ilişki (her Kullanıcı 0 ila n Sipariş ile ilişkilendirilebilir) ve Kullanıcılar ve Sağlayıcılar arasında Bire çok ilişki (her Kullanıcı 0 ila n Sağlayıcı ile ilişkilendirilebilir)

     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}) });
  • Siparişler ve SağlayıcıSiparişleri arasında bire çok ilişki (her Sipariş 1 ila n SağlayıcıSiparişinden oluşur):

     App.Order = DS.Model.extend({ createdAt: DS.attr('date'), user: DS.belongsTo('user', {async: true}), providerOrders: DS.hasMany('providerOrders', {async: true}) });
  • Sağlayıcılar ve SağlayıcıSiparişleri arasında bire çok ilişki (her Sağlayıcı, 0 ila n SağlayıcıSiparişi ile ilişkilendirilebilir):

     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}) });
  • SağlayıcıSiparişleri ve Öğeler arasında bire çok ilişki (her SağlayıcıSiparişi 1 ila n öğeden oluşur):

     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}) });

Ve Rota tanımımızı da unutmayalım:

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

Artık her ProviderOrder'ın bir Provider'ı var ve bu bizim ana hedefimizdi. Öğeler Sipariş'ten ProviderOrder'a taşınır ve bir ProviderOrder'daki tüm öğelerin tek bir ProviderOrder'a ait olduğu varsayılır.

Kod Dağınıklığını En Aza İndirme

Ne yazık ki, burada bazı kırılma değişiklikleri var. Öyleyse, Ember Data'nın kod tabanımızda ortaya çıkan kod karmaşasını en aza indirmemize nasıl yardımcı olabileceğini görelim.

Önceden, items.pushObject(item) ile öğeleri itiyorduk. Şimdi önce uygun ProviderOrder'ı bulmamız ve ona bir Öğe göndermemiz gerekiyor:

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

Bu çok fazla çalkantı ve kontrolörünkinden daha fazla Order işi olduğundan, bu kodu Order#pushItem içine taşımamız daha iyi olur:

 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); }); }); } });

Artık order.pushItem(item) gibi öğeleri doğrudan sipariş üzerine ekleyebiliriz.

Ve her Siparişin Öğelerini listelemek için:

 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'); } });

polimorfik ilişkiler

Şimdi, sistemimize işleri daha da karmaşık hale getiren ek bir geliştirme isteği ekleyelim:

Yeni gereksinim #2: Birden çok Sağlayıcı türünü destekleyin.

Basit örneğimiz için, iki tür Sağlayıcının ("Dükkan" ve "Kitapçı") tanımlandığını varsayalım:

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

Ember Data'nın polimorfik ilişkilere verdiği desteğin işe yarayacağı yer burasıdır. Ember Data bire bir, bire çoğa ve çoktan çoğa polimorfik ilişkileri destekler. Bu, basitçe, polymorphic: true ilişki belirtimine uygun özniteliği eklenerek yapılır. Örneğin:

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

Yukarıdaki polymorphic bayrak, bir ProviderOrder ile ilişkilendirilebilecek çeşitli türde Sağlayıcılar olduğunu gösterir (bizim durumumuzda, bir Mağaza veya Kitapçı).

Bir ilişki polimorfik olduğunda, sunucu yanıtı döndürülen nesnenin hem kimliğini hem de türünü belirtmelidir ( RESTSerializer bunu varsayılan olarak yapar); Örneğin:

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

Yeni Gereksinimlerin Karşılanması

Gereksinimleri karşılamak için polimorfik Sağlayıcılara ve Öğelere ihtiyacımız var. ProviderOrder, Sağlayıcıları Öğelerle bağladığından, ilişkilendirmelerini polimorfik ilişkilendirmelerle değiştirebiliriz:

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

Yine de geriye kalan bir sorun var: Sağlayıcının Öğeler ile polimorfik olmayan bir ilişkisi var ancak Öğe soyut bir tür. Bu nedenle, bunu ele almak için iki seçeneğimiz var:

  1. Tüm sağlayıcıların aynı öğe türüyle ilişkilendirilmesini zorunlu kılın (yani, Sağlayıcı ile ilişkilendirme için belirli bir Öğe türü bildirin)
  2. Sağlayıcıdaki items ilişkisini polimorfik olarak bildirin

Bizim durumumuzda, 2 numaralı seçeneğe gitmemiz ve Provider üzerindeki items ilişkilendirmesini polimorfik olarak ilan etmemiz gerekiyor:

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

Bunun herhangi bir kod karmaşasına yol açmadığını unutmayın; tüm çağrışımlar, bu değişiklikten önceki gibi çalışır. Ember Data'nın gücü en iyi şekilde!

Ember Data gerçekten tüm verilerimi modelleyebilir mi?

Elbette istisnalar vardır, ancak ActiveRecord kurallarını, verileri yapılandırmanın ve modellemenin standart ve esnek bir yolu olarak görüyorum, bu yüzden size ActiveRecord kurallarının Ember Data ile nasıl eşleştiğini göstereyim:

has_many :kullanıcılar aracılığıyla: :sahiplikler veya Ara Modelleri Temsil Etmek

Bu, ilişkili Kullanıcıları bulmak için Sahiplik adlı bir pivot modele başvuracaktır. Pivot model temelde bir pivot tabloysa , Ember Data'da bir ara model oluşturmaktan kaçınabilir ve her iki tarafta DS.hasMany ile ilişkiyi temsil edebilirsiniz.

Ancak kullanıcı arabiriminizde bu pivot ilişkiye ihtiyacınız varsa, DS.belongsTo('user', {async: true}) ve DS.belongsTo('provider', {async: true}) içeren bir Sahiplik modeli kurun, ve ardından Sahipliği kullanarak ilişkilendirmeye giden hem Kullanıcılara hem de Sağlayıcılara bir özellik ekleyin; Örneğin:

 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 :mappings, as: locatable

ActiveRecord nesnemizde tipik bir polimorfik ilişkimiz var:

 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

Bu çok (polimorfik) ila çok (normal polimorfik olmayan) bir ilişkidir. Ember Data'da bunu bir polimorfik DS.hasMany('locatable', {polymorphic: true, async: true}) ve statik bir DS.hasMany('location', {async: true}) ile ifade edebiliriz:

 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}) });

Kullanıcı gibi Locatables için sunucu, ilişkili konumların kimliklerini döndürmelidir:

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

Konum için sunucu, nesne dizisinde hem Kimliği hem de Locatable türünü döndürmelidir:

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

Ayrıca, statik bir çoktan çoğa ilişkiyle ilişkileri türe göre temsil edebilirsiniz:

 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}) });

Peki ya gerçek zamanlı veriler?

Ember Data'da push , pushPayload ve update var. Yeni/güncellenmiş kayıtları her zaman Ember Data'nın yerel önbelleğine (mağaza denir) manuel olarak gönderebilirsiniz ve gerisini o halleder.

 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); }); } });

Ben şahsen soketleri yalnızca çok küçük yükleri olan olaylar için kullanırım. Tipik bir olay, {"type": "shop", "id": "14"} yüküyle 'recordUpdated'dir ve ardından ApplicationRoute'da bu kaydın yerel önbellekte (mağazada) olup olmadığını kontrol edeceğim ve eğer öyleyse sadece yeniden getireceğim.

 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); } }); } });

Bu şekilde, kabul edilemez ek yük olmadan tüm istemcilere güncellenmiş olayları kayıt gönderebiliriz.

Ember Data'da gerçek zamanlı verilerle başa çıkmak için temelde iki yaklaşım vardır:

  1. Gerçek zamanlı iletişim kanalınız için bir Adapter yazın ve bunu RESTAdapter yerine kullanın.
  2. Uygun olduklarında kayıtları ana mağazaya gönderin.

İlk seçeneğin dezavantajı, biraz tekerleği yeniden icat etmeye benzer olmasıdır. İkinci seçenek için tüm rotalarda bulunan ana mağazaya route#store olarak erişmemiz gerekiyor.

Sarmak

Bu makalede, bir geliştirici olarak size sağlayabileceği değeri gösteren Ember Data'nın temel yapılarını ve paradigmalarını tanıttık. Ember Data, aksi takdirde etkisi yüksek olabilecek değişikliklere yanıt olarak kod karmaşasını en aza indirerek daha esnek ve akıcı bir geliştirme iş akışı sağlar.

Projeniz için Ember Data'yı kullanarak yaptığınız ön yatırım (zaman ve öğrenme eğrisi), sisteminiz kaçınılmaz olarak geliştikçe ve genişletilmesi, değiştirilmesi ve geliştirilmesi gerektiğinden şüphesiz değerli olacaktır.


EK: Gelişmiş Köz Veri Konuları

Bu ek, aşağıdakiler de dahil olmak üzere bir dizi daha gelişmiş Ember Data konusunu tanıtır:

  • Ember'in Modüler Tasarımı
  • Yan yükleme
  • Bağlantılar
  • Aktif Model Serileştirici ve Adaptör
  • Gömülü Kayıtlar Karışımı
  • İlişki Değiştiriciler (Async, Inverse ve Polymorphic)
  • GET İsteklerinde 'ids' Parametresi

Modüler tasarım

Ember Data, kaputun altında modüler bir tasarıma sahiptir. Anahtar bileşenler şunları içerir:

  1. Adapters , iletişimi yönetmekten sorumludur, şu anda yalnızca HTTP üzerinden REST.
  2. Serializers , JSON'dan veya tersinden modellerin oluşturulmasını yönetir.
  3. Oluşturulan kayıtları önbelleklerde Store .
  4. Container bunların hepsini birbirine yapıştırır.

Bu tasarımın faydaları şunları içerir:

  1. Verilerin seri durumdan çıkarılması ve depolanması, kullanılan iletişim kanalından ve talep edilen kaynaktan bağımsız olarak çalışır.
  2. ActiveModelSerializer veya EmbeddedRecordsMixin gibi çalışma konfigürasyonları kullanıma hazır olarak sağlanır.
  3. Veri kaynakları (örneğin, LocalStorage, CouchDB uygulaması, vb.), adaptörler değiştirilerek içeri ve dışarı değiştirilebilir.
  4. Konfigürasyonla ilgili birçok konvansiyona rağmen, her şeyi konfigüre etmek ve konfigürasyonunuzu/uygulamanızı toplulukla paylaşmak mümkündür.

Yan yükleme

Ember Data, verilerin "yandan yüklenmesini" destekler; yani, ilgili birden çok HTTP isteğini birleştirmeye yardımcı olmak için alınması gereken yardımcı verileri (istenen birincil verilerle birlikte) belirtmek.

Yaygın bir kullanım durumu, ilişkili modelleri yandan yüklemektir. Örneğin, her Mağazada çok sayıda yiyecek vardır, bu nedenle ilgili tüm yiyecekleri /shops yanıtına dahil edebiliriz:

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

Bakkal derneğine erişildiğinde, Ember Data şunları yayınlar:

 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" } ] }

Ancak, bunun yerine /shops uç noktasında ilişkili Marketleri döndürürsek, Ember Data'nın başka bir istek göndermesi gerekmez:

 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" } ] }

Bağlantılar

Ember Data, ilişkilendirme kimlikleri yerine bağlantıları kabul eder. Bağlantı olarak belirtilen bir ilişkiye erişildiğinde, Ember Data ilgili kayıtları almak için o bağlantıya bir GET isteği gönderir.

Örneğin, bir bakkaliye derneği için bir bağlantı döndürebiliriz:

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

Ve Ember Data daha sonra /shops/14/groceries bir istek gönderir.

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

İlişkilendirmeyi yine de verilerde temsil etmeniz gerektiğini unutmayın; bağlantılar yalnızca yeni bir HTTP isteği önerir ve ilişkileri etkilemez.

Aktif Model Serileştirici ve Adaptör

Muhtemelen, ActiveModelSerializer ve ActiveModelAdapter pratikte RESTSerializer ve RESTAdapter daha fazla kullanılır. Özellikle, arka uç Ruby on Rails ve ActiveModel::Serializers gem kullandığında, kutudan çıktığı ActiveModel::Serializers destekledikleri için ActiveModelSerializer ve ActiveModelAdapter kullanmak en iyi seçenektir.

Neyse ki ActiveModelSerializer / ActiveModelAdapter ve RESTSerializer / RESTAdapter arasındaki kullanım farklılıkları oldukça sınırlıdır; yani:

  1. ActiveModelSerializer , snake_case alan adlarını kullanırken, RESTSerializer camelCased alan adlarını gerektirir.
  2. ActiveModelAdapter , snake_case API yöntemlerine istek gönderirken, RESTSerializer , camelCased API yöntemlerine sorun gönderir.
  3. ActiveModelSerializer , ilişkilendirmeyle ilgili alan adlarının _id veya _ids ile bitmesini beklerken RESTSerializer , ilişkilendirmeyle ilgili alan adlarının ilişkilendirme alanıyla aynı olmasını bekler.

Adaptör ve Serileştirici seçimi ne olursa olsun, Ember Data modelleri tamamen aynı olacaktır. Yalnızca JSON temsili ve API uç noktaları farklı olacaktır.

Son ProviderOrder'ımızı örnek olarak alın:

 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}) });

Active Model Serileştirici ve Adapter ile sunucu şunları beklemelidir:

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

… ve şu şekilde yanıt vermelidir:

 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"} ] ] }

Gömülü Kayıtlar Karışımı

DS.EmbeddedRecordsMixin , DS.ActiveModelSerializer için, ilişkilendirmelerin nasıl serileştirileceğini veya seri durumdan çıkarılacağını yapılandırmaya izin veren bir uzantıdır. Henüz tamamlanmamış olsa da (özellikle polimorfik ilişkilerle ilgili olarak), yine de merak uyandırıcıdır.

Seçebilirsiniz:

  1. İlişkilendirmeleri serileştirmemek veya seri durumdan çıkarmamak.
  2. id veya ids ile ilişkilendirmeleri seri hale getirmek veya seri durumdan çıkarmak için.
  3. Gömülü modellerle ilişkileri seri hale getirmek veya seri durumdan çıkarmak için.

Bu, varsayılan olarak DS.hasMany ilişkili kimliklerin seri hale getirilmiş nesnelere eklenmediği bire çok ilişkilerde özellikle yararlıdır. Örnek olarak birçok ürün içeren bir alışveriş sepetini ele alalım. Bu örnekte, Öğeler bilinirken Sepet oluşturuluyor. Ancak, Sepeti kaydederken Ember Data, ilişkili Öğelerin kimliklerini istek yüküne otomatik olarak koymaz.

Ancak DS.EmbeddedRecordsMixin kullanarak, Ember Data'ya Sepetteki öğe kimliklerini aşağıdaki gibi serileştirmesini söylemek mümkündür:

 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}) });

Yukarıdaki örnekte gösterildiği gibi, EmbeddedRecordsMixin, attrs nesnesi aracılığıyla hangi ilişkilerin serileştirileceğini ve/veya seri durumdan çıkarılacağını açıkça belirtmeye izin verir. serialize ve deserialize için geçerli değerler şunlardır: - 'no' : serileştirilmiş/serileştirilmiş verilerde ilişkilendirmeyi içermez - 'id' veya 'ids' : serileştirilmiş/serileştirilmiş verilerde ilişkili kimlikleri içerir - 'records ': gerçek özellikleri içerir (yani, alan değerlerini kaydedin) serileştirilmiş/seri hale getirilmiş verilerde bir dizi olarak

İlişki Değiştiriciler (Async, Inverse ve Polymorphic)

Aşağıdaki ilişkilendirme değiştiricileri desteklenir: polymorphic , inverse ve zaman async

polimorfik değiştirici

Polimorfik bir birliktelikte, ilişkinin bir veya her iki tarafı belirli bir nesneden ziyade bir nesne sınıfını temsil eder.

Hem gönderileri hem de sayfaları etiketleme yeteneğini desteklememiz gereken önceki blog örneğimizi hatırlayın. Bunu desteklemek için aşağıdaki modele ulaştık:

  • Her Gönderi bir Etiketlenebilirdir ve birçok Etiketi vardır
  • Her Sayfa bir Etiketlenebilirdir ve birçok Etiketi vardır
  • Her Etiketin birçok polimorfik Etiketlenebiliri vardır

Bu modeli takiben, Etiketlerin herhangi bir "Etiketlenebilir" türüyle (bir Gönderi veya Sayfa olabilir) ilişkili olduğunu bildirmek için aşağıdaki gibi bir polymorphic değiştirici kullanılabilir:

 // 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}) });

Ters Değiştirici

Genellikle çağrışımlar çift yönlüdür. Örneğin, "Gönderinin birçok Yorumu var" bir ilişkilendirmenin bir yönü olurken, "Yorum bir Gönderiye aittir" bu ilişkinin diğer yönü (yani "ters") olacaktır.

İlişkilendirmede herhangi bir belirsizliğin olmadığı durumlarda, Ember Data ilişkinin ters kısmını çıkarabildiği için yalnızca bir yönün belirtilmesi gerekir.

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; Örneğin:

 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: []) .