porridge 0.1.0 → 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: f513e5f2d179544d20b2e0d181066402edd82f03ed491f7ef0c196516a193192
4
- data.tar.gz: 864b15e6a5049bc21e4734c184b686604fecef2598597d4287ddc70a7bdeb3de
3
+ metadata.gz: 1c767e7225acd4284055aa8379a67e9f8f8527d8946ff4df782e3c377d67bec8
4
+ data.tar.gz: b2bad4d91dc0feb01d990aaa1226345e49ce9baf0900578de6f720d47f86d5c3
5
5
  SHA512:
6
- metadata.gz: b8104a38ee8512d49eab94d6c5014158f5dac26d30a47410cfed46d92c5830c32817bc22be2c7274802de8464596a5c00e722992c826a6765b58c75eab0a3b1b
7
- data.tar.gz: f72647f1966d161194f6e239d2da082481a0a84733a0b8d30b99f893d65ab4475bd4f6c61ecf6a3c522b480a139932f75e0081f6eb6c306e1b53f29c7c1b990e
6
+ metadata.gz: 835d2f5b110f0de84a242416791b49b28847de02cab5324f87ea6ee6e6209236f9fbc9e288e3caa158056f8e060e9ff254bf48a73c3b925366ea4772fe11be78
7
+ data.tar.gz: bee59698bf34b8f763c36f39955ffa15ae60976cd151675bbfe2e049f6eb8ba4205d99db74d069b181c64636584d3dbc5816097ceafb9d9f081e7a6ac084d98a
data/.gitignore CHANGED
@@ -9,3 +9,6 @@
9
9
 
10
10
  # rspec failure tracking
11
11
  .rspec_status
12
+
13
+ # built gems
14
+ *.gem
@@ -0,0 +1,2 @@
1
+ # frozen_string_literal: true
2
+
@@ -0,0 +1,5 @@
1
+ #parse("Ruby File Header.rb")
2
+ require 'spec_helper'
3
+
4
+ describe Porridge::${NAME} do
5
+ end
data/.idea/porridge.iml CHANGED
@@ -11,12 +11,16 @@
11
11
  </content>
12
12
  <orderEntry type="inheritedJdk" />
13
13
  <orderEntry type="sourceFolder" forTests="false" />
14
+ <orderEntry type="library" scope="PROVIDED" name="activesupport (v5.2.5, rbenv: 3.0.0) [gem]" level="application" />
14
15
  <orderEntry type="library" scope="PROVIDED" name="ast (v2.4.2, rbenv: 3.0.0) [gem]" level="application" />
15
16
  <orderEntry type="library" scope="PROVIDED" name="bundler (v2.2.25, rbenv: 3.0.0) [gem]" level="application" />
16
17
  <orderEntry type="library" scope="PROVIDED" name="byebug (v11.1.3, rbenv: 3.0.0) [gem]" level="application" />
17
18
  <orderEntry type="library" scope="PROVIDED" name="codecov (v0.6.0, rbenv: 3.0.0) [gem]" level="application" />
19
+ <orderEntry type="library" scope="PROVIDED" name="concurrent-ruby (v1.1.9, rbenv: 3.0.0) [gem]" level="application" />
18
20
  <orderEntry type="library" scope="PROVIDED" name="diff-lcs (v1.5.0, rbenv: 3.0.0) [gem]" level="application" />
19
21
  <orderEntry type="library" scope="PROVIDED" name="docile (v1.4.0, rbenv: 3.0.0) [gem]" level="application" />
22
+ <orderEntry type="library" scope="PROVIDED" name="i18n (v1.8.11, rbenv: 3.0.0) [gem]" level="application" />
23
+ <orderEntry type="library" scope="PROVIDED" name="minitest (v5.15.0, rbenv: 3.0.0) [gem]" level="application" />
20
24
  <orderEntry type="library" scope="PROVIDED" name="parallel (v1.21.0, rbenv: 3.0.0) [gem]" level="application" />
21
25
  <orderEntry type="library" scope="PROVIDED" name="parser (v3.1.0.0, rbenv: 3.0.0) [gem]" level="application" />
22
26
  <orderEntry type="library" scope="PROVIDED" name="rainbow (v3.0.0, rbenv: 3.0.0) [gem]" level="application" />
@@ -36,6 +40,8 @@
36
40
  <orderEntry type="library" scope="PROVIDED" name="simplecov (v0.21.2, rbenv: 3.0.0) [gem]" level="application" />
37
41
  <orderEntry type="library" scope="PROVIDED" name="simplecov-html (v0.12.3, rbenv: 3.0.0) [gem]" level="application" />
38
42
  <orderEntry type="library" scope="PROVIDED" name="simplecov_json_formatter (v0.1.3, rbenv: 3.0.0) [gem]" level="application" />
43
+ <orderEntry type="library" scope="PROVIDED" name="thread_safe (v0.3.6, rbenv: 3.0.0) [gem]" level="application" />
44
+ <orderEntry type="library" scope="PROVIDED" name="tzinfo (v1.2.9, rbenv: 3.0.0) [gem]" level="application" />
39
45
  <orderEntry type="library" scope="PROVIDED" name="unicode-display_width (v2.1.0, rbenv: 3.0.0) [gem]" level="application" />
40
46
  </component>
41
47
  <component name="RakeTasksCache">
data/.yardopts ADDED
@@ -0,0 +1 @@
1
+ --private --protected lib/**/*.rb - README.md
data/CHANGELOG.md CHANGED
@@ -1,5 +1,9 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [0.2.0] - 2022-01-19
4
+
5
+ This is the initial functional release of the gem. Extractors, serializers, fields, field policies, and an elegant DSL over top were implemented added.
6
+
3
7
  ## [0.1.0] - 2022-01-16
4
8
 
5
9
  - Initial release. This version of the gem has no functionality whatsoever and is intended solely as a deployment test.
data/Gemfile.lock CHANGED
@@ -1,17 +1,27 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- porridge (0.1.0)
4
+ porridge (0.2.0)
5
+ activesupport (~> 5.0)
5
6
 
6
7
  GEM
7
8
  remote: https://rubygems.org/
8
9
  specs:
10
+ activesupport (5.2.5)
11
+ concurrent-ruby (~> 1.0, >= 1.0.2)
12
+ i18n (>= 0.7, < 2)
13
+ minitest (~> 5.1)
14
+ tzinfo (~> 1.1)
9
15
  ast (2.4.2)
10
16
  byebug (11.1.3)
11
17
  codecov (0.6.0)
12
18
  simplecov (>= 0.15, < 0.22)
19
+ concurrent-ruby (1.1.9)
13
20
  diff-lcs (1.5.0)
14
21
  docile (1.4.0)
22
+ i18n (1.8.11)
23
+ concurrent-ruby (~> 1.0)
24
+ minitest (5.15.0)
15
25
  parallel (1.21.0)
16
26
  parser (3.1.0.0)
17
27
  ast (~> 2.4.1)
@@ -54,6 +64,9 @@ GEM
54
64
  simplecov_json_formatter (~> 0.1)
55
65
  simplecov-html (0.12.3)
56
66
  simplecov_json_formatter (0.1.3)
67
+ thread_safe (0.3.6)
68
+ tzinfo (1.2.9)
69
+ thread_safe (~> 0.1)
57
70
  unicode-display_width (2.1.0)
58
71
 
59
72
  PLATFORMS
data/README.md CHANGED
@@ -1,5 +1,6 @@
1
1
  # Porridge
2
-
2
+ [![Gem Version](https://badge.fury.io/rb/porridge.svg)](https://badge.fury.io/rb/porridge)
3
+ ![Build](https://github.com/jacoblockard99/porridge/actions/workflows/build.yml/badge.svg)
3
4
  [![Maintainability](https://api.codeclimate.com/v1/badges/9c3a8a230097bac612e3/maintainability)](https://codeclimate.com/github/jacoblockard99/porridge/maintainability)
4
5
  [![codecov](https://codecov.io/gh/jacoblockard99/porridge/branch/master/graph/badge.svg?token=V9GxyepasN)](https://codecov.io/gh/jacoblockard99/porridge)
5
6
 
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Porridge
4
+ # {ArraySerializer} is a serializer that wraps another serializer, calling it for every element of the input array,
5
+ # if an array was given, or simply passing it the input if not.
6
+ class ArraySerializer < Serializer
7
+ # Creates a new instance of {ArraySerializer} with the given base serializer.
8
+ # @param base [Serializer, #call] the base serializer to call for any input, or all elements of that input if the
9
+ # input is an array.
10
+ # @raise [InvalidSerializerError] if the given base serializer is not a valid serializer.
11
+ def initialize(base)
12
+ Serializer.ensure_valid!(base)
13
+ @base = base
14
+ super()
15
+ end
16
+
17
+ # Serializes the given object, which may be an array, for the given input with the given options. If the object
18
+ # is an array (according to {#array?}), the base serializer {#base} will be called for each element, and an array
19
+ # with each result will be returned. If the object is not an array, will simply delegate to {#base}.
20
+ #
21
+ # The given object and options will be given to the base serializer for every element. Note that the options are
22
+ # *not* cloned or duplicated. Therefore <b>the base serializer must not mutate the options object</b> or else
23
+ # the other invocations will also receive the mutated version.
24
+ #
25
+ # @param object_or_objects [Object, Array<Object>] the object or array of objects for which to transform the input.
26
+ # @param input the object being transformed, typically either a hash or an array.
27
+ # @param options [Hash] a hash of "options," which may be application specific.
28
+ # @return [Object, Array<Object>] the transformed output if the object was not an array, or an array of all
29
+ # transformed outputs if the object was an array.
30
+ def call(object_or_objects, input, options)
31
+ if array?(object_or_objects)
32
+ object_or_objects.map { |object| base.call(object, input, options) }
33
+ else
34
+ base.call(object_or_objects, input, options)
35
+ end
36
+ end
37
+
38
+ protected
39
+
40
+ # Determines whether the given object is an array for the purposes of this {ArraySerializer} instance. The default
41
+ # implementation simple checks to see if the object implements the +map+ method. You may override this method to
42
+ # change the default behavior, if, for example, you have a non-array that implements +map+.
43
+ # @param object the object to check.
44
+ # @return [Boolean] +true+ if the given object functions like an array; +false+ if otherwise.
45
+ def array?(object)
46
+ object.respond_to? :map
47
+ end
48
+
49
+ private
50
+
51
+ # The base serializer, which will be called for the object, or each object, if an array is given.
52
+ # @return [Serializer, #call]
53
+ attr_reader :base
54
+ end
55
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Porridge
4
+ # {ChainSerializer} is a serializer that chains multiple other serializers together by passing the output of the
5
+ # first one as the input of the second, and the output of the second as the input of the third, and so on.
6
+ class ChainSerializer < Serializer
7
+ # Creates a new instance of {ChainSerializer} with the given serializers to chain.
8
+ # @param serializers [Array<Serializer,#call>] the splatted array of serializers to chain.
9
+ # @raise [InvalidSerializerError] if any of the given serializers are not valid serializers.
10
+ def initialize(*serializers)
11
+ super()
12
+ Serializer.ensure_valid!(*serializers)
13
+ @serializers = serializers
14
+ end
15
+
16
+ # Transforms the given input for the given object with the given options by chaining each serializer (contained in
17
+ # {#serializers}). The provided input will be given to the first serializer, whose output will be given to the next
18
+ # serializer, and so on for each serializer.
19
+ #
20
+ # The given object and options will be given to all the provided serializers. Note that the options are *not*
21
+ # cloned or duplicated. Therefore <b>none of the serializers should mutate the options object</b> or else
22
+ # all the other serializers will also receive the mutated version.
23
+ #
24
+ # @param object the object for which to transform the input.
25
+ # @param input the object being transformed, typically either a hash or an array.
26
+ # @param options [Hash] a hash of "options," which may be application specific.
27
+ # @return the transformed output, typically either a hash or an array, as returned from the final chained
28
+ # serializer.
29
+ def call(object, input, options)
30
+ output = input
31
+ serializers.each { |serializer| output = serializer.call(object, output, options) }
32
+ output
33
+ end
34
+
35
+ private
36
+
37
+ # The array of chained serializers.
38
+ # @return [Array<Serializer, #call>]
39
+ attr_reader :serializers
40
+ end
41
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Porridge
4
+ # {Error} is the base class for all porridge-related errors. You may thus catch {Error} to catch all porridge-specific
5
+ # errors.
6
+ class Error < StandardError
7
+ end
8
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Porridge
4
+ # {Extractor} is the nominal base class for all porridge value extractors.
5
+ #
6
+ # A (value) extractor is an object that is capable of retrieving a value from an object, given a set of "options",
7
+ # which may be application-specific. You are encouraged, but not required, to have your extractors derive from this
8
+ # class. Currently, any object that implements a +#call+ method is a valid extractor.
9
+ class Extractor
10
+ # Determines whether the given object is a valid porridge extractor. Currently, any object that responds to the
11
+ # +#call+ method is valid.
12
+ # @param object the object to check.
13
+ # @return [Boolean] +true+ if the object is a valid extractor; +false+ otherwise.
14
+ def self.valid?(object)
15
+ object.respond_to? :call
16
+ end
17
+
18
+ # Ensures that all the provided objects are valid extractors, raising {InvalidExtractorError} if not.
19
+ # @param objects [Array] the splatted array of objects to validate.
20
+ # @return [Boolean] +true+ if all the objects were valid; raises an error otherwise.
21
+ # @raise [InvalidExtractorError] if any of the provided objects are not valid extractors.
22
+ def self.ensure_valid!(*objects)
23
+ objects.each { |object| raise InvalidExtractorError unless valid?(object) }
24
+ true
25
+ end
26
+
27
+ # Should extract a value from the given object with the given options. Subclasses should override this method.
28
+ # @param object the object from which to retrieve the value.
29
+ # @param options [Hash] a hash of "options," which may be application-specific.
30
+ # @return the extracted value.
31
+ def call(object, options); end
32
+ end
33
+ end
@@ -0,0 +1,80 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Porridge
4
+ # {Factory} is a class that is capable of instantiating various porridge serializers and extractors. All extractor-
5
+ # creation methods are suffixed with +_extractor+, all serializer-creation methods are suffixed with +_serializer+,
6
+ # and all field serializer-creation methods are suffixed with +_field_serializer+.
7
+ #
8
+ # You may subclass this class if you wish to change its behavior. For example, if you wished to substitute your
9
+ # own "from name" extractor, that accesses a hash value rather than sends a method call, for the default one:
10
+ #
11
+ # class CustomPorridgeFactory < Porridge::Factory
12
+ # def from_name_extractor(name)
13
+ # extractor HashValueExtractor.new(name)
14
+ # end
15
+ # end
16
+ #
17
+ # {#from_name_field_serializer}, {#attribute_extractor}, and {#attribute_field_serializer} would then be automatically
18
+ # updated in the process, along with any other methods that depend on the aforementioned ones.
19
+ #
20
+ # This method is rarely used directly. Typically, it is used in conjunction with a {SerializerDefiner} and/or
21
+ # {SerializerDefinition} instance.
22
+ class Factory
23
+ def extractor(base)
24
+ return nil if base.nil?
25
+
26
+ Extractor.ensure_valid!(base)
27
+ base
28
+ end
29
+
30
+ def from_name_extractor(name)
31
+ extractor SendExtractor.new(name)
32
+ end
33
+
34
+ def custom_extractor(callback)
35
+ extractor callback
36
+ end
37
+
38
+ def association_extractor(serializer:, extractor: nil, extraction_name: nil, callback: nil, &block)
39
+ extractor SerializingExtractor.new(
40
+ extractor || custom_extractor(callback) || custom_extractor(block) || from_name_extractor(extraction_name),
41
+ serializer
42
+ )
43
+ end
44
+ alias belongs_to_extractor association_extractor
45
+ alias has_many_extractor association_extractor
46
+
47
+ def serializer(base)
48
+ return nil if base.nil?
49
+
50
+ Serializer.ensure_valid!(base)
51
+ base
52
+ end
53
+
54
+ def chain_serializer(*bases)
55
+ serializer ChainSerializer.new(*bases)
56
+ end
57
+ alias serializers chain_serializer
58
+
59
+ def for_extracted_serializer(serializer, extractor)
60
+ serializer SerializerForExtracted.new(serializer, extractor)
61
+ end
62
+ alias serializer_for_extracted for_extracted_serializer
63
+
64
+ def field_serializer(name, extractor)
65
+ serializer FieldSerializer.new(name, extractor)
66
+ end
67
+
68
+ def attribute_field_serializer(name, callback = nil, extraction_name: nil, &block)
69
+ extractor = custom_extractor(callback || block) || from_name_extractor(extraction_name || name)
70
+ field_serializer(name, extractor)
71
+ end
72
+
73
+ def association_field_serializer(name, options = {}, &block)
74
+ options[:extraction_name] ||= name
75
+ field_serializer(name, association_extractor(**options, &block))
76
+ end
77
+ alias belongs_to_field_serializer association_field_serializer
78
+ alias has_many_field_serializer association_field_serializer
79
+ end
80
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Porridge
4
+ # {FieldPolicy} is the nominal base class for all field policy classes.
5
+ #
6
+ # A field policy is an object that is capable of determining whether a certain "field" is allowed in a given
7
+ # context. Currently, it is primarily used in {FieldSerializer} as the default method of determining whether a field
8
+ # is valid. You are encouraged, but not required, to have your own custom field policies derive from this class.
9
+ # Currently, any object that implements the +#allowed?+ method is a valid field policy.
10
+ class FieldPolicy
11
+ # Determines whether the given object is a valid porridge field policy. Currently, any object that responds to the
12
+ # +#allowed?+ method is valid.
13
+ # @param object the object to check.
14
+ # @return [Boolean] +true+ if the object is a valid field policy; +false+ otherwise.
15
+ def self.valid?(object)
16
+ object.respond_to? :allowed?
17
+ end
18
+
19
+ # Ensures that all the provided objects are valid field policies, raising {InvalidFieldPolicyError} if not.
20
+ # @param objects [Array] the splatted array of objects to validate.
21
+ # @return [Boolean] +true+ if all the objects were valid; raises an error otherwise.
22
+ # @raise [InvalidFieldPolicyError] if any of the provided objects are not valid field policies.
23
+ def self.ensure_valid!(*objects)
24
+ objects.each { |object| raise InvalidFieldPolicyError unless valid?(object) }
25
+ true
26
+ end
27
+
28
+ # Determiners whether the field with the given name for the given object with the given options is currently
29
+ # allowed.
30
+ # @param _name the name of the field being validated.
31
+ # @param _object the object for which the field being validated is being generated.
32
+ # @param _options [Hash] the options with which the field being validated is being generated.
33
+ # @return [Boolean] +true+ if the indicated field is allowed; +false+ otherwise.
34
+ def allowed?(_name, _object, _options)
35
+ true
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,88 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Porridge
4
+ # {FieldSerializer} is a serializer that adds a "field" to a hash. It does so by using a predefined "field name" as
5
+ # the key and evaluates an {Extractor} for the object for the value.
6
+ #
7
+ # {FieldSerializer} is the most opinionated piece of the porridge framework. In particular, it adds to a
8
+ # +:field_hierarchy+ array in the options hash to keep track of nested fields. It also requires the use of a
9
+ # {FieldPolicy} object given in the options to determine whether a given field is allowed. Do not be afraid to
10
+ # subclass {FieldSerializer} or even create a new "field serializer" class altogether if you want to substantially
11
+ # change the way fields are implemented.
12
+ class FieldSerializer < Serializer
13
+ # Creates a new instance of {FieldSerializer} with the given field name and value extractor.
14
+ # @param name the name of the field; will be used as the key for the field in the hash.
15
+ # @param extractor [Extractor, #call] the value extractor to use to retrieve a value from the object, which will be
16
+ # used as the value for the field in the hash.
17
+ # @raise [InvalidExtractorError] if the provided extractor is not a valid extractor.
18
+ def initialize(name, extractor)
19
+ @name = name
20
+ @extractor = extractor
21
+ Extractor.ensure_valid!(extractor)
22
+ super()
23
+ end
24
+
25
+ # Serializes the given input hash for the given object with the given options by adding an element to the hash with
26
+ # a key that is equal to the field name ({#name}) and a value extracted from the object using the field extractor
27
+ # ({#extractor}).
28
+ # @param object the object for which to transform the input. The field value will be retrieved from this object
29
+ # using the extractor.
30
+ # @param hash [Hash] the input hash being transformed. A key-value pair will be added for the field.
31
+ # @param options [Hash] a hash of "options," which may be application specific.
32
+ # @option options [FieldPolicy, #allowed?] :field_policy the field policy to use to determine whether the field
33
+ # is currently allowed. This option *must* be provided.
34
+ # @return [Hash] the transformed hash.
35
+ # @raise [InvalidFieldPolicyError] if no field policy was provided or if the field policy was not a valid field
36
+ # policy object.
37
+ def call(object, hash, options)
38
+ if allowed?(object, options)
39
+ options = options.dup
40
+ add_field_to_hierarchy!(options)
41
+ hash_with_field(object, hash, options)
42
+ else
43
+ hash
44
+ end
45
+ end
46
+
47
+ private
48
+
49
+ # Determines whether the given object/options, along with the current {#name} constitutes a valid field. Currently,
50
+ # this is done by simply delegating to the field policy which should have been provided as an option.
51
+ # @param object the object for which the field being validated is being implemented.
52
+ # @param options [Hash] the options for which the field being validated is being implemented.
53
+ # @return [Boolean] +true+ if the indicated field is valid; +false+ otherwise.
54
+ # @raise [InvalidFieldPolicyError] if no field policy was provided or if the field policy was not a valid field
55
+ # policy object.
56
+ def allowed?(object, options)
57
+ FieldPolicy.ensure_valid!(options[:field_policy])
58
+ options[:field_policy].allowed?(name, object, options.except(:field_policy))
59
+ end
60
+
61
+ # Safely adds the current {#name} to the +:field_hierarchy+ in the given options hash. While the options hash itself
62
+ # is mutated, the field hierarchy array is first duplicated, meaning the options hash must have first been
63
+ # duplicated for this to be safe.
64
+ # @param options_hash [Hash] the options hash to which the field should be added.
65
+ # @return [void]
66
+ def add_field_to_hierarchy!(options_hash)
67
+ options_hash[:field_hierarchy] ||= []
68
+ options_hash[:field_hierarchy] = options_hash[:field_hierarchy].dup
69
+ options_hash[:field_hierarchy] << name
70
+ end
71
+
72
+ # Creates a new hash from the given one with a field for the given object injected.
73
+ # @param object the object for which to inject the field. Will be passed to the extractor.
74
+ # @param hash [Hash] the hash into which to inject the field.
75
+ # @param options [Hash] the options for which to inject the field. Will be passed to the extractor.
76
+ # @return [Hash] the transformed hash.
77
+ def hash_with_field(object, hash, options)
78
+ hash.merge(name => extractor.call(object, options))
79
+ end
80
+
81
+ # The name of the field being serialized by this {FieldSerializer}; used as the key for the field in the hash.
82
+ attr_reader :name
83
+
84
+ # The value extractor to use to retrieve a value from the object for which serialization is occurring.
85
+ # @return [Extractor, #call]
86
+ attr_reader :extractor
87
+ end
88
+ end
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Porridge
4
+ # {InvalidExtractorError} is the error that is thrown when a non-extractor object is used like an extractor.
5
+ class InvalidExtractorError < Error; end
6
+ end
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Porridge
4
+ # {InvalidFieldPolicyError} is the error that is thrown when a non-field-policy object is used like a field policy.
5
+ class InvalidFieldPolicyError < Error; end
6
+ end
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Porridge
4
+ # {InvalidSerializerError} is the error that is thrown when a non-serializer object is used like a serializer.
5
+ class InvalidSerializerError < Error; end
6
+ end
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_support'
4
+ require 'active_support/core_ext/hash/keys'
5
+
6
+ module Porridge
7
+ # {KeyNormalizingSerializer} is a serializer that wraps another serializer and recursively normalizes the keys of the
8
+ # resulting hash to either strings or symbols.
9
+ class KeyNormalizingSerializer < Serializer
10
+ # Creates a new instance of {KeyNormalizingSerializer} with the given base serializer and key type.
11
+ # @param base [Serializer, #call] the base serializer to wrap. Note that the output of the base serializer *must*
12
+ # be a hash.
13
+ # @param key_type [Symbol] the type that the keys should be normalized to. Both +:string+ and +:symbol+ are
14
+ # supported.
15
+ # @raise [InvalidSerializerError] if the given base serializer is not a valid serializer.
16
+ def initialize(base, key_type: :string)
17
+ Serializer.ensure_valid!(base)
18
+ @base = base
19
+ @key_type = key_type
20
+ super()
21
+ end
22
+
23
+ # Serializes the given input for the given object with the given options by delegating to the base serializer
24
+ # ({#base}) and recursively transforming the keys of the resulting hash to the appropriate type ({#key_type}).
25
+ # Note that the output of the base serializer *must* be a hash.
26
+ # @param object the object for which to transform the input.
27
+ # @param input the object being transformed, typically either a hash or an array.
28
+ # @param options [Hash] a hash of "options," which may be application specific.
29
+ # @return [Hash] the hash returned from the base serializer, normalized.
30
+ def call(object, input, options)
31
+ normalize_keys(base.call(object, input, options))
32
+ end
33
+
34
+ private
35
+
36
+ # Normalizes the keys of the given hash according to the {#key_type}. Uses ActiveSupport methods to accomplish this.
37
+ # @param hash [Hash] the hash to normalize.
38
+ # @return [Hash] the normalized hash.
39
+ def normalize_keys(hash)
40
+ key_type == :symbol ? hash.deep_symbolize_keys : hash.deep_stringify_keys
41
+ end
42
+
43
+ # The base serializer whose output hash will be normalized
44
+ # @return [Serializer, #call]
45
+ attr_reader :base
46
+
47
+ # The key type that the hash should be normalized to.
48
+ # @return [Symbol] either +:string+ or +:symbol+.
49
+ attr_reader :key_type
50
+ end
51
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Porridge
4
+ # {SendExtractor} is an extractor that retrieves a value from an object by simply calling a predefined method on it.
5
+ class SendExtractor < Extractor
6
+ # Creates a new instance of {SendExtractor} with the given method name.
7
+ # @param method_name [String, Symbol] the name of the method to call when extracting the value.
8
+ def initialize(method_name)
9
+ @method_name = method_name.to_s
10
+ super()
11
+ end
12
+
13
+ # Extracts the value from the given object by sending the method name ({#method_name}) to it.
14
+ # @param object the object from which to retrieve the value.
15
+ # @param _options [Hash] a hash of "options," which may be application-specific. These options are ignored.
16
+ # @return the extracted value, as returned from the sent method.
17
+ def call(object, _options)
18
+ object.respond_to?(method_name) ? object.send(method_name) : nil
19
+ end
20
+
21
+ private
22
+
23
+ # The name of the method to call when extracting the value.
24
+ # @return [String]
25
+ attr_reader :method_name
26
+ end
27
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Porridge
4
+ # {Serializer} is the nominal base class for all porridge serializers.
5
+ #
6
+ # A serializer is an object that arbitrarily transforms a given input for a given object with a given set of options.
7
+ # The input may be anything, but is typically either a hash or an array. In Rails applications, the object is often
8
+ # an ActiveRecord model. The options may be application-specific.
9
+ #
10
+ # Serializers are the heart and soul of the porridge gem and are typically layered with composition into a final
11
+ # serializer that is used to actually serialize an object into (typically) a hash or array. Thus, a serializer often
12
+ # simply wraps another serializer, transforming the object or options in some way.
13
+ #
14
+ # You are encouraged, but not required, to have all your serializers derive from this class. Currently, any object
15
+ # that implements the +#call+ method is a valid serializer.
16
+ class Serializer
17
+ # Determines whether the given object is a valid porridge serializer. Currently, any object that responds to the
18
+ # '#call' method is valid.
19
+ # @param object the object to check.
20
+ # @return [Boolean] +true+ if the object is a valid serializer; +false+ otherwise.
21
+ def self.valid?(object)
22
+ object.respond_to? :call
23
+ end
24
+
25
+ # Ensures that all the provided objects are valid serializers, raising {InvalidSerializerError} if not.
26
+ # @param objects [Array] the splatted array of objects to validate.
27
+ # @return [Boolean] +true+ if all the objects were valid; raises an error otherwise.
28
+ # @raise [InvalidSerializerError] if any of the provided objects are not valid serializers.
29
+ def self.ensure_valid!(*objects)
30
+ objects.each { |object| raise InvalidSerializerError unless valid?(object) }
31
+ true
32
+ end
33
+
34
+ # Should transforms the given input for the given object with the given options and return the desired output.
35
+ # @param _object the object for which to transform the input.
36
+ # @param input the object being transformed, typically either a hash or an array.
37
+ # @param _options [Hash] a hash of "options," which may be application specific.
38
+ # @return the transformed output, typically either a hash or an array.
39
+ def call(_object, input, _options)
40
+ input
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,73 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Porridge
4
+ # {SerializerDefiner} is a class that wraps a {Factory} and allows serializes to be easily defined with an elegant
5
+ # DSL.
6
+ class SerializerDefiner
7
+ FACTORY_PREFIX = 'create_'
8
+ SERIALIZER_SUFFIX = '_serializer'
9
+ FIELD_SERIALIZER_SUFFIX = '_field_serializer'
10
+
11
+ delegate :call, to: :defined_serializer
12
+
13
+ def initialize(factory = Factory.new)
14
+ @factory = factory
15
+ @serializers = []
16
+ end
17
+
18
+ def method_missing(method_name, *args, &block)
19
+ method_name = method_name.to_s
20
+ return factory.send(method_name.delete_prefix(FACTORY_PREFIX), *args, &block) if create_method? method_name
21
+
22
+ if serializer_method? method_name
23
+ return add_serializer(factory.send(method_name + SERIALIZER_SUFFIX, *args, &block))
24
+ end
25
+
26
+ if field_serializer_method? method_name
27
+ return add_serializer(factory.send(method_name + FIELD_SERIALIZER_SUFFIX, *args, &block))
28
+ end
29
+
30
+ super(method_name.to_sym, *args, &block)
31
+ end
32
+
33
+ def respond_to_missing?(method_name, include_private = false)
34
+ method_name = method_name.to_s
35
+ super(method_name.to_sym, include_private) ||
36
+ create_method?(method_name) ||
37
+ serializer_method?(method_name) ||
38
+ field_serializer_method?(method_name)
39
+ end
40
+
41
+ def defined_serializer
42
+ factory.serializers(*added_serializers)
43
+ end
44
+
45
+ def added_serializers
46
+ @serializers
47
+ end
48
+
49
+ def serializer(...)
50
+ add_serializer(factory.serializer(...))
51
+ end
52
+
53
+ private
54
+
55
+ def create_method?(method_name)
56
+ method_name.start_with?(FACTORY_PREFIX) && factory.respond_to?(method_name.delete_prefix(FACTORY_PREFIX))
57
+ end
58
+
59
+ def serializer_method?(method_name)
60
+ factory.respond_to?(method_name + SERIALIZER_SUFFIX)
61
+ end
62
+
63
+ def field_serializer_method?(method_name)
64
+ factory.respond_to?(method_name + FIELD_SERIALIZER_SUFFIX)
65
+ end
66
+
67
+ def add_serializer(serializer)
68
+ @serializers << serializer
69
+ end
70
+
71
+ attr_reader :factory
72
+ end
73
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Porridge
4
+ # {SerializerDefinition} is a class that allows serializers to be defined as with a {SerializerDefiner}, but within
5
+ # a class. Simply subclass this class and use the same DSL within it.
6
+ class SerializerDefinition
7
+ class << self
8
+ attr_writer :definer
9
+
10
+ delegate_missing_to :definer
11
+
12
+ def inherited(subclass)
13
+ super
14
+ definer.added_serializers.each { |serializer| subclass.definer.serializer(serializer) }
15
+ end
16
+
17
+ def definer
18
+ @definer ||= create_definer
19
+ end
20
+
21
+ def create_definer
22
+ SerializerDefiner.new
23
+ end
24
+
25
+ def reset!
26
+ @definer = nil
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Porridge
4
+ # {SerializerForExtracted} is a serializer that wraps another serializer and passes it an object that is extracted
5
+ # from the initial object using an {Extractor}.
6
+ class SerializerForExtracted < Serializer
7
+ # Creates a new instance of {SerializerForExtracted} with the given base serializer and extractor.
8
+ # @param base [Serializer, #call] the base serializer to wrap.
9
+ # @param extractor [Extractor, #call] the extractor to use to extract a value from the object before passing it
10
+ # to the base serializer.
11
+ # @raise [InvalidSerializerError] if the provided base serializer is not a valid serializer.
12
+ # @raise [InvalidExtractorError] if the provided extractor is not a valid extractor.
13
+ def initialize(base, extractor)
14
+ Serializer.ensure_valid!(base)
15
+ Extractor.ensure_valid!(extractor)
16
+ @base = base
17
+ @extractor = extractor
18
+ super()
19
+ end
20
+
21
+ # Serializes the given input for the given object with the given options by first extracted a value from the given
22
+ # object, then passing that value, along with the given input and options, to the base serializer ({#base}).
23
+ # @param object the object for which to transform the input. A value will be extracted and that value will be passed
24
+ # to the base serializer.
25
+ # @param input the object being transformed, typically either a hash or an array.
26
+ # @param options [Hash] a hash of "options," which may be application specific.
27
+ # @return the transformed output, typically either a hash or an array, as returned from the base serializer.
28
+ def call(object, input, options)
29
+ extracted_value = extractor.call(object, options)
30
+ base.call(extracted_value, input, options)
31
+ end
32
+
33
+ private
34
+
35
+ # The base serializer to wrap.
36
+ # @return [Serializer, #call]
37
+ attr_reader :base
38
+
39
+ # The extractor to use to extract a value from the object before passing it to the base serializer.
40
+ # @return [Extractor, #call]
41
+ attr_reader :extractor
42
+ end
43
+ end
@@ -0,0 +1,86 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_support'
4
+ require 'active_support/core_ext/string/inflections'
5
+
6
+ module Porridge
7
+ # {SerializerWithRoot} is a serializer that wraps another serializer and adds a "root key" to the resulting hash.
8
+ class SerializerWithRoot < Serializer
9
+ # Creates a new instance of {SerializerWithRoot} with the given base serializer and, optionally, root key.
10
+ # @param base [Serializer, #call] the base serializer to wrap.
11
+ # @param root_key the "root" key to inject into the resulting hash. If +nil+, which is the default, the root key
12
+ # will be inferred from the object.
13
+ # @raise [InvalidSerializerError] if the provided base serializer is not a valid serializer.
14
+ def initialize(base, root_key: nil)
15
+ Serializer.ensure_valid!(base)
16
+ @base = base
17
+ @root_key = root_key
18
+ super()
19
+ end
20
+
21
+ # Serializes the given input for the given object with the given options by delegating to the base serializer
22
+ # ({#base}) and adding a root key to the resulting hash. Note that the output of the base serializer *must* be a
23
+ # hash.
24
+ #
25
+ # If the root key was not set manually, it will be inferred from the "underscored" class name of the object. If the
26
+ # object is an array (according to {#array?}), then the class name will be derived from the first object in the
27
+ # array, and will be pluralized. Be aware that retrieving the first element of the "array" may cause an SQL query to
28
+ # be performed if the "array" is a Rails relation.
29
+ #
30
+ # Note that an inferred root key is always a string. You may wish to use a {KeyNormalizingSerializer} if symbol
31
+ # keys are desired.
32
+ #
33
+ # @param object the object for which to transform the input. If no root key was set manually, it will be inferred
34
+ # from the object's class.
35
+ # @param input the object being transformed, typically either a hash or an array.
36
+ # @param options [Hash] a hash of "options," which may be application specific.
37
+ # @return [Hash] the hash returned from the base serializer, injected with a root key.
38
+ def call(object, input, options)
39
+ { evaluate_root_key(object) => base.call(object, input, options) }
40
+ end
41
+
42
+ protected
43
+
44
+ # Determines whether the given object functions like an array for the purposes of this {SerializerWithRoot}. The
45
+ # default implementation checks to see whether the object implements both +#map+ and +#first+. You may override the
46
+ # default behavior by overriding this method. Note that if you override {ArraySerializer#like_array?} you will
47
+ # likely wish to override this method as well.
48
+ # @param object the object to check.
49
+ # @return [Boolean] +true+ if the given object is like an array; +false+ otherwise.
50
+ def array?(object)
51
+ object.respond_to?(:map) && object.respond_to?(:first)
52
+ end
53
+
54
+ private
55
+
56
+ # Gets a root key for the given object by either returning {#root_key}, or returning a singular/plural version
57
+ # of the {#base_root_key}, depending on whether the object is an array.
58
+ # @param object the object for which to get a root key.
59
+ # @return the resolved string root key.
60
+ def evaluate_root_key(object)
61
+ return root_key if root_key
62
+
63
+ array?(object) ? base_root_key(object).pluralize : base_root_key(object).singularize
64
+ end
65
+
66
+ # Gets the inferred base root key, without singularization or pluralization for the given object.
67
+ # @param object the object for which to get the base root key.
68
+ # @return [String] the resolved base root key.
69
+ def base_root_key(object)
70
+ representative_sample(object).class.name.underscore.to_s
71
+ end
72
+
73
+ # Gets a "representative" sample from the given object. In practice, this means either returning the object itself,
74
+ # or, if the object is an array-like structure, returning the first element.
75
+ def representative_sample(object)
76
+ array?(object) ? object.first : object
77
+ end
78
+
79
+ # The base serializer to wrap.
80
+ # @return [Serializer, #call]
81
+ attr_reader :base
82
+
83
+ # The explicit root key; if +nil+, will be inferred.
84
+ attr_reader :root_key
85
+ end
86
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Porridge
4
+ # {SerializingExtractor} is an extractor that wraps another extractor and serializes for its output using a provided
5
+ # serializer. Note that {SerializingExtractor} passes the output of the base extractor as the *object* for the
6
+ # serializer, not the input.
7
+ class SerializingExtractor < Extractor
8
+ # Creates a new instance of {SerializingExtractor} with the given base extractor and serializer.
9
+ # @param base [Extractor, #call] the extractor to wrap, whose output will be serialized for before being returned.
10
+ # @param serializer [Serializer, #call] the serializer to use to transform the output of the extractor.
11
+ # @raise [InvalidExtractorError] if the provided base extractor was not a valid extractor.
12
+ # @raise [InvalidSerializerError] if the provided serializer was not a valid serializer.
13
+ def initialize(base, serializer)
14
+ Extractor.ensure_valid!(base)
15
+ Serializer.ensure_valid!(serializer)
16
+ @base = base
17
+ @serializer = serializer
18
+ super()
19
+ end
20
+
21
+ # Extracts a value from the given object for the given options by:
22
+ # 1. Using the base extractor ({#extractor}) to extract a value from the object with the given options; and
23
+ # 2. Passing that value as the object to {#serializer#call}. A blank hash is given as the input, and the
24
+ # given options are passed along.
25
+ # @param object the object from which to retrieve the value.
26
+ # @param options [Hash] a hash of "options," which may be application-specific.
27
+ # @return the extracted value.
28
+ def call(object, options)
29
+ serializer.call(base.call(object, options), {}, options)
30
+ end
31
+
32
+ private
33
+
34
+ # The base extractor.
35
+ # @return [Extractor, #call]
36
+ attr_reader :base
37
+
38
+ # The serializer to use to serialize for the output of the base extractor.
39
+ # @return [Serializer, #call]
40
+ attr_reader :serializer
41
+ end
42
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Porridge
4
- VERSION = '0.1.0'
4
+ VERSION = '0.2.0'
5
5
  end
@@ -0,0 +1,70 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Porridge
4
+ # {WhitelistFieldPolicy} is a field policy that uses a nested whitelist of field names to determine which fields are
5
+ # valid.
6
+ class WhitelistFieldPolicy < FieldPolicy
7
+ # Creates a new instance of {WhitelistFieldPolicy} with the given whitelist.
8
+ # @param whitelist [Hash] the nested whitelist hash of allowed field names.
9
+ def initialize(whitelist)
10
+ @whitelist = whitelist
11
+ super()
12
+ end
13
+
14
+ # Determiners whether the field with the given name with the given options is currently allowed by checking the
15
+ # field hierarchy, which must be contained in +options[:field_hierarchy] against the whitelist.
16
+ # @param name the name of the field being validated.
17
+ # @param _object the object for which the field being validated is being generated.
18
+ # @param options [Hash] the options with which the field being validated is being generated.
19
+ # @return [Boolean] +true+ if the indicated field is allowed; +false+ otherwise.
20
+ def allowed?(name, _object, options)
21
+ field_hierarchy = options[:field_hierarchy] || []
22
+ _allowed?([*field_hierarchy, name], whitelist)
23
+ end
24
+
25
+ protected
26
+
27
+ # Determines whether the given object functions as a hash for the purposes of this {WhitelistFieldPolicy} instance.
28
+ # You may override this method if desired, but hashes must at least respond to +#[]+.
29
+ # @param input the input object to check.
30
+ # @return [Boolean] +true+ if the given object is like a hash; +false+ otherwise.
31
+ def hash?(input)
32
+ input.is_a? Hash
33
+ end
34
+
35
+ private
36
+
37
+ # @overload _allowed?(field_hierarchy, whitelist)
38
+ # Recursively traverses the given field hierarchy and determines whether the field indicated by the hierarchy is
39
+ # allowed for the given whitelist.
40
+ # @param field_hierarchy [Array] the field hierarchy to validate.
41
+ # @param whitelist [Hash] the nested whitelist hash of field names.
42
+ # @overload _allowed?(field_hierarchy, whitelist, level)
43
+ # Recursively traverses the given field hierarchy and determines whether the field indicated by the hierarchy is
44
+ # allowed for the given whitelist, starting from the specified level.
45
+ # @param field_hierarchy [Array] the field hierarchy to validate.
46
+ # @param whitelist [Hash] the nested whitelist hash of field names.
47
+ # @param level [Integer] the current level of the hierarchy being checked.
48
+ def _allowed?(field_hierarchy, whitelist, level = 0)
49
+ # If the level is equal to the field hierarchy length, then we've reached the end. Immediately return the
50
+ # truthiness of whitelist, which is now equal to the final resolved value referenced by the field hierarchy.
51
+ return !!whitelist if level >= field_hierarchy.count
52
+
53
+ # If the current whitelist is not a hash, then the field hierarchy is deeper than the whitelist.
54
+ # As an example, take this whitelist:
55
+ # { users: true }
56
+ # And this field hierarchy:
57
+ # [:user, :id]
58
+ # One interpretation of this is that since 'users' is true, all fields should be allowed. We take the opposite
59
+ # approach and say that no attributes have been explicitly defined.
60
+ # Therefore immediately return false.
61
+ return false unless hash?(whitelist)
62
+
63
+ _allowed?(field_hierarchy, whitelist[field_hierarchy[level]], level + 1)
64
+ end
65
+
66
+ # The nested whitelist hash of field names.
67
+ # @return [Hash]
68
+ attr_reader :whitelist
69
+ end
70
+ end
data/lib/porridge.rb CHANGED
@@ -1,8 +1,25 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative 'porridge/version'
4
+ require_relative 'porridge/extractor'
5
+ require_relative 'porridge/send_extractor'
6
+ require_relative 'porridge/serializer'
7
+ require_relative 'porridge/chain_serializer'
8
+ require_relative 'porridge/array_serializer'
9
+ require_relative 'porridge/key_normalizing_serializer'
10
+ require_relative 'porridge/serializer_for_extracted'
11
+ require_relative 'porridge/serializer_with_root'
12
+ require_relative 'porridge/error'
13
+ require_relative 'porridge/invalid_serializer_error'
14
+ require_relative 'porridge/invalid_extractor_error'
15
+ require_relative 'porridge/invalid_field_policy_error'
16
+ require_relative 'porridge/field_policy'
17
+ require_relative 'porridge/field_serializer'
18
+ require_relative 'porridge/serializing_extractor'
19
+ require_relative 'porridge/whitelist_field_policy'
20
+ require_relative 'porridge/factory'
21
+ require_relative 'porridge/serializer_definer'
22
+ require_relative 'porridge/serializer_definition'
4
23
 
5
- module Porridge
6
- class Error < StandardError; end
7
- # Your code goes here...
8
- end
24
+ # {Porridge} is the root namespace for all classes in the +porridge+ gem.
25
+ module Porridge; end
data/porridge.gemspec CHANGED
@@ -42,6 +42,8 @@ Gem::Specification.new do |spec|
42
42
  # Uncomment to register a new dependency of your gem
43
43
  # spec.add_dependency "example-gem", "~> 1.0"
44
44
 
45
+ spec.add_dependency 'activesupport', '~> 5.0'
46
+
45
47
  # For more information and examples about making a new gem, checkout our
46
48
  # guide at: https://bundler.io/guides/creating_gem.html
47
49
  spec.metadata['rubygems_mfa_required'] = 'true'
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: porridge
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jacob
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2022-01-16 00:00:00.000000000 Z
11
+ date: 2022-01-19 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: byebug
@@ -108,6 +108,20 @@ dependencies:
108
108
  - - "~>"
109
109
  - !ruby/object:Gem::Version
110
110
  version: '0.21'
111
+ - !ruby/object:Gem::Dependency
112
+ name: activesupport
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - "~>"
116
+ - !ruby/object:Gem::Version
117
+ version: '5.0'
118
+ type: :runtime
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - "~>"
123
+ - !ruby/object:Gem::Version
124
+ version: '5.0'
111
125
  description: |
112
126
  `porridge` is a plain Ruby gem that takes a flexible, object-oriented approach to serialization. `porridge`
113
127
  transforms objects into ruby hashes and arrays, which can then be serialized with other libraries.
@@ -120,12 +134,15 @@ files:
120
134
  - ".github/workflows/build.yml"
121
135
  - ".gitignore"
122
136
  - ".idea/.gitignore"
137
+ - ".idea/fileTemplates/includes/Ruby File Header.rb"
138
+ - ".idea/fileTemplates/internal/RSpec.rb"
123
139
  - ".idea/misc.xml"
124
140
  - ".idea/modules.xml"
125
141
  - ".idea/porridge.iml"
126
142
  - ".idea/vcs.xml"
127
143
  - ".rspec"
128
144
  - ".rubocop.yml"
145
+ - ".yardopts"
129
146
  - CHANGELOG.md
130
147
  - Gemfile
131
148
  - Gemfile.lock
@@ -135,7 +152,26 @@ files:
135
152
  - bin/console
136
153
  - bin/setup
137
154
  - lib/porridge.rb
155
+ - lib/porridge/array_serializer.rb
156
+ - lib/porridge/chain_serializer.rb
157
+ - lib/porridge/error.rb
158
+ - lib/porridge/extractor.rb
159
+ - lib/porridge/factory.rb
160
+ - lib/porridge/field_policy.rb
161
+ - lib/porridge/field_serializer.rb
162
+ - lib/porridge/invalid_extractor_error.rb
163
+ - lib/porridge/invalid_field_policy_error.rb
164
+ - lib/porridge/invalid_serializer_error.rb
165
+ - lib/porridge/key_normalizing_serializer.rb
166
+ - lib/porridge/send_extractor.rb
167
+ - lib/porridge/serializer.rb
168
+ - lib/porridge/serializer_definer.rb
169
+ - lib/porridge/serializer_definition.rb
170
+ - lib/porridge/serializer_for_extracted.rb
171
+ - lib/porridge/serializer_with_root.rb
172
+ - lib/porridge/serializing_extractor.rb
138
173
  - lib/porridge/version.rb
174
+ - lib/porridge/whitelist_field_policy.rb
139
175
  - porridge.gemspec
140
176
  homepage: https://github.com/jacoblockard99/porridge
141
177
  licenses: