I need to merge several hashes. I mean having:
a = {:x => 72, :y => 2}
b = {:x => 34, :y => 24}
c = a.deep_merge!(b)
and c is {:x => 106, :y => 26}.
Is there a feature in Crystal like deep_merge in Ruby?
EDIT : The real hash is more complex
{"request" => {"duration" => 15100012.0, "total" => 252562.0, "per_second" => 16725.0, "bytes" => 43440664.0}, "error" => {"socket" => 0.0, "read" => 25.0, "write" => 0.0, "http" => 0.0, "timeout" => 0.0}, "latency" => {"minimum" => 234.0, "maximum" => 219466.0, "average" => 7640.0, "deviation" => 12940.0}, "percentile" => {"fifty" => 2732.0, "ninety" => 20335.0, "ninety_nine" => 65824.0, "ninety_nine_ninety" => 199536.0}}
in fact it's a json transformed in a hash with PullParser
my final goal is to add create a new hash composed by values (added) from this hash on a loop
What you are describing is not a deep merge (a recursive operation), but just a custom merge strategy.
It can be solved by calling Hash#merge with a block:
a = {:x => 72, :y => 2}
b = {:x => 34, :y => 24}
c = a.merge(b) { |_, v1, v2| v1 + v2 }
pp c # => {:x => 106, :y => 26}
Related
using this Hash object
{"foo" => {"bar" => 1, "baz" => 2}, "bla" => [1,2,3]}
I want to produce this array of Hash objects
[
{"foo" => "*", "bla" => [1,2,3]},
{"foo" => {"bar" => "*", "baz" => 2}, "bla" => [1,2,3]},
{"foo" => {"bar" => "1", "baz" => "*"}, "bla" => [1,2,3]},
{"foo" => {"bar" => "*", "baz" => 2}, "bla" => "*"},
]
Where I basically went over each key and changed its value to "*" while preserving the overall structure of the hash and saved the new hash produced in some array.
I have tried many ideas, but most just wont work as I can guess the Array type before, I only know this hash is produced by JSON.parse and then changed into Hash(String, JSON::Any)
My current try at it
hash = {"bar" => {"and" => "2", "br" => "1"}}
arr = [hash, {"bar" => "1"}]
arr.delete(arr.last)
arr.delete(hash)
def changer(hash, arr, original = nil)
original = hash.dup
hash.each do |k, v|
if v.is_a?(Hash)
changer(v, arr, hash)
elsif v.is_a?(Array)
v.each do |a|
if a.is_a?(Hash)
changer(a, arr, hash)
end
end
elsif v.is_a?(String) && original.is_a?(Hash(String, String))
original[k.to_s] = "*"
arr << original
end
end
end
Crystal v0.25.0 implements JSON::Any and YAML::Any without recursive aliases. With that change:
require "json"
hash = JSON.parse(%(
{"foo": {"bar": 1, "baz": 2}, "bla": [1,2,3]}
))
def changer(any : JSON::Any)
result = [JSON::Any.new("*")]
if (hash = any.as_h?)
hash.each do |key, value|
changer(value).each do |s|
result << JSON::Any.new(hash.merge({key => s}))
end
end
end
result
end
puts changer(hash).join("\n")
*
{"foo" => "*", "bla" => [1_i64, 2_i64, 3_i64]}
{"foo" => {"bar" => "*", "baz" => 2_i64}, "bla" => [1_i64, 2_i64, 3_i64]}
{"foo" => {"bar" => 1_i64, "baz" => "*"}, "bla" => [1_i64, 2_i64, 3_i64]}
{"foo" => {"bar" => 1_i64, "baz" => 2_i64}, "bla" => "*"}
I'm trying to create an initializer for a Class that receives a Hash as parameter. The Hash is a {String => Type} hash, and can be nested. I'm getting an error when running this code:
#file: types.cr
class Types
alias Type = Nil |
Bool |
Int32 |
Int64 |
Float64 |
String |
Array(Type) |
Hash(String, Type)
def initialize(#input : Type)
end
end
input = {"a" => {"b" => {"c" => {"c1" => 1, "c2" => 2, "c3" => true}}}}
s = Types.new(input)
Here is the error I get when running the code above:
$ crystal types.cr
Error in types.cr:16: instantiating 'Types:Class#new(Hash(String, Hash(String, Hash(String, Hash(String, Bool | Int32)))))'
s = Types.new(input)
^~~
in types.cr:11: instance variable '#input' of Types must be Types::Type, not Hash(String, Hash(String, Hash(String, Hash(String, Bool | Int32))))
def initialize(#input : Type)
^~~~~~
Is this possible with Crystal? How should I approach this?
Thanks!
You can do this specifying type of each hash:
c = {"c1" => 1, "c2" => 2, "c3" => true} of String => Types::Type
b = {"c" => c} of String => Types::Type
a = {"b" => b} of String => Types::Type
t = Types.new({"a" => a} of String => Types::Type)
pp t # => #<Types:0x103085ec0
# #input=
# {"a" => {"b" => {"c" => {"c1" => 1, "c2" => 2, "c3" => true}}}}>
Another approach is to define and use Hash-like type:
alias Type = Nil |
Bool |
Int32 |
Int64 |
Float64 |
String |
Array(Type) |
Hash(String, Type)
alias TypesHash = Hash(String, Type)
t = TypesHash{
"a" => TypesHash{
"b" => TypesHash{
"c" => TypesHash{
"c1" => 1, "c2" => 2, "c3" => true,
},
},
},
}
t # {"a" => {"b" => {"c" => {"c1" => 1, "c2" => 2, "c3" => true}}}}
t["a"] # {"b" => {"c" => {"c1" => 1, "c2" => 2, "c3" => true}}}
t["a"].as(TypesHash)["b"] # {"c" => {"c1" => 1, "c2" => 2, "c3" => true}}
t["a"].as(TypesHash)["b"].as(TypesHash)["c"] # {"c1" => 1, "c2" => 2, "c3" => true}
So you can pass it to the constructor just like TypesHash object:
class Types
def initialize(#input : TypesHash); end
end
Types.new t
I'm trying to test a hybrid webView application and having an issue with the tap method on an iPhone 6 simulator. Touch works fine in the same app with calabash-cucumber.
Xcode: Version 7.0.1 (7A1001)
OS: OSX Yosemite 10.10.5 (14F27)
calabash-cucumber (0.16.4):
irb(main):002:0> query "webView css:'.ion-navicon'"
[
[0] {
"center" => {
"X" => 348.046875,
"Y" => 53.90625000000001
},
"webView" => "<UIWebView: 0x7f8761935e60; frame = (0 0; 320 568); autoresize = W+H; layer = <CALayer: 0x7f8761921320>>",
"nodeName" => "BUTTON",
"id" => "",
"textContent" => "\n\t\t\t",
"class" => "button button-icon button-clear ion-navicon",
"rect" => {
"x" => 348.0469,
"height" => 42,
"y" => 53.90625,
"width" => 35,
"left" => 280,
"top" => 25,
"center_y" => 53.90625,
"center_x" => 348.0469
},
"nodeType" => "ELEMENT_NODE"
}
]
irb(main):004:0> touch "webView css:'.ion-navicon'"
[
[0] {
"center" => {
"X" => 348.046875,
"Y" => 53.90625000000001
},
"webView" => "<UIWebView: 0x7f8761935e60; frame = (0 0; 320 568); autoresize = W+H; layer = <CALayer: 0x7f8761921320>>",
"nodeName" => "BUTTON",
"id" => "",
"textContent" => "\n\t\t\t",
"class" => "button button-icon button-clear ion-navicon",
"rect" => {
"x" => 348.0469,
"height" => 42,
"y" => 53.90625,
"width" => 35,
"left" => 280,
"top" => 25,
"center_y" => 53.90625,
"center_x" => 348.0469
},
"nodeType" => "ELEMENT_NODE"
}
]
That works great.
Calabash 2.0 hangs for a while on tap, and eventually spits out an error.
calabash (2.0.0.pre4):
irb(main):030:0> query "webView css:'.ion-navicon'"
Getting: http://127.0.0.1:37265/map {}
[
[0] {
"center" => {
"X" => 348.046875,
"Y" => 53.90625000000001
},
"webView" => "<UIWebView: 0x7f8310705f10; frame = (0 0; 320 568); autoresize = W+H; layer = <CALayer: 0x7f83107303e0>>",
"nodeName" => "BUTTON",
"id" => "",
"textContent" => "\n\t\t\t",
"class" => "button button-icon button-clear ion-navicon",
"rect" => {
"x" => 348.0469,
"height" => 42,
"y" => 53.90625,
"width" => 35,
"left" => 280,
"top" => 25,
"center_y" => 53.90625,
"center_x" => 348.0469
},
"nodeType" => "ELEMENT_NODE"
}
]
irb(main):027:0> tap "webView css:'.ion-navicon'"
Getting: http://127.0.0.1:37265/map {}
Getting: http://127.0.0.1:37265/uia {}
RuntimeError: Expected '' to be an array.
from /Users/szoradm/.rvm/gems/ruby-2.0.0-p643/gems/calabash-2.0.0.pre4/lib/calabash/ios/device/routes/uia_route_mixin.rb:138:in `expect_uia_results_is_array'
from /Users/szoradm/.rvm/gems/ruby-2.0.0-p643/gems/calabash-2.0.0.pre4/lib/calabash/ios/device/routes/uia_route_mixin.rb:115:in `handle_uia_results'
from /Users/szoradm/.rvm/gems/ruby-2.0.0-p643/gems/calabash-2.0.0.pre4/lib/calabash/ios/device/routes/uia_route_mixin.rb:105:in `uia_over_http'
from /Users/szoradm/.rvm/gems/ruby-2.0.0-p643/gems/calabash-2.0.0.pre4/lib/calabash/ios/device/routes/uia_route_mixin.rb:43:in `uia_route'
from /Users/szoradm/.rvm/gems/ruby-2.0.0-p643/gems/calabash-2.0.0.pre4/lib/calabash/ios/device/routes/uia_route_mixin.rb:56:in `uia_serialize_and_call'
from /Users/szoradm/.rvm/gems/ruby-2.0.0-p643/gems/calabash-2.0.0.pre4/lib/calabash/ios/device/gestures_mixin.rb:107:in `_tap'
from /Users/szoradm/.rvm/gems/ruby-2.0.0-p643/gems/calabash-2.0.0.pre4/lib/calabash/device.rb:145:in `tap'
from /Users/szoradm/.rvm/gems/ruby-2.0.0-p643/gems/calabash-2.0.0.pre4/lib/calabash/gestures.rb:50:in `tap'
from (irb):27
from /Users/szoradm/.rvm/rubies/ruby-2.0.0-p643/bin/irb:12:in `<main>'
Any suggestions?
Looks like a bug:
2.0.0.pre4/lib/calabash/ios/device/routes/uia_route_mixin.rb:138:
in `expect_uia_results_is_array'
The server is returning some unexpected output. Can you create an issue?
In the report, please note the Calabash iOS Server version. Thanks.
https://github.com/calabash/calabash
I have two models,
class User < ActiveRecord::Base
has_many :posts, -> { order('post.id') }
end
class Post < ActiveRecord::Base
belongs_to: user
end
For instance i'm having a #user and two posts associated. while doing #user.posts, the result be like.
[
[0] #<Post:0x0000000aa53a20> {
:id => 3,
:title => 'Hello World',
:comment => 'Long text comes here'
},
[1] #<Post:0x0000000aa53a41> {
:id => 5,
:title => 'Hello World 2',
:comment => 'Long text comes here too'
}
]
Now, I'm building one more extra object by doing #user.posts.build and
that the below result of doing #user.posts
[
[0] #<Post:0x0000000aa53a20> {
:id => 3,
:title => 'Hello World',
:comment => 'Long text comes here'
},
[1] #<Post:0x0000000aa53a41> {
:id => 5,
:title => 'Hello World 2',
:comment => 'Long text comes here too'
},
[2] #<Post:0x0000000aa53a50> {
:id => nil,
:title => nil,
:comment => nil
},
]
What i actually want is, to sort by object with nil first. The result should exactly look like,
[
[0] #<Post:0x0000000aa53a50> {
:id => nil,
:title => nil,
:comment => nil
},
[1] #<Post:0x0000000aa53a20> {
:id => 3,
:title => 'Hello World',
:comment => 'Long text comes here'
},
[2] #<Post:0x0000000aa53a41> {
:id => 5,
:title => 'Hello World 2',
:comment => 'Long text comes here too'
}
]
It can also be done by an custom method to sort by looping through each object. But don't want to write another method. The result should in Association Proxy and not an Array
Is it possible to achieve it in association proxy itself?
Suppose, you have the #posts variable where it contains the nil item.
#posts.sort{|i,j| i.id && j.id ? i <=> j : j.id ? -1 : 1 }
result => [nil, 3, 5]
When I update the existing subscription info using update_recurring method of autorize.net gateway then payment details (credit card number, CVV number and expiry date) are not being updated.
My code snippet is as follows:-
def create_card_subscription
credit_card = ActiveMerchant::Billing::CreditCard.new(
:first_name => params[:payment_details][:name],
:last_name => params[:payment_details][:last_name],
:number => params[:payment_details][:credit_card_number],
:month => params[:expiry_date_month],
:year => params[:expiry_date_year],
:verification_value => params[:payment_details][:cvv_code]
)
if credit_card.valid?
gateway = ActiveMerchant::Billing::AuthorizeNetGateway.new(:login => '*********', :password => '**************')
response = gateway.update_recurring(
{
"subscription.payment.credit_card.card_number" => "4111111111111111",
:duration =>{:start_date=>'2010-04-21', :occurrences=>1},
:billing_address=>{:first_name=>'xyz', :last_name=>'xyz'},
:subscription_id=>"******"
}
)
if response.success?
puts response.params.inspect
puts "Successfully charged $#{sprintf("%.2f", amount / 100)} to the credit card #{credit_card.display_number}. The Account number is #{response.params['rbAccountId']}"
else
puts response.message
end
else
#Credit Card information is invalid
end
render :action=>"card_payment"
end
Try something like this:
credit_card = ActiveMerchant::Billing::CreditCard.new({
:number => self.ccnum,
:verification_value => self.ccv,
:month => self.exp_month,
:year => self.exp_year,
:first_name => self.first_name,
:last_name => self.last_name
})
response = gateway.update_recurring({
:subscription_id => self.subscription_id,
:amount => 10000000,
:credit_card => credit_card,
:customer => {
:email => "fjdksl#jklfdsjflkd.com"
},
:billing_address => {
:first_name => self.first_name,
:last_name => self.last_name,
:address1 => self.address + " " + self.address2,
:city => self.city,
:state => self.state,
:country => "US",
:zip => self.zip
}
})