Steve Folly
Steve Folly

Reputation: 8607

Rails - should link text be set in controller or view?

I'm battling with how much logic should be in a controller and how much in a view?

For example, I have a 'toggle' link that turns a filter on and off. The result is a link with some text depending on the state of the toggle, and whether a query param added or not.

i.e. in one state

<%= link_to 'With filter', polymorphic_path(Thing, { filtered: 1 }) %>

and in the other state

<%= link_to 'Without filter', polymorphic_path(Thing, {}) %>

I'm torn between either assigning the link label and query params in the controller, so then I'll have no logic in the view, and both variables are set in the controller...

<%= link_to @filter_link_text, polymorphic_path(Thing, @filter_link_params) %>

but it seems like I'm putting too much view logic into the controller

or the controller sets up a simple flag and leaves the rest to the view...

<% if @offer_filter %>
  <%= link_to 'With filter', polymorphic_path(Thing, { filtered: 1 }) %>
<% else %>
  <%= link_to 'Without filter', polymorphic_path(Thing, {}) %>
<% end %>

Could translations help?

Thanks.

Upvotes: 1

Views: 65

Answers (1)

Schwern
Schwern

Reputation: 164819

You have a number of options, it depends on the circumstances.

You do not want to put view text into the controller. No @filter_link_text. The controller connects models to views. The view handles how things are displayed.

Simplest is what you already suggested, the controller sets a flag which the view uses.

<% if @offer_filter %>
  <%= link_to 'With filter', polymorphic_path(@thing, { filtered: 1 }) %>
<% else %>
  <%= link_to 'Without filter', polymorphic_path(@thing, {}) %>
<% end %>

Then you can move this into a partial. Move the code into app/views/shared/_filtered_thing.html.erb and render it in your view. This simplifies your view and allows sharing of view code.

<%= render 'shared/filtered_thing' %>

A middle ground is to use a decorator. A decorator is a thin wrapper around a model which lets you add and change its behavior specific to a certain use. It avoids fattening up the model with a bunch of view code.

draper implements decorators and ties in well with Rails. It uses a very similar problem as its example.

class ThingDecorator < Draper::Decorator
  delegate_all

  def filter_toggle_link(filtered)
    if filtered
      # h allows access to the Rails helper methods
      h.link_to 'With filter', h.polymorphic_path(self, { filtered: 1 })
    else
      h.link_to 'Without filter', h.polymorphic_path(self, {})
    end
  end
end

Now your view calls that method on the decorated object.

<%= @thing.filter_toggle_link(@offer_filter) %>

You can also add flags to your decorator. For example, if you needed to set filtered per object.

class ThingDecorator < Draper::Decorator
  delegate_all

  attr_accessor :filtered

  def filter_toggle_link
    if filtered?
      # h allows access to the Rails helper methods
      h.link_to 'With filter', h.polymorphic_path(self, { filtered: 1 })
    else
      h.link_to 'Without filter', h.polymorphic_path(self, {})
    end
  end

  def filtered?
    filtered
  end
end

# In the controller
@thing.filtered = true

# In the view
<%= @thing.filter_toggle_link %>

A big advantage of a decorator over a view partial is you can unit test a decorator.

Partials and decorators open up more ways to keep your view logic well factored and keeping it from bloating your controllers and models.

Upvotes: 2

Related Questions