How to add a method to URI module - ruby

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?

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')

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.

Related

How to stub writing to file in minitest

I have the following piece of code which I want to test using minitest:
def write(text, fname)
File.open(fname, 'w') do |f|
f << text
end
end
I wanted to mock the file using StringIO, so I did:
class TestWrite << Minitest::Test
def test_write
# arrange
m_file = StringIO.new
mock_open = Minitest::Mock.new
mock_open.expect :call, m_file, ['fname', 'w']
# act
File.stub :open, mock_open do
write('hello!', 'fname')
end
# assert
mock_open.verify # successful assert!
assert_equal 'hello!', m_file.string # failure :(
end
The second assert seem to fail because the code inside the do block of File.open never runs. Any idea what I'm doing wrong?

ENOENT while trying to read a zipped file

I'm writing a simple program that takes an input string, splits it in words and saves it in it's memory. There's three methods -- to save a string into memory, to load from file and to load from zip archive. Here's the code:
require 'zip'
class Storage
def initialize
#storage = ''
end
def add(string)
words = string.split ','
words.each do |word|
#storage << "#{word},"
end
end
def load_from_file(filename)
File.open filename, 'r' do |f|
f.each { |line| add line }
end
end
def load_from_zip(filename)
Zip::File.open "#{filename}.zip" do |zipfile|
zipfile.each { |entry| load_from_file entry.to_s }
end
end
end
While add and load_from_file methods work perfectly as I expect, load_from_zip returns me the following error each time I try to run it:
storage.rb:39:in `initialize': No such file or directory # rb_sysopen - test.txt (Errno::ENOENT)
Although the file exists in my archive. I would appreciate any suggestion on what I'm doing wrong
Zip::File.open "#{filename}.zip"
does not extract the zip file, it just opens it and shows what's inside.
You cannot call
File.open filename, 'r'
since filename isn't in your filesystem, just inside the .zip file.
You'll need to add a new method :
require 'zip'
class Storage
def initialize
#storage = ''
end
def add(string)
words = string.split ','
words.each do |word|
#storage << "#{word},"
end
end
def load_from_file(filename)
File.open filename, 'r' do |f|
f.each { |line| add line }
end
end
def load_from_zip(filename)
Zip::File.open "#{filename}.zip" do |zipfile|
zipfile.each { |entry| load_from_zipped_file(zipfile,entry)}
end
end
private
def load_from_zipped_file(zipfile, entry)
zipfile.read(entry).lines.each do |line|
add line
end
end
end
s = Storage.new
s.load_from_zip('test')

In ruby, how to load raw source code to an instance of Proc?(add `before` `after` hook to class methods)

Normally, we can do this with Proc objects:
[15] pry(main)> pr = -> { puts "test message" }
=> #<Proc:0x000000057b8a90#(pry):12 (lambda)>
[16] pry(main)> pr.call
test message
=> nil
[17] pry(main)> define_method("test_method", pr)
=> :test_method
[18] pry(main)> test_method
test message
But, what if I have a raw code string and want to load it into a proc? Fake code below:
raw_code = "puts 'test message'"
pr = -> { load(raw_code) } # how to define the `load` method to get this working?
pr.call # => test message
define_method("test_method", pr}
test_method # => test message
Actually, my original problem is how to write a method hook like this:
class TestClass
def test_method
puts url
end
def test_method_a
puts url
end
before :test_method, :test_method_a do
url = __method__.split("_").join("/")
end
end
TestClass.new.test_method # => "test/method"
TestClass.new.test_method_a # => "test/method/a"
My problem is more complicated, this is just a simple example to illustrate the key problem.
how to define the load method to get this working?
By spelling it eval:
raw_code = "puts 'test message'"
pr = -> { eval(raw_code) } # how to define the `load` method to get this working?
pr.call # => test message
define_method("test_method", pr)
test_method # => test message
--output:--
test message
test message
Actually, my original problem is how to write a method hook...
class TestClass
def initialize
#url = %q{__method__.to_s.split("_").join("/")}
end
def test_method
puts(eval #url)
end
def test_method_a
puts(eval #url)
end
end
TestClass.new.test_method # => "test/method"
TestClass.new.test_method_a # => "test/method/a"
--output:--
test/method
test/method/a
Actually, my original problem is how to write a method hook like this:
Module TestClass
def test_method
puts url
end
The problem is that url can never refer to a value outside the def. A def cuts off the visibility of local variables outside the def.
class TestClass
def test_method
puts #url
end
def test_method_a
puts #url
end
def self.before(*method_names, &block)
method_names.each do |method_name|
alias_method "first_#{method_name}", method_name
define_method(method_name, block) #This changes __method__ inside the block from nil to method_name
alias_method "second_#{method_name}", method_name
define_method(method_name) do
send "second_#{method_name}" #This method executes: #url = __method__.to_s.split(...
send "first_#{method_name}" #This method executes: puts #url
end
end
end
before :test_method, :test_method_a do
#url = __method__.to_s.split("_").join("/")
end
end
TestClass.new.test_method # => "test/method"
TestClass.new.test_method_a # => "test/method/a"
--output:--
test/method
test/method/a
Short version: load loads code from a file. If you want to run code that you already have in a string, you can use eval, or one of it's friends, class_eval or instance_eval.
If you do end up using eval, however, you need to be very careful so that you won't accidentally run code that could delete your files, install malware or whatever.
Longer version: For a working version (in ruby 2.2.3) with load, you would need to put your TestClass class in a file:
class TestClass
def test_method
puts "OHAI"
end
end
Let's save this class in a file called "test_class.rb".
With that, the following should just work:
pr = -> { load(File.join(__dir__, "test_class.rb")) }
pr.call
TestClass.new.test_method
This will not solve your "original problem", but it should give you a little better understanding on how load works.
My own solution and more detail shown here after reading reply:
Maybe there is a lot of confusion about why does my problem method hook matter with load raw source code to Proc instance.
And also, I've solved my problem,now.
So,let me explain the whole thing in detail:
1, my original problem comes from that I need to extract a bunch of duplicated code at head of some methods of a module just like this:
module TestClass
extend self
def test_method
url = __method__.to_s.split("_").join("/") # duplicated code,need to extract
puts url
end
def test_method_a
url = __method__.to_s.split("_").join("/") # duplicated code, need to extract
puts url
end
2, Then, after a lot of thinking, I come up with an idea, that's get test_method test_method_a's source code, and modify the source code by adding url = __method__.to_s.split("_").join("/") at the head of it, then redefine the method with new code.
3, After lot of hack, I failed with using eval and then, post here asking help.
4, After reading reply, I make sure eval is definitely what I want.
5, Finally, I succeed, code show here:
module TestClass
extend self
def test_method
puts url
end
def test_method_a
puts url
end
[ :test_method, :test_method_a ].each do |name|
method_source_code_ar = instance_method(name).source.split("\n")
method_source_code_ar.insert(1, 'url = __method__.to_s.split("_").join("/")')
method_source_code = method_source_code_ar[1..-2].join("\n")
define_method(name, -> { eval(method_source_code) })
end
end
TestClass.test_method # => test/method
TestClass.test_method_a # => test/method/a
6, More concise code version show:
module TestClass
extend self
def test_method
puts url
end
def test_method_a
puts url
end
[ :test_method, :test_method_a ].each do |name|
method_source_code_ar = instance_method(name).source.split("\n")
method_source_code_ar.insert(1, 'url = __method__.to_s.split("_").join("/")')
method_source_code = method_source_code_ar.join("\n")
eval(method_source_code)
end
end
7, As eval,for me, I think the key to understand it is,thinking not as if you're in writing mode, but as if code was running line by line until reaching the eval line code, by that time, what do you want the code to do? Sure, just eval the method_source_code string.

What is the right way to create a ruby method to get the number of spaces in the start of the string without opening String Class

I'm creating a program to create folder structure from the text file.
In that I need to get the spaces that comes before any word in the given line.
How can I implement this functionality in ruby without opening String class?
I come from C# and it's very easy to extract methods in it. But I don't know how rubyists approach the same problem!
Here is the half of the program that I have written.
require "FileUtils"
#lines_array = []
def file_to_array
file = File.open("fs.txt", "r") do |file|
file.each_line do |line|
#lines_array << line.rstrip
#lines_array.reject! {|l| l.empty?}
end
end
end
def creation
#lines_array.each do |i|
if ( /(.+)\.(\w+)/ =~ i )
FileUtils.touch i
else
FileUtils.mkdir_p i
end
end
end
def count_space
beginning = s.length - s.lstrip.length
beginning
end
How can I extract logic from file_to_array method? And how can I implement and use count_space on elements of #lines_array?
here is the solution:
s = ' aaa ' #3 spaces in start and after `aa` there are two spaces
s[/\A */].size # for blank spaces in start of string
=> 3
s[/ *\z/].size # for blank spaces a the end of string
=> 2
So if I understand you correctly, you want to be able to call the #count_spaces method on your lines. Well there are three ways to approach this.
Open up the String class (what you don't want to do)
Create a class that inherits from Sting
Create a class that contains the string as an instance variable (this is probably your prefered method)
Open the class
class String
def count_spaces
#code here
end
end
Inherited class
class FileSystemLine < String
def count_spaces
#code here
end
end
Class with data in instance variable
class FileSystemLine
def initialize(line)
#line = line
end
def count_spaces
#code here
# make sure that code acts on #line
# like #line.length - #line.strip.length
end
# If you want to keep string functionalities, you could look up ruby delegation or use this simple method_missing method
def method_missing(method, *args, &block)
if #line.respond_to?(method)
#line.__send__(method, *args, &block)
else
super
end
end
end
Just remember to initialize the class.
Option 1. remains the same, option 2. and option 3. both look like this: line = FilSystemLine.new(line)
So your code would look like:
#lines_array = []
def file_to_array
file = File.open("fs.txt", "r") do |file|
file.each_line do |line|
#lines_array << FileSystemLine.new(line.rstrip)
#lines_array.reject! {|l| l.empty?}
end
end
end

How do I access a class instance variable across class of same module?

I need to access the config variables from inside another class of a module.
In test.rb, how can I get the config values from client.rb? #config gives me an uninitialized var. It's in the same module but a different class.
Is the best bet to create a new instance of config? If so how do I get the argument passed in through run.rb?
Or, am I just structuring this all wrong or should I be using attr_accessor?
client.rb
module Cli
class Client
def initialize(config_file)
#config_file = config_file
#debug = false
end
def config
#config ||= Config.new(#config_file)
end
def startitup
Cli::Easy.test
end
end
end
config.rb
module Cli
class Config
def initialize(config_path)
#config_path = config_path
#config = {}
load
end
def load
begin
#config = YAML.load_file(#config_path)
rescue
nil
end
end
end
end
test.rb
module Cli
class Easy
def self.test
puts #config
end
end
end
run.rb
client = Cli::Client.new("path/to/my/config.yaml")
client.startitup
#config is a instance variable, if you want get it from outside you need to provide accessor, and give to Easy class self object.
client.rb:
attr_reader :config
#...
def startitup
Cli::Easy.test(self)
end
test.rb
def self.test(klass)
puts klass.config
end
If you use ##config, then you can acces to this variable without giving a self object, with class_variable_get.
class Lol
##lold = 0
def initialize(a)
##lold = a
end
end
x = Lol.new(4)
puts Lol.class_variable_get("##lold")
I recommend to you read metaprogramming ruby book.

Resources