Scopes

WARNING
This section is a work in progress.
Sometimes you might need to segment your data beyond just a few filters. You might have an User resource but you frequently need to see all the Active users or Admin users. You can use a filter for that or add a scope.
Generating scopes
bin/rails generate avo:scope admins# app/avo/scopes/admins.rb
class Avo::Scopes::Admins < Avo::Scopes::BaseScope
self.name = "Admins" # Name displayed on the scopes bar
self.description = "Admins only" # This is the tooltip value
self.scope = :admins # valid scope on the model you're using it
self.visible = -> { true } # control the visibility
end
# app/models/user.rb
class User < ApplicationRecord
scope :admins, -> { where role: :admin } # This is used in the scope file above
endRegistering scopes
Because scopes are re-utilizable, you must manually add that scope to a resource using the scope method inside the scopes method.
# app/avo/resources/user.rb
class Avo::Resources::User < Avo::BaseResource
def scopes
scope Avo::Scopes::Admins
end
end-> default
The default option lets you select a default scope that is applied when you navigate to the resources page.
This option can be configured using a static value or a proc, which is executed using the Avo::ExecutionContext. Check the Execution Context section for more information about what's available in the execution context.
# app/avo/resources/user.rb
class Avo::Resources::User < Avo::BaseResource
def scopes
scope Avo::Scopes::OddId
# EvenId scope is applied as default
scope Avo::Scopes::EvenId, default: true
end
end# app/avo/resources/user.rb
class Avo::Resources::User < Avo::BaseResource
def scopes
scope Avo::Scopes::OddId
# EvenId scope is applied as default if the current user is an admin
scope Avo::Scopes::EvenId, default: -> { current_user.admin? }
end
end-> remove_scope_all
If you don't want to have the All default scope you can remove it by executing the remove_scope_all method inside scopes method.
# app/avo/resources/user.rb
class Avo::Resources::User < Avo::BaseResource
def scopes
remove_scope_all
scope Avo::Scopes::Admins
end
endOptions
Execution Context
All options can be configured using static values or procs. The procs are executed using the Avo::ExecutionContext, which provides access to all default methods and attributes available in Avo's execution context. Each option has access to:
queryresourcescopescoped_query(check below Performance Note)
Performance Note
Inside each proc, you can call scoped_query, but use it with caution as it executes the scope. If the scope takes a while to execute, this could impact performance.
-> name
This value is going to be displayed on the scopes bar as the name of the scope.
Record counts
To show a record count next to the scope, use the built-in counter option instead of computing it inside name. It supports lazy and on-hover loading so it won't slow down the page.
# app/avo/scopes/even_id.rb
class Avo::Scopes::EvenId < Avo::Scopes::BaseScope
self.name = "Even"
end# app/avo/scopes/even_id.rb
class Avo::Scopes::EvenId < Avo::Scopes::BaseScope
# Please see the performance note above if you're using `scoped_query`
self.name = -> { "Even (#{scoped_query.count})" }
end-> description
This value is going to be displayed when the user hovers over the scope.
# app/avo/scopes/even_id.rb
class Avo::Scopes::EvenId < Avo::Scopes::BaseScope
self.description = "Only records that have an even ID."
end# app/avo/scopes/even_id.rb
class Avo::Scopes::EvenId < Avo::Scopes::BaseScope
self.description = -> {
"Only #{resource.name.downcase.pluralize} that have an even ID"
}
end-> scope
The scope you return here is going to be applied to the query of records on that page.
You can use a symbol which will indicate the scope on that model or a proc which will have the query available so you can apply any modifications you need.
# app/avo/scopes/even_id.rb
class Avo::Scopes::EvenId < Avo::Scopes::BaseScope
# This will use the `even_id` scope from the model
self.scope = :even_id
end# app/avo/scopes/even_id.rb
class Avo::Scopes::EvenId < Avo::Scopes::BaseScope
self.scope = -> { query.where("#{resource.model_key}.id % 2 = ?", "0") }
end-> visible
From this block you can show, hide, and authorize the scope on the resource.
Extra Access
The visible option has additional access to parent_record and parent_resource variables, which are useful when working with nested resources or association contexts.
# app/avo/scopes/even_id.rb
class Avo::Scopes::EvenId < Avo::Scopes::BaseScope
# Only show this scope to admins
self.visible = -> { current_user.admin? }
end-> counter
Displays a count badge next to the scope's label showing how many records match the scope. This is the built-in, recommended alternative to computing the count manually inside name.
The count is computed against the resource's authorization-scoped query and ignores any active search or filters, so it always reflects the whole scope.
Set it to true (or :eager) to render the count during the request:
# app/avo/scopes/active.rb
class Avo::Scopes::Active < Avo::Scopes::BaseScope
self.counter = true
endPerformance Note
An eager counter runs its count query on every page load. For large tables, use :lazy or :hover to defer it.
For finer control, pass a Hash with any of the keys below.
-> counter.loading
Controls when the count is fetched.
| Mode | Behavior |
|---|---|
:eager (or true) | Count is computed during the request and rendered inline. |
:lazy | Count loads in a deferred turbo-frame after the page paints. |
:hover | Same as :lazy — loads in a deferred turbo-frame after paint. |
# app/avo/scopes/active.rb
class Avo::Scopes::Active < Avo::Scopes::BaseScope
self.counter = :lazy
end-> counter.count
A custom count value. The count proc runs in the execution context with access to query, resource, and scope, where query is the unfiltered base query. It can return any value (not just a number) — whatever it returns becomes value in the format block:
# app/avo/scopes/active.rb
class Avo::Scopes::Active < Avo::Scopes::BaseScope
self.counter = {
loading: :lazy,
count: -> { query.active.count }
}
end-> counter.visible
A boolean or proc that shows the badge only in some cases. When it evaluates falsy, the count is hidden — the scope tab itself still shows (use the scope's own visible option to hide the tab). On nested association indexes, the proc receives parent_record and parent_resource the same way the scope's visible option does:
# app/avo/scopes/active.rb
class Avo::Scopes::Active < Avo::Scopes::BaseScope
self.counter = {
loading: :lazy,
visible: -> { current_user.admin? }
}
end-> counter.format
By default the count is rendered with number_to_delimited (e.g. 1,234). A format block renders it however you like. It runs in the execution context — the count is available as value (the result of your count block, or the computed count when you don't set one), alongside query, resource, and scope, where query is the same unfiltered base query used by counter.count — and can return any value (coerced to a string):
# app/avo/scopes/active.rb
class Avo::Scopes::Active < Avo::Scopes::BaseScope
self.counter = {
loading: :lazy,
format: -> { "#{value} #{resource.name.pluralize.downcase}" }
}
endThe format block can return plain text, so it also works as a text-only badge — it renders even when the scope has no numeric count. If your format block raises, Avo falls back to the default delimited format, so a bad formatter never breaks the page.
Full example
# app/avo/scopes/even_id.rb
class Avo::Scopes::EvenId < Avo::Scopes::BaseScope
self.name = "Even"
# This will compute the description based on the resource name
self.description = -> {
"Only #{resource.name.downcase.pluralize} that have an even ID"
}
# This will scope the query to only even IDs
self.scope = -> { query.where("#{resource.model_key}.id % 2 = ?", "0") }
# Only show this scope to admins
self.visible = -> { current_user.admin? }
# Show a record count badge, loaded lazily so it won't slow down the page
self.counter = :lazy
end