class-action 0.0.1 → 1.1.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.
@@ -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