poniard 0.0.3 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 0163d066c3e20cea1fbed0c012265482f2cf119d
4
- data.tar.gz: d787a5fb866cb192c7392df5089a4ea0ea7d43f6
3
+ metadata.gz: b20dd7bc5a80523165730ffd2352ba33036988b2
4
+ data.tar.gz: 9eb61c3e74958ace02b6865bb2d6ae0ed443b3a4
5
5
  SHA512:
6
- metadata.gz: 6c9367058ed0f3f8fbf2ff11b626c3eff4f19e17142edb2c0b551f3e010bdffe6d7d738de2451acbc41567d6dc7827123e46a80aac89d4ed5d27645606007fe0
7
- data.tar.gz: c8bd0c27c1341c9d56428a62fb23169059e74ffb2f426e58be38deb96ce8d9c6bb2df0174e54083b7850ee4f9bd470d498ab49ec9eaaa3496ab1c3dd1b5953ae
6
+ metadata.gz: b5ec064ebc9ed82ca7b3dd47e1e321b062ef153f795513b86feb00d08b0f742cbae5f272dd2ddf4f2c01c2b0b62f62b57e735a8981f3f0eea43587c8eb700ec0
7
+ data.tar.gz: bb8251380ad780db82f9a15171ba543787d78a25ee4c1368beffab81070d7ea4ff235333b99b74470d413cc34ebd926ee35299f8f43729b490cfe564a03086b6
data/CHANGELOG.md ADDED
@@ -0,0 +1,23 @@
1
+ # Poniard History
2
+
3
+ ## 1.0.0 - 20 April 2014
4
+
5
+ * `respond_with` returns 406 for unknown formats.
6
+ * Remove `OpenStruct` usage and other known cache-busting constructs.
7
+ * `redirect_to` supports redirects to arbitrary locations when passed a string
8
+ parameter.
9
+ * Complete documentation of public API.
10
+
11
+ ## 0.0.3 - 19 March 2014
12
+
13
+ * Support `head` responses.
14
+
15
+ ## 0.0.2 - 26 January 2013
16
+
17
+ * Hashes can be used directly as sources.
18
+ * Provide default argument for `response.default`.
19
+
20
+ ## 0.0.1 - 25 November 2012
21
+
22
+ * Initial release.
23
+
data/README.md CHANGED
@@ -3,23 +3,23 @@ Poniard
3
3
 
4
4
  A lightweight gem that provides an alternative to Rails controllers. It uses
5
5
  parameter based dependency injection to explicitly make dependencies available,
6
- rather than mixing them all in from a base class. This allows you to properly
7
- unit test your controllers in isolation, bringing all the design benefits of
8
- TDD (as opposed to "test-first" development which is more common with the
9
- standard integration style controller tests).
6
+ rather than mixing them all in from a base class. This allows controllers to be
7
+ properly tested in isolation, bringing all the design benefits of TDD (as
8
+ opposed to "test-first" development, which is more common with the standard
9
+ integration style controller tests).
10
10
 
11
- Poniard is designed to be compatible with standard controllers. You can use it
11
+ Poniard is designed to be compatible with standard controllers. It can be used
12
12
  for your entire application, just one action, or anything in between.
13
13
 
14
14
  Example
15
15
  -------
16
16
 
17
17
  A poniard controller is slightly more verbose than the what you may be used to.
18
- In particular, you need to specify all the different dependencies you wish to
19
- use (`response` and `finder` in this example) as parameters to your method.
20
- Poniard will introspect the method before calling, and ensure that the correct
21
- values are passed. These values will for the most part be the same objects you
22
- normally deal with in Rails (`session`, `flash`, etc...).
18
+ In particular, all the dependencies of a method (`response` in the following
19
+ example) must be declared as parameters to your method. Poniard will introspect
20
+ the method before calling, and ensure that the correct values are passed. These
21
+ values will for the most part be the same objects you normally deal with in
22
+ Rails (`session`, `flash`, etc...).
23
23
 
24
24
  The following controller renders the default index template, setting the
25
25
  instance variables `@message`.
@@ -34,12 +34,12 @@ module Controller
34
34
  end
35
35
  ```
36
36
 
37
- This is more explicit than traditional controllers in two ways: passing
38
- variables to the template is done with an explicit method call rather than
39
- instance variable assignment, and dependencies that would normally be made
40
- available by a superclass are passed in as parameters to the method.
37
+ This is differs from traditional controllers in two ways: passing variables to
38
+ the template is done with an explicit method call rather than instance variable
39
+ assignment, and dependencies that would normally be made available by
40
+ a superclass are passed in as parameters to the method.
41
41
 
42
- Wiring this controller into your application is a one-liner in your normal
42
+ Wiring this controller into an application is a one-liner in the normal
43
43
  controller definition.
44
44
 
45
45
  ```ruby
@@ -50,7 +50,7 @@ class RegistrationsController < ApplicationController
50
50
  end
51
51
  ```
52
52
 
53
- You can mix and match traditional and poniard styles. Some actions can be
53
+ Traditional and poniard styles can be used together. Some actions can be
54
54
  implemented in the normal controller, others can be provided by an injectable
55
55
  one.
56
56
 
@@ -74,8 +74,8 @@ end
74
74
  ### Sources
75
75
 
76
76
  Poniard knows about all the standard controller objects such as `response`,
77
- `session` and `flash`. You then layer your own domain specific definitions on
78
- top by creating **sources**:
77
+ `session` and `flash`. Domain specific definitions are then layered on top by
78
+ creating **sources**:
79
79
 
80
80
  ```ruby
81
81
  class Source
@@ -91,7 +91,7 @@ class Source
91
91
  end
92
92
  ```
93
93
 
94
- Wire this up in the `provided_by` call:
94
+ This is wired up in the `provided_by` call:
95
95
 
96
96
  ```ruby
97
97
  provided_by Controller::Registration, sources: [
@@ -99,8 +99,8 @@ provided_by Controller::Registration, sources: [
99
99
  ]
100
100
  ```
101
101
 
102
- You can specify as many sources as you like, making it easy to reuse logic
103
- across controllers.
102
+ Any number of sources can be used, making it easy to reuse logic across
103
+ controllers.
104
104
 
105
105
  Testing
106
106
  -------
@@ -139,40 +139,23 @@ Techniques
139
139
 
140
140
  ### Built-in sources
141
141
 
142
- Undocumented, but it is a pretty straight-forward mapping to Rails objects with
143
- the exception of `response`. The code is in `lib/poniard/controller_source.rb`.
142
+ See the [YARD docs][yard] for all the built in controller sources.
143
+
144
+ [yard]: http://rubydoc.info/github/xaviershay/poniard/master/Poniard/ControllerSource/
144
145
 
145
146
  ### Layouts
146
147
 
147
- If you implement a `layout` method in your controller, it will be used to
148
- select a layout for the controller. This is equivalent to adding a custom
149
- `layout` method to a standard controller.
148
+ If a `layout` method is implemented in a controller, it will be used to select
149
+ a layout for the controller. This is equivalent to adding a custom `layout`
150
+ method to a standard controller.
150
151
 
151
152
  ### Mime types
152
153
 
153
- The Rails `respond_with` API is not very OO, so is hard to test in isolation.
154
- Poniard provides a wrapper that allows you to provide a response object that is
155
- much easier to work with.
156
-
157
- ```ruby
158
- module Controller
159
- class Registration
160
- def index(response, finder)
161
- response.respond_with RegistrationsIndexResponse, finder.all
162
- end
154
+ The Rails `respond_to` API is not very Object-Oriented, so is hard to test in
155
+ isolation. Poniard provides an alternative [`respond_with`][respond-with] that
156
+ allows you to provide a response object, which is much easier to work with.
163
157
 
164
- RegistrationsIndexResponse = Struct.new(:registrations) do
165
- def html(response)
166
- response.default registrations: registrations
167
- end
168
-
169
- def json(response)
170
- response.render json: registrations.to_json
171
- end
172
- end
173
- end
174
- end
175
- ```
158
+ [respond-with]: http://rubydoc.info/github/xaviershay/poniard/master/Poniard/ControllerSource/Response.html#respond_with-instance_method
176
159
 
177
160
  ### Authorization
178
161
 
@@ -182,19 +165,19 @@ can then be handled in a standard manner using `rescue_from`.
182
165
  ```ruby
183
166
  module Source
184
167
  class Admin
185
- def current_organiser(session)
186
- Organiser.find_by_id(session[:organiser_id])
168
+ def current_admin(session)
169
+ User.find_by_id(session[:admin_id])
187
170
  end
188
171
 
189
- def authorized_organiser(current_organiser)
190
- current_organiser || raise(ResponseException::Unauthorized)
172
+ def authorized_admin(current_admin)
173
+ current_admin || raise(ResponseException::Unauthorized)
191
174
  end
192
175
  end
193
176
  end
194
177
  ```
195
178
 
196
- This can be slightly weird if the method you are authorizing does not actually
197
- need to interact with the organiser, since it will have a method parameter that
179
+ This can be slightly weird if the method being authorized does not actually
180
+ need to interact with the admin, since it will have a method parameter that
198
181
  is never used.
199
182
 
200
183
  ```ruby
@@ -209,7 +192,7 @@ def instance_method(name)
209
192
  end
210
193
 
211
194
  it 'requires authorization' do
212
- instance_method(:index).should have_param(:authorized_organiser)
195
+ instance_method(:index).should have_param(:authorized_admin)
213
196
  end
214
197
  ```
215
198
 
@@ -218,15 +201,12 @@ Developing
218
201
 
219
202
  ### Status
220
203
 
221
- Experimental. I've backported an existing app, added minor new features, and it
222
- was a pleasant experience. It needs a lot more usage before the API stabilizes,
223
- or it is even proved to be useful.
204
+ Not widely used. May be some obvious things missing from built-in controller
205
+ sources that you will have to add.
224
206
 
225
207
  ### Compatibility
226
208
 
227
- Requires 1.9, should be easy to backport to 1.8 if anyone is interested. Use
228
- 1.9 style hashes and probably relies on `methods` calls returning symbols
229
- rather than strings.
209
+ Requires 1.9 or above.
230
210
 
231
211
  ### Support
232
212
 
@@ -1,17 +1,30 @@
1
1
  module Poniard
2
+ # Mixing this module into a Rails controller provides the poniard DSL to that
3
+ # controller. To enable poniard on all controllers, mix this in to
4
+ # `ApplicationController`.
2
5
  module Controller
6
+ # @private
3
7
  def self.included(klass)
4
8
  klass.extend(ClassMethods)
5
9
  end
6
10
 
11
+ # Call the given method via the poniard injector. A `ControllerSource` is
12
+ # provided as default, along with any sources passed to `provided_by`.
7
13
  def inject(method)
8
14
  injector = Injector.new [
9
15
  ControllerSource.new(self)
10
16
  ] + self.class.sources.map(&:new)
11
- injector.dispatch self.class.provided_by.new.method(method)
17
+ injector.eager_dispatch self.class.provided_by.new.method(method)
12
18
  end
13
19
 
20
+ # Class methods that are automatically added when `Controller` is included.
14
21
  module ClassMethods
22
+ # For every non-inherited public instance method on the given class,
23
+ # generates a method of the same name that calls it via the injector.
24
+ #
25
+ # If a `layout` method is present on the class, it is given special
26
+ # treatment and set up so that it will be called using the Rails `layout`
27
+ # DSL method.
15
28
  def provided_by(klass = nil, opts = {})
16
29
  if klass
17
30
  methods = klass.public_instance_methods(false)
@@ -43,6 +56,10 @@ module Poniard
43
56
  end
44
57
  end
45
58
 
59
+ # An array of sources to be used for all injected methods on the host
60
+ # class. This is typically specified using the `sources` option to
61
+ # `provided_by`, however you can override it for more complicated dynamic
62
+ # behaviour.
46
63
  def sources
47
64
  @sources
48
65
  end
@@ -1,26 +1,74 @@
1
1
  module Poniard
2
+ # Poniard source providing access to Rails controller features. It is always
3
+ # available to poniard controllers added using
4
+ # {Poniard::Controller::ClassMethods#provided_by}.
2
5
  class ControllerSource
3
- Response = Struct.new(:controller, :injector) do
6
+ # A wrapper around the Rails response that provides a more OO-friendly
7
+ # interface, specifically designed with isolated unit testing in mind.
8
+ #
9
+ # This class should not be explictly constructed by users. It is provided
10
+ # in the response parameter provided by {ControllerSource}.
11
+ class Response
12
+ # @private
13
+ attr_reader :controller, :injector
14
+
15
+ # @private
16
+ def initialize(controller, injector)
17
+ @controller = controller
18
+ @injector = injector
19
+ end
20
+
21
+ # Calls the given route method with arguments, then redirects to the
22
+ # result. +_path+ is automatically appended so does not need to be
23
+ # included in the path. +_url+ suffixes are handled correctly, in that
24
+ # they are used as is without adding a +_path+ suffix. That is a little
25
+ # magic, justified by +_path+ being what you want 90% of the time.
26
+ #
27
+ # If +path+ is a string, redirect to it as is without calling any route
28
+ # helpers.
29
+ #
30
+ # @example
31
+ # def index(response)
32
+ # response.redirect_to :user, 1 # user_path(1)
33
+ # end
4
34
  def redirect_to(path, *args)
5
- unless path.to_s.ends_with?('_url')
6
- path = "#{path}_path"
35
+ case path
36
+ when Symbol
37
+ unless path.to_s.ends_with?('_url')
38
+ path = "#{path}_path"
39
+ end
40
+
41
+ controller.redirect_to(controller.send(path, *args))
42
+ else
43
+ controller.redirect_to path, *args
7
44
  end
8
-
9
- controller.redirect_to(controller.send(path, *args))
10
45
  end
11
46
 
47
+ # Redirect to the given action on the current controller.
12
48
  def redirect_to_action(action)
13
49
  controller.redirect_to action: action
14
50
  end
15
51
 
52
+ # Render the view associated with given action with the given instance
53
+ # variables.
54
+ #
55
+ # @param action[Symbol/String] name of the action
56
+ # @param ivars[Hash] instance variables to be set for view rendering
16
57
  def render_action(action, ivars = {})
17
58
  render action: action, ivars: ivars
18
59
  end
19
60
 
61
+ # Delegates directly to {ActionController::Head#head}.
20
62
  def head(*args)
21
63
  controller.head *args
22
64
  end
23
65
 
66
+ # Delegates to `ActionController::Base#render`, with two exceptions if
67
+ # the last argument is a hash:
68
+ #
69
+ # * +ivars+ is deleted from the hash and used to set instance variables
70
+ # that can then be accessed by any templates.
71
+ # * +headers+ is deleted from the hash and used to set response headers.
24
72
  def render(*args)
25
73
  opts = args.last
26
74
  if opts.is_a?(Hash)
@@ -41,43 +89,91 @@ module Poniard
41
89
  controller.render *args
42
90
  end
43
91
 
92
+ # Renders default template for the current action. Equivalent to not call
93
+ # any `render` method in a normal Rails controller. Unlike Rails, poniard
94
+ # does not support empty method bodies.
95
+ #
96
+ # @param ivars[Hash] instance variables to be set for view rendering
44
97
  def default(ivars = {})
45
98
  ivars.each do |name, val|
46
99
  controller.instance_variable_set("@#{name}", val)
47
100
  end
48
101
  end
49
102
 
103
+ # Object-oriented replacement for
104
+ # {ActionController::MimeResponds#respond_to} method. The given class is
105
+ # instantiated with the remaining arguments, and is expected to implement
106
+ # a method named after each format it supports. The methods are called
107
+ # via the injector.
108
+ #
109
+ # If the requested format is not implemented, a 406 "Not Acceptable"
110
+ # response is returned.
111
+ #
112
+ # @example
113
+ # class MyController
114
+ # IndexResponse = Struct.new(:results) do
115
+ # def html(response)
116
+ # response.default results: results
117
+ # end
118
+ #
119
+ # def json(response)
120
+ # response.render json: results
121
+ # end
122
+ # end
123
+ #
124
+ # def index(response)
125
+ # response.respond_with IndexResponse, Things.all
126
+ # end
127
+ # end
50
128
  def respond_with(klass, *args)
51
129
  obj = klass.new(*args)
52
130
  format = controller.request.format.symbol
53
131
  if obj.respond_to?(format)
54
132
  injector.dispatch obj.method(format)
133
+ else
134
+ head(406) # Not acceptable
55
135
  end
56
136
  end
57
137
 
138
+ # Delegates directly to {ActionController::DataStreaming#send_data}.
58
139
  def send_data(*args)
59
140
  controller.send_data(*args)
60
141
  end
61
142
  end
62
143
 
144
+ # @private
63
145
  def initialize(controller)
64
146
  @controller = controller
65
147
  end
66
148
 
149
+ # Provides direct access to +request+.
67
150
  def request; @controller.request; end
151
+
152
+ # Provides direct access to +params+.
68
153
  def params; @controller.params; end
154
+
155
+ # Provides direct access to +session+.
69
156
  def session; @controller.session; end
157
+
158
+ # Provides direct access to +flash+.
70
159
  def flash; @controller.flash; end
160
+
161
+ # Provides access to +flash.now+. It is useful, particularly when testing,
162
+ # to treat this as a completely separate concept from +flash+.
71
163
  def now_flash; @controller.flash.now; end
72
164
 
165
+ # Provides access to +render+ and friends abstracted behind {Response a
166
+ # nice OO interface}.
73
167
  def response(injector)
74
168
  Response.new(@controller, injector)
75
169
  end
76
170
 
171
+ # Provides direct access to +Rails.env+.
77
172
  def env
78
173
  Rails.env
79
174
  end
80
175
 
176
+ # Provides direct access to +Rails.application.config+.
81
177
  def app_config
82
178
  Rails.application.config
83
179
  end
@@ -1,52 +1,113 @@
1
- require 'ostruct'
2
-
3
1
  module Poniard
2
+ # A parameter pased dependency injector. Figures out which arguments to call
3
+ # a method with based on the names of method's parameters. Multiple sources
4
+ # can be provided to lookup parameter values. They are checked in reverse
5
+ # order.
6
+ #
7
+ # The injector itself is always available to methods via the +injector+
8
+ # parameter.
9
+ #
10
+ # @example
11
+ # def my_method(printer)
12
+ # printer.("hello!")
13
+ # end
14
+ #
15
+ # Injector.new([{
16
+ # printer: ->(msg) { puts msg }
17
+ # }]).dispatch(method(:my_method))
4
18
  class Injector
5
19
  attr_reader :sources
6
20
 
7
21
  def initialize(sources = [])
8
22
  @sources = sources.map {|source|
9
23
  if source.is_a? Hash
10
- OpenStruct.new(source)
24
+ HashSource.new(source)
11
25
  else
12
- source
26
+ ObjectSource.new(self, source)
13
27
  end
14
- }+ [self]
28
+ } + [HashSource.new(injector: self)]
15
29
  end
16
30
 
31
+ # Call the given method with arguments. If a parameter is not provided by
32
+ # any source, an {UnknownInjectable} instance is passed instead.
17
33
  def dispatch(method, overrides = {})
34
+ dispatch_method(method, UnknownInjectable.method(:new), overrides)
35
+ end
36
+
37
+ # Same as {#dispatch}, except it raises an exception immediately if any
38
+ # parameter is not provided by sources.
39
+ def eager_dispatch(method, overrides = {})
40
+ dispatch_method(method, ->(name) {
41
+ ::Kernel.raise UnknownParam, name
42
+ }, overrides)
43
+ end
44
+
45
+ private
46
+
47
+ def dispatch_method(method, unknown_param_f, overrides = {})
18
48
  args = method.parameters.map {|_, name|
19
49
  source = sources_for(overrides).detect {|source|
20
- source.respond_to?(name)
50
+ source.provides?(name)
21
51
  }
22
52
 
23
53
  if source
24
- dispatch(source.method(name), overrides)
54
+ source.dispatch(name, overrides)
25
55
  else
26
- UnknownInjectable.new(name)
56
+ unknown_param_f.(name)
27
57
  end
28
58
  }
29
59
  method.(*args)
30
60
  end
31
61
 
32
- def injector
33
- self
62
+ def sources_for(overrides)
63
+ [HashSource.new(overrides)] + sources
34
64
  end
65
+ end
35
66
 
36
- private
67
+ # @private
68
+ class HashSource
69
+ def initialize(hash)
70
+ @hash = hash
71
+ end
37
72
 
38
- def sources_for(overrides)
39
- [OpenStruct.new(overrides)] + sources
73
+ def provides?(name)
74
+ @hash.has_key?(name)
75
+ end
76
+
77
+ def dispatch(name, _)
78
+ @hash.fetch(name)
79
+ end
80
+ end
81
+
82
+ # @private
83
+ class ObjectSource
84
+ def initialize(injector, object)
85
+ @injector = injector
86
+ @object = object
87
+ end
88
+
89
+ def provides?(name)
90
+ @object.respond_to?(name)
91
+ end
92
+
93
+ def dispatch(name, overrides)
94
+ @injector.dispatch(@object.method(name), overrides)
40
95
  end
41
96
  end
42
97
 
98
+ # Raised by {Injector#eager_dispatch} if a parameter is not provided by any
99
+ # sources.
43
100
  class UnknownParam < RuntimeError; end
44
101
 
102
+ # An object that will raise an exception if any method is called on it. This
103
+ # is particularly useful in testing so that you only need to inject the
104
+ # parameters that should actually be called during the test.
45
105
  class UnknownInjectable < BasicObject
46
106
  def initialize(name)
47
107
  @name = name
48
108
  end
49
109
 
110
+ # @private
50
111
  def method_missing(*args)
51
112
  ::Kernel.raise UnknownParam,
52
113
  "Tried to call method on an uninjected param: #{@name}"
@@ -1,3 +1,5 @@
1
+ # Top-level namespace for Poniard. See +README.md+ for library description.
1
2
  module Poniard
2
- VERSION = '0.0.3'
3
+ # Current version of Poniard, in semantic versioning format.
4
+ VERSION = '1.0.0'
3
5
  end
data/poniard.gemspec CHANGED
@@ -14,7 +14,7 @@ Gem::Specification.new do |gem|
14
14
  gem.required_ruby_version = '>= 1.9.0'
15
15
  gem.files = Dir.glob("{spec,lib}/**/*.rb") + %w(
16
16
  README.md
17
- HISTORY.md
17
+ CHANGELOG.md
18
18
  LICENSE
19
19
  poniard.gemspec
20
20
  )
@@ -24,5 +24,4 @@ Gem::Specification.new do |gem|
24
24
  gem.version = Poniard::VERSION
25
25
  gem.has_rdoc = false
26
26
  gem.add_development_dependency 'rspec', '~> 2.11'
27
- gem.add_development_dependency 'rake'
28
27
  end
@@ -0,0 +1,294 @@
1
+ require 'integration_helper'
2
+
3
+ describe Poniard::ControllerSource, type: :controller do
4
+ describe 'Response' do
5
+ context '#default' do
6
+ render_views
7
+
8
+ poniard_controller do
9
+ def index(response)
10
+ response.default message: "implicit"
11
+ end
12
+ end
13
+
14
+ it 'renders template matching method name with instance variables' do
15
+ get :index
16
+ expect(response.body).to match(/Message: implicit/)
17
+ end
18
+ end
19
+
20
+ context '#render' do
21
+ render_views
22
+
23
+ poniard_controller do
24
+ def index(response)
25
+ response.render action: 'index',
26
+ headers: {'Content-Type' => 'text/plain'},
27
+ ivars: {message: 'explicit'}
28
+ end
29
+ end
30
+
31
+ it 'renders explicit template with instance variables' do
32
+ get :index
33
+ expect(response.body).to match(/Message: explicit/)
34
+ end
35
+
36
+ it 'can set headers' do
37
+ get :index
38
+ expect(response.headers['Content-Type']).to eq('text/plain')
39
+ end
40
+ end
41
+
42
+ context '#render_action' do
43
+ render_views
44
+
45
+ poniard_controller do
46
+ def index(response)
47
+ response.render_action 'index', message: 'explicit'
48
+ end
49
+ end
50
+
51
+ it 'renders explicit template with instance variables' do
52
+ get :index
53
+ expect(response.body).to match(/Message: explicit/)
54
+ end
55
+ end
56
+
57
+ describe '#head' do
58
+ poniard_controller do
59
+ def index(response)
60
+ response.head 422
61
+ end
62
+ end
63
+
64
+ it 'sets response code' do
65
+ get :index
66
+ expect(response.status).to eq(422)
67
+ end
68
+ end
69
+
70
+ describe '#redirect_to' do
71
+ # See fake admin_* route methods in integration_helper.rb
72
+
73
+ context 'path' do
74
+ poniard_controller do
75
+ def index(response)
76
+ response.redirect_to :admin, :secret
77
+ end
78
+ end
79
+
80
+ it 'redirects' do
81
+ get :index
82
+ expect(response).to redirect_to("/admin/secret")
83
+ end
84
+ end
85
+
86
+ context 'url' do
87
+ poniard_controller do
88
+ def index(response)
89
+ response.redirect_to :admin_url, :secret
90
+ end
91
+ end
92
+
93
+ it 'redirects' do
94
+ get :index
95
+ expect(response).to redirect_to("http://foo/admin/secret")
96
+ end
97
+ end
98
+
99
+ context 'arbitrary' do
100
+ poniard_controller do
101
+ def index(response)
102
+ response.redirect_to '/some_path'
103
+ end
104
+ end
105
+
106
+ it 'redirects' do
107
+ get :index
108
+ expect(response).to redirect_to("/some_path")
109
+ end
110
+ end
111
+ end
112
+
113
+ context '#redirect_to_action' do
114
+ poniard_controller do
115
+ def index(response)
116
+ response.redirect_to_action :new
117
+ end
118
+
119
+ def new
120
+ end
121
+ end
122
+
123
+ it 'redirects' do
124
+ pending "Not sure correct rspec-rails scaffolding to make this work"
125
+ get :index
126
+ expect(response).to redirect_to(:new)
127
+ end
128
+ end
129
+
130
+ describe '#send_data' do
131
+ poniard_controller do
132
+ def index(response)
133
+ response.send_data "data", type: "text/plain"
134
+ end
135
+ end
136
+
137
+ it 'sends body' do
138
+ get :index
139
+ expect(response.body).to eq("data")
140
+ end
141
+
142
+ it 'sets headers' do
143
+ get :index
144
+ expect(response.headers['Content-Type']).to eq("text/plain")
145
+ end
146
+ end
147
+
148
+ describe '#respond_with' do
149
+ render_views
150
+
151
+ poniard_controller do
152
+ def index(response)
153
+ responder = Class.new do
154
+ def initialize(body)
155
+ @body = body
156
+ end
157
+
158
+ def html(response); response.render text: "<b>#{@body}</b>" end
159
+ def text(response); response.render text: @body end
160
+ end
161
+
162
+ response.respond_with responder, "body"
163
+ end
164
+ end
165
+
166
+ it 'responds with html' do
167
+ get :index
168
+ expect(response.body).to eq("<b>body</b>")
169
+ end
170
+
171
+ it 'responds with text' do
172
+ get :index, format: :text
173
+ expect(response.body).to eq("body")
174
+ end
175
+
176
+ it 'handles unknown format' do
177
+ get :index, format: :json
178
+ expect(response.status).to eq(406)
179
+ end
180
+ end
181
+ end
182
+
183
+ context 'request source' do
184
+ poniard_controller do
185
+ def index(response, request)
186
+ response.render text: request.path
187
+ end
188
+ end
189
+
190
+ it 'provides access to raw rails request' do
191
+ get :index
192
+ expect(response.body).to eq("/anonymous")
193
+ end
194
+ end
195
+
196
+ context 'params source' do
197
+ poniard_controller do
198
+ def index(response, params)
199
+ response.render text: params[:q]
200
+ end
201
+ end
202
+
203
+ it 'provides access to raw rails params' do
204
+ get :index, q: 'query'
205
+ expect(response.body).to eq("query")
206
+ end
207
+ end
208
+
209
+ context 'session source' do
210
+ poniard_controller do
211
+ def index(response, session)
212
+ response.render text: session[:user_id]
213
+ end
214
+ end
215
+
216
+ it 'provides access to raw rails session' do
217
+ get :index, {}, {user_id: '1'}
218
+ expect(response.body).to eq("1")
219
+ end
220
+ end
221
+
222
+ context 'flash source' do
223
+ poniard_controller do
224
+ def index(response, flash)
225
+ flash[:message] = "FLASH"
226
+ response.render text: ""
227
+ end
228
+ end
229
+
230
+ it 'provides access to raw rails flash' do
231
+ get :index
232
+ expect(flash[:message]).to eq("FLASH")
233
+ end
234
+ end
235
+
236
+ context 'flash now source' do
237
+ poniard_controller do
238
+ def index(response, now_flash)
239
+ now_flash[:message] = "FLASH"
240
+ response.render text: ""
241
+ end
242
+ end
243
+
244
+ it 'provides access to raw rails flash' do
245
+ get :index
246
+ expect(flash[:message]).to eq("FLASH")
247
+ end
248
+ end
249
+
250
+ context 'env source' do
251
+ poniard_controller do
252
+ def index(response, env)
253
+ response.render text: env
254
+ end
255
+ end
256
+
257
+ it 'provides access to current environment' do
258
+ get :index
259
+ expect(response.body).to eq("test")
260
+ end
261
+ end
262
+
263
+ context 'config source' do
264
+ poniard_controller do
265
+ def index(response, app_config)
266
+ response.render text: app_config.secret_token
267
+ end
268
+ end
269
+
270
+ it 'provides access to current config' do
271
+ get :index
272
+ expect(response.body).to eq(PoniardApp.config.secret_token)
273
+ end
274
+ end
275
+
276
+ context 'with layout' do
277
+ render_views
278
+
279
+ poniard_controller do
280
+ def layout
281
+ 'admin'
282
+ end
283
+
284
+ def index(response, app_config)
285
+ response.render text: "secret", layout: true
286
+ end
287
+ end
288
+
289
+ it 'uses the provided layout' do
290
+ get :index
291
+ expect(response.body).to match(/ADMIN: secret/)
292
+ end
293
+ end
294
+ end
@@ -1,3 +1,5 @@
1
+ require 'spec_helper'
2
+
1
3
  require 'poniard/injector'
2
4
 
3
5
  describe Poniard::Injector do
@@ -66,6 +68,13 @@ describe Poniard::Injector do
66
68
  called.should == injector
67
69
  end
68
70
 
71
+ it 'allows nil values in hash sources' do
72
+ value = nil
73
+ injector = described_class.new
74
+ injector.dispatch ->(x) { value = x.nil? }, x: nil
75
+ value.should == true
76
+ end
77
+
69
78
  it 'yields a fail object when source is unknown' do
70
79
  called = false
71
80
  m = ->(unknown) {
@@ -80,4 +89,13 @@ describe Poniard::Injector do
80
89
  described_class.new.dispatch(m)
81
90
  called.should be_true
82
91
  end
92
+
93
+ describe '#eager_dispatch' do
94
+ it 'raises when source is unknown' do
95
+ m = ->(unknown) {}
96
+ expect {
97
+ described_class.new.eager_dispatch(m)
98
+ }.to raise_error(Poniard::UnknownParam, "unknown")
99
+ end
100
+ end
83
101
  end
@@ -0,0 +1,44 @@
1
+ ENV['RAILS_ENV'] = 'test'
2
+
3
+ require 'spec_helper'
4
+
5
+ require 'action_controller/railtie'
6
+ require 'rspec/rails'
7
+
8
+ require 'poniard'
9
+
10
+ Rails.backtrace_cleaner.remove_silencers!
11
+ class PoniardApp < Rails::Application
12
+ config.secret_token = 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'
13
+ config.session_store :cookie_store, :key => '_myproject_session'
14
+ config.active_support.deprecation = :log
15
+ config.eager_load = false
16
+ config.root = File.dirname(__FILE__)
17
+ end
18
+ PoniardApp.initialize!
19
+
20
+ class ApplicationController < ActionController::Base
21
+ before_filter :prepend_view_paths
22
+
23
+ def prepend_view_paths
24
+ prepend_view_path File.expand_path("../views", __FILE__)
25
+ end
26
+
27
+ def admin_path(page)
28
+ "/admin/#{page}"
29
+ end
30
+
31
+ def admin_url(page)
32
+ "http://foo/admin/#{page}"
33
+ end
34
+ end
35
+
36
+ RSpec.configure do |config|
37
+ def poniard_controller(&block)
38
+ controller(ApplicationController) do
39
+ include Poniard::Controller
40
+
41
+ provided_by(Class.new(&block))
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,3 @@
1
+ require 'simplecov'
2
+
3
+ SimpleCov.start
metadata CHANGED
@@ -1,43 +1,29 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: poniard
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.3
4
+ version: 1.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Xavier Shay
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-03-20 00:00:00.000000000 Z
11
+ date: 2014-04-20 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rspec
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - ~>
17
+ - - "~>"
18
18
  - !ruby/object:Gem::Version
19
19
  version: '2.11'
20
20
  type: :development
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
- - - ~>
24
+ - - "~>"
25
25
  - !ruby/object:Gem::Version
26
26
  version: '2.11'
27
- - !ruby/object:Gem::Dependency
28
- name: rake
29
- requirement: !ruby/object:Gem::Requirement
30
- requirements:
31
- - - '>='
32
- - !ruby/object:Gem::Version
33
- version: '0'
34
- type: :development
35
- prerelease: false
36
- version_requirements: !ruby/object:Gem::Requirement
37
- requirements:
38
- - - '>='
39
- - !ruby/object:Gem::Version
40
- version: '0'
41
27
  description: A dependency injector for Rails, allows you to write clean controllers.
42
28
  email:
43
29
  - contact@xaviershay.com
@@ -45,16 +31,19 @@ executables: []
45
31
  extensions: []
46
32
  extra_rdoc_files: []
47
33
  files:
48
- - spec/injector_spec.rb
34
+ - CHANGELOG.md
35
+ - LICENSE
36
+ - README.md
37
+ - lib/poniard.rb
49
38
  - lib/poniard/controller.rb
50
39
  - lib/poniard/controller_source.rb
51
40
  - lib/poniard/injector.rb
52
41
  - lib/poniard/version.rb
53
- - lib/poniard.rb
54
- - README.md
55
- - HISTORY.md
56
- - LICENSE
57
42
  - poniard.gemspec
43
+ - spec/controller_source_spec.rb
44
+ - spec/injector_spec.rb
45
+ - spec/integration_helper.rb
46
+ - spec/spec_helper.rb
58
47
  homepage: http://github.com/xaviershay/poniard
59
48
  licenses: []
60
49
  metadata: {}
@@ -64,20 +53,23 @@ require_paths:
64
53
  - lib
65
54
  required_ruby_version: !ruby/object:Gem::Requirement
66
55
  requirements:
67
- - - '>='
56
+ - - ">="
68
57
  - !ruby/object:Gem::Version
69
58
  version: 1.9.0
70
59
  required_rubygems_version: !ruby/object:Gem::Requirement
71
60
  requirements:
72
- - - '>='
61
+ - - ">="
73
62
  - !ruby/object:Gem::Version
74
63
  version: '0'
75
64
  requirements: []
76
65
  rubyforge_project:
77
- rubygems_version: 2.0.3
66
+ rubygems_version: 2.2.0
78
67
  signing_key:
79
68
  specification_version: 4
80
69
  summary: A dependency injector for Rails, allows you to write clean controllers.
81
70
  test_files:
71
+ - spec/controller_source_spec.rb
82
72
  - spec/injector_spec.rb
73
+ - spec/integration_helper.rb
74
+ - spec/spec_helper.rb
83
75
  has_rdoc: false
data/HISTORY.md DELETED
@@ -1,15 +0,0 @@
1
- # Poniard History
2
-
3
- ## 0.0.3 - 19 March 2014
4
-
5
- * Support `head` responses.
6
-
7
- ## 0.0.2 - 26 January 2013
8
-
9
- * Hashes can be used directly as sources.
10
- * Provide default argument for `response.default`.
11
-
12
- ## 0.0.1 - 25 November 2012
13
-
14
- * Initial release.
15
-