thumbs_up gem to sort liked posted - rails - ruby-on-rails

I'm using the thumbs_up gem to allow users to like designs and products. In the users profile, i have it showing all of the designs and products that the user has liked. Right now, i have concatenated the designs and products into one view but they are ordered 'created_at DESC' for the designs+products. I want to order it based on when the user voted so when a user votes on a design, it's first on their profile. Here is the code i have
in the model
def favorites
Design.joins(:votes).where('voter_id = ?', self.id).where('voteable_type = ?', 'Design').where('vote = ?', true)
end
def favs
Product.joins(:votes).where('voter_id = ?', self.id).where('voteable_type = ?', 'Product').where('vote = ?', true)
end
in the controller
#user = User.find_by_username(params[:id])
#designs = #user.favorites
#products = #user.favs
#favorites = #designs.to_a.concat #products.to_a
#favorites.sort! {|t1, t2| t2.created_at <=> t1.created_at}
I've even removed the default scope for designs and products in case that was the issue but it didn't resolve it.
i've also tried this with no luck
Design.joins(:votes).where('voter_id = ?', self.id).where('voteable_type = ?', 'Design').where('vote = ?', true).order('created_at DESC')
there is a timestamp 'created_at' that belongs to each vote. so i know this is possible.

If a user has_many :votes, votes belong_to :voteable and every user only votes on a specific voteable once, I would try something like this:
#user.votes.includes(:voteable).order('created_at DESC')
This will return collection of votes ordered by their create date. Then you can iterate over them and display their voteable attribute however you want. If you only want designs and not products then you will need to do a join instead of includes and filter by voteable_type

Related

Rails query with condition in count

I'm having a little trouble with a query in Rails.
Actually my problem is:
I want to select all users which do not have any user_plans AND his role.name is equals to default... OR has user_plans and all user_plans.expire_date are lower than today
user has_many roles
user has_many user_plans
users = User.where(gym_id: current_user.id).order(:id)
#users = []
for u in users
if u.roles.pluck(:name).include?('default')
add = true
for up in u.user_plans
if up.end_date > DateTime.now.to_date
add = false
end
end
if add
#users << u
end
end
end
This code up here, is doing exactly what I need, but with multiple queries.
I don't know how to build this in just one query.
I was wondering if it is possible to do something like
where COUNT(user_plans.expire_date < TODAY) == 0
User.joins(:user_plans, :roles).where("roles.name = 'default' OR user_plans.expire_date < ?", Date.today)
Should work, not tested, but should give you some idea you can play with (calling .distinct at the end may be necessary)
There is also where OR in Rails 5:
User.joins(:user_plans, :roles).where(roles: { name: 'default' }).or(
User.joins(:user_plans).where('user_plans.expire_date < ?', Date.today)
)
FYI: Calling .joins on User will only fetch those users who have at least one user_plan (in other words: will not fetch those who have no plans)

Query optimization in associated models

I have a User model
class User < ActiveRecord::Base
has_many :skills
has_one :profile
end
Profile table has two columns named, age & experience
Now, I've a search form where the parameters are passed are:
params[:skill_ids] = [273,122,233]
params[:age] = "23"
params[:experience] = "2"
I've to search through all the users where user's skills meet any of the params[:skill_ids] and also from the user's profile, their age and experience.
Do I have to go through a loop like:
users = []
User.all.each do |user|
if (user.skills.collect{|s| s.id} & params[:skill_ids] ) > 0
// skip other parts
users << user
end
end
or, any of you have any better solution?
Because your skills belong to exactly one user, you could first fetch all users belonging to the skill ids provided and filter them by the other criteria:
matching_users = User.includes(:skills, :profile)
.where(["skills.id in (?) AND profile.age = ? AND profile.experience = ?",
params[:skill_ids],
params[:age].to_i,
params[:experience].to_i]
)
Try this:
#users = User.includes(:skills).where(["skills.id in (?)", params[:skill_ids]]).all

Rails: How to use includes with conditions?

I have a ressource Room that has_many reservations. Likewise the ressource Reservation belongs_to a room.
My reservation model has a starts_at and an ends_at attribute.
In my index action for the Room controller I want to get all the Rooms and include all the reservations for a certain date(the date is given as a parameter).
I have tried this code, but it doesn't seem to work as intended.
#rooms = Room.includes(:reservations).where("reservations.starts_at" => params[:date])
I am aware that this code does not take into account that a room could be reserved for multiple days, but I had to start somewhere.
SUM UP
Based on a date the action should return all the rooms, but only include the reservations that is relevant for that date.
EDIT
This is what I ended up doing.
controllers/rooms_controller.rb
def index
#rooms = Room.includes(:reservations)
#date = params[:date] ||= Date.today
end
views/rooms/index.html.haml
- #rooms.each do |room|
- room.reservations.relevant(#date).each do |reservation|
= reservation.id
models/reservation.rb
def self.relevant(date = Date.today)
if date.blank?
date = Date.today
end
where(
'(starts_at BETWEEN ? AND ?) OR (ends_at BETWEEN ? AND ?)',
date.to_date.beginning_of_day, date.to_date.end_of_day,
date.to_date.beginning_of_day, date.to_date.end_of_day
)
end
It works alright, but the view is talking to the model I think?
If your where conditions refer to another table then you also need to need to specify references as well as includes. e.g.
#rooms = Room.includes(:reservations).
where("reservations.starts_at" => params[:date]).
references(:reservations)
See the API documentation here.

How to sort by results of a method

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 }

Rails ActiveRecord: Find All Users Except Current User

I feel this should be very simple but my brain is short-circuiting on it. If I have an object representing the current user, and want to query for all users except the current user, how can I do this, taking into account that the current user can sometimes be nil?
This is what I am doing right now:
def index
#users = User.all
#users.delete current_user
end
What I don't like is that I am doing post-processing on the query result. Besides feeling a little wrong, I don't think this will work nicely if I convert the query over to be run with will_paginate. Any suggestions for how to do this with a query? Thanks.
It is possible to do the following in Rails 4 and up:
User.where.not(id: id)
You can wrap it in a nice scope.
scope :all_except, ->(user) { where.not(id: user) }
#users = User.all_except(current_user)
Or use a class method if you prefer:
def self.all_except(user)
where.not(id: user)
end
Both methods will return an AR relation object. This means you can chain method calls:
#users = User.all_except(current_user).paginate
You can exclude any number of users because where() also accepts an array.
#users = User.all_except([1,2,3])
For example:
#users = User.all_except(User.unverified)
And even through other associations:
class Post < ActiveRecord::Base
has_many :comments
has_many :commenters, -> { uniq }, through: :comments
end
#commenters = #post.commenters.all_except(#post.author)
See where.not() in the API Docs.
#users = (current_user.blank? ? User.all : User.find(:all, :conditions => ["id != ?", current_user.id]))
You can also create named_scope, e.g. in your model:
named_scope :without_user, lambda{|user| user ? {:conditions => ["id != ?", user.id]} : {} }
and in controller:
def index
#users = User.without_user(current_user).paginate
end
This scope will return all users when called with nil and all users except given in param in other case. The advantage of this solution is that you are free to chain this call with other named scopes or will_paginate paginate method.
Here is a shorter version:
User.all :conditions => (current_user ? ["id != ?", current_user.id] : [])
One note on GhandaL's answer - at least in Rails 3, it's worth modifying to
scope :without_user, lambda{|user| user ? {:conditions => ["users.id != ?", user.id]} : {} }
(the primary change here is from 'id != ...' to 'users.id !=...'; also scope instead of named_scope for Rails 3)
The original version works fine when simply scoping the Users table. When applying the scope to an association (e.g. team.members.without_user(current_user).... ), this change was required to clarify which table we're using for the id comparison. I saw a SQL error (using SQLite) without it.
Apologies for the separate answer...i don't yet have the reputation to comment directly on GhandaL's answer.
Very easy solution I used
#users = User.all.where("id != ?", current_user.id)
User.all.where("id NOT IN(?)", current_user.id) will through exception
undefined method where for #<Array:0x0000000aef08f8>
User.where("id NOT IN (?)", current_user.id)
Another easy way you could do it:
#users = User.all.where("id NOT IN(?)", current_user.id)
an array would be more helpful
arrayID[0]=1
arrayID[1]=3
User.where.not(id: arrayID)
User.where(:id.ne=> current_user.id)
ActiveRecord::QueryMethods#excluding (Rails 7+)
Starting from Rails 7, there is a new method ActiveRecord::QueryMethods#excluding.
A quote right from the official Rails docs:
excluding(*records)
Excludes the specified record (or collection of records) from the resulting relation. For example:
Post.excluding(post)
# SELECT "posts".* FROM "posts" WHERE "posts"."id" != 1
Post.excluding(post_one, post_two)
# SELECT "posts".* FROM "posts" WHERE "posts"."id" NOT IN (1, 2)
This can also be called on associations. As with the above example, either a single record of collection thereof may be specified:
post = Post.find(1)
comment = Comment.find(2)
post.comments.excluding(comment)
# SELECT "comments".* FROM "comments" WHERE "comments"."post_id" = 1 AND "comments"."id" != 2
This is short-hand for .where.not(id: post.id) and .where.not(id: [post_one.id, post_two.id]).
An ArgumentError will be raised if either no records are specified, or if any of the records in the collection (if a collection is passed in) are not instances of the same model that the relation is scoping.
Also aliased as: without
Sources:
Official docs - ActiveRecord::QueryMethods#excluding
PR - Add #excluding to ActiveRecord::Relation to exclude a record (or collection of records) from the resulting relation.
What's Cooking in Rails 7?
What you are doing is deleting the current_user from the #users Array. This won't work since there isn't a delete method for arrays. What you probably want to do is this
def index
#users = User.all
#users - [current_user]
end
This will return a copy of the #users array, but with the current_user object removed (it it was contained in the array in the first place.
Note: This may not work if array subtraction is based on exact matches of objects and not the content. But it worked with strings when I tried it. Remember to enclose current_user in [] to force it into an Array.

Resources