transmutation 0.1.1 → 0.2.0

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
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