How do I find the path of a template file using ERB? - ruby

I am using embedded ruby (ERB) to generating text files. I need to know the directory of the template file in order to locate another file relative to the template file path. Is there a simple method from within ERB that will give me the file name and directory of the current template file?
I'm looking for something similar to __FILE__, but giving the template file instead of (erb).

When you use the ERB api from Ruby, you provide a string to ERB.new, so there isn’t really any way for ERB to know where that file came from. You can however tell the object which file it came from using the filename attribute:
t = ERB.new(File.read('my_template.erb')
t.filename = 'my_template.erb'
Now you can use __FILE__ in my_template.erb and it will refer to the name of the file. (This is what the erb executable does, which is why __FILE__ works in ERB files that you run from the command line).
To make this a bit a bit more useful, you could monkey patch ERB with a new method to read from a file and set the filename:
require 'erb'
class ERB
# these args are the args for ERB.new, which we pass through
# after reading the file into a string
def self.from_file(file, safe_level=nil, trim_mode=nil, eoutvar='_erbout')
t = new(File.read(file), safe_level, trim_mode, eoutvar)
t.filename = file
t
end
end
You can now use this method to read ERB files, and __FILE__ should work in them, and refer to the actual file and not just (erb):
t = ERB.from_file 'my_template.erb'
puts t.result

Related

Why is the "./" (dot + forward slash) needed in Ruby files when using the require keyword?

When attempting to place Ruby dependency files into a Ruby file, why is the "./" (dot + forward slash) necessary when typing out the file directory? Oddly enough it is only needed when using the require keyword and not the load keyword.
i.e.:
module.rb (the dependency)
module SomeModule
def someMethod
puts "hello"
end
end
method.rb
require "./module.rb"
#require "module.rb" does not work
class Animal
include SomeModule
end
class Person
include SomeModule
end
animal = Animal.new
animal.someMethod
person = Person.new
person.someMethod
#irb --> need to also write require " ./method.rb" to call it
If module.rb and method.rb are in the same directory, instead of using require you should use require_relative. Thus, the top of method.rb would look like this
require_relative 'module'
class Animal
It is only needed when using the require keyword and not the load
keyword. Why?
load, require and require_relative are all methods which take a filename as an argument that is to be loaded. They need to locate the filename passed an an argument
load
load checks for the file in the LOAD PATH which can be accessed using the global variable $LOAD_PATH or $: Even though the current working directory (designated by .) is NOT actually in the LOAD_PATH, the load method acts as if it is so is able to locate a file without explicitly appending the current directory to the file name passed as an argument
require
require is similar to load with two main differences
The first is that it's unaware about the current working directory. It does not add it to the LOAD PATH so once ruby searches it, it doesn't find the file. This is why you have to explicitly tell ruby about the current working directory and how to locate the file from the current directory using ./path_to_file.
If you don't want to add ./, you have to add the current directory to your Load Path
$: << '.'
require 'module.rb'
This will work as the current directory is now in the load path which is where ruby will search for the file.
The second main difference is that when you make multiple calls to require in a file passing it the same filename as an argument, the file will be required only the first time. Ruby keeps track of the files required. However with multiple calls to load with the same filename as an argument, the file is always loaded.
require 'time' => true
require 'time' => false #second call within the same file
require_relative
require_relative searches relative to the file in which the method call was executed which is why you don't need to alter the LOAD PATH by explicitly adding the current working directory

`__FILE__` not working within `DATA`/`__END__`

I'm working on Practicing Ruby's Self-Guided Course on Stream, File Formats, and Sockets, and came across the following problem in the pre-built test for the first exercise. The following test script is supposed to change the directory to the data subdirectory of the project folder:
eval(DATA.read) # load the test helper script
... # various calls to test method defined below
__END__
dir = File.dirname(__FILE__)
Dir.chdir("#{dir}/data")
...
But this breaks because __FILE__ returns (eval) (instead of the path to the file) and File.dirname(__FILE__) returns . Why is this happening, and how should it be written to yield the intended output instead?
__END__ and DATA aren't really relevant here. You're simply passing a string to Kernel#eval. For example, a simple eval('__FILE__') also returns "(eval)" because that's the default filename. It can be changed by passing another string but as third argument:
eval('__FILE__', nil, 'hello.rb') # => "hello.rb"
Or in your case:
eval(DATA.read, nil, __FILE__)

How to pass file url to helper method in middleman

I'm writing a helper method to convert images to base64 strings when needed. Below is the code
# config.rb
helpers do
def base64_url(img_link, file_type: "jpg")
require "base64"
if file_type =="jpg"
"data:image/jpg;base64,#{Base64.encode64(open(img_link).to_a.join)}"
elsif file_type =="png"
"data:image/jpg;base64,#{Base64.encode64(open(img_link).to_a.join)}"
else
link
end
end
end
In page.html.erb
<%= image_tag base64_url('/images/balcozy-logo.jpg') %>
Now the problem is when ruby reads '/images/balcozy-logo.jpg' it reads the file from system root not from the root of the project.
Error message as follows
Errno::ENOENT at /
No such file or directory # rb_sysopen - /images/balcozy-logo.jpg
How do I get around this and pass proper image url from project_root/source/images
In Middleman app.root returns the root directory of the application. There's also app.root_path, which does the same but returns a Pathname object, which is slightly more convenient:
full_path = app.root_path.join("source", img_link.gsub(/^\//, ''))
The gsub is necessary if img_link starts with a /, since it would be interpreted as the root of your filesystem.
I've taken the liberty of making a few more revisions to your method:
require "base64"
helpers do
def base64_url(path, file_type: "jpg")
return path unless ["jpg", "png"].include?(file_type)
full_path = app.root_path.join("source", path.gsub(/^\//, ''))
data_encoded = File.open(full_path, 'r') do |file|
Base64.urlsafe_encode64(file.read)
end
"data:image/#{file_type};base64,#{data_encoded}"
end
end
I've done a few things here:
Moved require "base64" to the top of the file; it doesn't belong inside a method.
Check file_type at the very beginning of the method and return early if it's not among the listed types.
Instead of open(filename).to_a.join (or the more succinct open(filename).read), use File.open. OpenURI (which supplies the open method you were using) is overkill for reading from the local filesystem.
Use Base64.urlsafe_encode64 instead of encode64. Probably not necessary but it doesn't hurt.
Remove the unnecessary if; since we know file_type will be either jpg or png we can use it directly in the data URI.
There may be a more elegant way to get file_path or determine the file's MIME type using Middleman's built-in asset system, but a very brief search of the docs didn't turn anything up.

Error reading local file in Sinatra

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.

My file is getting shorter and I don't know why

I have a requirement where I need to edit part of xml file and save it, but in my code some part of the xml file it not saving.I want to modify <mtn:ttl>4</mtn:ttl> to <mtn:ttl>9</mtn:ttl>, this part is getting modified in the below code but while writting/saving only part of file is getting chaged or the format of the file is getting chaged, can any one tell me how to solve this? original xml file size is 79kb but after editing and saving its becoming 78kb...
require "rexml/text"
require "rexml/document"
include REXML
File.open("c://conf//cad-mtn-config.xml") do |config_file|
# Open the document and edit the file
config = Document.new(config_file)
if testField.to_s.match(/<mtn:ttl>/)
config.root.elements[4].elements[11].elements[1].elements[1].elements[1].elements[8].text="9"
# Write the result to a new file.
formatter = REXML::Formatters::Default.new
File.open("c://mtn-3//mtn-2.2//conf//cad-mtn-config.xml", 'w') do |result|
formatter.write(config, result)
end
end
end
It looks like your trying to use regular expressions, why not just use rexml? The only requirement is that you need to know where the namespace is located online. Note if it were not mtn:ttl and just ttl you would not need the namespace.
require 'rexml/document'
file_path="path to file"
contents=File.new(file_path).read
xml_doc=REXML::Document.new(contents)
xml_doc.add_namespace('mtn',"http://url to mtn namespace")
xml_doc.root.elements.each('mtn:ttl') do |element|
element.text="9"
end
File.open(file_path,"w") do |data|
data<<xml_doc
end

Resources