rails how to find with no associated records - ruby-on-rails

I know that this will be an easy one but I'm having real issues working it out.
I have users that can have_many results. I'm trying to work out how to return users that don't yet have any results to the #starters object(from the controller).
#users = #event.entires
#starters = #users.where("results = ?", 0)
could anyone explain how i would check if a user has no results?

Best solution (as MrYoshiji commented)
#starters = #users.includes(:results).where(results: { id: nil })
This will execute the same query as the one in my second solution.
Other SQL solution
You could use a LEFT OUTER JOIN. This way, you will always have all the results from the "left" table (users) but you will also have matching records for the "right" table (results) eventhough there are non, which will leave you with empty fields that you can check.
#starters = #users.joins("LEFT OUTER JOIN results ON results.user_id = users.id").where("results.user_id IS NULL")
In your case, replace users with the name of your "user" model.
Other Ruby solution
#starters = #users.select { |u| !!u.results }
Here !! will force conversion to a boolean, if there are no results, u.results will return [] (empty array). And !![] equals true.

Try left joining and finding which one is null
#users.joins("LEFT OUTER JOIN user ON user.id = result.user_id").where("result.id IS NULL")

If your #users is an Array, try:
#starters = #users.select { |u| u.results.empty? }

This should get all of the users that do not have any results:
#starters = ActiveRecord::Base.execute("select * from users where id not in (select user_id from results)")
Another way to do this would be the following:
User.where("id not in ?", "(select user_id from results)")

Related

Create a WHERE (columns) IN (values) clause with Arel?

Is there a way to programatically create a where clause in Arel where the columns and values are specified separately?
SELECT users.*
WHERE (country, occupation) IN (('dk', 'nurse'), ('ch', 'doctor'), ...
Say the input is a really long list of pairs that we want to match.
I'm am NOT asking how to generate a WHERE AND OR clause which is really simple to do with ActiveRecord.
So far I just have basic string manipulation:
columns = [:country, :occupation]
pairs = [['dk', 'nurse'], ['ch', 'doctor']]
User.where(
"(#{columns.join(', ')}) IN (#{ pairs.map { '(?, ?)' }.join(', ')})",
*pairs
)
Its not just about the length of the query WHERE (columns) IN (values) will also perform much better on Postgres (and others as well) as it can use an index only scan where OR will cause a bitmap scan.
I'm only looking for answers that can demonstrate generating a WHERE (columns) IN (values) query with Arel. Not anything else.
All the articles I have read about Arel start building of a single column:
arel_table[:foo].eq...
And I have not been able to find any documentation or articles that cover this case.
The trick to this is to build the groupings correctly and then pass them through to the Arel In Node, for example:
columns = [:country, :occupation]
pairs = [['dk', 'nurse'], ['ch', 'doctor']]
User.where(
Arel::Nodes::In.new(
Arel::Nodes::Grouping.new( columns.map { |column| User.arel_table[column] } ),
pairs.map { |pair| Arel::Nodes::Grouping.new(
pair.map { |value| Arel::Nodes.build_quoted(value) }
)}
)
)
The above will generate the following SQL statement (for MySQL):
"SELECT users.* FROM users WHERE (users.country,
users.occupation) IN (('dk', 'nurse'), ('ch', 'doctor'))"
This will still generate long query with 'OR' in between. But I felt this is lil elegant/different approach to achieve what you want.
ut = User.arel_table
columns = [:country, :occupation]
pairs = [['dk', 'nurse'], ['ch', 'doctor']]
where_condition = pairs.map do |pair|
"(#{ut[columns[0]].eq(pair[0]).and(ut[columns[1]].eq(pair[1])).to_sql})"
end.join(' OR ')
User.where(where_condition)
I have tried this different approach at my end. Hope it will work for you.
class User < ActiveRecord::Base
COLUMNS = %i(
country
occupation
)
PAIRS = [['dk', 'nurse'], ['ch', 'doctor']]
scope :with_country_occupation, -> (pairs = PAIRS, columns = COLUMNS) { where(filter_country_occupation(pairs, columns)) }
def self.filter_country_occupation(pairs, columns)
pairs.each_with_index.reduce(nil) do |query, (pair, index)|
column_check = arel_table[columns[0]].eq(pair[0]).and(arel_table[columns[1]].eq(pair[1]))
if query.nil?
column_check
else
query.or(column_check)
end
end.to_sql
end
end
Call this scope User.with_country_occupation let me know if it works for you.
Thanks!
I think we can do this with Array Conditions as mentioned here
# notice the lack of an array as the last argument
Model.where("attribute = ? OR attribute2 = ?", value, value)
Also, as mentioned here we can use an SQL in statement:
Model.where('id IN (?)', [array of values])
Or simply, as kdeisz pointed out (Using Arel to create the SQL query):
Model.where(id: [array of values])
I have not tried myself, but you can try exploring with these examples.
Always happy to help!

How to get a unique set of parent models after querying on child

Order has_many Items is the relationship.
So let's say I have something like the following 2 orders with items in the database:
Order1 {email: alpha#example.com, items_attributes:
[{name: "apple"},
{name: "peach"}]
}
Order2 {email: beta#example.com, items_attributes:
[{name: "apple"},
{name: "apple"}]
}
I'm running queries for Order based on child attributes. So let's say I want the emails of all the orders where they have an Item that's an apple. If I set up the query as so:
orders = Order.joins(:items).where(items: {name:"apple"})
Then the result, because it's pulling at the Item level, will be such that:
orders.count = 3
orders.pluck(:email) = ["alpha#exmaple.com", "beta#example.com", "beta#example.com"]
But my desired outcome is actually to know what unique orders there are (I don't care that beta#example.com has 2 apples, only that they have at least 1), so something like:
orders.count = 2
orders.pluck(:email) = ["alpha#exmaple.com", "beta#example.com"]
How do I do this?
If I do orders.select(:id).distinct, this will fix the problem such that orders.count == 2, BUT this distorts the result (no longer creates AR objects), so that I can't iterate over it. So the below is fine
deduped_orders = orders.select(:id).distinct
deduped_orders.count = 2
deduped_orders.pluck(:email) = ["alpha#exmaple.com", "beta#example.com"]
But then the below does NOT work:
deduped_orders.each do |o|
puts o.email # ActiveModel::MissingAttributeError: missing attribute: email
end
Like I basically want the output of orders, but in a unique way.
I find using subqueries instead of joins a bit cleaner for this sort of thing:
Order.where(id: Item.select(:order_id).where(name: 'apple'))
that ends up with this (more or less) SQL:
select *
from orders
where id in (
select order_id
from items
where name = 'apple'
)
and the in (...) will clear up duplicates for you. Using a subquery also clearly expresses what you want to do–you want the orders that have an item named 'apple'–and the query says exactly that.
use .uniq instead of .distinct
deduped_orders = orders.select(:id).uniq
deduped_orders.count = 2
deduped_orders.pluck(:email) = ["alpha#exmaple.com", "beta#example.com"]
If you want to keep all the attributes of orders use group
deduped_orders = orders.group(:id).distinct
deduped_orders.each do |o|
puts o.email
end
#=> output: "alpha#exmaple.com", "beta#example.com"
I think you just need to remove select(:id)
orders = Order.joins(:items).where(items: {name:"apple"}).distinct
orders.pluck(:email)
# => ["alpha#exmaple.com", "beta#example.com"]
orders = deduped_orders
deduped_orders.each do |o|
puts o.email # loop twice
end

Rails retuning an array of ids with multiple conditions

In my controller i'm looking to return an array with 2 conditions attached.
parent_ids = StudentGuardian.where(:guardian_id => current_user.school_user.id).pluck(:student_id)
classmodule_ids = SubjectStudent.pluck (:class_id)
#homework = Homework.where("subject in ?", classmodule_ids)
So in this case I need to find a student id from a table and then I need to get a class_id from another table.
Then I am trying to display results.
Can I get both in to the one query?
I also tried #homework = Homework.where("subject in ?", parent_ids, classmodule_ids)Of course this does not work!
You just want to know if subject is in parent_ids or classmodule_ids?
So can you just merge parent_ids and classmodule_ids into one array:
# you could merge these a bunch of different ways, here's one:
search_ids = (parent_ids + classmodule_ids).uniq
# don't forget the () around ? below
#homework = Homework.where("subject in (?)", search_ids)
Or if for some reason you didn't want to combine parent_ids and classmodule_ids:
#homework = Homework.where("subject in (?) OR subject in (?)", parent_ids, classmodule_ids)
But all of that would mean subject is an id also... Is that the case?

Rails - Find with no associated records

I want to select one user and add to it associated records such as child for this example.
But only the child with a specific place_id.
This code works, but when the user doesn't have any child entry I got an error.
#user = User.includes(:child).find(params[:id],
:conditions => ["child.place_id = ?", #place_id])
Here is the error:
Couldn't find User with id=19 [WHERE (child.place_id = 0)]
Thanks !
Try where clause, since you are already using brute SQL. This will not produce error and will either fetch or set to nil:
#user = User.includes(:child).
where("users.id=? AND child.place_id = ?",
params[:id],#place_id).first
PS: Is it child.place_id or children.place_id? ActiveRecord tends to pluralize table names.
EDIT:
This only works if there are children. If you want it to work event without children,do this:
#user = User.joins('LEFT JOIN child on child.user_id = users.id').
where('child.place_id = ? AND users.id = ?', #place_id, params[:id]).
select('users.field1, child.field2 as field3')
If you want specific fields, add them in select method above, which is provided as an example.

Condition true for ALL records in join

I'm trying to return records from A where all matching records from B satisfy a condition. At the moment my query returns records from A where there is any record from B that satisfies the condition. Let me put this into a real world scenario.
Post.joins(:categories)
.where(:categories => { :type => "foo" })
This will return Posts that have a category of type "foo", what I want is Posts whose categories are ALL of type "foo"!
Help appreciated!
Using your db/schema.rb as posted in #rubyonrails on IRC something like:
Incident.select("incidents.id").
joins("INNER JOIN category_incidents ON category_incidents.incident_id = incidents.id").
joins("INNER JOIN category_marks ON category_marks.category_id = category_incidents.category_id").
where(:category_marks => { :user_group_id => current_user.user_group_id }).
group("incidents.id").
having("SUM(CASE WHEN category_marks.inc = 1 THEN 1 ELSE 0 END) = count(category_indicents.incident_id)")
would do the trick.
It joins the category_marks for the current_user and checks if the count of records with .inc = 1 equals the count of all joined records.
Do note that this only fetches incident.id
I would add a select to the end of this query to check if all categories have type foo. I would also simplify that check by adding an instance method to the Category model.
Post.joins(:categories).select{|p| p.categories.all?(&:type_foo?)}
Category Model
def type_foo?
type == "foo"
end
ADDITION: This is a bit "hacky" but you could make it a scope this way.
class Post < ActiveRecord::Base
scope :category_type_foo, lambda{
post_ids = Post.all.collect{|p| p.id if p.categories.all?(&:type_foo?).compact
Post.where(id: post_ids) }
end
Have you tried query in the opposite direction? i.e.
Categories.where(type: 'foo').joins(:posts)
I may have misunderstood your question though.
Another alternative is
Post.joins(:classifications).where(type: 'foo')

Resources