ActiveRecord Query Count Number of Relations - sql

This is a Ruby 1.9.3/Rails 3.2 project.
Let's say I have a model called Role, and a model called Employee, linked through a has_many/belongs_to relationship. A role has many employees, and an employee belongs to a role. Both of these models belong to a Store object that has many employees and roles.
Each role has a target_headcount attribute, representing the ideal number of employees in that position. From there, I have methods like the following for Role:
class Role < ActiveRecord::Base
# ...
# Number of employees currently filling this role.
def current_headcount
employees.count
end
# Number of headcount above or below the target.
def variance
current_headcount - target_headcount
end
end
Frequently, I need to get a collection of each role for which there is open headcount. I was doing this using the following class method for Role:
def self.open_headcount
all.select { |r| r.variance < 0 }
end
However, I'm now using meta_search, a RubyGem which requires an ActiveRecord::Relation object. I'd like to change open_headcount from a class method to a scope so that it returns an ActiveRecord::Relation object, but I'm not sure if it is possible.

This is super old, but FTR I probably could have done it with by using a SQL query to count the number of employees for the role and subtracting from it the value of the target column.

Related

RAILS: How to query if there's already a team formed by an array of users

I'm working on an rails API, more specifically in a create operation.
The workflow that I have is this, I have two rails applications, one is an API and the other is an interface. The API manages the different backend operations in order to handle data, store data, and respond in json format to the interface. The interface serves as the frontend, just making http requests to the API in order to display the information.
In my API I have the 3 model listed below:
class Team < ApplicationRecord
has_many :team_users
has_many :users, through: :team_users
end
class User <ApplicationRecord
has_many :team_users
has_many :teams, through: :team_users
end
class TeamUser < ApplicationRecord
belongs_to :user
belongs_to :team
end
Basically I'm sending an array of user ids from the Interface to the API and I would like to make a query to find out if there's already a team formed by the users (user ids) that I've passed to the API.
I have already tried to do this:
Team.joins(:team_users).where('team_users.user_id' => [3,5])
The problem with this query is that it returns every team that contains the users with id that are equal to 3 or 5. The correct result would be to return a team that has the users 3 and 5 or 5 and 3 as their team members.
Thanks in advance.
Update
The original business rule is this, have an aplication that keeps track of trees, so I have a model named tree and when we create a tree whe must say what team created this tree. So I used a multi select user dropdown field with select2 js library that is how I'm passing the user_ids to the API. So the basic idea is to check is theres already a team composed only by the users passed to the API, if there is already a team I use it's id and say that the tree was registered by that team, if there insn't a team with coposed only buy the users I create a new team and reference it's Id to the tree.
You can approach the problem in different ways. Scrolling over each Team record to check if it contains the associated user_ids is pretty straightforward but inefficient:
team.user_ids.sort == user_ids.sort
But we can make it performant by reversing the process, i.e. iterating over the user_ids to find out corresponding teams, taking their intersection of Team ids and finally checking if any team_id holds those user_ids. This line will return true if there's already a team formed by the users (user ids):
user_ids.map { |id| User.find(id).team_ids }.reduce(:&).present?
You may include it in the User class scope like below:
class User < ApplicationRecord
...
def self.team_exists_for?(user_ids)
# return early
# what should it return when user_ids is empty
# return ??? if user_ids.blank?
# what should it return when any of the id is absent from :users table
# set_of_all_user_ids = Set.new(User.pluck(:id))
# set_of_user_ids = Set.new(user_ids)
# return ??? unless set_of_user_ids.subset? set_of_all_user_ids
# finally
user_ids.map { |id| User.find(id).team_ids }.reduce(:&).present?
end
...
end
Update
So you want to find the team which only has those users provided by user_ids or create a team with them and assign back to the Tree model instance. Combining both approaches described above and defining a scope in the Team model itself seems like a better solution.
class Team < ApplicationRecord
...
def self.find_or_create_for(user_ids)
# find all team_ids which contain user_ids (inclusive)
team_ids = user_ids.map { |id| User.find(id).team_ids }.reduce(:&).flatten
if team_ids.present? # search for the team which ONLY has 'user_ids'
team_id = team_ids.find { |id| Team.find(id).user_ids.sort == user_ids.sort }
end
return Team.find(team_id) if team_id
# or create a team with user_ids and return
team = Team.create! # create a team with required attributes
team.user_ids = user_ids
team
end
...
end
i have implemented this as
add a field key: string on Team and in Team model
class Team < ApplicationRecord
has_many :team_users
has_many :users, through: :team_users
#callback
before_validation :update_key
def update_key
self.key = Team.key_for_users(self.users)
end
def self.key_for_users(users)
users.sort.map(&:id).join("_")
end
end
so basically after this callback whenever you will create a team there will be a key
for example: -
users = [3,5]
then key in Team will be 3_5
or users = [5,3]
then key in Team will be 5_3
From this we can easily get the result what you wanted
example: -
user_ids = [3,5]
[14] pry(main)> user_ids_simple = user_ids.join('_')
=> "3_5"
[15] pry(main)> user_ids_reverse = user_ids.reverse.join('_')
=> "5_3"
and query will be like this: -
Team.where("key IN (?)",[user_ids_simple, user_ids_reverse])
it may be helpful for you. thanks

Get Users with an attribute for each Group?

My models: User, Group, and Membership. Users can have many groups through memberships, and vice-versa. I want to create a tool for website admins that produces a large table with the following specification:
Every row represents a user,
Every column represents a group,
In each cell of the table there is a boolean indicating whether the user belongs to the group.
What would be the best way to do this? Is it possible to write a single SQL query that achieves it (i.e. User.find_by_sql)? If not, how else?
p.s. I actually need a bit more than this (I need two columns per group, the first one indicating membership, and the second one counting how many meetings the user has attended in that group, but this involves the Meeting model, so I'll leave that for later.
Assuming that you're asking about the backend methodology not the data visualization aspect most of what JuanM. said is correct. One thing I would recommend is avoid writing his 'get_groups' method and just set up a 'has many through' relationship between users in groups. To do so put
class User < ActiveRecord::Base
has_many :memberships
has_many :groups, through: :memberships
end
In your Users model and vice versa in your Groups model (assuming memberships 'belongs_to' both). Then you'll have a '.groups' method on any User instance and a '.users' method on any Group instance
This would be my approach:
Write a function that returns if the user belongs to a group passed by parameter. Get all the groups from a user. In your user.rb model you can add this method get_groups to retrieve all groups from the user and then a method is_in(group). See code below:
class User < ActiveRecord::Base
#validations, some other stuff
has_many :memberships
#this method stores all the group-ids of the user in an array
def get_groups
myGroups = []
memberships.each do |membership|
membership.groups.each do |group|
myGroups << group.id
end
end
return myGroups
end
#this method receive a Group object and return true if the user belongs to that group
def is_in(group)
groups = get_groups
return groups.include?(group.id)
end
Then in your view or controller you can work as follow:
#the columns of the table
groups = Group.all
#iterating all users
User.all.each do |user|
#each user has to see if it belongs to each group in the groups table
groups.each do |group|
#the boolean value you display in a cell
value = user.is_in(group)
end
end

Rails: select model

I have a model Shop:
class Shop < ActiveRecord::Base
has_and_belongs_to_many :services
end
and a model Service:
class Service < ActiveRecord::Base
has_and_belongs_to_many :shops
end
I would like to query every shop that provides all of the following services:
Reparation
Advise
Shipping
So return all shops where services contain reparation AND advise AND shipping.
Is this possible with ActiveRecord querying only?
Thanks!
See MySQL: Select records where joined table matches ALL values for how to do it in sql.
For a simpler method, which makes a series of simple queries instead of a single complex query, you could do this:
#starting with the services in an array called #services
#(which could come from params[:service_ids] for example)
#find shops which have ALL these services.
shop_ids = #services.map(&:shop_ids).inject{|a,b| a & b}
#shops = Shop.find(shop_ids)
Key to this is .inject{|a,b| a & b} : inject is an array method which performs a function between the first and second elements (a and b) then uses the result of that with the block again, with the third element etc, working it's way through the array. The & operator is array intersect, so you will end up only with the shop_ids which are returned for ALL services.

Returning multiple models with includes in Active Record

I have two models, Invitation and RSVP. An invitation has many rsvps and rsvp belongs to an invitation. I want to run a query that will return all invitations and the rsvps that belong to each invitation. I want to have all the attributes that belong to both invitation and rsvp. I'm aware of includes and have been trying things like
#results = RSVP.where(user_id: 3).includes(:invitation)
but I'm only getting the attributes of RSVP returned. I ideally want to have the attributes of the Invitation that RSVP belongs to added to the results. What concept am I missing or should I think of this a different way?
Let us assume that the Invitation model has two fields event_name and event_date that you want to access in your query results. You can customize the select list if provide a joins clause.
RSVP.select("rsvps.*, invitations.event_name invitation_event_name,
invitations.event_date invitation_event_date"
).where(user_id: 3).joins(:invitation).each do |rsvp|
puts rsvp.invitation_event_name, rsvp.invitation_event_date
end
RSVP.where(...) with or without the includes(...) is going to return a collection of RSVP objects. By including the :invitation association each RSVP has, you're eager-loading the :invitation for each RSVP in the collection all at once. This prevents a separate SELECT * FROM invitations WHERE ... query from being run for every RSVP in the collection when you refer to it's :invitation association.
.includes is nothing but a query optimization if you plan on using an association for objects within a collection. It does not merge attributes from the association into the model instances in the result set.
If you want to have an attribute from the associated Invitation included on the RSVP instances, you can use Rails delegate method. You can read about it here.
On your RSVP model you'd do something like this, listing out the desired attributes from Invitation in place of the placeholders I've left below.
class RSVP < ActiveRecord::Base
has_one :invitation
delegate :some_invitation_attribute, :another_invitation_attribute, to: :invitation
Now you can call :some_invitation_attribute and :another_invitation_attribute directly on an RSVP instance.
#results = RSVP.where(user_id: 3).includes(:invitation)
puts #results.first.some_invitation_attribute # delegates the .some_invitation_attribute method call to the associated Invitation

Determine if children added/deleted in Rails Callback

Scenario:
I have a habtm relationship and would like to determine if children in one direction have been added or deleted. I'm trying to use callbacks but find i don't have a record of changes to the children. Is there something like course.students.changed?
Using:
Rails 3.0.3
Ruby 1.9.2p0
Tables:
students - id, first_name, last_name
courses - id, name, location
courses_students - course_id, student_id
Models:
class Course
# Callbacks
before_save :student_maintenance
# Relationships
has_and_belongs_to_many :students
protected
def student_maintenance
# I want to do something like
students.changed?
students.changes.each do |student|
if marked_for_deletion
# do something
elsif marked_for_addition
# do something else
end
end
end
end
end
class Student
# Relationships
has_and_belongs_to_many :courses
end
If you want to capture when a student has been added or removed from a course, why not use a class for the join table *courses_students*? Since that's the place where an entry will be actually created or destroyed you could easily use after_create/after_destroy callbacks in there.
Similar to polarblau's answer -- but a tad more explanation, I hope:
In textual, UML terms, the relationship might be this:
Course ------*-> Student
Read: A Course has 0 to Many Students
You can add and remove students from the association at will.
However, you are interested in knowing more about the association, that is, when the student was added or dropped from the course. This "extra" information about an association leads to discovering you need another class, a.k.a. "Association Class."
So now you would insert that class in between your existing classes as so, taking the liberty to call it a "Registration":
Course ------*->Registration------1->Student
Read: A Course has 0 to Many Registration. Each Registration must have exactly 1 Student.
The Registration class might look like this:
+--------------+
| Registration |
+--------------|
| student |
| time_added |
| time_dropped |
+--------------+
So you can easily get a list of current students for a course (where time_dropped is nil). Or a list of dropped students (where time_dropped is not nil).