Rails 4:如何在这个长控制器方法中解耦逻辑?

时间:2014-09-06 11:26:05

标签: ruby-on-rails authentication controller refactoring

我正在使用has_secure_passwordrails 4.1.5应用。我想将我的登录功能与我的SessionsController分离,以便我可以重复使用它来从我的应用程序中的任何位置登录任何用户 - 例如在注册后登录用户,记录分析事件等。

所以我将我的代码重构为LoginUser服务对象,我很满意

问题是我的控制器在重构后仍然有一些耦合逻辑。我正在使用表单对象(通过改革gem)进行表单验证,然后将用户,会话和密码传递给LoginUser服务。

以下是SessionsController中的创建方法:

  def create
    login_form = Forms::LoginForm.new(User.new)

    if login_form.validate(params[:user]) # validate the form
      begin #find the user
        user = User.find_by!(email: params[:user][:email])
      rescue ActiveRecord::RecordNotFound => e
        flash.now.alert = 'invalid user credentials'
        render :new and return
      end
    else
      flash.now.alert = login_form.errors.full_messages
      render :new and return
    end

    user && login_service = LoginUser.new(user, session, params[:user][:password])
    login_service.on(:user_authenticated){ redirect_to root_url, success: "You have logged in" }

    login_service.execute
  end

一切都按预期工作,但我不满意的部分是验证表单之间的绑定逻辑,然后找到用户,然后再将其发送到服务宾语。此外,多重闪光警报感觉......还不错。

如何通过解耦这两种方法来改善这种方法?现在似乎是一个人背着另一个人。

这里有我的LoginUser服务对象

供您参考
class LoginUser
    include Wisper::Publisher

    attr_reader :user, :password
    attr_accessor :session

    def initialize(user, session, password)
        @user = user
        @session = session
        @password = password
    end

    def execute
        if user.authenticate(password)
            session[:user_id] = user.id
            publish(:user_authenticated, user)
        else
            publish(:user_login_failed)
        end
    end
end

1 个答案:

答案 0 :(得分:3)

最重要的是create是一种具有多重责任的方法,可以/应该被隔离。

我看到的责任是:

  1. 验证表格
  2. 找到用户
  3. 返回验证错误消息
  4. 返回未知用户错误消息
  5. 创建LoginService对象,设置after-auth行为并执行auth
  6. 清理它的设计目标是编写具有单一责任的方法,并尽可能注入依赖项。

    忽略UserService对象,我在重构中的第一次镜头可能如下所示:

    def create
      validate_form(user_params); return if performed?
      user = find_user_for_authentication(user_params); return if performed?
    
      login_service = LoginUser.new(user, session, user_params[:password])
      login_service.on(:user_authenticated){ redirect_to root_url, success: "You have logged in" }
      login_service.execute
    end
    
    private
    
    def user_params
      params[:user]
    end
    
    def validate_form(attrs)
      login_form = Forms::LoginForm.new(User.new)
      unless login_form.validate(attrs)
        flash.now.alert = login_form.errors.full_messages
        render :new
      end
    end
    
    def find_user_for_authentication(attrs)
      if (user = User.find_by_email(attrs[:email]))
        user
      else
        flash.now.alert = 'invalid user credentials'
        render :new
      end
    end
    

    值得注意的是,return if performed?条件会检查是否已调用renderredirect_to方法。如果是这样,则会调用return并提前完成create操作,以防止出现双重渲染/重定向错误。

    我认为这是一个很大的改进,因为责任已被分解为几种不同的方法。并且这些方法在很大程度上注入了它们的依赖性,以便它们在未来也可以继续自由发展。