logisticed 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +8 -0
- data/.vscode/settings.json +5 -0
- data/Gemfile +6 -0
- data/Gemfile.lock +19 -0
- data/README.md +79 -0
- data/Rakefile +2 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/db/migrate/1_create_logistics_migration.rb +24 -0
- data/lib/logisticed.rb +36 -0
- data/lib/logisticed/logistic.rb +23 -0
- data/lib/logisticed/logisticer.rb +96 -0
- data/lib/logisticed/sweeper.rb +57 -0
- data/lib/logisticed/version.rb +3 -0
- data/logisticed.gemspec +24 -0
- metadata +58 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: e3c3fe85b4c2c2975df8b5cd4870b555b85871a27b1599bde1e11a5139a039b8
|
|
4
|
+
data.tar.gz: 4ada53766738c01f2acc507e0ad3dad86b0e074b9f032d157dd2992205220cfc
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 38f274ccd8522f277a272f1b3c0c45cdcc4b1e2ec9bfbf02241ce887f395049fa41b2a42c4358cf8122126e3008017b2f9224ec4e625db9e4550a2400678a766
|
|
7
|
+
data.tar.gz: 5c5ae24ab9b8572cfffa83b28e5c039e6c9085158899e052f7c4b8c6d1b3b40ade8af3cc9245c9c3c2a4d6d9f775d09a002f158f17b42d02b2054ed3ed7755cb
|
data/.gitignore
ADDED
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
data/README.md
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# Logisticed
|
|
2
|
+
轻松记录每条记录状态变更时的操作时间以及操作人
|
|
3
|
+
## Installation
|
|
4
|
+
|
|
5
|
+
Add this line to your application's Gemfile:
|
|
6
|
+
|
|
7
|
+
```ruby
|
|
8
|
+
gem 'logisticed'
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Then, from your Rails app directory, create the `logistics` table:
|
|
12
|
+
```bash
|
|
13
|
+
$ rake logisticed_migration:install:migrations
|
|
14
|
+
$ rails db:migrate
|
|
15
|
+
```
|
|
16
|
+
## Usage
|
|
17
|
+
|
|
18
|
+
你只需要告诉 `logisticed` 需要监听什么字段, 并且被修改为什么值的时候就行了。
|
|
19
|
+
|
|
20
|
+
```ruby
|
|
21
|
+
class Page < ActiveRecord::Base
|
|
22
|
+
logisticed :status, values: [:active, :archived]
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
他将会为你提供 `active_at`、 `active_by`、 `archived_at`、 `archived_by` 这几个方法,为你提供某个状态最近的操作历史
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
如果在 model 中定义了枚举类型的字段,也可以在定义枚举的下面直接添加 `logisticed`,他会自动为你监听枚举的所有值,同时 logisticed 支持 `only` 和 `except` 这两个参数
|
|
29
|
+
|
|
30
|
+
```ruby
|
|
31
|
+
class Page < ActiveRecord::Base
|
|
32
|
+
enum status: [:draft, :active, :archived]
|
|
33
|
+
logisticed :status, only: [:active, :archived]
|
|
34
|
+
end
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
现在就已经可以监听所有的操作人和操作时间等信息了
|
|
38
|
+
```ruby
|
|
39
|
+
class PagesController < ApplicationController
|
|
40
|
+
def create
|
|
41
|
+
current_user # => #<User name: 'sss'>
|
|
42
|
+
@page = Page.first # => #<Page status: 'draft'>
|
|
43
|
+
@page.active!
|
|
44
|
+
@active_at # => 2021-01-22 17:15:13 +0800
|
|
45
|
+
@active_by # => #<User name: 'sss'>
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
当然你也可以使用 `@page.logistics` 得到 @page 这条记录的所有变更流程,也可以使用 `@page.active_logistics` 得到状态变为 active 的所有变更流程
|
|
51
|
+
|
|
52
|
+
除此之外你也可以使用 `as_user` 制定某个用户成为操作人员
|
|
53
|
+
|
|
54
|
+
```ruby
|
|
55
|
+
class PagesController < ApplicationController
|
|
56
|
+
def create
|
|
57
|
+
current_user # => #<User name: 'sss'>
|
|
58
|
+
user = User.last # => #<User name: 'smx'>
|
|
59
|
+
Logisticed::Logistic.as_user(user) do
|
|
60
|
+
@page = Page.first # => #<Page status: 'draft'>
|
|
61
|
+
@page.active!
|
|
62
|
+
@active_at # => 2021-01-22 17:15:13 +0800
|
|
63
|
+
@active_by # => #<User name: 'smx'>
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
```
|
|
68
|
+
# setting
|
|
69
|
+
|
|
70
|
+
```ruby
|
|
71
|
+
# config/initializers/logisticed.rb
|
|
72
|
+
|
|
73
|
+
Logisticed.config do |config|
|
|
74
|
+
config.current_user_method = :authenticated_user
|
|
75
|
+
# if your table primary_key type is uuid
|
|
76
|
+
config.logisticed_source_id_column_type = :uuid
|
|
77
|
+
config.logisticed_operator_id_column_type = :uuid
|
|
78
|
+
end
|
|
79
|
+
```
|
data/Rakefile
ADDED
data/bin/console
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
|
|
3
|
+
require "bundler/setup"
|
|
4
|
+
require "logisticed"
|
|
5
|
+
|
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
|
8
|
+
|
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
|
10
|
+
# require "pry"
|
|
11
|
+
# Pry.start
|
|
12
|
+
|
|
13
|
+
require "irb"
|
|
14
|
+
IRB.start(__FILE__)
|
data/bin/setup
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
if ActiveRecord.gem_version >= Gem::Version.new('5.0')
|
|
2
|
+
class CreateLogisticsMigration < ActiveRecord::Migration[4.2]; end
|
|
3
|
+
else
|
|
4
|
+
class CreateLogisticsMigration < ActiveRecord::Migration; end
|
|
5
|
+
end
|
|
6
|
+
CreateLogisticsMigration.class_eval do
|
|
7
|
+
def self.up
|
|
8
|
+
create_table Logisticed.logisticed_table do |t|
|
|
9
|
+
t.string :source_type
|
|
10
|
+
t.send(Logisticed.logisticed_source_id_column_type, :source_id)
|
|
11
|
+
t.string :operator_type
|
|
12
|
+
t.send(Logisticed.logisticed_operator_id_column_type, :operator_id)
|
|
13
|
+
t.string :value
|
|
14
|
+
t.string :remote_ip
|
|
15
|
+
t.string :request_uuid
|
|
16
|
+
t.datetime :created_at, null: false
|
|
17
|
+
end
|
|
18
|
+
add_index Logisticed.logisticed_table, [:source_type, :source_id], name: 'logisticed_source_index'
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def self.down
|
|
22
|
+
drop_table Logisticed.logisticed_table
|
|
23
|
+
end
|
|
24
|
+
end
|
data/lib/logisticed.rb
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'logisticed/version'
|
|
4
|
+
|
|
5
|
+
module Logisticed
|
|
6
|
+
class Migration < Rails::Engine; end
|
|
7
|
+
class Error < StandardError; end
|
|
8
|
+
extend ActiveSupport::Autoload
|
|
9
|
+
class << self
|
|
10
|
+
attr_accessor :logisticed_table, :logisticed_source_id_column_type, :logisticed_operator_id_column_type, :current_user_method
|
|
11
|
+
def config
|
|
12
|
+
yield(self)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def logistic_class
|
|
16
|
+
@logistic_class ||= Logistic
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def store
|
|
20
|
+
Thread.current[:logisticed_store] ||= {}
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
@current_user_method = :current_user
|
|
25
|
+
@logisticed_table = :logistics
|
|
26
|
+
@logisticed_source_id_column_type = :integer
|
|
27
|
+
@logisticed_operator_id_column_type = :integer
|
|
28
|
+
end
|
|
29
|
+
require 'logisticed/logistic'
|
|
30
|
+
require 'logisticed/logisticer'
|
|
31
|
+
::ActiveRecord::Base.include Logisticed::Logisticer
|
|
32
|
+
require 'logisticed/sweeper'
|
|
33
|
+
|
|
34
|
+
ActiveSupport.on_load(:active_record) do
|
|
35
|
+
include Logisticed
|
|
36
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
module Logisticed
|
|
2
|
+
class Logistic < ::ActiveRecord::Base
|
|
3
|
+
belongs_to :source, polymorphic: true
|
|
4
|
+
belongs_to :operator, polymorphic: true
|
|
5
|
+
before_create :set_logistic_user
|
|
6
|
+
|
|
7
|
+
def self.as_user(user)
|
|
8
|
+
last_logisticed_user = ::Logisticed.store[:logisticed_user]
|
|
9
|
+
::Logisticed.store[:logisticed_user] = user
|
|
10
|
+
yield
|
|
11
|
+
ensure
|
|
12
|
+
::Logisticed.store[:logisticed_user] = last_logisticed_user
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
private
|
|
16
|
+
|
|
17
|
+
def set_logistic_user
|
|
18
|
+
self.operator ||= ::Logisticed.store[:logisticed_user] # from .as_user
|
|
19
|
+
self.operator ||= ::Logisticed.store[:current_user].try!(:call) # from Sweeper
|
|
20
|
+
nil # prevent stopping callback chains
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Logisticed
|
|
4
|
+
module Logisticer
|
|
5
|
+
extend ActiveSupport::Concern
|
|
6
|
+
def self.included(base)
|
|
7
|
+
base.extend ClassMethods
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
module ClassMethods
|
|
11
|
+
def logisticed(attr, options = {})
|
|
12
|
+
struct = ListenerStruct.new(self, attr, options)
|
|
13
|
+
has_many :logistics, -> { order(created_at: :asc) },
|
|
14
|
+
as: :source, class_name: Logisticed.logistic_class.name,
|
|
15
|
+
inverse_of: :source
|
|
16
|
+
|
|
17
|
+
has_one :logistic, -> { order(created_at: :asc) },
|
|
18
|
+
as: :source, class_name: Logisticed.logistic_class.name,
|
|
19
|
+
inverse_of: :source
|
|
20
|
+
|
|
21
|
+
struct.normalize_values.each do |value|
|
|
22
|
+
has_many "#{value}_logistics".to_sym, -> { where(value: value).order(created_at: :asc) }, as: :source, class_name: Logisticed.logistic_class.name, inverse_of: :source
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
after_commit do
|
|
26
|
+
if new_value = send("#{attr}_previous_change")&.last.presence
|
|
27
|
+
execute_method_name = "#{attr}_change_to_#{new_value}"
|
|
28
|
+
send(execute_method_name) if respond_to?(execute_method_name)
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
class_eval do
|
|
33
|
+
def value_change_by(value)
|
|
34
|
+
logistics.where(value: value).last
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
struct.normalize_values.each do |value|
|
|
38
|
+
define_method "#{value}_at" do
|
|
39
|
+
value_change_by(value)&.created_at
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
define_method "#{value}_by" do
|
|
43
|
+
value_change_by(value)&.operator
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
define_method "#{attr}_change_to_#{value}" do
|
|
47
|
+
logistics.create(value: value)
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
class ListenerStruct
|
|
55
|
+
class ArgumentError < StandardError; end
|
|
56
|
+
extend Forwardable
|
|
57
|
+
attr_reader :klass, :column, :options, :values
|
|
58
|
+
def initialize(klass, column, options)
|
|
59
|
+
@klass = klass
|
|
60
|
+
@column = column.to_s
|
|
61
|
+
@options = options
|
|
62
|
+
@values = options[:values]
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def normalize_values
|
|
66
|
+
@normalize_values ||= \
|
|
67
|
+
if options[:only].present?
|
|
68
|
+
(column_enum_values & options[:only]).uniq
|
|
69
|
+
elsif options[:except].present?
|
|
70
|
+
(column_enum_values - options[:only]).uniq
|
|
71
|
+
else
|
|
72
|
+
column_enum_values
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
private
|
|
77
|
+
|
|
78
|
+
def column_enum_values
|
|
79
|
+
@column_enum_values ||= begin
|
|
80
|
+
if values.present?
|
|
81
|
+
case values
|
|
82
|
+
when Hash
|
|
83
|
+
values.keys.map(&:to_s)
|
|
84
|
+
when Array
|
|
85
|
+
values
|
|
86
|
+
else
|
|
87
|
+
raise ArgumentError, 'values must be Array or Hash'
|
|
88
|
+
end
|
|
89
|
+
else
|
|
90
|
+
klass.send(column.pluralize).keys.map(&:to_s)
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
end
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Logisticed
|
|
4
|
+
class Sweeper
|
|
5
|
+
STORED_DATA = {
|
|
6
|
+
current_remote_address: :remote_ip,
|
|
7
|
+
current_request_uuid: :request_uuid,
|
|
8
|
+
current_user: :current_user
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
delegate :store, to: ::Logisticed
|
|
12
|
+
|
|
13
|
+
def around(controller)
|
|
14
|
+
self.controller = controller
|
|
15
|
+
# set store[:current_remote_address], store[:current_request_uuid], store[:current_user]
|
|
16
|
+
STORED_DATA.each { |k, m| store[k] = send(m) }
|
|
17
|
+
yield
|
|
18
|
+
ensure
|
|
19
|
+
self.controller = nil
|
|
20
|
+
STORED_DATA.keys.each { |k| store.delete(k) }
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def current_user
|
|
24
|
+
lambda do
|
|
25
|
+
if controller.respond_to?(Logisticed.current_user_method, true)
|
|
26
|
+
controller.send(Logisticed.current_user_method)
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def controller
|
|
32
|
+
store[:current_controller]
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def controller=(value)
|
|
36
|
+
store[:current_controller] = value
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def remote_ip
|
|
40
|
+
controller.try(:request).try(:remote_ip)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def request_uuid
|
|
44
|
+
controller.try(:request).try(:uuid)
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
ActiveSupport.on_load(:action_controller) do
|
|
50
|
+
if defined?(ActionController::Base)
|
|
51
|
+
# Logisticed::Sweeper.new 的目的是保证每次访问一个action时都是一个单独的线程
|
|
52
|
+
ActionController::Base.around_action Logisticed::Sweeper.new
|
|
53
|
+
end
|
|
54
|
+
if defined?(ActionController::API)
|
|
55
|
+
ActionController::API.around_action Logisticed::Sweeper.new
|
|
56
|
+
end
|
|
57
|
+
end
|
data/logisticed.gemspec
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
require_relative 'lib/logisticed/version'
|
|
2
|
+
|
|
3
|
+
Gem::Specification.new do |spec|
|
|
4
|
+
spec.name = "logisticed"
|
|
5
|
+
spec.version = Logisticed::VERSION
|
|
6
|
+
spec.authors = ["sumingxuan"]
|
|
7
|
+
spec.email = ["1154621382@qq.com"]
|
|
8
|
+
|
|
9
|
+
spec.summary = %q{simple manage business change datas}
|
|
10
|
+
spec.description = %q{轻松的管理你的业务变更的操作人和操作时间}
|
|
11
|
+
spec.homepage = "https://github.com/SuMingXuan/logisticed"
|
|
12
|
+
spec.required_ruby_version = Gem::Requirement.new(">= 2.3.0")
|
|
13
|
+
|
|
14
|
+
spec.metadata["homepage_uri"] = spec.homepage
|
|
15
|
+
|
|
16
|
+
# Specify which files should be added to the gem when it is released.
|
|
17
|
+
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
|
18
|
+
spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
|
|
19
|
+
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
|
20
|
+
end
|
|
21
|
+
spec.bindir = "exe"
|
|
22
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
|
23
|
+
spec.require_paths = ["lib"]
|
|
24
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: logisticed
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- sumingxuan
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: exe
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2021-01-22 00:00:00.000000000 Z
|
|
12
|
+
dependencies: []
|
|
13
|
+
description: 轻松的管理你的业务变更的操作人和操作时间
|
|
14
|
+
email:
|
|
15
|
+
- 1154621382@qq.com
|
|
16
|
+
executables: []
|
|
17
|
+
extensions: []
|
|
18
|
+
extra_rdoc_files: []
|
|
19
|
+
files:
|
|
20
|
+
- ".gitignore"
|
|
21
|
+
- ".vscode/settings.json"
|
|
22
|
+
- Gemfile
|
|
23
|
+
- Gemfile.lock
|
|
24
|
+
- README.md
|
|
25
|
+
- Rakefile
|
|
26
|
+
- bin/console
|
|
27
|
+
- bin/setup
|
|
28
|
+
- db/migrate/1_create_logistics_migration.rb
|
|
29
|
+
- lib/logisticed.rb
|
|
30
|
+
- lib/logisticed/logistic.rb
|
|
31
|
+
- lib/logisticed/logisticer.rb
|
|
32
|
+
- lib/logisticed/sweeper.rb
|
|
33
|
+
- lib/logisticed/version.rb
|
|
34
|
+
- logisticed.gemspec
|
|
35
|
+
homepage: https://github.com/SuMingXuan/logisticed
|
|
36
|
+
licenses: []
|
|
37
|
+
metadata:
|
|
38
|
+
homepage_uri: https://github.com/SuMingXuan/logisticed
|
|
39
|
+
post_install_message:
|
|
40
|
+
rdoc_options: []
|
|
41
|
+
require_paths:
|
|
42
|
+
- lib
|
|
43
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
44
|
+
requirements:
|
|
45
|
+
- - ">="
|
|
46
|
+
- !ruby/object:Gem::Version
|
|
47
|
+
version: 2.3.0
|
|
48
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
49
|
+
requirements:
|
|
50
|
+
- - ">="
|
|
51
|
+
- !ruby/object:Gem::Version
|
|
52
|
+
version: '0'
|
|
53
|
+
requirements: []
|
|
54
|
+
rubygems_version: 3.0.8
|
|
55
|
+
signing_key:
|
|
56
|
+
specification_version: 4
|
|
57
|
+
summary: simple manage business change datas
|
|
58
|
+
test_files: []
|