ActiveRecord query with nested models and derived column values? - ruby-on-rails-3

I have the following relationships:
class Order < ActiveRecord::Base
has_many :item_selections, :dependent => :destroy
has_many :inventory_items, :through => :item_selections
end
class InventoryItem < ActiveRecord::Base
has_many :item_selections, :dependent => :destroy
has_many :orders, :through => :item_selections
end
class ItemSelection < ActiveRecord::Base
belongs_to :order
belongs_to :inventory_item
end
I am trying to create the ActiveRecord equivalent of this SQL query below and then load the sum of the *total_weight* & *total_volume* columns into an instance variable:
select t1.quantity, t2.volume, t2.weight,
t2.volume * t1.quantity as total_volume,
t1.quantity * t2.weight as total_weight
from orders t0
inner join item_selections t1 on t0.id = t1.order_id
inner join inventory_items t2 on t1.inventory_item_id = t2.id
where t0.id = <id_val>
Any idea on the right way to get these values using ActiveRecord?

This should work:
orders = Order.select('orders.*, t1.quantity, t2.volume, t2.weight, t2.volume * t1.quantity as total_volume, t1.quantity * t2.weight as total_weight').joins('inner join item_selections t1 on orders.id = t1.order_id, inner join inventory_items t2 on t1.inventory_item_id = t2.id').where(:id => id_val)
Using a custom select like this adds the other things selected as attributes of the returned objects, so you can reference them as though they were fields of the order object:
#total_volume_sum = orders.sum(:total_volume)
#total_weight_sum = orders.sum(:total_weight)

Related

How to chain joins in Sequel as in ActiveRecord

I have a model "MyModel" with the following relationships:
Class MyModel
#if MyModel < ActiveRecord::Base
has_one a
has_one b
#if MyModel < Sequel::Model
many_to_one a
many_to_one b
end
With ActiveRecord:
MyModel.joins(:a, :b)
or
MyModel.joins(:a).joins(:b)
gives the following SQL:
SELECT * FROM my_models INNER JOIN bs ON bs.id = my_models.b_id INNER JOIN as ON a.id = my_models.a_id
But with Sequel:
MyModel.join(:as, id: :a_id).join(:bs, id: :b_id)
gives the following SQL:
SELECT * FROM my_models INNER JOIN bs ON bs.id = my_models.b_id INNER JOIN as ON a.id = bs.a_id
Why does join use the last joined table's name? How do I get the same SQL generated by ActiveRecord in Sequel?
By default in Sequel's join, the implicit qualifier is the last joined table.
So,
MyModel.join(:as, id: :a_id).join(:bs, id: :b_id)
equals:
MyModel.join(:as, id: :a_id).join(:bs, id: Sequel[:as][:b_id])
Use an explicit qualifier:
MyModel.join(:as, id: :a_id).join(:bs, id: Sequel[:my_models][:b_id])
This is from the official Sequel IRC chat with #jeremyevans.

All columns in join in ruby on rails

class Category < ActiveRecord::Base
has_many :articles
end
class Article < ActiveRecord::Base
belongs_to :category
has_many :comments
has_many :tags
end
Category.joins(:articles) in rails equivalently
SELECT categories.* FROM categories
INNER JOIN articles ON articles.category_id = categories.id
if i want have rails-code is equivalently sql
SELECT * FROM categories
INNER JOIN articles ON articles.category_id = categories.id
what should I do?
Category.select('*').joins(:articles)
or
categoyes = Category.select('*').includes(:articles)
and
categoyes.each do |category|
puts category.articles
end
Try to use select method
categories = Category.select('*').joins(:articles)
categories.first.some_column_from_articles
I use this method
Category.connection.select_all("SELECT * FROM categories
INNER JOIN articles ON articles.category_id = categories.id").to_a

converting sql with multilevel joins to rails activerecord expression

'SELECT "complaints".* FROM "complaints" INNER JOIN "machines" ON "machines"."id" = "complaints"."machine_id" INNER JOIN "mclns" ON "machines"."mcln_id" = "mclns"."id" ORDER BY "mclns"."response_time" ASC'
I need the above sql to be converted to Active record statement
complaint model does not know about mcln
it has to go through machine
Complaint.joins(:machine=>:mcln) gives
SELECT "complaints".* FROM "complaints" INNER JOIN "machines" ON "machines"."id" = "complaints"."machine_id" INNER JOIN "mclns" ON "mclns"."machine_id" = "machines"."id"
but I need
SELECT "complaints".* FROM "complaints" INNER JOIN "machines" ON "machines"."id" = "complaints"."machine_id" INNER JOIN "mclns" ON "machines"."mcln_id" = "mclns"."id"
Update:
A machine can have a mcln and also many machines can have same mcln.
I have implemented it by using
has_one :mcln on Machine model and
belongs_to :machine on Mcln
And I'm not sure if thats correct implementation.
Your association is incorrect. If many machines can have the same mcln then the machines table should have a mcln_id column and your classes should look like
class Machine < AR::Base
belongs_to :mcln
end
class Mcln < AR::Base
has_many :machines
end
Then Complaint.joins(:machine => :mcln) should generate the SQL you want
try out this :
SELECT complaints.*,machines.*,mclns.* FROM `complaints`
INNER JOIN `machines`
ON ( machines.id = complaints.machine_id )
INNER JOIN `mclns`
( ON machines.mcln_id = mclns.id )

Multiple join query in a scope/named_scope

Is it possible to create a named_scope from the following query:
SELECT g.*, b.*
FROM users u
LEFT JOIN band_users bu ON u.id = bu.uid
LEFT JOIN bands b ON bu.bid = b.id
LEFT JOIN bands_gigs bg ON b.id = bg.bid
LEFT JOIN gigs g ON bg.gid = g.id
WHERE u.id = 1
I am struggling to do it, is it possible to represent multiple :joins in a named_scope or rails 3 scope?
Thanks
Yes, this can be done with:
class Band < ActiveRecord::Base
has_and_belongs_to_many :gigs
...
end
class User < ActiveRecord::Base
has_and_belongs_to_many :bands
scope :my_scope, joins(:bands => :gigs)
...
end

Rails3 ActiveRecord - Fetching all records that have A and B (through a has_many relationship)

I have the following model relationships:
class Article < ActiveRecord::Base
has_many :tags, :through => :article_tags
end
class ArticleTag < ActiveRecord::Base
belongs_to :tag
belongs_to :article
end
class Tag < ActiveRecord::Base
has_many :articles, :through => :article_tags
end
Now I want to load all the Articles that are tagged with both tag "A" and tag "B".
I'm pretty new to Rails and it has been a few years since I did any serious web-dev/SQL work but for the life of me I can't figure out how one would construct an ActiveRecord query to do this.
One way to do it in native SQL would be as follows:
SELECT a.* FROM articles a
INNER JOIN article_tags at ON at.article_id = a.id
INNER JOIN tags t ON at.tag_id = t.id
WHERE t.name = 'A'
intersect
SELECT a.* from articles a
INNER JOIN article_tags at ON at.article_id = a.id
INNER JOIN tags t ON at.tag_id = t.id
WHERE t.name = 'B'
Now that might not be the most efficient SQL but it works. At this late hour I can't think of a better solution in SQL.
Update: Further investigation has lead me to this SQL:
SELECT a.* FROM article_tags at, articles a, tags t
WHERE at.tag_id = t.id
AND (t.name = 'A' OR t.name = 'B')
AND a.id = at.vehicle_id
GROUP BY a.id HAVING COUNT(a.id) = 2
This seems like simpler (less verbose) SQL but in no more efficient. However, I can probably more easily construct a "find_by_sql" ActiveRecord query using this SQL.
Any guidance on how to best do this sort of query with ActiveRecord (preferably without resorting to SQL) would be greatly appreciated.
I ended up solving my own problem. I constructed a named scope as follows:
scope :tagged, lambda { |tag, *tags|
tags = tags.unshift(*tag)
joins(:tags).
where("lower(tags.name) = '" + tags.uniq.collect{ |t| t.to_s.downcase }.join("' OR lower(tags.name) = '") + "'").
group("articles.id").
having("count(articles.id) = #{tags.count}")
}
So, now I can do this in my controllers:
#tagged_articles = Article.tagged('A', 'B', 'C')
And #tagged_articles will include all the articles tagged with all of the tags 'A', 'B', and 'C'.