How to save many-to-many relations


#1

I have 2 models with a many-to-many relations:

  • Shop
  • Event

Being on templates/events/index.hbs page that displays the list of events, how is possible to assign an event to a shop (I have the instance of the current shop everywhere in the application via a service current-shop) ?

My first idea was to create an end-point on the backend side (`Rails’)

POST /shops/:shop_id/events/:id(.:format) v1/shop_events#handle_event

The difficulty I have on Ember side is how to call the above route from controllers/events/index.js controller ?

I tried to do as follows:

handleEvent(event) {
  let event = this.store.peekRecord('event', 1);
  let shop = this.store.peekRecord('shop', 1);
  shop.get('events').pushObject(event);
  shop.save();
}

but, of course Ember tried to hit PATCH http://localhost:3000/shops/:shop_id.

I’m using ember-data-url-templates and override adapters when needed. How to solve that without modifying the existing shop adapter ?

Thank you.


#2

Personally, I like exposing the join model as a resource, and just managing those.


#3

I created a separate model for ShopEvent:

import DS from 'ember-data';

export default DS.Model.extend({
  shop: DS.belongsTo(),
  event: DS.belongsTo()
});

Then I created its adapter to manage the end point:

#adapters/shop-event.js

import ApplicationAdapter from './application';
import UrlTemplates from "ember-data-url-templates";

export default ApplicationAdapter.extend(UrlTemplates, {
  urlTemplate: '{+host}/shops/{shopId}/events',
  createRecordUrlTemplate: '{+host}/shops/{shopId}/events/{eventId}',

  urlSegments: {
    shopId: function(type, id, snapshot, query) {
      if (query && query.shop_id) {
        return query.shop_id;
      }
      return snapshot.belongsTo('shop', { id: true });
    },

    eventId: function(type, id, snapshot, query) {
      if (query && query.event_id) {
        return query.event_id;
      }
      return snapshot.belongsTo('event', { id: true });
    },
  }
});

And finally in the controller action I save a ShopEvent resources s follows:

actions: {
    async handleEvent(event) {
      let published = event.get('published');
      let flashText = published ? 'flash.event.activated' : 'flash.event.deactivated';
      let shop = this.get('currentShop.shop');
      let eventId = event.get('id');
      let shopEvent = this.store.createRecord('shopEvent', {
        shop: shop,
        event: this.store.peekRecord('event', eventId),
      });

      await shopEvent.save();
      this.get('flashMessages').success(this.get('i18n').t(flashText));  
      
      await this.transitionToRoute('country-events');
    }
  }

#4

@NullVoxPopuli It seems to work for creating a ShopEvent instance but fails for deletion :frowning: Here is what I’m trying to do in country-events/index.js controller (the template displays country events and I have to activate/deactivate an event for a shop):

async handleEvent(event) {
      let activated = this.activatedByShop(event);
      let flashText = activatedByShop ? 'flash.event.deactivated' : 'flash.event.activated';
      
      let eventToHandle = this.store.peekRecord('event', event.get('id'));
      let shop = this.currentShop.get('shop');

      let shopEvent = this.store.createRecord('shopEvent', {
        shop: shop,
        event: eventToHandle
      });
      
      if (activated) {        
        shop.get('events').removeObject(eventToHandle);
        shopEvent.deleteRecord();
      } else {
        shop.get('events').addObject(eventToHandle);
      }
      
      await shopEvent.save();

      this.get('flashMessages').success(this.get('i18n').t(flashText));
      this.send('refreshModel');
      this.transitionToRoute('country-events');
    }
  },

  activatedByShop(event) {
    let shopEventsIds = this.get('currentShop.shop.events').mapBy('id');
    return shopEventsIds.includes(event.get('id'));
  }

The problem is that delete end-point is never hit :frowning:. Any idea ? Thank you.