2
0

ember-data.js 229 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651265226532654265526562657265826592660266126622663266426652666266726682669267026712672267326742675267626772678267926802681268226832684268526862687268826892690269126922693269426952696269726982699270027012702270327042705270627072708270927102711271227132714271527162717271827192720272127222723272427252726272727282729273027312732273327342735273627372738273927402741274227432744274527462747274827492750275127522753275427552756275727582759276027612762276327642765276627672768276927702771277227732774277527762777277827792780278127822783278427852786278727882789279027912792279327942795279627972798279928002801280228032804280528062807280828092810281128122813281428152816281728182819282028212822282328242825282628272828282928302831283228332834283528362837283828392840284128422843284428452846284728482849285028512852285328542855285628572858285928602861286228632864286528662867286828692870287128722873287428752876287728782879288028812882288328842885288628872888288928902891289228932894289528962897289828992900290129022903290429052906290729082909291029112912291329142915291629172918291929202921292229232924292529262927292829292930293129322933293429352936293729382939294029412942294329442945294629472948294929502951295229532954295529562957295829592960296129622963296429652966296729682969297029712972297329742975297629772978297929802981298229832984298529862987298829892990299129922993299429952996299729982999300030013002300330043005300630073008300930103011301230133014301530163017301830193020302130223023302430253026302730283029303030313032303330343035303630373038303930403041304230433044304530463047304830493050305130523053305430553056305730583059306030613062306330643065306630673068306930703071307230733074307530763077307830793080308130823083308430853086308730883089309030913092309330943095309630973098309931003101310231033104310531063107310831093110311131123113311431153116311731183119312031213122312331243125312631273128312931303131313231333134313531363137313831393140314131423143314431453146314731483149315031513152315331543155315631573158315931603161316231633164316531663167316831693170317131723173317431753176317731783179318031813182318331843185318631873188318931903191319231933194319531963197319831993200320132023203320432053206320732083209321032113212321332143215321632173218321932203221322232233224322532263227322832293230323132323233323432353236323732383239324032413242324332443245324632473248324932503251325232533254325532563257325832593260326132623263326432653266326732683269327032713272327332743275327632773278327932803281328232833284328532863287328832893290329132923293329432953296329732983299330033013302330333043305330633073308330933103311331233133314331533163317331833193320332133223323332433253326332733283329333033313332333333343335333633373338333933403341334233433344334533463347334833493350335133523353335433553356335733583359336033613362336333643365336633673368336933703371337233733374337533763377337833793380338133823383338433853386338733883389339033913392339333943395339633973398339934003401340234033404340534063407340834093410341134123413341434153416341734183419342034213422342334243425342634273428342934303431343234333434343534363437343834393440344134423443344434453446344734483449345034513452345334543455345634573458345934603461346234633464346534663467346834693470347134723473347434753476347734783479348034813482348334843485348634873488348934903491349234933494349534963497349834993500350135023503350435053506350735083509351035113512351335143515351635173518351935203521352235233524352535263527352835293530353135323533353435353536353735383539354035413542354335443545354635473548354935503551355235533554355535563557355835593560356135623563356435653566356735683569357035713572357335743575357635773578357935803581358235833584358535863587358835893590359135923593359435953596359735983599360036013602360336043605360636073608360936103611361236133614361536163617361836193620362136223623362436253626362736283629363036313632363336343635363636373638363936403641364236433644364536463647364836493650365136523653365436553656365736583659366036613662366336643665366636673668366936703671367236733674367536763677367836793680368136823683368436853686368736883689369036913692369336943695369636973698369937003701370237033704370537063707370837093710371137123713371437153716371737183719372037213722372337243725372637273728372937303731373237333734373537363737373837393740374137423743374437453746374737483749375037513752375337543755375637573758375937603761376237633764376537663767376837693770377137723773377437753776377737783779378037813782378337843785378637873788378937903791379237933794379537963797379837993800380138023803380438053806380738083809381038113812381338143815381638173818381938203821382238233824382538263827382838293830383138323833383438353836383738383839384038413842384338443845384638473848384938503851385238533854385538563857385838593860386138623863386438653866386738683869387038713872387338743875387638773878387938803881388238833884388538863887388838893890389138923893389438953896389738983899390039013902390339043905390639073908390939103911391239133914391539163917391839193920392139223923392439253926392739283929393039313932393339343935393639373938393939403941394239433944394539463947394839493950395139523953395439553956395739583959396039613962396339643965396639673968396939703971397239733974397539763977397839793980398139823983398439853986398739883989399039913992399339943995399639973998399940004001400240034004400540064007400840094010401140124013401440154016401740184019402040214022402340244025402640274028402940304031403240334034403540364037403840394040404140424043404440454046404740484049405040514052405340544055405640574058405940604061406240634064406540664067406840694070407140724073407440754076407740784079408040814082408340844085408640874088408940904091409240934094409540964097409840994100410141024103410441054106410741084109411041114112411341144115411641174118411941204121412241234124412541264127412841294130413141324133413441354136413741384139414041414142414341444145414641474148414941504151415241534154415541564157415841594160416141624163416441654166416741684169417041714172417341744175417641774178417941804181418241834184418541864187418841894190419141924193419441954196419741984199420042014202420342044205420642074208420942104211421242134214421542164217421842194220422142224223422442254226422742284229423042314232423342344235423642374238423942404241424242434244424542464247424842494250425142524253425442554256425742584259426042614262426342644265426642674268426942704271427242734274427542764277427842794280428142824283428442854286428742884289429042914292429342944295429642974298429943004301430243034304430543064307430843094310431143124313431443154316431743184319432043214322432343244325432643274328432943304331433243334334433543364337433843394340434143424343434443454346434743484349435043514352435343544355435643574358435943604361436243634364436543664367436843694370437143724373437443754376437743784379438043814382438343844385438643874388438943904391439243934394439543964397439843994400440144024403440444054406440744084409441044114412441344144415441644174418441944204421442244234424442544264427442844294430443144324433443444354436443744384439444044414442444344444445444644474448444944504451445244534454445544564457445844594460446144624463446444654466446744684469447044714472447344744475447644774478447944804481448244834484448544864487448844894490449144924493449444954496449744984499450045014502450345044505450645074508450945104511451245134514451545164517451845194520452145224523452445254526452745284529453045314532453345344535453645374538453945404541454245434544454545464547454845494550455145524553455445554556455745584559456045614562456345644565456645674568456945704571457245734574457545764577457845794580458145824583458445854586458745884589459045914592459345944595459645974598459946004601460246034604460546064607460846094610461146124613461446154616461746184619462046214622462346244625462646274628462946304631463246334634463546364637463846394640464146424643464446454646464746484649465046514652465346544655465646574658465946604661466246634664466546664667466846694670467146724673467446754676467746784679468046814682468346844685468646874688468946904691469246934694469546964697469846994700470147024703470447054706470747084709471047114712471347144715471647174718471947204721472247234724472547264727472847294730473147324733473447354736473747384739474047414742474347444745474647474748474947504751475247534754475547564757475847594760476147624763476447654766476747684769477047714772477347744775477647774778477947804781478247834784478547864787478847894790479147924793479447954796479747984799480048014802480348044805480648074808480948104811481248134814481548164817481848194820482148224823482448254826482748284829483048314832483348344835483648374838483948404841484248434844484548464847484848494850485148524853485448554856485748584859486048614862486348644865486648674868486948704871487248734874487548764877487848794880488148824883488448854886488748884889489048914892489348944895489648974898489949004901490249034904490549064907490849094910491149124913491449154916491749184919492049214922492349244925492649274928492949304931493249334934493549364937493849394940494149424943494449454946494749484949495049514952495349544955495649574958495949604961496249634964496549664967496849694970497149724973497449754976497749784979498049814982498349844985498649874988498949904991499249934994499549964997499849995000500150025003500450055006500750085009501050115012501350145015501650175018501950205021502250235024502550265027502850295030503150325033503450355036503750385039504050415042504350445045504650475048504950505051505250535054505550565057505850595060506150625063506450655066506750685069507050715072507350745075507650775078507950805081508250835084508550865087508850895090509150925093509450955096509750985099510051015102510351045105510651075108510951105111511251135114511551165117511851195120512151225123512451255126512751285129513051315132513351345135513651375138513951405141514251435144514551465147514851495150515151525153515451555156515751585159516051615162516351645165516651675168516951705171517251735174517551765177517851795180518151825183518451855186518751885189519051915192519351945195519651975198519952005201520252035204520552065207520852095210521152125213521452155216521752185219522052215222522352245225522652275228522952305231523252335234523552365237523852395240524152425243524452455246524752485249525052515252525352545255525652575258525952605261526252635264526552665267526852695270527152725273527452755276527752785279528052815282528352845285528652875288528952905291529252935294529552965297529852995300530153025303530453055306530753085309531053115312531353145315531653175318531953205321532253235324532553265327532853295330533153325333533453355336533753385339534053415342534353445345534653475348534953505351535253535354535553565357535853595360536153625363536453655366536753685369537053715372537353745375537653775378537953805381538253835384538553865387538853895390539153925393539453955396539753985399540054015402540354045405540654075408540954105411541254135414541554165417541854195420542154225423542454255426542754285429543054315432543354345435543654375438543954405441544254435444544554465447544854495450545154525453545454555456545754585459546054615462546354645465546654675468546954705471547254735474547554765477547854795480548154825483548454855486548754885489549054915492549354945495549654975498549955005501550255035504550555065507550855095510551155125513551455155516551755185519552055215522552355245525552655275528552955305531553255335534553555365537553855395540554155425543554455455546554755485549555055515552555355545555555655575558555955605561556255635564556555665567556855695570557155725573557455755576557755785579558055815582558355845585558655875588558955905591559255935594559555965597559855995600560156025603560456055606560756085609561056115612561356145615561656175618561956205621562256235624562556265627562856295630563156325633563456355636563756385639564056415642564356445645564656475648564956505651565256535654565556565657565856595660566156625663566456655666566756685669567056715672567356745675567656775678567956805681568256835684568556865687568856895690569156925693569456955696569756985699570057015702570357045705570657075708570957105711571257135714571557165717571857195720572157225723572457255726572757285729573057315732573357345735573657375738573957405741574257435744574557465747574857495750575157525753575457555756575757585759576057615762576357645765576657675768576957705771577257735774577557765777577857795780578157825783578457855786578757885789579057915792579357945795579657975798579958005801580258035804580558065807580858095810581158125813581458155816581758185819582058215822582358245825582658275828582958305831583258335834583558365837583858395840584158425843584458455846584758485849585058515852585358545855585658575858585958605861586258635864586558665867586858695870587158725873587458755876587758785879588058815882588358845885588658875888588958905891589258935894589558965897589858995900590159025903590459055906590759085909591059115912591359145915591659175918591959205921592259235924592559265927592859295930593159325933593459355936593759385939594059415942594359445945594659475948594959505951595259535954595559565957595859595960596159625963596459655966596759685969597059715972597359745975597659775978597959805981598259835984598559865987598859895990599159925993599459955996599759985999600060016002600360046005600660076008600960106011601260136014601560166017601860196020602160226023602460256026602760286029603060316032603360346035603660376038603960406041604260436044604560466047604860496050605160526053605460556056605760586059606060616062606360646065606660676068606960706071607260736074607560766077607860796080608160826083608460856086608760886089609060916092609360946095609660976098609961006101610261036104610561066107610861096110611161126113611461156116611761186119612061216122612361246125612661276128612961306131613261336134613561366137613861396140614161426143614461456146614761486149615061516152615361546155615661576158615961606161616261636164616561666167616861696170617161726173617461756176617761786179618061816182618361846185618661876188618961906191619261936194619561966197619861996200620162026203620462056206620762086209621062116212621362146215621662176218621962206221622262236224622562266227622862296230623162326233623462356236623762386239624062416242624362446245624662476248624962506251625262536254625562566257625862596260626162626263626462656266626762686269627062716272627362746275627662776278627962806281628262836284628562866287628862896290629162926293629462956296629762986299630063016302630363046305630663076308630963106311631263136314631563166317631863196320632163226323632463256326632763286329633063316332633363346335633663376338633963406341634263436344634563466347634863496350635163526353635463556356635763586359636063616362636363646365636663676368636963706371637263736374637563766377637863796380638163826383638463856386638763886389639063916392639363946395639663976398639964006401640264036404640564066407640864096410641164126413641464156416641764186419642064216422642364246425642664276428642964306431643264336434643564366437643864396440644164426443644464456446644764486449645064516452645364546455645664576458645964606461646264636464646564666467646864696470647164726473647464756476647764786479648064816482648364846485648664876488648964906491649264936494649564966497649864996500650165026503650465056506650765086509651065116512651365146515651665176518651965206521652265236524652565266527652865296530653165326533653465356536653765386539654065416542654365446545654665476548654965506551655265536554655565566557655865596560656165626563656465656566656765686569657065716572657365746575657665776578657965806581658265836584658565866587658865896590659165926593659465956596659765986599660066016602660366046605660666076608660966106611661266136614661566166617661866196620662166226623662466256626662766286629663066316632663366346635663666376638663966406641664266436644664566466647664866496650665166526653665466556656665766586659666066616662666366646665666666676668666966706671667266736674667566766677667866796680668166826683668466856686668766886689669066916692669366946695669666976698669967006701670267036704670567066707670867096710671167126713671467156716671767186719672067216722672367246725672667276728672967306731673267336734673567366737673867396740674167426743674467456746674767486749675067516752675367546755675667576758675967606761676267636764676567666767676867696770677167726773677467756776677767786779678067816782678367846785678667876788678967906791679267936794679567966797679867996800680168026803680468056806680768086809681068116812681368146815681668176818681968206821682268236824682568266827682868296830683168326833683468356836683768386839684068416842684368446845684668476848684968506851685268536854685568566857685868596860686168626863686468656866686768686869687068716872687368746875687668776878687968806881688268836884688568866887688868896890689168926893689468956896689768986899690069016902690369046905690669076908690969106911691269136914691569166917691869196920692169226923692469256926692769286929693069316932693369346935693669376938693969406941694269436944694569466947694869496950695169526953695469556956695769586959696069616962696369646965696669676968696969706971697269736974697569766977697869796980698169826983698469856986698769886989699069916992699369946995699669976998699970007001700270037004700570067007700870097010701170127013701470157016701770187019702070217022702370247025702670277028702970307031703270337034703570367037703870397040704170427043704470457046704770487049705070517052705370547055705670577058705970607061706270637064706570667067706870697070707170727073707470757076707770787079708070817082708370847085708670877088708970907091709270937094709570967097709870997100710171027103710471057106710771087109711071117112711371147115711671177118711971207121712271237124712571267127712871297130713171327133713471357136713771387139714071417142714371447145714671477148714971507151715271537154715571567157715871597160716171627163716471657166716771687169717071717172717371747175717671777178717971807181718271837184718571867187718871897190719171927193719471957196719771987199720072017202720372047205720672077208720972107211721272137214721572167217721872197220722172227223722472257226722772287229723072317232723372347235723672377238723972407241724272437244724572467247724872497250725172527253725472557256725772587259726072617262726372647265726672677268726972707271727272737274727572767277727872797280728172827283728472857286728772887289729072917292729372947295729672977298729973007301730273037304730573067307730873097310731173127313731473157316731773187319732073217322732373247325732673277328732973307331733273337334733573367337733873397340734173427343734473457346734773487349735073517352735373547355735673577358735973607361736273637364736573667367736873697370737173727373737473757376737773787379738073817382738373847385738673877388738973907391739273937394739573967397739873997400740174027403740474057406740774087409741074117412741374147415741674177418741974207421742274237424742574267427742874297430743174327433743474357436743774387439744074417442744374447445744674477448744974507451745274537454745574567457745874597460746174627463746474657466746774687469747074717472747374747475747674777478747974807481748274837484748574867487748874897490749174927493749474957496749774987499750075017502750375047505750675077508750975107511751275137514751575167517751875197520752175227523752475257526752775287529753075317532753375347535753675377538753975407541754275437544754575467547754875497550755175527553755475557556755775587559756075617562756375647565756675677568756975707571757275737574757575767577757875797580758175827583758475857586758775887589759075917592759375947595759675977598759976007601760276037604760576067607760876097610761176127613761476157616761776187619762076217622762376247625762676277628762976307631763276337634763576367637763876397640764176427643764476457646764776487649765076517652765376547655765676577658765976607661766276637664766576667667766876697670767176727673767476757676767776787679768076817682768376847685768676877688768976907691769276937694769576967697769876997700770177027703770477057706770777087709771077117712771377147715771677177718771977207721772277237724772577267727772877297730773177327733773477357736773777387739774077417742774377447745774677477748774977507751775277537754775577567757775877597760776177627763776477657766776777687769777077717772777377747775777677777778777977807781778277837784778577867787778877897790779177927793779477957796779777987799780078017802780378047805780678077808780978107811781278137814781578167817781878197820782178227823782478257826782778287829783078317832
  1. (function() {
  2. window.DS = Ember.Namespace.create({
  3. // this one goes to 11
  4. CURRENT_API_REVISION: 11
  5. });
  6. })();
  7. (function() {
  8. var DeferredMixin = Ember.DeferredMixin, // ember-runtime/mixins/deferred
  9. Evented = Ember.Evented, // ember-runtime/mixins/evented
  10. run = Ember.run, // ember-metal/run-loop
  11. get = Ember.get; // ember-metal/accessors
  12. var LoadPromise = Ember.Mixin.create(Evented, DeferredMixin, {
  13. init: function() {
  14. this._super.apply(this, arguments);
  15. this.one('didLoad', function() {
  16. run(this, 'resolve', this);
  17. });
  18. if (get(this, 'isLoaded')) {
  19. this.trigger('didLoad');
  20. }
  21. }
  22. });
  23. DS.LoadPromise = LoadPromise;
  24. })();
  25. (function() {
  26. var get = Ember.get, set = Ember.set;
  27. var LoadPromise = DS.LoadPromise; // system/mixins/load_promise
  28. /**
  29. A record array is an array that contains records of a certain type. The record
  30. array materializes records as needed when they are retrieved for the first
  31. time. You should not create record arrays yourself. Instead, an instance of
  32. DS.RecordArray or its subclasses will be returned by your application's store
  33. in response to queries.
  34. */
  35. DS.RecordArray = Ember.ArrayProxy.extend(Ember.Evented, LoadPromise, {
  36. /**
  37. The model type contained by this record array.
  38. @type DS.Model
  39. */
  40. type: null,
  41. // The array of client ids backing the record array. When a
  42. // record is requested from the record array, the record
  43. // for the client id at the same index is materialized, if
  44. // necessary, by the store.
  45. content: null,
  46. isLoaded: false,
  47. isUpdating: false,
  48. // The store that created this record array.
  49. store: null,
  50. objectAtContent: function(index) {
  51. var content = get(this, 'content'),
  52. reference = content.objectAt(index),
  53. store = get(this, 'store');
  54. if (reference) {
  55. return store.findByClientId(get(this, 'type'), reference.clientId);
  56. }
  57. },
  58. materializedObjectAt: function(index) {
  59. var reference = get(this, 'content').objectAt(index);
  60. if (!reference) { return; }
  61. if (get(this, 'store').recordIsMaterialized(reference.clientId)) {
  62. return this.objectAt(index);
  63. }
  64. },
  65. update: function() {
  66. if (get(this, 'isUpdating')) { return; }
  67. var store = get(this, 'store'),
  68. type = get(this, 'type');
  69. store.fetchAll(type, this);
  70. },
  71. addReference: function(reference) {
  72. get(this, 'content').addObject(reference);
  73. },
  74. removeReference: function(reference) {
  75. get(this, 'content').removeObject(reference);
  76. }
  77. });
  78. })();
  79. (function() {
  80. var get = Ember.get;
  81. DS.FilteredRecordArray = DS.RecordArray.extend({
  82. filterFunction: null,
  83. isLoaded: true,
  84. replace: function() {
  85. var type = get(this, 'type').toString();
  86. throw new Error("The result of a client-side filter (on " + type + ") is immutable.");
  87. },
  88. updateFilter: Ember.observer(function() {
  89. var store = get(this, 'store');
  90. store.updateRecordArrayFilter(this, get(this, 'type'), get(this, 'filterFunction'));
  91. }, 'filterFunction')
  92. });
  93. })();
  94. (function() {
  95. var get = Ember.get, set = Ember.set;
  96. DS.AdapterPopulatedRecordArray = DS.RecordArray.extend({
  97. query: null,
  98. replace: function() {
  99. var type = get(this, 'type').toString();
  100. throw new Error("The result of a server query (on " + type + ") is immutable.");
  101. },
  102. load: function(references) {
  103. var store = get(this, 'store'), type = get(this, 'type');
  104. this.beginPropertyChanges();
  105. set(this, 'content', Ember.A(references));
  106. set(this, 'isLoaded', true);
  107. this.endPropertyChanges();
  108. var self = this;
  109. // TODO: does triggering didLoad event should be the last action of the runLoop?
  110. Ember.run.once(function() {
  111. self.trigger('didLoad');
  112. });
  113. }
  114. });
  115. })();
  116. (function() {
  117. var get = Ember.get, set = Ember.set;
  118. /**
  119. A ManyArray is a RecordArray that represents the contents of a has-many
  120. relationship.
  121. The ManyArray is instantiated lazily the first time the relationship is
  122. requested.
  123. ### Inverses
  124. Often, the relationships in Ember Data applications will have
  125. an inverse. For example, imagine the following models are
  126. defined:
  127. App.Post = DS.Model.extend({
  128. comments: DS.hasMany('App.Comment')
  129. });
  130. App.Comment = DS.Model.extend({
  131. post: DS.belongsTo('App.Post')
  132. });
  133. If you created a new instance of `App.Post` and added
  134. a `App.Comment` record to its `comments` has-many
  135. relationship, you would expect the comment's `post`
  136. property to be set to the post that contained
  137. the has-many.
  138. We call the record to which a relationship belongs the
  139. relationship's _owner_.
  140. */
  141. DS.ManyArray = DS.RecordArray.extend({
  142. init: function() {
  143. this._super.apply(this, arguments);
  144. this._changesToSync = Ember.OrderedSet.create();
  145. },
  146. /**
  147. @private
  148. The record to which this relationship belongs.
  149. @property {DS.Model}
  150. */
  151. owner: null,
  152. // LOADING STATE
  153. isLoaded: false,
  154. loadingRecordsCount: function(count) {
  155. this.loadingRecordsCount = count;
  156. },
  157. loadedRecord: function() {
  158. this.loadingRecordsCount--;
  159. if (this.loadingRecordsCount === 0) {
  160. set(this, 'isLoaded', true);
  161. this.trigger('didLoad');
  162. }
  163. },
  164. fetch: function() {
  165. var references = get(this, 'content'),
  166. store = get(this, 'store'),
  167. type = get(this, 'type'),
  168. owner = get(this, 'owner');
  169. store.fetchUnloadedReferences(type, references, owner);
  170. },
  171. // Overrides Ember.Array's replace method to implement
  172. replaceContent: function(index, removed, added) {
  173. // Map the array of record objects into an array of client ids.
  174. added = added.map(function(record) {
  175. Ember.assert("You can only add records of " + (get(this, 'type') && get(this, 'type').toString()) + " to this relationship.", !get(this, 'type') || (get(this, 'type') === record.constructor));
  176. return get(record, '_reference');
  177. }, this);
  178. this._super(index, removed, added);
  179. },
  180. arrangedContentDidChange: function() {
  181. this.fetch();
  182. },
  183. arrayContentWillChange: function(index, removed, added) {
  184. var owner = get(this, 'owner'),
  185. name = get(this, 'name');
  186. if (!owner._suspendedRelationships) {
  187. // This code is the first half of code that continues inside
  188. // of arrayContentDidChange. It gets or creates a change from
  189. // the child object, adds the current owner as the old
  190. // parent if this is the first time the object was removed
  191. // from a ManyArray, and sets `newParent` to null.
  192. //
  193. // Later, if the object is added to another ManyArray,
  194. // the `arrayContentDidChange` will set `newParent` on
  195. // the change.
  196. for (var i=index; i<index+removed; i++) {
  197. var reference = get(this, 'content').objectAt(i);
  198. var change = DS.RelationshipChange.createChange(owner.get('clientId'), reference.clientId, get(this, 'store'), {
  199. parentType: owner.constructor,
  200. changeType: "remove",
  201. kind: "hasMany",
  202. key: name
  203. });
  204. this._changesToSync.add(change);
  205. }
  206. }
  207. return this._super.apply(this, arguments);
  208. },
  209. arrayContentDidChange: function(index, removed, added) {
  210. this._super.apply(this, arguments);
  211. var owner = get(this, 'owner'),
  212. name = get(this, 'name'),
  213. store = get(this, 'store');
  214. if (!owner._suspendedRelationships) {
  215. // This code is the second half of code that started in
  216. // `arrayContentWillChange`. It gets or creates a change
  217. // from the child object, and adds the current owner as
  218. // the new parent.
  219. for (var i=index; i<index+added; i++) {
  220. var reference = get(this, 'content').objectAt(i);
  221. var change = DS.RelationshipChange.createChange(owner.get('clientId'), reference.clientId, store, {
  222. parentType: owner.constructor,
  223. changeType: "add",
  224. kind:"hasMany",
  225. key: name
  226. });
  227. change.hasManyName = name;
  228. this._changesToSync.add(change);
  229. }
  230. // We wait until the array has finished being
  231. // mutated before syncing the OneToManyChanges created
  232. // in arrayContentWillChange, so that the array
  233. // membership test in the sync() logic operates
  234. // on the final results.
  235. this._changesToSync.forEach(function(change) {
  236. change.sync();
  237. });
  238. DS.OneToManyChange.ensureSameTransaction(this._changesToSync, store);
  239. this._changesToSync.clear();
  240. }
  241. },
  242. // Create a child record within the owner
  243. createRecord: function(hash, transaction) {
  244. var owner = get(this, 'owner'),
  245. store = get(owner, 'store'),
  246. type = get(this, 'type'),
  247. record;
  248. transaction = transaction || get(owner, 'transaction');
  249. record = store.createRecord.call(store, type, hash, transaction);
  250. this.pushObject(record);
  251. return record;
  252. }
  253. });
  254. })();
  255. (function() {
  256. })();
  257. (function() {
  258. var get = Ember.get, set = Ember.set, fmt = Ember.String.fmt,
  259. removeObject = Ember.EnumerableUtils.removeObject, forEach = Ember.EnumerableUtils.forEach;
  260. /**
  261. A transaction allows you to collect multiple records into a unit of work
  262. that can be committed or rolled back as a group.
  263. For example, if a record has local modifications that have not yet
  264. been saved, calling `commit()` on its transaction will cause those
  265. modifications to be sent to the adapter to be saved. Calling
  266. `rollback()` on its transaction would cause all of the modifications to
  267. be discarded and the record to return to the last known state before
  268. changes were made.
  269. If a newly created record's transaction is rolled back, it will
  270. immediately transition to the deleted state.
  271. If you do not explicitly create a transaction, a record is assigned to
  272. an implicit transaction called the default transaction. In these cases,
  273. you can treat your application's instance of `DS.Store` as a transaction
  274. and call the `commit()` and `rollback()` methods on the store itself.
  275. Once a record has been successfully committed or rolled back, it will
  276. be moved back to the implicit transaction. Because it will now be in
  277. a clean state, it can be moved to a new transaction if you wish.
  278. ### Creating a Transaction
  279. To create a new transaction, call the `transaction()` method of your
  280. application's `DS.Store` instance:
  281. var transaction = App.store.transaction();
  282. This will return a new instance of `DS.Transaction` with no records
  283. yet assigned to it.
  284. ### Adding Existing Records
  285. Add records to a transaction using the `add()` method:
  286. record = App.store.find(App.Person, 1);
  287. transaction.add(record);
  288. Note that only records whose `isDirty` flag is `false` may be added
  289. to a transaction. Once modifications to a record have been made
  290. (its `isDirty` flag is `true`), it is not longer able to be added to
  291. a transaction.
  292. ### Creating New Records
  293. Because newly created records are dirty from the time they are created,
  294. and because dirty records can not be added to a transaction, you must
  295. use the `createRecord()` method to assign new records to a transaction.
  296. For example, instead of this:
  297. var transaction = store.transaction();
  298. var person = App.Person.createRecord({ name: "Steve" });
  299. // won't work because person is dirty
  300. transaction.add(person);
  301. Call `createRecord()` on the transaction directly:
  302. var transaction = store.transaction();
  303. transaction.createRecord(App.Person, { name: "Steve" });
  304. ### Asynchronous Commits
  305. Typically, all of the records in a transaction will be committed
  306. together. However, new records that have a dependency on other new
  307. records need to wait for their parent record to be saved and assigned an
  308. ID. In that case, the child record will continue to live in the
  309. transaction until its parent is saved, at which time the transaction will
  310. attempt to commit again.
  311. For this reason, you should not re-use transactions once you have committed
  312. them. Always make a new transaction and move the desired records to it before
  313. calling commit.
  314. */
  315. var arrayDefault = function() { return []; };
  316. DS.Transaction = Ember.Object.extend({
  317. /**
  318. @private
  319. Creates the bucket data structure used to segregate records by
  320. type.
  321. */
  322. init: function() {
  323. set(this, 'buckets', {
  324. clean: Ember.OrderedSet.create(),
  325. created: Ember.OrderedSet.create(),
  326. updated: Ember.OrderedSet.create(),
  327. deleted: Ember.OrderedSet.create(),
  328. inflight: Ember.OrderedSet.create()
  329. });
  330. set(this, 'relationships', Ember.OrderedSet.create());
  331. },
  332. /**
  333. Creates a new record of the given type and assigns it to the transaction
  334. on which the method was called.
  335. This is useful as only clean records can be added to a transaction and
  336. new records created using other methods immediately become dirty.
  337. @param {DS.Model} type the model type to create
  338. @param {Object} hash the data hash to assign the new record
  339. */
  340. createRecord: function(type, hash) {
  341. var store = get(this, 'store');
  342. return store.createRecord(type, hash, this);
  343. },
  344. isEqualOrDefault: function(other) {
  345. if (this === other || other === get(this, 'store.defaultTransaction')) {
  346. return true;
  347. }
  348. },
  349. isDefault: Ember.computed(function() {
  350. return this === get(this, 'store.defaultTransaction');
  351. }),
  352. /**
  353. Adds an existing record to this transaction. Only records without
  354. modficiations (i.e., records whose `isDirty` property is `false`)
  355. can be added to a transaction.
  356. @param {DS.Model} record the record to add to the transaction
  357. */
  358. add: function(record) {
  359. Ember.assert("You must pass a record into transaction.add()", record instanceof DS.Model);
  360. var recordTransaction = get(record, 'transaction'),
  361. defaultTransaction = get(this, 'store.defaultTransaction');
  362. // Make `add` idempotent
  363. if (recordTransaction === this) { return; }
  364. // XXX it should be possible to move a dirty transaction from the default transaction
  365. // we could probably make this work if someone has a valid use case. Do you?
  366. Ember.assert("Once a record has changed, you cannot move it into a different transaction", !get(record, 'isDirty'));
  367. Ember.assert("Models cannot belong to more than one transaction at a time.", recordTransaction === defaultTransaction);
  368. this.adoptRecord(record);
  369. },
  370. relationshipBecameDirty: function(relationship) {
  371. get(this, 'relationships').add(relationship);
  372. },
  373. relationshipBecameClean: function(relationship) {
  374. get(this, 'relationships').remove(relationship);
  375. },
  376. /**
  377. Commits the transaction, which causes all of the modified records that
  378. belong to the transaction to be sent to the adapter to be saved.
  379. Once you call `commit()` on a transaction, you should not re-use it.
  380. When a record is saved, it will be removed from this transaction and
  381. moved back to the store's default transaction.
  382. */
  383. commit: function() {
  384. var store = get(this, 'store');
  385. var adapter = get(store, '_adapter');
  386. var defaultTransaction = get(store, 'defaultTransaction');
  387. var iterate = function(records) {
  388. var set = records.copy();
  389. set.forEach(function (record) {
  390. record.send('willCommit');
  391. });
  392. return set;
  393. };
  394. var relationships = get(this, 'relationships');
  395. var commitDetails = {
  396. created: iterate(this.bucketForType('created')),
  397. updated: iterate(this.bucketForType('updated')),
  398. deleted: iterate(this.bucketForType('deleted')),
  399. relationships: relationships
  400. };
  401. if (this === defaultTransaction) {
  402. set(store, 'defaultTransaction', store.transaction());
  403. }
  404. this.removeCleanRecords();
  405. if (!commitDetails.created.isEmpty() || !commitDetails.updated.isEmpty() || !commitDetails.deleted.isEmpty() || !relationships.isEmpty()) {
  406. if (adapter && adapter.commit) { adapter.commit(store, commitDetails); }
  407. else { throw fmt("Adapter is either null or does not implement `commit` method", this); }
  408. }
  409. // Once we've committed the transaction, there is no need to
  410. // keep the OneToManyChanges around. Destroy them so they
  411. // can be garbage collected.
  412. relationships.forEach(function(relationship) {
  413. relationship.destroy();
  414. });
  415. },
  416. /**
  417. Rolling back a transaction resets the records that belong to
  418. that transaction.
  419. Updated records have their properties reset to the last known
  420. value from the persistence layer. Deleted records are reverted
  421. to a clean, non-deleted state. Newly created records immediately
  422. become deleted, and are not sent to the adapter to be persisted.
  423. After the transaction is rolled back, any records that belong
  424. to it will return to the store's default transaction, and the
  425. current transaction should not be used again.
  426. */
  427. rollback: function() {
  428. // Loop through all of the records in each of the dirty states
  429. // and initiate a rollback on them. As a side effect of telling
  430. // the record to roll back, it should also move itself out of
  431. // the dirty bucket and into the clean bucket.
  432. ['created', 'updated', 'deleted', 'inflight'].forEach(function(bucketType) {
  433. var records = this.bucketForType(bucketType);
  434. forEach(records, function(record) {
  435. record.send('rollback');
  436. });
  437. records.clear();
  438. }, this);
  439. // Now that all records in the transaction are guaranteed to be
  440. // clean, migrate them all to the store's default transaction.
  441. this.removeCleanRecords();
  442. },
  443. /**
  444. @private
  445. Removes a record from this transaction and back to the store's
  446. default transaction.
  447. Note: This method is private for now, but should probably be exposed
  448. in the future once we have stricter error checking (for example, in the
  449. case of the record being dirty).
  450. @param {DS.Model} record
  451. */
  452. remove: function(record) {
  453. var defaultTransaction = get(this, 'store.defaultTransaction');
  454. defaultTransaction.adoptRecord(record);
  455. },
  456. /**
  457. @private
  458. Removes all of the records in the transaction's clean bucket.
  459. */
  460. removeCleanRecords: function() {
  461. var clean = this.bucketForType('clean');
  462. clean.forEach(function(record) {
  463. this.remove(record);
  464. }, this);
  465. clean.clear();
  466. },
  467. /**
  468. @private
  469. Returns the bucket for the given bucket type. For example, you might call
  470. `this.bucketForType('updated')` to get the `Ember.Map` that contains all
  471. of the records that have changes pending.
  472. @param {String} bucketType the type of bucket
  473. @returns Ember.Map
  474. */
  475. bucketForType: function(bucketType) {
  476. var buckets = get(this, 'buckets');
  477. return get(buckets, bucketType);
  478. },
  479. /**
  480. @private
  481. This method moves a record into a different transaction without the normal
  482. checks that ensure that the user is not doing something weird, like moving
  483. a dirty record into a new transaction.
  484. It is designed for internal use, such as when we are moving a clean record
  485. into a new transaction when the transaction is committed.
  486. This method must not be called unless the record is clean.
  487. @param {DS.Model} record
  488. */
  489. adoptRecord: function(record) {
  490. var oldTransaction = get(record, 'transaction');
  491. if (oldTransaction) {
  492. oldTransaction.removeFromBucket('clean', record);
  493. }
  494. this.addToBucket('clean', record);
  495. set(record, 'transaction', this);
  496. },
  497. /**
  498. @private
  499. Adds a record to the named bucket.
  500. @param {String} bucketType one of `clean`, `created`, `updated`, or `deleted`
  501. */
  502. addToBucket: function(bucketType, record) {
  503. this.bucketForType(bucketType).add(record);
  504. },
  505. /**
  506. @private
  507. Removes a record from the named bucket.
  508. @param {String} bucketType one of `clean`, `created`, `updated`, or `deleted`
  509. */
  510. removeFromBucket: function(bucketType, record) {
  511. this.bucketForType(bucketType).remove(record);
  512. },
  513. /**
  514. @private
  515. Called by a record's state manager to indicate that the record has entered
  516. a dirty state. The record will be moved from the `clean` bucket and into
  517. the appropriate dirty bucket.
  518. @param {String} bucketType one of `created`, `updated`, or `deleted`
  519. */
  520. recordBecameDirty: function(bucketType, record) {
  521. this.removeFromBucket('clean', record);
  522. this.addToBucket(bucketType, record);
  523. },
  524. /**
  525. @private
  526. Called by a record's state manager to indicate that the record has entered
  527. inflight state. The record will be moved from its current dirty bucket and into
  528. the `inflight` bucket.
  529. @param {String} bucketType one of `created`, `updated`, or `deleted`
  530. */
  531. recordBecameInFlight: function(kind, record) {
  532. this.removeFromBucket(kind, record);
  533. this.addToBucket('inflight', record);
  534. },
  535. recordIsMoving: function(kind, record) {
  536. this.removeFromBucket(kind, record);
  537. this.addToBucket('clean', record);
  538. },
  539. /**
  540. @private
  541. Called by a record's state manager to indicate that the record has entered
  542. a clean state. The record will be moved from its current dirty or inflight bucket and into
  543. the `clean` bucket.
  544. @param {String} bucketType one of `created`, `updated`, or `deleted`
  545. */
  546. recordBecameClean: function(kind, record) {
  547. this.removeFromBucket(kind, record);
  548. this.remove(record);
  549. }
  550. });
  551. })();
  552. (function() {
  553. var classify = Ember.String.classify, get = Ember.get;
  554. /**
  555. @private
  556. The Mappable mixin is designed for classes that would like to
  557. behave as a map for configuration purposes.
  558. For example, the DS.Adapter class can behave like a map, with
  559. more semantic API, via the `map` API:
  560. DS.Adapter.map('App.Person', { firstName: { keyName: 'FIRST' } });
  561. Class configuration via a map-like API has a few common requirements
  562. that differentiate it from the standard Ember.Map implementation.
  563. First, values often are provided as strings that should be normalized
  564. into classes the first time the configuration options are used.
  565. Second, the values configured on parent classes should also be taken
  566. into account.
  567. Finally, setting the value of a key sometimes should merge with the
  568. previous value, rather than replacing it.
  569. This mixin provides a instance method, `createInstanceMapFor`, that
  570. will reify all of the configuration options set on an instance's
  571. constructor and provide it for the instance to use.
  572. Classes can implement certain hooks that allow them to customize
  573. the requirements listed above:
  574. * `resolveMapConflict` - called when a value is set for an existing
  575. value
  576. * `transformMapKey` - allows a key name (for example, a global path
  577. to a class) to be normalized
  578. * `transformMapValue` - allows a value (for example, a class that
  579. should be instantiated) to be normalized
  580. Classes that implement this mixin should also implement a class
  581. method built using the `generateMapFunctionFor` method:
  582. DS.Adapter.reopenClass({
  583. map: DS.Mappable.generateMapFunctionFor('attributes', function(key, newValue, map) {
  584. var existingValue = map.get(key);
  585. for (var prop in newValue) {
  586. if (!newValue.hasOwnProperty(prop)) { continue; }
  587. existingValue[prop] = newValue[prop];
  588. }
  589. })
  590. });
  591. The function passed to `generateMapFunctionFor` is invoked every time a
  592. new value is added to the map.
  593. **/
  594. var resolveMapConflict = function(oldValue, newValue, mappingsKey) {
  595. return oldValue;
  596. };
  597. var transformMapKey = function(key, value) {
  598. return key;
  599. };
  600. var transformMapValue = function(key, value) {
  601. return value;
  602. };
  603. DS._Mappable = Ember.Mixin.create({
  604. createInstanceMapFor: function(mapName) {
  605. var instanceMeta = Ember.metaPath(this, ['DS.Mappable'], true);
  606. instanceMeta.values = instanceMeta.values || {};
  607. if (instanceMeta.values[mapName]) { return instanceMeta.values[mapName]; }
  608. var instanceMap = instanceMeta.values[mapName] = new Ember.Map();
  609. var klass = this.constructor;
  610. while (klass && klass !== DS.Store) {
  611. this._copyMap(mapName, klass, instanceMap);
  612. klass = klass.superclass;
  613. }
  614. instanceMeta.values[mapName] = instanceMap;
  615. return instanceMap;
  616. },
  617. _copyMap: function(mapName, klass, instanceMap) {
  618. var classMeta = Ember.metaPath(klass, ['DS.Mappable'], true);
  619. var classMap = classMeta[mapName];
  620. if (classMap) {
  621. classMap.forEach(eachMap, this);
  622. }
  623. function eachMap(key, value) {
  624. var transformedKey = (klass.transformMapKey || transformMapKey)(key, value);
  625. var transformedValue = (klass.transformMapValue || transformMapValue)(key, value);
  626. var oldValue = instanceMap.get(transformedKey);
  627. var newValue = transformedValue;
  628. if (oldValue) {
  629. newValue = (this.constructor.resolveMapConflict || resolveMapConflict)(oldValue, newValue, mapName);
  630. }
  631. instanceMap.set(transformedKey, newValue);
  632. }
  633. },
  634. });
  635. DS._Mappable.generateMapFunctionFor = function(mapName, transform) {
  636. return function(key, value) {
  637. var meta = Ember.metaPath(this, ['DS.Mappable'], true);
  638. var map = meta[mapName] || Ember.MapWithDefault.create({
  639. defaultValue: function() { return {}; }
  640. });
  641. transform.call(this, key, value, map);
  642. meta[mapName] = map;
  643. };
  644. };
  645. })();
  646. (function() {
  647. /*globals Ember*/
  648. /*jshint eqnull:true*/
  649. var get = Ember.get, set = Ember.set, fmt = Ember.String.fmt, once = Ember.run.once;
  650. var forEach = Ember.EnumerableUtils.forEach;
  651. // These values are used in the data cache when clientIds are
  652. // needed but the underlying data has not yet been loaded by
  653. // the server.
  654. var UNLOADED = 'unloaded';
  655. var LOADING = 'loading';
  656. var MATERIALIZED = { materialized: true };
  657. var CREATED = { created: true };
  658. // Implementors Note:
  659. //
  660. // The variables in this file are consistently named according to the following
  661. // scheme:
  662. //
  663. // * +id+ means an identifier managed by an external source, provided inside
  664. // the data provided by that source.
  665. // * +clientId+ means a transient numerical identifier generated at runtime by
  666. // the data store. It is important primarily because newly created objects may
  667. // not yet have an externally generated id.
  668. // * +type+ means a subclass of DS.Model.
  669. // Used by the store to normalize IDs entering the store. Despite the fact
  670. // that developers may provide IDs as numbers (e.g., `store.find(Person, 1)`),
  671. // it is important that internally we use strings, since IDs may be serialized
  672. // and lose type information. For example, Ember's router may put a record's
  673. // ID into the URL, and if we later try to deserialize that URL and find the
  674. // corresponding record, we will not know if it is a string or a number.
  675. var coerceId = function(id) {
  676. return id == null ? null : id+'';
  677. };
  678. var map = Ember.EnumerableUtils.map;
  679. /**
  680. The store contains all of the data for records loaded from the server.
  681. It is also responsible for creating instances of DS.Model that wraps
  682. the individual data for a record, so that they can be bound to in your
  683. Handlebars templates.
  684. Create a new store like this:
  685. MyApp.store = DS.Store.create();
  686. You can retrieve DS.Model instances from the store in several ways. To retrieve
  687. a record for a specific id, use the `find()` method:
  688. var record = MyApp.store.find(MyApp.Contact, 123);
  689. By default, the store will talk to your backend using a standard REST mechanism.
  690. You can customize how the store talks to your backend by specifying a custom adapter:
  691. MyApp.store = DS.Store.create({
  692. adapter: 'MyApp.CustomAdapter'
  693. });
  694. You can learn more about writing a custom adapter by reading the `DS.Adapter`
  695. documentation.
  696. */
  697. DS.Store = Ember.Object.extend(DS._Mappable, {
  698. /**
  699. Many methods can be invoked without specifying which store should be used.
  700. In those cases, the first store created will be used as the default. If
  701. an application has multiple stores, it should specify which store to use
  702. when performing actions, such as finding records by id.
  703. The init method registers this store as the default if none is specified.
  704. */
  705. init: function() {
  706. // Enforce API revisioning. See BREAKING_CHANGES.md for more.
  707. var revision = get(this, 'revision');
  708. if (revision !== DS.CURRENT_API_REVISION && !Ember.ENV.TESTING) {
  709. throw new Error("Error: The Ember Data library has had breaking API changes since the last time you updated the library. Please review the list of breaking changes at https://github.com/emberjs/data/blob/master/BREAKING_CHANGES.md, then update your store's `revision` property to " + DS.CURRENT_API_REVISION);
  710. }
  711. if (!get(DS, 'defaultStore') || get(this, 'isDefaultStore')) {
  712. set(DS, 'defaultStore', this);
  713. }
  714. // internal bookkeeping; not observable
  715. this.typeMaps = {};
  716. this.recordCache = [];
  717. this.clientIdToId = {};
  718. this.clientIdToType = {};
  719. this.clientIdToData = {};
  720. this.clientIdToPrematerializedData = {};
  721. this.recordArraysByClientId = {};
  722. this.relationshipChanges = {};
  723. this.recordReferences = {};
  724. // Internally, we maintain a map of all unloaded IDs requested by
  725. // a ManyArray. As the adapter loads data into the store, the
  726. // store notifies any interested ManyArrays. When the ManyArray's
  727. // total number of loading records drops to zero, it becomes
  728. // `isLoaded` and fires a `didLoad` event.
  729. this.loadingRecordArrays = {};
  730. set(this, 'defaultTransaction', this.transaction());
  731. },
  732. /**
  733. Returns a new transaction scoped to this store. This delegates
  734. responsibility for invoking the adapter's commit mechanism to
  735. a transaction.
  736. Transaction are responsible for tracking changes to records
  737. added to them, and supporting `commit` and `rollback`
  738. functionality. Committing a transaction invokes the store's
  739. adapter, while rolling back a transaction reverses all
  740. changes made to records added to the transaction.
  741. A store has an implicit (default) transaction, which tracks changes
  742. made to records not explicitly added to a transaction.
  743. @see {DS.Transaction}
  744. @returns DS.Transaction
  745. */
  746. transaction: function() {
  747. return DS.Transaction.create({ store: this });
  748. },
  749. ensureSameTransaction: function(records){
  750. var transactions = Ember.A();
  751. forEach( records, function(record){
  752. if (record){ transactions.pushObject(get(record, 'transaction')); }
  753. });
  754. var transaction = transactions.reduce(function(prev, t) {
  755. if (!get(t, 'isDefault')) {
  756. if (prev === null) { return t; }
  757. Ember.assert("All records in a changed relationship must be in the same transaction. You tried to change the relationship between records when one is in " + t + " and the other is in " + prev, t === prev);
  758. }
  759. return prev;
  760. }, null);
  761. if (transaction) {
  762. forEach( records, function(record){
  763. if (record){ transaction.add(record); }
  764. });
  765. } else {
  766. transaction = transactions.objectAt(0);
  767. }
  768. return transaction;
  769. },
  770. /**
  771. @private
  772. Instructs the store to materialize the data for a given record.
  773. To materialize a record, the store first retrieves the opaque data that was
  774. passed to either `load()` or `loadMany()`. Then, the data and the record
  775. are passed to the adapter's `materialize()` method, which allows the adapter
  776. to translate arbitrary data structures from the adapter into the normalized
  777. form the record expects.
  778. The adapter's `materialize()` method will invoke `materializeAttribute()`,
  779. `materializeHasMany()` and `materializeBelongsTo()` on the record to
  780. populate it with normalized values.
  781. @param {DS.Model} record
  782. */
  783. materializeData: function(record) {
  784. var clientId = get(record, 'clientId'),
  785. cidToData = this.clientIdToData,
  786. adapter = this.adapterForType(record.constructor),
  787. data = cidToData[clientId];
  788. cidToData[clientId] = MATERIALIZED;
  789. var prematerialized = this.clientIdToPrematerializedData[clientId];
  790. // Ensures the record's data structures are setup
  791. // before being populated by the adapter.
  792. record.setupData();
  793. if (data !== CREATED) {
  794. // Instructs the adapter to extract information from the
  795. // opaque data and materialize the record's attributes and
  796. // relationships.
  797. adapter.materialize(record, data, prematerialized);
  798. }
  799. },
  800. /**
  801. @private
  802. Returns true if there is already a record for this clientId.
  803. This is used to determine whether cleanup is required, so that
  804. "changes" to unmaterialized records do not trigger mass
  805. materialization.
  806. For example, if a parent record in a relationship with a large
  807. number of children is deleted, we want to avoid materializing
  808. those children.
  809. @param {String|Number} clientId
  810. @return {Boolean}
  811. */
  812. recordIsMaterialized: function(clientId) {
  813. return !!this.recordCache[clientId];
  814. },
  815. /**
  816. The adapter to use to communicate to a backend server or other persistence layer.
  817. This can be specified as an instance, a class, or a property path that specifies
  818. where the adapter can be located.
  819. @property {DS.Adapter|String}
  820. */
  821. adapter: 'DS.RESTAdapter',
  822. /**
  823. @private
  824. Returns a JSON representation of the record using the adapter's
  825. serialization strategy. This method exists primarily to enable
  826. a record, which has access to its store (but not the store's
  827. adapter) to provide a `serialize()` convenience.
  828. The available options are:
  829. * `includeId`: `true` if the record's ID should be included in
  830. the JSON representation
  831. @param {DS.Model} record the record to serialize
  832. @param {Object} options an options hash
  833. */
  834. serialize: function(record, options) {
  835. return this.adapterForType(record.constructor).serialize(record, options);
  836. },
  837. /**
  838. @private
  839. This property returns the adapter, after resolving a possible
  840. property path.
  841. If the supplied `adapter` was a class, or a String property
  842. path resolved to a class, this property will instantiate the
  843. class.
  844. This property is cacheable, so the same instance of a specified
  845. adapter class should be used for the lifetime of the store.
  846. @returns DS.Adapter
  847. */
  848. _adapter: Ember.computed(function() {
  849. var adapter = get(this, 'adapter');
  850. if (typeof adapter === 'string') {
  851. adapter = get(this, adapter, false) || get(Ember.lookup, adapter);
  852. }
  853. if (DS.Adapter.detect(adapter)) {
  854. adapter = adapter.create();
  855. }
  856. return adapter;
  857. }).property('adapter'),
  858. /**
  859. @private
  860. A monotonically increasing number to be used to uniquely identify
  861. data and records.
  862. It starts at 1 so other parts of the code can test for truthiness
  863. when provided a `clientId` instead of having to explicitly test
  864. for undefined.
  865. */
  866. clientIdCounter: 1,
  867. // .....................
  868. // . CREATE NEW RECORD .
  869. // .....................
  870. /**
  871. Create a new record in the current store. The properties passed
  872. to this method are set on the newly created record.
  873. Note: The third `transaction` property is for internal use only.
  874. If you want to create a record inside of a given transaction,
  875. use `transaction.createRecord()` instead of `store.createRecord()`.
  876. @param {subclass of DS.Model} type
  877. @param {Object} properties a hash of properties to set on the
  878. newly created record.
  879. @returns DS.Model
  880. */
  881. createRecord: function(type, properties, transaction) {
  882. properties = properties || {};
  883. // Create a new instance of the model `type` and put it
  884. // into the specified `transaction`. If no transaction is
  885. // specified, the default transaction will be used.
  886. var record = type._create({
  887. store: this
  888. });
  889. transaction = transaction || get(this, 'defaultTransaction');
  890. // adoptRecord is an internal API that allows records to move
  891. // into a transaction without assertions designed for app
  892. // code. It is used here to ensure that regardless of new
  893. // restrictions on the use of the public `transaction.add()`
  894. // API, we will always be able to insert new records into
  895. // their transaction.
  896. transaction.adoptRecord(record);
  897. // `id` is a special property that may not be a `DS.attr`
  898. var id = properties.id;
  899. // If the passed properties do not include a primary key,
  900. // give the adapter an opportunity to generate one. Typically,
  901. // client-side ID generators will use something like uuid.js
  902. // to avoid conflicts.
  903. var adapter;
  904. if (Ember.isNone(id)) {
  905. adapter = get(this, 'adapter');
  906. if (adapter && adapter.generateIdForRecord) {
  907. id = coerceId(adapter.generateIdForRecord(this, record));
  908. properties.id = id;
  909. }
  910. }
  911. id = coerceId(id);
  912. // Create a new `clientId` and associate it with the
  913. // specified (or generated) `id`. Since we don't have
  914. // any data for the server yet (by definition), store
  915. // the sentinel value CREATED as the data for this
  916. // clientId. If we see this value later, we will skip
  917. // materialization.
  918. var clientId = this.pushData(CREATED, id, type);
  919. // Now that we have a clientId, attach it to the record we
  920. // just created.
  921. set(record, 'clientId', clientId);
  922. // Move the record out of its initial `empty` state into
  923. // the `loaded` state.
  924. record.loadedData();
  925. // Make sure the data is set up so the record doesn't
  926. // try to materialize its nonexistent data.
  927. record.setupData();
  928. // Store the record we just created in the record cache for
  929. // this clientId.
  930. this.recordCache[clientId] = record;
  931. // Set the properties specified on the record.
  932. record.setProperties(properties);
  933. // Resolve record promise
  934. Ember.run(record, 'resolve', record);
  935. return record;
  936. },
  937. // .................
  938. // . DELETE RECORD .
  939. // .................
  940. /**
  941. For symmetry, a record can be deleted via the store.
  942. @param {DS.Model} record
  943. */
  944. deleteRecord: function(record) {
  945. record.deleteRecord();
  946. },
  947. /**
  948. For symmetry, a record can be unloaded via the store.
  949. @param {DS.Model} record
  950. */
  951. unloadRecord: function(record) {
  952. record.unloadRecord();
  953. },
  954. // ................
  955. // . FIND RECORDS .
  956. // ................
  957. /**
  958. This is the main entry point into finding records. The first parameter to
  959. this method is always a subclass of `DS.Model`.
  960. You can use the `find` method on a subclass of `DS.Model` directly if your
  961. application only has one store. For example, instead of
  962. `store.find(App.Person, 1)`, you could say `App.Person.find(1)`.
  963. ---
  964. To find a record by ID, pass the `id` as the second parameter:
  965. store.find(App.Person, 1);
  966. App.Person.find(1);
  967. If the record with that `id` had not previously been loaded, the store will
  968. return an empty record immediately and ask the adapter to find the data by
  969. calling the adapter's `find` method.
  970. The `find` method will always return the same object for a given type and
  971. `id`. To check whether the adapter has populated a record, you can check
  972. its `isLoaded` property.
  973. ---
  974. To find all records for a type, call `find` with no additional parameters:
  975. store.find(App.Person);
  976. App.Person.find();
  977. This will return a `RecordArray` representing all known records for the
  978. given type and kick off a request to the adapter's `findAll` method to load
  979. any additional records for the type.
  980. The `RecordArray` returned by `find()` is live. If any more records for the
  981. type are added at a later time through any mechanism, it will automatically
  982. update to reflect the change.
  983. ---
  984. To find a record by a query, call `find` with a hash as the second
  985. parameter:
  986. store.find(App.Person, { page: 1 });
  987. App.Person.find({ page: 1 });
  988. This will return a `RecordArray` immediately, but it will always be an
  989. empty `RecordArray` at first. It will call the adapter's `findQuery`
  990. method, which will populate the `RecordArray` once the server has returned
  991. results.
  992. You can check whether a query results `RecordArray` has loaded by checking
  993. its `isLoaded` property.
  994. */
  995. find: function(type, id) {
  996. if (id === undefined) {
  997. return this.findAll(type);
  998. }
  999. // We are passed a query instead of an id.
  1000. if (Ember.typeOf(id) === 'object') {
  1001. return this.findQuery(type, id);
  1002. }
  1003. return this.findById(type, coerceId(id));
  1004. },
  1005. /**
  1006. @private
  1007. This method returns a record for a given type and id combination.
  1008. If the store has never seen this combination of type and id before, it
  1009. creates a new `clientId` with the LOADING sentinel and asks the adapter to
  1010. load the data.
  1011. If the store has seen the combination, this method delegates to
  1012. `findByClientId`.
  1013. */
  1014. findById: function(type, id) {
  1015. var clientId = this.typeMapFor(type).idToCid[id];
  1016. if (clientId) {
  1017. return this.findByClientId(type, clientId);
  1018. }
  1019. clientId = this.pushData(LOADING, id, type);
  1020. // create a new instance of the model type in the
  1021. // 'isLoading' state
  1022. var record = this.materializeRecord(type, clientId, id);
  1023. // let the adapter set the data, possibly async
  1024. var adapter = this.adapterForType(type);
  1025. if (adapter && adapter.find) { adapter.find(this, type, id); }
  1026. else { throw "Adapter is either null or does not implement `find` method"; }
  1027. return record;
  1028. },
  1029. reloadRecord: function(record) {
  1030. var type = record.constructor,
  1031. adapter = this.adapterForType(type),
  1032. id = get(record, 'id');
  1033. Ember.assert("You cannot update a record without an ID", id);
  1034. Ember.assert("You tried to update a record but you have no adapter (for " + type + ")", adapter);
  1035. Ember.assert("You tried to update a record but your adapter does not implement `find`", adapter.find);
  1036. adapter.find(this, type, id);
  1037. },
  1038. /**
  1039. @private
  1040. This method returns a record for a given clientId.
  1041. If there is no record object yet for the clientId, this method materializes
  1042. a new record object. This allows adapters to eagerly load large amounts of
  1043. data into the store, and avoid incurring the cost to create the objects
  1044. until they are requested.
  1045. Several parts of Ember Data call this method:
  1046. * findById, if a clientId already exists for a given type and
  1047. id combination
  1048. * OneToManyChange, which is backed by clientIds, when getChild,
  1049. getOldParent or getNewParent are called
  1050. * RecordArray, which is backed by clientIds, when an object at
  1051. a particular index is looked up
  1052. In short, it's a convenient way to get a record for a known
  1053. clientId, materializing it if necessary.
  1054. @param {Class} type
  1055. @param {Number|String} clientId
  1056. */
  1057. findByClientId: function(type, clientId) {
  1058. var cidToData, record, id;
  1059. record = this.recordCache[clientId];
  1060. if (!record) {
  1061. // create a new instance of the model type in the
  1062. // 'isLoading' state
  1063. id = this.clientIdToId[clientId];
  1064. record = this.materializeRecord(type, clientId, id);
  1065. cidToData = this.clientIdToData;
  1066. if (typeof cidToData[clientId] === 'object') {
  1067. record.loadedData();
  1068. }
  1069. }
  1070. return record;
  1071. },
  1072. /**
  1073. @private
  1074. Given a type and array of `clientId`s, determines which of those
  1075. `clientId`s has not yet been loaded.
  1076. In preparation for loading, this method also marks any unloaded
  1077. `clientId`s as loading.
  1078. */
  1079. neededReferences: function(type, references) {
  1080. var neededReferences = [],
  1081. cidToData = this.clientIdToData,
  1082. reference;
  1083. for (var i=0, l=references.length; i<l; i++) {
  1084. reference = references[i];
  1085. if (cidToData[reference.clientId] === UNLOADED) {
  1086. neededReferences.push(reference);
  1087. cidToData[reference.clientId] = LOADING;
  1088. }
  1089. }
  1090. return neededReferences;
  1091. },
  1092. /**
  1093. @private
  1094. This method is the entry point that relationships use to update
  1095. themselves when their underlying data changes.
  1096. First, it determines which of its `clientId`s are still unloaded,
  1097. then converts the needed `clientId`s to IDs and invokes `findMany`
  1098. on the adapter.
  1099. */
  1100. fetchUnloadedReferences: function(type, references, owner) {
  1101. var neededReferences = this.neededReferences(type, references);
  1102. this.fetchMany(type, neededReferences, owner);
  1103. },
  1104. /**
  1105. @private
  1106. This method takes a type and list of `clientId`s, converts the
  1107. `clientId`s into IDs, and then invokes the adapter's `findMany`
  1108. method.
  1109. It is used both by a brand new relationship (via the `findMany`
  1110. method) or when the data underlying an existing relationship
  1111. changes (via the `fetchUnloadedReferences` method).
  1112. */
  1113. fetchMany: function(type, references, owner) {
  1114. if (!references.length) { return; }
  1115. var ids = map(references, function(reference) {
  1116. return reference.id;
  1117. });
  1118. var adapter = this.adapterForType(type);
  1119. if (adapter && adapter.findMany) { adapter.findMany(this, type, ids, owner); }
  1120. else { throw "Adapter is either null or does not implement `findMany` method"; }
  1121. },
  1122. referenceForId: function(type, id) {
  1123. var clientId = this.clientIdForId(type, id);
  1124. return this.referenceForClientId(clientId);
  1125. },
  1126. referenceForClientId: function(clientId) {
  1127. var references = this.recordReferences;
  1128. if (references[clientId]) {
  1129. return references[clientId];
  1130. }
  1131. var type = this.clientIdToType[clientId];
  1132. return references[clientId] = {
  1133. id: this.idForClientId(clientId),
  1134. clientId: clientId,
  1135. type: type
  1136. };
  1137. },
  1138. recordForReference: function(reference) {
  1139. return this.findByClientId(reference.type, reference.clientId);
  1140. },
  1141. /**
  1142. @private
  1143. `findMany` is the entry point that relationships use to generate a
  1144. new `ManyArray` for the list of IDs specified by the server for
  1145. the relationship.
  1146. Its responsibilities are:
  1147. * convert the IDs into clientIds
  1148. * determine which of the clientIds still need to be loaded
  1149. * create a new ManyArray whose content is *all* of the clientIds
  1150. * notify the ManyArray of the number of its elements that are
  1151. already loaded
  1152. * insert the unloaded clientIds into the `loadingRecordArrays`
  1153. bookkeeping structure, which will allow the `ManyArray` to know
  1154. when all of its loading elements are loaded from the server.
  1155. * ask the adapter to load the unloaded elements, by invoking
  1156. findMany with the still-unloaded IDs.
  1157. */
  1158. findMany: function(type, ids, record, relationship) {
  1159. // 1. Convert ids to client ids
  1160. // 2. Determine which of the client ids need to be loaded
  1161. // 3. Create a new ManyArray whose content is ALL of the clientIds
  1162. // 4. Decrement the ManyArray's counter by the number of loaded clientIds
  1163. // 5. Put the ManyArray into our bookkeeping data structure, keyed on
  1164. // the needed clientIds
  1165. // 6. Ask the adapter to load the records for the unloaded clientIds (but
  1166. // convert them back to ids)
  1167. if (!Ember.isArray(ids)) {
  1168. var adapter = this.adapterForType(type);
  1169. if (adapter && adapter.findHasMany) { adapter.findHasMany(this, record, relationship, ids); }
  1170. else { throw fmt("Adapter is either null or does not implement `findMany` method", this); }
  1171. return this.createManyArray(type, Ember.A());
  1172. }
  1173. // Coerce server IDs into Record Reference
  1174. var references = map(ids, function(reference) {
  1175. if (typeof reference !== 'object' && reference !== null) {
  1176. return this.referenceForId(type, reference);
  1177. }
  1178. return reference;
  1179. }, this);
  1180. var neededReferences = this.neededReferences(type, references),
  1181. manyArray = this.createManyArray(type, Ember.A(references)),
  1182. loadingRecordArrays = this.loadingRecordArrays,
  1183. reference, clientId, i, l;
  1184. // Start the decrementing counter on the ManyArray at the number of
  1185. // records we need to load from the adapter
  1186. manyArray.loadingRecordsCount(neededReferences.length);
  1187. if (neededReferences.length) {
  1188. for (i=0, l=neededReferences.length; i<l; i++) {
  1189. reference = neededReferences[i];
  1190. clientId = reference.clientId;
  1191. // keep track of the record arrays that a given loading record
  1192. // is part of. This way, if the same record is in multiple
  1193. // ManyArrays, all of their loading records counters will be
  1194. // decremented when the adapter provides the data.
  1195. if (loadingRecordArrays[clientId]) {
  1196. loadingRecordArrays[clientId].push(manyArray);
  1197. } else {
  1198. this.loadingRecordArrays[clientId] = [ manyArray ];
  1199. }
  1200. }
  1201. this.fetchMany(type, neededReferences, record);
  1202. } else {
  1203. // all requested records are available
  1204. manyArray.set('isLoaded', true);
  1205. Ember.run.once(function() {
  1206. manyArray.trigger('didLoad');
  1207. });
  1208. }
  1209. return manyArray;
  1210. },
  1211. /**
  1212. @private
  1213. This method delegates a query to the adapter. This is the one place where
  1214. adapter-level semantics are exposed to the application.
  1215. Exposing queries this way seems preferable to creating an abstract query
  1216. language for all server-side queries, and then require all adapters to
  1217. implement them.
  1218. @param {Class} type
  1219. @param {Object} query an opaque query to be used by the adapter
  1220. @return {DS.AdapterPopulatedRecordArray}
  1221. */
  1222. findQuery: function(type, query) {
  1223. var array = DS.AdapterPopulatedRecordArray.create({ type: type, query: query, content: Ember.A([]), store: this });
  1224. var adapter = this.adapterForType(type);
  1225. if (adapter && adapter.findQuery) { adapter.findQuery(this, type, query, array); }
  1226. else { throw "Adapter is either null or does not implement `findQuery` method"; }
  1227. return array;
  1228. },
  1229. /**
  1230. @private
  1231. This method returns an array of all records adapter can find.
  1232. It triggers the adapter's `findAll` method to give it an opportunity to populate
  1233. the array with records of that type.
  1234. @param {Class} type
  1235. @return {DS.AdapterPopulatedRecordArray}
  1236. */
  1237. findAll: function(type) {
  1238. var array = this.all(type);
  1239. this.fetchAll(type, array);
  1240. return array;
  1241. },
  1242. /**
  1243. @private
  1244. */
  1245. fetchAll: function(type, array) {
  1246. var sinceToken = this.typeMapFor(type).sinceToken,
  1247. adapter = this.adapterForType(type);
  1248. set(array, 'isUpdating', true);
  1249. if (adapter && adapter.findAll) { adapter.findAll(this, type, sinceToken); }
  1250. else { throw "Adapter is either null or does not implement `findAll` method"; }
  1251. },
  1252. /**
  1253. */
  1254. sinceForType: function(type, sinceToken) {
  1255. this.typeMapFor(type).sinceToken = sinceToken;
  1256. },
  1257. /**
  1258. */
  1259. didUpdateAll: function(type) {
  1260. var findAllCache = this.typeMapFor(type).findAllCache;
  1261. set(findAllCache, 'isUpdating', false);
  1262. },
  1263. /**
  1264. This method returns a filtered array that contains all of the known records
  1265. for a given type.
  1266. Note that because it's just a filter, it will have any locally
  1267. created records of the type.
  1268. Also note that multiple calls to `all` for a given type will always
  1269. return the same RecordArray.
  1270. @param {Class} type
  1271. @return {DS.RecordArray}
  1272. */
  1273. all: function(type) {
  1274. var typeMap = this.typeMapFor(type),
  1275. findAllCache = typeMap.findAllCache;
  1276. if (findAllCache) { return findAllCache; }
  1277. var array = DS.RecordArray.create({ type: type, content: Ember.A([]), store: this, isLoaded: true });
  1278. this.registerRecordArray(array, type);
  1279. typeMap.findAllCache = array;
  1280. return array;
  1281. },
  1282. /**
  1283. Takes a type and filter function, and returns a live RecordArray that
  1284. remains up to date as new records are loaded into the store or created
  1285. locally.
  1286. The callback function takes a materialized record, and returns true
  1287. if the record should be included in the filter and false if it should
  1288. not.
  1289. The filter function is called once on all records for the type when
  1290. it is created, and then once on each newly loaded or created record.
  1291. If any of a record's properties change, or if it changes state, the
  1292. filter function will be invoked again to determine whether it should
  1293. still be in the array.
  1294. Note that the existence of a filter on a type will trigger immediate
  1295. materialization of all loaded data for a given type, so you might
  1296. not want to use filters for a type if you are loading many records
  1297. into the store, many of which are not active at any given time.
  1298. In this scenario, you might want to consider filtering the raw
  1299. data before loading it into the store.
  1300. @param {Class} type
  1301. @param {Function} filter
  1302. @return {DS.FilteredRecordArray}
  1303. */
  1304. filter: function(type, query, filter) {
  1305. // allow an optional server query
  1306. if (arguments.length === 3) {
  1307. this.findQuery(type, query);
  1308. } else if (arguments.length === 2) {
  1309. filter = query;
  1310. }
  1311. var array = DS.FilteredRecordArray.create({ type: type, content: Ember.A([]), store: this, filterFunction: filter });
  1312. this.registerRecordArray(array, type, filter);
  1313. return array;
  1314. },
  1315. /**
  1316. This method returns if a certain record is already loaded
  1317. in the store. Use this function to know beforehand if a find()
  1318. will result in a request or that it will be a cache hit.
  1319. @param {Class} type
  1320. @param {string} id
  1321. @return {boolean}
  1322. */
  1323. recordIsLoaded: function(type, id) {
  1324. return !Ember.isNone(this.typeMapFor(type).idToCid[id]);
  1325. },
  1326. // ............
  1327. // . UPDATING .
  1328. // ............
  1329. /**
  1330. @private
  1331. If the adapter updates attributes or acknowledges creation
  1332. or deletion, the record will notify the store to update its
  1333. membership in any filters.
  1334. To avoid thrashing, this method is invoked only once per
  1335. run loop per record.
  1336. @param {Class} type
  1337. @param {Number|String} clientId
  1338. @param {DS.Model} record
  1339. */
  1340. dataWasUpdated: function(type, clientId, record) {
  1341. // Because data updates are invoked at the end of the run loop,
  1342. // it is possible that a record might be deleted after its data
  1343. // has been modified and this method was scheduled to be called.
  1344. //
  1345. // If that's the case, the record would have already been removed
  1346. // from all record arrays; calling updateRecordArrays would just
  1347. // add it back. If the record is deleted, just bail. It shouldn't
  1348. // give us any more trouble after this.
  1349. if (get(record, 'isDeleted')) { return; }
  1350. var cidToData = this.clientIdToData,
  1351. data = cidToData[clientId];
  1352. if (typeof data === "object") {
  1353. this.updateRecordArrays(type, clientId);
  1354. }
  1355. },
  1356. // ..............
  1357. // . PERSISTING .
  1358. // ..............
  1359. /**
  1360. This method delegates committing to the store's implicit
  1361. transaction.
  1362. Calling this method is essentially a request to persist
  1363. any changes to records that were not explicitly added to
  1364. a transaction.
  1365. */
  1366. commit: function() {
  1367. get(this, 'defaultTransaction').commit();
  1368. },
  1369. /**
  1370. Adapters should call this method if they would like to acknowledge
  1371. that all changes related to a record (other than relationship
  1372. changes) have persisted.
  1373. Because relationship changes affect multiple records, the adapter
  1374. is responsible for acknowledging the change to the relationship
  1375. directly (using `store.didUpdateRelationship`) when all aspects
  1376. of the relationship change have persisted.
  1377. It can be called for created, deleted or updated records.
  1378. If the adapter supplies new data, that data will become the new
  1379. canonical data for the record. That will result in blowing away
  1380. all local changes and rematerializing the record with the new
  1381. data (the "sledgehammer" approach).
  1382. Alternatively, if the adapter does not supply new data, the record
  1383. will collapse all local changes into its saved data. Subsequent
  1384. rollbacks of the record will roll back to this point.
  1385. If an adapter is acknowledging receipt of a newly created record
  1386. that did not generate an id in the client, it *must* either
  1387. provide data or explicitly invoke `store.didReceiveId` with
  1388. the server-provided id.
  1389. Note that an adapter may not supply new data when acknowledging
  1390. a deleted record.
  1391. @see DS.Store#didUpdateRelationship
  1392. @param {DS.Model} record the in-flight record
  1393. @param {Object} data optional data (see above)
  1394. */
  1395. didSaveRecord: function(record, data) {
  1396. record.adapterDidCommit();
  1397. if (data) {
  1398. this.updateId(record, data);
  1399. this.updateRecordData(record, data);
  1400. } else {
  1401. this.didUpdateAttributes(record);
  1402. }
  1403. },
  1404. /**
  1405. For convenience, if an adapter is performing a bulk commit, it can also
  1406. acknowledge all of the records at once.
  1407. If the adapter supplies an array of data, they must be in the same order as
  1408. the array of records passed in as the first parameter.
  1409. @param {#forEach} list a list of records whose changes the
  1410. adapter is acknowledging. You can pass any object that
  1411. has an ES5-like `forEach` method, including the
  1412. `OrderedSet` objects passed into the adapter at commit
  1413. time.
  1414. @param {Array[Object]} dataList an Array of data. This
  1415. parameter must be an integer-indexed Array-like.
  1416. */
  1417. didSaveRecords: function(list, dataList) {
  1418. var i = 0;
  1419. list.forEach(function(record) {
  1420. this.didSaveRecord(record, dataList && dataList[i++]);
  1421. }, this);
  1422. },
  1423. /**
  1424. This method allows the adapter to specify that a record
  1425. could not be saved because it had backend-supplied validation
  1426. errors.
  1427. The errors object must have keys that correspond to the
  1428. attribute names. Once each of the specified attributes have
  1429. changed, the record will automatically move out of the
  1430. invalid state and be ready to commit again.
  1431. TODO: We should probably automate the process of converting
  1432. server names to attribute names using the existing serializer
  1433. infrastructure.
  1434. @param {DS.Model} record
  1435. @param {Object} errors
  1436. */
  1437. recordWasInvalid: function(record, errors) {
  1438. record.adapterDidInvalidate(errors);
  1439. },
  1440. /**
  1441. This method allows the adapter to specify that a record
  1442. could not be saved because the server returned an unhandled
  1443. error.
  1444. @param {DS.Model} record
  1445. */
  1446. recordWasError: function(record) {
  1447. record.adapterDidError();
  1448. },
  1449. /**
  1450. This is a lower-level API than `didSaveRecord` that allows an
  1451. adapter to acknowledge the persistence of a single attribute.
  1452. This is useful if an adapter needs to make multiple asynchronous
  1453. calls to fully persist a record. The record will keep track of
  1454. which attributes and relationships are still outstanding and
  1455. automatically move into the `saved` state once the adapter has
  1456. acknowledged everything.
  1457. If a value is provided, it clobbers the locally specified value.
  1458. Otherwise, the local value becomes the record's last known
  1459. saved value (which is used when rolling back a record).
  1460. Note that the specified attributeName is the normalized name
  1461. specified in the definition of the `DS.Model`, not a key in
  1462. the server-provided data.
  1463. Also note that the adapter is responsible for performing any
  1464. transformations on the value using the serializer API.
  1465. @param {DS.Model} record
  1466. @param {String} attributeName
  1467. @param {Object} value
  1468. */
  1469. didUpdateAttribute: function(record, attributeName, value) {
  1470. record.adapterDidUpdateAttribute(attributeName, value);
  1471. },
  1472. /**
  1473. This method allows an adapter to acknowledge persistence
  1474. of all attributes of a record but not relationships or
  1475. other factors.
  1476. It loops through the record's defined attributes and
  1477. notifies the record that they are all acknowledged.
  1478. This method does not take optional values, because
  1479. the adapter is unlikely to have a hash of normalized
  1480. keys and transformed values, and instead of building
  1481. one up, it should just call `didUpdateAttribute` as
  1482. needed.
  1483. This method is intended as a middle-ground between
  1484. `didSaveRecord`, which acknowledges all changes to
  1485. a record, and `didUpdateAttribute`, which allows an
  1486. adapter fine-grained control over updates.
  1487. @param {DS.Model} record
  1488. */
  1489. didUpdateAttributes: function(record) {
  1490. record.eachAttribute(function(attributeName) {
  1491. this.didUpdateAttribute(record, attributeName);
  1492. }, this);
  1493. },
  1494. /**
  1495. This allows an adapter to acknowledge that it has saved all
  1496. necessary aspects of a relationship change.
  1497. This is separated from acknowledging the record itself
  1498. (via `didSaveRecord`) because a relationship change can
  1499. involve as many as three separate records. Records should
  1500. only move out of the in-flight state once the server has
  1501. acknowledged all of their relationships, and this differs
  1502. based upon the adapter's semantics.
  1503. There are three basic scenarios by which an adapter can
  1504. save a relationship.
  1505. ### Foreign Key
  1506. An adapter can save all relationship changes by updating
  1507. a foreign key on the child record. If it does this, it
  1508. should acknowledge the changes when the child record is
  1509. saved.
  1510. record.eachRelationship(function(name, meta) {
  1511. if (meta.kind === 'belongsTo') {
  1512. store.didUpdateRelationship(record, name);
  1513. }
  1514. });
  1515. store.didSaveRecord(record, data);
  1516. ### Embedded in Parent
  1517. An adapter can save one-to-many relationships by embedding
  1518. IDs (or records) in the parent object. In this case, the
  1519. relationship is not considered acknowledged until both the
  1520. old parent and new parent have acknowledged the change.
  1521. In this case, the adapter should keep track of the old
  1522. parent and new parent, and acknowledge the relationship
  1523. change once both have acknowledged. If one of the two
  1524. sides does not exist (e.g. the new parent does not exist
  1525. because of nulling out the belongs-to relationship),
  1526. the adapter should acknowledge the relationship once
  1527. the other side has acknowledged.
  1528. ### Separate Entity
  1529. An adapter can save relationships as separate entities
  1530. on the server. In this case, they should acknowledge
  1531. the relationship as saved once the server has
  1532. acknowledged the entity.
  1533. @see DS.Store#didSaveRecord
  1534. @param {DS.Model} record
  1535. @param {DS.Model} relationshipName
  1536. */
  1537. didUpdateRelationship: function(record, relationshipName) {
  1538. var relationship = this.relationshipChangeFor(get(record, 'clientId'), relationshipName);
  1539. //TODO(Igor)
  1540. if (relationship) { relationship.adapterDidUpdate(); }
  1541. },
  1542. /**
  1543. This allows an adapter to acknowledge all relationship changes
  1544. for a given record.
  1545. Like `didUpdateAttributes`, this is intended as a middle ground
  1546. between `didSaveRecord` and fine-grained control via the
  1547. `didUpdateRelationship` API.
  1548. */
  1549. didUpdateRelationships: function(record) {
  1550. var changes = this.relationshipChangesFor(get(record, 'clientId'));
  1551. for (var name in changes) {
  1552. if (!changes.hasOwnProperty(name)) { continue; }
  1553. changes[name].adapterDidUpdate();
  1554. }
  1555. },
  1556. /**
  1557. When acknowledging the creation of a locally created record,
  1558. adapters must supply an id (if they did not implement
  1559. `generateIdForRecord` to generate an id locally).
  1560. If an adapter does not use `didSaveRecord` and supply a hash
  1561. (for example, if it needs to make multiple HTTP requests to
  1562. create and then update the record), it will need to invoke
  1563. `didReceiveId` with the backend-supplied id.
  1564. When not using `didSaveRecord`, an adapter will need to
  1565. invoke:
  1566. * didReceiveId (unless the id was generated locally)
  1567. * didCreateRecord
  1568. * didUpdateAttribute(s)
  1569. * didUpdateRelationship(s)
  1570. @param {DS.Model} record
  1571. @param {Number|String} id
  1572. */
  1573. didReceiveId: function(record, id) {
  1574. var typeMap = this.typeMapFor(record.constructor),
  1575. clientId = get(record, 'clientId'),
  1576. oldId = get(record, 'id');
  1577. Ember.assert("An adapter cannot assign a new id to a record that already has an id. " + record + " had id: " + oldId + " and you tried to update it with " + id + ". This likely happened because your server returned data in response to a find or update that had a different id than the one you sent.", oldId === undefined || id === oldId);
  1578. typeMap.idToCid[id] = clientId;
  1579. this.clientIdToId[clientId] = id;
  1580. },
  1581. /**
  1582. @private
  1583. This method re-indexes the data by its clientId in the store
  1584. and then notifies the record that it should rematerialize
  1585. itself.
  1586. @param {DS.Model} record
  1587. @param {Object} data
  1588. */
  1589. updateRecordData: function(record, data) {
  1590. var clientId = get(record, 'clientId'),
  1591. cidToData = this.clientIdToData;
  1592. cidToData[clientId] = data;
  1593. record.didChangeData();
  1594. },
  1595. /**
  1596. @private
  1597. If an adapter invokes `didSaveRecord` with data, this method
  1598. extracts the id from the supplied data (using the adapter's
  1599. `extractId()` method) and indexes the clientId with that id.
  1600. @param {DS.Model} record
  1601. @param {Object} data
  1602. */
  1603. updateId: function(record, data) {
  1604. var typeMap = this.typeMapFor(record.constructor),
  1605. clientId = get(record, 'clientId'),
  1606. oldId = get(record, 'id'),
  1607. type = record.constructor,
  1608. id = this.preprocessData(type, data);
  1609. Ember.assert("An adapter cannot assign a new id to a record that already has an id. " + record + " had id: " + oldId + " and you tried to update it with " + id + ". This likely happened because your server returned data in response to a find or update that had a different id than the one you sent.", oldId === null || id === oldId);
  1610. typeMap.idToCid[id] = clientId;
  1611. this.clientIdToId[clientId] = id;
  1612. this.referenceForClientId(clientId).id = id;
  1613. },
  1614. /**
  1615. @private
  1616. This method receives opaque data provided by the adapter and
  1617. preprocesses it, returning an ID.
  1618. The actual preprocessing takes place in the adapter. If you would
  1619. like to change the default behavior, you should override the
  1620. appropriate hooks in `DS.Serializer`.
  1621. @see {DS.Serializer}
  1622. @return {String} id the id represented by the data
  1623. */
  1624. preprocessData: function(type, data) {
  1625. return this.adapterForType(type).extractId(type, data);
  1626. },
  1627. // .................
  1628. // . RECORD ARRAYS .
  1629. // .................
  1630. /**
  1631. @private
  1632. Register a RecordArray for a given type to be backed by
  1633. a filter function. This will cause the array to update
  1634. automatically when records of that type change attribute
  1635. values or states.
  1636. @param {DS.RecordArray} array
  1637. @param {Class} type
  1638. @param {Function} filter
  1639. */
  1640. registerRecordArray: function(array, type, filter) {
  1641. var recordArrays = this.typeMapFor(type).recordArrays;
  1642. recordArrays.push(array);
  1643. this.updateRecordArrayFilter(array, type, filter);
  1644. },
  1645. /**
  1646. @private
  1647. Create a `DS.ManyArray` for a type and list of clientIds
  1648. and index the `ManyArray` under each clientId. This allows
  1649. us to efficiently remove records from `ManyArray`s when
  1650. they are deleted.
  1651. @param {Class} type
  1652. @param {Array} clientIds
  1653. @return {DS.ManyArray}
  1654. */
  1655. createManyArray: function(type, clientIds) {
  1656. var array = DS.ManyArray.create({ type: type, content: clientIds, store: this });
  1657. clientIds.forEach(function(clientId) {
  1658. var recordArrays = this.recordArraysForClientId(clientId);
  1659. recordArrays.add(array);
  1660. }, this);
  1661. return array;
  1662. },
  1663. /**
  1664. @private
  1665. This method is invoked if the `filterFunction` property is
  1666. changed on a `DS.FilteredRecordArray`.
  1667. It essentially re-runs the filter from scratch. This same
  1668. method is invoked when the filter is created in th first place.
  1669. */
  1670. updateRecordArrayFilter: function(array, type, filter) {
  1671. var typeMap = this.typeMapFor(type),
  1672. cidToData = this.clientIdToData,
  1673. clientIds = typeMap.clientIds,
  1674. clientId, data, shouldFilter, record;
  1675. for (var i=0, l=clientIds.length; i<l; i++) {
  1676. clientId = clientIds[i];
  1677. shouldFilter = false;
  1678. data = cidToData[clientId];
  1679. if (typeof data === 'object') {
  1680. if (record = this.recordCache[clientId]) {
  1681. if (!get(record, 'isDeleted')) { shouldFilter = true; }
  1682. } else {
  1683. shouldFilter = true;
  1684. }
  1685. if (shouldFilter) {
  1686. this.updateRecordArray(array, filter, type, clientId);
  1687. }
  1688. }
  1689. }
  1690. },
  1691. updateRecordArraysLater: function(type, clientId) {
  1692. Ember.run.once(this, function() {
  1693. this.updateRecordArrays(type, clientId);
  1694. });
  1695. },
  1696. /**
  1697. @private
  1698. This method is invoked whenever data is loaded into the store
  1699. by the adapter or updated by the adapter, or when an attribute
  1700. changes on a record.
  1701. It updates all filters that a record belongs to.
  1702. To avoid thrashing, it only runs once per run loop per record.
  1703. @param {Class} type
  1704. @param {Number|String} clientId
  1705. */
  1706. updateRecordArrays: function(type, clientId) {
  1707. var recordArrays = this.typeMapFor(type).recordArrays,
  1708. filter;
  1709. recordArrays.forEach(function(array) {
  1710. filter = get(array, 'filterFunction');
  1711. this.updateRecordArray(array, filter, type, clientId);
  1712. }, this);
  1713. // loop through all manyArrays containing an unloaded copy of this
  1714. // clientId and notify them that the record was loaded.
  1715. var manyArrays = this.loadingRecordArrays[clientId];
  1716. if (manyArrays) {
  1717. for (var i=0, l=manyArrays.length; i<l; i++) {
  1718. manyArrays[i].loadedRecord();
  1719. }
  1720. this.loadingRecordArrays[clientId] = null;
  1721. }
  1722. },
  1723. /**
  1724. @private
  1725. Update an individual filter.
  1726. @param {DS.FilteredRecordArray} array
  1727. @param {Function} filter
  1728. @param {Class} type
  1729. @param {Number|String} clientId
  1730. */
  1731. updateRecordArray: function(array, filter, type, clientId) {
  1732. var shouldBeInArray, record;
  1733. if (!filter) {
  1734. shouldBeInArray = true;
  1735. } else {
  1736. record = this.findByClientId(type, clientId);
  1737. shouldBeInArray = filter(record);
  1738. }
  1739. var content = get(array, 'content');
  1740. var alreadyInArray = content.indexOf(clientId) !== -1;
  1741. var recordArrays = this.recordArraysForClientId(clientId);
  1742. var reference = this.referenceForClientId(clientId);
  1743. if (shouldBeInArray) {
  1744. recordArrays.add(array);
  1745. array.addReference(reference);
  1746. } else if (!shouldBeInArray) {
  1747. recordArrays.remove(array);
  1748. array.removeReference(reference);
  1749. }
  1750. },
  1751. /**
  1752. @private
  1753. When a record is deleted, it is removed from all its
  1754. record arrays.
  1755. @param {DS.Model} record
  1756. */
  1757. removeFromRecordArrays: function(record) {
  1758. var reference = get(record, '_reference');
  1759. var recordArrays = this.recordArraysForClientId(reference.clientId);
  1760. recordArrays.forEach(function(array) {
  1761. array.removeReference(reference);
  1762. });
  1763. },
  1764. // ............
  1765. // . INDEXING .
  1766. // ............
  1767. /**
  1768. @private
  1769. Return a list of all `DS.RecordArray`s a clientId is
  1770. part of.
  1771. @return {Object(clientId: Ember.OrderedSet)}
  1772. */
  1773. recordArraysForClientId: function(clientId) {
  1774. var recordArrays = get(this, 'recordArraysByClientId');
  1775. var ret = recordArrays[clientId];
  1776. if (!ret) {
  1777. ret = recordArrays[clientId] = Ember.OrderedSet.create();
  1778. }
  1779. return ret;
  1780. },
  1781. typeMapFor: function(type) {
  1782. var typeMaps = get(this, 'typeMaps');
  1783. var guidForType = Ember.guidFor(type);
  1784. var typeMap = typeMaps[guidForType];
  1785. if (typeMap) {
  1786. return typeMap;
  1787. } else {
  1788. return (typeMaps[guidForType] =
  1789. {
  1790. idToCid: {},
  1791. clientIds: [],
  1792. recordArrays: []
  1793. });
  1794. }
  1795. },
  1796. /** @private
  1797. For a given type and id combination, returns the client id used by the store.
  1798. If no client id has been assigned yet, one will be created and returned.
  1799. @param {DS.Model} type
  1800. @param {String|Number} id
  1801. */
  1802. clientIdForId: function(type, id) {
  1803. id = coerceId(id);
  1804. var clientId = this.typeMapFor(type).idToCid[id];
  1805. if (clientId !== undefined) { return clientId; }
  1806. return this.pushData(UNLOADED, id, type);
  1807. },
  1808. /**
  1809. @private
  1810. This method works exactly like `clientIdForId`, but does not
  1811. require looking up the `typeMap` for every `clientId` and
  1812. invoking a method per `clientId`.
  1813. */
  1814. clientIdsForIds: function(type, ids) {
  1815. var typeMap = this.typeMapFor(type),
  1816. idToClientIdMap = typeMap.idToCid;
  1817. return map(ids, function(id) {
  1818. id = coerceId(id);
  1819. var clientId = idToClientIdMap[id];
  1820. if (clientId) { return clientId; }
  1821. return this.pushData(UNLOADED, id, type);
  1822. }, this);
  1823. },
  1824. typeForClientId: function(clientId) {
  1825. return this.clientIdToType[clientId];
  1826. },
  1827. idForClientId: function(clientId) {
  1828. return this.clientIdToId[clientId];
  1829. },
  1830. // ................
  1831. // . LOADING DATA .
  1832. // ................
  1833. /**
  1834. Load new data into the store for a given id and type combination.
  1835. If data for that record had been loaded previously, the new information
  1836. overwrites the old.
  1837. If the record you are loading data for has outstanding changes that have not
  1838. yet been saved, an exception will be thrown.
  1839. @param {DS.Model} type
  1840. @param {String|Number} id
  1841. @param {Object} data the data to load
  1842. */
  1843. load: function(type, data, prematerialized) {
  1844. var id;
  1845. if (typeof data === 'number' || typeof data === 'string') {
  1846. id = data;
  1847. data = prematerialized;
  1848. prematerialized = null;
  1849. }
  1850. if (prematerialized && prematerialized.id) {
  1851. id = prematerialized.id;
  1852. } else if (id === undefined) {
  1853. var adapter = this.adapterForType(type);
  1854. id = this.preprocessData(type, data);
  1855. }
  1856. id = coerceId(id);
  1857. var typeMap = this.typeMapFor(type),
  1858. cidToData = this.clientIdToData,
  1859. clientId = typeMap.idToCid[id],
  1860. cidToPrematerialized = this.clientIdToPrematerializedData;
  1861. if (clientId !== undefined) {
  1862. cidToData[clientId] = data;
  1863. cidToPrematerialized[clientId] = prematerialized;
  1864. var record = this.recordCache[clientId];
  1865. if (record) {
  1866. once(record, 'loadedData');
  1867. }
  1868. } else {
  1869. clientId = this.pushData(data, id, type);
  1870. cidToPrematerialized[clientId] = prematerialized;
  1871. }
  1872. this.updateRecordArraysLater(type, clientId);
  1873. return this.referenceForClientId(clientId);
  1874. },
  1875. prematerialize: function(reference, prematerialized) {
  1876. this.clientIdToPrematerializedData[reference.clientId] = prematerialized;
  1877. },
  1878. loadMany: function(type, ids, dataList) {
  1879. if (dataList === undefined) {
  1880. dataList = ids;
  1881. ids = map(dataList, function(data) {
  1882. return this.preprocessData(type, data);
  1883. }, this);
  1884. }
  1885. return map(ids, function(id, i) {
  1886. return this.load(type, id, dataList[i]);
  1887. }, this);
  1888. },
  1889. loadHasMany: function(record, key, ids) {
  1890. record.materializeHasMany(key, ids);
  1891. // Update any existing many arrays that use the previous IDs,
  1892. // if necessary.
  1893. record.hasManyDidChange(key);
  1894. var relationship = record.cacheFor(key);
  1895. // TODO (tomdale) this assumes that loadHasMany *always* means
  1896. // that the records for the provided IDs are loaded.
  1897. if (relationship) { set(relationship, 'isLoaded', true); }
  1898. },
  1899. /** @private
  1900. Stores data for the specified type and id combination and returns
  1901. the client id.
  1902. @param {Object} data
  1903. @param {String|Number} id
  1904. @param {DS.Model} type
  1905. @returns {Number}
  1906. */
  1907. pushData: function(data, id, type) {
  1908. var typeMap = this.typeMapFor(type);
  1909. var idToClientIdMap = typeMap.idToCid,
  1910. clientIdToIdMap = this.clientIdToId,
  1911. clientIdToTypeMap = this.clientIdToType,
  1912. clientIds = typeMap.clientIds,
  1913. cidToData = this.clientIdToData;
  1914. var clientId = ++this.clientIdCounter;
  1915. cidToData[clientId] = data;
  1916. clientIdToTypeMap[clientId] = type;
  1917. // if we're creating an item, this process will be done
  1918. // later, once the object has been persisted.
  1919. if (id) {
  1920. idToClientIdMap[id] = clientId;
  1921. clientIdToIdMap[clientId] = id;
  1922. }
  1923. clientIds.push(clientId);
  1924. return clientId;
  1925. },
  1926. // ..........................
  1927. // . RECORD MATERIALIZATION .
  1928. // ..........................
  1929. materializeRecord: function(type, clientId, id) {
  1930. var record;
  1931. this.recordCache[clientId] = record = type._create({
  1932. store: this,
  1933. clientId: clientId,
  1934. });
  1935. set(record, 'id', id);
  1936. get(this, 'defaultTransaction').adoptRecord(record);
  1937. record.loadingData();
  1938. return record;
  1939. },
  1940. dematerializeRecord: function(record) {
  1941. var id = get(record, 'id'),
  1942. clientId = get(record, 'clientId'),
  1943. type = this.typeForClientId(clientId),
  1944. typeMap = this.typeMapFor(type);
  1945. record.updateRecordArrays();
  1946. delete this.recordCache[clientId];
  1947. delete this.clientIdToId[clientId];
  1948. delete this.clientIdToType[clientId];
  1949. delete this.clientIdToData[clientId];
  1950. delete this.recordArraysByClientId[clientId];
  1951. if (id) { delete typeMap.idToCid[id]; }
  1952. },
  1953. destroy: function() {
  1954. if (get(DS, 'defaultStore') === this) {
  1955. set(DS, 'defaultStore', null);
  1956. }
  1957. return this._super();
  1958. },
  1959. // ........................
  1960. // . RELATIONSHIP CHANGES .
  1961. // ........................
  1962. addRelationshipChangeFor: function(clientId, childKey, parentClientId, parentKey, change) {
  1963. var key = childKey + parentKey;
  1964. var changes = this.relationshipChanges;
  1965. if (!(clientId in changes)) {
  1966. changes[clientId] = {};
  1967. }
  1968. if (!(parentClientId in changes[clientId])) {
  1969. changes[clientId][parentClientId] = {};
  1970. }
  1971. if (!(key in changes[clientId][parentClientId])) {
  1972. changes[clientId][parentClientId][key] = {};
  1973. }
  1974. changes[clientId][parentClientId][key][change.changeType] = change;
  1975. },
  1976. removeRelationshipChangeFor: function(clientId, childKey, parentClientId, parentKey, type) {
  1977. var changes = this.relationshipChanges;
  1978. var key = childKey + parentKey;
  1979. if (!(clientId in changes) || !(parentClientId in changes[clientId]) || !(key in changes[clientId][parentClientId])){
  1980. return;
  1981. }
  1982. delete changes[clientId][parentClientId][key][type];
  1983. },
  1984. relationshipChangeFor: function(clientId, childKey, parentClientId, parentKey, type) {
  1985. var changes = this.relationshipChanges;
  1986. var key = childKey + parentKey;
  1987. if (!(clientId in changes) || !(parentClientId in changes[clientId])){
  1988. return;
  1989. }
  1990. if(type){
  1991. return changes[clientId][parentClientId][key][type];
  1992. }
  1993. else{
  1994. //TODO(Igor) what if both present
  1995. return changes[clientId][parentClientId][key]["add"] || changes[clientId][parentClientId][key]["remove"];
  1996. }
  1997. },
  1998. relationshipChangePairsFor: function(clientId){
  1999. var toReturn = [];
  2000. //TODO(Igor) What about the other side
  2001. var changesObject = this.relationshipChanges[clientId];
  2002. for (var objKey in changesObject){
  2003. if(changesObject.hasOwnProperty(objKey)){
  2004. for (var changeKey in changesObject[objKey]){
  2005. if(changesObject[objKey].hasOwnProperty(changeKey)){
  2006. toReturn.push(changesObject[objKey][changeKey]);
  2007. }
  2008. }
  2009. }
  2010. }
  2011. return toReturn;
  2012. },
  2013. relationshipChangesFor: function(clientId) {
  2014. var toReturn = [];
  2015. var relationshipPairs = this.relationshipChangePairsFor(clientId);
  2016. forEach(relationshipPairs, function(pair){
  2017. var addedChange = pair["add"];
  2018. var removedChange = pair["remove"];
  2019. if(addedChange){
  2020. toReturn.push(addedChange);
  2021. }
  2022. if(removedChange){
  2023. toReturn.push(removedChange);
  2024. }
  2025. });
  2026. return toReturn;
  2027. },
  2028. // ......................
  2029. // . PER-TYPE ADAPTERS
  2030. // ......................
  2031. adapterForType: function(type) {
  2032. this._adaptersMap = this.createInstanceMapFor('adapters');
  2033. var adapter = this._adaptersMap.get(type);
  2034. if (adapter) { return adapter; }
  2035. return this.get('_adapter');
  2036. },
  2037. // ..............................
  2038. // . RECORD CHANGE NOTIFICATION .
  2039. // ..............................
  2040. recordAttributeDidChange: function(reference, attributeName, newValue, oldValue) {
  2041. var record = this.recordForReference(reference),
  2042. dirtySet = new Ember.OrderedSet(),
  2043. adapter = this.adapterForType(record.constructor);
  2044. if (adapter.dirtyRecordsForAttributeChange) {
  2045. adapter.dirtyRecordsForAttributeChange(dirtySet, record, attributeName, newValue, oldValue);
  2046. }
  2047. dirtySet.forEach(function(record) {
  2048. record.adapterDidDirty();
  2049. });
  2050. },
  2051. recordBelongsToDidChange: function(dirtySet, child, relationship) {
  2052. var adapter = this.adapterForType(child.constructor);
  2053. if (adapter.dirtyRecordsForBelongsToChange) {
  2054. adapter.dirtyRecordsForBelongsToChange(dirtySet, child, relationship);
  2055. }
  2056. // adapterDidDirty is called by the RelationshipChange that created
  2057. // the dirtySet.
  2058. },
  2059. recordHasManyDidChange: function(dirtySet, parent, relationship) {
  2060. var adapter = this.adapterForType(parent.constructor);
  2061. if (adapter.dirtyRecordsForHasManyChange) {
  2062. adapter.dirtyRecordsForHasManyChange(dirtySet, parent, relationship);
  2063. }
  2064. // adapterDidDirty is called by the RelationshipChange that created
  2065. // the dirtySet.
  2066. }
  2067. });
  2068. DS.Store.reopenClass({
  2069. registerAdapter: DS._Mappable.generateMapFunctionFor('adapters', function(type, adapter, map) {
  2070. map.set(type, adapter);
  2071. }),
  2072. transformMapKey: function(key) {
  2073. if (typeof key === 'string') {
  2074. var transformedKey;
  2075. transformedKey = get(Ember.lookup, key);
  2076. Ember.assert("Could not find model at path " + key, transformedKey);
  2077. return transformedKey;
  2078. } else {
  2079. return key;
  2080. }
  2081. },
  2082. transformMapValue: function(key, value) {
  2083. if (Ember.Object.detect(value)) {
  2084. return value.create();
  2085. }
  2086. return value;
  2087. }
  2088. });
  2089. })();
  2090. (function() {
  2091. var get = Ember.get, set = Ember.set, guidFor = Ember.guidFor,
  2092. once = Ember.run.once, arrayMap = Ember.ArrayPolyfills.map;
  2093. /**
  2094. This file encapsulates the various states that a record can transition
  2095. through during its lifecycle.
  2096. ### State Manager
  2097. A record's state manager explicitly tracks what state a record is in
  2098. at any given time. For instance, if a record is newly created and has
  2099. not yet been sent to the adapter to be saved, it would be in the
  2100. `created.uncommitted` state. If a record has had local modifications
  2101. made to it that are in the process of being saved, the record would be
  2102. in the `updated.inFlight` state. (These state paths will be explained
  2103. in more detail below.)
  2104. Events are sent by the record or its store to the record's state manager.
  2105. How the state manager reacts to these events is dependent on which state
  2106. it is in. In some states, certain events will be invalid and will cause
  2107. an exception to be raised.
  2108. States are hierarchical. For example, a record can be in the
  2109. `deleted.start` state, then transition into the `deleted.inFlight` state.
  2110. If a child state does not implement an event handler, the state manager
  2111. will attempt to invoke the event on all parent states until the root state is
  2112. reached. The state hierarchy of a record is described in terms of a path
  2113. string. You can determine a record's current state by getting its manager's
  2114. current state path:
  2115. record.get('stateManager.currentPath');
  2116. //=> "created.uncommitted"
  2117. The `DS.Model` states are themselves stateless. What we mean is that,
  2118. though each instance of a record also has a unique instance of a
  2119. `DS.StateManager`, the hierarchical states that each of *those* points
  2120. to is a shared data structure. For performance reasons, instead of each
  2121. record getting its own copy of the hierarchy of states, each state
  2122. manager points to this global, immutable shared instance. How does a
  2123. state know which record it should be acting on? We pass a reference to
  2124. the current state manager as the first parameter to every method invoked
  2125. on a state.
  2126. The state manager passed as the first parameter is where you should stash
  2127. state about the record if needed; you should never store data on the state
  2128. object itself. If you need access to the record being acted on, you can
  2129. retrieve the state manager's `record` property. For example, if you had
  2130. an event handler `myEvent`:
  2131. myEvent: function(manager) {
  2132. var record = manager.get('record');
  2133. record.doSomething();
  2134. }
  2135. For more information about state managers in general, see the Ember.js
  2136. documentation on `Ember.StateManager`.
  2137. ### Events, Flags, and Transitions
  2138. A state may implement zero or more events, flags, or transitions.
  2139. #### Events
  2140. Events are named functions that are invoked when sent to a record. The
  2141. state manager will first look for a method with the given name on the
  2142. current state. If no method is found, it will search the current state's
  2143. parent, and then its grandparent, and so on until reaching the top of
  2144. the hierarchy. If the root is reached without an event handler being found,
  2145. an exception will be raised. This can be very helpful when debugging new
  2146. features.
  2147. Here's an example implementation of a state with a `myEvent` event handler:
  2148. aState: DS.State.create({
  2149. myEvent: function(manager, param) {
  2150. console.log("Received myEvent with "+param);
  2151. }
  2152. })
  2153. To trigger this event:
  2154. record.send('myEvent', 'foo');
  2155. //=> "Received myEvent with foo"
  2156. Note that an optional parameter can be sent to a record's `send()` method,
  2157. which will be passed as the second parameter to the event handler.
  2158. Events should transition to a different state if appropriate. This can be
  2159. done by calling the state manager's `transitionTo()` method with a path to the
  2160. desired state. The state manager will attempt to resolve the state path
  2161. relative to the current state. If no state is found at that path, it will
  2162. attempt to resolve it relative to the current state's parent, and then its
  2163. parent, and so on until the root is reached. For example, imagine a hierarchy
  2164. like this:
  2165. * created
  2166. * start <-- currentState
  2167. * inFlight
  2168. * updated
  2169. * inFlight
  2170. If we are currently in the `start` state, calling
  2171. `transitionTo('inFlight')` would transition to the `created.inFlight` state,
  2172. while calling `transitionTo('updated.inFlight')` would transition to
  2173. the `updated.inFlight` state.
  2174. Remember that *only events* should ever cause a state transition. You should
  2175. never call `transitionTo()` from outside a state's event handler. If you are
  2176. tempted to do so, create a new event and send that to the state manager.
  2177. #### Flags
  2178. Flags are Boolean values that can be used to introspect a record's current
  2179. state in a more user-friendly way than examining its state path. For example,
  2180. instead of doing this:
  2181. var statePath = record.get('stateManager.currentPath');
  2182. if (statePath === 'created.inFlight') {
  2183. doSomething();
  2184. }
  2185. You can say:
  2186. if (record.get('isNew') && record.get('isSaving')) {
  2187. doSomething();
  2188. }
  2189. If your state does not set a value for a given flag, the value will
  2190. be inherited from its parent (or the first place in the state hierarchy
  2191. where it is defined).
  2192. The current set of flags are defined below. If you want to add a new flag,
  2193. in addition to the area below, you will also need to declare it in the
  2194. `DS.Model` class.
  2195. #### Transitions
  2196. Transitions are like event handlers but are called automatically upon
  2197. entering or exiting a state. To implement a transition, just call a method
  2198. either `enter` or `exit`:
  2199. myState: DS.State.create({
  2200. // Gets called automatically when entering
  2201. // this state.
  2202. enter: function(manager) {
  2203. console.log("Entered myState");
  2204. }
  2205. })
  2206. Note that enter and exit events are called once per transition. If the
  2207. current state changes, but changes to another child state of the parent,
  2208. the transition event on the parent will not be triggered.
  2209. */
  2210. var stateProperty = Ember.computed(function(key) {
  2211. var parent = get(this, 'parentState');
  2212. if (parent) {
  2213. return get(parent, key);
  2214. }
  2215. }).property();
  2216. var isEmptyObject = function(object) {
  2217. for (var name in object) {
  2218. if (object.hasOwnProperty(name)) { return false; }
  2219. }
  2220. return true;
  2221. };
  2222. var hasDefinedProperties = function(object) {
  2223. for (var name in object) {
  2224. if (object.hasOwnProperty(name) && object[name]) { return true; }
  2225. }
  2226. return false;
  2227. };
  2228. var didChangeData = function(manager) {
  2229. var record = get(manager, 'record');
  2230. record.materializeData();
  2231. };
  2232. var willSetProperty = function(manager, context) {
  2233. context.oldValue = get(get(manager, 'record'), context.name);
  2234. var change = DS.AttributeChange.createChange(context);
  2235. get(manager, 'record')._changesToSync[context.attributeName] = change;
  2236. };
  2237. var didSetProperty = function(manager, context) {
  2238. var change = get(manager, 'record')._changesToSync[context.attributeName];
  2239. change.value = get(get(manager, 'record'), context.name);
  2240. change.sync();
  2241. };
  2242. // Whenever a property is set, recompute all dependent filters
  2243. var updateRecordArrays = function(manager) {
  2244. var record = manager.get('record');
  2245. record.updateRecordArraysLater();
  2246. };
  2247. DS.State = Ember.State.extend({
  2248. isLoaded: stateProperty,
  2249. isReloading: stateProperty,
  2250. isDirty: stateProperty,
  2251. isSaving: stateProperty,
  2252. isDeleted: stateProperty,
  2253. isError: stateProperty,
  2254. isNew: stateProperty,
  2255. isValid: stateProperty,
  2256. // For states that are substates of a
  2257. // DirtyState (updated or created), it is
  2258. // useful to be able to determine which
  2259. // type of dirty state it is.
  2260. dirtyType: stateProperty
  2261. });
  2262. // Implementation notes:
  2263. //
  2264. // Each state has a boolean value for all of the following flags:
  2265. //
  2266. // * isLoaded: The record has a populated `data` property. When a
  2267. // record is loaded via `store.find`, `isLoaded` is false
  2268. // until the adapter sets it. When a record is created locally,
  2269. // its `isLoaded` property is always true.
  2270. // * isDirty: The record has local changes that have not yet been
  2271. // saved by the adapter. This includes records that have been
  2272. // created (but not yet saved) or deleted.
  2273. // * isSaving: The record's transaction has been committed, but
  2274. // the adapter has not yet acknowledged that the changes have
  2275. // been persisted to the backend.
  2276. // * isDeleted: The record was marked for deletion. When `isDeleted`
  2277. // is true and `isDirty` is true, the record is deleted locally
  2278. // but the deletion was not yet persisted. When `isSaving` is
  2279. // true, the change is in-flight. When both `isDirty` and
  2280. // `isSaving` are false, the change has persisted.
  2281. // * isError: The adapter reported that it was unable to save
  2282. // local changes to the backend. This may also result in the
  2283. // record having its `isValid` property become false if the
  2284. // adapter reported that server-side validations failed.
  2285. // * isNew: The record was created on the client and the adapter
  2286. // did not yet report that it was successfully saved.
  2287. // * isValid: No client-side validations have failed and the
  2288. // adapter did not report any server-side validation failures.
  2289. // The dirty state is a abstract state whose functionality is
  2290. // shared between the `created` and `updated` states.
  2291. //
  2292. // The deleted state shares the `isDirty` flag with the
  2293. // subclasses of `DirtyState`, but with a very different
  2294. // implementation.
  2295. //
  2296. // Dirty states have three child states:
  2297. //
  2298. // `uncommitted`: the store has not yet handed off the record
  2299. // to be saved.
  2300. // `inFlight`: the store has handed off the record to be saved,
  2301. // but the adapter has not yet acknowledged success.
  2302. // `invalid`: the record has invalid information and cannot be
  2303. // send to the adapter yet.
  2304. var DirtyState = DS.State.extend({
  2305. initialState: 'uncommitted',
  2306. // FLAGS
  2307. isDirty: true,
  2308. // SUBSTATES
  2309. // When a record first becomes dirty, it is `uncommitted`.
  2310. // This means that there are local pending changes, but they
  2311. // have not yet begun to be saved, and are not invalid.
  2312. uncommitted: DS.State.extend({
  2313. // TRANSITIONS
  2314. enter: function(manager) {
  2315. var dirtyType = get(this, 'dirtyType'),
  2316. record = get(manager, 'record');
  2317. record.withTransaction(function (t) {
  2318. t.recordBecameDirty(dirtyType, record);
  2319. });
  2320. },
  2321. // EVENTS
  2322. willSetProperty: willSetProperty,
  2323. didSetProperty: didSetProperty,
  2324. becomeDirty: Ember.K,
  2325. willCommit: function(manager) {
  2326. manager.transitionTo('inFlight');
  2327. },
  2328. becameClean: function(manager) {
  2329. var record = get(manager, 'record'),
  2330. dirtyType = get(this, 'dirtyType');
  2331. record.withTransaction(function(t) {
  2332. t.recordBecameClean(dirtyType, record);
  2333. });
  2334. manager.transitionTo('loaded.materializing');
  2335. },
  2336. becameInvalid: function(manager) {
  2337. var dirtyType = get(this, 'dirtyType'),
  2338. record = get(manager, 'record');
  2339. record.withTransaction(function (t) {
  2340. t.recordBecameInFlight(dirtyType, record);
  2341. });
  2342. manager.transitionTo('invalid');
  2343. },
  2344. rollback: function(manager) {
  2345. get(manager, 'record').rollback();
  2346. }
  2347. }),
  2348. // Once a record has been handed off to the adapter to be
  2349. // saved, it is in the 'in flight' state. Changes to the
  2350. // record cannot be made during this window.
  2351. inFlight: DS.State.extend({
  2352. // FLAGS
  2353. isSaving: true,
  2354. // TRANSITIONS
  2355. enter: function(manager) {
  2356. var dirtyType = get(this, 'dirtyType'),
  2357. record = get(manager, 'record');
  2358. record.becameInFlight();
  2359. record.withTransaction(function (t) {
  2360. t.recordBecameInFlight(dirtyType, record);
  2361. });
  2362. },
  2363. // EVENTS
  2364. didCommit: function(manager) {
  2365. var dirtyType = get(this, 'dirtyType'),
  2366. record = get(manager, 'record');
  2367. record.withTransaction(function(t) {
  2368. t.recordBecameClean('inflight', record);
  2369. });
  2370. manager.transitionTo('saved');
  2371. manager.send('invokeLifecycleCallbacks', dirtyType);
  2372. },
  2373. becameInvalid: function(manager, errors) {
  2374. var record = get(manager, 'record');
  2375. set(record, 'errors', errors);
  2376. manager.transitionTo('invalid');
  2377. manager.send('invokeLifecycleCallbacks');
  2378. },
  2379. becameError: function(manager) {
  2380. manager.transitionTo('error');
  2381. manager.send('invokeLifecycleCallbacks');
  2382. }
  2383. }),
  2384. // A record is in the `invalid` state when its client-side
  2385. // invalidations have failed, or if the adapter has indicated
  2386. // the the record failed server-side invalidations.
  2387. invalid: DS.State.extend({
  2388. // FLAGS
  2389. isValid: false,
  2390. exit: function(manager) {
  2391. var record = get(manager, 'record');
  2392. record.withTransaction(function (t) {
  2393. t.recordBecameClean('inflight', record);
  2394. });
  2395. },
  2396. // EVENTS
  2397. deleteRecord: function(manager) {
  2398. manager.transitionTo('deleted');
  2399. get(manager, 'record').clearRelationships();
  2400. },
  2401. willSetProperty: willSetProperty,
  2402. didSetProperty: function(manager, context) {
  2403. var record = get(manager, 'record'),
  2404. errors = get(record, 'errors'),
  2405. key = context.name;
  2406. set(errors, key, null);
  2407. if (!hasDefinedProperties(errors)) {
  2408. manager.send('becameValid');
  2409. }
  2410. didSetProperty(manager, context);
  2411. },
  2412. becomeDirty: Ember.K,
  2413. rollback: function(manager) {
  2414. manager.send('becameValid');
  2415. manager.send('rollback');
  2416. },
  2417. becameValid: function(manager) {
  2418. manager.transitionTo('uncommitted');
  2419. },
  2420. invokeLifecycleCallbacks: function(manager) {
  2421. var record = get(manager, 'record');
  2422. record.trigger('becameInvalid', record);
  2423. }
  2424. })
  2425. });
  2426. // The created and updated states are created outside the state
  2427. // chart so we can reopen their substates and add mixins as
  2428. // necessary.
  2429. var createdState = DirtyState.create({
  2430. dirtyType: 'created',
  2431. // FLAGS
  2432. isNew: true
  2433. });
  2434. var updatedState = DirtyState.create({
  2435. dirtyType: 'updated'
  2436. });
  2437. createdState.states.uncommitted.reopen({
  2438. deleteRecord: function(manager) {
  2439. var record = get(manager, 'record');
  2440. record.withTransaction(function(t) {
  2441. t.recordIsMoving('created', record);
  2442. });
  2443. record.clearRelationships();
  2444. manager.transitionTo('deleted.saved');
  2445. }
  2446. });
  2447. createdState.states.uncommitted.reopen({
  2448. rollback: function(manager) {
  2449. this._super(manager);
  2450. manager.transitionTo('deleted.saved');
  2451. }
  2452. });
  2453. updatedState.states.uncommitted.reopen({
  2454. deleteRecord: function(manager) {
  2455. var record = get(manager, 'record');
  2456. record.withTransaction(function(t) {
  2457. t.recordIsMoving('updated', record);
  2458. });
  2459. manager.transitionTo('deleted');
  2460. get(manager, 'record').clearRelationships();
  2461. }
  2462. });
  2463. var states = {
  2464. rootState: Ember.State.create({
  2465. // FLAGS
  2466. isLoaded: false,
  2467. isReloading: false,
  2468. isDirty: false,
  2469. isSaving: false,
  2470. isDeleted: false,
  2471. isError: false,
  2472. isNew: false,
  2473. isValid: true,
  2474. // SUBSTATES
  2475. // A record begins its lifecycle in the `empty` state.
  2476. // If its data will come from the adapter, it will
  2477. // transition into the `loading` state. Otherwise, if
  2478. // the record is being created on the client, it will
  2479. // transition into the `created` state.
  2480. empty: DS.State.create({
  2481. // EVENTS
  2482. loadingData: function(manager) {
  2483. manager.transitionTo('loading');
  2484. },
  2485. loadedData: function(manager) {
  2486. manager.transitionTo('loaded.created');
  2487. }
  2488. }),
  2489. // A record enters this state when the store askes
  2490. // the adapter for its data. It remains in this state
  2491. // until the adapter provides the requested data.
  2492. //
  2493. // Usually, this process is asynchronous, using an
  2494. // XHR to retrieve the data.
  2495. loading: DS.State.create({
  2496. // EVENTS
  2497. loadedData: didChangeData,
  2498. materializingData: function(manager) {
  2499. manager.transitionTo('loaded.materializing.firstTime');
  2500. }
  2501. }),
  2502. // A record enters this state when its data is populated.
  2503. // Most of a record's lifecycle is spent inside substates
  2504. // of the `loaded` state.
  2505. loaded: DS.State.create({
  2506. initialState: 'saved',
  2507. // FLAGS
  2508. isLoaded: true,
  2509. // SUBSTATES
  2510. materializing: DS.State.create({
  2511. // FLAGS
  2512. isLoaded: false,
  2513. // EVENTS
  2514. willSetProperty: Ember.K,
  2515. didSetProperty: Ember.K,
  2516. didChangeData: didChangeData,
  2517. finishedMaterializing: function(manager) {
  2518. manager.transitionTo('loaded.saved');
  2519. },
  2520. // SUBSTATES
  2521. firstTime: DS.State.create({
  2522. exit: function(manager) {
  2523. var record = get(manager, 'record');
  2524. Ember.run.once(function() {
  2525. record.trigger('didLoad');
  2526. });
  2527. }
  2528. })
  2529. }),
  2530. reloading: DS.State.create({
  2531. // FLAGS
  2532. isReloading: true,
  2533. // TRANSITIONS
  2534. enter: function(manager) {
  2535. var record = get(manager, 'record'),
  2536. store = get(record, 'store');
  2537. store.reloadRecord(record);
  2538. },
  2539. exit: function(manager) {
  2540. var record = get(manager, 'record');
  2541. once(record, 'trigger', 'didReload');
  2542. },
  2543. // EVENTS
  2544. loadedData: didChangeData,
  2545. materializingData: function(manager) {
  2546. manager.transitionTo('loaded.materializing');
  2547. }
  2548. }),
  2549. // If there are no local changes to a record, it remains
  2550. // in the `saved` state.
  2551. saved: DS.State.create({
  2552. // EVENTS
  2553. willSetProperty: willSetProperty,
  2554. didSetProperty: didSetProperty,
  2555. didChangeData: didChangeData,
  2556. loadedData: didChangeData,
  2557. reloadRecord: function(manager) {
  2558. manager.transitionTo('loaded.reloading');
  2559. },
  2560. materializingData: function(manager) {
  2561. manager.transitionTo('loaded.materializing');
  2562. },
  2563. becomeDirty: function(manager) {
  2564. manager.transitionTo('updated');
  2565. },
  2566. deleteRecord: function(manager) {
  2567. manager.transitionTo('deleted');
  2568. get(manager, 'record').clearRelationships();
  2569. },
  2570. unloadRecord: function(manager) {
  2571. manager.transitionTo('deleted.saved');
  2572. get(manager, 'record').clearRelationships();
  2573. },
  2574. willCommit: function(manager) {
  2575. manager.transitionTo('relationshipsInFlight');
  2576. },
  2577. invokeLifecycleCallbacks: function(manager, dirtyType) {
  2578. var record = get(manager, 'record');
  2579. if (dirtyType === 'created') {
  2580. record.trigger('didCreate', record);
  2581. } else {
  2582. record.trigger('didUpdate', record);
  2583. }
  2584. }
  2585. }),
  2586. relationshipsInFlight: Ember.State.create({
  2587. // TRANSITIONS
  2588. enter: function(manager) {
  2589. var record = get(manager, 'record');
  2590. record.withTransaction(function (t) {
  2591. t.recordBecameInFlight('clean', record);
  2592. });
  2593. },
  2594. // EVENTS
  2595. didCommit: function(manager) {
  2596. var record = get(manager, 'record');
  2597. record.withTransaction(function(t) {
  2598. t.recordBecameClean('inflight', record);
  2599. });
  2600. manager.transitionTo('saved');
  2601. manager.send('invokeLifecycleCallbacks');
  2602. }
  2603. }),
  2604. // A record is in this state after it has been locally
  2605. // created but before the adapter has indicated that
  2606. // it has been saved.
  2607. created: createdState,
  2608. // A record is in this state if it has already been
  2609. // saved to the server, but there are new local changes
  2610. // that have not yet been saved.
  2611. updated: updatedState
  2612. }),
  2613. // A record is in this state if it was deleted from the store.
  2614. deleted: DS.State.create({
  2615. initialState: 'uncommitted',
  2616. dirtyType: 'deleted',
  2617. // FLAGS
  2618. isDeleted: true,
  2619. isLoaded: true,
  2620. isDirty: true,
  2621. // TRANSITIONS
  2622. setup: function(manager) {
  2623. var record = get(manager, 'record'),
  2624. store = get(record, 'store');
  2625. store.removeFromRecordArrays(record);
  2626. },
  2627. // SUBSTATES
  2628. // When a record is deleted, it enters the `start`
  2629. // state. It will exit this state when the record's
  2630. // transaction starts to commit.
  2631. uncommitted: DS.State.create({
  2632. // TRANSITIONS
  2633. enter: function(manager) {
  2634. var record = get(manager, 'record');
  2635. record.withTransaction(function(t) {
  2636. t.recordBecameDirty('deleted', record);
  2637. });
  2638. },
  2639. // EVENTS
  2640. willCommit: function(manager) {
  2641. manager.transitionTo('inFlight');
  2642. },
  2643. rollback: function(manager) {
  2644. get(manager, 'record').rollback();
  2645. },
  2646. becomeDirty: Ember.K,
  2647. becameClean: function(manager) {
  2648. var record = get(manager, 'record');
  2649. record.withTransaction(function(t) {
  2650. t.recordBecameClean('deleted', record);
  2651. });
  2652. manager.transitionTo('loaded.materializing');
  2653. }
  2654. }),
  2655. // After a record's transaction is committing, but
  2656. // before the adapter indicates that the deletion
  2657. // has saved to the server, a record is in the
  2658. // `inFlight` substate of `deleted`.
  2659. inFlight: DS.State.create({
  2660. // FLAGS
  2661. isSaving: true,
  2662. // TRANSITIONS
  2663. enter: function(manager) {
  2664. var record = get(manager, 'record');
  2665. record.becameInFlight();
  2666. record.withTransaction(function (t) {
  2667. t.recordBecameInFlight('deleted', record);
  2668. });
  2669. },
  2670. // EVENTS
  2671. didCommit: function(manager) {
  2672. var record = get(manager, 'record');
  2673. record.withTransaction(function(t) {
  2674. t.recordBecameClean('inflight', record);
  2675. });
  2676. manager.transitionTo('saved');
  2677. manager.send('invokeLifecycleCallbacks');
  2678. }
  2679. }),
  2680. // Once the adapter indicates that the deletion has
  2681. // been saved, the record enters the `saved` substate
  2682. // of `deleted`.
  2683. saved: DS.State.create({
  2684. // FLAGS
  2685. isDirty: false,
  2686. setup: function(manager) {
  2687. var record = get(manager, 'record'),
  2688. store = get(record, 'store');
  2689. store.dematerializeRecord(record);
  2690. },
  2691. invokeLifecycleCallbacks: function(manager) {
  2692. var record = get(manager, 'record');
  2693. record.trigger('didDelete', record);
  2694. }
  2695. })
  2696. }),
  2697. // If the adapter indicates that there was an unknown
  2698. // error saving a record, the record enters the `error`
  2699. // state.
  2700. error: DS.State.create({
  2701. isError: true,
  2702. // EVENTS
  2703. invokeLifecycleCallbacks: function(manager) {
  2704. var record = get(manager, 'record');
  2705. record.trigger('becameError', record);
  2706. }
  2707. })
  2708. })
  2709. };
  2710. DS.StateManager = Ember.StateManager.extend({
  2711. record: null,
  2712. initialState: 'rootState',
  2713. states: states,
  2714. unhandledEvent: function(manager, originalEvent) {
  2715. var record = manager.get('record'),
  2716. contexts = [].slice.call(arguments, 2),
  2717. errorMessage;
  2718. errorMessage = "Attempted to handle event `" + originalEvent + "` ";
  2719. errorMessage += "on " + record.toString() + " while in state ";
  2720. errorMessage += get(manager, 'currentState.path') + ". Called with ";
  2721. errorMessage += arrayMap.call(contexts, function(context){
  2722. return Ember.inspect(context);
  2723. }).join(', ');
  2724. throw new Ember.Error(errorMessage);
  2725. }
  2726. });
  2727. })();
  2728. (function() {
  2729. var LoadPromise = DS.LoadPromise; // system/mixins/load_promise
  2730. var get = Ember.get, set = Ember.set, none = Ember.isNone, map = Ember.EnumerableUtils.map;
  2731. var retrieveFromCurrentState = Ember.computed(function(key) {
  2732. return get(get(this, 'stateManager.currentState'), key);
  2733. }).property('stateManager.currentState');
  2734. DS.Model = Ember.Object.extend(Ember.Evented, LoadPromise, {
  2735. isLoaded: retrieveFromCurrentState,
  2736. isReloading: retrieveFromCurrentState,
  2737. isDirty: retrieveFromCurrentState,
  2738. isSaving: retrieveFromCurrentState,
  2739. isDeleted: retrieveFromCurrentState,
  2740. isError: retrieveFromCurrentState,
  2741. isNew: retrieveFromCurrentState,
  2742. isValid: retrieveFromCurrentState,
  2743. clientId: null,
  2744. id: null,
  2745. transaction: null,
  2746. stateManager: null,
  2747. errors: null,
  2748. /**
  2749. Create a JSON representation of the record, using the serialization
  2750. strategy of the store's adapter.
  2751. Available options:
  2752. * `includeId`: `true` if the record's ID should be included in the
  2753. JSON representation.
  2754. @param {Object} options
  2755. @returns {Object} an object whose values are primitive JSON values only
  2756. */
  2757. serialize: function(options) {
  2758. var store = get(this, 'store');
  2759. return store.serialize(this, options);
  2760. },
  2761. didLoad: Ember.K,
  2762. didReload: Ember.K,
  2763. didUpdate: Ember.K,
  2764. didCreate: Ember.K,
  2765. didDelete: Ember.K,
  2766. becameInvalid: Ember.K,
  2767. becameError: Ember.K,
  2768. data: Ember.computed(function() {
  2769. if (!this._data) {
  2770. this.materializeData();
  2771. }
  2772. return this._data;
  2773. }).property(),
  2774. materializeData: function() {
  2775. this.send('materializingData');
  2776. get(this, 'store').materializeData(this);
  2777. this.suspendRelationshipObservers(function() {
  2778. this.notifyPropertyChange('data');
  2779. });
  2780. },
  2781. _data: null,
  2782. init: function() {
  2783. this._super();
  2784. var stateManager = DS.StateManager.create({ record: this });
  2785. set(this, 'stateManager', stateManager);
  2786. this._setup();
  2787. stateManager.goToState('empty');
  2788. },
  2789. _setup: function() {
  2790. this._relationshipChanges = {};
  2791. this._changesToSync = {};
  2792. },
  2793. send: function(name, context) {
  2794. return get(this, 'stateManager').send(name, context);
  2795. },
  2796. withTransaction: function(fn) {
  2797. var transaction = get(this, 'transaction');
  2798. if (transaction) { fn(transaction); }
  2799. },
  2800. loadingData: function() {
  2801. this.send('loadingData');
  2802. },
  2803. loadedData: function() {
  2804. this.send('loadedData');
  2805. },
  2806. didChangeData: function() {
  2807. this.send('didChangeData');
  2808. },
  2809. setProperty: function(key, value, oldValue) {
  2810. this.send('setProperty', { key: key, value: value, oldValue: oldValue });
  2811. },
  2812. /**
  2813. Reload the record from the adapter.
  2814. This will only work if the record has already finished loading
  2815. and has not yet been modified (`isLoaded` but not `isDirty`,
  2816. or `isSaving`).
  2817. */
  2818. reload: function() {
  2819. this.send('reloadRecord');
  2820. },
  2821. deleteRecord: function() {
  2822. this.send('deleteRecord');
  2823. },
  2824. unloadRecord: function() {
  2825. Ember.assert("You can only unload a loaded, non-dirty record.", !get(this, 'isDirty'));
  2826. this.send('unloadRecord');
  2827. },
  2828. clearRelationships: function() {
  2829. this.eachRelationship(function(name, relationship) {
  2830. if (relationship.kind === 'belongsTo') {
  2831. set(this, name, null);
  2832. } else if (relationship.kind === 'hasMany') {
  2833. get(this, name).clear();
  2834. }
  2835. }, this);
  2836. },
  2837. updateRecordArrays: function() {
  2838. var store = get(this, 'store');
  2839. if (store) {
  2840. store.dataWasUpdated(this.constructor, get(this, 'clientId'), this);
  2841. }
  2842. },
  2843. /**
  2844. If the adapter did not return a hash in response to a commit,
  2845. merge the changed attributes and relationships into the existing
  2846. saved data.
  2847. */
  2848. adapterDidCommit: function() {
  2849. var attributes = get(this, 'data').attributes;
  2850. get(this.constructor, 'attributes').forEach(function(name, meta) {
  2851. attributes[name] = get(this, name);
  2852. }, this);
  2853. this.send('didCommit');
  2854. this.updateRecordArraysLater();
  2855. },
  2856. adapterDidDirty: function() {
  2857. this.send('becomeDirty');
  2858. this.updateRecordArraysLater();
  2859. },
  2860. dataDidChange: Ember.observer(function() {
  2861. var relationships = get(this.constructor, 'relationshipsByName');
  2862. this.updateRecordArraysLater();
  2863. relationships.forEach(function(name, relationship) {
  2864. if (relationship.kind === 'hasMany') {
  2865. this.hasManyDidChange(relationship.key);
  2866. }
  2867. }, this);
  2868. this.send('finishedMaterializing');
  2869. }, 'data'),
  2870. hasManyDidChange: function(key) {
  2871. var cachedValue = this.cacheFor(key);
  2872. if (cachedValue) {
  2873. var type = get(this.constructor, 'relationshipsByName').get(key).type;
  2874. var store = get(this, 'store');
  2875. var ids = this._data.hasMany[key] || [];
  2876. var references = map(ids, function(id) {
  2877. // if it was already a reference, return the reference
  2878. if (typeof id === 'object') { return id; }
  2879. return store.referenceForId(type, id);
  2880. });
  2881. set(cachedValue, 'content', Ember.A(references));
  2882. }
  2883. },
  2884. updateRecordArraysLater: function() {
  2885. Ember.run.once(this, this.updateRecordArrays);
  2886. },
  2887. setupData: function(prematerialized) {
  2888. this._data = {
  2889. attributes: {},
  2890. belongsTo: {},
  2891. hasMany: {},
  2892. id: null
  2893. };
  2894. },
  2895. materializeId: function(id) {
  2896. set(this, 'id', id);
  2897. },
  2898. materializeAttributes: function(attributes) {
  2899. Ember.assert("Must pass a hash of attributes to materializeAttributes", !!attributes);
  2900. this._data.attributes = attributes;
  2901. },
  2902. materializeAttribute: function(name, value) {
  2903. this._data.attributes[name] = value;
  2904. },
  2905. materializeHasMany: function(name, ids) {
  2906. this._data.hasMany[name] = ids;
  2907. },
  2908. materializeBelongsTo: function(name, id) {
  2909. this._data.belongsTo[name] = id;
  2910. },
  2911. rollback: function() {
  2912. this._setup();
  2913. this.send('becameClean');
  2914. this.suspendRelationshipObservers(function() {
  2915. this.notifyPropertyChange('data');
  2916. });
  2917. },
  2918. toStringExtension: function() {
  2919. return get(this, 'id');
  2920. },
  2921. /**
  2922. @private
  2923. The goal of this method is to temporarily disable specific observers
  2924. that take action in response to application changes.
  2925. This allows the system to make changes (such as materialization and
  2926. rollback) that should not trigger secondary behavior (such as setting an
  2927. inverse relationship or marking records as dirty).
  2928. The specific implementation will likely change as Ember proper provides
  2929. better infrastructure for suspending groups of observers, and if Array
  2930. observation becomes more unified with regular observers.
  2931. */
  2932. suspendRelationshipObservers: function(callback, binding) {
  2933. var observers = get(this.constructor, 'relationshipNames').belongsTo;
  2934. var self = this;
  2935. try {
  2936. this._suspendedRelationships = true;
  2937. Ember._suspendObservers(self, observers, null, 'belongsToDidChange', function() {
  2938. Ember._suspendBeforeObservers(self, observers, null, 'belongsToWillChange', function() {
  2939. callback.call(binding || self);
  2940. });
  2941. });
  2942. } finally {
  2943. this._suspendedRelationships = false;
  2944. }
  2945. },
  2946. becameInFlight: function() {
  2947. },
  2948. // FOR USE DURING COMMIT PROCESS
  2949. adapterDidUpdateAttribute: function(attributeName, value) {
  2950. // If a value is passed in, update the internal attributes and clear
  2951. // the attribute cache so it picks up the new value. Otherwise,
  2952. // collapse the current value into the internal attributes because
  2953. // the adapter has acknowledged it.
  2954. if (value !== undefined) {
  2955. get(this, 'data.attributes')[attributeName] = value;
  2956. this.notifyPropertyChange(attributeName);
  2957. } else {
  2958. value = get(this, attributeName);
  2959. get(this, 'data.attributes')[attributeName] = value;
  2960. }
  2961. this.updateRecordArraysLater();
  2962. },
  2963. _reference: Ember.computed(function() {
  2964. return get(this, 'store').referenceForClientId(get(this, 'clientId'));
  2965. }),
  2966. adapterDidInvalidate: function(errors) {
  2967. this.send('becameInvalid', errors);
  2968. },
  2969. adapterDidError: function() {
  2970. this.send('becameError');
  2971. },
  2972. /**
  2973. @private
  2974. Override the default event firing from Ember.Evented to
  2975. also call methods with the given name.
  2976. */
  2977. trigger: function(name) {
  2978. Ember.tryInvoke(this, name, [].slice.call(arguments, 1));
  2979. this._super.apply(this, arguments);
  2980. }
  2981. });
  2982. // Helper function to generate store aliases.
  2983. // This returns a function that invokes the named alias
  2984. // on the default store, but injects the class as the
  2985. // first parameter.
  2986. var storeAlias = function(methodName) {
  2987. return function() {
  2988. var store = get(DS, 'defaultStore'),
  2989. args = [].slice.call(arguments);
  2990. args.unshift(this);
  2991. return store[methodName].apply(store, args);
  2992. };
  2993. };
  2994. DS.Model.reopenClass({
  2995. isLoaded: storeAlias('recordIsLoaded'),
  2996. find: storeAlias('find'),
  2997. all: storeAlias('all'),
  2998. filter: storeAlias('filter'),
  2999. _create: DS.Model.create,
  3000. create: function() {
  3001. throw new Ember.Error("You should not call `create` on a model. Instead, call `createRecord` with the attributes you would like to set.");
  3002. },
  3003. createRecord: storeAlias('createRecord')
  3004. });
  3005. })();
  3006. (function() {
  3007. var get = Ember.get;
  3008. DS.Model.reopenClass({
  3009. attributes: Ember.computed(function() {
  3010. var map = Ember.Map.create();
  3011. this.eachComputedProperty(function(name, meta) {
  3012. if (meta.isAttribute) {
  3013. Ember.assert("You may not set `id` as an attribute on your model. Please remove any lines that look like: `id: DS.attr('<type>')` from " + this.toString(), name !== 'id');
  3014. meta.name = name;
  3015. map.set(name, meta);
  3016. }
  3017. });
  3018. return map;
  3019. })
  3020. });
  3021. var AttributeChange = DS.AttributeChange = function(options) {
  3022. this.reference = options.reference;
  3023. this.store = options.store;
  3024. this.name = options.name;
  3025. this.oldValue = options.oldValue;
  3026. };
  3027. AttributeChange.createChange = function(options) {
  3028. return new AttributeChange(options);
  3029. };
  3030. AttributeChange.prototype = {
  3031. sync: function() {
  3032. this.store.recordAttributeDidChange(this.reference, this.name, this.value, this.oldValue);
  3033. // TODO: Use this object in the commit process
  3034. this.destroy();
  3035. },
  3036. destroy: function() {
  3037. delete this.store.recordForReference(this.reference)._changesToSync[this.name];
  3038. }
  3039. };
  3040. DS.Model.reopen({
  3041. eachAttribute: function(callback, binding) {
  3042. get(this.constructor, 'attributes').forEach(function(name, meta) {
  3043. callback.call(binding, name, meta);
  3044. }, binding);
  3045. },
  3046. attributeWillChange: Ember.beforeObserver(function(record, key) {
  3047. var reference = get(record, '_reference'),
  3048. store = get(record, 'store');
  3049. record.send('willSetProperty', { reference: reference, store: store, name: key });
  3050. }),
  3051. attributeDidChange: Ember.observer(function(record, key) {
  3052. record.send('didSetProperty', { name: key });
  3053. })
  3054. });
  3055. function getAttr(record, options, key) {
  3056. var attributes = get(record, 'data').attributes;
  3057. var value = attributes[key];
  3058. if (value === undefined) {
  3059. value = options.defaultValue;
  3060. }
  3061. return value;
  3062. }
  3063. DS.attr = function(type, options) {
  3064. options = options || {};
  3065. var meta = {
  3066. type: type,
  3067. isAttribute: true,
  3068. options: options
  3069. };
  3070. return Ember.computed(function(key, value, oldValue) {
  3071. var data;
  3072. if (arguments.length > 1) {
  3073. Ember.assert("You may not set `id` as an attribute on your model. Please remove any lines that look like: `id: DS.attr('<type>')` from " + this.constructor.toString(), key !== 'id');
  3074. } else {
  3075. value = getAttr(this, options, key);
  3076. }
  3077. return value;
  3078. // `data` is never set directly. However, it may be
  3079. // invalidated from the state manager's setData
  3080. // event.
  3081. }).property('data').meta(meta);
  3082. };
  3083. })();
  3084. (function() {
  3085. })();
  3086. (function() {
  3087. var get = Ember.get, set = Ember.set,
  3088. none = Ember.isNone;
  3089. DS.belongsTo = function(type, options) {
  3090. Ember.assert("The first argument DS.belongsTo must be a model type or string, like DS.belongsTo(App.Person)", !!type && (typeof type === 'string' || DS.Model.detect(type)));
  3091. options = options || {};
  3092. var meta = { type: type, isRelationship: true, options: options, kind: 'belongsTo' };
  3093. return Ember.computed(function(key, value) {
  3094. if (arguments.length === 2) {
  3095. return value === undefined ? null : value;
  3096. }
  3097. var data = get(this, 'data').belongsTo,
  3098. store = get(this, 'store'), id;
  3099. if (typeof type === 'string') {
  3100. type = get(this, type, false) || get(Ember.lookup, type);
  3101. }
  3102. id = data[key];
  3103. if(!id) {
  3104. return null;
  3105. } else if (typeof id === 'object') {
  3106. return store.findByClientId(type, id.clientId);
  3107. } else {
  3108. return store.find(type, id);
  3109. }
  3110. }).property('data').meta(meta);
  3111. };
  3112. /**
  3113. These observers observe all `belongsTo` relationships on the record. See
  3114. `relationships/ext` to see how these observers get their dependencies.
  3115. */
  3116. DS.Model.reopen({
  3117. /** @private */
  3118. belongsToWillChange: Ember.beforeObserver(function(record, key) {
  3119. if (get(record, 'isLoaded')) {
  3120. var oldParent = get(record, key);
  3121. var childId = get(record, 'clientId'),
  3122. store = get(record, 'store');
  3123. if (oldParent){
  3124. var change = DS.RelationshipChange.createChange(childId, get(oldParent, 'clientId'), store, { key: key, kind:"belongsTo", changeType: "remove" });
  3125. change.sync();
  3126. this._changesToSync[key] = change;
  3127. }
  3128. }
  3129. }),
  3130. /** @private */
  3131. belongsToDidChange: Ember.immediateObserver(function(record, key) {
  3132. if (get(record, 'isLoaded')) {
  3133. var newParent = get(record, key);
  3134. if(newParent){
  3135. var childId = get(record, 'clientId'),
  3136. store = get(record, 'store');
  3137. var change = DS.RelationshipChange.createChange(childId, get(newParent, 'clientId'), store, { key: key, kind:"belongsTo", changeType: "add" });
  3138. change.sync();
  3139. if(this._changesToSync[key]){
  3140. DS.OneToManyChange.ensureSameTransaction([change, this._changesToSync[key]], store);
  3141. }
  3142. }
  3143. }
  3144. delete this._changesToSync[key];
  3145. })
  3146. });
  3147. })();
  3148. (function() {
  3149. var get = Ember.get, set = Ember.set;
  3150. var hasRelationship = function(type, options) {
  3151. options = options || {};
  3152. var meta = { type: type, isRelationship: true, options: options, kind: 'hasMany' };
  3153. return Ember.computed(function(key, value) {
  3154. var data = get(this, 'data').hasMany,
  3155. store = get(this, 'store'),
  3156. ids, relationship;
  3157. if (typeof type === 'string') {
  3158. type = get(this, type, false) || get(Ember.lookup, type);
  3159. }
  3160. ids = data[key];
  3161. relationship = store.findMany(type, ids || [], this, meta);
  3162. set(relationship, 'owner', this);
  3163. set(relationship, 'name', key);
  3164. return relationship;
  3165. }).property().meta(meta);
  3166. };
  3167. DS.hasMany = function(type, options) {
  3168. Ember.assert("The type passed to DS.hasMany must be defined", !!type);
  3169. return hasRelationship(type, options);
  3170. };
  3171. })();
  3172. (function() {
  3173. var get = Ember.get, set = Ember.set;
  3174. /**
  3175. @private
  3176. This file defines several extensions to the base `DS.Model` class that
  3177. add support for one-to-many relationships.
  3178. */
  3179. DS.Model.reopen({
  3180. // This Ember.js hook allows an object to be notified when a property
  3181. // is defined.
  3182. //
  3183. // In this case, we use it to be notified when an Ember Data user defines a
  3184. // belongs-to relationship. In that case, we need to set up observers for
  3185. // each one, allowing us to track relationship changes and automatically
  3186. // reflect changes in the inverse has-many array.
  3187. //
  3188. // This hook passes the class being set up, as well as the key and value
  3189. // being defined. So, for example, when the user does this:
  3190. //
  3191. // DS.Model.extend({
  3192. // parent: DS.belongsTo(App.User)
  3193. // });
  3194. //
  3195. // This hook would be called with "parent" as the key and the computed
  3196. // property returned by `DS.belongsTo` as the value.
  3197. didDefineProperty: function(proto, key, value) {
  3198. // Check if the value being set is a computed property.
  3199. if (value instanceof Ember.Descriptor) {
  3200. // If it is, get the metadata for the relationship. This is
  3201. // populated by the `DS.belongsTo` helper when it is creating
  3202. // the computed property.
  3203. var meta = value.meta();
  3204. if (meta.isRelationship && meta.kind === 'belongsTo') {
  3205. Ember.addObserver(proto, key, null, 'belongsToDidChange');
  3206. Ember.addBeforeObserver(proto, key, null, 'belongsToWillChange');
  3207. }
  3208. if (meta.isAttribute) {
  3209. Ember.addObserver(proto, key, null, 'attributeDidChange');
  3210. Ember.addBeforeObserver(proto, key, null, 'attributeWillChange');
  3211. }
  3212. meta.parentType = proto.constructor;
  3213. }
  3214. }
  3215. });
  3216. /**
  3217. These DS.Model extensions add class methods that provide relationship
  3218. introspection abilities about relationships.
  3219. A note about the computed properties contained here:
  3220. **These properties are effectively sealed once called for the first time.**
  3221. To avoid repeatedly doing expensive iteration over a model's fields, these
  3222. values are computed once and then cached for the remainder of the runtime of
  3223. your application.
  3224. If your application needs to modify a class after its initial definition
  3225. (for example, using `reopen()` to add additional attributes), make sure you
  3226. do it before using your model with the store, which uses these properties
  3227. extensively.
  3228. */
  3229. DS.Model.reopenClass({
  3230. /**
  3231. For a given relationship name, returns the model type of the relationship.
  3232. For example, if you define a model like this:
  3233. App.Post = DS.Model.extend({
  3234. comments: DS.hasMany(App.Comment)
  3235. });
  3236. Calling `App.Post.typeForRelationship('comments')` will return `App.Comment`.
  3237. @param {String} name the name of the relationship
  3238. @return {subclass of DS.Model} the type of the relationship, or undefined
  3239. */
  3240. typeForRelationship: function(name) {
  3241. var relationship = get(this, 'relationshipsByName').get(name);
  3242. return relationship && relationship.type;
  3243. },
  3244. /**
  3245. The model's relationships as a map, keyed on the type of the
  3246. relationship. The value of each entry is an array containing a descriptor
  3247. for each relationship with that type, describing the name of the relationship
  3248. as well as the type.
  3249. For example, given the following model definition:
  3250. App.Blog = DS.Model.extend({
  3251. users: DS.hasMany(App.User),
  3252. owner: DS.belongsTo(App.User),
  3253. posts: DS.hasMany(App.Post)
  3254. });
  3255. This computed property would return a map describing these
  3256. relationships, like this:
  3257. var relationships = Ember.get(App.Blog, 'relationships');
  3258. associatons.get(App.User);
  3259. //=> [ { name: 'users', kind: 'hasMany' },
  3260. // { name: 'owner', kind: 'belongsTo' } ]
  3261. relationships.get(App.Post);
  3262. //=> [ { name: 'posts', kind: 'hasMany' } ]
  3263. @type Ember.Map
  3264. @readOnly
  3265. */
  3266. relationships: Ember.computed(function() {
  3267. var map = new Ember.MapWithDefault({
  3268. defaultValue: function() { return []; }
  3269. });
  3270. // Loop through each computed property on the class
  3271. this.eachComputedProperty(function(name, meta) {
  3272. // If the computed property is a relationship, add
  3273. // it to the map.
  3274. if (meta.isRelationship) {
  3275. if (typeof meta.type === 'string') {
  3276. meta.type = Ember.get(Ember.lookup, meta.type);
  3277. }
  3278. var relationshipsForType = map.get(meta.type);
  3279. relationshipsForType.push({ name: name, kind: meta.kind });
  3280. }
  3281. });
  3282. return map;
  3283. }),
  3284. /**
  3285. A hash containing lists of the model's relationships, grouped
  3286. by the relationship kind. For example, given a model with this
  3287. definition:
  3288. App.Blog = DS.Model.extend({
  3289. users: DS.hasMany(App.User),
  3290. owner: DS.belongsTo(App.User),
  3291. posts: DS.hasMany(App.Post)
  3292. });
  3293. This property would contain the following:
  3294. var relationshipNames = Ember.get(App.Blog, 'relationshipNames');
  3295. relationshipNames.hasMany;
  3296. //=> ['users', 'posts']
  3297. relationshipNames.belongsTo;
  3298. //=> ['owner']
  3299. @type Object
  3300. @readOnly
  3301. */
  3302. relationshipNames: Ember.computed(function() {
  3303. var names = { hasMany: [], belongsTo: [] };
  3304. this.eachComputedProperty(function(name, meta) {
  3305. if (meta.isRelationship) {
  3306. names[meta.kind].push(name);
  3307. }
  3308. });
  3309. return names;
  3310. }),
  3311. /**
  3312. A map whose keys are the relationships of a model and whose values are
  3313. relationship descriptors.
  3314. For example, given a model with this
  3315. definition:
  3316. App.Blog = DS.Model.extend({
  3317. users: DS.hasMany(App.User),
  3318. owner: DS.belongsTo(App.User),
  3319. posts: DS.hasMany(App.Post)
  3320. });
  3321. This property would contain the following:
  3322. var relationshipsByName = Ember.get(App.Blog, 'relationshipsByName');
  3323. relationshipsByName.get('users');
  3324. //=> { key: 'users', kind: 'hasMany', type: App.User }
  3325. relationshipsByName.get('owner');
  3326. //=> { key: 'owner', kind: 'belongsTo', type: App.User }
  3327. @type Ember.Map
  3328. @readOnly
  3329. */
  3330. relationshipsByName: Ember.computed(function() {
  3331. var map = Ember.Map.create(), type;
  3332. this.eachComputedProperty(function(name, meta) {
  3333. if (meta.isRelationship) {
  3334. meta.key = name;
  3335. type = meta.type;
  3336. if (typeof type === 'string') {
  3337. type = get(this, type, false) || get(Ember.lookup, type);
  3338. meta.type = type;
  3339. }
  3340. map.set(name, meta);
  3341. }
  3342. });
  3343. return map;
  3344. }),
  3345. /**
  3346. A map whose keys are the fields of the model and whose values are strings
  3347. describing the kind of the field. A model's fields are the union of all of its
  3348. attributes and relationships.
  3349. For example:
  3350. App.Blog = DS.Model.extend({
  3351. users: DS.hasMany(App.User),
  3352. owner: DS.belongsTo(App.User),
  3353. posts: DS.hasMany(App.Post),
  3354. title: DS.attr('string')
  3355. });
  3356. var fields = Ember.get(App.Blog, 'fields');
  3357. fields.forEach(function(field, kind) {
  3358. console.log(field, kind);
  3359. });
  3360. // prints:
  3361. // users, hasMany
  3362. // owner, belongsTo
  3363. // posts, hasMany
  3364. // title, attribute
  3365. @type Ember.Map
  3366. @readOnly
  3367. */
  3368. fields: Ember.computed(function() {
  3369. var map = Ember.Map.create(), type;
  3370. this.eachComputedProperty(function(name, meta) {
  3371. if (meta.isRelationship) {
  3372. map.set(name, meta.kind);
  3373. } else if (meta.isAttribute) {
  3374. map.set(name, 'attribute');
  3375. }
  3376. });
  3377. return map;
  3378. }),
  3379. /**
  3380. Given a callback, iterates over each of the relationships in the model,
  3381. invoking the callback with the name of each relationship and its relationship
  3382. descriptor.
  3383. @param {Function} callback the callback to invoke
  3384. @param {any} binding the value to which the callback's `this` should be bound
  3385. */
  3386. eachRelationship: function(callback, binding) {
  3387. get(this, 'relationshipsByName').forEach(function(name, relationship) {
  3388. callback.call(binding, name, relationship);
  3389. });
  3390. }
  3391. });
  3392. DS.Model.reopen({
  3393. /**
  3394. Given a callback, iterates over each of the relationships in the model,
  3395. invoking the callback with the name of each relationship and its relationship
  3396. descriptor.
  3397. @param {Function} callback the callback to invoke
  3398. @param {any} binding the value to which the callback's `this` should be bound
  3399. */
  3400. eachRelationship: function(callback, binding) {
  3401. this.constructor.eachRelationship(callback, binding);
  3402. }
  3403. });
  3404. /**
  3405. @private
  3406. Helper method to look up the name of the inverse of a relationship.
  3407. In a has-many relationship, there are always two sides: the `belongsTo` side
  3408. and the `hasMany` side. When one side changes, the other side should be updated
  3409. automatically.
  3410. Given a model, the model of the inverse, and the kind of the relationship, this
  3411. helper returns the name of the relationship on the inverse.
  3412. For example, imagine the following two associated models:
  3413. App.Post = DS.Model.extend({
  3414. comments: DS.hasMany('App.Comment')
  3415. });
  3416. App.Comment = DS.Model.extend({
  3417. post: DS.belongsTo('App.Post')
  3418. });
  3419. If the `post` property of a `Comment` was modified, Ember Data would invoke
  3420. this helper like this:
  3421. DS._inverseNameFor(App.Comment, App.Post, 'hasMany');
  3422. //=> 'comments'
  3423. Ember Data uses the name of the relationship returned to reflect the changed
  3424. relationship on the other side.
  3425. */
  3426. DS._inverseRelationshipFor = function(modelType, inverseModelType) {
  3427. var relationshipMap = get(modelType, 'relationships'),
  3428. possibleRelationships = relationshipMap.get(inverseModelType),
  3429. possible, actual, oldValue;
  3430. if (!possibleRelationships) { return; }
  3431. if (possibleRelationships.length > 1) { return; }
  3432. return possibleRelationships[0];
  3433. };
  3434. /**
  3435. @private
  3436. Given a model and a relationship name, returns the model type of
  3437. the named relationship.
  3438. App.Post = DS.Model.extend({
  3439. comments: DS.hasMany('App.Comment')
  3440. });
  3441. DS._inverseTypeFor(App.Post, 'comments');
  3442. //=> App.Comment
  3443. @param {DS.Model class} modelType
  3444. @param {String} relationshipName
  3445. @return {DS.Model class}
  3446. */
  3447. DS._inverseTypeFor = function(modelType, relationshipName) {
  3448. var relationships = get(modelType, 'relationshipsByName'),
  3449. relationship = relationships.get(relationshipName);
  3450. if (relationship) { return relationship.type; }
  3451. };
  3452. })();
  3453. (function() {
  3454. var get = Ember.get, set = Ember.set;
  3455. var forEach = Ember.EnumerableUtils.forEach;
  3456. DS.RelationshipChange = function(options) {
  3457. this.firstRecordClientId = options.firstRecordClientId;
  3458. this.firstRecordKind = options.firstRecordKind;
  3459. this.firstRecordName = options.firstRecordName;
  3460. this.secondRecordClientId = options.secondRecordClientId;
  3461. this.secondRecordKind = options.secondRecordKind;
  3462. this.secondRecordName = options.secondRecordName;
  3463. this.store = options.store;
  3464. this.committed = {};
  3465. this.changeType = options.changeType;
  3466. };
  3467. DS.RelationshipChangeAdd = function(options){
  3468. DS.RelationshipChange.call(this, options);
  3469. };
  3470. DS.RelationshipChangeRemove = function(options){
  3471. DS.RelationshipChange.call(this, options);
  3472. };
  3473. /** @private */
  3474. DS.RelationshipChange.create = function(options) {
  3475. return new DS.RelationshipChange(options);
  3476. };
  3477. /** @private */
  3478. DS.RelationshipChangeAdd.create = function(options) {
  3479. return new DS.RelationshipChangeAdd(options);
  3480. };
  3481. /** @private */
  3482. DS.RelationshipChangeRemove.create = function(options) {
  3483. return new DS.RelationshipChangeRemove(options);
  3484. };
  3485. DS.OneToManyChange = {};
  3486. DS.OneToNoneChange = {};
  3487. DS.ManyToNoneChange = {};
  3488. DS.OneToOneChange = {};
  3489. DS.ManyToManyChange = {};
  3490. DS.RelationshipChange._createChange = function(options){
  3491. if(options.changeType === "add"){
  3492. return DS.RelationshipChangeAdd.create(options);
  3493. }
  3494. if(options.changeType === "remove"){
  3495. return DS.RelationshipChangeRemove.create(options);
  3496. }
  3497. };
  3498. DS.RelationshipChange.determineRelationshipType = function(recordType, knownSide){
  3499. var knownKey = knownSide.key, key, type, otherContainerType,assoc;
  3500. var knownContainerType = knownSide.kind;
  3501. var options = recordType.metaForProperty(knownKey).options;
  3502. var otherType = DS._inverseTypeFor(recordType, knownKey);
  3503. if(options.inverse){
  3504. key = options.inverse;
  3505. otherContainerType = get(otherType, 'relationshipsByName').get(key).kind;
  3506. }
  3507. else if(assoc = DS._inverseRelationshipFor(otherType, recordType)){
  3508. key = assoc.name;
  3509. otherContainerType = assoc.kind;
  3510. }
  3511. if(!key){
  3512. return knownContainerType === "belongsTo" ? "oneToNone" : "manyToNone";
  3513. }
  3514. else{
  3515. if(otherContainerType === "belongsTo"){
  3516. return knownContainerType === "belongsTo" ? "oneToOne" : "manyToOne";
  3517. }
  3518. else{
  3519. return knownContainerType === "belongsTo" ? "oneToMany" : "manyToMany";
  3520. }
  3521. }
  3522. };
  3523. DS.RelationshipChange.createChange = function(firstRecordClientId, secondRecordClientId, store, options){
  3524. // Get the type of the child based on the child's client ID
  3525. var firstRecordType = store.typeForClientId(firstRecordClientId), key, changeType;
  3526. changeType = DS.RelationshipChange.determineRelationshipType(firstRecordType, options);
  3527. if (changeType === "oneToMany"){
  3528. return DS.OneToManyChange.createChange(firstRecordClientId, secondRecordClientId, store, options);
  3529. }
  3530. else if (changeType === "manyToOne"){
  3531. return DS.OneToManyChange.createChange(secondRecordClientId, firstRecordClientId, store, options);
  3532. }
  3533. else if (changeType === "oneToNone"){
  3534. return DS.OneToNoneChange.createChange(firstRecordClientId, "", store, options);
  3535. }
  3536. else if (changeType === "manyToNone"){
  3537. return DS.ManyToNoneChange.createChange(firstRecordClientId, "", store, options);
  3538. }
  3539. else if (changeType === "oneToOne"){
  3540. return DS.OneToOneChange.createChange(firstRecordClientId, secondRecordClientId, store, options);
  3541. }
  3542. else if (changeType === "manyToMany"){
  3543. return DS.ManyToManyChange.createChange(firstRecordClientId, secondRecordClientId, store, options);
  3544. }
  3545. };
  3546. /** @private */
  3547. DS.OneToNoneChange.createChange = function(childClientId, parentClientId, store, options) {
  3548. var key = options.key;
  3549. var change = DS.RelationshipChange._createChange({
  3550. firstRecordClientId: childClientId,
  3551. store: store,
  3552. changeType: options.changeType,
  3553. firstRecordName: key,
  3554. firstRecordKind: "belongsTo"
  3555. });
  3556. store.addRelationshipChangeFor(childClientId, key, parentClientId, null, change);
  3557. return change;
  3558. };
  3559. /** @private */
  3560. DS.ManyToNoneChange.createChange = function(childClientId, parentClientId, store, options) {
  3561. var key = options.key;
  3562. var change = DS.RelationshipChange._createChange({
  3563. secondRecordClientId: childClientId,
  3564. store: store,
  3565. changeType: options.changeType,
  3566. secondRecordName: options.key,
  3567. secondRecordKind: "hasMany"
  3568. });
  3569. store.addRelationshipChangeFor(childClientId, key, parentClientId, null, change);
  3570. return change;
  3571. };
  3572. /** @private */
  3573. DS.ManyToManyChange.createChange = function(childClientId, parentClientId, store, options) {
  3574. // Get the type of the child based on the child's client ID
  3575. var childType = store.typeForClientId(childClientId), key;
  3576. // If the name of the belongsTo side of the relationship is specified,
  3577. // use that
  3578. // If the type of the parent is specified, look it up on the child's type
  3579. // definition.
  3580. key = options.key;
  3581. var change = DS.RelationshipChange._createChange({
  3582. firstRecordClientId: childClientId,
  3583. secondRecordClientId: parentClientId,
  3584. firstRecordKind: "hasMany",
  3585. secondRecordKind: "hasMany",
  3586. store: store,
  3587. changeType: options.changeType,
  3588. firstRecordName: key
  3589. });
  3590. store.addRelationshipChangeFor(childClientId, key, parentClientId, null, change);
  3591. return change;
  3592. };
  3593. /** @private */
  3594. DS.OneToOneChange.createChange = function(childClientId, parentClientId, store, options) {
  3595. // Get the type of the child based on the child's client ID
  3596. var childType = store.typeForClientId(childClientId), key;
  3597. // If the name of the belongsTo side of the relationship is specified,
  3598. // use that
  3599. // If the type of the parent is specified, look it up on the child's type
  3600. // definition.
  3601. if (options.parentType) {
  3602. key = inverseBelongsToName(options.parentType, childType, options.key);
  3603. //DS.OneToOneChange.maintainInvariant( options, store, childClientId, key );
  3604. } else if (options.key) {
  3605. key = options.key;
  3606. } else {
  3607. Ember.assert("You must pass either a parentType or belongsToName option to OneToManyChange.forChildAndParent", false);
  3608. }
  3609. var change = DS.RelationshipChange._createChange({
  3610. firstRecordClientId: childClientId,
  3611. secondRecordClientId: parentClientId,
  3612. firstRecordKind: "belongsTo",
  3613. secondRecordKind: "belongsTo",
  3614. store: store,
  3615. changeType: options.changeType,
  3616. firstRecordName: key
  3617. });
  3618. store.addRelationshipChangeFor(childClientId, key, parentClientId, null, change);
  3619. return change;
  3620. };
  3621. DS.OneToOneChange.maintainInvariant = function(options, store, childClientId, key){
  3622. if (options.changeType === "add" && store.recordIsMaterialized(childClientId)) {
  3623. var child = store.findByClientId(null, childClientId);
  3624. var oldParent = get(child, key);
  3625. if (oldParent){
  3626. var correspondingChange = DS.OneToOneChange.createChange(childClientId, oldParent.get('clientId'), store, {
  3627. parentType: options.parentType,
  3628. hasManyName: options.hasManyName,
  3629. changeType: "remove",
  3630. key: options.key
  3631. });
  3632. store.addRelationshipChangeFor(childClientId, key, options.parentClientId , null, correspondingChange);
  3633. correspondingChange.sync();
  3634. }
  3635. }
  3636. };
  3637. /** @private */
  3638. DS.OneToManyChange.createChange = function(childClientId, parentClientId, store, options) {
  3639. // Get the type of the child based on the child's client ID
  3640. var childType = store.typeForClientId(childClientId), key;
  3641. // If the name of the belongsTo side of the relationship is specified,
  3642. // use that
  3643. // If the type of the parent is specified, look it up on the child's type
  3644. // definition.
  3645. if (options.parentType) {
  3646. key = inverseBelongsToName(options.parentType, childType, options.key);
  3647. DS.OneToManyChange.maintainInvariant( options, store, childClientId, key );
  3648. } else if (options.key) {
  3649. key = options.key;
  3650. } else {
  3651. Ember.assert("You must pass either a parentType or belongsToName option to OneToManyChange.forChildAndParent", false);
  3652. }
  3653. var change = DS.RelationshipChange._createChange({
  3654. firstRecordClientId: childClientId,
  3655. secondRecordClientId: parentClientId,
  3656. firstRecordKind: "belongsTo",
  3657. secondRecordKind: "hasMany",
  3658. store: store,
  3659. changeType: options.changeType,
  3660. firstRecordName: key
  3661. });
  3662. store.addRelationshipChangeFor(childClientId, key, parentClientId, null, change);
  3663. return change;
  3664. };
  3665. DS.OneToManyChange.maintainInvariant = function(options, store, childClientId, key){
  3666. if (options.changeType === "add" && store.recordIsMaterialized(childClientId)) {
  3667. var child = store.findByClientId(null, childClientId);
  3668. var oldParent = get(child, key);
  3669. if (oldParent){
  3670. var correspondingChange = DS.OneToManyChange.createChange(childClientId, oldParent.get('clientId'), store, {
  3671. parentType: options.parentType,
  3672. hasManyName: options.hasManyName,
  3673. changeType: "remove",
  3674. key: options.key
  3675. });
  3676. store.addRelationshipChangeFor(childClientId, key, options.parentClientId , null, correspondingChange);
  3677. correspondingChange.sync();
  3678. }
  3679. }
  3680. };
  3681. DS.OneToManyChange.ensureSameTransaction = function(changes, store){
  3682. var records = Ember.A();
  3683. forEach(changes, function(change){
  3684. records.addObject(change.getSecondRecord());
  3685. records.addObject(change.getFirstRecord());
  3686. });
  3687. var transaction = store.ensureSameTransaction(records);
  3688. forEach(changes, function(change){
  3689. change.transaction = transaction;
  3690. });
  3691. };
  3692. DS.RelationshipChange.prototype = {
  3693. /**
  3694. Get the child type and ID, if available.
  3695. @returns {Array} an array of type and ID
  3696. */
  3697. getChildTypeAndId: function() {
  3698. return this.getTypeAndIdFor(this.child);
  3699. },
  3700. getSecondRecordName: function() {
  3701. var name = this.secondRecordName, store = this.store, parent;
  3702. if (!name) {
  3703. parent = this.secondRecordClientId;
  3704. if (!parent) { return; }
  3705. var childType = store.typeForClientId(this.firstRecordClientId);
  3706. var inverseType = DS._inverseTypeFor(childType, this.firstRecordName);
  3707. name = inverseHasManyName(inverseType, childType, this.firstRecordName);
  3708. this.secondRecordName = name;
  3709. }
  3710. return name;
  3711. },
  3712. /**
  3713. Get the name of the relationship on the belongsTo side.
  3714. @returns {String}
  3715. */
  3716. getFirstRecordName: function() {
  3717. var name = this.firstRecordName, store = this.store, parent;
  3718. if (!name) {
  3719. parent = this.secondRecordClientId;
  3720. if (!parent) { return; }
  3721. var childType = store.typeForClientId(this.firstRecordClientId);
  3722. var parentType = store.typeForClientId(parent);
  3723. if (!(childType && parentType)) { return; }
  3724. name = DS._inverseRelationshipFor(childType, parentType).name;
  3725. this.firstRecordName = name;
  3726. }
  3727. return name;
  3728. },
  3729. /** @private */
  3730. getTypeAndIdFor: function(clientId) {
  3731. if (clientId) {
  3732. var store = this.store;
  3733. return [
  3734. store.typeForClientId(clientId),
  3735. store.idForClientId(clientId)
  3736. ];
  3737. }
  3738. },
  3739. /** @private */
  3740. destroy: function() {
  3741. var childClientId = this.firstRecordClientId,
  3742. belongsToName = this.getFirstRecordName(),
  3743. hasManyName = this.getSecondRecordName(),
  3744. store = this.store,
  3745. child, oldParent, newParent, lastParent, transaction;
  3746. store.removeRelationshipChangeFor(childClientId, belongsToName, this.secondRecordClientId, hasManyName, this.changeType);
  3747. if (transaction = this.transaction) {
  3748. transaction.relationshipBecameClean(this);
  3749. }
  3750. },
  3751. /** @private */
  3752. getByClientId: function(clientId) {
  3753. var store = this.store;
  3754. // return null or undefined if the original clientId was null or undefined
  3755. if (!clientId) { return clientId; }
  3756. if (store.recordIsMaterialized(clientId)) {
  3757. return store.findByClientId(null, clientId);
  3758. }
  3759. },
  3760. getSecondRecord: function(){
  3761. return this.getByClientId(this.secondRecordClientId);
  3762. },
  3763. /** @private */
  3764. getFirstRecord: function() {
  3765. return this.getByClientId(this.firstRecordClientId);
  3766. },
  3767. /**
  3768. @private
  3769. Make sure that all three parts of the relationship change are part of
  3770. the same transaction. If any of the three records is clean and in the
  3771. default transaction, and the rest are in a different transaction, move
  3772. them all into that transaction.
  3773. */
  3774. ensureSameTransaction: function() {
  3775. var child = this.getFirstRecord(),
  3776. parentRecord = this.getSecondRecord();
  3777. var transaction = this.store.ensureSameTransaction([child, parentRecord]);
  3778. this.transaction = transaction;
  3779. return transaction;
  3780. },
  3781. callChangeEvents: function(){
  3782. var hasManyName = this.getSecondRecordName(),
  3783. belongsToName = this.getFirstRecordName(),
  3784. child = this.getFirstRecord(),
  3785. parentRecord = this.getSecondRecord();
  3786. var dirtySet = new Ember.OrderedSet();
  3787. // TODO: This implementation causes a race condition in key-value
  3788. // stores. The fix involves buffering changes that happen while
  3789. // a record is loading. A similar fix is required for other parts
  3790. // of ember-data, and should be done as new infrastructure, not
  3791. // a one-off hack. [tomhuda]
  3792. if (parentRecord && get(parentRecord, 'isLoaded')) {
  3793. this.store.recordHasManyDidChange(dirtySet, parentRecord, this);
  3794. }
  3795. if (child) {
  3796. this.store.recordBelongsToDidChange(dirtySet, child, this);
  3797. }
  3798. dirtySet.forEach(function(record) {
  3799. record.adapterDidDirty();
  3800. });
  3801. },
  3802. coalesce: function(){
  3803. var relationshipPairs = this.store.relationshipChangePairsFor(this.firstRecordClientId);
  3804. forEach(relationshipPairs, function(pair){
  3805. var addedChange = pair["add"];
  3806. var removedChange = pair["remove"];
  3807. if(addedChange && removedChange) {
  3808. addedChange.destroy();
  3809. removedChange.destroy();
  3810. }
  3811. });
  3812. }
  3813. };
  3814. DS.RelationshipChangeAdd.prototype = Ember.create(DS.RelationshipChange.create({}));
  3815. DS.RelationshipChangeRemove.prototype = Ember.create(DS.RelationshipChange.create({}));
  3816. DS.RelationshipChangeAdd.prototype.changeType = "add";
  3817. DS.RelationshipChangeAdd.prototype.sync = function() {
  3818. var secondRecordName = this.getSecondRecordName(),
  3819. firstRecordName = this.getFirstRecordName(),
  3820. firstRecord = this.getFirstRecord(),
  3821. secondRecord = this.getSecondRecord();
  3822. //Ember.assert("You specified a hasMany (" + hasManyName + ") on " + (!belongsToName && (newParent || oldParent || this.lastParent).constructor) + " but did not specify an inverse belongsTo on " + child.constructor, belongsToName);
  3823. //Ember.assert("You specified a belongsTo (" + belongsToName + ") on " + child.constructor + " but did not specify an inverse hasMany on " + (!hasManyName && (newParent || oldParent || this.lastParentRecord).constructor), hasManyName);
  3824. var transaction = this.ensureSameTransaction();
  3825. transaction.relationshipBecameDirty(this);
  3826. this.callChangeEvents();
  3827. if (secondRecord && firstRecord) {
  3828. if(this.secondRecordKind === "belongsTo"){
  3829. secondRecord.suspendRelationshipObservers(function(){
  3830. set(secondRecord, secondRecordName, firstRecord);
  3831. });
  3832. }
  3833. else if(this.secondRecordKind === "hasMany"){
  3834. secondRecord.suspendRelationshipObservers(function(){
  3835. get(secondRecord, secondRecordName).addObject(firstRecord);
  3836. });
  3837. }
  3838. }
  3839. if (firstRecord && secondRecord && get(firstRecord, firstRecordName) !== secondRecord) {
  3840. if(this.firstRecordKind === "belongsTo"){
  3841. firstRecord.suspendRelationshipObservers(function(){
  3842. set(firstRecord, firstRecordName, secondRecord);
  3843. });
  3844. }
  3845. else if(this.firstdRecordKind === "hasMany"){
  3846. firstRecord.suspendRelationshipObservers(function(){
  3847. get(firstRecord, firstRecordName).addObject(secondRecord);
  3848. });
  3849. }
  3850. }
  3851. this.coalesce();
  3852. };
  3853. DS.RelationshipChangeRemove.prototype.changeType = "remove";
  3854. DS.RelationshipChangeRemove.prototype.sync = function() {
  3855. var secondRecordName = this.getSecondRecordName(),
  3856. firstRecordName = this.getFirstRecordName(),
  3857. firstRecord = this.getFirstRecord(),
  3858. secondRecord = this.getSecondRecord();
  3859. //Ember.assert("You specified a hasMany (" + hasManyName + ") on " + (!belongsToName && (newParent || oldParent || this.lastParent).constructor) + " but did not specify an inverse belongsTo on " + child.constructor, belongsToName);
  3860. //Ember.assert("You specified a belongsTo (" + belongsToName + ") on " + child.constructor + " but did not specify an inverse hasMany on " + (!hasManyName && (newParent || oldParent || this.lastParentRecord).constructor), hasManyName);
  3861. var transaction = this.ensureSameTransaction(firstRecord, secondRecord, secondRecordName, firstRecordName);
  3862. transaction.relationshipBecameDirty(this);
  3863. this.callChangeEvents();
  3864. if (secondRecord && firstRecord) {
  3865. if(this.secondRecordKind === "belongsTo"){
  3866. set(secondRecord, secondRecordName, null);
  3867. }
  3868. else if(this.secondRecordKind === "hasMany"){
  3869. secondRecord.suspendRelationshipObservers(function(){
  3870. get(secondRecord, secondRecordName).removeObject(firstRecord);
  3871. });
  3872. }
  3873. }
  3874. if (firstRecord && get(firstRecord, firstRecordName)) {
  3875. if(this.firstRecordKind === "belongsTo"){
  3876. firstRecord.suspendRelationshipObservers(function(){
  3877. set(firstRecord, firstRecordName, null);
  3878. });
  3879. }
  3880. else if(this.firstdRecordKind === "hasMany"){
  3881. firstRecord.suspendRelationshipObservers(function(){
  3882. get(firstRecord, firstRecordName).removeObject(secondRecord);
  3883. });
  3884. }
  3885. }
  3886. this.coalesce();
  3887. };
  3888. function inverseBelongsToName(parentType, childType, hasManyName) {
  3889. // Get the options passed to the parent's DS.hasMany()
  3890. var options = parentType.metaForProperty(hasManyName).options;
  3891. var belongsToName;
  3892. if (belongsToName = options.inverse) {
  3893. return belongsToName;
  3894. }
  3895. return DS._inverseRelationshipFor(childType, parentType).name;
  3896. }
  3897. function inverseHasManyName(parentType, childType, belongsToName) {
  3898. var options = childType.metaForProperty(belongsToName).options;
  3899. var hasManyName;
  3900. if (hasManyName = options.inverse) {
  3901. return hasManyName;
  3902. }
  3903. return DS._inverseRelationshipFor(parentType, childType).name;
  3904. }
  3905. })();
  3906. (function() {
  3907. })();
  3908. (function() {
  3909. var set = Ember.set;
  3910. /**
  3911. This code registers an injection for Ember.Application.
  3912. If an Ember.js developer defines a subclass of DS.Store on their application,
  3913. this code will automatically instantiate it and make it available on the
  3914. router.
  3915. Additionally, after an application's controllers have been injected, they will
  3916. each have the store made available to them.
  3917. For example, imagine an Ember.js application with the following classes:
  3918. App.Store = DS.Store.extend({
  3919. adapter: 'App.MyCustomAdapter'
  3920. });
  3921. App.PostsController = Ember.ArrayController.extend({
  3922. // ...
  3923. });
  3924. When the application is initialized, `App.Store` will automatically be
  3925. instantiated, and the instance of `App.PostsController` will have its `store`
  3926. property set to that instance.
  3927. Note that this code will only be run if the `ember-application` package is
  3928. loaded. If Ember Data is being used in an environment other than a
  3929. typical application (e.g., node.js where only `ember-runtime` is available),
  3930. this code will be ignored.
  3931. */
  3932. Ember.onLoad('Ember.Application', function(Application) {
  3933. if (Application.registerInjection) {
  3934. Application.registerInjection({
  3935. name: "store",
  3936. before: "controllers",
  3937. // If a store subclass is defined, like App.Store,
  3938. // instantiate it and inject it into the router.
  3939. injection: function(app, stateManager, property) {
  3940. if (!stateManager) { return; }
  3941. if (property === 'Store') {
  3942. set(stateManager, 'store', app[property].create());
  3943. }
  3944. }
  3945. });
  3946. Application.registerInjection({
  3947. name: "giveStoreToControllers",
  3948. after: ['store','controllers'],
  3949. // For each controller, set its `store` property
  3950. // to the DS.Store instance we created above.
  3951. injection: function(app, stateManager, property) {
  3952. if (!stateManager) { return; }
  3953. if (/^[A-Z].*Controller$/.test(property)) {
  3954. var controllerName = property.charAt(0).toLowerCase() + property.substr(1);
  3955. var store = stateManager.get('store');
  3956. var controller = stateManager.get(controllerName);
  3957. if(!controller) { return; }
  3958. controller.set('store', store);
  3959. }
  3960. }
  3961. });
  3962. } else if (Application.initializer) {
  3963. Application.initializer({
  3964. name: "store",
  3965. initialize: function(container, application) {
  3966. container.register('store', 'main', application.Store);
  3967. // Eagerly generate the store so defaultStore is populated.
  3968. // TODO: Do this in a finisher hook
  3969. container.lookup('store:main');
  3970. }
  3971. });
  3972. Application.initializer({
  3973. name: "injectStore",
  3974. initialize: function(container) {
  3975. container.typeInjection('controller', 'store', 'store:main');
  3976. container.typeInjection('route', 'store', 'store:main');
  3977. }
  3978. });
  3979. }
  3980. });
  3981. })();
  3982. (function() {
  3983. var get = Ember.get, set = Ember.set, map = Ember.ArrayPolyfills.map, isNone = Ember.isNone;
  3984. function mustImplement(name) {
  3985. return function() {
  3986. throw new Ember.Error("Your serializer " + this.toString() + " does not implement the required method " + name);
  3987. };
  3988. }
  3989. /**
  3990. A serializer is responsible for serializing and deserializing a group of
  3991. records.
  3992. `DS.Serializer` is an abstract base class designed to help you build a
  3993. serializer that can read to and write from any serialized form. While most
  3994. applications will use `DS.JSONSerializer`, which reads and writes JSON, the
  3995. serializer architecture allows your adapter to transmit things like XML,
  3996. strings, or custom binary data.
  3997. Typically, your application's `DS.Adapter` is responsible for both creating a
  3998. serializer as well as calling the appropriate methods when it needs to
  3999. materialize data or serialize a record.
  4000. The serializer API is designed as a series of layered hooks that you can
  4001. override to customize any of the individual steps of serialization and
  4002. deserialization.
  4003. The hooks are organized by the three responsibilities of the serializer:
  4004. 1. Determining naming conventions
  4005. 2. Serializing records into a serialized form
  4006. 3. Deserializing records from a serialized form
  4007. Because Ember Data lazily materializes records, the deserialization
  4008. step, and therefore the hooks you implement, are split into two phases:
  4009. 1. Extraction, where the serialized forms for multiple records are
  4010. extracted from a single payload. The IDs of each record are also
  4011. extracted for indexing.
  4012. 2. Materialization, where a newly-created record has its attributes
  4013. and relationships initialized based on the serialized form loaded
  4014. by the adapter.
  4015. Additionally, a serializer can convert values from their JavaScript
  4016. versions into their serialized versions via a declarative API.
  4017. ## Naming Conventions
  4018. One of the most common uses of the serializer is to map attribute names
  4019. from the serialized form to your `DS.Model`. For example, in your model,
  4020. you may have an attribute called `firstName`:
  4021. ```javascript
  4022. App.Person = DS.Model.extend({
  4023. firstName: DS.attr('string')
  4024. });
  4025. ```
  4026. However, because the web API your adapter is communicating with is
  4027. legacy, it calls this attribute `FIRST_NAME`.
  4028. You can determine the attribute name used in the serialized form
  4029. by implementing `keyForAttributeName`:
  4030. ```javascript
  4031. keyForAttributeName: function(type, name) {
  4032. return name.underscore.toUpperCase();
  4033. }
  4034. ```
  4035. If your attribute names are not predictable, you can re-map them
  4036. one-by-one using the `map` API:
  4037. ```javascript
  4038. App.Person.map('App.Person', {
  4039. firstName: { key: '*API_USER_FIRST_NAME*' }
  4040. });
  4041. ```
  4042. ## Serialization
  4043. During the serialization process, a record or records are converted
  4044. from Ember.js objects into their serialized form.
  4045. These methods are designed in layers, like a delicious 7-layer
  4046. cake (but with fewer layers).
  4047. The main entry point for serialization is the `serialize`
  4048. method, which takes the record and options.
  4049. The `serialize` method is responsible for:
  4050. * turning the record's attributes (`DS.attr`) into
  4051. attributes on the JSON object.
  4052. * optionally adding the record's ID onto the hash
  4053. * adding relationships (`DS.hasMany` and `DS.belongsTo`)
  4054. to the JSON object.
  4055. Depending on the backend, the serializer can choose
  4056. whether to include the `hasMany` or `belongsTo`
  4057. relationships on the JSON hash.
  4058. For very custom serialization, you can implement your
  4059. own `serialize` method. In general, however, you will want
  4060. to override the hooks described below.
  4061. ### Adding the ID
  4062. The default `serialize` will optionally call your serializer's
  4063. `addId` method with the JSON hash it is creating, the
  4064. record's type, and the record's ID. The `serialize` method
  4065. will not call `addId` if the record's ID is undefined.
  4066. Your adapter must specifically request ID inclusion by
  4067. passing `{ includeId: true }` as an option to `serialize`.
  4068. NOTE: You may not want to include the ID when updating an
  4069. existing record, because your server will likely disallow
  4070. changing an ID after it is created, and the PUT request
  4071. itself will include the record's identification.
  4072. By default, `addId` will:
  4073. 1. Get the primary key name for the record by calling
  4074. the serializer's `primaryKey` with the record's type.
  4075. Unless you override the `primaryKey` method, this
  4076. will be `'id'`.
  4077. 2. Assign the record's ID to the primary key in the
  4078. JSON hash being built.
  4079. If your backend expects a JSON object with the primary
  4080. key at the root, you can just override the `primaryKey`
  4081. method on your serializer subclass.
  4082. Otherwise, you can override the `addId` method for
  4083. more specialized handling.
  4084. ### Adding Attributes
  4085. By default, the serializer's `serialize` method will call
  4086. `addAttributes` with the JSON object it is creating
  4087. and the record to serialize.
  4088. The `addAttributes` method will then call `addAttribute`
  4089. in turn, with the JSON object, the record to serialize,
  4090. the attribute's name and its type.
  4091. Finally, the `addAttribute` method will serialize the
  4092. attribute:
  4093. 1. It will call `keyForAttributeName` to determine
  4094. the key to use in the JSON hash.
  4095. 2. It will get the value from the record.
  4096. 3. It will call `serializeValue` with the attribute's
  4097. value and attribute type to convert it into a
  4098. JSON-compatible value. For example, it will convert a
  4099. Date into a String.
  4100. If your backend expects a JSON object with attributes as
  4101. keys at the root, you can just override the `serializeValue`
  4102. and `keyForAttributeName` methods in your serializer
  4103. subclass and let the base class do the heavy lifting.
  4104. If you need something more specialized, you can probably
  4105. override `addAttribute` and let the default `addAttributes`
  4106. handle the nitty gritty.
  4107. ### Adding Relationships
  4108. By default, `serialize` will call your serializer's
  4109. `addRelationships` method with the JSON object that is
  4110. being built and the record being serialized. The default
  4111. implementation of this method is to loop over all of the
  4112. relationships defined on your record type and:
  4113. * If the relationship is a `DS.hasMany` relationship,
  4114. call `addHasMany` with the JSON object, the record
  4115. and a description of the relationship.
  4116. * If the relationship is a `DS.belongsTo` relationship,
  4117. call `addBelongsTo` with the JSON object, the record
  4118. and a description of the relationship.
  4119. The relationship description has the following keys:
  4120. * `type`: the class of the associated information (the
  4121. first parameter to `DS.hasMany` or `DS.belongsTo`)
  4122. * `kind`: either `hasMany` or `belongsTo`
  4123. The relationship description may get additional
  4124. information in the future if more capabilities or
  4125. relationship types are added. However, it will
  4126. remain backwards-compatible, so the mere existence
  4127. of new features should not break existing adapters.
  4128. */
  4129. DS.Serializer = Ember.Object.extend({
  4130. init: function() {
  4131. this.mappings = Ember.Map.create();
  4132. this.configurations = Ember.Map.create();
  4133. this.globalConfigurations = {};
  4134. },
  4135. extract: mustImplement('extract'),
  4136. extractMany: mustImplement('extractMany'),
  4137. extractRecordRepresentation: function(loader, type, json, shouldSideload) {
  4138. var mapping = this.mappingForType(type);
  4139. var embeddedData, prematerialized = {}, reference;
  4140. if (shouldSideload) {
  4141. reference = loader.sideload(type, json);
  4142. } else {
  4143. reference = loader.load(type, json);
  4144. }
  4145. this.eachEmbeddedHasMany(type, function(name, relationship) {
  4146. var embeddedData = json[this.keyFor(relationship)];
  4147. if (!isNone(embeddedData)) {
  4148. this.extractEmbeddedHasMany(loader, relationship, embeddedData, reference, prematerialized);
  4149. }
  4150. }, this);
  4151. this.eachEmbeddedBelongsTo(type, function(name, relationship) {
  4152. var embeddedData = json[this.keyFor(relationship)];
  4153. if (!isNone(embeddedData)) {
  4154. this.extractEmbeddedBelongsTo(loader, relationship, embeddedData, reference, prematerialized);
  4155. }
  4156. }, this);
  4157. loader.prematerialize(reference, prematerialized);
  4158. return reference;
  4159. },
  4160. extractEmbeddedHasMany: function(loader, relationship, array, parent, prematerialized) {
  4161. var references = map.call(array, function(item) {
  4162. if (!item) { return; }
  4163. var reference = this.extractRecordRepresentation(loader, relationship.type, item, true);
  4164. // If the embedded record should also be saved back when serializing the parent,
  4165. // make sure we set its parent since it will not have an ID.
  4166. var embeddedType = this.embeddedType(parent.type, relationship.key);
  4167. if (embeddedType === 'always') {
  4168. reference.parent = parent;
  4169. }
  4170. return reference;
  4171. }, this);
  4172. prematerialized[relationship.key] = references;
  4173. },
  4174. extractEmbeddedBelongsTo: function(loader, relationship, data, parent, prematerialized) {
  4175. var reference = loader.sideload(relationship.type, data);
  4176. prematerialized[relationship.key] = reference;
  4177. // If the embedded record should also be saved back when serializing the parent,
  4178. // make sure we set its parent since it will not have an ID.
  4179. var embeddedType = this.embeddedType(parent.type, relationship.key);
  4180. if (embeddedType === 'always') {
  4181. reference.parent = parent;
  4182. }
  4183. },
  4184. //.......................
  4185. //. SERIALIZATION HOOKS
  4186. //.......................
  4187. /**
  4188. The main entry point for serializing a record. While you can consider this
  4189. a hook that can be overridden in your serializer, you will have to manually
  4190. handle serialization. For most cases, there are more granular hooks that you
  4191. can override.
  4192. If overriding this method, these are the responsibilities that you will need
  4193. to implement yourself:
  4194. * If the option hash contains `includeId`, add the record's ID to the serialized form.
  4195. By default, `serialize` calls `addId` if appropriate.
  4196. * Add the record's attributes to the serialized form. By default, `serialize` calls
  4197. `addAttributes`.
  4198. * Add the record's relationships to the serialized form. By default, `serialize` calls
  4199. `addRelationships`.
  4200. @param {DS.Model} record the record to serialize
  4201. @param {Object} [options] a hash of options
  4202. @returns {any} the serialized form of the record
  4203. */
  4204. serialize: function(record, options) {
  4205. options = options || {};
  4206. var serialized = this.createSerializedForm(), id;
  4207. if (options.includeId) {
  4208. if (id = get(record, 'id')) {
  4209. this._addId(serialized, record.constructor, id);
  4210. }
  4211. }
  4212. this.addAttributes(serialized, record);
  4213. this.addRelationships(serialized, record);
  4214. return serialized;
  4215. },
  4216. /**
  4217. @private
  4218. Given an attribute type and value, convert the value into the
  4219. serialized form using the transform registered for that type.
  4220. @param {any} value the value to convert to the serialized form
  4221. @param {String} attributeType the registered type (e.g. `string`
  4222. or `boolean`)
  4223. @returns {any} the serialized form of the value
  4224. */
  4225. serializeValue: function(value, attributeType) {
  4226. var transform = this.transforms ? this.transforms[attributeType] : null;
  4227. Ember.assert("You tried to use an attribute type (" + attributeType + ") that has not been registered", transform);
  4228. return transform.serialize(value);
  4229. },
  4230. /**
  4231. A hook you can use to normalize IDs before adding them to the
  4232. serialized representation.
  4233. Because the store coerces all IDs to strings for consistency,
  4234. this is the opportunity for the serializer to, for example,
  4235. convert numerical IDs back into number form.
  4236. @param {String} id the id from the record
  4237. @returns {any} the serialized representation of the id
  4238. */
  4239. serializeId: function(id) {
  4240. if (isNaN(id)) { return id; }
  4241. return +id;
  4242. },
  4243. /**
  4244. A hook you can use to change how attributes are added to the serialized
  4245. representation of a record.
  4246. By default, `addAttributes` simply loops over all of the attributes of the
  4247. passed record, maps the attribute name to the key for the serialized form,
  4248. and invokes any registered transforms on the value. It then invokes the
  4249. more granular `addAttribute` with the key and transformed value.
  4250. Since you can override `keyForAttributeName`, `addAttribute`, and register
  4251. custom tranforms, you should rarely need to override this hook.
  4252. @param {any} data the serialized representation that is being built
  4253. @param {DS.Model} record the record to serialize
  4254. */
  4255. addAttributes: function(data, record) {
  4256. record.eachAttribute(function(name, attribute) {
  4257. this._addAttribute(data, record, name, attribute.type);
  4258. }, this);
  4259. },
  4260. /**
  4261. A hook you can use to customize how the key/value pair is added to
  4262. the serialized data.
  4263. @param {any} serialized the serialized form being built
  4264. @param {String} key the key to add to the serialized data
  4265. @param {any} value the value to add to the serialized data
  4266. */
  4267. addAttribute: Ember.K,
  4268. /**
  4269. A hook you can use to customize how the record's id is added to
  4270. the serialized data.
  4271. The `addId` hook is called with:
  4272. * the serialized representation being built
  4273. * the resolved primary key (taking configurations and the
  4274. `primaryKey` hook into consideration)
  4275. * the serialized id (after calling the `serializeId` hook)
  4276. @param {any} data the serialized representation that is being built
  4277. @param {String} key the resolved primary key
  4278. @param {id} id the serialized id
  4279. */
  4280. addId: Ember.K,
  4281. /**
  4282. A hook you can use to change how relationships are added to the serialized
  4283. representation of a record.
  4284. By default, `addAttributes` loops over all of the relationships of the
  4285. passed record, maps the relationship names to the key for the serialized form,
  4286. and then invokes the public `addBelongsTo` and `addHasMany` hooks.
  4287. Since you can override `keyForBelongsTo`, `keyForHasMany`, `addBelongsTo`,
  4288. `addHasMany`, and register mappings, you should rarely need to override this
  4289. hook.
  4290. @param {any} data the serialized representation that is being built
  4291. @param {DS.Model} record the record to serialize
  4292. */
  4293. addRelationships: function(data, record) {
  4294. record.eachRelationship(function(name, relationship) {
  4295. if (relationship.kind === 'belongsTo') {
  4296. this._addBelongsTo(data, record, name, relationship);
  4297. } else if (relationship.kind === 'hasMany') {
  4298. this._addHasMany(data, record, name, relationship);
  4299. }
  4300. }, this);
  4301. },
  4302. /**
  4303. A hook you can use to add a `belongsTo` relationship to the
  4304. serialized representation.
  4305. The specifics of this hook are very adapter-specific, so there
  4306. is no default implementation. You can see `DS.JSONSerializer`
  4307. for an example of an implementation of the `addBelongsTo` hook.
  4308. The `belongsTo` relationship object has the following properties:
  4309. * **type** a subclass of DS.Model that is the type of the
  4310. relationship. This is the first parameter to DS.belongsTo
  4311. * **options** the options passed to the call to DS.belongsTo
  4312. * **kind** always `belongsTo`
  4313. Additional properties may be added in the future.
  4314. @param {any} data the serialized representation that is being built
  4315. @param {DS.Model} record the record to serialize
  4316. @param {String} key the key for the serialized object
  4317. @param {Object} relationship an object representing the relationship
  4318. */
  4319. addBelongsTo: Ember.K,
  4320. /**
  4321. A hook you can use to add a `hasMany` relationship to the
  4322. serialized representation.
  4323. The specifics of this hook are very adapter-specific, so there
  4324. is no default implementation. You may not need to implement this,
  4325. for example, if your backend only expects relationships on the
  4326. child of a one to many relationship.
  4327. The `hasMany` relationship object has the following properties:
  4328. * **type** a subclass of DS.Model that is the type of the
  4329. relationship. This is the first parameter to DS.hasMany
  4330. * **options** the options passed to the call to DS.hasMany
  4331. * **kind** always `hasMany`
  4332. Additional properties may be added in the future.
  4333. @param {any} data the serialized representation that is being built
  4334. @param {DS.Model} record the record to serialize
  4335. @param {String} key the key for the serialized object
  4336. @param {Object} relationship an object representing the relationship
  4337. */
  4338. addHasMany: Ember.K,
  4339. /**
  4340. NAMING CONVENTIONS
  4341. The most commonly overridden APIs of the serializer are
  4342. the naming convention methods:
  4343. * `keyForAttributeName`: converts a camelized attribute name
  4344. into a key in the adapter-provided data hash. For example,
  4345. if the model's attribute name was `firstName`, and the
  4346. server used underscored names, you would return `first_name`.
  4347. * `primaryKey`: returns the key that should be used to
  4348. extract the id from the adapter-provided data hash. It is
  4349. also used when serializing a record.
  4350. */
  4351. /**
  4352. A hook you can use in your serializer subclass to customize
  4353. how an unmapped attribute name is converted into a key.
  4354. By default, this method returns the `name` parameter.
  4355. For example, if the attribute names in your JSON are underscored,
  4356. you will want to convert them into JavaScript conventional
  4357. camelcase:
  4358. ```javascript
  4359. App.MySerializer = DS.Serializer.extend({
  4360. // ...
  4361. keyForAttributeName: function(type, name) {
  4362. return name.camelize();
  4363. }
  4364. });
  4365. ```
  4366. @param {DS.Model subclass} type the type of the record with
  4367. the attribute name `name`
  4368. @param {String} name the attribute name to convert into a key
  4369. @returns {String} the key
  4370. */
  4371. keyForAttributeName: function(type, name) {
  4372. return name;
  4373. },
  4374. /**
  4375. A hook you can use in your serializer to specify a conventional
  4376. primary key.
  4377. By default, this method will return the string `id`.
  4378. In general, you should not override this hook to specify a special
  4379. primary key for an individual type; use `configure` instead.
  4380. For example, if your primary key is always `__id__`:
  4381. ```javascript
  4382. App.MySerializer = DS.Serializer.extend({
  4383. // ...
  4384. primaryKey: function(type) {
  4385. return '__id__';
  4386. }
  4387. });
  4388. ```
  4389. In another example, if the primary key always includes the
  4390. underscored version of the type before the string `id`:
  4391. ```javascript
  4392. App.MySerializer = DS.Serializer.extend({
  4393. // ...
  4394. primaryKey: function(type) {
  4395. // If the type is `BlogPost`, this will return
  4396. // `blog_post_id`.
  4397. var typeString = type.toString.split(".")[1].underscore();
  4398. return typeString + "_id";
  4399. }
  4400. });
  4401. ```
  4402. @param {DS.Model subclass} type
  4403. @returns {String} the primary key for the type
  4404. */
  4405. primaryKey: function(type) {
  4406. return "id";
  4407. },
  4408. /**
  4409. A hook you can use in your serializer subclass to customize
  4410. how an unmapped `belongsTo` relationship is converted into
  4411. a key.
  4412. By default, this method calls `keyForAttributeName`, so if
  4413. your naming convention is uniform across attributes and
  4414. relationships, you can use the default here and override
  4415. just `keyForAttributeName` as needed.
  4416. For example, if the `belongsTo` names in your JSON always
  4417. begin with `BT_` (e.g. `BT_posts`), you can strip out the
  4418. `BT_` prefix:"
  4419. ```javascript
  4420. App.MySerializer = DS.Serializer.extend({
  4421. // ...
  4422. keyForBelongsTo: function(type, name) {
  4423. return name.match(/^BT_(.*)$/)[1].camelize();
  4424. }
  4425. });
  4426. ```
  4427. @param {DS.Model subclass} type the type of the record with
  4428. the `belongsTo` relationship.
  4429. @param {String} name the relationship name to convert into a key
  4430. @returns {String} the key
  4431. */
  4432. keyForBelongsTo: function(type, name) {
  4433. return this.keyForAttributeName(type, name);
  4434. },
  4435. /**
  4436. A hook you can use in your serializer subclass to customize
  4437. how an unmapped `hasMany` relationship is converted into
  4438. a key.
  4439. By default, this method calls `keyForAttributeName`, so if
  4440. your naming convention is uniform across attributes and
  4441. relationships, you can use the default here and override
  4442. just `keyForAttributeName` as needed.
  4443. For example, if the `hasMany` names in your JSON always
  4444. begin with the "table name" for the current type (e.g.
  4445. `post_comments`), you can strip out the prefix:"
  4446. ```javascript
  4447. App.MySerializer = DS.Serializer.extend({
  4448. // ...
  4449. keyForHasMany: function(type, name) {
  4450. // if your App.BlogPost has many App.BlogComment, the key from
  4451. // the server would look like: `blog_post_blog_comments`
  4452. //
  4453. // 1. Convert the type into a string and underscore the
  4454. // second part (App.BlogPost -> blog_post)
  4455. // 2. Extract the part after `blog_post_` (`blog_comments`)
  4456. // 3. Underscore it, to become `blogComments`
  4457. var typeString = type.toString().split(".")[1].underscore();
  4458. return name.match(new RegExp("^" + typeString + "_(.*)$"))[1].camelize();
  4459. }
  4460. });
  4461. ```
  4462. @param {DS.Model subclass} type the type of the record with
  4463. the `belongsTo` relationship.
  4464. @param {String} name the relationship name to convert into a key
  4465. @returns {String} the key
  4466. */
  4467. keyForHasMany: function(type, name) {
  4468. return this.keyForAttributeName(type, name);
  4469. },
  4470. //.........................
  4471. //. MATERIALIZATION HOOKS
  4472. //.........................
  4473. materialize: function(record, serialized, prematerialized) {
  4474. var id;
  4475. if (Ember.isNone(get(record, 'id'))) {
  4476. if (prematerialized && prematerialized.hasOwnProperty('id')) {
  4477. id = prematerialized.id;
  4478. } else {
  4479. id = this.extractId(record.constructor, serialized);
  4480. }
  4481. record.materializeId(id);
  4482. }
  4483. this.materializeAttributes(record, serialized, prematerialized);
  4484. this.materializeRelationships(record, serialized, prematerialized);
  4485. },
  4486. deserializeValue: function(value, attributeType) {
  4487. var transform = this.transforms ? this.transforms[attributeType] : null;
  4488. Ember.assert("You tried to use a attribute type (" + attributeType + ") that has not been registered", transform);
  4489. return transform.deserialize(value);
  4490. },
  4491. materializeAttributes: function(record, serialized, prematerialized) {
  4492. record.eachAttribute(function(name, attribute) {
  4493. if (prematerialized && prematerialized.hasOwnProperty(name)) {
  4494. record.materializeAttribute(name, prematerialized[name]);
  4495. } else {
  4496. this.materializeAttribute(record, serialized, name, attribute.type);
  4497. }
  4498. }, this);
  4499. },
  4500. materializeAttribute: function(record, serialized, attributeName, attributeType) {
  4501. var value = this.extractAttribute(record.constructor, serialized, attributeName);
  4502. value = this.deserializeValue(value, attributeType);
  4503. record.materializeAttribute(attributeName, value);
  4504. },
  4505. materializeRelationships: function(record, hash, prematerialized) {
  4506. record.eachRelationship(function(name, relationship) {
  4507. if (relationship.kind === 'hasMany') {
  4508. if (prematerialized && prematerialized.hasOwnProperty(name)) {
  4509. record.materializeHasMany(name, prematerialized[name]);
  4510. } else {
  4511. this.materializeHasMany(name, record, hash, relationship, prematerialized);
  4512. }
  4513. } else if (relationship.kind === 'belongsTo') {
  4514. if (prematerialized && prematerialized.hasOwnProperty(name)) {
  4515. record.materializeBelongsTo(name, prematerialized[name]);
  4516. } else {
  4517. this.materializeBelongsTo(name, record, hash, relationship, prematerialized);
  4518. }
  4519. }
  4520. }, this);
  4521. },
  4522. materializeHasMany: function(name, record, hash, relationship) {
  4523. var key = this._keyForHasMany(record.constructor, relationship.key);
  4524. record.materializeHasMany(name, this.extractHasMany(record.constructor, hash, key));
  4525. },
  4526. materializeBelongsTo: function(name, record, hash, relationship) {
  4527. var key = this._keyForBelongsTo(record.constructor, relationship.key);
  4528. record.materializeBelongsTo(name, this.extractBelongsTo(record.constructor, hash, key));
  4529. },
  4530. _extractEmbeddedRelationship: function(type, hash, name, relationshipType) {
  4531. var key = this['_keyFor' + relationshipType](type, name);
  4532. if (this.embeddedType(type, name)) {
  4533. return this['extractEmbedded' + relationshipType](type, hash, key);
  4534. }
  4535. },
  4536. _extractEmbeddedBelongsTo: function(type, hash, name) {
  4537. return this._extractEmbeddedRelationship(type, hash, name, 'BelongsTo');
  4538. },
  4539. _extractEmbeddedHasMany: function(type, hash, name) {
  4540. return this._extractEmbeddedRelationship(type, hash, name, 'HasMany');
  4541. },
  4542. /**
  4543. @private
  4544. This method is called to get the primary key for a given
  4545. type.
  4546. If a primary key configuration exists for this type, this
  4547. method will return the configured value. Otherwise, it will
  4548. call the public `primaryKey` hook.
  4549. @param {DS.Model subclass} type
  4550. @returns {String} the primary key for the type
  4551. */
  4552. _primaryKey: function(type) {
  4553. var config = this.configurationForType(type),
  4554. primaryKey = config && config.primaryKey;
  4555. if (primaryKey) {
  4556. return primaryKey;
  4557. } else {
  4558. return this.primaryKey(type);
  4559. }
  4560. },
  4561. /**
  4562. @private
  4563. This method looks up the key for the attribute name and transforms the
  4564. attribute's value using registered transforms.
  4565. Specifically:
  4566. 1. Look up the key for the attribute name. If available, this will use
  4567. any registered mappings. Otherwise, it will invoke the public
  4568. `keyForAttributeName` hook.
  4569. 2. Get the value from the record using the `attributeName`.
  4570. 3. Transform the value using registered transforms for the `attributeType`.
  4571. 4. Invoke the public `addAttribute` hook with the hash, key, and
  4572. transformed value.
  4573. @param {any} data the serialized representation being built
  4574. @param {DS.Model} record the record to serialize
  4575. @param {String} attributeName the name of the attribute on the record
  4576. @param {String} attributeType the type of the attribute (e.g. `string`
  4577. or `boolean`)
  4578. */
  4579. _addAttribute: function(data, record, attributeName, attributeType) {
  4580. var key = this._keyForAttributeName(record.constructor, attributeName);
  4581. var value = get(record, attributeName);
  4582. this.addAttribute(data, key, this.serializeValue(value, attributeType));
  4583. },
  4584. /**
  4585. @private
  4586. This method looks up the primary key for the `type` and invokes
  4587. `serializeId` on the `id`.
  4588. It then invokes the public `addId` hook with the primary key and
  4589. the serialized id.
  4590. @param {any} data the serialized representation that is being built
  4591. @param {Ember.Model subclass} type
  4592. @param {any} id the materialized id from the record
  4593. */
  4594. _addId: function(hash, type, id) {
  4595. var primaryKey = this._primaryKey(type);
  4596. this.addId(hash, primaryKey, this.serializeId(id));
  4597. },
  4598. /**
  4599. @private
  4600. This method is called to get a key used in the data from
  4601. an attribute name. It first checks for any mappings before
  4602. calling the public hook `keyForAttributeName`.
  4603. @param {DS.Model subclass} type the type of the record with
  4604. the attribute name `name`
  4605. @param {String} name the attribute name to convert into a key
  4606. @returns {String} the key
  4607. */
  4608. _keyForAttributeName: function(type, name) {
  4609. return this._keyFromMappingOrHook('keyForAttributeName', type, name);
  4610. },
  4611. /**
  4612. @private
  4613. This method is called to get a key used in the data from
  4614. a belongsTo relationship. It first checks for any mappings before
  4615. calling the public hook `keyForBelongsTo`.
  4616. @param {DS.Model subclass} type the type of the record with
  4617. the `belongsTo` relationship.
  4618. @param {String} name the relationship name to convert into a key
  4619. @returns {String} the key
  4620. */
  4621. _keyForBelongsTo: function(type, name) {
  4622. return this._keyFromMappingOrHook('keyForBelongsTo', type, name);
  4623. },
  4624. keyFor: function(description) {
  4625. var type = description.parentType,
  4626. name = description.key;
  4627. switch (description.kind) {
  4628. case 'belongsTo':
  4629. return this._keyForBelongsTo(type, name);
  4630. case 'hasMany':
  4631. return this._keyForHasMany(type, name);
  4632. }
  4633. },
  4634. /**
  4635. @private
  4636. This method is called to get a key used in the data from
  4637. a hasMany relationship. It first checks for any mappings before
  4638. calling the public hook `keyForHasMany`.
  4639. @param {DS.Model subclass} type the type of the record with
  4640. the `hasMany` relationship.
  4641. @param {String} name the relationship name to convert into a key
  4642. @returns {String} the key
  4643. */
  4644. _keyForHasMany: function(type, name) {
  4645. return this._keyFromMappingOrHook('keyForHasMany', type, name);
  4646. },
  4647. /**
  4648. @private
  4649. This method converts the relationship name to a key for serialization,
  4650. and then invokes the public `addBelongsTo` hook.
  4651. @param {any} data the serialized representation that is being built
  4652. @param {DS.Model} record the record to serialize
  4653. @param {String} name the relationship name
  4654. @param {Object} relationship an object representing the relationship
  4655. */
  4656. _addBelongsTo: function(data, record, name, relationship) {
  4657. var key = this._keyForBelongsTo(record.constructor, name);
  4658. this.addBelongsTo(data, record, key, relationship);
  4659. },
  4660. /**
  4661. @private
  4662. This method converts the relationship name to a key for serialization,
  4663. and then invokes the public `addHasMany` hook.
  4664. @param {any} data the serialized representation that is being built
  4665. @param {DS.Model} record the record to serialize
  4666. @param {String} name the relationship name
  4667. @param {Object} relationship an object representing the relationship
  4668. */
  4669. _addHasMany: function(data, record, name, relationship) {
  4670. var key = this._keyForHasMany(record.constructor, name);
  4671. this.addHasMany(data, record, key, relationship);
  4672. },
  4673. /**
  4674. @private
  4675. An internal method that handles checking whether a mapping
  4676. exists for a particular attribute or relationship name before
  4677. calling the public hooks.
  4678. If a mapping is found, and the mapping has a key defined,
  4679. use that instead of invoking the hook.
  4680. @param {String} publicMethod the public hook to invoke if
  4681. a mapping is not found (e.g. `keyForAttributeName`)
  4682. @param {DS.Model subclass} type the type of the record with
  4683. the attribute or relationship name.
  4684. @param {String} name the attribute or relationship name to
  4685. convert into a key
  4686. */
  4687. _keyFromMappingOrHook: function(publicMethod, type, name) {
  4688. var key = this.mappingOption(type, name, 'key');
  4689. if (key) {
  4690. return key;
  4691. } else {
  4692. return this[publicMethod](type, name);
  4693. }
  4694. },
  4695. /**
  4696. TRANSFORMS
  4697. */
  4698. registerTransform: function(type, transform) {
  4699. this.transforms[type] = transform;
  4700. },
  4701. registerEnumTransform: function(type, objects) {
  4702. var transform = {
  4703. deserialize: function(deserialized) {
  4704. return objects.objectAt(deserialized);
  4705. },
  4706. serialize: function(serialized) {
  4707. return objects.indexOf(serialized);
  4708. },
  4709. values: objects
  4710. };
  4711. this.registerTransform(type, transform);
  4712. },
  4713. /**
  4714. MAPPING CONVENIENCE
  4715. */
  4716. map: function(type, mappings) {
  4717. this.mappings.set(type, mappings);
  4718. },
  4719. configure: function(type, configuration) {
  4720. if (type && !configuration) {
  4721. Ember.merge(this.globalConfigurations, type);
  4722. return;
  4723. }
  4724. var config = Ember.create(this.globalConfigurations);
  4725. Ember.merge(config, configuration);
  4726. this.configurations.set(type, config);
  4727. },
  4728. mappingForType: function(type) {
  4729. this._reifyMappings();
  4730. return this.mappings.get(type) || {};
  4731. },
  4732. configurationForType: function(type) {
  4733. this._reifyConfigurations();
  4734. return this.configurations.get(type) || this.globalConfigurations;
  4735. },
  4736. _reifyMappings: function() {
  4737. if (this._didReifyMappings) { return; }
  4738. var mappings = this.mappings,
  4739. reifiedMappings = Ember.Map.create();
  4740. mappings.forEach(function(key, mapping) {
  4741. if (typeof key === 'string') {
  4742. var type = Ember.get(Ember.lookup, key);
  4743. Ember.assert("Could not find model at path " + key, type);
  4744. reifiedMappings.set(type, mapping);
  4745. } else {
  4746. reifiedMappings.set(key, mapping);
  4747. }
  4748. });
  4749. this.mappings = reifiedMappings;
  4750. this._didReifyMappings = true;
  4751. },
  4752. _reifyConfigurations: function() {
  4753. if (this._didReifyConfigurations) { return; }
  4754. var configurations = this.configurations,
  4755. reifiedConfigurations = Ember.Map.create();
  4756. configurations.forEach(function(key, mapping) {
  4757. if (typeof key === 'string' && key !== 'plurals') {
  4758. var type = Ember.get(Ember.lookup, key);
  4759. Ember.assert("Could not find model at path " + key, type);
  4760. reifiedConfigurations.set(type, mapping);
  4761. } else {
  4762. reifiedConfigurations.set(key, mapping);
  4763. }
  4764. });
  4765. this.configurations = reifiedConfigurations;
  4766. this._didReifyConfigurations = true;
  4767. },
  4768. mappingOption: function(type, name, option) {
  4769. var mapping = this.mappingForType(type)[name];
  4770. return mapping && mapping[option];
  4771. },
  4772. configOption: function(type, option) {
  4773. var config = this.configurationForType(type);
  4774. return config[option];
  4775. },
  4776. // EMBEDDED HELPERS
  4777. embeddedType: function(type, name) {
  4778. return this.mappingOption(type, name, 'embedded');
  4779. },
  4780. eachEmbeddedRecord: function(record, callback, binding) {
  4781. this.eachEmbeddedBelongsToRecord(record, callback, binding);
  4782. this.eachEmbeddedHasManyRecord(record, callback, binding);
  4783. },
  4784. eachEmbeddedBelongsToRecord: function(record, callback, binding) {
  4785. var type = record.constructor;
  4786. this.eachEmbeddedBelongsTo(record.constructor, function(name, relationship, embeddedType) {
  4787. var embeddedRecord = get(record, name);
  4788. if (embeddedRecord) { callback.call(binding, embeddedRecord, embeddedType); }
  4789. });
  4790. },
  4791. eachEmbeddedHasManyRecord: function(record, callback, binding) {
  4792. var type = record.constructor;
  4793. this.eachEmbeddedHasMany(record.constructor, function(name, relationship, embeddedType) {
  4794. var array = get(record, name);
  4795. for (var i=0, l=get(array, 'length'); i<l; i++) {
  4796. callback.call(binding, array.objectAt(i), embeddedType);
  4797. }
  4798. });
  4799. },
  4800. eachEmbeddedHasMany: function(type, callback, binding) {
  4801. this.eachEmbeddedRelationship(type, 'hasMany', callback, binding);
  4802. },
  4803. eachEmbeddedBelongsTo: function(type, callback, binding) {
  4804. this.eachEmbeddedRelationship(type, 'belongsTo', callback, binding);
  4805. },
  4806. eachEmbeddedRelationship: function(type, kind, callback, binding) {
  4807. type.eachRelationship(function(name, relationship) {
  4808. var embeddedType = this.embeddedType(type, name);
  4809. if (embeddedType) {
  4810. if (relationship.kind === kind) {
  4811. callback.call(binding, name, relationship, embeddedType);
  4812. }
  4813. }
  4814. }, this);
  4815. }
  4816. });
  4817. })();
  4818. (function() {
  4819. var none = Ember.isNone;
  4820. /**
  4821. DS.Transforms is a hash of transforms used by DS.Serializer.
  4822. */
  4823. DS.JSONTransforms = {
  4824. string: {
  4825. deserialize: function(serialized) {
  4826. return none(serialized) ? null : String(serialized);
  4827. },
  4828. serialize: function(deserialized) {
  4829. return none(deserialized) ? null : String(deserialized);
  4830. }
  4831. },
  4832. number: {
  4833. deserialize: function(serialized) {
  4834. return none(serialized) ? null : Number(serialized);
  4835. },
  4836. serialize: function(deserialized) {
  4837. return none(deserialized) ? null : Number(deserialized);
  4838. }
  4839. },
  4840. // Handles the following boolean inputs:
  4841. // "TrUe", "t", "f", "FALSE", 0, (non-zero), or boolean true/false
  4842. 'boolean': {
  4843. deserialize: function(serialized) {
  4844. var type = typeof serialized;
  4845. if (type === "boolean") {
  4846. return serialized;
  4847. } else if (type === "string") {
  4848. return serialized.match(/^true$|^t$|^1$/i) !== null;
  4849. } else if (type === "number") {
  4850. return serialized === 1;
  4851. } else {
  4852. return false;
  4853. }
  4854. },
  4855. serialize: function(deserialized) {
  4856. return Boolean(deserialized);
  4857. }
  4858. },
  4859. date: {
  4860. deserialize: function(serialized) {
  4861. var type = typeof serialized;
  4862. var date = null;
  4863. if (type === "string" || type === "number") {
  4864. // this is a fix for Safari 5.1.5 on Mac which does not accept timestamps as yyyy-mm-dd
  4865. if (type === "string" && serialized.search(/^\d{4}-\d{2}-\d{2}$/) !== -1) {
  4866. serialized += "T00:00:00Z";
  4867. }
  4868. date = new Date(serialized);
  4869. // this is a fix for IE8 which does not accept timestamps in ISO 8601 format
  4870. if (type === "string" && isNaN(date)) {
  4871. date = new Date(Date.parse(serialized.replace(/\-/ig, '/').replace(/Z$/, '').split('.')[0]));
  4872. }
  4873. return date;
  4874. } else if (serialized === null || serialized === undefined) {
  4875. // if the value is not present in the data,
  4876. // return undefined, not null.
  4877. return serialized;
  4878. } else {
  4879. return null;
  4880. }
  4881. },
  4882. serialize: function(date) {
  4883. if (date instanceof Date) {
  4884. var days = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
  4885. var months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
  4886. var pad = function(num) {
  4887. return num < 10 ? "0"+num : ""+num;
  4888. };
  4889. var utcYear = date.getUTCFullYear(),
  4890. utcMonth = date.getUTCMonth(),
  4891. utcDayOfMonth = date.getUTCDate(),
  4892. utcDay = date.getUTCDay(),
  4893. utcHours = date.getUTCHours(),
  4894. utcMinutes = date.getUTCMinutes(),
  4895. utcSeconds = date.getUTCSeconds();
  4896. var dayOfWeek = days[utcDay];
  4897. var dayOfMonth = pad(utcDayOfMonth);
  4898. var month = months[utcMonth];
  4899. return dayOfWeek + ", " + dayOfMonth + " " + month + " " + utcYear + " " +
  4900. pad(utcHours) + ":" + pad(utcMinutes) + ":" + pad(utcSeconds) + " GMT";
  4901. } else if (date === undefined) {
  4902. return undefined;
  4903. } else {
  4904. return null;
  4905. }
  4906. }
  4907. }
  4908. };
  4909. })();
  4910. (function() {
  4911. var get = Ember.get, set = Ember.set;
  4912. var generatedId = 0;
  4913. DS.JSONSerializer = DS.Serializer.extend({
  4914. init: function() {
  4915. this._super();
  4916. if (!get(this, 'transforms')) {
  4917. this.set('transforms', DS.JSONTransforms);
  4918. }
  4919. this.sideloadMapping = Ember.Map.create();
  4920. this.configure({
  4921. meta: 'meta',
  4922. since: 'since'
  4923. });
  4924. },
  4925. configure: function(type, configuration) {
  4926. if (type && !configuration) {
  4927. return this._super(type);
  4928. }
  4929. var sideloadAs = configuration.sideloadAs;
  4930. if (sideloadAs) {
  4931. this.sideloadMapping.set(sideloadAs, type);
  4932. delete configuration.sideloadAs;
  4933. }
  4934. this._super.apply(this, arguments);
  4935. },
  4936. addId: function(data, key, id) {
  4937. data[key] = id;
  4938. },
  4939. /**
  4940. A hook you can use to customize how the key/value pair is added to
  4941. the serialized data.
  4942. @param {any} hash the JSON hash being built
  4943. @param {String} key the key to add to the serialized data
  4944. @param {any} value the value to add to the serialized data
  4945. */
  4946. addAttribute: function(hash, key, value) {
  4947. hash[key] = value;
  4948. },
  4949. /**
  4950. @private
  4951. Creates an empty hash that will be filled in by the hooks called from the
  4952. `serialize()` method.
  4953. @return {Object}
  4954. */
  4955. createSerializedForm: function() {
  4956. return {};
  4957. },
  4958. extractAttribute: function(type, hash, attributeName) {
  4959. var key = this._keyForAttributeName(type, attributeName);
  4960. return hash[key];
  4961. },
  4962. extractId: function(type, hash) {
  4963. var primaryKey = this._primaryKey(type);
  4964. if (hash.hasOwnProperty(primaryKey)) {
  4965. // Ensure that we coerce IDs to strings so that record
  4966. // IDs remain consistent between application runs; especially
  4967. // if the ID is serialized and later deserialized from the URL,
  4968. // when type information will have been lost.
  4969. return hash[primaryKey]+'';
  4970. } else {
  4971. return null;
  4972. }
  4973. },
  4974. extractHasMany: function(type, hash, key) {
  4975. return hash[key];
  4976. },
  4977. extractBelongsTo: function(type, hash, key) {
  4978. return hash[key];
  4979. },
  4980. addBelongsTo: function(hash, record, key, relationship) {
  4981. var type = record.constructor,
  4982. name = relationship.key,
  4983. value = null,
  4984. embeddedChild;
  4985. if (this.embeddedType(type, name)) {
  4986. if (embeddedChild = get(record, name)) {
  4987. value = this.serialize(embeddedChild, { include: true });
  4988. }
  4989. hash[key] = value;
  4990. } else {
  4991. var id = get(record, relationship.key+'.id');
  4992. if (!Ember.isNone(id)) { hash[key] = id; }
  4993. }
  4994. },
  4995. /**
  4996. Adds a has-many relationship to the JSON hash being built.
  4997. The default REST semantics are to only add a has-many relationship if it
  4998. is embedded. If the relationship was initially loaded by ID, we assume that
  4999. that was done as a performance optimization, and that changes to the
  5000. has-many should be saved as foreign key changes on the child's belongs-to
  5001. relationship.
  5002. @param {Object} hash the JSON being built
  5003. @param {DS.Model} record the record being serialized
  5004. @param {String} key the JSON key into which the serialized relationship
  5005. should be saved
  5006. @param {Object} relationship metadata about the relationship being serialized
  5007. */
  5008. addHasMany: function(hash, record, key, relationship) {
  5009. var type = record.constructor,
  5010. name = relationship.key,
  5011. serializedHasMany = [],
  5012. manyArray, embeddedType;
  5013. // If the has-many is not embedded, there is nothing to do.
  5014. embeddedType = this.embeddedType(type, name);
  5015. if (embeddedType !== 'always') { return; }
  5016. // Get the DS.ManyArray for the relationship off the record
  5017. manyArray = get(record, name);
  5018. // Build up the array of serialized records
  5019. manyArray.forEach(function (record) {
  5020. serializedHasMany.push(this.serialize(record, { includeId: true }));
  5021. }, this);
  5022. // Set the appropriate property of the serialized JSON to the
  5023. // array of serialized embedded records
  5024. hash[key] = serializedHasMany;
  5025. },
  5026. // EXTRACTION
  5027. extract: function(loader, json, type, record) {
  5028. var root = this.rootForType(type);
  5029. this.sideload(loader, type, json, root);
  5030. this.extractMeta(loader, type, json);
  5031. if (json[root]) {
  5032. if (record) { loader.updateId(record, json[root]); }
  5033. this.extractRecordRepresentation(loader, type, json[root]);
  5034. }
  5035. },
  5036. extractMany: function(loader, json, type, records) {
  5037. var root = this.rootForType(type);
  5038. root = this.pluralize(root);
  5039. this.sideload(loader, type, json, root);
  5040. this.extractMeta(loader, type, json);
  5041. if (json[root]) {
  5042. var objects = json[root], references = [];
  5043. if (records) { records = records.toArray(); }
  5044. for (var i = 0; i < objects.length; i++) {
  5045. if (records) { loader.updateId(records[i], objects[i]); }
  5046. var reference = this.extractRecordRepresentation(loader, type, objects[i]);
  5047. references.push(reference);
  5048. }
  5049. loader.populateArray(references);
  5050. }
  5051. },
  5052. extractMeta: function(loader, type, json) {
  5053. var meta = json[this.configOption(type, 'meta')], since;
  5054. if (!meta) { return; }
  5055. if (since = meta[this.configOption(type, 'since')]) {
  5056. loader.sinceForType(type, since);
  5057. }
  5058. },
  5059. sideload: function(loader, type, json, root) {
  5060. var sideloadedType, mappings, loaded = {};
  5061. loaded[root] = true;
  5062. for (var prop in json) {
  5063. if (!json.hasOwnProperty(prop)) { continue; }
  5064. if (prop === root) { continue; }
  5065. if (prop === this.configOption(type, 'meta')) { continue; }
  5066. sideloadedType = type.typeForRelationship(prop);
  5067. if (!sideloadedType) {
  5068. sideloadedType = this.sideloadMapping.get(prop);
  5069. if (typeof sideloadedType === 'string') {
  5070. sideloadedType = get(Ember.lookup, sideloadedType);
  5071. }
  5072. Ember.assert("Your server returned a hash with the key " + prop + " but you have no mapping for it", !!sideloadedType);
  5073. }
  5074. this.sideloadRelationships(loader, sideloadedType, json, prop, loaded);
  5075. }
  5076. },
  5077. sideloadRelationships: function(loader, type, json, prop, loaded) {
  5078. loaded[prop] = true;
  5079. get(type, 'relationshipsByName').forEach(function(key, meta) {
  5080. key = meta.key || key;
  5081. if (meta.kind === 'belongsTo') {
  5082. key = this.pluralize(key);
  5083. }
  5084. if (json[key] && !loaded[key]) {
  5085. this.sideloadRelationships(loader, meta.type, json, key, loaded);
  5086. }
  5087. }, this);
  5088. this.loadValue(loader, type, json[prop]);
  5089. },
  5090. loadValue: function(loader, type, value) {
  5091. if (value instanceof Array) {
  5092. for (var i=0; i < value.length; i++) {
  5093. loader.sideload(type, value[i]);
  5094. }
  5095. } else {
  5096. loader.sideload(type, value);
  5097. }
  5098. },
  5099. // HELPERS
  5100. // define a plurals hash in your subclass to define
  5101. // special-case pluralization
  5102. pluralize: function(name) {
  5103. var plurals = this.configurations.get('plurals');
  5104. return (plurals && plurals[name]) || name + "s";
  5105. },
  5106. rootForType: function(type) {
  5107. var typeString = type.toString();
  5108. Ember.assert("Your model must not be anonymous. It was " + type, typeString.charAt(0) !== '(');
  5109. // use the last part of the name as the URL
  5110. var parts = typeString.split(".");
  5111. var name = parts[parts.length - 1];
  5112. return name.replace(/([A-Z])/g, '_$1').toLowerCase().slice(1);
  5113. }
  5114. });
  5115. })();
  5116. (function() {
  5117. function loaderFor(store) {
  5118. return {
  5119. load: function(type, data, prematerialized) {
  5120. return store.load(type, data, prematerialized);
  5121. },
  5122. loadMany: function(type, array) {
  5123. return store.loadMany(type, array);
  5124. },
  5125. updateId: function(record, data) {
  5126. return store.updateId(record, data);
  5127. },
  5128. populateArray: Ember.K,
  5129. sideload: function(type, data) {
  5130. return store.load(type, data);
  5131. },
  5132. sideloadMany: function(type, array) {
  5133. return store.loadMany(type, array);
  5134. },
  5135. prematerialize: function(reference, prematerialized) {
  5136. store.prematerialize(reference, prematerialized);
  5137. },
  5138. sinceForType: function(type, since) {
  5139. store.sinceForType(type, since);
  5140. }
  5141. };
  5142. }
  5143. DS.loaderFor = loaderFor;
  5144. /**
  5145. An adapter is an object that receives requests from a store and
  5146. translates them into the appropriate action to take against your
  5147. persistence layer. The persistence layer is usually an HTTP API, but may
  5148. be anything, such as the browser's local storage.
  5149. ### Creating an Adapter
  5150. First, create a new subclass of `DS.Adapter`:
  5151. App.MyAdapter = DS.Adapter.extend({
  5152. // ...your code here
  5153. });
  5154. To tell your store which adapter to use, set its `adapter` property:
  5155. App.store = DS.Store.create({
  5156. revision: 3,
  5157. adapter: App.MyAdapter.create()
  5158. });
  5159. `DS.Adapter` is an abstract base class that you should override in your
  5160. application to customize it for your backend. The minimum set of methods
  5161. that you should implement is:
  5162. * `find()`
  5163. * `createRecord()`
  5164. * `updateRecord()`
  5165. * `deleteRecord()`
  5166. To improve the network performance of your application, you can optimize
  5167. your adapter by overriding these lower-level methods:
  5168. * `findMany()`
  5169. * `createRecords()`
  5170. * `updateRecords()`
  5171. * `deleteRecords()`
  5172. * `commit()`
  5173. */
  5174. var get = Ember.get, set = Ember.set, merge = Ember.merge;
  5175. DS.Adapter = Ember.Object.extend(DS._Mappable, {
  5176. init: function() {
  5177. var serializer = get(this, 'serializer');
  5178. if (Ember.Object.detect(serializer)) {
  5179. serializer = serializer.create();
  5180. set(this, 'serializer', serializer);
  5181. }
  5182. this._attributesMap = this.createInstanceMapFor('attributes');
  5183. this._configurationsMap = this.createInstanceMapFor('configurations');
  5184. this._outstandingOperations = new Ember.MapWithDefault({
  5185. defaultValue: function() { return 0; }
  5186. });
  5187. this._dependencies = new Ember.MapWithDefault({
  5188. defaultValue: function() { return new Ember.OrderedSet(); }
  5189. });
  5190. this.registerSerializerTransforms(this.constructor, serializer, {});
  5191. this.registerSerializerMappings(serializer);
  5192. },
  5193. /**
  5194. Loads a payload for a record into the store.
  5195. This method asks the serializer to break the payload into
  5196. constituent parts, and then loads them into the store. For example,
  5197. if you have a payload that contains embedded records, they will be
  5198. extracted by the serializer and loaded into the store.
  5199. For example:
  5200. ```javascript
  5201. adapter.load(store, App.Person, {
  5202. id: 123,
  5203. firstName: "Yehuda",
  5204. lastName: "Katz",
  5205. occupations: [{
  5206. id: 345,
  5207. title: "Tricycle Mechanic"
  5208. }]
  5209. });
  5210. ```
  5211. This will load the payload for the `App.Person` with ID `123` and
  5212. the embedded `App.Occupation` with ID `345`.
  5213. @param {DS.Store} store
  5214. @param {subclass of DS.Model} type
  5215. @param {any} payload
  5216. */
  5217. load: function(store, type, payload) {
  5218. var loader = loaderFor(store);
  5219. get(this, 'serializer').extractRecordRepresentation(loader, type, payload);
  5220. },
  5221. /**
  5222. Acknowledges that the adapter has finished creating a record.
  5223. Your adapter should call this method from `createRecord` when
  5224. it has saved a new record to its persistent storage and received
  5225. an acknowledgement.
  5226. If the persistent storage returns a new payload in response to the
  5227. creation, and you want to update the existing record with the
  5228. new information, pass the payload as the fourth parameter.
  5229. For example, the `RESTAdapter` saves newly created records by
  5230. making an Ajax request. When the server returns, the adapter
  5231. calls didCreateRecord. If the server returns a response body,
  5232. it is passed as the payload.
  5233. @param {DS.Store} store
  5234. @param {subclass of DS.Model} type
  5235. @param {DS.Model} record
  5236. @param {any} payload
  5237. */
  5238. didCreateRecord: function(store, type, record, payload) {
  5239. store.didSaveRecord(record);
  5240. if (payload) {
  5241. var loader = DS.loaderFor(store);
  5242. var serializer = get(this, 'serializer');
  5243. loader.load = function(type, data, prematerialized) {
  5244. store.updateId(record, data);
  5245. return store.load(type, data, prematerialized);
  5246. };
  5247. get(this, 'serializer').extract(loader, payload, type);
  5248. }
  5249. },
  5250. /**
  5251. Acknowledges that the adapter has finished creating several records.
  5252. Your adapter should call this method from `createRecords` when it
  5253. has saved multiple created records to its persistent storage
  5254. received an acknowledgement.
  5255. If the persistent storage returns a new payload in response to the
  5256. creation, and you want to update the existing record with the
  5257. new information, pass the payload as the fourth parameter.
  5258. @param {DS.Store} store
  5259. @param {subclass of DS.Model} type
  5260. @param {DS.Model} record
  5261. @param {any} payload
  5262. */
  5263. didCreateRecords: function(store, type, records, payload) {
  5264. records.forEach(function(record) {
  5265. store.didSaveRecord(record);
  5266. }, this);
  5267. if (payload) {
  5268. var loader = DS.loaderFor(store);
  5269. get(this, 'serializer').extractMany(loader, payload, type, records);
  5270. }
  5271. },
  5272. /**
  5273. @private
  5274. Acknowledges that the adapter has finished updating or deleting a record.
  5275. Your adapter should call this method from `updateRecord` or `deleteRecord`
  5276. when it has updated or deleted a record to its persistent storage and
  5277. received an acknowledgement.
  5278. If the persistent storage returns a new payload in response to the
  5279. update or delete, and you want to update the existing record with the
  5280. new information, pass the payload as the fourth parameter.
  5281. @param {DS.Store} store
  5282. @param {subclass of DS.Model} type
  5283. @param {DS.Model} record
  5284. @param {any} payload
  5285. */
  5286. didSaveRecord: function(store, type, record, payload) {
  5287. store.didSaveRecord(record);
  5288. var serializer = get(this, 'serializer'),
  5289. mappings = serializer.mappingForType(type);
  5290. serializer.eachEmbeddedRecord(record, function(embeddedRecord, embeddedType) {
  5291. if (embeddedType === 'load') { return; }
  5292. this.didSaveRecord(store, embeddedRecord.constructor, embeddedRecord);
  5293. }, this);
  5294. if (payload) {
  5295. var loader = DS.loaderFor(store);
  5296. serializer.extract(loader, payload, type);
  5297. }
  5298. },
  5299. /**
  5300. Acknowledges that the adapter has finished updating a record.
  5301. Your adapter should call this method from `updateRecord` when it
  5302. has updated a record to its persistent storage and received an
  5303. acknowledgement.
  5304. If the persistent storage returns a new payload in response to the
  5305. update, pass the payload as the fourth parameter.
  5306. @param {DS.Store} store
  5307. @param {subclass of DS.Model} type
  5308. @param {DS.Model} record
  5309. @param {any} payload
  5310. */
  5311. didUpdateRecord: function() {
  5312. this.didSaveRecord.apply(this, arguments);
  5313. },
  5314. /**
  5315. Acknowledges that the adapter has finished deleting a record.
  5316. Your adapter should call this method from `deleteRecord` when it
  5317. has deleted a record from its persistent storage and received an
  5318. acknowledgement.
  5319. If the persistent storage returns a new payload in response to the
  5320. deletion, pass the payload as the fourth parameter.
  5321. @param {DS.Store} store
  5322. @param {subclass of DS.Model} type
  5323. @param {DS.Model} record
  5324. @param {any} payload
  5325. */
  5326. didDeleteRecord: function() {
  5327. this.didSaveRecord.apply(this, arguments);
  5328. },
  5329. /**
  5330. Acknowledges that the adapter has finished updating or deleting
  5331. multiple records.
  5332. Your adapter should call this method from its `updateRecords` or
  5333. `deleteRecords` when it has updated or deleted multiple records
  5334. to its persistent storage and received an acknowledgement.
  5335. If the persistent storage returns a new payload in response to the
  5336. creation, pass the payload as the fourth parameter.
  5337. @param {DS.Store} store
  5338. @param {subclass of DS.Model} type
  5339. @param {DS.Model} records
  5340. @param {any} payload
  5341. */
  5342. didSaveRecords: function(store, type, records, payload) {
  5343. records.forEach(function(record) {
  5344. store.didSaveRecord(record);
  5345. }, this);
  5346. if (payload) {
  5347. var loader = DS.loaderFor(store);
  5348. get(this, 'serializer').extractMany(loader, payload, type);
  5349. }
  5350. },
  5351. /**
  5352. Acknowledges that the adapter has finished updating multiple records.
  5353. Your adapter should call this method from its `updateRecords` when
  5354. it has updated multiple records to its persistent storage and
  5355. received an acknowledgement.
  5356. If the persistent storage returns a new payload in response to the
  5357. update, pass the payload as the fourth parameter.
  5358. @param {DS.Store} store
  5359. @param {subclass of DS.Model} type
  5360. @param {DS.Model} records
  5361. @param {any} payload
  5362. */
  5363. didUpdateRecords: function() {
  5364. this.didSaveRecords.apply(this, arguments);
  5365. },
  5366. /**
  5367. Acknowledges that the adapter has finished updating multiple records.
  5368. Your adapter should call this method from its `deleteRecords` when
  5369. it has deleted multiple records to its persistent storage and
  5370. received an acknowledgement.
  5371. If the persistent storage returns a new payload in response to the
  5372. deletion, pass the payload as the fourth parameter.
  5373. @param {DS.Store} store
  5374. @param {subclass of DS.Model} type
  5375. @param {DS.Model} records
  5376. @param {any} payload
  5377. */
  5378. didDeleteRecords: function() {
  5379. this.didSaveRecords.apply(this, arguments);
  5380. },
  5381. /**
  5382. Loads the response to a request for a record by ID.
  5383. Your adapter should call this method from its `find` method
  5384. with the response from the backend.
  5385. You should pass the same ID to this method that was given
  5386. to your find method so that the store knows which record
  5387. to associate the new data with.
  5388. @param {DS.Store} store
  5389. @param {subclass of DS.Model} type
  5390. @param {any} payload
  5391. @param {String} id
  5392. */
  5393. didFindRecord: function(store, type, payload, id) {
  5394. var loader = DS.loaderFor(store);
  5395. loader.load = function(type, data, prematerialized) {
  5396. prematerialized = prematerialized || {};
  5397. prematerialized.id = id;
  5398. return store.load(type, data, prematerialized);
  5399. };
  5400. get(this, 'serializer').extract(loader, payload, type);
  5401. },
  5402. /**
  5403. Loads the response to a request for all records by type.
  5404. You adapter should call this method from its `findAll`
  5405. method with the response from the backend.
  5406. @param {DS.Store} store
  5407. @param {subclass of DS.Model} type
  5408. @param {any} payload
  5409. */
  5410. didFindAll: function(store, type, payload) {
  5411. var loader = DS.loaderFor(store),
  5412. serializer = get(this, 'serializer');
  5413. store.didUpdateAll(type);
  5414. serializer.extractMany(loader, payload, type);
  5415. },
  5416. /**
  5417. Loads the response to a request for records by query.
  5418. Your adapter should call this method from its `findQuery`
  5419. method with the response from the backend.
  5420. @param {DS.Store} store
  5421. @param {subclass of DS.Model} type
  5422. @param {any} payload
  5423. @param {DS.AdapterPopulatedRecordArray} recordArray
  5424. */
  5425. didFindQuery: function(store, type, payload, recordArray) {
  5426. var loader = DS.loaderFor(store);
  5427. loader.populateArray = function(data) {
  5428. recordArray.load(data);
  5429. };
  5430. get(this, 'serializer').extractMany(loader, payload, type);
  5431. },
  5432. /**
  5433. Loads the response to a request for many records by ID.
  5434. You adapter should call this method from its `findMany`
  5435. method with the response from the backend.
  5436. @param {DS.Store} store
  5437. @param {subclass of DS.Model} type
  5438. @param {any} payload
  5439. */
  5440. didFindMany: function(store, type, payload) {
  5441. var loader = DS.loaderFor(store);
  5442. get(this, 'serializer').extractMany(loader, payload, type);
  5443. },
  5444. /**
  5445. Notifies the store that a request to the backend returned
  5446. an error.
  5447. Your adapter should call this method to indicate that the
  5448. backend returned an error for a request.
  5449. @param {DS.Store} store
  5450. @param {subclass of DS.Model} type
  5451. @param {DS.Model} record
  5452. */
  5453. didError: function(store, type, record) {
  5454. store.recordWasError(record);
  5455. },
  5456. dirtyRecordsForAttributeChange: function(dirtySet, record, attributeName, newValue, oldValue) {
  5457. if (newValue !== oldValue) {
  5458. // If this record is embedded, add its parent
  5459. // to the dirty set.
  5460. this.dirtyRecordsForRecordChange(dirtySet, record);
  5461. }
  5462. },
  5463. dirtyRecordsForRecordChange: function(dirtySet, record) {
  5464. dirtySet.add(record);
  5465. },
  5466. dirtyRecordsForBelongsToChange: function(dirtySet, child) {
  5467. this.dirtyRecordsForRecordChange(dirtySet, child);
  5468. },
  5469. dirtyRecordsForHasManyChange: function(dirtySet, parent) {
  5470. this.dirtyRecordsForRecordChange(dirtySet, parent);
  5471. },
  5472. /**
  5473. @private
  5474. This method recursively climbs the superclass hierarchy and
  5475. registers any class-registered transforms on the adapter's
  5476. serializer.
  5477. Once it registers a transform for a given type, it ignores
  5478. subsequent transforms for the same attribute type.
  5479. @param {Class} klass the DS.Adapter subclass to extract the
  5480. transforms from
  5481. @param {DS.Serializer} serializer the serializer to register
  5482. the transforms onto
  5483. @param {Object} seen a hash of attributes already seen
  5484. */
  5485. registerSerializerTransforms: function(klass, serializer, seen) {
  5486. var transforms = klass._registeredTransforms, superclass, prop;
  5487. for (prop in transforms) {
  5488. if (!transforms.hasOwnProperty(prop) || prop in seen) { continue; }
  5489. seen[prop] = true;
  5490. serializer.registerTransform(prop, transforms[prop]);
  5491. }
  5492. if (superclass = klass.superclass) {
  5493. this.registerSerializerTransforms(superclass, serializer, seen);
  5494. }
  5495. },
  5496. /**
  5497. @private
  5498. This method recursively climbs the superclass hierarchy and
  5499. registers any class-registered mappings on the adapter's
  5500. serializer.
  5501. @param {Class} klass the DS.Adapter subclass to extract the
  5502. transforms from
  5503. @param {DS.Serializer} serializer the serializer to register the
  5504. mappings onto
  5505. */
  5506. registerSerializerMappings: function(serializer) {
  5507. var mappings = this._attributesMap,
  5508. configurations = this._configurationsMap;
  5509. mappings.forEach(serializer.map, serializer);
  5510. configurations.forEach(serializer.configure, serializer);
  5511. },
  5512. /**
  5513. The `find()` method is invoked when the store is asked for a record that
  5514. has not previously been loaded. In response to `find()` being called, you
  5515. should query your persistence layer for a record with the given ID. Once
  5516. found, you can asynchronously call the store's `load()` method to load
  5517. the record.
  5518. Here is an example `find` implementation:
  5519. find: function(store, type, id) {
  5520. var url = type.url;
  5521. url = url.fmt(id);
  5522. jQuery.getJSON(url, function(data) {
  5523. // data is a hash of key/value pairs. If your server returns a
  5524. // root, simply do something like:
  5525. // store.load(type, id, data.person)
  5526. store.load(type, id, data);
  5527. });
  5528. }
  5529. */
  5530. find: null,
  5531. serializer: DS.JSONSerializer,
  5532. registerTransform: function(attributeType, transform) {
  5533. get(this, 'serializer').registerTransform(attributeType, transform);
  5534. },
  5535. /**
  5536. A public method that allows you to register an enumerated
  5537. type on your adapter. This is useful if you want to utilize
  5538. a text representation of an integer value.
  5539. Eg: Say you want to utilize "low","medium","high" text strings
  5540. in your app, but you want to persist those as 0,1,2 in your backend.
  5541. You would first register the transform on your adapter instance:
  5542. adapter.registerEnumTransform('priority', ['low', 'medium', 'high']);
  5543. You would then refer to the 'priority' DS.attr in your model:
  5544. App.Task = DS.Model.extend({
  5545. priority: DS.attr('priority')
  5546. });
  5547. And lastly, you would set/get the text representation on your model instance,
  5548. but the transformed result will be the index number of the type.
  5549. App: myTask.get('priority') => 'low'
  5550. Server Response / Load: { myTask: {priority: 0} }
  5551. @param {String} type of the transform
  5552. @param {Array} array of String objects to use for the enumerated values.
  5553. This is an ordered list and the index values will be used for the transform.
  5554. */
  5555. registerEnumTransform: function(attributeType, objects) {
  5556. get(this, 'serializer').registerEnumTransform(attributeType, objects);
  5557. },
  5558. /**
  5559. If the globally unique IDs for your records should be generated on the client,
  5560. implement the `generateIdForRecord()` method. This method will be invoked
  5561. each time you create a new record, and the value returned from it will be
  5562. assigned to the record's `primaryKey`.
  5563. Most traditional REST-like HTTP APIs will not use this method. Instead, the ID
  5564. of the record will be set by the server, and your adapter will update the store
  5565. with the new ID when it calls `didCreateRecord()`. Only implement this method if
  5566. you intend to generate record IDs on the client-side.
  5567. The `generateIdForRecord()` method will be invoked with the requesting store as
  5568. the first parameter and the newly created record as the second parameter:
  5569. generateIdForRecord: function(store, record) {
  5570. var uuid = App.generateUUIDWithStatisticallyLowOddsOfCollision();
  5571. return uuid;
  5572. }
  5573. */
  5574. generateIdForRecord: null,
  5575. materialize: function(record, data, prematerialized) {
  5576. get(this, 'serializer').materialize(record, data, prematerialized);
  5577. },
  5578. serialize: function(record, options) {
  5579. return get(this, 'serializer').serialize(record, options);
  5580. },
  5581. extractId: function(type, data) {
  5582. return get(this, 'serializer').extractId(type, data);
  5583. },
  5584. groupByType: function(enumerable) {
  5585. var map = Ember.MapWithDefault.create({
  5586. defaultValue: function() { return Ember.OrderedSet.create(); }
  5587. });
  5588. enumerable.forEach(function(item) {
  5589. map.get(item.constructor).add(item);
  5590. });
  5591. return map;
  5592. },
  5593. commit: function(store, commitDetails) {
  5594. this.save(store, commitDetails);
  5595. },
  5596. save: function(store, commitDetails) {
  5597. var adapter = this;
  5598. function filter(records) {
  5599. var filteredSet = Ember.OrderedSet.create();
  5600. records.forEach(function(record) {
  5601. if (adapter.shouldSave(record)) {
  5602. filteredSet.add(record);
  5603. }
  5604. });
  5605. return filteredSet;
  5606. }
  5607. this.groupByType(commitDetails.created).forEach(function(type, set) {
  5608. this.createRecords(store, type, filter(set));
  5609. }, this);
  5610. this.groupByType(commitDetails.updated).forEach(function(type, set) {
  5611. this.updateRecords(store, type, filter(set));
  5612. }, this);
  5613. this.groupByType(commitDetails.deleted).forEach(function(type, set) {
  5614. this.deleteRecords(store, type, filter(set));
  5615. }, this);
  5616. },
  5617. shouldSave: Ember.K,
  5618. createRecords: function(store, type, records) {
  5619. records.forEach(function(record) {
  5620. this.createRecord(store, type, record);
  5621. }, this);
  5622. },
  5623. updateRecords: function(store, type, records) {
  5624. records.forEach(function(record) {
  5625. this.updateRecord(store, type, record);
  5626. }, this);
  5627. },
  5628. deleteRecords: function(store, type, records) {
  5629. records.forEach(function(record) {
  5630. this.deleteRecord(store, type, record);
  5631. }, this);
  5632. },
  5633. findMany: function(store, type, ids) {
  5634. ids.forEach(function(id) {
  5635. this.find(store, type, id);
  5636. }, this);
  5637. }
  5638. });
  5639. DS.Adapter.reopenClass({
  5640. registerTransform: function(attributeType, transform) {
  5641. var registeredTransforms = this._registeredTransforms || {};
  5642. registeredTransforms[attributeType] = transform;
  5643. this._registeredTransforms = registeredTransforms;
  5644. },
  5645. map: DS._Mappable.generateMapFunctionFor('attributes', function(key, newValue, map) {
  5646. var existingValue = map.get(key);
  5647. merge(existingValue, newValue);
  5648. }),
  5649. configure: DS._Mappable.generateMapFunctionFor('configurations', function(key, newValue, map) {
  5650. var existingValue = map.get(key);
  5651. // If a mapping configuration is provided, peel it off and apply it
  5652. // using the DS.Adapter.map API.
  5653. var mappings = newValue && newValue.mappings;
  5654. if (mappings) {
  5655. this.map(key, mappings);
  5656. delete newValue.mappings;
  5657. }
  5658. merge(existingValue, newValue);
  5659. }),
  5660. resolveMapConflict: function(oldValue, newValue, mappingsKey) {
  5661. merge(newValue, oldValue);
  5662. return newValue;
  5663. }
  5664. });
  5665. })();
  5666. (function() {
  5667. var get = Ember.get;
  5668. DS.FixtureAdapter = DS.Adapter.extend({
  5669. simulateRemoteResponse: true,
  5670. latency: 50,
  5671. /*
  5672. Implement this method in order to provide data associated with a type
  5673. */
  5674. fixturesForType: function(type) {
  5675. if (type.FIXTURES) {
  5676. var fixtures = Ember.A(type.FIXTURES);
  5677. return fixtures.map(function(fixture){
  5678. if(!fixture.id){
  5679. throw new Error('the id property must be defined for fixture %@'.fmt(fixture));
  5680. }
  5681. fixture.id = fixture.id + '';
  5682. return fixture;
  5683. });
  5684. }
  5685. return null;
  5686. },
  5687. /*
  5688. Implement this method in order to query fixtures data
  5689. */
  5690. queryFixtures: function(fixtures, query, type) {
  5691. return fixtures;
  5692. },
  5693. /*
  5694. Implement this method in order to provide provide json for CRUD methods
  5695. */
  5696. mockJSON: function(type, record) {
  5697. return this.serialize(record, { includeId: true });
  5698. },
  5699. /*
  5700. Adapter methods
  5701. */
  5702. generateIdForRecord: function(store, record) {
  5703. return Ember.guidFor(record);
  5704. },
  5705. find: function(store, type, id) {
  5706. var fixtures = this.fixturesForType(type);
  5707. Ember.assert("Unable to find fixtures for model type "+type.toString(), !!fixtures);
  5708. if (fixtures) {
  5709. fixtures = fixtures.findProperty('id', id);
  5710. }
  5711. if (fixtures) {
  5712. this.simulateRemoteCall(function() {
  5713. store.load(type, fixtures);
  5714. }, store, type);
  5715. }
  5716. },
  5717. findMany: function(store, type, ids) {
  5718. var fixtures = this.fixturesForType(type);
  5719. Ember.assert("Unable to find fixtures for model type "+type.toString(), !!fixtures);
  5720. if (fixtures) {
  5721. fixtures = fixtures.filter(function(item) {
  5722. return ids.indexOf(item.id) !== -1;
  5723. });
  5724. }
  5725. if (fixtures) {
  5726. this.simulateRemoteCall(function() {
  5727. store.loadMany(type, fixtures);
  5728. }, store, type);
  5729. }
  5730. },
  5731. findAll: function(store, type) {
  5732. var fixtures = this.fixturesForType(type);
  5733. Ember.assert("Unable to find fixtures for model type "+type.toString(), !!fixtures);
  5734. this.simulateRemoteCall(function() {
  5735. store.loadMany(type, fixtures);
  5736. store.didUpdateAll(type);
  5737. }, store, type);
  5738. },
  5739. findQuery: function(store, type, query, array) {
  5740. var fixtures = this.fixturesForType(type);
  5741. Ember.assert("Unable to find fixtures for model type "+type.toString(), !!fixtures);
  5742. fixtures = this.queryFixtures(fixtures, query, type);
  5743. if (fixtures) {
  5744. this.simulateRemoteCall(function() {
  5745. array.load(fixtures);
  5746. }, store, type);
  5747. }
  5748. },
  5749. createRecord: function(store, type, record) {
  5750. var fixture = this.mockJSON(type, record);
  5751. fixture.id = this.generateIdForRecord(store, record);
  5752. this.simulateRemoteCall(function() {
  5753. store.didSaveRecord(record, fixture);
  5754. }, store, type, record);
  5755. },
  5756. updateRecord: function(store, type, record) {
  5757. var fixture = this.mockJSON(type, record);
  5758. this.simulateRemoteCall(function() {
  5759. store.didSaveRecord(record, fixture);
  5760. }, store, type, record);
  5761. },
  5762. deleteRecord: function(store, type, record) {
  5763. this.simulateRemoteCall(function() {
  5764. store.didSaveRecord(record);
  5765. }, store, type, record);
  5766. },
  5767. /*
  5768. @private
  5769. */
  5770. simulateRemoteCall: function(callback, store, type, record) {
  5771. if (get(this, 'simulateRemoteResponse')) {
  5772. setTimeout(callback, get(this, 'latency'));
  5773. } else {
  5774. callback();
  5775. }
  5776. }
  5777. });
  5778. })();
  5779. (function() {
  5780. DS.RESTSerializer = DS.JSONSerializer.extend({
  5781. keyForAttributeName: function(type, name) {
  5782. return Ember.String.decamelize(name);
  5783. },
  5784. keyForBelongsTo: function(type, name) {
  5785. var key = this.keyForAttributeName(type, name);
  5786. if (this.embeddedType(type, name)) {
  5787. return key;
  5788. }
  5789. return key + "_id";
  5790. }
  5791. });
  5792. })();
  5793. (function() {
  5794. /*global jQuery*/
  5795. var get = Ember.get, set = Ember.set, merge = Ember.merge;
  5796. /**
  5797. The REST adapter allows your store to communicate with an HTTP server by
  5798. transmitting JSON via XHR. Most Ember.js apps that consume a JSON API
  5799. should use the REST adapter.
  5800. This adapter is designed around the idea that the JSON exchanged with
  5801. the server should be conventional.
  5802. ## JSON Structure
  5803. The REST adapter expects the JSON returned from your server to follow
  5804. these conventions.
  5805. ### Object Root
  5806. The JSON payload should be an object that contains the record inside a
  5807. root property. For example, in response to a `GET` request for
  5808. `/posts/1`, the JSON should look like this:
  5809. ```js
  5810. {
  5811. "post": {
  5812. title: "I'm Running to Reform the W3C's Tag",
  5813. author: "Yehuda Katz"
  5814. }
  5815. }
  5816. ```
  5817. ### Conventional Names
  5818. Attribute names in your JSON payload should be the underscored versions of
  5819. the attributes in your Ember.js models.
  5820. For example, if you have a `Person` model:
  5821. ```js
  5822. App.Person = DS.Model.extend({
  5823. firstName: DS.attr('string'),
  5824. lastName: DS.attr('string'),
  5825. occupation: DS.attr('string')
  5826. });
  5827. ```
  5828. The JSON returned should look like this:
  5829. ```js
  5830. {
  5831. "person": {
  5832. "first_name": "Barack",
  5833. "last_name": "Obama",
  5834. "occupation": "President"
  5835. }
  5836. }
  5837. ```
  5838. */
  5839. DS.RESTAdapter = DS.Adapter.extend({
  5840. bulkCommit: false,
  5841. since: 'since',
  5842. serializer: DS.RESTSerializer,
  5843. init: function() {
  5844. this._super.apply(this, arguments);
  5845. },
  5846. shouldSave: function(record) {
  5847. var reference = get(record, '_reference');
  5848. return !reference.parent;
  5849. },
  5850. createRecord: function(store, type, record) {
  5851. var root = this.rootForType(type);
  5852. var data = {};
  5853. data[root] = this.serialize(record, { includeId: true });
  5854. this.ajax(this.buildURL(root), "POST", {
  5855. data: data,
  5856. context: this,
  5857. success: function(json) {
  5858. Ember.run(this, function(){
  5859. this.didCreateRecord(store, type, record, json);
  5860. });
  5861. },
  5862. error: function(xhr) {
  5863. this.didError(store, type, record, xhr);
  5864. }
  5865. });
  5866. },
  5867. dirtyRecordsForRecordChange: function(dirtySet, record) {
  5868. dirtySet.add(record);
  5869. get(this, 'serializer').eachEmbeddedRecord(record, function(embeddedRecord, embeddedType) {
  5870. if (embeddedType !== 'always') { return; }
  5871. if (dirtySet.has(embeddedRecord)) { return; }
  5872. this.dirtyRecordsForRecordChange(dirtySet, embeddedRecord);
  5873. }, this);
  5874. var reference = record.get('_reference');
  5875. if (reference.parent) {
  5876. var store = get(record, 'store');
  5877. var parent = store.recordForReference(reference.parent);
  5878. this.dirtyRecordsForRecordChange(dirtySet, parent);
  5879. }
  5880. },
  5881. dirtyRecordsForHasManyChange: Ember.K,
  5882. createRecords: function(store, type, records) {
  5883. if (get(this, 'bulkCommit') === false) {
  5884. return this._super(store, type, records);
  5885. }
  5886. var root = this.rootForType(type),
  5887. plural = this.pluralize(root);
  5888. var data = {};
  5889. data[plural] = [];
  5890. records.forEach(function(record) {
  5891. data[plural].push(this.serialize(record, { includeId: true }));
  5892. }, this);
  5893. this.ajax(this.buildURL(root), "POST", {
  5894. data: data,
  5895. context: this,
  5896. success: function(json) {
  5897. Ember.run(this, function(){
  5898. this.didCreateRecords(store, type, records, json);
  5899. });
  5900. }
  5901. });
  5902. },
  5903. updateRecord: function(store, type, record) {
  5904. var id = get(record, 'id');
  5905. var root = this.rootForType(type);
  5906. var data = {};
  5907. data[root] = this.serialize(record);
  5908. this.ajax(this.buildURL(root, id), "PUT", {
  5909. data: data,
  5910. context: this,
  5911. success: function(json) {
  5912. Ember.run(this, function(){
  5913. this.didSaveRecord(store, type, record, json);
  5914. });
  5915. },
  5916. error: function(xhr) {
  5917. this.didError(store, type, record, xhr);
  5918. }
  5919. });
  5920. },
  5921. updateRecords: function(store, type, records) {
  5922. if (get(this, 'bulkCommit') === false) {
  5923. return this._super(store, type, records);
  5924. }
  5925. var root = this.rootForType(type),
  5926. plural = this.pluralize(root);
  5927. var data = {};
  5928. data[plural] = [];
  5929. records.forEach(function(record) {
  5930. data[plural].push(this.serialize(record, { includeId: true }));
  5931. }, this);
  5932. this.ajax(this.buildURL(root, "bulk"), "PUT", {
  5933. data: data,
  5934. context: this,
  5935. success: function(json) {
  5936. Ember.run(this, function(){
  5937. this.didSaveRecords(store, type, records, json);
  5938. });
  5939. }
  5940. });
  5941. },
  5942. deleteRecord: function(store, type, record) {
  5943. var id = get(record, 'id');
  5944. var root = this.rootForType(type);
  5945. this.ajax(this.buildURL(root, id), "DELETE", {
  5946. context: this,
  5947. success: function(json) {
  5948. Ember.run(this, function(){
  5949. this.didSaveRecord(store, type, record, json);
  5950. });
  5951. }
  5952. });
  5953. },
  5954. deleteRecords: function(store, type, records) {
  5955. if (get(this, 'bulkCommit') === false) {
  5956. return this._super(store, type, records);
  5957. }
  5958. var root = this.rootForType(type),
  5959. plural = this.pluralize(root),
  5960. serializer = get(this, 'serializer');
  5961. var data = {};
  5962. data[plural] = [];
  5963. records.forEach(function(record) {
  5964. data[plural].push(serializer.serializeId( get(record, 'id') ));
  5965. });
  5966. this.ajax(this.buildURL(root, 'bulk'), "DELETE", {
  5967. data: data,
  5968. context: this,
  5969. success: function(json) {
  5970. Ember.run(this, function(){
  5971. this.didSaveRecords(store, type, records, json);
  5972. });
  5973. }
  5974. });
  5975. },
  5976. find: function(store, type, id) {
  5977. var root = this.rootForType(type);
  5978. this.ajax(this.buildURL(root, id), "GET", {
  5979. success: function(json) {
  5980. Ember.run(this, function(){
  5981. this.didFindRecord(store, type, json, id);
  5982. });
  5983. }
  5984. });
  5985. },
  5986. findAll: function(store, type, since) {
  5987. var root = this.rootForType(type);
  5988. this.ajax(this.buildURL(root), "GET", {
  5989. data: this.sinceQuery(since),
  5990. success: function(json) {
  5991. Ember.run(this, function(){
  5992. this.didFindAll(store, type, json);
  5993. });
  5994. }
  5995. });
  5996. },
  5997. findQuery: function(store, type, query, recordArray) {
  5998. var root = this.rootForType(type);
  5999. this.ajax(this.buildURL(root), "GET", {
  6000. data: query,
  6001. success: function(json) {
  6002. Ember.run(this, function(){
  6003. this.didFindQuery(store, type, json, recordArray);
  6004. });
  6005. }
  6006. });
  6007. },
  6008. findMany: function(store, type, ids, owner) {
  6009. var root = this.rootForType(type);
  6010. ids = this.serializeIds(ids);
  6011. this.ajax(this.buildURL(root), "GET", {
  6012. data: {ids: ids},
  6013. success: function(json) {
  6014. Ember.run(this, function(){
  6015. this.didFindMany(store, type, json);
  6016. });
  6017. }
  6018. });
  6019. },
  6020. /**
  6021. @private
  6022. This method serializes a list of IDs using `serializeId`
  6023. @returns {Array} an array of serialized IDs
  6024. */
  6025. serializeIds: function(ids) {
  6026. var serializer = get(this, 'serializer');
  6027. return Ember.EnumerableUtils.map(ids, function(id) {
  6028. return serializer.serializeId(id);
  6029. });
  6030. },
  6031. didError: function(store, type, record, xhr) {
  6032. if (xhr.status === 422) {
  6033. var data = JSON.parse(xhr.responseText);
  6034. store.recordWasInvalid(record, data['errors']);
  6035. } else {
  6036. this._super.apply(this, arguments);
  6037. }
  6038. },
  6039. ajax: function(url, type, hash) {
  6040. hash.url = url;
  6041. hash.type = type;
  6042. hash.dataType = 'json';
  6043. hash.contentType = 'application/json; charset=utf-8';
  6044. hash.context = this;
  6045. if (hash.data && type !== 'GET') {
  6046. hash.data = JSON.stringify(hash.data);
  6047. }
  6048. jQuery.ajax(hash);
  6049. },
  6050. url: "",
  6051. rootForType: function(type) {
  6052. var serializer = get(this, 'serializer');
  6053. return serializer.rootForType(type);
  6054. },
  6055. pluralize: function(string) {
  6056. var serializer = get(this, 'serializer');
  6057. return serializer.pluralize(string);
  6058. },
  6059. buildURL: function(record, suffix) {
  6060. var url = [this.url];
  6061. Ember.assert("Namespace URL (" + this.namespace + ") must not start with slash", !this.namespace || this.namespace.toString().charAt(0) !== "/");
  6062. Ember.assert("Record URL (" + record + ") must not start with slash", !record || record.toString().charAt(0) !== "/");
  6063. Ember.assert("URL suffix (" + suffix + ") must not start with slash", !suffix || suffix.toString().charAt(0) !== "/");
  6064. if (this.namespace !== undefined) {
  6065. url.push(this.namespace);
  6066. }
  6067. url.push(this.pluralize(record));
  6068. if (suffix !== undefined) {
  6069. url.push(suffix);
  6070. }
  6071. return url.join("/");
  6072. },
  6073. sinceQuery: function(since) {
  6074. var query = {};
  6075. query[get(this, 'since')] = since;
  6076. return since ? query : null;
  6077. }
  6078. });
  6079. })();
  6080. (function() {
  6081. })();
  6082. (function() {
  6083. //Copyright (C) 2011 by Living Social, Inc.
  6084. //Permission is hereby granted, free of charge, to any person obtaining a copy of
  6085. //this software and associated documentation files (the "Software"), to deal in
  6086. //the Software without restriction, including without limitation the rights to
  6087. //use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
  6088. //of the Software, and to permit persons to whom the Software is furnished to do
  6089. //so, subject to the following conditions:
  6090. //The above copyright notice and this permission notice shall be included in all
  6091. //copies or substantial portions of the Software.
  6092. //THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  6093. //IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  6094. //FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  6095. //AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  6096. //LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  6097. //OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
  6098. //SOFTWARE.
  6099. })();