Batman polymorphic belongs to association

120 Views Asked by At

I am trying to set up a polymorphic association for a parent that has multiple different types of children. What I find in the documentation is about the inverse problem: children with different types of parents.

I tried the code below, but that always results in productables of type 'Store', while I want them to be 'Book' or 'DVD'.

class App.Product extends Batman.Model
  @belongsTo 'productable', {polymorphic: true}

class App.Book extends Product
class App.DVD extends Product

class App.Store extends Batman.Model
  @hasMany 'products', {as: 'productable'}

Thanks.

1

There are 1 best solutions below

5
On

Yes, you're right. Usually, polymorphism is for a case like this:

class App.Product extends Batman.Model
  @belongsTo 'productable', polymorphic: true

class App.Store extends Batman.Model
  @hasMany 'products', as: 'productable', polymorphic: true

class App.VendingMachine extends Batman.Model
  @hasMany 'products', as: 'productable', polymorphic: true

Now, a Product will store both productable_id and productable_type. productable_type will be either Store or VendingMachine.

But... you want to have children of different types available from a single accessor? I don't know that batman.js supports this out of the box, but here's a way you might be able to get it done:

class App.Store extends Batman.Model
  @hasMany 'products' # setup the relationships individually
  @hasMany 'books'
  @hasMany 'dvds'

  @accessor 'allProducts', -> # then create a custom accessor for all of them
    products = @get('products')
    books = @get('books')
    dvds = @get('dvds')
    products.merge(books, dvds) # returns a new Batman.Set

Actually, the docs say not to use Set::merge inside accessors, but rather to use Batman.SetUnion. If this approach seems like it would work, you might want to look into that!

Then, if you need to send class names back in JSON, you can use @encode on your child models, for example:

class App.Product extends Batman.Model
  @resourceName: 'product'
  @encode 'product_type', 
    encode: (value, key, builtJSON, record) ->   
      builtJSON.product_type = Batman.helpers.camelize(record.constructor.resourceName)

Hope that helps!

Edit:

you could shorthand the relation and accessor, if you think you'll have a lot of them:

class App.Store extends Batman.Model
  @productTypes: [
    'products'
    'books'
    'dvds'
   ]
  for productType in @productTypes
      @hasMany productType

  @accessor 'allProducts', ->
    allProducts = new Batman.Set
    productSets = (@get(pt) for pt in @constructor.productTypes)
    allProducts.merge(productSets...)

Might be worth a shot anyways!