Rails how to group_by data with conditions? - sql

I have two tables , book:
book:
id
book_name
brand_id
belongs_to :brand, foreign_key: "brand_id"
and brand:
brand:
id
brand_name
has_many :books
I want to group data from book and get results like following:
id brand_name count
1 b1 20
2 b2 32
and display on view, use params to get values and insert into collection_select like:
<%= collection_select('', :brand_id, #brands , #brands.id, #brands.name + '(' + #brands.count + ')' , {:prompt => 'please select!'} ) %><br>
I want the dropdown list looks like :
b1(20)
b2(32)
how can I do that?
I tried #brands = Book.group(:brand_id).count
but it's only shows:
'1':20
'2':32
I don't know how to get value via params, and there is no brand_name, please give me some suggestions!

One of possible solutions to your problem is:
#brands = Brand.joins(:books).select("brands.id, brands.name, COUNT(books.id) as cnt").group("brands.id, brands.brand_name")
Now you can convert it to collection, which you can be used by select helper:
collection = #brands.map{|b| [ "#{b.name} (#{b.cnt})", b.id ] }
and use it in your form:
select 'book', 'brand_id', collection

Hey you can try this way. Just sure with your model name and table name in query. It returns you a active record array then you can manage in according to your collection select hash:
brands = Book.joins(:brand).select("brands.id as id, brands.name as brand_name,count(*) as count").group(:brand_id)
You can create your required array by
#brands = brands.collect{|o| ["#{o.brand_name}(#{o.count})",o.id]}
After that you can passed this array to select tag as:
<%= select_tag 'brand_id' , options_for_select(#brands) %>

Related

globalize3 - Query translated attribute

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!

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

Simple group by with thinking sphinx

I have an Item model with a name and user_id. I would like to search all Items and group by User so I can display each user with their items:
User 1:
Item A
Item B
User 2
Item C
User 3
Item D
Item E
Item D
...
In the console, I try this: (From the documentation)
Item.search({group_by: :user_id, limit: 50}).all
And I get this:
Sphinx Query (0.4ms)
Sphinx Caught Sphinx exception: can't dup Symbol (0 tries left)
TypeError: can't dup Symbol
from /Users/pinouchon/.rvm/gems/ruby-1.9.3-p392#gemset/gems/riddle-1.5.6/lib/riddle/client/message.rb:18:in `dup'
Same error with this:
Item.search({group_by: :user_id, order_group_by: '#count desc'}).each_with_group
Search with no group by returns results without any problem.
What's wrong ?
The quick answer: try sending through the attribute name as a string, not a symbol.
The longer answer: that query isn't going to give you the results you want – it'll return one item per user. You'd be better served sorting by user_id instead:
items = Item.search(
:order => 'user_id ASC, #weight DESC',
:sort_mode => :extended,
:limit => 50
)
From there, you could then get the layer of users grouping each items using Ruby/Rails:
items.group_by(&:user)

Writing a named scope in rails

I have three models: Products, Placements, Collections
I'm trying to write a name scope that only chooses products NOT in a certain collection.
products has_many :collections, :through => :placements
collections has_many :products, :through => :placements
I got about this far:
scope :not_in_front, joins(:collections).where('collections.id IS NOT ?', 4)
But that generated the opposite of what I expected in the query:
Product Load (0.3ms) SELECT "products".* FROM "products" INNER JOIN "placements" ON "products"."id" = "placements"."product_id" WHERE "placements"."collection_id" = 4
Any idea how to write this to only select the products not in that particular collection?
Instead of collections.id IS NOT 4 try collections.id != 4
The named scope was getting too ugly, so I went with this. Not sure it's the best way, but, it works...
def self.not_on_top_shelf
top_shelf = Collection.find_by_handle('top-shelf')
products = Product.find(:all, :order => "factor_score DESC")
not_on_top_shelf = products.map {|p| p unless p.collections.include?(top_shelf)}
not_on_top_shelf.compact #some products may not be in a collection
end

Problems with :uniq => true/Distinct option in a has_many_through association w/ named scope (Rails)

See updates at bottom of question.
I had to make some tweaks to my app to add new functionality, and my changes seem to have broken the :uniq option that was previously working perfectly.
Here's the set up:
#User.rb
has_many :products, :through => :seasons, :uniq => true
has_many :varieties, :through => :seasons, :uniq => true
has_many :seasons
#product.rb
has_many :seasons
has_many :users, :through => :seasons, :uniq => true
has_many :varieties
#season.rb
belongs_to :product
belongs_to :variety
belongs_to :user
named_scope :by_product_name, :joins => :product, :order => 'products.name'
#variety.rb
belongs_to :product
has_many :seasons
has_many :users, :through => :seasons, :uniq => true
First I want to show you the previous version of the view that is now breaking, so that we have a baseline to compare. The view below is pulling up products and varieties that belong to the user. In both versions below, I've assigned the same products/varieties to the user so the logs will looking at the exact same use case.
#user/show
<% #user.products.each do |product| %>
<%= link_to product.name, product %>
<% #user.varieties.find_all_by_product_id(product.id).each do |variety| %>
<%=h variety.name.capitalize %></p>
<% end %>
<% end %>
This works. It displays only one of each product, and then displays each product's varieties. In the log below, product ID 1 has 3 associated varieties. And product ID 43 has none.
Here's the log output for the code above:
Product Load (11.3ms) SELECT DISTINCT `products`.* FROM `products` INNER JOIN `seasons` ON `products`.id = `seasons`.product_id WHERE ((`seasons`.user_id = 1)) ORDER BY name, products.name
Product Columns (1.8ms) SHOW FIELDS FROM `products`
Variety Columns (1.9ms) SHOW FIELDS FROM `varieties`
Variety Load (0.7ms) SELECT DISTINCT `varieties`.* FROM `varieties` INNER JOIN `seasons` ON `varieties`.id = `seasons`.variety_id WHERE (`varieties`.`product_id` = 1) AND ((`seasons`.user_id = 1)) ORDER BY name
Variety Load (0.5ms) SELECT DISTINCT `varieties`.* FROM `varieties` INNER JOIN `seasons` ON `varieties`.id = `seasons`.variety_id WHERE (`varieties`.`product_id` = 43) AND ((`seasons`.user_id = 1)) ORDER BY name
Ok, so everything above is the previous version which was working great. In the new version, I added some columns to the join table called seasons, and made a bunch of custom methods that query those columns. As a result, I made the following changes to the view code that you saw above so that I could access those methods on the seasons model:
<% #user.seasons.by_product_name.each do |season| %>
<%= link_to season.product.name, season.product %>
#Note: I couldn't get this loop to work at all, so I settled for the following:
#<% #user.varieties.find_all_by_product_id(product.id).each do |variety| %>
<%=h season.variety.name.capitalize %>
<%end%>
<%end%>
Here's the log output for that:
SQL (0.9ms) SELECT count(DISTINCT "products".id) AS count_products_id FROM "products" INNER JOIN "seasons" ON "products".id = "seasons".product_id WHERE (("seasons".user_id = 1))
Season Load (1.8ms) SELECT "seasons".* FROM "seasons" INNER JOIN "products" ON "products".id = "seasons".product_id WHERE ("seasons".user_id = 1) AND ("seasons".user_id = 1) ORDER BY products.name
Product Load (0.7ms) SELECT * FROM "products" WHERE ("products"."id" = 43) ORDER BY products.name
CACHE (0.0ms) SELECT "seasons".* FROM "seasons" INNER JOIN "products" ON "products".id = "seasons".product_id WHERE ("seasons".user_id = 1) AND ("seasons".user_id = 1) ORDER BY products.name
Product Load (0.4ms) SELECT * FROM "products" WHERE ("products"."id" = 1) ORDER BY products.name
Variety Load (0.4ms) SELECT * FROM "varieties" WHERE ("varieties"."id" = 2) ORDER BY name
CACHE (0.0ms) SELECT * FROM "products" WHERE ("products"."id" = 1) ORDER BY products.name
Variety Load (0.4ms) SELECT * FROM "varieties" WHERE ("varieties"."id" = 8) ORDER BY name
CACHE (0.0ms) SELECT * FROM "products" WHERE ("products"."id" = 1) ORDER BY products.name
Variety Load (0.4ms) SELECT * FROM "varieties" WHERE ("varieties"."id" = 7) ORDER BY name
CACHE (0.0ms) SELECT * FROM "products" WHERE ("products"."id" = 43) ORDER BY products.name
CACHE (0.0ms) SELECT count(DISTINCT "products".id) AS count_products_id FROM "products" INNER JOIN "seasons" ON "products".id = "seasons".product_id WHERE (("seasons".user_id = 1))
CACHE (0.0ms) SELECT "seasons".* FROM "seasons" INNER JOIN "products" ON "products".id = "seasons".product_id WHERE ("seasons".user_id = 1) AND ("seasons".user_id = 1) ORDER BY products.name
CACHE (0.0ms) SELECT * FROM "products" WHERE ("products"."id" = 1) ORDER BY products.name
CACHE (0.0ms) SELECT * FROM "products" WHERE ("products"."id" = 1) ORDER BY products.name
CACHE (0.0ms) SELECT * FROM "varieties" WHERE ("varieties"."id" = 8) ORDER BY name
I'm having two problems:
(1) The :uniq option is not working for products. Three distinct versions of the same product are displaying on the page.
(2) The :uniq option is not working for varieties. I don't have validation set up on this yet, and if the user enters the same variety twice, it does appear on the page. In the previous working version, this was not the case.
The result I need is that only one product for any given ID displays, and all varieties associated with that ID display along with such unique product.
One thing that sticks out to me is the sql call in the most recent log output. It's adding 'count' to the distinct call. I'm not sure why it's doing that or whether it might be an indication of an issue. I found this unresolved lighthouse ticket that seems like it could potentially be related, but I'm not sure if it's the same issue: https://rails.lighthouseapp.com/projects/8994/tickets/2189-count-breaks-sqlite-has_many-through-association-collection-with-named-scope
Update
I think the problem is that the named_scope is being called once for each season. There needs to be something in the named_scope that narrows the returned products by season id.
What's happening right now is:
user = get me user
seasons = get me user's seasons (say, there are 3 seasons for the user)
products = get me the products
products += get me the products
products += get me the products
Give me each of the products
So what's happening is not that uniq is breaking, but rather than there's no delimeter on the named scope. (I think).
I tried the following, but it throws this exception: odd number list for Hash
named_scope :by_product_name, lambda { |seasons| { season_ids = seasons.map { |season| season.id }; :joins => :product, :conditions => { :seasons { :id => season_id } } :order => 'products.name' } }
Ideas?
Update #2
Ok, now I'm thinking maybe it's not the named scoped at all.
In #user/show, I just changed the loop to bypass the named scope:
<% #user.seasons.each do |season| %>
<%= link_to season.product.name, season.product %>
#Note: I couldn't get this loop to work at all, so I settled for the following:
#<% #user.varieties.find_all_by_product_id(product.id).each do |variety| %>
<%=h season.variety.name.capitalize %>
<%end%>
<%end%>
The above doesn't use the named scope, but I'm still getting the same result. In other words, I'm still seeing all instances of each product, instead of just one.
The code above that creates the first loop is the same as my original code that I listed at the top of this question. The difference is that this code is looping through seasons to hit the products, whereas my original code looped through products. This difference is where the problem is hiding, but I don't know how to fix it.
Also, I mentioned in my original question that I couldn't get the varieties loop working either. You can see the line commented in the code directly above. When looping through the seasons, instead of products, when Rails hits that varieties loop, it throws a name error:
undefined local variable or method `product'
Seems like that might be another symptom of the same problem?
Any other ideas?
I believe the issue is the formatting of the lambda. I obviously can't run the SQL, but the following lambda DOES create an apporpriate hash:
lambda { |seasons| season_ids = seasons.map { |season| season.id }; { :joins => :product, :conditions => { :seasons => { :id => season_ids } }, :order => 'products.name' } }
The output of that call with two seasons with ids 1 and 2 is:
{:joins=>:product, :conditions=>{:seasons=>{:id=>[1, 2]}}, :order=>"products.name"}