joyful_jsonapi 0.0.1
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/LICENSE.txt +201 -0
- data/README.md +603 -0
- data/lib/extensions/has_one.rb +18 -0
- data/lib/generators/serializer/USAGE +8 -0
- data/lib/generators/serializer/serializer_generator.rb +19 -0
- data/lib/generators/serializer/templates/serializer.rb.tt +6 -0
- data/lib/joyful_jsonapi.rb +11 -0
- data/lib/joyful_jsonapi/attribute.rb +29 -0
- data/lib/joyful_jsonapi/error_serializer.rb +41 -0
- data/lib/joyful_jsonapi/instrumentation.rb +2 -0
- data/lib/joyful_jsonapi/instrumentation/serializable_hash.rb +15 -0
- data/lib/joyful_jsonapi/instrumentation/serialized_json.rb +15 -0
- data/lib/joyful_jsonapi/instrumentation/skylight.rb +2 -0
- data/lib/joyful_jsonapi/instrumentation/skylight/normalizers/base.rb +7 -0
- data/lib/joyful_jsonapi/instrumentation/skylight/normalizers/serializable_hash.rb +22 -0
- data/lib/joyful_jsonapi/instrumentation/skylight/normalizers/serialized_json.rb +22 -0
- data/lib/joyful_jsonapi/link.rb +18 -0
- data/lib/joyful_jsonapi/multi_to_json.rb +100 -0
- data/lib/joyful_jsonapi/object_serializer.rb +314 -0
- data/lib/joyful_jsonapi/railtie.rb +11 -0
- data/lib/joyful_jsonapi/relationship.rb +120 -0
- data/lib/joyful_jsonapi/serialization_core.rb +156 -0
- data/lib/joyful_jsonapi/version.rb +3 -0
- metadata +254 -0
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
::ActiveRecord::Associations::Builder::HasOne.class_eval do
|
4
|
+
# Based on
|
5
|
+
# https://github.com/rails/rails/blob/master/activerecord/lib/active_record/associations/builder/collection_association.rb#L50
|
6
|
+
# https://github.com/rails/rails/blob/master/activerecord/lib/active_record/associations/builder/singular_association.rb#L11
|
7
|
+
def self.define_accessors(mixin, reflection)
|
8
|
+
super
|
9
|
+
name = reflection.name
|
10
|
+
mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1
|
11
|
+
def #{name}_id
|
12
|
+
# if an attribute is already defined with this methods name we should just use it
|
13
|
+
return read_attribute(__method__) if has_attribute?(__method__)
|
14
|
+
association(:#{name}).reader.try(:id)
|
15
|
+
end
|
16
|
+
CODE
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'rails/generators/base'
|
4
|
+
|
5
|
+
class SerializerGenerator < Rails::Generators::NamedBase
|
6
|
+
source_root File.expand_path('templates', __dir__)
|
7
|
+
|
8
|
+
argument :attributes, type: :array, default: [], banner: 'field field'
|
9
|
+
|
10
|
+
def create_serializer_file
|
11
|
+
template 'serializer.rb.tt', File.join('app', 'serializers', class_path, "#{file_name}_serializer.rb")
|
12
|
+
end
|
13
|
+
|
14
|
+
private
|
15
|
+
|
16
|
+
def attributes_names
|
17
|
+
attributes.map { |a| a.name.to_sym.inspect }
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module JoyfulJsonapi
|
4
|
+
require 'joyful_jsonapi/object_serializer'
|
5
|
+
require 'joyful_jsonapi/error_serializer'
|
6
|
+
if defined?(::Rails)
|
7
|
+
require 'joyful_jsonapi/railtie'
|
8
|
+
elsif defined?(::ActiveRecord)
|
9
|
+
require 'extensions/has_one'
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module JoyfulJsonapi
|
2
|
+
class Attribute
|
3
|
+
attr_reader :key, :method, :conditional_proc
|
4
|
+
|
5
|
+
def initialize(key:, method:, options: {})
|
6
|
+
@key = key
|
7
|
+
@method = method
|
8
|
+
@conditional_proc = options[:if]
|
9
|
+
end
|
10
|
+
|
11
|
+
def serialize(record, serialization_params, output_hash)
|
12
|
+
if include_attribute?(record, serialization_params)
|
13
|
+
output_hash[key] = if method.is_a?(Proc)
|
14
|
+
method.arity.abs == 1 ? method.call(record) : method.call(record, serialization_params)
|
15
|
+
else
|
16
|
+
record.public_send(method)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def include_attribute?(record, serialization_params)
|
22
|
+
if conditional_proc.present?
|
23
|
+
conditional_proc.call(record, serialization_params)
|
24
|
+
else
|
25
|
+
true
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
|
2
|
+
module JoyfulJsonapi
|
3
|
+
class ErrorSerializer
|
4
|
+
|
5
|
+
def initialize(model)
|
6
|
+
@model = model
|
7
|
+
@model.valid?
|
8
|
+
end
|
9
|
+
|
10
|
+
def serializable_hash
|
11
|
+
{ errors: errors_for(@model) }
|
12
|
+
end
|
13
|
+
|
14
|
+
def serialized_json(options = nil)
|
15
|
+
serializable_hash.to_json(options)
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def errors_for(resource)
|
21
|
+
resource.errors.messages.flat_map do |field, errors|
|
22
|
+
build_hashes_for(field, errors)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def build_hashes_for(field, errors)
|
27
|
+
errors.map do |error_message|
|
28
|
+
build_hash_for(field, error_message)
|
29
|
+
end.flatten
|
30
|
+
end
|
31
|
+
|
32
|
+
def build_hash_for(field, error_message)
|
33
|
+
{}.tap do |hash|
|
34
|
+
hash[:status] = "422"
|
35
|
+
hash[:source] = { pointer: "/data/attributes/#{field}" }
|
36
|
+
hash[:detail] = error_message
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'active_support/notifications'
|
2
|
+
|
3
|
+
module JoyfulJsonapi
|
4
|
+
module ObjectSerializer
|
5
|
+
|
6
|
+
alias_method :serializable_hash_without_instrumentation, :serializable_hash
|
7
|
+
|
8
|
+
def serializable_hash
|
9
|
+
ActiveSupport::Notifications.instrument(SERIALIZABLE_HASH_NOTIFICATION, { name: self.class.name }) do
|
10
|
+
serializable_hash_without_instrumentation
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'active_support/notifications'
|
2
|
+
|
3
|
+
module JoyfulJsonapi
|
4
|
+
module ObjectSerializer
|
5
|
+
|
6
|
+
alias_method :serialized_json_without_instrumentation, :serialized_json
|
7
|
+
|
8
|
+
def serialized_json
|
9
|
+
ActiveSupport::Notifications.instrument(SERIALIZED_JSON_NOTIFICATION, { name: self.class.name }) do
|
10
|
+
serialized_json_without_instrumentation
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'joyful_jsonapi/instrumentation/skylight/normalizers/base'
|
2
|
+
require 'joyful_jsonapi/instrumentation/serializable_hash'
|
3
|
+
|
4
|
+
module JoyfulJsonapi
|
5
|
+
module Instrumentation
|
6
|
+
module Skylight
|
7
|
+
module Normalizers
|
8
|
+
class SerializableHash < SKYLIGHT_NORMALIZER_BASE_CLASS
|
9
|
+
|
10
|
+
register JoyfulJsonapi::ObjectSerializer::SERIALIZABLE_HASH_NOTIFICATION
|
11
|
+
|
12
|
+
CAT = "view.#{JoyfulJsonapi::ObjectSerializer::SERIALIZABLE_HASH_NOTIFICATION}".freeze
|
13
|
+
|
14
|
+
def normalize(trace, name, payload)
|
15
|
+
[ CAT, payload[:name], nil ]
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'joyful_jsonapi/instrumentation/skylight/normalizers/base'
|
2
|
+
require 'joyful_jsonapi/instrumentation/serializable_hash'
|
3
|
+
|
4
|
+
module JoyfulJsonapi
|
5
|
+
module Instrumentation
|
6
|
+
module Skylight
|
7
|
+
module Normalizers
|
8
|
+
class SerializedJson < SKYLIGHT_NORMALIZER_BASE_CLASS
|
9
|
+
|
10
|
+
register JoyfulJsonapi::ObjectSerializer::SERIALIZED_JSON_NOTIFICATION
|
11
|
+
|
12
|
+
CAT = "view.#{JoyfulJsonapi::ObjectSerializer::SERIALIZED_JSON_NOTIFICATION}".freeze
|
13
|
+
|
14
|
+
def normalize(trace, name, payload)
|
15
|
+
[ CAT, payload[:name], nil ]
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module JoyfulJsonapi
|
2
|
+
class Link
|
3
|
+
attr_reader :key, :method
|
4
|
+
|
5
|
+
def initialize(key:, method:)
|
6
|
+
@key = key
|
7
|
+
@method = method
|
8
|
+
end
|
9
|
+
|
10
|
+
def serialize(record, serialization_params, output_hash)
|
11
|
+
output_hash[key] = if method.is_a?(Proc)
|
12
|
+
method.arity == 1 ? method.call(record) : method.call(record, serialization_params)
|
13
|
+
else
|
14
|
+
record.public_send(method)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,100 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'logger'
|
4
|
+
|
5
|
+
# Usage:
|
6
|
+
# class Movie
|
7
|
+
# def to_json(payload)
|
8
|
+
# JoyfulJsonapi::MultiToJson.to_json(payload)
|
9
|
+
# end
|
10
|
+
# end
|
11
|
+
module JoyfulJsonapi
|
12
|
+
module MultiToJson
|
13
|
+
# Result object pattern is from https://johnnunemaker.com/resilience-in-ruby/
|
14
|
+
# e.g. https://github.com/github/github-ds/blob/fbda5389711edfb4c10b6c6bad19311dfcb1bac1/lib/github/result.rb
|
15
|
+
class Result
|
16
|
+
def initialize(*rescued_exceptions)
|
17
|
+
@rescued_exceptions = if rescued_exceptions.empty?
|
18
|
+
[StandardError]
|
19
|
+
else
|
20
|
+
rescued_exceptions
|
21
|
+
end
|
22
|
+
|
23
|
+
@value = yield
|
24
|
+
@error = nil
|
25
|
+
rescue *rescued_exceptions => e
|
26
|
+
@error = e
|
27
|
+
end
|
28
|
+
|
29
|
+
def ok?
|
30
|
+
@error.nil?
|
31
|
+
end
|
32
|
+
|
33
|
+
def value!
|
34
|
+
if ok?
|
35
|
+
@value
|
36
|
+
else
|
37
|
+
raise @error
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def rescue
|
42
|
+
return self if ok?
|
43
|
+
|
44
|
+
Result.new(*@rescued_exceptions) { yield(@error) }
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def self.logger(device=nil)
|
49
|
+
return @logger = Logger.new(device) if device
|
50
|
+
@logger ||= Logger.new(IO::NULL)
|
51
|
+
end
|
52
|
+
|
53
|
+
# Encoder-compatible with default MultiJSON adapters and defaults
|
54
|
+
def self.to_json_method
|
55
|
+
encode_method = String.new(%(def _fast_to_json(object)\n ))
|
56
|
+
encode_method << Result.new(LoadError) {
|
57
|
+
require 'oj'
|
58
|
+
%(::Oj.dump(object, mode: :compat, time_format: :ruby, use_to_json: true))
|
59
|
+
}.rescue {
|
60
|
+
require 'yajl'
|
61
|
+
%(::Yajl::Encoder.encode(object))
|
62
|
+
}.rescue {
|
63
|
+
require 'jrjackson' unless defined?(::JrJackson)
|
64
|
+
%(::JrJackson::Json.dump(object))
|
65
|
+
}.rescue {
|
66
|
+
require 'json'
|
67
|
+
%(JSON.fast_generate(object, create_additions: false, quirks_mode: true))
|
68
|
+
}.rescue {
|
69
|
+
require 'gson'
|
70
|
+
%(::Gson::Encoder.new({}).encode(object))
|
71
|
+
}.rescue {
|
72
|
+
require 'active_support/json/encoding'
|
73
|
+
%(::ActiveSupport::JSON.encode(object))
|
74
|
+
}.rescue {
|
75
|
+
warn "No JSON encoder found. Falling back to `object.to_json`"
|
76
|
+
%(object.to_json)
|
77
|
+
}.value!
|
78
|
+
encode_method << "\nend"
|
79
|
+
end
|
80
|
+
|
81
|
+
def self.to_json(object)
|
82
|
+
_fast_to_json(object)
|
83
|
+
rescue NameError
|
84
|
+
define_to_json(JoyfulJsonapi::MultiToJson)
|
85
|
+
_fast_to_json(object)
|
86
|
+
end
|
87
|
+
|
88
|
+
def self.define_to_json(receiver)
|
89
|
+
cl = caller_locations[0]
|
90
|
+
method_body = to_json_method
|
91
|
+
logger.debug { "Defining #{receiver}._fast_to_json as #{method_body.inspect}" }
|
92
|
+
receiver.instance_eval method_body, cl.absolute_path, cl.lineno
|
93
|
+
end
|
94
|
+
|
95
|
+
def self.reset_to_json!
|
96
|
+
undef :_fast_to_json if method_defined?(:_fast_to_json)
|
97
|
+
logger.debug { "Undefining #{receiver}._fast_to_json" }
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
@@ -0,0 +1,314 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'active_support/time'
|
4
|
+
require 'active_support/json'
|
5
|
+
require 'active_support/concern'
|
6
|
+
require 'active_support/inflector'
|
7
|
+
require 'active_support/core_ext/numeric/time'
|
8
|
+
require 'joyful_jsonapi/attribute'
|
9
|
+
require 'joyful_jsonapi/relationship'
|
10
|
+
require 'joyful_jsonapi/link'
|
11
|
+
require 'joyful_jsonapi/serialization_core'
|
12
|
+
|
13
|
+
module JoyfulJsonapi
|
14
|
+
module ObjectSerializer
|
15
|
+
extend ActiveSupport::Concern
|
16
|
+
include SerializationCore
|
17
|
+
|
18
|
+
SERIALIZABLE_HASH_NOTIFICATION = 'render.joyful_jsonapi.serializable_hash'
|
19
|
+
SERIALIZED_JSON_NOTIFICATION = 'render.joyful_jsonapi.serialized_json'
|
20
|
+
|
21
|
+
included do
|
22
|
+
# Set record_type based on the name of the serializer class
|
23
|
+
set_type(reflected_record_type) if reflected_record_type
|
24
|
+
end
|
25
|
+
|
26
|
+
def initialize(resource, options = {})
|
27
|
+
process_options(options)
|
28
|
+
|
29
|
+
@resource = resource
|
30
|
+
end
|
31
|
+
|
32
|
+
def serializable_hash
|
33
|
+
return hash_for_collection if is_collection?(@resource, @is_collection)
|
34
|
+
|
35
|
+
hash_for_one_record
|
36
|
+
end
|
37
|
+
alias_method :to_hash, :serializable_hash
|
38
|
+
|
39
|
+
def hash_for_one_record
|
40
|
+
serializable_hash = { data: nil }
|
41
|
+
serializable_hash[:meta] = @meta if @meta.present?
|
42
|
+
serializable_hash[:links] = @links if @links.present?
|
43
|
+
|
44
|
+
return serializable_hash unless @resource
|
45
|
+
|
46
|
+
serializable_hash[:data] = self.class.record_hash(@resource, @fieldsets[self.class.record_type.to_sym], @params)
|
47
|
+
serializable_hash[:included] = self.class.get_included_records(@resource, @includes, @known_included_objects, @fieldsets, @params) if @includes.present?
|
48
|
+
serializable_hash
|
49
|
+
end
|
50
|
+
|
51
|
+
def hash_for_collection
|
52
|
+
serializable_hash = {}
|
53
|
+
|
54
|
+
data = []
|
55
|
+
included = []
|
56
|
+
fieldset = @fieldsets[self.class.record_type.to_sym]
|
57
|
+
@resource.each do |record|
|
58
|
+
data << self.class.record_hash(record, fieldset, @params)
|
59
|
+
included.concat self.class.get_included_records(record, @includes, @known_included_objects, @fieldsets, @params) if @includes.present?
|
60
|
+
end
|
61
|
+
|
62
|
+
serializable_hash[:data] = data
|
63
|
+
serializable_hash[:included] = included if @includes.present?
|
64
|
+
serializable_hash[:meta] = @meta if @meta.present?
|
65
|
+
serializable_hash[:links] = @links if @links.present?
|
66
|
+
serializable_hash
|
67
|
+
end
|
68
|
+
|
69
|
+
def serialized_json
|
70
|
+
ActiveSupport::JSON.encode(serializable_hash)
|
71
|
+
end
|
72
|
+
|
73
|
+
private
|
74
|
+
|
75
|
+
def process_options(options)
|
76
|
+
@fieldsets = deep_symbolize(options[:fields].presence || {})
|
77
|
+
@params = {}
|
78
|
+
|
79
|
+
return if options.blank?
|
80
|
+
|
81
|
+
@known_included_objects = {}
|
82
|
+
@meta = options[:meta]
|
83
|
+
@links = options[:links]
|
84
|
+
@is_collection = options[:is_collection]
|
85
|
+
@params = options[:params] || {}
|
86
|
+
raise ArgumentError.new("`params` option passed to serializer must be a hash") unless @params.is_a?(Hash)
|
87
|
+
|
88
|
+
if options[:include].present?
|
89
|
+
@includes = options[:include].delete_if(&:blank?).map(&:to_sym)
|
90
|
+
self.class.validate_includes!(@includes)
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
def deep_symbolize(collection)
|
95
|
+
if collection.is_a? Hash
|
96
|
+
Hash[collection.map do |k, v|
|
97
|
+
[k.to_sym, deep_symbolize(v)]
|
98
|
+
end]
|
99
|
+
elsif collection.is_a? Array
|
100
|
+
collection.map { |i| deep_symbolize(i) }
|
101
|
+
else
|
102
|
+
collection.to_sym
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
def is_collection?(resource, force_is_collection = nil)
|
107
|
+
return force_is_collection unless force_is_collection.nil?
|
108
|
+
|
109
|
+
resource.respond_to?(:size) && !resource.respond_to?(:each_pair)
|
110
|
+
end
|
111
|
+
|
112
|
+
class_methods do
|
113
|
+
|
114
|
+
def inherited(subclass)
|
115
|
+
super(subclass)
|
116
|
+
subclass.attributes_to_serialize = attributes_to_serialize.dup if attributes_to_serialize.present?
|
117
|
+
subclass.relationships_to_serialize = relationships_to_serialize.dup if relationships_to_serialize.present?
|
118
|
+
subclass.cachable_relationships_to_serialize = cachable_relationships_to_serialize.dup if cachable_relationships_to_serialize.present?
|
119
|
+
subclass.uncachable_relationships_to_serialize = uncachable_relationships_to_serialize.dup if uncachable_relationships_to_serialize.present?
|
120
|
+
subclass.transform_method = transform_method
|
121
|
+
subclass.cache_length = cache_length
|
122
|
+
subclass.race_condition_ttl = race_condition_ttl
|
123
|
+
subclass.data_links = data_links.dup if data_links.present?
|
124
|
+
subclass.cached = cached
|
125
|
+
subclass.set_type(subclass.reflected_record_type) if subclass.reflected_record_type
|
126
|
+
subclass.meta_to_serialize = meta_to_serialize
|
127
|
+
end
|
128
|
+
|
129
|
+
def reflected_record_type
|
130
|
+
return @reflected_record_type if defined?(@reflected_record_type)
|
131
|
+
|
132
|
+
@reflected_record_type ||= begin
|
133
|
+
if self.name.end_with?('Serializer')
|
134
|
+
self.name.split('::').last.chomp('Serializer').underscore.to_sym
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
def set_key_transform(transform_name)
|
140
|
+
mapping = {
|
141
|
+
camel: :camelize,
|
142
|
+
camel_lower: [:camelize, :lower],
|
143
|
+
dash: :dasherize,
|
144
|
+
underscore: :underscore
|
145
|
+
}
|
146
|
+
self.transform_method = mapping[transform_name.to_sym]
|
147
|
+
|
148
|
+
# ensure that the record type is correctly transformed
|
149
|
+
if record_type
|
150
|
+
set_type(record_type)
|
151
|
+
elsif reflected_record_type
|
152
|
+
set_type(reflected_record_type)
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
def run_key_transform(input)
|
157
|
+
if self.transform_method.present?
|
158
|
+
input.to_s.send(*@transform_method).to_sym
|
159
|
+
else
|
160
|
+
input.to_sym
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
def use_hyphen
|
165
|
+
warn('DEPRECATION WARNING: use_hyphen is deprecated and will be removed from joyful_jsonapi 2.0 use (set_key_transform :dash) instead')
|
166
|
+
set_key_transform :dash
|
167
|
+
end
|
168
|
+
|
169
|
+
def set_type(type_name)
|
170
|
+
self.record_type = run_key_transform(type_name)
|
171
|
+
end
|
172
|
+
|
173
|
+
def set_id(id_name = nil, &block)
|
174
|
+
self.record_id = block || id_name
|
175
|
+
end
|
176
|
+
|
177
|
+
def cache_options(cache_options)
|
178
|
+
self.cached = cache_options[:enabled] || false
|
179
|
+
self.cache_length = cache_options[:cache_length] || 5.minutes
|
180
|
+
self.race_condition_ttl = cache_options[:race_condition_ttl] || 5.seconds
|
181
|
+
end
|
182
|
+
|
183
|
+
def attributes(*attributes_list, &block)
|
184
|
+
attributes_list = attributes_list.first if attributes_list.first.class.is_a?(Array)
|
185
|
+
options = attributes_list.last.is_a?(Hash) ? attributes_list.pop : {}
|
186
|
+
self.attributes_to_serialize = {} if self.attributes_to_serialize.nil?
|
187
|
+
|
188
|
+
attributes_list.each do |attr_name|
|
189
|
+
method_name = attr_name
|
190
|
+
key = run_key_transform(method_name)
|
191
|
+
attributes_to_serialize[key] = Attribute.new(
|
192
|
+
key: key,
|
193
|
+
method: block || method_name,
|
194
|
+
options: options
|
195
|
+
)
|
196
|
+
end
|
197
|
+
end
|
198
|
+
|
199
|
+
alias_method :attribute, :attributes
|
200
|
+
|
201
|
+
def add_relationship(relationship)
|
202
|
+
self.relationships_to_serialize = {} if relationships_to_serialize.nil?
|
203
|
+
self.cachable_relationships_to_serialize = {} if cachable_relationships_to_serialize.nil?
|
204
|
+
self.uncachable_relationships_to_serialize = {} if uncachable_relationships_to_serialize.nil?
|
205
|
+
|
206
|
+
if !relationship.cached
|
207
|
+
self.uncachable_relationships_to_serialize[relationship.name] = relationship
|
208
|
+
else
|
209
|
+
self.cachable_relationships_to_serialize[relationship.name] = relationship
|
210
|
+
end
|
211
|
+
self.relationships_to_serialize[relationship.name] = relationship
|
212
|
+
end
|
213
|
+
|
214
|
+
def has_many(relationship_name, options = {}, &block)
|
215
|
+
relationship = create_relationship(relationship_name, :has_many, options, block)
|
216
|
+
add_relationship(relationship)
|
217
|
+
end
|
218
|
+
|
219
|
+
def has_one(relationship_name, options = {}, &block)
|
220
|
+
relationship = create_relationship(relationship_name, :has_one, options, block)
|
221
|
+
add_relationship(relationship)
|
222
|
+
end
|
223
|
+
|
224
|
+
def belongs_to(relationship_name, options = {}, &block)
|
225
|
+
relationship = create_relationship(relationship_name, :belongs_to, options, block)
|
226
|
+
add_relationship(relationship)
|
227
|
+
end
|
228
|
+
|
229
|
+
def meta(&block)
|
230
|
+
self.meta_to_serialize = block
|
231
|
+
end
|
232
|
+
|
233
|
+
def create_relationship(base_key, relationship_type, options, block)
|
234
|
+
name = base_key.to_sym
|
235
|
+
if relationship_type == :has_many
|
236
|
+
base_serialization_key = base_key.to_s.singularize
|
237
|
+
base_key_sym = base_serialization_key.to_sym
|
238
|
+
id_postfix = '_ids'
|
239
|
+
else
|
240
|
+
base_serialization_key = base_key
|
241
|
+
base_key_sym = name
|
242
|
+
id_postfix = '_id'
|
243
|
+
end
|
244
|
+
Relationship.new(
|
245
|
+
key: options[:key] || run_key_transform(base_key),
|
246
|
+
name: name,
|
247
|
+
id_method_name: compute_id_method_name(
|
248
|
+
options[:id_method_name],
|
249
|
+
"#{base_serialization_key}#{id_postfix}".to_sym,
|
250
|
+
block
|
251
|
+
),
|
252
|
+
record_type: options[:record_type] || run_key_transform(base_key_sym),
|
253
|
+
object_method_name: options[:object_method_name] || name,
|
254
|
+
object_block: block,
|
255
|
+
serializer: compute_serializer_name(options[:serializer] || base_key_sym),
|
256
|
+
relationship_type: relationship_type,
|
257
|
+
cached: options[:cached],
|
258
|
+
polymorphic: fetch_polymorphic_option(options),
|
259
|
+
conditional_proc: options[:if],
|
260
|
+
transform_method: @transform_method,
|
261
|
+
links: options[:links],
|
262
|
+
lazy_load_data: options[:lazy_load_data]
|
263
|
+
)
|
264
|
+
end
|
265
|
+
|
266
|
+
def compute_id_method_name(custom_id_method_name, id_method_name_from_relationship, block)
|
267
|
+
if block.present?
|
268
|
+
custom_id_method_name || :id
|
269
|
+
else
|
270
|
+
custom_id_method_name || id_method_name_from_relationship
|
271
|
+
end
|
272
|
+
end
|
273
|
+
|
274
|
+
def compute_serializer_name(serializer_key)
|
275
|
+
return serializer_key unless serializer_key.is_a? Symbol
|
276
|
+
namespace = self.name.gsub(/()?\w+Serializer$/, '')
|
277
|
+
serializer_name = serializer_key.to_s.classify + 'Serializer'
|
278
|
+
(namespace + serializer_name).to_sym
|
279
|
+
end
|
280
|
+
|
281
|
+
def fetch_polymorphic_option(options)
|
282
|
+
option = options[:polymorphic]
|
283
|
+
return false unless option.present?
|
284
|
+
return option if option.respond_to? :keys
|
285
|
+
{}
|
286
|
+
end
|
287
|
+
|
288
|
+
def link(link_name, link_method_name = nil, &block)
|
289
|
+
self.data_links = {} if self.data_links.nil?
|
290
|
+
link_method_name = link_name if link_method_name.nil?
|
291
|
+
key = run_key_transform(link_name)
|
292
|
+
|
293
|
+
self.data_links[key] = Link.new(
|
294
|
+
key: key,
|
295
|
+
method: block || link_method_name
|
296
|
+
)
|
297
|
+
end
|
298
|
+
|
299
|
+
def validate_includes!(includes)
|
300
|
+
return if includes.blank?
|
301
|
+
|
302
|
+
includes.detect do |include_item|
|
303
|
+
klass = self
|
304
|
+
parse_include_item(include_item).each do |parsed_include|
|
305
|
+
relationships_to_serialize = klass.relationships_to_serialize || {}
|
306
|
+
relationship_to_include = relationships_to_serialize[parsed_include]
|
307
|
+
raise ArgumentError, "#{parsed_include} is not specified as a relationship on #{klass.name}" unless relationship_to_include
|
308
|
+
klass = relationship_to_include.serializer.to_s.constantize unless relationship_to_include.polymorphic.is_a?(Hash)
|
309
|
+
end
|
310
|
+
end
|
311
|
+
end
|
312
|
+
end
|
313
|
+
end
|
314
|
+
end
|