Create a new object in an Ember many-to-many relationship if it does not already exist

I am working with two models that have a many to many relationship - 'recipe' and 'tag':

App.ApplicationAdapter = DS.FixtureAdapter.extend({});

App.Recipe = DS.Model.extend({
    title: attr('string'),
    source: attr('string'),
    notes: attr('string'),
    isFavourite: attr('boolean', {defaultValue: false}),
    tags: hasMany('tag', { async: true })
});

App.Tag = DS.Model.extend({
    name: attr('string'),
    recipes: hasMany('recipe', { async: true })
});

Within my template, there is an input for adding tags to a recipe. Users can add one tag or multiple by separating them with commas.

{{input type="text" enter="AddTags" placeholder="add a tag (or two!)"}}

This triggers the 'AddTags' function in my RecipeController:

App.RecipeController = Ember.ObjectController.extend({
    actions: {
        AddTags: function(tags){
            var store = this.store;
            var thisRecipe = this.get('model');
            var thisRecipeTags = thisRecipe.get('tags');
            var addedTags = tags.split(',');

            for (i = 0; i < addedTags.length; i++) {
                tag = store.createRecord('tag', {
                    name: addedTags[i]
                });

                thisRecipeTags.addObject(tag);
            }
        }
    }
});

Although the current setup works well, I would like to enhance it so that a new tag is only created if a tag with the same name doesn't already exist:

AddTags: function(tags){
    var store = this.store;
    var thisRecipe = this.get('model');
    var thisRecipeTags = thisRecipe.get('tags');
    var addedTags = tags.split(',');
    for (i = 0; i < addedTags.length; i++) {
        // If exisiting tag where name == addedTags[i]
            tag = existing tag object here
        // Else create a new tag
            tag = store.createRecord('tag', {
                name: addedTags[i]
            });

        thisRecipeTags.addObject(tag);
    }
}

Any suggestions on how I can achieve this?

Update: A JS bin with the code can be found here: http://jsbin.com/kixexe/1/edit?js,output

Answer №1

Check out the live demo here

Let me break it down for you...

In your jsbin, you're utilizing DS.FixtureAdapter, which needs a queryFixtures method implementation if querying fixtures is involved (refer to here) especially when querying by name. A quick way to implement this would be:

MyApp.TagAdapter = DS.FixtureAdapter.extend({
    queryFixtures: function(fixtures, query){
      var result = [];
      var tag = fixtures.findBy('name', query.name);
      if(tag){
        result.push(tag);
      }

    return result;
  }
});

Upon querying the store, you receive a promise that can be accessed using then()

If nothing is found - just continue as before...

If an existing tag is found - add it to other tags, then save both the tags and recipe records since it's a many-to-many relationship.

UPDATE

Modified solution available here

The previous for loop caused confusion when mixing promises, which isn't ideal. Ember provides a more standard/functional approach using forEach(). Additionally, whitespace at the ends of tags was trimmed using map(). Check out here for more on map() and forEach()

// trim whitespace
var addedTags = tags.split(',').map(function(str){ 
   return  str.replace(/^\s+|\s+$/gm,'');
});

// iterate through tags
addedTags.forEach(function(tagName){
  store.find('tag', { name: tagName }).then(function(results){
    if(results.content.length){
      thisRecipeTags.addObject(results.content[0]);  
      thisRecipeTags.save();
      thisRecipe.save();
    } 
    else {
      var tag = store.createRecord('tag', {
        name: tagName
      });
      thisRecipeTags.addObject(tag);
      thisRecipeTags.save();
      thisRecipe.save();                    
    }
  });
});

UPDATE #2

Previously, the tag wasn't being removed properly. Since it's a two-way mapping, removing the tag from the recipe (which you were doing) AND the recipe from the tag are necessary (which you weren't). The revised code is below:

removeTag: function(tag) {
  var recipe = this.get('model');
  recipe.get('tags').removeObject(tag);
  tag.get('recipes').removeObject(recipe.get('id'));
  tag.save();
  recipe.save();
},

Working (hopefully without issues this time ;) solution here

Answer №2

If you're looking to find a specific tag, you can do so by following these steps:

var existingTag = store.find('tag', { name: addedTags[i] });

You can then implement the logic as shown below:

AddTags: function(tags){
    var currentStore = this.store;
    var currentRecipe = this.get('model');
    var recipeTags = currentRecipe.get('tags');
    var tagsToAdd = tags.split(',');
    for (i = 0; i < tagsToAdd.length; i++) {
        var existingTag = currentStore.find('tag', { name: tagsToAdd[i] });
        if(existingTag){
            newTag = existingTag        
        } else {
            newTag = currentStore.createRecord('tag', {
                name: tagsToAdd[i]
            });        
        }

        recipeTags.addObject(newTag);
    }
}

Kindly note that I haven't been able to test this yet, so you may need to make adjustments to accommodate promises or other factors -- but it should give you a good starting point.

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Maintaining a JavaScript script within the local project directory and integrating it for use with Angular

I could use some assistance. I am currently working on an Angular project and making API calls (GET/PUT) to a remote server. In order to improve the performance of my application, I decided to store the necessary JS file locally in the assets folder. Init ...

The Next.js developer encounters an issue where the build fails due to a ReferenceError on a client component, stating that "window

Just starting out with nextjs, I'm sticking to using only the basic features without diving into any advanced functionalities. During the next build process, I encountered an issue where 6 paths failed because of a ReferenceError: window is not defin ...

Ways to customize the datetime-local format in an HTML input field

When dealing with the HTML input of datetime type, consider the following: Datefield: <input type="datetime-local" data-date="" data-date-format="DD MMMM YYYY, h:mm:ss"> The script below includes important code. $("input").val(moment().format(&apo ...

I'm having trouble getting the drag event to work for Three.js trackball controls within a UIkit

The issue at hand involves a fullscreen Three.js canvas that is functioning perfectly, but there are limitations when displaying a preview in a UIkit modal – zooming works, but panning does not. JS: Renderer and Controls renderer = new THREE.WebGLRende ...

Validating checkboxes using HTML5

When it comes to HTML5 form validation, there are some limitations. For instance, if you have multiple groups of checkboxes and at least one checkbox in each group needs to be checked, the built-in validation may fall short. This is where I encountered an ...

Oops! Looks like Handlebars.js is telling us that a Helper is missing

Currently, I am utilizing handlebars.js templates alongside node and express. My goal is to create a numbered list using the {{@index}} template tag. However, since the index starts at 0 and I want it to start from one, it seems that a custom helper is req ...

Tips for creating an HTML page with the dimensions of an A4 paper sheet?

My goal is to display the HTML page in a browser while containing the content within the dimensions of an A4 size page. Additionally, when printed, the HTML page should be formatted to fit onto A4-sized paper pages. <div classNa ...

Using Vuejs to dynamically apply a class and trigger a function based on a specified condition

I am currently facing three small issues in my Vue app. The first problem involves class binding. I have a bootstrap card that displays a question and its related answers. When a user clicks on a radio input, their answer is saved and the other options are ...

Discord.js version 13 encountered an issue where it is unable to access properties of undefined while

Having trouble with creating a warn system that just won't work! I've tried various solutions but nothing seems to be fixing it. Would greatly appreciate any help! Error Log: [FATAL] Possibly Unhandled Rejection at: Promise Promise { <reje ...

Fixing issue of Rails AJAX causing partials to be overwritten upon page refresh

While trying to implement a user show view with dynamic post creation, I encountered an issue when refreshing the page after successfully creating posts. The issue arises when the previously created posts are overwritten with null IDs. Additionally, I am u ...

Manipulating attributes in HTML using jQuery which are stored in a variable

Generating dynamic inputs with unique names is my current requirement. Initially, I store the html template content in a variable: var template = $("#question_template").html(); Next, to differentiate each set of added input blocks, I include an index: ...

Javascript - accessing a local file (located in the same directory as the HTML file, not an uploaded file)

I have been attempting to find a solution, but have had no success. Is there a way to read a file using JavaScript or HTML? I have a text file named "sample.txt" with some information in it. This file is located in the same folder as the HTML file that con ...

Generate checkboxes by utilizing the JSON data

Here is a snippet of my JSON data: [ { "type": "quant", "name": "horizontalError", "prop": [ 0.12, 12.9 ] }, { "type": "categor", "name": "magType", "prop": [ ...

Unable to transmit an object containing a property that includes an array of other objects

I am attempting to send an object that has the following structure: var postBody = { id: userID, objectID: [0, 1], objects: [ {id: 0, x: 0.33930041152263374, y: 0.08145246913580247, width: 0.0823045267489712, height: 0. ...

Dealing with Asynchronous Frustrations: Is it best to utilize callbacks and what is the most effective way to pass them across various

I am working on developing a straightforward text-based game that functions within a socket.io chat room on a node server. The structure of the program is as follows: At present, there are three key modules: Rogue: serves as the core of rogue game functi ...

Storing JSON response in memory with Node.js: A handy guide

Hi there! I have a Node.js application where I am calling a route that returns JSON. I'm interested in storing that JSON data in memory. Can anyone suggest the best way to accomplish this in Node.js? ...

Hide the scroll bar in html/css while keeping the functionality of the arrows intact

Is there a way to remove the scroll bar but still be able to access overflown data using arrows? Any assistance would be greatly appreciated. Thank you in advance. ...

What is the method for toggling the icon only when the ID matches?

After retrieving data from the API and mapping it out, I encountered an issue where toggling one icon would toggle all icons that were mapped out. My goal is to be able to click on an individual icon instead of affecting all icons. import { FaRegHeart, FaH ...

Experiencing issues with retrieving undefined values from a JavaScript object

Attempting to access a specific property value from a JavaScript object, I have written the following code: for (key in map.regions) { console.log(key); console.log(states); console.log(states.key); } The key variable will contain something l ...

How can I make my navbar stay fixed in place and also activate the transform scale functionality?

After fixing the position of my navbar with the class "top," I noticed that the transform property scale does not work on the div element I applied. The hover effect on the box only works when I remove the position from the navbar. This is the HTML code: ...