How to stop ActiveRecord from combining queries? - ruby-on-rails

Stripping out the irrelevant parts, I have the following models:
class Member < ActiveRecord::Base
attr_accessible :custom_fields_attributes
has_many :custom_fields, :as => :custom_fieldable
end
class CustomField < ActiveRecord::Base
attr_accessible :key_id, :value
belongs_to :key
belongs_to :custom_fieldable, :polymorphic => true
end
class Key < ActiveRecord::Base
attr_accessible :key
end
Suppose I have two entries in my keys table:
---------------
id | key
---+-----------
1 | Hair color
2 | Height
how do I run a query that would let me retrieve all members with "Brown" hair color and "5ft" in height?
I tried doing it as two successive commands:
ms = Member.joins(:custom_fields).where("custom_fields.key_id = 1 AND custom_fields.value = 'Brown'")
ms.joins(:custom_fields).where("custom_fields.key_id = 2 AND custom_fields.value = '5ft'")
However, this doesn't work because ActiveRecord runs the second query like this:
SELECT `members`.* FROM `members` INNER JOIN `custom_fields` ON
`custom_fields`.`custom_fieldable_id` = `members`.`id` AND
`custom_fields`.`custom_fieldable_type` = 'Member' WHERE
(custom_fields.key_id = 1 AND custom_fields.value = 'Brown') AND
(custom_fields.key_id = 2 AND custom_fields.value = '5ft')
The query above returns an empty record because no custom field can be simultaneously two things at once.
What I would like to do is to have Rails evaluate the first result, and then run the second query on it. How would I do that?
The version I'm using is Rails 3.2.14.

I suspect you will have to alias one (or both) of the times you're joining the custom_fields table if you want to do it in one query.
Something like:
Member
.joins('custom_fields as custom_fields_1').where("custom_fields_1.key_id = 1 AND custom_fields_1.value = 'Brown'")
.joins('custom_fields as custom_fields_2').where("custom_fields_2.key_id = 2 AND custom_fields_2.value = '5ft'")
Alternatively, run the two queries separately, and merge the results. eg.
brown_members = Member.joins(:custom_fields).where("custom_fields.key_id = 1 AND custom_fields.value = 'Brown'")
height_members = Member.joins(:custom_fields).where("custom_fields.key_id = 2 AND custom_fields.value = '5ft'")
ms = brown_members.merge(height_members)

Related

ActiveRecord::AssociationRelation loses foreign key value when combined with a UNION

I'm trying to write a named scope that uses a union:
# Simplified example
class MyModel < ActiveRecord::Base
belongs_to :owner
scope :my_named_scope, -> {
scope_a = where("#{arel_table.name}.a = 'foo'")
scope_b = where("#{arel_table.name}.b = 'bar'")
union = scope_a.union(scope_b)
from(union)
}
end
MyModel.my_named_scope works fine
owner.my_models.my_named_scope does not
In the failing case, I can call .to_sql on the ActiveRecord::AssociationRelation which my_named_scope returns, and that SQL has a syntax error:
SELECT "my_models".* FROM ( SELECT "my_models".* FROM "my_models" WHERE "my_models"."owner_id" = 1001 AND (my_models.a = 'foo') UNION SELECT "my_models".* FROM "my_models" WHERE "my_models"."owner_id" = AND (my_models.b = 'bar') ) WHERE "my_models"."owner_id" =
...The sql just cuts off after "owner_id" = as if it can't find the owner_id, even though the owner_id (1001) appears earlier in the query. How do I correct this?
TMI: Rails 4.2 (I know it's old. I'm at a big company with big tech debt.)

Better method to count an associated object off a group of objects

How would one count the number of Items that are tickets in the following scenario? The ticket column lives in the Items table.
I'm trying to grab a group of redemptions, and then count the number of items that are 'tickets' for that group of redemptions.
class Item < ActiveRecord::Base
has_many :redemptions
class Redemption < ActiveRecord::Base
belongs_to :item
#This method works, but is there a much better way?
def tickets_sold
my_tickets_sold = 0
#redemptions = Redemption.where(state: "valid")
redemptions.each do |redemption|
if redemption.item.ticket == true
my_tickets_sold = my_tickets_sold + 1
end
end
my_tickets_sold
end
Yes you can do it in better way, you can use below of the 3 ways
1) Add associans for getting records of sold tickets
has_many :sold_tickets, -> { left_outer_joins(:item).where("state = 'valid' AND items.ticket = true ") }
2) Add scope for fetching count of sold tickets
scope :sold_tickets_count, -> { left_outer_joins(:item).where("state = 'valid' AND items.ticket = true ").count }
3) Add instance method in model for fetching count of sold tickets
def sold_tickets_count
left_outer_joins(:item).where("state = 'valid' AND items.ticket = true ").count
end

Display Similar Items With Having Distinct Count Rails 5.1

I'm trying to display a list of gins that have a similar minimum number of botanicals on my show page. I feel I'm close, but the current output is not right. It's actually just printing the name of the gin a number of times.
Gin Load (1.6ms) SELECT "gins".* FROM "gins" INNER JOIN
"gins_botanicals" ON "gins_botanicals"."gin_id" = "gins"."id" INNER
JOIN "botanicals" ON "botanicals"."id" =
"gins_botanicals"."botanical_id" WHERE "botanicals"."id" IN (4, 10, 3)
AND ("gins"."id" != $1) GROUP BY gins.id HAVING (COUNT(distinct
botanicals.id) >= 3) [["id", 2]]
I have three models; two resources with a joins table:
gin.rb
class Gin < ApplicationRecord
belongs_to :distillery, inverse_of: :gins
accepts_nested_attributes_for :distillery, reject_if: lambda {|attributes| attributes['name'].blank?}
acts_as_punchable
has_many :gins_botanical
has_many :botanicals, through: :gins_botanical
botanical.rb
class Botanical < ApplicationRecord
has_many :gins_botanical
has_many :gins, through: :gins_botanical
gins_botanical.rb
class GinsBotanical < ApplicationRecord
belongs_to :gin
belongs_to :botanical
gins_controller
def show
#gin = Gin.friendly.find(params[:id])
#gin.punch(request)
#meta_title = meta_title #gin.name
#similiar_gins = Gin.joins(:botanicals).where("botanicals.id" => #gin.botanical_ids).where.not('gins.id' => #gin.id).having("COUNT(distinct botanicals.id) >= 3").group("gins.id")
end
so in #similar_gins i am trying to count how many matching botanicals does the current #gin have compared to all the other #gins and if >= 3 return the values.
And in my view:
show.html.erb
<% #similiar_gins.each do |gin| %>
<%= #gin.name %>
<% end %>
I'm suspecting my where is not correct...
Yes, I have the similar feature but I have implemented like below
#gin = Gin.find(params[:id])
if #gin.botanicals.count > 1
#botanicals = #gin.botanical_ids
#gin_ids = Botanical.select('distinct gin_id').where('gin_id IN (?)', #botanicals).limit(10)
#ids = #gin_ids.map(&:gin_id)
#similiar_gins = Gin.where('id IN (?)', #ids).where.not(id: #gin) #=> similar all without current gin
end
This code is converted from my code which is relation is category and jobs, if you need to see my code for showing the similar jobs then it is
def show
#job = Job.find(params[:id])
if #job.categories.count > 1
#category = #job.category_ids
#jobs = JobCategory.select('distinct job_id').where('category_id IN (?)', #category).limit(10)
ids = #jobs.map(&:job_id)
#releted_jobs = Job.where('id IN (?)', ids).where.not(id: #job)
end
end
Hope it helps

Rails ActiveRecord intersect query with has_many association

I have the following models:
class Piece < ActiveRecord::Base
has_many :instrument_pieces
has_many :instruments, through: :instrument_pieces
end
class Instrument < ActiveRecord::Base
has_many :pieces, through: :instrument_pieces
has_many :instrument_pieces
end
class InstrumentPiece < ActiveRecord::Base
belongs_to :instrument
belongs_to :piece
end
And I have the following query:
Piece
.joins(:instrument_pieces)
.where(instrument_pieces: { instrument_id: search_params[:instruments] } )
.find_each(batch_size: 20) do |p|
Where search_params[:instruments] is an array. The problem with this query is that it will retrieve all pieces that have any of the instruments, so if search_params[:instruments] = ["1","3"], the query will return pieces with an instrument association of either 1 or 3 or of both. I'd like the query to only return pieces whose instrument associations include both instruments 1 and 3. I've read through the docs, but I'm still not sure how this can be done...
It seems like what I wanted was an intersection between the two queries, so what i ended up doing was:
queries = []
query = Piece.joins(:instruments)
search_params[:instruments].each do |instrument|
queries << query.where(instruments: {id: instrument})
end
sql_str = ""
queries.each_with_index do |query, i|
sql_str += "#{query.to_sql}"
sql_str += " INTERSECT " if i != queries.length - 1
end
Piece.find_by_sql(sql_str).each do |p|
Very ugly, but ActiveRecord doesn't support INTERSECT yet. Time to wait for ActiveRecord 5, I suppose.
You can use where clause chaining to achieve this. Try:
query = Piece.joins(:instrument_pieces)
search_params[:instruments].each do |instrument|
query = query.where(instrument_pieces: { instrument_id: instrument } )
end
query.find_each(batch_size: 20) do |p|
or another version
query = Piece.joins(:instruments)
search_params[:instruments].each do |instrument|
query = query.where(instrument_id: instrument)
end
query.find_each(batch_size: 20) do |p|

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')

Resources