zen-service 2.1.0 → 2.2.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.
- checksums.yaml +4 -4
- data/.github/copilot-instructions.md +133 -0
- data/README.md +12 -0
- data/lib/zen/service/plugins.rb +9 -2
- data/lib/zen/service/version.rb +1 -1
- metadata +3 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 12dcff33c206c08f710f8cedf505f663387054b22aa6429556b20c47eac6b410
|
|
4
|
+
data.tar.gz: b314175b5a9a5634480ac0c4fbec0777fa895e68687eedbaad8382f1b26600fe
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 3bb64af64bf4641e01b686bf4b8bda93e5efcd8188d1ed6d1a864c74e0f2d916b03383345476c0aa1a860b3bae9ba6fbf370f6e309905d7ef5cb1186fd561b7d
|
|
7
|
+
data.tar.gz: b2678a54e99637e8d1706a6ccd913346d9e64d7ea0ef46e2c7e5637683844ab21220b6c1422601fc8466178358377e6da092cc0320cc5f6e427052b5e1b786f1
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
# Copilot Instructions for zen-service
|
|
2
|
+
|
|
3
|
+
## Project Overview
|
|
4
|
+
|
|
5
|
+
`zen-service` is a Ruby gem providing a flexible, plugin-based service object pattern. The architecture emphasizes extensibility through a plugin system where even core functionality (callable, attributes) is implemented as plugins.
|
|
6
|
+
|
|
7
|
+
## Architecture Fundamentals
|
|
8
|
+
|
|
9
|
+
### Plugin System
|
|
10
|
+
|
|
11
|
+
The entire gem is built around `Zen::Service::Plugins` - a dynamic plugin registration and loading system:
|
|
12
|
+
|
|
13
|
+
- Plugins auto-register using `extend Zen::Service::Plugins::Plugin` (converts module name to snake_case key)
|
|
14
|
+
- Services use plugins via `use :plugin_name, **options`
|
|
15
|
+
- Plugin lifecycle: `used(service_class)` → includes module → `configure(service_class)` if defined
|
|
16
|
+
- See [plugin.rb](lib/zen/service/plugins/plugin.rb) for the DSL: `register_as`, `default_options`, `service_extension`
|
|
17
|
+
|
|
18
|
+
### Core Plugins Architecture
|
|
19
|
+
|
|
20
|
+
Base `Zen::Service` uses two foundational plugins:
|
|
21
|
+
|
|
22
|
+
- `:callable` - provides `.call` and `.[]` class methods that instantiate + call
|
|
23
|
+
- `:attributes` - manages initialization parameters with runtime validation
|
|
24
|
+
|
|
25
|
+
### Service Attributes Pattern
|
|
26
|
+
|
|
27
|
+
Attributes are positional-or-named parameters resolved during initialization:
|
|
28
|
+
|
|
29
|
+
```ruby
|
|
30
|
+
attributes :foo, :bar
|
|
31
|
+
new(1, bar: 2) # foo=1, bar=2
|
|
32
|
+
new(foo: 1) # foo=1, bar=nil
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
- Attributes generate reader methods dynamically in a dedicated `AttributeMethods` module
|
|
36
|
+
- Each service class gets its own `AttributeMethods` constant to isolate attribute methods
|
|
37
|
+
- `with_attributes(hash)` creates clones with merged attributes
|
|
38
|
+
|
|
39
|
+
## Development Workflow
|
|
40
|
+
|
|
41
|
+
### Running Tests
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
bundle exec rspec spec # Run all specs
|
|
45
|
+
bundle exec rspec spec/zen/service_spec.rb # Single file
|
|
46
|
+
rake # Run specs + rubocop
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### Test Patterns
|
|
50
|
+
|
|
51
|
+
- Use `def_service { ... }` helper to define service classes in specs (see [spec_helper.rb](spec/spec_helper.rb))
|
|
52
|
+
- `build_service(*args, **kwargs)` instantiates service with attributes
|
|
53
|
+
- Services are frozen_string_literal by convention
|
|
54
|
+
|
|
55
|
+
### Building & Releasing
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
rake build # Build gem to pkg/
|
|
59
|
+
rake install # Install locally
|
|
60
|
+
rake release # Tag + push to rubygems.org
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## Key Conventions
|
|
64
|
+
|
|
65
|
+
### Plugin Implementation Pattern
|
|
66
|
+
|
|
67
|
+
1. Create module in `lib/zen/service/plugins/`
|
|
68
|
+
2. `extend Zen::Service::Plugins::Plugin` (auto-registers)
|
|
69
|
+
3. Define `used(service_class, **opts, &block)` for one-time setup
|
|
70
|
+
4. Define `configure(service_class, **opts, &block)` for reconfiguration
|
|
71
|
+
5. Use `prepend Extension` (for wrapping `call`) or `include` (for adding methods)
|
|
72
|
+
6. Add `ClassMethods` module for class-level functionality
|
|
73
|
+
|
|
74
|
+
Example from [result_yielding.rb](lib/zen/service/plugins/result_yielding.rb):
|
|
75
|
+
|
|
76
|
+
```ruby
|
|
77
|
+
module ResultYielding
|
|
78
|
+
extend Plugin
|
|
79
|
+
|
|
80
|
+
module Extension
|
|
81
|
+
def call
|
|
82
|
+
return super unless block_given?
|
|
83
|
+
result = nil
|
|
84
|
+
super { result = yield }
|
|
85
|
+
result
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def self.used(service_class)
|
|
90
|
+
service_class.prepend(Extension)
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
### Plugin Option Handling
|
|
96
|
+
|
|
97
|
+
- Use `default_options foo: 5` in plugin definition
|
|
98
|
+
- Access via `self.class.plugins[:plugin_name].options[:foo]`
|
|
99
|
+
- Options merge with defaults when using plugin
|
|
100
|
+
|
|
101
|
+
### Inheritance Behavior
|
|
102
|
+
|
|
103
|
+
- `attributes_list` is duplicated on inheritance
|
|
104
|
+
- Each subclass gets its own `AttributeMethods` module
|
|
105
|
+
- Plugin reflections accumulate through `ancestors.flat_map(&:service_plugins)`
|
|
106
|
+
|
|
107
|
+
## Critical Implementation Details
|
|
108
|
+
|
|
109
|
+
### Why Prepend vs Include
|
|
110
|
+
|
|
111
|
+
- `prepend Extension` - use when wrapping `call` method (allows `super` to reach original)
|
|
112
|
+
- `include` - use for adding new methods
|
|
113
|
+
- See `:result_yielding` (prepend) vs `:persisted_result` (extend in initialize)
|
|
114
|
+
|
|
115
|
+
### Attributes Resolution Edge Cases
|
|
116
|
+
|
|
117
|
+
- Cannot pass same attribute as both positional and named
|
|
118
|
+
- Cannot pass more attributes than declared
|
|
119
|
+
- Args filled in declaration order, then kwargs merged
|
|
120
|
+
|
|
121
|
+
### Plugin DSL Methods
|
|
122
|
+
|
|
123
|
+
From [plugin.rb](lib/zen/service/plugins/plugin.rb):
|
|
124
|
+
|
|
125
|
+
- `register_as :custom_name` - override auto-generated registration name
|
|
126
|
+
- `default_options hash` - set default plugin options
|
|
127
|
+
- `service_extension module` - extend `Zen::Service` base class globally
|
|
128
|
+
|
|
129
|
+
## Files of Note
|
|
130
|
+
|
|
131
|
+
- [plugins.rb](lib/zen/service/plugins.rb) - central plugin registry with `fetch` and `register`
|
|
132
|
+
- [pluggable.rb](lib/zen/service/plugins/pluggable.rb) - `use` DSL and plugin reflection system
|
|
133
|
+
- [spec_helper.rb](spec/spec_helper.rb) - `def_service` pattern for testing services
|
data/README.md
CHANGED
|
@@ -162,6 +162,18 @@ end
|
|
|
162
162
|
Todos::Show[todo] # => { id: 1, isCompleted: true }
|
|
163
163
|
```
|
|
164
164
|
|
|
165
|
+
**Note**: Custom plugins need to be registered before they can be used. Plugins that extend
|
|
166
|
+
`Zen::Service::Plugin` are automatically registered when the module is loaded. Alternatively,
|
|
167
|
+
you can register plugins manually:
|
|
168
|
+
|
|
169
|
+
```rb
|
|
170
|
+
# Register a plugin module
|
|
171
|
+
Zen::Service::Plugins.register(:my_plugin, MyPlugin)
|
|
172
|
+
|
|
173
|
+
# Register by class name (useful when autoload isn't available yet, e.g., during Rails boot)
|
|
174
|
+
Zen::Service::Plugins.register(:my_plugin, "MyApp::Services::MyPlugin")
|
|
175
|
+
```
|
|
176
|
+
|
|
165
177
|
## Development
|
|
166
178
|
|
|
167
179
|
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests.
|
data/lib/zen/service/plugins.rb
CHANGED
|
@@ -3,9 +3,10 @@
|
|
|
3
3
|
module Zen
|
|
4
4
|
module Service::Plugins
|
|
5
5
|
def self.fetch(name)
|
|
6
|
-
|
|
6
|
+
raise("extension `#{name}` is not registered") unless plugins.key?(name)
|
|
7
7
|
|
|
8
|
-
plugins[name]
|
|
8
|
+
extension = plugins[name]
|
|
9
|
+
extension.is_a?(String) ? constantize(extension) : extension
|
|
9
10
|
end
|
|
10
11
|
|
|
11
12
|
def self.register(name_or_hash, extension = nil)
|
|
@@ -28,6 +29,12 @@ module Zen
|
|
|
28
29
|
def self.plugins
|
|
29
30
|
@plugins ||= {}
|
|
30
31
|
end
|
|
32
|
+
|
|
33
|
+
def self.constantize(string)
|
|
34
|
+
return string.constantize if string.respond_to?(:constantize)
|
|
35
|
+
|
|
36
|
+
string.sub(/^::/, "").split("::").inject(Object) { |obj, const| obj.const_get(const) }
|
|
37
|
+
end
|
|
31
38
|
end
|
|
32
39
|
|
|
33
40
|
require_relative "plugins/plugin"
|
data/lib/zen/service/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: zen-service
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 2.
|
|
4
|
+
version: 2.2.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Artem Kuzko
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2025-12-
|
|
11
|
+
date: 2025-12-28 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: pry
|
|
@@ -101,6 +101,7 @@ executables: []
|
|
|
101
101
|
extensions: []
|
|
102
102
|
extra_rdoc_files: []
|
|
103
103
|
files:
|
|
104
|
+
- ".github/copilot-instructions.md"
|
|
104
105
|
- ".gitignore"
|
|
105
106
|
- ".rspec"
|
|
106
107
|
- ".rubocop.yml"
|