active_model_serializers 0.8.3 → 0.10.0.rc1
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 +4 -4
- data/.gitignore +3 -0
- data/.travis.yml +19 -20
- data/CHANGELOG.md +8 -67
- data/CONTRIBUTING.md +31 -0
- data/Gemfile +14 -1
- data/{MIT-LICENSE.txt → LICENSE.txt} +3 -2
- data/README.md +166 -495
- data/Rakefile +6 -12
- data/active_model_serializers.gemspec +21 -19
- data/lib/action_controller/serialization.rb +28 -27
- data/lib/active_model/serializer/adapter/fragment_cache.rb +78 -0
- data/lib/active_model/serializer/adapter/json/fragment_cache.rb +15 -0
- data/lib/active_model/serializer/adapter/json.rb +52 -0
- data/lib/active_model/serializer/adapter/json_api/fragment_cache.rb +22 -0
- data/lib/active_model/serializer/adapter/json_api.rb +152 -0
- data/lib/active_model/serializer/adapter/null.rb +11 -0
- data/lib/active_model/serializer/adapter.rb +87 -0
- data/lib/active_model/serializer/array_serializer.rb +32 -0
- data/lib/active_model/serializer/configuration.rb +13 -0
- data/lib/active_model/serializer/fieldset.rb +40 -0
- data/lib/active_model/{serializers → serializer}/version.rb +1 -1
- data/lib/active_model/serializer.rb +179 -436
- data/lib/active_model_serializers.rb +9 -86
- data/lib/generators/serializer/USAGE +0 -3
- data/lib/generators/serializer/serializer_generator.rb +1 -6
- data/lib/generators/serializer/templates/serializer.rb +2 -13
- data/test/action_controller/adapter_selector_test.rb +51 -0
- data/test/action_controller/explicit_serializer_test.rb +110 -0
- data/test/action_controller/json_api_linked_test.rb +173 -0
- data/test/{serialization_scope_name_test.rb → action_controller/serialization_scope_name_test.rb} +7 -11
- data/test/action_controller/serialization_test.rb +365 -0
- data/test/adapter/fragment_cache_test.rb +27 -0
- data/test/adapter/json/belongs_to_test.rb +41 -0
- data/test/adapter/json/collection_test.rb +59 -0
- data/test/adapter/json/has_many_test.rb +36 -0
- data/test/adapter/json_api/belongs_to_test.rb +147 -0
- data/test/adapter/json_api/collection_test.rb +89 -0
- data/test/adapter/json_api/has_many_embed_ids_test.rb +45 -0
- data/test/adapter/json_api/has_many_explicit_serializer_test.rb +98 -0
- data/test/adapter/json_api/has_many_test.rb +106 -0
- data/test/adapter/json_api/has_one_test.rb +59 -0
- data/test/adapter/json_api/linked_test.rb +257 -0
- data/test/adapter/json_test.rb +34 -0
- data/test/adapter/null_test.rb +25 -0
- data/test/adapter_test.rb +43 -0
- data/test/array_serializer_test.rb +21 -67
- data/test/fixtures/poro.rb +206 -0
- data/test/serializers/adapter_for_test.rb +50 -0
- data/test/serializers/associations_test.rb +106 -0
- data/test/serializers/attribute_test.rb +23 -0
- data/test/serializers/attributes_test.rb +28 -0
- data/test/serializers/cache_test.rb +128 -0
- data/test/serializers/configuration_test.rb +15 -0
- data/test/serializers/fieldset_test.rb +26 -0
- data/test/{generators_test.rb → serializers/generators_test.rb} +1 -27
- data/test/serializers/meta_test.rb +78 -0
- data/test/serializers/options_test.rb +21 -0
- data/test/serializers/serializer_for_test.rb +56 -0
- data/test/serializers/urls_test.rb +26 -0
- data/test/test_helper.rb +22 -13
- metadata +101 -42
- data/DESIGN.textile +0 -586
- data/Gemfile.edge +0 -9
- data/bench/perf.rb +0 -43
- data/cruft.md +0 -19
- data/lib/active_model/array_serializer.rb +0 -104
- data/lib/active_model/serializer/associations.rb +0 -233
- data/lib/active_record/serializer_override.rb +0 -16
- data/lib/generators/resource_override.rb +0 -13
- data/test/association_test.rb +0 -592
- data/test/caching_test.rb +0 -96
- data/test/no_serialization_scope_test.rb +0 -34
- data/test/serialization_test.rb +0 -392
- data/test/serializer_support_test.rb +0 -51
- data/test/serializer_test.rb +0 -1465
- data/test/test_fakes.rb +0 -217
data/Rakefile
CHANGED
@@ -1,18 +1,12 @@
|
|
1
|
-
#!/usr/bin/env rake
|
2
1
|
require "bundler/gem_tasks"
|
3
|
-
require "rake/testtask"
|
4
2
|
|
5
|
-
|
6
|
-
Rake::TestTask.new(:test) do |t|
|
7
|
-
t.libs << 'lib'
|
8
|
-
t.libs << 'test'
|
9
|
-
t.pattern = 'test/**/*_test.rb'
|
10
|
-
t.verbose = true
|
11
|
-
end
|
3
|
+
require 'rake/testtask'
|
12
4
|
|
13
|
-
|
14
|
-
|
15
|
-
|
5
|
+
Rake::TestTask.new do |t|
|
6
|
+
t.libs << "test"
|
7
|
+
t.test_files = FileList['test/**/*_test.rb']
|
8
|
+
t.ruby_opts = ['-r./test/test_helper.rb']
|
9
|
+
t.verbose = true
|
16
10
|
end
|
17
11
|
|
18
12
|
task :default => :test
|
@@ -1,24 +1,26 @@
|
|
1
|
-
#
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'active_model/serializer/version'
|
2
5
|
|
3
|
-
|
4
|
-
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "active_model_serializers"
|
8
|
+
spec.version = ActiveModel::Serializer::VERSION
|
9
|
+
spec.authors = ["Steve Klabnik"]
|
10
|
+
spec.email = ["steve@steveklabnik.com"]
|
11
|
+
spec.summary = %q{Conventions-based JSON generation for Rails.}
|
12
|
+
spec.description = %q{ActiveModel::Serializers allows you to generate your JSON in an object-oriented and convention-driven manner.}
|
13
|
+
spec.homepage = "https://github.com/rails-api/active_model_serializers"
|
14
|
+
spec.license = "MIT"
|
5
15
|
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
gem.summary = %q{Bringing consistency and object orientation to model serialization. Works great for client-side MVC frameworks!}
|
11
|
-
gem.homepage = "https://github.com/rails-api/active_model_serializers"
|
16
|
+
spec.files = `git ls-files -z`.split("\x0")
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ["lib"]
|
12
20
|
|
13
|
-
|
14
|
-
gem.files = `git ls-files`.split("\n")
|
15
|
-
gem.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
16
|
-
gem.name = "active_model_serializers"
|
17
|
-
gem.require_paths = ["lib"]
|
18
|
-
gem.version = ActiveModel::Serializer::VERSION
|
21
|
+
spec.add_dependency "activemodel", ">= 4.0"
|
19
22
|
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
gem.add_development_dependency "minitest"
|
23
|
+
spec.add_development_dependency "rails", ">= 4.0"
|
24
|
+
spec.add_development_dependency "bundler", "~> 1.6"
|
25
|
+
spec.add_development_dependency "rake"
|
24
26
|
end
|
@@ -1,32 +1,13 @@
|
|
1
|
+
require 'active_support/core_ext/class/attribute'
|
2
|
+
|
1
3
|
module ActionController
|
2
|
-
# Action Controller Serialization
|
3
|
-
#
|
4
|
-
# Overrides render :json to check if the given object implements +active_model_serializer+
|
5
|
-
# as a method. If so, use the returned serializer instead of calling +to_json+ on the object.
|
6
|
-
#
|
7
|
-
# This module also provides a serialization_scope method that allows you to configure the
|
8
|
-
# +serialization_scope+ of the serializer. Most apps will likely set the +serialization_scope+
|
9
|
-
# to the current user:
|
10
|
-
#
|
11
|
-
# class ApplicationController < ActionController::Base
|
12
|
-
# serialization_scope :current_user
|
13
|
-
# end
|
14
|
-
#
|
15
|
-
# If you need more complex scope rules, you can simply override the serialization_scope:
|
16
|
-
#
|
17
|
-
# class ApplicationController < ActionController::Base
|
18
|
-
# private
|
19
|
-
#
|
20
|
-
# def serialization_scope
|
21
|
-
# current_user
|
22
|
-
# end
|
23
|
-
# end
|
24
|
-
#
|
25
4
|
module Serialization
|
26
5
|
extend ActiveSupport::Concern
|
27
6
|
|
28
7
|
include ActionController::Renderers
|
29
8
|
|
9
|
+
ADAPTER_OPTION_KEYS = [:include, :fields, :root, :adapter]
|
10
|
+
|
30
11
|
included do
|
31
12
|
class_attribute :_serialization_scope
|
32
13
|
self._serialization_scope = :current_user
|
@@ -37,15 +18,35 @@ module ActionController
|
|
37
18
|
respond_to?(_serialization_scope, true)
|
38
19
|
end
|
39
20
|
|
40
|
-
def
|
21
|
+
def get_serializer(resource)
|
22
|
+
@_serializer ||= @_serializer_opts.delete(:serializer)
|
23
|
+
@_serializer ||= ActiveModel::Serializer.serializer_for(resource)
|
24
|
+
|
25
|
+
if @_serializer_opts.key?(:each_serializer)
|
26
|
+
@_serializer_opts[:serializer] = @_serializer_opts.delete(:each_serializer)
|
27
|
+
end
|
28
|
+
|
29
|
+
@_serializer
|
30
|
+
end
|
31
|
+
|
32
|
+
def use_adapter?
|
33
|
+
!(@_adapter_opts.key?(:adapter) && !@_adapter_opts[:adapter])
|
41
34
|
end
|
42
35
|
|
43
36
|
[:_render_option_json, :_render_with_renderer_json].each do |renderer_method|
|
44
37
|
define_method renderer_method do |resource, options|
|
45
|
-
|
38
|
+
@_adapter_opts, @_serializer_opts =
|
39
|
+
options.partition { |k, _| ADAPTER_OPTION_KEYS.include? k }.map { |h| Hash[h] }
|
40
|
+
|
41
|
+
if use_adapter? && (serializer = get_serializer(resource))
|
42
|
+
|
43
|
+
@_serializer_opts[:scope] ||= serialization_scope
|
44
|
+
@_serializer_opts[:scope_name] = _serialization_scope
|
46
45
|
|
47
|
-
|
48
|
-
|
46
|
+
# omg hax
|
47
|
+
object = serializer.new(resource, @_serializer_opts)
|
48
|
+
adapter = ActiveModel::Serializer::Adapter.create(object, @_adapter_opts)
|
49
|
+
super(adapter, options)
|
49
50
|
else
|
50
51
|
super(resource, options)
|
51
52
|
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
module ActiveModel
|
2
|
+
class Serializer
|
3
|
+
class Adapter
|
4
|
+
class FragmentCache
|
5
|
+
|
6
|
+
attr_reader :serializer
|
7
|
+
|
8
|
+
def initialize(adapter, serializer, options, root)
|
9
|
+
@root = root
|
10
|
+
@options = options
|
11
|
+
@adapter = adapter
|
12
|
+
@serializer = serializer
|
13
|
+
end
|
14
|
+
|
15
|
+
def fetch
|
16
|
+
klass = serializer.class
|
17
|
+
# It will split the serializer into two, one that will be cached and other wont
|
18
|
+
serializers = fragment_serializer(serializer.object.class.name, klass)
|
19
|
+
|
20
|
+
# Instanciate both serializers
|
21
|
+
cached_serializer = serializers[:cached].constantize.new(serializer.object)
|
22
|
+
non_cached_serializer = serializers[:non_cached].constantize.new(serializer.object)
|
23
|
+
|
24
|
+
cached_adapter = @adapter.class.new(cached_serializer, @options)
|
25
|
+
non_cached_adapter = @adapter.class.new(non_cached_serializer, @options)
|
26
|
+
|
27
|
+
# Get serializable hash from both
|
28
|
+
cached_hash = cached_adapter.serializable_hash
|
29
|
+
non_cached_hash = non_cached_adapter.serializable_hash
|
30
|
+
|
31
|
+
# Merge both results
|
32
|
+
@adapter.fragment_cache(cached_hash, non_cached_hash)
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
def cached_attributes(klass, serializers)
|
38
|
+
cached_attributes = (klass._cache_only) ? klass._cache_only : serializer.attributes.keys.delete_if {|attr| klass._cache_except.include?(attr) }
|
39
|
+
non_cached_attributes = serializer.attributes.keys.delete_if {|attr| cached_attributes.include?(attr) }
|
40
|
+
|
41
|
+
cached_attributes.each do |attribute|
|
42
|
+
options = serializer.class._attributes_keys[attribute]
|
43
|
+
options ||= {}
|
44
|
+
# Add cached attributes to cached Serializer
|
45
|
+
serializers[:cached].constantize.attribute(attribute, options)
|
46
|
+
end
|
47
|
+
|
48
|
+
non_cached_attributes.each do |attribute|
|
49
|
+
options = serializer.class._attributes_keys[attribute]
|
50
|
+
options ||= {}
|
51
|
+
# Add non-cached attributes to non-cached Serializer
|
52
|
+
serializers[:non_cached].constantize.attribute(attribute, options)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def fragment_serializer(name, klass)
|
57
|
+
cached = "#{name.capitalize}CachedSerializer"
|
58
|
+
non_cached = "#{name.capitalize}NonCachedSerializer"
|
59
|
+
|
60
|
+
Object.const_set cached, Class.new(ActiveModel::Serializer) unless Object.const_defined?(cached)
|
61
|
+
Object.const_set non_cached, Class.new(ActiveModel::Serializer) unless Object.const_defined?(non_cached)
|
62
|
+
|
63
|
+
klass._cache_options ||= {}
|
64
|
+
klass._cache_options[:key] = klass._cache_key if klass._cache_key
|
65
|
+
|
66
|
+
cached.constantize.cache(klass._cache_options)
|
67
|
+
|
68
|
+
cached.constantize.fragmented(serializer)
|
69
|
+
non_cached.constantize.fragmented(serializer)
|
70
|
+
|
71
|
+
serializers = {cached: cached, non_cached: non_cached}
|
72
|
+
cached_attributes(klass, serializers)
|
73
|
+
serializers
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
require 'active_model/serializer/adapter/json/fragment_cache'
|
2
|
+
|
3
|
+
module ActiveModel
|
4
|
+
class Serializer
|
5
|
+
class Adapter
|
6
|
+
class Json < Adapter
|
7
|
+
def serializable_hash(options = {})
|
8
|
+
if serializer.respond_to?(:each)
|
9
|
+
@result = serializer.map{|s| self.class.new(s).serializable_hash }
|
10
|
+
else
|
11
|
+
@hash = {}
|
12
|
+
|
13
|
+
@core = cache_check(serializer) do
|
14
|
+
serializer.attributes(options)
|
15
|
+
end
|
16
|
+
|
17
|
+
serializer.each_association do |name, association, opts|
|
18
|
+
if association.respond_to?(:each)
|
19
|
+
array_serializer = association
|
20
|
+
@hash[name] = array_serializer.map do |item|
|
21
|
+
cache_check(item) do
|
22
|
+
item.attributes(opts)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
else
|
26
|
+
if association
|
27
|
+
@hash[name] = cache_check(association) do
|
28
|
+
association.attributes(options)
|
29
|
+
end
|
30
|
+
elsif opts[:virtual_value]
|
31
|
+
@hash[name] = opts[:virtual_value]
|
32
|
+
else
|
33
|
+
@hash[name] = nil
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
@result = @core.merge @hash
|
38
|
+
end
|
39
|
+
|
40
|
+
if root = options.fetch(:root, serializer.json_key)
|
41
|
+
@result = { root => @result }
|
42
|
+
end
|
43
|
+
@result
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def fragment_cache(cached_hash, non_cached_hash)
|
48
|
+
Json::FragmentCache.new().fragment_cache(cached_hash, non_cached_hash)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module ActiveModel
|
2
|
+
class Serializer
|
3
|
+
class Adapter
|
4
|
+
class JsonApi < Adapter
|
5
|
+
class FragmentCache
|
6
|
+
|
7
|
+
def fragment_cache(root, cached_hash, non_cached_hash)
|
8
|
+
hash = {}
|
9
|
+
core_cached = cached_hash.first
|
10
|
+
core_non_cached = non_cached_hash.first
|
11
|
+
no_root_cache = cached_hash.delete_if {|key, value| key == core_cached[0] }
|
12
|
+
no_root_non_cache = non_cached_hash.delete_if {|key, value| key == core_non_cached[0] }
|
13
|
+
cached_resource = (core_cached[1]) ? core_cached[1].merge(core_non_cached[1]) : core_non_cached[1]
|
14
|
+
hash = (root) ? { root => cached_resource } : cached_resource
|
15
|
+
hash.merge no_root_non_cache.merge no_root_cache
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,152 @@
|
|
1
|
+
require 'active_model/serializer/adapter/json_api/fragment_cache'
|
2
|
+
|
3
|
+
module ActiveModel
|
4
|
+
class Serializer
|
5
|
+
class Adapter
|
6
|
+
class JsonApi < Adapter
|
7
|
+
def initialize(serializer, options = {})
|
8
|
+
super
|
9
|
+
serializer.root = true
|
10
|
+
@hash = { data: [] }
|
11
|
+
|
12
|
+
if fields = options.delete(:fields)
|
13
|
+
@fieldset = ActiveModel::Serializer::Fieldset.new(fields, serializer.json_key)
|
14
|
+
else
|
15
|
+
@fieldset = options[:fieldset]
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def serializable_hash(options = {})
|
20
|
+
if serializer.respond_to?(:each)
|
21
|
+
serializer.each do |s|
|
22
|
+
result = self.class.new(s, @options.merge(fieldset: @fieldset)).serializable_hash
|
23
|
+
@hash[:data] << result[:data]
|
24
|
+
|
25
|
+
if result[:included]
|
26
|
+
@hash[:included] ||= []
|
27
|
+
@hash[:included] |= result[:included]
|
28
|
+
end
|
29
|
+
end
|
30
|
+
else
|
31
|
+
@hash[:data] = attributes_for_serializer(serializer, @options)
|
32
|
+
add_resource_links(@hash[:data], serializer)
|
33
|
+
end
|
34
|
+
@hash
|
35
|
+
end
|
36
|
+
|
37
|
+
def fragment_cache(cached_hash, non_cached_hash)
|
38
|
+
root = false if @options.include?(:include)
|
39
|
+
JsonApi::FragmentCache.new().fragment_cache(root, cached_hash, non_cached_hash)
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
def add_links(resource, name, serializers)
|
45
|
+
resource[:links] ||= {}
|
46
|
+
resource[:links][name] ||= { linkage: [] }
|
47
|
+
resource[:links][name][:linkage] += serializers.map { |serializer| { type: serializer.type, id: serializer.id.to_s } }
|
48
|
+
end
|
49
|
+
|
50
|
+
def add_link(resource, name, serializer, val=nil)
|
51
|
+
resource[:links] ||= {}
|
52
|
+
resource[:links][name] = { linkage: nil }
|
53
|
+
|
54
|
+
if serializer && serializer.object
|
55
|
+
resource[:links][name][:linkage] = { type: serializer.type, id: serializer.id.to_s }
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def add_included(resource_name, serializers, parent = nil)
|
60
|
+
unless serializers.respond_to?(:each)
|
61
|
+
return unless serializers.object
|
62
|
+
serializers = Array(serializers)
|
63
|
+
end
|
64
|
+
resource_path = [parent, resource_name].compact.join('.')
|
65
|
+
if include_assoc?(resource_path)
|
66
|
+
@hash[:included] ||= []
|
67
|
+
|
68
|
+
serializers.each do |serializer|
|
69
|
+
attrs = attributes_for_serializer(serializer, @options)
|
70
|
+
|
71
|
+
add_resource_links(attrs, serializer, add_included: false)
|
72
|
+
|
73
|
+
@hash[:included].push(attrs) unless @hash[:included].include?(attrs)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
serializers.each do |serializer|
|
78
|
+
serializer.each_association do |name, association, opts|
|
79
|
+
add_included(name, association, resource_path) if association
|
80
|
+
end if include_nested_assoc? resource_path
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def attributes_for_serializer(serializer, options)
|
85
|
+
if serializer.respond_to?(:each)
|
86
|
+
result = []
|
87
|
+
serializer.each do |object|
|
88
|
+
options[:fields] = @fieldset && @fieldset.fields_for(serializer)
|
89
|
+
result << cache_check(object) do
|
90
|
+
options[:required_fields] = [:id, :type]
|
91
|
+
attributes = object.attributes(options)
|
92
|
+
attributes[:id] = attributes[:id].to_s
|
93
|
+
result << attributes
|
94
|
+
end
|
95
|
+
end
|
96
|
+
else
|
97
|
+
options[:fields] = @fieldset && @fieldset.fields_for(serializer)
|
98
|
+
options[:required_fields] = [:id, :type]
|
99
|
+
result = cache_check(serializer) do
|
100
|
+
result = serializer.attributes(options)
|
101
|
+
result[:id] = result[:id].to_s
|
102
|
+
result
|
103
|
+
end
|
104
|
+
end
|
105
|
+
result
|
106
|
+
end
|
107
|
+
|
108
|
+
def include_assoc?(assoc)
|
109
|
+
return false unless @options[:include]
|
110
|
+
check_assoc("#{assoc}$")
|
111
|
+
end
|
112
|
+
|
113
|
+
def include_nested_assoc?(assoc)
|
114
|
+
return false unless @options[:include]
|
115
|
+
check_assoc("#{assoc}.")
|
116
|
+
end
|
117
|
+
|
118
|
+
def check_assoc(assoc)
|
119
|
+
include_opt = @options[:include]
|
120
|
+
include_opt = include_opt.split(',') if include_opt.is_a?(String)
|
121
|
+
include_opt.any? do |s|
|
122
|
+
s.match(/^#{assoc.gsub('.', '\.')}/)
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
def add_resource_links(attrs, serializer, options = {})
|
127
|
+
options[:add_included] = options.fetch(:add_included, true)
|
128
|
+
|
129
|
+
serializer.each_association do |name, association, opts|
|
130
|
+
attrs[:links] ||= {}
|
131
|
+
|
132
|
+
if association.respond_to?(:each)
|
133
|
+
add_links(attrs, name, association)
|
134
|
+
else
|
135
|
+
if opts[:virtual_value]
|
136
|
+
add_link(attrs, name, nil, opts[:virtual_value])
|
137
|
+
else
|
138
|
+
add_link(attrs, name, association)
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
if options[:add_included]
|
143
|
+
Array(association).each do |association|
|
144
|
+
add_included(name, association)
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|
@@ -0,0 +1,87 @@
|
|
1
|
+
require 'active_model/serializer/adapter/fragment_cache'
|
2
|
+
|
3
|
+
module ActiveModel
|
4
|
+
class Serializer
|
5
|
+
class Adapter
|
6
|
+
extend ActiveSupport::Autoload
|
7
|
+
autoload :Json
|
8
|
+
autoload :Null
|
9
|
+
autoload :JsonApi
|
10
|
+
|
11
|
+
attr_reader :serializer
|
12
|
+
|
13
|
+
def initialize(serializer, options = {})
|
14
|
+
@serializer = serializer
|
15
|
+
@options = options
|
16
|
+
end
|
17
|
+
|
18
|
+
def serializable_hash(options = {})
|
19
|
+
raise NotImplementedError, 'This is an abstract method. Should be implemented at the concrete adapter.'
|
20
|
+
end
|
21
|
+
|
22
|
+
def as_json(options = {})
|
23
|
+
hash = serializable_hash(options)
|
24
|
+
include_meta(hash)
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.create(resource, options = {})
|
28
|
+
override = options.delete(:adapter)
|
29
|
+
klass = override ? adapter_class(override) : ActiveModel::Serializer.adapter
|
30
|
+
klass.new(resource, options)
|
31
|
+
end
|
32
|
+
|
33
|
+
def self.adapter_class(adapter)
|
34
|
+
"ActiveModel::Serializer::Adapter::#{adapter.to_s.classify}".safe_constantize
|
35
|
+
end
|
36
|
+
|
37
|
+
def fragment_cache(*args)
|
38
|
+
raise NotImplementedError, 'This is an abstract method. Should be implemented at the concrete adapter.'
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
|
43
|
+
def cache_check(serializer)
|
44
|
+
@cached_serializer = serializer
|
45
|
+
@klass = @cached_serializer.class
|
46
|
+
if is_cached?
|
47
|
+
@klass._cache.fetch(cache_key, @klass._cache_options) do
|
48
|
+
yield
|
49
|
+
end
|
50
|
+
elsif is_fragment_cached?
|
51
|
+
FragmentCache.new(self, @cached_serializer, @options, @root).fetch
|
52
|
+
else
|
53
|
+
yield
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def is_cached?
|
58
|
+
@klass._cache && !@klass._cache_only && !@klass._cache_except
|
59
|
+
end
|
60
|
+
|
61
|
+
def is_fragment_cached?
|
62
|
+
@klass._cache_only && !@klass._cache_except || !@klass._cache_only && @klass._cache_except
|
63
|
+
end
|
64
|
+
|
65
|
+
def cache_key
|
66
|
+
(@klass._cache_key) ? "#{@klass._cache_key}/#{@cached_serializer.object.id}-#{@cached_serializer.object.updated_at}" : @cached_serializer.object.cache_key
|
67
|
+
end
|
68
|
+
|
69
|
+
def meta
|
70
|
+
serializer.meta if serializer.respond_to?(:meta)
|
71
|
+
end
|
72
|
+
|
73
|
+
def meta_key
|
74
|
+
serializer.meta_key || "meta"
|
75
|
+
end
|
76
|
+
|
77
|
+
def root
|
78
|
+
serializer.json_key
|
79
|
+
end
|
80
|
+
|
81
|
+
def include_meta(json)
|
82
|
+
json[meta_key] = meta if meta && root
|
83
|
+
json
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module ActiveModel
|
2
|
+
class Serializer
|
3
|
+
class ArraySerializer
|
4
|
+
include Enumerable
|
5
|
+
delegate :each, to: :@objects
|
6
|
+
|
7
|
+
attr_reader :meta, :meta_key
|
8
|
+
|
9
|
+
def initialize(objects, options = {})
|
10
|
+
options.merge!(root: nil)
|
11
|
+
|
12
|
+
@objects = objects.map do |object|
|
13
|
+
serializer_class = options.fetch(
|
14
|
+
:serializer,
|
15
|
+
ActiveModel::Serializer.serializer_for(object)
|
16
|
+
)
|
17
|
+
serializer_class.new(object, options)
|
18
|
+
end
|
19
|
+
@meta = options[:meta]
|
20
|
+
@meta_key = options[:meta_key]
|
21
|
+
end
|
22
|
+
|
23
|
+
def json_key
|
24
|
+
@objects.first.json_key if @objects.first
|
25
|
+
end
|
26
|
+
|
27
|
+
def root=(root)
|
28
|
+
@objects.first.root = root if @objects.first
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
module ActiveModel
|
2
|
+
class Serializer
|
3
|
+
module Configuration
|
4
|
+
include ActiveSupport::Configurable
|
5
|
+
extend ActiveSupport::Concern
|
6
|
+
|
7
|
+
included do |base|
|
8
|
+
base.config.array_serializer = ActiveModel::Serializer::ArraySerializer
|
9
|
+
base.config.adapter = :json
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module ActiveModel
|
2
|
+
class Serializer
|
3
|
+
class Fieldset
|
4
|
+
|
5
|
+
def initialize(fields, root = nil)
|
6
|
+
@root = root
|
7
|
+
@raw_fields = fields
|
8
|
+
end
|
9
|
+
|
10
|
+
def fields
|
11
|
+
@fields ||= parsed_fields
|
12
|
+
end
|
13
|
+
|
14
|
+
def fields_for(serializer)
|
15
|
+
key = serializer.json_key || serializer.class.root_name
|
16
|
+
fields[key.to_sym]
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
attr_reader :raw_fields, :root
|
22
|
+
|
23
|
+
def parsed_fields
|
24
|
+
if raw_fields.is_a?(Hash)
|
25
|
+
raw_fields.inject({}) { |h,(k,v)| h[k.to_sym] = v.map(&:to_sym); h}
|
26
|
+
elsif raw_fields.is_a?(Array)
|
27
|
+
if root.nil?
|
28
|
+
raise ArgumentError, 'The root argument must be specified if the fileds argument is an array.'
|
29
|
+
end
|
30
|
+
hash = {}
|
31
|
+
hash[root.to_sym] = raw_fields.map(&:to_sym)
|
32
|
+
hash
|
33
|
+
else
|
34
|
+
{}
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|