active_model_serializers 0.10.0.rc1 → 0.10.0.rc2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (52) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +1 -2
  3. data/Gemfile +1 -1
  4. data/README.md +6 -3
  5. data/lib/action_controller/serialization.rb +9 -1
  6. data/lib/active_model/serializer.rb +15 -21
  7. data/lib/active_model/serializer/adapter.rb +13 -4
  8. data/lib/active_model/serializer/adapter/flatten_json.rb +12 -0
  9. data/lib/active_model/serializer/adapter/fragment_cache.rb +5 -5
  10. data/lib/active_model/serializer/adapter/json.rb +8 -10
  11. data/lib/active_model/serializer/adapter/json_api.rb +34 -30
  12. data/lib/active_model/serializer/adapter/json_api/fragment_cache.rb +3 -2
  13. data/lib/active_model/serializer/array_serializer.rb +8 -5
  14. data/lib/active_model/serializer/configuration.rb +1 -1
  15. data/lib/active_model/serializer/fieldset.rb +2 -2
  16. data/lib/active_model/serializer/railtie.rb +8 -0
  17. data/lib/active_model/serializer/version.rb +1 -1
  18. data/lib/active_model_serializers.rb +1 -0
  19. data/lib/generators/serializer/resource_override.rb +12 -0
  20. data/test/action_controller/adapter_selector_test.rb +7 -5
  21. data/test/action_controller/explicit_serializer_test.rb +33 -9
  22. data/test/action_controller/json_api_linked_test.rb +25 -19
  23. data/test/action_controller/rescue_from_test.rb +32 -0
  24. data/test/action_controller/serialization_scope_name_test.rb +2 -2
  25. data/test/action_controller/serialization_test.rb +41 -23
  26. data/test/adapter/fragment_cache_test.rb +1 -1
  27. data/test/adapter/json/belongs_to_test.rb +9 -2
  28. data/test/adapter/json/collection_test.rb +16 -2
  29. data/test/adapter/json/has_many_test.rb +1 -1
  30. data/test/adapter/json_api/belongs_to_test.rb +45 -35
  31. data/test/adapter/json_api/collection_test.rb +30 -23
  32. data/test/adapter/json_api/has_many_embed_ids_test.rb +2 -2
  33. data/test/adapter/json_api/has_many_explicit_serializer_test.rb +14 -14
  34. data/test/adapter/json_api/has_many_test.rb +22 -18
  35. data/test/adapter/json_api/has_one_test.rb +8 -6
  36. data/test/adapter/json_api/linked_test.rb +97 -71
  37. data/test/adapter/json_test.rb +1 -1
  38. data/test/adapter_test.rb +1 -1
  39. data/test/array_serializer_test.rb +14 -0
  40. data/test/fixtures/poro.rb +31 -7
  41. data/test/generators/scaffold_controller_generator_test.rb +24 -0
  42. data/test/{serializers/generators_test.rb → generators/serializer_generator_test.rb} +2 -10
  43. data/test/serializers/adapter_for_test.rb +1 -1
  44. data/test/serializers/associations_test.rb +21 -0
  45. data/test/serializers/attribute_test.rb +16 -1
  46. data/test/serializers/attributes_test.rb +35 -0
  47. data/test/serializers/cache_test.rb +14 -4
  48. data/test/serializers/configuration_test.rb +1 -1
  49. data/test/serializers/meta_test.rb +38 -9
  50. data/test/serializers/serializer_for_test.rb +9 -0
  51. data/test/test_helper.rb +10 -7
  52. metadata +12 -5
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: e77127a346e9bfdfb4b48152c333394b5c5e2e41
4
- data.tar.gz: cc4ba7acbb64a83d588e6da78c54ace9e9309734
3
+ metadata.gz: 47b346a9a30d6c7e246505a4b48e3c8dd593c2a6
4
+ data.tar.gz: 74a94269fb1ffd10b1e30b65cd99e43ca3a62b30
5
5
  SHA512:
6
- metadata.gz: fca71a58180a630765a4628c9202b775fb855c36d517546a6fa122166944cd14d79e968369d7fdabce45c6bd585b55a3e9c17ccd72b8b1287fe8eff0b8507167
7
- data.tar.gz: dafdf5f8c3f57bdbaf141d05573459bc14f314a0a5bc6a30f94eb8f8d6f2e8d29649ba34187112c27c819536dd77a7983715b066c83283ef1434a8ea333669ff
6
+ metadata.gz: 61c621fc667fc3e1e168e50c4af8f267f4b7e3e82ce634d2555e814b2dde4a4514a9033cfa6f917b225bcc01b1f86fee36625a74e4a5d3dbeffc4209ff17aa70
7
+ data.tar.gz: a365c42818a1d413d10d78abce01954160879249b6b4e6119db93178d54aa4b608c026c730b2da2f00af7c77537a1260a506ed8fbf8cecf73341f90d437b5f89
@@ -15,13 +15,12 @@ install:
15
15
  - bundle install --retry=3
16
16
 
17
17
  env:
18
- - "RAILS_VERSION=3.2"
19
18
  - "RAILS_VERSION=4.0"
20
19
  - "RAILS_VERSION=4.1"
20
+ - "RAILS_VERSION=4.2"
21
21
  - "RAILS_VERSION=master"
22
22
 
23
23
  matrix:
24
24
  allow_failures:
25
25
  - rvm: ruby-head
26
26
  - env: "RAILS_VERSION=master"
27
- - env: "RAILS_VERSION=3.2"
data/Gemfile CHANGED
@@ -5,7 +5,7 @@ gemspec
5
5
 
6
6
  gem "minitest"
7
7
 
8
- version = ENV["RAILS_VERSION"] || "4.1"
8
+ version = ENV["RAILS_VERSION"] || "4.2"
9
9
 
10
10
  if version == "master"
11
11
  gem "rails", github: "rails/rails"
data/README.md CHANGED
@@ -8,6 +8,9 @@ AMS does this through two components: **serializers** and **adapters**.
8
8
  Serializers describe _which_ attributes and relationships should be serialized.
9
9
  Adapters describe _how_ attributes and relationships should be serialized.
10
10
 
11
+ By default AMS will use the **Json Adapter**. But we strongly advise you to use JsonApi Adapter that follows 1.0 of the format specified in [jsonapi.org/format](http://jsonapi.org/format).
12
+ Check how to change the adapter in the sections bellow.
13
+
11
14
  # RELEASE CANDIDATE, PLEASE READ
12
15
 
13
16
  This is the master branch of AMS. It will become the `0.10.0` release when it's
@@ -175,7 +178,7 @@ end
175
178
 
176
179
  #### JSONAPI
177
180
 
178
- This adapter follows RC3 of the format specified in
181
+ This adapter follows RC4 of the format specified in
179
182
  [jsonapi.org/format](http://jsonapi.org/format). It will include the associated
180
183
  resources in the `"included"` member when the resource names are included in the
181
184
  `include` option.
@@ -265,7 +268,7 @@ The options are the same options of ```ActiveSupport::Cache::Store```, plus
265
268
  a ```key``` option that will be the prefix of the object cache
266
269
  on a pattern ```"#{key}/#{object.id}-#{object.updated_at}"```.
267
270
 
268
- The cache support is optimized to use the cached object in multiple request. An object cached on an ```show``` request will be reused at the ```index```. If there is a relationship with another cached serializer it will also be created and reused automatically.
271
+ The cache support is optimized to use the cached object in multiple request. An object cached on a ```show``` request will be reused at the ```index```. If there is a relationship with another cached serializer it will also be created and reused automatically.
269
272
 
270
273
  **[NOTE] Every object is individually cached.**
271
274
 
@@ -294,7 +297,7 @@ but in this case it will be automatically expired after 3 hours.
294
297
 
295
298
  ### Fragmenting Caching
296
299
 
297
- If there is some API endpoint that shouldn't be fully cached, you can still optmise it, using Fragment Cache on the attributes and relationships that you want to cache.
300
+ If there is some API endpoint that shouldn't be fully cached, you can still optimise it, using Fragment Cache on the attributes and relationships that you want to cache.
298
301
 
299
302
  You can define the attribute by using ```only``` or ```except``` option on cache method.
300
303
 
@@ -6,7 +6,7 @@ module ActionController
6
6
 
7
7
  include ActionController::Renderers
8
8
 
9
- ADAPTER_OPTION_KEYS = [:include, :fields, :root, :adapter]
9
+ ADAPTER_OPTION_KEYS = [:include, :fields, :adapter]
10
10
 
11
11
  included do
12
12
  class_attribute :_serialization_scope
@@ -53,6 +53,14 @@ module ActionController
53
53
  end
54
54
  end
55
55
 
56
+ def rescue_with_handler(exception)
57
+ @_serializer = nil
58
+ @_serializer_opts = nil
59
+ @_adapter_opts = nil
60
+
61
+ super(exception)
62
+ end
63
+
56
64
  module ClassMethods
57
65
  def serialization_scope(scope)
58
66
  self._serialization_scope = scope
@@ -19,18 +19,22 @@ module ActiveModel
19
19
  attr_accessor :_cache_only
20
20
  attr_accessor :_cache_except
21
21
  attr_accessor :_cache_options
22
+ attr_accessor :_cache_digest
22
23
  end
23
24
 
24
25
  def self.inherited(base)
25
- base._attributes = []
26
- base._attributes_keys = {}
27
- base._associations = {}
26
+ base._attributes = self._attributes.try(:dup) || []
27
+ base._attributes_keys = self._attributes_keys.try(:dup) || {}
28
+ base._associations = self._associations.try(:dup) || {}
28
29
  base._urls = []
30
+ serializer_file = File.open(caller.first[/^[^:]+/])
31
+ base._cache_digest = Digest::MD5.hexdigest(serializer_file.read)
29
32
  end
30
33
 
31
34
  def self.attributes(*attrs)
32
35
  attrs = attrs.first if attrs.first.class == Array
33
36
  @_attributes.concat attrs
37
+ @_attributes.uniq!
34
38
 
35
39
  attrs.each do |attr|
36
40
  define_method attr do
@@ -42,7 +46,7 @@ module ActiveModel
42
46
  def self.attribute(attr, options = {})
43
47
  key = options.fetch(:key, attr)
44
48
  @_attributes_keys[attr] = {key: key} if key != attr
45
- @_attributes.concat [key]
49
+ @_attributes << key unless @_attributes.include?(key)
46
50
  define_method key do
47
51
  object.read_attribute_for_serialization(attr)
48
52
  end unless method_defined?(key) || _fragmented.respond_to?(attr)
@@ -115,7 +119,9 @@ module ActiveModel
115
119
  end
116
120
 
117
121
  def self.serializer_for(resource, options = {})
118
- if resource.respond_to?(:to_ary)
122
+ if resource.respond_to?(:serializer_class)
123
+ resource.serializer_class
124
+ elsif resource.respond_to?(:to_ary)
119
125
  config.array_serializer
120
126
  else
121
127
  options
@@ -139,14 +145,6 @@ module ActiveModel
139
145
  adapter_class
140
146
  end
141
147
 
142
- def self._root
143
- @@root ||= false
144
- end
145
-
146
- def self._root=(root)
147
- @@root = root
148
- end
149
-
150
148
  def self.root_name
151
149
  name.demodulize.underscore.sub(/_serializer$/, '') if name
152
150
  end
@@ -156,7 +154,7 @@ module ActiveModel
156
154
  def initialize(object, options = {})
157
155
  @object = object
158
156
  @options = options
159
- @root = options[:root] || (self.class._root ? self.class.root_name : false)
157
+ @root = options[:root]
160
158
  @meta = options[:meta]
161
159
  @meta_key = options[:meta_key]
162
160
  @scope = options[:scope]
@@ -170,11 +168,7 @@ module ActiveModel
170
168
  end
171
169
 
172
170
  def json_key
173
- if root == true || root.nil?
174
- self.class.root_name
175
- else
176
- root
177
- end
171
+ self.class.root_name
178
172
  end
179
173
 
180
174
  def id
@@ -182,7 +176,7 @@ module ActiveModel
182
176
  end
183
177
 
184
178
  def type
185
- object.class.to_s.demodulize.underscore.pluralize
179
+ object.class.model_name.plural
186
180
  end
187
181
 
188
182
  def attributes(options = {})
@@ -214,7 +208,7 @@ module ActiveModel
214
208
  if serializer_class
215
209
  serializer = serializer_class.new(
216
210
  association_value,
217
- options.merge(serializer_from_options(association_options))
211
+ options.except(:serializer).merge(serializer_from_options(association_options))
218
212
  )
219
213
  elsif !association_value.nil? && !association_value.instance_of?(Object)
220
214
  association_options[:association_options][:virtual_value] = association_value
@@ -5,6 +5,7 @@ module ActiveModel
5
5
  class Adapter
6
6
  extend ActiveSupport::Autoload
7
7
  autoload :Json
8
+ autoload :FlattenJson
8
9
  autoload :Null
9
10
  autoload :JsonApi
10
11
 
@@ -21,7 +22,8 @@ module ActiveModel
21
22
 
22
23
  def as_json(options = {})
23
24
  hash = serializable_hash(options)
24
- include_meta(hash)
25
+ include_meta(hash) unless self.class == FlattenJson
26
+ hash
25
27
  end
26
28
 
27
29
  def self.create(resource, options = {})
@@ -48,7 +50,7 @@ module ActiveModel
48
50
  yield
49
51
  end
50
52
  elsif is_fragment_cached?
51
- FragmentCache.new(self, @cached_serializer, @options, @root).fetch
53
+ FragmentCache.new(self, @cached_serializer, @options).fetch
52
54
  else
53
55
  yield
54
56
  end
@@ -63,6 +65,13 @@ module ActiveModel
63
65
  end
64
66
 
65
67
  def cache_key
68
+ parts = []
69
+ parts << object_cache_key
70
+ parts << @klass._cache_digest unless @klass._cache_options && @klass._cache_options[:skip_digest]
71
+ parts.join("/")
72
+ end
73
+
74
+ def object_cache_key
66
75
  (@klass._cache_key) ? "#{@klass._cache_key}/#{@cached_serializer.object.id}-#{@cached_serializer.object.updated_at}" : @cached_serializer.object.cache_key
67
76
  end
68
77
 
@@ -75,11 +84,11 @@ module ActiveModel
75
84
  end
76
85
 
77
86
  def root
78
- serializer.json_key
87
+ serializer.json_key.to_sym if serializer.json_key
79
88
  end
80
89
 
81
90
  def include_meta(json)
82
- json[meta_key] = meta if meta && root
91
+ json[meta_key] = meta if meta
83
92
  json
84
93
  end
85
94
  end
@@ -0,0 +1,12 @@
1
+ module ActiveModel
2
+ class Serializer
3
+ class Adapter
4
+ class FlattenJson < Json
5
+ def serializable_hash(options = {})
6
+ super
7
+ @result
8
+ end
9
+ end
10
+ end
11
+ end
12
+ end
@@ -5,8 +5,7 @@ module ActiveModel
5
5
 
6
6
  attr_reader :serializer
7
7
 
8
- def initialize(adapter, serializer, options, root)
9
- @root = root
8
+ def initialize(adapter, serializer, options)
10
9
  @options = options
11
10
  @adapter = adapter
12
11
  @serializer = serializer
@@ -35,8 +34,9 @@ module ActiveModel
35
34
  private
36
35
 
37
36
  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) }
37
+ attributes = serializer.class._attributes
38
+ cached_attributes = (klass._cache_only) ? klass._cache_only : attributes.reject {|attr| klass._cache_except.include?(attr) }
39
+ non_cached_attributes = attributes - cached_attributes
40
40
 
41
41
  cached_attributes.each do |attribute|
42
42
  options = serializer.class._attributes_keys[attribute]
@@ -75,4 +75,4 @@ module ActiveModel
75
75
  end
76
76
  end
77
77
  end
78
- end
78
+ end
@@ -6,7 +6,7 @@ module ActiveModel
6
6
  class Json < Adapter
7
7
  def serializable_hash(options = {})
8
8
  if serializer.respond_to?(:each)
9
- @result = serializer.map{|s| self.class.new(s).serializable_hash }
9
+ @result = serializer.map{|s| FlattenJson.new(s).serializable_hash }
10
10
  else
11
11
  @hash = {}
12
12
 
@@ -23,7 +23,7 @@ module ActiveModel
23
23
  end
24
24
  end
25
25
  else
26
- if association
26
+ if association && association.object
27
27
  @hash[name] = cache_check(association) do
28
28
  association.attributes(options)
29
29
  end
@@ -37,16 +37,14 @@ module ActiveModel
37
37
  @result = @core.merge @hash
38
38
  end
39
39
 
40
- if root = options.fetch(:root, serializer.json_key)
41
- @result = { root => @result }
42
- end
43
- @result
40
+ { root => @result }
41
+ end
42
+
43
+ def fragment_cache(cached_hash, non_cached_hash)
44
+ Json::FragmentCache.new().fragment_cache(cached_hash, non_cached_hash)
44
45
  end
45
- end
46
46
 
47
- def fragment_cache(cached_hash, non_cached_hash)
48
- Json::FragmentCache.new().fragment_cache(cached_hash, non_cached_hash)
49
47
  end
50
48
  end
51
49
  end
52
- end
50
+ end
@@ -6,7 +6,6 @@ module ActiveModel
6
6
  class JsonApi < Adapter
7
7
  def initialize(serializer, options = {})
8
8
  super
9
- serializer.root = true
10
9
  @hash = { data: [] }
11
10
 
12
11
  if fields = options.delete(:fields)
@@ -29,7 +28,7 @@ module ActiveModel
29
28
  end
30
29
  else
31
30
  @hash[:data] = attributes_for_serializer(serializer, @options)
32
- add_resource_links(@hash[:data], serializer)
31
+ add_resource_relationships(@hash[:data], serializer)
33
32
  end
34
33
  @hash
35
34
  end
@@ -41,18 +40,18 @@ module ActiveModel
41
40
 
42
41
  private
43
42
 
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 } }
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 } }
48
47
  end
49
48
 
50
- def add_link(resource, name, serializer, val=nil)
51
- resource[:links] ||= {}
52
- resource[:links][name] = { linkage: nil }
49
+ def add_relationship(resource, name, serializer, val=nil)
50
+ resource[:relationships] ||= {}
51
+ resource[:relationships][name] = { data: nil }
53
52
 
54
53
  if serializer && serializer.object
55
- resource[:links][name][:linkage] = { type: serializer.type, id: serializer.id.to_s }
54
+ resource[:relationships][name][:data] = { type: serializer.type, id: serializer.id.to_s }
56
55
  end
57
56
  end
58
57
 
@@ -68,7 +67,7 @@ module ActiveModel
68
67
  serializers.each do |serializer|
69
68
  attrs = attributes_for_serializer(serializer, @options)
70
69
 
71
- add_resource_links(attrs, serializer, add_included: false)
70
+ add_resource_relationships(attrs, serializer, add_included: false)
72
71
 
73
72
  @hash[:included].push(attrs) unless @hash[:included].include?(attrs)
74
73
  end
@@ -85,26 +84,31 @@ module ActiveModel
85
84
  if serializer.respond_to?(:each)
86
85
  result = []
87
86
  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
87
+ result << resource_object_for(object, options)
95
88
  end
96
89
  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
90
+ result = resource_object_for(serializer, options)
104
91
  end
105
92
  result
106
93
  end
107
94
 
95
+ def resource_object_for(serializer, options)
96
+ options[:fields] = @fieldset && @fieldset.fields_for(serializer)
97
+ options[:required_fields] = [:id, :type]
98
+
99
+ cache_check(serializer) do
100
+ attributes = serializer.attributes(options)
101
+
102
+ result = {
103
+ id: attributes.delete(:id).to_s,
104
+ type: attributes.delete(:type)
105
+ }
106
+
107
+ result[:attributes] = attributes if attributes.any?
108
+ result
109
+ end
110
+ end
111
+
108
112
  def include_assoc?(assoc)
109
113
  return false unless @options[:include]
110
114
  check_assoc("#{assoc}$")
@@ -123,19 +127,19 @@ module ActiveModel
123
127
  end
124
128
  end
125
129
 
126
- def add_resource_links(attrs, serializer, options = {})
130
+ def add_resource_relationships(attrs, serializer, options = {})
127
131
  options[:add_included] = options.fetch(:add_included, true)
128
132
 
129
133
  serializer.each_association do |name, association, opts|
130
- attrs[:links] ||= {}
134
+ attrs[:relationships] ||= {}
131
135
 
132
136
  if association.respond_to?(:each)
133
- add_links(attrs, name, association)
137
+ add_relationships(attrs, name, association)
134
138
  else
135
139
  if opts[:virtual_value]
136
- add_link(attrs, name, nil, opts[:virtual_value])
140
+ add_relationship(attrs, name, nil, opts[:virtual_value])
137
141
  else
138
- add_link(attrs, name, association)
142
+ add_relationship(attrs, name, association)
139
143
  end
140
144
  end
141
145