State Of The Waves

0 views
Skip to first unread message

Dan Yoder

unread,
Apr 21, 2008, 3:42:48 AM4/21/08
to ruby...@googlegroups.com
Hi Everybody,

There has been a lot of activity and some really interesting design
discussion / evolution going on, so I thought I'd try to kind of take
a step back at take a look at the big picture.

The biggest concern I have continues to be the tests. We are up to 16
tests now as people have started adding tests every time they commit
something, which is AWESOME. I think we have a decent approach to
testing now which makes it pretty easy to add tests. So what I'd like
to do is to develop a list of areas where we need tests and then just
break them up. If everyone just adds a few tests each day over the
next week, we should quickly be able to compile a reasonable initial
set of tests. I think this should be our top priority for the next
week or however long until we get them done.

Lawrence moved us forward with support for Thin, and, more generally,
true support for any Rack Handler. That instantly gives us a pretty
good shot at running on everything out there. The best part about it
is that everything is just configuration options. With our inheritable
configuration feature, I think we are now in very good shape in terms
of making it almost trivial to configure Waves apps. In addition,
Matthew and Lawrence have also add a couple of more mapping methods so
that everything from threading to error-handling can now be done via
mappings.

Matthew and Pascal are doing good work on implementing pluggable ORM
support via Layers. There is some work remaining here, but I think the
design is solid and we are pretty close to being able to roll that
out, which means it will be in the next Gem. Ultimately, this will
mean that you can run 'waves --orm sequel' or whatever and it will set
up the application something like this:

module Blog
include Waves::Foundations::Default
include Waves::Layers::ORM::Sequel
end

so that it is not only trivial to set up an ORM, but trivial to
_change_ it as well.

Matthew is also working on multi-app support, which I am not sure we
want to try to squeeze into the next Gem, but we've had some good
dialog on #waves about this and I think we have a consensus about the
approach, so hopefully we will at least have an experimental version
soon.

Pascal and John have done a great job setting up the #waves IRC
channel on freenode, complete with our own bot (or bots - surf_chica
has been AWOL lately) which is now archiving the discussions for us so
you can catch up if you miss something. I think this work has greatly
accelerated our work and is an invaluable contribution.

There is so much good stuff going on I am sure I've forgotten
something. Suffice to say, we are making rapid progress, and I think
Waves is considerably more compelling already than it was when I first
announced it not even three months ago. I am really impressed with
both quantity and quality of the contributions so far and continually
humbled by the opportunity to work with all of you.

A note on the whole Foundations / Layers thing. This has confused some
and worried others. First, a concise definition: A Layer is a plug-in
basically, only it uses Gems and require / include as the means of
integration. A Foundation is a Layer that provides the interface that
Waves expects of any application module. That's it. That's all there
is to it. The concern has been that we are introducing terminology
that is potentially confusing, given that a Layer isn't really a new
concept, per se, so why have a new word for it?

My only answer is that we can't really call it a module because that
is too vague and because Layers aren't really typical Ruby modules. We
also can't call it a plug-in because, at least within the Ruby Web app
framework world, Rails has kind of hijacked the term. Thus, while I
remain open to debating the terminology, I think we do need a distinct
word for it.

Some of you may have noticed that I have begun to pick up my
involvement again, thank goodness. There has been an interesting
little side thing going on where I signed on at YellowPages.com (YPC)
to lead their strategic R&D development group. This initially took me
away from Waves quite a bit, but the long-term payoff is that YPC is
now actively using Waves for new development projects. YPC has not
formally endorsed or embraced the project yet (this takes some time),
but this is an exciting step forward for our little framework, since
YPC is one of the biggest Ruby shops (in terms of site traffic) in the
world. We are also hiring, so if you are interested in working on
Waves as part of your actual job, please let me know!

My own focus going forward will be on removing the blocking for
threaded servers. Lawrence had a great suggestion, which was to keep
the current dispatcher as the default for threaded servers as a
"paranoid" implementation, and to implement the non-blocking version
as a second option. Since the dispatcher is actually set in the
configuration file, this makes perfect sense. A second priority will
be getting Waves running on JRuby, so it can take advantage of Java's
mature native threading implementation. Third will be setting up some
decent benchmarking so that (a) we can prove that Waves runs pretty
fast and (b) so that I can win a bet I have with Lawrence about JRuby
Waves versus Thin Waves (threaded versus evented).

Finally, regarding the release plan, I think we have enough stuff in
our current bag to actually skip right to 0.8.0 ONCE WE HAVE ENOUGH
TESTS! :) Hopefully, we can do this in early May. My biggest concern
after the tests is the docs and making sure we have a conversion
script or at least a guide of some kind for 0.7.2 apps. I am going to
be converting some apps over, so I will have a good handle on this
soon. (Also, this is partly why the Web site hasn't been updated
lately, because I need to get the Pages CMS running on Waves edge).

I can say that I am really excited about getting this one out the
door. I think it will be a major step forward.

Anyway, thanks again for all your hard work!

Regards,
Dan

Lawrence Pit

unread,
Apr 21, 2008, 9:12:45 AM4/21/08
to ruby...@googlegroups.com

from doc of Waves::Controllers::Mixin :

> Controllers do not determine which view will be invoked.

Who does determine which view will be invoked??

Suppose I have:

path %r{^/posts(?:\.(.*?))$}, :method => :get do | format |
resource( :post ) { ... }
end

Format can be say html, xml, rss, atom, csv ... where am I supposed to
handle these different formats if not in the controller?


Suppose for whatever reason I do want to render the view with a call
from a controller method. What would that look like?


Lawrence

Pascal

unread,
Apr 21, 2008, 2:37:58 PM4/21/08
to ruby...@googlegroups.com
On Apr 21, 2008, at 6:12 AM, Lawrence Pit wrote:


from doc of Waves::Controllers::Mixin :

Controllers do not determine which view will be invoked.

Who does determine which view will be invoked??

Suppose I have:

 path %r{^/posts(?:\.(.*?))$}, :method => :get do | format |
   resource( :post ) { ... }
 end

my own decoding from:

              resource( model ) {  controller { find( name ) } | view { |data| editor( model => data ) } }    

is the "editor" is the name of the template.

Cheers,
Pascal.
--

Matthew King

unread,
Apr 21, 2008, 3:17:34 PM4/21/08
to ruby...@googlegroups.com

On Apr 21, 2008, at 1:37 PM, Pascal wrote:
>
> On Apr 21, 2008, at 6:12 AM, Lawrence Pit wrote:
>>
>>
>> from doc of Waves::Controllers::Mixin :
>>
>>> Controllers do not determine which view will be invoked.
>>
>> Who does determine which view will be invoked??
>>
>> Suppose I have:
>>
>> path %r{^/posts(?:\.(.*?))$}, :method => :get do | format |
>> resource( :post ) { ... }
>> end
>
> my own decoding from:
>
>> resource( model ) { controller { find( name ) } |
>> view { |data| editor( model => data ) } }
>
> is the "editor" is the name of the template.

The argument to resource() sets the name of the controller and view.

In the view method's block, method_missing renders a template based
on the method name.


Dan Yoder

unread,
Apr 21, 2008, 4:39:41 PM4/21/08
to ruby...@googlegroups.com
You can also use the mapping pattern to extract the format (or use the
accepts header, whatever) and pass it to the view method. As with
almost everything in Waves, you can provide your own View or
Controller mixins to change this behavior, but the Waves default idiom
is to pass an assigns hash as the argument to the view method. These
become instance variables accessible from within the view template.
Alternatively, you can also just switch on the designed format
directly within the mapping block. It might be worthwhile enhancing
the view class to check the file extension or accepts header and
incorporate that into the template selection. I have done this before
in Rails and I've seen other implementations, where the templates are
then given names like show.xml.erb. If such a template can't be found,
then the simpler form is used (ex: show.erb).
Dan

ab5tract

unread,
Apr 21, 2008, 5:50:36 PM4/21/08
to rubywaves
> ... and I've seen other implementations, where the templates are
> then given names like show.xml.erb. If such a template can't be found,
> then the simpler form is used (ex: show.erb).

i was wondering why waves wasn't using this format by default. the
mapping mechanism almost implies multiple versions (or
implementations) of a view.

Lawrence Pit

unread,
Apr 21, 2008, 8:01:11 PM4/21/08
to ruby...@googlegroups.com

> The argument to resource() sets the name of the controller and view.

I need to determine which template to use based on various business
rules that involves querying the request object and the database.

I don't see who else but the controller should contain all this logic.
Putting it in a mapping rule seems mis-use of what mapping is about.


> You can also use the mapping pattern to extract the format (or use the
> accepts header, whatever) and pass it to the view method.

For this a helper method would be useful because this involves quite a
bit of logic (esp. parsing the accepts header looking for highest priority).


> As with
> almost everything in Waves, you can provide your own View or
> Controller mixins to change this behavior, but the Waves default idiom
> is to pass an assigns hash as the argument to the view method. These
> become instance variables accessible from within the view template.
>

so if I want to use a handful of variables in my view, the controller
method should return a hash. ok.


> Alternatively, you can also just switch on the designed format
> directly within the mapping block. It might be worthwhile enhancing
> the view class to check the file extension or accepts header and
> incorporate that into the template selection. I have done this before
> in Rails and I've seen other implementations, where the templates are
> then given names like show.xml.erb. If such a template can't be found,
> then the simpler form is used (ex: show.erb).
>

That's the sort of thing I'm looking for yes.


In my master branch I've added a render method:

http://tinyurl.com/4xrbz4

This allows e.g. :

path %r{^/posts/?}, :method => :get do
resource( :post ) { controller { all } }
end

class Blog::Controllers::Post
def all
data = model.all
render { list( :posts => data) }
end
end


This goes against the comments provided in Waves::Controllers::Mixin :

"
# Since the mapping file handles "glueing" controllers to views,
controllers
# don't have to be at all concerned with views. They don't have to set
# instance variables, layouts, or contain logic to select the appropriate
# view based on the request. All they do is worry about updating the model
# when necessary based on the request.
"

But then it also says:

"
# In some cases, controllers can choose to
# directly modify the response and possibly even short-circuit the view
# entirely. A good example of this is a redirect.
"

Lawrence

Dan Yoder

unread,
Apr 22, 2008, 1:53:11 AM4/22/08
to ruby...@googlegroups.com
> I need to determine which template to use based on various business
> rules that involves querying the request object and the database.

That is not a use case I had considered (where the view selection
logic is non-trivial).

In that case, adding a render mechanism to the controller is perhaps
your best option.

> I don't see who else but the controller should contain all this logic.
> Putting it in a mapping rule seems mis-use of what mapping is about.

Can you elaborate on this a bit? I don't really mind supporting a
render method in the controller, since all the mapping lambda needs to
do is return a string (in the simplest case). That said, my intent was
decouple the view and the controller. IMHO, the controller should not
be responsible for selecting the view because this gives the
controller too much to do and limits reuse.

> > You can also use the mapping pattern to extract the format (or use the
> > accepts header, whatever) and pass it to the view method.
> For this a helper method would be useful because this involves quite a
> bit of logic (esp. parsing the accepts header looking for highest priority).

I would argue parsing the accepts header probably belongs in
Waves::Request#accepts or something like this, not in the controller.
I am curious, though: where are you actually running into a
requirement to parse it? I've never had to do that, just pick off the
first mime-type of the string.

> so if I want to use a handful of variables in my view, the controller
> method should return a hash. ok.

No, just the data. The view BLOCK then can pass a hash to the view
METHOD if needed.

> That's the sort of thing I'm looking for yes.

This would be my preferred approach: create an enhanced view class
that checks (parses?) the accept header and / or takes an optional
format parameter, and then selects the view based on the format if
possible.

> In my master branch I've added a render method:

> [...]


> This goes against the comments provided in Waves::Controllers::Mixin :

> [...]


>
> But then it also says:
> # In some cases, controllers can choose to
> # directly modify the response and possibly even short-circuit the view
> # entirely. A good example of this is a redirect.

I think you've got it down pretty well. If the requirements really
requires the controller to do the render, then you can do that. I have
been reluctant to provide first-class controller-based rendering
support but it probably should be there for folks who really need it
or just are more comfortable doing that. In fact, there is nothing to
prevent creating a controller mixin that automatically calls render
based on the controller method name, ala Rails.

Regards,
Dan

Lawrence Pit

unread,
Apr 22, 2008, 9:42:21 PM4/22/08
to ruby...@googlegroups.com

> I don't see who else but the controller should contain all this logic.
> Putting it in a mapping rule seems mis-use of what mapping is about.
Can you elaborate on this a bit?
I've got this map rule:

map %r{^/company/(\w+)$}, :method => :get do |name|
  resource (:company) { controller { find(name) } }
end

If the request for /company/xyz is coming from a browser I'd like to show HTML output. If the request is coming from curl accepting only xml I'd like to show XML output.

How would you do that?


I would argue parsing the accepts header probably belongs in
Waves::Request#accepts or something like this, not in the controller.
  
That 's actually what I meant with a "helper method".

I am curious, though: where are you actually running into a
requirement to parse it? I've never had to do that, just pick off the
first mime-type of the string.

  
picking off the first one is already parsing... it's too much work if you repeatedly need to do that. And it's not correct. ;) The first one might be a mime type that you don't support, like "application/vnd.wap.wbxml".

So you'd need to loop through the accept-header and execute on the first one that you support AND that you want to respond to. If you find e.g. 'text/xml', which waves support, but you can't and/or don't want to respond to it in the given context, then you want to skip to the next possibility.


 so if I want to use a handful of variables in my view, the controller
 method should return a hash. ok.
    
No, just the data. The view BLOCK then can pass a hash to the view
METHOD if needed.
  
I'm missing something I guess. Suppose I want to show a page with e.g. the latest posts and in the sidebar the latest comments and the top voters. Currently I would then return from my controller like so:

   def all
     posts = model.all
     comments = ...
     topvoters = ...
     {:posts => posts, :comments => comments, :topvoters => topvoters}
   end

Then I my view I can reference @posts, @comments and @topvoters.

How would you do it?




Lawrence

Pascal

unread,
Apr 23, 2008, 2:52:20 AM4/23/08
to ruby...@googlegroups.com
On Apr 22, 2008, at 6:42 PM, Lawrence Pit wrote:

> I don't see who else but the controller should contain all this logic.
> Putting it in a mapping rule seems mis-use of what mapping is about.
Can you elaborate on this a bit?
I've got this map rule:

map %r{^/company/(\w+)$}, :method => :get do |name|
  resource (:company) { controller { find(name) } }
end

If the request for /company/xyz is coming from a browser I'd like to show HTML output. If the request is coming from curl accepting only xml I'd like to show XML output.

How would you do that?
Don't you have access to all the headers an could route to separate controller methods?  Or even a different crontroller?




I would argue parsing the accepts header probably belongs in
Waves::Request#accepts or something like this, not in the controller.
  
That 's actually what I meant with a "helper method".
I am curious, though: where are you actually running into a
requirement to parse it? I've never had to do that, just pick off the
first mime-type of the string.

  
picking off the first one is already parsing... it's too much work if you repeatedly need to do that. And it's not correct. ;) The first one might be a mime type that you don't support, like "application/vnd.wap.wbxml".

So you'd need to loop through the accept-header and execute on the first one that you support AND that you want to respond to. If you find e.g. 'text/xml', which waves support, but you can't and/or don't want to respond to it in the given context, then you want to skip to the next possibility.

 so if I want to use a handful of variables in my view, the controller
 method should return a hash. ok.
    
No, just the data. The view BLOCK then can pass a hash to the view
METHOD if needed.
  
I'm missing something I guess. Suppose I want to show a page with e.g. the latest posts and in the sidebar the latest comments and the top voters. Currently I would then return from my controller like so:

   def all
     posts = model.all
     comments = ...
     topvoters = ...
     {:posts => posts, :comments => comments, :topvoters => topvoters}
   end

Then I my view I can reference @posts, @comments and @topvoters.
Seems like a valid use case to me.

Dan Yoder

unread,
Apr 23, 2008, 4:00:23 AM4/23/08
to ruby...@googlegroups.com
> Don't you have access to all the headers an could route to separate
> controller methods? Or even a different crontroller?

That is, in fact, the point of contention.

Supposing we had a solid accepts parser so that you could say, as a
constraint, :accepts => 'text/xml' for a rule, would that help? That
was always sort of the plan.

> picking off the first one is already parsing... it's too much work if you
> repeatedly need to do that. And it's not correct. ;) The first one might be
> a mime type that you don't support, like "application/vnd.wap.wbxml".

I guess the main thing is that request[:accepts] =~
%r{application/xml} is a bad practice. ;)

> Currently I would then return from my controller like so:
>
> def all
> posts = model.all
> comments = ...
> topvoters = ...
> {:posts => posts, :comments => comments, :topvoters => topvoters}
> end
>
> Then I my view I can reference @posts, @comments and @topvoters.
> Seems like a valid use case to me.

Yes, in that case, it makes sense. I didn't fully grasp the context.
There is an entirely different discussion here about whether we can
support components and maybe even the original notion of MVC (PARC)
somehow. There is nothing to prevent you from calling a controller
from within a view, but I am undecided about this, since technically a
controller may attempt to modify the headers of the response. But at
any rate, given that these approaches are not well-understood or
-defined, a hash would be the easiest thing to do here.

Regards,
Dan

Reply all
Reply to author
Forward
0 new messages