Ruby FileUtils.mkdir_p is only creating parent directories - ruby

I have a controller in Rails, with an action that is meant to create a new directory.
This action should create the directory "/public/graph_templates/aaa/test". However, it leaves off the final directory "test". Why is this only creating parent directories?
def create_temporary_template
dir = File.dirname("#{Rails.root}/public/graph_templates/aaa/test")
FileUtils.mkdir_p dir
end
Docs: http://ruby-doc.org/stdlib-1.9.3/libdoc/fileutils/rdoc/FileUtils.html#method-c-mkdir_p

Because you use dir = File.dirname("#{Rails.root}/public/graph_templates/aaa/test"),
then the dir is "#{Rails.root}/public/graph_templates/aaa".
You could just pass the path to FileUtils.mkdir_p.
def create_temporary_template
dir = "#{Rails.root}/public/graph_templates/aaa/test"
FileUtils.mkdir_p dir
end

The problem is in your use of dirname:
File.dirname("/foo/bar")
# => "/foo"
dirname removes the last entry from the path. Per the documentation:
Returns all components of the filename given in file_name except the last one.
Usually that's the correct thing if your path contains a directory, or directory hierarchy, with the filename:
File.dirname("/foo/bar/baz.txt")
# => "/foo/bar"
But, in this case it's chopping off your desired trailing directory.
I'd recommend taking a look at the Pathname class that is included in Ruby's Standard Library. It wraps File, Dir, FileUtils, FileTest, and probably a Swiss-Army knife and kitchen sink into one class, making it very convenient to work on files and directories with one class.
require 'pathname'
dir = Pathname.new("/foo/bar/baz.txt")
# => "/foo/bar"
dir.mkpath # would create the path
I've found Pathname to be very useful, though it's still young.

Related

Why are Ruby's file related types string-based (stringly typed)?

e.g. Dir.entries returns an array of strings vs an array containing File or Dir instances.
Most methods on Dir and File types. The instances are aneamic in comparison.
There is no Dir#folders or Dir#files - instead I explicitly
loop over Dir.entries
build the path (File.expand_path) for
each item
check File.directory?
Simple use-cases like get all .svg files in this directory seem to require a number of hoops/loops/checks. Am I using Ruby wrong or does this facet of Ruby seem very un-ruby-ish?
Depending on your needs, File or Dir might do just fine.
When you need to chain commands and (rightfully) think it feels un-ruby-ish to only use class methods with string parameters, you can use Pathname. It is a standard library.
Examples
Dirs and Files
require 'pathname'
my_folder = Pathname.new('./')
dirs, files = my_folder.children.partition(&:directory?)
# dirs is now an Array of Pathnames pointing to subdirectories of my_folder
# files is now an Array of Pathnames pointing to files inside my_folder
All .svg files
If for some reason there might be folders with .svg extension, you can just filter the pathnames returned by Pathname.glob :
svg_files = Pathname.glob("folder/", "*.svg").select(&:file?)
If you want a specific syntax :
class Pathname
def files
children.select(&:file?)
end
end
aDir = Pathname.new('folder/')
p aDir.files.find_all{ |f| f.extname == '.svg' }
Iterating the Directory tree
Pathname#find will help.
Until you open the file it is just a path (string).
To open all .svg files
svgs = Dir.glob(File.join('/path/to/dir', '*.svg'))
On windows case doesn't matter in file paths, but in all unixoid systems (Linux, MacOS...) file.svg is different from file.SVG
To get all .svg files and.SVG files you need File::FNM_CASEFOLD flag.
If you want to get .svg files recursively, you need **/*.svg
svgs = Dir.glob('/path/to/dir/**/*.svg', File::FNM_CASEFOLD)
If you expect directories ending in.svg then filter them out
svgs.reject! { |path| File.directory?(path) }

Ruby FileUtils.mv: Error file not found

The following piece of code should iterate over a directory list replacing the old directory name with the new one. However, the FileUtils.mv call returns no such file or directory.
I have added the line File.exists? which returns true for all paths passed to it via this loop
Dir["projects/*/*/old"].each{|dir|
Dir.chdir dir
Dir.chdir "../"
puts File.exists?("#{Dir.pwd }/old")
FileUtils.mv "#{Dir.pwd }/old", "#{Dir.pwd }/new_path"
}
Any thoughts would be greatly appreciated.
It seems that you have a folder match as well. Because, File.exists? 'folder' returns true as well.
See if you have any folders with the name old.
# This will list all directories with a name "old"
find . -name old -type d

Finding a path without the file name

I'm attempting to create a a program that will give the full path to a file. This can be done with File.absolute_path, but it also adds the file name to the path. For example,
def test
path = File.absolute_path("test.rb")
puts path
end
#=> C:/users/james/myscripts/test/test.rb
I need to exclude the last part /test.rb so that the path would only contain: C:/users/james/myscripts/test. Is there a way to do this?
File.dirname will return the directory part of the path:
File.dirname(File.absolute_path("test.txt"))
# => C:/users/james/myscripts/test
If File.absolute_path("test.txt") gives the absolute path, and you want the directory of it, then that means that you just want the current directory. That is given by:
Dir.pwd

Keeping track of current directory per user

I am currently creating a client/server application which is trying to keep track of multiple connected users current directories by way of pairing their unique identifier (username), and a new Dir object to an array of hashes like so:
users = []
user = {:user => "userN", :dir => Dir.new(".")}
users.push(user)
...
Although when accessing the dir key within the users hash, I can't seem to use the objects methods properly.
For example:
users[0][:dir].chdir("../")
Returns undefined methodchrdirfor #<Dir:.>
Likewise the method entries which is supposed to accept 1 argument for listing the contents of a directory, only accepts 0 arguments, and when called with 0 arguments it only lists the current directory initialized when Dir was created.
Is there a simple way to keep track of a user's pseudo location within the filesystem?
Edit:: I found the Pathname class and it sort of implements what I need. I am just wondering now if there is a cleaner way to implementing the cd and ls commands when using it.
#Simulate a single users default directory starting point
$dir = Pathname.pwd
#Create a backup of the current directory, change to new directory,
#test to see if the directory exists and if not return to the backup
def cd(dir)
backup = $dir
$dir += dir
$dir = backup if !($dir.directory?)
end
#Take the array of Pathname objects from entries and convert them
#to their string directory values and return the sorted array
def ls(dir)
$dir.entries.map { |pathobject| pathobject.to_s }.sort
end
Your problem actually isn't using a hash incorrectly, it's that Dir.chdir is a global method that changes the working directory of the current process. Dir.entries is similar.
If you're trying to keep track of a path on a per user basis, you could store it as a File, which can also be a directory. That is, directories are represented as a File, so even though it's called "file", it can still store a directory path.
The answer to my question as I've found out is to use the Pathname class: Pathname
It allows you to use the += operator to transverse the file system, although you will have to manually implement many checks to make sure where you are going to transverse to actually exists.
When I implemented my ls command I simply mapped the output of Pathname.entries, and sorted the results.
def ls(pathname)
pathname.entries.map { |pathobject| pathobject.to_s }.sort
end
This gave you an array of sorted strings of all the files in the current directory that Pathname is set to.
For cd you need to make sure the directory exists and if not revert to the previously good directory.
def cd(pathname, directory_to_move_to)
directory_backup = pathname
pathname += directory_to_move_to
pathname = directory_backup if !(pathname.directory?)
end
Example usage:
my_pathname = Pathname.pwd
cd(my_pathname, "../")
ls(my_pathname)

How to get the next and previous directory full path name without changing the current directory?

I have tried the following which helped me to see the 1-level down directory path:
E:\WIPData\Ruby\Scripts>irb
irb(main):001:0> Dir.pwd
=> "E:/WIPData/Ruby/Scripts"
irb(main):004:0> Dir.pwd.gsub(/\/Scripts/,'')
=> "E:/WIPData/Ruby"
irb(main):005:0>
Is there a way to get the the directory full path 1-level up and 1-level down, from the current directory without changing it?
File structure
=================
Dir-> E:\WIPData\
|
(E:\WIPData\Ruby)
|
--------------------------------------------------
| | | | |
(xxx) (yyyy) (zzzz) (pppp) (E:\WIPData\Ruby\Scripts) <= It is PWD
you can find first subDirectory in your current directory this way:
Dir.glob('*').find { |fn| File.directory?(fn) }
allthough, it's not uniquely defined, as someone said.
and first parent directory this way:
File.expand_path("..", Dir.pwd)
HTH
To get the parent directory from a path, use:
File.dirname('/path/to/a/file.txt')
=> "/path/to/a"
There isn't a way to get "the" child directory, unless there is only one, because file systems don't have a concept of a default sub-directory. If there is only one, it's an obvious choice to you, but not to your code. To get a list of the sub-directories only:
Dir.entries('.').select{ |e| File.directory?(e) }
That will return the child directories under '.' (AKA the current directory) as an array, which will be ['.', '..'] at a minimum, meaning the current and parent directories respectively. For instance, in the current directory my pry instance is running in, I get back:
[".", "..", ".svn", "old"]
as the list of available directories. Which is the default? I could do this:
Dir.entries('.').select{ |e| File.directory?(e) && !e[/^\./] }
=> ["old"]
which returns the only "visible" directory, i.e., it isn't a "hidden" directory because it doesn't start with '.'. That isn't the default, because, as I said, the file system has no "default" child directory concept. In another directory I'd probably see many directories returned, so I'd have to specify which to descend into, or use for file access.
Ruby has a nice suite of File and Dir tools, plus the Find class, so read through their documentation to see what you can do.

Resources