Skip to content

Commit

Permalink
Introduce Advisory Lock
Browse files Browse the repository at this point in the history
- Remove column_default method (inline)
- Shift handle_ranking call to `ranks` method
  • Loading branch information
brendon committed Mar 18, 2024
1 parent a0eb31c commit 5b4593e
Show file tree
Hide file tree
Showing 2 changed files with 97 additions and 12 deletions.
27 changes: 15 additions & 12 deletions lib/ranked-model.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
require File.dirname(__FILE__)+'/ranked-model/ranker'
require File.dirname(__FILE__)+'/ranked-model/advisory_lock'
require File.dirname(__FILE__)+'/ranked-model/railtie' if defined?(Rails::Railtie)

module RankedModel
Expand All @@ -17,8 +18,6 @@ def self.included base

extend RankedModel::ClassMethods

before_save :handle_ranking

scope :rank, lambda { |name|
reorder ranker(name.to_sym).column
}
Expand All @@ -28,12 +27,6 @@ def self.included base

private

def handle_ranking
self.class.rankers.each do |ranker|
ranker.with(self).handle_ranking
end
end

module ClassMethods

def ranker name
Expand All @@ -48,7 +41,7 @@ def ranks *args
self.rankers ||= []
ranker = RankedModel::Ranker.new(*args)

if column_default(ranker)
if ActiveRecord::Base.connected? && table_exists? && column_defaults[ranker.name.to_s]
raise NonNilColumnDefault, %Q{Your ranked model column "#{ranker.name}" must not have a default value in the database.}
end

Expand All @@ -66,10 +59,20 @@ def ranks *args
end

public "#{ranker.name}_position", "#{ranker.name}_position="
end

def column_default ranker
column_defaults[ranker.name.to_s] if ActiveRecord::Base.connected? && table_exists?
if ActiveRecord::Base.connected? && table_exists?
advisory_lock = AdvisoryLock.new(base_class, ranker.column)
before_create advisory_lock
before_update advisory_lock
before_destroy advisory_lock
end

before_save { ranker.with(self).handle_ranking }

if ActiveRecord::Base.connected? && table_exists? && local_variables.include?(:advisory_lock)
after_commit advisory_lock
after_rollback advisory_lock
end
end

end
Expand Down
82 changes: 82 additions & 0 deletions lib/ranked-model/advisory_lock.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
require "fileutils"
require "openssl"

module RankedModel
class AdvisoryLock
Adapter = Struct.new(:initialise, :aquire, :release, keyword_init: true)

attr_reader :base_class

def initialize(base_class, column)
@base_class = base_class
@column = column.to_s

@adapters = {
"Mysql2" => Adapter.new(
initialise: -> {},
aquire: -> { connection.execute "SELECT GET_LOCK(#{connection.quote(lock_name)}, -1)" },
release: -> { connection.execute "SELECT RELEASE_LOCK(#{connection.quote(lock_name)})" }
),
"PostgreSQL" => Adapter.new(
initialise: -> {},
aquire: -> { connection.execute "SELECT pg_advisory_lock(#{lock_name.hex & 0x7FFFFFFFFFFFFFFF})" },
release: -> { connection.execute "SELECT pg_advisory_unlock(#{lock_name.hex & 0x7FFFFFFFFFFFFFFF})" }
),
"SQLite" => Adapter.new(
initialise: -> {
FileUtils.mkdir_p "#{Dir.pwd}/tmp"
filename = "#{Dir.pwd}/tmp/#{lock_name}.lock"
@file ||= File.open filename, File::RDWR | File::CREAT, 0o644
},
aquire: -> {
@file.flock File::LOCK_EX
},
release: -> {
@file.flock File::LOCK_UN
}
)
}

@adapters.default = Adapter.new(initialise: -> {}, aquire: -> {}, release: -> {})

adapter.initialise.call
end

def aquire(record)
adapter.aquire.call
end

def release(record)
adapter.release.call
end

alias_method :before_create, :aquire
alias_method :before_update, :aquire
alias_method :before_destroy, :aquire
alias_method :after_commit, :release
alias_method :after_rollback, :release

private

def connection
base_class.connection
end

def adapter_name
connection.adapter_name
end

def adapter
@adapters[adapter_name]
end

def lock_name
lock_name = ["ranked-model"]
lock_name << connection.current_database if connection.respond_to?(:current_database)
lock_name << base_class.table_name
lock_name << @column

OpenSSL::Digest::MD5.hexdigest(lock_name.join("."))[0...32]
end
end
end

0 comments on commit 5b4593e

Please sign in to comment.