Arel syntax for ( this AND this) OR ( this AND this) - ruby-on-rails

how would I do this in Arel ( this AND this) OR ( this AND this)
Context is rails 3.0.7

Mike's answer was basically what you're looking for
class Model < ActiveRecord::Base
def self.this_or_that(a1, a2, a3, a4)
t = Model.arel_table
q1 = t[:a].eq(a1).and(t[:b].eq(a2))
q2 = t[:c].eq(a3).and(t[:d].eq(a4))
where(q1.or(q2))
end
end
some slides I put together on Arel

I'd put this all in a single where block. For example, here's a 'scope' based on a similar complex 'where' clause:
class Foo < ActiveRecord::Base
def self.my_complex_where(args)
where("(col1 = ? AND col2 = ?) OR (col3 = ? AND col4 = ?)",
args[:val1], args[:val2], args[:val3], args[:val4])
end
end
Alternatively, you could do it using this notation:
class Foo < ActiveRecord::Base
def self.my_complex_where(args)
where("(col1 = :val1 AND col2 = :val2) OR (col3 = :val3 AND col4 = :val4)",
:val1 => args[:val1],
:val2 => args[:val2],
:val3 => args[:val3],
:val4 => args[:val4])
end
end

You need to drop down to more raw AREL
t = Model.arel_table
a = (t[:a].eq(nil).and(t[:b].eq(nil)))
b = (t[:a].not_eq(nil).and(t[:b].not_eq(nil)))
Model.where(a.and(b))

Besides the answers already given, you might also want to take a look at the gem 'squeel'
Example:
Person.where{(name =~ 'Ernie%') & (salary < 50000) | (name =~ 'Joe%') & (salary > 100000)}

I don't know about helper methods, but you can always drop down to (almost) pure SQL:
Client.where("orders_count = ? AND locked = ?", params[:orders], false)

Related

How to build a query with arbitrary placeholder conditions in ActiveRecord?

Assume I have an arbitrary number of Group records and I wanna query User record which has_many :groups, the catch is that users are queries by two bound fields from the groups table.
At the SQL level, I should end up with something like this:
SELECT * FROM users where (categories.id = 1 OR users.status = 0) OR(categories.id = 2 OR users.status = 1) ... -- to infinity
This is an example of what I came up with:
# Doesn't look like a good solution. Just for illustration.
or_query = groups.map do |g|
"(categories.id = #{g.category.id} AND users.status = #{g.user_status.id} )"
end.join('OR')
User.joins(:categories).where(or_query) # Works
What I think I should be doing is something along the lines of this:
# Better?
or_query = groups.map do |g|
"(categories.id = ? AND users.status = ? )".bind(g.category.id, g.user_status.id) #Fake method BTW
end.join('OR')
User.joins(:categories).where(or_query) # Works
How can I achieve this?
There has to be a better way, right?
I'm using Rails 4.2. So the shiny #or operator isn't supported for me.
I would collect the condition parameters separately into an array and pass that array (splatted, i.e. as an arguments list) to the where condition:
or_query_params = []
or_query = groups.map do |g|
or_query_params += [g.category_id, g.user_status.id]
"(categories.id = ? AND users.status = ?)"
end.join(' OR ')
User.joins(:categories).where(or_query, *or_query_params)
Alternatively, you might use ActiveRecord sanitization:
or_query = groups.map do |g|
"(categories.id = #{ActiveRecord::Base.sanitize(g.category_id)} AND users.status = #{ActiveRecord::Base.sanitize(g.user_status.id)})"
end.join(' OR ')
User.joins(:categories).where(or_query)

how to convert sql query in ruby and rails?

SELECT A.FirstName, A.LastName, B.PatientId, B.RoomNumber, B.AdmissionDate, B.DischargeDate, B.MeasureCategory
FROM DimPatient A, DimPatientStay B
WHERE A.Id = B.PatientId AND A.FirstName = 'Anuj' AND B.MeasureCategory = 'ED'
hi some updation for this
i solved this prob by
MODELNAME.find_by_sql("your sql query")
You can try this to find the result from sql query in Rails
query_params = Hash.new
sql_query = "SELECT A.FirstName, A.LastName, B.PatientId, B.RoomNumber, B.AdmissionDate, B.DischargeDate, B.MeasureCategory
FROM DimPatient A, DimPatientStay B
WHERE A.Id = B.PatientId AND A.FirstName = :first_name AND B.MeasureCategory = :measure_category"
query_params[:first_name] = first_name
query_params[:measure_category] = measure_category
#query_results = ActiveRecord::Base.connection.select_all(
ActiveRecord::Base.send("sanitize_sql_array",[sql_query, query_params] )
)
I guess you could try:
ActiveRecord::Base.connection().execute(#your_sql_here)
Suppose A is one class and B is another, you should use includes as following:
A.includes(:b).where(...) # add you condition in where
I suggest to check good video tutorials of ActiveRecord here

Rails: Postgresql where with multiple conditions with join (polymorphic)

Hi guys here is my code:
class Tailor < ActiveRecord::Base
has_many :tailor_items
has_many :order_items
[:collars, :sexes, :sleeves].each do |attribute|
has_many attribute, through: :tailor_items, source: :item, source_type: attribute.to_s.classify
end
end
class TailorItem < ActiveRecord::Base
belongs_to :tailor
belongs_to :item, polymorphic: true
end
class Collar < ActiveRecord::Base
end
What I need to do is this:
For a given shirt I need to select a tailor. A shirt can have a collar, male/female or a certain type of sleeve. Some tailors can make all collars but only a few sleeves, others can make only male stuff, etc.
The priority doesnt matter for this example. The idea is that I end up with 1 tailor.
I tried this:
tailors = Tailor.joins(:tailor_items).where("(item_id = ? and item_type = ?)",1,"Collar")
if tailors.count > 1
tailors.where("(item_id = ? and item_type = ?)",2,"Sleeve")
if tailors.count > 1
# and so forth.
end
end
But I never get a row back.
If I say:
Tailor.find(1).tailor_items
I get two results (sudo code for simplicity)
<id: 1, item_type: "Collar"><id:2, item_type:"Sleeve">
and for second tailor:
Tailor.find(2).tailor_items
I get two results (sudo code for simplicity)
<id: 1, item_type: "Collar"><id:3, item_type:"Sleeve">
but when I try to chain them in the query its no worky...
Not even if I put it all in one where:
Tailor.where("(item_id = 1 and item_type = 'Collar') and (item_id = 2 and item_type = 'Sleeve')")
I still get 0 results.
Tailor.where("item_id = 1 and item_type = 'Collar'") returns: Tailor #1
Tailor.where("item_id = 2 and item_type = 'Sleeve'") returns: Tailor #1
but together they return nothing.
Tailor Load (0.0ms) SELECT "tailors".* FROM "tailors" INNER
JOIN "tailor_items" ON "tailor_items"."tailor_id" = "tailors"."id" WHERE ((tailo
r_items.item_id = 1 and tailor_items.item_type = 'Collar') and (tailor_items.ite
m_id = 2 and tailor_items.item_type = 'Sleeve'))
I am confused..
Thanks for your help.
I run:
Win XP
Postgresql
Rails 3.2.2
PS: The only thing missing to make this complete after a polymorphic join is a bit of XML. :P Otherwise its just not enterprise-y enough..
EDIT:
Implementing Rob di Marcos scope, I get this SQL:
SELECT "tailors".* FROM "tailors" WHERE
(EXISTS(SELECT * FROM tailor_items WHERE tailor_items.item_id = 1 and tailor_items.item_type = 'Collar'))
AND (exists(select * from tailor_items where tailor_items.item_id = 2 and tailor_items.item_type = 'Sleeve'))
This returns
2 tailors instead of only tailor 1 who can do both (while tailor 2 cant do sleeve #2)
The problem is that the where needs to match on two rows. I generally will use sub-queries to test for this. So something like
Tailor.where("exists (select 'x' from tailor_items where
tailor_id = tailors.id and tailor_items.item_id = ? and
tailor_items.item_type=?)", 1, 'Collar').
where("exists (select 'x' from tailor_items where
tailor_id = tailors.id and tailor_items.item_id = ? and
tailor_items.item_type=?)", 2, 'Sleeve')
In this example, I have one sub-query for each tailor item I am looking for. I could easily make this a scope on Tailor like:
class Tailor
# ....
scope :with_item, lambda{ |item_id, item_type |
where("exists (select 'x' from tailor_items where
tailor_id = tailors.id and tailor_items.item_id = ? and
tailor_items.item_type=?)", item_id, item_type)
}
and then be able to chain my Tailor request
Tailor.with_item(1, 'Collar').with_item(2, 'Sleeve')

How can I write the most efficient scope for asking only those parent objects which have some certain child objects

I have:
class Evaluation < ActiveRecord::Base
has_many :scores
end
class Score < ActiveRecord::Base
scope :for_disability_and_score,
lambda { |disability, score|
where('total_score >= ? AND name = ?', score, disability)
}
end
The scores table has a total_score field and a name field.
How can i write a scope to ask for only those evaluations which have a Score with name 'vision' and total_score of 2 and they have another Score with name 'hearing' and total_score of 3.
And how can all this be generalized to ask for those evaluations which have n scores with my parameters?
In raw sql it would be like:
I managed to do it in raw sql:
sql = %q-SELECT "evaluations".*
FROM "evaluations"
INNER JOIN "scores" AS s1 ON "s1"."evaluation_id" = "evaluations"."id"
INNER JOIN "target_disabilities" AS t1 ON "t1"."id" = "s1"."target_disability_id"
INNER JOIN "scores" AS s2 ON "s2"."evaluation_id" = "evaluations"."id"
INNER JOIN "target_disabilities" AS t2 ON "t2"."id" = "s2"."target_disability_id"
WHERE "t1"."name" = 'vision' AND (s1.total_score >= 1)
AND "t2"."name" = 'hearing' AND (s2.total_score >= 2)-
Here the point is to repeat this:
INNER JOIN "scores" AS s1 ON "s1"."evaluation_id" = "evaluations"."id"
and this (by replacing s1 to s2 and s3 and so forth):
WHERE (s1.total_score >= 1)
But it should be a rails way of doing this... :) Hopefully
Try this:
scope :by_scores, lambda do |params|
if params.is_a? Hash
query = joins(:scores)
params.each_pair do |name, score|
query = query.where( 'scores.total_score >= ? AND scores.name = ?', score, name)
end
query
end
end
Then call like this:
Evaluation.by_scores 'vision' => 2, 'hearing' => 3

Searching and comparing ActiveRecord attributes to find largest value

I have a model that would look something like:
my_diet = Diet.new
my_diet.food_type_1 = "beef"
my_diet.food_type_1_percentage = 40
my_diet.food_type_2 = "carrots"
my_diet.food_type_2_percentage = 50
my_diet.food_type_3 = "beans"
my_diet.food_type_3_percentage = 5
my_diet.food_type_4 = "chicken"
my_diet.food_type_4_percentage = 5
I need to find which food_type has the highest percentage. So far I've tried creating a hash out of the attibutes and percentages then sorting the hash (see below) but it feels like there must be a cleaner way to do it.
food_type_percentages = { :food_type_1 => my_diet.foo_type_percentage_1_percentage.nil? ? 0 : my_dient.food_type_1_percentage,
:food_type_2 => my_diet.foo_type_percentage_2_percentage.nil? ? 0 : my_dient.food_type_2_percentage,
:food_type_3 => my_diet.foo_type_percentage_3_percentage.nil? ? 0 : my_dient.food_type_3_percentage,
:food_type_4 => my_diet.foo_type_percentage_4_percentage.nil? ? 0 : my_dient.food_type_4_percentage
}
food_type_percentages.sort {|a,b| a[1]<=>b[1]}.last
Any ideas?
Thanks!
To find the max value amongst columns of an existent row in the DB, do the following:
d = Diet.first(:select => "*, GREATEST(
food_type_1_percentage,
food_type_2_percentage,
food_type_3_percentage,
food_type_4_percentage) AS top_food_type_percentage,
CASE GREATEST(
food_type_1_percentage,
food_type_2_percentage,
food_type_3_percentage,
food_type_4_percentage)
WHEN food_type_1_percentage THEN food_type_1
WHEN food_type_2_percentage THEN food_type_2
WHEN food_type_3_percentage THEN food_type_3
WHEN food_type_4_percentage THEN food_type_4
END AS top_food_type")
d.top_food_type # carrots
d.top_food_type_percentage # 50
If you are trying to find the top food type in the current model instance then
class Diet < ActiveRecord::Base
def top_food_type
send(top_food_type_col)
end
def top_food_type_percentage
send("#{top_food_type_col}_percentage")
end
FOOD_TYPE_COL = %w(food_type_1 food_type_2 food_type_3 food_type_4)
def top_food_type_col
#top_food_type_col ||= FOOD_TYPE_COL.sort do |a, b|
send("#{a}_percentage") <=> send("#{b}_percentage")
end.last
end
end
Now you can do the following:
d = Diet.new
....
....
....
d.top_food_type # carrots
d.top_food_type_percentage # 50
I assume food_percentage is the column
if you just want to find out ref this
Diet.maximum('food_percentage') # gives 50
OR you want complete record use this
Diet.find(:first, :order=> 'food_percentage DESC', :limit=>1)

Resources