feat: add Prism, Authorizable and Cache
This commit is contained in:
1 parent
40225e5791
commit
f8f799f38f
2 files changed
+152
No files matched your search
@@ -0,0 +1,40 @@
|
||||
module Prism
|
||||
# Wires Policy lookups into a Controller, the request-cycle counterpart
|
||||
# to Policy the way Authenticatable is to Orm::SecurePassword:
|
||||
#
|
||||
# class PostsController < ApplicationController
|
||||
# include Prism::Authorizable
|
||||
# rescue_from Prism::Policy::NotAuthorizedError, with: :forbidden
|
||||
#
|
||||
# def destroy
|
||||
# post = Post.find(params['id'])
|
||||
# authorize(post)
|
||||
# post.destroy
|
||||
# ...
|
||||
# end
|
||||
#
|
||||
# private
|
||||
#
|
||||
# def forbidden(_exception)
|
||||
# halt(403, 'not allowed')
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# `authorize` defaults the query to "#{action_name}?" (`destroy?` inside
|
||||
# a `destroy` action), so most call sites don't have to name it.
|
||||
module Authorizable
|
||||
def authorize(record, query = nil)
|
||||
query ||= "#{action_name}?"
|
||||
raise Policy::NotAuthorizedError.new(record, query) unless policy(record).public_send(query)
|
||||
record
|
||||
end
|
||||
|
||||
def policy(record)
|
||||
Policy.for_record(record).new(current_user, record)
|
||||
end
|
||||
|
||||
def policy_scope(scope)
|
||||
Policy.for_record(scope).const_get(:Scope).new(current_user, scope).resolve
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,112 @@
|
||||
module Prism
|
||||
# Fragment/object cache used by view fragment caching (Prism::CacheHelpers)
|
||||
# and available for general application use. Keys are always coerced to
|
||||
# String so callers can pass symbols, arrays (joined by expand_cache_key
|
||||
# in Prism::View) or plain strings interchangeably.
|
||||
module Cache
|
||||
class Entry
|
||||
def initialize(value, expires_at)
|
||||
@value = value
|
||||
@expires_at = expires_at
|
||||
end
|
||||
|
||||
attr_reader :value
|
||||
|
||||
def expired?(now)
|
||||
@expires_at && now >= @expires_at
|
||||
end
|
||||
end
|
||||
|
||||
# In-process store backed by a Hash. Lives for the lifetime of the
|
||||
# Ruby process (matches how Orm::Store keeps everything in memory too);
|
||||
# not shared across worker processes.
|
||||
#
|
||||
# Ruby Hashes preserve insertion order, so this doubles as an LRU
|
||||
# structure when `max_size` is set: every read/write re-inserts the key
|
||||
# (moving it to the end, i.e. most recently used) and writes evict from
|
||||
# the front (least recently used) once the store is over capacity.
|
||||
# `max_size: nil` (the default) keeps the old unbounded behavior.
|
||||
class MemoryStore
|
||||
def initialize(max_size: nil)
|
||||
@data = {}
|
||||
@mutex = Mutex.new
|
||||
@max_size = max_size
|
||||
end
|
||||
|
||||
def read(key)
|
||||
@mutex.synchronize { read_locked(key.to_s) }
|
||||
end
|
||||
|
||||
def write(key, value, expires_in: nil)
|
||||
expires_at = expires_in ? monotonic_now + expires_in : nil
|
||||
key = key.to_s
|
||||
@mutex.synchronize do
|
||||
@data.delete(key)
|
||||
@data[key] = Entry.new(value, expires_at)
|
||||
evict_excess
|
||||
end
|
||||
value
|
||||
end
|
||||
|
||||
def fetch(key, expires_in: nil)
|
||||
existing = read(key)
|
||||
return existing unless existing.nil?
|
||||
value = yield
|
||||
write(key, value, expires_in: expires_in)
|
||||
value
|
||||
end
|
||||
|
||||
def delete(key)
|
||||
@mutex.synchronize { !@data.delete(key.to_s).nil? }
|
||||
end
|
||||
|
||||
def delete_matched(pattern)
|
||||
regex = pattern.is_a?(Regexp) ? pattern : Regexp.new(Regexp.escape(pattern.to_s).gsub('\*', '.*'))
|
||||
@mutex.synchronize { @data.reject! { |key, _| key =~ regex } }
|
||||
end
|
||||
|
||||
def exist?(key)
|
||||
!read(key).nil?
|
||||
end
|
||||
|
||||
def clear
|
||||
@mutex.synchronize { @data.clear }
|
||||
end
|
||||
|
||||
def size
|
||||
@mutex.synchronize { @data.size }
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def read_locked(key)
|
||||
entry = @data[key]
|
||||
return nil unless entry
|
||||
if entry.expired?(monotonic_now)
|
||||
@data.delete(key)
|
||||
return nil
|
||||
end
|
||||
@data.delete(key)
|
||||
@data[key] = entry
|
||||
entry.value
|
||||
end
|
||||
|
||||
def evict_excess
|
||||
return unless @max_size
|
||||
@data.shift while @data.size > @max_size
|
||||
end
|
||||
|
||||
def monotonic_now
|
||||
Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
||||
end
|
||||
end
|
||||
|
||||
class << self
|
||||
def store
|
||||
@store ||= MemoryStore.new
|
||||
end
|
||||
|
||||
attr_writer :store
|
||||
end
|
||||
end
|
||||
end
|
||||
Reference in new issue
Block a user