objectify 0.0.3 → 0.0.5

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.
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.