Elixir/Phoenix query incl quantity - psql

I have following tables in my db:
schema "foods" do
field :name, :string
has_many :order_items, MyApp.OrderItem
end
schema "order_items" do
field :quantity, :integer, default: 1
field :price, :integer
belongs_to :food, MyApp.Food
Order items keeps the food_id, as well as quantity. Now I'm trying to create a query that will show how much each food item has been ordered. This is what i have so far:
fooditems = Repo.all(from p in OrderItem, join: f in Food, on: p.food_id == f.id,
group_by: f.name, select: {f.name, count(p.id)}, order_by: count(p.id))
Basically, I have two questions: 1) How can I make quantity field(from order item table) be also included in the query count? For example, if in order item the quantity is 2, the count() will only count is as only 1, so how can I make the count() to count not only by id, but also include the quantity(from order item table, quantity field)?
Additionally, I'm not sure how to call the query from the html. I've tried
<%= for item <- #fooditems do %>
<tr>
<td><%= item.name %></td>
</tr>
<% end %>
But it returns argument error, so I believe, I'm just calling it the wrong way with item.name. Generally, I want to have a table that will list food names, and the respective quantity (from order_item table, incl quantity field).
Thanks

As you're doing a group_by on the Food's name, I'm guessing you want the sum of quantity. Here's how I would do it:
Repo.all(from p in OrderItem,
join: f in Food,
on: p.food_id == f.id,
group_by: f.name,
select: %{name: f.name, count: count(p.id), quantity: sum(p.quantity)},
order_by: count(p.id))
This generates the following query:
SELECT f1."name", count (o0."id"), sum (o0."quantity") FROM "order_items" AS o0 INNER JOIN "foods" AS f1 ON o0."food_id" = f1."id" GROUP BY f1."name" ORDER BY count (o0."id")
For the second part, Repo.all returns a list of whatever you have in the select part of your query, which in your case was a tuple, and not a map, but you tried to access it like a Map.
For your original query, this will work:
<%= for {food_name, count} <- #fooditems do %>
For the query I wrote above, which returns a Map, you can access the fields easily:
<%= for item <- #fooditems do %>
<tr>
<td><%= item.name %></td>
<td><%= item.count %></td>
<td><%= item.quantity %></td>
</tr>
<% end %>

Related

Rails 6: Dynamic nested forms: get id of nested resource selected by fields_for and collection_select

If you're facing a similar issue you might found interesting (re)reading this posts:
Can someone explain collection_select to me in clear, simple terms?
I'm trying to implement nested forms with several models and a dynamically added collection_select field, based on this great tutorial https://www.driftingruby.com/episodes/nested-forms-from-scratch
Sadly, I currently have trouble to retrieve the Product.id from my controller to save the record appropriately.
In my schema:
a Formular has many Products (though FormularProducts) has many Orders
a Order has many OrderProducts (tracking its quantity) and many Products (tracking :product_id)
an OrderProduct belongs to an Order and belongs to a Product
a Product has many Formulars, has many Orders, has many FormularProducts and has many OrderProducts
When I trigger a submission with multiples OrderProduct records, only the last :product_id is passed:
Started PATCH "/formulars/1/orders/53"
Parameters:
{
"authenticity_token"=>"[FILTERED]",
"product_id"=>{"id"=>"7"}, # <- :product_id of the last collection_select
"order"=>
{
"order_products_attributes"=>
{
"1612773831378"=>{"quantity"=>"1", # <- each OrderProduct dynamically added
"_destroy"=>"false"}, # <-
"1612774917407"=>{"quantity"=>"6", # <- frontend JS substitutes the associated
"_destroy"=>"false"}, # <- objects' :object_id with a unique
"1612774918874"=>{"quantity"=>"4", # <- timestamp.
"_destroy"=>"false"}}}, # <- I need to access :product_id from here,
} # for each OrderProduct added
},
"commit"=>"Modifier ce(tte) Commande",
"formular_id"=>"1",
"id"=>"53" # <- Order's :id
}
Here's how the field is dynamically prepend to the form:
# app/views/orders/_form.html.erb
<%= form_with(model: [formular, order] do |form |%> # <- Form for an Order binded to a Formular (nested routes)
....
<%= link_to_add_product_row('Add an article', form, :order_products) %> # <- Call a Helper method
<% end %>
# app/helpers/application_helper.rb
def link_to_add_product_row(name, f, association, **args)
new_object = f.object.send(association).klass.new # <- create a new instance of the associated object
# f.object == #<Order id: 53> in this context
# new_object == #<OrderProduct id: nil, order_id: nil, product_id: nil, quantity: 1>
id = new_object.object_id # <- get its unique :object_id
fields = f.fields_for(association, new_object, child_index: id) do |builder|
render(association.to_s.singularize, f: builder) # 'orders/_order_product' is a partial shown below
end
link_to(name, '#', class: "add_fields " + args[:class], data: {id: id, fields: fields.gsub("\n",'')})
end
# app/views/orders/_order_product.html.erb
<tr>
#
# I need this collection_select to:
# - display the Product's name
# - display only Products associated to the selected Formular
# - pre-select the Product record, if set
# - return the Product.id to the backend
#
<td><%= collection_select(:product_id, :id, #formular.products, :id, :name) %></td>
<td></td>
<td><%= f.number_field :quantity, hide_label: true %></td>
<td><%= f.submit %></td>
<td>
<%= f.hidden_field :_destroy, as: :hidden, method: :delete %>
<%= link_to 'Delete', '#', class: 'remove_order_product' %>
</td>
</tr>
$(document).on('turbolinks:load', function() {
$('form').on('click', '.add_fields', function(event) {
var regexp, time;
time = new Date().getTime();
regexp = new RegExp($(this).data('id'), 'g');
$('.fields').append($(this).data('fields').replace(regexp, time));
return event.preventDefault();
});
});
# app/controllers/orders_controller.rb
# before_action callback executed on :create and :update
# goal is to retrieve OrderProducts from params, and associate
# them to the current instance of Order
def set_order_products
order_products_params = order_params.dig(:order, :order_products_attributes)
if order_products_params
order_products_params.each_value do |param|
product = Product.find(param['product_id'])
#order.order_products.build(product: product,
quantity: param['quantity'])
end
end
end
# If OrderProducts params were like:
#
# {"TIMESTAMP"=>{
# "product_id"=>"42", # <- this parameter is missing
# "quantity"=>"3",
# "_destroy"=>"false"}}
#
# then I'd be able to identify to which product this OrderProduct is associated to
Let me know if you want mode code.
Any idea about how to achieve this goal ? I've tried adding hidden_fields, tweaking collection_select, using a select and options_from_collection_for_select / options_for_select; but I must admit that I'm lost (and I may have overcomplexified my schema).
TL_DR: How can I retrieve a Product's ID from a field_for(:order_products) dynamically generated inside Order's controller ?
From Can someone explain collection_select to me in clear, simple terms? https://stackoverflow.com/a/8908298/11509906
zquares said:
With regards to using form_for, again in very simple terms, for all tags that come within the form_for, eg. f.text_field, you dont need to supply the first (object) parameter. This is taken from the form_for syntax.
I changed my collection_select from:
<%= collection_select(:product_id, :id, #formular.products, :id, :name) %>
# Parameters: {"authenticity_token"=>"[FILTERED]", "product_id"=>{"id"=>"7"}, "order"=>{"order_products_attributes"=>{"1612782399659"=>{"quantity"=>"3", "_destroy"=>"false"}, "1612782403591"=>{"quantity"=>"5", "_destroy"=>"false"}}}, "commit"=>"Modifier ce(tte) Commande", "formular_id"=>"1", "id"=>"53"}
to:
<%= f.collection_select(:product_id, #formular.products, :id, :name) %>
# Parameters: {"authenticity_token"=>"[FILTERED]", "order"=>{"order_products_attributes"=>{"1612782084184"=>{"product_id"=>"3", "quantity"=>"3", "_destroy"=>"false"}, "1612782085172"=>{"product_id"=>"33", "quantity"=>"-3", "_destroy"=>"false"}}}, "commit"=>"Modifier ce(tte) Commande", "formular_id"=>"1", "id"=>"53"}
So now I can fetch the associated product within Order's controller

Getting count of residents in each country

Say I have a Users table, and each user has a country_id (the list of countries is stored in another table).
I want to display a list of each country and how many members it has, ie:
Canada: 16
Romania: 12
USA: 9
I was using some raw SQL, but the move to postgres was messy, so I'd like to use a cleaner implementation. Is there a nice 'railsy' way to go about getting said list?
This should return a hash with country_id => count pairs:
#users_by_country = User.group(:country_id).count
#=> { 1 => 104, 2 => 63, ... }
The generated SQL query looks like this:
SELECT COUNT(*) AS count_all, country_id AS country_id FROM `users` GROUP BY country_id
And in your view:
<% #countries.each do |country| %>
<%= country.name %>: <%= #users_by_country[country.id] %>
<% end>
though you have not provided any detail . so its hard to answer exaclty, but may be this will help
User.group(:country_id).count
If you have a Countries model and you want to do it a little differently so that it is more human readable you could use .map.
Countries.map{|o| { Country: o.name, members: o.members.count } }
Obviously you need to have the associations. If countries is just a field on User then this will not work.
BR

rails activerecord multiple query

Thanks for your time !
It's a simple question but I've searched for some time and still cannot get the function suit me.
I get a table named *lr_transaction_tables* in DB and a class LrTransactionsTable < ActiveRecord::Base with it.
I can #entry_ary = LrTransactionsTable.find(:all) to fetch the whole table and present it in the view by :
<table id="trans-war-table">
<% #entry_ary.each do |item| %>
<tr>
<td><%= item.ltname %></td>
<td><%= item.name %></td>
</tr>
<% end %>
</table>
But how do I get the data where the data under ltname column is "specific word" instead of all of it.
I've tried #entry_ary = LrTransactionsTable.find_by_ltname( "specific word" ), but it give me the error that undefined methodeach' for nil:NilClass`
ltname and name is the column name in the table.
For Rails 2
LrTransactionsTable.find(:all, :conditions => {:ltname => "specific word"})
For Rails 3
#entry_ary = LrTransactionsTable.where(ltname: "specific word")
OR
#entry_ary = LrTransactionsTable.where(["ltname =? ", "specific word"])
I'll use the where method to set a condition:
LrTransactionsTable.where(:ltname => "specific word")
try best way, always right query in model
in your controller
LrTransactionsTable.find_ltname("specific word")
and write in your model
def find_ltname(name)
where("ltname = ?", name)
end
or you can also create a scope for that
scope :find_ltname, lambda{|name|{ :conditions => ["ltname = ?", name]}
try this:
#entry_ary = LrTransactionsTable.where(ltname: "specific word")

Nested form using accepts_nested_attributes_for with pre-population from another table

I'm using Rails 2.3.5 and have a nested structure as follows:
Lists has_many Items
Items_Features has_many Features
Items_Features has_many Items
Items_Features has a text field to hold the value of the feature
Then I have a nested form with partials to update and display this so that it updates Lists, Items and Items_Features
What I want to do is generate input fields for each of the rows in features so that the user can fill in a value and it gets inserted/updated in items_features. I also want a label next to the box to display the feature name.
It might look like this:
List name: Cheeses
Item1 name: Edam
Feature, hardness: - fill in - <= this list of features from feature table
Feature, smell: - fill in -
How can I interrupt the nice and easy accepts_nested_attributes_for system to display this as I want?
EDIT:
Here's the code for the Item class now i've got some sql in there:
class Item < ActiveRecord::Base
belongs_to :list
has_many :users
has_many :votes
has_many :items_features
validates_presence_of :name, :message => "can't be blank"
accepts_nested_attributes_for :items_features, :reject_if => lambda { |a| a.values.all?(&:blank?) }, :allow_destroy => true
def self.get_two_random(list_id)
Item.find_by_list_id(list_id, :limit => 2, :order => 'rand()')
end
def self.get_item_features(item_id)
sql = "select items_features.*, features.id as new_feature_id, features.name as feature_name "
sql += "from items_features "
sql += " right outer join features "
sql += " on features.id = items_features.feature_id "
sql += " left outer join items "
sql += " on items.id = items_features.item_id "
sql += "where items_features.item_id = ? or items_features.item_id is null"
find_by_sql([sql, item_id])
end
end
Here's my display code - I need to save the feature_id into new records somehow:
<% form_tag item_path, :method => :put do %>
<% for items_feature in #item_features %>
<% fields_for "items_features[]", items_feature do |f| %>
<%=h items_feature.feature_name %> <%= f.text_field :featurevalue %><br><Br>
<% end %>
<% end %>
<p><%= submit_tag "Submit"%></p>
<% end %>
Hmm, that isn't working - it prints out fine but then the server gives me:
/!\ FAILSAFE /!\ Thu Apr 15 16:44:59 +0100 2010
Status: 500 Internal Server Error
expected Array (got Hash) for param `items_features'
When I post it back
Try the examples here
http://github.com/ryanb/complex-form-examples

Rails - Creating a select tag from a object hash

I need to create a select box from the values available in a Hash.
For instance, I have a 'thing' and the 'thing' has a variety of status fields:
1 => 'State A'
2 => 'State B'
available via a method on thing.
How can I build a select tag from this?
Just as Schrockwell has said:
Hash.each |a| returns an array of the form a = [key, value], so for the hash #status_fields you can write:
<%= collection_select('thing', 'status', #status_fields, :first, :last) %>
Alternatively, if you'd like the key to show up in the select list and the value point to the select list value, then:
<%= collection_select('thing', 'status', #status_fields, :last, :first) %>
This will select the option given by thing.status or nothing if nil is returned
If you want to just create any selection not tied to an object use
<%= select_tag('name', options_from_collection_for_select(#status_fields, :first, :last, '2')) %>
where '2' is the index of the desired selection
PS: I don't have enough reputation to just amend the original post or comment on it
you could do something like
select "foo", "bar", #hash_object
or
select "foo", "bar", #hash_object.map { |h| [h.key, h.value] }
I'd probably invert your hash first to make the key point to the value
The select helper method will accept a hash in the form { text_displayed_in_select => select_value }, so you'll probably want to invert that hash.
Hash.each |a| returns an array of the form a = [key, value], so for the hash #status_fields you can write:
<%= collection_select('thing', 'status', #status_fields, :first, :last) %>
Alternatively, if you'd like the key to show up in the select list and the value point to the select list value, then:
<%= collection_select('thing', 'status', #status_fields, :last, :first) %>

Resources