random musing about rails

3 views
Skip to first unread message

Jay Donnell

unread,
Aug 7, 2009, 11:03:53 PM8/7/09
to rubywaves
"These sorts of things are why I love #rails. http://bit.ly/13V5sC"

This was a tweet which dan asked me to explain in more detail. I
thought this group would be a good place for that discussion so here
it is. The key piece of code is this.

class UsersController < ApplicationController::Base
respond_to :html, :xml, :json

def index
respond_with(@users = User.all)
end

def create
@user = User.create(params[:user])
respond_with(@user, :location => users_url)
end
end


So why do I like this? Well, there are two reasons. It's a nice
abstraction which keeps the book keeping from cluttering up my
controller. I.e. everything that needs to be done to return xml vs
json vs html is abstracted away. All I have to do in the controller is
worry about setting up the model (i.e. resource).

The second reason; it's a convention that removes unimportant
decisions so that I can focus on my product and not infrastructure
design. Also, since it's a rail's convention, I can grab any rails
developer and they know immediately what this is doing instead of
having to learn my apps specific way of abstracting this which is
unique to my app/company.

I say this as a comparison to typical java and php frameworks. It's
been a while since I've done any significant waves dev. What is the
canonical way of doing this in waves?

Daniel Yoder

unread,
Aug 8, 2009, 4:06:13 PM8/8/09
to ruby...@googlegroups.com
To begin, thank you Jay for posting this here and giving us an opportunity to discuss it detail. I should provide a little disclaimer about my tweet: generally, I've tried to refrain from being too critical of Rails, at least publicly, but last week was a particularly trying week, largely thanks to a variety of Rails misfeatures that led to difficult to find bugs and left me very frustrated.

That said, let me dive in to the meat of Jay's post.

The key piece of code is this.

class UsersController < ApplicationController::Base
 respond_to :html, :xml, :json

 def index
   respond_with(@users = User.all)
 end

 def create
   @user = User.create(params[:user])
   respond_with(@user, :location => users_url)
 end
end

The context here is that this is replacement for 
      respond_to do |format|
        format.html
        format.xml { render :xml => @users }
        format.json { render :json => @users }
      end
and this, in fact, can still be used to override default behavior. This is the construct that I was actually referring to in my response. (Specifically, I said it was this sort of thing that motivated me to write Waves.) The fact that Rails is now essentially building abstractions on top of this is not an improvement, it is a colossal design flaw. More on this in a second.

So why do I like this? Well, there are two reasons. It's a nice
abstraction which keeps the book keeping from cluttering up my
controller. I.e. everything that needs to be done to return xml vs
json vs html is abstracted away. All I have to do in the controller is
worry about setting up the model (i.e. resource).

The second reason; it's a convention that removes unimportant
decisions so that I can focus on my product and not infrastructure
design. Also, since it's a rail's convention, I can grab any rails
developer and they know immediately what this is doing instead of
having to learn my apps specific way of abstracting this which is
unique to my app/company.

Okay, fair enough. My frustration is with the abstraction, not the idea of abstracting this. Regarding conventions, if the convention is flawed, it actually does no good for lots of people to learn it. In fact, it's actually harmful in that you have to waste time and effort correcting it. Furthermore, what if the convention isn't necessary, in that there are already perfectly valid Ruby idioms for doing something?

Well, what is so wrong with this particular convention? To begin with, this is not REST. That often seems to strike people as pedantry, but it isn't. Roy Fielding invented the term. It has specific meanings and design goals. They aren't terribly well documented but there are numerous folks out there (including myself) who have gone to the trouble to decode his rants, so it isn't like it's impossible. And it seems to me that if you are going to use a term like REST to market what you're doing, you ought to make sure you know what it means. Otherwise, we end up spending half our time explaining that by REST, we don't mean using file extensions in URLs (which is actually the EXACT OPPOSITE of REST). 

At some point, we might as well just come up with a new term since, thanks to Rails, and misfeatures like this, most of the Rails community is now using REST differently from the guy who defined the term and co-authored the HTTP spec.

Secondly, real REST has certain design advantages that the Rails version throws away BY CONVENTION! I can't do any meaningful content negotiation because everything is just based on format: JSON or XML is not terribly meaningful, which is why people invented things like XML Schema. Am I returning a user record? An event? A list of ice-cream flavors? Who knows? What about language?  Is this the Spanish version? Chinese? Are their character set issues? Who knows?

Rails idea seems to be that everything is encoded in the URL (or is just implicit), which means the client now has to know the conventions for that application, which are not standardized anywhere, and thus make the interface very prone to hard to find bugs down the road. 

Again, if you don't care about any of that, fine, but then how, specifically, is this interface an improvement? In the comments everyone lauds the authors for this "small, but meaningful, step towards robust controller-level REST support." Well ... in this version of REST, which is essentially undefined (even more so than Roy's version), what are the benefits of this "small, but meaningful step"?

Rails is essentially redefining REST in some ad hoc way, defining a set of conventions that encourage API developers to write tightly coupled APIs, and ignoring a standard, widely implemented protocol in order to do so. All while patting themselves heartily on the back. WTF?

It gets worse, though. Even if I were to accept that semantically rich URLs is the "new REST" and that somehow was a good thing, I'd say this is a very poor way to do it. Let's start with the #responds_to class method. This new DSL allows you to define exceptions to the rule in order to return 404s, which many Rails developers will surely take advantage of ... so in reality, many apps will end up looking like this (from the same article):
      class UsersController < ApplicationController::Base
        respond_to :html, :only => :index
        respond_to :xml, :json, :except => :show
        ...

So now I can no longer look at my #show method and see, ahah!, I am only returning HTML here. Also, respond_to and respond_with (what's the difference again ... and is this related to responds_to? no? ah ... ) can still both be used within methods to take blocks of format methods, including the oxymoronic format.any to provide a default response. I can't really see how this is any different from saying something like this:

      case request.extension
      when :xml then @user.to_xml
      when :json then @user.to_json
      else render( @user )
      end

In other words, why do I need a convention at all? Any Ruby programmer will understand this code, and it does exactly the same thing as respond_*. The Rails community seems to be hell bent on creating a set of conventions that Rails programmers will understand instead of embracing those that Ruby already has ... which is somewhat parallel to what they're doing with REST.

Let's suppose though, that we ditched respond_to and format etc. Why not just put this in your application controller?

      def default_response( *formats, data ) 
        if formats.include? request.extension
          begin
            render( data )
          rescue NoTemplateError
            if data.respond_to?( convert = "to_#{request.extension}" )
              data.send( convert )
            else
              not_found
            end
          end
        end
      end

This is more explicit (again, meaning any Ruby programmer can follow along), can be overridden in controllers where the default is a bit different (yay, object-oriented programming), and it is easy to add new variations that build on it. For example, suppose, God forbid, I actually did decide that sometimes using the Accept header might be useful to determine my response? I could add a method that checks the response header and then calls #default_response as a fall back. How would I do something like that using this new respond_* DSL? 

This is where Rails advocates start trying to have it both ways. They will say, "But you _can_ do that, if you want." Or, "Feel free to submit a patch or write a plug-in." Or (of course, my favorite), "You can always write your own framework." But none of these options change the fact that the Rails core team, is, in point of fact, advocating a very flawed design that seems to almost make a point of avoiding using the Ruby language or REST.

I say this as a comparison to typical java and php frameworks.

Well, that is where the love-hate part comes in for me. I love Rails because it has made it possible for me to make a living writing Ruby code. And at one point, it was definitely state-of-the-art as far as Web frameworks go (something I happened to be following very closely circa 2004-5), so whatever flaws it had, you could chalk up to growing pains and just the fact that innovating is intrinsically error-prone. But we're 5+ years into this and, if anything, Rails seems more hell-bent than ever to fork the Ruby community and define everything in its own terms.

It's
been a while since I've done any significant waves dev. What is the
canonical way of doing this in waves?

Okay, so enough with me ranting about Rails shortcomings. What is our solution in Waves-land? At the moment, I would say we don't have one, if only because we don't have a working stable release, our documentation is woefully inadequate and out of date, and so on. I have been working away on some of these things, hoping to kick-start Waves again with an 0.9.0 release.

That said, Waves views matching the requested format as just another aspect of matching a request. You can match on the extension, the Accept header, or a combination of both. Let's just take the extension for the moment, since it is closest to the Rails examples above. Note that this is without using any helper methods, just so you can see clearly what is going on:


      on( :get,  [ 'blogs', :name ], :extension => [ :xml, :json ]  ) { 
        if instance = model.find_by_name( captured.name )
          if instance.respond_to?( convert = "to_#{request.extension}" )
            return instance.send( convert )
          end
        end
        not_found
`      }


Similarly, I can define one that renders HTML as my default:

      on( :get, :show => [ 'blogs', :name ] ) { 
        if instance = model.find_by_name( captured.name )
          view.show( instance )
        else
          not_found
        end
`      }

In a real Classic application, using the defined helpers, these would look like:

      on( :get, :show => [ 'blogs', :name ] ) { 
        view.show( controller.find )
`     }
      on( :get, [ 'blogs', 'name' ], :extension => %w( json xml ) ) {
        format( request.extension, controller.find )
      }
      on( :get, :show => [ 'blogs', :name ], :accept => 'vnd.acme.com.blog.v2+json' ) { 
        format( :json, controller.find )
`     }

Notice that I've effortlessly added a variation that checks the Accept header for a custom MIME type. The Waves approach is faster (we don't even begin processing if the file extension doesn't match), more expressive (everything is one place), more concise (especially for real world usage), and more extensible (I can trivially add actual REST support). And it doesn't rely on any funky Rails-land magic: it is just ruby code.

As awesome as that is, we are working (sort of ... first we just need to get 0.9.0 out) on a couple of variations of REST foundations, that make this even simpler. That would look something like:

      resource :entry, :expires => 3.days, [ ‘blog’, :name ]  do 
         get( 'vnd.acme.com.entry.v2+json' ) { format( :json, controller.find ) }
         get( :html ) { view.show( controller.find ) }
      end 

This DSL, in turn, would call the various #on methods as above. Which, in turn, simply define functors for #get. Which, in turn, actually defines a real #get method on the resource, that does all the matching for you. So you can still turn around and on the next line, do something like this:

      # never match POST requests for this resource
      def post ; not_found ; end

In other words, it is all just Ruby.

Thanks again for your post, Jay, and for inspiring me to think about this and articulate why I think Waves provides (or will provide, if we ever get our shit together and/or I can ever stop spending all my time debugging Rails code) a powerful alternative to Rails.

Regards,
Dan

Eero Saynatkari

unread,
Aug 9, 2009, 5:37:56 AM8/9/09
to rubywaves
Dan went over his plans, I had worked toward something of
this kind:

resource :Page do

# The information we need from URLs
url_of_form :path => 0..-1, :name

# Add some custom MIME types to help distinguish representations etc.
introduce_mime "vnd.com.example.wiki.page.editable", :exts => ".editable"


# GET
#
retrievable {

# This is just common functionality abstracted.
#
processor = lambda {|request| make_page }

# Unrendered page contents
#
representation "text/plain", "text/x-markdown" do
response_is("text/plain") { processor.call(request).raw_content }
end

# Regular rendered page
#
representation "text/html", "application/xhtml+xml" do
transitions {
view "Edit page", Site/Page/URL, "application/vnd.com.example.wiki.page.editable"

view "View raw contents", Site/Page/URL, "text/plain"
view "Show page history", Site/History/URL
view "Other pages under #{path}", Site/Pages/path
}

response_is("text/html") { processor.call request }

rendered_by Hoshi, :from => "p/page.rb"
end

# New pages are created in Pages instead.

# Editable version of existing page
#
representation "application/vnd.com.example.wiki.page.editable" do
transitions {
view "Cancel Editing", Site/Page/URL # GET default MIME
view "View raw contents", Site/Page/URL, "text/plain"
view "Show page history", Site/History/URL
view "Other pages under #{path}", Site/Pages/path
delete "Delete This Page", Site/Page/URL # DELETE
update "Commit Your Changes", Site/Page/URL # POST
}

response_is("text/html") { processor.call request }

rendered_by Hoshi, :from => "p/editable_page.rb"
end

}

end


Some specs exist in the edge repo, for example

http://github.com/waves/edge/blob/master/spec/foundations/rest/viewability_spec.rb


Excerpts from Daniel Yoder's message of Sat Aug 08 23:06:13 +0300 2009:


>
> Well, what is so wrong with this particular convention? To begin with,
> this is not REST. That often seems to strike people as pedantry, but
> it isn't. Roy Fielding invented the term. It has specific meanings and
> design goals. They aren't terribly well documented but there are
> numerous folks out there (including myself) who have gone to the
> trouble to decode his rants, so it isn't like it's impossible. And it
> seems to me that if you are going to use a term like REST to market
> what you're doing, you ought to make sure you know what it means.
> Otherwise, we end up spending half our time explaining that by REST,
> we don't mean using file extensions in URLs (which is actually the
> EXACT OPPOSITE of REST).

The way I have come to think of REST, and the problems it
presents to framework-level developers is that it is only
_descriptive_, not _definitive_. It offers no aid or guide
to the framework developer, internally.

It may be implemented any which way you desire, coupled, de-
coupled, in C without structs: the only thing that matters
when considering the RESTiness of it is the external interface
that is produced.


Eero

--
Magic is insufficiently advanced technology.

Daniel Yoder

unread,
Aug 9, 2009, 3:34:56 PM8/9/09
to ruby...@googlegroups.com
Yes, also Eero's version is at least partly coded (at least it appears
so in edge?).
Dan

John Haltiwanger

unread,
Aug 22, 2009, 5:42:52 PM8/22/09
to rubywaves

Jay donnell

unread,
Aug 22, 2009, 6:13:28 PM8/22/09
to ruby...@googlegroups.com
I keep intending to reply, but haven't taken the time to do it justice :(
Reply all
Reply to author
Forward
0 new messages