robzolkos
1/15/2013 - 3:02 AM

gistfile1.rb

# 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