logisticed 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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: []
|