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 方法,它继续“显示”,并将“updateProgress”作为参数“id”传递。我没有过多地摆弄这个,而是劫持了“新”方法,并在每次想要更新下载状态时让我的 jQuery 调用它。请原谅我,在学习基础知识之前,我可能咬得太多。

其次,一次只能一个人使用这个 webapp,因为我不得不使用类变量而不是实例变量。如果我使用实例变量,那么当一个方法查看另一个方法应该设置的值时,它们将为零。阅读为什么会这样,我想我明白了,但是解决方案是什么?有没有一种简单的方法可以在 Rails 的控制器中的方法之间共享值?我在这里发现了一个类似的问题,其中的答案建议在模型中进行计算,但这也适用于我的目的吗?

4

2 回答 2

3

是的,Ruby on Rails 很容易陷入混乱。有时甚至被称为“出轨”。问题是,如果您不花一些时间“重回正轨”,那么您的火车无论如何也不会很快上路。那些火车轮子在泥里跑得不太好;)

一旦你发现自己在做诸如“劫持“新”方法”和“类变量而不是实例变量”之类的事情,那么你就偏离了轨道。

我会从你正在尝试做的事情重新开始。当我遇到你的情况时,我尝试过“修复”它,但它往往会变得更糟,而不是更好!

所以我会重新开始,这次更努力地坚持标准。将 REST 用于您的路由和控制器方法 - “更新”,而不是 updateProgress。我建议您实际使用 rails 生成器来生成控制器和模型。还要确保在添加之前让应用程序在没有 ajax 的情况下以标准方式运行。不确定你有没有。

您可以在保存后使用更好的 redirect_to 更改“继续显示页面”之类的内容。请参阅 api 示例。抱歉,如果这不是您正在寻找的即时答案,但我认为这是一个有效的长期答案:)

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

Routes: http://guides.rubyonrails.org/routing.html # 这让你可以定义一个资源并拥有标准的控制器方法。

您可能还想在开始时使用脚手架,它将为您布局所有 RESTful 内容。这里有一篇很棒的文章:http: //viget.com/extend/rails-3-generators-scaffolding

于 2013-09-18T01:37:19.307 回答
2

上面代码的问题是你试图在控制器中放入太多的逻辑。

您的控制器create应该只使用 URL 创建一个DownloadJob。仅此而已,只需创建作业并将其存储在数据库中。如果完成,将用户重定向到控制器的show方法。该方法DownloadJob按其加载id并呈现其状态或进度。这部分非常简单。您可以遵循任何有关 CRUD 控制器和基本模型的指南。

注意:您此时什么都没有下载,因此processa 的 aDownloadJob将始终为空。

下一部分更有趣。创建a 后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之前不会返回。将始终显示100%。after_saveshowprocess

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

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

你得到什么: create 方法的即时返回。如果您观看该show方法(通过常规 javascript 调用),您会看到下载进度。多用户支持,多并行下载支持。

于 2013-09-18T08:22:16.800 回答