objectify 0.0.3 → 0.0.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (45) hide show
  1. data/.document +5 -0
  2. data/.gitignore +47 -2
  3. data/.rspec +1 -0
  4. data/Gemfile +1 -1
  5. data/Gemfile.lock +105 -16
  6. data/LICENSE.txt +20 -0
  7. data/README.md +230 -0
  8. data/Rakefile +14 -7
  9. data/lib/objectify.rb +1 -26
  10. data/lib/objectify/config/action.rb +31 -0
  11. data/lib/objectify/config/context.rb +97 -0
  12. data/lib/objectify/config/policies.rb +18 -0
  13. data/lib/objectify/executor.rb +21 -0
  14. data/lib/objectify/injector.rb +39 -0
  15. data/lib/objectify/instantiator.rb +14 -0
  16. data/lib/objectify/instrumentation.rb +12 -0
  17. data/lib/objectify/logging.rb +22 -0
  18. data/lib/objectify/policy_chain_executor.rb +27 -0
  19. data/lib/objectify/rails/application.rb +16 -0
  20. data/lib/objectify/rails/controller.rb +117 -0
  21. data/lib/objectify/rails/helpers.rb +14 -0
  22. data/lib/objectify/rails/log_subscriber.rb +59 -0
  23. data/lib/objectify/rails/railtie.rb +11 -0
  24. data/lib/objectify/rails/renderer.rb +47 -0
  25. data/lib/objectify/rails/routing.rb +97 -0
  26. data/lib/objectify/resolver.rb +27 -0
  27. data/lib/objectify/resolver_locator.rb +73 -0
  28. data/lib/objectify/route.rb +4 -0
  29. data/lib/objectify/version.rb +1 -1
  30. data/objectify.gemspec +13 -8
  31. data/spec/config/action_spec.rb +79 -0
  32. data/spec/config/context_spec.rb +137 -0
  33. data/spec/config/policies_spec.rb +23 -0
  34. data/spec/executor_spec.rb +47 -0
  35. data/spec/injector_spec.rb +88 -0
  36. data/spec/instantiator_spec.rb +22 -0
  37. data/spec/policy_chain_executor_spec.rb +60 -0
  38. data/spec/rails/renderer_spec.rb +63 -0
  39. data/spec/rails/routing_spec.rb +171 -0
  40. data/spec/resolver_locator_spec.rb +109 -0
  41. data/spec/resolver_spec.rb +28 -0
  42. data/spec/route_spec.rb +11 -0
  43. data/spec/spec_helper.rb +13 -5
  44. metadata +142 -62
  45. data/spec/objectify_spec.rb +0 -60
data/.document ADDED
@@ -0,0 +1,5 @@
1
+ lib/**/*.rb
2
+ bin/*
3
+ -
4
+ features/**/*.feature
5
+ LICENSE.txt
data/.gitignore CHANGED
@@ -1,3 +1,48 @@
1
+ # rcov generated
2
+ coverage
3
+
4
+ # rdoc generated
5
+ rdoc
6
+
7
+ # yard generated
8
+ doc
9
+ .yardoc
10
+
11
+ # bundler
1
12
  .bundle
2
- .idea
3
- pkg
13
+
14
+ # built gems
15
+ *.gem
16
+
17
+ # Have editor/IDE/OS specific files you need to ignore? Consider using a global gitignore:
18
+ #
19
+ # * Create a file at ~/.gitignore
20
+ # * Include files you want ignored
21
+ # * Run: git config --global core.excludesfile ~/.gitignore
22
+ #
23
+ # After doing this, these files will be ignored in all your git projects,
24
+ # saving you from having to 'pollute' every project you touch with them
25
+ #
26
+ # Not sure what to needs to be ignored for particular editors/OSes? Here's some ideas to get you started. (Remember, remove the leading # of the line)
27
+ #
28
+ # For MacOS:
29
+ #
30
+ #.DS_Store
31
+
32
+ # For TextMate
33
+ #*.tmproj
34
+ #tmtags
35
+
36
+ # For emacs:
37
+ #*~
38
+ #\#*
39
+ #.\#*
40
+
41
+ # For vim:
42
+ #*.swp
43
+
44
+ # For redcar:
45
+ #.redcar
46
+
47
+ # For rubinius:
48
+ #*.rbc
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --color
data/Gemfile CHANGED
@@ -1,3 +1,3 @@
1
- source "http://rubygems.org"
1
+ source "https://rubygems.org"
2
2
 
3
3
  gemspec
data/Gemfile.lock CHANGED
@@ -1,28 +1,117 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- objectify (0.0.2)
5
- activesupport (>= 2.3)
4
+ bitlove-objectify (0.0.1)
5
+ i18n
6
+ rails (>= 3.0.0)
6
7
 
7
8
  GEM
8
- remote: http://rubygems.org/
9
+ remote: https://rubygems.org/
9
10
  specs:
10
- activesupport (3.0.5)
11
- diff-lcs (1.1.2)
12
- rake (0.8.7)
13
- rspec (2.5.0)
14
- rspec-core (~> 2.5.0)
15
- rspec-expectations (~> 2.5.0)
16
- rspec-mocks (~> 2.5.0)
17
- rspec-core (2.5.1)
18
- rspec-expectations (2.5.0)
11
+ actionmailer (3.1.1)
12
+ actionpack (= 3.1.1)
13
+ mail (~> 2.3.0)
14
+ actionpack (3.1.1)
15
+ activemodel (= 3.1.1)
16
+ activesupport (= 3.1.1)
17
+ builder (~> 3.0.0)
18
+ erubis (~> 2.7.0)
19
+ i18n (~> 0.6)
20
+ rack (~> 1.3.2)
21
+ rack-cache (~> 1.1)
22
+ rack-mount (~> 0.8.2)
23
+ rack-test (~> 0.6.1)
24
+ sprockets (~> 2.0.2)
25
+ activemodel (3.1.1)
26
+ activesupport (= 3.1.1)
27
+ builder (~> 3.0.0)
28
+ i18n (~> 0.6)
29
+ activerecord (3.1.1)
30
+ activemodel (= 3.1.1)
31
+ activesupport (= 3.1.1)
32
+ arel (~> 2.2.1)
33
+ tzinfo (~> 0.3.29)
34
+ activeresource (3.1.1)
35
+ activemodel (= 3.1.1)
36
+ activesupport (= 3.1.1)
37
+ activesupport (3.1.1)
38
+ multi_json (~> 1.0)
39
+ arel (2.2.3)
40
+ bourne (1.0)
41
+ mocha (= 0.9.8)
42
+ builder (3.0.0)
43
+ diff-lcs (1.1.3)
44
+ erubis (2.7.0)
45
+ git (1.2.5)
46
+ hike (1.2.1)
47
+ i18n (0.6.0)
48
+ jeweler (1.6.4)
49
+ bundler (~> 1.0)
50
+ git (>= 1.2.5)
51
+ rake
52
+ json (1.7.3)
53
+ mail (2.3.3)
54
+ i18n (>= 0.4.0)
55
+ mime-types (~> 1.16)
56
+ treetop (~> 1.4.8)
57
+ mime-types (1.18)
58
+ mocha (0.9.8)
59
+ rake
60
+ multi_json (1.3.5)
61
+ polyglot (0.3.3)
62
+ rack (1.3.6)
63
+ rack-cache (1.2)
64
+ rack (>= 0.4)
65
+ rack-mount (0.8.3)
66
+ rack (>= 1.0.0)
67
+ rack-ssl (1.3.2)
68
+ rack
69
+ rack-test (0.6.1)
70
+ rack (>= 1.0)
71
+ rails (3.1.1)
72
+ actionmailer (= 3.1.1)
73
+ actionpack (= 3.1.1)
74
+ activerecord (= 3.1.1)
75
+ activeresource (= 3.1.1)
76
+ activesupport (= 3.1.1)
77
+ bundler (~> 1.0)
78
+ railties (= 3.1.1)
79
+ railties (3.1.1)
80
+ actionpack (= 3.1.1)
81
+ activesupport (= 3.1.1)
82
+ rack-ssl (~> 1.3.2)
83
+ rake (>= 0.8.7)
84
+ rdoc (~> 3.4)
85
+ thor (~> 0.14.6)
86
+ rake (0.9.2.2)
87
+ rdoc (3.12)
88
+ json (~> 1.4)
89
+ rspec (2.4.0)
90
+ rspec-core (~> 2.4.0)
91
+ rspec-expectations (~> 2.4.0)
92
+ rspec-mocks (~> 2.4.0)
93
+ rspec-core (2.4.0)
94
+ rspec-expectations (2.4.0)
19
95
  diff-lcs (~> 1.1.2)
20
- rspec-mocks (2.5.0)
96
+ rspec-mocks (2.4.0)
97
+ sprockets (2.0.4)
98
+ hike (~> 1.2)
99
+ rack (~> 1.0)
100
+ tilt (!= 1.3.0, ~> 1.1)
101
+ thor (0.14.6)
102
+ tilt (1.3.3)
103
+ treetop (1.4.10)
104
+ polyglot
105
+ polyglot (>= 0.3.1)
106
+ tzinfo (0.3.33)
21
107
 
22
108
  PLATFORMS
23
109
  ruby
24
110
 
25
111
  DEPENDENCIES
26
- objectify!
27
- rake
28
- rspec
112
+ bitlove-objectify!
113
+ bourne (= 1.0)
114
+ bundler (~> 1.0.0)
115
+ jeweler (~> 1.6.4)
116
+ mocha (= 0.9.8)
117
+ rspec (~> 2.4.0)
data/LICENSE.txt ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2012 James Golick, BitLove Inc.
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,230 @@
1
+ # objectify
2
+
3
+ Objectify is a framework that codifies good object oriented design practices for building maintainable rails applications.
4
+
5
+ ## How it works
6
+
7
+ Objectify has two primary components:
8
+
9
+ 1. A request execution framework that separates the responsibilities that are typically jammed together in rails controller actions in to 3 types of components: Policies, Services, and Responders. Properly separating and assigning these responsibilities makes for far more testable code, and facilitates better reuse of components.
10
+
11
+ The flow of an objectify request is as follows:
12
+
13
+ 0. Objectify actions are configured in the routes file:
14
+
15
+ ```ruby
16
+ # config/routes.rb
17
+ # ... snip ...
18
+ objectify.resources :pictures
19
+ ```
20
+
21
+ Objectify currently only supports resourceful actions, but that's just a temporary thing.
22
+
23
+ 1. The policy chain is resolved (based on the various levels of configuration) and executed. Objectify calls the #allowed?(...) method on each policy in the chain. If one of the policies fails, the chain short-circuits at that point, and objectify executes the configured responder for that policy.
24
+
25
+ An example Policy:
26
+
27
+ ```ruby
28
+ class RequiresLoginPolicy
29
+ # more on how current user gets injected below
30
+ def allowed?(current_user)
31
+ !current_user.nil?
32
+ end
33
+ end
34
+ ```
35
+
36
+ A responder, in case that policy fails.
37
+
38
+ ```ruby
39
+ class UnauthenticatedResponder
40
+ # yes, at some point we probably need a better interface
41
+ # for handling responses, but this'll do for now.
42
+ def call(controller, renderer)
43
+ renderer.redirect_to controller.login_url
44
+ end
45
+ end
46
+ ```
47
+
48
+ Here's how you setup the RequiresLoginPolicy to run by default (you can configure specific actions to ignore it), and connect the policy with its responder.
49
+
50
+ ```ruby
51
+ # config/routes.rb
52
+ MyApp::Application.routes.draw do
53
+ objectify.defaults :policies => :requires_login
54
+ objectify.policy_responders :requires_login => :unauthenticated
55
+ end
56
+ ```
57
+
58
+ 2. If all the policies succeed, the service for that action is executed. A service is typically responsible for fetching and / or manipulating data.
59
+
60
+ A very simple example of a service:
61
+
62
+ ```ruby
63
+ class PicturesCreateService
64
+ # the current_user and the request's params will be automatically injected here.
65
+ def call(current_user, params)
66
+ current_user.pictures.create params[:picture]
67
+ end
68
+ end
69
+ ```
70
+
71
+ 3. Finally, the responder is executed. Following with our Pictures#create example:
72
+
73
+ ```ruby
74
+ class PicturesCreateResponder
75
+ # service_result is exactly what it sounds like
76
+ def call(service_result, controller, renderer)
77
+ if service_result.persisted?
78
+ renderer.redirect_to service_result
79
+ else
80
+ # this is the only way that you can pass data to the view layer
81
+ # and you can only pass one thing. Hint: use a presenter.
82
+ renderer.data(service_result)
83
+ renderer.render :template => "pictures/edit.html.erb"
84
+ end
85
+ end
86
+ end
87
+ ```
88
+
89
+ 2. A dependency injection framework. Objectify automatically injects dependencies in to objects it manages based on parameter names. So, if you have a service method signature like: PictureCreationService#call(params), objectify will automatically inject the request's params when it calls that method. It's very simple to create custom injections by implementing Resolver classes. More on that below.
90
+
91
+
92
+ ## What if I have a bunch of existing rails code?
93
+
94
+ Objectify has a legacy mode that allows you to execute the policy chain as a before_filter in your ApplicationController. You can also configure policies (and skip_policies) for your "legacy" actions. That way, access control code is shared between the legacy and objectified components of your application.
95
+
96
+ I completely rewrote our legacy authentication system as a set of objectify policies, resolvers, and services - I'm gonna package that up and release it soon.
97
+
98
+ Here's how to run the policy chain in your ApplicationController - it'll figure out which policies to run itself:
99
+
100
+ ```ruby
101
+ class ApplicationController < ActionController::Base
102
+ include Objectify::Rails::ControllerHelpers
103
+
104
+ around_filter :objectify_around_filter
105
+ before_filter :execute_policy_chain
106
+ end
107
+ ```
108
+
109
+ And to configure policies for a legacy action:
110
+
111
+ ```ruby
112
+ # config/routes.rb
113
+ MyApp::Application.routes.draw do
114
+ objectify.defaults :policies => :requires_login
115
+ objectify.policy_responders :requires_login => :unauthenticated
116
+ objectify.legacy_action :controller, :action, :policies => [:x, :y, :z],
117
+ :skip_policies => [:requires_login]
118
+ end
119
+ ```
120
+
121
+ Then, you need to create an ObjectifyController that inherits from ApplicationController, and configure objectify to use that:
122
+
123
+ ```ruby
124
+ # app/controllers/objectify_controller.rb
125
+ class ObjectifyController < ApplicationController
126
+ include Objectify::Rails::LegacyControllerBehaviour
127
+ end
128
+ ```
129
+
130
+ ```ruby
131
+ # config/application.rb
132
+ module MyApp
133
+ class Application < Rails::Application
134
+ # ...snip...
135
+ objectify.objectify_controller = "objectify"
136
+ end
137
+ end
138
+ ```
139
+
140
+
141
+ ## Custom Injections
142
+
143
+ Several of the above methods have parameters named 'current_user'. By default, objectify won't know how to inject a parameter by that name, so it'll raise an error when it encounters one. Here's how to create a custom resolver for it that'll automatically get found by name.
144
+
145
+ ```ruby
146
+ # app/resolvers/current_user_resolver.rb
147
+ class CurrentUserResolver
148
+ def initialize(user_finder = User)
149
+ @user_finder = user_finder
150
+ end
151
+
152
+ # note that resolvers themselves get injected
153
+ def call(session)
154
+ @user_finder.find_by_id(session[:current_user_id])
155
+ end
156
+ end
157
+ ```
158
+
159
+ ### Why did you constructor-inject the User constant in to the CurrentUserResolver?
160
+
161
+ Because that makes it possible to test in isolation.
162
+
163
+ ```ruby
164
+ describe "CurrentUserResolver" do
165
+ before do
166
+ @user = stub("User")
167
+ @user_finder = stub("UserFinder", :find_by_id => nil)
168
+ @user_finder.stubs(:find_by_id).with(10).returns(@user)
169
+
170
+ @resolver = CurrentUserResolver.new(@user_finder)
171
+ end
172
+
173
+ it "returns whatever the finder returns" do
174
+ @resolver.call({:current_user_id => 42}).should be_nil
175
+ @resolver.call({:current_user_id => 10}).should == @user
176
+ end
177
+ end
178
+ ```
179
+
180
+ ## Views
181
+
182
+ Objectify has two major impacts on your views.
183
+
184
+ 1. You can only pass one variable from an objectified action to the controller. You do that by calling renderer.data(the_object_you_want_to_pass). Then, you call objectify_data in the view to fetch the data. If it's not there, it'll raise an error. Use a presenter or some kind of other struct object to pass multiple objects to your views.
185
+
186
+ 2. You can reuse your policies in your views. require "objectify/rails/helpers" and add Objectify::Rails::Helpers to your helpers list, and you'll get a helper called #policy_allowed?(policy_name). Yay code reuse.
187
+
188
+ ## Installation
189
+
190
+ ```ruby
191
+ # Gemfile
192
+ gem "objectify", "> 0"
193
+
194
+ # config/application.rb
195
+ module MyApp
196
+ class Application < Rails::Application
197
+ require "objectify/rails/application"
198
+ require "objectify/rails/controller"
199
+ # only have to require this if you want objectify logging
200
+ require "objectify/rails/log_subscriber"
201
+ include Objectify::Rails::Application
202
+ end
203
+ end
204
+ ```
205
+
206
+ ## Issues
207
+
208
+ We're using this thing in production to serve millions of requests every day. However, it's far from being complete. Here are some of the problems that still need solving:
209
+
210
+ * Support for all the kinds of routing that rails does.
211
+ * Caching of policy results per-request, so we don't have to run them twice if they're used in views.
212
+ * Smarter injection strategies, and possibly caching.
213
+ * ???
214
+
215
+ ## Credits
216
+
217
+ * Author: James Golick @jamesgolick
218
+ * Advice (and the idea for injections based on method parameter names): Gary Bernhardt @garybernhardt
219
+ * Feedback: Jake Douglas @jakedouglas
220
+ * Feedback: Julie Haché @juliehache
221
+ * The gem name: Andrew Kiellor @akiellor
222
+
223
+ ## The other objectify gem
224
+
225
+ If you were looking for the gem that *used* to be called objectify on rubygems.org, it's here: https://github.com/akiellor/objectify
226
+
227
+ ## Copyright
228
+
229
+ Copyright (c) 2012 James Golick, BitLove Inc. See LICENSE.txt for
230
+ further details.