rdf-serializers 0.0.6 → 0.0.10

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 57bc75187cb8b34149be75b41e6be7c195c22fef
4
- data.tar.gz: 339e1701a6a8f04dfb4776bbd4cb3615765bc401
2
+ SHA256:
3
+ metadata.gz: b3eefb3fefa363f766d3a84709471d7e89cf6bc73f6bcf8ad112f36b4da21b2d
4
+ data.tar.gz: 7eb9282e4c581e7b2c0316667115a07c6054389147ad0f340dc8312c39254b01
5
5
  SHA512:
6
- metadata.gz: 0526660616a11ce3d3fb062dad5c47f968123fc657f5c941fc3250c8f98f7f70197255485f9d77266143e69e7ee0ef590def34f2e17be00d6f272788e592c4ab
7
- data.tar.gz: 73f4828406d358c6596eb12ce66bb40b6030656eeb599a626c3012e8b0772abc3f8ecf386786505308c2e0ce9b8ddab1431bba2816a8e880dd2596ec0476c3ff
6
+ metadata.gz: 8490b88c0abf65e096932fc3061dee4c7dc04591cdae1fddcb8a99dfe0f1579c3fded9e30f719fee447420f1f23258479589cf0735e430f8dfe5042644c584c9
7
+ data.tar.gz: f0e3e87cbb50b888aaaadce341784bff28e7964ae22d2edb50461e75b6d074f772172b3fb3de6c18560da685960a909cc3be86a9111c32d6f5653a2530f9c076
@@ -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,67 @@
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
+ return [] if iris.blank?
20
+
21
+ sequence = RDF::Node.new
22
+
23
+ [
24
+ value_to_hex(iri_from_record(record).to_s, predicate, sequence, nil, serialization_params),
25
+ value_to_hex(sequence, RDF.type, RDF.Seq, nil, serialization_params)
26
+ ] + iris.map.with_index do |iri, index|
27
+ value_to_hex(sequence, RDF["_#{index}"], iri, nil, serialization_params)
28
+ end
29
+ end
30
+
31
+ def relationship_statements(record, iris, serialization_params)
32
+ iris.map do |related_iri|
33
+ value_to_hex(
34
+ iri_from_record(record).to_s,
35
+ predicate,
36
+ related_iri,
37
+ nil,
38
+ serialization_params
39
+ )
40
+ end
41
+ end
42
+
43
+ def include_relationship?(record, serialization_params, included = false)
44
+ return false if lazy_load_data && !included
45
+
46
+ super(record, serialization_params)
47
+ end
48
+
49
+ def iris_from_record_and_relationship(record, params = {})
50
+ initialize_static_serializer unless @initialized_static_serializer
51
+
52
+ associated_object = fetch_associated_object(record, params)
53
+ return [] unless associated_object
54
+
55
+ if associated_object.respond_to? :map
56
+ return associated_object.compact.map do |object|
57
+ iri_from_record(object)
58
+ end
59
+ end
60
+
61
+ [iri_from_record(associated_object)]
62
+ end
63
+ end
64
+ end
65
+ end
66
+
67
+ 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.6'
5
+ VERSION = '0.0.10'
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.6
4
+ version: 0.0.10
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-09-16 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: :repository
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, graph = nil)
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
- @repository << ::RDF::Statement.new(subject, ::RDF::URI(predicate), obj, graph_name: graph)
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
- @repository << triple
58
- end
59
- end
60
- end
61
-
62
- def repository
63
- return @repository if @repository.present?
64
- @repository = ::RDF::Repository.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
- @repository
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
- @repository << 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