active_model_serializers 0.10.0.rc2 → 0.10.0.rc3

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