Module: Rhino::ComputedAttributeSpec

Defined in:
lib/rhino/computed_attribute_spec.rb

Overview

Parses a model's computed-attribute declarations (+rhino_record_computed_attributes+ / rhino_collection_computed_attributes) and binds the arguments a client sent for ?attributes[param]=value (and ?computed_attributes[param]=value) to the declared parameters.

Declaration forms (both may be mixed in one hash):

{
# Legacy: anything that is not an extended spec is used as-is — a
# callable is called, any other value is serialized literally.
'active_users_count' => ->(scope, _user) { scope.count },
'schema_version'     => 3,

# Extended: a hash carrying at least one of params/optional/with.
'revenue' => {
  params: %i[from to], optional: [:to],
  with: ->(scope, _user, from, to = nil) { ... }
}
}

Unlike named scopes, there is deliberately NO symbol or bare-list shorthand: 'version' => 'v3' and 'tags' => %w[a b] are valid literal value declarations today, and reinterpreting them as parameter lists would silently change what a shipped model returns. A declaration is an extended spec if and only if it is a hash carrying params, optional or with — those three keys are reserved inside a computed-attribute declaration.

Constant Summary collapse

SUBJECT =

The noun every computed-attribute argument error message starts with.

"Computed attribute"
SPEC_KEYS =

The keys whose presence marks a declaration hash as an extended spec.

%i[params optional with].freeze

Class Method Summary collapse

Class Method Details

.bind(name, spec, raw) ⇒ Object

Bind the raw value a client sent for one attribute to positional arguments, in the order the model declared them.

Callers MUST have already checked that the attribute is declared and policy-visible: the messages raised here name the attribute.



96
97
98
99
100
101
102
103
104
# File 'lib/rhino/computed_attribute_spec.rb', line 96

def bind(name, spec, raw)
  Rhino::ArgumentBinder.bind(
    subject: SUBJECT,
    name: name,
    spec: spec,
    raw: raw,
    error_class: Rhino::InvalidComputedAttributeArgumentsError
  )
end

.names(declared) ⇒ Object

The declared attribute names only.



72
73
74
# File 'lib/rhino/computed_attribute_spec.rb', line 72

def names(declared)
  normalize(declared).keys
end

.normalize(declared) ⇒ Object

Normalize a raw declaration hash into name => { params:, optional:, target: }.

target is the callable (or the literal value) that produces the attribute; for a legacy declaration it is the declared value itself.



45
46
47
48
49
50
51
# File 'lib/rhino/computed_attribute_spec.rb', line 45

def normalize(declared)
  return {} unless declared.is_a?(Hash)

  declared.each_with_object({}) do |(name, value), out|
    out[name.to_s] = normalize_entry(value)
  end
end

.normalize_entry(value) ⇒ Object



53
54
55
56
57
58
59
60
61
# File 'lib/rhino/computed_attribute_spec.rb', line 53

def normalize_entry(value)
  return { params: [], optional: [], target: value } unless spec?(value)

  spec = value.symbolize_keys

  Rhino::ArgumentBinder
    .normalize_params(spec[:params], spec[:optional])
    .merge(target: spec[:with])
end

.parameterised?(spec) ⇒ Boolean

Whether an entry is parameterised, and therefore must be called strictly with the bound arguments rather than through the tolerant arity branch a parameterless declaration keeps.

Returns:

  • (Boolean)


87
88
89
# File 'lib/rhino/computed_attribute_spec.rb', line 87

def parameterised?(spec)
  Array(spec[:params]).any?
end

.requires_arguments?(spec) ⇒ Boolean

Whether the attribute declares at least one parameter that the client MUST supply. Such attributes are skipped — never 403'd — when no selection was made (a bare GET /computed) and when a direct serialization call passes no arguments for them.

Returns:

  • (Boolean)


80
81
82
# File 'lib/rhino/computed_attribute_spec.rb', line 80

def requires_arguments?(spec)
  (Array(spec[:params]).map(&:to_s) - Array(spec[:optional]).map(&:to_s)).any?
end

.spec?(value) ⇒ Boolean

Whether a declared value is an extended spec rather than a legacy callable/literal declaration.

Returns:

  • (Boolean)


65
66
67
68
69
# File 'lib/rhino/computed_attribute_spec.rb', line 65

def spec?(value)
  return false unless value.is_a?(Hash)

  SPEC_KEYS.any? { |key| value.key?(key) || value.key?(key.to_s) }
end