How can I supply data to yield in my rails partial? - ruby-on-rails

I'm trying to render a partial with a certain layout file. But be able to specify unique values for that specific partial. Below are the current files I have, but all that happens is the yield keywords both show the collection value. I'd like to pass a custom "title" and show the collection in the "body".
application/_news_article.html.erb
<!-- application/_news_article.html.erb -->
<% if news_article.report_text.present? %>
<div class="news_article_entry">
<div class="text-box">
<span><%= "#{news_article.published_date.strftime("%D")}" %></span> -
<%= "#{news_article.report_text}" %>
</div>
<% if news_article.impact_text.present? %>
<div class="impact-text" data-toggle="tooltip" data-placement="bottom"
title="<%= news_article.impact_text %>">Analysis</div>
<% end %>
</div>
<hr />
<% end %>
application/_news_articles.html.erb
<!-- application/_news_articles.html.erb -->
<%= render partial: "news_article", collection: articles %>
layouts/_panel_box_scrollable.html.erb
<!-- layouts/_panel_box_scrollable.html.erb -->
<div class="panel panel-default skill-evaluation-box">
<div class="skill-heading">
<div class="row">
<div class="col-xs-12">
<%= "#{yield :title}".upcase %>
</div>
</div>
</div>
<div class="skill-body scrollable">
<%= yield :body %>
</div>
</div>
schools/show.html.erb
<!-- schools/show.html.erb -->
<%= render partial: 'news_articles', layout: "layouts/panel_box_scrollable", locals: { articles: #articles } %>

Yield is where the partial will be rendered. If you want to render a title you need to pass in a title through locals or use a variable potentially on one of your models or set in the controller. In this case if you want to render a title do something like:
<%= render partial: 'news_articles', layout: "layouts/panel_box_scrollable", locals: { articles: #articles, title: 'My Title' } %>
Then in your layout do:
<div class="panel panel-default skill-evaluation-box">
<div class="skill-heading">
<div class="row">
<div class="col-xs-12">
<%= title.upcase %>
</div>
</div>
</div>
<div class="skill-body scrollable">
<%= yield %>
</div>
</div>

Related

Rails : Named yield not rendering wit partial

I am trying to render a partial using a named yield. Here's my template
<%= render 'shared/remote_modal', modal_title: "bru" do %>
<%= content_for(:modal_content) do %>
<%= render #registrations, show_learner: true, show_product: false %>
<% end %>
<% end %>
and here's shared/remote_modal :
<div class="relative px-4 w-full max-w-6xl md:h-auto">
<!-- Modal content -->
<div class="relative bg-white rounded-lg shadow dark:bg-gray-700">
<!-- Modal header -->
<div class="w-full">
<div class="flex justify-between items-start p-5 rounded-t">
<div class="w-full-0">
<h3 class="text-lg font-semibold text-gray-900 dark:text-white">
<%= modal_title %>
</h3>
</div>
<i class="close-modal fas fa-times text-lg cursor-pointer"></i>
</div>
</div>
<!-- Modal body -->
<div class="px-6">
<div class="w-full">
<%= yield(:modal_content) %>
</div>
</div>
</div>
As you can see shared/remote_modal has yield(:modal_content) and my template uses content_for(:modal_content). However the block within content_for(:modal_content) is not rendering.
If I change it to a "simple" "unnamed" yield though, it will render properly. Are named yield not supported with partials ? Is there a workaround to this ?
A possible issue could be that you're using content_for inside the partial, which might not work as expected.
Instead, you can try a different approach (not sure if that's the same you already tried). Try passing the content as a block (without using content_for), and then render this block inside the partial using a simple yield:
<%= render 'shared/remote_modal', modal_title: "bru" do %>
<%= render #registrations, show_learner: true, show_product: false %>
<% end %>
Partial:
...
<!-- Modal body -->
<div class="px-6">
<div class="w-full">
<%= yield %>
</div>
</div>
...

Display last record in erb Rails with will_paginate

I am trying to display the last instanced record from my model Tribune with this layout :
Some random tribune
Some random tribune
Last
Last recorded tribune
I am using the gem will_paginate which allow me to display 10 tribunes / per page.
The issue is that the layout is working but applied to each page.
Every 10 tribunes, one is identified as "last". Obviously, I would like to have only one tribune identified as last.
Here is my code :
<div class="wrapping">
<% #tribunes.each do |tribune| %>
<div class="container">
<div class="mouse-out-container"></div>
<div class="row">
<% if tribune == #tribunes.last
%>
<h1>Last</h1>
<div class="col-xs-12 col-md-12">
<div class="card">
<div class="card-category">Popular</div>
<div class="card-description">
<h2><%= tribune.title %></h2>
<p><%= tribune.content.split[0...25].join(' ') %>...</p>
</div>
<img class="card-user" src="https://kitt.lewagon.com/placeholder/users/tgenaitay">
<%= link_to "", tribune, :class => "card-link" %>
</div>
<% else %>
<div class="col-xs-12 col-md-12">
<div class="card">
<div class="card-category">Popular</div>
<div class="card-description">
<h2><%= tribune.title %></h2>
<p><%= tribune.content.split[0...25].join(' ') %>...</p>
</div>
<img class="card-user" src="https://kitt.lewagon.com/placeholder/users/tgenaitay">
<%= link_to "", tribune, :class => "card-link" %>
</div>
</div>
<% end %>
<% end %>
</div>
<div class="center-paginate">
<%= will_paginate #tribunes, renderer: BootstrapPagination::Rails %>
</div>
</div>
</div>
When all Goole-fu fails, we have to dig in the source code. There we find some interesting methods:
# Any will_paginate-compatible collection should have these methods:
# current_page, per_page, offset, total_entries, total_pages
#
# It can also define some of these optional methods:
# out_of_bounds?, previous_page, next_page
From these, the method next_page looks interesting, as it seems to return nil if there are no more pages.
Now we can construct the loop:
<% #tribunes.each do |tribune| %>
<% if !#tribunes.next_page && tribune == #tribunes.last %>
<!-- We're on the last page and the last tribune of that page -->
Last tribune content
<% else %>
<!-- We still have tribunes to go -->
Normal tribune content
<% end %>
<% end %>

Rails 4 - Multiple yield blocks on a partial

Is it possible to have a partial using more than one yield block? I wanted to use it to implement bootstrap modal boxes on my project, kinda like this:
<div class="modal fade" role="dialog" aria-labelledby="myModalLabel">
<div class="modal-dialog modal-lg" role="document">
<div class="modal-content">
<div class="modal-header">
<%= yield :header %>
</div>
<div class="modal-body">
<%= yield :body %>
</div>
<div class="modal-footer">
<%= yield :footer %>
</div>
</div>
</div>
</div>
And this is more or less how I was thinking of using it
<%= render partial: "shared/modal" do %>
<% content_for :header do %>
...
<% end %> %>
<% content_for :body do %>
...
<% end %> %>
<% content_for :footer do %>
...
<% end %> %>
<% end %>
Is there a way to do this? Is this maybe a bad approach for some reason?
Through much trial and error, I think I solved this in Rails 5 but needed to insert a blank yield in my partial in order to get it to work:
_partial.html.erb
<div class="partial">
<%= yield %> <!--Does not do anything-->
<div class="header">
<%= yield :header %>
</div>
<div class="text">
<%= yield :text %>
</div>
</div>
Implemented as:
full_layout.html.erb
<%= render "partial" do %>
<% content_for :header do %>
<h1>Header content</h1>
<% end %>
<% content_for :text do %>
<p>Text content</p>
<% end %>
<% end %>
I think you have an issue with your render partial. Notice you have all of your content_for blocks within the render partial block.
<%= content_for :thing do %>
Some content
<% end %>
<%= render partial: "blah" %>
It's no problem to use multiple yield blocks in your partial. Only thing is to make sure that many is actually needed. For an example content_for sections are basically placeholders for content that could vary based on the logic of the application. So, it's totally fine to use multiple yields in one page.
You can also just run put an yield there withouth the output. This way you can also use the default block.
<div>
<% yield %>
<div class="mt-3">
<div class="text-2xl tracking-wide font-bold text-gray-900">
heading
<%= yield :heading %>
</div>
</div>
<div class="relative bg-white rounded-xl shadow-xl mb-8 min-h-28">
<%= yield %>
</div>
...

Rails loop, link_to model_view

Rails: Need help looping through model array to link to the show page. I want to show the name, but link to the path. Seems like it should be simple but I have been coding all night and my brain is fried! please help.
<div class="container">
<div class="row">
<% #bars.each do |bar| %>
<div class="col-xs-6 something">
<div class="firstBar">
<%= link_to bars_path %>
<% end %>
</div>
</div>
</div>
</div>
This should work:
<div class="container">
<div class="row">
<% #bars.each do |bar| %>
<div class="col-xs-6 something">
<div class="firstBar">
<%= link_to bar.name, bar %>
</div>
</div>
<% end %>
</div>
</div>
You could also do <%= link_to bar.name, bars_path(bar) %>, but is prettier to just give the object. Rails will know which Url helper to use given a specific object.
Take a look at the UrlHelper documentation
Try this
<div class="container">
<div class="row">
<% #bars.each do |bar| %>
<div class="col-xs-6 something">
<div class="firstBar">
<%= link_to bar.name, bar_path(bar) %>
</div>
</div>
<% end %>
</div>
</div>

rails leaving out some parts from fragment caching

I have a rails 4 app using pundit gem for authorization. If I do russian-doll fragment caching like the code below, the conditional statement used for authorization will be also cached, which is not good, since edit/delete buttons should only be available for the post.user.
What is the good way to get around this? Should I split the cache into smaller parts or is there a way to exclude some parts of the caching? What's the rails convention in this case?
index.html.erb
<% cache ["posts-index", #posts.map(&:id), #posts.map(&:updated_at).max, #posts.map {|post| post.user.profile.updated_at}.max] do %>
<%= render #posts %>
<% end %>
_post.html.erb
<% cache ['post', post, post.user.profile ] do %>
<div class="row>
<div class="col-md-2">
<%= link_to user_path(post.user) do %>
<%= image_tag post.user.avatar.url(:base_thumb), class: 'post-avatar' %>
<% end %>
</div>
<div class="col-md-8">
<span class="post-user-name"><%= post.user.full_name %></span>
<span class="post-updated"><%= local_time_ago(post.updated_at) %></span>
<div class="post-body">
<%= post.body %>
</div>
<div class="col-md-2" style="text-align:right;">
<!--############### THIS IS THE PART THAT SHOULD NOT BE CACHED #############-->
<% if policy(post).edit? && policy(post).delete? %>
<li class="dropdown">
<ul class = "dropdown-menu dropdown-menu-right">
<li>
<%= link_to "Edit Post", edit_post_path(post), remote: true, type: "button", 'data-toggle' => "modal", 'data-target' => "#updatepost_#{post.id}" %>
</li>
<li>
Delete Post
</li>
</ul>
</li>
<% end %>
<!--########################## UNTIL HERE ############################-->
</div>
</div>
<div class = "row comment-top-row" style="padding-bottom:10px;">
<div class="col-md-12 post-comment-form">
<%= render partial: 'posts/post_comments/post_comment_form', locals: { post: post } %>
</div>
</div>
<div class = "row">
<div class="col-md-12 post-comment-insert-<%= post.id%>">
<%= render partial: 'posts/post_comments/post_comment', collection: post.post_comments.ordered.included, as: :post_comment, locals: {post: post} %>
</div>
</div>
<% if policy(post).edit? %>
<div class="modal fade updatepost" id="updatepost_<%= post.id %>" tabindex="-1" role="dialog" aria-labelledby="myModalLabel">
<!-- FORM GETS RENDERED HERE VIA JS -->
</div>
<% end %>
<% if policy(post).delete? %>
<div class="modal fade" id="deletepost_<%= post.id %>" tabindex="-1" role="dialog" aria-labelledby="myModalLabel">
......
</div>
<% end %>
<% end %>
Russian Doll Caching is simple but handy way for caching, there's no complex options or convention to exclude part of fragment from it. Above that, It's more relative to cache strategies. Here are two strategies for this user specific situation:
Rearrange individually and Cache fragments manually, which I don't recommend. Because it's more complex and doesn't leverage the advantages of Russian Doll Caching. Not so maintainable as well. Here is an example:
index.html.erb
<% # pull out cache %>
<%= render #posts %>
_post.html.erb
<% cache post %>
<%= # first part %>
<% end %>
<% # without cache %>
<%= # user specific part %>
<% cache post %>
<%= # third part %>
<% end %>
Preferred way: Add current_user as part of cache_key, which means you will have as many fragment caches as approximately your users and the fragments will automatically invalidate whenever the post or the user has changed their fingerprint. This is more elegant and maintainable. Here is an example:
index.html.erb
<% cache ["posts-index", #posts.map(&:id), #posts.map(&:updated_at).max, #posts.map {|post| post.user.profile.updated_at}.max] do %>
<%= render #posts %>
<% end %>
_post.html.erb
<% cache ['post', post, post.user.profile, current_user ] do %>
<div class="row>
<div class="col-md-2">
<%= link_to user_path(post.user) do %>
<%= image_tag post.user.avatar.url(:base_thumb), class: 'post-avatar' %>
<% end %>
</div>
<div class="col-md-8">
<span class="post-user-name"><%= post.user.full_name %></span>
<span class="post-updated"><%= local_time_ago(post.updated_at) %></span>
<div class="post-body">
<%= post.body %>
</div>
<div class="col-md-2" style="text-align:right;">
<% if policy(post).edit? && policy(post).delete? %>
<li class="dropdown">
<ul class = "dropdown-menu dropdown-menu-right">
<li>
<%= link_to "Edit Post", edit_post_path(post), remote: true, type: "button", 'data-toggle' => "modal", 'data-target' => "#updatepost_#{post.id}" %>
</li>
<li>
Delete Post
</li>
</ul>
</li>
<% end %>
</div>
</div>
<div class = "row comment-top-row" style="padding-bottom:10px;">
<div class="col-md-12 post-comment-form">
<%= render partial: 'posts/post_comments/post_comment_form', locals: { post: post } %>
</div>
</div>
<div class = "row">
<div class="col-md-12 post-comment-insert-<%= post.id%>">
<%= render partial: 'posts/post_comments/post_comment', collection: post.post_comments.ordered.included, as: :post_comment, locals: {post: post} %>
</div>
</div>
<% if policy(post).edit? %>
<div class="modal fade updatepost" id="updatepost_<%= post.id %>" tabindex="-1" role="dialog" aria-labelledby="myModalLabel">
<!-- FORM GETS RENDERED HERE VIA JS -->
</div>
<% end %>
<% if policy(post).delete? %>
<div class="modal fade" id="deletepost_<%= post.id %>" tabindex="-1" role="dialog" aria-labelledby="myModalLabel">
......
</div>
<% end %>
<% end %>

Resources