Reputation: 15151
I want to download a binary file from http
or https
URL like:
URI('https://www.google.com/favicon.ico').download('google.ico')
I wrote the method for it like this:
module URI
def download(file)
File.open(file, 'wb') {|f| f.write(open(self).read)}
end
end
This method ends up with an error ArgumentError: extra arguments
, while following code is working.
file = 'google.ico'
url = 'https://www.google.com/favicon.ico')
File.open(file, 'wb') {|f| f.write(open(url).read)}
What am I doing wrong? How should I fix it?
Upvotes: 1
Views: 264
Reputation: 15151
I thought I'm calling Kernel.open
from open-uri
, but inside the module URI
OpenURI::OpenRead
is called.
At first I added binding.pry
module URI
def download(file)
binding.pry
File.open(file, 'wb') {|f| f.write(open(self).read)}
end
end
And checked which method is called like this:
pry(#<URI::HTTPS>)> show-method open
From: /Users/ironsand/.rbenv/versions/2.4.3/lib/ruby/2.4.0/open-uri.rb @ line 720:
Owner: OpenURI::OpenRead
Visibility: public
Number of lines: 3
def open(*rest, &block)
OpenURI.open_uri(self, *rest, &block)
end
pry(#<URI::HTTPS>)> exit
pry(main)> show-method open
From: /Users/ironsand/.rbenv/versions/2.4.3/lib/ruby/2.4.0/open-uri.rb @ line 29:
Owner: Kernel
Visibility: private
Number of lines: 11
def open(name, *rest, &block) # :doc:
if name.respond_to?(:open)
name.open(*rest, &block)
elsif name.respond_to?(:to_str) &&
%r{\A[A-Za-z][A-Za-z0-9+\-\.]*://} =~ name &&
(uri = URI.parse(name)).respond_to?(:open)
uri.open(*rest, &block)
else
open_uri_original_open(name, *rest, &block)
end
end
To use proper method I should have called the method explicitly.
module URI
def download(file)
File.open(file, 'wb') {|f| f.write(OpenURI.open_uri(self).read)}
end
end
The code above works as I expected.
Upvotes: 1
Reputation: 11206
URI module doesn't download file. File class doesn't either. open
comes from open-uri
stdlib which is why it works in your 2nd example. If you have curl
in your system this should work:
module URI
def self.download(file_url)
filename = file_url.split('/').last
`curl -O #{file_url}`
end
end
If you DON'T have curl
use open-uri
require 'open-uri'
module URI
def self.download(file_url)
filename = file_url.split('/').last
File.open(filename, "wb") do |saved_file|
open(file_url, "rb") do |read_file|
saved_file.write(read_file.read)
end
end
end
end
And call it like this
URI.download('https://www.google.com/favicon.ico')
Note, URI behaves like more like a class so you need to define the method on the base object self
otherwise you'll need to create an instance, but since it's just a module, use def self.some_method(some_arg)
to be able to call URL.some_method(some_arg)
While this works, it is not recommended for production. Why do you wanna monkey patch URI when you can simply write your own module which does this?
You're better off doing something like this:
module SimpleFileDownload
require 'open-uri'
def self.download(file_url)
filename = file_url.split('/').last
File.open(filename, "wb") do |saved_file|
open(file_url, "rb") do |read_file|
saved_file.write(read_file.read)
end
end
end
end
and call it like:
SimpleFileDownload.download('https://www.google.com/favicon.ico')
Upvotes: 1