I need to find out the records which are at least associated to any one of the records.
I've this relationship:
Class Category < ActiveRecord::Base
has_many :services
end
Class Service < ActiveRecord::Base
has_many :team_leaders, through: :allocations
has_many :workers, through: :allocations
end
I need to find out only those services which has at least one worker or one teamleader associated to it. How to do it?
I don't know how you could do this without writing some SQL, but this is how I would have done it:
Service.includes(:team_leaders, :workers).where('team_leaders.id is not null OR workers.id is not null').references(:team_leaders, :workers).all
Edit: adding .references (see comments below)
c = Category.first
c.services.each do |service|
if Allocation.exists?(:service_id => service.id)
puts service.name
puts service.service_name
end
end
It will list only those services which has associated workers and team_leaders.
Try this query
Service.joins(:team_leaders, :workers).where('team_leaders.id is not null or workers.id is not null')
Related
I want to find the team with the most superheroes. I did some googling, but still cannot figure out what to do as I think there is a lot of SQL involved. Basically, I want the view file to display: "The team with the most superheroes is [team name] and the number of superheroes in this team is [number]." Your help would much be appreciated as I am only a beginner in Rails.
This is the best I could come up with in my view file:
<% current_user.teams.each do |team| %>
<%= team.superheroes.count%>
<% end %>
user.rb
class User < ApplicationRecord
has_many :superheroes
has_many :teams, through: :superheroes
end
superhero.rb
class Superhero < ApplicationRecord
belongs_to :user
has_many :superhero_teams
has_many :teams, through: :superhero_teams
end
team.rb
class Team < ApplicationRecord
has_many :superhero_teams
has_many :superheroes, through: :superhero_teams
end
teams = Team.left_joins(:superheroes)
.group(:id) # group by superhero id
.select(
# teams.*
Teams.arel_table[Arel.star],
# COUNT(superheroes.*) AS superheroes_count
Superhero.arel_table[Arel.star].count.as('superheroes_count')
)
.order(superheroes_count: :desc)
teams.each do |team|
puts "#{team.name} has #{team.superheroes_count} members"
end
This selects a count of the number of joined rows and orders the results by it. An alternative is to add a counter-cache to the association which makes for more effective read queries at the price of an extra UPDATE query when creating the records.
Can you try this?
Superhero.joins(:teams).group('superheros.id').order("count(superheros.id) DESC")
It will arrange the no of superhero in each team, with the largest count at top.
So the first record of the query will be the answer you are looking for.
You Can also re-check the result by looking at the counts
Superhero.joins(:teams).group('superheros.id').order("count(superheros.id) DESC").count("superheros.id")
I have models User, Group, and Membership with the following structure:
class User < ApplicationRecord
has_many :memberships
has_many :groups, through: :memberships
end
class Group < ApplicationRecord
has_many :memberships
has_many :users, through: :memberships
end
class Membership < ApplicationRecord
belongs_to :user
belongs_to :group
end
Basically, Membership is the joins table for User and Group.
Given a User user, how can I find its sibling users who all belong to at least one of the same group? That is, something like
user.groups.users # assuming such a line were possible
I'd like to do it in a single query and solely in Active Record, but I'm OK with two queries if that's faster or much more readable. (The DB language is PSQL if that helps as well.)
There are some ways to combine JOINs and sub-selects to get exactly one DB query, try this one:
User
.joins(:memberships)
.where.not(id: user.id)
.where(
memberships: {
group_id: user.memberships.select(:group_id)})
P.S. Don't forget about indexes on all *_id columns to get fast queries.
P.P.S. One more way: 2 sub-selects, 1 DB query. Test which one suits your requirements better:
user_groups_rel = user
.memberships
.select(:group_id)
groups_users_rel = Membership
.select(:user_id)
.where(group_id: user_groups_rel)
User
.where.not(id: user.id)
.where(id: groups_users_rel)
Try the following by using includes:
groups = User.groups.includes(:users)
groups.each do |group|
puts "user = #{group.user}"
end
I have a simple Customer model with a has many relationship with a Purchase model.
class Customer < ActiveRecord::Base
has_many :purchases
end
I am repeatedly finding that I need to order Customer.purchases in my views in the following way:
#customer.purchases.joins(:shop).order("shops.position").order(:position) #yes, two orders chained
In the interest of keeping things DRY, I'd like to put this somewhere centralized so I don't have to repeatedly do it. Ideally, I'd like to make it the default ordering for Customer.purchases. For example:
class Customer < ActiveRecord::Base
has_many :purchases, :order => joins(:shop).order("shops.position").order(:position)
end
Obviously the above doesn't work. How should I do this?
In your customer model you specified joins(:shop) is the value for the key :order. I think here is the problem, So you can use the joins as a key instead of order like below,
class Customer < ActiveRecord::Base
has_many :purchases, :joins => [:shop], :order => "shops.position"
end
I think it may work.
In your purchases model, you can create a class method:
Purchase.rb:
def self.order_by_position
joins(:shop).order("shops.position").order(:position)
end
Then you can say things like:
#customer.purchases.order_by_position
Purchase.order_by_position
You could create a method on Customer that returns ordered purchases:
class Customer < ActiveRecord::Base
has_many :purchases
def ordered_purchases
purchases.joins(:shop).order("shops.position").order(:position)
end
end
and call #customer.ordered_purchases from your views.
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
I have a has_many :through relationship set up like so
class Situation < ActiveRecord::Base
has_many :notifications
has_many :notiftypes, through: :notifications
end
class Notification < ActiveRecord::Base
belongs_to :situation
belongs_to :notiftype
end
class Notiftype < ActiveRecord::Base
has_many :notifications
has_many :situations, through: :notifications
end
So, a Situation has many Notifications, which can be of many types (Notiftype).
My problem is trying to query for the notiftypes that have not been set for a particular situation.
Want to find records with no associated records in Rails 3
The answers in that question get me close, but only to the point of finding Notiftypes that have not been set AT ALL.
If this were the standard :situation has_many :notiftypes I could just do a Left Outer Join like so
myquery = Notiftype.joins('LEFT OUTER JOIN situations ON situations.notiftype_id = notiftype.id').where('notiftype_id IS NULL')
but I'm really not sure how to do this with the intermediate table between them.
I have been trying consecutive joins but it's not working. I'm not sure how to join the two separated tables.
Can anyone explain the right way to query the db? I am using SQLite, Rails 3.1, Ruby 1.9.2 right now, but likely Postgresql in the future.
Try this:
class Situation < ActiveRecord::Base
# ...
has_many :notiftypes, through: :notifications do
def missing(reload=false)
#missing_notiftypes = nil if reload
#missing_notiftypes ||= proxy_owner.notiftype_ids.empty? ?
Notiftype.all :
Notiftype.where("id NOT IN (?)", proxy_owner.notiftype_ids)
end
end
end
Now to get the missing Notiftype
situation.notiftypes.missing
If you want to further optimize this to use one SQL rather than two you can do the following:
class Situation < ActiveRecord::Base
# ...
has_many :notiftypes, through: :notifications do
def missing(reload=false)
#missing_notiftypes = nil if reload
#missing_notiftypes ||= Notiftype.joins("
LEFT OUTER JOIN (#{proxy_owner.notiftypes.to_sql}) A
ON A.id = notiftypes.id").
where("A.id IS NULL")
end
end
end
You can access the missing Notifytypes as:
situation.notiftypes.missing