hub_kernel 0.25.0 → 0.26.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +46 -0
- data/lib/hub_kernel/port_associations.rb +26 -6
- data/lib/hub_kernel/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: fbd4a1ca059e2203955aab7a07510cd07620427086b6153455f0e84d1f4ea639
|
|
4
|
+
data.tar.gz: 26a9be066b129fa83177261e5f9e15cc72c206c3210dc4fc1f26c6a65ac29676
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 80bcd028c34e3be272d7c81b81852a0814c592df1cdbfb58954095dc4f240db87d02e4cd78afebd7cec0a0a9d1212d5f0585203724eefab74e81d42901b43050
|
|
7
|
+
data.tar.gz: 782767a253efd77447d8be7b2d16dc7086f8715515d19189435cb4a4fc5c64663cb25e721e2056fd19356b82b84b742bfbedab434652b6030a5a9c21fb41323c
|
data/README.md
CHANGED
|
@@ -167,6 +167,52 @@ the app fills the port with.
|
|
|
167
167
|
The model names only its own hub, so the crossing check passes it. The app's
|
|
168
168
|
`to_prepare` block that fills the port is the one place both models are named.
|
|
169
169
|
|
|
170
|
+
### Has-many links through a record port
|
|
171
|
+
A hub's model lists the records another hub owns that point back at it with
|
|
172
|
+
`has_many_port`, naming its own hub and one of that hub's record ports:
|
|
173
|
+
|
|
174
|
+
```ruby
|
|
175
|
+
module Sales
|
|
176
|
+
class Job < ApplicationRecord
|
|
177
|
+
has_many_port :quotes, port: Sales, record: :quote_record
|
|
178
|
+
end
|
|
179
|
+
end
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
hub_kernel defines an ordinary `has_many :quotes` whose class is the model the app filled
|
|
183
|
+
the port with. `job.quotes` lists the quotes, and `includes`, `preload`, `joins`, `where`
|
|
184
|
+
and `order` work on the link as on any `has_many`. Every other `has_many` option, such as
|
|
185
|
+
`foreign_key:` or `inverse_of:`, and a scope passed before the options, reach the
|
|
186
|
+
association unchanged, apart from the options below that write to the listed records.
|
|
187
|
+
The class name comes from the port, so one passed is not used.
|
|
188
|
+
|
|
189
|
+
A has-many link is always read-only, and no option makes it writable:
|
|
190
|
+
|
|
191
|
+
- A record listed through the link, loaded, preloaded or queried, raises
|
|
192
|
+
`ActiveRecord::ReadOnlyRecord` when it is saved or destroyed.
|
|
193
|
+
- Adding a record to the list with `<<`, `push`, `append`, `concat` or `replace`, removing
|
|
194
|
+
one with `delete`, `destroy`, `delete_all`, `destroy_all` or `clear`, and building or
|
|
195
|
+
creating one with `build`, `new`, `create` or `create!` raise
|
|
196
|
+
`ActiveRecord::ReadOnlyRecord`, on the list and on a query made from it.
|
|
197
|
+
- Assigning the list with `quotes=` or `quote_ids=` raises `ActiveRecord::ReadOnlyRecord`.
|
|
198
|
+
- Writing the listed records all at once with `update_all`, `update_counters`, `insert`,
|
|
199
|
+
`insert!`, `insert_all`, `insert_all!`, `upsert` or `upsert_all` raises
|
|
200
|
+
`ActiveRecord::ReadOnlyRecord`.
|
|
201
|
+
- `autosave`, `counter_cache` and a `dependent` value that destroys, deletes or nullifies
|
|
202
|
+
the listed records each write to them, so passing any of them raises an `ArgumentError`
|
|
203
|
+
naming it.
|
|
204
|
+
|
|
205
|
+
`dependent: :restrict_with_exception` and `dependent: :restrict_with_error` write nothing
|
|
206
|
+
to the listed records, so the link takes them. Deleting the model while the link lists
|
|
207
|
+
records is refused, the first way by raising `ActiveRecord::DeleteRestrictionError` and
|
|
208
|
+
the second by adding an error and returning false, and deleting it when the link lists
|
|
209
|
+
none goes ahead.
|
|
210
|
+
|
|
211
|
+
The link is defined as soon as both the model has loaded and the port is filled, in
|
|
212
|
+
whichever order those happen, the same way as a `belongs_to_port` link. A boot with eager
|
|
213
|
+
loading on defines it, and after a code reload in development the reloaded model lists
|
|
214
|
+
the reloaded model the app fills the port with.
|
|
215
|
+
|
|
170
216
|
### The start check
|
|
171
217
|
Every hub that declares a port or exposes a method is on `HubKernel::Hubs.list`. Call
|
|
172
218
|
`HubKernel::Hubs.check!` after the app fills its ports, and the app refuses to start
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
module HubKernel
|
|
2
2
|
module PortAssociations
|
|
3
3
|
WRITING_OPTIONS = %i[autosave dependent touch counter_cache].freeze
|
|
4
|
+
LIST_WRITING_OPTIONS = %i[autosave dependent counter_cache].freeze
|
|
5
|
+
RESTRICTING = %i[restrict_with_error restrict_with_exception].freeze
|
|
6
|
+
LIST_WRITING_METHODS = %i[<< push append concat build new create create! delete destroy delete_all destroy_all clear replace update_all insert insert! insert_all insert_all! upsert upsert_all].freeze
|
|
4
7
|
|
|
5
8
|
def belongs_to_port(name, scope = nil, port:, record:, **options)
|
|
6
9
|
refuse_writing_options(name, options)
|
|
@@ -10,18 +13,35 @@ module HubKernel
|
|
|
10
13
|
end
|
|
11
14
|
end
|
|
12
15
|
|
|
16
|
+
def has_many_port(name, scope = nil, port:, record:, **options)
|
|
17
|
+
refuse_writing_options(name, options.reject { |option, value| option == :dependent && RESTRICTING.include?(value) }, LIST_WRITING_OPTIONS)
|
|
18
|
+
refusal = "#{self.name}'s #{name} link is read-only, so no record can be written through it"
|
|
19
|
+
port.link_record(record, [ self.name, name ]) do |model|
|
|
20
|
+
has_many name, read_only(scope, refusing_list(refusal)), **options, class_name: model.name
|
|
21
|
+
refuse_replacing_list(name, refusal)
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
13
25
|
private
|
|
14
26
|
|
|
15
|
-
def refuse_writing_options(name, options)
|
|
16
|
-
writing = options.keys &
|
|
27
|
+
def refuse_writing_options(name, options, writers = WRITING_OPTIONS)
|
|
28
|
+
writing = options.keys & writers
|
|
17
29
|
raise ArgumentError, "#{name} is a read-only record port link, so it does not take #{writing.to_sentence}" if writing.any?
|
|
18
30
|
end
|
|
19
31
|
|
|
20
|
-
def read_only(scope)
|
|
21
|
-
return -> { readonly } unless scope
|
|
22
|
-
return -> { instance_exec(&scope).readonly } if scope.arity.zero?
|
|
32
|
+
def read_only(scope, *extensions)
|
|
33
|
+
return -> { readonly.extending(*extensions) } unless scope
|
|
34
|
+
return -> { instance_exec(&scope).readonly.extending(*extensions) } if scope.arity.zero?
|
|
35
|
+
|
|
36
|
+
->(owner) { instance_exec(owner, &scope).readonly.extending(*extensions) }
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def refusing_list(refusal)
|
|
40
|
+
Module.new { LIST_WRITING_METHODS.each { |method| define_method(method) { |*| raise ActiveRecord::ReadOnlyRecord, refusal } } }
|
|
41
|
+
end
|
|
23
42
|
|
|
24
|
-
|
|
43
|
+
def refuse_replacing_list(name, refusal)
|
|
44
|
+
[ :"#{name}=", :"#{name.to_s.singularize}_ids=" ].each { |method| define_method(method) { |*| raise ActiveRecord::ReadOnlyRecord, refusal } }
|
|
25
45
|
end
|
|
26
46
|
|
|
27
47
|
def refuse_creating_through(name)
|
data/lib/hub_kernel/version.rb
CHANGED