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.