Ember 데이터: ember-data 라이브러리에 대한 포괄적인 자습서

게시 됨: 2022-03-11

Ember Data(일명 ember-data 또는 ember.data)는 Ember.js 애플리케이션에서 모델 데이터를 강력하게 관리하기 위한 라이브러리입니다. Ember Data의 개발자는 기본 지속성 메커니즘에 영향을 받지 않도록 설계되었으므로 스트리밍 WebSocket 또는 로컬 IndexedDB 저장소와 마찬가지로 HTTP를 통한 JSON API에서도 잘 작동한다고 말합니다. ActiveRecord와 같은 서버측 ORM(객체 관계형 매핑)에서 찾을 수 있는 많은 기능을 제공하지만 브라우저에서 JavaScript의 고유한 환경을 위해 특별히 설계되었습니다.

Ember Data는 grok하는 데 시간이 걸릴 수 있지만 일단 그렇게 하면 투자 가치가 충분하다는 것을 알게 될 것입니다. 궁극적으로 시스템의 개발, 향상 및 유지 관리가 훨씬 쉬워집니다.

API가 Ember Data 모델, 어댑터 및 직렬 변환기를 사용하여 표시될 때 각 연결은 단순히 필드 이름이 됩니다. 이것은 각 연결의 내부 세부 정보를 캡슐화하여 연결 자체에 대한 변경 사항에서 나머지 코드를 격리합니다. 예를 들어 특정 연관이 다형성이거나 많은 연관 맵의 결과인 경우 나머지 코드는 신경 쓰지 않습니다.

또한 코드 기반은 모델의 JSON, XML 또는 YAML 표현이 아니라 모델의 필드와 함수를 기대하기 때문에 중요하더라도 백엔드 변경으로부터 코드 기반이 크게 격리됩니다.

이 튜토리얼에서는 Ember Data의 가장 두드러진 기능을 소개하고 실제 예제에 중점을 두어 Ember Data가 코드 변동을 최소화하는 데 어떻게 도움이 되는지 보여줍니다.

더 많은 고급 Ember Data 주제와 예를 설명하는 부록도 제공됩니다.

참고: 이 기사에서는 Ember.js에 대한 기본적인 지식이 있다고 가정합니다. Ember.js에 익숙하지 않다면 인기 있는 Ember.js 튜토리얼에서 소개를 확인하세요. 또한 러시아어, 포르투갈어 및 스페인어로 제공되는 전체 스택 JavaScript 가이드가 있습니다.

Ember 데이터 가치 제안

Ember Data 라이브러리가 고객 요구 사항을 수용하는 데 어떻게 도움이 되는지 보여주는 예.

간단한 예를 고려하여 시작하겠습니다.

기본 블로그 시스템을 위한 작업 코드 기반이 있다고 가정해 보겠습니다. 시스템에는 서로 다대다 관계가 있는 게시물과 태그가 포함되어 있습니다.

Pages를 지원해야 한다는 요구 사항이 있을 때까지는 괜찮습니다. 요구 사항은 또한 WordPress에서 페이지에 태그를 지정할 수 있으므로 그렇게 할 수 있어야 한다고 명시되어 있습니다.

이제 태그는 더 이상 게시물에만 적용되지 않고 페이지에도 적용될 수 있습니다. 결과적으로 태그와 게시물 간의 단순한 연결은 더 이상 적절하지 않습니다. 대신 다음과 같은 다대다 단방향 다형성 관계가 필요합니다.

  • 각 게시물은 태그 가능하며 많은 태그가 있습니다.
  • 각 페이지는 태그 가능하며 많은 태그가 있습니다.
  • 각 태그에는 많은 다형성 태그 가능이 있습니다.

이 새롭고 더 복잡한 연결 집합으로 전환하면 코드 전체에 상당한 영향을 미치므로 많은 변동이 발생할 수 있습니다. JSON에 대한 다형성 연결을 직렬화하는 방법을 모르기 때문에 GET /posts/:id/tagsGET /pages/:id/tags tags와 같은 API 끝점을 더 만들 것입니다. 그런 다음 기존 JSON 파서 기능을 모두 버리고 추가된 새 리소스에 대해 새 기능을 작성합니다. 어. 지루하고 고통스럽습니다.

이제 Ember Data를 사용하여 이에 접근하는 방법을 고려해 보겠습니다.

Ember Data에서 이 수정된 연결 집합을 수용하려면 다음에서 이동하기만 하면 됩니다.

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

에게:

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

나머지 코드에서 발생하는 변동은 최소화되고 대부분의 템플릿을 재사용할 수 있습니다. 특히 Post의 tags 연결 이름은 변경되지 않은 상태로 유지됩니다. 또한 나머지 코드 기반은 tags 연결의 존재에만 의존하고 세부 사항은 알지 못합니다.

Ember 데이터 입문서

실제 사례를 살펴보기 전에 Ember Data의 몇 가지 기본 사항을 검토해 보겠습니다.

경로 및 모델

Ember.js에서 라우터는 템플릿 표시, 데이터 로드 및 기타 애플리케이션 상태 설정을 담당합니다. 라우터는 현재 URL을 사용자가 정의한 경로와 일치시키므로 Route는 템플릿이 표시할 모델을 지정할 책임이 있습니다(Ember는 이 모델이 Ember.Object 의 하위 클래스일 것으로 예상합니다).

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

Ember Data는 DS.Model 의 하위 클래스인 Ember.Object 을 제공하며 편의를 위해 단일 레코드 또는 다중 레코드를 저장하거나 업데이트하는 기능을 추가합니다.

새 모델을 만들기 위해 DS.Model 의 하위 클래스를 만듭니다(예: App.User = DS.Model.extend({}) ).

Ember Data는 서버에서 잘 정의되고 직관적인 JSON 구조를 기대하고 새로 생성된 레코드를 동일한 구조화된 JSON으로 직렬화합니다.

Ember Data는 또한 모델 작업을 위한 DS.RecordArray 와 같은 어레이 클래스 제품군을 제공합니다. 여기에는 일대다 또는 다대다 관계 처리, 비동기 데이터 검색 처리 등과 같은 책임이 있습니다.

모델 속성

기본 모델 속성은 DS.attr 을 사용하여 정의됩니다. 예:

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

DS.attr 에 의해 생성된 필드만 레코드 생성 또는 업데이트를 위해 서버에 전달되는 페이로드에 포함됩니다.

DS.attrstring , number , booleandate 의 네 가지 데이터 유형을 허용합니다.

RESTSerializer

기본적으로:

  • Ember Data는 API 응답에서 객체 생성(직렬화 해제) 및 API 요청에 대한 JSON 생성(직렬화)을 위해 RESTSerializer 를 사용합니다.
  • RESTSerializerDS.belongsTo 에 의해 생성된 필드에 서버의 JSON 응답에 포함된 user 라는 필드가 있을 것으로 예상합니다. 해당 필드에는 참조된 레코드의 ID가 포함됩니다.
  • RESTSerializer 는 연결된 주문의 ID를 사용하여 API에 전달된 페이로드에 user 필드를 추가합니다.

예를 들어 응답은 다음과 같을 수 있습니다.

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

주문을 저장하기 위해 RESTSerializer 에서 생성한 HTTP 요청은 다음과 같습니다.

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

일대일 관계

예를 들어 각 사용자에게 고유한 프로필이 있다고 가정해 보겠습니다. 사용자와 프로필 모두에서 DS.belongsTo 를 사용하여 Ember Data에서 이 관계를 나타낼 수 있습니다.

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

그런 다음 user.get('profile') 과의 연결을 가져오거나 user.set('profile', aProfile) 로 설정할 수 있습니다.

RESTSerializer 는 연결된 모델의 ID가 각 모델에 제공될 것으로 예상합니다. 예:

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

마찬가지로 요청 페이로드에 연결된 모델의 ID를 포함합니다.

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

일대다 및 다대일 관계

게시물에 많은 댓글이 있는 모델이 있다고 가정해 보겠습니다. Ember Data에서 DS.hasMany('comment', {async: true})DS.belongsTo('post', {async: true}) 와 이 관계를 나타낼 수 있습니다.

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

그런 다음 post.get('comments', {async: true}) 으로 연결된 항목을 가져오고 post.get('comments').then(function(comments){ return comments.pushObject(aComment);}) 로 새 연결을 추가할 수 있습니다. post.get('comments').then(function(comments){ return comments.pushObject(aComment);}) .

그러면 서버는 게시물의 해당 댓글에 대한 ID 배열로 응답합니다.

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

... 그리고 각 댓글에 대한 ID:

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

RESTSerializer 는 연결된 게시물의 ID를 댓글에 추가합니다.

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

기본적으로 RESTSerializer는 직렬화하는 개체에 DS.hasMany 관련 ID를 추가 하지 않습니다 . 이러한 연결은 "다" 쪽(즉, DS.belongsTo 연결이 있는 연결)에 지정되기 때문입니다. 따라서 이 예에서는 Post에 많은 주석이 있지만 해당 ID는 Post 개체에 추가 되지 않습니다 .

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

DS.hasMany ID도 직렬화하도록 "강제"하려면 Embedded Records Mixin을 사용할 수 있습니다.

다대다 관계

우리 모델에서 작성자는 여러 게시물을 가질 수 있고 한 게시물에는 여러 작성자가 있을 수 있다고 가정합니다.

Ember Data에서 이 관계를 나타내기 위해 DS.hasMany('author', {async: true}) 를 사용하고 Author에서 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}) });

그런 다음 author.get('posts') 를 사용하여 연결된 항목을 가져오고 author.get('posts').then(function(posts){ return posts.pushObject(aPost);}) 으로 새 연결을 추가할 수 있습니다.

그러면 서버는 해당 개체에 대한 ID 배열로 응답합니다. 예:

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

이것은 다대다 관계이므로 RESTSerializer는 연결된 개체의 ID 배열을 추가합니다. 예:

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

실제 사례: 기존 주문 시스템 개선

기존 주문 시스템에서 각 사용자에는 많은 주문이 있고 각 주문에는 많은 항목이 있습니다. 당사 시스템에는 제품을 주문할 수 있는 여러 공급자(예: 공급업체)가 있지만 각 주문에는 단일 공급자의 항목만 포함될 수 있습니다.

새로운 요구 사항 #1: 여러 공급자의 항목을 포함하려면 단일 주문을 활성화하십시오.

기존 시스템에서는 공급자와 주문 사이에 일대다 관계가 있습니다. 그러나 여러 공급자의 항목을 포함하도록 주문을 확장하면 이 단순한 관계가 더 이상 적절하지 않습니다.

특히, 공급자가 전체 주문과 연관된 경우 향상된 시스템에서 해당 주문은 다른 공급자로부터 주문한 항목도 포함할 수 있습니다. 따라서 각 주문의 어느 부분이 각 공급자와 관련이 있는지 나타내는 방법이 필요합니다. 또한 공급자가 주문에 액세스할 때 고객이 다른 공급자에게 주문했을 수 있는 다른 항목이 아니라 자신이 주문한 항목만 볼 수 있어야 합니다.

한 가지 접근 방식은 두 개의 새로운 다대다 연결을 도입하는 것입니다. 하나는 Order와 Item 사이에, 다른 하나는 Order와 Provider 사이에 있습니다.

그러나 일을 더 단순하게 유지하기 위해 "ProviderOrder"라고 하는 데이터 모델에 새로운 구성을 도입했습니다.

제도 관계

향상된 데이터 모델은 다음 연결을 수용해야 합니다.

  • 사용자와 주문 간의 일대다 관계(각 사용자는 0~n개의 주문과 연관될 수 있음) 사용자와 공급자 간의 일대다 관계(각 사용자는 0~n개의 공급자와 연관될 수 있음)

     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}) });
  • Orders와 ProviderOrders 간의 일대다 관계(각 주문은 1~n개의 ProviderOrders로 구성됨):

     App.Order = DS.Model.extend({ createdAt: DS.attr('date'), user: DS.belongsTo('user', {async: true}), providerOrders: DS.hasMany('providerOrders', {async: true}) });
  • Providers와 ProviderOrders 간의 일대다 관계(각 Provider는 0 ~ 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}) });
  • ProviderOrders와 항목 간의 일대다 관계(각 ProviderOrder는 1~n개의 항목으로 구성됨):

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

그리고 Route 정의를 잊지 마십시오.

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

이제 각 ProviderOrder에는 우리의 주요 목표인 하나의 Provider가 있습니다. 항목이 Order에서 ProviderOrder로 이동되고 하나의 ProviderOrder에 있는 모든 항목이 단일 Provider에 속한다고 가정합니다.

코드 변동 최소화

불행히도 여기에 몇 가지 주요 변경 사항이 있습니다. 이제 Ember Data가 코드 기반에서 발생하는 코드 변동을 최소화하는 데 어떻게 도움이 되는지 살펴보겠습니다.

이전에는 items.pushObject(item) 으로 항목을 푸시했습니다. 이제 먼저 적절한 ProviderOrder를 찾아 항목을 푸시해야 합니다.

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

이것은 변동이 많고 컨트롤러보다 Order의 작업이 더 많기 때문에 이 코드를 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); }); }); } });

이제 order.pushItem(item) 과 같이 주문에 직접 항목을 추가할 수 있습니다.

각 주문의 항목을 나열하는 경우:

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

다형성 관계

이제 상황을 더욱 복잡하게 만드는 추가 개선 요청을 시스템에 도입하겠습니다.

새로운 요구 사항 #2: 여러 유형의 공급자를 지원합니다.

간단한 예에서 두 가지 유형의 제공자("상점" 및 "서점")가 정의되어 있다고 가정해 보겠습니다.

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

여기에서 다형성 관계에 대한 Ember Data의 지원이 유용할 것입니다. Ember Data는 일대일, 일대다 및 다대다 다형성 관계를 지원합니다. 이것은 연관 사양에 polymorphic: true 속성을 추가함으로써 간단히 수행됩니다. 예를 들어:

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

위의 polymorphic 플래그는 ProviderOrder(이 경우 Shop 또는 Bookstore)와 연결할 수 있는 다양한 유형의 제공자가 있음을 나타냅니다.

관계가 다형성인 경우 서버 응답은 반환된 개체의 ID 유형을 모두 나타내야 합니다( RESTSerializer 가 기본적으로 이 작업을 수행함). 예:

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

새로운 요구 사항 충족

요구 사항을 충족하려면 다형성 공급자와 항목이 필요합니다. ProviderOrder는 공급자를 항목과 연결하므로 해당 연결을 다형성 연결로 변경할 수 있습니다.

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

하지만 남아 있는 문제가 있습니다. 공급자는 항목에 대한 비다형성 연결을 가지고 있지만 항목은 추상 유형입니다. 따라서 이 문제를 해결할 수 있는 두 가지 옵션이 있습니다.

  1. 모든 공급자가 동일한 항목 유형과 연결되어야 함(즉, 공급자와의 연결을 위해 특정 유형의 항목 선언)
  2. Provider의 items 연관을 다형성으로 선언

우리의 경우 옵션 #2를 사용하여 Provider의 items 연관을 다형성으로 선언해야 합니다.

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

이로 인해 코드 변동이 발생 하지 않습니다. 모든 연결은 이 변경 이전과 동일하게 작동합니다. Ember Data의 힘은 최고!

Ember Data가 실제로 내 모든 데이터를 모델링할 수 있습니까?

물론 예외가 있지만 ActiveRecord 규칙을 데이터를 구조화하고 모델링하는 표준적이고 유연한 방법으로 간주하므로 ActiveRecord 규칙이 Ember Data에 매핑되는 방법을 보여드리겠습니다.

has_many :users through: :소유권 또는 중간 모델 대표

그러면 소유권이라는 피벗 모델 을 참조하여 연결된 사용자를 찾습니다. 피벗 모델 이 기본적으로 피벗 테이블 인 경우 Ember Data에서 중간 모델을 생성하지 않고 양쪽에서 DS.hasMany 와의 관계를 나타낼 수 있습니다.

그러나 프론트 엔드 내부에 피벗 관계가 필요한 경우 DS.belongsTo('user', {async: true})DS.belongsTo('provider', {async: true}) 를 포함하는 소유권 모델을 설정합니다. 그런 다음 소유권을 사용하여 연결에 매핑되는 속성을 사용자와 공급자 모두에 추가합니다. 예:

 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 : 매핑, as: 위치 지정 가능

ActiveRecord 개체에는 일반적인 다형성 관계가 있습니다.

 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

이것은 다(다형성) 대 다(일반 비다형성) 관계입니다. Ember Data에서는 이것을 다형성 DS.hasMany('locatable', {polymorphic: true, async: true}) 및 정적 DS.hasMany('location', {async: true}) 수 있습니다.

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

사용자와 같은 위치 지정 가능 항목의 경우 서버는 연결된 위치에 대한 ID를 반환해야 합니다.

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

위치의 경우 서버는 개체 배열에서 위치 지정 가능 유형과 ID를 모두 반환해야 합니다.

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

또한 정적 다대다 관계를 사용하여 유형별로 관계를 나타낼 수 있습니다.

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

실시간 데이터는 어떻습니까?

Ember Data에는 push , pushPayloadupdate 가 있습니다. 새/업데이트된 레코드를 Ember Data의 로컬 캐시(저장소라고 함)에 항상 수동으로 푸시할 수 있으며 나머지는 모두 처리합니다.

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

저는 개인적으로 페이로드가 매우 작은 이벤트에만 소켓을 사용합니다. 일반적인 이벤트는 페이로드가 {"type": "shop", "id": "14"} 인 'recordUpdated'입니다. 그런 다음 ApplicationRoute에서 해당 레코드가 로컬 캐시(저장소)에 있는지 확인합니다. 그냥 다시 가져옵니다.

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

이렇게 하면 허용할 수 없는 오버헤드 없이 레코드 업데이트된 이벤트를 모든 클라이언트에 보낼 수 있습니다.

Ember Data에는 실시간 데이터를 처리하기 위한 두 가지 접근 방식이 있습니다.

  1. 실시간 통신 채널용 어댑터를 작성하고 RESTAdapter 대신 사용하십시오.
  2. 레코드를 사용할 수 있을 때마다 주 저장소에 푸시합니다.

첫 번째 옵션의 단점은 바퀴를 재발명하는 것과 다소 비슷하다는 것입니다. 두 번째 옵션의 경우 route#store 로 모든 경로에서 사용할 수 있는 기본 저장소에 액세스해야 합니다.

마무리

이 기사에서는 Ember Data의 주요 구성 및 패러다임을 소개하여 개발자에게 제공할 수 있는 가치를 보여줍니다. Ember Data는 보다 유연하고 간소화된 개발 워크플로를 제공하여 큰 영향을 미칠 변경 사항에 대한 응답으로 코드 변동을 최소화합니다.

프로젝트에 Ember Data를 사용하는 데 드는 선행 투자(시간 및 학습 곡선)는 시스템이 필연적으로 발전하고 확장, 수정 및 향상되어야 하므로 의심할 여지 없이 가치가 있음을 증명할 것입니다.


부록: 고급 Ember 데이터 주제

이 부록에서는 다음을 포함한 여러 고급 Ember Data 주제를 소개합니다.

  • Ember의 모듈식 디자인
  • 사이드로딩
  • 연결
  • 활성 모델 직렬 변환기 및 어댑터
  • 임베디드 레코드 믹스인
  • 연관 수정자(비동기, 역 및 다형성)
  • GET 요청의 'ids' 매개변수

모듈식 설계

Ember Data는 후드 아래에 모듈식 설계가 있습니다. 주요 구성 요소는 다음과 같습니다.

  1. Adapters 는 현재 HTTP를 통한 REST만 통신 처리를 담당합니다.
  2. Serializers 는 JSON 또는 그 반대의 모델 생성을 관리합니다.
  3. 생성된 레코드를 캐시에 Store 합니다.
  4. Container 는 이 모든 것을 함께 접착합니다.

이 디자인의 이점은 다음과 같습니다.

  1. 데이터의 역직렬화 및 저장은 사용된 통신 채널 및 요청된 리소스와 독립적으로 작동합니다.
  2. ActiveModelSerializer 또는 EmbeddedRecordsMixin과 같은 작업 구성은 즉시 제공됩니다.
  3. 데이터 소스(예: LocalStorage, CouchDB 구현 등)는 어댑터를 변경하여 교체할 수 있습니다.
  4. 구성에 대한 많은 관례에도 불구하고 모든 것을 구성하고 구성/구현을 커뮤니티와 공유하는 것이 가능합니다.

사이드로딩

Ember Data는 데이터의 "사이드로딩"을 지원합니다. 즉, 여러 관련 HTTP 요청을 통합하는 데 도움이 되도록 검색해야 하는 보조 데이터(요청된 기본 데이터와 함께)를 나타냅니다.

일반적인 사용 사례는 관련 모델을 사이드로딩하는 것입니다. 예를 들어, 각 Shop에는 많은 식료품이 있으므로 /shops 응답에 모든 관련 식료품을 포함할 수 있습니다.

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

식료품 협회에 액세스하면 Ember Data에서 다음을 발행합니다.

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

그러나 대신 /shops 엔드포인트에서 연결된 식료품을 반환하는 경우 Ember Data는 다른 요청을 발행할 필요가 없습니다.

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

연결

Ember Data는 연결 ID 대신 링크를 허용합니다. 링크로 지정된 연관이 액세스되면 Ember Data는 연관된 레코드를 가져오기 위해 해당 링크에 GET 요청을 발행합니다.

예를 들어 식료품 협회에 대한 링크를 반환할 수 있습니다.

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

그런 다음 Ember Data는 /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" } ] }

데이터에서 연관성을 나타내야 한다는 점을 명심하십시오. 링크는 새로운 HTTP 요청을 제안할 뿐 연결에 영향을 미치지 않습니다.

활성 모델 직렬 변환기 및 어댑터

틀림없이 ActiveModelSerializerActiveModelAdapterRESTSerializerRESTAdapter 보다 실제로 더 많이 사용됩니다. 특히 백엔드가 Ruby on Rails 및 ActiveModel::Serializers gem을 사용하는 경우 가장 좋은 방법은 ActiveModel ActiveModel::Serializers Serializers를 기본적으로 지원하기 때문에 ActiveModelSerializerActiveModelAdapter 를 사용하는 것입니다.

다행히도 ActiveModelSerializer / ActiveModelAdapterRESTSerializer / RESTAdapter 간의 사용 차이는 상당히 제한적입니다. 즉:

  1. ActiveModelSerializer 는 snake_case 필드 이름을 사용하고 RESTSerializer 는 camelCased 필드 이름을 요구합니다.
  2. ActiveModelAdapter 는 snake_case API 메소드에 대한 요청을 발행하는 반면 RESTSerializer 는 camelCased API 메소드에 대한 요청을 발행합니다.
  3. ActiveModelSerializer 는 연결 관련 필드 이름이 _id 또는 _ids 로 끝날 것으로 예상하는 반면 RESTSerializer 는 연결 관련 필드 이름이 연결 필드와 동일할 것으로 예상합니다.

어댑터 및 직렬 변환기 선택에 관계없이 Ember Data 모델은 정확히 동일합니다. JSON 표현과 API 엔드포인트만 다릅니다.

최종 ProviderOrder를 예로 들어보겠습니다.

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

활성 모델 직렬 변환기 및 어댑터를 사용하는 경우 서버는 다음을 기대해야 합니다.

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

... 그리고 다음과 같이 응답해야 합니다.

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

임베디드 레코드 믹스인

DS.EmbeddedRecordsMixin 은 연결이 직렬화 또는 역직렬화되는 방식을 구성할 수 있는 DS.ActiveModelSerializer 의 확장입니다. 아직 완전하지는 않지만(특히 다형성 연관과 관련하여) 그럼에도 불구하고 흥미롭습니다.

다음을 선택할 수 있습니다.

  1. 연결을 직렬화하거나 역직렬화하지 않습니다.
  2. id 또는 id와의 연관을 직렬화 또는 역직렬화합니다.
  3. 임베디드 모델과의 연관을 직렬화 또는 역직렬화합니다.

이는 기본적으로 직렬화된 개체에 DS.hasMany 관련 ID가 추가되지 않는 일대다 관계에서 특히 유용합니다. 많은 품목이 있는 장바구니를 예로 들어 보겠습니다. 이 예에서는 항목이 알려진 동안 카트가 생성됩니다. 그러나 장바구니를 저장할 때 Ember Data는 요청 페이로드에 연결된 항목의 ID를 자동으로 넣지 않습니다.

그러나 DS.EmbeddedRecordsMixin 을 사용하면 Ember Data에 카트의 항목 ID를 다음과 같이 직렬화하도록 지시할 수 있습니다.

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

위의 예에서 볼 수 있듯이 EmbeddedRecordsMixin을 사용하면 attrs 개체를 통해 직렬화 및/또는 역직렬화할 연결을 명시적으로 지정할 수 있습니다. serializedeserialize 에 유효한 값은 다음과 같습니다. - 'no' : 직렬화/역직렬화 데이터에 연결을 포함하지 않습니다. - 'id' 또는 'ids' : 직렬화/역직렬화 데이터에 연결된 ID를 포함합니다. - 'records ': 실제 속성을 포함합니다. (즉, 레코드 필드 값) 직렬화/역직렬화 데이터의 배열

연관 수정자(비동기, 역 및 다형성)

다음 연관 수정자가 지원됩니다: polymorphic , inverseasync

다형성 수정자

다형성 연결에서 연결의 한쪽 또는 양쪽은 특정 개체가 아니라 개체 클래스를 나타냅니다.

게시물과 페이지 모두에 태그를 지정하는 기능을 지원해야 했던 블로그의 이전 예를 생각해 보십시오. 이를 지원하기 위해 다음 모델에 도달했습니다.

  • 각 게시물은 태그 가능하며 많은 태그가 있습니다.
  • 각 페이지는 태그 가능하며 많은 태그가 있습니다.
  • 각 태그에는 많은 다형성 태그 가능이 있습니다.

해당 모델에 따라 polymorphic 수정자를 사용하여 다음과 같이 태그가 모든 유형의 "태그 가능"(게시물 또는 페이지일 수 있음)과 관련되어 있음을 선언할 수 있습니다.

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

역 수정자

일반적으로 연결은 양방향입니다. 예를 들어, "게시물에 많은 댓글이 있음"은 연결의 한 방향이 되는 반면 "댓글은 게시물에 속함"은 해당 연결의 다른 방향(즉, "반대")이 됩니다.

연관에 모호함이 없는 경우 Ember Data는 연관의 역 부분을 추론할 수 있으므로 한 방향만 지정하면 됩니다.

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; 예:

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