In my rails app, new line items are created daily. I need to be able to have my smart_listing show how many apples and oranges were ordered. For instance:
Line Item QTY
Apple 2
Orange 1
What I am getting is:
Line Item QTY
Apple 1
Apple 1
Orange 1
line_item_scope = LineItem.all
line_item_scope = line_item_scope.where(created_at: Date.today.beginning_of_day..Date.today.end_of_day)
if customer_signed_in?
line_item_scope = line_item_scope.ticket.customer(current_customer.id)
end
#line_items = smart_listing_create(:line_items, line_item_scope, partial: "line_items/listing2", default_sort: {updated_at: "desc"})
My initial thought was to create a .map(&:name).uniq but that returns an array when I need a relationship to go into the smart listing.
If you need to display just LineItem's name and the number of items of that name, then group method can help:
line_item_scope.group(:name).count
This will construct a hash:
result = { "Apple" => 2, "Orange" => 1 }
Then this hash can be iterated to display the values:
result.each do |name, count|
...
end
Or the number of line items can be selected as a column:
line_items_scope =
LineItem.group(:name)
.order(:name)
.select("name, COUNT(*) as count")
Then line_items_scope can be fed to smart_listing_create as a ActiveRecordRelation
Related
Let's suppose, there are three tables in the database:
courses (:id, :name)
course_details (:course_id, :effective_date, :status),
course_codes (:course_detail_id, :code)
course has many course_details
course_detail has many cource_codes
Course can have multiple course_details records which are effective_dated applicable (means only one record of course_detail will be used in the system).
Problem statement: I want to filter courses by course codes given code. And course should only be filtered by the course_codes which are linked with effective dated course_detail and should skip the past effective dated records.
course = Course.find(params[:id])
course_detail = CourseDetail.find_by(effective_date: CourseDetail.max(effective_date), course_id: course.id)
If I use this code this will filter course irrespective of effective_dated course_details:
Course.left_joins(course_details: :course_codes).where(course_details: { course_codes: { code: params[:code] } })
courses:
Id
Name
1
English
2
Maths
course_details:
id
course_id
effective_date
1
1
2020-10-01
2
1
2021-01-01
3
2
2020-09-01
course_codes:
id
course_detail_id
code.
1
1
eng-01
2
2
eng-505
3
3
math-01
when I pass code = eng-01 it should return empty array instead of course with id 1.
Can somebody please help me?
To resolve this issue, I used a subquery that returns ids of course_details of all the courses according to effective_date:
query = "select child.id from courses as parent
inner join course_details as child on child.course_id = parent.id
where child.effective_date =
(select max(child1.effective_date) as effective_date
from course_details as child1
where child1.course_id = parent.id
and (child1.effective_date <= CURRENT_DATE
or child1.effective_date = (select min(child2.effective_date) as effective_date
from course_details as child2
where child2.course_id = parent.id)
))"
effective_dated_ids = Course.find_by_sql(query).pluck(:id)
After getting all the ids, I passed these ids in search.
records = Course.left_joins(course_details: :course_codes).where(course_details: { id: effective_record_ids, course_codes: { course_code: params[:course_code] } })
And it worked as expected.
So, I have an after_save hook on review model which calls calculate_specific_rating function of product model. The function goes like this:
def calculate_specific_rating
ratings = reviews.reload.all.pluck(:rating)
specific_rating = Hash.new(0)
ratings.each { |rating| specific_rating[rating] += 1 }
self.specific_rating = specific_rating
save
end
Right now, it returns
specific_rating => {
"2"=> 3, "4"=> 1
}
I want it to return like:
specific_rating => {
"1"=> 0, "2"=>3, "3"=>0, "4"=>1, "5"=>0
}
Also, is it okay to initialize a new hash everytime a review is saved? I want some alternative. Thanks
You can create a range from 1 until the maximum value in ratings plus 1 and start iterating through it, yielding an array where the first element is the current one, and the second element is the total of times the current element is present in ratings. After everything the result is converted to a hash:
self.specific_rating = (1..ratings.max + 1).to_h { |e| [e.to_s, ratings.count(e)] }
save
You could also do something like this -
def calculate_specific_rating
ratings = [1,2,3,4,5]
existing_ratings = reviews.group_by(&:rating).map{|k,v| [k, v.count]}.to_h
Hash[(ratings - existing_ratings.keys).map {|x| [x, 0]}].merge(existing_ratings)
end
which gives
{3=>0, 4=>0, 5=>0, 2=>3, 1=>1}
In the existing product controller, we are sorting products based on price.
def index
#products = Product.all.order(price: :asc)
#products = #products.paginate(:page => params[:page], :per_page => 20)
end
Now, I would like to change the sorting so that we will user interests. The over all sorting policy will be like this:
items belonging to user interested categories will be listed at top. Remaining items will be listed afterwards.
Within user interested grouping, products shall be ordered based on price. (similar approach for the non interested grouping).
Users Table
id name interested_categories
1 Alice [ 10, 11 ]
2 Bruce [ 11 ]
3 Carol [ 10, 12 ]
Categories Table
id category_name
10 School
11 Kitchen
12 Camping
Products Table
id product_name price category_id
1 pencil 2 10
you could try this
def index
filtered_products = Product.where(category_id: current_user.interested_categories)
all_products = Product.where.not(category_id: current_user.interested_categories).order(price: :asc)
#final_products = (filtered_products+all_products).paginate(:page => params[:page], :per_page => 20)
end
then your views take the #final_products or just change the variable into #products
filtered_products = Product.where(category_id: current_user.interested_categories).to_a.sort_by { |product| [product.category, product.price] }
remaining_products = Product.where.not(category_id: current_user.interested_categories).to_a.sort_by { |product| product.price }
#products = filtered_products + remaining_products.paginate
Converting to Array allows you to use sort_by to sort by two criteria.
Consider i have a 15 categories and 6 sub-categories and i have table items where i have set of records where i have to fetch in the following manner
category 1 ---> level 1 ---> 3 items with maximum price
category 1 ---> level 2 ---> 3 items with maximum price
...
...
...
category 15 ---> level 6 ---> 3 items with maximum price
and
#categories.each do |value|
#sub-categories.each do |value1|
array = Item.find(:all, :conditions => ["customer_id IN (?) AND category_id = ? AND sub-category_id = ?", #customer, value.id, value1.id], :order => 'price DESC', :limit => 3)
array.each do |value2|
#max_price_item_of_each_customer << value2
end
end
end
but this would take much time as this iterates. So how can i change this in such a way the time can be reduced? Any help is appreciated.
Try:
#max_price_item_of_each_customer = []
#categories.each do |value|
#max_price_item_of_each_customer += Item.find(:all, :conditions => ["customer_id IN (?) AND category_id = ? AND sub-category_id in (?)", #customer, value.id, #sub-categories.map(&:id)], :order => 'price DESC', :limit => 3)
end
This all depends on the scale of records you're working with, but if you're working with a reasonable set, this should be faster and will reduce your queries to 1.
#customer_id = 1
#categories = [1, 2, 3]
#subs = [4, 5, 6]
#max_price_item_of_each_customer = []
items = Item.where(customer_id: #customer, category_id: #categories, subcategory_id: #subcategories)
items.group_by{|item| item.category_id}.each_pair do |category_id, category_items|
category_items.group_by{|item| item.subcategory_id}.each_pair do |subcategory_id, subcategory_items|
#max_price_item_of_each_customer += subcategory_items.sort{|x, y| y.price <=> x.price }.first(3)
end
end
The solution below might work if you use Postgresql.
Select a group of 3 item ids from items table, sorted by price descending and grouped by category_id and subcategory_id. You can use Postgres array_agg to collect the item ids after grouping.
Select items row, where the item ids are in those grouped item ids. After that, order the result by category_id ascending, subcategory_id ascending, and price descending
The result is ActiveRecord::Relation, so you can iterate the items as usual. Since the result is flattened (but already ordered by categories, subcategories, and price), you need to separate the different categories and subcategories yourself.
grouped_item_ids = Item.where(customer_id: customer_id).
select("items.category_id, items.subcategory_id, (array_agg(items.id order by items.price desc))[1:3] AS item_ids").
group("items.category_id, items.subcategory_id").map {|item| item["item_ids"]}
#items = Item.where(id: grouped_item_ids.flatten).
order("items.category_id ASC, items.subcategory_id ASC, items.price desc")
Following query works for me
#max_price_item_of_each_customer =Item.find_by_sql(["SELECT i1.* FROM item i1
LEFT OUTER JOIN item i2 ON (i1.category_id = i2.category_id AND i1.sub-category_id = i2.sub-category_id AND i1.id < i2.id)
WHERE i1.customer_id IN (?) AND i1.category_id IN (?)
GROUP BY i1.id HAVING COUNT(*) < 3
ORDER BY price DESC", #customer, #categories.map(&:id)])
I would like to analyse data in my database to find out how many times certain words appear.
Ideally I would like a list of the top 20 words used in a particular column.
What would be the easiest way of going about this.
Create an autovivified hash and then loop through the rows populating the hash and incrementing the value each time you get the same key (word). Then sort the hash by value.
A word counter...
I wasn't sure if you were asking how to get rails to work on this or how to count words, but I went ahead and did a column-oriented ruby wordcounter anyway.
(BTW, at first I did try the autovivified hash, what a cool trick.)
# col: a column name or number
# strings: a String, Array of Strings, Array of Array of Strings, etc.
def count(col, *strings)
(#h ||= {})[col = col.to_s] ||= {}
[*strings].flatten.each { |s|
s.split.each { |s|
#h[col][s] ||= 0
#h[col][s] += 1
}
}
end
def formatOneCol a
limit = 2
a.sort { |e1,e2| e2[1]<=>e1[1] }.each { |results|
printf("%9d %s\n", results[1], results[0])
return unless (limit -= 1) > 0
}
end
def formatAllCols
#h.sort.each { |a|
printf("\n%9s\n", "Col " + a[0])
formatOneCol a[1]
}
end
count(1,"how now")
count(1,["how", "now", "brown"])
count(1,[["how", "now"], ["brown", "cow"]])
count(2,["you see", "see you",["how", "now"], ["brown", "cow"]])
count(2,["see", ["see", ["see"]]])
count("A_Name Instead","how now alpha alpha alpha")
formatAllCols
$ ruby count.rb
Col 1
3 how
3 now
Col 2
5 see
2 you
Col A_Name Instead
3 alpha
1 how
$
digitalross answer looks too verbose to me, also, as you tag ruby-on-rails and said you use DB.. i'm assuming you need an activerecord model so i'm giving you a full solution
in your model:
def self.top_strs(column_symbol, top_num)
h = Hash.new(0)
find(:all, :select => column_symbol).each do |obj|
obj.send(column_symbol).split.each do |word|
h[word] += 1
end
end
h.map.sort_by(&:second).reverse[0..top_num]
end
for example, model Comment, column body:
Comment.top_strs(:body, 20)