I have a Rails app in which I am rendering a block of Haml stuff stored in a model attribute. It would be nice to use Rails view helpers in that block of Haml. Currently I am using the Haml::Engine#render in a view helper to render the content of this model attribute. It works well enough but I can't use things like =link_to. To illustrate the problem:
irb(main):003:0> haml_text=<<"EOH"
irb(main):004:0" %p
irb(main):005:0" =image_tag 'someimage'
irb(main):006:0" EOH
=> "%p\n =image_tag 'someimage'\n"
irb(main):007:0> engine = Haml::Engine.new(haml_text)
=> #<Haml::Engine:0x7fa9ff7f1150 ... >
irb(main):008:0> engine.render
NoMethodError: undefined method `image_tag' for #<Object:0x7fa9ff7e9a40>
from (haml):2:in `render'
from /usr/lib/ruby/gems/1.8/gems/haml-3.0.25/lib/haml/engine.rb:178:in `render'
from /usr/lib/ruby/gems/1.8/gems/haml-3.0.25/lib/haml/engine.rb:178:in `instance_eval'
from /usr/lib/ruby/gems/1.8/gems/haml-3.0.25/lib/haml/engine.rb:178:in `render'
from (irb):8
Any thoughts on how to do that?
Better ideas?
The render method allows you to specify a context. Something like
base = Class.new do
include ActionView::Helpers::AssetTagHelper
include ApplicationHelper
end.new
Haml::Engine.new(src).render(base)
could work.
Marcel was going in the right direction. But you need to get a valid scope for the render engine from somewhere. What I did was call the helper with a valid scope like this:
In my_view/edit.html.haml
=my_revertable_field(self, 'hello world')
In application_helper.rb
def my_revertable_field(haml_scope, title, field)
template =<<EOS
.field
#{label}
= text_field_tag #{field.name}, #{field.amount}, :size=>5, :class=>"text"
= image_tag("refreshArrow.gif",:class=>"revert-icon", :style=>"display:none;",:title=>"Revert to default, #{field.default}")
EOS
end
Then you have a valid haml scope and so image_tab, form_tag_helpers all work
class RailsRenderingContext
def self.create(controller)
view_context = ApplicationController.helpers
class << view_context; include Rails.application.routes.url_helpers; end
view_context.request = controller.request
view_context.view_paths = controller.view_paths
view_context.controller = controller
view_context
end
end
class MyController < ApplicationController
def show
# ...
engine = Haml::Engine.new haml
ctx = RailsRenderingContext.create(self)
engine.render ctx
end
end
It works for me. Based on this issue.
Related
Just stumbled upon a problem and to the moment, cannot solve it. So here's the setting:
I have an ERB template fetched from the database and rendered to html
Class MyController < ApplicationController
include AssetTagHelper
...
def Show
template=Page.find(...) # <%=image_tag('Test.png')%>
#html=ERB.new(template).result(binding)
end
...
Now the problem is image_tag 'src' resolves into '/images/Test.png', when normally it should resolve to '/assets/Test.png'. So I looked into the rails source of AssetTagHelper which led me to AssetUrlHelper and the following call chain: image_path => asset_path => compute_asset_path. And compute_asset_path legitimately states it should actually resolve to /images/Test.png...
What am I missing here? How can I make the image tag work and give me 'assets/Test.png'?
Thanks in advance for all replies!
Just for the record - while debugging figured out that normally compute_asset_path is overriden in sprockets-rails-2.0.1/lib/sprockets/rails/helper.rb
Solved the issue by moving #html=ERB.new(template).result(binding) from controller to view. Hope this helps somebody ))
Like example I show how to create ERB from database for Mailer class. For another classes all same.
Completed mailer to create email templates from database:
class UserMailer < ActionMailer::Base
# included helper
include ActionView::Helpers::NumberHelper
include ActionView::Helpers::TextHelper
# another helpers...
# included helper
def mailer(from, to, subject, path, name)
mail( from: from, to: to, subject: subject, template_path: path, template_name: name )
end
def get_template(template_name)
#erb = EmailTemplate.where(name: template_name, mailer_name: UserMailer.to_s.underscore).first rescue ''
#template_content_html = ERB.new(#erb.template_html).result(binding).html_safe rescue ''
#template_content_text = ERB.new(#erb.template_text).result(binding).html_safe rescue ''
end
def test(user_id)
from = 'from#mail.com'
recipients = 'to#mail.com'
subject = "test"
template_path = "user_mailer"
get_template(__method__) #def
template_name = "general"
mailer(from, recipients, subject, template_path, template_name)
end
end
for include helper in Mailer you can use construction like this:
include ActionView::Helpers::NumberHelper
works perfect from rails 3.2.13. earlier not tried.
I'm new to Rails and I'm having a hard time understanding why my helper module doesn't work when called from the ActionMailer. I'm calling the same method from a different partial and it works fine. The problem is not so much the method but my session variable (session[:geo]) - it says "undefined method `session'".
here is my code any suggestion is much appreciated
products_helper.rb
def isUserLocal?
session[:geo] #true or false
end
def itemTotalPrice(item)
if self.isUserLocal?
item.line_item_us_total_price
else
item.line_item_w_total_price
end
end
order-notifier - ActionMailer
class OrderNotifier < ActionMailer::Base
helper :Products #helpers are not available in ActionMailers by default
received.html.erb
<%= render #order.line_items -%>
_line_items.html.erb
number_to_currency(itemTotalPrice(line_item))
I am trying to render html in my model (for Mandrill inline code) but i cant seem to get the url_helpers to render correctly
questions.haml
%a{:href => email_question_url(question)}
model.rb
view = ActionView::Base.new(ActionController::Base.view_paths, #email_vars)
view.extend ApplicationHelper
questions_html = view.render(:partial => 'transactional_mailer/questions_html')
I ideally want to run:
view.include Rails.application.routes.url_helpers
But that bombs out with undefined method include for actioniew::base
Any suggestions on how i could approach this?
I had to open the instance class and include the instance methods within it via:
view = ActionView::Base.new(ActionController::Base.view_paths,{})
class << view
include Rails.application.routes.url_helpers
end
questions_html = view.render(:partial => 'transactional_mailer/questions_html')
In my case, where I needed to test (via RSpec) a class that required a reference to the view instance inside of it, I did the following:
let(:view) do
ActionView::Base.new(ActionController::Base.view_paths, {}).tap do |view_instance|
view_instance.instance_eval { self.class.include Rails.application.routes.url_helpers }
end
end
It worked for me. It doesn't look nice but it works.
In my Rails 3 application I use Ajax to get a formatted HTML:
$.get("/my/load_page?page=5", function(data) {
alert(data);
});
class MyController < ApplicationController
def load_page
render :js => get_page(params[:page].to_i)
end
end
get_page uses the content_tag method and should be available also in app/views/my/index.html.erb.
Since get_page uses many other methods, I encapsulated all the functionality in:
# lib/page_renderer.rb
module PageRenderer
...
def get_page
...
end
...
end
and included it like that:
# config/environment.rb
require 'page_renderer'
# app/controllers/my_controller.rb
class MyController < ApplicationController
include PageRenderer
helper_method :get_page
end
But, since the content_tag method isn't available in app/controllers/my_controller.rb, I got the following error:
undefined method `content_tag' for #<LoungeController:0x21486f0>
So, I tried to add:
module PageRenderer
include ActionView::Helpers::TagHelper
...
end
but then I got:
undefined method `output_buffer=' for #<LoungeController:0x21bded0>
What am I doing wrong ?
How would you solve this ?
To answer the proposed question, ActionView::Context defines the output_buffer method, to resolve the error simply include the relevant module:
module PageRenderer
include ActionView::Helpers::TagHelper
include ActionView::Context
...
end
Helpers are really view code and aren't supposed to be used in controllers, which explains why it's so hard to make it happen.
Another (IMHO, better) way to do this would be to build a view or partial with the HTML that you want wrapped around the params[:page].to_i. Then, in your controller, you can use render_to_string to populate :js in the main render at the end of the load_page method. Then you can get rid of all that other stuff and it'll be quite a bit cleaner.
Incidentally, helper_method does the opposite of what you're trying to do - it makes a controller method available in the views.
If you don't want to include all of the cruft from those two included modules, another option is to call content_tag via ActionController::Base.helpers. Here's some code I recently used to achieve this, also utilizing safe_join:
helpers = ActionController::Base.helpers
code_reactions = user_code_reactions.group_by(&:code_reaction).inject([]) do |arr, (code_reaction, code_reactions_array)|
arr << helpers.content_tag(:div, nil, class: "code_reaction_container") do
helpers.safe_join([
helpers.content_tag(:i, nil, class: "#{ code_reaction.fa_style } fa-#{ code_reaction.fa_icon }"),
helpers.content_tag(:div, "Hello", class: "default_reaction_description"),
])
end
end
I have a scenario where I would like to have the name of the view that will be rendering while I'm in the layout file. I can find solutions to find which layout will be wrapping the current view from the view, but not the other way around. How can I find which view is rendering?
In Rails 3.0.3, I was able to see the name of the controller and action using controller_name and action_name. But those are not publicly documented (at least the action name) so I wouldn't depend on it long-term.
It might be better to monkey patch template render. In an initializer:
module ActionView::Rendering
alias_method :_render_template_original, :_render_template
def _render_template(template, layout = nil, options = {})
#last_template = template
_render_template_original(template, layout, options)
end
end
Then use #last_template in your layout.
The following solution works in Rails 3.1. Place this code in an initializer.
(The rails 3.0.3 answer is not working any more in Rails 3.1)
This enables an #active_template variable for every controller. This is an instance of an ActionViewTemplate class.
The method active_template_virtual_path method returns the template as a name in the following form "controller/action"
class ActionController::Base
attr_accessor :active_template
def active_template_virtual_path
self.active_template.virtual_path if self.active_template
end
end
class ActionView::TemplateRenderer
alias_method :_render_template_original, :render_template
def render_template(template, layout_name = nil, locals = {})
#view.controller.active_template = template if #view.controller
result = _render_template_original( template, layout_name, locals)
#view.controller.active_template = nil if #view.controller
return result
end
end
I like the following approach, as you can reduce code size a good bit in a lot of cases. Include this in your application_controller.rb:
before_filter :instantiate_controller_and_action_names
caches_action :instantiate_controller_and_action_names
def instantiate_controller_and_action_names
#current_action = action_name
#current_controller = controller_name
end
Then, if your views correspond to an action, you just do:
dosomething if #current_action == 'new'