Skip to content

Commit f99eb8c

Browse files
authored
Merge pull request #18 from fluence-eu/feat/attachable-image-shortcut
feat(attachable): expand the image shortcut in the type key
2 parents ed078d3 + b1d9ca6 commit f99eb8c

3 files changed

Lines changed: 68 additions & 2 deletions

File tree

‎README.md‎

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -225,7 +225,7 @@ accepts these nested keys under `filters[<name>]`:
225225
| Key | Meaning |
226226
|------------------------|----------------------------------------------------|
227227
| `present` | strict true or bare key → attached; explicit false (`'false'`, `'0'`) → unattached |
228-
| `type` | blob content type — scalar `=`, array `IN` |
228+
| `type` | blob content type — scalar `=`, array `IN`, or the `image` shortcut |
229229
| `min_size` / `max_size`| inclusive blob byte size bounds |
230230

231231
```ruby
@@ -237,8 +237,15 @@ end
237237

238238
Contract.filterable(filters: { document: { present: true } })
239239
Contract.filterable(filters: { document: { type: %w[application/pdf image/png], max_size: 5_000_000 } })
240+
Contract.filterable(filters: { document: { type: 'image' } })
240241
```
241242

243+
The `image` shortcut expands at query time against
244+
`ActiveStorage.variable_content_types` + `web_image_content_types` (a real
245+
content type always carries a slash, so the name is unambiguous), and composes
246+
inside arrays next to literal types. An unknown literal stays a plain equality
247+
that matches nothing; an unknown symbol narrows nothing.
248+
242249
Everything resolves through the associations `has_one_attached` /
243250
`has_many_attached` generate, so the gem still has no ActiveStorage
244251
dependency; a declared name without those associations narrows nothing, and

‎lib/filterable/attachable.rb‎

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,14 @@ module Attachable
1515
# The explicit values that flip +present+ to its unattached side.
1616
FALSE_VALUES = [false, 0, '0', 'false'].freeze
1717

18+
# The symbolic +type+ shortcuts, each expanded lazily at query time against
19+
# ActiveStorage's configured content-type lists — never frozen at boot, and
20+
# only ever called once the model proved it has real attachments, so the
21+
# gem still loads without ActiveStorage.
22+
TYPE_SHORTCUTS = {
23+
'image' => -> { ActiveStorage.variable_content_types | ActiveStorage.web_image_content_types }
24+
}.freeze
25+
1826
module_function
1927

2028
# Narrows the scope from each accepted attachment key.
@@ -72,12 +80,37 @@ def accepted(params, scope)
7280
def entries(name, attachment, bounds)
7381
[
7482
[:present, presence_value(bounds)],
75-
[:type, Filterable::ValueNormalization.normalize(bounds[:type])],
83+
[:type, content_types(bounds[:type])],
7684
[:min_size, Filterable::Rangeable.parse(bounds[:min_size])],
7785
[:max_size, Filterable::Rangeable.parse(bounds[:max_size])]
7886
].filter_map { |key, value| [name, attachment, key, value, bounds[key]] unless value.nil? }
7987
end
8088

89+
# The normalized +type+ value, with symbolic shortcuts expanded. A real
90+
# content type always carries a slash, so a known shortcut name is
91+
# unambiguous; an unknown literal stays a plain equality that matches
92+
# nothing, never a silent no-op.
93+
#
94+
# @api private
95+
# @param raw [Object] the raw +type+ value.
96+
# @return [String, Array, nil] the usable content type(s), or nil.
97+
def content_types(raw)
98+
Filterable::ValueNormalization.normalize(expand_types(raw))
99+
end
100+
101+
# Expands every known shortcut in a raw +type+ value, leaving everything
102+
# else untouched.
103+
#
104+
# @api private
105+
# @param raw [Object] the raw +type+ value, scalar or array.
106+
# @return [Object] the value with shortcuts replaced by content-type lists.
107+
def expand_types(raw)
108+
return raw.flat_map { |value| expand_types(value) } if raw.is_a?(Array)
109+
110+
shortcut = TYPE_SHORTCUTS[raw.to_s]
111+
shortcut ? shortcut.call : raw
112+
end
113+
81114
# How a +present+ key reads: +:attached+ on a strict true or bare key,
82115
# +:missing+ on an explicit false, nil otherwise.
83116
#

‎spec/active_storage_spec.rb‎

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,32 @@ def attach(record, name, content_type, byte_size = 1)
154154
expect(result).to contain_exactly(pdf_contract, png_contract)
155155
end
156156

157+
it 'expands the image shortcut against the configured content types' do
158+
[:image, 'image'].each do |shortcut|
159+
result = Contract.filterable(filters: { document: { type: shortcut } })
160+
161+
expect(result).to contain_exactly(png_contract)
162+
end
163+
end
164+
165+
it 'expands the image shortcut inside an array, next to literal types' do
166+
result = Contract.filterable(filters: { document: { type: ['image', 'application/pdf'] } })
167+
168+
expect(result).to contain_exactly(pdf_contract, png_contract)
169+
end
170+
171+
it 'keeps an unknown literal type as an equality that matches nothing' do
172+
result = Contract.filterable(filters: { document: { type: 'garbage' } })
173+
174+
expect(result).to be_empty
175+
end
176+
177+
it 'ignores an unknown symbolic type' do
178+
result = Contract.filterable(filters: { document: { type: :video } })
179+
180+
expect(result).to contain_exactly(pdf_contract, png_contract, bare_contract)
181+
end
182+
157183
it 'applies inclusive byte size bounds' do
158184
attach(pdf_contract, 'annexes', 'text/csv', 100)
159185
attach(png_contract, 'annexes', 'text/csv', 5000)

0 commit comments

Comments
 (0)