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
Related
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
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.
I have a scenario where i am calling a Controller method from the view,that returns an array of pages,which can be either nil or must be having values.i need to iterate that array in a for loop to generate a dynamic list in my view.pls help
Implementing logic in your controllers is generally not a good idea you should move logic into the corresponding model.
But if you really want to you can use controller variable to reach the actual controller from your view.
Example:
<p>
Now we're printing out the result of the some_method:
<%= controller.some_method.inspect %>
</p>
Iterating through the returned value is easy:
<ul>
<% controller.some_method.each do |item| -%>
<li>item.inspect</li>
<% end -%>
</ul>
Probably the visibility of the method in your controller is not public, so you may need to use the Object#send method.
<%= controller.send(:my_protected_method).inspect %>
Update
To handle the nil reponse, I would create a helper method in the ApplicationHelper class:
def my_method
(controller.my_method || [])
end
And then in the view:
<% my_method.each do |item| -%>
<%= item.inspect %>
<% end %->
I'm following the instructions at: http://agilewebdevelopment.com/plugins/acts_as_taggable_on_steroids to add the tag cloud to my view:
in the controller:
class PostController < ApplicationController
def tag_cloud
#tags = Post.tag_counts
end
end
I also added the tag_cloud method as a helper method in the controller
and in the view:
<% tag_cloud #tags, %w(css1 css2 css3 css4) do |tag, css_class| %> (line 1)
<%= link_to tag.name, { :action => :tag, :id => tag.name }, :class => css_class %> (line2)
<% end %> (line 3)
However:
1) if I don't add the helper_method :tag_cloud in the controller I get a undefined method error for tag_cloud
2) if I do add the helper method I get: wrong number of arguments (2 for 0) on the same line 1 of my sample code above.
Suggestions?
SOLUTION
I ended up not doing what I had as example code in the view.
Instead I did this:
<% #post.tags.each do |tag| %>
<%= link_to( tag.name,tag,:class => "tag_cloud_item_link") %>
<% end %>
1.
Methods defined in the controller are not accessible to views unless you add (as you mention) the helper_method call.
2.
Your method tag_cloud that you've defined as a helper in your controller doesn't take any parameters, but you are trying to call tag_cloud with #tags, %w(css1...), & a block.
Your tag_cloud method will return an #tags instance variable and that's it.
From the post you've provided that you are working off of, did you include TagsHelper in your ApplicationHelper? I'm guessing that this defines a tag_cloud helper method that will accept the params that you are trying to pass in.
I'm having the same issue. Just like the OP, Moving the "tag_cloud" helper method to the TagHelper seemed to get rid of some issue, but creates the "wrong number of arguments" error in the process.
cbrulak said he found a work arround. Can you update us and possibly send a PM to the "Acts-as-taggable-on" authors at https://github.com/mbleigh/acts-as-taggable-on
I arrived here because I wanted to define a method to use in the view. The simple answer was that it should have been defined in the model (e.g. conversation.rb file), and not in the helper.
I've been away from Rails for a while now, so maybe I'm missing something simple.
How can you accomplish this:
<%= yield_or :sidebar do %>
some default content
<% end %>
Or even:
<%= yield_or_render :sidebar, 'path/to/default/sidebar' %>
In the first case, I'm trying:
def yield_or(content, &block)
content_for?(content) ? yield(content) : yield
end
But that throws a 'no block given' error.
In the second case:
def yield_or_render(content, template)
content_for?(content) ? yield(content) : render(template)
end
This works when there's no content defined, but as soon as I use content_for to override the default content, it throws the same error.
I used this as a starting point, but it seems it only works when used directly in the view.
Thanks!
How about something like this?
<% if content_for?(:whatever) %>
<div><%= yield(:whatever) %></div>
<% else %>
<div>default_content_here</div>
<% end %>
Inspiration from this SO question
Try this:
# app/helpers/application_helper.rb
def yield_or(name, content = nil, &block)
if content_for?(name)
content_for(name)
else
block_given? ? capture(&block) : content
end
end
so you could do
<%= yield_or :something, 'default content' %>
or
<%= yield_or :something do %>
block of default content
<% end %>
where the default can be overridden using
<%= content_for :something do %>
overriding content
<% end %>
I didn't know you could use content_for(:content_tag) without a block and it will return the same content as if using yield(:content_tag).
So:
def yield_or_render(content, template)
content_for?(content) ? content_for(content) : render(template)
end