linked_rails 0.0.4.pre.g83aa52ab3 → 0.0.4.pre.g222bfeeca

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 (32) hide show
  1. checksums.yaml +4 -4
  2. data/app/controllers/linked_rails/bulk_controller.rb +37 -2
  3. data/app/models/linked_rails/actions/item.rb +1 -1
  4. data/app/models/linked_rails/collection/view.rb +9 -8
  5. data/app/models/linked_rails/form/field/file_input.rb +5 -1
  6. data/app/models/linked_rails/form/field_factory.rb +1 -7
  7. data/app/models/linked_rails/form.rb +1 -0
  8. data/app/models/linked_rails/manifest.rb +94 -20
  9. data/app/models/linked_rails/menus/item.rb +1 -1
  10. data/app/models/linked_rails/menus/list.rb +15 -4
  11. data/app/models/linked_rails/ontology.rb +1 -0
  12. data/app/policies/linked_rails/collection_policy.rb +1 -0
  13. data/app/policies/linked_rails/form_policy.rb +13 -0
  14. data/app/policies/linked_rails/ontology_policy.rb +13 -0
  15. data/app/serializers/linked_rails/collection/view_serializer.rb +1 -0
  16. data/app/workers/linked_rails/invalidation_stream_worker.rb +16 -0
  17. data/lib/generators/linked_rails/install/templates/locales.yml +2 -0
  18. data/lib/linked_rails/controller/error_handling.rb +5 -0
  19. data/lib/linked_rails/errors/forbidden.rb +37 -0
  20. data/lib/linked_rails/errors.rb +3 -0
  21. data/lib/linked_rails/helpers/resource_helper.rb +11 -1
  22. data/lib/linked_rails/iri_mapper.rb +1 -1
  23. data/lib/linked_rails/model/cacheable.rb +45 -0
  24. data/lib/linked_rails/model/collections.rb +3 -1
  25. data/lib/linked_rails/model/iri.rb +1 -0
  26. data/lib/linked_rails/model.rb +1 -0
  27. data/lib/linked_rails/params_parser.rb +14 -4
  28. data/lib/linked_rails/policy.rb +4 -0
  29. data/lib/linked_rails/storage.rb +32 -0
  30. data/lib/linked_rails/url.rb +11 -0
  31. data/lib/linked_rails.rb +11 -2
  32. metadata +24 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: bc8a8c53b8422c4f81be3dbb64c8488b6d80a659b0fb585f252c6d66842f240c
4
- data.tar.gz: 83172f0d901f0ad055186902ea9f9b5e9500b16bfa2071cad5533127765b57ec
3
+ metadata.gz: bd9b4e8c03eaa18c8caf50b78d32ce0308439d18dcfe4ad712b029e6110afba2
4
+ data.tar.gz: 47b2df4167fb6731b4adbbd5aa49174c882ad9fa5e3031319f3a853ea29caa08
5
5
  SHA512:
6
- metadata.gz: 0c00be6915256007188a56ed84b1b485dd5c56fcf6e310ab525d66488f26498b0127abb4bf39f6e6b206192d0eed9410b891ba21407794653349bbcde95fc95b
7
- data.tar.gz: d1488f86b20714eb98e4cbfdf9c8a6765e84be907dae2573274c02adc22047f0a6c206dc10ce2086823ab42028517d7f440e65b121aee53e35ba84b1e57f72b6
6
+ metadata.gz: 6720e547504da1983928e5d27dbcbfda727aaf5f69467dfa1a9e0b056ea1b678243d4e26484db3939069eedd9bed415a086e85f003deeb72983534fde939e79e
7
+ data.tar.gz: 07cbdab89b3ef4184ff48e8bafe5d4c50da244d934da82ba71c86aaa518ed9efc47820de8d097524659f0f8b49943b34f1edb8b102179fe2e622d8507ce25b85
@@ -21,8 +21,11 @@ module LinkedRails
21
21
  return response_for_wrong_host(opts) if wrong_host?(opts[:iri])
22
22
 
23
23
  include = opts[:include].to_s == 'true'
24
+ resource = LinkedRails.iri_mapper.resource_from_iri(request_path_to_url(opts[:iri]), user_context)
24
25
 
25
- response_from_request(include, RDF::URI(opts[:iri]))
26
+ return response_from_request(include, RDF::URI(opts[:iri])) if resource.blank?
27
+
28
+ response_from_resource(include, opts[:iri], resource)
26
29
  rescue StandardError => e
27
30
  handle_resource_error(opts, e)
28
31
  end
@@ -84,10 +87,19 @@ module LinkedRails
84
87
  false
85
88
  end
86
89
 
90
+ def resource_cache_control(cacheable, status, resource_policy)
91
+ return :private unless status == 200 && cacheable
92
+ return 'no-cache' unless resource_policy.try(:public_resource?)
93
+
94
+ :public
95
+ end
96
+
87
97
  def resource_params(param)
88
98
  params = param.permit(:include, :iri)
89
99
  params[:iri] = URI(params[:iri])
90
100
  params
101
+ rescue URI::InvalidURIError
102
+ params.except(:iri)
91
103
  end
92
104
 
93
105
  def resource_response_body(iri, rack_body, status)
@@ -131,6 +143,12 @@ module LinkedRails
131
143
  }.merge(opts)
132
144
  end
133
145
 
146
+ def resource_status(resource_policy)
147
+ raise(LinkedRails::Errors::Forbidden.new(query: :show?)) unless resource_policy.show?
148
+
149
+ 200
150
+ end
151
+
134
152
  def response_for_wrong_host(opts)
135
153
  iri = opts[:iri]
136
154
  term = term_from_vocab(iri)
@@ -139,6 +157,23 @@ module LinkedRails
139
157
  ontology_term_response(iri, term, opts[:include])
140
158
  end
141
159
 
160
+ def response_from_resource(include, iri, resource)
161
+ resource_policy = policy(resource)
162
+ status = resource_status(resource_policy)
163
+
164
+ resource_response(
165
+ iri,
166
+ body: response_from_resource_body(include, iri, resource, status),
167
+ cache: resource_cache_control(resource.try(:cacheable?), status, resource_policy),
168
+ language: I18n.locale,
169
+ status: status
170
+ )
171
+ end
172
+
173
+ def response_from_resource_body(include, _iri, resource, status)
174
+ include && status == 200 ? resource_body(resource) : nil
175
+ end
176
+
142
177
  def term_from_vocab(iri)
143
178
  vocab = Vocab.for(iri)
144
179
  tag = iri.to_s.split(vocab.to_s).last
@@ -216,7 +251,7 @@ module LinkedRails
216
251
 
217
252
  def sanitized_relative_path(iri) # rubocop:disable Metrics/AbcSize
218
253
  iri.path = "#{iri.path}/" unless iri.path&.ends_with?('/')
219
- uri = URI(LinkedRails.iri.path.present? ? iri.to_s.split("#{LinkedRails.iri.path}/").last : iri)
254
+ uri = URI(LinkedRails.iri.path.chomp('/').present? ? iri.to_s.split("#{LinkedRails.iri.path}/").last : iri)
220
255
 
221
256
  [uri.path, uri.query].compact.join('?')
222
257
  end
@@ -187,7 +187,7 @@ module LinkedRails
187
187
 
188
188
  def target_url_fallback # rubocop:disable Metrics/AbcSize
189
189
  base = (resource.try(:singular_resource?) ? resource.singular_iri : resource.iri).dup
190
- base.path = "#{base.path}/#{target_path}" if target_path.present?
190
+ base.path = "#{base.path.chomp('/')}/#{target_path}" if target_path.present?
191
191
  base.query = Rack::Utils.parse_nested_query(base.query).merge(target_query).to_param if target_query.present?
192
192
  base
193
193
  end
@@ -8,8 +8,9 @@ module LinkedRails
8
8
  include LinkedRails::Model
9
9
 
10
10
  attr_accessor :collection, :filter
11
- delegate :apply_scope, :association_base, :association_class, :default_page_size, :display, :include_members,
12
- :parent, :policy, :total_page_count, :unfiltered_collection, :user_context, to: :collection
11
+ delegate :apply_scope, :association_base, :association_class, :child_resource, :default_page_size, :display,
12
+ :include_members,:parent, :policy, :total_page_count, :unfiltered_collection, :user_context,
13
+ to: :collection
13
14
  delegate :count, to: :members
14
15
 
15
16
  alias id iri
@@ -42,7 +43,7 @@ module LinkedRails
42
43
  end
43
44
 
44
45
  def preview_includes
45
- include_members ? {member_sequence: :members} : %i[member_sequence]
46
+ include_members ? {members: {}, member_sequence: :members} : %i[member_sequence]
46
47
  end
47
48
 
48
49
  def title
@@ -75,19 +76,19 @@ module LinkedRails
75
76
  end
76
77
 
77
78
  def iris_from_scope?
78
- members_query.is_a?(ActiveRecord::Relation) && !polymorphic_collection?
79
+ members_query.is_a?(ActiveRecord::Relation) && !polymorphic_collection?(members_query.klass)
79
80
  end
80
81
 
81
82
  def members_array
82
83
  members_query.to_a
83
84
  end
84
85
 
85
- def polymorphic_collection?
86
- column = association_class.inheritance_column
87
- polymorphic = association_class.columns_hash.include?(column)
86
+ def polymorphic_collection?(klass)
87
+ column = klass.inheritance_column
88
+ polymorphic = klass.columns_hash.include?(column)
88
89
  return false unless polymorphic
89
90
 
90
- return true if association_class.descends_from_active_record?
91
+ return true if klass.descends_from_active_record?
91
92
 
92
93
  members_query.where_values_hash.include?(column) &&
93
94
  members_query.where_values_hash[column] != association_class.to_s
@@ -4,7 +4,11 @@ module LinkedRails
4
4
  class Form
5
5
  class Field
6
6
  class FileInput < Field
7
- attr_accessor :max_size
7
+ attr_writer :max_size
8
+
9
+ def max_size
10
+ @max_size.respond_to?(:call) ? @max_size.call : @max_size
11
+ end
8
12
  end
9
13
  end
10
14
  end
@@ -52,13 +52,7 @@ module LinkedRails
52
52
  private
53
53
 
54
54
  def attr_column(name)
55
- column_model =
56
- if model_class.is_delegated_attribute?(name)
57
- model_class.class_for_delegated_attribute(name)
58
- else
59
- model_class
60
- end
61
- column_model.column_for_attribute(name)
55
+ model_class.column_for_attribute(name)
62
56
  end
63
57
 
64
58
  def attr_to_datatype # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength
@@ -5,6 +5,7 @@ require 'pundit'
5
5
  module LinkedRails
6
6
  class Form # rubocop:disable Metrics/ClassLength
7
7
  include LinkedRails::Model
8
+ include LinkedRails::Model::Cacheable
8
9
 
9
10
  class_attribute :abstract_form, :pages, :model_class
10
11
 
@@ -5,6 +5,27 @@ module LinkedRails
5
5
  include ActiveModel::Model
6
6
  include LinkedRails::Model
7
7
 
8
+ def save
9
+ Storage.hset(
10
+ :persistent,
11
+ :manifest,
12
+ URL.as_href(LinkedRails.iri) => web_manifest.to_json
13
+ )
14
+ end
15
+
16
+ def web_manifest
17
+ web_manifest_base.merge(
18
+ ontola: web_manifest_ontola_section,
19
+ serviceworker: web_manifest_sw_section
20
+ )
21
+ end
22
+
23
+ private
24
+
25
+ def allowed_external_sources
26
+ []
27
+ end
28
+
8
29
  def app_name
9
30
  Rails.application.railtie_name.chomp('_application').humanize
10
31
  end
@@ -17,8 +38,27 @@ module LinkedRails
17
38
  '#eef0f2'
18
39
  end
19
40
 
41
+ def blob_preview_iri
42
+ return unless ActiveStorage::Blob.service.present?
43
+
44
+ "#{LinkedRails.iri(path: 'rails/active_storage/blobs/redirect')}/{signed_id}/preview"
45
+ end
46
+
47
+ def blob_upload_iri
48
+ return unless ActiveStorage::Blob.service.present?
49
+
50
+ LinkedRails.iri(path: 'rails/active_storage/direct_uploads')
51
+ end
52
+
20
53
  def css_class; end
21
54
 
55
+ def csp_entries
56
+ {
57
+ connectSrc: [ActiveStorage::Blob.service.try(:bucket)&.url].compact,
58
+ scriptSrc: [ActiveStorage::Blob.service.try(:bucket)&.url].compact
59
+ }
60
+ end
61
+
22
62
  def header_background
23
63
  :primary
24
64
  end
@@ -27,17 +67,16 @@ module LinkedRails
27
67
  :white
28
68
  end
29
69
 
30
- def preload_iris
31
- [
32
- scope,
33
- LinkedRails.iri(path: 'ns/core').to_s,
34
- LinkedRails.iri(path: 'c_a').to_s,
35
- LinkedRails.iri(path: 'menus').to_s
36
- ]
70
+ def icons
71
+ []
72
+ end
73
+
74
+ def lang
75
+ :nl
37
76
  end
38
77
 
39
78
  def scope
40
- @scope ||= LinkedRails.iri.to_s
79
+ LinkedRails.iri.to_s
41
80
  end
42
81
 
43
82
  def site_theme_color
@@ -52,43 +91,46 @@ module LinkedRails
52
91
  app_name
53
92
  end
54
93
 
94
+ def start_url
95
+ scope == '/' ? scope : "#{scope}/"
96
+ end
97
+
55
98
  def theme; end
56
99
 
57
100
  def theme_options
58
101
  {}
59
102
  end
60
103
 
61
- def web_manifest
62
- web_manifest_base.merge(
63
- ontola: web_manifest_ontola_section,
64
- serviceworker: web_manifest_sw_section
65
- )
66
- end
67
-
68
104
  def web_manifest_base # rubocop:disable Metrics/MethodLength
69
105
  {
70
106
  background_color: background_color,
71
107
  dir: :rtl,
72
108
  display: :standalone,
73
- lang: :nl,
109
+ icons: icons,
110
+ lang: lang,
74
111
  name: app_name,
75
112
  scope: scope,
76
113
  short_name: app_name,
77
- start_url: scope,
114
+ start_url: start_url,
78
115
  theme_color: site_theme_color
79
116
  }
80
117
  end
81
118
 
82
119
  def web_manifest_ontola_section # rubocop:disable Metrics/MethodLength
83
120
  {
84
- css_class: css_class,
121
+ allowed_external_sources: allowed_external_sources,
122
+ blob_preview_iri: blob_preview_iri,
123
+ blob_upload_iri: blob_upload_iri,
124
+ csp: csp_entries,
85
125
  header_background: header_background,
86
126
  header_text: header_text,
87
- preload: preload_iris,
127
+ preconnect: preconnect,
88
128
  primary_color: site_theme_color,
89
129
  secondary_color: site_secondary_color,
130
+ styled_headers: styled_headers,
90
131
  theme: theme,
91
132
  theme_options: theme_options.to_query,
133
+ tracking: tracking,
92
134
  website_iri: LinkedRails.iri.to_s,
93
135
  websocket_path: websocket_path
94
136
  }
@@ -96,7 +138,7 @@ module LinkedRails
96
138
 
97
139
  def web_manifest_sw_section
98
140
  {
99
- src: "#{scope}/sw.js?manifestLocation=#{Rack::Utils.escape("#{scope}/manifest.json")}",
141
+ src: "#{scope.chomp('/')}/sw.js",
100
142
  scope: scope
101
143
  }
102
144
  end
@@ -104,5 +146,37 @@ module LinkedRails
104
146
  def websocket_path
105
147
  Rails.application.config.try(:action_cable).try(:mount_path).try(:[], 1..-1)
106
148
  end
149
+
150
+ def preconnect
151
+ []
152
+ end
153
+
154
+ def styled_headers
155
+ false
156
+ end
157
+
158
+ def tracking
159
+ []
160
+ end
161
+
162
+ class << self
163
+ def destroy(iri)
164
+ Storage.hdel(:persistent, :manifest, URL.as_href(iri))
165
+ end
166
+
167
+ def move(from, to)
168
+ Storage.hset(
169
+ :persistent,
170
+ :redirect_prefix,
171
+ URL.as_href(from) => URL.as_href(to)
172
+ )
173
+
174
+ data = Storage.hget(:persistent, :manifest, URL.as_href(from))
175
+
176
+ Storage.hset(:persistent, :manifest, URL.as_href(to), data) if data
177
+
178
+ destroy(from)
179
+ end
180
+ end
107
181
  end
108
182
  end
@@ -81,7 +81,7 @@ module LinkedRails
81
81
  parent = parent_from_params(params, user_context)
82
82
  return if parent.blank?
83
83
 
84
- parent.menu_sequence
84
+ parent.try(:menu_sequence)
85
85
  end
86
86
  end
87
87
  end
@@ -26,7 +26,7 @@ module LinkedRails
26
26
  end
27
27
 
28
28
  def menus
29
- @menus ||= available_menus.map(&method(:memoised_menu_item))
29
+ @menus ||= available_menus.map(&method(:memoized_menu_item))
30
30
  end
31
31
 
32
32
  def menu(tag)
@@ -49,9 +49,8 @@ module LinkedRails
49
49
  end
50
50
 
51
51
  def menu_item(tag, options) # rubocop:disable Metrics/AbcSize
52
- if options[:policy].present?
53
- return unless resource_policy(options[:policy_resource]).send(options[:policy], *options[:policy_arguments])
54
- end
52
+ return unless show_menu_item?(tag, options)
53
+
55
54
  options[:label_params] ||= {}
56
55
  options[:label_params][:default] ||= ["menus.default.#{tag}".to_sym, tag.to_s.capitalize]
57
56
  options[:label] ||= default_label(tag, options)
@@ -60,6 +59,10 @@ module LinkedRails
60
59
  LinkedRails.menus_item_class.new(resource: resource, tag: tag, parent: self, **options)
61
60
  end
62
61
 
62
+ def policy_verdict(policy, options)
63
+ policy.send(options[:policy], *options[:policy_arguments])
64
+ end
65
+
63
66
  def resource_policy(policy_resource)
64
67
  policy_resource ||= resource
65
68
  policy_resource = instance_exec(&policy_resource) if policy_resource.respond_to?(:call)
@@ -69,6 +72,14 @@ module LinkedRails
69
72
  @resource_policy[policy_resource] ||= Pundit.policy(user_context, policy_resource)
70
73
  end
71
74
 
75
+ def show_menu_item?(_tag, options)
76
+ return true if options[:policy].blank?
77
+
78
+ policy = resource_policy(options[:policy_resource])
79
+
80
+ policy_verdict(policy, options)
81
+ end
82
+
72
83
  def iri_template
73
84
  base_template = resource.send(resource.try(:singular_resource?) ? :singular_iri_template : :iri_template)
74
85
 
@@ -4,6 +4,7 @@ module LinkedRails
4
4
  class Ontology
5
5
  include ActiveModel::Model
6
6
  include LinkedRails::Model
7
+ include LinkedRails::Model::Cacheable
7
8
 
8
9
  def classes
9
10
  @classes ||= LinkedRails.linked_models.map do |klass|
@@ -8,6 +8,7 @@ module LinkedRails
8
8
  policy = Pundit.policy!(user_context, child_resource)
9
9
  verdict = policy.create?
10
10
  @message = policy.message
11
+ @action_status = policy.action_status
11
12
  verdict
12
13
  end
13
14
 
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module LinkedRails
4
+ class FormPolicy < LinkedRails.policy_parent_class
5
+ def show?
6
+ true
7
+ end
8
+
9
+ def public_resource?
10
+ true
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module LinkedRails
4
+ class OntologyPolicy < LinkedRails.policy_parent_class
5
+ def show?
6
+ true
7
+ end
8
+
9
+ def public_resource?
10
+ true
11
+ end
12
+ end
13
+ end
@@ -17,6 +17,7 @@ module LinkedRails
17
17
  has_one :collection, predicate: Vocab.as.partOf
18
18
  has_one :unfiltered_collection, predicate: Vocab.ontola[:baseCollection]
19
19
  has_one :member_sequence, predicate: Vocab.as.items
20
+ has_many :members
20
21
  end
21
22
  end
22
23
  end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module LinkedRails
4
+ class InvalidationStreamWorker < ActiveJob::Base
5
+ def perform(type, iri, resource_type)
6
+ entry = {
7
+ type: type,
8
+ resource: iri,
9
+ resourceType: resource_type
10
+ }
11
+ id = Storage.xadd(:stream, LinkedRails.cache_stream, entry)
12
+
13
+ raise('No message id returned, implies failure') if id.blank?
14
+ end
15
+ end
16
+ end
@@ -11,6 +11,8 @@ en:
11
11
  update:
12
12
  label: "Edit"
13
13
  success: 'Updated successfully'
14
+ errors:
15
+ access_denied: "You're not authorized for this action. (%{action})"
14
16
  linked_rails:
15
17
  status:
16
18
  400: "Error in request"
@@ -6,6 +6,10 @@ module LinkedRails
6
6
  extend ActiveSupport::Concern
7
7
  include ActiveSupport::Rescuable
8
8
 
9
+ included do
10
+ rescue_from LinkedRails::Errors::Forbidden, with: :handle_error
11
+ end
12
+
9
13
  private
10
14
 
11
15
  def add_error_snackbar(error)
@@ -69,6 +73,7 @@ module LinkedRails
69
73
  'Doorkeeper::Errors::InvalidGrantReuse' => 422,
70
74
  'LinkedRails::Auth::Errors::Expired' => 410,
71
75
  'LinkedRails::Auth::Errors::Unauthorized' => 401,
76
+ 'LinkedRails::Errors::Forbidden' => 403,
72
77
  'Pundit::NotAuthorizedError' => 403
73
78
  }
74
79
  end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ module LinkedRails
4
+ module Errors
5
+ class Forbidden < StandardError
6
+ attr_reader :query, :record, :policy, :action
7
+
8
+ # @param [Hash] options
9
+ # @option options [String] query The action of the request
10
+ # @option options [ActiveRecord::Base] record The record that was requested
11
+ # @option options [Policy] policy The policy that raised the exception
12
+ # @option options [String] message Override the default error message
13
+ # @return [String] the message
14
+ def initialize(**options)
15
+ @query = options.fetch(:query).to_s
16
+ @record = options[:record]
17
+ @policy = options[:policy]
18
+ @action = @query[-1] == '?' ? @query[0..-2] : @query
19
+ @message = options[:message]
20
+
21
+ raise StandardError if @query.blank? && @message.blank?
22
+
23
+ super(@message || default_message)
24
+ end
25
+
26
+ private
27
+
28
+ def default_message
29
+ I18n.t(
30
+ "pundit.#{@policy.class.to_s.underscore}.#{@query}",
31
+ action: @action,
32
+ default: I18n.t('errors.access_denied')
33
+ )
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,3 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'errors/forbidden'
@@ -46,12 +46,22 @@ module LinkedRails
46
46
 
47
47
  private
48
48
 
49
+ def request_path_to_url(path)
50
+ return path unless path.present? && URI(path).relative?
51
+
52
+ port = [80, 443].include?(request.port) ? nil : request.port
53
+ URI::Generic.new(request.scheme, nil, request.host, port, nil, path, nil, nil, nil).to_s
54
+ end
55
+
49
56
  def build_new_resource
50
57
  controller_class.build_new(user_context: user_context)
51
58
  end
52
59
 
53
60
  def new_resource_from_parent
54
- return requested_resource.child_resource if requested_resource.is_a?(Collection)
61
+ if requested_resource.is_a?(LinkedRails.collection_class) ||
62
+ requested_resource.is_a?(LinkedRails.collection_view_class)
63
+ return requested_resource.child_resource
64
+ end
55
65
 
56
66
  parent_resource.build_child(
57
67
  controller_class,
@@ -38,7 +38,7 @@ module LinkedRails
38
38
  params = Rails.application.routes.recognize_path(iri.to_s, method: method)
39
39
 
40
40
  route_params_to_opts(params.merge(query), iri.to_s)
41
- rescue ActionController::RoutingError
41
+ rescue ActionController::RoutingError, SystemStackError
42
42
  EMPTY_IRI_OPTS.dup
43
43
  end
44
44
 
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ module LinkedRails
4
+ module Model
5
+ module Cacheable
6
+ extend ActiveSupport::Concern
7
+
8
+ included do
9
+ if respond_to?(:after_commit)
10
+ after_commit :publish_create, on: :create, if: :should_publish_changes
11
+ after_commit :publish_update, on: :update, if: :should_publish_changes
12
+ after_commit :publish_delete, on: :destroy, if: :should_publish_changes
13
+ end
14
+ end
15
+
16
+ def cacheable?
17
+ true
18
+ end
19
+
20
+ def publish_create
21
+ publish_message('io.ontola.transactions.Created')
22
+ end
23
+
24
+ def publish_update
25
+ publish_message('io.ontola.transactions.Updated')
26
+ end
27
+
28
+ def publish_delete
29
+ publish_message('io.ontola.transactions.Deleted')
30
+ end
31
+
32
+ private
33
+
34
+ def publish_message(type)
35
+ LinkedRails::InvalidationStreamWorker.perform_now(type, iri.to_s, self.class.iri.to_s)
36
+ rescue StandardError
37
+ LinkedRails::InvalidationStreamWorker.perform_later(type, iri.to_s, self.class.iri.to_s)
38
+ end
39
+
40
+ def should_publish_changes
41
+ cacheable? && !Rails.env.test?
42
+ end
43
+ end
44
+ end
45
+ end
@@ -70,7 +70,9 @@ module LinkedRails
70
70
 
71
71
  module ClassMethods
72
72
  def collection_iri(**opts)
73
- LinkedRails.iri(path: collection_root_relative_iri(**opts))
73
+ fragment = opts.delete(:fragment)
74
+
75
+ LinkedRails.iri(path: collection_root_relative_iri(**opts), fragment: fragment)
74
76
  end
75
77
 
76
78
  # Sets the defaults for all collections for this class.
@@ -62,6 +62,7 @@ module LinkedRails
62
62
  iri = root_relative_iri.dup
63
63
  iri.scheme = LinkedRails.scheme
64
64
  iri.host = LinkedRails.host
65
+ iri.path = iri.path.presence || '/'
65
66
  iri
66
67
  end
67
68
 
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative 'model/actionable'
4
+ require_relative 'model/cacheable'
4
5
  require_relative 'model/collections'
5
6
  require_relative 'model/dirty'
6
7
  require_relative 'model/enhancements'
@@ -118,9 +118,13 @@ module LinkedRails
118
118
  end
119
119
 
120
120
  def parse_attribute(klass, field_options, value)
121
- parsed_value = parse_enum_attribute(klass, field_options.key, value) || value
121
+ [field_options.key, parse_attribute_value(klass, field_options, value)]
122
+ end
123
+
124
+ def parse_attribute_value(klass, field_options, value)
125
+ return nil if value == Vocab.libro[:null]
122
126
 
123
- [field_options.key, parsed_value.to_s]
127
+ (parse_enum_attribute(klass, field_options.key, value) || value).to_s
124
128
  end
125
129
 
126
130
  def parse_enum_attribute(klass, key, value)
@@ -133,9 +137,15 @@ module LinkedRails
133
137
 
134
138
  def parse_iri_param(iri, reflection)
135
139
  key = foreign_key_for_reflection(reflection)
136
- value = parse_iri_param_value(iri, reflection) if key
140
+ return unless key
137
141
 
138
- [key, value.to_s] if value
142
+ if iri == Vocab.libro[:null]
143
+ [key, nil]
144
+ else
145
+ value = parse_iri_param_value(iri, reflection)
146
+
147
+ [key, value.to_s] if value
148
+ end
139
149
  end
140
150
 
141
151
  def parse_iri_param_value(iri, reflection)
@@ -46,6 +46,10 @@ module LinkedRails
46
46
  self.class.policy_class
47
47
  end
48
48
 
49
+ def public_resource?
50
+ false
51
+ end
52
+
49
53
  def show?
50
54
  false
51
55
  end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'redis'
4
+
5
+ module LinkedRails
6
+ class Storage
7
+ REDIS_DB = {
8
+ cache: LinkedRails.cache_redis_database,
9
+ persistent: LinkedRails.persistent_redis_database,
10
+ stream: LinkedRails.stream_redis_database
11
+ }.freeze
12
+ KEYS = {
13
+ manifest: 'cache:Manifest',
14
+ redirect_exact: 'cache:Redirect:Exact',
15
+ redirect_prefix: 'cache:Redirect:Prefix'
16
+ }.freeze
17
+
18
+ class << self
19
+ %i[xadd].each do |method|
20
+ define_method(method) do |db, *args|
21
+ Redis.new(db: REDIS_DB.fetch(db)).send(method, *args)
22
+ end
23
+ end
24
+
25
+ %i[hset hdel hget].each do |method|
26
+ define_method(method) do |db, key, *args|
27
+ Redis.new(db: REDIS_DB.fetch(db)).send(method, KEYS.fetch(key), *args)
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,11 @@
1
+ module LinkedRails
2
+ class URL
3
+ class << self
4
+ def as_href(url)
5
+ uri = URI(url)
6
+ uri.path = uri.path.presence || '/'
7
+ uri.to_s
8
+ end
9
+ end
10
+ end
11
+ end
data/lib/linked_rails.rb CHANGED
@@ -22,6 +22,10 @@ module LinkedRails
22
22
 
23
23
  mattr_accessor :whitelisted_spi_ips
24
24
  mattr_writer :host, :scheme
25
+ mattr_accessor :persistent_redis_database, default: ENV['PERSISTENT_REDIS_DATABASE'].presence || 6
26
+ mattr_accessor :stream_redis_database, default: ENV['STREAM_REDIS_DATABASE'].presence || 7
27
+ mattr_accessor :cache_redis_database, default: ENV['CACHE_REDIS_DATABASE'].presence || 8
28
+ mattr_accessor :cache_stream, default: ENV['CACHE_STREAM'].presence || 'transactions'
25
29
 
26
30
  def self.configurable_class(parent, klass, default: nil, reader: nil) # rubocop:disable Metrics/AbcSize
27
31
  method = :"#{[parent, klass.to_s.downcase].compact.join('_')}_class"
@@ -50,8 +54,10 @@ module LinkedRails
50
54
  @@scheme ||= Rails.application.routes.default_url_options[:protocol] || :http # rubocop:disable Style/ClassVars
51
55
  end
52
56
 
53
- def iri(**opts)
54
- RDF::URI.new(**{scheme: LinkedRails.scheme, host: LinkedRails.host}.merge(opts))
57
+ def iri(**args)
58
+ opts = {scheme: LinkedRails.scheme, host: LinkedRails.host}.merge(args)
59
+ opts[:path] = opts[:path].presence || '/'
60
+ RDF::URI.new(**opts)
55
61
  end
56
62
  end
57
63
 
@@ -84,6 +90,7 @@ ActiveSupport::Inflector.inflections do |inflect|
84
90
  inflect.acronym 'SHACL'
85
91
  end
86
92
 
93
+ require 'linked_rails/errors'
87
94
  require 'linked_rails/uri_template'
88
95
  require 'linked_rails/vocab'
89
96
  require 'linked_rails/cache'
@@ -101,3 +108,5 @@ require 'linked_rails/routes'
101
108
  require 'linked_rails/serializer'
102
109
  require 'linked_rails/translate'
103
110
  require 'linked_rails/railtie'
111
+ require 'linked_rails/url'
112
+ require 'linked_rails/storage'
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: linked_rails
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.4.pre.g83aa52ab3
4
+ version: 0.0.4.pre.g222bfeeca
5
5
  platform: ruby
6
6
  authors:
7
7
  - Arthur Dingemans
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-08-02 00:00:00.000000000 Z
11
+ date: 2022-08-31 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: active_response
@@ -66,6 +66,20 @@ dependencies:
66
66
  - - ">="
67
67
  - !ruby/object:Gem::Version
68
68
  version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: redis
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
69
83
  - !ruby/object:Gem::Dependency
70
84
  name: rdf
71
85
  requirement: !ruby/object:Gem::Requirement
@@ -285,8 +299,10 @@ files:
285
299
  - app/policies/linked_rails/collection/view_policy.rb
286
300
  - app/policies/linked_rails/collection_policy.rb
287
301
  - app/policies/linked_rails/enum_value_policy.rb
302
+ - app/policies/linked_rails/form_policy.rb
288
303
  - app/policies/linked_rails/menus/item_policy.rb
289
304
  - app/policies/linked_rails/menus/list_policy.rb
305
+ - app/policies/linked_rails/ontology_policy.rb
290
306
  - app/policies/linked_rails/sequence_policy.rb
291
307
  - app/serializers/linked_rails/actions/item_serializer.rb
292
308
  - app/serializers/linked_rails/actions/object_serializer.rb
@@ -324,6 +340,7 @@ files:
324
340
  - app/serializers/linked_rails/web_page_serializer.rb
325
341
  - app/serializers/linked_rails/web_site_serializer.rb
326
342
  - app/serializers/linked_rails/widget_serializer.rb
343
+ - app/workers/linked_rails/invalidation_stream_worker.rb
327
344
  - config/initializers/inflections.rb
328
345
  - lib/generators/linked_rails/install/install_generator.rb
329
346
  - lib/generators/linked_rails/install/templates/README
@@ -373,6 +390,8 @@ files:
373
390
  - lib/linked_rails/enhancements/destroyable/controller.rb
374
391
  - lib/linked_rails/enhancements/updatable/controller.rb
375
392
  - lib/linked_rails/enhancements/updatable/serializer.rb
393
+ - lib/linked_rails/errors.rb
394
+ - lib/linked_rails/errors/forbidden.rb
376
395
  - lib/linked_rails/helpers/delta_helper.rb
377
396
  - lib/linked_rails/helpers/ontola_actions_helper.rb
378
397
  - lib/linked_rails/helpers/resource_helper.rb
@@ -381,6 +400,7 @@ files:
381
400
  - lib/linked_rails/middleware/linked_data_params.rb
382
401
  - lib/linked_rails/model.rb
383
402
  - lib/linked_rails/model/actionable.rb
403
+ - lib/linked_rails/model/cacheable.rb
384
404
  - lib/linked_rails/model/collections.rb
385
405
  - lib/linked_rails/model/dirty.rb
386
406
  - lib/linked_rails/model/enhancements.rb
@@ -404,10 +424,12 @@ files:
404
424
  - lib/linked_rails/serializer/actionable.rb
405
425
  - lib/linked_rails/serializer/menuable.rb
406
426
  - lib/linked_rails/serializer/singularable.rb
427
+ - lib/linked_rails/storage.rb
407
428
  - lib/linked_rails/test_methods.rb
408
429
  - lib/linked_rails/translate.rb
409
430
  - lib/linked_rails/types/iri_type.rb
410
431
  - lib/linked_rails/uri_template.rb
432
+ - lib/linked_rails/url.rb
411
433
  - lib/linked_rails/version.rb
412
434
  - lib/linked_rails/vocab.rb
413
435
  - lib/nill_class_renderer.rb