It looks quite verbose to write something as this "#{file_name}" instead of just file_name.
def write_hello(file_name)
File.open("#{file_name}", "w") { |file| file.puts "Hello, world! I'm Ruby :)"}
end
What can I do to make it cleaner?
Updated
I just have ruby naming conventions, so I updated the fileName to file_name now.
That's needlessly verbose.
x = "test"
"#{x}"
# => "test"
It's literally the same thing in your case.
A Ruby idiomatic version of this code looks like:
def write_hello(filename)
File.open(filename, "w") do |file|
file.puts "Hello, world! I'm Ruby :)"
end
end
This uses the multi-line do ... end form which is often a lot easier to follow and understand but is otherwise equivalent to the { ... } form. New Ruby people may be a little perplexed at how { x: 'y' } and { |x| 'y' } are dramatically different things, so this avoids the ambiguity there.
It's not necessary to enquote something in isolation. That would be necessary if you wanted to append a file extension, like:
File.open("#{filename}.txt", "w") do |file|
# ...
end
Where you'd call that like:
write_hello(:example)
And that would create example.txt
Your code works, but is actually bad code. Indeed it should be written as fileName, although a Rubyist would not name a variable like that in camel case.
However, it would be better to write shorter, like
File.write(fileName, "Hello, world! I'm Ruby :)")
although you would have to explicitly add a new line character at the end if the string if you want one, as Stefan notes.
Related
This is a part of my file:
project(':facebook-android-sdk-3-6-0').projectDir = new File('facebook-android-sdk-3-6-0/facebook-android-sdk-3.6.0/facebook')
project(':Forecast-master').projectDir = new File('forecast-master/Forecast-master/Forecast')
project(':headerListView').projectDir = new File('headerlistview/headerListView')
project(':library-sliding-menu').projectDir = new File('library-sliding-menu/library-sliding-menu')
I need to extract the names of the libs. This is my ruby function:
def GetArray
out_file = File.new("./out.txt", "w")
File.foreach("./file.txt") do |line|
l=line.scan(/project\(\'\:(.*)\'\).projectDir/)
File.open(out_file, "w") do |f|
l.each do |ch|
f.write("#{ch}\n")
end
end
puts "#{l} "
end
end
My function returns this:
[]
[["CoverFlowLibrary"]]
[["Android-RSS-Reader-Library-master"]]
[["library"]]
[["facebook-android-sdk-3-6-0"]]
[["Forecast-master"]]
My problem is that I find nothing in out_file. How can I write to a file? Otherwise, I only need to get the name of the libs in the file.
Meditate on this:
"project(':facebook-android-sdk-3-6-0').projectDir'".scan(/project\(\'\:(.*)\'\).projectDir/)
# => [["facebook-android-sdk-3-6-0"]]
When scan sees the capturing (...), it will create a sub-array. That's not what you want. The knee-jerk reaction is to flatten the resulting array of arrays but that's really just a band-aid on the code because you chose the wrong method.
Instead consider this:
"project(':facebook-android-sdk-3-6-0').projectDir'"[/':([^']+)'/, 1]
# => "facebook-android-sdk-3-6-0"
This is using String's [] method to apply a regular expression with a capture and return that captured text. No sub-arrays are created.
scan is powerful and definitely has its place, but not for this sort of "find one thing" parsing.
Regarding your code, I'd do something like this untested code:
def get_array
File.new('./out.txt', 'w') do |out_file|
File.foreach('./file.txt') do |line|
l = line[/':([^']+)'/, 1]
out_file.puts l
puts l
end
end
end
Methods in Ruby are NOT camelCase, they're snake_case. Constants, like classes, start with a capital letter and are CamelCase. Don't go all Java on us, especially if you want to write code for a living. So GetArray should be get_array. Also, don't start methods with "get_", and don't call it array; Use to_a to be idiomatic.
When building a regular expression start simple and do your best to keep it simple. It's a maintainability thing and helps to reduce insanity. /':([^']+)'/ is a lot easier to read and understand, and accomplishes the same as your much-too-complex pattern. Regular expression engines are greedy and lazy and want to do as little work as possible, which is sometimes totally evil, but once you understand what they're doing it's possible to write very small/succinct patterns to accomplish big things.
Breaking it down, it basically says "find the first ': then start capturing text until the next ', which is what you're looking for. project( can be ignored as can ).projectDir.
And actually,
/':([^']+)'/
could really be written
/:([^']+)'/
but I felt generous and looked for the leading ' too.
The problem is that you're opening the file twice: once in:
out_file = File.new("./out.txt", "w")
and then once for each line:
File.open(out_file, "w") do |f| ...
Try this instead:
def GetArray
File.open("./out.txt", "w") do |f|
File.foreach("./file.txt") do |line|
l=line.scan(/project\(\'\:(.*)\'\).projectDir/)
l.each do |ch|
f.write("#{ch}\n")
end # l.each
end # File.foreach
end # File.open
end # def GetArray
I started learning Chef to manage our servers and I stumbled in a very weird (in my opinion) behavior in Ruby. I do not know Ruby so this might be just a misunderstanding from my part.
The error I was getting was
`delete': Permission denied - [some path]/metadata.json (Errno::EACCES)
Since I knew for sure that it was not actually about permissions, the next logical thing was to check for file locking. After digging a bit through the relevant code, I discovered that there is a method that produces a checksum for each file.
load_root
file_metadata
checksum
md5_checksum_for_file
generate_md5_checksum_for_file
checksum_file
def checksum_file(file, digest)
File.open(file, 'rb') { |f| checksum_io(f, digest) }
end
def checksum_io(io, digest)
while chunk = io.read(1024 * 8)
digest.update(chunk)
end
digest.hexdigest
end
Having found that, I searched a bit and found an answer about closing files in Ruby and it seemed that the code was actually fine... but it was not. I tried to change the method to the
"block format" and it worked without error:
def checksum_file(file, digest)
File.open(file, 'rb') do |f|
checksum_io(f, digest)
end
end
Can someone please explain the difference between the two versions of the code?
-- Edit --
It seems that this problem occurs only in Windows and maybe only when using the ruby provided by ChefDK 0.3.0 :
ruby 2.0.0p451 (2014-02-24) [i386-mingw32]
Answer of your question
Can someone please explain the difference between the two versions of the code?
Block always return something so do end and and { ... } dose't really matter This is just personnel programming preference.
There are two different convention with block i'm going to dispense
to you now and it up to you which religion you want to subscribe to.
First religion says that
when you have a single line or single line block you would use the curly braces And if you have a multi line block you would use do and end.
words.each { |word| puts word } # single line or single line block
words.each do |word| # multi line block
puts word
p 1
end
Second religion says that
If your block simply does something has side effect and you dont care about the return value you might put do and end
words.each do |word|
puts word
end
where as if you do care about return value you would use { ... }
back_words = words.map{ |word| word.reverse }
hope I answere your question !!!
I'm trying to figure out why the following code does not work in a here-doc string eval:
script = <<FILE
def i_feel(&block)
block_given? ? "I feel #{ yield } :D" : "I don't know how I feel :/"
end
i_feel
# => "I don't know how I feel :/"
i_feel { 'great' }
# => "I feel great :D"
FILE
puts script
`<main>': no block given (yield) (LocalJumpError)
I know that part of the reason is the string interpolation. But how do I make it work? Try it without the here-doc first and see if it works?
You can avoid interpolation in a heredoc by surrounding the identifier with ':
script = <<'FILE'
something with #{interpolation} like syntax.
FILE
puts script
This will just print out the literal something with #{interpolation} like syntax. Without the ' around FILE you would probably get an error (if interpolation was defined you wouldn’t).
You can also use ` (backticks) to execute the contents of the heredoc:
s = <<`SCRIPT`
echo foo
echo bar
SCRIPT
s will now contain the result of executing the heredoc – in this case the string "foo\nbar\n".
I think that I just figured it out.
script = <<FILE
def i_feel(&block)
block_given? ? "I feel yield :D" : "I don't know how I feel :/"
end
i_feel
# => "I don't know how I feel :/"
i_feel { 'great' }
# => "I feel great :D"
FILE
puts script
Just remove the string interpolation brackets and here doc prints it just fine without them. I think I forgot about setting it up to interpolate is all. Oops! I'll get back to you all on this when I can.
Maybe now I can get something new from it. At least the error went away.
That will stop distracting me.
Here's the answer and a little more about what my intended purpose is. You want to see what you just ran so this is the complete version.
script = <<FILE
def how_i_feel
block_given? ? "I feel \#{ yield }." : "I don't know how I feel :/."
end
#{ def how_i_feel
block_given? ? "I feel #{ yield }." : "I don't know how I feel :/."
end }
how_i_feel
#{ how_i_feel }
how_i_feel { 'great :D' }
#{ how_i_feel { 'great :D' } }
FILE
puts script
What Victor said was right.
Here doc includes scope also.
In this case it is the interpolation of the interpolation.
Sounds a little confusing but just look at what level those brackets go to.
Notice also that we have to escape the original #{ yield } less it try to evaluate it.
That was what caused all the trouble in the first place.
Hope this helps.
And by the way this is just copied from https://github.com/JoshCheek/ruby-kickstart/blob/kevin_challenge/cheatsheets/blocks_procs_and_lambdas.rb
From that you can see that you don't need the &block parameter.
very new to Ruby, I've got the following situation. I have a file with values separated by new lines, they look like this:
18917
18927
18929
...
I want to prepend a folder path to all of them, then grab the first 2 characters and prepend that as well, then the value in the file and then append a '.jpg' at the end so they would end up looking like this:
path/to/foler/18/18917.jpg
So I've code this ruby code:
folder = "/path/to/folder"
lines = File.readlines("values.csv")
images = lines.collect.to_s.gsub("\n", ".jpg,")
images.split(',').collect { |dogtag | puts "bad dog: #{folder}/#{dogtag[0,2]}/#{dogtag}" }
Now, this almost works, the part that is not working is the grabbing of the first 2 characters. I also tried it with the method outside quotes (and without the #{} of course) but it just produces an empty result.
Could someone please point out my folly?
Eventually I want to delete those images but I'm guessing that substituting 'File.delete' for 'puts' above would do the trick?
As usual, thanks in advance for taking the time to look at this.
You don't seem to be understanding what collect does.
I would rewrite your snippet like this:
File.read("values.csv").each do |line|
puts "bad dog: /path/to/folder/#{line[0,2]}/#{line.chomp}.jpg"
end
-- Update for last comment: --
If you don't want to use an if statement to check if a file exists before deleting it, you have two option (AFAIK).
Use rescue:
File.read("values.csv").each do |line|
begin
File.delete "/path/to/folder/#{line[0,2]}/#{line.chomp}.jpg"
rescue Errno::ENOENT
next
end
end
Or extend the File class with a delete_if_exists method. You can just put this at the top of your script:
class File
def self.delete_if_exists(f)
if exists?(f)
delete(f)
end
end
end
With this, you can do:
File.read("values.csv").each do |line|
File.delete_if_exists "/path/to/folder/#{line[0,2]}/#{line.chomp}.jpg"
end
I'm just starting out using Ruby and I've written a bit of code to do basic parsing of a CSV file (Line is a basic class, omitted for brevity):
class File
def each_csv
each do |line|
yield line.split(",")
end
end
end
lines = Array.new
File.open("some.csv") do |file|
file.each_csv do |csv|
lines << Line.new(:field1 => csv[0], :field2 => csv[1])
end
end
I have a feeling I would be better off using collect somehow rather than pushing each Line onto the array but I can't work out how to do it.
Can anyone show me how to do it or is it perfectly fine as it is?
Edit: I should have made it clear that I'm not actually going to use this code in production, it's more to get used to the constructs of the language. It is still useful to know there are libraries to do this properly though.
Here's a (possibly wild) idea, use the Struct class instead of rolling your own simple POD class. But what you want from this is to have a constructor that accepts all of the arguments that could be generated from the file data.
Line = Struct.new(:field1, :field2, :field3)
Then at the core of the algorithm you want something like:
File.open("test.csv").lines.inject([]) do |result, line|
result << Line.new(line.split(",", Line.length))
end
or being a bit more concise and functional-like:
lines = File.open("test.csv").lines.map { |line| Line.new(line.split(",", Line.length)) }
To be honest I haven't used the Struct class much, but I should be, and I will probably refactor stuff already written to use it. It allows you to access the variables by their names like:
Line.field1 = blah
Line.field2 = 1
The Ruby Struct class.
So to actually answer your question, and looking above at the code, I would say it would be much simpler to use collect/map to perform the computation. The map function together with inject are very powerful and I find I use them quite frequently.
I don't know if you are aware of it, but ruby has it's own class for parsing and writing CSV files.
I found an example of using collect to turn a csv file into an array of hashes.
def csv_to_array(file_location)
csv = CSV::parse(File.open(file_location, 'r') {|f| f.read })
fields = csv.shift
csv.collect { |record| Hash[*(0..(fields.length - 1)).collect {|index| [fields[index],record[index].to_s] }.flatten ] }
end
This example is taken from this article.
If you are unfamiliar with the * notion, it basically dissolves the outer [] brackets, turning an array into a comma separated list of its elements.
Have you looked at FasterCSV, it does what your trying to do here, along with dealing with some of the brain deadness you find in some CSV files
See how this works for you (functional programming is fun!):
Try using inject. Inject takes as a parameter the starting "accumulator", and then a two parameter block:
[1,2,3].inject(0) { |sum,num| sum+num }
is naturally 6
[1,2,3].inject(5) { |sum,num| sum+num }
is 11
[1,2,3].inject(2) { |sum,num| sum*num }
is 12
To the point:
class Line
def initialize(options)
#options = options
end
def to_s
#options[:field1]+" "+#options[:field2]
end
end
File.open("test.csv").lines.inject([]) do |lines,line|
split = line.split(",")
lines << Line.new(:field1 => split[0],:field2 => split[1])
end