How to sort by results of a method - ruby-on-rails

I have a method on my User.rb model in my Rails app that returns a reputation score based on a user's various contributions to the site.
def total_votes
answerkarma = AnswerVote.joins(:answer).where(answers: {user_id: self.id}).sum('value')
contributionkarma = Contribution.where(user_id: self.id).sum('value')
answerkarma + contributionkarma
end
In the index action of my imaginary artists controller, sometimes I retrieve all artists, and sometimes I retrieve only those filtered by location
def index
if params[:province_id]
province = params[:province_id]
province = province.to_i
#artistsbyprovince = User.artists_by_province(province)
else
#artists = User.where(:onionpealer => true)
end
end
If I'm filtering by location, the User model calls this scope method
scope :artists_by_province, lambda {|province|
joins(:contact).
where( contacts: {province_id: province},
users: {onionpealer: true})
}
What I'd like to do, however, is, in both cases (whether filtering by location or retrieving all users) is to sort the retrieval of the artists by the results of total_votes method, so that artists with the highest number of points appear at the top.
Can you suggest how I might do that. Thank you in advance.

If do not pay attention on wrong query architecture, sorting can be done by ruby array#sort_by
#artistsbyprovince = User.artists_by_province(province).sort_by{ |u| -u.total_votes }

Related

Rails append the correct model object from has_many association to an array of Models

companies = Company.all
companies.each do |company|
company.locations = current_user.locations.where(company_id: company.id)
end
return companies
Is there a better way of doing what I'm trying to do above, preferably without looping through all companies?
You need to use includes, given you did set up relationships properly:
companies = Company.all.includes(:locations)
I would be concerned here because you dont use any pagination, could lead to tons of data.
There are many articles explaining in depth like this
After your edit, I'd say
companies = Company.all
locations_index = current_user.locations.group_by &:company_id
# no need for where(company_id: companies.map(&:id)) because you query all companies anyway
companies.each do |company|
company.locations = locations_index.fetch(company.id, [])
end
This way, you have 2 queries max.

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 }

Ruby on Rails search 2 models

Right... I've spent 3 days trying to do this myself to no a vale.
I have 2 models called Film and Screenings. Screenings belongs_to Film, Film has_many Screenings.
The Film has certain attributes(:title, :date_of_release, :description, :genre).
The Screening has the attributes(:start_time, :date_being_screened, :film_id(foreign key of Film)).
What I am trying to do is create a Search against both of these models.
I want to do something like this...
#films = Film.advanced_search(params[:genre], params[:title], params[:start_time], params[:date_showing])
And then in the Film model...
def self.advanced_search(genre, title, start_time, date)
search_string = "%" + title + "%"
self.find(:all, :conditions => ["title LIKE ? OR genre = ? OR start_time LIKE ? OR date_showing = ?", title, genre, start_time, date], order: 'title')
end
end
I don't think this could ever work quite like this, but I'm hoping my explanation is detailed enough for anyone to understand what im TRYING to do?? :-/
Thanks for any help guys
I would extract the search capability into a separate (non-ActiveRecord) class, such as AdvancedSearch as it doesn't neatly fit into either the Film or Screening class.
Rather than writing a complex SQL query, you could just search the films, then the screenings, and combine the results, for example:
class AdvancedSearch
def self.search
film_matches = Film.advanced_search(...) # return an Array of Film objects
screening_matches = Screening.advanced_search(...) # return an Array of Screening objects
# combine the results
results = film_matches + screening_matches.map(&:film)
results.uniq # may be necessary to remove duplicates
end
end
Update
Let's say your advanced search form has two fields - Genre and Location. So when you submit the form, the params sent are:
{ :genre => 'Comedy', :location => 'London' }
Your controller would then something like:
def advanced_search(params)
film_matches = Film.advanced_search(:genre => params[:genre])
screening_matches = Screening.advanced_search(:location => params[:location])
# remaining code as above
end
i.e. you're splitting the params, sending each to a different model to run a search, and then combining the results.
This is essentially an OR match - it would return films that match the genre or are being screened at that specified venue. (If you wanted and AND match you would need to the work out the array intersection).
I wanted to write something but this cast says all http://railscasts.com/episodes/111-advanced-search-form
Almost the same case as yours.

Selective ActiveRecord

Say I'm having a table 'Users'.
A user can exist 3 times (records) in my table, in 3 different states (state1, state2, state3).
First state1 will be created, then state2, ...
If state3 exists, I don't want to look at state1 and state2 anymore, but I'll have to keep them in my table, for later purposes.
All 3 records have the same uuid.
If I want to collect all users, I can't use User.all (because he will give me all 3 states for the same user).
Is there a short solution for this in my model? Now I'm collecting all uuid's and for each uuid I'll check which is the latest state, then I put those records in an array.
Problem with this array is that it is just 'an array', instead of an ActiveRecord object.
#uuid = []
#users = [] #will contain only the latest states at the end
User.all.each do |u|
#uuid << u.uuid unless #uuid.includes?(u.uuid)
end
#uuid.each do |u|
if user = User.find_by_state_and_uuid(3, u)
#users << user
elsif user = User.find_by_state_and_uuid(2, u)
#users << user
elsif user = User.find_by_state_and_uuid(1, u)
#users << user
end
end
Any ideas how I can translate this logic to an ActiveRecord object?
In short: User.magic_function to return only the latest state of each uuid
Thanks in advance!
Wouter
If you plan ahead, you can always sort on your state and return the "highest" one. This works well if you have a linear progression from one to the next. As an example:
user = User.where(:uuid => u).order('users.state DESC').first
For more complicated transitions you're not going to be able to use this trick. You could try using a different column for ordering, such as fetching the last by created_at time.
From a design perspective it seems highly unusual to have several user records in different states. A better plan might be to split out the state-driven part of the user record into a separate table and do the state tracking there, everything linked back to a singular user record.
Have you looked into using scopes? You should be able to create a scope for each User state, and then use those for querying.
Try :
User.get_user(state, uuid)
And make the scope in your user model :
scope :get_user, lambda { |*args| { :conditions => ["state = ? AND uuid = ?",args.first , args.second ] }}

ActiveRecord query returns an incorrect model

I have been scratching my head over this one for a little while, and though I'm sure its a stupid mistake, I've reached the point where I must consult SO if I am to preserve the hair follicles I have left.
I've written a function in Rails (3.1.2) which should return an array populated with ActiveRecord model objects (users, in this case) which meet a certain criterion. The criterion is that the user's current list (denoted by the field active_list_id) must not be nil. The code follows:
def build_list_array
#lists = Array.new
User.all.each do |user|
#active_list_id = user.active_list_id
#lists<< List.find(#active_list_id) if #active_list_id != nil #TODO WHAT?!? WHY IS THIS RETURNING USERS?
end
end
As you can see, I'm initializing an empty array, cycling through all users and adding their active list to the array if the relevant reference on the user record is not nil. The problem is, this is returning user objects, not list objects.
Here are the associations from the user and list models:
user model:
has_many :lists
has_many :tasks
list model:
belongs_to :user
A brief word about the reference to active_list: A user can have many lists, but only one is active at any time. Therefore, I need to reference that list on the user record. The active list is not a foreign key in the typical sense, then.
I appreciate any help you can give me...Thanks =)
As it stands, your build_list_array will return an array of User because of the behavior of each. When iterating over a collection using each, the call to each returns the original collection.
For example,
list = []
# returns => []
[1,2,3,4,5].each { |number| list << number * 10 }
# returns => [1, 2, 3, 4, 5]
list
# returns => [10, 20, 30, 40, 50]
In your code, the last statement in your build_list_array method is the each call, meaning the return value of each is what is returned by the method. If you simply added a return statement at the end of the method you would be good to go.
def build_list_array
#lists = Array.new
User.all.each do |user|
#active_list_id = user.active_list_id
#lists<< List.find(#active_list_id) if #active_list_id
end
return #lists # Actually return #lists
end
That being said, you should probably use something like Bradley's answer as a basis for more "correct" Rails code.
each always returns the collection it iterates on (no matter what happens inside the block). Sounds like you want to return #lists at the end of your method.
You seem to be making a curious use of instance variables. You could also fetch this in one query via a join, something along the lines of
List.joins('inner join users on active_list_id =lists.id')
Activerecord's Arel is your friend here:
User.where(:active_list_id.not_eq => nil)
Extending Steven's answer, to get the Lists
class User
belongs_to :active_list, :class_name => "List"
def build_list_array
#lists = User.where('active_list_id is not null').map(&:active_list).compact

Resources