【问题标题】:Ruby on Rails Best practices - Big Controller vs Small ControllerRuby on Rails 最佳实践 - 大控制器与小控制器
【发布时间】:2012-08-12 15:21:13
【问题描述】:

我需要一些关于 Ruby on Rails 中的最佳实践的信息,尤其是对于必须做很多事情的控制器,所以,一个简单的“展示”动作现在已经到线了。我知道,这不是很好,而且我有特定的代码。

这是一个示例代码:

def show
    sound = Sound.find(params[:id])
    @xml_path = File.dirname(sound.file.path)
    s3 = AWS::S3.new(
        :access_key_id => 'XXX',
        :secret_access_key => 'XXX')
    @url = s3.buckets['dev'].objects[sound.file.path[1..-1]].url_for(:read, :expires => 10*60)

    if sound.id_job != 0 && sound.transcript_progress != 100
      @response = Savon.client("http://srap.php?wsdl").request(:avance) do
        soap.body = { 
         :Jeton => "abcdef",
         :ID_job => sound.id_job,
        }
      end
      @response = @response.to_hash
      @progress = @response[:avance][:avancement].to_s.split("#")[1]# ID_job received is formed like "OK#123", we keep "123"
      if @progress == "Termine"
         sound.transcript_progress = 100
      elsif @progress == "ERROR"
        flash.now[:alert] = "Oups, il semblerait que le fichier soit illisible, ou qu'il n'y ait rien a ecouter !"
      elsif @progress != "Queued"
        sound.transcript_progress  = @response[:avance_response][:avancement].to_s.split("#")[2].split("%")[0].to_i
      end
      sound.save
    end

    if sound.transcript_progress == 100 # If transcription finished
      # Get XML File URL on the FTP
      @xml_path = Savon.client("http://srap.php?wsdl").request(:donneResultat) do
      soap.body = { 
       :Jeton => "XXX",
       :FichierSon => sound.id_job
      }
      end

      # Parse XML Path URL on Kimsufi
      @xml_path = @xml_path.to_hash[:donne_resultat_transposition_response][:chemin_fichier].to_s.split("#")[2].to_s.split("/")[5]


      # Create local directory (/tmp/sounds) for XML Temp Save
      if ! File.directory?(Rails.root.to_s + '/tmp/sounds')
        Dir.mkdir(Rails.root.to_s + '/tmp/sounds')
      end
      # Get XML from FTP
      ftp=Net::FTP.new                                     
      ftp.connect("ftp.com", 21)                                                         
      ftp.login("XXX", "XXX")                
      if ftp.closed?
        flash.now[:alert] = "Oups, il semblerait qu'il y ait eu un problème ! Merci d'actualiser la page"
      else  
        ftp.passive = true
        ftp.chdir('results')
        ftp.getbinaryfile(@xml_path, Rails.root.to_s + '/tmp/sounds/' + @xml_path)
        ftp.close
      end

      # Send XML on S3
      s3 = AWS::S3.new(
        :access_key_id => 'XXX',
        :secret_access_key => 'XXX')
      @xml_new = (File.dirname(@sound.file.path) + '/' + File.basename(@xml_path))[1..-1]
      s3.buckets['dev'].objects[@xml_new].write(Pathname.new(Rails.root.to_s + '/tmp/sounds/' + @xml_path))
      @file = s3.buckets['dev'].objects[@xml_new].read()
    end


    # A lot of logic again, i've not did it yet

  end

如你所见,我在这里有很多逻辑, 我必须检查转录是否结束, 如果没有,请更新 progress_bar (@sound.transcript_progress), 如果是,我首先必须连接到一个肥皂操作来获取 XML 路径,然后通过 FTP 获取 XML,然后将其存储到 Amazon S3(糟糕的 SOAP,我必须重新解析所有响应......)。

在我所有的动作控制器中,我必须连接 S3 / SOAP / FTP,而不是 以相同的顺序..所以我想为每个人上一堂课,比如 C++,一种抽象。我想把事情做好,我不在乎(很多)如何 完成。但是 MVC 的最佳实践是什么?我必须做一个 新文件夹“类?”新控制器?

【问题讨论】:

  • 先提示几个提示:else if 应该是 elsif,if (ftp.closed? == true) 应该是 if ftp.closed?
  • 为什么要分配给sound,然后复制到@sound?
  • 谢谢 2,我刚刚编辑了。关于@sound 中的声音副本,我不知道为什么,但在第一个 SOAP 范围内,@sound.id 无效,而 sound.id 有效。所以为了调试,我正在这样做。
  • 带前导 @ 的变量在控制器操作范围之外可用 - 只有当这些变量需要在您的视图中可用时,您才需要使用它。
  • 这是一篇关于 Fat Models 和 Skinny Controllers 的好博文:weblog.jamisbuck.org/2006/10/18/skinny-controller-fat-model,在您的情况下,最好考虑 Fat Libraries 和 Skinny Controllers。

标签: ruby-on-rails ruby model-view-controller ruby-on-rails-3.2


【解决方案1】:

这更像是一个长篇评论,因为它解释了您的困境的根源,但没有提供任何解决方案。

这个问题实际上是由于对 MVC 的误解造成的,而 MVC 是 RoR 普及的。

这是导致控制器内爆的两个因素的组合:

  • 一方面你有anemic model,因为RoR 使用ORM 实例集合而不是真正的模型层。其原因是 Rails 最初被创建为用于快速原型设计(生成一次性代码)的框架。原型设计正是 Active Record 最擅长的。使用脚手架,您可以轻松地从现有数据库中生成活动记录结构。

    但这会导致某些域业务逻辑在您的控制器中泄漏。

  • 另一方面,你有不存在的观点。由于目标是原型设计,Rails 倾向于通过将视图合并到控制器中来摆脱实际上可以包含表示逻辑的视图。现在缺少的视图被替换为简单的模板,这些模板被称为“视图”。

    这会强制控制器包含表示逻辑。

这两个因素将是我想要断言 RoR 甚至不是 MVC 框架的原因。结果模式实际上更接近Model-View-Presenter。尽管它已被简化到开始破坏Separation of Concerns 的程度。

【讨论】:

  • 好的,我明白你的意思了,谢谢你这个有趣的回复!
  • 我认为指责 Rails 并不能以任何方式回答这个问题......这段代码可以重构,包括方法提取、模型中的移动逻辑、关注点或特定服务。它们是在 Rails 中避免胖控制器的很多方法。您可以使用任何框架编写“糟糕”的代码。
【解决方案2】:

您的大部分逻辑不属于控制器。控制器的职责是将输入(HTTP 请求及其参数)与输出(您的视图)联系起来。其他一切都是应该在模型中实现的业务逻辑 - 在您的情况下,Sound 看起来像。例如,您的每个if 块都可以作为Sound 类的实例方法来实现。如果您发现自己在各种模型中重用代码(如 AWS 存储位),请在 Module(lib 下)中实现它们,并将该模块包含在这些模型中。

【讨论】:

  • 好的,如果我很好理解 2 个步骤: 1- 所有“if Blocks”都必须移动到我的模型/声音 2- 应该在每个逻辑中实现 AWS、SOAP 和 FTP 等所有逻辑一个模块,一个 SOAP 模块,一个 AWS 模块和一个 FTP 模块?我说的对吗?
  • 没有硬性规定,但这似乎是一个好方法。两个大的“if”块是一个很好的起点,但一般来说,每个方法都应该做一件事。如果您发现自己在模型中编写了很长的方法,请将它们分解成更小的单元。
【解决方案3】:

看起来所有这些都应该重构为模型(或库模块)并分解为更小的函数。这样做的最佳原因是,您可以设置单元测试来单独测试较小的部分。控制器只需要实例化模型并将数据返回给浏览器。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2010-09-12
    • 2018-09-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-03-09
    • 1970-01-01
    相关资源
    最近更新 更多