# I don't really see any services here. What I see is:
# - Normal HTTP boundary stuff (params flash, redirect).
# - Model creation and retrieval.
# - Warden manipulation, which is an odd done but smells like boundary.
#
# I left all of the HTTP boundary stuff in the controller (and only the
# controller). I moved the model creation/retrieval into simple class methods
# in the models. I moved the warden manipulation stuff into
# ApplicationController (with caveats that I'll discuss inline).
#
# Commentary on each class follows:
# Commentary:
#
# I simplified the `new` action by pushing the new-account-with-owner
# relationship down to the model. Depending on the domain, it may make sense
# to run build_owner on all new objects; it's not clear. (This is also true of
# all of the model changes that follow. Some or all of them may make more
# sense if enforced all of the time--either after-the-fact via foreign keys
# and validations, or that plus before-the-fact by hooking into object
# creation.
#
# The AccountsController unpacks the params for Account, but lets Account
# deal with the details of manipulating the database and model relationships.
# I don't know what the proper name for the create_with_owner method is,
# since I don't know the domain.
#
# Pulling globally stateful stuff out into a reasonable object is difficult
# and it's probably always going to be a leaky abstraction. If it were a ton
# of code, I'd pull it out just for organization's sake. Since it's small,
# I'd rather leave it with the rest of the tightly coupled evil in the
# controller. I prefer to centralize the evil in my applications. (That's
# jokes but also real talk).
module Subscribem
class AccountsController < ApplicationController
def new
@account = Account.new_with_owner
end
def create
account = Account.create_with_owner(params[:account])
force_authentication(account.owner.id, account.id)
flash[:success] = "Your account has been successfully created."
redirect_to subscribem.root_url(:subdomain => account.subdomain)
end
end
end
# Commentary:
#
# Same story as the AccountsController, really. I pull the model creation out
# into a method (again, I don't know its proper name).
module Subscribem
class Account::UsersController < ApplicationController
def new
@user = Subscribem::User.new
end
def create
user = User.create_within_account(account, params[:user])
force_authentication(user, account)
flash[:success] = "You have signed up successfully."
redirect_to root_path
end
end
end
# Commentary:
#
# I'm conflicted about putting auth in ApplicationController vs. moving it to
# a module or another object; I really don't know which of the three I
# prefer. The nice thing about putting it here (or in a module) is that
# controllers don't have to be digging around in the rack env to pull the
# warden object out. If this were another application concern--one that
# didn't cut across the entire application like authentication does--then I
# wouldn't do this. If ApplicationController hit 50 lines or so, I'd probably
# pull things out.
class ApplicationController
def force_authentication(user, account)
env['warden'].set_user(user, :scope => :user)
env['warden'].set_user(account, :scope => :account)
end
end
# Commentary:
#
# The Account model encapsulates manipulation of accounts, including creating
# them.
class Account < ActiveRecord::BallAndChain
def self.new_with_owner
account = Subscribem::Account.new
account.build_owner
account
end
def self.create_with_owner(params)
account = create(params)
account.users << account.owner
account.create_schema
account
end
end
# Commentary:
#
# Same as Account: it encapsulates creation.
class User < ActiveRecord::BallAndChain
def self.create_within_account(account, params)
user = Subscribem::User.create(params[:user])
account.users << user
end
end