linked_rails 0.0.1
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 +7 -0
- data/LICENSE +674 -0
- data/README.md +65 -0
- data/Rakefile +34 -0
- data/app/controllers/linked_rails/actions/items_controller.rb +9 -0
- data/app/controllers/linked_rails/bulk_controller.rb +195 -0
- data/app/controllers/linked_rails/current_user_controller.rb +13 -0
- data/app/controllers/linked_rails/enum_values_controller.rb +49 -0
- data/app/controllers/linked_rails/forms_controller.rb +13 -0
- data/app/controllers/linked_rails/manifests_controller.rb +21 -0
- data/app/controllers/linked_rails/menus/items_controller.rb +9 -0
- data/app/controllers/linked_rails/menus/lists_controller.rb +9 -0
- data/app/controllers/linked_rails/not_found_controller.rb +15 -0
- data/app/controllers/linked_rails/ontologies_controller.rb +7 -0
- data/app/models/linked_rails/actions/default_actions/create.rb +60 -0
- data/app/models/linked_rails/actions/default_actions/destroy.rb +45 -0
- data/app/models/linked_rails/actions/default_actions/update.rb +50 -0
- data/app/models/linked_rails/actions/default_actions.rb +17 -0
- data/app/models/linked_rails/actions/item.rb +234 -0
- data/app/models/linked_rails/actions/list.rb +113 -0
- data/app/models/linked_rails/collection/filter.rb +16 -0
- data/app/models/linked_rails/collection/filter_field.rb +30 -0
- data/app/models/linked_rails/collection/filter_option.rb +17 -0
- data/app/models/linked_rails/collection/filterable.rb +92 -0
- data/app/models/linked_rails/collection/infinite_view.rb +98 -0
- data/app/models/linked_rails/collection/iri.rb +74 -0
- data/app/models/linked_rails/collection/iri_mapping.rb +33 -0
- data/app/models/linked_rails/collection/paginated_view.rb +41 -0
- data/app/models/linked_rails/collection/sortable.rb +60 -0
- data/app/models/linked_rails/collection/sorting.rb +72 -0
- data/app/models/linked_rails/collection/view.rb +101 -0
- data/app/models/linked_rails/collection.rb +220 -0
- data/app/models/linked_rails/condition.rb +7 -0
- data/app/models/linked_rails/creative_work.rb +21 -0
- data/app/models/linked_rails/current_user.rb +28 -0
- data/app/models/linked_rails/entry_point.rb +53 -0
- data/app/models/linked_rails/enum_value.rb +33 -0
- data/app/models/linked_rails/form/field/association_input.rb +23 -0
- data/app/models/linked_rails/form/field/checkbox_group.rb +10 -0
- data/app/models/linked_rails/form/field/checkbox_input.rb +10 -0
- data/app/models/linked_rails/form/field/color_input.rb +17 -0
- data/app/models/linked_rails/form/field/date_input.rb +10 -0
- data/app/models/linked_rails/form/field/date_time_input.rb +10 -0
- data/app/models/linked_rails/form/field/email_input.rb +10 -0
- data/app/models/linked_rails/form/field/file_input.rb +10 -0
- data/app/models/linked_rails/form/field/location_input.rb +11 -0
- data/app/models/linked_rails/form/field/markdown_input.rb +10 -0
- data/app/models/linked_rails/form/field/number_input.rb +10 -0
- data/app/models/linked_rails/form/field/password_input.rb +10 -0
- data/app/models/linked_rails/form/field/postal_range_input.rb +10 -0
- data/app/models/linked_rails/form/field/radio_group.rb +10 -0
- data/app/models/linked_rails/form/field/resource_field.rb +23 -0
- data/app/models/linked_rails/form/field/select_input.rb +11 -0
- data/app/models/linked_rails/form/field/slider_input.rb +10 -0
- data/app/models/linked_rails/form/field/text_area_input.rb +10 -0
- data/app/models/linked_rails/form/field/text_input.rb +10 -0
- data/app/models/linked_rails/form/field/toggle_button_group.rb +10 -0
- data/app/models/linked_rails/form/field.rb +117 -0
- data/app/models/linked_rails/form/field_factory.rb +219 -0
- data/app/models/linked_rails/form/group.rb +39 -0
- data/app/models/linked_rails/form/page.rb +31 -0
- data/app/models/linked_rails/form.rb +156 -0
- data/app/models/linked_rails/manifest.rb +102 -0
- data/app/models/linked_rails/media_object.rb +31 -0
- data/app/models/linked_rails/menus/item.rb +92 -0
- data/app/models/linked_rails/menus/list.rb +138 -0
- data/app/models/linked_rails/ontology/base.rb +50 -0
- data/app/models/linked_rails/ontology/class.rb +43 -0
- data/app/models/linked_rails/ontology/property.rb +19 -0
- data/app/models/linked_rails/ontology.rb +34 -0
- data/app/models/linked_rails/property_query.rb +11 -0
- data/app/models/linked_rails/resource.rb +17 -0
- data/app/models/linked_rails/sequence.rb +64 -0
- data/app/models/linked_rails/shacl/node_shape.rb +21 -0
- data/app/models/linked_rails/shacl/property_shape.rb +53 -0
- data/app/models/linked_rails/shacl/shape.rb +33 -0
- data/app/models/linked_rails/web_page.rb +22 -0
- data/app/models/linked_rails/web_site.rb +17 -0
- data/app/models/linked_rails/widget.rb +55 -0
- data/app/policies/linked_rails/actions/item_policy.rb +11 -0
- data/app/policies/linked_rails/actions/list_policy.rb +11 -0
- data/app/policies/linked_rails/collection/view_policy.rb +13 -0
- data/app/policies/linked_rails/collection_policy.rb +41 -0
- data/app/policies/linked_rails/enum_value_policy.rb +32 -0
- data/app/policies/linked_rails/menus/item_policy.rb +11 -0
- data/app/policies/linked_rails/menus/list_policy.rb +11 -0
- data/app/policies/linked_rails/sequence_policy.rb +9 -0
- data/app/serializers/linked_rails/actions/item_serializer.rb +28 -0
- data/app/serializers/linked_rails/collection/filter_field_serializer.rb +12 -0
- data/app/serializers/linked_rails/collection/filter_option_serializer.rb +12 -0
- data/app/serializers/linked_rails/collection/filter_serializer.rb +13 -0
- data/app/serializers/linked_rails/collection/sorting_serializer.rb +13 -0
- data/app/serializers/linked_rails/collection/view_serializer.rb +22 -0
- data/app/serializers/linked_rails/collection_serializer.rb +44 -0
- data/app/serializers/linked_rails/condition_serializer.rb +9 -0
- data/app/serializers/linked_rails/creative_work_serializer.rb +10 -0
- data/app/serializers/linked_rails/current_user_serializer.rb +7 -0
- data/app/serializers/linked_rails/entry_point_serializer.rb +20 -0
- data/app/serializers/linked_rails/enum_value_serializer.rb +12 -0
- data/app/serializers/linked_rails/form/field/association_input_serializer.rb +13 -0
- data/app/serializers/linked_rails/form/field/resource_field_serializer.rb +11 -0
- data/app/serializers/linked_rails/form/field/select_input_serializer.rb +11 -0
- data/app/serializers/linked_rails/form/field_serializer.rb +39 -0
- data/app/serializers/linked_rails/form/group_serializer.rb +14 -0
- data/app/serializers/linked_rails/form/page_serializer.rb +13 -0
- data/app/serializers/linked_rails/form_serializer.rb +9 -0
- data/app/serializers/linked_rails/media_object_serializer.rb +17 -0
- data/app/serializers/linked_rails/menus/item_serializer.rb +35 -0
- data/app/serializers/linked_rails/menus/list_serializer.rb +13 -0
- data/app/serializers/linked_rails/ontology/class_serializer.rb +19 -0
- data/app/serializers/linked_rails/ontology/property_serializer.rb +16 -0
- data/app/serializers/linked_rails/ontology_serializer.rb +8 -0
- data/app/serializers/linked_rails/rdf_error_serializer.rb +8 -0
- data/app/serializers/linked_rails/sequence_serializer.rb +14 -0
- data/app/serializers/linked_rails/shacl/node_shape_serializer.rb +12 -0
- data/app/serializers/linked_rails/shacl/property_shape_serializer.rb +38 -0
- data/app/serializers/linked_rails/shacl/shape_serializer.rb +25 -0
- data/app/serializers/linked_rails/web_page_serializer.rb +10 -0
- data/app/serializers/linked_rails/web_site_serializer.rb +11 -0
- data/app/serializers/linked_rails/widget_serializer.rb +15 -0
- data/config/initializers/inflections.rb +5 -0
- data/lib/generators/linked_rails/install/install_generator.rb +65 -0
- data/lib/generators/linked_rails/install/templates/app_menu_list.rb +41 -0
- data/lib/generators/linked_rails/install/templates/application_action_list.rb +3 -0
- data/lib/generators/linked_rails/install/templates/application_form.rb +3 -0
- data/lib/generators/linked_rails/install/templates/application_menu_list.rb +3 -0
- data/lib/generators/linked_rails/install/templates/application_policy.rb +18 -0
- data/lib/generators/linked_rails/install/templates/application_serializer.rb +5 -0
- data/lib/generators/linked_rails/install/templates/initializer.rb +9 -0
- data/lib/generators/linked_rails/install/templates/locales.yml +12 -0
- data/lib/generators/linked_rails/install/templates/rdf_responder.rb +5 -0
- data/lib/generators/linked_rails/install/templates/rdf_serializers_initializer.rb +5 -0
- data/lib/generators/linked_rails/install/templates/vocab.rb +5 -0
- data/lib/generators/linked_rails/install/templates/vocab.yml +26 -0
- data/lib/generators/linked_rails/model/model_generator.rb +58 -0
- data/lib/generators/linked_rails/model/templates/action_list.rb.tt +6 -0
- data/lib/generators/linked_rails/model/templates/controller.rb.tt +7 -0
- data/lib/generators/linked_rails/model/templates/create_table_migration.rb.tt +24 -0
- data/lib/generators/linked_rails/model/templates/form.rb.tt +6 -0
- data/lib/generators/linked_rails/model/templates/menu_list.rb.tt +6 -0
- data/lib/generators/linked_rails/model/templates/model.rb.tt +15 -0
- data/lib/generators/linked_rails/model/templates/module.rb.tt +7 -0
- data/lib/generators/linked_rails/model/templates/policy.rb.tt +6 -0
- data/lib/generators/linked_rails/model/templates/serializer.rb.tt +9 -0
- data/lib/linked_rails/active_response/controller/collections.rb +43 -0
- data/lib/linked_rails/active_response/controller/crud_defaults.rb +92 -0
- data/lib/linked_rails/active_response/controller/params.rb +51 -0
- data/lib/linked_rails/active_response/controller.rb +37 -0
- data/lib/linked_rails/active_response/responders/rdf.rb +158 -0
- data/lib/linked_rails/cache.rb +35 -0
- data/lib/linked_rails/callable_variable.rb +25 -0
- data/lib/linked_rails/constraints/whitelist.rb +36 -0
- data/lib/linked_rails/controller/authorization.rb +15 -0
- data/lib/linked_rails/controller/error_handling.rb +76 -0
- data/lib/linked_rails/controller.rb +43 -0
- data/lib/linked_rails/engine.rb +7 -0
- data/lib/linked_rails/enhanceable.rb +21 -0
- data/lib/linked_rails/enhancements/actionable/model.rb +71 -0
- data/lib/linked_rails/enhancements/actionable/serializer.rb +25 -0
- data/lib/linked_rails/enhancements/creatable/action.rb +15 -0
- data/lib/linked_rails/enhancements/creatable/controller.rb +15 -0
- data/lib/linked_rails/enhancements/destroyable/action.rb +15 -0
- data/lib/linked_rails/enhancements/destroyable/controller.rb +15 -0
- data/lib/linked_rails/enhancements/destroyable/routing.rb +19 -0
- data/lib/linked_rails/enhancements/indexable/model.rb +10 -0
- data/lib/linked_rails/enhancements/menuable/model.rb +36 -0
- data/lib/linked_rails/enhancements/menuable/serializer.rb +33 -0
- data/lib/linked_rails/enhancements/route_concerns.rb +56 -0
- data/lib/linked_rails/enhancements/singularable/controller.rb +43 -0
- data/lib/linked_rails/enhancements/singularable/model.rb +47 -0
- data/lib/linked_rails/enhancements/singularable/serializer.rb +28 -0
- data/lib/linked_rails/enhancements/tableable/model.rb +28 -0
- data/lib/linked_rails/enhancements/updatable/action.rb +15 -0
- data/lib/linked_rails/enhancements/updatable/controller.rb +15 -0
- data/lib/linked_rails/enhancements/updatable/routing.rb +20 -0
- data/lib/linked_rails/enhancements/updatable/serializer.rb +17 -0
- data/lib/linked_rails/enhancements.rb +22 -0
- data/lib/linked_rails/helpers/delta_helper.rb +86 -0
- data/lib/linked_rails/helpers/ontola_actions_helper.rb +32 -0
- data/lib/linked_rails/helpers/resource_helper.rb +70 -0
- data/lib/linked_rails/iri_mapper.rb +125 -0
- data/lib/linked_rails/middleware/linked_data_params.rb +224 -0
- data/lib/linked_rails/model/collections.rb +82 -0
- data/lib/linked_rails/model/dirty.rb +82 -0
- data/lib/linked_rails/model/enhancements.rb +61 -0
- data/lib/linked_rails/model/filtering.rb +89 -0
- data/lib/linked_rails/model/indexable.rb +51 -0
- data/lib/linked_rails/model/iri.rb +121 -0
- data/lib/linked_rails/model/iri_mapping.rb +69 -0
- data/lib/linked_rails/model/serialization.rb +88 -0
- data/lib/linked_rails/model/sorting.rb +20 -0
- data/lib/linked_rails/model.rb +68 -0
- data/lib/linked_rails/params_parser.rb +93 -0
- data/lib/linked_rails/policy/attribute_conditions.rb +53 -0
- data/lib/linked_rails/policy.rb +189 -0
- data/lib/linked_rails/rdf_error.rb +36 -0
- data/lib/linked_rails/renderers.rb +46 -0
- data/lib/linked_rails/routes.rb +108 -0
- data/lib/linked_rails/serializer.rb +137 -0
- data/lib/linked_rails/translate.rb +176 -0
- data/lib/linked_rails/version.rb +5 -0
- data/lib/linked_rails/vocab.rb +81 -0
- data/lib/linked_rails.rb +86 -0
- data/lib/nill_class_renderer.rb +3 -0
- data/lib/rails/welcome_controller.rb +45 -0
- data/lib/tasks/linked_rails_tasks.rake +6 -0
- metadata +416 -0
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module LinkedRails
|
|
4
|
+
module Helpers
|
|
5
|
+
module OntolaActionsHelper
|
|
6
|
+
def add_exec_action_header(headers, action)
|
|
7
|
+
headers['Exec-Action'] ||= ''
|
|
8
|
+
headers['Exec-Action'] += "#{action}\n"
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def ontola_copy_action(value)
|
|
12
|
+
Vocab.libro["actions/copyToClipboard?#{{value: value}.to_param}"]
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def ontola_dialog_action(resource, opener: nil)
|
|
16
|
+
Vocab.libro["actions/dialog/alert?#{{resource: resource, opener: opener}.compact.to_param}"]
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def ontola_dialog_close_action
|
|
20
|
+
Vocab.libro['actions/dialog/close']
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def ontola_redirect_action(location, reload: nil)
|
|
24
|
+
Vocab.libro["actions/redirect?#{{location: location, reload: reload}.compact.to_param}"]
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def ontola_snackbar_action(text)
|
|
28
|
+
Vocab.libro["actions/snackbar?#{{text: text}.to_param}"]
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module LinkedRails
|
|
4
|
+
module Helpers
|
|
5
|
+
module ResourceHelper
|
|
6
|
+
def current_resource
|
|
7
|
+
return @current_resource if instance_variable_defined?(:@current_resource)
|
|
8
|
+
|
|
9
|
+
@current_resource ||= resolve_current_resource
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def new_resource
|
|
13
|
+
@new_resource ||=
|
|
14
|
+
if requested_resource.try(:singular_resource?)
|
|
15
|
+
requested_resource
|
|
16
|
+
elsif parent_resource
|
|
17
|
+
new_resource_from_parent
|
|
18
|
+
else
|
|
19
|
+
build_new_resource
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def params_for_parent
|
|
24
|
+
params.dup
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def parent_from_params
|
|
28
|
+
@parent_from_params ||= LinkedRails.iri_mapper.parent_from_params(params, user_context)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def parent_from_params!
|
|
32
|
+
parent_from_params || raise(ActiveRecord::RecordNotFound)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def parent_resource
|
|
36
|
+
@parent_resource ||= requested_resource_parent || parent_from_params
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def parent_resource!
|
|
40
|
+
parent_resource || raise(ActiveRecord::RecordNotFound)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def requested_resource_parent
|
|
44
|
+
requested_resource&.parent
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
private
|
|
48
|
+
|
|
49
|
+
def build_new_resource
|
|
50
|
+
controller_class.build_new(user_context: user_context)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def new_resource_from_parent
|
|
54
|
+
parent_resource.build_child(
|
|
55
|
+
controller_class,
|
|
56
|
+
user_context: user_context
|
|
57
|
+
)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def resolve_current_resource
|
|
61
|
+
case action_name
|
|
62
|
+
when 'create', 'new'
|
|
63
|
+
new_resource
|
|
64
|
+
else
|
|
65
|
+
requested_resource
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module LinkedRails
|
|
4
|
+
class IRIMapper
|
|
5
|
+
class << self
|
|
6
|
+
EMPTY_IRI_OPTS = {params: {}}.freeze
|
|
7
|
+
|
|
8
|
+
# Converts an IRI into a hash containing the action, class, iri and the params of the iri
|
|
9
|
+
# @return [Hash]
|
|
10
|
+
# @example Valid resource IRI
|
|
11
|
+
# opts_from_iri('https://example.com/resource/1?foo=bar')
|
|
12
|
+
# => {
|
|
13
|
+
# action: 'show',
|
|
14
|
+
# class: Resource,
|
|
15
|
+
# params: {
|
|
16
|
+
# id: '1',
|
|
17
|
+
# foo: 'bar'
|
|
18
|
+
# }
|
|
19
|
+
# }
|
|
20
|
+
# @example Valid collection IRI
|
|
21
|
+
# opts_from_iri('https://example.com/resources?foo=bar')
|
|
22
|
+
# => {
|
|
23
|
+
# action: 'index',
|
|
24
|
+
# class: Resource,
|
|
25
|
+
# params: {
|
|
26
|
+
# foo: 'bar'
|
|
27
|
+
# }
|
|
28
|
+
# }
|
|
29
|
+
# @example Invalid IRI
|
|
30
|
+
# opts_from_iri('https://example.com/invalid/1')
|
|
31
|
+
# => {
|
|
32
|
+
# params: {}
|
|
33
|
+
# }
|
|
34
|
+
def opts_from_iri(iri, method: 'GET')
|
|
35
|
+
query = Rack::Utils.parse_nested_query(URI(iri.to_s).query)
|
|
36
|
+
params = Rails.application.routes.recognize_path(iri.to_s, method: method)
|
|
37
|
+
|
|
38
|
+
route_params_to_opts(params.merge(query))
|
|
39
|
+
rescue ActionController::RoutingError
|
|
40
|
+
EMPTY_IRI_OPTS.dup
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def index_resource_from_iri(iri, user_context)
|
|
44
|
+
ensure_absolute_iri!(iri)
|
|
45
|
+
|
|
46
|
+
opts = opts_from_iri(iri)
|
|
47
|
+
index_resource_from_opts(opts, user_context)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def index_resource_from_iri!(iri, user_context)
|
|
51
|
+
index_resource_from_iri(iri, user_context) || raise(ActiveRecord::RecordNotFound)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def index_resource_from_opts(opts, user_context)
|
|
55
|
+
opts[:class]&.requested_index_resource(opts[:params], user_context) if opts[:action] == 'index'
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def parent_from_params(params, user_context)
|
|
59
|
+
return unless params.key?(:parent_iri)
|
|
60
|
+
|
|
61
|
+
parent_iri = "/#{params[:parent_iri]}"
|
|
62
|
+
opts = LinkedRails.iri_mapper.opts_from_iri(parent_iri)
|
|
63
|
+
opts[:params] = params.except(:parent_iri, :singular_route).merge(opts[:params])
|
|
64
|
+
|
|
65
|
+
LinkedRails.iri_mapper.resource_from_opts(opts, user_context)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def resource_from_iri(iri, user_context)
|
|
69
|
+
ensure_absolute_iri!(iri)
|
|
70
|
+
|
|
71
|
+
opts = opts_from_iri(iri)
|
|
72
|
+
resource_from_opts(opts, user_context)
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def resource_from_iri!(iri, user_context)
|
|
76
|
+
resource_from_iri(iri, user_context) || raise(ActiveRecord::RecordNotFound)
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def resource_from_opts(opts, user_context)
|
|
80
|
+
opts[:class]&.requested_resource(opts, user_context)
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def route_params_to_opts(params)
|
|
84
|
+
controller_class = class_for_controller(params[:controller])
|
|
85
|
+
|
|
86
|
+
return EMPTY_IRI_OPTS.dup if controller_class.blank?
|
|
87
|
+
|
|
88
|
+
{
|
|
89
|
+
action: params[:action],
|
|
90
|
+
class: controller_class,
|
|
91
|
+
params: params.except(:action, :controller)
|
|
92
|
+
}.with_indifferent_access
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def single_resource_from_iri(iri, user_context)
|
|
96
|
+
ensure_absolute_iri!(iri)
|
|
97
|
+
|
|
98
|
+
opts = opts_from_iri(iri)
|
|
99
|
+
single_resource_from_opts(opts, user_context)
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def single_resource_from_iri!(iri, user_context)
|
|
103
|
+
single_resource_from_iri(iri, user_context) || raise(ActiveRecord::RecordNotFound)
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def single_resource_from_opts(opts, user_context)
|
|
107
|
+
opts[:class]&.requested_single_resource(opts[:params], user_context) unless opts[:params] == 'index'
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
private
|
|
111
|
+
|
|
112
|
+
def class_for_controller(controller)
|
|
113
|
+
return if controller.blank?
|
|
114
|
+
|
|
115
|
+
"::#{controller.camelize}Controller"
|
|
116
|
+
.safe_constantize
|
|
117
|
+
.try(:controller_class)
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def ensure_absolute_iri!(iri)
|
|
121
|
+
raise("An absolute url is expected. #{iri} is given.") if iri.blank? || URI(iri).relative?
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
end
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module LinkedRails
|
|
4
|
+
module Middleware
|
|
5
|
+
class LinkedDataParams # rubocop:disable Metrics/ClassLength
|
|
6
|
+
def initialize(app)
|
|
7
|
+
@app = app
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def call(env)
|
|
11
|
+
req = Rack::Request.new(env)
|
|
12
|
+
params_from_query(req)
|
|
13
|
+
params_from_graph(req)
|
|
14
|
+
|
|
15
|
+
@app.call(env)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
private
|
|
19
|
+
|
|
20
|
+
def add_param(hash, key, value) # rubocop:disable Metrics/MethodLength
|
|
21
|
+
case hash[key]
|
|
22
|
+
when nil
|
|
23
|
+
hash[key] = value
|
|
24
|
+
when Hash
|
|
25
|
+
hash[key].merge!(value)
|
|
26
|
+
when Array
|
|
27
|
+
hash[key].append(value)
|
|
28
|
+
else
|
|
29
|
+
hash[key] = [hash[key], value]
|
|
30
|
+
end
|
|
31
|
+
hash
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def associated_class_from_params(reflection, graph, subject)
|
|
35
|
+
return reflection.klass unless reflection.polymorphic?
|
|
36
|
+
|
|
37
|
+
query = graph.query(subject: subject, predicate: Vocab.rdfv[:type])
|
|
38
|
+
if query.empty?
|
|
39
|
+
raise("No type given for '#{subject}' referenced by polymorphic association '#{reflection.name}'")
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
iri_to_class(query.first.object)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def blob_attribute(base_params, value)
|
|
46
|
+
base_params["<#{value}>"] if value.starts_with?(Vocab.ll['blobs/'])
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def convert_query_params(request, target_class)
|
|
50
|
+
keys = target_class.predicate_mapping.keys.map(&:to_s)
|
|
51
|
+
class_key = target_class.to_s.underscore
|
|
52
|
+
data = request.params[class_key] || {}
|
|
53
|
+
request.params.slice(*keys.map(&:to_s)).each do |key, value|
|
|
54
|
+
add_param_from_query(data, target_class, key, value)
|
|
55
|
+
end
|
|
56
|
+
request.update_param(class_key, data) if data.present?
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def add_param_from_query(data, target_class, key, value)
|
|
60
|
+
data[target_class.predicate_mapping[RDF::URI(key)].key] = value
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def enum_attribute(klass, key, value)
|
|
64
|
+
opts = RDF::Serializers.serializer_for(klass).try(:enum_options, key)
|
|
65
|
+
return if opts.blank?
|
|
66
|
+
|
|
67
|
+
opts.detect { |_k, options| options.iri == value }&.first&.to_s
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def graph_from_request(request)
|
|
71
|
+
request_graph = request.delete_param("<#{Vocab.ll[:graph].value}>")
|
|
72
|
+
return if request_graph.blank?
|
|
73
|
+
|
|
74
|
+
RDF::Graph.load(
|
|
75
|
+
request_graph[:tempfile].path,
|
|
76
|
+
content_type: request_graph[:type],
|
|
77
|
+
canonicalize: true,
|
|
78
|
+
intern: false
|
|
79
|
+
)
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def iri_to_class(iri)
|
|
83
|
+
iri.to_s.split(LinkedRails.app_vocab.to_s).pop&.classify&.safe_constantize ||
|
|
84
|
+
ApplicationRecord.descendants.detect { |klass| klass.iri == iri }
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def logger
|
|
88
|
+
Rails.logger
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def nested_attributes(base_params, graph, subject, klass, association, collection) # rubocop:disable Metrics/ParameterLists
|
|
92
|
+
nested_resources =
|
|
93
|
+
if graph.query([subject, Vocab.rdfv[:first], nil]).present?
|
|
94
|
+
nested_attributes_from_list(base_params, graph, subject, klass)
|
|
95
|
+
else
|
|
96
|
+
parsed = parse_nested_resource(base_params, graph, subject, klass)
|
|
97
|
+
collection ? {rand(1_000_000_000).to_s => parsed} : parsed
|
|
98
|
+
end
|
|
99
|
+
["#{association}_attributes", nested_resources]
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def nested_attributes_from_list(base_params, graph, subject, klass)
|
|
103
|
+
Hash[
|
|
104
|
+
RDF::List.new(subject: subject, graph: graph)
|
|
105
|
+
.map { |nested| [rand(1_000_000_000).to_s, parse_nested_resource(base_params, graph, nested, klass)] }
|
|
106
|
+
]
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
# Converts a serialized graph from a multipart request body to a nested
|
|
110
|
+
# attributes hash.
|
|
111
|
+
#
|
|
112
|
+
# The graph sent to the server should be sent under the `ll:graph` form name.
|
|
113
|
+
# The entrypoint for the graph is the `ll:targetResource` subject, which is
|
|
114
|
+
# assumed to be the resource intended to be targeted by the request (i.e. the
|
|
115
|
+
# resource to be created, updated, or deleted).
|
|
116
|
+
#
|
|
117
|
+
# @return [Hash] A hash of attributes, empty if no statements were given.
|
|
118
|
+
def params_from_graph(request)
|
|
119
|
+
graph = graph_from_request(request)
|
|
120
|
+
|
|
121
|
+
return unless graph
|
|
122
|
+
|
|
123
|
+
request.update_param(:body_graph, graph)
|
|
124
|
+
target_class = target_class_from_path(request)
|
|
125
|
+
return if target_class.blank?
|
|
126
|
+
|
|
127
|
+
update_actor_param(request, graph)
|
|
128
|
+
update_target_params(request, graph, target_class)
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def params_from_query(request)
|
|
132
|
+
target_class = target_class_from_path(request) if request.params.present?
|
|
133
|
+
return unless target_class.try(:predicate_mapping)
|
|
134
|
+
|
|
135
|
+
convert_query_params(request, target_class)
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
def parse_nested_resource(base_params, graph, subject, klass)
|
|
139
|
+
resource = parse_resource(base_params, graph, subject, klass)
|
|
140
|
+
resource[:id] ||= LinkedRails.iri_mapper.opts_from_iri(subject)[:params][:id] if subject.iri?
|
|
141
|
+
resource
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
# Recursively parses a resource from graph
|
|
145
|
+
def parse_resource(base_params, graph, subject, klass)
|
|
146
|
+
graph
|
|
147
|
+
.query([subject])
|
|
148
|
+
.map { |statement| parse_statement(base_params, graph, statement, klass) }
|
|
149
|
+
.compact
|
|
150
|
+
.reduce({}) { |h, (k, v)| add_param(h, k, v) }
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
def parse_statement(base_params, graph, statement, klass)
|
|
154
|
+
field = serializer_field(klass, statement.predicate)
|
|
155
|
+
if field.is_a?(FastJsonapi::Attribute)
|
|
156
|
+
parsed_attribute(base_params, klass, field.key, statement.object.value)
|
|
157
|
+
elsif field.is_a?(FastJsonapi::Relationship)
|
|
158
|
+
parsed_association(base_params, graph, statement.object, klass, field.association || field.key)
|
|
159
|
+
end
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
def parsed_association(base_params, graph, object, klass, association)
|
|
163
|
+
reflection = klass.reflect_on_association(association) || raise("#{association} not found for #{klass}")
|
|
164
|
+
|
|
165
|
+
if graph.has_subject?(object)
|
|
166
|
+
association_klass = associated_class_from_params(reflection, graph, object)
|
|
167
|
+
nested_attributes(base_params, graph, object, association_klass, association, reflection.collection?)
|
|
168
|
+
elsif object.iri?
|
|
169
|
+
attributes_from_iri(object, association, reflection)
|
|
170
|
+
end
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
def attributes_from_iri(object, association, reflection)
|
|
174
|
+
if reflection.options[:through]
|
|
175
|
+
key = reflection.has_one? ? "#{association}_id" : "#{association.to_s.singularize}_ids"
|
|
176
|
+
elsif reflection.belongs_to?
|
|
177
|
+
key = reflection.foreign_key
|
|
178
|
+
end
|
|
179
|
+
return unless key
|
|
180
|
+
|
|
181
|
+
resource = LinkedRails.iri_mapper.resource_from_iri(object, nil)
|
|
182
|
+
value = resource&.send(reflection.association_primary_key)
|
|
183
|
+
|
|
184
|
+
[key, value] if value
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
def parsed_attribute(base_params, klass, key, value)
|
|
188
|
+
[key, blob_attribute(base_params, value) || enum_attribute(klass, key, value) || value]
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
def serializer_field(klass, predicate)
|
|
192
|
+
field = klass.try(:predicate_mapping).try(:[], predicate)
|
|
193
|
+
logger.info("#{predicate} not found for #{klass || 'nil'}") if field.blank?
|
|
194
|
+
field
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
def target_class_from_path(request) # rubocop:disable Metrics/AbcSize
|
|
198
|
+
opts = LinkedRails.iri_mapper.opts_from_iri(
|
|
199
|
+
request.base_url + request.env['REQUEST_URI'],
|
|
200
|
+
method: request.request_method
|
|
201
|
+
)
|
|
202
|
+
|
|
203
|
+
logger.info("No class found for #{request.base_url + request.env['REQUEST_URI']}") unless opts[:class]
|
|
204
|
+
|
|
205
|
+
opts[:class]
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
def update_actor_param(request, graph)
|
|
209
|
+
actor = graph.query([Vocab.ll[:targetResource], Vocab.schema.creator]).first
|
|
210
|
+
return if actor.blank?
|
|
211
|
+
|
|
212
|
+
request.update_param(:actor_iri, actor.object)
|
|
213
|
+
graph.delete(actor)
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
def update_target_params(request, graph, target_class)
|
|
217
|
+
key = target_class.to_s.demodulize.underscore
|
|
218
|
+
from_body = parse_resource(request.params, graph, Vocab.ll[:targetResource], target_class)
|
|
219
|
+
|
|
220
|
+
request.update_param(key, from_body.merge(request.params[key] || {}))
|
|
221
|
+
end
|
|
222
|
+
end
|
|
223
|
+
end
|
|
224
|
+
end
|