Why are singleton Puppet Arrays Strings in my ruby code? - ruby

I've create a custom type in Puppet (simplified for this example). If I use it like this (two items in collections)...
my_type { "example1":
ensure => present,
collections => ["abc", "def"]
}
...in my provider, resource[:collections] is of type Array. That is good, and right.
But if collections contains only 1 item...
my_type { "example2":
ensure => present,
collections => ["abc"],
}
...resource[:collections] is a String, which is most disconcerting, and a pain in the ass to deal with.
Is this a Ruby thing, a Puppet thing (I'm new to both) or just some cosmic wrinkle in the coding universe I've stumbled upon? And more importantly, is there a workaround? Or am I just plain doing it wrong? I've been told that before. Don't hold back.

While I can't tell you why this happens, the standard workaround for dealing with things that can be either arrays or single objects is using the splat operator like this:[*foo]. In case foo was an array its elements will be "exploded" into a new one, so you still have an array. If foo was just a plain object, you now have a one element array.

It's not a Ruby thing.
resource = { :collection => ["abc"] }
resource[:collection].class
=> Array
It seems a bit odd if the Puppet DSL would change that behavior since it's Ruby based after all. But if it really does you can take Michaels' advice. Example:
[*resource[:collection]]
=> ["abc"]
resource[:str]="abc"
resource[:str]
=> "abc"
[*resource[:str]]
=> ["abc"]

Related

Is there a Ruby idiom for encapsulating an object in an Array if it isn't already?

I am working with an external API with which I'm exchanging XML messages. So I use a lot of Hash#from_xml.
However, #from_xml only encodes elements in an Array if they are repeating elements. It makes sense, but it breaks when I am trying to loop through a repeatable element that appears only once. For example:
<Stuff>
<SKU>ABC-123</SKU>
<SKU>DEF-456</SKU>
<SKU>XYZ-789</SKU>
</Stuff>
works great, because:
my_hash = Hash.from_xml(xmlstring)["Stuff"]
will contain 3 SKUs, so I can do:
my_hash["Stuff"].each do |sku|
# process the sku
end
But it fails with this XML:
<Stuff>
<SKU>XYZ-789</SKU>
</Stuff>
because myhash['SKU'] is a Hash, not an Array. I'm having to do this now:
my_hash['SKU'] = [my_hash['SKU']] if my_hash['SKU'].kind_of?(Hash)
Is there a cleaner way?
Just wrap it in an array and flatten it:
array_of_one_or_many = [my_hash['SKU']].flatten
If it's already an array it will unwrap it and make it a common array anyway. Works for both cases.
When I've encountered this in the past, I've used
foo = ([] << bar).flatten
bar is the object and foo will be a flat array.
You can use Array()
irb(main):012:0> Array(1)
=> [1]
irb(main):013:0> Array([1])
=> [1]

Is there any difference between the `:key => "value"` and `key: "value"` hash notations?

Is there any difference between :key => "value" (hashrocket) and key: "value" (Ruby 1.9) notations?
If not, then I would like to use key: "value" notation. Is there a gem that helps me to convert from :x => to x: notations?
Yes, there is a difference. These are legal:
h = { :$in => array }
h = { :'a.b' => 'c' }
h[:s] = 42
but these are not:
h = { $in: array }
h = { 'a.b': 'c' } # but this is okay in Ruby2.2+
h[s:] = 42
You can also use anything as a key with => so you can do this:
h = { C.new => 11 }
h = { 23 => 'pancakes house?' }
but you can't do this:
h = { C.new: 11 }
h = { 23: 'pancakes house?' }
The JavaScript style (key: value) is only useful if all of your Hash keys are "simple" symbols (more or less something that matches /\A[a-z_]\w*\z/i, AFAIK the parser uses its label pattern for these keys).
The :$in style symbols show up a fair bit when using MongoDB so you'll end up mixing Hash styles if you use MongoDB. And, if you ever work with specific keys of Hashes (h[:k]) rather than just whole hashes (h = { ... }), you'll still have to use the colon-first style for symbols; you'll also have to use the leading-colon style for symbols that you use outside of Hashes. I prefer to be consistent so I don't bother with the JavaScript style at all.
Some of the problems with the JavaScript-style have been fixed in Ruby 2.2. You can now use quotes if you have symbols that aren't valid labels, for example:
h = { 'where is': 'pancakes house?', '$set': { a: 11 } }
But you still need the hashrocket if your keys are not symbols.
key: "value" is a convenience feature of Ruby 1.9; so long as you know your environment will support it, I see no reason not to use it. It's just much easier to type a colon than a rocket, and I think it looks much cleaner. As for there being a gem to do the conversion, probably not, but it seems like an ideal learning experience for you, if you don't already know file manipulation and regular expressions.
Ruby hash-keys assigned by hash-rockets can facilitate strings for key-value pairs (e.g. 's' => x) whereas key assignment via symbols (e.g. key: "value" or :key => "value") cannot be assigned with strings. Although hash-rockets provide freedom and functionality for hash-tables, specifically allowing strings as keys, application performance may be slower than if the hash-tables were to be constructed with symbols as hash-keys. The following resources may be able to clarify any differences between hashrockets and symbols:
Ryan Sobol's Symbols in Ruby
Ruby Hashes Exaplained by Erik Trautman
The key: value JSON-style assignments are a part of the new Ruby 1.9 hash syntax, so bear in mind that this syntax will not work with older versions of Ruby. Also, the keys are going to be symbols. If you can live with those two constraints, new hashes work just like the old hashes; there's no reason (other than style, perhaps) to convert them.
Doing :key => value is the same as doing key: value, and is really just a convenience. I haven't seen other languages that use the =>, but others like Javascript use the key: value in their Hash-equivalent datatypes.
As for a gem to convert the way you wrote out your hashes, I would just stick with the way you are doing it for your current project.
*Note that in using key: value the key will be a symbol, and to access the value stored at that key in a foo hash would still be foo[:key].

Advantages/disadvantages to using struct as a parameter while defining new API

The APIs that I am writing are for a Ruby class that inherits from ActiveRecord. I am trying to write static methods to avoid leaking the ActiveRecord instance. All the APIs now need tuple to uniquely identify a database row.
Is it a good idea to have APIs of the form:
API1(abc, def, ....)
API2(abc, def, ....)
and so on
or should I define a struct with fields to help with future changes?
Any other ideas are greatly welcome!
Using a struct would be strange in Ruby, a Hash would be normal:
def self.api1(options)
# Look at options[:abc], options[:def], ...
end
And then it could be called using named arguments like this:
C.api1 :abc => 'abc', :def => '...'
Easy to extend, common Ruby practice, and easy to make certain parameters optional.
To continue what mu is describing, a common Ruby idiom you'll see it to have a method set itself some default options and then merge into that hash the options that the method received. This way you can be sure that some minimum list of options always exist:
def self.api1(options={})
default_options = { :foo => 'bar', :baz => nil }
options = default_options.merge options
# Now include your code and you can assume that options[:foo] and options[:bar] are there
end
This comes in handy when your method, for example, outputs the value of :baz. Now you don't need to check that it exists first, you can just output it knowing that it would always exist.

Inconsistent implicit hash creation in Ruby?

Ok, so I was comparing some stuff in my own DSL to Ruby. One construct they both support is this
x=["key" => "value"]
Knowing the difference between arrays and hashes, I would think this to be illegal, but the result in Ruby is
[{"key" => "value"}]
Why is this? And with this kinda syntax why can't you do
x=("key" => "value")
Why is an array a special case for implicitly created hashes?
Another special case is in a function call, consider:
def f(x)
puts "OK: #{x.inspect}"
end
f("foo" => "bar")
=> OK: {"foo"=>"bar"}
So in some contexts, Hashes can be built implicitly (by detecting the => operator?). I suppose the answer is just that this was Matz's least-surprising behavior.
With this apparent inconsistency in implicit hash creation, ruby achieves consistency in this regard:
func(whatever...)
can always be substituted with:
args = [whatever...]
func(*args)
You can convert between argument lists and arrays, and therefore it is logical that they have the same syntax.
I would say that the interpreter figures out that "key" => "value" is a hash, the same way it would figure out that 5 is a number when you put it into an array.
So if you write:
x = [5]
The interpreter is not going to think that it is a string, and return:
x = ["5"]
It seems that ruby implicitly creates hashes in some instances.

bracket syntax for Ruby Hashes

Do these two statements pass the same type of argument (a Hash) to the new method?
#seat = Seat.new(:flight_id => #flight.id)
#seat = Seat.new({:flight_id => #flight.id})
Do the Hash brackets {} change anything in the second example?
They are both the same, the {} add nothing in the second argument, apart from making things even more explicit than they already were (using the => syntax is enough to say 'this is a hash' to anyone using ruby for any length of time).
Ruby will automatically turn a list of parameters like:
someFunction(:arg1 => value1, :arg2 => value2)
into a hash and pass it as a single argument for you. The time when you need to add {} around hashes is when you have things like a hash of hashes or a function that expects two hashes (such as several rails methods when you need to pass both options and html_options), like this:
someFunction({:arg1 => value1, :arg2 => value2}, {:arg3 => value3})
which will pass in two hashes (the interpreter wouldn't be able to deduce where the 2 hashes were split if left to itself, so you need to give it the {} to tell it what to do in this case)
More information is available in the Pickaxe book chapter: More About Methods in the section on Collecting Hash Arguments at the bottom.
This seems like a good place to mention another alternate syntax, using the comma to separate items within braces (using your example):
#seat = Seat.new({:flight_id, #flight.id})
I don't normally use the comma syntax in standard code -- like workmad3 says, the arrow (=>) makes the hash more obvious. But in an interactive Ruby session (irb), it is easier to type a comma than the arrow:
{:eyes, "blue", :height, 6.2} # => {:height=>6.2, :eyes=>"blue"}
And in Ruby 1.9, the idiomatic version has even fewer commas:
{eyes: "blue", height: 6.2}

Resources