How to Average Multiple Columns in Rails - ruby-on-rails

I have the following objects: Products, Ratings, and Users. I am trying to filter and select a number of Products that the User owns (through a has_many :through relationship with UserProducts) and average a certain column the Ratings table that matches their User ID and the correct Product ID.
So, my function is something along these lines:
def find_rating(criteria)
product = self.products.find(:all, :conditions => ["criteria = ?", criteria])
rating = self.ratings.where("product_id = ?", product).average(:overall)
end
I think that I'm going about this the wrong way, because I'm trying to find a product_id by passing an entire array of data consisting of multiple products. But, I think of using a more traditional loop and that seems convoluted. Can someone point me in the right direction for solving this problem? Thanks!

If product is a single entry, as it appears to be in your code, I would do this:
rating = self.products.find_by_criteria(criteria).ratings.average(:overall)
If it's an array of products, this method may help you: http://apidock.com/rails/ActiveRecord/Batches/ClassMethods/find_each

Related

Using has_scope to filter by nested attributes in a HABTM relationship

I have an API in which i have a HABTM relationship between Characters and Movies (a movie has many characters and a character has many movies). I'm trying to set up a filter with has_scope gem so i can do something like
/api/characters?by_movie=:movie_id so instead of getting all the Characters from index in my CharactersController, i only get the characters that take part in a specific movie.
The way the relationship is set up allows me to do something like Characters.find(1).movies -> returns the list of movies of that character. And if i send a POST to character and i want to add movies to it i can do it like "movie_ids":[1,2].
I have tried this approach with no success:
in my Character.rb scope :by_movie, -> id, {where(movie_ids: id)} and scope :by_movie, -> id, {where(movies: id)} and in my controller: has_scope :by_movie, using: :id
The documentation in has_scope is very little, i wasn't able to find my specific problem, i hope some of you can help me, thank you.
Well, i figured it ou (sort of)
app/models/movie.rb
has_and_belongs_to_many :genres
scope :by_genre, -> (genres) {
genre_array = genres.split(',') # this turns the genre string into an array
joins(:genres).where(genres: genre_array) # this joins the genres table to our movies model, these tables are related already by a HABTM relationship.
}
And in our app/controllers/movies_controller.rb
has_scope :by_genre
#movies = apply_scopes(Movie).all
The only problem i can't solve is that if i pass let's say
localhost:3000/studios/movies?by_genre=1,2,3 #This is what your GET should look like. 1, 2 and 3 are the id's of the Genres the movie has.
say a movie has all three genres, so when i send this, i get as a result that one movie three times, and not only one result. So instead of comparing our genres_array with genre_ids array, it's sort of comparing each element of genres_array with genre_ids, one at a time. I haven't been able to solve this problem yet.

Rails 4 Selecting uniq objects related AR CollectionProxy

I'm dealing with a shopping cart, which has many products in it. The products can be sold by multiple different companies. What I'm trying to do is select the companies uniquely so that I can create one order per vendor.
the hack
companies = []
#cart.products.each { |p| companies << p.company }
companies.uniq
#create order for each company
I'm not sure if #pluck is something I should be using here, but I do know that there has got to be a more efficient way of collecting my companies "uniquely". #uniq doesn't seem to be working for me, and neither does #cart.products.pluck(:company)
I have no :company_id in my Bid model, it's a has_many/has_one relationship
pluck is used for retrieving array of values in provided column like:
#cart.products.pluck(:company_id) # => [1,2,3]
For collecting companies you can do companies = #cart.products.collect(&:company).uniq

Ruby/rails sort_by not ordering properly?

I have a long array of Photo model objects, and I want to sort them by created_at, newest first, then get a new array with the first 21 photos.
My problem is that the final array is not ordered properly.
Here is my code:
#recent_photos = photos.sort_by(&:created_at).reverse.first(21)
when I print out #recent_photos the created_at values are ordered like this:
1458948707
1458943713
1458947042
1458945171
...
What is the correct way to sort objects?
UPDATE:
here's how the initial list is compiled:
photos = #user.photos
#following = #user.following
#following.each do |f|
photos += f.photos if f.id != #user.id
end
#user.memberships.each do |group|
photos += group.photos
end
SOLUTION:
problem was with the question - I wanted to sort by timestamp not created_at, and those were timestamp values in the output
You can crunch it all down into a single query:
#recent_photos = Photo.where(
user_id: #user.following_ids
).order('created_at DESC').limit(21)
You really do not want to be doing N queries for each of these as it will get slower and slower as a person has more people they're following. If they follow 10,000 people that's a ridiculous number of queries.
If you add a :through definition to your model you may even be able to query the photos directly:
has_many :follower_photos,
class_name: 'Photo',
through: :followers
Whatever your constraints are, boil them down to something you can query in one shot whenever possible. If that's not practical, get it down to a predictable number of queries, never N.
Try:
#recent_photos = Photo.order('created_at desc').first(21)

Rails sort users by method

I'm trying to rank my user's in order of an integer. The integer I'm getting is in my User Model.
def rating_number
Impression.where("impressionable_id = ?", self).count
end
This gives each User on the site a number (in integer form). Now, on the homepage, I want to show an ordered list that places these user's in order with the user with the highest number first and lowest number second. How can I accomplish this in the controller???
#users = User....???
Any help would be appreciated!
UPDATE
Using this in the controller
#users = User.all.map(&:rating_number)
and this for the view
<% #users.each do |user| %>
<li><%= user %></li>
<% end %>
shows the user's count. Unfortunately, the variable user is acting as the integer not the user, so attaching user.name doesn't work. Also, the list isn't in order based on the integer..
The advice here is still all kinds of wrong; all other answers will perform terribly. Trying to do this via a nested select count(*) is almost as bad an idea as using User.all and sorting in memory.
The correct way to do this if you want it to work on a reasonably large data set is to use counter caches and stop trying to order by the count of a related record.
Add a rating_number column to the users table, and make sure it has an index defined on it
Add a counter cache to your belongs_to:
class Impression < ActiveRecord::Base
belongs_to :user, counter_cache: :rating_number
end
Now creating/deleting impressions will modify the associated user's rating_number.
Order your results by rating_number, dead simple:
User.order(:rating_number)
The advice here is just all kinds of wrong. First of model your associations correctly. Secondly you dont ever want to do User.all and then sort it in-memory based on anything. How do you think it will perform with lots of records?
What you want to do is query your user rows and sort them based on a subquery that counts impressions for that user.
User.order("(SELECT COUNT(impressions.id) FROM impressions WHERE impressionable_id = users.id) DESC")
While this is not terribly efficient, it is still much more efficient than operating with data sets in memory. The next step is to cache the impressions count on the user itself (a la counter cache), and then use that for sorting.
It just pains me that doing User.all is the first suggestion...
If impressions is a column in your users table, you can do
User.order('impressions desc')
Edit
Since it's not a column in your users table, you can do this:
User.all.each(&:rating_number).sort {|x,y| y <=> x }
Edit
Sorry, you want this:
User.all.sort { |x, y| y.rating_number <=> x.rating_number }

How do I select only the associated objects in a Rails "where" query?

I have a model Category, which has_many Products, and a Product in turn has_many Categories. When a user searches for a Category, I'd like to return the products of the matching Categories without losing my Arel object. Here's what I have so far:
Category.where("upper(title) like ?", search_term.upcase).map {|category| category.products}.flatten
This does the trick of returning the products, but of course what I have is an array and not Arel. I can get as far as adding an :includes(:products) clause, so I do indeed get the products back but I still have them attached to their categories. How do I adjust my query so that all I get back is an Arel that only addresses products?
If it is products that you want then you should probably start with the Product object when you are searching. For example ,you could do it like this:
Product.joins(:categories).where("upper(categories.title) like ?", search_term.upcase)
The reason I use joins instead of includes is that joins will perform an INNER JOIN instead of LEFT OUTER JOIN which is what you need to only return the products that are actually associated with the found categories.
To make it a little more elegant you could wrap it all up in a scope in your Product model like this:
# In Product.rb
scope :in_categories_like, Proc.new{ |search_term|
joins(:categories).where("upper(categories.title) like ?", search_term.upcase)
}
# In use
#products = Product.in_categories_like(params[:search_term])

Resources