Given a JSON object
{"a": 1, "b":2}
and a value object that is derived from a struct:
class A < Stuct.new(:a, :b)
end
How would I make an instance of A that has the values from the JSON?
I am trying:
a = A.new(JSON.parse({a:1,b:2}.to_json).values)
=> #<struct A a=[1, 2], b=nil>
But I would expect a->1, and b->2
Try using:
a = A.new(*JSON[json].values)
a.class # => A < #<Class:0x00000102955828>
The problem is that values returns an array, but you need the individual elements of the array. Using * "splats" the array back into its components, which makes Struct happy when you pass the values to new.
EDIT:
This will fail if the ordering of the JSON and the Struct do not match!
This forces the order of the values.
a = A.new(*JSON[json].values_at('a', 'b'))
{
:a => 1,
:b => 2
}
a.class # => A < #<Class:0x00000102955828>
JSON preserves the hash insertion order, as does Ruby, so, JSON rendered and parsed by Ruby will be correct. JSON rendered by something that doesn't preserve the order could be a problem, but values_at fixes the problem.
Note that JSON converts symbols to strings, so the keys passed to values_at have to be strings, not symbols.
If it does not have to be a predefined struct, this will work
a = Struct.new(*json.keys).new(*json.values)
You can use the splat operator to pass the array values as arguments to the new function.
a = A.new(*{a:1,b:2}.values)
Related
I am storing the result in hash like this
I have assigned the result like this
Result['UserCreation']={"Test1"=>"Rajagopalan"}
So it created the hash like this
{"UserCreation"=>{"Test1"=>"Rajagopalan"}}
Now, I don't know how to assign another result for Test2. When I tend to assign result like this
Result['UserCreation']={"Test2"=>"Kali"}
It's replacing the existing result, and it's correctly doing it's Job, but I want to create result hash like given below when I assign the Result of Test2
{"UserCreation"=>{"Test1"=>"Rajagopalan","Test2"=>"Kali"}}
How can I achieve this?
Let us assume in this order I receive parameters
'UserCreation',{"Test1"=>"Rajagopalan"},
'UserCreation',{"Test2"=>"Kali"}
'contactcreate',{"Test2"=>"Kali"}
Result
{"UserCreation"=>{"Test1"=>"Rajagopalan","Test2"=>"Kali"},'contactcreate'=>{"Test2"=>"Kali"}}
All these values are the parameter to the functions.
You should use Hash#merge! method:
Result['UserCreation'].merge!({"Test2"=>"Kali"})
Here's a brief explanation:
When you use the assignment (Result['UserCreation']={"Test2"=>"Kali"}) you completely replace the value for the particular hash key. If you want to add (merge) something inside the existing hash you should use merge! method.
Notice that you can use Hash#merge! method because you know that the value of Result['UserCreation'] is a hash itself.
Also notice that there's merge method without bang (!). The difference that bang-version will mutate (change) your object. Consider this:
hash = {}
hash.merge({'one' => 1})
# hash variable will hold its initial value
# because `merge` method will not change it.
p hash # => {}
hash.merge!('one' => 1)
# This time we use bang-version, so hash variable
# will be updated.
p hash # => {"one"=>1}
One more thing about Ruby, notice how in the bang-version we omit curly braces. It's possible to do it if the last argument you passing to the method is a Hash.
Also, by convention in Ruby snake-case is using for variable and method naming, i.e.
result = {}
result['user_creation'] = {'test_1' => 'Rajagopalan'}
result['user_creation'].merge!('test_2' => 'Kali')
Of course, there's a field to play. For example, you can set the initial value like this:
result = {'user_creation' => {}}
result['user_creation'].merge!('test_1' => 'Rajagopalan')
result['user_creation'].merge!('test_2' => 'Kali')
or event update several pairs:
result = {'user_creation' => {}}
result['user_creation'].merge!(
'test_1' => 'Rajagopalan',
'test_2' => 'Kali'
)
UPDATE
For your case if you receive these parameters:
'UserCreation',{"Test1"=>"Rajagopalan"},
'UserCreation',{"Test2"=>"Kali"}
'contactcreate',{"Test2"=>"Kali"}
suppose that the first parameter named kind and the last one named value:
# kind = 'UserCreation' and value = '{"Test1"=>"Rajagopalan"}'.
result = {}
# Here we check `result[kind]` if there's no key, a new hash will
# be assigned, otherwise the existing value will be used.
result[kind] ||= {}
result[kind].merge!(value)
Maybe you want to use Hash#store:
result = {}
result['UserCreation'] = {"Test1"=>"Rajagopalan"}
result['UserCreation'].store("Test2", "Kali")
result #=> {"UserCreation"=>{"Test1"=>"Rajagopalan", "Test2"=>"Kali"}}
I have following hash of hash of array with me:
Hash["signin"]["elementname"]["ids"]` -- here ids are "macid" and "winid"
Its structure is like elementname has two kind of ids macid and winid.
On runtime when I pass a parameter say mac then I am trying to build a hash from a existing hash which would have only macid.
So that, I am trying to convert a Hash["signin"]["elementname"]["ids"].
This should work
Hash[common_ids.map { |a|
[a[0], Hash[a[1].map { |b|
[b[0], b[1]['mac_id']]
}]]
}]
You need to do the Hash[...] part because the map method on a hash turns it into an array of key-value pairs.
Hopefully that code works for you. It worked for me in irb. You may want to rename the a and b variables to something that makes more sense.
Here's some explanation:
When you call .map on a hash, it provides each value as a [k, v] array, it expects the contents of the block to evaluate to a [k, v] array, and the result returned by .map is an array of [k, v] arrays. Hash[...] is used to turn that style of array back into a hash (see http://ruby-doc.org/core-2.1.5/Hash.html#method-c-5B-5D).
For
common_ids = {"signin"=> { "company_txt"=>{"mac_id"=> "mac_id_1", "win_id"=> "win_id_1"}, "username_txtbx"=> {"mac_id"=>"mac_id_2", "win_id"=>"win_id_2"} } }
here is what happens:
The first level block (with parameter a) gets
a = ["signin", { "company_txt"=>{"mac_id"=> "mac_id_1", "win_id"=> "win_id_1"}, "username_txtbx"=> {"mac_id"=>"mac_id_2", "win_id"=>"win_id_2"} }]
It calls map on the second entry of that array (the value part of the key-value pair) using the second block (with parameter b), which first gets
b = ["company_txt", {"mac_id"=> "mac_id_1", "win_id"=> "win_id_1"}]
It returns
["company_txt", "mac_id_1"]
Then it gets
b = ["username_txtbx", {"mac_id"=>"mac_id_2", "win_id"=>"win_id_2"}]
It returns
["username_txtbx", "mac_id_2"]
The result of this inner map is
[["company_txt", "mac_id_1"], ["username_txtbx", "mac_id_2"]]
Calling Hash[...] on this gives
{"company_txt" => "mac_id_1", "username_txtbx" => "mac_id_2"}
This is then given as the second element of the array for the outer map, resulting in
["signin", {"company_txt" => "mac_id_1", "username_txtbx" => "mac_id_2"}]
If you had a second top-level element of common_ids, it would result in the same processing. When the outer map call completes, you have
[["signin", {"company_txt" => "mac_id_1", "username_txtbx" => "mac_id_2"}], ...]
where the ... represents where additional top-level elements of common_ids would go.
Calling Hash[...] on this gives
{"signin" => {"company_txt" => "mac_id_1", "username_txtbx" => "mac_id_2"}, ...}
where the ... represents any additional top-level key-value pairs in the form k => v.
Hopefully that explanation helps.
I am new to ruby language so when I was trying to sort a hash by value
I used this method to sort:
movie_popularity.sort_by{|m,p| p}.reverse
but the the sort method returns an array while I need a hash to be returned so I used this command:
movie_popularity=Hash[*movie_popularity.sort_by{|m,p| p}.reverse.flatten]
my Question is what is the meaning of * and flatten in the above line?
Thanks =)
The * is called the "splat operator"; I'm not sure I could give you the technical definition (though I'm sure you'd find it soon enough with Google's help), but the way I'd describe it is that it basically takes the place of hand-writing multiple comma-separated values in code.
To make this more concrete, consider the case of Hash[] which you've used in your example. The Hash class has a [] class method which takes a variable number of arguments and can normally be called like this:
# Returns { "foo" => 1, "bar" => 2 }
h = Hash["foo", 1, "bar", 2]
Notice how that isn't an array or a hash or anything that I passed in; it's a (hand-written) sequence of values. The * operator allows you to achieve basically the same thing using an array--in your case, the one returned by movie_popularity.sort_by{|m,p| p}.reverse.flatten.
As for that flatten call: when you call sort_by on a hash, you're really leveraging the Enumerable module which is included in a variety of classes (most notably Array and Hash) that provide enumeration. In the case of a hash, you've probably noticed that instead of iterating over one like this:
hash.each { |value| ... }
Instead you do this:
hash.each { |key, value| ... }
That is, iterating over a hash yields two values on each iteration. So your sort_by call on its own would return a sequence of pairs. Calling flatten on this result collapses the pairs into a one-dimensional sequence of values, like this:
# Returns [1, 2, 3, 4]
[[1, 2], [3, 4]].flatten
'flatten' flattens an array: http://www.ruby-doc.org/core-1.9.3/Array.html#method-i-flatten
'*' is the splat operator: http://theplana.wordpress.com/2007/03/03/ruby-idioms-the-splat-operator/
The pertinent bit in the last url is this:
a = [[:planes, 21], [:cars, 36]]
h = Hash[*a] # => { :planes=>21, :cars=>36}
Sometimes I want a variable to always be an array, whether its a scalar or already an array.
So I normally do:
[variable].flatten
which is compatible with ruby-1.8.5, 1.8.7, 1.9.x.
With this method when variable is a string (variable = "asdf"), it gives me ["asdf"]. If it's already an array (variable = ["asdf","bvcx"]), it gives me: ["asdf","bvcx"].
Does anyone have a better way? "Better" meaning more readable, more performant, succinct or more effective in other ways.
Array(variable)
should do the trick. It uses the little known Kernel#Array method.
The way I do, and think is the standard way, is using [*...]:
variable1 = "string"
variable2 = ["element1", "element2"]
[*variable1] #=> ["string"]
[*variable2] #=> ["element1", "element2"]
You might need something like Array.eat. Most other methods either call #to_a or #to_ary on the object. If you where using [ obj ].flatten that might give surprising results. #flatten will also mangle nested arrays unless called with a level parameter and will make an extra copy of the array.
Active support provides Array.wrap, but that also calls #to_ary, which might or might not be to your liking, depending on your needs.
require 'active_support/core_ext/array/wrap'
class Array
# Coerce an object to be an array. Any object that is not an array will become
# a single element array with object at index 0.
#
# coercing nil returns an empty array.
#
def self.eat( object )
object.nil? and return []
object.kind_of?( Array ) and return object
[object]
end
end # class Array
a = { a: 3 }
p [a].flatten # => [{:a=>3}]
p [*a] # => [[:a, 3]] -> OOPS
p Array a # => [[:a, 3]] -> OOPS
p Array.wrap a # => [{:a=>3}]
p Array.eat a # => [{:a=>3}]
I have an each method that is run on some user-submitted data.
Sometimes it will be an array, other times it won't be.
Example submission:
<numbers>
<number>12345</number>
</numbers>
Another example:
<numbers>
<number>12345</number>
<number>09876</number>
</numbers>
I have been trying to do an each do on that, but when there is only one number I get a TypeError (Symbol as array index) error.
I recently asked a question that was tangentally similar. You can easily force any Ruby object into an array using Array.
p Array([1,2,3]) #-> [1,2,3]
p Array(123) #-> [123]
Of course, arrays respond to each. So if you force everying into an array, your problem should be solved.
A simple workaround is to just check if your object responds to :each; and if not, wrap it in an array.
irb(main):002:0> def foo x
irb(main):003:1> if x.respond_to? :each then x else [x] end
irb(main):005:1> end
=> nil
irb(main):007:0> (foo [1,2,3]).each { |x| puts x }
1
2
3
=> [1, 2, 3]
irb(main):008:0> (foo 5).each { |x| puts x }
5
=> [5]
It looks like the problem you want to solve is not the problem you are having.
TypeError (Symbol as array index)
That error tells me that you have an array, but are treating it like a hash and passing in a symbol key when it expects an integer index.
Also, most XML parsers provide child nodes as array, even if there is only one. So this shouldn't be necesary.
In the case of arguments to a method, you can test the object type. This allows you to pass in a single object or an array, and converts to an array only if its not one so you can treat it identically form that point on.
def foo(obj)
obj = [obj] unless obj.is_a?(Array)
do_something_with(obj)
end
Or something a bit cleaner but more cryptic
def foo(obj)
obj = [*obj]
do_something_with(obj)
end
This takes advantage of the splat operator to splat out an array if it is one. So it splats it out (or doesn't change it) and you can then wrap it an array and your good to go.
I was in the same position recently except the object I was working with was either a hash or an array of hashes. If you are using Rails, you can use Array.wrap because Array(hash) converts hashes to an array.
Array({foo: "bar"}) #=> [[:foo, "bar"]]
Array.wrap({foo: "bar"}) #=> [{:foo=>"bar"}]
Array.wrap(123) #=> [123]
Array.wrap([123]) #=> [123]
I sometimes use this cheap little trick:
[might_be_an_array].flatten.each { |x| .... }
Use the splat operator:
[*1] # => [1]
[*[1,2]] # => [1,2]
Like Mark said, you're looking for "respond_to?" Another option would be to use the conditional operator like this:
foo.respond_to? :each ? foo.each{|x| dostuff(x)} : dostuff(foo);
What are you trying to do with each number?
You should try to avoid using respond_to? message as it is not a very object oriented aproach.
Check if is it possible to find in the xml generator code where it is assigning an integer value when there is just one <"number"> tag and modify it to return an array.
Maybe it is a complex task, but I would try to do this in order to get a better OO design.
I don't know much anything about ruby, but I'd assume you could cast (explicitly) the input to an array - especially given that if the input is simply one element longer it's automatically converted to an array.
Have you tried casting it?
If your input is x, use x.to_a to convert your input into an array.
[1,2,3].to_a
=> [1, 2, 3]
1.to_a
=> [1]
"sample string".to_a
=> ["sample string"]
Edit: Newer versions of Ruby seem to not define a default .to_a for some standard objects anymore. You can always use the "explicit cast" syntax Array(x) to achieve the same effect.