rdf-serializers 0.0.5 → 0.0.9

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 35f15bf416da298540d39bee8d5db00fbaf5ff10
4
- data.tar.gz: a9d3d79948b0b50fa6209e9f7ab893012dd10b21
2
+ SHA256:
3
+ metadata.gz: 0be7947d0024e7f830b59b3a3a54ee24ba41f577abab0e85d31f332b5e992e01
4
+ data.tar.gz: 63ac5d819554a72e03e2ab66b1420fd956c29db46d40031b107cdfd0e775f957
5
5
  SHA512:
6
- metadata.gz: 92b62c5b1c3ab8afef09058f761c92ceaf1ca4dd0cb9e217acec641eee1c9bdcffdbb0116fa240b43daddcd165cb59afddb69e706fa85a1aa4785c4a59facdc1
7
- data.tar.gz: 983c6a0d3eb67d4832014a1e0ca61e196b774000385b0acfeed33abfc99fc7df34e1d3b79892f8e0439ba5422c4c7e1c8b39b234ca006faae1abd55861b49d16
6
+ metadata.gz: ec6636a77e6799e8aae8f2c623081b45b22fe0e291f51370eaee3aea16e6f34557bcef3aae4496fc3c0b087cc765d499ee58fcfe222208b24650a44b86fe6afd
7
+ data.tar.gz: 6bc906962033ad39c23efa768c111cfa0a7d52ed6727f6786dc78592f5165ee34bf2b783ddf7438a005ea1a2a48ad65a632889715eaaca12f84965a895a8d1c9
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RDF
4
+ module Serializers
5
+ def self.configure
6
+ yield @config ||= RDF::Serializers::Configuration.new
7
+ end
8
+
9
+ def self.config
10
+ @config
11
+ end
12
+
13
+ class Configuration
14
+ include ActiveSupport::Configurable
15
+ config_accessor :always_include_named_graphs
16
+ config_accessor :default_graph
17
+ config_accessor :serializer_lookup_chain
18
+ config_accessor :serializer_lookup_enabled
19
+ end
20
+
21
+ configure do |config|
22
+ config.always_include_named_graphs = true
23
+
24
+ config.serializer_lookup_enabled = true
25
+
26
+ # For configuring how serializers are found.
27
+ # This should be an array of procs.
28
+ #
29
+ # The priority of the output is that the first item
30
+ # in the evaluated result array will take precedence
31
+ # over other possible serializer paths.
32
+ #
33
+ # i.e.: First match wins.
34
+ #
35
+ # @example output
36
+ # => [
37
+ # "CustomNamespace::ResourceSerializer",
38
+ # "ParentSerializer::ResourceSerializer",
39
+ # "ResourceNamespace::ResourceSerializer" ,
40
+ # "ResourceSerializer"]
41
+ #
42
+ # If CustomNamespace::ResourceSerializer exists, it will be used
43
+ # for serialization
44
+ config.serializer_lookup_chain = RDF::Serializers::LookupChain::DEFAULT.dup
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RDF
4
+ module Serializers
5
+ module DataTypeHelper
6
+ def xsd_to_rdf(xsd, value, opts = {}) # rubocop:disable Metrics/CyclomaticComplexity, Metrics/MethodLength
7
+ klass =
8
+ case xsd
9
+ when RDF::XSD[:anyURI]
10
+ RDF::URI
11
+ when RDF::XSD[:integer]
12
+ RDF::Literal::Integer
13
+ when RDF::XSD[:dateTime]
14
+ RDF::Literal::DateTime
15
+ when RDF::XSD[:date]
16
+ RDF::Literal::Date
17
+ when RDF::XSD[:boolean]
18
+ RDF::Literal::Boolean
19
+ when RDF::XSD[:time]
20
+ RDF::Literal::Time
21
+ when RDF::XSD[:long], RDF::XSD[:double]
22
+ RDF::Literal::Double
23
+ when RDF::XSD[:decimal]
24
+ RDF::Literal::Decimal
25
+ when RDF::XSD[:token]
26
+ RDF::Literal::Token
27
+ else
28
+ RDF::Literal
29
+ end
30
+
31
+ klass.new(value, opts)
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,70 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RDF
4
+ module Serializers
5
+ class HndJSONParser
6
+ include DataTypeHelper
7
+
8
+ HEX_SUBJECT = 0
9
+ HEX_PREDICATE = 1
10
+ HEX_OBJECT = 2
11
+ HEX_DATATYPE = 3
12
+ HEX_LANGUAGE = 4
13
+ HEX_GRAPH = 5
14
+
15
+ def parse_body(body)
16
+ body.split("\n").map { |line| parse_hex(JSON.parse(line)) }
17
+ end
18
+
19
+ def parse_hex(hex)
20
+ subject = parse_subject(hex[HEX_SUBJECT])
21
+ predicate = RDF::URI(hex[HEX_PREDICATE])
22
+ object = parse_object(hex[HEX_OBJECT], hex[HEX_DATATYPE], hex[HEX_LANGUAGE])
23
+ graph = hex[HEX_GRAPH].present? ? RDF::URI(hex[HEX_GRAPH]) : RDF::Serializers.config.default_graph
24
+
25
+ RDF::Statement.new(
26
+ subject,
27
+ predicate,
28
+ object,
29
+ graph_name: graph
30
+ )
31
+ end
32
+
33
+ private
34
+
35
+ def blank_node(id)
36
+ @blank_nodes ||= {}
37
+ @blank_nodes[id] ||= RDF::Node(id)
38
+ end
39
+
40
+ def parse_object(value, datatype, language)
41
+ case datatype
42
+ when 'http://www.w3.org/1999/02/22-rdf-syntax-ns#namedNode'
43
+ RDF::URI(value)
44
+ when 'http://www.w3.org/1999/02/22-rdf-syntax-ns#blankNode'
45
+ blank_node(value.sub('_:', ''))
46
+ when language
47
+ RDF::Literal(value, datatype: RDF.langString, language: language)
48
+ else
49
+ xsd_to_rdf(datatype, value, language: language.presence)
50
+ end
51
+ end
52
+
53
+ def parse_subject(subject)
54
+ if subject.is_a?(RDF::Resource)
55
+ subject
56
+ elsif subject.start_with?('_')
57
+ blank_node(subject.sub('_:', ''))
58
+ else
59
+ RDF::URI(subject)
60
+ end
61
+ end
62
+
63
+ class << self
64
+ def parse(body)
65
+ new.parse_body(body)
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,71 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RDF
4
+ module Serializers
5
+ module HextupleSerializer
6
+ def iri_from_record(record)
7
+ return record if record.try(:uri?)
8
+
9
+ raise FastJsonapi::MandatoryField, 'record has no iri' unless record.respond_to?(:iri)
10
+
11
+ record.iri
12
+ end
13
+
14
+ def normalized_object(object)
15
+ case object
16
+ when ::RDF::Term
17
+ object
18
+ when ActiveSupport::TimeWithZone
19
+ ::RDF::Literal(object.to_datetime)
20
+ else
21
+ ::RDF::Literal(object)
22
+ end
23
+ end
24
+
25
+ def object_value(obj)
26
+ if obj.is_a?(::RDF::URI)
27
+ obj.value
28
+ elsif obj.is_a?(::RDF::Node)
29
+ obj.to_s
30
+ else
31
+ obj.value.to_s
32
+ end
33
+ end
34
+
35
+ def object_datatype(obj)
36
+ if obj.is_a?(::RDF::URI)
37
+ 'http://www.w3.org/1999/02/22-rdf-syntax-ns#namedNode'
38
+ elsif obj.is_a?(::RDF::Node)
39
+ 'http://www.w3.org/1999/02/22-rdf-syntax-ns#blankNode'
40
+ elsif obj.try(:datatype?)
41
+ obj.datatype
42
+ else
43
+ lit = RDF::Literal(obj)
44
+ lit.datatype.to_s
45
+ end
46
+ end
47
+
48
+ def value_to_hex(iri, predicate, object, graph = nil, serialization_params = {})
49
+ return if object.nil?
50
+
51
+ obj = normalized_object(object)
52
+
53
+ [
54
+ iri.to_s,
55
+ predicate.to_s,
56
+ object_value(obj),
57
+ object_datatype(obj),
58
+ obj.try(:language) || '',
59
+ operation((graph || ::RDF::Serializers.config.default_graph)&.value, serialization_params[:context])
60
+ ]
61
+ end
62
+
63
+ def operation(operation, graph_name)
64
+ return nil if operation.blank?
65
+ return operation if graph_name.blank?
66
+
67
+ "#{operation}?graph=#{WEBrick::HTTPUtils.escape_form(graph_name.to_s)}"
68
+ end
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RDF
4
+ module Serializers
5
+ class ListSerializer
6
+ include RDF::Serializers::ObjectSerializer
7
+
8
+ def hextuples_for_collection
9
+ @resource.map do |resource|
10
+ RDF::Serializers.serializer_for(resource).record_hextuples(resource, nil, @includes, @params)
11
+ end.flatten(1)
12
+ end
13
+
14
+ class << self
15
+ def validate_includes!(_includes); end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,61 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RDF
4
+ module Serializers
5
+ # Extracted from active_model_serializers
6
+ module LookupChain
7
+ # Standard appending of Serializer to the resource name.
8
+ #
9
+ # Example:
10
+ # Author => AuthorSerializer
11
+ BY_RESOURCE = lambda do |resource_class, _namespace|
12
+ serializer_from(resource_class)
13
+ end
14
+
15
+ # Uses the namespace of the resource to find the serializer
16
+ #
17
+ # Example:
18
+ # British::Author => British::AuthorSerializer
19
+ BY_RESOURCE_NAMESPACE = lambda do |resource_class, _namespace|
20
+ resource_namespace = namespace_for(resource_class)
21
+ serializer_name = serializer_from(resource_class)
22
+
23
+ "#{resource_namespace}::#{serializer_name}"
24
+ end
25
+
26
+ # Uses the controller namespace of the resource to find the serializer
27
+ #
28
+ # Example:
29
+ # Api::V3::AuthorsController => Api::V3::AuthorSerializer
30
+ BY_NAMESPACE = lambda do |resource_class, namespace|
31
+ resource_name = resource_class_name(resource_class)
32
+ namespace ? "#{namespace}::#{resource_name}Serializer" : nil
33
+ end
34
+
35
+ DEFAULT = [
36
+ BY_NAMESPACE,
37
+ BY_RESOURCE_NAMESPACE,
38
+ BY_RESOURCE
39
+ ].freeze
40
+
41
+ module_function
42
+
43
+ def namespace_for(klass)
44
+ klass.name.deconstantize
45
+ end
46
+
47
+ def resource_class_name(klass)
48
+ klass.name.demodulize
49
+ end
50
+
51
+ def serializer_from_resource_name(name)
52
+ "#{name}Serializer"
53
+ end
54
+
55
+ def serializer_from(klass)
56
+ name = resource_class_name(klass)
57
+ serializer_from_resource_name(name)
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RDF
4
+ module Serializers
5
+ class NilSerializer
6
+ include RDF::Serializers::ObjectSerializer
7
+
8
+ class << self
9
+ def validate_includes!(_includes); end
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,170 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'oj'
4
+
5
+ module RDF
6
+ module Serializers
7
+ module ObjectSerializer
8
+ extend ActiveSupport::Concern
9
+ include FastJsonapi::ObjectSerializer
10
+ include SerializationCore
11
+
12
+ def dump(*args, **options)
13
+ case args.first
14
+ when :hndjson
15
+ render_hndjson
16
+ else
17
+ render_repository(*args, options)
18
+ end
19
+ end
20
+
21
+ def triples(*args, **options)
22
+ if include_named_graphs?(*args)
23
+ repository.triples(*args, options)
24
+ else
25
+ repository.project_graph(nil).triples(*args, options)
26
+ end
27
+ end
28
+
29
+ private
30
+
31
+ def blank_node(id)
32
+ @blank_nodes ||= {}
33
+ @blank_nodes[id] ||= RDF::Node(id)
34
+ end
35
+
36
+ def hextuples_for_collection
37
+ data = []
38
+ fieldset = @fieldsets[self.class.record_type.to_sym]
39
+ @resource.each do |record|
40
+ data.concat self.class.record_hextuples(record, fieldset, @includes, @params)
41
+ next unless @includes.present?
42
+
43
+ data.concat(
44
+ self.class.get_included_records_hex(record, @includes, @known_included_objects, @fieldsets, @params)
45
+ )
46
+ end
47
+
48
+ data
49
+ end
50
+
51
+ def hextuples_for_one_record
52
+ serializable_hextuples = []
53
+
54
+ serializable_hextuples.concat self.class.record_hextuples(
55
+ @resource,
56
+ @fieldsets[self.class.record_type.to_sym],
57
+ @includes,
58
+ @params
59
+ )
60
+
61
+ if @includes.present?
62
+ serializable_hextuples.concat self.class.get_included_records_hex(
63
+ @resource,
64
+ @includes,
65
+ @known_included_objects,
66
+ @fieldsets,
67
+ @params
68
+ )
69
+ end
70
+
71
+ serializable_hextuples
72
+ end
73
+
74
+ def include_named_graphs?(*args)
75
+ ::RDF::Serializers.config.always_include_named_graphs ||
76
+ ::RDF::Writer.for(*args.presence || :nquads).instance_methods.include?(:write_quad)
77
+ end
78
+
79
+ def meta_hextuples
80
+ return [] unless @meta.is_a?(Array)
81
+
82
+ @meta.map do |statement|
83
+ if statement.is_a?(Array)
84
+ value_to_hex(statement[0], statement[1], statement[2], statement[3], @params)
85
+ else
86
+ value_to_hex(statement.subject.to_s, statement.predicate, statement.object, statement.graph_name, @params)
87
+ end
88
+ end.compact
89
+ end
90
+
91
+ def repository
92
+ return @repository if @repository.present?
93
+
94
+ @repository = ::RDF::Repository.new
95
+ parser = HndJSONParser.new
96
+
97
+ serializable_hextuples.compact.each do |hextuple|
98
+ @repository << parser.parse_hex(hextuple)
99
+ end
100
+
101
+ @repository
102
+ end
103
+
104
+ def render_hndjson
105
+ serializable_hextuples
106
+ .map { |s| Oj.fast_generate(s) }
107
+ .join("\n")
108
+ end
109
+
110
+ def render_repository(*args, options)
111
+ if include_named_graphs?(*args)
112
+ repository.dump(*args, options)
113
+ else
114
+ repository.project_graph(nil).dump(*args, options)
115
+ end
116
+ end
117
+
118
+ def serializable_hextuples
119
+ if is_collection?(@resource, @is_collection)
120
+ hextuples_for_collection + meta_hextuples
121
+ elsif !@resource
122
+ []
123
+ else
124
+ hextuples_for_one_record + meta_hextuples
125
+ end
126
+ end
127
+
128
+ class_methods do
129
+ def create_relationship(base_key, relationship_type, options, block)
130
+ association = options.delete(:association)
131
+ image = options.delete(:image)
132
+ predicate = options.delete(:predicate)
133
+ sequence = options.delete(:sequence)
134
+ relation = super
135
+ relation.association = association
136
+ relation.image = image
137
+ relation.predicate = predicate
138
+ relation.sequence = sequence
139
+
140
+ relation
141
+ end
142
+
143
+ # Checks for the `class_name` property on the Model's association to
144
+ # determine a serializer.
145
+ def association_serializer_for(name)
146
+ model_class_name = self.name.to_s.demodulize.classify.gsub(/Serializer$/, '')
147
+ model_class = model_class_name.safe_constantize
148
+
149
+ association_class_name = model_class.try(:reflect_on_association, name)&.class_name
150
+
151
+ return nil unless association_class_name
152
+
153
+ serializer_for(association_class_name)
154
+ end
155
+
156
+ def serializer_for(name)
157
+ associatopm_serializer = association_serializer_for(name)
158
+ return associatopm_serializer if associatopm_serializer
159
+
160
+ begin
161
+ RDF::Serializers.serializer_for(const_get(name.to_s.classify))
162
+ rescue NameError
163
+ raise NameError, "#{self.name} cannot resolve a serializer class for '#{name}'. " \
164
+ 'Consider specifying the serializer directly through options[:serializer].'
165
+ end
166
+ end
167
+ end
168
+ end
169
+ end
170
+ end
@@ -0,0 +1,65 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RDF
4
+ module Serializers
5
+ module Relationship
6
+ include HextupleSerializer
7
+
8
+ attr_accessor :predicate, :image, :association, :sequence
9
+
10
+ def serialize_hex(record, included, serialization_params)
11
+ return [] unless include_relationship?(record, serialization_params, included) && predicate.present?
12
+
13
+ iris = iris_from_record_and_relationship(record, serialization_params)
14
+
15
+ sequence ? relationship_sequence(record, iris, serialization_params) : relationship_statements(record, iris, serialization_params)
16
+ end
17
+
18
+ def relationship_sequence(record, iris, serialization_params)
19
+ sequence = RDF::Node.new
20
+
21
+ [
22
+ value_to_hex(iri_from_record(record).to_s, predicate, sequence, nil, serialization_params),
23
+ value_to_hex(sequence, RDF.type, RDF.Seq, nil, serialization_params)
24
+ ] + iris.map.with_index do |iri, index|
25
+ value_to_hex(sequence, RDF["_#{index}"], iri, nil, serialization_params)
26
+ end
27
+ end
28
+
29
+ def relationship_statements(record, iris, serialization_params)
30
+ iris.map do |related_iri|
31
+ value_to_hex(
32
+ iri_from_record(record).to_s,
33
+ predicate,
34
+ related_iri,
35
+ nil,
36
+ serialization_params
37
+ )
38
+ end
39
+ end
40
+
41
+ def include_relationship?(record, serialization_params, included = false)
42
+ return false if lazy_load_data && !included
43
+
44
+ super(record, serialization_params)
45
+ end
46
+
47
+ def iris_from_record_and_relationship(record, params = {})
48
+ initialize_static_serializer unless @initialized_static_serializer
49
+
50
+ associated_object = fetch_associated_object(record, params)
51
+ return [] unless associated_object
52
+
53
+ if associated_object.respond_to? :map
54
+ return associated_object.compact.map do |object|
55
+ iri_from_record(object)
56
+ end
57
+ end
58
+
59
+ [iri_from_record(associated_object)]
60
+ end
61
+ end
62
+ end
63
+ end
64
+
65
+ FastJsonapi::Relationship.prepend(RDF::Serializers::Relationship)
@@ -16,18 +16,45 @@ module RDF
16
16
  symbols = [symbols] unless symbols.respond_to?(:each)
17
17
  symbols.each do |symbol|
18
18
  format = RDF::Format.for(symbol)
19
- raise "#{symbol} if not a known rdf format" if format.nil?
19
+ raise "#{symbol} is not a known rdf format" if format.nil?
20
+
20
21
  Mime::Type.register format.content_type.first, format.file_extension.first
21
- add_renderer(format, opts)
22
+ add_renderer(format.file_extension.first, format.content_type.first, format.symbols.first, opts)
22
23
  end
23
24
  end
24
25
 
25
- def self.add_renderer(format, opts = {})
26
- ActionController::Renderers.add format.file_extension.first do |resource, options|
27
- self.content_type = format.content_type.first
28
- get_serializer(resource, options.merge(adapter: :rdf)).adapter.dump(format.symbols.first, opts)
26
+ def self.add_renderer(ext, content_type, symbol, opts = {})
27
+ ActionController::Renderers.add ext do |resource, options|
28
+ self.content_type = content_type
29
+ serializer_opts = RDF::Serializers::Renderers.transform_opts(
30
+ options,
31
+ respond_to?(:serializer_params, true) ? serializer_params : {}
32
+ )
33
+ RDF::Serializers.serializer_for(resource)&.new(resource, serializer_opts)&.dump(symbol, opts)
29
34
  end
30
35
  end
36
+
37
+ def self.transform_include(include, root = nil)
38
+ return root if include.blank?
39
+ return [root, include].compact.join('.') if include.is_a?(Symbol) || include.is_a?(String)
40
+
41
+ if include.is_a?(Hash)
42
+ include.flat_map do |k, v|
43
+ transform_include(v, [root, k].compact.join('.'))
44
+ end
45
+ elsif include.is_a?(Array)
46
+ include.flat_map do |v|
47
+ transform_include(v, root)
48
+ end
49
+ end.compact
50
+ end
51
+
52
+ def self.transform_opts(options, params)
53
+ (options || {}).merge(
54
+ include: transform_include(options[:include]),
55
+ params: params
56
+ )
57
+ end
31
58
  end
32
59
  end
33
60
  end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RDF
4
+ module Serializers
5
+ module Scalar
6
+ include HextupleSerializer
7
+
8
+ attr_accessor :predicate, :image, :datatype
9
+
10
+ def initialize(key:, method:, options: {})
11
+ super
12
+ @predicate = options[:predicate]
13
+ @image = options[:image]
14
+ @datatype = options[:datatype]
15
+ end
16
+
17
+ def serialize_hex(record, serialization_params)
18
+ return [] unless conditionally_allowed?(record, serialization_params) && predicate.present?
19
+
20
+ value = value_from_record(record, method, serialization_params)
21
+
22
+ return [] if value.nil?
23
+
24
+ if value.is_a?(Array)
25
+ value.map { |arr_item| value_to_hex(iri_from_record(record).to_s, predicate, arr_item, nil, serialization_params) }
26
+ elsif value.is_a?(::RDF::List)
27
+ first = value.statements.first&.subject || RDF.nil
28
+ value.statements.map do |statement|
29
+ value_to_hex(statement.subject.to_s, statement.predicate, statement.object, statement.graph_name, serialization_params)
30
+ end + [
31
+ value_to_hex(iri_from_record(record).to_s, predicate, first, nil, serialization_params)
32
+ ]
33
+ else
34
+ [value_to_hex(iri_from_record(record).to_s, predicate, value, nil, serialization_params)]
35
+ end
36
+ end
37
+
38
+ def value_from_record(record, method, serialization_params)
39
+ if method.is_a?(Proc)
40
+ FastJsonapi.call_proc(method, record, serialization_params)
41
+ else
42
+ v = record.public_send(method)
43
+ v.is_a?('ActiveRecord'.safe_constantize&.const_get('Relation') || NilClass) ? v.to_a : v
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
49
+
50
+ FastJsonapi::Scalar.prepend(RDF::Serializers::Scalar)
@@ -0,0 +1,130 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RDF
4
+ module Serializers
5
+ module SerializationCore
6
+ extend ActiveSupport::Concern
7
+
8
+ included do
9
+ include HextupleSerializer
10
+ extend HextupleSerializer
11
+ end
12
+
13
+ class_methods do
14
+ def relationships_hextuples(record, relationships = nil, fieldset = nil, includes_list = [], params = {})
15
+ relationships = relationships_to_serialize if relationships.nil?
16
+ relationships = relationships.slice(*fieldset) if fieldset.present?
17
+ relationships = [] if fieldset == []
18
+
19
+ statements = []
20
+ relationships.each do |key, relationship|
21
+ included = includes_list.present? && includes_list.include?(key)
22
+ statements.concat relationship.serialize_hex(record, included, params)
23
+ end
24
+
25
+ statements
26
+ end
27
+
28
+ def attributes_hextuples(record, fieldset = nil, params = {})
29
+ attributes = attributes_to_serialize
30
+ attributes = attributes.slice(*fieldset) if fieldset.present?
31
+ attributes = {} if fieldset == []
32
+
33
+ statements = attributes.flat_map do |_k, attr|
34
+ attr.serialize_hex(record, params)
35
+ end
36
+
37
+ statements.compact
38
+ end
39
+
40
+ def statements_hextuples(record, params = {})
41
+ statements = []
42
+
43
+ _statements&.map do |key|
44
+ send(key, record, params).each do |statement|
45
+ statements << if statement.is_a?(Array)
46
+ value_to_hex(statement[0], statement[1], statement[2], statement[3], params)
47
+ else
48
+ value_to_hex(statement.subject.to_s, statement.predicate, statement.object, statement.graph_name, params)
49
+ end
50
+ end
51
+ end
52
+
53
+ statements.compact
54
+ end
55
+
56
+ def record_hextuples(record, fieldset, includes_list, params = {})
57
+ if cache_store_instance
58
+ record_hex = Rails.cache.fetch(record.cache_key, expires_in: cache_length, race_condition_ttl: race_condition_ttl) do
59
+ temp_hex = []
60
+ temp_hex.concat attributes_hextuples(record, fieldset, params) if attributes_to_serialize.present?
61
+ temp_hex.concat statements_hextuples(record, params)
62
+ if cachable_relationships_to_serialize.present?
63
+ temp_hex.concat relationships_hextuples(record, cachable_relationships_to_serialize, fieldset, includes_list, params)
64
+ end
65
+ temp_hex
66
+ end
67
+ if uncachable_relationships_to_serialize.present?
68
+ record_hex.concat relationships_hextuples(record, uncachable_relationships_to_serialize, fieldset, includes_list, params)
69
+ end
70
+ else
71
+ record_hex = []
72
+ record_hex.concat attributes_hextuples(record, fieldset, params) if attributes_to_serialize.present?
73
+ record_hex.concat statements_hextuples(record, params)
74
+ if relationships_to_serialize.present?
75
+ record_hex.concat relationships_hextuples(record, nil, fieldset, includes_list, params)
76
+ end
77
+ end
78
+ record_hex
79
+ end
80
+
81
+ def get_included_records_hex(record, includes_list, known_included_objects, fieldsets, params = {})
82
+ return unless includes_list.present?
83
+
84
+ includes_list.sort.each_with_object([]) do |include_item, included_records|
85
+ items = parse_include_item(include_item)
86
+ remaining_items = remaining_items(items)
87
+
88
+ items.each do |item|
89
+ next unless relationships_to_serialize && relationships_to_serialize[item]
90
+
91
+ relationship_item = relationships_to_serialize[item]
92
+ next unless relationship_item.include_relationship?(record, params)
93
+
94
+ relationship_type = relationship_item.relationship_type
95
+
96
+ included_objects = relationship_item.fetch_associated_object(record, params)
97
+ next if included_objects.blank?
98
+
99
+ included_objects = [included_objects] unless relationship_type == :has_many
100
+
101
+ static_serializer = relationship_item.static_serializer
102
+ static_record_type = relationship_item.static_record_type
103
+
104
+ included_objects.each do |inc_obj|
105
+ serializer = static_serializer || relationship_item.serializer_for(inc_obj, params)
106
+ record_type = static_record_type || serializer.record_type
107
+
108
+ if remaining_items.present?
109
+ serializer_records =
110
+ serializer
111
+ .get_included_records_hex(inc_obj, remaining_items, known_included_objects, fieldsets, params)
112
+ included_records.concat(serializer_records) unless serializer_records.empty?
113
+ end
114
+
115
+ code = "#{record_type}_#{serializer.iri_from_record(inc_obj)}"
116
+ next if known_included_objects.key?(code)
117
+
118
+ known_included_objects[code] = inc_obj
119
+
120
+ included_records.concat(
121
+ serializer.record_hextuples(inc_obj, fieldsets[record_type], includes_list, params)
122
+ )
123
+ end
124
+ end
125
+ end
126
+ end
127
+ end
128
+ end
129
+ end
130
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RDF
4
+ module Serializers
5
+ module Statements
6
+ extend ActiveSupport::Concern
7
+
8
+ included do
9
+ class_attribute :_statements
10
+ self._statements ||= []
11
+ end
12
+
13
+ module ClassMethods
14
+ def inherited(base)
15
+ super
16
+ base._statements = _statements.dup
17
+ end
18
+
19
+ def statements(attribute)
20
+ self._statements << attribute
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
26
+
27
+ FastJsonapi::ObjectSerializer.include(RDF::Serializers::Statements)
@@ -2,6 +2,6 @@
2
2
 
3
3
  module RDFSerializers
4
4
  module Version
5
- VERSION = '0.0.5'
5
+ VERSION = '0.0.9'
6
6
  end
7
7
  end
@@ -1,4 +1,88 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'active_model_serializers/adapter/rdf'
4
- require 'active_model_serializers/serializer'
3
+ require 'fast_jsonapi'
4
+
5
+ require 'rdf/serializers/lookup_chain'
6
+ require 'rdf/serializers/config'
7
+ require 'rdf/serializers/statements'
8
+ require 'rdf/serializers/hextuple_serializer'
9
+ require 'rdf/serializers/data_type_helper'
10
+ require 'rdf/serializers/hdnjson_parser'
11
+ require 'rdf/serializers/serialization_core'
12
+ require 'rdf/serializers/object_serializer'
13
+ require 'rdf/serializers/scalar'
14
+ require 'rdf/serializers/relationship'
15
+ require 'rdf/serializers/nil_serializer'
16
+ require 'rdf/serializers/list_serializer'
17
+
18
+ module RDF
19
+ module Serializers
20
+ class << self
21
+ # Extracted from active_model_serializers
22
+ # @param resource [ActiveRecord::Base, ActiveModelSerializers::Model]
23
+ # @return [ActiveModel::Serializer]
24
+ # Preferentially returns
25
+ # 1. resource.serializer_class
26
+ # 2. ArraySerializer when resource is a collection
27
+ # 3. options[:serializer]
28
+ # 4. lookup serializer when resource is a Class
29
+ def serializer_for(resource_or_class, options = {})
30
+ if resource_or_class.respond_to?(:serializer_class)
31
+ resource_or_class.serializer_class
32
+ elsif resource_or_class.respond_to?(:to_ary)
33
+ unless resource_or_class.all? { |resource| resource.is_a?(resource_or_class.first.class) }
34
+ return ListSerializer
35
+ end
36
+
37
+ serializer_for(resource_or_class.first)
38
+ else
39
+ resource_class = resource_or_class.class == Class ? resource_or_class : resource_or_class.class
40
+ options.fetch(:serializer) { get_serializer_for(resource_class, options[:namespace]) }
41
+ end
42
+ end
43
+
44
+ private
45
+
46
+ # Extracted from active_model_serializers
47
+ # Find a serializer from a class and caches the lookup.
48
+ # Preferentially returns:
49
+ # 1. class name appended with "Serializer"
50
+ # 2. try again with superclass, if present
51
+ # 3. nil
52
+ def get_serializer_for(klass, namespace = nil)
53
+ return nil unless config.serializer_lookup_enabled
54
+
55
+ return NilSerializer if klass == NilClass
56
+
57
+ cache_key = ActiveSupport::Cache.expand_cache_key(klass, namespace)
58
+ serializers_cache.fetch_or_store(cache_key) do
59
+ # NOTE(beauby): When we drop 1.9.3 support we can lazify the map for perfs.
60
+ lookup_chain = serializer_lookup_chain_for(klass, namespace)
61
+ serializer_class = lookup_chain.map(&:safe_constantize).find do |x|
62
+ x&.include?(FastJsonapi::SerializationCore)
63
+ end
64
+
65
+ if serializer_class
66
+ serializer_class
67
+ elsif klass.superclass
68
+ get_serializer_for(klass.superclass, namespace)
69
+ end
70
+ end
71
+ end
72
+
73
+ # Extracted from active_model_serializers
74
+ # Used to cache serializer name => serializer class
75
+ # when looked up by Serializer.get_serializer_for.
76
+ def serializers_cache
77
+ @serializers_cache ||= Concurrent::Map.new
78
+ end
79
+
80
+ def serializer_lookup_chain_for(klass, namespace = nil)
81
+ lookups = config.serializer_lookup_chain
82
+ Array[*lookups].flat_map do |lookup|
83
+ lookup.call(klass, namespace)
84
+ end.compact
85
+ end
86
+ end
87
+ end
88
+ end
metadata CHANGED
@@ -1,29 +1,43 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rdf-serializers
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.5
4
+ version: 0.0.9
5
5
  platform: ruby
6
6
  authors:
7
7
  - Arthur Dingemans
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-12-11 00:00:00.000000000 Z
11
+ date: 2021-08-26 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
- name: active_model_serializers
14
+ name: jsonapi-serializer
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
17
  - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: '0.10'
19
+ version: 2.0.0
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
- version: '0.10'
26
+ version: 2.0.0
27
+ - !ruby/object:Gem::Dependency
28
+ name: oj
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
27
41
  - !ruby/object:Gem::Dependency
28
42
  name: railties
29
43
  requirement: !ruby/object:Gem::Requirement
@@ -33,7 +47,7 @@ dependencies:
33
47
  version: 4.2.0
34
48
  - - "<"
35
49
  - !ruby/object:Gem::Version
36
- version: '6'
50
+ version: '7'
37
51
  type: :runtime
38
52
  prerelease: false
39
53
  version_requirements: !ruby/object:Gem::Requirement
@@ -43,7 +57,7 @@ dependencies:
43
57
  version: 4.2.0
44
58
  - - "<"
45
59
  - !ruby/object:Gem::Version
46
- version: '6'
60
+ version: '7'
47
61
  - !ruby/object:Gem::Dependency
48
62
  name: rdf
49
63
  requirement: !ruby/object:Gem::Requirement
@@ -58,22 +72,32 @@ dependencies:
58
72
  - - "~>"
59
73
  - !ruby/object:Gem::Version
60
74
  version: '3.0'
61
- description:
75
+ description:
62
76
  email: arthur@argu.co
63
77
  executables: []
64
78
  extensions: []
65
79
  extra_rdoc_files: []
66
80
  files:
67
- - lib/active_model_serializers/adapter/rdf.rb
68
- - lib/active_model_serializers/adapter/rdf/relationship.rb
69
- - lib/active_model_serializers/serializer.rb
70
81
  - lib/rdf/serializers.rb
82
+ - lib/rdf/serializers/config.rb
83
+ - lib/rdf/serializers/data_type_helper.rb
84
+ - lib/rdf/serializers/hdnjson_parser.rb
85
+ - lib/rdf/serializers/hextuple_serializer.rb
86
+ - lib/rdf/serializers/list_serializer.rb
87
+ - lib/rdf/serializers/lookup_chain.rb
88
+ - lib/rdf/serializers/nil_serializer.rb
89
+ - lib/rdf/serializers/object_serializer.rb
90
+ - lib/rdf/serializers/relationship.rb
71
91
  - lib/rdf/serializers/renderers.rb
92
+ - lib/rdf/serializers/scalar.rb
93
+ - lib/rdf/serializers/serialization_core.rb
94
+ - lib/rdf/serializers/statements.rb
72
95
  - lib/rdf/serializers/version.rb
73
- homepage: https://github.com/argu-co/rdf-serializers
74
- licenses: []
96
+ homepage: https://github.com/ontola/rdf-serializers
97
+ licenses:
98
+ - MIT
75
99
  metadata: {}
76
- post_install_message:
100
+ post_install_message:
77
101
  rdoc_options: []
78
102
  require_paths:
79
103
  - lib
@@ -88,9 +112,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
88
112
  - !ruby/object:Gem::Version
89
113
  version: '0'
90
114
  requirements: []
91
- rubyforge_project:
92
- rubygems_version: 2.6.11
93
- signing_key:
115
+ rubygems_version: 3.1.2
116
+ signing_key:
94
117
  specification_version: 4
95
118
  summary: Adds RDF serialization, like n-triples or turtle, to active model serializers
96
119
  test_files: []
@@ -1,60 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module ActiveModelSerializers
4
- module Adapter
5
- class RDF
6
- class Relationship < JsonApi::Relationship
7
- def triples
8
- return [] if subject.blank? || predicate.blank? || data.empty?
9
- data.map do |object|
10
- raise "#{object} is not a RDF::Resource but a #{object.class}" unless object.is_a?(::RDF::Resource)
11
- ::RDF::Statement.new(subject, predicate, object)
12
- end
13
- end
14
-
15
- private
16
-
17
- def data
18
- @data ||=
19
- if association.collection?
20
- objects_for_many(association).compact
21
- else
22
- [object_for_one(association)].compact
23
- end
24
- end
25
-
26
- def objects_for_many(association)
27
- collection_serializer = association.lazy_association.serializer
28
- if collection_serializer.respond_to?(:each)
29
- collection_serializer.map do |serializer|
30
- serializer.read_attribute_for_serialization(:rdf_subject)
31
- end
32
- else
33
- []
34
- end
35
- end
36
-
37
- def object_for_one(association)
38
- if belongs_to_id_on_self?(association)
39
- parent_serializer.read_attribute_for_serialization(:rdf_subject)
40
- else
41
- serializer = association.lazy_association.serializer
42
- if (virtual_value = association.virtual_value)
43
- virtual_value[:id]
44
- elsif serializer && association.object
45
- serializer.read_attribute_for_serialization(:rdf_subject)
46
- end
47
- end
48
- end
49
-
50
- def predicate
51
- @predicate ||= association.reflection.options[:predicate]
52
- end
53
-
54
- def subject
55
- @subject ||= parent_serializer.read_attribute_for_serialization(:rdf_subject)
56
- end
57
- end
58
- end
59
- end
60
- end
@@ -1,126 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module ActiveModelSerializers
4
- module Adapter
5
- class RDF < Base
6
- extend ActiveSupport::Autoload
7
- autoload :Relationship
8
-
9
- delegate :object, to: :serializer
10
- delegate :dump, :triples, to: :graph
11
-
12
- def initialize(serializer, options = {})
13
- super
14
- @include_directive = JSONAPI::IncludeDirective.new(options[:include], allow_wildcard: true)
15
- @fieldset = options[:fieldset] || ActiveModel::Serializer::Fieldset.new(options.delete(:fields))
16
- @resource_identifiers = Set.new
17
- end
18
-
19
- protected
20
-
21
- attr_reader :fieldset
22
-
23
- private
24
-
25
- def add_attribute(subject, predicate, value)
26
- return unless predicate
27
- value = value.respond_to?(:each) ? value : [value]
28
- value.compact.map { |v| add_triple(subject, predicate, v) }
29
- end
30
-
31
- def add_triple(subject, predicate, object)
32
- obj =
33
- case object
34
- when ::RDF::Term
35
- object
36
- when ActiveSupport::TimeWithZone
37
- ::RDF::Literal(object.to_datetime)
38
- else
39
- ::RDF::Literal(object)
40
- end
41
- @graph << ::RDF::Statement.new(subject, ::RDF::URI(predicate), obj)
42
- end
43
-
44
- def attributes_for(serializer, fields)
45
- serializer.attributes(fields).each do |key, value|
46
- add_attribute(
47
- serializer.read_attribute_for_serialization(:rdf_subject),
48
- serializer.class._attributes_data[key].try(:options).try(:[], :predicate),
49
- value
50
- )
51
- end
52
- end
53
-
54
- def custom_triples_for(serializer)
55
- serializer.class.try(:_triples)&.map do |key|
56
- serializer.read_attribute_for_serialization(key).each do |triple|
57
- @graph << triple
58
- end
59
- end
60
- end
61
-
62
- def graph
63
- return @graph if @graph.present?
64
- @graph = ::RDF::Graph.new
65
-
66
- serializers.each { |serializer| process_resource(serializer, @include_directive) }
67
- serializers.each { |serializer| process_relationships(serializer, @include_directive) }
68
- instance_options[:meta]&.each { |meta| add_triple(*meta) }
69
-
70
- @graph
71
- end
72
-
73
- def process_relationship(serializer, include_slice)
74
- return serializer.each { |s| process_relationship(s, include_slice) } if serializer.respond_to?(:each)
75
- return unless serializer&.object && process_resource(serializer, include_slice)
76
- process_relationships(serializer, include_slice)
77
- end
78
-
79
- def process_relationships(serializer, include_slice)
80
- return unless serializer.respond_to?(:associations)
81
- serializer.associations(include_slice).each do |association|
82
- process_relationship(association.lazy_association.serializer, include_slice[association.key])
83
- end
84
- end
85
-
86
- def process_resource(serializer, include_slice = {})
87
- if serializer.is_a?(ActiveModel::Serializer::CollectionSerializer)
88
- return serializer.map { |child| process_resource(child, include_slice) }
89
- end
90
- return unless serializer.respond_to?(:rdf_subject) || serializer.object.respond_to?(:rdf_subject)
91
- return false unless @resource_identifiers.add?(serializer.read_attribute_for_serialization(:rdf_subject))
92
- resource_object_for(serializer, include_slice)
93
- true
94
- end
95
-
96
- def relationships_for(serializer, requested_associations, include_slice)
97
- include_directive = JSONAPI::IncludeDirective.new(requested_associations, allow_wildcard: true)
98
- serializer.associations(include_directive, include_slice).each do |association|
99
- Relationship.new(serializer, instance_options, association).triples.each do |triple|
100
- @graph << triple
101
- end
102
- end
103
- end
104
-
105
- def resource_object_for(serializer, include_slice = {})
106
- type = type_for(serializer, instance_options).to_s
107
- serializer.fetch(self) do
108
- break nil if serializer.read_attribute_for_serialization(:rdf_subject).nil?
109
- requested_fields = fieldset&.fields_for(type)
110
- attributes_for(serializer, requested_fields)
111
- custom_triples_for(serializer)
112
- end
113
- requested_associations = fieldset.fields_for(type) || '*'
114
- relationships_for(serializer, requested_associations, include_slice)
115
- end
116
-
117
- def serializers
118
- serializer.respond_to?(:each) ? serializer : [serializer]
119
- end
120
-
121
- def type_for(serializer, instance_options)
122
- JsonApi::ResourceIdentifier.new(serializer, instance_options).as_json[:type]
123
- end
124
- end
125
- end
126
- end
@@ -1,12 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module ActiveModel
4
- class Serializer
5
- class_attribute :_triples
6
-
7
- def self.triples(attribute)
8
- self._triples ||= []
9
- self._triples << attribute
10
- end
11
- end
12
- end