globalize3 - Query translated attribute - sql

I have 2 models:
class Gender < ActiveRecord::Base
translates :name
has_many :products
end
class Product < ActiveRecord::Base
translates :description
belongs_to :gender
end
After integrating with globalize3 I cannot figure out how to get query that joins to work, for example:
Product.joins(:gender).where(genders: { name: 'male' })
which generates this sql query:
SELECT "products".* FROM "products"
INNER JOIN "genders" ON "genders"."id" = "products"."gender_id"
WHERE "genders"."name" = 'male'`
But I think I need a sql query that looks like this?
SELECT * FROM products
INNER JOIN genders on genders.id = products.gender_id
INNER JOIN gender_translations on gender_translations.gender_id = genders.id
WHERE gender_translations.name = 'male';
So how does one do the rails equivalent of this sql query?

Something along the lines of Product.joins(gender: :translations).where(gender: { translations: { name: 'male' }}) should do the trick I believe.
The joins(gender: :translations) is a nested inner join. So you're joining products-> genders, and genders -> gender_translations.
The where hash syntax is just an attempt to generate the SQL: where("gender_translations.prompt = 'male'"). If the hash syntax isn't correct/fights you, I'd just revert to the raw SQL just mentioned. It's arguably more clear anyways!

Related

How to convert this sql joins/count statements in clean ruby ActiveRecord

I'm trying to convert with no success my find_by_sql statement into a pure ActiveRecord query.
It is:
Corner.find_by_sql('SELECT corners.id, corners.name, count(members.*) FROM corners LEFT JOIN places ON corners.id = places.ubicacion_id LEFT JOIN members ON places.id = members.place_id GROUP BY corners.id,corners.name ORDER BY corners.name;')
nicely formatted, the sql expression would be:
SELECT corners.id,
corners.name,
count(members.*)
FROM corners
LEFT JOIN places ON corners.id = places.ubicacion_id
LEFT JOIN members ON places.id = members.place_id
GROUP BY corners.id,
corners.name
ORDER BY corners.name;
In very old versions of ActiveRecord, my approach would be using the find :all and then passing a hash of options, but this way is deprecated:
Corner.find( :all,
:joins => "LEFT JOIN places ON corners.id = places.ubicacion_id",
:joins => "LEFT JOIN members ON places.id = members.place_id",
:group => "corners.id,corners.name",
:order => "corners.name",
:select => "corners.id, corners.name, count(members.*)"
)
Which one would be the best approach to rewrite in the ActiveRecord way the query? This last snippet works well, but it makes no difference on using it rather than the plain sql one:
Corner.joins("LEFT JOIN places ON corners.id = places.ubicacion_id").joins("LEFT JOIN members ON places.id = members.place_id").group("corners.id,corners.name").order("corners.name").select("corners.id, corners.name, count(members.*)")
Many thanks!
It looks like you probably want to set up some model relationships to make this more ActiveRecord-like. You can find descriptions of how to do this in the Active Record Associations documentation.
Consider these relationships:
class Member < ActiveRecord::Base
belongs_to :place
end
class Place < ActiveRecord::Base
belongs_to :corner
has_many :members
end
class Corner < ActiveRecord::Base
has_many :places, foreign_key: "ubicacion_id"
end
Given those, you should be able to do something like this:
Corner.
select("corners.id, corners.name, count(members.*)").
joins(:places, :members).
group("corners.id, corners.name").
order("corners.name")
Each of the methods chained in the query will refine the query incrementally, much like building a native SQL statement. You can find the official documentation for these methods in the Active Record Query Interface

ActiveRecord .merge not working on two relations

I have the following models in my app:
class Company < ActiveRecord::Base
has_many :gallery_cards, dependent: :destroy
has_many :photos, through: :gallery_cards
has_many :direct_photos, class_name: 'Photo'
end
class Photo < ActiveRecord::Base
belongs_to :gallery_card
belongs_to :company
end
class GalleryCard < ActiveRecord::Base
belongs_to :company
has_many :photos
end
As you can see, Company has_many :photos, through: :gallery_cards and also has_many :photos. Photo has both a gallery_card_id and a company_id column.
What I want to be able to do is write a query like #company.photos that returns an ActiveRecord::Relation of all the company's photos. In my Company model, I currently have the method below, but that returns an array or ActiveRecord objects, rather than a relation.
def all_photos
photos + direct_photos
end
I've tried using the .merge() method (see below), but that returns an empty relation. I think the reason is because the conditions that are used to select #company.photos and #company.direct_photos are different. This SO post explains it in more detail.
#company = Company.find(params[:id])
photos = #company.photos
direct_photos = #company.direct_photos
direct_photos.merge(photos) = []
photos.merge(direct_photos) = []
I've also tried numerous combinations of .joins and .includes without success.
this might be a candidate for a raw SQL query, but my SQL skills are rather basic.
For what it's worth, I revisited this and came up (with help) another query that grabs everything in one shot, rather than building an array of ids for a second query. This also includes the other join tables:
Photo.joins("
LEFT OUTER JOIN companies ON photos.company_id = #{id}
LEFT OUTER JOIN gallery_cards ON gallery_cards.id = photos.gallery_card_id
LEFT OUTER JOIN quote_cards ON quote_cards.id = photos.quote_card_id
LEFT OUTER JOIN team_cards ON team_cards.id = photos.team_card_id
LEFT OUTER JOIN who_cards ON who_cards.id = photos.who_card_id
LEFT OUTER JOIN wild_cards ON wild_cards.id = photos.wild_card_id"
).where("photos.company_id = #{id}
OR gallery_cards.company_id = #{id}
OR quote_cards.company_id = #{id}
OR team_cards.company_id = #{id}
OR who_cards.company_id = #{id}
OR wild_cards.company_id = #{id}").uniq
ActiveRecord's merge returns the intersection not the union of the two queries – counterintuitively IMO.
To find the union, you need to use OR, for which ActiveRecord has poor built-in support. So I think you're correct that its best to write the conditions in SQL:
def all_photos
Photo.joins("LEFT OUTER JOIN gallery_cards ON gallery_cards.id = photos.gallery_card_id")
.where("photos.company_id = :id OR gallery_cards.company_id = :id", id: id)
end
ETA The query associates the gallery_cards to photos with a LEFT OUTER JOIN, which preserves those photo rows without associated gallery card rows. You can then query based on either photos columns or on associated gallery_cards columns – in this case, company_id from either table.
You can leverage ActiveRecord scope chaining to join and query from additional tables:
def all_photos
Photo.joins("LEFT OUTER JOIN gallery_cards ON gallery_cards.id = photos.gallery_card_id")
.joins("LEFT OUTER JOIN quote_cards ON quote_cards.id = photos.quote_card_id")
.where("photos.company_id = :id OR gallery_cards.company_id = :id OR quote_cards.company_id = :id", id: id)
end

Query using condition within an array

I have 2 models, user and centre, which have a many to many relationship.
class User < ActiveRecord::Base
attr_accessible :name
has_and_belongs_to_many :centres
end
class Centre < ActiveRecord::Base
attr_accessible :name, :centre_id, :city_id, :state_id
has_and_belongs_to_many :users
end
Now I have an user with multiple centres, and I want to retrieve all the centres that have the same "state_id" as that user.
This is what I am doing now
state_id_array = []
user.centres.each do |centre|
state_id_array << centre.state_id
end
return Centre.where("state_id IN (?)", state_id_array).uniq
It works, but it's very ugly. Is there a better way for achieving this? Ideally a one line query.
UPDATE
Now I have
Centre.where('centres.state_id IN (?)', Centre.select('state_id').joins(:user).where('users.id=(?)', user))
The subquery work by itself, but when I tried to execute the entire query, I get NULL for the inner query.
Centre.select('state_id').joins(:user).where('users.id=(?)', user)
will generate
SELECT state_id FROM "centres" INNER JOIN "centres_users" ON "centres_users"."centre_id" = "centres"."id" INNER JOIN "users" ON "users"."id" = "centres_users"."user_id" WHERE (users.id = (5))
Which return 'SA', 'VIC', 'VIC'
but the whole query will generate
SELECT DISTINCT "centres".* FROM "centres" WHERE (centres.state_id IN (NULL,NULL,NULL))
Does user also has state_id column if yes then try this,
User.joins("LEFT OUTER JOIN users ON users.state_id = centers.state_id")
else
try User.joins(:center)
Solved.
.select(:state_id)
will retrieve a model with only the state_id column populated. To retrieve a field, use
.pluck(:state_id)
Below is the final query I had
Centre.where('centres.state_id IN (?)', Centre.joins(:user).where('users.id=(?)', user).pluck('state_id').uniq)

Rails activerecord inner join a custom object

I have classes:
class Want < ActiveRecord::Base
has_many :cached_buy_offers, dependent: :destroy
end
class CachedBuyOffer < ActiveRecord::Base
belongs_to :want
end
So, I can do
Want.joins(:cached_buy_offers)
which works as expected.
I want to generate following sql:
select * from wants
inner join
(select cached_buy_offers.want_id, max(buy_offer_cents) as max_buy_offer, count(cached_buy_offers.want_id) as buy_offer_count
from cached_buy_offers
where cached_buy_offers.want_id in (1,2,3,4)
group by cached_buy_offers.want_id
order by max_buy_offer) as cached_buy_offers
on cached_buy_offers.want_id = wants.id
Inner sql query can be generated using:
ids = [1,2,3,4]
CachedBuyOffer.select('cached_buy_offers.want_id, max(buy_offer_cents) as max_buy_offer, count(cached_buy_offers.want_id) as buy_offer_count').where('cached_buy_offers.want_id in (?)',ids).group('cached_buy_offers.want_id').order('max_buy_offer')
But when I try to do this:
Want.joins(CachedBuyOffer.select ..... the above activerecord inner query)
throws an error RuntimeError: unknown class: CachedBuyOffer
How can I generate the required sql?
You can use Arel.sql.
ids = [1,2,3,4]
cached_buy_offer_subquery = CachedBuyOffer
.select('cached_buy_offers.want_id,
max(buy_offer_cents) as max_buy_offer,
count(cached_buy_offers.want_id) as buy_offer_count')
.where('cached_buy_offers.want_id in (?)',ids)
.group('cached_buy_offers.want_id')
.order('max_buy_offer').to_sql
Want.joins("INNER JOIN (#{Arel.sql(cached_buy_offer_subquery)}) cached_buy_offers ON cached_buy_offers.want_id = wants.id")
.joins takes an association key as an argument, such as Want.joins(:cached_buy_offer). You can chain queries off of that!

Custom select on join table

I'm using rails 3.2, and trying to use ActiveRecord to query my database.
I have 2 activerecord models, Admin and Order:
class Admin < ActiveRecord::Base
attr_accessible :email, :name
has_many :orders
class Order < ActiveRecord::Base
attr_accessible :operation
belongs_to :admin
In my case, order.operation is a string, representing order type. I'm trying to build a query giving me three columns:
admin.name, admin.orders.where(:operation => 'bonus').count, admin.orders.where(:operation => 'gift').count
Is there a way to fit it in a single query?
EDITED:
here's raw sql to get what I need:
SELECT t_a.admin_id_com, t_a.name, gb_f_f_gb_f_fi.bonus_count, gb_f_f_gb_f_fi.gifts_count
FROM (
SELECT f_fil.admin_id_gifts, f_fil.gifts_count, f_filt.admin_id_bonus, f_filt.bonus_count
FROM (
SELECT admin_id as admin_id_gifts, count(distinct id) as gifts_count FROM orders WHERE operation = 'new gifts!' GROUP BY admin_id_gifts)
f_fil LEFT OUTER JOIN (
SELECT admin_id as admin_id_bonus, count(distinct id) as bonus_count FROM orders WHERE operation = 'new bonuses!' GROUP BY admin_id_bonus)
f_filt ON (f_fil.admin_id_gifts = f_filt.admin_id_bonus))
gb_f_f_gb_f_fi LEFT OUTER JOIN (
SELECT id AS admin_id_com, t_ad.name FROM admins t_ad) t_a ON (gb_f_f_gb_f_fi.admin_id_gifts = t_a.admin_id_com)
Is it possible to buid a query like that using ActiveRecord?
Try this:
#admins = Admin.joins(:orders).
select("admins.id, admins.name, orders.id,
SUM((orders.operation = 'bonus')::integer) AS bonus_count,
SUM((orders.operation = 'gift')::integer) AS gift_count ").
group("admins.id ")
# access each columns as
admin.name, admin.bonus_count, admin.gift_count
Other option is to use eager loading, it will use two queries but might be faster
#admins = Admin.includes(:orders)
# in admin.rb
def orders_count(type)
# Don't use where here as it would make a separate query instead of using eager loading records
orders.select{|x| x.operation == type}.count
end
# access each columns as
admin.name, admin.orders_count("bonus"), admin.orders_count("gift")