nested_record 0.1.1 → 1.0.0.beta
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/Gemfile.lock +12 -12
- data/lib/nested_record.rb +1 -0
- data/lib/nested_record/base.rb +135 -54
- data/lib/nested_record/collection.rb +20 -1
- data/lib/nested_record/errors.rb +1 -0
- data/lib/nested_record/macro.rb +2 -2
- data/lib/nested_record/methods.rb +50 -0
- data/lib/nested_record/methods/many.rb +113 -0
- data/lib/nested_record/methods/one.rb +86 -0
- data/lib/nested_record/setup.rb +72 -177
- data/lib/nested_record/type.rb +3 -39
- data/lib/nested_record/type/many.rb +24 -0
- data/lib/nested_record/type/one.rb +17 -0
- data/lib/nested_record/version.rb +1 -1
- data/spec/nested_record/base_spec.rb +106 -9
- data/spec/nested_record/type/many_spec.rb +23 -0
- data/spec/nested_record/type/one_spec.rb +22 -0
- data/spec/nested_record_spec.rb +346 -13
- data/spec/spec_helper.rb +1 -0
- data/spec/support/model.rb +19 -15
- metadata +11 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6708f834d18f3aeb71c1c05238bd610d63d25cd04aa2019a7e08c78bc122e6cc
|
4
|
+
data.tar.gz: 41aa9e91f4b29e186331e5a242901c7f860fc10b1fdb42cf1cb0b00cebf17483
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 36d195b559a3d9c0e4cdc24234dffadc9fcc7e0decfab35e98e153d3a427b1a85c1a48d4803ff0f8fa6e1c94ea81178fe6c163625276bcaeb72b187aabe05687
|
7
|
+
data.tar.gz: 628ca798a9e9a704ac8d4c340d4124cba83013ac721c229a46b758452109e775f2a485971a6c0c80f9cf63eb3eff670869a0b35c60d3c2a3dac400c440facf89
|
data/Gemfile.lock
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
nested_record (0.
|
4
|
+
nested_record (1.0.0.beta)
|
5
5
|
rails (~> 5.2)
|
6
6
|
|
7
7
|
GEM
|
@@ -53,14 +53,14 @@ GEM
|
|
53
53
|
byebug (11.0.1)
|
54
54
|
coderay (1.1.2)
|
55
55
|
concurrent-ruby (1.1.5)
|
56
|
-
crass (1.0.
|
56
|
+
crass (1.0.5)
|
57
57
|
diff-lcs (1.3)
|
58
|
-
erubi (1.
|
58
|
+
erubi (1.9.0)
|
59
59
|
globalid (0.4.2)
|
60
60
|
activesupport (>= 4.2.0)
|
61
|
-
i18n (1.
|
61
|
+
i18n (1.7.0)
|
62
62
|
concurrent-ruby (~> 1.0)
|
63
|
-
loofah (2.
|
63
|
+
loofah (2.3.1)
|
64
64
|
crass (~> 1.0.2)
|
65
65
|
nokogiri (>= 1.5.9)
|
66
66
|
mail (2.7.1)
|
@@ -69,11 +69,11 @@ GEM
|
|
69
69
|
mimemagic (~> 0.3.2)
|
70
70
|
method_source (0.9.2)
|
71
71
|
mimemagic (0.3.3)
|
72
|
-
mini_mime (1.0.
|
72
|
+
mini_mime (1.0.2)
|
73
73
|
mini_portile2 (2.4.0)
|
74
|
-
minitest (5.
|
75
|
-
nio4r (2.
|
76
|
-
nokogiri (1.10.
|
74
|
+
minitest (5.13.0)
|
75
|
+
nio4r (2.5.2)
|
76
|
+
nokogiri (1.10.4)
|
77
77
|
mini_portile2 (~> 2.4.0)
|
78
78
|
pry (0.12.2)
|
79
79
|
coderay (~> 1.1.0)
|
@@ -100,8 +100,8 @@ GEM
|
|
100
100
|
rails-dom-testing (2.0.3)
|
101
101
|
activesupport (>= 4.2.0)
|
102
102
|
nokogiri (>= 1.6)
|
103
|
-
rails-html-sanitizer (1.0
|
104
|
-
loofah (~> 2.
|
103
|
+
rails-html-sanitizer (1.3.0)
|
104
|
+
loofah (~> 2.3)
|
105
105
|
railties (5.2.3)
|
106
106
|
actionpack (= 5.2.3)
|
107
107
|
activesupport (= 5.2.3)
|
@@ -122,7 +122,7 @@ GEM
|
|
122
122
|
diff-lcs (>= 1.2.0, < 2.0)
|
123
123
|
rspec-support (~> 3.8.0)
|
124
124
|
rspec-support (3.8.2)
|
125
|
-
sprockets (
|
125
|
+
sprockets (4.0.0)
|
126
126
|
concurrent-ruby (~> 1.0)
|
127
127
|
rack (> 1, < 3)
|
128
128
|
sprockets-rails (3.2.1)
|
data/lib/nested_record.rb
CHANGED
data/lib/nested_record/base.rb
CHANGED
@@ -7,6 +7,8 @@ class NestedRecord::Base
|
|
7
7
|
include NestedRecord::Macro
|
8
8
|
|
9
9
|
class << self
|
10
|
+
include ActiveModel::Callbacks
|
11
|
+
|
10
12
|
def attributes_builder # :nodoc:
|
11
13
|
unless defined?(@attributes_builder) && @attributes_builder
|
12
14
|
@attributes_builder = ActiveModel::AttributeSet::Builder.new(attribute_types, _default_attributes)
|
@@ -15,9 +17,10 @@ class NestedRecord::Base
|
|
15
17
|
end
|
16
18
|
|
17
19
|
def inherited(klass)
|
18
|
-
|
20
|
+
parent = self
|
21
|
+
if parent < NestedRecord::Base
|
19
22
|
klass.class_eval do
|
20
|
-
attribute :type, :string
|
23
|
+
attribute :type, :string unless parent.has_attribute? :type
|
21
24
|
@deep_inherted = true
|
22
25
|
end
|
23
26
|
end
|
@@ -29,31 +32,31 @@ class NestedRecord::Base
|
|
29
32
|
end
|
30
33
|
|
31
34
|
def new(attributes = nil)
|
32
|
-
if
|
33
|
-
|
34
|
-
klass = find_instance_class(attributes['type'])
|
35
|
+
if local_subtype?
|
36
|
+
return super
|
35
37
|
else
|
36
|
-
|
38
|
+
if attributes
|
39
|
+
attributes = attributes.stringify_keys
|
40
|
+
klass = find_subtype(attributes['type'])
|
41
|
+
else
|
42
|
+
klass = self
|
43
|
+
end
|
37
44
|
end
|
38
45
|
if self == klass
|
39
|
-
super
|
46
|
+
super
|
40
47
|
else
|
41
48
|
klass.new(attributes)
|
42
49
|
end
|
43
50
|
end
|
44
51
|
|
45
52
|
def instantiate(attributes)
|
46
|
-
klass =
|
53
|
+
klass = find_subtype(attributes['type'])
|
47
54
|
attributes = klass.attributes_builder.build_from_database(attributes)
|
48
55
|
klass.allocate.tap do |instance|
|
49
|
-
instance.
|
56
|
+
instance.init_with_attributes(attributes)
|
50
57
|
end
|
51
58
|
end
|
52
59
|
|
53
|
-
def collection_class_name
|
54
|
-
:NestedRecord_Collection
|
55
|
-
end
|
56
|
-
|
57
60
|
def collection_class
|
58
61
|
return const_get(collection_class_name, false) if const_defined?(collection_class_name, false)
|
59
62
|
record_class = self
|
@@ -67,78 +70,83 @@ class NestedRecord::Base
|
|
67
70
|
end
|
68
71
|
|
69
72
|
def collection_methods(&block)
|
73
|
+
raise ArgumentError, 'block is required for .collection_methods' unless block
|
70
74
|
collection_class.class_eval(&block)
|
71
75
|
end
|
72
76
|
|
73
|
-
def
|
74
|
-
raise
|
77
|
+
def subtypes(options)
|
78
|
+
raise NestedRecord::ConfigurationError, '.subtypes is supported only for base classes' if deep_inherited?
|
75
79
|
if options[:full] && options[:namespace]
|
76
|
-
raise
|
80
|
+
raise NestedRecord::ConfigurationError, ':full and :namespace options cannot be used together'
|
77
81
|
end
|
78
|
-
@
|
82
|
+
@subtypes_options = options
|
79
83
|
end
|
80
84
|
|
81
85
|
def instance_type
|
82
86
|
@instance_type ||=
|
83
|
-
if
|
87
|
+
if subtypes_underscored?
|
84
88
|
type_const.underscore
|
85
89
|
else
|
86
90
|
type_const.dup
|
87
91
|
end
|
88
92
|
end
|
89
93
|
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
@inherited_types_options
|
94
|
+
def subtype(name, &block)
|
95
|
+
raise NotImplementedError, 'TODO: Subtyping from local subtype is not supported at the moment' if local_subtype?
|
96
|
+
class_name = name.to_s.camelize
|
97
|
+
subtype = Class.new(self) do
|
98
|
+
@local_subtype = true
|
99
|
+
@type_const = class_name
|
100
|
+
class_eval(&block) if block
|
98
101
|
end
|
102
|
+
local_subtypes!.const_set(class_name, subtype)
|
99
103
|
end
|
100
104
|
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
if inherited_types_namespace
|
105
|
-
name.gsub(/\A#{inherited_types_namespace}::/, '')
|
106
|
-
else
|
107
|
-
name
|
105
|
+
def attribute(name, *args, primary: false, **options)
|
106
|
+
super(name, *args, **options).tap do
|
107
|
+
primary_key(name) if primary
|
108
108
|
end
|
109
109
|
end
|
110
110
|
|
111
|
-
def
|
112
|
-
|
111
|
+
def primary_key(*attributes)
|
112
|
+
unless attributes.empty?
|
113
|
+
self.primary_key = attributes
|
114
|
+
end
|
115
|
+
@primary_key
|
113
116
|
end
|
114
117
|
|
115
|
-
def
|
116
|
-
|
118
|
+
def def_primary_uuid(name)
|
119
|
+
attribute name, :string, default: -> { SecureRandom.uuid }, primary: true
|
120
|
+
end
|
117
121
|
|
118
|
-
|
119
|
-
|
122
|
+
def primary_key=(attributes)
|
123
|
+
attributes = Array(attributes)
|
124
|
+
raise ArgumentError, 'primary_key cannot be an empty array' if attributes.empty?
|
125
|
+
@primary_key = attributes.map(&:to_s)
|
126
|
+
end
|
120
127
|
|
121
|
-
|
128
|
+
def type_for_attribute(attr_name)
|
129
|
+
attribute_types[attr_name.to_s]
|
122
130
|
end
|
123
131
|
|
124
|
-
def
|
125
|
-
|
132
|
+
def has_attribute?(attr_name)
|
133
|
+
attribute_types.key?(attr_name.to_s)
|
126
134
|
end
|
127
135
|
|
128
|
-
def
|
136
|
+
def find_subtype(type_name)
|
129
137
|
return self unless type_name.present?
|
130
138
|
|
131
|
-
type_name = type_name.camelize
|
139
|
+
type_name = type_name.to_s.camelize
|
132
140
|
|
133
|
-
subclass =
|
141
|
+
subclass = local_subtype(type_name)
|
142
|
+
subclass ||=
|
134
143
|
begin
|
135
144
|
if type_name.start_with?('::')
|
136
145
|
ActiveSupport::Dependencies.constantize(type_name)
|
137
|
-
elsif
|
146
|
+
elsif subtypes_store_full?
|
138
147
|
ActiveSupport::Dependencies.constantize(type_name)
|
139
|
-
elsif
|
140
|
-
ActiveSupport::Dependencies.safe_constantize("#{
|
141
|
-
|
148
|
+
elsif subtypes_namespace
|
149
|
+
ActiveSupport::Dependencies.safe_constantize("#{subtypes_namespace}::#{type_name}") || ActiveSupport::Dependencies.constantize(type_name)
|
142
150
|
else
|
143
151
|
NestedRecord.lookup_const(self, type_name)
|
144
152
|
end
|
@@ -153,11 +161,75 @@ class NestedRecord::Base
|
|
153
161
|
end
|
154
162
|
subclass
|
155
163
|
end
|
164
|
+
|
165
|
+
protected
|
166
|
+
|
167
|
+
def subtypes_options
|
168
|
+
@subtypes_options ||= {}
|
169
|
+
if deep_inherited?
|
170
|
+
superclass.subtypes_options.merge(@subtypes_options)
|
171
|
+
else
|
172
|
+
@subtypes_options
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
private
|
177
|
+
|
178
|
+
def collection_class_name
|
179
|
+
:NestedRecord_Collection
|
180
|
+
end
|
181
|
+
|
182
|
+
def type_const
|
183
|
+
@type_const ||= if subtypes_namespace
|
184
|
+
name.gsub(/\A#{subtypes_namespace}::/, '')
|
185
|
+
else
|
186
|
+
name
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
def subtypes_store_full?
|
191
|
+
!subtypes_namespace && subtypes_options.fetch(:full) { true }
|
192
|
+
end
|
193
|
+
|
194
|
+
def subtypes_namespace
|
195
|
+
return @subtypes_namespace if defined?(@subtypes_namespace)
|
196
|
+
|
197
|
+
namespace = subtypes_options.fetch(:namespace) { false }
|
198
|
+
return (@subtypes_namespace = false) unless namespace
|
199
|
+
|
200
|
+
@subtypes_namespace = NestedRecord.lookup_const(self, namespace).name
|
201
|
+
end
|
202
|
+
|
203
|
+
def subtypes_underscored?
|
204
|
+
subtypes_options.fetch(:underscored) { false }
|
205
|
+
end
|
206
|
+
|
207
|
+
def local_subtype?
|
208
|
+
@local_subtype == true
|
209
|
+
end
|
210
|
+
|
211
|
+
def local_subtypes
|
212
|
+
(const_defined?(:LocalTypes, false) && const_get(:LocalTypes, false)) || nil
|
213
|
+
end
|
214
|
+
|
215
|
+
def local_subtypes!
|
216
|
+
local_subtypes || const_set(:LocalTypes, Module.new)
|
217
|
+
end
|
218
|
+
|
219
|
+
def local_subtype(type_name)
|
220
|
+
(local_subtypes&.const_defined?(type_name, false) && local_subtypes.const_get(type_name, false)) || nil
|
221
|
+
end
|
156
222
|
end
|
157
223
|
|
158
224
|
def initialize(attributes = nil)
|
159
225
|
super
|
160
|
-
self.type = self.class.instance_type if self.class.deep_inherited?
|
226
|
+
self.type = self.class.instance_type if self.class.deep_inherited? && !(attributes&.key?('type') || attributes&.key?(:type))
|
227
|
+
_run_initialize_callbacks
|
228
|
+
end
|
229
|
+
|
230
|
+
def init_with_attributes(attributes)
|
231
|
+
@attributes = attributes
|
232
|
+
_run_initialize_callbacks
|
161
233
|
end
|
162
234
|
|
163
235
|
def ==(other)
|
@@ -179,12 +251,21 @@ class NestedRecord::Base
|
|
179
251
|
|
180
252
|
def match?(attrs)
|
181
253
|
attrs.all? do |attr, others|
|
182
|
-
|
183
|
-
|
184
|
-
|
254
|
+
case attr
|
255
|
+
when :_is_a?, '_is_a?'
|
256
|
+
is_a? others
|
257
|
+
when :_instance_of?, '_instance_of?'
|
258
|
+
instance_of? others
|
185
259
|
else
|
186
|
-
|
260
|
+
ours = read_attribute(attr)
|
261
|
+
if others.is_a? Array
|
262
|
+
others.include? ours
|
263
|
+
else
|
264
|
+
others == ours
|
265
|
+
end
|
187
266
|
end
|
188
267
|
end
|
189
268
|
end
|
269
|
+
|
270
|
+
define_model_callbacks :initialize, only: :after
|
190
271
|
end
|
@@ -86,11 +86,30 @@ class NestedRecord::Collection
|
|
86
86
|
end
|
87
87
|
|
88
88
|
def reject_by!(attrs)
|
89
|
-
return to_enum(:reject_by!) unless block_given?
|
90
89
|
attrs = attrs.stringify_keys
|
91
90
|
reject! { |obj| obj.match?(attrs) }
|
92
91
|
end
|
93
92
|
|
93
|
+
def select
|
94
|
+
if block_given?
|
95
|
+
dup.select! { |obj| yield obj }
|
96
|
+
else
|
97
|
+
to_enum(:select)
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
%i[select reject sort_by].each do |meth|
|
102
|
+
class_eval <<~RUBY, __FILE__, __LINE__ + 1
|
103
|
+
def #{meth}
|
104
|
+
if block_given?
|
105
|
+
dup.#{meth}! { |obj| yield obj }
|
106
|
+
else
|
107
|
+
to_enum(:#{meth})
|
108
|
+
end
|
109
|
+
end
|
110
|
+
RUBY
|
111
|
+
end
|
112
|
+
|
94
113
|
def find_by(attrs)
|
95
114
|
attrs = attrs.stringify_keys
|
96
115
|
find { |obj| obj.match?(attrs) }
|
data/lib/nested_record/errors.rb
CHANGED
data/lib/nested_record/macro.rb
CHANGED
@@ -8,8 +8,8 @@ module NestedRecord::Macro
|
|
8
8
|
NestedRecord::Setup::HasMany.new(self, name, **options, &block)
|
9
9
|
end
|
10
10
|
|
11
|
-
def has_one_nested(name, **options)
|
12
|
-
NestedRecord::Setup::HasOne.new(self, name, **options)
|
11
|
+
def has_one_nested(name, **options, &block)
|
12
|
+
NestedRecord::Setup::HasOne.new(self, name, **options, &block)
|
13
13
|
end
|
14
14
|
end
|
15
15
|
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
class NestedRecord::Methods < Module
|
2
|
+
def initialize(setup)
|
3
|
+
@setup = setup
|
4
|
+
end
|
5
|
+
|
6
|
+
def define(name)
|
7
|
+
method_name = public_send("#{name}_method_name")
|
8
|
+
method_body = public_send("#{name}_method_body")
|
9
|
+
case method_body
|
10
|
+
when Proc
|
11
|
+
define_method(method_name, &method_body)
|
12
|
+
when String
|
13
|
+
module_eval <<~RUBY
|
14
|
+
def #{method_name}
|
15
|
+
#{method_body}
|
16
|
+
end
|
17
|
+
RUBY
|
18
|
+
else
|
19
|
+
fail
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def writer_method_name
|
24
|
+
:"#{@setup.name}="
|
25
|
+
end
|
26
|
+
|
27
|
+
def upsert_attributes_method_name
|
28
|
+
:"upsert_#{@setup.name}_attributes"
|
29
|
+
end
|
30
|
+
|
31
|
+
def rewrite_attributes_method_name
|
32
|
+
:"rewrite_#{@setup.name}_attributes"
|
33
|
+
end
|
34
|
+
|
35
|
+
def attributes_writer_method_name
|
36
|
+
:"#{@setup.name}_attributes="
|
37
|
+
end
|
38
|
+
|
39
|
+
def define_attributes_writer_method
|
40
|
+
case @setup.attributes_writer_strategy
|
41
|
+
when :rewrite
|
42
|
+
alias_method attributes_writer_method_name, rewrite_attributes_method_name
|
43
|
+
when :upsert
|
44
|
+
alias_method attributes_writer_method_name, upsert_attributes_method_name
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
require 'nested_record/methods/many'
|
49
|
+
require 'nested_record/methods/one'
|
50
|
+
end
|