ializer 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.github/workflows/ruby.yml +45 -0
- data/.github/workflows/rubygems-publish.yml +39 -0
- data/.gitignore +12 -0
- data/.mdlrc +1 -0
- data/.rspec +3 -0
- data/.rubocop.yml +21 -0
- data/.ruby-version +1 -0
- data/Gemfile +5 -0
- data/LICENSE.txt +7 -0
- data/README.md +557 -0
- data/Rakefile +8 -0
- data/bin/console +8 -0
- data/bin/setup +8 -0
- data/ializer.gemspec +33 -0
- data/lib/de/ser/ializer.rb +87 -0
- data/lib/ializer/big_decimal_de_ser.rb +17 -0
- data/lib/ializer/boolean_de_ser.rb +18 -0
- data/lib/ializer/config.rb +47 -0
- data/lib/ializer/date_de_ser.rb +19 -0
- data/lib/ializer/default_de_ser.rb +15 -0
- data/lib/ializer/fix_num_de_ser.rb +17 -0
- data/lib/ializer/float_de_ser.rb +31 -0
- data/lib/ializer/millis_de_ser.rb +17 -0
- data/lib/ializer/string_de_ser.rb +15 -0
- data/lib/ializer/symbol_de_ser.rb +15 -0
- data/lib/ializer/time_de_ser.rb +17 -0
- data/lib/ializer/version.rb +5 -0
- data/lib/ializer.rb +42 -0
- data/lib/ser/ializer/field.rb +45 -0
- data/lib/ser/ializer.rb +162 -0
- metadata +200 -0
data/Rakefile
ADDED
data/bin/console
ADDED
data/bin/setup
ADDED
data/ializer.gemspec
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
lib = File.expand_path('lib', __dir__)
|
4
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
5
|
+
require 'ializer/version'
|
6
|
+
|
7
|
+
Gem::Specification.new do |spec|
|
8
|
+
spec.name = 'ializer'
|
9
|
+
spec.version = Ializer::VERSION
|
10
|
+
spec.authors = ['Jeremy Steinberg']
|
11
|
+
|
12
|
+
spec.summary = 'Simple (de)serializers for Ruby objects'
|
13
|
+
spec.homepage = 'https://github.com/jsteinberg/ializer'
|
14
|
+
spec.license = 'MIT'
|
15
|
+
|
16
|
+
spec.files = `git ls-files -z`.split("\x0").reject do |f|
|
17
|
+
f.match(%r{^(test|spec|features)/})
|
18
|
+
end
|
19
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
20
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
21
|
+
spec.require_paths = ['lib']
|
22
|
+
|
23
|
+
spec.add_dependency 'activesupport'
|
24
|
+
spec.add_dependency 'multi_json', '~> 1.0'
|
25
|
+
|
26
|
+
spec.add_development_dependency 'bundler'
|
27
|
+
spec.add_development_dependency 'pry'
|
28
|
+
spec.add_development_dependency 'rake'
|
29
|
+
spec.add_development_dependency 'rspec'
|
30
|
+
spec.add_development_dependency 'rubocop'
|
31
|
+
spec.add_development_dependency 'rubocop-rspec'
|
32
|
+
spec.add_development_dependency 'simplecov'
|
33
|
+
end
|
@@ -0,0 +1,87 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module De
|
4
|
+
module Ser
|
5
|
+
class Ializer < ::Ser::Ializer
|
6
|
+
class << self
|
7
|
+
def parse(data, model_class)
|
8
|
+
return parse_many(data, model_class) if data.is_a? Array
|
9
|
+
|
10
|
+
parse_one(data, model_class)
|
11
|
+
end
|
12
|
+
|
13
|
+
def parse_many(data, model_class)
|
14
|
+
data.map { |obj_data| parse_one(obj_data, model_class) }
|
15
|
+
end
|
16
|
+
|
17
|
+
def parse_one(data, model_class)
|
18
|
+
# OpenStruct lazily defines methods so respond_to? fails initially
|
19
|
+
return parse_ostruct(data) if ostruct?(model_class)
|
20
|
+
|
21
|
+
parse_object(data, model_class.new)
|
22
|
+
end
|
23
|
+
|
24
|
+
def parse_object(data, object)
|
25
|
+
data.each do |key, value|
|
26
|
+
parse_attribute(object, key, value)
|
27
|
+
end
|
28
|
+
|
29
|
+
object
|
30
|
+
end
|
31
|
+
|
32
|
+
def parse_json(json, model_class)
|
33
|
+
data = MultiJson.load(json)
|
34
|
+
parse(data, model_class)
|
35
|
+
end
|
36
|
+
|
37
|
+
def parse_attribute(object, key, value)
|
38
|
+
field = _attributes[key]
|
39
|
+
|
40
|
+
return unless field
|
41
|
+
|
42
|
+
return unless object.respond_to?(field.setter)
|
43
|
+
|
44
|
+
parse_field(object, field, value)
|
45
|
+
end
|
46
|
+
|
47
|
+
protected
|
48
|
+
|
49
|
+
def create_anon_class
|
50
|
+
Class.new(De::Ser::Ializer)
|
51
|
+
end
|
52
|
+
|
53
|
+
private
|
54
|
+
|
55
|
+
def parse_field(object, field, value)
|
56
|
+
parsed_value = field.parse(value)
|
57
|
+
|
58
|
+
return if parsed_value.nil?
|
59
|
+
|
60
|
+
object.public_send(field.setter, parsed_value)
|
61
|
+
end
|
62
|
+
|
63
|
+
def ostruct?(model_class)
|
64
|
+
defined?(OpenStruct) && model_class <= OpenStruct
|
65
|
+
end
|
66
|
+
|
67
|
+
def parse_ostruct(data)
|
68
|
+
object = OpenStruct.new
|
69
|
+
|
70
|
+
data.each do |key, value|
|
71
|
+
parse_ostruct_field(object, key, value)
|
72
|
+
end
|
73
|
+
|
74
|
+
object
|
75
|
+
end
|
76
|
+
|
77
|
+
def parse_ostruct_field(object, key, value)
|
78
|
+
field = _attributes[key]
|
79
|
+
|
80
|
+
return unless field
|
81
|
+
|
82
|
+
parse_field(object, field, value)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'bigdecimal'
|
4
|
+
|
5
|
+
module Ializer
|
6
|
+
class BigDecimalDeSer
|
7
|
+
def self.serialize(value, _context = nil)
|
8
|
+
value.to_s('F')
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.parse(value)
|
12
|
+
BigDecimal(value)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
Ser::Ializer.register('decimal', Ializer::BigDecimalDeSer, :BigDecimal, :decimal)
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Ializer
|
4
|
+
class BooleanDeSer
|
5
|
+
def self.serialize(value, _context = nil)
|
6
|
+
value
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.parse(value)
|
10
|
+
return value if value.is_a? TrueClass
|
11
|
+
return value if value.is_a? FalseClass
|
12
|
+
|
13
|
+
value.to_s == 'true'
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
Ser::Ializer.register('boolean', Ializer::BooleanDeSer, :Boolean, :boolean)
|
@@ -0,0 +1,47 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Ializer
|
4
|
+
class Config
|
5
|
+
def initialize
|
6
|
+
@warn_on_default = true
|
7
|
+
end
|
8
|
+
|
9
|
+
##
|
10
|
+
# :key_transform=: key_transform
|
11
|
+
#
|
12
|
+
# symbol of string transform to call on field keys
|
13
|
+
# default is +:dasherize+.
|
14
|
+
def key_transform=(key_transform)
|
15
|
+
self.key_transformer = key_transform&.to_proc
|
16
|
+
end
|
17
|
+
|
18
|
+
##
|
19
|
+
# :attr_accessor: key_transformer
|
20
|
+
#
|
21
|
+
# object that responds_to :call with arity 1
|
22
|
+
# the Field name is passed into the key_transformer
|
23
|
+
# A key_transformer has higher precedence than key_transform
|
24
|
+
# default is +nil+.
|
25
|
+
attr_accessor :key_transformer
|
26
|
+
|
27
|
+
##
|
28
|
+
# :attr_accessor: warn_on_default
|
29
|
+
#
|
30
|
+
# The DefaultDeSer when converting to JSON will only work properly for standard
|
31
|
+
# JSON value types(:string, :number, :boolean)
|
32
|
+
# A warning message will be logged if the DefaultDeSer has been used
|
33
|
+
# default is +true+.
|
34
|
+
attr_accessor :warn_on_default
|
35
|
+
alias warn_on_default? warn_on_default
|
36
|
+
|
37
|
+
##
|
38
|
+
# :attr_accessor: raise_on_default
|
39
|
+
#
|
40
|
+
# The DefaultDeSer when converting to JSON will only work properly for standard
|
41
|
+
# JSON value types(:string, :number, :boolean)
|
42
|
+
# An error will be raised if the DefaultDeSer has been used
|
43
|
+
# default is +nil+.
|
44
|
+
attr_accessor :raise_on_default
|
45
|
+
alias raise_on_default? raise_on_default
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'date'
|
4
|
+
|
5
|
+
module Ializer
|
6
|
+
class DateDeSer
|
7
|
+
def self.serialize(value, _context = nil)
|
8
|
+
value.to_s
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.parse(value)
|
12
|
+
Date.parse(value)
|
13
|
+
rescue ArgumentError
|
14
|
+
nil
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
Ser::Ializer.register('date', Ializer::DateDeSer, Date, :date)
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Ializer
|
4
|
+
class FixNumDeSer
|
5
|
+
def self.serialize(value, _context = nil)
|
6
|
+
value
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.parse(value)
|
10
|
+
return value if value.is_a? Numeric
|
11
|
+
|
12
|
+
value.to_i
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
Ser::Ializer.register('integer', Ializer::FixNumDeSer, Integer, :integer)
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Ializer
|
4
|
+
class FloatDeSer
|
5
|
+
NAN_STRING = Float::NAN.to_s
|
6
|
+
INFINITY_STRING = Float::INFINITY.to_s
|
7
|
+
NEGATIVE_INFINITY_STRING = (-Float::INFINITY).to_s
|
8
|
+
|
9
|
+
def self.serialize(value, _context = nil)
|
10
|
+
value = value.to_f unless value.is_a? Float
|
11
|
+
|
12
|
+
return NAN_STRING if value.nan?
|
13
|
+
|
14
|
+
return value.to_s if value.infinite?
|
15
|
+
|
16
|
+
value
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.parse(value)
|
20
|
+
return Float::NAN if value == NAN_STRING
|
21
|
+
|
22
|
+
return -Float::INFINITY if value == NEGATIVE_INFINITY_STRING
|
23
|
+
|
24
|
+
return Float::INFINITY if value == INFINITY_STRING
|
25
|
+
|
26
|
+
value.to_f
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
Ser::Ializer.register('float', Ializer::FloatDeSer, Float, :float)
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'time'
|
4
|
+
|
5
|
+
module Ializer
|
6
|
+
class MillisDeSer
|
7
|
+
def self.serialize(value, _context = nil)
|
8
|
+
(value.to_f * 1000).to_i
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.parse(value)
|
12
|
+
Time.at(value / 1000.0)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
Ser::Ializer.register('millis', Ializer::MillisDeSer, :Millis, :millis)
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Ializer
|
4
|
+
class StringDeSer
|
5
|
+
def self.serialize(value, _context = nil)
|
6
|
+
value.to_s
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.parse(value)
|
10
|
+
value.to_s
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
Ser::Ializer.register('string', Ializer::StringDeSer, String, :string)
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Ializer
|
4
|
+
class SymbolDeSer
|
5
|
+
def self.serialize(value, _context = nil)
|
6
|
+
value.to_s
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.parse(value)
|
10
|
+
value.to_sym
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
Ser::Ializer.register('symbol', Ializer::SymbolDeSer, Symbol, :symbol, :sym)
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'active_support/time'
|
4
|
+
|
5
|
+
module Ializer
|
6
|
+
class TimeDeSer
|
7
|
+
def self.serialize(value, _context = nil)
|
8
|
+
value.to_time.iso8601(3) # to_time to force consistent serialization
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.parse(value)
|
12
|
+
DateTime.iso8601 value
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
Ser::Ializer.register('timestamp', Ializer::TimeDeSer, Time, DateTime, :timestamp)
|
data/lib/ializer.rb
ADDED
@@ -0,0 +1,42 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'ializer/version'
|
4
|
+
|
5
|
+
require 'ser/ializer/field'
|
6
|
+
require 'ser/ializer'
|
7
|
+
require 'de/ser/ializer'
|
8
|
+
|
9
|
+
require 'ializer/big_decimal_de_ser'
|
10
|
+
require 'ializer/boolean_de_ser'
|
11
|
+
require 'ializer/date_de_ser'
|
12
|
+
require 'ializer/default_de_ser'
|
13
|
+
require 'ializer/fix_num_de_ser'
|
14
|
+
require 'ializer/float_de_ser'
|
15
|
+
require 'ializer/millis_de_ser'
|
16
|
+
require 'ializer/string_de_ser'
|
17
|
+
require 'ializer/symbol_de_ser'
|
18
|
+
require 'ializer/time_de_ser'
|
19
|
+
|
20
|
+
require 'ializer/config'
|
21
|
+
|
22
|
+
require 'multi_json'
|
23
|
+
|
24
|
+
module Ializer
|
25
|
+
# Returns the global configuration instance
|
26
|
+
def self.config
|
27
|
+
@config ||= Ializer::Config.new
|
28
|
+
end
|
29
|
+
|
30
|
+
# Initialization block is passed a global Config instance that can be
|
31
|
+
# used to configure Ializer behavior. E.g., if you want to
|
32
|
+
# disable automation DefaultDeSer warnings put the following in
|
33
|
+
# an initializer: config/initializers/ializer.rb
|
34
|
+
#
|
35
|
+
# Ializer.setup do |config|
|
36
|
+
# config.warn_on_default = false
|
37
|
+
# end
|
38
|
+
#
|
39
|
+
def self.setup
|
40
|
+
yield config
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Ser
|
4
|
+
class Ializer
|
5
|
+
class Field
|
6
|
+
class << self
|
7
|
+
def transform(key)
|
8
|
+
return key unless ::Ializer.config.key_transformer
|
9
|
+
|
10
|
+
::Ializer.config.key_transformer.call(key)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
attr_reader :name, :setter, :key, :deser, :model_class, :if_condition, :block
|
15
|
+
|
16
|
+
def initialize(name, options, &block)
|
17
|
+
@name = name
|
18
|
+
@setter = options[:setter] || "#{name}="
|
19
|
+
@key = options[:key] || Field.transform(name.to_s)
|
20
|
+
@deser = options[:deser]
|
21
|
+
@if_condition = options[:if]
|
22
|
+
@model_class = options[:model_class]
|
23
|
+
@block = block
|
24
|
+
end
|
25
|
+
|
26
|
+
def serialize(value, context)
|
27
|
+
deser.serialize(value, context)
|
28
|
+
end
|
29
|
+
|
30
|
+
def parse(value)
|
31
|
+
if model_class
|
32
|
+
deser.parse(value, model_class)
|
33
|
+
else
|
34
|
+
deser.parse(value)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def valid_for_context?(object, context)
|
39
|
+
return true if if_condition.nil?
|
40
|
+
|
41
|
+
if_condition.call(object, context)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
data/lib/ser/ializer.rb
ADDED
@@ -0,0 +1,162 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Ser
|
4
|
+
class Ializer # rubocop:disable Metrics/ClassLength
|
5
|
+
@@method_registry = {} # rubocop:disable Style/ClassVars
|
6
|
+
|
7
|
+
class << self
|
8
|
+
# Public DSL
|
9
|
+
def property(name, options = {}, &block)
|
10
|
+
return add_attribute(Field.new(name, options, &block)) if options[:deser]
|
11
|
+
|
12
|
+
return default(name, options, &block) unless options[:type]
|
13
|
+
|
14
|
+
meth = lookup_method(options[:type])
|
15
|
+
|
16
|
+
return meth.call(name, options, &block) if meth
|
17
|
+
|
18
|
+
default(name, options, &block)
|
19
|
+
end
|
20
|
+
|
21
|
+
def nested(name, options = {}, &block)
|
22
|
+
if block
|
23
|
+
deser = create_anon_class
|
24
|
+
deser.class_eval(&block)
|
25
|
+
options[:deser] = deser
|
26
|
+
end
|
27
|
+
|
28
|
+
add_attribute(Field.new(name, options))
|
29
|
+
end
|
30
|
+
|
31
|
+
def with(deser)
|
32
|
+
deser._attributes.values.map(&:dup).each(&method(:add_attribute))
|
33
|
+
end
|
34
|
+
# End Public DSL
|
35
|
+
|
36
|
+
def serialize(object, context = nil)
|
37
|
+
return serialize_one(object, context) unless valid_enumerable?(object)
|
38
|
+
|
39
|
+
object.map { |o| serialize_one(o, context) }
|
40
|
+
end
|
41
|
+
|
42
|
+
def serialize_json(object, context = nil)
|
43
|
+
MultiJson.dump(serialize(object, context))
|
44
|
+
end
|
45
|
+
|
46
|
+
def register(method_name, deser, *matchers)
|
47
|
+
raise ArgumentError, 'register should only be called on the Ser::Ializer class' unless self == Ser::Ializer
|
48
|
+
|
49
|
+
define_singleton_method(method_name) do |name, options = {}, &block|
|
50
|
+
options[:deser] = deser
|
51
|
+
add_attribute Field.new(name, options, &block)
|
52
|
+
end
|
53
|
+
|
54
|
+
matchers.each do |matcher|
|
55
|
+
method_registry[matcher.to_s.to_sym] = method_name
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def register_default(deser)
|
60
|
+
define_singleton_method('default') do |name, options = {}, &block|
|
61
|
+
raise ArgumentError, warning_message(name) if ::Ializer.config.raise_on_default?
|
62
|
+
|
63
|
+
puts warning_message(name) if ::Ializer.config.warn_on_default?
|
64
|
+
|
65
|
+
options[:deser] = deser
|
66
|
+
add_attribute Field.new(name, options, &block)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def attribute_names
|
71
|
+
_attributes.values.map(&:name)
|
72
|
+
end
|
73
|
+
|
74
|
+
protected
|
75
|
+
|
76
|
+
def create_anon_class
|
77
|
+
Class.new(Ser::Ializer)
|
78
|
+
end
|
79
|
+
|
80
|
+
def method_registry
|
81
|
+
@@method_registry
|
82
|
+
end
|
83
|
+
|
84
|
+
def lookup_method(type)
|
85
|
+
method_name = method_registry[type.to_s.to_sym]
|
86
|
+
|
87
|
+
return nil unless method_name
|
88
|
+
|
89
|
+
method(method_name)
|
90
|
+
end
|
91
|
+
|
92
|
+
def _attributes
|
93
|
+
@attributes ||=
|
94
|
+
if equal? Ser::Ializer
|
95
|
+
{}
|
96
|
+
else
|
97
|
+
superclass._attributes.dup
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
private
|
102
|
+
|
103
|
+
def warning_message(name)
|
104
|
+
"Warning: #{self} using default DeSer for property #{name}"
|
105
|
+
end
|
106
|
+
|
107
|
+
def add_attribute(field)
|
108
|
+
_attributes[field.key] = field
|
109
|
+
|
110
|
+
if field.block
|
111
|
+
add_attribute_with_block(field)
|
112
|
+
else
|
113
|
+
add_attribute_with_method(field)
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
def add_attribute_with_block(field)
|
118
|
+
define_singleton_method field.name do |object, context|
|
119
|
+
value = field.block.call(object, context)
|
120
|
+
|
121
|
+
serialize_field(field, value, context)
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
def add_attribute_with_method(field)
|
126
|
+
define_singleton_method field.name do |object, context|
|
127
|
+
value = object.public_send(field.name)
|
128
|
+
|
129
|
+
serialize_field(field, value, context)
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
def serialize_field(field, value, context)
|
134
|
+
return nil if value.nil?
|
135
|
+
|
136
|
+
return field.serialize(value, context) unless valid_enumerable?(value)
|
137
|
+
|
138
|
+
value.map { |v| field.serialize(v, context) }
|
139
|
+
end
|
140
|
+
|
141
|
+
def serialize_one(object, context)
|
142
|
+
_attributes.values.each_with_object({}) do |field, data|
|
143
|
+
next unless field.valid_for_context?(object, context)
|
144
|
+
|
145
|
+
value = public_send(field.name, object, context)
|
146
|
+
|
147
|
+
next if value.nil?
|
148
|
+
|
149
|
+
data[field.key] = value
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
def valid_enumerable?(object)
|
154
|
+
return true if object.is_a? Array
|
155
|
+
|
156
|
+
return true if defined?(ActiveRecord) && object.is_a?(ActiveRecord::Relation)
|
157
|
+
|
158
|
+
false
|
159
|
+
end
|
160
|
+
end
|
161
|
+
end
|
162
|
+
end
|