copy_tuner_client 2.1.2 → 2.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.
Files changed (60) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +5 -0
  3. data/.rubocop.yml +46 -1
  4. data/CLAUDE.md +1 -0
  5. data/Gemfile +1 -0
  6. data/copy_tuner_client.gemspec +3 -3
  7. data/gemfiles/8.0.gemfile +3 -3
  8. data/gemfiles/8.1.gemfile +3 -3
  9. data/gemfiles/main.gemfile +3 -3
  10. data/lib/copy_tuner_client/cache.rb +34 -27
  11. data/lib/copy_tuner_client/client.rb +35 -23
  12. data/lib/copy_tuner_client/configuration.rb +62 -40
  13. data/lib/copy_tuner_client/copyray/rewriter.rb +1 -1
  14. data/lib/copy_tuner_client/copyray.rb +1 -0
  15. data/lib/copy_tuner_client/copyray_middleware.rb +31 -24
  16. data/lib/copy_tuner_client/dotted_hash.rb +7 -7
  17. data/lib/copy_tuner_client/engine.rb +4 -6
  18. data/lib/copy_tuner_client/helper_extension.rb +58 -54
  19. data/lib/copy_tuner_client/i18n_backend.rb +12 -14
  20. data/lib/copy_tuner_client/i18n_compat.rb +2 -1
  21. data/lib/copy_tuner_client/poller.rb +3 -4
  22. data/lib/copy_tuner_client/prefixed_logger.rb +13 -12
  23. data/lib/copy_tuner_client/process_guard.rb +29 -26
  24. data/lib/copy_tuner_client/queue_with_timeout.rb +29 -19
  25. data/lib/copy_tuner_client/rails.rb +9 -8
  26. data/lib/copy_tuner_client/request_sync.rb +26 -24
  27. data/lib/copy_tuner_client/simple_form_extention.rb +10 -6
  28. data/lib/copy_tuner_client/translation_log.rb +10 -5
  29. data/lib/copy_tuner_client/version.rb +1 -1
  30. data/lib/copy_tuner_client.rb +4 -8
  31. data/lib/tasks/copy_tuner_client_tasks.rake +14 -16
  32. data/skills/copy-tuner-to-locales-migrate-prefix/scripts/migrate_prefix.rb +14 -7
  33. data/skills/copy-tuner-to-t-migrate/scripts/migrate_tt.rb +19 -17
  34. data/spec/copy_tuner_client/cache_spec.rb +63 -57
  35. data/spec/copy_tuner_client/client_spec.rb +82 -81
  36. data/spec/copy_tuner_client/configuration_spec.rb +223 -222
  37. data/spec/copy_tuner_client/copyray/marker_spec.rb +2 -1
  38. data/spec/copy_tuner_client/copyray/rewriter_spec.rb +15 -10
  39. data/spec/copy_tuner_client/copyray_middleware_spec.rb +9 -6
  40. data/spec/copy_tuner_client/copyray_spec.rb +8 -8
  41. data/spec/copy_tuner_client/dotted_hash_spec.rb +33 -33
  42. data/spec/copy_tuner_client/helper_extension_spec.rb +47 -44
  43. data/spec/copy_tuner_client/i18n_backend_spec.rb +144 -143
  44. data/spec/copy_tuner_client/poller_spec.rb +25 -24
  45. data/spec/copy_tuner_client/prefixed_logger_spec.rb +15 -12
  46. data/spec/copy_tuner_client/process_guard_spec.rb +27 -27
  47. data/spec/copy_tuner_client/request_sync_spec.rb +51 -58
  48. data/spec/copy_tuner_client/translation_log_spec.rb +38 -24
  49. data/spec/copy_tuner_client_spec.rb +5 -6
  50. data/spec/support/client_spec_helpers.rb +1 -1
  51. data/spec/support/defines_constants.rb +3 -34
  52. data/spec/support/fake_client.rb +1 -3
  53. data/spec/support/fake_copy_tuner_app.rb +54 -62
  54. data/spec/support/fake_html_safe_string.rb +2 -3
  55. data/spec/support/fake_logger.rb +22 -21
  56. data/spec/support/fake_passenger.rb +4 -6
  57. data/spec/support/fake_unicorn.rb +2 -4
  58. data/spec/support/middleware_stack.rb +3 -3
  59. data/spec/support/writing_cache.rb +4 -4
  60. metadata +1 -1
@@ -8,8 +8,8 @@ module CopyTunerClient
8
8
  # Rack middleware that synchronizes with CopyTuner during each request.
9
9
  #
10
10
  # This is injected into the Rails middleware stack in development environments.
11
- class RequestSync
12
- VIEW_PATH = File.expand_path('../../../ui/views/', __FILE__)
11
+ class RequestSync # rubocop:disable Metrics/ClassLength
12
+ VIEW_PATH = File.expand_path('../../ui/views', __dir__)
13
13
 
14
14
  # @param app [Rack] the upstream app into whose responses to inject the editor
15
15
  # @param options [Hash]
@@ -29,7 +29,7 @@ module CopyTunerClient
29
29
  # Invokes the upstream Rack application and flushes the cache after each
30
30
  # request.
31
31
  def call(env)
32
- if /^\/copytuner/ =~ ::Rack::Request.new(env).path_info
32
+ if %r{^/copytuner} =~ ::Rack::Request.new(env).path_info
33
33
  dup._call(env)
34
34
  else
35
35
  first_request = @first
@@ -53,7 +53,7 @@ module CopyTunerClient
53
53
  @req = ::Rack::Request.new(env)
54
54
 
55
55
  case @req.path_info
56
- when /^\/copytuner\/?$/
56
+ when %r{^/copytuner/?$}
57
57
  index
58
58
  when /sync/
59
59
  sync
@@ -71,21 +71,19 @@ module CopyTunerClient
71
71
 
72
72
  def sync
73
73
  @cache.sync
74
- ::Rack::Response.new{|r| r.redirect('/copytuner/')}.finish
74
+ ::Rack::Response.new { |r| r.redirect('/copytuner/') }.finish
75
75
  end
76
76
 
77
- def render(view, layout=true)
77
+ def render(view, layout: true) # rubocop:disable Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
78
78
  add_rack_array = true
79
- if view.is_a? Hash
79
+ if view.is_a?(Hash)
80
80
  layout = false
81
- if view[:object]
82
- object = view[:object]
83
- end
81
+ object = view[:object] if view[:object]
84
82
 
85
83
  if view[:collection]
86
- return view[:collection].map do |object|
87
- render({:partial => view[:partial], :object => object})
88
- end.join(' ')
84
+ return view[:collection].map { |object|
85
+ render({ partial: view[:partial], object: })
86
+ }.join(' ')
89
87
  end
90
88
 
91
89
  if view[:partial]
@@ -93,17 +91,19 @@ module CopyTunerClient
93
91
  view = "_#{view[:partial]}"
94
92
  end
95
93
  end
96
- binding = Proc.new {}.binding
97
- if layout
98
- body = render_with_layout(view) do
94
+ binding = proc {}.binding
95
+ body =
96
+ if layout
97
+ render_with_layout(view) do
98
+ render_without_layout(view, binding)
99
+ end
100
+ else
99
101
  render_without_layout(view, binding)
100
102
  end
101
- else
102
- body = render_without_layout(view, binding)
103
- end
104
103
  if add_rack_array
105
104
  ::Rack::Response.new(
106
- body, 200,
105
+ body,
106
+ 200,
107
107
  ::Rack::CONTENT_TYPE => 'text/html; charset=utf-8'
108
108
  ).finish
109
109
  else
@@ -115,13 +115,14 @@ module CopyTunerClient
115
115
  # that block are interpolated into the layout
116
116
  def render_with_layout(view)
117
117
  body = ERB.new(File.read(File.join(VIEW_PATH, 'layouts/copytuner_default.html.erb')))
118
- body.result(Proc.new {}.binding)
118
+ body.result(proc {}.binding)
119
119
  end
120
120
 
121
121
  # you have to pass a binding to this (a proc) so that ERB can have
122
122
  # access to helper functions and local variables
123
123
  def render_without_layout(view, binding)
124
- ERB.new(File.read(File.join(VIEW_PATH, 'copytuner', view.to_s + '.html.erb')), eoutvar: 'frobnitz').result(binding)
124
+ path = File.join(VIEW_PATH, 'copytuner', "#{view}.html.erb")
125
+ ERB.new(File.read(path), eoutvar: 'frobnitz').result(binding)
125
126
  end
126
127
 
127
128
  def cancel_sync?(env)
@@ -133,17 +134,18 @@ module CopyTunerClient
133
134
  end
134
135
 
135
136
  def asset_request?(env)
136
- env['PATH_INFO'] =~ /^\/assets/
137
+ env['PATH_INFO'] =~ %r{^/assets}
137
138
  end
138
139
 
139
140
  def in_interval?
140
141
  return false if @last_synced.nil?
141
142
  return false if @interval <= 0
143
+
142
144
  next_sync_at > Time.now.utc
143
145
  end
144
146
 
145
147
  def next_sync_at
146
- @last_synced + @interval if @last_synced and @interval
148
+ @last_synced + @interval if @last_synced && @interval
147
149
  end
148
150
 
149
151
  def update_last_synced
@@ -1,29 +1,33 @@
1
1
  require 'copy_tuner_client/copyray'
2
2
 
3
3
  begin
4
- require "simple_form"
4
+ require 'simple_form'
5
5
  rescue LoadError
6
+ # simple_form は任意依存のため、未導入環境では以降の拡張定義自体をスキップする
6
7
  end
7
8
 
8
9
  if defined?(SimpleForm)
10
+ # SimpleForm のラベル生成に Copyray のオーバーレイマーカーを注入する
9
11
  module SimpleForm::Components::Labels
10
12
  protected
13
+
11
14
  def label_translation_with_copyray_comment
12
15
  source = label_translation_without_copyray_comment
13
16
 
14
17
  if !CopyTunerClient.configuration.disable_copyray_comment_injection && object.class.respond_to?(:lookup_ancestors)
15
18
  attributes_scope = "#{object.class.i18n_scope}.attributes"
16
- defaults = object.class.lookup_ancestors.map do |klass|
17
- "#{attributes_scope}.#{klass.model_name.i18n_key}.#{reflection_or_attribute_name}"
18
- end
19
+ defaults =
20
+ object.class.lookup_ancestors.map do |klass|
21
+ "#{attributes_scope}.#{klass.model_name.i18n_key}.#{reflection_or_attribute_name}"
22
+ end
19
23
  CopyTunerClient::Copyray.augment_template(source, defaults.shift)
20
24
  else
21
25
  source
22
26
  end
23
27
  end
24
28
  if CopyTunerClient.configuration.enable_middleware?
25
- alias_method :label_translation_without_copyray_comment, :label_translation
26
- alias_method :label_translation, :label_translation_with_copyray_comment
29
+ alias label_translation_without_copyray_comment label_translation
30
+ alias label_translation label_translation_with_copyray_comment
27
31
  end
28
32
  end
29
33
  end
@@ -1,4 +1,5 @@
1
1
  module CopyTunerClient
2
+ # リクエスト中に翻訳されたキーと結果をスレッドローカルに記録する(Copyray オーバーレイの JSON 出力に使う)
2
3
  class TranslationLog
3
4
  def self.translations
4
5
  Thread.current[:translations]
@@ -23,16 +24,19 @@ module CopyTunerClient
23
24
 
24
25
  def self.install_hook
25
26
  I18n.class_eval do
27
+ # alias_method が参照するため、alias_method 群より先に定義する必要がある
28
+ # rubocop:disable Style/ClassMethodsDefinitions
26
29
  class << self
27
30
  def translate_with_copy_tuner_hook(key = nil, **options)
28
31
  scope = options[:scope]
29
32
  scope = scope.dup if scope.is_a?(Array) || scope.is_a?(String)
30
33
  result = translate_without_copy_tuner_hook(key, **options)
31
34
 
32
- if key.is_a?(Array)
33
- key.zip(result).each { |k, v| CopyTunerClient::TranslationLog.add(I18n.normalize_keys(nil, k, scope).compact.join('.'), v) unless v.is_a?(Array) }
34
- else
35
- CopyTunerClient::TranslationLog.add(I18n.normalize_keys(nil, key, scope).compact.join('.'), result) unless result.is_a?(Array)
35
+ pairs = key.is_a?(Array) ? key.zip(result) : [[key, result]]
36
+ pairs.each do |k, v|
37
+ next if v.is_a?(Array)
38
+
39
+ CopyTunerClient::TranslationLog.add(I18n.normalize_keys(nil, k, scope).compact.join('.'), v)
36
40
  end
37
41
  result
38
42
  end
@@ -40,9 +44,10 @@ module CopyTunerClient
40
44
  if CopyTunerClient.configuration.enable_middleware?
41
45
  alias_method :translate_without_copy_tuner_hook, :translate
42
46
  alias_method :translate, :translate_with_copy_tuner_hook
43
- alias :t :translate
47
+ alias_method :t, :translate
44
48
  end
45
49
  end
50
+ # rubocop:enable Style/ClassMethodsDefinitions
46
51
  end
47
52
  end
48
53
  end
@@ -1,6 +1,6 @@
1
1
  module CopyTunerClient
2
2
  # Client version
3
- VERSION = '2.1.2'.freeze
3
+ VERSION = '2.2.0'.freeze
4
4
 
5
5
  # API version being used to communicate with the server
6
6
  API_VERSION = '2.0'.freeze
@@ -55,16 +55,12 @@ module CopyTunerClient
55
55
  # @param apply [Boolean] (internal) whether the configuration should be applied yet.
56
56
  #
57
57
  # @yield [Configuration] the configuration to be modified
58
- def self.configure(apply = true)
58
+ def self.configure(apply: true)
59
59
  self.configuration ||= Configuration.new
60
- yield configuration
60
+ yield(configuration)
61
61
 
62
- if apply
63
- configuration.apply
64
- end
62
+ configuration.apply if apply
65
63
  end
66
64
  end
67
65
 
68
- if defined? ::Rails
69
- require 'copy_tuner_client/rails'
70
- end
66
+ require 'copy_tuner_client/rails' if defined? Rails
@@ -1,25 +1,23 @@
1
- namespace :copy_tuner do
2
- desc "Notify CopyTuner of a new deploy."
3
- task :deploy => :environment do
1
+ namespace :copy_tuner do # rubocop:disable Metrics/BlockLength
2
+ desc 'Notify CopyTuner of a new deploy.'
3
+ task deploy: :environment do
4
4
  CopyTunerClient.deploy
5
- puts "Successfully marked all blurbs as published."
5
+ puts 'Successfully marked all blurbs as published.'
6
6
  end
7
7
 
8
- desc "Export CopyTuner blurbs to yaml."
8
+ desc 'Export CopyTuner blurbs to yaml.'
9
9
  task :export, %i[path] => :environment do |_, args|
10
- args.with_defaults(path: "config/locales/copy_tuner.yml")
10
+ args.with_defaults(path: 'config/locales/copy_tuner.yml')
11
11
  CopyTunerClient.cache.sync
12
12
 
13
- if yml = CopyTunerClient.export
14
- File.new("#{Rails.root}/#{args[:path]}", 'w').write(yml)
15
- puts "Successfully exported blurbs to #{args[:path]}."
16
- else
17
- raise "No blurbs have been cached."
18
- end
13
+ yml = CopyTunerClient.export or raise 'No blurbs have been cached.'
14
+
15
+ File.new("#{Rails.root}/#{args[:path]}", 'w').write(yml)
16
+ puts "Successfully exported blurbs to #{args[:path]}."
19
17
  end
20
18
 
21
- desc "Detect invalid keys."
22
- task :detect_conflict_keys => :environment do
19
+ desc 'Detect invalid keys.'
20
+ task detect_conflict_keys: :environment do
23
21
  conflict_keys = CopyTunerClient::DottedHash.conflict_keys(CopyTunerClient.cache.blurbs)
24
22
 
25
23
  if conflict_keys.empty?
@@ -30,8 +28,8 @@ namespace :copy_tuner do
30
28
  end
31
29
  end
32
30
 
33
- desc "Detect html incompatible keys."
34
- task :detect_html_incompatible_keys => :environment do
31
+ desc 'Detect html incompatible keys.'
32
+ task detect_html_incompatible_keys: :environment do
35
33
  require 'copy_tuner_client/i18n_compat'
36
34
  html_incompatible_blurbs = CopyTunerClient::I18nCompat.select_html_incompatible_blurbs(CopyTunerClient.cache.blurbs)
37
35
 
@@ -40,12 +40,16 @@ end
40
40
  options = {}
41
41
  parser =
42
42
  OptionParser.new do |opts|
43
- opts.banner = 'Usage: bin/rails runner migrate_prefix.rb -- --prefix PREFIX --export PATH --out PATH [--regexp PATTERN] [--originals-glob GLOB]'
43
+ opts.banner =
44
+ 'Usage: bin/rails runner migrate_prefix.rb -- --prefix PREFIX --export PATH --out PATH ' \
45
+ '[--regexp PATTERN] [--originals-glob GLOB]'
44
46
  opts.on('--prefix PREFIX', 'ドット区切りの対象 prefix(例: date / activerecord.attributes)') { |v| options[:prefix] = v }
45
47
  opts.on('--export PATH', '全件 export YAML(手順3で出力)') { |v| options[:export] = v }
46
48
  opts.on('--out PATH', '移行分の出力先(例: config/locales/0010_date.yml)') { |v| options[:out] = v }
47
49
  opts.on('--regexp PATTERN', 'leaf キー検証用の正規表現(省略時は prefix から \A<prefix>\. を生成)') { |v| options[:regexp] = v }
48
- opts.on('--originals-glob GLOB', 'オリジナルファイルの glob(既定: config/locales/0000_original_*.yml)') { |v| options[:originals_glob] = v }
50
+ opts.on('--originals-glob GLOB', 'オリジナルファイルの glob(既定: config/locales/0000_original_*.yml)') do |v|
51
+ options[:originals_glob] = v
52
+ end
49
53
  opts.on('--locales LIST', 'カンマ区切りの対象 locale(既定: I18n.available_locales。検証用の上書き)') { |v| options[:locales] = v }
50
54
  end
51
55
  # `bin/rails runner script -- ...` の `--` 以降だけを渡したいが、runner が剥がさない環境もあるため両対応。
@@ -186,9 +190,7 @@ LOCALES.each do |locale|
186
190
  end
187
191
 
188
192
  # 全 locale でスキップ=当該 prefix がどこにも存在しない(prefix の typo)。
189
- if merged_by_locale.empty?
190
- die("prefix '#{PREFIX}' のキーが export にもオリジナルにも見つからない。prefix を確認すること。")
191
- end
193
+ die("prefix '#{PREFIX}' のキーが export にもオリジナルにも見つからない。prefix を確認すること。") if merged_by_locale.empty?
192
194
 
193
195
  # ---- (2) 静的ガード(書き出し前にできる検証を先に。中途半端な --out を残さない)----
194
196
  # --out が次回起動時に Rails の i18n glob でロードされる場所か(採番ミス・配置ミスの検出)。
@@ -206,7 +208,10 @@ merged_by_locale.each do |locale, merged|
206
208
  non_matching = leaf_entries(merged).keys.grep_v(regexp)
207
209
  next if non_matching.empty?
208
210
 
209
- die("locale #{locale}: regexp #{regexp.inspect} にマッチしない leaf キーがある(regexp/prefix 不一致):\n #{non_matching.join("\n ")}")
211
+ die(
212
+ "locale #{locale}: regexp #{regexp.inspect} にマッチしない leaf キーがある(regexp/prefix 不一致):\n " \
213
+ "#{non_matching.join("\n ")}"
214
+ )
210
215
  end
211
216
 
212
217
  # 書き出し(ここまでの検証を通過してから)。merged_by_locale は { "ja" => {...}, "en" => {...} } 形なので
@@ -217,7 +222,9 @@ puts "配置: #{out_path} (locale #{merged_by_locale.keys.join(',')} / leaf
217
222
 
218
223
  # YAML ラウンドトリップ(to_yaml → 再読込で各 locale の merged と一致するか)。
219
224
  merged_by_locale.each do |locale, merged|
220
- die("YAML ラウンドトリップ不一致。書き出し結果が壊れている: #{out_path} (locale #{locale})") unless load_locale_tree(out_path, locale) == merged
225
+ next if load_locale_tree(out_path, locale) == merged
226
+
227
+ die("YAML ラウンドトリップ不一致。書き出し結果が壊れている: #{out_path} (locale #{locale})")
221
228
  end
222
229
 
223
230
  # ---- (3) 移行漏れ検証(削除をメモリ上でシミュレート)----
@@ -86,7 +86,7 @@ RELATIVE_KEY_RE = /\btt\b\s*(?:\(\s*)?['":]\s*\./
86
86
  # Rewriter(大文字 ⟦CT: 固定)の除去網をすり抜け画面に残る。I18n.t 化では直らない。
87
87
  # 正しい修正は method 名を第一引数・表示テキストを第二引数へ分離すること。
88
88
  LABEL_FIRST_ARG_RE = /
89
- (?: (?<![\w.])\w+\.label\b | \blabel_tag\b | (?<![\w.])label(?![\w]) )
89
+ (?: (?<![\w.])\w+\.label\b | \blabel_tag\b | (?<![\w.])label(?!\w) )
90
90
  \s*\(?\s*
91
91
  tt\b (?=\s*[("':@])
92
92
  /x
@@ -110,14 +110,14 @@ def scan_file(path, in_app:)
110
110
  next unless line =~ TT_CALL
111
111
 
112
112
  entry = { file: rel, lineno: idx + 1, code: line.rstrip }
113
- yield(classify_line(line, entry, in_app: in_app))
113
+ yield(classify_line(line, entry, in_app:))
114
114
  end
115
115
  rescue StandardError
116
116
  nil
117
117
  end
118
118
 
119
119
  # ヒット 1 行を分類し、[:safe | :suspicious | :other, entry] を返す。
120
- def classify_line(line, entry, in_app:)
120
+ def classify_line(line, entry, in_app:) # rubocop:disable Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
121
121
  # tt の定義(def/alias)は呼び出しではない。t( へ置換すると Rails の t を壊すので絶対に自動変換しない。
122
122
  if line.match?(TT_DEFINITION)
123
123
  entry[:reason] = 'tt の定義(def/alias)。自動変換禁止。呼び出しを全て t へ移した後に手動で削除する'
@@ -143,15 +143,16 @@ def classify_line(line, entry, in_app:)
143
143
 
144
144
  entry[:kind] = :string_manipulation
145
145
  entry[:relative_key] = line.match?(RELATIVE_KEY_RE)
146
- entry[:hint] = if entry[:relative_key]
147
- 'I18n.t へ。相対キー(.foo)は絶対キーへ書き換えが必要'
148
- else
149
- 'I18n.t(絶対キー) へ置換を検討'
150
- end
146
+ entry[:hint] =
147
+ if entry[:relative_key]
148
+ 'I18n.t へ。相対キー(.foo)は絶対キーへ書き換えが必要'
149
+ else
150
+ 'I18n.t(絶対キー) へ置換を検討'
151
+ end
151
152
  [:suspicious, entry]
152
153
  end
153
154
 
154
- buckets = { safe: safe, suspicious: suspicious, other: other }
155
+ buckets = { safe:, suspicious:, other: }
155
156
  Dir.glob(ALL_GLOB).each do |path|
156
157
  # vendor / node_modules / tmp 等のノイズを除外
157
158
  next if relative(path).start_with?('vendor/', 'node_modules/', 'tmp/', '.git/')
@@ -163,7 +164,7 @@ end
163
164
 
164
165
  if options[:json]
165
166
  require 'json'
166
- puts JSON.pretty_generate(safe: safe, suspicious: suspicious, other: other)
167
+ puts JSON.pretty_generate(safe:, suspicious:, other:)
167
168
  exit(0)
168
169
  end
169
170
 
@@ -172,13 +173,14 @@ def print_section(title, entries)
172
173
  entries.each do |e|
173
174
  # hint があれば必ず kind も付く(classify_line で対で設定)ので素直に出す。
174
175
  # suspicious 内のサブ種別(label_arg / string_manipulation)を可視化する。
175
- suffix = if e[:hint]
176
- " # [#{e[:kind]}] #{e[:hint]}"
177
- elsif e[:reason]
178
- " # #{e[:reason]}"
179
- else
180
- ''
181
- end
176
+ suffix =
177
+ if e[:hint]
178
+ " # [#{e[:kind]}] #{e[:hint]}"
179
+ elsif e[:reason]
180
+ " # #{e[:reason]}"
181
+ else
182
+ ''
183
+ end
182
184
  puts "#{e[:file]}:#{e[:lineno]}: #{e[:code].strip}#{suffix}"
183
185
  end
184
186
  end
@@ -137,7 +137,7 @@ describe 'CopyTunerClient::Cache' do
137
137
  cache.download
138
138
 
139
139
  expect(cache['en.test.key']).to eq('test value')
140
- expect(cache['en.test.empty']).to eq(nil)
140
+ expect(cache['en.test.empty']).to be_nil
141
141
  expect(cache.blank_keys).to contain_exactly('en.test.empty')
142
142
 
143
143
  cache['en.test.empty'] = ''
@@ -157,8 +157,8 @@ describe 'CopyTunerClient::Cache' do
157
157
  it 'flush時に接続エラーが発生した場合はエラーログを出力すること' do
158
158
  failure = 'server is napping'
159
159
  logger = FakeLogger.new
160
- expect(client).to receive(:upload).and_raise(CopyTunerClient::ConnectionError.new(failure))
161
- cache = build_cache(logger: logger)
160
+ allow(client).to receive(:upload).and_raise(CopyTunerClient::ConnectionError.new(failure))
161
+ cache = build_cache(logger:)
162
162
  cache['upload.key'] = 'upload'
163
163
 
164
164
  cache.flush
@@ -169,8 +169,8 @@ describe 'CopyTunerClient::Cache' do
169
169
  it 'download時に接続エラーが発生した場合はエラーログを出力すること' do
170
170
  failure = 'server is napping'
171
171
  logger = FakeLogger.new
172
- expect(client).to receive(:download).and_raise(CopyTunerClient::ConnectionError.new(failure))
173
- cache = build_cache(logger: logger, ready: true)
172
+ allow(client).to receive(:download).and_raise(CopyTunerClient::ConnectionError.new(failure))
173
+ cache = build_cache(logger:, ready: true)
174
174
 
175
175
  cache.download
176
176
 
@@ -179,26 +179,28 @@ describe 'CopyTunerClient::Cache' do
179
179
 
180
180
  it '最初のダウンロードが完了するまでブロックすること' do
181
181
  logger = FakeLogger.new
182
- expect(logger).to receive(:flush)
182
+ allow(logger).to receive(:flush)
183
183
  client.delay = true
184
- cache = build_cache(logger: logger)
184
+ cache = build_cache(logger:)
185
185
 
186
186
  t_download = Thread.new { cache.download }
187
187
  sleep 0.1 until cache.pending?
188
188
 
189
- t_wait = Thread.new do
190
- cache.wait_for_download
191
- end
189
+ t_wait =
190
+ Thread.new do
191
+ cache.wait_for_download
192
+ end
192
193
  sleep 0.1 until logger.has_entry?(:info, 'Waiting for first download')
193
194
  client.go
194
195
  expect(t_download.join(1)).not_to be_nil
195
- expect(cache.pending?).to be_falsey
196
+ expect(cache).not_to be_pending
196
197
  expect(t_wait.join(1)).not_to be_nil
198
+ expect(logger).to have_received(:flush)
197
199
  end
198
200
 
199
201
  it 'ダウンロード前はブロックしないこと' do
200
202
  logger = FakeLogger.new
201
- cache = build_cache(logger: logger)
203
+ cache = build_cache(logger:)
202
204
 
203
205
  finished = false
204
206
  Thread.new do
@@ -208,7 +210,7 @@ describe 'CopyTunerClient::Cache' do
208
210
 
209
211
  sleep(1)
210
212
 
211
- expect(finished).to eq(true)
213
+ expect(finished).to be(true)
212
214
  expect(logger).not_to have_entry(:info, 'Waiting for first download')
213
215
  end
214
216
 
@@ -226,21 +228,21 @@ describe 'CopyTunerClient::Cache' do
226
228
  match do |thread|
227
229
  sleep(0.1)
228
230
 
229
- if thread.status === false
230
- violated('アンロック前に終了してしまった')
231
+ if thread.status == false
232
+ violated?('アンロック前に終了してしまった')
231
233
  else
232
234
  mutex.unlock
233
235
  sleep(0.1)
234
236
 
235
- if thread.status === false
237
+ if thread.status == false
236
238
  true
237
239
  else
238
- violated('アンロック後もスレッドが終了しない')
240
+ violated?('アンロック後もスレッドが終了しない')
239
241
  end
240
242
  end
241
243
  end
242
244
 
243
- def violated(failure)
245
+ def violated?(failure)
244
246
  @failure_message = failure
245
247
  false
246
248
  end
@@ -277,13 +279,15 @@ describe 'CopyTunerClient::Cache' do
277
279
  config.project_id = 1
278
280
  config.cache = cache
279
281
  end
280
- expect(cache).to receive(:flush).at_least(:once)
282
+ allow(cache).to receive(:flush)
281
283
 
282
284
  CopyTunerClient.flush
285
+
286
+ expect(cache).to have_received(:flush).at_least(:once)
283
287
  end
284
288
 
285
289
  describe '#to_tree_hash' do
286
- subject { cache.to_tree_hash }
290
+ subject(:tree_hash) { cache.to_tree_hash }
287
291
 
288
292
  let(:cache) do
289
293
  cache = build_cache
@@ -292,7 +296,7 @@ describe 'CopyTunerClient::Cache' do
292
296
  end
293
297
 
294
298
  it 'データがない場合は空ハッシュを返すこと' do
295
- is_expected.to eq({})
299
+ expect(tree_hash).to eq({})
296
300
  end
297
301
 
298
302
  context 'フラットなキーの場合' do
@@ -303,17 +307,17 @@ describe 'CopyTunerClient::Cache' do
303
307
  end
304
308
 
305
309
  it 'ツリー構造に変換されること' do
306
- is_expected.to eq({
307
- 'ja' => {
308
- 'views' => {
309
- 'hoge' => 'test',
310
- 'fuga' => 'test2'
311
- }
312
- },
313
- 'en' => {
314
- 'hello' => 'world'
315
- }
316
- })
310
+ expect(tree_hash).to eq({
311
+ 'ja' => {
312
+ 'views' => {
313
+ 'hoge' => 'test',
314
+ 'fuga' => 'test2',
315
+ },
316
+ },
317
+ 'en' => {
318
+ 'hello' => 'world',
319
+ },
320
+ })
317
321
  end
318
322
  end
319
323
 
@@ -326,26 +330,26 @@ describe 'CopyTunerClient::Cache' do
326
330
  end
327
331
 
328
332
  it '正しいツリー構造になること' do
329
- is_expected.to eq({
330
- 'ja' => {
331
- 'views' => {
332
- 'users' => {
333
- 'index' => 'user index',
334
- 'show' => 'user show'
335
- },
336
- 'posts' => {
337
- 'index' => 'post index'
338
- }
339
- }
340
- },
341
- 'en' => {
342
- 'common' => {
343
- 'buttons' => {
344
- 'save' => 'Save'
345
- }
346
- }
347
- }
348
- })
333
+ expect(tree_hash).to eq({
334
+ 'ja' => {
335
+ 'views' => {
336
+ 'users' => {
337
+ 'index' => 'user index',
338
+ 'show' => 'user show',
339
+ },
340
+ 'posts' => {
341
+ 'index' => 'post index',
342
+ },
343
+ },
344
+ },
345
+ 'en' => {
346
+ 'common' => {
347
+ 'buttons' => {
348
+ 'save' => 'Save',
349
+ },
350
+ },
351
+ },
352
+ })
349
353
  end
350
354
  end
351
355
  end
@@ -353,7 +357,7 @@ describe 'CopyTunerClient::Cache' do
353
357
  describe '#version' do
354
358
  it 'クライアントのetagを返すこと(効率的なバージョンチェック)' do
355
359
  cache = build_cache
356
- client_instance = cache.send(:client)
360
+ client_instance = cache.__send__(:client)
357
361
 
358
362
  # ETag が設定されている場合
359
363
  client_instance.etag = '"abc123"'
@@ -366,7 +370,7 @@ describe 'CopyTunerClient::Cache' do
366
370
 
367
371
  it 'etagがnilの場合も正常に動作すること' do
368
372
  cache = build_cache
369
- client_instance = cache.send(:client)
373
+ client_instance = cache.__send__(:client)
370
374
  client_instance.etag = nil
371
375
 
372
376
  expect(cache.version).to be_nil
@@ -391,7 +395,7 @@ describe 'CopyTunerClient::Cache' do
391
395
  end
392
396
 
393
397
  describe '#export' do
394
- subject { cache.export }
398
+ subject(:export_result) { cache.export }
395
399
 
396
400
  let(:cache) do
397
401
  cache = build_cache
@@ -404,12 +408,14 @@ describe 'CopyTunerClient::Cache' do
404
408
  config.project_id = 1
405
409
  config.cache = cache
406
410
  end
407
- expect(cache).to receive(:export)
411
+ allow(cache).to receive(:export)
408
412
  CopyTunerClient.export
413
+
414
+ expect(cache).to have_received(:export)
409
415
  end
410
416
 
411
417
  it 'blurbキーがない場合はyamlを返さないこと' do
412
- is_expected.to eq nil
418
+ expect(export_result).to be_nil
413
419
  end
414
420
 
415
421
  context '1階層のblurbキーがある場合' do