The defmodel
macro, used to define Toucan models, and
the IModel
protocol and default implementations, which implement Toucan model functionality.
The `defmodel` macro, used to define Toucan models, and the `IModel` protocol and default implementations, which implement Toucan model functionality.
(add-property! k & {:keys [insert update select]})
Define a new model property and set the functions used to implement its functionality. See documentation for more details.
(add-property! :timestamped? :insert (fn [obj _] (let [now (java.sql.Timestamp. (System/currentTimeMillis))] (assoc obj :created-at now, :updated-at now))) :update (fn [obj _] (assoc obj :updated-at (java.sql.Timestamp. (System/currentTimeMillis)))))
Define a new model property and set the functions used to implement its functionality. See documentation for more details. (add-property! :timestamped? :insert (fn [obj _] (let [now (java.sql.Timestamp. (System/currentTimeMillis))] (assoc obj :created-at now, :updated-at now))) :update (fn [obj _] (assoc obj :updated-at (java.sql.Timestamp. (System/currentTimeMillis)))))
(add-type! k & {:keys [in out]})
Add a new type mapping for type named by key K. Supply mappings for the functions that should prepare value
when it goes :in
to the database, and for when it comes :out
.
;; add a :json type (using Cheshire) will serialize objects as JSON ;; going into the DB, and deserialize JSON coming out from the DB (add-type! :json :in json/generate-string :out #(json/parse-string % keyword))
Add a new type mapping for type named by key K. Supply mappings for the functions that should prepare value when it goes `:in` to the database, and for when it comes `:out`. ;; add a :json type (using Cheshire) will serialize objects as JSON ;; going into the DB, and deserialize JSON coming out from the DB (add-type! :json :in json/generate-string :out #(json/parse-string % keyword))
(defmodel model table-name)
(defmodel model docstr? table-name)
Define a new "model". Models encapsulate information and behaviors related to a specific table in the application DB, and have their own unique record type.
defmodel
defines a backing record type following the format <model>Instance
. For example, the class associated
with User
is <root-namespace>.user/UserInstance
. (The root namespace defaults to models
but can be configured
via set-root-namespace!
)
This class is used for both the titular model (e.g. User
) and for objects that are fetched from the DB. This means
they can share the IModel
protocol and simplifies the interface somewhat; functions like types
work on either
the model or instances fetched from the DB.
(defmodel User :user_table) ; creates class UserInstance
and DB model User
(db/select User, ...) ; use with toucan.db
functions. All results are instances of UserInstance
The record type automatically extends IModel
with IModelDefaults
, but you can override specific methods, or
implement other protocols, by passing them to defmodel
, the same way you would with defrecord
.
(defmodel User :user_table IModel (hydration-keys [_] [:user]) (properties [_] {:timestamped true}) (pre-insert [user] user))
This is equivalent to:
(extend (class User) ; it's somewhat more readable to write (class User)
instead of UserInstance
IModel (merge IModelDefaults
{...}))
Finally, the model itself is invokable. Calling with no args returns all values of that object; calling with a single arg can be used to fetch a specific instance by its integer ID.
(Database) ; return a seq of all Databases (as instances of DatabaseInstance
)
(Database 1) ; return Database 1
Define a new "model". Models encapsulate information and behaviors related to a specific table in the application DB, and have their own unique record type. `defmodel` defines a backing record type following the format `<model>Instance`. For example, the class associated with `User` is `<root-namespace>.user/UserInstance`. (The root namespace defaults to `models` but can be configured via `set-root-namespace!`) This class is used for both the titular model (e.g. `User`) and for objects that are fetched from the DB. This means they can share the `IModel` protocol and simplifies the interface somewhat; functions like `types` work on either the model or instances fetched from the DB. (defmodel User :user_table) ; creates class `UserInstance` and DB model `User` (db/select User, ...) ; use with `toucan.db` functions. All results are instances of `UserInstance` The record type automatically extends `IModel` with `IModelDefaults`, but you can override specific methods, or implement other protocols, by passing them to `defmodel`, the same way you would with `defrecord`. (defmodel User :user_table IModel (hydration-keys [_] [:user]) (properties [_] {:timestamped true}) (pre-insert [user] user)) This is equivalent to: (extend (class User) ; it's somewhat more readable to write `(class User)` instead of `UserInstance` IModel (merge IModelDefaults {...})) Finally, the model itself is invokable. Calling with no args returns *all* values of that object; calling with a single arg can be used to fetch a specific instance by its integer ID. (Database) ; return a seq of *all* Databases (as instances of `DatabaseInstance`) (Database 1) ; return Database 1
(do-post-select model obj)
Don't call this directly! Apply internal functions like post-select
when an object is retrieved from the DB.
Don't call this directly! Apply internal functions like `post-select` when an object is retrieved from the DB.
(do-pre-insert model obj)
Don't call this directly! Apply functions like pre-insert
before inserting an object into the DB.
Don't call this directly! Apply functions like `pre-insert` before inserting an object into the DB.
(do-pre-update model obj)
Don't call this directly! Apply internal functions like pre-update
before updating an object in the DB.
Don't call this directly! Apply internal functions like `pre-update` before updating an object in the DB.
Used by internal functions like do-post-select
.
Used by internal functions like `do-post-select`.
The IModel
protocol defines the various methods that are used to provide custom behavior for various models.
This protocol contains the various methods model classes can optionally implement. All methods have a default
implementation provided by IModelDefaults
; new models created with the defmodel
macro automatically implement
this protocol using those default implementations. To override one or more implementations, use extend
and
merge
your custom implementations with IModelDefaults
:
(defmodel MyModel)
(extend (class MyModel) IModel (merge IModelDefaults {...}))
The `IModel` protocol defines the various methods that are used to provide custom behavior for various models. This protocol contains the various methods model classes can optionally implement. All methods have a default implementation provided by `IModelDefaults`; new models created with the `defmodel` macro automatically implement this protocol using those default implementations. To override one or more implementations, use `extend` and `merge` your custom implementations with `IModelDefaults`: (defmodel MyModel) (extend (class MyModel) IModel (merge IModelDefaults {...}))
(primary-key this)
Defines the primary key. Defaults to :id
(primary-key [_] :id)
NOTE: composite keys are currently not supported
Defines the primary key. Defaults to :id (primary-key [_] :id) NOTE: composite keys are currently not supported
(post-insert this)
Gets called by insert!
with an object that was newly inserted into the database.
This provides an opportunity to trigger specific logic that should occur when an object is inserted or modify the
object that is returned. The value returned by this method is returned to the caller of insert!
. The default
implementation is identity
.
(post-insert [user] (assoc user :newly-created true))
(post-insert [user] (add-user-to-magic-perm-groups! user) user)
Gets called by `insert!` with an object that was newly inserted into the database. This provides an opportunity to trigger specific logic that should occur when an object is inserted or modify the object that is returned. The value returned by this method is returned to the caller of `insert!`. The default implementation is `identity`. (post-insert [user] (assoc user :newly-created true)) (post-insert [user] (add-user-to-magic-perm-groups! user) user)
(properties this)
Return a map of properties of this model. Properties can be used to implement advanced behavior across many different models; see the documentation for more details. Declare a model's properties as such:
(properties [_] {:timestamped? true})
Define functions to handle objects with those properties using add-property!
:
(add-property! :timestamped?
:insert (fn [obj] (assoc obj :created-at (new-timestamp), :updated-at (new-timestamp)))
:update (fn [obj] (assoc obj :updated-at (new-timestamp))))
Return a map of properties of this model. Properties can be used to implement advanced behavior across many different models; see the documentation for more details. Declare a model's properties as such: (properties [_] {:timestamped? true}) Define functions to handle objects with those properties using `add-property!`: (add-property! :timestamped? :insert (fn [obj] (assoc obj :created-at (new-timestamp), :updated-at (new-timestamp))) :update (fn [obj] (assoc obj :updated-at (new-timestamp))))
(hydration-keys this)
The hydration-keys
method can be overrode to specify the keyword field names that should be hydrated
as instances of this model. For example, User
might include :creator
, which means hydrate
will
look for :creator_id
or :creator-id
in other objects to find the User ID, and fetch the Users
corresponding to those values.
The `hydration-keys` method can be overrode to specify the keyword field names that should be hydrated as instances of this model. For example, `User` might include `:creator`, which means `hydrate` will look for `:creator_id` or `:creator-id` in other objects to find the User ID, and fetch the `Users` corresponding to those values.
(post-update this)
Gets called by update!
with an object that was successfully updated in the database.
This provides an opportunity to trigger specific logic that should occur when an object is updated.
The value returned by this method is not returned to the caller of update!
. The default
implementation is nil
(not invoked).
Note: This method is not invoked when calling update!
with a honeysql-form
form.
(post-update [user] (audit-user-updated! user)
Gets called by `update!` with an object that was successfully updated in the database. This provides an opportunity to trigger specific logic that should occur when an object is updated. The value returned by this method is not returned to the caller of `update!`. The default implementation is `nil` (not invoked). Note: This method is *not* invoked when calling `update!` with a `honeysql-form` form. (post-update [user] (audit-user-updated! user)
(pre-insert this)
Gets called by insert!
immediately before inserting a new object.
This provides an opportunity to do things like encode JSON or provide default values for certain fields.
(pre-insert [query]
(let [defaults {:version 1}]
(merge defaults query))) ; set some default values
Gets called by `insert!` immediately before inserting a new object. This provides an opportunity to do things like encode JSON or provide default values for certain fields. (pre-insert [query] (let [defaults {:version 1}] (merge defaults query))) ; set some default values
(pre-update this)
Called by update!
before DB operations happen. A good place to set updated values for fields like updated-at
,
or to check preconditions.
Called by `update!` before DB operations happen. A good place to set updated values for fields like `updated-at`, or to check preconditions.
(types this)
Return a map of keyword field names to their types for fields that should be serialized/deserialized in a special
way. Values belonging to a type are sent through an input function before being inserted into the DB, and sent
through an output function on their way out. :keyword
is the only type enabled by default; you can add more by
calling add-type!
:
(add-type! :json, :in json/generate-string, :out json/parse-string)
Set the types for a model like so:
;; convert `:category` to a keyword when it comes out of the DB; convert back to a string before going in
(types [_] {:category :keyword})
Return a map of keyword field names to their types for fields that should be serialized/deserialized in a special way. Values belonging to a type are sent through an input function before being inserted into the DB, and sent through an output function on their way out. `:keyword` is the only type enabled by default; you can add more by calling `add-type!`: (add-type! :json, :in json/generate-string, :out json/parse-string) Set the types for a model like so: ;; convert `:category` to a keyword when it comes out of the DB; convert back to a string before going in (types [_] {:category :keyword})
(pre-delete this)
Called by delete!
for each matching object that is about to be deleted.
Implementations can delete any objects related to this object by recursively calling delete!
, or do
any other cleanup needed, or check some preconditions that must be fulfilled before deleting an object.
The output of this function is ignored.
(pre-delete [{database-id :id :as database}]
(delete! Card :database_id database-id)
...)
Called by `delete!` for each matching object that is about to be deleted. Implementations can delete any objects related to this object by recursively calling `delete!`, or do any other cleanup needed, or check some preconditions that must be fulfilled before deleting an object. The output of this function is ignored. (pre-delete [{database-id :id :as database}] (delete! Card :database_id database-id) ...)
(default-fields this)
Return a sequence of keyword field names that should be fetched by default when calling
select
or invoking the model (e.g., (Database 1)
).
Return a sequence of keyword field names that should be fetched by default when calling `select` or invoking the model (e.g., `(Database 1)`).
(post-select this)
Called on the results from a call to select
and similar functions. Default implementation doesn't do anything,
but you can provide custom implementations to do things like remove sensitive fields or add dynamic new ones.
For example, let's say we want to add a :name
field to Users that combines their :first-name
and :last-name
:
(defn- post-select [user]
(assoc user :name (str (:first-name user) " " (:last-name user))))
Then, when we select a User:
(User 1) ; -> {:id 1, :first-name "Cam", :last-name "Saul", :name "Cam Saul"}
Called on the results from a call to `select` and similar functions. Default implementation doesn't do anything, but you can provide custom implementations to do things like remove sensitive fields or add dynamic new ones. For example, let's say we want to add a `:name` field to Users that combines their `:first-name` and `:last-name`: (defn- post-select [user] (assoc user :name (str (:first-name user) " " (:last-name user)))) Then, when we select a User: (User 1) ; -> {:id 1, :first-name "Cam", :last-name "Saul", :name "Cam Saul"}
Default implementations for IModel
methods.
Default implementations for `IModel` methods.
(invoke-model-or-instance obj & args)
Check whether OBJ is an model (e.g. Database
) or an object from the DB; if an model,
call invoked-model
; otherwise call get
.
Check whether OBJ is an model (e.g. `Database`) or an object from the DB; if an model, call `invoked-model`; otherwise call `get`.
(model? model)
Is model a valid toucan model?
Is model a valid toucan model?
(root-namespace)
Fetch the parent namespace for all Toucan models.
Fetch the parent namespace for all Toucan models.
(set-root-namespace! new-root-namespace)
Set the root namespace where all models are expected to live.
(set-root-namespace! 'my-project.models)
In this example, Toucan would look for a model named UserFollow
in the namespace my-project.models.user-follow
.
Set the root namespace where all models are expected to live. (set-root-namespace! 'my-project.models) In this example, Toucan would look for a model named `UserFollow` in the namespace `my-project.models.user-follow`.
cljdoc is a website building & hosting documentation for Clojure/Script libraries
× close