h2. merb-resource-scope Brought to you by Dynamic50
What the heck is this? It's a way of decorating your restful routes with a specification.
This specification can provide some useful information when the request has hit a certain route.
THIS MEANS THAT YOUR CONTROLLERS WILL BECOME DRY AS A BONE! AND THEY WILL KNOW HOW TO FIND ITS
RESOURCE AND ENCLOSING RESOURCE, without before filters. ALSO YOUR RESOURCE WILL BE AUTOMATICALLY
SCOPED, ie users/1/posts/1/comments/3
So the controller will have access to find_resource method and that resource will be found from
users.get(1).posts.get(1).comments.get(3) Not comment.get(3) the comments has to belong to
the post and the post has to belong_to the user!
A route of users/1/posts/1 would have 2 specifications. one for the 'users' and one for the 'posts'
and the order of the specifications is quite important.
So once that route has been hit we have 2 specs "users" and "posts", the posts controller is now aware of that
the current request is nested inside user, so you could call /user/1 the 'enclosing resource'
The controller can now determine its "resource_scope". which can then be used to find
resources and resources in a DRY way. With the extra bonus of getting automatic scoping.
Note this is being tested using a test-stack that has been tested on stack --edge
and latest stable datamapper
Nothing better then examples to see how stuff works, so lets have a look at them now.
h2. Examples
Ever seen "lets pretend", well today we have [users, posts, comments, profile_image, :images]
user has 1, :profile_image
user has n, :posts
user has n, :comments
post has n, :comments
image has n, :comments
There are 2 steps to resource_scope enlightenment;
Setting up your resourceful routes
Making your controller resource_scope aware
h2. Setting Up the Routes Example 1
What on earth are the options and what do they do? see router/resource_specification
resource_with_spec is exactly the same as the "resource" method except a specification is added to the request
resources_with_spec is exactly the same as the "resources" method except a specification is added to the request
You Specify spec option with the :spec => {}, all other arguments will be parsed to the resource method
ie. @resources_with_spec :posts, :spec => {:permalink => :title}, :collection => {:recent => :get}@
resource_with_spec :myhome, :spec => {:class_name => "User", :find_method => Proc.new{ session.authentication.user}} do
resources_with_spec :posts do
resources_with_spec :comments
end
end
h2. Setting Up the Controllers Example 1
lets make Comments Controller be resource_scope aware
so lets just make the comments controller do exactly that
class Comments < Application
build_resource_scope
end
yep thats it!! so what does that do???
this will add a before filter that will do exactly that, along with some included actions.
This is just a module and in the config we can set to use our own default actions
ok now lets pretend we have just requested "/myhome/posts/1/comments"
this will hit the default index action which looks something a bit like this
def index
self.current_resources = find_resources
display current_resources
end
what is happening here?
find_resources - is essentially doing this.
@session.authentication.user.posts.get(1).comments@
Now you can override this method in your controller change the default behavior
def find_resources
@resource_scope.all :conditions => {:pending => false}
end
@resource_scope what exactly is that? Its the resource_scope which for this particular instance
is @session.authentication.user.posts.get(1).comments@
we would also have access to some included url helpers NOTE resource_url is not the same as resource method
@resources_url == "/myhome/posts/1/comments"@
@enclosing_resource_url == "/myhome/posts/1"@
@enclosing_resources_url == "/myhome/posts"@
You can now extend your enclosing_resource_url, resource_url with a block,
why? Well if you want to get to link to post/1/tags when you are on the post/1/comments page for instance
h2. Example
@enclosing_resource_url{|route| route.add(:tags)}@
So what you do is use a block and you will have the route_generate object available to you
which means you can use the add method!!!
look at controllers/helpers for more info on the add methods, But essentially it takes
up to 3 parameters
@add(:comment, :new)@
@add(:comment, :edit, @comment)@
@add(:comment, @comment)@
see URL helper for more info on these lets a heading below just in case
ok now lets pretend we have just requested "/myhome/posts/1"
def show
self.current_resource = find_resource
raise ::Merb::ControllerExceptions::NotFound unless current_resource
display current_resource
end
find_resource here would be essential resource_scope.get(1)
which would mean - session.authentication.user.posts.get(1)
@resource_url(current_resource) == "/myhome/posts/1"@
@resource_url(current_resource, :edit) == "/myhome/posts/1/edit"@
@resource_url(current_resource, :edit, :a => "w") == "/myhome/posts/1/edit?a=w"@
@new_resource_url == "/myhome/posts/new"@
@enclosing_resource_url(:edit) == "/myhome/edit"@
@new_enclosing_resource_url == "/myhome/new"@
If you want to see more look at the test-stack | and the controller/actions | and specs
There are more specs to come with more examples but for now that is all she wrote.
Also need to add slice support which I have stubbed
h2. url helpers
The url helper basically generate a name route using the specifications
and the captured name_prefix in the route behaviors, so in essence
they create a name_route. This is important to remember when
you are just using the specification route extension method
as you will need to give the route the correct name, basically
needs to match the resource_name, or you change the resource_name
so yeah remember its looking for NAMED ROUTES
h3. new_resources_url - Just pass in params hash
will generate a scope url like /posts/1/comments/new
@new_resource_url@
@new_resource_url(:paramexample => "whatver")@
h3. resource_url - Pass in a the current_resource OR a symbol for singletons and then params hash
@resource_url(current_resource)@
@resource_url(current_resource, :edit)@
@resource_url(current_resource, :whatever => "whatberparams")@
@resource_url(:myhome)@
@resource_url(:myhome, :edit)@
h3. resources_url - Pass in custom routes and then params hash
@resources_url@
@resources_url(:pending)@
@resources_url(:parms => "whateverparams")@
h3. enclosing_resource_url - Pass in custom routes and then params hash
@enclosing_resource_url@
@enclosing_resource_url(:edit)@
@enclosing_resource_url(:parms => "whateverparams")@
h3. enclosing_resources_url - Pass in custom routes and then params hash
@enclosing_resources_url@
@enclosing_resources_url(:pending)@
@enclosing_resources_url(:pending, :q => {"Asdf dsaf"})@
@enclosing_resources_url(:a => "whateverparams")@
h3. new_enclosing_resource_url - Just params hash
will generate a scope url like /posts/1/comments/new
@new_enclosing_resource_url@
@new_enclosing_resource_url(:paramexample => "whatver")@
h3. build_resource_scope options
-
:actions => {:only => [:action_names], :exclude => [:actions_name]} # include what actions you want
-
:singleton => true # makes sure that only singleton actions are included i.e not index
-
:build_scope => take the same options that you would pass to a before filter i.e :only, :if etc!!!
h3. config for my own actions
Merb::Plugins.config[:merb_resource_scope][:actions] = MyOwnModuleForMyDefaultCoolActions
Merb::Plugins.config[:merb_resource_scope][:singleton_actions] = MyReallyCoolDefaultSingletonActions
h3. Resources
GOOGLE GROUP
http://groups.google.com/group/merb-resource-scope
LIGHTHOUSE
http://dynamic50.lighthouseapp.com/projects/18588-merb-resource-scope/overview
Inspiration And Thanks
(RC) resources_controller from my old rails world, respect goes out to our good friend Ian White
http://github.com/ianwhite/resources_controller/tree/master
Thanks
Gfriends Aimee for putting up with me, and Megan my special daughter
License
(The MIT License)
Copyright (c) 2008 Dynamic50
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
'Software'), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.