nobrainer 0.42.0 → 0.43.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +7 -2
- data/lib/no_brainer/criteria/join.rb +1 -0
- data/lib/no_brainer/document/association/belongs_to.rb +48 -6
- data/lib/no_brainer/document/association/core.rb +4 -3
- data/lib/no_brainer/document/association/eager_loader.rb +15 -2
- data/lib/no_brainer/document/association/has_many.rb +26 -8
- data/lib/no_brainer/error.rb +17 -15
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ea5237da296873c106bd564c6ca6e62674dd63bf3e1214e8542c3097bb978c82
|
4
|
+
data.tar.gz: bc02529b83bc6cb8fac1d1895fc3ae927220f6f497a4c4c921e41786b41d3a1a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8aa09bfe2bfc6948258d43d6e9ffc16261ea718da79aabfe2b012ead4b69a42fb0fc5d8d30e6a26a0a1880210ae7f0d342ef0911f3193fa91235e07edb87f509
|
7
|
+
data.tar.gz: 999c029ba2e70dd0fd4f458f091c25bbc493d4be0a181e09ae66871a0f6d43bfab005088d509c509cd6cf58bc3528daf72b296a7e02e589d1d23a59b1502c09d
|
data/CHANGELOG.md
CHANGED
@@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
7
7
|
## [Unreleased]
|
8
8
|
|
9
9
|
|
10
|
+
## [0.43.0] - 2022-06-16
|
11
|
+
### Added
|
12
|
+
- Implements polymorphic associations
|
13
|
+
|
10
14
|
## [0.42.0] - 2022-06-15
|
11
15
|
### Added
|
12
16
|
- Add support for partial compound index queries
|
@@ -124,8 +128,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
124
128
|
- Locks: bug fix: allow small timeouts in lock()
|
125
129
|
- Fix reentrant lock counter on steals
|
126
130
|
|
127
|
-
[Unreleased]: https://github.com/nobrainerorm/nobrainer/compare/v0.
|
128
|
-
[0.
|
131
|
+
[Unreleased]: https://github.com/nobrainerorm/nobrainer/compare/v0.43.0...HEAD
|
132
|
+
[0.43.0]: https://github.com/nobrainerorm/nobrainer/compare/v0.42.0...v0.43.0
|
133
|
+
[0.42.0]: https://github.com/nobrainerorm/nobrainer/compare/v0.41.1...v0.42.0
|
129
134
|
[0.41.1]: https://github.com/nobrainerorm/nobrainer/compare/v0.41.0...v0.41.1
|
130
135
|
[0.41.0]: https://github.com/nobrainerorm/nobrainer/compare/v0.40.0...v0.41.0
|
131
136
|
[0.40.0]: https://github.com/nobrainerorm/nobrainer/compare/v0.36.0...v0.40.0
|
@@ -16,6 +16,7 @@ module NoBrainer::Criteria::Join
|
|
16
16
|
association = model.association_metadata[k.to_sym]
|
17
17
|
raise "`#{k}' must be an association on `#{model}'" unless association
|
18
18
|
raise "join() does not support through associations" if association.options[:through]
|
19
|
+
raise "join() does not support polymorphic associations" if association.options[:polymorphic]
|
19
20
|
|
20
21
|
criteria = association.base_criteria
|
21
22
|
criteria = case v
|
@@ -2,8 +2,11 @@ class NoBrainer::Document::Association::BelongsTo
|
|
2
2
|
include NoBrainer::Document::Association::Core
|
3
3
|
|
4
4
|
class Metadata
|
5
|
-
VALID_OPTIONS = [
|
6
|
-
|
5
|
+
VALID_OPTIONS = %i[
|
6
|
+
primary_key foreign_key foreign_type class_name foreign_key_store_as
|
7
|
+
index validates required uniq unique polymorphic
|
8
|
+
]
|
9
|
+
|
7
10
|
include NoBrainer::Document::Association::Core::Metadata
|
8
11
|
include NoBrainer::Document::Association::EagerLoader::Generic
|
9
12
|
|
@@ -11,6 +14,12 @@ class NoBrainer::Document::Association::BelongsTo
|
|
11
14
|
options[:foreign_key].try(:to_sym) || :"#{target_name}_#{primary_key}"
|
12
15
|
end
|
13
16
|
|
17
|
+
def foreign_type
|
18
|
+
return nil unless options[:polymorphic]
|
19
|
+
|
20
|
+
options[:foreign_type].try(:to_sym) || (:"#{target_name}_type")
|
21
|
+
end
|
22
|
+
|
14
23
|
def primary_key
|
15
24
|
# We default the primary_key to `:id' and not `target_model.pk_name',
|
16
25
|
# because we don't want to require the target_model to be already loaded.
|
@@ -30,12 +39,22 @@ class NoBrainer::Document::Association::BelongsTo
|
|
30
39
|
end
|
31
40
|
end
|
32
41
|
|
33
|
-
def target_model
|
34
|
-
|
42
|
+
def target_model(target_class = nil)
|
43
|
+
return if options[:polymorphic] && target_class.nil?
|
44
|
+
|
45
|
+
model_name = if options[:polymorphic]
|
46
|
+
target_class
|
47
|
+
else
|
48
|
+
options[:class_name] || target_name.to_s.camelize
|
49
|
+
end
|
50
|
+
|
51
|
+
get_model_by_name(model_name)
|
35
52
|
end
|
36
53
|
|
37
|
-
def base_criteria
|
38
|
-
target_model
|
54
|
+
def base_criteria(target_class = nil)
|
55
|
+
model = target_model(target_class)
|
56
|
+
|
57
|
+
model ? model.without_ordering : nil
|
39
58
|
end
|
40
59
|
|
41
60
|
def hook
|
@@ -47,6 +66,11 @@ class NoBrainer::Document::Association::BelongsTo
|
|
47
66
|
raise "Cannot declare `#{target_name}' in #{owner_model}: the foreign_key `#{foreign_key}' is already used"
|
48
67
|
end
|
49
68
|
|
69
|
+
if options[:polymorphic] && options[:class_name]
|
70
|
+
raise 'You cannot set class_name on a polymorphic belongs_to'
|
71
|
+
end
|
72
|
+
|
73
|
+
owner_model.field(foreign_type) if options[:polymorphic]
|
50
74
|
owner_model.field(foreign_key, :store_as => options[:foreign_key_store_as], :index => options[:index])
|
51
75
|
|
52
76
|
unless options[:validates] == false
|
@@ -85,6 +109,7 @@ class NoBrainer::Document::Association::BelongsTo
|
|
85
109
|
end
|
86
110
|
|
87
111
|
def eager_load_owner_key; foreign_key; end
|
112
|
+
def eager_load_owner_type; foreign_type; end
|
88
113
|
def eager_load_target_key; primary_key; end
|
89
114
|
end
|
90
115
|
|
@@ -97,6 +122,17 @@ class NoBrainer::Document::Association::BelongsTo
|
|
97
122
|
@target_container = nil
|
98
123
|
end
|
99
124
|
|
125
|
+
def polymorphic_read
|
126
|
+
return target if loaded?
|
127
|
+
|
128
|
+
target_class = owner.read_attribute(foreign_type)
|
129
|
+
fk = owner.read_attribute(foreign_key)
|
130
|
+
|
131
|
+
if target_class && fk
|
132
|
+
preload(base_criteria(target_class).where(primary_key => fk).first)
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
100
136
|
def read
|
101
137
|
return target if loaded?
|
102
138
|
|
@@ -105,6 +141,12 @@ class NoBrainer::Document::Association::BelongsTo
|
|
105
141
|
end
|
106
142
|
end
|
107
143
|
|
144
|
+
def polymorphic_write(target)
|
145
|
+
owner.write_attribute(foreign_key, target.try(primary_key))
|
146
|
+
owner.write_attribute(foreign_type, target.root_class.name)
|
147
|
+
preload(target)
|
148
|
+
end
|
149
|
+
|
108
150
|
def write(target)
|
109
151
|
assert_target_type(target)
|
110
152
|
owner.write_attribute(foreign_key, target.try(primary_key))
|
@@ -33,8 +33,8 @@ module NoBrainer::Document::Association::Core
|
|
33
33
|
|
34
34
|
def hook
|
35
35
|
options.assert_valid_keys(*self.class.const_get(:VALID_OPTIONS))
|
36
|
-
delegate("#{target_name}=", :write)
|
37
|
-
delegate("#{target_name}", :read)
|
36
|
+
delegate("#{target_name}=", "#{'polymorphic_' if options[:polymorphic]}write".to_sym)
|
37
|
+
delegate("#{target_name}", "#{'polymorphic_' if options[:polymorphic]}read".to_sym)
|
38
38
|
end
|
39
39
|
|
40
40
|
def add_callback_for(what)
|
@@ -62,7 +62,8 @@ module NoBrainer::Document::Association::Core
|
|
62
62
|
|
63
63
|
included { attr_accessor :metadata, :owner }
|
64
64
|
|
65
|
-
delegate :primary_key, :foreign_key, :
|
65
|
+
delegate :primary_key, :foreign_key, :foreign_type, :target_name,
|
66
|
+
:target_model, :base_criteria, :to => :metadata
|
66
67
|
|
67
68
|
def initialize(metadata, owner)
|
68
69
|
@metadata, @owner = metadata, owner
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module NoBrainer::Document::Association::EagerLoader
|
2
4
|
extend self
|
3
5
|
|
@@ -5,11 +7,22 @@ module NoBrainer::Document::Association::EagerLoader
|
|
5
7
|
# Used in associations to declare generic eager loading capabilities
|
6
8
|
# The association should implement loaded?, preload,
|
7
9
|
# eager_load_owner_key and eager_load_target_key.
|
8
|
-
def eager_load(docs, additional_criteria=nil)
|
10
|
+
def eager_load(docs, additional_criteria = nil)
|
9
11
|
owner_key = eager_load_owner_key
|
12
|
+
owner_type = eager_load_owner_type
|
10
13
|
target_key = eager_load_target_key
|
11
14
|
|
12
|
-
|
15
|
+
if is_a?(NoBrainer::Document::Association::BelongsTo::Metadata) && owner_type
|
16
|
+
target_class = docs.first.__send__(owner_type)
|
17
|
+
|
18
|
+
if docs.detect { |doc| doc.__send__(owner_type) != target_class }
|
19
|
+
raise NoBrainer::Error::PolymorphicAssociationWithDifferentTypes,
|
20
|
+
"The documents to be eager loaded doesn't have the same " \
|
21
|
+
'type, which is not supported'
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
criteria = target_class ? base_criteria(target_class) : base_criteria
|
13
26
|
criteria = criteria.merge(additional_criteria) if additional_criteria
|
14
27
|
|
15
28
|
unloaded_docs = docs.reject { |doc| doc.associations[self].loaded? }
|
@@ -2,12 +2,20 @@ class NoBrainer::Document::Association::HasMany
|
|
2
2
|
include NoBrainer::Document::Association::Core
|
3
3
|
|
4
4
|
class Metadata
|
5
|
-
VALID_OPTIONS = [:primary_key, :foreign_key, :class_name, :dependent, :scope
|
5
|
+
VALID_OPTIONS = [:primary_key, :foreign_key, :class_name, :dependent, :scope,
|
6
|
+
:as]
|
6
7
|
include NoBrainer::Document::Association::Core::Metadata
|
7
8
|
include NoBrainer::Document::Association::EagerLoader::Generic
|
8
9
|
|
9
10
|
def foreign_key
|
10
|
-
options[:foreign_key].try(:to_sym)
|
11
|
+
return options[:foreign_key].try(:to_sym) if options.key?(:foreign_key)
|
12
|
+
return :"#{options[:as]}_#{primary_key}" if options[:as]
|
13
|
+
|
14
|
+
:"#{owner_model.name.split('::').last.underscore}_#{primary_key}"
|
15
|
+
end
|
16
|
+
|
17
|
+
def foreign_type
|
18
|
+
options[:foreign_type].try(:to_sym) || (options[:as] && :"#{options[:as]}_type")
|
11
19
|
end
|
12
20
|
|
13
21
|
def primary_key
|
@@ -30,9 +38,9 @@ class NoBrainer::Document::Association::HasMany
|
|
30
38
|
# caching is hard (rails console reload, etc.).
|
31
39
|
target_model.association_metadata.values.select do |assoc|
|
32
40
|
assoc.is_a?(NoBrainer::Document::Association::BelongsTo::Metadata) and
|
33
|
-
assoc.foreign_key ==
|
34
|
-
assoc.primary_key ==
|
35
|
-
assoc.target_model.root_class == owner_model.root_class
|
41
|
+
assoc.foreign_key == foreign_key and
|
42
|
+
assoc.primary_key == primary_key and
|
43
|
+
assoc.target_model(target_model).root_class == owner_model.root_class
|
36
44
|
end
|
37
45
|
end
|
38
46
|
|
@@ -46,7 +54,7 @@ class NoBrainer::Document::Association::HasMany
|
|
46
54
|
|
47
55
|
if options[:dependent]
|
48
56
|
unless [:destroy, :delete, :nullify, :restrict, nil].include?(options[:dependent])
|
49
|
-
raise "Invalid dependent option: `#{options[:dependent].inspect}'. "
|
57
|
+
raise "Invalid dependent option: `#{options[:dependent].inspect}'. " \
|
50
58
|
"Valid options are: :destroy, :delete, :nullify, or :restrict"
|
51
59
|
end
|
52
60
|
add_callback_for(:before_destroy)
|
@@ -54,12 +62,22 @@ class NoBrainer::Document::Association::HasMany
|
|
54
62
|
end
|
55
63
|
|
56
64
|
def eager_load_owner_key; primary_key; end
|
65
|
+
def eager_load_owner_type; foreign_type; end
|
57
66
|
def eager_load_target_key; foreign_key; end
|
58
67
|
end
|
59
68
|
|
60
69
|
def target_criteria
|
61
|
-
@target_criteria ||=
|
62
|
-
|
70
|
+
@target_criteria ||= begin
|
71
|
+
query_criteria = { foreign_key => owner.__send__(primary_key) }
|
72
|
+
|
73
|
+
if metadata.options[:as]
|
74
|
+
query_criteria = query_criteria.merge(
|
75
|
+
foreign_type => owner.root_class.name
|
76
|
+
)
|
77
|
+
end
|
78
|
+
|
79
|
+
base_criteria.where(query_criteria).after_find(set_inverse_proc)
|
80
|
+
end
|
63
81
|
end
|
64
82
|
|
65
83
|
def read
|
data/lib/no_brainer/error.rb
CHANGED
@@ -1,19 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module NoBrainer::Error
|
2
|
-
class
|
3
|
-
class
|
4
|
-
class
|
5
|
-
class
|
6
|
-
class
|
7
|
-
class
|
8
|
-
class
|
9
|
-
class
|
10
|
-
class
|
11
|
-
class
|
12
|
-
class
|
13
|
-
class
|
14
|
-
class
|
15
|
-
class
|
16
|
-
class
|
4
|
+
class AssociationNotPersisted < RuntimeError; end
|
5
|
+
class AtomicBlock < RuntimeError; end
|
6
|
+
class ChildrenExist < RuntimeError; end
|
7
|
+
class Connection < RuntimeError; end
|
8
|
+
class DocumentNotFound < RuntimeError; end
|
9
|
+
class DocumentNotPersisted < RuntimeError; end
|
10
|
+
class InvalidPolymorphicType < RuntimeError; end
|
11
|
+
class LockInvalidOp < RuntimeError; end
|
12
|
+
class LostLock < RuntimeError; end
|
13
|
+
class LockUnavailable < RuntimeError; end
|
14
|
+
class MissingAttribute < RuntimeError; end
|
15
|
+
class MissingIndex < RuntimeError; end
|
16
|
+
class PolymorphicAssociationWithDifferentTypes < RuntimeError; end
|
17
|
+
class ReadonlyField < RuntimeError; end
|
18
|
+
class UnknownAttribute < RuntimeError; end
|
17
19
|
|
18
20
|
class DocumentInvalid < RuntimeError
|
19
21
|
attr_accessor :instance
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: nobrainer
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.43.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Nicolas Viennot
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2022-06-
|
11
|
+
date: 2022-06-16 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activemodel
|