Multi-lined html output using a decorator - ruby-on-rails

When you just need to return one line of html, decorators are great!
However, I don't know how I should conditionally render a complex, multilined section of HTML, such as this:
<% if current_user == #user || current_user.is_admin? %>
<h3> Private Info: </h3> <h4> - Only <%= #user.is_owner? %> can view this information </h4>
<p><strong>First Name:</strong> <%= #user.decorate_first_name %> </p>
<p><strong>Last Name:</strong> <%= #user.decorate_last_name %></p>
<p><strong>Email:</strong> <%= #user.decorate_email %> </p>
<% end %>
As you can see, I'm wrapping it in an if statement, which is exactly what I want to replicate with a decorator, but I'm not sure how.
Should I create a partial and render the partial with a decorator?
app/decorators/user_decorator.rb:
def decorate_private_info
h.render 'private_info' if current_user? || admin?
end

Related

Rails: Rendering a partial in a loop results in undefined variable

I'm having trouble with attempting to render a partial within a loop. Essentially, I have an app where events are created, people join the event, and for each event, attendees have a list of items they will bring which they can select from their own inventory of items.
In my show.html.erb for Events:
<% #event.attendees.each do |user| %>
<div class="col-md-4">
<h4><%= user.first_name %> <%= user.last_name %></h4>
<% if user.event_items.any? %>
<div class="event-item-list">
<% user.event_items.each do |eventitem| %>
<div class="event-item-div" >
<p class="event-item-title"><%= eventitem.item.title %></p>
</div>
<% end %>
<% end %>
</div>
<% if user == current_user %>
<%= render 'event_items/form' %>
<% end %>
</div>
<% end %>
This works and shows the title of each event item that is added to the list.
However, if I want to move the content of the P tag to a partial, I get "undefined local variable or method `eventitem'". Which doesn't make sense to me since I would assume that whatever is passed in as a partial in that loop would be treated the same as if I just left that P tag content in there. At this point, all I have in the partial would be:
<p class="event-item-title><%= eventitem.item.title %><p>
Any insight would be appreciated.
Partials don't inherit local variables automagically, you have to define them. According to Rails Guides to pass local variables to a partial you need to set them in the locals hash
<%= render 'event_items/form', locals: {eventitem: eventitem} %>

Rails: How to make small changes in different views

Is it possible to make small changes in different views?
The same partial is rendered in index.html.erb and show.html.erb as below.
index.html.erb
<%= render #schedules %>
show.html.erb
<%= render #schedules %>
What I'd like to do is not to display some value in the index.html.erb. (and display some value in both erb)
For example, I'd like to display start_at and end_at only in show.html.erb and display title in both erb.
_schedule.html.erb
<% schedule.rooms.each_with_index do |a, idx| %>
<% a.events.each do |e| %>
<%= l(e.start_at) %>-<%= l(e.end_at) %> # display only show.html.erb
<%= e.title %> #display both erb
...
<% end %>
...
<% end %>
Althogh I come up with idea which I create two partials, it contradicts the DRY policy.
It would be appreciated if you could give me any idea.
You can use controller.action_name.
<% if controller.action_name == 'show' %>
<%= l(e.start_at) %>-<%= l(e.end_at) %> # display only show.html.erb
<% end %>
The params hash also contains the action_name.
action_name is enough and do the trick but personally I don't like this. I'd do two separate partials.
Can check current action and current controller on page. So we can call single partial from different actions and can customize as per action name or action and controller name.
eg.
<% schedule.rooms.each_with_index do |a, idx| %>
<% a.events.each do |e| %>
<% if #current_controller == "events" and #current_action == "show" %>
<%= l(e.start_at) %>-<%= l(e.end_at) %> # display only show.html.erb
<% end %>
<%= e.title %> #display both erb
...
<% end %>
...
<% end %>
Also need to update Application Controller.
class ApplicationController < ActionController::Base
before_filter :instantiate_controller_and_action_names
def instantiate_controller_and_action_names
#current_controller = controller_name
#current_action = action_name
end
end
You could use CSS to hide/show the content based on context.
In practice, I have found this a good way to reuse partials that have small differences. Especially when those differences don't cost anything to compute i.e. printing a date
You can cache the partials without worrying about where they are rendered
Reduce conditional logic
Remove duplication
<% if controller.action_name == 'show' %> is fine for a simple use case. If/When you come to have multiple places where the partial needs to be rendered, it will become unwieldy. The CSS solution would only require another wrapper <div class="schedules--whatever"> and the related CSS style.
show.html.erb
<div class="schedules--show">
<%= render #schedules %>
</div>
index.html.erb
<div class="schedules--index">
<%= render #schedules %>
</div>
_schedule.html.erb
<% schedule.rooms.each_with_index do |a, idx| %>
<% a.events.each do |e| %>
<div class="event__date">
<%= l(e.start_at) %>-<%= l(e.end_at) %>
</div>
<%= e.title %>
...
<% end %>
...
<% end %>
schedules.css
.schedules--show .event__date {
display: block;
}
.schedules--index .event__date {
display: none;
}

Helper Method detect post - comment from user

I'm trying to create a helper method that will display {user.name} has no submitted posts." on the profile show view of user if they haven't yet submitted any posts and display the number posts they have . currently on my show view i have <%= render #user.posts %> which displays nothing when there are 0 posts submitted.
the partial for post is :
<div class="media">
<%= render partial: 'votes/voter', locals: { post: post } %>
<div class="media-body">
<h4 class="media-heading">
<%= link_to post.title, topic_post_path(post.topic, post) %>
<%= render partial: "labels/list", locals: { labels: post.labels } %>
</h4>
<small>
submitted <%= time_ago_in_words(post.created_at) %> ago by <%= post.user.name %> <br>
<%= post.comments.count %> Comments
</small>
</div>
</div>
ive tried :
def no_post_submitted?(user)
user.post.count(0)
"{user.name} has not submitted any posts yet."
end
on my user show view :
<%= if no_post_submitted?(#user) %>
<%= render #user.posts %>
which im more than sure is wrong but i have no idea how to implement this method .
Where you are using render #user.posts you can just add a simple conditional:
<% if #user.posts.empty? %>
<p><%= #user.name %> has no submitted posts</p>
<% else %>
<%= render #user.posts %>
<% end %>
There wouldn't be much point creating a helper for this unless you need to use it in multiple places.
Render collection returns nil if the collection is empty so you can use the || operator:
<%= render #user.posts || "{#user.name} has not submitted any posts yet." %>
Or if there is more code render another partial:
<%= render #user.posts || render 'no_posts' %>
In Ruby methods automatically return the last value so this method:
def no_post_submitted?(user)
user.post.count(0)
"{user.name} has not submitted any posts yet."
end
Will always return a string - if you use a string literal in a condition it will be evaluated as true with the warning warning: string literal in condition. Also that is not how you use count - passing 0 will cause it to query on column 0 or just error.
http://apidock.com/rails/ActiveRecord/Calculations/ClassMethods/count
So to fix the method you would do:
def no_post_submitted?(user)
user.posts.empty?
end
However that conditional is so simple that it does not really warrant a helper method. Instead you would just write:
<%= if user.post.any? %>
<%= render #user.posts %>
<% else %>
<%= "{user.name} has not submitted any posts yet." %>
<% end %>
There are a couple of problems with your solution. Remember, rails is more about convention over configuration.
Your method no_post_submitted? should actually return true/false since its a method ending with ?. Also it should be named no_posts_submitted? for clarity. It should look something like this:
def no_post_submitted?(user)
user.posts.count > 0
end
Then, there should be another helper method that will print your required message, Something like:
def no_posts_message(user)
"{user.name} has not submitted any posts yet."
end
And eventually you can all plug it in like this:
<% if no_posts_submitted?(user) %>
<%= no_posts_message(user) %>
<% else>
<%= render #user.posts %>
<% end %>
As per the docs:
In the event that the collection is empty, render will return nil, so it should be fairly simple to provide alternative content.
<h1>Products</h1>
<%= render(#products) || "There are no products available." %>
--
So...
<%= render(#user.posts) || "#{#user.name} has not submitted any posts yet." %>

Rendering A Partial / Layout With Multiple Blocks

I have a very simple requirement - I have a layout comprising of a header and body. It is a sub-layout of the page, not for the page itself.
This layout is repeated throughout multiple pages, and it is possible the structure around it will change. So I want to be able to separate the content of the header and the content of the body from the structure that contains it.
My first attempt was to use render a partial as a layout that used named yields to render a header and body:
<header class="Resource-header">
<%= yield :resource_header %>
</header>
<div class="Resource-body">
<%= yield :resource_body %>
</div>
Then render it from my templates like this:
<%= render layout: 'admin/resource' do %>
<% content_for :resource_header do %>
<% end %>
<% content_for :resource_body do %>
<% end %>
<% end %>
However, this renders nothing.
I started playing with the order of things, and discovered that if the content_for blocks are declared before the call to the partial, this approach does work:
<% content_for :resource_header do %>
<% end %>
<% content_for :resource_body do %>
<% end %>
<%= render layout: 'admin/resource' do %><% end %>
However this just feels incredibly hacky. It seems that content_for is scoped globally, and there is no association between the content_for block and the partial rendering.
So what is the correct way for me to achieve this?
I just happened to have exactly same problem.
Solution is:
in your partial layout file 'admin/resource' body:
<header class="Resource-header">
<%= yield resource, :resource_header %>
</header>
<div class="Resource-body">
<%= yield resource, :resource_body %>
</div>
in your templates do:
<%= render layout: 'admin/resource' do |resource, section| %>
<% case section %>
<% when :resource_header %>
Resource header shows here.
<% when :resource_body %>
Resource body shows here.
<% end %>
<% end %>
Take a look on rails presenters https://www.ruby-toolbox.com/categories/rails_presenters
Maybe your solution is cells gem.
Eventhough the question is quite old now, I had a similar issue today. I came up with sth. like this. No gem or custom class required, just some fancy block usage ;)
<!-- app/views/layouts/fancy-blocks.html.erb -->
<%
body, footer = nil
yield(
proc {|&blk| body = capture(&blk) },
proc {|&blk| footer = capture(&blk) }
)
%>
<section class="body"><%= body %></section>
<footer><%= footer %></footer>
<!-- app/views/some-other/view.html.erb -->
<%= render 'layout/fancy-blocks' do |body, footer| %>
<% body.call do %>
BODY
<% end %>
<% footer.call do %>
FOOTER
<% end %>
<% end %>

Conditional line spacing in ERb partials

This is the code for an address partial I just wrote. People might put single line addresses in either street line, company name is optional, etc... It works exactly how I want it to, but I know that checking each variable twice is ugly and terrible.
<%= "#{a.name}" unless a.name.blank? %>
<% unless a.name.blank? %> <br> <% end %>
<%= "#{a.company_name}" unless a.company_name.blank? %>
<% unless a.company_name.blank? %> <br> <% end %>
<%= "#{a.street_1}" unless a.street_1.blank? %>
<% unless a.street_1.blank? %> <br> <% end %>
<%= "#{a.street_2}" unless a.street_2.blank? %>
<% unless a.street_2.blank? %> <br> <% end %>
<%= "#{a.city}, #{a.state} #{a.zip}" %>
So, my gratuitous use of unless aside, how should I be putting in a conditional line break?
Update:
As discussed below, it is dangerous to use .html_safe on user input. If you do use a helper method as suggested below, you must also sanitize all user input on the way into the database. I've rewritten the code above as:
<% unless a.name.blank? %>
<%= a.name %>
<br>
<% end %>
<% unless a.company_name.blank? %>
<%= a.company_name %>
<br>
<% end %>
<% unless a.street_1.blank? %>
<%= a.street_1 %>
<br>
<% end %>
<% unless a.street_2.blank? %>
<%= a.street_2 %>
<br>
<% end %>
<%= "#{a.city}, #{a.state}" %> <%= a.zip %>
The redundant checking was just me overcomplicating things. I'd strongly recommend against using .html_safe in a situation like this, since you create new problems for yourself: sanitizing the input, and remembering which fields are safe. Better to not override the sensible protection Rails provides.
There are many, many ways to go about cleaning it up, but a helper would be appropriate here:
module ApplicationHelper
def format_address(a)
top = [a.name, a.company_name, a.street_1, a.street_2]
top.reject! {|s| s.blank?} # remove null and empty values
"#{top.join('<br/>')}#{a.city}, #{a.state} #{a.zip}".html_safe
end
end
Then in your view:
<%= format_address(a) %>

Resources