copy_tuner_client 2.4.0 → 2.5.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: 803784553a4dbeb3916ccdffef8ebe40626644c18b1ac7fa0e42ff9158d9ae1a
4
- data.tar.gz: aa985dc5ac60c2f137fe95a9fd5c3c15fd7e8667e7b4f183d49f1e89ba448aa7
3
+ metadata.gz: 938a08aeb03719e98a6e118eb714b820798f91ff3d5e69cce65eeea3d8b243ca
4
+ data.tar.gz: 9da7bbe8fa7ed6b92b90fa090f9ebf5ec67cb8945bace845494aa7638640b3ce
5
5
  SHA512:
6
- metadata.gz: 321d3cc14cfd86127ca2bbe204fa89cd76f152a9770991ed0d8939f6b7724500b1bd7c92dca63b615e4ae741b2652ef0176f60e4c6342afd0a1c718ae8503ddc
7
- data.tar.gz: 3860c2738c49a2c7fd4af233ba3f062fda3fff1a6d789a851a1f2973029e4cbc201f6d61898ba05f63bf9f79fd5e19d2b8a40ddf387f58d0699e0475d2d08f8c
6
+ metadata.gz: 86d05a06a42a06da4586d60bc80649cdb9f4cd98c26425d6528f7b121c2ca880b366bc12aeec7b20952bbc7129b2837dcd9261851bb960ddebd7de773e0b64b6
7
+ data.tar.gz: 831783835b4ede6b590dd298e89f596c8c9ca417260e4ae490f8b6bbca576e07c7b5f89a490eb8b2b651e305a66b0c9dac7c526f0592d2fdef7f7337d70f6607
@@ -3,6 +3,10 @@ require 'copy_tuner_client/copyray/marker'
3
3
  module CopyTunerClient
4
4
  # 翻訳テキストにオーバーレイ編集用のマーカートークンを埋め込む
5
5
  class Copyray
6
+ # NOTE: マーカーは CopyrayMiddleware の Rewriter でしか除去されない。注入可否を「format が html か」ではなく
7
+ # 「この描画の出力が middleware を通るか」で判定するため、middleware が env にこの印を立てる。
8
+ ENV_KEY = 'copy_tuner.copyray_injectable'.freeze
9
+
6
10
  # This:
7
11
  # message
8
12
  # Becomes:
@@ -1,5 +1,6 @@
1
1
  # cf) xray-rails : xray/middleware.rb
2
2
 
3
+ require 'copy_tuner_client/copyray'
3
4
  require 'copy_tuner_client/copyray/rewriter'
4
5
 
5
6
  module CopyTunerClient
@@ -11,6 +12,7 @@ module CopyTunerClient
11
12
 
12
13
  def call(env)
13
14
  CopyTunerClient::TranslationLog.clear
15
+ env[CopyTunerClient::Copyray::ENV_KEY] = true
14
16
  status, headers, response = @app.call(env)
15
17
  if rewritable?(status, headers) && (body = response_body(response))
16
18
  rewrite_response(env, status, headers, body, response)
@@ -17,6 +17,13 @@ module CopyTunerClient
17
17
  )
18
18
  end
19
19
 
20
+ ActiveSupport.on_load(:action_controller_base) do
21
+ CopyTunerClient::HelperExtension.hook_render_to_string(
22
+ ActionController::Base,
23
+ middleware_enabled: CopyTunerClient.configuration.enable_middleware?
24
+ )
25
+ end
26
+
20
27
  CopyTunerClient::TranslationLog.install_hook if CopyTunerClient.configuration.enable_middleware?
21
28
 
22
29
  require 'copy_tuner_client/simple_form_extention'
@@ -1,5 +1,7 @@
1
+ require 'copy_tuner_client/copyray'
2
+
1
3
  module CopyTunerClient
2
- # ActionView の translate/t を alias_method で差し替え、Copyray のオーバーレイマーカーを注入する
4
+ # ActionView の translate/t を差し替えて Copyray のオーバーレイマーカーを注入し、注入してよい描画経路を制御する
3
5
  module HelperExtension
4
6
  # NOTE: class_eval ブロック内で def すると、ブロック内メソッドの複雑さが
5
7
  # hook_translation_helper 自体の Metrics/AbcSize としてカウントされてしまうため、
@@ -50,14 +52,40 @@ module CopyTunerClient
50
52
  # マーカー混入は実害が大きいため、controller の型で明示除外する。
51
53
  return false if defined?(ActionMailer::Base) && current_controller.is_a?(ActionMailer::Base)
52
54
 
53
- # NOTE: request が無い/format が html でない経路には注入しない。&. と || false で
54
- # request 不在時も安全に false を返す。
55
- current_controller.request&.format&.html? || false
55
+ request = current_controller.request
56
+ return false unless passes_copyray_middleware?(request)
57
+
58
+ # NOTE: middleware を通っても render json: などは rewritable? に弾かれ書き換えられないため、
59
+ # format が html でない経路には注入しない。
60
+ request.format&.html? || false
56
61
  end
57
62
  private :copyray_injectable?
63
+
64
+ # NOTE: format が html でも出力が CopyrayMiddleware を通らない経路(ApplicationController.renderer や
65
+ # render_to_string)ではマーカーが除去されずに残るため、middleware が立てた印がある場合に限り注入する。
66
+ def passes_copyray_middleware?(request)
67
+ request&.env&.[](CopyTunerClient::Copyray::ENV_KEY)
68
+ end
69
+ private :passes_copyray_middleware?
58
70
  end
59
71
  private_constant :CopyrayCommentInjection
60
72
 
73
+ # NOTE: render_to_string の結果は PDF 化や JSON 埋め込みなどレスポンス以外に使われ、middleware の
74
+ # 書き換えを通らない。実行中だけ印を外し、その間の translate にマーカーを注入させない。
75
+ module RenderToStringGuard
76
+ def render_to_string(*, **, &)
77
+ env = request&.env
78
+ return super unless env&.delete(CopyTunerClient::Copyray::ENV_KEY)
79
+
80
+ begin
81
+ super
82
+ ensure
83
+ env[CopyTunerClient::Copyray::ENV_KEY] = true
84
+ end
85
+ end
86
+ end
87
+ private_constant :RenderToStringGuard
88
+
61
89
  def self.hook_translation_helper(mod, middleware_enabled:)
62
90
  return unless middleware_enabled
63
91
 
@@ -68,5 +96,11 @@ module CopyTunerClient
68
96
  alias_method :t, :translate
69
97
  end
70
98
  end
99
+
100
+ def self.hook_render_to_string(mod, middleware_enabled:)
101
+ return unless middleware_enabled
102
+
103
+ mod.prepend(RenderToStringGuard)
104
+ end
71
105
  end
72
106
  end
@@ -1,6 +1,6 @@
1
1
  module CopyTunerClient
2
2
  # Client version
3
- VERSION = '2.4.0'.freeze
3
+ VERSION = '2.5.0'.freeze
4
4
 
5
5
  # API version being used to communicate with the server
6
6
  API_VERSION = '2.0'.freeze
@@ -1,6 +1,7 @@
1
1
  require 'spec_helper'
2
2
  require 'copy_tuner_client/copyray_middleware'
3
3
  require 'copy_tuner_client/copyray/marker'
4
+ require 'copy_tuner_client/copyray'
4
5
  require 'copy_tuner_client/translation_log'
5
6
 
6
7
  describe CopyTunerClient::CopyrayMiddleware do
@@ -78,6 +79,19 @@ describe CopyTunerClient::CopyrayMiddleware do
78
79
  end
79
80
  end
80
81
 
82
+ context '下流の app を呼ぶとき' do
83
+ it '下流の app に渡る env にフラグが立っている' do
84
+ received_env = nil
85
+ recording_app =
86
+ ->(env) do
87
+ received_env = env.dup
88
+ [200, { 'Content-Type' => 'application/json' }, ['{}']]
89
+ end
90
+ described_class.new(recording_app).call({})
91
+ expect(received_env[CopyTunerClient::Copyray::ENV_KEY]).to be true
92
+ end
93
+ end
94
+
81
95
  context 'HTML 以外のレスポンスのとき' do
82
96
  let(:headers) { { 'Content-Type' => 'application/json' } }
83
97
  let(:body) { "{\"x\":\"#{marker('a.b')}\"}" }
@@ -17,7 +17,12 @@ describe CopyTunerClient::HelperExtension do
17
17
  end
18
18
 
19
19
  def request_class
20
- @request_class ||= Struct.new(:format)
20
+ @request_class ||= Struct.new(:format, :env)
21
+ end
22
+
23
+ # NOTE: CopyrayMiddleware を通るリクエストを再現するため、フラグ付きの env を持つ request を作る。
24
+ def injectable_request(format = :html)
25
+ request_class.new(format_class.new(format), { CopyTunerClient::Copyray::ENV_KEY => true })
21
26
  end
22
27
 
23
28
  def controller_class
@@ -44,7 +49,7 @@ describe CopyTunerClient::HelperExtension do
44
49
  # デフォルトはそれを再現した controller を持たせておく。
45
50
  let(:view) do
46
51
  Class.new.include(keyword_arguments_helper).new.tap do |v|
47
- v.controller = controller_class.new(request_class.new(format_class.new(:html)))
52
+ v.controller = controller_class.new(injectable_request)
48
53
  end
49
54
  end
50
55
 
@@ -84,10 +89,20 @@ describe CopyTunerClient::HelperExtension do
84
89
 
85
90
  %i[json text csv pdf].each do |format|
86
91
  it "does not inject the marker when request.format is :#{format}" do
87
- view.controller = controller_class.new(request_class.new(format_class.new(format)))
92
+ view.controller = controller_class.new(injectable_request(format))
88
93
  expect(view.translate('some.key', name: 'World')).to eq 'Hello, World'
89
94
  end
90
95
  end
96
+
97
+ it 'format が html でも env にフラグが無ければ注入しない' do
98
+ view.controller = controller_class.new(request_class.new(format_class.new(:html), {}))
99
+ expect(view.translate('some.key', name: 'World')).to eq 'Hello, World'
100
+ end
101
+
102
+ it 'request.env が nil でも落ちずに注入しない' do
103
+ view.controller = controller_class.new(request_class.new(format_class.new(:html), nil))
104
+ expect(view.translate('some.key', name: 'World')).to eq 'Hello, World'
105
+ end
91
106
  end
92
107
 
93
108
  context 'injection guard by controller' do
@@ -109,7 +124,7 @@ describe CopyTunerClient::HelperExtension do
109
124
 
110
125
  it 'does not raise when ActionMailer is not loaded' do
111
126
  hide_const('ActionMailer::Base') if defined?(ActionMailer::Base)
112
- view.controller = controller_class.new(request_class.new(format_class.new(:html)))
127
+ view.controller = controller_class.new(injectable_request)
113
128
  expect { view.translate('some.key', name: 'World') }.not_to raise_error
114
129
  end
115
130
  end
@@ -118,10 +133,91 @@ describe CopyTunerClient::HelperExtension do
118
133
  # 維持されなければならない。注入ガードが初期値登録まで巻き添えで止めていないことを保証する。
119
134
  context 'default value registration' do
120
135
  it 'registers the default value even when the marker is not injected' do
121
- view.controller = controller_class.new(request_class.new(format_class.new(:json)))
136
+ view.controller = controller_class.new(injectable_request(:json))
122
137
  allow(I18n).to receive(:t)
123
138
  view.translate('some.key', name: 'World', default: 'Default')
124
139
  expect(I18n).to have_received(:t).with('some.key', hash_including(default: 'Default'))
125
140
  end
126
141
  end
142
+
143
+ describe '.hook_render_to_string' do
144
+ # NOTE: ActionController::Base を読み込まずに検証するため、super 呼び出し時の env の状態を
145
+ # 記録するだけの最小の基底クラスに prepend する。
146
+ let(:base_class) do
147
+ Class.new do
148
+ attr_reader :request, :recorded
149
+
150
+ def initialize(request)
151
+ @request = request
152
+ @recorded = []
153
+ end
154
+
155
+ def render_to_string(*_args, raise_error: false, nested: false)
156
+ record_flag
157
+ if nested
158
+ render_to_string
159
+ record_flag
160
+ end
161
+ raise 'boom' if raise_error
162
+
163
+ 'rendered'
164
+ end
165
+
166
+ def record_flag
167
+ @recorded << request&.env&.key?(CopyTunerClient::Copyray::ENV_KEY)
168
+ end
169
+ end
170
+ end
171
+ let(:env) { { CopyTunerClient::Copyray::ENV_KEY => true } }
172
+ let(:controller) { base_class.new(request_class.new(format_class.new(:html), env)) }
173
+
174
+ context 'middleware_enabled が true のとき' do
175
+ before { described_class.hook_render_to_string(base_class, middleware_enabled: true) }
176
+
177
+ it '実行中は env からフラグが外れる' do
178
+ controller.render_to_string
179
+ expect(controller.recorded).to eq [false]
180
+ end
181
+
182
+ it '元の戻り値を返す' do
183
+ expect(controller.render_to_string).to eq 'rendered'
184
+ end
185
+
186
+ it '終了後はフラグが復元される' do
187
+ controller.render_to_string
188
+ expect(env[CopyTunerClient::Copyray::ENV_KEY]).to be true
189
+ end
190
+
191
+ it '例外が起きてもフラグが復元される' do
192
+ expect { controller.render_to_string(raise_error: true) }.to raise_error('boom')
193
+ expect(env[CopyTunerClient::Copyray::ENV_KEY]).to be true
194
+ end
195
+
196
+ it '入れ子でも外側の終了時まで外れたまま' do
197
+ controller.render_to_string(nested: true)
198
+ expect(controller.recorded).to eq [false, false, false]
199
+ expect(env[CopyTunerClient::Copyray::ENV_KEY]).to be true
200
+ end
201
+
202
+ it '元々フラグが無い env にはフラグを足さない' do
203
+ env.clear
204
+ controller.render_to_string
205
+ expect(env).not_to have_key(CopyTunerClient::Copyray::ENV_KEY)
206
+ end
207
+
208
+ it 'request が nil でも落ちない' do
209
+ controller = base_class.new(nil)
210
+ expect(controller.render_to_string).to eq 'rendered'
211
+ end
212
+ end
213
+
214
+ context 'middleware_enabled が false のとき' do
215
+ before { described_class.hook_render_to_string(base_class, middleware_enabled: false) }
216
+
217
+ it 'フックせず実行中もフラグが残る' do
218
+ controller.render_to_string
219
+ expect(controller.recorded).to eq [true]
220
+ end
221
+ end
222
+ end
127
223
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: copy_tuner_client
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.4.0
4
+ version: 2.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - SonicGarden