simple_form many to many association as tree? - ruby-on-rails

I am trying to build a UI for updating a model ("Profile") that has a many to many relationship with another model ("Category"). The "Category" model has a self referential relationship with itself for "Sub Categories".
In my simple_form I want to display the categories as checkboxes with their sub categories nested below them as check boxes.
In my current code all I have is this for the association field:
= f.association :categories, as: :check_boxes, collection: #categories
I am just retrieving the top level categories into the variable #categories.
I'm not sure where to go from here. What is the best way to do this?

Ancestry
We've done this before using the ancestry gem:
The problem you have is your self-referential association won't give you the scope required to create a "real" nested dropdown; it has to be able to consider all the nested data.
Instead, what we did was to firstly employee the ancestry gem, and then use a partial and helper to get the nested dropdown affect:
#app/models/category.rb
Class Category < ActiveRecord::Base
has_ancestry
end
Display
If you store the dependent data like that, it allows you to create a partial-based nested effect:
#app/views/admin/categories/index.html.erb
<%= render partial: "category", locals: { collection: collection } %>
#app/views/categories/_category.html.erb
<!-- Categories -->
<ol class="categories">
<% collection.arrange.each do |category, sub_item| %>
<li>
<!-- Category -->
<div class="category">
<%= link_to category.title, edit_admin_category_path(category) %>
<%= link_to "+", admin_category_new_path(category), title: "New Categorgy", data: {placement: "bottom"} %>
</div>
<!-- Children -->
<% if category.has_children? %>
<%= render partial: "category", locals: { collection: category.children } %>
<% end %>
</li>
<% end %>
</ol>
Dropdown
#app/helpers/application_helper.rb
Class ApplicationHelper
def nested_dropdown(items)
result = []
items.map do |item, sub_items|
result << [('- ' * item.depth) + item.name, item.id]
result += nested_dropdown(sub_items) unless sub_items.blank?
end
result
end
end
This will allow you to call:
= f.input :categories, as: :select, collection: nested_dropdown(#categories)

Related

How to pass more than one parameters to render in rails?

Below is my review.html.erb:
<% provide(:title, 'All reviews') %>
<h1>All reviews</h1>
<ol class="reviews">
<%= render #reviews %>
</ol>
<%= will_paginate #reviews %>
And my _review.html.erb looks like:
<li>
<p>Student: <%= Student.find(review.student_id).name%></p>
<p>Score: <%= review.score%></p>
<p>Review: <%= review.review%></p>
<p>Created at: <%= review.created_at%></p>
</li>
How can I pass #students as well to render for example?
I tried <%= render #reviews, #students %> in review.html.erb and Student: <%= student.name%> in _review.html.erb. It didn't work.
You don't actually need to pass multiple parameters. You just need to setup the assocations between reviews and students:
class Student < ApplicationRecord
has_many :reviews
end
class Review < ApplicationRecord
belongs_to :student
# optional but avoids a law of demeter violation
delegate :name, to: :student, prefix: true
end
<li>
<p>Student: <%= review.student_name %></p>
<p>Score: <%= review.score %></p>
<p>Review: <%= review.review %></p>
<p>Created at: <%= review.created_at %></p>
</li>
To avoid a N+1 query issue you should use includes or eager_load to load the student with the reviews:
#reviews = Review.includes(:student)
.all
If you do actually want to pass additional arguments when rendering a collection (which isn't needed here) you do it with local assigns:
<%= render #reviews, locals: { foo: 'bar' } %>
This will be available in the partial as foo or local_assigns(:foo).
Reivew table and students are related
In _review.html.erb , you don't need use Student.find(review.student_id)
<li>
<p>Student: <%= review.student&.name%></p> // changed
....
</li>

How to loop through a self referential model in Ruby on Rails

I'm new to ruby on rails and I'm building a wiki app where the navigation is to be sorted by categories. Each article, or page, can belong to a category, but a category can also be a sub-category of another category. An administrator will be able to create new categories or sub-categories calling for a dynamic approach to generating a list of categories for the menu. I'm trying to figure out how to display a list of all parent categories and all of their children and grandchildren categories where the menu would look something like this:
1. Parent1
1.a Child1
1.b Child2
2. Parent2
2.a Child1
2.a.1 Grandchild1
I currently have some nested loops in my view which kind of work, but it's not dynamic since it will only show the first two generations, and I would have to repeat the code to show more.
Model:
class Category < ApplicationRecord
has_many :sub_categories, class_name: "Category", foreign_key: "category_id"
belongs_to :category, class_name: "Category", optional: true
end
Controller:
class CategoriesController < ApplicationController
def index
#sorted_categories = Category.order(:sort_number).where("category_id IS NULL")
#sub_categories = Category.order(:sort_number).where("category_id IS NOT NULL")
end
end
View:
<% if #categories.nil? %>
<h3>There are currently no categories.</h3>
<% else %>
<ul>
<% #sorted_categories.each do |c| %>
<li><%= c.name %><%= link_to 'Move Up', categories_move_up_path(c) %> Sort:<%= c.sort_number %></li>
<% #sub_categories.each do |s| %>
<% if s.category_id == c.id %>
<ul>
<li>
<%= s.name %><%= link_to 'Move Up', categories_move_up_path(s) %> Sort:<%= s.sort_number %>
</li>
</ul>
<% end %>
<% end %>
<% end %>
</ul>
<% end %>
Any advice would be greatly appreciated, thanks!
Have a look at the acts_as_list gem, it does exactly what you want.
It will define a parent_id column, and each object will be a child of a parent, so that you can create infinite tree of categories ans sub-categories.
It also provides the methods to move objects up and down.

Rails ancestry gem + has_many

I have 2 models: Post and Category
Category has_many Posts,
Post belongs_to Category,
Category model uses ancestry gem,
the goal is to get all posts that belongs_to the given category and to all its ancestors. Should I simply use a loop for this or there is some smarter way to do this?
You can use this to get the posts that belong the given category or one of its ancestors:
Post.where(:category_id => category.path_ids)
The ancestry gem passes nested hash objects when you use it, so you can select a master node and then use the hash object as a way to iterate through & get all of its ancestors:
#controller
#category = Category.find params[:id]
#view
render partial: "category", locals: { category: #category }
#partial
<ol class="categories">
<% category.each do |category, sub_item| %>
<li>
<%= category.name %>
<% if category.has_children? %>
<%= render partial: "category", locals: { category: category.children } %>
<% end %>
</li>
<% end %>
</ol>

Rails 3.2 Ajax Update Div when Text Field Populated Many to Many Form

In the end I would like a text field that passes a client_id to the partial. I would like to do this asynchronously so the shipment_products partial would dynamically change when the textfield value was updated. What is the best way to do this?
In index.html.erb
<!-- Text Field Here-->
<div id="available_products">
<%= render "shipment_products" %>
</div>
In _shipment_products.html.erb
<div id="shipment_products_container">
<h3>Assign Products to Ship<\h3>
<ul class="shipment_products" id="shipment_products">
<% Product.by_client(client_id).each do |product|%> <!-- TextField value passed here -->
<%= content_tag_for :li, product, :value => product.id do %>
<%= hidden_field_tag("shipment[product_ids][]", product.id) %>
<%= product.product_name %>
<% end %>
<% end %>
<\ul>
</div>
Model relationships:
Models and Relationships
Shipment has_many :products :through => :shipment_products
Product has_many :shipments :through => :shipment_products
ShipmentProducts belongs_to :shipment, belongs_to :product
Product belongs_to :client
Client has_many :products
This is similar to what I want in the end.
Since I do not know what's in your controller and how you make routes, I made some suggestions. Change a few things to the actual. I assume that you do not need to change index action (if only to add respond_to js)
index.html.erb
<%= form_tag shipment_path, remote: true do %> - points to index action
<%= text_field_tag :client_id %>
<%= submit_tag :load %>
<% end %>
<div id="available_products">
<%= render "shipment_products" %>
</div>
index.js.erb
$('#available_products').html("<%= j render "shipment_products" %>");
I think you should use nested model concept for this please refer-
http://railscasts.com/episodes/197-nested-model-form-part-2?view=asciicast

How to get model objects in the form with rails check_box?

How do i get checkbox values in the form from the database? I want the form to bring the existing sub category name,and when i check the checkbox to select that particular category name and not create a new one.I have tried ryan bate's railscast but was no help to me. The realationship here is Category has_many SubCategories and SubCategory belongs_to Category.Thank you.
<%= form_for #category ,:url=>{:action =>"create"} do |f| %>
<%=f.text_field :category_name %>
<%= f.fields_for :sub_categories do |s| %>
<% #category.sub_categories.each do |sub|%>
<%=s.check_box "name",{},sub.id %> <!--need help here-->
<%end%>
<%end%>
<%=f.submit "submit"%>
<%end%>
Based on the exchange in the comments, it appears that you want to use the checkboxes to assign SubCategory objects to a Category object. If that's the case, you're association should be that a Category has_and_belongs_to_many :sub_categories. Then your form would look something like:
<%= form_for #category ,:url=>{:action =>"create"} do |f| %>
 <%=f.text_field :category_name %>
<% SubCategories.each do |sc| %>
<div>
<%= check_box_tag :sub_category_ids, sub_category_id, #category.sub_categories.include?(sc), :name => 'category[sub_category_ids][]' -%>
<%= label_tag :sub_category_ids, sc.name -%>
</div>
<% end -%>
<% end %>
Which will show a category form and then list all of the sub_categories that can be assigned or unassigned by checking the checkboxes.
You will also need a join table "categories_sub_categories" for this new association and logic (likely in your controller) to handle the actual assignment.
example for your category_controller.rb
def create
#category = Category.find(params[:id])
#use the checked sub_category_ids from the form to find and assign the sub_categories.
assigned_sub_categories = SubCategory.find(params[:category][:sub_category_ids]) rescue []
#category.sub_categories = assigned_sub_categories
if #category.save
…
else
…
end
end

Resources