If someone asked me what was the best thing I had learnt over the last 12 months, I would say: REST.
I know that the RESTful approach requires changing our way of thinking, and it took me a while to "switch". The benefits are huge. From my experience the main advantages of REST are:
One of the examples that is hard/not worth (according to some people) to implement in a RESTful way is "Forgot your password" functionality. Some people feel like it could be just "/users/4/reset_password" and that we could use a custom action for the users controller. I know that it can work, but I see no reason to break the REST rules here (by adding a non-REST action to the controller).
My RESTful solution
The feature should let users provide an email, send them a link to a page, where they can change their password.
We have several steps to follow:
Given these steps, I split the code into the following controllers and actions:
Email Verification
We want to let people create a new Email Verification, so:
I created an EmailVerificationsController with new/create actions.
It is responsible for:
After veryfing the email, we want people to change (update) their Password, so:
PasswordController with edit/update actions
it's responsible for:
That's it.
Techniques
This example shows that a controller doesn't have to reflect a ActiveRecord class. There is no such model as EmailVerification in my code. It could be worth considering it, but in my case I don't think it was needed.
A useful technique for REST-izing your design is to turn verbs into nouns, thus making them easier to expose as resources. In this case, we wanted to verify an email, so I turned it into "creating an EmailVerification resource".
Sometimes changing the words slightly can help. Instead of resetting passwords, I use "update a Password".
As always, I try to move as much logic to the model as I can. Ideally I just call one model method and handle exceptions if there are any. See this article for more details about this approach.
A controller can use other models. In our case the PasswordsController could call User.update_attributes method to change the password.
Bonus
A friend of mine asked recently how I'd implement a feature, that let's an admin making another user a superuser.
My thinking here is that I turn the "making someone a superuser" into "creating a new superuser". This shows me that we now have a resource called Superuser. According to REST a Create action expects the params to be sent using POST. So we can just POST the id of the user to the Create action. In the Create action we just call admin.make_superuser(params[:id]).
Questions
What is your opinion about these solutions?
Would you solve them differently?
Feel free to ask me how I'd RESTfully implement other features.
I know that the RESTful approach requires changing our way of thinking, and it took me a while to "switch". The benefits are huge. From my experience the main advantages of REST are:
- the consistency it brings to the app,
- a standardized way of creating controllers,
- thinking always in terms of resources - what is the 'thing' that the user wants to create/update/destroy?
- a higher-lever abstraction that simplifies communication with other people in the team
- very often there's only one RESTful way of implementing a certain feature
- consistent API, in one project we use Flex for the UI, and being RESTful makes it very easy to work with Rails controllers.
One of the examples that is hard/not worth (according to some people) to implement in a RESTful way is "Forgot your password" functionality. Some people feel like it could be just "/users/4/reset_password" and that we could use a custom action for the users controller. I know that it can work, but I see no reason to break the REST rules here (by adding a non-REST action to the controller).
My RESTful solution
The feature should let users provide an email, send them a link to a page, where they can change their password.
We have several steps to follow:
- Show a page where people can type their email, verify the email
- Generate a reset_password code.
- Send an email with this link.
- Clicking on this link should check if it's a valid reset_password code
- If it is valid, then show a page where a user can type the new password twice.
- Display a message appropriate for the result (error when the passwords don't match etc.)
Given these steps, I split the code into the following controllers and actions:
Email Verification
We want to let people create a new Email Verification, so:
I created an EmailVerificationsController with new/create actions.
It is responsible for:
- finding the user by email,
- generating the reset_password code,
- sending the email,
- handling errors
After veryfing the email, we want people to change (update) their Password, so:
PasswordController with edit/update actions
it's responsible for:
- authenticating the user by the reset_password code,
- letting the user change the password,
- displaying errors.
That's it.
Techniques
This example shows that a controller doesn't have to reflect a ActiveRecord class. There is no such model as EmailVerification in my code. It could be worth considering it, but in my case I don't think it was needed.
A useful technique for REST-izing your design is to turn verbs into nouns, thus making them easier to expose as resources. In this case, we wanted to verify an email, so I turned it into "creating an EmailVerification resource".
Sometimes changing the words slightly can help. Instead of resetting passwords, I use "update a Password".
As always, I try to move as much logic to the model as I can. Ideally I just call one model method and handle exceptions if there are any. See this article for more details about this approach.
A controller can use other models. In our case the PasswordsController could call User.update_attributes method to change the password.
Bonus
A friend of mine asked recently how I'd implement a feature, that let's an admin making another user a superuser.
My thinking here is that I turn the "making someone a superuser" into "creating a new superuser". This shows me that we now have a resource called Superuser. According to REST a Create action expects the params to be sent using POST. So we can just POST the id of the user to the Create action. In the Create action we just call admin.make_superuser(params[:id]).
Questions
What is your opinion about these solutions?
Would you solve them differently?
Feel free to ask me how I'd RESTfully implement other features.
7 comments:
Now only port this solution to restful_authentication :)
Interesting solution. I like it!
I think your password solution is spot-on, but the superuser example seems a bit too specific. Unless there's a Superuser model that attaches to users by composition, I feel like "Create Superuser" implies something different than what you have -- it feels more like making a new superuser out of whole cloth, not changing a privilege on an existing user.
Assuming a simple "edit the User resource" is deemed insufficient, I probably would have handled that one with a Privilege resource. Then you can create, show, modify, or delete privileges as necessary, and lock it down with whatever authorizations are appropriate. Superuser would be just another privilege.
interesting article, I haven't jumped onto rest yet, but I'm still having troubles thinking rest resources... I feel like I have to "scale down" my application or something like that...
for example, how would you handle an "aprove/close/reject invoice" event? may be an update on an invoice_state resource?
and if I have to recalculate some averages? a resource "salary_average" which can be updated...
ps: I agree with steve, having a prileges or permission resource makes much sense
@bragi
Yes, I'm thinking about it :)
@seban
Thanks!
@steve
That's a good suggestion, thanks. To be honest, I'm not sure what exactly was needed for my friend's "superuser feature". I just wanted to highlight that having a Superusers resource is a possible option.
@opensas
"for example, how would you handle an "aprove/close/reject invoice" event? may be an update on an invoice_state resource?"
I did something similar in one of my projects by creating Approvement, Rejection and other such resources. I even had model classes for them, as it was really important to know when all the things happened and by whom.
"and if I have to recalculate some averages? a resource "salary_average" which can be updated..."
I'm not sure I understand...
Are you asking how to implement actions that calculate something in order to return a result?
Having a salary_average resource sounds good here. You could POST a department id and just return the numbers for the given department.
So, you would have:
salary_average/4
which could go to the salary_averages_controller, CREATE action.
def create
Department.find(params[:id]).salary_average
end
Makes sense?
Personally I think that you have misunderstood REST (the general theory) for the Rails-specific implementation of REST. Theoretically REST simply suggests that you have a uniformly addressable resource (the 'RE' in REST) upon which you invoke state changes by trigger events (actions, the 'ST' part). There is absolutely nothing incompatible between REST and a reset_password custom method.
If, however, you want to cut down on the number of urls related to a password reset you could use the HTTP verbs in an interesting way. It would certainly seem that the actual change of the password could be PUT:/user/4/password while the entry screen gould be GET:/user/4/password and the email request to get that page would be POST:/user/4/password. By so doing you have conceptually turned the password into something like a resource while still strongly relating it to the resource to which it belongs.
And I agree with steve eley regarding the Superuser solution. The idea that you are creating a new Role or Privilege is much more straightforward than something akin to a user subclass (even at the resource level).
Post a Comment