logisticed 0.1.0

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