leafo / lapis

A web framework for Lua and OpenResty written in MoonScript
http://leafo.net/lapis/
MIT License
3.14k stars 247 forks source link

Feature request: Lua sub-applications #255

Open starius opened 9 years ago

starius commented 9 years ago

Currently, a sub-application is a class (Moonscript). Lua applications are instances, not classes. I want to use sub-applications written in Lua.

nelson2005 commented 9 years ago

+1 on this. Moonscript is cool, but learning (yet another) language in order to use lapis adds a hurdle to lapis adoption

lerrua commented 9 years ago

+1 Man you did a great great job with lapis, but Moonscript is not cool. I don't like CofeeScript, I guess JavaScript and Lua are amazing languages so we don't need replace then.

VaiN474 commented 9 years ago

I really like moonscript. It was easy to learn and understand the benefits of it, however, I still prefer to work in Lua as I'd much rather use etlua templates than pure script. This way it's pretty much web development as usual but with a much better framework.

I'm a bit confused what you guys think needs added. Is there some benefit to sub-apps (as classes) that I'm not aware of? From what I can tell, you can get the same functionality by including an external function and passing the app to it.

applications/foo.lua:

    function foo(app)
        app:get("/test", function(self)
            return "bar"
        end)
        return app
    end
    return foo

app.lua:

    require("applications.foo")(app)
    -- or
    subapp = require("applications.foo")
    subapp(app)

Just an example. In this way you can add additional functionality or just keep things separate and clean.

starius commented 9 years ago

Is there some benefit to sub-apps (as classes) that I'm not aware of?

You can add a path prefix to a sub-application. It is good for isolation between sub-applications and prevention of path collisions.

nelson2005 commented 9 years ago

Examples like vain474's this are very helpful to me- thanks

VaiN474 commented 9 years ago

Here's another alternative to sub-apps which may be useful. I like to keep all of my routes in the app.lua file and call the actions from separate files:

app.lua:

--[[ ACTIONS ]]--
local function action(f)
    local script = loadfile("actions/"..f..".lua")
    if not script then
        -- replace with your own error solution
        return function(self)
            self.title = "Woops..."
            self.errormsg = "Action script not found for this page"
            return { layout = nil, render = "error" }
        end
    end
    return script(self)
end

--[[ ROUTES ]]--
-- INDEX --
app:get("index","/", action("index_get"))
...

then all you need is a Lua file that returns a function:

_actions/indexget.lua:

return function(self)
    return "Welcome to Lapis " .. require("lapis.version")
end

This allows you to use the same function for multiple routes and possibly reduce code. With most of the routes being a single line it makes it very easy to see them all, and keep track of your path structure. This also comes in handy for splats, as you can add additional checks to determine which action to call, while still keeping routes separate from the logic.

The use of loadfile() for every route may impact performance (would this effect code cache?), so you might want to do your own testing to find what works best for your project. If anyone else has other examples for alternatives, I'd really like to see some. There's many different ways to do things, so a class solution might not be the best for everyone.

starius commented 9 years ago

The use of loadfile() for every route may impact performance

So you can use require instead of loadfile.

nelson2005 commented 9 years ago

That technique looks useful. I tried using require, didn't manage to get it to work. I had to change the script lookup to

local script = require("actions."..f)

but hitting the index page of the server resulted in the error

"...ce1/openresty/luajit/share/lua/5.1/lapis/application.lua:621: attempt to call local 'fn' (a string value)"

any hints? maybe it's just a lua thing that I'm missing, I'm pretty new to lua.

VaiN474 commented 9 years ago

Check for typos. The example uses f and your error says you're calling fn. You would also need to change return script(self) to just return script. Or just call require directly within the route:

app:get("index","/", require("actions.index_get"))

Using require doesn't need the action function. It was just used with the loadfile example to keep things a little cleaner.

nelson2005 commented 9 years ago

Thanks much for your suggestion. I didn't find any reference to 'fn', but I did get it going with with the require syntax that you suggested. In your original index_get.lua, you wrote return function(self)- is there some way to add an argument besides self to that?

VaiN474 commented 9 years ago

I tried a few different methods and kept running into the same fn error you had. This is the only way I've had success:

local index_get = require("actions.index_get")
app:get("index","/", function(self)
    return index_get(self,"foo")
end)

one-liner:

app:get("index","/", function(self) return require("actions.index_get")(self,"foo") end)

This is because self can't be passed when using require directly, as it doesn't exist yet. Routes seem to require the function to be anonymous.

nelson2005 commented 9 years ago

oh well. thanks for the tips!

CriztianiX commented 9 years ago

Thanks.. it works...