bcdd-result 0.13.0 → 1.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.
Files changed (81) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +4 -4
  3. data/CHANGELOG.md +61 -21
  4. data/README.md +397 -227
  5. data/Rakefile +1 -1
  6. data/Steepfile +1 -1
  7. data/examples/multiple_listeners/Rakefile +8 -8
  8. data/examples/multiple_listeners/app/models/account/owner_creation.rb +1 -1
  9. data/examples/multiple_listeners/app/models/user/creation.rb +1 -1
  10. data/examples/multiple_listeners/app/models/user/token/creation.rb +1 -1
  11. data/examples/multiple_listeners/config/initializers/bcdd.rb +0 -2
  12. data/examples/multiple_listeners/config.rb +3 -3
  13. data/examples/multiple_listeners/db/setup.rb +2 -3
  14. data/examples/multiple_listeners/lib/bcdd/result/event_logs_record.rb +27 -0
  15. data/examples/multiple_listeners/lib/event_logs_listener/stdout.rb +60 -0
  16. data/examples/multiple_listeners/lib/runtime_breaker.rb +1 -1
  17. data/examples/service_objects/Rakefile +36 -0
  18. data/examples/service_objects/app/models/account/member.rb +10 -0
  19. data/examples/service_objects/app/models/account.rb +11 -0
  20. data/examples/service_objects/app/models/user/token.rb +7 -0
  21. data/examples/service_objects/app/models/user.rb +15 -0
  22. data/examples/service_objects/app/services/account/owner_creation.rb +47 -0
  23. data/examples/service_objects/app/services/application_service.rb +79 -0
  24. data/examples/service_objects/app/services/user/creation.rb +56 -0
  25. data/examples/service_objects/app/services/user/token/creation.rb +37 -0
  26. data/examples/service_objects/config/boot.rb +17 -0
  27. data/examples/service_objects/config/initializers/bcdd.rb +9 -0
  28. data/examples/service_objects/config.rb +20 -0
  29. data/examples/service_objects/db/setup.rb +49 -0
  30. data/examples/single_listener/Rakefile +5 -5
  31. data/examples/single_listener/app/models/account/owner_creation.rb +1 -1
  32. data/examples/single_listener/app/models/user/creation.rb +1 -1
  33. data/examples/single_listener/app/models/user/token/creation.rb +1 -1
  34. data/examples/single_listener/config/initializers/bcdd.rb +0 -2
  35. data/examples/single_listener/config.rb +1 -1
  36. data/examples/single_listener/lib/{single_transitions_listener.rb → single_event_logs_listener.rb} +32 -23
  37. data/lib/bcdd/{result/context → context}/callable_and_then.rb +6 -5
  38. data/lib/bcdd/{result/context → context}/expectations/mixin.rb +1 -1
  39. data/lib/bcdd/{result/context → context}/expectations.rb +2 -2
  40. data/lib/bcdd/context/failure.rb +9 -0
  41. data/lib/bcdd/{result/context → context}/mixin.rb +2 -2
  42. data/lib/bcdd/{result/context → context}/success.rb +11 -11
  43. data/lib/bcdd/context.rb +115 -0
  44. data/lib/bcdd/failure.rb +23 -0
  45. data/lib/bcdd/result/_self.rb +198 -0
  46. data/lib/bcdd/result/callable_and_then/caller.rb +1 -1
  47. data/lib/bcdd/result/config/switchers/addons.rb +2 -2
  48. data/lib/bcdd/result/config/switchers/constant_aliases.rb +1 -3
  49. data/lib/bcdd/result/config/switchers/features.rb +5 -5
  50. data/lib/bcdd/result/config/switchers/pattern_matching.rb +1 -1
  51. data/lib/bcdd/result/config.rb +7 -5
  52. data/lib/bcdd/result/contract/type_checker.rb +4 -0
  53. data/lib/bcdd/result/{transitions → event_logs}/config.rb +5 -3
  54. data/lib/bcdd/result/{transitions → event_logs}/listener.rb +5 -5
  55. data/lib/bcdd/result/{transitions → event_logs}/listeners.rb +17 -17
  56. data/lib/bcdd/result/{transitions → event_logs}/tracking/disabled.rb +1 -1
  57. data/lib/bcdd/result/{transitions → event_logs}/tracking/enabled.rb +15 -13
  58. data/lib/bcdd/result/{transitions → event_logs}/tracking.rb +4 -3
  59. data/lib/bcdd/result/{transitions → event_logs}/tree.rb +27 -11
  60. data/lib/bcdd/result/event_logs.rb +27 -0
  61. data/lib/bcdd/result/failure.rb +1 -3
  62. data/lib/bcdd/result/success.rb +1 -3
  63. data/lib/bcdd/result/version.rb +1 -1
  64. data/lib/bcdd/result.rb +23 -191
  65. data/lib/bcdd/success.rb +23 -0
  66. data/sig/bcdd/context.rbs +175 -0
  67. data/sig/bcdd/failure.rbs +13 -0
  68. data/sig/bcdd/result/config.rbs +1 -3
  69. data/sig/bcdd/result/context.rbs +2 -174
  70. data/sig/bcdd/result/contract.rbs +1 -0
  71. data/sig/bcdd/result/{transitions.rbs → event_logs.rbs} +19 -19
  72. data/sig/bcdd/result.rbs +13 -31
  73. data/sig/bcdd/success.rbs +13 -0
  74. metadata +41 -24
  75. data/examples/multiple_listeners/lib/bcdd/result/transitions_record.rb +0 -28
  76. data/examples/multiple_listeners/lib/transitions_listener/stdout.rb +0 -54
  77. data/lib/bcdd/result/context/failure.rb +0 -9
  78. data/lib/bcdd/result/context.rb +0 -93
  79. data/lib/bcdd/result/failure/methods.rb +0 -21
  80. data/lib/bcdd/result/success/methods.rb +0 -21
  81. data/lib/bcdd/result/transitions.rb +0 -27
data/Rakefile CHANGED
@@ -15,7 +15,7 @@ Rake::TestTask.new(:test_configuration) do |t|
15
15
  t.test_files = FileList.new('test/**/configuration_test.rb')
16
16
  end
17
17
 
18
- Rake::TestTask.new(:test_transitions_duration) do |t|
18
+ Rake::TestTask.new(:test_event_logs_duration) do |t|
19
19
  t.libs += %w[lib test]
20
20
 
21
21
  t.test_files = FileList.new('test/**/duration_test.rb')
data/Steepfile CHANGED
@@ -10,7 +10,7 @@ target :lib do
10
10
  # check 'app/models/**/*.rb' # Glob
11
11
  # ignore 'lib/templates/*.rb'
12
12
 
13
- library 'singleton' # Standard libraries
13
+ # library 'singleton' # Standard libraries
14
14
  # library 'strong_json' # Gems
15
15
 
16
16
  # configure_code_diagnostics(D::Ruby.default) # `default` diagnostics setting (applies by default)
@@ -7,7 +7,7 @@ end
7
7
 
8
8
  # Usage:
9
9
  #
10
- # rake DISABLE_TRANSITIONS=t
10
+ # rake DISABLE_EVENT_LOGS=t
11
11
  # rake DISABLE_LISTENER=t
12
12
  #
13
13
  # rake HIDE_GIVEN_AND_CONTINUE=t
@@ -17,19 +17,19 @@ end
17
17
  # rake BREAK_USER_TOKEN_CREATION=t
18
18
  #
19
19
  # rake BREAK_ACCOUNT_CREATION=t HIDE_GIVEN_AND_CONTINUE=t
20
- task default: %i[bcdd_result_transitions]
20
+ task default: %i[bcdd_result_event_logs]
21
21
 
22
22
  desc 'creates an account and an owner user through BCDD::Result'
23
- task :bcdd_result_transitions do
23
+ task :bcdd_result_event_logs do
24
24
  require_relative 'config'
25
25
 
26
26
  BCDD::Result.configuration do |config|
27
- config.feature.disable!(:transitions) if ENV['DISABLE_TRANSITIONS']
27
+ config.feature.disable!(:event_logs) if ENV['DISABLE_EVENT_LOGS']
28
28
 
29
29
  unless ENV['DISABLE_LISTENER']
30
- config.transitions.listener = BCDD::Result::Transitions::Listeners[
31
- TransitionsListener::Stdout,
32
- BCDD::Result::TransitionsRecord::Listener
30
+ config.event_logs.listener = BCDD::Result::EventLogs::Listeners[
31
+ EventLogsListener::Stdout,
32
+ BCDD::Result::EventLogsRecord::Listener
33
33
  ]
34
34
  end
35
35
  end
@@ -49,7 +49,7 @@ task :bcdd_result_transitions do
49
49
  nil
50
50
  end
51
51
 
52
- puts "\nBCDD::Result::TransitionsRecord.count: #{BCDD::Result::TransitionsRecord.count}"
52
+ puts "\nBCDD::Result::EventLogsRecord.count: #{BCDD::Result::EventLogsRecord.count}"
53
53
 
54
54
  puts "\nBenchmark: #{bench}"
55
55
  end
@@ -6,7 +6,7 @@ class Account
6
6
  include BCDD::Result::RollbackOnFailure
7
7
 
8
8
  def call(**input)
9
- BCDD::Result.transitions(name: self.class.name) do
9
+ BCDD::Result.event_logs(name: self.class.name) do
10
10
  Given(input)
11
11
  .and_then(:normalize_input)
12
12
  .and_then(:validate_input)
@@ -6,7 +6,7 @@ class User
6
6
  include BCDD::Result::RollbackOnFailure
7
7
 
8
8
  def call(**input)
9
- BCDD::Result.transitions(name: self.class.name) do
9
+ BCDD::Result.event_logs(name: self.class.name) do
10
10
  Given(input)
11
11
  .and_then(:normalize_input)
12
12
  .and_then(:validate_input)
@@ -5,7 +5,7 @@ class User::Token
5
5
  include BCDD::Context.mixin
6
6
 
7
7
  def call(**input)
8
- BCDD::Result.transitions(name: self.class.name) do
8
+ BCDD::Result.event_logs(name: self.class.name) do
9
9
  Given(input)
10
10
  .and_then(:normalize_input)
11
11
  .and_then(:validate_input)
@@ -3,8 +3,6 @@
3
3
  BCDD::Result.config.then do |config|
4
4
  config.addon.enable!(:continue)
5
5
 
6
- config.constant_alias.enable!('BCDD::Context')
7
-
8
6
  config.pattern_matching.disable!(:nil_as_valid_value_checking)
9
7
 
10
8
  # config.feature.disable!(:expectations) if Rails.env.production?
@@ -10,11 +10,11 @@ require_relative 'config/initializers/bcdd'
10
10
  require 'db/setup'
11
11
 
12
12
  require 'lib/bcdd/result/rollback_on_failure'
13
- require 'lib/bcdd/result/transitions_record'
13
+ require 'lib/bcdd/result/event_logs_record'
14
14
  require 'lib/runtime_breaker'
15
15
 
16
- module TransitionsListener
17
- require 'lib/transitions_listener/stdout'
16
+ module EventLogsListener
17
+ require 'lib/event_logs_listener/stdout'
18
18
  end
19
19
 
20
20
  require 'app/models/account'
@@ -10,13 +10,12 @@ ActiveRecord::Base.establish_connection(
10
10
 
11
11
  ActiveRecord::Schema.define do
12
12
  suppress_messages do
13
- create_table :bcdd_result_transitions do |t|
13
+ create_table :bcdd_result_event_logs do |t|
14
14
  t.string :root_name, null: false, index: true
15
15
  t.string :trace_id, index: true
16
16
  t.integer :version, null: false
17
17
  t.integer :duration, null: false, index: true
18
- t.json :ids_tree, null: false, default: []
19
- t.json :ids_matrix, null: false, default: {}
18
+ t.json :ids, null: false, default: {}
20
19
  t.json :records, null: false, default: []
21
20
 
22
21
  t.timestamps
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ class BCDD::Result::EventLogsRecord < ActiveRecord::Base
4
+ self.table_name = 'bcdd_result_event_logs'
5
+
6
+ class Listener
7
+ include ::BCDD::Result::EventLogs::Listener
8
+
9
+ def on_finish(event_logs:)
10
+ metadata = event_logs[:metadata]
11
+ root_name = event_logs.dig(:records, 0, :root, :name) || 'Unknown'
12
+
13
+ BCDD::Result::EventLogsRecord.create(
14
+ root_name: root_name,
15
+ trace_id: metadata[:trace_id],
16
+ version: event_logs[:version],
17
+ duration: metadata[:duration],
18
+ ids: metadata[:ids],
19
+ records: event_logs[:records]
20
+ )
21
+ rescue ::StandardError => e
22
+ err = "#{e.message} (#{e.class}); Backtrace: #{e.backtrace.join(', ')}"
23
+
24
+ ::Kernel.warn "Error on BCDD::Result::EventLogsRecord::Listener#on_finish: #{err}"
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ class EventLogsListener::Stdout
4
+ include BCDD::Result::EventLogs::Listener
5
+
6
+ def initialize
7
+ @buffer = []
8
+ end
9
+
10
+ def on_start(scope:)
11
+ scope => { id:, name:, desc: }
12
+
13
+ @buffer << [id, "##{id} #{name} - #{desc}".chomp('- ')]
14
+ end
15
+
16
+ def on_record(record:)
17
+ record => { current: { id: }, result: { kind:, type: } }
18
+
19
+ method_name = record.dig(:and_then, :method_name)
20
+
21
+ @buffer << [id, " * #{kind}(#{type}) from method: #{method_name}".chomp('from method: ')]
22
+ end
23
+
24
+ MapNestedMessages = ->(event_logs, buffer, hide_given_and_continue) do
25
+ ids_level_parent = event_logs.dig(:metadata, :ids, :level_parent)
26
+
27
+ messages = buffer.filter_map { |(id, msg)| "#{' ' * ids_level_parent[id].first}#{msg}" if ids_level_parent[id] }
28
+
29
+ messages.reject! { _1.match?(/\(_(given|continue)_\)/) } if hide_given_and_continue
30
+
31
+ messages
32
+ end
33
+
34
+ def on_finish(event_logs:)
35
+ messages = MapNestedMessages[event_logs, @buffer, ENV['HIDE_GIVEN_AND_CONTINUE']]
36
+
37
+ puts messages.join("\n")
38
+ end
39
+
40
+ def before_interruption(exception:, event_logs:)
41
+ messages = MapNestedMessages[event_logs, @buffer, ENV['HIDE_GIVEN_AND_CONTINUE']]
42
+
43
+ puts messages.join("\n")
44
+
45
+ bc = ::ActiveSupport::BacktraceCleaner.new
46
+ bc.add_filter { |line| line.gsub(__dir__.sub('/lib', ''), '').sub(/\A\//, '')}
47
+ bc.add_silencer { |line| /lib\/bcdd\/result/.match?(line) }
48
+ bc.add_silencer { |line| line.include?(RUBY_VERSION) }
49
+
50
+ dir = "#{FileUtils.pwd[1..]}/"
51
+
52
+ listener_filename = File.basename(__FILE__).chomp('.rb')
53
+
54
+ cb = bc.clean(exception.backtrace)
55
+ cb.each { _1.sub!(dir, '') }
56
+ cb.reject! { _1.match?(/block \(\d levels?\) in|in `block in|internal:kernel|#{listener_filename}/) }
57
+
58
+ puts "\nException:\n #{exception.message} (#{exception.class})\n\nBacktrace:\n #{cb.join("\n ")}"
59
+ end
60
+ end
@@ -6,6 +6,6 @@ module RuntimeBreaker
6
6
  def self.try_to_interrupt(env:)
7
7
  return unless String(ENV[env]).strip.start_with?(/1|t/)
8
8
 
9
- raise Interruption, "Runtime breaker activated (#{env})"
9
+ raise Interruption, "#{env}"
10
10
  end
11
11
  end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ if RUBY_VERSION <= '3.1'
4
+ puts 'This example requires Ruby 3.1 or higher.'
5
+ exit! 1
6
+ end
7
+
8
+ task default: %i[bcdd_result_event_logs]
9
+
10
+ task :config do
11
+ require_relative 'config'
12
+ end
13
+
14
+ desc 'creates an account and an owner user through BCDD::Result'
15
+ task bcdd_result_event_logs: %i[config] do
16
+ result1 = Account::OwnerCreation.call(
17
+ owner: {
18
+ name: "\tJohn Doe \n",
19
+ email: ' JOHN.doe@email.com',
20
+ password: '123123123',
21
+ password_confirmation: '123123123'
22
+ }
23
+ )
24
+
25
+ puts result1.inspect
26
+ puts
27
+
28
+ result2 = Account::OwnerCreation.call(
29
+ uuid: "",
30
+ owner: {}
31
+ ).on_failure(:invalid_input) do |output|
32
+ output[:input].errors.full_messages.each do |message|
33
+ puts message
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Account::Member < ActiveRecord::Base
4
+ self.table_name = 'account_members'
5
+
6
+ enum role: { owner: 0, admin: 1, contributor: 2 }
7
+
8
+ belongs_to :user, inverse_of: :memberships
9
+ belongs_to :account, inverse_of: :memberships
10
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Account < ActiveRecord::Base
4
+ has_many :memberships, inverse_of: :account, dependent: :destroy, class_name: '::Account::Member'
5
+ has_many :users, through: :memberships, inverse_of: :accounts
6
+
7
+ where_ownership = -> { where(account_members: {role: :owner}) }
8
+
9
+ has_one :ownership, where_ownership, dependent: nil, inverse_of: :account, class_name: '::Account::Member'
10
+ has_one :owner, through: :ownership, source: :user
11
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ class User::Token < ActiveRecord::Base
4
+ self.table_name = 'user_tokens'
5
+
6
+ belongs_to :user, inverse_of: :token
7
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ class User < ActiveRecord::Base
4
+ has_secure_password
5
+
6
+ has_many :memberships, inverse_of: :user, dependent: :destroy, class_name: '::Account::Member'
7
+ has_many :accounts, through: :memberships, inverse_of: :users
8
+
9
+ where_ownership = -> { where(account_members: { role: :owner }) }
10
+
11
+ has_one :ownership, where_ownership, inverse_of: :user, class_name: '::Account::Member'
12
+ has_one :account, through: :ownership, inverse_of: :owner
13
+
14
+ has_one :token, inverse_of: :user, dependent: :destroy, class_name: '::User::Token'
15
+ end
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Account
4
+ class OwnerCreation < ApplicationService
5
+ input do
6
+ attribute :uuid, :string, default: -> { ::SecureRandom.uuid }
7
+ attribute :owner
8
+
9
+ before_validation do |input|
10
+ input.uuid = input.uuid.strip.downcase
11
+ end
12
+
13
+ validates :uuid, presence: true, format: { with: /\A[0-9a-f]{8}-([0-9a-f]{4}-){3}[0-9a-f]{12}\z/i }
14
+ validates :owner, presence: true, type: ::Hash
15
+ end
16
+
17
+ def call(attributes)
18
+ rollback_on_failure {
19
+ Given(attributes)
20
+ .and_then(:create_owner)
21
+ .and_then(:create_account)
22
+ .and_then(:link_owner_to_account)
23
+ }.and_expose(:account_owner_created, %i[user account])
24
+ end
25
+
26
+ private
27
+
28
+ def create_owner(owner:, **)
29
+ ::User::Creation.call(owner).handle do |on|
30
+ on.success { |output| Continue(user: output[:user], token: output[:token]) }
31
+ on.failure { |output| Failure(:invalid_owner, **output) }
32
+ end
33
+ end
34
+
35
+ def create_account(uuid:, **)
36
+ account = ::Account.create(uuid:)
37
+
38
+ account.persisted? ? Continue(account:) : Failure(:invalid_record, **account.errors.messages)
39
+ end
40
+
41
+ def link_owner_to_account(account:, user:, **)
42
+ Member.create!(account:, user:, role: :owner)
43
+
44
+ Continue()
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,79 @@
1
+ # frozen_string_literal: true
2
+
3
+ class ApplicationService
4
+ Error = ::Class.new(::StandardError)
5
+
6
+ class Input
7
+ def self.inherited(subclass)
8
+ subclass.include ::ActiveModel::API
9
+ subclass.include ::ActiveModel::Attributes
10
+ subclass.include ::ActiveModel::Dirty
11
+ subclass.include ::ActiveModel::Validations::Callbacks
12
+ end
13
+ end
14
+
15
+ class << self
16
+ def input=(klass)
17
+ const_defined?(:Input, false) and raise ArgumentError, "#{self}::Input class already defined"
18
+
19
+ unless klass.is_a?(::Class) && klass < Input
20
+ raise ArgumentError, 'must be a ApplicationService::Input subclass'
21
+ end
22
+
23
+ const_set(:Input, klass)
24
+ end
25
+
26
+ def input(&block)
27
+ return const_get(:Input, false) if const_defined?(:Input, false)
28
+
29
+ klass = ::Class.new(Input)
30
+ klass.class_eval(&block)
31
+
32
+ self.input = klass
33
+ end
34
+
35
+ def inherited(subclass)
36
+ subclass.include ::BCDD::Context.mixin(config: { addon: { continue: true } })
37
+ end
38
+
39
+ def call(arg)
40
+ new(input.new(arg)).call!
41
+ end
42
+ end
43
+
44
+ private_class_method :new
45
+
46
+ attr_reader :input
47
+
48
+ def initialize(input)
49
+ @input = input
50
+ end
51
+
52
+ def call!
53
+ ::BCDD::Result.event_logs(name: self.class.name) do
54
+ if input.invalid?
55
+ Failure(:invalid_input, input: input)
56
+ else
57
+ call(input.attributes.deep_symbolize_keys)
58
+ end
59
+ end
60
+ end
61
+
62
+ def call(attributes)
63
+ raise Error, 'must be implemented in a subclass'
64
+ end
65
+
66
+ private
67
+
68
+ def rollback_on_failure(model: ::ActiveRecord::Base)
69
+ result = nil
70
+
71
+ model.transaction do
72
+ result = yield
73
+
74
+ raise ::ActiveRecord::Rollback if result.failure?
75
+ end
76
+
77
+ result
78
+ end
79
+ end
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ class User
4
+ class Creation < ApplicationService
5
+ input do
6
+ attribute :uuid, :string, default: -> { ::SecureRandom.uuid }
7
+ attribute :name, :string
8
+ attribute :email, :string
9
+ attribute :password, :string
10
+ attribute :password_confirmation, :string
11
+
12
+ before_validation do |input|
13
+ input.uuid = input.uuid.strip.downcase
14
+ input.name = input.name.strip.gsub(/\s+/, ' ')
15
+ input.email = input.email.strip.downcase
16
+ end
17
+
18
+ validates :uuid, presence: true, format: { with: /\A[0-9a-f]{8}-([0-9a-f]{4}-){3}[0-9a-f]{12}\z/i }
19
+ validates :name, presence: true
20
+ validates :email, presence: true, format: { with: ::URI::MailTo::EMAIL_REGEXP }
21
+ validates :password, :password_confirmation, presence: true
22
+ end
23
+
24
+ def call(attributes)
25
+ Given(attributes)
26
+ .and_then(:validate_email_uniqueness)
27
+ .then { |result|
28
+ rollback_on_failure {
29
+ result
30
+ .and_then(:create_user)
31
+ .and_then(:create_user_token)
32
+ }
33
+ }
34
+ .and_expose(:user_created, %i[user token])
35
+ end
36
+
37
+ private
38
+
39
+ def validate_email_uniqueness(email:, **)
40
+ ::User.exists?(email:) ? Failure(:email_already_taken) : Continue()
41
+ end
42
+
43
+ def create_user(uuid:, name:, email:, password:, password_confirmation:)
44
+ user = ::User.create(uuid:, name:, email:, password:, password_confirmation:)
45
+
46
+ user.persisted? ? Continue(user:) : Failure(:invalid_record, **user.errors.messages)
47
+ end
48
+
49
+ def create_user_token(user:, **)
50
+ Token::Creation.call(user: user).handle do |on|
51
+ on.success { |output| Continue(token: output[:token]) }
52
+ on.failure { raise 'Token creation failed' }
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ class User::Token
4
+ class Creation < ApplicationService
5
+ input do
6
+ attribute :user
7
+ attribute :executed_at, :time, default: -> { ::Time.current }
8
+
9
+ validates :user, presence: true, type: ::User
10
+ validates :executed_at, presence: true
11
+ end
12
+
13
+ def call(attributes)
14
+ Given(attributes)
15
+ .and_then(:validate_token_existence)
16
+ .and_then(:create_token)
17
+ .and_expose(:token_created, %i[token])
18
+ end
19
+
20
+ private
21
+
22
+ def validate_token_existence(user:, **)
23
+ user.token.nil? ? Continue() : Failure(:token_already_exists)
24
+ end
25
+
26
+ def create_token(user:, executed_at:, **)
27
+ token = user.create_token(
28
+ access_token: ::SecureRandom.hex(24),
29
+ refresh_token: ::SecureRandom.hex(24),
30
+ access_token_expires_at: executed_at + 15.days,
31
+ refresh_token_expires_at: executed_at + 30.days
32
+ )
33
+
34
+ token.persisted? ? Continue(token:) : Failure(:token_creation_failed, **token.errors.messages)
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/inline'
4
+
5
+ $LOAD_PATH.unshift(__dir__)
6
+
7
+ gemfile do
8
+ source 'https://rubygems.org'
9
+
10
+ gem 'sqlite3', '~> 1.7'
11
+ gem 'bcrypt', '~> 3.1.20'
12
+ gem 'activerecord', '~> 7.1', '>= 7.1.3', require: 'active_record'
13
+ gem 'type_validator'
14
+ gem 'bcdd-result', path: '../../'
15
+ end
16
+
17
+ require 'active_support/all'
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ BCDD::Result.config.then do |config|
4
+ config.addon.enable!(:continue)
5
+
6
+ config.pattern_matching.disable!(:nil_as_valid_value_checking)
7
+
8
+ # config.feature.disable!(:expectations) if Rails.env.production?
9
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/inline'
4
+
5
+ $LOAD_PATH.unshift(__dir__)
6
+
7
+ require_relative 'config/boot'
8
+ require_relative 'config/initializers/bcdd'
9
+
10
+ require 'db/setup'
11
+
12
+ require 'app/models/account'
13
+ require 'app/models/account/member'
14
+ require 'app/models/user'
15
+ require 'app/models/user/token'
16
+
17
+ require 'app/services/application_service'
18
+ require 'app/services/account/owner_creation'
19
+ require 'app/services/user/token/creation'
20
+ require 'app/services/user/creation'
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_support/all'
4
+
5
+ ActiveRecord::Base.establish_connection(
6
+ host: 'localhost',
7
+ adapter: 'sqlite3',
8
+ database: ':memory:'
9
+ )
10
+
11
+ ActiveRecord::Schema.define do
12
+ suppress_messages do
13
+ create_table :accounts do |t|
14
+ t.string :uuid, null: false, index: {unique: true}
15
+
16
+ t.timestamps
17
+ end
18
+
19
+ create_table :users do |t|
20
+ t.string :uuid, null: false, index: {unique: true}
21
+ t.string :name, null: false
22
+ t.string :email, null: false, index: {unique: true}
23
+ t.string :password_digest, null: false
24
+
25
+ t.timestamps
26
+ end
27
+
28
+ create_table :user_tokens do |t|
29
+ t.belongs_to :user, null: false, foreign_key: true, index: true
30
+ t.string :access_token, null: false
31
+ t.string :refresh_token, null: false
32
+ t.datetime :access_token_expires_at, null: false
33
+ t.datetime :refresh_token_expires_at, null: false
34
+
35
+ t.timestamps
36
+ end
37
+
38
+ create_table :account_members do |t|
39
+ t.integer :role, null: false, default: 0
40
+ t.belongs_to :user, null: false, foreign_key: true, index: true
41
+ t.belongs_to :account, null: false, foreign_key: true, index: true
42
+
43
+ t.timestamps
44
+
45
+ t.index %i[account_id role], unique: true, where: "(role = 0)"
46
+ t.index %i[account_id user_id], unique: true
47
+ end
48
+ end
49
+ end
@@ -7,7 +7,7 @@ end
7
7
 
8
8
  # Usage:
9
9
  #
10
- # rake DISABLE_TRANSITIONS=t
10
+ # rake DISABLE_EVENT_LOGS=t
11
11
  # rake DISABLE_LISTENER=t
12
12
  #
13
13
  # rake HIDE_GIVEN_AND_CONTINUE=t
@@ -17,18 +17,18 @@ end
17
17
  # rake BREAK_USER_TOKEN_CREATION=t
18
18
  #
19
19
  # rake BREAK_ACCOUNT_CREATION=t HIDE_GIVEN_AND_CONTINUE=t
20
- task default: %i[bcdd_result_transitions]
20
+ task default: %i[bcdd_result_event_logs]
21
21
 
22
22
  task :config do
23
23
  require_relative 'config'
24
24
  end
25
25
 
26
26
  desc 'creates an account and an owner user through BCDD::Result'
27
- task bcdd_result_transitions: %i[config] do
27
+ task bcdd_result_event_logs: %i[config] do
28
28
  BCDD::Result.configuration do |config|
29
- config.feature.disable!(:transitions) if ENV['DISABLE_TRANSITIONS']
29
+ config.feature.disable!(:event_logs) if ENV['DISABLE_EVENT_LOGS']
30
30
 
31
- config.transitions.listener = SingleTransitionsListener unless ENV['DISABLE_LISTENER']
31
+ config.event_logs.listener = SingleEventLogsListener unless ENV['DISABLE_LISTENER']
32
32
  end
33
33
 
34
34
  result = nil