feat(orm): extend migrator with status and unscoped
This commit is contained in:
1 parent
2aede88152
commit
13b4613023
2 files changed
+178
-8
No files matched your search
@@ -20,6 +20,15 @@ module Prism
|
||||
entries.reject { |entry| applied.include?(entry.version) }
|
||||
end
|
||||
|
||||
# Pairs every migration file on disk with whether it has been applied
|
||||
# to this store, in the same order `entries`/`pending` use - the data
|
||||
# `prism db:status` (like `rails db:migrate:status`) prints, but kept
|
||||
# here so it is available to any caller, not just the CLI.
|
||||
def status
|
||||
applied = @store.applied_migrations
|
||||
entries.map { |entry| [entry, applied.include?(entry.version)] }
|
||||
end
|
||||
|
||||
def migrate!
|
||||
applied = pending.map { |entry| apply(entry) }
|
||||
applied.map(&:version)
|
||||
|
||||
+169
-8
@@ -5,7 +5,22 @@ module Prism
|
||||
|
||||
class Model
|
||||
include Validatable
|
||||
include Callbacks
|
||||
include Attributes
|
||||
include Dirty
|
||||
include Locking
|
||||
include Pessimistic
|
||||
include Paranoia
|
||||
include ListPosition
|
||||
include Serialization
|
||||
include Associable
|
||||
include NestedAttributes
|
||||
include SecurePassword
|
||||
include SecureToken
|
||||
include TokenFor
|
||||
include Encryption
|
||||
include Transactional
|
||||
include Enum
|
||||
|
||||
class << self
|
||||
def store
|
||||
@@ -58,17 +73,44 @@ module Prism
|
||||
def find(id)
|
||||
row = store.find(table_name, id)
|
||||
raise RecordNotFound, "no #{name} with id #{id}" unless row
|
||||
raise RecordNotFound, "no #{name} with id #{id}" if paranoid? && !row[paranoid_column].nil?
|
||||
instantiate(row)
|
||||
end
|
||||
|
||||
def all
|
||||
base = @default_scope_block ? Relation.new(self).instance_exec(&@default_scope_block) : Relation.new(self)
|
||||
paranoid_scope(base)
|
||||
end
|
||||
|
||||
def unscoped
|
||||
Relation.new(self)
|
||||
end
|
||||
|
||||
def default_scope(&block)
|
||||
@default_scope_block = block
|
||||
end
|
||||
|
||||
def scopes
|
||||
@scopes ||= {}
|
||||
end
|
||||
|
||||
def scope(name, body)
|
||||
scopes[name.to_sym] = body
|
||||
define_singleton_method(name) { |*args| all.instance_exec(*args, &body) }
|
||||
end
|
||||
|
||||
def where(conditions)
|
||||
all.where(conditions)
|
||||
end
|
||||
|
||||
def not(conditions)
|
||||
all.not(conditions)
|
||||
end
|
||||
|
||||
def none
|
||||
all.none
|
||||
end
|
||||
|
||||
def order(column, direction = :asc)
|
||||
all.order(column, direction)
|
||||
end
|
||||
@@ -77,20 +119,90 @@ module Prism
|
||||
all.limit(count)
|
||||
end
|
||||
|
||||
def distinct
|
||||
all.distinct
|
||||
end
|
||||
|
||||
def find_by(conditions)
|
||||
all.find_by(conditions)
|
||||
end
|
||||
|
||||
def find_by!(conditions)
|
||||
all.find_by!(conditions)
|
||||
end
|
||||
|
||||
def first
|
||||
all.first
|
||||
end
|
||||
|
||||
def first!
|
||||
all.first!
|
||||
end
|
||||
|
||||
def last
|
||||
all.last
|
||||
end
|
||||
|
||||
def last!
|
||||
all.last!
|
||||
end
|
||||
|
||||
def sum(column)
|
||||
all.sum(column)
|
||||
end
|
||||
|
||||
def average(column)
|
||||
all.average(column)
|
||||
end
|
||||
|
||||
def minimum(column)
|
||||
all.minimum(column)
|
||||
end
|
||||
|
||||
def maximum(column)
|
||||
all.maximum(column)
|
||||
end
|
||||
|
||||
def group(column)
|
||||
all.group(column)
|
||||
end
|
||||
|
||||
def find_each(batch_size: 1000, &block)
|
||||
all.find_each(batch_size: batch_size, &block)
|
||||
end
|
||||
|
||||
def find_in_batches(batch_size: 1000, &block)
|
||||
all.find_in_batches(batch_size: batch_size, &block)
|
||||
end
|
||||
|
||||
def pick(*attributes)
|
||||
all.pick(*attributes)
|
||||
end
|
||||
|
||||
def ids
|
||||
all.ids
|
||||
end
|
||||
|
||||
def page(number)
|
||||
all.page(number)
|
||||
end
|
||||
|
||||
def per(size)
|
||||
all.per(size)
|
||||
end
|
||||
|
||||
def count
|
||||
store.count(table_name)
|
||||
(@default_scope_block || paranoid?) ? all.count : store.count(table_name)
|
||||
end
|
||||
|
||||
# Raw counter bump used by belongs_to counter_cache - see
|
||||
# Store#increment_counter for why this bypasses Model#save.
|
||||
def increment_counter(counter_name, id, by = 1)
|
||||
store.increment_counter(table_name, id, counter_name, by)
|
||||
end
|
||||
|
||||
def decrement_counter(counter_name, id, by = 1)
|
||||
increment_counter(counter_name, id, -by)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -99,6 +211,7 @@ module Prism
|
||||
@attributes = {}
|
||||
@new_record = true
|
||||
@destroyed = false
|
||||
apply_attribute_defaults
|
||||
assign(attributes)
|
||||
end
|
||||
|
||||
@@ -107,13 +220,25 @@ module Prism
|
||||
end
|
||||
|
||||
def []=(key, value)
|
||||
@attributes[key.to_s] = value
|
||||
key = key.to_s
|
||||
track_attribute_change(key, @attributes[key], value)
|
||||
@attributes[key] = value
|
||||
end
|
||||
|
||||
def id
|
||||
self['id']
|
||||
end
|
||||
|
||||
# Rails' `to_param` hook: the value a model contributes to a URL when
|
||||
# a route segment is built from it. The router never calls this
|
||||
# itself (every url_for/path caller passes an explicit params hash,
|
||||
# see lib/prism/renderable.rb#url_for) - it exists so a model can
|
||||
# define ONE place to override (e.g. a slug) and callers opt in by
|
||||
# writing `post.to_param` instead of `post.id`.
|
||||
def to_param
|
||||
id&.to_s
|
||||
end
|
||||
|
||||
def new_record?
|
||||
@new_record
|
||||
end
|
||||
@@ -126,19 +251,55 @@ module Prism
|
||||
@destroyed
|
||||
end
|
||||
|
||||
# Mass-assignment goes through a writer method when one is defined
|
||||
# (plain columns, associations, and virtual attributes like
|
||||
# SecurePassword's `password=`) so `Model.new(password: 'x')` runs
|
||||
# the same logic as calling `record.password = 'x'` by hand, instead
|
||||
# of poking the raw column hash and skipping it.
|
||||
def assign(attributes)
|
||||
attributes.each { |key, value| self[key] = value }
|
||||
attributes.each do |key, value|
|
||||
setter = "#{key}="
|
||||
respond_to?(setter) ? send(setter, value) : (self[key] = value)
|
||||
end
|
||||
self
|
||||
end
|
||||
|
||||
# The whole before_save..after_save sequence runs inside one
|
||||
# `transaction` so after_commit only fires once persistence is truly
|
||||
# settled, and any exception raised by a callback rolls the write
|
||||
# back (StaleObjectError included) instead of leaving it half-done.
|
||||
# A `return false` from an aborted before_save/before_create still
|
||||
# unwinds through that transaction as an uncommitted attempt, which
|
||||
# is why after_rollback can fire even when nothing was ever written -
|
||||
# there was never anything to roll back on disk, but the write this
|
||||
# record attempted did not go through, matching what the hook name
|
||||
# promises.
|
||||
def save
|
||||
return false unless valid?
|
||||
touch_timestamps
|
||||
if new_record?
|
||||
@attributes['id'] = self.class.store.insert(self.class.table_name, @attributes)
|
||||
@new_record = false
|
||||
else
|
||||
self.class.store.update(self.class.table_name, id, @attributes)
|
||||
creating = new_record?
|
||||
self.class.transaction do
|
||||
enlist_in_transaction(creating ? :create : :update)
|
||||
return false unless run_callbacks(:before_save)
|
||||
return false unless run_callbacks(creating ? :before_create : :before_update)
|
||||
if creating
|
||||
initialize_lock_version
|
||||
@attributes['id'] = self.class.store.insert(self.class.table_name, @attributes)
|
||||
@new_record = false
|
||||
elsif self.class.locking_enabled?
|
||||
begin
|
||||
row = self.class.store.update(self.class.table_name, id, dirty_attributes,
|
||||
[self.class.locking_column, lock_version.to_i])
|
||||
rescue Store::StaleRowError
|
||||
raise StaleObjectError.new(self, :update)
|
||||
end
|
||||
@attributes[self.class.locking_column] = row[self.class.locking_column]
|
||||
else
|
||||
self.class.store.update(self.class.table_name, id, dirty_attributes)
|
||||
end
|
||||
changes_applied
|
||||
run_callbacks(creating ? :after_create : :after_update)
|
||||
run_callbacks(:after_save)
|
||||
end
|
||||
true
|
||||
end
|
||||
|
||||
Reference in new issue
Block a user