linked_rails 0.0.3 → 0.0.4.pre.ge1306665a

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.
Files changed (173) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE +21 -674
  3. data/app/controllers/linked_rails/actions/objects_controller.rb +9 -0
  4. data/app/controllers/linked_rails/bulk_controller.rb +71 -21
  5. data/app/controllers/linked_rails/enum_values_controller.rb +0 -42
  6. data/app/models/linked_rails/actions/item.rb +64 -55
  7. data/app/models/linked_rails/actions/list.rb +6 -31
  8. data/app/models/linked_rails/actions/object.rb +38 -0
  9. data/app/models/linked_rails/collection/configuration.rb +55 -0
  10. data/app/models/linked_rails/collection/filter.rb +1 -1
  11. data/app/models/linked_rails/collection/filter_field.rb +19 -2
  12. data/app/models/linked_rails/collection/filter_option.rb +1 -1
  13. data/app/models/linked_rails/collection/filterable.rb +6 -9
  14. data/app/models/linked_rails/collection/infinite.rb +113 -0
  15. data/app/models/linked_rails/collection/infinite_view.rb +1 -90
  16. data/app/models/linked_rails/collection/iri.rb +47 -43
  17. data/app/models/linked_rails/collection/iri_mapping.rb +15 -7
  18. data/app/models/linked_rails/collection/paginated.rb +46 -0
  19. data/app/models/linked_rails/collection/paginated_view.rb +1 -33
  20. data/app/models/linked_rails/collection/sortable.rb +1 -9
  21. data/app/models/linked_rails/collection/sorting.rb +1 -1
  22. data/app/models/linked_rails/collection/view.rb +51 -14
  23. data/app/models/linked_rails/collection.rb +53 -85
  24. data/app/models/linked_rails/creative_work.rb +1 -1
  25. data/app/models/linked_rails/entry_point.rb +8 -5
  26. data/app/models/linked_rails/enum_value.rb +40 -2
  27. data/app/models/linked_rails/form/field/association_input.rb +7 -1
  28. data/app/models/linked_rails/form/field/file_input.rb +1 -0
  29. data/app/models/linked_rails/form/field/resource_field.rb +2 -0
  30. data/{lib/linked_rails/enhancements/indexable/model.rb → app/models/linked_rails/form/field/url_input.rb} +3 -3
  31. data/app/models/linked_rails/form/field.rb +37 -13
  32. data/app/models/linked_rails/form/field_factory.rb +48 -18
  33. data/app/models/linked_rails/form/group.rb +4 -6
  34. data/app/models/linked_rails/form/page.rb +8 -4
  35. data/app/models/linked_rails/form.rb +16 -21
  36. data/app/models/linked_rails/manifest.rb +8 -2
  37. data/app/models/linked_rails/menus/item.rb +10 -13
  38. data/app/models/linked_rails/menus/list.rb +16 -7
  39. data/app/models/linked_rails/ontology/base.rb +3 -1
  40. data/app/models/linked_rails/ontology/class.rb +3 -3
  41. data/app/models/linked_rails/ontology.rb +5 -5
  42. data/app/models/linked_rails/property_query.rb +2 -0
  43. data/app/models/linked_rails/sequence.rb +4 -13
  44. data/app/models/linked_rails/shacl/property_shape.rb +1 -1
  45. data/app/models/linked_rails/web_page.rb +0 -4
  46. data/app/models/linked_rails/web_site.rb +0 -4
  47. data/app/models/linked_rails/widget.rb +4 -11
  48. data/app/policies/linked_rails/actions/object_policy.rb +11 -0
  49. data/app/policies/linked_rails/collection_policy.rb +2 -2
  50. data/app/serializers/linked_rails/actions/item_serializer.rb +5 -5
  51. data/app/serializers/linked_rails/actions/object_serializer.rb +9 -0
  52. data/app/serializers/linked_rails/collection/filter_field_serializer.rb +3 -2
  53. data/app/serializers/linked_rails/collection/filter_option_serializer.rb +1 -1
  54. data/app/serializers/linked_rails/collection/filter_serializer.rb +1 -1
  55. data/app/serializers/linked_rails/collection/sorting_serializer.rb +1 -1
  56. data/app/serializers/linked_rails/collection/view_serializer.rb +3 -3
  57. data/app/serializers/linked_rails/collection_serializer.rb +8 -7
  58. data/app/serializers/linked_rails/condition_serializer.rb +3 -3
  59. data/app/serializers/linked_rails/entry_point_serializer.rb +3 -3
  60. data/app/serializers/linked_rails/enum_value_serializer.rb +1 -0
  61. data/app/serializers/linked_rails/form/field/association_input_serializer.rb +1 -0
  62. data/app/serializers/linked_rails/form/field/file_input_serializer.rb +11 -0
  63. data/app/serializers/linked_rails/form/field_serializer.rb +3 -1
  64. data/app/serializers/linked_rails/form/group_serializer.rb +1 -1
  65. data/app/serializers/linked_rails/form/page_serializer.rb +1 -1
  66. data/app/serializers/linked_rails/menus/item_serializer.rb +3 -3
  67. data/app/serializers/linked_rails/menus/list_serializer.rb +1 -1
  68. data/app/serializers/linked_rails/ontology_serializer.rb +2 -2
  69. data/app/serializers/linked_rails/property_query_serializer.rb +7 -0
  70. data/app/serializers/linked_rails/sequence_serializer.rb +2 -5
  71. data/app/serializers/linked_rails/shacl/node_shape_serializer.rb +1 -1
  72. data/app/serializers/linked_rails/shacl/property_shape_serializer.rb +1 -1
  73. data/app/serializers/linked_rails/shacl/shape_serializer.rb +5 -5
  74. data/app/serializers/linked_rails/web_page_serializer.rb +3 -3
  75. data/app/serializers/linked_rails/web_site_serializer.rb +1 -1
  76. data/app/serializers/linked_rails/widget_serializer.rb +3 -3
  77. data/lib/generators/linked_rails/install/install_generator.rb +5 -8
  78. data/lib/generators/linked_rails/install/templates/README +2 -0
  79. data/lib/generators/linked_rails/install/templates/app_menu_list.rb +36 -7
  80. data/lib/generators/linked_rails/install/templates/application_menu_list.rb +40 -1
  81. data/lib/generators/linked_rails/install/templates/initializer.rb +1 -2
  82. data/lib/generators/linked_rails/install/templates/locales.yml +14 -0
  83. data/lib/generators/linked_rails/install/templates/rdf_serializers_initializer.rb +1 -1
  84. data/lib/generators/linked_rails/install/templates/vocab.rb +1 -0
  85. data/lib/generators/linked_rails/install/templates/vocab.yml +2 -2
  86. data/lib/generators/linked_rails/model/model_generator.rb +0 -1
  87. data/lib/generators/linked_rails/model/templates/controller.rb.tt +5 -1
  88. data/lib/generators/linked_rails/model/templates/form.rb.tt +3 -0
  89. data/lib/generators/linked_rails/model/templates/menu_list.rb.tt +15 -0
  90. data/lib/generators/linked_rails/model/templates/policy.rb.tt +13 -0
  91. data/lib/generators/linked_rails/model/templates/serializer.rb.tt +5 -1
  92. data/lib/linked_rails/active_response/controller/collections.rb +1 -1
  93. data/lib/linked_rails/active_response/controller/crud_defaults.rb +4 -4
  94. data/lib/linked_rails/active_response/controller/params.rb +10 -10
  95. data/lib/linked_rails/active_response/controller.rb +8 -18
  96. data/lib/linked_rails/active_response/responders/rdf.rb +19 -10
  97. data/lib/linked_rails/callable_variable.rb +1 -1
  98. data/lib/linked_rails/collection_params_parser.rb +93 -0
  99. data/lib/linked_rails/controller/actionable.rb +121 -0
  100. data/lib/linked_rails/controller/authorization.rb +6 -0
  101. data/lib/linked_rails/controller/default_actions/create.rb +52 -0
  102. data/lib/linked_rails/controller/default_actions/destroy.rb +42 -0
  103. data/lib/linked_rails/controller/default_actions/update.rb +43 -0
  104. data/lib/linked_rails/controller/delta.rb +78 -0
  105. data/lib/linked_rails/controller/error_handling.rb +10 -10
  106. data/lib/linked_rails/controller/rendering.rb +48 -0
  107. data/lib/linked_rails/controller.rb +24 -4
  108. data/lib/linked_rails/enhancements/creatable/controller.rb +1 -1
  109. data/lib/linked_rails/enhancements/destroyable/controller.rb +1 -1
  110. data/lib/linked_rails/enhancements/updatable/controller.rb +1 -1
  111. data/lib/linked_rails/enhancements.rb +0 -16
  112. data/lib/linked_rails/helpers/delta_helper.rb +26 -57
  113. data/lib/linked_rails/helpers/ontola_actions_helper.rb +2 -2
  114. data/lib/linked_rails/helpers/resource_helper.rb +4 -2
  115. data/lib/linked_rails/iri_mapper.rb +17 -39
  116. data/lib/linked_rails/middleware/error_handling.rb +51 -0
  117. data/lib/linked_rails/middleware/linked_data_params.rb +30 -151
  118. data/lib/linked_rails/model/actionable.rb +68 -0
  119. data/lib/linked_rails/model/collections.rb +201 -39
  120. data/lib/linked_rails/model/dirty.rb +7 -8
  121. data/lib/linked_rails/model/enhancements.rb +1 -6
  122. data/lib/linked_rails/model/filtering.rb +4 -6
  123. data/lib/linked_rails/model/indexable.rb +6 -16
  124. data/lib/linked_rails/model/iri.rb +28 -19
  125. data/lib/linked_rails/model/iri_mapping.rb +37 -8
  126. data/lib/linked_rails/model/menuable.rb +28 -0
  127. data/lib/linked_rails/model/serialization.rb +2 -15
  128. data/lib/linked_rails/model/singularable.rb +57 -0
  129. data/lib/linked_rails/model/sorting.rb +0 -5
  130. data/lib/linked_rails/model/tables.rb +26 -0
  131. data/lib/linked_rails/model.rb +17 -7
  132. data/lib/linked_rails/params_parser.rb +133 -55
  133. data/lib/linked_rails/policy/attribute_conditions.rb +2 -2
  134. data/lib/linked_rails/policy.rb +40 -46
  135. data/lib/linked_rails/railtie.rb +11 -0
  136. data/lib/linked_rails/rdf_error.rb +2 -2
  137. data/lib/linked_rails/renderers.rb +1 -0
  138. data/lib/linked_rails/routes.rb +38 -22
  139. data/lib/linked_rails/serializer/actionable.rb +27 -0
  140. data/lib/linked_rails/serializer/menuable.rb +31 -0
  141. data/lib/linked_rails/serializer/singularable.rb +26 -0
  142. data/lib/linked_rails/serializer.rb +28 -11
  143. data/lib/linked_rails/test_methods.rb +114 -0
  144. data/lib/linked_rails/translate.rb +31 -9
  145. data/lib/linked_rails/types/iri_type.rb +37 -0
  146. data/lib/linked_rails/uri_template.rb +30 -0
  147. data/lib/linked_rails/version.rb +1 -1
  148. data/lib/linked_rails/vocab.rb +9 -0
  149. data/lib/linked_rails.rb +30 -13
  150. data/lib/rails/welcome_controller.rb +3 -3
  151. data/lib/rdf/list.rb +9 -0
  152. data/lib/rdf/query_fix.rb +15 -0
  153. metadata +57 -33
  154. data/app/models/linked_rails/actions/default_actions/create.rb +0 -60
  155. data/app/models/linked_rails/actions/default_actions/destroy.rb +0 -45
  156. data/app/models/linked_rails/actions/default_actions/update.rb +0 -50
  157. data/app/models/linked_rails/actions/default_actions.rb +0 -17
  158. data/lib/generators/linked_rails/install/templates/application_action_list.rb +0 -3
  159. data/lib/generators/linked_rails/model/templates/action_list.rb.tt +0 -6
  160. data/lib/linked_rails/enhancements/actionable/model.rb +0 -71
  161. data/lib/linked_rails/enhancements/actionable/serializer.rb +0 -25
  162. data/lib/linked_rails/enhancements/creatable/action.rb +0 -15
  163. data/lib/linked_rails/enhancements/destroyable/action.rb +0 -15
  164. data/lib/linked_rails/enhancements/destroyable/routing.rb +0 -19
  165. data/lib/linked_rails/enhancements/menuable/model.rb +0 -36
  166. data/lib/linked_rails/enhancements/menuable/serializer.rb +0 -33
  167. data/lib/linked_rails/enhancements/route_concerns.rb +0 -56
  168. data/lib/linked_rails/enhancements/singularable/controller.rb +0 -43
  169. data/lib/linked_rails/enhancements/singularable/model.rb +0 -47
  170. data/lib/linked_rails/enhancements/singularable/serializer.rb +0 -28
  171. data/lib/linked_rails/enhancements/tableable/model.rb +0 -28
  172. data/lib/linked_rails/enhancements/updatable/action.rb +0 -15
  173. data/lib/linked_rails/enhancements/updatable/routing.rb +0 -20
@@ -7,7 +7,7 @@ module LinkedRails
7
7
  extend ActiveSupport::Concern
8
8
 
9
9
  included do
10
- active_response :create
10
+ has_collection_create_action
11
11
  end
12
12
  end
13
13
  end
@@ -7,7 +7,7 @@ module LinkedRails
7
7
  extend ActiveSupport::Concern
8
8
 
9
9
  included do
10
- active_response :destroy
10
+ has_resource_destroy_action
11
11
  end
12
12
  end
13
13
  end
@@ -7,7 +7,7 @@ module LinkedRails
7
7
  extend ActiveSupport::Concern
8
8
 
9
9
  included do
10
- active_response :update
10
+ has_resource_update_action
11
11
  end
12
12
  end
13
13
  end
@@ -1,22 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'enhancements/route_concerns'
4
-
5
- require_relative 'enhancements/actionable/model'
6
- require_relative 'enhancements/actionable/serializer'
7
- require_relative 'enhancements/creatable/action'
8
3
  require_relative 'enhancements/creatable/controller'
9
- require_relative 'enhancements/destroyable/action'
10
4
  require_relative 'enhancements/destroyable/controller'
11
- require_relative 'enhancements/destroyable/routing'
12
- require_relative 'enhancements/indexable/model'
13
- require_relative 'enhancements/menuable/model'
14
- require_relative 'enhancements/menuable/serializer'
15
- require_relative 'enhancements/singularable/controller'
16
- require_relative 'enhancements/singularable/model'
17
- require_relative 'enhancements/singularable/serializer'
18
- require_relative 'enhancements/tableable/model'
19
- require_relative 'enhancements/updatable/action'
20
5
  require_relative 'enhancements/updatable/controller'
21
- require_relative 'enhancements/updatable/routing'
22
6
  require_relative 'enhancements/updatable/serializer'
@@ -3,61 +3,8 @@
3
3
  module LinkedRails
4
4
  module Helpers
5
5
  module DeltaHelper
6
- def changed_relation_triples(predicate, destructed, resources)
7
- related_resource_invalidations = resources.map(&method(:invalidate_resource_delta))
8
-
9
- return related_resource_invalidations unless predicate
10
-
11
- if destructed
12
- return related_resource_invalidations + [
13
- [current_resource.iri, predicate, Vocab.sp[:Variable], delta_iri(:remove)]
14
- ]
15
- end
16
-
17
- related_resource_invalidations + resources.map do |resource|
18
- [current_resource.iri, predicate, resource.iri, delta_iri(:replace)]
19
- end
20
- end
21
-
22
- def changed_relations_triples # rubocop:disable Metrics/AbcSize
23
- current_resource.previously_changed_relations.flat_map do |key, value|
24
- if key.to_s.ends_with?('_collection')
25
- [invalidate_collection_delta(current_resource.send(key))]
26
- else
27
- destructed = current_resource.send(:association_has_destructed?, key)
28
- many = value.relationship_type == :has_many
29
- relation = current_resource.send(key)
30
- changed_relation_triples(value.predicate, destructed, many ? relation : [relation])
31
- end
32
- end
33
- end
34
-
35
- def change_triple(predicate, value)
36
- if value.nil?
37
- RDF::Statement.new(current_resource.iri, predicate, Vocab.sp[:Variable], graph_name: delta_iri(:remove))
38
- else
39
- RDF::Statement.new(current_resource.iri, predicate, value, graph_name: delta_iri(:replace))
40
- end
41
- end
42
-
43
- def changes_triples # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
44
- serializer = RDF::Serializers.serializer_for(current_resource).new(current_resource)
45
-
46
- current_resource.previous_changes_by_predicate.map do |predicate, (_old_value, _new_value)|
47
- serializer_attributes = current_resource.class.predicate_mapping[predicate]
48
- next if serializer_attributes.is_a?(FastJsonapi::Relationship)
49
-
50
- attr_name = serializer_attributes.key
51
- serialized_value =
52
- serializer.class.attributes_to_serialize[attr_name]&.serialize(current_resource, serializer_params, {})
53
- (serialized_value.is_a?(Array) ? serialized_value : [serialized_value]).map do |value|
54
- change_triple(predicate, value)
55
- end
56
- end.compact.flatten
57
- end
58
-
59
6
  def delta_iri(delta)
60
- %i[remove replace invalidate].include?(delta) ? Vocab.ontola[delta] : Vocab.ll[delta]
7
+ %i[remove replace invalidate].include?(delta) ? Vocab.ontola[delta] : Vocab.ld[delta]
61
8
  end
62
9
 
63
10
  def invalidate_collection_delta(collection)
@@ -65,7 +12,9 @@ module LinkedRails
65
12
  end
66
13
 
67
14
  def invalidate_parent_collections_delta(resource)
68
- resource&.parent_collections(try(:user_context))&.map(&method(:invalidate_collection_delta)) || []
15
+ context = user_context if respond_to?(:user_context, true)
16
+
17
+ resource&.parent_collections(context)&.map(&method(:invalidate_collection_delta)) || []
69
18
  end
70
19
 
71
20
  def invalidate_resource_delta(resource)
@@ -75,11 +24,31 @@ module LinkedRails
75
24
  end
76
25
 
77
26
  def resource_added_delta(resource)
78
- invalidate_parent_collections_delta(resource)
27
+ invalidate_parent_collections_delta(resource) + singular_added_delta(resource)
79
28
  end
80
29
 
81
30
  def resource_removed_delta(resource)
82
- invalidate_parent_collections_delta(resource)
31
+ invalidate_parent_collections_delta(resource) + singular_removed_delta(resource)
32
+ end
33
+
34
+ def same_as_statement(from, to)
35
+ [
36
+ from,
37
+ Vocab.owl.sameAs,
38
+ to
39
+ ]
40
+ end
41
+
42
+ def singular_added_delta(resource)
43
+ return [] unless resource.try(:singular_resource?)
44
+
45
+ [same_as_statement(resource.singular_iri, resource.iri)]
46
+ end
47
+
48
+ def singular_removed_delta(resource)
49
+ return [] unless resource.try(:singular_resource?)
50
+
51
+ [invalidate_resource_delta(resource.singular_iri)]
83
52
  end
84
53
  end
85
54
  end
@@ -12,8 +12,8 @@ module LinkedRails
12
12
  Vocab.libro["actions/copyToClipboard?#{{value: value}.to_param}"]
13
13
  end
14
14
 
15
- def ontola_dialog_action(resource, opener: nil)
16
- Vocab.libro["actions/dialog/alert?#{{resource: resource, opener: opener}.compact.to_param}"]
15
+ def ontola_dialog_action(resource, opener: nil, size: nil)
16
+ Vocab.libro["actions/dialog/alert?#{{resource: resource, opener: opener, size: size}.compact.to_param}"]
17
17
  end
18
18
 
19
19
  def ontola_dialog_close_action
@@ -41,7 +41,7 @@ module LinkedRails
41
41
  end
42
42
 
43
43
  def requested_resource_parent
44
- requested_resource&.parent
44
+ requested_resource.try(:parent)
45
45
  end
46
46
 
47
47
  private
@@ -51,6 +51,8 @@ module LinkedRails
51
51
  end
52
52
 
53
53
  def new_resource_from_parent
54
+ return requested_resource.child_resource if requested_resource.is_a?(Collection)
55
+
54
56
  parent_resource.build_child(
55
57
  controller_class,
56
58
  user_context: user_context
@@ -59,7 +61,7 @@ module LinkedRails
59
61
 
60
62
  def resolve_current_resource
61
63
  case action_name
62
- when 'create', 'new'
64
+ when 'create'
63
65
  new_resource
64
66
  else
65
67
  requested_resource
@@ -12,6 +12,7 @@ module LinkedRails
12
12
  # => {
13
13
  # action: 'show',
14
14
  # class: Resource,
15
+ # iri: 'https://example.com/resource/1?foo=bar',
15
16
  # params: {
16
17
  # id: '1',
17
18
  # foo: 'bar'
@@ -22,6 +23,7 @@ module LinkedRails
22
23
  # => {
23
24
  # action: 'index',
24
25
  # class: Resource,
26
+ # iri: 'https://example.com/resources?foo=bar',
25
27
  # params: {
26
28
  # foo: 'bar'
27
29
  # }
@@ -35,38 +37,21 @@ module LinkedRails
35
37
  query = Rack::Utils.parse_nested_query(URI(iri.to_s).query)
36
38
  params = Rails.application.routes.recognize_path(iri.to_s, method: method)
37
39
 
38
- route_params_to_opts(params.merge(query))
40
+ route_params_to_opts(params.merge(query), iri.to_s)
39
41
  rescue ActionController::RoutingError
40
42
  EMPTY_IRI_OPTS.dup
41
43
  end
42
44
 
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
45
  def parent_from_params(params, user_context)
59
46
  return unless params.key?(:parent_iri)
60
47
 
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])
48
+ parent_iri = LinkedRails.iri(path: "/#{params[:parent_iri]}")
64
49
 
65
- LinkedRails.iri_mapper.resource_from_opts(opts, user_context)
50
+ LinkedRails.iri_mapper.resource_from_iri(parent_iri, user_context)
66
51
  end
67
52
 
68
53
  def resource_from_iri(iri, user_context)
69
- ensure_absolute_iri!(iri)
54
+ return nil unless absolute_iri?(iri)
70
55
 
71
56
  opts = opts_from_iri(iri)
72
57
  resource_from_opts(opts, user_context)
@@ -80,7 +65,7 @@ module LinkedRails
80
65
  opts[:class]&.requested_resource(opts, user_context)
81
66
  end
82
67
 
83
- def route_params_to_opts(params)
68
+ def route_params_to_opts(params, iri)
84
69
  controller_class = class_for_controller(params[:controller])
85
70
 
86
71
  return EMPTY_IRI_OPTS.dup if controller_class.blank?
@@ -88,27 +73,17 @@ module LinkedRails
88
73
  {
89
74
  action: params[:action],
90
75
  class: controller_class,
91
- params: params.except(:action, :controller)
76
+ iri: iri,
77
+ params: sanitized_route_params(controller_class, params)
92
78
  }.with_indifferent_access
93
79
  end
94
80
 
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
81
+ private
105
82
 
106
- def single_resource_from_opts(opts, user_context)
107
- opts[:class]&.requested_single_resource(opts[:params], user_context) unless opts[:params] == 'index'
83
+ def absolute_iri?(iri)
84
+ iri.present? && !URI(iri).relative?
108
85
  end
109
86
 
110
- private
111
-
112
87
  def class_for_controller(controller)
113
88
  return if controller.blank?
114
89
 
@@ -117,8 +92,11 @@ module LinkedRails
117
92
  .try(:controller_class)
118
93
  end
119
94
 
120
- def ensure_absolute_iri!(iri)
121
- raise("An absolute url is expected. #{iri} is given.") if iri.blank? || URI(iri).relative?
95
+ def sanitized_route_params(controller_class, params)
96
+ new_params = params.except(:action, :controller)
97
+ nested_key = :"#{controller_class.name.tableize.singularize}_id"
98
+ new_params[:id] ||= new_params.delete(nested_key) if new_params.key?(nested_key)
99
+ new_params
122
100
  end
123
101
  end
124
102
  end
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ module LinkedRails
4
+ module Middleware
5
+ class ErrorHandling
6
+ def initialize(app)
7
+ @app = app
8
+ end
9
+
10
+ def call(env)
11
+ @app.call(env)
12
+ rescue StandardError => e
13
+ handle_error(env, e)
14
+ end
15
+
16
+ private
17
+
18
+ def controller_class(req)
19
+ controller = req.controller_class
20
+
21
+ return ApplicationController if controller == ActionDispatch::Request::PASS_NOT_FOUND
22
+
23
+ controller
24
+ rescue StandardError
25
+ ApplicationController
26
+ end
27
+
28
+ def controller_instance(env)
29
+ return env['action_controller.instance'] if env['action_controller.instance']
30
+
31
+ req = ActionDispatch::Request.new(env)
32
+ res = ApplicationController.make_response!(req)
33
+ controller = controller_class(req).new
34
+ controller.set_request!(req)
35
+ controller.set_response!(res)
36
+
37
+ controller
38
+ end
39
+
40
+ def handle_error(env, error)
41
+ controller = controller_instance(env)
42
+
43
+ controller.send(:handle_and_report_error, error)
44
+ error_response = controller.response
45
+ body = ActionDispatch::Response::RackBody.new(error_response)
46
+
47
+ [error_response.status, error_response.headers, body]
48
+ end
49
+ end
50
+ end
51
+ end
@@ -1,8 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # require 'empathy/emp_json'
4
+
3
5
  module LinkedRails
4
6
  module Middleware
5
- class LinkedDataParams # rubocop:disable Metrics/ClassLength
7
+ class LinkedDataParams
8
+ include ::Empathy::EmpJson::Helpers::Slices
9
+ include ::Empathy::EmpJson::Helpers::Primitives
10
+
6
11
  def initialize(app)
7
12
  @app = app
8
13
  end
@@ -10,40 +15,15 @@ module LinkedRails
10
15
  def call(env)
11
16
  req = Rack::Request.new(env)
12
17
  params_from_query(req)
13
- params_from_graph(req)
18
+ params_from_slice(req)
14
19
 
15
20
  @app.call(env)
16
21
  end
17
22
 
18
23
  private
19
24
 
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/'])
25
+ def add_param_from_query(data, target_class, key, value)
26
+ data[target_class.predicate_mapping[RDF::URI(key)].key] = value
47
27
  end
48
28
 
49
29
  def convert_query_params(request, target_class)
@@ -56,76 +36,32 @@ module LinkedRails
56
36
  request.update_param(class_key, data) if data.present?
57
37
  end
58
38
 
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?
39
+ def slice_from_request(request)
40
+ return unless request.content_type == Mime::Type.lookup_by_extension(:empjson).to_s
73
41
 
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
42
+ body = request.body.read
90
43
 
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]
44
+ JSON.parse(body) if body.present?
100
45
  end
101
46
 
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.
47
+ # Converts a emp slice from to a nested attributes hash.
111
48
  #
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
49
+ # The entrypoint for the slice is the `.` subject, which is
114
50
  # assumed to be the resource intended to be targeted by the request (i.e. the
115
51
  # resource to be created, updated, or deleted).
116
52
  #
117
53
  # @return [Hash] A hash of attributes, empty if no statements were given.
118
- def params_from_graph(request)
119
- graph = graph_from_request(request)
54
+ def params_from_slice(request)
55
+ slice = slice_from_request(request)
120
56
 
121
- return unless graph
57
+ return unless slice
122
58
 
123
- request.update_param(:body_graph, graph)
59
+ request.env['emp_json'] = slice
124
60
  target_class = target_class_from_path(request)
125
61
  return if target_class.blank?
126
62
 
127
- update_actor_param(request, graph)
128
- update_target_params(request, graph, target_class)
63
+ update_actor_param(request, slice)
64
+ update_target_params(request, slice, target_class)
129
65
  end
130
66
 
131
67
  def params_from_query(request)
@@ -135,87 +71,30 @@ module LinkedRails
135
71
  convert_query_params(request, target_class)
136
72
  end
137
73
 
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
74
  def target_class_from_path(request) # rubocop:disable Metrics/AbcSize
198
75
  opts = LinkedRails.iri_mapper.opts_from_iri(
199
76
  request.base_url + request.env['REQUEST_URI'],
200
77
  method: request.request_method
201
78
  )
202
79
 
203
- logger.info("No class found for #{request.base_url + request.env['REQUEST_URI']}") unless opts[:class]
80
+ Rails.logger.info("No class found for #{request.base_url + request.env['REQUEST_URI']}") unless opts[:class]
204
81
 
205
82
  opts[:class]
206
83
  end
207
84
 
208
- def update_actor_param(request, graph)
209
- actor = graph.query([Vocab.ll[:targetResource], Vocab.schema.creator]).first
85
+ def update_actor_param(request, slice)
86
+ actor = values_from_slice(slice, '.', Vocab.schema.creator)
87
+
210
88
  return if actor.blank?
211
89
 
212
- request.update_param(:actor_iri, actor.object)
213
- graph.delete(actor)
90
+ request.update_param(:actor_iri, emp_to_primitive(actor))
214
91
  end
215
92
 
216
- def update_target_params(request, graph, target_class)
93
+ def update_target_params(request, slice, target_class)
217
94
  key = target_class.to_s.demodulize.underscore
218
- from_body = parse_resource(request.params, graph, Vocab.ll[:targetResource], target_class)
95
+
96
+ parser = ParamsParser.new(slice: slice, params: request.params)
97
+ from_body = parser.parse_resource('.', target_class)
219
98
 
220
99
  request.update_param(key, from_body.merge(request.params[key] || {}))
221
100
  end
@@ -0,0 +1,68 @@
1
+ # frozen_string_literal: true
2
+
3
+ module LinkedRails
4
+ module Model
5
+ module Actionable
6
+ extend ActiveSupport::Concern
7
+
8
+ def actions(user_context = nil)
9
+ action_list(user_context).actions
10
+ end
11
+
12
+ def action(tag, user_context = nil)
13
+ actions(user_context).find { |a| a.tag == tag }
14
+ end
15
+
16
+ def action_list(user_context)
17
+ @action_list ||= {}
18
+ @action_list[user_context] ||= self.class.action_list.new(resource: self, user_context: user_context)
19
+ end
20
+
21
+ def action_triples
22
+ @action_triples ||= triples_for_actions(actions) + triples_for_actions(collection_actions)
23
+ end
24
+
25
+ def collection_actions
26
+ (try(:collections) || []).map do |opts|
27
+ collection_for(opts[:name]).actions
28
+ end.flatten
29
+ end
30
+
31
+ def favorite_actions
32
+ actions.filter(&:favorite)
33
+ end
34
+
35
+ private
36
+
37
+ def triples_for_actions(actions)
38
+ actions.flat_map do |action|
39
+ [
40
+ [iri, action.predicate, action.iri],
41
+ [iri, Vocab.schema.potentialAction, action.iri]
42
+ ]
43
+ end
44
+ end
45
+
46
+ module ClassMethods
47
+ def action_list
48
+ @action_list ||= define_action_list
49
+ end
50
+
51
+ private
52
+
53
+ def action_superclass
54
+ superclass.try(:action_list) || LinkedRails.action_list_parent_class
55
+ end
56
+
57
+ def define_action_list
58
+ klass = Class.new(action_superclass)
59
+ actionable_class = self
60
+ klass.define_singleton_method(:actionable_class) do
61
+ actionable_class
62
+ end
63
+ klass
64
+ end
65
+ end
66
+ end
67
+ end
68
+ end