What is <<- in ruby? - ruby

From the Ruby_Newbie Guide to Symbols:
Author is trying to show a simplified implementation of the attr_writer method.
#!/usr/bin/env ruby
def make_me_a_setter(thename)
eval <<-SETTERDONE # <----- Here
def #{thename}(myarg)
##{thename} = myarg
end
SETTERDONE
end
class Example
make_me_a_setter :symboll
make_me_a_setter "stringg"
def show_symboll
puts #symboll
end
def show_stringg
puts #stringg
end
end
example = Example.new
example.symboll("ITS A SYMBOL")
example.stringg("ITS A STRING")
example.show_symboll
example.show_stringg

It's a heredoc. From the "Here Documents" documentation:
If you are writing a large block of text you may use a “here document”
or “heredoc”:
expected_result = <<HEREDOC
This would contain specially formatted text.
That might span many lines
HEREDOC
The heredoc starts on the line following <<HEREDOC and ends with the
next line that starts with HEREDOC. The result includes the ending
newline.

It's a multi line string. The code evaluates code embedded inside string. More on multi line strings:
http://blog.jayfields.com/2006/12/ruby-multiline-strings-here-doc-or.html
P.S. Using eval is not recommended, alternatives - yield, instance_eval, class_eval.

Related

Insert text before the end of a file

I am trying to write a script that will insert a text before the last end tag within a Ruby file. For example, I want to insert the following:
def hello
puts "hello!"
end
within the following file, just before the end of the class:
class ApplicationController < ActionController::Base
# Prevent CSRF attacks by raising an exception.
# For APIs, you may want to use :null_session instead.
protect_from_forgery with: :exception
helper_method :authenticated?, :current_user
def current_user?
session[:current_user]
end
end
The result should look like this:
class ApplicationController < ActionController::Base
# Prevent CSRF attacks by raising an exception.
# For APIs, you may want to use :null_session instead.
protect_from_forgery with: :exception
helper_method :authenticated?, :current_user
def current_user?
session[:current_user]
end
def hello
puts "hello!"
end
end
I have tried to find a regex that would match the last occurence of end and replace it with the block I want to add but all regexes I have tried match the first end only. Tried these:
end(?=[^end]*$)
end(?!.*end)
(.*)(end)(.*)
To replace the string, I do the following (maybe the EOL characters are screwing up the matching?):
file_to_override = File.read("app/controllers/application_controller.rb")
file_to_override = file_to_override.sub(/end(?=[^end]*$)/, "#{new_string}\nend")
EDIT: I also tried with the solution provided in How to replace the last occurrence of a substring in ruby? but strangely, it replaces all occurences of end.
What am I doing wrong? Thanks!
The approach explained in the post is working here, too. You just need to re-organize capturing groups and use the /m modifier that forces . to match newline symbols, too.
new_string = <<EOS
def hello
puts "Hello!"
end
EOS
file_to_override = <<EOS
class ApplicationController < ActionController::Base
# Prevent CSRF attacks by raising an exception.
# For APIs, you may want to use :null_session instead.
protect_from_forgery with: :exception
helper_method :authenticated?, :current_user
def current_user?
session[:current_user]
end
end
EOS
file_to_override=file_to_override.gsub(/(.*)(\nend\b.*)/m, "\\1\n#{new_string}\\2")
puts file_to_override
See IDEONE demo
The /(.*)(\nend\b.*)/m pattern will match and capture into Group 1 all the text up to the last whole word (due to the \n before and \b after) end preceded with a line feed, and will place the line feed, "end" and whatever remains into Group 2. In the replacement, we back-reference the captured substrings with backreferences \1 and \2 and also insert the string we need to insert.
If there are no other words after the last end, you could even use a /(.*)(\nend\s*\z)/m regex.
Suppose you read the file into the string text:
text = <<_
class A
def a
'hi'
end
end
_
and wish to insert the string to_enter:
to_enter = <<_
def hello
puts "hello!"
end
_
before the last end. You could write
r = /
.* # match any number of any character (greedily)
\K # discard everything matched so far
(?=\n\s*end\b) # match end-of-line, indenting spaces, and "end" followed
# by a word break in a positive lookahead
/mx # multi-line and extended/free-spacing regex definition modes
puts text.sub(r, to_enter)
(prints)
class A
def a
'hi'
end
def hello
puts "hello!"
end
end
Note that sub is replacing an empty string with to_enter.
Edit: Answer from Wiktor is exactly what I was looking for. Leaving the following too because it works as well.
Finally, I gave up on replacing using a regex. Instead, I use the position of the last end:
positions = file_to_override.enum_for(:scan, /end/).map { Regexp.last_match.begin(0) }
Then, before writing the file, I add what I need within the string at last position - 1:
new_string = <<EOS
def hello
puts "Hello!"
end
EOS
file_to_override[positions.last - 1] = "\n#{test_string}\n"
File.open("app/controllers/application_controller.rb", 'w') {|file| file.write(file_to_override)}
This works but it doesn't look like idiomatic Ruby to me.
You can also find and replace the last occurence of "end" (note that this will also match the end in # Hello my friend, but see below) like this
# Our basics: In this text ...
original_content = "# myfile.rb\n"\
"module MyApp\n"\
" class MyFile\n"\
" def myfunc\n"\
" end\n"\
" end\n"\
"end\n"
# ...we want to inject this:
substitute = "# this will come to a final end!\n"\
"end\n"
# Now find the last end ...
idx = original_content.rindex("end") # => index of last "end"(69)
# ... and substitute it
original_content[idx..idx+3] = substitute # (3 = "end".length)
This solution is somewhat more old-school (dealing with indexes in strings felt much cooler some years ago) and in this form more "vulnerable" but avoids you to sit down and digest the regexps. Dont get me wrong, regular expressions are a tool of incredible power and the minutes learning them are worth it.
That said, you can use all the regular expressions from the other answers also with rindex (e.g. rindex(/ *end/)).

Ruby method help required

I am reading Metaprogramming Ruby book, and there is method, which I cant understant:
def to_alphanumeric(s)
s.gsub /[^\w\s]/, ''
end
I see there is Argument Variable (s), which is called lately and is converted to some weird expression?
What exactly can I do with this method, is he useful?
Following method works just fine:
def to_alphanumeric(s)
s.gsub %r([aeiou]), '<\1>'
end
p = to_alphanumeric("hello")
p p
>> "h<>ll<>"
But if I upgrade method to class, simply calling the method + argv to_alphanumeric, no longer work:
class String
def to_alphanumeric(s)
s.gsub %r([aeiou]), '<\1>'
end
end
p = to_alphanumeric("hello")
p p
undefined method `to_alphanumeric' for String:Class (NoMethodError)
Would it hurt to check the documentation?
http://www.ruby-doc.org/core-2.0/String.html#method-i-gsub
Returns a copy of str with the all occurrences of pattern substituted for the second argument.
The /[^\w\s]/ pattern means "everything that is not a word or whitespace"
Take a look at Rubular, the regular expression /[^\w\s]/ matches special characters like ^, /, or $ which are neither word characters (\w) or whitespace (\s). Therefore the function removes special characters like ^, / or $.
>> "^/$%hel1241lo".gsub /[^\w\s]/, ''
=> "hel1241lo"
call it simple like a function:
>> to_alphanumeric("U.S.A!")
=> "USA"

Passing all arguments at once to a method

I am trying to read arguments from a text file and the pass them all at once to a Ruby method.
The arguments in the text file are properly formatted e.g.:
"path", ["elem1","elem2"], 4,"string"
I intend to make a function call like this:
my_method("path", ["elem1","elem2"], 4,"string")
This hopefully I am trying to achieve like this:
IO.readlines("path").each do |line|
puts "#{line}"
my_method(*line.split(","))
end
The problem is that in the method all the array elements are wrapped in quotes. So my method ends up getting this:
""path"", "["elem1","elem2"]", "4",""string""
Now, this is probably because its an array of strings, but why wrap it with an additional "" when I say *arr?
If I use eval:
IO.readlines("path").each do |line|
puts "#{line}"
my_method(*eval(line))
end
I end up with syntax error, unexpected ',' after the first argument in "path", ["elem1","elem2"], 4,"string"
How do I achieve passing all the elements to the method at once reading the arguments from a text file
Also since Ruby does not care about types, why do I have to wrap my arguments with "" in the first place. If I don't wrap the argument in a quote, I get undefined variable for main:object error.
I have one solution, but instead of using "," as your delimiter use some other special character as delimiter in the input line.
# Input line in somefile.txt delimited by "||" :
# "path" || ["elem1","elem2"] || 4 || "string"
def my_method(arg1, arg2, arg3, arg4)
path = arg1
arr = arg2.gsub(/([\[\]])/, "").split(",")
number = arg3.to_i
string = arg4
puts "path : #{path} and is #{path.class}"
puts "arr : #{arr} and is #{arr.class}"
puts "number : #{number} and is #{number.class}"
puts "string : #{string} and is #{string.class}"
end
IO.readlines("somefile.txt").each do |line|
my_method(*line.gsub(/[(\\")]/, " ").split("||"))
end
I hope this helped you out. Let me know if you have any problem.
IO.readlines("path").each do |line|
params = line.split(",").each do |param|
param = eval(param)
end
my_method(*params)
end
When you read the line, all params are strings, so to get arrays and integers you might try to eval then first.
the eval tip might be enough to fix your code.
if you pass the param without quotes, the interpreter will understand it as a constant and not as a string. Thats why you get undefined variable. Again, the eval tip should solve this.
OBS: Be careful with eval since it will execute any code, a command to erase the file or even worse (like mess with your computer or server) if the person behind the source of that file knows it.

Looking to clean up a small ruby script

I'm looking for a much more idiomatic way to do the following little ruby script.
File.open("channels.xml").each do |line|
if line.match('(mms:\/\/{1}[a-zA-Z\.\d\/\w-]+)')
puts line.match('(mms:\/\/{1}[a-zA-Z\.\d\/\w-]+)')
end
end
Thanks in advance for any suggestions.
The original:
File.open("channels.xml").each do |line|
if line.match('(mms:\/\/{1}[a-zA-Z\.\d\/\w-]+)')
puts line.match('(mms:\/\/{1}[a-zA-Z\.\d\/\w-]+)')
end
end
can be changed into this:
m = nil
open("channels.xml").each do |line|
puts m if m = line.match(%r|(mms://{1}[\w\./-]+)|)
end
File.open can be changed to just open.
if XYZ
puts XYZ
end
can be changed to puts x if x = XYZ as long as x has occurred at some place in the current scope before the if statement.
The Regexp '(mms:\/\/{1}[a-zA-Z\.\d\/\w-]+)' can be refactored a little bit. Using the %rXX notation, you can create regular expressions without the need for so many backslashes, where X is any matching character, such as ( and ) or in the example above, | |.
This character class [a-zA-Z\.\d\/\w-] (read: A to Z, case insensitive, the period character, 0 to 9, a forward slash, any word character, or a dash) is a little redundant. \w denotes "word characters", i.e. A-Za-z0-9 and underscore. Since you specify \w as a positive match, A-Za-z and \d are redundant.
Using those 2 cleanups, the Regexp can be changed into this: %r|(mms://{1}[\w\./-]+)|
If you'd like to avoid the weird m = nil scoping sorcery, this will also work, but is less idiomatic:
open("channels.xml").each do |line|
m = line.match(%r|(mms://{1}[\w\./-]+)|) and puts m
end
or the longer, but more readable version:
open("channels.xml").each do |line|
if m = line.match(%r|(mms://{1}[\w\./-]+)|)
puts m
end
end
One very easy to read approach is just to store the result of the match, then only print if there's a match:
File.open("channels.xml").each do |line|
m = line.match('(mms:\/\/{1}[a-zA-Z\.\d\/\w-]+)')
puts m if m
end
If you want to start getting clever (and have less-readable code), use $& which is the global variable that receives the match variable:
File.open("channels.xml").each do |line|
puts $& if line.match('(mms:\/\/{1}[a-zA-Z\.\d\/\w-]+)')
end
Personally, I would probably just use the POSIX grep command. But there is Enumerable#grep in Ruby, too:
puts File.readlines('channels.xml').grep(%r|mms://{1}[\w\./-]+|)
Alternatively, you could use some of Ruby's file and line processing magic that it inherited from Perl. If you pass the -p flag to the Ruby interpreter, it will assume that the script you pass in is wrapped with while gets; ...; end and at the end of each loop it will print the current line. You can then use the $_ special variable to access the current line and use the next keyword to skip iteration of the loop if you don't want the line printed:
ruby -pe 'next unless $_ =~ %r|mms://{1}[\w\./-]+|' channels.xml
Basically,
ruby -pe 'next unless $_ =~ /re/' file
is equivalent to
grep -E re file

How do you loop through a multiline string in Ruby?

Pretty simple question from a first-time Ruby programmer.
How do you loop through a slab of text in Ruby? Everytime a newline is met, I want to re-start the inner-loop.
def parse(input)
...
end
String#each_line
str.each_line do |line|
#do something with line
end
What Iraimbilanja said.
Or you could split the string at new lines:
str.split(/\r?\n|\r/).each { |line| … }
Beware that each_line keeps the line feed chars, while split eats them.
Note the regex I used here will take care of all three line ending formats. String#each_line separates lines by the optional argument sep_string, which defaults to $/, which itself defaults to "\n" simply.
Lastly, if you want to do more complex string parsing, check out the built-in StringScanner class.
You can also do with with any pattern:
str.scan(/\w+/) do |w|
#do something
end
str.each_line.chomp do |line|
# do something with a clean line without line feed characters
end
I think this should take care of the newlines.

Resources