-
Notifications
You must be signed in to change notification settings - Fork 22
Add drag-and-drop positioning for categories scoped by category type #712
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
2433294
d2621e5
6868f51
f48878c
fc75a3b
bf30088
84414bc
dacfd50
ebe566a
6f25c55
acc05c8
2db8644
567036f
7bbfe6d
5cb1fbe
26ad452
3b9e531
45de6eb
03ef134
d2fd70f
b5aef7c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -43,11 +43,17 @@ def create | |
| end | ||
|
|
||
| def update | ||
| if @category.update(category_params) | ||
| redirect_to categories_path, notice: "Category was successfully updated.", status: :see_other | ||
| else | ||
| set_form_variables | ||
| render :edit, status: :unprocessable_content | ||
| respond_to do |format| | ||
| if @category.update(category_params) | ||
| format.html { redirect_to categories_path, notice: "Category was successfully updated.", status: :see_other } | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. added formats |
||
| format.json { head :ok } | ||
| else | ||
| format.html do | ||
| set_form_variables | ||
| render :edit, status: :unprocessable_entity | ||
| end | ||
| format.json { render json: { errors: @category.errors }, status: :unprocessable_entity } | ||
| end | ||
| end | ||
| end | ||
|
|
||
|
|
@@ -69,8 +75,12 @@ def set_category | |
|
|
||
| # Strong parameters | ||
| def category_params | ||
| params.require(:category).permit( | ||
| :name, :category_type_id, :metadatum_id, :published, :position | ||
| ) | ||
| if params[:category] | ||
| params.require(:category).permit( | ||
| :name, :category_type_id, :metadatum_id, :published, :position | ||
| ) | ||
| else | ||
| params.permit(:position) | ||
| end | ||
| end | ||
| end | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -35,7 +35,7 @@ export default class extends Controller { | |
| const id = item.dataset.sortableId; | ||
| 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 | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thank you!!! |
||
| }); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,16 +1,25 @@ | ||
| class Category < ApplicationRecord | ||
| include NameFilterable | ||
| include Positioning | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @jmilljr24 did i need to do this?
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think so. I don't see any reference in the doc's of having to include anything. just the basic |
||
|
|
||
| positioned on: :metadatum_id | ||
|
|
||
| belongs_to :category_type, class_name: "CategoryType", foreign_key: :metadatum_id | ||
| # belongs_to :category_type, class_name: "Metadatum", foreign_key: :metadatum_id | ||
| has_many :categorizable_items, dependent: :destroy | ||
| has_many :workshops, through: :categorizable_items, source: :categorizable, source_type: "Workshop" | ||
|
|
||
| scope :age_ranges, -> { joins(:category_type).where("metadata.name = 'AgeRange'") } | ||
| scope :published, -> { where(published: true) } | ||
| scope :ordered_by_position_and_name, -> { reorder(position: :asc, name: :asc) } | ||
|
|
||
| # Validations | ||
| validates :name, presence: true, uniqueness: { case_sensitive: false } | ||
| validates :position, numericality: { only_integer: true, allow_nil: true } | ||
| validates :position, numericality: { | ||
| only_integer: true, | ||
| greater_than: 0, | ||
| allow_nil: true # position gem handles assigning after validations so it needs to allow nil | ||
| } | ||
|
|
||
| # Cache expiration | ||
| after_save :expire_categories_cache | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| class ChangePositionDefaultToNil < ActiveRecord::Migration[8.1] | ||
| def change | ||
| change_column_default :categories, :position, from: 0, to: nil | ||
| end | ||
| end |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| class AddPositionIndexToCategory < ActiveRecord::Migration[8.1] | ||
| def change | ||
| Category.heal_position_column!(name: :desc) | ||
| add_index :categories, [ :metadatum_id, :position ], unique: true | ||
| end | ||
| end |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -10,7 +10,7 @@ | |
| # | ||
| # It's strongly recommended that you check this file into your version control system. | ||
|
|
||
| ActiveRecord::Schema[8.1].define(version: 2026_01_20_212705) do | ||
| ActiveRecord::Schema[8.1].define(version: 2026_01_25_175620) do | ||
| create_table "action_text_mentions", charset: "utf8mb4", collation: "utf8mb4_unicode_ci", force: :cascade do |t| | ||
| t.bigint "action_text_rich_text_id", null: false | ||
| t.datetime "created_at", null: false | ||
|
|
@@ -129,17 +129,17 @@ | |
| t.string "country" | ||
| t.string "device_type" | ||
| t.string "ip" | ||
| t.text "landing_page", size: :medium | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. i thought we'd gotten out of this back and forth :(
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. from production
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The change you pushed seemed to at least fix the charset and collation changes in my db, so progress! |
||
| t.text "landing_page" | ||
| t.float "latitude" | ||
| t.float "longitude" | ||
| t.string "os" | ||
| t.string "os_version" | ||
| t.string "platform" | ||
| t.text "referrer", size: :medium | ||
| t.text "referrer" | ||
| t.string "referring_domain" | ||
| t.string "region" | ||
| t.datetime "started_at" | ||
| t.text "user_agent", size: :medium | ||
| t.text "user_agent" | ||
| t.bigint "user_id" | ||
| t.string "utm_campaign" | ||
| t.string "utm_content" | ||
|
|
@@ -217,9 +217,10 @@ | |
| t.integer "legacy_id" | ||
| t.integer "metadatum_id" | ||
| t.string "name" | ||
| t.integer "position", default: 10, null: false | ||
| t.integer "position", null: false | ||
| t.boolean "published", default: false | ||
| t.datetime "updated_at", precision: nil, null: false | ||
| t.index ["metadatum_id", "position"], name: "index_categories_on_metadatum_id_and_position", unique: true | ||
| t.index ["metadatum_id"], name: "index_categories_on_metadatum_id" | ||
| end | ||
|
|
||
|
|
@@ -331,7 +332,7 @@ | |
| t.string "last_name", null: false | ||
| t.string "linked_in_url" | ||
| t.date "member_since" | ||
| t.text "notes", size: :medium | ||
| t.text "notes" | ||
| t.boolean "profile_is_searchable", default: true, null: false | ||
| t.boolean "profile_show_affiliations", default: true, null: false | ||
| t.boolean "profile_show_bio", default: true, null: false | ||
|
|
@@ -484,9 +485,9 @@ | |
| create_table "notifications", id: :integer, charset: "utf8mb4", collation: "utf8mb4_unicode_ci", force: :cascade do |t| | ||
| t.datetime "created_at", precision: nil, null: false | ||
| t.datetime "delivered_at" | ||
| t.text "email_body_html", size: :medium | ||
| t.text "email_body_text", size: :medium | ||
| t.text "email_subject", size: :medium | ||
| t.text "email_body_html" | ||
| t.text "email_body_text" | ||
| t.text "email_subject" | ||
| t.string "kind", null: false | ||
| t.integer "noticeable_id" | ||
| t.string "noticeable_type" | ||
|
|
@@ -742,7 +743,7 @@ | |
| end | ||
|
|
||
| create_table "tutorials", charset: "utf8mb4", collation: "utf8mb4_unicode_ci", force: :cascade do |t| | ||
| t.text "body", size: :medium | ||
| t.text "body" | ||
| t.datetime "created_at", null: false | ||
| t.boolean "featured", default: false, null: false | ||
| t.integer "position", default: 10, null: false | ||
|
|
@@ -1072,7 +1073,6 @@ | |
| end | ||
|
|
||
| add_foreign_key "action_text_mentions", "action_text_rich_texts" | ||
| add_foreign_key "active_storage_attachments", "active_storage_blobs", column: "blob_id" | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @jmilljr24 is this odd to be removed from schema?
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, not sure why thats happening. Looking at the |
||
| add_foreign_key "active_storage_variant_records", "active_storage_blobs", column: "blob_id" | ||
| add_foreign_key "age_ranges", "windows_types" | ||
| add_foreign_key "banners", "users", column: "created_by_id" | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -49,17 +49,54 @@ | |
| let!(:category_type) { create(:category_type) } | ||
|
|
||
| it "orders categories by position first, then by name" do | ||
| cat_c_pos_30 = create(:category, name: "C Category", position: 30, category_type: category_type) | ||
| cat_a_pos_1 = create(:category, name: "A Category", position: 1, category_type: category_type) | ||
| cat_b_pos_1 = create(:category, name: "B Category", position: 1, category_type: category_type) | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. don't need two 1's anymore, since that's now invalid |
||
| cat_b_pos_1 = create(:category, name: "B Category", position: 2, category_type: category_type) | ||
| cat_d_pos_20 = create(:category, name: "D Category", position: 20, category_type: category_type) | ||
| cat_c_pos_30 = create(:category, name: "C Category", position: 30, category_type: category_type) | ||
|
|
||
| cat_a_pos_1.update_columns(position: 1) | ||
| cat_b_pos_1.update_columns(position: 2) | ||
| cat_d_pos_20.update_columns(position: 20) | ||
| cat_c_pos_30.update_columns(position: 30) | ||
|
|
||
| # Using COALESCE to place NULL values last | ||
| categories = Category.where(category_type: category_type) | ||
| .order(Arel.sql("categories.position, categories.name")) | ||
| .ordered_by_position_and_name | ||
|
|
||
| # The order should be: position 1 in name order (A, B), then position 2 (C), then nil (D) | ||
| expect(categories.to_a).to eq([ cat_a_pos_1, cat_b_pos_1, cat_d_pos_20, cat_c_pos_30 ]) | ||
| end | ||
| end | ||
|
|
||
| describe "positioning" do | ||
| let!(:category_type1) { create(:category_type, name: "Type 1") } | ||
| let!(:category_type2) { create(:category_type, name: "Type 2") } | ||
|
|
||
| it "maintains separate position sequences for different category types" do | ||
| cat1_type1 = create(:category, name: "Cat1 Type1", category_type: category_type1, position: 1) | ||
| cat2_type1 = create(:category, name: "Cat2 Type1", category_type: category_type1, position: 2) | ||
| cat1_type2 = create(:category, name: "Cat1 Type2", category_type: category_type2, position: 1) | ||
| cat2_type2 = create(:category, name: "Cat2 Type2", category_type: category_type2, position: 2) | ||
|
|
||
| expect(cat1_type1.position).to eq(1) | ||
| expect(cat2_type1.position).to eq(2) | ||
| expect(cat1_type2.position).to eq(1) | ||
| expect(cat2_type2.position).to eq(2) | ||
| end | ||
|
|
||
| it "allows updating position within the same category type scope" do | ||
| cat1 = create(:category, name: "First", category_type: category_type1, position: 1) | ||
| cat2 = create(:category, name: "Second", category_type: category_type1, position: 2) | ||
| cat3 = create(:category, name: "Third", category_type: category_type1, position: 3) | ||
|
|
||
| # Update cat3 to position 1 - the positioning gem should handle reordering | ||
| cat3.update(position: 1) | ||
| # Reload to get updated positions from the database | ||
| cat1.reload | ||
| cat2.reload | ||
| cat3.reload | ||
|
|
||
| # cat3 should now be at position 1 | ||
| expect(cat3.position).to eq(1) | ||
| end | ||
| end | ||
| end | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this was blowing up after upgrading ruby