merb_resource_controller
A merb plugin that provides the default CRUD
actions for controllers and allows for easy customization
of the generated actions. merb_resource_controller
only supports datamapper
so far. Implementing support
for active_record
would be trivial, but I have no need for it.
Currently, merb_resource_controller
needs an extra specification inside the controller to define the nesting strategy
to use. This shouldn’t really be necessary, since this information could be provided by the Merb::Router
.
I’m planning to have a look into this soon! With that in place, nested resource_controllers could be 3 liners
that just do what you would expect them to do.
To get you started with merb_resource_controller
, all you have to do is extend it into your controller.
Most probably you will want extend it into the Application
controller, which will make the controlling
method available to all your application’s controllers.
class Application < Merb::Controller
extend Merb::ResourceController::Mixin::ClassMethods
end
And here is a quick example showing a controller for a standard toplevel resource with all standard CRUD
actions.
class Articles < Application
controlling :articles
end
If you don’t want all standard CRUD
actions to be generated, give merb_resource_controller
the exact list
of actions you want your controller to have.
class Articles < Application
controlling :articles do |a|
a.action :create
a.action :update
end
end
Alternatively, you can also add more than one action at once using the actions
method available on the ResourceProxy
class Articles < Application
controlling :articles do |a|
a.actions :create, :update
end
end
If you are using models that are nested inside some module
(maybe you’re developing a slice
?), you need to
provide the fully qualified class name to the controlling
method. Per default, merb_resource_controller
assumes
that you don’t want to use the fully qualified name to generate your instance variables and association calls.
class Ratings < Application
controlling "Community::Rating"
end
This will initialize the instance variable @ratings
(or @rating
respectively). If this resource is nested below
another resource (see next section), this also means that the :ratings
(or :rating
respectively) association will
be used to navigate from the parent resource down to this resource.
If however, you want to use fully qualified names for your instance variables and association calls, you can explicitly
pass :fully_qualified => true
to the controlling
method (false
is the default).
class Ratings < Application
controlling "Community::Rating", :fully_qualified => true
end
This will initialize the instance variable @community_ratings
(or @community_rating
respectively). If this resource
is nested below another resource (see next section), this also means that the :community_ratings
(or
:community_rating
respectively) association will be used to navigate from the parent resource down to this resource.
Nested Resources
merb_resource_controller
will automatically recognize if it is accessed from a toplevel or a nested url, and
will adjust its actions accordingly. It will also initialize all the instance variables
you would expect for your chosen nesting strategy. This means, that when you have an URL like
/articles/1/comments
, you will have an @article
variable pointing to Article.get(1)
and a @comments
variable
pointing to Article.get(1).comments
.
It’s important to note, that no database operations will be performed more than once!
So in the case of /articles/1/comments/1/ratings
, merb_resource_controller
will do exactly what you would
expect it to do: It will issue Article.get(1).comments.get(1).ratings
, initializing
@article
, @comment
and @ratings
with the intermediate results on it’s way down the association chain.
Speaking of an association chain, it is worth mentioning that it is by no means necessary that your
underlying models are connected via real datamapper associations (like has n, ...
or belongs_to
).
The only requirement is, that the object returned from a call to the nested collection respond_to?(:get)
(in most cases it will be a DataMapper::Collection
anyway).
</code>
class Comments < Application
controlling :comments do |c|
c.belongs_to :article
end
end
class Ratings < Application
controlling :ratings do |r|
r.belongs_to [ :article, :comment ]
end
end
Nested Singleton Resources
First of all, singleton resources (currently) must always be nested below at least one parent resource.
This is mostly because I somehow can’t really imagine a usecase for a real toplevel singleton resource? I mean, there
must be some information on how to load that one resource. Since we’re mostly dealing with database backends here,
we should probably need some kind of key to identify exactly one dataset, but we don’t get that key from a resource
like /profile
(where is the :id
param here?). So my guess is, that most of the toplevel singleton resources that
are actually used, really are nested below some kind of resource that’s stored in the session
, most probably the
authenticated user
object. If this is the case, it would be perfectly valid, to just really nest those singletons
below their real parent, and probably add a route alias to keep up the disguise. If you can come up with a usecase
where you have a real toplevel singleton resource that needs no key to be loaded, just don’t use
merb_resource_controller
. Really, it’s that simple! If however, I’m completely on the wrong track here, I would be glad
to be informed about it, so that I can fix things up.
The example code below behaves almost exactly like described in the section on Nested Resources above. The only
two differences are, that 1) no index action
will be generated and that 2) the initialized instance variables will be
named @article
and @editor
if you have a singleton resource nested like /articles/1/editor
. Just like you would
expect, no?
class Editors < Application
controlling :editor, :singleton => true do |e|
e.belongs_to :article
end
end
DataMapper’s Identity Map
Including Merb::ResourceController::DM::IdentityMapSupport
into any of your controller will wrap all actions
inside a dm repository block in order to be able to use DM’s identitiy map feature. For more information on that topic,
have a look at DataMapper’s Identity Map
Feel free to include this module into single controllers instead of including it into Application
controller, which
enables identity maps for every controller action in your app.
class Application < Merb::Controller
include Merb::ResourceController::DM::IdentityMapSupport
end
Action Timeouts
Extending Merb::ResourceController::ActionTimeout
into any of your controllers, will give you the set_action_timeout
method. If you specify an action_timeout greater or equal to 1
and you have the system_timer
gem installed, the
action will timeout after the given amount of seconds. For a more detailed explanation on the topic have a look at
Battling Wedged Mongrels with a Request Timeout
Feel free to set action timeouts for single controllers instead of doing so in Application
controller, which sets
the same action timeout for every controller action in your app.
class Application < Merb::Controller
extend Merb::ResourceController::ActionTimeout
set_action_timeout 1
end
Defined Actions
All the above controllers will have the following actions defined. Feel free to override them, to customize how the controllers behave. Of course you are also free to override all the methods used by these defined actions.
def index
load_resource
display requested_resource
end
def show
load_resource
raise Merb::ControllerExceptions::NotFound unless requested_resource
display requested_resource
end
def new
only_provides :html
load_resource
set_member(new_member)
display member
end
def edit
only_provides :html
load_resource
raise Merb::ControllerExceptions::NotFound unless requested_resource
display requested_resource
end
def create
load_resource
set_member(new_member(params[member_name]))
if member.save
= flash_supported? ? { :message => } : {}
redirect redirect_on_successful_create,
else
.merge!() if flash_supported?
render :new
end
end
def update
load_resource
raise Merb::ControllerExceptions::NotFound unless requested_resource
if requested_resource.update_attributes(params[member_name])
= flash_supported? ? { :message => } : {}
redirect redirect_on_successful_update,
else
.merge!() if flash_supported?
display requested_resource, :edit
end
end
def destroy
load_resource
raise Merb::ControllerExceptions::NotFound unless requested_resource
if requested_resource.destroy
= flash_supported? ? { :message => } : {}
redirect redirect_on_successful_destroy,
else
raise Merb::ControllerExceptions::InternalServerError
end
end
Additional Helper Methods
In addition to the actions and the methods they are using, you will probably want to override the redirection targets These are the default method implementations you can override to this effect:
def redirect_on_successful_create
target = singleton_controller? ? member_name : member
resource(*(has_parent? ? parents + [ target ] : [ target ]))
end
def redirect_on_successful_update
target = singleton_controller? ? member_name : member
resource(*(has_parent? ? parents + [ target ] : [ target ]))
end
def redirect_on_successful_destroy
if singleton_controller?
has_parent? ? resource(parent) : '/'
else
resource(*(has_parent? ? parents + [ collection_name ] : [ collection_name ]))
end
end
More Information
Have a look at Merb::ResourceController::Actions
, Merb::ResourceController::Mixin::InstanceMethods
and
Merb::ResourceController::Mixin::FlashSupport
inside lib/merb_resource_controller/actions.rb
and
lib/merb_resource_controller/resource_controller.rb
to see what methods will be available inside your controllers.
If you want to see where the real work gets done_, have a look at @lib/merb_resource_controller/resourceproxy.rb@
Of course, you should also have a look at the specs, to get a more concrete idea of how merb_resource_controller
behaves under the given circumstances.
TODO
- Infer route nesting strategy from
Merb::Router
- Customizable support for
provides
andonly_provides
- Support for user stamps (aka created_by and updated_by)
- Support for pagination once an official merb pagination solution exists