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