Skip to content

Instantly share code, notes, and snippets.

@jcasimir
Created March 22, 2012 17:41
Show Gist options
  • Save jcasimir/2160696 to your computer and use it in GitHub Desktop.
Save jcasimir/2160696 to your computer and use it in GitHub Desktop.
class ApplicationController < ActionController::Base
extend AttributeViewable
end
module AttributeViewer
def attr_viewable(*names)
names.each do |name|
attr_accessor name
helper_method name
private "#{name}=".to_sym
end
end
end
<table>
<% messages.each do |message| %>
<tr>
<td><%= message.subject %></td>
<td><%= message.body %></td>
<td><%= link_to 'Show', message %></td>
<td><%= link_to 'Edit', edit_message_path(message) %></td>
<td><%= link_to 'Destroy', message, confirm: 'Are you sure?', method: :delete %></td>
</tr>
<% end %>
</table>
class MessagesController < ApplicationController
attr_viewable :message, :messages
def index
self.messages = Message.all
end
end
@scottburton11
Copy link

@tenderlove I like how that's handled in the JS templating world, where the context is usually a javascript object. However, to apply that to this example, this.messages would be the key pointing to a collection. How else would you handle accessing a messages collection? context.each {..}?

@tenderlove
Copy link

@scottburton11 I would add a messages method to the context object. So in your view, you'd say ctx.messages.each { ... }. The context object would be constructed in each action, like this:

class MessagesController < ApplicationController
  attr_accessor :context
  helper_method :context

  IndexContainer = Struct.new(:messages)
  ShowContainer  = Struct.new(:message)

  def index
    self.context = IndexContainer.new(Message.all)
  end

  def show
    self.context = ShowContainer.new(Message.find(params[:id]))
  end
end

Then if you accidentally call context.message in the view of your index, you'll get an exception. Or if you call context.messages from your show view, you get an exception.

@scottburton11
Copy link

Ah, I see now.

@myobie
Copy link

myobie commented Mar 22, 2012

How hard would it be to mixin the context methods so messages will work without the context.? I cannot currently think of an easy way to achieve that.

@tenderlove
Copy link

@myobie you could mix in a module on each request. So something like:

class MessagesController < ApplicationController
  def index
    extend Module.new {
      attr_accessor :messages
    }

    self.messages = IndexContainer.new(Message.all)
  end
end

But I strongly advise against doing that. It breaks method caches which will slow method lookup on every request, and couples your code with the fact that the controller is a new instance on every request.

Usage of a context (or container, or presenter, or whatever you want to call it) object allows you to:

  • test behavior of the container outside of your controller (you don't need to write a functional test to test the Container)
  • decouple the view from the controller (you could test the view without instantiating a controller)
  • get NoMethodErrors raised exactly at the point where the error occurred (rather than waiting for the nil to be used)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment