pakyow-reflection 1.0.0.rc1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/CHANGELOG.md +0 -0
- data/LICENSE +4 -0
- data/README.md +29 -0
- data/lib/pakyow/reflection/action.rb +25 -0
- data/lib/pakyow/reflection/attribute.rb +29 -0
- data/lib/pakyow/reflection/behavior/config.rb +33 -0
- data/lib/pakyow/reflection/behavior/reflecting.rb +45 -0
- data/lib/pakyow/reflection/behavior/rendering/install_form_metadata.rb +28 -0
- data/lib/pakyow/reflection/builders/abstract.rb +13 -0
- data/lib/pakyow/reflection/builders/actions.rb +80 -0
- data/lib/pakyow/reflection/builders/endpoints.rb +86 -0
- data/lib/pakyow/reflection/builders/helpers/controller.rb +234 -0
- data/lib/pakyow/reflection/builders/source.rb +51 -0
- data/lib/pakyow/reflection/endpoint.rb +74 -0
- data/lib/pakyow/reflection/extensions/controller.rb +302 -0
- data/lib/pakyow/reflection/framework.rb +70 -0
- data/lib/pakyow/reflection/mirror.rb +343 -0
- data/lib/pakyow/reflection/nested.rb +29 -0
- data/lib/pakyow/reflection/scope.rb +62 -0
- data/lib/pakyow/reflection.rb +12 -0
- metadata +133 -0
@@ -0,0 +1,74 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Pakyow
|
4
|
+
module Reflection
|
5
|
+
class Exposure
|
6
|
+
attr_reader :scope, :node, :binding, :dataset, :parent, :children
|
7
|
+
|
8
|
+
def initialize(scope:, node:, binding:, dataset: nil, parent: nil)
|
9
|
+
@scope = scope
|
10
|
+
@node = node
|
11
|
+
@binding = binding
|
12
|
+
@dataset = parse_dataset(dataset) if dataset
|
13
|
+
@parent = parent
|
14
|
+
@children = []
|
15
|
+
|
16
|
+
if parent
|
17
|
+
parent.children << self
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def parse_dataset(dataset)
|
24
|
+
options = {}
|
25
|
+
|
26
|
+
dataset.to_s.split(";").each do |dataset_part|
|
27
|
+
key, value = dataset_part.split(":", 2).map(&:to_s).map(&:strip)
|
28
|
+
|
29
|
+
value = if value.include?(",") || value.include?("(")
|
30
|
+
value.split(",").map { |value_part|
|
31
|
+
parse_value_part(value_part)
|
32
|
+
}
|
33
|
+
else
|
34
|
+
parse_value_part(value)
|
35
|
+
end
|
36
|
+
|
37
|
+
options[key.to_sym] = value
|
38
|
+
end
|
39
|
+
|
40
|
+
options
|
41
|
+
end
|
42
|
+
|
43
|
+
def parse_value_part(value_part)
|
44
|
+
value_part = value_part.strip
|
45
|
+
|
46
|
+
if value_part.include?("(")
|
47
|
+
value_part.split("(").map { |sub_value_part|
|
48
|
+
sub_value_part.strip.gsub(")", "")
|
49
|
+
}
|
50
|
+
else
|
51
|
+
value_part
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
class Endpoint
|
57
|
+
attr_reader :view_path, :options, :exposures
|
58
|
+
|
59
|
+
def initialize(view_path, options: {})
|
60
|
+
@view_path = view_path
|
61
|
+
@options = options || {}
|
62
|
+
@exposures = []
|
63
|
+
end
|
64
|
+
|
65
|
+
def type
|
66
|
+
@options[:type] || :member
|
67
|
+
end
|
68
|
+
|
69
|
+
def add_exposure(exposure)
|
70
|
+
@exposures << exposure
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,302 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "pakyow/support/core_refinements/array/ensurable"
|
4
|
+
|
5
|
+
require "pakyow/support/extension"
|
6
|
+
|
7
|
+
module Pakyow
|
8
|
+
module Reflection
|
9
|
+
module Extension
|
10
|
+
module Controller
|
11
|
+
extend Support::Extension
|
12
|
+
restrict_extension Controller
|
13
|
+
using Support::Refinements::Array::Ensurable
|
14
|
+
|
15
|
+
def with_reflected_action
|
16
|
+
if reflected_action
|
17
|
+
yield reflected_action
|
18
|
+
else
|
19
|
+
trigger 404
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def with_reflected_endpoint
|
24
|
+
if reflected_endpoint
|
25
|
+
yield reflected_endpoint
|
26
|
+
else
|
27
|
+
trigger 404
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def reflected_scope
|
32
|
+
connection.get(:__reflected_scope)
|
33
|
+
end
|
34
|
+
|
35
|
+
def reflected_action
|
36
|
+
connection.get(:__reflected_action)
|
37
|
+
end
|
38
|
+
|
39
|
+
def reflected_endpoint
|
40
|
+
connection.get(:__reflected_endpoint)
|
41
|
+
end
|
42
|
+
|
43
|
+
def reflects_specific_object?(object_name)
|
44
|
+
(
|
45
|
+
self.class.__object_name.name == object_name && (
|
46
|
+
connection.get(:__endpoint_name) == :show || connection.get(:__endpoint_name) == :edit
|
47
|
+
)
|
48
|
+
) || parent_resource_named?(object_name)
|
49
|
+
end
|
50
|
+
|
51
|
+
def reflective_expose
|
52
|
+
reflected_endpoint.exposures.each do |reflected_exposure|
|
53
|
+
if reflected_exposure.parent.nil? || reflected_exposure.binding.to_s.include?("form")
|
54
|
+
if dataset = reflected_exposure.dataset
|
55
|
+
query = data.send(reflected_exposure.scope.plural_name)
|
56
|
+
|
57
|
+
if dataset.include?(:limit)
|
58
|
+
query = query.limit(dataset[:limit].to_i)
|
59
|
+
end
|
60
|
+
|
61
|
+
if dataset.include?(:order)
|
62
|
+
query = query.order(*dataset[:order].to_a)
|
63
|
+
end
|
64
|
+
|
65
|
+
if dataset.include?(:query) && dataset[:query] != "all"
|
66
|
+
case dataset[:query]
|
67
|
+
when Array
|
68
|
+
dataset[:query].each do |key, value|
|
69
|
+
value = if respond_to?(value)
|
70
|
+
public_send(value)
|
71
|
+
else
|
72
|
+
value
|
73
|
+
end
|
74
|
+
|
75
|
+
query = query.public_send(key, value)
|
76
|
+
end
|
77
|
+
else
|
78
|
+
query = query.public_send(dataset[:query].to_s)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
else
|
82
|
+
query = data.send(reflected_exposure.scope.plural_name)
|
83
|
+
|
84
|
+
if reflects_specific_object?(reflected_exposure.scope.plural_name)
|
85
|
+
if resource = resource_with_name(reflected_exposure.scope.plural_name)
|
86
|
+
if resource == self.class
|
87
|
+
query_param = resource.param
|
88
|
+
params_param = resource.param
|
89
|
+
else
|
90
|
+
query_param = resource.param
|
91
|
+
params_param = resource.nested_param
|
92
|
+
end
|
93
|
+
|
94
|
+
query = query.send(:"by_#{query_param}", params[params_param])
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
if reflected_exposure.children.any?
|
100
|
+
associations = data.send(
|
101
|
+
reflected_exposure.scope.plural_name
|
102
|
+
).source.class.associations.values.flatten
|
103
|
+
|
104
|
+
reflected_exposure.children.each do |nested_reflected_exposure|
|
105
|
+
association = associations.find { |possible_association|
|
106
|
+
possible_association.associated_source_name == nested_reflected_exposure.scope.plural_name
|
107
|
+
}
|
108
|
+
|
109
|
+
if association
|
110
|
+
query = query.including(association.name)
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
if reflects_specific_object?(reflected_exposure.scope.plural_name) && query.dup.count == 0
|
116
|
+
trigger 404
|
117
|
+
else
|
118
|
+
if !reflected_exposure.binding.to_s.include?("form") || reflected_endpoint.view_path.end_with?("/edit")
|
119
|
+
expose reflected_exposure.binding, query
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
def reflective_create
|
127
|
+
logger.debug "[reflection] create"
|
128
|
+
handle_submitted_data
|
129
|
+
end
|
130
|
+
|
131
|
+
def reflective_update
|
132
|
+
logger.debug "[reflection] update"
|
133
|
+
handle_submitted_data
|
134
|
+
end
|
135
|
+
|
136
|
+
def reflective_delete
|
137
|
+
logger.debug "[reflection] delete"
|
138
|
+
handle_submitted_data
|
139
|
+
end
|
140
|
+
|
141
|
+
def verify_submitted_form
|
142
|
+
with_reflected_action do |reflected_action|
|
143
|
+
local = self
|
144
|
+
|
145
|
+
verify do
|
146
|
+
required reflected_action.scope.name do
|
147
|
+
reflected_action.attributes.each do |attribute|
|
148
|
+
if attribute.required?
|
149
|
+
required attribute.name do
|
150
|
+
validate :presence
|
151
|
+
end
|
152
|
+
else
|
153
|
+
optional attribute.name
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
local.__send__(:verify_nested_data, reflected_action.nested, self)
|
158
|
+
end
|
159
|
+
end
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
def handle_submitted_data
|
164
|
+
with_reflected_action do |reflected_action|
|
165
|
+
proxy = data.public_send(reflected_action.scope.plural_name)
|
166
|
+
|
167
|
+
# Pull initial values from the params.
|
168
|
+
#
|
169
|
+
values = params[reflected_action.scope.name]
|
170
|
+
|
171
|
+
# Associate the object with its parent when creating.
|
172
|
+
#
|
173
|
+
if self.class.parent && connection.get(:__endpoint_name) == :create
|
174
|
+
# TODO: Handle cases where objects are associated by id but routed by another field.
|
175
|
+
# Implement when we allow foreign keys to be specified in associations.
|
176
|
+
#
|
177
|
+
values[self.class.parent.nested_param] = params[self.class.parent.nested_param]
|
178
|
+
end
|
179
|
+
|
180
|
+
# Limit the action for update, delete.
|
181
|
+
#
|
182
|
+
if connection.get(:__endpoint_name) == :update || connection.get(:__endpoint_name) == :delete
|
183
|
+
proxy = proxy.public_send(:"by_#{proxy.source.class.primary_key_field}", params[self.class.param])
|
184
|
+
trigger 404 if proxy.count == 0
|
185
|
+
end
|
186
|
+
|
187
|
+
proxy.transaction do
|
188
|
+
unless connection.get(:__endpoint_name) == :delete
|
189
|
+
handle_nested_values_for_source(values, proxy.source.class)
|
190
|
+
end
|
191
|
+
|
192
|
+
@object = proxy.send(
|
193
|
+
connection.get(:__endpoint_name), values
|
194
|
+
)
|
195
|
+
end
|
196
|
+
end
|
197
|
+
rescue Data::ConstraintViolation
|
198
|
+
trigger 404
|
199
|
+
end
|
200
|
+
|
201
|
+
def redirect_to_reflected_destination
|
202
|
+
if destination = reflected_destination
|
203
|
+
redirect destination
|
204
|
+
end
|
205
|
+
end
|
206
|
+
|
207
|
+
def reflected_destination
|
208
|
+
with_reflected_action do |reflected_action|
|
209
|
+
if connection.form && origin = connection.form[:origin]
|
210
|
+
if instance_variable_defined?(:@object) && origin == "/#{reflected_action.scope.plural_name}/new"
|
211
|
+
if self.class.routes[:get].any? { |route| route.name == :show }
|
212
|
+
path(:"#{reflected_action.scope.plural_name}_show", @object.one.to_h)
|
213
|
+
elsif self.class.routes[:get].any? { |route| route.name == :list }
|
214
|
+
path(:"#{reflected_action.scope.plural_name}_list")
|
215
|
+
else
|
216
|
+
origin
|
217
|
+
end
|
218
|
+
else
|
219
|
+
origin
|
220
|
+
end
|
221
|
+
else
|
222
|
+
nil
|
223
|
+
end
|
224
|
+
end
|
225
|
+
end
|
226
|
+
|
227
|
+
private
|
228
|
+
|
229
|
+
def parent_resource_named?(object_name, context = self.class)
|
230
|
+
if context && context.parent
|
231
|
+
context.parent.__object_name&.name == object_name || parent_resource_named?(object_name, context.parent)
|
232
|
+
else
|
233
|
+
false
|
234
|
+
end
|
235
|
+
end
|
236
|
+
|
237
|
+
def resource_with_name(object_name, context = self.class)
|
238
|
+
if context.__object_name&.name == object_name
|
239
|
+
context
|
240
|
+
elsif context.parent
|
241
|
+
resource_with_name(object_name, context.parent)
|
242
|
+
end
|
243
|
+
end
|
244
|
+
|
245
|
+
def verify_nested_data(nested, context)
|
246
|
+
local = self
|
247
|
+
nested.each do |object|
|
248
|
+
context.required object.name do
|
249
|
+
optional local.data.public_send(object.plural_name).source.class.primary_key_field
|
250
|
+
|
251
|
+
object.attributes.each do |attribute|
|
252
|
+
if attribute.required?
|
253
|
+
required attribute.name
|
254
|
+
else
|
255
|
+
optional attribute.name
|
256
|
+
end
|
257
|
+
end
|
258
|
+
|
259
|
+
local.__send__(:verify_nested_data, object.nested, self)
|
260
|
+
end
|
261
|
+
end
|
262
|
+
end
|
263
|
+
|
264
|
+
def handle_nested_values_for_source(values, source)
|
265
|
+
source.associations.values_at(:has_one, :has_many).flatten.each do |association|
|
266
|
+
Array.ensure(values).each do |object|
|
267
|
+
if object.include?(association.name)
|
268
|
+
handle_nested_values_for_source(
|
269
|
+
object[association.name],
|
270
|
+
association.associated_source
|
271
|
+
)
|
272
|
+
|
273
|
+
if association.result_type == :many
|
274
|
+
object[association.name] = Array.ensure(object[association.name]).map { |related|
|
275
|
+
if related.include?(association.associated_source.primary_key_field)
|
276
|
+
data.public_send(association.associated_source_name).send(
|
277
|
+
:"by_#{association.associated_source.primary_key_field}",
|
278
|
+
related[association.associated_source.primary_key_field]
|
279
|
+
).update(related).one
|
280
|
+
else
|
281
|
+
data.public_send(association.associated_source_name).create(related).one
|
282
|
+
end
|
283
|
+
}
|
284
|
+
else
|
285
|
+
related = object[association.name]
|
286
|
+
object[association.name] = if related.include?(association.associated_source.primary_key_field)
|
287
|
+
data.public_send(association.associated_source_name).send(
|
288
|
+
:"by_#{association.associated_source.primary_key_field}",
|
289
|
+
related[association.associated_source.primary_key_field]
|
290
|
+
).update(related).one
|
291
|
+
else
|
292
|
+
data.public_send(association.associated_source_name).create(related).one
|
293
|
+
end
|
294
|
+
end
|
295
|
+
end
|
296
|
+
end
|
297
|
+
end
|
298
|
+
end
|
299
|
+
end
|
300
|
+
end
|
301
|
+
end
|
302
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "pakyow/framework"
|
4
|
+
require "pakyow/support/inflector"
|
5
|
+
|
6
|
+
require "pakyow/reflection/behavior/config"
|
7
|
+
require "pakyow/reflection/behavior/reflecting"
|
8
|
+
require "pakyow/reflection/behavior/rendering/install_form_metadata"
|
9
|
+
require "pakyow/reflection/mirror"
|
10
|
+
|
11
|
+
module Pakyow
|
12
|
+
module Reflection
|
13
|
+
class Framework < Pakyow::Framework(:reflection)
|
14
|
+
def boot
|
15
|
+
object.include Behavior::Config
|
16
|
+
object.include Behavior::Reflecting
|
17
|
+
|
18
|
+
object.isolated :Renderer do
|
19
|
+
include Behavior::Rendering::InstallFormMetadata
|
20
|
+
end
|
21
|
+
|
22
|
+
object.isolated :Controller do
|
23
|
+
def reflect(&block)
|
24
|
+
operations.reflect(controller: self, &block)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
object.after "load" do
|
29
|
+
operation :reflect do
|
30
|
+
action :verify do
|
31
|
+
if (reflected_action = controller.connection.get(:__reflected_action)) && reflected_action.name == controller.connection.get(:__endpoint_name)
|
32
|
+
case reflected_action.name
|
33
|
+
when :create, :update
|
34
|
+
controller.verify_submitted_form
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
action :perform do
|
40
|
+
if (reflected_action = controller.connection.get(:__reflected_action)) && reflected_action.name == controller.connection.get(:__endpoint_name)
|
41
|
+
case reflected_action.name
|
42
|
+
when :create
|
43
|
+
controller.reflective_create
|
44
|
+
when :update
|
45
|
+
controller.reflective_update
|
46
|
+
when :delete
|
47
|
+
controller.reflective_delete
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
action :expose do
|
53
|
+
if controller.connection.set?(:__reflected_endpoint)
|
54
|
+
controller.reflective_expose
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
action :redirect do
|
59
|
+
if (reflected_action = controller.connection.get(:__reflected_action)) && reflected_action.name == controller.connection.get(:__endpoint_name)
|
60
|
+
unless controller.connection.halted?
|
61
|
+
controller.redirect_to_reflected_destination
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|