json_apiable 0.1.0 → 0.2.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b0527cb82ea3c0689d6086d1728291e77bff1cd94da969953b8965e729eb66b6
4
- data.tar.gz: e98ff51f719ca22f14574c9acdb4a8b1175e13d513b8f93f2595d4d83cdffa89
3
+ metadata.gz: 02ceaeecfcdb306933da14c8f4596e355958a6bd6902dc37a365295e409d085e
4
+ data.tar.gz: b5dc5338fcb3cd7fd1991f4dc18871ffdc3ed4d63c99d0e826469b558dba8dc0
5
5
  SHA512:
6
- metadata.gz: c424b59b59efa76d8f04c248345ce5a42b9c2b2f53bf539a73f0eea4e4b4551186f32c8923fc097182c54e123582e9a4bdfdf7a715e5417de724bbe485715715
7
- data.tar.gz: 73af3e75acfb6a3e8a69fafdb338882cbcd2e6bcc38d5b9e65600ee5824b7af98ae650e38ec8520fe9b1f38a2591d34652081a18e3d1df1d977c072c5ecfdc48
6
+ metadata.gz: 5c5ddc873cee6e321ab133f8422d22dfe3cc2c46ae57af5c2a1bbfc9b759e00c50b333e11af758ef80f89c8f1b1a5edcbbbd8edb2dd79c13f4659ae20e8a5d4a
7
+ data.tar.gz: 8e9cbdf0d269a0682b2f863b50fdcc71ed00a991174be19d293d75463578cada37390254d45d7a389b6447ece782d6c922b9bef79bc8d9518ede3bbbbf24188f
data/.gitignore CHANGED
@@ -13,3 +13,5 @@
13
13
  # rspec failure tracking
14
14
  .rspec_status
15
15
  /spec/support/rails_app/db/*.sqlite3
16
+ /spec/support/rails_app/tmp/
17
+ /spec/support/rails_app/log/
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- json_apiable (0.1.0)
4
+ json_apiable (0.2.0)
5
5
  activerecord (>= 4.2)
6
6
  activesupport (>= 4.2)
7
7
  fast_jsonapi (~> 1.5)
@@ -186,7 +186,7 @@ DEPENDENCIES
186
186
  rails
187
187
  rails-controller-testing
188
188
  rake (~> 10.0)
189
- rspec-rails
189
+ rspec-rails (~> 3.9)
190
190
  sqlite3
191
191
 
192
192
  BUNDLED WITH
data/README.md CHANGED
@@ -1,8 +1,10 @@
1
1
  # JsonApiable
2
2
 
3
- Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/json_apiable`. To experiment with that code, run `bin/console` for an interactive prompt.
3
+ JsonApiable is a Ruby module that makes it easier for Rails API controllers to handle JSON:API parameter and relationship parsing,
4
+ strong parameter validation, returning well-structured errors and more - all in a Rails-friendly way.
4
5
 
5
- TODO: Delete this and the text above, and describe your gem
6
+ JsonApiable doesn't assume anything about other JSON:API gems you may be using.
7
+ Feel free to use it in conjunction with fast_jsonapi, active_model_serializer, jsonapi-resources or any other library.
6
8
 
7
9
  ## Installation
8
10
 
@@ -21,8 +23,137 @@ Or install it yourself as:
21
23
  $ gem install json_apiable
22
24
 
23
25
  ## Usage
26
+ ### Basics
27
+ ```ruby
28
+ # include JsonApiable in your Base API controller
29
+ class API::BaseController < ActionController::Base
30
+ # By including JsonApiable, you get the following before/after actions in your controllers:
31
+ #
32
+ # before_action :ensure_jsonapi_content_type - Ensure correct Content-Type (application/vnd.api+json) is set in
33
+ # the request, and return error othewrise
34
+ # before_action :ensure_jsonapi_valid_query_params - Ensure only valid query parameters are used
35
+ # before_action :parse_jsonapi_pagination - Parse "?page:{number:1, size:25}" query hash, set defaults and
36
+ # return errors when invalid values received
37
+ # Read more: https://jsonapi.org/format/#fetching-pagination
38
+ # before_action :parse_jsonapi_include - Parse "?include=posts.author" include directives .
39
+ # Read more: https://jsonapi.org/format/#fetching-includes
40
+ # after_action :set_jsonapi_content_type - Ensure correct Content-Type (application/vnd.api+json) is set
41
+ # in the response
42
+
43
+ # By including JsonApiable, you get the following exceptions handled automatically:
44
+ # rescue_from ArgumentError
45
+ # rescue_from ActionController::UnpermittedParameters
46
+ # rescue_from MalformedRequestError
47
+ # rescue_from UnprocessableEntityError
48
+ # rescue_from ActiveRecord::RecordNotFound
49
+ include JsonApiable
50
+ end
51
+
52
+ class API::PostsController < API::BaseController
53
+
54
+ # GET /v1/posts
55
+ def index
56
+ # pass page and include info to your logic
57
+ posts = GetPostsService.call(jsonapi_page, jsonapi_include)
58
+ # some other gem, such as fast_jsonapi is assumed to produce the json:api output
59
+ render json: posts
60
+ end
61
+
62
+ # PATCH /v1/posts/123/update
63
+ # { "data":
64
+ # { "type": "post",
65
+ # "attributes": {
66
+ # "title": "My New Title"
67
+ # },
68
+ # "relationships": {
69
+ # "author": {
70
+ # "data": {
71
+ # "type": "user",
72
+ # "id": "4528"
73
+ # },
74
+ # "comments": {
75
+ # "data": [
76
+ # { "type": "comment", "id": "1489" },
77
+ # { "type": "comment", "id": "1490" }
78
+ # ]
79
+ # }
80
+ # }
81
+ # }
82
+ # }
83
+ # }
84
+ def update
85
+ @post = Post.find(params[:id])
86
+
87
+ # turn relationships into Rails associations and assign them together with attributes
88
+ # as you would normally do in Rails
89
+ #
90
+ # jsonapi_assign_params =>
91
+ # { "title"=>"My New Title",
92
+ # "author_id" => 4528,
93
+ # "comments_attributes"=>{
94
+ # "0"=>{"id"=>"1489", "_destroy"=>"false"},
95
+ # "1"=>{"id"=>"1490", "_destroy"=>"false"}},
96
+ # "comment_ids"=>["1489", "1490"],
97
+ #
98
+ # }
99
+ @post.update_attributes!(jsonapi_assign_params)
100
+ render json: @user
101
+ end
102
+
103
+ def create
104
+ # use jsonapi_attribute_present? to quickly test presence of specific attributes
105
+ raise UnprocessableEntityError, 'No title!' unless jsonapi_attribute_present?(:title)
106
+ # use jsonapi_attribute_value to get attribute values. If non-existent, nil would be returned
107
+ @title = jsonapi_attribute_value(:title)
108
+ # exclude 'author' attribute from assign params, for example because it's a separate table on the DB level)
109
+ @author_name = jsonapi_exclude_attribute(:author_name)
110
+ # exclude 'comments' relationship from assign params, for example because we want to filter which ones are added to post
111
+ @comments_hash = jsonapi_exclude_relationship(:comments)
112
+ do_some_logc_with_excluded_params
113
+ # jsonapi_assign_params wouldn't include 'author' attribute and 'comments' relationship
114
+ User.create!(jsonapi_assign_params)
115
+ end
116
+
117
+ protected
118
+
119
+ # declare which attributes should be allowed to be assigned. Complex attributes are allowed
120
+ def jsonapi_allowed_attributes
121
+ [:title,
122
+ :body,
123
+ dates: %i[first_drafted published last_edited]]
124
+ end
125
+
126
+ # declare which relationships should be allowed to be assigned
127
+ def jsonapi_allowed_relationships
128
+ %i[comments contributors]
129
+ end
130
+
131
+
132
+
133
+ end
134
+ ````
135
+ ### Configuration
136
+ Add an initializer to your app with the following config block:
137
+ ```ruby
138
+ JsonApiable.configure do |config|
139
+ # white-list query params that should be allowed
140
+ config.valid_query_params = %w[ id access_token user_id organization_id ]
141
+
142
+ # by default, error is returned if the request Content-Type isn't valid JSON-API. Override the behaviour by using this block:
143
+ config.supported_media_type_proc = proc do |request|
144
+ request.content_type == JsonApiable::JSONAPI_CONTENT_TYPE || request.headers['My-Special-Header'].present?
145
+ end
146
+
147
+ # by default, ActiveRecord::RecordNotFound is caught by JsonApiable and turned into an error response. If your backend raises a different class of exception, set it here
148
+ config.not_found_exception_class = MyExceptionClass
149
+
150
+ end
151
+ ```
24
152
 
25
- TODO: Write usage instructions here
153
+ ### Limitations
154
+ - `has_one` associations are currently not supported by `jsonapi_assign_params`. So if the updated resource
155
+ contains an association whose foreign key exists in
156
+ - complex attributes currently don't work
26
157
 
27
158
  ## Development
28
159
 
data/json_apiable.gemspec CHANGED
@@ -36,6 +36,6 @@ Gem::Specification.new do |spec|
36
36
  spec.add_development_dependency 'rails'
37
37
  spec.add_development_dependency 'rails-controller-testing'
38
38
  spec.add_development_dependency 'rake', '~> 10.0'
39
- spec.add_development_dependency 'rspec-rails'
39
+ spec.add_development_dependency 'rspec-rails', '~> 3.9'
40
40
  spec.add_development_dependency 'sqlite3'
41
41
  end
@@ -8,7 +8,7 @@ module JsonApiable
8
8
  MAX_PAGE_SIZE = 214_748_364
9
9
 
10
10
  def initialize
11
- @valid_query_params = %w[id access_token filter include page]
11
+ @valid_query_params = %w[id user_id access_token filter include page]
12
12
  @supported_media_type_proc = nil
13
13
  @not_found_exception_class = ActiveRecord::RecordNotFound
14
14
  @page_size = DEFAULT_PAGE_SIZE
@@ -9,12 +9,12 @@ module JsonApiable
9
9
  :jsonapi_exclude_attributes, :jsonapi_exclude_relationships
10
10
 
11
11
  included do
12
- before_action :ensure_content_type
13
- before_action :ensure_valid_query_params
14
- before_action :parse_pagination
15
- before_action :parse_include
12
+ before_action :ensure_jsonapi_content_type
13
+ before_action :ensure_jsonapi_valid_query_params
14
+ before_action :parse_jsonapi_pagination
15
+ before_action :parse_jsonapi_include
16
16
 
17
- after_action :set_content_type
17
+ after_action :set_jsonapi_content_type
18
18
 
19
19
  rescue_from ArgumentError, with: :respond_to_bad_argument
20
20
  rescue_from ActionController::UnpermittedParameters, with: :respond_to_bad_argument
@@ -46,6 +46,10 @@ module JsonApiable
46
46
  jsonapi_build_params.dig(:data, :attributes, attrib_key).present?
47
47
  end
48
48
 
49
+ def jsonapi_attribute_value(attrib_key)
50
+ jsonapi_build_params.dig(:data, :attributes, attrib_key)
51
+ end
52
+
49
53
  def jsonapi_assign_params
50
54
  @jsonapi_assign_params ||= ParamsParser.parse_body_params(request,
51
55
  jsonapi_build_params,
@@ -87,11 +91,11 @@ module JsonApiable
87
91
  %i[]
88
92
  end
89
93
 
90
- def ensure_content_type
94
+ def ensure_jsonapi_content_type
91
95
  respond_to_unsupported_media_type unless supported_media_type?
92
96
  end
93
97
 
94
- def ensure_valid_query_params
98
+ def ensure_jsonapi_valid_query_params
95
99
  invalid_params = request.query_parameters.keys.reject { |k| JsonApiable.configuration.valid_query_params.include?(k) }
96
100
  respond_to_bad_argument(invalid_params.first) if invalid_params.present?
97
101
  end
@@ -104,15 +108,15 @@ module JsonApiable
104
108
  end
105
109
  end
106
110
 
107
- def set_content_type
111
+ def set_jsonapi_content_type
108
112
  response.headers['Content-Type'] = JSONAPI_CONTENT_TYPE
109
113
  end
110
114
 
111
- def parse_pagination
115
+ def parse_jsonapi_pagination
112
116
  @jsonapi_page = PaginationParser.parse_pagination!(query_params, jsonapi_default_page_size)
113
117
  end
114
118
 
115
- def parse_include
119
+ def parse_jsonapi_include
116
120
  @jsonapi_include = query_params[:include].presence&.gsub(/ /, '')&.split(',')&.map(&:to_sym).to_a
117
121
  end
118
122
 
@@ -1,3 +1,3 @@
1
1
  module JsonApiable
2
- VERSION = "0.1.0"
2
+ VERSION = "0.2.0"
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: json_apiable
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mike Polischuk
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2020-01-06 00:00:00.000000000 Z
11
+ date: 2020-01-13 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord
@@ -168,16 +168,16 @@ dependencies:
168
168
  name: rspec-rails
169
169
  requirement: !ruby/object:Gem::Requirement
170
170
  requirements:
171
- - - ">="
171
+ - - "~>"
172
172
  - !ruby/object:Gem::Version
173
- version: '0'
173
+ version: '3.9'
174
174
  type: :development
175
175
  prerelease: false
176
176
  version_requirements: !ruby/object:Gem::Requirement
177
177
  requirements:
178
- - - ">="
178
+ - - "~>"
179
179
  - !ruby/object:Gem::Version
180
- version: '0'
180
+ version: '3.9'
181
181
  - !ruby/object:Gem::Dependency
182
182
  name: sqlite3
183
183
  requirement: !ruby/object:Gem::Requirement