facio 0.1.11
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.
- checksums.yaml +7 -0
- data/.rubocop.yml +14 -0
- data/CHANGELOG.md +5 -0
- data/CODE_OF_CONDUCT.md +84 -0
- data/LICENSE.txt +21 -0
- data/README.md +143 -0
- data/Rakefile +8 -0
- data/facio.gemspec +50 -0
- data/lib/facio/active_model/associations.rb +51 -0
- data/lib/facio/active_model/type/model.rb +24 -0
- data/lib/facio/base_model.rb +9 -0
- data/lib/facio/concerns/callbacks.rb +37 -0
- data/lib/facio/concerns/execution.rb +21 -0
- data/lib/facio/concerns/service_context.rb +46 -0
- data/lib/facio/concerns/service_result.rb +42 -0
- data/lib/facio/concerns/transactional.rb +27 -0
- data/lib/facio/concerns/translations.rb +21 -0
- data/lib/facio/context.rb +25 -0
- data/lib/facio/context_failure.rb +12 -0
- data/lib/facio/error.rb +6 -0
- data/lib/facio/result.rb +4 -0
- data/lib/facio/service.rb +57 -0
- data/lib/facio/version.rb +5 -0
- data/lib/facio.rb +21 -0
- data/lib/generators/facio/USAGE +9 -0
- data/lib/generators/facio/service_generator.rb +17 -0
- data/lib/generators/facio/templates/context.rb +13 -0
- data/lib/generators/facio/templates/service.rb +13 -0
- data/lib/generators/facio/templates/service_test.rb +11 -0
- data/sig/mando.rbs +4 -0
- metadata +228 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 5f58925ad262f641e798bae5733d68b32ec51b1501386c9feced448901e8e551
|
|
4
|
+
data.tar.gz: c4c7e9d879b8c61783435acdaf60eb0c6c6c6aef56b115637c2173e8b8417a4d
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 40fae9340200e51cfbaab3543dd41cc5eb0e7909d41273eee70f5249f1cb3a505f6c3b9faa81092b780bc826569291e55357fc384abda2d5450c663e1dfaeb49
|
|
7
|
+
data.tar.gz: 1a89e4752315ca730bf34bcb3f6644360f66b103f4ad85edf3e3b1e9dee588abbfbe0fac4d9ba19b72550853a79c7a376bc77c82e74c63f690777062586d1eca
|
data/.rubocop.yml
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# https://www.fastruby.io/blog/ruby/code-quality/how-we-use-rubocop-and-standardrb.html
|
|
2
|
+
require:
|
|
3
|
+
- standard
|
|
4
|
+
|
|
5
|
+
inherit_gem:
|
|
6
|
+
standard: config/base.yml
|
|
7
|
+
|
|
8
|
+
AllCops:
|
|
9
|
+
TargetRubyVersion: 3.3
|
|
10
|
+
NewCops: enable
|
|
11
|
+
Exclude:
|
|
12
|
+
- node_modules/**/*
|
|
13
|
+
- public/**/*
|
|
14
|
+
- vendor/**/*
|
data/CHANGELOG.md
ADDED
data/CODE_OF_CONDUCT.md
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# Contributor Covenant Code of Conduct
|
|
2
|
+
|
|
3
|
+
## Our Pledge
|
|
4
|
+
|
|
5
|
+
We as members, contributors, and leaders pledge to make participation in our community a harassment-free experience for everyone, regardless of age, body size, visible or invisible disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation.
|
|
6
|
+
|
|
7
|
+
We pledge to act and interact in ways that contribute to an open, welcoming, diverse, inclusive, and healthy community.
|
|
8
|
+
|
|
9
|
+
## Our Standards
|
|
10
|
+
|
|
11
|
+
Examples of behavior that contributes to a positive environment for our community include:
|
|
12
|
+
|
|
13
|
+
* Demonstrating empathy and kindness toward other people
|
|
14
|
+
* Being respectful of differing opinions, viewpoints, and experiences
|
|
15
|
+
* Giving and gracefully accepting constructive feedback
|
|
16
|
+
* Accepting responsibility and apologizing to those affected by our mistakes, and learning from the experience
|
|
17
|
+
* Focusing on what is best not just for us as individuals, but for the overall community
|
|
18
|
+
|
|
19
|
+
Examples of unacceptable behavior include:
|
|
20
|
+
|
|
21
|
+
* The use of sexualized language or imagery, and sexual attention or
|
|
22
|
+
advances of any kind
|
|
23
|
+
* Trolling, insulting or derogatory comments, and personal or political attacks
|
|
24
|
+
* Public or private harassment
|
|
25
|
+
* Publishing others' private information, such as a physical or email
|
|
26
|
+
address, without their explicit permission
|
|
27
|
+
* Other conduct which could reasonably be considered inappropriate in a
|
|
28
|
+
professional setting
|
|
29
|
+
|
|
30
|
+
## Enforcement Responsibilities
|
|
31
|
+
|
|
32
|
+
Community leaders are responsible for clarifying and enforcing our standards of acceptable behavior and will take appropriate and fair corrective action in response to any behavior that they deem inappropriate, threatening, offensive, or harmful.
|
|
33
|
+
|
|
34
|
+
Community leaders have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, and will communicate reasons for moderation decisions when appropriate.
|
|
35
|
+
|
|
36
|
+
## Scope
|
|
37
|
+
|
|
38
|
+
This Code of Conduct applies within all community spaces, and also applies when an individual is officially representing the community in public spaces. Examples of representing our community include using an official e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event.
|
|
39
|
+
|
|
40
|
+
## Enforcement
|
|
41
|
+
|
|
42
|
+
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community leaders responsible for enforcement at tom@degrunt.nl. All complaints will be reviewed and investigated promptly and fairly.
|
|
43
|
+
|
|
44
|
+
All community leaders are obligated to respect the privacy and security of the reporter of any incident.
|
|
45
|
+
|
|
46
|
+
## Enforcement Guidelines
|
|
47
|
+
|
|
48
|
+
Community leaders will follow these Community Impact Guidelines in determining the consequences for any action they deem in violation of this Code of Conduct:
|
|
49
|
+
|
|
50
|
+
### 1. Correction
|
|
51
|
+
|
|
52
|
+
**Community Impact**: Use of inappropriate language or other behavior deemed unprofessional or unwelcome in the community.
|
|
53
|
+
|
|
54
|
+
**Consequence**: A private, written warning from community leaders, providing clarity around the nature of the violation and an explanation of why the behavior was inappropriate. A public apology may be requested.
|
|
55
|
+
|
|
56
|
+
### 2. Warning
|
|
57
|
+
|
|
58
|
+
**Community Impact**: A violation through a single incident or series of actions.
|
|
59
|
+
|
|
60
|
+
**Consequence**: A warning with consequences for continued behavior. No interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, for a specified period of time. This includes avoiding interactions in community spaces as well as external channels like social media. Violating these terms may lead to a temporary or permanent ban.
|
|
61
|
+
|
|
62
|
+
### 3. Temporary Ban
|
|
63
|
+
|
|
64
|
+
**Community Impact**: A serious violation of community standards, including sustained inappropriate behavior.
|
|
65
|
+
|
|
66
|
+
**Consequence**: A temporary ban from any sort of interaction or public communication with the community for a specified period of time. No public or private interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, is allowed during this period. Violating these terms may lead to a permanent ban.
|
|
67
|
+
|
|
68
|
+
### 4. Permanent Ban
|
|
69
|
+
|
|
70
|
+
**Community Impact**: Demonstrating a pattern of violation of community standards, including sustained inappropriate behavior, harassment of an individual, or aggression toward or disparagement of classes of individuals.
|
|
71
|
+
|
|
72
|
+
**Consequence**: A permanent ban from any sort of public interaction within the community.
|
|
73
|
+
|
|
74
|
+
## Attribution
|
|
75
|
+
|
|
76
|
+
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 2.0,
|
|
77
|
+
available at https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
|
|
78
|
+
|
|
79
|
+
Community Impact Guidelines were inspired by [Mozilla's code of conduct enforcement ladder](https://github.com/mozilla/diversity).
|
|
80
|
+
|
|
81
|
+
[homepage]: https://www.contributor-covenant.org
|
|
82
|
+
|
|
83
|
+
For answers to common questions about this code of conduct, see the FAQ at
|
|
84
|
+
https://www.contributor-covenant.org/faq. Translations are available at https://www.contributor-covenant.org/translations.
|
data/LICENSE.txt
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 Tom de Grunt
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
|
13
|
+
all copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
# Facio
|
|
2
|
+
|
|
3
|
+
Command pattern for Ruby (on Rails). From Wikipedia:
|
|
4
|
+
|
|
5
|
+
> In object-oriented programming, the command pattern is a behavioral design pattern in which an object is used to encapsulate all information needed to perform an action or trigger an event at a later time. This information includes the method name, the object that owns the method and values for the method parameters.
|
|
6
|
+
|
|
7
|
+
It basically allows you to encapsulate bits of code for a specific purpose. It's also nicely explained by [refactoring.guru](https://refactoring.guru/design-patterns/command)
|
|
8
|
+
|
|
9
|
+
## Installation
|
|
10
|
+
|
|
11
|
+
Install the gem and add to the application's Gemfile by executing:
|
|
12
|
+
|
|
13
|
+
$ bundle add facio
|
|
14
|
+
|
|
15
|
+
If bundler is not being used to manage dependencies, install the gem by executing:
|
|
16
|
+
|
|
17
|
+
$ gem install facio
|
|
18
|
+
|
|
19
|
+
## Generator
|
|
20
|
+
|
|
21
|
+
Facio comes with a handy generator to generate a service and context:
|
|
22
|
+
|
|
23
|
+
```shell
|
|
24
|
+
rails g facio:service module/my_service
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
this will do the following:
|
|
28
|
+
|
|
29
|
+
```
|
|
30
|
+
create app/services/module/my_service_service.rb
|
|
31
|
+
create app/services/module/my_service_context.rb
|
|
32
|
+
create test/services/module/my_service_test.rb
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Usage
|
|
36
|
+
|
|
37
|
+
### Service
|
|
38
|
+
|
|
39
|
+
Every service has a context, which encapsulates all the information to perform the action. The perform method on the service should be your main point of entry.
|
|
40
|
+
|
|
41
|
+
```ruby
|
|
42
|
+
# simple_service.rb
|
|
43
|
+
class SimpleService < Facio::Service
|
|
44
|
+
context do
|
|
45
|
+
attribute :value
|
|
46
|
+
attribute :result
|
|
47
|
+
end
|
|
48
|
+
def perform
|
|
49
|
+
context.result = context.value.to_s.reverse
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# You can perform the service as follows:
|
|
54
|
+
SimpleService.perform(value: "no lemon, no melon").result
|
|
55
|
+
# => "nolem on ,nomel on"
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
You can also have the service perform at a later time:
|
|
59
|
+
```ruby
|
|
60
|
+
# later_service.rb
|
|
61
|
+
class LaterService < Facio::Service
|
|
62
|
+
context do
|
|
63
|
+
attribute :message
|
|
64
|
+
attribute :result
|
|
65
|
+
end
|
|
66
|
+
def perform
|
|
67
|
+
context.message.text.reverse
|
|
68
|
+
context.message.save!
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# This will perform the service at a later time:
|
|
73
|
+
SimpleService.perform_later(message: Message.find(1))
|
|
74
|
+
```
|
|
75
|
+
In this case, once the service is done, you should see the Message's text being reversed.
|
|
76
|
+
|
|
77
|
+
Services are subclasses of ActiveJob::Base, which basically gives you things like concurrency control and retry logic.
|
|
78
|
+
|
|
79
|
+
### Context
|
|
80
|
+
|
|
81
|
+
A context is just an PORO, which includes the ActiveModel::API, which includes validation. You can add validation to the context,
|
|
82
|
+
Facio will check the validity of the context before performing the service. Context can be created in separate files, but also inline in the service.
|
|
83
|
+
|
|
84
|
+
It has some useful extra's allowing for associations (has_one/has_many), it also has a type caster for models, so that you can pass the id or the model itself into an attribute.
|
|
85
|
+
|
|
86
|
+
Using context like below (inline) will then construct a Class, with Facio::Context as a base_class. You can also use external context classes and refer to the in the inline class using the base_class.
|
|
87
|
+
```ruby
|
|
88
|
+
# some_context.rb
|
|
89
|
+
class SomeContext < Facio::Context
|
|
90
|
+
attribute :special
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# simple_service.rb
|
|
94
|
+
class OtherService < Facio::Service
|
|
95
|
+
context base_class: "SomeContext" do
|
|
96
|
+
attribute :value
|
|
97
|
+
attribute :result
|
|
98
|
+
end
|
|
99
|
+
def perform
|
|
100
|
+
context.result = context.value.to_s.reverse
|
|
101
|
+
context.result += "special" if context.special.present?
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
Next to this, you can have your service-class be accompanied by a context-class, if that's your preference. The service will find the matching context:
|
|
107
|
+
|
|
108
|
+
```ruby
|
|
109
|
+
# fancy_context.rb
|
|
110
|
+
class FancyContext < Facio::Context
|
|
111
|
+
attribute :fancy
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
# fancy_service.rb
|
|
115
|
+
class FancyService < Facio::Service
|
|
116
|
+
def perform
|
|
117
|
+
context.result = context.fancy.to_s.reverse
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
### Result
|
|
123
|
+
|
|
124
|
+
When the context model includes a result attribute, you can (and should) use that to return the outcome of the service. Facio will set it to the last value of the perform if you don't. Additionally, when the return value is more complex, or you need validation on the return, you can use the result object. This works as follows:
|
|
125
|
+
|
|
126
|
+
```ruby
|
|
127
|
+
class InlineResultService < Facio::Service
|
|
128
|
+
context do
|
|
129
|
+
attribute :value
|
|
130
|
+
end
|
|
131
|
+
result do
|
|
132
|
+
attribute :text
|
|
133
|
+
end
|
|
134
|
+
def perform
|
|
135
|
+
result.text = context.value.to_s.reverse
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
## License
|
|
142
|
+
|
|
143
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
data/Rakefile
ADDED
data/facio.gemspec
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "lib/facio/version"
|
|
4
|
+
|
|
5
|
+
Gem::Specification.new do |spec|
|
|
6
|
+
spec.name = "facio"
|
|
7
|
+
spec.version = Facio::VERSION
|
|
8
|
+
spec.authors = ["Tom de Grunt"]
|
|
9
|
+
spec.email = ["tom@degrunt.nl"]
|
|
10
|
+
|
|
11
|
+
spec.summary = "Command pattern"
|
|
12
|
+
spec.description = "Command pattern for Ruby"
|
|
13
|
+
spec.homepage = "https://github.com/entdec/facio"
|
|
14
|
+
spec.license = "MIT"
|
|
15
|
+
spec.required_ruby_version = ">= 3.0.0"
|
|
16
|
+
|
|
17
|
+
spec.metadata["homepage_uri"] = spec.homepage
|
|
18
|
+
spec.metadata["source_code_uri"] = "https://github.com/entdec/facio"
|
|
19
|
+
|
|
20
|
+
# Specify which files should be added to the gem when it is released.
|
|
21
|
+
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
|
22
|
+
spec.files = Dir.chdir(__dir__) do
|
|
23
|
+
`git ls-files -z`.split("\x0").reject do |f|
|
|
24
|
+
(File.expand_path(f) == __FILE__) ||
|
|
25
|
+
f.start_with?(*%w[bin/ test/ spec/ features/ .git .github appveyor Gemfile])
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
spec.bindir = "exe"
|
|
29
|
+
spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
|
|
30
|
+
spec.require_paths = ["lib"]
|
|
31
|
+
|
|
32
|
+
rails_deps_version = "> 7"
|
|
33
|
+
|
|
34
|
+
# Uncomment to register a new dependency of your gem
|
|
35
|
+
spec.add_dependency "actionpack", rails_deps_version
|
|
36
|
+
spec.add_dependency "activemodel", rails_deps_version
|
|
37
|
+
spec.add_dependency "activejob", rails_deps_version
|
|
38
|
+
spec.add_dependency "activesupport", rails_deps_version
|
|
39
|
+
spec.add_dependency "railties", rails_deps_version
|
|
40
|
+
|
|
41
|
+
spec.add_development_dependency "activerecord", rails_deps_version
|
|
42
|
+
spec.add_development_dependency "globalid", "~> 1.2"
|
|
43
|
+
spec.add_development_dependency "debug", "~> 1.9"
|
|
44
|
+
spec.add_development_dependency "rubocop", "~> 1"
|
|
45
|
+
spec.add_development_dependency "standard", "~> 1"
|
|
46
|
+
spec.add_development_dependency "sqlite3", "~> 1.4"
|
|
47
|
+
|
|
48
|
+
# For more information and examples about making a new gem, check out our
|
|
49
|
+
# guide at: https://bundler.io/guides/creating_gem.html
|
|
50
|
+
end
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
|
|
2
|
+
module ActiveModel
|
|
3
|
+
module Associations
|
|
4
|
+
extend ActiveSupport::Concern
|
|
5
|
+
|
|
6
|
+
class_methods do
|
|
7
|
+
def has_one(association_id, options = {})
|
|
8
|
+
class_name = options[:class_name] || association_id.to_s.classify
|
|
9
|
+
|
|
10
|
+
define_method(association_id) do
|
|
11
|
+
instance_variable_get("@#{association_id}") || begin
|
|
12
|
+
instance_variable_set("@#{association_id}", class_name.constantize.new)
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
define_method("#{association_id}=") do |new_value|
|
|
17
|
+
instance_variable_set("@#{association_id}", new_value)
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def has_many(association_id, options = {})
|
|
22
|
+
class_name = options[:class_name] || association_id.to_s.singularize.classify
|
|
23
|
+
klass = class_name.constantize
|
|
24
|
+
|
|
25
|
+
define_method(association_id) do
|
|
26
|
+
instance_variable_get("@#{association_id}") || begin
|
|
27
|
+
instance_variable_set("@#{association_id}", [])
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Assignment using models
|
|
32
|
+
define_method("#{association_id}=") do |attributes|
|
|
33
|
+
instance_variable_set("@#{association_id}", attributes)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Assignment using attributes, e.g. from a form
|
|
37
|
+
define_method("#{association_id}_attributes=") do |attributes|
|
|
38
|
+
if attributes.is_a?(Hash) || attributes.is_a?(ActionController::Parameters)
|
|
39
|
+
# Reject any attributes that have the are a non-numeric key, e.g. things like "TEMPLATE"
|
|
40
|
+
attributes = attributes.reject { |k, v| k.to_i.to_s != k }.values
|
|
41
|
+
# Reject any attributes that have the _destroy flag set
|
|
42
|
+
attributes = attributes.reject { |v| v["_destroy"] == "1" || v[:_destroy] == "1" }
|
|
43
|
+
attributes = attributes.map { |params| klass.new(params) }
|
|
44
|
+
|
|
45
|
+
instance_variable_set("@#{association_id}", attributes)
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
module ActiveModel
|
|
2
|
+
module Type
|
|
3
|
+
class Model < ActiveModel::Type::Value
|
|
4
|
+
attr_reader :class_name
|
|
5
|
+
def initialize(**options)
|
|
6
|
+
@class_name = options.delete(:class_name)
|
|
7
|
+
super()
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def cast(value)
|
|
11
|
+
return nil if value.nil?
|
|
12
|
+
|
|
13
|
+
klass = class_name.constantize
|
|
14
|
+
if value.is_a?(::Integer) || value.is_a?(::String)
|
|
15
|
+
klass.find(value)
|
|
16
|
+
elsif value.is_a?(klass)
|
|
17
|
+
value
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
ActiveModel::Type.register(:model, ActiveModel::Type::Model)
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
module Callbacks
|
|
2
|
+
extend ActiveSupport::Concern
|
|
3
|
+
|
|
4
|
+
included do
|
|
5
|
+
around_perform do |job, block|
|
|
6
|
+
@context = self.class.context_class.new(arguments.first)
|
|
7
|
+
@result = self.class.result_class&.new
|
|
8
|
+
@performed = false
|
|
9
|
+
|
|
10
|
+
if @context.valid?
|
|
11
|
+
result = nil
|
|
12
|
+
if transactional && defined?(ActiveRecord::Base)
|
|
13
|
+
ActiveRecord::Base.transaction(requires_new: true) do
|
|
14
|
+
result = block.call
|
|
15
|
+
# This will only rollback the changes of the service, SILENTLY, however the context will be failed? already.
|
|
16
|
+
# This is the most close to expected behaviour this can get.
|
|
17
|
+
raise ActiveRecord::Rollback if context.failed?
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
else
|
|
21
|
+
result = block.call
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
@performed = true
|
|
25
|
+
# Purely as a convenience, but also to enforce a standard
|
|
26
|
+
context.result ||= result if @result.nil?
|
|
27
|
+
end
|
|
28
|
+
# self
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
before_enqueue do |job|
|
|
32
|
+
@performed = false
|
|
33
|
+
@context = self.class.context_class.new(arguments.first)
|
|
34
|
+
@result = self.class.result_class&.new
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
module Execution
|
|
2
|
+
extend ActiveSupport::Concern
|
|
3
|
+
|
|
4
|
+
def facio_perform_with_arguments(*args)
|
|
5
|
+
facio_perform_without_arguments
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
included do
|
|
9
|
+
if self < ActiveJob::Base
|
|
10
|
+
def self.method_added(method_name)
|
|
11
|
+
if method_name == :perform
|
|
12
|
+
# Avoid reentrance
|
|
13
|
+
unless method_defined?(:facio_perform_without_arguments)
|
|
14
|
+
alias_method :facio_perform_without_arguments, :perform
|
|
15
|
+
alias_method :perform, :facio_perform_with_arguments # This invokes method_added(:perform)
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
module ServiceContext
|
|
2
|
+
extend ActiveSupport::Concern
|
|
3
|
+
|
|
4
|
+
included do
|
|
5
|
+
attr_accessor :context
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
class_methods do
|
|
9
|
+
# Allows you to define the context of the service inline:
|
|
10
|
+
#
|
|
11
|
+
# context do
|
|
12
|
+
# attr_accessor :value
|
|
13
|
+
# end
|
|
14
|
+
#
|
|
15
|
+
# Under normal circumstances the context class is subclassed from Facio::Context, but if you want to override
|
|
16
|
+
# this behaviour you can pass a base_class as an argument.
|
|
17
|
+
#
|
|
18
|
+
def context(base_class: context_base_class, &)
|
|
19
|
+
# This is to be able to define the context class in a sensible module name
|
|
20
|
+
module_obj = derived_context_name.deconstantize.present? ? derived_context_name.deconstantize.safe_constantize : Object
|
|
21
|
+
@context_class = module_obj.const_set(derived_context_name.demodulize, Class.new(base_class))
|
|
22
|
+
@context_class.instance_exec(&)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# Allows you to define the context class name for this service, when it's different than the default.
|
|
26
|
+
def context_class_name(name = nil)
|
|
27
|
+
@context_class_name = name if name
|
|
28
|
+
@context_class_name || derived_context_name
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Helpers
|
|
32
|
+
|
|
33
|
+
def context_base_class
|
|
34
|
+
defined?(ApplicationContext) ? ApplicationContext : Facio::Context
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def context_class
|
|
38
|
+
@context_class || context_class_name.safe_constantize
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def derived_context_name
|
|
42
|
+
name = self.name
|
|
43
|
+
name.gsub(/Service\z/, "Context")
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
module ServiceResult
|
|
2
|
+
extend ActiveSupport::Concern
|
|
3
|
+
|
|
4
|
+
class_methods do
|
|
5
|
+
# Allows you to define the result of the service inline:
|
|
6
|
+
#
|
|
7
|
+
# result do
|
|
8
|
+
# attr_accessor :value
|
|
9
|
+
# end
|
|
10
|
+
#
|
|
11
|
+
# Under normal circumstances the result class is subclassed from Facio::Result, but if you want to override
|
|
12
|
+
# this behaviour you can pass a base_class as an argument.
|
|
13
|
+
#
|
|
14
|
+
def result(base_class: result_base_class, &)
|
|
15
|
+
mdl = derived_context_name.deconstantize.present? ? derived_context_name.deconstantize.safe_constantize : Object
|
|
16
|
+
@result_class = mdl.const_set(derived_result_name.demodulize, Class.new(base_class))
|
|
17
|
+
@result_class.instance_exec(&)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# Allows you to define the result class name for this service, when it's different than the default.
|
|
21
|
+
def result_class_name(name = nil)
|
|
22
|
+
@result_class_name = name if name
|
|
23
|
+
@result_class_name || derived_result_name
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# Helpers
|
|
27
|
+
|
|
28
|
+
def result_base_class
|
|
29
|
+
# FIXME: This doesn't work sadly
|
|
30
|
+
Object.const_defined?("ApplicationResult") ? Object.const_get("ApplicationResult") : Facio::Result
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def result_class
|
|
34
|
+
@result_class || result_class_name.safe_constantize
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def derived_result_name
|
|
38
|
+
name = self.name
|
|
39
|
+
name.gsub(/Service\z/, "Result")
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
module Transactional
|
|
2
|
+
extend ActiveSupport::Concern
|
|
3
|
+
|
|
4
|
+
included do
|
|
5
|
+
delegate :transactional, to: :class
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
class_methods do
|
|
9
|
+
# Allows you to define whether the service should be transactional or not.
|
|
10
|
+
# Setting this to true will run the complete perform method in a transaction.
|
|
11
|
+
#
|
|
12
|
+
# If you do not define this, it will default to false, unless a superclass
|
|
13
|
+
# defines it as true.
|
|
14
|
+
def transactional(value = nil)
|
|
15
|
+
@transactional = value if value
|
|
16
|
+
@transactional = nil unless defined?(@transactional)
|
|
17
|
+
if @transactional.nil?
|
|
18
|
+
@transactional = if superclass < Facio::Service
|
|
19
|
+
superclass.transactional
|
|
20
|
+
else
|
|
21
|
+
false
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
@transactional
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
module Translations
|
|
2
|
+
extend ActiveSupport::Concern
|
|
3
|
+
|
|
4
|
+
included do
|
|
5
|
+
def t(key, passed_options = {})
|
|
6
|
+
@service_scope ||= nil
|
|
7
|
+
|
|
8
|
+
unless @service_scope.present?
|
|
9
|
+
parts = (is_a?(Class) ? self : self.class).to_s.underscore.tr("/", ".").split(".")
|
|
10
|
+
parts[-1] = "#{parts.last.gsub("_service", "").pluralize}.service" if parts.last.end_with?("_service")
|
|
11
|
+
parts[-1] = "#{parts.last.gsub("_context", "").pluralize}.context" if parts.last.end_with?("_context")
|
|
12
|
+
@service_scope = parts.compact.join(".")
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
options = {scope: @service_scope}
|
|
16
|
+
options[:default] = ::I18n.t(key) unless key.start_with?(".")
|
|
17
|
+
|
|
18
|
+
::I18n.t(key, **options.merge(passed_options))
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
module Facio
|
|
2
|
+
class Context < BaseModel
|
|
3
|
+
attribute :result
|
|
4
|
+
|
|
5
|
+
def fail!(attr = nil, message = :invalid, options = {})
|
|
6
|
+
@success = false
|
|
7
|
+
merge_errors!(attr, message, options)
|
|
8
|
+
raise Facio::ContextFailure, self
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
private
|
|
12
|
+
|
|
13
|
+
def merge_errors!(attr, message = :invalid, options = {})
|
|
14
|
+
return unless attr
|
|
15
|
+
|
|
16
|
+
if attr.is_a? String
|
|
17
|
+
errors.add(:base, attr)
|
|
18
|
+
elsif attr.is_a? ActiveModel::Errors
|
|
19
|
+
errors.merge!(attr)
|
|
20
|
+
elsif attr.is_a? Symbol
|
|
21
|
+
errors.add(attr, message, **options)
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
data/lib/facio/error.rb
ADDED
data/lib/facio/result.rb
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
require_relative "concerns/service_context"
|
|
2
|
+
require_relative "concerns/service_result"
|
|
3
|
+
require_relative "concerns/transactional"
|
|
4
|
+
require_relative "concerns/translations"
|
|
5
|
+
require_relative "concerns/callbacks"
|
|
6
|
+
require_relative "concerns/execution"
|
|
7
|
+
|
|
8
|
+
module Facio
|
|
9
|
+
class Service < ActiveJob::Base
|
|
10
|
+
include ServiceContext
|
|
11
|
+
include ServiceResult
|
|
12
|
+
include Transactional
|
|
13
|
+
include Translations
|
|
14
|
+
include Callbacks
|
|
15
|
+
include Execution
|
|
16
|
+
|
|
17
|
+
class << self
|
|
18
|
+
def perform(...)
|
|
19
|
+
job = job_or_instantiate(...)
|
|
20
|
+
job.perform_now
|
|
21
|
+
job
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
alias_method :perform_now, :perform
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# Returns the result, be it the result object or the context object
|
|
28
|
+
def result
|
|
29
|
+
@result || context.result
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# Returns the errors on the context object
|
|
33
|
+
delegate :errors, to: :@context
|
|
34
|
+
|
|
35
|
+
# Returns whether the context is valid
|
|
36
|
+
delegate :valid?, to: :@context
|
|
37
|
+
# Returns whether the coentext is invalid
|
|
38
|
+
delegate :invalid?, to: :@context
|
|
39
|
+
|
|
40
|
+
# Returns whether the service has been performed (only when using perform)
|
|
41
|
+
def performed?
|
|
42
|
+
@performed
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Returns whether the service was successfully performed
|
|
46
|
+
def success?
|
|
47
|
+
success = performed? && valid?
|
|
48
|
+
success &&= @result.valid? if @result
|
|
49
|
+
success
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# Returns whether the service failed to perform
|
|
53
|
+
def failed?
|
|
54
|
+
!success?
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
data/lib/facio.rb
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "active_job"
|
|
4
|
+
require "active_model"
|
|
5
|
+
require "active_support"
|
|
6
|
+
require "action_controller"
|
|
7
|
+
|
|
8
|
+
require_relative "facio/version"
|
|
9
|
+
require_relative "facio/active_model/associations"
|
|
10
|
+
require_relative "facio/active_model/type/model"
|
|
11
|
+
require_relative "facio/base_model"
|
|
12
|
+
require_relative "facio/error"
|
|
13
|
+
require_relative "facio/context"
|
|
14
|
+
require_relative "facio/context_failure"
|
|
15
|
+
require_relative "facio/result"
|
|
16
|
+
require_relative "facio/service"
|
|
17
|
+
|
|
18
|
+
module Facio
|
|
19
|
+
class Error < StandardError; end
|
|
20
|
+
# Your code goes here...
|
|
21
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "rails/generators"
|
|
4
|
+
|
|
5
|
+
module Facio
|
|
6
|
+
class ServiceGenerator < ::Rails::Generators::NamedBase
|
|
7
|
+
desc "Generates a service and context"
|
|
8
|
+
|
|
9
|
+
source_root File.expand_path("templates", __dir__)
|
|
10
|
+
|
|
11
|
+
def copy_initializer_file
|
|
12
|
+
template "service.rb", "app/services/#{name}_service.rb"
|
|
13
|
+
template "context.rb", "app/services/#{name}_context.rb"
|
|
14
|
+
template "service_test.rb", "test/services/#{name}_service_test.rb"
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'test_helper'
|
|
4
|
+
|
|
5
|
+
class <%= name.camelize %>ServiceTest < ActiveSupport::TestCase
|
|
6
|
+
test 'service context result is success' do
|
|
7
|
+
context = <%=name.camelize%>Service.perform(some: 'some')
|
|
8
|
+
assert context.success?
|
|
9
|
+
assert_equal 'emos', context.some
|
|
10
|
+
end
|
|
11
|
+
end
|
data/sig/mando.rbs
ADDED
metadata
ADDED
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: facio
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.11
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Tom de Grunt
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: exe
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2025-11-20 00:00:00.000000000 Z
|
|
12
|
+
dependencies:
|
|
13
|
+
- !ruby/object:Gem::Dependency
|
|
14
|
+
name: actionpack
|
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
|
16
|
+
requirements:
|
|
17
|
+
- - ">"
|
|
18
|
+
- !ruby/object:Gem::Version
|
|
19
|
+
version: '7'
|
|
20
|
+
type: :runtime
|
|
21
|
+
prerelease: false
|
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
+
requirements:
|
|
24
|
+
- - ">"
|
|
25
|
+
- !ruby/object:Gem::Version
|
|
26
|
+
version: '7'
|
|
27
|
+
- !ruby/object:Gem::Dependency
|
|
28
|
+
name: activemodel
|
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
|
30
|
+
requirements:
|
|
31
|
+
- - ">"
|
|
32
|
+
- !ruby/object:Gem::Version
|
|
33
|
+
version: '7'
|
|
34
|
+
type: :runtime
|
|
35
|
+
prerelease: false
|
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
37
|
+
requirements:
|
|
38
|
+
- - ">"
|
|
39
|
+
- !ruby/object:Gem::Version
|
|
40
|
+
version: '7'
|
|
41
|
+
- !ruby/object:Gem::Dependency
|
|
42
|
+
name: activejob
|
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
|
44
|
+
requirements:
|
|
45
|
+
- - ">"
|
|
46
|
+
- !ruby/object:Gem::Version
|
|
47
|
+
version: '7'
|
|
48
|
+
type: :runtime
|
|
49
|
+
prerelease: false
|
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
51
|
+
requirements:
|
|
52
|
+
- - ">"
|
|
53
|
+
- !ruby/object:Gem::Version
|
|
54
|
+
version: '7'
|
|
55
|
+
- !ruby/object:Gem::Dependency
|
|
56
|
+
name: activesupport
|
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
|
58
|
+
requirements:
|
|
59
|
+
- - ">"
|
|
60
|
+
- !ruby/object:Gem::Version
|
|
61
|
+
version: '7'
|
|
62
|
+
type: :runtime
|
|
63
|
+
prerelease: false
|
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
65
|
+
requirements:
|
|
66
|
+
- - ">"
|
|
67
|
+
- !ruby/object:Gem::Version
|
|
68
|
+
version: '7'
|
|
69
|
+
- !ruby/object:Gem::Dependency
|
|
70
|
+
name: railties
|
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
|
72
|
+
requirements:
|
|
73
|
+
- - ">"
|
|
74
|
+
- !ruby/object:Gem::Version
|
|
75
|
+
version: '7'
|
|
76
|
+
type: :runtime
|
|
77
|
+
prerelease: false
|
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
79
|
+
requirements:
|
|
80
|
+
- - ">"
|
|
81
|
+
- !ruby/object:Gem::Version
|
|
82
|
+
version: '7'
|
|
83
|
+
- !ruby/object:Gem::Dependency
|
|
84
|
+
name: activerecord
|
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
|
86
|
+
requirements:
|
|
87
|
+
- - ">"
|
|
88
|
+
- !ruby/object:Gem::Version
|
|
89
|
+
version: '7'
|
|
90
|
+
type: :development
|
|
91
|
+
prerelease: false
|
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
93
|
+
requirements:
|
|
94
|
+
- - ">"
|
|
95
|
+
- !ruby/object:Gem::Version
|
|
96
|
+
version: '7'
|
|
97
|
+
- !ruby/object:Gem::Dependency
|
|
98
|
+
name: globalid
|
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
|
100
|
+
requirements:
|
|
101
|
+
- - "~>"
|
|
102
|
+
- !ruby/object:Gem::Version
|
|
103
|
+
version: '1.2'
|
|
104
|
+
type: :development
|
|
105
|
+
prerelease: false
|
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
107
|
+
requirements:
|
|
108
|
+
- - "~>"
|
|
109
|
+
- !ruby/object:Gem::Version
|
|
110
|
+
version: '1.2'
|
|
111
|
+
- !ruby/object:Gem::Dependency
|
|
112
|
+
name: debug
|
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
|
114
|
+
requirements:
|
|
115
|
+
- - "~>"
|
|
116
|
+
- !ruby/object:Gem::Version
|
|
117
|
+
version: '1.9'
|
|
118
|
+
type: :development
|
|
119
|
+
prerelease: false
|
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
121
|
+
requirements:
|
|
122
|
+
- - "~>"
|
|
123
|
+
- !ruby/object:Gem::Version
|
|
124
|
+
version: '1.9'
|
|
125
|
+
- !ruby/object:Gem::Dependency
|
|
126
|
+
name: rubocop
|
|
127
|
+
requirement: !ruby/object:Gem::Requirement
|
|
128
|
+
requirements:
|
|
129
|
+
- - "~>"
|
|
130
|
+
- !ruby/object:Gem::Version
|
|
131
|
+
version: '1'
|
|
132
|
+
type: :development
|
|
133
|
+
prerelease: false
|
|
134
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
135
|
+
requirements:
|
|
136
|
+
- - "~>"
|
|
137
|
+
- !ruby/object:Gem::Version
|
|
138
|
+
version: '1'
|
|
139
|
+
- !ruby/object:Gem::Dependency
|
|
140
|
+
name: standard
|
|
141
|
+
requirement: !ruby/object:Gem::Requirement
|
|
142
|
+
requirements:
|
|
143
|
+
- - "~>"
|
|
144
|
+
- !ruby/object:Gem::Version
|
|
145
|
+
version: '1'
|
|
146
|
+
type: :development
|
|
147
|
+
prerelease: false
|
|
148
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
149
|
+
requirements:
|
|
150
|
+
- - "~>"
|
|
151
|
+
- !ruby/object:Gem::Version
|
|
152
|
+
version: '1'
|
|
153
|
+
- !ruby/object:Gem::Dependency
|
|
154
|
+
name: sqlite3
|
|
155
|
+
requirement: !ruby/object:Gem::Requirement
|
|
156
|
+
requirements:
|
|
157
|
+
- - "~>"
|
|
158
|
+
- !ruby/object:Gem::Version
|
|
159
|
+
version: '1.4'
|
|
160
|
+
type: :development
|
|
161
|
+
prerelease: false
|
|
162
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
163
|
+
requirements:
|
|
164
|
+
- - "~>"
|
|
165
|
+
- !ruby/object:Gem::Version
|
|
166
|
+
version: '1.4'
|
|
167
|
+
description: Command pattern for Ruby
|
|
168
|
+
email:
|
|
169
|
+
- tom@degrunt.nl
|
|
170
|
+
executables: []
|
|
171
|
+
extensions: []
|
|
172
|
+
extra_rdoc_files: []
|
|
173
|
+
files:
|
|
174
|
+
- ".rubocop.yml"
|
|
175
|
+
- CHANGELOG.md
|
|
176
|
+
- CODE_OF_CONDUCT.md
|
|
177
|
+
- LICENSE.txt
|
|
178
|
+
- README.md
|
|
179
|
+
- Rakefile
|
|
180
|
+
- facio.gemspec
|
|
181
|
+
- lib/facio.rb
|
|
182
|
+
- lib/facio/active_model/associations.rb
|
|
183
|
+
- lib/facio/active_model/type/model.rb
|
|
184
|
+
- lib/facio/base_model.rb
|
|
185
|
+
- lib/facio/concerns/callbacks.rb
|
|
186
|
+
- lib/facio/concerns/execution.rb
|
|
187
|
+
- lib/facio/concerns/service_context.rb
|
|
188
|
+
- lib/facio/concerns/service_result.rb
|
|
189
|
+
- lib/facio/concerns/transactional.rb
|
|
190
|
+
- lib/facio/concerns/translations.rb
|
|
191
|
+
- lib/facio/context.rb
|
|
192
|
+
- lib/facio/context_failure.rb
|
|
193
|
+
- lib/facio/error.rb
|
|
194
|
+
- lib/facio/result.rb
|
|
195
|
+
- lib/facio/service.rb
|
|
196
|
+
- lib/facio/version.rb
|
|
197
|
+
- lib/generators/facio/USAGE
|
|
198
|
+
- lib/generators/facio/service_generator.rb
|
|
199
|
+
- lib/generators/facio/templates/context.rb
|
|
200
|
+
- lib/generators/facio/templates/service.rb
|
|
201
|
+
- lib/generators/facio/templates/service_test.rb
|
|
202
|
+
- sig/mando.rbs
|
|
203
|
+
homepage: https://github.com/entdec/facio
|
|
204
|
+
licenses:
|
|
205
|
+
- MIT
|
|
206
|
+
metadata:
|
|
207
|
+
homepage_uri: https://github.com/entdec/facio
|
|
208
|
+
source_code_uri: https://github.com/entdec/facio
|
|
209
|
+
post_install_message:
|
|
210
|
+
rdoc_options: []
|
|
211
|
+
require_paths:
|
|
212
|
+
- lib
|
|
213
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
214
|
+
requirements:
|
|
215
|
+
- - ">="
|
|
216
|
+
- !ruby/object:Gem::Version
|
|
217
|
+
version: 3.0.0
|
|
218
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
219
|
+
requirements:
|
|
220
|
+
- - ">="
|
|
221
|
+
- !ruby/object:Gem::Version
|
|
222
|
+
version: '0'
|
|
223
|
+
requirements: []
|
|
224
|
+
rubygems_version: 3.4.10
|
|
225
|
+
signing_key:
|
|
226
|
+
specification_version: 4
|
|
227
|
+
summary: Command pattern
|
|
228
|
+
test_files: []
|