Skip to content

feat(attachable): expand the image shortcut in the type key - #18

Merged
jplot merged 2 commits into
mainfrom
feat/attachable-image-shortcut
Jul 3, 2026
Merged

feat(attachable): expand the image shortcut in the type key#18
jplot merged 2 commits into
mainfrom
feat/attachable-image-shortcut

Conversation

@jplot

@jplot jplot commented Jul 3, 2026

Copy link
Copy Markdown
Member

Summary

The type: :image shortcut validated after #17: instead of enumerating MIME types by hand, filters[document][type]=image expands against ActiveStorage's own configured lists.

Changes

  • TYPE_SHORTCUTS: 'image' (accepted as Symbol or String — query strings only carry strings, and a real content type always has a slash, so the name is unambiguous) expands lazily at query time against ActiveStorage.variable_content_types | web_image_content_types; the lambda only runs once the model proved it has real attachments, so the gem still loads without ActiveStorage
  • composes inside arrays next to literal types (type: ['image', 'application/pdf'])
  • guarded semantics, spec-locked: an unknown literal ('garbage') stays a plain equality that matches nothing — a legitimate empty result, never converted into a silent no-op; an unknown symbol (:video) narrows nothing
  • only image ships: the two lists above are the only media-category lists ActiveStorage exposes (content_types_to_serve_as_binary/content_types_allowed_inline are serving-policy lists, not categories)

How to Test

  1. bundle exec rake
  2. Contract.filterable(filters: { document: { type: 'image' } }) → blobs dont le content_type est dans les listes AS ; type: 'garbage' → résultat vide ; type: :video → relation intacte

jplot added 2 commits July 3, 2026 16:45
- `type: 'image'` / `type: :image` expands lazily at query time
  against ActiveStorage.variable_content_types | web_image_content_types
  — never frozen at boot, and the lambda only runs once the model
  proved it has real attachments, so the gem still loads without
  ActiveStorage
- a real content type always carries a slash, so the shortcut name is
  unambiguous; an unknown literal stays a plain equality that matches
  nothing (never converted into a silent no-op), an unknown symbol
  narrows nothing; shortcuts compose inside arrays next to literals
Copilot AI review requested due to automatic review settings July 3, 2026 14:45

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@fluence-ci

fluence-ci Bot commented Jul 3, 2026

Copy link
Copy Markdown

📊 Code Quality Report

Tool Base PR Δ
Reek 75 75 smells ±0
Flog total 851.5 866.5 🔴 +15.0
Flay total 572 572 ±0

🔍 Complexity / duplication score increased — review the deltas above.

🔍 All 75 reek smells on this PR
Smell File Context Lines Message
ControlParameter lib/filterable/attachable.rb Filterable::Attachable#apply 138 is controlled by argument 'key'
ControlParameter lib/filterable/attachable.rb Filterable::Attachable#presence 156 is controlled by argument 'value'
DataClump lib/filterable/attachable.rb Filterable::Attachable 208, 223, 238 takes parameters ['attachment', 'scope'] to 3 methods
DataClump lib/filterable/attachable.rb Filterable::Attachable 137, 154, 170, 181, 194 takes parameters ['attachment', 'sub_scope', 'value'] to 5 methods
DataClump lib/filterable/attachable.rb Filterable::Attachable 34, 47, 62 takes parameters ['params', 'scope'] to 3 methods
DataClump lib/filterable/declarations_validator.rb Filterable::DeclarationsValidator 79, 93, 113 takes parameters ['kind', 'public_name'] to 3 methods
DataClump lib/filterable/equatable.rb Filterable::Equatable 18, 31, 43, 57 takes parameters ['params', 'scope'] to 4 methods
DataClump lib/filterable/scopable.rb Filterable::Scopable 17, 30, 44 takes parameters ['params', 'scope'] to 3 methods
DataClump lib/filterable/sortable.rb Filterable::Sortable 19, 33, 44 takes parameters ['params', 'scope'] to 3 methods
DataClump lib/filterable/target.rb Filterable::Target 81, 102, 116 takes parameters ['model', 'path'] to 3 methods
DataClump lib/filterable/target.rb Filterable::Target 29, 47, 157 takes parameters ['sub_scope', 'target'] to 3 methods
DataClump lib/filterable/togglable.rb Filterable::Togglable 24, 37, 51 takes parameters ['params', 'scope'] to 3 methods
DuplicateMethodCall lib/filterable/attachable.rb Filterable::Attachable#accepted 67, 69 calls 'declared[name]' 2 times
DuplicateMethodCall lib/filterable/attachable.rb Filterable::Attachable#presence 156, 158 calls 'sub_scope.where' 2 times
DuplicateMethodCall lib/filterable/concern.rb Filterable::Concern#effective_filters 124, 124 calls 'params[:filters]' 2 times
DuplicateMethodCall lib/filterable/declarations_validator.rb Filterable::DeclarationsValidator#path_error 96, 102 calls '@model.name' 2 times
DuplicateMethodCall lib/filterable/sortable.rb Filterable::Sortable#terms 45, 47 calls 'params[:sort]' 2 times
DuplicateMethodCall lib/filterable/target.rb Filterable::Target#apply 174, 174 calls 'resolution[:condition]' 2 times
DuplicateMethodCall lib/filterable/togglable.rb Filterable::Togglable#toggled_on? 74, 76 calls 'TRUE_VALUES.include?(value)' 2 times
FeatureEnvy lib/filterable/concern.rb Filterable::Concern#effective_filters 123, 123, 124, 124 refers to 'params' more than self (maybe move it to another class?)
FeatureEnvy lib/filterable/concern.rb Filterable::Concern#evaluated_defaults 135, 135 refers to 'value' more than self (maybe move it to another class?)
LongParameterList lib/filterable/attachable.rb Filterable::Attachable#apply 137 has 4 parameters
LongParameterList lib/filterable/declarations_validator.rb Filterable::DeclarationsValidator#column_error 113 has 4 parameters
ManualDispatch lib/filterable/attachable.rb Filterable::Attachable#accepted 65 manually dispatches method call
ManualDispatch lib/filterable/attachable.rb Filterable::Attachable#attachment_reflection 240 manually dispatches method call
ManualDispatch lib/filterable/concern.rb Filterable::Concern#add_filter 33 manually dispatches method call
ManualDispatch lib/filterable/concern.rb Filterable::Concern#applied_filters 99 manually dispatches method call
ManualDispatch lib/filterable/concern.rb Filterable::Concern#default_filter_params 48 manually dispatches method call
ManualDispatch lib/filterable/concern.rb Filterable::Concern#effective_filters 125 manually dispatches method call
ManualDispatch lib/filterable/concern.rb Filterable::Concern#evaluated_defaults 135 manually dispatches method call
ManualDispatch lib/filterable/concern.rb Filterable::Concern#filters 21 manually dispatches method call
ManualDispatch lib/filterable/concern.rb Filterable::Concern#indifferent? 147 manually dispatches method call
ManualDispatch lib/filterable/concerns/attachable.rb Filterable::Concerns::Attachable#attachable_attachment_names 25 manually dispatches method call
ManualDispatch lib/filterable/concerns/datable.rb Filterable::Concerns::Datable#datable_attribute_names 27 manually dispatches method call
ManualDispatch lib/filterable/concerns/equatable.rb Filterable::Concerns::Equatable#equatable_attribute_names 25 manually dispatches method call
ManualDispatch lib/filterable/concerns/rangeable.rb Filterable::Concerns::Rangeable#rangeable_attribute_names 26 manually dispatches method call
ManualDispatch lib/filterable/concerns/scopable.rb Filterable::Concerns::Scopable#scopable_scope_names 24 manually dispatches method call
ManualDispatch lib/filterable/concerns/sortable.rb Filterable::Concerns::Sortable#sortable_attribute_names 24 manually dispatches method call
ManualDispatch lib/filterable/concerns/togglable.rb Filterable::Concerns::Togglable#togglable_scope_names 25 manually dispatches method call
ManualDispatch lib/filterable/datable.rb Filterable::Datable#bounds 38 manually dispatches method call
ManualDispatch lib/filterable/declarations_validator.rb Filterable::DeclarationsValidator#attachment_errors 144, 147 manually dispatches method call
ManualDispatch lib/filterable/declarations_validator.rb Filterable::DeclarationsValidator#column_errors 65 manually dispatches method call
ManualDispatch lib/filterable/declarations_validator.rb Filterable::DeclarationsValidator#scope_errors 127, 130 manually dispatches method call
ManualDispatch lib/filterable/equatable.rb Filterable::Equatable#values 59 manually dispatches method call
ManualDispatch lib/filterable/rangeable.rb Filterable::Rangeable#bounds 36 manually dispatches method call
ManualDispatch lib/filterable/scopable.rb Filterable::Scopable#accepted 47, 51 manually dispatches method call
ManualDispatch lib/filterable/togglable.rb Filterable::Togglable#accepted 54, 57 manually dispatches method call
NestedIterators lib/filterable/datable.rb Filterable::Datable#accepted 55 contains iterators nested 2 deep
NestedIterators lib/filterable/datable/after.rb Filterable::Datable::After#call 19 contains iterators nested 2 deep
NestedIterators lib/filterable/datable/before.rb Filterable::Datable::Before#call 19 contains iterators nested 2 deep
NestedIterators lib/filterable/datable/range.rb Filterable::Datable::Range#call 20 contains iterators nested 2 deep
NestedIterators lib/filterable/datable/since.rb Filterable::Datable::Since#call 19 contains iterators nested 2 deep
NestedIterators lib/filterable/rangeable.rb Filterable::Rangeable#accepted 53 contains iterators nested 2 deep
NestedIterators lib/filterable/rangeable/maximum.rb Filterable::Rangeable::Maximum#call 19 contains iterators nested 2 deep
NestedIterators lib/filterable/rangeable/minimum.rb Filterable::Rangeable::Minimum#call 19 contains iterators nested 2 deep
NilCheck lib/filterable/attachable.rb Filterable::Attachable#entries 86 performs a nil-check
NilCheck lib/filterable/concern.rb Filterable::Concern#evaluated_defaults 136 performs a nil-check
NilCheck lib/filterable/equatable.rb Filterable::Equatable#accepted 46 performs a nil-check
NilCheck lib/filterable/scopable.rb Filterable::Scopable#accepted 51 performs a nil-check
NilCheck lib/filterable/target.rb Filterable::Target#polymorphic_hop? 106 performs a nil-check
NilCheck lib/filterable/target.rb Filterable::Target#resolve 87 performs a nil-check
NilCheck lib/filterable/togglable.rb Filterable::Togglable#toggled_on? 71 performs a nil-check
RepeatedConditional lib/filterable/declarations_validator.rb Filterable::DeclarationsValidator 65, 127, 144 tests '@model.respond_to?(reader)' at least 3 times
TooManyStatements lib/filterable/attachable.rb Filterable::Attachable#accepted 62 has approx 6 statements
TooManyStatements lib/filterable/attachable.rb Filterable::Attachable#presence 154 has approx 6 statements
TooManyStatements lib/filterable/concern.rb Filterable::Concern#applied_filters 94 has approx 6 statements
TooManyStatements lib/filterable/datable.rb Filterable::Datable#accepted 52 has approx 6 statements
TooManyStatements lib/filterable/rangeable.rb Filterable::Rangeable#accepted 50 has approx 6 statements
TooManyStatements lib/filterable/scopable.rb Filterable::Scopable#accepted 44 has approx 8 statements
TooManyStatements lib/filterable/sortable.rb Filterable::Sortable#terms 44 has approx 6 statements
TooManyStatements lib/filterable/target.rb Filterable::Target#resolve 81 has approx 8 statements
TooManyStatements lib/filterable/togglable.rb Filterable::Togglable#accepted 51 has approx 7 statements
TooManyStatements lib/filterable/value_normalization.rb Filterable::ValueNormalization#normalize 15 has approx 6 statements
UtilityFunction lib/filterable/concern.rb Filterable::Concern#indifferent? 146 doesn't depend on instance state (maybe move it to another class?)
UtilityFunction lib/filterable/declarations_validator.rb Filterable::DeclarationsValidator#column_error 113 doesn't depend on instance state (maybe move it to another class?)
📊 Flog complexity breakdown
866.5: flog total
     7.2: flog/method average

    32.0: main#none
    21.2: Filterable::Railtie::initializer#filterable.active_record lib/filterable/railtie.rb:19-30
    16.6: Filterable::Target#narrow        lib/filterable/target.rb:29-37
    16.4: Filterable::Scopable#accepted    lib/filterable/scopable.rb:44-55
    15.5: Filterable::Sortable#terms       lib/filterable/sortable.rb:44-53
    15.0: Filterable::Togglable#accepted   lib/filterable/togglable.rb:51-61
    14.8: Filterable::Concern#applied_filters lib/filterable/concern.rb:94-101
    14.4: Filterable::Concern#effective_filters lib/filterable/concern.rb:122-127
    14.1: Filterable::Attachable#accepted  lib/filterable/attachable.rb:62-71
    13.9: Filterable::Target#polymorphic_join lib/filterable/target.rb:133-137
    12.5: Filterable::Target#resolve       lib/filterable/target.rb:81-93
    12.5: Filterable::Attachable#entries   lib/filterable/attachable.rb:80-87
    12.4: Filterable::Datable::Range#call  lib/filterable/datable/range.rb:17-24
    11.9: Filterable::Attachable#presence  lib/filterable/attachable.rb:154-161
    11.8: Filterable::Target#polymorphic_resolve lib/filterable/target.rb:116-124
    11.5: Filterable::Concern#evaluated_defaults lib/filterable/concern.rb:133-138
    11.3: Filterable::DeclarationsValidator#errors lib/filterable/declarations_validator.rb:49-53
    10.9: Filterable::DeclarationsValidator#path_error lib/filterable/declarations_validator.rb:93-103
    10.4: Filterable::Rangeable#accepted   lib/filterable/rangeable.rb:50-58
    10.4: Filterable::Datable#accepted     lib/filterable/datable.rb:52-60
    10.3: Filterable::ValueNormalization#normalize lib/filterable/value_normalization.rb:15-25
    10.1: Filterable::Concern#filterable   lib/filterable/concern.rb:76-81
     9.9: Filterable::Target#unpack        lib/filterable/target.rb:62-70
     9.6: Filterable::DeclarationsValidator#scope_errors lib/filterable/declarations_validator.rb:125-134
     9.6: Filterable::Target#apply         lib/filterable/target.rb:172-176
     9.0: Filterable::Concerns::Datable#none
     8.9: Filterable::Datable::After#call  lib/filterable/datable/after.rb:16-21
     8.9: Filterable::Datable::Before#call lib/filterable/datable/before.rb:16-21
     8.9: Filterable::Datable::Since#call  lib/filterable/datable/since.rb:16-21
     8.9: Filterable::Rangeable::Maximum#call lib/filterable/rangeable/maximum.rb:16-21
     8.9: Filterable::Rangeable::Minimum#call lib/filterable/rangeable/minimum.rb:16-21
     8.8: Filterable::Concern#add_filter   lib/filterable/concern.rb:30-38
     8.6: Filterable::Concern#default_filter_params lib/filterable/concern.rb:46-53
     8.6: Filterable::Concerns::Togglable#togglable_scope_names lib/filterable/concerns/togglable.rb:23-30
     8.6: Filterable::Concerns::Sortable#sortable_attribute_names lib/filterable/concerns/sortable.rb:22-29
     8.6: Filterable::Concerns::Scopable#scopable_scope_names lib/filterable/concerns/scopable.rb:22-29
     8.6: Filterable::Concerns::Rangeable#rangeable_attribute_names lib/filterable/concerns/rangeable.rb:24-31
     8.6: Filterable::Concerns::Attachable#attachable_attachment_names lib/filterable/concerns/attachable.rb:23-30
     8.6: Filterable::Concerns::Datable#datable_attribute_names lib/filterable/concerns/datable.rb:25-32
     8.6: Filterable::Concerns::Equatable#equatable_attribute_names lib/filterable/concerns/equatable.rb:23-30
     8.5: Filterable::Target#polymorphic_hop? lib/filterable/target.rb:102-107
     8.4: Filterable::Target#narrow_equal  lib/filterable/target.rb:47-54
     8.3: Filterable::Sortable#call        lib/filterable/sortable.rb:19-25
     8.3: Filterable::DeclarationsValidator#attachment_errors lib/filterable/declarations_validator.rb:142-151
     8.3: Filterable::Datable#bounds       lib/filterable/datable.rb:36-40
     8.3: Filterable::Rangeable#bounds     lib/filterable/rangeable.rb:34-38
📊 Flay duplication breakdown
Total score (lower is better) = 572

1) Similar code found in :module (mass = 255)
  lib/filterable/datable/after.rb:4
  lib/filterable/datable/before.rb:4
  lib/filterable/datable/since.rb:4
  lib/filterable/rangeable/maximum.rb:4
  lib/filterable/rangeable/minimum.rb:4

2) Similar code found in :module (mass = 205)
  lib/filterable/concerns/attachable.rb:4
  lib/filterable/concerns/equatable.rb:4
  lib/filterable/concerns/scopable.rb:4
  lib/filterable/concerns/sortable.rb:4
  lib/filterable/concerns/togglable.rb:4

3) Similar code found in :defn (mass = 66)
  lib/filterable/datable.rb:52
  lib/filterable/rangeable.rb:50

4) Similar code found in :defn (mass = 46)
  lib/filterable/datable.rb:36
  lib/filterable/rangeable.rb:34

@fluence-ci

fluence-ci Bot commented Jul 3, 2026

Copy link
Copy Markdown

🛡️ RuboCop: 0 offenses (0)

Base PR Δ
Offenses 0 0 ±0

No RuboCop offenses.

@fluence-ci

fluence-ci Bot commented Jul 3, 2026

Copy link
Copy Markdown

🛡️ Coverage Report: 100.0% (0.0%)

████████████████████ 100.0% — 573/573 lines covered

Base PR Δ
Total 100.0% 100.0% ±0%

All changes are tested.

Changed files

File Coverage Δ vs base Lines
🟢 lib/filterable/attachable.rb 100.0% ±0% 63/63

@fluence-ci

fluence-ci Bot commented Jul 3, 2026

Copy link
Copy Markdown

🛡️ Documentation Quality: 100.0% (0.0%)

████████████████████ 100.0% — 1310/1310 checks passing

Base PR Δ
Coverage 100.0% 100.0% ±0%
Failing 0 0 ±0

Documentation quality above threshold.

Changed files

File Failing Δ vs base
🟢 lib/filterable/attachable.rb 0 ±0

@jplot
jplot merged commit f99eb8c into main Jul 3, 2026
16 checks passed
@jplot
jplot deleted the feat/attachable-image-shortcut branch July 3, 2026 14:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants