Skip to content

Conversation

Copy link
Contributor

Copilot AI commented Jan 19, 2026

What is the goal of this PR and why is this important?

Implements drag-and-drop reordering for categories on the categories index page, matching the existing FAQ implementation. Positions are scoped to metadatum_id (category_type) so categories of different types maintain independent position sequences.

How did you approach the change?

Model (category.rb)

  • Added positioned scope: :metadatum_id to enable position management per category type

Controller (categories_controller.rb)

  • Handle ordering parameter in update action for drag-and-drop position updates
  • Validate ordering is a positive integer (regex check prevents silent conversion of invalid input)
  • Return appropriate HTTP status: 200 (success), 400 (invalid input), 422 (update failed)

View (categories/index.html.erb)

  • Added drag handle column with data-sortable-handle icon
  • Added data-controller="sortable" and data-sortable-url-value to tbody
  • Added data-sortable-id to each row

Tests

  • Request specs for position updates with valid/invalid ordering values
  • Model specs verifying position scoping by metadatum_id
  • Uses factories for consistency

Reuses existing sortable_controller.js Stimulus controller and positioning gem (v0.4.7).

Anything else to add?

Position updates work across paginated results since the positioning gem handles server-side state. Categories can share position values across different category types due to the metadatum_id scope.

Original prompt

Use position gem to be able to drag and drop categories on categories index, and automatically updating position values. (we have an implementation on faq's index)
Positions should be relevant within their category_type(metadatam_id) only.


💬 We'd love your input! Share your thoughts on Copilot coding agent in our 2 minute survey.

Copilot AI changed the title [WIP] Add drag and drop functionality for categories index Add drag-and-drop positioning for categories scoped by category type Jan 19, 2026
Copilot AI requested a review from maebeale January 19, 2026 15:37
@maebeale maebeale marked this pull request as ready for review January 19, 2026 15:49
@maebeale
Copy link
Collaborator

@jmilljr24 lgtm, but you have more context on gem implementation. main thing is scoping the position uniqueness to within the CategoryType (via metadatum_id, until that table gets renamed and it can be category_type_id)

i've got a separate pr to fix the allow_nil in the category model.

@jmilljr24
Copy link
Collaborator

@jmilljr24 lgtm, but you have more context on gem implementation. main thing is scoping the position uniqueness to within the CategoryType (via metadatum_id, until that table gets renamed and it can be category_type_id)

i've got a separate pr to fix the allow_nil in the category model.

Awesome! Yes, scoping is a great feature!!

@jmilljr24
Copy link
Collaborator

Check out the stimulus controller we have sortable_controller.js. That handles all the requests to the controller and a simple
@category.update(category_params) is all you need.

Copy link
Collaborator

@jmilljr24 jmilljr24 left a comment

Choose a reason for hiding this comment

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

@maebeale Love this feature. Can you update the stimulus sortable controller to send the param as position: instead of ordering: I missed it when we changed everything over to the same column name. Faq is broken at the moment.

body: JSON.stringify({ position: newIndex + 1 })

Once merged we can just run Category.heal_position_column! in production to dedup positions and remove the default value.

Comment on lines 47 to 60
if params[:ordering].present?
ordering = params[:ordering]
# Validate ordering is a valid positive integer
if ordering.to_s.match?(/^\d+$/) && (ordering = ordering.to_i) > 0
if @category.update(position: ordering)
head :ok
else
head :unprocessable_entity
end
else
head :bad_request
end
return
end
Copy link
Collaborator

Choose a reason for hiding this comment

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

Shouldn't need this. @category.update(category_params) should do the trick and the position gem will handle validation.

Copy link
Collaborator

@maebeale maebeale Jan 23, 2026

Choose a reason for hiding this comment

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

@jmilljr24 i removed it and the UI stopped working and my tests were failing, so i'm going to keep it for now and figure out how to remove it later.

Copy link
Collaborator

Choose a reason for hiding this comment

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

nvm. i see now it's bc of the stimulus controller sending ordering param instead of position!

@maebeale maebeale force-pushed the copilot/enable-drag-and-drop-categories branch from 7e07d0e to f6ce54f Compare January 23, 2026 14:40
const url = this.urlValue.replace(":id", id);
put(url, {
body: JSON.stringify({ ordering: newIndex + 1 }), // add 1 to change sortablejs 0-based index to 1-based for positioning gem
body: JSON.stringify({ position: newIndex + 1 }), // add 1 to change sortablejs 0-based index to 1-based for positioning gem
Copy link
Collaborator

Choose a reason for hiding this comment

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

Thank you!!!

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.

3 participants