Chef Ruby -- are Loops within the variables for template possible? - ruby

I have spent already a long deal of time trying to figure out how to do this, I have also investigated but I have not found the right approach to it...?
basically I am trying to do something like the following:
types = ['type1','type2']
classes = ['class1','class2']
classes.each do |class|
types.each do |type|
template "/files/filename.txt" do
source "source_file.erb"
owner "root"
group "root"
mode "0440"
variables({
:pri_areas => node['area']['#{type}']['#{class}'],
:rev_areas => node['area']['#{type}']['#{class}']
})
end
end
end
Obviously I got all the attributes already defined so everything looks all right from that front..
I still cannot manage to get a loop with arrays like that withing the variables?
Maybe another different approach?
Any ideas/help?
Thanks very much.

Your code has some issues which you need to fix before it will properly work.
At first, class is a reserved keyword in Ruby and thus can't be used as a variable name. You should use another one, e.g. klass.
Secondly, class (or klass) as well as type are already strings inside your loop. Thus you don't need to attempt string interpolation. You can directly use this:
variables({
:pri_areas => node['area'][type][klass],
:rev_areas => node['area'][type][klass]
})
The reason why your string interpolation didn't work is that ruby knows two different kinds of String literals: ones with " and ones with '. The difference is that the ones delimited with ' do not allow string interpolation and generally do not interpret anything inside then as something else than the literal written string. Only in Strings delimited by ", you can perform string interpolation like "#{foo}" and use escape sequences like \n.

Related

Why freezing hash literal is not the same as freezing string literal?

I have been reading about ways to reduce memory usage in my Ruby/Rails app, and one thing that is mentioned is freezing objects. I have tried the code below (MRI, Ruby 2.3.3) and it does save memory, according to Activity Monitor, compared to not freezing the string.
pipeline = []
100_000.times { pipeline << 'hello world'.freeze }
However, if I try the same with a hash literal, it uses lots of memory, unless I assign the hash to a variable and freeze it before.
pipeline = []
100_000.times { pipeline << {hello: 'world'}.freeze } # Uses about 25MB
my_hash = {hello: 'world'}
my_hash.freeze
100_000.times { pipeline << my_hash} # This uses about 1MB
Can anyone explain why? I always thought the string case was a bit strange, because it looks like you're simply creating lots of different string objects, freezing each one separately, and adding lots of frozen objects to the array. Don't know why it works, but hey, it did. Now, the hash case is more in line with what I expected, but I don't know why it won't behave like the string.
It's probably the case that the Ruby optimizer can identify that string as being the same from one loop to the next, but it's unable to identify that hash as being identical so it makes new ones. In the second variant you literally use the same hash so the optimizer can handle it.
For proof, look at this:
pipeline = []
100_000.times { pipeline << 'hello world'.freeze }
pipeline.map(&:object_id).uniq.length
# => 1
That's an array of identical objects, one allocation only.
pipeline = []
100_000.times { pipeline << {hello: 'world'}.freeze }
pipeline.map(&:object_id).uniq.length
# => 100000
That's 100,000 different objects.
Can anyone explain why? I always thought the string case was a bit strange, because it looks like you're simply creating lots of different string objects, freezing each one separately, and adding lots of frozen objects to the array.
The expression form
'string literal'.freeze
is a special expression form that is special-cased by the language. It not only freezes the string object, it also performs de-duplication. (Similar to symbols.)
It is a special-cased expression form. It is not evaluating the string literal and then sending it the message freeze. Rather, it is treated as a single entity, a different form of string literal if you will.
In fact, the original proposal did introduce a different form of string literal like this:
'string literal'f
The proposal was changed to make it forwards-compatible: 'foo'f would be a syntax error, if you had to run your code in older versions of Ruby, whereas 'foo'.freeze just works the same way in older versions of Ruby, it only uses more memory.
Note: this means it only works for literals. Here, the string is de-duplicated:
'foo'.freeze
Here, it is not:
foo = 'foo'
foo.freeze
Don't know why it works, but hey, it did.
Basically, it works, because the language specification says so.
Now, the hash case is more in line with what I expected, but I don't know why it won't behave like the string.
Again, it doesn't work, because the language specification only special-cases string literals.

Convert and String to Variable Ruby [duplicate]

I have a string, which has been created at runtime. I want to use this string as a variable to store some data into it. How can I convert the string into a variable name?
If you can forgive an # sign in front of the variable name, the following will work:
variable_name = ... # determine user-given variable name
instance_variable_set("##{variable_name}", :something)
This will create a variable named #whatever, with its value set to :something. The :something, clearly, could be anything you want. This appears to work in global scope, by declaring a spontaneous Object instance which binds everything (I cannot find a reference for this).
The instance_variable_get method will let you retrieve a value by name in the same manner.
instance_variable_get("##{variable_name}")
You can use eval() for this provided that you've declared your variable first:
>> foo = []
>> eval("foo")[1] = "bar"
>> foo[1]
=> "bar"
Here are the docs.
Rather than do that directly, why not consider using a hash instead. The string would be the key, and the value you want to store would be the value. Something like this:
string_values = { }
some_string = params[:some_string] # parameter was, say "Hello"
string_values[some_string] = 42
string_values # { 'Hello' => 42 }
some_number = string_values[some_string]
some_number # 42
This has a couple of benefits. First, it means you're not doing anything magic that might be hard to figure out later. Second, you're using a very common Ruby idiom that's used for similar functionality throughout Rails.
Now simply using instance_variable_set method, you can create a instance variable at runtime.
instance_variable_set('#' + 'users', User.all)
I don't mean to be negative, but tread carefully. Ruby gives you a lot of features for highly dynamic programming such as define_method, storing blocks as Proc objects to be called later, etc. Generally these are cleaner code and far safer. 99% of the time using eval() is a mistake.
And absolutely never use eval() on a string that contains user submitted input.
As Jason Watkins says, a lot of people might be quick to use eval() first thing and this would be a serious mistake. Most of the time you can use the technique described by Tack if you've taken care to use a class instance variable instead of a local one.
Local variables are generally not retrievable. Anything with the # or ## prefix is easily retrieved.

Is it possible to define a specific part of a string in an external file as a variable? [duplicate]

This question already has answers here:
Closed 10 years ago.
Possible Duplicate:
In Ruby, can you perform string interpolation on data read from a file?
I need to loop through a few 'li' elements:
for i in 1..5
Xpaths.getPath("location","elements")
end
Then, all xpaths are in an external file, so the value of such 'elements' variable is as follows:
{
"location":
{
"elements" :"//ul[#id='locations']/li[#{i}]/a"
}
}
The elements variable is read as a string, and [#{i}] is not replaced with values such as [1]. Is there a way to define a specific part of a string in an external file as a variable?
If I understand your question correctly, you have some external data file in JSON format, whose structure has certain fields as XPath strings on which you would like to perform Ruby string interpolation.
The short answer is that yes, you can do this directly using, say Kernel#eval. There are also other options such as using erb. A solution using the simple "eval" route might look like this:
xpaths = JSON.load(File.read('my-xpaths.json'))
(1..5).each do |i|
xpath = eval(xpaths['location']['elements'].to_json)
# => "//ul[#id='locations']/li[1]/a"
end
Of course, using "eval" is fraught with peril since you are essentially executing code from another source, so there are many precautions you must take to ensure safety. A better approach might involve doing a simple regular expression replacement so that you can constrain the interpolation on the XPath item:
xpaths['location']['elements'] # => "//ul[#id='locations']/li[__INDEX__]/a"
xpath = xpaths['location']['elements'].gsub(/__INDEX__/, i.to_s)
# => "//ul[#id='locations']/li[1]/a"
See also this related question: In Ruby, can you perform string interpolation on data read from a file?

Rails 3 - Check if string/text includes a certain word/character via regex in controller

I am working on a quoting mechanism in my app, where it should be possible to simply type #26, for example, in the comment form in order to quote comment 26 of that topic.
To check if a user wants to quote one or more comments in the first place, I put an if condition after my current_user.comments.build and before #comment.save.
But, just to make my question a bit more general and easier to adapt:
if #comment.content.include?(/\A[\w+\-.]+#[a-z\d\-.]+\.[a-z]+\z/i)
I want something like this. That example was for checking if the comment's content includes emails. But logically I get an "can't convert regexp to string" error.
How can you do the include? method in rails with an regexp? So, to check whether a text includes a string of a certain regex format?
Or is the controller the wrong place for such regex actions?
I do ruby regex'es this way:
stringObj.match(/regex/)
There's also
if #comment.content =~ /regex/
If you had an array of all previous comments #prev_comments and wanted to replace them all in one shot, you could:
pattern = /#(\d+)/
#comment.content.gsub(pattern) do
cur_match = Regexp.last_match
idx = cur_match[1].to_i - 1
#prev_comments[idx]
end
Trick is using Regexp.last_match to get the current match, which made me wonder if it was thread safe. It is, apparently.
adapted (stolen) from the below more general String extension
class String
def js_replace(pattern, &block)
gsub(pattern) do |_|
md = Regexp.last_match
args = [md.to_s, md.captures, md.begin(0), self].flatten
block.call(*args)
end
end
end
Source: http://vemod.net/string-js_replace
To match the nature of .include?
stringObj.match(/regex/).present?
Would give similar true/false outcomes if you're using Rails (or ActiveSupport)

Using a string as a variable at run time

I have a string, which has been created at runtime. I want to use this string as a variable to store some data into it. How can I convert the string into a variable name?
If you can forgive an # sign in front of the variable name, the following will work:
variable_name = ... # determine user-given variable name
instance_variable_set("##{variable_name}", :something)
This will create a variable named #whatever, with its value set to :something. The :something, clearly, could be anything you want. This appears to work in global scope, by declaring a spontaneous Object instance which binds everything (I cannot find a reference for this).
The instance_variable_get method will let you retrieve a value by name in the same manner.
instance_variable_get("##{variable_name}")
You can use eval() for this provided that you've declared your variable first:
>> foo = []
>> eval("foo")[1] = "bar"
>> foo[1]
=> "bar"
Here are the docs.
Rather than do that directly, why not consider using a hash instead. The string would be the key, and the value you want to store would be the value. Something like this:
string_values = { }
some_string = params[:some_string] # parameter was, say "Hello"
string_values[some_string] = 42
string_values # { 'Hello' => 42 }
some_number = string_values[some_string]
some_number # 42
This has a couple of benefits. First, it means you're not doing anything magic that might be hard to figure out later. Second, you're using a very common Ruby idiom that's used for similar functionality throughout Rails.
Now simply using instance_variable_set method, you can create a instance variable at runtime.
instance_variable_set('#' + 'users', User.all)
I don't mean to be negative, but tread carefully. Ruby gives you a lot of features for highly dynamic programming such as define_method, storing blocks as Proc objects to be called later, etc. Generally these are cleaner code and far safer. 99% of the time using eval() is a mistake.
And absolutely never use eval() on a string that contains user submitted input.
As Jason Watkins says, a lot of people might be quick to use eval() first thing and this would be a serious mistake. Most of the time you can use the technique described by Tack if you've taken care to use a class instance variable instead of a local one.
Local variables are generally not retrievable. Anything with the # or ## prefix is easily retrieved.

Resources