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.
Files changed (207) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +674 -0
  3. data/README.md +65 -0
  4. data/Rakefile +34 -0
  5. data/app/controllers/linked_rails/actions/items_controller.rb +9 -0
  6. data/app/controllers/linked_rails/bulk_controller.rb +195 -0
  7. data/app/controllers/linked_rails/current_user_controller.rb +13 -0
  8. data/app/controllers/linked_rails/enum_values_controller.rb +49 -0
  9. data/app/controllers/linked_rails/forms_controller.rb +13 -0
  10. data/app/controllers/linked_rails/manifests_controller.rb +21 -0
  11. data/app/controllers/linked_rails/menus/items_controller.rb +9 -0
  12. data/app/controllers/linked_rails/menus/lists_controller.rb +9 -0
  13. data/app/controllers/linked_rails/not_found_controller.rb +15 -0
  14. data/app/controllers/linked_rails/ontologies_controller.rb +7 -0
  15. data/app/models/linked_rails/actions/default_actions/create.rb +60 -0
  16. data/app/models/linked_rails/actions/default_actions/destroy.rb +45 -0
  17. data/app/models/linked_rails/actions/default_actions/update.rb +50 -0
  18. data/app/models/linked_rails/actions/default_actions.rb +17 -0
  19. data/app/models/linked_rails/actions/item.rb +234 -0
  20. data/app/models/linked_rails/actions/list.rb +113 -0
  21. data/app/models/linked_rails/collection/filter.rb +16 -0
  22. data/app/models/linked_rails/collection/filter_field.rb +30 -0
  23. data/app/models/linked_rails/collection/filter_option.rb +17 -0
  24. data/app/models/linked_rails/collection/filterable.rb +92 -0
  25. data/app/models/linked_rails/collection/infinite_view.rb +98 -0
  26. data/app/models/linked_rails/collection/iri.rb +74 -0
  27. data/app/models/linked_rails/collection/iri_mapping.rb +33 -0
  28. data/app/models/linked_rails/collection/paginated_view.rb +41 -0
  29. data/app/models/linked_rails/collection/sortable.rb +60 -0
  30. data/app/models/linked_rails/collection/sorting.rb +72 -0
  31. data/app/models/linked_rails/collection/view.rb +101 -0
  32. data/app/models/linked_rails/collection.rb +220 -0
  33. data/app/models/linked_rails/condition.rb +7 -0
  34. data/app/models/linked_rails/creative_work.rb +21 -0
  35. data/app/models/linked_rails/current_user.rb +28 -0
  36. data/app/models/linked_rails/entry_point.rb +53 -0
  37. data/app/models/linked_rails/enum_value.rb +33 -0
  38. data/app/models/linked_rails/form/field/association_input.rb +23 -0
  39. data/app/models/linked_rails/form/field/checkbox_group.rb +10 -0
  40. data/app/models/linked_rails/form/field/checkbox_input.rb +10 -0
  41. data/app/models/linked_rails/form/field/color_input.rb +17 -0
  42. data/app/models/linked_rails/form/field/date_input.rb +10 -0
  43. data/app/models/linked_rails/form/field/date_time_input.rb +10 -0
  44. data/app/models/linked_rails/form/field/email_input.rb +10 -0
  45. data/app/models/linked_rails/form/field/file_input.rb +10 -0
  46. data/app/models/linked_rails/form/field/location_input.rb +11 -0
  47. data/app/models/linked_rails/form/field/markdown_input.rb +10 -0
  48. data/app/models/linked_rails/form/field/number_input.rb +10 -0
  49. data/app/models/linked_rails/form/field/password_input.rb +10 -0
  50. data/app/models/linked_rails/form/field/postal_range_input.rb +10 -0
  51. data/app/models/linked_rails/form/field/radio_group.rb +10 -0
  52. data/app/models/linked_rails/form/field/resource_field.rb +23 -0
  53. data/app/models/linked_rails/form/field/select_input.rb +11 -0
  54. data/app/models/linked_rails/form/field/slider_input.rb +10 -0
  55. data/app/models/linked_rails/form/field/text_area_input.rb +10 -0
  56. data/app/models/linked_rails/form/field/text_input.rb +10 -0
  57. data/app/models/linked_rails/form/field/toggle_button_group.rb +10 -0
  58. data/app/models/linked_rails/form/field.rb +117 -0
  59. data/app/models/linked_rails/form/field_factory.rb +219 -0
  60. data/app/models/linked_rails/form/group.rb +39 -0
  61. data/app/models/linked_rails/form/page.rb +31 -0
  62. data/app/models/linked_rails/form.rb +156 -0
  63. data/app/models/linked_rails/manifest.rb +102 -0
  64. data/app/models/linked_rails/media_object.rb +31 -0
  65. data/app/models/linked_rails/menus/item.rb +92 -0
  66. data/app/models/linked_rails/menus/list.rb +138 -0
  67. data/app/models/linked_rails/ontology/base.rb +50 -0
  68. data/app/models/linked_rails/ontology/class.rb +43 -0
  69. data/app/models/linked_rails/ontology/property.rb +19 -0
  70. data/app/models/linked_rails/ontology.rb +34 -0
  71. data/app/models/linked_rails/property_query.rb +11 -0
  72. data/app/models/linked_rails/resource.rb +17 -0
  73. data/app/models/linked_rails/sequence.rb +64 -0
  74. data/app/models/linked_rails/shacl/node_shape.rb +21 -0
  75. data/app/models/linked_rails/shacl/property_shape.rb +53 -0
  76. data/app/models/linked_rails/shacl/shape.rb +33 -0
  77. data/app/models/linked_rails/web_page.rb +22 -0
  78. data/app/models/linked_rails/web_site.rb +17 -0
  79. data/app/models/linked_rails/widget.rb +55 -0
  80. data/app/policies/linked_rails/actions/item_policy.rb +11 -0
  81. data/app/policies/linked_rails/actions/list_policy.rb +11 -0
  82. data/app/policies/linked_rails/collection/view_policy.rb +13 -0
  83. data/app/policies/linked_rails/collection_policy.rb +41 -0
  84. data/app/policies/linked_rails/enum_value_policy.rb +32 -0
  85. data/app/policies/linked_rails/menus/item_policy.rb +11 -0
  86. data/app/policies/linked_rails/menus/list_policy.rb +11 -0
  87. data/app/policies/linked_rails/sequence_policy.rb +9 -0
  88. data/app/serializers/linked_rails/actions/item_serializer.rb +28 -0
  89. data/app/serializers/linked_rails/collection/filter_field_serializer.rb +12 -0
  90. data/app/serializers/linked_rails/collection/filter_option_serializer.rb +12 -0
  91. data/app/serializers/linked_rails/collection/filter_serializer.rb +13 -0
  92. data/app/serializers/linked_rails/collection/sorting_serializer.rb +13 -0
  93. data/app/serializers/linked_rails/collection/view_serializer.rb +22 -0
  94. data/app/serializers/linked_rails/collection_serializer.rb +44 -0
  95. data/app/serializers/linked_rails/condition_serializer.rb +9 -0
  96. data/app/serializers/linked_rails/creative_work_serializer.rb +10 -0
  97. data/app/serializers/linked_rails/current_user_serializer.rb +7 -0
  98. data/app/serializers/linked_rails/entry_point_serializer.rb +20 -0
  99. data/app/serializers/linked_rails/enum_value_serializer.rb +12 -0
  100. data/app/serializers/linked_rails/form/field/association_input_serializer.rb +13 -0
  101. data/app/serializers/linked_rails/form/field/resource_field_serializer.rb +11 -0
  102. data/app/serializers/linked_rails/form/field/select_input_serializer.rb +11 -0
  103. data/app/serializers/linked_rails/form/field_serializer.rb +39 -0
  104. data/app/serializers/linked_rails/form/group_serializer.rb +14 -0
  105. data/app/serializers/linked_rails/form/page_serializer.rb +13 -0
  106. data/app/serializers/linked_rails/form_serializer.rb +9 -0
  107. data/app/serializers/linked_rails/media_object_serializer.rb +17 -0
  108. data/app/serializers/linked_rails/menus/item_serializer.rb +35 -0
  109. data/app/serializers/linked_rails/menus/list_serializer.rb +13 -0
  110. data/app/serializers/linked_rails/ontology/class_serializer.rb +19 -0
  111. data/app/serializers/linked_rails/ontology/property_serializer.rb +16 -0
  112. data/app/serializers/linked_rails/ontology_serializer.rb +8 -0
  113. data/app/serializers/linked_rails/rdf_error_serializer.rb +8 -0
  114. data/app/serializers/linked_rails/sequence_serializer.rb +14 -0
  115. data/app/serializers/linked_rails/shacl/node_shape_serializer.rb +12 -0
  116. data/app/serializers/linked_rails/shacl/property_shape_serializer.rb +38 -0
  117. data/app/serializers/linked_rails/shacl/shape_serializer.rb +25 -0
  118. data/app/serializers/linked_rails/web_page_serializer.rb +10 -0
  119. data/app/serializers/linked_rails/web_site_serializer.rb +11 -0
  120. data/app/serializers/linked_rails/widget_serializer.rb +15 -0
  121. data/config/initializers/inflections.rb +5 -0
  122. data/lib/generators/linked_rails/install/install_generator.rb +65 -0
  123. data/lib/generators/linked_rails/install/templates/app_menu_list.rb +41 -0
  124. data/lib/generators/linked_rails/install/templates/application_action_list.rb +3 -0
  125. data/lib/generators/linked_rails/install/templates/application_form.rb +3 -0
  126. data/lib/generators/linked_rails/install/templates/application_menu_list.rb +3 -0
  127. data/lib/generators/linked_rails/install/templates/application_policy.rb +18 -0
  128. data/lib/generators/linked_rails/install/templates/application_serializer.rb +5 -0
  129. data/lib/generators/linked_rails/install/templates/initializer.rb +9 -0
  130. data/lib/generators/linked_rails/install/templates/locales.yml +12 -0
  131. data/lib/generators/linked_rails/install/templates/rdf_responder.rb +5 -0
  132. data/lib/generators/linked_rails/install/templates/rdf_serializers_initializer.rb +5 -0
  133. data/lib/generators/linked_rails/install/templates/vocab.rb +5 -0
  134. data/lib/generators/linked_rails/install/templates/vocab.yml +26 -0
  135. data/lib/generators/linked_rails/model/model_generator.rb +58 -0
  136. data/lib/generators/linked_rails/model/templates/action_list.rb.tt +6 -0
  137. data/lib/generators/linked_rails/model/templates/controller.rb.tt +7 -0
  138. data/lib/generators/linked_rails/model/templates/create_table_migration.rb.tt +24 -0
  139. data/lib/generators/linked_rails/model/templates/form.rb.tt +6 -0
  140. data/lib/generators/linked_rails/model/templates/menu_list.rb.tt +6 -0
  141. data/lib/generators/linked_rails/model/templates/model.rb.tt +15 -0
  142. data/lib/generators/linked_rails/model/templates/module.rb.tt +7 -0
  143. data/lib/generators/linked_rails/model/templates/policy.rb.tt +6 -0
  144. data/lib/generators/linked_rails/model/templates/serializer.rb.tt +9 -0
  145. data/lib/linked_rails/active_response/controller/collections.rb +43 -0
  146. data/lib/linked_rails/active_response/controller/crud_defaults.rb +92 -0
  147. data/lib/linked_rails/active_response/controller/params.rb +51 -0
  148. data/lib/linked_rails/active_response/controller.rb +37 -0
  149. data/lib/linked_rails/active_response/responders/rdf.rb +158 -0
  150. data/lib/linked_rails/cache.rb +35 -0
  151. data/lib/linked_rails/callable_variable.rb +25 -0
  152. data/lib/linked_rails/constraints/whitelist.rb +36 -0
  153. data/lib/linked_rails/controller/authorization.rb +15 -0
  154. data/lib/linked_rails/controller/error_handling.rb +76 -0
  155. data/lib/linked_rails/controller.rb +43 -0
  156. data/lib/linked_rails/engine.rb +7 -0
  157. data/lib/linked_rails/enhanceable.rb +21 -0
  158. data/lib/linked_rails/enhancements/actionable/model.rb +71 -0
  159. data/lib/linked_rails/enhancements/actionable/serializer.rb +25 -0
  160. data/lib/linked_rails/enhancements/creatable/action.rb +15 -0
  161. data/lib/linked_rails/enhancements/creatable/controller.rb +15 -0
  162. data/lib/linked_rails/enhancements/destroyable/action.rb +15 -0
  163. data/lib/linked_rails/enhancements/destroyable/controller.rb +15 -0
  164. data/lib/linked_rails/enhancements/destroyable/routing.rb +19 -0
  165. data/lib/linked_rails/enhancements/indexable/model.rb +10 -0
  166. data/lib/linked_rails/enhancements/menuable/model.rb +36 -0
  167. data/lib/linked_rails/enhancements/menuable/serializer.rb +33 -0
  168. data/lib/linked_rails/enhancements/route_concerns.rb +56 -0
  169. data/lib/linked_rails/enhancements/singularable/controller.rb +43 -0
  170. data/lib/linked_rails/enhancements/singularable/model.rb +47 -0
  171. data/lib/linked_rails/enhancements/singularable/serializer.rb +28 -0
  172. data/lib/linked_rails/enhancements/tableable/model.rb +28 -0
  173. data/lib/linked_rails/enhancements/updatable/action.rb +15 -0
  174. data/lib/linked_rails/enhancements/updatable/controller.rb +15 -0
  175. data/lib/linked_rails/enhancements/updatable/routing.rb +20 -0
  176. data/lib/linked_rails/enhancements/updatable/serializer.rb +17 -0
  177. data/lib/linked_rails/enhancements.rb +22 -0
  178. data/lib/linked_rails/helpers/delta_helper.rb +86 -0
  179. data/lib/linked_rails/helpers/ontola_actions_helper.rb +32 -0
  180. data/lib/linked_rails/helpers/resource_helper.rb +70 -0
  181. data/lib/linked_rails/iri_mapper.rb +125 -0
  182. data/lib/linked_rails/middleware/linked_data_params.rb +224 -0
  183. data/lib/linked_rails/model/collections.rb +82 -0
  184. data/lib/linked_rails/model/dirty.rb +82 -0
  185. data/lib/linked_rails/model/enhancements.rb +61 -0
  186. data/lib/linked_rails/model/filtering.rb +89 -0
  187. data/lib/linked_rails/model/indexable.rb +51 -0
  188. data/lib/linked_rails/model/iri.rb +121 -0
  189. data/lib/linked_rails/model/iri_mapping.rb +69 -0
  190. data/lib/linked_rails/model/serialization.rb +88 -0
  191. data/lib/linked_rails/model/sorting.rb +20 -0
  192. data/lib/linked_rails/model.rb +68 -0
  193. data/lib/linked_rails/params_parser.rb +93 -0
  194. data/lib/linked_rails/policy/attribute_conditions.rb +53 -0
  195. data/lib/linked_rails/policy.rb +189 -0
  196. data/lib/linked_rails/rdf_error.rb +36 -0
  197. data/lib/linked_rails/renderers.rb +46 -0
  198. data/lib/linked_rails/routes.rb +108 -0
  199. data/lib/linked_rails/serializer.rb +137 -0
  200. data/lib/linked_rails/translate.rb +176 -0
  201. data/lib/linked_rails/version.rb +5 -0
  202. data/lib/linked_rails/vocab.rb +81 -0
  203. data/lib/linked_rails.rb +86 -0
  204. data/lib/nill_class_renderer.rb +3 -0
  205. data/lib/rails/welcome_controller.rb +45 -0
  206. data/lib/tasks/linked_rails_tasks.rake +6 -0
  207. metadata +416 -0
@@ -0,0 +1,69 @@
1
+ # frozen_string_literal: true
2
+
3
+ module LinkedRails
4
+ module Model
5
+ module IriMapping
6
+ extend ActiveSupport::Concern
7
+
8
+ module ClassMethods
9
+ def parent_from_params(params, user_context)
10
+ LinkedRails.iri_mapper.parent_from_params(params, user_context)
11
+ end
12
+
13
+ def requested_index_resource(params, user_context)
14
+ if params.key?(:parent_iri)
15
+ collection_from_parent(index_collection_params(params, user_context))
16
+ else
17
+ root_collection(index_collection_params(params, user_context))
18
+ end
19
+ end
20
+
21
+ def requested_index_resource!(params, user_context)
22
+ requested_index_resource(params, user_context) || raise(ActiveRecord::RecordNotFound)
23
+ end
24
+
25
+ def requested_resource(opts, user_context)
26
+ if collection_action?(opts)
27
+ requested_index_resource(opts[:params], user_context)
28
+ elsif singular_action?(opts)
29
+ resource = requested_singular_resource(opts[:params], user_context)
30
+ resource&.singular_resource = true
31
+ resource
32
+ else
33
+ requested_single_resource(opts[:params], user_context)
34
+ end
35
+ end
36
+
37
+ def requested_single_resource(params, _user_context)
38
+ if self < ActiveRecord::Base
39
+ find_by(primary_key => params[:id]) if params.key?(:id)
40
+ else
41
+ new(params)
42
+ end
43
+ end
44
+
45
+ def requested_single_resource!(params, user_context)
46
+ requested_single_resource(params, user_context) || raise(ActiveRecord::RecordNotFound)
47
+ end
48
+
49
+ private
50
+
51
+ def collection_action?(opts)
52
+ %w[index create].include?(opts[:action]) && !opts[:params][:singular_route]
53
+ end
54
+
55
+ def index_collection_params(params, user_context)
56
+ params_hash = params.is_a?(ActionController::Parameters) ? params.to_unsafe_h : params
57
+
58
+ {
59
+ user_context: user_context
60
+ }.merge(params_hash).with_indifferent_access
61
+ end
62
+
63
+ def singular_action?(opts)
64
+ opts[:params][:singular_route]
65
+ end
66
+ end
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,88 @@
1
+ # frozen_string_literal: true
2
+
3
+ module LinkedRails
4
+ module Model
5
+ module Serialization
6
+ extend ActiveSupport::Concern
7
+
8
+ alias read_attribute_for_serialization send
9
+
10
+ def preview_includes
11
+ self.class.preview_includes
12
+ end
13
+
14
+ def show_includes
15
+ preview_includes
16
+ end
17
+
18
+ module ClassMethods
19
+ def attributes_from_filters(filters)
20
+ filters.each_with_object({}) do |(key, value), hash|
21
+ next unless value.count == 1
22
+
23
+ attribute = predicate_mapping[key]&.key
24
+ hash[attribute] = value.first if attribute
25
+ end
26
+ end
27
+
28
+ def include_in_collection?
29
+ false
30
+ end
31
+
32
+ def input_select_property
33
+ Vocab.schema.name
34
+ end
35
+
36
+ # The associations to preload when serializing multiple records
37
+ def includes_for_serializer
38
+ {}
39
+ end
40
+
41
+ def predicate_for_key(key)
42
+ return if key.blank?
43
+
44
+ predicate_mapping.detect { |_key, value| value.key.to_sym == key.to_sym }&.first ||
45
+ predicate_for_key(try(:attribute_aliases)&.key(key.to_s))
46
+ end
47
+
48
+ def predicate_mapping
49
+ @predicate_mapping ||= Hash[attribute_mapping + reflection_mapping]
50
+ end
51
+
52
+ # The associations to include when serializing multiple records
53
+ def preview_includes
54
+ []
55
+ end
56
+
57
+ # The associations to include when serializing one record
58
+ def show_includes
59
+ preview_includes
60
+ end
61
+
62
+ private
63
+
64
+ def attribute_mapping
65
+ serializer = RDF::Serializers.serializer_for(self)
66
+ return [] if serializer.try(:attributes_to_serialize).blank?
67
+
68
+ serializer
69
+ .attributes_to_serialize
70
+ .values
71
+ .select { |attr| attr.predicate.present? }
72
+ .map { |attr| [attr.predicate, attr] }
73
+ end
74
+
75
+ def reflection_mapping
76
+ serializer = RDF::Serializers.serializer_for(self)
77
+ return [] if serializer.try(:relationships_to_serialize).blank?
78
+
79
+ serializer
80
+ .relationships_to_serialize
81
+ .values
82
+ .select { |value| value.predicate.present? }
83
+ .map { |value| [value.predicate, value] }
84
+ end
85
+ end
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module LinkedRails
4
+ module Model
5
+ module Sorting
6
+ extend ActiveSupport::Concern
7
+
8
+ included do
9
+ class_attribute :default_sortings, instance_accessor: false, instance_predicate: false
10
+ self.default_sortings = [{key: Vocab.schema.dateCreated, direction: :desc}]
11
+ end
12
+
13
+ module ClassMethods
14
+ def default_sort_column
15
+ :created_at
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,68 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'model/collections'
4
+ require_relative 'model/dirty'
5
+ require_relative 'model/enhancements'
6
+ require_relative 'model/filtering'
7
+ require_relative 'model/indexable'
8
+ require_relative 'model/iri'
9
+ require_relative 'model/iri_mapping'
10
+ require_relative 'model/serialization'
11
+ require_relative 'model/sorting'
12
+
13
+ module LinkedRails
14
+ module Model
15
+ extend ActiveSupport::Concern
16
+ include Collections
17
+ include Dirty
18
+ include Enhancements
19
+ include Filtering
20
+ include Indexable
21
+ include Iri
22
+ include IriMapping
23
+ include Serialization
24
+ include Sorting
25
+
26
+ def build_child(klass, user_context: nil)
27
+ klass.build_new(parent: self, user_context: user_context)
28
+ end
29
+
30
+ def singular_resource?
31
+ false
32
+ end
33
+
34
+ module ClassMethods
35
+ def build_new(parent: nil, user_context: nil)
36
+ new(attributes_for_new(parent: parent, user_context: user_context))
37
+ end
38
+
39
+ def form_class
40
+ @form_class ||= "#{name}Form".safe_constantize || superclass.try(:form_class)
41
+ end
42
+
43
+ def label
44
+ obj = iri.is_a?(Array) ? iri.first : iri
45
+ LinkedRails.translate(:class, :label, obj) if obj
46
+ end
47
+
48
+ def plural_label
49
+ obj = iri.is_a?(Array) ? iri.first : iri
50
+ LinkedRails.translate(:class, :plural_label, obj) if obj
51
+ end
52
+
53
+ def policy_class
54
+ @policy_class ||= "#{name}Policy".safe_constantize || superclass.try(:policy_class)
55
+ end
56
+
57
+ private
58
+
59
+ def attribute_from_filter(filter, predicate)
60
+ filter[predicate]&.first if filter
61
+ end
62
+
63
+ def attributes_for_new(_opts)
64
+ {}
65
+ end
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,93 @@
1
+ # frozen_string_literal: true
2
+
3
+ module LinkedRails
4
+ class ParamsParser
5
+ attr_reader :params, :user_context
6
+
7
+ def initialize(params)
8
+ @user_context = params[:user_context]
9
+ @params = params.is_a?(Hash) ? ActionController::Parameters.new(params) : params
10
+ end
11
+
12
+ def before_params
13
+ return @before_params if instance_variable_defined?(:@before_params)
14
+
15
+ values = permit_params(before: [])[:before]
16
+ return @before_params = nil if values.blank?
17
+
18
+ @before_params = values.map do |f|
19
+ key, value = f.split('=')
20
+ {key: RDF::URI(CGI.unescape(key)), value: value}
21
+ end
22
+ end
23
+
24
+ def collection_params
25
+ return @collection_params if instance_variable_defined?(:@collection_params)
26
+
27
+ values = permit_params(:display, :page_size, :title, :type)
28
+
29
+ filter = filter_params
30
+ values[:filter] = filter if filter
31
+
32
+ sort = sorting_params
33
+ values[:sort] = sort if sort
34
+
35
+ values[:user_context] = user_context if user_context
36
+
37
+ @collection_params = values
38
+ end
39
+
40
+ def collection_view_params
41
+ return @collection_view_params if instance_variable_defined?(:@collection_view_params)
42
+
43
+ values = permit_params(:page)
44
+
45
+ before = before_params
46
+ values[:before] = before if before
47
+
48
+ @collection_view_params = values
49
+ end
50
+
51
+ def filter_params # rubocop:disable Metrics/AbcSize
52
+ return @filter_params if instance_variable_defined?(:@filter_params)
53
+
54
+ values = permit_params(filter: [])[:filter] || permit_params(filter: {})[:filter]
55
+ return @filter_params = values if values.is_a?(Hash)
56
+ return @filter_params = {} if values.blank?
57
+
58
+ @filter_params = values.each_with_object({}) do |f, hash|
59
+ values = f.split('=')
60
+ key = RDF::URI(CGI.unescape(values.first))
61
+ hash[key] ||= []
62
+ hash[key] << CGI.unescape(values.second)
63
+ end
64
+ end
65
+
66
+ def sorting_params
67
+ return @sorting_params if instance_variable_defined?(:@sorting_params)
68
+
69
+ values = permit_params(sort: [])[:sort]
70
+ return @sorting_params = nil if values.blank?
71
+
72
+ @sorting_params = values.map do |f|
73
+ parse_filter_value(f)
74
+ end
75
+ end
76
+
77
+ private
78
+
79
+ def parse_filter_value(value)
80
+ return value if value.is_a?(Hash)
81
+
82
+ key, value = value.split('=')
83
+ {key: RDF::URI(CGI.unescape(key)), direction: value}
84
+ end
85
+
86
+ def permit_params(*keys)
87
+ params
88
+ .permit(*keys)
89
+ .to_h
90
+ .with_indifferent_access
91
+ end
92
+ end
93
+ end
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ module LinkedRails
4
+ module Policy
5
+ module AttributeConditions
6
+ extend ActiveSupport::Concern
7
+
8
+ private
9
+
10
+ def check_has_properties(properties)
11
+ properties.all? { |k, v| @record.send(k).nil? != v }
12
+ end
13
+
14
+ def check_has_values(values)
15
+ values.all? { |k, v| @record.send(k) == v }
16
+ end
17
+
18
+ def check_new_record(boolean)
19
+ @record.new_record? == boolean
20
+ end
21
+
22
+ module ClassMethods
23
+ private
24
+
25
+ def has_properties_shapes(properties) # rubocop:disable Naming/PredicateName
26
+ properties.map do |key, boolean|
27
+ SHACL::PropertyShape.new(
28
+ path: policy_class.predicate_for_key(key),
29
+ max_count: boolean ? nil : 0,
30
+ min_count: boolean ? 1 : nil
31
+ )
32
+ end
33
+ end
34
+
35
+ def has_values_shapes(values) # rubocop:disable Naming/PredicateName
36
+ values.map do |key, value|
37
+ enum = RDF::Serializers.serializer_for(policy_class).enum_options(key).try(:[], value)
38
+ santized_value = enum ? -> { enum.iri } : value
39
+
40
+ SHACL::PropertyShape.new(
41
+ path: policy_class.predicate_for_key(key),
42
+ has_value: santized_value
43
+ )
44
+ end
45
+ end
46
+
47
+ def new_record_shapes(boolean)
48
+ has_properties_shapes(created_at: !boolean)
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,189 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'active_response/controller'
4
+ require_relative 'controller/error_handling'
5
+ require_relative 'policy/attribute_conditions'
6
+
7
+ module LinkedRails
8
+ module Policy
9
+ extend ActiveSupport::Concern
10
+
11
+ included do
12
+ extend Enhanceable
13
+ include AttributeConditions
14
+
15
+ enhanceable :policy_class, :Policy
16
+ class_attribute :_permitted_attributes
17
+
18
+ attr_reader :message, :user_context, :record
19
+
20
+ def initialize(user_context, record)
21
+ @user_context = user_context
22
+ @record = record
23
+ end
24
+ end
25
+
26
+ def create_child?(klass, opts = {})
27
+ child_policy(klass, opts).create?
28
+ end
29
+
30
+ def index_children?(klass, opts = {})
31
+ child_policy(klass, opts).show?
32
+ end
33
+
34
+ def permitted_attributes
35
+ self.class.permitted_attributes
36
+ .select { |opts| attribute_permitted?(opts[:conditions]) }
37
+ .map { |opts| sanitized_attributes(opts[:attributes], opts[:options] || {}) }
38
+ .flatten
39
+ end
40
+
41
+ def show?
42
+ false
43
+ end
44
+
45
+ def update?
46
+ false
47
+ end
48
+
49
+ def create?
50
+ false
51
+ end
52
+
53
+ def destroy?
54
+ false
55
+ end
56
+
57
+ private
58
+
59
+ def attribute_permitted?(conditions)
60
+ conditions.all? do |key, opts|
61
+ raise "Unknown attribute condition #{key}" unless respond_to?("check_#{key}", true)
62
+
63
+ send(:"check_#{key}", opts)
64
+ end
65
+ end
66
+
67
+ def child_policy(klass, opts = {})
68
+ Pundit.policy(user_context, record.build_child(klass, opts.merge(user_context: user_context)))
69
+ end
70
+
71
+ def forbid_with_message(message)
72
+ @message = message
73
+ false
74
+ end
75
+
76
+ def parent_policy
77
+ return if record.try(:parent).blank?
78
+
79
+ @parent_policy ||= Pundit.policy(user_context, record.parent)
80
+ end
81
+
82
+ def policy_class
83
+ self.class.policy_class
84
+ end
85
+
86
+ def sanitize_array_attribute(attr)
87
+ [attr, attr => []]
88
+ end
89
+
90
+ def sanitize_attribute(attr)
91
+ attr
92
+ end
93
+
94
+ def sanitized_attributes(attributes, opts)
95
+ if opts[:nested]
96
+ attributes.map(&method(:sanitize_nested_attribute))
97
+ elsif opts[:array]
98
+ attributes.map(&method(:sanitize_array_attribute))
99
+ else
100
+ attributes.map(&method(:sanitize_attribute))
101
+ end
102
+ end
103
+
104
+ def sanitize_nested_attribute(key) # rubocop:disable Metrics/AbcSize
105
+ association = record.class.reflect_on_association(key)
106
+
107
+ return nil if association.blank? || (!association.polymorphic? && !association.klass)
108
+
109
+ nested_attributes =
110
+ if association.polymorphic?
111
+ Pundit.policy(user_context, record).try("#{association.name}_attributes") || []
112
+ else
113
+ child = record.build_child(association.klass, user_context: user_context)
114
+ Pundit.policy(user_context, child).permitted_attributes
115
+ end
116
+
117
+ {"#{key}_attributes" => nested_attributes + %i[id _destroy]}
118
+ end
119
+
120
+ module ClassMethods
121
+ def condition_for(attr, pass, shape_opts = {})
122
+ raise("#{attr} not permitted by #{self}") if attribute_options(attr).blank? && pass.permission_required?
123
+
124
+ alternatives = node_shapes_for(attr, **shape_opts)
125
+ if alternatives.count == 1
126
+ Condition.new(shape: alternatives.first, pass: pass)
127
+ elsif alternatives.count.positive?
128
+ Condition.new(shape: SHACL::NodeShape.new(or: alternatives), pass: pass)
129
+ else
130
+ pass
131
+ end
132
+ end
133
+
134
+ def permitted_attributes
135
+ initialize_permitted_attributes
136
+
137
+ _permitted_attributes
138
+ end
139
+
140
+ private
141
+
142
+ def attribute_options(attr)
143
+ permitted_attributes.select { |opts| opts[:attributes].include?(attr) }
144
+ end
145
+
146
+ def condition_alternatives(attr)
147
+ attribute_options(attr)
148
+ .select { |opts| opts[:conditions].present? }
149
+ .map { |opts| opts[:conditions] }
150
+ end
151
+
152
+ def node_shapes_for(attr, property: [], sh_not: [])
153
+ alternatives = condition_alternatives(attr)
154
+ alternatives = [[]] if alternatives.empty? && (property.any? || sh_not.any?)
155
+
156
+ alternatives.map do |props|
157
+ properties = property_shapes(props) + property
158
+ SHACL::NodeShape.new(property: properties, sh_not: sh_not)
159
+ end
160
+ end
161
+
162
+ def initialize_permitted_attributes
163
+ return if _permitted_attributes && method(:_permitted_attributes).owner == singleton_class
164
+
165
+ self._permitted_attributes = superclass.try(:_permitted_attributes)&.dup || []
166
+ end
167
+
168
+ def permit_attributes(attrs, conditions = {})
169
+ permitted_attributes << {attributes: attrs, conditions: conditions, options: {}}
170
+ end
171
+
172
+ def permit_array_attributes(attrs, conditions = {})
173
+ permitted_attributes << {attributes: attrs, conditions: conditions, options: {array: true}}
174
+ end
175
+
176
+ def permit_nested_attributes(attrs, conditions = {})
177
+ permitted_attributes << {attributes: attrs, conditions: conditions, options: {nested: true}}
178
+ end
179
+
180
+ def policy_class
181
+ @policy_class ||= name.sub(/Policy/, '').classify.safe_constantize
182
+ end
183
+
184
+ def property_shapes(conditions)
185
+ conditions.map { |key, options| send("#{key}_shapes", options) }.flatten.compact
186
+ end
187
+ end
188
+ end
189
+ end