class-action 0.0.1 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,72 @@
1
+ module ClassAction
2
+ module RSpec
3
+
4
+ class RespondToFormatMatcher
5
+
6
+ def initialize(format)
7
+ @format = format.to_sym
8
+ end
9
+
10
+ def on(condition)
11
+ @condition = condition
12
+ self
13
+ end
14
+
15
+ def matches?(action, &block)
16
+ @action = action
17
+
18
+ if action.class._responders.key?([@format, @condition])
19
+
20
+ if block
21
+ # Response defined, we return true but we need to also execute the block,
22
+ # as it might contain additional checks. First run the action's response
23
+ # block, for this.
24
+ respond_block = action.class._responders[ [@format, @condition] ]
25
+ action.instance_exec &respond_block if respond_block
26
+ action.send :copy_assigns_to_controller
27
+ block.call
28
+ end
29
+
30
+ true
31
+ else
32
+ false
33
+ end
34
+ end
35
+
36
+ def description
37
+ if @condition
38
+ "respond to format :#{@format} on :#{@condition}"
39
+ else
40
+ "respond to format :#{@format}"
41
+ end
42
+ end
43
+
44
+ def failure_message_for_should
45
+ if @condition
46
+ "expected action of class #{@action.class} to respond to format :#{@format} on :#{@condition}"
47
+ else
48
+ "expected action of class #{@action.class} to respond to format :#{@format}"
49
+ end
50
+ end
51
+
52
+ def failure_message_for_should_not
53
+ if @condition
54
+ "expected action of class #{@action.class} not to respond to format :#{@format} on :#{@condition}"
55
+ else
56
+ "expected action of class #{@action.class} not to respond to format :#{@format}"
57
+ end
58
+ end
59
+
60
+ end
61
+
62
+ end
63
+ end
64
+
65
+ RSpec::Matchers.module_eval do
66
+ def respond_to_format(format)
67
+ ClassAction::RSpec::RespondToFormatMatcher.new(format)
68
+ end
69
+ def respond_to_any_format
70
+ ClassAction::RSpec::RespondToFormatMatcher.new(:any)
71
+ end
72
+ end
@@ -0,0 +1,60 @@
1
+ module ClassAction
2
+ module RSpec
3
+
4
+ class RespondWithMatcher
5
+
6
+ def initialize(expected)
7
+ @expected = expected.to_sym
8
+ end
9
+
10
+ def on(condition)
11
+ @condition = condition
12
+ self
13
+ end
14
+
15
+ def matches?(action)
16
+ @action = action
17
+ @actual = action.class._responses[@condition].try(:to_sym)
18
+ @actual == @expected
19
+ end
20
+
21
+ def description
22
+ if @condition
23
+ "respond with method :#{@expected} on :#{@condition}"
24
+ else
25
+ "respond with method :#{@expected}"
26
+ end
27
+ end
28
+
29
+ def failure_message_for_should
30
+ suffix = if @actual
31
+ ", but it responds with :#{@actual}"
32
+ else
33
+ ", but it has no response method"
34
+ end
35
+
36
+ if @condition
37
+ "expected action of class #{@action.class} to respond with :#{@expected} on :#{@condition}#{suffix}"
38
+ else
39
+ "expected action of class #{@action.class} to respond with :#{@expected}#{suffix}"
40
+ end
41
+ end
42
+
43
+ def failure_message_for_should_not
44
+ if @condition
45
+ "expected action of class #{@action.class} not to respond with :#{@expected} on :#{@condition}"
46
+ else
47
+ "expected action of class #{@action.class} not to respond with :#{@expected}"
48
+ end
49
+ end
50
+
51
+ end
52
+
53
+ end
54
+ end
55
+
56
+ RSpec::Matchers.module_eval do
57
+ def respond_with(expected)
58
+ ClassAction::RSpec::RespondWithMatcher.new(expected)
59
+ end
60
+ end
@@ -0,0 +1,3 @@
1
+ Dir[ File.expand_path('../rspec/*.rb', __FILE__) ].each do |file|
2
+ require file
3
+ end
@@ -1,3 +1,3 @@
1
1
  module ClassAction
2
- VERSION = "0.0.1"
2
+ VERSION = "1.1.0"
3
3
  end
data/lib/class_action.rb CHANGED
@@ -28,8 +28,13 @@ module ClassAction
28
28
  raise ArgumentError, "ClassAction does not support anonymous classes" if action_class.name.nil?
29
29
 
30
30
  class_eval <<-RUBY, __FILE__, __LINE__+1
31
+ def _#{action}_action_class
32
+ @_class_action ||= #{action_class.name}.new self
33
+ end
34
+ private :_#{action}_action_class
35
+
31
36
  def #{action}
32
- _execute_class_action :#{action}, #{action_class.name}
37
+ _#{action}_action_class._execute
33
38
  end
34
39
  RUBY
35
40
 
@@ -43,18 +48,18 @@ module ClassAction
43
48
  # mimes_for_respond_to hash.
44
49
  def inject_class_action_mimes(action, klass)
45
50
  # If no responders or a default responder is given, we don't do anything.
46
- return if klass.responders.empty? || klass.responders.has_key?(:any)
51
+ return if klass._responders.empty? || klass._responders.any? { |(mime, _condition), _block| mime == :any }
47
52
 
48
53
  mimes = mimes_for_respond_to.dup
49
54
 
50
55
  # Make sure no extra mimes are allowed for the action.
51
56
  mimes.each do |mime, restrictions|
52
- next if klass.responders.key?(mime)
57
+ next if klass._responders.any? { |(m, _codition), _block| m == mime }
53
58
  exclude_class_action_in_mime_type mime, restrictions, action
54
59
  end
55
60
 
56
61
  # Include all action mimes.
57
- klass.responders.each do |mime, _block|
62
+ klass._responders.each do |(mime, _condition), _block|
58
63
  mimes[mime] ||= { :only => [] }
59
64
  include_class_action_in_mime_type mime, mimes[mime], action
60
65
  end
@@ -64,7 +69,7 @@ module ClassAction
64
69
 
65
70
  def include_class_action_in_mime_type(mime, restrictions, action)
66
71
  if restrictions && restrictions[:except] && restrictions[:except].include?(action)
67
- logger.warn "Warning: action #{action} (ClassAction) responds to `#{mime}` but it does not accept this mime type"
72
+ logger.warn "Warning: action #{action} (ClassAction) responds to `#{mime}` but it does not accept this mime type" if logger
68
73
  elsif restrictions && restrictions[:only] && !restrictions[:only].include?(action)
69
74
  restrictions[:only] << action
70
75
  end
@@ -90,9 +95,8 @@ module ClassAction
90
95
 
91
96
  private
92
97
 
93
- def _execute_class_action(name, klass)
94
- @_class_action = klass.new(self)
95
- @_class_action._execute
98
+ def _class_action
99
+ send(:"_#{action_name}_action_class")
96
100
  end
97
101
 
98
102
  end
@@ -5,6 +5,7 @@ describe ClassAction::Action do
5
5
  let(:controller) { double(:controller, :view_assigns => {}, :response_body => nil) }
6
6
  let(:action_class) { Class.new(ClassAction::Action) }
7
7
  let(:action) { action_class.new(controller) }
8
+ before { allow(controller).to receive(:class_action).and_return(action) }
8
9
 
9
10
  it "should by default be available" do
10
11
  expect(action).to be_available
@@ -15,14 +16,16 @@ describe ClassAction::Action do
15
16
  expect(action_class.helpers).to be_a(Module)
16
17
  end
17
18
 
18
- it "should define the helper method in the action's helpers module, which should call the method on the controller action" do
19
+ before do
19
20
  action_class.class_eval do
20
21
  def helper1
21
22
  'HELPER RESULT'
22
23
  end
23
24
  helper_method :helper1
24
25
  end
26
+ end
25
27
 
28
+ it "should define the helper method in the action's helpers module, which should call the method on the controller action" do
26
29
  helpers = action_class.helpers
27
30
  klass = Class.new do
28
31
  include helpers
@@ -38,20 +41,45 @@ describe ClassAction::Action do
38
41
  expect(controller).to receive(:class_action).and_return(action)
39
42
  expect(obj.helper1).to eql('HELPER RESULT')
40
43
  end
44
+
45
+ it "should also expose helper methods on the controller" do
46
+ action # Load the action up to include the helpers module.
47
+ expect(controller).to respond_to(:helper1)
48
+ expect(controller.helper1).to eql('HELPER RESULT')
49
+ end
50
+
51
+ it "should also expose helper methods defined in a superclass" do
52
+ action_subclass = Class.new(action_class) do
53
+ def helper2
54
+ 'HELPER2'
55
+ end
56
+ helper_method :helper2
57
+ end
58
+
59
+ obj= Class.new{ include action_subclass.helpers }.new
60
+ expect(obj).to respond_to(:helper1)
61
+ expect(obj).to respond_to(:helper2)
62
+ end
63
+
41
64
  end
42
65
 
43
- describe '.controller_method' do
44
- before { allow(controller).to receive(:load_post) }
45
- before { action_class.class_eval { controller_method :load_post } }
66
+ describe 'controller methods' do
67
+ let(:result) { double(:result) }
68
+ before { allow(controller).to receive(:load_post).and_return(result) }
46
69
 
47
- it "should create a protected method :load_post" do
48
- expect(action.protected_methods).to include(:load_post)
70
+ it "should make the action respond to :load_post, but protectedly" do
71
+ expect(action).not_to respond_to(:load_post)
72
+ expect(action.respond_to?(:load_post, true)).to be_true # matcher doesn't work with second argument
73
+ end
74
+
75
+ it "should pass the method :load_post on to the controller" do
76
+ expect(action.load_post).to be(result)
49
77
  end
50
78
 
51
- it "should create a proxy to the controller" do
52
- result = double(:result)
53
- expect(controller).to receive(:load_post).and_return(result)
54
- expect(action.send(:load_post)).to be(result)
79
+ it "should create a protected method :load_post the first time it is called" do
80
+ expect(action.protected_methods).not_to include(:load_post)
81
+ action.load_post
82
+ expect(action.protected_methods).to include(:load_post)
55
83
  end
56
84
 
57
85
  it "should copy assigns to the controller before executing the controller method, and copy them back afterwards" do
@@ -68,7 +96,6 @@ describe ClassAction::Action do
68
96
  end
69
97
 
70
98
  action_class.class_eval do
71
- controller_method :increase_var
72
99
  def execute
73
100
  @var = 2
74
101
  increase_var
@@ -98,7 +125,7 @@ describe ClassAction::Action do
98
125
  def method4; end
99
126
  end
100
127
 
101
- expect(action_class.action_methods).to eql([ :method1, :method2 ])
128
+ expect(action_class._action_methods).to eql([ :method1, :method2 ])
102
129
  end
103
130
  end
104
131
 
@@ -111,22 +138,65 @@ describe ClassAction::Action do
111
138
  it "should execute all action methods in the action, and call #copy_assigns_to_controller finally" do
112
139
  called = []
113
140
 
114
- expect(action_class).to receive(:action_methods).and_return([:method1, :method2])
115
- expect(action).to receive(:method1) { called << :method1 }
116
- expect(action).to receive(:method2) { called << :method2 }
141
+ expect(action_class).to receive(:_action_methods).and_return([:method1, :method2])
142
+
143
+ action_class.class_eval do
144
+ def method1
145
+ @called << :method1
146
+ end
147
+ def method2
148
+ @called << :method2
149
+ end
150
+ end
117
151
 
152
+ action.instance_variable_set '@called', called
118
153
  action._execute
119
154
  expect(called).to eql([:method1, :method2])
120
155
  end
121
156
 
157
+ it "should skip methods that take arguments" do
158
+ action_class.class_eval do
159
+ def one
160
+ @called = []
161
+ @called << :one
162
+ end
163
+ def two(*args)
164
+ @called << :two
165
+ end
166
+ def three(arg)
167
+ @called << :three
168
+ end
169
+ def three(arg = nil)
170
+ @called << :four
171
+ end
172
+ end
173
+
174
+ action._execute
175
+ called = action.instance_variable_get('@called')
176
+ expect(called).to eql([:one])
177
+ end
178
+
122
179
  it "should stop executing when a response body is set" do
123
180
  called = []; response_body = nil
124
181
 
125
182
  allow(controller).to receive(:response_body) { response_body }
126
- expect(action_class).to receive(:action_methods).and_return([:method1, :method2])
127
- expect(action).to receive(:method1) { called << :method1; response_body = '<html></html>' }
128
- expect(action).not_to receive(:method2)
183
+ allow(controller).to receive(:response).and_return(double())
184
+ allow(controller.response).to receive(:body) { response_body }
185
+ allow(controller.response).to receive(:body=) { |val| response_body = val }
186
+
187
+ expect(action_class).to receive(:_action_methods).and_return([:method1, :method2])
129
188
 
189
+ action_class.class_eval do
190
+ def method1
191
+ @called << :method1
192
+ response.body = '<html></html>'
193
+ end
194
+ def method2
195
+ @called << :method2
196
+ end
197
+ end
198
+
199
+ action.instance_variable_set '@called', called
130
200
  action._execute
131
201
  expect(called).to eql([:method1])
132
202
  end
@@ -134,11 +204,20 @@ describe ClassAction::Action do
134
204
  it "should call _respond at the end" do
135
205
  called = []
136
206
 
137
- expect(action_class).to receive(:action_methods).and_return([:method1, :method2])
138
- expect(action).to receive(:method1) { called << :method1 }
139
- expect(action).to receive(:method2) { called << :method2 }
207
+ expect(action_class).to receive(:_action_methods).and_return([:method1, :method2])
208
+
209
+ action_class.class_eval do
210
+ def method1
211
+ @called << :method1
212
+ end
213
+ def method2
214
+ @called << :method2
215
+ end
216
+ end
217
+
140
218
  expect(action).to receive(:_respond) { called << :_respond }
141
219
 
220
+ action.instance_variable_set '@called', called
142
221
  action._execute
143
222
  expect(called).to eql([:method1, :method2, :_respond])
144
223
  end
@@ -168,7 +247,7 @@ describe ClassAction::Action do
168
247
  action._execute
169
248
  end
170
249
 
171
- context "with no respond with or responders" do
250
+ context "with no response method or responders" do
172
251
  it "should not call a respond method, but copy all instance variables into the controller at the end" do
173
252
  expect(controller).not_to receive(:respond_with)
174
253
  expect(controller).not_to receive(:respond_to)
@@ -176,16 +255,21 @@ describe ClassAction::Action do
176
255
  end
177
256
  end
178
257
 
179
- context "having set a respond_with" do
258
+ context "having set a response method" do
180
259
  let(:response) { double(:response) }
181
260
  before do
182
261
  action_class.class_eval do
183
262
  respond_with :response
263
+ respond_with :invalid_response, on: :invalid
264
+
265
+ protected
266
+
267
+ def invalid?() false end
184
268
  end
185
- expect(action).to receive(:response).and_return(response)
186
269
  end
187
270
 
188
- it "should call the respond_with method and use it in the response" do
271
+ it "should use the value of #response" do
272
+ expect(action).to receive(:response).and_return(response)
189
273
  expect(controller).to receive(:respond_with).with(response) do |&blk|
190
274
  expect(blk).to be_nil
191
275
  end
@@ -193,10 +277,27 @@ describe ClassAction::Action do
193
277
  action._execute
194
278
  end
195
279
 
280
+ it "should use the value of #invalid_response if invalid? returns true" do
281
+ allow(action).to receive(:invalid?).and_return(true)
282
+ expect(action).to receive(:invalid_response).and_return(response)
283
+ expect(controller).to receive(:respond_with).with(response)
284
+ action._execute
285
+ end
286
+
287
+ it "should read an instance variable if this is set" do
288
+ action_class.class_eval do
289
+ respond_with :@response
290
+ end
291
+ action.instance_variable_set '@response', response
292
+ expect(controller).to receive(:respond_with).with(response)
293
+ action._execute
294
+ end
295
+
196
296
  it "should use the _respond_block if it is set" do
197
297
  block = proc{}
198
298
  allow(action).to receive(:_respond_block).and_return(block)
199
299
 
300
+ expect(action).to receive(:response).and_return(response)
200
301
  expect(controller).to receive(:respond_with).with(response) do |&blk|
201
302
  expect(blk).to be(block)
202
303
  end
@@ -227,18 +328,29 @@ describe ClassAction::Action do
227
328
 
228
329
  # Private method, but specced individually to make spec terser.
229
330
 
230
- let(:respond_block) { action.send(:_respond_block) }
331
+ def respond_block
332
+ action.send(:_respond_block)
333
+ end
231
334
 
232
335
  it "should create a block using the given responders, which is executed on the action" do
233
336
  called = nil; receiver = nil
234
337
  json_block = proc { receiver = self; called = :json }
235
338
  html_block = proc { receiver = self; called = :html }
236
- any_block = proc { receiver = self; called = :any }
339
+ html_invalid_block = proc { receiver = self; called = :html_invalid }
340
+ any_ok_block = proc { receiver = self; called = :any_ok }
237
341
 
238
342
  action_class.class_eval do
239
343
  respond_to :json, &json_block
240
344
  respond_to :html, &html_block
241
- respond_to_any &any_block
345
+ respond_to :html, on: :invalid, &html_invalid_block
346
+ respond_to_any on: :ok, &any_ok_block
347
+
348
+ attr_accessor :status
349
+
350
+ protected
351
+
352
+ def ok?() status == :ok end
353
+ def invalid?() status == :invalid end
242
354
  end
243
355
 
244
356
  # Simulate ActionController's format collector.
@@ -247,16 +359,77 @@ describe ClassAction::Action do
247
359
  def collector.html(&block) @html_block = block end
248
360
  def collector.any(&block) @any_block = block end
249
361
 
362
+ action.status = :ok
250
363
  respond_block.call collector
364
+ collector.json_block.call
365
+ expect(receiver).to be(action); expect(called).to be(:json)
251
366
 
367
+ action.status = :invalid
368
+ respond_block.call collector
252
369
  collector.json_block.call
253
370
  expect(receiver).to be(action); expect(called).to be(:json)
254
371
 
372
+ action.status = :ok
373
+ respond_block.call collector
255
374
  collector.html_block.call
256
375
  expect(receiver).to be(action); expect(called).to be(:html)
257
376
 
377
+ action.status = :invalid
378
+ respond_block.call collector
379
+ collector.html_block.call
380
+ expect(receiver).to be(action); expect(called).to be(:html_invalid)
381
+
382
+ action.status = :ok
383
+ respond_block.call collector
258
384
  collector.any_block.call
259
- expect(receiver).to be(action); expect(called).to be(:any)
385
+ expect(receiver).to be(action); expect(called).to be(:any_ok)
386
+
387
+ receiver = nil
388
+ called = nil
389
+
390
+ action.status = :invalid
391
+ expect(collector).not_to receive(:any)
392
+ respond_block.call collector
393
+ end
394
+
395
+ it "should copy assigns back to the controller after responding" do
396
+ action_class.class_eval do
397
+ respond_to :html do
398
+ @my_var = :value
399
+ end
400
+ end
401
+
402
+ collector = Class.new{ attr_reader :html_block }.new
403
+ def collector.html(&block) @html_block = block end
404
+ respond_block.call collector
405
+
406
+ collector.html_block.call
407
+ expect(controller.instance_variable_get('@my_var')).to be(:value)
408
+ end
409
+
410
+ it "should take responders to a subclass" do
411
+ action_class.class_eval do
412
+ respond_to :html
413
+ end
414
+ action_subclass = Class.new(action_class) do
415
+ respond_to :json
416
+ end
417
+
418
+ expect(action_subclass._responders).to eql(
419
+ [ :html, nil ] => nil,
420
+ [ :json, nil ] => nil
421
+ )
422
+ end
423
+
424
+ it "should copy the respond_with_method to a subclass" do
425
+ action_class.class_eval do
426
+ respond_with :post
427
+ end
428
+
429
+ action_subclass = Class.new(action_class)
430
+ expect(action_subclass).to have(1)._response
431
+ expect(action_subclass._responses.keys[0]).to be_nil
432
+ expect(action_subclass._responses.values[0]).to be(:post)
260
433
  end
261
434
 
262
435
  end
@@ -0,0 +1,69 @@
1
+ require 'spec_helper'
2
+ require 'class_action/rspec'
3
+
4
+ describe ClassAction::RSpec::HaveClassActionMatcher do
5
+
6
+ class ClassActionTestController < ActionController::Base
7
+ class Index < ClassAction::Action
8
+ end
9
+ end
10
+
11
+ let(:controller) { ClassActionTestController.new }
12
+
13
+ it "should fail if the controller does not support class actions" do
14
+ expect { expect(controller).to have_class_action(:index) }.to \
15
+ raise_error(
16
+ RSpec::Expectations::ExpectationNotMetError,
17
+ "expected controller of class ClassActionTestController to have class action :index, but it does not support class actions"
18
+ )
19
+ end
20
+
21
+ context "having included ClassAction" do
22
+ before { ClassActionTestController.send :include, ClassAction }
23
+
24
+ it "should fail if the controller does not have an index action" do
25
+ expect { expect(controller).to have_class_action(:index) }.to \
26
+ raise_error(
27
+ RSpec::Expectations::ExpectationNotMetError,
28
+ "expected controller of class ClassActionTestController to have class action :index"
29
+ )
30
+ end
31
+
32
+ it "should fail if the controller's index action is not a class action" do
33
+ ClassActionTestController.class_eval do
34
+ def index
35
+ end
36
+ end
37
+
38
+ expect { expect(controller).to have_class_action(:index) }.to \
39
+ raise_error(
40
+ RSpec::Expectations::ExpectationNotMetError,
41
+ "expected action ClassActionTestController#index to be a class action"
42
+ )
43
+ end
44
+
45
+ it "should pass if the index action is a class action" do
46
+ ClassActionTestController.class_eval do
47
+ class_action :index
48
+ end
49
+
50
+ expect { expect(controller).to have_class_action(:index) }.not_to raise_error
51
+ end
52
+
53
+ it "should fail if the controller's index action uses a different class" do
54
+ ClassActionTestController.class_eval do
55
+ class Index2 < ClassAction::Action
56
+ end
57
+
58
+ class_action :index2, Index2
59
+ end
60
+
61
+ expect { expect(controller).to have_class_action(:index2).using_class(ClassActionTestController::Index) }.to \
62
+ raise_error(
63
+ RSpec::Expectations::ExpectationNotMetError,
64
+ "expected action ClassActionTestController#index2 to use class ClassActionTestController::Index, but it used Index2"
65
+ )
66
+ end
67
+ end
68
+
69
+ end