cm-active_model_serializers 0.10.0.rc1.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.
Files changed (62) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +21 -0
  3. data/.travis.yml +26 -0
  4. data/CHANGELOG.md +8 -0
  5. data/CONTRIBUTING.md +31 -0
  6. data/Gemfile +17 -0
  7. data/LICENSE.txt +22 -0
  8. data/README.md +326 -0
  9. data/Rakefile +12 -0
  10. data/cm-active_model_serializers.gemspec +26 -0
  11. data/lib/action_controller/serialization.rb +62 -0
  12. data/lib/active_model/serializer.rb +261 -0
  13. data/lib/active_model/serializer/adapter.rb +87 -0
  14. data/lib/active_model/serializer/adapter/fragment_cache.rb +78 -0
  15. data/lib/active_model/serializer/adapter/json.rb +52 -0
  16. data/lib/active_model/serializer/adapter/json/fragment_cache.rb +15 -0
  17. data/lib/active_model/serializer/adapter/json_api.rb +152 -0
  18. data/lib/active_model/serializer/adapter/json_api/fragment_cache.rb +22 -0
  19. data/lib/active_model/serializer/adapter/null.rb +11 -0
  20. data/lib/active_model/serializer/array_serializer.rb +32 -0
  21. data/lib/active_model/serializer/configuration.rb +13 -0
  22. data/lib/active_model/serializer/fieldset.rb +40 -0
  23. data/lib/active_model/serializer/version.rb +5 -0
  24. data/lib/active_model_serializers.rb +18 -0
  25. data/lib/generators/serializer/USAGE +6 -0
  26. data/lib/generators/serializer/serializer_generator.rb +37 -0
  27. data/lib/generators/serializer/templates/serializer.rb +8 -0
  28. data/test/action_controller/adapter_selector_test.rb +51 -0
  29. data/test/action_controller/explicit_serializer_test.rb +110 -0
  30. data/test/action_controller/json_api_linked_test.rb +173 -0
  31. data/test/action_controller/serialization_scope_name_test.rb +63 -0
  32. data/test/action_controller/serialization_test.rb +365 -0
  33. data/test/adapter/fragment_cache_test.rb +27 -0
  34. data/test/adapter/json/belongs_to_test.rb +48 -0
  35. data/test/adapter/json/collection_test.rb +73 -0
  36. data/test/adapter/json/has_many_test.rb +36 -0
  37. data/test/adapter/json_api/belongs_to_test.rb +147 -0
  38. data/test/adapter/json_api/collection_test.rb +89 -0
  39. data/test/adapter/json_api/has_many_embed_ids_test.rb +45 -0
  40. data/test/adapter/json_api/has_many_explicit_serializer_test.rb +98 -0
  41. data/test/adapter/json_api/has_many_test.rb +106 -0
  42. data/test/adapter/json_api/has_one_test.rb +59 -0
  43. data/test/adapter/json_api/linked_test.rb +257 -0
  44. data/test/adapter/json_test.rb +34 -0
  45. data/test/adapter/null_test.rb +25 -0
  46. data/test/adapter_test.rb +43 -0
  47. data/test/array_serializer_test.rb +43 -0
  48. data/test/fixtures/poro.rb +213 -0
  49. data/test/serializers/adapter_for_test.rb +50 -0
  50. data/test/serializers/associations_test.rb +127 -0
  51. data/test/serializers/attribute_test.rb +38 -0
  52. data/test/serializers/attributes_test.rb +63 -0
  53. data/test/serializers/cache_test.rb +128 -0
  54. data/test/serializers/configuration_test.rb +15 -0
  55. data/test/serializers/fieldset_test.rb +26 -0
  56. data/test/serializers/generators_test.rb +59 -0
  57. data/test/serializers/meta_test.rb +78 -0
  58. data/test/serializers/options_test.rb +21 -0
  59. data/test/serializers/serializer_for_test.rb +65 -0
  60. data/test/serializers/urls_test.rb +26 -0
  61. data/test/test_helper.rb +38 -0
  62. metadata +195 -0
@@ -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 && association.object
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,15 @@
1
+ module ActiveModel
2
+ class Serializer
3
+ class Adapter
4
+ class Json < Adapter
5
+ class FragmentCache
6
+
7
+ def fragment_cache(cached_hash, non_cached_hash)
8
+ non_cached_hash.merge cached_hash
9
+ end
10
+
11
+ end
12
+ end
13
+ end
14
+ end
15
+ 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,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,11 @@
1
+ module ActiveModel
2
+ class Serializer
3
+ class Adapter
4
+ class Null < Adapter
5
+ def serializable_hash(options = {})
6
+ {}
7
+ end
8
+ end
9
+ end
10
+ end
11
+ 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.except(:serializer))
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
@@ -0,0 +1,5 @@
1
+ module ActiveModel
2
+ class Serializer
3
+ VERSION = "0.10.0.rc1.1"
4
+ end
5
+ end
@@ -0,0 +1,18 @@
1
+ require 'active_model'
2
+ require 'active_model/serializer/version'
3
+ require 'active_model/serializer'
4
+ require 'active_model/serializer/fieldset'
5
+
6
+ begin
7
+ require 'action_controller'
8
+ require 'action_controller/serialization'
9
+
10
+ ActiveSupport.on_load(:action_controller) do
11
+ include ::ActionController::Serialization
12
+ ActionDispatch::Reloader.to_prepare do
13
+ ActiveModel::Serializer.serializers_cache.clear
14
+ end
15
+ end
16
+ rescue LoadError
17
+ # rails not installed, continuing
18
+ end