ni 0.1.0 → 0.1.1

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