RejectedSoftware Forums

Sign up

Pages: 1 2

Passing parameters to diet templates

I cannot find a good way to pass parameters to templates, but I have
several sub-optimal ways of doing it...

One is wrapper that adds a parameter:
void auth(alias View)(Request req, Response res) {
auto user = getLoggedInUser();
view(req, res, user);
}

This doesn't scale well, as all views needs this field, and I haven't
found a way to nest several wrappers.

Another way is to hijack the render method to add fields:
void render(string templateName, Params)(Response res, Request req) {
auto user = getLoggedInUser();
if(user)

 vibe.http.server.render!(templateName, Params, user)(res, req);

else

 vibe.http.server.render!(templateName, Params)(res, req);

}

As you see, this allows me to only add the parameter if it makes sense. A
static if in the diet template could check if it's passed in (could share
a layout with a login/logout view even for static templates)
But I don't know how to do this for several parameters either.

The "best" way I've found is passing in the Request, and let the views
call helper methods in the application. I don't really like this approach
as I would like the static typing present, and want to keep my views as
dumb as possible.

So.. What's a good way to pass parameters to diet templates?

Re: Passing parameters to diet templates

On Thu, 31 May 2012 10:04:17 +0200, simendsjo simendsjo@gmail.com wrote:

I cannot find a good way to pass parameters to templates, but I have
several sub-optimal ways of doing it...

One is wrapper that adds a parameter:
void auth(alias View)(Request req, Response res) {
auto user = getLoggedInUser();
view(req, res, user);
}

This doesn't scale well, as all views needs this field, and I haven't
found a way to nest several wrappers.

Another way is to hijack the render method to add fields:
void render(string templateName, Params)(Response res, Request req) {
auto user = getLoggedInUser();
if(user)

 vibe.http.server.render!(templateName, Params, user)(res, req);

else

 vibe.http.server.render!(templateName, Params)(res, req);

}

As you see, this allows me to only add the parameter if it makes sense.
A static if in the diet template could check if it's passed in (could
share a layout with a login/logout view even for static templates)
But I don't know how to do this for several parameters either.

The "best" way I've found is passing in the Request, and let the views
call helper methods in the application. I don't really like this
approach as I would like the static typing present, and want to keep my
views as dumb as possible.

So.. What's a good way to pass parameters to diet templates?

I found an acceptable way to handle this, but it requires some cleanup.

I store all "injectors", as I called them, in template parameters, and
each injector adds it's values, pops off it's own method pointer and calls
the next.
The last method pointer is the actual view.

Usage:
// the first value is the view
routes.get("/test", &inject!(test, injectUser, injectA, injectB,
injectC))

The inject method:
// We need to add a variant as I didn't find a way to allow default values
when using variadic arguments. I just add the request..
void inject(alias view, Injectors...)(Request req, Response res)
{

 static if(Injectors.length == 0)
 {
     alias view!(Request, "req") Fn;
 }
 else static if(Injectors.length == 1)
 {
     alias Injectors[0] Injector;
     alias Injector!(view, Request, "req") Fn;
 }
 else
 {
     alias Injectors[0] Injector;
     alias Injectors[1..$] Rest;
     alias Injector!(Rest, view, Request, "req") Fn;
 }
 Fn(req, res, [Variant(req)]);

}

Each inject method looks like this:
void injectA(Params...)(Request req, Response res, Variant[] args...)
{

 int a = 10;

 alias Params[0] next;
 next!(Params[1..$], int, "a")(req, res, args~Variant(a));

}

And the view:
void test(Params...)(Request req, Response res, Variant[] args...)
{

 res.renderCompat!("test.dt", Params)(args);

}

This way, each injector can choose to store a default value, or not add
the value at all.
For diet templates, this means you'll have to check if a variable exists
if you choose to use an injector that doesn't always add a default value.

Re: Passing parameters to diet templates

Do you think its still worth to make this kind of parameter grouping
once the normal res.render!(templ, a, b, c, d) is used instead of
renderCompat?

Since the parameters of a template are effectively defined by the
template just like they are defined for a function, I thought that
direct passing of them was ok. Or, better put, to me the tradeoff
between understandability and terseness of the code is slightly in favor
of explicit parameter passing.

...but to be more constructive - with DMD 2.060 the following simple (*)
scheme without Variant should also work:

import vibe.d;

// inject library:
template injectReverse(Injectors...)
{

alias Injectors[0] First;
alias Injectors[1 .. $] Rest;
alias First!(Rest) injectReverse;

}
void reqInjector(alias Next, Vars...)(HttpServerRequest req,

HttpServerResponse res)

{

Next!(Vars, req)(req, res);

}
@property auto inject(alias Page, Injectors...)()
{

return &injectReverse!(Injectors, reqInjector, Page);

}

// application:
void authInjector(alias Next, Vars...)(HttpServerRequest req,

HttpServerResponse res)

{

string userinfo;
Next!(Vars, userinfo)(req, res);

}

void somethingInjector(alias Next, Vars...)(HttpServerRequest req,

HttpServerResponse res)

{

string something_else;
Next!(Vars, something_else)(req, res);

}

void page(VARS...)(HttpServerRequest req, HttpServerResponse res)
{

res.render!("upload_form.dt", VARS);

}

static this()
{

listenHttp(new HttpServerSettings, inject!(page, authInjector, 

somethingInjector));
}

(*) Actually this template stuff regularily makes my brain hurt...

Re: Passing parameters to diet templates

Btw. I've just scanned over your code again and noticed that this is
basically exactly the same as you are doing, just without the Variants
and written in a different order.

Re: Passing parameters to diet templates

On Thu, 31 May 2012 17:42:05 +0200, Sönke Ludwig
sludwig@rejectedsoftware.com wrote:

Do you think its still worth to make this kind of parameter grouping
once the normal res.render!(templ, a, b, c, d) is used instead of
renderCompat?

Yes, yes, yes :)

Since the parameters of a template are effectively defined by the
template just like they are defined for a function, I thought that
direct passing of them was ok. Or, better put, to me the tradeoff
between understandability and terseness of the code is slightly in favor
of explicit parameter passing.

But the order of the parameters is important if you pass around parameters.
I believe it will be a maintenance nightmare when you try to
add/change/remove parameters in the future.

..but to be more constructive - with DMD 2.060 the following simple (*)
scheme without Variant should also work:

import vibe.d;

// inject library:
template injectReverse(Injectors...)
{

alias Injectors[0] First;
alias Injectors[1 .. $] Rest;
alias First!(Rest) injectReverse;

}
void reqInjector(alias Next, Vars...)(HttpServerRequest req,

HttpServerResponse res)

{

Next!(Vars, req)(req, res);

}
@property auto inject(alias Page, Injectors...)()
{

return &injectReverse!(Injectors, reqInjector, Page);

}

// application:
void authInjector(alias Next, Vars...)(HttpServerRequest req,

HttpServerResponse res)

{

string userinfo;
Next!(Vars, userinfo)(req, res);

}

void somethingInjector(alias Next, Vars...)(HttpServerRequest req,

HttpServerResponse res)

{

string something_else;
Next!(Vars, something_else)(req, res);

}

void page(VARS...)(HttpServerRequest req, HttpServerResponse res)
{

res.render!("upload_form.dt", VARS);

}

static this()
{

listenHttp(new HttpServerSettings, inject!(page, authInjector,  

somethingInjector));
}

(*) Actually this template stuff regularily makes my brain hurt...

Pretty much what I came up with, but when I tried to "clean" it up, I
ended up having to use string mixins to avoid several dmd bugs.. How I
long for the day dmd-just-works(TM)
I really like this solution, and will steal it if you don't add it to vibe
:)

Re: Passing parameters to diet templates

Am 31.05.2012 18:24, schrieb simendsjo:

But the order of the parameters is important if you pass around parameters.
I believe it will be a maintenance nightmare when you try to
add/change/remove parameters in the future.

For normal functions and partially for renderCompat() yes but not for
render(), the parameters there are only referenced by name.

But the maintainance argument is convincing, even if you can partially
avoid the maintainance problems by passing a struct/class to the
template that can later be extended. I can put this into the framework,
just need to think about a proper place...

Re: Passing parameters to diet templates

Actually, now I think this is actually really cool!process the request and optionally not call the next injector,
but return or throw an HttpServerError. With this, the system is
basically very similar to node.js' connect framework.

Its now in vibe.templ.utils. I will also refactor some of my code
to use it when I have time.

Re: Passing parameters to diet templates

On Sun, 03 Jun 2012 12:13:49 +0200, Sönke Ludwig
sludwig@rejectedsoftware.com wrote:

Actually, now I think this is actually really cool!request and optionally not call the next injector, but return or throw
an HttpServerError. With this, the system is basically very similar to
node.js' connect framework.

Its now in vibe.templ.utils. I will also refactor some of my code to use
it when I have time.

I don't know any node.js, but it's a very convenient function.
A problem is that you cannot easily get the parameters from the request
handler.
This could be solved by adding an AA containing indexes to parameters by
name:
void f(size_t[string] paramIndexes, Params...)(Request, Response)
A CTFE function / mixin template should be able to take care of adding the
correct indexes and calling the next injector, but I'm not sure this is a
good design :)

Re: Passing parameters to diet templates

Am 03.06.2012 21:05, schrieb simendsjo:

On Sun, 03 Jun 2012 12:13:49 +0200, Sönke Ludwig
sludwig@rejectedsoftware.com wrote:

Actually, now I think this is actually really cool!the request and optionally not call the next injector, but return or
throw an HttpServerError. With this, the system is basically very
similar to node.js' connect framework.

Its now in vibe.templ.utils. I will also refactor some of my code to
use it when I have time.

I don't know any node.js, but it's a very convenient function.
A problem is that you cannot easily get the parameters from the request
handler.
This could be solved by adding an AA containing indexes to parameters by
name:
void f(size_t[string] paramIndexes, Params...)(Request, Response)
A CTFE function / mixin template should be able to take care of adding
the correct indexes and calling the next injector, but I'm not sure this
is a good design :)

This should work:

mixin(localAliases!(0, VARS));

with localAliases as defined in templ.diet.d:

template localAliases(int i, ALIASES...)
{

static if( i < ALIASES.length ){
	enum string localAliases = "alias ALIASES["~cttostring(i)~"] 

"~__traits(identifier, ALIASES[i])~";\n"

~localAliases!(i+1, ALIASES);

} else {

enum string localAliases = "";

}
}

Also not really pretty but it works and it's just one line.

Re: Passing parameters to diet templates

On Sun, 03 Jun 2012 21:31:13 +0200, Sönke Ludwig
sludwig@rejectedsoftware.com wrote:

Am 03.06.2012 21:05, schrieb simendsjo:

On Sun, 03 Jun 2012 12:13:49 +0200, Sönke Ludwig
sludwig@rejectedsoftware.com wrote:

Actually, now I think this is actually really cool!the request and optionally not call the next injector, but return or
throw an HttpServerError. With this, the system is basically very
similar to node.js' connect framework.

Its now in vibe.templ.utils. I will also refactor some of my code to
use it when I have time.

I don't know any node.js, but it's a very convenient function.
A problem is that you cannot easily get the parameters from the request
handler.
This could be solved by adding an AA containing indexes to parameters by
name:
void f(size_t[string] paramIndexes, Params...)(Request, Response)
A CTFE function / mixin template should be able to take care of adding
the correct indexes and calling the next injector, but I'm not sure this
is a good design :)

This should work:

mixin(localAliases!(0, VARS));

with localAliases as defined in templ.diet.d:

template localAliases(int i, ALIASES...)
{

static if( i < ALIASES.length ){
	enum string localAliases = "alias ALIASES["~cttostring(i)~"]  

"~__traits(identifier, ALIASES[i])~";\n"

~localAliases!(i+1, ALIASES);

} else {

enum string localAliases = "";

}

}

Also not really pretty but it works and it's just one line.

Ah. I tested using .stringof, but that didn't give me the original
parameter-name.
A problem with this method (If I read it correctly - haven't tried it) is
that it mixes in every parameter to local scope.
This becomes a problem when an injector is changed or a new one added.
Suddenly code that used to work will get "identifier already defined"
errors.

mixin(localAliases!(["param1", "param2"], VARS);

I agree it's not exactly pretty, and will seem very magical, but then code
should be ready to work with a wide variety of injectors without needing a
lot of change.
Perhaps even better; accept a tuple so you don't have to
"assert(typeof(param1))":
mixin(localAliases!(Tuple!(int, "param1", SomeType, "param2")))

So... Type safe, extensible, ugly as hell :)

Pages: 1 2