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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 83f9ef4ebaca920508cb7aa96f3ce4eea6b899338b000c5a38edc6cacf342473
4
- data.tar.gz: 86a27632c1fc83f7ba7c735bea83c41a1b0d9e52b2d546bfdd3054e4766c447d
3
+ metadata.gz: 12dcff33c206c08f710f8cedf505f663387054b22aa6429556b20c47eac6b410
4
+ data.tar.gz: b314175b5a9a5634480ac0c4fbec0777fa895e68687eedbaad8382f1b26600fe
5
5
  SHA512:
6
- metadata.gz: 755c8e023bed8067af7fd509018ff8a001cb5618cde43fc00aa6b0244165482d58fadea79becc7b4ebb9f6a1e87aef9d9901aebd54e2218390553c2fcd71edbc
7
- data.tar.gz: 55ae83ddff091119dff6b1448e37e704dcdf39cdc616fcc11b51cccd3c979113e41df8396c1cb5688c459dcc3002534ea2f512c54815a3ceecf85cc74e89d8b5
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.
@@ -3,9 +3,10 @@
3
3
  module Zen
4
4
  module Service::Plugins
5
5
  def self.fetch(name)
6
- require("zen/service/plugins/#{name}") unless plugins.key?(name)
6
+ raise("extension `#{name}` is not registered") unless plugins.key?(name)
7
7
 
8
- plugins[name] || raise("extension `#{name}` is not registered")
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"
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Zen
4
4
  class Service
5
- VERSION = "2.1.0"
5
+ VERSION = "2.2.0"
6
6
  end
7
7
  end
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.1.0
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-20 00:00:00.000000000 Z
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"