简体   繁体   中英

How do I share variables between methods in a controller in Rails 4?

I'm trying to learn Ruby on Rails, so one of the projects I am doing is developing an app where you enter a URL, click submit, the server starts downloading the file from that URL, and then you see a page showing you the details of the download (periodically updated from the server). I haven't gotten my head around some of the idiosyncrasies of instance variables vs. class variables in Ruby, and I have done something horrible:

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

First of all, I wasn't able to get it to call the updateProgress method, it kept going to "show", and passing "updateProgress" as the parameter "id". Instead of fiddling too much with this, I just hijacked the "new" method and had my jQuery call that every time it wants an update on the download status. Forgive me for this, I probably bit off more than I can chew before learning the basics.

Secondly, only one person can use this webapp at a time, because I had to use class variables instead of instance variables. If I used instance variables then they would be nil by the time one method looked at a value another method was supposed to have set. Reading up on why that is, I think I get the idea, but what is the solution? Is there an easy way to share values between methods in a controller in rails? I've found a similar question on here where the answers recommended doing the calculations in the model, but would that work for my purposes as well?

Yes, it's easy to get into quite a mess with Ruby on Rails. It's sometimes even called 'going off the rails'. The problem is that if you don't spend some time getting 'back on the rails', you train isn't going anyway anytime soon. Those train wheels don't run so well in the mud ;)

Once you find yourself doing things like 'hijacking the "new" method' and "class variables instead of instance variables" then you've gone off the rails.

I would start again with what you are trying to do. When I've been in your situation I've tried 'fixing' it but it tends to get worse, not better!

So I would start again, this time try harder to stick with standards. Use REST for your routes and controller methods - 'update', not updateProgress. I would suggest you actually use the rails generators to generate controllers and models. Also make sure you have got the app working the standard way without ajax before adding it. Not sure if you had.

You can change things like 'kept going to show page' with a better redirect_to after save. See api examples. Sorry if this is not the immediate answer you were looking for but I think it's a valid long-term answer :)

Generators: http://guides.rubyonrails.org/command_line.html#rails-generate

Routes: http://guides.rubyonrails.org/routing.html # This lets you define a resource and have standard controller methods.

You may want to use scaffolding also when starting out which will layout all the RESTful stuff for you. There's a great post on that here: http://viget.com/extend/rails-3-generators-scaffolding

The problem with the code above is that you try to put too much logic in the controller.

Your controller's create should just create a DownloadJob with an URL. Nothing more, just create the job and store it in the database. If done, redirect the user to the controller's show method. That method loads the DownloadJob by its id and renders its status or progress. This part is very simple. You can follow any guide about CRUD controllers and basic model.

Note: You download nothing in the moment, therefore the process of a DownloadJob would always be empty.

The next part is more interesting. After a DownloadJob is created it should start the download itself. No need to have the controller involved. While downloading the DownloadJob it can update itself with its current 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

Advice to read: http://guides.rubyonrails.org/active_record_callbacks.html

In the moment the download of the web resource would block your controller because new does not return before the after_save callback has finished. show would always display process of 100%.

To avoid blocking the controller, process the download in the background. Use delayed_jobs or a similar gem to do so. This also allows to download multiple URLs at the same time, just start more DelayedJob workers if needed.

Advice to read: https://github.com/collectiveidea/delayed_job/

What you get: An Instant return of the create method. If you watch the show method (via regular javascript calls) you see the progress of downloading. Multi user support, multi parallel download support.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM