key: value vs key :value in ruby? - ruby

In associations, we usually do a :b (belongs_to :something). When we create a hash with symbol keys we usually do a: b. Having said that my question is what is the difference between the two syntax. Also is there any logic to memorize when to use which convention?

This isn't about convention, it's about syntax.
:something is a Symbol.
belongs_to :something is a method that is being sent to an implicit self while also omitting the parentheses. We can write it as follows to make that obvious:
self.belongs_to(:something)
:something is thus just an argument being passed to the method belongs_to.
In a Hash, we can use a Symbol as the key:
hash = { :something => "hello" }
Ruby introduced an alternative syntax in version 1.9 that can be used when the key is a symbol:
hash = { something: "hello" }
Both versions are equivalent.

The difference here is between method call and hash key. They look very similar and can easily be confused if you're not sure what you're looking for.
In your first example:
a :b
In long-form this is:
a(:b)
Where now that's clearly an argument (:b) to a method (a).
In the other form it's different:
a: b
Where if that's part of a method call like this:
f a: b
Then that actually means:
f(a: b)
Which in full form is:
f({ a: b })
Where that's a hash definition following the key: value style. Here :a is the key (Symbol) and b is the value (variable or method call).
You'll often see a: :b where you have symbol key and value.
To differentiate between these two forms when reading code have a look at where the code appears to get a sense of context. When writing code, always frame your thinking in terms of method calls and hash definitions more clearly.

Related

Convert string into hash in ruby

I have a string like this "{ssl:true,sslAllowInvalidCertificates:true}"
(please note that the string can contain any no. of key/value pairs)
I want to convert this into hash, in Ruby, like this:
{ssl:true,sslAllowInvalidCertificates:true}
(Please note that the output is to be exactly similar to the above. It should not be in 'generic' hash notation like
{"ssl" => "true","sslAllowInvalidCertificates" => "true"}
The MongoDB client library can recognize the option only if it is exactly same as per requirement, else throws error.
How to do this in ruby?
TIA!
TL;DR
To convert your String into a Hash, you either have to parse it yourself, or call Kernel#eval on it. Either way, your real issue seems to be round-tripping this back to a string in your expected format. One way to do that is to re-open the Hash class and add a custom output method, rather than relying on the Hash#to_s method aliased to Hash#inspect.
String to Hash
If you trust the data source and have some mechanism to sanitize or check the String for potential arbitrary code execution, #eval is certainly the easiest thing you can do. I'd personally add a tiny bit of safety by making sure the String isn't tainted first, though. For example:
str = "{ssl:true,sslAllowInvalidCertificates:true}"
raise "string tainted: #{str}" if str.tainted?
hsh = eval str
#=> {:ssl=>true, :sslAllowInvalidCertificates=>true}
However, if you don't trust the source or structure of your String, you can parse and validate the information yourself using some variant of the following as a starting point:
hsh = Hash[str.scan(/\w+/).each_slice(2).to_a]
#=> {:ssl=>true, :sslAllowInvalidCertificates=>true}
Hash to Custom String
If you then want to dump it back out to your custom format as a String, you can monkeypatch the Hash class or add a singleton method to a given Hash instance to provide a #to_mongo method. For example:
class Hash
def to_mongo
str = self.map { |k, v| '%s:%s' % [k, v] }.join ?,
'{%s}' % str
end
end
Calling this method on your Hash instance will yield the results you seem to want:
hsh.to_mongo
#=> "{ssl:true,sslAllowInvalidCertificates:true}"
It seems there is some confusion surrounding the fat arrow syntax for hashes in ruby. You should be able to run eval on the string to generate the following hash:
{:ssl=>true, :sslAllowInvalidCertificates=>true}
You mention that the output cannot be in "generic" hash notation, which I assume is referring to the fat arrow notation used in your example.
Since Ruby 1.9, a new syntax can be used to create a hash
{foo: "bar"}
rather than the previous
{:foo => "bar"}
Interactive ruby consoles, such as irb and pry, try to print human friendly strings for the hash. Creating a hash with either of the two previous syntaxes will produce the same result in the console:
{:foo=>"bar"}
However, in memory, both of the objects are equivalent.
(There is the caveat that your "generic" hash example uses strings as keys. If that's what you're referring to, you can call #symbolize_keys on the hash)

How does a code block in Ruby know what variable belongs to an aspect of an object?

Consider the following:
(1..10).inject{|memo, n| memo + n}
Question:
How does n know that it is supposed to store all the values from 1..10? I'm confused how Ruby is able to understand that n can automatically be associated with (1..10) right away, and memo is just memo.
I know Ruby code blocks aren't the same as the C or Java code blocks--Ruby code blocks work a bit differently. I'm confused as to how variables that are in between the upright pipes '|' will automatically be assigned to parts of an object. For example:
hash1 = {"a" => 111, "b" => 222}
hash2 = {"b" => 333, "c" => 444}
hash1.merge(hash2) {|key, old, new| old}
How do '|key, old, new|' automatically assign themselves in such a way such that when I type 'old' in the code block, it is automatically aware that 'old' refers to the older hash value? I never assigned 'old' to anything, just declared it. Can someone explain how this works?
The parameters for the block are determined by the method definition. The definition for reduce/inject is overloaded (docs) and defined in C, but if you wanted to define it, you could do it like so (note, this doesn't cover all the overloaded cases for the actual reduce definition):
module Enumerable
def my_reduce(memo=nil, &blk)
# if a starting memo is not given, it defaults to the first element
# in the list and that element is skipped for iteration
elements = memo ? self : self[1..-1]
memo ||= self[0]
elements.each { |element| memo = blk.call(memo, element) }
memo
end
end
This method definition determines what values to use for memo and element and calls the blk variable (a block passed to the method) with them in a specific order.
Note, however, that blocks are not like regular methods, because they don't check the number of arguments. For example: (note, this example shows the usage of yield which is another way to pass a block parameter)
def foo
yield 1
end
# The b and c variables here will be nil
foo { |a, b, c| [a,b,c].compact.sum } # => 1
You can also use deconstruction to define variables at the time you run the block, for example if you wanted to reduce over a hash you could do something like this:
# this just copies the hash
{a: 1}.reduce({}) { |memo, (key, val)| memo[key] = val; memo }
How this works is, calling reduce on a hash implicitly calls to_a, which converts it to a list of tuples (e.g. {a: 1}.to_a = [[:a, 1]]). reduce passes each tuple as the second argument to the block. In the place where the block is called, the tuple is deconstructed into separate key and value variables.
A code block is just a function with no name. Like any other function, it can be called multiple times with different arguments. If you have a method
def add(a, b)
a + b
end
How does add know that sometimes a is 5 and sometimes a is 7?
Enumerable#inject simply calls the function once for each element, passing the element as an argument.
It looks a bit like this:
module Enumerable
def inject(memo)
each do |el|
memo = yield memo, el
end
memo
end
end
And memo is just memo
what do you mean, "just memo"? memo and n take whatever values inject passes. And it is implemented to pass accumulator/memo as first argument and current collection element as second argument.
How do '|key, old, new|' automatically assign themselves
They don't "assign themselves". merge assigns them. Or rather, passes those values (key, old value, new value) in that order as block parameters.
If you instead write
hash1.merge(hash2) {|foo, bar, baz| bar}
It'll still work exactly as before. Parameter names mean nothing [here]. It's actual values that matter.
Just to simplify some of the other good answers here:
If you are struggling understanding blocks, an easy way to think of them is as a primitive and temporary method that you are creating and executing in place, and the values between the pipe characters |memo| is simply the argument signature.
There is no special special concept behind the arguments, they are simply there for the method you are invoking to pass a variable to, like calling any other method with an argument. Similar to a method, the arguments are "local" variables within the scope of the block (there are some nuances to this depending on the syntax you use to call the block, but I digress, that is another matter).
The method you pass the block to simply invokes this "temporary method" and passes the arguments to it that it is designed to do. Just like calling a method normally, with some slight differences, such as there are no "required" arguments. If you do not define any arguments to receive, it will happily just not pass them instead of raising an ArgumentError. Likewise, if you define too many arguments for the block to receive, they will simply be nil within the block, no errors for not being defined.

Changing the object identity of a formal parameter

I will try to explain the problem with a simple example:
def enclose(x)
[x]
end
In my application, enclose does something more complex, but in essence it returns an array, the content of which is solely determined by the value of the parameter x. I could it use it like this:
foo = 'abcd'
....
foo = enclose(foo)
Now to my question: Is it possible to write a method enclose!, which simply replaces the parameter by its enclosed version, so that the example could be written as
foo = 'abcd'
....
enclose!(foo)
Since Ruby passes arguments by reference, I thought hat this could maybe be possible. The naive approach,
def enclose!(x)
x = [x]
end
does not work - I think this is because the assignment creates a new object and leaves the actual parameter untouched.
Is there way, that I can achieve my goal? I think in Smallalk, there would be a method become which would change the object identity, but I didn't find something similar in Ruby.
Since Ruby passes arguments by reference, I thought hat this could maybe be possible.
Ruby is pass-by-value, not pass-by-reference, which you have proven yourself, because otherwise your code would have worked.
I think in Smallalk, there would be a method become which would change the object identity, but I didn't find something similar in Ruby.
There isn't. Ruby has neither pass-by-reference nor become:, what you want simply isn't possible.
There's some other interesting posts about how ruby is pass by value, but the values are references.
What it boils down to is, you can modify the variable an object refers to, but you cannot change it to refer to another object.
> a = [1]
=> [1]
> def add_a(array)
> array << "a"
> end
=> :add_a
> add_a a
=> [1, "a"]
> a
=> [1, "a"]
There is a way to sort of accomplish what you are asking for but it's not quite pretty. Ruby has this concept of a binding (http://ruby-doc.org/core-2.2.0/Binding.html), which is like a CallContext in .NET.
You can do something like this:
def enclose(x)
[x]
end
def enclose!(x, binding)
eval("#{x} = [#{x}]", binding)
end
foo = 'abcd'
enclose!(:foo, binding)
=> ["abcd"]
In the script above, the :foo means you are passing the name of the variable, and the binding (context) where to find its value. Then you're dynamically calling eval to evaluate the assignment operation foo = [foo].

ruby - omitting default parameter before splat

I have the following method signature
def invalidate_cache(suffix = '', *args)
# blah
end
I don't know if this is possible but I want to call invalidate_cache and omit the first argument sometimes, for example:
middleware.invalidate_cache("test:1", "test")
This will of course bind the first argument to suffix and the second argument to args.
I would like both arguments to be bound to args without calling like this:
middleware.invalidate_cache("", "test:1", "test")
Is there a way round this?
Use keyword arguments (this works in Ruby 2.0+):
def invalidate_cache(suffix: '', **args) # note the double asterisk
[suffix, args]
end
> invalidate_cache(foo: "any", bar: 4242)
=> ["", {:foo=>"any", :bar=>4242}]
> invalidate_cache(foo: "any", bar: 4242, suffix: "aaaaa")
=> ["aaaaa", {:foo=>"any", :bar=>4242}]
Note that you will have the varargs in a Hash instead of an Array and keys are limited to valid Symbols.
If you need to reference the arguments by position, create an Array from the Hash with Hash#values.
How about you create a wrapper method for invalidate_cache that just calls invalidate_cache with the standard argument for suffix.
In order to do this, your code has to have some way of telling the difference between a suffix, and just another occurrence of args. E.g. In your first example, how is your program supposed to know that you didn't mean for "test:1" to actually be the suffix?
If you can answer that question, you can write some code to make the method determine at run time whether or not you provided a suffix. For example, say you specify that all suffixes have to start with a period (and no other arguments will). Then you could do something like this:
def invalidate_cache(*args)
suffix = (args.first =~ /^\./) ? args.shift : ''
[suffix, args]
end
invalidate_cache("test:1", "test") #=> ["", ["test:1", "test"]]
invalidate_cache(".jpeg", "test:1", "test") #=> [".jpeg", ["test:1", "test"]]
If, however, there actually is no way of telling the difference between an argument meant as a suffix and one meant to be lumped in with args, then you're kind of stuck. You'll either have to keep passing suffix explicitly, change the method signature to use keyword arguments (as detailed in karatedog's answer), or take an options hash.

undefined method `assoc' for #<Hash:0x10f591518> (NoMethodError)

I'm trying to return a list of values based on user defined arguments, from hashes defined in the local environment.
def my_method *args
#initialize accumulator
accumulator = Hash.new(0)
#define hashes in local environment
foo=Hash["key1"=>["var1","var2"],"key2"=>["var3","var4","var5"]]
bar=Hash["key3"=>["var6"],"key4"=>["var7","var8","var9"],"key5"=>["var10","var11","var12"]]
baz=Hash["key6"=>["var13","var14","var15","var16"]]
#iterate over args and build accumulator
args.each do |x|
if foo.has_key?(x)
accumulator=foo.assoc(x)
elsif bar.has_key?(x)
accumulator=bar.assoc(x)
elsif baz.has_key?(x)
accumulator=baz.assoc(x)
else
puts "invalid input"
end
end
#convert accumulator to list, and return value
return accumulator = accumulator.to_a {|k,v| [k].product(v).flatten}
end
The user is to call the method with arguments that are keywords, and the function to return a list of values associated with each keyword received.
For instance
> my_method(key5,key6,key1)
=> ["var10","var11","var12","var13","var14","var15","var16","var1","var2"]
The output can be in any order. I received the following error when I tried to run the code:
undefined method `assoc' for #<Hash:0x10f591518> (NoMethodError)
Please would you point me how to troubleshoot this? In Terminal assoc performs exactly how I expect it to:
> foo.assoc("key1")
=> ["var1","var2"]
I'm guessing you're coming to Ruby from some other language, as there is a lot of unnecessary cruft in this method. Furthermore, it won't return what you expect for a variety of reasons.
`accumulator = Hash.new(0)`
This is unnecessary, as (1), you're expecting an array to be returned, and (2), you don't need to pre-initialize variables in ruby.
The Hash[...] syntax is unconventional in this context, and is typically used to convert some other enumerable (usually an array) into a hash, as in Hash[1,2,3,4] #=> { 1 => 2, 3 => 4}. When you're defining a hash, you can just use the curly brackets { ... }.
For every iteration of args, you're assigning accumulator to the result of the hash lookup instead of accumulating values (which, based on your example output, is what you need to do). Instead, you should be looking at various array concatenation methods like push, +=, <<, etc.
As it looks like you don't need the keys in the result, assoc is probably overkill. You would be better served with fetch or simple bracket lookup (hash[key]).
Finally, while you can call any method in Ruby with a block, as you've done with to_a, unless the method specifically yields a value to the block, Ruby will ignore it, so [k].product(v).flatten isn't actually doing anything.
I don't mean to be too critical - Ruby's syntax is extremely flexible but also relatively compact compared to other languages, which means it's easy to take it too far and end up with hard to understand and hard to maintain methods.
There is another side effect of how your method is constructed wherein the accumulator will only collect the values from the first hash that has a particular key, even if more than one hash has that key. Since I don't know if that's intentional or not, I'll preserve this functionality.
Here is a version of your method that returns what you expect:
def my_method(*args)
foo = { "key1"=>["var1","var2"],"key2"=>["var3","var4","var5"] }
bar = { "key3"=>["var6"],"key4"=>["var7","var8","var9"],"key5"=>["var10","var11","var12"] }
baz = { "key6"=>["var13","var14","var15","var16"] }
merged = [foo, bar, baz].reverse.inject({}, :merge)
args.inject([]) do |array, key|
array += Array(merged[key])
end
end
In general, I wouldn't define a method with built-in data, but I'm going to leave it in to be closer to your original method. Hash#merge combines two hashes and overwrites any duplicate keys in the original hash with those in the argument hash. The Array() call coerces an array even when the key is not present, so you don't need to explicitly handle that error.
I would encourage you to look up the inject method - it's quite versatile and is useful in many situations. inject uses its own accumulator variable (optionally defined as an argument) which is yielded to the block as the first block parameter.

Resources