Accessing specific attribute of an object - ruby

Hello I have the following object
object = [#<ShopifyAPI::DiscountCode:0x000000000e1c78a8 #attributes={"code"=>"Disc2", "amount"=>"1.00", "type"=>"percentage"}, #prefix_options={}, #persisted=true>]
How can I properly access the "code" name of that object?
I have tried object[:code] and object.code but it appears I am overlooking something.

object is an array of ShopifyAPI::DiscountCode.
The best way to access it is
object[0].attributes['code']
If u want code of all the objects available in the array, you could get the array of values by
object.map { |obj| obj.attributes['code'] }

Given that this is an Array of ShopifyAPI::DiscountCodes (which inherit from ActiveResource::Base)
You can call the code method on them. eg:
object[0].code
#=> "Disc2"
object.map(&:code)
#=> ["Disc2"]

First, object is array:
obj0 = object[0]
Second, this is instance variable:
attributes = obj0.instance_variable_get(:#attributes)
Last, gets values by keys:
attributes['code']

Related

Pulling out values from element of array

I have a hash that is generated by IB-ruby and looks like this:
{:contract=>#<IB::Stock:0x0000560721a1aee0 #attributes={:symbol=>"AAPL", :currency=>"USD", :sec_type=>"STK", :created_at=>2019-10-23 23:03:35 +0200, :con_id=>0, :right=>"", :include_expired=>false, :exchange=>"SMART"}>, :last_price=>0.24308e3, :high=>0.24324e3, :low=>0.24122e3, :close_price=>0.23996e3, :open_tick=>0.2421e3, :bid_price=>0.2431e3, :ask_price=>0.24319e3}
How do I pull out the symbol ("AAPL") and the closing_price (0.23996e3) for further processing?
What you posted is the string representation of a Hash.
This Hash has a key :close_price, whose value you can access in this way:
your_hash[:close_price] #=> 0.23996e3
The hash also has a key :contract whose value is an instance of the class IB::Stock. To access this object:
ib_stock_instance = your_hash[:contract]
ib_stock_instance.class #=> IB::Stock

How to add object attributes in array from another array in Ruby

Firstly I have an array of multiple objects [#<Vgpop::Game:0x00007fcd5b246a00 #name="Super Smash Bros. Ultimate", #console=nil, #score=nil>....]
Then I have an array of my consoles that looks like this: ["Nintendo Switch"...]
How do I map the values from my second array to my object array so that the return is:
[#<Vgpop::Game:0x00007fcd5b246a00 #name="Super Smash Bros. Ultimate", #console="Nintendo Switch", #score=nil>....]
Seems like we can come up with this solution my friend:
first_array.each_with_index { |vgpop, i| vgpop.name = second_array[i] }

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.

ruby - calling a method on a dynamically named object

I have an array of strings, that represent existing object names.
JoesDev = Dev.new
MarksDev = Dev.new
SamsDev = Dev.new
devices=['JoesDev', 'MarksDev', 'SamsDev' ]
i'd like to iterate over the devices array, while calling a method on the objects that each item in the array is named after.
i.e;
JoesDev.method_name
MarksDev.method_name
SamsDev.method_name
how can i do this? thx.
devices.each{|name| self.class.const_get(name).method_name}
You can use the const_get method from Module to have Ruby return the constant with the given name. In your case, it will return the Dev instance for whatever device name you give it.
Using .each to iterate the items, your code could look like
devices.each do |device_name|
device = self.class.const_get(device_name)
device.method_name
end
# Which can be shortened to
devices.each{ |dev| self.class.const_get(dev).method_name }
However, there are better ways to implement this type of thing. The most common way is using a Hash. In your example, the list of devices could look something like
devices = {
joe: Dev.new,
mark: Dev.new,
sam: Dev.new
}
Then, iterating over the devices is as simple as
devices.each do |dev|
dev.method_name
end
# Or
devices.each{ |dev| dev.method_name }
Extra: If you want to get a little fancy, you can use the block version of Hash::new to make adding new devices extremely simple.
# Create the hash
devices = Hash.new{ |hash, key| hash[key] = Dev.new }
# Add the devices
devices['joe']
devices['mark']
devices['sam']
This kind of hash works exactly the same as the one shown above, but will create a new entry if the given key cannot be found in the hash. A potential problem with this design, then, is that you can accidentally add new devices if you make a typo. For example
devices['jon'] # This would make a new Dev instance, which may be undesirable.
Well one way is surely to use eval, a method that allows you to execute arbitrary strings as if they were code.
So, in your example:
var_names.each{ |var_name| eval("#{var_name}.some_method") }
Needless to say, it is very dangerous to let unfiltered strings to be used as code, very bad thingsā„¢ may happen!

Create an object if one is not found

How do I create an object if one is not found? This is the query I was running:
#event_object = #event_entry.event_objects.find_all_by_plantype('dog')
and I was trying this:
#event_object = EventObject.new unless #event_entry.event_objects.find_all_by_plantype('dog')
but that does not seem to work. I know I'm missing something very simple like normal :( Thanks for any help!!! :)
find_all style methods return an array of matching records. That is an empty array if no matching records are found. And an empty is truthy. Which means:
arr = []
if arr
puts 'arr is considered turthy!' # this line will execute
end
Also, the dynamic finder methods (like find_by_whatever) are officially depreacted So you shouldn't be using them.
You probably want something more like:
#event_object = #event_entry.event_objects.where(plantype: 'dog').first || EventObject.new
But you can also configure the event object better, since you obviously want it to belong to #event_entry.
#event_object = #event_entry.event_objects.where(plantype: 'dog').first
#event_object ||= #event_entry.event_objects.build(plantype: dog)
In this last example, we try to find an existing object by getting an array of matching records and asking for the first item. If there are no items, #event_object will be nil.
Then we use the ||= operator that says "assign the value on the right if this is currently set to a falsy value". And nil is falsy. So if it's nil we can build the object form the association it should belong to. And we can preset it's attributes while we are at it.
Why not use built in query methods like find_or_create_by or find_or_initialize_by
#event_object = #event_entry.event_objects.find_or_create_by(plantype:'dog')
This will find an #event_entry.event_object with plantype = 'dog' if one does not exist it will then create one instead.
find_or_initialize_by is probably more what you want as it will leave #event_object in an unsaved state with just the association and plantype set
#event_object = #event_entry.event_objects.find_or_initialize_by(plantype:'dog')
This assumes you are looking for a single event_object as it will return the first one it finds with plantype = 'dog'. If more than 1 event_object can have the plantype ='dog' within the #event_entry scope then this might not be the best solution but it seems to fit with your description.

Resources