Skip to main content

Relation

Struct Relation 

Source
pub struct Relation<P> { /* private fields */ }
Expand description

One relation of a parent resource’s records.

Declared by ResourceDef::relation; the related resource must be registered on the same panel, which keys the relation by its slug:

let def: ResourceDef<PostResource> =
    ResourceDef::new().relation(Relation::has_many::<CommentResource>(lens!(Comment.post_id)));

The table is the related resource’s own list table, its columns, search, sort, filters and pager, over its tenant-scoped query narrowed to the record, titled with its plural label or label:

  • its policy applies as on its own list: no section renders when it refuses ViewAny, or when the resource is tenant-scoped and the request has no tenant, and each row keeps only the actions its record allows;
  • a has_many table carries the related resource’s View, Edit, Delete and custom actions and its bulk bar, and a create button when the related resource has a form and its policy allows Create. The button opens the create page with the owner preselected, as in /admin/comments/-/create?post_id=…: a default for a relationship choice only, validated like any other create;
  • a belongs_to_many table keeps View, Edit and Delete, and offers no custom action and no create button, since a record created there would link nothing;
  • writes started from the table carry ?return= and land back on the page, which the panel follows only to a path under its own prefix;
  • its URL parameters take the related resource’s slug as a prefix, as in ?comments.q=, so several tables share one page, and sorting, searching, filtering and paging update it in place. On the edit page that keeps the fields the user edited, but loses repeater rows added and options fetched since the page loaded. A refused save renders no related table.

A has_one relation renders no table: the related resource’s form edits the record’s one child inside the record’s own form.

tab moves the table off the detail and edit pages onto a page of its own.

Mounting refuses a relation to a resource the panel does not register, and two relations of one resource to the same related resource.

Implementations§

Source§

impl<P> Relation<P>
where P: Model + IntoExpr<P> + 'static,

Source

pub fn has_many<C>( foreign_key: impl ForeignKey<C::Model, P::PrimaryKey>, ) -> Self
where C: Resource,

Declare a has_many child resource whose foreign_key holds the owner’s primary key, a single column.

let comments: Relation<Post> = Relation::has_many::<CommentResource>(lens!(Comment.post_id));
Source

pub fn has_one<C>(foreign_key: impl ForeignKey<C::Model, P::PrimaryKey>) -> Self
where C: Resource,

Declare a has_one child resource whose foreign_key holds the owner’s primary key, a single column: the record’s one child, edited by the child resource’s own form inside the owner’s create and edit forms rather than by a table.

let profile: Relation<User> = Relation::has_one::<ProfileResource>(lens!(Profile.user_id));

The related resource’s form renders as a section of the owner’s form, titled with its label or label, each control posting under the child’s slug, as in profiles.bio; the foreign key renders no control, since the owner’s key fills it. Saving the owner writes the child in the same transaction, after the owner: a create creates it, and an edit updates the record’s child or creates one when it has none. Either way the child goes through its own resource as on its own pages: its policy, tenancy, field access, control rules, unique probe, validate_record, record fn and commit hooks. A refusal from either form rolls both back and renders both with their errors. The owner’s hooks receive the owner’s change, the child’s its own.

The section renders only while the request may write the child: Create on the related resource, or View and Update on the record’s child, with the foreign key neither hidden nor locked by field access. A record with more than one child, or one the related resource’s query leaves out, renders none. A submission posting under the prefix with no section is refused, and one posting nothing under it, such as an editable cell’s or an import row’s, leaves the child alone. The relation renders no table.

Mounting refuses a related resource with no form, one whose form binds no control for the foreign key, a soft-deleting one, one whose form holds a control that needs its own keys or routes (a file field, a repeater, an embedded value, a searchable or dependent choice), an owner with no form, and an owner form key the prefix would shadow.

Source

pub fn belongs_to_many<C>(via: impl Into<Path<P, List<C::Model>>>) -> Self
where C: Resource, P: Send + Sync,

Declare a many-to-many child resource whose records the owner links through via, its #[has_many(via = joins.target)] field: the owner’s linked records, which the table attaches and detaches as join rows.

let tags: Relation<Post> = Relation::belongs_to_many::<TagResource>(Post::fields().tags());

The section’s header carries Attach, whose dialog asks for one record through a searchable choice offered as a relationship’s options are; attaching a linked record changes nothing. Each row and the bulk bar carry Detach, which deletes the join rows and leaves the records. Both post under the owner’s record: {list}/{key}/-/relations/{slug}/-/actions/attach, …/-/actions/detach with the selection’s ids, and …/{related}/-/actions/detach for one row. The owner’s hooks receive Change::Attach or Change::Detach.

Attaching and detaching ask the owner’s policy for Update, as editing it does: no button renders without it, and the routes answer 403. The attach dialog offers the related resource’s rows as its relationship choices do; a record its policy refuses View shows without a Detach action, as it shows without its other row actions, and is not detached. Mounting refuses a join model a link cannot write.

Source§

impl<P> Relation<P>

Source

pub fn label(self, label: impl Into<Text>) -> Self

Titles the section label instead of the related resource’s label: its plural one over a table, its singular one over a has_one relation’s form.

Source

pub fn tab(self) -> Self

Serves the table on a page of its own, {list}/{key}/{related slug}, instead of under the record’s detail and edit pages, linked from the record’s navigation: the tab bar its detail page, edit page, record pages and relation tabs share.

The page loads the record as its detail page does, through the resource’s query and Ability::View, and answers 403 when the related resource’s policy refuses ViewAny. Mounting refuses a has_one relation as a tab, since its form edits inside the record’s, and a tab whose related slug is edit, delete, - or a record page’s.

Trait Implementations§

Source§

impl<P> Debug for Relation<P>

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more

Auto Trait Implementations§

§

impl<P> !RefUnwindSafe for Relation<P>

§

impl<P> !UnwindSafe for Relation<P>

§

impl<P> Forget for Relation<P>

§

impl<P> Freeze for Relation<P>
where RelationKind<P>: Freeze, Arc<dyn Fn(&P) -> (Expr<bool>, String) + Sync + Send>: Freeze,

§

impl<P> Move for Relation<P>

§

impl<P> Send for Relation<P>
where RelationKind<P>: Send, Arc<dyn Fn(&P) -> (Expr<bool>, String) + Sync + Send>: Send,

§

impl<P> Sync for Relation<P>
where RelationKind<P>: Sync, Arc<dyn Fn(&P) -> (Expr<bool>, String) + Sync + Send>: Sync,

§

impl<P> Unpin for Relation<P>
where RelationKind<P>: Unpin, Arc<dyn Fn(&P) -> (Expr<bool>, String) + Sync + Send>: Unpin,

§

impl<P> UnsafeUnpin for Relation<P>
where RelationKind<P>: UnsafeUnpin, Arc<dyn Fn(&P) -> (Expr<bool>, String) + Sync + Send>: UnsafeUnpin,

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<ST, DT> CastableFrom<ST, Initialized, Initialized> for DT
where ST: ?Sized, DT: ?Sized,

Source§

impl<ST, DT> CastableFrom<ST, Uninit, Uninit> for DT
where ST: ?Sized, DT: ?Sized,

Source§

impl<T> ErasedDestructor for T
where T: 'static,

Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T> Instrument for T

Source§

fn instrument(self, span: Span) -> Instrumented<Self> ⓘ

Instruments this type with the provided Span, returning an Instrumented wrapper. Read more
Source§

fn in_current_span(self) -> Instrumented<Self> ⓘ

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> Read<Exclusive, BecauseExclusive> for T
where T: ?Sized,

Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = !

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, !>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
Source§

impl<V, T> VZip<V> for T
where V: MultiLane<T>,

Source§

fn vzip(self) -> V

Source§

impl<T> WithSubscriber for T

Source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self> ⓘ
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a WithDispatch wrapper. Read more
Source§

fn with_current_subscriber(self) -> WithDispatch<Self> ⓘ

Attaches the current default Subscriber to this type, returning a WithDispatch wrapper. Read more