如何在Rails 4的控制器中共享方法之间的变量?

4

我正在尝试学习Ruby on Rails,所以我正在开发一个应用程序。在这个应用程序中,您可以输入URL,点击提交按钮,服务器开始从该URL下载文件,然后您可以看到一个页面,显示下载的细节(从服务器定期更新)。我还没有完全理解Ruby中实例变量和类变量的差异,我犯了一个错误:

class ProgressWebsController < ApplicationController
  layout "application"
   include ActionController::Live

  before_action :set_progress_web, only: [:edit, :update, :destroy]
  @@thread
  @@test=0
  @@clonePW

  # GET /progress_webs
  # GET /progress_webs.json
  def index
    @progress_web=ProgressWeb.new
  end

  def updateProgress
    puts "Not gonna happen"
  end

  # GET /progress_webs/1
  # GET /progress_webs/1.json
  def show
    puts "Downloading %s" % @@clonePW['url'].to_s
    @@thread = download(@@clonePW['url'].to_s)
    @progress_web=@@clonePW
    @@start = Time.now

  end

  # GET /progress_webs/new
  def new
    if( @@test.eql?("100.00"))
      puts "DOWNLOAD COMPLETE"
      @@thread.exit
      render :partial => "complete", :locals => { :progress_int => @@test, :done_int =>@@done, :elapsed_int =>@@elapsed_int }
      return
    end

    @@test= "%.2f" % @@thread[:progress].to_f 
    @@done= "%d" % @@thread[:done] 
    now = Time.now
    elapsed =now - @@start
    @@elapsed_int="%d" % elapsed
    render :partial => "progress", :locals => { :progress_int => @@test, :done_int =>@@done, :elapsed_int =>@@elapsed_int }
  end

def download(url)
Thread.new do
  thread = Thread.current
  body = thread[:body] = []
  url = URI.parse url
  Net::HTTP.new(url.host, url.port).request_get(url.path) do |response|
    length = thread[:length] = response['Content-Length'].to_i
    response.read_body do |fragment|
      body << fragment
      thread[:done] = (thread[:done] || 0) + fragment.length
      thread[:progress] = thread[:done].quo(length) * 100
    end
   end
 end
end

首先,我无法调用updateProgress方法,它一直跳到“show”,并将“updateProgress”作为参数“id”传递。为了避免过多的折腾,每次需要更新下载状态时我都会劫持“new”方法,并让jQuery调用该方法。对不起,我可能在学习基础知识之前就想得太多了。
其次,一次只能有一个人使用这个Web应用程序,因为我必须使用类变量而不是实例变量。如果我使用实例变量,那么另一个方法应该设置的值会被设为空值。阅读了解释之后,我认为我理解了其中的道理,但解决方案是什么呢?在Rails控制器中有没有一种简单的方法可以在方法之间共享值?我在这里找到了一个类似的问题,答案建议在模型中进行计算,但对于我的情况是否也适用呢?
2个回答

3
是的,使用Ruby on Rails很容易陷入混乱。有时甚至被称为“偏离轨道”。问题在于,如果您不花些时间“回到正轨”,您的火车将无法前进。那些火车轮子在泥泞中行驶得不太顺畅 ;)
一旦你发现自己在做像“劫持‘new’方法”和“类变量而不是实例变量”这样的事情,你就偏离了轨道。
我建议您从头开始考虑您想要做什么。当我处于您的境地时,我曾试图“修复”它,但往往会变得更糟,而不是更好!
所以我会重新开始,这次更加努力地坚持标准。对于您的路由和控制器方法,请使用REST-“update”,而不是updateProgress。我建议您实际使用Rails生成器来生成控制器和模型。还要确保您在添加ajax之前已经按标准方式使应用程序正常工作。不确定您是否已经这样做了。
您可以使用更好的redirect_to来更改诸如“继续显示页面”的内容。请参见API示例。 抱歉,如果这不是您正在寻找的直接答案,但我认为这是一个有效的长期答案 :)

生成器: http://guides.rubyonrails.org/command_line.html#rails-generate

路由: http://guides.rubyonrails.org/routing.html # 这样可以定义资源并拥有标准的控制器方法。

当你刚开始时,可能还想使用脚手架,它会为你布局所有RESTful的东西。这里有一篇很棒的文章: http://viget.com/extend/rails-3-generators-scaffolding


2
上述代码的问题在于您试图将过多的逻辑放入控制器中。
您的控制器的“create”应该只创建一个带有URL的“DownloadJob”。仅此而已,只需创建作业并将其存储在数据库中。如果完成了,将用户重定向到控制器的“show”方法。该方法通过其ID加载“DownloadJob”,并呈现其状态或进度。这部分非常简单。您可以遵循有关CRUD控制器和基本模型的任何指南。
注意:您当前未下载任何内容,因此“DownloadJob”的“process”始终为空。
下一部分更有趣。创建“DownloadJob”后,它应该开始下载本身。无需涉及控制器。在下载“DownloadJob”时,它可以使用其当前“process”更新自己。
class DownloadJob < ActiveRecord::Base

  after_save :download

  # ...

  private
    def download
      uri = URI.parse url
      Net::HTTP.new(uri.host, uri.port).request_get(uri.path) do |response|
        received = 0
        length = response['Content-Length'].to_i

        response.read_body do |fragment|
          body << fragment
            received += fragment.length
            self.update_attribute(:progress => (received.quo(length) * 100))
          end
        end
      end
    end

建议阅读:http://guides.rubyonrails.org/active_record_callbacks.html

在下载网络资源时,new方法不会在after_save回调完成之前返回,因此会阻塞控制器。这意味着show方法总是显示100%的进程。

为避免阻塞控制器,请在后台处理下载。使用delayed_jobs或类似的gem来实现。这也允许同时下载多个URL,如果需要,只需启动更多的DelayedJob工作者即可。

建议阅读:https://github.com/collectiveidea/delayed_job/

你将获得:create方法的即时返回。如果通过常规的JavaScript调用观察show方法,则可以看到下载的进度。支持多用户和多并行下载。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接