nested_record 0.1.1 → 1.0.0.beta
Sign up to get free protection for your applications and to get access to all the features.
- 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
|