Module: Rhino::HidableColumns

Extended by:
ActiveSupport::Concern
Included in:
RhinoModel
Defined in:
lib/rhino/concerns/hidable_columns.rb

Overview

Column-level visibility control concern. Mirrors the Laravel HidableColumns trait.

Base hidden columns: password, remember_token, created_at, updated_at,

deleted_at, discarded_at, email_verified_at

Usage:

class User < ApplicationRecord
  include Rhino::HidableColumns

  rhino_additional_hidden :secret_field, :internal_notes
end

Adding computed attributes to JSON responses:

class Comment < Rhino::RhinoModel
  def author_name
    user&.name || 'Anonymous'
  end

  def rhino_computed_attributes
    {
      'author_name' => author_name
    }
  end
end

Policy-based hiding:

class UserPolicy < Rhino::ResourcePolicy
  def hidden_attributes_for_show(user)
    has_role?(user, 'admin') ? [] : ['email', 'phone']
  end

  def permitted_attributes_for_show(user)
    has_role?(user, 'admin') ? ['*'] : ['id', 'name', 'avatar']
  end
end

Constant Summary collapse

BASE_HIDDEN_COLUMNS =
%w[
  password
  password_digest
  remember_token
  created_at
  updated_at
  deleted_at
  discarded_at
  email_verified_at
].freeze

Instance Method Summary collapse

Instance Method Details

#as_rhino_jsonHash

Serialize to JSON excluding hidden columns and respecting policy whitelist.

The current user is resolved automatically from RequestStore. Policy filtering (blacklist + whitelist) is applied AFTER computed attributes are merged, so computed attributes are always subject to policy control.

Do NOT override this method. Override rhino_computed_attributes instead to add computed/virtual attributes to the JSON response.

Returns:

  • (Hash)


87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
# File 'lib/rhino/concerns/hidable_columns.rb', line 87

def as_rhino_json
  user = rhino_current_user
  hidden = hidden_columns_for(user)
  result = as_json(except: hidden)

  # Merge computed attributes from model BEFORE applying policy filtering
  computed = rhino_computed_attributes
  result.merge!(computed) if computed.is_a?(Hash) && computed.any?

  # Apply blacklist to the final hash (covers DB columns from as_json
  # overrides AND computed attributes from rhino_computed_attributes)
  hidden_set = Set.new(hidden)
  result.reject! { |key, _| hidden_set.include?(key) }

  # Apply whitelist to the final hash (covers computed attributes too)
  permitted = policy_permitted_attributes(user)
  if permitted && permitted != ['*']
    permitted_set = Set.new(permitted.map(&:to_s))
    permitted_set.add('id') # id is always allowed
    result.select! { |key, _| permitted_set.include?(key) }
  end

  result
end

#hidden_columns_for(user = nil) ⇒ Array<String>

Get the list of columns to hide for the current user. Merges base + static + policy-defined hidden columns. Resolves the user from RequestStore automatically.

Returns:

  • (Array<String>)

    Column names to hide



69
70
71
72
73
74
75
# File 'lib/rhino/concerns/hidable_columns.rb', line 69

def hidden_columns_for(user = nil)
  user ||= rhino_current_user
  columns = BASE_HIDDEN_COLUMNS.dup
  columns.concat(additional_hidden_columns)
  columns.concat(policy_hidden_columns(user))
  columns.uniq
end

#rhino_computed_attributesHash

Override this method in your model to add computed/virtual attributes to the JSON response. These attributes are subject to policy-level blacklist (hidden_attributes_for_show) and whitelist (permitted_attributes_for_show) just like database columns.

Examples:

def rhino_computed_attributes
  {
    'full_name' => "#{first_name} #{last_name}",
    'is_overdue' => due_date&.past?,
    'days_until_expiry' => expiry_date ? (expiry_date - Date.current).to_i : nil
  }
end

Returns:

  • (Hash)

    key-value pairs to merge into the JSON response



127
128
129
# File 'lib/rhino/concerns/hidable_columns.rb', line 127

def rhino_computed_attributes
  {}
end