catch_all/2 and {response, List}

1 view
Skip to first unread message

Bryan Fink

unread,
Sep 4, 2007, 9:54:46 PM9/4/07
to erlyweb
Hi. I'm finally making the transition from ErlyWeb 0.5 to 0.6.2 (hey,
why fix what isn't broken?). I'm really excited by the new catch_all/
2 function for controllers, but I'm having some trouble.

It seems like I can't return {response, List} tuples from a function
that I have invoked through an {ewc, ...} tuple. So, if I have:

-module(mod_controller).
-export([cool_fun/2]).

catch_all(A, Params) ->
{ewc, mod_controller, cool_fun, Params}.

cool_fun(A, Params) ->
{response, [{data, "blah"}]}.

Then ErlyWeb will pass "blah" to my view module if I navigate to /
mod_controller/cool_fun/1. But, if I navigate to /mod_controller/1,
ErlyWeb will complain about an invalid response tuple.

So, I thought about just calling cool_fun/2 from catch_all/2:

catch_all(A, Params) ->
cool_fun(A, Params).

But then, of course, ErlyWeb doesn't know what function to call in the
view.

Does anyone have a solution to the invalid response tuple problem, or
does anyone know of a way to specify what view function to render your
response with?

It seems like catch_all/2 isn't well documented yet. In fact, most
places seem to refer instead to a catch_all/3. Did I miss something?

Thanks,
Bryan

P.S. Yes, it looks silly to return {response, [{data, "blah"}]}. In
reality I'm passing more than just {data, Data} in the response list -
like header settings - but I was going for the simplest example.

Yariv Sadan

unread,
Sep 5, 2007, 2:06:15 AM9/5/07
to erl...@googlegroups.com
On 9/4/07, Bryan Fink <bryan...@gmail.com> wrote:
>
> Hi. I'm finally making the transition from ErlyWeb 0.5 to 0.6.2 (hey,
> why fix what isn't broken?). I'm really excited by the new catch_all/
> 2 function for controllers, but I'm having some trouble.
>
> It seems like I can't return {response, List} tuples from a function
> that I have invoked through an {ewc, ...} tuple. So, if I have:
>
> -module(mod_controller).
> -export([cool_fun/2]).
>
> catch_all(A, Params) ->
> {ewc, mod_controller, cool_fun, Params}.
>
> cool_fun(A, Params) ->
> {response, [{data, "blah"}]}.
>
> Then ErlyWeb will pass "blah" to my view module if I navigate to /
> mod_controller/cool_fun/1. But, if I navigate to /mod_controller/1,
> ErlyWeb will complain about an invalid response tuple.

Ugh... sounds like a bug.


>
> So, I thought about just calling cool_fun/2 from catch_all/2:
>
> catch_all(A, Params) ->
> cool_fun(A, Params).
>
> But then, of course, ErlyWeb doesn't know what function to call in the
> view.
>
> Does anyone have a solution to the invalid response tuple problem, or
> does anyone know of a way to specify what view function to render your
> response with?
>
> It seems like catch_all/2 isn't well documented yet. In fact, most
> places seem to refer instead to a catch_all/3. Did I miss something?

catch_all/3 was quickly replaced by catch_all/2. I wanted to wait
before documenting it to see if anyone on the list would catch any
issues with it, which apparently did happen :)

I'll fix it by tomorrow.

Thanks,
Yariv

Bryan Fink

unread,
Sep 5, 2007, 8:58:09 AM9/5/07
to erlyweb
On Sep 5, 2:06 am, "Yariv Sadan" <yarivsa...@gmail.com> wrote:

> On 9/4/07, Bryan Fink <bryan.f...@gmail.com> wrote:
> > It seems like catch_all/2 isn't well documented yet. In fact, most
> > places seem to refer instead to a catch_all/3. Did I miss something?
>
> catch_all/3 was quickly replaced by catch_all/2. I wanted to wait
> before documenting it to see if anyone on the list would catch any
> issues with it, which apparently did happen :)

Fair enough. And actually, I hadn't remembered your post[1]
explicitly about catch_all, but only the thread[2] in which the
feature was proposed. As such, I didn't even think of adding a
catch_all/1 function to my view. I think I may be able to make that
method work for me in this case.

> I'll fix it by tomorrow.

If you can, that would be great. If it's going to be a huge pain,
I'll give catch_all/1 in my view a try.

Thanks,
Bryan

[1] http://groups.google.com/group/erlyweb/browse_frm/thread/ec33d99b1dfef58b/a907c393a02549d3
[2] http://groups.google.com/group/erlyweb/browse_frm/thread/d29415997edbdef5/966e36a959fea48c

Yariv Sadan

unread,
Sep 6, 2007, 3:07:59 AM9/6/07
to erl...@googlegroups.com
>
> Fair enough. And actually, I hadn't remembered your post[1]
> explicitly about catch_all, but only the thread[2] in which the
> feature was proposed. As such, I didn't even think of adding a
> catch_all/1 function to my view. I think I may be able to make that
> method work for me in this case.
>
> > I'll fix it by tomorrow.
>
> If you can, that would be great. If it's going to be a huge pain,
> I'll give catch_all/1 in my view a try.

Sorry, stuff came up and I wasn't able to look into it yet. But did
using catch_all in the view work?

Yariv

Bryan Fink

unread,
Sep 6, 2007, 7:19:24 PM9/6/07
to erlyweb

I hear you there - having a rather busy week myself.

In any case, it seems that I was too tired to understand the error I
was seeing if I call another function directly from catch_all/2. It
looks like it is never okay for catch_all/2 to return a {response,
List} tuple. Returning {response, [{data, "blah"}]} makes the
complaint:

yaws code at appmod:0 crashed or ret bad val:{data, "blah"}

It says that regardless of whether I have catch_all/1 defined in my
view or not.

-Bryan

Bryan Fink

unread,
Sep 7, 2007, 8:25:29 AM9/7/07
to erlyweb
Quick update. Poking at my code this morning on the T, I suddenly
remembered that in order to return a {data, X} tuple as part of a
{response, X} tuple, you have to wrap it in a {body, X} tuple. So,
instead of {response, [{data, "blah"}]}, you'll have {response,
[{body, {data, "blah"}}]}.

That solves part of my problem, at least. My code is in a bit of
disarray right now from going back and forth between so many fix
attempts, so I can't tell exactly how much this fixes. Right now it
looks like there's still an issue. I'll try to clean up this weekend
and let people know what I find.

-Bryan

Bryan Fink

unread,
Sep 9, 2007, 9:53:54 PM9/9/07
to erlyweb
Alright, so after playing some more, it looks like adding catch_all/1
to my view module works now. So, instead of having
controller:catch_all/2 return an {ewc, ...}, I can call my intended
controller function directly, and just return its return value. The
{data, ...} tuple of that return now gets passed to view:catch_all/1.
>From there, I just call my intended view function directly. It's not
perfectly beautiful, but it works for this case.

As for returning an {ewc, ...} from catch_all/2, it still seems broken
in strange ways. Yariv, have you been able to reproduce the errors
I've described, or would you like me to whip up a quick controller and
view with examples of each for you to play with?

-Bryan

Yariv Sadan

unread,
Sep 9, 2007, 10:38:28 PM9/9/07
to erl...@googlegroups.com
After returning {ewc, ...}, you should still implement catch_all/1 in
the view for the result to be rendered. It looks like what you want to
return is {replace, {ewc, ...}}. This would tell ErlyWeb to skip
sending the result to the view function. Would that work for you?

Yariv

Bryan Fink

unread,
Sep 10, 2007, 9:50:29 AM9/10/07
to erlyweb
On Sep 9, 10:38 pm, "Yariv Sadan" <yarivsa...@gmail.com> wrote:
> After returning {ewc, ...}, you should still implement catch_all/1 in
> the view for the result to be rendered. It looks like what you want to
> return is {replace, {ewc, ...}}. This would tell ErlyWeb to skip
> sending the result to the view function. Would that work for you?

Hmmm...okay, it looks like I didn't understand exactly how the ewc and
replace tuples worked. I'll need to write up an example to see the
call list for myself.

I think the part in the doc that was confusing me is where it said,
"and send the result to the view function." I assumed that was the
view function for the component function named in the ewc tuple, but
apparently that's not the whole story.

I'll see if I can suggest a wording that would have made it clearer to
me once I understand it tonight.

Thanks,
Bryan

Bryan Fink

unread,
Sep 10, 2007, 9:07:31 PM9/10/07
to erlyweb

Okay, I finally understand. I had read the wrong meaning into "Render
the component's function...". The difference between ewc and replace
is now obvious. Furthermore, I have no idea what rewrite to suggest
now, as it now seems to me like the natural reading of "Render the
component's function..." should be the right one. :P

In any case, I can now say that I know exactly which cases have
problems.

Specifically: a component cannot return a header tuple inside a
response tuple list if that component has been rendered as a result of
some other controller function returning an ewc or replace tuple.
That is, if I have this controller code:

-module(example_controller).
-export([one/1, two/1]).

one(A) ->
{ewc, "example", "two", [A]}.

two(A) ->
{response, [{header, {set_cookie, "example=check"}}, {body, {data,
"blah"}}]}.

And this view code:

<%@ one(Data) %>
<% Data %>

<%@ two(Data) %>
<% Data %>

Then, requesting /example/two behaves fine - my browser gets a
response with the text "blah" back, with a cookie. However,
requesting /example/one spits out this error:

ERROR erlang code crashed:
File: appmod:0
Reason: {invalid_response,[{header,{set_cookie,"example=check"}},
{rendered,["blah",<<"\n">>]}],
"Response values other than 'data' and 'ewc'
tuples must be enclosed a 'response' tuple. In addition, subcomponents
may only return 'data' and/or 'ewc' tuples."}
Req: {http_request,'GET',{abs_path,"/example/one"},{1,1}}

If I enclose the ewc tuple within a replace tuple, I still get the
same error.

However, if I remove the header tuple from the response list,
everything works.

You'll notice that this doesn't involve catch_all/1,2 any more, but I
do see exactly the same behavior if I use it instead of one/1 in the
example.

So, where do I go from here? Should this work, or am I using ewc and/
or replace completely inappropriately? Is the proper method to just
make the call directly? That is, should I change my controller to
read:

one(A) ->
two(A).

And my view to read:

<%@ one(Data) %>
<% two(Data) %>

It works, as far as there being no errors. But, in the case of
catch_all, when I might conceivably want to render two different
functions depending on the parameters, it means that I would have to
keep track of more. I don't actually need to do that yet, but just
looking ahead.

Thoughts?

-Bryan

P.S. Sorry this debugging has been so convoluted. I knew there was
something I wasn't getting about ewc - I should have started
there. :P

Yariv Sadan

unread,
Sep 11, 2007, 1:57:55 AM9/11/07
to erl...@googlegroups.com
>
> Okay, I finally understand. I had read the wrong meaning into "Render
> the component's function...". The difference between ewc and replace
> is now obvious. Furthermore, I have no idea what rewrite to suggest
> now, as it now seems to me like the natural reading of "Render the
> component's function..." should be the right one. :P

Maybe instead of "send the result to the view function" it should say
"pass the result...". 'Send' is an overloaded term here, especially in
the Erlang context.

>
> In any case, I can now say that I know exactly which cases have
> problems.
>
> Specifically: a component cannot return a header tuple inside a
> response tuple list if that component has been rendered as a result of
> some other controller function returning an ewc or replace tuple.

Yes, that's intentional. The thinking behind this design is that when
the user requests a URL, e.g. http://foo.com/bar/baz, only the "baz"
function of the "bar" controller should be able to return headers.
Other controller functions involved in the page rendering, i.e., those
that belong to sub-components, only render fragments of the page --
they don't return headers. This prevents weird situations such as when
two subcomponents return different values for the "redirect" header,
and it simplifies debugging because you always know where your headers
come from. (It also simplified ErlyWeb's internals, and since until
now nobody has had problems with this design, I decided to keep it.)

For now, it looks like the best approach is manually call two() from
one(), as you suggested above. However, I think the best approach in
the long run may be to maintain the the restriction that only the
requested controller function can return headers, *unless* this
function returns a {replace, {ewc..}} tuple, in which case the
destination function would be able to return headers as well. I'll
have to think about it a bit more to make sure there isn't a
better/simpler solution, but right now it seems like the most
reasonable choice.

Thanks for reporting!

Cheers,
Yariv

Bryan Fink

unread,
Sep 11, 2007, 10:05:00 AM9/11/07
to erlyweb
On Sep 11, 1:57 am, "Yariv Sadan" <yarivsa...@gmail.com> wrote:
> > Okay, I finally understand. I had read the wrong meaning into "Render
> > the component's function...". The difference between ewc and replace
> > is now obvious. Furthermore, I have no idea what rewrite to suggest
> > now, as it now seems to me like the natural reading of "Render the
> > component's function..." should be the right one. :P
>
> Maybe instead of "send the result to the view function" it should say
> "pass the result...". 'Send' is an overloaded term here, especially in
> the Erlang context.

I think what I'd be looking for is something that made it clear that
by returning an ewc, you're "pushing" your view call onto a stack, to
be called ("popped") after all other renderings are complete.

> > Specifically: a component cannot return a header tuple inside a
> > response tuple list if that component has been rendered as a result of
> > some other controller function returning an ewc or replace tuple.
>
> Yes, that's intentional. The thinking behind this design is that when

> the user requests a URL, e.g.http://foo.com/bar/baz, only the "baz"


> function of the "bar" controller should be able to return headers.
> Other controller functions involved in the page rendering, i.e., those
> that belong to sub-components, only render fragments of the page --
> they don't return headers. This prevents weird situations such as when
> two subcomponents return different values for the "redirect" header,
> and it simplifies debugging because you always know where your headers
> come from. (It also simplified ErlyWeb's internals, and since until
> now nobody has had problems with this design, I decided to keep it.)

Ah, yes, that does make sense. Especially once you remember that a
controller function can return a list of ewc and data tuples, so there
could very like be multiple subcomponent returns to deal with. I
hadn't used that idea yet.

> > It works, as far as there being no errors. But, in the case of
> > catch_all, when I might conceivably want to render two different
> > functions depending on the parameters, it means that I would have to
> > keep track of more. I don't actually need to do that yet, but just
> > looking ahead.
>
> > Thoughts?
>
> For now, it looks like the best approach is manually call two() from
> one(), as you suggested above. However, I think the best approach in
> the long run may be to maintain the the restriction that only the
> requested controller function can return headers, *unless* this
> function returns a {replace, {ewc..}} tuple, in which case the
> destination function would be able to return headers as well. I'll
> have to think about it a bit more to make sure there isn't a
> better/simpler solution, but right now it seems like the most
> reasonable choice.

Yes, I agree, now that I see the design philosophy laid out. It does
seem like a component rendered from a replace tuple should be able to
return headers, but the direct call is working for me, at least, right
now, so now hurry as far as I can see. ;)

Thanks for the explanation,
Bryan

Reply all
Reply to author
Forward
0 new messages