access ActionView helpers from inside the Model

2019-08-31 02:23发布

问题:

I have a simple template system which calls a show_me method defined inside different classes of my model (kinda Widgets) when rendering a template. These widgets originally returned the html as a string. So in my erb I have somethink like this.

<% @template.widgets.each do |widget| %>
   <%= widget.show_me %>    
<% end %>

As the views of the widgets became more complex I start using partials to render them, calling the ActionView::Base render method from inside my widgets (please don't throw up yet :)

def show_me
      # ... specific calculations and other magic.
    ActionView::Base.new(MyApp::Application.config.view_path).render(:partial => "widgets/weather_widget", :locals => {:data => data, :settings => settings})  
end

So, this works like a charm (really...) but when I want to use helpers inside the widget specific partial (eg. widgets/_weater_widget.html.erb) they don't work. for example. javascript_tag raises

can't convert nil into String
actionpack (3.0.0) lib/action_view/helpers/asset_tag_helper.rb:790:in `join'
actionpack (3.0.0) lib/action_view/helpers/asset_tag_helper.rb:790:in `rails_asset_id'
actionpack (3.0.0) lib/action_view/helpers/asset_tag_helper.rb:813:in `rewrite_asset_path'
actionpack (3.0.0) lib/action_view/helpers/asset_tag_helper.rb:742:in `compute_public_path'
actionpack (3.0.0) lib/action_view/helpers/asset_tag_helper.rb:256:in `javascript_path'
actionpack (3.0.0) lib/action_view/helpers/asset_tag_helper.rb:822:in `javascript_src_tag'
actionpack (3.0.0) lib/action_view/helpers/asset_tag_helper.rb:362:in `block in javascript_include_tag'
actionpack (3.0.0) lib/action_view/helpers/asset_tag_helper.rb:362:in `collect'
actionpack (3.0.0) lib/action_view/helpers/asset_tag_helper.rb:362:in `javascript_include_tag'

I think I'm missing something when I create the ActionView::Base.

Any thoughts how to solve this? Also any suggestion on the overall design is welcome too :)

回答1:

Please for the love of all things MVC don't render view code from a model. :( There is always a better way to do it.

Example:

Model

class Widget < ActiveRecord::Base
  def name
    "weather_widget"
  end

  def settings
    ## something here
  end

  def data
    ## calculations here
  end
end

Helper

module WidgetHelper

  def render_widget(widget)
    render(:partial => "widgets/_#{widget.name}", :locals => {:data => widget.data, :settings => widget.settings})
  end

end

View

<% @template.widgets.each do |widget| %>
  <%= render_widget(widget) %>    
<% end %>

Obviously that may not be the exact solution for your situation, but is just a guide to show you something that can be done to keep your code clean. :)



回答2:

You may be able to get a call to a view helper to work from a model, but Rails will fight you every step of the way. Rails doesn't want you to do things like this.

Consider introducing a widget presenter class. It could live in app/helpers, like

module WidgetHelper
  class WidgetPresenter < Struct.new(:widget)
    def show_me
      render(:partial => "widgets/#{widget.class}/show", :locals => {:widget => widget })
    end
  end

  def show_widgets(template)
    @template.widgets.map do |widget|
        WidgetPresenter.new(widget).show_me
    end.join
  end
end

That way you can use OO techniques to share presentation logic between widget presenters, and you can keep Rails happy (and future developers) by isolating your information from it's presentation.