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 +4 -4
- data/README.md +22 -16
- data/Rakefile +18 -0
- data/benchmark/base_classes.rb +38 -0
- data/lib/rails/patch/json/encode.rb +17 -7
- data/lib/rails/patch/json/encode/version.rb +1 -1
- data/rails-patch-json-encode.gemspec +5 -2
- data/test/abstract_unit.rb +45 -0
- data/test/encoding_test.rb +485 -0
- data/test/encoding_test_cases.rb +98 -0
- data/test/time_zone_test_helpers.rb +26 -0
- metadata +57 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: fc0249750b2f5bb056856a8f3174ec5d7c96ab61
|
4
|
+
data.tar.gz: d9c341cb2f47d207cccc25f208c43e9929b2d7b3
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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,
|
11
|
+
First, let's measure the time before the patch.
|
12
|
+
Go to your Rails console and type:
|
12
13
|
|
13
|
-
|
14
|
+
require 'benchmark'
|
15
|
+
DATA = Hash.new
|
14
16
|
key = 'aaa'
|
15
|
-
1000.times {
|
16
|
-
|
17
|
+
1000.times { DATA[key.succ!] = DATA.keys }
|
18
|
+
Benchmark.realtime { 5.times { DATA.to_json } }
|
17
19
|
|
18
|
-
|
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 '
|
23
|
+
gem 'yajl-ruby', require: 'yajl'
|
24
24
|
|
25
|
-
In this case I choose the
|
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
|
-
|
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
|
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
|
-
|
34
|
+
Now it's done. Reopen your Rails console and rerun the benchmark to see the difference.
|
35
35
|
|
36
|
-
|
36
|
+
## Warning
|
37
37
|
|
38
|
-
|
38
|
+
If you are using Oj gem, there is no need to install this gem. Call `Oj.optimize_rails` instead.
|
39
39
|
|
40
|
-
|
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**.
|
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
|
-
|
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
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
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
|
@@ -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.
|
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.
|
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:
|
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:
|
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:
|
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.
|
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
|