Class: OpenLoam::Perspective
- Inherits:
-
TenantRecord
- Object
- TenantRecord
- OpenLoam::Perspective
- Includes:
- Auditable
- Defined in:
- app/models/open_loam/perspective.rb
Overview
A saved view of an entity's admin index — which columns show, active filters, sort, and page size — that a user can name, make their default, or share.
Three visibility tiers, widening in audience:
* "private" — only its owner sees it (owner_id).
* "role" — everyone with the named membership role in the tenant.
* "tenant" — everyone in the tenant.
A private view is invisible to others by construction (see OpenLoam::Perspectives.visible_to), not merely by a permission check.
Tenant-scoped like everything else, and audited. Optimistic-locked (lock_version) so two people editing a shared view don't silently clobber each other — the admin controller rescues ActiveRecord::StaleObjectError.
Constant Summary collapse
- VISIBILITIES =
%w[private role tenant].freeze
- NON_FILTERABLE =
Columns a saved filter/sort may NEVER touch: tenant_id is enforced by the default scope (a filter on it is a footgun, not a leak), and the rest are plumbing, not data. Everything else on the entity is fair game.
%w[id tenant_id lock_version deleted_at created_at updated_at].freeze
Constants included from Auditable
Instance Method Summary collapse
-
#apply(scope) ⇒ Object
Apply this view's stored filters and sort to a base scope, returning a relation (columns and page_size are read by the controller/view).
- #columns ⇒ Object
-
#make_default! ⇒ Object
Make this the default for its audience, unsetting the sibling default it would otherwise compete with (same entity + visibility, and same owner/role where those narrow the audience).
- #page_size ⇒ Object
Instance Method Details
#apply(scope) ⇒ Object
Apply this view's stored filters and sort to a base scope, returning a relation (columns and page_size are read by the controller/view). SAFE BY CONSTRUCTION: a filter or sort key is honored only if it names a real, non-plumbing column of the entity — a crafted key (arbitrary SQL, or tenant_id) is skipped, never executed. Values ride as hash conditions, so Active Record quotes them.
39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 |
# File 'app/models/open_loam/perspective.rb', line 39 def apply(scope) allowed = filterable_columns(scope.klass) filters = config_hash("filters") # A free-text "q" runs the entity's own Searchable search when it has one; # on a non-searchable entity `search` returns `all`, so this safely no-ops. if filters["q"].present? && scope.klass.respond_to?(:search) scope = scope.search(filters["q"]) end filters.each do |column, value| next if column == "q" next unless allowed.include?(column.to_s) scope = scope.where(column => value) end sort = config_hash("sort") if allowed.include?(sort["field"].to_s) # reorder (not order) so the saved sort REPLACES the base order; id keeps # pagination stable when the sort column has ties. direction = sort["dir"].to_s == "desc" ? :desc : :asc scope = scope.reorder(sort["field"] => direction, id: :desc) end scope end |
#columns ⇒ Object
67 68 69 |
# File 'app/models/open_loam/perspective.rb', line 67 def columns Array(config_hash_root["columns"]) end |
#make_default! ⇒ Object
Make this the default for its audience, unsetting the sibling default it would otherwise compete with (same entity + visibility, and same owner/role where those narrow the audience).
78 79 80 81 82 83 84 85 86 87 |
# File 'app/models/open_loam/perspective.rb', line 78 def make_default! siblings = self.class.where(entity_type: entity_type, visibility: visibility).where.not(id: id) siblings = siblings.where(owner_id: owner_id) if visibility == "private" siblings = siblings.where(role: role) if visibility == "role" transaction do siblings.update_all(is_default: false) update!(is_default: true) end end |
#page_size ⇒ Object
71 72 73 |
# File 'app/models/open_loam/perspective.rb', line 71 def page_size config_hash_root["page_size"] end |