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.
@@ -0,0 +1,113 @@
1
+ class NestedRecord::Methods
2
+ class Many < self
3
+ def initialize(setup)
4
+ super
5
+ define :writer
6
+ define :rewrite_attributes
7
+ define :upsert_attributes
8
+ define :validation
9
+ define_attributes_writer_method
10
+ end
11
+
12
+ def validation_method_name
13
+ :"validate_associated_records_for_#{@setup.name}"
14
+ end
15
+
16
+ def writer_method_body
17
+ setup = @setup
18
+ proc do |records|
19
+ collection_class = setup.collection_class
20
+ return super(records.dup) if records.is_a? collection_class
21
+ collection = collection_class.new
22
+ records.each { |obj| collection << obj }
23
+ super(collection)
24
+ end
25
+ end
26
+
27
+ def upsert_attributes_method_body
28
+ setup = @setup
29
+ name = @setup.name
30
+ proc do |data|
31
+ attributes_collection =
32
+ if data.is_a? Hash
33
+ data.values
34
+ else
35
+ data
36
+ end
37
+ collection = public_send(name)
38
+ attributes_collection.each do |attributes|
39
+ attributes = attributes.stringify_keys
40
+ next if setup.reject_if_proc&.call(attributes)
41
+
42
+ if (pkey_attributes = setup.primary_key)
43
+ klass = setup.record_class
44
+ else
45
+ klass = setup.record_class.find_subtype(attributes['type'])
46
+ while !(pkey_attributes = klass.primary_key) && (klass < NestedRecord::Base)
47
+ klass = klass.superclass
48
+ end
49
+ unless pkey_attributes
50
+ raise NestedRecord::ConfigurationError, 'You should specify a primary_key when using :upsert strategy'
51
+ end
52
+ end
53
+ key = { _is_a?: klass }
54
+ pkey_attributes.each do |name|
55
+ value = attributes[name]
56
+ if (type = klass.type_for_attribute(name))
57
+ value = type.cast(value)
58
+ end
59
+ key[name] = value
60
+ end
61
+ if (record = collection.find_by(key))
62
+ record.assign_attributes(attributes)
63
+ else
64
+ collection.build(attributes)
65
+ end
66
+ end
67
+ end
68
+ end
69
+
70
+ def rewrite_attributes_method_body
71
+ setup = @setup
72
+ writer_method_name = self.writer_method_name
73
+ proc do |data|
74
+ attributes_collection =
75
+ if data.is_a? Hash
76
+ data.values
77
+ else
78
+ data
79
+ end
80
+ collection = setup.collection_class.new
81
+ attributes_collection.each do |attributes|
82
+ attributes = attributes.stringify_keys
83
+ next if setup.reject_if_proc&.call(attributes)
84
+ collection.build(attributes)
85
+ end
86
+ public_send(writer_method_name, collection)
87
+ end
88
+ end
89
+
90
+ def validation_method_body
91
+ name = @setup.name
92
+ proc do
93
+ collection = public_send(name)
94
+ collection.map.with_index do |record, index|
95
+ next true if record.valid?
96
+ record.errors.each do |attribute, message|
97
+ error_attribute = "#{name}[#{index}].#{attribute}"
98
+ errors[error_attribute] << message
99
+ errors[error_attribute].uniq!
100
+ end
101
+ record.errors.details.each_key do |attribute|
102
+ error_attribute = "#{name}[#{index}].#{attribute}"
103
+ record.errors.details[attribute].each do |error|
104
+ errors.details[error_attribute] << error
105
+ errors.details[error_attribute].uniq!
106
+ end
107
+ end
108
+ false
109
+ end.all?
110
+ end
111
+ end
112
+ end
113
+ end
@@ -0,0 +1,86 @@
1
+ class NestedRecord::Methods
2
+ class One < self
3
+ def initialize(setup)
4
+ super
5
+ define :writer
6
+ define :build
7
+ alias_method rewrite_attributes_method_name, build_method_name
8
+ define :bang
9
+ define :upsert_attributes
10
+ define :validation
11
+ define_attributes_writer_method
12
+ end
13
+
14
+ def writer_method_body
15
+ setup = @setup
16
+ proc do |record|
17
+ unless record.nil? || record.kind_of?(setup.record_class)
18
+ raise NestedRecord::TypeMismatchError, "#{record.inspect} should be a #{setup.record_class}"
19
+ end
20
+ super(record)
21
+ end
22
+ end
23
+
24
+ def build_method_name
25
+ :"build_#{@setup.name}"
26
+ end
27
+
28
+ def build_method_body
29
+ setup = @setup
30
+ writer_method_name = self.writer_method_name
31
+ proc do |attributes = {}|
32
+ record = setup.record_class.new(attributes)
33
+ public_send(writer_method_name, record)
34
+ end
35
+ end
36
+
37
+ def bang_method_name
38
+ :"#{@setup.name}!"
39
+ end
40
+
41
+ def bang_method_body
42
+ <<~RUBY
43
+ #{@setup.name} || #{build_method_name}
44
+ RUBY
45
+ end
46
+
47
+ def upsert_attributes_method_body
48
+ build_method_name = self.build_method_name
49
+ name = @setup.name
50
+ proc do |attributes|
51
+ if (record = public_send(name))
52
+ record.assign_attributes(attributes)
53
+ else
54
+ public_send(build_method_name, attributes)
55
+ end
56
+ end
57
+ end
58
+
59
+ def validation_method_name
60
+ :"validate_associated_record_for_#{@setup.name}"
61
+ end
62
+
63
+ def validation_method_body
64
+ name = @setup.name
65
+ proc do
66
+ record = public_send(name)
67
+ return true unless record
68
+ return true if record.valid?
69
+
70
+ record.errors.each do |attribute, message|
71
+ error_attribute = "#{name}.#{attribute}"
72
+ errors[error_attribute] << message
73
+ errors[error_attribute].uniq!
74
+ end
75
+ record.errors.details.each_key do |attribute|
76
+ error_attribute = "#{name}.#{attribute}"
77
+ record.errors.details[attribute].each do |error|
78
+ errors.details[error_attribute] << error
79
+ errors.details[error_attribute].uniq!
80
+ end
81
+ end
82
+ false
83
+ end
84
+ end
85
+ end
86
+ end
@@ -1,22 +1,60 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class NestedRecord::Setup
4
- def initialize(owner, name, **options, &extension)
4
+ attr_reader :name, :primary_key, :reject_if_proc
5
+
6
+ def initialize(owner, name, **options, &block)
5
7
  @options = options
6
8
  @owner = owner
7
- if options[:class_name]
8
- @record_class = options[:class_name]
9
+ @name = name
10
+
11
+ if block
12
+ case (cn = options.fetch(:class_name) { false })
13
+ when true
14
+ cn = name.to_s.camelize
15
+ cn = cn.singularize if self.is_a?(HasMany)
16
+ class_name = cn
17
+ when false
18
+ class_name = nil
19
+ when String, Symbol
20
+ class_name = cn.to_s
21
+ else
22
+ raise NestedRecord::ConfigurationError, "Bad :class_name option #{cn.inspect}"
23
+ end
24
+ @record_class = Class.new(NestedRecord::Base, &block)
25
+ @owner.const_set(class_name, @record_class) if class_name
9
26
  else
10
- @record_class = name.to_s.camelize
11
- @record_class = @record_class.singularize if self.is_a?(HasMany)
27
+ if options.key? :class_name
28
+ case (cn = options.fetch(:class_name))
29
+ when String, Symbol
30
+ @record_class = cn.to_s
31
+ else
32
+ raise NestedRecord::ConfigurationError, "Bad :class_name option #{cn.inspect}"
33
+ end
34
+ else
35
+ cn = name.to_s.camelize
36
+ cn = cn.singularize if self.is_a?(HasMany)
37
+ @record_class = cn
38
+ end
12
39
  end
13
- @name = name
14
- @extension = extension
15
40
 
16
- define_methods
41
+ case (aw = options.fetch(:attributes_writer) { {} })
42
+ when Hash
43
+ @attributes_writer_opts = aw
44
+ when true, false
45
+ @attributes_writer_opts = {}
46
+ when Symbol
47
+ @attributes_writer_opts = { strategy: aw }
48
+ else
49
+ raise NestedRecord::ConfigurationError, "Bad :attributes_writer option #{aw.inspect}"
50
+ end
51
+ @reject_if_proc = @attributes_writer_opts[:reject_if]
52
+
53
+ @methods_extension = build_methods_extension
17
54
 
18
55
  @owner.attribute @name, type, default: default_value
19
- @owner.validate validation_method_name
56
+ @owner.include @methods_extension
57
+ @owner.validate @methods_extension.validation_method_name
20
58
  end
21
59
 
22
60
  def record_class
@@ -26,57 +64,36 @@ class NestedRecord::Setup
26
64
  @record_class
27
65
  end
28
66
 
29
- private
30
-
31
- def writer_method_name
32
- @writer_method_name ||= :"#{@name}="
33
- end
34
-
35
- def attributes_writer_method_name
36
- @attributes_writer_method_name ||= :"#{@name}_attributes="
67
+ def primary_key
68
+ return @primary_key if defined? @primary_key
69
+ @primary_key = Array(@options[:primary_key])
70
+ if @primary_key.empty?
71
+ @primary_key = nil
72
+ else
73
+ @primary_key = @primary_key.map(&:to_s)
74
+ end
75
+ @primary_key
37
76
  end
38
77
 
39
- def validation_method_name
40
- @validation_method_name ||= :"validate_associated_records_for_#{@name}"
78
+ def attributes_writer_strategy
79
+ return unless @options.fetch(:attributes_writer) { true }
80
+ case (strategy = @attributes_writer_opts.fetch(:strategy) { :upsert })
81
+ when :rewrite, :upsert
82
+ return strategy
83
+ else
84
+ raise NestedRecord::ConfigurationError, "Unknown strategy #{strategy.inspect}"
85
+ end
41
86
  end
42
87
 
43
- def define_methods
44
- define_writer_method
45
- define_attributes_writer_method
46
- define_validation_method
47
- end
88
+ private
48
89
 
49
90
  class HasMany < self
50
- def initialize(*)
51
- super
52
- if (attributes_writer_opts = @options[:attributes_writer]).is_a? Hash
53
- @reject_if_proc = attributes_writer_opts[:reject_if]
54
- end
55
- end
56
-
57
91
  def type
58
- @type ||= NestedRecord::Type::HasMany.new(self)
59
- end
60
-
61
- def collection_class_name
62
- @collection_class_name ||= :"NestedRecord_Many#{@name.to_s.camelize}"
92
+ @type ||= NestedRecord::Type::Many.new(self)
63
93
  end
64
94
 
65
95
  def collection_class
66
- return @owner.const_get(collection_class_name, false) if @owner.const_defined?(collection_class_name, false)
67
- extension = @extension
68
- collection_superclass = record_class.collection_class
69
- @owner.const_set(
70
- collection_class_name,
71
- Class.new(collection_superclass) do
72
- @record_class = collection_superclass.record_class
73
- include Module.new(&extension) if extension
74
- end
75
- )
76
- end
77
-
78
- def reject?(attributes)
79
- @reject_if_proc&.call(attributes)
96
+ record_class.collection_class
80
97
  end
81
98
 
82
99
  private
@@ -85,76 +102,14 @@ class NestedRecord::Setup
85
102
  []
86
103
  end
87
104
 
88
- def define_writer_method
89
- setup = self
90
- @owner.define_method(writer_method_name) do |records|
91
- collection_class = setup.collection_class
92
- return super(records.dup) if records.is_a? collection_class
93
- collection = collection_class.new
94
- records.each do |obj|
95
- collection << obj
96
- end
97
- super(collection)
98
- end
99
- end
100
-
101
- def define_attributes_writer_method
102
- return unless @options.fetch(:attributes_writer) { true }
103
- setup = self
104
- writer_method = writer_method_name
105
- @owner.define_method(attributes_writer_method_name) do |data|
106
- attributes_collection =
107
- if data.is_a? Hash
108
- data.values
109
- else
110
- data
111
- end
112
- collection = setup.collection_class.new
113
- attributes_collection.each do |attributes|
114
- attributes = attributes.stringify_keys
115
- next if setup.reject?(attributes)
116
- collection.build(attributes)
117
- end
118
- public_send(writer_method, collection)
119
- end
120
- end
121
-
122
- def define_validation_method
123
- setup = self
124
- name = @name
125
- @owner.define_method(validation_method_name) do
126
- collection = public_send(name)
127
- collection.map do |record|
128
- next true if record.valid?
129
- record.errors.each do |attribute, message|
130
- error_attribute = "#{name}.#{attribute}"
131
- errors[error_attribute] << message
132
- errors[error_attribute].uniq!
133
- end
134
- record.errors.details.each_key do |attribute|
135
- error_attribute = "#{name}.#{attribute}"
136
- record.errors.details[attribute].each do |error|
137
- errors.details[error_attribute] << error
138
- errors.details[error_attribute].uniq!
139
- end
140
- end
141
- false
142
- end.all?
143
- end
105
+ def build_methods_extension
106
+ NestedRecord::Methods::Many.new(self)
144
107
  end
145
108
  end
146
109
 
147
110
  class HasOne < self
148
- def define_methods
149
- define_writer_method
150
- define_build_method
151
- define_attributes_writer_method
152
- define_validation_method
153
- define_bang_method
154
- end
155
-
156
111
  def type
157
- @type ||= NestedRecord::Type::HasOne.new(self)
112
+ @type ||= NestedRecord::Type::One.new(self)
158
113
  end
159
114
 
160
115
  private
@@ -163,68 +118,8 @@ class NestedRecord::Setup
163
118
  nil
164
119
  end
165
120
 
166
- def build_method_name
167
- :"build_#{@name}"
168
- end
169
-
170
- def bang_method_name
171
- :"#{@name}!"
172
- end
173
-
174
- def define_writer_method
175
- setup = self
176
- @owner.define_method(writer_method_name) do |record|
177
- unless record.nil? || record.kind_of?(setup.record_class)
178
- raise NestedRecord::TypeMismatchError, "#{record.inspect} should be a #{setup.record_class}"
179
- end
180
- super(record)
181
- end
182
- end
183
-
184
- def define_attributes_writer_method
185
- return unless @options.fetch(:attributes_writer) { true }
186
- @owner.alias_method attributes_writer_method_name, build_method_name
187
- end
188
-
189
- def define_validation_method
190
- setup = self
191
- name = @name
192
- @owner.define_method(validation_method_name) do
193
- record = public_send(name)
194
- return true unless record
195
- return true if record.valid?
196
-
197
- record.errors.each do |attribute, message|
198
- error_attribute = "#{name}.#{attribute}"
199
- errors.details[error_attribute] << message
200
- errors.details[error_attribute].uniq!
201
- end
202
- record.errors.details.each_key do |attribute|
203
- error_attribute = "#{name}.#{attribute}"
204
- record.errors.details[attribute].each do |error|
205
- errors.details[error_attribute] << error
206
- errors.details[error_attribute].uniq!
207
- end
208
- end
209
- false
210
- end
211
- end
212
-
213
- def define_build_method
214
- setup = self
215
- writer_method = writer_method_name
216
- @owner.define_method(build_method_name) do |attributes = {}|
217
- record = setup.record_class.new(attributes)
218
- public_send(writer_method, record)
219
- end
220
- end
221
-
222
- def define_bang_method
223
- @owner.class_eval <<~RUBY
224
- def #{bang_method_name}
225
- #{@name} || #{build_method_name}
226
- end
227
- RUBY
121
+ def build_methods_extension
122
+ NestedRecord::Methods::One.new(self)
228
123
  end
229
124
  end
230
125
  end