In apps I like to follow a pattern: each model has their own controller that
handles the CRUD actions for that
model. For example, the Blog
model has the BlogsController
and the User
model has the UsersController
. This is nice because if I need to know how to
CRUD a particular model I know
right where to look, in its corresponding controller.
Ok let’s go with this some more.
Say I have an Account
and it needs to be authorized by someone before you can
access it. Let’s model it:
class Account < ActiveRecord::Base
has_one :authorization
end
class Authorization < ActiveRecord::Base
belongs_to :account
belongs_to :user
end
class User < ActiveRecord::Base
end
We could have modeled this as a boolean attribute authorized
on the Account
class but we also want to track what time an account was authorized (we can let
Rails do this for us by using created_on
) and which User
authorized it. I
like this because I think boolean flags feel dirty and can quickly get out of
hand. I’d rather model states as objects, making my domain model richer. The
phrase Oh we’ll just set a flag, is definitely one of my most hated in software
development.
Anyway here’s our tables:
accounts (id, number, balance)
authorizations (id, account_id, user_id, created_on)
Now following the controller per model pattern we’d have :
AccountsController
AuthorizationsController
UsersController
Now I don’t like to consider performance but by creating an Authorization
model we now require a join from accounts
to authorizations
just to
determine if an Account
has been authorized as opposed to just reading an
attribute in the Account
object.
Maybe we went too far but what if didn’t then, we’d have something like:
class AccountsController < ApplicationController
def authorize
end
# all our CRUD actions
end
What is #authorize? That is not Creating, Reading, Updating or Deleting an
Account
object. I guess you could say “yeah it’s Updating just 1 attribute
of an Account
”. No, I don’t like it; I want my #edit action to edit the
whole object. Like I said before, I want my controllers to handle only the
CRUD actions for a particular
model. It turned out we were missing a concept, an Authorization
.
Replacing that boolean with a model kept our CRUD only pattern alive. Authorizing an account became creating
an Authorization
:
class AuthorizationsController < ApplicationController
def new
end
def create
end
end
What is #authorize? That is not Creating, Reading, Updating or Deleting an
Account
object. I guess you could say yeah it’s Updating just 1 attribute of
an Account
. No, I don’t like it; I want my #edit action to edit the whole
object. Like I said before, I want my controllers to handle only the CRUD actions for a particular model.
It turned out we were missing a concept, an Authorization
. Replacing that
boolean with a model kept our CRUD only pattern alive. Authorizing an account became creating
an Authorization
:
class AuthorizationsController < ApplicationController
def new
@account = Account.find params[:id]
end
def create
@account = Account.find params[:id]
if @account.update_attributes params[:account]
redirect_to :controller => 'accounts', :action => :show, :id => @account.id
else
render :action => :new
end
end
end
What if we relaxed the pattern a bit. Say, no longer can controllers only deal with their corresponding models.
For example, the BlogsController
can now work with any model not just Blog
s.
However, controllers can still only have CRUD actions.
Let’s also eliminate the Authorization
model and just let Account
have a
boolean attribute named authorized
, a integer called authorizer_id
and a
datetime called authorized_on
.
Alright here we go.
class SessionsController < ApplicationController
def new
end
def create
end
end
The #new
action would display something along the lines of a form saying ‘Do
you want to authorize Account #A-1’? The form would then post to #create
,
using hidden fields containing a boolean value of true
for the Account
object’s authorized
attribute, the current time for its authorized_on
attribute, and the id of the currently logged in user for its authorizer_id
attribute. The #update_attributes
call in #create
would update the
Account
objects authorized
, authorizer_id
and authorized_on
attributes,
thus authorizing the Account
.
Here’s another common one.
When dealing with sessions don’t create a LoginController
with say #index
and #login
actions, create a SessionsController
who is #new
and #create
actions authenticate the given credentials.
class UsersController < ApplicationController
def send_password_reminder
end
end
Another one; typically apps that require a login end up with a ‘forgot password’
feature where a user gets emailed a link that allows them to reset their
password. Usually this ends us in the UsersController
like this:
class RemindersController < ApplicationController
def create
end
end
What is #send_password_reminder?
It’s trash. And its certainly not CRUD. How about we do this:
class UsersController < ApplicationController
def edit_password
end
def update_password
end
def edit
end
def update
end
end
Ahh that’s better. Back to our CRUD. Now there’s no Reminder
model but remember we relaxed
the pattern by allowing controllers to deal with any model. And if someone says
Hey I don’t think the password reminder form’s working right, we know right
where to look, in the RemindersController
because we’re creating a reminder;
we don’t have to wade through the UsersController
looking for some weird named
action like #send_password_reminder
.
One more.
Sometimes in apps, users like to be able to edit their profile, which would include things like first name, last name, address, etc. But it would not include password. They like to be able to edit/change their password separately. Typically this ends up like this:
class PasswordsController < ApplicationController
def new
end
def create
end
end
Now we have an #edit_password
and #update_password
in addition to our normal
#edit
and #update
actions which deal with editing non-password user info.
Once again, trash. How about this:
class PasswordsController < ApplicationController
def new
end
def create
end
end
There we go. We’re creating a new password for a given User
. Back to our
CRUD.
You see with this relaxed version of the controller per model pattern we still get to keep our nice CRUD actions, so every controller looks exactly the same i.e. they have all the same named actions. And we don’t have to create a whole bunch of models, which results in more tables, more joins, etc. This style is in line with the new REST stuff in Rails 1.2.
CRUD 4 Life.