json_apiable 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
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