lapis.validate.types — Add types.params_map validation type, the params compatible variant of types.map_ofmodel:update will now only assign the update object to the model instnance if the update completes successfullymodel:update support the returns option to control the RETURNING clause of the generated querymodel:update when timestamps are enabled, the generated updated_at value is assigned to the model instancelapis.validate.types — Fix bug where types.params_shape would not return the state objectmodel:update will avoid storing db.raw values on passed as update object to the model instance if the update does not complmete successfullyModel:include_in can now use computed keys to dynamically calculate a foreign key value by applying a function to each passed in object to load. This can be done by specifying a function instead of a field name when defining the column mapping tablelapis.validate.types — add types.params_array for validating an array of objects with a common shapelapis.validate.types — add types.flatten_errors for error output compatibility with tableshapelapis.validate.types — types.params_shape can now accept numerical names for fields for validating array like objects with a fixed number of entrieslapis generate — Rockspec generator can now specify --moonscript and --cqueues to automatically append dependencieslapis migrate — Add the --dry-run flag to to run all pending migrations in a transaction that is never commited. (Note: in some databases, there are queries that can not be rolled back)types.truncated_text--rockspec command to lapis newlapis generate migration command to either create blank migration file or append a new migration at the end of the existing migration file. See lapis generate migration --help for more information
lapis generate rockspec command to create a new rockspec for managing app dependencies. See lapis generate rockspec --help for more information
--app-name--postgres, --sqlite, --moonscript, etc.package.path, package.cpath, package.searchers, and package.loaders is involved
lapis simulate command where header output wouldn’t be normalized correctlylapis.validate.types params_shape was not correctly passing initial state object through the validationargparse specification
lapis new creates as generator modules. All of these files can be independently created using the lapis generate command if necessary, eg. lapis generate app
lapis new will now internally call lapis generate to create the necessary fileslapis generate {NAME} --help can now be used for every template generator to view configuration optionslapis generate {NAME})
app – For creating initial lapis application file in (app.lua or app.moon)config – For creating config.lua or config.moonmodels – For creating autoloader modules.lua or modules.moon modulegitignore – For creating initial .gitignore when using lapis new --git tupfile – For creating initial Tupfile and Tuprules.tup when using lapis new --tupnginx.config – For creating initial nginx.confnginx.mime_types – For creating mime.types nginx configuration includecode_cache and num_workers, and instead now matches the Lua version of the config.lapis.db db.clause supports a prefix option to optionally append something to the front of the encoded clause if it contains any fields.lapis.db.model Generic preload can take the a callback function with the name of a relation to execute a callback on the loaded objects. Eg. preload(users, { profile = function(profiles) ... end })Model:include_in add skip_included option to do nothing on objects that already have the related field loadedModel:include_in add loaded_results_callback option to provide a function callback to be called with with the list of objects that were fetched from the queryModel:include_in when loading objects by a composite key, duplicate composite values will be stripped to reduce the size of the query generated (Singular keys already functioned this way)lapis.db.model.relations mark_loaded_relations can now take a third argument of a value to set for the relation (defaults to true)lapis.util singularize function has been improved slightly with more cases (eg. vertex, child, index, status) and will work with all capital words. Note that this function will never be comprehensive, only suitable for calculating a quick default in scenarios where names aren’t explicitly specifiedlapis.validate.types Add multi_params for joining two params_shape objects togetherpreloadpreloadlapis simulate command to simulate a request to the application
without starting a server. This is useful for testing and debugging. See
lapis simulate --help for details about usage.lapis.db.sqlite, lapis.db.sqlite.model,
lapis.db.sqlite.schema)request:get_request() method to get a reference to the request object.
(Note this just returns self, but it is useful in cases where the request
object is being proxied through the helper chain of a widget)accept
request header is set to application/jsoncontent_for now stores content blocks directly on the request object
(prefixed with _content_for). This may solve a bug where widget helper
chain could have cached an outdated copy of the content_for state.content_for will now throw an error if attempted to be used in a context
without a request objectrequest.options (aka self.options)
so that the view can know if a layout will be rendered within the current
request. Note: actions that skip layout by default (eg. json, redirect_to)
will write false to options.layout.set_backend, set_logger, get_logger, init_logger)lapis.db.pagination Paginators now have a simple fallback for clause
generation when the database module lacks a clause parser. (Will work for
simple queries without aggregates instead of throwing an error when calling
total_items and has_items)lapis.spec Remove undocumented test helpers: use_db_connection, assert_no_queries.lapis.spec use_test_env and use_test_server no longer interact with
database connection in setup and teardown.lapis.spec.request The mocked ngx object now covers more fields to make
it suitable for more kinds of requestslapis.validate.types module with tableshape compatible types for parameter validation: params_shape, assert_error, cleaned_text, valid_text, trimmed_text, truncated_text, limited_text, empty, file_upload, db_id, db_enumlapis.validate Add the with_params helper function for wrapping action function with parameter validationlapis.util.utf8 Add string_length function for counting the number of characters in a stringlapis new command will always write a config.lua/config.moon file. The default configuration’s nginx specific variables will be phased out a future updatelapis generate sub commands now can generate both Lua and MoonScript files. Project type detection has been added, in addition to flags to directly specify the file typelapis command line tool’s argument parsing has been rewritten using argparse. New help commands are available for every command, with full argument documentation.lapis generate subcommands now use argparse and have full command line documentation availablelapis new command will now fail if the requested server type is not available. The --force option can be used to bypass the errorinit_by_lua block as an example to suppress global variable warning_ command. See lapis help _lapis.application yield_error now has a default error message if one isn’t providedlapis.validate assert_valid can take a tableshape object as the validation object. Will return transformed value and state on successlapis.db encode_clause will now generate query with capital NOT when using false valuelapis.db Add the db.clause constructor for safely building SQL fragments including clauses and conditionalsdb.clause objects when appropriate for configuring filtering (eg. Model:find, relation where: clause, etc.)lapis.db The lapis.db.encode_clause function can be used to convert a db.clause object a fragment of SQL codelapis.application Add Application:extend() method to create a sub-class of Application when MoonScript is not availablelapis.application All route related methods are now consistently available for both application instances and classes. This includes include, match, extend, find_action, before_filter, get, post, put, deletelapis.session The get_session function can now take a string object as first argument, as an alternative to the request object, to decode a session from a cookie’s string valuelapis.html Add Widget:extend() method for creating sublcass of the Widget class when MoonScript is not availablelapis.html The classnames function will now recurisvely evaluate any tables in the argumentlapis.html Add is_mixins_class function to determine if a class is a dynamically generated mixin class created by Widget:includelapis.etlua The element function has been added to the template scope to allow rendering HTML elements programmatically (similar to the HTML builder syntax)lapis.etlua self has been added to the template scope to allow accessing the instance of the EtluaWidgetlapis migrate command now supports a --transaction flag, can be set to global to apply a transaction across all migrations to be run, or individual to apply a transaction to each migration run.lapis.flow Add Flow:extend() method for sublcassing the Flow classlapis.db Internally, queries now use db.clause to generate SQL conditional statements . This means that order of certain fields may now be different when using where clauses.lapis.db It is no longer possible to override fields configured by a relation when specifying a where: option. This also applies to paginators generated for relationslapis.db.model Relations can now be specified with a direct reference to a Model class, or a function that should resolve to a Model class (existing support for relation name has not changed)lapis.application Inheritance of routes is now more well defined, allowing for route names and paths to be overidden by subclasses or instances during the creation of the router. If you aren’t using application inheritance then this will not affect you.lapis.application Lazy action loading is now supported for actions generated by include and actions built by the HTTP verb match helpers (get, post, etc.). Previously, if provided an action name (or true), it would load the action module immediately. Now all named actions are consistently loaded on first request regardless of where they are usedlapis.application The find_action method now searches up inheritance hierarchy, and can be used on both app classes and instanceslapis.application Trying to call enable or match directly on the lapis.Application class reference will now throw an error to help prevent accidentally mutating the global objectlapis.html The Widget:render() method will now return nothing instead of nillapis.router Router URL creation (aka url_for) has been rewritten to be substantially faster. Previously routes were re-parsed on calls to url_for but will not generate from a cached intermediate form that will allow the URL to be generated with little overhead.lapis.cqueues Add error capturing around the app boot process to provide better error message when attempting to load a faulty app, and prevent infinite loop processing bug from lua-httpLAPIS_FORCE_LOGGING can not be set to 0 to force logging offapplication field is no longer present on the lapis.init module. This was never documented. Use require("lapis.application") instead.These changes should have no effect on the end user implementing an app, but they are documented here in case you were depending on the undocumented structure of lapis
lapis.application The internal structure used by the HTTP verb match helpers has been changed (get, post, etc.)lapis.router Parsed routes are now stored, and the arguments for fill_path have changedlapis.application The way routes are internally managed and iterated has been rewritten to provide unified interface based on metatable inheritance. The lapis.application.route_group module has been added to work with this interface.Model:include_in supports a new { load = false } option to disable the conversion of query results to model instances{ order = false } when creating a paginator from a model relation to strip the default order that may have been specified in the relation definitionlapis.util.utf8: Add a UTF8 aware trim LPeg pattern, called trim) (Note: the utility function trim located lapis.util still only operates on ascii whitespace)Postgres
model:update method can take a where clause to apply a conditional updatemodel:update is now well defined, and works similar to model:delete. Will return boolean true/false depending on if the update was able complete, and the resulting object from the update query. (Warning: Previously model:update would return the result object regardless of success as the first return value, but this functionality was undocumented.)db.insert can now take options as a table, and supports receiving returning columns as a option{ on_conflict = "do_nothing" } option to db.insert to not throw an error if insertion is canceled due to a unique key constraint conflict (using the ON CONFLICT DO NOTHING query syntax introduced in Postgres 9.5)MySQL
model:update will return true if the number of affected rows is greater than 0, followed by the result objectlapis.html: All element helper methods (eg. div, b, span, etc.) now return nil instead of the previously undocumented behavior of returning the buffer object. This is to prevent accidentally leaking data when writing malformed syntax such as div div! (instead of the correct (div -> div!)lapis.util.utf8: whitespace pattern is aware of invalid use of direction markerslapis.db.postgres.model: On model creation, if a returning * clause is provided then unnecessary extra returning fields are not included in the querylapis.db.postgres: Nginx environment detection is more accurate to allow pgmoon connections to be created without error in more stages of the Nginx worker/request lifecyclelapis.db.postgres: OpenResty specific performance metrics about socket reuse are only written when an nginx socket is in use lapis.db.postgres: Add connection specific performance metric for when any socket type other than nginx is usedlapis.validate: Fixed bug where the input options table passed into validate would get mutated, removing the optional fieldWidget\include is now implemented completely different: A dynamic mixins class is generated and inserted into the class hierarchy of the widget when include is first called. All fields and methods from any included classes are copied into this mixin class.
super can be used to call the method provided by the included by the class when overridingsuper to call through multiple included classes)lapis.spec.request.stub_request no longer creates an anonymous app subclass, instead it will override the dispatch method on an instance of the class before stubbing the requestlapis.spec.request.mock_action will no longer push the test environment on every call, but instead will use the current environment. (Note: v1.8.2 added autodetection of busted to ensure specs are always running the test environment, so it’s not necessary to manually set the environment)on_invalid_method option to respond_tofetch relation can be set to true to autogenerate a get_ method based
on the provided preload functiondb.preload. Relation names prefixed with ? will be ignored if the object being preloaded does not contain a relation with that name.
hello when the model does not have hello will result in an error, but preloading ?hello will do nothing.? relations can have nested preloads, if the relation is not found then none of the nested preloads are loaded. This works best when combined with polymorphic relations where the objects may not all share the same relation interface.preload_relations, deprecate itdb.encode_valuesdb.encode_assigns, db.encode_clause, db.encode_values throws error on empty table instead of generating invalid SQL from empty tablesorted_pairs spec helperrespond_to, widget including, lazy loaded actions)lapis.utf8, for identifying single UTF8 chars and whitespace (lpeg patterns that can be used for trimming and sanitization)cascade option to postgres drop index schema functionmimetypes dependencyyield_error or assert_error are not captured by a outer coroutine then a hard error will happenRequest\add_params does not need a nameopenresty (for openresty install on mac with brew); characters in generated SQL for some postgres schema functionsif_not_exists option will now use the sql clause if not exists instead of running a query to see if the entity existsdb.delete can take additional arguments for RETURNING, Model\delete
method also supports additional arguments to control returning clauseget_X_paginated model relation method now supports passing where and
order options to the paginator constructororder and group at the same time with Model\include_in will no
longer generate an invalid querylapis command, the environment is immediately loaded when
checking what commands are available for the server, fixes
#665lapis.nginx.cache can correctly serialize array and boolean types that can
result from parameter parsing, fixes
683- is no longer matched by accident for route pattern filters #696busted, the test environment is set as the default
environment any tests are run. It is not longer necessary to have a setup or
use_test_env call to configure the environmentlapis commands when using an incomplete
configurationdefault_environment function moved to lapis.environment
module, added set_default_environment functionbelongs_to with composite key
(has_one should be used instead)has_one relation--trace option for the lapis command is enabled by default when running in testModel\paginated() model method arguments"“to detect if
ordered paginator should be usedflip and local_key used with include_in or any relations are now
deprecated. Replacement syntax available in documentation for
Model\include_ininclude_in and any relations. See more documentation for
Model\include_ininclude_in and relationseach_item method for Paginators that will return a Lua iterator for going
over every row but queries in batches set by per_pageordered option can be passed to Model\paginated an get_X_paginated
to create an OrderedPaginator instead of a OffsetPaginatorskip_render action render option to prevent Lapis from writing
anything (Suitable for manually writing to output buffer with something like
ngx.print)fetch relations can now specify if they return a collection of items with
many so nested preloading can traverse the loaded objectsserve alias for the lapis server command line commandapp_only code cache option for cqueues to attempt to load app on every
dispatchresty_mysql object can be included in the MySQL configuration object to
provide additional parameters when initializng a resty.mysql connectiontimeout field from the PostgreSQL
config object (Contributed by turbo 679)set_logger and get_logger functions to the lapis.db.mysql and
lapis.db.postgres modulelapis.db.postgres) escape_identifier function will now flatten db.list
objects into raw SQLlapis.db.postgres.schema) create_index support a concurrently option to
create index concurrently (Contributed by Michael Ball 659)test validation for manually specifying a function to test inputassert_error will now fail with error if the error it yielded was not
captured and execution attempted to continuecontent_by_lua_block (Contributed by
ryanford 674)each_page method on paginators no longer allocates a coroutine, a plain
function iterator is usedOrderedPaginator will throw an
errormeasure_performance now works even when outside of nginx when reporting
query timelapis.html) classnames ignores empty strings and will return first
argument directly if passed a stringlapis.logging) query log function can now take a prefix and durationlocals action render option' characters{ json = false } in action render will actually render false as
json instead of skipping json rendercmd_url)timestamp option for model\updateChanges
key parameter and replaced with it randomly generated string stored in cookie.lapis.encoding module to encode an decode. Session loading error messages have changed to include the error from encode_with_secret and decode_with_secretAdditions
enum type fro MySQLto_json and JSON renderbind_host config option to set which interface cqueues/lua-http binds to, defaults to 0.0.0.0Fixes
json_params works with cqueues/lua-httpAdditions
preload function that can preload many relations
at once for many kinds of model instances, including recursively preloading relationscqueues and lua-http server backendfetch relations can provide a method for preloading with the preload optiondefault_url_params has been added to request support object for extending the default parameters passed into url_formatches_pattern validation can be used to test an input against a Lua patternrelation_is_loaded function can be used to test if a model has a relation loadedmock_request can insert a session via a Lua tablelapis.time module with a sleep function that executes correct implementation for the current backend serverwhen instead of where when creating an index an error is thrownlapis.spec.request, the mock_request function supports cookies optionrender_to_file method for widgetsFlowChanges
ngx.md5 implemented with luacryptofind and sub instead of match to parse very long paths faster (#498)db.NULL values are always stripped when inserting a new row from a model’s create methodcqueues/lua-http implementationluacrypto with luaosslMinor changes
mime.types file (#513)Fixes
*.lua would be included in .gitignore for Lua projects (#501)json action return handlerContent-Disposition header for file uploads is no longer case sensitive Note on cqueues/lua-http
With this release we're adding a new server backend for Lapis. Although the backend is functional, there are some things that aren’t available yet. Database connections are shared across all requests so you have to be careful about leaving transactions or state open. Additionally, there’s no way to spawn multiple workers. For production environments we still recommend OpenResty.
Security Fixes
trim implementation was vulnerable to high CPU usage with a specially crafted string due to how the pattern was written. (More information: 4a58f5c1)Additions
has_one relations can specify a local_key (similar to the parameter passed to include_in)has_many relations can specify a local_keylapis generate flow my_flowChanges
_ as the backing variable, so it’s clear they can be used on any type of object. (Previously _req)Request to make way for different server backends/opt/openresty/nginx/sbin/ to the OpenResty search path for lapis server (Cristian Haunsen)Fixes
has_one work correctly by default if primary key is not idAdditions
flow method to request object for invoking flowswrite and action return valueshas_one relations now work with where clausedb now supports db.listChanges
db.NULL passed to Model.create will be strippeddb.NULL passed to Model.update will be stripped, and not assigned to updated model__call metamethodnil as an argument to truncate tables will errorFixes
? inside query value with no interpolation. Interpolation is only performed when extra fields are passed.where in include_in and find_all will no longer generate invalid queryAdditions
url_for will only include optional components of routes if all nested parameters are providedtime_ago_in_words is documented, add date module as a dependencyvalidate can take options, add key option to return errors as hash table instead of arraylayout field of app can be specified as string, will be loaded as module from views directory at runtimeFixes
url_for was including character class of named parameter in result--trace flag interrupting arguments of command line commandsAdditions
Read about all the new route matching syntax: Routes and URL patterns
url_for can correctly fill the splat parameterallow_error option to mock_request to allow 500 error to return without failing specOrderedPaginatorChanges
class attributes within HTML builder do not add a blank attributeFixes
OrderedPaginator with multiple fields now works correctly when fetching additional pagestime_ago_in_wordsAdditions
include_in with the correct parrameterspreload_relations and preload_relation on models with relationsclear_loaded_relation method on models with relationssuper for lua classes to avoid issues with complicated hierarchiesModel.include_in can take a relation name with for_relation to mark it as loadedModel.include_in can take a group option to add a group by clause to queryModel.include_in can take a value option to transform the fetched results before being placed on the model instanceslapis new now generates a models.moon file, with code to autoload from models directoryChanges
Model.include_in will now set an empty array for any many preloads that return no resultsModel.refresh will clear relation load status when refreshing all columnsFixes
__inherited callback was called twice when using Lua classesAdditions
nil relationsclear_loaded_relation method to clear a cached relation’s value to module lapis.models.relationsclass attribute for HTML builders can now take a table to generate classFixes
https URLs generated with port 443 will have the port stripped from the outputreturning option for Model\create can take the value "*" to return all fieldsMisc
Additions
Model\scoped_model for easily creating plugins/extensions that have their own modelslapis command line commandsbuild_url can take empty string scheme to build protocol relative URLutil.date_diff functionChanges
ngx’s query string escape function will be used if availableAdditions
array propertydb.postgres.array and db.postgres.is_array for constructing and testing arraysgenerators.create_table now fails if table already exists (Postgres, MySQL)create_index now fails by default if index exists (Postgres, MySQL)create_index can take index_name option to set specific index name (Postgres, MySQL)create_index can take if_not_exists option to skip if already exixists (Postgres)belongs_to relations now support a key optionModel\select now can take a load option to control what model the results are loaded with. Can take false to not load any modelhas_items method to paginatorenum schema type for alias to small int (Postgres)fields optionChanges
url_for no longer leaves a ? at
the end of the URLBugs
before_filter would not work in Lua APIBreaking changes
There are a few changes that affect backwards compatibility:
default Postgres backend, which utilizes ngx_postgres, has been entirely removed. Early users of Lapis are recommended to upgrade to pgmoonhas_many relation now defines two methods: the get_relation method no longer returns a pager but all the associated rows. A new method, get_relation_paginated will return a pagerdb.list for automatically building SQL list syntax for where x in () queriesinclude_in can now preload many associated objects when many option is set to trueinclude_in can now specify the local_key to use a different key than idpolymorphic_belongs_to relationhas_many now defines two methods, one for pagination and one for fetching all the rowshas_many relation now supports the order optionsingularize function, can singularize words like Categories to Categorysingular_name method to model classes, used when determining field name in relations and inclue_inclause parameter to find_alldelete method returns true if a row is actually removed, false otherwisenil valuelapis.cachecached will only cache GET requestscached updated to not interfere with request methodurl_for now takes a third argument, a table of query parametersreq object.max_request_args, controls OpenResty’s maximum request argument truncation from get_post_args and get_uri_args.lapis.spec module with use_test_env and use_test_server for bustedrespond_to was returning multiple arguments and interfering with Lua match APIenum function for modelsreturning option to Model.create to fetch initial values from database during createdb.raw values passed to Model.create are loaded from database after createdb.raw values passed to Model.update are loaded from database after updateModel.updatecontent_for data in widgets is prefixed to avoid conflict with user defined variablescontent_for of same name will now append instead of overwriteFlow can now expose instance variable assignments to request with expose_assignslapis generate command, add basic generators for model and specrequest method in lapis.spec.server will now send merged GET parameters passed with those in URLhttp.request when URL does not contain pathjson_params will read body before trying to parse itutil.autoload can take multiple search prefixesutil.slugify improved to avoid generating multiple -, will also conver _ to - nowupdated_at update can be disabled for model update method nowtotal_items method of paginator aware of joins, bails out with group by@POST is only parsed if content-type header is set to be a formtruncate_tables is only allowed to run in test environmentmock_request supports expect_jsonmock_request correctly stubs ngx.now, ngx.update_time and remote_addrmeasure_performance configlapis.db queries from command line.etlua extension--etlua-config flag to lapis new to create blank project with etlua configlapis.serve is now executed from Nginx configuration. Default application code is now a regular Lua module that can be required with no side effectsngx_postgres is deprecated, docs removed. pgmoon documentation added. (ngx_postgres support will be removed in 1.1.0)lapis build firstcookie_attributes extension function for configuring new cookieslapis.serve (only applies when sending many different applications)render/widget functions in etlua template scope were shadowedModel\columns method to get list of column names and types for a tableLAPIS_SHOW_QUERIES environment variable can be used to enable printing pgmoon queries during specsLAPIS_OPENRESTY environment variable can be used to set OpenResty binary locationlapis.serve that prevented a string argument from loading correctlyModel\include_in can take a where option to filter resultsModel\include_in no longer throws error when not using primary keymodel\find_all can take a where option to filter resultsmodel\find_all can take a db.raw value as search keyupdated_at and created_at aren’t overwritten when creating a model if manually setdb.escape_identifier is aware of db.raw values, won’t try to escape themcreate_index can take a index method and a tablespace in options table (dant4z)Model\create instead of just those for values provideddb.raw values would not work in create_index, prevening you from creating indexes on expressionsutil.time_ago_in_words can handle dates in the futureutil.parse_query_string will correctly parse empty string valuesassert_error passes through all arguments passed to it instead of just first argumentcached can have a dynamically set dictionary by setting dict to a functionwidget within MoonScript template will automatically be instantiatedlogging config directive for fine grained control over individual logging@content_for@has_content_forcached APINULL check used = instead of is NULL in generated SQLinclude_in, take take fields to select as an optiondb.is_raw(val) for detecting if value is created from db.rawdb.format_date(d) for formatting time to SQL syntaxjson_params function to parse JSON from request body into @params@content_for can now be used to set content from view widgets to be seen by layoutrespond_to now provides a default HEAD actionlapis signal command line command to send arbitrary signal to Nginx processfind_action method to applicationsprepare_results option to paginatedlapis.server, will cache an instance of the class and reuse it@include class method on widgets and applications can take a string and will load it using require@include class method on widgets for mixing in other classes"lapis.spec.server"’s request function can be passed headers, will extract host header if passed a full URL"lapis.spec.server" module for spawning a test serverenv LAPIS_ENVIRONMENT=envname is now injected on top of config to ensure
correct environment is passed down when changing environments of a running
server. Default config has been updated to no longer have env
LAPIS_ENVIRONMENT, it’s recommended but not required to remove that line
from your config.mock_request can take a previous requests headers, see mocking a
requestlapis.cache moduleautoload utility methodwidget method inside of HTML renderer will inject current helpers and set @parentcode_cache as a variable nowlapis migrate command will start a temporary server if it can’t find a running onelapis term command to stop a backgrounded serverModel\count for counting items in a queryModel\update and Model\delete will convert primary key nil values to db.NULLlapis build command for building config and sending HUPlapis migrate command for running migrations on command linelapis new can take optional --git and --tup flagslapis_environment module to get default environmentjson and redirect_to can also set statustrim_filter helper, reference