Rails serialization of Range of integers is broken - ruby-on-rails

I need to serialize ruby Ranges using YAML, in a rails context.
I wanted to check if ranges of integers and ranges of strings were serialized properly.
Here was my test:
# classic irb
require 'yaml' # => true
YAML::VERSION # => "0.60"
YAML.dump(1..2) # => "--- !ruby/range \nbegin: 1\nend: 2\nexcl: false\n"
YAML.dump("1".."2") # => "--- !ruby/range \nbegin: \"1\"\nend: \"2\"\nexcl: false\n"
The two outputs are dutifully distinct, so I got forward and coded it inside my rails application.
However it seems that within a rails context, ruby forgets how to properly serialize a range of integers!
# ./script/rails console
Rails::VERSION::STRING # => "3.0.15"
RUBY_VERSION # => "1.8.7"
YAML::VERSION # => "0.60"
YAML.dump(1..2) # => "--- !ruby/range\n begin: 1\n end: 2\n excl: false"
YAML.dump("1".."2") # => "--- !ruby/range\n begin: 1\n end: 2\n excl: false"
# The two outputs are identical, the distinction between integers and strings is lost!
Both ruby and ruby on rails seem to use the same version of the YAML library.
If I don't get it wrong, my version of ruby doesn't support switching between multiple coder engines.
I have a few questions:
What is the cause of this difference?
Does this problem arise with newer versions of ruby / rails?
How could I fix that properly, in a compatible manner?
Thank you for your help.

A range is a Ruby internal, not a YAML base type like an integer or string. Rather than encode the range as you are, use its start and end points and reconstruct the range on the receiving end.
I use something like:
[1] (pry) main: 0> range = 0..1
=> 0..1
[2] (pry) main: 0> require 'yaml'
=> true
[3] (pry) main: 0> YAML.dump(range)
=> "--- !ruby/range\nbegin: 0\nend: 1\nexcl: false\n"
[4] (pry) main: 0> YAML.dump({'min' => range.min, 'max' => range.max})
=> "---\nmin: 0\nmax: 1\n"
And then I can recreate the range on the receiving side using something like:
Range.new(*YAML.load(YAML.dump({'min' => range.min, 'max' => range.max})).values)
=> 0..1
or this if you're not sure that 'min' and 'max' will be in the right order:
[19] (pry) main: 0> Range.new(*YAML.load(YAML.dump({'min' => range.min, 'max' => range.max})).values_at('min', 'max'))
=> 0..1
Adding some information regarding Ruby 1.9.3+ serializing ranges of characters:
[2] (pry) main: 0> range = '0'..'1'
=> "0".."1"
[3] (pry) main: 0> YAML.dump(range)
=> "--- !ruby/range\nbegin: '0'\nend: '1'\nexcl: false\n"
[5] (pry) main: 0> RUBY_VERSION
=> "1.9.3"
And again with 1.9.2+:
[2] (pry) main: 0> range = '0'..'1'
=> "0".."1"
[3] (pry) main: 0> YAML.dump(range)
=> "--- !ruby/range \nbegin: \"0\"\nend: \"1\"\nexcl: false\n"
[4] (pry) main: 0> RUBY_VERSION
=> "1.9.2"
And, the workaround maintains the range start/end types:
[6] (pry) main: 0> Range.new(*YAML.load(YAML.dump({'min' => range.min, 'max' => range.max})).values_at('min', 'max'))
=> "0".."1"
In both cases the YAML_VERSION is 0.60.

Related

rails console OR irb find out the RAILS_VERSION of the current environment

Somewhat related to:
Determine ruby version from within Rails
How to find out the RAILS_VERSION from within rails console?
Short info in Rails.gem_version:
Rails.gem_version
# => Gem::Version.new("6.0.1")
Long info in Rails::Info:
Rails::Info
=> About your application's environment
Rails version 6.0.1
Ruby version ruby 2....
RubyGems version 2.7...
Rack version 2.0...
Middleware UTF8Cleaner::Middleware, Rack::Cors, ActionDispatch::HostAuthorization, Rack::Sendfile, ActionDispatch::Static, Rack::Lock, ActionDispatch::Executor, ...
Application root /home/aaa/bb/app
Environment development
Database adapter mysql
Database schema version 20195201212345
One of possible ways is:
pry(main)> Rails.version
=> "5.1.7"
or like above was suggested:
pry(main)> Rails.gem_version
=> Gem::Version.new("5.1.7")
Then you can build some conditions using this technique of versions comparisons:
[20] pry(main)> Rails.version.starts_with?('5.1')
=> true
[21] pry(main)> Gem::Version.new(Rails.version) > Gem::Version.new('5.1.2')
=> true
[22] pry(main)> Gem::Version.new(Rails.version) > Gem::Version.new('5.2.3')
=> false
[23] pry(main)> Gem::Version.new(Rails.version) == Gem::Version.new('5.1')
=> false
[24] pry(main)> Gem::Version.new(Rails.version) == Gem::Version.new('5.1.7')
=> true
OR
[28] pry(main)> Rails.gem_version == Gem::Version.new('5.1.7')
=> true
[29] pry(main)> Rails.gem_version >= Gem::Version.new('5.1.2')
=> true
[30] pry(main)> Rails.gem_version < Gem::Version.new('5.2.2')
=> true

The provided regular expression is using multiline anchors (^ or $), which may present a security risk

I am using Ruby 2.3.0 and Rails 4.2.5 and I am follow this document https://www.sitepoint.com/youtube-rails/
Since here there is regular expression are used to validate a youtube url. I am copy paste it in my rails application its working fine on rails console
[1] pry(main)> YT_LINK_FORMAT = /^(?:https?:\/\/)?(?:www\.)?youtu(?:\.be|be\.com)\/(?:watch\?v=)?([\w-]{10,})/
=> /^(?:https?:\/\/)?(?:www\.)?youtu(?:\.be|be\.com)\/(?:watch\?v=)?([\w-]{10,})/
[2] pry(main)> video_url = "https://www.youtube.com/watch?v=aZngT1Eas4w"
=> "https://www.youtube.com/watch?v=aZngT1Eas4w"
[3] pry(main)> uid = video_url.match(YT_LINK_FORMAT)
=> #<MatchData "https://www.youtube.com/watch?v=aZngT1Eas4w" 1:"aZngT1Eas4w">
[4] pry(main)> uid[2]
=> nil
[5] pry(main)> uid[1]
=> "aZngT1Eas4w"
[6] pry(main)> video_url = "https://youtu.be/aZngT1Eas4w"
=> "https://youtu.be/aZngT1Eas4w"
[7] pry(main)> uid = video_url.match(YT_LINK_FORMAT)
=> #<MatchData "https://youtu.be/aZngT1Eas4w" 1:"aZngT1Eas4w">
[8] pry(main)> uid[1]
=> "aZngT1Eas4w"
But when I run my rails application I got this error
"The provided regular expression is using multiline anchors (^ or $), which may present a security risk. Did you mean to use \A and \z, or forgot to add the :multiline => true option?"
I am also try to use this regular expression
/^(?:https?:\/\/)?(?:www\.)?youtu(?:\.be|be\.com)\/(?:watch\?v=)?([\w-]{10,})/
=> /^(?:https?:\/\/)?(?:www\.)?youtu(?:\.be|be\.com)\/(?:watch\?v=)?([\w-]{10,})/
But the same problem is still there
This regular expression is working for rails 5
YT_LINK_FORMAT = /(http:\/\/|https:\/\/|)(www.)?(youtu(be\.com|\.be|be\.com))\/(video\/|embed\/|watch\?v=|v\/)?([A-Za-z0-9._%-]*)(\&\S+)?/

ObjectSpace.each_object(Foo).count

I'am trying to figure out ObjectSpace.each_object
In console:
class Foo; end
Foo.new
ObjectSpace.each_object(Foo).count
=> 1
GC.start
ObjectSpace.each_object(Foo).count
=> 1
I've seen examples and I know that the second count should be 0.
Any ideas what is going on here?
Thanks.
It depends on your console.
IRB
The last result is saved as _, even if it hasn't been explicitely assigned.
Running GC.start won't remove the last object :
irb(main):001:0> class Foo; end
=> nil
irb(main):002:0>
irb(main):003:0* Foo.new
=> #<Foo:0x007fca7a309f98>
irb(main):004:0> p ObjectSpace.each_object(Foo).count; GC.start; p ObjectSpace.each_object(Foo).count
1
1
=> 1
irb(main):005:0> p ObjectSpace.each_object(Foo).count; GC.start; p ObjectSpace.each_object(Foo).count
1
0
=> 0
Pry
You can access the last result and the second to last result with _ and __ :
[1] pry(main)> 'a'
=> "a"
[2] pry(main)> 'b'
=> "b"
[3] pry(main)> p _, __
"b"
"a"
=> ["b", "a"]
Pry saves all the 100 last results in _out_ Pry::HistoryArray:
[1] pry(main)> class Foo; end
=> nil
[2] pry(main)> Foo.new
=> #<Foo:0x007fd093102118>
[3] pry(main)> ObjectSpace.each_object(Foo).count
=> 1
[4] pry(main)> GC.start
=> nil
[5] pry(main)> ObjectSpace.each_object(Foo).count
=> 1
[6] pry(main)> _out_[2]
=> #<Foo:0x007fd093102118>
You can use _out_.pop! to remove its last element :
[1] pry(main)> class Foo; end
=> nil
[2] pry(main)> Foo.new
=> #<Foo:0x007fa90b1ad360>
[3] pry(main)> ObjectSpace.each_object(Foo).count
=> 1
[4] pry(main)> GC.start
=> nil
[5] pry(main)> ObjectSpace.each_object(Foo).count
=> 1
[6] pry(main)> 5.times{_out_.pop!}
=> 5
[7] pry(main)> GC.start
=> nil
[8] pry(main)> ObjectSpace.each_object(Foo).count
=> 0
Inside a script
If you execute :
class Foo; end
Foo.new
p ObjectSpace.each_object(Foo).count
GC.start
p ObjectSpace.each_object(Foo).count
inside a script, you get :
1
0
GC.start does not force the garbage collector to start.
It is slightly unclear from the documentation, but it just instructs the engine to schedule a garbage collection. That said, one can not rely on GC.start would immediately remove objects from the heap.

Ruby tempfile anomalous behavior

This is my pry session output:
[1] pry(SomeTask)> epub
=> #<File:/somepath/tmp/x.epub>
[2] pry(SomeTask)> epub.size
=> 134
[3] pry(SomeTask)> File.size("/somepath/tmp/x.epub")
=> 44299
[4] pry(SomeTask)> epub.class
=> Tempfile
I see that File.size yields a different result than the size method of the Tempfile instance.
How is this possible?
The devil is in the details. From the docs for Tempfile#size (emphasis mine):
size()
Returns the size of the temporary file. As a side effect, the IO buffer is flushed before determining the size.
What's happening is that you're using File.size to read the size of the file before the buffer has been flushed—i.e. before all of the bytes have been written to the file—and then you're using Tempfile#size, which flushes that buffer before it calculates the size:
tmp = Tempfile.new('foo')
tmp.write('a' * 1000)
File.size(tmp)
# => 0
tmp.size
# => 1000
But see what happens when you call tmp.size before File.size(tmp):
tmp = Tempfile.new('bar')
tmp.write('a' * 1000)
tmp.size
# => 1000
File.size(tmp)
# => 1000
You can get the behavior you want out of File.size by manually flushing the buffer:
tmp = Tempfile.new('baz')
tmp.write('a' * 1000)
tmp.flush
File.size(tmp)
# => 1000
I'm using Pry version 0.10.1 on Ruby 2.2.2 and can't duplicate that situation:
[1] (pry) main: 0> foo = Tempfile.new('foo')
#<File:/var/folders/yb/whn8dwns6rl92jswry5cz87dsgk2n1/T/foo20150819-83612-1tpkqm4>
[2] (pry) main: 0> File.size(foo.path)
=> 0
[3] (pry) main: 0> foo.size
=> 0
After initialization, the file size is 0 bytes.
[4] (pry) main: 0> foo.write('a')
=> 1
[5] (pry) main: 0> File.size(foo.path)
=> 0
After writing one character to foo, the data has been buffered and not flushed to disk as I'd expect.
[6] (pry) main: 0> foo.size
=> 1
[7] (pry) main: 0> File.size(foo.path)
=> 1
foo.size flushes the buffer then returns the size of the file, which matches what File.size says it is.
When dealing with temporary files created by Tempfile, we don't care or want to know what their size is. They're temporary and will disappear (eventually) and are treated like buffers. If you need a file that is more permanent, then create and write to a normal file.

The loading another class has side effect with mongoid

Can somebody explain me this:
User < AbstractUser
store_in collection: 'users'
InvitedUser < AbstractUser
store_in collection: 'invited_users'
Then when I am loading the InvitedUser while working with a user object I have the following side effect.
[3] pry(#<UserInvitationsController>)> User.collection
=> #<Moped::Collection:0x007f8f008f21e0
...
#name="users">
[4] pry(#<UserInvitationsController>)> InvitedUser
=> false
[5] pry(#<UserInvitationsController>)> User.collection
=> #<Moped::Collection:0x007f8f00202d30
#name="invited_users">
And from then on the mongoid operations on User won't work properly...
I also reported an issue on github:
https://github.com/mongoid/mongoid/issues/3408
It seems that the side effect is caused by the common ancestor.
Without common ancestor everything is like it should be.
=> User
[2] pry(main)> User.collection.name
=> "users"
[3] pry(main)> InvitedUser.collection.name
=> "invited_users"
[4] pry(main)> User.collection.name
=> "users"
[5] pry(main)> InvitedUser.collection.name
=> "invited_users"
[6] pry(main)> exit
With common ancestor the side effect appears
→ ./bin/rails c
Loading development environment (Rails 4.0.0)
[1] pry(main)> User.collection.name
=> "users"
[2] pry(main)> InvitedUser.collection.name
=> "invited_users"
[3] pry(main)> User.collection.name
=> "invited_users"
[4] pry(main)> InvitedUser.collection.name
=> "invited_users"
[5] pry(main)>

Resources