#287 Presenters from Scratch pro
Oct 03, 2011 | 14 minutes | Views, Refactoring
Clean up complex view logic with the help of presenters, and doing this from scratch gives you a lot of flexibility. Here I show not only how to create presenters, but how to test them using Test Unit and RSpec.
- Download:
- source code
- mp4
- m4v
- webm
- ogv
any reason you're using a base class instead of a module, for the base presenter? i'm still pretty early in my rails career (~1yr), but I thought "the rails way" was to use modules instead of inheritance.
If I am overriding
initialize
then I will use a superclass. Otherwise I would have made this a module.BTW, including modules is inheritance in Ruby. Take a look at http://robots.thoughtbot.com/post/14825364877/evaluating-alternative-decorator-implementations-in which covers alternative approaches.
Guess you need to change the copyright notice at http://railscasts.com/about .
The purpose of the 'pro' accounts would be defeated by the present Copyright statement :)
Thank you for spotting this, fixed!
Ryan,
This is fantastic, glad to see you'll be making some more money for all your hard work. I'm very happy to go pro. Quick question though, I was very excited to see how you handled your payment, subscriptions, etc. by looking at this app on github. But for some reason, it doesn't look like it's updated there. I see no routes, controllers, models, engines, or anything that builds the subscription/payment parts. How are you handling this/can we view this code?
I will be releasing these changes as open source, keep an eye out for it.
can you explain what is Presenters ?
A presenter is a class which knows about both a model and the view. It can help clean up complex view logic. Watch the episode for more details. :)
Draper describes itself as a Decorator. Can anyone explain the difference between a Presenter object and a Decorator object??
Presenter design pattern aka presenter refactoring is a way of simplifying controller logic by extracting it out into its own class which has one or more methods. Thus, making it easier to test controller logic by usually transforming each assigned instance variable in a controller action into its own method within the presenter class. Anytime you see a controller action which has many instances variables, it needs to be refactored to use a presenter. Decorator design pattern is a way of adding additional behaviors or responsibilities to an object dynamically without subclassing. The decorator pattern is a very good example of the Open-Close-Principle (OCP) which states that an class is closed to modification but open to extension. In short, the decorator pattern is a composing operation whereas the presenter pattern is a decomposing operation.
Also - America in South America means South America
There is some discussion about this on this Draper issue: https://github.com/jcasimir/draper/issues/11
Awesome screencast. I love the from scratch episodes and I also enjoyed the inclusion of Test::Unit.
+1 kudos for also showing Test::Unit and not only RSpec!
I don't like coupling the name of the model to the name of the presenter. What if you have multiple ways of displaying users, or if you more than one user-like objects?
A simple solution might be something like:
The "with" option would be fine too, but the "present" helper in this screencast already takes an optional second argument for this specific purpose.
Please correct me if I'm mistaken, but shouldn't your present definition in the ApplicationController look like this?
The object comes before the template in the initialize method of the BasePresenter.
Thanks for catching this, I'll update the show notes to mention this.
ApplicationController#present
is not used in profile-after. It could be safely commented out.Am I right?
Awesome information. Glad I subscribed =]
My only question might be a little out of scope for this episode, but how would one go about adding a blank presenter file for inclusion when scaffold is run?
I would probably make my own generator for this. See episode 218 for details. This generator can wrap the scaffold generator if you want.
Hi, question. I'm using rspec, and added the 'include ActionView::TestCase::Behavior' as you recommended. However, I'm getting a "undefined method 'setup'" error from action_controller/test_case.rb:12. I am using Rails 3.1.
Any thoughts on what I'm doing wrong?
The
setup
method is only for Test Unit. If you're using RSpec you can dobefore(:each)
instead.Hi, I think Lee didn't use 'setup' method in his code, the same thing is happening to me and the output indicates the error occurred on actionpack-3.1.0/lib/action_controller/test_case.rb:12; Full trace is here https://gist.github.com/1453733. Any ideas? thanks!
I found the problem was
rspec-rails
(2.6.1) wouldn't includeRSpec::Rails::RailsExampleGroup
in a custom example group for you. So puttingconfig.include RSpec::Rails::RailsExampleGroup, example_group: {file_path: %r{spec/presenters}}
beforeinclude ActionView::TestCase::Behavior
will work. I must be using a different version of rspec-rails.I added spec/support/presenters.rb file and I require it from spec_helper.rb:
the later instruction allows me simple check if presenter renders a template inside test
presenter.method.should render_template('blah/_blah')
I've been doing:
I like your way better, although I wish this was possible:
Same here. The error happens when i include ActionView::TestCase::Behavior. I'm using rails 3.1.1. The setup action is called inside the Behavior module itself. So, how do i get access to views in RSpec?
Did you get this to work in the end? I'm having the exact same problem...
Suggestion: where you've got Types, "Free Episodes" and "Pro Episodes" in the right hand navigation, how about a "Both Types" option?
If you click on the big "Railscast" logo on the top left hand corner...you'll get "both types".
I agree having a link in the right hand nav would be useful, though!
Both types are the default search option. If you select a type you can cancel that by clicking the "x" in the Filter section about the episodes list.
Ryan,
I am so pleased that you have decided to offer more screencasts. Thanks for all your great work, you have passed on an incredible amount of knowledge over the years through these screencasts.
Is it me or is there any issues with the audio?
The requested URL /assets/subscriptions/qgznl20zqplReMjco1UASA/videos/287-presenters-from-scratch.mp4 was not found on this server
Am I the only one who can't access video?
I'm having the same issue
Sometimes the pro episode linking takes a few minutes after subscribing. If it doesn't start working let me know.
Great tutorial, as always! Quick question however. How can we use this technique for local objects inside partials?
We have a partial with a local variable called user (not @user). when trying to apply the same logic to it, i get back the following error 500:
I would have to see the stack trace to find out where this
to_presenter
method is coming from. Perhaps there is anotherpresent
method already defined that has different functionality.you were right. found and fixed. thx!
By the way, there's a small typo in the user_presenter.rb file:
should be
I think it's doesn't matter - in 1.9.2 you can write both {:a => 1} and {a: 1}
aha. we're still using 1.8.7 :)
Hash rocket is dead! ;)
btw: :this #looks ugly for me
+1 ;)
HashRocket 4-eva.
This is the best of both worlds. Free RailsCasts to get you going and Pro to push you further. I am waiting for your previous screen-casts to be brought up to date. There is a wealth of knowledge there already.
Keep them coming and congratulations on expanding on what you are already very good at. Can we expect you to respond more actively on the "Pro" screen-cast questions though? Since these are more in-depth, naturally there are more questions.
Bharat
I should have the first revised episode up later this week. I also hope to be more active in the comments as RailsCasts becomes my full time job. Almost there.
That is great news Ryan. Keep up the good work.
Best regards,
Bharat
So this screencast is just creating draper library...? :)
It has important differences such as making presenters in the view instead of the controller, and passing the template object through. Overall I think the implementation is simpler and more flexible, but it's up to you to decide which you prefer.
+ testing it ;)
ActionView::TestCase::Behavior does not exists in Rails 2.3.x. Does anyone know how to test the presenter within a Rails 2.3.14 app and RSpec 1.3.2?
Same problem here. Has anyone found a workaround?
Still no chance?
No one has the same problem?
Ryan, I am a big fan of your Railscasts and I congratulate on Railscasts Pro. However, for a "pro" edition, I found the cast very short and basic. In the cast you assumed that the view only gets one value set (presents). What if the view needs more information, e.g. the controller did that:
def index
@customers = Customers.all
@prices = Prices.all
...
end
How would you do that?
And the second thing is that you just scratched the testing issue. I believe that Presenters shine most in testing.
Maybe I am setting the expectations too high, but I expected a much more detailed (paid) cast.
My 2 cents, good luck with Railscast Pro.
Even though the topics will be more advanced, they will not necessarily be more detailed. I am unable to produce a half hour episode each week unfortunately, but I will consider revisiting specific areas of presenters in future pro episodes to address the scenarios you mentioned. Thanks for the feedback.
I think the apx. length of your casts is perfect!
I didn't miss a single episode since #1 but I wouldn't have time to watch a 1h cast each Monday.
Keep going and congrats for Pro!
Hello Helmut,
You may want to checkout http://www.peepcode.com. I subscribe to both (RailsCasts Pro and Peepcode) and find that they nicely compliment each other. Producing full length screencasts once a week is a very difficult thing to do. That is why Peepcode averages one screencasts a month (more or less). It is very nice to have Ryan's screencasts every week :)
Nifty stuff! I agree that the controller shouldn't need to know about what is basically a super-helper for the view. I've been trying to think if there's any way to pull the "present" helper method out so that it's seamless to the view as well that it's using a presenter.
Possibly a super smart layout? Any ideas?
I prefer the presenter be a bit verbose in the view so that it is obvious we are doing something special here beyond calling model attributes. Otherwise one might think calling
user.twitter
would just return the twitter username and not handle the link, etc.That said, you could do something like smart partials where the object passed to the partial is a presenter instead of a simple object. But unless Rails adopts presenters as part of their stack, I prefer they be explicitly declared whenever used.
Hey Ryan,
how about extracting this into its own gem! ;)
Are you going to include specs (and tests) in all the updates to old screencasts? That would be a huge incentive to me to subscribe.
(And thanks for not using only PayPal. Limiting payment to PayPal would be a big *dis*incentive to me.)
I'd +1 that
My favourite ruby book of all time is Metaprogramming Ruby
The thing I loved most about this book is that BDD is part of every example. I always feel that by showing just the code, you're only showing half of the solution to a problem?
Wouldn't it be better that these comments would only be shown to the subscribers?
Don't think so. Those comments are great free advertising for Ryan and show both how people like the screencast and how good his support is.
i would suggest to you make a new video or expand current code sample to show how test/spec the views after apply a presenter on it.
I think it is missing to be full complete episode.
Thanks for you great work!
The show notes point out that this follows the work covered in http://railscasts.com/episodes/275-how-i-test. In that episode Ryan showed us how he handles testing on a normal project. His techniques there would cover what you are talking about.
Ryan, thank you very much for this great episode. I created a gem using your approach on presenters. Check it out: exhibit.
I'm curious why you don't feel the controller should know about Presenters? It seems to me the one thing controllers and presenters have in common, is they both have knowledge of both the model and the view.
I need to look more into the subtle differences between presenters and decorators... maybe understanding that will help me understand why you think the presenters should be instantiated in the view.
Is it very different from https://github.com/jamesgolick/active_presenter ?
Suggested refactor:
It's better to pass an instance as a parameter than a class
why? Any particular reason for that?
and by the way: you frget that presenter initalizer takes 2 arguments
You're totally right / I completely overlooked the two arguments making my solution wrong.
Generally, I don't like the "class goes in, instance comes out" approach but I think it's fine in this case
Hey Ryan!
It was great episode. Just one think I really didn't like was defining presents method in base presenter. It could be something optional to use but in most cases we have 1-1 relation which can be expressed in BasePresenter in following way:
what do you think?
How would using HAML change the presenter implementation?
Why should it? HAML is just templating language so you would just use same methods in HAML syntax from the view.
What if I want to use HAML in the presenter? I added lines to BasePresenter like so:
and used it like this in show.haml:
- presenter.show_field('Efficacy', presenter.efficacy)
and I can show that show_field is being called, but no HTML is produced. Maybe it is in the wrong context.
Any suggestions on how to write a presenter for a collection of object, like for an index view?
Wondering the same thing. Anybody have any insights they can share?
see my reply below for some thoughts
Maybe a CollectionPresenter that delegates the finer details to the instance presenter. You could even make it a base class, and subclass it with UserCollectionPresenter, etc. depending on how diverse your views are.
It is much cleaner, but we can take it a little further. If we change the
present
method to:We can slim our view down to:
We'll still have access to the template via
h
, just need to make it a public method.I think this pollutes the namespace in the view too much. Is
avatar
a helper method? instance var? presenter method of @user? It's not clear any more.I like it. I'm not sure if I agree with Jeremy Seitz, as I did something similar to Austin Schneider.
In my view code, note that you can't tell the difference between
post
(asPostPresenter
) andpost
(my active record model). I think the ambiguity is fine, or good actually; it's easy to read. To be able to do that, I overrode method_missing to point to the model. That way I don't have to usedelegate
. But this makes it a little easier to deal with when shifting code around via partials and not having to rename passed locals.Heh. I'm glad you still have the source on github for pro. That
hande_none
was driving me bonkers...After watching the video, I'm slightly confused. It seems that presenters more or less add another layer of complexity to the view concept. Is there some convention that if your controller and view are below a certain complexity level that a presenter isn't a best choice? When would I want to use a presenter vs leaving the show action with a little more data in it?
Also, are presenters main-stream or becoming main-stream? I've heard little about them until recently. Are they something that will most likely stick and become a standard convention?
Looks like something worth learning more about, but not sure I'm fully convinced they are very useful, but that's probably just my lack of experience with them speaking.
So I am implementing this and I am slightly confused as the best way to access the presenter from the layout.
The reason I would like to do this is for menu building.
Has anyone else dealt with this?
Would love to see a follow up RailsCast or a Github example app that has built a Presenter for a Report or Dashboard. That is to say, a Presenter that incorporates data from several models into a single view.
Shouldn't this line:
klass ||= "{object.class}Presenter".constantize
be
klass ||= "#{object.class}Presenter".constantize
?
Ta for that!
Hi, just wanted thank you for a great episode, and point out that the ASCII cast states that I should use view_content rather than view_context. Took me a while to figure that out =)
Thanks.
Hey Ryan,
Is there any reason not to have the method missing check the @object first? As the presenters are usual about a particular model, and delegating a dozen items can be painful.
Something like:
Thanks for the great episode!
is there a way to delegate all instance variables of @template? for example, @controller, @action and others are not set within the scope of the presenter
I've got a solution but would like to have something better
When writing a presenter from scratch, what is the best way to access the rails helpers? Inside the class I'd have a @context variable which would be used to call the view helpers,
e.g. @context.link_to "help"...
That's the why Ryan suggests the "h" method. It's an easier way to access the @template attribute.
That way you could do something as:
I recently found that when using the
method_missing
trick in my base presenter class, it raises an exception when I try to use thecapture
method directly.Dumb example:
This raises
ArgumentError: wrong number of arguments (0 for 1)
From what I can tell, the reason is because it conflicts with Kernel::capture, maybe because
Kernel
always have precedence, or something.In any case, I added a hard
capture
method to my base presenter class, to work around the issue:Is there an elegant way to deal with optionally-passed locals?
For example say I call
How can I access that
size
value in the presenter?One way is to pass the value into the presenter. For example, from the partial, I might write
<%= post_presenter.some_method(size) %>
But since
size
is an optional value passed to the partial, it might not exist, and thus would raise an exception when trying to callsome_method
. You could check if the local variable exists like this:However, this is ugly, and defeats the point of the presenter! This is the code that we're trying to avoid in the first place.
Thoughts?
You can just set reasonable defaults in your presenter class's method definitions
Can I use presenters to clean up a view that does not have a corresponding model (e.g. my Home page, static_pages#home)? If so, how would I set up the presenter class? Thanks!
thank