hookercookerman-merb-resource-scope 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (31) hide show
  1. data/LICENSE +20 -0
  2. data/README.textile +297 -0
  3. data/Rakefile +82 -0
  4. data/TODO +3 -0
  5. data/lib/dependencies.rb +1 -0
  6. data/lib/merb-resource-scope.rb +25 -0
  7. data/lib/merb-resource-scope/controller/actions.rb +67 -0
  8. data/lib/merb-resource-scope/controller/helpers.rb +252 -0
  9. data/lib/merb-resource-scope/controller/scoped_resource_mixin.rb +300 -0
  10. data/lib/merb-resource-scope/controller/singleton_actions.rb +25 -0
  11. data/lib/merb-resource-scope/merbtasks.rb +6 -0
  12. data/lib/merb-resource-scope/router/resource_specification.rb +144 -0
  13. data/lib/merb-resource-scope/specification.rb +36 -0
  14. data/spec/app.rb +7 -0
  15. data/spec/controller/_build_enclosing_scope_spec.rb +107 -0
  16. data/spec/controller/scope_resource_mixin_spec.rb +0 -0
  17. data/spec/integration/admin_posts_controller_spec.rb +158 -0
  18. data/spec/integration/campaign_post_comments_controller_spec.rb +158 -0
  19. data/spec/integration/campaign_posts_controller_spec.rb +73 -0
  20. data/spec/integration/campaigns_controller_spec.rb +43 -0
  21. data/spec/integration/images_spec.rb +17 -0
  22. data/spec/integration/myhome_controller_spec.rb +29 -0
  23. data/spec/integration/myhome_posts_comment_controller_spec.rb +44 -0
  24. data/spec/integration/myhome_posts_controller_spec.rb +55 -0
  25. data/spec/integration/profile_images_controller_spec.rb +61 -0
  26. data/spec/integration/tags_controller_spec.rb +34 -0
  27. data/spec/integration/user_posts_controller_spec.rb +71 -0
  28. data/spec/request_with_controller.rb +103 -0
  29. data/spec/spec_helper.rb +33 -0
  30. data/spec/specification_spec.rb +1 -0
  31. metadata +88 -0
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2008 Dynamic50
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.
@@ -0,0 +1,297 @@
1
+ h2. merb-resource-scope Brought to you by Dynamic50
2
+
3
+ What the heck is this? It's a way of decorating your restful routes with a specification.
4
+ This specification can provide some useful information when the request has hit a certain route.
5
+
6
+ THIS MEANS THAT YOUR CONTROLLERS WILL BECOME DRY AS A BONE! AND THEY WILL KNOW HOW TO FIND ITS
7
+ RESOURCE AND ENCLOSING RESOURCE, without before filters. ALSO YOUR RESOURCE WILL BE AUTOMATICALLY
8
+ SCOPED, ie users/1/posts/1/comments/3
9
+
10
+ So the controller will have access to find_resource method and that resource will be found from
11
+ users.get(1).posts.get(1).comments.get(3) Not comment.get(3) the comments has to belong to
12
+ the post and the post has to belong_to the user!
13
+
14
+ A route of users/1/posts/1 would have 2 specifications. one for the 'users' and one for the 'posts'
15
+ and the order of the specifications is quite important.
16
+
17
+ So once that route has been hit we have 2 specs "users" and "posts", the posts controller is now aware of that
18
+ the current request is nested inside user, so you could call /user/1 the 'enclosing resource'
19
+
20
+ The controller can now determine its "resource_scope". which can then be used to find
21
+ resources and resources in a DRY way. With the extra bonus of getting automatic scoping.
22
+
23
+ Note this is being tested using a test-stack that has been tested on stack --edge
24
+ and latest stable datamapper
25
+
26
+ Nothing better then examples to see how stuff works, so lets have a look at them now.
27
+
28
+ h2. Examples
29
+
30
+ Ever seen "lets pretend", well today we have [users, posts, comments, profile_image, :images]
31
+
32
+ <pre>
33
+ <code>
34
+ user has 1, :profile_image
35
+ user has n, :posts
36
+ user has n, :comments
37
+ post has n, :comments
38
+ image has n, :comments
39
+ </code>
40
+ </pre>
41
+
42
+ There are 2 steps to resource_scope enlightenment;
43
+
44
+ # Setting up your resourceful routes
45
+ # Making your controller resource_scope aware
46
+
47
+ h2. Setting Up the Routes Example 1
48
+
49
+ What on earth are the options and what do they do? see router/resource_specification
50
+
51
+ resource_with_spec is exactly the same as the "resource" method except a specification is added to the request
52
+ resources_with_spec is exactly the same as the "resources" method except a specification is added to the request
53
+
54
+ You Specify spec option with the :spec => {}, all other arguments will be parsed to the resource method
55
+
56
+ ie. @resources_with_spec :posts, :spec => {:permalink => :title}, :collection => {:recent => :get}@
57
+
58
+
59
+ <pre>
60
+ <code>
61
+ resource_with_spec :myhome, :spec => {:class_name => "User", :find_method => Proc.new{ session.authentication.user}} do
62
+ resources_with_spec :posts do
63
+ resources_with_spec :comments
64
+ end
65
+ end
66
+ </code>
67
+ </pre>
68
+
69
+ h2. Setting Up the Controllers Example 1
70
+
71
+ lets make Comments Controller be resource_scope aware
72
+ so lets just make the comments controller do exactly that
73
+
74
+ <pre>
75
+ <code>
76
+ class Comments < Application
77
+ build_resource_scope
78
+ end
79
+ </code>
80
+ </pre>
81
+
82
+ yep thats it!! so what does that do???
83
+
84
+ this will add a before filter that will do exactly that, along with some included actions.
85
+ This is just a module and in the config we can set to use our own default actions
86
+
87
+ ok now lets pretend we have just requested "/myhome/posts/1/comments"
88
+
89
+ this will hit the default index action which looks something a bit like this
90
+
91
+ <pre>
92
+ <code>
93
+ def index
94
+ self.current_resources = find_resources
95
+ display current_resources
96
+ end
97
+ </code>
98
+ </pre>
99
+
100
+ what is happening here?
101
+
102
+ find_resources - is essentially doing this.
103
+
104
+ @session.authentication.user.posts.get(1).comments@
105
+
106
+ Now you can override this method in your controller change the default behavior
107
+
108
+ <pre>
109
+ <code>
110
+ def find_resources
111
+ @resource_scope.all :conditions => {:pending => false}
112
+ end
113
+ </code>
114
+ </pre>
115
+
116
+ @resource_scope what exactly is that? Its the resource_scope which for this particular instance
117
+ is @session.authentication.user.posts.get(1).comments@
118
+
119
+ we would also have access to some included url helpers NOTE resource_url is not the same as resource method
120
+
121
+ @resources_url == "/myhome/posts/1/comments"@
122
+ @enclosing_resource_url == "/myhome/posts/1"@
123
+ @enclosing_resources_url == "/myhome/posts"@
124
+
125
+ You can now extend your enclosing_resource_url, resource_url with a block,
126
+
127
+ why? Well if you want to get to link to post/1/tags when you are on the post/1/comments page for instance
128
+
129
+ h2. Example
130
+
131
+ @enclosing_resource_url{|route| route.add(:tags)}@
132
+
133
+ So what you do is use a block and you will have the route_generate object available to you
134
+ which means you can use the add method!!!
135
+
136
+ look at controllers/helpers for more info on the add methods, But essentially it takes
137
+ up to 3 parameters
138
+
139
+ @add(:comment, :new)@
140
+ @add(:comment, :edit, @comment)@
141
+ @add(:comment, @comment)@
142
+
143
+ see URL helper for more info on these lets a heading below just in case
144
+
145
+ ok now lets pretend we have just requested "/myhome/posts/1"
146
+
147
+ <pre>
148
+ <code>
149
+ def show
150
+ self.current_resource = find_resource
151
+ raise ::Merb::ControllerExceptions::NotFound unless current_resource
152
+ display current_resource
153
+ end
154
+ </code>
155
+ </pre>
156
+
157
+ find_resource here would be essential resource_scope.get(1)
158
+ which would mean - session.authentication.user.posts.get(1)
159
+
160
+ @resource_url(current_resource) == "/myhome/posts/1"@
161
+
162
+ @resource_url(current_resource, :edit) == "/myhome/posts/1/edit"@
163
+
164
+ @resource_url(current_resource, :edit, :a => "w") == "/myhome/posts/1/edit?a=w"@
165
+
166
+ @new_resource_url == "/myhome/posts/new"@
167
+
168
+ @enclosing_resource_url(:edit) == "/myhome/edit"@
169
+
170
+ @new_enclosing_resource_url == "/myhome/new"@
171
+
172
+ If you want to see more look at the test-stack | and the controller/actions | and specs
173
+
174
+ There are more specs to come with more examples but for now that is all she wrote.
175
+ Also need to add slice support which I have stubbed
176
+
177
+ h2. url helpers
178
+
179
+ The url helper basically generate a name route using the specifications
180
+ and the captured name_prefix in the route behaviors, so in essence
181
+ they create a name_route. This is important to remember when
182
+ you are just using the specification route extension method
183
+ as you will need to give the route the correct name, basically
184
+ needs to match the resource_name, or you change the resource_name
185
+
186
+ so yeah remember its looking for NAMED ROUTES
187
+
188
+ h3. new_resources_url - Just pass in params hash
189
+
190
+
191
+ will generate a scope url like /posts/1/comments/new
192
+
193
+ @new_resource_url@
194
+ @new_resource_url(:paramexample => "whatver")@
195
+
196
+ h3. resource_url - Pass in a the current_resource OR a symbol for singletons and then params hash
197
+
198
+
199
+ @resource_url(current_resource)@
200
+ @resource_url(current_resource, :edit)@
201
+ @resource_url(current_resource, :whatever => "whatberparams")@
202
+ @resource_url(:myhome)@
203
+ @resource_url(:myhome, :edit)@
204
+
205
+ h3. resources_url - Pass in custom routes and then params hash
206
+
207
+
208
+ @resources_url@
209
+ @resources_url(:pending)@
210
+ @resources_url(:parms => "whateverparams")@
211
+
212
+ h3. enclosing_resource_url - Pass in custom routes and then params hash
213
+
214
+
215
+ @enclosing_resource_url@
216
+ @enclosing_resource_url(:edit)@
217
+ @enclosing_resource_url(:parms => "whateverparams")@
218
+
219
+ h3. enclosing_resources_url - Pass in custom routes and then params hash
220
+
221
+
222
+ @enclosing_resources_url@
223
+ @enclosing_resources_url(:pending)@
224
+ @enclosing_resources_url(:pending, :q => {"Asdf dsaf"})@
225
+ @enclosing_resources_url(:a => "whateverparams")@
226
+
227
+ h3. new_enclosing_resource_url - Just params hash
228
+
229
+ will generate a scope url like /posts/1/comments/new
230
+
231
+ @new_enclosing_resource_url@
232
+ @new_enclosing_resource_url(:paramexample => "whatver")@
233
+
234
+
235
+ h3. build_resource_scope options
236
+
237
+
238
+ - :actions => {:only => [:action_names], :exclude => [:actions_name]} # include what actions you want
239
+ - :singleton => true # makes sure that only singleton actions are included i.e not index
240
+ - :build_scope => take the same options that you would pass to a before filter i.e :only, :if etc!!!
241
+
242
+
243
+ h3. config for my own actions
244
+
245
+ Merb::Plugins.config[:merb_resource_scope][:actions] = MyOwnModuleForMyDefaultCoolActions
246
+
247
+ Merb::Plugins.config[:merb_resource_scope][:singleton_actions] = MyReallyCoolDefaultSingletonActions
248
+
249
+
250
+ h3. Resources
251
+
252
+
253
+
254
+ GOOGLE GROUP
255
+ http://groups.google.com/group/merb-resource-scope
256
+
257
+ LIGHTHOUSE
258
+ http://dynamic50.lighthouseapp.com/projects/18588-merb-resource-scope/overview
259
+
260
+
261
+ Inspiration And Thanks
262
+ ======================
263
+
264
+ (RC) resources_controller from my old rails world, respect goes out to our good friend Ian White
265
+ http://github.com/ianwhite/resources_controller/tree/master
266
+
267
+
268
+ Thanks
269
+ ======
270
+
271
+ Gfriends Aimee for putting up with me, and Megan my special daughter
272
+
273
+
274
+ License
275
+ -------
276
+ (The MIT License)
277
+
278
+ Copyright (c) 2008 Dynamic50
279
+
280
+ Permission is hereby granted, free of charge, to any person obtaining
281
+ a copy of this software and associated documentation files (the
282
+ 'Software'), to deal in the Software without restriction, including
283
+ without limitation the rights to use, copy, modify, merge, publish,
284
+ distribute, sublicense, and/or sell copies of the Software, and to
285
+ permit persons to whom the Software is furnished to do so, subject to
286
+ the following conditions:
287
+
288
+ The above copyright notice and this permission notice shall be
289
+ included in all copies or substantial portions of the Software.
290
+
291
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
292
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
293
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
294
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
295
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
296
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
297
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,82 @@
1
+ require 'rubygems'
2
+ require 'rake/gempackagetask'
3
+ require 'merb-core'
4
+ require 'merb-core/tasks/merb'
5
+ require 'merb-core/tasks/merb_rake_helper'
6
+
7
+ GEM_NAME = "merb-resource-scope"
8
+ GEM_VERSION = "0.1.0"
9
+ AUTHOR = "hookercookerman"
10
+ EMAIL = "hookercookerman@gmail.com"
11
+ HOMEPAGE = "http://www.dynamic50.com/"
12
+ SUMMARY = "Merb plugin that provides scoped resourceful controllers"
13
+
14
+ spec = eval(File.read('merb-resource-scope.gemspec'))
15
+ Rake::GemPackageTask.new(spec) { |pkg| pkg.gem_spec = spec }
16
+
17
+ Rake::GemPackageTask.new(spec) do |pkg|
18
+ pkg.gem_spec = spec
19
+ end
20
+
21
+ # Checks to see if Windows platform or if "SUDOLESS" environment variable is set
22
+ sudo = ((RUBY_PLATFORM =~ /win32|mingw|bccwin|cygwin/) rescue nil) ? '' : ('sudo' unless ENV['SUDOLESS'])
23
+ task :install => [:package] do
24
+ sh %{#{sudo} gem install pkg/#{spec.name}-#{spec.version}}
25
+ end
26
+
27
+ desc "Uninstall the gem"
28
+ task :uninstall do
29
+ Merb::RakeHelper.uninstall(GEM_NAME, :version => GEM_VERSION)
30
+ end
31
+
32
+ desc "Create a gemspec file"
33
+ task :gemspec do
34
+ File.open("#{GEM_NAME}.gemspec", "w") do |file|
35
+ file.puts spec.to_ruby
36
+ end
37
+ end
38
+
39
+ require 'spec/rake/spectask'
40
+ require 'spec/rake/verify_rcov'
41
+ require 'rake/rdoctask'
42
+ ##############################################################################
43
+ # Specs
44
+ ##############################################################################
45
+
46
+ desc "Run the specs for merb-resource-scope"
47
+ Spec::Rake::SpecTask.new(:spec) do |t|
48
+ t.spec_files = FileList['spec/**/*_spec.rb']
49
+ t.spec_opts = ["--colour"]
50
+ end
51
+
52
+ namespace :spec do
53
+ desc "Generate RCov report for merb-resource-scope"
54
+ Spec::Rake::SpecTask.new(:rcov) do |t|
55
+ t.spec_files = FileList['spec/**/*_spec.rb']
56
+ t.rcov = true
57
+ t.rcov_dir = 'doc/coverage'
58
+ t.rcov_opts = ['--text-report', '--exclude', "spec/,#{File.expand_path(File.join(File.dirname(__FILE__),'../../test-app'))}"]
59
+ end
60
+
61
+ namespace :rcov do
62
+ desc "Verify RCov threshold for merb-resource-scope"
63
+ RCov::VerifyTask.new(:verify => "spec:rcov") do |t|
64
+ t.threshold = 100.0
65
+ t.index_html = File.join(File.dirname(__FILE__), 'doc/coverage/index.html')
66
+ end
67
+ end
68
+
69
+ desc "Generate specdoc for merb-resource-scope"
70
+ Spec::Rake::SpecTask.new(:doc) do |t|
71
+ t.spec_files = FileList['spec/**/*_spec.rb']
72
+ t.spec_opts = ["--format", "specdoc:SPECDOC"]
73
+ end
74
+
75
+ namespace :doc do
76
+ desc "Generate html specdoc for merb-resource-scope"
77
+ Spec::Rake::SpecTask.new(:html => :rdoc) do |t|
78
+ t.spec_files = FileList['spec/**/*_spec.rb']
79
+ t.spec_opts = ["--format", "html:doc/rspec_report.html", "--diff"]
80
+ end
81
+ end
82
+ end
data/TODO ADDED
@@ -0,0 +1,3 @@
1
+ TODO:
2
+ More Specs where they need to be, as they are currently lacking
3
+ Slice support
@@ -0,0 +1 @@
1
+ dependency "merb-core", "0.9.13" # Provides support for querystring arguments to be passed in to controller actions
@@ -0,0 +1,25 @@
1
+ # make sure we're running inside Merb
2
+ if defined?(Merb::Plugins)
3
+
4
+ require "merb-resource-scope" / "controller" / "actions"
5
+ require "merb-resource-scope" / "controller" / "singleton_actions"
6
+ require "merb-resource-scope" / "controller" / "helpers"
7
+ require "merb-resource-scope" / "specification"
8
+ require "merb-resource-scope" / "router" / "resource_specification"
9
+ require "merb-resource-scope" / "controller" / "scoped_resource_mixin"
10
+
11
+ Merb::Plugins.config[:merb_resource_scope] = {
12
+ :actions => MerbResourceScope::Controller::Actions,
13
+ :singleton_actions => MerbResourceScope::Controller::SingletonActions
14
+ }.merge(Merb::Plugins.config[:merb_resource_scope] || {})
15
+
16
+ Merb::BootLoader.before_app_loads do
17
+ Merb::Controller.send(:include, MerbResourceScope::Controller::ScopedResourceMixin)
18
+
19
+ module Merb::GlobalHelpers
20
+ include MerbResourceScope::Controller::Helpers
21
+ end
22
+ end
23
+
24
+ Merb::Plugins.add_rakefiles "merb-resource-scope/merbtasks"
25
+ end
@@ -0,0 +1,67 @@
1
+ module MerbResourceScope
2
+ module Controller
3
+ module Actions
4
+
5
+ def self.included(base)
6
+ unless base == MerbResourceScope::Controller::SingletonActions
7
+ base.show_action(:show, :index, :new, :edit, :update, :create, :destroy)
8
+ end
9
+ end
10
+
11
+ def index
12
+ self.current_resources = find_resources
13
+ display current_resources
14
+ end
15
+
16
+ def show
17
+ self.current_resource = find_resource
18
+ raise ::Merb::ControllerExceptions::NotFound unless current_resource
19
+ display current_resource
20
+ end
21
+
22
+ def new
23
+ only_provides :html
24
+ self.current_resource = new_resource
25
+ render
26
+ end
27
+
28
+ def edit
29
+ only_provides :html
30
+ self.current_resource = find_resource
31
+ raise ::Merb::ControllerExceptions::NotFound unless current_resource
32
+ render
33
+ end
34
+
35
+ def create
36
+ raise ::Merb::ControllerExceptions::BadRequest, "No params passed to create a new object, check your new action view!" if params[@_current_specification.resource_name].nil?
37
+ self.current_resource = new_resource
38
+ if current_resource.save
39
+ redirect current_resources_url
40
+ else
41
+ render :new
42
+ end
43
+ end
44
+
45
+ def update
46
+ self.current_resource = find_resource
47
+ raise ::Merb::ControllerExceptions::NotFound unless current_resource
48
+ if current_resource.update_attributes(params[_current_specification.resource_name]) || !current_resource.dirty?
49
+ redirect current_resource_url(current_resource)
50
+ else
51
+ raise ::Merb::ControllerExceptions::BadRequest
52
+ end
53
+ end
54
+
55
+ def destroy
56
+ self.current_resource = find_resource
57
+ raise ::Merb::ControllerExceptions::NotFound unless current_resource
58
+ if current_resource.destroy
59
+ redirect current_resources_url
60
+ else
61
+ raise ::Merb::ControllerExceptions::BadRequest
62
+ end
63
+ end
64
+
65
+ end # Actions
66
+ end # Controller
67
+ end # MerbResourcesController