Rails: group_by then map children - ruby-on-rails

I have a method that looks like this:
def categorised_templates
template_categories.all_parents.map do |cat|
cat.templates.by_brand(id).group_by(&:category)
end
end
Which returns something like this:
[{"Communications":["#<Template:0x00007fef0efcdd48>","#<Template:0x00007fef0efcdb90>"]},{"Beta":["#<Template:0x00007feefe1bb008>"]}]
How can I render the Template objects into JSON just like the categories? I need to maintain the group_by hierarchy.

Turns out it was easy enough. This change:
def categorised_templates
arr = template_categories.all_parents.map do |cat|
cat.templates.by_brand(id).group_by(&:category)
end
arr.map { |cat| cat.transform_values!(&:to_s) }
end
Returns this result.
[{"Communications":"[#<Template id: 2, ...]"}]

Related

How to push/shovel/append active record relation correctly?

I have a fairly complex search form that allows for multiple duplicate nested fields to be submitted at once. I generate unique id's on clone so i can separate them using jquery. I then iterate over the duplicates and do something like the following:
So i have something like:
{..., "search"=>{"searches"=>{}, "searches_0"=>{}...{other_attributes}}
def search_post
search = []
params.each do |k, v|
search << do_search(params, v)
end
end
def do_search(search, v)
search_array = []
search = Model.where() if v[:this_param].present?
search = Model.where() if v[:that_param].present?
# it will be only one of the `search` as this_param or that_param can't be searched
together
search_array << search.where(attr: search[:attr]) if attr.present?
search_array << search.where(attr_2: search[:attr_2]) if attr_2.present?
search_array << search.where(attr_3 search[:attr_3]) if attr_3.present?
search_array.uniq
end
This gives a result like:
[#<ActiveRecord::Relation [#<LineItem id: 15, created_at: "2020-01-03 15:49:19", updated_at: "2020-01-03 15:49:19", ...>]>, #<ActiveRecord::Relation [#<LineItem id: 14, created_at: "2020-01-03 15:49:19", updated_at: "2020-01-03 15:49:19", ...>]>]
I obviously get an array but I need to do more queries on it.
I have tried using search.reduce([], :concat).uniq but this only removes all of the results and only keeps the ActiveRecord::Relation aspect of the array.
What I need is to shovel the results from the loop and be able to use where on it.
How can this be accomplished?
By the looks of it you can try using a query object:
class ModelQuery
def initialize(initial_scope = Model.all)
#initial_scope = initial_scope
end
def call(params)
scoped = by_this_param(initial_scope, params[:this_param])
scoped = by_that_param(initial_scope, params[:that_param])
scoped = by_other_param(initial_scope, params[:other])
# ...
scoped
end
def by_this_param(s, this_param = nil)
this_param ? s.where(this_attr: this_param) : s
end
def by_that_param(s, that_param = nil)
that_param ? s.where(that_attr: that_param) : s
end
# ...
def by_other(s, other = nil)
other ? s.where(other_attr: other) : s
end
end
You can then do things like:
q = ModelQuery.new
# or perhaps...
# q = ModelQuery.new(Model.where(whatever: "however"))
q.call params
q.order(whatever_column: :asc).group(:however)
Obviously you need to adapt and extend the code above to your variable names/parameters. Another upside of using the query object pattern is that it gives you a subtle nudge to structure your parameters coherently so you can pass from view and return from AR quickly, without much fiddling about with the input/output.
Give it a try!

Flatten array of nested hashes

I have an array of nested hashes that looks something like this:
[{"month"=>1,
"percentiles"=>{"25"=>768.06, "50"=>1868.5, "75"=>3043.79, "90"=>4161.6},
"total_revenue"=>1308620.0,
"year"=>2017},
{"month"=>2,
"percentiles"=>{"25"=>922.63, "50"=>2074.31, "75"=>3048.87, "90"=>4018.6},
"total_revenue"=>1105860.0,
"year"=>2017}]
That I would like to flatten into this:
[{"month"=>1,
"25"=>768.06, "50"=>1868.5, "75"=>3043.79, "90"=>4161.6,
"total_revenue"=>1308620.0,
"year"=>2017},
{"month"=>2,
"25"=>922.63, "50"=>2074.31, "75"=>3048.87, "90"=>4018.6,
"total_revenue"=>1105860.0,
"year"=>2017}]
I have been looking and testing different methods with no luck. Any ideas on how to accomplish this? The end goal is to mass update/insert these into a database, so if there is a better way to accomplish that, I would like to see a different approach.
If you don't mind modifying the array in-place then you could say:
array.each { |h| h.merge!(h.delete('percentiles')) }
If you're not sure that all the hashes have 'percentiles' keys then you could say:
# Explicitly check
array.each { |h| h.merge!(h.delete('percentiles')) if(h.has_key?('percentiles')) }
# Convert possible `nil`s to `{ }`
array.each { |h| h.merge!(h.delete('percentiles').to_h) }
# Filter before merging
array.select { |h| h.has_key?('percentiles') }.each { |h| h.merge!(h.delete('percentiles')) }
If you want to flatten all hash values then you can do things like this:
array.each do |h|
h.keys.each do |k|
if(h[k].is_a?(Hash))
h.merge!(h.delete(k))
end
end
end
If you don't want to modify the hashes inside the array then variations on:
flat = array.map(&:dup).each { |h| h.merge!(h.delete('percentiles')) }
flat = array.map do |e|
e.each_with_object({}) do |(k, v), h|
if(v.is_a?(Hash))
h.merge!(v)
else
h[k] = v
end
end
end

Sorting by integers represented as strings

I would like sort array of ActiveRecord objects by related object's attribute value. Meaning something like this:
Item has one product which has an attribute SKU. The SKU is mostly integer stored as a string, but could be alphanumeric as well.
sorted = items.sort_by { |item| Integer(item.product.sku) } rescue items
For now in case of error the items with original order returns.
What would I like to do?
Extend the Array class to achieve something like:
items.numeric_sort { |item| item.product.sku }
What I did so far?
1. Building a lambda expression and passing it
class Array
def numeric_sort(&lambda)
if lambda.respond_to? :call
self.sort_by(&lambda) rescue self
else
self.sort_by { |el| Integer(el) } rescue self
end
end
end
product_bin = lambda { |task_item| Integer(item.product.bin) }
items.numeric_sort(&product_bin)
2. Building lambda expression from methods chain
class Object
def send_chain(keys)
keys.inject(self, :send)
end
end
class Array
def numeric_sort_by(*args)
(args.length == 1) ? lam = lambda {|el| Integer(el.send(args))} : lam = lambda {|el| Integer(el.send_chain(args))}
self.sort_by(&lam) rescue self
end
end
items.numeric_sort_by(:product, :sku)
Is it all makes any sense?
Can you please point me in the right direction to implement the syntax I mentioned above, if it is possible at all.
Thanks.
EDIT: the sku could be alphanumeric as well. Sorry for the confusion.
Try this solution.
There is no error handling.
It's just an idea to develop if you like it.
class Array
def numeric_sort_by(*args)
self.sort_by do |element|
object = element
args.size.times { |n| object = object.send(args[n]) }
object.to_f
end
end
end
items.numeric_sort_by 'product', 'sku'
So the straightforward implementation was:
sorted = items.sort_by { |item| Integer(item.product.sku) } rescue items
And the desired was:
items.numeric_sort_by { |item| item.product.sku }
I was manage to achieve it by yielding a block into the sort_by:
class Array
def numeric_sort_by(&block)
return to_enum :numeric_sort_by unless block_given?
self.sort_by { |element| Integer(yield(element)) } rescue self
end
end

Call the same function on a list and return a list with no duplicates?

I have this function:
medIntCategory = MedicalInterventionCategory.find_by_category_text(category.category.text)
However now I have a list of categories called categories.
I would like to execute the above code for each category and get back a list of medIntCategories, but with no duplicates.
Is there a simple way to do this since I am only dealing with integers?
in simple terms:
categoryList = []
for each category in categories do
categoryList += MedicalInterventionCategory.find_by_category_text(category.category.text)
end
But with duplicate checking
This sounds like a job for Array#map and Array#uniq:
category_list = categories.map{|category|
MedicalInterventionCategory.find_by_category_text(category.category.text)
}.uniq
#result=Array.new
##assuming that it returns an array
medIntCategory = MedicalInterventionCategory.find_by_category_text(category.category.text)
##get the first category obtained
#result << medIntCategory
if medIntCategory.present?
medIntCategory.each do |m|
##add in same array only if not present
if !#result.include?(m)
#result << m.find_by_category_text(c.category.text)
end
end
##return a unique value array
#result.flatten.compact.uniq unless #result.blank?
end
HOPE IT HELPS
I think this would work
category_list = []
categories.each do |category|
category_list << MedicalInterventionCategory.find_by_category_text(category.category.text).distinct
end

Ruby On Rails : create nested Array

I'm trying to make an array of objects, let's call it "categories", and I want each object in this array to have an array called "items" within it, so the result will be something like this:
[category:id=11, name="beer", items[1,2,3,4]]
I've tried this code:
#category ||= Array.new
#categoryItems ||= Array.new
#venues.categories.enabled.each do |category|
#category.push(category)
#categoryItems.push(category.items.enabled)
end
but I don't know how to name the items inside so I can use them in json afterward. How can I do this?
You can try create hash.
#category ||= Array.new
#venues.categories.enabled.each do |category|
hash = {}
hash[:category][:id] = category.id
hash[:category][:name] = category.name
hash[:category][:items] = category.items.enabled.pluck(:id).join(',')
#category << hash
end

Resources