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
Related
I do have a more elaborate index method in my events controller
def index
if params[:q]
params[:q][:combinator] = "and"
params[:q][:groupings] = []
split_geo = params[:q][:address_or_city_or_state_or_country_or_continent_cont_all].split((/(,\s*)+/))
split_geo.map! do |a|
I18n.transliterate a
end
split_geo.each_with_index do |word, index|
params[:q][:groupings][index] = { address_or_city_or_state_or_country_or_continent_cont_all: word }
end
end
#q = Event.ransack(params[:q])
#pagy, #events = pagy(#q.result(distinct: true))
end
In my events index page I have:
<h2>Upcoming Events</h2>
<br>
<%== pagy_bootstrap_nav(#pagy) %>
<br>
<div class="event-list-wrapper">
<% #events.upcoming_events.each do |event| %>
<%= render 'event', event: event %>
<% end %>
</div>
When I remove my upcoming_events scope, the records will display correctly.
In my event.rb model I have:
scope :upcoming_events, -> { where('event_start_date > ?', Time.now).or(where('event_end_date > ?', Date.yesterday)) }
Am I missing something in order to get pagy with work with my event scope?
The search method is non-crud action and map is a private method, restaurant, dish, location, pictures are models. these models data contains an array. so how I write test case for map method and search method. restaurant and location has HABTM association, and also restaurant and dish has HABTM association, restaurant and pictures have a polymorphic association, and also dish and pictures has a polymorphic association
def search
map
if params[:name]
#items = Dish.search(params[:name])
end
if params[:price]
#items = Dish.sortby_price(params[:price]).search(params[:name])
end
if params[:ratings]
#items = Dish.sortby_ratings(params[:name])
end
if params[:rating]
#items = Dish.sortby_rating(params[:rating])
end
if params[:category]
#items= Dish.sortby_dietary(params[:category]).search(params[:name])
end
if params[:restaurant]
#restaurants =
Restaurant.find(params[:restaurant])
#items = #restaurants.dishes
end
end
private
def map
#items = Dish.search(params[:name])
restaurants = []
locations = []
pictures = []
#items.each do |d|
#restaurants = d.restaurants
restaurants.push(#restaurants)
d.restaurants.each do |r|
#pictures = r.pictures
pictures.push(#pictures)
#locations = r.locations
locations.push(#locations)
end
end
gon.restaurants = restaurants
gon.locations = locations
gon.pictures = pictures
x = []
#items.each do |d|
#restaurants = d.restaurants
d.restaurants.each do |r|
x.push(r.id)
end
end
y = []
x.each do |x|
r = Restaurant.find(x)
d = r.dishes.count
y.push(d)
end
gon.dishes_count = y
end
Some people say that there is no need to test private methods. But in a company i'm working for we do test private methods.
For your case I'd recommend to do this:
test method #map separately from action #search. You need to check that gon, #items, #restaurants, #pictures, #locations objects got populated correctly.
You can test private methods by using method #send.
Example:
describe '#map' do
subject { controller.send(:map) }
# you would need to stub params method
before { allow(controller).to receive(:params).and_return({ name: 'my name' }) }
it { expect(controller.instance_variable_get(:#items)).to include/not be_blank/... }
end
Test method #search without actually calling method map.
Example:
describe '#search' do
before { allow(controller).to receive(:map) }
# you can set different context where you test cases with different parameters
context 'when params[:name] and params[:ratings] exist' do
before { get :search, { name: '...', ratings: '...' } }
it {...}
end
end
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
as in title I'm trying to create helper that does that but I'm struggling. I'm getting errors or simply empty list like this:
And I want to achieve this:
There is to much logic to simply put this code in view. A results is a hash where the key is a website id and value is either an array of bookmarks ids or just bookmark id.
My code:
module WebsitesHelper
def present_search_results(results)
content_tag(:ul, class: "websites-list") do
results.each do |key, value|
website = Website.find(key)
concat(content_tag(:li, website.url, class: "website-#{key}") do
bookmarks = website.bookmarks.select do |b|
if value.is_a?(Array)
value.include?(b.id)
else
value = b.id
end
end
content_tag(:ul, nil, id: "website-#{key}") do
bookmarks.each do |b|
content_tag(:li, b.title)
end
end
end)
end
end
end
end
If you want to stick with helpers, then something like this could help:
def present_search_results(results)
content_tag(:ul, class: "websites-list") do
results.map do |website_id, bookmarks|
bookmarks = [bookmarks] unless bookmarks.is_a?(Array)
content_tag(:li, class: "website-#{website_id}") do
website = Website.find(website_id)
concat(website.url)
concat(
content_tag(:ul, class: "bookmarks-list") do
bookmarks.map do |bookmark_id|
bookmark = Bookmark.find(bookmark_id)
content_tag(:li, bookmark.title)
end.reduce(:+)
end
)
end
end.reduce(:+)
end
end
But, in my opinion, that code is not easy to read, so you could use plain html instead, like this:
def present_search_results(results)
list = "<ul class='websites-list'>"
results.each do |(website_id, bookmarks)|
bookmarks = [bookmarks] unless bookmarks.is_a?(Array)
website = Website.find(website_id)
list += "<li class='website-#{website_id}'>#{website}"
list += "<ul class='bookmarks-list'>"
bookmarks.each do |bookmark_id|
bookmark = Bookmark.find(bookmark_id)
list += "<li>#{bookmark.title}</li>"
end
list += "</ul></li>"
end
list += "</ul>"
list.html_safe
end
I like this one better, since it is easier to read. But both with output the list you want.
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.