media_types-serialization 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +12 -0
  3. data/.idea/.rakeTasks +7 -0
  4. data/.idea/dictionaries/Derk_Jan.xml +7 -0
  5. data/.idea/inspectionProfiles/Project_Default.xml +6 -0
  6. data/.idea/media_types-serialization.iml +55 -0
  7. data/.idea/misc.xml +7 -0
  8. data/.idea/modules.xml +8 -0
  9. data/.idea/runConfigurations/test.xml +20 -0
  10. data/.idea/vcs.xml +6 -0
  11. data/.travis.yml +17 -0
  12. data/CHANGELOG.md +5 -0
  13. data/CODE_OF_CONDUCT.md +74 -0
  14. data/Gemfile +4 -0
  15. data/Gemfile.lock +76 -0
  16. data/LICENSE.txt +21 -0
  17. data/README.md +209 -0
  18. data/Rakefile +10 -0
  19. data/bin/console +14 -0
  20. data/bin/setup +8 -0
  21. data/lib/media_types/serialization.rb +194 -0
  22. data/lib/media_types/serialization/base.rb +138 -0
  23. data/lib/media_types/serialization/error.rb +7 -0
  24. data/lib/media_types/serialization/migrations_command.rb +38 -0
  25. data/lib/media_types/serialization/migrations_support.rb +41 -0
  26. data/lib/media_types/serialization/mime_type_support.rb +55 -0
  27. data/lib/media_types/serialization/no_content_type_given.rb +11 -0
  28. data/lib/media_types/serialization/no_media_type_serializers.rb +11 -0
  29. data/lib/media_types/serialization/no_serializer_for_content_type.rb +15 -0
  30. data/lib/media_types/serialization/renderer.rb +31 -0
  31. data/lib/media_types/serialization/renderer/register.rb +4 -0
  32. data/lib/media_types/serialization/version.rb +5 -0
  33. data/lib/media_types/serialization/wrapper.rb +15 -0
  34. data/lib/media_types/serialization/wrapper/html_wrapper.rb +42 -0
  35. data/lib/media_types/serialization/wrapper/media_collection_wrapper.rb +48 -0
  36. data/lib/media_types/serialization/wrapper/media_index_wrapper.rb +48 -0
  37. data/lib/media_types/serialization/wrapper/media_object_wrapper.rb +45 -0
  38. data/lib/media_types/serialization/wrapper/media_wrapper.rb +32 -0
  39. data/lib/media_types/serialization/wrapper/root_key.rb +20 -0
  40. data/media_types-serialization.gemspec +48 -0
  41. metadata +212 -0
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_support/concern'
4
+
5
+ module MediaTypes
6
+ module Serialization
7
+ module MimeTypeSupport
8
+ extend ActiveSupport::Concern
9
+
10
+ included do
11
+ mattr_accessor :media_type_constructable, :serializes_html_flag, :media_type_versions
12
+ end
13
+
14
+ class_methods do
15
+ def current_mime_type(view: nil)
16
+ media_type_constructable&.view(view)
17
+ end
18
+
19
+ def media_types(view: nil)
20
+ media_type_view = current_mime_type(view: view)
21
+
22
+ suffixes = [].tap do |result|
23
+ result << :json if self.instance_methods(false).include?(:to_json)
24
+ result << :xml if self.instance_methods(false).include?(:to_xml)
25
+ end
26
+
27
+ additionals = [].tap do |result|
28
+ result << 'text/html' if serializes_html_flag || self.instance_methods(false).include?(:to_html)
29
+ end
30
+
31
+ [media_type_view].concat(
32
+ media_type_versions.map { |version| media_type_view&.version(version) },
33
+ media_type_versions.flat_map do |version|
34
+ (suffixes).map { |suffix| media_type_view&.suffix(suffix)&.version(version) }
35
+ end,
36
+ additionals
37
+ ).compact.uniq
38
+ end
39
+
40
+ alias_method :media_type, :media_types
41
+
42
+ protected
43
+
44
+ def serializes_media_type(media_type, additional_versions: [])
45
+ self.media_type_constructable = media_type&.to_constructable
46
+ self.media_type_versions = additional_versions
47
+ end
48
+
49
+ def serializes_html
50
+ self.serializes_html_flag = true
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,11 @@
1
+ require 'media_types/serialization/error'
2
+
3
+ module MediaTypes
4
+ module Serialization
5
+ class NoContentTypeGiven < Error
6
+ def initialize
7
+ super 'Unable to render data because :content_type was not passed in to "render media: data, content_type: ..."'
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,11 @@
1
+ require 'media_types/serialization/error'
2
+
3
+ module MediaTypes
4
+ module Serialization
5
+ class NoMediaTypeSerializers < Error
6
+ def initialize
7
+ super 'No serializer has been set up for this request. You can not fix this by changing the request headers.'
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,15 @@
1
+ require 'media_types/serialization/error'
2
+
3
+ module MediaTypes
4
+ module Serialization
5
+ class NoSerializerForContentType < Error
6
+ def initialize(given, supported)
7
+ super format(
8
+ 'Unable to serialize to requested Content-Type: %<given>s. I can give you: %<supported>s',
9
+ given: given.inspect,
10
+ supported: supported.map(&:to_s)
11
+ )
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,31 @@
1
+ require 'media_types/serialization/no_content_type_given'
2
+ require 'active_support/core_ext/object/blank'
3
+ require 'active_support/core_ext/hash/conversions'
4
+
5
+ module MediaTypes
6
+ module Serialization
7
+ # noinspection RubyConstantNamingConvention
8
+ Renderer = lambda do |obj, options|
9
+ content_type = options[:content_type] || options[:mime_type]&.to_s || self.content_type&.to_s || obj.current_media_type.to_s
10
+ raise NoContentTypeGiven if content_type.blank?
11
+
12
+ self.content_type ||= content_type
13
+
14
+ if content_type.ends_with?('+json') || Mime::Type.lookup(content_type) == Mime[:json]
15
+ obj.class.instance_methods(false).include?(:to_json) ? obj.to_json(options) : obj.to_hash.to_json(options)
16
+ elsif content_type.ends_with?('+xml') || Mime::Type.lookup(content_type) == Mime[:xml]
17
+ obj.class.instance_methods(false).include?(:to_xml) ? obj.to_xml(options) : obj.to_hash.to_xml(options)
18
+ elsif Mime::Type.lookup(content_type) == Mime[:html]
19
+ obj.class.instance_methods(false).include?(:to_html) ? obj.to_html : obj.to_s
20
+ else
21
+ obj.to_body(content_type: options.delete(:content_type) || content_type, **options)
22
+ end
23
+ end
24
+
25
+ module_function
26
+
27
+ def register_renderer
28
+ ::ActionController::Renderers.add :media, &Renderer
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,4 @@
1
+ require 'action_controller/metal/renderers'
2
+ require 'media_types/serialization/renderer'
3
+
4
+ ::MediaTypes::Serialization.register_renderer
@@ -0,0 +1,5 @@
1
+ module MediaTypes
2
+ module Serialization
3
+ VERSION = "0.1.0"
4
+ end
5
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'media_types/serialization/wrapper/root_key'
4
+ require 'media_types/serialization/wrapper/html_wrapper'
5
+ require 'media_types/serialization/wrapper/media_wrapper'
6
+ require 'media_types/serialization/wrapper/media_object_wrapper'
7
+ require 'media_types/serialization/wrapper/media_index_wrapper'
8
+ require 'media_types/serialization/wrapper/media_collection_wrapper'
9
+
10
+ module MediaTypes
11
+ module Serialization
12
+ module Wrapper
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'delegate'
4
+
5
+ require 'action_controller'
6
+
7
+ require 'media_types/serialization/base'
8
+ require 'media_types/serialization/wrapper/root_key'
9
+
10
+ module MediaTypes
11
+ module Serialization
12
+ module Wrapper
13
+ class HtmlWrapper < DelegateClass(Base)
14
+
15
+ delegate :to_s, to: :to_html
16
+ delegate :class, to: :__getobj__
17
+
18
+ def initialize(serializer, view: nil, **render_options)
19
+ super serializer
20
+ self.view = view
21
+ self.render_options = render_options
22
+ end
23
+
24
+ def to_html
25
+ return super if __getobj__.respond_to?(:to_html)
26
+ ActionController::Base.render(
27
+ 'serializers/wrapper/html_wrapper',
28
+ assigns: {
29
+ serializer: self,
30
+ view: view,
31
+ **render_options
32
+ }
33
+ )
34
+ end
35
+
36
+ private
37
+
38
+ attr_accessor :view, :render_options
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'delegate'
4
+
5
+ require 'active_support/core_ext/string/inflections'
6
+
7
+ require 'media_types/serialization/base'
8
+ require 'media_types/serialization/wrapper/root_key'
9
+
10
+ module MediaTypes
11
+ module Serialization
12
+ module Wrapper
13
+ class MediaCollectionWrapper < DelegateClass(Base)
14
+
15
+ delegate :to_json, to: :to_hash
16
+ delegate :class, to: :__getobj__
17
+
18
+ def initialize(serializer)
19
+ super serializer
20
+ end
21
+
22
+ def to_hash
23
+ { Wrapper::RootKey.new(__getobj__.class).pluralize => {
24
+ '_embedded': auto_wrap_serializable.map(&method(:item_hash)),
25
+ '_links': {}
26
+ } }
27
+ end
28
+ alias to_h to_hash
29
+
30
+ def collect_links
31
+ {}
32
+ end
33
+
34
+ private
35
+
36
+ def auto_wrap_serializable
37
+ Array(serializable)
38
+ end
39
+
40
+ def item_hash(item)
41
+ __getobj__.instance_exec do
42
+ set(item).send(:to_hash)
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'delegate'
4
+
5
+ require 'active_support/core_ext/string/inflections'
6
+
7
+ require 'media_types/serialization/base'
8
+ require 'media_types/serialization/wrapper/root_key'
9
+
10
+ module MediaTypes
11
+ module Serialization
12
+ module Wrapper
13
+ class MediaIndexWrapper < DelegateClass(Base)
14
+
15
+ delegate :to_json, to: :to_hash
16
+ delegate :class, to: :__getobj__
17
+
18
+ def initialize(serializer)
19
+ super serializer
20
+ end
21
+
22
+ def to_hash
23
+ { Wrapper::RootKey.new(__getobj__.class).pluralize => {
24
+ '_index': auto_wrap_serializable.map(&method(:item_hash)),
25
+ '_links': {}
26
+ } }
27
+ end
28
+ alias to_h to_hash
29
+
30
+ def collect_links
31
+ {}
32
+ end
33
+
34
+ private
35
+
36
+ def auto_wrap_serializable
37
+ Array(serializable)
38
+ end
39
+
40
+ def item_hash(item)
41
+ __getobj__.instance_exec do
42
+ set(item).send(:extract_self)
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'delegate'
4
+
5
+ require 'active_support/core_ext/string/inflections'
6
+
7
+ require 'media_types/serialization/base'
8
+ require 'media_types/serialization/wrapper/media_wrapper'
9
+ require 'media_types/serialization/wrapper/root_key'
10
+
11
+ module MediaTypes
12
+ module Serialization
13
+ module Wrapper
14
+ class MediaObjectWrapper < DelegateClass(Base)
15
+
16
+ delegate :to_json, to: :to_hash
17
+ delegate :class, to: :__getobj__
18
+
19
+ def initialize(serializer)
20
+ super serializer
21
+ end
22
+
23
+ def to_hash
24
+ unwrapped = auto_unwrap_serializable.tap { |u| set(u) }
25
+ { RootKey.new(__getobj__.class).singularize => unwrapped && super || nil }
26
+ end
27
+ alias to_h to_hash
28
+
29
+ def collect_links
30
+ return {} unless serializable
31
+ __getobj__.send(:header_links)
32
+ end
33
+
34
+ private
35
+
36
+ AUTO_UNWRAP_KLAZZES = [Array, defined?(ActiveRecord) ? ActiveRecord::Relation : nil].compact.freeze
37
+
38
+ def auto_unwrap_serializable
39
+ return serializable unless AUTO_UNWRAP_KLAZZES.any? { |klazz| serializable.is_a?(klazz) }
40
+ serializable.first
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'media_types'
4
+
5
+ require 'media_types/serialization/wrapper/media_index_wrapper'
6
+ require 'media_types/serialization/wrapper/media_collection_wrapper'
7
+ require 'media_types/serialization/wrapper/media_object_wrapper'
8
+
9
+ module MediaTypes
10
+ module Serialization
11
+ module Wrapper
12
+ class MediaWrapper
13
+
14
+ AUTO_WRAPPER_MAPPING = {
15
+ ::MediaTypes::INDEX_VIEW => MediaIndexWrapper,
16
+ ::MediaTypes::COLLECTION_VIEW => MediaCollectionWrapper,
17
+ ::MediaTypes::CREATE_VIEW => MediaObjectWrapper,
18
+ nil => MediaObjectWrapper
19
+ }.freeze
20
+
21
+ DEFAULT_WRAPPER = MediaObjectWrapper
22
+
23
+ class << self
24
+ def new(serializer, view: nil)
25
+ wrapper = AUTO_WRAPPER_MAPPING.fetch(String(view)) { DEFAULT_WRAPPER }
26
+ wrapper.new(serializer)
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'delegate'
4
+
5
+ require 'active_support/core_ext/object/blank'
6
+ require 'active_support/core_ext/string/inflections'
7
+
8
+ module MediaTypes
9
+ module Serialization
10
+ module Wrapper
11
+ class RootKey < DelegateClass(String)
12
+ def initialize(klazz)
13
+ base = klazz.name.demodulize.chomp(MediaTypes::Serialization.common_suffix || 'Serializer').presence ||
14
+ klazz.parent.name.demodulize
15
+ super base.underscore
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,48 @@
1
+
2
+ lib = File.expand_path("../lib", __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require "media_types/serialization/version"
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "media_types-serialization"
8
+ spec.version = MediaTypes::Serialization::VERSION
9
+ spec.authors = ["Derk-Jan Karrenbeld"]
10
+ spec.email = ["derk-jan@xpbytes.com"]
11
+
12
+ spec.summary = 'Add media types supported serialization using your favourite serializer'
13
+ spec.homepage = 'https://github.com/XPBytes/media_types-serialization'
14
+ spec.license = 'MIT'
15
+
16
+ # Prevent pushing this gem to RubyGems.org. To allow pushes either set the 'allowed_push_host'
17
+ # to allow pushing to a single host or delete this section to allow pushing to any host.
18
+ if spec.respond_to?(:metadata)
19
+ # spec.metadata["allowed_push_host"] = "TODO: Set to 'http://mygemserver.com'"
20
+
21
+ spec.metadata["homepage_uri"] = spec.homepage
22
+ spec.metadata["source_code_uri"] = spec.homepage
23
+ spec.metadata["changelog_uri"] = spec.homepage + '/CHANGELOG.md'
24
+ else
25
+ raise "RubyGems 2.0 or newer is required to protect against " \
26
+ "public gem pushes."
27
+ end
28
+
29
+ # Specify which files should be added to the gem when it is released.
30
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
31
+ spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
32
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
33
+ end
34
+ spec.bindir = "exe"
35
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
36
+ spec.require_paths = ["lib"]
37
+
38
+ spec.add_dependency 'actionpack', '>= 4.0.0'
39
+ spec.add_dependency 'activesupport', '>= 4.0.0'
40
+ spec.add_dependency 'media_types', '>= 0.6.0'
41
+ spec.add_dependency 'oj', '>= 3.5.0'
42
+ spec.add_dependency 'http_headers-accept', '< 1.0.0'
43
+ spec.add_dependency 'http_headers-link', '< 1.0.0'
44
+
45
+ spec.add_development_dependency "bundler", "~> 2.0"
46
+ spec.add_development_dependency "rake", "~> 10.0"
47
+ spec.add_development_dependency "minitest", "~> 5.0"
48
+ end