objectify 0.1.1 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +63 -10
- data/lib/objectify/config/action.rb +3 -2
- data/lib/objectify/config/context.rb +7 -1
- data/lib/objectify/config/injectables.rb +9 -0
- data/lib/objectify/injector.rb +23 -2
- data/lib/objectify/rails/controller.rb +57 -6
- data/lib/objectify/rails/routes.rb +7 -0
- data/lib/objectify/rails/routing.rb +36 -8
- data/lib/objectify/route.rb +1 -1
- data/lib/objectify/version.rb +1 -1
- data/spec/config/action_spec.rb +7 -3
- data/spec/config/context_spec.rb +5 -2
- data/spec/config/injectables_spec.rb +8 -0
- data/spec/injector_spec.rb +37 -1
- data/spec/rails/routing_spec.rb +90 -7
- data/spec/route_spec.rb +2 -2
- data/thoughts/responders.md +156 -0
- metadata +18 -16
data/README.md
CHANGED
@@ -37,10 +37,8 @@ Objectify has two primary components:
|
|
37
37
|
|
38
38
|
```ruby
|
39
39
|
class UnauthenticatedResponder
|
40
|
-
|
41
|
-
|
42
|
-
def call(controller, renderer)
|
43
|
-
renderer.redirect_to controller.login_url
|
40
|
+
def call(format, routes)
|
41
|
+
format.any { redirect_to routes.login_url }
|
44
42
|
end
|
45
43
|
end
|
46
44
|
```
|
@@ -73,14 +71,14 @@ Objectify has two primary components:
|
|
73
71
|
```ruby
|
74
72
|
class PicturesCreateResponder
|
75
73
|
# service_result is exactly what it sounds like
|
76
|
-
def call(service_result,
|
74
|
+
def call(service_result, format)
|
77
75
|
if service_result.persisted?
|
78
|
-
|
76
|
+
format.any { redirect_to service_result }
|
79
77
|
else
|
80
|
-
#
|
81
|
-
#
|
82
|
-
|
83
|
-
|
78
|
+
# the service_result is always the only thing passed to the view
|
79
|
+
# (hint: use a presenter)
|
80
|
+
# you can access it with the `objectify_data` helper.
|
81
|
+
format.any { render :template => "pictures/new.html.erb" }
|
84
82
|
end
|
85
83
|
end
|
86
84
|
end
|
@@ -229,6 +227,61 @@ describe "CurrentUserResolver" do
|
|
229
227
|
end
|
230
228
|
```
|
231
229
|
|
230
|
+
### Decorators
|
231
|
+
|
232
|
+
Decorators are a great way to create truly modular and composable software. Here's a great example.
|
233
|
+
|
234
|
+
In objectify\_auth, there's a SessionsCreateService that you can use as the basis for the creat action in your /sessions resource. By default, it does very little:
|
235
|
+
|
236
|
+
```ruby
|
237
|
+
class SessionsCreateService
|
238
|
+
def initialize(authenticator, session_creator)
|
239
|
+
@authenticator = authenticator
|
240
|
+
@session_creator = session_creator
|
241
|
+
end
|
242
|
+
|
243
|
+
def call(params, session)
|
244
|
+
@authenticator.call(params[:email], params[:password]).tap do |user|
|
245
|
+
@session_creator.call(session) if user
|
246
|
+
end
|
247
|
+
end
|
248
|
+
end
|
249
|
+
```
|
250
|
+
|
251
|
+
Let's say we wanted to add remember token issuance. We could rewrite the entire SessionsCreationService or extend (with inheritance) it to do that, but then we'd have to retest the whole unit again. A decorator allows us to avoid that:
|
252
|
+
|
253
|
+
```ruby
|
254
|
+
class SessionsCreateServiceWithRememberToken
|
255
|
+
def initialize(sessions_create_service, remember_token_generator)
|
256
|
+
@sessions_create_service = sessions_create_service
|
257
|
+
@remember_token_generator = remember_token_generator
|
258
|
+
end
|
259
|
+
|
260
|
+
def call(params, session, cookies)
|
261
|
+
@sessions_create_service.call(params, session).tap do |user|
|
262
|
+
if user
|
263
|
+
token = @remember_token_generator.call(user)
|
264
|
+
cookies[:remember_token] = { ... }
|
265
|
+
end
|
266
|
+
end
|
267
|
+
end
|
268
|
+
end
|
269
|
+
```
|
270
|
+
|
271
|
+
This makes for a very simple and easy to test extension to our SessionsCreateService. We can tell objectify to use this decorator like this:
|
272
|
+
|
273
|
+
```ruby
|
274
|
+
# config/routes.rb
|
275
|
+
objectify.decorators :sessions_create_service => :sessions_create_service_with_remember_token
|
276
|
+
```
|
277
|
+
|
278
|
+
If we wanted to specify additional decorators, it'd look like this:
|
279
|
+
|
280
|
+
```ruby
|
281
|
+
# config/routes.rb
|
282
|
+
objectify.decorators :sessions_create_service => [:sessions_create_service_with_remember_token, :sessions_create_service_with_captcha_verification]
|
283
|
+
```
|
284
|
+
|
232
285
|
## Views
|
233
286
|
|
234
287
|
Objectify has two major impacts on your views.
|
@@ -6,9 +6,10 @@ module Objectify
|
|
6
6
|
class Action
|
7
7
|
attr_reader :resource_name, :name, :route, :policies, :service, :responder
|
8
8
|
|
9
|
-
def initialize(
|
9
|
+
def initialize(routing_opts,
|
10
|
+
resource_name, name, options, default_policies,
|
10
11
|
route_factory = Route)
|
11
|
-
@route = route_factory.new(
|
12
|
+
@route = route_factory.new(routing_opts)
|
12
13
|
@resource_name = resource_name
|
13
14
|
@name = name
|
14
15
|
@policies = default_policies.merge(options, options[name])
|
@@ -57,7 +57,7 @@ module Objectify
|
|
57
57
|
|
58
58
|
def legacy_action(route)
|
59
59
|
actions[route] ||
|
60
|
-
@action_factory.new(route.
|
60
|
+
@action_factory.new(route.opts, route.opts[:controller], route.opts[:action], {}, policies)
|
61
61
|
end
|
62
62
|
|
63
63
|
def injector
|
@@ -86,6 +86,12 @@ module Objectify
|
|
86
86
|
end
|
87
87
|
end
|
88
88
|
|
89
|
+
def append_decorators(opts)
|
90
|
+
opts.each do |k,v|
|
91
|
+
injectables.add_decorators(k, v)
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
89
95
|
def instantiator
|
90
96
|
@instantiator ||= Instantiator.new(injector)
|
91
97
|
end
|
@@ -6,6 +6,7 @@ module Objectify
|
|
6
6
|
|
7
7
|
def initialize(config = {})
|
8
8
|
@config = config
|
9
|
+
@decorators = {}
|
9
10
|
end
|
10
11
|
|
11
12
|
def add_resolver(name, value)
|
@@ -24,6 +25,14 @@ module Objectify
|
|
24
25
|
@config[name] || context[name] || [:unknown, name]
|
25
26
|
end
|
26
27
|
|
28
|
+
def add_decorators(name, value)
|
29
|
+
@decorators[name] = value
|
30
|
+
end
|
31
|
+
|
32
|
+
def decorators(name)
|
33
|
+
[*@decorators[name]].compact
|
34
|
+
end
|
35
|
+
|
27
36
|
def context
|
28
37
|
@context && @context.config || {}
|
29
38
|
end
|
data/lib/objectify/injector.rb
CHANGED
@@ -8,6 +8,7 @@ module Objectify
|
|
8
8
|
|
9
9
|
def initialize(config)
|
10
10
|
@config = config
|
11
|
+
@decoration_context = {}
|
11
12
|
end
|
12
13
|
|
13
14
|
def call(object, method)
|
@@ -15,7 +16,7 @@ module Objectify
|
|
15
16
|
instrument("inject.objectify", payload) do |payload|
|
16
17
|
method_obj = method_object(object, method)
|
17
18
|
injectables = method_obj.parameters.map do |reqd, name|
|
18
|
-
@config.get(name) if reqd == :req
|
19
|
+
@decoration_context[name] || @config.get(name) if reqd == :req
|
19
20
|
end.compact
|
20
21
|
arguments = injectables.map do |type, value|
|
21
22
|
if type == :unknown
|
@@ -39,7 +40,19 @@ module Objectify
|
|
39
40
|
payload[:injectables] = injectables
|
40
41
|
payload[:arguments] = arguments
|
41
42
|
|
42
|
-
object.send(method, *arguments)
|
43
|
+
result = object.send(method, *arguments)
|
44
|
+
if method == :new
|
45
|
+
base_name = object.name.underscore.to_sym
|
46
|
+
add_decoration_context(base_name, result)
|
47
|
+
result = @config.decorators(base_name).inject(result) do |memo, decorator|
|
48
|
+
call(decorator.to_s.classify.constantize, :new).tap do |decorated|
|
49
|
+
add_decoration_context(base_name, decorated)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
clear_decoration_context
|
53
|
+
end
|
54
|
+
|
55
|
+
result
|
43
56
|
end
|
44
57
|
end
|
45
58
|
|
@@ -63,5 +76,13 @@ module Objectify
|
|
63
76
|
|
64
77
|
raise ArgumentError, "Can't figure out how to inject #{value}."
|
65
78
|
end
|
79
|
+
|
80
|
+
def add_decoration_context(name, object)
|
81
|
+
@decoration_context[name] = [:value, object]
|
82
|
+
end
|
83
|
+
|
84
|
+
def clear_decoration_context
|
85
|
+
@decoration_context.clear
|
86
|
+
end
|
66
87
|
end
|
67
88
|
end
|
@@ -3,6 +3,7 @@ require "objectify/executor"
|
|
3
3
|
require "objectify/policy_chain_executor"
|
4
4
|
require "objectify/instrumentation"
|
5
5
|
require "objectify/rails/renderer"
|
6
|
+
require "objectify/rails/routes"
|
6
7
|
|
7
8
|
module Objectify
|
8
9
|
module Rails
|
@@ -20,6 +21,12 @@ module Objectify
|
|
20
21
|
objectify.injector
|
21
22
|
end
|
22
23
|
|
24
|
+
def objectify_routes
|
25
|
+
@objectify_routes ||= Routes.new.tap do |routes|
|
26
|
+
routes.default_url_options = url_options
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
23
30
|
def request_injectables_context
|
24
31
|
klass = Objectify::Config::Injectables
|
25
32
|
@request_injectables_context ||= klass.new.tap do |injectables_context|
|
@@ -31,8 +38,14 @@ module Objectify
|
|
31
38
|
injectables_context.add_value(:response, response)
|
32
39
|
injectables_context.add_value(:flash, flash)
|
33
40
|
injectables_context.add_value(:renderer, Renderer.new(self))
|
41
|
+
injectables_context.add_value(:format, objectify_response_collector)
|
42
|
+
injectables_context.add_value(:routes, objectify_routes)
|
34
43
|
end
|
35
44
|
end
|
45
|
+
|
46
|
+
def objectify_response_collector
|
47
|
+
@objectify_response_collector ||= ActionController::MimeResponds::Collector.new { default_response }
|
48
|
+
end
|
36
49
|
|
37
50
|
def objectify_executor
|
38
51
|
objectify.executor
|
@@ -43,8 +56,9 @@ module Objectify
|
|
43
56
|
end
|
44
57
|
|
45
58
|
def objectify_route
|
46
|
-
|
47
|
-
|
59
|
+
routing_options = params[:objectify] ? params[:objectify] : {:controller => params[:controller].to_sym, :action => params[:action].to_sym}
|
60
|
+
routing_options.merge!(:action => params[:action]) if routing_options.delete(:append_action)
|
61
|
+
@objectify_route ||= Objectify::Route.new(routing_options)
|
48
62
|
end
|
49
63
|
|
50
64
|
def action
|
@@ -57,6 +71,7 @@ module Objectify
|
|
57
71
|
|
58
72
|
def execute_policy_chain
|
59
73
|
policy_chain_executor.call(action)
|
74
|
+
objectify_respond_if_response
|
60
75
|
end
|
61
76
|
|
62
77
|
def objectify_around_filter
|
@@ -66,10 +81,19 @@ module Objectify
|
|
66
81
|
end
|
67
82
|
|
68
83
|
def execute_objectify_action
|
69
|
-
|
70
|
-
request_injectables_context.add_value(:service_result,
|
84
|
+
@objectify_service_result = objectify_executor.call(action.service, :service)
|
85
|
+
request_injectables_context.add_value(:service_result, @objectify_service_result)
|
71
86
|
|
72
87
|
objectify_executor.call(action.responder, :responder)
|
88
|
+
objectify_respond_if_response
|
89
|
+
end
|
90
|
+
|
91
|
+
def objectify_respond_if_response
|
92
|
+
if format = request.negotiate_mime(objectify_response_collector.order)
|
93
|
+
self.content_type ||= format.to_s
|
94
|
+
lookup_context.freeze_formats([format.to_sym])
|
95
|
+
instance_eval &objectify_response_collector.response_for(format)
|
96
|
+
end
|
73
97
|
end
|
74
98
|
end
|
75
99
|
|
@@ -81,11 +105,24 @@ module Objectify
|
|
81
105
|
klass.helper_method(:objectify_executor) if klass.respond_to?(:helper_method)
|
82
106
|
end
|
83
107
|
|
84
|
-
def
|
108
|
+
def action_missing(name, *args, &block)
|
85
109
|
instrument("start_processing.objectify", :route => objectify_route)
|
86
110
|
|
87
111
|
execute_objectify_action
|
88
112
|
end
|
113
|
+
|
114
|
+
def controller_path
|
115
|
+
action.resource_name
|
116
|
+
end
|
117
|
+
|
118
|
+
def action_name
|
119
|
+
action.name
|
120
|
+
end
|
121
|
+
|
122
|
+
private
|
123
|
+
def view_assigns
|
124
|
+
{:_objectify_data => @objectify_service_result}
|
125
|
+
end
|
89
126
|
end
|
90
127
|
|
91
128
|
module ControllerBehaviour
|
@@ -96,16 +133,30 @@ module Objectify
|
|
96
133
|
klass.helper_method(:objectify_executor) if klass.respond_to?(:helper_method)
|
97
134
|
end
|
98
135
|
|
99
|
-
def
|
136
|
+
def action_missing(name, *args, &block)
|
100
137
|
instrument("start_processing.objectify", :route => objectify_route)
|
101
138
|
|
102
139
|
if execute_policy_chain
|
103
140
|
execute_objectify_action
|
104
141
|
end
|
105
142
|
end
|
143
|
+
|
144
|
+
def controller_path
|
145
|
+
action.resource_name
|
146
|
+
end
|
147
|
+
|
148
|
+
def action_name
|
149
|
+
action.name
|
150
|
+
end
|
151
|
+
|
152
|
+
private
|
153
|
+
def view_assigns
|
154
|
+
{:_objectify_data => @objectify_service_result}
|
155
|
+
end
|
106
156
|
end
|
107
157
|
|
108
158
|
class ObjectifyController < ActionController::Base
|
159
|
+
around_filter :objectify_around_filter
|
109
160
|
include ControllerBehaviour
|
110
161
|
end
|
111
162
|
end
|
@@ -6,7 +6,7 @@ module Objectify
|
|
6
6
|
module Routing
|
7
7
|
RESOURCE_ACTIONS = [:index, :show, :new, :create,
|
8
8
|
:edit, :update, :destroy].freeze
|
9
|
-
OBJECTIFY_OPTIONS = [:policies].freeze
|
9
|
+
OBJECTIFY_OPTIONS = [:policies, :service, :skip_policies].freeze
|
10
10
|
|
11
11
|
class ObjectifyMapper
|
12
12
|
def initialize(rails_mapper,
|
@@ -24,12 +24,31 @@ module Objectify
|
|
24
24
|
rails_options = options.merge(:controller => controller)
|
25
25
|
|
26
26
|
args.each do |resource_name|
|
27
|
-
|
27
|
+
objectify_defaults = {:resource => resource_name}
|
28
|
+
merged_defaults = merge_defaults(objectify_defaults.merge(:append_action => true),
|
29
|
+
rails_options)
|
28
30
|
@rails_mapper.resources(resource_name, merged_defaults)
|
29
|
-
RESOURCE_ACTIONS.each
|
31
|
+
RESOURCE_ACTIONS.each do |action_name|
|
32
|
+
append_action(objectify_defaults.merge(:action => action_name),
|
33
|
+
resource_name,
|
34
|
+
action_name,
|
35
|
+
objectify_options)
|
36
|
+
end
|
30
37
|
end
|
31
38
|
end
|
32
39
|
|
40
|
+
def match(options)
|
41
|
+
from,to = options.detect { |k,v| k.is_a?(String) }
|
42
|
+
resource,action = to.split("#").map(&:to_sym)
|
43
|
+
controller = @application.objectify.objectify_controller
|
44
|
+
objectify_options = extract_objectify_options(options)
|
45
|
+
objectify_defaults = {"path" => from.dup}
|
46
|
+
rails_options = merge_defaults(objectify_defaults, options)
|
47
|
+
@rails_mapper.match rails_options.merge(from => "#{controller}#action")
|
48
|
+
|
49
|
+
append_action(objectify_defaults, resource, action, objectify_options)
|
50
|
+
end
|
51
|
+
|
33
52
|
def defaults(options)
|
34
53
|
@application.objectify.append_defaults(options)
|
35
54
|
end
|
@@ -50,8 +69,16 @@ module Objectify
|
|
50
69
|
@application.objectify.append_values(options)
|
51
70
|
end
|
52
71
|
|
72
|
+
def decorators(options)
|
73
|
+
@application.objectify.append_decorators(options)
|
74
|
+
end
|
75
|
+
|
53
76
|
def legacy_action(controller, actions, options)
|
54
|
-
[*actions].each
|
77
|
+
[*actions].each do |action_name|
|
78
|
+
routing_opts = {:controller => controller,
|
79
|
+
:action => action_name}
|
80
|
+
append_action(routing_opts, controller, action_name, options)
|
81
|
+
end
|
55
82
|
end
|
56
83
|
|
57
84
|
private
|
@@ -61,14 +88,15 @@ module Objectify
|
|
61
88
|
end.compact.flatten]
|
62
89
|
end
|
63
90
|
|
64
|
-
def objectify_defaults
|
65
|
-
defaults = {:objectify =>
|
91
|
+
def merge_defaults(objectify_defaults, rails_options)
|
92
|
+
defaults = {:objectify => objectify_defaults}
|
66
93
|
defaults = (rails_options[:defaults] || {}).merge(defaults)
|
67
94
|
defaults = rails_options.merge(:defaults => defaults)
|
68
95
|
end
|
69
96
|
|
70
|
-
def append_action(resource_name, action_name, options)
|
71
|
-
action = @action_factory.new(
|
97
|
+
def append_action(routing_opts, resource_name, action_name, options)
|
98
|
+
action = @action_factory.new(routing_opts,
|
99
|
+
resource_name,
|
72
100
|
action_name,
|
73
101
|
options,
|
74
102
|
@application.objectify.policies)
|
data/lib/objectify/route.rb
CHANGED
data/lib/objectify/version.rb
CHANGED
data/spec/config/action_spec.rb
CHANGED
@@ -8,6 +8,8 @@ describe "Objectify::Config::Action" do
|
|
8
8
|
|
9
9
|
@route = stub("Route")
|
10
10
|
@route_factory = stub("RouteFactory", :new => @route)
|
11
|
+
|
12
|
+
@routing_opts = {:some => :route}
|
11
13
|
end
|
12
14
|
|
13
15
|
context "when everything is specified" do
|
@@ -23,7 +25,8 @@ describe "Objectify::Config::Action" do
|
|
23
25
|
:index => @index_options
|
24
26
|
}
|
25
27
|
|
26
|
-
@action = Objectify::Config::Action.new(
|
28
|
+
@action = Objectify::Config::Action.new(@routing_opts,
|
29
|
+
:pictures,
|
27
30
|
:index,
|
28
31
|
@options,
|
29
32
|
@default_policies,
|
@@ -31,7 +34,7 @@ describe "Objectify::Config::Action" do
|
|
31
34
|
end
|
32
35
|
|
33
36
|
it "creates and stores a route from the route factory" do
|
34
|
-
@route_factory.should have_received(:new).with(
|
37
|
+
@route_factory.should have_received(:new).with(@routing_opts)
|
35
38
|
@action.route.should == @route
|
36
39
|
end
|
37
40
|
|
@@ -61,7 +64,8 @@ describe "Objectify::Config::Action" do
|
|
61
64
|
context "when nothing is specified" do
|
62
65
|
before do
|
63
66
|
@options = {}
|
64
|
-
@action = Objectify::Config::Action.new(
|
67
|
+
@action = Objectify::Config::Action.new(@routing_opts,
|
68
|
+
:pictures,
|
65
69
|
:index,
|
66
70
|
@options,
|
67
71
|
@default_policies,
|
data/spec/config/context_spec.rb
CHANGED
@@ -76,7 +76,8 @@ describe "Objectify::Config::Context" do
|
|
76
76
|
@action_factory = stub("ActionFactory", :new => @action)
|
77
77
|
@policies = stub("Policies")
|
78
78
|
@policies_factory = stub("PoliciesFactory", :new => @policies)
|
79
|
-
@
|
79
|
+
@route_opts = {:controller => :controller, :action => :action}
|
80
|
+
@route = stub("Route", :opts => @route_opts)
|
80
81
|
|
81
82
|
@context = Objectify::Config::Context.new(@policies_factory, @action_factory)
|
82
83
|
end
|
@@ -88,7 +89,9 @@ describe "Objectify::Config::Context" do
|
|
88
89
|
|
89
90
|
it "creates a new action with the controller and action name and its policies" do
|
90
91
|
@action_factory.should have_received(:new).
|
91
|
-
with(
|
92
|
+
with(@route_opts,
|
93
|
+
:controller,
|
94
|
+
:action, {}, @policies)
|
92
95
|
end
|
93
96
|
end
|
94
97
|
|
@@ -37,4 +37,12 @@ describe "Objectify::Config::Injectables" do
|
|
37
37
|
@injectables.context = @context
|
38
38
|
@injectables.get(:controller).should == [:value, :something]
|
39
39
|
end
|
40
|
+
|
41
|
+
it "accepts decorators" do
|
42
|
+
@injectables.add_decorators(:base, :decorator1)
|
43
|
+
@injectables.decorators(:base).should == [:decorator1]
|
44
|
+
@injectables.add_decorators(:base, [:decorator1, :decorator2])
|
45
|
+
@injectables.decorators(:base).should == [:decorator1, :decorator2]
|
46
|
+
@injectables.decorators(:nonexistent).should == []
|
47
|
+
end
|
40
48
|
end
|
data/spec/injector_spec.rb
CHANGED
@@ -40,7 +40,7 @@ describe "Objectify::Injector" do
|
|
40
40
|
end
|
41
41
|
|
42
42
|
before do
|
43
|
-
@config = stub("Config", :get => nil)
|
43
|
+
@config = stub("Config", :get => nil, :decorators => [])
|
44
44
|
@injector = Objectify::Injector.new(@config)
|
45
45
|
end
|
46
46
|
|
@@ -99,4 +99,40 @@ describe "Objectify::Injector" do
|
|
99
99
|
@injector.call(obj, :requires_params).call.should == :SOMETHING
|
100
100
|
end
|
101
101
|
end
|
102
|
+
|
103
|
+
class ToBeDecorated
|
104
|
+
end
|
105
|
+
|
106
|
+
class Decorator1
|
107
|
+
attr_reader :to_be_decorated
|
108
|
+
|
109
|
+
def initialize(to_be_decorated)
|
110
|
+
@to_be_decorated = to_be_decorated
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
class Decorator2
|
115
|
+
attr_reader :to_be_decorated
|
116
|
+
|
117
|
+
def initialize(to_be_decorated)
|
118
|
+
@to_be_decorated = to_be_decorated
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
context "decorating an object" do
|
123
|
+
before do
|
124
|
+
decorators = [:decorator1, :decorator2]
|
125
|
+
@config.stubs(:decorators).with(:to_be_decorated).returns(decorators)
|
126
|
+
@result = @injector.call(ToBeDecorated, :new)
|
127
|
+
end
|
128
|
+
|
129
|
+
it "decorates left to right" do
|
130
|
+
@result.should be_instance_of(Decorator2)
|
131
|
+
@result.to_be_decorated.should be_instance_of(Decorator1)
|
132
|
+
@result.to_be_decorated.to_be_decorated.should be_instance_of(ToBeDecorated)
|
133
|
+
|
134
|
+
@config.stubs(:decorators).with(:to_be_decorated).returns([])
|
135
|
+
@injector.call(ToBeDecorated, :new).should be_instance_of(ToBeDecorated)
|
136
|
+
end
|
137
|
+
end
|
102
138
|
end
|
data/spec/rails/routing_spec.rb
CHANGED
@@ -11,6 +11,7 @@ describe "Objectify::Rails::Routing::ObjectifyMapper" do
|
|
11
11
|
:append_implementations => nil,
|
12
12
|
:append_resolvers => nil,
|
13
13
|
:append_values => nil,
|
14
|
+
:append_decorators => nil,
|
14
15
|
:objectify_controller => "some_controller")
|
15
16
|
@rails_mapper = stub("RailsMapper", :resources => nil,
|
16
17
|
:match => nil)
|
@@ -25,6 +26,9 @@ describe "Objectify::Rails::Routing::ObjectifyMapper" do
|
|
25
26
|
|
26
27
|
context "adding a resource" do
|
27
28
|
before do
|
29
|
+
@rails_routing_info = {:resource => :pictures,
|
30
|
+
:append_action => true}
|
31
|
+
@objectify_routing_info = {:resource => :pictures}
|
28
32
|
@mapper.resources(:pictures, :policies => :some_policy,
|
29
33
|
:create => {
|
30
34
|
:policies => :blocked_user,
|
@@ -34,7 +38,7 @@ describe "Objectify::Rails::Routing::ObjectifyMapper" do
|
|
34
38
|
|
35
39
|
it "correctly adds the resource to the rails mapper" do
|
36
40
|
opts = { :controller => @objectify.objectify_controller,
|
37
|
-
:defaults => {:objectify =>
|
41
|
+
:defaults => {:objectify => @rails_routing_info}}
|
38
42
|
@rails_mapper.should have_received(:resources).
|
39
43
|
with(:pictures, opts)
|
40
44
|
end
|
@@ -48,8 +52,13 @@ describe "Objectify::Rails::Routing::ObjectifyMapper" do
|
|
48
52
|
:policies => :some_policy
|
49
53
|
}
|
50
54
|
Objectify::Rails::Routing::RESOURCE_ACTIONS.each do |action|
|
55
|
+
routing_info = @objectify_routing_info.merge(:action => action)
|
51
56
|
@action_factory.should have_received(:new).
|
52
|
-
with(
|
57
|
+
with(routing_info,
|
58
|
+
:pictures,
|
59
|
+
action,
|
60
|
+
opts,
|
61
|
+
@policies)
|
53
62
|
end
|
54
63
|
end
|
55
64
|
|
@@ -61,6 +70,9 @@ describe "Objectify::Rails::Routing::ObjectifyMapper" do
|
|
61
70
|
|
62
71
|
context "adding a resource with its own defaults" do
|
63
72
|
before do
|
73
|
+
@rails_routing_info = {:resource => :pictures,
|
74
|
+
:append_action => true}
|
75
|
+
@objectify_routing_info = {:resource => :pictures}
|
64
76
|
@mapper.resources(:pictures, :policies => :some_policy,
|
65
77
|
:create => {
|
66
78
|
:policies => :blocked_user,
|
@@ -71,7 +83,7 @@ describe "Objectify::Rails::Routing::ObjectifyMapper" do
|
|
71
83
|
|
72
84
|
it "correctly adds the resource to the rails mapper" do
|
73
85
|
opts = { :controller => @objectify.objectify_controller,
|
74
|
-
:defaults => {:objectify =>
|
86
|
+
:defaults => {:objectify => @rails_routing_info,
|
75
87
|
:some => :bullshit}}
|
76
88
|
@rails_mapper.should have_received(:resources).
|
77
89
|
with(:pictures, opts)
|
@@ -86,8 +98,10 @@ describe "Objectify::Rails::Routing::ObjectifyMapper" do
|
|
86
98
|
:policies => :some_policy
|
87
99
|
}
|
88
100
|
Objectify::Rails::Routing::RESOURCE_ACTIONS.each do |action|
|
101
|
+
routing_info = @objectify_routing_info.merge(:action => action)
|
89
102
|
@action_factory.should have_received(:new).
|
90
|
-
with(
|
103
|
+
with(routing_info,
|
104
|
+
:pictures, action, opts, @policies)
|
91
105
|
end
|
92
106
|
end
|
93
107
|
|
@@ -97,6 +111,58 @@ describe "Objectify::Rails::Routing::ObjectifyMapper" do
|
|
97
111
|
end
|
98
112
|
end
|
99
113
|
|
114
|
+
context "#match" do
|
115
|
+
before do
|
116
|
+
@routing_info = {"path" => "/pictures"}
|
117
|
+
@opts = { "/pictures" => "pictures#create" }
|
118
|
+
@mapper.match @opts
|
119
|
+
end
|
120
|
+
|
121
|
+
it "correctly adds the resource to the rails mapper" do
|
122
|
+
opts = { "/pictures" => "#{@objectify.objectify_controller}#action",
|
123
|
+
:defaults => {:objectify => @routing_info}}
|
124
|
+
@rails_mapper.should have_received(:match).with(opts)
|
125
|
+
end
|
126
|
+
|
127
|
+
it "creates an action" do
|
128
|
+
@action_factory.should have_received(:new).
|
129
|
+
with(@routing_info, :pictures, :create, {}, @policies)
|
130
|
+
end
|
131
|
+
|
132
|
+
it "appends each of the actions to the objectify object" do
|
133
|
+
@objectify.should have_received(:append_action).
|
134
|
+
with(@action).times(1)
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
context "#match with overrides" do
|
139
|
+
before do
|
140
|
+
@routing_info = {"path" => "/pictures"}
|
141
|
+
@opts = { "/pictures" => "pictures#create" }
|
142
|
+
@objectify_opts = {:service => :pics, :skip_policies => :an}
|
143
|
+
@opts.merge!(@objectify_opts)
|
144
|
+
|
145
|
+
@mapper.match @opts
|
146
|
+
end
|
147
|
+
|
148
|
+
it "correctly adds the resource to the rails mapper" do
|
149
|
+
opts = { "/pictures" => "#{@objectify.objectify_controller}#action",
|
150
|
+
:defaults => {:objectify => @routing_info}}
|
151
|
+
@rails_mapper.should have_received(:match).with(opts)
|
152
|
+
end
|
153
|
+
|
154
|
+
it "creates an action" do
|
155
|
+
@action_factory.should have_received(:new).
|
156
|
+
with(@routing_info, :pictures, :create,
|
157
|
+
@objectify_opts, @policies)
|
158
|
+
end
|
159
|
+
|
160
|
+
it "appends each of the actions to the objectify object" do
|
161
|
+
@objectify.should have_received(:append_action).
|
162
|
+
with(@action).times(1)
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
100
166
|
context "adding a resource with an alternative objectify_controller" do
|
101
167
|
before do
|
102
168
|
@objectify.stubs(:objectify_controller).returns("asdfbsdf")
|
@@ -110,7 +176,8 @@ describe "Objectify::Rails::Routing::ObjectifyMapper" do
|
|
110
176
|
|
111
177
|
it "correctly adds the resource to the rails mapper" do
|
112
178
|
opts = { :controller => @objectify.objectify_controller,
|
113
|
-
:defaults => {:objectify => {:resource => :pictures
|
179
|
+
:defaults => {:objectify => {:resource => :pictures,
|
180
|
+
:append_action => true},
|
114
181
|
:some => :bullshit}}
|
115
182
|
@rails_mapper.should have_received(:resources).
|
116
183
|
with(:pictures, opts)
|
@@ -161,6 +228,13 @@ describe "Objectify::Rails::Routing::ObjectifyMapper" do
|
|
161
228
|
@objectify.should have_received(:append_values).
|
162
229
|
with(@opts)
|
163
230
|
end
|
231
|
+
|
232
|
+
it "hands decorators to the context" do
|
233
|
+
@opts = { :base => :decorator }
|
234
|
+
@mapper.decorators @opts
|
235
|
+
@objectify.should have_received(:append_decorators).
|
236
|
+
with(@opts)
|
237
|
+
end
|
164
238
|
end
|
165
239
|
|
166
240
|
context "adding a legacy action" do
|
@@ -170,11 +244,20 @@ describe "Objectify::Rails::Routing::ObjectifyMapper" do
|
|
170
244
|
end
|
171
245
|
|
172
246
|
it "creates actions for each of the specified actions" do
|
247
|
+
routing_opts = {:controller => :controller}
|
173
248
|
@action_factory.should have_received(:new).
|
174
|
-
with(:
|
249
|
+
with(routing_opts.merge(:action => :action1),
|
250
|
+
:controller,
|
251
|
+
:action1,
|
252
|
+
@opts,
|
253
|
+
@policies)
|
175
254
|
|
176
255
|
@action_factory.should have_received(:new).
|
177
|
-
with(:
|
256
|
+
with(routing_opts.merge(:action => :action2),
|
257
|
+
:controller,
|
258
|
+
:action2,
|
259
|
+
@opts,
|
260
|
+
@policies)
|
178
261
|
end
|
179
262
|
|
180
263
|
it "passes the actions to the objectify objects" do
|
data/spec/route_spec.rb
CHANGED
@@ -4,8 +4,8 @@ require "objectify/route"
|
|
4
4
|
describe "Objectify::Route" do
|
5
5
|
context "two routes with the same path" do
|
6
6
|
it "have the same #hash value" do
|
7
|
-
Objectify::Route.new(:pictures, :index).should ==
|
8
|
-
Objectify::Route.new(:pictures, :index)
|
7
|
+
Objectify::Route.new(:controller => :pictures, :action => :index).should ==
|
8
|
+
Objectify::Route.new(:controller => :pictures, :action => :index)
|
9
9
|
end
|
10
10
|
end
|
11
11
|
end
|
@@ -0,0 +1,156 @@
|
|
1
|
+
# responders
|
2
|
+
|
3
|
+
Currently responders collaborate with other objects to actually initiate the response. They're by far the weakest abstraction in objectify. This document contains some thoughts on how to fix that.
|
4
|
+
|
5
|
+
## Value objects approach
|
6
|
+
|
7
|
+
Bryan Helmkamp's proposal on the mailing list was to have responders return value objects that describe the response, and then objectify could use those descriptors to actually fire the response using rails' APIs.
|
8
|
+
|
9
|
+
An example:
|
10
|
+
|
11
|
+
```ruby
|
12
|
+
class SessionNewResponder
|
13
|
+
def call
|
14
|
+
Objectify::Render::Template.new(:name => "new.html.erb")
|
15
|
+
end
|
16
|
+
end
|
17
|
+
```
|
18
|
+
|
19
|
+
There are two problems with this. First of all, typing out the whole namespace is cumbersome. Though, that can be solved with one `include`.
|
20
|
+
|
21
|
+
```ruby
|
22
|
+
class SessionNewResponder
|
23
|
+
include Objectify::Render
|
24
|
+
|
25
|
+
def call
|
26
|
+
Template.new(:name => "new.html.erb")
|
27
|
+
end
|
28
|
+
end
|
29
|
+
```
|
30
|
+
|
31
|
+
The second problem is how to handle multiple formats. One idea is to use the rails block syntax:
|
32
|
+
|
33
|
+
```ruby
|
34
|
+
class SessionNewResponder
|
35
|
+
include Objectify::Render
|
36
|
+
|
37
|
+
def call(service_result)
|
38
|
+
FormattedResponse.new(:data => service_result) do |format|
|
39
|
+
format.html
|
40
|
+
format.js
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
```
|
45
|
+
|
46
|
+
Or something like that. I'm not a huge fan of that syntax because it seems difficult to test, although maybe that's solved by making the result of the little DSL very simple.
|
47
|
+
|
48
|
+
```ruby
|
49
|
+
class SessionNewResponder
|
50
|
+
include Objectify::Render
|
51
|
+
|
52
|
+
def call(service_result)
|
53
|
+
FormattedResponse.new(:data => service_result) do |format|
|
54
|
+
format.html Template.new("somewhere.html.erb")
|
55
|
+
format.js
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
```
|
60
|
+
|
61
|
+
After writing this, I'm starting to think it's the right approach.
|
62
|
+
|
63
|
+
## Rename to "Responses"?
|
64
|
+
|
65
|
+
One idea I had this weekend, was to rename responders to responses. That way, conceptually, you're defining a type of response. I think that might be easier for people to think about.
|
66
|
+
|
67
|
+
Also, I think we should use methods to respond to different content types. Can't believe it took me this long to think of this.
|
68
|
+
|
69
|
+
```ruby
|
70
|
+
class UnauthorizedResponse
|
71
|
+
include Objectify::Response
|
72
|
+
|
73
|
+
respond_with :html, :js
|
74
|
+
status 403
|
75
|
+
|
76
|
+
def js
|
77
|
+
:default
|
78
|
+
end
|
79
|
+
|
80
|
+
def any(service_result, renderer)
|
81
|
+
renderer.template :name => "unauthorized.html.erb", :data => service_result
|
82
|
+
end
|
83
|
+
end
|
84
|
+
```
|
85
|
+
|
86
|
+
```ruby
|
87
|
+
class PicturesCreateSuccessfulResponse
|
88
|
+
include Objectify::Response
|
89
|
+
|
90
|
+
when_policy :create_successful
|
91
|
+
respond_with :html, :js, :json
|
92
|
+
|
93
|
+
def html(service_result, renderer)
|
94
|
+
responder.redirect_to service_result
|
95
|
+
end
|
96
|
+
|
97
|
+
def js(service_result, renderer)
|
98
|
+
responder.redirect_to "xyz"
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
class PicturesCreateUnsuccessfulResponse
|
103
|
+
include Objectify::Response
|
104
|
+
|
105
|
+
when_not_policy :create_successful
|
106
|
+
respond_with :html, :js, :json
|
107
|
+
|
108
|
+
def html(service_result, renderer)
|
109
|
+
responder.redirect_to service_result
|
110
|
+
end
|
111
|
+
|
112
|
+
def js(service_result, renderer)
|
113
|
+
responder.redirect_to "xyz"
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
class PicturesCreateResponse < Objectify::Response
|
118
|
+
respond do |service_result, format|
|
119
|
+
if service_result.persisted?
|
120
|
+
format.html { redirect_to service_result }
|
121
|
+
format.js { render :template => "pictures/show.json.json_builder" }
|
122
|
+
format.json { render :template => "pictures/show.json.json_builder" }
|
123
|
+
else
|
124
|
+
format.html { render :template => "pictures/new.html.erb" }
|
125
|
+
format.js { render :json => { :errors => service_result.errors, :status => :unprocessable_entity } }
|
126
|
+
format.json { render :template => "pictures/show.json.json_builder" }
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
130
|
+
```
|
131
|
+
|
132
|
+
Tests?
|
133
|
+
|
134
|
+
```ruby
|
135
|
+
describe "PicturesCreateResponse" do
|
136
|
+
before do
|
137
|
+
@format = Objectify::FakeFormat.new
|
138
|
+
@response = PicturesCreateResponse.new
|
139
|
+
end
|
140
|
+
|
141
|
+
context "when create is successful" do
|
142
|
+
before do
|
143
|
+
@service_result = stub("Result", :persisted? => true)
|
144
|
+
@response.respond(@service_result, @format)
|
145
|
+
end
|
146
|
+
|
147
|
+
it "renders a template called 'pictures/new.html.erb' for html" do
|
148
|
+
@response.should render_template("pictures/new.html.erb")
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
152
|
+
```
|
153
|
+
|
154
|
+
## Other approaches?
|
155
|
+
|
156
|
+
* Something that can be tested mockist-style, perhaps? I have no idea what this would look like, though, really.
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: objectify
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,11 +9,11 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2012-05
|
12
|
+
date: 2012-06-05 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: rspec
|
16
|
-
requirement: &
|
16
|
+
requirement: &70262082543760 !ruby/object:Gem::Requirement
|
17
17
|
none: false
|
18
18
|
requirements:
|
19
19
|
- - ~>
|
@@ -21,10 +21,10 @@ dependencies:
|
|
21
21
|
version: 2.4.0
|
22
22
|
type: :development
|
23
23
|
prerelease: false
|
24
|
-
version_requirements: *
|
24
|
+
version_requirements: *70262082543760
|
25
25
|
- !ruby/object:Gem::Dependency
|
26
26
|
name: bundler
|
27
|
-
requirement: &
|
27
|
+
requirement: &70262082559640 !ruby/object:Gem::Requirement
|
28
28
|
none: false
|
29
29
|
requirements:
|
30
30
|
- - ! '>='
|
@@ -32,10 +32,10 @@ dependencies:
|
|
32
32
|
version: 1.0.0
|
33
33
|
type: :development
|
34
34
|
prerelease: false
|
35
|
-
version_requirements: *
|
35
|
+
version_requirements: *70262082559640
|
36
36
|
- !ruby/object:Gem::Dependency
|
37
37
|
name: jeweler
|
38
|
-
requirement: &
|
38
|
+
requirement: &70262082559180 !ruby/object:Gem::Requirement
|
39
39
|
none: false
|
40
40
|
requirements:
|
41
41
|
- - ~>
|
@@ -43,10 +43,10 @@ dependencies:
|
|
43
43
|
version: 1.6.4
|
44
44
|
type: :development
|
45
45
|
prerelease: false
|
46
|
-
version_requirements: *
|
46
|
+
version_requirements: *70262082559180
|
47
47
|
- !ruby/object:Gem::Dependency
|
48
48
|
name: bourne
|
49
|
-
requirement: &
|
49
|
+
requirement: &70262082558700 !ruby/object:Gem::Requirement
|
50
50
|
none: false
|
51
51
|
requirements:
|
52
52
|
- - =
|
@@ -54,10 +54,10 @@ dependencies:
|
|
54
54
|
version: '1.0'
|
55
55
|
type: :development
|
56
56
|
prerelease: false
|
57
|
-
version_requirements: *
|
57
|
+
version_requirements: *70262082558700
|
58
58
|
- !ruby/object:Gem::Dependency
|
59
59
|
name: mocha
|
60
|
-
requirement: &
|
60
|
+
requirement: &70262082558160 !ruby/object:Gem::Requirement
|
61
61
|
none: false
|
62
62
|
requirements:
|
63
63
|
- - =
|
@@ -65,10 +65,10 @@ dependencies:
|
|
65
65
|
version: 0.9.8
|
66
66
|
type: :development
|
67
67
|
prerelease: false
|
68
|
-
version_requirements: *
|
68
|
+
version_requirements: *70262082558160
|
69
69
|
- !ruby/object:Gem::Dependency
|
70
70
|
name: rails
|
71
|
-
requirement: &
|
71
|
+
requirement: &70262082557540 !ruby/object:Gem::Requirement
|
72
72
|
none: false
|
73
73
|
requirements:
|
74
74
|
- - ! '>='
|
@@ -76,10 +76,10 @@ dependencies:
|
|
76
76
|
version: 3.0.0
|
77
77
|
type: :runtime
|
78
78
|
prerelease: false
|
79
|
-
version_requirements: *
|
79
|
+
version_requirements: *70262082557540
|
80
80
|
- !ruby/object:Gem::Dependency
|
81
81
|
name: i18n
|
82
|
-
requirement: &
|
82
|
+
requirement: &70262082557000 !ruby/object:Gem::Requirement
|
83
83
|
none: false
|
84
84
|
requirements:
|
85
85
|
- - ! '>='
|
@@ -87,7 +87,7 @@ dependencies:
|
|
87
87
|
version: '0'
|
88
88
|
type: :runtime
|
89
89
|
prerelease: false
|
90
|
-
version_requirements: *
|
90
|
+
version_requirements: *70262082557000
|
91
91
|
description: Objects on rails.
|
92
92
|
email:
|
93
93
|
- jamesgolick@gmail.com
|
@@ -118,6 +118,7 @@ files:
|
|
118
118
|
- lib/objectify/rails/log_subscriber.rb
|
119
119
|
- lib/objectify/rails/railtie.rb
|
120
120
|
- lib/objectify/rails/renderer.rb
|
121
|
+
- lib/objectify/rails/routes.rb
|
121
122
|
- lib/objectify/rails/routing.rb
|
122
123
|
- lib/objectify/route.rb
|
123
124
|
- lib/objectify/version.rb
|
@@ -135,6 +136,7 @@ files:
|
|
135
136
|
- spec/route_spec.rb
|
136
137
|
- spec/spec_helper.rb
|
137
138
|
- thoughts/injector.md
|
139
|
+
- thoughts/responders.md
|
138
140
|
homepage: https://github.com/bitlove/objectify
|
139
141
|
licenses: []
|
140
142
|
post_install_message:
|