json_pure 2.6.3 → 2.7.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
  SHA256:
3
- metadata.gz: 47480495892b93950cd4e9e2a1cb7bdc9979b0c407265c562b07924adf33e5a3
4
- data.tar.gz: d7a139c397c9d1999f1a447f8c54900a7b4ee221393a3aaa2cf7af4c03f98d09
3
+ metadata.gz: 77e458393f6a12f043ff7acf7aca6f70409158bf1ebe613f009f88551da32295
4
+ data.tar.gz: cabc5299a4f77bf82eb7077d3048433bbb1a3bffbd2d3c1b95385712614423ef
5
5
  SHA512:
6
- metadata.gz: 1ab8582b0634745b2981725141ddf1893604e66bcf787339438d1cf3ff9ec988e1b575c4e01dd888c69c35df2193b6376db068b37ce6331e224e45593f08118d
7
- data.tar.gz: a8eb764c94a11f9e6d0e95296635dff0bf409816f19cca71f90fb9ba8d0c8edacb97ac1804b57107f4fe385cdde8d0864808b148a66dca9ba4e541e938a080b9
6
+ metadata.gz: 8ab9bf253868c6a6f637b85bc1ef432c289b733e0f41086e4a9a4a9a27fb63ebef4f7b15adb1a8792f7486f010080e9c2f902d8efaeedbb0fd2575e744e5b167
7
+ data.tar.gz: a1e77e1df8e8d219a7a88fc62160d1748f19ff4cc29120cba136114370920021216ae9e9b761391c90158566d4601d70005c4df18ae145a00b47f6803e6dca5d
data/CHANGES.md CHANGED
@@ -1,5 +1,31 @@
1
1
  # Changes
2
2
 
3
+ ### 2023-12-01 (2.7.0)
4
+
5
+ * Add a strict option to Generator #519
6
+ * `escape_slash` option was renamed as `script_safe` and now also escape U+2028 and U+2029. `escape_slash` is now an alias of `script_safe` #525
7
+ * Remove unnecessary initialization of create_id in JSON.parse() #454
8
+ * Improvements to Hash#to_json in pure implementation generator #203
9
+ * Use ruby_xfree to free buffers #518
10
+ * Fix "unexpected token" offset for Infinity #507
11
+ * Avoid using deprecated BigDecimal.new on JRuby #546
12
+ * Removed code for Ruby 1.8 #540
13
+ * Rename JSON::ParseError to JSON:ParserError #530
14
+ * Call super in included hook #486
15
+ * JRuby requires a minimum of Java 8 #516
16
+ * Always indent even if empty #517
17
+
18
+ ### 2022-11-30 (2.6.3)
19
+
20
+ * bugfix json/pure mixing escaped with literal unicode raises Encoding::CompatibilityError #483
21
+ * Stop including the parser source __LINE__ in exceptions #470
22
+
23
+ ### 2022-11-17 (2.6.2)
24
+
25
+ * Remove unknown keyword arg from DateTime.parse #488
26
+ * Ignore java artifacts by @hsbt #489
27
+ * Fix parser bug for empty string allocation #496
28
+
3
29
  ### 2021-10-24 (2.6.1)
4
30
 
5
31
  * Restore version.rb with 2.6.1
@@ -105,6 +131,19 @@
105
131
  I changed these mentions to be consistent with the Ruby license setting in
106
132
  the gemspec files which were already correct now.
107
133
 
134
+ ## 2017-01-13 (1.8.6)
135
+ * Be compatible with ancient ruby 1.8 (maybe?)
136
+
137
+ ## 2015-09-11 (1.8.5)
138
+ * Be compatible with ruby 2.4.0
139
+ * There were still some mentions of dual GPL licensing in the source, but JSON
140
+ has just the Ruby license that itself includes an explicit dual-licensing
141
+ clause that allows covered software to be distributed under the terms of
142
+ the Simplified BSD License instead for all ruby versions >= 1.9.3. This is
143
+ however a GPL compatible license according to the Free Software Foundation.
144
+ I changed these mentions to be consistent with the Ruby license setting in
145
+ the gemspec files which were already correct now.
146
+
108
147
  ## 2015-06-01 (1.8.3)
109
148
  * Fix potential memory leak, thx to nobu.
110
149
 
data/README.md CHANGED
@@ -12,8 +12,7 @@ will be two variants available:
12
12
  extensions, which are both part of the ruby standard library.
13
13
  * The quite a bit faster native extension variant, which is in parts
14
14
  implemented in C or Java and comes with its own unicode conversion
15
- functions and a parser generated by the ragel state machine compiler
16
- http://www.complang.org/ragel/ .
15
+ functions and a parser generated by the [Ragel] state machine compiler.
17
16
 
18
17
  Both variants of the JSON generator generate UTF-8 character sequences by
19
18
  default. If an :ascii\_only option with a true value is given, they escape all
@@ -71,8 +70,7 @@ with:
71
70
  ## Compiling the extensions yourself
72
71
 
73
72
  If you want to create the `parser.c` file from its `parser.rl` file or draw nice
74
- graphviz images of the state machines, you need ragel from:
75
- http://www.complang.org/ragel/
73
+ graphviz images of the state machines, you need [Ragel].
76
74
 
77
75
  ## Usage
78
76
 
@@ -423,3 +421,5 @@ The latest version of this library can be downloaded at
423
421
  Online Documentation should be located at
424
422
 
425
423
  * https://www.rubydoc.info/gems/json
424
+
425
+ [Ragel]: http://www.colm.net/open-source/ragel/
data/VERSION CHANGED
@@ -1 +1 @@
1
- 2.6.3
1
+ 2.7.0
data/json_pure.gemspec CHANGED
@@ -41,12 +41,12 @@ Gem::Specification.new do |s|
41
41
  "lib/json/pure/parser.rb".freeze,
42
42
  "lib/json/version.rb".freeze,
43
43
  ]
44
- s.homepage = "http://flori.github.com/json".freeze
44
+ s.homepage = "https://flori.github.io/json".freeze
45
45
  s.metadata = {
46
46
  'bug_tracker_uri' => 'https://github.com/flori/json/issues',
47
47
  'changelog_uri' => 'https://github.com/flori/json/blob/master/CHANGES.md',
48
- 'documentation_uri' => 'http://flori.github.io/json/doc/index.html',
49
- 'homepage_uri' => 'http://flori.github.io/json/',
48
+ 'documentation_uri' => 'https://flori.github.io/json/doc/index.html',
49
+ 'homepage_uri' => s.homepage,
50
50
  'source_code_uri' => 'https://github.com/flori/json',
51
51
  'wiki_uri' => 'https://github.com/flori/json/wiki'
52
52
  }
@@ -2,7 +2,10 @@
2
2
  unless defined?(::JSON::JSON_LOADED) and ::JSON::JSON_LOADED
3
3
  require 'json'
4
4
  end
5
- defined?(::BigDecimal) or require 'bigdecimal'
5
+ begin
6
+ require 'bigdecimal'
7
+ rescue LoadError
8
+ end
6
9
 
7
10
  class BigDecimal
8
11
  # Import a JSON Marshalled object.
@@ -26,4 +29,4 @@ class BigDecimal
26
29
  def to_json(*args)
27
30
  as_json.to_json(*args)
28
31
  end
29
- end
32
+ end if defined?(::BigDecimal)
@@ -5,14 +5,25 @@ end
5
5
 
6
6
  class Range
7
7
 
8
- # Deserializes JSON string by constructing new Range object with arguments
9
- # <tt>a</tt> serialized by <tt>to_json</tt>.
8
+ # Returns a new \Range object constructed from <tt>object['a']</tt>,
9
+ # which must be an array of values suitable for a call to Range.new:
10
+ #
11
+ # require 'json/add/range'
12
+ # Range.json_create({"a"=>[1, 4]}) # => 1..4
13
+ # Range.json_create({"a"=>[1, 4, true]}) # => 1...4
14
+ # Range.json_create({"a" => ['a', 'd']}) # => "a".."d"
15
+ #
10
16
  def self.json_create(object)
11
17
  new(*object['a'])
12
18
  end
13
19
 
14
- # Returns a hash, that will be turned into a JSON object and represent this
15
- # object.
20
+ # Returns a 2-element hash representing +self+:
21
+ #
22
+ # require 'json/add/range'
23
+ # (1..4).as_json # => {"json_class"=>"Range", "a"=>[1, 4, false]}
24
+ # (1...4).as_json # => {"json_class"=>"Range", "a"=>[1, 4, true]}
25
+ # ('a'..'d').as_json # => {"json_class"=>"Range", "a"=>["a", "d", false]}
26
+ #
16
27
  def as_json(*)
17
28
  {
18
29
  JSON.create_id => self.class.name,
@@ -20,9 +31,13 @@ class Range
20
31
  }
21
32
  end
22
33
 
23
- # Stores class name (Range) with JSON array of arguments <tt>a</tt> which
24
- # include <tt>first</tt> (integer), <tt>last</tt> (integer), and
25
- # <tt>exclude_end?</tt> (boolean) as JSON string.
34
+ # Returns a JSON string representing +self+:
35
+ #
36
+ # require 'json/add/range'
37
+ # (1..4).to_json # => "{\"json_class\":\"Range\",\"a\":[1,4,false]}"
38
+ # (1...4).to_json # => "{\"json_class\":\"Range\",\"a\":[1,4,true]}"
39
+ # ('a'..'d').to_json # => "{\"json_class\":\"Range\",\"a\":[\"a\",\"d\",false]}"
40
+ #
26
41
  def to_json(*args)
27
42
  as_json.to_json(*args)
28
43
  end
data/lib/json/common.rb CHANGED
@@ -3,6 +3,9 @@ require 'json/version'
3
3
  require 'json/generic_object'
4
4
 
5
5
  module JSON
6
+ NOT_SET = Object.new.freeze
7
+ private_constant :NOT_SET
8
+
6
9
  class << self
7
10
  # :call-seq:
8
11
  # JSON[object] -> new_array or new_string
@@ -295,19 +298,9 @@ module JSON
295
298
  #
296
299
  def generate(obj, opts = nil)
297
300
  if State === opts
298
- state, opts = opts, nil
301
+ state = opts
299
302
  else
300
- state = State.new
301
- end
302
- if opts
303
- if opts.respond_to? :to_hash
304
- opts = opts.to_hash
305
- elsif opts.respond_to? :to_h
306
- opts = opts.to_h
307
- else
308
- raise TypeError, "can't convert #{opts.class} into Hash"
309
- end
310
- state = state.configure(opts)
303
+ state = State.new(opts)
311
304
  end
312
305
  state.generate(obj)
313
306
  end
@@ -334,19 +327,9 @@ module JSON
334
327
  # JSON.fast_generate(a)
335
328
  def fast_generate(obj, opts = nil)
336
329
  if State === opts
337
- state, opts = opts, nil
330
+ state = opts
338
331
  else
339
- state = JSON.create_fast_state
340
- end
341
- if opts
342
- if opts.respond_to? :to_hash
343
- opts = opts.to_hash
344
- elsif opts.respond_to? :to_h
345
- opts = opts.to_h
346
- else
347
- raise TypeError, "can't convert #{opts.class} into Hash"
348
- end
349
- state.configure(opts)
332
+ state = JSON.create_fast_state.configure(opts)
350
333
  end
351
334
  state.generate(obj)
352
335
  end
@@ -592,13 +575,13 @@ module JSON
592
575
  # Sets or returns the default options for the JSON.dump method.
593
576
  # Initially:
594
577
  # opts = JSON.dump_default_options
595
- # opts # => {:max_nesting=>false, :allow_nan=>true, :escape_slash=>false}
578
+ # opts # => {:max_nesting=>false, :allow_nan=>true, :script_safe=>false}
596
579
  attr_accessor :dump_default_options
597
580
  end
598
581
  self.dump_default_options = {
599
582
  :max_nesting => false,
600
583
  :allow_nan => true,
601
- :escape_slash => false,
584
+ :script_safe => false,
602
585
  }
603
586
 
604
587
  # :call-seq:
@@ -628,7 +611,7 @@ module JSON
628
611
  # puts File.read(path)
629
612
  # Output:
630
613
  # {"foo":[0,1],"bar":{"baz":2,"bat":3},"bam":"bad"}
631
- def dump(obj, anIO = nil, limit = nil)
614
+ def dump(obj, anIO = nil, limit = nil, strict: NOT_SET)
632
615
  if anIO and limit.nil?
633
616
  anIO = anIO.to_io if anIO.respond_to?(:to_io)
634
617
  unless anIO.respond_to?(:write)
@@ -638,6 +621,7 @@ module JSON
638
621
  end
639
622
  opts = JSON.dump_default_options
640
623
  opts = opts.merge(:max_nesting => limit) if limit
624
+ opts[:strict] = strict if NOT_SET != strict
641
625
  result = generate(obj, opts)
642
626
  if anIO
643
627
  anIO.write result
@@ -37,25 +37,34 @@ module JSON
37
37
  '\\' => '\\\\',
38
38
  } # :nodoc:
39
39
 
40
- ESCAPE_SLASH_MAP = MAP.merge(
40
+ ESCAPE_PATTERN = /[\/"\\\x0-\x1f]/n # :nodoc:
41
+
42
+ SCRIPT_SAFE_MAP = MAP.merge(
41
43
  '/' => '\\/',
44
+ "\u2028".b => '\u2028',
45
+ "\u2029".b => '\u2029',
42
46
  )
43
47
 
48
+ SCRIPT_SAFE_ESCAPE_PATTERN = Regexp.union(ESCAPE_PATTERN, "\u2028".b, "\u2029".b)
49
+
44
50
  # Convert a UTF8 encoded Ruby string _string_ to a JSON string, encoded with
45
51
  # UTF16 big endian characters as \u????, and return it.
46
- def utf8_to_json(string, escape_slash = false) # :nodoc:
52
+ def utf8_to_json(string, script_safe = false) # :nodoc:
47
53
  string = string.dup
48
54
  string.force_encoding(::Encoding::ASCII_8BIT)
49
- map = escape_slash ? ESCAPE_SLASH_MAP : MAP
50
- string.gsub!(/[\/"\\\x0-\x1f]/) { map[$&] || $& }
55
+ if script_safe
56
+ string.gsub!(SCRIPT_SAFE_ESCAPE_PATTERN) { SCRIPT_SAFE_MAP[$&] || $& }
57
+ else
58
+ string.gsub!(ESCAPE_PATTERN) { MAP[$&] || $& }
59
+ end
51
60
  string.force_encoding(::Encoding::UTF_8)
52
61
  string
53
62
  end
54
63
 
55
- def utf8_to_json_ascii(string, escape_slash = false) # :nodoc:
64
+ def utf8_to_json_ascii(string, script_safe = false) # :nodoc:
56
65
  string = string.dup
57
66
  string.force_encoding(::Encoding::ASCII_8BIT)
58
- map = escape_slash ? ESCAPE_SLASH_MAP : MAP
67
+ map = script_safe ? SCRIPT_SAFE_MAP : MAP
59
68
  string.gsub!(/[\/"\\\x0-\x1f]/n) { map[$&] || $& }
60
69
  string.gsub!(/(
61
70
  (?:
@@ -115,7 +124,8 @@ module JSON
115
124
  # * *space_before*: a string that is put before a : pair delimiter (default: ''),
116
125
  # * *object_nl*: a string that is put at the end of a JSON object (default: ''),
117
126
  # * *array_nl*: a string that is put at the end of a JSON array (default: ''),
118
- # * *escape_slash*: true if forward slash (/) should be escaped (default: false)
127
+ # * *script_safe*: true if U+2028, U+2029 and forward slash (/) should be escaped
128
+ # as to make the JSON object safe to interpolate in a script tag (default: false).
119
129
  # * *check_circular*: is deprecated now, use the :max_nesting option instead,
120
130
  # * *max_nesting*: sets the maximum level of data structure nesting in
121
131
  # the generated JSON, max_nesting = 0 if no maximum should be checked.
@@ -130,7 +140,8 @@ module JSON
130
140
  @array_nl = ''
131
141
  @allow_nan = false
132
142
  @ascii_only = false
133
- @escape_slash = false
143
+ @script_safe = false
144
+ @strict = false
134
145
  @buffer_initial_length = 1024
135
146
  configure opts
136
147
  end
@@ -158,7 +169,11 @@ module JSON
158
169
 
159
170
  # If this attribute is set to true, forward slashes will be escaped in
160
171
  # all json strings.
161
- attr_accessor :escape_slash
172
+ attr_accessor :script_safe
173
+
174
+ # If this attribute is set to true, attempting to serialize types not
175
+ # supported by the JSON spec will raise a JSON::GeneratorError
176
+ attr_accessor :strict
162
177
 
163
178
  # :stopdoc:
164
179
  attr_reader :buffer_initial_length
@@ -200,8 +215,13 @@ module JSON
200
215
  end
201
216
 
202
217
  # Returns true, if forward slashes are escaped. Otherwise returns false.
203
- def escape_slash?
204
- @escape_slash
218
+ def script_safe?
219
+ @script_safe
220
+ end
221
+
222
+ # Returns true, if forward slashes are escaped. Otherwise returns false.
223
+ def strict?
224
+ @strict
205
225
  end
206
226
 
207
227
  # Configure this State instance with the Hash _opts_, and return
@@ -214,7 +234,7 @@ module JSON
214
234
  else
215
235
  raise TypeError, "can't convert #{opts.class} into Hash"
216
236
  end
217
- for key, value in opts
237
+ opts.each do |key, value|
218
238
  instance_variable_set "@#{key}", value
219
239
  end
220
240
  @indent = opts[:indent] if opts.key?(:indent)
@@ -226,7 +246,16 @@ module JSON
226
246
  @ascii_only = opts[:ascii_only] if opts.key?(:ascii_only)
227
247
  @depth = opts[:depth] || 0
228
248
  @buffer_initial_length ||= opts[:buffer_initial_length]
229
- @escape_slash = !!opts[:escape_slash] if opts.key?(:escape_slash)
249
+
250
+ @script_safe = if opts.key?(:script_safe)
251
+ !!opts[:script_safe]
252
+ elsif opts.key?(:escape_slash)
253
+ !!opts[:escape_slash]
254
+ else
255
+ false
256
+ end
257
+
258
+ @strict = !!opts[:strict] if opts.key?(:strict)
230
259
 
231
260
  if !opts.key?(:max_nesting) # defaults to 100
232
261
  @max_nesting = 100
@@ -243,7 +272,7 @@ module JSON
243
272
  # passed to the configure method.
244
273
  def to_h
245
274
  result = {}
246
- for iv in instance_variables
275
+ instance_variables.each do |iv|
247
276
  iv = iv.to_s[1..-1]
248
277
  result[iv.to_sym] = self[iv]
249
278
  end
@@ -287,7 +316,13 @@ module JSON
287
316
  # Converts this object to a string (calling #to_s), converts
288
317
  # it to a JSON string, and returns the result. This is a fallback, if no
289
318
  # special method #to_json was defined for some object.
290
- def to_json(*) to_s.to_json end
319
+ def to_json(generator_state)
320
+ if generator_state.strict?
321
+ raise GeneratorError, "#{self.class} not allowed in JSON"
322
+ else
323
+ to_s.to_json
324
+ end
325
+ end
291
326
  end
292
327
 
293
328
  module Hash
@@ -310,21 +345,18 @@ module JSON
310
345
  end
311
346
 
312
347
  def json_transform(state)
313
- delim = ','
314
- delim << state.object_nl
315
- result = '{'
316
- result << state.object_nl
348
+ delim = ",#{state.object_nl}"
349
+ result = "{#{state.object_nl}"
317
350
  depth = state.depth += 1
318
351
  first = true
319
352
  indent = !state.object_nl.empty?
320
- each { |key,value|
353
+ each { |key, value|
321
354
  result << delim unless first
322
355
  result << state.indent * depth if indent
323
- result << key.to_s.to_json(state)
324
- result << state.space_before
325
- result << ':'
326
- result << state.space
327
- if value.respond_to?(:to_json)
356
+ result = "#{result}#{key.to_s.to_json(state)}#{state.space_before}:#{state.space}"
357
+ if state.strict?
358
+ raise GeneratorError, "#{value.class} not allowed in JSON"
359
+ elsif value.respond_to?(:to_json)
328
360
  result << value.to_json(state)
329
361
  else
330
362
  result << %{"#{String(value)}"}
@@ -365,7 +397,9 @@ module JSON
365
397
  each { |value|
366
398
  result << delim unless first
367
399
  result << state.indent * depth if indent
368
- if value.respond_to?(:to_json)
400
+ if state.strict?
401
+ raise GeneratorError, "#{value.class} not allowed in JSON"
402
+ elsif value.respond_to?(:to_json)
369
403
  result << value.to_json(state)
370
404
  else
371
405
  result << %{"#{String(value)}"}
@@ -419,9 +453,9 @@ module JSON
419
453
  string = encode(::Encoding::UTF_8)
420
454
  end
421
455
  if state.ascii_only?
422
- '"' << JSON.utf8_to_json_ascii(string, state.escape_slash) << '"'
456
+ '"' << JSON.utf8_to_json_ascii(string, state.script_safe) << '"'
423
457
  else
424
- '"' << JSON.utf8_to_json(string, state.escape_slash) << '"'
458
+ '"' << JSON.utf8_to_json(string, state.script_safe) << '"'
425
459
  end
426
460
  end
427
461
 
data/lib/json/version.rb CHANGED
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: false
2
2
  module JSON
3
3
  # JSON version
4
- VERSION = '2.6.3'
4
+ VERSION = '2.7.0'
5
5
  VERSION_ARRAY = VERSION.split(/\./).map { |x| x.to_i } # :nodoc:
6
6
  VERSION_MAJOR = VERSION_ARRAY[0] # :nodoc:
7
7
  VERSION_MINOR = VERSION_ARRAY[1] # :nodoc:
data/lib/json.rb CHANGED
@@ -285,6 +285,15 @@ require 'json/common'
285
285
  # # Raises JSON::NestingError (nesting of 2 is too deep):
286
286
  # JSON.generate(obj, max_nesting: 2)
287
287
  #
288
+ # ====== Escaping Options
289
+ #
290
+ # Options +script_safe+ (boolean) specifies wether <tt>'\u2028'</tt>, <tt>'\u2029'</tt>
291
+ # and <tt>'/'</tt> should be escaped as to make the JSON object safe to interpolate in script
292
+ # tags.
293
+ #
294
+ # Options +ascii_only+ (boolean) specifies wether all characters outside the ASCII range
295
+ # should be escaped.
296
+ #
288
297
  # ====== Output Options
289
298
  #
290
299
  # The default formatting options generate the most compact
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: json_pure
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.6.3
4
+ version: 2.7.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Florian Frank
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-12-05 00:00:00.000000000 Z
11
+ date: 2023-12-01 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: This is a JSON implementation in pure Ruby.
14
14
  email: flori@ping.de
@@ -44,14 +44,14 @@ files:
44
44
  - lib/json/pure/generator.rb
45
45
  - lib/json/pure/parser.rb
46
46
  - lib/json/version.rb
47
- homepage: http://flori.github.com/json
47
+ homepage: https://flori.github.io/json
48
48
  licenses:
49
49
  - Ruby
50
50
  metadata:
51
51
  bug_tracker_uri: https://github.com/flori/json/issues
52
52
  changelog_uri: https://github.com/flori/json/blob/master/CHANGES.md
53
- documentation_uri: http://flori.github.io/json/doc/index.html
54
- homepage_uri: http://flori.github.io/json/
53
+ documentation_uri: https://flori.github.io/json/doc/index.html
54
+ homepage_uri: https://flori.github.io/json
55
55
  source_code_uri: https://github.com/flori/json
56
56
  wiki_uri: https://github.com/flori/json/wiki
57
57
  post_install_message:
@@ -73,7 +73,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
73
73
  - !ruby/object:Gem::Version
74
74
  version: '0'
75
75
  requirements: []
76
- rubygems_version: 3.4.0.dev
76
+ rubygems_version: 3.5.0.dev
77
77
  signing_key:
78
78
  specification_version: 4
79
79
  summary: JSON Implementation for Ruby