String Replace Ruby - ruby

I am trying to play around my codes in ruby.
Here's what I got:
name = "Rol"
title = name
name = "Sam"
puts name
puts title
OUTPUT:
Sam
Rol
name = "Rol"
title = name
name.replace("Sam")
puts name
puts title
OUTPUT:
Sam
Sam
Basically I wondering why when I use reassignment equal sign it changes just the variable name and left the title with the old value. However upon using the replace method, it changes the value of both?
Any explanation in layman's term on this? Newbie in ruby here.

The simple layman terms are:
Assignments change variables.
Mutation methods change objects.
Variables and objects are different things in Ruby. Each variable is a label that points to an object. Each method call is sent to an object. For convenience we often simplify discussion, because it is easy to say "The name is 'Sam'" when in detail we mean "The value stored in the String object pointed to by the name variable is 'Sam'"
In your first example:
The line name = "Rol" creates a new String object from the literal "Rol" and points the local variable name at it.
The line title = name points the local variable title to the same String object.
The line name = "Sam" creates a new String object from the literal "Sam" and points the local variable name at it. Now name and title point to different String objects.
At this point there are two independent String variables, with two different variables pointing at them, so any uses of them remain separate.
In your second example:
The line name = "Rol" creates a new String object from the literal "Rol" and points the local variable name at it.
The line title = name points the local variable title to the same String object.
The line name.replace("Sam") modifies the object. It doesn't matter which variable you used to access the object in the first place. title.replace("Sam") would have identical results, since it refers to the same object.

The answer is very simple.
When you call:
title = name
then both variables: title and name have reference to the same object, which is next modified by:
name.replace("Sam")

String#replace replaces the content of the string referenced by name. However, it works at reference level as opposite to = that assigns a new String object to the left variable.
name is assigned to title, which means at that point in time both variables point to the same memory allocation. Since replace replaces the referenced string, the change is effectively propagated to both.
You get a better sense if you compare the object_id of the values. In the case of the assignment, the object ID changes because you effectively create and assign a new object:
name = "Rol"
title = name
name.object_id
# => 70336016119860
title.object_id
# => 70336016119860
name = "Sam"
name.object_id
# => 70336016085020
title.object_id
# => 70336016119860
For the case of replace, instead, the object_id doesn't change because you change the value directly without creating a new object.
name = "Rol"
title = name
name.object_id
# => 70336003479640
title.object_id
# => 70336003479640
name.replace("Sam")
name.object_id
# => 70336003479640
title.object_id
# => 70336003479640

Related

Using slice! on a variable is modifying the node attribute that populated the variable

In OpsWorks Stacks, I have set a layer attribute using the custom JSON field:
{
"layer_apps" : [
"app_manager"
]
}
The app_ portion of the attribute is necessary for the workflow. At times, I need to temporarily remove the app_ portion within a cookbook. To do this, I use slice!:
node['layer_apps'].each do |app_name|
install_certs_app_name = app_name
install_certs_app_name.slice!('app_') # 'app_manager' => 'manager'
# snip
end
However, once this is done, even though app_name isn't being directly modified, each node['layer_apps'] attribute gets sliced, which carries on to subsequent cookbooks and causes failures. The behaviour I expected was that slice! would modify app_name, and not the current node['layer_apps'] attribute. Thinking that app_name was a link to the attribute rather than being it's own variable, I tried assigning its value to a separate variable (install_certs_app_name and similar in other cookbooks), but the behaviour persisted.
Is this expected behaviour in Ruby/Chef? Is there a better way to be excluding the app_ prefix from the attribute?
app_name is being directly modified. That's the reason for the bang ! after the method... so that you're aware that the method mutates the object.
and app_name and install_certs_app_name are referencing the same object.
Note that slice and slice! both return "app_" but the bang object mutates the caller by removing the sliced text.
If you did
result = install_certs_app_name.slice!('app_')
puts result
==> app_
puts install_certs_app_name
--> manager
Try (instead)
install_certs_app_name = app_name.dup
install_certs_app_name.slice!('app_')
So you have two separate objects.
Alternatively,
install_certs_app_name = app_name.sub('app_', '')
In case you'd want a variable sliced, what you'll is the non-destructive version:
str.slice and not str.slice!
These are often referred to as Bang-methods, and replace the variable in place.
Below is an example with the .downcase method. This is the same principle for .slice.
EDIT:
However, since .slice returns the part that's been cut out, you could just remove the app_-part .sub like
"app_manager".sub("app_",'') #=> "manager"
http://ruby-for-beginners.rubymonstas.org/objects/bangs.html
https://ruby-doc.org/core-2.2.0/String.html#method-i-slice
When you assigning app_name to install_certs_app_name you still referencing to the same object. In order to create new object you can do:
install_certs_app_name = app_name.dup
New object with the same value is created. And slicing install_certs_app_name does not affect app_name this way.

Parse a string with multiple XML-like tags using Ruby

I have a string which looks like the following:
string = " <SET-TOPIC>INITIATE</SET-TOPIC>
<SETPROFILE>
<PROFILE-KEY>predicates_live</PROFILE-KEY>
<PROFILE-VALUE>yes</PROFILE-VALUE>
</SETPROFILE>
<think>
<set><name>first_time_initiate</name>yes</set>
</think>
<SETPROFILE>
<PROFILE-KEY>first_time_initiate</PROFILE-KEY>
<PROFILE-VALUE>YES</PROFILE-VALUE>
</SETPROFILE>"
My objective is to be able to read out each top level that is in caps with the parse. I use a case statement to evaluate what is the top level key, such as <SETPROFILE> but there can be lots of different values, and then run a method that does different things with the contnts of the tag.
What this means is I need to be able to know very easily:
top_level_keys = ['SET-TOPIC', 'SET-PROFILE', 'SET-PROFILE']
when I pass in the key know the full value
parsed[0].value = {:PROFILE-KEY => predicates_live, :PROFILE-VALUE => yes}
parsed[0].key = ['SET-TOPIC']
I currently parse the whole string as follows:
doc = Nokogiri::XML::DocumentFragment.parse(string)
parsed = doc.search('*').each_with_object({}){ |n, h|
h[n.name] = n.text
}
As a result, I only parse and know of the second tag. The values from the first tag do not show up in the parsed variable.
I have control over what the tags are, if that helps.
But I need to be able to parse and know the contents of both tag as a result of the parse because I need to apply a method for each instance of the node.
Note: the string also contains just regular text, both before, in between, and after the XML-like tags.
It depends on what you are going to achieve. The problem is that you are overriding hash keys by new values. The easiest way to collect values is to store them in array:
parsed = doc.search('*').each_with_object({}) do |n, h|
# h[n.name] = n.text :: removed because it overrides values
(h[n.name] ||= []) << n.text
end

Ruby String to access an object attribute

I have a text file (objects.txt) which contains Objects and its attributes.
The content of the file is something like:
Object.attribute = "data"
On a different file, I am Loading the objects.txt file and if I type:
puts object.attribute it prints out data
The issue comes when I am trying to access the object and/or the attribute with a string. What I am doing is:
var = "object" + "." + "access"
puts var
It prints out object.access and not the content of it "data".
I have already tried with instance_variable_get and it works, but I have to modify the object.txt and append an # at the beginning to make it an instance variable, but I cannot do this, because I am not the owner of the object.txt file.
As a workaround I can parse the object.txt file and get the data that I need but I don't want to do this, as I want take advantage of what is already there.
Any suggestions?
Yes, puts is correctly spitting out "object.access" because you are creating that string exactly.
In order to evaluate a string as if it were ruby code, you need to use eval()
eg:
var = "object" + "." + "access"
puts eval(var)
=> "data"
Be aware that doing this is quite dangerous if you are evaluating anything that potentially comes from another user.

Passing Field Symbol value to Return parameter of method

I have the below code which uses a method. When I try to assign the Field Symbol value [Type ANY] to the return parameter RO_TAB [Type Ref to Data], I am getting an error message OBJECTS_MOVE_NOT SUPPORTED [Conversion of type "l" to type "g" not supported.].
The issue is happening after a BW system upgrade along with which we also moved to ABAP objects. The code executes perfectly in the older version of ABAP.
The dump occurs in the below line:
RO_TAB = <lf_storage>.
I have no idea why.
method GET_LU_STORAGE_FOR_ODS.
* IMPORTS
* IF_ODS TYPE RSODSTECH
* IF_ODS_TABLE_TYPE TYPE ZODS_TAB_TYPE
* RETURNS
* RO_TAB TYPE REF TO DATA
FIELD-SYMBOLS:
<lf_storage> TYPE ANY.
DATA:
lf_index TYPE SY-TABIX,
lf_sindex TYPE STRING,
lf_name TYPE STRING.
lf_index = GET_LU_STORAGE_INDEX(
IF_ODS = IF_ODS
IF_ODS_TABLE_TYPE = IF_ODS_TABLE_TYPE ).
lf_sindex = lf_index.
CONCATENATE
'MO_LU_DATA_'
lf_sindex
INTO lf_name.
ASSIGN lf_name TO <lf_storage>.
RO_TAB = <lf_storage>.
endmethod.
You need to create a data object first, using the CREATE DATA statement. Then you can ASSIGN a field symbol to work with the dynamically created data object. There's an example in the online manual. A field symbol is not a reference, it simply places the variable assigned to it in its position. You're effectively trying to move a string (which is what lf_name is) to a reference variable, and that won't work.
You cannot assign a variable of type STRING to a variable of type REF TO DATA.
The following code snippet shows how it should be done.
DATA: lf_name TYPE string.
DATA: lo_tab TYPE REF TO DATA.
FIELD-SYMBOLS: <lf_name> TYPE string.
lf_name = 'test'.
GET REFERENCE OF lf_name INTO lo_tab.
*lf_name = lo_tab. "this is not allowed
ASSIGN lo_tab->* TO <lf_name>.
So in your case it would be sufficient to define a field symbol.
FIELD-SYMBOLS: <lf_name> TYPE STRING.
then assign the contents referenced by RO_TAB to this field symbol.
ASSIGN ro_tab->* TO <lf_name>.
and finally do the concatenation.
CONCATENATE
'MO_LU_DATA_'
lf_index
INTO <lf_name>.
That's all! No further assignments should be required.
How about just this?
lf_sindex = lf_index.
CONCATENATE
'MO_LU_DATA_'
lf_sindex
INTO RO_TAB.

Error "object doesn't support this property or method: dbrowser.GetRoProperty"

I was trying to run below script, but it's giving me an error that says:
object doesn't support this property or method: "dbrowser.GetRoProperty"
SystemUtil.Run "iexplore.exe","http://usps.com/"
Set dbrowser = description.Create
dbrowser ("micclass").value = "Browser"
dbrowser("openurl").value = "https://www.usps.com"
dbrowser("title").value = "USPS - The United States Postal Service (U.S. Postal Service)"
print(dbrowser.getroproperty("title"))
Your dbrowser object is of type Description not Browser you need to create a Browser object based on this description. Replace the last line with:
Print Browser(dbrowser).GetROProperty("title")
Note, there are two changes here
Using Browser(dbrowser)
Removing the parens from the print sub.
Edit: also note that descriptions are regular expressions by default so the parens in the title may cause problems, you should mark it as not regex.
dbrowser("title").RegularExpression = False
Description.Create is used to create a 0-based Properties collection object. The variable dbrowser is preceded by the Set statement. Usage of Set statement binds an object as a reference to another object. Therefore, dbrowser becomes an object reference to the description object represented by Description.Create
A description object does not have a stand-alone use, but coupled with the ChildObjects method, it becomes an extremely powerful approach in dealing with AUT’s objects .For More Info, check link
So the code should be like
SystemUtil.Run "iexplore.exe","http://usps.com/"
wait(10)
Set dbrowser = description.Create
dbrowser ("micclass").value = "Browser"
dbrowser("openurl").value = "https://www.usps.com"
dbrowser("title").value = "USPS.*" ''Using Regular Expression here
Set colObject = Desktop.ChildObjects( dbrowser )
Print (colObject(0).GetROProperty("title"))

Resources