cheap_ams 0.10.0.rc2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (97) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +22 -0
  3. data/.travis.yml +26 -0
  4. data/CHANGELOG.md +13 -0
  5. data/CONTRIBUTING.md +31 -0
  6. data/Gemfile +35 -0
  7. data/LICENSE.txt +22 -0
  8. data/README.md +348 -0
  9. data/Rakefile +12 -0
  10. data/appveyor.yml +25 -0
  11. data/cheap_ams.gemspec +49 -0
  12. data/docs/README.md +27 -0
  13. data/docs/general/adapters.md +51 -0
  14. data/docs/general/getting_started.md +73 -0
  15. data/docs/howto/add_pagination_links.md +112 -0
  16. data/docs/howto/add_root_key.md +51 -0
  17. data/lib/action_controller/serialization.rb +62 -0
  18. data/lib/active_model/serializable_resource.rb +84 -0
  19. data/lib/active_model/serializer/adapter/flatten_json.rb +19 -0
  20. data/lib/active_model/serializer/adapter/fragment_cache.rb +82 -0
  21. data/lib/active_model/serializer/adapter/json/fragment_cache.rb +16 -0
  22. data/lib/active_model/serializer/adapter/json.rb +53 -0
  23. data/lib/active_model/serializer/adapter/json_api/fragment_cache.rb +24 -0
  24. data/lib/active_model/serializer/adapter/json_api/pagination_links.rb +58 -0
  25. data/lib/active_model/serializer/adapter/json_api.rb +183 -0
  26. data/lib/active_model/serializer/adapter/null.rb +11 -0
  27. data/lib/active_model/serializer/adapter.rb +98 -0
  28. data/lib/active_model/serializer/array_serializer.rb +35 -0
  29. data/lib/active_model/serializer/association.rb +21 -0
  30. data/lib/active_model/serializer/associations.rb +97 -0
  31. data/lib/active_model/serializer/belongs_to_reflection.rb +10 -0
  32. data/lib/active_model/serializer/collection_reflection.rb +7 -0
  33. data/lib/active_model/serializer/configuration.rb +14 -0
  34. data/lib/active_model/serializer/fieldset.rb +42 -0
  35. data/lib/active_model/serializer/has_many_reflection.rb +10 -0
  36. data/lib/active_model/serializer/has_one_reflection.rb +10 -0
  37. data/lib/active_model/serializer/lint.rb +131 -0
  38. data/lib/active_model/serializer/railtie.rb +9 -0
  39. data/lib/active_model/serializer/reflection.rb +74 -0
  40. data/lib/active_model/serializer/singular_reflection.rb +7 -0
  41. data/lib/active_model/serializer/version.rb +5 -0
  42. data/lib/active_model/serializer.rb +205 -0
  43. data/lib/active_model_serializers.rb +29 -0
  44. data/lib/generators/serializer/USAGE +6 -0
  45. data/lib/generators/serializer/resource_override.rb +12 -0
  46. data/lib/generators/serializer/serializer_generator.rb +37 -0
  47. data/lib/generators/serializer/templates/serializer.rb +8 -0
  48. data/test/action_controller/adapter_selector_test.rb +53 -0
  49. data/test/action_controller/explicit_serializer_test.rb +134 -0
  50. data/test/action_controller/json_api/linked_test.rb +180 -0
  51. data/test/action_controller/json_api/pagination_test.rb +116 -0
  52. data/test/action_controller/serialization_scope_name_test.rb +67 -0
  53. data/test/action_controller/serialization_test.rb +426 -0
  54. data/test/adapter/fragment_cache_test.rb +37 -0
  55. data/test/adapter/json/belongs_to_test.rb +47 -0
  56. data/test/adapter/json/collection_test.rb +82 -0
  57. data/test/adapter/json/has_many_test.rb +47 -0
  58. data/test/adapter/json_api/belongs_to_test.rb +157 -0
  59. data/test/adapter/json_api/collection_test.rb +96 -0
  60. data/test/adapter/json_api/has_many_embed_ids_test.rb +45 -0
  61. data/test/adapter/json_api/has_many_explicit_serializer_test.rb +98 -0
  62. data/test/adapter/json_api/has_many_test.rb +145 -0
  63. data/test/adapter/json_api/has_one_test.rb +81 -0
  64. data/test/adapter/json_api/json_api_test.rb +38 -0
  65. data/test/adapter/json_api/linked_test.rb +283 -0
  66. data/test/adapter/json_api/pagination_links_test.rb +115 -0
  67. data/test/adapter/json_api/resource_type_config_test.rb +59 -0
  68. data/test/adapter/json_test.rb +47 -0
  69. data/test/adapter/null_test.rb +25 -0
  70. data/test/adapter_test.rb +52 -0
  71. data/test/array_serializer_test.rb +97 -0
  72. data/test/capture_warnings.rb +57 -0
  73. data/test/fixtures/active_record.rb +57 -0
  74. data/test/fixtures/poro.rb +266 -0
  75. data/test/generators/scaffold_controller_generator_test.rb +24 -0
  76. data/test/generators/serializer_generator_test.rb +56 -0
  77. data/test/lint_test.rb +44 -0
  78. data/test/poro_test.rb +9 -0
  79. data/test/serializable_resource_test.rb +27 -0
  80. data/test/serializers/adapter_for_test.rb +50 -0
  81. data/test/serializers/association_macros_test.rb +36 -0
  82. data/test/serializers/associations_test.rb +150 -0
  83. data/test/serializers/attribute_test.rb +62 -0
  84. data/test/serializers/attributes_test.rb +63 -0
  85. data/test/serializers/cache_test.rb +164 -0
  86. data/test/serializers/configuration_test.rb +15 -0
  87. data/test/serializers/fieldset_test.rb +26 -0
  88. data/test/serializers/meta_test.rb +121 -0
  89. data/test/serializers/options_test.rb +21 -0
  90. data/test/serializers/root_test.rb +23 -0
  91. data/test/serializers/serializer_for_test.rb +65 -0
  92. data/test/serializers/urls_test.rb +26 -0
  93. data/test/support/rails_app.rb +21 -0
  94. data/test/support/stream_capture.rb +49 -0
  95. data/test/support/test_case.rb +5 -0
  96. data/test/test_helper.rb +41 -0
  97. metadata +287 -0
@@ -0,0 +1,73 @@
1
+ # Getting Started
2
+
3
+ ## Installation
4
+
5
+ ### ActiveModel::Serializer is already included on Rails >= 5
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ ```
10
+ gem 'active_model_serializers'
11
+ ```
12
+
13
+ And then execute:
14
+
15
+ ```
16
+ $ bundle
17
+ ```
18
+
19
+ ## Creating a Serializer
20
+
21
+ The easiest way to create a new serializer is to generate a new resource, which
22
+ will generate a serializer at the same time:
23
+
24
+ ```
25
+ $ rails g resource post title:string body:string
26
+ ```
27
+
28
+ This will generate a serializer in `app/serializers/post_serializer.rb` for
29
+ your new model. You can also generate a serializer for an existing model with
30
+ the serializer generator:
31
+
32
+ ```
33
+ $ rails g serializer post
34
+ ```
35
+
36
+ The generated seralizer will contain basic `attributes` and
37
+ `has_many`/`has_one`/`belongs_to` declarations, based on the model. For example:
38
+
39
+ ```ruby
40
+ class PostSerializer < ActiveModel::Serializer
41
+ attributes :title, :body
42
+
43
+ has_many :comments
44
+ has_one :author
45
+
46
+ end
47
+ ```
48
+
49
+ and
50
+
51
+ ```ruby
52
+ class CommentSerializer < ActiveModel::Serializer
53
+ attributes :name, :body
54
+
55
+ belongs_to :post_id
56
+
57
+ end
58
+ ```
59
+
60
+ ## Rails Integration
61
+
62
+ AMS will automatically integrate with you Rails app, you won't need to update your controller, this is a example of how it will look like:
63
+
64
+ ```ruby
65
+ class PostsController < ApplicationController
66
+
67
+ def show
68
+ @post = Post.find(params[:id])
69
+ render json: @post
70
+ end
71
+
72
+ end
73
+ ```
@@ -0,0 +1,112 @@
1
+ # How to add pagination links
2
+
3
+ ### JSON-API adapter
4
+
5
+ Pagination links will be included in your response automatically as long as the resource is paginated and if you are using a ```JSON-API``` adapter.
6
+
7
+ If you want pagination links in your response, use [Kaminari](https://github.com/amatsuda/kaminari) or [WillPaginate](https://github.com/mislav/will_paginate).
8
+
9
+ ###### Kaminari examples
10
+ ```ruby
11
+ #array
12
+ @posts = Kaminari.paginate_array([1, 2, 3]).page(3).per(1)
13
+ render json: @posts
14
+
15
+ #active_record
16
+ @posts = Post.page(3).per(1)
17
+ render json: @posts
18
+ ```
19
+
20
+ ###### WillPaginate examples
21
+
22
+ ```ruby
23
+ #array
24
+ @posts = [1,2,3].paginate(page: 3, per_page: 1)
25
+ render json: @posts
26
+
27
+ #active_record
28
+ @posts = Post.page(3).per_page(1)
29
+ render json: @posts
30
+ ```
31
+
32
+ ```ruby
33
+ ActiveModel::Serializer.config.adapter = :json_api
34
+ ```
35
+
36
+ ex:
37
+ ```json
38
+ {
39
+ "data": [
40
+ {
41
+ "type": "articles",
42
+ "id": "3",
43
+ "attributes": {
44
+ "title": "JSON API paints my bikeshed!",
45
+ "body": "The shortest article. Ever.",
46
+ "created": "2015-05-22T14:56:29.000Z",
47
+ "updated": "2015-05-22T14:56:28.000Z"
48
+ }
49
+ }
50
+ ],
51
+ "links": {
52
+ "self": "http://example.com/articles?page[number]=3&page[size]=1",
53
+ "first": "http://example.com/articles?page[number]=1&page[size]=1",
54
+ "prev": "http://example.com/articles?page[number]=2&page[size]=1",
55
+ "next": "http://example.com/articles?page[number]=4&page[size]=1",
56
+ "last": "http://example.com/articles?page[number]=13&page[size]=1"
57
+ }
58
+ }
59
+ ```
60
+
61
+ AMS pagination relies on a paginated collection with the methods `current_page`, `total_pages`, and `size`, such as are supported by both [Kaminari](https://github.com/amatsuda/kaminari) or [WillPaginate](https://github.com/mislav/will_paginate).
62
+
63
+
64
+ ### JSON adapter
65
+
66
+ If you are using `JSON` adapter, pagination links will not be included automatically, but it is possible to do so using `meta` key.
67
+
68
+ In your action specify a custom serializer.
69
+ ```ruby
70
+ render json: @posts, serializer: PaginatedSerializer, each_serializer: PostPreviewSerializer
71
+ ```
72
+
73
+ And then, you could do something like the following class.
74
+ ```ruby
75
+ class PaginatedSerializer < ActiveModel::Serializer::ArraySerializer
76
+ def initialize(object, options={})
77
+ meta_key = options[:meta_key] || :meta
78
+ options[meta_key] ||= {}
79
+ options[meta_key] = {
80
+ current_page: object.current_page,
81
+ next_page: object.next_page,
82
+ prev_page: object.prev_page,
83
+ total_pages: object.total_pages,
84
+ total_count: object.total_count
85
+ }
86
+ super(object, options)
87
+ end
88
+ end
89
+ ```
90
+ ex.
91
+ ```json
92
+ {
93
+ "articles": [
94
+ {
95
+ "id": 2,
96
+ "title": "JSON API paints my bikeshed!",
97
+ "body": "The shortest article. Ever."
98
+ }
99
+ ],
100
+ "meta": {
101
+ "current_page": 3,
102
+ "next_page": 4,
103
+ "prev_page": 2,
104
+ "total_pages": 10,
105
+ "total_count": 10
106
+ }
107
+ }
108
+ ```
109
+
110
+ ### FlattenJSON adapter
111
+
112
+ This adapter does not allow us to use `meta` key, due to that it is not possible to add pagination links.
@@ -0,0 +1,51 @@
1
+ # How to add root key
2
+
3
+ Add the root key to your API is quite simple with AMS. The **Adapter** is what determines the format of your JSON response. The default adapter is the ```FlattenJSON``` which doesn't have the root key, so your response is something similar to:
4
+
5
+ ```json
6
+ {
7
+ "id": 1,
8
+ "title": "Awesome Post Tile",
9
+ "content": "Post content"
10
+ }
11
+ ```
12
+
13
+ In order to add the root key you need to use the ```JSON``` Adapter, you can change this in an initializer:
14
+
15
+ ```ruby
16
+ ActiveModel::Serializer.config.adapter = :json
17
+ ```
18
+
19
+ You can also specify a class as adapter, as long as it complies with the AMS adapters interface.
20
+ It will add the root key to all your serialized endpoints.
21
+
22
+ ex:
23
+
24
+ ```json
25
+ {
26
+ "post": {
27
+ "id": 1,
28
+ "title": "Awesome Post Tile",
29
+ "content": "Post content"
30
+ }
31
+ }
32
+ ```
33
+
34
+ or if it returns a collection:
35
+
36
+ ```json
37
+ {
38
+ "posts": [
39
+ {
40
+ "id": 1,
41
+ "title": "Awesome Post Tile",
42
+ "content": "Post content"
43
+ },
44
+ {
45
+ "id": 2,
46
+ "title": "Another Post Tile",
47
+ "content": "Another post content"
48
+ }
49
+ ]
50
+ }
51
+ ```
@@ -0,0 +1,62 @@
1
+ require 'active_support/core_ext/class/attribute'
2
+
3
+ module ActionController
4
+ module Serialization
5
+ extend ActiveSupport::Concern
6
+
7
+ include ActionController::Renderers
8
+
9
+ # Deprecated
10
+ ADAPTER_OPTION_KEYS = ActiveModel::SerializableResource::ADAPTER_OPTION_KEYS
11
+
12
+ included do
13
+ class_attribute :_serialization_scope
14
+ self._serialization_scope = :current_user
15
+ end
16
+
17
+ def serialization_scope
18
+ send(_serialization_scope) if _serialization_scope &&
19
+ respond_to?(_serialization_scope, true)
20
+ end
21
+
22
+ def get_serializer(resource, options = {})
23
+ if ! use_adapter?
24
+ warn "ActionController::Serialization#use_adapter? has been removed. "\
25
+ "Please pass 'adapter: false' or see ActiveSupport::SerializableResource#serialize"
26
+ options[:adapter] = false
27
+ end
28
+ ActiveModel::SerializableResource.serialize(resource, options) do |serializable_resource|
29
+ if serializable_resource.serializer?
30
+ serializable_resource.serialization_scope ||= serialization_scope
31
+ serializable_resource.serialization_scope_name = _serialization_scope
32
+ begin
33
+ serializable_resource.adapter
34
+ rescue ActiveModel::Serializer::ArraySerializer::NoSerializerError
35
+ resource
36
+ end
37
+ else
38
+ resource
39
+ end
40
+ end
41
+ end
42
+
43
+ # Deprecated
44
+ def use_adapter?
45
+ true
46
+ end
47
+
48
+ [:_render_option_json, :_render_with_renderer_json].each do |renderer_method|
49
+ define_method renderer_method do |resource, options|
50
+ options.fetch(:context) { options[:context] = request }
51
+ serializable_resource = get_serializer(resource, options)
52
+ super(serializable_resource, options)
53
+ end
54
+ end
55
+
56
+ module ClassMethods
57
+ def serialization_scope(scope)
58
+ self._serialization_scope = scope
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,84 @@
1
+ require 'set'
2
+ module ActiveModel
3
+ class SerializableResource
4
+
5
+ ADAPTER_OPTION_KEYS = Set.new([:include, :fields, :adapter])
6
+
7
+ def initialize(resource, options = {})
8
+ @resource = resource
9
+ @adapter_opts, @serializer_opts =
10
+ options.partition { |k, _| ADAPTER_OPTION_KEYS.include? k }.map { |h| Hash[h] }
11
+ end
12
+
13
+ delegate :serializable_hash, :as_json, :to_json, to: :adapter
14
+
15
+ # Primary interface to building a serializer (with adapter)
16
+ # If no block is given,
17
+ # returns the serializable_resource, ready for #as_json/#to_json/#serializable_hash.
18
+ # Otherwise, yields the serializable_resource and
19
+ # returns the contents of the block
20
+ def self.serialize(resource, options = {})
21
+ serializable_resource = SerializableResource.new(resource, options)
22
+ if block_given?
23
+ yield serializable_resource
24
+ else
25
+ serializable_resource
26
+ end
27
+ end
28
+
29
+ def serialization_scope=(scope)
30
+ serializer_opts[:scope] = scope
31
+ end
32
+
33
+ def serialization_scope
34
+ serializer_opts[:scope]
35
+ end
36
+
37
+ def serialization_scope_name=(scope_name)
38
+ serializer_opts[:scope_name] = scope_name
39
+ end
40
+
41
+ def adapter
42
+ @adapter ||= ActiveModel::Serializer::Adapter.create(serializer_instance, adapter_opts)
43
+ end
44
+ alias_method :adapter_instance, :adapter
45
+
46
+ def serializer_instance
47
+ @serializer_instance ||= serializer.new(resource, serializer_opts)
48
+ end
49
+
50
+ # Get serializer either explicitly :serializer or implicitly from resource
51
+ # Remove :serializer key from serializer_opts
52
+ # Replace :serializer key with :each_serializer if present
53
+ def serializer
54
+ @serializer ||=
55
+ begin
56
+ @serializer = serializer_opts.delete(:serializer)
57
+ @serializer ||= ActiveModel::Serializer.serializer_for(resource)
58
+
59
+ if serializer_opts.key?(:each_serializer)
60
+ serializer_opts[:serializer] = serializer_opts.delete(:each_serializer)
61
+ end
62
+ @serializer
63
+ end
64
+ end
65
+ alias_method :serializer_class, :serializer
66
+
67
+ # True when no explicit adapter given, or explicit appear is truthy (non-nil)
68
+ # False when explicit adapter is falsy (nil or false)
69
+ def use_adapter?
70
+ !(adapter_opts.key?(:adapter) && !adapter_opts[:adapter])
71
+ end
72
+
73
+ def serializer?
74
+ use_adapter? && !!(serializer)
75
+ end
76
+
77
+ private
78
+
79
+ ActiveModelSerializers.silence_warnings do
80
+ attr_reader :resource, :adapter_opts, :serializer_opts
81
+ end
82
+
83
+ end
84
+ end
@@ -0,0 +1,19 @@
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
+
10
+ private
11
+
12
+ # no-op: FlattenJson adapter does not include meta data, because it does not support root.
13
+ def include_meta(json)
14
+ json
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,82 @@
1
+ module ActiveModel
2
+ class Serializer
3
+ class Adapter
4
+ class FragmentCache
5
+
6
+ attr_reader :serializer
7
+
8
+ def initialize(adapter, serializer, options)
9
+ @options = options
10
+ @adapter = adapter
11
+ @serializer = serializer
12
+ end
13
+
14
+ def fetch
15
+ klass = serializer.class
16
+ # It will split the serializer into two, one that will be cached and other wont
17
+ serializers = fragment_serializer(serializer.object.class.name, klass)
18
+
19
+ # Instanciate both serializers
20
+ cached_serializer = serializers[:cached].constantize.new(serializer.object)
21
+ non_cached_serializer = serializers[:non_cached].constantize.new(serializer.object)
22
+
23
+ cached_adapter = @adapter.class.new(cached_serializer, @options)
24
+ non_cached_adapter = @adapter.class.new(non_cached_serializer, @options)
25
+
26
+ # Get serializable hash from both
27
+ cached_hash = cached_adapter.serializable_hash
28
+ non_cached_hash = non_cached_adapter.serializable_hash
29
+
30
+ # Merge both results
31
+ @adapter.fragment_cache(cached_hash, non_cached_hash)
32
+ end
33
+
34
+ private
35
+
36
+ def cached_attributes(klass, serializers)
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
+
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 = "#{to_valid_const_name(name)}CachedSerializer"
58
+ non_cached = "#{to_valid_const_name(name)}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
+
76
+ def to_valid_const_name(name)
77
+ name.gsub('::', '_')
78
+ end
79
+ end
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,16 @@
1
+ require 'active_model/serializer/adapter/fragment_cache'
2
+ module ActiveModel
3
+ class Serializer
4
+ class Adapter
5
+ class Json < Adapter
6
+ class FragmentCache
7
+
8
+ def fragment_cache(cached_hash, non_cached_hash)
9
+ non_cached_hash.merge cached_hash
10
+ end
11
+
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,53 @@
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 = nil)
8
+ options ||= {}
9
+ if serializer.respond_to?(:each)
10
+ @result = serializer.map { |s| FlattenJson.new(s).serializable_hash(options) }
11
+ else
12
+ @hash = {}
13
+
14
+ @core = cache_check(serializer) do
15
+ serializer.attributes(options)
16
+ end
17
+
18
+ serializer.associations.each do |association|
19
+ serializer = association.serializer
20
+ opts = association.options
21
+
22
+ if serializer.respond_to?(:each)
23
+ array_serializer = serializer
24
+ @hash[association.key] = array_serializer.map do |item|
25
+ cache_check(item) do
26
+ item.attributes(opts)
27
+ end
28
+ end
29
+ else
30
+ @hash[association.key] =
31
+ if serializer && serializer.object
32
+ cache_check(serializer) do
33
+ serializer.attributes(options)
34
+ end
35
+ elsif opts[:virtual_value]
36
+ opts[:virtual_value]
37
+ end
38
+ end
39
+ end
40
+ @result = @core.merge @hash
41
+ end
42
+
43
+ { root => @result }
44
+ end
45
+
46
+ def fragment_cache(cached_hash, non_cached_hash)
47
+ Json::FragmentCache.new().fragment_cache(cached_hash, non_cached_hash)
48
+ end
49
+
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,24 @@
1
+ require 'active_model/serializer/adapter/fragment_cache'
2
+ module ActiveModel
3
+ class Serializer
4
+ class Adapter
5
+ class JsonApi < Adapter
6
+ class FragmentCache
7
+
8
+ def fragment_cache(root, cached_hash, non_cached_hash)
9
+ hash = {}
10
+ core_cached = cached_hash.first
11
+ core_non_cached = non_cached_hash.first
12
+ no_root_cache = cached_hash.delete_if {|key, value| key == core_cached[0] }
13
+ no_root_non_cache = non_cached_hash.delete_if {|key, value| key == core_non_cached[0] }
14
+ cached_resource = (core_cached[1]) ? core_cached[1].deep_merge(core_non_cached[1]) : core_non_cached[1]
15
+ hash = (root) ? { root => cached_resource } : cached_resource
16
+
17
+ hash.deep_merge no_root_non_cache.deep_merge no_root_cache
18
+ end
19
+
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,58 @@
1
+ module ActiveModel
2
+ class Serializer
3
+ class Adapter
4
+ class JsonApi < Adapter
5
+ class PaginationLinks
6
+ FIRST_PAGE = 1
7
+
8
+ attr_reader :collection, :context
9
+
10
+ def initialize(collection, context)
11
+ @collection = collection
12
+ @context = context
13
+ end
14
+
15
+ def serializable_hash(options = {})
16
+ pages_from.each_with_object({}) do |(key, value), hash|
17
+ params = query_parameters.merge(page: { number: value, size: collection.size }).to_query
18
+
19
+ hash[key] = "#{url(options)}?#{params}"
20
+ end
21
+ end
22
+
23
+ private
24
+
25
+ def pages_from
26
+ return {} if collection.total_pages == FIRST_PAGE
27
+
28
+ {}.tap do |pages|
29
+ pages[:self] = collection.current_page
30
+
31
+ unless collection.current_page == FIRST_PAGE
32
+ pages[:first] = FIRST_PAGE
33
+ pages[:prev] = collection.current_page - FIRST_PAGE
34
+ end
35
+
36
+ unless collection.current_page == collection.total_pages
37
+ pages[:next] = collection.current_page + FIRST_PAGE
38
+ pages[:last] = collection.total_pages
39
+ end
40
+ end
41
+ end
42
+
43
+ def url(options)
44
+ @url ||= options.fetch(:links, {}).fetch(:self, nil) || original_url
45
+ end
46
+
47
+ def original_url
48
+ @original_url ||= context.original_url[/\A[^?]+/]
49
+ end
50
+
51
+ def query_parameters
52
+ @query_parameters ||= context.query_parameters
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end