Archives

Tags

Showing articles with tag merb. Show all articles

My first Merb site

Just a quick note to my legions of loyal followers (hey, Mum), I've just finished my first website written using the Merb framework.

S'coolhouse Rock and Swing

Development travelled along relatively well. I ran into a few issues early on with dependency hell and some bugs and/or missing functionality in a few gems. I blogged about a few of my hacks.

Here is my (partial) technology stack:

  • ORM: DataMapper
  • Tests: RSpec
  • Views: HAML/SASS (which I won't be using again, even though it's very well written)
  • Hosting: Dreamhost (using Phusion Passenger, which was easy to use with a config.ru file for Rack)

I still haven't worked out how to properly get Capistrano working with the Dreamhost setup, but shit happens...

Auf wiedersehen!

REST-mapping anchor elements in Merb

One of the things I liked about the REST implementation in Rails was the ability to use an anchor to fire off a HTTP "PUT" or "DELETE" request (or, rather the illusion of such) using the link_to helper method. It basically generates a chunk of inline JavaScript in the anchors onclick event that constructs a POST form, adds a few hidden elements, and finally submits it.

I noticed the functionality was not implemented in Merb. This is not totally surprising to me. I mean, inline JavaScript is never pretty. It also means we are relying on Javascript for the link to work correctly.

Regardless of my initial inhibitions, I decided to implement the functionality into a Merb application I was working on. The main chunk came down to this mixin to Merb::AssetsMixin:

module Merb
  module AssetsMixin

    alias_method :orig_link_to, :link_to

    def link_to(name, url='', opts={})
      if opts.include?(:method)
        method = opts.delete(:method)

        if [ :put, :delete ].include? method
          opts[:onclick] = "var f = document.createElement('form'); f.style.display = 'none'; " +
                           "this.parentNode.appendChild(f); f.method = 'POST'; f.action = this.href; " +
                           "var m = document.createElement('input'); m.setAttribute('type', 'hidden'); " +
                           "m.setAttribute('name', '_method'); m.setAttribute('value', '#{method}'); " +
                           "f.appendChild(m);f.submit(); return false;"
        else
          raise ArgumentError, "The :method option only accepts :put or :delete."
        end
      end

      orig_link_to name, url, opts
    end

  end
end

It mimicks Rails behaviour. Therefore, I call it like this:

link_to "Delete", url(:post, 1), :method => :delete
# OR
link_to "Save changes", url(:post, 1), :method => :put

I also wrote a bunch of specs and threw it up on GitHub. Eventually, I plan to set it (and the rest of my hacks) up as proper Merb plugins. If anyone wants to give me a hand, feel free to fork the project(s) and do your thing...

Multiple before/after model filters with Merb

This evening I found myself in a position in which I wanted to execute several "before" methods hooks/filters on a DataMapper model in a Merb application. I had a look around in the source and API docs, but couldn't really find a nice way of doing it (even though having every hook on a seperate line is not such a bad thing in terms of readability, I suppose).

Anyway, I came up with the following mixin for Extlib (which houses DataMapper's before/after hook functionality):

module Extlib
  module Hook
    module ClassMethods

      alias_method :singular_before, :before
      alias_method :singular_after, :after

      def before(target_methods, filter_methods = nil, &block)
        insert_multiple_hooks "before", target_methods, filter_methods, &block
      end

      def after(target_methods, filter_methods = nil, &block)
        insert_multiple_hooks "after", target_methods, filter_methods, &block
      end

      private

      def insert_multiple_hooks(context, target_methods, filter_methods, &block)
        targets = [target_methods].flatten
        filters = [filter_methods].flatten

        targets.each do |target|
          filters.each do |filter|
            send("singular_#{context}", target.to_sym, filter.to_sym, &block)
          end
        end
      end

    end
  end
end

Hopefully it is fairly straight-forward. Basically, it will allow you to pass an array to the before and after methods. For example, you can do this:

before [ :create, :update ], [ :set_filename, :set_filesize, :generate_thumbnail ]

Which is equivalent to:

before :create, :set_filename
before :create, :set_filesize
before :create, :generate_thumbnail
before :update, :set_filename
before :update, :set_filesize
before :update, :generate_thumbnail

I got it loaded into my Merb app by creating an app/lib directory and requireing the contents of it from the config/init.rb file.