Using views and states
In the introducing chapter we got our twittering working. However, the #display view does two jobs at once.
It’s listing the tweets and managing the form for new posts.
Why not have a look at it.
In a component-oriented architecture things should be separated for good encapsulation so let’s learn two simple steps to do just that.
Extracting partials
Often it is good practice to move view fragment to partials. A partial is reusable and helps limiting the scope of variables used in the view.
In Cells (and Apotomo) we don’t distinguish between view and partial – there’s no need for making thing more complex. Everything is a view… or a partial. We simply call it view.
Let’s extract the list to a separate view. Or partial. Whatever.
Notice how we refer to tweets as a local, and not as instance variable anymore (line 2). Overally, it is cleaner to work with locals. Instance variables are dangerous.
The new view must be rendered in the display view.
The render :view call looks and feels similar to partial rendering (line 2).
Apotomo’s #render is identical to the one found in controllers. It accepts two additional options, :view and :state. Check the API to learn more.
Rendering states vs. helpers
Partials are nice and a quick way to DRY up things. Rendering states gives you even more power. It allows rendering and executing an associated piece of code. Sounds like a helper, doesn’t it?
Rendering states in views is really comparable to using plain helpers. But – ha! – states are object-oriented, can be inherited and overwriten. How does that look like?
I replace the render :view with a render :state. The cool thing is, I can pass any options to the state method (line 2).
1class TwitterWidget < Apotomo::Widget 2 responds_to_event :submit, :with => :process_tweet 3 4 def display 5 @tweets = Tweet.find(:all) 6 render 7 end 8 9 def process_tweet(evt) 10 Tweet.new(:text => evt[:text]).save 11 12 @tweets = Tweet.find(:all) # this is wet! 13 replace :view => :display 14 end 15 16 def list(tweets) 17 render :locals => {:tweets => tweets} 18 end 19end
One thing changed: We got a new state #list (line 16-18)! It requires one argument tweets. So, when we render this state, we have to pass in a tweets list. It’s important to see how states urge you to define interfaces – and this is a good thing.
Summing up the rendering cycle
So, what’s going on here, step-by-step.
- The
#displaystate called in#render_widgetcollects all tweets and renders its view. - In the
displayview, we callrender :stateand thus invoke a second rendering cycle. - The called
#liststate receives the tweets and renders its own view,list.haml(line 17). - The
list.hamlview uses locals only.
Extracting the form to a separate state
Why not extract the form to another state and see how it looks? We will soon learn that this absolutely makes sense.
First, I define a new state method that just renders (line 19-21). We then need an appropriate view.
Nothing new, just the form in a separate partial file.
The last step is to render the form state in our main view.
Much simpler now, what was the actual form is now a delegation to the #form state (line 4).
DRYing up the widget class
After all this work, we can also clean-up our TwitterWidget and remove redundant queries to the Tweet model.
1class TwitterWidget < Apotomo::Widget 2 responds_to_event :submit, :with => :process_tweet 3 4 def display 5 @tweets = Tweet.find(:all) 6 render 7 end 8 9 def process_tweet(evt) 10 Tweet.new(:text => evt[:text]).save 11 12 replace :state => :display 13 end 14 15 def list(tweets) 16 render :locals => {:tweets => tweets} 17 end 18 19 def form 20 render 21 end 22 23end
Look what happens now in #process_state – after a tweet submission is consumed we simply re-run the #display state and let it manage the rendering of the widget (line 12).
That looks quite ok now. A quick discussion when to use states and then we’re done for today!
Views vs. States
Don’t confuse the :view and :state option. The first simply renders the view whereas the latter invokes a state method, which itself renders. Both are helpful and it’s a matter of design where to use which.
Another interesting concept is extracting logic and views to separate states, as we did with the form and the tweet list. As a rule of thumb, it is nothing wrong to move things to discrete states. Nevertheless, you could also create a separate widget class. We will learn more about that soon.
In the next lesson we discover AJAX validations.
