hookercookerman-merb-resource-scope 0.1.0

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