ledger_sync-domains 1.0.0.rc4 → 1.0.0.rc6
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/ruby.yml +36 -0
- data/.gitignore +1 -0
- data/.rubocop.yml +10 -1
- data/.ruby-version +1 -0
- data/CHANGELOG.md +13 -0
- data/Gemfile.lock +12 -10
- data/README.md +59 -9
- data/ledger_sync-domains.gemspec +1 -1
- data/lib/ledger_sync/domains/operation/add.rb +2 -3
- data/lib/ledger_sync/domains/operation/find.rb +2 -3
- data/lib/ledger_sync/domains/operation/remove.rb +2 -3
- data/lib/ledger_sync/domains/operation/resource.rb +25 -0
- data/lib/ledger_sync/domains/operation/search.rb +2 -3
- data/lib/ledger_sync/domains/operation/transition.rb +2 -3
- data/lib/ledger_sync/domains/operation/update.rb +2 -3
- data/lib/ledger_sync/domains/operation.rb +73 -34
- data/lib/ledger_sync/domains/serializer/relation.rb +120 -0
- data/lib/ledger_sync/domains/serializer/struct.rb +69 -0
- data/lib/ledger_sync/domains/serializer.rb +6 -64
- data/lib/ledger_sync/domains/store.rb +39 -0
- data/lib/ledger_sync/domains/version.rb +1 -1
- data/lib/ledger_sync/domains.rb +21 -1
- metadata +13 -8
- data/.github/workflows/main.yml +0 -18
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b26c844bc9d5327343fc366e9854181829cc53f9b06c2d2de0e8fcd9f0a34d81
|
4
|
+
data.tar.gz: 858fd914201d49fc83d24476997f6c2ae8e96d8e627bbc10507ed3e4015826ed
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: baba4dde6e0410790569e69df334e7b0891d9d7aa649a500bdd30cca130735604f654f98d2f341d82f1bf60586ed2705e5cb5ca52e8446f8a007c7c69c70de7e
|
7
|
+
data.tar.gz: 11b56944c28b7b200117875564ea2eaa7a25b0af883beb5c2ac9db5e66f1f0040c1895abc3ac1b109d8ab70367d023842d8fa828cc50a4c7a0f6c0b97247589e
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# This workflow uses actions that are not certified by GitHub.
|
2
|
+
# They are provided by a third-party and are governed by
|
3
|
+
# separate terms of service, privacy policy, and support
|
4
|
+
# documentation.
|
5
|
+
# This workflow will download a prebuilt Ruby version, install dependencies and run tests with Rake
|
6
|
+
# For more information see: https://github.com/marketplace/actions/setup-ruby-jruby-and-truffleruby
|
7
|
+
|
8
|
+
name: Ruby
|
9
|
+
|
10
|
+
on:
|
11
|
+
push:
|
12
|
+
branches: [main]
|
13
|
+
pull_request:
|
14
|
+
branches: [main]
|
15
|
+
|
16
|
+
jobs:
|
17
|
+
test:
|
18
|
+
runs-on: ubuntu-latest
|
19
|
+
strategy:
|
20
|
+
matrix:
|
21
|
+
ruby-version: ["2.7", "3.0"]
|
22
|
+
|
23
|
+
steps:
|
24
|
+
- uses: actions/checkout@v2
|
25
|
+
- name: Set up Ruby
|
26
|
+
# To automatically get bug fixes and new Ruby versions for ruby/setup-ruby,
|
27
|
+
# change this to (see https://github.com/ruby/setup-ruby#versioning):
|
28
|
+
# uses: ruby/setup-ruby@v1
|
29
|
+
uses: ruby/setup-ruby@473e4d8fe5dd94ee328fdfca9f8c9c7afc9dae5e
|
30
|
+
with:
|
31
|
+
ruby-version: ${{ matrix.ruby-version }}
|
32
|
+
bundler-cache: true # runs 'bundle install' and caches installed gems automatically
|
33
|
+
- name: Rspec
|
34
|
+
run: bundle exec rspec
|
35
|
+
- name: Rubocop
|
36
|
+
run: bundle exec rubocop
|
data/.gitignore
CHANGED
data/.rubocop.yml
CHANGED
data/.ruby-version
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
ruby-2.7.2
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,18 @@
|
|
1
1
|
## [Unreleased]
|
2
2
|
|
3
|
+
## [1.0.0.rc5] - 2012-12-16
|
4
|
+
|
5
|
+
- Fix: Bring back Resource to pre-defined operations
|
6
|
+
|
7
|
+
## [1.0.0.rc5] - 2021-11-05
|
8
|
+
|
9
|
+
- Add: Introduce Serializer::Relation to proxy AR Queries
|
10
|
+
- Refactor Struct out of Serializer
|
11
|
+
|
12
|
+
## [1.0.0.rc4] - 2021-09-23
|
13
|
+
|
14
|
+
- Add: Additional operations
|
15
|
+
|
3
16
|
## [1.0.0.rc3] - 2021-09-06
|
4
17
|
|
5
18
|
- Fix: Operation::Transition uses correct module name
|
data/Gemfile.lock
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
ledger_sync-domains (1.0.0.
|
4
|
+
ledger_sync-domains (1.0.0.rc6)
|
5
5
|
ledger_sync (~> 2.2.0)
|
6
6
|
|
7
7
|
GEM
|
@@ -69,8 +69,8 @@ GEM
|
|
69
69
|
faraday-rack (~> 1.0)
|
70
70
|
multipart-post (>= 1.2, < 3)
|
71
71
|
ruby2_keywords (>= 0.0.4)
|
72
|
-
faraday-detailed_logger (2.
|
73
|
-
faraday (>= 0.
|
72
|
+
faraday-detailed_logger (2.4.2)
|
73
|
+
faraday (>= 0.16, < 2)
|
74
74
|
faraday-em_http (1.0.0)
|
75
75
|
faraday-em_synchrony (1.0.0)
|
76
76
|
faraday-excon (1.1.0)
|
@@ -79,15 +79,15 @@ GEM
|
|
79
79
|
faraday-net_http_persistent (1.2.0)
|
80
80
|
faraday-patron (1.0.0)
|
81
81
|
faraday-rack (1.0.0)
|
82
|
-
faraday_middleware (1.
|
82
|
+
faraday_middleware (1.2.0)
|
83
83
|
faraday (~> 1.0)
|
84
84
|
fingerprintable (1.2.1)
|
85
85
|
colorize
|
86
86
|
i18n (1.8.10)
|
87
87
|
concurrent-ruby (~> 1.0)
|
88
|
-
|
88
|
+
ipaddr (1.2.3)
|
89
|
+
ledger_sync (2.2.3)
|
89
90
|
activemodel
|
90
|
-
colorize
|
91
91
|
dry-schema (~> 1.5.4)
|
92
92
|
dry-validation (~> 1.5.6)
|
93
93
|
faraday
|
@@ -98,19 +98,21 @@ GEM
|
|
98
98
|
openssl (~> 2.2.0)
|
99
99
|
pd_ruby
|
100
100
|
rack (~> 2.2.3)
|
101
|
+
rainbow (~> 3.0)
|
101
102
|
resonad
|
102
103
|
simply_serializable (>= 1.5.1)
|
103
104
|
minitest (5.14.4)
|
104
105
|
multipart-post (2.1.1)
|
105
|
-
nokogiri (1.12.
|
106
|
+
nokogiri (1.12.5-x86_64-linux)
|
106
107
|
racc (~> 1.4)
|
107
|
-
openssl (2.2.
|
108
|
+
openssl (2.2.1)
|
109
|
+
ipaddr
|
108
110
|
parallel (1.20.1)
|
109
111
|
parser (3.0.2.0)
|
110
112
|
ast (~> 2.4.1)
|
111
113
|
pd_ruby (0.2.3)
|
112
114
|
colorize
|
113
|
-
racc (1.
|
115
|
+
racc (1.6.0)
|
114
116
|
rack (2.2.3)
|
115
117
|
rainbow (3.0.0)
|
116
118
|
rake (13.0.6)
|
@@ -163,4 +165,4 @@ DEPENDENCIES
|
|
163
165
|
rubocop (~> 1.7)
|
164
166
|
|
165
167
|
BUNDLED WITH
|
166
|
-
2.2.
|
168
|
+
2.2.26
|
data/README.md
CHANGED
@@ -6,7 +6,7 @@ LedgerSync has been developed to handle cross-service (API) communication in ele
|
|
6
6
|
|
7
7
|
Operations are great to ensure there is single point to perform specific action. If the return object is regular ActiveRecord Model object, there is nothing that stops developer from accessing cross-domain relationship, updating it or calling another action on it. You can have rubocop scanning through the code and screaming every time it finds something fishy, or you can just stop passing ActiveRecord objects around. And that is where `LedgerSync::Serializer` comes handy. It gives you simple way to define how your object should look towards specific domain. Instead of passing ruby hash(es), you work with `OpenStruct` objects that supports relationships.
|
8
8
|
|
9
|
-
Use LedgerSync
|
9
|
+
Use `LedgerSync::Operation` to trigger actions from other domains and `LedgerSync::Serializer` to pass around serialized objects instead of ActiveRecord Models. ActiveRecord Models are compatible with LedgerSync Resources and can be serialized to OpenStruct objects automatically.
|
10
10
|
|
11
11
|
## Installation
|
12
12
|
|
@@ -27,9 +27,41 @@ Or install it yourself as:
|
|
27
27
|
## Usage
|
28
28
|
|
29
29
|
LedgerSync comes with 3 components.
|
30
|
-
1.
|
31
|
-
2.
|
32
|
-
3.
|
30
|
+
1. Registration
|
31
|
+
2. Resource
|
32
|
+
3. Serializers
|
33
|
+
4. Operations
|
34
|
+
|
35
|
+
### Register your engines
|
36
|
+
|
37
|
+
`LedgerSync::Domains` requires little configuration so it can properly pick up domains and their namespace. While `LedgerSync::Domains` is not required to be used with Rails, it is the most expected usecase. THe problem with Rails (and possibly other codebases) is that the `MainApp` does not have a module namespace and referencing it from other domain operations is not as simple. There are two methods to help you register.
|
38
|
+
|
39
|
+
#### Register your Main App
|
40
|
+
|
41
|
+
`register_main_domain` creates a domain with name `:main` without any `base_module`. This way you can reference `MainApp` as `domain: :main` and serializers/operations will be picked up from there.
|
42
|
+
|
43
|
+
```ruby
|
44
|
+
# config/application.rb
|
45
|
+
module DropBot
|
46
|
+
class Application < Rails::Application
|
47
|
+
LedgerSync::Domains.register_main_domain
|
48
|
+
# ...
|
49
|
+
end
|
50
|
+
end
|
51
|
+
```
|
52
|
+
|
53
|
+
#### Register your Engine
|
54
|
+
|
55
|
+
`register_domain` creates a domain with your own `name` and your own `base_module`. Lets say you place your engines under `engines` folder and you just created `billing` engine. To register it as a domain, update your `engine.rb` file. After that use it in your operations as `domain: :engine`. You can pass in string or symbol.
|
56
|
+
```ruby
|
57
|
+
# engines/billing/lib/billing/engine.rb
|
58
|
+
module Billing
|
59
|
+
class Engine < ::Rails::Engine
|
60
|
+
isolate_namespace Billing
|
61
|
+
LedgerSync::Domains.register_domain(:billing, base_module: Billing)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
```
|
33
65
|
|
34
66
|
### Resource/Model
|
35
67
|
|
@@ -186,7 +218,7 @@ module Auth
|
|
186
218
|
return failure('Not found') if resource.nil?
|
187
219
|
|
188
220
|
if resource.update(active: false)
|
189
|
-
success(
|
221
|
+
success(resource)
|
190
222
|
else
|
191
223
|
failure('Unable to deactivate')
|
192
224
|
end
|
@@ -208,10 +240,12 @@ module Auth
|
|
208
240
|
end
|
209
241
|
end
|
210
242
|
```
|
211
|
-
|
243
|
+
|
244
|
+
Successful result of an operation should be serialized resource(s). Operations automatically perform success result serialization for you. Any `ActiveRecord::Base` or `LedgerSync::Resource` object will be automatically serialized through matching serializer for the target domain. Any other value will remain untouched. Hashes and arrays are deep-serialized, so you can return hash or array including multiple ActiveRecord objects. Operation by itself doesn't know what domain triggered it, and therefore it requires target domain to be passed either as a module, string or symbol. Here is how that looks for the example above.
|
245
|
+
|
212
246
|
|
213
247
|
```ruby
|
214
|
-
irb(main):001:0> op = Auth::Users::FindOperation.new(id: 1, limit: {},
|
248
|
+
irb(main):001:0> op = Auth::Users::FindOperation.new(id: 1, limit: {}, domain: Client)
|
215
249
|
=> #<Auth::Users::FindOperation:0x0000555937876cf8 @serializer=#<Auth::Users::ClientSerializer:0x0000555937876dc0>, @deserializer=nil...
|
216
250
|
irb(main):002:0> op.perform
|
217
251
|
=> #<LedgerSync::Domains::Operation::OperationResult::Success:0x00005559372f9d70 @meta=nil, @value=#<OpenStruct email="test@ledger_sync.dev">>
|
@@ -221,17 +255,33 @@ irb(main):003:0> op.result.value
|
|
221
255
|
|
222
256
|
And thats it. Now you can use `LedgerSync` to define operations and have their results serialized against specific domain you are requesting it from.
|
223
257
|
|
258
|
+
#### Internal operations
|
259
|
+
|
260
|
+
Sometimes you want to create operation, that is not accessible from the rest of the app. The nature of ruby allows all defined classes be accessible from everywhere. To prevent execution of an operation from different domain, use `internal` flag when defining class.
|
261
|
+
|
262
|
+
```ruby
|
263
|
+
module Auth
|
264
|
+
module Users
|
265
|
+
class FindOperation < LedgerSync::Domains::Operation::Find
|
266
|
+
internal
|
267
|
+
end
|
268
|
+
end
|
269
|
+
end
|
270
|
+
```
|
271
|
+
|
272
|
+
When performing an operation there are series of guards. First one validates if operation is allowed to be executed. That means either it is not flagged as internal operation, or target domain is same domain as module operation is defined in. If operation is not allowed to be executed, failure is returned with `LedgerSync::Domains::InternalOperationError` error.
|
273
|
+
|
224
274
|
### Cross-domain relationships
|
225
275
|
|
226
276
|
One important note about relationships. Splitting your app into multiple engines is eventually gonna lead to your ActiveRecord Models to have relationships that reference Models from other engines. There are two ways how to look at this issue.
|
227
277
|
|
228
278
|
#### Use of cross-domain relationships is bad
|
229
279
|
|
230
|
-
In this case, you don't define them. Or if you do, you override reader method to raise exception. If `user` references `customer`, you don't access it through `user.customer`, but you retrieve it through operation `Engine::Customers::FindOperation.new(id: user.customer_id)`. This is a clean solution, but it will lead to N+1 queries.
|
280
|
+
In this case, you don't define them. Or if you do, you override reader method to raise exception. If `user` references `customer`, you don't access it through `user.customer`, but you retrieve it through operation `Engine::Customers::FindOperation.new(id: user.customer_id, domain: 'OtherEngine')`. This is a clean solution, but it will lead to N+1 queries.
|
231
281
|
|
232
282
|
#### Use of cross-domain relationships is fine
|
233
283
|
|
234
|
-
If you
|
284
|
+
If you try to get into the root of an issue, you will realize that resources reference each other is not really the source of it. The problem is that if you work with ActiveRecord objects, there is nothing stopping you from accessing related records and modifying them.
|
235
285
|
|
236
286
|
That cannot happen when accessing objects from other domains. In that case you retrieve serialized object through operation. Accessing relationships through serialized object will always return serialized relationship.
|
237
287
|
|
data/ledger_sync-domains.gemspec
CHANGED
@@ -12,7 +12,7 @@ Gem::Specification.new do |spec|
|
|
12
12
|
spec.description = 'Use LedgerSync Operations and Serializers for cross-domain communication.'
|
13
13
|
spec.homepage = 'https://engineering.dropbot.sh'
|
14
14
|
spec.license = 'MIT'
|
15
|
-
spec.required_ruby_version = Gem::Requirement.new('>=
|
15
|
+
spec.required_ruby_version = Gem::Requirement.new('>= 2.7.2')
|
16
16
|
|
17
17
|
# spec.metadata["allowed_push_host"] = "TODO: Set to 'http://mygemserver.com'"
|
18
18
|
|
@@ -1,13 +1,12 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require_relative '../operation'
|
4
|
+
require_relative 'resource'
|
4
5
|
|
5
6
|
module LedgerSync
|
6
7
|
module Domains
|
7
8
|
class Operation
|
8
|
-
class Add
|
9
|
-
include LedgerSync::Domains::Operation::Mixin
|
10
|
-
|
9
|
+
class Add < Resource
|
11
10
|
private
|
12
11
|
|
13
12
|
def operate
|
@@ -1,13 +1,12 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require_relative '../operation'
|
4
|
+
require_relative 'resource'
|
4
5
|
|
5
6
|
module LedgerSync
|
6
7
|
module Domains
|
7
8
|
class Operation
|
8
|
-
class Find
|
9
|
-
include LedgerSync::Domains::Operation::Mixin
|
10
|
-
|
9
|
+
class Find < Resource
|
11
10
|
class Contract < LedgerSync::Ledgers::Contract
|
12
11
|
params do
|
13
12
|
required(:id).filled(:integer)
|
@@ -1,13 +1,12 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require_relative '../operation'
|
4
|
+
require_relative 'resource'
|
4
5
|
|
5
6
|
module LedgerSync
|
6
7
|
module Domains
|
7
8
|
class Operation
|
8
|
-
class Remove
|
9
|
-
include LedgerSync::Domains::Operation::Mixin
|
10
|
-
|
9
|
+
class Remove < Resource
|
11
10
|
private
|
12
11
|
|
13
12
|
def operate
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module LedgerSync
|
4
|
+
module Domains
|
5
|
+
class Operation
|
6
|
+
class Resource
|
7
|
+
include LedgerSync::Domains::Operation::Mixin
|
8
|
+
|
9
|
+
def resource_class
|
10
|
+
@resource_class ||= inferred_resource_class
|
11
|
+
end
|
12
|
+
|
13
|
+
private
|
14
|
+
|
15
|
+
def inferred_resource_class
|
16
|
+
name = self.class.to_s.split('::')
|
17
|
+
name.pop # remove serializer/operation class from name
|
18
|
+
resource = name.pop.singularize # pluralized resource module name
|
19
|
+
|
20
|
+
self.class.const_get((name + [resource]).join('::'))
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -1,13 +1,12 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require_relative '../operation'
|
4
|
+
require_relative 'resource'
|
4
5
|
|
5
6
|
module LedgerSync
|
6
7
|
module Domains
|
7
8
|
class Operation
|
8
|
-
class Search
|
9
|
-
include LedgerSync::Domains::Operation::Mixin
|
10
|
-
|
9
|
+
class Search < Resource
|
11
10
|
# make kaminari work with serialized results
|
12
11
|
class PaginatedResult < SimpleDelegator
|
13
12
|
attr_accessor :next_page, :last_page, :current_page, :total_pages,
|
@@ -1,13 +1,12 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require_relative '../operation'
|
4
|
+
require_relative 'resource'
|
4
5
|
|
5
6
|
module LedgerSync
|
6
7
|
module Domains
|
7
8
|
class Operation
|
8
|
-
class Transition
|
9
|
-
include LedgerSync::Domains::Operation::Mixin
|
10
|
-
|
9
|
+
class Transition < Resource
|
11
10
|
class Contract < LedgerSync::Ledgers::Contract
|
12
11
|
params do
|
13
12
|
required(:model_name).filled(:string)
|
@@ -1,13 +1,12 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require_relative '../operation'
|
4
|
+
require_relative 'resource'
|
4
5
|
|
5
6
|
module LedgerSync
|
6
7
|
module Domains
|
7
8
|
class Operation
|
8
|
-
class Update
|
9
|
-
include LedgerSync::Domains::Operation::Mixin
|
10
|
-
|
9
|
+
class Update < Resource
|
11
10
|
private
|
12
11
|
|
13
12
|
def operate
|
@@ -1,7 +1,16 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
unless Object.const_defined?('ActiveRecord')
|
4
|
+
# reopen AR module for non-rails apps
|
5
|
+
module ActiveRecord
|
6
|
+
module Base; end
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
3
10
|
module LedgerSync
|
4
11
|
module Domains
|
12
|
+
class InternalOperationError < LedgerSync::Error::OperationError; end
|
13
|
+
|
5
14
|
class Operation
|
6
15
|
class OperationResult
|
7
16
|
module ResultTypeBase
|
@@ -22,26 +31,16 @@ module LedgerSync
|
|
22
31
|
include LedgerSync::ResultBase
|
23
32
|
end
|
24
33
|
|
25
|
-
module Mixin
|
34
|
+
module Mixin # rubocop:disable Metrics/ModuleLength
|
26
35
|
module ClassMethods
|
27
|
-
|
28
|
-
name = to_s.split('::')
|
29
|
-
name.pop # remove serializer/operation class from name
|
30
|
-
resource = name.pop.singularize # pluralized resource module name
|
31
|
-
|
32
|
-
const_get((name + [resource]).join('::'))
|
33
|
-
end
|
34
|
-
|
35
|
-
def inferred_serializer_class
|
36
|
-
const_get("#{inferred_resource_class}Serializer")
|
37
|
-
end
|
36
|
+
@internal = false
|
38
37
|
|
39
|
-
def
|
40
|
-
|
38
|
+
def internal
|
39
|
+
@internal = true
|
41
40
|
end
|
42
41
|
|
43
|
-
def
|
44
|
-
|
42
|
+
def internal?
|
43
|
+
!!@internal
|
45
44
|
end
|
46
45
|
end
|
47
46
|
|
@@ -61,14 +60,22 @@ module LedgerSync
|
|
61
60
|
|
62
61
|
attr_reader :params, :result
|
63
62
|
|
64
|
-
def initialize(
|
65
|
-
@
|
66
|
-
@deserializer = deserializer
|
63
|
+
def initialize(domain:, **params)
|
64
|
+
@domain = domain
|
67
65
|
@params = params
|
68
66
|
@result = nil
|
69
67
|
end
|
70
68
|
|
71
69
|
def perform # rubocop:disable Metrics/MethodLength
|
70
|
+
unless allowed?
|
71
|
+
return failure(
|
72
|
+
LedgerSync::Domains::InternalOperationError.new(
|
73
|
+
operation: self,
|
74
|
+
message: 'Cross-domain operation execution is not allowed'
|
75
|
+
)
|
76
|
+
)
|
77
|
+
end
|
78
|
+
|
72
79
|
if performed?
|
73
80
|
return failure(
|
74
81
|
LedgerSync::Error::OperationError::PerformedOperationError.new(
|
@@ -93,32 +100,47 @@ module LedgerSync
|
|
93
100
|
end
|
94
101
|
end
|
95
102
|
|
103
|
+
def allowed?
|
104
|
+
return true unless self.class.internal?
|
105
|
+
|
106
|
+
local_domain == @domain
|
107
|
+
end
|
108
|
+
|
96
109
|
def performed?
|
97
110
|
@performed == true
|
98
111
|
end
|
99
112
|
|
100
113
|
def serialize(resource:)
|
101
|
-
|
114
|
+
serializer_for(resource: resource).serialize(resource: resource)
|
102
115
|
end
|
103
116
|
|
104
|
-
def
|
105
|
-
|
117
|
+
def serializer_for(resource:)
|
118
|
+
serializer_class_for(resource: resource).new
|
106
119
|
end
|
107
120
|
|
108
|
-
def
|
109
|
-
|
121
|
+
def serializer_class_for(resource:)
|
122
|
+
Object.const_get(
|
123
|
+
[
|
124
|
+
serializer_module_for(resource: resource),
|
125
|
+
"#{domain}Serializer"
|
126
|
+
].join('::')
|
127
|
+
)
|
110
128
|
end
|
111
129
|
|
112
|
-
def
|
113
|
-
|
130
|
+
def serializer_module_for(resource:)
|
131
|
+
(
|
132
|
+
resource.class.try(:serializer_module) || resource.class
|
133
|
+
).to_s.pluralize
|
114
134
|
end
|
115
135
|
|
116
|
-
def
|
117
|
-
|
136
|
+
def domain
|
137
|
+
LedgerSync::Domains.domains.module_for(domain: @domain)
|
118
138
|
end
|
119
139
|
|
120
|
-
def
|
121
|
-
|
140
|
+
def local_domain
|
141
|
+
LedgerSync::Domains.domains.domain_for(
|
142
|
+
base_module: self.class.to_s.split('::').first.constantize
|
143
|
+
)
|
122
144
|
end
|
123
145
|
|
124
146
|
# Results
|
@@ -132,7 +154,20 @@ module LedgerSync
|
|
132
154
|
end
|
133
155
|
|
134
156
|
def success(value, meta: nil)
|
135
|
-
@result = OperationResult.Success(value, meta: meta)
|
157
|
+
@result = OperationResult.Success(deep_serialize(value), meta: meta)
|
158
|
+
end
|
159
|
+
|
160
|
+
def deep_serialize(value)
|
161
|
+
case value
|
162
|
+
when ActiveRecord::Base, LedgerSync::Resource
|
163
|
+
serialize(resource: value)
|
164
|
+
when Hash
|
165
|
+
value.transform_values { deep_serialize(_1) }
|
166
|
+
when Array
|
167
|
+
value.map { deep_serialize(_1) }
|
168
|
+
else
|
169
|
+
value
|
170
|
+
end
|
136
171
|
end
|
137
172
|
|
138
173
|
def success?
|
@@ -145,13 +180,17 @@ module LedgerSync
|
|
145
180
|
|
146
181
|
def validate
|
147
182
|
LedgerSync::Util::Validator.new(
|
148
|
-
contract:
|
183
|
+
contract: validation_contract_class,
|
149
184
|
data: params
|
150
185
|
).validate
|
151
186
|
end
|
152
187
|
|
153
|
-
def
|
154
|
-
@
|
188
|
+
def validation_contract_class
|
189
|
+
@validation_contract_class ||= inferred_validation_contract_class
|
190
|
+
end
|
191
|
+
|
192
|
+
def inferred_validation_contract_class
|
193
|
+
self.class.const_get('Contract')
|
155
194
|
end
|
156
195
|
|
157
196
|
def errors
|
@@ -0,0 +1,120 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module LedgerSync
|
4
|
+
module Domains
|
5
|
+
class Serializer < LedgerSync::Serializer
|
6
|
+
class Relation
|
7
|
+
def initialize(serializer:, resource:, attribute:)
|
8
|
+
@query = resource.try(attribute)
|
9
|
+
@serializer = serializer
|
10
|
+
end
|
11
|
+
|
12
|
+
QUERY_METHODS = %w[
|
13
|
+
distinct distinct! eager_load eager_load! extending extending!
|
14
|
+
from from! group group! having having! includes includes!
|
15
|
+
joins joins! left_outer_joins left_outer_joins!
|
16
|
+
limit limit! offset offset! order order! preload preload!
|
17
|
+
references references! unscope unscope! where where!
|
18
|
+
load reload reset
|
19
|
+
].freeze # lock lock!
|
20
|
+
READER_METHODS = %w[
|
21
|
+
fifth fifth! find find_by find_by! first first! forty_two forty_two!
|
22
|
+
fourth fourth! last last! second second! second_to_last
|
23
|
+
second_to_last! third third! third_to_last third_to_last!
|
24
|
+
].freeze
|
25
|
+
READERS_METHODS = %w[find_each each map to_ary].freeze
|
26
|
+
# in_batches does not make sense as it renders relation
|
27
|
+
READERS_BATCH_METHODS = %w[find_in_batches].freeze
|
28
|
+
INSPECT_METHODS = %w[
|
29
|
+
any? blank? empty? explain many? none? one? size to_sql exists? count
|
30
|
+
ids maximum minimum sum none none! loaded?
|
31
|
+
].freeze
|
32
|
+
|
33
|
+
QUERY_METHODS.each do |name|
|
34
|
+
define_method(name) do |*args|
|
35
|
+
@query = @query.send(name, *args)
|
36
|
+
|
37
|
+
self
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
READER_METHODS.each do |name|
|
42
|
+
define_method(name) do |*args, **params, &block|
|
43
|
+
item = @query.send(name, *args, **params, &block)
|
44
|
+
|
45
|
+
@serializer.serialize(resource: item)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
READERS_METHODS.each do |name|
|
50
|
+
define_method(name) do |*args, **params, &block|
|
51
|
+
@query.send(name, *args, **params).map do |item|
|
52
|
+
resource = @serializer.serialize(resource: item)
|
53
|
+
|
54
|
+
block&.yield(resource) || resource
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
READERS_BATCH_METHODS.each do |name|
|
60
|
+
define_method(name) do |*args, **params, &block|
|
61
|
+
@query.send(name, *args, **params).each do |batch|
|
62
|
+
resources = batch.map do |item|
|
63
|
+
@serializer.serialize(resource: item)
|
64
|
+
end
|
65
|
+
|
66
|
+
block&.yield(resources)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
INSPECT_METHODS.each do |name|
|
72
|
+
define_method(name) do |*args|
|
73
|
+
@query.send(name, *args)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def inspect(*_args)
|
78
|
+
entries = to_ary.take(11).map!(&:inspect)
|
79
|
+
|
80
|
+
entries[10] = '...' if entries.size == 11
|
81
|
+
|
82
|
+
"#<#{self.class.name} [#{entries.join(', ')}]>"
|
83
|
+
end
|
84
|
+
|
85
|
+
def as_json(*_args)
|
86
|
+
to_ary.map!(&:as_json)
|
87
|
+
end
|
88
|
+
|
89
|
+
# pass through Enumerable methods
|
90
|
+
def respond_to_missing?(name, _include_private)
|
91
|
+
Enumerable.instance_methods.include?(name.to_sym)
|
92
|
+
end
|
93
|
+
|
94
|
+
def method_missing(name, *args, **params, &block)
|
95
|
+
return super unless Enumerable.instance_methods.include?(name.to_sym)
|
96
|
+
|
97
|
+
to_ary.send(name, *args, **params, &block)
|
98
|
+
end
|
99
|
+
|
100
|
+
class SerializerReferencesOneType
|
101
|
+
def proxy(serializer:, resource:, attribute:)
|
102
|
+
item = resource.try(attribute)
|
103
|
+
|
104
|
+
serializer.serialize(resource: item)
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
class SerializerReferencesManyType
|
109
|
+
def proxy(serializer:, resource:, attribute:)
|
110
|
+
Relation.new(
|
111
|
+
serializer: serializer,
|
112
|
+
resource: resource,
|
113
|
+
attribute: attribute
|
114
|
+
)
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'relation'
|
4
|
+
|
5
|
+
module LedgerSync
|
6
|
+
module Domains
|
7
|
+
class Serializer < LedgerSync::Serializer
|
8
|
+
class Struct
|
9
|
+
# This is the most ruby hackery I ever did
|
10
|
+
# Defining Record class that inherits from SimpleDelegator is not
|
11
|
+
# enough. Adding methods through define_method was adding these methods
|
12
|
+
# to all objects created through this main class. Specifically address
|
13
|
+
# record has an attribute called address, which returned serialized
|
14
|
+
# address. This is a same approach, but with dynamic class definition
|
15
|
+
# to avoid defining methods in unrelated classes. We are using
|
16
|
+
# SimpleDelegator to delegate these methods into OpenStruct passed in.
|
17
|
+
# Pure hackery.
|
18
|
+
def self.build(hash, serializer_name, resource:, references:) # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
|
19
|
+
klass = Class.new(SimpleDelegator) do
|
20
|
+
def self.with_lazy_references(hash, struct_class:, resource:, references:) # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
|
21
|
+
define_method('valid?') { resource.valid? }
|
22
|
+
define_method('errors') { resource.errors }
|
23
|
+
define_method('to_hash') { hash }
|
24
|
+
references.each do |args|
|
25
|
+
define_method(args.hash_attribute) do
|
26
|
+
Relation.const_get(
|
27
|
+
args.type.class.to_s.split('::').last
|
28
|
+
).new.proxy(
|
29
|
+
serializer: args.type.serializer.new,
|
30
|
+
resource: resource,
|
31
|
+
attribute: args.resource_attribute
|
32
|
+
)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
new(struct_class.new(hash))
|
37
|
+
end
|
38
|
+
|
39
|
+
def to_param
|
40
|
+
id.to_s
|
41
|
+
end
|
42
|
+
|
43
|
+
def persisted?
|
44
|
+
id.present?
|
45
|
+
end
|
46
|
+
|
47
|
+
def to_json(*args)
|
48
|
+
JSON.generate(to_hash, *args)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
name = serializer_name.split('::')
|
53
|
+
class_name = name.pop.gsub(/[^0-9a-z ]/i, '').gsub(/.*\KSerializer/, '')
|
54
|
+
struct_name = "#{class_name}Struct"
|
55
|
+
module_name = name.empty? ? Object : Object.const_get(name.join('::'))
|
56
|
+
unless module_name.const_defined?(struct_name)
|
57
|
+
module_name.const_set(struct_name, Class.new(OpenStruct))
|
58
|
+
end
|
59
|
+
|
60
|
+
klass.with_lazy_references(
|
61
|
+
hash,
|
62
|
+
struct_class: module_name.const_get(struct_name),
|
63
|
+
resource: resource, references: references
|
64
|
+
)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -1,71 +1,10 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require_relative 'serializer/struct'
|
4
|
+
|
3
5
|
module LedgerSync
|
4
6
|
module Domains
|
5
7
|
class Serializer < LedgerSync::Serializer
|
6
|
-
def create_record_from(hash, resource:, references:) # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
|
7
|
-
# This is the most ruby hackery I ever did
|
8
|
-
# Defining Record class that inherits from SimpleDelegator is not
|
9
|
-
# enough. Adding methods through define_method was adding these methods
|
10
|
-
# to all objects created through this main class. Specifically address
|
11
|
-
# record has an attribute called address, which returned serialized
|
12
|
-
# address. This is a same approach, but with dynamic class definition
|
13
|
-
# to avoid defining methods in unrelated classes. We are using
|
14
|
-
# SimpleDelegator to delegate these methods into OpenStruct passed in.
|
15
|
-
# Pure hackery.
|
16
|
-
klass = Class.new(SimpleDelegator) do
|
17
|
-
def self.with_lazy_references(hash, struct_class:, resource:, references:) # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
|
18
|
-
define_method('valid?') { resource.valid? }
|
19
|
-
define_method('errors') { resource.errors }
|
20
|
-
define_method('to_hash') { hash }
|
21
|
-
references.each do |args|
|
22
|
-
define_method(args.hash_attribute) do
|
23
|
-
if args.references_many?
|
24
|
-
resource.try(args.resource_attribute).each do |item|
|
25
|
-
args.type.serializer.new.serialize(resource: item)
|
26
|
-
end
|
27
|
-
else
|
28
|
-
item = resource.try(args.resource_attribute)
|
29
|
-
next if item.nil?
|
30
|
-
|
31
|
-
args.type.serializer.new.serialize(resource: item)
|
32
|
-
end
|
33
|
-
end
|
34
|
-
end
|
35
|
-
|
36
|
-
new(struct_class.new(hash))
|
37
|
-
end
|
38
|
-
|
39
|
-
def to_param
|
40
|
-
id.to_s
|
41
|
-
end
|
42
|
-
|
43
|
-
def persisted?
|
44
|
-
id.present?
|
45
|
-
end
|
46
|
-
|
47
|
-
def to_json(*args)
|
48
|
-
JSON.generate(to_hash, *args)
|
49
|
-
end
|
50
|
-
end
|
51
|
-
|
52
|
-
name = self.class.to_s.split('::')
|
53
|
-
class_name = name.pop.gsub(/[^0-9a-z ]/i, '').gsub(/.*\KSerializer/, '')
|
54
|
-
struct_name = "#{class_name}Struct"
|
55
|
-
module_name = name.empty? ? Object : Object.const_get(name.join('::'))
|
56
|
-
module_name.const_set(struct_name, Class.new(OpenStruct))
|
57
|
-
|
58
|
-
klass.with_lazy_references(
|
59
|
-
hash,
|
60
|
-
struct_class: module_name.const_get(struct_name),
|
61
|
-
resource: resource, references: references
|
62
|
-
)
|
63
|
-
end
|
64
|
-
|
65
|
-
def self.references
|
66
|
-
@references ||= []
|
67
|
-
end
|
68
|
-
|
69
8
|
def self.split_attributes
|
70
9
|
regular = []
|
71
10
|
references = []
|
@@ -98,7 +37,10 @@ module LedgerSync
|
|
98
37
|
other_hash: serializer_attribute.hash_attribute_hash_for(resource: resource)
|
99
38
|
)
|
100
39
|
end
|
101
|
-
|
40
|
+
Serializer::Struct.build(
|
41
|
+
ret, self.class.to_s,
|
42
|
+
resource: resource, references: references
|
43
|
+
)
|
102
44
|
end
|
103
45
|
end
|
104
46
|
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module LedgerSync
|
4
|
+
module Domains
|
5
|
+
class ConfigurationStore
|
6
|
+
attr_reader :configs
|
7
|
+
|
8
|
+
def initialize
|
9
|
+
@configs = {}
|
10
|
+
end
|
11
|
+
|
12
|
+
def register_domain(config:)
|
13
|
+
@configs[config.root_key] = config
|
14
|
+
end
|
15
|
+
|
16
|
+
def module_for(domain:)
|
17
|
+
@configs[domain]&.base_module
|
18
|
+
end
|
19
|
+
|
20
|
+
def domain_for(base_module:)
|
21
|
+
config = @configs.values.find { |c| c.base_module == base_module }
|
22
|
+
config ||= @configs[:main]
|
23
|
+
|
24
|
+
config.root_key
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
class Configuration
|
29
|
+
attr_accessor :name
|
30
|
+
attr_reader :root_key, :base_module
|
31
|
+
|
32
|
+
def initialize(root_key, base_module:)
|
33
|
+
@root_key = root_key.to_sym
|
34
|
+
@name = root_key.to_s.capitalize
|
35
|
+
@base_module = base_module
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
data/lib/ledger_sync/domains.rb
CHANGED
@@ -2,8 +2,10 @@
|
|
2
2
|
|
3
3
|
require 'ledger_sync'
|
4
4
|
require_relative 'domains/version'
|
5
|
+
require_relative 'domains/store'
|
5
6
|
require_relative 'domains/serializer'
|
6
7
|
require_relative 'domains/operation'
|
8
|
+
require_relative 'domains/operation/resource'
|
7
9
|
require_relative 'domains/operation/add'
|
8
10
|
require_relative 'domains/operation/find'
|
9
11
|
require_relative 'domains/operation/remove'
|
@@ -12,5 +14,23 @@ require_relative 'domains/operation/transition'
|
|
12
14
|
require_relative 'domains/operation/update'
|
13
15
|
|
14
16
|
module LedgerSync
|
15
|
-
module Domains
|
17
|
+
module Domains
|
18
|
+
def self.domains
|
19
|
+
@domains ||= LedgerSync::Domains::ConfigurationStore.new
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.register_domain(*args, **params)
|
23
|
+
config = LedgerSync::Domains::Configuration.new(*args, **params)
|
24
|
+
yield(config) if block_given?
|
25
|
+
|
26
|
+
domains.register_domain(config: config)
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.register_main_domain
|
30
|
+
config = LedgerSync::Domains::Configuration.new(:main, base_module: nil)
|
31
|
+
config.name = 'Main'
|
32
|
+
|
33
|
+
domains.register_domain(config: config)
|
34
|
+
end
|
35
|
+
end
|
16
36
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ledger_sync-domains
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0.0.
|
4
|
+
version: 1.0.0.rc6
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jozef Vaclavik
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2021-
|
11
|
+
date: 2021-12-16 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: ledger_sync
|
@@ -73,10 +73,11 @@ executables: []
|
|
73
73
|
extensions: []
|
74
74
|
extra_rdoc_files: []
|
75
75
|
files:
|
76
|
-
- ".github/workflows/
|
76
|
+
- ".github/workflows/ruby.yml"
|
77
77
|
- ".gitignore"
|
78
78
|
- ".rspec"
|
79
79
|
- ".rubocop.yml"
|
80
|
+
- ".ruby-version"
|
80
81
|
- CHANGELOG.md
|
81
82
|
- Gemfile
|
82
83
|
- Gemfile.lock
|
@@ -91,10 +92,14 @@ files:
|
|
91
92
|
- lib/ledger_sync/domains/operation/add.rb
|
92
93
|
- lib/ledger_sync/domains/operation/find.rb
|
93
94
|
- lib/ledger_sync/domains/operation/remove.rb
|
95
|
+
- lib/ledger_sync/domains/operation/resource.rb
|
94
96
|
- lib/ledger_sync/domains/operation/search.rb
|
95
97
|
- lib/ledger_sync/domains/operation/transition.rb
|
96
98
|
- lib/ledger_sync/domains/operation/update.rb
|
97
99
|
- lib/ledger_sync/domains/serializer.rb
|
100
|
+
- lib/ledger_sync/domains/serializer/relation.rb
|
101
|
+
- lib/ledger_sync/domains/serializer/struct.rb
|
102
|
+
- lib/ledger_sync/domains/store.rb
|
98
103
|
- lib/ledger_sync/domains/version.rb
|
99
104
|
homepage: https://engineering.dropbot.sh
|
100
105
|
licenses:
|
@@ -103,7 +108,7 @@ metadata:
|
|
103
108
|
homepage_uri: https://engineering.dropbot.sh
|
104
109
|
source_code_uri: https://github.com/sandbite/ledger_sync-domains
|
105
110
|
changelog_uri: https://github.com/sandbite/ledger_sync-domains/blob/main/CHANGELOG.md
|
106
|
-
post_install_message:
|
111
|
+
post_install_message:
|
107
112
|
rdoc_options: []
|
108
113
|
require_paths:
|
109
114
|
- lib
|
@@ -111,15 +116,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
111
116
|
requirements:
|
112
117
|
- - ">="
|
113
118
|
- !ruby/object:Gem::Version
|
114
|
-
version:
|
119
|
+
version: 2.7.2
|
115
120
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
116
121
|
requirements:
|
117
122
|
- - ">"
|
118
123
|
- !ruby/object:Gem::Version
|
119
124
|
version: 1.3.1
|
120
125
|
requirements: []
|
121
|
-
rubygems_version: 3.2.
|
122
|
-
signing_key:
|
126
|
+
rubygems_version: 3.2.9
|
127
|
+
signing_key:
|
123
128
|
specification_version: 4
|
124
129
|
summary: LedgerSync for Domains/Engines
|
125
130
|
test_files: []
|
data/.github/workflows/main.yml
DELETED
@@ -1,18 +0,0 @@
|
|
1
|
-
name: Ruby
|
2
|
-
|
3
|
-
on: [push,pull_request]
|
4
|
-
|
5
|
-
jobs:
|
6
|
-
build:
|
7
|
-
runs-on: ubuntu-latest
|
8
|
-
steps:
|
9
|
-
- uses: actions/checkout@v2
|
10
|
-
- name: Set up Ruby
|
11
|
-
uses: ruby/setup-ruby@v1
|
12
|
-
with:
|
13
|
-
ruby-version: 3.0.0
|
14
|
-
- name: Run the default task
|
15
|
-
run: |
|
16
|
-
gem install bundler -v 2.2.15
|
17
|
-
bundle install
|
18
|
-
bundle exec rake
|