Combine multiple queries into one active record relation? - ruby-on-rails

I am trying to write a search query for my app where based on the query string it will search for groups or users matching the string.
Here is what I have written:
def search_api
#groups = Group.where("name ILIKE '#{params[:query]}'")
#users = User.where("first_name ILIKE '#{params[:query]}' OR last_name ILIKE '#{params[:query]}")
end
Is there a way to combine these two queries into one activerecord relation array? Besides the brute force iterating over both arrays and putting them into one?

I am not sure about the solution but I have a security suggestion.
Your queries are not SQL Injection safe. You could pass array instead of injecting params in SQL string.
The following queries are SQL injection safe:
#groups = Group.where("name ILIKE ?", "#{params[:query]}")
#users = User.where("first_name ILIKE ? OR last_name ILIKE ?", "#{params[:query]}")

So here is a solution. Not sure that it is better than leaving everything as it is, but formally it's the answer to a question (besides that the result is not an ActiveRecord relation):
Group.connection.execute("(SELECT id, 'groups' as table FROM groups WHERE...) UNION (SELECT id, 'users' as table FROM users WHERE...)")
This returns an object of type PG::Result which you can treat as array of hashes. And, as it has already been said, it is good to pass arguments as an array instead of inserting them directly into SQL. Unfortunately, if You want to get a result as ActiveRecord, you may use UNION only for different queries to one table. In that case it looks like:
Group.find_by_sql("(SELECT * FROM groups WHERE...) UNION (SELECT * FROM groups WHERE...)")

Related

AR friendly where clause with table and field specified

How do I convert this to more of more AR friendly where clause?
relation.where("users.id = (?)", user_id)
end
I tried
relation.where(id: ?, user_id)
but I need to specify the table since the AR has multiple joins.
As I want to be able to provide just a single user_id or an array of user_ids.
It seems a little hard to say with so little information. But, have you tried:
relation.where(users: {id: user_id})
user_id can be an integer, an array of ids, an enumerable of users, etc.

How to query a ActiveRecord Relation for created_by field

So i'm currently using the following command to join and query my tables - looking for an OrderItem amongst my Orders where the orderable_id = applicable_product_item_id the total_price = 0 and the buyer_id = current_user
Order.joins(:items)
.where(order_items: {id: OrderItem.where(orderable_id: applicable_product_item_id)})
.where(total_price: 0)
.where(buyer_id: current_user)
This all works fine, but now i want to query further and i want to know if the order that it has found has a created_at date > searchable_created_by_date
i've tried using another .where in the query as well as selecting the .first in the array and further querying that i.e. query = above_query.first
then
query.where("created_at > ?", searchable_created_by_date)
but i get
Undefined method where for #<Order:0x007fbc8d8edf90>
furman87's comment sounds right to me:
You'll have to specify the table in your where clause -- .where("orders.created_at > ?", searchable_created_by_date)
You might also try:
Order.
where(total_price: 0).
where(buyer_id: current_user).
where("created_at > ?", searchable_created_by_date).
joins(:order_items).
where(order_items: {id: OrderItem.where(orderable_id: applicable_product_item_id)})
I think putting the created_at statement before the joins statement will disambiguate the query - but I'm not 100% sure.
Also, I would have thought that you would have done joins(:order_items). But, I suppose that depends on how you have your associations set up. If joins(:items) works for you, then more power to you! (And ignore the comment.)

How to get a most recent value group by year by using SQL

I have a Company model that has_many Statement.
class Company < ActiveRecord::Base
has_many :statements
end
I want to get statements that have most latest date field grouped by fiscal_year_end field.
I implemented the function like this:
c = Company.first
c.statements.to_a.group_by{|s| s.fiscal_year_end }.map{|k,v| v.max_by(&:date) }
It works ok, but if possible I want to use ActiveRecord query(SQL), so that I don't need to load unnecessary instance to memory.
How can I write it by using SQL?
select t.username, t.date, t.value
from MyTable t
inner join (
select username, max(date) as MaxDate
from MyTable
group by username
) tm on t.username = tm.username and t.date = tm.MaxDate
For these kinds of things, I find it helpful to get the raw SQL working first, and then translate it into ActiveRecord afterwards. It sounds like a textbook case of GROUP BY:
SELECT fiscal_year_end, MAX(date) AS max_date
FROM statements
WHERE company_id = 1
GROUP BY fiscal_year_end
Now you can express that in ActiveRecord like so:
c = Company.first
c.statements.
group(:fiscal_year_end).
order(nil). # might not be necessary, depending on your association and Rails version
select("fiscal_year_end, MAX(date) AS max_date")
The reason for order(nil) is to prevent ActiveRecord from adding ORDER BY id to the query. Rails 4+ does this automatically. Since you aren't grouping by id, it will cause the error you're seeing. You could also order(:fiscal_year_end) if that is what you want.
That will give you a bunch of Statement objects. They will be read-only, and every attribute will be nil except for fiscal_year_end and the magically-present new field max_date. These instances don't represent specific statements, but statement "groups" from your query. So you can do something like this:
- #statements_by_fiscal_year_end.each do |s|
%tr
%td= s.fiscal_year_end
%td= s.max_date
Note there is no n+1 query problem here, because you fetched everything you need in one query.
If you decide that you need more than just the max date, e.g. you want the whole statement with the latest date, then you should look at your options for the greatest n per group problem. For raw SQL I like LATERAL JOIN, but the easiest approach to use with ActiveRecord is DISTINCT ON.
Oh one more tip: For debugging weird errors, I find it helpful to confirm what SQL ActiveRecord is trying to use. You can use to_sql to get that:
c = Company.first
puts c.statements.
group(:fiscal_year_end).
select("fiscal_year_end, MAX(date) AS max_date").
to_sql
In that example, I'm leaving off order(nil) so you can see that ActiveRecord is adding an ORDER BY clause you don't want.
for example you want to get all statements by start of the months you should use this
#companey = Company.first
#statements = #companey.statements.find(:all, :order => 'due_at, id', :limit => 50)
then group them as you want
#monthly_statements = #statements.group_by { |statement| t.due_at.beginning_of_month }
Building upon Bharat's answer you can do this type of query in Rails using find_by_sql in this way:
Statement.find_by_sql ["Select t.* from statements t INNER JOIN (
SELECT fiscal_year_end, max(date) as MaxDate GROUP BY fiscal_year_end
) tm on t.fiscal_year_end = tm.fiscal_year_end AND
t.created_at = tm.MaxDate WHERE t.company_id = ?", company.id]
Note the last where part to make sure the statements belong to a specific company instance, and that this is called from the class. I haven't tested this with the array form, but I believe you can turn this into a scope and use it like this:
# In Statement model
scope :latest_from_fiscal_year, lambda |enterprise_id| {
find_by_sql[..., enterprise_id] # Query above
}
# Wherever you need these statements for a particular company
company = Company.find(params[:id])
latest_statements = Statement.latest_from_fiscal_year(company.id)
Note that if you somehow need all the latest statements for all companies then this most likely leave you with a N+1 queries problem. But that is a beast for another day.
Note: If anyone else has a way to have this query work on the association without using the last where part (company.statements.latest_from_year and such) let me know and I'll edit this, in my case in rails 3 it just pulled em from the whole table without filtering.

CONTAIN or LIKE sql statement for ActiveRecord has_and_belongs_to_many relationship

I have 2 ActiveRecords: Article and Tag, in a many to many relationship. Basically I want to know how to select with a CONTAINS or LIKE condition, ie. to define a condition on a many to many relationship to contain a specified subset within an array.
The code structure I am trying to work out is as follows:
tag_names = ["super", "awesome", "dope"]
tags = Tag.where("name IN (?)", tag_names)
# The following is my non-working code to illustrate
# what I'm trying to do:
articles = Article.where("tags CONTAINS (?)", tags)
articles = Article.joins(:tags).where("articles.tags CONTAINS (?)", tags)
If you have different tables then you have to use joins and then specify a condition on your join:
Article.joins(:tags).where('tags.id IN ?', tag_ids)
If you want more flexibility on you queries, you could also use Arel and write something like the following:
tags = Tag.arel_table
tags_ids = Tag.where(tags[:name].matches("%#{some_tag}%"))
Article.joins(:tags).where(tagss[:id].in(tags_ids))
You can read more about matches in this answer.
I prefer Arel conditions over pure String or even Hash conditions.

Rails ActiveRecord Join

I'm using rails and am trying to figure out how to use ActiveRecord within the method to combine the following into one query:
def children_active(segment)
parent_id = Category.select('id').where('segment' => segment)
Category.where('parent_id'=>parent_id, 'active' => true)
end
Basically, I'm trying to get sub categories of a category that is designated by a unique column called segment. Right now, I'm getting the id of the category in the first query, and then using that value for the parent_id in the second query. I've been trying to figure out how to use AR to do a join so that it can be accomplished in just one query.
You can use self join with a alias table name:
Category.joins("LEFT OUTER JOIN categories AS segment_categories on segment_categories.id = categories.parent_id").where("segment_categories.segment = ?", segment).where("categories.active = ?", true)
This may looks not so cool, but it can implement the query in one line, and there will be much less performance loss than your solution when data collection is big, because "INCLUDE IN" is much more slower than "JOIN".

Resources