How to display all has_many items in index view? - ruby-on-rails

I have two models, a calculation name and a different table with a bunch of calculations that go along with it. I have successfully established a one to many relationship (one name has many states) so that when I display calculation variables in my name/show view it works perfectly:
names/show view
<p><%= #name.calc_name %></p>
<% #name.states.each do |state| %>
<p><%= state.orbital_subset %></p>
<% end %>
I would, however like to display variables in my state value on the index page of names. Currently, I have a nice list of each name but creating an inner loop that loops through the states doesn't seem to work well. This works great too:
names/index view
<% #names.each do |name| %>
<tr>
<td><%= name.calc_name %></td>
</tr>
<% end %>
The best solution I found online is of this format which does not work:
<% #names.states.each do |state| %>
<p><%= state.orbital_subset %></p>
<% end %>
Should I be nesting two do loops on the index page since I am looping both through all of the names and through all of the states? Here are my models:
class Name < ActiveRecord::Base
has_many :states
end
class State < ActiveRecord::Base
belongs_to :name
end
The error that I get when I view names index is:
NoMethodError in Names#index

<% #names.each do |name| %>
<% name.states.each do |state| %>
<td><%= state.orbital_subset %></td>
<% end %>
<% end %>

Related

Removing conditional logic from a shared partial view or alternative solution

For a current project, I have duplicate code between views, and I'm not sure of the best route to refactor it.
I appear to be in a position where I can have duplicate code across various .html.erb files, or I could put identical code into a partial and use conditionals. I've always heard logic should stay out of views. Neither option seems ideal, and I don't currently know of alternatives.
To illustrate my question, I created a simple rails app called animals. I scaffolded for two models: one for cat and one for dog. Images display their corresponding attributes:
Displaying #cats and #dogs is pretty much the same. Cats just have a column for meows while Dogs have a column for barks, and a dog has the additional attribute column of plays_catch.
Lets say we choose to reduce the duplicate code for displaying cats and dogs by making a shared view partial:
#views/shared/_animal.html.erb
<tr>
<td><%= animal.name %></td>
<td><%= animal.age %> </td>
<% if animal.class == Cat %>
<td><%= animal.meows %> </td>
<% end %>
<% if animal.class == Dog %>
<td><%= animal.barks %> </td>
<td><%= animal.plays_catch %> </td>
<% end %>
</tr>
Then to render #cats = Cat.all:
<%= render partial: "shared/animal", collection: #cats %>
Then to render #dogs = Dog.all:
<%= render partial: "shared/animal", collection: #dogs %>
Obviously it would be overkill to do something like this for this specific example, but the real world project I'm applying it to would not be overkill.
The overall question is: how do you remove nearly identical code that iterates over collections, where the only difference is adding/removing a column of information? It just doesn't feel right to put that logic in the view itself, and leaving the duplication feels wrong.
You could use decorators and add methods that return the extra column(s):
class DogDecorator < Draper::Decorator
def extra_columns
[:barks, plays_catch]
end
end
class CatDecorator < Draper::Decorator
def extra_columns
[:meows]
end
end
...
<% animal.extra_columns.each do |column| %>
<td><%= animal.attributes[column.to_s] %>
<% end %>
...
<% #cats = CatDecorator.decorate_collection(Cat.all)
<%= render partial: "shared/animal", collection: #cats %>
You can use respond_to? to solve the problem more generically. The view logic doesn't feel so wrong when it's more generic.
<% [:meows, :barks, :plays_catch].each do |method| %>
<% if animal.respond_to?(method) %>
<td><%= animal.send(method) %> </td>
<% end %>
<% end %>
You can add a method of the same name to both Cat and Dog classes which would return the specific instance attributes names and values. I'd recommend returning two arrays (one with the names of the fields, other with the fields' values, or vice-versa) since hashes are not exactly ordered. This way you can control the order in which they'll appear in the view.
For example:
#models/cat.rb
def fields_and_attributes
fields = ["Name","Age","Meows"]
attributes = [self.name, self.age]
if self.meows
attributes.push("Yes")
else
attributes.push("No")
end
[fields,attributes] # make sure each attribute is positioned in the same index of its corresponding field
end
#models/dog.rb
def fields_and_attributes
fields = ["Name","Age","Plays catch"]
attributes = [self.name, self.age]
if self.plays_catch
attributes.push("Yes")
else
attributes.push("No")
end
[fields,attributes] # make sure each attribute is positioned in the same index of its corresponding field
end
#controllers/animals_controller.rb
def display_animals
#animals = Cat.all + Dog.all # an array containing the different animals
end
#views/display_animals.html.erb
for i in (0...#animals.size)
fields_and_attributes = #animals[i].fields_and_attributes
for f in (0...fields_and_attributes[0].size)
<p><%= fields_and_attributes[0][f] %> : <%= fields_and_attributes[1][f] %></p>
end
end
Here, we first iterate over all of the animals and call the .fields_and_attributes method of that specific record; we then iterate over the results of calling that method, displaying fields and attributes in the same order as the one defined within the method and also guaranteeing that the code will display every field and every attribute regardless of the difference in the total number of fields for each different animal.
I don't know of any canonical way to accomplish this, but I would use one partial for this in the following way:
<tr>
<% animal.attributes.each do |_, value| %>
<td><%= value %></td>
<% end %>
</tr>
You can get rid of repeated attributes calls by providing in the partial a local variable with pre-obtained model attributes.
EDIT: if you only want to display some attributes.
# Declare whitelist of attributes
# (you can also declare a blacklist and just calculate the difference between two array: all_attributes - blacklist_attributes):
<% whitelist = [:name, :age, :barks] %>
<%= render partial: 'shared/animal',
collection: #dogs,
locals: {attrs: (#dogs.first.attributes.keys.map(&:to_sym) & whitelist)} %>
views/shared/_animal.html.erb:
<tr>
<% attrs.each do |attr| %>
<td><%= animal[attr] %></td>
<% end %>
</tr>
Below is my answer after reviewing posted answers. Basically:
I left the differences within each scaffold model's index page
I made shared partials for common table headers and table data
code below:
#app/views/cats/index.html.erb
<h1>Listing Cats</h1>
<table>
<thead>
<tr>
<%= render partial: "shared/cat_dog_table_headers" %>
<th>Meows</th>
</tr>
</thead>
<tbody>
<% #cats.each do |cat| %>
<tr>
<%= render partial: "shared/cat_dog_table_data", locals: {animal: cat} %>
<td><%= cat.meows %></td>
</tr>
<% end %>
</tbody>
</table>
<br>
<%= link_to 'New Cat', new_cat_path %>
And for the dogs:
#app/views/dogs/index.html.erb
<h1>Listing Dogs</h1>
<table>
<thead>
<tr>
<%= render partial: "shared/cat_dog_table_headers" %>
<th>Barks</th>
<th>Plays catch</th>
</tr>
</thead>
<tbody>
<% #dogs.each do |dog| %>
<tr>
<%= render partial: "shared/cat_dog_table_data", locals: {animal: dog} %>
<td><%= dog.barks %></td>
<td><%= dog.plays_catch %></td>
</tr>
<% end %>
</tbody>
</table>
<br>
<%= link_to 'New Dog', new_dog_path %>
The shared table headers for cats and dogs:
#app/views/shared/_cat_dog_table_headers
<td><%= Name %></td>
<td><%= Age %></td>
The shared table data for cats and dogs:
#app/views/shared/_cat_dog_table_data_headers
<td><%= animal.name %></td>
<td><%= animal.age %></td>

rails 4 def to_param with 2 models

I came accross something strange when trying to create friendly URLs.
I have an item model which has:
def to_param
"#{id}-#{name}".parameterize
end
This makes my item URLs contain the "ID" and "name" just fine:
www.domain.com/items/ID-name
I also have a category model (item belongs to category and user models) where I have the same def to_param as above but the category URLs stay "unfriendly" no "name" included:
domain.com/categories/ID
I have name column in category table and it has values.
I also use ancestry for the category model. Maybe has_ancestry is causing the issue?
I tried below but no luck:
def to_param
[id, name.parameterize].join("-")
end
Thanks for any advice!
Papirtiger's comment lead me to the solution.
The problem was I had links like:
<% #category.children.in_groups_of(4, false) do |childs| %>
<tr>
<% for categories in childs %>
<td>
<%= link_to "../categories/#{categories.id}" do %><%= categories.name %><% end %></td>
<% end %>
</tr>
Beginners solution. :)
Removed the ugly part and now it works:
<% #category.children.in_groups_of(4, false) do |childs| %>
<tr>
<% for categories in childs %>
<td><%= link_to categories do %><%= categories.name %><% end %> (<%= Item.where(:category_id => categories.id).count %>)</td>
<% end %>
</tr>

Create multiple records related to existing model

I'm a rails noob trying to develop a simple exercise log app, and I'm up against my limits. A user selects a category, and a specific workout, and I return a list of exercises belonging to that workout.
An effort is a user specific instance of an exercise.
class Exercise < ActiveRecord::Base
has_many :efforts
end
class Effort < ActiveRecord::Base
belongs_to :exercise
end
I'm retrieving the list of exercises in the efforts controller
class EffortsController < ApplicationController
def log_workout
#exercises = Exercise.where("category_id = ? AND workout_id = ?", params[:exercise][:category_id], params[:exercise][:workout_id])
#effort = Effort.new
end
end
My problem is that I'm not sure about my approach up to this stage. It does work so I have attempted to enable the user to log their workout by using a form like the following but I'm (not surprisingly) not getting the right info back from it and I'm not sure where to go with it...
<%= form_tag save_workout_path, method: :put do %>
<table>
<tr>
<th>Exercise</th>
<th>Sets</th>
<th>Reps</th>
</tr>
<%= #exercises.each do |exercise| %>
<%= fields_for "efforts[]", #effort do |f| %>
<tr>
<td><%= exercise.name %></td>
<td><%= f.number_field :sets %></td>
<td><%= f.number_field :reps %></td>
</tr>
<% end %>
<% end %>
</table>
<%= submit_tag "Log it" %>
<% end %>
If anybody has any thoughts/guidance/solutions I'd appreciate it

Ruby on Rails - How to combine display Search results in the View

I have Search controller that searches 2 models - Posts and Categories. The search works, however I am unable to display results in the View correctly - I can't get category names to show up.
I am very confused and frustrated at this point and hope to find some help!
I'm pretty sure (99% sure) the problem is in the View somewhere, because I can get results to display through render inspect thingy.
SearchController.rb
class SearchController < ApplicationController
def index
#posts = Post.search(params[:search])
#categories = Category.search(params[:search])
# combine the results
#results = #posts + #categories
#results.uniq # may be necessary to remove duplicates
end
end
index.html.erb (views/search)
<%= render 'posts/posts', :posts => #posts %>
_posts.html.erb (view/posts)
<h1>Listing posts</h1>
<table>
<tr>
<th>Name</th>
<th>Category</th>
<th>Description</th>
<th>Flag</th>
</tr>
<% if posts %>
<% posts.each do |post| %>
<tr>
<td><%= post.name %></td>
<td><%= post.category.name %></td>
<td><%= post.description %></td>
<td><%= post.flag %></td>
</tr>
<% end %>
<% else %>
<tr><td>No posts</td></tr>
<% end %>
</table>
I can get posts that match the search to display, but I can't display categories. How can I do this? Any help highly appreciated!! Thank you.
If you are using a search backend like sunspot solr then you would be able to combine the searches like
#search = Sunspot.search [User, Company] do
fulltext params[:search]
end
#results = #search.results
And then return the necessary values. In this example, it's showing where you can retrieve the class of the action (controller_name) may not work depending on which controller the results are returned in.
<% #results.each do |result| %>
<% case result.class.to_s %>
<% when "Company" %>
<li><%= "Company: #{result.name}" %></li>
<% when "User" %>
<li><%= "User: #{result.username}" %></li>
<% end %>
<% end %>
The answer (I include all the things I changed to make it work + few files that I didn't change but that have to be there), or How to make simple search for multiple models:
SearchController.rb
class SearchController < ApplicationController
def index
#posts = Post.search(params[:search])
end
end
post.rb
class Post < ActiveRecord::Base
attr_accessible :category_id :name :description :flag
belongs_to :category
def self.search(search)
if search
find(:all, :joins => :category, :conditions => ['posts.equipment LIKE ? OR posts.description LIKE ? or categories.name like ?', "%#{search}%", "%#{search}%", "%#{search}%"])
else
find(:all)
end
end
search/index.html.erb
<%= render 'posts/posts', :posts => #posts %>
I added 2 files _post.html.erb and _category.html.erb. They are similar, this is _post.html.erb:
post: <%= post.name %>
(This might not be necessary in some cases or for some models. I can search a third model without this file in its' views. However the third model doesn't have file like _posts.html.erb either).
Finally, _posts.html.erb remains the same:
...
<% if posts %>
<% posts.each do |post| %>
<tr>
<td><%= post.name %></td>
<td><%= post.category.name %></td>
<td><%= post.description %></td>
<td><%= post.flag %></td>
</tr>
<% end %>
<% else %>
<tr><td>No posts</td></tr>
<% end %>
...
This works now. Can add new models to the search easily. The only other thing needed for the search is input field.

Nested forms in rails for existing objects

In my app, I have a page where I want admin users to be able to update a particular characteristic of my "Package" model, which belongs to both the "Order" model and the "Item" model. It's a little complicated, but I basically want to present in a table all of the Packages belonging to a given Item, ordered in a particular way (that's what my packages_for_log method below does), with two blanks for updating the weight of the item. All the updates should ideally be submitted at once, with a single submit button at the bottom of the page. I've attempted a whole bunch of solutions, and my current one is below, but gives this error when I visit the page in my server:
undefined method `actual_lbs' for #<ActionView::Helpers::FormBuilder:0x007ff67df6c9c8>
The error's confusing to me, cause I was hoping that I was calling that method on the package instance, not a helper. Bit confused. At any rate, my code is below. The relevant section of the view:
<% form_for(#item) do |a| %>
<% #item.packages_for_log.each do |p| %>
<%= a.fields_for p do |i| %>
<tr>
<td><%= p.order.name %></td>
<td><%= p.order.processed_notes %></td>
<% if p.order.user %>
<td><%= "#{p.order.user.name.first(3).upcase}-#{p.id}" %></td>
<% else %>
<td><%= p.order.id %></td>
<% end %>
<td>
<%= i.text_field :actual_lbs %>
</td>
<td>
<%= i.text_field :actual_oz %>
</td>
<%= i.hidden_field :true_weight, value: (i.actual_lbs + i.actual_oz/16) %>
</tr>
<% end %>
<% end %>
<% end %>
Relevant section of the package.rb model file:
attr_accessible :order_id, :price, :true_weight, :actual_lbs, :actual_oz
attr_accessor :actual_lbs, :actual_oz # These two are virtual attributes for the above calc
And I added resources :packages to my routes file.
Any idea what I'm doing wrong? It's important to me that I loop through to create a table based on "p" and then edit that same "p" object. Just not sure how to do it. Pretty new to Rails.
I think your problem is this line:
<%= i.hidden_field :true_weight, value: (i.actual_lbs + i.actual_oz/16)
You need to put p.actual_lbs and p.actual_oz
EDIT: By the way, you probably need to move the true weight calculation to your controller action (CREATE action). I don't think :true_weight will get passed as you intended it to, using the above method.

Resources