Groups and subgroups in Rails - ruby-on-rails-3

I'm currently sorting products based on release date this way:
<% #product_months.each do |month, products| %>
<h2><%= month.strftime("%B %Y") %></h2>
<% products.each do |product| %>
<p><b><%= product.title %></b> on <%= product.street_date.to_date.to_s(:long) %></p>
<% end %>
<% end %>
In the controller:
#products = Product.where('street_date > ?', Date.today).order('street_date ASC')
#product_months = #products.group_by { |t| t.street_date.beginning_of_month }
Products, though, are also grouped into three sales_seaons, spring, summer and fall. I'd like to further group the items into their seaons, like this. Year > Season > Product, instead of Month > Product, but the grouping is proving a little too complicated for me to get. Any thoughts?

Scopes are your friend. They add class level methods to the model they're defined in. The best part about them is that they're chainable.
There are many ways to get the groupings you're talking about but here's what I would start with.
class Product
# ... your code here
scope :by_sales_season, lambda {|season| where('sales_season = ?', season}
scope :by_year, lambda {|year| where('street_date >= ?' DateTime.now(year) }
# ... more of your code
end
Then in your controller you could:
#products = Product.by_year(2012).by_sales_season("spring")
Of course this will not return to all products across every year and/or every season, though the scopes could be modified to do such a thing, or you could loop over the years and seasons accordingly. However, if you do it this way versus using the group_by method which is an Enumerable method then you don't risk pulling the entire result set into memory when you only want a subset of it.

Related

Query model for all possible values of a column - Ruby Rails [duplicate]

How can I get unique values from column in the table?
For example, I have this Products table:
ID NAME CATEGORY
1 name1 1st_cat
2 name2 2nd_cat
3 name3 1st_cat
Here I want to get only 2 values - 1st_cat and 2nd_cat:
<%Products.each do |p|%>
<%=p.category%>
<%end%>
Two more ways:
Product.select(:category).map(&:category).uniq # Ruby does the work
Product.uniq.pluck(:category) # DB does the work (superior)
For Rails >= 5.1 use:
Product.distinct.pluck(:category) # DB does the work (superior)
...because Relation#uniq was deprecated.
I think you can do this:
<% Products.select("DISTINCT(CATEGORY)").each do |p| %>
<%= p.category %>
<% end %>
Source: http://guides.rubyonrails.org/active_record_querying.html#selecting-specific-fields
This does all the work in the database server. The result is a simple array.
<% Product.distinct(:category).pluck(:category).each do |category|
<%= category %>
<% end %>
Rails will generate SQL that works on any database (Postgres, MySQL, etc).
SELECT DISTINCT "products"."category" FROM "products"
I suggest to use Products.all.distinct.pluck(:category) because uniq has been deprecated since rails 5 and it will be removed on rails 5.1
Try this (in the rails console)
Product.group(:category)
Product.group(:category).each { |p| p.name }
For postgres
<% Product.select("DISTINCT ON (category) *").each do |category|
<%= category %>
<%= name %>
<% end %>
Update
even better
<% Product.select(%(DISTINCT ON (category) "#{Product.table_name}".*)).each do |category|
<%= category %>
<%= name %>
<% end %>
because it can return wrong columns when you do joins (e.g. returns id column from joined table, but not products)
If you or anyone want to get two or more attributes from a table like products, based on a distinct feature of an attribute, only this solution will help you for Rails >= 5.1
distinct_products = Product.select("DISTINCT ON (category) *")
# it's an active record relation class.
> distinct_products.class
=> Product::ActiveRecord_Relation
N.B. Don't use .pluck() on the distinct_products. It will reselect from the products table and the distinct feature will not work anymore.
Needed to get unique output and was trying the 'uniq' method unsuccessfully. Tried several solutions posted here unsuccessfully. I'm using devise which gives me access to the current_user method and working with two tables, one being a join (an item has_many :things).
This solution ultimately worked for me :
#current_user.things.select(:item_fk).distinct.each do |thing|
<%= thing.item.attribute %>
<% end %>

Rails form select with NULL (no choice) support

How can I add a NULL option to my form select? I have a table:
categories
id
category_id
name
If I'm creating a new category, I want to be able to select the NO_CATEGORY option (NULL value and id).
My view code:
<%= f.collection_select :supercategory_id, Category.all, :id , :name %>
Also, it is a good idea? Isn't it better to have some predefined ROOT category in the database? Thank you.
Try:
<%= f.collection_select :supercategory_id, Category.all, :id , :name, :include_blank => true %>
Its ok to have null. Just have your model logic know that it should create a new category and assign it rather than mass assign from the select. Might be something that happens in a before_validation method

How to group collection by columns with rails

My table named stocks contains product_id, color_id, storage_id and in_stock as columns.
For a given product I want to group all stocks by storage, then for each storage I want to show product (color): in_stock
How should I write a method and how to render?
<% Stock.all.group_by(&:storage).each do |storage, products| %>
Storage: <%= storage %>
<% products.each do |product| %>
(<%= product.color_id %>): <%= product.in_stock %>
<% end %>
<% end %>
Edit: updated to use ERB, and in_stock as a number and not a boolean yes/no column, and use color_id in place of color.name because that assumes you have a relationship to a color.
Not structured exactly how you need it (not enough info from your post), but something like this perhaps?
items = Stock.select('color_id, in_stock').group('stocks.storage_id').where(:product_id => foo)
items.each {|item| puts "(#{lookup_color(item.color_id)}): #{in_stock}"

Limiting the SQL queries generated by Rails when accessing the data from multiple tables on one page

I have many items and many users in a database. A user can set a status to the various items. The three tables are (they have been simplified in this explanation) users, statuses, items. The models are shown below.
class Item < ActiveRecord::Base
has_many :statuses
end
class Status < ActiveRecord::Base
belongs_to :user
belongs_to :item
end
class User < ActiveRecord::Base
has_many :statuses
end
The controller
class ItemController < ApplicationController
def index
#items = Item.all
end
end
The view (this has been simplified, in my code I actually generate a form for the status so the user can create/modify their status but in the example below I'm just making as if the status is being printed out as text):
<th>Item ID</th>
<th>Item Status</th>
...
<% #items.each do |item| %>
<td><%= item.id %></td>
<% status = item.statuses.where(:user_id => current_user.id) %>
<td><%= status.first.status %></td>
<% end %>
Here are the queries generated by Rails:
Item Load (0.3ms) SELECT `items`.* FROM `items` ORDER BY id asc
SQL (0.2ms) SELECT COUNT(*) FROM `statuses` WHERE (`statuses`.item_id = 1) AND (`statuses`.`user_id` = 103)
SQL (0.2ms) SELECT COUNT(*) FROM `statuses` WHERE (`statuses`.item_id = 2) AND (`statuses`.`user_id` = 103)
Status Load (0.1ms) SELECT `statuses`.* FROM `statuses` WHERE (`statuses`.item_id = 2) AND (`statuses`.`user_id` = 103) LIMIT 1
SQL (0.2ms) SELECT COUNT(*) FROM `statuses` WHERE (`statuses`.item_id = 3) AND (`statuses`.`user_id` = 103)
All the items are selected in one SQL query which I think is good. Then it seems the SELECT COUNT (*) query is executed, if a status is found then that status is fetched using another SQL query, this happens for each item.
Guessing there is a much better way of going about this, when I have a few hundred items the number of SQL queries being carried out is huge! If anyone has any tips for how they would go about this I'd be interested to hear.
Thanks.
The solution is to include the statuses when you build the items.
#items = Item.includes(:statuses).all
And then use a select method to return the relevant status.
item.statuses.select {|s| s.user_id => current_user.id}
The rails magic sometimes results in these crazy queries, since item.statuses can be interpreted as either an array of status objects, or an ActiveRecord Relation.
When you call the where method on item.statuses, it defaults to ActiveRecord Relation behaviour, and builds a new SQL query. If you call select, item.statuses behaves as an array.
This is also the case for methods like sum. Very annoying!

how to get x most recent records from two tables at once

I have two tables: one for posts and one for topics:
posts: id, topic_id, content, created_at, updated_at
topics: id, title, content, created_at, updated_at
I want to retrieve x most recent records from both of them based on updated_at column. So in the end I expect to have them in ONE collection consisted of BOTH Post and Topic type objects. I hope I made myself clear.
Looks like I haven't made myself clear enough in the end. But in meantime I came up with solution. It's not perfect but it works.
def recent_activities(count: 10)
posts = Post.order(updated_at: :desc).limit(count)
topics = Topic.order(updated_at: :desc).limit(count)
activities = posts + topics
activities.sort! { |x, y| y.updated_at <=> x.updated_at }
activities.first(count)
end
So, as you can see post.topic_id and topic.id association is not relevant. There are two tables which just happens to both have updated_at columns. I need recent X records from group consisted of posts and topics - in snipped above called activities.
since you already have the associations, why not just:
<% Topic.order(:created_at => :asc).limit(10) do |topic| %>
<%= topic.title %>
<%= topic.content %>
<%= topic.post.content %>
<%= topic.created_at %>
<%= topic.post.created_at %>
<% end %>