Ember Data in the Wild Getting Ember Data to Work With Your API © 2016 David Tang Contents Chapter1-Welcome . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1 WhyIWroteThisBook? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1 MyPromise . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1 IsThisBookForMe? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2 EmberDataSnippetsforAtom . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2 Conventions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2 Errata . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2 GetinTouch . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2 Chapter2-EmberDataOverview. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3 ArchitecturalOverview . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3 ModelAttributesandTransforms . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4 UsingtheStore . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6 Adapters . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7 Relationships . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8 Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9 Chapter3-TalkingtoAPIswithAdapters . . . . . . . . . . . . . . . . . . . . . . . . . . . 10 TheRESTAdapter . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11 TheJSON-APIAdapter . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12 TheActiveModelAdapter . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14 BackgroundReloading . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15 Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15 Chapter4-APIResponseFormatsandSerializers . . . . . . . . . . . . . . . . . . . . . . 16 TheJoboftheSerializer . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16 TheJSONSerializerFormat . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16 TheRESTSerializerFormat . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17 TheJSON-APISerializerFormat . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19 TheBaseSerializer . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24 UsingaSerializer . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24 Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24 Chapter5-CommonAdapterandSerializerCustomizations . . . . . . . . . . . . . . . . 25 CONTENTS ChangingtheRESTfulPath . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25 ChangingtheURLforCertainOperations . . . . . . . . . . . . . . . . . . . . . . . . . . 25 MappingDifferentlyNamedPayloadKeystoModelAttributes . . . . . . . . . . . . . . 26 MappingForeignKeystoRelationships . . . . . . . . . . . . . . . . . . . . . . . . . . . 27 SettingthePrimaryKey . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28 NormalizingResponses . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29 NormalizingResponsesbyStoreCall . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30 NormalizingSingleResourceObjects . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31 Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33 Chapter6-WritingAdaptersandSerializersfromScratch . . . . . . . . . . . . . . . . . . 34 Setup . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34 FindingAllRecords . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35 FindingaSingleRecord . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 38 CreatingRecords . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 41 UpdatingaRecord . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 45 DeletingaRecord . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 46 WritingaLocalStorageAdapter . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 49 Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 57 Chapter7-NestedResourcePathsandRelationshipLinks . . . . . . . . . . . . . . . . . . 58 Chapter8-WorkingwithNestedDataandEmbeddedRecords . . . . . . . . . . . . . . . 62 DeclaringAttributesWithoutTransforms . . . . . . . . . . . . . . . . . . . . . . . . . . 62 EmbeddedRecords . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 63 Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 66 Chapter9-HandlingCustomErrorResponses . . . . . . . . . . . . . . . . . . . . . . . . 67 ValidationErrors . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 67 ControllingtheInvalidStatusCode. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 69 ControllingErrorResponsePayloads . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 70 AdapterErrors . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 72 Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 73 Chapter10-TestingEmberDataCustomizations . . . . . . . . . . . . . . . . . . . . . . . 74 TestingIndirectlywithAcceptanceTests . . . . . . . . . . . . . . . . . . . . . . . . . . . 74 TestingwithmoduleFor() . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 74 TestingNormalizationwiththeStore . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 75 TestingSerializationwiththeStore . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 77 Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 80 Chapter11-CommonCustomizationswithJSON-API . . . . . . . . . . . . . . . . . . . . 81 ChangingAttributeCasing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 81 OverridingaResourceObject’sType . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 85 CONTENTS OverridingHTTPverbs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 88 Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 89 Chapter12-Goodbye . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 90 Chapter 1 - Welcome Why I Wrote This Book? Let me tell you my story. I was drawn to the Ember framework because of all the great things you hearaboutitlikeconventionoverconfiguration,anoutoftheboxtestharness,andstabilitywithout stagnation.IalsoreallylikedthatEmberhadanopinionatedwaytoworkwithAPIsandhandledata in a JavaScript application with its companion library Ember Data. Ember Data features concepts likeadapters,identitymapping,andmodelrelationships,whichaddressedissuesIhadexperienced whenbuildingclient-sideapplications.IfollowedalongthroughvarioustutorialsusingEmberData wheretheAPIwasalreadysetup.ThenIdecidedtouseEmberonarealproject.However,Iwasn’t incontrolofthatAPI.TheAPI’sformatdidn’tmatchtheformatthatEmberDataexpected.Ihada payloadstructurelikethefollowing,whichdoesn’tworkwithEmberDataoutofthebox. 1 { 2 "data": [ 3 { "id": 1, "name": "Fiona" }, 4 { "id": 2, "name": "Steve" } 5 ] 6 } Ididalittleresearchandstartedseeingterminologylikeserializers,transforms,embeddedrecords, and snapshots; the large Ember Data API documentation; and various forum threads suggesting differentsolutionsthatIcouldn’tgettowork.Iquicklyfeltoverwhelmed.Ihitabrickwallandhad a hard time proceeding forward. I definitely considered using the familiar jQuery AJAX instead. This is when I really started digging into Ember Data and learning everything I could about it so thatIcouldleveragethepowerofthisamazinglibrary. SowhyamIwritingthisbook?Plainandsimple,IwanttohelpanyonefrustratedwithEmberData learntoadaptittoyourAPIquicklywithouthittingthatbrickwallthatIhit.Onceyouunderstand how Ember Data works, adapting it to your API isn’t that difficult. This book is something I wish I had available when I first got started with Ember. My goal with this book is to take you from beginnertoexpertwhenworkingwithEmberDataandlearnhowtoadaptittofitanyAPI. My Promise IplantokeepthisbookuptodateasEmberDatachanges.CurrentlythisbooksupportsEmberData 2.x.ThesourcecodewillbeavailableonGitHubat:https://github.com/skaterdav85/ember-data-in- 1 Chapter1-Welcome 2 the-wild¹. Is This Book For Me? This book is for anyone interested in learning more about Ember Data. Maybe you’ve been using $.ajax() and would like to start using Ember Data. Maybe you’ve used Ember Data with an API thatfollowsthedefaultconventions,andyouarelookingtolearnmoreaboutwhat’sgoingonunder the hood. Maybe you want to write your own adapter to work with a particular persistence layer. ThisbookistargetedtowardsanyonewithalittleEmberexperienceandaninterestinlearningabout Ember Data. If you haven’t used Ember Data before, Chapter 2 - Ember Data Overview provides a short introduction, but I recommend reading the first few sections in the Ember Guides on Ember Datatogetacquainted. Ember Data Snippets for Atom If you happen to be using the Atom editor, I wrote a package called ember-data-snippets that you might find useful. It contains snippets for all of the public and inherited adapter and serializer methods.Toinstallthispackage,searchforember-data-snippetsinAtom’spackagemanager. Conventions WhenIuse“JSON-API”,IamreferringtothetheJSON-APIspecification². Errata I have done the best I can to ensure this book doesn’t have any typos and errors. If you do find mistakeshowever,pleasefileanissueonGithub³,andIwillfixitassoonasIcan. Get in Touch Do you have questions on any of the material in this book? Do you need help getting your API to work with Ember Data? Feel free to reach out to me on Twitter at @skaterdav85 or send me an emailatdavid@thejsguycom. ¹https://github.com/skaterdav85/ember-data-in-the-wild ²http://jsonapi.org/ ³https://github.com/skaterdav85/ember-data-in-the-wild Chapter 2 - Ember Data Overview Architectural Overview InordertostartworkingeffectivelywithEmberData,it’susefultohaveanarchitecturaloverview ofthecorepiecesandhowtheyworktogether.Thischapterwillprovideasolidfoundationofhow allthepiecesinEmberDatafittogethersothatyoucanstartcustomizingittofityourAPI. EmberDataArchitecture Inthediagramabove,ontheleftistheapplication,whichinteractswiththestore.Bydefault,routes and controllers have access to the store through the store property. The store can also be injected intootherpartsofyourapplicationlikecomponentsusingEmber.inject.service()orthroughan initializer. Thestore,aninstanceof DS.Store,isanEmberservicethatactsasadataaccesslayerandcachefor the models in your application. It is responsible for creating models on the client and saving them back to the persistence layer. The store can also request data froma persistence layer and turn that dataintorichclient-sidemodels.Thesemodelsarethencachedforsubsequentretrieval. The store also implements the identity map pattern to prevent duplicate retrieval of objects from a persistence layer. For example, let’s say you make two requests. The first request is for a list of contactsandthislistcontainsacontactwithanidof1.Thesecondrequesthappenssomewhereelse in the application and is for the contact with an id of 1. This means there are two contact objects with an id of 1 in memory. Keeping these duplicate contact objects in sync can be tough, is more work on your part, and often isn’t very reusable. Identity mapping is a pattern that is used by the storetopreserveobjectidentityandreturnthesameobjectinstances,regardlessofhowmanytimes youaskforit. The store delegates the specifics of how to work with a persistence layer to an adapter. This is the adapterdesignpatterninuse.Thinkoftheadapterpatternlikehandlingelectricaloutletswhenyou travel abroad. If you have a three-pronged electrical plug, it won’t fit in a two-prong wall outlet. Instead,youneedtouseatraveladaptertoconverttheexistingthree-prongedplugconfigurationto conformtothesocketofthecountryyouarevisiting.Sameideaherebutinsteadofhavinganadapter to fit the electrical outlet, you have an adapter to map calls on the store to how you communicate 3 Chapter2-EmberDataOverview 4 withyourbackend,whetheritiswithAJAX,websockets,localStorage,orIndexedDB.Byisolating the specifics of where the data comes from from your application in an adapter, if the way you communicatewithyourbackendchangesinthefuture,onlytheadapterwillneedtochangeinstead ofacrosstheapplication. Betweentheadapterandthepersistencelayeristheserializer.Theserializerhastwojobs.First,itis used to format data sent to the server, also known as serialization. Second, the serializer is used to formatdatareceivedfromtheserver,knownasnormalization.Aswe’llseelaterinthisbook,Ember shipswiththreedifferentserializers,whichcanbeextendedtofitanyAPI. NowthatwehaveagoodideaofthecorepiecesbehindEmberData,let’sgothroughanexample. Model Attributes and Transforms To start working with Ember Data, you first need to think about the underlying data in your applicationandrepresentthemasmodels.Forexample,acatapplicationwouldlikelyhavemodels cat,home,andowner.WecanuseEmberCLItogenerateamodelclass: 1 ember g model cat Thiswillgeneratethefollowingmodelclass: 1 // app/models/cat.js 2 import DS from 'ember-data'; 3 4 export default DS.Model.extend({ 5 }); Next,attributesofthemodelcanbespecifiedusingtransforms.Transformsallowyoutotransform properties from the server before they are set as attributes on a model or sent back to the server. Below is an example of the cat model using the four built-in transforms: string, number, boolean, anddate. 1 // app/models/cat.js 2 export default DS.Model.extend({ 3 name: DS.attr('string'), 4 age: DS.attr('number'), 5 adopted: DS.attr('boolean'), 6 birthday: DS.attr('date') 7 }); Thebuilt-intransformsinclude: Chapter2-EmberDataOverview 5 TransformName Usage string DS.attr(‘string’) number DS.attr(‘number’) boolean DS.attr(‘boolean’) date DS.attr(‘date’) When a model is created, the attributes are coerced to the types specified in the corresponding DS.attr() call. For example, let’s say a cat resource came in from the server looking like the following: 1 { 2 "id": 1, 3 "name": "Frisky", 4 "age": "10", 5 "adopted": "true", 6 "birthday": "2005-11-05T13:15:30Z", 7 "color": "white" 8 } Thenameattributewouldbesetonthemodelasastring.Theageattributewouldbecoercedtothe number10onthemodel.Theadoptedattributewouldbecoercedtoabooleanvalueof trueonthe model.ThebirthdayattributewouldbecoercedtoaDateobject.Lastly,becausethecolorattribute wasnotspecifiedonthemodelclass,itwouldn’tgetsetonthecatmodel. Behindthescenes,eachoftheseDS.attr()callsmaptoaspecifictransformclassthatextendsfrom DS.Transform.Ifyoudon’tpassanythingtoDS.attr(),thevaluewillbepassedthroughasis.This isusefulaswe’llseeinChapter8-WorkingwithNestedDataandEmbeddedRecords. The built-in transforms are self-explanatory for the most part. The string transform will coerce the value to a string using the native String constructor function. The number transform will coerce thevaluetoanumberusingthenativeNumberconstructorfunction.Iftheattributeisnotanumber, nullis returned. The boolean transform not only transforms boolean values, but the strings “true”, “t”, or “1” in any casing and the number 1 will all coerce to true, and anything else will coerce to false. The DateTransform can deserialize a few different values. If the date is a string, it should be in a format recognized by Date.parse()⁴. According to MDN, that date format should be either RFC2822 or ISO 8601. The ISO 8601 format looks like this: YYYY-MM-DDTHH:mm:ss.sssZ. More information on that can be found in the MDN documentation for Date.prototype.toISOString()⁵. Because Date.parse() in some browsers does not support simplified ISO 8601 dates, like Safari 5-, IE 8-, Firefox 3.6-, Ember uses a shim⁶. Alternatively, a number can be passed that represents ⁴https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/parse ⁵https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toISOString ⁶https://github.com/csnover/js-iso8601
Description: