How does using String#between? with DateTime objects work in Rails? - ruby

Using Ruby 2.3.2, Rails 5.0.0.1
In Ruby console
2.3.2 :001 > date_time_str = "2015-01-08 08:17:15 UTC"
=> "2015-01-08 08:17:15 UTC"
2.3.2 :002 > date_time_range_start = "2015-01-08 08:16:15 UTC"
=> "2015-01-08 08:16:15 UTC"
2.3.2 :003 > date_time_range_end = "2015-01-08 08:20:15 UTC"
=> "2015-01-08 08:20:15 UTC"
2.3.2 :008 > require 'date'
=> true
2.3.2 :009 > date_time_str.between?(DateTime.parse(date_time_range_start), DateTime.parse(date_time_range_end))
ArgumentError: comparison of String with DateTime failed
from (irb):9:in `between?'
from (irb):9
from /home/jignesh/.rvm/rubies/ruby-2.3.2/bin/irb:11:in `<main>'
I expected the above behaviour as I was attempting to compare a String instance with a DateTime instance. However when I attempt the same in rails console it returns expected result as shown below.
In Rails console
Loading development environment (Rails 5.0.0.1)
2.3.2 :001 > date_time_str = "2015-01-08 08:17:15 UTC"
=> "2015-01-08 08:17:15 UTC"
2.3.2 :002 > date_time_range_start = "2015-01-08 08:16:15 UTC"
=> "2015-01-08 08:16:15 UTC"
2.3.2 :003 > date_time_range_end = "2015-01-08 08:20:15 UTC"
=> "2015-01-08 08:20:15 UTC"
2.3.2 :004 > date_time_str.between?(DateTime.parse(date_time_range_start), DateTime.parse(date_time_range_end))
=> true
2.3.2 :005 > date_time_str_2 = "2015-01-08 08:21:15 UTC"
=> "2015-01-08 08:21:15 UTC"
2.3.2 :006 > date_time_str_2.between?(DateTime.parse(date_time_range_end), DateTime.parse(r_e))
=> false
Can anybody please help me understand what Rails does internally to make the above work?

String#<=> in ruby tries to compare itself to the other object. If the other object is incomparable (not a string), then the implementation for string specifically (c-source here) compares the other object to the receiving string.
In other words, "str" <=> obj is equivalent to
if obj.is_a?(String)
# compare bytes
elsif obj.respond_to?(:to_str)
obj = obj.to_str
# compare bytes
else
# a few safety checks
-1 * (obj <=> "str")
end
Now we have "str" <=> obj boiling down to obj <=> "str". So far this is just Ruby, no Rails. Since Datetime#<=> has no special handling for string, it returns nil and story ends here.
In Rails, they redefined Datetime#<=> and defined String#to_datetime

Related

Oj Time dump - differences in linux and windows

I have a time stamp I am obtaining from an Access database. Unfortunately, the timestamp represents the time only, but is stored as 1899-12-30 13:05:00 +0000. The date part of it is in a different field. I need to preserve the data as much as possible while I am storing it in a json blob.
I am using Oj (which is wonderful) to dump the data to json. I have encountered the following behavior on the Windows platform:
irb(main):001:0> require 'oj'
=> true
irb(main):002:0> t = Time.new(1899,12,30,13,5,0) #this is my actual timestamp
=> 1899-12-30 13:05:00 +0000
irb(main):003:0> Oj.dump(t)
RangeError: bignum too big to convert into `long'
from (irb):3:in `dump'
from (irb):3
from C:/RailsInstaller/Ruby2.2.0/bin/irb:11:in `<main>'
irb(main):004:0>
Now, on linux:
2.3.0 :001 > require 'oj'
=> true
2.3.0 :002 > t = Time.new(1899,12,30,13,5,0)
=> 1899-12-30 13:05:00 +0000
2.3.0 :003 > Oj.dump(t)
=> "{\"^t\":-2209114500.000000000e0}"
I need to make this work on Windows because my source database is MS Access. Please help.
Time is not handled properly, but you can convert to float. The result string differ a bit from your original:
irb(main):001:0> require 'oj'
=> true
irb(main):002:0> t = Time.new(1899,12,30,13,5,0)
=> 1899-12-30 13:05:00 +0100
irb(main):003:0> Oj.dump(t)
RangeError: bignum too big to convert into `long'
from (irb):3:in `dump'
from (irb):3
from C:/Ruby23-x64/bin/irb.cmd:19:in `<main>'
irb(main):005:0> Oj.dump(t.to_f)
=> "-2209118100.0"
Or if you need the exact response you can use an array:
irb(main):001:0> require 'oj'
=> true
irb(main):002:0> t = Time.new(1899,12,30,13,5,0)
=> 1899-12-30 13:05:00 +0100
irb(main):012:0> A1 = Array.new(1)
=> [nil]
irb(main):017:0> A1[0] = '^t'
=> "^t"
irb(main):028:0> A1[1] = t.to_f
=> "-2209118100.0"
irb(main):035:0> Oj.dump(A1).tr("[","{").tr("]","}")
=> "{\"^t\",\"-2209118100.0\"}"

Strings that compare equal don't find same objects in Hash

I have two strings that appear equal:
context = "Marriott International World’s Most ADMIRED Lodging Company by FORTUNE for 14th yr. via #FortuneMagazine http://cnnmon.ie/1kcFZSQ"
slice_str = context.slice(105,24) # => "http://cnnmon.ie/1kcFZSQ"
str = "http://cnnmon.ie/1kcFZSQ"
slice_str == str # => true
slice_str.eql? str # => true
But when I look up values in a hash where the keys are the strings, they do not return the same thing in Ruby 2.1.0 and Ruby 2.1.1:
redirects = {"http://cnnmon.ie/1kcFZSQ"=>""}
redirects.key?(slice_str) # => false
redirects.key?(str) # => true
What explanation is there for this behaviour? Ruby 1.9.3 works as expected.
This was a bug in ruby < 2.1.3
$ rvm use 2.1.2
Using /Users/richniles/.rvm/gems/ruby-2.1.2
$ irb
2.1.2 :001 > context = "Marriott International World’s Most ADMIRED Lodging Company by FORTUNE for 14th yr. via #FortuneMagazine http://cnnmon.ie/1kcFZSQ"
=> "Marriott International World’s Most ADMIRED Lodging Company by FORTUNE for 14th yr. via #FortuneMagazine http://cnnmon.ie/1kcFZSQ"
2.1.2 :002 > slice_str = context.slice(105,24) # => "http://cnnmon.ie/1kcFZSQ"
=> "http://cnnmon.ie/1kcFZSQ"
2.1.2 :003 > str = "http://cnnmon.ie/1kcFZSQ"
=> "http://cnnmon.ie/1kcFZSQ"
2.1.2 :004 > redirects = {"http://cnnmon.ie/1kcFZSQ"=>""}
=> {"http://cnnmon.ie/1kcFZSQ"=>""}
2.1.2 :005 > redirects.key?(slice_str)
=> false
2.1.2 :006 > redirects.key?(str)
=> true
but do the same in ruby 2.1.3:
$ rvm use 2.1.3
Using /Users/richniles/.rvm/gems/ruby-2.1.3
$ irb
2.1.3 :001 > context = "Marriott International World’s Most ADMIRED Lodging Company by FORTUNE for 14th yr. via #FortuneMagazine http://cnnmon.ie/1kcFZSQ"
=> "Marriott International World’s Most ADMIRED Lodging Company by FORTUNE for 14th yr. via #FortuneMagazine http://cnnmon.ie/1kcFZSQ"
2.1.3 :002 > slice_str = context.slice(105,24) # => "http://cnnmon.ie/1kcFZSQ"
=> "http://cnnmon.ie/1kcFZSQ"
2.1.3 :003 > str = "http://cnnmon.ie/1kcFZSQ"
=> "http://cnnmon.ie/1kcFZSQ"
2.1.3 :004 > redirects = {"http://cnnmon.ie/1kcFZSQ"=>""}
=> {"http://cnnmon.ie/1kcFZSQ"=>""}
2.1.3 :005 > redirects.key?(slice_str)
=> true
2.1.3 :006 > redirects.key?(str)
=> true
For Hash keys, its key#hash method determines, whether keys considered equal or not.
In ruby 2.1.1 for your example those two string hashes are different:
slice_str.hash == str.hash # => false in Ruby 2.1.1
Though, it's totally unclear to me, why sliced string has different hash.
Even stranger -- I've found if you test the code on ASCII-only string (your string, but with ' instead of ’) -- the hashes will be the same!
It's really weird.
The only solution I've found (though it doesn't look elegant at all):
slice_str = context.slice(105,24).chars.join # split it into separate chars and then join back
p str.hash == slice_str.hash # true now
p redirects.key?(slice_str) # true now
UPD: Oops, I haven't saw link to bug in the comments above :(

Convert string datetime to Ruby datetime

How do I convert this "2013-10-20 18:36:40" into a Ruby datetime?
I'm trying the following, but it's not working:
"2013-10-20 18:36:40".to_datetime
That's making it this and missing the time:
2013-10-20 00:00:00 UTC
Use DateTime::strptime:
require 'date'
DateTime.strptime("2013-10-20 18:36:40", "%Y-%m-%d %H:%M:%S")
#<DateTime: 2013-10-20T18:36:40+00:00 ((2456586j,67000s,0n),+0s,2299161j)>
You can do the following if rails is installed on your system:--
require 'active_support/core_ext/string/conversions'
"2013-10-20 18:36:40".to_time
1.9.2p320 :001 > require 'active_support/core_ext/string/conversions'
=> true
1.9.2p320 :003 > "2013-10-20 18:36:40".to_time
=> 2013-10-20 18:36:40 UTC
There is also DateTime#parse method:
2.1.0 :001 > require 'date'
=> true
2.1.0 :002 > DateTime.parse('2013-10-20 18:36:40')
=> #<DateTime: 2013-10-20T18:36:40+00:00 ((2456586j,67000s,0n),+0s,2299161j)>
If your work with rails consider writing timezone-safe code:
Time.zone.parse("2013-10-20 18:36:40")
http://www.elabs.se/blog/36-working-with-time-zones-in-ruby-on-rails

How can I escape a Ruby symbol with quotes only if needed?

IRB and Rails console both have a nice way of outputting symbols that only quote-escapes them when necessary. Some examples:
1.9.3p194 :001 > "#test".to_sym
=> :#test
1.9.3p194 :002 > "#Test".to_sym
=> :#Test
1.9.3p194 :003 > "#123".to_sym
=> :"#123"
1.9.3p194 :004 > "##_test".to_sym
=> :##_test
1.9.3p194 :005 > "test?".to_sym
=> :test?
1.9.3p194 :006 > "test!".to_sym
=> :test!
1.9.3p194 :007 > "_test!".to_sym
=> :_test!
1.9.3p194 :008 > "_test?".to_sym
=> :_test?
1.9.3p194 :009 > "A!".to_sym
=> :"A!"
1.9.3p194 :010 > "#a!".to_sym
=> :"#a!"
How would you do this yourself, so that you could do:
puts "This is valid code: #{escape_symbol(some_symbol)}"
The easiest and best way to do this is via Symbol's inspect method:
1.9.3p194 :013 > puts "This is valid code: #{"#a!".to_sym.inspect}"
This is valid code: :"#a!"
=> nil
1.9.3p194 :014 > puts "This is valid code: #{"a!".to_sym.inspect}"
This is valid code: :a!
You could look at the sym_inspect(VALUE sym) method in string.c in Ruby 1.9.3 that does that, if you're curious.
So, even though you don't need another method to call inspect, this would be the simplest implementation:
def escape_symbol(sym)
sym.inspect
end
Here's my attempt at implementing with a few regexs, although I'd suggest using inspect instead if you can:
def escape_symbol(sym)
sym =~ /^[#a-zA-Z_]#?[a-zA-Z_0-9]*$/ || sym =~ /^[a-z_][a-zA-Z_0-9]*[?!]?$/ ? ":#{sym}" : ":\"#{sym.gsub(/"/, '\\"')}\""
end

How do I access a value from a JSON string?

I have a variable. When I do puts var_name I get this hash:
"{\"numConnections\": 163}"
But when I try to get that number 163 from the value numConnections it isn't working. Here is what I am trying:
connections = temp_var["\"numConnections\""]
puts connections.inspect
or:
connections = temp_var["numConnections"]
puts connections.inspect
both of which equally don't work.
Any idea how to extract that 163 from there?
If you have a JSON string, you need to parse it into a hash before you can use it to access its keys and values in a hash-like way. Consider this IRB session:
1.9.3p194 :001 > require 'json'
=> true
1.9.3p194 :002 > temp_var = "{\"numConnections\": 163}"
=> "{"numConnections": 163}"
1.9.3p194 :003 > temp_var.class
=> String
1.9.3p194 :004 > JSON.parse(temp_var)
=> {"numConnections"=>163}
1.9.3p194 :005 > JSON.parse(temp_var)['numConnections']
=> 163

Resources