json_translate 2.0.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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 17ce402d321bc3d0032b0a3b746c7ba961e4136e
4
+ data.tar.gz: 17f938bd688e2c2e623362f8044233a2bcf2d14d
5
+ SHA512:
6
+ metadata.gz: 68aca958c304187679d5ac66b90b15d3eaa0d0a3096030b3d2054b19978e52bd239f8f52186e085c59564430e9ca4ffd805e03c650118a6aaa33ab08d1bc5a70
7
+ data.tar.gz: 35075fb936242cc9ced7917db628b7266cd5ab8d853b2268c6ac912bac3e2374dbcddd3edd074b942ed02bd0a3916b49d35e83fa86b15efaa49e6105ee309471
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2012 Rob Worley
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,138 @@
1
+ # JSON Translate
2
+
3
+ Rails I18n library for ActiveRecord model/data translation using PostgreSQL's
4
+ JSONB datatype. It provides an interface inspired by
5
+ [Globalize3](https://github.com/svenfuchs/globalize3) but removes the need to
6
+ maintain separate translation tables.
7
+
8
+ [![Build Status](https://api.travis-ci.org/cfabianski/json_translate.png)](https://travis-ci.org/cfabianski/json_translate)
9
+ [![License](http://img.shields.io/badge/license-mit-brightgreen.svg)](COPYRIGHT)
10
+ [![Code Climate](https://codeclimate.com/github/cfabianski/json_translate.png)](https://codeclimate.com/github/cfabianski/json_translate)
11
+
12
+ ## Requirements
13
+
14
+ * ActiveRecord > 5.0.0 (4+ for JRuby)
15
+ * I18n
16
+
17
+ ## Installation
18
+
19
+ gem install json_translate
20
+
21
+ When using bundler, put it in your Gemfile:
22
+
23
+ ```ruby
24
+ source 'https://rubygems.org'
25
+
26
+ gem 'activerecord'
27
+ gem 'pg', :platform => :ruby
28
+ gem 'activerecord-jdbcpostgresql-adapter', :platform => :jruby
29
+ gem 'json_translate'
30
+ ```
31
+
32
+ ## Model translations
33
+
34
+ Model translations allow you to translate your models' attribute values. E.g.
35
+
36
+ ```ruby
37
+ class Post < ActiveRecord::Base
38
+ translates :title, :body
39
+ end
40
+ ```
41
+
42
+ Allows you to translate the attributes :title and :body per locale:
43
+
44
+ ```ruby
45
+ I18n.locale = :en
46
+ post.title # => This database rocks!
47
+
48
+ I18n.locale = :he
49
+ post.title # => אתר זה טוב
50
+ ```
51
+
52
+ You also have locale-specific convenience methods from [easy_globalize3_accessors](https://github.com/paneq/easy_globalize3_accessors):
53
+
54
+ ```ruby
55
+ I18n.locale = :en
56
+ post.title # => This database rocks!
57
+ post.title_he # => אתר זה טוב
58
+ ```
59
+
60
+ To find records using translations without constructing JSONB queries by hand:
61
+
62
+ ```ruby
63
+ Post.with_title_translation("This database rocks!") # => #<ActiveRecord::Relation ...>
64
+ Post.with_title_translation("אתר זה טוב", :he) # => #<ActiveRecord::Relation ...>
65
+ ```
66
+
67
+ In order to make this work, you'll need to define an JSONB column for each of
68
+ your translated attributes, using the suffix "_translations":
69
+
70
+ ```ruby
71
+ class CreatePosts < ActiveRecord::Migration
72
+ def up
73
+ create_table :posts do |t|
74
+ t.column :title_translations, 'jsonb'
75
+ t.column :body_translations, 'jsonb'
76
+ t.timestamps
77
+ end
78
+ end
79
+ def down
80
+ drop_table :posts
81
+ end
82
+ end
83
+ ```
84
+
85
+ ## I18n fallbacks for missing translations
86
+
87
+ It is possible to enable fallbacks for missing translations. It will depend
88
+ on the configuration setting you have set for I18n translations in your Rails
89
+ config.
90
+
91
+ You can enable them by adding the next line to `config/application.rb` (or
92
+ only `config/environments/production.rb` if you only want them in production)
93
+
94
+ ```ruby
95
+ config.i18n.fallbacks = true
96
+ ```
97
+
98
+ Sven Fuchs wrote a [detailed explanation of the fallback
99
+ mechanism](https://github.com/svenfuchs/i18n/wiki/Fallbacks).
100
+
101
+ ## Temporarily disable fallbacks
102
+
103
+ If you've enabled fallbacks for missing translations, you probably want to disable
104
+ them in the admin interface to display which translations the user still has to
105
+ fill in.
106
+
107
+ From:
108
+
109
+ ```ruby
110
+ I18n.locale = :en
111
+ post.title # => This database rocks!
112
+ post.title_nl # => This database rocks!
113
+ ```
114
+
115
+ To:
116
+
117
+ ```ruby
118
+ I18n.locale = :en
119
+ post.title # => This database rocks!
120
+ post.disable_fallback
121
+ post.title_nl # => nil
122
+ ```
123
+
124
+ You can also call your code into a block that temporarily disable or enable fallbacks.
125
+
126
+ ```ruby
127
+ I18n.locale = :en
128
+ post.title_nl # => This database rocks!
129
+
130
+ post.disable_fallback do
131
+ post.title_nl # => nil
132
+ end
133
+
134
+ post.disable_fallback
135
+ post.enable_fallback do
136
+ post.title_nl # => This database rocks!
137
+ end
138
+ ```
@@ -0,0 +1,137 @@
1
+ module JSONTranslate
2
+ module Translates
3
+ SUFFIX = "_translations"
4
+
5
+ def translates(*attrs)
6
+ include InstanceMethods
7
+
8
+ class_attribute :translated_attrs
9
+ alias_attribute :translated_attribute_names, :translated_attrs # Improve compatibility with the gem globalize
10
+ self.translated_attrs = attrs
11
+
12
+ attrs.each do |attr_name|
13
+ define_method attr_name do
14
+ read_json_translation(attr_name)
15
+ end
16
+
17
+ define_method "#{attr_name}=" do |value|
18
+ write_json_translation(attr_name, value)
19
+ end
20
+
21
+ define_singleton_method "with_#{attr_name}_translation" do |value, locale = I18n.locale|
22
+ quoted_translation_store = connection.quote_column_name("#{attr_name}#{SUFFIX}")
23
+ translation_hash = { "#{locale}" => value }
24
+ where("#{quoted_translation_store} @> :translation::jsonb", translation: translation_hash.to_json)
25
+ end
26
+ end
27
+
28
+ alias_method_chain :respond_to?, :translates
29
+ alias_method_chain :method_missing, :translates
30
+ end
31
+
32
+ # Improve compatibility with the gem globalize
33
+ def translates?
34
+ included_modules.include?(InstanceMethods)
35
+ end
36
+
37
+ module InstanceMethods
38
+ def disable_fallback
39
+ toggle_fallback(false)
40
+ end
41
+
42
+ def enable_fallback
43
+ toggle_fallback(true)
44
+ end
45
+
46
+ protected
47
+
48
+ def json_translate_fallback_locales(locale)
49
+ return if @enabled_fallback == false || !I18n.respond_to?(:fallbacks)
50
+ I18n.fallbacks[locale]
51
+ end
52
+
53
+ def read_json_translation(attr_name, locale = I18n.locale)
54
+ translations = send("#{attr_name}#{SUFFIX}") || {}
55
+ translation = translations[locale.to_s]
56
+
57
+ if fallback_locales = json_translate_fallback_locales(locale)
58
+ fallback_locales.each do |fallback_locale|
59
+ t = translations[fallback_locale.to_s]
60
+ if t && !t.empty? # differs from blank?
61
+ translation = t
62
+ break
63
+ end
64
+ end
65
+ end
66
+
67
+ translation
68
+ end
69
+
70
+ def write_json_translation(attr_name, value, locale = I18n.locale)
71
+ translation_store = "#{attr_name}#{SUFFIX}"
72
+ translations = send(translation_store) || {}
73
+ send("#{translation_store}_will_change!") unless translations[locale.to_s] == value
74
+ translations[locale.to_s] = value
75
+ send("#{translation_store}=", translations)
76
+ value
77
+ end
78
+
79
+ def respond_to_with_translates?(symbol, include_all = false)
80
+ return true if parse_translated_attribute_accessor(symbol)
81
+ respond_to_without_translates?(symbol, include_all)
82
+ end
83
+
84
+ def method_missing_with_translates(method_name, *args)
85
+ translated_attr_name, locale, assigning = parse_translated_attribute_accessor(method_name)
86
+
87
+ return method_missing_without_translates(method_name, *args) unless translated_attr_name
88
+
89
+ if assigning
90
+ write_json_translation(translated_attr_name, args.first, locale)
91
+ else
92
+ read_json_translation(translated_attr_name, locale)
93
+ end
94
+ end
95
+
96
+ # Internal: Parse a translated convenience accessor name.
97
+ #
98
+ # method_name - The accessor name.
99
+ #
100
+ # Examples
101
+ #
102
+ # parse_translated_attribute_accessor("title_en=")
103
+ # # => [:title, :en, true]
104
+ #
105
+ # parse_translated_attribute_accessor("title_fr")
106
+ # # => [:title, :fr, false]
107
+ #
108
+ # Returns the attribute name Symbol, locale Symbol, and a Boolean
109
+ # indicating whether or not the caller is attempting to assign a value.
110
+ def parse_translated_attribute_accessor(method_name)
111
+ return unless method_name =~ /\A([a-z_]+)_([a-z]{2})(=?)\z/
112
+
113
+ translated_attr_name = $1.to_sym
114
+ return unless translated_attrs.include?(translated_attr_name)
115
+
116
+ locale = $2.to_sym
117
+ assigning = $3.present?
118
+
119
+ [translated_attr_name, locale, assigning]
120
+ end
121
+
122
+ def toggle_fallback(enabled)
123
+ if block_given?
124
+ old_value = @enabled_fallback
125
+ begin
126
+ @enabled_fallback = enabled
127
+ yield
128
+ ensure
129
+ @enabled_fallback = old_value
130
+ end
131
+ else
132
+ @enabled_fallback = enabled
133
+ end
134
+ end
135
+ end
136
+ end
137
+ end
@@ -0,0 +1,3 @@
1
+ module JSONTranslate
2
+ VERSION = "2.0.0"
3
+ end
@@ -0,0 +1,4 @@
1
+ require 'active_record'
2
+ require 'json_translate/translates'
3
+
4
+ ActiveRecord::Base.extend(JSONTranslate::Translates)
data/test/database.yml ADDED
@@ -0,0 +1,6 @@
1
+ test:
2
+ adapter: postgresql
3
+ host: localhost
4
+ database: json_translate_test
5
+ username: postgres
6
+ min_messages: warning
@@ -0,0 +1,5 @@
1
+ source 'https://rubygems.org'
2
+ gemspec :path => './../..'
3
+
4
+ gem 'pg'
5
+ gem 'activerecord', '~> 5.0.0'
@@ -0,0 +1,59 @@
1
+ require 'minitest/autorun'
2
+ require 'json_translate'
3
+
4
+ require 'database_cleaner'
5
+ DatabaseCleaner.strategy = :transaction
6
+
7
+ MiniTest::Test = MiniTest::Unit::TestCase unless MiniTest.const_defined?(:Test) # Rails 4.0.x
8
+
9
+ class Post < ActiveRecord::Base
10
+ translates :title
11
+ end
12
+
13
+ class JSONTranslate::Test < Minitest::Test
14
+ class << self
15
+ def prepare_database
16
+ create_database
17
+ create_table
18
+ end
19
+
20
+ private
21
+
22
+ def db_config
23
+ @db_config ||= begin
24
+ filepath = File.join('test', 'database.yml')
25
+ YAML.load_file(filepath)['test']
26
+ end
27
+ end
28
+
29
+ def establish_connection(config)
30
+ ActiveRecord::Base.establish_connection(config)
31
+ ActiveRecord::Base.connection
32
+ end
33
+
34
+ def create_database
35
+ system_config = db_config.merge('database' => 'postgres', 'schema_search_path' => 'public')
36
+ connection = establish_connection(system_config)
37
+ connection.create_database(db_config['database']) rescue nil
38
+ end
39
+
40
+ def create_table
41
+ connection = establish_connection(db_config)
42
+ connection.create_table(:posts, :force => true) do |t|
43
+ t.column :title_translations, 'jsonb'
44
+ end
45
+ end
46
+ end
47
+
48
+ prepare_database
49
+
50
+ def setup
51
+ I18n.available_locales = ['en', 'en-US', 'fr']
52
+ I18n.config.enforce_available_locales = true
53
+ DatabaseCleaner.start
54
+ end
55
+
56
+ def teardown
57
+ DatabaseCleaner.clean
58
+ end
59
+ end
@@ -0,0 +1,148 @@
1
+ # -*- encoding : utf-8 -*-
2
+ require 'test_helper'
3
+
4
+ class TranslatesTest < JSONTranslate::Test
5
+ def test_assigns_in_current_locale
6
+ I18n.with_locale(:en) do
7
+ p = Post.new(:title => "English Title")
8
+ assert_equal("English Title", p.title_translations['en'])
9
+ end
10
+ end
11
+
12
+ def test_retrieves_in_current_locale
13
+ p = Post.new(:title_translations => { "en" => "English Title", "fr" => "Titre français" })
14
+ I18n.with_locale(:fr) do
15
+ assert_equal("Titre français", p.title)
16
+ end
17
+ end
18
+
19
+ def test_retrieves_in_current_locale_with_fallbacks
20
+ I18n::Backend::Simple.include(I18n::Backend::Fallbacks)
21
+ I18n.default_locale = :"en-US"
22
+
23
+ p = Post.new(:title_translations => {"en" => "English Title"})
24
+ I18n.with_locale(:fr) do
25
+ assert_equal("English Title", p.title)
26
+ end
27
+ end
28
+
29
+ def test_assigns_in_specified_locale
30
+ I18n.with_locale(:en) do
31
+ p = Post.new(:title_translations => { "en" => "English Title" })
32
+ p.title_fr = "Titre français"
33
+ assert_equal("Titre français", p.title_translations["fr"])
34
+ end
35
+ end
36
+
37
+ def test_persists_changes_in_specified_locale
38
+ I18n.with_locale(:en) do
39
+ p = Post.create!(:title_translations => { "en" => "Original Text" })
40
+ p.title_en = "Updated Text"
41
+ p.save!
42
+ assert_equal("Updated Text", Post.last.title_en)
43
+ end
44
+ end
45
+
46
+ def test_retrieves_in_specified_locale
47
+ I18n.with_locale(:en) do
48
+ p = Post.new(:title_translations => { "en" => "English Title", "fr" => "Titre français" })
49
+ assert_equal("Titre français", p.title_fr)
50
+ end
51
+ end
52
+
53
+ def test_retrieves_in_specified_locale_with_fallbacks
54
+ I18n::Backend::Simple.include(I18n::Backend::Fallbacks)
55
+ I18n.default_locale = :"en-US"
56
+
57
+ p = Post.new(:title_translations => { "en" => "English Title" })
58
+ I18n.with_locale(:fr) do
59
+ assert_equal("English Title", p.title_fr)
60
+ end
61
+ end
62
+
63
+ def test_fallback_from_empty_string
64
+ I18n::Backend::Simple.include(I18n::Backend::Fallbacks)
65
+ I18n.default_locale = :"en-US"
66
+
67
+ p = Post.new(:title_translations => { "en" => "English Title", "fr" => "" })
68
+ I18n.with_locale(:fr) do
69
+ assert_equal("English Title", p.title_fr)
70
+ end
71
+ end
72
+
73
+ def test_retrieves_in_specified_locale_with_fallback_disabled
74
+ I18n::Backend::Simple.include(I18n::Backend::Fallbacks)
75
+ I18n.default_locale = :"en-US"
76
+
77
+ p = Post.new(:title_translations => { "en" => "English Title" })
78
+ p.disable_fallback
79
+ I18n.with_locale(:fr) do
80
+ assert_equal(nil, p.title_fr)
81
+ end
82
+ end
83
+
84
+ def test_retrieves_in_specified_locale_with_fallback_disabled_using_a_block
85
+ I18n::Backend::Simple.include(I18n::Backend::Fallbacks)
86
+ I18n.default_locale = :"en-US"
87
+
88
+ p = Post.new(:title_translations => { "en" => "English Title" })
89
+ p.enable_fallback
90
+
91
+ assert_equal("English Title", p.title_fr)
92
+ p.disable_fallback { assert_nil p.title_fr }
93
+ end
94
+
95
+ def test_retrieves_in_specified_locale_with_fallback_reenabled
96
+ I18n::Backend::Simple.include(I18n::Backend::Fallbacks)
97
+ I18n.default_locale = :"en-US"
98
+
99
+ p = Post.new(:title_translations => { "en" => "English Title" })
100
+ p.disable_fallback
101
+ p.enable_fallback
102
+ I18n.with_locale(:fr) do
103
+ assert_equal("English Title", p.title_fr)
104
+ end
105
+ end
106
+
107
+ def test_retrieves_in_specified_locale_with_fallback_reenabled_using_a_block
108
+ I18n::Backend::Simple.include(I18n::Backend::Fallbacks)
109
+ I18n.default_locale = :"en-US"
110
+
111
+ p = Post.new(:title_translations => { "en" => "English Title" })
112
+ p.disable_fallback
113
+
114
+ assert_nil(p.title_fr)
115
+ p.enable_fallback { assert_equal("English Title", p.title_fr) }
116
+ end
117
+
118
+ def test_method_missing_delegates
119
+ assert_raises(NoMethodError) { Post.new.nonexistant_method }
120
+ end
121
+
122
+ def test_method_missing_delegates_non_translated_attributes
123
+ assert_raises(NoMethodError) { Post.new.other_fr }
124
+ end
125
+
126
+ def test_persists_translations_assigned_as_hash
127
+ p = Post.create!(:title_translations => { "en" => "English Title", "fr" => "Titre français" })
128
+ p.reload
129
+ assert_equal({"en" => "English Title", "fr" => "Titre français"}, p.title_translations)
130
+ end
131
+
132
+ def test_persists_translations_assigned_to_localized_accessors
133
+ p = Post.create!(:title_en => "English Title", :title_fr => "Titre français")
134
+ p.reload
135
+ assert_equal({"en" => "English Title", "fr" => "Titre français"}, p.title_translations)
136
+ end
137
+
138
+ def test_with_translation_relation
139
+ p = Post.create!(:title_translations => { "en" => "Alice in Wonderland", "fr" => "Alice au pays des merveilles" })
140
+ I18n.with_locale(:en) do
141
+ assert_equal p.title_en, Post.with_title_translation("Alice in Wonderland").first.try(:title)
142
+ end
143
+ end
144
+
145
+ def test_class_method_translates?
146
+ assert_equal true, Post.translates?
147
+ end
148
+ end
metadata ADDED
@@ -0,0 +1,116 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: json_translate
3
+ version: !ruby/object:Gem::Version
4
+ version: 2.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Rob Worley
8
+ - Cédric Fabianski
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2016-11-22 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: activerecord
16
+ requirement: !ruby/object:Gem::Requirement
17
+ requirements:
18
+ - - ">="
19
+ - !ruby/object:Gem::Version
20
+ version: 3.1.0
21
+ type: :runtime
22
+ prerelease: false
23
+ version_requirements: !ruby/object:Gem::Requirement
24
+ requirements:
25
+ - - ">="
26
+ - !ruby/object:Gem::Version
27
+ version: 3.1.0
28
+ - !ruby/object:Gem::Dependency
29
+ name: rake
30
+ requirement: !ruby/object:Gem::Requirement
31
+ requirements:
32
+ - - ">="
33
+ - !ruby/object:Gem::Version
34
+ version: '0'
35
+ type: :development
36
+ prerelease: false
37
+ version_requirements: !ruby/object:Gem::Requirement
38
+ requirements:
39
+ - - ">="
40
+ - !ruby/object:Gem::Version
41
+ version: '0'
42
+ - !ruby/object:Gem::Dependency
43
+ name: minitest
44
+ requirement: !ruby/object:Gem::Requirement
45
+ requirements:
46
+ - - ">="
47
+ - !ruby/object:Gem::Version
48
+ version: '4.0'
49
+ type: :development
50
+ prerelease: false
51
+ version_requirements: !ruby/object:Gem::Requirement
52
+ requirements:
53
+ - - ">="
54
+ - !ruby/object:Gem::Version
55
+ version: '4.0'
56
+ - !ruby/object:Gem::Dependency
57
+ name: database_cleaner
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
+ description: Rails I18n library for ActiveRecord model/data translation using PostgreSQL's
71
+ JSONB datatype. Translations are stored directly in the model table rather than
72
+ shadow tables.
73
+ email: cfabianski@me.com
74
+ executables: []
75
+ extensions: []
76
+ extra_rdoc_files: []
77
+ files:
78
+ - MIT-LICENSE
79
+ - README.md
80
+ - lib/json_translate.rb
81
+ - lib/json_translate/translates.rb
82
+ - lib/json_translate/version.rb
83
+ - test/database.yml
84
+ - test/gemfiles/Gemfile.rails-5.0.x
85
+ - test/test_helper.rb
86
+ - test/translates_test.rb
87
+ homepage: https://github.com/cfabiaski/json_translate
88
+ licenses:
89
+ - MIT
90
+ metadata: {}
91
+ post_install_message:
92
+ rdoc_options: []
93
+ require_paths:
94
+ - lib
95
+ required_ruby_version: !ruby/object:Gem::Requirement
96
+ requirements:
97
+ - - ">="
98
+ - !ruby/object:Gem::Version
99
+ version: '0'
100
+ required_rubygems_version: !ruby/object:Gem::Requirement
101
+ requirements:
102
+ - - ">="
103
+ - !ruby/object:Gem::Version
104
+ version: '0'
105
+ requirements: []
106
+ rubyforge_project:
107
+ rubygems_version: 2.6.6
108
+ signing_key:
109
+ specification_version: 4
110
+ summary: Rails I18n library for ActiveRecord model/data translation using PostgreSQL's
111
+ JSONB datatype.
112
+ test_files:
113
+ - test/database.yml
114
+ - test/gemfiles/Gemfile.rails-5.0.x
115
+ - test/test_helper.rb
116
+ - test/translates_test.rb