3 ms·
Identity Map for Backbone.js Models
- moe 15y agoThis seems like a useful addition. I hope it will gain traction (if the impl is sober, I haven't tried it out yet). My main issue with backbone are the Collection shortcomings (like this one) which require just the kind of boilerplate-workarounds that are so easy to get subtly wrong. Another example would be the lack of infrastructure for nested Collections (e.g. cf. https://github.com/documentcloud/backbone/issues/483#issuecomment-1595018 https://github.com/documentcloud/backbone/issues/483#issueco...). I understand that backbone doesn't want to grow fat in the core. However it would be nice to see a standard library emerge to cover these very common requirements (perhaps call it "ribcage"?).
- prateekdayal 15y ago> if the impl is sober, I haven't tried it out yet The way Backbone is structured (or this could be my limited understanding of it), you have to hack it at a very low level to get something like this going. Some people may have concerns with respect to Backbone.js upgrades breaking this. However, if you have test coverage in your app, upgrading backbone.js should not be an issue. It has worked well for us from 0.3.3 through 0.5.3 for example.
- mise 15y agoThe description of the solution sounds like a Factory to me. Is the Identity Map any different to a Factory approach?
- deleted 15y ago[deleted]
- latch 15y agoYes, they are different. The Factory Pattern is a pretty basic creational pattern. An Identify Map is more specialized. It specifically exists to avoid having multiple instances of the same entity. An identity map doesn't create objects, but it does cache and short-circuit the creation. If you did: id = 1 userA = Factory.create(:user, id) userB = Factory.create(:user, id) You should reasonably expect userA and userB to be different instances. If you asked an Identity Map for the same thing, they'd be the same instance.
- jvega 15y agoSounds more like singleton pattern. The concept is interesting, but the implementation is very intrusive Every new release of "Backbone" they must be merge his changes
- deleted 15y ago[deleted]
- crescentfresh 15y ago> One way to avoid the problem is to remember to pass the ticket object from the collection to the model and not instantiate a new one. My initial thought exactly. > However, as your app grows complex, you will not be able to keep a track of all the places where an object with a particular id is being initialized. That's a bit vague. Why not? This seems not much more than trying to standardize a global registry lookup for your models. Why not just make your collection globally accessible, and reference the fetched model with $app.tickets.get(id) ?
- prateekdayal 15y ago> This seems not much more than trying to standardize a global registry lookup for your models. Why not just make your collection globally accessible, and reference the fetched model with Because the collection could be inside another object. Also there would be other places in the app where ticket models are being initialized. By defining/having to refer to global variables, you are just writing code that is harder to maintain. The identity map approach is more like Rails where in a single request, the same SQL does not hit the db again and just returns the pre-fetched results. You don't have to think about where else you are fetching the same records ever.
- prateekdayal 15y agoAlso a ticket could be present in two different collections fetched from different urls. For example, the same ticket could be in /tickets/all_tickets and /tickets/my_tickets. The book keeping just keeps getting harder as your app grows
- jashkenas 15y agoNaturally, in most cases you'll want to only have a single representation of any given model on the client-side at a time. However -- there's really no need to do something this (https://gist.github.com/1393465 https://gist.github.com/1393465) fancy to get this behavior. Instead of making a new Ticket with an existing ticket's "id" ... just use the existing ticket. To alter SupportBee's example: var ticketView = new SB.Views.TicketView({ model: SB.Tickets.get(ticketId) }); prateekdayal responds, elsewhere in this thread: > Because the collection could be inside another object. > Also there would be other places in the app where > ticket models are being initialized. By defining/having > to refer to global variables, you are just writing code > that is harder to maintain. > > The identity map approach is more like Rails where in > a single request, the same SQL does not hit the db again > and just returns the pre-fetched results. You don't have > to think about where else you are fetching the same > records ever. Not quite. Being "more like Rails" doesn't always make more sense, when you're talking about a JavaScript application. In this case, you're creating an implicit global list for all Tickets, regardless of collection -- there's nothing that's less "global" about it. The global lookup is simply hidden inside of the extension, and subtly changes the behavior of "new". Now, when I write: "new Ticket(id)", will I get a new Ticket? Maybe, maybe not -- it depends on the id, and whether the client happens to have a cached copy. Clearer patterns for this include: Tickets.get(id) || new Ticket; Tickets.findOrCreate(id); ... and so on.
- prateekdayal 15y agoThanks for the comment Jeremy. What about this case (copying from my comment on my blog) "Also a ticket could be present in two different collections fetched from different urls. For example, the same ticket (id) could be in /tickets/all_tickets and /tickets/my_tickets. The book-keeping just keeps getting harder as your app grows"
- jashkenas 15y agoSure -- let's look at this particular case. In your implementation, you're using the "name" property of the constructor function to namespace lookups in your global cache: instance = Backbone.CacheStore.get(this.name + "/" + attributes.id); ... relying on the "name" property simply won't work in older versions of IE, unfortunately. Here's one quick sketch of a way to build a "findOrCreate" that you can use on all of your ticket collections -- and also have a reference to all of your client-side loaded tickets if you ever need it. SB.TicketCollection = Backbone.Collection.extend({ findOrCreate: function(id) { var model = SB.Tickets.get(id) || new SB.Ticket({id: id}); this.add(model); return model; } }); SB.Tickets = new SB.TicketCollection; Now, whenever you use an SB.TicketCollection, you can get the appropriate behavior with "findOrCreate". This particular pattern isn't terribly useful for many applications because a model that consists of only an ID doesn't give you much to work with. But perhaps I'm not fully understanding your use case.
- nikcub 15y ago> This is where the problem starts. You now have two instances of the model for the same ticket id. The problem is your architecture. The simplest solution is to always use a collection cache. The reason you separate views and collections is so that you only have each object in one place, you are killing the separation here by gluing them back together.