Ruby on rails控制器代码,需要重构最好的方法才能更干燥?

时间:2013-06-22 18:41:20

标签: ruby-on-rails ruby-on-rails-3 refactoring controllers

我有一个欢迎的wizzard,它在第一次登录时构建用户配置文件。问题是它实施起来很乱,但是我已经尝试重构几次并重写它但是不能用更好的东西来创建下面。

在理想的世界中,它将全部在welcome_controller.rb中,但这引起了很大的麻烦,所以现在我改写了profile_controller的更新方法。

关于如何改善这一点的任何想法都会让它变得更加干燥和清洁?很想收到一些关于此的好的意见和想法,或许将所有更新内容转移到欢迎控制器?

WelcomeController:

class WelcomeController < ApplicationController

  before_filter :authenticate_user!
  before_filter :load_step

  layout "welcome"

  def sub_layout
   "center"
  end

  def edit
    # form updates post to edit since
    # profile is non existant yet

    params[:step] = "photos" unless params[:step]

    @photos   = Photo.where(:attachable_id => current_user.id)
    @profile  = Profile.where(:user_id => current_user.id).first
    @photo    = Photo.new

    if ["photos", "basics", "details", "test"].member?(params[:step])

      # force rendering the correct step
      case current_user.profile.step
        when 1
          render :template => "/profiles/edit/edit_photos", :layout => "welcome"
        when 2
          render :template => "/profiles/edit/edit_basics", :layout => "welcome"
        when 3
          render :template => "/profiles/edit/edit_details", :layout => "welcome"
        when 4
          render :template => "/profiles/edit/edit_test", :layout => "welcome"
      end

    else
      render :action => "/profiles/edit/edit_photos"
    end
  end

  def load_step

    redirect_to root_path if current_user.profile.complete

    case current_user.profile.step
      when 1
        redirect_to "/welcome" unless params[:controller] == "welcome"
      when 2
        redirect_to "/welcome/basics" unless params[:controller] == "welcome" && params[:action] == "edit" && params[:step] == "basics"
      when 3
        redirect_to "/welcome/details" unless params[:controller] == "welcome" && params[:action] == "edit" && params[:step] == "details"
      when 4
        redirect_to "/welcome/test" unless params[:controller] == "welcome" && params[:action] == "edit" && params[:step] == "test"
    end
  end

end

ProfileController可:

class ProfileController < ApplicationController

...
 def update
    @profile        = Profile.find(params[:id])
    @tags           = Session.tag_counts_on(:tags)
    @profile.form   = params[:form]
    @match          = Match.where(:user_id => current_user.id).first
    authorize! :update, @profile

    respond_to do |format|

      if @profile.update_attributes(params[:profile])

        if current_user.profile.complete
          format.html { redirect_to "/profiles/#{ current_user.username }/edit/#{ @profile.form }", notice: t('notice.saved') }
        else
          case current_user.profile.step
            when 1
              current_user.profile.update_attributes(:step => 2)
              format.html { redirect_to "/welcome/basics", notice: t('notice.saved') }
            when 2
              current_user.profile.update_attributes(:step => 3)
              format.html { redirect_to "/welcome/details", notice: t('notice.saved') }
            when 3
              current_user.profile.update_attributes(:step => 4)
              format.html { redirect_to "/welcome/test", notice: t('notice.saved') }
          end
        end

      else

        if current_user.profile.complete
          format.html { render action: "/edit/edit_" + params[:profile][:form], :what => @profile.form }
        else
          case current_user.profile.step
            when 1
              current_user.profile.update_attributes(:step => 2)
              format.html { redirect_to "/welcome/basics", notice: t('notice.saved') }
            when 2
              current_user.profile.update_attributes(:step => 3)
              format.html { redirect_to "/welcome/details", notice: t('notice.saved') }
            when 3
              current_user.profile.update_attributes(:complete => 1)
              format.html { redirect_to root_path }
          end
        end
      end

    end

  end
  ...
  end

视图位于/ profiles / edit / *

2 个答案:

答案 0 :(得分:3)

众所周知,奇才很难做到正确,我从未见过完全满足我的实施。我通常会使用所谓的“表单对象”并为每一步创建一个安静的控制器。

关于这个问题有一个很好的(但付费的)Railscast

要点是:使用ActiveModel创建一个像常规ActiveRecord模型一样嘎嘎叫的对象。

例如:

class Welcome::BasicInformation
  include ActiveModel::Validations
  include ActiveModel::Conversion
  extend ActiveModel::Naming

  def persisted?
    false
  end

  def initialize(user)
    @user = user
  end

  attr_reader :user

  delegate :some_field, :some_other_field, to: :user

  validates_presence_of :some_field

  def save(params)
    user.some_field = params[:some_field]
    user.some_other_field = params[:some_other_field]
    if valid?
      user.step = 2
      user.save
    end
  end

  def photo
    @photo ||= Photo.new
  end

  def profile
    @profile ||= user.profiles.first
  end

end

你基本上每一步都要创建一个这样的模型。

然后,您可以为每个步骤创建控制器,并为所有步骤使用专门的ApplicationController:

class Welcome::ApplicationController < ::ApplicationController

  layout "welcome"
  before_filter :authentice_user!

end

每一步:

class Welcome::BasicInformationsControlller < Welcome::ApplicationController

  def new
    @step = Welcome::BasicInformation.new(current_user)
  end

  def create
    @step = Welcome::BasicInformation.new(current_user)
    if @step.save(params[:welcome_basic_information])
      redirect_to welcome_some_other_step_path, notice: "Yay"
    else
      render :new
    end
  end

end

为每一步创建一条路线:

namespace :welcome do
  resource :basic_information, only: [:new, :create]
  resource :some_other_step,   only: [:new, :create]
end

这只会留下一些自动重定向,例如禁止用户进入他们尚未被允许访问的步骤。由于您为每个步骤使用单独的URL,因此这可能不那么重要。

您可以在表单对象中存储有关访问哪个步骤的信息:

class Welcome::BasicInformation
  # ...
  def allowed?
    user.profile.step == 1
  end
end

然后稍微重构一下控制器:

class Welcome::BasicInformationsController < Welcome::ApplicationController

  before_filter :allowed?

  def new
  end

  def create
    if step.save(params[:welcome_basic_information])
      redirect_to welcome_some_other_step_path, notice: "Yay"
    else
      render :new
    end
  end

  private

  def step
    @step ||= Welcome::BasicInformation.new(current_user)
  end
  helper_method :step

  def allowed?
    redirect_to previous_step_path unless step.allowed?
  end

end

这可能不会更短,但我确实感觉它是多么灵活,每个步骤的重点是什么,如何在每个步骤上进行不同的验证等等。每个控制器/模型组合都非常容易理解,对其他人来说是可以理解的。

答案 1 :(得分:1)

我会做一些事情,但首先是一些想法。

  1. 有时你需要稍微休息一下才能使代码更具可读性。就是这种情况
  2. 像在此处一样,在控制器之间重定向不是一种好方法
  3. 那么,我要做什么。

    1. 将有关这些步骤的所有代码放在一个控制器(最好是配置文件)中,并使用路由调整URL。
    2. 创建单个节目和单个保存操作
    3. 如果我理解正确,将向用户显示的步骤仅取决于current_user上设置的用户#步骤。因此,我认为没有必要传递任何网址变量,您可以从current_user获取当前/下一步。

      代码重构(可能是一些错误,没有测试) 全部在ProfileController

        def edit
          @profile  = Profile.find(current_user.id)
          @next_step = current_user.step.to_i + 1 # I imply that there's just single permissable next step
          render :template => "/profiles/edit/#{@next_step}", :layout => "welcome"
        end
      
        def update
          @profile = Profile.find(params[:id])
          authorize! :update, @profile
      
          if @profile.update_attributes(params[:profile])
            # you should pass step number in params so I get's updated by default.
            redirect_to "/welcome/basics", notice: t('notice.saved')
          else
      
          end
        end