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