Skip to content

Commit

Permalink
enhanced mimic
Browse files Browse the repository at this point in the history
  • Loading branch information
ohler55 committed Apr 29, 2013
1 parent 92944e9 commit 9061c07
Show file tree
Hide file tree
Showing 6 changed files with 82 additions and 13 deletions.
8 changes: 8 additions & 0 deletions README.md
Expand Up @@ -32,6 +32,14 @@ A fast JSON parser and Object marshaller as a Ruby gem.

## <a name="release">Release Notes</a>

### Release 2.0.12

- Another fix for mimic.

- mimic_JSON now can now be called after loading the json gem. This will
replace the json gem methods after loading. This may be more compatible in
many cases.

### Release 2.0.11

- Fixed mimic issue with Debian
Expand Down
28 changes: 20 additions & 8 deletions ext/oj/oj.c
Expand Up @@ -979,15 +979,25 @@ define_mimic_json(int argc, VALUE *argv, VALUE self) {
if (Qnil == mimic) {
VALUE ext;
VALUE dummy;


// Either set the paths to indicate JSON has been loaded or replaces the
// methods if it has been loaded.
if (rb_const_defined_at(rb_cObject, rb_intern("JSON"))) {
rb_raise(rb_const_get_at(Oj, rb_intern("MimicError")),
"JSON module already exists. Can not mimic. Do not require 'json' before calling mimic_JSON.");
mimic = rb_const_get_at(rb_cObject, rb_intern("JSON"));
} else {
mimic = rb_define_module("JSON");
}
if (rb_const_defined_at(mimic, rb_intern("Ext"))) {
ext = rb_const_get_at(mimic, rb_intern("Ext"));
} else {
ext = rb_define_module_under(mimic, "Ext");
}
if (!rb_const_defined_at(ext, rb_intern("Parser"))) {
dummy = rb_define_class_under(ext, "Parser", rb_cObject);
}
if (!rb_const_defined_at(ext, rb_intern("Generator"))) {
dummy = rb_define_class_under(ext, "Generator", rb_cObject);
}
mimic = rb_define_module("JSON");
ext = rb_define_module_under(mimic, "Ext");
dummy = rb_define_class_under(ext, "Parser", rb_cObject);
dummy = rb_define_class_under(ext, "Generator", rb_cObject);
// convince Ruby that the json gem has already been loaded
dummy = rb_gv_get("$LOADED_FEATURES");
if (rb_type(dummy) == T_ARRAY) {
Expand All @@ -1001,7 +1011,8 @@ define_mimic_json(int argc, VALUE *argv, VALUE self) {
rb_funcall2(Oj, rb_intern("mimic_loaded"), 0, 0);
}
}

dummy = rb_gv_get("$VERBOSE");
rb_gv_set("$VERBOSE", Qfalse);
rb_define_module_function(mimic, "parser=", no_op1, 1);
rb_define_module_function(mimic, "generator=", no_op1, 1);
rb_define_module_function(mimic, "create_id=", mimic_create_id, 1);
Expand All @@ -1022,6 +1033,7 @@ define_mimic_json(int argc, VALUE *argv, VALUE self) {

rb_define_module_function(mimic, "parse", mimic_parse, -1);
rb_define_module_function(mimic, "parse!", mimic_parse, -1);
rb_gv_set("$VERBOSE", dummy);

array_nl_sym = ID2SYM(rb_intern("array_nl")); rb_gc_register_address(&array_nl_sym);
create_additions_sym = ID2SYM(rb_intern("create_additions")); rb_gc_register_address(&create_additions_sym);
Expand Down
8 changes: 4 additions & 4 deletions lib/oj/mimic.rb
Expand Up @@ -4,15 +4,15 @@ module Oj
def self.mimic_loaded(mimic_paths=[])
gems_dir = File.dirname(File.dirname(File.dirname(File.dirname(__FILE__))))
Dir.foreach(gems_dir) do |gem|
next unless (gem.start_with?('json-') || gem.start_with?('json_pure'))
next unless (gem.start_with?('json') || gem.start_with?('json_pure'))
$LOADED_FEATURES << File.join(gems_dir, gem, 'lib', 'json.rb')
end
# and another approach in case the first did not get all
$LOAD_PATH.each do |d|
next unless File.exist?(d)
Dir.entries(d) do |file|
next unless file.ends_with?('jsob.rb')
$LOADED_FEATURES << file unless $LOADED_FEATURES.include?(file)
Dir.foreach(d) do |file|
next unless file.end_with?('json.rb')
$LOADED_FEATURES << File.join(d, file) unless $LOADED_FEATURES.include?(file)
end
end
mimic_paths.each { |p| $LOADED_FEATURES << p }
Expand Down
2 changes: 1 addition & 1 deletion lib/oj/version.rb
@@ -1,5 +1,5 @@

module Oj
# Current version of the module.
VERSION = '2.0.11'
VERSION = '2.0.12'
end
14 changes: 14 additions & 0 deletions notes
Expand Up @@ -3,6 +3,20 @@
^c^d hide subtree
^c^s show subtree

- make test for mimic
- try define or load module
- see if it works after loading json

- replace_JSON
- after json require

- allow maps to be used for classnames when encoding and decoding objects
- build a string to string map/table that allows lookups in either direction
- could use exising cache to do the mapping for decode
- problem if no class_cache

- try hash for cache/str-str mapping
-

---------------------------
Tried a separate thread for the parser and the results were poor. The parsing is 10% to 15% of the total so splitting
Expand Down
35 changes: 35 additions & 0 deletions test/test_mimic_after.rb
@@ -0,0 +1,35 @@
#!/usr/bin/env ruby
# encoding: UTF-8

# Ubuntu does not accept arguments to ruby when called using env. To get warnings to show up the -w options is
# required. That can be set in the RUBYOPT environment variable.
# export RUBYOPT=-w

$VERBOSE = true

$: << File.join(File.dirname(__FILE__), "../lib")
$: << File.join(File.dirname(__FILE__), "../ext")

require 'test/unit'
require 'stringio'
require 'oj'
require 'json'

class MimicAfter < ::Test::Unit::TestCase

def test0_mimic_json
assert(!defined?(JSON).nil?)
Oj.mimic_JSON
end

# dump
def test_dump_string
Oj.default_options= {:indent => 2} # JSON this will not change anything
json = JSON.dump([1, true, nil])
assert_equal(%{[
1,
true,
null]}, json)
end

end # MimicAfter

2 comments on commit 9061c07

@batter
Copy link

@batter batter commented on 9061c07 Apr 29, 2013

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey Peter, thanks for the sweet gem. Thought you should be aware of a possible problem that these changes could cause. This change seems to cause an exception to be raised by ExecJS if this version of the gem (2.0.12) is bundled into a rails application along with uglifier and oj_mimic_json and oj_mimic_json is required prior to uglifier.

Prior to this version of the gem, oj_mimic_json had to be required before the call to Bundler.require in a rails app, to ensure that it was recognized before json or multi_json were required (otherwise a fatal error would get thrown). Because of this, here is what my config/boot.rb looked like:

# config/boot.rb
require 'rubygems'
require 'oj_mimic_json'

# Set up gems listed in the Gemfile.
ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__)

require 'bundler/setup' if File.exists?(ENV['BUNDLE_GEMFILE'])

Just updated the bundled version of Oj for this app from 2.0.11 to 2.0.12, went to run rails console and here is the error that's getting spit out:

/Users/batkins/.rvm/gems/ruby-1.9.3-p392@pda/gems/execjs-1.4.0/lib/execjs/json.rb:5:in `<module:JSON>': uninitialized constant ExecJS::JSON::MultiJson (NameError)
    from /Users/batkins/.rvm/gems/ruby-1.9.3-p392@pda/gems/execjs-1.4.0/lib/execjs/json.rb:4:in `<module:ExecJS>'
    from /Users/batkins/.rvm/gems/ruby-1.9.3-p392@pda/gems/execjs-1.4.0/lib/execjs/json.rb:3:in `<top (required)>'
    from /Users/batkins/.rvm/gems/ruby-1.9.3-p392@pda/gems/execjs-1.4.0/lib/execjs/external_runtime.rb:103:in `require'
    from /Users/batkins/.rvm/gems/ruby-1.9.3-p392@pda/gems/execjs-1.4.0/lib/execjs/external_runtime.rb:103:in `available?'
    from /Users/batkins/.rvm/gems/ruby-1.9.3-p392@pda/gems/execjs-1.4.0/lib/execjs/runtimes.rb:56:in `each'
    from /Users/batkins/.rvm/gems/ruby-1.9.3-p392@pda/gems/execjs-1.4.0/lib/execjs/runtimes.rb:56:in `find'
    from /Users/batkins/.rvm/gems/ruby-1.9.3-p392@pda/gems/execjs-1.4.0/lib/execjs/runtimes.rb:56:in `best_available'
    from /Users/batkins/.rvm/gems/ruby-1.9.3-p392@pda/gems/execjs-1.4.0/lib/execjs/runtimes.rb:50:in `autodetect'
    from /Users/batkins/.rvm/gems/ruby-1.9.3-p392@pda/gems/execjs-1.4.0/lib/execjs.rb:5:in `<module:ExecJS>'
    from /Users/batkins/.rvm/gems/ruby-1.9.3-p392@pda/gems/execjs-1.4.0/lib/execjs.rb:4:in `<top (required)>'
    from /Users/batkins/.rvm/gems/ruby-1.9.3-p392@pda/gems/uglifier-1.3.0/lib/uglifier.rb:3:in `require'
    from /Users/batkins/.rvm/gems/ruby-1.9.3-p392@pda/gems/uglifier-1.3.0/lib/uglifier.rb:3:in `<top (required)>'
    from /Users/batkins/.rvm/gems/ruby-1.9.3-p392@global/gems/bundler-1.3.5/lib/bundler/runtime.rb:72:in `require'
    from /Users/batkins/.rvm/gems/ruby-1.9.3-p392@global/gems/bundler-1.3.5/lib/bundler/runtime.rb:72:in `block (2 levels) in require'
    from /Users/batkins/.rvm/gems/ruby-1.9.3-p392@global/gems/bundler-1.3.5/lib/bundler/runtime.rb:70:in `each'
    from /Users/batkins/.rvm/gems/ruby-1.9.3-p392@global/gems/bundler-1.3.5/lib/bundler/runtime.rb:70:in `block in require'
    from /Users/batkins/.rvm/gems/ruby-1.9.3-p392@global/gems/bundler-1.3.5/lib/bundler/runtime.rb:59:in `each'
    from /Users/batkins/.rvm/gems/ruby-1.9.3-p392@global/gems/bundler-1.3.5/lib/bundler/runtime.rb:59:in `require'
    from /Users/batkins/.rvm/gems/ruby-1.9.3-p392@global/gems/bundler-1.3.5/lib/bundler.rb:132:in `require'
    from /Users/batkins/Documents/Projects/pda/config/application.rb:13:in `<top (required)>'
    from /Users/batkins/.rvm/gems/ruby-1.9.3-p392@pda/gems/railties-3.2.13/lib/rails/commands.rb:39:in `require'
    from /Users/batkins/.rvm/gems/ruby-1.9.3-p392@pda/gems/railties-3.2.13/lib/rails/commands.rb:39:in `<top (required)>'
    from script/rails:6:in `require'
    from script/rails:6:in `<main>'

Removing the require 'oj_mimic_json' line from my config/boot.rb file seems to make these errors go away, but it makes me a little nervous as to whether this error could pop up when the application is deployed into production, since it appears that if the oj_mimic_json gem is required prior to multi_json (which many gems seem to require), then this exception will get raised, and I don't think the order in which Bundler requires gems can be guaranteed, right?

Should I just remove the oj_mimic_json gem from my bundle and then manually call Oj.mimic_JSON() in my config/application.rb after the Bundler.require block? Seems like it would be a safer in ensuring this exception won't get raised.

@ohler55
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I expected the existing oj_mimic_json gem to work as before. I kept that part the same. Calling mimic after requiring the json gem should overwrite the json gem's methods. Over-ridding the json gem has been a tough one to keep working. Calling mimic after loading the json may be the best approach. Any feedback on what works best for you would be appreciated.

Please sign in to comment.