resource_this - DRY Rails Resource Controllers

I’ve always been annoyed at the lack of maintainability that comes with using multiple resource controllers in my Rails apps. Each generated resource controller clocks in at 85 lines, and most of mine only differ from each other by a line or two – an added before_filter or a change in the url that the users is redirected to after the creation of a new Widget. Not very DRY. When coming back to each one of these controllers to add or adjust features, it takes me entirely too much time to sift through the stock 85 lines and find my application-specific behavior.

Enter resource_this

git clone git://github.com/jnewland/resource_this.git

resource_this aims to solve this maintainability problem by making your stock resource controllers look like this:

  class PostsController < ActionController::Base
   resource_this
  end

Behind the scenes, this code is generated:

  class PostsController < ActionController::Base
    before_filter :load_post, :only => [ :show, :edit, :update, :destroy ]
    before_filter :load_posts, :only => [ :index ]
    before_filter :new_post, :only => [ :new ]
    before_filter :create_post, :only => [ :create ]
    before_filter :update_post, :only => [ :update ]
    before_filter :destroy_post, :only => [ :destroy ]

  protected
    def load_post
      @post = Post.find(params[:id])
    end

    def new_post
      @post = Post.new
    end

    def create_post
      @post = Post.new(params[:post])
      @created = @post.save
    end

    def update_post
      @updated = @post.update_attributes(params[:post])
    end

    def destroy_post
      @post = @post.destroy
    end

    def load_posts
      @posts = Post.find(:all)
    end

  public
    def index
      respond_to do |format|
        format.html
        format.xml  { render :xml => @posts }
        format.js
      end
    end

    def show          
      respond_to do |format|
        format.html
        format.xml  { render :xml => @post }
        format.js
      end
    end

    def new          
      respond_to do |format|
        format.html { render :action => :edit }
        format.xml  { render :xml => @post }
        format.js
      end
    end

    def create
      respond_to do |format|
        if @created
          flash[:notice] = 'Post was successfully created.'
          format.html { redirect_to @post }
          format.xml  { render :xml => @post, :status => :created, :location => @post }
          format.js
        else
          format.html { render :action => :new }
          format.xml  { render :xml => @post.errors, :status => :unprocessable_entity }
          format.js
        end
      end
    end 

    def edit
      respond_to do |format|
        format.html
        format.js
      end
    end

    def update
      respond_to do |format|
        if @updated
          flash[:notice] = 'Post was successfully updated.'
          format.html { redirect_to @post }
          format.xml  { head :ok }
          format.js
        else
          format.html { render :action => :edit }
          format.xml  { render :xml => @post.errors, :status => :unprocessable_entity }
          format.js
        end
      end
    end

    def destroy          
      respond_to do |format|
        format.html { redirect_to :action => posts_url }
        format.xml  { head :ok }
        format.js
      end
    end
  end

Nested resources like so:

  class CommentsController < ActionController::Base
    resource_this :nested => [:posts]
  end

This generates a very similar controller to the one above with adjusted redirects and one additional before_filter / loader method pair to grab the parent resource. In this case:

  before_filter :load_post

  def load_post
    @post = Post.find(params[:post_id])
  end

The separation of logic – DB operations in before_filters, rendering in the standard resource controller methods – makes this approach ridiculously easy to customize. Need to load an additional object for the :show action? Slap another before_filter on it. Need to change the path that the :update action redirects to? Override the :update action with your new rendering behavior. And this customized behavior sticks out like a sore thumb – making it infinitely easier to maintain.

Oh, there’s also a generator:

./script/generate resource_this FooKlass [title:string body:text]

This works just like the resource generator, with the addition of the resource_this line to your controller and a functional test. No views are generated, so the test focuses on the XML behavior of this controller.

Contributing

resource_this is hosted on GitHub, so feel free to fork it and send a pull request with your changes.

Joyent Slingshot Demo Notes

Eric Wagoner gave a demo of the Joyent’s newly released offline Rails application toolkit, Slinghot. He’s had access to Slingshot for a couple weeks ahead of the public release, and shared his early impressions with us tonight. These are rough notes from his presentation.

  • A local Slingshot app is distributed as a DMG on OS X, which is a full-stack Ruby VM plus your Rails apps. Lots of files. I mean, lots.: $ find Radiant.app | wc -l -> 8087
  • Server side, Slingshot uses a plugin that generates a ‘sync’ controller. This controller has several actions:
    • sync.up
    • sync.down
    • sync.log
  • These toss XML back and forth between the server and client app, containing the changed Models and some metadata.
  • Synchronization conflicts are handled in your application’s domain. This is important to note – Slingshot is not a silver bullet for the age old offline/online synchronization problem. However, it lets you solve this problem however your application needs.

MyConfPlan Javascript Widget

I’ve hacked up a simple Javascript widget for everyone out there using myconfplan, Dr Nic Williams’ wonderful conference session planning tool. Nic recently added JSON and XML feeds to myconfplan, so this is a natural progression.

The XHTML generated by this widget is in hCalendar format, with separate events for each session. If you’re using a Microformats aware browser (or the wonderful microformats bookmarklet), you’ll be able to add my RailsConf sessions to your local calendaring app.

To display your conference session selections on your blog or website, insert these two lines of code:

<script src="http://files.jnewland.com/display_myconfplan.js" type="text/javascript"></script>
<script src="JSON_URL?callback=display_myconfplan" type="text/javascript"></script>

Replace JSON_URL with the URL to the JSON feed for one of your conferences. For example, to display my RailsConf 2007 sessions in my sidebar, I used this:

<script src="http://files.jnewland.com/display_myconfplan.js" type="text/javascript"></script>
<script src="http://myconfplan.com/users/jnewland/conferences/RailsConf2007.js?callback=display_myconfplan" type="text/javascript"></script>

Now seems like a good time to mention that I’ll be at RailsConf 2007 later this month in Portland, OR, and I couldn’t be more excited about it. You can view my session selections in the sidebar, on on myconfplan. Also, I’m staying at the Jupiter Hotel, which looks like a blast. Let me know if you’ll be there too!

Capazon - Capistrano Meets Amazon EC2

UPDATE: For those looking for Capistrano 2.0 support, check out Capazon 0.2.0

Just a quick note to announce Capazon 0.1.0, a Capistrano extension library to manage Amazon EC2 instances. If you are familiar with Capistrano and have an Amazon EC2 account, give it a whirl:

  • gem install capazon
  • Edit your your config/deploy.rb:
require 'capazon'

#AWS login info
set :aws_access_key_id, 'XXX'
set :aws_secret_access_key, 'X'

# Name of the keypair used to spawn and connect to the Amazon EC2 Instance
# Defaults to one created by the setup_keypair task
set :aws_keypair_name, "#{application}-capazon"

# Path to the private key for the Amazon EC2 Instance mentioned above
# Detaults to one created by setup_keypair task
set :aws_private_key_path, "#{Dir.pwd}/#{aws_keypair_name}-key"

#defaults to an ubuntu image
#set :aws_ami_id, "ami-e4b6538d"

#defaults to, um, default
#set :aws_security_group, "default"
  • $ cap describe_images
  * executing task describe_images
IMAGE   ami-0386636a    rbuilder-online/nuxleus-1.3-x86_9327.img.manifest.xml   099034111737    available       true
IMAGE   ami-08866361    rbuilder-online/test1-1.0-x86_9326.img.manifest.xml     099034111737    available       true
IMAGE   ami-1281647b    rbuilder-online/mw-tour-1.6.8-x86_9458.img.manifest.xml 099034111737    available       true
IMAGE   ami-1681647f    rbuilder-online/mw-tour-1.6.8-x86_9459.img.manifest.xml 099034111737    available       true
  • $ AWS_AMI_ID=XXXX cap run_instance

This release just scratches the surface of what I hope to accomplish with Capazon – my end goal is to provide a shared AMI as a companion to Capazon which will encapsulate some Rails deployment best practices.

Please report any bugs you may come across, and stay tuned for updates!