linked_rails 0.0.3 → 0.0.4.pre.g14b377f91
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 +4 -4
- data/LICENSE +21 -674
- data/app/controllers/linked_rails/actions/objects_controller.rb +9 -0
- data/app/controllers/linked_rails/bulk_controller.rb +71 -21
- data/app/controllers/linked_rails/enum_values_controller.rb +0 -42
- data/app/models/linked_rails/actions/item.rb +64 -55
- data/app/models/linked_rails/actions/list.rb +6 -31
- data/app/models/linked_rails/actions/object.rb +38 -0
- data/app/models/linked_rails/collection/configuration.rb +55 -0
- data/app/models/linked_rails/collection/filter.rb +1 -1
- data/app/models/linked_rails/collection/filter_field.rb +19 -2
- data/app/models/linked_rails/collection/filter_option.rb +1 -1
- data/app/models/linked_rails/collection/filterable.rb +6 -9
- data/app/models/linked_rails/collection/infinite.rb +113 -0
- data/app/models/linked_rails/collection/infinite_view.rb +1 -90
- data/app/models/linked_rails/collection/iri.rb +47 -43
- data/app/models/linked_rails/collection/iri_mapping.rb +15 -7
- data/app/models/linked_rails/collection/paginated.rb +46 -0
- data/app/models/linked_rails/collection/paginated_view.rb +1 -33
- data/app/models/linked_rails/collection/sortable.rb +1 -9
- data/app/models/linked_rails/collection/sorting.rb +1 -1
- data/app/models/linked_rails/collection/view.rb +51 -14
- data/app/models/linked_rails/collection.rb +53 -85
- data/app/models/linked_rails/creative_work.rb +1 -1
- data/app/models/linked_rails/entry_point.rb +8 -5
- data/app/models/linked_rails/enum_value.rb +40 -2
- data/app/models/linked_rails/form/field/association_input.rb +7 -1
- data/app/models/linked_rails/form/field/file_input.rb +1 -0
- data/app/models/linked_rails/form/field/resource_field.rb +2 -0
- data/{lib/linked_rails/enhancements/indexable/model.rb → app/models/linked_rails/form/field/url_input.rb} +3 -3
- data/app/models/linked_rails/form/field.rb +37 -13
- data/app/models/linked_rails/form/field_factory.rb +48 -18
- data/app/models/linked_rails/form/group.rb +4 -6
- data/app/models/linked_rails/form/page.rb +8 -4
- data/app/models/linked_rails/form.rb +16 -21
- data/app/models/linked_rails/manifest.rb +104 -22
- data/app/models/linked_rails/menus/item.rb +10 -13
- data/app/models/linked_rails/menus/list.rb +16 -7
- data/app/models/linked_rails/ontology/base.rb +3 -1
- data/app/models/linked_rails/ontology/class.rb +3 -3
- data/app/models/linked_rails/ontology.rb +5 -5
- data/app/models/linked_rails/property_query.rb +2 -0
- data/app/models/linked_rails/sequence.rb +4 -13
- data/app/models/linked_rails/shacl/property_shape.rb +1 -1
- data/app/models/linked_rails/web_page.rb +0 -4
- data/app/models/linked_rails/web_site.rb +0 -4
- data/app/models/linked_rails/widget.rb +4 -11
- data/app/policies/linked_rails/actions/object_policy.rb +11 -0
- data/app/policies/linked_rails/collection_policy.rb +2 -2
- data/app/serializers/linked_rails/actions/item_serializer.rb +5 -5
- data/app/serializers/linked_rails/actions/object_serializer.rb +9 -0
- data/app/serializers/linked_rails/collection/filter_field_serializer.rb +3 -2
- data/app/serializers/linked_rails/collection/filter_option_serializer.rb +1 -1
- data/app/serializers/linked_rails/collection/filter_serializer.rb +1 -1
- data/app/serializers/linked_rails/collection/sorting_serializer.rb +1 -1
- data/app/serializers/linked_rails/collection/view_serializer.rb +3 -3
- data/app/serializers/linked_rails/collection_serializer.rb +8 -7
- data/app/serializers/linked_rails/condition_serializer.rb +3 -3
- data/app/serializers/linked_rails/entry_point_serializer.rb +3 -3
- data/app/serializers/linked_rails/enum_value_serializer.rb +1 -0
- data/app/serializers/linked_rails/form/field/association_input_serializer.rb +1 -0
- data/app/serializers/linked_rails/form/field/file_input_serializer.rb +11 -0
- data/app/serializers/linked_rails/form/field_serializer.rb +3 -1
- data/app/serializers/linked_rails/form/group_serializer.rb +1 -1
- data/app/serializers/linked_rails/form/page_serializer.rb +1 -1
- data/app/serializers/linked_rails/menus/item_serializer.rb +3 -3
- data/app/serializers/linked_rails/menus/list_serializer.rb +1 -1
- data/app/serializers/linked_rails/ontology_serializer.rb +2 -2
- data/app/serializers/linked_rails/property_query_serializer.rb +7 -0
- data/app/serializers/linked_rails/sequence_serializer.rb +2 -5
- data/app/serializers/linked_rails/shacl/node_shape_serializer.rb +1 -1
- data/app/serializers/linked_rails/shacl/property_shape_serializer.rb +1 -1
- data/app/serializers/linked_rails/shacl/shape_serializer.rb +5 -5
- data/app/serializers/linked_rails/web_page_serializer.rb +3 -3
- data/app/serializers/linked_rails/web_site_serializer.rb +1 -1
- data/app/serializers/linked_rails/widget_serializer.rb +3 -3
- data/lib/generators/linked_rails/install/install_generator.rb +5 -8
- data/lib/generators/linked_rails/install/templates/README +2 -0
- data/lib/generators/linked_rails/install/templates/app_menu_list.rb +36 -7
- data/lib/generators/linked_rails/install/templates/application_menu_list.rb +40 -1
- data/lib/generators/linked_rails/install/templates/initializer.rb +1 -2
- data/lib/generators/linked_rails/install/templates/locales.yml +14 -0
- data/lib/generators/linked_rails/install/templates/rdf_serializers_initializer.rb +1 -1
- data/lib/generators/linked_rails/install/templates/vocab.rb +1 -0
- data/lib/generators/linked_rails/install/templates/vocab.yml +2 -2
- data/lib/generators/linked_rails/model/model_generator.rb +0 -1
- data/lib/generators/linked_rails/model/templates/controller.rb.tt +5 -1
- data/lib/generators/linked_rails/model/templates/form.rb.tt +3 -0
- data/lib/generators/linked_rails/model/templates/menu_list.rb.tt +15 -0
- data/lib/generators/linked_rails/model/templates/policy.rb.tt +13 -0
- data/lib/generators/linked_rails/model/templates/serializer.rb.tt +5 -1
- data/lib/linked_rails/active_response/controller/collections.rb +1 -1
- data/lib/linked_rails/active_response/controller/crud_defaults.rb +4 -4
- data/lib/linked_rails/active_response/controller/params.rb +10 -10
- data/lib/linked_rails/active_response/controller.rb +8 -18
- data/lib/linked_rails/active_response/responders/rdf.rb +19 -10
- data/lib/linked_rails/callable_variable.rb +1 -1
- data/lib/linked_rails/collection_params_parser.rb +93 -0
- data/lib/linked_rails/controller/actionable.rb +121 -0
- data/lib/linked_rails/controller/authorization.rb +6 -0
- data/lib/linked_rails/controller/default_actions/create.rb +52 -0
- data/lib/linked_rails/controller/default_actions/destroy.rb +42 -0
- data/lib/linked_rails/controller/default_actions/update.rb +43 -0
- data/lib/linked_rails/controller/delta.rb +58 -0
- data/lib/linked_rails/controller/error_handling.rb +10 -10
- data/lib/linked_rails/controller/rendering.rb +48 -0
- data/lib/linked_rails/controller.rb +24 -4
- data/lib/linked_rails/enhancements/creatable/controller.rb +1 -1
- data/lib/linked_rails/enhancements/destroyable/controller.rb +1 -1
- data/lib/linked_rails/enhancements/updatable/controller.rb +1 -1
- data/lib/linked_rails/enhancements.rb +0 -16
- data/lib/linked_rails/helpers/delta_helper.rb +26 -57
- data/lib/linked_rails/helpers/ontola_actions_helper.rb +2 -2
- data/lib/linked_rails/helpers/resource_helper.rb +4 -2
- data/lib/linked_rails/iri_mapper.rb +17 -39
- data/lib/linked_rails/middleware/error_handling.rb +51 -0
- data/lib/linked_rails/middleware/linked_data_params.rb +30 -151
- data/lib/linked_rails/model/actionable.rb +68 -0
- data/lib/linked_rails/model/collections.rb +201 -39
- data/lib/linked_rails/model/dirty.rb +6 -18
- data/lib/linked_rails/model/enhancements.rb +1 -6
- data/lib/linked_rails/model/filtering.rb +4 -6
- data/lib/linked_rails/model/indexable.rb +6 -16
- data/lib/linked_rails/model/iri.rb +28 -19
- data/lib/linked_rails/model/iri_mapping.rb +37 -8
- data/lib/linked_rails/model/menuable.rb +28 -0
- data/lib/linked_rails/model/serialization.rb +2 -15
- data/lib/linked_rails/model/singularable.rb +57 -0
- data/lib/linked_rails/model/sorting.rb +0 -5
- data/lib/linked_rails/model/tables.rb +26 -0
- data/lib/linked_rails/model.rb +17 -7
- data/lib/linked_rails/params_parser.rb +131 -54
- data/lib/linked_rails/policy/attribute_conditions.rb +2 -2
- data/lib/linked_rails/policy.rb +40 -46
- data/lib/linked_rails/railtie.rb +11 -0
- data/lib/linked_rails/rdf_error.rb +2 -2
- data/lib/linked_rails/renderers.rb +1 -0
- data/lib/linked_rails/routes.rb +38 -22
- data/lib/linked_rails/serializer/actionable.rb +27 -0
- data/lib/linked_rails/serializer/menuable.rb +31 -0
- data/lib/linked_rails/serializer/singularable.rb +26 -0
- data/lib/linked_rails/serializer.rb +28 -11
- data/lib/linked_rails/test_methods.rb +114 -0
- data/lib/linked_rails/translate.rb +31 -9
- data/lib/linked_rails/types/iri_type.rb +37 -0
- data/lib/linked_rails/uri_template.rb +30 -0
- data/lib/linked_rails/version.rb +1 -1
- data/lib/linked_rails/vocab.rb +9 -0
- data/lib/linked_rails.rb +30 -13
- data/lib/rails/welcome_controller.rb +3 -3
- data/lib/rdf/list.rb +9 -0
- data/lib/rdf/query_fix.rb +15 -0
- metadata +71 -33
- data/app/models/linked_rails/actions/default_actions/create.rb +0 -60
- data/app/models/linked_rails/actions/default_actions/destroy.rb +0 -45
- data/app/models/linked_rails/actions/default_actions/update.rb +0 -50
- data/app/models/linked_rails/actions/default_actions.rb +0 -17
- data/lib/generators/linked_rails/install/templates/application_action_list.rb +0 -3
- data/lib/generators/linked_rails/model/templates/action_list.rb.tt +0 -6
- data/lib/linked_rails/enhancements/actionable/model.rb +0 -71
- data/lib/linked_rails/enhancements/actionable/serializer.rb +0 -25
- data/lib/linked_rails/enhancements/creatable/action.rb +0 -15
- data/lib/linked_rails/enhancements/destroyable/action.rb +0 -15
- data/lib/linked_rails/enhancements/destroyable/routing.rb +0 -19
- data/lib/linked_rails/enhancements/menuable/model.rb +0 -36
- data/lib/linked_rails/enhancements/menuable/serializer.rb +0 -33
- data/lib/linked_rails/enhancements/route_concerns.rb +0 -56
- data/lib/linked_rails/enhancements/singularable/controller.rb +0 -43
- data/lib/linked_rails/enhancements/singularable/model.rb +0 -47
- data/lib/linked_rails/enhancements/singularable/serializer.rb +0 -28
- data/lib/linked_rails/enhancements/tableable/model.rb +0 -28
- data/lib/linked_rails/enhancements/updatable/action.rb +0 -15
- data/lib/linked_rails/enhancements/updatable/routing.rb +0 -20
@@ -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
|
7
|
+
class LinkedDataParams
|
8
|
+
include ::Empathy::EmpJson::Helpers::Slices
|
9
|
+
include ::Empathy::EmpJson::Helpers::Parsing
|
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
|
-
|
18
|
+
params_from_slice(req)
|
14
19
|
|
15
20
|
@app.call(env)
|
16
21
|
end
|
17
22
|
|
18
23
|
private
|
19
24
|
|
20
|
-
def
|
21
|
-
|
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
|
60
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
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
|
119
|
-
|
54
|
+
def params_from_slice(request)
|
55
|
+
slice = slice_from_request(request)
|
120
56
|
|
121
|
-
return unless
|
57
|
+
return unless slice
|
122
58
|
|
123
|
-
request.
|
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,
|
128
|
-
update_target_params(request,
|
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,
|
209
|
-
actor =
|
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
|
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,
|
93
|
+
def update_target_params(request, slice, target_class)
|
217
94
|
key = target_class.to_s.demodulize.underscore
|
218
|
-
|
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
|
@@ -5,8 +5,181 @@ module LinkedRails
|
|
5
5
|
module Collections
|
6
6
|
extend ActiveSupport::Concern
|
7
7
|
|
8
|
+
COLLECTION_CUSTOMIZABLE_OPTIONS = {
|
9
|
+
# display [Sym] The default display type.
|
10
|
+
# Choose between :grid, :settingsTable, :table, :card, :default
|
11
|
+
display: :default,
|
12
|
+
# grid_max_columns [Integer] The default amount of columns to use in a grid.
|
13
|
+
grid_max_columns: 3,
|
14
|
+
# page_size [Integer] The default page size.
|
15
|
+
page_size: 20,
|
16
|
+
# table_type [Sym] The columns to use in the table.
|
17
|
+
table_type: lambda {
|
18
|
+
case display&.to_sym
|
19
|
+
when :table
|
20
|
+
:default
|
21
|
+
when :settingsTable
|
22
|
+
:settings
|
23
|
+
end
|
24
|
+
},
|
25
|
+
# title String The default title.
|
26
|
+
title: -> { title_from_translation },
|
27
|
+
# type [Sym] The default pagination type.
|
28
|
+
# Choose between :paginated, :infinite.
|
29
|
+
type: :paginated
|
30
|
+
}.freeze
|
31
|
+
COLLECTION_STATIC_OPTIONS = {
|
32
|
+
# association [Sym] The association of the collection items.
|
33
|
+
association: nil,
|
34
|
+
# association_base [Scope, Array] The items of the collection.
|
35
|
+
association_base: -> { apply_scope(sorted_association(filtered_association)) },
|
36
|
+
# association_class [Class] The class of the collection items.
|
37
|
+
association_class: nil,
|
38
|
+
# association_scope [Sym] The scope applied to the collection.
|
39
|
+
association_scope: nil,
|
40
|
+
# call_to_action [String] The label shown as call to action.
|
41
|
+
call_to_action: nil,
|
42
|
+
# collection_class [Class] The base class of the collection.
|
43
|
+
# If you want to use a class other than LinkedRails.collection_class.
|
44
|
+
collection_class: nil,
|
45
|
+
# collection_class [Hash] The default filters applied to the collection.
|
46
|
+
default_filters: {},
|
47
|
+
# collection_class [Array<Hash>] The default sortings applied to the collection.
|
48
|
+
default_sortings: [{key: Vocab.schema.dateCreated, direction: :desc}],
|
49
|
+
# include_members [Boolean] Whether to include the members of this collection.
|
50
|
+
include_members: false,
|
51
|
+
# iri_template_keys [Array<Sym>] Custom query keys for the iri template
|
52
|
+
iri_template_keys: [],
|
53
|
+
# joins [Array<Sym>, Sym] The associations to join
|
54
|
+
joins: nil,
|
55
|
+
# parent [Instance] The default parent of a collection.
|
56
|
+
parent: nil,
|
57
|
+
# parent_iri [Array<String>] The iri elements of the parent
|
58
|
+
parent_iri: -> { parent&.iri_elements },
|
59
|
+
# part_of [Instance] The record to serialize as isPartOf
|
60
|
+
part_of: -> { parent },
|
61
|
+
# policy_scope [Scope] The policy scope class to be used for scoping
|
62
|
+
# Set to false to skip scoping
|
63
|
+
policy_scope: -> { policy ? policy::Scope : Pundit::PolicyFinder.new(filtered_association).scope! },
|
64
|
+
# route_key [Symbol, String] The route key for the association
|
65
|
+
route_key: nil,
|
66
|
+
# view [IRI] The view to use for rendering the members
|
67
|
+
view: nil
|
68
|
+
}.freeze
|
69
|
+
COLLECTION_OPTIONS = COLLECTION_CUSTOMIZABLE_OPTIONS.merge(COLLECTION_STATIC_OPTIONS)
|
70
|
+
|
71
|
+
module ClassMethods
|
72
|
+
def collection_iri(**opts)
|
73
|
+
LinkedRails.iri(path: collection_root_relative_iri(**opts))
|
74
|
+
end
|
75
|
+
|
76
|
+
# Sets the defaults for all collections for this class.
|
77
|
+
# Can be overridden by #with_collection, called from associated models,
|
78
|
+
# or by passing parameters in an iri.
|
79
|
+
# @param [Hash] options
|
80
|
+
def collection_options(**options)
|
81
|
+
initialize_default_collection_opts
|
82
|
+
|
83
|
+
options.each do |key, value|
|
84
|
+
raise("Invalid key passed to collection_options: #{key}") unless valid_collection_option?(key)
|
85
|
+
|
86
|
+
_default_collection_opts[key] = value
|
87
|
+
end
|
88
|
+
_default_collection_opts[:iri_template] = _default_collection_opts[:collection_class].generate_iri_template(
|
89
|
+
_default_collection_opts[:iri_template_keys]
|
90
|
+
)
|
91
|
+
_default_collection_opts
|
92
|
+
end
|
93
|
+
|
94
|
+
def collection_root_relative_iri(**opts)
|
95
|
+
opts[:filter] = LinkedRails.collection_class.filter_iri_opts(opts[:filter]) if opts.key?(:filter)
|
96
|
+
opts[:route_key] = collection_route_key
|
97
|
+
default_collection_option(:iri_template).expand(**opts)
|
98
|
+
end
|
99
|
+
|
100
|
+
def collection_route_key
|
101
|
+
default_collection_option(:route_key) || route_key
|
102
|
+
end
|
103
|
+
|
104
|
+
def default_collection_options
|
105
|
+
initialize_default_collection_opts
|
106
|
+
|
107
|
+
_default_collection_opts
|
108
|
+
end
|
109
|
+
|
110
|
+
def default_collection_option(key)
|
111
|
+
default_collection_options[key]
|
112
|
+
end
|
113
|
+
|
114
|
+
# Defines a collection to be used in {collection_for}
|
115
|
+
# @see Ldable#collection_for
|
116
|
+
# @note Adds a instance_method <name>_collection
|
117
|
+
# @param [Hash] name as to be used in {collection_for}
|
118
|
+
# @param [Hash] options See COLLECTION_OPTIONS
|
119
|
+
# @return [Collection]
|
120
|
+
def with_collection(name, **options) # rubocop:disable Metrics/AbcSize
|
121
|
+
options[:association] ||= name.to_sym
|
122
|
+
options[:association_class] ||= name.to_s.classify.constantize
|
123
|
+
merged_options = options[:association_class].default_collection_options.merge(options)
|
124
|
+
merged_options[:iri_template] = merged_options[:collection_class].generate_iri_template(
|
125
|
+
merged_options[:iri_template_keys]
|
126
|
+
)
|
127
|
+
collections_add(name: name, options: merged_options)
|
128
|
+
|
129
|
+
define_method "#{name.to_s.singularize}_collection" do |opts = {}|
|
130
|
+
collection_for(name, **opts)
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
private
|
135
|
+
|
136
|
+
def collections_add(opts)
|
137
|
+
initialize_collections
|
138
|
+
collections.delete_if { |c| c[:name] == opts[:name] }
|
139
|
+
opts[:options] = sanitized_collection_options(opts[:options])
|
140
|
+
collections.append(opts)
|
141
|
+
end
|
142
|
+
|
143
|
+
def initialize_collections
|
144
|
+
return if collections && method(:collections).owner == singleton_class
|
145
|
+
|
146
|
+
self.collections = superclass.try(:collections)&.dup || []
|
147
|
+
end
|
148
|
+
|
149
|
+
def initialize_default_collection_opts # rubocop:disable Metrics/AbcSize
|
150
|
+
return if _default_collection_opts && method(:_default_collection_opts).owner == singleton_class
|
151
|
+
|
152
|
+
self._default_collection_opts = (superclass.try(:_default_collection_opts) || COLLECTION_OPTIONS).dup
|
153
|
+
|
154
|
+
_default_collection_opts[:collection_class] ||= LinkedRails.collection_class
|
155
|
+
_default_collection_opts[:association_class] = self
|
156
|
+
_default_collection_opts[:iri_template] = _default_collection_opts[:collection_class].generate_iri_template(
|
157
|
+
_default_collection_opts[:iri_template_keys]
|
158
|
+
)
|
159
|
+
|
160
|
+
_default_collection_opts
|
161
|
+
end
|
162
|
+
|
163
|
+
def sanitized_collection_options(opts)
|
164
|
+
opts.each_with_object(HashWithIndifferentAccess.new) do |(key, value), hash|
|
165
|
+
raise("Invalid key passed to with_collection: #{key}") unless valid_collection_option?(key.to_sym)
|
166
|
+
|
167
|
+
hash_key = COLLECTION_CUSTOMIZABLE_OPTIONS.key?(key.to_sym) ? "default_#{key}" : key
|
168
|
+
|
169
|
+
hash[hash_key] = value
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
def valid_collection_option?(key)
|
174
|
+
COLLECTION_OPTIONS.key?(key) || key == :iri_template
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
8
178
|
included do
|
9
179
|
class_attribute :collections
|
180
|
+
class_attribute :_default_collection_opts,
|
181
|
+
instance_accessor: false,
|
182
|
+
instance_predicate: false
|
10
183
|
end
|
11
184
|
|
12
185
|
# Initialises a {Collection} for one of the collections defined by {has_collection}
|
@@ -18,19 +191,42 @@ module LinkedRails
|
|
18
191
|
# @param [ApplicationRecord] part_of
|
19
192
|
# @param [Hash] opts Additional options to be passed to the collection.
|
20
193
|
# @return [Collection]
|
21
|
-
def collection_for(name, instance_opts
|
22
|
-
collection_opts =
|
194
|
+
def collection_for(name, **instance_opts)
|
195
|
+
collection_opts = collection_options_for(name).dup
|
23
196
|
return if collection_opts.blank?
|
24
197
|
|
25
198
|
collection_opts[:name] = name
|
26
199
|
collection_opts[:parent] = self
|
27
|
-
|
28
|
-
|
200
|
+
collection_class =
|
201
|
+
collection_opts.delete(:collection_class) ||
|
202
|
+
collection_opts[:association_class].default_collection_option(:collection_class) ||
|
203
|
+
LinkedRails.collection_class
|
29
204
|
collection_class.collection_or_view(collection_opts, instance_opts)
|
30
205
|
end
|
31
206
|
|
207
|
+
def collection_iri(collection, **opts)
|
208
|
+
LinkedRails.iri(path: collection_root_relative_iri(collection, **opts))
|
209
|
+
end
|
210
|
+
|
211
|
+
def collection_options_for(name)
|
212
|
+
opts = collections.detect { |c| c[:name] == name.to_sym }
|
213
|
+
raise("Collection #{name} not found for #{self}") unless opts
|
214
|
+
|
215
|
+
opts[:options] || {}
|
216
|
+
end
|
217
|
+
|
218
|
+
def collection_root_relative_iri(collection, **opts)
|
219
|
+
collection_opts = collection_options_for(collection).dup
|
220
|
+
template = collection_opts[:iri_template]
|
221
|
+
klass = collection_opts[:association_class]
|
222
|
+
opts[:route_key] = collection_opts[:route_key] || klass.collection_route_key
|
223
|
+
opts[:parent_iri] = iri_elements
|
224
|
+
|
225
|
+
template.expand(**opts).to_s
|
226
|
+
end
|
227
|
+
|
32
228
|
def parent_collections(user_context)
|
33
|
-
return [] if try(:parent).try(:collections).blank?
|
229
|
+
return [self.class.root_collection(user_context: user_context)] if try(:parent).try(:collections).blank?
|
34
230
|
|
35
231
|
parent_collections_for(parent, user_context)
|
36
232
|
end
|
@@ -43,40 +239,6 @@ module LinkedRails
|
|
43
239
|
.select { |collection| is_a?(collection[:options][:association_class]) }
|
44
240
|
.map { |collection| parent.collection_for(collection[:name], user_context: user_context) }
|
45
241
|
end
|
46
|
-
|
47
|
-
module ClassMethods
|
48
|
-
def collections_add(opts)
|
49
|
-
initialize_collections
|
50
|
-
collections.delete_if { |c| c[:name] == opts[:name] }
|
51
|
-
collections.append(opts)
|
52
|
-
end
|
53
|
-
|
54
|
-
def initialize_collections
|
55
|
-
return if collections && method(:collections).owner == singleton_class
|
56
|
-
|
57
|
-
self.collections = superclass.try(:collections)&.dup || []
|
58
|
-
end
|
59
|
-
|
60
|
-
# Defines a collection to be used in {collection_for}
|
61
|
-
# @see Ldable#collection_for
|
62
|
-
# @note Adds a instance_method <name>_collection
|
63
|
-
# @param [Hash] name as to be used in {collection_for}
|
64
|
-
# @param [Hash] options
|
65
|
-
# @option options [Sym] association the name of the association
|
66
|
-
# @option options [Class] association_class the class of the association
|
67
|
-
# @option options [Sym] joins the associations to join
|
68
|
-
# @return [Collection]
|
69
|
-
def with_collection(name, options = {})
|
70
|
-
options[:association] ||= name.to_sym
|
71
|
-
options[:association_class] ||= name.to_s.classify.constantize
|
72
|
-
|
73
|
-
collections_add(name: name, options: options)
|
74
|
-
|
75
|
-
define_method "#{name.to_s.singularize}_collection" do |opts = {}|
|
76
|
-
collection_for(name, opts)
|
77
|
-
end
|
78
|
-
end
|
79
|
-
end
|
80
242
|
end
|
81
243
|
end
|
82
244
|
end
|
@@ -22,40 +22,28 @@ module LinkedRails
|
|
22
22
|
end
|
23
23
|
end
|
24
24
|
|
25
|
-
def
|
26
|
-
serializer_class = RDF::Serializers.serializer_for(self)
|
27
|
-
return {} unless respond_to?(:previous_changes) && serializer_class
|
28
|
-
|
29
|
-
Hash[
|
30
|
-
previous_changes
|
31
|
-
.map { |k, v| [serializer_class.attributes_to_serialize[k.to_sym]&.predicate, v] }
|
32
|
-
.select { |k, _v| k.present? }
|
33
|
-
]
|
34
|
-
end
|
35
|
-
|
36
|
-
def previously_changed_relations
|
25
|
+
def previously_changed_relations(inverted = nil)
|
37
26
|
serializer_class = RDF::Serializers.serializer_for(self)
|
38
27
|
return {} unless serializer_class.try(:relationships_to_serialize)
|
39
28
|
|
40
29
|
serializer_class.relationships_to_serialize.select do |key, _value|
|
41
30
|
if respond_to?(key)
|
42
31
|
association_key = key.to_s.ends_with?('_collection') ? send(key).association : key
|
43
|
-
association_has_destructed?(association_key) || association_changed?(association_key)
|
32
|
+
association_has_destructed?(association_key) || association_changed?(association_key, inverted)
|
44
33
|
end
|
45
34
|
end.with_indifferent_access
|
46
35
|
end
|
47
36
|
|
48
37
|
private
|
49
38
|
|
50
|
-
def association_changed?(association) # rubocop:disable Metrics/AbcSize
|
39
|
+
def association_changed?(association, inverted) # rubocop:disable Metrics/AbcSize
|
51
40
|
ids_method = "#{association.to_s.singularize}_ids"
|
52
41
|
return true if previous_changes.include?("#{association}_id") || previous_changes.include?(ids_method)
|
53
42
|
return false unless try(:association_cached?, association)
|
43
|
+
records = self.class.reflect_on_association(association).collection? ? send(association) : [send(association)]
|
54
44
|
|
55
|
-
|
56
|
-
|
57
|
-
else
|
58
|
-
send(association)&.previous_changes&.present?
|
45
|
+
records.reject { |a| a == inverted }.any? do |a|
|
46
|
+
a&.previous_changes.present? || a&.previously_changed_relations(self).present?
|
59
47
|
end
|
60
48
|
end
|
61
49
|
|
@@ -16,14 +16,13 @@ module LinkedRails
|
|
16
16
|
|
17
17
|
module ClassMethods
|
18
18
|
# Adds an enhancement to a model and includes the Model module.
|
19
|
-
def enhance(enhancement, opts
|
19
|
+
def enhance(enhancement, **opts)
|
20
20
|
initialize_enhancements
|
21
21
|
already_included = enhanced_with?(enhancement)
|
22
22
|
|
23
23
|
self.enhancements[enhancement] = opts
|
24
24
|
return if already_included
|
25
25
|
|
26
|
-
enhance_routing(enhancement) if enhancement.const_defined?(:Routing) && enhanced_with?(enhancement, :Routing)
|
27
26
|
include enhancement::Model if enhancement.const_defined?(:Model) && enhanced_with?(enhancement, :Model)
|
28
27
|
end
|
29
28
|
|
@@ -46,10 +45,6 @@ module LinkedRails
|
|
46
45
|
|
47
46
|
private
|
48
47
|
|
49
|
-
def enhance_routing(enhancement)
|
50
|
-
LinkedRails::Enhancements::RouteConcerns.add_concern(enhancement)
|
51
|
-
end
|
52
|
-
|
53
48
|
def initialize_enhancements
|
54
49
|
return if enhancements && method(:enhancements).owner == singleton_class
|
55
50
|
|