How to put conditions on associated records? - ruby-on-rails

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.

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

Looping through a nested attribute with external model details

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 %>

Rails 3.1 :has_many, :through complex ordering

I have an orders model with line_items and vendors. When displaying an order, I want to group line_items by vendors.
class LineItem < ActiveRecord::Base
belongs_to :order
belongs_to :vendor
end
class Order < ActiveRecord::Base
has_many :line_items
has_many :vendors, :through => :line_items
end
class Vendor < ActiveRecord::Base
has_many :line_items
end
I want to display a sorted list of vendors and line items:
You have placed an order for the following items:
Vendor 1
Line item 1
Line item 2
Line item 3
Vendor 2
Line Item 4
Line Item 5
...
My current thought is
order.vendors.each do |a_vendor|
a_vendor.name
!!?? AND THEN WHAT GOES HERE ??!!
end
please help. I can't figure this out. maybe this could be done by sorting?
If the order only has one vendor, then I only want to show the one vendor.
How about this:
<% #order.line_items.all.group_by{|i| i.vendor}.each do |vendor, items| %>
<%= content_tag :h2, vendor.id %>
<ul>
<% items.each do |i| %>
<%= content_tag :li, i.id %>
<% end %>
</ul>
<% end %>
[edits]
sort_by(&:vendor) is the same as sort_by{|v| v.vendor}, but the block-style syntax gives you a little more flexibility. For example, you can sort by vendor name in the controller with:
#sorted = #order.line_items.all.group_by(&:vendor).sort_by{|vendor, items| vendor.name}
Then in the view:
<% #sorted.each do |vendor, items| %>
<%= content_tag :h2, vendor.name %>
<ul>
<% items.each do |i| %>
<%= content_tag :li, i.id %>
<% end %>
</ul>
<% end %>
Alternatively, can sort in the model by adding an SQL snippet
to the :order option of the has_many association.
(See: http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html#method-i-has_many)
class Order < ActiveRecord::Base
has_many :line_items, :order => "item_number"
has_many :vendors, :through => :line_items, :order => "name"
end
Then your view is very simple:
<% #order.vendors.each do |vendor| %>
<h3><%= vendor.name %></h3>
<ul>
<% vendor.line_items.where(:order_id=>#order.id).each do |item| %>
<li><%= item.description %></li>
<% end -%>
</ul>
<% end -%>

Resources