Multiple dropdown menus in rails 6 - mysql

Quite new to rails and can't figure out how to build a dynamic menu bar.
The database uses a simple method to store menu items:
id | title | url | parent | position
--------------------------------------------------------------------
1 Books /books 0 1 (first link on menu bar)
2 Fiction /books/fiction 1 1 (first link under Books menu)
3 CDs /cd 0 2 (second link on menu bar)
4 Fantasy /books/fantasy 1
5 Singles /cd/single 2
6 Albums /cd/album 2
I need to get all entries in this table and display them according to their parent.
All the items with parent 0 will show on the menu bar.
When user hovers any parent, all its child elements are listed below it.
When I did this in PHP, I used a loop that resulted in many database calls; One to get all the parent items in position order (as the admin can change the order in which the parent items appear on the menu), and then one for each parent to get all its child elements (also in position order).
I then added each resulting item to an array that was named after the parent_id and finally concatonated the arrays.
$item[$parent][] = '<a href...'
I know the SQL was inefficient (and the PHP probably was, too) but it didn't matter back then. Now it does.
Any idea how I can make this work efficiently in ruby?

Let's assume that the database table that stores this menu information is called nav_items. Then the Rails convention would be to have a model called NavItem:
# app/models/nav_item.rb
class NavItem < ApplicationRecord
has_many :child_menu_items, class_name: NavItem, inverse_of: :parent_menu_item, foreign_key: :parent
belongs_to :parent_menu_item, class_name: NavItem, foreign_key: :parent, inverse_of: :child_menu_items
scope :top_level, { where("parent = ?", 0) }
end
# app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
before_action :populate_nav_vars
private
def populate_nav_vars
#nav_items = NavItem.top_level
end
end
# app/view/layouts/_nav.html
# #nav_items is defined in the controller as NavItem.top_level
# the nav structure is shown here as nested <ul> lists
# it can be rendered appropriately with css (e.g. Bootstrap)
<ul>
<%- #nav_items.each do |top_level_item| %>
<li><%= top_level_item.title %></li>
<li>
<ul>
<%- top_level_item.child_menu_items.each do |child_item| %>
<li><%= child_item.title %>
<%- end %>
</ul>
</li>
<%- end %>
</ul>
You really shouldn't worry about performance, it should not be noticeable at all. I assume here that you want the navigation menu on every page, so the convenient place to populate the required variables is in ApplicationController and populate the variable in a before_action callback.
If the performance issue concerns you, then you should make the navigation page elements a view partial, and apply rails caching to the partial.

Related

Creating a Restaurant Menu should I use a has_many through or habtm association?

I am attempting to create a Restaurant project and design the database for the menu. While creating the database schema, I seem to keep spinning my wheels on how to implement the correct associations among the following models MenuItem, Menu, and a Menu Section. Every time I go through the has_many :through, and has_and_belongs_to_many, I can see the benefits of both but then when I implement them I get confused. Additionally, I have the confusion of a self join to the mix which further throws me off.
I think that I am over engineering but some input into the database schema would be helpful. More specifically, which association should I use for the following scenario in your opinion?
So far I have three classes/tables Restaurant, Menu, and MenuItem and the scenario I have crafted:
class Restaurant
has_many :menus (#Desert, Drinks, Dinner, Lunch, etc.)
end
class Menu
has_many :sections, class_name: "Menu", foreign_key: "sections_id"
belongs_to :section, class_name: "Menu"
has_many :menu_items, through: :sections
belongs_to :restaurant
end
## Context for the Menu and Sections
If I had a drinks menu with the following sections, Non Alcoholic,
Spirits, Wine, etc. These 'sections' are essentially menus with
their own menu items. This is why I put a self join on the Menu
class. Maybe Im mistaken.
class MenuItem
belongs_to :section, class_name: "Menu"
belongs_to :menu
end
####
The menu item for example of 'Yellow Tail Merlot' belongs to the
section "Wine" under the menu of "Drinks". This is precisely where
I am getting thrown off. I get the feeling that I'm making this
too complicated but when I say it out loud to myself it makes
perfect sense.
Ok so now here is version 2 with the habtm:
class Restaurant
has_many :menus (#Desert, Drinks, Dinner, Lunch, etc.)
end
class Menu
has_and_belongs_to_many :menu_items
end
class MenuItem
has_and_belongs_to_many :menus
has_and_belongs_to_many :sections, class_name: "Menu"
end
As you can see the above code in version 2 gets hairy. In fact, I don't even know if the sections part of has_and_belongs_to_many is possible. Anyways, this is my rationale taken from the Rails Associations docs.
A has_and_belongs_to_many association creates a direct many-to-many connection with another model, with no intervening model. For example, if your application includes menus and menu items, with each menu having many menu items and each menu item appearing in many menus, you could declare the models this way.
The quote is from the rails docs but I switched assemblies and parts with menus and menu items. Reading it that way makes complete sense. So now I ask, what do you guys think? Which would be better to use? Thank you.
should I use a has_many through or habtm association?
They both can be used to create similar many to many associations. The key difference is that has_and_belongs_to_many does not use a model for the join entities. While this sounds simple and appealing it does have some huge cons:
There is no practical way to add access additional columns on the join table. For example you want to add an "position" column to sort the items - you´re out of luck with has_and_belongs_to_many.
There is no way to directly query the join table.
So has_and_belongs_to_many is great for those zero complexity cases and not very good in real life. For everything else there is has_many through:.
class Restaurant
has_many :menus
has_many :menu_sections, through: :menus
has_many :menu_items, through: :menu_sections
end
class Menu
belongs_to :restaurant
has_many :menu_sections
has_many :menu_items, through: :menu_sections
end
class MenuSection
belongs_to :menu
has_one :restaurant, through: :menu
has_many :menu_items
end
class MenuItem
belongs_to :menu_section
has_one :menu, through: :menu_section
has_one :restaurant, through: :menu
end
I'd use belongs_to and instead of using a self join on the Menu table, create a new class/table called Section.
Updated would be:
class Restaurant
has_many :menus
end
class Menu
has_many :menu_items
has_many :sections
belongs_to :restaurant
end
class Section
belongs_to :menu
has_many :menu_items
end
class MenuItem
belongs_to :menu
belongs_to :section
end
The sections you describe are more categories, e.g. Wine is an alcoholic drink, Wine is the menu item and 'alcoholic drink' is the category. You could even go one step further and have Wine associated with two sections, 'alcoholic' and 'drink'.

How to show all the Users who have liked my specific Items

I apologize for the newbie questions still relatively new to rails. I'm trying to show all the users who have liked a current user's specific items. With the help of the SO community and looking at different Rails guides - I'm 85% there. Currently, I'm displaying all users who have liked all items (not just my specific ones which is what I want) I've listed below all the relevant simple code - thank you so much guys!!
Index.html.erb
<%- #likers.each do |liker| %>
<%= image_tag liker.avatar, width: 25, class: "css-style" %>&nbsp
<%= liker.username %>
<% end %>
Items_controller
def index
#items = Item.order("created_at DESC")
if current_user.present?
#likers = Item.where("user_id", current_user.id).map(&:users).flatten
end
end
So, you want all #likers to be all the people who like the current_user's items?
#likers = current_user.items.map(&:likes).flatten.map(&:user).flatten.uniq
I've added the uniq so if a user likes more than one of these posts, you won't see them turn up multiple times. You can omit that, of course, if you want duplicates.
I'm also making some guesses about your other models based on your previous question, so you might need to tweak it depending on your actual implementation.

Rails/Sqlite 3 - Displaying table data on page

I'm very very very new to Rails, and I've got a fast approaching deadline to code my first rail app as part of a challenge.
I've so far created the basic blog application, to get my head around how rails works.
I need to display a list of all data held within a table on a page in my site. In blog, I've got an index page - which lists all blog posts so I've been trying to follow the same process.
I've created a populated table in my database, and a controller - but when I try and display info from that database on my page I get the following error:
uninitialized constant RecordsController::Aliens
class RecordsController < ApplicationController
def index
#records= Records.all
end
end
All I want to achieve is a simple table, which has the list of my records in, so far I'm not having much luck retrieving the data from the database.
Any help appreciated!
I assume you want to list all aliens am I right?
As your model is called Alien you can call it like that:
def index
#aliens = Alien.all
end
And then in your view something like:
<% #aliens.each do |alien| %>
<div class="alien">
<ul>
<% alien.attributes.each do |attr_name, attr_value| %>
<li><strong><%= attr_name %>:</strong> <%= attr_value %></li>
<% end %>
</ul>
</div>
<hr>
<% end %>

Add instruction to drop down select box in Rails?

I am displaying a list of all users (from a Users model) in my Rails 3 application. In a helper (in this case it's Faults) I have the following:
def user_all_select_options
User.all.map{ |user| [user.name, user.id] }
end
In the view I have the following:
<%= f.select :user_id, user_all_select_options %>
This simply creates a drop down list with the first users name pre-selected as it is at the top of the list. What I am trying to do is add an instruction(?) at the top of the list that is unselectable. The idea is to prevent the top user from getting constantly accidentally set as the required user even when they don't want to be.
So rather than the select box looking like this:
I would like it to look like this:
Is this possible, if so, how??
Pass the :prompt option.
<%= f.select :user_id, user_all_select_options, :prompt => 'Select a user' %>

Reconstruction User based Modular Website

I am developing a website in Ruby on Rails, MySQL, and Javascript.
The website is modular, meaning that a user can customize their homepage and drag and drop one module to another area where it will live. there are three columns where the module can be dropped. (Think iGoogle or Netvibes)
What is the best way to store this data, so that when the user returns their homepage can be reconstructed quickly?
I've considered doing it in a way where each module get's an ID that corresponds to the user, it's row and it's positiong in that row. (So something like.. user|column|row, would equal 1204|3|27, meaning that for user #1204 this module would be in column #3 and 27 spaces from the top.
Then when the user returns, just loop through, adding 1 to each until it reaches the end of the column and start again at the other one until all 3 columns are populated.
I feel like this would be very slow though, and there must be a better way to do it.
Any suggestions? Mostly looking for Database structure, but if you have some Rails code that would corrilate to this I wouldn't mind seein git.
I think the best is to keep your data in a single text field
For example 1204|3|27 should be in a text field ... with a good index per user id you should get the configuration very very fast. After that you just need to "explode" your configuration for "|" .
Regards
I say model it very straightforwardly:
class ModuleInstallation < AR::Base
belongs_to :user
belongs_to :module
validates_presence_of :column
validates_presence_of :row
end
class User < AR::Base
has_many :module_installations, :order => :column
has_many :modules, :through => :module_installations
end
Then let your controller handle any more sorting, something like this (untested, but look through it and the documentation for the methods I'm using to get the concepts):
#columns = current_user.module_installations.group_by(&:column)
#columns.each { |column, modules| modules.sort! { |x,y| x.row <=> y.row } }
Then your view is relatively simple:
<% #columns.each do |column, modules| %>
<div class="column-<%= column %>">
<% modules.each do |module| %>
<div class="module">
<%= module.to_html %>
</div>
<% end %>
</div>
<% end %>