Active Record querying with joins and group by - ruby-on-rails

I'm designing an API to get data from the following scenario :
brands table :
+------------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+------------+--------------+------+-----+---------+----------------+
| id | bigint(20) | NO | PRI | NULL | auto_increment |
| name | varchar(255) | YES | | NULL | |
+------------+--------------+------+-----+---------+----------------+
items table :
+---------------------------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+---------------------------+--------------+------+-----+---------+----------------+
| id | bigint(20) | NO | PRI | NULL | auto_increment |
| category_id | bigint(20) | YES | MUL | NULL | |
| brand_id | bigint(20) | YES | | NULL | |
+---------------------------+--------------+------+-----+---------+----------------+
item_skus table :
+---------------------------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+---------------------------+--------------+------+-----+---------+----------------+
| id | bigint(20) | NO | PRI | NULL | auto_increment |
| item_id | bigint(20) | YES | MUL | NULL | |
| number_of_stock | int(11) | YES | | NULL | |
+---------------------------+--------------+------+-----+---------+----------------+
Item model association with ItemSku and Brand
belongs_to :brand
has_many :skus, class_name: 'ItemSku'
Simply i want the counts of stock available items and all items for each brand.
{
"brandCounts":[
{
"id":7006,
"name":"Brand 01",
"stockAvailableItemCount":50,
"allItemCount":60
},
{
"id":20197,
"name":"Brand 02"
"availableItemCount":150,
"allItemCount":660
}
]
}
Implementation :
brand_counts = []
brand_counts_hash = Hash.new()
items = Item.left_outer_joins(:skus).where(category_id: params[:id]).pluck(:brand_id, :number_of_stock, :item_id)
items.each do |item|
brand_id = item[0]
stock = item[1]
if brand_counts_hash.has_key?(brand_id)
item_count_arry = brand_counts_hash[brand_id]
stock_available_item_count = item_count_arry[0]
all_item_count = item_count_arry[1]
if stock > 0
brand_counts_hash[brand_id] = [stock_available_item_count + 1, all_item_count + 1]
else
brand_counts_hash[brand_id] = [stock_available_item_count, all_item_count + 1]
end
else
stock_available_item_count = 0
all_item_count = 0
if stock > 0
stock_available_item_count += 1
all_item_count += 1
brand_counts_hash[brand_id] = [stock_available_item_count, all_item_count]
else
all_item_count += 1
brand_counts_hash[brand_id] = [stock_available_item_count, all_item_count]
end
end
end
brand_counts_hash.each do |key, value|
stock_available_item_count = value[0]
all_item_count = value[1]
brand_counts << {
id: key,
name: get_brand_name(key),
stock_available_item_count: stock_available_item_count,
all_item_count: all_item_count
}
end
#brand_counts = brand_counts
render 'brands/counts/index', formats: :json
end
def get_brand_name(brand_id)
brand = Brand.find_by(id: brand_id)
brand.name unless brand == nil
end
Is there a way to optimize this further without multiple loops maybe?

Assume your Brand model also has the following association defined
has_many :items
and the final result you want is like
{
"brandCounts":[
{
"id":7006,
"name":"Brand 01",
"stockAvailableItemCount":50,
"allItemCount":60
},
{
"id":20197,
"name":"Brand 02"
"availableItemCount":150,
"allItemCount":660
}
]
}
The following code may not work when you copy and paste to your project. But it demonstrate how this problem can be solved with less code
Brand.includes(items: :skus).all.map do |brand|
{
id: brand.id,
name: brand.name,
stockAvailableItemCount: brand.items.count,
allItemCount: brand.items.map {|item| item.skus.sum(:number_of_stock)}.sum
}
end
if you need json format, just use to_json to the result of above code.

Related

Symfony 3 & Doctrine - Complex form with OnetoMany and ManyToMany relation

I want to manage the rates of a product in multi-currency and keep historic of currency rates.
So onetomany relation:
A Rates can have many CurrencyRate.
A manyto many relation:
Many currencyRate have many currencies.
RATES :
+-----------------+----------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-----------------+----------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| rate | double | YES | | NULL | |
| timeStamp | datetime | NO | | NULL | |
| currencyRate_id | int(11) | YES | MUL | NULL | |
+-----------------+----------+------+-----+---------+----------------+
CURRENCY RATES
+-----------+----------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-----------+----------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| rate | double | NO | | NULL | |
| timeStamp | datetime | NO | | NULL | |
+-----------+----------+------+-----+---------+----------------+
currencyrateshascurrencies (manytomany join table)
+-----------------+---------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-----------------+---------+------+-----+---------+-------+
| currency_id | int(11) | NO | PRI | NULL | |
| currencyRate_id | int(11) | NO | PRI | NULL | |
+-----------------+---------+------+-----+---------+-------+
currencies
+-------------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-------------+--------------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| name | varchar(255) | NO | UNI | NULL | |
| abreviation | varchar(5) | NO | UNI | NULL | |
+-------------+--------------+------+-----+---------+----------------+
I want to generate a form from all of this.
The html form would get all available currencies with a text field to indicate the CurrencyRate.
Ex :
USD <input type="text">
EUR <input type="text">
CNY <input type="text">
...
I saw the documentation on Symfony about manytomany form. But mine is more complex with an additional onetomany relation and text field. I am totaly lost.
Thanks if you can put me on the right direction.
Best regards,
Pierre
I found the response :
First I needed to correct my Schema which didn't need a ManytoMany relation :
Rates :
+-----------+----------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-----------+----------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| rate | double | YES | | NULL | |
| timeStamp | datetime | NO | | NULL | |
+-----------+----------+------+-----+---------+----------------+
CURRENCY RATES
+-------------+---------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-------------+---------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| ratio | double | NO | | NULL | |
| currency_id | int(11) | YES | MUL | NULL | |
| Rate_id | int(11) | YES | MUL | NULL | |
+-------------+---------+------+-----+---------+----------------+
Currencies
+-------------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-------------+--------------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| name | varchar(255) | NO | UNI | NULL | |
| abreviation | varchar(5) | NO | UNI | NULL | |
+-------------+--------------+------+-----+---------+----------------+
Then One Form Type
class CurrencyRateType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('currency',EntityType::class, array(
'class'=>'AppBundle:Currencies',
'choice_label' => 'abreviation',
'disabled' => true,
))
->add('ratio', TextType::class);
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => CurrencyRates::class,
));
}
}
which is embedded in the final form type :
class RateType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder ->add('rate')
->add('CurrencyRate', CollectionType::class, array(
'entry_type' => CurrencyRateType::class,
'entry_options' => array('label' => false))
)
->add('save', SubmitType::class, array('label' => 'create'));
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => Rates::class,
));
}
}

Querying in Rails

How can I get the first name and last name from the table User through table Friend which is referenced by the user_id with the condition of user_id 5 and status is pending and place it in a each loop but i also need the contents of friend controller in rails. Thank a lot for the help
Controller
#add = Friend.where(user_id: 5, status: 'Pending')
User Database
+-----------------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-----------------+--------------+------+-----+---------+----------------+
| id | int | NO | PRI | NULL | auto_increment |
| first_name | text | YES | | NULL | |
| last_name | text | YES | | NULL | |
| email | text | YES | | NULL | |
| contact_number | varchar | YES | | NULL | |
| birth_date | date | YES | | NULL | |
| created_at | datetime | NO | | NULL | |
| updated_at | datetime | NO | | NULL | |
Friend
+--------------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+--------------+--------------+------+-----+---------+----------------+
| id | int | NO | PRI | NULL | auto_increment |
| request_date | date | YES | | NULL | |
| reason | text | YES | | NULL | |
| status | varchar(255) | YES | | NULL | |
| userid | int | YES | MUL | NULL | |
How about this:
#add = Friend.joins(:user).select("users.first_name, users.last_name").where(user_id: 5, status: 'Pending')
Seems like youre already fetching the id of the user, stored in the user_id field. In order to get the associated user, i would do
#user = User.find(#add.id)
This retrieves the user based on the id you pass, and from there you can do stuff like
#user.first_name
to get the values
All you need to do in Friend model belongs_to user and then
#add =Friend.where("user_id = ? AND status = ?", 5,"Pending")
and
<#add.each do |s|>
<%s.user.first_name%>
<%=<%s.user.last_name%>
// and so on..
<%end%>

Rails POST Form Don't Send Full of Data to Database

I work on beginner level web application which is save off days request of users by using simple form.
Here is codes
views/days/index.html.erb
<%= form_for #daysoff, url: {action: "create"} do |f| %>
<%= f.date_field :offdate, id: 'altField' %>
<p> User holding the fort / Yerine bakicak kisi
<%= f.collection_select(:assign_id, #people, :id, :name) %> </p>
<%= f.hidden_field :user_id, value: #user.id %>
<%= f.submit "Submit" %>
<% end %>
controllers/days_controller.rb
class DaysController < ApplicationController
unloadable
def index
#people = User.all
#user = User.current
#daysoff = Daysoff.new
# params[:daysoff][:user_id] = #user.id
end
def create
off_dates = params["daysoff"]["offdate"].try(:split,',')
off_dates.each do |off_date|
#days_off = Daysoff.new
#days_off.offdate = Date.strptime(off_date.strip, "%m/%d/%Y")
#days_off.user_id = User.current
#days_off.assign_id = params["assign_id"]
#days_off.status = 0
#days_off.save!
end
redirect_to "/days_off_redmine", :flash => { :success => "Istek gonderildi." }
end
There is no any model validation.
Database migration file.
class CreateDaysoffs < ActiveRecord::Migration
def change
create_table :daysoffs do |t|
t.date :offdate
t.integer :user_id
t.integer :assign_id
t.boolean :status
end
end
end
When i try to fill and post form,
Offdates and status filling correctly but user_id and assign_id insert db as NULL.
+----+------------+---------+-----------+--------+
| id | offdate | user_id | assign_id | status |
+----+------------+---------+-----------+--------+
| 1 | 2015-07-14 | NULL | NULL | 0 |
| 2 | 2015-07-15 | NULL | NULL | 0 |
| 3 | 2015-07-14 | NULL | NULL | 0 |
| 4 | 2015-07-15 | NULL | NULL | 0 |
| 5 | 2015-07-22 | NULL | NULL | 0 |
| 6 | 2015-07-23 | NULL | NULL | 0 |
| 7 | 2015-07-21 | NULL | NULL | 0 |
| 8 | 2015-07-22 | NULL | NULL | 0 |
| 9 | 2015-07-21 | NULL | NULL | 0 |
| 10 | 2015-07-22 | NULL | NULL | 0 |
| 11 | 2015-07-21 | NULL | NULL | 0 |
| 12 | 2015-07-22 | NULL | NULL | 0 |
| 13 | 2015-07-29 | NULL | NULL | 0 |
| 14 | 2015-07-30 | NULL | NULL | 0 |
| 15 | 2015-07-29 | NULL | NULL | 0 |
| 16 | 2015-07-30 | NULL | NULL | 0 |
+----+------------+---------+-----------+--------+
Why this occur like that?
Skipping the fact that code looks really bad...
form attributes are present in params[:daysoff], you would need to get to fields by it, for example:
to get assign_id by params[:daysoff][:assign_id]
#days_off.user_id = User.current should be rather: #days_off.user_id = User.current.id
or #days_off should have in model class added
belongs_to :user and it would be working fine, unless User.current is nil

Why does this before_save not update the verified date in rails?

My before_save doesn't update the verified_date field.
Why is that? Other processes can update the field ok.
Model:
class Link < ActiveRecord::Base
belongs_to :group
validates_presence_of :url_address
validates_presence_of :group_id
validates_size_of :version_number, :maximum => 10 #, :allow_nil => true
before_save :verify_this_link
acts_as_list
...
def verify_this_link
verified_date = Time.now
end
end
mysql> describe links;
+----------------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+----------------+--------------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| url_address | varchar(255) | NO | | NULL | |
| alt_text | varchar(255) | YES | | NULL | |
| group_id | int(11) | YES | | NULL | |
| position | int(11) | YES | | NULL | |
| created_at | datetime | YES | | NULL | |
| updated_at | datetime | YES | | NULL | |
| version_number | varchar(255) | YES | | NULL | |
| content_date | date | YES | | NULL | |
| verified_date | date | YES | | NULL | |
+----------------+--------------+------+-----+---------+----------------+
mysql> select id, substr(url_address,1,20),
verified_date from links where id > 350;
+-----+--------------------------+---------------+
| id | substr(url_address,1,20) | verified_date |
+-----+--------------------------+---------------+
| 351 | http://magicmodels.r | NULL |
| 352 | http://jsbin.com/#ja | 2014-07-12 |
| 353 | http://www.javascrip | 2014-07-12 |
| 354 | http://www.test.com | 2014-08-08 |
| 357 | http://www.t5.com | 2014-07-12 |
+-----+--------------------------+---------------+
5 rows in set (0.00 sec)
Try:
def verify_this_link
self.verified_date = Time.now
end
Reference https://stackoverflow.com/a/6326323/252671

Rails ActiveRecord group_by & sum db results for use with Lazy HighCharts

I am completely new to RoR/Ruby and i am using Lazy High Charts gem to generate some purdy charts based on some database information.
I have tried the answers that were provided in a previous question but i am still a bit confused as to how to do this..
I need to sum amount_used, and billed_amount and group by month/year (e.g; Aug/2012)
The end result will be something similar to a dual axis chart with two series "Amount Used", and "Cost".. This information is specific to a certain account_id.
Invoices table
+---------------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+---------------+--------------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| account_id | int(11) | YES | | NULL | |
| invoice_date | varchar(255) | YES | | NULL | |
| amount_used | float | YES | | NULL | |
| billed_amount | float | YES | | NULL | |
| comments | text | YES | | NULL | |
| created_at | datetime | NO | | NULL | |
| updated_at | datetime | NO | | NULL | |
+---------------+--------------+------+-----+---------+----------------+
Controller Chart Code
#account = Account.find(params[:id])
#invoices = Invoice.where("account_id = #{#account.id}").order("invoice_date DESC")
#h = LazyHighCharts::HighChart.new('area') do |f|
f.options[:chart][:defaultSeriesType] = "area"
#Sample dates right now, should be the grouped_by :invoice_date
f.xAxis( :categories => ['May', 'Jun', 'Jul'] )
f.yAxis([
{
:title => { :text => "Amount Used" }
},
{
:title => { :text => "Cost" },
:opposite => true
}
])
#Sample data right now, should be the summed amounts of the :amount_used correpsonding for each above grouped invoice_date
f.series(:name => "Amount Used", :data => [100,300,500] )
#Sample data right now, should be the summed amounts of the :billed_amount correpsonding for each above grouped invoice date
f.series(:name => "Cost", :yAxis => 1, :data => [200,400,600] )
end
It looks like you have everything in place. Here's how you can pull data from db:
#aggregated_invoices = Invoice.
where(:account_id => params[:id]).
order("invoice_date DESC").
group("invoice_date").
select("DATE_FORMAT(invoice_date, '%Y-%m-01') AS invoice_date, sum(amount_used) AS amount_used, sum(billed_amount) AS billed_amount")
# Then use these instead of sample data:
#categories = #aggregated_invoices.map {|i| i.invoice_date.strftime("%b/%Y")}
#amount_used_data = #aggregated_invoices.map(&:amount_used)
#billed_amount_data = #aggregated_invoices.map(&:billed_amount)

Resources