implemented partial batch update for exercises

This commit is contained in:
Hauke Klement
2015-03-12 11:05:11 +01:00
parent f1de18141a
commit 6ee0b6bf81
9 changed files with 105 additions and 4 deletions

View File

@ -15,11 +15,45 @@ $(function() {
$('body, html').scrollTo('#add-file');
};
var ajaxError = function(response) {
$.flash.danger({
text: $('#flash').data('message-failure')
});
};
var buildCheckboxes = function() {
$('tbody tr').each(function(index, element) {
var td = $('td.public', element);
var checkbox = $('<input>', {
checked: td.data('value'),
type: 'checkbox'
});
td.on('click', function(event) {
event.preventDefault();
checkbox.prop('checked', !checkbox.prop('checked'));
});
td.html(checkbox);
});
};
var discardFile = function(event) {
event.preventDefault();
$(this).parents('li').remove();
};
var enableBatchUpdate = function() {
$('thead .batch a').on('click', function(event) {
event.preventDefault();
if (!$(this).data('toggled')) {
$(this).data('toggled', true);
$(this).text($(this).data('text'));
buildCheckboxes();
} else {
performBatchUpdate();
}
});
};
var enableInlineFileCreation = function() {
$('#add-file').on('click', addFileForm);
$('#files').on('click', 'li .discard-file', discardFile);
@ -84,6 +118,23 @@ $(function() {
});
};
var performBatchUpdate = function() {
var jqxhr = $.ajax({
data: {
exercises: _.map($('tbody tr'), function(element) {
return {
id: $(element).data('id'),
public: $('.public input', element).prop('checked')
};
})
},
dataType: 'json',
method: 'PUT'
});
jqxhr.done(window.CodeOcean.refresh);
jqxhr.fail(ajaxError);
};
var toggleCodeHeight = function() {
$('code').on('click', function() {
$(this).css({
@ -93,7 +144,9 @@ $(function() {
};
if ($.isController('exercises')) {
if ($('.edit_exercise, .new_exercise').isPresent()) {
if ($('table').isPresent()) {
enableBatchUpdate();
} else if ($('.edit_exercise, .new_exercise').isPresent()) {
execution_environments = $('form').data('execution-environments');
file_types = $('form').data('file-types');
new MarkdownEditor('#exercise_instructions');

View File

@ -15,6 +15,17 @@ class ExercisesController < ApplicationController
end
private :authorize!
def batch_update
@exercises = Exercise.all
authorize!
@exercises = params[:exercises].values.map do |exercise_params|
exercise = Exercise.find(exercise_params.delete(:id))
exercise.update(exercise_params)
exercise
end
render(json: {exercises: @exercises})
end
def clone
exercise = @exercise.duplicate(public: false, token: nil, user: current_user)
exercise.send(:generate_token)

View File

@ -4,6 +4,10 @@ class ExercisePolicy < AdminOrAuthorPolicy
end
private :author?
def batch_update?
admin?
end
[:clone?, :destroy?, :edit?, :show?, :statistics?, :update?].each do |action|
define_method(action) { admin? || author? || team_member? }
end

View File

@ -17,17 +17,21 @@ h1 = Exercise.model_name.human(count: 2)
th = sort_link(@search, :execution_environment_id, t('activerecord.attributes.exercise.execution_environment'))
th = t('.test_files')
th = t('activerecord.attributes.exercise.maximum_score')
th = t('activerecord.attributes.exercise.public')
th
= t('activerecord.attributes.exercise.public')
- if policy(Exercise).batch_update?
br
span.batch = link_to(t('shared.batch_update'), '#', 'data-text' => t('shared.update', model: t('activerecord.models.exercise.other')))
th colspan=6 = t('shared.actions')
tbody
- @exercises.each do |exercise|
tr
tr data-id=exercise.id
td = exercise.title
td = link_to(exercise.author, exercise.author)
td = link_to(exercise.execution_environment, exercise.execution_environment)
td = exercise.files.teacher_defined_tests.count
td = exercise.maximum_score
td = symbol_for(exercise.public?)
td.public data-value=exercise.public? = symbol_for(exercise.public?)
td = link_to(t('shared.show'), exercise)
td = link_to(t('shared.edit'), edit_exercise_path(exercise))
td = link_to(t('shared.destroy'), exercise, data: {confirm: t('shared.confirm_destroy')}, method: :delete)

View File

@ -283,6 +283,7 @@ de:
administration: Administration
already_signed_in: Sie sind bereits angemeldet.
apply_filters: Filter anwenden
batch_update: Batch-Update
confirm_destroy: Sind Sie sicher?
create: '%{model} erstellen'
created_at: Erstellt

View File

@ -283,6 +283,7 @@ en:
administration: Administration
already_signed_in: You are already signed in.
apply_filters: Apply filters
batch_update: Batch Update
confirm_destroy: Are you sure?
create: 'Create %{model}'
created_at: Created At

View File

@ -22,6 +22,10 @@ Rails.application.routes.draw do
end
resources :exercises do
collection do
match '', to: 'exercises#batch_update', via: [:patch, :put]
end
member do
post :clone
get :implement

View File

@ -5,6 +5,20 @@ describe ExercisesController do
let(:user) { FactoryGirl.create(:admin) }
before(:each) { allow(controller).to receive(:current_user).and_return(user) }
describe 'PUT #batch_update' do
let(:attributes) { {public: true} }
let(:request) { proc { put :batch_update, exercises: {0 => attributes.merge(id: exercise.id)} } }
before(:each) { request.call }
it 'updates the exercises' do
expect_any_instance_of(Exercise).to receive(:update).with(attributes)
request.call
end
expect_json
expect_status(200)
end
describe 'POST #clone' do
let(:request) { proc { post :clone, id: exercise.id } }

View File

@ -5,6 +5,15 @@ describe ExercisePolicy do
let(:exercise) { FactoryGirl.build(:dummy, team: FactoryGirl.create(:team)) }
permissions :batch_update? do
it 'grants access to admins only' do
expect(subject).to permit(FactoryGirl.build(:admin), exercise)
[:external_user, :teacher].each do |factory_name|
expect(subject).not_to permit(FactoryGirl.build(factory_name), exercise)
end
end
end
[:create?, :index?, :new?].each do |action|
permissions(action) do
it 'grants access to admins' do