ledger_sync-domains 1.0.0.rc1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.github/workflows/main.yml +18 -0
- data/.gitignore +11 -0
- data/.rspec +3 -0
- data/.rubocop.yml +16 -0
- data/CHANGELOG.md +5 -0
- data/Gemfile +12 -0
- data/Gemfile.lock +161 -0
- data/LICENSE.txt +21 -0
- data/README.md +254 -0
- data/Rakefile +12 -0
- data/bin/console +15 -0
- data/bin/setup +8 -0
- data/ledger_sync-domains.gemspec +34 -0
- data/lib/ledger_sync/domains/operation/add.rb +46 -0
- data/lib/ledger_sync/domains/operation/find.rb +49 -0
- data/lib/ledger_sync/domains/operation/remove.rb +48 -0
- data/lib/ledger_sync/domains/operation/search.rb +82 -0
- data/lib/ledger_sync/domains/operation/transition.rb +69 -0
- data/lib/ledger_sync/domains/operation/update.rb +48 -0
- data/lib/ledger_sync/domains/operation.rb +174 -0
- data/lib/ledger_sync/domains/serializer.rb +98 -0
- data/lib/ledger_sync/domains/version.rb +7 -0
- data/lib/ledger_sync/domains.rb +16 -0
- metadata +97 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 5ee91277b3f0600c5553ce5f2183da338ef451b649bbf452a9871d9aa4048f01
|
4
|
+
data.tar.gz: 3723fb3b32fcdffdc87cc4355b4b898f070cac3793f9278186f55137ff123c04
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 477cffd0916f91dfffcc6099758d597e8df1cb3f4b11aa0464e3e791b254bdccb2f6b0a3f53a2186298d17c8d41714925e6676f028d093af6b7430b172e414fc
|
7
|
+
data.tar.gz: 7b002212bff9beb39ee7b3c5fe87d6112ef56ab5f6392e7c9b2ed16e5630f3f3b5ba89efa0deba17ea912b5f289e661bf8f00e49d6d3073d781c1d3d61de4606
|
@@ -0,0 +1,18 @@
|
|
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
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.rubocop.yml
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
AllCops:
|
2
|
+
TargetRubyVersion: 3.0
|
3
|
+
|
4
|
+
Style/StringLiterals:
|
5
|
+
Enabled: true
|
6
|
+
EnforcedStyle: single_quotes
|
7
|
+
|
8
|
+
Style/StringLiteralsInInterpolation:
|
9
|
+
Enabled: true
|
10
|
+
EnforcedStyle: single_quotes
|
11
|
+
|
12
|
+
Layout/LineLength:
|
13
|
+
Max: 100
|
14
|
+
|
15
|
+
Style/Documentation:
|
16
|
+
Enabled: false
|
data/CHANGELOG.md
ADDED
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,161 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
ledger_sync-domains (1.0.0.rc1)
|
5
|
+
ledger_sync (~> 2.2.0)
|
6
|
+
|
7
|
+
GEM
|
8
|
+
remote: https://rubygems.org/
|
9
|
+
specs:
|
10
|
+
activemodel (6.1.4.1)
|
11
|
+
activesupport (= 6.1.4.1)
|
12
|
+
activesupport (6.1.4.1)
|
13
|
+
concurrent-ruby (~> 1.0, >= 1.0.2)
|
14
|
+
i18n (>= 1.6, < 2)
|
15
|
+
minitest (>= 5.1)
|
16
|
+
tzinfo (~> 2.0)
|
17
|
+
zeitwerk (~> 2.3)
|
18
|
+
ast (2.4.2)
|
19
|
+
colorize (0.8.1)
|
20
|
+
concurrent-ruby (1.1.9)
|
21
|
+
diff-lcs (1.4.4)
|
22
|
+
dotenv (2.7.6)
|
23
|
+
dry-configurable (0.12.1)
|
24
|
+
concurrent-ruby (~> 1.0)
|
25
|
+
dry-core (~> 0.5, >= 0.5.0)
|
26
|
+
dry-container (0.8.0)
|
27
|
+
concurrent-ruby (~> 1.0)
|
28
|
+
dry-configurable (~> 0.1, >= 0.1.3)
|
29
|
+
dry-core (0.7.1)
|
30
|
+
concurrent-ruby (~> 1.0)
|
31
|
+
dry-equalizer (0.3.0)
|
32
|
+
dry-inflector (0.2.1)
|
33
|
+
dry-initializer (3.0.4)
|
34
|
+
dry-logic (1.2.0)
|
35
|
+
concurrent-ruby (~> 1.0)
|
36
|
+
dry-core (~> 0.5, >= 0.5)
|
37
|
+
dry-schema (1.5.6)
|
38
|
+
concurrent-ruby (~> 1.0)
|
39
|
+
dry-configurable (~> 0.8, >= 0.8.3)
|
40
|
+
dry-core (~> 0.4)
|
41
|
+
dry-equalizer (~> 0.2)
|
42
|
+
dry-initializer (~> 3.0)
|
43
|
+
dry-logic (~> 1.0)
|
44
|
+
dry-types (~> 1.4)
|
45
|
+
dry-types (1.5.1)
|
46
|
+
concurrent-ruby (~> 1.0)
|
47
|
+
dry-container (~> 0.3)
|
48
|
+
dry-core (~> 0.5, >= 0.5)
|
49
|
+
dry-inflector (~> 0.1, >= 0.1.2)
|
50
|
+
dry-logic (~> 1.0, >= 1.0.2)
|
51
|
+
dry-validation (1.5.6)
|
52
|
+
concurrent-ruby (~> 1.0)
|
53
|
+
dry-container (~> 0.7, >= 0.7.1)
|
54
|
+
dry-core (~> 0.4)
|
55
|
+
dry-equalizer (~> 0.2)
|
56
|
+
dry-initializer (~> 3.0)
|
57
|
+
dry-schema (~> 1.5, >= 1.5.2)
|
58
|
+
faraday (1.7.1)
|
59
|
+
faraday-em_http (~> 1.0)
|
60
|
+
faraday-em_synchrony (~> 1.0)
|
61
|
+
faraday-excon (~> 1.1)
|
62
|
+
faraday-httpclient (~> 1.0.1)
|
63
|
+
faraday-net_http (~> 1.0)
|
64
|
+
faraday-net_http_persistent (~> 1.1)
|
65
|
+
faraday-patron (~> 1.0)
|
66
|
+
faraday-rack (~> 1.0)
|
67
|
+
multipart-post (>= 1.2, < 3)
|
68
|
+
ruby2_keywords (>= 0.0.4)
|
69
|
+
faraday-detailed_logger (2.3.0)
|
70
|
+
faraday (>= 0.8, < 2)
|
71
|
+
faraday-em_http (1.0.0)
|
72
|
+
faraday-em_synchrony (1.0.0)
|
73
|
+
faraday-excon (1.1.0)
|
74
|
+
faraday-httpclient (1.0.1)
|
75
|
+
faraday-net_http (1.0.1)
|
76
|
+
faraday-net_http_persistent (1.2.0)
|
77
|
+
faraday-patron (1.0.0)
|
78
|
+
faraday-rack (1.0.0)
|
79
|
+
faraday_middleware (1.1.0)
|
80
|
+
faraday (~> 1.0)
|
81
|
+
fingerprintable (1.2.1)
|
82
|
+
colorize
|
83
|
+
i18n (1.8.10)
|
84
|
+
concurrent-ruby (~> 1.0)
|
85
|
+
ledger_sync (2.2.0)
|
86
|
+
activemodel
|
87
|
+
colorize
|
88
|
+
dry-schema (~> 1.5.4)
|
89
|
+
dry-validation (~> 1.5.6)
|
90
|
+
faraday
|
91
|
+
faraday-detailed_logger
|
92
|
+
faraday_middleware
|
93
|
+
fingerprintable (>= 1.2.1)
|
94
|
+
nokogiri
|
95
|
+
openssl (~> 2.2.0)
|
96
|
+
pd_ruby
|
97
|
+
rack (~> 2.2.3)
|
98
|
+
resonad
|
99
|
+
simply_serializable (>= 1.5.1)
|
100
|
+
minitest (5.14.4)
|
101
|
+
multipart-post (2.1.1)
|
102
|
+
nokogiri (1.12.4-x86_64-linux)
|
103
|
+
racc (~> 1.4)
|
104
|
+
openssl (2.2.0)
|
105
|
+
parallel (1.20.1)
|
106
|
+
parser (3.0.2.0)
|
107
|
+
ast (~> 2.4.1)
|
108
|
+
pd_ruby (0.2.3)
|
109
|
+
colorize
|
110
|
+
racc (1.5.2)
|
111
|
+
rack (2.2.3)
|
112
|
+
rainbow (3.0.0)
|
113
|
+
rake (13.0.6)
|
114
|
+
regexp_parser (2.1.1)
|
115
|
+
resonad (1.4.0)
|
116
|
+
rexml (3.2.5)
|
117
|
+
rspec (3.10.0)
|
118
|
+
rspec-core (~> 3.10.0)
|
119
|
+
rspec-expectations (~> 3.10.0)
|
120
|
+
rspec-mocks (~> 3.10.0)
|
121
|
+
rspec-core (3.10.1)
|
122
|
+
rspec-support (~> 3.10.0)
|
123
|
+
rspec-expectations (3.10.1)
|
124
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
125
|
+
rspec-support (~> 3.10.0)
|
126
|
+
rspec-mocks (3.10.2)
|
127
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
128
|
+
rspec-support (~> 3.10.0)
|
129
|
+
rspec-support (3.10.2)
|
130
|
+
rubocop (1.20.0)
|
131
|
+
parallel (~> 1.10)
|
132
|
+
parser (>= 3.0.0.0)
|
133
|
+
rainbow (>= 2.2.2, < 4.0)
|
134
|
+
regexp_parser (>= 1.8, < 3.0)
|
135
|
+
rexml
|
136
|
+
rubocop-ast (>= 1.9.1, < 2.0)
|
137
|
+
ruby-progressbar (~> 1.7)
|
138
|
+
unicode-display_width (>= 1.4.0, < 3.0)
|
139
|
+
rubocop-ast (1.11.0)
|
140
|
+
parser (>= 3.0.1.1)
|
141
|
+
ruby-progressbar (1.11.0)
|
142
|
+
ruby2_keywords (0.0.5)
|
143
|
+
simply_serializable (1.5.1)
|
144
|
+
fingerprintable (>= 1.2.1)
|
145
|
+
tzinfo (2.0.4)
|
146
|
+
concurrent-ruby (~> 1.0)
|
147
|
+
unicode-display_width (2.0.0)
|
148
|
+
zeitwerk (2.4.2)
|
149
|
+
|
150
|
+
PLATFORMS
|
151
|
+
x86_64-linux
|
152
|
+
|
153
|
+
DEPENDENCIES
|
154
|
+
dotenv
|
155
|
+
ledger_sync-domains!
|
156
|
+
rake (~> 13.0)
|
157
|
+
rspec (~> 3.0)
|
158
|
+
rubocop (~> 1.7)
|
159
|
+
|
160
|
+
BUNDLED WITH
|
161
|
+
2.2.15
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2021 Jozef Vaclavik
|
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,254 @@
|
|
1
|
+
# LedgerSync::Domains
|
2
|
+
|
3
|
+
Achieving Domain Driven Design (DDD) inside of a rails application is not a simple task. Rails engines by themself provide minimal value for code isolation. There are several gems that allows you to handle cross-engine communication through service/operation/inflection classes. Unfortunately these only touches execution, but they won't help you with passing around serialized objects.
|
4
|
+
|
5
|
+
LedgerSync has been developed to handle cross-service (API) communication in elegant way. Same aproach can be used for cross-domain communication.
|
6
|
+
|
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
|
+
|
9
|
+
Use LedgerSync Operations to trigger actions from other domains and LedgerSync Serializers 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
|
+
|
11
|
+
## Installation
|
12
|
+
|
13
|
+
Add this line to your application's Gemfile:
|
14
|
+
|
15
|
+
```ruby
|
16
|
+
gem 'ledger_sync-domains'
|
17
|
+
```
|
18
|
+
|
19
|
+
And then execute:
|
20
|
+
|
21
|
+
$ bundle install
|
22
|
+
|
23
|
+
Or install it yourself as:
|
24
|
+
|
25
|
+
$ gem install ledger_sync-domains
|
26
|
+
|
27
|
+
## Usage
|
28
|
+
|
29
|
+
LedgerSync comes with 3 components.
|
30
|
+
1. Resource
|
31
|
+
2. Serializers
|
32
|
+
3. Operations
|
33
|
+
|
34
|
+
### Resource/Model
|
35
|
+
|
36
|
+
While `LedgerSync::Resources` represent object from external service, in case of domains we replace this with build in ActiveRecord Models. If you wish to use `LedgerSync::Domains` outside of a rails app (there is nothing that stops you doing that), just use LedgerSync::Resource to prepare your data for Serializers.
|
37
|
+
|
38
|
+
Use of ActiveRecord Models allows us to easily work with current rails data structure. Define your models as you would do in any rails application.
|
39
|
+
|
40
|
+
```ruby
|
41
|
+
class Customer < ActiveRecord::Base
|
42
|
+
has_many :addresses
|
43
|
+
belongs_to :user, class_name: 'Auth::User'
|
44
|
+
end
|
45
|
+
|
46
|
+
class Address < ActiveRecord::Base
|
47
|
+
belongs_to :customer
|
48
|
+
end
|
49
|
+
|
50
|
+
module Auth
|
51
|
+
class User < ActiveRecord::Base
|
52
|
+
has_many :customers
|
53
|
+
end
|
54
|
+
end
|
55
|
+
```
|
56
|
+
|
57
|
+
Alternatively you can use LedgerSync::Resource class to wrap your data into Model-like objects with relationships.
|
58
|
+
```ruby
|
59
|
+
class Customer < LedgerSync::Resource
|
60
|
+
attribute :name, type: LedgerSync::Type::String
|
61
|
+
|
62
|
+
references_many :addresses, to: Address
|
63
|
+
references_one :user, to: Auth::User
|
64
|
+
end
|
65
|
+
|
66
|
+
class Address < LedgerSync::Resource
|
67
|
+
attribute :address, type: LedgerSync::Type::String
|
68
|
+
attribute :city, type: LedgerSync::Type::String
|
69
|
+
attribute :country, type: LedgerSync::Type::String
|
70
|
+
|
71
|
+
references_one :customer, to: Customer
|
72
|
+
end
|
73
|
+
|
74
|
+
module Auth
|
75
|
+
class User < LedgerSync::Resource
|
76
|
+
attribute :email, type: LedgerSync::Type::String
|
77
|
+
|
78
|
+
references_many :customers, to: Customer
|
79
|
+
end
|
80
|
+
end
|
81
|
+
```
|
82
|
+
|
83
|
+
In above example(s) you can see two models `Customer` and `Address` that are part of `Main` domain, and `User` model being part of `Auth` domain. For the sake of examples, we will have also `Client` domain that consumes both resources from `Main` as well as `Auth` domain.
|
84
|
+
|
85
|
+
### Serializers
|
86
|
+
|
87
|
+
Next step is to serialize records from these Models/Resources. One of the concepts of DDD is to be able to present your object differently to each domain. For example `User` can exposes different set of attributes towards `Client` domain as well as `Main` domain. `LedgerSync::Serializer` allows you to define serializer for each domain and specify which attributes will be exposed towards each domain.
|
88
|
+
|
89
|
+
```ruby
|
90
|
+
module Auth
|
91
|
+
module Users
|
92
|
+
class AuthSerializer < LedgerSync::Domains::Serializer
|
93
|
+
attribute :email
|
94
|
+
end
|
95
|
+
|
96
|
+
class ClientSerializer < LedgerSync::Domains::Serializer
|
97
|
+
attribute :email
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
module Addresses
|
103
|
+
class ClientSerializer < LedgerSync::Domains::Serializer
|
104
|
+
attribute :address
|
105
|
+
attribute :city
|
106
|
+
attribute :state
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
module Customers
|
111
|
+
class AuthSerializer < LedgerSync::Domains::Serializer
|
112
|
+
attribute :id, resource_attribute: :ledger_id
|
113
|
+
attribute :name
|
114
|
+
references_one :user, resource_attribute: :user,
|
115
|
+
serializer: Auth::Users::AuthSerializer
|
116
|
+
end
|
117
|
+
|
118
|
+
class ClientSerializer < LedgerSync::Domains::Serializer
|
119
|
+
attribute :id, resource_attribute: :ledger_id
|
120
|
+
attribute :name
|
121
|
+
references_many :addresses, resource_attribute: :addresses,
|
122
|
+
serializer: Addresses::ClientSerializer
|
123
|
+
end
|
124
|
+
end
|
125
|
+
```
|
126
|
+
|
127
|
+
In above example we represent `Customer` with two serializers aimed towards two domains. `Customer::AuthSerializer` exposes customer and `user` relationship, while `Customer::ClientSerializer` exposes customer and `addresses` relationship.
|
128
|
+
|
129
|
+
`LedgerSync::Domains::Serializer` serializes into `OpenStruct` object. Original resource is part of it and used to lazily load and serialize relationships. This way relationships are queried and serialized only once required. Here is quick example below.
|
130
|
+
|
131
|
+
|
132
|
+
Here is quick example how that looks in above example.
|
133
|
+
```ruby
|
134
|
+
# load all above classes - watch for dependencies
|
135
|
+
|
136
|
+
irb(main):001:0> user = Auth::User.new(email: 'test@ledger_sync.dev')
|
137
|
+
=> #<Auth::User:0x00005624204f1da8 @resource_attributes=#<LedgerSync::ResourceAttributeSet:0x00005624204f1790 @attributes={:external_id=>#<L...
|
138
|
+
irb(main):002:0> customer = Customer.new(ledger_id: '123', name: 'LedgerSync', user: user)
|
139
|
+
=> #<Customer:0x0000562420792300 @resource_attributes=#<LedgerSync::ResourceAttributeSet:0x0000562420791dd8 @attributes={:external_id=>#<Led...
|
140
|
+
|
141
|
+
irb(main):003:0> auth_customer = Customers::AuthSerializer.new.serialize(resource: customer)
|
142
|
+
=> #<Customers::AuthStruct id="123", name="LedgerSync">
|
143
|
+
irb(main):004:0> auth_customer.user
|
144
|
+
=> #<Auth::Users::AuthStruct email="test@ledger_sync.dev">
|
145
|
+
|
146
|
+
irb(main):005:0> client_customer = Customers::ClientSerializer.new.serialize(resource: customer)
|
147
|
+
=> #<Customers::ClientStruct id="123", name="LedgerSync">
|
148
|
+
irb(main):006:0> client_customer.user
|
149
|
+
Traceback (most recent call last):
|
150
|
+
3: from ./bin/console:15:in `<main>'
|
151
|
+
2: from (irb):06:in `<main>'
|
152
|
+
1: from /root/.asdf/installs/ruby/3.0.0/lib/ruby/3.0.0/delegate.rb:91:in `method_missing'
|
153
|
+
NoMethodError (undefined method `user' for #<OpenStruct id="123", name="LedgerSync">)
|
154
|
+
irb(main):007:0> client_customer.addresses
|
155
|
+
=> []
|
156
|
+
```
|
157
|
+
Above you can see that `client_customer` can't access `user` relationship, while `auth_customer` can.
|
158
|
+
|
159
|
+
### Operations
|
160
|
+
|
161
|
+
Operations allows you to expose actions towards other domains. Validate input based on specified contract and return result object that you can use to retrieve data or error details. Here is sample operation to fetch object by specific ID and one to deactivate an user.
|
162
|
+
|
163
|
+
```ruby
|
164
|
+
module Auth
|
165
|
+
module Users
|
166
|
+
class FindOperation < LedgerSync::Domains::Operation::Find
|
167
|
+
# Ha, that was easy!
|
168
|
+
end
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
module Auth
|
173
|
+
module Users
|
174
|
+
class DeactivateOperation
|
175
|
+
include LedgerSync::Domains::Operation::Mixin
|
176
|
+
|
177
|
+
class Contract < LedgerSync::Ledgers::Contract
|
178
|
+
params do
|
179
|
+
required(:id).value(:integer)
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
private
|
184
|
+
|
185
|
+
def operate
|
186
|
+
return failure('Not found') if resource.nil?
|
187
|
+
|
188
|
+
if resource.update(active: false)
|
189
|
+
success(serialize(resource: resource))
|
190
|
+
else
|
191
|
+
failure('Unable to deactivate')
|
192
|
+
end
|
193
|
+
end
|
194
|
+
|
195
|
+
def resource
|
196
|
+
@resource ||= User.find_by(id: params[:id])
|
197
|
+
end
|
198
|
+
|
199
|
+
def failure(message)
|
200
|
+
super(
|
201
|
+
LedgerSync::Error::OperationError.new(
|
202
|
+
operation: self,
|
203
|
+
message: message
|
204
|
+
)
|
205
|
+
)
|
206
|
+
end
|
207
|
+
end
|
208
|
+
end
|
209
|
+
end
|
210
|
+
```
|
211
|
+
Successful result of an operation should be serialized resource(s). Operation by itself doesn't know what domain triggered it, and therefore you need to pass in serializer you want to use to serialize the resource. Here is how that looks for the example above.
|
212
|
+
|
213
|
+
```ruby
|
214
|
+
irb(main):001:0> op = Auth::Users::FindOperation.new(id: 1, limit: {}, serializer: Auth::Users::ClientSerializer.new)
|
215
|
+
=> #<Auth::Users::FindOperation:0x0000555937876cf8 @serializer=#<Auth::Users::ClientSerializer:0x0000555937876dc0>, @deserializer=nil...
|
216
|
+
irb(main):002:0> op.perform
|
217
|
+
=> #<LedgerSync::Domains::Operation::OperationResult::Success:0x00005559372f9d70 @meta=nil, @value=#<OpenStruct email="test@ledger_sync.dev">>
|
218
|
+
irb(main):003:0> op.result.value
|
219
|
+
=> #<Auth::Users::ClientStruct email="test@ledger_sync.dev">
|
220
|
+
```
|
221
|
+
|
222
|
+
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
|
+
|
224
|
+
### Cross-domain relationships
|
225
|
+
|
226
|
+
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
|
+
|
228
|
+
#### Use of cross-domain relationships is bad
|
229
|
+
|
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.
|
231
|
+
|
232
|
+
#### Use of cross-domain relationships is fine
|
233
|
+
|
234
|
+
If you double down on getting into 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
|
+
|
236
|
+
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
|
+
|
238
|
+
The problem is when working with records within current engine where fetching data from ActiveRecord is accepted practice. There is nothing that stops you from crossing domain boundary.
|
239
|
+
|
240
|
+
The obvious solution is to use Operations to work with Models within current engine as well. Serialized `OpenStruct` objects are (or try to be) compatible with ActiveRecord objects. They require almost zero changes in your templates. So think of them as drop-in replacement.
|
241
|
+
|
242
|
+
## Development
|
243
|
+
|
244
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
245
|
+
|
246
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
247
|
+
|
248
|
+
## Contributing
|
249
|
+
|
250
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/ledger_sync-domains.
|
251
|
+
|
252
|
+
## License
|
253
|
+
|
254
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
data/Rakefile
ADDED
data/bin/console
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require "bundler/setup"
|
5
|
+
require "ledger_sync/domains"
|
6
|
+
|
7
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
8
|
+
# with your gem easier. You can also use a different console, if you like.
|
9
|
+
|
10
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
11
|
+
# require "pry"
|
12
|
+
# Pry.start
|
13
|
+
|
14
|
+
require "irb"
|
15
|
+
IRB.start(__FILE__)
|
data/bin/setup
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "lib/ledger_sync/domains/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |spec|
|
6
|
+
spec.name = 'ledger_sync-domains'
|
7
|
+
spec.version = LedgerSync::Domains::VERSION
|
8
|
+
spec.authors = ['Jozef Vaclavik']
|
9
|
+
spec.email = ['jozef@dropbot.sh']
|
10
|
+
|
11
|
+
spec.summary = 'LedgerSync for Domains/Engines'
|
12
|
+
spec.description = 'Use LedgerSync Operations and Serializers for cross-domain communication.'
|
13
|
+
spec.homepage = 'https://engineering.dropbot.sh'
|
14
|
+
spec.license = 'MIT'
|
15
|
+
spec.required_ruby_version = Gem::Requirement.new('>= 3.0.0')
|
16
|
+
|
17
|
+
# spec.metadata["allowed_push_host"] = "TODO: Set to 'http://mygemserver.com'"
|
18
|
+
|
19
|
+
spec.metadata['homepage_uri'] = spec.homepage
|
20
|
+
spec.metadata['source_code_uri'] = 'https://github.com/LedgerSync/ledger_sync-domains'
|
21
|
+
spec.metadata['changelog_uri'] = 'https://github.com/LedgerSync/ledger_sync-domains/blob/main/CHANGELOG.md'
|
22
|
+
|
23
|
+
# Specify which files should be added to the gem when it is released.
|
24
|
+
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
25
|
+
spec.files = Dir.chdir(File.expand_path(__dir__)) do
|
26
|
+
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{\A(?:test|spec|features)/}) }
|
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
|
+
spec.add_dependency 'ledger_sync', '~> 2.2.0'
|
33
|
+
spec.add_development_dependency 'dotenv'
|
34
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../operation'
|
4
|
+
|
5
|
+
module LedgerSync
|
6
|
+
module Domains
|
7
|
+
class Operation
|
8
|
+
class Add
|
9
|
+
include LedgerSync::Domains::Operation::Mixin
|
10
|
+
|
11
|
+
private
|
12
|
+
|
13
|
+
def operate
|
14
|
+
if resource.save
|
15
|
+
success
|
16
|
+
else
|
17
|
+
failure(
|
18
|
+
'Please review the problems below:',
|
19
|
+
data: serialize(resource: resource)
|
20
|
+
)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def resource
|
25
|
+
@resource ||= resource_class.new(params)
|
26
|
+
end
|
27
|
+
|
28
|
+
def success
|
29
|
+
super(
|
30
|
+
serialize(resource: resource)
|
31
|
+
)
|
32
|
+
end
|
33
|
+
|
34
|
+
def failure(message, data: nil)
|
35
|
+
super(
|
36
|
+
LedgerSync::Error::OperationError.new(
|
37
|
+
operation: self,
|
38
|
+
message: message,
|
39
|
+
response: data
|
40
|
+
)
|
41
|
+
)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../operation'
|
4
|
+
|
5
|
+
module LedgerSync
|
6
|
+
module Domains
|
7
|
+
class Operation
|
8
|
+
class Find
|
9
|
+
include LedgerSync::Domains::Operation::Mixin
|
10
|
+
|
11
|
+
class Contract < LedgerSync::Ledgers::Contract
|
12
|
+
params do
|
13
|
+
required(:id).filled(:integer)
|
14
|
+
required(:limit).value(:hash)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def operate
|
21
|
+
if resource
|
22
|
+
success
|
23
|
+
else
|
24
|
+
failure('Not found')
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def resource
|
29
|
+
@resource ||= resource_class.find_by(id: params[:id])
|
30
|
+
end
|
31
|
+
|
32
|
+
def success
|
33
|
+
super(
|
34
|
+
serialize(resource: resource)
|
35
|
+
)
|
36
|
+
end
|
37
|
+
|
38
|
+
def failure(message)
|
39
|
+
super(
|
40
|
+
LedgerSync::Error::OperationError.new(
|
41
|
+
operation: self,
|
42
|
+
message: message
|
43
|
+
)
|
44
|
+
)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../operation'
|
4
|
+
|
5
|
+
module LedgerSync
|
6
|
+
module Domains
|
7
|
+
class Operation
|
8
|
+
class Remove
|
9
|
+
include LedgerSync::Domains::Operation::Mixin
|
10
|
+
|
11
|
+
private
|
12
|
+
|
13
|
+
def operate
|
14
|
+
return failure('Resource not found') unless resource
|
15
|
+
|
16
|
+
if resource.destroy
|
17
|
+
success
|
18
|
+
else
|
19
|
+
failure(
|
20
|
+
'Please review the problems below:',
|
21
|
+
data: serialize(resource: resource)
|
22
|
+
)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def resource
|
27
|
+
@resource ||= resource_class.find_by(id: params[:id])
|
28
|
+
end
|
29
|
+
|
30
|
+
def success
|
31
|
+
super(
|
32
|
+
true
|
33
|
+
)
|
34
|
+
end
|
35
|
+
|
36
|
+
def failure(message, data: nil)
|
37
|
+
super(
|
38
|
+
LedgerSync::Error::OperationError.new(
|
39
|
+
operation: self,
|
40
|
+
message: message,
|
41
|
+
response: data
|
42
|
+
)
|
43
|
+
)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../operation'
|
4
|
+
|
5
|
+
module LedgerSync
|
6
|
+
module Domains
|
7
|
+
class Operation
|
8
|
+
class Search
|
9
|
+
include LedgerSync::Domains::Operation::Mixin
|
10
|
+
|
11
|
+
# make kaminari work with serialized results
|
12
|
+
class PaginatedResult < SimpleDelegator
|
13
|
+
attr_accessor :next_page, :last_page, :current_page, :total_pages,
|
14
|
+
:limit_value, :total_count
|
15
|
+
|
16
|
+
def self.from_result(result, items:)
|
17
|
+
search = new(items)
|
18
|
+
search.next_page = result.next_page
|
19
|
+
search.last_page = result.last_page?
|
20
|
+
search.current_page = result.current_page
|
21
|
+
search.total_pages = result.total_pages
|
22
|
+
search.total_count = result.total_count
|
23
|
+
search.limit_value = result.limit_value
|
24
|
+
search
|
25
|
+
end
|
26
|
+
|
27
|
+
def last_page?
|
28
|
+
last_page == true
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
class Contract < LedgerSync::Ledgers::Contract
|
33
|
+
params do
|
34
|
+
required(:query).value(:hash)
|
35
|
+
required(:limit).value(:hash)
|
36
|
+
required(:includes).value(:array)
|
37
|
+
required(:order).value(:string)
|
38
|
+
required(:page).value(:integer)
|
39
|
+
required(:per).value(:integer)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
private
|
44
|
+
|
45
|
+
def operate
|
46
|
+
if resources
|
47
|
+
success
|
48
|
+
else
|
49
|
+
failure('Not found')
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def resources # rubocop:disable Metrics/AbcSize
|
54
|
+
@resources ||= resource_class.where(params[:limit])
|
55
|
+
.where(params[:query])
|
56
|
+
.includes(params[:includes])
|
57
|
+
.order(params[:order])
|
58
|
+
.page(params[:page])
|
59
|
+
.per(params[:per])
|
60
|
+
end
|
61
|
+
|
62
|
+
def success
|
63
|
+
super(
|
64
|
+
PaginatedResult.from_result(
|
65
|
+
resources,
|
66
|
+
items: resources.map { |resource| serialize(resource: resource) }
|
67
|
+
)
|
68
|
+
)
|
69
|
+
end
|
70
|
+
|
71
|
+
def failure(message)
|
72
|
+
super(
|
73
|
+
LedgerSync::Error::OperationError.new(
|
74
|
+
operation: self,
|
75
|
+
message: message
|
76
|
+
)
|
77
|
+
)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../operation'
|
4
|
+
|
5
|
+
module LedgerSync
|
6
|
+
module Domains
|
7
|
+
class Resource
|
8
|
+
class Transition
|
9
|
+
include LedgerSync::Domains::Operation::Mixin
|
10
|
+
|
11
|
+
class Contract < LedgerSync::Ledgers::Contract
|
12
|
+
params do
|
13
|
+
required(:model_name).filled(:string)
|
14
|
+
required(:id).filled(:integer)
|
15
|
+
required(:event).value(:string)
|
16
|
+
required(:attrs).maybe(:hash)
|
17
|
+
required(:attrs).maybe(:array)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def operate
|
24
|
+
if resource.present?
|
25
|
+
if resource.send(guard_method, params[:attrs]) &&
|
26
|
+
resource.send(event_method, params[:attrs])
|
27
|
+
success
|
28
|
+
else
|
29
|
+
failure('Unable to transition')
|
30
|
+
end
|
31
|
+
else
|
32
|
+
failure('Not found')
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def guard_method
|
37
|
+
"may_#{params[:event]}?"
|
38
|
+
end
|
39
|
+
|
40
|
+
def event_method
|
41
|
+
"#{params[:event]}!"
|
42
|
+
end
|
43
|
+
|
44
|
+
def resource
|
45
|
+
@resource ||= resource_class.find_by(id: params[:id])
|
46
|
+
end
|
47
|
+
|
48
|
+
def resource_class
|
49
|
+
@resource_class ||= Object.const_get(params[:model_name])
|
50
|
+
end
|
51
|
+
|
52
|
+
def success
|
53
|
+
super(
|
54
|
+
serialize(resource: resource)
|
55
|
+
)
|
56
|
+
end
|
57
|
+
|
58
|
+
def failure(message)
|
59
|
+
super(
|
60
|
+
LedgerSync::Error::OperationError.new(
|
61
|
+
operation: self,
|
62
|
+
message: message
|
63
|
+
)
|
64
|
+
)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../operation'
|
4
|
+
|
5
|
+
module LedgerSync
|
6
|
+
module Domains
|
7
|
+
class Operation
|
8
|
+
class Update
|
9
|
+
include LedgerSync::Domains::Operation::Mixin
|
10
|
+
|
11
|
+
private
|
12
|
+
|
13
|
+
def operate
|
14
|
+
return failure('Resource not found') unless resource
|
15
|
+
|
16
|
+
if resource.update(params.except(:id))
|
17
|
+
success
|
18
|
+
else
|
19
|
+
failure(
|
20
|
+
'Please review the problems below:',
|
21
|
+
data: serialize(resource: resource)
|
22
|
+
)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def resource
|
27
|
+
@resource ||= resource_class.find_by(id: params[:id])
|
28
|
+
end
|
29
|
+
|
30
|
+
def success
|
31
|
+
super(
|
32
|
+
serialize(resource: resource)
|
33
|
+
)
|
34
|
+
end
|
35
|
+
|
36
|
+
def failure(message, data: nil)
|
37
|
+
super(
|
38
|
+
LedgerSync::Error::OperationError.new(
|
39
|
+
operation: self,
|
40
|
+
message: message,
|
41
|
+
response: data
|
42
|
+
)
|
43
|
+
)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,174 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module LedgerSync
|
4
|
+
module Domains
|
5
|
+
class Operation
|
6
|
+
class OperationResult
|
7
|
+
module ResultTypeBase
|
8
|
+
attr_reader :meta
|
9
|
+
|
10
|
+
def self.included(base)
|
11
|
+
base.class_eval do
|
12
|
+
simply_serialize only: %i[meta]
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def initialize(*args, meta: nil)
|
17
|
+
@meta = meta
|
18
|
+
super(*args)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
include LedgerSync::ResultBase
|
23
|
+
end
|
24
|
+
|
25
|
+
module Mixin
|
26
|
+
module ClassMethods
|
27
|
+
def inferred_resource_class
|
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
|
38
|
+
|
39
|
+
def inferred_deserializer_class
|
40
|
+
const_get("#{inferred_resource_class}Deserializer")
|
41
|
+
end
|
42
|
+
|
43
|
+
def inferred_validation_contract_class
|
44
|
+
const_get("#{self}::Contract")
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def self.included(base)
|
49
|
+
base.include SimplySerializable::Mixin
|
50
|
+
base.include Fingerprintable::Mixin
|
51
|
+
base.include LedgerSync::Error::HelpersMixin
|
52
|
+
base.extend ClassMethods
|
53
|
+
|
54
|
+
base.class_eval do
|
55
|
+
simply_serialize only: %i[
|
56
|
+
params
|
57
|
+
result
|
58
|
+
]
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
attr_reader :params, :result
|
63
|
+
|
64
|
+
def initialize(serializer: nil, deserializer: nil, **params)
|
65
|
+
@serializer = serializer
|
66
|
+
@deserializer = deserializer
|
67
|
+
@params = params
|
68
|
+
@result = nil
|
69
|
+
end
|
70
|
+
|
71
|
+
def perform # rubocop:disable Metrics/MethodLength
|
72
|
+
if performed?
|
73
|
+
return failure(
|
74
|
+
LedgerSync::Error::OperationError::PerformedOperationError.new(
|
75
|
+
operation: self
|
76
|
+
)
|
77
|
+
)
|
78
|
+
end
|
79
|
+
return errors unless valid?
|
80
|
+
|
81
|
+
@result = begin
|
82
|
+
operate
|
83
|
+
rescue LedgerSync::Error => e
|
84
|
+
failure(e)
|
85
|
+
rescue StandardError => e
|
86
|
+
failure(e)
|
87
|
+
ensure
|
88
|
+
@performed = true
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def performed?
|
93
|
+
@performed == true
|
94
|
+
end
|
95
|
+
|
96
|
+
def serialize(resource:)
|
97
|
+
serializer.serialize(resource: resource)
|
98
|
+
end
|
99
|
+
|
100
|
+
def deserializer
|
101
|
+
@deserializer ||= deserializer_class.new
|
102
|
+
end
|
103
|
+
|
104
|
+
def deserializer_class
|
105
|
+
@deserializer_class ||= self.class.inferred_deserializer_class
|
106
|
+
end
|
107
|
+
|
108
|
+
def serializer
|
109
|
+
@serializer ||= serializer_class.new
|
110
|
+
end
|
111
|
+
|
112
|
+
def serializer_class
|
113
|
+
@serializer_class ||= self.class.inferred_serializer_class
|
114
|
+
end
|
115
|
+
|
116
|
+
def resource_class
|
117
|
+
@resource_class ||= self.class.inferred_resource_class
|
118
|
+
end
|
119
|
+
|
120
|
+
# Results
|
121
|
+
|
122
|
+
def failure(error)
|
123
|
+
@result = OperationResult.Failure(error)
|
124
|
+
end
|
125
|
+
|
126
|
+
def failure?
|
127
|
+
result.failure?
|
128
|
+
end
|
129
|
+
|
130
|
+
def success(value, meta: nil)
|
131
|
+
@result = OperationResult.Success(value, meta: meta)
|
132
|
+
end
|
133
|
+
|
134
|
+
def success?
|
135
|
+
result.success?
|
136
|
+
end
|
137
|
+
|
138
|
+
def valid?
|
139
|
+
validate.success?
|
140
|
+
end
|
141
|
+
|
142
|
+
def validate
|
143
|
+
LedgerSync::Util::Validator.new(
|
144
|
+
contract: validation_contract,
|
145
|
+
data: params
|
146
|
+
).validate
|
147
|
+
end
|
148
|
+
|
149
|
+
def validation_contract
|
150
|
+
@validation_contract ||= self.class.inferred_validation_contract_class
|
151
|
+
end
|
152
|
+
|
153
|
+
def errors
|
154
|
+
validate.validator.errors
|
155
|
+
end
|
156
|
+
|
157
|
+
# Comparison
|
158
|
+
|
159
|
+
def ==(other)
|
160
|
+
return false unless self.class == other.class
|
161
|
+
return false unless params == other.params
|
162
|
+
|
163
|
+
true
|
164
|
+
end
|
165
|
+
|
166
|
+
private
|
167
|
+
|
168
|
+
def operate
|
169
|
+
raise NotImplementedError, self.class.name
|
170
|
+
end
|
171
|
+
end
|
172
|
+
end
|
173
|
+
end
|
174
|
+
end
|
@@ -0,0 +1,98 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module LedgerSync
|
4
|
+
module Domains
|
5
|
+
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(struct, resource:, references:) # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
|
18
|
+
define_method('valid?') { resource.valid? }
|
19
|
+
define_method('errors') { resource.errors }
|
20
|
+
references.each do |args|
|
21
|
+
define_method(args.hash_attribute) do
|
22
|
+
if args.references_many?
|
23
|
+
resource.try(args.resource_attribute).each do |item|
|
24
|
+
args.type.serializer.new.serialize(resource: item)
|
25
|
+
end
|
26
|
+
else
|
27
|
+
item = resource.try(args.resource_attribute)
|
28
|
+
next if item.nil?
|
29
|
+
|
30
|
+
args.type.serializer.new.serialize(resource: item)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
new(struct)
|
36
|
+
end
|
37
|
+
|
38
|
+
def to_param
|
39
|
+
id.to_s
|
40
|
+
end
|
41
|
+
|
42
|
+
def persisted?
|
43
|
+
id.present?
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
name = self.class.to_s.split('::')
|
48
|
+
struct_name = name.pop.sub(/.*\KSerializer/, 'Struct')
|
49
|
+
module_name = name.empty? ? Object : Object.const_get(name.join('::'))
|
50
|
+
module_name.const_set(struct_name, Class.new(OpenStruct))
|
51
|
+
|
52
|
+
klass.with_lazy_references(
|
53
|
+
module_name.const_get(struct_name).new(hash),
|
54
|
+
resource: resource, references: references
|
55
|
+
)
|
56
|
+
end
|
57
|
+
|
58
|
+
def self.references
|
59
|
+
@references ||= []
|
60
|
+
end
|
61
|
+
|
62
|
+
def self.split_attributes
|
63
|
+
regular = []
|
64
|
+
references = []
|
65
|
+
|
66
|
+
attributes.each_value do |attr|
|
67
|
+
if attr.references_many? || attr.references_one?
|
68
|
+
references.push(attr)
|
69
|
+
else
|
70
|
+
regular.push(attr)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
[regular, references]
|
74
|
+
end
|
75
|
+
|
76
|
+
def serialize(args = {}) # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
|
77
|
+
only_changes = args.fetch(:only_changes, false)
|
78
|
+
resource = args.fetch(:resource)
|
79
|
+
|
80
|
+
ret = {}
|
81
|
+
|
82
|
+
regular, references = self.class.split_attributes
|
83
|
+
regular.each do |serializer_attribute|
|
84
|
+
if (only_changes && !resource.attribute_changed?(serializer_attribute.resource_attribute)) || # rubocop:disable Layout/LineLength
|
85
|
+
(serializer_attribute.if_method.present? && !send(serializer_attribute.if_method, resource: resource)) # rubocop:disable Layout/LineLength
|
86
|
+
next
|
87
|
+
end
|
88
|
+
|
89
|
+
ret = LedgerSync::Util::HashHelpers.deep_merge(
|
90
|
+
hash_to_merge_into: ret,
|
91
|
+
other_hash: serializer_attribute.hash_attribute_hash_for(resource: resource)
|
92
|
+
)
|
93
|
+
end
|
94
|
+
create_record_from(ret, resource: resource, references: references)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'ledger_sync'
|
4
|
+
require_relative 'domains/version'
|
5
|
+
require_relative 'domains/serializer'
|
6
|
+
require_relative 'domains/operation'
|
7
|
+
require_relative 'domains/operation/add'
|
8
|
+
require_relative 'domains/operation/find'
|
9
|
+
require_relative 'domains/operation/remove'
|
10
|
+
require_relative 'domains/operation/search'
|
11
|
+
require_relative 'domains/operation/transition'
|
12
|
+
require_relative 'domains/operation/update'
|
13
|
+
|
14
|
+
module LedgerSync
|
15
|
+
module Domains; end
|
16
|
+
end
|
metadata
ADDED
@@ -0,0 +1,97 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: ledger_sync-domains
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0.rc1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Jozef Vaclavik
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2021-09-03 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: ledger_sync
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 2.2.0
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 2.2.0
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: dotenv
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
description: Use LedgerSync Operations and Serializers for cross-domain communication.
|
42
|
+
email:
|
43
|
+
- jozef@dropbot.sh
|
44
|
+
executables: []
|
45
|
+
extensions: []
|
46
|
+
extra_rdoc_files: []
|
47
|
+
files:
|
48
|
+
- ".github/workflows/main.yml"
|
49
|
+
- ".gitignore"
|
50
|
+
- ".rspec"
|
51
|
+
- ".rubocop.yml"
|
52
|
+
- CHANGELOG.md
|
53
|
+
- Gemfile
|
54
|
+
- Gemfile.lock
|
55
|
+
- LICENSE.txt
|
56
|
+
- README.md
|
57
|
+
- Rakefile
|
58
|
+
- bin/console
|
59
|
+
- bin/setup
|
60
|
+
- ledger_sync-domains.gemspec
|
61
|
+
- lib/ledger_sync/domains.rb
|
62
|
+
- lib/ledger_sync/domains/operation.rb
|
63
|
+
- lib/ledger_sync/domains/operation/add.rb
|
64
|
+
- lib/ledger_sync/domains/operation/find.rb
|
65
|
+
- lib/ledger_sync/domains/operation/remove.rb
|
66
|
+
- lib/ledger_sync/domains/operation/search.rb
|
67
|
+
- lib/ledger_sync/domains/operation/transition.rb
|
68
|
+
- lib/ledger_sync/domains/operation/update.rb
|
69
|
+
- lib/ledger_sync/domains/serializer.rb
|
70
|
+
- lib/ledger_sync/domains/version.rb
|
71
|
+
homepage: https://engineering.dropbot.sh
|
72
|
+
licenses:
|
73
|
+
- MIT
|
74
|
+
metadata:
|
75
|
+
homepage_uri: https://engineering.dropbot.sh
|
76
|
+
source_code_uri: https://github.com/LedgerSync/ledger_sync-domains
|
77
|
+
changelog_uri: https://github.com/LedgerSync/ledger_sync-domains/blob/main/CHANGELOG.md
|
78
|
+
post_install_message:
|
79
|
+
rdoc_options: []
|
80
|
+
require_paths:
|
81
|
+
- lib
|
82
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
83
|
+
requirements:
|
84
|
+
- - ">="
|
85
|
+
- !ruby/object:Gem::Version
|
86
|
+
version: 3.0.0
|
87
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
88
|
+
requirements:
|
89
|
+
- - ">"
|
90
|
+
- !ruby/object:Gem::Version
|
91
|
+
version: 1.3.1
|
92
|
+
requirements: []
|
93
|
+
rubygems_version: 3.2.3
|
94
|
+
signing_key:
|
95
|
+
specification_version: 4
|
96
|
+
summary: LedgerSync for Domains/Engines
|
97
|
+
test_files: []
|