Merge a hash inside array - ruby-on-rails

I am new to Ruby and Rails, I have stuck in a situation that I need to create an array of hashes. Please see below the code:
def self.v_and_c items
result = []
items.try(:each) do |item|
result << item
if item.is_parent_variation
check_ancestor item
result << { :item_variation => #variations }
result << { :options => #options }
elsif item.is_parent_customization
check_ancestor item
result << { :customizations => #customizations }
result << { :ingredients => #ingredients }
end
end
result
end
Here is the output of the function:
{"items":[{"id":1,"name":"Cake"},{"item_variation":null},{"options":null}]}
But I wanted to do like this.
{"items":[{"id":1,"name":"Cake","item_variation":null, "options":null} ]}

You could try something like this:
def self.v_and_c items
result = []
items.try(:each) do |item|
item_hash = {}.merge(item)
if item.is_parent_variation
check_ancestor item
item_hash.merge!({ item_variation: #variations }).merge!({ options: #options})
elsif item.is_parent_customization
check_ancestor item
item_hash.merge!({ customizations: #customizations }).merge!({ ingredients: #ingredients})
end
result.push(item_hash)
end
result
end
Explanation:
For each iteration of loop, create an item_hash and merge all the requisite hashes in it and then push the resulting hash into the result array.
Few pointers:
Take care of new Ruby hash syntax
If check ancestor is needed in both if and else why not do it outside?

It should be simple like this, use .merge method
def self.v_and_c items
result = []
items.try(:each) do |item|
result << item
if item.is_parent_variation
check_ancestor item
result = result.merge { :item_variation => #variations }
result = result.merge { :options => #options }
elsif item.is_parent_customization
check_ancestor item
result = result.merge { :customizations => #customizations }
result = result.merge { :ingredients => #ingredients }
end
end
result
end

def self.v_and_c items
[].tap do |result|
items.try(:each) do |item|
result_hash = item.dup
if item.is_parent_variation
check_ancestor item
result_hash.merge!({ item_variation: #variations, options: #options })
elsif item.is_parent_customization
check_ancestor item
result_hash.merge!({ customizations: #customizations, ingredients: #ingredients })
end
result << result_hash
end
end
end

Related

Named scope with multiple values

I'm having some trouble with my named scope.
def self.by_status(status)
arr = status.split(',').map{ |s| s }
logger.debug "RESULT: #{arr.inspect}"
where(status: arr)
end
When I call this scope with more than one value, the result of arr = ["New", "Open"]
This does not return any results, while it should. If I try this command in the console: Shipment.where(status: ['New', 'Open']) I get the results that I'm expecting.
Am I missing something here?
Edit (added the call of the class method ):
def self.to_csv(options = {}, vendor_id, status)
CSV.generate(options) do |csv|
csv << column_names
if !vendor_id.blank? && status.blank?
by_vendor_id(vendor_id).each do |product|
csv << product.attributes.values_at(*column_names)
end
elsif !vendor_id.blank? && !status.blank?
by_vendor_id(vendor_id).by_status(status).each do |product|
csv << product.attributes.values_at(*column_names)
end
elsif vendor_id.blank? && !status.blank?
logger.debug "by_status result: #{by_status(status).inspect}"
by_status(status).each do |product|
csv << product.attributes.values_at(*column_names)
end
else
all.each do |product|
csv << product.attributes.values_at(*column_names)
end
end
end
end
Try this in your model:
scope :by_status, ->(*statuses) { where(status: statuses) }
Then in your code you can call:
Shipment.by_status('New', 'Open')
This has the flexibility to just take one argument, too:
Shipment.by_status('New')

Ruby on rails method partials?

I was wondering if it is possible to create a method partial in ruby on rails, for example I have this code;-
#cart = Cart.where(:user_id => current_user.id).first if user_signed_in?
#slots = #cart.slots.first
#slot_list = [#slots.slot_one, #slots.slot_two, #slots.slot_three, #slots.slot_four, #slots.slot_five,
#slots.slot_six, #slots.slot_seven, #slots.slot_eight, #slots.slot_nine, #slots.slot_ten]
#user_products = []
#product = []
#slot_list.each do |item|
if item.nil?
p 'Item empty'
else
#product << item
end
end
#product.each do |item|
items = Product.where(:product_id => item).first
#user_products << items
end
Written in multiple methods to get the #user_products, I was wondering if there was a way so I don't have to write this all the time and possibly run a method or use a partial?
Would it be worth creating a helper that does this and returns the #user_products variable?
I took my own advice and created two helpers, one to return the #user_products and another to return the #total.
I added the names of the methods to our helper_method
helper_method :user_is_admin?, :authenticate_admin!, :product_available?, :get_user_products!, :get_user_total!
then added these two methods at the bottom of the file;-
get_user_products!
def get_user_products!
#cart = Cart.where(:user_id => current_user.id).first if user_signed_in?
#slots = #cart.slots.first
#slot_list = [#slots.slot_one, #slots.slot_two, #slots.slot_three, #slots.slot_four, #slots.slot_five,
#slots.slot_six, #slots.slot_seven, #slots.slot_eight, #slots.slot_nine, #slots.slot_ten]
#user_products = []
#product = []
#slot_list.each do |item|
if item.nil?
p 'Item empty'
else
#product << item
end
end
#product.each do |item|
items = Product.where(:product_id => item).first
#user_products << items
end
return #user_products
end
get_user_total!
def get_user_total!
#total = 0
#cart = Cart.where(:user_id => current_user.id).first if user_signed_in?
#slots = #cart.slots.first
#slot_list = [#slots.slot_one, #slots.slot_two, #slots.slot_three, #slots.slot_four, #slots.slot_five,
#slots.slot_six, #slots.slot_seven, #slots.slot_eight, #slots.slot_nine, #slots.slot_ten]
#user_products = []
#product = []
#slot_list.each do |item|
if item.nil?
p 'Item empty'
else
#product << item
end
end
#product.each do |item|
items = Product.where(:product_id => item).first
#user_products << items
end
#user_products.each do |p|
#total += p.product_price
end
return #total
end
To use these methods inside whatever controller you then do the following;-
#user_products = get_user_products!
#total = get_user_total!
I assume this is in a controller?
What you want is to use plain old Ruby objects (POROs). So, you might have something like this:
class UserProducts
class << self
def get(options={})
#cart = Cart.where(:user_id => current_user.id).first if user_signed_in?
#slots = #cart.slots.first
#slot_list = [
#slots.slot_one,
#slots.slot_two,
#slots.slot_three,
#slots.slot_four,
#slots.slot_five,
#slots.slot_six,
#slots.slot_seven,
#slots.slot_eight,
#slots.slot_nine,
#slots.slot_ten
]
#user_products = []
#product = []
#slot_list.each do |item|
if item.nil?
p 'Item empty'
else
#product << item
end
end
#product.each do |item|
items = Product.where(:product_id => item).first
#user_products << items
end
end
end
Then, in your controller, you'd do something like:
class FooController < ApplicationController
def index
UserProducts.get(user_id: current_user.id)
end
end
So, UserProducts is essentially a service object. I think some people call them use cases. I tend to call them 'managers'. I put them in their own directory as app/managers/user_products.rb.

rails form nested hash in each do loop

Here is my method
def categories
#categories = {}
cat = Category.includes(:sub_categories).where('categories.status = ?', true).order(:id)
cat.each do |category|
category.sub_categories.each do |sub_category|
#categories[category.name] = { name: sub_category.name }
end
end
end
What I am trying to do is
Assume my category.name is mobile phones and my sub_category.name will have list of mobile phone brands. But my above method prints one sub category only because the name is variable but how to create nested hash.
Any other proper method of doing this
That's because you are overwriting the key in each subcategory. You have to store an array of subcategories for each key:
{"name1"=>[{"subcategory1"=>...},{"subcategory2"=>...}],"name2"=>[]}
Try this:
def categories
#categories = {}
cat = Category.includes(:sub_categories).where('categories.status = ?', true).order(:id)
cat.each do |category|
category.sub_categories.each do |sub_category|
#categories[category.name] = [] unless #categories[category.name]
#categories[category.name] << { name: sub_category.name }
end
end
end
Also if the category.status is a boolean, you can do:
cat = Category.includes(:sub_categories).where(status: true).order(:id)
And remove the sql query from a controller, which is ugly.
EDITED
As long as you have a hash of arrays, in order to render the view you will have to iterate again:
#categories.each do |category, values|
values.each do |sub_category|
subcategory["what you need"]
end
end

Ruby, Map, Object attributes

I have an object that looks like the below:
class Report
attr_accessor :weekly_stats, :report_times
def initialize
#weekly_stats = Hash.new {|h, k| h[k]={}}
#report_times = Hash.new {|h, k| h[k]={}}
values = []
end
end
I want to loop through the weekly_stats and report_times and upcase each key and assign it its value.
Right now I have this:
report.weekly_stats.map do |attribute_name, value|
report.values <<
{
:name => attribute_name.upcase,
:content => value ||= "Not Currently Available"
}
end
report.report_times.map do |attribute_name, value|
report.values <<
{
:name => attribute_name.upcase,
:content => format_date(value)
}
end
report.values
Is there a way I could map both the weekly stats and report times in one loop?
Thanks
(#report_times.keys + #weekly_stats.keys).map do |attribute_name|
{
:name => attribute_name.upcase,
:content => #report_times[attribute_name] ? format_date(#report_times[attribute_name]) : #weekly_stats[attribute_name] || "Not Currently Available"
}
end
If you are guaranteed nil or empty string in weekly_stats, and a date object in report_times, then you could use this information to work through a merged hash:
merged = report.report_times.merge( report.weekly_stats )
report.values = merged.map do |attribute_name, value|
{
:name => attribute_name.upcase,
:content => value.is_a?(Date) ? format_date(value) : ( value || "Not Currently Available")
}
end

Refactoring a switched block statement for group_by in Ruby on Rails

How can I rewrite this code so it's completely dynamic, and I don't have to use the case clause to manually list all possible values of #group?
# Grouping
#group = params[:group] if !params[:group].blank?
case #group
when 'category_id'
#ideas_grouped = #ideas.group_by { |i| i.category_id }
when 'status_id'
#ideas_grouped = #ideas.group_by { |i| i.status_id }
when 'personal_bias'
#ideas_grouped = #ideas.group_by { |i| i.personal_bias }
when 'business_value'
#ideas_grouped = #ideas.group_by { |i| i.business_value }
end
You can use some sort of meta programming
Above code can be refactored in one of the way is
if params[:group].present? && ["category_id","status_id","personal_bias","business_value"].include?(params[:group])
#ideas_grouped = #ideas.group_by { |i| i.send(params[:group]) }
end
If you need no white-listing:
#ideas_grouped = if (group = params[:group]).present?
#ideas.group_by(&group.to_sym)
end
If you need white-listing you may call include? first (see Amar's answer), but to add something new, let me push it with a declarative approach (Object#whitelist is left as an exercise for the reader, maybe comes from Ick):
#ideas_grouped = params[:group].whitelist(IdeaGroupers).maybe do |group|
#ideas.group_by(&group.to_sym)
end
Try out this:
#ideas_grouped = #ideas.group_by { |i| i.send(:"#{#group}")} if (#group = params[:group])
what about :
#group = params[:group] if !params[:group].blank?
#ideas_grouped = ideas_hash.fetch(#group)
def ideas_hash
{
'category_id' => ideas_by_category_id,
'status_id' => ideas_by_status_id,
'personal_bias' => ideas_by_personal_bias
'business_value' => ideas_by_business_value
}
end
def ideas_by_category_id
#ideas.group_by { |i| i.category_id }
end
def ideas_by_status_id
#ideas.group_by { |i| i.status_id }
end
def ideas_by_personal_bias
#ideas.group_by { |i| i.personal_bias }
end
def ideas_by_business_value
#ideas.group_by { |i| i.business_value }
end
I would also make ideas_hash and all of the others method private.
Okay, the last time I touched Ruby is too far back so I cannot give you an example. As far as I understand your problem you are doing a mapping (group -> accessor method) there. So either you use a map object or you build a mapping function using a lambda.

Resources