如何使这种控制器方法减轻重量?

时间:2011-05-10 19:24:45

标签: ruby model-view-controller business-logic

假设我有以下控制器和包含的方法。我觉得这个控制器方法(列表)太重了。我该怎么分开呢?什么应该移动到视图层,什么应该移动到模型层?

注意:返回格式(包含:text,:leaf:,id,:cls)包含与ProjectFile数据库表中的字段不同的字段,因此这让我怀疑这个控制器方法实际应该移动到多少模型层。

class ProjectFileController < ApplicationController
  before_filter :require_user

  def list
    @project_id = params[:project_id]
    @folder_id = params[:folder_id]

    current_user = UserSession.find
    @user_id = current_user && current_user.record.id

    node_list = []

    # If no project id was specified, return a list of all projects.
    if @project_id == nil and @folder_id == nil
      # Get a list of projects for the current user.
      projects = Project.find_all_by_user_id(@user_id)

      # Add each project to the node list.
      projects.each do |project|
        node_list << {
          :text => project.name,
          :leaf => false,
          :id => project.id.to_s + '|0',
          :cls => 'project',
          :draggable => false
        }
      end
    else
      # If a project id was specfied, but no file id was also specified, return a
      # list of all top-level folders for the given project.
      if @project_id != nil and @folder_id == nil
        # Look for top-level folders for the project. 
        @folder_id = 0
      end

      directory_list = []
      file_list = []

      known_file_extensions = ['rb', 'erb', 'rhtml', 'php', 'py', 'css', 'html', 'txt', 'js', 'bmp', 'gif', 'h', 'jpg', 'mov', 'mp3', 'pdf', 'png', 'psd', 'svg', 'wav', 'xsl']

      # Get a list of files by project and parent directory.
      project_files = ProjectFile.find_all_by_project_id(@project_id,
                                                         :conditions => "ancestry like '%#{@folder_id}'",
                                                         :order => 'name')

      project_files.each do |project_file|
        file_extension = File.extname(project_file.name).gsub('.', '')

        if known_file_extensions.include? file_extension
          css_class_name = file_extension
        else
          css_class_name = 'unknown'
        end

        # Determine whether this is a file or directory.
        if project_file.is_directory
          directory_list << {
            :text => project_file.name,
            :leaf => false,
            :id => @project_id + '|' + project_file.id.to_s,
            :cls => css_class_name
          }
        else
          file_list << {
            :text => project_file.name,
            :leaf => true,
            :id => @project_id + '|' + project_file.id.to_s,
            :cls => css_class_name
          }
        end
      end

      node_list = directory_list | file_list
    end

    render :json => node_list
  end
end

3 个答案:

答案 0 :(得分:3)

我认为您可以将大部分逻辑放在模型中ProjectFile或任何合适的模型名称:

ProjectFile < ActiveRecord::Base
  def node_list(project_id, folder_id, user_id)
    node_list = []

    # If no project id was specified, return a list of all projects.
    if project_id == nil and folder_id == nil
    # Get a list of projects for the current user.
    projects = Project.find_all_by_user_id(user_id)

      # Add each project to the node list.
      projects.each do |project|
        node_list << {
          :text => project.name,
          :leaf => false,
          :id => project.id.to_s + '|0',
          :cls => 'project',
          :draggable => false
        }
      end
    else
      # If a project id was specfied, but no file id was also specified, return a
      # list of all top-level folders for the given project.
      if project_id != nil and folder_id == nil
        # Look for top-level folders for the project. 
        folder_id = 0
      end

      directory_list = []
      file_list = []

      known_file_extensions = ['rb', 'erb', 'rhtml', 'php', 'py', 'css', 'html', 'txt', 'js', 'bmp', 'gif', 'h', 'jpg', 'mov', 'mp3', 'pdf', 'png', 'psd', 'svg', 'wav', 'xsl']

      # Get a list of files by project and parent directory.
      project_files = ProjectFile.find_all_by_project_id(project_id,
                                                     :conditions => "ancestry like '%#{folder_id}'",
                                                     :order => 'name')

      project_files.each do |project_file|
        file_extension = File.extname(project_file.name).gsub('.', '')

        if known_file_extensions.include? file_extension
          css_class_name = file_extension
        else
          css_class_name = 'unknown'
        end

        # Determine whether this is a file or directory.
        if project_file.is_directory
          directory_list << {
            :text => project_file.name,
            :leaf => false,
            :id => project_id + '|' + project_file.id.to_s,
            :cls => css_class_name
        }
        else
          file_list << {
            :text => project_file.name,
            :leaf => true,
            :id => project_id + '|' + project_file.id.to_s,
            :cls => css_class_name
          }
        end
      end

      node_list = directory_list | file_list
    end
end

然后将node_list分解为更易于管理的方法。我上面定义的方法很长并且做了很多事情(低内聚力),所以打破它会有助于消除这些缺点。

在您的控制器中,您可以这样称呼:

class ProjectFileController < ApplicationController
  before_filter :require_user

  def list
    @project_id = params[:project_id]
    @folder_id = params[:folder_id]

    current_user = UserSession.find
    @user_id = current_user && current_user.record.id

    nodes = node_list(@project_id, @folder_id, @user_id)
    render :json => nodes
  end
end

现在您的控制器更易于阅读并且提取了业务逻辑。这遵循咒语“瘦的控制器,胖模型”。

答案 1 :(得分:1)

您的用户模型中应该有一个关联,如

has_many => :projects

因此,您可以使用

获取项目对象的数组
current_user.projects

而不是:

projects = Project.find_all_by_user_id(@user_id)

这意味着您也不需要检索user_id。

您还可以将用于填充node_list的所有逻辑放入用户模型中。只需输入你的模型:

def node_list(project_id, folder_id=0)
  if @project_id == nil
    list = self.projects.map do |project|
      {
        :text => project.name,
        :leaf => false,
        :id => project.id.to_s + '|0',
        :cls => 'project',
        :draggable => false
      }
    end
  else
    ... # the rest of your code here, etc
  end
  return list
end

请注意,如果folder_id为nil且设置为零,也可以删除您的检查,因为folder_id=0中的def node_list(project_id, folder_id=0)会自动执行此操作。

然后您的控制器将如下所示:

def list
  @current_user = UserSession.find
  render :json => @current_user.node_list(params[:project_id], params[:folder_id])
end

同时

因为你只是组合了directory_list和file_list,为什么不把if-else语句组合成:

{
  :text => project_file.name,
  :leaf => project_file.is_directory,
  :id => @project_id + '|' + project_file.id.to_s,
  :cls => css_class_name
}

如有必要,您随后可以随后对数组进行排序。

答案 2 :(得分:1)

@Chad,

我认为你已经有很好的例子来重构你在我之前发布的人的代码。

你似乎有能力编写ruby代码,所以我将尝试从“如何重构”的观点回答你的问题。

  • 保持控制器枯瘦

    • 在适用的过滤器之前使用
    • 每个控制器端点(动作)是控制器类中的一个方法,它应该具有“单一”的责任。
    • 您的“list”方法应该找到相关数据(使用对适当模型类的方法调用)并让Rails执行默认呈现。你正在返回json,所以一行就可以了。
    • 现在你需要在模型中调用最多一个,特殊情况下两个方法。
    • 我为我提出这句格言“如果行动方法超过10行,那就错了”。
  • 保持模特脂肪

    • 将问题分解为更小的问题。
    • 你的list方法有太多的代码分支。
    • 也许在较高的层次上你可能想要将它分解为模型中的“语义”控制器类型方法,该方法执行if if else的主要操作。
    • 其他较小的方法完成其余的工作。
    • 模型方法也应该有一小组参数。参数太多而且很脆弱,没有参数意味着它与太多的状态联系在一起。
    • 具有1或2个参数的方法是好的。您可以拥有一个包含3个参数选项的方法。任何超过3个参数的内容都适合重构。
    • 当前用户对象可以传递给模型,没有任何问题。
    • 或者通过在其中添加更多方法来丰富您的用户模型。 (has_many:项目,然后使用协会提供的方法)。
    • 我随时都会将协会文档随身携带。
    • 我随时都会随身携带enumerables文档。
    • 了解Enumerables中的地图,选择,拒绝,查找操作。他们是你的朋友!
祝你好运! :)