active_model_serializers 0.10.0.rc5 → 0.10.0

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 (57) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +10 -0
  3. data/.travis.yml +0 -8
  4. data/CHANGELOG.md +23 -0
  5. data/CONTRIBUTING.md +14 -4
  6. data/Gemfile +1 -2
  7. data/README.md +3 -3
  8. data/active_model_serializers.gemspec +1 -1
  9. data/appveyor.yml +6 -10
  10. data/docs/ARCHITECTURE.md +1 -1
  11. data/docs/README.md +1 -0
  12. data/docs/general/deserialization.md +19 -19
  13. data/docs/general/serializers.md +33 -0
  14. data/docs/howto/serialize_poro.md +32 -0
  15. data/lib/active_model/serializer.rb +2 -0
  16. data/lib/active_model/serializer/caching.rb +185 -3
  17. data/lib/active_model/serializer/field.rb +36 -2
  18. data/lib/active_model/serializer/lint.rb +8 -18
  19. data/lib/active_model/serializer/version.rb +1 -1
  20. data/lib/active_model_serializers.rb +0 -2
  21. data/lib/active_model_serializers/adapter/attributes.rb +20 -38
  22. data/lib/active_model_serializers/adapter/base.rb +8 -15
  23. data/lib/active_model_serializers/adapter/json.rb +12 -2
  24. data/lib/active_model_serializers/adapter/json_api.rb +33 -30
  25. data/lib/active_model_serializers/adapter/json_api/pagination_links.rb +10 -5
  26. data/lib/active_model_serializers/adapter/null.rb +1 -2
  27. data/lib/active_model_serializers/register_jsonapi_renderer.rb +3 -2
  28. data/lib/active_model_serializers/serializable_resource.rb +1 -1
  29. data/lib/active_model_serializers/test/schema.rb +44 -9
  30. data/test/action_controller/json_api/transform_test.rb +2 -1
  31. data/test/action_controller/serialization_test.rb +3 -1
  32. data/test/active_model_serializers/test/schema_test.rb +5 -3
  33. data/test/active_model_serializers/test/serializer_test.rb +1 -2
  34. data/test/adapter/json/transform_test.rb +14 -14
  35. data/test/adapter/json_api/has_many_test.rb +3 -2
  36. data/test/adapter/json_api/has_one_test.rb +3 -2
  37. data/test/adapter/json_api/pagination_links_test.rb +39 -21
  38. data/test/adapter/json_api/transform_test.rb +36 -34
  39. data/test/adapter/polymorphic_test.rb +111 -12
  40. data/test/adapter_test.rb +27 -0
  41. data/test/array_serializer_test.rb +10 -25
  42. data/test/benchmark/bm_caching.rb +17 -15
  43. data/test/benchmark/controllers.rb +9 -2
  44. data/test/benchmark/fixtures.rb +56 -4
  45. data/test/cache_test.rb +103 -6
  46. data/test/fixtures/active_record.rb +10 -0
  47. data/test/fixtures/poro.rb +31 -3
  48. data/test/serializers/associations_test.rb +43 -15
  49. data/test/serializers/attribute_test.rb +44 -16
  50. data/test/serializers/meta_test.rb +5 -7
  51. data/test/support/isolated_unit.rb +0 -1
  52. data/test/test_helper.rb +19 -21
  53. metadata +7 -12
  54. data/lib/active_model_serializers/cached_serializer.rb +0 -87
  55. data/lib/active_model_serializers/fragment_cache.rb +0 -118
  56. data/test/active_model_serializers/cached_serializer_test.rb +0 -80
  57. data/test/active_model_serializers/fragment_cache_test.rb +0 -34
@@ -6,24 +6,29 @@ module ActiveModelSerializers
6
6
 
7
7
  attr_reader :collection, :context
8
8
 
9
- def initialize(collection, context)
9
+ def initialize(collection, adapter_options)
10
10
  @collection = collection
11
- @context = context
11
+ @adapter_options = adapter_options
12
+ @context = adapter_options.fetch(:serialization_context)
12
13
  end
13
14
 
14
- def serializable_hash(options = {})
15
+ def as_json
15
16
  per_page = collection.try(:per_page) || collection.try(:limit_value) || collection.size
16
17
  pages_from.each_with_object({}) do |(key, value), hash|
17
18
  params = query_parameters.merge(page: { number: value, size: per_page }).to_query
18
19
 
19
- hash[key] = "#{url(options)}?#{params}"
20
+ hash[key] = "#{url(adapter_options)}?#{params}"
20
21
  end
21
22
  end
22
23
 
24
+ protected
25
+
26
+ attr_reader :adapter_options
27
+
23
28
  private
24
29
 
25
30
  def pages_from
26
- return {} if collection.total_pages == FIRST_PAGE
31
+ return {} if collection.total_pages <= FIRST_PAGE
27
32
 
28
33
  {}.tap do |pages|
29
34
  pages[:self] = collection.current_page
@@ -1,8 +1,7 @@
1
1
  module ActiveModelSerializers
2
2
  module Adapter
3
3
  class Null < Base
4
- # Since options param is not being used, underscored naming of the param
5
- def serializable_hash(_options = nil)
4
+ def serializable_hash(*)
6
5
  {}
7
6
  end
8
7
  end
@@ -57,8 +57,9 @@ end
57
57
  ActionController::Renderers.add :jsonapi do |json, options|
58
58
  json = serialize_jsonapi(json, options).to_json(options) unless json.is_a?(String)
59
59
  self.content_type ||= media_type
60
- headers.merge! ActiveModelSerializers::Jsonapi::HEADERS[:response]
61
60
  self.response_body = json
62
61
  end
63
62
 
64
- ActionController::Base.send :include, ActiveModelSerializers::Jsonapi::ControllerSupport
63
+ ActiveSupport.on_load(:action_controller) do
64
+ include ActiveModelSerializers::Jsonapi::ControllerSupport
65
+ end
@@ -2,7 +2,7 @@ require 'set'
2
2
 
3
3
  module ActiveModelSerializers
4
4
  class SerializableResource
5
- ADAPTER_OPTION_KEYS = Set.new([:include, :fields, :adapter, :meta, :meta_key, :links])
5
+ ADAPTER_OPTION_KEYS = Set.new([:include, :fields, :adapter, :meta, :meta_key, :links, :serialization_context, :key_transform])
6
6
  include ActiveModelSerializers::Logging
7
7
 
8
8
  delegate :serializable_hash, :as_json, :to_json, to: :adapter
@@ -10,19 +10,38 @@ module ActiveModelSerializers
10
10
  # get :index
11
11
  # assert_response_schema
12
12
  def assert_response_schema(schema_path = nil, message = nil)
13
- matcher = AssertResponseSchema.new(schema_path, response, message)
13
+ matcher = AssertResponseSchema.new(schema_path, request, response, message)
14
14
  assert(matcher.call, matcher.message)
15
15
  end
16
16
 
17
- MissingSchema = Class.new(Errno::ENOENT)
18
- InvalidSchemaError = Class.new(StandardError)
17
+ def assert_request_schema(schema_path = nil, message = nil)
18
+ matcher = AssertRequestSchema.new(schema_path, request, response, message)
19
+ assert(matcher.call, matcher.message)
20
+ end
21
+
22
+ # May be renamed
23
+ def assert_request_response_schema(schema_path = nil, message = nil)
24
+ assert_request_schema(schema_path, message)
25
+ assert_response_schema(schema_path, message)
26
+ end
27
+
28
+ def assert_schema(payload, schema_path = nil, message = nil)
29
+ matcher = AssertSchema.new(schema_path, request, response, message, payload)
30
+ assert(matcher.call, matcher.message)
31
+ end
19
32
 
20
- class AssertResponseSchema
21
- attr_reader :schema_path, :response, :message
33
+ MissingSchema = Class.new(Minitest::Assertion)
34
+ InvalidSchemaError = Class.new(Minitest::Assertion)
22
35
 
23
- def initialize(schema_path, response, message)
36
+ class AssertSchema
37
+ attr_reader :schema_path, :request, :response, :message, :payload
38
+
39
+ # Interface may change.
40
+ def initialize(schema_path, request, response, message, payload = nil)
24
41
  require_json_schema!
42
+ @request = request
25
43
  @response = response
44
+ @payload = payload
26
45
  @schema_path = schema_path || schema_path_default
27
46
  @message = message
28
47
  @document_store = JsonSchema::DocumentStore.new
@@ -32,7 +51,7 @@ module ActiveModelSerializers
32
51
  def call
33
52
  json_schema.expand_references!(store: document_store)
34
53
  status, errors = json_schema.validate(response_body)
35
- @message ||= errors.map(&:to_s).to_sentence
54
+ @message = [message, errors.map(&:to_s).to_sentence].compact.join(': ')
36
55
  status
37
56
  end
38
57
 
@@ -41,11 +60,11 @@ module ActiveModelSerializers
41
60
  attr_reader :document_store
42
61
 
43
62
  def controller_path
44
- response.request.filtered_parameters[:controller]
63
+ request.filtered_parameters[:controller]
45
64
  end
46
65
 
47
66
  def action
48
- response.request.filtered_parameters[:action]
67
+ request.filtered_parameters[:action]
49
68
  end
50
69
 
51
70
  def schema_directory
@@ -68,6 +87,10 @@ module ActiveModelSerializers
68
87
  load_json(response.body)
69
88
  end
70
89
 
90
+ def request_params
91
+ request.env['action_dispatch.request.request_parameters']
92
+ end
93
+
71
94
  def json_schema
72
95
  @json_schema ||= JsonSchema.parse!(schema_data)
73
96
  end
@@ -98,6 +121,18 @@ module ActiveModelSerializers
98
121
  raise LoadError, "You don't have json_schema installed in your application. Please add it to your Gemfile and run bundle install"
99
122
  end
100
123
  end
124
+ class AssertResponseSchema < AssertSchema
125
+ def initialize(*)
126
+ super
127
+ @payload = response_body
128
+ end
129
+ end
130
+ class AssertRequestSchema < AssertSchema
131
+ def initialize(*)
132
+ super
133
+ @payload = request_params
134
+ end
135
+ end
101
136
  end
102
137
  end
103
138
  end
@@ -60,10 +60,11 @@ module ActionController
60
60
  end
61
61
 
62
62
  def render_resource_with_transform_with_global_config
63
- setup_post
64
63
  old_transform = ActiveModelSerializers.config.key_transform
64
+ setup_post
65
65
  ActiveModelSerializers.config.key_transform = :camel_lower
66
66
  render json: @post, serializer: PostSerializer, adapter: :json_api
67
+ ensure
67
68
  ActiveModelSerializers.config.key_transform = old_transform
68
69
  end
69
70
  end
@@ -454,13 +454,15 @@ module ActionController
454
454
  end
455
455
 
456
456
  def test_render_event_is_emmited
457
- ::ActiveSupport::Notifications.subscribe('render.active_model_serializers') do |name|
457
+ subscriber = ::ActiveSupport::Notifications.subscribe('render.active_model_serializers') do |name|
458
458
  @name = name
459
459
  end
460
460
 
461
461
  get :render_using_implicit_serializer
462
462
 
463
463
  assert_equal 'render.active_model_serializers', @name
464
+ ensure
465
+ ActiveSupport::Notifications.unsubscribe(subscriber) if subscriber
464
466
  end
465
467
  end
466
468
  end
@@ -54,13 +54,15 @@ module ActiveModelSerializers
54
54
 
55
55
  def test_that_raises_error_with_a_custom_message_with_a_invalid_schema
56
56
  message = 'oh boy the show is broken'
57
+ exception_message = "#/name: failed schema #/properties/name: For 'properties/name', \"Name 1\" is not an integer. and #/description: failed schema #/properties/description: For 'properties/description', \"Description 1\" is not a boolean."
58
+ expected_message = "#{message}: #{exception_message}"
57
59
 
58
60
  get :show
59
61
 
60
62
  error = assert_raises Minitest::Assertion do
61
63
  assert_response_schema(nil, message)
62
64
  end
63
- assert_equal(message, error.message)
65
+ assert_equal(expected_message, error.message)
64
66
  end
65
67
 
66
68
  def test_that_assert_with_a_custom_schema
@@ -102,14 +104,14 @@ module ActiveModelSerializers
102
104
  end
103
105
 
104
106
  def test_with_a_non_existent_file
105
- message = %r{.* - No Schema file at test/support/schemas/non-existent.json}
107
+ message = 'No Schema file at test/support/schemas/non-existent.json'
106
108
 
107
109
  get :show
108
110
 
109
111
  error = assert_raises ActiveModelSerializers::Test::Schema::MissingSchema do
110
112
  assert_response_schema('non-existent.json')
111
113
  end
112
- assert_match(message, error.message)
114
+ assert_equal(message, error.message)
113
115
  end
114
116
 
115
117
  def test_that_raises_with_a_invalid_json_body
@@ -10,9 +10,8 @@ module ActiveModelSerializers
10
10
  render json: Profile.new(name: 'Name 1', description: 'Description 1', comments: 'Comments 1')
11
11
  end
12
12
 
13
- # For Rails4.0
14
13
  def render_some_text
15
- Rails.version > '4.1' ? render(plain: 'ok') : render(text: 'ok')
14
+ render(plain: 'ok')
16
15
  end
17
16
  end
18
17
 
@@ -8,9 +8,11 @@ module ActiveModelSerializers
8
8
  context = Minitest::Mock.new
9
9
  context.expect(:request_url, URI)
10
10
  context.expect(:query_parameters, {})
11
- @options = {}
12
- @options[:key_transform] = key_transform if key_transform
13
- @options[:serialization_context] = context
11
+ options = {}
12
+ options[:key_transform] = key_transform if key_transform
13
+ options[:serialization_context] = context
14
+ serializer = CustomBlogSerializer.new(@blog)
15
+ @adapter = ActiveModelSerializers::Adapter::Json.new(serializer, options)
14
16
  end
15
17
 
16
18
  Post = Class.new(::Model)
@@ -18,24 +20,22 @@ module ActiveModelSerializers
18
20
  attributes :id, :title, :body, :publish_at
19
21
  end
20
22
 
21
- def setup
23
+ setup do
22
24
  ActionController::Base.cache_store.clear
23
25
  @blog = Blog.new(id: 1, name: 'My Blog!!', special_attribute: 'neat')
24
- serializer = CustomBlogSerializer.new(@blog)
25
- @adapter = ActiveModelSerializers::Adapter::Json.new(serializer)
26
26
  end
27
27
 
28
28
  def test_transform_default
29
29
  mock_request
30
30
  assert_equal({
31
31
  blog: { id: 1, special_attribute: 'neat', articles: nil }
32
- }, @adapter.serializable_hash(@options))
32
+ }, @adapter.serializable_hash)
33
33
  end
34
34
 
35
35
  def test_transform_global_config
36
36
  mock_request
37
37
  result = with_config(key_transform: :camel_lower) do
38
- @adapter.serializable_hash(@options)
38
+ @adapter.serializable_hash
39
39
  end
40
40
  assert_equal({
41
41
  blog: { id: 1, specialAttribute: 'neat', articles: nil }
@@ -45,7 +45,7 @@ module ActiveModelSerializers
45
45
  def test_transform_serialization_ctx_overrides_global_config
46
46
  mock_request(:camel)
47
47
  result = with_config(key_transform: :camel_lower) do
48
- @adapter.serializable_hash(@options)
48
+ @adapter.serializable_hash
49
49
  end
50
50
  assert_equal({
51
51
  Blog: { Id: 1, SpecialAttribute: 'neat', Articles: nil }
@@ -56,7 +56,7 @@ module ActiveModelSerializers
56
56
  mock_request(:blam)
57
57
  result = nil
58
58
  assert_raises NoMethodError do
59
- result = @adapter.serializable_hash(@options)
59
+ result = @adapter.serializable_hash
60
60
  end
61
61
  end
62
62
 
@@ -64,28 +64,28 @@ module ActiveModelSerializers
64
64
  mock_request(:dash)
65
65
  assert_equal({
66
66
  blog: { id: 1, :"special-attribute" => 'neat', articles: nil }
67
- }, @adapter.serializable_hash(@options))
67
+ }, @adapter.serializable_hash)
68
68
  end
69
69
 
70
70
  def test_transform_unaltered
71
71
  mock_request(:unaltered)
72
72
  assert_equal({
73
73
  blog: { id: 1, special_attribute: 'neat', articles: nil }
74
- }, @adapter.serializable_hash(@options))
74
+ }, @adapter.serializable_hash)
75
75
  end
76
76
 
77
77
  def test_transform_camel
78
78
  mock_request(:camel)
79
79
  assert_equal({
80
80
  Blog: { Id: 1, SpecialAttribute: 'neat', Articles: nil }
81
- }, @adapter.serializable_hash(@options))
81
+ }, @adapter.serializable_hash)
82
82
  end
83
83
 
84
84
  def test_transform_camel_lower
85
85
  mock_request(:camel_lower)
86
86
  assert_equal({
87
87
  blog: { id: 1, specialAttribute: 'neat', articles: nil }
88
- }, @adapter.serializable_hash(@options))
88
+ }, @adapter.serializable_hash)
89
89
  end
90
90
  end
91
91
  end
@@ -131,8 +131,9 @@ module ActiveModelSerializers
131
131
  id: '1',
132
132
  type: 'virtual-values',
133
133
  relationships: {
134
- maker: { data: { id: 1 } },
135
- reviews: { data: [{ id: 1 }, { id: 2 }] }
134
+ maker: { data: { type: 'makers', id: '1' } },
135
+ reviews: { data: [{ type: 'reviews', id: '1' },
136
+ { type: 'reviews', id: '2' }] }
136
137
  }
137
138
  }
138
139
  }, adapter.serializable_hash)
@@ -65,8 +65,9 @@ module ActiveModelSerializers
65
65
  id: '1',
66
66
  type: 'virtual-values',
67
67
  relationships: {
68
- maker: { data: { id: 1 } },
69
- reviews: { data: [{ id: 1 }, { id: 2 }] }
68
+ maker: { data: { type: 'makers', id: '1' } },
69
+ reviews: { data: [{ type: 'reviews', id: '1' },
70
+ { type: 'reviews', id: '2' }] }
70
71
  }
71
72
  }
72
73
  }
@@ -25,13 +25,13 @@ module ActiveModelSerializers
25
25
  context = Minitest::Mock.new
26
26
  context.expect(:request_url, original_url)
27
27
  context.expect(:query_parameters, query_parameters)
28
- @options = {}
29
- @options[:serialization_context] = context
28
+ context.expect(:key_transform, nil)
30
29
  end
31
30
 
32
- def load_adapter(paginated_collection, options = {})
33
- options = options.merge(adapter: :json_api)
34
- ActiveModelSerializers::SerializableResource.new(paginated_collection, options)
31
+ def load_adapter(paginated_collection, mock_request = nil)
32
+ render_options = { adapter: :json_api }
33
+ render_options[:serialization_context] = mock_request if mock_request
34
+ serializable(paginated_collection, render_options)
35
35
  end
36
36
 
37
37
  def using_kaminari(page = 2)
@@ -101,44 +101,62 @@ module ActiveModelSerializers
101
101
  end
102
102
  end
103
103
 
104
+ def expected_response_with_no_data_pagination_links
105
+ {}.tap do |hash|
106
+ hash[:data] = []
107
+ hash[:links] = {}
108
+ end
109
+ end
110
+
104
111
  def test_pagination_links_using_kaminari
105
- adapter = load_adapter(using_kaminari)
112
+ adapter = load_adapter(using_kaminari, mock_request)
106
113
 
107
- mock_request
108
- assert_equal expected_response_with_pagination_links, adapter.serializable_hash(@options)
114
+ assert_equal expected_response_with_pagination_links, adapter.serializable_hash
109
115
  end
110
116
 
111
117
  def test_pagination_links_using_will_paginate
112
- adapter = load_adapter(using_will_paginate)
118
+ adapter = load_adapter(using_will_paginate, mock_request)
113
119
 
114
- mock_request
115
- assert_equal expected_response_with_pagination_links, adapter.serializable_hash(@options)
120
+ assert_equal expected_response_with_pagination_links, adapter.serializable_hash
116
121
  end
117
122
 
118
123
  def test_pagination_links_with_additional_params
119
- adapter = load_adapter(using_will_paginate)
124
+ adapter = load_adapter(using_will_paginate, mock_request({ test: 'test' }))
120
125
 
121
- mock_request({ test: 'test' })
122
126
  assert_equal expected_response_with_pagination_links_and_additional_params,
123
- adapter.serializable_hash(@options)
127
+ adapter.serializable_hash
128
+ end
129
+
130
+ def test_pagination_links_when_zero_results_kaminari
131
+ @array = []
132
+
133
+ adapter = load_adapter(using_kaminari(1), mock_request)
134
+
135
+ assert_equal expected_response_with_no_data_pagination_links, adapter.serializable_hash
136
+ end
137
+
138
+ def test_pagination_links_when_zero_results_will_paginate
139
+ @array = []
140
+
141
+ adapter = load_adapter(using_will_paginate(1), mock_request)
142
+
143
+ assert_equal expected_response_with_no_data_pagination_links, adapter.serializable_hash
124
144
  end
125
145
 
126
146
  def test_last_page_pagination_links_using_kaminari
127
- adapter = load_adapter(using_kaminari(3))
147
+ adapter = load_adapter(using_kaminari(3), mock_request)
128
148
 
129
- mock_request
130
- assert_equal expected_response_with_last_page_pagination_links, adapter.serializable_hash(@options)
149
+ assert_equal expected_response_with_last_page_pagination_links, adapter.serializable_hash
131
150
  end
132
151
 
133
152
  def test_last_page_pagination_links_using_will_paginate
134
- adapter = load_adapter(using_will_paginate(3))
153
+ adapter = load_adapter(using_will_paginate(3), mock_request)
135
154
 
136
- mock_request
137
- assert_equal expected_response_with_last_page_pagination_links, adapter.serializable_hash(@options)
155
+ assert_equal expected_response_with_last_page_pagination_links, adapter.serializable_hash
138
156
  end
139
157
 
140
158
  def test_not_showing_pagination_links
141
- adapter = load_adapter(@array)
159
+ adapter = load_adapter(@array, mock_request)
142
160
 
143
161
  assert_equal expected_response_without_pagination_links, adapter.serializable_hash
144
162
  end