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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f4736181e42c531aad96202018bb79f79ec46aa69a0cf03a0a991abead29f4af
4
- data.tar.gz: 7aa80b14123c4c8b1e8175d9ff5d187b6918d31ff0f1521a394ebd061d7b14cf
3
+ metadata.gz: 6708f834d18f3aeb71c1c05238bd610d63d25cd04aa2019a7e08c78bc122e6cc
4
+ data.tar.gz: 41aa9e91f4b29e186331e5a242901c7f860fc10b1fdb42cf1cb0b00cebf17483
5
5
  SHA512:
6
- metadata.gz: f02b179664dc676bd35d4e6087194f714d58d56cf88058ea7d739be48e34ec330a1df0a2dd197eb2ed58579757331af7d13b6133ceae5a768f6b07773be0c32b
7
- data.tar.gz: cffc632808753b0ecc39e2400328c42776f7a503e77f7373085438003c4efc3907d5d4a426737660875a611b22c0ce860f8eeb41b79edb1dd921dbafb7b11538
6
+ metadata.gz: 36d195b559a3d9c0e4cdc24234dffadc9fcc7e0decfab35e98e153d3a427b1a85c1a48d4803ff0f8fa6e1c94ea81178fe6c163625276bcaeb72b187aabe05687
7
+ data.tar.gz: 628ca798a9e9a704ac8d4c340d4124cba83013ac721c229a46b758452109e775f2a485971a6c0c80f9cf63eb3eff670869a0b35c60d3c2a3dac400c440facf89
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- nested_record (0.1.1)
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.4)
56
+ crass (1.0.5)
57
57
  diff-lcs (1.3)
58
- erubi (1.8.0)
58
+ erubi (1.9.0)
59
59
  globalid (0.4.2)
60
60
  activesupport (>= 4.2.0)
61
- i18n (1.6.0)
61
+ i18n (1.7.0)
62
62
  concurrent-ruby (~> 1.0)
63
- loofah (2.2.3)
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.1)
72
+ mini_mime (1.0.2)
73
73
  mini_portile2 (2.4.0)
74
- minitest (5.11.3)
75
- nio4r (2.3.1)
76
- nokogiri (1.10.3)
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.4)
104
- loofah (~> 2.2, >= 2.2.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 (3.7.2)
125
+ sprockets (4.0.0)
126
126
  concurrent-ruby (~> 1.0)
127
127
  rack (> 1, < 3)
128
128
  sprockets-rails (3.2.1)
@@ -9,6 +9,7 @@ module NestedRecord
9
9
  require 'nested_record/base'
10
10
  require 'nested_record/collection'
11
11
  require 'nested_record/setup'
12
+ require 'nested_record/methods'
12
13
  require 'nested_record/type'
13
14
  require 'nested_record/errors'
14
15
  require 'nested_record/lookup_const'
@@ -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
- if self < NestedRecord::Base
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 attributes
33
- attributes = attributes.stringify_keys
34
- klass = find_instance_class(attributes['type'])
35
+ if local_subtype?
36
+ return super
35
37
  else
36
- klass = self
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(attributes)
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 = find_instance_class(attributes['type'])
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.instance_variable_set(:@attributes, attributes)
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 inherited_types(options)
74
- raise ArgumentError, '.inherited_types is supported only for base classes' if deep_inherited?
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 ArgumentError, ':full and :namespace options cannot be used at the same time'
80
+ raise NestedRecord::ConfigurationError, ':full and :namespace options cannot be used together'
77
81
  end
78
- @inherited_types_options = options
82
+ @subtypes_options = options
79
83
  end
80
84
 
81
85
  def instance_type
82
86
  @instance_type ||=
83
- if inherited_types_underscored?
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
- protected
91
-
92
- def inherited_types_options
93
- @inherited_types_options ||= {}
94
- if deep_inherited?
95
- superclass.inherited_types_options.merge(@inherited_types_options)
96
- else
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
- private
102
-
103
- def type_const
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 inherited_types_store_full?
112
- !inherited_types_namespace && inherited_types_options.fetch(:full) { true }
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 inherited_types_namespace
116
- return @inherited_types_namespace if defined?(@inherited_types_namespace)
118
+ def def_primary_uuid(name)
119
+ attribute name, :string, default: -> { SecureRandom.uuid }, primary: true
120
+ end
117
121
 
118
- namespace = inherited_types_options.fetch(:namespace) { false }
119
- return (@inherited_types_namespace = false) unless namespace
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
- @inherited_types_namespace = NestedRecord.lookup_const(self, namespace).name
128
+ def type_for_attribute(attr_name)
129
+ attribute_types[attr_name.to_s]
122
130
  end
123
131
 
124
- def inherited_types_underscored?
125
- inherited_types_options.fetch(:underscored) { false }
132
+ def has_attribute?(attr_name)
133
+ attribute_types.key?(attr_name.to_s)
126
134
  end
127
135
 
128
- def find_instance_class(type_name)
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 inherited_types_store_full?
146
+ elsif subtypes_store_full?
138
147
  ActiveSupport::Dependencies.constantize(type_name)
139
- elsif inherited_types_namespace
140
- ActiveSupport::Dependencies.safe_constantize("#{inherited_types_namespace}::#{type_name}") || ActiveSupport::Dependencies.constantize(type_name)
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
- ours = read_attribute(attr)
183
- if others.is_a? Array
184
- others.include? ours
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
- others == ours
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) }
@@ -2,4 +2,5 @@ module NestedRecord
2
2
  class Error < StandardError; end
3
3
  class TypeMismatchError < Error; end
4
4
  class InvalidTypeError < Error; end
5
+ class ConfigurationError < Error; end
5
6
  end
@@ -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