Ville Lehtinen
Ville Lehtinen

Reputation: 33

Ember computed property for retrieving one record out of a hasMany relationship?

Here's my situation, simplified:

// model/price-source.js

export default DS.Model.extend({
  price: DS.attr('number'),
  product: DS.belongsTo('product')
)};

// model/product.js

export default DS.Model.extend({
  priceSources: DS.hasMany('price-source')
)};

In my products template, I want to be able to simply refer to the source with the lowest price, like so:

// templates/products.hbs

{{#each model as |product|}}
<span>{{product.cheapestSource.price}} €</span>
{{/each}}

How would I go about setting up the cheapestSource computed property? I imagine I'd have to do something like this:

// model/product.js

  cheapestSource: Ember.computed('priceSources', function() {
    let sources = this.get('priceSources');
    let cheapest = sources.get('firstObject');

    // iterate over sources and set cheapest to whichever has the lowest price

    return cheapest;
  })

The problem is, I have little idea how to loop through the hasMany relationship (apart from using the handlebars {{#each}} helper), and whether a computed property can even consist of a single Ember Data record from another model. Does sources.@each somehow play into this, if so, how?

Any help and ideas are appreciated, thanks.

Upvotes: 0

Views: 702

Answers (3)

Ville Lehtinen
Ville Lehtinen

Reputation: 33

I got it working by sorting the priceSources into a computed property sortedPrices, then calling the firstObject of the sortedPrices in the template. Will edit this post with the actual solution soon.

It took ages to test because I didn't realize that commenting out handlebars blocks will break the rendering of html inside them. Note to self...


EDIT: This did it:

export default DS.Model.extend({
  priceSources: DS.hasMany('price-source'),
  sortProperties: ['price:asc'],
  sortedSources: Ember.computed.sort('priceSources', 'sortProperties')
});

Then in the template:

<span>{{product.sortedSources.firstObject.price}} €</span>

Works ok, without a ton of code.

Upvotes: 1

Ember Freak
Ember Freak

Reputation: 12872

If you got time try this solution too. this.get('priceSources') it returns Promise so you need to access resultant in then method and wrap it in DS.PromiseObject so that you can access it like normal object in template.

cheapestSource: Ember.computed('[email protected]', function() {
        return DS.PromiseObject.create({
            promise: this.get('priceSources').then(sources => {
                let resultObj = {}
                    //sources is normal array you can apply your logic and set resultObj
                return resultObj;
            })
        });
    })

Upvotes: 0

Sedad Kosovac
Sedad Kosovac

Reputation: 668

This you can do on a controller where you need to use cheapestSource.

   cheapestSource: Ember.computed('priceSources', function() {
        let sources = this.get('priceSources');
        let cheapest = sources.get('firstObject');   
        let array = sources.mapBy("price");
        let min = array.reduce(function(a, b, i, array) {return Math.min(a,b)});
        sources.forEach(function(source){
          if (source.get("price") == min){
            cheapest = source;
          } 
       });
    return cheapest;
    })

Model is bit hard to achieve what you want this is one why using one computed and after template is render computed becomes object that you need.

cheapestSource: Ember.computed('priceSources', function() {
        let product = this;
        this.get('priceSources').then((sources)=>{
        let array = sources.mapBy("price");
        if(array.length>0){
        let min = array.reduce(function(a, b, i, array) {return Math.min(a,b)});
        sources.forEach(function(source){
          if (source.get("price") == min){
            product.set("cheapestSource", source);
          } 
       });
      }
    });
  })

When I have issues like this I use active model adapter on Rails and return for example cheapestSourcePrice as part of product in my custom serializer then in Ember product model just add cheapestSourcePrice and in template {{product.cheapestSourcePrice}} You dont want ember to do heavy lifting like this but if you dont control the server then do it like this. And one more thing after it sets source to cheapesetSource computed is no more until refresh. If you need it to stay computed you must add one more property on model then set him insted example

cheapestSource2: DS.attr()

this will allow for it to be an object

product.set("cheapestSource2", source);

and then in template {{product.cheapestSource}}{{product.cheapestSource2.price}}

first property you call is there so computed is called.

Upvotes: 0

Related Questions