2

I have a welcome wizzard that builds a user profile when first login.Problem is it is quite messy implemented but I have tried to refactor it several times and rewrite it but cannot comeup with something better then below.

In ideal world it would be all inside welcome_controller.rb but this have caused much headache so Now i rewrote the update method for profile_controller instead.

Any thoughts on how to improve this make it more dry and clean? Would love to recieve some good input on this and thoughts perhaps to move all update stuff to welcome controller instead?

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

Views are in /profiles/edit/*

4

2 回答 2

3

众所周知,Wizards 很难做到正确,而且我从未见过让我完全满意的实现。我通常使用所谓的“表单对象”并为每个步骤创建一个安静的控制器。

关于这个主题有一个优秀的(但付费的)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

这可能不会更短,但我确实喜欢它的灵活性,每个步骤的重点,如何对每个步骤进行不同的验证等等。每个控制器/模型组合都非常容易理解,其他人也可以理解。

于 2013-06-22T21:24:26.990 回答
1

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

  1. 有时您必须稍微打破休息状态以使代码更具可读性。就是这样
  2. 像您在此处所做的那样,在控制器之间重定向不是一种好方法

所以,我会怎么做。

  1. 将有关这些步骤的所有代码放在一个控制器中(最好是配置文件),并使用路由调整 url。
  2. 创建单个显示和单个保存操作

如果我理解正确,将向用户显示的步骤仅取决于在 current_user 上设置的 User#step。因此,我认为确实不需要传递任何 url 变量,您可以从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
于 2013-06-22T20:55:16.303 回答