diff --git a/docs/actions.md b/docs/actions.md index 4869f54e..ce63bd52 100644 --- a/docs/actions.md +++ b/docs/actions.md @@ -6,14 +6,17 @@ Every HTTP request that is handled by Lapis follows the same basic flow after being handed off from processing server. The first step is routing. The path of an incoming request is matched against the routes defined in your application -to look up the corresponding *action function*. An action is a regular -Lua/MoonScript function that will be called if the associated route matches. +to look up the corresponding *action function*. An action is a regular Lua +function that will be called if the associated route matches. An *action function* is invoked with one argument, a [*request -object*](#request-object), when processing a request. The request object is -where you'll store all the data you want to share between your actions and -views. Additionally, the request object is your interface to the webserver on -how the result is sent to the client. +object*](#request-object), when processing a request. This object contains +properties and methods that allow you to access data bout the request, like +form paramters and URL parameters. + +The request object is mutable, it's a place where you can store any data you +want to share between your actions and views. Additionally, the request object +is your interface to the webserver on how the result is sent to the client. The return value of the action is used to control how the output is rendered. A string return value will be rendered to the browser directly. A table return @@ -25,6 +28,48 @@ If there is no route that matches the request then the default route handler is executed, read more in [*application callbacks*](#application-configuration/callbacks). +## Defining an action + +To define an action, you minimally need two components: a URL path pattern and +an action function. Optionally, you can assign a name to the action, which can +be referenced when generating URLs in other sections of your app. + +$dual_code{ +moon = [[ +lapis = require "lapis" + +class App extends lapis.Application + -- an unnamed action + "/": => "hello" + + -- an action with a name + [logout: "/logout"]: => status: 404 + + -- a named action with a path parameter that loads the action function by + -- module name + [profile: "/profile/:username"]: "user_profile" +]], +lua = [[ +local lapis = require("lapis") +local app = lapis.Application() + +-- an unnamed action +app:match("/", function(self) return "hello" end) + +-- an action with a name +app:match("logout", "/logout", function(self) return {status = 404} end) + +-- a named action with a path parameter that loads the action function by +-- module name +app:match("profile", "/profile/:username", "user_profile") +]] +} + +> Instead of directly providing a function value to the action definition, you +> can supply a string. The action will then be lazily loaded by the module name +> upon its first execution. The `actions_prefix` is prepended to the beginning +> of the module name, with the default being `"actions."`. + ## Routes & URL Patterns Route patterns use a special syntax to define dynamic parameters of the URL and @@ -49,12 +94,15 @@ app:match("/users/all", function(self) end) ]] } -These routes match the URLs verbatim. The leading `/` is required. The route -must match the entire path of the request. That means a request to -`/hello/world` will not match the route `/hello`. +The simplest route patterns match the path verbatim with no variables or +wildcards. For any route pattern, the leading `/` is mandatory. A route pattern +always matches the entire path, from start to finish. A request to +`/hello/world` will not match `/hello`, and will instead fail with a not found +error. -You can specify a named parameter with a `:` followed immediately by the name. -The parameter will match all characters excluding `/` (in the general case): +You can specify a named parameter by using a `:` immediately followed by the +name. This parameter will match as many characters as possible excluding the +`/` character: $dual_code{ lua = [[ @@ -62,7 +110,9 @@ app:match("/page/:page", function(self) print(self.params.page) end) -app:match("/post/:post_id/:post_name", function(self) end) +app:match("/post/:post_id/:post_name", function(self) + -- ... +end) ]], moon = [[ lapis = require "lapis" @@ -80,16 +130,19 @@ The captured values of the route parameters are saved in the `params` field of the request object by their name. A named parameter must contain at least 1 character, and will fail to match otherwise. -A splat is another kind of pattern that will match as much as it can, including -any `/` characters. The splat is stored in a `splat` named parameter in the -`params` table of the request object. It's just a single `*` +A splat, `*`, is a special pattern that matches as much as it can, including +any `/` characters. This splat is stored as the `splat` named parameter in the +`params` table of the request object. If the splat is the last character in the +route, it will match until the end of the provided path. However, if there is +any text following the splat in the pattern, it will match as much as possible +up until any text that matches the remaining part of the pattern. $dual_code{ lua = [[ app:match("/browse/*", function(self) print(self.params.splat) end) -app:match("/user/:name/file/*", function(self) +app:match("/user/:name/file/*/download", function(self) print(self.params.name, self.params.splat) end) ]], @@ -100,7 +153,7 @@ class App extends lapis.Application "/browse/*": => print @params.splat - "/user/:name/file/*": => + "/user/:name/file/*/download": => print @params.name, @params.splat ]] } diff --git a/docs/models.md b/docs/models.md index acb60635..9f9ef418 100644 --- a/docs/models.md +++ b/docs/models.md @@ -101,29 +101,31 @@ By default all models expect the table to have a primary key called`"id"`. This can be changed by setting the $self_ref{"primary_key"} field on the class. -```lua +$dual_code{ +lua = [[ local Users = Model:extend("users", { primary_key = "login" }) -``` - -```moon +]], +moon = [[ class Users extends Model @primary_key: "login" -``` +]] +} If there are multiple primary keys then an array table can be used: -```lua +$dual_code{ +lua = [[ local Followings = Model:extend("followings", { primary_key = { "user_id", "followed_user_id" } }) -``` - -```moon +]], +moon = [[ class Followings extends Model @primary_key: { "user_id", "followed_user_id" } -``` +]] +} A unique primary key is needed for every row in order to `update` and `delete` rows without affecting other rows unintentially. @@ -135,7 +137,8 @@ fetching data about the underlying table. For the following examples assume we have the following models: -```lua +$dual_code{ +lua = [[ local Model = require("lapis.db.model").Model local Users = Model:extend("users") @@ -143,16 +146,16 @@ local Users = Model:extend("users") local Tags = Model:extend("tags", { primary_key = {"user_id", "tag"} }) -``` - -```moon +]], +moon = [[ import Model from require "lapis.db.model" class Users extends Model class Tags extends Model @primary_key: {"user_id", "tag"} -``` +]] +} ### `Model:find(...)` @@ -308,7 +311,8 @@ $options_table{ For example: -```lua +$dual_code{ +lua = [[ local users = UserProfile:find_all({1,2,3,4}, { key = "user_id", fields = "user_id, twitter_account", @@ -316,9 +320,8 @@ local users = UserProfile:find_all({1,2,3,4}, { public = true } }) -``` - -```moon +]], +moon = [[ users = UserProfile\find_all {1,2,3,4}, { key: "user_id" fields: "user_id, twitter_account" @@ -326,7 +329,8 @@ users = UserProfile\find_all {1,2,3,4}, { public: true } } -``` +]] +} ```sql SELECT user_id, twitter_account from "things" where "user_id" in (1, 2, 3, 4) and "public" = TRUE @@ -366,19 +370,20 @@ an auto-incrementing key from the insert statement. > In MySQL the *last insert id* is used to get the id of the row since the > `RETURNING` statement is not available. -```lua +$dual_code{ +lua = [[ local user = Users:create({ login = "superuser", password = "1234" }) -``` - -```moon +]], +moon = [[ user = Users\create { login: "superuser" password: "1234" } -``` +]] +} ```sql INSERT INTO "users" ("password", "login") VALUES ('1234', 'superuser') RETURNING "id" @@ -393,17 +398,18 @@ For example, we might create a new row in a table with a `position` column set to the next highest number: -```lua +$dual_code{ +lua = [[ local user = Users:create({ position = db.raw("(select coalesce(max(position) + 1, 0) from users)") }) -``` - -```moon +]], +moon = [[ user = Users\create { position: db.raw "(select coalesce(max(position) + 1, 0) from users)" } -``` +]] +} ```sql INSERT INTO "users" (position) @@ -483,38 +489,42 @@ The output might look like this: Returns the name of the table backed by the model. -```lua +$dual_code{ +lua = [[ Model:extend("users"):table_name() --> "users" Model:extend("user_posts"):table_name() --> "user_posts" -``` - -```moon +]], +moon = [[ (class Users extends Model)\table_name! --> "users" (class UserPosts extends Model)\table_name! --> "user_posts" -``` +]] +} + +
-

This class method can be overridden to change what table a model uses: -

```moon class Users extends Model @table_name: => "active_users" ``` +
+ ### `Model:singular_name()` Returns the singular name of the table. -```lua +$dual_code{ +lua = [[ Model:extend("users"):singular_name() --> "user" Model:extend("user_posts"):singular_name() --> "user_post" -``` - -```moon +]], +moon = [[ (class Users extends Model)\singular_name! --> "user" (class UserPosts extends Model)\singular_name! --> "user_post" -``` +]] +} The singular name is used internally by Lapis when calculating what the name of the field is when loading rows with `include_in`. It's also used when @@ -554,10 +564,10 @@ automatically chosen. Possible values for `key` argument: -* **string** -- for each object, the value `object[key]` is used to lookup instances of the model by the model's primary key. The model is assumed to have a singular primary key, and will error otherwise - * with `flip` enabled: `key` is used as the foreign key column name, and `object[opts.local_key or "id"]` is used to pull the values -* **array of string** -- for each object, a composite key is created by individually mapping each field of the key array via `object[key]` to the composite primary key of the model -* **column mapping table** -- explicitly specify the mapping of fields to columns. The *key* of the table will be used as the column name, and the value in the table will be used as the field name referenced from the `objects` argument. If the value is a function, then the function will be called for each object to dynamically calculate the foreign key value. +* **String** -- For each object, the value `object[key]` is used to look up instances of the model by the model's primary key. It's assumed that the model has a singular primary key; otherwise, an error will occur. + * With `flip` enabled: `key` is used as the foreign key column name, and `object[opts.local_key or "id"]` is used to retrieve the values. +* **Array of Strings** -- For each object, a composite key is created by mapping each field of the key array individually via `object[key]` to the composite primary key of the model. +* **Column Mapping Table** -- This allows for the explicit specification of the mapping of fields to columns. The *key* of the table is used as the column name, while the value in the table is used as the field name referenced from the `objects` argument. If the value is a function, this function will be called for each object to dynamically calculate the foreign key value. `include_in` supports the following options (via the optional `opts` argument): @@ -692,20 +702,21 @@ foreign key on the array of model instances that points to the rows we are preloading. By default, the value of the foreign key is mapped to the primary key of the model that is being loaded. -```lua +$dual_code{ +lua = [[ local posts = Posts:select() -- this gets all the posts Users:include_in(posts, "user_id") print(posts[1].user.name) -- print the fetched data -``` - -```moon +]], +moon = [[ posts = Posts\select! -- this gets all the posts Users\include_in posts, "user_id" print posts[1].user.name -- print the fetched data -``` +]] +} ```sql SELECT * from "posts" @@ -727,7 +738,8 @@ In this next example a column mapping table is used to explicitly specify what fields in our object array match to the columns in our query. Here are the relevant models: -```lua +$dual_code{ +lua = [[ local Model = require("lapis.db.model").Model -- table with columns: id, name @@ -735,10 +747,8 @@ local Users = Model:extend("users") -- table with columns: user_id, twitter_account, facebook_username local UserData = Model:extend("user_data") - -``` - -```moon +]], +moon = [[ import Model from require "lapis.db.model" -- columns: id, name @@ -746,7 +756,8 @@ class Users extends Model -- columns: user_id, twitter_account, facebook_username class UserData extends Model -``` +]] +} Now let's say we have an array of users and we want to fetch the associated user data. diff --git a/lapis/flow.moon b/lapis/flow.moon index d6107545..f8ffce41 100644 --- a/lapis/flow.moon +++ b/lapis/flow.moon @@ -10,8 +10,8 @@ is_flow = (cls) -> MEMO_KEY = setmetatable {}, __tostring: -> "::memo_key::" --- make method cached on the instance so that it's not called multiple times --- FIXME: if expose assigns is enabled, don't write memo to exposed request +-- cache the result of a method after first invocation. Arguments for +-- subsequent calls are ignored memo = (fn) -> (...) => cache = rawget @, MEMO_KEY