cheap_ams 0.10.0.rc2

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 (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