Content
View differences
Updated by David Friquet about 8 hours ago
### Before filing a report
Found on `dev` at v17.8.0 while implementing FND-169. No existing report found for `Type`'s `default_scope`.
### Steps to reproduce
Server-side ordering defect — reproduces in a Rails console, no UI needed:
1. Use any instance with seeded work package types
2. Open a Rails console
3. Run `Type.roots.order(:name).pluck(:name)`
4. Compare with `Type.roots.reorder(:name).pluck(:name)`
To see it surface in the UI, with the `type_variants` feature flag enabled:
1. Log in as an administrator
2. Go to Administration → Work packages → Types, and add a variant under two different root types
3. Open any type → any configuration tab → "Switch to linked mode" (or "Change source" if already linked)
4. Look at the order of the source list in the dialog
### What is the buggy behavior?
* `**.order(...)**` **on** `**Type**` **is silently ignored.** `app/models/type.rb:97` declares `default_scope { order("position ASC") }`, so a caller's ordering is appended as a tiebreaker that never fires:
```text
Type.order(:name).to_sql → ORDER BY position ASC, "types"."name" ASC
Type.roots.order(:name) → ["Task", "Milestone", "Summary task", "Feature", "Epic"]
Type.roots.reorder(:name) → ["All CFS", "Another type", "Bug", "Epic", "Feature"]
```
* **Ordering the whole** `**types**` **table by** `**position**` **is meaningless once variants exist.** `acts_as_list scope: :parent_id` (`app/models/type.rb:82`) numbers each family independently, so several variants legitimately hold `position = 1` and interleave between unrelated root types. On a seeded instance the linked-mode source list comes out as:
```text
Bug, Task, Smoke consumer, Smoke source, Task, Task, Smoke consumer, Milestone, Summary task, Feature
```
* **This is a regression, not long-standing debt.** Before variants, `app/models/type.rb` declared a plain `acts_as_list` — one single list covering the whole table — and the global position ordering was correct.
* **Two call sites were written expecting** `**.order(:name)**` **to work, and it never did:** the FND-169 add-type dialog (fixed there by never ordering across lists) and `app/forms/work_package_types/configuration_links/source_form.rb:67` (still open, separate WP).
`acts_as_list` is behaving correctly and cannot help here. Its `scope:` option partitions the lists properly — `first?`, `move_higher` and `higher_item` all respect the partition, and several records legitimately share `position = 1`, one per family. What the gem deliberately does not provide is a total order _across_ lists, because no such order is defined. It also never adds a `default_scope` of its own and defends against the host's, using `reorder(...)` internally throughout.
### What is the expected behavior?
1. `Type` has no `default_scope`, and a caller's `.order(...)` takes effect
2. Every read whose order is user-visible or API-visible orders explicitly. Sites listing whole families read one `acts_as_list` list at a time — roots in position order, then each family's members — instead of ordering the flat table
3. Specs assert ordering at the user-visible boundaries: the API type collection, the work package type picker, and the admin type list
Suggested sequence. The safety net has to exist **before** the removal, or the sweep cannot be verified:
1. Add `scope :in_position_order, -> { order(:position) }` — meaningful only on a single list (`Type.roots`, or one family's `children`), never on the whole table
2. Add the order assertions from point 3 above
3. Sweep the ~124 `Type.<method>` call sites and the three associations (`Project`, `WorkPackageCustomField`, `ProjectCustomField`), leaving `exists?`, `count` and `pluck`\-only sites unordered
4. Delete the `default_scope` last
Note that deleting the line alone does not restore alphabetical order — it restores **arbitrary** order, since Postgres guarantees nothing without `ORDER BY`. Surfaces that would silently become non-deterministic include the work package type picker on create and edit, `/api/v3/types`, form configuration, admin type lists, and seeders. No current spec would catch that, because type specs generally assert membership rather than order.
Out of scope: changing what the orders _are_. This preserves today's intended ordering and only makes it explicit.
### Logs
No errors in the browser console or in `production.log`. The queries succeed — they just order by the wrong column.
### Screenshots and other files
Not applicable: reproducible in a Rails console, output quoted above.
### Environment information
**OpenProject installation type**
* Docker-compose installation (development environment)
**OpenProject version**
v17.8.0 (`dev`, commit `d718e0fd2`)
**Browser**
Not applicable — server-side query ordering, identical in every browser.
**Operating System**
* Linux (Debian, Docker development environment)
**Language**
Not applicable — the defect is not locale-dependent.
Found on `dev` at v17.8.0 while implementing FND-169. No existing report found for `Type`'s `default_scope`.
### Steps to reproduce
Server-side ordering defect — reproduces in a Rails console, no UI needed:
1. Use any instance with seeded work package types
2. Open a Rails console
3. Run `Type.roots.order(:name).pluck(:name)`
4. Compare with `Type.roots.reorder(:name).pluck(:name)`
To see it surface in the UI, with the `type_variants` feature flag enabled:
1. Log in as an administrator
2. Go to Administration → Work packages → Types, and add a variant under two different root types
3. Open any type → any configuration tab → "Switch to linked mode" (or "Change source" if already linked)
4. Look at the order of the source list in the dialog
### What is the buggy behavior?
* `**.order(...)**` **on** `**Type**` **is silently ignored.** `app/models/type.rb:97` declares `default_scope { order("position ASC") }`, so a caller's ordering is appended as a tiebreaker that never fires:
```text
Type.order(:name).to_sql → ORDER BY position ASC, "types"."name" ASC
Type.roots.order(:name) → ["Task", "Milestone", "Summary task", "Feature", "Epic"]
Type.roots.reorder(:name) → ["All CFS", "Another type", "Bug", "Epic", "Feature"]
```
* **Ordering the whole** `**types**` **table by** `**position**` **is meaningless once variants exist.** `acts_as_list scope: :parent_id` (`app/models/type.rb:82`) numbers each family independently, so several variants legitimately hold `position = 1` and interleave between unrelated root types. On a seeded instance the linked-mode source list comes out as:
```text
Bug, Task, Smoke consumer, Smoke source, Task, Task, Smoke consumer, Milestone, Summary task, Feature
```
* **This is a regression, not long-standing debt.** Before variants, `app/models/type.rb` declared a plain `acts_as_list` — one single list covering the whole table — and the global position ordering was correct.
* **Two call sites were written expecting** `**.order(:name)**` **to work, and it never did:** the FND-169 add-type dialog (fixed there by never ordering across lists) and `app/forms/work_package_types/configuration_links/source_form.rb:67` (still open, separate WP).
`acts_as_list` is behaving correctly and cannot help here. Its `scope:` option partitions the lists properly — `first?`, `move_higher` and `higher_item` all respect the partition, and several records legitimately share `position = 1`, one per family. What the gem deliberately does not provide is a total order _across_ lists, because no such order is defined. It also never adds a `default_scope` of its own and defends against the host's, using `reorder(...)` internally throughout.
### What is the expected behavior?
1. `Type` has no `default_scope`, and a caller's `.order(...)` takes effect
2. Every read whose order is user-visible or API-visible orders explicitly. Sites listing whole families read one `acts_as_list` list at a time — roots in position order, then each family's members — instead of ordering the flat table
3. Specs assert ordering at the user-visible boundaries: the API type collection, the work package type picker, and the admin type list
Suggested sequence. The safety net has to exist **before** the removal, or the sweep cannot be verified:
1. Add `scope :in_position_order, -> { order(:position) }` — meaningful only on a single list (`Type.roots`, or one family's `children`), never on the whole table
2. Add the order assertions from point 3 above
3. Sweep the ~124 `Type.<method>` call sites and the three associations (`Project`, `WorkPackageCustomField`, `ProjectCustomField`), leaving `exists?`, `count` and `pluck`\-only sites unordered
4. Delete the `default_scope` last
Note that deleting the line alone does not restore alphabetical order — it restores **arbitrary** order, since Postgres guarantees nothing without `ORDER BY`. Surfaces that would silently become non-deterministic include the work package type picker on create and edit, `/api/v3/types`, form configuration, admin type lists, and seeders. No current spec would catch that, because type specs generally assert membership rather than order.
Out of scope: changing what the orders _are_. This preserves today's intended ordering and only makes it explicit.
### Logs
No errors in the browser console or in `production.log`. The queries succeed — they just order by the wrong column.
### Screenshots and other files
Not applicable: reproducible in a Rails console, output quoted above.
### Environment information
**OpenProject installation type**
* Docker-compose installation (development environment)
**OpenProject version**
v17.8.0 (`dev`, commit `d718e0fd2`)
**Browser**
Not applicable — server-side query ordering, identical in every browser.
**Operating System**
* Linux (Debian, Docker development environment)
**Language**
Not applicable — the defect is not locale-dependent.