emp_json 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 9c980a0bd41e0236610d10af6521e6e41ed39329973c51acdaf48a5567e6162a
4
+ data.tar.gz: a260ad775292dfbdc03c25a2d9472164687b918d73f85af4706a1cad7e31f367
5
+ SHA512:
6
+ metadata.gz: 2b7c38569fc8700ea138d1fb0fc6dfb82caee3b8c3e805f50d5c06a7912f1bba3233c568980b4614d9d8ac4477ced6fb07dfdb3482fa00ec4eafbb69722500af
7
+ data.tar.gz: 591b643b63e2d0e497c7ba8c0989518a357b34d0b3d36b04b72872d9c11637bf5a28215aeb23f779ba5066c939bd72ad680d7e68d3d00c1b70d0dc27fb5c92e3
data/.rubocop.yml ADDED
@@ -0,0 +1,24 @@
1
+ AllCops:
2
+ TargetRubyVersion: 2.7
3
+ NewCops: enable
4
+
5
+
6
+ Style/StringLiterals:
7
+ Enabled: true
8
+ EnforcedStyle: double_quotes
9
+
10
+ Style/StringLiteralsInInterpolation:
11
+ Enabled: true
12
+ EnforcedStyle: double_quotes
13
+
14
+ Metrics/PerceivedComplexity:
15
+ Max: 10
16
+
17
+ Metrics/CyclomaticComplexity:
18
+ Max: 10
19
+
20
+ Metrics/MethodLength:
21
+ Max: 20
22
+
23
+ Layout/LineLength:
24
+ Max: 120
data/CHANGELOG.md ADDED
@@ -0,0 +1,5 @@
1
+ ## [Unreleased]
2
+
3
+ ## [0.1.0] - 2022-05-09
4
+
5
+ - Initial release
@@ -0,0 +1 @@
1
+ Be civil
data/Gemfile ADDED
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ source "https://rubygems.org"
4
+
5
+ # Specify your gem's dependencies in emp_json_rb.gemspec
6
+ gemspec
7
+
8
+ gem "rake", "~> 13.0"
9
+
10
+ gem "minitest", "~> 5.0"
11
+
12
+ gem "rubocop", "~> 1.21"
13
+
14
+ gem "activemodel", "~> 7"
15
+
16
+ gem "activesupport", "~> 7"
17
+
18
+ gem "rdf"
data/Gemfile.lock ADDED
@@ -0,0 +1,62 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ emp_json (1.1.0)
5
+
6
+ GEM
7
+ remote: https://rubygems.org/
8
+ specs:
9
+ activemodel (7.0.3)
10
+ activesupport (= 7.0.3)
11
+ activesupport (7.0.3)
12
+ concurrent-ruby (~> 1.0, >= 1.0.2)
13
+ i18n (>= 1.6, < 2)
14
+ minitest (>= 5.1)
15
+ tzinfo (~> 2.0)
16
+ ast (2.4.2)
17
+ concurrent-ruby (1.1.10)
18
+ i18n (1.10.0)
19
+ concurrent-ruby (~> 1.0)
20
+ link_header (0.0.8)
21
+ minitest (5.15.0)
22
+ parallel (1.22.1)
23
+ parser (3.1.2.0)
24
+ ast (~> 2.4.1)
25
+ rainbow (3.1.1)
26
+ rake (13.0.6)
27
+ rdf (3.2.7)
28
+ link_header (~> 0.0, >= 0.0.8)
29
+ regexp_parser (2.3.1)
30
+ rexml (3.2.5)
31
+ rubocop (1.29.0)
32
+ parallel (~> 1.10)
33
+ parser (>= 3.1.0.0)
34
+ rainbow (>= 2.2.2, < 4.0)
35
+ regexp_parser (>= 1.8, < 3.0)
36
+ rexml (>= 3.2.5, < 4.0)
37
+ rubocop-ast (>= 1.17.0, < 2.0)
38
+ ruby-progressbar (~> 1.7)
39
+ unicode-display_width (>= 1.4.0, < 3.0)
40
+ rubocop-ast (1.17.0)
41
+ parser (>= 3.1.1.0)
42
+ ruby-progressbar (1.11.0)
43
+ tzinfo (2.0.4)
44
+ concurrent-ruby (~> 1.0)
45
+ unicode-display_width (2.1.0)
46
+
47
+ PLATFORMS
48
+ arm64-darwin-21
49
+ ruby
50
+ x86_64-linux
51
+
52
+ DEPENDENCIES
53
+ activemodel (~> 7)
54
+ activesupport (~> 7)
55
+ emp_json!
56
+ minitest (~> 5.0)
57
+ rake (~> 13.0)
58
+ rdf
59
+ rubocop (~> 1.21)
60
+
61
+ BUNDLED WITH
62
+ 2.3.9
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2022 Thom van Kalkeren
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,33 @@
1
+ # Empathy::Json
2
+
3
+ A gem to serialize and manipulate [EmpJson](https://empathy.tools/specifications/emp-json) slices.
4
+
5
+ ## Installation
6
+
7
+ Install the gem and add to the application's Gemfile by executing:
8
+
9
+ $ bundle add emp_json_rb
10
+
11
+ If bundler is not being used to manage dependencies, install the gem by executing:
12
+
13
+ $ gem install emp_json_rb
14
+
15
+ ## Usage
16
+
17
+ The module was built to work with [rdf-serializers](https://github.com/ontola/rdf-serializers), and uses serializer descriptions from that gem.
18
+
19
+ Include the `Empathy::Json::Slices` module and call `emp_json_hash(resource)` for a hash representation or `render_emp_json(resource)` for a stringified version.
20
+
21
+ ## Development
22
+
23
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
24
+
25
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
26
+
27
+ ## Contributing
28
+
29
+ Bug reports and pull requests are welcome on GitHub at https://github.com/empathy-tools/emp_json_rb. Contributors are expected to adhere to the [code of conduct](https://github.com/empathy-tools/emp_json_rb/blob/main/CODE_OF_CONDUCT.md).
30
+
31
+ ## License
32
+
33
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ require "rake/testtask"
5
+
6
+ Rake::TestTask.new(:test) do |t|
7
+ t.libs << "test"
8
+ t.libs << "lib"
9
+ t.test_files = FileList["test/**/*.rb"]
10
+ end
11
+
12
+ require "rubocop/rake_task"
13
+
14
+ RuboCop::RakeTask.new
15
+
16
+ task default: %i[test rubocop]
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "lib/empathy/emp_json/version"
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = "emp_json"
7
+ spec.version = Empathy::EmpJson::VERSION
8
+ spec.authors = ["Thom van Kalkeren"]
9
+ spec.email = ["thom@ontola.io"]
10
+
11
+ spec.summary = "Serializer for the EmpJson specification."
12
+ spec.description = "Serializer for the EmpJson specification."
13
+ spec.homepage = "https://empathy.tools/tools/emp-json"
14
+ spec.license = "MIT"
15
+ spec.required_ruby_version = ">= 2.7.0.preview1"
16
+
17
+ # spec.metadata["allowed_push_host"] = "TODO: Set to your gem server 'https://example.com'"
18
+
19
+ spec.metadata["homepage_uri"] = spec.homepage
20
+ spec.metadata["source_code_uri"] = "https://github.com/empathy-tools/emp_json_rb"
21
+ spec.metadata["bug_tracker_uri"] = "https://github.com/empathy-tools/emp_json_rb/issues"
22
+ spec.metadata["changelog_uri"] = "https://github.com/empathy-tools/emp_json_rb/CHANGELOG.md"
23
+
24
+ # Specify which files should be added to the gem when it is released.
25
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
26
+ spec.files = Dir.chdir(__dir__) do
27
+ `git ls-files -z`.split("\x0").reject do |f|
28
+ (f == __FILE__) || f.match(%r{\A(?:(?:bin|test|spec|features)/|\.(?:git|travis|circleci)|appveyor)})
29
+ end
30
+ end
31
+ spec.bindir = "exe"
32
+ spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
33
+ spec.require_paths = ["lib"]
34
+
35
+ # Uncomment to register a new dependency of your gem
36
+ # spec.add_dependency "example-gem", "~> 1.0"
37
+
38
+ # For more information and examples about making a new gem, check out our
39
+ # guide at: https://bundler.io/guides/creating_gem.html
40
+ spec.metadata["rubygems_mfa_required"] = "true"
41
+ end
@@ -0,0 +1,127 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/time_with_zone"
4
+
5
+ module Empathy
6
+ module EmpJson
7
+ module Helpers
8
+ # Functions relating to serializing primitives.
9
+ module Primitives # rubocop:disable Metrics/ModuleLength
10
+ EMP_TYPE_GLOBAL_ID = "id"
11
+ EMP_TYPE_LOCAL_ID = "lid"
12
+ EMP_TYPE_DATETIME = "dt"
13
+ EMP_TYPE_STRING = "s"
14
+ EMP_TYPE_BOOL = "b"
15
+ EMP_TYPE_INTEGER = "i"
16
+ EMP_TYPE_LONG = "l"
17
+ EMP_TYPE_PRIMITIVE = "p"
18
+ EMP_TYPE_LANGSTRING = "ls"
19
+
20
+ RDF_RDFV = "http://www.w3.org/1999/02/22-rdf-syntax-ns#"
21
+ RDF_RDFV_LANGSTRING = "#{RDF_RDFV}langString"
22
+ RDF_RDFS = "http://www.w3.org/2000/01/rdf-schema#"
23
+ RDF_XSD = "http://www.w3.org/2001/XMLSchema#"
24
+ RDF_XSD_TOKEN = "#{RDF_XSD}token"
25
+ RDF_XSD_STRING = "#{RDF_XSD}string"
26
+ RDF_XSD_DATETIME = "#{RDF_XSD}dateTime"
27
+ RDF_XSD_BOOLEAN = "#{RDF_XSD}boolean"
28
+ RDF_XSD_INTEGER = "#{RDF_XSD}integer"
29
+
30
+ def object_to_value(value)
31
+ return primitive_to_value(value.iri) if value.respond_to?(:iri)
32
+
33
+ return primitive_to_value(value.subject) if value.is_a?(RDF::List)
34
+
35
+ primitive_to_value(value)
36
+ end
37
+
38
+ def node_to_local_id(value)
39
+ shorthand(EMP_TYPE_LOCAL_ID, "_:#{value.id}")
40
+ end
41
+
42
+ def primitive_to_value(value) # rubocop:disable Metrics/MethodLength, Metrics/CyclomaticComplexity, Metrics/AbcSize
43
+ throw_unknown_ruby_object(value) if value.nil?
44
+
45
+ case value
46
+ when RDF::Node
47
+ node_to_local_id(value)
48
+ when RDF::URI, URI
49
+ shorthand(EMP_TYPE_GLOBAL_ID, value.to_s)
50
+ when DateTime, ActiveSupport::TimeWithZone
51
+ shorthand(EMP_TYPE_DATETIME, value.iso8601)
52
+ when String
53
+ shorthand(EMP_TYPE_STRING, value)
54
+ when true, false
55
+ shorthand(EMP_TYPE_BOOL, value.to_s)
56
+ when Symbol
57
+ primitive(RDF_XSD_TOKEN, value.to_s)
58
+ when Integer
59
+ integer_to_value(value)
60
+ when Float, Numeric
61
+ use_rdf_rb_for_primitive(value)
62
+ when RDF::Literal
63
+ rdf_literal_to_value(value)
64
+ else
65
+ throw_unknown_ruby_object(value)
66
+ end
67
+ end
68
+
69
+ def integer_to_value(value)
70
+ size = value.bit_length
71
+ if size <= 32
72
+ shorthand(EMP_TYPE_INTEGER, value.to_s)
73
+ elsif size > 32 && size <= 64
74
+ shorthand(EMP_TYPE_LONG, value.to_s)
75
+ else
76
+ use_rdf_rb_for_primitive(value)
77
+ end
78
+ end
79
+
80
+ def rdf_literal_to_value(value)
81
+ case value.datatype.to_s
82
+ when RDF_RDFV_LANGSTRING
83
+ {
84
+ type: EMP_TYPE_LANGSTRING,
85
+ l: value.language.to_s,
86
+ v: value.value
87
+ }
88
+ when RDF_XSD_STRING
89
+ shorthand(EMP_TYPE_STRING, value.value)
90
+ when RDF_XSD_DATETIME
91
+ shorthand(EMP_TYPE_DATETIME, value.value)
92
+ when RDF_XSD_BOOLEAN
93
+ shorthand(EMP_TYPE_BOOL, value.value)
94
+ when RDF_XSD_INTEGER
95
+ integer_to_value(value.to_i)
96
+ else
97
+ throw "unknown RDF::Literal"
98
+ end
99
+ end
100
+
101
+ def use_rdf_rb_for_primitive(value)
102
+ rdf = RDF::Literal(value)
103
+ primitive(rdf.datatype.to_s, rdf.value)
104
+ end
105
+
106
+ def shorthand(type, value)
107
+ {
108
+ type: type,
109
+ v: value
110
+ }
111
+ end
112
+
113
+ def primitive(datatype, value)
114
+ {
115
+ type: EMP_TYPE_PRIMITIVE,
116
+ dt: datatype,
117
+ v: value
118
+ }
119
+ end
120
+
121
+ def throw_unknown_ruby_object(value)
122
+ throw "unknown ruby object: #{value} (#{value.class})"
123
+ end
124
+ end
125
+ end
126
+ end
127
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Empathy
4
+ module EmpJson
5
+ module Helpers
6
+ # Functions relating to serializing RDF::List records
7
+ module RDFList
8
+ def add_rdf_list_to_slice(slice, symbolize, **options)
9
+ elem = options.delete(:resource)
10
+ loop do
11
+ list_item_to_record(slice, elem, symbolize)
12
+
13
+ break if elem.rest_subject == NS.rdfv.nil
14
+
15
+ elem = elem.rest
16
+ end
17
+ end
18
+
19
+ def list_item_to_record(slice, elem, symbolize) # rubocop:disable Metrics/AbcSize
20
+ rid = add_record_to_slice(slice, elem)
21
+ add_attribute_to_record(slice, rid, NS.rdfv.type, NS.rdfv.List, symbolize)
22
+ first = elem.first.is_a?(RDF::Term) ? elem.first : retrieve_id(elem.first)
23
+ add_attribute_to_record(slice, rid, NS.rdfv.first, first, symbolize)
24
+ add_attribute_to_record(slice, rid, NS.rdfv.rest, elem.rest_subject, symbolize)
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,90 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/core_ext/hash/indifferent_access"
4
+
5
+ module Empathy
6
+ module EmpJson
7
+ module Helpers
8
+ # Additional functions for working with slices
9
+ module Slices
10
+ # Retrieves all values for the given [field].
11
+ def all_values_from_slice(slice, field, _website_iri = nil)
12
+ slice
13
+ .values
14
+ .flat_map { |record| field_from_record(record, field) }
15
+ .map { |values| normalise_slice_values(values) }
16
+ .compact
17
+ end
18
+
19
+ def add_record_to_slice(slice, resource)
20
+ id = resource.is_a?(RDF::Resource) ? resource : retrieve_id(resource)
21
+ value = primitive_to_value(id)
22
+
23
+ unless slice_includes_record?(slice, resource)
24
+ slice[value[:v]] = {
25
+ _id: value
26
+ }
27
+ end
28
+
29
+ value[:v]
30
+ end
31
+
32
+ # Returns a normalised fields array for a record from a slice.
33
+ def values_from_slice(slice, id, field, website_iri = nil)
34
+ values = field_from_slice(slice, id, field, website_iri)
35
+
36
+ normalise_slice_values(values)
37
+ end
38
+
39
+ # Returns the fields for a record from a slice.
40
+ def field_from_slice(slice, id, field, website_iri = nil)
41
+ record = record_from_slice(slice, id, website_iri)
42
+ return unless record.present?
43
+
44
+ field_from_record(record, field)
45
+ end
46
+
47
+ # Returns a record from a slice.
48
+ def record_from_slice(slice, id, website_iri = nil)
49
+ slice[absolutized_id(retrieve_id(id), website_iri)] || slice[retrieve_id(id)]
50
+ end
51
+
52
+ def field_from_record(record, field)
53
+ field = URI(field.to_s)
54
+ symbolized = (field.fragment || field.path.split("/").last).camelize(:lower)
55
+ (record[field.to_s] || record[symbolized])&.compact
56
+ end
57
+
58
+ def retrieve_id(resource)
59
+ return resource.to_s if resource.is_a?(URI) || resource.is_a?(RDF::URI)
60
+
61
+ resource.try(:iri) || resource.try(:subject) || resource.try(:id) || resource
62
+ end
63
+
64
+ def absolutized_id(id, website_iri = nil)
65
+ return id.to_s.delete_prefix(website_iri) if website_iri.present?
66
+
67
+ id.to_s
68
+ end
69
+
70
+ def normalise_slice_values(values)
71
+ values.is_a?(Array) ? values.map(&:with_indifferent_access) : values&.with_indifferent_access
72
+ end
73
+
74
+ def expand(slice, website_iri = current_tenant)
75
+ return slice unless website_iri.present?
76
+
77
+ slice.transform_keys { |k| k.start_with?("/") ? website_iri + k : k }
78
+ end
79
+
80
+ def field_to_symbol(uri)
81
+ (uri.fragment || uri.path.split("/").last).camelize(:lower)
82
+ end
83
+
84
+ def slice_includes_record?(slice, resource)
85
+ slice.key?(object_to_value(resource)[:v])
86
+ end
87
+ end
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Empathy
4
+ module EmpJson
5
+ module Helpers
6
+ # Functions relating to adding values to records
7
+ module Values
8
+ def add_attribute_to_record(slice, rid, key, value, symbolize)
9
+ return if value.nil?
10
+
11
+ symbol = uri_to_symbol(key, symbolize)
12
+ emp_value = value_to_emp_value(value)
13
+ current_value = slice[rid][symbol]
14
+
15
+ slice[rid][symbol] =
16
+ if current_value.is_a?(Array)
17
+ [*current_value, *(emp_value.is_a?(Array) ? emp_value : [emp_value])]
18
+ elsif current_value
19
+ [current_value, *(emp_value.is_a?(Array) ? emp_value : [emp_value])]
20
+ else
21
+ emp_value
22
+ end
23
+ end
24
+
25
+ def wrap_relation_in_sequence(value, resource)
26
+ LinkedRails::Sequence.new(
27
+ value.is_a?(Array) ? value : [value],
28
+ parent: resource,
29
+ scope: false
30
+ )
31
+ end
32
+
33
+ def uri_to_symbol(uri, symbolize)
34
+ return uri.to_s unless symbolize
35
+
36
+ casing = symbolize == :class ? :upper : :lower
37
+
38
+ (uri.fragment || uri.path.split("/").last).camelize(casing)
39
+ end
40
+
41
+ def value_to_emp_value(value)
42
+ return object_to_value(value) if value.nil?
43
+
44
+ case value
45
+ when SUPPORTS_SEQUENCE ? LinkedRails::Sequence : nil
46
+ object_to_value(value.iri)
47
+ when SUPPORTS_RDF_RB ? RDF::List : nil
48
+ object_to_value(value.subject)
49
+ when Array, *(SUPPORTS_AR ? [ActiveRecord::Associations::CollectionProxy, ActiveRecord::Relation] : [])
50
+ value.map { |v| object_to_value(v) }.compact
51
+ else
52
+ object_to_value(value)
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,102 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Empathy
4
+ module EmpJson
5
+ module Serializer
6
+ # Functions relating to serializing attributes, relations and statements
7
+ module Fields
8
+ # Modifies the record parameter
9
+ def serialize_attributes_to_record(serializer, slice, resource, rid, serialization_params)
10
+ return [] if serializer.attributes_to_serialize.blank?
11
+
12
+ nested_resources = []
13
+ serializer.attributes_to_serialize.each do |_, attr|
14
+ next if attr.predicate.blank? || !attr.conditionally_allowed?(resource, serialization_params)
15
+
16
+ value = value_for_attribute(attr, resource, serialization_params)
17
+ symbol = predicate_to_symbol(attr)
18
+ add_attribute_to_record(slice, rid, symbol, value, false)
19
+ collect_nested_resources(nested_resources, slice, resource, value, serialization_params) if value.present?
20
+ end
21
+
22
+ nested_resources
23
+ end
24
+
25
+ # Modifies the slice parameter
26
+ def serialize_statements(serializer, slice, resource, serialization_params)
27
+ serializer._statements&.each do |key|
28
+ serializer.send(key, resource, serialization_params).each do |statement|
29
+ subject, predicate, value = unpack_statement(statement)
30
+
31
+ next if value.nil?
32
+
33
+ symbol = uri_to_symbol(predicate, symbolize)
34
+ rid = add_record_to_slice(slice, subject)
35
+ add_attribute_to_record(slice, rid, symbol, value, false)
36
+ end
37
+ end
38
+ end
39
+
40
+ # Modifies the record parameter
41
+ def serialize_relations_to_record(serializer, slice, resource, rid, serialization_params)
42
+ return [] if serializer.relationships_to_serialize.blank?
43
+
44
+ nested_resources = []
45
+ serializer.relationships_to_serialize.each do |_, relationship|
46
+ next unless relationship.include_relationship?(resource, serialization_params)
47
+
48
+ value = value_for_relation(relationship, resource, serialization_params)
49
+ next if value.nil?
50
+
51
+ symbol = predicate_to_symbol(relationship)
52
+ add_attribute_to_record(slice, rid, symbol, value, false)
53
+
54
+ collect_nested_resources(nested_resources, slice, resource, value, serialization_params)
55
+ end
56
+
57
+ nested_resources
58
+ end
59
+
60
+ def value_for_attribute(attr, resource, serialization_params)
61
+ return resource.try(attr.method) if attr.method.is_a?(Symbol)
62
+
63
+ FastJsonapi.call_proc(attr.method, resource, serialization_params)
64
+ end
65
+
66
+ def value_for_relation(relation, resource, serialization_params)
67
+ value = unpack_relation_value(relation, resource, serialization_params)
68
+
69
+ return if value.nil?
70
+
71
+ if SUPPORTS_SEQUENCE && relation.sequence
72
+ wrap_relation_in_sequence(value, resource)
73
+ else
74
+ value
75
+ end
76
+ end
77
+
78
+ def unpack_relation_value(relation, resource, serialization_params)
79
+ if relation.object_block
80
+ FastJsonapi.call_proc(relation.object_block, resource, serialization_params)
81
+ else
82
+ resource.try(relation.key)
83
+ end
84
+ end
85
+
86
+ def unpack_statement(statement)
87
+ subject = statement.try(:subject) || statement[0]
88
+ predicate = statement.try(:predicate) || statement[1]
89
+ value = statement.try(:object) || statement[2]
90
+
91
+ [subject, predicate, value]
92
+ end
93
+
94
+ def predicate_to_symbol(attr)
95
+ return uri_to_symbol(attr.predicate, symbolize) if attr.predicate.present?
96
+
97
+ attr.key
98
+ end
99
+ end
100
+ end
101
+ end
102
+ end
@@ -0,0 +1,59 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Empathy
4
+ module EmpJson
5
+ module Serializer
6
+ # Functions relating to determining inclusion of records during serialization.
7
+ module Inclusion
8
+ # Modifies the nested_resources parameter
9
+ def collect_nested_resources(nested_resources, slice, resource, value, serialization_params)
10
+ case value
11
+ when LinkedRails::Sequence
12
+ collect_sequence_and_members(nested_resources, slice, resource, value, serialization_params)
13
+ when RDF::List
14
+ nested_resources.push value unless value.subject == NS.rdfv.nil
15
+ when Array
16
+ collect_array_members(nested_resources, slice, resource, value, serialization_params)
17
+ else
18
+ nested_resources.push value if blank_value?(value) || nested_resource?(resource, value)
19
+ end
20
+ end
21
+
22
+ def collect_sequence_and_members(nested_resources, slice, resource, value, serialization_params)
23
+ value.members.map { |m| collect_nested_resources(nested_resources, slice, resource, m, serialization_params) }
24
+ add_sequence_to_slice(slice, serialization_params, resource: value) unless slice[value.iri.to_s]
25
+ end
26
+
27
+ def collect_array_members(nested_resources, slice, resource, value, serialization_params)
28
+ value.each { |m| collect_nested_resources(nested_resources, slice, resource, m, serialization_params) }
29
+ end
30
+
31
+ def blank_value?(value)
32
+ value.try(:iri)&.is_a?(RDF::Node)
33
+ end
34
+
35
+ def nested_resource?(resource, value)
36
+ value.try(:iri)&.to_s&.include?("#") &&
37
+ !resource.iri.to_s.include?("#") &&
38
+ value.iri.to_s.starts_with?(resource.iri.to_s)
39
+ end
40
+
41
+ def process_includes(slice, serialization_params, **options)
42
+ includes = options.delete(:includes)
43
+ resource = options.delete(:resource)
44
+
45
+ includes.each do |prop, nested|
46
+ value = resource.try(prop)
47
+ next if value.blank?
48
+
49
+ (value.is_a?(Array) || value.is_a?(ActiveRecord::Relation) ? value : [value]).each do |v|
50
+ next if slice_includes_record?(slice, v)
51
+
52
+ resource_to_emp_json(slice, serialization_params, resource: v, includes: nested, **options)
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support"
4
+ require "active_support/core_ext/object/try"
5
+
6
+ module Empathy
7
+ module EmpJson
8
+ module Serializer
9
+ # Functions relating to serializing records
10
+ module Records
11
+ def serialize_record(slice, serialization_params, **options)
12
+ resource = options.delete(:resource)
13
+ serializer = options.delete(:serializer_class) || RDF::Serializers.serializer_for(resource)
14
+
15
+ rid = add_record_to_slice(slice, resource)
16
+
17
+ nested = serialize_record_fields(serializer, slice, resource, rid, serialization_params)
18
+
19
+ nested
20
+ .reject { |r| slice_includes_record?(slice, r) }
21
+ .each { |r| resource_to_emp_json(slice, serialization_params, resource: r) }
22
+ process_includes(slice, serialization_params, resource: resource, **options) if options[:includes]
23
+ end
24
+
25
+ def serialize_record_fields(serializer, slice, resource, rid, serialization_params)
26
+ nested = serialize_attributes_to_record(serializer, slice, resource, rid, serialization_params)
27
+ nested += serialize_relations_to_record(serializer, slice, resource, rid, serialization_params)
28
+
29
+ serialize_statements(serializer, slice, resource, serialization_params)
30
+
31
+ nested
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Empathy
4
+ module EmpJson
5
+ module Serializer
6
+ # Functions relating to serializing RDF::Sequence records
7
+ module Sequence
8
+ def add_sequence_to_slice(slice, serialization_params, **options)
9
+ resource = options.delete(:resource)
10
+ serializer = options[:serializer_class] || RDF::Serializers.serializer_for(resource)
11
+
12
+ rid = add_record_to_slice(slice, resource)
13
+
14
+ serialize_attributes_to_record(serializer, slice, resource, rid, serialization_params)
15
+ process_sequence_members(serializer, slice, resource, rid, serialization_params, **options)
16
+ end
17
+
18
+ def process_sequence_members(serializer, slice, resource, rid, serialization_params, **options) # rubocop:disable Metrics/ParameterLists
19
+ nested = []
20
+ resource.members.each_with_index.map do |m, i|
21
+ process_sequence_member(m, i, nested, serializer, slice, resource, rid, serialization_params)
22
+ end
23
+ nested.each do |r|
24
+ resource_to_emp_json(slice, serialization_params, resource: r, includes: options[:includes])
25
+ end
26
+ end
27
+
28
+ def process_sequence_member(member, index, nested, serializer, slice, resource, rid, serialization_params) # rubocop:disable Metrics/ParameterLists
29
+ index_predicate = serializer.relationships_to_serialize[:members].predicate
30
+ symbol = uri_to_symbol(index_predicate.call(self, index), symbolize)
31
+ slice[rid][symbol] = value_to_emp_value(member)
32
+ collect_nested_resources(nested, slice, resource, member, serialization_params)
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Empathy
4
+ module EmpJson
5
+ module Serializer
6
+ # Module for serializing records to slices.
7
+ module Serializing
8
+ def render_emp_json(resource = @resource)
9
+ Oj.fast_generate(emp_json_hash(resource))
10
+ end
11
+
12
+ def emp_json_hash(_resource = @resource)
13
+ create_slice(@resource)
14
+ end
15
+
16
+ def create_slice(resources)
17
+ slice = {}
18
+
19
+ (resources.is_a?(Array) ? resources : [resources]).each do |resource|
20
+ resource_to_emp_json(
21
+ slice,
22
+ @params,
23
+ resource: resource,
24
+ serializer_class: self.class,
25
+ includes: @rdf_includes
26
+ )
27
+ end
28
+
29
+ slice
30
+ end
31
+
32
+ def resource_to_emp_json(slice, serialization_params, **options) # rubocop:disable Metrics/AbcSize
33
+ return if options[:resource].blank? || retrieve_id(options[:resource]).is_a?(Proc)
34
+
35
+ if SUPPORTS_SEQUENCE && options[:resource].is_a?(LinkedRails::Sequence)
36
+ add_sequence_to_slice(
37
+ slice,
38
+ serialization_params,
39
+ **options
40
+ )
41
+ elsif SUPPORTS_RDF_RB && options[:resource].is_a?(RDF::List)
42
+ add_rdf_list_to_slice(slice, symbolize, **options)
43
+ else
44
+ if options[:serializer_class] == RDF::Serializers::ListSerializer
45
+ raise("Trying to serialize mixed resources")
46
+ end
47
+
48
+ serialize_record(
49
+ slice,
50
+ serialization_params,
51
+ **options
52
+ )
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "serializer/fields"
4
+ require_relative "serializer/inclusion"
5
+ require_relative "serializer/records"
6
+ require_relative "serializer/sequence"
7
+ require_relative "serializer/serializing"
8
+
9
+ module Empathy
10
+ module EmpJson
11
+ # Module to use with the rdf-serializers gem.
12
+ module Serializer
13
+ extend ActiveSupport::Concern
14
+
15
+ include Helpers::Primitives
16
+ include Helpers::RDFList
17
+ include Helpers::Slices
18
+ include Helpers::Values
19
+ include Fields
20
+ include Inclusion
21
+ include Records
22
+ include Sequence
23
+ include Serializing
24
+
25
+ included do
26
+ attr_accessor :symbolize
27
+ end
28
+
29
+ def initialize(resource, opts = {})
30
+ self.symbolize = opts.delete(:symbolize)
31
+
32
+ super
33
+ end
34
+
35
+ def dump(*args, **options)
36
+ case args.first
37
+ when :empjson
38
+ render_emp_json
39
+ else
40
+ super(*args, **options)
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Empathy
4
+ module EmpJson
5
+ VERSION = "1.1.0"
6
+ end
7
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "empathy/emp_json/helpers/primitives"
4
+ require "empathy/emp_json/helpers/rdf_list"
5
+ require "empathy/emp_json/helpers/slices"
6
+ require "empathy/emp_json/helpers/values"
7
+ require "empathy/emp_json/serializer"
8
+
9
+ module Empathy
10
+ module EmpJson
11
+ SUPPORTS_RDF_RB = Object.const_defined?("RDF")
12
+ SUPPORTS_SEQUENCE = Object.const_defined?("LinkedRails")
13
+ SUPPORTS_AR = Object.const_defined?("ActiveRecord")
14
+
15
+ class Error < StandardError; end
16
+ end
17
+ end
@@ -0,0 +1,8 @@
1
+ module Emp
2
+ module Json
3
+ module Rb
4
+ VERSION: String
5
+ # See the writing guide of rbs: https://github.com/ruby/rbs#guides
6
+ end
7
+ end
8
+ end
metadata ADDED
@@ -0,0 +1,70 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: emp_json
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Thom van Kalkeren
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2022-05-31 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: Serializer for the EmpJson specification.
14
+ email:
15
+ - thom@ontola.io
16
+ executables: []
17
+ extensions: []
18
+ extra_rdoc_files: []
19
+ files:
20
+ - ".rubocop.yml"
21
+ - CHANGELOG.md
22
+ - CODE_OF_CONDUCT.md
23
+ - Gemfile
24
+ - Gemfile.lock
25
+ - LICENSE.txt
26
+ - README.md
27
+ - Rakefile
28
+ - emp-json-rb.gemspec
29
+ - lib/empathy/emp_json.rb
30
+ - lib/empathy/emp_json/helpers/primitives.rb
31
+ - lib/empathy/emp_json/helpers/rdf_list.rb
32
+ - lib/empathy/emp_json/helpers/slices.rb
33
+ - lib/empathy/emp_json/helpers/values.rb
34
+ - lib/empathy/emp_json/serializer.rb
35
+ - lib/empathy/emp_json/serializer/fields.rb
36
+ - lib/empathy/emp_json/serializer/inclusion.rb
37
+ - lib/empathy/emp_json/serializer/records.rb
38
+ - lib/empathy/emp_json/serializer/sequence.rb
39
+ - lib/empathy/emp_json/serializer/serializing.rb
40
+ - lib/empathy/emp_json/version.rb
41
+ - sig/emp/json/rb.rbs
42
+ homepage: https://empathy.tools/tools/emp-json
43
+ licenses:
44
+ - MIT
45
+ metadata:
46
+ homepage_uri: https://empathy.tools/tools/emp-json
47
+ source_code_uri: https://github.com/empathy-tools/emp_json_rb
48
+ bug_tracker_uri: https://github.com/empathy-tools/emp_json_rb/issues
49
+ changelog_uri: https://github.com/empathy-tools/emp_json_rb/CHANGELOG.md
50
+ rubygems_mfa_required: 'true'
51
+ post_install_message:
52
+ rdoc_options: []
53
+ require_paths:
54
+ - lib
55
+ required_ruby_version: !ruby/object:Gem::Requirement
56
+ requirements:
57
+ - - ">="
58
+ - !ruby/object:Gem::Version
59
+ version: 2.7.0.preview1
60
+ required_rubygems_version: !ruby/object:Gem::Requirement
61
+ requirements:
62
+ - - ">="
63
+ - !ruby/object:Gem::Version
64
+ version: '0'
65
+ requirements: []
66
+ rubygems_version: 3.3.7
67
+ signing_key:
68
+ specification_version: 4
69
+ summary: Serializer for the EmpJson specification.
70
+ test_files: []