solid-process 0.2.0 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d4b6ab233b8f225123d7a74114c281011f94f7a186faeaee54b3d25e9ba51c2e
4
- data.tar.gz: 57a1de84e07d74971640ce4c03b1e12812d1da7765eb83b6ebb460e92972d1bb
3
+ metadata.gz: 973ae538a96362750bac5c31245c2e8390144c4d67688940fa8b28f2ee27e538
4
+ data.tar.gz: 057c4bbbd8e2c4fc955c548a714fdb7fbe4396e1b8d624dc7702cc4750e929fa
5
5
  SHA512:
6
- metadata.gz: 378aff92aaacc388198e4c72a5ce0fb72df788b1dd7763d2b0f11024629c8988d95e4e52015393dddd4cfbd1116539f1c6f5e1a541422e296117a27e7a7e778b
7
- data.tar.gz: 0040c63c885ff1177dd11c864d208a64c2b46dec1bee9bf71ea2e7df5ac2340668ac8e524c848c30b736457c3a64051e0084e01c3d401cf3bbaf1293959e6764
6
+ metadata.gz: 20c3a43c503dfc26560ca25cb20a3d4d53f2014de3f9883bfbd872e7759881fc636da985f335d53f9c9c81b730b797f9759649543b49644ddf2e774222a4d2cc
7
+ data.tar.gz: 3802c1bab8d942f351099e0510f9cfae3d86b110508cea3d96b61c4bd4eb5953c249b2280d4565bc3b02990744d908f229267edffc88eab38100664bebe83959
data/CHANGELOG.md CHANGED
@@ -1,7 +1,88 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [0.4.0] - 2024-06-23
4
+
5
+ ### Added
6
+
7
+ - Add `Solid::Process.configuration(freeze: true)`.
8
+
9
+ - Add `Solid::Process::EventLogs::BasicLoggerListener`.
10
+
11
+ - Add `Solid::Process::BacktraceCleaner`.
12
+
13
+ - Add `Solid::Process.configure` as an alias to `Solid::Process.configuration`.
14
+
15
+ - Add `Solid::Value` to allow defining a value object.
16
+
17
+ - `Solid::Model`
18
+ - Add `after_initialize` callback.
19
+ - Add `#[]` to access instance attributes.
20
+
21
+ - `solid/validators`
22
+ - Add `id_validator`, to check if the value is a positive integer or a string that represents a positive integer.
23
+ - Add `is_validator`, to check if the value satisfy the given predicate methods.
24
+
25
+ ### Changed
26
+
27
+ - Replace the usage of `deep_symbolize_keys` with `symbolize_keys` to perform the call more efficiently.
28
+
29
+ - Change `email_validator` and `uuid_validator` to use `I18n` messages.
30
+
31
+ - Relax `ActiveModel` dependency to `>= 6.0`.
32
+
33
+ ### Removed
34
+
35
+ - Remove some validators:
36
+ - `bool_validator`
37
+ - `is_a_validator`
38
+ - `persisted_validator`
39
+
40
+ ## [0.3.0] - 2024-04-14
41
+
42
+ ### Changed
43
+
44
+ - Replace `bcdd-result` gem `solid-result` gem. This change removes the constant aliases `Solid::Output` and `Solid::Result`, as they are no longer needed.
45
+
3
46
  ## [0.2.0] - 2024-03-18
4
47
 
48
+ ### Added
49
+
50
+ - Add `Solid::Process#success?`, `Solid::Process#failure?` that delegate to `Solid::Process#output.success?` and `Solid::Process#output.failure?`.
51
+
52
+ - Add `Solid::Process#with` method to create a new process instance with new dependencies.
53
+
54
+ - Add `Solid::Process#new` method. It's similar to `Solid::Process#with` but does not require passing the dependencies. If nothing is passed, it will use the same dependencies.
55
+
56
+ - Add `rescue_from` (from `::ActiveSupport::Rescuable`) method to `Solid::Process` to rescue exceptions and return a `Solid::Output`.
57
+
58
+ - Add `Solid::Process.config` and `Solid::Process.configuration` methods to define a configuration for all processes.
59
+
60
+ ### Changed
61
+
62
+ - Move `Solid::Input` features to `Solid::Model` module. This change does not promote breaking changes.
63
+
5
64
  ## [0.1.0] - 2024-03-16
6
65
 
7
- - Initial release
66
+ ### Added
67
+
68
+ - Add `Solid::Model` module to define a model with attributes and validations.
69
+ - It includes `ActiveModel::Api`, `ActiveModel::Access`, `ActiveModel::Attributes`, `ActiveModel::Dirty`, `ActiveModel::Validations::Callbacks`.
70
+
71
+ - Add `Solid::Input` class (which includes `Solid::Model`) to define an input object with attributes and validations.
72
+
73
+ - Add `Solid::Output` and `Solid::Result` as constant aliases of `BCDD::Context`.
74
+
75
+ - Add `Solid::Success()` and `Solid::Failure()` methods to create `BCDD::Context` instances.
76
+
77
+ - Add `Solid::Process` class to be inherited and define a process with inputs, outputs, and steps.
78
+
79
+ - Add several ActiveModel validations to be used in `Solid::Model` and `Solid::Input` classes.
80
+ - `bool_validator`
81
+ - `email_validator`
82
+ - `instance_of_validator`
83
+ - `is_a_validator`
84
+ - `kind_of_validator`
85
+ - `persisted_validator`
86
+ - `respond_to_validator`
87
+ - `singleton_validator`
88
+ - `uuid_validator`
data/README.md CHANGED
@@ -1,9 +1,9 @@
1
1
  <p align="center">
2
- <h1 align="center" id="-solidprocess">🚄 Solid::Process</h1>
3
- <p align="center"><i>Ruby on Rails + Business Processes</i></p>
2
+ <h1 align="center" id="-solidprocess">⚛️ Solid::Process</h1>
3
+ <p align="center"><i>Write business logic for Ruby/Rails that scales.</i></p>
4
4
  <p align="center">
5
- <a href="https://codeclimate.com/github/serradura/solid-process/maintainability"><img src="https://api.codeclimate.com/v1/badges/643a53e99bb591321c9f/maintainability" /></a>
6
- <a href="https://codeclimate.com/github/serradura/solid-process/test_coverage"><img src="https://api.codeclimate.com/v1/badges/643a53e99bb591321c9f/test_coverage" /></a>
5
+ <a href="https://codeclimate.com/github/solid-process/solid-process/maintainability"><img src="https://api.codeclimate.com/v1/badges/643a53e99bb591321c9f/maintainability" /></a>
6
+ <a href="https://codeclimate.com/github/solid-process/solid-process/test_coverage"><img src="https://api.codeclimate.com/v1/badges/643a53e99bb591321c9f/test_coverage" /></a>
7
7
  <img src="https://img.shields.io/badge/Ruby%20%3E%3D%202.7%2C%20%3C%3D%20Head-ruby.svg?colorA=444&colorB=333" alt="Ruby">
8
8
  <img src="https://img.shields.io/badge/Rails%20%3E%3D%206.0%2C%20%3C%3D%20Edge-rails.svg?colorA=444&colorB=333" alt="Rails">
9
9
  </p>
@@ -22,6 +22,26 @@ This library is tested against:
22
22
  | 3.3 | ✅ | ✅ | ✅ | ✅ | ✅ |
23
23
  | Head | | | | ✅ | ✅ |
24
24
 
25
+ ## Introduction
26
+
27
+ `solid-process` is a Ruby/Rails library designed to encapsulate business logic into manageable processes. It simplifies writing, testing, maintaining, and evolving your code, ensuring it remains clear and approachable as your application scales.
28
+
29
+ **Key Objectives:**
30
+
31
+ 1. **Seamless Rails integration:** Designed to fully complement the Ruby on Rails framework, this library integrates smoothly without conflicting with existing Rails conventions and capabilities.
32
+
33
+ 2. **Support progressive mastery:** Offers an intuitive entry point for novices while providing robust, advanced features that cater to experienced developers.
34
+
35
+ 3. **Promote conceptual integrity and rapid onboarding:** By maintaining a consistent design philosophy, `solid-process` reduces the learning curve for new developers, allowing them to contribute more effectively and quickly.
36
+
37
+ 4. **Minimize technical debt:** Facilitate smoother transitions and updates as your application expands and your team size increases.
38
+
39
+ 5. **Enhanced observability:** Equipped with sophisticated instrumentation mechanisms, the library enables detailed logging and tracing without compromising clarity, even when processes are nested. This ensures the code is both easy to understand and to observe.
40
+
41
+ ### Examples
42
+
43
+ Checkout the [solid-rails-app](https://github.com/solid-process/solid-rails-app) for a full example of how to use `solid-process` in a Rails application. Or take a look at the [examples](examples) folder in this repository.
44
+
25
45
  ## Installation
26
46
 
27
47
  Add this line to your application's Gemfile:
@@ -54,7 +74,7 @@ To install this gem onto your local machine, run `bundle exec rake install`. To
54
74
 
55
75
  ## Contributing
56
76
 
57
- Bug reports and pull requests are welcome on GitHub at https://github.com/serradura/solid-process. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/serradura/solid-process/blob/master/CODE_OF_CONDUCT.md).
77
+ Bug reports and pull requests are welcome on GitHub at https://github.com/solid-process/solid-process. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/solid-process/solid-process/blob/main/CODE_OF_CONDUCT.md).
58
78
 
59
79
  ## License
60
80
 
@@ -62,4 +82,4 @@ The gem is available as open source under the terms of the [MIT License](https:/
62
82
 
63
83
  ## Code of Conduct
64
84
 
65
- Everyone interacting in the Solid::Process project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/serradura/solid-process/blob/master/CODE_OF_CONDUCT.md).
85
+ Everyone interacting in the Solid::Process project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/solid-process/solid-process/blob/main/CODE_OF_CONDUCT.md).
data/lib/solid/input.rb CHANGED
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "model"
4
-
5
3
  class Solid::Input
6
4
  include ::Solid::Model
7
5
  end
data/lib/solid/model.rb CHANGED
@@ -5,6 +5,16 @@ require_relative "model/access"
5
5
  module Solid::Model
6
6
  extend ::ActiveSupport::Concern
7
7
 
8
+ module ClassMethods
9
+ def [](...)
10
+ new(...)
11
+ end
12
+
13
+ def inherited(subclass)
14
+ subclass.include(::Solid::Model)
15
+ end
16
+ end
17
+
8
18
  included do
9
19
  include ::ActiveModel.const_defined?(:Api, false) ? ::ActiveModel::Api : ::ActiveModel::Model
10
20
  include ::ActiveModel.const_defined?(:Access, false) ? ::ActiveModel::Access : ::Solid::Model::Access
@@ -12,19 +22,21 @@ module Solid::Model
12
22
  include ::ActiveModel::Attributes
13
23
  include ::ActiveModel::Dirty
14
24
  include ::ActiveModel::Validations::Callbacks
25
+
26
+ extend ActiveModel::Callbacks
27
+
28
+ define_model_callbacks :initialize, only: :after
15
29
  end
16
30
 
17
- module ClassMethods
18
- def [](...)
19
- new(...)
20
- end
31
+ def initialize(...)
32
+ super
21
33
 
22
- def inherited(subclass)
23
- subclass.include(::Solid::Model)
24
- end
34
+ run_callbacks(:initialize)
25
35
  end
26
36
 
27
37
  def inspect
28
38
  "#<#{self.class.name} #{attributes.map { |k, v| "#{k}=#{v.inspect}" }.join(" ")}>"
29
39
  end
40
+
41
+ alias_method :[], :public_send
30
42
  end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "solid/result"
4
+
5
+ module Solid
6
+ def self.Success(...)
7
+ ::Solid::Output::Success(...)
8
+ end
9
+
10
+ def self.Failure(...)
11
+ ::Solid::Output::Failure(...)
12
+ end
13
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Solid::Process::BacktraceCleaner < ActiveSupport::BacktraceCleaner
4
+ def initialize
5
+ super
6
+
7
+ add_blocks_silencer
8
+ end
9
+
10
+ private
11
+
12
+ BLOCKS_PATTERN = /in [`']block in|in `then'|internal:kernel|block \(\d+ levels?\) in/.freeze
13
+
14
+ def add_blocks_silencer
15
+ add_silencer { |line| line.match?(BLOCKS_PATTERN) }
16
+ end
17
+ end
@@ -9,14 +9,14 @@ class Solid::Process
9
9
  self.input = arg
10
10
 
11
11
  run_callbacks(:call) do
12
- ::BCDD::Result.event_logs(name: self.class.name) do
12
+ ::Solid::Result.event_logs(name: self.class.name) do
13
13
  self.output =
14
14
  if dependencies&.invalid?
15
15
  Failure(:invalid_dependencies, dependencies: dependencies)
16
16
  elsif input.invalid?
17
17
  Failure(:invalid_input, input: input)
18
18
  else
19
- super(input.attributes.deep_symbolize_keys)
19
+ super(input.attributes.symbolize_keys)
20
20
  end
21
21
  rescue ::Exception => exception
22
22
  rescue_with_handler(exception) || raise
@@ -0,0 +1,78 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Solid::Process::EventLogs::BasicLoggerListener
4
+ include ActiveSupport::Configurable
5
+ include Solid::Result::EventLogs::Listener
6
+
7
+ config_accessor(:logger, :backtrace_cleaner)
8
+
9
+ self.logger = ActiveSupport::Logger.new($stdout)
10
+ self.backtrace_cleaner = Solid::Process::BacktraceCleaner.new
11
+
12
+ module MessagesNesting
13
+ MAP_STEP_METHOD = lambda do |record_result|
14
+ kind, type, value = record_result.values_at(:kind, :type, :value)
15
+
16
+ value_keys = "#{value.keys.join(":, ")}:"
17
+ value_keys = "" if value_keys == ":"
18
+
19
+ case type
20
+ when :_given_ then "Given(#{value_keys})"
21
+ when :_continue_ then "Continue(#{value_keys})"
22
+ else "#{kind.capitalize}(:#{type}, #{value_keys})"
23
+ end
24
+ end
25
+
26
+ MAP_STEP_MESSAGE = lambda do |record|
27
+ step = MAP_STEP_METHOD[record[:result]]
28
+
29
+ method_name = record.dig(:and_then, :method_name)
30
+
31
+ " * #{step} from method: #{method_name}".chomp("from method: ").chomp(" ")
32
+ end
33
+
34
+ MAP_IDS_WITH_MESSAGES = lambda do |records|
35
+ process_ids = []
36
+
37
+ records.each_with_object([]) do |record, messages|
38
+ current = record[:current]
39
+
40
+ current_id = current[:id]
41
+
42
+ unless process_ids.include?(current_id)
43
+ process_ids << current_id
44
+
45
+ id, name, desc = current.values_at(:id, :name, :desc)
46
+
47
+ messages << [current_id, "##{id} #{name} - #{desc}".chomp("- ").chomp(" ")]
48
+ end
49
+
50
+ messages << [current_id, MAP_STEP_MESSAGE[record]]
51
+ end
52
+ end
53
+
54
+ def self.map(event_logs)
55
+ ids_level_parent = event_logs.dig(:metadata, :ids, :level_parent)
56
+
57
+ messages = MAP_IDS_WITH_MESSAGES[event_logs[:records]]
58
+
59
+ messages.map { |(id, msg)| "#{" " * ids_level_parent[id].first}#{msg}" }
60
+ end
61
+ end
62
+
63
+ def on_finish(event_logs:)
64
+ messages = MessagesNesting.map(event_logs)
65
+
66
+ logger.info messages.join("\n")
67
+ end
68
+
69
+ def before_interruption(exception:, event_logs:)
70
+ messages = MessagesNesting.map(event_logs)
71
+
72
+ logger.info messages.join("\n")
73
+
74
+ cleaned_backtrace = backtrace_cleaner.clean(exception.backtrace).join("\n ")
75
+
76
+ logger.error "\nException:\n #{exception.message} (#{exception.class})\n\nBacktrace:\n #{cleaned_backtrace}"
77
+ end
78
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Solid::Process
4
+ module EventLogs
5
+ require_relative "event_logs/basic_logger_listener"
6
+ end
7
+ end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Solid
4
4
  class Process
5
- VERSION = "0.2.0"
5
+ VERSION = "0.4.0"
6
6
  end
7
7
  end
data/lib/solid/process.rb CHANGED
@@ -2,11 +2,12 @@
2
2
 
3
3
  require "active_support/all"
4
4
  require "active_model"
5
- require "bcdd/result"
6
5
 
7
6
  module Solid
8
- require "solid/input"
9
- require "solid/result"
7
+ require_relative "model"
8
+ require_relative "value"
9
+ require_relative "input"
10
+ require_relative "output"
10
11
 
11
12
  class Process
12
13
  require "solid/process/version"
@@ -16,12 +17,14 @@ module Solid
16
17
  require "solid/process/callbacks"
17
18
  require "solid/process/class_methods"
18
19
  require "solid/process/active_record"
20
+ require "solid/process/backtrace_cleaner"
21
+ require "solid/process/event_logs"
19
22
 
20
23
  extend ClassMethods
21
24
 
22
25
  include Callbacks
23
26
  include ::ActiveSupport::Rescuable
24
- include ::BCDD::Context.mixin(config: {addon: {continue: true}})
27
+ include ::Solid::Output.mixin(config: {addon: {continue: true}})
25
28
 
26
29
  def self.inherited(subclass)
27
30
  super
@@ -33,15 +36,17 @@ module Solid
33
36
  new.call(arg)
34
37
  end
35
38
 
36
- def self.configuration(&block)
39
+ def self.config
40
+ Config.instance
41
+ end
42
+
43
+ def self.configuration(freeze: true, &block)
37
44
  yield config
38
45
 
39
- config.freeze
46
+ config.tap { _1.freeze if freeze }
40
47
  end
41
48
 
42
- def self.config
43
- Config.instance
44
- end
49
+ singleton_class.alias_method :configure, :configuration
45
50
 
46
51
  attr_reader :output, :input, :dependencies
47
52
 
@@ -120,7 +125,7 @@ module Solid
120
125
  def output=(result)
121
126
  output_already_set! unless output.nil?
122
127
 
123
- raise Error, "The result #{result.inspect} must be a BCDD::Context." unless result.is_a?(::BCDD::Context)
128
+ raise Error, "The result #{result.inspect} must be a Solid::Output." unless result.is_a?(::Solid::Output)
124
129
 
125
130
  @output = result
126
131
  end
@@ -1,9 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class EmailValidator < ActiveModel::EachValidator
4
- def validate_each(obj, attribute, value)
4
+ def validate_each(model, attribute, value)
5
+ return model.errors.add(attribute, :blank, **options) if value.blank?
6
+
5
7
  return if value.is_a?(String) && URI::MailTo::EMAIL_REGEXP.match?(value)
6
8
 
7
- obj.errors.add attribute, (options[:message] || "is not an email")
9
+ model.errors.add(attribute, :invalid, **options)
8
10
  end
9
11
  end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "solid/validators"
4
+
5
+ class IdValidator < ActiveModel::EachValidator
6
+ OPTIONS = {only_integer: true, greater_than: 0}.freeze
7
+
8
+ def validate_each(model, attribute, value)
9
+ opts = OPTIONS.merge(options.except(*OPTIONS.keys))
10
+
11
+ opts[:attributes] = attribute
12
+
13
+ ::ActiveModel::Validations::NumericalityValidator.new(opts).validate_each(model, attribute, value)
14
+ end
15
+
16
+ private_constant :OPTIONS
17
+ end
@@ -1,13 +1,15 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "solid/validators"
4
+
3
5
  class InstanceOfValidator < ActiveModel::EachValidator
4
- def validate_each(obj, attribute, value)
6
+ def validate_each(model, attribute, value)
5
7
  with_option = Array.wrap(options[:with] || options[:in])
6
8
 
7
9
  return if with_option.any? { |type| value.instance_of?(type) }
8
10
 
9
- expectation = with_option.map(&:name).join(" | ")
11
+ message = "is not an instance of #{with_option.map(&:name).join(" | ")}"
10
12
 
11
- obj.errors.add(attribute, (options[:message] || "is not an instance of #{expectation}"))
13
+ Solid::Validators.add_error(model, attribute, message, options)
12
14
  end
13
15
  end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "solid/validators"
4
+
5
+ class IsValidator < ActiveModel::EachValidator
6
+ def validate_each(model, attribute, value)
7
+ with_option = Array.wrap(options[:with] || options[:in])
8
+
9
+ return if with_option.all? do |predicate|
10
+ raise ArgumentError, "expected a predicate method, got #{predicate.inspect}" unless predicate.end_with?("?")
11
+
12
+ value.try(predicate)
13
+ end
14
+
15
+ message = "does not satisfy the predicate#{"s" if with_option.size > 1}: #{with_option.join(" & ")}"
16
+
17
+ Solid::Validators.add_error(model, attribute, message, options)
18
+ end
19
+ end
@@ -1,13 +1,15 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "solid/validators"
4
+
3
5
  class KindOfValidator < ActiveModel::EachValidator
4
- def validate_each(obj, attribute, value)
6
+ def validate_each(model, attribute, value)
5
7
  with_option = Array.wrap(options[:with] || options[:in])
6
8
 
7
9
  return if with_option.any? { |type| value.is_a?(type) }
8
10
 
9
- expectation = with_option.map(&:name).join(" | ")
11
+ message = "is not a #{with_option.map(&:name).join(" | ")}"
10
12
 
11
- obj.errors.add(attribute, (options[:message] || "is not a #{expectation}"))
13
+ Solid::Validators.add_error(model, attribute, message, options)
12
14
  end
13
15
  end
@@ -1,13 +1,15 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "solid/validators"
4
+
3
5
  class RespondToValidator < ActiveModel::EachValidator
4
- def validate_each(obj, attribute, value)
6
+ def validate_each(model, attribute, value)
5
7
  with_option = Array.wrap(options[:with] || options[:in])
6
8
 
7
9
  return if with_option.all? { value.respond_to?(_1) }
8
10
 
9
- expectation = with_option.map(&:inspect).join(" & ")
11
+ message = "does not respond to #{with_option.map(&:inspect).join(" & ")}"
10
12
 
11
- obj.errors.add(attribute, (options[:message] || "does not respond to #{expectation}"))
13
+ Solid::Validators.add_error(model, attribute, message, options)
12
14
  end
13
15
  end
@@ -1,21 +1,21 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "solid/validators"
4
+
3
5
  class SingletonValidator < ActiveModel::EachValidator
4
- def validate_each(obj, attribute, value)
6
+ def validate_each(model, attribute, value)
5
7
  with_option = Array.wrap(options[:with] || options[:in])
6
8
 
7
- unless value.is_a?(Module)
8
- return obj.errors.add(attribute, options[:message] || "is not a class or module")
9
- end
9
+ return model.errors.add(attribute, options[:message] || "is not a class or module") unless value.is_a?(Module)
10
10
 
11
- is_valid = with_option.any? do |type|
12
- type.is_a?(Module) or raise ArgumentError, "#{type.inspect} is not a class or module"
11
+ return if with_option.any? do |type|
12
+ raise ArgumentError, "#{type.inspect} is not a class or module" unless type.is_a?(Module)
13
13
 
14
14
  value == type || (value < type || value.is_a?(type))
15
15
  end
16
16
 
17
- expectation = with_option.map(&:name).join(" | ")
17
+ message = "is not #{with_option.map(&:name).join(" | ")}"
18
18
 
19
- is_valid or obj.errors.add(attribute, (options[:message] || "is not #{expectation}"))
19
+ Solid::Validators.add_error(model, attribute, message, options)
20
20
  end
21
21
  end
@@ -5,16 +5,16 @@ class UuidValidator < ActiveModel::EachValidator
5
5
  CASE_SENSITIVE = /\A#{PATTERN}\z/.freeze
6
6
  CASE_INSENSITIVE = /\A#{PATTERN}\z/i.freeze
7
7
 
8
- def validate_each(obj, attribute, value)
8
+ def validate_each(model, attribute, value)
9
9
  case_sensitive = options.fetch(:case_sensitive, true)
10
10
 
11
+ return model.errors.add(attribute, :blank, **options) if value.blank?
12
+
11
13
  regexp = case_sensitive ? CASE_SENSITIVE : CASE_INSENSITIVE
12
14
 
13
15
  return if value.is_a?(String) && value.match?(regexp)
14
16
 
15
- message = options[:message] || "is not a valid UUID (case #{case_sensitive ? "sensitive" : "insensitive"})"
16
-
17
- obj.errors.add(attribute, message)
17
+ model.errors.add(attribute, :invalid, **options)
18
18
  end
19
19
 
20
20
  private_constant :PATTERN
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Solid
4
+ module Validators
5
+ def self.add_error(model, attribute, message, options)
6
+ if ActiveModel.const_defined?(:Error)
7
+ model.errors.add(attribute, **options.merge(message: message))
8
+ else
9
+ model.errors.add(attribute, options.fetch(:message, message))
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Solid::Value
4
+ module ClassMethods
5
+ UNDEFINED = ::Object.new
6
+
7
+ def new(value = UNDEFINED)
8
+ return value if value.is_a?(self)
9
+
10
+ UNDEFINED.equal?(value) ? super() : super(value: value)
11
+ end
12
+
13
+ def attribute(...)
14
+ super(:value, ...)
15
+ end
16
+
17
+ def validates(...)
18
+ super(:value, ...)
19
+ end
20
+ end
21
+
22
+ def self.included(subclass)
23
+ subclass.include Solid::Model
24
+ subclass.extend ClassMethods
25
+ subclass.attribute
26
+ end
27
+
28
+ def ==(other)
29
+ other.is_a?(self.class) && other.value == value
30
+ end
31
+
32
+ def hash
33
+ value.hash
34
+ end
35
+
36
+ def to_s
37
+ value.to_s
38
+ end
39
+
40
+ alias_method :eql?, :==
41
+ end
metadata CHANGED
@@ -1,29 +1,29 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: solid-process
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Rodrigo Serradura
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2024-03-19 00:00:00.000000000 Z
11
+ date: 2024-06-23 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
- name: bcdd-result
14
+ name: solid-result
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
17
  - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: '1.0'
19
+ version: '2.0'
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
- version: '1.0'
26
+ version: '2.0'
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: activemodel
29
29
  requirement: !ruby/object:Gem::Requirement
@@ -31,9 +31,6 @@ dependencies:
31
31
  - - ">="
32
32
  - !ruby/object:Gem::Version
33
33
  version: '6.0'
34
- - - "<"
35
- - !ruby/object:Gem::Version
36
- version: '8.0'
37
34
  type: :runtime
38
35
  prerelease: false
39
36
  version_requirements: !ruby/object:Gem::Requirement
@@ -41,9 +38,6 @@ dependencies:
41
38
  - - ">="
42
39
  - !ruby/object:Gem::Version
43
40
  version: '6.0'
44
- - - "<"
45
- - !ruby/object:Gem::Version
46
- version: '8.0'
47
41
  - !ruby/object:Gem::Dependency
48
42
  name: appraisal
49
43
  requirement: !ruby/object:Gem::Requirement
@@ -58,7 +52,7 @@ dependencies:
58
52
  - - "~>"
59
53
  - !ruby/object:Gem::Version
60
54
  version: '2.5'
61
- description: Ruby on Rails + Business Processes
55
+ description: Write business logic for Ruby/Rails that scales.
62
56
  email:
63
57
  - rodrigo.serradura@gmail.com
64
58
  executables: []
@@ -87,25 +81,28 @@ files:
87
81
  - lib/solid/input.rb
88
82
  - lib/solid/model.rb
89
83
  - lib/solid/model/access.rb
84
+ - lib/solid/output.rb
90
85
  - lib/solid/process.rb
91
86
  - lib/solid/process/active_record.rb
87
+ - lib/solid/process/backtrace_cleaner.rb
92
88
  - lib/solid/process/callbacks.rb
93
89
  - lib/solid/process/caller.rb
94
90
  - lib/solid/process/class_methods.rb
95
91
  - lib/solid/process/config.rb
96
92
  - lib/solid/process/error.rb
93
+ - lib/solid/process/event_logs.rb
94
+ - lib/solid/process/event_logs/basic_logger_listener.rb
97
95
  - lib/solid/process/version.rb
98
- - lib/solid/result.rb
99
- - lib/solid/validators/all.rb
100
- - lib/solid/validators/bool_validator.rb
96
+ - lib/solid/validators.rb
101
97
  - lib/solid/validators/email_validator.rb
98
+ - lib/solid/validators/id_validator.rb
102
99
  - lib/solid/validators/instance_of_validator.rb
103
- - lib/solid/validators/is_a_validator.rb
100
+ - lib/solid/validators/is_validator.rb
104
101
  - lib/solid/validators/kind_of_validator.rb
105
- - lib/solid/validators/persisted_validator.rb
106
102
  - lib/solid/validators/respond_to_validator.rb
107
103
  - lib/solid/validators/singleton_validator.rb
108
104
  - lib/solid/validators/uuid_validator.rb
105
+ - lib/solid/value.rb
109
106
  homepage: https://github.com/serradura/solid-process
110
107
  licenses:
111
108
  - MIT
@@ -129,8 +126,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
129
126
  - !ruby/object:Gem::Version
130
127
  version: '0'
131
128
  requirements: []
132
- rubygems_version: 3.1.6
129
+ rubygems_version: 3.5.10
133
130
  signing_key:
134
131
  specification_version: 4
135
- summary: Ruby on Rails + Business Processes
132
+ summary: Write business logic for Ruby/Rails that scales.
136
133
  test_files: []
data/lib/solid/result.rb DELETED
@@ -1,17 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Solid
4
- Output = ::BCDD::Context
5
-
6
- Result = ::BCDD::Context
7
- Success = Result::Success
8
- Failure = Result::Failure
9
-
10
- def self.Success(...)
11
- Result::Success(...)
12
- end
13
-
14
- def self.Failure(...)
15
- Result::Failure(...)
16
- end
17
- end
@@ -1,12 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative "instance_of_validator"
4
- require_relative "is_a_validator"
5
- require_relative "kind_of_validator"
6
- require_relative "respond_to_validator"
7
- require_relative "singleton_validator"
8
-
9
- require_relative "bool_validator"
10
- require_relative "email_validator"
11
- require_relative "persisted_validator"
12
- require_relative "uuid_validator"
@@ -1,9 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- class BoolValidator < ActiveModel::EachValidator
4
- def validate_each(obj, attribute, value)
5
- return if value == true || value == false
6
-
7
- obj.errors.add attribute, (options[:message] || "is not a boolean")
8
- end
9
- end
@@ -1,6 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative "kind_of_validator"
4
-
5
- class IsAValidator < KindOfValidator
6
- end
@@ -1,9 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- class PersistedValidator < ActiveModel::EachValidator
4
- def validate_each(record, attribute, value)
5
- return if (options[:allow_nil] && value.nil?) || value.try(:persisted?)
6
-
7
- record.errors.add(attribute, (options[:message] || "must be persisted"))
8
- end
9
- end