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 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