rails group by to make nested table - ruby-on-rails-3

I have product and stocks. Each stock contains product_id, color_id, storage_id and in_stock.
For a given product I want to group stocks by storage then colors and output in_stock like:
Storage 1
Storage 1
Color A: in_stock
Color B: in_stock
Storage 2:
Color A: in_stock
Color B: in_stock
Unfortunately I can't render the view.
So far my query is the following:
def all_units_in_stock
stocks.in_stock.group(:storage_id, :color_id).includes(:storage, :color)
end
which returns
Product.find(11).all_units_in_stock
Product Load (37.1ms) SELECT "products".* FROM "products" WHERE "products"."id" = ? LIMIT 1 [["id", 11]]
Stock Load (0.3ms) SELECT "stocks".* FROM "stocks" WHERE "stocks"."product_id" = 11 AND (in_stock > 0) GROUP BY storage_id
Storage Load (0.2ms) SELECT "storages".* FROM "storages" WHERE "storages"."id" IN (1, 2, 3, 4, 5)
Color Load (0.2ms) SELECT "colors".* FROM "colors" WHERE "colors"."id" IN (3)
=> [#<Stock id: 162, product_id: 11, storage_id: 1, in_stock: 10.0, created_at: "2011-11-07 22:54:50", updated_at: "2011-11-07 22:54:50", color_id: 3>, #<Stock id: 163, product_id: 11, storage_id: 2, in_stock: 10.0, created_at: "2011-11-07 22:54:50", updated_at: "2011-11-07 22:54:50", color_id: 3>

Order by storage, then color. Then use the group_by function on the results, this will create an ordered hash with the Storage Id as the key. Because you ordered by storage & color, the ordering will be maintained in the hash.
products = Product.find(11).all_units_in_stock
#group_by_storage = products.group_by {|p| p.storage_id }
Now in your view you can iterate through the hash.
<% #group_by_storage.each do |key, value| %>
<ul>
<li><%= key %>
<ul>
<li><%= value.color_id %>
</ul>
</li>
<ul>
Please excuse the erb syntax if in correct, I'm used to HAML markup.

Related

Rails add divide calculation to existing query

I have a query in which I pull all users who have invested in the portfolio, like below:
class PortfolioShareholdersQuery
def initialize(portfolio)
#portfolio = portfolio
end
def call
User.joins(:cash_transactions)
.where(cash_transactions: { to_wallet: portfolio.wallet })
.select('users.*, SUM(cash_transactions.shares_number) as total_shares_number')
.group('users.id')
end
attr_reader :portfolio
end
Is it possible to add inside of above query a line that will do the division of two numbers as below?
(user.total_shares_number / portfolio.portfolio_setup_infos.last.total_shares_sold)
# total_shares_number comes from first select
# portfolio_setup_infos is a has_many relation to portfolio
[Edit]
#models association
class User < ApplicationRecord
has_one :wallet, as: :walletable
has_many :cash_transactions, through: :wallet
end
class Portfolio < ApplicationRecord
has_one :wallet, as: :walletable
end
class Wallet < ApplicationRecord
belongs_to :walletable, polymorphic: true
has_many :cash_transactions
end
class CashTransaction < ApplicationRecord
belongs_to :wallet
belongs_to :to_wallet, class_name: 'Wallet', optional: true
end
[Edit 2]
> divisor = portfolio.portfolio_setup_infos.last.total_shares_sold
PortfolioSetupInfo Load (0.5ms) SELECT "portfolio_setup_infos".* FROM "portfolio_setup_infos" WHERE "portfolio_setup_infos"."portfolio_id" = $1 ORDER BY "portfolio_setup_infos"."id" DESC LIMIT $2 [["portfolio_id", 6], ["LIMIT", 1]]
=> 263
> shareholders = User.joins(:cash_transactions).where(cash_transactions: { to_wallet: portfolio.wallet }).select("users.*, SUM(cash_transactions.shares_number) as total_shares_number, (total_shares_number/#{divisor}").group('users.id')
Wallet Load (0.6ms) SELECT "wallets".* FROM "wallets" INNER JOIN "spv_setups" ON "wallets"."walletable_id" = "spv_setups"."id" WHERE "spv_setups"."portfolio_id" = $1 AND "wallets"."walletable_type" = $2 LIMIT $3 [["portfolio_id", 6], ["walletable_type", "SpvSetup"], ["LIMIT", 1]]
User Load (3.3ms) SELECT users.*, SUM(cash_transactions.shares_number) as total_shares_number, (total_shares_number/263 FROM "users" INNER JOIN "wallets" ON "wallets"."walletable_type" = $1 AND "wallets"."walletable_id" = "users"."id" INNER JOIN "cash_transactions" ON "cash_transactions"."wallet_id" = "wallets"."id" WHERE "cash_transactions"."to_wallet_id" = $2 GROUP BY "users"."id" /* loading for inspect */ LIMIT $3 [["walletable_type", "User"], ["to_wallet_id", 8], ["LIMIT", 11]]
Traceback (most recent call last):
ActiveRecord::StatementInvalid (PG::SyntaxError: ERROR: syntax error at or near "FROM")
LINE 1: ... as total_shares_number, (total_shares_number/263 FROM "user...
^
class Portfolio < ApplicationRecord
def divisor
portfolio_setup_infos.last.total_shares_sold
end
end
class PortfolioShareholdersQuery
def initialize(portfolio)
#portfolio = portfolio
#divisor = portfolio.divisor.to_f
end
def call
select =<<-SEL.squish
users.*,
SUM(cash_transactions.shares_number) as total_shares_number,
(SUM(cash_transactions.shares_number)/#{divisor}) as ratio
SEL
User.joins(:cash_transactions)
.where(cash_transactions: { to_wallet: portfolio.wallet })
.select(select)
.group('users.id')
end
attr_reader :portfolio, :divisor
end

Creating sale.order.line from web service (IMPORTING) ? Odoo V9e

If there is only one product in memo_data['items'] the sale order line will be created but if there are two or more products it will be reefused with an error: Too many values to unpack.
Here is the code:
for item in memo_data['items']:
c_id = self.env['sale.order.line'].search([('creditmemo_id','=',memo_data['creditmemo_id'])])
if not c_id:
_logger.info("THIS IS CID: %s" % memo_data['creditmemo_id'])
a_product = self.env['product.product'].search([('default_code', '=',item['sku'])])
if a_product:
_logger.info("FOUND THE PRODUCT with id: %s" %a_product.id)
pos_price = float(item['row_total'])
neg_price = pos_price - (2 * pos_price)
res12 = {
'order_id': is_exist.id,
'product_id': a_product.id,
'price_unit': neg_price,
'sequence': 10,
'product_uom_qty': float(item['qty']),
'creditmemo_id' : memo_data['creditmemo_id'],
'creditmemo_date': memo_data['created_at'],
'creditmemo_increment_id' : memo_data['increment_id'],
}
self.env['sale.order.line'].create(res12)`enter code here`
i assume you are trying to loop the list of records. but inside loop try to change memo_data to item:
for item in memo_data['items']:
c_id = self.env['sale.order.line'].search([('creditmemo_id','=',item['creditmemo_id'])])
if not c_id:
_logger.info("THIS IS CID: %s" % item['creditmemo_id'])
a_product = self.env['product.product'].search([('default_code', '=',item['sku'])])
if a_product:
_logger.info("FOUND THE PRODUCT with id: %s" %a_product.id)
pos_price = float(item['row_total'])
neg_price = pos_price - (2 * pos_price)
res12 = {
'order_id': is_exist.id,
'product_id': a_product.id,
'price_unit': neg_price,
'sequence': 10,
'product_uom_qty': float(item['qty']),
'creditmemo_id' : item['creditmemo_id'],
'creditmemo_date': item['created_at'],
'creditmemo_increment_id' : item['increment_id'],
}
I think you need to handle your dict like this
for field, possible_values in fields.iteritems():
print field, possible_values
python 2.7 : iteritems()
python 3: items().
Thanks

Group by Week and Count Column Values in Rails

Looking to do a simple-ish SQL query in rails (using Active Record) and I'm running into some trouble.
I want to return a JSON response so the client can consume the following data structure:
{"2014-12-01-2014-12-07": {
"foo": 100,
"bar": 50,
"baz": 20,
"blah": 10,
},
"2014-12-08-2014-12-14": {
"foo": 40,
"bar": 550,
"baz": 210,
"blah": 10,
}
}
Where foo, bar, baz, blah etc. are possible values available in an array on the Foo model (from Foo::PossibleStates). I want to return summed counts of each type per week. I know roughly how I'd go about this in Mongo (the world I'm more familiar with), but am running into trouble with the nuances in SQL and Rails/Active Record. Any direction would be greatly appreciated! Here's what I've tried so far:
class FooController < ApplicationController
def index
start_date = params[:start_date]
end_date = params[:end_date]
#jsonFooData = Foo.group('created_at').group("workflow_state").sum('workflow_state')
end
end
I think it is not an easy task.
If you are using postgres, it could be achieved by one sql and a loop:
# models/foo.rb
# call this Foo.count_by_week in your controller
def self.count_by_week
raw_result = group_by_week_and_state.count
# {['2014-12-01-2014-12-07', 'foo'] => 100, ['2014-12-01-2014-12-07', 'bar'] => 100, '...' => '...'}
raw_result.each_with_object({}) do |(k, v), result|
result[k[0]] ||= {}
result[k[0]][k[1]] = v
end
end
def self.group_by_week_and_state
group("#{weekday_query(0)} || \'-\' || #{weekday_query(6)}").group('workflow_state')
end
# build sql part for day offset of week (0 => mon, 6 => sun)
def self.weekday_query(offset)
"to_char(cast(date_trunc(\'week\', created_at) as date) + #{offset}, \'YYYY-MM-DD\')"
end
results = Hash.new 0
counts = Hash.new 0
# Get the results 'foos' with all the foo records grouped by beginning of the week. I'm using 'Monday' here but can be changed to any other weekday.
foos = Foo.all.group_by{|f| f.created_at.beginning_of_week(:monday)}
# loop through each grouped item
foos.collect do |foo|
# collect the workflow states and loop through them and count the unique workflow states.
foo[1].collect{|f| f.workflow_state}.each do |ws|
counts[ws] += 1
end
# assign the workflow states counts to the beginning of the week
results[foo[0]] = counts
# reset the counter to new hash
counts = Hash.new 0
end
# output the results
puts results

Update Attributes For Nested Forms

Hello I'm creating an online retail store.
I have a Category model and a Sizes model. They are nested in a form. When I create a Category I also create sizes for that category.
Right now I can create a Category and sizes. However I can't update the sizes in the nested form.
So click edit category and change the name of one size then click update. I get the below error. On screen it just says "has already been taken".
How do I update sizes through this nested form?
/Users/Documents/Safsy/Website/Safsy/Safsy/app/controllers/categories_controller.rb # line 40 CategoriesController#update:
39: def update
=> 40: binding.pry
41: if #category.update(category_params)
42: redirect_to #category
43: flash[:success] = 'Category was successfully updated.'
44: else
45: render "edit"
46: end
47: end
[1] pry(#<CategoriesController>)>
Unpermitted parameters: _destroy, id
Unpermitted parameters: _destroy, id
Unpermitted parameters: _destroy, id
Unpermitted parameters: _destroy, id
Unpermitted parameters: _destroy, id
Unpermitted parameters: _destroy, id
Unpermitted parameters: _destroy, id
Unpermitted parameter: _destroy
Unpermitted parameter: _destroy
Unpermitted parameter: _destroy
Unpermitted parameter: _destroy
Unpermitted parameter: _destroy
Unpermitted parameter: _destroy
Unpermitted parameter: _destroy
(0.1ms) begin transaction
Category Load (0.1ms) SELECT "categories".* FROM "categories" WHERE "categories"."id" = ? LIMIT 1 [["id", 64]]
Size Exists (0.1ms) SELECT 1 AS one FROM "sizes" WHERE "sizes"."title" = 'XSmall' LIMIT 1
Size Exists (0.1ms) SELECT 1 AS one FROM "sizes" WHERE "sizes"."title" = 'Small' LIMIT 1
Size Exists (0.1ms) SELECT 1 AS one FROM "sizes" WHERE "sizes"."title" = 'Medium' LIMIT 1
Size Exists (0.1ms) SELECT 1 AS one FROM "sizes" WHERE "sizes"."title" = 'Large' LIMIT 1
Size Exists (0.1ms) SELECT 1 AS one FROM "sizes" WHERE "sizes"."title" = 'XL' LIMIT 1
Size Exists (0.1ms) SELECT 1 AS one FROM "sizes" WHERE "sizes"."title" = 'XXL' LIMIT 1
Size Exists (0.1ms) SELECT 1 AS one FROM "sizes" WHERE "sizes"."title" = 'XXXL' LIMIT 1
CACHE (0.0ms) SELECT 1 AS one FROM "sizes" WHERE "sizes"."title" = 'XSmall' LIMIT 1
CACHE (0.0ms) SELECT 1 AS one FROM "sizes" WHERE "sizes"."title" = 'Small' LIMIT 1
CACHE (0.0ms) SELECT 1 AS one FROM "sizes" WHERE "sizes"."title" = 'Medium' LIMIT 1
CACHE (0.0ms) SELECT 1 AS one FROM "sizes" WHERE "sizes"."title" = 'Large' LIMIT 1
CACHE (0.0ms) SELECT 1 AS one FROM "sizes" WHERE "sizes"."title" = 'XL' LIMIT 1
CACHE (0.0ms) SELECT 1 AS one FROM "sizes" WHERE "sizes"."title" = 'XXL' LIMIT 1
Size Exists (0.1ms) SELECT 1 AS one FROM "sizes" WHERE "sizes"."title" = 'XXXL test' LIMIT 1
Category Exists (0.1ms) SELECT 1 AS one FROM "categories" WHERE ("categories"."name" = 'Shorts' AND "categories"."id" != 68) LIMIT 1
(0.1ms) rollback transaction
Category Model:
class Category < ActiveRecord::Base
has_ancestry
has_many :products
has_many :sizes
validates :name, presence: true, length: { maximum: 20 }, uniqueness: true
accepts_nested_attributes_for :sizes, allow_destroy: true
end
Sizes Model:
class Size < ActiveRecord::Base
validates :title, presence: true, length: { maximum: 15 }
validates :title, uniqueness: true
belongs_to :category
end
Category controller:
class CategoriesController < ApplicationController
before_action :set_category, only: [:show, :edit, :update]
before_action :admin_user, only: [:destroy, :index, :edit, :show]
def index
#categories = Category.all
end
def show
#tags = Product.where(category_id: #category.id).tag_counts_on(:tags)
if params[:tag]
#products = Product.tagged_with(params[:tag])
else
#products = Product.where(category_id: #category.id).order("created_at DESC")
end
end
def new
#category = Category.new
3.times do
#category.sizes.build
end
end
def edit
end
def create
#category = Category.new(category_params)
if #category.save
redirect_to #category
flash[:success] = "You have created a new category"
else
flash[:danger] = "Your category didn't save"
render "new"
end
end
def update
binding.pry
if #category.update(category_params)
redirect_to #category
flash[:success] = 'Category was successfully updated.'
else
render "edit"
end
end
def destroy
category = Category.find(params[:id])
category.sizes.destroy_all
category.destroy
flash[:success] = "Category deleted"
redirect_to categories_path
end
private
def set_category
#category = Category.find(params[:id])
end
def category_params
params.require(:category).permit(:name, :parent_id, size_ids: [], sizes_attributes: [:title])
end
end
Sizes Controller:
class SizesController < ApplicationController
before_action :logged_in_user, only: [:create, :index, :destroy, :update]
before_action :admin_user, only: [:create, :index, :destroy, :update]
def create
#size = Size.create(size_params)
end
def index
#sizes = Size.all
end
def destroy
Size.find(params[:id]).destroy
end
def update
#size.update_attributes(size_params)
end
private
def size_params
params.require(:size).permit(:title, :category_id)
end
end
Here are the params at Category Update method:
39: def update
=> 40: binding.pry
41: if #category.update(category_params)
42: redirect_to #category
43: flash[:success] = 'Category was successfully updated.'
44: else
45: render "edit"
46: end
47: end
[1] pry(#<CategoriesController>)> params
=> {"utf8"=>"✓",
"_method"=>"patch",
"authenticity_token"=>"ZHuWURBwIctFJLgZ1HGNeKvGpK8LlgH9S6Mrh7No/CFdjtwNFoNtfi9NIVHBVhApYH/U5tuOzV0eqhSq/95SCw==",
"category"=>
{"name"=>"Shorts",
"parent_id"=>"64",
"sizes_attributes"=>
{"0"=>{"title"=>"XSmall", "_destroy"=>"false", "id"=>"21"},
"1"=>{"title"=>"Small", "_destroy"=>"false", "id"=>"22"},
"2"=>{"title"=>"Medium", "_destroy"=>"false", "id"=>"23"},
"3"=>{"title"=>"Large", "_destroy"=>"false", "id"=>"24"},
"4"=>{"title"=>"XL", "_destroy"=>"false", "id"=>"25"},
"5"=>{"title"=>"XXL", "_destroy"=>"false", "id"=>"26"},
"6"=>{"title"=>"XXXL test", "_destroy"=>"false", "id"=>"27"}}},
"commit"=>"Update Category",
"controller"=>"categories",
"action"=>"update",
"id"=>"68"}
Permit _destroy and id of nested form like this:-
def category_params
params.require(:category).permit(:name, :parent_id, size_ids: [], sizes_attributes: [:id, :title, :_destroy])
end

What is the proper way to use includes (Rails 4, ActiveRecord, PostgreSQL)

I have the following Models:
Moon -> Planet -> Star -> Galaxy -> Universe
Moon belongs_to Planet, Planet belongs_to Star and so on:
class Moon < ActiveRecord::Base
belongs_to :planet, inverse_of: :moons
has_one :star, through: :planet
has_one :galaxy, through: :star
has_one :universe, through: :galaxy
end
class Planet < ActiveRecord::Base
belongs_to :star, inverse_of: :planets
has_many :moons, inverse_of: :planet, dependent: :destroy
end
The Problem
I'm trying to get Rails to load a hierarchy of these objects to memory. I'm trying to achieve 2 things:
Load everything using one efficient query
Be able to use calls such as moon.galaxy and not moon.planet.star.galaxy without additional calls to the database.
I tried two approaches. The first (m1) is calling includes on Moon with all the relations flatten. This results in a very inefficient query but I can call m1.galaxy. The second (m2) is calling includes with the hierarchy of relations. This results in an efficient query, but I can call m2.galaxy without going to the database.
What is the proper way to do that?
Example
m1 - inefficient query, m2 - efficient query
irb(main):152:0* m1 = Moon.includes(:planet, :star, :galaxy).where(galaxies: {name: 'The Milky Way'}).first
SQL (1.9ms) SELECT "moons"."id" AS t0_r0, "moons"."name" AS t0_r1, "moons"."planet_id" AS t0_r2, "moons"."created_at" AS t0_r3, "moons"."updated_at" AS t0_r4, "planets"."id" AS t1_r0, "planets"."name" AS t1_r1, "planets"."star_id" AS t1_r2, "planets"."created_at" AS t1_r3, "planets"."updated_at" AS t1_r4, "stars"."id" AS t2_r0, "stars"."name" AS t2_r1, "stars"."galaxy_id" AS t2_r2, "stars"."created_at" AS t2_r3, "stars"."updated_at" AS t2_r4, "galaxies"."id" AS t3_r0, "galaxies"."name" AS t3_r1, "galaxies"."universe_id" AS t3_r2, "galaxies"."created_at" AS t3_r3, "galaxies"."updated_at" AS t3_r4 FROM "moons" LEFT OUTER JOIN "planets" ON "planets"."id" = "moons"."planet_id" LEFT OUTER JOIN "planets" "planets_moons_join" ON "planets_moons_join"."id" = "moons"."planet_id" LEFT OUTER JOIN "stars" ON "stars"."id" = "planets_moons_join"."star_id" LEFT OUTER JOIN "planets" "planets_moons_join_2" ON "planets_moons_join_2"."id" = "moons"."planet_id" LEFT OUTER JOIN "stars" "stars_moons_join" ON "stars_moons_join"."id" = "planets_moons_join_2"."star_id" LEFT OUTER JOIN "galaxies" ON "galaxies"."id" = "stars_moons_join"."galaxy_id" WHERE "galaxies"."name" = $1 ORDER BY "moons"."id" ASC LIMIT 1 [["name", "The Milky Way"]]
=> #<Moon id: 1, name: "The Moon", planet_id: 1, created_at: "2015-10-28 10:02:04", updated_at: "2015-10-28 10:02:04">
irb(main):153:0> m2 = Moon.includes(planet: {star: :galaxy}).where(galaxies: {name: 'The Milky Way'}).first
SQL (0.5ms) SELECT "moons"."id" AS t0_r0, "moons"."name" AS t0_r1, "moons"."planet_id" AS t0_r2, "moons"."created_at" AS t0_r3, "moons"."updated_at" AS t0_r4, "planets"."id" AS t1_r0, "planets"."name" AS t1_r1, "planets"."star_id" AS t1_r2, "planets"."created_at" AS t1_r3, "planets"."updated_at" AS t1_r4, "stars"."id" AS t2_r0, "stars"."name" AS t2_r1, "stars"."galaxy_id" AS t2_r2, "stars"."created_at" AS t2_r3, "stars"."updated_at" AS t2_r4, "galaxies"."id" AS t3_r0, "galaxies"."name" AS t3_r1, "galaxies"."universe_id" AS t3_r2, "galaxies"."created_at" AS t3_r3, "galaxies"."updated_at" AS t3_r4 FROM "moons" LEFT OUTER JOIN "planets" ON "planets"."id" = "moons"."planet_id" LEFT OUTER JOIN "stars" ON "stars"."id" = "planets"."star_id" LEFT OUTER JOIN "galaxies" ON "galaxies"."id" = "stars"."galaxy_id" WHERE "galaxies"."name" = $1 ORDER BY "moons"."id" ASC LIMIT 1 [["name", "The Milky Way"]]
=> #<Moon id: 1, name: "The Moon", planet_id: 1, created_at: "2015-10-28 10:02:04", updated_at: "2015-10-28 10:02:04">
m1 - I can call moon.galaxy.name in memory, m2 - moon.galaxy.name calls DB
irb(main):154:0> m1.galaxy.name
=> "The Milky Way"
irb(main):155:0> m2.galaxy.name
Galaxy Load (0.5ms) SELECT "galaxies".* FROM "galaxies" INNER JOIN "stars" ON "galaxies"."id" = "stars"."galaxy_id" INNER JOIN "planets" ON "stars"."id" = "planets"."star_id" WHERE "planets"."id" = $1 LIMIT 1 [["id", 1]]
=> "The Milky Way"
m1 - moon.planet.star.galaxy.name calls DB, m2 - I can call moon.planet.star.galaxy.name in memory
irb(main):156:0> m1.planet.star.galaxy.name
Star Load (3.3ms) SELECT "stars".* FROM "stars" WHERE "stars"."id" = $1 LIMIT 1 [["id", 1]]
Galaxy Load (0.3ms) SELECT "galaxies".* FROM "galaxies" WHERE "galaxies"."id" = $1 LIMIT 1 [["id", 1]]
=> "The Milky Way"
irb(main):157:0> m2.planet.star.galaxy.name
=> "The Milky Way"
A Diff of the Queries
Hm, I think your second way (m2) is correct. Bay also consider eager_load because includes may trigger several SQL queries instead of one for each association (planet, start etc).
I think your problem in these incorrect associations star, galaxy and universe.
class Moon < ActiveRecord::Base
belongs_to :planet, inverse_of: :moons
has_one :star, through: :planet
has_one :galaxy, through: :star
has_one :universe, through: :galaxy
end
Just remove them and try again.
Please notice you can't use has_one such way (example in documentation)