rails-patch-json-encode 0.1.1 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 7de82dfcf0621f732cbb61b7973a2a8be0af6576
4
- data.tar.gz: 52320bd295199b7797f6715a425ae5594c20652f
3
+ metadata.gz: fc0249750b2f5bb056856a8f3174ec5d7c96ab61
4
+ data.tar.gz: d9c341cb2f47d207cccc25f208c43e9929b2d7b3
5
5
  SHA512:
6
- metadata.gz: 0f6a698e80c1f7279f58f421e3227045b5d81335ae8f148b2c9c53774c1e3e94880d465ca0a9f6d6e672ae5696eca351d272f1be1e54834e76a3110b8d229e76
7
- data.tar.gz: 01eb45b91075c2e77b9d985de3e1a0bbe46eed10b8c0b7fa9b41b85f0d536785e4a7a4e39bc56ffe4c838e9eaa9facdc372774dd347b774d008d4fe591d93468
6
+ metadata.gz: 3a435841a65c874ef8735a29f751ff19e406fa52579057b800ae15f8d0d1de05f7813fb9be4159dee01248b41a7cd48b0b15d968e5d9960f34fdc785896e792d
7
+ data.tar.gz: f9a2fa556af989e56efc38ce7b5cf5ed4534145e777b2e7e6ba7634e6fa3039280941653f1c21f4a21a737db30578ee3fd7df11576b6d881fd9038be4baa377f
data/README.md CHANGED
@@ -8,38 +8,44 @@ All credits goes to [Jason Hutchens](https://github.com/jasonhutchens) for disco
8
8
 
9
9
  ## Installation
10
10
 
11
- First, go to your Rails console and type:
11
+ First, let's measure the time before the patch.
12
+ Go to your Rails console and type:
12
13
 
13
- data = Hash.new
14
+ require 'benchmark'
15
+ DATA = Hash.new
14
16
  key = 'aaa'
15
- 1000.times { data[key.succ!] = data.keys }
16
- 1000 * Benchmark.realtime { data.to_json }
17
+ 1000.times { DATA[key.succ!] = DATA.keys }
18
+ Benchmark.realtime { 5.times { DATA.to_json } }
17
19
 
18
- See how Rails performs before the patch.
19
-
20
- Second, bundle install this gem with a fast JSON encoding gem in your Rails' Gemfile.
20
+ Then bundle install this gem with a fast JSON encoding gem in your Rails' Gemfile.
21
21
 
22
22
  gem 'rails-patch-json-encode'
23
- gem 'oj'
23
+ gem 'yajl-ruby', require: 'yajl'
24
24
 
25
- In this case I choose the oj gem, but you can [choose a json-encoder gem that multi_json supports](https://github.com/intridea/multi_json#supported-json-engines).
25
+ In this case I choose the yajl-ruby gem, but you can [choose a json-encoder gem that multi_json supports](https://github.com/intridea/multi_json#supported-json-engines).
26
26
 
27
- Last, there are two levels of patch available. You have to choose one and call it explictly:
27
+ The final step is to choose a patch. Two types of patches are available, and you have to choose one and invoke it explictly:
28
28
 
29
29
  * `Rails::Patch::Json::Encode.patch_base_classes` patches all Ruby base classes.
30
30
  * `Rails::Patch::Json::Encode.patch_renderers` patches Rails' ActionController::Renderers only. This is for those who had issue with the JSON gem, as patching base classes cause infinite recursive loop.
31
31
 
32
- Place one of them in Rails' initializers like config/initializers/rails_patch_json_encode.rb, and Rails should now use the faster encoder.
32
+ Place one of them in a Rails initializer (e.g. `config/initializers/rails_patch_json_encode.rb`), and Rails should now use the faster encoder.
33
33
 
34
- ## Benchmark
34
+ Now it's done. Reopen your Rails console and rerun the benchmark to see the difference.
35
35
 
36
- For console benchmark comparison, restart console after the above installation. Call `Rails::Patch::Json::Encode.patch_base_classes` in console, then re-run the test to see how the performance changes.
36
+ ## Warning
37
37
 
38
- The actual performance boost on real-world applications will probably be less than that. For one of my page I see the rendering time dropped by 25%.
38
+ If you are using Oj gem, there is no need to install this gem. Call `Oj.optimize_rails` instead.
39
39
 
40
- ## Warning
40
+ Rails in recent years added safety nets to handle nested NaN, infinity and IO objects. This gem does not handle these cases.
41
41
 
42
- This gem may break your app. **Test your app**. I am not sure if this is production ready.
42
+ This gem may break your app. **Test your app**.
43
+
44
+ ## Benchmark
45
+
46
+ `rake benchmark` is provided to show the difference before and after the patch. From my machine the time is dropped to 14% when using yajl on Rails 5.2.
47
+
48
+ The actual performance boost on real-world applications will probably be less than that. For one of my page I see the rendering time dropped by 25%.
43
49
 
44
50
  ## What's with the name
45
51
 
data/Rakefile CHANGED
@@ -1 +1,19 @@
1
1
  require "bundler/gem_tasks"
2
+
3
+ task default: :test
4
+
5
+ desc "Benchmark"
6
+ task :benchmark do
7
+ require_relative 'benchmark/base_classes'
8
+ end
9
+
10
+ ### Test
11
+ require "rake/testtask"
12
+ dir = File.dirname(__FILE__)
13
+ Rake::TestTask.new do |t|
14
+ t.libs << "test"
15
+ t.test_files = Dir.glob("#{dir}/test/**/*_test.rb")
16
+ t.warning = true
17
+ t.verbose = true
18
+ t.ruby_opts = ["--dev"] if defined?(JRUBY_VERSION)
19
+ end
@@ -0,0 +1,38 @@
1
+ require 'benchmark'
2
+ require 'json'
3
+ require 'yajl'
4
+ require 'active_support'
5
+ require 'active_support/json'
6
+ require 'active_support/core_ext/object/json'
7
+ require 'rails/patch/json/encode'
8
+
9
+ puts <<MESSAGE
10
+ Benchmark: base classes patch mode
11
+
12
+ Gem versions:
13
+ ActiveSupport: #{ActiveSupport.version}
14
+ MultiJson: #{MultiJson::VERSION}
15
+
16
+ MESSAGE
17
+
18
+ # Prepare data
19
+
20
+ DATA = Hash.new
21
+ key = 'aaa'
22
+ 1000.times { DATA[key.succ!] = DATA.keys }
23
+ DATA.freeze
24
+
25
+ LOOP = 5
26
+ def loop_encoding
27
+ LOOP.times { DATA.to_json }
28
+ end
29
+
30
+ # Benchmark
31
+
32
+ print 'before patch: '
33
+ puts(Benchmark.measure{ loop_encoding })
34
+
35
+ Rails::Patch::Json::Encode.patch_base_classes
36
+
37
+ print 'after patch: '
38
+ puts(Benchmark.measure{ loop_encoding })
@@ -1,4 +1,5 @@
1
1
  require "rails/patch/json/encode/version"
2
+ require 'multi_json'
2
3
 
3
4
  module Rails::Patch::Json::Encode
4
5
  # Use multi_json instead of Rails' to_json method (which calls ActiveSupport::JSON)
@@ -20,15 +21,24 @@ module Rails::Patch::Json::Encode
20
21
  end
21
22
  end
22
23
 
23
- # Code from http://devblog.agworld.com.au/post/42586025923/the-performance-of-to-json-in-rails-sucks-and-theres
24
+
25
+
26
+ # Combine http://devblog.agworld.com.au/post/42586025923/the-performance-of-to-json-in-rails-sucks-and-theres
27
+ # and Rails' ToJsonWithActiveSupportEncoder together,
24
28
  # essentially reversing Rails' hard-coded call to ActiveSupport::JSON.encode
25
- def self.patch_base_classes
26
- [Object, Array, FalseClass, Float, Hash, Integer, NilClass, String, TrueClass].each do |klass|
27
- klass.class_eval do
28
- def to_json(opts = {})
29
- MultiJson::dump(self.as_json(opts), opts)
30
- end
29
+ module ToJsonWithMultiJson
30
+ def to_json(options = {})
31
+ if options.is_a?(::JSON::State)
32
+ super(options)
33
+ else
34
+ ::MultiJson::dump(self.as_json(options), options)
31
35
  end
32
36
  end
33
37
  end
38
+
39
+ def self.patch_base_classes
40
+ [Object, Array, FalseClass, Float, Hash, Integer, NilClass, String, TrueClass, Enumerable].reverse_each do |klass|
41
+ klass.prepend(ToJsonWithMultiJson)
42
+ end
43
+ end
34
44
  end
@@ -2,7 +2,7 @@ module Rails
2
2
  module Patch
3
3
  module Json
4
4
  module Encode
5
- VERSION = "0.1.1"
5
+ VERSION = "0.2.0"
6
6
  end
7
7
  end
8
8
  end
@@ -18,8 +18,11 @@ Gem::Specification.new do |spec|
18
18
  spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
19
  spec.require_paths = ["lib"]
20
20
 
21
- spec.add_development_dependency "bundler", "~> 1.3"
22
- spec.add_development_dependency "rake"
21
+ spec.add_development_dependency "bundler", "~> 1.16.0"
22
+ spec.add_development_dependency "rake", "~> 12.3.0"
23
+ spec.add_development_dependency "activesupport", "~> 5.1.4"
24
+ spec.add_development_dependency "yajl-ruby"
25
+ spec.add_development_dependency "byebug"
23
26
 
24
27
  spec.add_dependency 'multi_json', '>= 1.9.3', '~> 1.0'
25
28
  end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ ORIG_ARGV = ARGV.dup
4
+
5
+ require "active_support/core_ext/kernel/reporting"
6
+
7
+ silence_warnings do
8
+ Encoding.default_internal = Encoding::UTF_8
9
+ Encoding.default_external = Encoding::UTF_8
10
+ end
11
+
12
+ require "active_support/testing/autorun"
13
+ require "active_support/testing/method_call_assertions"
14
+
15
+ ENV["NO_RELOAD"] = "1"
16
+ require "active_support"
17
+
18
+ Thread.abort_on_exception = true
19
+
20
+ # Show backtraces for deprecated behavior for quicker cleanup.
21
+ ActiveSupport::Deprecation.debug = true
22
+
23
+ # Default to old to_time behavior but allow running tests with new behavior
24
+ ActiveSupport.to_time_preserves_timezone = ENV["PRESERVE_TIMEZONES"] == "1"
25
+
26
+ # Disable available locale checks to avoid warnings running the test suite.
27
+ I18n.enforce_available_locales = false
28
+
29
+ class ActiveSupport::TestCase
30
+ include ActiveSupport::Testing::MethodCallAssertions
31
+
32
+ # Skips the current run on Rubinius using Minitest::Assertions#skip
33
+ private def rubinius_skip(message = "")
34
+ skip message if RUBY_ENGINE == "rbx"
35
+ end
36
+
37
+ # Skips the current run on JRuby using Minitest::Assertions#skip
38
+ private def jruby_skip(message = "")
39
+ skip message if defined?(JRUBY_VERSION)
40
+ end
41
+
42
+ def frozen_error_class
43
+ Object.const_defined?(:FrozenError) ? FrozenError : RuntimeError
44
+ end
45
+ end
@@ -0,0 +1,485 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'yajl'
4
+ require "securerandom"
5
+ require "abstract_unit"
6
+ require "active_support/core_ext/string/inflections"
7
+ require "active_support/core_ext/regexp"
8
+ require "active_support/json"
9
+ require "active_support/time"
10
+ require "time_zone_test_helpers"
11
+ require "encoding_test_cases"
12
+ require 'rails/patch/json/encode'
13
+
14
+ Rails::Patch::Json::Encode.patch_base_classes
15
+
16
+ class TestJSONEncoding < ActiveSupport::TestCase
17
+ include TimeZoneTestHelpers
18
+
19
+ def sorted_json(json)
20
+ if json.start_with?("{") && json.end_with?("}")
21
+ "{" + json[1..-2].split(",").sort.join(",") + "}"
22
+ else
23
+ json
24
+ end
25
+ end
26
+
27
+ JSONTest::EncodingTestCases.constants.each do |class_tests|
28
+ define_method("test_#{class_tests[0..-6].underscore}") do
29
+ begin
30
+ prev = ActiveSupport.use_standard_json_time_format
31
+
32
+ standard_class_tests = /Standard/.match?(class_tests)
33
+
34
+ ActiveSupport.escape_html_entities_in_json = !standard_class_tests
35
+ ActiveSupport.use_standard_json_time_format = standard_class_tests
36
+ JSONTest::EncodingTestCases.const_get(class_tests).each do |pair|
37
+ assert_equal pair.last, sorted_json(ActiveSupport::JSON.encode(pair.first))
38
+ end
39
+ ensure
40
+ ActiveSupport.escape_html_entities_in_json = false
41
+ ActiveSupport.use_standard_json_time_format = prev
42
+ end
43
+ end
44
+ end
45
+
46
+ def test_process_status
47
+ rubinius_skip "https://github.com/rubinius/rubinius/issues/3334"
48
+
49
+ # There doesn't seem to be a good way to get a handle on a Process::Status object without actually
50
+ # creating a child process, hence this to populate $?
51
+ system("not_a_real_program_#{SecureRandom.hex}")
52
+ assert_equal %({"exitstatus":#{$?.exitstatus},"pid":#{$?.pid}}), ActiveSupport::JSON.encode($?)
53
+ end
54
+
55
+ def test_hash_encoding
56
+ assert_equal %({\"a\":\"b\"}), ActiveSupport::JSON.encode(a: :b)
57
+ assert_equal %({\"a\":1}), ActiveSupport::JSON.encode("a" => 1)
58
+ assert_equal %({\"a\":[1,2]}), ActiveSupport::JSON.encode("a" => [1, 2])
59
+ assert_equal %({"1":2}), ActiveSupport::JSON.encode(1 => 2)
60
+
61
+ assert_equal %({\"a\":\"b\",\"c\":\"d\"}), sorted_json(ActiveSupport::JSON.encode(a: :b, c: :d))
62
+ end
63
+
64
+ def test_hash_keys_encoding
65
+ ActiveSupport.escape_html_entities_in_json = true
66
+ assert_equal "{\"\\u003c\\u003e\":\"\\u003c\\u003e\"}", ActiveSupport::JSON.encode("<>" => "<>")
67
+ ensure
68
+ ActiveSupport.escape_html_entities_in_json = false
69
+ end
70
+
71
+ def test_utf8_string_encoded_properly
72
+ result = ActiveSupport::JSON.encode("€2.99")
73
+ assert_equal '"€2.99"', result
74
+ assert_equal(Encoding::UTF_8, result.encoding)
75
+
76
+ result = ActiveSupport::JSON.encode("✎☺")
77
+ assert_equal '"✎☺"', result
78
+ assert_equal(Encoding::UTF_8, result.encoding)
79
+ end
80
+
81
+ def test_non_utf8_string_transcodes
82
+ s = "二".encode("Shift_JIS")
83
+ result = ActiveSupport::JSON.encode(s)
84
+ assert_equal '"二"', result
85
+ assert_equal Encoding::UTF_8, result.encoding
86
+ end
87
+
88
+ def test_wide_utf8_chars
89
+ w = "𠜎"
90
+ result = ActiveSupport::JSON.encode(w)
91
+ assert_equal '"𠜎"', result
92
+ end
93
+
94
+ def test_wide_utf8_roundtrip
95
+ hash = { string: "𐒑" }
96
+ json = ActiveSupport::JSON.encode(hash)
97
+ decoded_hash = ActiveSupport::JSON.decode(json)
98
+ assert_equal "𐒑", decoded_hash["string"]
99
+ end
100
+
101
+ def test_hash_key_identifiers_are_always_quoted
102
+ values = { 0 => 0, 1 => 1, :_ => :_, "$" => "$", "a" => "a", :A => :A, :A0 => :A0, "A0B" => "A0B" }
103
+ assert_equal %w( "$" "A" "A0" "A0B" "_" "a" "0" "1" ).sort, object_keys(ActiveSupport::JSON.encode(values))
104
+ end
105
+
106
+ def test_hash_should_allow_key_filtering_with_only
107
+ assert_equal %({"a":1}), ActiveSupport::JSON.encode({ "a" => 1, :b => 2, :c => 3 }, { only: "a" })
108
+ end
109
+
110
+ def test_hash_should_allow_key_filtering_with_except
111
+ assert_equal %({"b":2}), ActiveSupport::JSON.encode({ "foo" => "bar", :b => 2, :c => 3 }, { except: ["foo", :c] })
112
+ end
113
+
114
+ def test_time_to_json_includes_local_offset
115
+ with_standard_json_time_format(true) do
116
+ with_env_tz "US/Eastern" do
117
+ assert_equal %("2005-02-01T15:15:10.000-05:00"), ActiveSupport::JSON.encode(Time.local(2005, 2, 1, 15, 15, 10))
118
+ end
119
+ end
120
+ end
121
+
122
+ def test_hash_with_time_to_json
123
+ with_standard_json_time_format(false) do
124
+ assert_equal '{"time":"2009/01/01 00:00:00 +0000"}', { time: Time.utc(2009) }.to_json
125
+ end
126
+ end
127
+
128
+ def test_nested_hash_with_float
129
+ assert_nothing_raised do
130
+ hash = {
131
+ "CHI" => {
132
+ display_name: "chicago",
133
+ latitude: 123.234
134
+ }
135
+ }
136
+ ActiveSupport::JSON.encode(hash)
137
+ end
138
+ end
139
+
140
+ def test_hash_like_with_options
141
+ h = JSONTest::Hashlike.new
142
+ json = h.to_json only: [:foo]
143
+
144
+ assert_equal({ "foo" => "hello" }, JSON.parse(json))
145
+ end
146
+
147
+ def test_object_to_json_with_options
148
+ obj = Object.new
149
+ obj.instance_variable_set :@foo, "hello"
150
+ obj.instance_variable_set :@bar, "world"
151
+ json = obj.to_json only: ["foo"]
152
+
153
+ assert_equal({ "foo" => "hello" }, JSON.parse(json))
154
+ end
155
+
156
+ def test_struct_to_json_with_options
157
+ struct = Struct.new(:foo, :bar).new
158
+ struct.foo = "hello"
159
+ struct.bar = "world"
160
+ json = struct.to_json only: [:foo]
161
+
162
+ assert_equal({ "foo" => "hello" }, JSON.parse(json))
163
+ end
164
+
165
+ def test_hash_should_pass_encoding_options_to_children_in_as_json
166
+ person = {
167
+ name: "John",
168
+ address: {
169
+ city: "London",
170
+ country: "UK"
171
+ }
172
+ }
173
+ json = person.as_json only: [:address, :city]
174
+
175
+ assert_equal({ "address" => { "city" => "London" } }, json)
176
+ end
177
+
178
+ def test_hash_should_pass_encoding_options_to_children_in_to_json
179
+ person = {
180
+ name: "John",
181
+ address: {
182
+ city: "London",
183
+ country: "UK"
184
+ }
185
+ }
186
+ json = person.to_json only: [:address, :city]
187
+
188
+ assert_equal(%({"address":{"city":"London"}}), json)
189
+ end
190
+
191
+ def test_array_should_pass_encoding_options_to_children_in_as_json
192
+ people = [
193
+ { name: "John", address: { city: "London", country: "UK" } },
194
+ { name: "Jean", address: { city: "Paris", country: "France" } }
195
+ ]
196
+ json = people.as_json only: [:address, :city]
197
+ expected = [
198
+ { "address" => { "city" => "London" } },
199
+ { "address" => { "city" => "Paris" } }
200
+ ]
201
+
202
+ assert_equal(expected, json)
203
+ end
204
+
205
+ def test_array_should_pass_encoding_options_to_children_in_to_json
206
+ people = [
207
+ { name: "John", address: { city: "London", country: "UK" } },
208
+ { name: "Jean", address: { city: "Paris", country: "France" } }
209
+ ]
210
+ json = people.to_json only: [:address, :city]
211
+
212
+ assert_equal(%([{"address":{"city":"London"}},{"address":{"city":"Paris"}}]), json)
213
+ end
214
+
215
+ People = Class.new(BasicObject) do
216
+ include Enumerable
217
+ def initialize
218
+ @people = [
219
+ { name: "John", address: { city: "London", country: "UK" } },
220
+ { name: "Jean", address: { city: "Paris", country: "France" } }
221
+ ]
222
+ end
223
+ def each(*, &blk)
224
+ @people.each do |p|
225
+ yield p if blk
226
+ p
227
+ end.each
228
+ end
229
+ end
230
+
231
+ def test_enumerable_should_generate_json_with_as_json
232
+ json = People.new.as_json only: [:address, :city]
233
+ expected = [
234
+ { "address" => { "city" => "London" } },
235
+ { "address" => { "city" => "Paris" } }
236
+ ]
237
+
238
+ assert_equal(expected, json)
239
+ end
240
+
241
+ def test_enumerable_should_generate_json_with_to_json
242
+ json = People.new.to_json only: [:address, :city]
243
+ assert_equal(%([{"address":{"city":"London"}},{"address":{"city":"Paris"}}]), json)
244
+ end
245
+
246
+ def test_enumerable_should_pass_encoding_options_to_children_in_as_json
247
+ json = People.new.each.as_json only: [:address, :city]
248
+ expected = [
249
+ { "address" => { "city" => "London" } },
250
+ { "address" => { "city" => "Paris" } }
251
+ ]
252
+
253
+ assert_equal(expected, json)
254
+ end
255
+
256
+ def test_enumerable_should_pass_encoding_options_to_children_in_to_json
257
+ json = People.new.each.to_json only: [:address, :city]
258
+
259
+ assert_equal(%([{"address":{"city":"London"}},{"address":{"city":"Paris"}}]), json)
260
+ end
261
+
262
+ class CustomWithOptions
263
+ attr_accessor :foo, :bar
264
+
265
+ def as_json(options = {})
266
+ options[:only] = %w(foo bar)
267
+ super(options)
268
+ end
269
+ end
270
+
271
+ def test_hash_to_json_should_not_keep_options_around
272
+ f = CustomWithOptions.new
273
+ f.foo = "hello"
274
+ f.bar = "world"
275
+
276
+ hash = { "foo" => f, "other_hash" => { "foo" => "other_foo", "test" => "other_test" } }
277
+ assert_equal({ "foo" => { "foo" => "hello", "bar" => "world" },
278
+ "other_hash" => { "foo" => "other_foo", "test" => "other_test" } }, ActiveSupport::JSON.decode(hash.to_json))
279
+ end
280
+
281
+ def test_array_to_json_should_not_keep_options_around
282
+ f = CustomWithOptions.new
283
+ f.foo = "hello"
284
+ f.bar = "world"
285
+
286
+ array = [f, { "foo" => "other_foo", "test" => "other_test" }]
287
+ assert_equal([{ "foo" => "hello", "bar" => "world" },
288
+ { "foo" => "other_foo", "test" => "other_test" }], ActiveSupport::JSON.decode(array.to_json))
289
+ end
290
+
291
+ class OptionsTest
292
+ def as_json(options = :default)
293
+ options
294
+ end
295
+ end
296
+
297
+ def test_hash_as_json_without_options
298
+ json = { foo: OptionsTest.new }.as_json
299
+ assert_equal({ "foo" => :default }, json)
300
+ end
301
+
302
+ def test_array_as_json_without_options
303
+ json = [ OptionsTest.new ].as_json
304
+ assert_equal([:default], json)
305
+ end
306
+
307
+ def test_struct_encoding
308
+ Struct.new("UserNameAndEmail", :name, :email)
309
+ Struct.new("UserNameAndDate", :name, :date)
310
+ Struct.new("Custom", :name, :sub)
311
+ user_email = Struct::UserNameAndEmail.new "David", "sample@example.com"
312
+ user_birthday = Struct::UserNameAndDate.new "David", Date.new(2010, 01, 01)
313
+ custom = Struct::Custom.new "David", user_birthday
314
+
315
+ json_strings = ""
316
+ json_string_and_date = ""
317
+ json_custom = ""
318
+
319
+ assert_nothing_raised do
320
+ json_strings = user_email.to_json
321
+ json_string_and_date = user_birthday.to_json
322
+ json_custom = custom.to_json
323
+ end
324
+
325
+ assert_equal({ "name" => "David",
326
+ "sub" => {
327
+ "name" => "David",
328
+ "date" => "2010-01-01" } }, ActiveSupport::JSON.decode(json_custom))
329
+
330
+ assert_equal({ "name" => "David", "email" => "sample@example.com" },
331
+ ActiveSupport::JSON.decode(json_strings))
332
+
333
+ assert_equal({ "name" => "David", "date" => "2010-01-01" },
334
+ ActiveSupport::JSON.decode(json_string_and_date))
335
+ end
336
+
337
+ def test_nil_true_and_false_represented_as_themselves
338
+ assert_nil nil.as_json
339
+ assert_equal true, true.as_json
340
+ assert_equal false, false.as_json
341
+ end
342
+
343
+ class HashWithAsJson < Hash
344
+ attr_accessor :as_json_called
345
+
346
+ def initialize(*)
347
+ super
348
+ end
349
+
350
+ def as_json(options = {})
351
+ @as_json_called = true
352
+ super
353
+ end
354
+ end
355
+
356
+ def test_json_gem_dump_by_passing_active_support_encoder
357
+ h = HashWithAsJson.new
358
+ h[:foo] = "hello"
359
+ h[:bar] = "world"
360
+
361
+ assert_equal %({"foo":"hello","bar":"world"}), JSON.dump(h)
362
+ assert_nil h.as_json_called
363
+ end
364
+
365
+ def test_json_gem_generate_by_passing_active_support_encoder
366
+ h = HashWithAsJson.new
367
+ h[:foo] = "hello"
368
+ h[:bar] = "world"
369
+
370
+ assert_equal %({"foo":"hello","bar":"world"}), JSON.generate(h)
371
+ assert_nil h.as_json_called
372
+ end
373
+
374
+ def test_json_gem_pretty_generate_by_passing_active_support_encoder
375
+ h = HashWithAsJson.new
376
+ h[:foo] = "hello"
377
+ h[:bar] = "world"
378
+
379
+ assert_equal <<EXPECTED.chomp, JSON.pretty_generate(h)
380
+ {
381
+ "foo": "hello",
382
+ "bar": "world"
383
+ }
384
+ EXPECTED
385
+ assert_nil h.as_json_called
386
+ end
387
+
388
+ def test_twz_to_json_with_use_standard_json_time_format_config_set_to_false
389
+ with_standard_json_time_format(false) do
390
+ zone = ActiveSupport::TimeZone["Eastern Time (US & Canada)"]
391
+ time = ActiveSupport::TimeWithZone.new(Time.utc(2000), zone)
392
+ assert_equal "\"1999/12/31 19:00:00 -0500\"", ActiveSupport::JSON.encode(time)
393
+ end
394
+ end
395
+
396
+ def test_twz_to_json_with_use_standard_json_time_format_config_set_to_true
397
+ with_standard_json_time_format(true) do
398
+ zone = ActiveSupport::TimeZone["Eastern Time (US & Canada)"]
399
+ time = ActiveSupport::TimeWithZone.new(Time.utc(2000), zone)
400
+ assert_equal "\"1999-12-31T19:00:00.000-05:00\"", ActiveSupport::JSON.encode(time)
401
+ end
402
+ end
403
+
404
+ def test_twz_to_json_with_custom_time_precision
405
+ with_standard_json_time_format(true) do
406
+ with_time_precision(0) do
407
+ zone = ActiveSupport::TimeZone["Eastern Time (US & Canada)"]
408
+ time = ActiveSupport::TimeWithZone.new(Time.utc(2000), zone)
409
+ assert_equal "\"1999-12-31T19:00:00-05:00\"", ActiveSupport::JSON.encode(time)
410
+ end
411
+ end
412
+ end
413
+
414
+ def test_time_to_json_with_custom_time_precision
415
+ with_standard_json_time_format(true) do
416
+ with_time_precision(0) do
417
+ assert_equal "\"2000-01-01T00:00:00Z\"", ActiveSupport::JSON.encode(Time.utc(2000))
418
+ end
419
+ end
420
+ end
421
+
422
+ def test_datetime_to_json_with_custom_time_precision
423
+ with_standard_json_time_format(true) do
424
+ with_time_precision(0) do
425
+ assert_equal "\"2000-01-01T00:00:00+00:00\"", ActiveSupport::JSON.encode(DateTime.new(2000))
426
+ end
427
+ end
428
+ end
429
+
430
+ def test_twz_to_json_when_wrapping_a_date_time
431
+ zone = ActiveSupport::TimeZone["Eastern Time (US & Canada)"]
432
+ time = ActiveSupport::TimeWithZone.new(DateTime.new(2000), zone)
433
+ assert_equal '"1999-12-31T19:00:00.000-05:00"', ActiveSupport::JSON.encode(time)
434
+ end
435
+
436
+ def test_exception_to_json
437
+ exception = Exception.new("foo")
438
+ assert_equal '"foo"', ActiveSupport::JSON.encode(exception)
439
+ end
440
+
441
+ class InfiniteNumber
442
+ def as_json(options = nil)
443
+ { "number" => Float::INFINITY }
444
+ end
445
+ end
446
+
447
+ def test_to_json_works_when_as_json_returns_infinite_number
448
+ assert_equal '{"number":null}', InfiniteNumber.new.to_json
449
+ end
450
+
451
+ class NaNNumber
452
+ def as_json(options = nil)
453
+ { "number" => Float::NAN }
454
+ end
455
+ end
456
+
457
+ def test_to_json_works_when_as_json_returns_NaN_number
458
+ assert_equal '{"number":null}', NaNNumber.new.to_json
459
+ end
460
+
461
+ def test_to_json_works_on_io_objects
462
+ assert_equal STDOUT.to_s.to_json, STDOUT.to_json
463
+ end
464
+
465
+ private
466
+
467
+ def object_keys(json_object)
468
+ json_object[1..-2].scan(/([^{}:,\s]+):/).flatten.sort
469
+ end
470
+
471
+ def with_standard_json_time_format(boolean = true)
472
+ old, ActiveSupport.use_standard_json_time_format = ActiveSupport.use_standard_json_time_format, boolean
473
+ yield
474
+ ensure
475
+ ActiveSupport.use_standard_json_time_format = old
476
+ end
477
+
478
+ def with_time_precision(value)
479
+ old_value = ActiveSupport::JSON::Encoding.time_precision
480
+ ActiveSupport::JSON::Encoding.time_precision = value
481
+ yield
482
+ ensure
483
+ ActiveSupport::JSON::Encoding.time_precision = old_value
484
+ end
485
+ end
@@ -0,0 +1,98 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bigdecimal"
4
+ require "date"
5
+ require "time"
6
+ require "pathname"
7
+ require "uri"
8
+
9
+ module JSONTest
10
+ class Foo
11
+ def initialize(a, b)
12
+ @a, @b = a, b
13
+ end
14
+ end
15
+
16
+ class Hashlike
17
+ def to_hash
18
+ { foo: "hello", bar: "world" }
19
+ end
20
+ end
21
+
22
+ class Custom
23
+ def initialize(serialized)
24
+ @serialized = serialized
25
+ end
26
+
27
+ def as_json(options = nil)
28
+ @serialized
29
+ end
30
+ end
31
+
32
+ MyStruct = Struct.new(:name, :value) do
33
+ def initialize(*)
34
+ @unused = "unused instance variable"
35
+ super
36
+ end
37
+ end
38
+
39
+ module EncodingTestCases
40
+ TrueTests = [[ true, %(true) ]]
41
+ FalseTests = [[ false, %(false) ]]
42
+ NilTests = [[ nil, %(null) ]]
43
+ NumericTests = [[ 1, %(1) ],
44
+ [ 2.5, %(2.5) ],
45
+ [ 0.0 / 0.0, %(null) ],
46
+ [ 1.0 / 0.0, %(null) ],
47
+ [ -1.0 / 0.0, %(null) ],
48
+ [ BigDecimal("0.0") / BigDecimal("0.0"), %(null) ],
49
+ [ BigDecimal("2.5"), %("#{BigDecimal('2.5')}") ]]
50
+
51
+ StringTests = [[ "this is the <string>", %("this is the \\u003cstring\\u003e")],
52
+ [ 'a "string" with quotes & an ampersand', %("a \\"string\\" with quotes \\u0026 an ampersand") ],
53
+ [ "http://test.host/posts/1", %("http://test.host/posts/1")],
54
+ [ "Control characters: \x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f\u2028\u2029",
55
+ %("Control characters: \\u0000\\u0001\\u0002\\u0003\\u0004\\u0005\\u0006\\u0007\\b\\t\\n\\u000b\\f\\r\\u000e\\u000f\\u0010\\u0011\\u0012\\u0013\\u0014\\u0015\\u0016\\u0017\\u0018\\u0019\\u001a\\u001b\\u001c\\u001d\\u001e\\u001f\\u2028\\u2029") ]]
56
+
57
+ ArrayTests = [[ ["a", "b", "c"], %([\"a\",\"b\",\"c\"]) ],
58
+ [ [1, "a", :b, nil, false], %([1,\"a\",\"b\",null,false]) ]]
59
+
60
+ HashTests = [[ { foo: "bar" }, %({\"foo\":\"bar\"}) ],
61
+ [ { 1 => 1, 2 => "a", 3 => :b, 4 => nil, 5 => false }, %({\"1\":1,\"2\":\"a\",\"3\":\"b\",\"4\":null,\"5\":false}) ]]
62
+
63
+ RangeTests = [[ 1..2, %("1..2")],
64
+ [ 1...2, %("1...2")],
65
+ [ 1.5..2.5, %("1.5..2.5")]]
66
+
67
+ SymbolTests = [[ :a, %("a") ],
68
+ [ :this, %("this") ],
69
+ [ :"a b", %("a b") ]]
70
+
71
+ ObjectTests = [[ Foo.new(1, 2), %({\"a\":1,\"b\":2}) ]]
72
+ HashlikeTests = [[ Hashlike.new, %({\"bar\":\"world\",\"foo\":\"hello\"}) ]]
73
+ StructTests = [[ MyStruct.new(:foo, "bar"), %({\"name\":\"foo\",\"value\":\"bar\"}) ],
74
+ [ MyStruct.new(nil, nil), %({\"name\":null,\"value\":null}) ]]
75
+ CustomTests = [[ Custom.new("custom"), '"custom"' ],
76
+ [ Custom.new(nil), "null" ],
77
+ [ Custom.new(:a), '"a"' ],
78
+ [ Custom.new([ :foo, "bar" ]), '["foo","bar"]' ],
79
+ [ Custom.new(foo: "hello", bar: "world"), '{"bar":"world","foo":"hello"}' ],
80
+ [ Custom.new(Hashlike.new), '{"bar":"world","foo":"hello"}' ],
81
+ [ Custom.new(Custom.new(Custom.new(:a))), '"a"' ]]
82
+
83
+ RegexpTests = [[ /^a/, '"(?-mix:^a)"' ], [/^\w{1,2}[a-z]+/ix, '"(?ix-m:^\\\\w{1,2}[a-z]+)"']]
84
+
85
+ URITests = [[ URI.parse("http://example.com"), %("http://example.com") ]]
86
+
87
+ PathnameTests = [[ Pathname.new("lib/index.rb"), %("lib/index.rb") ]]
88
+
89
+ DateTests = [[ Date.new(2005, 2, 1), %("2005/02/01") ]]
90
+ TimeTests = [[ Time.utc(2005, 2, 1, 15, 15, 10), %("2005/02/01 15:15:10 +0000") ]]
91
+ DateTimeTests = [[ DateTime.civil(2005, 2, 1, 15, 15, 10), %("2005/02/01 15:15:10 +0000") ]]
92
+
93
+ StandardDateTests = [[ Date.new(2005, 2, 1), %("2005-02-01") ]]
94
+ StandardTimeTests = [[ Time.utc(2005, 2, 1, 15, 15, 10), %("2005-02-01T15:15:10.000Z") ]]
95
+ StandardDateTimeTests = [[ DateTime.civil(2005, 2, 1, 15, 15, 10), %("2005-02-01T15:15:10.000+00:00") ]]
96
+ StandardStringTests = [[ "this is the <string>", %("this is the <string>")]]
97
+ end
98
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TimeZoneTestHelpers
4
+ def with_tz_default(tz = nil)
5
+ old_tz = Time.zone
6
+ Time.zone = tz
7
+ yield
8
+ ensure
9
+ Time.zone = old_tz
10
+ end
11
+
12
+ def with_env_tz(new_tz = "US/Eastern")
13
+ old_tz, ENV["TZ"] = ENV["TZ"], new_tz
14
+ yield
15
+ ensure
16
+ old_tz ? ENV["TZ"] = old_tz : ENV.delete("TZ")
17
+ end
18
+
19
+ def with_preserve_timezone(value)
20
+ old_preserve_tz = ActiveSupport.to_time_preserves_timezone
21
+ ActiveSupport.to_time_preserves_timezone = value
22
+ yield
23
+ ensure
24
+ ActiveSupport.to_time_preserves_timezone = old_preserve_tz
25
+ end
26
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rails-patch-json-encode
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - lulalala
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2014-08-22 00:00:00.000000000 Z
12
+ date: 2018-01-05 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: bundler
@@ -17,16 +17,58 @@ dependencies:
17
17
  requirements:
18
18
  - - "~>"
19
19
  - !ruby/object:Gem::Version
20
- version: '1.3'
20
+ version: 1.16.0
21
21
  type: :development
22
22
  prerelease: false
23
23
  version_requirements: !ruby/object:Gem::Requirement
24
24
  requirements:
25
25
  - - "~>"
26
26
  - !ruby/object:Gem::Version
27
- version: '1.3'
27
+ version: 1.16.0
28
28
  - !ruby/object:Gem::Dependency
29
29
  name: rake
30
+ requirement: !ruby/object:Gem::Requirement
31
+ requirements:
32
+ - - "~>"
33
+ - !ruby/object:Gem::Version
34
+ version: 12.3.0
35
+ type: :development
36
+ prerelease: false
37
+ version_requirements: !ruby/object:Gem::Requirement
38
+ requirements:
39
+ - - "~>"
40
+ - !ruby/object:Gem::Version
41
+ version: 12.3.0
42
+ - !ruby/object:Gem::Dependency
43
+ name: activesupport
44
+ requirement: !ruby/object:Gem::Requirement
45
+ requirements:
46
+ - - "~>"
47
+ - !ruby/object:Gem::Version
48
+ version: 5.1.4
49
+ type: :development
50
+ prerelease: false
51
+ version_requirements: !ruby/object:Gem::Requirement
52
+ requirements:
53
+ - - "~>"
54
+ - !ruby/object:Gem::Version
55
+ version: 5.1.4
56
+ - !ruby/object:Gem::Dependency
57
+ name: yajl-ruby
58
+ requirement: !ruby/object:Gem::Requirement
59
+ requirements:
60
+ - - ">="
61
+ - !ruby/object:Gem::Version
62
+ version: '0'
63
+ type: :development
64
+ prerelease: false
65
+ version_requirements: !ruby/object:Gem::Requirement
66
+ requirements:
67
+ - - ">="
68
+ - !ruby/object:Gem::Version
69
+ version: '0'
70
+ - !ruby/object:Gem::Dependency
71
+ name: byebug
30
72
  requirement: !ruby/object:Gem::Requirement
31
73
  requirements:
32
74
  - - ">="
@@ -71,9 +113,14 @@ files:
71
113
  - LICENSE.txt
72
114
  - README.md
73
115
  - Rakefile
116
+ - benchmark/base_classes.rb
74
117
  - lib/rails/patch/json/encode.rb
75
118
  - lib/rails/patch/json/encode/version.rb
76
119
  - rails-patch-json-encode.gemspec
120
+ - test/abstract_unit.rb
121
+ - test/encoding_test.rb
122
+ - test/encoding_test_cases.rb
123
+ - test/time_zone_test_helpers.rb
77
124
  homepage: https://github.com/GoodLife/rails-patch-json-encode
78
125
  licenses:
79
126
  - MIT
@@ -94,8 +141,12 @@ required_rubygems_version: !ruby/object:Gem::Requirement
94
141
  version: '0'
95
142
  requirements: []
96
143
  rubyforge_project:
97
- rubygems_version: 2.2.2
144
+ rubygems_version: 2.6.13
98
145
  signing_key:
99
146
  specification_version: 4
100
147
  summary: A monkey patch to speed up Rails' JSON generation time.
101
- test_files: []
148
+ test_files:
149
+ - test/abstract_unit.rb
150
+ - test/encoding_test.rb
151
+ - test/encoding_test_cases.rb
152
+ - test/time_zone_test_helpers.rb