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.
@@ -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
@@ -0,0 +1,8 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /_yardoc/
4
+ /coverage/
5
+ /doc/
6
+ /pkg/
7
+ /spec/reports/
8
+ /tmp/
@@ -0,0 +1,5 @@
1
+ {
2
+ "cSpell.words": [
3
+ "logisticed"
4
+ ]
5
+ }
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ source "https://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in logisticed.gemspec
4
+ gemspec
5
+
6
+ gem "rake", "~> 12.0"
@@ -0,0 +1,19 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ logisticed (0.1.0)
5
+
6
+ GEM
7
+ remote: https://rubygems.org/
8
+ specs:
9
+ rake (12.3.3)
10
+
11
+ PLATFORMS
12
+ ruby
13
+
14
+ DEPENDENCIES
15
+ logisticed!
16
+ rake (~> 12.0)
17
+
18
+ BUNDLED WITH
19
+ 2.1.4
@@ -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
+ ```
@@ -0,0 +1,2 @@
1
+ require "bundler/gem_tasks"
2
+ task :default => :spec
@@ -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__)
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -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
@@ -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
@@ -0,0 +1,3 @@
1
+ module Logisticed
2
+ VERSION = "0.1.0"
3
+ end
@@ -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: []