"Add comment", R2.0 templates, and respond_to
Posted 3 months ago by Raul Parolari
Since R2.0 was introduced, several emails pointed out that the ‘add_comment’ (to a blog post) link did not work. They were correct (there was some confusion in the email threads, as most people still continued to use R1.2.3, where the form was working but with minor glitches; so, completely different problems got mixed up).
The reason for the failure with R2.0 was the following; let us glance at the code in the controller was:
def new
@comment = Comment.new
respond_to do |format|
format.html # new.rhtml
format.js # new.rjs (notice this comment!)
end
end
The Respective templates for the two formats were new.rhtml and new.rjs; but the new.rjs template was never executed, and its output discarded by the Ajax client. After an ordeal of tests and (somehow foolish) studies of how the responds_to method works internally in Rails, we went back to read DHH’s note for 2.0, and this section finally caught our attention:
the format of the templates has been separated from its rendering engine. So the new format for templates is action.format.renderer
Could that be the reason for our misery? and if so, what are the ‘format’ and the ‘renderer’ for our Rjs template? a caritative note in a thread clarified it: it had to be .js.rjs (in poetry: the Rjs engine renders JavaScript format..).
So, our derelict new.rjs file was to be born again as new.js.rjs (and then.. clicking for the N+1 time the Add a comment link.. the form suddenly appeared in front of our incredulous eyes!).
All right: given the time spent (the value of N was indeed high) on this affair, let’s at least learn something more.
Extending the example given by DHH with the rjs case, we could
have this correspondence between an edit action and its templates:
def edit
respond_to do |format|
format.html # --> edit.html.erb
format.csv # --> edit.csv.erb
format.js # --> edit.js.rjs
format.iphone # --> edit.iphone.haml
end
end
And of course the templates can be changed (with another ‘renderer’) with no impact on controller logic, which shows the beauty of the design. [Note: new.rhtml still works.. which may be the reason that our brain unconsciously assumed that new.js would still work too!].
Now, a comment on the code itself (read the following only if you are interested in Ruby, beyond Rails). Since I saw a RESTful controller, I have always asked myself this: how in the world the respond_to method works?
Look at the code above: those ‘format.xyz’ calls are method calls, that are going to be executed one after the other (rails magic or not, this is how Ruby works)! but on the other hand we know that the result is like they were in an implicit case statement! so which is it? (if you ventured a timid “perhaps both statements are true?”, you are ahead!).
A detailed explanation of this intriguing mechanism (that we read while trying to debug the problem) would take a few pages; I describe instead a summary, taking as example our add_comment action. First, two preliminary observations:
-
a Rails request can express in the Url a :format parameter (eg: ../posts/3.xml), or specify in the HTTP_ACCEPT header a particular Mime Type requested.
In our case, the “Add comment” link requests an Url like “/blogs/2/posts/3/comments/new” which does not have a :format extension; however Rails’ friend Prototype inserts (in the client) a helpful “text/javascript” in the HTTP_ACCEPT header. - inside the Rails code that we will examine, the “mime type” is not just a string or symbol, but the MIME::Type object which contains all the synonyms (eg, ‘application/javascript’) along with its short string and version. In what follows, I will allude to the ‘mime type’, for simplicity (but you know what it is).
All right: fortified by the above, let’s attack the respond_to method, following our ‘/blogs/2/posts/3/comments/new’ action (invoked via Ajax from the client), whose code we refresh:
def new
@comment = Comment.new
respond_to do |format|
format.html # -> new.html.erb
format.js # -> new.js.rjs
end
end
The steps are the following:
-
The respond_to method starts by retrieving an Array of the Mime types for the request. The first value will be ‘text/javascript’ (from the HTTP_ACCEPT
header), but Rails includes several other choices (at lower priority).
The method also sets up (indirectly) an (empty) hash of @responses. It also receives the block contained in the ‘new’ action (in Ruby, this trick is done via a & prefix to the last parameter listed, like &block). -
Then it starts executing the block: this means that the first ‘format’ call in the block, which is format.html, is executed; this proves what we stated before: those format.xxx statements are indeed executed serially!
But.. how can it work if all of them are executed? well, it all depends on what ‘execution’ means, as we will see next. -
Actually the method ‘format.html’ (or any format.xxx) does not exist; so Ruby invokes the famous method_missing which retrieves the suffix (:html in this case) and creates a Proc object for ‘render :action => @controller.action_name’, ie. ‘render new’.
It also sets (inside this object) 2 variables, ‘template_format’ and ‘content_type’ to ‘html’ to render (when called) the particular template required.
The Proc object is assigned to the @responses hash for the :html mime type (key to hash). -
The same goes for the next call, ‘format.js’; another Proc object is added to the hash @responses for the mime type for ‘js’.
Similarly, it attaches the 2 variables seen above to remember that it needs to render :js.
[Digression: if you wonder how several callable objects can be built with variables that have the same name but different values (and coexist retaining those values until later when called!), good for you; welcome to closures]. -
Now that the list of ‘format.xxx’ calls has ran (storing those ‘render new’ methods in hash @responses) the respond_to method needs to pick one of them; which one will it choose? (dramatic pause..)
Do you remember that in step 1 we built the array @mime_type_priority, and that the first entry listed was the mime type for ‘text/javascript’? well, the method scans the array extracting the mime types looking for a match in the @responses hash; in our case, it finds and calls right away the template ‘new’ for ‘js’, and extract from hash @responses the method with that mime type: that is our new.js.rjs!
Indeed those format.xxx calls listed in the controller action were all executed, serially: but execution meant creating dynamic callable objects (each of which calls a specific template), and then picking up one of them according to a priority list! Ah, Ruby!
RailsSpace will_paginate
Posted 3 months ago by Raul Parolari
The old pagination used in Rails 1.2.3 has been removed from the code in 2.0, but it is still supported via the plugin classic_pagination. However, there are good reasons to kiss her goodbye:
- first, a practical one: the classic_pagination plugin exists just for temporary compatibility; there is no guarantee that it will be maintained.
- second, the plugin forces the programmer to jump through some hoops: see listing 11.10 (or in the final version the method ‘paginate’ in application.rb), which, depending on the argument class, calls the plugin (‘super’) or does ‘manual pagination’; the writer’s comment (which aptly captures the absurdity of the situation) is: “Paginate by hand”.
- third, it has issues of performances when the number of pages is large.
- finally, a design opinion: this code is a bit uncharacteristic for Rails; the class chosen for Pagination is the ActionController, but the logic seems instead more related to ActiveRecord and ActionView (as pagination is a ‘presentation’ of ‘Model’ instances). The only advantage that I see in having this in the Controller is that the params method is available (while in a Model, it is not); so, a parameter such as ‘params[:page]’ does not need to be passed (in a scale from 0 to 10, weight=1 !).
There are several plugins that replace pagination; I examine here one of the most praised, will_paginate.
Let us start from the good news; give a look at the listing we just mentioned, 11.10 (method ‘paginate’ in application.rb): well, it just disappears! there is simply no need for it. It is remarkable that at the introduction of that method (start of chapter 11.1.4), the book comments:
# what we would really like to do is this:
def search
if params[:q]
..
@pages, @users = paginate(@users)
end
end
Well, with will_paginate that wish becomes reality! if the collection on which we want to paginate has already been computed, eg in @users, we can just write:
@users = @users.paginate(:page => params[:page], :per_page => 10)
And to paginate on a model we could write (example):
@specs = Spec.paginate(:page => curr_page, :per_page => 10)
But wait a moment: the old pagination returned two objects (see above, @pages, @users), one to manage the pagination, the other with the results for the current page; now, we have only one object, @specs in the example!
The answer is: indeed we have just one object, and it is a bit smarter; it knows both the result of the search and the one for the pagination! (removing the confusion around those 2 objects was such a gift!).
All right; let us now proceed methodically and show how the code in chapters 10&11 can be changed. First, delete the old plugin and install will_paginate (see http://rock.errtheblog.com/will_paginate):
Let’s start recoding a few methods; the new code is:
def index
@title = "Community"
@letters = ("A".."Z").to_a
if params[:id]
@initial = params[:id]
curr_page = params[:page]
specs = Spec.search_on_last_name(@initial)
users = specs.collect { |spec| spec.user }
@users = users.paginate(:page => curr_page, :per_page => 10)
end
end
Here the pagination is done in the last step, on users (not any longer on specs). So, the search method invoked is just a model search method:
# search specs based on last_name initial
def self.search_on_last_name(name_initial)
find(:all, :conditions => ["last_name like ?", name_initial + '%'],
:order => "last_name, first_name")
end
Now, the partials which present the paginated users: first, the _user_table.rhtml (or _user_table.html.erb if you use the rails 2.0 naming convention; both are supported in 2.0); I show what changes from the old to the new version:
<% if paginated? %>
<tr>
<td colspan="4" align="right">
Pages: <%= pagination_links(@pages, :params => params) %>
</td>
</tr>
<% end %>
<tr>
<td colspan="4" align="right">
<%= will_paginate @users %>
</td>
</tr>
Finally, we have to recode the _result_summary.rhtml file, that displays the total number of results and the ones displayed in the page. Here I did not find really friendly methods (I mean methods that avoid the business of ‘offset +- 1’); so we will pretend that the friendly methods exist, and then we will add them to the plugin:
<% if @users %>
<p>
Found <%= pluralize(@users.total_entries, "match") %>.
<% if @users.paginated? %>
Displaying users <%= @users.first_item %>–<%= @users.last_item %>.
<% end %>
</p>
<% end %>
Now we add to the plugin the new methods added, by reopening.. ehm, which is the will_paginate class? let’s ask Ruby! bring up the rails console:
ruby script/console
>> Spec.paginate(:all, :page => 1).class
>> WillPaginate::Collection
All right, so we need to reopen the WillPaginate::Collection class; we have learnt how to reopen a class in a recent post (on Session); thus, under the lib directory we write a file (that must be required) that will do:
class WillPaginate::Collection
def first_item; offset + 1; end
def last_item ; first_item + length - 1; end
def paginated?; total_entries > size; end
end
[Note: in case anyone finds similar methods in will_paginate, let me know and I will edit this post. The point here is just to centralize the treacherous +-1 and >,>= operations].
All this work pays handsomely when we add the search for users across spec/faq/user (chapter 11). As mentioned at the beginning, the long and twisted method ‘paginate’ (listing 11.10) in application.rb disappears! we just need to change one call from the community controller in the action ‘search’:
@pages, @users = paginate(@users)
@users = @users.paginate(:page => params[:page], :per_page => 10)
This single call builds the paginated @users, that can be fed to the View search.html.erb (which calls the same partials that we have seen above). With one shot (ie, one line), we have solved the problem; nice!
And, to finish chapter 11, we port the pagination for the A/S/L search: the old code is:
specs = Spec.find_by_asl(params)
@pages, @users = paginate(specs.collect { |spec| spec.user })
views/controllers/community_controller.rb browse method
specs = Spec.find_by_asl(params)
users = specs.collect { |spec| spec.user }
@users = users.paginate(:page => params[:page], :per_page => 10)
Notice that we could concatenate statements, for example:
specs = Spec.find_by_asl(params)
@users = specs.collect { |spec| spec.user }.paginate(:page => params[:page], :per_page => 10)
This is very much in vogue in Ruby/Rails as it is a compact notation, which eliminates intermediate local variables (and if you really want to impress your friends, you can even concatenate the 3 statements!).
Personally, I still like lines of code of about 80 characters (instead of 100, or 140 with the 3 lines concatenated; which is not unusual in Rails internal code!). In any case, the important is to understand all these types of notations.
Last observations:
- All the code presented above was tested ok (using the web browser, and running the test suite).
- RailsSpace uses pagination again later, in chapter 15, for posts in blogs; the modification should be easy, if you have read this post (so, we leave it as an exercise to the motivated reader :-).
- Finally: for more on will_paginate, see the brilliant screencast from Ryan Bates at http://media.railscasts.com/videos/051_will_paginate.mov
Overwriting default thumbnails
Posted 3 months ago by Michael Hartl
Just a quick note: once you get to Chapter 12 on Avatars, you should add the following validation to the User model:
class User < ActiveRecord::Base
.
.
.
def validate
# Protect against overwriting default thumbnail...
if %w(default_thumbnail default).include?(screen_name)
errors.add(:screen_name, "cannot be that, nice try though")
end
end
.
.
.
end
Without this validation, users called ‘default’ and ‘default_thumbnail’ can both overwrite the default thumbnail in the avatar image directory. D’oh! Thanks to Rohit Koul for pointing it out.
Reopening classes to improve the Session interface
Posted 3 months ago by Raul Parolari
Chapter 7 of RailsSpace refactors the management of the session login-logout feature, presenting 3 methods:
# Method calls: # Object:
user.login!(session) # User instance
User.logout!(session, cookies) # User class
logged_in? # controller
This significantly improved the initial version which disseminated in the code accesses to the session internal data structures (although there are still direct accesses to read the user_id, that we will tackle later).
However, there is something a bit odd: the object on which the methods act changes each time: it is an User instance to log in the user, then the User class to log him out, and finally it is the controller (‘self’ is the controller) to verify the log in. Could we have an uniform way to invoke those 3 actions?
Looking inside those methods, it is inmediate to realize that the real object on which the methods act is not user, but session, which belongs to the class CGI::Session (in my machine, file /usr…ruby/1.8/cgi/session.rb). If we only could add methods in there!
But wait: this is Ruby; we can always reopen a class and add functionality! very much like the book did when it reopened the class String to add the beautiful method or_else? (ch 9). Comforted by that thought, we created a file with our session extension in the lib directory (that we will ‘require’ at the top of the application.rb file):
class CGI::Session
def login!(user)
self[:user_id] = user.id
end
def logout!
self[:user_id] = nil
end
def logged_in?
not self[:user_id].nil?
end
end # CGI::Session
The content of the methods is identical as before (aside from a detail in the logout action regarding the deletion of the long term cookie, that we will perform now in the controller), and the calls are now:
# In class User Controller
# in login and register actions
session.login!(@user) # old: @user.login!(session)
# in logout action
session.logout! # old: User.logout!(session, ..)
cookies.delete(:authorization_token)
# In class ApplicationController
unless session.logged_in? # old: unless logged_in?
end
[A curious detail: observe how the place of the terms session,login,user exactly reverse in the login! call; of course, as the ‘actor’ is now the session].
The same modifications have to be done in the tests. The only place where I left ‘logged_in?’ is in the layout, as it is friendlier in that context (in the application helper, I made that method call session.logged_in?). Finally, I deleted the methods dealing with session from the User model.
We can at this point observe that it would be nice to also centralize all those accesses to the session hash (like ‘User.find(session[:user_id]’) to retrieve the user_id without exposing the session hash. We can of course achieve that adding a method ‘user_id’ to our Session extension.
But, at this point, observing that the ‘login!’ and ‘logout!’ methods are nothing else than getters/setters on the session, we become tempted to simplify things even further:
class Session_Invalid_UserId < Exception; end
class CGI::Session
# returns current user id in session
def user_id
self[:user_id]
end
# sets session to given user (or nil)
def user_id=(value)
self[:user_id] =
case value
when Fixnum: value
when User: value.id
when nil: value
else
raise Session_Invalid_UserId, "invalid user_id #{value}"
end
end
# returns true if there is a session user
def user_id?
self[:user_id] != nil
end
end # CGI::Session
The 3 invocations (session.user_id, session.user_id = @user, session.user_id?) can replace not only the calls login!, logout!, logged_in? but also the accesses to retrieve the user id. Notice also that by doing this we can now detect an error when setting the user_id to something else than a number, an user instance, or nil (for logout).
After having replaced all the strings in the app and test files (using global replaces), we reopened our browser and everything worked ok! Alas, the happiness was short-lived, as the attempt to run the test suite failed!
We discovered that the tests do not use the CGI::Session but rather a simulation of it, called ActionController::TestSession, that it is just an access to a hash called ‘data’. We could then repair the problem, by simply adding in our session_ext file an extension to that class:
class ActionController::TestSession
def user_id; data[:user_id]; end
def user_id=(value); data[:user_id] = value; end
def user_id?; data[:user_id] != nil; end
end
And the test suite ran.. fine (phew!).
Perhaps all the above work seems an unnecessary trouble (disclosure: we certainly shared that thought, when the tests failed miserably and we had not idea why!). However, in large projects it is often useful at a certain point to organize all functionalities in the most apt way (as, for some reason, entropy, or what we programmers call ‘disorder/noise’, has a subtle way to take off exponentially in software systems).
Using Ruby, this means identifying the Classes and Objects around which to coalesce functionalities.
What this exercise tried to demonstrate is that even when those classes are not under our control (like the unfamiliar CGI::Session and the unexpected TestSession), Ruby offers us the possibility to reopen those classes and extend their capabilities in the best way that fits our design.
Login action: from 2 user structures (@user, user), to one
Posted 3 months ago by Raul Parolari
Hi, everybody,
I am a new blogger for RailsSpace. Two words about me; I am Italian (so forgive the occasional slip-up) and US resident (Bay Area). After years of real-time apps (network mgmt, Snmp), I began a sabbatical into scripting languages; after a furious flirt with Perl, I finally met Ruby (and nothing was ever the same again..). Recently, I began to read RailsSpace, and I was granted the chance to comment some aspects of the code.
I start discussing the Login action, at its inception, in chapter 6:
def login
if request.post? and params(:user)
@user = User.new(params[:user])
user = User.find_by_screen_name_and_password(@user.screen_name,
@user.password)
if user
session[:user_id] = user.id
# flash and redirect
else
@user.password = nil
# flash
end
end
end
The weak point is this juggling between the @user (the user received from the form) and user (the one from the database) structures at every statement. This usage only intensifies in the next chapters as the method becomes progressively more complex. Could we have only 1 structure? Yes, if we collect the user credentials from params:
def login
if request.post? and params(:user)
screen_name = params[:user][:screen_name]
password = params[:user][:password]
@user = User.find_by_screen_name_and_password(screen_name, password)
if @user
session[:user_id] = @user.id
# flash and redirect
else
# bad credentials: return data rcvd, but wout password
@user = User.new(params[:user])
@user.password = nil
# flash
end
end
end
Notice that we now need to create the user in the else branch; but the benefit is that there is only 1 structure across the whole method (which, as said, will become quite complex later). And the method is more efficient, as (in the succesful case) we call only once ActiveRecord to build the user.
Ah, some may say, we do have only 1 structure, but we now access a nested hash!: is this what you call simplification?..
Answer: we can hide the access to the data in a method; it will be generic for any object and any nr of attributes; so the usage will be:
name, pwd = params_for(:user, :screen_name, :password)
# retrieves named params from object
def params_for(object, *names)
obj_hash = params[object.to_sym]
return [ ] unless obj_hash
names.inject([]) { |arr, name| arr << obj_hash[name.to_sym] }
end
This method iterates across the array of names received, extracting the values from the nested hash, and feeding the result array (inject is a cute way to iterate accumulating a result, without creating extra local variables; very idiomatic inside Rails code).
All right; so the login looks like:
def login
if request.post? and params(:user)
name, pwd = params_for(:user, :screen_name, :password)
@user = User.find_by_screen_name_and_password(name, pwd)
# all the rest of the logic is as above
end
Now the resulting method uses only 1 user structure, and the extraction of parameters is friendly enough. As the method complexity escalates in following chapters (‘remember_me’, cookies, etc), the programmer can concentrate on the features (without worrying about which structure to access at each statement).