rails - implémentation d'un verrou simple pour empêcher l'utilisateur de modifier simultanément les mêmes données
-
05-07-2019 - |
Question
J'ai une application où je dois empêcher les utilisateurs de modifier des données alors qu'elles sont modifiées par un autre utilisateur. J'essaie de trouver le meilleur moyen de le faire et je voulais demander des idées. Jusqu'à présent, j'ai créé un modèle de paramètres qui stocke la configuration à l'échelle de l'application sur la base de données sous forme de paires clé / valeur. Donc, pour le verrou, j’ai une instance de paramètres appelée LOCKED_TABLE_UID, qui stocke l’id utilisateur de l’utilisateur modifiant la table ou null (nil) si la table est libre.
>> lock = Setting.find_by_key('LOCKED_TABLE_UID')
Ensuite, j'ai implémenté deux méthodes dans mon contrôleur d'application pour acquérir et libérer le verrou:
# current_user returns the user currently logged in
def acquire_lock
lock = Setting.find_by_key("LOCKED_TABLE_UID")
if lock.value
# if lock taken, see if it's the current_user or someone else
if lock.value.to_i == current_user.id.to_i
return true
else
return false
end
else
# lock is free, assign it to this user
lock.value = current_user.id
return true if lock.save
end
end
def release_lock
lock = Setting.find_by_key("LOCKED_TABLE_UID")
if lock.value
# the lock belongs to current_user, so he can release it
if lock.value.to_i == current_user.id.to_i
lock.value = nil
return true if lock.save
else
# not your lock, go away
return false
end
else
# lock is free, quit bugging
return true
end
end
Ce que je veux, c'est créer un code bloc contenant le mécanisme de verrouillage, comme ceci:
def some_crud_action
requires_locking do |lock|
if lock
# do some CRUD stuff here
else
# decline CRUD and give some error
end
end
end
J'apprécierais de l'aide à ce sujet - mais je suis également ouvert à d'autres suggestions sur la façon de réaliser tout cela, ou certaines choses que j'ai peut-être oubliées. Ce verrou ne doit pas nécessairement être atomique, mais assez basique et le plus important - que cela fonctionne :) merci.
La solution
Vous êtes presque là. Créez votre require_locking? action comme bon vous semble. Ensuite, traitez-le avec un before_filter.
before_filter :requires_locking?, :only => [:update, :destroy]
after_filter :release_lock, :only => [:update, :destroy]
def requires_locking do |lock|
unless acquire_lock
lock = Setting.find_by_key("LOCKED_TABLE_UID")
user_with_lock = User.find(lock.value)
flash[:message] = "Action denied: Table locked by: #{user_with_lock.name}"
redirect_to :back
end
end
Autres conseils
Avez-vous vu la fonctionnalité de verrouillage intégrée à ActiveRecord?
J'aime l’idée, mais vous voyez un gros problème avec votre solution, c’est que vous obtenez et libérez des verrous de tables entières.
Pour une très petite application, cela pourrait bien fonctionner, mais imaginons que des milliers d'utilisateurs tentent d'accéder, par exemple, au tableau "PRODUITS" et d'attendre parce que quelqu'un édite une entrée totalement indépendante de leurs propres produits.
Vous pourriez peut-être utiliser une approche plus fine et verrouiller des lignes particulières au lieu de tableaux. Le verrou inclurait alors le nom de la table, l’ID de ligne et l’ID d’utilisateur.
Je pense que la acts_as_lockable_by gem est exactement ce que vous avez demandé en termes plus simples et avec moins de code. Il est facilement intégrable avec des rails ou même un projet Ruby nu.
Avec cette gemme, vous obtenez les méthodes lock
, unlock
et renew_lock
atomiques. De plus, vous obtenez un verrou ttl
à expiration automatique. Par conséquent, si la merde frappe le ventilateur et que vous ne pouvez pas déverrouiller la ressource, elle sera automatiquement déverrouillée pour vous!