active_model_serializers 0.10.0.rc2 → 0.10.0.rc3

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 (104) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +2 -0
  3. data/.rubocop.yml +82 -0
  4. data/.rubocop_todo.yml +315 -0
  5. data/.simplecov +99 -0
  6. data/.travis.yml +8 -0
  7. data/CHANGELOG.md +9 -3
  8. data/Gemfile +39 -8
  9. data/README.md +55 -31
  10. data/Rakefile +29 -2
  11. data/active_model_serializers.gemspec +37 -13
  12. data/appveyor.yml +25 -0
  13. data/docs/README.md +29 -0
  14. data/docs/general/adapters.md +110 -0
  15. data/docs/general/configuration_options.md +11 -0
  16. data/docs/general/getting_started.md +73 -0
  17. data/docs/howto/add_pagination_links.md +112 -0
  18. data/docs/howto/add_root_key.md +51 -0
  19. data/docs/howto/outside_controller_use.md +42 -0
  20. data/lib/action_controller/serialization.rb +24 -33
  21. data/lib/active_model/serializable_resource.rb +70 -0
  22. data/lib/active_model/serializer.rb +50 -131
  23. data/lib/active_model/serializer/adapter.rb +84 -21
  24. data/lib/active_model/serializer/adapter/flatten_json.rb +9 -9
  25. data/lib/active_model/serializer/adapter/fragment_cache.rb +10 -13
  26. data/lib/active_model/serializer/adapter/json.rb +25 -28
  27. data/lib/active_model/serializer/adapter/json/fragment_cache.rb +2 -12
  28. data/lib/active_model/serializer/adapter/json_api.rb +100 -98
  29. data/lib/active_model/serializer/adapter/json_api/fragment_cache.rb +4 -14
  30. data/lib/active_model/serializer/adapter/json_api/pagination_links.rb +50 -0
  31. data/lib/active_model/serializer/adapter/null.rb +2 -8
  32. data/lib/active_model/serializer/array_serializer.rb +22 -17
  33. data/lib/active_model/serializer/association.rb +20 -0
  34. data/lib/active_model/serializer/associations.rb +97 -0
  35. data/lib/active_model/serializer/belongs_to_reflection.rb +10 -0
  36. data/lib/active_model/serializer/collection_reflection.rb +7 -0
  37. data/lib/active_model/serializer/configuration.rb +1 -0
  38. data/lib/active_model/serializer/fieldset.rb +7 -7
  39. data/lib/active_model/serializer/has_many_reflection.rb +10 -0
  40. data/lib/active_model/serializer/has_one_reflection.rb +10 -0
  41. data/lib/active_model/serializer/lint.rb +129 -0
  42. data/lib/active_model/serializer/railtie.rb +7 -0
  43. data/lib/active_model/serializer/reflection.rb +74 -0
  44. data/lib/active_model/serializer/singular_reflection.rb +7 -0
  45. data/lib/active_model/serializer/utils.rb +35 -0
  46. data/lib/active_model/serializer/version.rb +1 -1
  47. data/lib/active_model_serializers.rb +28 -14
  48. data/lib/generators/serializer/serializer_generator.rb +7 -7
  49. data/lib/generators/serializer/templates/{serializer.rb → serializer.rb.erb} +2 -2
  50. data/lib/tasks/rubocop.rake +0 -0
  51. data/test/action_controller/adapter_selector_test.rb +3 -3
  52. data/test/action_controller/explicit_serializer_test.rb +9 -9
  53. data/test/action_controller/json_api/linked_test.rb +179 -0
  54. data/test/action_controller/json_api/pagination_test.rb +116 -0
  55. data/test/action_controller/serialization_scope_name_test.rb +10 -6
  56. data/test/action_controller/serialization_test.rb +149 -112
  57. data/test/active_record_test.rb +9 -0
  58. data/test/adapter/fragment_cache_test.rb +11 -1
  59. data/test/adapter/json/belongs_to_test.rb +4 -5
  60. data/test/adapter/json/collection_test.rb +30 -21
  61. data/test/adapter/json/has_many_test.rb +20 -9
  62. data/test/adapter/json_api/belongs_to_test.rb +38 -38
  63. data/test/adapter/json_api/collection_test.rb +22 -23
  64. data/test/adapter/json_api/has_many_embed_ids_test.rb +2 -2
  65. data/test/adapter/json_api/has_many_explicit_serializer_test.rb +4 -4
  66. data/test/adapter/json_api/has_many_test.rb +54 -19
  67. data/test/adapter/json_api/has_one_test.rb +28 -8
  68. data/test/adapter/json_api/json_api_test.rb +37 -0
  69. data/test/adapter/json_api/linked_test.rb +75 -75
  70. data/test/adapter/json_api/pagination_links_test.rb +115 -0
  71. data/test/adapter/json_api/resource_type_config_test.rb +59 -0
  72. data/test/adapter/json_test.rb +18 -5
  73. data/test/adapter_test.rb +10 -11
  74. data/test/array_serializer_test.rb +63 -5
  75. data/test/capture_warnings.rb +65 -0
  76. data/test/fixtures/active_record.rb +56 -0
  77. data/test/fixtures/poro.rb +60 -29
  78. data/test/generators/scaffold_controller_generator_test.rb +1 -2
  79. data/test/generators/serializer_generator_test.rb +17 -12
  80. data/test/lint_test.rb +37 -0
  81. data/test/logger_test.rb +18 -0
  82. data/test/poro_test.rb +9 -0
  83. data/test/serializable_resource_test.rb +27 -0
  84. data/test/serializers/adapter_for_test.rb +123 -3
  85. data/test/serializers/association_macros_test.rb +36 -0
  86. data/test/serializers/associations_test.rb +70 -47
  87. data/test/serializers/attribute_test.rb +28 -4
  88. data/test/serializers/attributes_test.rb +8 -14
  89. data/test/serializers/cache_test.rb +58 -31
  90. data/test/serializers/fieldset_test.rb +3 -4
  91. data/test/serializers/meta_test.rb +42 -28
  92. data/test/serializers/root_test.rb +21 -0
  93. data/test/serializers/serializer_for_test.rb +1 -1
  94. data/test/support/rails_app.rb +21 -0
  95. data/test/support/serialization_testing.rb +13 -0
  96. data/test/support/simplecov.rb +6 -0
  97. data/test/support/stream_capture.rb +50 -0
  98. data/test/support/test_case.rb +5 -0
  99. data/test/test_helper.rb +41 -29
  100. data/test/utils/include_args_to_hash_test.rb +79 -0
  101. metadata +123 -17
  102. data/test/action_controller/json_api_linked_test.rb +0 -179
  103. data/test/action_controller/rescue_from_test.rb +0 -32
  104. data/test/serializers/urls_test.rb +0 -26
@@ -1,12 +1,12 @@
1
- module ActiveModel
2
- class Serializer
3
- class Adapter
4
- class FlattenJson < Json
1
+ class ActiveModel::Serializer::Adapter::FlattenJson < ActiveModel::Serializer::Adapter::Json
5
2
  def serializable_hash(options = {})
6
- super
7
- @result
3
+ super.each_value.first
4
+ end
5
+
6
+ private
7
+
8
+ # no-op: FlattenJson adapter does not include meta data, because it does not support root.
9
+ def include_meta(json)
10
+ json
8
11
  end
9
- end
10
- end
11
- end
12
12
  end
@@ -1,8 +1,4 @@
1
- module ActiveModel
2
- class Serializer
3
- class Adapter
4
- class FragmentCache
5
-
1
+ class ActiveModel::Serializer::Adapter::FragmentCache
6
2
  attr_reader :serializer
7
3
 
8
4
  def initialize(adapter, serializer, options)
@@ -35,7 +31,7 @@ module ActiveModel
35
31
 
36
32
  def cached_attributes(klass, serializers)
37
33
  attributes = serializer.class._attributes
38
- cached_attributes = (klass._cache_only) ? klass._cache_only : attributes.reject {|attr| klass._cache_except.include?(attr) }
34
+ cached_attributes = (klass._cache_only) ? klass._cache_only : attributes.reject { |attr| klass._cache_except.include?(attr) }
39
35
  non_cached_attributes = attributes - cached_attributes
40
36
 
41
37
  cached_attributes.each do |attribute|
@@ -54,13 +50,13 @@ module ActiveModel
54
50
  end
55
51
 
56
52
  def fragment_serializer(name, klass)
57
- cached = "#{name.capitalize}CachedSerializer"
58
- non_cached = "#{name.capitalize}NonCachedSerializer"
53
+ cached = "#{to_valid_const_name(name)}CachedSerializer"
54
+ non_cached = "#{to_valid_const_name(name)}NonCachedSerializer"
59
55
 
60
56
  Object.const_set cached, Class.new(ActiveModel::Serializer) unless Object.const_defined?(cached)
61
57
  Object.const_set non_cached, Class.new(ActiveModel::Serializer) unless Object.const_defined?(non_cached)
62
58
 
63
- klass._cache_options ||= {}
59
+ klass._cache_options ||= {}
64
60
  klass._cache_options[:key] = klass._cache_key if klass._cache_key
65
61
 
66
62
  cached.constantize.cache(klass._cache_options)
@@ -68,11 +64,12 @@ module ActiveModel
68
64
  cached.constantize.fragmented(serializer)
69
65
  non_cached.constantize.fragmented(serializer)
70
66
 
71
- serializers = {cached: cached, non_cached: non_cached}
67
+ serializers = { cached: cached, non_cached: non_cached }
72
68
  cached_attributes(klass, serializers)
73
69
  serializers
74
70
  end
75
- end
76
- end
77
- end
71
+
72
+ def to_valid_const_name(name)
73
+ name.gsub('::', '_')
74
+ end
78
75
  end
@@ -1,50 +1,47 @@
1
- require 'active_model/serializer/adapter/json/fragment_cache'
1
+ class ActiveModel::Serializer::Adapter::Json < ActiveModel::Serializer::Adapter
2
+ extend ActiveSupport::Autoload
3
+ autoload :FragmentCache
2
4
 
3
- module ActiveModel
4
- class Serializer
5
- class Adapter
6
- class Json < Adapter
7
- def serializable_hash(options = {})
5
+ def serializable_hash(options = nil)
6
+ options ||= {}
8
7
  if serializer.respond_to?(:each)
9
- @result = serializer.map{|s| FlattenJson.new(s).serializable_hash }
8
+ result = serializer.map { |s| FlattenJson.new(s).serializable_hash(options) }
10
9
  else
11
- @hash = {}
10
+ hash = {}
12
11
 
13
- @core = cache_check(serializer) do
12
+ core = cache_check(serializer) do
14
13
  serializer.attributes(options)
15
14
  end
16
15
 
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|
16
+ serializer.associations.each do |association|
17
+ serializer = association.serializer
18
+ opts = association.options
19
+
20
+ if serializer.respond_to?(:each)
21
+ array_serializer = serializer
22
+ hash[association.key] = array_serializer.map do |item|
21
23
  cache_check(item) do
22
24
  item.attributes(opts)
23
25
  end
24
26
  end
25
27
  else
26
- if association && association.object
27
- @hash[name] = cache_check(association) do
28
- association.attributes(options)
28
+ hash[association.key] =
29
+ if serializer && serializer.object
30
+ cache_check(serializer) do
31
+ serializer.attributes(options)
32
+ end
33
+ elsif opts[:virtual_value]
34
+ opts[:virtual_value]
29
35
  end
30
- elsif opts[:virtual_value]
31
- @hash[name] = opts[:virtual_value]
32
- else
33
- @hash[name] = nil
34
- end
35
36
  end
36
37
  end
37
- @result = @core.merge @hash
38
+ result = core.merge hash
38
39
  end
39
40
 
40
- { root => @result }
41
+ { root => result }
41
42
  end
42
43
 
43
44
  def fragment_cache(cached_hash, non_cached_hash)
44
- Json::FragmentCache.new().fragment_cache(cached_hash, non_cached_hash)
45
+ ActiveModel::Serializer::Adapter::Json::FragmentCache.new().fragment_cache(cached_hash, non_cached_hash)
45
46
  end
46
-
47
- end
48
- end
49
- end
50
47
  end
@@ -1,15 +1,5 @@
1
- module ActiveModel
2
- class Serializer
3
- class Adapter
4
- class Json < Adapter
5
- class FragmentCache
6
-
1
+ class ActiveModel::Serializer::Adapter::Json::FragmentCache
7
2
  def fragment_cache(cached_hash, non_cached_hash)
8
3
  non_cached_hash.merge cached_hash
9
4
  end
10
-
11
- end
12
- end
13
- end
14
- end
15
- end
5
+ end
@@ -1,156 +1,158 @@
1
- require 'active_model/serializer/adapter/json_api/fragment_cache'
1
+ class ActiveModel::Serializer::Adapter::JsonApi < ActiveModel::Serializer::Adapter
2
+ extend ActiveSupport::Autoload
3
+ autoload :PaginationLinks
4
+ autoload :FragmentCache
2
5
 
3
- module ActiveModel
4
- class Serializer
5
- class Adapter
6
- class JsonApi < Adapter
7
6
  def initialize(serializer, options = {})
8
7
  super
9
- @hash = { data: [] }
10
-
11
- if fields = options.delete(:fields)
8
+ @included = ActiveModel::Serializer::Utils.include_args_to_hash(@options[:include])
9
+ fields = options.delete(:fields)
10
+ if fields
12
11
  @fieldset = ActiveModel::Serializer::Fieldset.new(fields, serializer.json_key)
13
12
  else
14
13
  @fieldset = options[:fieldset]
15
14
  end
16
15
  end
17
16
 
18
- def serializable_hash(options = {})
17
+ def serializable_hash(options = nil)
18
+ options ||= {}
19
19
  if serializer.respond_to?(:each)
20
- serializer.each do |s|
21
- result = self.class.new(s, @options.merge(fieldset: @fieldset)).serializable_hash
22
- @hash[:data] << result[:data]
23
-
24
- if result[:included]
25
- @hash[:included] ||= []
26
- @hash[:included] |= result[:included]
27
- end
28
- end
20
+ serializable_hash_for_collection(serializer, options)
29
21
  else
30
- @hash[:data] = attributes_for_serializer(serializer, @options)
31
- add_resource_relationships(@hash[:data], serializer)
22
+ serializable_hash_for_single_resource(serializer, options)
32
23
  end
33
- @hash
34
24
  end
35
25
 
36
26
  def fragment_cache(cached_hash, non_cached_hash)
37
27
  root = false if @options.include?(:include)
38
- JsonApi::FragmentCache.new().fragment_cache(root, cached_hash, non_cached_hash)
28
+ ActiveModel::Serializer::Adapter::JsonApi::FragmentCache.new.fragment_cache(root, cached_hash, non_cached_hash)
39
29
  end
40
30
 
41
31
  private
42
32
 
43
- def add_relationships(resource, name, serializers)
44
- resource[:relationships] ||= {}
45
- resource[:relationships][name] ||= { data: [] }
46
- resource[:relationships][name][:data] += serializers.map { |serializer| { type: serializer.type, id: serializer.id.to_s } }
47
- end
48
-
49
- def add_relationship(resource, name, serializer, val=nil)
50
- resource[:relationships] ||= {}
51
- resource[:relationships][name] = { data: nil }
33
+ def serializable_hash_for_collection(serializer, options)
34
+ hash = { data: [] }
35
+ serializer.each do |s|
36
+ result = self.class.new(s, @options.merge(fieldset: @fieldset)).serializable_hash(options)
37
+ hash[:data] << result[:data]
52
38
 
53
- if serializer && serializer.object
54
- resource[:relationships][name][:data] = { type: serializer.type, id: serializer.id.to_s }
39
+ if result[:included]
40
+ hash[:included] ||= []
41
+ hash[:included] |= result[:included]
42
+ end
55
43
  end
56
- end
57
44
 
58
- def add_included(resource_name, serializers, parent = nil)
59
- unless serializers.respond_to?(:each)
60
- return unless serializers.object
61
- serializers = Array(serializers)
45
+ if serializer.paginated?
46
+ hash[:links] ||= {}
47
+ hash[:links].update(links_for(serializer, options))
62
48
  end
63
- resource_path = [parent, resource_name].compact.join('.')
64
- if include_assoc?(resource_path)
65
- @hash[:included] ||= []
66
49
 
67
- serializers.each do |serializer|
68
- attrs = attributes_for_serializer(serializer, @options)
50
+ hash
51
+ end
69
52
 
70
- add_resource_relationships(attrs, serializer, add_included: false)
53
+ def serializable_hash_for_single_resource(serializer, options)
54
+ primary_data = primary_data_for(serializer, options)
55
+ relationships = relationships_for(serializer)
56
+ included = included_for(serializer)
57
+ hash = { data: primary_data }
58
+ hash[:data][:relationships] = relationships if relationships.any?
59
+ hash[:included] = included if included.any?
71
60
 
72
- @hash[:included].push(attrs) unless @hash[:included].include?(attrs)
73
- end
74
- end
61
+ hash
62
+ end
75
63
 
76
- serializers.each do |serializer|
77
- serializer.each_association do |name, association, opts|
78
- add_included(name, association, resource_path) if association
79
- end if include_nested_assoc? resource_path
64
+ def resource_identifier_type_for(serializer)
65
+ if ActiveModel::Serializer.config.jsonapi_resource_type == :singular
66
+ serializer.object.class.model_name.singular
67
+ else
68
+ serializer.object.class.model_name.plural
80
69
  end
81
70
  end
82
71
 
83
- def attributes_for_serializer(serializer, options)
84
- if serializer.respond_to?(:each)
85
- result = []
86
- serializer.each do |object|
87
- result << resource_object_for(object, options)
88
- end
72
+ def resource_identifier_id_for(serializer)
73
+ if serializer.respond_to?(:id)
74
+ serializer.id
89
75
  else
90
- result = resource_object_for(serializer, options)
76
+ serializer.object.id
91
77
  end
92
- result
93
78
  end
94
79
 
95
- def resource_object_for(serializer, options)
96
- options[:fields] = @fieldset && @fieldset.fields_for(serializer)
97
- options[:required_fields] = [:id, :type]
80
+ def resource_identifier_for(serializer)
81
+ type = resource_identifier_type_for(serializer)
82
+ id = resource_identifier_id_for(serializer)
98
83
 
99
- cache_check(serializer) do
100
- attributes = serializer.attributes(options)
84
+ { id: id.to_s, type: type }
85
+ end
101
86
 
102
- result = {
103
- id: attributes.delete(:id).to_s,
104
- type: attributes.delete(:type)
105
- }
87
+ def resource_object_for(serializer, options = {})
88
+ options[:fields] = @fieldset && @fieldset.fields_for(serializer)
106
89
 
90
+ cache_check(serializer) do
91
+ result = resource_identifier_for(serializer)
92
+ attributes = serializer.attributes(options).except(:id)
107
93
  result[:attributes] = attributes if attributes.any?
108
94
  result
109
95
  end
110
96
  end
111
97
 
112
- def include_assoc?(assoc)
113
- return false unless @options[:include]
114
- check_assoc("#{assoc}$")
98
+ def primary_data_for(serializer, options)
99
+ if serializer.respond_to?(:each)
100
+ serializer.map { |s| resource_object_for(s, options) }
101
+ else
102
+ resource_object_for(serializer, options)
103
+ end
104
+ end
105
+
106
+ def relationship_value_for(serializer, options = {})
107
+ if serializer.respond_to?(:each)
108
+ serializer.map { |s| resource_identifier_for(s) }
109
+ else
110
+ if options[:virtual_value]
111
+ options[:virtual_value]
112
+ elsif serializer && serializer.object
113
+ resource_identifier_for(serializer)
114
+ end
115
+ end
115
116
  end
116
117
 
117
- def include_nested_assoc?(assoc)
118
- return false unless @options[:include]
119
- check_assoc("#{assoc}.")
118
+ def relationships_for(serializer)
119
+ Hash[serializer.associations.map { |association| [association.key, { data: relationship_value_for(association.serializer, association.options) }] }]
120
120
  end
121
121
 
122
- def check_assoc(assoc)
123
- include_opt = @options[:include]
124
- include_opt = include_opt.split(',') if include_opt.is_a?(String)
125
- include_opt.any? do |s|
126
- s.match(/^#{assoc.gsub('.', '\.')}/)
122
+ def included_for(serializer)
123
+ included = @included.flat_map do |inc|
124
+ association = serializer.associations.find { |assoc| assoc.key == inc.first }
125
+ _included_for(association.serializer, inc.second) if association
127
126
  end
127
+
128
+ included.uniq
128
129
  end
129
130
 
130
- def add_resource_relationships(attrs, serializer, options = {})
131
- options[:add_included] = options.fetch(:add_included, true)
131
+ def _included_for(serializer, includes)
132
+ if serializer.respond_to?(:each)
133
+ serializer.flat_map { |s| _included_for(s, includes) }.uniq
134
+ else
135
+ return [] unless serializer && serializer.object
132
136
 
133
- serializer.each_association do |name, association, opts|
134
- attrs[:relationships] ||= {}
137
+ primary_data = primary_data_for(serializer, @options)
138
+ relationships = relationships_for(serializer)
139
+ primary_data[:relationships] = relationships if relationships.any?
135
140
 
136
- if association.respond_to?(:each)
137
- add_relationships(attrs, name, association)
138
- else
139
- if opts[:virtual_value]
140
- add_relationship(attrs, name, nil, opts[:virtual_value])
141
- else
142
- add_relationship(attrs, name, association)
143
- end
144
- end
141
+ included = [primary_data]
145
142
 
146
- if options[:add_included]
147
- Array(association).each do |association|
148
- add_included(name, association)
143
+ includes.each do |inc|
144
+ association = serializer.associations.find { |assoc| assoc.key == inc.first }
145
+ if association
146
+ included.concat(_included_for(association.serializer, inc.second))
147
+ included.uniq!
149
148
  end
150
149
  end
150
+
151
+ included
151
152
  end
152
153
  end
153
- end
154
- end
155
- end
154
+
155
+ def links_for(serializer, options)
156
+ JsonApi::PaginationLinks.new(serializer.object, options[:context]).serializable_hash(options)
157
+ end
156
158
  end
@@ -1,23 +1,13 @@
1
- module ActiveModel
2
- class Serializer
3
- class Adapter
4
- class JsonApi < Adapter
5
- class FragmentCache
6
-
1
+ class ActiveModel::Serializer::Adapter::JsonApi::FragmentCache
7
2
  def fragment_cache(root, cached_hash, non_cached_hash)
8
3
  hash = {}
9
4
  core_cached = cached_hash.first
10
5
  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] }
6
+ no_root_cache = cached_hash.delete_if { |key, value| key == core_cached[0] }
7
+ no_root_non_cache = non_cached_hash.delete_if { |key, value| key == core_non_cached[0] }
13
8
  cached_resource = (core_cached[1]) ? core_cached[1].deep_merge(core_non_cached[1]) : core_non_cached[1]
14
9
  hash = (root) ? { root => cached_resource } : cached_resource
15
10
 
16
11
  hash.deep_merge no_root_non_cache.deep_merge no_root_cache
17
12
  end
18
-
19
- end
20
- end
21
- end
22
- end
23
- end
13
+ end