Discrepancy when capturing Rails view block - ruby-on-rails

I have an ERB view with two blocks:
<%= test_h1 do %>
<%= 'test1' %>
<% end -%>
<%= test_h2 do %>
<%= 'test2' %>
<% end -%>
where test_h1 and test_h2 are similar helpers, but one is defined in a helper file, while another via helper_method in a controller:
module TestHelper
def test_h1(&block)
link_to '/url' do
capture(&block)
end
end
end
class TestController < ApplicationController
helper_method :test_h2
def test_h2(&block)
helpers.link_to '/url' do
helpers.capture(&block)
end
end
end
test_h1 produces the expected result and test_h2 renders the inner template block first:
test1
test2
Why? What would be an idiomatic way to write test_h2 ?

I think both examples of views should be re-written as:
<%= test_h1 do %>
<% 'test1' %>
<% end -%>
<%= test_h2 do %>
<% 'test2' %>
<% end -%>
My understanding that '<%=' forces to render the output of the block to the output stream, that was not an intended behavior in these two examples

capture overrides current output buffer and just calls the block (which is still bound to other view context), thus override has no effect when called from controller because view_context is not the same context the view is being rendered in.
To work around contexts you can define your helper like so:
# in controller
helper do
def test_h3(&block)
# this will run in view context, so call `controller.some_func` to access controller instance
link_to '/url' do
capture(&block)
end
end
end

When using capture from your controller the output is appended to the page buffer, as a result the <%= from of your erb is outputting immediately to the page output.
To work around, you need to use <% instead within your test_h2 block. So to get the expected behavior in both cases, use this syntax:
<%= test_h1 do %>
<%= 'test1' %>
<% end -%>
<%= test_h2 do %>
<% 'test2' %>
<% end -%>
More info in this article: https://thepugautomatic.com/2013/06/helpers/

The idomatic way to do it in rails would be to move the test_h2 method to a concern and include that concern in controller as well as helper class.
Or else define test_h2 as helper_method in your controller class.
But generally methods that are needed in multiple places should be placed in concerns, and include those concerns wherever needed.
Also if you need methods for views, then include concerns or define your own methods inside helpers.
Refer Can we call a Controller's method from a view (as we call from helper ideally)?
How to use concerns in Rails 4

Related

Rails ERB iterate over array

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' %>

Convert View block to helper method

I have an Each/do block in my view currently, but I'd prefer to push this code into a helper, as I need to add a few conditional statements in there so I don't want to clutter up my view. Here is the view I have currently that I have been trying to code as a helper method with no luck so far
<% update.voters_who_voted.each do |voter| %>
<%= link_to profile_path(voter) do %>
<%= thirty_avatar(voter) %>
<% end %>
<% end %>
How would this translate into a helper with this name
def find_voters_who_voted(update)
...
...
end
I've tried this with no luck
def find_voters_who_voted(update)
update.voters_who_voted.each do |voter|
link_to profile_path(voter) do
thirty_avatar(voter)
end
end
end
Remember that all you are doing is displaying the return value of this method. It looks like you are expecting it to act as if it's part of the view, but that's not the way helpers work. Something like this would return the equivalent of your original code block
def find_voters_who_voted(update)
update.voters_who_voted.collect do |voter|
link_to profile_path(voter) do
thirty_avatar(voter)
end
end.join
end

How do I yield from an ERB code block without rendering it?

Consider the following:
view.html.erb:
<%= make_backwards do %>
stressed
<% end %>
helper.rb:
def make_backwards
yield.reverse
end
The view renders stresseddesserts instead of just desserts. How do I use the content in yield without rendering the code block?
ERB has an internal buffer, which makes using blocks a bit more complicated, as you can see in your code example.
Rails provides a capture method, which allows you to capture a string inside this buffer and return it from a block.
So your helper would become the following:
def make_backwards
capture do
yield.reverse
end
end
You could try doing the ff:
Option 1:
<%= make_backwards { "stressed" } %>
Option 2:
<%= make_backwards do %>
<% "stressed" %>
<% end %>
Let me know if it helps.

Call a controller method automatically when rendering a partial

I have a partial that needs to have some controller logic run before it can render without issue. Is there some way to associate the partial with some controller logic that is run whenever it is rendered?
For example, this is what my current code looks like:
MyDataController:
class MyDataController < ApplicationController
def view
#obj = MyData.find(params[:id])
run_logic_for_partial
end
def some_method_i_dont_know_about
#obj = MyData.find(params[:id])
# Doesn't call run_logic_for_partial
end
def run_logic_for_partial
#important_hash = {}
for item in #obj.internal_array
#important_hash[item] = "Important value"
end
end
end
view.html.erb:
Name: <%= #obj.name %>
Date: <%= #obj.date %>
<%= render :partial => "my_partial" %>
some_method_i_dont_know_about.html.erb:
Name: <%= #obj.name %>
User: <%= #obj.user %>
<%# This will fail because #important_hash isn't initialized %>
<%= render :partial => "my_partial" %>
_my_partial.html.erb:
<% for item in #obj.internal_array %>
<%= item.to_s %>: <%= #important_hash[item] %>
<% end %>
How can I make sure that run_logic_for_partial is called whenever _my_partial.html.erb is rendered, even if the method isn't explicitly called from the controller? If I can't, are there any common patterns used in Rails to deal with these kinds of situations?
You should be using a views helper for this sort of logic. If you generated your resource using rails generate, a helper file for your resource should already be in your app/helpers directory. Otherwise, you can create it yourself:
# app/helpers/my_data.rb
module MyDataHelper
def run_logic_for_partial(obj)
important_hash = {}
for item in obj.internal_array
important_hash[item] = "Important value" // you'll need to modify this keying to suit your purposes
end
important_hash
end
end
Then, in your partial, pass the object you want to operate on to your helper:
# _my_partial.html.erb
<% important_hash = run_logic_for_partial(#obj) %>
<% for item in important_hash %>
<%= item.to_s %>: <%= important_hash[item] %>
<% end %>
Or:
# app/helpers/my_data.rb
module MyDataHelper
def run_logic_for_partial(item)
# Do your logic
"Important value"
end
end
# _my_partial.html.erb
<% for item in #obj.internal_array %>
<%= item.to_s %>: <%= run_logic_for_partial(item) %>
<% end %>
EDIT:
As commented Ian Kennedy points out, this logic can also reasonably be abstracted into a convenience method in your model:
# app/models/obj.rb
def important_hash
hash = {}
for item in internal_array
important_hash[item] = "Important value"
end
hash
end
Then, you'd access the important_hash attribute in the following manner in your partial:
# _my_partial.html.erb
<% for item in #obj.important_hash %>
<%= item.to_s %>: <%= item %>
<% end %>
What you're trying to do runs against the grain of how Rails controllers/views are designed to be used. It would be better to structure things a bit differently. Why not put run_logic_for_partial into a helper, and make it take an argument (rather than implicitly working on #obj)?
To see an example of a view "helper", look here: http://guides.rubyonrails.org/getting_started.html#view-helpers

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