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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 98a9a322cfce591cdeed882ba3759de892d83285750060039705ef2ca639e57b
4
- data.tar.gz: 8382121c87166d2c09ff841a9e2eb9b42ac51ee83a021f9281b1ecb8736043c5
3
+ metadata.gz: b26c844bc9d5327343fc366e9854181829cc53f9b06c2d2de0e8fcd9f0a34d81
4
+ data.tar.gz: 858fd914201d49fc83d24476997f6c2ae8e96d8e627bbc10507ed3e4015826ed
5
5
  SHA512:
6
- metadata.gz: ce9543df99e765a129f1e489fb50c426ff4bf204af1580e4e0d68eb18cf8c453dd681cd645d47b4c0d91f4f3f5d010c5136a961f023f08cab2937337ca592662
7
- data.tar.gz: 1b0ee50fa56bc4412bc13b7f398ea88d7d2366e0df2811d9bc6d895d071aaf7e3b0dfcdec6068d182d56edfd1493d4bcc1289c759884583335d75b0d710d1d65
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
@@ -9,3 +9,4 @@
9
9
 
10
10
  # rspec failure tracking
11
11
  .rspec_status
12
+ .byebug_history
data/.rubocop.yml CHANGED
@@ -1,5 +1,14 @@
1
+ inherit_mode:
2
+ merge:
3
+ - Exclude
4
+
1
5
  AllCops:
2
- TargetRubyVersion: 3.0
6
+ TargetRubyVersion: 2.7
7
+ Exclude:
8
+ - "Gemfile"
9
+ - "Rakefile"
10
+ - "bin/**/*"
11
+ - "spec/**/*"
3
12
 
4
13
  Style/StringLiterals:
5
14
  Enabled: true
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.rc4)
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.3.0)
73
- faraday (>= 0.8, < 2)
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.1.0)
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
- ledger_sync (2.2.0)
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.4-x86_64-linux)
106
+ nokogiri (1.12.5-x86_64-linux)
106
107
  racc (~> 1.4)
107
- openssl (2.2.0)
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.5.2)
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.15
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 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.
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. Resource
31
- 2. Serializers
32
- 3. Operations
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(serialize(resource: resource))
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
- 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.
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: {}, serializer: Auth::Users::ClientSerializer.new)
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 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.
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
 
@@ -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('>= 3.0.0')
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
- 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
36
+ @internal = false
38
37
 
39
- def inferred_deserializer_class
40
- const_get("#{inferred_resource_class}Deserializer")
38
+ def internal
39
+ @internal = true
41
40
  end
42
41
 
43
- def inferred_validation_contract_class
44
- const_get('Contract')
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(serializer: nil, deserializer: nil, **params)
65
- @serializer = serializer
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
- serializer.serialize(resource: resource)
114
+ serializer_for(resource: resource).serialize(resource: resource)
102
115
  end
103
116
 
104
- def deserializer
105
- @deserializer ||= deserializer_class.new
117
+ def serializer_for(resource:)
118
+ serializer_class_for(resource: resource).new
106
119
  end
107
120
 
108
- def deserializer_class
109
- @deserializer_class ||= self.class.inferred_deserializer_class
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 serializer
113
- @serializer ||= serializer_class.new
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 serializer_class
117
- @serializer_class ||= self.class.inferred_serializer_class
136
+ def domain
137
+ LedgerSync::Domains.domains.module_for(domain: @domain)
118
138
  end
119
139
 
120
- def resource_class
121
- @resource_class ||= self.class.inferred_resource_class
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: validation_contract,
183
+ contract: validation_contract_class,
149
184
  data: params
150
185
  ).validate
151
186
  end
152
187
 
153
- def validation_contract
154
- @validation_contract ||= self.class.inferred_validation_contract_class
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
- create_record_from(ret, resource: resource, references: references)
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
@@ -2,6 +2,6 @@
2
2
 
3
3
  module LedgerSync
4
4
  module Domains
5
- VERSION = '1.0.0.rc4'
5
+ VERSION = '1.0.0.rc6'
6
6
  end
7
7
  end
@@ -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; end
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.rc4
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-09-23 00:00:00.000000000 Z
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/main.yml"
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: 3.0.0
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.3
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: []
@@ -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