transmutation 0.1.1 → 0.2.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e34773430c58186f819264aea12092ad75e712c2c7e6142ecc8cc872f670741a
4
- data.tar.gz: 21a4b0a2c8b8dae95455d47a4401536e3f99d61fa88254c717ae00edbd8fddc1
3
+ metadata.gz: 0dd356719d0c48951dd08d327c72b1b94b7b7ef4437c105fcbc7a22558bf4c14
4
+ data.tar.gz: efc9997ee8be40ce11894d7a64e570e7d53f617a3251bebb48c4f940050065be
5
5
  SHA512:
6
- metadata.gz: 34b55f58bc7b23bb41e64cfe696f5852567b9afee1f2c86623b24ee606ec3e17dd4896518602937b998019ea34c92a47c89932a3fc13f4c2a3f101eef62d0882
7
- data.tar.gz: e46827b2eebf91448544faa63b042e4ce082ecae26c4533b88ebe853cd00fb00c6a2b4be1b03961ad26bfebef798ad9567e0847cd1abf3deedff7e42bf5d015d
6
+ metadata.gz: 93784f5a82ac13bcb4dc2560a12fccdee59135e5576dad82a556f4cb5f709811b7909aaa024273e1a38c1900d0534f47abc0f3ff3410c0b3dbce215365f31b84
7
+ data.tar.gz: 29daa074911fb3d8f984a5088963686155939093126a467940e54bd9d836b00705bfd6c13e54f1d2ed5199c4bdff18fc4d513ec9e692102f6065726d3a9fd994
data/.rubocop.yml CHANGED
@@ -1,3 +1,5 @@
1
+ inherit_from: .rubocop_todo.yml
2
+
1
3
  AllCops:
2
4
  TargetRubyVersion: 3.0
3
5
  NewCops: disable
data/.rubocop_todo.yml ADDED
@@ -0,0 +1,16 @@
1
+ # This configuration was generated by
2
+ # `rubocop --auto-gen-config`
3
+ # on 2024-06-05 00:21:36 UTC using RuboCop version 1.63.4.
4
+ # The point is for the user to remove these configuration records
5
+ # one by one as the offenses are removed from the code base.
6
+ # Note that changes in the inspected code, or installation of new
7
+ # versions of RuboCop, may require this file to be generated again.
8
+
9
+ # Offense count: 2
10
+ # Configuration parameters: AllowedConstants.
11
+ Style/Documentation:
12
+ Exclude:
13
+ - 'spec/**/*'
14
+ - 'test/**/*'
15
+ - 'lib/transmutation/serialization.rb'
16
+ - 'lib/transmutation/serialization/rendering.rb'
data/Gemfile CHANGED
@@ -15,3 +15,5 @@ gem "rubocop-rspec", require: false
15
15
  gem "simplecov"
16
16
  gem "simplecov-lcov"
17
17
  gem "undercover"
18
+
19
+ gem "pry"
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- transmutation (0.1.1)
4
+ transmutation (0.2.0)
5
5
  zeitwerk (~> 2.6.15)
6
6
 
7
7
  GEM
@@ -9,21 +9,27 @@ GEM
9
9
  specs:
10
10
  ast (2.4.2)
11
11
  bigdecimal (3.1.7)
12
+ coderay (1.1.3)
12
13
  diff-lcs (1.5.1)
13
14
  docile (1.4.0)
14
15
  imagen (0.1.8)
15
16
  parser (>= 2.5, != 2.5.1.1)
16
17
  json (2.7.2)
17
18
  language_server-protocol (3.17.0.3)
19
+ method_source (1.1.0)
18
20
  parallel (1.24.0)
19
21
  parser (3.3.1.0)
20
22
  ast (~> 2.4.1)
21
23
  racc
24
+ pry (0.14.2)
25
+ coderay (~> 1.1)
26
+ method_source (~> 1.0)
22
27
  racc (1.7.3)
23
28
  rainbow (3.1.1)
24
29
  rake (13.2.1)
25
30
  regexp_parser (2.9.0)
26
- rexml (3.2.6)
31
+ rexml (3.2.8)
32
+ strscan (>= 3.0.9)
27
33
  rspec (3.13.0)
28
34
  rspec-core (~> 3.13.0)
29
35
  rspec-expectations (~> 3.13.0)
@@ -70,6 +76,7 @@ GEM
70
76
  simplecov-html (0.12.3)
71
77
  simplecov-lcov (0.8.0)
72
78
  simplecov_json_formatter (0.1.4)
79
+ strscan (3.1.0)
73
80
  undercover (0.5.0)
74
81
  bigdecimal
75
82
  imagen (>= 0.1.8)
@@ -83,6 +90,7 @@ PLATFORMS
83
90
  ruby
84
91
 
85
92
  DEPENDENCIES
93
+ pry
86
94
  rake (~> 13.0)
87
95
  rspec (~> 3.0)
88
96
  rubocop (~> 1.21)
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Transmutation
4
+ # @api private
5
+ module ClassAttributes
6
+ def class_attribute(
7
+ *names,
8
+ instance_accessor: true,
9
+ instance_reader: instance_accessor,
10
+ instance_writer: instance_accessor,
11
+ default: nil
12
+ )
13
+ class_attribute_reader(*names, instance_reader: instance_reader, default: default)
14
+ class_attribute_writer(*names, instance_writer: instance_writer, default: default)
15
+ end
16
+
17
+ def class_attribute_reader(*names, instance_reader: true, default: nil)
18
+ names.each do |name|
19
+ self.class.define_method(name) do
20
+ instance_variable_get("@#{name}")
21
+ end
22
+
23
+ define_method(name) { self.class.send(name) } if instance_reader
24
+
25
+ instance_variable_set("@#{name}", default)
26
+ end
27
+ end
28
+
29
+ def class_attribute_writer(*names, instance_writer: true, default: nil)
30
+ names.each do |name|
31
+ self.class.define_method("#{name}=") do |value|
32
+ instance_variable_set("@#{name}", value)
33
+ end
34
+
35
+ define_method("#{name}=") { |value| self.class.send("#{name}=", value) } if instance_writer
36
+
37
+ instance_variable_set("@#{name}", default)
38
+ end
39
+ end
40
+ end
41
+ end
@@ -1,21 +1,27 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Transmutation
4
- class CollectionSerializer # rubocop:disable Style/Documentation
4
+ # Out-of-the-box collection serializer.
5
+ #
6
+ # This serializer will be used to serialize all collections of objects.
7
+ #
8
+ # @example Basic usage
9
+ # Transmutation::CollectionSerializer.new([object, object]).to_json
10
+ class CollectionSerializer
5
11
  include Transmutation::Serialization
6
12
 
7
- def initialize(objects, namespace: "", serializer: nil)
13
+ def initialize(objects, namespace: nil, serializer: nil)
8
14
  @objects = objects
9
15
  @namespace = namespace
10
16
  @serializer = serializer
11
17
  end
12
18
 
13
19
  def as_json(options = {})
14
- serializers = serialize(objects, namespace: namespace, serializer: serializer)
20
+ objects.map { |item| serialize(item, namespace: namespace, serializer: serializer).as_json(options) }
21
+ end
15
22
 
16
- serializers.map do |serializer|
17
- serializer.as_json(options)
18
- end
23
+ def to_json(options = {})
24
+ as_json(options).to_json
19
25
  end
20
26
 
21
27
  private
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Transmutation
4
+ module Serialization
5
+ class Lookup
6
+ # @api public
7
+ class SerializerNotFound < Transmutation::Error
8
+ attr_reader :object, :namespace, :name
9
+
10
+ def initialize(object, namespace: nil, name: nil)
11
+ @object = object
12
+ @namespace = namespace
13
+ @name = name
14
+
15
+ super [
16
+ "Couldn't find serializer for #{object.class.name}#{namespace.empty? ? "" : " in #{namespace}"}.",
17
+ "Tried looking for the following classes: #{attempted_lookups}."
18
+ ].join(" ")
19
+ end
20
+
21
+ private
22
+
23
+ def attempted_lookups
24
+ namespaces_chain.map { |namespace| [namespace, name].join("::") }.join(", ")
25
+ end
26
+
27
+ def namespaces_chain
28
+ @namespaces_chain ||= begin
29
+ namespace_parts = namespace.split("::")
30
+
31
+ namespace_parts.filter_map.with_index do |part, index|
32
+ [*namespace_parts[...index], part].join("::")
33
+ end.reverse
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,89 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Transmutation
4
+ module Serialization
5
+ # @api private
6
+ class Lookup
7
+ def initialize(caller, namespace: nil)
8
+ @caller = caller
9
+ @namespace = namespace
10
+ end
11
+
12
+ # Bubbles up the namespace until we find a matching serializer.
13
+ #
14
+ # @see Transmutation::Serialization#lookup_serializer
15
+ #
16
+ # Example:
17
+ #
18
+ # namespace: Api::V1::Admin::Detailed
19
+ # serializer: Chat::User
20
+ #
21
+ # This method will attempt to find a serializer defined in the following order:
22
+ #
23
+ # - Api::V1::Admin::Detailed::Chat::UserSerializer
24
+ # - Api::V1::Admin::Chat::UserSerializer
25
+ # - Api::V1::Chat::UserSerializer
26
+ # - Api::Chat::UserSerializer
27
+ # - Chat::UserSerializer
28
+ #
29
+ # Note: This never bubbles up the object's namespace, only the caller's namespace.
30
+ def serializer_for(object, serializer: nil)
31
+ return Transmutation::CollectionSerializer if object.respond_to?(:map)
32
+
33
+ serializer_name = serializer_name_for(object, serializer: serializer)
34
+
35
+ return constantize_serializer!(Object, serializer_name, object: object) if serializer_name.start_with?("::")
36
+
37
+ potential_namespaces.each do |potential_namespace|
38
+ return potential_namespace.const_get(serializer_name) if potential_namespace.const_defined?(serializer_name)
39
+ end
40
+
41
+ raise SerializerNotFound.new(@object, namespace: serializer_namespace, name: serializer_name)
42
+ end
43
+
44
+ # Returns the highest specificity serializer name for the given object.
45
+ #
46
+ # @param object [Object] The object to find the serializer name for.
47
+ #
48
+ # @return [String] The serializer name.
49
+ def serializer_name_for(object, serializer: nil)
50
+ return "::Transmutation::CollectionSerializer" if object.respond_to?(:map)
51
+
52
+ "#{serializer&.delete_suffix("Serializer") || object.class.name}Serializer"
53
+ end
54
+
55
+ private
56
+
57
+ def potential_namespaces
58
+ @potential_namespaces ||= begin
59
+ namespace_parts = serializer_namespace.split("::")
60
+
61
+ namespace_parts.filter_map.with_index do |part, index|
62
+ namespace = [*namespace_parts[...index], part].join("::")
63
+
64
+ next if namespace.empty?
65
+
66
+ Object.const_get(namespace) if Object.const_defined?(namespace)
67
+ end.reverse
68
+ end
69
+ end
70
+
71
+ def serializer_namespace
72
+ return caller_namespace if @namespace.nil?
73
+ return @namespace if @namespace.start_with?("::")
74
+
75
+ "#{caller_namespace}::#{@namespace}"
76
+ end
77
+
78
+ def caller_namespace
79
+ @caller_namespace ||= @caller.class.name.split("::")[...-1].join("::")
80
+ end
81
+
82
+ def constantize_serializer!(namespace, name, object:)
83
+ raise SerializerNotFound.new(object, namespace: namespace, name: name) unless namespace.const_defined?(name)
84
+
85
+ namespace.const_get(name)
86
+ end
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Transmutation
4
+ module Serialization
5
+ module Rendering
6
+ def render(json: nil, serialize: true, **args)
7
+ return super(**args) unless json
8
+ return super(json: json, **args) unless serialize
9
+
10
+ super(**args, json: serialize(json))
11
+ end
12
+ end
13
+ end
14
+ end
@@ -1,123 +1,35 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- module Transmutation # rubocop:disable Style/Documentation
4
- using Transmutation::StringRefinements
5
-
6
- module Serialization # rubocop:disable Style/Documentation
7
- def self.included(base)
8
- caller_class = (base.instance_of?(Class) ? base.to_s : self.class.to_s)
9
- caller_namespace = caller_class.include?("::") ? caller_class.split("::").first : ""
10
- base.class_eval do
11
- @caller_namespace = caller_namespace
12
- end
13
- end
14
-
15
- BY_CALLER_NAMESPACE_WITH_NAMESPACE_AND_SERIALIZER = lambda do |caller, _resource_class, namespace:, serializer:|
16
- caller_namespace = namespace.to_s.include?("::") ? "" : get_caller_namespace(caller.class)
17
-
18
- converted_namespace = convert_namespace(namespace)
19
-
20
- converted_serializer = convert_serializer(serializer)
21
-
22
- serializer_name = serializer_from_resource_name(converted_serializer)
23
-
24
- "#{caller_namespace}::#{converted_namespace}::#{serializer_name}"
25
- end
26
-
27
- BY_RESOURCE_CALLER_NAMESPACE_WITH_NAMESPACE = lambda do |caller, resource_class, namespace:, **_|
28
- caller_namespace = namespace.to_s.include?("::") ? "" : get_caller_namespace(caller.class)
29
-
30
- converted_namespace = convert_namespace(namespace)
31
-
32
- serializer_name = serializer_from_resource_name(resource_class)
33
-
34
- "#{caller_namespace}::#{converted_namespace}::#{serializer_name}"
35
- end
36
-
37
- BY_CALLER_NAMESPACE_WITH_SERIALIZER = lambda do |caller, _resource_class, serializer:, **_|
38
- caller_namespace = get_caller_namespace(caller.class)
39
-
40
- converted_serializer = convert_serializer(serializer)
41
-
42
- serializer_name = serializer_from_resource_name(converted_serializer)
43
-
44
- "#{caller_namespace}::#{serializer_name}"
45
- end
46
-
47
- BY_RESOURCE_CALLER_NAMESPACE = lambda do |caller, resource_class, **_|
48
- caller_namespace = get_caller_namespace(caller.class)
49
-
50
- serializer_name = serializer_from_resource_name(resource_class)
51
-
52
- "#{caller_namespace}::#{serializer_name}"
53
- end
54
-
55
- BY_RESOURCE = lambda do |_caller, resource_class, **_|
56
- serializer_name = serializer_from_resource_name(resource_class)
57
-
58
- "::#{serializer_name}"
59
- end
60
-
61
- LOOKUP_STRATEGIES = [
62
- BY_CALLER_NAMESPACE_WITH_NAMESPACE_AND_SERIALIZER,
63
- BY_RESOURCE_CALLER_NAMESPACE_WITH_NAMESPACE,
64
- BY_CALLER_NAMESPACE_WITH_SERIALIZER,
65
- BY_RESOURCE_CALLER_NAMESPACE,
66
- BY_RESOURCE
67
- ].freeze
68
-
69
- def lookup_serializer(object, namespace: "", serializer: nil)
70
- lookup_serializer!(object, namespace: namespace, serializer: serializer)
71
- rescue NameError
72
- nil
73
- end
74
-
75
- def render(**args)
76
- return super(**args) unless args[:json]
77
- return super(**args) if args[:serialize] == false
78
-
79
- super(**args, json: serialize(args[:json]))
80
- end
81
-
82
- def lookup_serializer!(object, namespace: "", serializer: nil)
83
- LOOKUP_STRATEGIES.each do |strategy|
84
- return Object.const_get(strategy.call(self, object.class, namespace: namespace, serializer: serializer))
85
- rescue NameError
86
- next
87
- end
88
-
89
- raise NameError, "Serializer not found for #{object.class}"
90
- end
91
-
92
- def serialize(object, namespace: "", serializer: nil)
93
- unless object.respond_to?(:map)
94
- return lookup_serializer!(object, namespace: namespace,
95
- serializer: serializer).new(object)
96
- end
97
-
98
- object.map do |entry_object|
99
- lookup_serializer!(entry_object, namespace: namespace, serializer: serializer).new(entry_object)
100
- end
101
- end
102
-
103
- module_function
104
-
105
- def get_caller_namespace(base)
106
- base.class_eval do
107
- @caller_namespace
108
- end
109
- end
110
-
111
- def convert_namespace(namespace)
112
- namespace.to_s.camelcase
113
- end
114
-
115
- def convert_serializer(serializer)
116
- serializer.to_s.camelcase.gsub("Serializer", "")
117
- end
118
-
119
- def serializer_from_resource_name(name)
120
- "#{name}Serializer"
3
+ module Transmutation
4
+ module Serialization
5
+ # Serialize a given object with the looked up serializer.
6
+ #
7
+ #
8
+ # @param object [Object] The object to serialize.
9
+ # @param namespace [String, Symbol, Module] The namespace to lookup the serializer in.
10
+ # @param serializer [String, Symbol, Class] The serializer to use.
11
+ #
12
+ # @return [Transmutation::Serializer] The serialized object. This will respond to `#as_json` and `#to_json`.
13
+ def serialize(object, namespace: nil, serializer: nil)
14
+ lookup_serializer(object, namespace: namespace, serializer: serializer).new(object)
15
+ end
16
+
17
+ # Lookup the serializer for the given object.
18
+ #
19
+ # This calls {Transmutation::Serialization::Lookup#serializer_for} to find the serializer for the given object.
20
+ #
21
+ # @param object [Object] The object to lookup the serializer for.
22
+ # @param namespace [String, Symbol, Module] The namespace to lookup the serializer in.
23
+ # @param serializer [String, Symbol, Class] The serializer to use.
24
+ #
25
+ # @return [Class<Transmutation::Serializer>] The serializer for the given object.
26
+ #
27
+ def lookup_serializer(object, namespace: nil, serializer: nil)
28
+ Lookup.new(self, namespace: namespace).serializer_for(object, serializer: serializer)
29
+ end
30
+
31
+ private_class_method def self.included(base)
32
+ base.include(Rendering) if base.respond_to?(:render)
121
33
  end
122
34
  end
123
35
  end
@@ -1,7 +1,19 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Transmutation
4
- class Serializer # rubocop:disable Style/Documentation
4
+ # Base class for your serializers.
5
+ #
6
+ # @example Basic usage
7
+ # class UserSerializer < Transmutation::Serializer
8
+ # attributes :first_name, :last_name
9
+ #
10
+ # attribute :full_name do
11
+ # "#{object.first_name} #{object.last_name}".strip
12
+ # end
13
+ # end
14
+ class Serializer
15
+ extend ClassAttributes
16
+
5
17
  def initialize(object)
6
18
  @object = object
7
19
  end
@@ -11,31 +23,52 @@ module Transmutation
11
23
  end
12
24
 
13
25
  def as_json(_options = {})
14
- _attributes.each_with_object({}) do |(attr_name, attr_options), hash|
26
+ attributes_config.each_with_object({}) do |(attr_name, attr_options), hash|
15
27
  hash[attr_name.to_s] = attr_options[:block] ? instance_exec(&attr_options[:block]) : object.send(attr_name)
16
28
  end
17
29
  end
18
30
 
19
- def self.attribute(attr_name, &block)
20
- _attributes[attr_name] = { block: block }
31
+ # Define an attribute to be serialized
32
+ #
33
+ # @param attribute_name [Symbol] The name of the attribute to serialize
34
+ # @param block [Proc] The block to call to get the value of the attribute.
35
+ # The block is called in the context of the serializer instance.
36
+ #
37
+ # @example
38
+ # class UserSerializer < Transmutation::Serializer
39
+ # attribute :first_name
40
+ #
41
+ # attribute :full_name do
42
+ # "#{object.first_name} #{object.last_name}".strip
43
+ # end
44
+ # end
45
+ def self.attribute(attribute_name, &block)
46
+ attributes_config[attribute_name] = { block: block }
21
47
  end
22
48
 
23
- def self.attributes(*attr_name)
24
- attr_name.each do |name|
25
- attribute(name)
49
+ # Shorthand for defining multiple attributes
50
+ #
51
+ # @param attribute_names [Array<Symbol>] The names of the attributes to serialize
52
+ #
53
+ # @example
54
+ # class UserSerializer < Transmutation::Serializer
55
+ # attributes :first_name, :last_name
56
+ # end
57
+ def self.attributes(*attribute_names)
58
+ attribute_names.each do |attr_name|
59
+ attribute(attr_name)
26
60
  end
27
61
  end
28
62
 
29
- def self._attributes
30
- @@attributes ||= {} # rubocop:disable Style/ClassVars
31
- end
32
-
33
- def _attributes
34
- self.class._attributes
35
- end
36
-
37
63
  private
38
64
 
65
+ class_attribute :attributes_config, instance_writer: false, default: {}
66
+
39
67
  attr_reader :object
68
+
69
+ private_class_method def self.inherited(subclass)
70
+ super
71
+ subclass.attributes_config = attributes_config.dup
72
+ end
40
73
  end
41
74
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Transmutation
4
- VERSION = "0.1.1"
4
+ VERSION = "0.2.0"
5
5
  end
data/lib/transmutation.rb CHANGED
@@ -4,6 +4,43 @@ require "zeitwerk"
4
4
  loader = Zeitwerk::Loader.for_gem
5
5
  loader.setup
6
6
 
7
+ # A performant and expressive solution for serializing Ruby objects into JSON, with a touch of opinionated "magic" ✨.
8
+ #
9
+ # @example Basic usage
10
+ # # Define a data class.
11
+ # class User
12
+ # attr_reader :name, :email
13
+ #
14
+ # def initialize(name:, email:)
15
+ # @name = name
16
+ # @email = email
17
+ # end
18
+ # end
19
+ #
20
+ # # Define a serializer.
21
+ # class UserSerializer < Transmutation::Serializer
22
+ # attribute :name
23
+ # end
24
+ #
25
+ # # Create an instance of the data class.
26
+ # user = User.new(name: "John", email: "john@example.com")
27
+ #
28
+ # # Serialize the data class instance.
29
+ # UserSerializer.new(user).to_json # => "{\"name\":\"John\"}"
30
+ #
31
+ # @example Within a Rails controller
32
+ # class UsersController < ApplicationController
33
+ # include Transmutation::Serialization
34
+ #
35
+ # def show
36
+ # user = User.find(params[:id])
37
+ #
38
+ # # Automatically lookup the UserSerializer
39
+ # # Serialize the data class instance using the UserSerializer
40
+ # # Render the result as JSON to the client
41
+ # render json: user # => "{\"name\":\"John\"}"
42
+ # end
43
+ # end
7
44
  module Transmutation
8
45
  class Error < StandardError; end
9
46
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: transmutation
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - nitemaeric
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: exe
11
11
  cert_chain: []
12
- date: 2024-05-30 00:00:00.000000000 Z
12
+ date: 2024-06-05 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: zeitwerk
@@ -35,6 +35,7 @@ extra_rdoc_files: []
35
35
  files:
36
36
  - ".rspec"
37
37
  - ".rubocop.yml"
38
+ - ".rubocop_todo.yml"
38
39
  - ".ruby-version"
39
40
  - CHANGELOG.md
40
41
  - CODE_OF_CONDUCT.md
@@ -44,10 +45,13 @@ files:
44
45
  - README.md
45
46
  - Rakefile
46
47
  - lib/transmutation.rb
48
+ - lib/transmutation/class_attributes.rb
47
49
  - lib/transmutation/collection_serializer.rb
48
50
  - lib/transmutation/serialization.rb
51
+ - lib/transmutation/serialization/lookup.rb
52
+ - lib/transmutation/serialization/lookup/serializer_not_found.rb
53
+ - lib/transmutation/serialization/rendering.rb
49
54
  - lib/transmutation/serializer.rb
50
- - lib/transmutation/string_refinements.rb
51
55
  - lib/transmutation/version.rb
52
56
  - sig/transmutation.rbs
53
57
  homepage: https://github.com/spellbook-technology/transmutation
@@ -1,37 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Transmutation
4
- module StringRefinements # rubocop:disable Style/Documentation
5
- DELIMITERS = %r{[a-z][A-Z]|\s*-\s*|\s*/\s*|\s*:+\s*|\s*_\s*|\s+}
6
-
7
- refine String do
8
- def camelcase
9
- return capitalize unless match? DELIMITERS
10
-
11
- split(%r{\s*-\s*|\s*/\s*|\s*:+\s*}).then { |parts| combine parts, :capitalize, "::" }
12
- .then { |text| text.split(/\s*_\s*|\s+/) }
13
- .then { |parts| combine parts, :capitalize }
14
- end
15
-
16
- def first(maximum = 0)
17
- return self if empty?
18
- return self[0] if maximum.zero?
19
- return "" if maximum.negative?
20
-
21
- self[..(maximum - 1)]
22
- end
23
-
24
- def capitalize = empty? ? self : first.upcase + self[1, size]
25
-
26
- private
27
-
28
- def combine(parts, method, delimiter = "")
29
- parts.reduce "" do |result, part|
30
- next part.public_send method if result.empty?
31
-
32
- "#{result}#{delimiter}#{part.__send__ method}"
33
- end
34
- end
35
- end
36
- end
37
- end