mirror of
https://github.com/scinote-eln/scinote-web.git
synced 2025-10-07 20:40:26 +08:00
Add unique database index to checklist items position (#5886)
* Add unique database index to checklist items position and update errors to render flash message when a checklist item validation fails [SCI-8841] * Improve protocol checklist item reordering [SCI-8841] * Add act as list to gemfile and use sequential updates for reodering with act_as_list [SCI-8841]
This commit is contained in:
parent
382a4e57ac
commit
31f8bc19d5
7 changed files with 51 additions and 40 deletions
1
Gemfile
1
Gemfile
|
@ -54,6 +54,7 @@ gem 'js_cookie_rails' # Simple JS API for cookies
|
||||||
gem 'spinjs-rails'
|
gem 'spinjs-rails'
|
||||||
|
|
||||||
gem 'activerecord-import'
|
gem 'activerecord-import'
|
||||||
|
gem 'acts_as_list'
|
||||||
gem 'ajax-datatables-rails', '~> 0.3.1'
|
gem 'ajax-datatables-rails', '~> 0.3.1'
|
||||||
gem 'aspector' # Aspect-oriented programming for Rails
|
gem 'aspector' # Aspect-oriented programming for Rails
|
||||||
gem 'auto_strip_attributes', '~> 2.1' # Removes unnecessary whitespaces AR
|
gem 'auto_strip_attributes', '~> 2.1' # Removes unnecessary whitespaces AR
|
||||||
|
|
|
@ -132,6 +132,8 @@ GEM
|
||||||
i18n (>= 1.6, < 2)
|
i18n (>= 1.6, < 2)
|
||||||
minitest (>= 5.1)
|
minitest (>= 5.1)
|
||||||
tzinfo (~> 2.0)
|
tzinfo (~> 2.0)
|
||||||
|
acts_as_list (1.1.0)
|
||||||
|
activerecord (>= 4.2)
|
||||||
addressable (2.8.4)
|
addressable (2.8.4)
|
||||||
public_suffix (>= 2.0.2, < 6.0)
|
public_suffix (>= 2.0.2, < 6.0)
|
||||||
aes_key_wrap (1.1.0)
|
aes_key_wrap (1.1.0)
|
||||||
|
@ -707,6 +709,7 @@ PLATFORMS
|
||||||
DEPENDENCIES
|
DEPENDENCIES
|
||||||
active_model_serializers (~> 0.10.7)
|
active_model_serializers (~> 0.10.7)
|
||||||
activerecord-import
|
activerecord-import
|
||||||
|
acts_as_list
|
||||||
ajax-datatables-rails (~> 0.3.1)
|
ajax-datatables-rails (~> 0.3.1)
|
||||||
aspector
|
aspector
|
||||||
auto_strip_attributes (~> 2.1)
|
auto_strip_attributes (~> 2.1)
|
||||||
|
|
|
@ -27,15 +27,13 @@ module StepElements
|
||||||
|
|
||||||
render json: checklist_item, serializer: ChecklistItemSerializer, user: current_user
|
render json: checklist_item, serializer: ChecklistItemSerializer, user: current_user
|
||||||
rescue ActiveRecord::RecordInvalid
|
rescue ActiveRecord::RecordInvalid
|
||||||
render json: checklist_item, serializer: ChecklistItemSerializer,
|
render json: { errors: checklist_item.errors }, status: :unprocessable_entity
|
||||||
user: current_user,
|
|
||||||
status: :unprocessable_entity
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def update
|
def update
|
||||||
old_text = @checklist_item.text
|
old_text = @checklist_item.text
|
||||||
@checklist_item.assign_attributes(
|
@checklist_item.assign_attributes(
|
||||||
checklist_item_params.merge(last_modified_by: current_user)
|
checklist_item_params.except(:position, :id).merge(last_modified_by: current_user)
|
||||||
)
|
)
|
||||||
|
|
||||||
if @checklist_item.save!
|
if @checklist_item.save!
|
||||||
|
@ -49,9 +47,7 @@ module StepElements
|
||||||
|
|
||||||
render json: @checklist_item, serializer: ChecklistItemSerializer, user: current_user
|
render json: @checklist_item, serializer: ChecklistItemSerializer, user: current_user
|
||||||
rescue ActiveRecord::RecordInvalid
|
rescue ActiveRecord::RecordInvalid
|
||||||
render json: @checklist_item, serializer: ChecklistItemSerializer,
|
render json: { errors: @checklist_item.errors }, status: :unprocessable_entity
|
||||||
user: current_user,
|
|
||||||
status: :unprocessable_entity
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def toggle
|
def toggle
|
||||||
|
@ -97,14 +93,14 @@ module StepElements
|
||||||
end
|
end
|
||||||
|
|
||||||
def reorder
|
def reorder
|
||||||
@checklist.with_lock do
|
checklist_item = @checklist.checklist_items.find(checklist_item_params[:id])
|
||||||
params[:checklist_item_positions].each do |id, position|
|
ActiveRecord::Base.transaction do
|
||||||
@checklist.checklist_items.find(id).update_column(:position, position)
|
checklist_item.insert_at(checklist_item_params[:position])
|
||||||
end
|
|
||||||
@checklist.touch
|
@checklist.touch
|
||||||
end
|
end
|
||||||
|
|
||||||
render json: params[:checklist_item_positions], status: :ok
|
render json: params[:checklist_item_positions], status: :ok
|
||||||
|
rescue ActiveRecord::RecordInvalid
|
||||||
|
render json: { errors: checklist_item.errors }, status: :conflict
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
@ -122,7 +118,7 @@ module StepElements
|
||||||
end
|
end
|
||||||
|
|
||||||
def checklist_item_params
|
def checklist_item_params
|
||||||
params.require(:attributes).permit(:text, :position)
|
params.require(:attributes).permit(:text, :position, :id)
|
||||||
end
|
end
|
||||||
|
|
||||||
def checklist_toggle_item_params
|
def checklist_toggle_item_params
|
||||||
|
|
|
@ -172,8 +172,8 @@
|
||||||
);
|
);
|
||||||
|
|
||||||
if(callback) callback();
|
if(callback) callback();
|
||||||
}).error(() => {
|
}).error((xhr) => {
|
||||||
HelperModule.flashAlertMsg(this.i18n.t('errors.general'), 'danger');
|
this.setFlashErrors(xhr.responseJSON.errors)
|
||||||
});
|
});
|
||||||
|
|
||||||
this.update();
|
this.update();
|
||||||
|
@ -190,7 +190,7 @@
|
||||||
updatedItem.attributes.id = item.attributes.id
|
updatedItem.attributes.id = item.attributes.id
|
||||||
this.$set(this.checklistItems, item.attributes.position, updatedItem)
|
this.$set(this.checklistItems, item.attributes.position, updatedItem)
|
||||||
},
|
},
|
||||||
error: () => HelperModule.flashAlertMsg(this.i18n.t('errors.general'), 'danger')
|
error: (xhr) => setFlashErrors(xhr.responseJSON.errors)
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
// create item, then append next one
|
// create item, then append next one
|
||||||
|
@ -231,26 +231,26 @@
|
||||||
startReorder() {
|
startReorder() {
|
||||||
this.reordering = true;
|
this.reordering = true;
|
||||||
},
|
},
|
||||||
endReorder() {
|
endReorder(event) {
|
||||||
this.reordering = false;
|
this.reordering = false;
|
||||||
this.saveItemOrder();
|
if(
|
||||||
|
Number.isInteger(event.newIndex)
|
||||||
|
&& Number.isInteger(event.newIndex)
|
||||||
|
&& event.newIndex !== event.oldIndex
|
||||||
|
){
|
||||||
|
const { id, position } = this.orderedChecklistItems[event.newIndex]?.attributes
|
||||||
|
this.saveItemOrder(id, position);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
saveItemOrder() {
|
saveItemOrder(id, position) {
|
||||||
let checklistItemPositions =
|
|
||||||
{
|
|
||||||
checklist_item_positions: this.orderedChecklistItems.map(
|
|
||||||
(i) => [i.attributes.id, i.attributes.position]
|
|
||||||
)
|
|
||||||
};
|
|
||||||
|
|
||||||
$.ajax({
|
$.ajax({
|
||||||
type: "POST",
|
type: "POST",
|
||||||
url: this.element.attributes.orderable.urls.reorder_url,
|
url: this.element.attributes.orderable.urls.reorder_url,
|
||||||
data: JSON.stringify(checklistItemPositions),
|
data: JSON.stringify({ attributes: { id, position } }),
|
||||||
contentType: "application/json",
|
contentType: "application/json",
|
||||||
dataType: "json",
|
dataType: "json",
|
||||||
error: (() => HelperModule.flashAlertMsg(this.i18n.t('errors.general'), 'danger')),
|
error: (xhr) => this.setFlashErrors(xhr.responseJSON.errors),
|
||||||
success: (() => this.update())
|
success: () => this.update()
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
handleMultilinePaste(data) {
|
handleMultilinePaste(data) {
|
||||||
|
@ -274,6 +274,14 @@
|
||||||
};
|
};
|
||||||
|
|
||||||
synchronousPost(0);
|
synchronousPost(0);
|
||||||
|
},
|
||||||
|
setFlashErrors(errors) {
|
||||||
|
for(const key in errors){
|
||||||
|
HelperModule.flashAlertMsg(
|
||||||
|
this.i18n.t(`activerecord.errors.models.checklist_item.attributes.${key}`),
|
||||||
|
'danger'
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,10 +5,11 @@ class ChecklistItem < ApplicationRecord
|
||||||
length: { maximum: Constants::TEXT_MAX_LENGTH }
|
length: { maximum: Constants::TEXT_MAX_LENGTH }
|
||||||
validates :checklist, presence: true
|
validates :checklist, presence: true
|
||||||
validates :checked, inclusion: { in: [true, false] }
|
validates :checked, inclusion: { in: [true, false] }
|
||||||
validates :position, uniqueness: { scope: :checklist }, unless: -> { position.nil? }
|
validates :position, uniqueness: { scope: :checklist }
|
||||||
|
|
||||||
belongs_to :checklist,
|
belongs_to :checklist,
|
||||||
inverse_of: :checklist_items
|
inverse_of: :checklist_items
|
||||||
|
acts_as_list scope: :checklist, top_of_list: 0, sequential_updates: true
|
||||||
belongs_to :created_by,
|
belongs_to :created_by,
|
||||||
foreign_key: 'created_by_id',
|
foreign_key: 'created_by_id',
|
||||||
class_name: 'User',
|
class_name: 'User',
|
||||||
|
@ -18,8 +19,6 @@ class ChecklistItem < ApplicationRecord
|
||||||
class_name: 'User',
|
class_name: 'User',
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
after_destroy :update_positions
|
|
||||||
|
|
||||||
# conditional touch excluding checked updates
|
# conditional touch excluding checked updates
|
||||||
after_destroy :touch_checklist
|
after_destroy :touch_checklist
|
||||||
after_save :touch_checklist
|
after_save :touch_checklist
|
||||||
|
@ -35,12 +34,4 @@ class ChecklistItem < ApplicationRecord
|
||||||
checklist.touch
|
checklist.touch
|
||||||
# rubocop:enable Rails/SkipsModelValidations
|
# rubocop:enable Rails/SkipsModelValidations
|
||||||
end
|
end
|
||||||
|
|
||||||
def update_positions
|
|
||||||
transaction do
|
|
||||||
checklist.checklist_items.order(position: :asc).each_with_index do |checklist_item, i|
|
|
||||||
checklist_item.update!(position: i)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
|
@ -232,6 +232,10 @@ en:
|
||||||
attributes:
|
attributes:
|
||||||
output_id:
|
output_id:
|
||||||
creates_cycle: "mustn't create cycle"
|
creates_cycle: "mustn't create cycle"
|
||||||
|
checklist_item:
|
||||||
|
attributes:
|
||||||
|
text: Text is too long
|
||||||
|
position: "Position has already been taken by another item in the checklist"
|
||||||
errors:
|
errors:
|
||||||
general: "Something went wrong."
|
general: "Something went wrong."
|
||||||
general_text_too_long: 'Text is too long'
|
general_text_too_long: 'Text is too long'
|
||||||
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class
|
||||||
|
AddIndexToChecklistItems < ActiveRecord::Migration[7.0]
|
||||||
|
def change
|
||||||
|
add_index :checklist_items, %i(checklist_id position), unique: true
|
||||||
|
end
|
||||||
|
end
|
Loading…
Add table
Reference in a new issue