Rails - Pass current_user into SQL query - sql

I'm looking for a way to pass the Rails/Devise variable "current_user" into a SQL query.
I have an app with two models, Users and Tips. Users can friend other users and send tips to each other. I'm trying to display a user's friend list ordered by the number of tips that the user has sent to each friend, so that the friend to whom the user has sent the most tips shows up at the top of the friend list, and so on.
I've read that RoR isn't equipped to handle this kind of query easily, so I've been able to put together the following SQL query, which works fine:
def friend_list
#friends = User.find_by_sql("SELECT users.*,
COUNT(tips.id) AS c FROM users, tips
WHERE tips.recipient_id = users.id
AND tips.user_id = 3
GROUP BY users.id ORDER BY c DESC")
end
The only problem is, I have manually entered a user.id there ("3") when ideally that "3" would be replaced by "current_user" so that each time a user loads this list they get their friend list ranked by who they themselves have sent tips to, rather than everyone just seeing user 3's ranking.
In an ideal world this would look something like:
AND tips.user_id = current_user
but that doesn't work. How can I pass a variable into this query so that it is different for each person viewing?
UPDATE:
User model (excerpt):
has_many :tips
has_many :received_tips, :class_name => "Tip", :foreign_key => "recipient_id"
Tip model (excerpt):
belongs_to :user
belongs_to :recipient, :class_name => "User"

You cannot access current_user in models if using devise...though there are few good ways to do it
##method one---in your controller
#users=User.find(current_user.id)
##method two---in your controller
#users=User.get_user_details(current_user.id)
##in model
def self.get_user_details(current_user_id)
User.find(current_user_id)
end
###you can also use in this way in controller
#all_videos = Video.where("videos.user_id !=?",current_user.id)
.........so this can be your solution............
##in controller or pass current_user and user it in your model
def friend_list
#friends = User.joins(:tips).select("users.* and count(tips.id) as c").where ("tips.users_id= ?",current_user.id).group("users.id").order("c DESC")
##or
#friends = User.all(:joins => :tips, :select => "users.*, count(tips.id) as tips_count", :group => "users.id",:order=>"tips_count DESC")
end

You can try this
def friend_list
#friends = User.find_by_sql("SELECT users.*,
COUNT(tips.id) AS c FROM users, tips
WHERE tips.recipient_id = users.id
AND tips.user_id = ?
GROUP BY users.id ORDER BY c DESC", current_user.id)
end
Anyway it is not that hard to do this query using active_record.

I know this is old, but I want to add that you could also use string injection.
def friend_list
#Declare the query string you want to pass
#query = "SELECT users.*,
COUNT(tips.id) AS c FROM users, tips
WHERE tips.recipient_id = users.id
AND tips.user_id = #{current_user.id}
GROUP BY users.id ORDER BY DESC"
#friends = User.find_by_sql(#query)
end

Related

Query using condition within an array

I have 2 models, user and centre, which have a many to many relationship.
class User < ActiveRecord::Base
attr_accessible :name
has_and_belongs_to_many :centres
end
class Centre < ActiveRecord::Base
attr_accessible :name, :centre_id, :city_id, :state_id
has_and_belongs_to_many :users
end
Now I have an user with multiple centres, and I want to retrieve all the centres that have the same "state_id" as that user.
This is what I am doing now
state_id_array = []
user.centres.each do |centre|
state_id_array << centre.state_id
end
return Centre.where("state_id IN (?)", state_id_array).uniq
It works, but it's very ugly. Is there a better way for achieving this? Ideally a one line query.
UPDATE
Now I have
Centre.where('centres.state_id IN (?)', Centre.select('state_id').joins(:user).where('users.id=(?)', user))
The subquery work by itself, but when I tried to execute the entire query, I get NULL for the inner query.
Centre.select('state_id').joins(:user).where('users.id=(?)', user)
will generate
SELECT state_id FROM "centres" INNER JOIN "centres_users" ON "centres_users"."centre_id" = "centres"."id" INNER JOIN "users" ON "users"."id" = "centres_users"."user_id" WHERE (users.id = (5))
Which return 'SA', 'VIC', 'VIC'
but the whole query will generate
SELECT DISTINCT "centres".* FROM "centres" WHERE (centres.state_id IN (NULL,NULL,NULL))
Does user also has state_id column if yes then try this,
User.joins("LEFT OUTER JOIN users ON users.state_id = centers.state_id")
else
try User.joins(:center)
Solved.
.select(:state_id)
will retrieve a model with only the state_id column populated. To retrieve a field, use
.pluck(:state_id)
Below is the final query I had
Centre.where('centres.state_id IN (?)', Centre.joins(:user).where('users.id=(?)', user).pluck('state_id').uniq)

has_one association selecting specific field

I have user model which has_one association with user_profile
And I want to select name field from user_profile instead of user_profile.*
I have tried,
user = User.first
user.user_profile.select(:name)
But this is not working.any way?
UPDATED:
It seems, that rails handles the other direction and one-to-one connections differently. You have two options:
1) Define the selected attributes in the association, it will always select those
has_one :user_profile, :select => [:name, :id]
2) Define a specific find in your model, where you can add select, like this:
def my_profile
UserProfile.find(self.user_profile_id)
end
....
my_profile.select(:name)
ORIGINAL:
In case of has_many direction, it works:
I've tried your method in my code, like:
= User.find("admin").article.select(:title).to_sql
It returns the correct sql:
SELECT title
FROM "articles"
WHERE "articles"."user_id" = 1364

Writing a named scope in rails

I have three models: Products, Placements, Collections
I'm trying to write a name scope that only chooses products NOT in a certain collection.
products has_many :collections, :through => :placements
collections has_many :products, :through => :placements
I got about this far:
scope :not_in_front, joins(:collections).where('collections.id IS NOT ?', 4)
But that generated the opposite of what I expected in the query:
Product Load (0.3ms) SELECT "products".* FROM "products" INNER JOIN "placements" ON "products"."id" = "placements"."product_id" WHERE "placements"."collection_id" = 4
Any idea how to write this to only select the products not in that particular collection?
Instead of collections.id IS NOT 4 try collections.id != 4
The named scope was getting too ugly, so I went with this. Not sure it's the best way, but, it works...
def self.not_on_top_shelf
top_shelf = Collection.find_by_handle('top-shelf')
products = Product.find(:all, :order => "factor_score DESC")
not_on_top_shelf = products.map {|p| p unless p.collections.include?(top_shelf)}
not_on_top_shelf.compact #some products may not be in a collection
end

Need help finding the first created record of each group, in Rails ActiveRecord

I have a table called FeedItems, and basically the user needs to see the first created feed item for each post id.
At the moment I'm using 'group', and SQLite is giving me the last created, for some reason. I've tried to sort the list of feed items before grouping, but it makes no difference.
user_id | post_id | action
----------------------------------------
1 1 'posted'
3 2 'loved' <--- this, being created afterwards
should not appear in the query.
I know this has to do with an INNER JOIN, and I have seen some similar examples of this being done, however the difference is that I'm not sure how to do this AND use the existing query I already have to find out if the users are friends with the current user.
Here's the code for the model:
class FeedItem < ActiveRecord::Base
belongs_to :post
belongs_to :user
default_scope :order => 'created_at desc'
scope :feed_for, lambda { |user| feed_items_for(user).group(:post_id) }
private
def self.feed_items_for(user)
friend_ids = %(SELECT friend_id FROM friendships WHERE (user_id = :user_id OR friend_id = :user_id))
ghosted_ids = %(SELECT pending_friend_id FROM friend_requests WHERE user_id = :user_id)
where("user_id IN (#{friend_ids}) OR user_id IN (#{ghosted_ids}) OR user_id = :user_id", {:user_id => user.id})
end
end
Any help would be greatly appreciated, thanks.
ActiveRecord provides you with dynamic attribute-based finders, which means that you have
Feeds.find_last_by_post_id(117)
If you would prefer to have the fist, you have:
Feeds.where(:post_id => 117).first
And you can always do, which I'm not sure is a "best practice":
Feeds.where(:post_id => 117).order('created_on DESC').first
You can read more about it at:
http://api.rubyonrails.org/classes/ActiveRecord/Base.html

named_scope or find_by_sql?

I have three models:
User
Award
Trophy
The associations are:
User has many awards
Trophy has many awards
Award belongs to user
Award belongs to trophy
User has many trophies through awards
Therefore, user_id is a fk in awards, and trophy_id is a fk in awards.
In the Trophy model, which is an STI model, there's a trophy_type column. I want to return a list of users who have been awarded a specific trophy -- (trophy_type = 'GoldTrophy'). Users can be awarded the same trophy more than once. (I don't want distinct results.)
Can I use a named_scope? How about chaining them? Or do I need to use find_by_sql? Either way, how would I code it?
If you want to go down the named_scope route, you can do the following:
Add a has_many :users to Trophy, such as:
has_many :users, :through => :awards
And the following named_scope:
named_scope :gold, :conditions => { :trophy_type => 'GoldTrophy' }
You can call the following:
Trophy.gold.first.users
You need to call '.first' because the named_scope will return a collection. Not ideal. That said, in your case it's probably perfectly appropriate to use neither find_by_sql or named_scope. How about using good old:
Trophy.find_by_trophy_type('GoldTrophy').users
This will do exactly what you want without having to dig down into the SQL.
I am always comfortable with the "find_by_sql" You can use it
Using find_by_sql as follows
User.find_by_sql("select u.id, u.name, t.trophy_type
from users u, awards a, trophies t
where a.user_id=u.id and
t.trophy_id=a.id and
t.trophy_type = 'GoldTrophy'"
)
I am not sure using "named_scope" But try this
class User < ActiveRecord::Base
named_scope :gold_trophy_holder,
:select=>" users.id, users.name, trophies.trophy_type",
:joins => :awards, "LEFT JOIN awards ON awards.id = trophies.award_id"
:conditions => ['trophies.trophy_type = ?', 'GoldTrophy']
end