Looping through a nested attribute with external model details - ruby-on-rails

three-months-in beginner with Ruby on Rails here, so I apologize if any of my terminology is incorrect. I have a question about referencing outside models from a nested attribute.
I have three models. Tasks with nested attributes for Task Products, and a separate Items table with pre-populated products.
Each Task has many Task Products, and the Task Product has a "product_id" column which is in reference to an existing product in the Item table. In creating an table index of each Task, I am having trouble figuring out how to have the nested Task Product's product_id's list out the Item instead of just the bare id.
Here's the code I'm working with:
tasks_controller.rb -->
def dashboard
#tasks = Task.includes(:task_products, :storeorder).last(100)
#tasks.each do |task|
task.storeorder do |storeorder|
end
task.task_products.each do |task_product|
#item = Item.where(:id => task_product.product_id)
end
end
end
task.rb -->
class Task < ApplicationRecord
has_many :task_products
accepts_nested_attributes_for :task_products
end
task_product.rb -->
class TaskProduct < ApplicationRecord
belongs_to :task
has_many :items
end
item.rb -->
class Item < ActiveRecord::Base
belongs_to :task_product
def item_select
"#{vendor_name} (#{description})"
end
end
dashboard.html.erb -->
<td>
<% t.task_products.each do |tp| %>
# Existing code that lists each task product in a list on the table:
<p><%= tp.product_id %></p>
# The ideal code I would like to run:
<p><%= link_to #item.item_select, item_path(id: #item.id) %>
<% end %>
</td>
Any ideas how I can run the #item call as it pertains to the 'tp.product_id' code in the html file?
Appreciate any help I can get. Searching for this issue has left me with many purple links, but none of which address this particular issue.
EDIT: In case anybody happens upon this that was in the same predicament as me, I have one recommendation: Learn your associations.
Updated code:
tasks_controller.rb -->
def dashboard
#tasks = Task.includes(:task_products, :storeorder).last(100)
end
task.rb -->
class Task < ApplicationRecord
has_many :task_products
accepts_nested_attributes_for :task_products
end
task_product.rb -->
class TaskProduct < ApplicationRecord
belongs_to :task
belongs_to :item, foreign_key: :product_id
end
item.rb -->
class Item < ActiveRecord::Base
has_many :task_products, foreign_key: :product_id
def item_select
"#{vendor_name} (#{description})"
end
end
dashboard.html.erb -->
<td>
<% t.task_products.each do |tp| %>
<% tp.items.each do |item| %>
<p><%= link_to item.item_select, item_path(item) %></p>
<% end %>
<% end %>
</td>

First, by using Item.where in your controller, you're actually setting #item to a collection of Items. Second, by setting it inside a loop, you're overwriting it with each TaskProduct, so only the last one will be accurate in the view.
I'm assuming you want to list [a subset of] every Item for every Task. In that case, you'd be better not to set them in your controller at all:
def dashboard
#tasks = Task.includes(:task_products, :storeorder).last(100)
end
Instead, just loop through them in the view:
<% #tasks.each do |t| %>
Task <%= t %>
<% t.task_products.each do |tp| %>
TaskProduct <%= tp %>
<% tp.items.each do |item| %>
<p><%= link_to item.item_select, item_path(item) %></p>
<% end %>
<% end %>
<% end %>

I don't see any need of all the loops in your dashboard_controller.rb
#tasks.each do |task|
task.storeorder do |storeorder|
end
task.task_products.each do |task_product|
#item = Item.where(:id => task_product.product_id)
end
end
Here's what you need in html.erb:
# The ideal code I would like to run:
<% tp.items.each do |item| %>
<p><%= link_to item.item_select, item_path(item) %>
<% end %>

Related

List sections with books associated with them

I'm fairly new to ruby on rails and this has been kind of an interesting problem since this seems easy to implement in other languages but I don't know how to tackle it in this one. There was a similar post to this but it had two separate models which I would like to avoid.
This is my end goal:
Section Name
Book A, author
Book B, author
Section Name
Book C, author
Book D, author
Ideally, I'd like to have books be one model, so my model looks like this:
Book Model
class Book < ApplicationRecord
validates :section, :title, :author, presence: true
Book Controller
def index
#books = Book.all
I'm assuming I would need some sort of view that has it list it like below but I'm not sure how to go from there.
<% #sections.each do |section| %>
<% Book.each do |book| %>
<%= book.name %>
<% end %>
<% end %>
Any help would be very appreciated!
Firstly you need migration and associations between these models
change_table :books do |t|
t.belongs_to :section, foreign_key: true, null: false
end
class Book < ApplicationRecord
belongs_to :section
class Section < ApplicationRecord
has_many :books, dependent: :destroy
And in view you can iterate through sections and separately through evert section books
<% #sections.each do |section| %>
<div><b><%= section.name %></b></div>
<ul>
<% section.books.each do |book| %>
<li>
<%= book.name %>, <%= book.author %>
</li>
<% end %>
</ul>
<% end %>
what you need is this:
<% #sections.each do |section| %>
<% section.books.each do |book| %>
<%= book.name %>
<% end %>
<% end %>

Where's the textNode in the view coming from?

# controller
def index
#teams = TeamMember.where(user: current_user)
end
# view
<%= #teams.each do |t| %>
<h2><%= t.project.name %></h2>
<p>Where's the line below this coming from?</p>
<!-- what's happening here? -->
<% end %>
The result in the browser looks as follows. It returns #projects as a string. Where is this coming from and how do I remove it?
ERB
You trigger ERB by using embeddings such as <% %>, <% -%>, and <%= %>. The <%= %> tag set is used when you want output.
You have used <%= %> for looping projects and the block form of Array#each returns the original Array object, which is why it prints the projects result at the end.
you have to use <% %> for #projects.each do |p| instead of <%= %>
# view
<% #projects.each do |p| %>
<h2><%= p.name %></h2>
<% end %>
Team member Model
class TeamMember < ApplicationRecord
belongs_to :user
belongs_to :project
end
User Model
class User < ApplicationRecord
has_many :team_members
end
Project Model
class Project < ApplicationRecord
has_many :team_members
end
Controller
def index
#teams = current_user.team_members
end
View
<% #teams.each do |t| %> // Remove '=' from the loop itereation
<h2> <%= t.project.name %> </h2>
<% end %>

Rails accepts_nested_attributes_for and a ternary association

tl, dr: Is it possible to populate a ternary association with accepts_nested_attributes and consequently pass the tests from this PR?
Basically I created four models A, B, C and Abc and the latter is a join table for a ternary association with the other models. The problem is using accepts_nested_attributes_for I can't seem to save the whole ternary association. Instead I either create two join models with binary associations (A-B, B-C or A-C) or the database complains about missing foreign keys. Checkout this test and the code:
class A < ApplicationRecord
has_and_belongs_to_many :bs, join_table: :abcs
has_and_belongs_to_many :cs, join_table: :abcs
accepts_nested_attributes_for :bs, :cs
end
class B < ApplicationRecord; end
class C < ApplicationRecord; end
class Abc < ApplicationRecord
belongs_to :a
belongs_to :b
belongs_to :c
end
class AsController < ApplicationController
def new
#a = A.new
#a.bs.build
#a.cs.build
end
def create
#a = A.new(a_params)
if #a.save
redirect_to(new_a_path)
else
render(:new)
end
end
def a_params
params.require(:a).permit(:name, bs_attributes: [:name], cs_attributes: [:name])
end
end
<%= form_for(#a) do |f| %>
<% if #a.errors.any? %>
<ul>
<% #a.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
<% end %>
<%= f.label(:name) %>
<%= f.text_field(:name) %>
<ul>
BS:
<%= f.fields_for(:bs) do |ff| %>
<li>
<%= ff.label(:name) %>
<%= ff.text_field(:name) %>
</li>
<% end %>
</ul>
<ul>
CS:
<%= f.fields_for(:cs) do |ff| %>
<li>
<%= ff.label(:name) %>
<%= ff.text_field(:name) %>
</li>
<% end %>
</ul>
<%= f.submit %>
<% end %>
I've also tried creating it from the ternary table which works for one single association among the models (A-B-C), but it's impossible for multiple (A1-B1-C1, A1-B2-C1). Check out the code:
class AsController < ApplicationController
def new
#abc = Abc.new
#abc.build_a
#abc.build_b # 2.times { #abc.build_b } only overwrites #abc.b
#abc.build_c
end
end
It seems no matter what Rails can only create associations between two models leaving the ternary model missing one association.
I have isolated the issue as a repository's pull request as you can see HERE

Access Outside Model Attribute in View layer

I have three models, Movie and MovieCheckout and User. I made the correct associations between the classes.
In my index, I want to display each movie and who checked it out.
<% #movie.each do |movie| %>
<% MovieCheckout.find_by movie_id: movie.id %> //I want to display MovieCheckout's user_id here.
<% end %>
I would like to know if there was a code that went something like this
<% MovieCheckout.find_by movie_id: movie.id %>.user_id
I was thinking of placing this logic in my MovieController
def index
#total_movies = Movie.count
#moviecheckout = MovieCheckout.find_by movie_id: movie.id
end
But I cannot reference movie outside of the view layer index.
You're trying to execute Ruby code outside the <% %> tags. This means the .user_id part is not evaluated.
Try something like this:
<% MovieCheckout.find_by(movie_id: movie.id).user_id %>
And indeed, this stuff doesn't belong in a View.
class MovieCheckout < ActiveRecord::Base
belongs_to :movie
belongs_to :user
end
class Movie < ActiveRecord::Base
has_many :checkouts, class_name: MovieCheckout, inverse_of: :movie
end
<% #movie.each do |movie| %>
<% for co in movie.checkouts %>
<%= co.user.name %>
<% end %>
<% end %>

How to put conditions on associated records?

Here are my models:
class Checklist < ActiveRecord::Base
has_many :checklists_tasks
has_many :tasks, through: :checklists_tasks
end
class ChecklistsTask < ActiveRecord::Base
belongs_to :checklist
belongs_to :task
end
class Section < ActiveRecord::Base
has_many :tasks
end
class Task < ActiveRecord::Base
belongs_to :section
has_many :checklists_tasks
has_many :checklists, through: :checklists_tasks
end
I then have a view like this:
<% #sections.each do |section| %>
<h2><%= section.name %></h2>
<ul>
<% section.tasks.each.do |task| %>
<li><%= task.name %></li>
<% end %>
</ul>
<% end %>
How do I query Section and make sure that the tasks associated with each Section are all associated with a certain Checklist?
To clarify, /checklists/1/show and /checklists/2/show should use the view above and ouput the same sections, but the tasks in the sections could be different.
If you mean to show the section's tasks which are also belong to current checklist, try this:
<% #sections.each do |section| %>
<h2><%= section.name %></h2>
<ul>
<% # get the checklist's id in url => /checklists/:id %>
<% check_list_id = params[:id] %>
<% # query tasks using section_id and check_list_id %>
<% tasks = Task.find_by(section_id: section.id, check_list_id: check_list_id) %>
<% tasks.each.do |task| %>
<li><%= task.name %></li>
<% end %>
<% end %>
Ended up solving this by adding a method to Section.
class Section < ActiveRecord::Base
has_many :tasks
def tasks_by_checklist(checklist)
if checklist != nil
Task.where(section_id: self.id, checklists_tasks: { checklist_id: checklist.id}).includes(:checklists_tasks)
else
self.tasks
end
end
end
Then updated the view to use this new method:
...
<% section.tasks_by_checklist(#checklist).each.do |task| %>
...
Since #checklist is nil in /tasks (index), but set in /checklist/:id/show, the same view works as intended for both controller actions.
I'm still curious if there is a way to do it by editing #sections = Section.all in the controller to something more clever.

Resources