media_types-serialization 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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