My models are:
class Link < ActiveRecord::Base
has_many :votes
belongs_to :user
end
class Vote < ActiveRecord::Base
belongs_to :user
belongs_to :link
end
class User < ActiveRecord::Base
has_secure_password
has_many :links
has_many :votes
end
I have a page where I list all the links in the system. For each link I want to display both the current sum of all the votes as well as whether or not the user making the request has voted on that particular link (and if so what that vote's value was). I want to do this in the most efficient manner.
Currently in order to return the links and sums of the votes I have this in my Link controller:
def index
#links = Link.all(:joins => :votes, :select => "links.*, sum(votes.value) as votes_total", :group => "links.id")
end
Which works well and gives me all my information in a single call. So my question is, do I need to make a second query to return the vote (if it exists) for current_user or can I incorporate that into my first query somehow?
Additionally, perhaps I should set up some kind of sum_caching in the database but I'm not sure the best way to go about that either. Any thoughts?
You could do something like this:
#links = Link.
joins(:votes).
select(
"links.*, sum(votes.value) as votes_total, sum(case votes.user_id when #{current_user.id} then votes.value else 0 end) as current_vote",
:group => "links.id"
)
That would give you the current user's vote value as #links.first.current_vote. Of course, if your system has votes with a value of zero, it doesn't distinguish between "current user voted 0 on this link" and "current user did not vote on this link". You could add yet another selected value (sum(case votes.user_id when #{current_user.id} then 1 else 0 end) as current_vote_exists) to handle that, I suppose.
Naturally, you want to be really careful when interpolating into SQL like this, and/or look up the SQL-sanitizing tools that come with ActiveRecord.
Related
In my method I want to check if user has only one shareholder, if it so it should return true (later on it's used in if block). Basically it should reflect something like User.find_by(id: user.id).shareholder.count < 1 because it overloads the database with unnecessary queries (I have db with ~30k users).
What I was thinking is to create queries with where so I have this one:
def one_shareholder?(shareholder)
ShareholdersUser.where(user_id: shareholder.owner_id).
where.not(shareholder_id: shareholder.id).exists?
end
But I don't know how to implement query which will be counting if this user has only one shareholder. Should I use something like find_each ?
Edit:
user has_many :shareholder
shareholder belongs_to :owner
ShareholdersUser belongs_to :user and :shareholder
Maybe this can give you an hint. I used something similar in a project where I have these models:
class Company < ApplicationRecord
has_many :permits
end
and
class Permit < ApplicationRecord
belongs_to :company
end
For fetching companies having just one permit I used:
Company.joins(:permits).group('companies.id').having('count(company_id) = 1')
Maybe you can pluck the ids and use the array to check wether the company is in the array. For example:
ids_of_companies_having_one_permit = Company.joins(:permits).group('companies.id').having('count(company_id) = 1').pluck(:id)
Then check:
if ids_of_companies_having_one_permit.include? company.id ....
This is the thread I followed: Find all records which have a count of an association greater than zero
Firstly, if you are finding user from his ID then can directly use User.find(user.id) instead of User.find_by(id: user.id)
Secondly, As you mentioned in your question that you want either true/false for your if condition.
And as per your query I think you have user has_many shareholder association implemented.
So you can directly use User.find(user.id).shareholders < 1 in your if condition like below,
if User.find(user.id).shareholders.count < 1
#do something...
end
Note: I've used the plural of the shareholder in condition because we have has_many association
Each User can have many Resources, and each of those Resources has many Votes, and each of those votes have a value attribute that I want to sum all that particular users resources.
If I were to type this in a syntactically incorrect way I want something like...
#user.resources.votes.sum(&:value), but that obviously won't work.
I believe I need to use collect but I am not sure?
This is the closest I got but it prints them out, heh
<%= #user.resources.collect { |r| r.votes.sum(&:value) } %>
I'd recommend setting up a has_many :through relationship between the User and Vote objects. Set the models up like this:
class User < ActiveRecord::Base
has_many :resources
has_many :votes, :through => :resources
end
class Resource < ActiveRecord::Base
belongs_to :user
has_many :votes
end
class Vote < ActiveRecord::Base
belongs_to :resource
end
Once this is done you can simply call user.votes and do whatever you want with that collection.
For more info on has_many :through relations, see this guide: http://guides.rubyonrails.org/association_basics.html#the-has_many-through-association
How can you tell who voted having a Vote instance? Your Vote model has to have voter_id field and additional association:
# in Vote.rb
belongs_to :voter, class_name: 'User', foreign_key: 'voter_id'
And in your User model:
# in User.rb
has_may :submited_votes, class_name: 'Vote', foreign_key: 'voter_id'
So, #user.votes (as David Underwood proposed) will give you #user resources' votes. And #user.submited_votes will give you votes submitted by the #user.
Using just User <- Resource <- Vote relation won't allow you to separate some user's votes made by him and votes made for its resources.
For a total sum this should work or something real close.
sum = 0
#user.resources.each do |r|
r.votes.each do |v|
sum += v.value
end
end
This might work for you:
#user.resources.map {|r| r.votes.sum(:value)}.sum
How many records do you have, there is a way to push this to the database level I believe, I would have to check, but if it is only a few records then doing this in ruby would probably be ok
Try this code
#user.resources.map(&:votes).flatten.map(&:value).sum
In my Rails models I have:
class Song < ActiveRecord::Base
has_many :flags
has_many :accounts, :through => :flags
end
class Account < ActiveRecord::Base
has_many :flags
has_many :songs, :through => :flags
end
class Flag < ActiveRecord::Base
belongs_to :song
belongs_to :account
end
I'm looking for a way to create a scope in the Song model that fetches songs that DO NOT have a given account associated with it.
I've tried:
Song.joins(:accounts).where('account_id != ?', #an_account)
but it returns an empty set. This might be because there are songs that have no accounts attached to it? I'm not sure, but really struggling with this one.
Update
The result set I'm looking for includes songs that do not have a given account associated with it. This includes songs that have no flags.
Thanks for looking.
Am I understanding your question correctly - you want Songs that are not associated with a particular account?
Try:
Song.joins(:accounts).where(Account.arel_table[:id].not_eq(#an_account.id))
Answer revised: (in response to clarification in the comments)
You probably want SQL conditions like this:
Song.all(:conditions =>
["songs.id NOT IN (SELECT f.song_id FROM flags f WHERE f.account_id = ?)", #an_account.id]
)
Or in ARel, you could get the same SQL generated like this:
songs = Song.arel_table
flags = Flag.arel_table
Song.where(songs[:id].not_in(
flags.project(:song_id).where(flags[:account_id].eq(#an_account.id))
))
I generally prefer ARel, and I prefer it in this case too.
If your where clause is not a typo, it is incorrect. Code frequently uses == for equality, but sql does not, use a single equals sign as such:
Song.joins(:accounts).where('account_id = ?', #an_account.id)
Edit:
Actually there is a way to use activerecord to do this for you, instead of writing your own bound sql fragments:
Song.joins(:accounts).where(:accounts => {:id => #an_account.id})
I have the following models: Users, Groups, Conversations, ConversationParticipants( has a read boolean)
What I want to do is get an unread Count for a particular user in a particular group.
Should I be using a named_scope for this? If so, which model would this belong in (not sure)...
Also, I can do: #user.conversation_participations which then has the read field, problem is it does not have the group field as conversation_participations links to conversations (which has the group_id) via a conversation_id key.
Thoughts?
Thanks
You didn't show the code for the models, so I made some assumptions. Here's one way:
class Conversation < ActiveRecord::Base
belongs_to :group
has_many :conversation_participants
has_many :participants, :through => :conversation_participants,\
:source => :user
scope :unread, lambda { |user,group| includes(:group,:conversation_participants).\
where(:group_id => group.id,\
:conversation_participants => {:read => false,:user_id => user.id})
}
end
You're asking for "unread conversations belonging to a specific user and group". Since the thing being asked for is a set of Conversations, that's a natural place to put the scope.
EDIT
I see you wanted the count, not the result set. Just add .count to the scope:
Conversation.unread(user,group).count
EDIT 2
is it possible to do something like
this instead to get the #,
current_user.unread(group).count ..?
Add an instance method on User:
def unread(group)
Conversation.unread(self,group)
end
Now you can call current_user.unread(group).count
If I understand the question correctly. I would use a named_scope in the ConversationParticipants called something like in_group:
scope :in_group, lambda do
|group| joins(:conversation).where('conversation.group_id = ?', group.id)
end
I'm assuming the ConversationParticipants has belongs_to :conversation.
Now you can do:
#user.conversation_participations.in_group( some_group )
Using ActiveRecord, I have an object, Client, that zero or more Users (i.e. via a has_many association). Client also has a 'primary_contact' attribute that can be manually set, but always has to point to one of the associated users. I.e. primary_contact can only be blank if there are no associated users.
What's the best way to implement Client such that:
a) The first time a user is added to a client, primary_contact is set to point to that user?
b) The primary_contact is always guaranteed to be in the users association, unless all of the users are deleted? (This has two parts: when setting a new primary_contact or removing a user from the association)
In other words, how can I designate and reassign the title of "primary contact" to one of a given client's users? I've tinkered around with numerous filters and validations, but I just can't get it right. Any help would be appreciated.
UPDATE: Though I'm sure there are a myriad of solutions, I ended up having User inform Client when it is being deleted and then using a before_save call in Client to validate (and set, if necessary) its primary_contact. This call is triggered by User just before it is deleted. This doesn't catch all of the edge cases when updating associations, but it's good enough for what I need.
My solution is to do everything in the join model. I think this works correctly on the client transitions to or from zero associations, always guaranteeing a primary contact is designated if there is any existing association. I'd be interested to hear anyone's feedback.
I'm new here, so cannot comment on François below. I can only edit my own entry. His solution presumes user to client is one to many, whereas my solution presumes many to many. I was thinking the user model represented an "agent" or "rep" perhaps, and would surely manage multiple clients. The question is ambiguous in this regard.
class User < ActiveRecord::Base
has_many :user_clients, :dependent => true
has_many :clients, :through => :user_client
end
class UserClient < ActiveRecord::Base
belongs_to :user
belongs_to :client
# user_client join table contains :primary column
after_create :init_primary
before_destroy :preserve_primary
def init_primary
# first association for a client is always primary
if self.client.user_clients.length == 1
self.primary = true
self.save
end
end
def preserve_primary
if self.primary
#unless this is the last association, make soemone else primary
unless self.client.user_clients.length == 1
# there's gotta be a more concise way...
if self.client.user_clients[0].equal? self
self.client.user_clients[1].primary = true
else
self.client.user_clients[0].primary = true
end
end
end
end
end
class Client < ActiveRecord::Base
has_many :user_clients, :dependent => true
has_many :users, :through => :user_client
end
Though I'm sure there are a myriad of solutions, I ended up having User inform Client when it is being deleted and then using a before_save call in Client to validate (and set, if necessary) its primary_contact. This call is triggered by User just before it is deleted. This doesn't catch all of the edge cases when updating associations, but it's good enough for what I need.
I would do this using a boolean attribute on users. #has_one can be used to find the first model that has this boolean set to true.
class Client < AR::B
has_many :users, :dependent => :destroy
has_one :primary_contact, :class_name => "User",
:conditions => {:primary_contact => true},
:dependent => :destroy
end
class User < AR::B
belongs_to :client
after_save :ensure_only_primary
before_create :ensure_at_least_one_primary
after_destroy :select_another_primary
private
# We always want one primary contact, so find another one when I'm being
# deleted
def select_another_primary
return unless primary_contact?
u = self.client.users.first
u.update_attribute(:primary_contact, true) if u
end
def ensure_at_least_one_primary
return if self.client.users.count(:primary_contact).nonzero?
self.primary_contact = true
end
# We want only 1 primary contact, so if I am the primary contact, all other
# ones have to be secondary
def ensure_only_primary
return unless primary_contact?
self.client.users.update_all(["primary_contact = ?", false], ["id <> ?", self.id])
end
end