Call a controller method automatically when render

2019-08-28 01:30发布

问题:

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?

回答1:

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


回答2:

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