Especificando el orden de los resultados usando datamapper

I have two models that are associated. I have an Artist model and a Review model.

class Artist
  include DataMapper::Resource

  property :id, Serial
  property :name, String

  has n, :reviews

end

class Review
  include DataMapper::Resource

  property :id, Serial
  property :rating, Integer
  property :body, String

  belongs_to :artist
end

My goal is to retrieve an array of artists ordered by the average rating of the reviews associated with them. I'm not sure if there is any one line solution to this, but my current method is to create an array with the id's of the artists sorted by average rating. The details of how I obtain this array is not important for this question but let's say I now have an array.

ids_sorted_by_rating = [4,5,23,9,2,48,17,....]

I then retrieve the artists.

artists = Artist.all(:id => ids_sorted_by_rating)

The problem is that datamapper orders by :id unless you specify another option. So after all my hard work to get ids_sorted_by_rating, I'm still left with a list of artists just sorted by :id.

Any solution that gives me a list of artists ordered by the average rating of the associated reviews would be MUCH appreciated. Bonus points if I don't need to hit the database a million times to get that list. :)

Thanks and if you need more info just ask and I'll provide!

preguntado el 09 de marzo de 12 a las 22:03

2 Respuestas

AFAIK there is no easy way to do sorting by aggregation in DataMapper (the way to go to do it in single query). You can use an app-side reordering of results (unless your datasets are too big for this):

hash = artists.index_by(&:id)
# if you do not have index_by - use a artists.inject({}){|h,a| h.merge(a.id => a)} 
ids_sorted_by_rating.map{|id| hash[id]}

respondido 09 mar '12, 23:03

BTW, i didn't forget about you. I just haven't had a chance to try this out. - wuliwong

Thanks, I wound up going a different direction from the problem I was writing this question to solve but I came up on this same issue in a new place and this works. I think what I need to start doing is creating custom associations instead of just telling datamapper :through => Resource - wuliwong

If you just want to display the data (e.g. you're creating a web page with the list) and aren't going to be altering the Artists you could "cheat" and use SQL (assuming you're using a SQL datastore):

repository(:default).adapter.select(
'SELECT artists.*
FROM artists JOIN reviews on artists.id = reviews.artist_id
GROUP BY artists.id
ORDER BY AVG(reviews.rating)')

Esto no vuelve Artist objects, but an array of Structs that will each have all the properties of an Artist, so you can get all the data, but updating them won't do much good. A later query can then fetch the "real" Artist (e.g. a user clicks on the link for an artist and you can use the appropriate id value to query for the corresponding Artist objeto).

You'll also need to be careful about different dialects of SQL, and may need to tweak it to work on whatever DBMS you're using (this works on Sqlite, I haven't tried any others).

The array will however be in order of the average rating of the reviews of each artist.

respondido 10 mar '12, 00:03

I thought about taking this path but I switch between Sqlite and Postgres depending if I'm on my local machine or heroku. It is nice to just stick with straight Datamapper then you can account for the change from Sqlite to Postgres in one line. Thanks though. If it becomes a serious problem of resource consumption in the future, I may have to implement something like this. - wuliwong

No es la respuesta que estás buscando? Examinar otras preguntas etiquetadas or haz tu propia pregunta.