I know this is more a ruby question than chef, but...
I have some attributes like:
default['my_cookbook']['some_namespace1']['some_attribute1'] = 'some_value1'
default['my_cookbook']['some_namespace1']['some_attribute2'] = 'some_value2'
default['my_cookbook']['some_namespace1']['some_attribute2'] = 'some_value3'
...
default['my_cookbook']['some_namespace2']['some_attribute1'] = 'some_value1'
default['my_cookbook']['some_namespace2']['some_attribute2'] = 'some_value2'
default['my_cookbook']['some_namespace2']['some_attribute2'] = 'some_value3'
...
On the other hand, I am creating a template resource like this:
template 'template_name' do
source 'template_source.erb'
variables (
my_namespace_1: node['my_cookbook']['some_namespace1'],
my_namespace_2: node['my_cookbook']['some_namespace2']
)
end
Then in the template_source.erb I try:
...
<%= #my_namespace_1['some_attribute1'] %> #=> 'some_value1'
...
However when I run Kitchen I get this, instead of 'some_value1':
Chef::Mixin::Template::TemplateError
------------------------------------
undefined method `[]' for nil:NilClass
How should I send the template variable to use it this way?
EDIT: This applies only to Ruby in general and not to Chef in particular.
Pass a nested hash:
template 'template_name' do
source 'template_source.erb'
variables (
my_namespace_1: {
some_attribute1: node['my_cookbook']['some_namespace1']['some_attribute1']
}
)
end
But rather than copying the values verbatim you can use the full power of the Hash class to slice, dice and merge together whatever you want:
template 'template_name' do
source 'template_source.erb'
variables (
node['my_cookbook'].slice('some_namespace1', 'some_namespace2')
)
end
One gotcha in Ruby that you have tripped on is that symbols are usually used as hash keys:
# newer literal syntax
a_hash = {
foo: 'bar'
}
# or with the older hash-rocket syntax
a_hash = {
:foo => 'bar'
}
Symbols are extemly efficient since they are interned strings that are stored in table - when comparing symbols you compare the object ID instead of comparing each character in the string.
In fact strings are only really used when you want keys in the hash that are not valid Ruby symbols - like when building a hash of HTTP headers.
Ruby does not treat symbol and string keys indifferently:
{
foo: 'bar'
}[:foo]
# => bar
{
foo: 'bar'
}['foo']
# => nil
So to access the passed variable in the template you would use:
<%= #my_namespace_1[:some_attribute1] %>
What you have in the example should be working. I am guessing you have a typo somewhere in your original recipe that you corrected when generic-ifying the code.
Related
i'm trying (and actually succeded, but i don't understand how it works) to write a custom method for a hash in my model (I'm working on Ruby on Rails 6).
My hash looks like this
my_hash = {
[['name_1', 'slug_1']=>value_1],
[['name_2', 'slug_2']=>value_2],
[['name_1', 'slug_1']=>value_3],
[['name_2', 'slug_2']=>value_4]
}
So basically a hash of arrays. You notice that the 'keys' are arrays that repeat themselves many times, but with different values. What i want to achieve is to write a custom method that "joins" all the keys in only one key, which will have an array of values assigned, so basically i should be able to get:
my_hash = {
['name_1', 'slug_1']=>"values": [value_1, value_3],
['name_2', 'slug_2']=>"values": [value_2, value_4]
}
For that, I have this piece of code, which i use many times:
my_hash.inject({}) do |hash, record|
# each record has the following format => [["unit_name", "axis.slug"]=>average_value(float)]
keys, value = record
# now keys has ["unit_name", "axis.slug"] and values equals average_value
hash[keys.first] ||= {}
hash[keys.first][keys.last] = value.to_f
hash
end
Since I use this many times, i wanted to write a custom method, so i did:
def format_hash_data my_hash
my_hash.inject({}) do |hash, record|
# each record has the following format => [["unit_name", "axis.slug"]=>average_value(float)]
keys, value = record
# now keys has ["unit_name", "axis.slug"] and values equals average_value
hash[keys.first] ||= {}
hash[keys.first][keys.last] = value.to_f
hash
end
end
And used it like: my_hash = format_hash_data(my_hash) with no success(it threw an error saying that 'format_hash_data' was not a valid method for the class).
So I fiddled around and added 'self' to the name of the method, leaving:
def self.format_hash_data my_hash
my_hash.inject({}) do |hash, record|
# each record has the following format => [["unit_name", "axis.slug"]=>average_value(float)]
keys, value = record
# now keys has ["unit_name", "axis.slug"] and values equals average_value
hash[keys.first] ||= {}
hash[keys.first][keys.last] = value.to_f
hash
end
end
Which, to my surprise, worked flawlessly when using my_hash = format_hash_data(my_hash)
I don't really understand why adding 'self' makes my code works, maybe anyone can shed some light? I tried using things like send() or instance_eval first, to just send the piece of code to the actual hash as a method (something like my_hash.instance_eval(my_method)) but I couldn't get it working.
I'm sorry about the long explanation, I hope i was clear enough so any of you who had this same dilemma can understand. Thanks in advance.
Prepending self. to the method name makes it a class method instead of an instance method. If you are not sure of the difference, you should look it up as it is fundamental to properly defining and using classes and methods.
As a class method, you would use it as:
my_hash = MyHash.format_hash_data(my_hash)
Or if you're in scope of the class, simply my_hash = format_hash_data(my_hash), which is why it worked in your case with the self. prepended (class method definition).
If you want to define it as an instance method (a method that is defined for the instance), you would use it like so:
my_hash = my_hash.format_hash_data
And the definition would use the implicit self of the instance:
def format_hash_data
self.inject({}) do |hash, record|
# each record has the following format => [["unit_name", "axis.slug"]=>average_value(float)]
keys, value = record
# now keys has ["unit_name", "axis.slug"] and values equals average_value
hash[keys.first] ||= {}
hash[keys.first][keys.last] = value.to_f
hash
end
end
In a Rails environment i get a params hash from a form.
Besides others, like
params[:vocab][:name]
params[:vocab][:priority]
I have the 3 fields tag_1, tag_2, tag_3 whose values are stored in
params[:vocab][:tag_1]
params[:vocab][:tag_2]
params[:vocab][:tag_3]
My question is:
Can I iterate over these three hash keys?
I want to do sth like
for i in 1..3 do
iterator = ":tag_" + i.to_s
puts params[:vocab][iterator.to_sym]
end
but that doesn't work.
Your approach doesn't work because:
':tag_2'.to_sym
# => :":tag_2"
so it would work if you remove leading colon from ":tag_" + i.to_s
You can also do it this way, iterating over keys:
[:tag_1, :tag_2, :tag_3].each { |key| puts params[:vocab][key] }
You can construct symbols via interpolation; prefixing a string with a colon will cause Ruby to interpret it as a symbol.
(1..3).each do |i|
puts params[:vocab][:"tag_#{i}"]
end
I am learning rails and going back to ruby to understand how methods in rails (and ruby really work). When I see method calls like:
validates :first_name, :presence => true
I get confused. How do you write methods in ruby that accept symbols or hashes. The source code for the validates method is confusing too. Could someone please simplify this topic of using symbols as arguments in ruby class and instance methods for me?
UPDATE:
Good one #Dave! But What I was trying out was something like:
def full_name (:first_name, :last_name)
#first_name = :first_name
#last_name = :last_name
p "#{#first_name} #{last_name}"
end
full_name("Breta", "Von Sustern")
Which obviously raises errors. I am trying to understand: Why is passing symbols like this as arguments wrong if symbols are just like any other value?
Symbols and hashes are values like any other, and can be passed like any other value type.
Recall that ActiveRecord models accept a hash as an argument; it ends up being similar to this (it's not this simple, but it's the same idea in the end):
class User
attr_accessor :fname, :lname
def initialize(args)
#fname = args[:fname] if args[:fname]
#lname = args[:lname] if args[:lname]
end
end
u = User.new(:fname => 'Joe', :lname => 'Hacker')
This takes advantage of not having to put the hash in curly-brackets {} unless you need to disambiguate parameters (and there's a block parsing issue as well when you skip the parens).
Similarly:
class TestItOut
attr_accessor :field_name, :validations
def initialize(field_name, validations)
#field_name = field_name
#validations = validations
end
def show_validations
puts "Validating field '#{field_name}' with:"
validations.each do |type, args|
puts " validator '#{type}' with args '#{args}'"
end
end
end
t = TestItOut.new(:name, presence: true, length: { min: 2, max: 10 })
t.show_validations
This outputs:
Validating field 'name' with:
validator 'presence' with args 'true'
validator 'length' with args '{min: 2, max: 10}'
From there you can start to see how things like this work.
I thought I'd add an update for Ruby 2+ since this is the first result I found for 'symbols as arguments'.
Since Ruby 2.0.0 you can also use symbols when defining a method. When calling the method these symbols will then act almost the same as named optional parameters in other languages. See example below:
def variable_symbol_method(arg, arg_two: "two", arg_three: "three")
[arg, arg_two, arg_three]
end
result = variable_symbol_method :custom_symbol, arg_three: "Modified symbol arg"
# result is now equal to:
[:custom_symbol, "two", "Modified symbol arg"]
As shown in the example, we omit arg_two: when calling the method and in the method body we can still access it as variable arg_two. Also note that the variable arg_three is indeed altered by the function call.
In Ruby, if you call a method with a bunch of name => value pairs at the end of the argument list, these get automatically wrapped in a Hash and passed to your method as the last argument:
def foo(kwargs)
p kwargs
end
>> foo(:abc=>"def", 123=>456)
{:abc=>"def", 123=>456}
>> foo("cabbage")
"cabbage"
>> foo(:fluff)
:fluff
There's nothing "special" about how you write the method, it's how you call it. It would be perfectly legal to just pass a regular Hash object as the kwargs parameter. This syntactic shortcut is used to implement named parameters in an API.
A Ruby symbol is just a value as any other, so in your example, :first_name is just a regular positional argument. :presence is a symbol used as a Hash key – any type can be used as a Hash key, but symbols are a common choice because they're immutable values.
I think all replies have missed the point of question; and the fact it is asked by someone who is - I guess - not clear on what a symbol is ?
As a newcomer to Ruby I had similar confusions and to me an answer like following would have made more sense
Method Arguments are local variables populated by passed in values.
You cant use symbols as Arguments by themselves, as you cant change value of a symbol.
Symbols are not limited to hashes. They are identifiers, without the extra storage space of a string. It's just a way to say "this is ...."
A possible function definition for the validates call could be (just to simplify, I don't know off the top of my head what it really is):
def validates(column, options)
puts column.to_s
if options[:presence]
puts "Found a presence option"
end
end
Notice how the first symbol is a parameter all of its own, and the rest is the hash.
This question already has answers here:
How to avoid NoMethodError for nil elements when accessing nested hashes? [duplicate]
(4 answers)
Closed 7 years ago.
In Rails we can do the following in case a value doesn't exist to avoid an error:
#myvar = #comment.try(:body)
What is the equivalent when I'm digging deep into a hash and don't want to get an error?
#myvar = session[:comments][#comment.id]["temp_value"]
# [:comments] may or may not exist here
In the above case, session[:comments]try[#comment.id] doesn't work. What would?
You forgot to put a . before the try:
#myvar = session[:comments].try(:[], #comment.id)
since [] is the name of the method when you do [#comment.id].
The announcement of Ruby 2.3.0-preview1 includes an introduction of Safe navigation operator.
A safe navigation operator, which already exists in C#, Groovy, and
Swift, is introduced to ease nil handling as obj&.foo. Array#dig and
Hash#dig are also added.
This means as of 2.3 below code
account.try(:owner).try(:address)
can be rewritten to
account&.owner&.address
However, one should be careful that & is not a drop in replacement of #try. Take a look at this example:
> params = nil
nil
> params&.country
nil
> params = OpenStruct.new(country: "Australia")
#<OpenStruct country="Australia">
> params&.country
"Australia"
> params&.country&.name
NoMethodError: undefined method `name' for "Australia":String
from (pry):38:in `<main>'
> params.try(:country).try(:name)
nil
It is also including a similar sort of way: Array#dig and Hash#dig. So now this
city = params.fetch(:[], :country).try(:[], :state).try(:[], :city)
can be rewritten to
city = params.dig(:country, :state, :city)
Again, #dig is not replicating #try's behaviour. So be careful with returning values. If params[:country] returns, for example, an Integer, TypeError: Integer does not have #dig method will be raised.
The most beautiful solution is an old answer by Mladen Jablanović, as it lets you to dig in the hash deeper than you could with using direct .try() calls, if you want the code still look nice:
class Hash
def get_deep(*fields)
fields.inject(self) {|acc,e| acc[e] if acc}
end
end
You should be careful with various objects (especially params), because Strings and Arrays also respond to :[], but the returned value may not be what you want, and Array raises exception for Strings or Symbols used as indexes.
That is the reason why in the suggested form of this method (below) the (usually ugly) test for .is_a?(Hash) is used instead of (usually better) .respond_to?(:[]):
class Hash
def get_deep(*fields)
fields.inject(self) {|acc,e| acc[e] if acc.is_a?(Hash)}
end
end
a_hash = {:one => {:two => {:three => "asd"}, :arr => [1,2,3]}}
puts a_hash.get_deep(:one, :two ).inspect # => {:three=>"asd"}
puts a_hash.get_deep(:one, :two, :three ).inspect # => "asd"
puts a_hash.get_deep(:one, :two, :three, :four).inspect # => nil
puts a_hash.get_deep(:one, :arr ).inspect # => [1,2,3]
puts a_hash.get_deep(:one, :arr, :too_deep ).inspect # => nil
The last example would raise an exception: "Symbol as array index (TypeError)" if it was not guarded by this ugly "is_a?(Hash)".
The proper use of try with a hash is #sesion.try(:[], :comments).
#session.try(:[], :comments).try(:[], commend.id).try(:[], 'temp_value')
Update: As of Ruby 2.3 use #dig
Most objects that respond to [] expect an Integer argument, with Hash being an exception that will accept any object (such as strings or symbols).
The following is a slightly more robust version of Arsen7's answer that supports nested Array, Hash, as well as any other objects that expect an Integer passed to [].
It's not fool proof, as someone may have created an object that implements [] and does not accept an Integer argument. However, this solution works great in the common case e.g. pulling nested values from JSON (which has both Hash and Array):
class Hash
def get_deep(*fields)
fields.inject(self) { |acc, e| acc[e] if acc.is_a?(Hash) || (e.is_a?(Integer) && acc.respond_to?(:[])) }
end
end
It can be used the same as Arsen7's solution but also supports arrays e.g.
json = { 'users' => [ { 'name' => { 'first_name' => 'Frank'} }, { 'name' => { 'first_name' => 'Bob' } } ] }
json.get_deep 'users', 1, 'name', 'first_name' # Pulls out 'Bob'
say you want to find params[:user][:email] but it's not sure whether user is there in params or not. Then-
you can try:
params[:user].try(:[], :email)
It will return either nil(if user is not there or email is not there in user) or otherwise the value of email in user.
As of Ruby 2.3 this gets a little easier. Instead of having to nest try statements or define your own method you can now use Hash#dig (documentation).
h = { foo: {bar: {baz: 1}}}
h.dig(:foo, :bar, :baz) #=> 1
h.dig(:foo, :zot) #=> nil
Or in the example above:
session.dig(:comments, #comment.id, "temp_value")
This has the added benefit of being more like try than some of the examples above. If any of the arguments lead to the hash returning nil then it will respond nil.
#myvar = session.fetch(:comments, {}).fetch(#comment.id, {})["temp_value"]
From Ruby 2.0, you can do:
#myvar = session[:comments].to_h[#comment.id].to_h["temp_value"]
From Ruby 2.3, you can do:
#myvar = session.dig(:comments, #comment.id, "temp_value")
Another approach:
#myvar = session[:comments][#comment.id]["temp_value"] rescue nil
This might also be consider a bit dangerous because it can hide too much, personally I like it.
If you want more control, you may consider something like:
def handle # just an example name, use what speaks to you
raise $! unless $!.kind_of? NoMethodError # Do whatever checks or
# reporting you want
end
# then you may use
#myvar = session[:comments][#comment.id]["temp_value"] rescue handle
When you do this:
myhash[:one][:two][:three]
You're just chaining a bunch of calls to a "[]" method, an the error occurs if myhash[:one] returns nil, because nil doesn't have a [] method. So, one simple and rather hacky way is to add a [] method to Niclass, which returns nil: i would set this up in a rails app as follows:
Add the method:
#in lib/ruby_extensions.rb
class NilClass
def [](*args)
nil
end
end
Require the file:
#in config/initializers/app_environment.rb
require 'ruby_extensions'
Now you can call nested hashes without fear: i'm demonstrating in the console here:
>> hash = {:foo => "bar"}
=> {:foo=>"bar"}
>> hash[:foo]
=> "bar"
>> hash[:doo]
=> nil
>> hash[:doo][:too]
=> nil
Andrew's answer didn't work for me when I tried this again recently. Maybe something has changed?
#myvar = session[:comments].try('[]', #comment.id)
The '[]' is in quotes instead of a symbol :[]
Try to use
#myvar = session[:comments][#comment.id]["temp_value"] if session[:comments]
When I assign in my controller
#my_hash = { :my_key => :my_value }
and test that controller by doing
get 'index'
assigns(:my_hash).should == { :my_key => :my_value }
then I get the following error message:
expected: {:my_key=>:my_value},
got: {"my_key"=>:my_value} (using ==)
Why does this automatic symbol to string conversion happen? Why does it affect the key of the hash?
It may end up as a HashWithIndifferentAccess if Rails somehow gets ahold of it, and that uses string keys internally. You might want to verify the class is the same:
assert_equal Hash, assigns(:my_hash).class
Parameters are always processed as the indifferent access kind of hash so you can retrieve using either string or symbol. If you're assigning this to your params hash on the get or post call, or you might be getting converted.
Another thing you can do is freeze it and see if anyone attempts to modify it because that should throw an exception:
#my_hash = { :my_key => :my_value }.freeze
You might try calling "stringify_keys":
assigns(:my_hash).should == { :my_key => :my_value }.stringify_keys
AHA! This is happening not because of Rails, per se, but because of Rspec.
I had the same problem testing the value of a Hashie::Mash in a controller spec (but it applies to anything that quacks like a Hash)
Specifically, in a controller spec, when you call assigns to access the instance variables set in the controller action, it's not returning exactly the instance variable you set, but rather, a copy of the variable that Rspec stores as a member of a HashWithIndifferentAccess (containing all the assigned instance variables). Unfortunately, when you stick a Hash (or anything that inherits from Hash) into a HashWithIndifferentAccess, it is automatically converted to an instance of that same, oh-so-convenient but not-quite-accurate class :)
The easiest work-around is to avoid the conversion by accessing the variable directly, before it's converted "for your convenience", using: controller.view_assigns['variable_name'] (note: the key here must be a string, not a symbol)
So the test in the original post should pass if it were changed to:
get 'index'
controller.view_assigns['my_hash'].should == { :my_key => :my_value }
(of course, .should is no longer supported in new versions of RSpec, but just for comparison I kept it the same)
See this article for further explanation:
http://ryanogles.by/rails/hashie/rspec/testing/2012/12/26/rails-controller-specs-dont-always-play-nice-with-hashie.html
I know this is old, but if you are upgrading from Rails-3 to 4, your controller tests may still have places where Hash with symbol keys was used but compared with the stringified version, just to prevent the wrong expectation.
Rails-4 has fixed this issue: https://github.com/rails/rails/pull/5082 .
I suggest updating your tests to have expectations against the actual keys.
In Rails-3 the assigns method converts your #my_hash to HashWithIndifferentAccess that stringifies all the keys -
def assigns(key = nil)
assigns = #controller.view_assigns.with_indifferent_access
key.nil? ? assigns : assigns[key]
end
https://github.com/rails/rails/blob/3-2-stable/actionpack/lib/action_dispatch/testing/test_process.rb#L7-L10
Rails-4 updated it to return the original keys -
def assigns(key = nil)
assigns = {}.with_indifferent_access
#controller.view_assigns.each { |k, v| assigns.regular_writer(k, v) }
key.nil? ? assigns : assigns[key]
end
https://github.com/rails/rails/blob/4-0-stable/actionpack/lib/action_dispatch/testing/test_process.rb#L7-L11
You can also pass your Hash object to the initializer of HashWithIndifferentAccess.
You can use HashWithIndifferentAccess.new as Hash init:
Thor::CoreExt::HashWithIndifferentAccess.new( to: 'mail#somehost.com', from: 'from#host.com')