rdf-serializers 0.0.4 → 0.0.8

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: baf9653c33444a315a892a3720aa0be0503540db
4
- data.tar.gz: ca4fbe6d2ad6b87d3fabd8f1b68167fd0470475b
2
+ SHA256:
3
+ metadata.gz: 3782c211f6d60abc17cf58d6c0566c94913f5429d19f8c7ba633687dabf95ea8
4
+ data.tar.gz: c2cd93434a13082bd8d8da0b15b0d4554d7215b77e4e89ae2cf2fbeacb566957
5
5
  SHA512:
6
- metadata.gz: 06fda50c2eaec5b6bb346e31f40d78d24645211c3d87eef0c6206492d47f481cbde8a0a540ae447a0fdc75a6e31be45e9686bdfd252121f7bb998aa89c7db9ac
7
- data.tar.gz: 94a3b7701bd3291a3cff825782ff06945d1382336574de174030991f2c3f20558537c2ce6423fc8d8fd90c460094f3579c6f2d889abdf2af90c2215691ac8a2a
6
+ metadata.gz: c4938923d6d9bcb9b41e4c774025786dddf554dd8089691604e24235fff5311b8cb9cc39382a3e808d3aedd7909ee7e38fd1c273526aa469699413807cc6311f
7
+ data.tar.gz: 0a4131a7e8e1054620945780c4a430e33c846f78f2eedf450a881cfb2452ffbbd852f58c765540bbf329791b6614f40afe2c797d6f942f4021eca882284425aa
@@ -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,168 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RDF
4
+ module Serializers
5
+ module ObjectSerializer
6
+ extend ActiveSupport::Concern
7
+ include FastJsonapi::ObjectSerializer
8
+ include SerializationCore
9
+
10
+ def dump(*args, **options)
11
+ case args.first
12
+ when :hndjson
13
+ render_hndjson
14
+ else
15
+ render_repository(*args, options)
16
+ end
17
+ end
18
+
19
+ def triples(*args, **options)
20
+ if include_named_graphs?(*args)
21
+ repository.triples(*args, options)
22
+ else
23
+ repository.project_graph(nil).triples(*args, options)
24
+ end
25
+ end
26
+
27
+ private
28
+
29
+ def blank_node(id)
30
+ @blank_nodes ||= {}
31
+ @blank_nodes[id] ||= RDF::Node(id)
32
+ end
33
+
34
+ def hextuples_for_collection
35
+ data = []
36
+ fieldset = @fieldsets[self.class.record_type.to_sym]
37
+ @resource.each do |record|
38
+ data.concat self.class.record_hextuples(record, fieldset, @includes, @params)
39
+ next unless @includes.present?
40
+
41
+ data.concat(
42
+ self.class.get_included_records_hex(record, @includes, @known_included_objects, @fieldsets, @params)
43
+ )
44
+ end
45
+
46
+ data
47
+ end
48
+
49
+ def hextuples_for_one_record
50
+ serializable_hextuples = []
51
+
52
+ serializable_hextuples.concat self.class.record_hextuples(
53
+ @resource,
54
+ @fieldsets[self.class.record_type.to_sym],
55
+ @includes,
56
+ @params
57
+ )
58
+
59
+ if @includes.present?
60
+ serializable_hextuples.concat self.class.get_included_records_hex(
61
+ @resource,
62
+ @includes,
63
+ @known_included_objects,
64
+ @fieldsets,
65
+ @params
66
+ )
67
+ end
68
+
69
+ serializable_hextuples
70
+ end
71
+
72
+ def include_named_graphs?(*args)
73
+ ::RDF::Serializers.config.always_include_named_graphs ||
74
+ ::RDF::Writer.for(*args.presence || :nquads).instance_methods.include?(:write_quad)
75
+ end
76
+
77
+ def meta_hextuples
78
+ return [] unless @meta.is_a?(Array)
79
+
80
+ @meta.map do |statement|
81
+ if statement.is_a?(Array)
82
+ value_to_hex(statement[0], statement[1], statement[2], statement[3], @params)
83
+ else
84
+ value_to_hex(statement.subject.to_s, statement.predicate, statement.object, statement.graph_name, @params)
85
+ end
86
+ end.compact
87
+ end
88
+
89
+ def repository
90
+ return @repository if @repository.present?
91
+
92
+ @repository = ::RDF::Repository.new
93
+ parser = HndJSONParser.new
94
+
95
+ serializable_hextuples.compact.each do |hextuple|
96
+ @repository << parser.parse_hex(hextuple)
97
+ end
98
+
99
+ @repository
100
+ end
101
+
102
+ def render_hndjson
103
+ serializable_hextuples
104
+ .map { |s| Oj.fast_generate(s) }
105
+ .join("\n")
106
+ end
107
+
108
+ def render_repository(*args, options)
109
+ if include_named_graphs?(*args)
110
+ repository.dump(*args, options)
111
+ else
112
+ repository.project_graph(nil).dump(*args, options)
113
+ end
114
+ end
115
+
116
+ def serializable_hextuples
117
+ if is_collection?(@resource, @is_collection)
118
+ hextuples_for_collection + meta_hextuples
119
+ elsif !@resource
120
+ []
121
+ else
122
+ hextuples_for_one_record + meta_hextuples
123
+ end
124
+ end
125
+
126
+ class_methods do
127
+ def create_relationship(base_key, relationship_type, options, block)
128
+ association = options.delete(:association)
129
+ image = options.delete(:image)
130
+ predicate = options.delete(:predicate)
131
+ sequence = options.delete(:sequence)
132
+ relation = super
133
+ relation.association = association
134
+ relation.image = image
135
+ relation.predicate = predicate
136
+ relation.sequence = sequence
137
+
138
+ relation
139
+ end
140
+
141
+ # Checks for the `class_name` property on the Model's association to
142
+ # determine a serializer.
143
+ def association_serializer_for(name)
144
+ model_class_name = self.name.to_s.demodulize.classify.gsub(/Serializer$/, '')
145
+ model_class = model_class_name.safe_constantize
146
+
147
+ association_class_name = model_class.try(:reflect_on_association, name)&.class_name
148
+
149
+ return nil unless association_class_name
150
+
151
+ serializer_for(association_class_name)
152
+ end
153
+
154
+ def serializer_for(name)
155
+ associatopm_serializer = association_serializer_for(name)
156
+ return associatopm_serializer if associatopm_serializer
157
+
158
+ begin
159
+ RDF::Serializers.serializer_for(const_get(name.to_s.classify))
160
+ rescue NameError
161
+ raise NameError, "#{self.name} cannot resolve a serializer class for '#{name}'. " \
162
+ 'Consider specifying the serializer directly through options[:serializer].'
163
+ end
164
+ end
165
+ end
166
+ end
167
+ end
168
+ 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.4'
5
+ VERSION = '0.0.8'
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,29 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rdf-serializers
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.4
4
+ version: 0.0.8
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-24 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
- name: active_model_serializers
14
+ name: fast_jsonapi
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - "~>"
17
+ - - ">="
18
18
  - !ruby/object:Gem::Version
19
- version: '0.10'
19
+ version: '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: '0'
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: railties
29
29
  requirement: !ruby/object:Gem::Requirement
@@ -33,7 +33,7 @@ dependencies:
33
33
  version: 4.2.0
34
34
  - - "<"
35
35
  - !ruby/object:Gem::Version
36
- version: '6'
36
+ version: '7'
37
37
  type: :runtime
38
38
  prerelease: false
39
39
  version_requirements: !ruby/object:Gem::Requirement
@@ -43,37 +43,47 @@ dependencies:
43
43
  version: 4.2.0
44
44
  - - "<"
45
45
  - !ruby/object:Gem::Version
46
- version: '6'
46
+ version: '7'
47
47
  - !ruby/object:Gem::Dependency
48
48
  name: rdf
49
49
  requirement: !ruby/object:Gem::Requirement
50
50
  requirements:
51
51
  - - "~>"
52
52
  - !ruby/object:Gem::Version
53
- version: '2.2'
53
+ version: '3.0'
54
54
  type: :runtime
55
55
  prerelease: false
56
56
  version_requirements: !ruby/object:Gem::Requirement
57
57
  requirements:
58
58
  - - "~>"
59
59
  - !ruby/object:Gem::Version
60
- version: '2.2'
61
- description:
60
+ version: '3.0'
61
+ description:
62
62
  email: arthur@argu.co
63
63
  executables: []
64
64
  extensions: []
65
65
  extra_rdoc_files: []
66
66
  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
67
  - lib/rdf/serializers.rb
68
+ - lib/rdf/serializers/config.rb
69
+ - lib/rdf/serializers/data_type_helper.rb
70
+ - lib/rdf/serializers/hdnjson_parser.rb
71
+ - lib/rdf/serializers/hextuple_serializer.rb
72
+ - lib/rdf/serializers/list_serializer.rb
73
+ - lib/rdf/serializers/lookup_chain.rb
74
+ - lib/rdf/serializers/nil_serializer.rb
75
+ - lib/rdf/serializers/object_serializer.rb
76
+ - lib/rdf/serializers/relationship.rb
71
77
  - lib/rdf/serializers/renderers.rb
78
+ - lib/rdf/serializers/scalar.rb
79
+ - lib/rdf/serializers/serialization_core.rb
80
+ - lib/rdf/serializers/statements.rb
72
81
  - lib/rdf/serializers/version.rb
73
- homepage: https://github.com/argu-co/rdf-serializers
74
- licenses: []
82
+ homepage: https://github.com/ontola/rdf-serializers
83
+ licenses:
84
+ - MIT
75
85
  metadata: {}
76
- post_install_message:
86
+ post_install_message:
77
87
  rdoc_options: []
78
88
  require_paths:
79
89
  - lib
@@ -88,9 +98,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
88
98
  - !ruby/object:Gem::Version
89
99
  version: '0'
90
100
  requirements: []
91
- rubyforge_project:
92
- rubygems_version: 2.6.11
93
- signing_key:
101
+ rubygems_version: 3.1.2
102
+ signing_key:
94
103
  specification_version: 4
95
104
  summary: Adds RDF serialization, like n-triples or turtle, to active model serializers
96
105
  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,125 +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(data, subject, value)
26
- predicate = data.options[:predicate]
27
- return unless predicate
28
- value = value.respond_to?(:each) ? value : [value]
29
- value.compact.map { |v| add_triple(subject, predicate, v) }
30
- end
31
-
32
- def add_triple(subject, predicate, object)
33
- obj =
34
- case object
35
- when ::RDF::Term
36
- object
37
- when ActiveSupport::TimeWithZone
38
- ::RDF::Literal(object.to_datetime)
39
- else
40
- ::RDF::Literal(object)
41
- end
42
- @graph << ::RDF::Statement.new(subject, ::RDF::URI(predicate), obj)
43
- end
44
-
45
- def attributes_for(serializer, fields)
46
- serializer.class._attributes_data.map do |key, data|
47
- next if data.excluded?(serializer)
48
- next unless fields.nil? || fields.include?(key)
49
- add_attribute(data, serializer.read_attribute_for_serialization(:rdf_subject), serializer.attributes[key])
50
- end
51
- end
52
-
53
- def custom_triples_for(serializer)
54
- serializer.class.try(:_triples)&.map do |key|
55
- serializer.read_attribute_for_serialization(key).each do |triple|
56
- @graph << triple
57
- end
58
- end
59
- end
60
-
61
- def graph
62
- return @graph if @graph.present?
63
- @graph = ::RDF::Graph.new
64
-
65
- serializers.each { |serializer| process_resource(serializer, @include_directive) }
66
- serializers.each { |serializer| process_relationships(serializer, @include_directive) }
67
- instance_options[:meta]&.each { |meta| add_triple(*meta) }
68
-
69
- @graph
70
- end
71
-
72
- def process_relationship(serializer, include_slice)
73
- return serializer.each { |s| process_relationship(s, include_slice) } if serializer.respond_to?(:each)
74
- return unless serializer&.object && process_resource(serializer, include_slice)
75
- process_relationships(serializer, include_slice)
76
- end
77
-
78
- def process_relationships(serializer, include_slice)
79
- return unless serializer.respond_to?(:associations)
80
- serializer.associations(include_slice).each do |association|
81
- process_relationship(association.lazy_association.serializer, include_slice[association.key])
82
- end
83
- end
84
-
85
- def process_resource(serializer, include_slice = {})
86
- if serializer.is_a?(ActiveModel::Serializer::CollectionSerializer)
87
- return serializer.map { |child| process_resource(child, include_slice) }
88
- end
89
- return unless serializer.respond_to?(:rdf_subject) || serializer.object.respond_to?(:rdf_subject)
90
- return false unless @resource_identifiers.add?(serializer.read_attribute_for_serialization(:rdf_subject))
91
- resource_object_for(serializer, include_slice)
92
- true
93
- end
94
-
95
- def relationships_for(serializer, requested_associations, include_slice)
96
- include_directive = JSONAPI::IncludeDirective.new(requested_associations, allow_wildcard: true)
97
- serializer.associations(include_directive, include_slice).each do |association|
98
- Relationship.new(serializer, instance_options, association).triples.each do |triple|
99
- @graph << triple
100
- end
101
- end
102
- end
103
-
104
- def resource_object_for(serializer, include_slice = {})
105
- type = type_for(serializer, instance_options).to_s
106
- serializer.fetch(self) do
107
- break nil if serializer.read_attribute_for_serialization(:rdf_subject).nil?
108
- requested_fields = fieldset&.fields_for(type)
109
- attributes_for(serializer, requested_fields)
110
- custom_triples_for(serializer)
111
- end
112
- requested_associations = fieldset.fields_for(type) || '*'
113
- relationships_for(serializer, requested_associations, include_slice)
114
- end
115
-
116
- def serializers
117
- serializer.respond_to?(:each) ? serializer : [serializer]
118
- end
119
-
120
- def type_for(serializer, instance_options)
121
- JsonApi::ResourceIdentifier.new(serializer, instance_options).as_json[:type]
122
- end
123
- end
124
- end
125
- 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