ni 0.1.0 → 0.1.1

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,109 @@
1
+ module Ni
2
+ module Params
3
+ extend ActiveSupport::Concern
4
+
5
+ class Mapper
6
+ CONFIG_MAP = {
7
+ String => :description,
8
+ Hash => :options,
9
+ Proc => :contract,
10
+ Symbol => :method_contract
11
+ }
12
+
13
+ def self.perform(args)
14
+ args.each_with_object({}) do |next_arg, acc|
15
+ option = CONFIG_MAP[next_arg.class]
16
+
17
+ fail 'unknown argument' unless option.present?
18
+
19
+ acc[option] = next_arg
20
+ end
21
+ end
22
+ end
23
+
24
+ included do
25
+ def allow_to_read?(action, name)
26
+ receive_contracts = select_contracts_for_action(self.class.receive_contracts, action)
27
+ mutate_contracts = select_contracts_for_action(self.class.mutate_contracts, action)
28
+ provide_contracts = select_contracts_for_action(self.class.provide_contracts, action)
29
+
30
+ return true if receive_contracts.blank? && mutate_contracts.blank? && provide_contracts.blank?
31
+
32
+ receive_contracts.keys.include?(name) ||
33
+ mutate_contracts.keys.include?(name)
34
+ end
35
+
36
+ def allow_to_write?(action, name)
37
+ receive_contracts = select_contracts_for_action(self.class.receive_contracts, action)
38
+ mutate_contracts = select_contracts_for_action(self.class.mutate_contracts, action)
39
+ provide_contracts = select_contracts_for_action(self.class.provide_contracts, action)
40
+
41
+ return true if receive_contracts.blank? && mutate_contracts.blank? && provide_contracts.blank?
42
+
43
+ provide_contracts.keys.include?(name) ||
44
+ mutate_contracts.keys.include?(name)
45
+ end
46
+
47
+ protected
48
+
49
+ def ensure_received_params(action=DEFAULT_ACTION)
50
+ ensure_contracts self.class.receive_contracts, action
51
+ ensure_contracts self.class.mutate_contracts, action
52
+ end
53
+
54
+ def ensure_provided_params(action=DEFAULT_ACTION)
55
+ ensure_contracts self.class.provide_contracts, action
56
+ end
57
+
58
+ def ensure_contracts(contracts, action)
59
+ select_contracts_for_action(contracts, action).each do |param, contract|
60
+ value = context.raw_get(param)
61
+
62
+ if contract[:contract].present?
63
+ unless contract[:contract].call(value)
64
+ fail "Value of `#{param}` doesn't match to contract"
65
+ end
66
+ end
67
+
68
+ if contract[:method_contract].present?
69
+ unless value.respond_to?(contract[:method_contract], true)
70
+ fail "Value of `#{param}` doesn't respond to contract method :#{contract[:method_contract]}"
71
+ end
72
+
73
+ unless value.send(contract[:method_contract])
74
+ fail "Value of `#{param}` doesn't match to contract :#{contract[:method_contract]}"
75
+ end
76
+ end
77
+ end
78
+ end
79
+
80
+ def select_contracts_for_action(contracts, action)
81
+ (contracts || {}).select do |_, contract|
82
+ contract.dig(:options, :for).blank? || contract.dig(:options, :for) == action
83
+ end
84
+ end
85
+ end
86
+
87
+ module ClassMethods
88
+ attr_accessor :receive_contracts, :provide_contracts, :mutate_contracts
89
+
90
+ def receive(*args)
91
+ @receive_contracts ||= {}
92
+ name = args.shift
93
+ @receive_contracts[name] = Mapper.perform(args)
94
+ end
95
+
96
+ def provide(*args)
97
+ @provide_contracts ||= {}
98
+ name = args.shift
99
+ @provide_contracts[name] = Mapper.perform(args)
100
+ end
101
+
102
+ def mutate(*args)
103
+ @mutate_contracts ||= {}
104
+ name = args.shift
105
+ @mutate_contracts[name] = Mapper.perform(args)
106
+ end
107
+ end
108
+ end
109
+ end
@@ -0,0 +1,15 @@
1
+ module Ni
2
+ class Result
3
+ attr_reader :context
4
+
5
+ delegate :success?, :valid?, :errors, to: :context
6
+
7
+ def initialize(context, args=[])
8
+ @context, @args = context, args
9
+ end
10
+
11
+ def to_ary
12
+ [self] + @args
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,51 @@
1
+ module Ni
2
+ module Storages
3
+ if defined?(ActiveRecord)
4
+ class ActiveRecordMetadataRepository < ActiveRecord::Base
5
+ self.table_name = "ni_metadata"
6
+
7
+ TIMER_KEY = 'timer'.freeze
8
+
9
+ def self.store(uid, key, data)
10
+ record = self.where(uid: uid, key: key).first_or_initialize
11
+ record.update(data: data.to_json)
12
+ end
13
+
14
+ def self.fetch(uid, key)
15
+ record = self.where(uid: uid, key: key).first
16
+
17
+ if record.present?
18
+ JSON.parse(record.data, symbolize_names: true)
19
+ else
20
+ {}
21
+ end
22
+ end
23
+
24
+ def self.setup_timer!(timer_id, datetime, timer_klass_name, timer_action, system_uid)
25
+ data = [timer_klass_name, timer_action, system_uid].to_json
26
+ self.create!(uid: timer_id, key: TIMER_KEY, run_timer_at: datetime, data: data)
27
+ end
28
+
29
+ def self.clear_timer!(timer_id)
30
+ self.where(uid: timer_id, key: TIMER_KEY).delete_all
31
+ end
32
+
33
+ def self.fetch_timers
34
+ self.where(key: TIMER_KEY).where("run_timer_at < ?", Time.now).map do |record|
35
+ [record.uid] + JSON.parse(record.data)
36
+ end
37
+ end
38
+ end
39
+ else
40
+ class ActiveRecordMetadataRepository
41
+ def self.store(uid, key, data)
42
+ raise "ActiveRecord not found"
43
+ end
44
+
45
+ def self.fetch(uid, key)
46
+ raise "ActiveRecord not found"
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,147 @@
1
+ module Ni
2
+ module Storages
3
+ class Default
4
+ METADATA_REPOSITORY_KEY = 'store_state'
5
+
6
+ def initialize(context, metadata_repository_klass)
7
+ @context, @metadata_repository_klass = context, metadata_repository_klass
8
+
9
+ @storages_map = {}
10
+
11
+ setup_custom_storages
12
+
13
+ if defined?(ActiveRecord) && !skip_default_storages?
14
+ register_storage :active_record_record, {
15
+ match: ActiveRecord::Base,
16
+ store: -> (record) { record.save!; [record.class.name, record.id] },
17
+ fetch: -> (data) { data.first.constantize.find(data.last) }
18
+ }
19
+
20
+ register_storage :active_record_collection, {
21
+ match: -> (value) do
22
+ value.respond_to?(:each) &&
23
+ value.all? { |element| element.is_a?(ActiveRecord::Base) } &&
24
+ value.all? { |element| element.class == value.first.class }
25
+ end,
26
+ store: -> (records) do
27
+ records.each(&:save!)
28
+ [records.first.class.name] + records.map(&:id)
29
+ end,
30
+ fetch: -> (data) do
31
+ klass = data.shift.constantize
32
+ klass.where(id: data)
33
+ end
34
+ }
35
+ end
36
+
37
+ unless skip_default_storages?
38
+ register_storage :true_value, {
39
+ match: TrueClass,
40
+ store: -> (value) { nil },
41
+ fetch: -> (data) { true }
42
+ }
43
+
44
+ register_storage :false_value, {
45
+ match: FalseClass,
46
+ store: -> (value) { nil },
47
+ fetch: -> (data) { false }
48
+ }
49
+
50
+ register_storage :string_value, {
51
+ match: String,
52
+ store: -> (value) { value },
53
+ fetch: -> (data) { data }
54
+ }
55
+
56
+ register_storage :integer_value, {
57
+ match: Integer,
58
+ store: -> (value) { value },
59
+ fetch: -> (data) { data.to_i }
60
+ }
61
+
62
+ register_storage :float_value, {
63
+ match: Float,
64
+ store: -> (value) { value },
65
+ fetch: -> (data) { data.to_f }
66
+ }
67
+
68
+ register_storage :symbol_value, {
69
+ match: Symbol,
70
+ store: -> (value) { value },
71
+ fetch: -> (data) { data.to_sym }
72
+ }
73
+
74
+ register_storage :nil_value, {
75
+ match: NilClass,
76
+ store: -> (value) { nil },
77
+ fetch: -> (data) { nil }
78
+ }
79
+ end
80
+ end
81
+
82
+ def store
83
+ fetch_data = {}
84
+
85
+ @context.each do |name, value|
86
+ known_storage, config = @storages_map.find do |storage_name, cfg|
87
+ match = cfg[:match]
88
+
89
+ (match.is_a?(Class) && value.is_a?(match)) ||
90
+ (match.is_a?(String) && value.is_a?(match.constantize)) ||
91
+ (match.is_a?(Proc) && match.call(value))
92
+ end
93
+
94
+ if respond_to?(:"store_#{name}")
95
+ fetch_data[name] = { known_storage: nil, data: public_send(:"store_#{name}", value) }
96
+ elsif known_storage.present?
97
+ known_logic = config[:store]
98
+
99
+ if known_logic.is_a?(Proc)
100
+ fetch_data[name] = { known_storage: known_storage, data: known_logic.call(value) }
101
+ else
102
+ raise "Storage logic type is not supported"
103
+ end
104
+ else
105
+ raise "Logic for storing #{name} was not defined"
106
+ end
107
+ end
108
+
109
+ @metadata_repository_klass.store(@context.system_uid, METADATA_REPOSITORY_KEY, fetch_data)
110
+ end
111
+
112
+ def fetch
113
+ fetch_data = @metadata_repository_klass.fetch(@context.system_uid, METADATA_REPOSITORY_KEY)
114
+
115
+ fetch_data.each do |name, data|
116
+ next if @context.has_key?(name) # do not restore an existing value. E.g. it can be provided on process launch
117
+
118
+ if respond_to?(:"store_#{name}")
119
+ @context.raw_set(name, public_send(:"fetch_#{name}", data[:data]))
120
+ elsif data[:known_storage].present?
121
+ if @storages_map[data[:known_storage].to_sym].present?
122
+ @context.raw_set(name, @storages_map[data[:known_storage].to_sym][:fetch].call(data[:data]))
123
+ else
124
+ raise "Storage is not known"
125
+ end
126
+ else
127
+ raise "Doesn't know how to fetch #{name}"
128
+ end
129
+ end
130
+ end
131
+
132
+ private
133
+
134
+ def skip_default_storages?
135
+ false
136
+ end
137
+
138
+ def setup_custom_storages
139
+ # can be defined for custom storages
140
+ end
141
+
142
+ def register_storage(name, config)
143
+ @storages_map[name] ||= config
144
+ end
145
+ end
146
+ end
147
+ end
@@ -0,0 +1,31 @@
1
+ module Ni
2
+ module StoragesConfig
3
+ extend ActiveSupport::Concern
4
+
5
+ module ClassMethods
6
+ cattr_accessor :context_storage_klass, :metadata_repository_klass
7
+
8
+ def storage(klass)
9
+ self.context_storage_klass = klass
10
+ end
11
+
12
+ def metadata_repository(klass)
13
+ self.metadata_repository_klass = klass
14
+ end
15
+
16
+ def copy_storage_and_metadata_repository(interactor_klass)
17
+ unless self.context_storage_klass.present?
18
+ if interactor_klass.context_storage_klass.present?
19
+ storage interactor_klass.context_storage_klass
20
+ end
21
+ end
22
+
23
+ unless self.metadata_repository_klass.present?
24
+ if interactor_klass.metadata_repository_klass.present?
25
+ metadata_repository interactor_klass.metadata_repository_klass
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,24 @@
1
+ module Ni::Tools
2
+ class Timers
3
+ def self.fetch_and_run(metadata_repository_klass, exceptions_logger=nil)
4
+ current_timers = metadata_repository_klass.fetch_timers
5
+ exceptions = []
6
+
7
+ current_timers.each do |data|
8
+ id, klass_name, action, system_uid = data
9
+
10
+ begin
11
+ klass_name.constantize.public_send(action, system_uid: system_uid)
12
+ rescue Exception => e
13
+ exceptions << e
14
+ ensure
15
+ metadata_repository_klass.clear_timer!(id)
16
+ end
17
+ end
18
+
19
+ if exceptions_logger.present? && exceptions.present?
20
+ exceptions.each { |e| exceptions_logger.log(e) }
21
+ end
22
+ end
23
+ end
24
+ end
@@ -1,3 +1,3 @@
1
1
  module Ni
2
- VERSION = "0.1.0"
2
+ VERSION = "0.1.1"
3
3
  end
data/ni.gemspec CHANGED
@@ -29,9 +29,7 @@ Gem::Specification.new do |spec|
29
29
 
30
30
  # Specify which files should be added to the gem when it is released.
31
31
  # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
32
- spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
33
- `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
34
- end
32
+ spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features|coverage)/}) }
35
33
  spec.bindir = "exe"
36
34
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
37
35
  spec.require_paths = ["lib"]
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ni
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - KnightWhosayNi
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2019-09-19 00:00:00.000000000 Z
11
+ date: 2019-09-24 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -64,12 +64,31 @@ files:
64
64
  - ".travis.yml"
65
65
  - CODE_OF_CONDUCT.md
66
66
  - Gemfile
67
+ - Gemfile.lock
67
68
  - LICENSE.txt
68
69
  - README.md
69
70
  - Rakefile
70
71
  - bin/console
71
72
  - bin/setup
72
73
  - lib/ni.rb
74
+ - lib/ni/action_chain.rb
75
+ - lib/ni/context.rb
76
+ - lib/ni/expressions_language_engine/processor.rb
77
+ - lib/ni/expressions_language_engine/tokenizer.rb
78
+ - lib/ni/flows/base.rb
79
+ - lib/ni/flows/branch_interactor.rb
80
+ - lib/ni/flows/inline_interactor.rb
81
+ - lib/ni/flows/isolated_inline_interactor.rb
82
+ - lib/ni/flows/utils/handle_wait.rb
83
+ - lib/ni/flows/wait_for_condition.rb
84
+ - lib/ni/help.rb
85
+ - lib/ni/main.rb
86
+ - lib/ni/params.rb
87
+ - lib/ni/result.rb
88
+ - lib/ni/storages/active_record_metadata_repository.rb
89
+ - lib/ni/storages/default.rb
90
+ - lib/ni/storages_config.rb
91
+ - lib/ni/tools/timers.rb
73
92
  - lib/ni/version.rb
74
93
  - ni.gemspec
75
94
  homepage: https://github.com/trueknightwhosayni/ni