Since I'm dealing quite a lot with durations in my Rails app, I would also like to use them as hash keys in some places. However, it does not seem to work as expected for me.
Creating the initial hash works fine. For example, the following will work:
>> hash = {1.week => 'abc', 1.month => 'def'}
However, retrieving the values from the hash is not possible:
>> hash[1.month]
=> nil
Some further investigation showed me the reason for this:
>> 1.week.eql? 1.week
=> false
Which was quite unexpected
Furthermore, it seems like these objects behave differently to normal objects of FixNum class. For normal FixNum objects, the value of the object_id seems to be always the same, e.g:
>> 15.object_id
=> 31
>> 15.object_id
=> 31
For Durations, this is different, although they are from the same class
>> 1.month.class
=> Fixnum
>> 1.month.object_id
=> 70305513092140
>> 1.month.object_id
=> 70305513086860
So, it seems like the objects are always different, which is why hashes will not work. The only solution is to access with exactly the same object:
>> a = 1.month
=> 1 month
>> hash = {a => 'b'}
=> {1 month=>"b"}
>> hash[a]
=> "b"
Obviously, this is not always possible if you have a lot of objects with dynamically created durations.
Grouping by durations does not work either:
>> limits.group_by(&:duration)
=> #<OrderedHash {1 month=>[#<Limit:0x7fe28e441380>], 1 month=>[#<Limit:0x7fe28e4290c8>]}>
So, I'm wondering whether it is possible to get durations working as hash keys somehow? So far I have not found a good solution and I'm not sure if there is one. The important this is that functions like (Time.now - duration) should keep working.
FYI: My Ruby version - 1.8.7, Rails version - 2.3.18
Wow, that is a weird finding. This does work, and I haven't been able to break it, but it seems a little fragile:
hash = {1.year.inspect => "val1", 2.month.inspect => "val2", (1.year-4.days).inspect => "val3"}
=> {"1 year"=>"val1", "2 months"=>"val2", "1 year and -4 days"=>"val3"}
hash[2.months.inspect]
=> "val2"
hash[(1.year-4.days).inspect]
=> "val3"
and to get the durations back
hash.keys.collect{|k| eval(k.gsub(" and ","+").split(" ").join("."))}
=> [1 year, 2 months, 1 year and -4 days]
Related
So I understand that you can update some models using Model.update(ids, values) where ids represent the ID and values represent the actual changes. I do also understand that you can pass in a hash, such as {1 => {column_name: "new value"}, 2 => {column_name: "new value"}} which works just fine.
However, what if, instead of IDs, I wanted to use another column such as uuid? Can I use the .update method in such a way to where it would do something like Model.update(uuid: hash.keys, hash.values)?
It doesn't appear that I can do this with this method, but is there another way that I can do this so that I don't have to iterate through every single key and value in my long array (which contains thousands of keys and values)
This is what happens when I try to implement what I would like to do:
[2] pry(#<MyWorker>)> test = {"b7d720f984abeda37836d07b2147560ce06fb4d7e30fe8d59c1fe610cb440bbf" => {:protocol => "udp", :port => 5}}
=> {"b7d720f984abeda37836d07b2147560ce06fb4d7e30fe8d59c1fe610cb440bbf"=>{:protocol=>"udp", :port=>5}}
[3] pry(#<MyWorker>)> Port.update(uuid: test.keys, test.values)
SyntaxError: unexpected ')', expecting =>
...e(uuid: test.keys, test.values)
... ^
[3] pry(#<MyWorker>)>
As Sebastian Palma said, you can do this using a where clause before the update action like so:
Port.where(uuid: test.keys).update(test.values)
This question already has answers here:
How to remove a key from Hash and get the remaining hash in Ruby/Rails?
(16 answers)
Closed 4 years ago.
I'm trying to delete an attribute and its value from a hash. Its seems simple based on answers I see on here but it doesn't appear to work for me. Curious if anyone has any thoughts as to why? Also... this is NOT a duplicate of the question that was linked. I have tried except and slice... neither of those work as well. I'm guessing my dataset it different.
Here is an example hash I have:
{:data=>[{:id=>1, :make=>"Ford", :model=>"Excursion", :year=>2018, :color=>"silver", :vin=>"123456789F22"},{=>[{:id=>2, :make=>"Mazda", :model=>"RX7", :year=>1980, :color=>"blue", :vin=>"123456789F22"},{=>[{:id=>3, :make=>"Chevy", :model=>"Dorado", :year=>2018, :color=>"white", :vin=>"123456789F22"}]}
I have tried the following:
hashval.delete("color")
hashval.except!("color")
hashval.each {|h| h.delete("color")}
I also tried :color in case the string format was wrong
hashval.delete(:color)
hashval.except!(:color)
hashval.each {|h| h.delete(:color)}
but when I try to display the resulting hash
logger.info "hash result: #{hashval}"
I still see the original hash with the color still in there. Any thoughts on what I am doing wrong?
Ok... more info! If I do this:
hashval.delete(:data)
It does delete :data (and everything else after that). So it has something to do with the attributes in that hash array?
As it turns out, the answer is:
hashval = { data: vehicles.map { |v| v.table_data.except(:color) } }
I guess this issue was marked closed as a duplicate (even though it wasn't) so I cant add the solution.
You keys are symbols so, hash.delete(:color) should work:
h = {:id=>1, :make=>"Ford", :model=>"Excursion", :year=>2018, :color=>"silver", :vin=>"123456789F22"}
h.key?(:color) # => true
h.delete(:color)
h.key?(:color) # => false
h # => {:id=>1, :make=>"Ford", :model=>"Excursion", :year=>2018, :vin=>"123456789F22"}
Also hash might be a reserved word since if I open irb or console and type hash I get back an integer. I have no idea what it is, but it makes me think hash shouldn't be used as a var.
hash#delete works if you use a symbol:
irb
irb(main):001:0> hash = {:id=>1, :make=>"Ford", :model=>"Excursion", :year=>2018, :color=>"silver", :vin=>"123456789F22"}
=> {:id=>1, :make=>"Ford", :model=>"Excursion", :year=>2018, :color=>"silver", :vin=>"123456789F22"}
irb(main):002:0> hash.delete(:color)
=> "silver"
irb(main):003:0> hash
=> {:id=>1, :make=>"Ford", :model=>"Excursion", :year=>2018, :vin=>"123456789F22"}
I'm new to Ruby and I've run into an issue I can't solve.
I'm trying to use gsub() to match a pattern in a string, then use that match as an index into a hash. So far, I haven't been able to figure it out. Here's some code:
farm = { "pig_num" => 5, "horse_num" => 2, "cow_num" => 4}
assessment = "There are 'pig_num' pigs on this farm"
assessment.gsub(/'(.+?)'/, '\1') # => "There are pig_num pigs on this farm"
assessment.gsub(/'(.+?)'/, farm) # => "There are pigs on this farm"
assessment.gsub(/'(.+?)'/, farm['\1']) # => TypeError: no implicit conversion of nil into String
assessment.gsub(/'(.+?)'/) { |key| farm[key] }
The first call to gsub() shows that I am matching the string I want.
The second call is an attempt to use the gsub(pattern, hash) flavor found at the Ruby documentation site.
The third call is trying to reference the value using the match as an index.
The fourth is some fancy pants way I thought might work using a lambda/proc/block.
What am I doing wrong?
farm = { "pig_num" => 5, "horse_num" => 2, "cow_num" => 4}
assessment = "There are 'pig_num' pigs on this farm"
1
"You may want to get the first object from farm hash but you need to tell from which hash you want to retrieve value". Otherwise, you need to use just Integer with String type like above.
assessment.gsub(/'(.+?)'/, '1')
2
when you 'gsub' string, you get 'pig_num' because you include '' inside the regex so that result would be "'pig_num'". However, the key of hash is "pig_num". "pig_num" and "'pig_num'" are different. That is why you can't get data properly.
assessment.gsub(/'(.+?)'/, farm)
3
You can not point index inside blacket but hash key
assessment.gsub(/'(.+?)'/, farm["pig_num"].to_s)
4
As I said before, you get "'pig_num'" as key. If you print out the key value inside the block, you will see it. You need to change it to 'Pure key'. To get rid of quotation, you can use gsub again and make it empty instead of quotation. gsub! is a destructive method which means modifies original value. If you use just gusb, the method returns modified value but the original key itself (in this situation) does not change. Otherwise, you need to assign the new value to another variable.
assessment.gsub(/'(.+?)'/) { |key| p key.gsub!("\'", ""); farm[key] }
I hope this answer is helpful for you. Cheers
Try this
assessment.gsub(/#{farm.keys.join('|')}/, farm)
I see on your code with regex match it will recognize 'pig_num' is the hash key to find on farm hash. So you need to change your hash like
farm = { "'pig_num'" => 5, "'horse_num'" => 2, "'cow_num'" => 4} or you can change your regex. Example
farm = { "'pig_num'" => 5, "'horse_num'" => 2, "'cow_num'" => 4}
assessment.gsub(/'(.+?)'/, farm) # => "There are 5 pigs on this farm"
Or
farm = { "pig_num" => 5, "horse_num" => 2, "cow_num" => 4}
assessment.gsub(/pig_num/, farm) # => "There are '5' pigs on this farm"
With some minor adjustments, your can use sprintf's %{name} or %<name>snotation:
farm = { pig_num: 5, horse_num: 2, cow_num: 4 }
assessment = "There are '%{pig_num}' pigs on this farm"
sprintf(assessment, farm)
#=> "There are '5' pigs on this farm"
Have been hacking together a couple of libraries, and had an issue where a string was getting 'double escaped'.
for example:
Fixed example
> x = ['a']
=> ["a"]
> x.to_s
=> "[\"a\"]"
>
Then again to
\"\[\\\"s\\\"\]\"
This was happening while dealing with http headers. I have a header which will be an array, but the http library is doing it's own character escaping on the array.to_s value.
The workaround I found, was to convert the array to a string myself, and then 'undo' the to_s. Like so:
formatted_value = value.to_s
if value.instance_of?(Array)
formatted_value = formatted_value.gsub(/\\/,"") #remove backslash
formatted_value = formatted_value.gsub(/"/,"") #remove single quote
formatted_value = formatted_value.gsub(/\[/,"") #remove [
formatted_value = formatted_value.gsub(/\]/,"") #remove ]
end
value = formatted_value
... There's gotta be a better way ... (without needing to monkey-patch the gems I'm using). (yeah, this break's if my string actually contains those strings.)
Suggestions?
** UPDATE 2 **
Okay. Still having troubles in this neighborhood, but now I think I've figured out the core issue. It's serializing my array to json after a to_s call. At least, that seems to be reproducing what I'm seeing.
['a'].to_s.to_json
I'm calling a method in a gem that is returning the results of a to_s, and then I'm calling to_json on it.
I've edited my answer due to your edited question:
I still can't duplicate your results!
>> x = ['a']
=> ["a"]
>> x.to_s
=> "a"
But when I change the last call to this:
>> x.inspect
=> "[\"a\"]"
So I'll assume that's what you're doing?
it's not necessarily escaping the values - per se. It's storing the string like this:
%{["a"]}
or rather:
'["a"]'
In any case. This should work to un-stringify it:
>> x = ['a']
=> ["a"]
>> y = x.inspect
=> "[\"a\"]"
>> z = Array.class_eval(y)
=> ["a"]
>> x == z
=> true
I'm skeptical about the safe-ness of using class_eval though, be wary of user inputs because it may produce un-intended side effects (and by that I mean code injection attacks) unless you're very sure you know where the original data came from, or what was allowed through to it.
I am working on the acts_as_taggable_on plugin, but there is something I can't really understand (even if it is just a very simple code line).
puts "before: " + cache.inspect
# cache.delete_if { |key, value| key.id == owner.id && key.class == owner.class } # original code line
cache.delete_if { |key, value| true } # my test code
puts "after: " + cache.inspect
# output
before: {#<TaggableUser id: 1, name: nil>=>["dog"]}
after: {# TaggableUser id: 1, name: nil>=>["dog"]}
My problem is that the cache.delete_if doesn't delete anything even if it always evaluates to true. I simply don't get why ... and really tried much. It's only the problem with that cache hash. But I really couldn't find anything special about that particular hash.
The cache is created in that method:
def cached_owned_tag_list_on(context)
variable_name = "#owned_#{context}_list"
cache = instance_variable_get(variable_name) || instance_variable_set(variable_name, {})
end
The full code can be viewed here (see line 60): http://github.com/mbleigh/acts-as-taggable-on/blob/master/lib/acts_as_taggable_on/acts_as_taggable_on/ownership.rb#L60
One step further
When I do a rehash before the delete_if it works. What can "corrupt" a hash in a way that rehash is needed before any deletions works?
From the documentation of rehash:
call-seq:
hsh.rehash -> hsh
Rebuilds the hash based on the current hash values for each key. If
values of key objects have changed since they were inserted, this
method will reindex <i>hsh</i>.
So your keys (which are regular ActiveRecord instances) had their hashes changed from the time they were created. Looking into the AR sources:
# File activerecord/lib/active_record/base.rb, line 1613
1613: def hash
1614: id.hash
1615: end
So, their id's were changed. Why can that happen? Well, the obvious cause is that the object was created, then put into hash, and after that saved (which assigned it an id and changed its hash).
Also, this has another bad consequence: as the hash of all those newly-created objects is that of nil, if there were multiple unsaved objects added to the hash, they all will occupy the same slot, and trying to index the hash with some other unsaved object will return nonsense.
Are you certain that cache is a Hash? The behavior you are describing is not normal.
$ ruby -v
ruby 1.8.7 (2009-06-12 patchlevel 174) [universal-darwin10.0]
>> h = {:a => 1, :b => 2}
=> {:b=>2, :a=>1}
>> h
=> {:b=>2, :a=>1}
>> h.delete_if {|k,v| v == 2}
=> {:a=>1}
>> h
=> {:a=>1}
>> h = {:a => 1, :b => 2}
=> {:b=>2, :a=>1}
>> h.delete_if {|k,v| true}
=> {}
>> h
=> {}
Maybe it is a bug of acts_as_taggable_on and you can just fill in a bug report.