Rails 3.1 :has_many, :through complex ordering - ruby-on-rails

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

Related

Rails many to many relations with has many through relationships

I have the following models using the has_many :through relationship.
A recipe can have many seasons.
A season can have many recipes.
Recipe
class Recipe < ApplicationRecord
attribute :name
has_many :recipe_seasons
has_many :seasons, through: :recipe_seasons
end
Season
class Season < ApplicationRecord
has_many :recipe_seasons
has_many :recipes, through: :recipe_seasons
end
Recipe Season
class RecipeSeason < ApplicationRecord
belongs_to :recipe
belongs_to :season
end
I'm currently displaying all recipes on the index page using the the following
Controller
def index
#recipes = Recipe.all
render index: #recipes, include: [:recipe_seasons, :seasons]
end
View
<% if #recipes.present? %>
<% #recipes.each do |recipe| %>
<%= link_to recipe.name,[recipe] %>
<% end %>
What I want to do is to have the seasons displayed with the each recipe. A recipe can have more than one season and so I added another for loop inside the existing one for recipes.
I have so far tried:
<% #recipes.each do |recipe| %>
<% recipe.seasons.each do |season| %>
<%= link_to recipe.name,[recipe] %>
<%= season.name %>
<% end %>
<% end %>
<% end %>
Current Behaviour
Recipe 1 - Season 1
Recipe 1 - Season 2
Expected Behaviour
Recipe 1 - Season 1, Season 2
Recipe 2 - Season 4
You must include the seasons in the body parameter of the link_to (the text displayed in the link)
<% #recipes.each do |recipe| %>
<%= link_to "#{recipe.name} - #{recipe.seasons.map(&:name).join(', ')}", [recipe] %>
<% end %>

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

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

Refactoring view loops to use fewer queries via includes with a HABTM relationship

I have been trying to refactor this code to reduce the db calls by possibly using "includes". I would like to replace the three nested loops in the view. Tried various options but got stuck... I'm still getting familiar with active record querying.
How can I make this more efficient with less queries?
Is using includes the best option?
If so, how do I access the various fields through my HABTM relationships?
Thanks.
Models:
class Category < ActiveRecord::Base
has_many :pub_types
end
class PubType < ActiveRecord::Base
belongs_to :category
has_and_belongs_to_many :issues
end
class Issue < ActiveRecord::Base
has_and_belongs_to_many :pub_types
has_many :images, :dependent => :destroy
end
Controller:
def home
#categories = Category.all
#issues_and_pubs = Issue.joins(:pub_types).uniq
end
View:
<% #categories.each do |category| %>
<%= category.name %>
<% #issues_and_pubs.where(:pub_types => {:category_id => ["#{category.id}"]}).each do |issue| %>
<% issue.images.each do |img| %>
<% if img.featured == true %>
<%= cl_image_tag img.image, :width => 295, :height => 155, :alt => img.name, :crop => :fill %>
<%= link_to issue.name, issue %>
<% end %>
<% end %>
<%= issue.issue_date.try(:strftime, "%B %d, %Y") %>
<%= issue.pub_types.map(&:name).join(", ") %>
<% end %>
<% end %>
Try this instead.
Add this to Category:
has_many :issues, through: :pub_types
Controller:
#categories = Category.includes(:issues => :images)
View:
#categories.each do |category|
category.issues.each do |issue|
issue.images.each do |image|
issue.pub_types # might still result in N+1, haven't tested
On the note above about N+1 on pub_types, I have had occasions where I've eager loaded associations, but Rails has not taken them into account when calling from children to parents. One approach I've used in the past is to be explicit with the references:
Category.includes(:issues => [:pub_types, :images])
Without the has_many through:, this would look rightly peculiar:
Category.includes(:pub_types => [:issues => [:pub_types, :images]])

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