# NAME Dancer2::Plugin::QuickORM - Adds QuickORM syntactic sugar to Dancer2 # VERSION version 1.0000 # SYNOPSIS package MyApp; use Dancer2; use Dancer2::Plugin::QuickORM; set plugins => { QuickORM => { default => { class => 'MyApp::ORM', }, }, }; # 'widget' table -> singular/plural keywords widget()/widgets() get '/widget/:id' => sub { return widget( route_parameters->get('id') ); }; get '/widgets' => sub { return [ widgets( { color => 'blue' } ) ]; }; # get at the raw ORM connection directly get '/stats' => sub { return { row_count => orm()->handle('widget')->all }; }; # DESCRIPTION `Dancer2::Plugin::QuickORM` looks at the ORM(s) you've configured with [DBIx::QuickORM](https://metacpan.org/pod/DBIx%3A%3AQuickORM), walks every table in their schema(s), and generates a Dancer2 keyword (i.e. an importable function) for each one, so routes can query it without any boilerplate. For each configured ORM name (see ["CONFIGURATION AND ENVIRONMENT"](#configuration-and-environment)), the plugin's `BUILD` step: 1. Loads the configured `class` and calls `$class->orm($name)` to obtain a connection. 2. Iterates `$orm->schema($name)->tables` and, for each table, derives a singular and a plural keyword name from the table name (splitting `StudlyCaps`/`CamelCase` into words and singularising/pluralising with [Lingua::EN::Inflect::Phrase](https://metacpan.org/pod/Lingua%3A%3AEN%3A%3AInflect%3A%3APhrase)). 3. Registers those keywords, plus schema-prefixed variants (`_` and `_`), so a table is always reachable unambiguously even if more than one configured ORM exposes a table of the same name. ## Generated keywords If a table's singular and plural forms differ (the common case, e.g. `widget`/`widgets`), two keywords are generated: - `singular($id)` Fetches the single row with that primary key. Croaks if called with no argument, or with a hashref (which is a search, not a primary key). - `plural($where)` Runs a search. Called with no arguments, returns every row. Called with a hashref, runs it as search conditions against the table. Croaks if given anything other than a hashref. If a table's singular and plural forms are identical (e.g. `moose`), a single "smart" keyword is generated instead, which inspects the shape of its argument to decide what you meant: - a hashref is treated as search conditions (like the plural form above); - `undef` (i.e. no argument) croaks, since there's no sensible default; - anything else (a plain scalar, or an arrayref) is treated as a primary key value and looked up like the singular form above; - any other kind of reference (coderef, scalarref, etc.) croaks, since it's neither a valid search nor a valid primary key. ## Keyword collisions Two different kinds of collision are handled, and handled differently: - Two configured ORMs derive the same bare keyword The bare keyword (e.g. `widget`) becomes ambiguous: calling it croaks, telling you to use the schema-prefixed form (e.g. `default_widget` or `second_widget`) instead. The schema-prefixed forms always work. - A derived keyword collides with an existing keyword If the bare keyword name is already in use (either because it's a core Dancer2 DSL keyword, e.g. `session` or `response_header`, or because this plugin has already registered it itself - its own `orm` keyword, see below), the bare form is silently skipped so the existing keyword keeps working. The schema-prefixed form is still registered and reachable. # SUBROUTINES/METHODS ## orm orm(); # the connection configured as 'default' orm($name); # the connection configured under $name Returns the raw ORM connection object for the given configured name (defaults to `'default'` when no name is given). Croaks if `$name` was not configured. This is the one keyword the plugin always registers itself (via `plugin_keywords`), regardless of what tables are configured; use it when you need something the generated per-table keywords don't cover. ## BUILD Runs once when the plugin is instantiated. Reads the plugin configuration, loads each configured ORM class, and generates the per-table keywords described in ["DESCRIPTION"](#description). Not intended to be called directly. # ATTRIBUTES This plugin exposes no public attributes of its own. All of its behaviour is driven by the `plugins->QuickORM` section of your Dancer2 configuration; see ["CONFIGURATION AND ENVIRONMENT"](#configuration-and-environment). # DIAGNOSTICS - `ORM '%s' is missing 'class' in the configuration` A configured ORM name has no `class` key. Every entry under `plugins->QuickORM` must specify one. - `Failed to load ORM class '%s': %s` The configured `class` could not be `require`d; the underlying error is appended. - `ORM '%s' is not defined in the configuration` `orm($name)` was called with a name that wasn't configured. - `'%s' requires a primary key value` A singular (or smart) keyword was called with no argument. - `'%s' expects a single primary key value, not a hashref of search conditions` A singular keyword was called with a hashref; use the plural (search) keyword instead. - `'%s' expects a hashref of search conditions` A plural keyword was called with a non-hashref, non-undef argument. - `'%s' requires a primary key value or a hashref of search conditions` A smart keyword (identical singular/plural table) was called with no argument. - `'%s' does not accept a reference of type '%s'` A smart keyword was called with a reference that's neither a hashref (search) nor usable as a primary key. - `%s is ambiguous; use _%s instead!` The bare keyword is generated by more than one configured ORM; call the schema-prefixed form instead. # CONFIGURATION AND ENVIRONMENT Configure one or more named ORMs under `plugins->QuickORM` in your Dancer2 config: plugins: QuickORM: default: class: MyApp::ORM dsn: "dbi:Pg:dbname=myapp" reporting: class: MyApp::ORM dsn: "dbi:Pg:dbname=myapp_reporting" Each key under `QuickORM` is an ORM name (`default` is used when `orm()` is called without an argument); each value is a hashref that must contain a `class`, plus whatever other keys your ORM class needs. Every key/value pair in a given ORM's configuration (including `class` itself) is also copied into `%ENV` as `QuickORM__`, and the name itself is set as `QuickORM__name`, so the ORM class's `orm()` constructor can pick up its configuration from the environment if it prefers that to being passed arguments directly. The configured `class` must implement the following [DBIx::QuickORM](https://metacpan.org/pod/DBIx%3A%3AQuickORM) features: $class->orm($name) # -> connection object $orm->schema($name)->tables # -> list of table objects $table->name # -> table name string $orm->handle($table_name) # -> handle object $handle->by_id($id) # -> single row (hashref) or undef $handle->where($hashref)->all # -> list of matching rows $handle->all # -> list of every row # DEPENDENCIES - [Dancer2](https://metacpan.org/pod/Dancer2), naturally. - [DBIx::QuickORM](https://metacpan.org/pod/DBIx%3A%3AQuickORM) - [Carp](https://metacpan.org/pod/Carp), - [Lingua::EN::Inflect::Phrase](https://metacpan.org/pod/Lingua%3A%3AEN%3A%3AInflect%3A%3APhrase) # BUGS AND LIMITATIONS Table names that singularise/pluralise to the same word as a Dancer2 core DSL keyword or another already-registered keyword are only reachable via their schema-prefixed keyword; see ["Keyword collisions"](#keyword-collisions). For tables whose singular and plural forms are identical, an arrayref argument is treated as a primary key value (passed straight through to the backend) rather than being rejected outright, since it isn't a hashref and isn't some other clearly-invalid reference type; whether that's a meaningful primary key value is left to the configured ORM class. Please report any bugs or feature requests through the issue tracker for this distribution. # SEE ALSO [Dancer2](https://metacpan.org/pod/Dancer2), [Dancer2::Plugin](https://metacpan.org/pod/Dancer2%3A%3APlugin), [DBIx::QuickORM](https://metacpan.org/pod/DBIx%3A%3AQuickORM) # AUTHOR D Ruth Holloway # COPYRIGHT AND LICENSE This software is copyright (c) 2026 by D Ruth Holloway. This is free software; you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself. # POD ERRORS Hey! **The above document had some coding errors, which are explained below:** - Around line 264: You forgot a '=back' before '=head1'