I'm trying to insert some images in between my topics (one in between every 3 topics for the first 3 groups of topics -- ie. 3 images in total), but I'm confused as to where since my forums/show.html.erb only has <%= render #topics %> and not some sort of each loop that I could manipulate.
If you are not looking for a very fancy ruby way to do it, just do it the dirty way:
<% #topics.each_with_index do |topic, index| %>
<% if index != 0 && index%3==0 %>
<%= render #ad %>
<% end %>
<%= render topic %>
<% end %>
or if your partial takes the entire array do that in your partial logic. However if you want a fancy way, this solution will help you: New row every 3 items
Related
I have a rails 4 app. I have to differentiate the different cache keys somehow but don't know the naming conventions.
FIRST EXAMPLE:
I have a task model with index, completed_tasks and incoming_tasks actions. A have the same instance name (#tasks) though because of the pagination.
At the moment the cache-keys are named like below. My questions: 1. Is the cache-key structure good enough? 2. Is it important in which order I put the parts of the key in the array? For example [#tasks.map(&:id), #tasks.map(&:updated_at).max, 'completed-tasks'] is better than ['completed-tasks', #tasks.map(&:id), #tasks.map(&:updated_at).max]?
completed_tasks.html.erb
<% cache([#tasks.map(&:id), #tasks.map(&:updated_at).max, 'completed-tasks']) do %>
<%= render #tasks %>
<% end %>
tasks.html.erb
<% cache([#tasks.map(&:id), #tasks.map(&:updated_at).max]) do %>
<%= render #tasks %>
<% end %>
incoming_tasks.html.erb
<% cache([#tasks.map(&:id), #tasks.map(&:updated_at).max, 'incoming-tasks']) do %>
<%= render #tasks %>
<% end %>
SECOND EXAMPLE:
I also have problem with the naming conventions of the russian-doll-caching:
products/index.html.erb
<% cache([#products.map(&:id), #products.map(&:updated_at).max]) do %>
<%= render #products %>
<% end %>
_product.html.erb
<% cache(product) do %>
<%= product.name %>
....
<% end %>
Is this version good enough or I always should put some string in both the outer and inner caching key array to avoid problems with similarly named cache-keys on other pages. For instance I plan to put <% cache(#product) do %> on the profile#show page which would be exactly the same like the inner caching in my example. If the key has to be different what the rails convention is to name the inner an outer cache keys?
First, according to Russian Doll Caching article, I think it's not necessary to set the cache_key on your own, you could just leave it to rails, it generates cache_key auto. For example, the cache_key of #tasks = Task.incoming should differ from #tasks = Task.completed with something like views/task/1-20160330214154/task/2-20160330214154/d5f56b3fdb0dbaf184cc7ff72208195e and views/task/3-20160330214154/task/4-20160330214154/84cc7ff72208195ed5f56b3fdb0dbaf1
cache [#tasks, 'incoming_tasks'] do
...
end
Second, As for the namespace, though the template digest will be the same but the #tasks digest will be different. So it seems to be okay without namespace in this case.
cache #tasks do
...
end
Third, when it comes to namespace, I prefer prefix rather than suffix. i.e.
cache ['incoming_tasks', #tasks] do
...
end
As the second example, I think this would do just fine.
<% cache #product do # first layer cache %>
<% cache #products do # second layer cache %>
<%= render #products %>
<% end %>
<% cache #product do # second layer cache %>
<%= product.name %>
....
<% end %>
<% end %>
The caching key for app/views/products/show.html.erb will be something like views/product/123-20160310191209/707c67b2d9fb66ab41d93cb120b61f46. That last bit is a MD5 of the template file itself and all of its dependencies. It'll change if you change either the template or any of the dependencies, and thus allow the cache to expire automatically.
For further reading: https://github.com/rails/cache_digests
It's best practice to always put a string at the end. It really just needs to be something that makes sense to you.
Rails 4.2
I have a collection displaying from a partial - is there a way to add a fragment of code in after a certain number in the collection?
So I have a 10 blog posts being shown in a list, each post's formatting from the partial list. I'd like to insert my AdSense code after every 3 posts
<%= render partial: "list", collection: #posts %>
Is there a way to do that automatically, or would I have to do something like assign a numbered id to each post and have "if (id/3) % 1 == 0", and conditionally display it?
Well inside the partial _list.html.erb, you can do check the counter variable provided by Rails list_iteration. So your check will be inside the _list.html.erb partial as:
render 'ad' if list_iteration.index % 3 == 0
You can also use list_counter helper.
Read this Rendering a collection of partials
<%= render partial: "ad", collection: #advertisements %>
This will render “advertiser/_ad.html.erb” and pass the local variable ad to the template for display. An iteration object will automatically be made available to the template with a name of the form partial_name_iteration. The iteration object has knowledge about which index the current object has in the collection and the total size of the collection. The iteration object also has two convenience methods, first? and last?. In the case of the example above, the template would be fed ad_iteration. For backwards compatibility the partial_name_counter is still present and is mapped to the iteration's index method.
Something like:
<% #posts.each_slice(3) do |posts| %>
<%= render partial: "list", collection: posts %>
<%= render partial: 'ad_sense' %>
<% end %>
Might work
each_slice docs for further reading
You could do something with the loop index like
<% #posts.each_with_index do |post, index| %>
<%= render partial: 'list', locals:{post: post} %>
<% if index % 3 == 2 %>
#adsense code here
<% end %>
<%end %>
I'm trying to experiment with blocks and how to iterate over collections in ERB. I have a models in a one-to-many relatinship (Channel and their corresponding types).
controller
class HomePageController < ActionController
def index
#channels = Channel.all
end
end
Then in the view, I iterate over all the attributes belonging to a Channel. When I want to print all types, this code gives me the desired output:
view
<% #channels.each do |channel| %>
<% #types.each do |type| %>
<%= Type.find(type).name %>
<% end %>
<% end %>
At first I tried to achieve this by using the yield keyword in a neat one-liner but I couldn't manage to print anything to the browser, only to the console
<% #types.each {|type| yield Type.find(type).name } %>
Is there an equivalent one-liner?
First of all this method is so inefficient, you are doing n-queries, to find each record of type Type instead convert those into an array of types by using a single query in the controller, assume that that array is in type_ids
# controller
#channels = Channel.includes(:types) # avoiding n+1 queries
# view
<% #channels.each do |channel| %>
# some channel info output
<% channel.types.each do |type| %>
<%= type.name %>
<% end %> # types loop
<% end %> # channel loop
As #Almaron mentioned, you could render a partial for more simplification, if you have a partial called _type.html.erb you can call render directly
# view
<%= render channel.types %>
Rails will do all the iterating and rendering.
First of all, this kind of code does not belong to the view. Don't tackle the database from the view (in your case Type.find()). Move it to the controller where it belongs.
The second thing to note is the difference between <%= and <% tags. The first one outputs the returned result, while the second one doesn't. The problem with .each is that it returns the object it has been used on, so in your case if you just go <%= #types.each {|type| Type.find(type).name } %> you'll get the #types array printed out.
If you want to simplify that code, you can use a helper method for iterating and a partial for rendering each item. That way you get something like this
<% collection_iterate #items, 'item_partial' %>
I'm fairly new to rails, and working on a application that would display daily submissions. I'm now trying to display posts for every day. So I did a few things:
Set up the controller instance variable to a single #submissions, which takes the 'num_days_ago' input:
#num_days_ago = 1
#submissions = Submission.daily(#num_days_ago).paginate(page: params[:page], per_page: 10).order('rank DESC')
Set up the scope to be responsive to the num_days_ago input:
scope :daily, -> (num) { where( created_at: (num.days.ago.beginning_of_day..num.days.ago.end_of_day)) }
Added some helper methods to handle the dates in the views:
module WelcomeHelper
def the_date(num_days)
date = num_days.days.ago.to_date
date.to_formatted_s(:long_ordinal) end
def num_days_since_first_day
(Date.today - (Submission.last.created_at).to_date ).to_i end
end
Created a loop in the view to make it display all daily posts (I will add pagination and infinite scroll later):
<div class="container">
<% (0..num_days_since_first_day).each do |num| %>
<h3><%= the_date(num) %></h3>
<ul>
<%= render #submissions %>
</ul>
<div id="show_more">
<%= link_to 'Show More', welcome_index_path(page: 2), class: "show_more_link", remote: true %>
</div>
<% #num_days_ago +=1 %>
<% end %>
</div>
Also, here is the submissions partial being displayed in the Index view:
<li>
<%= render partial: 'votes/voter', locals: { submission: submission } %>
<%= submission.title %></br>
<%= submission.description %> | $<%= submission.price %> | <b><%= submission.merchant.name %></b> | Submitted by: <%= submission.user.name %>
</li>
</br>
So now, the issue I'm having is that the #submissions instance variable will always be the first day. So I'm trying to figure out how to get this to be responsive to the #num_days_ago which is being updated += 1 after leach loop. Any suggestions on how to make this happen?
Right now, it's displaying all of the days that have had posts since the first day a post was created, but the posts displayed for each day are all the 12 posts that were created on the first day.
#submissions always displays the submission of the first day because you've never changed it after you initialized it in the controller. Incrementing #num_days_ago in the view does nothing because the view does not use it.
If you want to display submissions since the first day, you need to set an array of submission from the first day to #submissions.
#submissions = num_days_since_first_day.times.map do |num_days_ago|
Submission.daily(num_days_ago).paginate(page: params[:page], per_page: 10).order('rank DESC')
end
And render each of them as you iterate in the view.
<%= render #submissions[num] %>
I think you can remove some redundant code and further refactor the code above, but that's the basic idea.
Expanding on New row every 3 items -- I'm trying to insert ads in between my Forem (https://github.com/radar/forem) topics -- one in between every 3 topics for the first 3 groups of topics (3 ads in total).
UPDATE
I ended up with this thanks to the answer below, unfortunately it doesn't seem to pass on test as a valid local (can't find topic inside forem/topics/topic):
<% #topics.in_groups_of(3).each_with_index do |grouped_topics, index| %>
<%= render partial: "forem/topics/topic", collection: grouped_topics %>
<% if index < 3 %>
<p>Ad</p>
<% end %>
<% end %>
Live test app (click the big green Run button to test):
http://runnable.com/VFUNK2ho3Fpr8Fp2/forem-with-ads-in-between-topics
File in question: views/forem/forums/show.html.erb
in_groups_of split your array into array of arrays, so that should be:
<% #comments.in_groups_of(3, false).each_with_index do |grouped_comments, index| %>
<% grouped_comments.each do |comment %>
...
<% end %>
<% if index < 3 %>
<%= image_tag "selfie.jpg">
<% end %>
<% end %>