rails - implementing a simple lock to prevent user

2019-03-11 18:56发布

问题:

I have an app where I need to prevent users from editing data while it is being edited by a different user. I'm trying to think of the best way to do it and wanted to ask for ideas. So far, I've created a settings model that stores application wide configuration on the db in key/value pairs. So, for the lock, I have a settings instance that's called LOCKED_TABLE_UID, and it stored the user_id of the user editing the table or null (nil) if the table is free.

>> lock = Setting.find_by_key('LOCKED_TABLE_UID')

Then, I implemented 2 methods in my application controller to acquire and release the lock:

# 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

What I want is to create some kind of block code that contains the locking mechanism, something like this:

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

I'd appreciate help on this - but I'm also open to other suggestions on how to accomplish all that, or some things I may have overlooked. This lock doesn't have to be atomic, but fairly basic and most important - that it works :) thanks.

回答1:

You're almost there. Create your require_locking? action as you see fit. Then process it with a 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


回答2:

Have you seen the ActiveRecord built-in locking feature?

  • Optimistic Locking
  • Pessimistic Locking


回答3:

I like the idea but see a big problem with your solution, and it is that you are getting and releasing locks of entire tables.

For a very small app that could be fine, but imagine if you have thousands of users trying to access to, say, the 'PRODUCTS' table and having to wait because someone is editing an entry totally unrelated to their own products.

Maybe you could have a finer grain approach and lock particular rows instead of tables. Then the lock would include the table name, row ID and user ID.



回答4:

I think acts_as_lockable_by gem is exactly doing what you asked for in simpler terms and less code. It's easily integrable with rails or even a bare ruby project.

With this gem, you get atomic lock, unlock and renew_lock methods. Also, you get an auto expiring ttl locks, so if shit hits the fan and you could not unlock the resource it will be automatically unlocked for you!