Cleaning up view ruby logic and separating concerns into model/controller - ruby-on-rails

I want to display a random assortment of 6 tools from my database on my home page. I have created a Pages controller with a home action.
This is my Pages controller:
class PagesController < ApplicationController
def home
#tools = Tool.all
end
end
Then in my home.html.erb view I use the .sample method to grab random tools from my database as such(I repeat this 6 times using tool1, tool2, tool3, etc variables for each):
<% tool1 = #tools.sample %>
<%= image_tag tool1.tool_image.url(:medium) %>
<%= tool1.name %>
<%= tool1.description %>
I am wondering if there is a better way to do this. It seems that I have logic in my view and there must be a way to move that logic somewhere else? My model, controller, etc. How would one go about cleaning this code up so that it's good rails code? Or maybe this is good rails code and I just don't know it since I am a beginner.

Your controller doesn't need to extract everything from the tools_table, so I'd first remove the .all. Your example makes it seem like you just need 6 random objects from the database, here's one way to do that:
class PagesController < ApplicationController
def home
#tools = Tool.order("RANDOM()").first(6)
end
end
Then in your view you can just loop through those:
<% #tools.each do |tool| %>
<%= image_tag tool.tool_image.url(:medium) %>
<%= tool.name %>
<%= tool.description %>
<% end %>

In addition to Anthony's answer.
To clear up the view with some rails magic you can also add a partial to your app/views/tools called:
_tool.html.erb
Looking like:
<%= image_tag tool.tool_image.url(:medium) %>
<%= tool.name %>
<%= tool.description %>
And then change your view to
<%= render #tools %>
And Rails will know what to do if #tools is a collection of tools 😄

Related

How much should I avoid computations in my views?

I am building an application where n users can talk to each other (like a messaging application) in public. Because you might want to have a different bio for each talk you do (for example a discussion about me regarding Ruby on Rails would need a different bio than one about Psychology) I have a Spkr model which has a User and a Tlk. The below code successfully means that on the users profile page, for each instance of them being a Spkr, the Tlk, and it's participants is visible with each Spkr's image (so if a Tlk has three participants, then all three images will be visible).
The setup is such where the default image is the User's image, but the Spkr can also customise their image by uploading one as a Spkr. I am worried that I am loading the front end with too much computation. Right now everything works... so is it ok? Or should I be limiting the computation happening when building views?
Thank you
<% #user.spkrs.each do |spkr| %>
<%= link_to show_tlk_path(spkr.tlk) do %>
<h4><%= spkr.tlk.title %></h4>
<% spkr.tlk.spkrs.each do |speaker| %>
<div class="tlk-tlking-image spkr-image image-spkr-<%= spkr.id %>"
<% if speaker.image.present? %>
style="background-image: url(<%= rails_blob_url(speaker.image) %>)"
<% elsif speaker.user.image.present? %>
style="background-image: url(<%= rails_blob_url(speaker.user.image) %>)"
<% end %>
>
</div>
<p><%= speaker.name %></p>
<% end %>
<% end %>
<% end %>
It tends to be considered good practice to keep the view as free of 'back end' calculations as possible. These files are often worked on by front end developers who may not even know how to code ruby, so the less of it that is in the view the better. It's also just not where it belongs in rail's Model Controller View framework.
First of all the code you've put can be simplified to:
<% #user.spkrs.each do |spkr| %>
<%= link_to show_tlk_path(spkr.tlk) do %>
<h4><%= spkr.tlk.title %></h4>
<% spkr.tlk.spkrs.each do |speaker| %>
<div class="tlk-tlking-image spkr-image image-spkr-<%= spkr.id %>"
style="background-image: url(<%= rails_blob_url((speaker.image || speaker.user.image) %>)"
>
</div>
<p><%= speaker.name %></p>
<% end %>
<% end %>
<% end %>
But as you say, if you want to handle this in a more appropriate place, I'd add a method to the Speaker class:
# app/models/speaker.rb
class Speaker << ApplicationBase
def image_for_view
image || user.image
end
end
This will let you call speaker.image_for_view which I think reads nicely in the view file itself.
Along with the great answer let me just add something that might help you to make views more clear. Might not be relevant to your question directly but might help you to get some idea how you can make views beautiful.
The first thing to make views look good are helpers. Though rails provide helpers for every controller, helpers are global meaning it can be used anywhere in any views. So, global formatings should be done with helpers. Like if you want a date formatter that needs to be used in a lot of view files, you can create a helper called date_helper.rb in app/helpers and put you desired date formatting -
module DateHelper
def formatted_date(date)
date.strftime([%m/%d/%Y')
end
end
Next is what rails people like to call a Presenter pattern. This is helpful when you don't want some logic to be shared across all views. Some logic that doesn't feel like belongs in controller or model are put there to make views readable. Suppose you have a view like below which is a bit messy -
<p>
Post title: <%= post.title.gsub("forbidden word", "") %>
<%= link_to "Read post", post, class: "w-75 p-3 text-#{post.draft? ? "orange" : "green"} border-#{post.draft? ? "orange" : "green"}" %>
</p>
To make this more beautiful you can create a presenter class named post_presenter.rb which should reside in app/presenters and write some code like -
class PostPresenter
def initialize(post)
#post = post
end
def title_without_forbidden_words
#post.title.gsub("forbidden word", "")
end
def css_color
#post.draft? ? "orange" : "green"
end
end
and in the view -
<% presenter = PostPresenter.new(post) %>
<p>
Post title: <%= presenter.title_without_forbidden_words %>
<%= link_to "Read post", post, class: "w-75 p-3 text-#{presenter.css_color} border-#{presenter.css_color}" %>
</p>
Such way a view might be more clear and also it can be lifesaver for frontend developers. This are the best two methods I found till now that makes a rails view beautiful which I always try to use.
Examples are taken from rubyguides website. Thanks to them,

Rails - how to write an index view?

I'm having trouble figuring out how to display an index.
In my organisation requests view folder, I have a file called index.html.erb.
In that file, I'm trying to list each organisation request. I've tried each of the following formulations:
<% OrganisationRequest.each do |OrgReq| %>
<% organisation_request.each do |OrgReq| %>
<% #organisation_request.each do |OrgReq| %>
<% #organisation_requests.each do |OrgReq| %>
In each case, I get an error that says:
formal argument cannot be a constant
I thought a constant meant something beginning with a capital letter. 3 of the above attempts don't begin with a capital letter.
It's also confusing to me since in my user index, I have <% User.each %> and I don't get an error message.
Can anyone see what's gone wrong? How do I ask for a list of objects?
If you have your data and view right, you should be able to fix with:
<% #organisation_requests.each do |org_req| %>
...
<% end %>
If we stick Rails conventions, we'd say that, you have a OrganisationRequests controller, has such content.
class OrganisationRequestsController < ApplicationController
...
def index
#your_local_variable = OrganisationRequest.find(...)
end
...
end
That is to say, you need to use, #your_local_variable inside view file.
<% #your_local_variable.each do |o| %>
....
<% end %>
If the variable inside index action is #organisation_requests, use that.

Moving rails from to from _form.html.erb to application.html.erb

I have most of the functionality done for a site. Now I am trying to make it look nice. I have a _form.html.erb that works great.
<%= form_for(#card) do |f| %>
<% if #card.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(#card.errors.count, "error") %> prohibited this card from being saved:</h2>
<ul>
<% #card.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
</div>
<% end %>
<div class="field">
<%= f.label :event %><br />
<%= f.text_field :event %>
</div>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
Files
view
- cards
-- new.html.erb
-- index.html.erb
-- show.html.erb
- layouts
-- application.html.erb
- pages
-- index.html.erb
I make a call for the form from new.html.erb and it works sends it to show.html.erb, just as I want. I'm using bootstrap and decided to make use of the nav bar. I have placed the nav bar code into the application.html.erb. It works just fine, well kind of. I want what would normally be a search function to be the add a new card.
When I add the form call it does not work, when I add it directly to the application page it does not work. I'm not sure, I have spent hours on this. I got it to work only on the show.html.erb page, both index pages would error out. I honestly don't remember how I did this though.
I'm trying to learn by doing, but I am stuck and need some help.
Thank you,
Ian
I guess that when you say that its working in your new.html.erb you have a new action inside your cards_controller, and inside this action you have something like: #card = Card.new
Well, if you want to put this form in another view, like in the application.html.erb you need to set first your #card variable, so you can do something like:
# application_controller:
before_filter :new_card
def new_card
#card = Card.new
end
be aware that all the controller that inherits from application controller will set this #card variable
#instance_variable
The underlying problem here is that you're calling a partial - by design, these are meant to give you the ability to call the functionality the file contains anywhere in your application
The problem you have is you're referencing an #instance_variable directly in your partial.
This isn't normally an issue - if you're using partials like you were originally (to modularize views), it should be okay. The problems arise when you try and use the partials in a more generalized way, as you are doing now:
#app/views/controller/_form.html.erb
<%= form_for(#card) do |f| %>
This relies on the #card instance variable being made available, which won't be if you're loading the partial in any other controller than the cards_controller.
--
Fix
The way to fix this is to either populate the #card instance variable in the application controller (as described by edymerchk), or to pass the raw value through the locals hash of the partial call:
This will allow you to use the card local variable in your partial:
#app/views/controller/_form.html.erb
<%= form_for card do |f| %>
-
Alternatively, you could also set the #card instance variable, as recommended in another answer:
#app/controllers/application_controller.rb
Class ApplicationController < ActionController::Base
before_action :set_card
private
def set_card
#card = Card.new
end
end

Ruby on Rails - Render form for model

when I render an partial for my model I'm using:
<%= partial #my_model %>
Automatically it looks for the file ..view/my_models/_my_model.html.erb
I really like this notation because it feels the right way!
My Problem:
Now I want a notation to automatically look up for the edit partial.
Is there a way? Until now I used
<%= partial 'edit' %>
This is ok, but I have a lot of subclasses for my model and I liked the way that it automatically looks up in the right subclasses view folder for the template.
Until know I have to look for the class for my model and then call
<% if #my_model.class == FirstSubClass %>
<%= partial 'firstsubclasses/_edit.html.erb' %>
<% elsif #my_model.class == SecondSubClass %>
<%= partial 'secondsubclasses/_edit.html.erb' %>
<% end %>
I prefer one line :) Any ideas?
Try:
<%= partial '#{#my_model.class.name.tableize}/_edit.html.erb' %>
tableize is a method of ActiveSupport::Inflector, which includes some other cool naming manipulation methods.

Rendering the output of action in template

Is there some way to output an action in Rails template? Something equivalent to ASP.NET MVC Html.RenderAction ?
I need to render some stuff in sidebar and I don't want to put queries in partials or specific controller. So far I can think of only one way - put something into #stuff (by whatever means) instance var and let render find the proper partial or specify it explicitly. It would be better to be able to change only one file to change the contents of sidebar (as in ASP).
Yes, there is. You can add the "sidebar" partial to the application layout (in the app/views/layouts folder).
You can put the code that gets the "sidebar" variables in a before_filter in the ApplicationController
class ApplicationController < ActionController::Base
before_filter :sidebar_function
have a look at
http://guides.rubyonrails.org/layouts_and_rendering.html
especially read the subitem
http://guides.rubyonrails.org/layouts_and_rendering.html#using-content_for
I do not know ASP.net, however, I think Rails yield might be the solution. Here is a small example:
view:
<% content_for :one do %>
Test one
<% end %>
<% content_for :two do %>
Test two
<% end %>
<p>Hi</p>
application.html.erb
<%= yield :one %>
<%= yield %>
<%= yield :two %>
See Railsguides: http://guides.rubyonrails.org/layouts_and_rendering.html#understanding-yield

Resources