Skip to content

feat: allow hash keys to be resolved as template attributes#354

Open
papilip wants to merge 1 commit into
martenframework:mainfrom
papilip:feature/hash-template-key-access
Open

feat: allow hash keys to be resolved as template attributes#354
papilip wants to merge 1 commit into
martenframework:mainfrom
papilip:feature/hash-template-key-access

Conversation

@papilip

@papilip papilip commented Apr 12, 2026

Copy link
Copy Markdown
Contributor

Summary

Allow Hash keys to be used as template attributes, so that {{ my_hash.my_key }} resolves to the value associated with my_key.

Before: Only methods listed in template_attributes (like empty?, size, keys) were accessible. Hash keys were silently ignored, resulting in empty values with no error — a common pitfall.

After: Hash keys are resolved first, with a fallback to the standard template_attributes methods.

Motivation

When passing a Hash(String, String) to a template context:

render "my_template.html", context: {
  item: {"type" => "order", "amount" => "60.00"},
}

Users expect {{ item.type }} to output order. Instead, it silently outputs nothing because Hash only exposes methods like empty? and size to the template engine.

This is particularly confusing because:

  • No error is raised — values are just empty
  • NamedTuple works as expected via Object::Auto
  • The fix is non-obvious (requires creating a custom struct with Object::Auto)

Changes

  • src/marten/template/ext/hash.cr: Override resolve_template_attribute to check has_key?(key) before falling back to the macro-generated method
  • spec/marten/template/ext/hash_spec.cr: 4 new tests covering key access, fallback to standard attributes, and error on unknown keys

Test plan

  • Existing hash spec tests still pass (9 tests)
  • New tests for key access (4 tests)
  • size attribute still works when a key named size does not exist (fallback)
  • UnknownVariable is raised for non-existent keys that are not standard attributes

This makes it possible to access hash values using {{ my_hash.my_key }}
in templates, in addition to the standard hash methods (empty?, size, etc.).

Previously, only methods listed in template_attributes were accessible.
Hash keys were silently ignored, resulting in empty values in templates
with no error raised — a common pitfall when passing Hash(String, String)
to template contexts.

The key lookup takes precedence over standard attributes, with a fallback
to the original resolve_template_attribute for methods like size, keys, etc.
Comment on lines +12 to +16
if has_key?(key)
self[key]
else
previous_def
end

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unfortunately, this change has serious limitations. If a hash contains a key like size, then it will be impossible to get the size from the hash when calling {{ my_hash.size }}. Similarly, any other method specified in #template_attributes will only work as long as a key with the same name is not part of the hash.

I think that we should give access to hash methods with priority over keys. That said, I don't want to allow hash key lookups if there is not a built-in way to actually do key lookups for keys such as "size".

The real way forward would be to add both support for dotted access and bracket access (eg. {{ my_hash["size"] }}).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

2 participants