Rails SQL in controller - sql

I'm using Xeditable and RABL in a Rails app.
I have a workorder that belongs to a workgroup.
I want to assign the workorder to an employee in that workgroup.
I'm using this as the source in the Xeditable:
data-source="/employees.json?workgroup=<%= workorder.workgroup.id%>"
And this is the code I'm trying in the employee controller:
def index
#employees = Employee.order(:first_name)
#employees = Employee.joins(:empgroups).where(:workgroup_id => params[:workgroup]) if params[:workgroup].present?
end
This is the SQL that gets generated:
SELECT "employees".* FROM "employees" INNER JOIN "empgroups" ON "empgroups"."employee_id" = "employees"."id" WHERE "employees"."workgroup_id" = 2
The issue is the WHERE should be `WHERE "empgroups"."workgroup_id" = 2
How do I change this line of code?
#employees = Employee.joins(:empgroups).where(:workgroup_id => params[:workgroup]) if params[:workgroup].present?
Thanks for the help!

You can use nested hash syntax:
#employees = Employee.joins(:empgroups).where(
empgroups: { workgroup_id: params[:workgroup] }
) if params[:workgroup].present?

This should work:
Employee.joins(:empgroups).where(:empgroups => {:workgroup_id => params[:workgroup]})
The :workgroup_id is actually an attribute/column on a joined table, and not on the base table that you are querying from, so you need to specify where that column is located during the where clause.
Remember that in a pinch you can also type in SQL for the where clause, but will also have to remember to properly reference the right table.
Employee.joins(:empgroups).where("empgroups.workgroup_id = ?", params[:workgroup])

Related

How to convert complex Postgres SQL query / subqueries to active record in Ruby on Rails 5.2?

My application requires a text field and its translations to be displayed all at once in the edit view.
Available translations and even the list of configured languages evolve over time, so that the edit view must display the available translations, and create empty fields for missing ones.
Thus, the _form.html.erb file contains nested fields to handle these translations.
<%= f.fields_for translation_fields, translations.sort_by { |e| [ e.language == current_language.to_s ? 0 : 1, e.language ] } do |locution| %>
But this only allows handling of existing translations.
To allow additional languages support, I need to create a right-join with the list of languages, and initialise empty records accordingly. The corresponding SQL query for providing translations fields for the name of Playground object with id=555 is:
select 555 as document_id, 'Playground' as document_type, 'name' as field_name, property as language,
case when property = language then existing_translations.id else null end as id,
case when property = language then existing_translations.translation else null end as translation
from
(select *
from dqm_app.translations
where document_id = 555 and field_name = 'name' and document_type = 'Playground'
) as existing_translations
right outer join
(select property
from dqm_app.parameters inner join dqm_app.parameters_lists on parameters.parameters_list_id = parameters_lists.id
where parameters_lists.CODE = 'LIST_OF_LANGUAGES') as langues on property = language
As a result, we can see herunder that only a french translation exists, and other translations are to be created:
To reproduce this in Active Record, I created subqueries, which I need to join:
<% existing_translations = Translation.where({document_id: document_id, document_type: document_type, field_name: field_name}) %>
<% languages = Parameter.where("parameters_list_id = (select id from parameters_lists where code = 'LIST_OF_LANGUAGES')") %>
How can I create a right outer join between these subqueries?
Is there a better way to achieve this?
Thanks a lot!
Note: I finally solved any complex query by using AREL, starting with
this cheat sheet.

Rails ActiveRecord Join Query With conditions

I have following SQL Query:
SELECT campaigns.* , campaign_countries.points, offers.image
FROM campaigns
JOIN campaign_countries ON campaigns.id = campaign_countries.campaign_id
JOIN countries ON campaign_countries.country_id = countries.id
JOIN offers ON campaigns.offer_id = offers.id
WHERE countries.code = 'US'
This works perfectly well. I want its rails active record version some thing like:
Campaign.includes(campaign_countries: :country).where(countries: {code: "US"})
Above code runs more or less correct query (did not try to include offers table), issue is returned result is collection of Campaign objects so obviously it does not include Points
My tables are:
campaigns --HAS_MANY--< campaign_countries --BELONGS_TO--< countries
campaigns --BELONGS_TO--> offers
Any suggestions to write AR version of this SQL? I don't want to use SQL statement in my code.
I some how got this working without SQL but surely its poor man's solution:
in my controller I have:
campaigns = Campaign.includes(campaign_countries: :country).where(countries: {code: country.to_s})
render :json => campaigns.to_json(:country => country)
in campaign model:
def points_for_country country
CampaignCountry.joins(:campaign, :country).where(countries: {code: country}, campaigns: {id: self.id}).first
end
def as_json options={}
json = {
id: id,
cid: cid,
name: name,
offer: offer,
points_details: options[:country] ? points_for_country(options[:country]) : ""
}
end
and in campaign_countries model:
def as_json options={}
json = {
face_value: face_value,
actual_value: actual_value,
points: points
}
end
Why this is not good solution? because it invokes too many queries:
1. It invokes query when first join is performed to get list of campaigns specific to country
2. For each campaign found in first query it will invoke one more query on campaign_countries table to get Points for that campaign and country.
This is bad, Bad and BAD solution. Any suggestions to improve this?
If You have campaign, You can use campaign.campaign_countries to get associated campaign_countries and just get points from them.
> campaign.campaign_countries.map(&:points)
=> [1,2,3,4,5]
Similarly You will be able to get image from offers relation.
EDIT:
Ok, I guess now I know what's going on. You can use joins with select to get object with attached fields from join tables.
cs = Campaign.joins(campaign_countries: :country).joins(:offers).select('campaigns.*, campaign_countries.points, offers.image').where(countries: {code: "US"})
You can than reference additional fields by their name on Campaign object
cs.first.points
cs.first.image
But be sure, that additional column names do not overlap with some primary table fields or object methods.
EDIT 2:
After some more research I came to conclusion that my first version was actually correct for this case. I will use my own console as example.
> u = User.includes(:orders => :cart).where(:carts => { :id => [5168, 5167] }).first
> u.orders.length # no query is performed
=> 2
> u.orders.count # count query is performed
=> 5
So when You use includes with condition on country, in campaign_countries are stored only campaign_countries that fulfill Your condition.
Try this:
Campaign.joins( [{ :campaign_countries => :countries}, :offers]).where('`countries`.`code` = ?', "US")

Is it the right way to do union query after joins in rails 3.2?

There are 3 models log (which belongs to customer), customer and project in rails 3.2 app. Both customer and project have sales_id field. Here is the query we want to do:
return the following logs for customers 1) logs for customers whose sales_id is equal to session[:user_id] and 2) logs for customers whose projects' sales_id is equal to session[:user_id]
The rails query for 1) could be:
Log.joins(:customer).where(:customers => {:sales_id => session[:user_id]})
Rails query for 2) could be:
Log.joins(:customer => :projects).where(:projects => {:sales_id => session[:user_id})
To combine the queries above, is it the right way to do the following?
Log.joins([:customer, {:customer => :projects}]).where('customers.sales_id = id OR projects.sales_id = id', id: session[:user_id])
Chapter 11.2.4 in http://guides.rubyonrails.org/v3.2.13/active_record_querying.html talks about an interesting query case. We haven't tested the query above yet. We would like to know if the union query above is indeed correct.
Rails doesn't support union natively. In your case, I think it doesn't need union, just use left outer join.
Log.joins('left outer JOIN `customers` ON `customers`.`id` = `logs`.`customer_id`
left outer JOIN `projects` ON `projects`.`customer_id` = `customers`.`id`').where('customers.sales_id = :id OR projects.sales_id = :id', id: session[:user_id]).distinct

Are those two queries the same in rails 3.2.12?

We need to retrieve logs for customer comm record in our rails app. The condition in plain english is logs for customer_comm_record based on:
#1. the sales_id in customer comm record's customers is equal to current user id
#2. the sales_id in customer comm record's customer's project is equal to the current user id.
The SQL code for #1 could be (SQL#1):
Log.joins(:customer_comm_record =>:customer).where('customers.sales_id = ?', session[:user_id])
SQL code for #2 could be (SQL#2):
Log.joins(:customer_comm_record =>{ :customer => :projects}).where('projects.sales_id = ?', session[:user_id])
Here is what we come up (SQL#3) in one line:
Log.joins(:customer_comm_record =>{ :customer => :projects}).where('customers.sales_id = ? OR projects.sales_id = ?', session[:user_id], session[:user_id])
The SQL code returned by above in rails console is (replacing session[:user_id] with 1) :
SELECT "logs".* FROM "logs" INNER JOIN "customer_comm_records" ON "customer_comm_records"."id" = "logs"."customer_comm_record_id" INNER JOIN "customers" ON "customers"."id" = "customer_comm_records"."customer_id" INNER JOIN "projects" ON "projects"."customer_id" = "customers"."id" WHERE (customers.sales_id = 1 OR projects.sales_id = 1)
The question is weather SQL#3 == SQL#1 UNION SQL#2. Can someone answer the question? If it is not, what's the right one? Thanks.
The only differences are:
The first method will return duplicate entries where the sales_id for the customer and project both match the users id.
The second method might not return records where a customer with a matching sales_id does not have a project (no projects.customer_id for that customer).
The last difference only matters if a customer does not necessarily have any projects. Otherwise, the second method is better because it avoids dealing with duplicates.
If a customer does not always have a project, a simple workaround would be to use includes instead of joins:
Log.includes(:customer_comm_record =>{ :customer => :projects}).
where('customers.sales_id = :id OR projects.sales_id = :id', id: session[:user_id])
This will force a LEFT JOIN and will return customers regardless of whether they are associated with a project.

Nested sql queries in rails when :has_and_belongst_to_many

In my application I the next task that has not already been done by a user. I have Three models, A Book that has many Tasks and then I have a User that has has and belongs to many tasks. The table tasks_users table contains all completed tasks so I need to write a complex query to find the next task to perform.
I have came up with two solutions in pure SQL that works, but I cant translate them to rails, thats what I need help with
SELECT * FROM `tasks`
WHERE `tasks`.`book_id` = #book_id
AND `tasks`.`id` NOT IN (
SELECT `tasks_users`.`task_id`
FROM `tasks_users`
WHERE `tasks_users`.`user_id` = #user_id)
ORDER BY `task`.`date` ASC
LIMIT 1;
and equally without nested select
SELECT *
FROM tasks
LEFT JOIN tasks_users
ON tasks_users.tasks_id = task.id
AND tasks_users.user_id = #user_id
WHERE tasks_users.task_id IS NULL
AND tasks.book_id = #book_id
LIMIT 1;
This is what I Have done in rails with the MetaWhere plugin
book.tasks.joins(:users.outer).where(:users => {:id => nil})
but I cant figure out how to get the current user there too,
Thanks for any help!
I think this will duplicate the second form with the LEFT JOIN:
class Task < ActiveRecord::Base
scope :next_task, lambda { |book,user| book.tasks.\
joins("LEFT JOIN task_users ON task_users.task_id=tasks.id AND task_users.user_id=#{user.id}").\
where(:tasks=>{:task_users=>{:task_id=>nil}}).\
order("date DESC").limit(1) }
end
Note that instead of tasks_users this uses the table name task_user, which is more typical for a join model. Also, it needs to be called with:
Task.next_task(#book_id,#user_id)
book.tasks.where("tasks.id not in (select task_id from tasks_users where user_id=?)", #user_id).first
That would give you the first task that doesn't already have an entry in tasks_users for the current user.