Hi there,
I'm currently working on a drop in replacement gem for ruby-pg under JRuby. While running the active record test suite on the gem, I realized the following inconsistencies between JRuby and MRI:
- Let's start with the easy one less important:
#!/usr/bin/env ruby
hash = {:foo => :bar}
hash.freeze
hash[:foo] = ":bazz"
Under MRI this code will throw the following error:
./test_hash.rb:5:in `[]=': can't modify frozen Hash (RuntimeError)
from ./test_hash.rb:5:in `<main>'
Under JRuby it will throw the following:
RuntimeError: can't modify frozen hash
[]= at org/jruby/RubyHash.java:903
(root) at ./test_hash.rb:5
What's relevant here is the case of Hash vs. hash. Surprisingly, there's a test in rails that test the actual error message and it's not case insensitive. I guess the question now is what's the right course of action here.
- This is more relevant, since it affects the behavior of active record's associations behavior under JRuby, which is currently faulty for the following reasons:
First the bug, is in Array::delete. Although the ruby docs say that Array::delete(obj) will return obj if an object was found (and deleted) that's equal to obj. But the actual implementation returns the last object in the array that was equal to obj instead of obj (as seen in the source code and docs here). On the other hand JRuby do the right thing and return obj. I know, I said JRuby is doing the right thing, but it's still a difference between the two implementations and MRI is the defacto standard. I also don't know what's the right course of action which is why I'm posting this here to discuss.
A test case to explain the difference in behavior is:
#!/usr/bin/env ruby
class Foo
attr_reader :name, :age
def initialize name, age
@name = name
@age = age
end
def == other
other.name == name
end
end
foo1 = Foo.new "John Shahid", 27
foo2 = Foo.new "John Shahid", 28
array = [foo1]
temp = array.delete foo2
puts "#{temp.age} should equal 28"
Output under MRI: 27 should equal 28
Under JRuby: 28 should equal 28
In case you were wondering where this is relevant or someone had the same problem in rails. The problem is in activerecord/lib/active_record/associations/collection_association.rb:422 where the code tries to merge in memory records that could possibly be modified with records that were just retrieved from the db (as shown below). In MRI mem_record will hold the memory record since we're deleting from the memory array, while JRuby will return the object we're trying to delete which is the persisted record. The symptom of this bug is that you loose your memory modifications if you don't save the record to the db, since everytime you merge the persisted data will override what's in memory.
persisted.map! do |record|
if mem_record = memory.delete(record)
puts "record.attribute_names & mem_record.attribute_names: #{record.attribute_names & mem_record.attribute_names}"
puts "mem_record.changes: #{mem_record.changes.keys}"
((record.attribute_names & mem_record.attribute_names) - mem_record.changes.keys).each do |name|
mem_record[name] = record[name]
end
mem_record
else
record
end
end
Sorry for this lengthy issue, I tried to be thorough as much as I can. I'm happy to write the specs in ruby-specs and send a patch to make the spec pass. I don't know which implementation should be fixed and want everyone to be on the same page (including the MRI guys if this will involve a change in their implementation) before I go on.
Hi there,
I'm currently working on a drop in replacement gem for ruby-pg under JRuby. While running the active record test suite on the gem, I realized the following inconsistencies between JRuby and MRI:
Under MRI this code will throw the following error:
Under JRuby it will throw the following:
What's relevant here is the case of
Hashvs.hash. Surprisingly, there's a test in rails that test the actual error message and it's not case insensitive. I guess the question now is what's the right course of action here.First the bug, is in Array::delete. Although the ruby docs say that Array::delete(obj) will return obj if an object was found (and deleted) that's equal to obj. But the actual implementation returns the last object in the array that was equal to obj instead of obj (as seen in the source code and docs here). On the other hand JRuby do the right thing and return obj. I know, I said JRuby is doing the right thing, but it's still a difference between the two implementations and MRI is the defacto standard. I also don't know what's the right course of action which is why I'm posting this here to discuss.
A test case to explain the difference in behavior is:
Output under MRI:
27 should equal 28Under JRuby:
28 should equal 28In case you were wondering where this is relevant or someone had the same problem in rails. The problem is in
activerecord/lib/active_record/associations/collection_association.rb:422where the code tries to merge in memory records that could possibly be modified with records that were just retrieved from the db (as shown below). In MRImem_recordwill hold the memory record since we're deleting from thememoryarray, while JRuby will return the object we're trying to delete which is the persisted record. The symptom of this bug is that you loose your memory modifications if you don't save the record to the db, since everytime you merge the persisted data will override what's in memory.Sorry for this lengthy issue, I tried to be thorough as much as I can. I'm happy to write the specs in ruby-specs and send a patch to make the spec pass. I don't know which implementation should be fixed and want everyone to be on the same page (including the MRI guys if this will involve a change in their implementation) before I go on.