rails3-merb2-win-for-us-number-one
December 27th, 2008
The first WIN for us to come out of the Rails/Merb merge is that the awesome provides API in merb (which Dan gave a compelling introduction to at RailsCamp4), has made its way to rails-3 (along with some tweaks to the API).
This is a win for us because our response_for plugin provides similar functionality (for rails 2 apps). So that's one less plugin for me to maintain.
Can't wait for RARB!
refactorin' VC, 64 stylee
December 1st, 2008
After railscamp#4, I went to #rorosyd's November meetup which experimented with 2 sessions of 6 lightning talk's, and was GREAT (but the lack of time for questions was not so great).
I gave one on refactoring VC (as in MVC) with response_for and inherit_views. For some reason, I had a serious bout of nostalgia as I was preparing the talk and came up with this (watch the for the 'bytes free' automatic countdown). If that pisses you off, then the slides will piss you off slightly less.
MateWatch revisited '08
November 8th, 2008
The Background
Recording worked hours can be something of a chore, thankfully help is at hand. Matewatch is a bare-minimum solution to the problem, providing you with the data you need (hours spent on a textmate project) and nothing more. It's free, inherently, in the sense that the Ruby code is being given away.
Ian White produced MateWatch and we use it in-house for time tracking. I haven't really used it yet so can't vouch for it, but a year on I doubt Ray and Ian would be using it if it wasn't doing what they wanted.
For installation and use instructions see the original posting
Gotcha
If you're doing this ~ now you may hit upon issues with the current Macports version of Rubyosa (0.4.0) when trying to start Matewatch.
I hit upon this error:
Library/Ruby/Gems/1.8/gems/rubyosa-0.4.0/lib/rbosa.rb:530: [BUG] Bus Error
ruby 1.8.6 (2008-03-03) [universal-darwin9.0]
Abort trap
The solution is to install an older version ruby gem and modify a source file for rubyosa to use this package instead of the most recent.
sudo gem install libxml-ruby --version 0.3.8.4
sudo sed -i -e "s|require 'xml/libxml'|gem 'xml/libxml', '= 0.3.8.4'|" /Library/Ruby/Gems/1.8/gems/rubyosa-0.4.0/lib/rbosa.rb
My solution is from here and the issue is bug-reported here.
Updating inherit_views for edge rails
September 23rd, 2008
Rails edge is getting lots of changes under the hood, consequently garlic has been kicking up a fuss for response_for and inherit_views...
So, I've rewritten them! Both plugins are now a lot cleaner, and have less LOC, due to some great changes under the actionpack hood.
inherit_views | response_for
inherit_views is a pretty simple concept. When you subclass a controller in rails, it would be great if you controller inherited its views from the the view path of the superclass controller. Here's how to use it (or you can just Skip ahead to the rewrite).
Using inherit_views
If you want every controller to inherit it's views from parent controllers, then do this:
class ApplicationController < ActionController::Base
inherit_views
I think this is generally a bad idea, I use inherit views when I need to (when I subclass a controller):
class AccountImagesController < ImagesController
inherit_views 'images'
When you use inherit_views, all view types are supported, as are partials. This means that with the following setup:
images/_form.html.erb
images/edit.html.erb
account_images/_form.html.erb
rendering 'edit' from AccountImages controller will render account_images/_form.html.erb inside images/edit.html.erb.
Rewriting inherit_views for edge
The inherit_views plugin used to acheive this by alias_method_chaining render on the controller and the view, and it would substitute an inherited view if the standard view couldn't be found. Along the way, it got patched for various changes in the rails internal, and well... it got pretty messy.
Edge rails has had a lot of template love recently, and one of the great things is that the notion of picking a template to render now has a single point of entry: ActionView::Base#_pick_template. (there is also _pick_partial_template, but that just massages the filename into the partial format, then calls _pick_template).
This made me stoked because all of the shennanigans in inherit_views could be swept away, and replaced with #_pick_template_from_inherit_view_paths. The only thing left for the plugin to do is to manage the inherit view paths (these are stored on the controller).
A note for current inherit_views users, I've removed some of the API which I never used (render_parent, and some other things). Let me know if you really need that stuff...
inherit_views | response_for
Updating response_for for edge rails
September 23rd, 2008
Rails edge is getting lots of changes under the hood, consequently garlic has been kicking up a fuss for response_for and inherit_views...
So, I've rewritten them! Both plugins are now a lot cleaner, and have less LOC, due to some great changes under the actionpack hood.
inherit_views | response_for
Summary: response_for lets you specify a default response for an action. If that action (or a before_filter) doesn't perform a render or redirect, then the default response is rendered. You can also 'stack' responses - the latter ones override the former ones.
To get the full benefit of response_for, you need to write your actions with it in mind. It's not allways appropriate - but for some things it's great. if you have a set of actions that you use a lot (say if you're a resources_controller user), or if you use superclass controllers, then you'll probably get some benefits form response_for.
Before talking about the rewrite, I'll just give a before and after example of a common use case for response_for - subclassed controller action. If you want to skip this just head straight to the section regarding the rewrite.
Before
Here is a fairly standard controller with an action that does some stuff, then conditionally renders a response.
class SuperclassController < ApplicationController
# the action does some stuff, then conditionally renders a repsonse
# for :html, and :js
def the_action
do_some_stuff
did_yet_more_stuff = if do_some_more_stuff
do_yet_more_stuff
else
do_the_other_thing
end
respond_to do |format|
if did_yet_more_stuff
format.html do
flash[:notice] = "Well done you"
redirect_to the_done_url
end
format.js
else
format.html do
flash[:notice] = "Bad boy"
redirect_to the_bad_url
end
format.js { render :action => 'the_action' }
end
end
end
end
There are two main reasons to override this action in a subclassed controller, (i) to change the response, and (ii) to change the logic. Both of these require a rewrite of the entire action! This is painful.
Code speaks louder than words, so here are two subclassed controllers, both of which add authentication, one which chnages the html response on success, the other which changes the logic. Click here to skip the code.
class Subclass1Controller < SuperclassController
# We add authentication, and change the response
before_filter :require_login
def the_action
do_some_stuff
did_yet_more_stuff = if do_some_more_stuff
do_yet_more_stuff
else
do_the_other_thing
end
respond_to do |format|
if did_yet_more_stuff
format.html do
# We change the response
flash[:notice] = "Well done #{current_user.display_name}"
redirect_to the_done_url
end
format.js
else
format.html do
flash[:notice] = "Bad boy"
redirect_to the_bad_url
end
format.js { render :action => 'the_action' }
end
end
end
end
class Subclass2Controller < SuperclassController
# We add authentication, and change the logic
before_filter :require_login
def the_action
do_some_stuff
# Here we change the logic, but we have to write out the response again
did_yet_more_stuff = if do_some_more_stuff_for_user(current_user)
do_yet_more_stuff
else
do_the_other_thing
end
respond_to do |format|
if did_yet_more_stuff
format.html do
flash[:notice] = "Well done you"
redirect_to the_done_url
end
format.js
else
format.html do
flash[:notice] = "Bad boy"
redirect_to the_bad_url
end
format.js { render :action => 'the_action' }
end
end
end
end
After..
Here's the same effect, with response for. We do need to write the superclass action a little differently first. We just take the response out of the action. We need to add an instance variable, so the response can conditionally render different things.
class SuperclassController < ApplicationController
# this time with response_for
def the_action
do_some_stuff
@did_yet_more_stuff = if do_some_more_stuff
do_yet_more_stuff
else
do_the_other_thing
end
end
response_for :the_action do |format|
if @did_yet_more_stuff
format.html do
flash[:notice] = "Well done you"
redirect_to the_done_url
end
format.js
else
format.html do
flash[:notice] = "Bad boy"
redirect_to the_bad_url
end
format.js { render :action => 'the_action' }
end
end
end
Now, our two subclassed controllers look like this:
class Subclass1Controller < SuperclassController
# We add authentication, and change the response
before_filter :require_login
# we only override the repsonse that has changed
response_for :the_action do |format|
if @did_yet_more_stuff
format.html do
flash[:notice] = "Well done #{current_user.display_name}"
redirect_to the_done_url
end
end
end
end
class Subclass2Controller < SuperclassController
# We add authentication, and change the logic
before_filter :require_login
def the_action
do_some_stuff
# Here we change the logic, we don't need to touch the response
@did_yet_more_stuff = if do_some_more_stuff_for_user(current_user)
do_yet_more_stuff
else
do_the_other_thing
end
end
end
The rewrite
The original response_for had to do some pretty tricky messin about to kick in once the action had done its thing. Things got a whole lot easier when actionpack introduced the default_render hook.
default_render is called after your action if a response has not explicitly been rendered (or redirected). You can use this yourself in your controllers if you want to do something other than just rendering the default template. response_for alias_method_chains it to render any declared responses..
If you're a previous response_for user
Along with the rewrite, I changed the way repsonse_for works. It used to intercept respond_to, so that you could override any repsonse declared in your action. This resulted in a bunch of weird edge cases, and so I decided to ditch it (read more about this at the end of the response_for README). If you need the old functionality use the 0.1-stable branch.
inherit_views | response_for
ardes plugins available github and lighthouse
April 30th, 2008
Many of our plugins are now available on github: github.com/ianwhite
And OSS projects such as these can now be hosted free on lighthouse: ianwhite.lighthouseapp.com
The ones that you see on lighthouse have all had a good dusting off to make sure they're compliant with the very latest edge, and BC to 2.0.2. response_for is now branched to support edge and 2.0.2, but the other plugins haven't yet required this.
If you don't see one you use there, it means that I haven't deemed it being used by many people - so leave a comment to say otherwise
resources_controller new trunk
February 26th, 2008
There's going to be some new features coming in resources_controller. Stay tuned...
Meanwhile, rc has got a new repository location (the old one will still work for a while, but will remain at the current version):
trunk: http://svn.ardes.com/resources_controller/trunk/resources_controller
tag 0.5: http://svn.ardes.com/resources_controller/tags/0.5/resources_controller
For those interested in providing patches, you should checkout http://svn.ardes.com/resources_controller/trunk and run rake pre_commit in that directory to check your changes. Then send a diff along, or post it on the google group.
response_for_resources_controller
November 20th, 2007
If you're using response_for, then you might want to grab response_for_resources_controller
It simply replaces the default action modules with ones that use the response_for idiom.
This means that you can override what an action does without overriding how it responds. For example, I want a standard REST controller, that sets a protected attribute, user, on create.
class PostsController < ApplicationController
resources_controller_for :posts
def create
self.resource = new_resource
resource.user = current_user
save_resource
end # the standard response_for :create is used
end
If you don't know what response_for does, it lets you declaratively set the respond_to of an action. For example: if you want to override some aspect of the response, here I just want to change the flash message on successful create:
class LoginsController < ApplicationController
resources_controller_for :logins
response_for :create do |format|
format.html { flash[:notice = "You have logged in" } if resource_saved?
end
end
miscellany
I added save_resource and resource_saved? to rc, as a way of sharing the result of the save outside the scope of the action. Previously I've done this with valid?, but realised that this might hit the database unnecessarily in some circumstances. You can use either idiom.
introducing response_for: decorate your actions with respond_to blocks
October 23rd, 2007
If you've ever subclassed a controller to deal with a particular MIME type, say FBML, you will have had the painful experience of having to copy and paste all of your contoller's actions, adding, or replacing the fbml blocks into the code.
This doesn't feel right - and it isn't. If you have to change some controller logic - you have to remember to change it in all these subclassed controllers.
Enter response_for (rdoc here).
response_for lets you declaratively specify how your actions should respond to particular Mime types. This helps particularly when your action's logic is not simple (like a CRUD action - but it still helps there). Here's an example I just made up:
class ReportController < ApplicationController
# this puppy gets some models to collaborate to form a report
def report
@report = {}
@report[:invoiced] = Customer.find_invoiced
@report[:outstanding] = @report[:invoiced].select {|c| c.latest_invoice.outstanding? }
respond_to do |format|
format.html
format.xml { # some funky xml stuff here }
end
end
end
Let's say I wanted to make FBML responding version of this controller, normally, I'd have to copy/paste this action and add the fbml responses. With response_for, I do this:
class FacebookReportController < ReportController
response_for :report do |format|
format.fbml { # my funky fbml stuff here, accessing @report}
end
end
So, down the line, if I change the way my @report object is generated, I don't have to change my subclassed controllers.
Another example: create
You can choose to replace the respond_to block of the action as well. This means that common actions can be written, and the response can be adjusted according to your needs (resources_controller comes to mind).
Here's a standard create action:
class ForumsController < ApplicationController
def create
@forum = Forum.new(params[:forum])
@forum.save
respond_to do |format|
if @forum.valid?
flash[:notice] = 'Forum was successfully created.'
format.html { redirect_to(@forum) }
format.xml { render :xml => @forum, :status => :created, :location => @forum }
else
format.html { render :action => "new" }
format.xml { render :xml => @forum.errors, :status => :unprocessable_entity }
end
end
end
end
Let's say you want to create an FBML only version of this:
class FacebookForumsController < ForumsController
response_for :create, :replace => true do |format|
if @forum.valid?
format.fbml { # funky fbml stuff for valid record }
else
format.fbml { # and for an invalid record}
end
end
end
One last example
For many actions, you just want to tell your action to respond to particular MIME type and a template does the rest:
class FacebookUsersController < UsersController response_for :index, :show, :types => [:fbml] end
You don't need to have a respond_to block defined in your actions - the parent controller could look like:
class UsersController < ApplicationController
def index
@users = User.find :all
end
def show
@user = User.find params[:id]
end
end
resources_controller + response_for
For those users of resources_controller, response_for gives you an easy way of adding responses on an ad-hoc basis (like if only one or two of your actions need to be RSS feeds or summat)
This is the first release...
and I welcome any bug reports
50% less code with resources_controller
October 16th, 2007
Jason Lee of Big First Name just posted his experiences of using resources_controller on the google group.
He writes:
I've just converted one my existing apps ( http://big.first.name ) to using the resources_controller plugin and thought I'd share some results with the group...
before RC, the output of "rake stats"...
+---------------+-------+-------+---------+--------- | Name | Lines | LOC | Classes | Methods | Controllers | 1785 | 1324 | 20 | 112
after RC conversion (with only minor refactoring)...
+---------------+-------+-------+---------+--------- | Name | Lines | LOC | Classes | Methods | Controllers | 877 | 655 | 20 | 45
That's 50% less code and 60% less methods. I've still got some chunky controller code lying around but overall I'm very happy with the result.
That's cool!
resources_controller at LRUG
October 10th, 2007
I gave a talk about resources_controller at LRUG which was great fun.
Thanks to everyone for listening and for the feedback. Skills Matter are apparently going to post a video of the presentation at some point. In the meantime, the slides are here.
RailsConf Europe and resources_controller
September 19th, 2007
I've been chatting to a few people at RailsConfEuropoe about resources_controller, so I thought I'd say a few words about waht it's key features are, and about RC at RailsConfEuropoe in general.
Key features
There's a few plugins out there that try and solve the same sort of problem - DRY up RESTful controllers. I believe that RC's standout features can be seen when considering how to write controller for a polymorhpic has_many relationship.
Polymorphic Tags
So you want to tag a bunch of models, and so you sure the :polymorphic has_many assoc, and make a Tag model with belongs_to :taggable, :polymorphic => true.
You want tags to be nested under a bunch of different resources like this:
map.resources :users do |user| user.resources :tags, :controller => 'user_tags' end map.resources :posts do |post| post.resources :tags, :controller => 'post_tags' end
Standardly, you'd then have to write two controllers: UserTagsController, and PostTagsController, and map the above two nested routes to those different controllers. These would be essentially the same functionality except:
- they would load different models in before filters:
@userin one and@postin the other, - they get the post from different collections (
@tag = @user.tags.find(params[:tags_id])vs@tag = @post.tags.find(params[:tags_id])), - they redirect to different routes on completion of certain actions
user_tags_pathandpost_tags_pathin the other.
To do this, even with plugins to dry everything up, you still need to create two (or three, or four) controllers for tags - all doing essentially the same thing.
it gets worse. You'll need a bunch of different views - because they all need to link to urls relative to the enclosing resource. Suddenly you've got a lot of really similar code - or some really ungly hacks in your views.
(and all of this gets much worse if you have deeply nested routes)
Polymorphic tags with resources_controller
resources_controller (used in the default way) inspects the route that was used to invoke the controller. From this, it:
- loads all of the enclosing resources,
- uses the immediately enclosing resource as the resource service (the object that we send find and new to to - in the case of /posts it would be the Post class, in the case of /post/1/tags it would be the @post.tags association),
- does some method missing magic so that you can refer to all named routes relative to the current resource
All this means you just need to write one controller, and one set of views for Tags
Here's some sample code
class TagsController < ApplicationCntroller
resources_controller_for :tags
end
in show.html.erb:
<%= link_to 'tags', resources_path %>
The above will be user_tags_path(@user) in one case and post_tags_path(@post) in another.
It gets better, you can refer to the enclosing resource as well:
<%= link_to "back to #{enclosing_resource_name.humanize}", enclosing_resource_path %>
And if you have routes like /users/1/posts/2/tags, and /posts/1/tags, the you can use the same view for posts:
<%= link_to 'tags', resource_tags_path %> # in /users/1/posts/2/tags will be:
# user_post_tags_path(@user, @post)
<%= link_to 'tags', resource_tags_path %> # in /posts/2/tags will be:
# post_tags_path(@post)
That's just some of the features, I'd love to get feedback, patches, bug reports, etc. There are links to RC via svn, and rdoc on our plugins page
BoF and RejectConf
Man, I've got a lot to learn about presentations...
I gave a BoF at RailsConfEurope07 session on resources_controller - I was expecting about 10 people and a round table discussion on taking the pain out of RESTful controllers. About 50 or 60 people turned up so we ended up all crowded round a couple of laptops. But everyone was friendly and there was no heckling. Paolo, who gave the incredibly entertaining talk on widgets, took some photos
There was, however, plenty of heckling at the RejectConf talk. The format was 5 minutes of slides, 20 seconds automatic countdown for each one. I wrote the presentation during the day, and ran through it with my wife before hand. It sucked - way too much info. So I cut it down.
However, I was first up, and gave the audience the choice of the insane, or sane, talk, and they chose insane. I tried to get across about 1/2 an hour's worth of stuff in 5 minutes, and the audience looked as though they were in a wind tunnel. It was a great icebreaker for the other speakers though. I'll post the slides here in due course.
RejectConf was simply awesome by the way. The berlin ruby user group rocks.
resources_controller - update
September 5th, 2007
It's been a while, but some major improvements to resources_controller have just been checked in.
The test coverage has slipped just a bit - that's my next task. However, it's still pretty good. Thanks to the RC group (in particluar Chris Hapgood) and the Guys at Greenvoice for nudging me towards singular resources, and away from lots of inherited controllers.
Major headlines
- Singular resources are fully supported
- RC loads enclosing resources by default, which means you pretty much just have to write one line to have all your routes taken care of (more on this below).
- Cleaner code
Go get get it from the ArDes plugins page.
What follows is from the rdoc
With resources_controller (http://svn.ardes.com/rails_plugins/resources_controller) you can quickly add an ActiveResource compliant controller for your your RESTful models.
Examples
Here are some examples - for more on how to use RC go to the Usage section at the bottom, for syntax head to resources_controller_for
Example 1: Super simple usage
Here's a simple example of how it works with a Forums has many Posts model:
class ForumsController < ApplicationController
resources_controller_for :forums
end
Your controller will get the standard CRUD actions, @forum will be set in member actions, @forums in index.
Example 2: Specifying enclosing resources
class PostsController < ApplicationController
resources_controller_for :posts, :in => :forum
end
As above, but the controller will load @forum on every action, and use @forum to find and create @posts
Wildcard enclosing resources
All of the above examples will work for any routes that match what it specified
PATH RESOURCES CONTROLLER WILL DO:
Example 1 /forums @forums = Forum.find(:all)
/users/2/forums @user = User.find(2)
@forums = @user.forums.find(:all)
Example 2 /posts @posts = Post.find(:all)
/forums/2/posts @forum = Forum.find(2)
@posts = @forum.posts.find(:all)
/sites/4/forums/3/posts @site = Site.find(4)
@forum = @site.forums.find(3)
@posts = @forum.posts.find(:all)
/users/2/posts/1 This won't work as the controller specified
that :posts are :in => :forum
It is up to you which routes to open to the controller (in config/routes.rb). When you do, RC will use the route segments to drill down to the specified resource. This means that if User 3 does not have Post 5, then /users/3/posts/5 will raise a RecordNotFound Error. You dont' have to write any extra code to do this oft repeated controller pattern.
With RC, your route specification flows through to the controller - no need to repeat yourself.
If you don't want to have RC match wildcard resources just pass :load_enclosing => false
resources_controller_for :posts, :in => :forum, :load_enclosing => 'false'
Example 3: Singleton resource
Here's an example of a singleton, the account pattern that is so common.
class AccountController < ApplicationController
resources_controller_for :account, :class => User, :singleton => true do
@current_user
end
end
Your controller will use the block to find the resource. The @account will be assigned to @current_user
Example 4: Allowing PostsController to be used all over
First thing to do is remove :in => :forum
class PostsController < ApplicationController
resources_controller_for :posts
end
This will now work for /users/2/posts.
Example 4 and a bit: Mapping non standard resources
How about /account/posts? The account is found in a non standard way - RC won't be able to figure out how tofind it if it appears in the route. So we give it some help.
(in PostsController)
map_resource :account, :singleton => true, :class => User, :find => :current_user
Now, if :account apears in any part of a route (for PostsController) it will be mapped to (in this case) the current_user method of teh PostsController.
To make the :account mapping available to all, just chuck it in ApplicationController
This will work for any resource which can't be inferred from its route segment name
map_resource :peeps, :source => :users map_resource :posts, :class => BadlyNamedPostClass
Example 5: Singleton association
Here's another singleton example - one where it corresponds to a has_one or belongs_to association
class ImageController < ApplicationController
resources_controller_for :image, :singleton => true
end
When invoked with /users/3/image RC will find @user, and use @user.image to find the resource, and @user.build_image, to create a new resource.
Putting it all together
An exmaple app
config/routes.rb:
map.resource :account do |account| account.resource :image account.resources :posts end map.resources :users do |user| user.resource :image user.resources :posts end map.resources :forums do |forum| forum.resources :posts forum.resource :image end
app/controllers:
class ApplicationController < ActionController::Base map_resource :account, :singleton => true, :find => :current_user def current_user # get it from session or whatnot end class ForumsController < AplicationController resources_controller_for :forums end class PostsController < AplicationController resources_controller_for :posts end class UsersController < AplicationController resources_controller_for :users end class ImageController < AplicationController resources_controller_for :image, :singleton => true end class AccountController < ApplicationController resources_controller_for :account, :singleton => true, :find => :current_user end
This is how the app will handle the following routes:
PATH CONTROLLER WHICH WILL DO:
/forums forums @forums = Forum.find(:all)
/forums/2/posts posts @forum = Forum.find(2)
@posts = @forum.forums.find(:all)
/forums/2/image image @forum = Forum.find(2)
@image = @forum.image
/image no route
/posts no route
/users/2/posts/3 posts @user = User.find(2)
@post = @user.posts.find(3)
/users/2/image POST image @user = User.find(2)
@image = @user.build_image(params[:image])
/account account @account = self.current_user
/account/image image @account = self.current_user
@image = @account.image
/account/posts/3 PUT posts @account = self.current_user
@post = @account.posts.find(3)
@post.update_attributes(params[:post])
Views
Ok - so how do I write the views?
For most cases, just in exactly the way you would expect to. RC sets the instance variables to what they should be.
But, in some cases, you are going to have different variables set - for example
/users/1/posts => @user, @posts /forums/2/posts => @forum, @posts
Here are some options (all are appropriate for different circumstances):
- test for the existence of @user or @forum in the view, and display it differently
- have two different controllers UserPostsController and ForumPostsController, with different views (and direct the routes to them in routes.rb)
- use enclosing_resource - which always refers to the... immediately enclosing resource.
Using the last technique, you might write your posts index as follows (here assuming that both Forum and User have .name)
<h1>Posts for <%= link_to enclosing_resource_path, "#{enclosing_resource_name.humanize}: #{enclosing_resource.name}" %></h1>
<%= render :partial => 'post', :collection => @posts %>
Notice enclosing_resource_name - this will be something like 'user', or 'post'. Also enclosing_resource_path - in RC you get all of the named route helpers relativised to the current resource and enclosing_resource. See NamedRouteHelper for more details.
This can useful when writing the _post partial:
<p>
<%= post.name %>
<%= link_to 'edit', edit_resource_path(tag) %>
<%= link_to 'destroy', resource_path(tag), :method => :delete %>
</p>
when viewed at /users/1/posts it will show
<p> Cool post <a href="/users/1/posts/1/edit">edit</a> <a href="js nightmare with /users/1/posts/1">delete</a> </p> ...
when viewd at /forums/1/posts it will show
<p> Cool post <a href="/forums/1/posts/3/edit">edit</a> <a href="js nightmare with /forums/1/posts/3">delete</a> </p> ...
This is like polymorphic urls, except that RC will just use whatever enclosing resources are currently loaded to generate the urls/paths.
General Usage
To use RC, there are just three class methods on controller to learn.
resources_controller_for name, options, &block nested_in name, options, &block map_resource name, options, &block
Customising finding and creating
If you want to implement something like query params you can override find_resources. If you want to change the way your new resources are created you can override new_resource.
class PostsController < ApplicationController
resources_controller_for :posts
def find_resources
resource_service.find :all, :order => params[:sort_by]
end
def new_resource
returning resource_service.new(params[resource_name]) do |post|
post.ip_address = request.remote_ip
end
end
end
In the same way, you can override find_resource.
Writing controller actions
You can make use of RC internals to simplify your actions.
Here's an example where you want to re-order an acts_as_list model. You define a class method on the model (say *order_by_ids* which takes and array of ids). You can then make use of *resource_service* (which makes use of awesome rails magic) to send correctly scoped messages to your models.
Here's how to write an order action
def order
resource_service.order_by_ids["things_order"]
end
the route
map.resources :things, :collection => {:order => :put}
and the view can conatin a scriptaculous drag and drop with param name 'things_order'
When this controller is invoked of /things the :order_by_ids message will be sent to the Thing class, when it's invoked by /foos/1/things, then :order_by_ids message will be send to Foo.find(1).things association
ArDes Rails Plugins Page
June 7th, 2007
Check out our rails plugins page, which contains links to rdoc, svn and other plugin related stuff.
You may also be interested in the latest build report which shows what plugins are tested against what versions of rails.
Announcing inherit_views
February 23rd, 2007
Edge rails recently added support for multiple view paths. Very cool. This means you can ship a plugin complete with views, and have your controllers use those views (without copying them to app/views), and you get overriding for free.
inherit_views (svn and rdoc) is a plugin that allows you to do something similar, but within a controller hierarchy. (This is not a replacement, or duplication of 'multiple view paths')
Example
Here's an example of the syntax
class PostsController < ApplicationController inherit_views end class UserPostsController < PostsController end
UserPostsController will look for a particular view in app/views/user_posts, if it doesn't find it, then it looks in app/views/posts.
For the example above, you may wish to change some aspects of just index.rhtml, and leave the other views alone. Previously you would have to set up a bunch of erb (or haml, or whatever) files that either duplicate, or render the unchanged files. No longer.
render_parent
The inheritance model is familiar, so why not have the familiar 'super' construct in views?. You can now:
posts/_resource.rhtml:
<%= post.title %>
<%= link_to 'comments', comments_path(post) %>
user_posts/_resource.rhtml:
<%= render_parent :locals => {:post => post} %>
<%= link_to 'my comments', user_comments_path(@user, post) %>
Why?
- Making life for STI users (I'm a heavy user) easier. This is a classic case where inheriting views makes life easier. It's also easier if you use something like resources_controller to decouple the model name form the view (instead of @my_model - which then becomes @my_model_subclass, use a local, or in the case of resources_controller resource)
- Providing the basic tools to build auto-scaffold type systems. I think the idea of these systems is great - but they never work quite right for me. For me, they cover too much ground, make too many assumptions. So, I'd like to have a set of tools that help me easily build one of these auto-scaffold things for use in just one, or a few projects. inherit_views and resources_controller are two such tools.
Two main reasons: