Removing an anti-pattern from views to controller - ruby-on-rails

I have two models with a relation:
Company has_many :quotes
I want to remove an anti-pattern logic and place it in the controller:
<%= Quote.where(company_id: c.id).where(paid: true).count %>
The setup:
Controller:
#companies = Company.all
Views:
<% #companies.each do |c| %>
<%= Quote.where(company_id: c.id).where(paid: true).count %>
<% end %>
I want to show total quotes for each company. While the above works, I do not want that logic in the views. How to put that in the controller?
I have this in controller:
...
#jobs_count = #companies.map do |c| Quote.where(company_id: c.id).where(paid:true) end
...
That does not work. The view page does not use params.

Company.has_many :quotes already allows you to use company.quotes rather than Quote.where(company_id: company.id).
Now, define this scope on the Quote model:
class Quote
scope :paid, -> { where paid: true }
# ...
end
Now your view can say
<% #companies.each do |c| %>
<%= c.quotes.paid.count %>
<% end %>
... with no need for any changes in the controller.
Does that look better?

PJSCopeland has made a great suggestion by using a named scope:
class Quote
scope :paid, -> { where paid: true }
end
but I would suggest taking it a step further by keeping "quotes paid count" as a concern of your Company model:
class Company
def quote_count
quotes.paid.count
end
end
Then in your view:
<% #companies.each do |c| %>
<%= c.quote_count %>
<% end %>
This also makes it easier to unit test you Quote and Company models.

Related

How do I implement associations in searchkick

First, the example I read in the docs shows to declare the associated model as singular, :address, but if I do I get the error Association named 'address' was not found on User; If I change it to plural :addresses, then the next problem I have is the association doesn't work in views undefined method `country' for ...
Why am I declaring the association as plural and how can I make the association available in the view
User.rb:
class User < ActiveRecord::Base
searchkick word_middle: ['full_name', 'description', 'interests']
has_many :addresses
scope :search_import, -> { includes(:addresses) }
search.html.erb:
<% #users.each do |u| %>
<li>
<%= link_to "#{u.first_name} #{u.middle_name} #{u.last_name}", page_path(name: u.name) %>
<% #ua=u.addresses.where("current=?", true) %>
<% if #ua.country=="US" %>
<%= #ua.city %>, <%= #ua.state %> <%= ISO3166::Country.find_country_by_alpha2(#ua.country) %>
<% else %>
<%= #ua.city %>, <%= ISO3166::Country.find_country_by_alpha2(#ua.country) %>
<% end %>
</li>
<% end %>
</ul>
In controller, do this: #user = User.includes(:addresses).where(your_query) to make the association readily available in view.
And yes has_many associations are bound to be plural: "User has_one
:life" and "User has_many :passions"; does it make sense?
And finally your error: where returns an array because you queried: "bring me all records which fulfill this condition". find, on the other hand, will bring back 1 specific record as it expects a unique attribute or brings back first record that matches that attribute.
What you need to do:
You should either do this (if you are dead-sure that you will get 1
such record or you need only one of that type):
<% #ua=u.addresses.where("current=?", true).first %>
OR
If you need to go through all the resultant array then:
<% #ua=u.addresses.where("current=?", true) %>
<% #ua.each do |ua| %>
# Your code for each ua instead of #ua.
<% end %>
Happy Learning :)

Rails logic works in view but not controller

I have the following loop in my view to display all divisions in a given tournament. When I try to replicate the logic in the controller, then pass the variable to the view, I and "undefined methods" error. Any help with what I'm doing wrong would be appreciated.
Models
class Tournament < ApplicationRecord
has_and_belongs_to_many :divisions
end
class Division < ApplicationRecord
has_and_belongs_to_many :tournaments
end
WORKING Controller & View
Controller
def index
#tournaments = Tournament.all
end
View
<% tournament.divisions.ids.each do |tdi| %>
<%= Division.find(tdi).name %>
<% end %>
NOT WORKING Controller & View
Controller
def index
#tournaments = Tournament.all
#tournaments.divisions.ids.each do |tdi|
#divisions = Division.find(tdi).name
end
end
View
<%= #divisions %>
When I try the second (and I'm sure more correct) implementation, I get a "undefined method `divisions'" error for the following line in the index method:
#tournaments.divisions.ids.each do |tdi|
The problem is that #tournaments = Tournament.all this line returns a list of tournament objects. So, in the second line you cannot relate a list of object with any kind of association. You have to Iterate through the #tournaments and then find the divisions for each tournament.
def index
#tournaments = Tournament.all
# you can also eager load the tournamnets
# #tournaments = Tournament.includes(:divisions)
#tournaments.each do |tournament|
tournament.divisions.ids.each do |tdi|
#divisions = Division.find(tdi).name
end
end
end
What I think the error is telling you is that the method ids is not a method of divisions. Yo would have to define that method as scope in your Division model.
scope :ids, -> { pluck(:id) }
Another thing is that I don't understand why are you trying to do something like this:
<% tournament.divisions.ids.each do |tdi| %>
<%= Division.find(tdi).name %>
<% end %>
when you can just simply do this:
<% tournament.divisions.each do |tdi| %>
<%= tdi.name %>
<% end %>
First thing, in the 2nd case you are calling association on collection, You need to do this, in controller, ultimately you need all the division names, here you also have n + 1 query problem so to solve that
def index
#tournaments = Tournament.includes(:divisions)
end
In view
#tournaments.each do |tournament|
tournament.divisions.each do |division|
<%= division.name %>
<% end %>
<% end %>
Hope that helps!

Rails Form element to convey two values

I have a form that takes bookings for an event for people. The form displays events vertically, and a name & checkbox for each of the possible people next to each event.
How should I best convey the two pieces of information that i need per checkbox? that is, the event_id and the person_id
I'm not totally sure wether I got you right. This is the model I assume you're talking about:
# event.rb
class Event
has_many :people
scope :possible_people, -> { #whatever .. }
end
# person.rb
class Person
belongs_to :event
end
# events_controller.rb
class EventsController
def index
#events = Event.all
end
end
And this might be a possible solution to change an events relation to people:
# index.html.erb
<ul id="events">
<% #events.each do |event| %>
<li class="event">
<%= form_for #event do |form| %>
<% event.possible_people.each do |person| %>
<%= check_box_tag "event[person_ids][]", person.id, #event.people.include?(person) %>
<% end %>
<%= f.submit_tag 'Save Event' %>
<% end %>
</li>
<% end %>
</ul>
The important part is <%= check_box_tag "event[person_ids][]", person.id, #event.people.include?(person) %> where you actually change the the relation of a specific person to the event.
Good luck ;)
Well, you can try out something like below line, I am assuming you have a multiselect checkboxes and i am passing a Hash of event_id => plate_id as value to checkbox.
<%= check_box_tag 'booking[event_people_ids][]', {booking.event_id => booking.plate_id} %>
You will get the value in params as:
booking => {event_people_ids =>["{"72"=>"3"}}
I ended up doing this:
<%= check_box_tag "booking[]", "#{event.id}-#{person.id}" %>
and then in then to process them:
params[:booking].each do |booking|
booking = booking.split('-')
a = {
:booking_id => #booking.id,
:person_id => booking[1],
:event_id => booking[0]
}
Appointment.create(a)
end
I was hoping for a more railish way to do it but this works.

How to view RoR controller instance variables in view?

I have a table Projects each with 0 or more Categories. On my view, I want to display 0 projects until a JQuery click event associated with each category--i.e. when the user clicks "Food," I want to display all projects with category Food; when the user clicks "Photos," I want to display BOTH food and photos-related projects.
So on the jQuery click event I define an ajax call:
params = 'category_name=' + cat;
$.ajax({
url: "/projects_controller/filter_list",
data: params
})
where "cat" is the names of the Categories selected (in the format "Food Photography Journal etc")
In my projects_controller I started a filter_list method:
def filter_list
#categories = []
words = params[:category_name].split(/\W+/)
words.each { |word| #categories.push(Category.where("name = ?", word)) }
#projects = ...
end
But now I'm stuck. 1) How do I get all the projects associated with any of the categories in #categories? and 2) How do I display the #projects variable on my view? Right now I just display all like this:
<% Project.all.each do |project| %>
<tr style="display:none" class="project <% project.categories.all.each do |cat| %><%= cat.name %> <% end %>">
<td><%= project.filename %></td>
<td><a href='project/<%= project.id %>'><%= project.location %></a>
<td><% project.categories.all.each do |cat| %><%= cat.name %>, <% end %></td>
<% end %>
Your instance variables $categories, #projects are already available in the view. So in the view you can use #project rather than accessing the Class Project itself.
<% #projects.each do |project| %>
...
<% end %>
But probably you did not design your models correctly. Establish the correct relationships in your model. If a project belongs to a category, you can associate them as follows:
#models/category.rb
class Category < ActiveRecord::Base
has_many :projects
end
#models/project.rb
class Project < ActiveRecord::Base
belongs_to :category
end
#controllers/categories_controller.rb
def index
#categories = Category.all #or use your own query
end
#views/categories/index.erb
<% #categories.each do |category| %>
# here you can get all projects under this category using
# category.projects
<% end %>
Note: i'm used to HAML, so sorry if my ERB syntax is wrong
The according view(s) to a controller class have have access to the class level instance variables (i.e. #my_variable).
Simply let #projects = Project.all.each in your controller and substitute Project.all with #projects in your view.

Rails: check presence of nested attribute

If I have the following nested model relationships (all has_many):
Countries < Cities < Streets < Homes
In a show view, how can I check if a particular Country has any homes?
Edit:
Adding the suggested method of chaining with the map method (first try to map to streets). So far it's not restricting the records
<% #countries.each do |country| %>
<% if country.cities.map(&:streets).any? %>
....
<% end %>
<% end %>
You can call or #country.cities.map(&:streets).flatten.map(&:homes).present? or #country.cities.map(&:streets).map(&:homes).any?
<% if #country.cities.map(&:streets).flatten.map(&:homes).flatten.any? %>
Tro-lol-lo yo-lo-puki
<% end %>
Also you can wrap this long line into your model method:
class Country < ActiveRecord::Base
def any_homes?
cities.map(&:streets).flatten.map(&:homes).flatten.any?
end
end
Usage
<% if #country.any_homes? %>
Tro-lol-lo yo-lo-puki
<% end %>
And of course it looks like a good data structure for refactoring! It wants to be refactored!

Resources