Opening relative paths from gem - ruby

I'm writing a simple gem that can load from and save data to text files and zip archives. So, it has four methods: load_from_file, load_from_zip, save_to_file and save_to_zip respectfully. The problem is that I can't figure out how to specify relative paths for loading and saving for these methods. Here they go:
def load_from_file(filename)
File.open(filename) do |f|
f.each { |line| add(line) } # `add` is my another class method
end
end
def load_from_zip(filename)
Zip::File.open("#{filename}.zip") do |zipfile|
zipfile.each { |entry| read_zipped_file(zipfile, entry) } # my private method
end
end
def save_to_file(filename)
File.write("#{filename}.txt", data)
end
def save_to_zip(filename)
Zip::File.open("#{filename}.zip", Zip::File::CREATE) do |zipfile|
zipfile.get_output_stream('output.txt') { |f| f.print data }
end
end
private
def read_zipped_file(zipfile, entry)
zipfile.read(entry).lines.each { |line| add(line) }
end
So what I want basically is to allow this gem to load and save files by relative paths whereever it is used in system, e.g. I have an app located in /home/user/my_app with two files - app.rb and data.txt, and I could be able to read the file from this directory without specifying absolute path.
Example:
# app.rb
require 'my_gem'
my_gem = MyGem.new
my_gem.load_from_file('data.txt')
(Sorry for bad English)
UPD: This is not Rails gem and I'm not using Rails. All this is only pure Ruby.

Short answer
If I understand it correctly, you don't need to change anything.
Inside app.rb and your gem, relative paths will be understood relatively to Dir.pwd.
If you run ruby app.rb from inside /home/user/my_app :
Dir.pwd will be /home/user/my_app
both app.rb and my_gem will look for 'data.txt' inside /home/user/my_app.
Useful methods, just in case
Dir.chdir
If for some reason Dir.pwd isn't the desired folder, you could change directory :
Dir.chdir('/home/user/my_app') do
# relative paths will be based from /home/user/my_app
# call your gem from here
end
Get the directory of current file :
__dir__ will help you get the directory :
Returns the canonicalized absolute path of the directory of the file
from which this method is called.
Get the current file :
__FILE__ will return the current file. (Note : uppercase.)
Concatenate file paths :
If you need to concatenate file paths, use File.expand_path or File.join. Please don't concatenate strings.
If you don't trust that the relative path will be correctly resolved, you could send an absolute path to your method :
my_gem.load_from_file(File.expand_path('data.txt'))

Related

How to include code from a file in Ruby?

I'm new to Ruby and I'm figuring out how to include a file in a script. I tried require and include but it doesn't work.
Here's the file I'd like to include, worth noting that it's not a module.
# file1.rb
if Constants.elementExist(driver, 'Allow') == true
allowElement = driver.find_element(:id, 'Allow')
allowElement.click()
sleep 1
wait.until {
driver.find_element(:id, 'Ok').click()
}
username = driver.find_element(:id, 'UsernameInput')
username.clear
username.send_keys Constants.itech_email
password = driver.find_element(:id, 'PasswordInput')
password.clear
password.send_keys Constants.itechPass
driver.find_element(:id, 'Login').click
else
username = driver.find_element(:id, 'UsernameInput')
username.clear
username.send_keys Constants.itech_email
password = driver.find_element(:id, 'PasswordInput')
password.clear
password.send_keys Constants.itechPass
driver.find_element(:id, 'Login').click
end
That file contains several lines of codes that are reusable or repeatable in my case. It's not inside a class or a module. It's a straightforward Ruby script, and I'd like to use this on my second script inside a module.
# file2.rb
module File2
module IOS
# include file1.rb
end
end
This way, it should just run the code of file1.rb inside file2.rb.
How can I do this in Ruby?
According to the Ruby docs, require has the following behavior:
If the filename does not resolve to an absolute path, it will be
searched for in the directories listed in $LOAD_PATH ($:).
Which means that in order to run file1.rb's code inside file2.rb, where both files are in the exact same folder, you'd have to do the following:
# file2.rb
module File2
module IOS
# Absolute path to file1.rb, adding '.rb' is optional
require './file1'
end
end
use require_relative:
require_relative 'file1.rb'

Testing input/output with rspec and plain ruby

I am trying to create a test for a FileProcessor that reads from a text file, passes it to another class and then writes output. I made a test file and am able to access but it feels bulky. I'm also going to need to test that it writes the output in a new file and I am not sure how to set this up. I've seen a lot of tutorials but they are be rails centric. My goal is to get rid of writing the path in the test and to clean up the generated output files after each test.
describe FileProcessor do
test_file = File.dirname(__FILE__) + '/fixtures/test_input.txt'
output_file = File.dirname(__FILE__) + '/fixtures/test_output.txt'
subject {FileProcessor.new(test_file, output_file)}
describe '#read_file' do
it 'reads a file' do
expect(subject.read_file).to eq('This is a test.')
end
end
def write_file(str)
File.open("#{output_file}", "w+") { |file| file.write(str) }
end
end
How about using StringIO:
require 'stringio'
class FileProcessor
def initialize(infile, outfile)
#infile = infile
#outfile = outfile
#content = nil
end
def read_file
#content ||= #infile.read
end
def write_file(text)
#outfile.write(text)
end
end
describe FileProcessor do
let(:outfile) { StringIO.new }
subject(:file_processor) do
infile = StringIO.new('This is a test')
FileProcessor.new(infile, outfile)
end
describe '#read_file' do
it "returns correct text" do
expect(file_processor.read_file).to eq("This is a test")
end
end
describe '#write_file' do
it "writes correct text" do
file_processor.write_file("Hello world")
outfile.rewind
expect(outfile.read).to eq("Hello world")
end
end
end
There's not a great way to avoid writing the path of your input file. You could move that into a helper method, but on the other hand having the path in the test has the benefit that someone else (or you six months from now) looking at the code will know immediately where the test data comes from.
As for the output file, the simplest solution is to use Ruby's built-in Tempfile class. Tempfile.new is like File.new, except that it automatically puts the file in /tmp (or wherever your OS's temporary file directory is) and gives it a unique name. This way you don't have to worry about cleaning it up, because the next time you run the test it'll use a file with a different name (and your OS will automatically delete the file). For example:
require 'tempfile'
describe FileProcessor do
let(:test_file_path) { File.dirname(__FILE__) + '/fixtures/test_input.txt' }
let(:output_file) { Tempfile.new('test_output.txt').path }
subject { FileProcessor.new(test_file_path, output_file.path) }
describe '#read_file' do
it 'reads a file' do
expect(subject.read_file).to eq('This is a test.')
end
end
end
Using let (instead of just assigning a local variable) ensures that each example will use its own unique output file. In RSpec you should almost always prefer let.
If you want to get really serious, you could instead use the FakeFS gem, which mocks all of Ruby's built-in file-related classes (File, Pathname, etc.) so you're never writing to your actual filesystem. Here's a quick tutorial on using FakeFS: http://www.bignerdranch.com/blog/fake-it/

config location ruby and using rake for unit test

Learning Ruby, my Ruby app directory structure follows the convention
with lib/ and test/
in my root directory I have a authentication config file, that I read from one of the classes in lib/. It is read as File.open('../myconf').
When testing with Rake, the file open doesn't work since the working directory is the root, not lib/ or test/.
In order to solve this, I have two questions:
is it possible, and should I specify rake working directory to test/ ?
should I use different file discovery method? Although I prefer convention over config.
lib/A.rb
class A
def openFile
if File.exists?('../auth.conf')
f = File.open('../auth.conf','r')
...
else
at_exit { puts "Missing auth.conf file" }
exit
end
end
test/testopenfile.rb
require_relative '../lib/A'
require 'test/unit'
class TestSetup < Test::Unit::TestCase
def test_credentials
a = A.new
a.openFile #error
...
end
end
Trying to invoke with Rake. I did setup a task to copy the auth.conf to the test directory, but turns out that the working dir is above test/.
> rake
cp auth.conf test/
/.../.rvm/rubies/ruby-1.9.3-p448/bin/ruby test/testsetup.rb
Missing auth.conf file
Rakefile
task :default => [:copyauth,:test]
desc "Copy auth.conf to test dir"
task :copyauth do
sh "cp auth.conf test/"
end
desc "Test"
task :test do
ruby "test/testsetup.rb"
end
You're probably getting that error because you're running rake from the projects root directory, which means that the current working directory will be set to that directory. This probably means that the call to File.open("../auth.conf") will start looking one directory up from your current working directory.
Try specifying the absolute path to the config file, for example something like this:
class A
def open_file
path = File.join(File.dirname(__FILE__), "..", "auth.conf")
if File.exists?(path)
f = File.open(path,'r')
# do stuff...
else
at_exit { puts "Missing auth.conf file" }
exit
end
end
Btw, I took the liberty of changing openFile -> open_file, since that's more consistent with ruby coding conventions.
I recommend using File.expand_path method for that. You can evaluate auth.conf file location based on __FILE__(current file - lib/a.rb in your case) or Rails.root depending on what you need.
def open_file
filename = File.expand_path("../auth.conf", __FILE__) # => 'lib/auth.conf'
if File.exists?(filename)
f = File.open(filename,'r')
...
else
at_exit { puts "Missing auth.conf file" }
exit
end
end

Create a file in a specified directory

How can I create a new file in a specific directory. I created this class:
class FileManager
def initialize()
end
def createFile(name,extension)
return File.new(name <<"."<<extension, "w+")
end
end
I would like to specify a directory (path) where to create the file. If this one doesn't exist, he will be created. So do I have to use fileutils as shown here just after file creation or can I specify directly in the creation the place where create the file?
Thanks
The following code checks that the directory you've passed in exists (pulling the directory from the path using File.dirname), and creates it if it does not. It then creates the file as you did before.
require 'fileutils'
def create_file(path, extension)
dir = File.dirname(path)
unless File.directory?(dir)
FileUtils.mkdir_p(dir)
end
path << ".#{extension}"
File.new(path, 'w')
end

ruby method zip trying to get a string of zips I made

I have a method that zips up files I pass in.
require 'zip/zip'
def zipup(aname, aloc="/tmp/")
Zip::ZipFile.open "#{aloc}"+File.basename(aname)+".zip", Zip::ZipFile::CREATE do |zipfile|
zipfile.add File.basename(aname), aname
end
end
I need to get a string object or array object from this method that has the archive.zip name of every file that has been compressed.
rubyzip does have a to_s method all though I have failed in getting the syntax correct.
http://rubyzip.sourceforge.net/classes/Zip/ZipEntry.html#M000131
thanks from a new rubyist.
Welcome Joey, do you use the 'zip/zip' gem or just 'zip' ? If you require something, better add it to the question next time. This gem needs some extra documentation and methods it seems to me.
This works
require 'zip' #or 'zip/zip' both work
def zip_list(filename)
zipfile = Zip::ZipFile.open(filename)
list = []
zipfile.each { |entry| list << entry.name }
list
end
puts zip_list("c:/temp/zip1.zip")
another way
require 'zip/zip'
Zip::ZipFile.open("c:/temp/zip1.rb.zip") do |zipfile|
zipfile.entries.each do |entry|
puts entry.name
end
end

Resources