Error reading local file in Sinatra - ruby

I'm trying to write a Sinatra app that reads in a list from a file, and then spits back a random item from that list.
I'm having trouble figuring out the path to the file to read it, though. Sinatra says 'no such file or directory' when I try to load an item in my browser:
Errno::ENOENT at /wod
No such file or directory - http://localhost:4567/listing.txt
Here is the code:
require 'sinatra'
#list
get /item
puts read_list[rand(#list.size)]
end
def read_list
File.open('listing.txt', 'r').readlines
end
I have the file in /public, which the Sinatra README says is the default location for hosting static files. Furthermore, if I put it in /public I can navigate to localhost:4567/listing.txt and read the file in the browser.

A couple things I noticed:
get /item
isn't correct, it should be:
get '/item' do
If you start your code inside the same directory the Ruby code is in, the current working-directory will be ".", which is where Ruby will look when trying to:
File.open('listing.txt', 'r').readlines
Ruby will actually use './listing.txt' as the path. That's OK if you manually launch the code from the root directory of the application, but that doesn't work well if you try to launch it from anywhere else.
It's better to be explicit about the location of the file when you're actually trying to load something for use with a web server. Instead of relying on chance, there are a couple things you can do to help make it more bullet-proof. Consider this:
def read_list
running_dir = File.dirname(__FILE__)
running_dir = Dir.pwd if (running_dir == '.')
File.open(running_dir + '/public/listing.txt', 'r').readlines
end
File.dirname gets the path information from __FILE__, which is the absolute path and name of the current file running. If the application was started from the same directory as the file, that will be ., which isn't what we want. In that case, we want the absolute path of the current working-directory, which Dir.pwd returns. Then we can append that to the path of the file you want, from the root of the application.

You'll need to do File.read('public/listing.txt', 'r') to get what you want here.
File.open isn't part of Sinatra and doesn't know to look in a specific place for static files, so it just looks in the current working directory.

Related

"No such file or directory" error

I am working through the event_manager intro to ruby lessons, and need to load a file called
event_attendees.csv from my event_manager.rb.
I cannot figure out where to put the event_attendees.csv file. I know that it needs to go in the root directory but I cannot figure out where that is.
When I look at the Dir.pwd for my ruby document, I get:
C:/Ruby_Documents/event_manger/event_manager/lib
Does it matter that windows uses \ instead of / when I call the doc? This is where I am:
puts "EventManager initialized"
contents = File.read "event_attendees.csv"
puts contents
according to event_manager instructions you just need one event_manager directory and you need to put your event_attendees.csv in the same directory.

Reading files elsewhere in directory w/ Ruby

I've got a project structure as follows:
info.config (just a JSON file w/ prefs+creds)
main.rb
tasks/
test.rb
In both main.rb (at the root of the project), and test.rb (under the tasks folder), I want to be able to read and parse the info.config file. I've figured out how to do that in main.rb with the following:
JSON.parse(File.read('info.config'))
Of course, that doesn't work in test.rb.
Question: How can I read the file from a test.rb even though it's one level deeper in the hierarchy?
Appreciate any guidance I can get! Thanks!
Use relative path:
path = File.join(
File.dirname(File.dirname(File.absolute_path(__FILE__))),
'info.config'
)
JSON.parse(File.read(path))
File.dirname(File.absolute_path(__FILE__)) will give you the directory where test.rb resides. -> (1)
File.dirname(File.dirname(File.absolute_path(__FILE__))) will give you parent directory of (1).
Reference: File::absolute_path, File::dirname
UPDATE
Using File::expand_path is more readable.
path = File.expand_path('../../info.config', __FILE__)
JSON.parse(File.read(path))
What I usually do is:
Create file called environment or similar in your project root. This file has only one purpose - to extend load path:
require 'pathname'
ROOT_PATH = Pathname.new(File.dirname(__FILE__))
$:.unshift ROOT_PATH
Require this file at the beginning of your code. From now on every time you call require, you can use relative_path to you root directory, without worrying where file you are requiring it from is located.
When using File, you can simple do:
File.open(ROOT_PATH.join 'task', 'test.rb')
You can do as below using File::expand_path :
path = File.expand_path("info.config","#{File.dirname(__FILE__)}/..")
JSON.parse(File.read(path))
File.dirname(__FILE__) will give you the path as "root_path_of_your_projet/tasks/".
"#{File.dirname(__FILE__)}/.." will give you the path as "root_path_of_your_projet/". .. means go one level up from the current directory.
File.expand_path("info.config","root_path_of_your_projet/") will give you the actual path to the file as "root_path_of_your_projet/info.config".
You can also use __dir__ instead of File.dirname(__FILE__).
__dir__ : Returns the canonicalized absolute path of the directory of the file from which this method is called.
Hope that explanation helps.

Referencing file location in RSpec Rake task vs. rspec runner

I have this directory structure:
project_dir
spec
person
person_invalid_address_examples.yaml
person_spec.rb
rakefile.rb
The person_spec.rb has this piece of code in it:
describe "Create person tests"
...
context "Person with invalid address" do
invalid_address_examples = []
File.open("person_invalid_address_examples.yaml", "r") do |file|
invalid_address_examples = YAML::load(file)
end
invalid_address_examples.each do |example|
it "Does not allow to create person with #{example[:description]}" do
person.address = example[:value]
result = person.create
result.should_not be_success
end
end
end
...
end
Now when I run from the person directory rspec person_spec.rb everything works as expected. But if I run RSpec rake task from the rakefile I get No such file or directory error... The problem is obviously present also the other way round - if I configure filename with path relative to the rakefile location then RSpec rake task works fine but I get No such file or directory error from the rspec runner.. Is there a way to configure filename with path so that it is working for the RSpec rake task and Rspec runner at the same time?
Whether your File.open works depends on the load path -- ruby looks up that relative path in the dirs in the current load path. You can look at the load path in the special $: variable.
Try looking at the value of this variable compared between both methods of executing the spec, and see how/if it differs.
It may be that the current working directory (basically, what directory you executed the command from, shows up in a list of paths as .) is on the load path, and the current working directory ends up different in your two different methods of running the spec.
Where is your yaml file located? Is your YAML file used only for testing, can you put it wherever you want?
You have various options, but they all depend on supplying either an absolute path, or a relative path that will always be on the load path.
Move the yml file to somewhere that is always on the load path. Your spec dir is probably already on the load path. You can put your yml in ./spec/example.yml. Or put your yml in a subdir, but reference that subdir in the open too -- spec/support/data/examples.yml, and then open "data/examples.yml" (starting from a dir on the load path, data/examples.yml will resolve).
Or, ignoring the load path, you could use the special __FILE__ variable to construct the complete path to your yml file, from it's relative location to the current file. __FILE__ is the file path of the source file where the code referencing it is.
Or, probably better than 2, you could add a directory of example data to the load path in your spec_helper.rb, by constructing a path with __FILE__, and then adding it to the $: variable. For instance, a example_data directory.
Probably #1 is sufficient for your needs though. Put the yml inside your spec directory -- or put it in a subdir of your spec directory, but include that subdir in the open argument.
It's because of
File.open("person_invalid_address_examples.yaml", "r")
It opens the file where the rspec is running.
In your case you should define file more apparently something like this:
file_path = File.expand_path("person_invalid_address_examples.yaml", File.dirname(__FILE__))
File.open(file_path, "r")

passing arguaments to a system call in Rails, but not plain ruby program

Ubuntu 12.04
Sinatra 1.3.3
Why does passing an argument to a ruby system call (%x[] or ``) give me a 'not found' error in my sinatra app? The same code works fine in a normal ruby script running from the same directory.
I have a file test.rb like this
output = %x["ls"]
p output
When I run it with "ruby test.rb" I get the contents of the current directory in the console, as expected.
If I modify the program to give an argument to the system call like so:
output = %x["ls sub_dir/"]
p output
I get the contents of sub_dir, which sits in the current directory, as expected.
So far so good.
Now if I make a Sintra app with a post method:
require 'rubygems'
require 'bundler/setup'
require 'sinatra'
post "/" do
output = x["ls"]
return output
end
The response to a Post call to "/" returns the contents of the current directory, which includes 'sub_dir', as expected.
If I try to add the argument to the system call to the sinatra app like so:
require 'rubygems'
require 'bundler/setup'
require 'sinatra'
post "/" do
output = x["ls sub_dir/"]
return output
end
the response is nil and there is an error in the console:
sh: 1: ls sub_dir/: not found
Why does adding a parameter to a system call in my sinatra app cause it to crash, when the same code called from a plain ruby script, run from the same location works perfectly.
By the way, the 'ls' example shown here is not the command I really need to run, so please don't explain a different way to get this information. I have an executable file that takes a file name as a parameter that I need to run, which behaves exactly the same way.
Thanks in advance!
If you want to specify a path in relation to the application, you could use something like this:
post "/" do
path = File.join(File.dirname(__FILE__), "sub_dir")
%x[ls #{path}]
end
However, if you want to list the contents of a directory, why not do it in Ruby?
I rewrote the sinatra app in another file in the same directory.
Everything works as expected.
I did not find the reason and I deleted the original file so that I won't lose anymore time trying to figure it out.

Ruby: How to find path relative to where class was instantiated?

I am creating a gem that is a Rack application, so I assume my application is going to be instantiated in a config.ru file. I expect certain paths to be relative to this config.ru file. So how can I get and set the path when the app is initialized?
For example:
Hidden away in my gem:
class MyApp
def initialize
#base_path = get_the_base_path_here
end
def call(env)
html = render_view(#base_path + '/views/index.erb')
end
end
User of the gem's config.ru:
require 'my_app'
run MyApp.new
...and their views directory:
/views
index.erb
Update:
One way to achieve this is to pass in the base path as an argument, but I would like to find a way to achieve this without passing it as an argument.
require 'my_app'
run MyApp.new(File.dirname(__FILE__))
Absolute Path of Current File
In general, you can simply use File.expand_path(__FILE__) to find the absolute path of the current file, which you can then store a variable or global if you like. For example:
$file_path = File.expand_path(__FILE__)
Absolute Path of Current Program
File.expand_path($0) is similar, but returns the program that was called. The distinction is sometimes subtle, but can be useful from time to time.
Creating an Absolute Path to a File in the Same Base Directory
If you want to use the directory name of the location of the current file to address another file, you can use File#join. For example:
File.join File.dirname(File.expand_path(__FILE__)), '.X11-unix'
=> "/tmp/.X11-unix"
Probably not the best way but you can find config.ru with:
$:.find{|path| File.exists? "#{path}/config.ru"}

Resources